Skip to content

Commit

Permalink
Finish everything in PA1-A, PA1-B, PA2 and PA3.
Browse files Browse the repository at this point in the history
  • Loading branch information
xumingkuan committed Jan 25, 2020
1 parent 315b97f commit 08aafd5
Show file tree
Hide file tree
Showing 51 changed files with 2,191 additions and 254 deletions.
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,7 @@ javadoc {
options.addBooleanOption('-enable-preview', true)
options.showFromPrivate()
}

tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
57 changes: 57 additions & 0 deletions report-PA1-A.md
Original file line number Diff line number Diff line change
@@ -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<Block>`,然后发现需要修一下TacGen.java, Namer.java, Typer.java, Decaf.spec这些与本阶段无关的文件的依赖关系。

在Tree.java中,我为`Modifiers`类加入了`abstract`修饰符,并为`MethodDef``ClassDef`类增加了这一修饰。

### 局部类型推断

与抽象类类似,我照着给出的EBNF语法规范修改了Decaf.jacc,然后我以为需要写类型推断,就开始写一个新的构造函数试图通过`Expr initVal`来推断类型,直到我发现答案中的类型是`<none>`。于是我只能将`typeLit`的类型改为`Optional<typeLit>`,并修一下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)`<del>并发现前者是从`svFields`复制过来的但忘了把`FIELD_LIST`改掉了于是发了个pr</del>。

#### Lambda 表达式

我在Tree.java中参考其他类实现了继承`Expr`类的`Lambda`类,其中指导书中的`paramList`我沿用了代码中的`List<LocalVarDef>`(毕竟是按照`LocalVarDef`来打印)。对于两种Lambda 表达式,我用了`Optional<Expr>``Optional<Block>`两个变量,并保证其中有且仅有一个是`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)`这条语句的二义性问题。
Binary file added report-PA1-A.pdf
Binary file not shown.
85 changes: 85 additions & 0 deletions report-PA1-B.md
Original file line number Diff line number Diff line change
@@ -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<MutablePair<List<Tree.TypeLit>, 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`使其对应成员字段选择、数组索引、函数调用这一优先级<del>,同时发现函数类型部分可以使用`SemValue``thunkList`而无需开一个`List<MutablePair<List<Tree.TypeLit>, Integer>> typeListList`</del>。对于`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`消耗了第一个`'}'`,于是在第二个`'}'`处再次报错。

## 致谢

无。
Binary file added report-PA1-B.pdf
Binary file not shown.
Loading

0 comments on commit 08aafd5

Please sign in to comment.