SemAn - LVal
This commit is contained in:
parent
b688385835
commit
e868f3030c
605
src/pins25/phase/SemAn.java
Normal file
605
src/pins25/phase/SemAn.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user