Skip to content

Commit 76b396b

Browse files
authored
Merge pull request #43 from source-academy/compiler
Fix compiler bugs
2 parents fbe7220 + d3a1ce2 commit 76b396b

File tree

9 files changed

+165
-27
lines changed

9 files changed

+165
-27
lines changed

src/ast/types/blocks-and-statements.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export interface ExpressionStatement extends BaseNode {
7979
stmtExp: StatementExpression;
8080
}
8181

82-
export type StatementExpression = MethodInvocation | Assignment;
82+
export type StatementExpression = MethodInvocation | Assignment | UnaryExpression;
8383

8484
export interface MethodInvocation extends BaseNode {
8585
kind: "MethodInvocation";

src/compiler/__tests__/__utils__/test-utils.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { inspect } from "util";
2-
import { Compiler } from "../../compiler";
2+
import { compile } from "../../index";
33
import { BinaryWriter } from "../../binary-writer";
44
import { AST } from "../../../ast/types/packages-and-modules";
55
import { javaPegGrammar } from "../../grammar"
@@ -19,7 +19,6 @@ const pathToTestDir = "./src/compiler/__tests__/";
1919
const parser = peggy.generate(javaPegGrammar, {
2020
allowedStartRules: ["CompilationUnit"],
2121
});
22-
const compiler = new Compiler();
2322
const binaryWriter = new BinaryWriter();
2423

2524
export function runTest(program: string, expectedLines: string[]) {
@@ -30,7 +29,7 @@ export function runTest(program: string, expectedLines: string[]) {
3029
console.log(inspect(ast, false, null, true));
3130
}
3231

33-
const classFile = compiler.compile(ast as AST);
32+
const classFile = compile(ast as AST);
3433
binaryWriter.writeBinary(classFile, pathToTestDir);
3534

3635
const prevDir = process.cwd();

src/compiler/__tests__/tests/ifElse.test.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,43 @@ const testCases: testCase[] = [
4242
`,
4343
expectedLines: ["ok", "done"],
4444
},
45+
{
46+
comment: "if and else, single variable conditional expression",
47+
program: `
48+
public class Main {
49+
public static void main(String[] args) {
50+
boolean b = true;
51+
if (b) {
52+
System.out.println("This is expected");
53+
} else {
54+
System.out.println("This is incorrect");
55+
}
56+
57+
boolean c = false;
58+
if (c) {
59+
System.out.println("Incorrect");
60+
} else {
61+
System.out.println("Correct");
62+
}
63+
64+
boolean g = false;
65+
if (!g) {
66+
System.out.println("This is expected");
67+
} else {
68+
System.out.println("This is incorrect");
69+
}
70+
71+
boolean h = true;
72+
if (!h) {
73+
System.out.println("Incorrect");
74+
} else {
75+
System.out.println("Correct");
76+
}
77+
}
78+
}
79+
`,
80+
expectedLines: ["This is expected", "Correct", "This is expected", "Correct"],
81+
},
4582
{
4683
comment: "if and else",
4784
program: `
@@ -200,10 +237,22 @@ const testCases: testCase[] = [
200237
} else {
201238
System.out.println("Yes2");
202239
}
240+
241+
boolean b = true;
242+
boolean c = !b;
243+
244+
if (!(!(!(!c)))) {
245+
System.out.println("Hmm");
246+
} else {
247+
System.out.println("Yes3");
248+
}
249+
250+
boolean d = !(!(!(b == c)));
251+
System.out.println(d);
203252
}
204253
}
205254
`,
206-
expectedLines: ["Yes1", "Yes2"],
255+
expectedLines: ["Yes1", "Yes2", "Yes3", "true"],
207256
},
208257
{
209258
comment: "complex conditional expression",

src/compiler/__tests__/tests/unaryExpression.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,27 @@ import {
44
} from "../__utils__/test-utils";
55

66
const testCases: testCase[] = [
7+
{
8+
comment: "single increment/decrement statement",
9+
program: `
10+
public class Main {
11+
public static void main(String[] args) {
12+
int a = 1;
13+
int b = 2;
14+
15+
++a;
16+
b++;
17+
System.out.println(a);
18+
System.out.println(b);
19+
20+
a--;
21+
--b;
22+
System.out.println(a);
23+
System.out.println(b);
24+
}
25+
}`,
26+
expectedLines: ["2", "3", "1", "2"],
27+
},
728
{
829
comment: "postfix increment/decrement",
930
program: `

src/compiler/binary-writer.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,17 @@ export class BinaryWriter {
4444
}
4545

4646
writeBinary(classFile: ClassFile, filepath: string) {
47-
const filename = filepath + 'Main.class'
47+
const filename = filepath + this.getClassName(classFile) + '.class'
4848
const binary = this.toBinary(classFile)
4949
fs.writeFileSync(filename, binary)
5050
}
5151

52+
private getClassName(classFile: ClassFile) {
53+
const classInfo = classFile.constantPool[classFile.thisClass - 1] as ConstantClassInfo
54+
const classNameInfo = classFile.constantPool[classInfo.nameIndex - 1] as ConstantUtf8Info
55+
return classNameInfo.value
56+
}
57+
5258
private toBinary(classFile: ClassFile) {
5359
this.byteArray = []
5460
this.constantPool = classFile.constantPool

src/compiler/code-generator.ts

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,10 @@ function compile(node: Node, cg: CodeGenerator): CompileResult {
179179
const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => CompileResult } = {
180180
Block: (node: Node, cg: CodeGenerator) => {
181181
cg.symbolTable.extend()
182+
const block = node as Block
182183
let maxStack = 0
183-
let resultType = ''
184-
;(node as Block).blockStatements.forEach(x => {
184+
let resultType = EMPTY_TYPE
185+
block.blockStatements.forEach(x => {
185186
const { stackSize: stackSize, resultType: type } = compile(x, cg)
186187
maxStack = Math.max(maxStack, stackSize)
187188
resultType = type
@@ -418,8 +419,10 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi
418419
literalType: { kind: kind, value: value }
419420
} = node
420421
const boolValue = value === 'true'
421-
if (kind === 'BooleanLiteral' && onTrue === boolValue) {
422-
cg.addBranchInstr(OPCODE.GOTO, targetLabel)
422+
if (kind === 'BooleanLiteral') {
423+
if (onTrue === boolValue) {
424+
cg.addBranchInstr(OPCODE.GOTO, targetLabel)
425+
}
423426
return { stackSize: 0, resultType: cg.symbolTable.generateFieldDescriptor('boolean') }
424427
}
425428
}
@@ -470,20 +473,20 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi
470473
} else if (op in reverseLogicalOp) {
471474
if (isNullLiteral(left)) {
472475
// still use l to represent the first argument pushed onto stack
473-
l = f(right, targetLabel, onTrue)
476+
l = compile(right, cg)
474477
cg.addBranchInstr(
475478
onTrue !== (op === '!=') ? OPCODE.IFNULL : OPCODE.IFNONNULL,
476479
targetLabel
477480
)
478481
} else if (isNullLiteral(right)) {
479-
l = f(left, targetLabel, onTrue)
482+
l = compile(left, cg)
480483
cg.addBranchInstr(
481484
onTrue !== (op === '!=') ? OPCODE.IFNULL : OPCODE.IFNONNULL,
482485
targetLabel
483486
)
484487
} else {
485-
l = f(left, targetLabel, onTrue)
486-
r = f(right, targetLabel, onTrue)
488+
l = compile(left, cg)
489+
r = compile(right, cg)
487490
cg.addBranchInstr(onTrue ? logicalOp[op] : reverseLogicalOp[op], targetLabel)
488491
}
489492
return {
@@ -496,7 +499,9 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi
496499
}
497500
}
498501

499-
return compile(node, cg)
502+
const res = compile(node, cg)
503+
cg.addBranchInstr(onTrue ? OPCODE.IFNE : OPCODE.IFEQ, cg.labels[cg.labels.length - 1])
504+
return res
500505
}
501506
return f(node, cg.labels[cg.labels.length - 1], false)
502507
},
@@ -544,8 +549,11 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi
544549
},
545550

546551
ExpressionStatement: (node: Node, cg: CodeGenerator) => {
547-
const n = node as ExpressionStatement
548-
return compile(n.stmtExp, cg)
552+
const { stmtExp } = node as ExpressionStatement
553+
if (stmtExp.kind === 'PrefixExpression' || stmtExp.kind === 'PostfixExpression') {
554+
return codeGenerators['IncrementDecrementExpression'](stmtExp, cg)
555+
}
556+
return compile(stmtExp, cg)
549557
},
550558

551559
MethodInvocation: (node: Node, cg: CodeGenerator) => {
@@ -703,6 +711,18 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi
703711
}
704712
},
705713

714+
IncrementDecrementExpression: (node: Node, cg: CodeGenerator) => {
715+
// handle cases of ++x, x++, x--, --x that do not add object to operand stack
716+
if (node.kind === 'PrefixExpression' || node.kind === 'PostfixExpression') {
717+
const { name } = node.expression as ExpressionName
718+
const info = cg.symbolTable.queryVariable(name)
719+
if (!Array.isArray(info)) {
720+
cg.code.push(OPCODE.IINC, info.index, node.operator === '++' ? 1 : -1)
721+
}
722+
}
723+
return { stackSize: 0, resultType: EMPTY_TYPE }
724+
},
725+
706726
PrefixExpression: (node: Node, cg: CodeGenerator) => {
707727
const { operator: op, expression: expr } = node as PrefixExpression
708728
if (op === '++' || op === '--') {
@@ -799,9 +819,11 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi
799819
field
800820
)
801821
}
822+
const fetchedFieldTypeDescriptor = (fieldInfos[fieldInfos.length - 1] as FieldInfo)
823+
.typeDescriptor
802824
return {
803-
stackSize: 1,
804-
resultType: (fieldInfos[fieldInfos.length - 1] as FieldInfo).typeDescriptor
825+
stackSize: 1 + (['D', 'J'].includes(fetchedFieldTypeDescriptor) ? 1 : 0),
826+
resultType: fetchedFieldTypeDescriptor
805827
}
806828
} else {
807829
cg.code.push(
@@ -917,14 +939,19 @@ class CodeGenerator {
917939
}
918940

919941
methodNode.methodHeader.formalParameterList.forEach(p => {
920-
this.symbolTable.insertVariableInfo({
942+
const paramInfo = {
921943
name: p.identifier,
922944
accessFlags: 0,
923945
index: this.maxLocals,
924946
typeName: p.unannType,
925947
typeDescriptor: this.symbolTable.generateFieldDescriptor(p.unannType)
926-
})
927-
this.maxLocals++
948+
}
949+
this.symbolTable.insertVariableInfo(paramInfo)
950+
if (['D', 'J'].includes(paramInfo.typeDescriptor)) {
951+
this.maxLocals += 2
952+
} else {
953+
this.maxLocals++
954+
}
928955
})
929956

930957
if (methodNode.methodHeader.identifier === '<init>') {

src/compiler/grammar.pegjs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -862,11 +862,20 @@ ForUpdate
862862
863863
StatementExpressionList
864864
= head:StatementExpression tail:(comma @StatementExpression)* {
865-
return [head, ...tail];
865+
return [head, ...tail].map(s => {
866+
return {
867+
kind: "ExpressionStatement", stmtExp: s
868+
}
869+
});
866870
}
867871
868872
ExpressionStatement
869-
= @StatementExpression semicolon
873+
= se:StatementExpression semicolon {
874+
return {
875+
kind: "ExpressionStatement",
876+
stmtExp: se,
877+
}
878+
}
870879
871880
StatementExpression
872881
= Assignment

src/compiler/grammar.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -864,11 +864,20 @@ ForUpdate
864864
865865
StatementExpressionList
866866
= head:StatementExpression tail:(comma @StatementExpression)* {
867-
return [head, ...tail];
867+
return [head, ...tail].map(s => {
868+
return {
869+
kind: "ExpressionStatement", stmtExp: s
870+
}
871+
});
868872
}
869873
870874
ExpressionStatement
871-
= @StatementExpression semicolon
875+
= se:StatementExpression semicolon {
876+
return {
877+
kind: "ExpressionStatement",
878+
stmtExp: se,
879+
}
880+
}
872881
873882
StatementExpression
874883
= Assignment

src/compiler/import/lib-info.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,25 @@ export const rawLibInfo = {
3434
"public void println(float)",
3535
"public void println(double)",
3636
"public void println(char)",
37-
"public void println(boolean)"
37+
"public void println(boolean)",
38+
"public void print(java.lang.String)",
39+
"public void print(int)",
40+
"public void print(long)",
41+
"public void print(float)",
42+
"public void print(double)",
43+
"public void print(char)",
44+
"public void print(boolean)"
45+
]
46+
}
47+
]
48+
},
49+
{
50+
"name": "java.util",
51+
"classes": [
52+
{
53+
"name": "public java.util.Arrays",
54+
"methods": [
55+
"public static java.lang.String toString(int[])"
3856
]
3957
}
4058
]

0 commit comments

Comments
 (0)