SemAn - LVal

This commit is contained in:
Gašper Dobrovoljc 2025-04-17 10:01:15 +02:00
parent b688385835
commit e868f3030c
No known key found for this signature in database
GPG Key ID: 0E7E037018CFA5A5

605
src/pins25/phase/SemAn.java Normal file
View File

@ -0,0 +1,605 @@
package pins25.phase;
import java.util.*;
import pins25.common.*;
/**
* Semanticni analizator.
*/
public class SemAn {
@SuppressWarnings({"doclint:missing"})
public SemAn() {
throw new Report.InternalError();
}
/**
* Abstraktno sintaksno drevo z dodanimi atributi semanticne analize.
* <p>
* Dodani atributi:
* <ol>
* <li>({@link Abstr}) lokacija kode, ki pripada posameznemu vozliscu;</li>
* <li>({@link SemAn}) definicija uporabljenega imena;</li>
* <li>({@link SemAn}) ali je dani izraz levi izraz.</li>
* </ol>
*/
public static class AttrAST extends Abstr.AttrAST {
/**
* Atribut: definicija uporabljenega imena.
*/
public final Map<AST.NameExpr, AST.Def> attrDef;
/**
* Atribut: ali je dani izraz levi izraz.
*/
public final Map<AST.Expr, Boolean> attrLVal;
/**
* Ustvari novo abstraktno sintaksno drevo z dodanim atributi semanticne
* analize.
*
* @param attrAST Abstraktno sintaksno drevo z dodanimi atributi abstraktne
* sintakse.
* @param attrDef Atribut: definicija uporabljenega imena.
* @param attrLVal Atribut: ali je dani izraz levi izraz.
*/
public AttrAST(final Abstr.AttrAST attrAST, final Map<AST.NameExpr, AST.Def> attrDef,
final Map<AST.Expr, Boolean> attrLVal) {
super(attrAST);
this.attrDef = attrDef;
this.attrLVal = attrLVal;
}
/**
* Ustvari novo abstraktno sintaksno drevo z dodanimi atributi semanticne
* analize.
*
* @param attrAST Abstraktno sintaksno drevo z dodanimi atributi semanticne
* analize.
*/
public AttrAST(final AttrAST attrAST) {
super(attrAST);
this.attrDef = attrAST.attrDef;
this.attrLVal = attrAST.attrLVal;
}
@Override
public String head(final AST.Node node, final boolean highlighted) {
final StringBuffer head = new StringBuffer();
head.append(super.head(node, false));
switch (node) {
case final AST.NameExpr nameExpr:
final AST.Def def = attrDef.get(nameExpr);
if (def == null)
break;
final Report.Locatable loc = attrLoc.get(def);
if (loc == null)
break;
head.append((" ") + (highlighted ? "\033[31m" : "") + "def@" + loc.location().toString()
+ (highlighted ? "\033[30m" : ""));
break;
default:
break;
}
switch (node) {
case final AST.Expr expr:
final Boolean lval = attrLVal.get(expr);
if (lval == null)
break;
if (lval)
head.append((" ") + (highlighted ? "\033[31m" : "") + "lval" + (highlighted ? "\033[30m" : ""));
break;
default:
break;
}
return head.toString();
}
}
/**
* Opravi semanticno analizo.
*
* @param abstrAttrAST Abstraktno sintaksno drevo z dodanimi atributi abstraktne
* sintakse.
* @return Abstraktno sintaksno drevo z dodanimi atributi semanticne analize.
*/
public static AttrAST analyze(Abstr.AttrAST abstrAttrAST) {
AttrAST attrAST = new AttrAST(abstrAttrAST, new HashMap<AST.NameExpr, AST.Def>(),
new HashMap<AST.Expr, Boolean>());
attrAST = new NameResolver(attrAST).resolve();
attrAST = new TypeResolver(attrAST).resolve();
attrAST = new LValResolver(attrAST).resolve();
return attrAST;
}
/**
* Razresevanje imen.
*/
private static class NameResolver {
/**
* Abstraktno sintaksno drevo z dodanimi atributi semanticne analize.
*/
private final AttrAST attrAST;
/**
* Ustvari nov razresevalnik imen.
*
* @param attrAST Abstraktno sintaksno drevo z dodanimi atributi semanticne
* analize.
*/
public NameResolver(final AttrAST attrAST) {
this.attrAST = attrAST;
}
/**
* Sprozi razresevanje imen.
*
* @return Abstraktno sintaksno drevo z dodanimi atributi semanticne analize
* ({@link AttrAST#attrDef} izracunan in nespremenljiv).
*/
public AttrAST resolve() {
attrAST.ast.accept(new ResolverVisitor(), null);
return new AttrAST(attrAST, Collections.unmodifiableMap(attrAST.attrDef), attrAST.attrLVal);
}
/**
* Simbolna tabela, ki se uporablja med razresevanjem imen.
*/
private final SymbolTable symbolTable = new SymbolTable();
/**
* Simbolna tabela.
*/
private class SymbolTable {
/**
* Definicija v trenutnem dosega na dani staticni globini.
*
* @param depth Staticna globina definicije.
* @param def Definicija.
*/
private record ScopedDef(int depth, AST.Def def) {
}
/**
* Preslikava imena v seznam definicij tega imena na razlicnih staticnih
* globinah.
*/
private final HashMap<String, LinkedList<ScopedDef>> namesToDefs;
/**
* Seznami imen definiranih na posameznih staticnih globinah.
*/
private final LinkedList<LinkedList<String>> namesToDefsByDepth;
/**
* Trenutna staticna globina.
*/
private int depth;
/**
* Ustvari novo simbolno tabelo.
*/
public SymbolTable() {
namesToDefs = new HashMap<String, LinkedList<ScopedDef>>();
namesToDefsByDepth = new LinkedList<LinkedList<String>>();
depth = -1;
newScope();
}
/**
* Pripravi simbolno tabelo za vstavljanje definicij imen v novem dosegu.
*/
public void newScope() {
depth++;
namesToDefsByDepth.addFirst(new LinkedList<String>());
}
/**
* Razveljavi trenutni doseg.
*/
public void oldScope() {
for (final String name : namesToDefsByDepth.getFirst()) {
final LinkedList<ScopedDef> defsOfName = namesToDefs.get(name);
if (defsOfName.size() == 1)
namesToDefs.remove(name);
else
defsOfName.removeFirst();
}
namesToDefsByDepth.removeFirst();
depth--;
}
/**
* Vstavi novo definicijo imena v trenutni doseg.
*
* @param def Definicija imena.
* @return {@code true}, ce je vstavitev mozna (pred to vstavitvijo v tem dosegu
* se ni definicije tega imena), ali {@code false}, ce vstavitev ni
* mozna (pred to vstavitvijo je v tem dosegu ze definicija tega imena).
*/
public boolean ins(final AST.Def def) {
final LinkedList<ScopedDef> defsOfOldName = namesToDefs.get(def.name);
if (defsOfOldName == null) {
final LinkedList<ScopedDef> defsOfNewName = new LinkedList<ScopedDef>();
defsOfNewName.addFirst(new ScopedDef(depth, def));
namesToDefs.put(def.name, defsOfNewName);
namesToDefsByDepth.getFirst().add(def.name);
return true;
} else {
if (defsOfOldName.getFirst().depth == depth)
return false;
defsOfOldName.addFirst(new ScopedDef(depth, def));
namesToDefsByDepth.getFirst().add(def.name);
return true;
}
}
/**
* Vrne definicijo imena.
*
* @param name Ime.
* @return Definicija imena ali {@code null}, ce ime ni definirano v tem in
* obsegajocih dosegih.
*/
public AST.Def fnd(final String name) {
final LinkedList<ScopedDef> defsOfName = namesToDefs.get(name);
return (defsOfName == null) ? null : defsOfName.getFirst().def();
}
}
/**
* Obiskovalec za razresevanje imen.
*/
private class ResolverVisitor implements AST.FullVisitor<Object, ResolverVisitor.Pass> {
@SuppressWarnings({"doclint:missing"})
public ResolverVisitor() {
}
/**
* Dva preleta abstraktnega sintaksnega drevesa med razresevanjem imen.
* <p>
* Med prvim preletom se obdelajo definicije funkcij in spremenljivk (ne pa tudi
* telesa funkcij), med drugim preletom se obdela vse ostalo (tudi telesa
* funkcij). Oba preleta se prepletata in se razcepita le pri obdelavi zaporedja
* vozlisc.
*/
private enum Pass {
/**
* Prelet definicij funkcij in spremenljivk.
*/
Defs,
/**
* Prelet vsega razen definicij funkcij in spremenljivk.
*/
Rest,
}
@Override
public Object visit(final AST.Nodes<? extends AST.Node> nodes, final Pass pass) {
for (final AST.Node node : nodes) {
switch (node) {
case final AST.FunDef funDef:
funDef.accept(this, Pass.Defs);
break;
case final AST.VarDef varDef:
varDef.accept(this, Pass.Defs);
break;
default:
break;
}
}
for (final AST.Node node : nodes) {
switch (node) {
case final AST.FunDef funDef:
funDef.accept(this, Pass.Rest);
break;
case final AST.VarDef varDef:
varDef.accept(this, Pass.Rest);
break;
default:
node.accept(this, null);
break;
}
}
return null;
}
@Override
public Object visit(final AST.FunDef funDef, final Pass pass) {
switch (pass) {
case Defs: {
if (!symbolTable.ins(funDef))
throw new Report.Error(attrAST.attrLoc.get(funDef),
"Illegal definition of function '" + funDef.name + "'.");
break;
}
case Rest: {
symbolTable.newScope();
funDef.pars.accept(this, null);
funDef.stmts.accept(this, null);
symbolTable.oldScope();
break;
}
default:
throw new Report.InternalError();
}
return null;
}
@Override
public Object visit(final AST.ParDef parDef, final Pass pass) {
if (!symbolTable.ins(parDef))
throw new Report.Error(attrAST.attrLoc.get(parDef),
"Illegal definition of parameter '" + parDef.name + "'.");
return null;
}
@Override
public Object visit(final AST.VarDef varDef, final Pass pass) {
switch (pass) {
case Defs: {
if (!symbolTable.ins(varDef))
throw new Report.Error(attrAST.attrLoc.get(varDef),
"Illegal definition of variable '" + varDef.name + "'.");
varDef.inits.accept(this, null);
break;
}
case Rest: {
break;
}
default:
throw new Report.InternalError();
}
return null;
}
@Override
public Object visit(final AST.LetStmt letStmt, final Pass pass) {
symbolTable.newScope();
letStmt.defs.accept(this, null);
letStmt.stmts.accept(this, null);
symbolTable.oldScope();
return null;
}
@Override
public Object visit(final AST.VarExpr varExpr, final Pass pass) {
final AST.Def def = symbolTable.fnd(varExpr.name);
if (def == null)
throw new Report.Error(attrAST.attrLoc.get(varExpr), "Undefined name '" + varExpr.name + "'.");
attrAST.attrDef.put(varExpr, def);
return null;
}
@Override
public Object visit(final AST.CallExpr callExpr, final Pass pass) {
final AST.Def def = symbolTable.fnd(callExpr.name);
if (def == null)
throw new Report.Error(attrAST.attrLoc.get(callExpr), "Undefined name '" + callExpr.name + "'.");
attrAST.attrDef.put(callExpr, def);
callExpr.args.accept(this, null);
return null;
}
}
}
/**
* Preverjanje tipov.
*/
private static class TypeResolver {
/**
* Abstraktno sintaksno drevo z dodanimi atributi semanticne analize.
*/
private final AttrAST attrAST;
/**
* Ustvari nov razresevalnik imen.
*
* @param attrAST Abstraktno sintaksno drevo z dodanimi atributi semanticne
* analize
*/
public TypeResolver(final AttrAST attrAST) {
this.attrAST = attrAST;
}
/**
* Sprozi razresevanje imen.
*
* @return Abstraktno sintaksno drevo z dodanimi atributi semanticne analize.
*/
public AttrAST resolve() {
attrAST.ast.accept(new ResolverVisitor(), null);
return new AttrAST(attrAST, attrAST.attrDef, attrAST.attrLVal);
}
/**
* Obiskovalec za preverjanje tipov.
*/
private class ResolverVisitor implements AST.FullVisitor<Object, ResolverVisitor.Pass> {
@SuppressWarnings({"doclint:missing"})
public ResolverVisitor() {
}
/**
* Dva preleta abstraktnega sintaksnega drevesa med razresevanjem imen.
* <p>
* Med prvim preletom se obdelajo definicije funkcij in spremenljivk (ne pa tudi
* telesa funkcij), med drugim preletom se obdela vse ostalo (tudi telesa
* funkcij). Oba preleta se prepletata in se razcepita le pri obdelavi zaporedja
* vozlisc.
*/
private enum Pass {
/**
* Prelet definicij funkcij in spremenljivk.
*/
Defs,
/**
* Prelet vsega razen definicij funkcij in spremenljivk.
*/
Rest,
}
@Override
public Object visit(final AST.FunDef funDef, final Pass pass) {
funDef.pars.accept(this, pass);
funDef.stmts.accept(this, pass);
if (funDef.stmts.size() != 0) {
AST.Stmt lastStmt = funDef.stmts.getAll().getLast();
loop:
while (true) {
switch (lastStmt) {
case AST.ExprStmt exprStmt:
break loop;
case AST.LetStmt letStmt:
if (letStmt.stmts.size() != 0) {
lastStmt = letStmt.stmts.getAll().getLast();
} else
throw new Report.Error(attrAST.attrLoc.get(funDef),
"Function '" + funDef.name + "' does not return any value.");
break;
default:
throw new Report.Error(attrAST.attrLoc.get(funDef),
"Function '" + funDef.name + "' does not return any value.");
}
}
}
return null;
}
@Override
public Object visit(final AST.VarExpr varExpr, final Pass pass) {
switch (attrAST.attrDef.get(varExpr)) {
case final AST.VarDef varDef:
break;
case final AST.ParDef parDef:
break;
default:
throw new Report.Error(attrAST.attrLoc.get(varExpr),
"'" + varExpr.name + "' is not a variable or a parameter.");
}
return null;
}
@Override
public Object visit(final AST.CallExpr callExpr, final Pass pass) {
switch (attrAST.attrDef.get(callExpr)) {
case final AST.FunDef funDef: {
if (funDef.pars.size() != callExpr.args.size())
throw new Report.Error(attrAST.attrLoc.get(callExpr),
"Illegal number of arguments in a call of function '" + callExpr.name + "'.");
break;
}
default:
throw new Report.Error(attrAST.attrLoc.get(callExpr), "'" + callExpr.name + "' is not a function.");
}
callExpr.args.accept(this, null);
return null;
}
}
}
/**
* Preverjanje levih vrednosti.
*/
private static class LValResolver {
/**
* Abstraktno sintaksno drevo z dodanimi atributi semanticne analize.
*/
private final AttrAST attrAST;
/**
* Ustvari nov razresevalnik levih vrednosti.
*
* @param attrAST Abstraktno sintaksno drevo z dodanimi atributi semanticne
* analize.
*/
public LValResolver(final AttrAST attrAST) {
this.attrAST = attrAST;
}
/**
* Sprozi preverjanje levih vrednosti.
*
* @return Abstraktno sintaksno drevo z dodanimi atributi semanticne analize
* ({@link AttrAST#attrLVal} izracunan in nespremenljiv).
*/
public AttrAST resolve() {
attrAST.ast.accept(new ResolverVisitor(), null);
return new AttrAST(attrAST, attrAST.attrDef, Collections.unmodifiableMap(attrAST.attrLVal));
}
/**
* Obiskovalec za preverjanje levih vrednosti.
*/
private class ResolverVisitor implements AST.FullVisitor<Object, Object> {
@Override
public Object visit(AST.AssignStmt assignStmt, Object arg) {
switch (assignStmt.dstExpr) {
case AST.VarExpr varExpr:
attrAST.attrLVal.put(varExpr, true);
return null;
case AST.UnExpr unExpr:
if (unExpr.oper == AST.UnExpr.Oper.VALUEAT) {
attrAST.attrLVal.put(unExpr, true);
return null;
}
default:
}
throw new Report.Error(attrAST.attrLoc.get(assignStmt.dstExpr), "Illegal assignment target");
}
}
}
// --- ZAGON ---
/**
* Zagon semanticne analize kot samostojnega programa.
*
* @param cmdLineArgs Argumenti v ukazni vrstici.
*/
public static void main(final String[] cmdLineArgs) {
System.out.println("This is PINS'25 compiler (semantic analysis):");
try {
if (cmdLineArgs.length == 0)
throw new Report.Error("No source file specified in the command line.");
if (cmdLineArgs.length > 1)
Report.warning("Unused arguments in the command line.");
try (SynAn synAn = new SynAn(cmdLineArgs[0])) {
// abstraktna sintaksa:
final Abstr.AttrAST abstrAttrAST = Abstr.constructAST(synAn);
// semanticna analiza:
final SemAn.AttrAST semanAttrAST = SemAn.analyze(abstrAttrAST);
(new AST.Logger(semanAttrAST)).log();
}
// Upajmo, da kdaj pridemo to te tocke.
// A zavedajmo se sledecega:
// 1. Prevod je zaradi napak v programu lahko napacen :-o
// 2. Izvorni program se zdalec ni tisto, kar je programer hotel, da bi bil ;-)
Report.info("Done.");
} catch (Report.Error error) {
// Izpis opisa napake.
System.err.println(error.getMessage());
System.exit(1);
}
}
}