diff --git a/build.gradle b/build.gradle index e5ec2e8..16c1d83 100644 --- a/build.gradle +++ b/build.gradle @@ -77,3 +77,7 @@ javadoc { options.addBooleanOption('-enable-preview', true) options.showFromPrivate() } + +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" +} \ No newline at end of file diff --git a/report-PA1-A.md b/report-PA1-A.md new file mode 100644 index 0000000..7da57b1 --- /dev/null +++ b/report-PA1-A.md @@ -0,0 +1,57 @@ +# PA1-A 实验报告 + +计科70 徐明宽 2017011310 + +## 工作内容 + +### 抽象类 + +我修改了Decaf.jflex, Decaf.jacc, Tokens.java, JaccParser.java等文件以加入`abstract`关键字(以及后面要用到的`var`、`fun`关键字与`=>`操作符)(后来为了后面的PA1-B阶段又在Decaf.spec和LLParser.java中加入了这些关键字与操作符),并照着给出的EBNF语法规范修改了Decaf.jacc。在Tree.java中,由于`body`这个成员不一定要有,我尝试为`Block`类加入空构造函数发现输出与答案不一样之后将`body`的类型更改为`Optional`,然后发现需要修一下TacGen.java, Namer.java, Typer.java, Decaf.spec这些与本阶段无关的文件的依赖关系。 + +在Tree.java中,我为`Modifiers`类加入了`abstract`修饰符,并为`MethodDef`和`ClassDef`类增加了这一修饰。 + +### 局部类型推断 + +与抽象类类似,我照着给出的EBNF语法规范修改了Decaf.jacc,然后我以为需要写类型推断,就开始写一个新的构造函数试图通过`Expr initVal`来推断类型,直到我发现答案中的类型是``。于是我只能将`typeLit`的类型改为`Optional`,并修一下Namer.java这一与本阶段无关的文件中用到`typeLit`的地方。 + +### First-class Functions + +#### 函数类型 + +我参考Decaf.jacc中的`VarList`实现了`TypeList`,参考FunType.java和Tree.java中的`TArray`类实现了`TLambda`类,在SemValue.java中增加了`typeList`的判断,在Visitor.java中添加了`visitTLambda`(以及后面的`visitLambda`),在AbstractParser.java中参考`protected SemValue svVars(Tree.LocalVarDef... vars)`实现了`protected SemValue svTypes(Tree.TypeLit... types)`并发现前者是从`svFields`复制过来的但忘了把`FIELD_LIST`改掉了于是发了个pr。 + +#### Lambda 表达式 + +我在Tree.java中参考其他类实现了继承`Expr`类的`Lambda`类,其中指导书中的`paramList`我沿用了代码中的`List`(毕竟是按照`LocalVarDef`来打印)。对于两种Lambda 表达式,我用了`Optional`和`Optional`两个变量,并保证其中有且仅有一个是`isPresent()`的。然后我发现`var f = fun (int x) => x + 1;`被解析成`var f = (fun (int x) => x) + 1;`了,于是改了一下运算符优先级。 + +#### 函数调用 + +我把`Call`类的`receiver`和`method`改成一个变量`methodExpr`之后,出现了大量依赖关系问题,我只能先把Decaf.spec, TacEmitter.java和Typer.java中的部分代码注释掉,以后再修。 + +### 运算符优先级 + +在我完成PA1-A的实验内容之后,申奥同学与我讨论了decaf语言的二义性问题,我们由`(class a)b(1)`这条语句的二义性发现decaf语言的文档并没有规定强制类型转换的优先级。我们发现原有的框架会把`(class a)b(1)`解析成`(class a)(b(1))`(这是因为原有的框架的Call不支持另一种解析方式),却会把`(class a)b.c(1)`解析成`((class a)b).c(1)`,会把`a.b.c(1)`解析成`(a.b).c(1)`。将这一现象反馈给助教后,助教及时更新了文档,将强制类型转换的优先级规定为与负号一样(和C++语言中对C风格强制类型转换的规定一样)。 + +修改Decaf.jacc、加入一行`%prec`即可指定这一优先级。 + +这里有个坑:在这一修改后,可能需要删掉build文件夹或者更新src文件夹下代码的修改日期来彻底重新编译,才能使修改生效(否则会编译成功但运行时报syntax error)。 + +## 回答问题 + +#### Q1. (Java) AST 结点间是有继承关系的。若结点 `A` 继承了 `B`,那么语法上会不会 `A` 和 `B` 有什么关系? + +若`A`继承了`B`,那么语法上`A`一定是一个`B`(`B`可被解析成`A`),如`LocalVarDef`是`Stmt`,`TInt`是`TypeLit`。 + +#### Q2. 原有框架是如何解决空悬 else (dangling-else) 问题的? + +框架为`ElseClause`的空解析用`%prec`指定了`EMPTY`的优先级,比`ELSE`的优先级更低,因此`if...if...else`会优先将后者的`ElseClause`解析为`else{...}`而不是空,从而解决空悬else问题。 + +#### Q3. 这个概念模型与框架的实现有什么区别?我们的具体语法树在哪里? + +区别在于我们没有显式地构建出具体语法树,而是通过Decaf.jacc生成的parser由单词流直接构建抽象语法树。我们的具体语法树只在Decaf.jacc里,用以指示构建抽象语法树的规则。 + +## 致谢 + +我完成词法分析部分以后阅读了实验指导书却还不知道接下来应该去修改哪个文件以实现文法分析部分的逻辑,在此感谢罗承扬同学告诉我应该去看Tree.java这一文件(在此之后,有若干个其他同学也问了我这个问题)。 + +感谢申奥同学(学号2017012518)与我讨论decaf语言的二义性问题,并一起检查每个移进/归约冲突以发现`(class a)b(1)`这条语句的二义性问题。 \ No newline at end of file diff --git a/report-PA1-A.pdf b/report-PA1-A.pdf new file mode 100644 index 0000000..372c673 Binary files /dev/null and b/report-PA1-A.pdf differ diff --git a/report-PA1-B.md b/report-PA1-B.md new file mode 100644 index 0000000..e5e8cbe --- /dev/null +++ b/report-PA1-B.md @@ -0,0 +1,85 @@ +# PA1-B 实验报告 + +计科70 徐明宽 2017011310 + +## 工作内容 + +### LL(1) 语法分析 + +#### 抽象类 + +对`methodDef `,我在Decaf.spec中仿照`STATIC Type Id '(' VarList ')' Block FieldList`写了`ABSTRACT Type Id '(' VarList ')' ';' FieldList`(`classDef `已在PA1-A中改好)。 + +#### 局部类型推断 + +与PA1-A相同。 + +#### First-class Functions + +##### 函数类型 + +我参考Decaf.spec中的`VarList`实现了`TypeList`,并写了非终结符`AfterAtomType`。我在SemValue.java中添加了`List, Integer>> typeListList`以同时存储形如`int(int, int)[](int, int, int)`中的参数列表`int, int`和`int, int, int`以及其中的`[]`的个数,并在AbstractParser.java中添加了`protected SemValue svTypess()`。 + +然后我发现对`new`的处理也用到了`Type`的定义,需要手动加入函数类型,类比`AfterAtomType`实现了`AfterNewAtomType`。 + +##### Lambda 表达式 + +我参照实验说明写了`FUN '(' VarList ')' AfterFunExpr`,并在`AfterFunExpr`的产生式中实现了Lambda表达式的两种情况。 + +##### 函数调用 + +对于`AfterLParen`和`Expr8`中的函数调用,我重写了`ExprT8`使其对应成员字段选择、数组索引、函数调用这一优先级,同时发现函数类型部分可以使用`SemValue`的`thunkList`而无需开一个`List, Integer>> typeListList`。对于`Expr9`中的函数调用,我直接将其删除了,因为函数调用并不属于这一优先级。 + +### 错误恢复 + +我实现了“实验内容”一节中推荐的算法,当遇到$Begin(A)$中的符号时恢复分析$A$,当遇到$End(A)$中的符号时返回`null`(并且使得$A$的各父节点均返回`null`),否则跳过该符号(消耗终结符)。 + +在实验过程中,我发现`parseSymbol`的参数`symbol`为`int`类型,调试时不能直观看出输出的`symbol`具体表示哪个非终结符,于是我**读入**了`LLTable.java`,在里面查找字符串`public static final int`,以此建立一个`symbol`与非终结符字符串的对应关系表,方便调试。 + +### 运算符结合性 + +我发现框架中对大小比较运算符实现的是左结合,而语言规范中写的是不结合,于是将产生式`ExprT4 -> Op4 Expr5 ExprT4`改成了`ExprT4 -> Op4 Expr5`。 + +## 回答问题 + +#### Q1. 本阶段框架是如何解决空悬 else (dangling-else) 问题的? + +如https://github.com/paulzfm/ll1pg/wiki/2.-Resolving-Conflicts所述,当冲突发生时写在前面的产生式优先级更高,所以`E : else S | /* empty */`会优先将`E`解析为`else S`而不是空,从而解决空悬else问题。 + +#### Q2. 使用 LL(1) 文法如何描述二元运算符的优先级与结合性?请结合框架中的文法,**举例**说明。 + +框架中用多个非终结符来描述二元运算符的优先级,如`Expr6`表示“项”,即只含乘除模及更高优先级运算符的表达式;`Expr5`则表示只含加减及更高优先级运算符的表达式。对于左结合的二元运算符如加减(`Op5`),框架中实现了产生式`Expr5 -> Expr6 ExprT5`与`ExprT5 -> Op5 Expr6 ExprT5 | /* empty */`;右结合的只需将左结合的代码中在`thunkList`的开头插入改为在结尾插入;不结合的可以写`ExprT4 -> Op4 Expr5 | /* empty */`。 + +#### Q3. 无论何种错误恢复方法,都无法完全避免误报的问题。 请举出一个**具体的** Decaf 程序(显然它要有语法错误),用你实现的错误恢复算法进行语法分析时**会带来误报**。 并说明该算法为什么**无法避免**这种误报。 + +```java +class Main { + static void main() { + int(, int) x; + } +} +``` + +我的输出: + +``` +*** Error at (3,13): syntax error +*** Error at (3,18): syntax error +*** Error at (5,1): syntax error +``` + +其中`Error at (5,1)`明显属于误报。对比我在PA1-A阶段的输出: + +``` +*** Error at (3,13): syntax error +``` + +这是因为集合$End(A)$太大了,应跳过一些符号时我们却选择了分析失败、不跳过符号直接返回。如$',' \in follow(AfterAtomType)$,但我们在应用展开式`Var -> Type Id`时并不期望`Id`以`','`开头。因此,虽然$',' \in follow(AfterAtomType)$,我们也不应直接返回,因为$',' \in follow(AfterAtomType)$是因为有`TypeList -> Type TypeList1`、`TypeList1 -> ',' Type TypeList1`、`Type -> AtomType AfterAtomType`这些产生式,而我们*在分析到 `AfterAtomType`时* 并没有遇到过`TypeList`这个非终结符,所以可以预见的是这个`','`将会导致大量非终结符分析失败,但我们在这里甚至不能排除这个`','`是`Block`后面的这一可能性:**我们无法权衡应跳过一个终结符还是回溯若干层非终结符**,因而**无法避免**这种误报。 + +我们可能会想, + +我们的错误恢复算法在读入`int(`后看到`','`,分析非终结符`TypeList`、`AfterAtomType`、`Type`时均没有消耗这个`','`就分析失败了,于是接下来试图分析`Type`后面的`Id`看到`','`失败,于是分析`Var`失败,分析`Initializer`看到`','`失败,于是分析`SimpleStmt`失败,分析`Stmt`失败,分析`StmtList`失败,分析`Block`失败,返回到`FieldList`时才意识到必须要跳过这个`','`了,于是应用了我们并不期望的产生式`FieldList -> Type Id AfterIdField FieldList`,消耗了`int`然后看到`')'`于是分析`Id`失败,跳过`')' x`后消耗了`';'`,回到`ClassDef`消耗了第一个`'}'`,于是在第二个`'}'`处再次报错。 + +## 致谢 + +无。 \ No newline at end of file diff --git a/report-PA1-B.pdf b/report-PA1-B.pdf new file mode 100644 index 0000000..14d06cb Binary files /dev/null and b/report-PA1-B.pdf differ diff --git a/report-PA2.md b/report-PA2.md new file mode 100644 index 0000000..1e4712e --- /dev/null +++ b/report-PA2.md @@ -0,0 +1,146 @@ +# PA2 实验报告 + +计科70 徐明宽 2017011310 + +## 工作内容 + +### 合并前一阶段的工作 + +我使用`git am`打了patch,在这过程中发现Namer.java中有我在PA1-A中为了能编译通过而修改的与patch不同的部分,于是我把我自己的修改撤销掉以后成功打上了patch。 + +### 抽象类 + +方便起见,我在ClassSymbol.java中、在MethodSymbol.java中、在Tree.java的`Classdef`类和`MethodDef`类中加入了函数`isAbstract()`。 + +由于主类不能是抽象的,我在Namer.java中的`visitTopLevel`中加入了相应判断。 + +为了输出类类型的`modifiers`(即`ABSTRACT`),我在ClassSymbol.java中加入了`modifiers`,并修改了Namer.java中的`createClassSymbol`。 + +为了正确输出抽象方法的`FormalScope`,我修改了PrettyScope.java与FormalScope.java,将后者中的`nested`变量改为`Optional`类型,并在Namer.java中的`visitMethodDef`函数内对于抽象方法不执行`ctx.open(formal);`等代码段,也即使得`nested`变量为`Optional.empty()`。 + +在Typer.java的`visitMethodDef`函数中,我加入判断使得只有非抽象方法才执行该代码段。 + +对于错误1,我实现了NotAbstractClassError.java,在Tree.java中为`ClassDef`类新增了成员变量`public List unOverriddenMethods;`,参考实验指导书在Namer.java中的`visitClassDef`函数内实现了相应逻辑。在实现时,我只考虑了没有报`BadOverrideError`或`DeclConflictError`错误的方法,也即加入了判断`((Tree.MethodDef) field).symbol != null`。 + +对于错误2,我实现了InstantAbstractClassError.java,在Typer.java中的`visitNewClass`函数内实现了相应逻辑。 + +### 局部类型推导 + +我在Namer.java的`visitLocalVarDef`中对于需要类型推导的情况暂不去检查类型,在Typer.java中已经推导出`initVal`的类型以后将其类型赋给需要类型推导的变量,并检查若结果为`void`类型则报错。 + +### First-class Functions + +#### 函数调用 + +为了调对局部类型推导的测例var-error-1.decaf,我先实现了这一部分。 + +由于需要将大段逻辑从`Call`中移到`VarSel`中,我在Tree.java中删去了`Call`的成员变量`methodName`与成员方法`setThis()`,在`VarSel`中加入了成员变量`public boolean isArrayLength = false; public boolean isStaticMethod = false; public Optional receiverClassName;`。这样,我便可以在Typer.java中让`visitVarSel`中的`expr`返回(存储)足够多的信息,以此在`visitCall`函数中调用`typeCall`。对于`expr`返回的不是函数类型的情况,我实现了NotCallableTypeError.java并直接报错。 + +在`visitCall`中,我将`NotClassFieldError`、`RefNonStaticError`与`FieldNotFoundError`的报错位置由以前的`call.pos`改为了`call.methodExpr.pos`。 + +#### 函数类型 + +我实现了BadFunArgTypeError.java,并在TypeLitVisited.java中实现了`default void visitTLambda(Tree.TLambda typeLambda, ScopeStack ctx)`,在返回类型与所有参数类型`accept`之后再去(当有参数为`void`类型时)报错并将该函数类型的`type`设为`ERROR`(而不是有错就直接设为`ERROR`并返回)。 + +在局部类型推导部分,我去掉了Typer.java的`visitLocalVarDef`中要求不能为函数类型的检查。 + +#### Lambda表达式作用域 + +我参考LocalScope.java实现了LambdaScope.java,参考MethodSymbol.java实现了LambdaSymbol.java。在LocalScope.java中,我在构造函数中加入了`parent`为`LambdaScope`的情况判断,并将`public List nestedLocalScopes()`改为了`public List nestedLocalOrLambdaScopes()`。 + +在Scope.java中,我将`isFormalOrLocalScope`改为了`isFormalOrLocalOrLambdaScope`,并修改了所有用到该函数的地方。 + +在VarSymbol.java中,我修改了`isParam()`的判断。 + +在Namer.java中,我参考`visitMethodDef`实现了`visitLambda`。为了使得这个函数会被访问到,我修改了函数`visitLocalVarDef, visitIf, visitWhile, visitFor`并简单实现了以下函数:`visitExprEval, visitAssign, visitBinary, visitCall, visitClassCast, visitClassTest, visitIndexSel, visitNewArray, visitPrint, visitReturn, visitUnary, visitVarSel`。 + +在Typer.java的`visitVarSel`中,我加入了`LambdaSymbol`的相关判断。 + +在Symbol.java中,我不得已删去了`type`的`final`修饰符;在VarSymbol.java中,我对于局部类型推导实现了新的构造函数;在LambdaSymbol.java中与VarSymbol.java中,我实现了函数`public void setType(Type type)`。以此,我能在Namer.java中定义lambda表达式与需要局部类型推导的变量的symbol,并修改ScopeStack.java中的`lookupBefore`函数,满足实验要求。 + +我实现了AssignCaptureError.java,在LocalScope.java中加入了成员变量`public final Scope parent;`,并在Typer.java的`visitAssign`中加入了lambda表达式中的对捕获的外层的非类作用域中的符号直接赋值的判断。 + +在Namer.java的`visitLocalVarDef`中,我将`def.initVal.ifPresent(objects -> objects.accept(this, ctx));`移到了所有`ctx.declare(symbol);`语句的后面。 + +#### Lambda 表达式返回类型 + +我在Tree.java中的`Stmt`里新增了变量`public Type returnType`,在BuiltInType.java中新增了`BuiltInType INCMP`(Incompatible),并规定任何类型都是`INCMP`的子类型。这样,推导出该类型即表明“类型不兼容” 。之后我在Type.java中实现了函数`public Type upperBound`与`public Type lowerBound`,用于求出两个`Type`的上下界。为了处理`Stmt`里`returnType`可能为空的问题,我又在`Stmt`里实现了函数`public void updateReturnType`。以此,我修改了Typer.java中的函数`visitBlock, visitFor, visitIf, visitWhile, visitReturn`以更新return type。 + +我实现了IncompatRetTypeError.java,当block lambda的返回类型为`INCMP`时即报错。当block lambda的`returns`为`false`时,报`MissingReturnError`。 + +#### 函数变量 + +我去掉了Typer.java的`visitAssign`中要求不能为函数变量的检查。为了对于对一个类的成员方法进行赋值的情况报错,我实现了AssignMethodError.java,并在Tree.java的`VarSel`中加入了成员变量`public boolean isMethod = false;`,以区分函数变量与成员方法。 + +#### 回到函数调用 + +我在Typer.java中的`visitIndexSel`中加入了对于`expr.array.type`为`ERROR`时不再报错的检查。 + +我实现了BadLambdaArgCountError.java,简化了Typer.java中的`visitCall`使其不再调用`typeCall`。 + +## 回答问题 + +#### Q1. 实验框架中是如何实现根据符号名在作用域中查找该符号的?在符号定义和符号引用时的查找有何不同? + +框架中根据符号名在作用域中查找该符号的逻辑实现在了ScopeStack.java中,具体为`findConflict`和`lookupBefore`这两个函数,它们都调用了(前者还调用了(可以改成私有的)公有函数`lookup`,而`lookup`也调用了`findWhile`)一个私有函数`findWhile`,该函数直接**从内向外**遍历当前作用域栈,查找是否有满足要求的符号(要求即上述两个函数传入`findWhile`的参数,包括对作用域的限制条件与对符号的限制条件;而在一个作用域中查找即直接在`TreeMap`(平衡树)中查找该字符串),如果有多个满足要求的话返回第一个(即最内层的)。 + +在符号定义时,我们采用的是`findConflict`,即当当前作用域为非类作用域时对作用域的限制条件为非类作用域或全局作用域(否则无限制),对符号无限制。由于在Namer.java中定义一个符号时还没有访问到后面的符号,我们无需考虑一个符号与后面的符号重名的问题——毕竟报错是后面再次发现同样名称的符号时干的事情。 + +在符号引用时,我们采用的是`lookupBefore`,即对作用域无限制,但对局部作用域中的符号限制在当前定义变量的位置之前(如果当前正在定义变量的话),以避免形如`int x = x + 1;`的情况。在Typer.java中,我们已经在上一阶段定义了所有的符号(因此可以查找到后面的符号,这也是与`findConflict`的一个不同之处),因此必须对符号加以位置的限制(在实验中的lambda表达式作用域部分,我们还要加以 “不是当前正在定义的符号” 这一限制)。 + +考虑下例: + +```java +var a = fun(int u) { + var b = fun(int v) { + int c = c; + }; + int c; +}; +``` + +在符号定义时,在定义第一个`int c`时第二个还没有定义,在定义第二个`int c`时第一个又在内层,因此`findConflict`查找不到;在符号引用时,`int c = c;`要引用`c`,而此时第一个`int c`正在定义,第二个`int c`位置又在后面,因此会报错`undeclared variable 'c'`。 + +#### Q2. 对 AST 的两趟遍历分别做了什么事?分别确定了哪些节点的类型? + +第一趟遍历,即Namer.java中,我们检查了冲突的与其他一些不合法的定义(如定义一个类型为`void`的变量等),检查了类的继承关系形成森林,建立了所有作用域(`Scope`)与相应的符号(`Symbol`)表(虽然其中部分类型未知),定位了主类`Main`与主函数`static void main()`。概括地说,我们明确了程序声明了哪些标识符。 + +由于`Namer`继承了`TypeLitVisited`,我们在第一趟遍历中即确定了`TInt, TBool, TString, TVoid, TClass, TArray`以及实验中实现的`TLambda`节点的类型。在Namer.java中,我们也确定了`ClassDef, MethodDef, LocalVarDef`(如果不需要局部类型推导的话)节点的类型。 + +第二趟遍历,即Typer.java中,我们进行了自顶向下的类型检查,确定了(除`TopLevel`外)其余所有节点的类型,并在发现错误时根据具体情况报相应的错。换句话说,我们明确了每一处使用的标识符对应于哪一处的声明,以及各语句和表达式是否类型正确。 + +#### Q3. 在遍历 AST 时,是如何实现对不同类型的 AST 节点分发相应的处理函数的?请简要分析。 + +参考[实验说明中的“访问者模式”小节](https://decaf-lang.gitbook.io/decaf-book/java-kuang-jia-fen-jie-duan-zhi-dao/pa2-yu-yi-fen-xi/visitor),我们让每个节点都支持一个负责转发的方法`accept`,如: + +```java +@Override +public void accept(Visitor v, C ctx) { + v.visitTopLevel(this, ctx); +} +``` + +再将遍历AST的`Namer`和`Typer`等都实现为一个访问者的实例,这样,对于任意的节点(`TreeNode`),我们只要调用它的 `accept` 方法,那么按照 OOP 的动态分派机制, 根据这个`TreeNode`的具体类型,相应的处理函数将被调用。具体来说,执行 `node.accept(v)` 时: + +- 如果`node instanceof TopLevel`,那么相当于调用的是`TopLevel`类的`accept`方法,该方法会调用`v.visitTopLevel(node, ctx);` +- 如果`node instanceof ClassDef`,那么相当于调用的是`ClassDef`类的`accept`方法,该方法会调用`v.visitClassDef(node, ctx);` +- …… + +## 致谢 + +感谢罗承扬、杨家齐、以及编译原理群中的同学们和助教们与我的讨论。 + +## 声明 + +我于2019年11月17日将代码和报告(包括实验报告中问题的回答) 交给了傅舟涛同学参考。 + +我于2019年11月17日将代码交给了蒋佳轩同学、胡亦行同学参考。 + +我于2019年11月18日将报告交给了胡亦行同学参考。 + +我于2019年11月25日将代码和报告交给了卢睿同学参考。 + +我于2019年11月25日将代码交给了尹龙晖同学参考。 + +我于2019年11月29日将代码交给了潘慰慈同学参考。 + diff --git a/report-PA2.pdf b/report-PA2.pdf new file mode 100644 index 0000000..1ad12b0 Binary files /dev/null and b/report-PA2.pdf differ diff --git a/report-PA3.md b/report-PA3.md new file mode 100644 index 0000000..d11200f --- /dev/null +++ b/report-PA3.md @@ -0,0 +1,90 @@ +# PA3 实验报告 + +计科70 徐明宽 2017011310 + +## 工作内容 + +### 动态错误检查 + +我在TacEmitter.java中参考`visitClassCast`中对运行时错误的检测,在`visitBinary`中判断运算符为除号或取模时新建了一个label `ok`,当`rhs.val`不为零时直接跳转到`ok`,否则输出错误信息(在RuntimeError.java中加入了`String DIVISION_BY_ZERO`)并停止运行。 + +### 抽象类 + +我将TacGen.java中遍历函数体的语句加入了`ifPresent`判断。 + +### 局部类型推导 + +无。 + +### First-class Functions + +#### 扩展call + +我在TacEmitter.java的`visitVarSel`中,利用PA2实现好的`expr.isMethod`与`expr.isStaticMethod`(这里发现Typer.java中有一处未对`expr.isStaticMethod`和`expr.receiverClassName`赋值,且未写`expr.setThis();`,还有一处未对`expr.isStaticMethod`赋值,便将它们补上),对于static的情况申请8字节内存,存储`0`和函数指针;对于non-static的情况申请12字节内存,存储`1`、函数指针和this(即`object.val`)。对于non-static的情况,我在FuncVisitor.java中实现了`visitFuncEntry`函数以通过`Temp object, String clazz, String method`获取函数指针;对于static的情况,我在此时暂未实现获取函数指针的代码。 + +在FuncVisitor.java中仿照`public Temp visitMemberCall(Temp object, String clazz, String method, List args, boolean needReturn)`实现了Call节点仅需的`public Temp visitCall(Temp entry, List args, boolean needReturn)`函数之后,我在TacEmitter.java的`visitCall`函数中调用了`expr.methodExpr.accept(this, mv);`并对`methodExpr`(可能是)`VarSel`节点返回的`val`对应地址的第一个元素进行了判断,如果是`0`则为static情况,`1`则为non-static情况,后者需要向参数列表的开头加入this指针。与实验说明“当检测到并非方法调用时”不同,我并没有沿用原有的逻辑(毕竟PA1都把Call节点的`receiver`删了啊),而是全部重写。 + +#### 将方法名直接当做函数使用 + +我发现TacGen.java中无法直接新建虚表,于是在ProgramWriter.java的`class Context`新建了(全局的)虚表`staticVtbl`,类名为`static`(使用Decaf的关键字即可不和任何一个类重名),而存储的“成员”函数名为`className + "." + funcName`。因此,我在`putOffsets`中对这个虚表进行了特判,以避免函数在`Map`中被存为`"static." + funcName`导致重名。然后我发现ProgramWriter.java又无法判断一个方法是不是静态的,于是仅实现了函数`visitStaticMethod`,而在TacGen.java中对静态方法进行判断。最后,由于静态方法的虚表也需要进行`putVTable`和`putOffsets`,我在ProgramWriter.java中实现了函数`visitVTablesPostProcess`,并在TacGen.java中调用,以完成建虚表的这些后处理工作。 + +建好静态方法的虚表后,我便可以在TacEmitter.java的`visitVarSel`中获取静态方法的函数指针了:只需在FuncVisitor.java中再写一个`visitFuncEntry`,将`visitLoadFrom(object)`替换成`visitLoadVTable("static")`即可。 + +接下来,我遇到了本次实验中的一个**困难**:模拟器找不到`main`方法了。经研究代码,我发现FuncLabel.java中对`main`方法的标签有特判:并不是写做`String.format("_L_%s_%s", clazz, method)`的形式,而是直接写成`"main"`。并且,这也不只是在`prettyString`中这么做,而是在Simulator.java的`private Map _label_to_addr`中亦为如此。然而,Simulator.java又到这个`Map`中去找了`_L_Main_main`(因为我在静态方法的虚表里用的是这个),那自然是找不到了。 + +如何解决呢?我认为,在Decaf语言中,`main`方法与其他方法并没有多少不同,同样可以被调用,同样可以被赋值给变量,同样可以直接当做函数使用。因此,我不认为每当想找一个FuncLabel时都应对`main`进行特判,而是应该将`main`的label改为与其他静态方法格式相同。修改了FuncLabel.java中的相关逻辑后这个问题得以解决。 + +模拟器能运行后,我在测试以下Decaf程序时又遇到了问题:输出为`truefalse`。 + +```java +class Main { + static void main() { + Print(even(0)); + } + static bool even(int x) { + Print(x % 2 == 0); + return x % 2 == 0; + } +} +``` + +经调试,我发现问题出在我在TacEmitter.java的`visitCall`中(参考原有的代码,直接)对静态方法和非静态方法分别写了`expr.val = ...`,而两次这样的“赋值”在tac代码里是不会赋给同一个临时变量的。我改为`expr.val = mv.freshTemp()`和两次`mv.visitAssign(expr.val, ...)`从而解决了问题。 + +#### Lambda表达式 + +为了精确地知道lambda表达式捕获了哪些变量,我参考自己在PA2阶段在Typer.java的`visitAssign`中实现的相关逻辑,在LambdaScope.java中新增了变量`public final Scope parent`和`private Map capturedVar`、方法`public void capture(VarSymbol varSymbol)`和`public List capturedVars()`,在`visitVarSel`中利用作用域栈获取了哪些`LambdaScope`应该捕获当前`VarSel`的变量,而没有像实验说明推荐的那样额外保存一个lambda表达式栈。为方便调试,在PrettyScope.java中加一行`lambdaScope.capturedVar.values().forEach(printer::println);`即可输出所有被Lambda表达式捕获的变量。 + +值得注意的是,对于`this`中的field,要捕获的是`this`,因此在Typer.java的`visitVarSel`中执行完`expr.setThis();`后应将待捕获的变量改为`this`。 + +为了新建lambda的虚表,我在GlobalScope.java中新增了变量`public List lambdaSymbols`,在Namer.java中即将所有`LambdaSymbol`统计了下来。这样,在ProgramWriter.java中新建虚表`lambdaVtbl`(类名为`fun`,使用Decaf的关键字即可不和任何一个类重名)、新增函数`visitLambda`后,我便可以在TacGen.java中调用ProgramWriter.java的`visitLambda`以建立lambda的虚表,在FuncVisitor.java中再写一个`visitFuncEntry`,在TacEmitter.java的`visitLambda`中申请`(3 + 被捕获的变量数目) * 4`个字节的内存,依次存储`2`、函数指针、被捕获的变量数目,以及按照`pos`的顺序存储每个被捕获的变量。注意我并没有对捕获this的情况进行特判。 + +在TacEmitter.java的`visitCall`中的lambda表达式这一情况,我使用了`emitWhile`与FuncVisitor.java中新实现的`visitParm`对被捕获的变量进行了传参。 + +在TacEmitter.java的`visitLambda`中,我需要实现lambda表达式的函数体,又不能破坏声明lambda表达式的函数的原有框架。于是,我通过无条件跳转指令获得了一块不会被访问到的tac代码空间,在这块空间里调用FuncVisitor.java中新实现的`visitLambdaLabel`,将形参的`temp`传入,再实现lambda表达式的函数体。 + +为了判断当前是在哪个lambda表达式内部,我在TacEmitter.java中新增了变量`Stack lambdaStack`,如果在某个lambda表达式内部的话在TacEmitter.java的`visitVarSel`和`visitThis`中暴力查找待访问的变量被捕获的位置(如果被捕获的话)。当然,这里可以实现被优化为使用`Map`做到每次`O(log(被捕获的变量个数))`的时间复杂度,但被捕获的变量个数普遍较少,加上实验并不关心运行速度,暴力即有足够好的效果。 + +这样实现后,我又遇到了一个困难:lambda表达式的函数并不能顺利地按照预想的方式被调用,出现的问题包括但不限于当捕获的变量个数多于外层函数的参数个数时在FuncVisitor.java里`getArgTemp`里访问`argsTemps[index]`会数组越界。解决方式显然应该是为每个lambda表达式新开一个FuncVisitor,即*真正* 地新生成一个函数,这只需在FuncVisitor.java中实现一个函数`visitLambdaFunc`(以替换掉之前写的`visitLambdaLabel`,也就是说对于lambda表达式只获得一个FuncLabel对于TacEmitter.java来说并不够用)返回一个新的`FuncVisitor`即可。除此以外,对于实验说明里所写的静态与非静态方法的“新生成一个函数”,均只是开了几个字节的内存存储函数指针等信息罢了,并不需要新开FuncVisitor。 + +#### 拓展:数组长度的情况 + +我额外实现了将数组长度当做函数使用的情况。在TacEmitter.java的`visitVarSel`中,对于这种情况,我申请8字节内存,存储`3`和数组指针;在`visitCall`中,我对于这种情况直接`mv.visitAssign(expr.val, mv.visitLoadFrom(entry, -4));`即可。 + +## 回答问题 + +#### Q1. lambda语法实现的流程? + +见上文"Lambda表达式"一节。 + +#### Q2. 实现工程中遇到的困难? + +见上文“将方法名直接当做函数使用”一节与"Lambda表达式"一节。 + +## 致谢 + +感谢罗承扬同学与我的讨论。 + +## 声明 + +我于2019年12月15日将代码和报告交给了傅舟涛同学参考。 + diff --git a/report-PA3.pdf b/report-PA3.pdf new file mode 100644 index 0000000..bc4a6a1 Binary files /dev/null and b/report-PA3.pdf differ diff --git a/src/main/jacc/Decaf.jacc b/src/main/jacc/Decaf.jacc index 74097b4..de3f6f1 100644 --- a/src/main/jacc/Decaf.jacc +++ b/src/main/jacc/Decaf.jacc @@ -24,7 +24,9 @@ import java.util.Optional; %token LESS_EQUAL GREATER_EQUAL EQUAL NOT_EQUAL %token '+' '-' '*' '/' '%' '=' '>' '<' '.' %token ',' ';' '!' '(' ')' '[' ']' '{' '}' +%token ABSTRACT VAR FUN ARROW +%nonassoc ARROW %left OR %left AND %left EQUAL NOT_EQUAL @@ -69,7 +71,12 @@ ClassList : ClassList ClassDef ClassDef : CLASS Id ExtendsClause '{' FieldList '}' { - $$ = svClass(new ClassDef($2.id, Optional.ofNullable($3.id), $5.fieldList, $1.pos)); + $$ = svClass(new ClassDef(0, $2.id, Optional.ofNullable($3.id), $5.fieldList, $1.pos)); + } + | ABSTRACT CLASS Id ExtendsClause '{' FieldList '}' + { + $$ = svClass(new ClassDef(Modifiers.ABSTRACT, $3.id, Optional.ofNullable($4.id), + $6.fieldList, $2.pos)); } ; @@ -107,11 +114,17 @@ Var : Type Id MethodDef : STATIC Type Id '(' VarList ')' Block { - $$ = svField(new MethodDef(true, $3.id, $2.type, $5.varList, $7.block, $3.pos)); + $$ = svField(new MethodDef(Modifiers.STATIC, $3.id, $2.type, $5.varList, + Optional.of($7.block), $3.pos)); } | Type Id '(' VarList ')' Block { - $$ = svField(new MethodDef(false, $2.id, $1.type, $4.varList, $6.block, $2.pos)); + $$ = svField(new MethodDef(0, $2.id, $1.type, $4.varList, Optional.of($6.block), $2.pos)); + } + | ABSTRACT Type Id '(' VarList ')' ';' + { + $$ = svField(new MethodDef(Modifiers.ABSTRACT, $3.id, $2.type, $5.varList, + Optional.empty(), $3.pos)); } ; @@ -162,6 +175,31 @@ Type : INT { $$ = svType(new TArray($1.type, $1.type.pos)); } + | Type '(' TypeList ')' + { + $$ = svType(new TLambda($1.type, $3.typeList, $1.type.pos)); + } + ; + +TypeList : TypeList1 + { + $$ = $1; + } + | /* empty */ + { + $$ = svTypes(); + } + ; + +TypeList1 : TypeList1 ',' Type + { + $$ = $1; + $$.typeList.add($3.type); + } + | Type + { + $$ = svTypes($1.type); + } ; // Statements @@ -225,7 +263,13 @@ StmtList : StmtList Stmt SimpleStmt : Var Initializer { - $$ = svStmt(new LocalVarDef($1.type, $1.id, $2.pos, Optional.ofNullable($2.expr), $1.pos)); + $$ = svStmt(new LocalVarDef(Optional.of($1.type), $1.id, $2.pos, + Optional.ofNullable($2.expr), $1.pos)); + } + | VAR Id '=' Expr + { + $$ = svStmt(new LocalVarDef(Optional.empty(), $2.id, $3.pos, + Optional.ofNullable($4.expr), $2.pos)); } | LValue '=' Expr { @@ -286,9 +330,17 @@ Expr : Literal { $$ = svExpr($1.lValue); } - | Receiver Id '(' ExprList ')' + | FUN '(' VarList ')' ARROW Expr + { + $$ = svExpr(new Lambda($3.varList, $6.expr, $1.pos)); + } + | FUN '(' VarList ')' Block + { + $$ = svExpr(new Lambda($3.varList, $5.block, $1.pos)); + } + | Expr '(' ExprList ')' { - $$ = svExpr(new Call(Optional.ofNullable($1.expr), $2.id, $4.exprList, $3.pos)); + $$ = svExpr(new Call($1.expr, $3.exprList, $2.pos)); } | Expr '+' Expr { diff --git a/src/main/java/decaf/driver/error/AssignCaptureError.java b/src/main/java/decaf/driver/error/AssignCaptureError.java new file mode 100644 index 0000000..c9fe953 --- /dev/null +++ b/src/main/java/decaf/driver/error/AssignCaptureError.java @@ -0,0 +1,20 @@ +package decaf.driver.error; + +import decaf.frontend.tree.Pos; + +/** + * example:cannot assign value to captured variables in lambda expression
+ * PA2 + */ +public class AssignCaptureError extends DecafError { + + public AssignCaptureError(Pos pos) { + super(pos); + } + + @Override + protected String getErrMsg() { + return "cannot assign value to captured variables in lambda expression"; + } + +} diff --git a/src/main/java/decaf/driver/error/AssignMethodError.java b/src/main/java/decaf/driver/error/AssignMethodError.java new file mode 100644 index 0000000..2430959 --- /dev/null +++ b/src/main/java/decaf/driver/error/AssignMethodError.java @@ -0,0 +1,23 @@ +package decaf.driver.error; + +import decaf.frontend.tree.Pos; + +/** + * example:cannot assign value to class member method 'sf'
+ * PA2 + */ +public class AssignMethodError extends DecafError { + + private String name; + + public AssignMethodError(Pos pos, String name) { + super(pos); + this.name = name; + } + + @Override + protected String getErrMsg() { + return "cannot assign value to class member method '" + name + "'"; + } + +} diff --git a/src/main/java/decaf/driver/error/BadFunArgTypeError.java b/src/main/java/decaf/driver/error/BadFunArgTypeError.java new file mode 100644 index 0000000..a4425e7 --- /dev/null +++ b/src/main/java/decaf/driver/error/BadFunArgTypeError.java @@ -0,0 +1,20 @@ +package decaf.driver.error; + +import decaf.frontend.tree.Pos; + +/** + * example:arguments in function type must be non-void known type
+ * PA2 + */ +public class BadFunArgTypeError extends DecafError { + + public BadFunArgTypeError(Pos pos) { + super(pos); + } + + @Override + protected String getErrMsg() { + return "arguments in function type must be non-void known type"; + } + +} diff --git a/src/main/java/decaf/driver/error/BadLambdaArgCountError.java b/src/main/java/decaf/driver/error/BadLambdaArgCountError.java new file mode 100644 index 0000000..71dd9b8 --- /dev/null +++ b/src/main/java/decaf/driver/error/BadLambdaArgCountError.java @@ -0,0 +1,25 @@ +package decaf.driver.error; + +import decaf.frontend.tree.Pos; + +/** + * example:lambda expression expects 1 argument(s) but 3 given
+ * PA2 + */ +public class BadLambdaArgCountError extends DecafError { + + private int expect; + + private int count; + + public BadLambdaArgCountError(Pos pos, int expect, int count) { + super(pos); + this.expect = expect; + this.count = count; + } + + @Override + protected String getErrMsg() { + return "lambda expression expects " + expect + " argument(s) but " + count + " given"; + } +} diff --git a/src/main/java/decaf/driver/error/IncompatRetTypeError.java b/src/main/java/decaf/driver/error/IncompatRetTypeError.java new file mode 100644 index 0000000..44b3b42 --- /dev/null +++ b/src/main/java/decaf/driver/error/IncompatRetTypeError.java @@ -0,0 +1,17 @@ +package decaf.driver.error; + +import decaf.frontend.tree.Pos; + +// Typer error + +public class IncompatRetTypeError extends DecafError { + + public IncompatRetTypeError(Pos pos) { + super(pos); + } + + @Override + protected String getErrMsg() { + return "incompatible return types in blocked expression"; + } +} diff --git a/src/main/java/decaf/driver/error/InstantAbstractClassError.java b/src/main/java/decaf/driver/error/InstantAbstractClassError.java new file mode 100644 index 0000000..63270cd --- /dev/null +++ b/src/main/java/decaf/driver/error/InstantAbstractClassError.java @@ -0,0 +1,23 @@ +package decaf.driver.error; + +import decaf.frontend.tree.Pos; + +/** + * example:cannot instantiate abstract class 'Foo'
+ * PA2 + */ +public class InstantAbstractClassError extends DecafError { + + private String name; + + public InstantAbstractClassError(Pos pos, String name) { + super(pos); + this.name = name; + } + + @Override + protected String getErrMsg() { + return "cannot instantiate abstract class '" + name + "'"; + } + +} diff --git a/src/main/java/decaf/driver/error/NotAbstractClassError.java b/src/main/java/decaf/driver/error/NotAbstractClassError.java new file mode 100644 index 0000000..ce13a7b --- /dev/null +++ b/src/main/java/decaf/driver/error/NotAbstractClassError.java @@ -0,0 +1,23 @@ +package decaf.driver.error; + +import decaf.frontend.tree.Pos; + +/** + * example:'Foo' is not abstract and does not override all abstract methods
+ * PA2 + */ +public class NotAbstractClassError extends DecafError { + + private String name; + + public NotAbstractClassError(Pos pos, String name) { + super(pos); + this.name = name; + } + + @Override + protected String getErrMsg() { + return "'" + name + "' is not abstract and does not override all abstract methods"; + } + +} diff --git a/src/main/java/decaf/driver/error/NotCallableTypeError.java b/src/main/java/decaf/driver/error/NotCallableTypeError.java new file mode 100644 index 0000000..2ef0ba6 --- /dev/null +++ b/src/main/java/decaf/driver/error/NotCallableTypeError.java @@ -0,0 +1,23 @@ +package decaf.driver.error; + +import decaf.frontend.tree.Pos; + +/** + * example:string is not a callable type
+ * PA2 + */ +public class NotCallableTypeError extends DecafError { + + private String name; + + public NotCallableTypeError(Pos pos, String name) { + super(pos); + this.name = name; + } + + @Override + protected String getErrMsg() { + return name + " is not a callable type"; + } + +} diff --git a/src/main/java/decaf/frontend/parsing/AbstractParser.java b/src/main/java/decaf/frontend/parsing/AbstractParser.java index fe18b42..ffb475e 100644 --- a/src/main/java/decaf/frontend/parsing/AbstractParser.java +++ b/src/main/java/decaf/frontend/parsing/AbstractParser.java @@ -139,6 +139,19 @@ protected SemValue svType(Tree.TypeLit type) { return v; } + protected SemValue svTypes(Tree.TypeLit... types) { + var v = new SemValue(SemValue.Kind.TYPE_LIST, types.length == 0 ? Pos.NoPos : types[0].pos); + v.typeList = new ArrayList<>(); + v.typeList.addAll(Arrays.asList(types)); + return v; + } + + protected SemValue svTypess() { + var v = new SemValue(SemValue.Kind.TYPE_LIST_LIST, Pos.NoPos); + v.typeListList = new ArrayList<>(); + return v; + } + protected SemValue svStmt(Tree.Stmt stmt) { var v = new SemValue(SemValue.Kind.STMT, stmt == null ? Pos.NoPos : stmt.pos); v.stmt = stmt; diff --git a/src/main/java/decaf/frontend/parsing/JaccParser.java b/src/main/java/decaf/frontend/parsing/JaccParser.java index 8e74964..b03b4c6 100644 --- a/src/main/java/decaf/frontend/parsing/JaccParser.java +++ b/src/main/java/decaf/frontend/parsing/JaccParser.java @@ -75,6 +75,10 @@ public int tokenOf(int code) { case Tokens.GREATER_EQUAL -> decaf.frontend.parsing.JaccTokens.GREATER_EQUAL; case Tokens.EQUAL -> decaf.frontend.parsing.JaccTokens.EQUAL; case Tokens.NOT_EQUAL -> decaf.frontend.parsing.JaccTokens.NOT_EQUAL; + case Tokens.ABSTRACT -> decaf.frontend.parsing.JaccTokens.ABSTRACT; + case Tokens.VAR -> decaf.frontend.parsing.JaccTokens.VAR; + case Tokens.FUN -> decaf.frontend.parsing.JaccTokens.FUN; + case Tokens.ARROW -> decaf.frontend.parsing.JaccTokens.ARROW; default -> code; // single-character, use their ASCII code! }; } diff --git a/src/main/java/decaf/frontend/parsing/LLParser.java b/src/main/java/decaf/frontend/parsing/LLParser.java index 44203ea..9b0056a 100644 --- a/src/main/java/decaf/frontend/parsing/LLParser.java +++ b/src/main/java/decaf/frontend/parsing/LLParser.java @@ -100,10 +100,51 @@ public int tokenOf(int code) { case Tokens.GREATER_EQUAL -> GREATER_EQUAL; case Tokens.EQUAL -> EQUAL; case Tokens.NOT_EQUAL -> NOT_EQUAL; + case Tokens.ABSTRACT -> ABSTRACT; + case Tokens.VAR -> VAR; + case Tokens.FUN -> FUN; + case Tokens.ARROW -> ARROW; default -> code; // single-character, use their ASCII code! }; } + /*private String[] symbols; // for debug, may cause timeout + private boolean init = false; + private void initSymbols() { + init = true; + try { + java.io.File lltable = new java.io.File( + "C:\\Users\\xmk\\Desktop\\decaf-2017011310\\build\\generated-src\\ll1pg\\LLTable.java"); + // TODO: Don't forget to change the directory if you want to use this part of code to debug! + if (lltable.isFile() && lltable.exists()) { + InputStream in = new java.io.FileInputStream(lltable); + java.util.Scanner scanner = new java.util.Scanner(in); + symbols = new String[600]; + for (int i = 0; i < 128; i++) { + symbols[i] = "\'" + (char) i + "\'"; + } + for (int i = 0; i < 200; i++) { + String s = scanner.nextLine(); + int pos = s.indexOf("public static final int"); + if (pos != -1) { + int pos2 = s.indexOf("="); + int num = 0; + for (int j = pos2 + 2; true; j++) { + if (s.charAt(j) >= '0' && s.charAt(j) <= '9') + num = num * 10 + s.charAt(j) - '0'; + else + break; + } + symbols[num] = s.substring(pos + 24, pos2 - 1); + } + } + } + } + catch (java.io.IOException | NullPointerException e) { + System.err.println(e); + } + }*/ + /** * Parse function for every non-terminal, with error recovery. * NOTE: the current implementation is buggy and may throw {@link NullPointerException}. @@ -114,7 +155,38 @@ public int tokenOf(int code) { * @return the parsed value of {@code symbol} if parsing succeeds, or else {@code null}. */ private SemValue parseSymbol(int symbol, Set follow) { + follow.addAll(followSet(symbol)); + + /*if (false) { // debug + if (!init) + initSymbols(); + System.err.print("parsing symbol " + symbols[symbol] + ":"); + for (var i : followSet(symbol)) { + System.err.print(" " + (i == -1 ? "eof" : symbols[i])); + } + System.err.print(" token=" + symbols[token]); + System.err.println(); + }*/ + var result = query(symbol, token); // get production by lookahead symbol +// System.err.println(result); + if (result == null) { + yyerror("syntax error"); + if (follow.contains(token)) { +// System.err.println("failed " + symbols[symbol] + ": follow contains " + symbols[token]); + return null; + } + while (result == null) { +// System.err.println("skip " + symbols[token]); + matchToken(token); // skip the token + result = query(symbol, token); + if (result == null && follow.contains(token)) { +// System.err.println("failed " + symbols[symbol] + ": follow contains " + symbols[token]); + return null; + } + } + } + var actionId = result.getKey(); // get user-defined action var right = result.getValue(); // right-hand side of production @@ -123,13 +195,21 @@ private SemValue parseSymbol(int symbol, Set follow) { for (var i = 0; i < length; i++) { // parse right-hand side symbols one by one var term = right.get(i); + var nextFollow = new TreeSet<>(follow); params[i + 1] = isNonTerminal(term) - ? parseSymbol(term, follow) // for non terminals: recursively parse it + ? parseSymbol(term, nextFollow) // for non terminals: recursively parse it : matchToken(term) // for terminals: match token ; } + for (var i = 0; i < length; i++) { + if (params[i + 1] == null) { // syntax error +// System.err.println("failed " + symbols[symbol]); + return null; + } + } act(actionId, params); // do user-defined action +// System.err.println("parsed symbol " + symbols[symbol] + ":" + params[0].toString()); return params[0]; } diff --git a/src/main/java/decaf/frontend/parsing/SemValue.java b/src/main/java/decaf/frontend/parsing/SemValue.java index f85ab89..1436f17 100644 --- a/src/main/java/decaf/frontend/parsing/SemValue.java +++ b/src/main/java/decaf/frontend/parsing/SemValue.java @@ -3,6 +3,7 @@ import decaf.frontend.tree.Pos; import decaf.frontend.tree.Tree; import decaf.lowlevel.StringUtils; +import org.apache.commons.lang3.tuple.MutablePair; import java.util.List; @@ -14,8 +15,8 @@ */ class SemValue { enum Kind { - TOKEN, CLASS, CLASS_LIST, FIELD, FIELD_LIST, VAR, VAR_LIST, TYPE, STMT, STMT_LIST, BLOCK, EXPR, EXPR_LIST, - LVALUE, ID, TEMPORARY + TOKEN, CLASS, CLASS_LIST, FIELD, FIELD_LIST, VAR, VAR_LIST, TYPE, TYPE_LIST, TYPE_LIST_LIST, + STMT, STMT_LIST, BLOCK, EXPR, EXPR_LIST, LVALUE, ID, TEMPORARY } /** @@ -82,6 +83,8 @@ enum Kind { List varList; // a list can only contain local vars Tree.TypeLit type; + List typeList; + List, Integer>> typeListList; // for int(int)[](int)[] Tree.Stmt stmt; List stmtList; @@ -100,6 +103,7 @@ enum Kind { public String toString() { String msg = switch (kind) { case TOKEN -> switch (code) { + case Tokens.ABSTRACT -> "keyword : abstract"; case Tokens.BOOL -> "keyword : bool"; case Tokens.BREAK -> "keyword : break"; case Tokens.CLASS -> "keyword : class"; @@ -117,9 +121,11 @@ public String toString() { case Tokens.RETURN -> "keyword : return"; case Tokens.STRING -> "keyword : string"; case Tokens.THIS -> "keyword : this"; + case Tokens.VAR -> "keyword : var"; case Tokens.VOID -> "keyword : void"; case Tokens.WHILE -> "keyword : while"; - case Tokens.STATIC -> "keyword : static"; + case Tokens.STATIC -> "keyword : static"; + case Tokens.FUN -> "keyword : fun"; case Tokens.INT_LIT -> "int literal : " + intVal; case Tokens.BOOL_LIT -> "bool literal : " + boolVal; case Tokens.STRING_LIT -> "string literal : " + StringUtils.quote(strVal); @@ -130,6 +136,7 @@ public String toString() { case Tokens.LESS_EQUAL -> "operator : <="; case Tokens.NOT_EQUAL -> "operator : !="; case Tokens.OR -> "operator : ||"; + case Tokens.ARROW -> "operator : =>"; default -> "operator : " + (char) code; }; case CLASS -> "CLASS: " + clazz; @@ -139,6 +146,8 @@ public String toString() { case VAR -> "VAR: " + type + " " + id; case VAR_LIST -> "VAR_LIST: " + varList; case TYPE -> "TYPE: " + type; + case TYPE_LIST -> "TYPE_LIST: " + typeList; + case TYPE_LIST_LIST -> "TYPE_LIST_LIST: " + typeListList; case STMT -> "STMT: " + stmt; case STMT_LIST -> "STMT_LIST: " + stmtList; case BLOCK -> "BLOCK: " + block; @@ -166,6 +175,10 @@ private void UserAction(SemValue $$, SemValue $1, SemValue $2, SemValue $3, SemValue $4, SemValue $5, SemValue $6) { { // Your action +// 'var' Id '=' Expr +// { +// $$ = svStmt(new LocalVarDef(new TypeLit(($4.expr).kind, "LocalVarDef", $1.pos), $2.id, $3.pos, Optional.of($4.expr), $1.pos)); +// } } } } diff --git a/src/main/java/decaf/frontend/parsing/Tokens.java b/src/main/java/decaf/frontend/parsing/Tokens.java index af06e3e..4e2bf96 100644 --- a/src/main/java/decaf/frontend/parsing/Tokens.java +++ b/src/main/java/decaf/frontend/parsing/Tokens.java @@ -35,6 +35,10 @@ public interface Tokens { int GREATER_EQUAL = 28; int EQUAL = 29; int NOT_EQUAL = 30; + int ABSTRACT= 31; + int VAR = 32; + int FUN = 34; + int ARROW = 35; // MUST use ASCII code to encode a single-character token. // '!' (code=33) diff --git a/src/main/java/decaf/frontend/scope/FormalScope.java b/src/main/java/decaf/frontend/scope/FormalScope.java index fdafe36..7b3f5c8 100644 --- a/src/main/java/decaf/frontend/scope/FormalScope.java +++ b/src/main/java/decaf/frontend/scope/FormalScope.java @@ -2,6 +2,8 @@ import decaf.frontend.symbol.MethodSymbol; +import java.util.Optional; + /** * Formal scope: stores parameter variable symbols. It is owned by a method symbol. */ @@ -9,6 +11,7 @@ public class FormalScope extends Scope { public FormalScope() { super(Kind.FORMAL); + nested = Optional.empty(); } public MethodSymbol getOwner() { @@ -24,13 +27,17 @@ public boolean isFormalScope() { return true; } + public boolean hasLocalScope() { + return nested.isPresent(); + } + /** * Get the local scope associated with the method body. * * @return local scope */ public LocalScope nestedLocalScope() { - return nested; + return nested.get(); } /** @@ -39,10 +46,10 @@ public LocalScope nestedLocalScope() { * @param scope local scope */ void setNested(LocalScope scope) { - nested = scope; + nested = Optional.of(scope); } private MethodSymbol owner; - private LocalScope nested; + private Optional nested; } diff --git a/src/main/java/decaf/frontend/scope/GlobalScope.java b/src/main/java/decaf/frontend/scope/GlobalScope.java index 76076c4..7a4a6be 100644 --- a/src/main/java/decaf/frontend/scope/GlobalScope.java +++ b/src/main/java/decaf/frontend/scope/GlobalScope.java @@ -1,6 +1,7 @@ package decaf.frontend.scope; import decaf.frontend.symbol.ClassSymbol; +import decaf.frontend.symbol.LambdaSymbol; import java.util.ArrayList; import java.util.List; @@ -37,4 +38,6 @@ public List nestedClassScopes() { } return scopes; } + + public List lambdaSymbols = new ArrayList<>(); } diff --git a/src/main/java/decaf/frontend/scope/LambdaScope.java b/src/main/java/decaf/frontend/scope/LambdaScope.java new file mode 100644 index 0000000..3f67ef3 --- /dev/null +++ b/src/main/java/decaf/frontend/scope/LambdaScope.java @@ -0,0 +1,82 @@ +package decaf.frontend.scope; + +import decaf.frontend.symbol.LambdaSymbol; +import decaf.frontend.symbol.VarSymbol; + +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; + +/** + * Lambda scope: stores parameter variable symbols. It is owned by a lambda expression symbol. + */ +public class LambdaScope extends Scope { + + public LambdaScope(Scope parent) { + super(Kind.LAMBDA); + assert parent.isLocalScope(); + ((LocalScope) parent).nestedLocalOrLambdaScopes().add(this); + this.parent = parent; + } + + public LambdaSymbol getOwner() { + return owner; + } + + public void setOwner(LambdaSymbol owner) { + this.owner = owner; + } + + @Override + public boolean isLambdaScope() { + return true; + } + + /** + * Get the local scope associated with the method body. + * + * @return local scope + */ + public LocalScope nestedLocalScope() { + return nested; + } + + /** + * Set the local scope. + * + * @param scope local scope + */ + void setNested(LocalScope scope) { + nested = scope; + } + + /** + * Capture a VarSymbol. + * + * @param varSymbol the VarSymbol to be captured + */ + public void capture(VarSymbol varSymbol) { + capturedVar.put(varSymbol.name, varSymbol); + } + + /** + * Get captured VarSymbols. + * + * @return list of VarSymbols + */ + public List capturedVars() { + var list = new ArrayList<>(capturedVar.values()); + Collections.sort(list); + return list; + } + + private LambdaSymbol owner; + + private LocalScope nested; + + public final Scope parent; + + private Map capturedVar = new TreeMap<>(); +} diff --git a/src/main/java/decaf/frontend/scope/LocalScope.java b/src/main/java/decaf/frontend/scope/LocalScope.java index 674269a..dcb2edf 100644 --- a/src/main/java/decaf/frontend/scope/LocalScope.java +++ b/src/main/java/decaf/frontend/scope/LocalScope.java @@ -1,5 +1,7 @@ package decaf.frontend.scope; +import decaf.frontend.tree.Tree; + import java.util.ArrayList; import java.util.List; @@ -10,12 +12,15 @@ public class LocalScope extends Scope { public LocalScope(Scope parent) { super(Kind.LOCAL); - assert parent.isFormalOrLocalScope(); + assert parent.isFormalOrLocalOrLambdaScope(); if (parent.isFormalScope()) { ((FormalScope) parent).setNested(this); - } else { + } else if (parent.isLocalScope()) { ((LocalScope) parent).nested.add(this); + } else { + ((LambdaScope) parent).setNested(this); } + this.parent = parent; } @Override @@ -24,13 +29,15 @@ public boolean isLocalScope() { } /** - * Collect all local scopes defined inside this scope. + * Collect all local scopes and lambda scopes defined inside this scope. * - * @return local scopes + * @return local scopes and lambda scopes */ - public List nestedLocalScopes() { + public List nestedLocalOrLambdaScopes() { return nested; } - private List nested = new ArrayList<>(); + private List nested = new ArrayList<>(); + + public final Scope parent; } diff --git a/src/main/java/decaf/frontend/scope/Scope.java b/src/main/java/decaf/frontend/scope/Scope.java index 8893817..0f6d5fb 100644 --- a/src/main/java/decaf/frontend/scope/Scope.java +++ b/src/main/java/decaf/frontend/scope/Scope.java @@ -7,23 +7,25 @@ /** * Scopes. *

- * A scope stores the mapping from names to {@link Symbol}s. Four kinds of scopes are used: + * A scope stores the mapping from names to {@link Symbol}s. Five kinds of scopes are used: *

    *
  • global scope: stores globally-defined classes
  • *
  • class scope: stores class members
  • *
  • formal scope: stores parameters
  • *
  • local scope: stores locally-defined variables
  • + *
  • lambda scope: stores parameters
  • *
* * @see GlobalScope * @see ClassScope * @see FormalScope * @see LocalScope + * @see LambdaScope */ public abstract class Scope implements Iterable { public enum Kind { - GLOBAL, CLASS, FORMAL, LOCAL + GLOBAL, CLASS, FORMAL, LOCAL, LAMBDA } public final Kind kind; @@ -99,8 +101,12 @@ public boolean isFormalScope() { return false; } - public boolean isFormalOrLocalScope() { - return isFormalScope() || isLocalScope(); + public boolean isLambdaScope() { + return false; + } + + public boolean isFormalOrLocalOrLambdaScope() { + return isFormalScope() || isLocalScope() || isLambdaScope(); } protected Map symbols = new TreeMap<>(); diff --git a/src/main/java/decaf/frontend/scope/ScopeStack.java b/src/main/java/decaf/frontend/scope/ScopeStack.java index dc856ad..b2e564f 100644 --- a/src/main/java/decaf/frontend/scope/ScopeStack.java +++ b/src/main/java/decaf/frontend/scope/ScopeStack.java @@ -77,12 +77,12 @@ public MethodSymbol currentMethod() { * Otherwise, only push the `scope`. *

* REQUIRES: you don't open multiple class scopes, and never open a class scope when the current scope is - * a formal/local scope. + * a formal/local/lambda scope. */ public void open(Scope scope) { assert !scope.isGlobalScope(); if (scope.isClassScope()) { - assert !currentScope().isFormalOrLocalScope(); + assert !currentScope().isFormalOrLocalOrLambdaScope(); var classScope = (ClassScope) scope; classScope.parentScope.ifPresent(this::open); currClass = classScope.getOwner(); @@ -122,20 +122,22 @@ public Optional lookup(String key) { } /** - * Same with {@link #lookup} but we restrict the symbol's position to be before the given {@code pos}. + * Same with {@link #lookup} but we restrict the symbol's position to be before the given {@code pos} + * and the symbol is not being defined. * * @param key symbol's name * @param pos position - * @return innermost found symbol before {@code pos} (if any) + * @return innermost found symbol before {@code pos} and with a proper type (if any) */ public Optional lookupBefore(String key, Pos pos) { - return findWhile(key, whatever -> true, s -> !(s.domain().isLocalScope() && s.pos.compareTo(pos) >= 0)); + return findWhile(key, whatever -> true, + s -> !(s.domain().isLocalScope() && s.pos.compareTo(pos) >= 0) && s.type != null); } /** * Find if a symbol is conflicting with some already defined symbol. Rules: - * First, if the current scope is local scope or formal scope, then it cannot conflict with any already defined - * symbol till the formal scope, and it cannot conflict with any names in the global scope. + * First, if the current scope is local scope or formal scope or lambda scope, then it cannot conflict with any + * already defined symbol till the formal scope, and it cannot conflict with any names in the global scope. *

* Second, if the current scope is class scope or global scope, then it cannot conflict with any already defined * symbol. @@ -146,8 +148,8 @@ public Optional lookupBefore(String key, Pos pos) { * @return innermost conflicting symbol (if any) */ public Optional findConflict(String key) { - if (currentScope().isFormalOrLocalScope()) - return findWhile(key, Scope::isFormalOrLocalScope, whatever -> true).or(() -> global.find(key)); + if (currentScope().isFormalOrLocalOrLambdaScope()) + return findWhile(key, Scope::isFormalOrLocalOrLambdaScope, whatever -> true).or(() -> global.find(key)); return lookup(key); } diff --git a/src/main/java/decaf/frontend/symbol/ClassSymbol.java b/src/main/java/decaf/frontend/symbol/ClassSymbol.java index 19dca9b..361747a 100644 --- a/src/main/java/decaf/frontend/symbol/ClassSymbol.java +++ b/src/main/java/decaf/frontend/symbol/ClassSymbol.java @@ -3,6 +3,7 @@ import decaf.frontend.scope.ClassScope; import decaf.frontend.scope.GlobalScope; import decaf.frontend.tree.Pos; +import decaf.frontend.tree.Tree; import decaf.frontend.type.ClassType; import decaf.lowlevel.tac.ClassInfo; @@ -23,19 +24,24 @@ public final class ClassSymbol extends Symbol { */ public final ClassScope scope; - public ClassSymbol(String name, ClassType type, ClassScope scope, Pos pos) { + public final Tree.Modifiers modifiers; + + public ClassSymbol(String name, ClassType type, ClassScope scope, Pos pos, Tree.Modifiers modifiers) { super(name, type, pos); this.parentSymbol = Optional.empty(); - this.scope = scope; this.type = type; + this.scope = scope; + this.modifiers = modifiers; scope.setOwner(this); } - public ClassSymbol(String name, ClassSymbol parentSymbol, ClassType type, ClassScope scope, Pos pos) { + public ClassSymbol(String name, ClassSymbol parentSymbol, ClassType type, ClassScope scope, Pos pos, + Tree.Modifiers modifiers) { super(name, type, pos); this.parentSymbol = Optional.of(parentSymbol); - this.scope = scope; this.type = type; + this.scope = scope; + this.modifiers = modifiers; scope.setOwner(this); } @@ -49,6 +55,10 @@ public boolean isClassSymbol() { return true; } + public boolean isAbstract() { + return modifiers.isAbstract(); + } + /** * Set as main class, by {@link decaf.frontend.typecheck.Namer}. */ @@ -67,7 +77,10 @@ public boolean isMainClass() { @Override protected String str() { - return "class " + name + parentSymbol.map(classSymbol -> " : " + classSymbol.name).orElse(""); + var modStr = modifiers.toString(); + if (!modStr.isEmpty()) modStr += " "; + return modStr + "class " + name + + parentSymbol.map(classSymbol -> " : " + classSymbol.name).orElse(""); } /** diff --git a/src/main/java/decaf/frontend/symbol/LambdaSymbol.java b/src/main/java/decaf/frontend/symbol/LambdaSymbol.java new file mode 100644 index 0000000..3564ef4 --- /dev/null +++ b/src/main/java/decaf/frontend/symbol/LambdaSymbol.java @@ -0,0 +1,49 @@ +package decaf.frontend.symbol; + +import decaf.frontend.scope.ClassScope; +import decaf.frontend.scope.FormalScope; +import decaf.frontend.scope.LambdaScope; +import decaf.frontend.tree.Pos; +import decaf.frontend.tree.Tree; +import decaf.frontend.type.FunType; + +/** + * Lambda symbol, representing a lambda expression definition. + */ +public final class LambdaSymbol extends Symbol { + + public FunType type; + + /** + * Associated lambda scope of the lambda expression parameters. + */ + public final LambdaScope scope; + + public final ClassSymbol owner; + + public LambdaSymbol(LambdaScope scope, Pos pos, ClassSymbol owner) { + super("lambda@" + pos, null, pos); + this.scope = scope; + this.owner = owner; + } + + public void setType(FunType type) { + this.type = type; + super.type = type; + } + + @Override + public ClassScope domain() { + return (ClassScope) definedIn; + } + + @Override + public boolean isLambdaSymbol() { + return true; + } + + @Override + protected String str() { + return String.format("function %s : %s", name, type); + } +} diff --git a/src/main/java/decaf/frontend/symbol/MethodSymbol.java b/src/main/java/decaf/frontend/symbol/MethodSymbol.java index 2d274cb..1e50cdf 100644 --- a/src/main/java/decaf/frontend/symbol/MethodSymbol.java +++ b/src/main/java/decaf/frontend/symbol/MethodSymbol.java @@ -69,5 +69,9 @@ public boolean isStatic() { return modifiers.isStatic(); } + public boolean isAbstract() { + return modifiers.isAbstract(); + } + private boolean main = false; } diff --git a/src/main/java/decaf/frontend/symbol/Symbol.java b/src/main/java/decaf/frontend/symbol/Symbol.java index 3af14e3..b44ea28 100644 --- a/src/main/java/decaf/frontend/symbol/Symbol.java +++ b/src/main/java/decaf/frontend/symbol/Symbol.java @@ -7,20 +7,21 @@ /** * Symbols. *

- * A symbol is created when a definition is identified and type-checked, indicating a class/variable/method is - * resolved successfully, by {@link decaf.frontend.typecheck.Namer}. + * A symbol is created when a definition is identified and type-checked, indicating a class/variable/method/ + * lambda expression is resolved successfully, by {@link decaf.frontend.typecheck.Namer}. *

* Symbols are used in two ways: stored in the symbol table of a scope, and referred by other expressions/statements. * * @see ClassSymbol * @see MethodSymbol * @see VarSymbol + * @see LambdaSymbol */ public abstract class Symbol implements Comparable { public final String name; - public final Type type; + public Type type; public final Pos pos; @@ -55,6 +56,10 @@ public boolean isMethodSymbol() { return false; } + public boolean isLambdaSymbol() { + return false; + } + /** * Get string representation of a symbol, excluding the position. * diff --git a/src/main/java/decaf/frontend/symbol/VarSymbol.java b/src/main/java/decaf/frontend/symbol/VarSymbol.java index 0bc01d1..49aa25e 100644 --- a/src/main/java/decaf/frontend/symbol/VarSymbol.java +++ b/src/main/java/decaf/frontend/symbol/VarSymbol.java @@ -16,6 +16,15 @@ public VarSymbol(String name, Type type, Pos pos) { super(name, type, pos); } + public VarSymbol(String name, Pos pos) { + // For type deducing. + super(name, null, pos); + } + + public void setType(Type type) { + super.type = type; + } + /** * Create a variable symbol for {@code this}. * @@ -42,7 +51,7 @@ public boolean isLocalVar() { } public boolean isParam() { - return definedIn.isFormalScope(); + return definedIn.isFormalScope() || definedIn.isLambdaScope(); } public boolean isMemberVar() { diff --git a/src/main/java/decaf/frontend/tacgen/TacEmitter.java b/src/main/java/decaf/frontend/tacgen/TacEmitter.java index edb3dae..6ed574c 100644 --- a/src/main/java/decaf/frontend/tacgen/TacEmitter.java +++ b/src/main/java/decaf/frontend/tacgen/TacEmitter.java @@ -1,5 +1,6 @@ package decaf.frontend.tacgen; +import decaf.frontend.symbol.VarSymbol; import decaf.frontend.tree.Tree; import decaf.frontend.tree.Visitor; import decaf.frontend.type.BuiltInType; @@ -10,8 +11,7 @@ import decaf.lowlevel.tac.RuntimeError; import decaf.lowlevel.tac.TacInstr; -import java.util.ArrayList; -import java.util.Stack; +import java.util.*; import java.util.function.Consumer; import java.util.function.Function; @@ -33,6 +33,14 @@ public interface TacEmitter extends Visitor { */ Stack

+ * Push a Tree.Lambda when entering a lambda expression, and pop when leaving a lambda expression. + */ + Stack lambdaStack = new Stack<>(); + @Override default void visitBlock(Tree.Block block, FuncVisitor mv) { for (var stmt : block.stmts) { @@ -233,17 +241,62 @@ default void visitBinary(Tree.Binary expr, FuncVisitor mv) { }; expr.lhs.accept(this, mv); expr.rhs.accept(this, mv); + + if (expr.op.equals(Tree.BinaryOp.DIV) || expr.op.equals(Tree.BinaryOp.MOD)) { + var ok = mv.freshLabel(); + mv.visitBranch(TacInstr.CondBranch.Op.BNEZ, expr.rhs.val, ok); + mv.visitPrint(RuntimeError.DIVISION_BY_ZERO); + mv.visitIntrinsicCall(Intrinsic.HALT); + mv.visitLabel(ok); + } expr.val = mv.visitBinary(op, expr.lhs.val, expr.rhs.val); } @Override default void visitVarSel(Tree.VarSel expr, FuncVisitor mv) { + if (expr.isArrayLength) { + // a: 3, arrayEntry + var eight = mv.visitLoad(8); + var a = mv.visitIntrinsicCall(Intrinsic.ALLOCATE, true, eight); + var three = mv.visitLoad(3); + mv.visitStoreTo(a, three); + var object = expr.receiver.get(); + object.accept(this, mv); + mv.visitStoreTo(a, 4, object.val); + expr.val = a; + return; + } + if (expr.isMethod) { + if (expr.isStaticMethod) { + // a: 0, funcEntry + var eight = mv.visitLoad(8); + var a = mv.visitIntrinsicCall(Intrinsic.ALLOCATE, true, eight); + var zero = mv.visitLoad(0); + mv.visitStoreTo(a, zero); + var entry = mv.visitFuncEntry(expr.receiverClassName.get(), expr.name); + mv.visitStoreTo(a, 4, entry); + expr.val = a; + } else { + // a: 1, funcEntry, this + var twelve = mv.visitLoad(12); + var a = mv.visitIntrinsicCall(Intrinsic.ALLOCATE, true, twelve); + var one = mv.visitLoad(1); + mv.visitStoreTo(a, one); + var object = expr.receiver.get(); + object.accept(this, mv); + var entry = mv.visitFuncEntry(object.val, expr.receiverClassName.get(), expr.name); + mv.visitStoreTo(a, 4, entry); + mv.visitStoreTo(a, 8, object.val); + expr.val = a; + } + return; + } if (expr.symbol.isMemberVar()) { var object = expr.receiver.get(); object.accept(this, mv); expr.val = mv.visitMemberAccess(object.val, expr.symbol.getOwner().name, expr.name); } else { // local or param - expr.val = expr.symbol.temp; + expr.val = getCapturedVar(expr.name, mv).orElse(expr.symbol.temp); } } @@ -255,6 +308,46 @@ default void visitIndexSel(Tree.IndexSel expr, FuncVisitor mv) { expr.val = mv.visitLoadFrom(addr); } + @Override + default void visitLambda(Tree.Lambda expr, FuncVisitor mv) { + var capturedVars = expr.scope.capturedVars(); + + var originalFunc = mv.freshLabel(); + mv.visitBranch(originalFunc); + int numParams = capturedVars.size(); + lambdaStack.push(expr); + FuncVisitor lambdaMv = mv.visitLambdaFunc(expr.pos, numParams + expr.params.size()); + for (var param : expr.params) { + param.symbol.temp = lambdaMv.getArgTemp(numParams); + numParams++; + } + if (expr.hasReturnExpr()) { + expr.returnExpr.get().accept(this, lambdaMv); + lambdaMv.visitReturn(expr.returnExpr.get().val); + } else { + expr.body.get().accept(this, lambdaMv); + } + lambdaMv.visitEnd(); + lambdaStack.pop(); + mv.visitLabel(originalFunc); + + // a: 2, funcEntry, numCaptured, arg1, arg2, ... + var size = mv.visitLoad(12 + 4 * capturedVars.size()); + var a = mv.visitIntrinsicCall(Intrinsic.ALLOCATE, true, size); + var two = mv.visitLoad(2); + mv.visitStoreTo(a, two); + var entry = mv.visitFuncEntry(expr.pos); + mv.visitStoreTo(a, 4, entry); + var numCaptured = mv.visitLoad(capturedVars.size()); + mv.visitStoreTo(a, 8, numCaptured); + for (int i = 0; i < capturedVars.size(); i++ ) { + var varI = capturedVars.get(i); + var argI = getCapturedVar(varI.name, mv).orElse(varI.temp); + mv.visitStoreTo(a, 12 + i * 4, argI); + } + expr.val = a; + } + @Override default void visitNewArray(Tree.NewArray expr, FuncVisitor mv) { expr.length.accept(this, mv); @@ -268,37 +361,80 @@ default void visitNewClass(Tree.NewClass expr, FuncVisitor mv) { @Override default void visitThis(Tree.This expr, FuncVisitor mv) { - expr.val = mv.getArgTemp(0); + expr.val = getCapturedVar("this", mv).orElse(mv.getArgTemp(0)); } @Override default void visitCall(Tree.Call expr, FuncVisitor mv) { - if (expr.isArrayLength) { // special case for array.length() - var array = expr.receiver.get(); - array.accept(this, mv); - expr.val = mv.visitLoadFrom(array.val, -4); - return; - } + expr.methodExpr.accept(this, mv); expr.args.forEach(arg -> arg.accept(this, mv)); var temps = new ArrayList(); expr.args.forEach(arg -> temps.add(arg.val)); + var type = mv.visitLoadFrom(expr.methodExpr.val); + var entry = mv.visitLoadFrom(expr.methodExpr.val, 4); - if (expr.symbol.isStatic()) { - if (expr.symbol.type.returnType.isVoidType()) { - mv.visitStaticCall(expr.symbol.owner.name, expr.symbol.name, temps); - } else { - expr.val = mv.visitStaticCall(expr.symbol.owner.name, expr.symbol.name, temps, true); - } + var exit = mv.freshLabel(); + if (!expr.type.isVoidType()) { + expr.val = mv.freshTemp(); + } + var notStatic = mv.freshLabel(); + mv.visitBranch(TacInstr.CondBranch.Op.BNEZ, type, notStatic); + // static: 0, funcEntry + if (expr.type.isVoidType()) { + mv.visitCall(entry, temps, false); } else { - var object = expr.receiver.get(); - object.accept(this, mv); - if (expr.symbol.type.returnType.isVoidType()) { - mv.visitMemberCall(object.val, expr.symbol.owner.name, expr.symbol.name, temps); - } else { - expr.val = mv.visitMemberCall(object.val, expr.symbol.owner.name, expr.symbol.name, temps, true); - } + mv.visitAssign(expr.val, mv.visitCall(entry, temps, true)); + } + mv.visitBranch(exit); + mv.visitLabel(notStatic); + + var minusOne = mv.visitLoad(-1); + var typeMinusOne = mv.visitBinary(TacInstr.Binary.Op.ADD, type, minusOne); + var notNonStatic = mv.freshLabel(); + mv.visitBranch(TacInstr.CondBranch.Op.BNEZ, typeMinusOne, notNonStatic); + // non-static: 1, funcEntry, this + var thisPointer = mv.visitLoadFrom(expr.methodExpr.val, 8); + mv.visitParm(thisPointer); + if (expr.type.isVoidType()) { + mv.visitCall(entry, temps, false); + } else { + mv.visitAssign(expr.val, mv.visitCall(entry, temps, true)); + } + mv.visitBranch(exit); + mv.visitLabel(notNonStatic); + + var minusTwo = mv.visitLoad(-2); + var typeMinusTwo = mv.visitBinary(TacInstr.Binary.Op.ADD, type, minusTwo); + var notLambda = mv.freshLabel(); + mv.visitBranch(TacInstr.CondBranch.Op.BNEZ, typeMinusTwo, notLambda); + // lambda: 2, funcEntry, numCaptured, arg1, arg2, ... + var numCaptured = mv.visitLoadFrom(expr.methodExpr.val, 8); + var twelve = mv.visitLoad(12); + var four = mv.visitLoad(4); + var currentArgPos = mv.visitBinary(TacInstr.Binary.Op.ADD, expr.methodExpr.val, twelve); + Function test = v -> numCaptured; + Consumer body = v -> { + var currentArg = v.visitLoadFrom(currentArgPos); + v.visitParm(currentArg); + v.visitBinarySelf(TacInstr.Binary.Op.ADD, numCaptured, minusOne); + v.visitBinarySelf(TacInstr.Binary.Op.ADD, currentArgPos, four); + }; + emitWhile(test, body, mv.freshLabel(), mv); + if (expr.type.isVoidType()) { + mv.visitCall(entry, temps, false); + } else { + mv.visitAssign(expr.val, mv.visitCall(entry, temps, true)); + } + mv.visitBranch(exit); + mv.visitLabel(notLambda); + + // array: 3, arrayEntry + if (!expr.type.isVoidType()) { + mv.visitAssign(expr.val, mv.visitLoadFrom(entry, -4)); } + + mv.visitLabel(exit); } @Override @@ -598,4 +734,21 @@ private Temp emitClassTest(Temp object, String clazz, FuncVisitor mv) { return ret; } + + private Optional getCapturedVar(List capturedVars, String name, FuncVisitor mv) { + for (int i = 0; i < capturedVars.size(); i++) { + if (capturedVars.get(i).name.equals(name)) { + return Optional.of(mv.getArgTemp(i)); + } + } + return Optional.empty(); + } + + private Optional getCapturedVar(String name, FuncVisitor mv) { + if (lambdaStack.empty()) { + return name.equals("this") ? Optional.of(mv.getArgTemp(0)) : Optional.empty(); + } else { + return getCapturedVar(lambdaStack.peek().scope.capturedVars(), name, mv); + } + } } diff --git a/src/main/java/decaf/frontend/tacgen/TacGen.java b/src/main/java/decaf/frontend/tacgen/TacGen.java index 51f8fe7..173831d 100644 --- a/src/main/java/decaf/frontend/tacgen/TacGen.java +++ b/src/main/java/decaf/frontend/tacgen/TacGen.java @@ -29,6 +29,17 @@ public TacProg transform(Tree.TopLevel tree) { // Step 1: create virtual tables. pw.visitVTables(); + for (var clazz : tree.classes) { + for (var method : clazz.methods()) { + if (method.isStatic()) { + pw.visitStaticMethod(clazz.name, method.name); + } + } + } + for (var lambdaSymbol : tree.globalScope.lambdaSymbols) { + pw.visitLambda(lambdaSymbol.pos); + } + pw.visitVTablesPostProcess(); // Step 2: emit tac instructions for every method. for (var clazz : tree.classes) { @@ -52,7 +63,7 @@ public TacProg transform(Tree.TopLevel tree) { } } - method.body.accept(this, mv); + method.body.ifPresent(objects -> objects.accept(this, mv)); mv.visitEnd(); } } diff --git a/src/main/java/decaf/frontend/tree/Tree.java b/src/main/java/decaf/frontend/tree/Tree.java index a5e5156..5375cb6 100644 --- a/src/main/java/decaf/frontend/tree/Tree.java +++ b/src/main/java/decaf/frontend/tree/Tree.java @@ -1,10 +1,10 @@ package decaf.frontend.tree; +import decaf.backend.dataflow.Loc; import decaf.frontend.scope.GlobalScope; import decaf.frontend.scope.LocalScope; -import decaf.frontend.symbol.ClassSymbol; -import decaf.frontend.symbol.MethodSymbol; -import decaf.frontend.symbol.VarSymbol; +import decaf.frontend.scope.LambdaScope; +import decaf.frontend.symbol.*; import decaf.frontend.type.FunType; import decaf.frontend.type.Type; import decaf.lowlevel.instr.Temp; @@ -22,9 +22,9 @@ public abstract class Tree { public enum Kind { TOP_LEVEL, CLASS_DEF, VAR_DEF, METHOD_DEF, - T_INT, T_BOOL, T_STRING, T_VOID, T_CLASS, T_ARRAY, + T_INT, T_BOOL, T_STRING, T_VOID, T_CLASS, T_ARRAY, T_LAMBDA, LOCAL_VAR_DEF, BLOCK, ASSIGN, EXPR_EVAL, SKIP, IF, WHILE, FOR, BREAK, RETURN, PRINT, - INT_LIT, BOOL_LIT, STRING_LIT, NULL_LIT, VAR_SEL, INDEX_SEL, CALL, + INT_LIT, BOOL_LIT, STRING_LIT, NULL_LIT, VAR_SEL, INDEX_SEL, CALL, LAMBDA, THIS, UNARY_EXPR, BINARY_EXPR, READ_INT, READ_LINE, NEW_CLASS, NEW_ARRAY, CLASS_TEST, CLASS_CAST } @@ -70,6 +70,7 @@ public void accept(Visitor v, C ctx) { */ public static class ClassDef extends TreeNode { // Tree elements + public Modifiers modifiers; public final Id id; public Optional parent; public final List fields; @@ -78,10 +79,12 @@ public static class ClassDef extends TreeNode { // For type check public ClassDef superClass; public ClassSymbol symbol; + public List unOverriddenMethods; public boolean resolved = false; - public ClassDef(Id id, Optional parent, List fields, Pos pos) { + public ClassDef(int modifiers, Id id, Optional parent, List fields, Pos pos) { super(Kind.CLASS_DEF, "ClassDef", pos); + this.modifiers = new Modifiers(modifiers, pos); this.id = id; this.parent = parent; this.fields = fields; @@ -92,6 +95,10 @@ public boolean hasParent() { return parent.isPresent(); } + public boolean isAbstract() { + return modifiers.isAbstract(); + } + public List methods() { var methods = new ArrayList(); for (var field : fields) { @@ -105,16 +112,17 @@ public List methods() { @Override public Object treeElementAt(int index) { return switch (index) { - case 0 -> id; - case 1 -> parent; - case 2 -> fields; + case 0 -> modifiers; + case 1 -> id; + case 2 -> parent; + case 3 -> fields; default -> throw new IndexOutOfBoundsException(index); }; } @Override public int treeArity() { - return 3; + return 4; } @Override @@ -191,16 +199,17 @@ public static class MethodDef extends Field { public Id id; public TypeLit returnType; public List params; - public Block body; + public Optional body; // For convenience public String name; // For type check public FunType type; public MethodSymbol symbol; - public MethodDef(boolean isStatic, Id id, TypeLit returnType, List params, Block body, Pos pos) { + public MethodDef(int modifiers, Id id, TypeLit returnType, List params, + Optional body, Pos pos) { super(Kind.METHOD_DEF, "MethodDef", pos); - this.modifiers = isStatic ? new Modifiers(Modifiers.STATIC, pos) : new Modifiers(); + this.modifiers = new Modifiers(modifiers, pos); this.id = id; this.returnType = returnType; this.params = params; @@ -212,6 +221,10 @@ public boolean isStatic() { return modifiers.isStatic(); } + public boolean isAbstract() { + return modifiers.isAbstract(); + } + @Override public Object treeElementAt(int index) { return switch (index) { @@ -239,8 +252,9 @@ public void accept(Visitor v, C ctx) { *

* Decaf only supports * - basic types (integer, boolean, string, void), - * - class types (using class identifiers), and - * - array types (whose element could be any type, but homogeneous). + * - class types (using class identifiers), + * - array types (whose element could be any type, but homogeneous), and + * - function types (lambda expression). */ public static abstract class TypeLit extends TreeNode { public Type type; @@ -416,6 +430,43 @@ public void accept(Visitor v, C ctx) { } } + /** + * Function type. + *

+     *     returnType '(' paramType1 ',' paramType2 ',' ... ')'
+     * 
+ */ + public static class TLambda extends TypeLit { + // Tree element + public TypeLit returnType; + public List argTypes; + + public TLambda(TypeLit returnType, List argTypes, Pos pos) { + super(Kind.T_LAMBDA, "TLambda", pos); + this.returnType = returnType; + this.argTypes = argTypes; + } + + @Override + public Object treeElementAt(int index) { + return switch (index) { + case 0 -> returnType; + case 1 -> argTypes; + default -> throw new IndexOutOfBoundsException(index); + }; + } + + @Override + public int treeArity() { + return 2; + } + + @Override + public void accept(Visitor v, C ctx) { + v.visitTLambda(this, ctx); + } + } + /** * Statement. @@ -429,6 +480,17 @@ public Stmt(Kind kind, String displayName, Pos pos) { * For type check: does this return a value? */ public boolean returns = false; + public Type returnType; + + public void updateReturnType(Stmt that) { + if (that.returnType == null) + return; + if (returnType == null) { + returnType = that.returnType; + return; + } + returnType = returnType.upperBound(that.returnType); + } public boolean isBlock() { return false; @@ -444,7 +506,7 @@ public boolean isBlock() { */ public static class LocalVarDef extends Stmt { // Tree elements - public TypeLit typeLit; + public Optional typeLit; public Id id; public Pos assignPos; public Optional initVal; @@ -453,7 +515,7 @@ public static class LocalVarDef extends Stmt { // For type check public VarSymbol symbol; - public LocalVarDef(TypeLit typeLit, Id id, Pos assignPos, Optional initVal, Pos pos) { + public LocalVarDef(Optional typeLit, Id id, Pos assignPos, Optional initVal, Pos pos) { // pos = id.pos, assignPos = position of the '=' // TODO: looks not very consistent, maybe we shall always report error simply at `pos`, not `assignPos`? super(Kind.LOCAL_VAR_DEF, "LocalVarDef", pos); @@ -465,7 +527,7 @@ public LocalVarDef(TypeLit typeLit, Id id, Pos assignPos, Optional initVal } public LocalVarDef(TypeLit typeLit, Id id, Pos pos) { - this(typeLit, id, Pos.NoPos, Optional.empty(), pos); + this(Optional.of(typeLit), id, Pos.NoPos, Optional.empty(), pos); } @Override @@ -501,6 +563,11 @@ public static class Block extends Stmt { // For type check public LocalScope scope; + public Block() { + super(Kind.BLOCK, "Empty Block", Pos.NoPos); + // for abstract methods + } + public Block(List stmts, Pos pos) { super(Kind.BLOCK, "Block", pos); this.stmts = stmts; @@ -1030,6 +1097,10 @@ public static class VarSel extends LValue { // For type check public VarSymbol symbol; public boolean isClassName = false; + public boolean isArrayLength = false; + public boolean isMethod = false; + public boolean isStaticMethod = false; + public Optional receiverClassName; public VarSel(Optional receiver, Id variable, Pos pos) { super(Kind.VAR_SEL, "VarSel", pos); @@ -1465,67 +1536,102 @@ public void accept(Visitor v, C ctx) { } } - /** * Call expression. *
-     *     {receiver '.'}? id '(' arg1 ',' arg2 ',' ... ')'
+     *     methodExpr '(' arg1 ',' arg2 ',' ... ')'
      * 
*/ public static class Call extends Expr { // Tree elements - public Optional receiver; - public Id method; + public Expr methodExpr; public List args; - // - public String methodName; // For type check public MethodSymbol symbol; public boolean isArrayLength = false; - public Call(Optional receiver, Id method, List args, Pos pos) { + public Call(Expr methodExpr, List args, Pos pos) { super(Kind.CALL, "Call", pos); - this.receiver = receiver; - this.method = method; + this.methodExpr = methodExpr; this.args = args; - this.methodName = method.name; } - public Call(Id method, List args, Pos pos) { - this(Optional.empty(), method, args, pos); + @Override + public Object treeElementAt(int index) { + return switch (index) { + case 0 -> methodExpr; + case 1 -> args; + default -> throw new IndexOutOfBoundsException(index); + }; } - public Call(Expr receiver, Id method, List args, Pos pos) { - this(Optional.of(receiver), method, args, pos); + @Override + public int treeArity() { + return 2; } - /** - * Set its receiver as {@code this}. - *

- * Reversed for type check. - */ - public void setThis() { - this.receiver = Optional.of(new This(pos)); + @Override + public void accept(Visitor v, C ctx) { + v.visitCall(this, ctx); + } + } + + /** + * Lambda expression. + *

+     *     'fun' '(' type1 id1 ',' type2 id2 ',' ... ')' => returnExpr
+     *     'fun' '(' type1 id1 ',' type2 id2 ',' ... ')' '{' body '}'
+     * 
+ */ + public static class Lambda extends Expr { + // Tree elements + public List params; + public Optional returnExpr; + public Optional body; + // For type check + // We can only get a scope in Namer.java. + // Since the return type is deduced in Typer.java, we cannot get a final symbol in Namer.java. + public LambdaSymbol symbol; + public LambdaScope scope; + + public Lambda(List params, Expr returnExpr, Pos pos) { + super(Kind.LAMBDA, "Lambda", pos); + this.params = params; + this.returnExpr = Optional.of(returnExpr); + this.body = Optional.empty(); + } + + public Lambda(List params, Block body, Pos pos) { + super(Kind.LAMBDA, "Lambda", pos); + this.params = params; + this.returnExpr = Optional.empty(); + this.body = Optional.of(body); + } + + public boolean hasReturnExpr() { + return returnExpr.isPresent(); + } + + public boolean hasBody() { + return body.isPresent(); } @Override public Object treeElementAt(int index) { return switch (index) { - case 0 -> receiver; - case 1 -> method; - case 2 -> args; + case 0 -> params; + case 1 -> (hasReturnExpr() ? returnExpr : body); default -> throw new IndexOutOfBoundsException(index); }; } @Override public int treeArity() { - return 3; + return 2; } - @Override public void accept(Visitor v, C ctx) { - v.visitCall(this, ctx); + v.visitLambda(this, ctx); } } @@ -1566,12 +1672,14 @@ public static class Modifiers { // Available modifiers: public static final int STATIC = 1; + public static final int ABSTRACT = 2; public Modifiers(int code, Pos pos) { this.code = code; this.pos = pos; flags = new ArrayList<>(); if (isStatic()) flags.add("STATIC"); + if (isAbstract()) flags.add("ABSTRACT"); } public Modifiers() { @@ -1579,7 +1687,11 @@ public Modifiers() { } public boolean isStatic() { - return (code & 1) == 1; + return (code & STATIC) == STATIC; + } + + public boolean isAbstract() { + return (code & ABSTRACT) == ABSTRACT; } @Override diff --git a/src/main/java/decaf/frontend/tree/Visitor.java b/src/main/java/decaf/frontend/tree/Visitor.java index 78c5686..81f60ec 100644 --- a/src/main/java/decaf/frontend/tree/Visitor.java +++ b/src/main/java/decaf/frontend/tree/Visitor.java @@ -50,6 +50,10 @@ default void visitTArray(Tree.TArray that, C ctx) { visitOthers(that, ctx); } + default void visitTLambda(Tree.TLambda that, C ctx) { + visitOthers(that, ctx); + } + default void visitLocalVarDef(Tree.LocalVarDef that, C ctx) { visitOthers(that, ctx); } @@ -158,6 +162,10 @@ default void visitClassCast(Tree.ClassCast that, C ctx) { visitOthers(that, ctx); } + default void visitLambda(Tree.Lambda that, C ctx) { + visitOthers(that, ctx); + } + /* The default handler */ default void visitOthers(TreeNode that, C ctx) { // do nothing diff --git a/src/main/java/decaf/frontend/type/BuiltInType.java b/src/main/java/decaf/frontend/type/BuiltInType.java index e466641..b63a0ce 100644 --- a/src/main/java/decaf/frontend/type/BuiltInType.java +++ b/src/main/java/decaf/frontend/type/BuiltInType.java @@ -41,11 +41,23 @@ private BuiltInType(String name) { */ public static final BuiltInType ERROR = new BuiltInType("Error"); + /** + * Ill-typed, reserved for type checking (ONLY in return type deduction of lambda expression). + * A well-typed program can never contain this. + */ + public static final BuiltInType INCMP = new BuiltInType("Incompatible"); + @Override public boolean subtypeOf(Type that) { if (eq(ERROR) || that.eq(ERROR)) { return true; } + if (that.eq(INCMP)) { + return true; + } + if (eq(INCMP)) { + return false; + } if (eq(NULL) && that.isClassType()) { return true; } @@ -77,6 +89,11 @@ public boolean noError() { return !eq(ERROR); } + @Override + public boolean isIncompatible() { + return eq(INCMP); + } + @Override public String toString() { return name; diff --git a/src/main/java/decaf/frontend/type/Type.java b/src/main/java/decaf/frontend/type/Type.java index 9f527f5..965f683 100644 --- a/src/main/java/decaf/frontend/type/Type.java +++ b/src/main/java/decaf/frontend/type/Type.java @@ -1,5 +1,7 @@ package decaf.frontend.type; +import java.util.ArrayList; + /** * Type. *

@@ -49,6 +51,10 @@ public boolean noError() { return true; } + public boolean isIncompatible() { + return false; + } + public boolean hasError() { return !noError(); } @@ -62,6 +68,7 @@ public boolean hasError() { *

  • transitive: {@code t1} {@literal <:} {@code t3} if * {@code t1} {@literal <:} {@code t2} and {@code t2} {@literal <:} {@code t3}
  • *
  • error is special: {@code error} {@literal <:} {@code t}, {@code t} {@literal <:} {@code error}
  • + *
  • incmp is special: {@code t} {@literal <:} {@code incmp}
  • *
  • null is an object: {@code null} {@literal <:} {@code class c} for every class {@code c}
  • *
  • class inheritance: {@code class c1} {@literal <:} {@code class c2} if {@code c1} extends {@code c2}
  • *
  • function: {@code (t1, t2, ..., tn) => t} {@literal <:} {@code (s1, s2, ..., sn) => s} if @@ -81,5 +88,86 @@ public boolean hasError() { */ public abstract boolean eq(Type that); + /** + * Upper bound and lower bound of two types. + * + * @param that another type + * @return upper bound or lower bound + */ + public Type upperBound(Type that) { + if (hasError()) + return that; + if (that.hasError()) + return this; + if (subtypeOf(that)) + return that; + if (that.subtypeOf(this)) + return this; + if (isClassType()) { + ClassType result = (ClassType) this; + while (result.superType.isPresent()) { + result = result.superType.get(); + if (that.subtypeOf(result)) { + return result; + } + } + } + if (isFuncType() && that.isFuncType()) { + assert this instanceof FunType; + FunType thisFunc = (FunType) this; + FunType thatFunc = (FunType) that; + if (thisFunc.argTypes.size() != thatFunc.argTypes.size()) { + return BuiltInType.INCMP; + } + Type returnType = thisFunc.returnType.upperBound(thatFunc.returnType); + if (returnType.isIncompatible()) { + return BuiltInType.INCMP; + } + var argTypes = new ArrayList(); + for (int i = 0; i < thisFunc.argTypes.size(); i++) { + argTypes.add(thisFunc.argTypes.get(i).lowerBound(thatFunc.argTypes.get(i))); + if (argTypes.get(i).isIncompatible()) { + return BuiltInType.INCMP; + } + } + return new FunType(returnType, argTypes); + } + return BuiltInType.INCMP; + } + + public Type lowerBound(Type that) { + if (hasError()) + return that; + if (that.hasError()) + return this; + if (isIncompatible() || that.isIncompatible()) + return BuiltInType.INCMP; + if (subtypeOf(that)) + return this; + if (that.subtypeOf(this)) + return that; + if (isFuncType() && that.isFuncType()) { + assert this instanceof FunType; + FunType thisFunc = (FunType) this; + FunType thatFunc = (FunType) that; + if (thisFunc.argTypes.size() != thatFunc.argTypes.size()) { + return BuiltInType.INCMP; + } + Type returnType = thisFunc.returnType.lowerBound(thatFunc.returnType); + if (returnType.isIncompatible()) { + return BuiltInType.INCMP; + } + var argTypes = new ArrayList(); + for (int i = 0; i < thisFunc.argTypes.size(); i++) { + argTypes.add(thisFunc.argTypes.get(i).upperBound(thatFunc.argTypes.get(i))); + if (argTypes.get(i).isIncompatible()) { + return BuiltInType.INCMP; + } + } + return new FunType(returnType, argTypes); + } + return BuiltInType.INCMP; + } + public abstract String toString(); } diff --git a/src/main/java/decaf/frontend/typecheck/Namer.java b/src/main/java/decaf/frontend/typecheck/Namer.java index 3dc295f..a9818cd 100644 --- a/src/main/java/decaf/frontend/typecheck/Namer.java +++ b/src/main/java/decaf/frontend/typecheck/Namer.java @@ -5,6 +5,7 @@ import decaf.driver.error.*; import decaf.frontend.scope.*; import decaf.frontend.symbol.ClassSymbol; +import decaf.frontend.symbol.LambdaSymbol; import decaf.frontend.symbol.MethodSymbol; import decaf.frontend.symbol.VarSymbol; import decaf.frontend.tree.Tree; @@ -13,10 +14,7 @@ import decaf.frontend.type.FunType; import decaf.frontend.type.Type; -import java.util.ArrayList; -import java.util.Map; -import java.util.Optional; -import java.util.TreeMap; +import java.util.*; /** * The namer phase: resolve all symbols defined in the abstract syntax tree and store them in symbol tables (i.e. @@ -87,7 +85,7 @@ public void visitTopLevel(Tree.TopLevel program, ScopeStack ctx) { // static void main() { ... } boolean found = false; for (var clazz : classes.values()) { - if (clazz.name.equals("Main")) { + if (clazz.name.equals("Main") && !clazz.isAbstract()) { var symbol = clazz.symbol.scope.find("main"); if (symbol.isPresent() && symbol.get().isMethodSymbol()) { var method = (MethodSymbol) symbol.get(); @@ -158,13 +156,13 @@ private void createClassSymbol(Tree.ClassDef clazz, GlobalScope global) { var base = global.getClass(clazz.parent.get().name); var type = new ClassType(clazz.name, base.type); var scope = new ClassScope(base.scope); - var symbol = new ClassSymbol(clazz.name, base, type, scope, clazz.pos); + var symbol = new ClassSymbol(clazz.name, base, type, scope, clazz.pos, clazz.modifiers); global.declare(symbol); clazz.symbol = symbol; } else { var type = new ClassType(clazz.name); var scope = new ClassScope(); - var symbol = new ClassSymbol(clazz.name, type, scope, clazz.pos); + var symbol = new ClassSymbol(clazz.name, type, scope, clazz.pos, clazz.modifiers); global.declare(symbol); clazz.symbol = symbol; } @@ -174,15 +172,41 @@ private void createClassSymbol(Tree.ClassDef clazz, GlobalScope global) { public void visitClassDef(Tree.ClassDef clazz, ScopeStack ctx) { if (clazz.resolved) return; + clazz.unOverriddenMethods = new ArrayList<>(); + if (clazz.hasParent()) { clazz.superClass.accept(this, ctx); + clazz.unOverriddenMethods.addAll(clazz.superClass.unOverriddenMethods); } ctx.open(clazz.symbol.scope); for (var field : clazz.fields) { field.accept(this, ctx); + if (field instanceof Tree.MethodDef && ((Tree.MethodDef) field).symbol != null) { + // A method without BadOverrideError or DeclConflictError. + Tree.MethodDef method = (Tree.MethodDef) field; + int methodIndex = -1; + for (int i = 0; i < clazz.unOverriddenMethods.size(); i++) { + if (clazz.unOverriddenMethods.get(i).name.equals(method.name)) { + methodIndex = i; + break; + } + } + if (method.isAbstract()) { + if (methodIndex == -1) { + clazz.unOverriddenMethods.add(method); + } + } else { + if (methodIndex != -1) { + clazz.unOverriddenMethods.remove(methodIndex); + } + } + } } ctx.close(); + if (!clazz.unOverriddenMethods.isEmpty() && !clazz.isAbstract()) { + issue(new NotAbstractClassError(clazz.pos, clazz.name)); + } clazz.resolved = true; } @@ -216,10 +240,12 @@ public void visitVarDef(Tree.VarDef varDef, ScopeStack ctx) { public void visitMethodDef(Tree.MethodDef method, ScopeStack ctx) { var earlier = ctx.findConflict(method.name); if (earlier.isPresent()) { - if (earlier.get().isMethodSymbol()) { // may be overriden + if (earlier.get().isMethodSymbol()) { // may be overridden var suspect = (MethodSymbol) earlier.get(); - if (suspect.domain() != ctx.currentScope() && !suspect.isStatic() && !method.isStatic()) { - // Only non-static methods can be overriden, but the type signature must be equivalent. + if (suspect.domain() != ctx.currentScope() && !suspect.isStatic() && !method.isStatic() && + !(method.isAbstract() && !suspect.isAbstract())) { + // Only non-static methods can be overridden, but the type signature must be equivalent. + // Abstract methods cannot override non-abstract methods. var formal = new FormalScope(); typeMethod(method, ctx, formal); if (method.type.subtypeOf(suspect.type)) { // override success @@ -227,9 +253,11 @@ public void visitMethodDef(Tree.MethodDef method, ScopeStack ctx) { ctx.currentClass()); ctx.declare(symbol); method.symbol = symbol; - ctx.open(formal); - method.body.accept(this, ctx); - ctx.close(); + if (!method.isAbstract()) { + ctx.open(formal); + method.body.get().accept(this, ctx); + ctx.close(); + } } else { issue(new BadOverrideError(method.pos, method.name, suspect.owner.name)); } @@ -248,9 +276,11 @@ public void visitMethodDef(Tree.MethodDef method, ScopeStack ctx) { ctx.currentClass()); ctx.declare(symbol); method.symbol = symbol; - ctx.open(formal); - method.body.accept(this, ctx); - ctx.close(); + if (!method.isAbstract()) { + ctx.open(formal); + method.body.get().accept(this, ctx); + ctx.close(); + } } private void typeMethod(Tree.MethodDef method, ScopeStack ctx, FormalScope formal) { @@ -260,12 +290,103 @@ private void typeMethod(Tree.MethodDef method, ScopeStack ctx, FormalScope forma var argTypes = new ArrayList(); for (var param : method.params) { param.accept(this, ctx); - argTypes.add(param.typeLit.type); + argTypes.add(param.typeLit.get().type); } method.type = new FunType(method.returnType.type, argTypes); ctx.close(); } + @Override + public void visitExprEval(Tree.ExprEval stmt, ScopeStack ctx) { + stmt.expr.accept(this, ctx); + } + + @Override + public void visitAssign(Tree.Assign stmt, ScopeStack ctx) { + stmt.lhs.accept(this, ctx); + stmt.rhs.accept(this, ctx); + } + + @Override + public void visitBinary(Tree.Binary expr, ScopeStack ctx) { + expr.lhs.accept(this, ctx); + expr.rhs.accept(this, ctx); + } + + @Override + public void visitCall(Tree.Call expr, ScopeStack ctx) { + expr.methodExpr.accept(this, ctx); + for (var arg : expr.args) { + arg.accept(this, ctx); + } + } + + @Override + public void visitClassCast(Tree.ClassCast expr, ScopeStack ctx) { + expr.obj.accept(this, ctx); + } + + @Override + public void visitClassTest(Tree.ClassTest expr, ScopeStack ctx) { + expr.obj.accept(this, ctx); + } + + @Override + public void visitIndexSel(Tree.IndexSel expr, ScopeStack ctx) { + expr.array.accept(this, ctx); + expr.index.accept(this, ctx); + } + + @Override + public void visitNewArray(Tree.NewArray expr, ScopeStack ctx) { + expr.elemType.accept(this, ctx); + expr.length.accept(this, ctx); + } + + @Override + public void visitPrint(Tree.Print stmt, ScopeStack ctx) { + for (var expr : stmt.exprs) { + expr.accept(this, ctx); + } + } + + @Override + public void visitReturn(Tree.Return stmt, ScopeStack ctx) { + stmt.expr.ifPresent(e -> e.accept(this, ctx)); + } + + @Override + public void visitUnary(Tree.Unary expr, ScopeStack ctx) { + expr.operand.accept(this, ctx); + } + + @Override + public void visitVarSel(Tree.VarSel expr, ScopeStack ctx) { + expr.receiver.ifPresent(objects -> objects.accept(this, ctx)); + } + + @Override + public void visitLambda(Tree.Lambda lambda, ScopeStack ctx) { + lambda.scope = new LambdaScope(ctx.currentScope()); + ctx.open(lambda.scope); + for (var param : lambda.params) { + param.accept(this, ctx); + } + if (lambda.hasReturnExpr()) { + var local = new LocalScope(ctx.currentScope()); + ctx.open(local); + lambda.returnExpr.get().accept(this, ctx); + ctx.close(); + } else { + lambda.body.get().accept(this, ctx); + } + ctx.close(); + lambda.symbol = new LambdaSymbol(lambda.scope, lambda.pos, ctx.currentClass()); + lambda.scope.setOwner(lambda.symbol); + ctx.declare(lambda.symbol); + ctx.global.lambdaSymbols.add(lambda.symbol); + } + @Override public void visitBlock(Tree.Block block, ScopeStack ctx) { block.scope = new LocalScope(ctx.currentScope()); @@ -278,23 +399,34 @@ public void visitBlock(Tree.Block block, ScopeStack ctx) { @Override public void visitLocalVarDef(Tree.LocalVarDef def, ScopeStack ctx) { - def.typeLit.accept(this, ctx); + def.typeLit.ifPresent(objects -> objects.accept(this, ctx)); var earlier = ctx.findConflict(def.name); if (earlier.isPresent()) { issue(new DeclConflictError(def.pos, def.name, earlier.get().pos)); + def.initVal.ifPresent(objects -> objects.accept(this, ctx)); + return; + } + + if (def.typeLit.isEmpty()) { + var symbol = new VarSymbol(def.name, def.id.pos); + ctx.declare(symbol); + def.symbol = symbol; + def.initVal.ifPresent(objects -> objects.accept(this, ctx)); return; } - if (def.typeLit.type.eq(BuiltInType.VOID)) { + if (def.typeLit.get().type.eq(BuiltInType.VOID)) { issue(new BadVarTypeError(def.pos, def.name)); + def.initVal.ifPresent(objects -> objects.accept(this, ctx)); return; } - if (def.typeLit.type.noError()) { - var symbol = new VarSymbol(def.name, def.typeLit.type, def.id.pos); + if (def.typeLit.get().type.noError()) { + var symbol = new VarSymbol(def.name, def.typeLit.get().type, def.id.pos); ctx.declare(symbol); def.symbol = symbol; + def.initVal.ifPresent(objects -> objects.accept(this, ctx)); } } @@ -303,6 +435,8 @@ public void visitFor(Tree.For loop, ScopeStack ctx) { loop.scope = new LocalScope(ctx.currentScope()); ctx.open(loop.scope); loop.init.accept(this, ctx); + loop.cond.accept(this, ctx); + loop.update.accept(this, ctx); for (var stmt : loop.body.stmts) { stmt.accept(this, ctx); } @@ -311,12 +445,14 @@ public void visitFor(Tree.For loop, ScopeStack ctx) { @Override public void visitIf(Tree.If stmt, ScopeStack ctx) { + stmt.cond.accept(this, ctx); stmt.trueBranch.accept(this, ctx); stmt.falseBranch.ifPresent(b -> b.accept(this, ctx)); } @Override public void visitWhile(Tree.While loop, ScopeStack ctx) { + loop.cond.accept(this, ctx); loop.body.accept(this, ctx); } diff --git a/src/main/java/decaf/frontend/typecheck/TypeLitVisited.java b/src/main/java/decaf/frontend/typecheck/TypeLitVisited.java index f35c2a6..81cc16a 100644 --- a/src/main/java/decaf/frontend/typecheck/TypeLitVisited.java +++ b/src/main/java/decaf/frontend/typecheck/TypeLitVisited.java @@ -2,11 +2,14 @@ import decaf.driver.ErrorIssuer; import decaf.driver.error.BadArrElementError; +import decaf.driver.error.BadFunArgTypeError; import decaf.driver.error.ClassNotFoundError; import decaf.frontend.scope.ScopeStack; import decaf.frontend.tree.Tree; import decaf.frontend.tree.Visitor; -import decaf.frontend.type.BuiltInType; +import decaf.frontend.type.*; + +import java.util.ArrayList; /** * Infer the types of type literals in the abstract syntax tree. @@ -56,7 +59,33 @@ default void visitTArray(Tree.TArray typeArray, ScopeStack ctx) { issue(new BadArrElementError(typeArray.pos)); typeArray.type = BuiltInType.ERROR; } else { - typeArray.type = new decaf.frontend.type.ArrayType(typeArray.elemType.type); + typeArray.type = new ArrayType(typeArray.elemType.type); + } + } + + @Override + default void visitTLambda(Tree.TLambda typeLambda, ScopeStack ctx) { + boolean hasError = false; + typeLambda.returnType.accept(this, ctx); + if (typeLambda.returnType.type.eq(BuiltInType.ERROR)) { + hasError = true; + } + ArrayList argTypes = new ArrayList<>(); + for (var argType : typeLambda.argTypes) { + argType.accept(this, ctx); + if (argType.type.eq(BuiltInType.ERROR)) { + hasError = true; + } else if (argType.type.eq(BuiltInType.VOID)) { + issue(new BadFunArgTypeError(argType.pos)); + hasError = true; + } else if (!hasError) { + argTypes.add(argType.type); + } + } + if (hasError) { + typeLambda.type = BuiltInType.ERROR; + } else { + typeLambda.type = new FunType(typeLambda.returnType.type, argTypes); } } diff --git a/src/main/java/decaf/frontend/typecheck/Typer.java b/src/main/java/decaf/frontend/typecheck/Typer.java index 8ac8adc..cf6a1fd 100644 --- a/src/main/java/decaf/frontend/typecheck/Typer.java +++ b/src/main/java/decaf/frontend/typecheck/Typer.java @@ -3,20 +3,15 @@ import decaf.driver.Config; import decaf.driver.Phase; import decaf.driver.error.*; -import decaf.frontend.scope.ScopeStack; -import decaf.frontend.symbol.ClassSymbol; -import decaf.frontend.symbol.MethodSymbol; -import decaf.frontend.symbol.VarSymbol; +import decaf.frontend.scope.*; +import decaf.frontend.symbol.*; import decaf.frontend.tree.Pos; import decaf.frontend.tree.Tree; -import decaf.frontend.type.ArrayType; -import decaf.frontend.type.BuiltInType; -import decaf.frontend.type.ClassType; -import decaf.frontend.type.Type; +import decaf.frontend.type.*; import decaf.lowlevel.log.IndentPrinter; import decaf.printing.PrettyScope; -import java.util.Optional; +import java.util.*; /** * The typer phase: type check abstract syntax tree and annotate nodes with inferred (and checked) types. @@ -62,9 +57,11 @@ public void visitClassDef(Tree.ClassDef clazz, ScopeStack ctx) { @Override public void visitMethodDef(Tree.MethodDef method, ScopeStack ctx) { ctx.open(method.symbol.scope); - method.body.accept(this, ctx); - if (!method.symbol.type.returnType.isVoidType() && !method.body.returns) { - issue(new MissingReturnError(method.body.pos)); + if (!method.isAbstract()) { + method.body.get().accept(this, ctx); + if (!method.symbol.type.returnType.isVoidType() && !method.body.get().returns) { + issue(new MissingReturnError(method.body.get().pos)); + } } ctx.close(); } @@ -82,6 +79,7 @@ public void visitBlock(Tree.Block block, ScopeStack ctx) { ctx.open(block.scope); for (var stmt : block.stmts) { stmt.accept(this, ctx); + block.updateReturnType(stmt); } ctx.close(); block.returns = !block.stmts.isEmpty() && block.stmts.get(block.stmts.size() - 1).returns; @@ -94,8 +92,26 @@ public void visitAssign(Tree.Assign stmt, ScopeStack ctx) { var lt = stmt.lhs.type; var rt = stmt.rhs.type; - if (lt.noError() && (lt.isFuncType() || !rt.subtypeOf(lt))) { + if (lt.noError() && rt.noError() && !rt.subtypeOf(lt)) { issue(new IncompatBinOpError(stmt.pos, lt.toString(), "=", rt.toString())); + // Shall we return? + } + if (lt.noError() && stmt.lhs instanceof Tree.VarSel) { + var lvar = (Tree.VarSel) stmt.lhs; + if (lvar.isMethod) { + issue(new AssignMethodError(stmt.pos, lvar.name)); + return; + } + if (lambdaLevel > 0 && lvar.symbol != null && lvar.symbol.domain() != ctx.currentScope() && + !lvar.symbol.isMemberVar()) { + Scope currentLambdaScope = ctx.currentScope(); + while (lvar.symbol.domain() != currentLambdaScope && currentLambdaScope.isLocalScope()) { + currentLambdaScope = ((LocalScope) currentLambdaScope).parent; + } + if (lvar.symbol.domain() != currentLambdaScope) { + issue(new AssignCaptureError(stmt.pos)); + } + } } } @@ -109,7 +125,11 @@ public void visitExprEval(Tree.ExprEval stmt, ScopeStack ctx) { public void visitIf(Tree.If stmt, ScopeStack ctx) { checkTestExpr(stmt.cond, ctx); stmt.trueBranch.accept(this, ctx); - stmt.falseBranch.ifPresent(b -> b.accept(this, ctx)); + stmt.updateReturnType(stmt.trueBranch); + if (stmt.falseBranch.isPresent()) { + stmt.falseBranch.get().accept(this, ctx); + stmt.updateReturnType(stmt.falseBranch.get()); + } // if-stmt returns a value iff both branches return stmt.returns = stmt.trueBranch.returns && stmt.falseBranch.isPresent() && stmt.falseBranch.get().returns; } @@ -119,6 +139,7 @@ public void visitWhile(Tree.While loop, ScopeStack ctx) { checkTestExpr(loop.cond, ctx); loopLevel++; loop.body.accept(this, ctx); + loop.updateReturnType(loop.body); loopLevel--; } @@ -131,6 +152,7 @@ public void visitFor(Tree.For loop, ScopeStack ctx) { loopLevel++; for (var stmt : loop.body.stmts) { stmt.accept(this, ctx); + loop.updateReturnType(stmt); } loopLevel--; ctx.close(); @@ -143,15 +165,26 @@ public void visitBreak(Tree.Break stmt, ScopeStack ctx) { } } + /** + * To know if we need to check the return type, we need to know if we are in a lambda expression, i.e. + * lambdaLevel {@literal >} 1? + *

    + * Increase this counter when entering a lambda expression, and decrease it when leaving one. + */ + private int lambdaLevel = 0; + @Override public void visitReturn(Tree.Return stmt, ScopeStack ctx) { - var expected = ctx.currentMethod().type.returnType; stmt.expr.ifPresent(e -> e.accept(this, ctx)); var actual = stmt.expr.map(e -> e.type).orElse(BuiltInType.VOID); - if (actual.noError() && !actual.subtypeOf(expected)) { - issue(new BadReturnTypeError(stmt.pos, expected.toString(), actual.toString())); + if (lambdaLevel == 0) { + var expected = ctx.currentMethod().type.returnType; + if (actual.noError() && !actual.subtypeOf(expected)) { + issue(new BadReturnTypeError(stmt.pos, expected.toString(), actual.toString())); + } } stmt.returns = stmt.expr.isPresent(); + stmt.returnType = actual; } @Override @@ -298,8 +331,13 @@ public void visitNewArray(Tree.NewArray expr, ScopeStack ctx) { public void visitNewClass(Tree.NewClass expr, ScopeStack ctx) { var clazz = ctx.lookupClass(expr.clazz.name); if (clazz.isPresent()) { - expr.symbol = clazz.get(); - expr.type = expr.symbol.type; + if (clazz.get().isAbstract()) { + issue(new InstantAbstractClassError(expr.pos, expr.clazz.name)); + expr.type = BuiltInType.ERROR; + } else { + expr.symbol = clazz.get(); + expr.type = expr.symbol.type; + } } else { issue(new ClassNotFoundError(expr.pos, expr.clazz.name)); expr.type = BuiltInType.ERROR; @@ -318,20 +356,38 @@ public void visitThis(Tree.This expr, ScopeStack ctx) { @Override public void visitVarSel(Tree.VarSel expr, ScopeStack ctx) { - if (expr.receiver.isEmpty()) { + expr.receiverClassName = Optional.empty(); + if (expr.receiver.isEmpty()) { // this class // Variable, which should be complicated since a legal variable could refer to a local var, - // a visible member var, and a class name. + // a visible member var, a class name, or a method name. var symbol = ctx.lookupBefore(expr.name, localVarDefPos.orElse(expr.pos)); if (symbol.isPresent()) { if (symbol.get().isVarSymbol()) { var var = (VarSymbol) symbol.get(); expr.symbol = var; expr.type = var.type; + var capturedVar = var; if (var.isMemberVar()) { if (ctx.currentMethod().isStatic()) { issue(new RefNonStaticError(expr.pos, ctx.currentMethod().name, expr.name)); } else { expr.setThis(); + capturedVar = (VarSymbol) ctx.currentMethod().scope.find("this").get(); + } + } + if (lambdaLevel > 0 && capturedVar.domain() != ctx.currentScope()) { + Scope currentLambdaScope = ctx.currentScope(); + while (capturedVar.domain() != currentLambdaScope) { + if (currentLambdaScope.isLocalScope()) { + currentLambdaScope = ((LocalScope) currentLambdaScope).parent; + } else if (currentLambdaScope.isLambdaScope()) { + // Capture the variable. + ((LambdaScope) currentLambdaScope).capture(capturedVar); + currentLambdaScope = ((LambdaScope) currentLambdaScope).parent; + } else { + // Formal or class scope, no lambda expressions outside. + break; + } } } return; @@ -343,6 +399,28 @@ public void visitVarSel(Tree.VarSel expr, ScopeStack ctx) { expr.isClassName = true; return; } + + if (symbol.get().isMethodSymbol()) { + var method = (MethodSymbol) symbol.get(); + expr.type = method.type; + expr.isMethod = true; + expr.isStaticMethod = method.isStatic(); + if (!ctx.currentMethod().isStatic()) { + expr.setThis(); + } + expr.receiverClassName = Optional.of(ctx.currentClass().name); + if (!method.isStatic() && ctx.currentMethod().isStatic()) { + issue(new RefNonStaticError(expr.pos, ctx.currentMethod().name, expr.name)); + } + return; + } + + if (symbol.get().isLambdaSymbol()) { + // Impossible? + var lambda = (LambdaSymbol) symbol.get(); + expr.type = lambda.type; + return; + } } expr.type = BuiltInType.ERROR; @@ -360,9 +438,27 @@ public void visitVarSel(Tree.VarSel expr, ScopeStack ctx) { if (receiver instanceof Tree.VarSel) { var v1 = (Tree.VarSel) receiver; + var v2 = expr.variable; if (v1.isClassName) { - // special case like MyClass.foo: report error cannot access field 'foo' from 'class : MyClass' - issue(new NotClassFieldError(expr.pos, expr.name, ctx.getClass(v1.name).type.toString())); + // Special case: invoking a static method, like MyClass.foo() + var clazz = ctx.getClass(v1.name); + var symbol = clazz.scope.lookup(v2.name); + if (symbol.isPresent() && symbol.get().isMethodSymbol()) { + if (!((MethodSymbol) symbol.get()).isStatic()) { + // Cannot access a non-static method by MyClass.foo() + issue(new NotClassFieldError(expr.pos, expr.name, ctx.getClass(v1.name).type.toString())); + return; + } + expr.type = symbol.get().type; + expr.receiverClassName = Optional.of(v1.name); + expr.isMethod = true; + expr.isStaticMethod = true; + } else if (symbol.isEmpty()) { + issue(new FieldNotFoundError(expr.pos, expr.name, ctx.getClass(v1.name).type.toString())); + } else { + // Special case like MyClass.foo: report error cannot access field 'foo' from 'class : MyClass' + issue(new NotClassFieldError(expr.pos, expr.name, ctx.getClass(v1.name).type.toString())); + } return; } } @@ -371,11 +467,19 @@ public void visitVarSel(Tree.VarSel expr, ScopeStack ctx) { return; } + if (rt.isArrayType() && expr.variable.name.equals("length")) { // Special case: array.length() + expr.isArrayLength = true; + expr.type = new FunType(BuiltInType.INT, new ArrayList<>()); + return; + } + if (!rt.isClassType()) { issue(new NotClassFieldError(expr.pos, expr.name, rt.toString())); return; } + expr.receiverClassName = Optional.of(((ClassType) rt).name); + var ct = (ClassType) rt; var field = ctx.getClass(ct.name).scope.lookup(expr.name); if (field.isPresent() && field.get().isVarSymbol()) { @@ -388,6 +492,10 @@ public void visitVarSel(Tree.VarSel expr, ScopeStack ctx) { issue(new FieldNotAccessError(expr.pos, expr.name, ct.toString())); } } + } else if (field.isPresent() && field.get().isMethodSymbol()) { + expr.type = ((MethodSymbol) field.get()).type; + expr.isMethod = true; + expr.isStaticMethod = ((MethodSymbol) field.get()).isStatic(); } else if (field.isEmpty()) { issue(new FieldNotFoundError(expr.pos, expr.name, ct.toString())); } else { @@ -402,6 +510,11 @@ public void visitIndexSel(Tree.IndexSel expr, ScopeStack ctx) { var at = expr.array.type; var it = expr.index.type; + if (at.hasError()) { + expr.type = BuiltInType.ERROR; + return; + } + if (!at.isArrayType()) { issue(new NotArrayError(expr.array.pos)); expr.type = BuiltInType.ERROR; @@ -416,65 +529,62 @@ public void visitIndexSel(Tree.IndexSel expr, ScopeStack ctx) { @Override public void visitCall(Tree.Call expr, ScopeStack ctx) { + expr.methodExpr.accept(this, ctx); expr.type = BuiltInType.ERROR; - Type rt; - boolean thisClass = false; - - if (expr.receiver.isPresent()) { - var receiver = expr.receiver.get(); - allowClassNameVar = true; - receiver.accept(this, ctx); - allowClassNameVar = false; - rt = receiver.type; - - if (receiver instanceof Tree.VarSel) { - var v1 = (Tree.VarSel) receiver; - if (v1.isClassName) { - // Special case: invoking a static method, like MyClass.foo() - typeCall(expr, false, v1.name, ctx, true); - return; - } - } - } else { - thisClass = true; - expr.setThis(); - rt = ctx.currentClass().type; + if (expr.methodExpr.type.hasError()) { + return; } - if (rt.noError()) { - if (rt.isArrayType() && expr.methodName.equals("length")) { // Special case: array.length() - if (!expr.args.isEmpty()) { - issue(new BadLengthArgError(expr.pos, expr.args.size())); - } - expr.isArrayLength = true; - expr.type = BuiltInType.INT; - return; - } + if (!expr.methodExpr.type.isFuncType()) { + issue(new NotCallableTypeError(expr.pos, expr.methodExpr.type.toString())); + return; + } - if (rt.isClassType()) { - typeCall(expr, thisClass, ((ClassType) rt).name, ctx, false); + var methodType = (FunType) expr.methodExpr.type; + expr.type = methodType.returnType; + // typing args + var args = expr.args; + for (var arg : args) { + arg.accept(this, ctx); + } + + // check signature compatibility + if (methodType.argTypes.size() != args.size()) { + if (expr.methodExpr instanceof Tree.VarSel) { + issue(new BadArgCountError(expr.pos, ((Tree.VarSel) expr.methodExpr).name, + methodType.argTypes.size(), args.size())); } else { - issue(new NotClassFieldError(expr.pos, expr.methodName, rt.toString())); + issue(new BadLambdaArgCountError(expr.pos, methodType.argTypes.size(), args.size())); + } + } + for (int i = 0; i < java.lang.Math.min(methodType.argTypes.size(), args.size()); i++) { + Type t1 = methodType.argTypes.get(i); + Type t2 = args.get(i).type; + if (t2.noError() && !t2.subtypeOf(t1)) { + issue(new BadArgTypeError(args.get(i).pos, i + 1, t2.toString(), t1.toString())); } } + } private void typeCall(Tree.Call call, boolean thisClass, String className, ScopeStack ctx, boolean requireStatic) { + // This function becomes unused. var clazz = thisClass ? ctx.currentClass() : ctx.getClass(className); - var symbol = clazz.scope.lookup(call.methodName); + var methodExpr = (Tree.VarSel) call.methodExpr; + var symbol = clazz.scope.lookup(methodExpr.name); if (symbol.isPresent()) { if (symbol.get().isMethodSymbol()) { var method = (MethodSymbol) symbol.get(); call.symbol = method; call.type = method.type.returnType; if (requireStatic && !method.isStatic()) { - issue(new NotClassFieldError(call.pos, call.methodName, clazz.type.toString())); + issue(new NotClassFieldError(call.methodExpr.pos, methodExpr.name, clazz.type.toString())); return; } // Cannot call this's member methods in a static method if (thisClass && ctx.currentMethod().isStatic() && !method.isStatic()) { - issue(new RefNonStaticError(call.pos, ctx.currentMethod().name, method.name)); + issue(new RefNonStaticError(call.methodExpr.pos, ctx.currentMethod().name, method.name)); } // typing args @@ -498,10 +608,10 @@ private void typeCall(Tree.Call call, boolean thisClass, String className, Scope } } } else { - issue(new NotClassMethodError(call.pos, call.methodName, clazz.type.toString())); + issue(new NotClassMethodError(call.methodExpr.pos, methodExpr.name, clazz.type.toString())); } } else { - issue(new FieldNotFoundError(call.pos, call.methodName, clazz.type.toString())); + issue(new FieldNotFoundError(call.methodExpr.pos, methodExpr.name, clazz.type.toString())); } } @@ -547,11 +657,67 @@ public void visitLocalVarDef(Tree.LocalVarDef stmt, ScopeStack ctx) { localVarDefPos = Optional.ofNullable(stmt.id.pos); initVal.accept(this, ctx); localVarDefPos = Optional.empty(); - var lt = stmt.symbol.type; - var rt = initVal.type; - if (lt.noError() && (lt.isFuncType() || !rt.subtypeOf(lt))) { - issue(new IncompatBinOpError(stmt.assignPos, lt.toString(), "=", rt.toString())); + if (stmt.typeLit.isPresent()) { + var lt = stmt.symbol.type; + var rt = initVal.type; + if (lt.hasError() || rt.hasError()) { + return; + } + if (!rt.subtypeOf(lt)) { + issue(new IncompatBinOpError(stmt.assignPos, lt.toString(), "=", rt.toString())); + } + } else { + // Deduce the type. + var rt = initVal.type; + if (initVal.type.eq(BuiltInType.VOID)) { + issue(new BadVarTypeError(stmt.pos, stmt.name)); + rt = BuiltInType.ERROR; + } + stmt.symbol.setType(rt); + } + } + + @Override + public void visitLambda(Tree.Lambda lambda, ScopeStack ctx) { + Type returnType; + var argTypes = new ArrayList(); + boolean hasError = false; + lambdaLevel++; // for return type check + ctx.open(lambda.scope); + for (var param : lambda.params) { + param.accept(this, ctx); + argTypes.add(param.symbol.type); + if (param.symbol.type.hasError()) + hasError = true; + } + if (lambda.hasReturnExpr()) { + ctx.open(lambda.scope.nestedLocalScope()); + lambda.returnExpr.get().accept(this, ctx); + returnType = lambda.returnExpr.get().type; + ctx.close(); + } else { + // Open local scope in visitBlock. + lambda.body.get().accept(this, ctx); + returnType = lambda.body.get().returnType; + if (returnType == null) { + returnType = BuiltInType.VOID; + } + if (!returnType.isVoidType() && !lambda.body.get().returns) { + issue(new MissingReturnError(lambda.body.get().pos)); + } + if (returnType.isIncompatible()) { + issue(new IncompatRetTypeError(lambda.body.get().pos)); + } + } + ctx.close(); + lambdaLevel--; + if (hasError || returnType.hasError()) { + lambda.type = BuiltInType.ERROR; + } else { + var lambdaType = new FunType(returnType, argTypes); + lambda.type = lambdaType; + lambda.symbol.setType(lambdaType); } } diff --git a/src/main/java/decaf/lowlevel/label/FuncLabel.java b/src/main/java/decaf/lowlevel/label/FuncLabel.java index e8f3b39..1e737e6 100644 --- a/src/main/java/decaf/lowlevel/label/FuncLabel.java +++ b/src/main/java/decaf/lowlevel/label/FuncLabel.java @@ -30,19 +30,20 @@ public boolean isFunc() { return true; } - private FuncLabel() { + /*private FuncLabel() { super(Kind.FUNC, "main"); this.clazz = "Main"; this.method = "main"; - } + }*/ /** * Special function label: main entry. */ - public static FuncLabel MAIN_LABEL = new FuncLabel() { + public static FuncLabel MAIN_LABEL = new FuncLabel("Main", "main"); + /*public static FuncLabel MAIN_LABEL = new FuncLabel() { @Override public String prettyString() { return name; } - }; + };*/ } diff --git a/src/main/java/decaf/lowlevel/tac/FuncVisitor.java b/src/main/java/decaf/lowlevel/tac/FuncVisitor.java index e93fb35..2b5cfe4 100644 --- a/src/main/java/decaf/lowlevel/tac/FuncVisitor.java +++ b/src/main/java/decaf/lowlevel/tac/FuncVisitor.java @@ -285,6 +285,84 @@ public void visitIntrinsicCall(Intrinsic func, Temp... args) { visitIntrinsicCall(func, false, args); } + /** + * Append instructions to get the entry of a member method. + * + * @param object object ref temp + * @param clazz class name + * @param method member method name + * @return the method entry + */ + public Temp visitFuncEntry(Temp object, String clazz, String method) { + var vtbl = visitLoadFrom(object); + return visitLoadFrom(vtbl, ctx.getOffset(clazz, method)); + } + + /** + * Append instructions to get the entry of a static method. + * + * @param clazz class name + * @param method member method name + * @return the method entry + */ + public Temp visitFuncEntry(String clazz, String method) { + var vtbl = visitLoadVTable("static"); + return visitLoadFrom(vtbl, ctx.getOffset(clazz, method)); + } + + /** + * Append instructions to get the entry of a lambda expression. + * + * @param pos the position of the lambda expression + * @return the method entry + */ + public Temp visitFuncEntry(decaf.frontend.tree.Pos pos) { + var vtbl = visitLoadVTable("fun"); + return visitLoadFrom(vtbl, ctx.getOffset("lambda", pos.toString())); + } + + /** + * Generate TAC code for a lambda function. + * + * @param pos the position of the lambda expression + * @param numArgs number of arguments + */ + public FuncVisitor visitLambdaFunc(decaf.frontend.tree.Pos pos, int numArgs) { + var entry = ctx.getFuncLabel("lambda", pos.toString()); + return new FuncVisitor(entry, numArgs, ctx); + } + + /** + * Append instructions to add parameters to the following call. + * + * @param arg argument temp + */ + public void visitParm(Temp arg) { + func.add(new TacInstr.Parm(arg)); + } + + /** + * Append instructions to invoke a method or a lambda expression. + * + * @param args argument temps + * @param needReturn do we need a fresh temp to store the return value? (default false) + * @return the fresh temp if we need return (or else null) + */ + public Temp visitCall(Temp entry, List args, boolean needReturn) { + Temp temp = null; + + for (var arg : args) { + func.add(new TacInstr.Parm(arg)); + } + if (needReturn) { + temp = freshTemp(); + func.add(new TacInstr.IndirectCall(temp, entry)); + } else { + func.add(new TacInstr.IndirectCall(entry)); + } + return temp; + } + /** * Append an instruction to print a string. * diff --git a/src/main/java/decaf/lowlevel/tac/ProgramWriter.java b/src/main/java/decaf/lowlevel/tac/ProgramWriter.java index baa9479..f801a8f 100644 --- a/src/main/java/decaf/lowlevel/tac/ProgramWriter.java +++ b/src/main/java/decaf/lowlevel/tac/ProgramWriter.java @@ -44,6 +44,36 @@ public void visitVTables() { } } + /** + * Post-processing after generating TAC code for virtual tables. + */ + public void visitVTablesPostProcess() { + ctx.putVTable(ctx.staticVtbl); + ctx.putOffsets(ctx.staticVtbl); + ctx.putVTable(ctx.lambdaVtbl); + ctx.putOffsets(ctx.lambdaVtbl); + } + + /** + * Update virtual table for a static method. + * + * @param className class name + * @param funcName function name + */ + public void visitStaticMethod(String className, String funcName) { + ctx.staticVtbl.memberMethods.add(ctx.getFuncLabel(className, funcName)); + } + + /** + * Update virtual table for a lambda expression. + * + * @param pos the position of the lambda expression + */ + public void visitLambda(decaf.frontend.tree.Pos pos) { + ctx.putFuncLabel("lambda", pos.toString()); + ctx.lambdaVtbl.memberMethods.add(ctx.getFuncLabel("lambda", pos.toString())); + } + /** * Generate TAC code for the main method. */ @@ -109,7 +139,7 @@ private void buildVTableFor(ClassInfo clazz) { // Member methods consist of ones that are: // 1. inherited from super class - // 2. overriden by this class + // 2. overridden by this class if (parent.isPresent()) { for (var lbl : parent.get().memberMethods) { @@ -130,7 +160,7 @@ private void buildVTableFor(ClassInfo clazz) { // Similarly, member variables consist of ones that are: // 1. inherited from super class - // 2. overriden by this class (Decaf doesn't support this, but handle it for future) + // 2. overridden by this class (Decaf doesn't support this, but handle it for future) if (parent.isPresent()) { for (var variable : parent.get().memberVariables) { @@ -191,6 +221,16 @@ int getOffset(String clazz, String member) { } void putOffsets(VTable vtbl) { + if (vtbl.className.equals("static") || vtbl.className.equals("fun")) { + // special case: static vtable or lambda vtable + var offset = 8; + for (var l : vtbl.memberMethods) { + offsets.put(l.clazz + "." + l.method, offset); + offset += 4; + } + return; + } + var prefix = vtbl.className + "."; var offset = 8; @@ -215,6 +255,10 @@ void putOffsets(VTable vtbl) { List funcs = new ArrayList<>(); private int nextTempLabelId = 1; + + public VTable staticVtbl = new VTable("static", Optional.empty()); + + public VTable lambdaVtbl = new VTable("fun", Optional.empty()); } } diff --git a/src/main/java/decaf/lowlevel/tac/RuntimeError.java b/src/main/java/decaf/lowlevel/tac/RuntimeError.java index 7d8b249..a9b0c2c 100644 --- a/src/main/java/decaf/lowlevel/tac/RuntimeError.java +++ b/src/main/java/decaf/lowlevel/tac/RuntimeError.java @@ -17,4 +17,6 @@ private RuntimeError() { public static final String CLASS_CAST_ERROR2 = " cannot be cast to "; public static final String CLASS_CAST_ERROR3 = "\n"; + + public static final String DIVISION_BY_ZERO = "Decaf runtime error: Division by zero error\n"; } diff --git a/src/main/java/decaf/printing/PrettyScope.java b/src/main/java/decaf/printing/PrettyScope.java index 0827f27..e0b8bf7 100644 --- a/src/main/java/decaf/printing/PrettyScope.java +++ b/src/main/java/decaf/printing/PrettyScope.java @@ -36,7 +36,18 @@ public void pretty(Scope scope) { printer.incIndent(); if (scope.isEmpty()) printer.println(""); else scope.forEach(printer::println); - pretty(formalScope.nestedLocalScope()); + if (formalScope.hasLocalScope()) { + pretty(formalScope.nestedLocalScope()); + } + printer.decIndent(); + } else if (scope.isLambdaScope()) { + var lambdaScope = (LambdaScope) scope; + printer.formatLn("FORMAL SCOPE OF '%s':", lambdaScope.getOwner().name); + printer.incIndent(); + if (scope.isEmpty()) printer.println(""); + else scope.forEach(printer::println); +// lambdaScope.capturedVar.values().forEach(printer::println); + pretty(lambdaScope.nestedLocalScope()); printer.decIndent(); } else if (scope.isLocalScope()) { var localScope = (LocalScope) scope; @@ -44,7 +55,7 @@ public void pretty(Scope scope) { printer.incIndent(); if (scope.isEmpty()) printer.println(""); else scope.forEach(printer::println); - localScope.nestedLocalScopes().forEach(this::pretty); + localScope.nestedLocalOrLambdaScopes().forEach(this::pretty); printer.decIndent(); } } diff --git a/src/main/jflex/Decaf.jflex b/src/main/jflex/Decaf.jflex index 0f00be9..f96be8f 100644 --- a/src/main/jflex/Decaf.jflex +++ b/src/main/jflex/Decaf.jflex @@ -67,6 +67,9 @@ BAD_ESC = "\\"[^nrt\"\\] "ReadLine" { return keyword(Tokens.READ_LINE); } "static" { return keyword(Tokens.STATIC); } "instanceof" { return keyword(Tokens.INSTANCE_OF); } +"abstract" { return keyword(Tokens.ABSTRACT); } +"var" { return keyword(Tokens.VAR); } +"fun" { return keyword(Tokens.FUN); } // operators, with more than one character "<=" { return operator(Tokens.LESS_EQUAL); } @@ -75,6 +78,7 @@ BAD_ESC = "\\"[^nrt\"\\] "!=" { return operator(Tokens.NOT_EQUAL); } "&&" { return operator(Tokens.AND); } "||" { return operator(Tokens.OR); } +"=>" { return operator(Tokens.ARROW); } {SIMPLE_OPERATOR} { return operator((int) yycharat(0)); } // literals diff --git a/src/main/ll1pg/Decaf.spec b/src/main/ll1pg/Decaf.spec index 3d8163e..d62fd6f 100644 --- a/src/main/ll1pg/Decaf.spec +++ b/src/main/ll1pg/Decaf.spec @@ -4,6 +4,7 @@ %import decaf.frontend.tree.Tree.* java.util.* +org.apache.commons.lang3.tuple.MutablePair %class public abstract class LLTable extends AbstractParser %output "LLTable.java" @@ -20,6 +21,7 @@ IDENTIFIER AND OR STATIC INSTANCE_OF LESS_EQUAL GREATER_EQUAL EQUAL NOT_EQUAL '+' '-' '*' '/' '%' '=' '>' '<' '.' ',' ';' '!' '(' ')' '[' ']' '{' '}' +ABSTRACT VAR FUN ARROW %% @@ -45,7 +47,12 @@ ClassList : ClassDef ClassList ClassDef : CLASS Id ExtendsClause '{' FieldList '}' { - $$ = svClass(new ClassDef($2.id, Optional.ofNullable($3.id), $5.fieldList, $1.pos)); + $$ = svClass(new ClassDef(0, $2.id, Optional.ofNullable($3.id), $5.fieldList, $1.pos)); + } + | ABSTRACT CLASS Id ExtendsClause '{' FieldList '}' + { + $$ = svClass(new ClassDef(Modifiers.ABSTRACT, $3.id, Optional.ofNullable($4.id), + $6.fieldList, $2.pos)); } ; @@ -62,13 +69,21 @@ ExtendsClause : EXTENDS Id FieldList : STATIC Type Id '(' VarList ')' Block FieldList { $$ = $8; - $$.fieldList.add(0, new MethodDef(true, $3.id, $2.type, $5.varList, $7.block, $3.pos)); + $$.fieldList.add(0, new MethodDef(Modifiers.STATIC, $3.id, $2.type, $5.varList, + Optional.of($7.block), $3.pos)); + } + | ABSTRACT Type Id '(' VarList ')' ';' FieldList + { + $$ = $8; + $$.fieldList.add(0, new MethodDef(Modifiers.ABSTRACT, $3.id, $2.type, $5.varList, + Optional.empty(), $3.pos)); } | Type Id AfterIdField FieldList { $$ = $4; if ($3.varList != null) { - $$.fieldList.add(0, new MethodDef(false, $2.id, $1.type, $3.varList, $3.block, $2.pos)); + $$.fieldList.add(0, new MethodDef(0, $2.id, $1.type, $3.varList, + Optional.of($3.block), $2.pos)); } else { $$.fieldList.add(0, new VarDef($1.type, $2.id, $2.pos)); } @@ -143,24 +158,62 @@ AtomType : INT } ; -Type : AtomType ArrayType +Type : AtomType AfterAtomType { $$ = $1; - for (int i = 0; i < $2.intVal; i++) { - $$.type = new TArray($$.type, $1.type.pos); + for (int i = 0; i < $2.typeListList.size(); i++) { + for (int j = 0; j < $2.typeListList.get(i).right; j++) { + $$.type = new TArray($$.type, $$.type.pos); + } + if ($2.typeListList.get(i).left != null) { + $$ = svType(new TLambda($$.type, $2.typeListList.get(i).left, $$.type.pos)); + } } + // System.err.println("Type: " + $$.toString()); } ; -ArrayType : '[' ']' ArrayType +AfterAtomType : '[' ']' AfterAtomType { $$ = $3; - $$.intVal++; + if ($$.typeListList.isEmpty()) { + $$.typeListList.add(MutablePair.of(null, 1)); + } + else { + Integer numArray = $$.typeListList.get(0).getRight(); + $$.typeListList.get(0).setRight(numArray + 1); // int[][](...) + } + } + | '(' TypeList ')' AfterAtomType + { + $$ = $4; + $$.typeListList.add(0, MutablePair.of($2.typeList, 0)); // int(...) } | /* empty */ { - $$ = new SemValue(); - $$.intVal = 0; // counter + $$ = svTypess(); + } + ; + +TypeList : Type TypeList1 + { + $$ = $2; + $$.typeList.add(0, $1.type); + } + | /* empty */ + { + $$ = svTypes(); + } + ; + +TypeList1 : ',' Type TypeList1 + { + $$ = $3; + $$.typeList.add(0, $2.type); + } + | /* empty */ + { + $$ = svTypes(); } ; @@ -225,7 +278,13 @@ StmtList : Stmt StmtList SimpleStmt : Var Initializer { - $$ = svStmt(new LocalVarDef($1.type, $1.id, $2.pos, Optional.ofNullable($2.expr), $1.pos)); + $$ = svStmt(new LocalVarDef(Optional.of($1.type), $1.id, $2.pos, + Optional.ofNullable($2.expr), $1.pos)); + } + | VAR Id '=' Expr + { + $$ = svStmt(new LocalVarDef(Optional.empty(), $2.id, $3.pos, + Optional.ofNullable($4.expr), $2.pos)); } | Expr Initializer { @@ -349,7 +408,7 @@ Op5 : '+' } ; -Op6 : '*' +Op6 : '*' { $$ = new SemValue(); $$.pos = $1.pos; @@ -389,6 +448,24 @@ Expr : Expr1 { $$ = $1; } + | FUN '(' VarList ')' AfterFunExpr + { + if ($5.expr != null) { + $$ = svExpr(new Lambda($3.varList, $5.expr, $1.pos)); + } else { + $$ = svExpr(new Lambda($3.varList, $5.block, $1.pos)); + } + } + ; + +AfterFunExpr : ARROW Expr + { + $$ = $2; + } + | Block + { + $$ = $1; + } ; Expr1 : Expr2 ExprT1 @@ -466,14 +543,15 @@ Expr4 : Expr5 ExprT4 } ; -ExprT4 : Op4 Expr5 ExprT4 +ExprT4 : Op4 Expr5 { var sv = new SemValue(); sv.code = $1.code; sv.pos = $1.pos; sv.expr = $2.expr; - $$ = $3; + $$ = new SemValue(); + $$.thunkList = new ArrayList<>(); $$.thunkList.add(0, sv); } | /* empty */ @@ -554,7 +632,7 @@ AfterLParen : CLASS Id ')' Expr7 if (sv.expr != null) { $$ = svExpr(new IndexSel($$.expr, sv.expr, sv.pos)); } else if (sv.exprList != null) { - $$ = svExpr(new Call($$.expr, sv.id, sv.exprList, sv.pos)); + $$ = svExpr(new Call($$.expr, sv.exprList, sv.pos)); } else { $$ = svExpr(new VarSel($$.expr, sv.id, sv.pos)); } @@ -570,7 +648,7 @@ Expr8 : Expr9 ExprT8 if (sv.expr != null) { $$ = svExpr(new IndexSel($$.expr, sv.expr, sv.pos)); } else if (sv.exprList != null) { - $$ = svExpr(new Call($$.expr, sv.id, sv.exprList, sv.pos)); + $$ = svExpr(new Call($$.expr, sv.exprList, sv.pos)); } else { $$ = svExpr(new VarSel($$.expr, sv.id, sv.pos)); } @@ -588,34 +666,28 @@ ExprT8 : '[' Expr ']' ExprT8 $$ = $4; $$.thunkList.add(0, sv); } - | '.' Id ExprListOpt ExprT8 + | '.' Id ExprT8 { var sv = new SemValue(); sv.id = $2.id; sv.pos = $2.pos; - if ($3.exprList != null) { - sv.exprList = $3.exprList; - sv.pos = $3.pos; - } - $$ = $4; + $$ = $3; $$.thunkList.add(0, sv); } - | /* empty */ + | '(' ExprList ')' ExprT8 { - $$ = new SemValue(); - $$.thunkList = new ArrayList<>(); - } - ; + var sv = new SemValue(); + sv.exprList = $2.exprList; + sv.pos = $1.pos; -ExprListOpt : '(' ExprList ')' - { - $$ = $2; - $$.pos = $1.pos; + $$ = $4; + $$.thunkList.add(0, sv); } | /* empty */ { $$ = new SemValue(); + $$.thunkList = new ArrayList<>(); } ; @@ -647,13 +719,9 @@ Expr9 : Literal $$ = svExpr(new NewArray($2.type, $2.expr, $1.pos)); } } - | Id ExprListOpt + | Id { - if ($2.exprList != null) { - $$ = svExpr(new Call($1.id, $2.exprList, $2.pos)); - } else { - $$ = svExpr(new VarSel($1.id, $1.pos)); - } + $$ = svExpr(new VarSel($1.id, $1.pos)); } ; @@ -679,25 +747,47 @@ AfterNewExpr : Id '(' ')' { $$ = svId($1.id); } - | AtomType '[' AfterLBrack + | AtomType AfterNewAtomType { $$ = $1; - for (int i = 0; i < $3.intVal; i++) { - $$.type = new TArray($$.type, $1.pos); + for (int i = 0; i < $2.typeListList.size(); i++) { + for (int j = 0; j < $2.typeListList.get(i).right; j++) { + $$.type = new TArray($$.type, $$.type.pos); + } + if ($2.typeListList.get(i).left != null) { + $$ = svType(new TLambda($$.type, $2.typeListList.get(i).left, $$.type.pos)); + } } - $$.expr = $3.expr; + $$.expr = $2.expr; } ; -AfterLBrack : ']' '[' AfterLBrack +AfterNewAtomType: '[' AfterLBrack { - $$ = $3; - $$.intVal++; + $$ = $2; + } + | '(' TypeList ')' AfterNewAtomType + { + $$ = $4; + $$.typeListList.add(0, MutablePair.of($2.typeList, 0)); // int(...) + } + ; + +AfterLBrack : ']' AfterNewAtomType + { + $$ = $2; + if ($$.typeListList.isEmpty()) { + $$.typeListList.add(MutablePair.of(null, 1)); + } + else { + Integer numArray = $$.typeListList.get(0).getRight(); + $$.typeListList.get(0).setRight(numArray + 1); // int[][](...) + } } | Expr ']' { - $$ = svExpr($1.expr); - $$.intVal = 0; // counter + $$ = svTypess(); + $$.expr = $1.expr; // use expr to store the last bracket } ; diff --git a/test.bat b/test.bat new file mode 100644 index 0000000..e901d26 --- /dev/null +++ b/test.bat @@ -0,0 +1,78 @@ +@echo off +set stage=PA3 +if "%2"=="" ( + if "%1"=="compile" ( + call gradlew build + echo gradlew build done + goto :end + ) else if "%1"=="test" ( + call gradlew build + echo gradlew build done + java -jar -ea --enable-preview build/libs/decaf.jar -t %stage% --log-color TestCases\test.decaf --log-level all + rm test.tac + goto :end + ) else if "%1"=="tac" ( + call gradlew build + echo gradlew build done + java -jar -ea --enable-preview build/libs/decaf.jar -t %stage% --log-color TestCases\test.decaf --log-level all + goto :end + ) else if "%1"=="output" ( + java -jar -ea --enable-preview build/libs/decaf.jar -t %stage% --log-color TestCases\test.decaf > TestCases\test.output + rm test.tac + goto :end + ) else if "%1"=="clean" ( + rm -r build + goto :end + ) else ( + :show_help + echo Usage: + echo Compile: test compile + echo Compile and run: test {stage} {testcase} + echo Compile and run custom test: test test + echo Output custom test result to test.output: test output + echo Compile and run custom test and keep TAC: test tac + echo Run: test {stage} {testcase} run + echo Generate testcase: test {stage} {testcase} gen + echo Compile and run but do not diff: test {stage} {testcase} nodiff + echo Compile and run and output TAC but do not diff: test {stage} {testcase} tac + echo Delete build/ , compile and run: test {stage} {testcase} full + echo Delete build/: test clean + echo {stage}: [S1, S1-LL, S2, S3] + goto :end + ) +) + +if "%3" == "" ( + call gradlew build + echo gradlew build done +) else if "%3" == "run" ( + rem a +) else if "%3" == "gen" ( + rem a +) else if "%3" == "full" ( + rm -r build + call gradlew build + echo gradlew build done +) else if "%3" == "nodiff" ( + call gradlew build + echo gradlew build done +) else if "%3" == "tac" ( + call gradlew build + echo gradlew build done +) else ( + goto :show_help +) +java -jar -ea --enable-preview build/libs/decaf.jar -t %stage% --log-color TestCases\%1\%2.decaf --log-level all +if "%3" neq "nodiff" if "%3" neq "tac" ( + java -jar -ea --enable-preview build/libs/decaf.jar -t %stage% --log-color TestCases\%1\%2.decaf > TestCases\%1\output\%2.output +) +if "%3" == "gen" ( + copy TestCases\%1\output\%2.output TestCases\%1\result\%2.result +) else if "%3" neq "nodiff" if "%3" neq "tac" ( + fc TestCases\%1\output\%2.output TestCases\%1\result\%2.result +) +if "%3" neq "tac" ( + rm %2.tac +) + +:end \ No newline at end of file