Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ast/types/blocks-and-statements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
5 changes: 2 additions & 3 deletions src/compiler/__tests__/__utils__/test-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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";
import { javaPegGrammar } from "../../grammar"
Expand All @@ -19,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[]) {
Expand All @@ -30,7 +29,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();
Expand Down
51 changes: 50 additions & 1 deletion src/compiler/__tests__/tests/ifElse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: `
Expand Down Expand Up @@ -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",
Expand Down
21 changes: 21 additions & 0 deletions src/compiler/__tests__/tests/unaryExpression.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: `
Expand Down
8 changes: 7 additions & 1 deletion src/compiler/binary-writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
59 changes: 43 additions & 16 deletions src/compiler/code-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,10 @@ 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 => {
let resultType = EMPTY_TYPE
block.blockStatements.forEach(x => {
const { stackSize: stackSize, resultType: type } = compile(x, cg)
maxStack = Math.max(maxStack, stackSize)
resultType = type
Expand Down Expand Up @@ -418,8 +419,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') }
}
}
Expand Down Expand Up @@ -470,20 +473,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 {
Expand All @@ -496,7 +499,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)
},
Expand Down Expand Up @@ -544,8 +549,11 @@ 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
if (stmtExp.kind === 'PrefixExpression' || stmtExp.kind === 'PostfixExpression') {
return codeGenerators['IncrementDecrementExpression'](stmtExp, cg)
}
return compile(stmtExp, cg)
},

MethodInvocation: (node: Node, cg: CodeGenerator) => {
Expand Down Expand Up @@ -703,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 === '--') {
Expand Down Expand Up @@ -799,9 +819,11 @@ 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(
Expand Down Expand Up @@ -917,14 +939,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 === '<init>') {
Expand Down
13 changes: 11 additions & 2 deletions src/compiler/grammar.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 11 additions & 2 deletions src/compiler/grammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 19 additions & 1 deletion src/compiler/import/lib-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[])"
]
}
]
Expand Down