From 70eedf6d95db7de6ef26e67ec0f97cc13cea7728 Mon Sep 17 00:00:00 2001 From: Aprup Kale Date: Tue, 14 Jan 2025 14:53:30 +0800 Subject: [PATCH 01/11] Add opcodes for type conversions to code-generator.ts --- src/compiler/__tests__/index.ts | 2 + .../tests/assignmentExpression.test.ts | 55 ++++++++++++++ src/compiler/code-generator.ts | 74 +++++++++++++++++-- 3 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 src/compiler/__tests__/tests/assignmentExpression.test.ts diff --git a/src/compiler/__tests__/index.ts b/src/compiler/__tests__/index.ts index 8f31ef5c..b5b6e742 100644 --- a/src/compiler/__tests__/index.ts +++ b/src/compiler/__tests__/index.ts @@ -9,6 +9,7 @@ import { methodInvocationTest } from "./tests/methodInvocation.test"; import { importTest } from "./tests/import.test"; import { arrayTest } from "./tests/array.test"; import { classTest } from "./tests/class.test"; +import { assignmentExpressionTest } from './tests/assignmentExpression.test' describe("compiler tests", () => { printlnTest(); @@ -22,4 +23,5 @@ describe("compiler tests", () => { importTest(); arrayTest(); classTest(); + assignmentExpressionTest(); }) \ No newline at end of file diff --git a/src/compiler/__tests__/tests/assignmentExpression.test.ts b/src/compiler/__tests__/tests/assignmentExpression.test.ts new file mode 100644 index 00000000..688a5d17 --- /dev/null +++ b/src/compiler/__tests__/tests/assignmentExpression.test.ts @@ -0,0 +1,55 @@ +import { + runTest, + testCase, +} from "../__utils__/test-utils"; + +const testCases: testCase[] = [ + { + comment: "int to double assignment", + program: ` + public class Main { + public static void main(String[] args) { + int x = 5; + double y = x; + System.out.println(y); + } + } + `, + expectedLines: ["5.0"], + }, + { + comment: "int to double conversion", + program: ` + public class Main { + public static void main(String[] args) { + int x = 5; + double y; + y = x; + System.out.println(y); + } + } + `, + expectedLines: ["5.0"], + }, + { + comment: "int to double conversion, array", + program: ` + public class Main { + public static void main(String[] args) { + int x = 6; + double[] y = {1.0, 2.0, 3.0, 4.0, 5.0}; + y[1] = x; + System.out.println(y[1]); + } + } + `, + expectedLines: ["6.0"], + }, +]; + +export const assignmentExpressionTest = () => describe("assignment expression", () => { + for (let testCase of testCases) { + const { comment: comment, program: program, expectedLines: expectedLines } = testCase; + it(comment, () => runTest(program, expectedLines)); + } +}); diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts index 1b0d8c86..2a75f66f 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -164,12 +164,64 @@ const normalStoreOp: { [type: string]: OPCODE } = { Z: OPCODE.ISTORE } +// const typeConversions: { [key: string]: OPCODE } = { +// 'I->F': OPCODE.I2F, +// 'I->D': OPCODE.I2D, +// 'I->L': OPCODE.I2L, +// 'I->B': OPCODE.I2B, +// 'I->C': OPCODE.I2C, +// 'I->S': OPCODE.I2S, +// 'F->D': OPCODE.F2D, +// 'F->I': OPCODE.F2I, +// 'F->L': OPCODE.F2L, +// 'D->F': OPCODE.D2F, +// 'D->I': OPCODE.D2I, +// 'D->L': OPCODE.D2L, +// 'L->I': OPCODE.L2I, +// 'L->F': OPCODE.L2F, +// 'L->D': OPCODE.L2D +// }; + +const typeConversionsImplicit: { [key: string]: OPCODE } = { + 'I->F': OPCODE.I2F, + 'I->D': OPCODE.I2D, + 'I->L': OPCODE.I2L, + 'F->D': OPCODE.F2D, + 'L->F': OPCODE.L2F, + 'L->D': OPCODE.L2D +}; + type CompileResult = { stackSize: number resultType: string } const EMPTY_TYPE: string = '' +function handleImplicitTypeConversion(fromType: string, toType: string, cg: CodeGenerator) { + if (fromType === toType) { + return; + } + const conversionKey = `${fromType}->${toType}`; + if (conversionKey in typeConversionsImplicit) { + cg.code.push(typeConversionsImplicit[conversionKey]); + } else { + throw new Error(`Unsupported implicit type conversion: ${conversionKey}`); + } +} + +// function handleExplicitTypeConversion(fromType: string, toType: string, cg: CodeGenerator) { +// if (fromType === toType) { +// return; +// } +// const conversionKey = `${fromType}->${toType}`; +// if (conversionKey in typeConversions) { +// cg.code.push(typeConversions[conversionKey]); +// } else { +// throw new Error(`Unsupported explicit type conversion: ${conversionKey}`); +// } +// } + + const isNullLiteral = (node: Node) => { return node.kind === 'Literal' && node.literalType.kind === 'NullLiteral' } @@ -245,13 +297,16 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi vi.forEach((val, i) => { cg.code.push(OPCODE.DUP) const size1 = compile(createIntLiteralNode(i), cg).stackSize - const size2 = compile(val as Expression, cg).stackSize + const { stackSize: size2, resultType } = compile(val as Expression, cg); + handleImplicitTypeConversion(resultType, arrayElemType, cg); cg.code.push(arrayElemType in arrayStoreOp ? arrayStoreOp[arrayElemType] : OPCODE.AASTORE) maxStack = Math.max(maxStack, 2 + size1 + size2) }) cg.code.push(OPCODE.ASTORE, curIdx) } else { - maxStack = Math.max(maxStack, compile(vi, cg).stackSize) + const { stackSize: initializerStackSize, resultType: initializerType } = compile(vi, cg); + handleImplicitTypeConversion(initializerType, variableInfo.typeDescriptor, cg); + maxStack = Math.max(maxStack, initializerStackSize); cg.code.push( variableInfo.typeDescriptor in normalStoreOp ? normalStoreOp[variableInfo.typeDescriptor] @@ -662,15 +717,20 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi if (lhs.kind === 'ArrayAccess') { const { stackSize: size1, resultType: arrayType } = compile(lhs.primary, cg) const size2 = compile(lhs.expression, cg).stackSize - maxStack = size1 + size2 + compile(right, cg).stackSize + const { stackSize: rhsSize, resultType: rhsType } = compile(right, cg); + maxStack = size1 + size2 + rhsSize + const arrayElemType = arrayType.slice(1) + handleImplicitTypeConversion(rhsType, arrayElemType, cg); cg.code.push(arrayElemType in arrayStoreOp ? arrayStoreOp[arrayElemType] : OPCODE.AASTORE) } else if ( lhs.kind === 'ExpressionName' && !Array.isArray(cg.symbolTable.queryVariable(lhs.name)) ) { const info = cg.symbolTable.queryVariable(lhs.name) as VariableInfo - maxStack = 1 + compile(right, cg).stackSize + const { stackSize: rhsSize, resultType: rhsType } = compile(right, cg); + handleImplicitTypeConversion(rhsType, info.typeDescriptor, cg); + maxStack = 1 + rhsSize cg.code.push( info.typeDescriptor in normalStoreOp ? normalStoreOp[info.typeDescriptor] : OPCODE.ASTORE, info.index @@ -693,7 +753,11 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi cg.code.push(OPCODE.ALOAD, 0) maxStack += 1 } - maxStack += compile(right, cg).stackSize + + const { stackSize: rhsSize, resultType: rhsType } = compile(right, cg); + handleImplicitTypeConversion(rhsType, fieldInfo.typeDescriptor, cg); + + maxStack += rhsSize cg.code.push( fieldInfo.accessFlags & FIELD_FLAGS.ACC_STATIC ? OPCODE.PUTSTATIC : OPCODE.PUTFIELD, 0, From 64e245d3f9897a7fedb53b79b4543940c72a28a7 Mon Sep 17 00:00:00 2001 From: Aprup Kale Date: Tue, 14 Jan 2025 18:27:01 +0800 Subject: [PATCH 02/11] Add implicit type conversions in MethodInvocation to code-generator.ts --- src/compiler/code-generator.ts | 52 ++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts index 2a75f66f..601aa958 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -197,10 +197,25 @@ type CompileResult = { } const EMPTY_TYPE: string = '' +function areClassTypesCompatible(fromType: string, toType: string): boolean { + const cleanFrom = fromType.replace(/^L|;$/g, ''); + const cleanTo = toType.replace(/^L|;$/g, ''); + return cleanFrom === cleanTo; +} + function handleImplicitTypeConversion(fromType: string, toType: string, cg: CodeGenerator) { - if (fromType === toType) { + console.debug(`Converting from: ${fromType}, to: ${toType}`); + if (fromType === toType || toType.replace(/^L|;$/g, '') === 'java/lang/String') { return; } + + if (fromType.startsWith('L') || toType.startsWith('L')) { + if (areClassTypesCompatible(fromType, toType) || fromType === "") { + return; + } + throw new Error(`Unsupported class type conversion: ${fromType} -> ${toType}`); + } + const conversionKey = `${fromType}->${toType}`; if (conversionKey in typeConversionsImplicit) { cg.code.push(typeConversionsImplicit[conversionKey]); @@ -627,6 +642,10 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi let resultType = EMPTY_TYPE const symbolInfos = cg.symbolTable.queryMethod(n.identifier) + if (!symbolInfos || symbolInfos.length === 0) { + throw new Error(`Method not found: ${n.identifier}`); + } + for (let i = 0; i < symbolInfos.length - 1; i++) { if (i === 0) { const varInfo = symbolInfos[i] as VariableInfo @@ -649,10 +668,31 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi } const argTypes: Array = [] + + const methodInfo = symbolInfos[symbolInfos.length - 1] as MethodInfos; + if (!methodInfo || methodInfo.length === 0) { + throw new Error(`No method information found for ${n.identifier}`); + } + + const fullDescriptor = methodInfo[0].typeDescriptor; // Full descriptor, e.g., "(Ljava/lang/String;C)V" + const paramDescriptor = fullDescriptor.slice(1, fullDescriptor.indexOf(')')); // Extract "Ljava/lang/String;C" + const params = paramDescriptor.match(/(\[+[BCDFIJSZ])|(\[+L[^;]+;)|[BCDFIJSZ]|L[^;]+;/g); + + // Parse individual parameter types + if (params && params.length !== n.argumentList.length) { + throw new Error( + `Parameter mismatch: expected ${params?.length || 0}, got ${n.argumentList.length}` + ); + } + n.argumentList.forEach((x, i) => { - const argCompileResult = compile(x, cg) - maxStack = Math.max(maxStack, i + 1 + argCompileResult.stackSize) - argTypes.push(argCompileResult.resultType) + const argCompileResult = compile(x, cg); + maxStack = Math.max(maxStack, i + 1 + argCompileResult.stackSize); + + const expectedType = params?.[i]; // Expected parameter type + handleImplicitTypeConversion(argCompileResult.resultType, expectedType ?? '', cg); + + argTypes.push(argCompileResult.resultType); }) const argDescriptor = '(' + argTypes.join('') + ')' @@ -687,7 +727,9 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi } if (!foundMethod) { - throw new InvalidMethodCallError(n.identifier) + throw new InvalidMethodCallError( + `No method matching signature ${n.identifier}${argDescriptor} found.` + ); } return { stackSize: maxStack, resultType: resultType } }, From a33c9aff758bd423b6a7e06177bbaa69be93df9e Mon Sep 17 00:00:00 2001 From: Aprup Kale Date: Tue, 14 Jan 2025 18:28:12 +0800 Subject: [PATCH 03/11] Add implicit type conversions in MethodInvocation to code-generator.ts --- src/compiler/code-generator.ts | 73 +++++++++++++++++----------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts index 601aa958..3d9e74b3 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -189,7 +189,7 @@ const typeConversionsImplicit: { [key: string]: OPCODE } = { 'F->D': OPCODE.F2D, 'L->F': OPCODE.L2F, 'L->D': OPCODE.L2D -}; +} type CompileResult = { stackSize: number @@ -198,29 +198,29 @@ type CompileResult = { const EMPTY_TYPE: string = '' function areClassTypesCompatible(fromType: string, toType: string): boolean { - const cleanFrom = fromType.replace(/^L|;$/g, ''); - const cleanTo = toType.replace(/^L|;$/g, ''); - return cleanFrom === cleanTo; + const cleanFrom = fromType.replace(/^L|;$/g, '') + const cleanTo = toType.replace(/^L|;$/g, '') + return cleanFrom === cleanTo } function handleImplicitTypeConversion(fromType: string, toType: string, cg: CodeGenerator) { - console.debug(`Converting from: ${fromType}, to: ${toType}`); + console.debug(`Converting from: ${fromType}, to: ${toType}`) if (fromType === toType || toType.replace(/^L|;$/g, '') === 'java/lang/String') { - return; + return } if (fromType.startsWith('L') || toType.startsWith('L')) { - if (areClassTypesCompatible(fromType, toType) || fromType === "") { - return; + if (areClassTypesCompatible(fromType, toType) || fromType === '') { + return } - throw new Error(`Unsupported class type conversion: ${fromType} -> ${toType}`); + throw new Error(`Unsupported class type conversion: ${fromType} -> ${toType}`) } - const conversionKey = `${fromType}->${toType}`; + const conversionKey = `${fromType}->${toType}` if (conversionKey in typeConversionsImplicit) { - cg.code.push(typeConversionsImplicit[conversionKey]); + cg.code.push(typeConversionsImplicit[conversionKey]) } else { - throw new Error(`Unsupported implicit type conversion: ${conversionKey}`); + throw new Error(`Unsupported implicit type conversion: ${conversionKey}`) } } @@ -236,7 +236,6 @@ function handleImplicitTypeConversion(fromType: string, toType: string, cg: Code // } // } - const isNullLiteral = (node: Node) => { return node.kind === 'Literal' && node.literalType.kind === 'NullLiteral' } @@ -312,16 +311,16 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi vi.forEach((val, i) => { cg.code.push(OPCODE.DUP) const size1 = compile(createIntLiteralNode(i), cg).stackSize - const { stackSize: size2, resultType } = compile(val as Expression, cg); - handleImplicitTypeConversion(resultType, arrayElemType, cg); + const { stackSize: size2, resultType } = compile(val as Expression, cg) + handleImplicitTypeConversion(resultType, arrayElemType, cg) cg.code.push(arrayElemType in arrayStoreOp ? arrayStoreOp[arrayElemType] : OPCODE.AASTORE) maxStack = Math.max(maxStack, 2 + size1 + size2) }) cg.code.push(OPCODE.ASTORE, curIdx) } else { - const { stackSize: initializerStackSize, resultType: initializerType } = compile(vi, cg); - handleImplicitTypeConversion(initializerType, variableInfo.typeDescriptor, cg); - maxStack = Math.max(maxStack, initializerStackSize); + const { stackSize: initializerStackSize, resultType: initializerType } = compile(vi, cg) + handleImplicitTypeConversion(initializerType, variableInfo.typeDescriptor, cg) + maxStack = Math.max(maxStack, initializerStackSize) cg.code.push( variableInfo.typeDescriptor in normalStoreOp ? normalStoreOp[variableInfo.typeDescriptor] @@ -643,7 +642,7 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi const symbolInfos = cg.symbolTable.queryMethod(n.identifier) if (!symbolInfos || symbolInfos.length === 0) { - throw new Error(`Method not found: ${n.identifier}`); + throw new Error(`Method not found: ${n.identifier}`) } for (let i = 0; i < symbolInfos.length - 1; i++) { @@ -669,30 +668,30 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi const argTypes: Array = [] - const methodInfo = symbolInfos[symbolInfos.length - 1] as MethodInfos; + const methodInfo = symbolInfos[symbolInfos.length - 1] as MethodInfos if (!methodInfo || methodInfo.length === 0) { - throw new Error(`No method information found for ${n.identifier}`); + throw new Error(`No method information found for ${n.identifier}`) } - const fullDescriptor = methodInfo[0].typeDescriptor; // Full descriptor, e.g., "(Ljava/lang/String;C)V" - const paramDescriptor = fullDescriptor.slice(1, fullDescriptor.indexOf(')')); // Extract "Ljava/lang/String;C" - const params = paramDescriptor.match(/(\[+[BCDFIJSZ])|(\[+L[^;]+;)|[BCDFIJSZ]|L[^;]+;/g); + const fullDescriptor = methodInfo[0].typeDescriptor // Full descriptor, e.g., "(Ljava/lang/String;C)V" + const paramDescriptor = fullDescriptor.slice(1, fullDescriptor.indexOf(')')) // Extract "Ljava/lang/String;C" + const params = paramDescriptor.match(/(\[+[BCDFIJSZ])|(\[+L[^;]+;)|[BCDFIJSZ]|L[^;]+;/g) // Parse individual parameter types if (params && params.length !== n.argumentList.length) { throw new Error( `Parameter mismatch: expected ${params?.length || 0}, got ${n.argumentList.length}` - ); + ) } n.argumentList.forEach((x, i) => { - const argCompileResult = compile(x, cg); - maxStack = Math.max(maxStack, i + 1 + argCompileResult.stackSize); + const argCompileResult = compile(x, cg) + maxStack = Math.max(maxStack, i + 1 + argCompileResult.stackSize) - const expectedType = params?.[i]; // Expected parameter type - handleImplicitTypeConversion(argCompileResult.resultType, expectedType ?? '', cg); + const expectedType = params?.[i] // Expected parameter type + handleImplicitTypeConversion(argCompileResult.resultType, expectedType ?? '', cg) - argTypes.push(argCompileResult.resultType); + argTypes.push(argCompileResult.resultType) }) const argDescriptor = '(' + argTypes.join('') + ')' @@ -729,7 +728,7 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi if (!foundMethod) { throw new InvalidMethodCallError( `No method matching signature ${n.identifier}${argDescriptor} found.` - ); + ) } return { stackSize: maxStack, resultType: resultType } }, @@ -759,19 +758,19 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi if (lhs.kind === 'ArrayAccess') { const { stackSize: size1, resultType: arrayType } = compile(lhs.primary, cg) const size2 = compile(lhs.expression, cg).stackSize - const { stackSize: rhsSize, resultType: rhsType } = compile(right, cg); + const { stackSize: rhsSize, resultType: rhsType } = compile(right, cg) maxStack = size1 + size2 + rhsSize const arrayElemType = arrayType.slice(1) - handleImplicitTypeConversion(rhsType, arrayElemType, cg); + handleImplicitTypeConversion(rhsType, arrayElemType, cg) cg.code.push(arrayElemType in arrayStoreOp ? arrayStoreOp[arrayElemType] : OPCODE.AASTORE) } else if ( lhs.kind === 'ExpressionName' && !Array.isArray(cg.symbolTable.queryVariable(lhs.name)) ) { const info = cg.symbolTable.queryVariable(lhs.name) as VariableInfo - const { stackSize: rhsSize, resultType: rhsType } = compile(right, cg); - handleImplicitTypeConversion(rhsType, info.typeDescriptor, cg); + const { stackSize: rhsSize, resultType: rhsType } = compile(right, cg) + handleImplicitTypeConversion(rhsType, info.typeDescriptor, cg) maxStack = 1 + rhsSize cg.code.push( info.typeDescriptor in normalStoreOp ? normalStoreOp[info.typeDescriptor] : OPCODE.ASTORE, @@ -796,8 +795,8 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi maxStack += 1 } - const { stackSize: rhsSize, resultType: rhsType } = compile(right, cg); - handleImplicitTypeConversion(rhsType, fieldInfo.typeDescriptor, cg); + const { stackSize: rhsSize, resultType: rhsType } = compile(right, cg) + handleImplicitTypeConversion(rhsType, fieldInfo.typeDescriptor, cg) maxStack += rhsSize cg.code.push( From 1848b4df56f67140c12f211c457662683bc01368 Mon Sep 17 00:00:00 2001 From: Aprup Kale Date: Tue, 14 Jan 2025 23:00:08 +0800 Subject: [PATCH 04/11] Handle stack size during primitive type conversions in code-generator.ts --- src/compiler/code-generator.ts | 37 ++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts index 3d9e74b3..3d4e5a2c 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -203,15 +203,15 @@ function areClassTypesCompatible(fromType: string, toType: string): boolean { return cleanFrom === cleanTo } -function handleImplicitTypeConversion(fromType: string, toType: string, cg: CodeGenerator) { +function handleImplicitTypeConversion(fromType: string, toType: string, cg: CodeGenerator): number { console.debug(`Converting from: ${fromType}, to: ${toType}`) if (fromType === toType || toType.replace(/^L|;$/g, '') === 'java/lang/String') { - return + return 0; } if (fromType.startsWith('L') || toType.startsWith('L')) { if (areClassTypesCompatible(fromType, toType) || fromType === '') { - return + return 0; } throw new Error(`Unsupported class type conversion: ${fromType} -> ${toType}`) } @@ -219,6 +219,13 @@ function handleImplicitTypeConversion(fromType: string, toType: string, cg: Code const conversionKey = `${fromType}->${toType}` if (conversionKey in typeConversionsImplicit) { cg.code.push(typeConversionsImplicit[conversionKey]) + if (!(fromType in ['L', 'D']) && toType in ['L', 'D']) { + return 1; + } else if (!(toType in ['L', 'D']) && fromType in ['L', 'D']) { + return -1; + } else { + return 0; + } } else { throw new Error(`Unsupported implicit type conversion: ${conversionKey}`) } @@ -312,15 +319,15 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi cg.code.push(OPCODE.DUP) const size1 = compile(createIntLiteralNode(i), cg).stackSize const { stackSize: size2, resultType } = compile(val as Expression, cg) - handleImplicitTypeConversion(resultType, arrayElemType, cg) + const stackSizeChange = handleImplicitTypeConversion(resultType, arrayElemType, cg) cg.code.push(arrayElemType in arrayStoreOp ? arrayStoreOp[arrayElemType] : OPCODE.AASTORE) - maxStack = Math.max(maxStack, 2 + size1 + size2) + maxStack = Math.max(maxStack, 2 + size1 + size2 + stackSizeChange) }) cg.code.push(OPCODE.ASTORE, curIdx) } else { const { stackSize: initializerStackSize, resultType: initializerType } = compile(vi, cg) - handleImplicitTypeConversion(initializerType, variableInfo.typeDescriptor, cg) - maxStack = Math.max(maxStack, initializerStackSize) + const stackSizeChange = handleImplicitTypeConversion(initializerType, variableInfo.typeDescriptor, cg) + maxStack = Math.max(maxStack, initializerStackSize + stackSizeChange) cg.code.push( variableInfo.typeDescriptor in normalStoreOp ? normalStoreOp[variableInfo.typeDescriptor] @@ -686,10 +693,10 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi n.argumentList.forEach((x, i) => { const argCompileResult = compile(x, cg) - maxStack = Math.max(maxStack, i + 1 + argCompileResult.stackSize) const expectedType = params?.[i] // Expected parameter type - handleImplicitTypeConversion(argCompileResult.resultType, expectedType ?? '', cg) + const stackSizeChange = handleImplicitTypeConversion(argCompileResult.resultType, expectedType ?? '', cg) + maxStack = Math.max(maxStack, i + 1 + argCompileResult.stackSize + stackSizeChange) argTypes.push(argCompileResult.resultType) }) @@ -759,10 +766,10 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi const { stackSize: size1, resultType: arrayType } = compile(lhs.primary, cg) const size2 = compile(lhs.expression, cg).stackSize const { stackSize: rhsSize, resultType: rhsType } = compile(right, cg) - maxStack = size1 + size2 + rhsSize const arrayElemType = arrayType.slice(1) - handleImplicitTypeConversion(rhsType, arrayElemType, cg) + const stackSizeChange = handleImplicitTypeConversion(rhsType, arrayElemType, cg) + maxStack = Math.max(maxStack, size1 + size2 + rhsSize + stackSizeChange) cg.code.push(arrayElemType in arrayStoreOp ? arrayStoreOp[arrayElemType] : OPCODE.AASTORE) } else if ( lhs.kind === 'ExpressionName' && @@ -770,8 +777,8 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi ) { const info = cg.symbolTable.queryVariable(lhs.name) as VariableInfo const { stackSize: rhsSize, resultType: rhsType } = compile(right, cg) - handleImplicitTypeConversion(rhsType, info.typeDescriptor, cg) - maxStack = 1 + rhsSize + const stackSizeChange = handleImplicitTypeConversion(rhsType, info.typeDescriptor, cg) + maxStack = Math.max(maxStack, 1 + rhsSize + stackSizeChange) cg.code.push( info.typeDescriptor in normalStoreOp ? normalStoreOp[info.typeDescriptor] : OPCODE.ASTORE, info.index @@ -796,9 +803,9 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi } const { stackSize: rhsSize, resultType: rhsType } = compile(right, cg) - handleImplicitTypeConversion(rhsType, fieldInfo.typeDescriptor, cg) + const stackSizeChange = handleImplicitTypeConversion(rhsType, fieldInfo.typeDescriptor, cg) - maxStack += rhsSize + maxStack = Math.max(maxStack, maxStack + rhsSize + stackSizeChange) cg.code.push( fieldInfo.accessFlags & FIELD_FLAGS.ACC_STATIC ? OPCODE.PUTSTATIC : OPCODE.PUTFIELD, 0, From f88069edf5844a90c6b45d264757ff2620f0a6b6 Mon Sep 17 00:00:00 2001 From: Aprup Kale Date: Tue, 21 Jan 2025 11:10:24 +0800 Subject: [PATCH 05/11] Handle type conversions in binary expressions in code-generator.ts --- src/compiler/__tests__/tests/println.test.ts | 16 +++ src/compiler/code-generator.ts | 126 ++++++++++++++++--- src/jvm/utils/index.ts | 2 +- 3 files changed, 126 insertions(+), 18 deletions(-) diff --git a/src/compiler/__tests__/tests/println.test.ts b/src/compiler/__tests__/tests/println.test.ts index 4e2e4635..9b5ebbea 100644 --- a/src/compiler/__tests__/tests/println.test.ts +++ b/src/compiler/__tests__/tests/println.test.ts @@ -98,6 +98,22 @@ const testCases: testCase[] = [ `, expectedLines: ["true", "false"], }, + { + comment: "println with concatenated arguments", + program: ` + public class Main { + public static void main(String[] args) { + System.out.println("Hello" + " " + "world!"); + System.out.println("This is an int: " + 123); + System.out.println("This is a float: " + 4.5f); + System.out.println("This is a long: " + 10000000000L); + System.out.println("This is a double: " + 10.3); + } + } + `, + expectedLines: ["Hello world!", "This is an int: 123", "This is a float: 4.5", + "This is a long: 10000000000", "This is a double: 10.3"], + }, { comment: "multiple println statements", program: ` diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts index 3d4e5a2c..36878210 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -243,6 +243,36 @@ function handleImplicitTypeConversion(fromType: string, toType: string, cg: Code // } // } +function generateStringConversion(valueType: string, cg: CodeGenerator): void { + const stringClass = 'java/lang/String'; + + // Map primitive types to `String.valueOf()` method descriptors + const valueOfDescriptors: { [key: string]: string } = { + I: '(I)Ljava/lang/String;', // int + J: '(J)Ljava/lang/String;', // long + F: '(F)Ljava/lang/String;', // float + D: '(D)Ljava/lang/String;', // double + Z: '(Z)Ljava/lang/String;', // boolean + B: '(B)Ljava/lang/String;', // byte + S: '(S)Ljava/lang/String;', // short + C: '(C)Ljava/lang/String;' // char + }; + + const descriptor = valueOfDescriptors[valueType]; + if (!descriptor) { + throw new Error(`Unsupported primitive type for String conversion: ${valueType}`); + } + + const methodIndex = cg.constantPoolManager.indexMethodrefInfo( + stringClass, + 'valueOf', + descriptor + ); + + cg.code.push(OPCODE.INVOKESTATIC, 0, methodIndex); +} + + const isNullLiteral = (node: Node) => { return node.kind === 'Literal' && node.literalType.kind === 'NullLiteral' } @@ -849,33 +879,95 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi } } - const { stackSize: size1, resultType: type } = compile(left, cg) - const { stackSize: size2 } = compile(right, cg) + const { stackSize: size1, resultType: leftType } = compile(left, cg) + const { stackSize: size2, resultType: rightType } = compile(right, cg) + + if (op === '+' && + (leftType === 'Ljava/lang/String;' + || rightType === 'Ljava/lang/String;')) { + console.debug(`String concatenation detected: ${leftType} ${op} ${rightType}`) + + if (leftType !== 'Ljava/lang/String;') { + generateStringConversion(leftType, cg); + } + + if (rightType !== 'Ljava/lang/String;') { + generateStringConversion(rightType, cg); + } + + // Invoke `String.concat` for concatenation + const concatMethodIndex = cg.constantPoolManager.indexMethodrefInfo( + 'java/lang/String', + 'concat', + '(Ljava/lang/String;)Ljava/lang/String;' + ); + cg.code.push(OPCODE.INVOKEVIRTUAL, 0, concatMethodIndex); + + return { + stackSize: Math.max(size1, size2 + 1), // Max stack size plus one for the concatenation + resultType: 'Ljava/lang/String;' + }; + } - switch (type) { + if (leftType !== rightType) { + console.debug( + `Type mismatch detected: leftType=${leftType}, rightType=${rightType}. Applying implicit conversions.` + ); + + if (['D', 'F'].includes(leftType) || ['D', 'F'].includes(rightType)) { + // Promote both to double if one is double, or to float otherwise + if (leftType !== 'D' && rightType === 'D') { + handleImplicitTypeConversion(leftType, 'D', cg); + } else if (leftType === 'D' && rightType !== 'D') { + handleImplicitTypeConversion(rightType, 'D', cg); + } else if (leftType !== 'F' && rightType === 'F') { + handleImplicitTypeConversion(leftType, 'F', cg); + } else if (leftType === 'F' && rightType !== 'F') { + handleImplicitTypeConversion(rightType, 'F', cg); + } + } else if (['J'].includes(leftType) || ['J'].includes(rightType)) { + // Promote both to long if one is long + if (leftType !== 'J' && rightType === 'J') { + handleImplicitTypeConversion(leftType, 'J', cg); + } else if (leftType === 'J' && rightType !== 'J') { + handleImplicitTypeConversion(rightType, 'J', cg); + } + } else { + // Promote both to int as the common type for smaller types like byte, short, char + if (leftType !== 'I') { + handleImplicitTypeConversion(leftType, 'I', cg); + } + if (rightType !== 'I') { + handleImplicitTypeConversion(rightType, 'I', cg); + } + } + } + + // Perform the operation + switch (leftType) { case 'B': - cg.code.push(intBinaryOp[op], OPCODE.I2B) - break + cg.code.push(intBinaryOp[op], OPCODE.I2B); + break; case 'D': - cg.code.push(doubleBinaryOp[op]) - break + cg.code.push(doubleBinaryOp[op]); + break; case 'F': - cg.code.push(floatBinaryOp[op]) - break + cg.code.push(floatBinaryOp[op]); + break; case 'I': - cg.code.push(intBinaryOp[op]) - break + cg.code.push(intBinaryOp[op]); + break; case 'J': - cg.code.push(longBinaryOp[op]) - break + cg.code.push(longBinaryOp[op]); + break; case 'S': - cg.code.push(intBinaryOp[op], OPCODE.I2S) - break + cg.code.push(intBinaryOp[op], OPCODE.I2S); + break; } return { - stackSize: Math.max(size1, 1 + (['D', 'J'].includes(type) ? 1 : 0) + size2), - resultType: type + stackSize: Math.max(size1, 1 + (['D', 'J'].includes(leftType) ? 1 : 0) + size2), + resultType: leftType } }, diff --git a/src/jvm/utils/index.ts b/src/jvm/utils/index.ts index ea115a78..112e2406 100644 --- a/src/jvm/utils/index.ts +++ b/src/jvm/utils/index.ts @@ -213,7 +213,7 @@ export function getField(ref: any, fieldName: string, type: JavaType) { } export function asDouble(value: number): number { - return value + return Number(value) } export function asFloat(value: number): number { From db877207f67641405dcb0ae445074d9ef035c471 Mon Sep 17 00:00:00 2001 From: Aprup Kale Date: Tue, 21 Jan 2025 11:27:26 +0800 Subject: [PATCH 06/11] Fix bugs with float conversions --- src/compiler/code-generator.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts index 36878210..16b45704 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -177,9 +177,9 @@ const normalStoreOp: { [type: string]: OPCODE } = { // 'D->F': OPCODE.D2F, // 'D->I': OPCODE.D2I, // 'D->L': OPCODE.D2L, -// 'L->I': OPCODE.L2I, -// 'L->F': OPCODE.L2F, -// 'L->D': OPCODE.L2D +// 'J->I': OPCODE.L2I, +// 'J->F': OPCODE.L2F, +// 'J->D': OPCODE.L2D // }; const typeConversionsImplicit: { [key: string]: OPCODE } = { @@ -187,8 +187,8 @@ const typeConversionsImplicit: { [key: string]: OPCODE } = { 'I->D': OPCODE.I2D, 'I->L': OPCODE.I2L, 'F->D': OPCODE.F2D, - 'L->F': OPCODE.L2F, - 'L->D': OPCODE.L2D + 'J->F': OPCODE.L2F, + 'J->D': OPCODE.L2D } type CompileResult = { @@ -219,9 +219,9 @@ function handleImplicitTypeConversion(fromType: string, toType: string, cg: Code const conversionKey = `${fromType}->${toType}` if (conversionKey in typeConversionsImplicit) { cg.code.push(typeConversionsImplicit[conversionKey]) - if (!(fromType in ['L', 'D']) && toType in ['L', 'D']) { + if (!(fromType in ['J', 'D']) && toType in ['J', 'D']) { return 1; - } else if (!(toType in ['L', 'D']) && fromType in ['L', 'D']) { + } else if (!(toType in ['J', 'D']) && fromType in ['J', 'D']) { return -1; } else { return 0; From 6e0a2d7c8e895f399d820600d4764c81ef8ca8aa Mon Sep 17 00:00:00 2001 From: Aprup Kale Date: Tue, 21 Jan 2025 13:11:52 +0800 Subject: [PATCH 07/11] Allow primitive types to be safely converted to String --- src/compiler/code-generator.ts | 10 +++++----- src/jvm/utils/index.ts | 2 +- src/types/types/references.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts index 16b45704..f91cc424 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -167,16 +167,16 @@ const normalStoreOp: { [type: string]: OPCODE } = { // const typeConversions: { [key: string]: OPCODE } = { // 'I->F': OPCODE.I2F, // 'I->D': OPCODE.I2D, -// 'I->L': OPCODE.I2L, +// 'I->J': OPCODE.I2L, // 'I->B': OPCODE.I2B, // 'I->C': OPCODE.I2C, // 'I->S': OPCODE.I2S, // 'F->D': OPCODE.F2D, // 'F->I': OPCODE.F2I, -// 'F->L': OPCODE.F2L, +// 'F->J': OPCODE.F2L, // 'D->F': OPCODE.D2F, // 'D->I': OPCODE.D2I, -// 'D->L': OPCODE.D2L, +// 'D->J': OPCODE.D2L, // 'J->I': OPCODE.L2I, // 'J->F': OPCODE.L2F, // 'J->D': OPCODE.L2D @@ -185,7 +185,7 @@ const normalStoreOp: { [type: string]: OPCODE } = { const typeConversionsImplicit: { [key: string]: OPCODE } = { 'I->F': OPCODE.I2F, 'I->D': OPCODE.I2D, - 'I->L': OPCODE.I2L, + 'I->J': OPCODE.I2L, 'F->D': OPCODE.F2D, 'J->F': OPCODE.L2F, 'J->D': OPCODE.L2D @@ -904,7 +904,7 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi cg.code.push(OPCODE.INVOKEVIRTUAL, 0, concatMethodIndex); return { - stackSize: Math.max(size1, size2 + 1), // Max stack size plus one for the concatenation + stackSize: Math.max(size1 + 1, size2 + 1), // Max stack size plus one for the concatenation resultType: 'Ljava/lang/String;' }; } diff --git a/src/jvm/utils/index.ts b/src/jvm/utils/index.ts index 112e2406..091dbc44 100644 --- a/src/jvm/utils/index.ts +++ b/src/jvm/utils/index.ts @@ -213,7 +213,7 @@ export function getField(ref: any, fieldName: string, type: JavaType) { } export function asDouble(value: number): number { - return Number(value) + return value; } export function asFloat(value: number): number { diff --git a/src/types/types/references.ts b/src/types/types/references.ts index 69c75e6d..edd87ad9 100644 --- a/src/types/types/references.ts +++ b/src/types/types/references.ts @@ -106,7 +106,7 @@ export class String extends ClassType { } public canBeAssigned(type: Type): boolean { - if (type instanceof Primitives.Null) return true + if (type instanceof PrimitiveType) return true return type instanceof String } } From 7e58159a15987f11c614e544dd7e3611dfc36d97 Mon Sep 17 00:00:00 2001 From: Aprup Kale Date: Tue, 21 Jan 2025 13:54:31 +0800 Subject: [PATCH 08/11] Allow primitive types other than boolean in ternary conditional --- src/compiler/code-generator.ts | 60 ++++++++++++++++++++++++++++++++++ src/types/types/primitives.ts | 2 +- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts index f91cc424..a6256650 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -272,6 +272,61 @@ function generateStringConversion(valueType: string, cg: CodeGenerator): void { cg.code.push(OPCODE.INVOKESTATIC, 0, methodIndex); } +// function generateBooleanConversion(type: string, cg: CodeGenerator): number { +// let stackChange = 0; // Tracks changes to the stack size +// +// switch (type) { +// case 'I': // int +// case 'B': // byte +// case 'S': // short +// case 'C': // char +// // For integer-like types, compare with zero +// cg.code.push(OPCODE.ICONST_0); // Push 0 +// stackChange += 1; // `ICONST_0` pushes a value onto the stack +// cg.code.push(OPCODE.IF_ICMPNE); // Compare and branch +// stackChange -= 2; // `IF_ICMPNE` consumes two values from the stack +// break; +// +// case 'J': // long +// // For long, compare with zero +// cg.code.push(OPCODE.LCONST_0); // Push 0L +// stackChange += 2; // `LCONST_0` pushes two values onto the stack (long takes 2 slots) +// cg.code.push(OPCODE.LCMP); // Compare top two longs +// stackChange -= 4; // `LCMP` consumes four values (two long operands) and pushes one result +// cg.code.push(OPCODE.IFNE); // If not equal, branch +// stackChange -= 1; // `IFNE` consumes one value (the comparison result) +// break; +// +// case 'F': // float +// // For float, compare with zero +// cg.code.push(OPCODE.FCONST_0); // Push 0.0f +// stackChange += 1; // `FCONST_0` pushes a value onto the stack +// cg.code.push(OPCODE.FCMPL); // Compare top two floats +// stackChange -= 2; // `FCMPL` consumes two values (float operands) and pushes one result +// cg.code.push(OPCODE.IFNE); // If not equal, branch +// stackChange -= 1; // `IFNE` consumes one value (the comparison result) +// break; +// +// case 'D': // double +// // For double, compare with zero +// cg.code.push(OPCODE.DCONST_0); // Push 0.0d +// stackChange += 2; // `DCONST_0` pushes two values onto the stack (double takes 2 slots) +// cg.code.push(OPCODE.DCMPL); // Compare top two doubles +// stackChange -= 4; // `DCMPL` consumes four values (two double operands) and pushes one result +// cg.code.push(OPCODE.IFNE); // If not equal, branch +// stackChange -= 1; // `IFNE` consumes one value (the comparison result) +// break; +// +// case 'Z': // boolean +// // Already a boolean, no conversion needed +// break; +// +// default: +// throw new Error(`Cannot convert type ${type} to boolean.`); +// } +// +// return stackChange; // Return the net change in stack size +// } const isNullLiteral = (node: Node) => { return node.kind === 'Literal' && node.literalType.kind === 'NullLiteral' @@ -535,6 +590,11 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi cg.addBranchInstr(OPCODE.GOTO, targetLabel) } return { stackSize: 0, resultType: cg.symbolTable.generateFieldDescriptor('boolean') } + } else { + if (onTrue === (parseInt(value) !== 0)) { + cg.addBranchInstr(OPCODE.GOTO, targetLabel) + } + return { stackSize: 0, resultType: cg.symbolTable.generateFieldDescriptor('boolean') } } } diff --git a/src/types/types/primitives.ts b/src/types/types/primitives.ts index 1c9c0f6d..4832c6f0 100644 --- a/src/types/types/primitives.ts +++ b/src/types/types/primitives.ts @@ -13,7 +13,7 @@ export class Boolean extends PrimitiveType { } public canBeAssigned(type: Type): boolean { - return type instanceof Boolean + return type instanceof PrimitiveType && !(type instanceof Null) } } From 6bbafe799f6a5c4b59d3edb661db52044f52428e Mon Sep 17 00:00:00 2001 From: Aprup Kale Date: Tue, 21 Jan 2025 14:35:06 +0800 Subject: [PATCH 09/11] Fix bugs in type checker --- src/types/types/methods.ts | 1 + src/types/types/primitives.ts | 2 +- src/types/types/references.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/types/types/methods.ts b/src/types/types/methods.ts index 3a4adb6e..7c8c9365 100644 --- a/src/types/types/methods.ts +++ b/src/types/types/methods.ts @@ -189,6 +189,7 @@ export class Method implements Type { } public invoke(args: Arguments): Type | TypeCheckerError { + if (this.methodName === 'println') return new Void() const error = this.parameters.invoke(args) if (error instanceof TypeCheckerError) return error return this.returnType diff --git a/src/types/types/primitives.ts b/src/types/types/primitives.ts index 4832c6f0..1c9c0f6d 100644 --- a/src/types/types/primitives.ts +++ b/src/types/types/primitives.ts @@ -13,7 +13,7 @@ export class Boolean extends PrimitiveType { } public canBeAssigned(type: Type): boolean { - return type instanceof PrimitiveType && !(type instanceof Null) + return type instanceof Boolean } } diff --git a/src/types/types/references.ts b/src/types/types/references.ts index edd87ad9..69c75e6d 100644 --- a/src/types/types/references.ts +++ b/src/types/types/references.ts @@ -106,7 +106,7 @@ export class String extends ClassType { } public canBeAssigned(type: Type): boolean { - if (type instanceof PrimitiveType) return true + if (type instanceof Primitives.Null) return true return type instanceof String } } From 57eeb8bcbb757765aadfbbadd7873a91b08410a2 Mon Sep 17 00:00:00 2001 From: Aprup Kale Date: Tue, 21 Jan 2025 15:44:36 +0800 Subject: [PATCH 10/11] Enable unary expressions for non-integer types --- .../tests/arithmeticExpression.test.ts | 52 ++++++++++++++ .../tests/assignmentExpression.test.ts | 69 +++++++++++++++++++ .../__tests__/tests/unaryExpression.test.ts | 39 +++++++++++ src/compiler/code-generator.ts | 13 +++- 4 files changed, 172 insertions(+), 1 deletion(-) diff --git a/src/compiler/__tests__/tests/arithmeticExpression.test.ts b/src/compiler/__tests__/tests/arithmeticExpression.test.ts index abe02048..72a38c1d 100644 --- a/src/compiler/__tests__/tests/arithmeticExpression.test.ts +++ b/src/compiler/__tests__/tests/arithmeticExpression.test.ts @@ -78,6 +78,58 @@ const testCases: testCase[] = [ expectedLines: ["-2147483648", "-32769", "-32768", "-129", "-128", "-1", "0", "1", "127", "128", "32767", "32768", "2147483647"], }, + { + comment: "Mixed int and float addition (order swapped)", + program: ` + public class Main { + public static void main(String[] args) { + int a = 5; + float b = 2.5f; + System.out.println(a + b); + } + } + `, + expectedLines: ["7.5"], + }, + { + comment: "Mixed long and double multiplication", + program: ` + public class Main { + public static void main(String[] args) { + double a = 3.5; + long b = 10L; + System.out.println(a * b); + } + } + `, + expectedLines: ["35.0"], + }, + { + comment: "Mixed long and double multiplication (order swapped)", + program: ` + public class Main { + public static void main(String[] args) { + long a = 10L; + double b = 3.5; + System.out.println(a * b); + } + } + `, + expectedLines: ["35.0"], + }, + { + comment: "Mixed int and double division", + program: ` + public class Main { + public static void main(String[] args) { + double a = 2.0; + int b = 5; + System.out.println(a / b); + } + } + `, + expectedLines: ["0.4"], + } ]; export const arithmeticExpressionTest = () => describe("arithmetic expression", () => { diff --git a/src/compiler/__tests__/tests/assignmentExpression.test.ts b/src/compiler/__tests__/tests/assignmentExpression.test.ts index 688a5d17..5950de37 100644 --- a/src/compiler/__tests__/tests/assignmentExpression.test.ts +++ b/src/compiler/__tests__/tests/assignmentExpression.test.ts @@ -45,6 +45,75 @@ const testCases: testCase[] = [ `, expectedLines: ["6.0"], }, + { + comment: "int to long", + program: ` + public class Main { + public static void main(String[] args) { + int a = 123; + long b = a; + System.out.println(b); + } + } + `, + expectedLines: ["123"], + }, + { + comment: "int to float", + program: ` + public class Main { + public static void main(String[] args) { + int a = 123; + float b = a; + System.out.println(b); + } + } + `, + expectedLines: ["123.0"], + }, + + // long -> other types + { + comment: "long to float", + program: ` + public class Main { + public static void main(String[] args) { + long a = 9223372036854775807L; + float b = a; + System.out.println(b); + } + } + `, + expectedLines: ["9.223372E18"], + }, + { + comment: "long to double", + program: ` + public class Main { + public static void main(String[] args) { + long a = 9223372036854775807L; + double b = a; + System.out.println(b); + } + } + `, + expectedLines: ["9.223372036854776E18"], + }, + + // float -> other types + { + comment: "float to double", + program: ` + public class Main { + public static void main(String[] args) { + float a = 3.0f; + double b = a; + System.out.println(b); + } + } + `, + expectedLines: ["3.0"], + }, ]; export const assignmentExpressionTest = () => describe("assignment expression", () => { diff --git a/src/compiler/__tests__/tests/unaryExpression.test.ts b/src/compiler/__tests__/tests/unaryExpression.test.ts index 175c6a2d..0e9aa469 100644 --- a/src/compiler/__tests__/tests/unaryExpression.test.ts +++ b/src/compiler/__tests__/tests/unaryExpression.test.ts @@ -159,6 +159,45 @@ const testCases: testCase[] = [ }`, expectedLines: ["10", "10", "-10", "-10", "-10", "-10", "10", "9", "-10"], }, + { + comment: "unary plus/minus for long", + program: ` + public class Main { + public static void main(String[] args) { + long a = 9223372036854775807L; + System.out.println(+a); + System.out.println(-a); + } + } + `, + expectedLines: ["9223372036854775807", "-9223372036854775807"], + }, + { + comment: "unary plus/minus for float", + program: ` + public class Main { + public static void main(String[] args) { + float a = 4.5f; + System.out.println(+a); + System.out.println(-a); + } + } + `, + expectedLines: ["4.5", "-4.5"], + }, + { + comment: "unary plus/minus for double", + program: ` + public class Main { + public static void main(String[] args) { + double a = 10.75; + System.out.println(+a); + System.out.println(-a); + } + } + `, + expectedLines: ["10.75", "-10.75"], + }, { comment: "bitwise complement", program: ` diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts index a6256650..715d89f6 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -1063,7 +1063,18 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi const compileResult = compile(expr, cg) if (op === '-') { - cg.code.push(OPCODE.INEG) + const negationOpcodes: { [type: string]: OPCODE } = { + I: OPCODE.INEG, // Integer negation + J: OPCODE.LNEG, // Long negation + F: OPCODE.FNEG, // Float negation + D: OPCODE.DNEG, // Double negation + }; + + if (compileResult.resultType in negationOpcodes) { + cg.code.push(negationOpcodes[compileResult.resultType]); + } else { + throw new Error(`Unary '-' not supported for type: ${compileResult.resultType}`); + } } else if (op === '~') { cg.code.push(OPCODE.ICONST_M1, OPCODE.IXOR) compileResult.stackSize = Math.max(compileResult.stackSize, 2) From 2a2cc0b829a803c014966bd753d4c77f93777f8c Mon Sep 17 00:00:00 2001 From: Aprup Kale Date: Sat, 25 Jan 2025 14:13:21 +0800 Subject: [PATCH 11/11] Fix bug in type conversion for binary expressions --- src/compiler/code-generator.ts | 40 ++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts index 715d89f6..f574d5c9 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -940,6 +940,8 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi } const { stackSize: size1, resultType: leftType } = compile(left, cg) + const insertConversionIndex = cg.code.length; + cg.code.push(OPCODE.NOP); const { stackSize: size2, resultType: rightType } = compile(right, cg) if (op === '+' && @@ -969,42 +971,58 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi }; } + let finalType = leftType; + if (leftType !== rightType) { console.debug( `Type mismatch detected: leftType=${leftType}, rightType=${rightType}. Applying implicit conversions.` ); + const conversionKeyLeft = `${leftType}->${rightType}` + const conversionKeyRight = `${rightType}->${leftType}` + if (['D', 'F'].includes(leftType) || ['D', 'F'].includes(rightType)) { // Promote both to double if one is double, or to float otherwise if (leftType !== 'D' && rightType === 'D') { - handleImplicitTypeConversion(leftType, 'D', cg); + cg.code.fill(typeConversionsImplicit[conversionKeyLeft], + insertConversionIndex, insertConversionIndex + 1) + finalType = 'D'; } else if (leftType === 'D' && rightType !== 'D') { - handleImplicitTypeConversion(rightType, 'D', cg); + cg.code.push(typeConversionsImplicit[conversionKeyRight]) + finalType = 'D'; } else if (leftType !== 'F' && rightType === 'F') { - handleImplicitTypeConversion(leftType, 'F', cg); + // handleImplicitTypeConversion(leftType, 'F', cg); + cg.code.fill(typeConversionsImplicit[conversionKeyLeft], + insertConversionIndex, insertConversionIndex + 1) + finalType = 'F'; } else if (leftType === 'F' && rightType !== 'F') { - handleImplicitTypeConversion(rightType, 'F', cg); + cg.code.push(typeConversionsImplicit[conversionKeyRight]) + finalType = 'F'; } } else if (['J'].includes(leftType) || ['J'].includes(rightType)) { // Promote both to long if one is long if (leftType !== 'J' && rightType === 'J') { - handleImplicitTypeConversion(leftType, 'J', cg); + cg.code.fill(typeConversionsImplicit[conversionKeyLeft], + insertConversionIndex, insertConversionIndex + 1) } else if (leftType === 'J' && rightType !== 'J') { - handleImplicitTypeConversion(rightType, 'J', cg); + cg.code.push(typeConversionsImplicit[conversionKeyRight]) } + finalType = 'J'; } else { // Promote both to int as the common type for smaller types like byte, short, char if (leftType !== 'I') { - handleImplicitTypeConversion(leftType, 'I', cg); + cg.code.fill(typeConversionsImplicit[conversionKeyLeft], + insertConversionIndex, insertConversionIndex + 1) } if (rightType !== 'I') { - handleImplicitTypeConversion(rightType, 'I', cg); + cg.code.push(typeConversionsImplicit[conversionKeyRight]) } + finalType = 'I'; } } // Perform the operation - switch (leftType) { + switch (finalType) { case 'B': cg.code.push(intBinaryOp[op], OPCODE.I2B); break; @@ -1026,8 +1044,8 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi } return { - stackSize: Math.max(size1, 1 + (['D', 'J'].includes(leftType) ? 1 : 0) + size2), - resultType: leftType + stackSize: Math.max(size1, 1 + (['D', 'J'].includes(finalType) ? 1 : 0) + size2), + resultType: finalType } },