diff --git a/src/ClassFile/types/attributes.ts b/src/ClassFile/types/attributes.ts index 0335e0c..bcb0ca8 100644 --- a/src/ClassFile/types/attributes.ts +++ b/src/ClassFile/types/attributes.ts @@ -101,7 +101,7 @@ export interface SameFrameExtended { export interface AppendFrame { frameType: number /* 252-254 */; offsetDelta: number; - stack: Array; + locals: Array; } export interface FullFrame { diff --git a/src/ast/astExtractor/ast-extractor.ts b/src/ast/astExtractor/ast-extractor.ts index b6cffcc..955f78f 100644 --- a/src/ast/astExtractor/ast-extractor.ts +++ b/src/ast/astExtractor/ast-extractor.ts @@ -12,7 +12,6 @@ export class ASTExtractor extends BaseJavaCstVisitorWithDefaults { kind: "CompilationUnit", topLevelClassOrInterfaceDeclarations: [], }; - this.validateVisitor(); } extract(cst: CstNode): AST { diff --git a/src/ast/astExtractor/block-statement-extractor.ts b/src/ast/astExtractor/block-statement-extractor.ts index 5609567..10c1141 100644 --- a/src/ast/astExtractor/block-statement-extractor.ts +++ b/src/ast/astExtractor/block-statement-extractor.ts @@ -31,7 +31,6 @@ export class BlockStatementExtractor extends BaseJavaCstVisitorWithDefaults { constructor() { super(); - this.validateVisitor(); } extract(cst: BlockStatementCstNode): BlockStatement { @@ -39,10 +38,13 @@ export class BlockStatementExtractor extends BaseJavaCstVisitorWithDefaults { return { kind: "LocalVariableDeclarationStatement", localVariableType: this.type, - variableDeclarationList: { - variableDeclaratorId: this.identifier, - variableInitializer: this.value, - }, + variableDeclaratorList: [ + { + kind: "VariableDeclarator", + variableDeclaratorId: this.identifier, + variableInitializer: this.value, + } + ], }; } diff --git a/src/ast/astExtractor/class-extractor.ts b/src/ast/astExtractor/class-extractor.ts index 27069eb..4e7e7cb 100644 --- a/src/ast/astExtractor/class-extractor.ts +++ b/src/ast/astExtractor/class-extractor.ts @@ -19,7 +19,6 @@ export class ClassExtractor extends BaseJavaCstVisitorWithDefaults { this.modifier = []; this.identifier = ''; this.body = []; - this.validateVisitor(); } extract(cst: CstNode): ClassDeclaration { diff --git a/src/ast/astExtractor/method-extractor.ts b/src/ast/astExtractor/method-extractor.ts index aa2dd75..9eacbff 100644 --- a/src/ast/astExtractor/method-extractor.ts +++ b/src/ast/astExtractor/method-extractor.ts @@ -28,7 +28,6 @@ export class MethodExtractor extends BaseJavaCstVisitorWithDefaults { this.identifier = ''; this.params = []; this.body = []; - this.validateVisitor(); } private getAndPop() { @@ -48,12 +47,13 @@ export class MethodExtractor extends BaseJavaCstVisitorWithDefaults { methodModifier: this.modifier, methodHeader: { result: "void", - methodDeclarator: { - identifier: this.identifier, - formalParameterList: this.params - } + identifier: this.identifier, + formalParameterList: this.params + }, + methodBody: { + kind: "Block", + blockStatements:this.body, }, - methodBody: this.body, }; } @@ -89,8 +89,9 @@ export class MethodExtractor extends BaseJavaCstVisitorWithDefaults { const argName = this.getAndPop(); const typeName = this.getAndPop(); this.params.push({ + kind: "FormalParameter", unannType: typeName, - variableDeclaratorId: argName, + identifier: argName, }); } diff --git a/src/ast/types/ast.ts b/src/ast/types/ast.ts index 3cfcf77..4197e0a 100644 --- a/src/ast/types/ast.ts +++ b/src/ast/types/ast.ts @@ -1,22 +1,14 @@ import { CompilationUnit } from "./packages-and-modules"; import { - BinaryExpression, - Literal, - LocalVariableDeclarationStatement, - UnaryExpression, + Block, + BlockStatement, + Expression, } from "./blocks-and-statements"; -export interface ExpressionMap { - BinaryExpression: BinaryExpression; - Literal: Literal; - UnaryExpression: UnaryExpression; -} - -type Expression = ExpressionMap[keyof ExpressionMap]; - interface NodeMap { CompilationUnit: CompilationUnit; - LocalVariableDeclarationStatement: LocalVariableDeclarationStatement; + Block: Block; + BlockStatement: BlockStatement; Expression: Expression; } diff --git a/src/ast/types/blocks-and-statements.ts b/src/ast/types/blocks-and-statements.ts index 4cd969f..2ee5c51 100644 --- a/src/ast/types/blocks-and-statements.ts +++ b/src/ast/types/blocks-and-statements.ts @@ -1,32 +1,87 @@ import { Identifier, UnannType } from "./classes"; -export type BlockStatement = LocalVariableDeclarationStatement; +export interface Block { + kind: "Block"; + blockStatements: Array; +} +export type BlockStatement = LocalVariableDeclarationStatement | Statement; + export interface LocalVariableDeclarationStatement { kind: "LocalVariableDeclarationStatement"; localVariableType: LocalVariableType; - variableDeclarationList: VariableDeclarator; + variableDeclaratorList: Array; +} + +export type Statement = StatementWithoutTrailingSubstatement | IfStatement | WhileStatement | ForStatement; + +export interface IfStatement { + kind: "IfStatement"; + condition: Expression; + consequent: Statement; + alternate: Statement; +} + +export interface WhileStatement { + kind: "WhileStatement"; + condition: Expression; + body: Statement; +} + +export interface DoStatement { + kind: "DoStatement"; + condition: Expression; + body: Statement; +} + +export type ForStatement = BasicForStatement | EnhancedForStatement; +export interface BasicForStatement { + kind: "BasicForStatement"; + forInit: Array | LocalVariableDeclarationStatement; + condition: Expression; + forUpdate: Array; + body: Statement; +} + +export interface EnhancedForStatement { + kind: "EnhancedForStatement"; } +export type StatementWithoutTrailingSubstatement = Block | ExpressionStatement | DoStatement; + +export type ExpressionStatement = MethodInvocation | Assignment; + +export interface MethodInvocation { + kind: "MethodInvocation"; + identifier: Identifier + argumentList: ArgumentList; +} + +export type ArgumentList = Array; + export type LocalVariableType = UnannType; export interface VariableDeclarator { + kind: "VariableDeclarator"; variableDeclaratorId: VariableDeclaratorId; variableInitializer: VariableInitializer; } + export type VariableDeclaratorId = Identifier; export type VariableInitializer = Expression; -export type Expression = Literal | BinaryExpression | UnaryExpression; + +export type Expression = Primary | BinaryExpression | UnaryExpression; +export type Primary = Literal | ExpressionName | Assignment; export interface Literal { kind: "Literal"; literalType: - | IntegerLiteral - | FloatingPointLiteral - | BooleanLiteral - | CharacterLiteral - | TextBlockLiteral - | StringLiteral - | NullLiteral; + | IntegerLiteral + | FloatingPointLiteral + | BooleanLiteral + | CharacterLiteral + | TextBlockLiteral + | StringLiteral + | NullLiteral; } export type IntegerLiteral = @@ -34,6 +89,7 @@ export type IntegerLiteral = | HexIntegerLiteral | OctalIntegerLiteral | BinaryIntegerLiteral; + export interface DecimalIntegerLiteral { kind: "DecimalIntegerLiteral"; value: string; @@ -86,11 +142,24 @@ export interface NullLiteral { export interface BinaryExpression { kind: "BinaryExpression"; - operator: "+" | "-" | "*" | "/"; + operator: "+" | "-" | "*" | "/" | "%" | "|" | "&" | "^" | "<<" | ">>" | ">>>" | "==" | "!=" | "<" | "<=" | ">" | ">=" | "&&" | "||"; left: Expression; right: Expression; } +export interface ExpressionName { + kind: "ExpressionName"; + name: string; +} + +export interface Assignment { + kind: "Assignment"; + left: LeftHandSide; + operator: string; + right: Expression; +} + +export type LeftHandSide = ExpressionName; export type UnaryExpression = PrefixExpression; export interface PrefixExpression { kind: "PrefixExpression"; diff --git a/src/ast/types/classes.ts b/src/ast/types/classes.ts index 9766d70..469718c 100644 --- a/src/ast/types/classes.ts +++ b/src/ast/types/classes.ts @@ -1,4 +1,4 @@ -import { BlockStatement } from "./blocks-and-statements"; +import { Block } from "./blocks-and-statements"; export type ClassDeclaration = NormalClassDeclaration; @@ -41,22 +41,20 @@ export type MethodModifier = export interface MethodHeader { result: Result; - methodDeclarator: MethodDeclarator; -} - -export type Result = "void"; -export interface MethodDeclarator { identifier: Identifier; formalParameterList: Array; } +export type Result = "void"; + export interface FormalParameter { + kind: "FormalParameter"; unannType: UnannType; - variableDeclaratorId: Identifier; + identifier: Identifier; } export type UnannType = string; export type VariableDeclaratorId = Identifier; -export type MethodBody = Array; +export type MethodBody = Block; export type Identifier = string; diff --git a/src/compiler/binaryWriter.ts b/src/compiler/binary-writer.ts similarity index 72% rename from src/compiler/binaryWriter.ts rename to src/compiler/binary-writer.ts index 49872fa..a4eaf50 100644 --- a/src/compiler/binaryWriter.ts +++ b/src/compiler/binary-writer.ts @@ -1,6 +1,15 @@ import { CONSTANT_TAG } from "../ClassFile/constants/constants"; import { ClassFile } from "../ClassFile/types"; -import { AttributeInfo, CodeAttribute, ExceptionHandler } from "../ClassFile/types/attributes"; +import { + AppendFrame, + AttributeInfo, + CodeAttribute, + ExceptionHandler, + ObjectVariableInfo, + StackMapTableAttribute, + UninitializedVariableInfo, + VerificationTypeInfo +} from "../ClassFile/types/attributes"; import { ConstantClassInfo, ConstantFieldrefInfo, @@ -13,6 +22,8 @@ import { import { FieldInfo } from "../ClassFile/types/fields"; import { MethodInfo } from "../ClassFile/types/methods"; +import * as fs from "fs"; + const u1 = 1; const u2 = 2; const u4 = 4; @@ -26,7 +37,13 @@ export class BinaryWriter { this.constantPool = []; } - toBinary(classFile: ClassFile) { + writeBinary(classFile: ClassFile, filepath: string) { + const filename = filepath + "Main.class"; + const binary = this.toBinary(classFile); + fs.writeFileSync(filename, binary); + } + + private toBinary(classFile: ClassFile) { this.byteArray = []; this.constantPool = classFile.constantPool; @@ -53,7 +70,7 @@ export class BinaryWriter { private write(value: number, numOfBytes: number = u1) { - const bytes = []; + const bytes: Array = []; for (let i = 0; i < numOfBytes; i++) { bytes.push(value & 0xff); value >>>= 8; @@ -130,6 +147,9 @@ export class BinaryWriter { case "Code": this.writeCodeAttribute(attribute as CodeAttribute); break; + case "StackMapTable": + this.writeStackMapTableAttribute(attribute as StackMapTableAttribute); + break; default: ; } } @@ -140,12 +160,37 @@ export class BinaryWriter { this.write(attribute.codeLength, u4); this.writeDataView(attribute.code); this.write(attribute.exceptionTableLength, u2); - attribute.exceptionTable.forEach(e => this.writeException(e)); + attribute.exceptionTable.forEach(e => this.writeExceptionHandler(e)); this.write(attribute.attributesCount, u2); attribute.attributes.forEach(a => this.writeAttribute(a)); } - private writeException(e: ExceptionHandler) { - e; + private writeExceptionHandler(e: ExceptionHandler) { + this.write(e.startPc, u2); + this.write(e.endPc, u2); + this.write(e.handlerPc, u2); + this.write(e.catchType, u2); + } + + private writeStackMapTableAttribute(attribute: StackMapTableAttribute) { + this.write(attribute.entries.length, u2); + attribute.entries.forEach(frame => { + const { frameType: frameType } = frame; + this.write(frameType); + if (252 <= frameType && frameType <= 254) { + const { offsetDelta: offsetDelta, locals: locals } = frame as AppendFrame; + this.write(offsetDelta, u2); + locals.forEach(l => this.writeVerificationTypeInfo(l)); + } + }); + } + + private writeVerificationTypeInfo(vtInfo: VerificationTypeInfo) { + this.write(vtInfo.tag); + if (vtInfo.tag == 7) { + this.write((vtInfo as ObjectVariableInfo).cpoolIndex, u2); + } else if (vtInfo.tag == 8) { + this.write((vtInfo as UninitializedVariableInfo).offset, u2); + } } -} \ No newline at end of file +} diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts new file mode 100644 index 0000000..586cb70 --- /dev/null +++ b/src/compiler/code-generator.ts @@ -0,0 +1,350 @@ +import { OPCODE } from "../ClassFile/constants/instructions"; +import { ExceptionHandler, AttributeInfo } from "../ClassFile/types/attributes"; +import { Node } from "../ast/types/ast"; +import { + MethodInvocation, + Literal, + Block, + BinaryExpression, + LocalVariableDeclarationStatement, + ExpressionName, + Assignment, + IfStatement, + WhileStatement, + BasicForStatement, +} from "../ast/types/blocks-and-statements"; +import { MethodDeclaration } from "../ast/types/classes"; +import { ConstantPoolManager } from "./constant-pool-manager"; +import { SymbolTable, SymbolType } from "./symbol-table" + +type Label = { + offset: number; + pointedBy: number[]; +}; + +const opToOpcode: { [type: string]: OPCODE } = { + "+": OPCODE.IADD, + "-": OPCODE.ISUB, + "*": OPCODE.IMUL, + "/": OPCODE.IDIV, + "%": OPCODE.IREM, + "|": OPCODE.IOR, + "&": OPCODE.IAND, + "^": OPCODE.IXOR, + "<<": OPCODE.ISHL, + ">>": OPCODE.ISHR, + ">>>": OPCODE.IUSHR, + + "==": OPCODE.IF_ICMPEQ, + "!=": OPCODE.IF_ICMPNE, + "<": OPCODE.IF_ICMPLT, + "<=": OPCODE.IF_ICMPLE, + ">": OPCODE.IF_ICMPGT, + ">=": OPCODE.IF_ICMPGE, +}; + +const reverseLogicalOp: { [type: string]: OPCODE } = { + "==": OPCODE.IF_ICMPNE, + "!=": OPCODE.IF_ICMPEQ, + "<": OPCODE.IF_ICMPGE, + "<=": OPCODE.IF_ICMPGT, + ">": OPCODE.IF_ICMPLE, + ">=": OPCODE.IF_ICMPLT, +}; + +const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => number } = { + Block: (node: Node, cg: CodeGenerator) => { + let maxStack = 0; + (node as Block).blockStatements.forEach(x => { + maxStack = Math.max(maxStack, codeGenerators[x.kind](x, cg)); + }) + return maxStack; + }, + + LocalVariableDeclarationStatement: (node: Node, cg: CodeGenerator) => { + let maxStack = 0; + const { variableDeclaratorList: lst } = node as LocalVariableDeclarationStatement; + lst.forEach(v => { + const { variableDeclaratorId: identifier, variableInitializer: vi } = v; + const curIdx = cg.maxLocals++; + cg.symbolTable.insert(identifier, SymbolType.VARIABLE, { index: curIdx }); + if (vi) { + maxStack = Math.max(maxStack, codeGenerators[vi.kind](vi, cg)); + cg.code.push(OPCODE.ISTORE, curIdx); + } + }); + return maxStack; + }, + + BasicForStatement: (node: Node, cg: CodeGenerator) => { + let maxStack = 0; + const { forInit, condition, forUpdate, body: originalBody } = node as BasicForStatement; + + if (forInit instanceof Array) { + forInit.forEach(e => maxStack = Math.max(maxStack, codeGenerators[e.kind](e, cg))); + } else { + maxStack = Math.max(maxStack, codeGenerators[forInit.kind](forInit, cg)); + } + + const whileNode: WhileStatement = { + kind: "WhileStatement", + condition: condition, + body: { + kind: "Block", + blockStatements: [originalBody, ...forUpdate], + } + }; + maxStack = Math.max(maxStack, codeGenerators[whileNode.kind](whileNode, cg)); + + return maxStack; + }, + + DoStatement: (node: Node, cg: CodeGenerator) => { + const { body } = node as WhileStatement; + codeGenerators[body.kind](body, cg); + + node.kind = "WhileStatement"; + return codeGenerators[node.kind](node, cg); + }, + + WhileStatement: (node: Node, cg: CodeGenerator) => { + let maxStack = 0; + const { condition, body } = node as WhileStatement; + + const startLabel = cg.generateNewLabel(); + startLabel.offset = cg.code.length; + const endLabel = cg.generateNewLabel(); + + maxStack = Math.max(maxStack, codeGenerators["LogicalExpression"](condition, cg)); + maxStack = Math.max(maxStack, codeGenerators[body.kind](body, cg)); + + cg.addBranchInstr(OPCODE.GOTO, startLabel); + endLabel.offset = cg.code.length; + return maxStack; + }, + + IfStatement: (node: Node, cg: CodeGenerator) => { + let maxStack = 0; + const { condition: condition, consequent: consequent, alternate: alternate } = node as IfStatement; + + const elseLabel = cg.generateNewLabel(); + maxStack = Math.max(maxStack, codeGenerators["LogicalExpression"](condition, cg)); + maxStack = Math.max(maxStack, codeGenerators[consequent.kind](consequent, cg)); + + const endLabel = cg.generateNewLabel(); + if (alternate) { + cg.addBranchInstr(OPCODE.GOTO, endLabel); + } + + elseLabel.offset = cg.code.length; + if (alternate) { + maxStack = Math.max(maxStack, codeGenerators[alternate.kind](alternate, cg)); + endLabel.offset = cg.code.length; + } + + return maxStack; + }, + + LogicalExpression: (node: Node, cg: CodeGenerator) => { + const f = (node: Node, targetLabel: Label, onTrue: boolean): number => { + if (node.kind === "Literal") { + const { literalType: { kind: kind, value: value } } = node as Literal; + const boolValue = value === "true"; + if (kind === "BooleanLiteral" && onTrue === boolValue) { + cg.addBranchInstr(OPCODE.GOTO, targetLabel); + return 0; + } + } + + if (node.kind === "BinaryExpression") { + const { left: left, right: right, operator: op } = node as BinaryExpression; + let lsize = 0; + let rsize = 0; + if (op === "&&") { + if (onTrue) { + const falseLabel = cg.generateNewLabel(); + lsize = f(left, falseLabel, false); + rsize = f(right, targetLabel, true); + falseLabel.offset = cg.code.length; + } else { + lsize = f(left, targetLabel, false); + rsize = f(right, targetLabel, false); + } + return Math.max(lsize, 1 + rsize); + } else if (op === "||") { + if (onTrue) { + lsize = f(left, targetLabel, true); + rsize = f(right, targetLabel, true); + } else { + const falseLabel = cg.generateNewLabel(); + lsize = f(left, falseLabel, true); + rsize = f(right, targetLabel, false); + falseLabel.offset = cg.code.length; + } + return Math.max(lsize, 1 + rsize); + } else if (op in reverseLogicalOp) { + lsize = f(left, targetLabel, onTrue); + rsize = f(right, targetLabel, onTrue); + cg.addBranchInstr(onTrue ? opToOpcode[op] : reverseLogicalOp[op], targetLabel); + return Math.max(lsize, 1 + rsize); + } + } + + return codeGenerators[node.kind](node, cg); + } + return f(node, cg.labels[cg.labels.length - 1], false); + }, + + MethodInvocation: (node: Node, cg: CodeGenerator) => { + const n = node as MethodInvocation; + let maxStack = 1; + + const { parentClassName: p1, typeDescriptor: t1 } = cg.symbolTable.query("out", SymbolType.CLASS); + const { parentClassName: p2, typeDescriptor: t2 } = cg.symbolTable.query("println", SymbolType.CLASS); + const out = cg.constantPoolManager.indexFieldrefInfo(p1 as string, "out", t1 as string); + const descriptor = + n.argumentList[0].kind === "Literal" + && (n.argumentList[0] as Literal).literalType.kind === "StringLiteral" ? t2 as string : "(I)V"; + const println = cg.constantPoolManager.indexMethodrefInfo(p2 as string, "println", descriptor); + + cg.code.push(OPCODE.GETSTATIC, 0, out); + n.argumentList.forEach((x, i) => { + maxStack = Math.max(maxStack, i + 1 + codeGenerators[x.kind](x, cg)); + }) + cg.code.push(OPCODE.INVOKEVIRTUAL, 0, println); + return maxStack; + }, + + Assignment: (node: Node, cg: CodeGenerator) => { + const { left: left, operator: op, right: right } = node as Assignment; + let maxStack = op === "=" ? 0 : codeGenerators[left.kind](left, cg); + codeGenerators[right.kind](right, cg); + if (op !== "=") { + cg.code.push(opToOpcode[op.substring(0, op.length - 1)]); + } + const { index: idx } = cg.symbolTable.query(left.name, SymbolType.VARIABLE); + cg.code.push(OPCODE.ISTORE, idx as number); + return maxStack; + }, + + BinaryExpression: (node: Node, cg: CodeGenerator) => { + const { left: left, right: right, operator: op } = node as BinaryExpression; + const lsize = codeGenerators[left.kind](left, cg); + const rsize = codeGenerators[right.kind](right, cg); + cg.code.push(opToOpcode[op]); + return Math.max(lsize, 1 + rsize); + }, + + ExpressionName: (node: Node, cg: CodeGenerator) => { + const { name: name } = node as ExpressionName; + const { index: idx } = cg.symbolTable.query(name, SymbolType.VARIABLE); + cg.code.push(OPCODE.ILOAD, idx as number); + return 1; + }, + + Literal: (node: Node, cg: CodeGenerator) => { + const { kind, value } = (node as Literal).literalType; + switch (kind) { + case "StringLiteral": { + const strIdx = cg.constantPoolManager.indexStringInfo(value); + cg.code.push(OPCODE.LDC, strIdx); + return 1; + } + case "DecimalIntegerLiteral": { + const n = parseInt(value); + if (-128 <= n && n < 128) { + cg.code.push(OPCODE.BIPUSH, n); + } else if (-32768 <= n && n < 32768) { + cg.code.push(OPCODE.SIPUSH, n >> 8, n & 0xff); + } else { + const idx = cg.constantPoolManager.indexIntegerInfo(n); + cg.code.push(OPCODE.LDC, idx); + } + return 1; + } + } + + return 1; + }, +} + +class CodeGenerator { + symbolTable: SymbolTable; + constantPoolManager: ConstantPoolManager; + maxLocals: number = 0; + labels: Label[] = []; + code: number[] = []; + + constructor(symbolTable: SymbolTable, constantPoolManager: ConstantPoolManager) { + this.symbolTable = symbolTable; + this.constantPoolManager = constantPoolManager; + } + + generateNewLabel(): Label { + const lable = { + offset: 0, + pointedBy: [], + }; + this.labels.push(lable); + return lable; + } + + addBranchInstr(opcode: OPCODE, label: Label) { + label.pointedBy.push(this.code.length); + this.code.push(opcode, 0, 0); + } + + resolveLabels() { + for (let label of this.labels) { + label.pointedBy.forEach(idx => { + const offset = label.offset - idx; + this.code[idx + 1] = offset >> 8; + this.code[idx + 2] = offset & 0xff; + }); + } + } + + generateCode(methodNode: MethodDeclaration) { + this.symbolTable.extend(); + methodNode.methodHeader.formalParameterList.forEach(p => { + this.symbolTable.insert(p.identifier, SymbolType.VARIABLE, { + index: this.maxLocals + }); + this.maxLocals++; + }); + + const { methodBody } = methodNode; + const maxStack = Math.max(this.maxLocals, codeGenerators[methodBody.kind](methodBody, this)); + const exceptionTable: Array = []; + const attributes: Array = []; + + this.code.push(OPCODE.RETURN); + this.resolveLabels(); + const codeBuf = new Uint8Array(this.code).buffer; + const dataView = new DataView(codeBuf); + this.code.forEach((x, i) => dataView.setUint8(i, x)); + + const attributeLength = 12 + this.code.length + 8 * exceptionTable.length + + attributes.map(attr => attr.attributeLength + 6).reduce((acc, val) => acc + val, 0); + this.symbolTable.teardown(); + + return { + attributeNameIndex: this.constantPoolManager.indexUtf8Info("Code"), + attributeLength: attributeLength, + maxStack: maxStack, + maxLocals: this.maxLocals, + codeLength: this.code.length, + code: dataView, + exceptionTableLength: exceptionTable.length, + exceptionTable: exceptionTable, + attributesCount: attributes.length, + attributes: attributes + } + } +} + +export function generateCode(symbolTable: SymbolTable, constantPoolManager: ConstantPoolManager, + methodNode: MethodDeclaration) { + const codeGenerator = new CodeGenerator(symbolTable, constantPoolManager); + return codeGenerator.generateCode(methodNode); +} diff --git a/src/compiler/compiler-utils.ts b/src/compiler/compiler-utils.ts index da8de24..784e57e 100644 --- a/src/compiler/compiler-utils.ts +++ b/src/compiler/compiler-utils.ts @@ -1,6 +1,6 @@ import { ACCESS_FLAGS } from "../ClassFile/types"; import { METHOD_FLAGS } from "../ClassFile/types/methods"; -import { ClassModifier, FormalParameter, MethodModifier, UnannType } from "../ast/types/classes"; +import { ClassModifier, MethodModifier, UnannType } from "../ast/types/classes"; const typeMap = new Map([ ['byte', 'B'], @@ -27,16 +27,12 @@ export function generateFieldDescriptor(typeName: UnannType) { typeName = typeName.slice(0, last); if (typeName === "String") { typeName = "java/lang/String"; - } else if (typeName === "System") { - typeName = "java/lang/System"; - } else if (typeName === "PrintStream") { - typeName = "java/io/PrintStream"; } return "[".repeat(dim) + (typeMap.has(typeName) ? typeMap.get(typeName) : 'L' + typeName + ';'); } -export function generateMethodDescriptor(params: Array, result: string) { - const paramsDescriptor = params.map(p => generateFieldDescriptor(p.unannType)).join(","); +export function generateMethodDescriptor(paramsType: Array, result: string) { + const paramsDescriptor = paramsType.map(generateFieldDescriptor).join(","); const resultDescriptor = generateFieldDescriptor(result); return '(' + paramsDescriptor + ')' + resultDescriptor; diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index 2c98bbd..6e3c977 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -1,17 +1,25 @@ import { ClassFile } from "../ClassFile/types"; import { AST } from "../ast/types/packages-and-modules"; -import { ClassDeclaration, MethodBody, MethodDeclaration } from "../ast/types/classes"; -import { AttributeInfo, CodeAttribute, ExceptionHandler } from "../ClassFile/types/attributes"; +import { ClassDeclaration, MethodDeclaration } from "../ast/types/classes"; +import { AttributeInfo } from "../ClassFile/types/attributes"; import { FieldInfo } from "../ClassFile/types/fields"; import { MethodInfo } from "../ClassFile/types/methods"; import { ConstantPoolManager } from "./constant-pool-manager"; -import { generateClassAccessFlags, generateMethodAccessFlags, generateMethodDescriptor } from "./compiler-utils"; +import { + generateClassAccessFlags, + generateFieldDescriptor, + generateMethodAccessFlags, + generateMethodDescriptor +} from "./compiler-utils"; +import { SymbolTable, SymbolType } from "./symbol-table"; +import { generateCode } from "./code-generator"; const MAGIC = 0xcafebabe; const MINOR_VERSION = 0; const MAJOR_VERSION = 61; export class Compiler { + private symbolTable: SymbolTable; private constantPoolManager: ConstantPoolManager; private interfaces: Array; private fields: Array; @@ -19,14 +27,28 @@ export class Compiler { private attributes: Array; constructor() { + this.setup(); + } + + private setup() { this.constantPoolManager = new ConstantPoolManager(); this.interfaces = []; this.fields = []; this.methods = []; this.attributes = []; + this.symbolTable = new SymbolTable(); + this.symbolTable.insert("out", SymbolType.CLASS, { + parentClassName: "java/lang/System", + typeDescriptor: generateFieldDescriptor("java/io/PrintStream") + }); + this.symbolTable.insert("println", SymbolType.CLASS, { + parentClassName: "java/io/PrintStream", + typeDescriptor: generateMethodDescriptor(["java/lang/String"], "void") + }); } compile(ast: AST) { + this.setup(); const classFiles: Array = []; ast.topLevelClassOrInterfaceDeclarations.forEach(x => classFiles.push(this.compileClass(x))); return classFiles[0]; @@ -35,10 +57,10 @@ export class Compiler { private compileClass(classNode: ClassDeclaration): ClassFile { const parentClassName = "java/lang/Object"; const className = classNode.typeIdentifier; - this.addMethodrefInfo(parentClassName, "", "()V"); - const superClassIndex = this.addClassInfo(parentClassName); - const thisClassIndex = this.addClassInfo(className); - this.addUtf8Info("Code"); + this.constantPoolManager.indexMethodrefInfo(parentClassName, "", "()V"); + const superClassIndex = this.constantPoolManager.indexClassInfo(parentClassName); + const thisClassIndex = this.constantPoolManager.indexClassInfo(className); + this.constantPoolManager.indexUtf8Info("Code"); classNode.classBody.forEach(m => this.compileMethod(m)); const constantPool = this.constantPoolManager.getPool(); @@ -62,40 +84,18 @@ export class Compiler { }; } - private addUtf8Info(value: string) { - return this.constantPoolManager.addUtf8Info({ value: value }); - } - - private addClassInfo(className: string) { - return this.constantPoolManager.addClassInfo({ - name: { value: className } - }); - } - - private addMethodrefInfo(className: string, methodName: string, descriptor: string) { - return this.constantPoolManager.addMethodrefInfo({ - class: { - name: { value: className, } - }, - nameAndType: { - name: { value: methodName }, - descriptor: { value: descriptor }, - } - }); - } - private compileMethod(methodNode: MethodDeclaration) { const header = methodNode.methodHeader; - const body = methodNode.methodBody; - const methodName = header.methodDeclarator.identifier; - const params = header.methodDeclarator.formalParameterList; + const methodName = header.identifier; + const params = header.formalParameterList; - const nameIndex = this.addUtf8Info(methodName); - const descriptor = generateMethodDescriptor(params, header.result); - const descriptorIndex = this.addUtf8Info(descriptor); + const nameIndex = this.constantPoolManager.indexUtf8Info(methodName); + const descriptor = generateMethodDescriptor(params.map(x => x.unannType), header.result); + const descriptorIndex = this.constantPoolManager.indexUtf8Info(descriptor); const attributes: Array = []; - attributes.push(this.addCodeAttribute(body)); + attributes.push(generateCode(this.symbolTable, this.constantPoolManager, methodNode)); + this.methods.push({ accessFlags: generateMethodAccessFlags(methodNode.methodModifier), nameIndex: nameIndex, @@ -104,32 +104,4 @@ export class Compiler { attributes: attributes }); } - - private addCodeAttribute(block: MethodBody): CodeAttribute { - let maxStack = 0; - let maxLocals = 1; - const code: number[] = []; - const exceptionTable: Array = []; - const attributes: Array = []; - - code.push(0xb1); - const codeBuf = new Uint8Array(code).buffer; - const dataView = new DataView(codeBuf); - code.forEach((x, i) => dataView.setUint8(i, x)); - - const attributeLength = 12 + code.length + 8 * exceptionTable.length + - attributes.map(attr => attr.attributeLength + 6).reduce((acc, val) => acc + val, 0); - return { - attributeNameIndex: this.addUtf8Info("Code"), - attributeLength: attributeLength, - maxStack: maxStack, - maxLocals: maxLocals, - codeLength: code.length, - code: dataView, - exceptionTableLength: exceptionTable.length, - exceptionTable: exceptionTable, - attributesCount: attributes.length, - attributes: attributes - } - } } diff --git a/src/compiler/constant-pool-manager.ts b/src/compiler/constant-pool-manager.ts index 3dfc7cc..b0440d2 100644 --- a/src/compiler/constant-pool-manager.ts +++ b/src/compiler/constant-pool-manager.ts @@ -3,6 +3,7 @@ import { ConstantInfo } from "../ClassFile/types/constants"; import { ConstantClassValue, ConstantFieldrefValue, + ConstantIntegerValue, ConstantMethodrefValue, ConstantNameAndTypeValue, ConstantStringValue, @@ -32,28 +33,72 @@ export class ConstantPoolManager { return this.constantPool; } - addUtf8Info(value: ConstantUtf8Value) { - return this.writeIfAbsent(CONSTANT_TAG.Utf8, value); + indexUtf8Info(value: string) { + return this.writeIfAbsent(CONSTANT_TAG.Utf8, { + value: value + }); } - addClassInfo(value: ConstantClassValue) { - return this.writeIfAbsent(CONSTANT_TAG.Class, value); + indexIntegerInfo(value: number) { + return this.writeIfAbsent(CONSTANT_TAG.Integer, { + value: value + }) } - addStringInfo(value: ConstantStringValue) { - return this.writeIfAbsent(CONSTANT_TAG.String, value); + indexClassInfo(className: string) { + return this.writeIfAbsent(CONSTANT_TAG.Class, { + name: { value: className } + }); } - addNameAndTypeInfo(value: ConstantNameAndTypeValue) { - return this.writeIfAbsent(CONSTANT_TAG.NameAndType, value); + indexStringInfo(string: string) { + return this.writeIfAbsent(CONSTANT_TAG.String, { + string: { value: string } + }); } - addFieldrefInfo(value: ConstantFieldrefValue) { - return this.writeIfAbsent(CONSTANT_TAG.Fieldref, value); + indexFieldrefInfo(className: string, fieldName: string, descriptor: string) { + return this.writeIfAbsent(CONSTANT_TAG.Fieldref, { + class: { + name: { value: className, } + }, + nameAndType: { + name: { value: fieldName }, + descriptor: { value: descriptor }, + } + }); } - addMethodrefInfo(value: ConstantMethodrefValue) { - return this.writeIfAbsent(CONSTANT_TAG.Methodref, value); + indexMethodrefInfo(className: string, methodName: string, descriptor: string) { + return this.writeIfAbsent(CONSTANT_TAG.Methodref, { + class: { + name: { value: className, } + }, + nameAndType: { + name: { value: methodName }, + descriptor: { value: descriptor }, + } + }); + } + + indexNameAndTypeInfo(name: string, type: string) { + return this.writeIfAbsent(CONSTANT_TAG.NameAndType, { + name: { value: name }, + descriptor: { value: type } + }); + } + + + private addUtf8Info(value: ConstantUtf8Value) { + return this.writeIfAbsent(CONSTANT_TAG.Utf8, value); + } + + private addClassInfo(value: ConstantClassValue) { + return this.writeIfAbsent(CONSTANT_TAG.Class, value); + } + + private addNameAndTypeInfo(value: ConstantNameAndTypeValue) { + return this.writeIfAbsent(CONSTANT_TAG.NameAndType, value); } private writeIfAbsent(tag: CONSTANT_TAG, value: ConstantTypeValue) { @@ -87,6 +132,9 @@ export class ConstantPoolManager { case CONSTANT_TAG.Utf8: this.writeUtf8Info(task.value as ConstantUtf8Value); break; + case CONSTANT_TAG.Integer: + this.writeIntegerInfo(task.value as ConstantIntegerValue); + break; case CONSTANT_TAG.Class: this.writeClassInfo(task.value as ConstantClassValue); break; @@ -114,6 +162,13 @@ export class ConstantPoolManager { }); } + private writeIntegerInfo(val: ConstantIntegerValue) { + this.constantPool.push({ + tag: CONSTANT_TAG.Integer, + value: val.value, + }); + } + private writeClassInfo(val: ConstantClassValue) { const nameIndex = this.addUtf8Info(val.name); this.constantPool.push({ @@ -130,16 +185,6 @@ export class ConstantPoolManager { }); } - private writeNameAndTypeInfo(val: ConstantNameAndTypeValue) { - const nameIndex = this.addUtf8Info(val.name); - const descriptorIndex = this.addUtf8Info(val.descriptor); - this.constantPool.push({ - tag: CONSTANT_TAG.NameAndType, - nameIndex: nameIndex, - descriptorIndex: descriptorIndex, - }); - } - private writeFieldrefInfo(val: ConstantFieldrefValue) { const classIndex = this.addClassInfo(val.class); const nameAndTypeIndex = this.addNameAndTypeInfo(val.nameAndType); @@ -159,4 +204,14 @@ export class ConstantPoolManager { nameAndTypeIndex: nameAndTypeIndex, }) } + + private writeNameAndTypeInfo(val: ConstantNameAndTypeValue) { + const nameIndex = this.addUtf8Info(val.name); + const descriptorIndex = this.addUtf8Info(val.descriptor); + this.constantPool.push({ + tag: CONSTANT_TAG.NameAndType, + nameIndex: nameIndex, + descriptorIndex: descriptorIndex, + }); + } } diff --git a/src/compiler/constant-value-types.ts b/src/compiler/constant-value-types.ts index 0b3e6c1..6a5e17c 100644 --- a/src/compiler/constant-value-types.ts +++ b/src/compiler/constant-value-types.ts @@ -1,13 +1,25 @@ +export interface ConstantUtf8Value { + value: string; +} + +export interface ConstantIntegerValue { + value: number; +} + export interface ConstantClassValue { name: ConstantUtf8Value; } -export interface ConstantMethodrefValue { +export interface ConstantStringValue { + string: ConstantUtf8Value +} + +export interface ConstantFieldrefValue { class: ConstantClassValue; nameAndType: ConstantNameAndTypeValue; } -export interface ConstantFieldrefValue { +export interface ConstantMethodrefValue { class: ConstantClassValue; nameAndType: ConstantNameAndTypeValue; } @@ -17,18 +29,12 @@ export interface ConstantNameAndTypeValue { descriptor: ConstantUtf8Value; } -export interface ConstantStringValue { - string: ConstantUtf8Value -} - -export interface ConstantUtf8Value { - value: string; -} - export type ConstantTypeValue = + | ConstantUtf8Value + | ConstantIntegerValue | ConstantClassValue - | ConstantMethodrefValue - | ConstantNameAndTypeValue | ConstantStringValue - | ConstantUtf8Value; + | ConstantFieldrefValue + | ConstantMethodrefValue + | ConstantNameAndTypeValue; diff --git a/src/compiler/symbol-table.ts b/src/compiler/symbol-table.ts new file mode 100644 index 0000000..fae0acb --- /dev/null +++ b/src/compiler/symbol-table.ts @@ -0,0 +1,77 @@ +export type Symbol = { + name: string, + type: SymbolType +}; + +export enum SymbolType { + CLASS, + VARIABLE +} + +export interface SymbolInfo { + index?: number, + parentClassName?: string, + typeDescriptor?: string +}; + +type Table = Map; + +export class SymbolTable { + private tables: Array; + private curTable: Table; + private curIdx: number; + + constructor() { + this.tables = [this.getNewTable()]; + this.curTable = this.tables[0]; + this.curIdx = 0; + } + + private getNewTable() { + return new Map(); + } + + extend() { + const table = this.getNewTable(); + this.tables.push(table); + this.curTable = table; + this.curIdx++; + } + + teardown() { + this.tables.pop(); + this.curIdx--; + this.curTable = this.tables[this.curIdx]; + } + + insert(name: string, type: SymbolType, info: SymbolInfo) { + const symbol: Symbol = { + name: name, + type: type + }; + const key = JSON.stringify(symbol); + + if (this.curTable.has(key)) { + throw new Error("Same symbol already exists in the table"); + } + + this.curTable.set(key, info); + } + + query(name: string, type: SymbolType): SymbolInfo { + const symbol: Symbol = { + name: name, + type: type + }; + const key = JSON.stringify(symbol); + + for (let i = this.curIdx; i >= 0; i--) { + const table = this.tables[i]; + if (table.has(key)) { + return table.get(key) as SymbolInfo; + } + } + + throw new Error("Symbol " + name + " with type " + type + " is undefined"); + } +} \ No newline at end of file diff --git a/src/compiler/utils.ts b/src/compiler/utils.ts deleted file mode 100644 index 4d6722b..0000000 --- a/src/compiler/utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as fs from "fs"; - -export function writeToFile(filename: string, binary: Uint8Array) { - fs.writeFileSync(filename, binary); -}