From 17598816185bd8aa9840e142997bda6729841631 Mon Sep 17 00:00:00 2001
From: Recolic Keghart <root@recolic.net>
Date: Mon, 8 Apr 2019 19:52:33 -0700
Subject: [PATCH] global_forward pass

---
 .../chocopy/common/analysis/SymbolTable.java  |  4 ++
 .../java/chocopy/pa2/StudentAnalysis.java     | 18 +++++-
 src/main/java/chocopy/pa2/TypeChecker.java    | 64 ++++++++++++-------
 3 files changed, 61 insertions(+), 25 deletions(-)

diff --git a/src/main/java/chocopy/common/analysis/SymbolTable.java b/src/main/java/chocopy/common/analysis/SymbolTable.java
index 41b9ab0..e9bc757 100644
--- a/src/main/java/chocopy/common/analysis/SymbolTable.java
+++ b/src/main/java/chocopy/common/analysis/SymbolTable.java
@@ -47,6 +47,10 @@ public class SymbolTable<T> {
         return this;
     }
 
+    public void overwrite_put(String k, T v) {
+        tab.put(k, v);
+    }
+
     /** Returns whether NAME has a mapping in this region (ignoring
      *  enclosing regions. */
     public boolean declares(String name) {
diff --git a/src/main/java/chocopy/pa2/StudentAnalysis.java b/src/main/java/chocopy/pa2/StudentAnalysis.java
index b8e6f26..26d710c 100644
--- a/src/main/java/chocopy/pa2/StudentAnalysis.java
+++ b/src/main/java/chocopy/pa2/StudentAnalysis.java
@@ -22,11 +22,25 @@ public class StudentAnalysis {
         //    declarationAnalyzer.getGlobals();
         SymbolTable<SymbolType> globalSym = new SymbolTable<>();
 
-        //if (!program.hasErrors()) {
         // TODO: I should run this part again and again, until symbolTable convergences.
         TypeChecker symBuilder = new TypeChecker(globalSym, program.errors, true);
         program.dispatch(symBuilder);
-        //}
+        // program.dispatch(symBuilder); // Twice run to update these use-before-decl globalDecl/nonlocalDecl symTable entry.
+        // note: thanks to the silly grammar, it doesn't allow type-deduction.
+        //       so we can support use-before-decl by only one extra run, rather  than many many times.
+        //       now globalDecl and nonLocalDecl relies to the second run.
+        //       but we can just allow globalDecl and nonLocalDecl to update symTable on the typeCheckerRun below!
+        //       what a silly design!
+        // def fuck():
+        //     def shit():
+        //         def func():
+        //             nonlocal c
+        //             # ...
+        //         nonlocal b
+        //         c:decltype(b) = None
+        //     nonlocal a
+        //     b:decltype(a) = None
+
 
         if(!program.hasErrors()) {
             TypeChecker next_time = new TypeChecker(symBuilder.getGlobals(), program.errors, false);
diff --git a/src/main/java/chocopy/pa2/TypeChecker.java b/src/main/java/chocopy/pa2/TypeChecker.java
index 19a64f5..1a24a45 100644
--- a/src/main/java/chocopy/pa2/TypeChecker.java
+++ b/src/main/java/chocopy/pa2/TypeChecker.java
@@ -24,7 +24,8 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
     /** Collector for errors. */
     private Errors errors;
 
-    private boolean isFirstRun;
+    private boolean isBuildingSym;
+    public boolean buildingSymDone;
 
     /** Creates a type checker using GLOBALSYMBOLS for the initial global
      *  symbol table and ERRORS0 to receive semantic errors. */
@@ -32,7 +33,8 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
         sym = globalSymbols;
         globals = sym;
         errors = errors0;
-        isFirstRun = firstRun;
+        isBuildingSym = firstRun;
+        buildingSymDone = true;
     }
     public SymbolTable<SymbolType> getGlobals() {
         return globals;
@@ -42,15 +44,17 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
      *  The message is constructed with MESSAGE and ARGS as for
      *  String.format. */
     private void err(Node node, String message, Object... args) {
-        if(!isFirstRun)
+        if(!isBuildingSym)
             errors.semError(node, message, args);
     }
 
     ///////////////////////////// Program //////////////////////////////////////
     @Override
     public SymbolType analyze(Program program) {
-        if(isFirstRun)
+        if(isBuildingSym) {
             sym.put("object", OBJECT_TYPE);
+            sym.put("print", new FuncType(new ArrayList<ValueType>(Arrays.asList(OBJECT_TYPE)), NONE_TYPE));
+        }
 
         for (Declaration decl : program.declarations) {
             String name = decl.getIdentifier().name;
@@ -64,11 +68,11 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
             }
 
             // TODO: DO NOT throw on duplicate id. generate a compiler error.
-            if(isFirstRun)
+            if(isBuildingSym)
                 sym.put(name, type);
         }
 
-        if(isFirstRun)
+        if(isBuildingSym)
             // firstRun: symbolTable and memberMap is not useable now. STOP dispatching to avoid crash.
             return null;
         for (Stmt stmt : program.statements) {
@@ -80,7 +84,7 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
     ///////////////////////////// Def&decl s //////////////////////////////////////
     @Override
     public SymbolType analyze(VarDef varDef) {
-        if(isFirstRun) {
+        if(isBuildingSym) {
             // firstRun: symbolTable and memberMap is not useable now. STOP dispatching to avoid crash.
             return resolveTypeAnnotation(varDef.var.type);
         }
@@ -93,9 +97,9 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
 
     @Override
     public SymbolType analyze(ClassDef classDef) {
-        if(isFirstRun) {
-            assert classDef.memberMap == null;
-            classDef.memberMap = new HashMap<>(); // clearing may prevent symTable from convergence forever.
+        if(isBuildingSym) {
+            if(classDef.memberMap == null)
+                classDef.memberMap = new HashMap<>(); // clearing may prevent symTable from convergence forever.
         }
         assert classDef.memberMap != null;
 
@@ -128,7 +132,7 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
         for(Declaration decl : classDef.declarations) {
             String name = decl.getIdentifier().name;
             SymbolType type = decl.dispatch(this);
-            if(isFirstRun)
+            if(isBuildingSym)
                 classDef.memberMap.put(name, type);
         }
         System.out.println("debug: self_type=" + result_type.toString() + ", where symbolMap=" + result_type.memberMap);
@@ -139,9 +143,10 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
 
     @Override
     public SymbolType analyze(FuncDef funcDef) {
-        if(isFirstRun) {
-            assert funcDef.symTable == null;
-            sym = funcDef.symTable = new SymbolTable<SymbolType>(sym);
+        if(isBuildingSym) {
+            if(funcDef.symTable == null)
+                funcDef.symTable = new SymbolTable<SymbolType>(sym);
+            sym = funcDef.symTable;
         }
         else
             sym = funcDef.symTable;
@@ -154,7 +159,7 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
             ValueType type = resolveTypeAnnotation(param.type);
             //if(typeIsUserDefinedClass(type))
             //    type = (ValueType) sym.get(((ClassValueType) type).className());
-            if(isFirstRun)
+            if(isBuildingSym)
                 sym.put(name, type);
             args.add(type);
         }
@@ -162,11 +167,11 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
         for(Declaration decl : funcDef.declarations) {
             String name = decl.getIdentifier().name;
             SymbolType type = decl.dispatch(this);
-            if(isFirstRun)
+            if(isBuildingSym)
                 sym.put(name, type);
         }
 
-        if(!isFirstRun) {
+        if(!isBuildingSym) {
             // firstRun: symbolTable and memberMap is not useable now. STOP dispatching to avoid crash.
             for(Stmt stmt : funcDef.statements) {
                 stmt.dispatch(this);
@@ -174,7 +179,7 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
         }
 
         FuncType funcT = new FuncType(args, resolveTypeAnnotation(funcDef.returnType));
-        if(isFirstRun)
+        if(isBuildingSym)
             sym.put(funcDef.name.name, funcT); // this funcdef should be add to both parentSym and localSym to support recursive func.
 
         // TA don't like it. OK I won't dispatch this id...
@@ -191,11 +196,17 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
     public SymbolType analyze(GlobalDecl globalDecl) {
         String name = globalDecl.variable.name;
         SymbolType T = globals.get(name);
-        if(T == null)
+        System.out.println("DEBUG< GOL< " + isBuildingSym + " buildingSym, name=" + name + ", Type=" + T);
+        if(T == null) {
+            if(isBuildingSym)
+                return OBJECT_TYPE; // workaround for fucking use-before-decl
             errors.semError(globalDecl, "global id '" + name + "' not found in global scope..");
+        }
         if(sym == globals) // ref equal
             errors.semError(globalDecl, "global declaration '" + name + "' not allowed in global scope.");
-        //sym.put(name, T);
+        if(!isBuildingSym)
+            // dirty! see comment in StudentAnalysis.java
+            sym.overwrite_put(name, T);
         return T;
     }
 
@@ -203,10 +214,16 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
     public SymbolType analyze(NonLocalDecl nonLocalDecl) {
         String name = nonLocalDecl.variable.name;
         SymbolType T = sym.get(name); // auto-iterate through the tree.
-        if(T == null)
+        if(T == null) {
+            if(isBuildingSym)
+                return OBJECT_TYPE;
             errors.semError(nonLocalDecl, "nonlocal id '" + name + "' not found in parent scope..");
+        }
         if(sym == globals)
             errors.semError(nonLocalDecl, "nonlocal declaration '" + name + "' not allowed in global scope.");
+        if(!isBuildingSym)
+            // dirty! see comment in StudentAnalysis.java
+            sym.overwrite_put(name, T);
         return T;
     }
 
@@ -414,7 +431,7 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
             for(Expr arg : node.args) {
                 args_type.add(arg.dispatch(this));
             }
-            if(!fType.parameters.equals(args_type)) {
+            if(!matchArgList(args_type, fType.parameters)) {
                 err(node, "function parameter type list mismatch: " + node.function.name + ". Expected " + fType.parameters.toString() + ", got " + args_type.toString());
             }
             return node.setInferredType(fType.returnType);
@@ -511,7 +528,7 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
             return NONE_TYPE;
         }
         //System.out.println("manual lookup: memberMap=" + ((ClassValueType)sym.get(objType.toString())).memberMap );
-        System.out.println("debug: isFirstRun=" + isFirstRun + ", memberMap=" + objType.memberMap + ", objtype=" + objType.toString());
+        System.out.println("debug: isBuildingSym=" + isBuildingSym + ", memberMap=" + objType.memberMap + ", objtype=" + objType.toString());
         // Won't work.
         SymbolType resultType = objType.memberMap.get(node.member.name);
         if(resultType == null) {
@@ -603,6 +620,7 @@ public class TypeChecker extends AbstractNodeAnalyzer<SymbolType> {
         return new ListValueType(elementType);
     }
 
+    ///////////////////////////// helpers ///////////////////////////////
     private boolean typeConvertible(SymbolType from, SymbolType to) {
         if(from == null || to == null)
             throw new RuntimeException("debug: SymbolType can not be null");
-- 
GitLab