From d41aaa6ea15deceb0778306ce622c781cfec4147 Mon Sep 17 00:00:00 2001 From: 1001mei <77192251+1001mei@users.noreply.github.com> Date: Wed, 10 Apr 2024 21:31:55 +0800 Subject: [PATCH 1/5] Fix conditional expression bug --- .../__tests__/__utils__/test-utils.ts | 7 +-- src/compiler/__tests__/tests/ifElse.test.ts | 51 ++++++++++++++++++- src/compiler/binary-writer.ts | 8 ++- src/compiler/code-generator.ts | 44 +++++++++------- 4 files changed, 88 insertions(+), 22 deletions(-) diff --git a/src/compiler/__tests__/__utils__/test-utils.ts b/src/compiler/__tests__/__utils__/test-utils.ts index 889d59a..f2c9974 100644 --- a/src/compiler/__tests__/__utils__/test-utils.ts +++ b/src/compiler/__tests__/__utils__/test-utils.ts @@ -1,5 +1,6 @@ import { inspect } from "util"; -import { Compiler } from "../../compiler"; +//import { Compiler } from "../../compiler"; +import { compile } from "../../index"; import { BinaryWriter } from "../../binary-writer"; import { AST } from "../../../ast/types/packages-and-modules"; import { javaPegGrammar } from "../../grammar" @@ -19,7 +20,7 @@ const pathToTestDir = "./src/compiler/__tests__/"; const parser = peggy.generate(javaPegGrammar, { allowedStartRules: ["CompilationUnit"], }); -const compiler = new Compiler(); +//const compiler = new Compiler(); const binaryWriter = new BinaryWriter(); export function runTest(program: string, expectedLines: string[]) { @@ -30,7 +31,7 @@ export function runTest(program: string, expectedLines: string[]) { console.log(inspect(ast, false, null, true)); } - const classFile = compiler.compile(ast as AST); + const classFile = compile(ast as AST); binaryWriter.writeBinary(classFile, pathToTestDir); const prevDir = process.cwd(); diff --git a/src/compiler/__tests__/tests/ifElse.test.ts b/src/compiler/__tests__/tests/ifElse.test.ts index 85cbaa6..7a287e5 100644 --- a/src/compiler/__tests__/tests/ifElse.test.ts +++ b/src/compiler/__tests__/tests/ifElse.test.ts @@ -42,6 +42,43 @@ const testCases: testCase[] = [ `, expectedLines: ["ok", "done"], }, + { + comment: "if and else, single variable conditional expression", + program: ` + public class Main { + public static void main(String[] args) { + boolean b = true; + if (b) { + System.out.println("This is expected"); + } else { + System.out.println("This is incorrect"); + } + + boolean c = false; + if (c) { + System.out.println("Incorrect"); + } else { + System.out.println("Correct"); + } + + boolean g = false; + if (!g) { + System.out.println("This is expected"); + } else { + System.out.println("This is incorrect"); + } + + boolean h = true; + if (!h) { + System.out.println("Incorrect"); + } else { + System.out.println("Correct"); + } + } + } + `, + expectedLines: ["This is expected", "Correct", "This is expected", "Correct"], + }, { comment: "if and else", program: ` @@ -200,10 +237,22 @@ const testCases: testCase[] = [ } else { System.out.println("Yes2"); } + + boolean b = true; + boolean c = !b; + + if (!(!(!(!c)))) { + System.out.println("Hmm"); + } else { + System.out.println("Yes3"); + } + + boolean d = !(!(!(b == c))); + System.out.println(d); } } `, - expectedLines: ["Yes1", "Yes2"], + expectedLines: ["Yes1", "Yes2", "Yes3", "true"], }, { comment: "complex conditional expression", diff --git a/src/compiler/binary-writer.ts b/src/compiler/binary-writer.ts index 3fd5165..59b2ea4 100644 --- a/src/compiler/binary-writer.ts +++ b/src/compiler/binary-writer.ts @@ -44,11 +44,17 @@ export class BinaryWriter { } writeBinary(classFile: ClassFile, filepath: string) { - const filename = filepath + 'Main.class' + const filename = filepath + this.getClassName(classFile) + '.class' const binary = this.toBinary(classFile) fs.writeFileSync(filename, binary) } + private getClassName(classFile: ClassFile) { + const classInfo = classFile.constantPool[classFile.thisClass - 1] as ConstantClassInfo; + const classNameInfo = classFile.constantPool[classInfo.nameIndex - 1] as ConstantUtf8Info; + return classNameInfo.value; + } + private toBinary(classFile: ClassFile) { this.byteArray = [] this.constantPool = classFile.constantPool diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts index dc955f1..25a7a2a 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -181,11 +181,11 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi cg.symbolTable.extend() let maxStack = 0 let resultType = '' - ;(node as Block).blockStatements.forEach(x => { - const { stackSize: stackSize, resultType: type } = compile(x, cg) - maxStack = Math.max(maxStack, stackSize) - resultType = type - }) + ; (node as Block).blockStatements.forEach(x => { + const { stackSize: stackSize, resultType: type } = compile(x, cg) + maxStack = Math.max(maxStack, stackSize) + resultType = type + }) cg.symbolTable.teardown() return { stackSize: maxStack, resultType } @@ -418,8 +418,10 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi literalType: { kind: kind, value: value } } = node const boolValue = value === 'true' - if (kind === 'BooleanLiteral' && onTrue === boolValue) { - cg.addBranchInstr(OPCODE.GOTO, targetLabel) + if (kind === 'BooleanLiteral') { + if (onTrue === boolValue) { + cg.addBranchInstr(OPCODE.GOTO, targetLabel) + } return { stackSize: 0, resultType: cg.symbolTable.generateFieldDescriptor('boolean') } } } @@ -470,20 +472,20 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi } else if (op in reverseLogicalOp) { if (isNullLiteral(left)) { // still use l to represent the first argument pushed onto stack - l = f(right, targetLabel, onTrue) + l = compile(right, cg) cg.addBranchInstr( onTrue !== (op === '!=') ? OPCODE.IFNULL : OPCODE.IFNONNULL, targetLabel ) } else if (isNullLiteral(right)) { - l = f(left, targetLabel, onTrue) + l = compile(left, cg) cg.addBranchInstr( onTrue !== (op === '!=') ? OPCODE.IFNULL : OPCODE.IFNONNULL, targetLabel ) } else { - l = f(left, targetLabel, onTrue) - r = f(right, targetLabel, onTrue) + l = compile(left, cg) + r = compile(right, cg) cg.addBranchInstr(onTrue ? logicalOp[op] : reverseLogicalOp[op], targetLabel) } return { @@ -496,7 +498,9 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi } } - return compile(node, cg) + const res = compile(node, cg) + cg.addBranchInstr(onTrue ? OPCODE.IFNE : OPCODE.IFEQ, cg.labels[cg.labels.length - 1]) + return res } return f(node, cg.labels[cg.labels.length - 1], false) }, @@ -799,9 +803,10 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi field ) } + const fetchedFieldTypeDescriptor = (fieldInfos[fieldInfos.length - 1] as FieldInfo).typeDescriptor return { - stackSize: 1, - resultType: (fieldInfos[fieldInfos.length - 1] as FieldInfo).typeDescriptor + stackSize: 1 + (['D', 'J'].includes(fetchedFieldTypeDescriptor) ? 1 : 0), + resultType: fetchedFieldTypeDescriptor } } else { cg.code.push( @@ -917,14 +922,19 @@ class CodeGenerator { } methodNode.methodHeader.formalParameterList.forEach(p => { - this.symbolTable.insertVariableInfo({ + const paramInfo = { name: p.identifier, accessFlags: 0, index: this.maxLocals, typeName: p.unannType, typeDescriptor: this.symbolTable.generateFieldDescriptor(p.unannType) - }) - this.maxLocals++ + } + this.symbolTable.insertVariableInfo(paramInfo) + if (['D', 'J'].includes(paramInfo.typeDescriptor)) { + this.maxLocals += 2 + } else { + this.maxLocals++ + } }) if (methodNode.methodHeader.identifier === '') { From 714ce07107931896ff96350babd8ee1b6de4d56c Mon Sep 17 00:00:00 2001 From: 1001mei <77192251+1001mei@users.noreply.github.com> Date: Wed, 10 Apr 2024 21:49:04 +0800 Subject: [PATCH 2/5] Fix eslint --- src/compiler/binary-writer.ts | 6 +++--- src/compiler/code-generator.ts | 20 +++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/compiler/binary-writer.ts b/src/compiler/binary-writer.ts index 59b2ea4..0660108 100644 --- a/src/compiler/binary-writer.ts +++ b/src/compiler/binary-writer.ts @@ -50,9 +50,9 @@ export class BinaryWriter { } private getClassName(classFile: ClassFile) { - const classInfo = classFile.constantPool[classFile.thisClass - 1] as ConstantClassInfo; - const classNameInfo = classFile.constantPool[classInfo.nameIndex - 1] as ConstantUtf8Info; - return classNameInfo.value; + const classInfo = classFile.constantPool[classFile.thisClass - 1] as ConstantClassInfo + const classNameInfo = classFile.constantPool[classInfo.nameIndex - 1] as ConstantUtf8Info + return classNameInfo.value } private toBinary(classFile: ClassFile) { diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts index 25a7a2a..a4e28d7 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -179,13 +179,14 @@ function compile(node: Node, cg: CodeGenerator): CompileResult { const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => CompileResult } = { Block: (node: Node, cg: CodeGenerator) => { cg.symbolTable.extend() + const block = node as Block let maxStack = 0 - let resultType = '' - ; (node as Block).blockStatements.forEach(x => { - const { stackSize: stackSize, resultType: type } = compile(x, cg) - maxStack = Math.max(maxStack, stackSize) - resultType = type - }) + let resultType = EMPTY_TYPE + block.blockStatements.forEach(x => { + const { stackSize: stackSize, resultType: type } = compile(x, cg) + maxStack = Math.max(maxStack, stackSize) + resultType = type + }) cg.symbolTable.teardown() return { stackSize: maxStack, resultType } @@ -548,8 +549,8 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi }, ExpressionStatement: (node: Node, cg: CodeGenerator) => { - const n = node as ExpressionStatement - return compile(n.stmtExp, cg) + const { stmtExp } = node as ExpressionStatement + return compile(stmtExp, cg) }, MethodInvocation: (node: Node, cg: CodeGenerator) => { @@ -803,7 +804,8 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi field ) } - const fetchedFieldTypeDescriptor = (fieldInfos[fieldInfos.length - 1] as FieldInfo).typeDescriptor + const fetchedFieldTypeDescriptor = (fieldInfos[fieldInfos.length - 1] as FieldInfo) + .typeDescriptor return { stackSize: 1 + (['D', 'J'].includes(fetchedFieldTypeDescriptor) ? 1 : 0), resultType: fetchedFieldTypeDescriptor From 3d2da414545ad7526855ff1ed7766b2e7a1f3f1c Mon Sep 17 00:00:00 2001 From: 1001mei <77192251+1001mei@users.noreply.github.com> Date: Wed, 10 Apr 2024 23:30:53 +0800 Subject: [PATCH 3/5] Fix unary expression stand-alone bug --- src/ast/types/blocks-and-statements.ts | 40 +++++++++---------- .../__tests__/tests/unaryExpression.test.ts | 21 ++++++++++ src/compiler/code-generator.ts | 15 +++++++ src/compiler/grammar.pegjs | 13 +++++- src/compiler/grammar.ts | 13 +++++- src/compiler/import/lib-info.ts | 20 +++++++++- 6 files changed, 97 insertions(+), 25 deletions(-) diff --git a/src/ast/types/blocks-and-statements.ts b/src/ast/types/blocks-and-statements.ts index d1b3cc4..b4042d2 100644 --- a/src/ast/types/blocks-and-statements.ts +++ b/src/ast/types/blocks-and-statements.ts @@ -79,7 +79,7 @@ export interface ExpressionStatement extends BaseNode { stmtExp: StatementExpression; } -export type StatementExpression = MethodInvocation | Assignment; +export type StatementExpression = MethodInvocation | Assignment | UnaryExpression; export interface MethodInvocation extends BaseNode { kind: "MethodInvocation"; @@ -142,13 +142,13 @@ export interface ClassInstanceCreationExpression extends BaseNode { export interface Literal extends BaseNode { kind: "Literal"; literalType: - | IntegerLiteral - | FloatingPointLiteral - | BooleanLiteral - | CharacterLiteral - | TextBlockLiteral - | StringLiteral - | NullLiteral; + | IntegerLiteral + | FloatingPointLiteral + | BooleanLiteral + | CharacterLiteral + | TextBlockLiteral + | StringLiteral + | NullLiteral; } export type IntegerLiteral = @@ -243,18 +243,18 @@ export interface Assignment extends BaseNode { kind: "Assignment"; left: LeftHandSide; operator: - | "=" - | "+=" - | "-=" - | "*=" - | "/=" - | "%=" - | "|=" - | "&=" - | "^=" - | "<<=" - | ">>=" - | ">>>="; + | "=" + | "+=" + | "-=" + | "*=" + | "/=" + | "%=" + | "|=" + | "&=" + | "^=" + | "<<=" + | ">>=" + | ">>>="; right: Expression; } diff --git a/src/compiler/__tests__/tests/unaryExpression.test.ts b/src/compiler/__tests__/tests/unaryExpression.test.ts index 604248d..ecee098 100644 --- a/src/compiler/__tests__/tests/unaryExpression.test.ts +++ b/src/compiler/__tests__/tests/unaryExpression.test.ts @@ -4,6 +4,27 @@ import { } from "../__utils__/test-utils"; const testCases: testCase[] = [ + { + comment: "single increment/decrement statement", + program: ` + public class Main { + public static void main(String[] args) { + int a = 1; + int b = 2; + + ++a; + b++; + System.out.println(a); + System.out.println(b); + + a--; + --b; + System.out.println(a); + System.out.println(b); + } + }`, + expectedLines: ["2", "3", "1", "2"], + }, { comment: "postfix increment/decrement", program: ` diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts index a4e28d7..183734b 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -550,6 +550,9 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi ExpressionStatement: (node: Node, cg: CodeGenerator) => { const { stmtExp } = node as ExpressionStatement + if (stmtExp.kind === 'PrefixExpression' || stmtExp.kind === 'PostfixExpression') { + return codeGenerators['IncrementDecrementExpression'](stmtExp, cg) + } return compile(stmtExp, cg) }, @@ -708,6 +711,18 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi } }, + IncrementDecrementExpression: (node: Node, cg: CodeGenerator) => { + // handle cases of ++x, x++, x--, --x that do not add object to operand stack + if (node.kind === 'PrefixExpression' || node.kind === 'PostfixExpression') { + const { name } = node.expression as ExpressionName + const info = cg.symbolTable.queryVariable(name) + if (!Array.isArray(info)) { + cg.code.push(OPCODE.IINC, info.index, node.operator === '++' ? 1 : -1) + } + } + return { stackSize: 0, resultType: EMPTY_TYPE } + }, + PrefixExpression: (node: Node, cg: CodeGenerator) => { const { operator: op, expression: expr } = node as PrefixExpression if (op === '++' || op === '--') { diff --git a/src/compiler/grammar.pegjs b/src/compiler/grammar.pegjs index a3bb96d..788c642 100755 --- a/src/compiler/grammar.pegjs +++ b/src/compiler/grammar.pegjs @@ -862,11 +862,20 @@ ForUpdate StatementExpressionList = head:StatementExpression tail:(comma @StatementExpression)* { - return [head, ...tail]; + return [head, ...tail].map(s => { + return { + kind: "ExpressionStatement", stmtExp: s + } + }); } ExpressionStatement - = @StatementExpression semicolon + = se:StatementExpression semicolon { + return { + kind: "ExpressionStatement", + stmtExp: se, + } + } StatementExpression = Assignment diff --git a/src/compiler/grammar.ts b/src/compiler/grammar.ts index 16a3516..d1a2356 100755 --- a/src/compiler/grammar.ts +++ b/src/compiler/grammar.ts @@ -864,11 +864,20 @@ ForUpdate StatementExpressionList = head:StatementExpression tail:(comma @StatementExpression)* { - return [head, ...tail]; + return [head, ...tail].map(s => { + return { + kind: "ExpressionStatement", stmtExp: s + } + }); } ExpressionStatement - = @StatementExpression semicolon + = se:StatementExpression semicolon { + return { + kind: "ExpressionStatement", + stmtExp: se, + } + } StatementExpression = Assignment diff --git a/src/compiler/import/lib-info.ts b/src/compiler/import/lib-info.ts index 4f393dc..d334e76 100644 --- a/src/compiler/import/lib-info.ts +++ b/src/compiler/import/lib-info.ts @@ -34,7 +34,25 @@ export const rawLibInfo = { "public void println(float)", "public void println(double)", "public void println(char)", - "public void println(boolean)" + "public void println(boolean)", + "public void print(java.lang.String)", + "public void print(int)", + "public void print(long)", + "public void print(float)", + "public void print(double)", + "public void print(char)", + "public void print(boolean)" + ] + } + ] + }, + { + "name": "java.util", + "classes": [ + { + "name": "public java.util.Arrays", + "methods": [ + "public static java.lang.String toString(int[])" ] } ] From b40010ee7ad9819b8ce60a9ccc0052cae39530b2 Mon Sep 17 00:00:00 2001 From: 1001mei <77192251+1001mei@users.noreply.github.com> Date: Wed, 10 Apr 2024 23:37:48 +0800 Subject: [PATCH 4/5] Fix linting issue --- src/ast/types/blocks-and-statements.ts | 38 +++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/ast/types/blocks-and-statements.ts b/src/ast/types/blocks-and-statements.ts index b4042d2..fe5dc7a 100644 --- a/src/ast/types/blocks-and-statements.ts +++ b/src/ast/types/blocks-and-statements.ts @@ -142,13 +142,13 @@ export interface ClassInstanceCreationExpression extends BaseNode { export interface Literal extends BaseNode { kind: "Literal"; literalType: - | IntegerLiteral - | FloatingPointLiteral - | BooleanLiteral - | CharacterLiteral - | TextBlockLiteral - | StringLiteral - | NullLiteral; + | IntegerLiteral + | FloatingPointLiteral + | BooleanLiteral + | CharacterLiteral + | TextBlockLiteral + | StringLiteral + | NullLiteral; } export type IntegerLiteral = @@ -243,18 +243,18 @@ export interface Assignment extends BaseNode { kind: "Assignment"; left: LeftHandSide; operator: - | "=" - | "+=" - | "-=" - | "*=" - | "/=" - | "%=" - | "|=" - | "&=" - | "^=" - | "<<=" - | ">>=" - | ">>>="; + | "=" + | "+=" + | "-=" + | "*=" + | "/=" + | "%=" + | "|=" + | "&=" + | "^=" + | "<<=" + | ">>=" + | ">>>="; right: Expression; } From d3a1ce20aeb3315eba57ca7c34cf00f4d6228404 Mon Sep 17 00:00:00 2001 From: 1001mei <77192251+1001mei@users.noreply.github.com> Date: Wed, 10 Apr 2024 23:40:14 +0800 Subject: [PATCH 5/5] Remove commented lines --- src/compiler/__tests__/__utils__/test-utils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compiler/__tests__/__utils__/test-utils.ts b/src/compiler/__tests__/__utils__/test-utils.ts index f2c9974..ac9fec1 100644 --- a/src/compiler/__tests__/__utils__/test-utils.ts +++ b/src/compiler/__tests__/__utils__/test-utils.ts @@ -1,5 +1,4 @@ import { inspect } from "util"; -//import { Compiler } from "../../compiler"; import { compile } from "../../index"; import { BinaryWriter } from "../../binary-writer"; import { AST } from "../../../ast/types/packages-and-modules"; @@ -20,7 +19,6 @@ const pathToTestDir = "./src/compiler/__tests__/"; const parser = peggy.generate(javaPegGrammar, { allowedStartRules: ["CompilationUnit"], }); -//const compiler = new Compiler(); const binaryWriter = new BinaryWriter(); export function runTest(program: string, expectedLines: string[]) {