diff --git a/src/compiler/__tests__/index.ts b/src/compiler/__tests__/index.ts index 8f31ef5..b5b6e74 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/arithmeticExpression.test.ts b/src/compiler/__tests__/tests/arithmeticExpression.test.ts index abe0204..72a38c1 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 new file mode 100644 index 0000000..5950de3 --- /dev/null +++ b/src/compiler/__tests__/tests/assignmentExpression.test.ts @@ -0,0 +1,124 @@ +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"], + }, + { + 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", () => { + for (let testCase of testCases) { + const { comment: comment, program: program, expectedLines: expectedLines } = testCase; + it(comment, () => runTest(program, expectedLines)); + } +}); diff --git a/src/compiler/__tests__/tests/println.test.ts b/src/compiler/__tests__/tests/println.test.ts index 4e2e463..9b5ebbe 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/__tests__/tests/unaryExpression.test.ts b/src/compiler/__tests__/tests/unaryExpression.test.ts index 175c6a2..0e9aa46 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 1b0d8c8..f574d5c 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -164,12 +164,170 @@ const normalStoreOp: { [type: string]: OPCODE } = { Z: OPCODE.ISTORE } +// const typeConversions: { [key: string]: OPCODE } = { +// 'I->F': OPCODE.I2F, +// 'I->D': OPCODE.I2D, +// '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->J': OPCODE.F2L, +// 'D->F': OPCODE.D2F, +// 'D->I': OPCODE.D2I, +// 'D->J': OPCODE.D2L, +// 'J->I': OPCODE.L2I, +// 'J->F': OPCODE.L2F, +// 'J->D': OPCODE.L2D +// }; + +const typeConversionsImplicit: { [key: string]: OPCODE } = { + 'I->F': OPCODE.I2F, + 'I->D': OPCODE.I2D, + 'I->J': OPCODE.I2L, + 'F->D': OPCODE.F2D, + 'J->F': OPCODE.L2F, + 'J->D': OPCODE.L2D +} + type CompileResult = { stackSize: number resultType: string } 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): number { + console.debug(`Converting from: ${fromType}, to: ${toType}`) + if (fromType === toType || toType.replace(/^L|;$/g, '') === 'java/lang/String') { + return 0; + } + + if (fromType.startsWith('L') || toType.startsWith('L')) { + if (areClassTypesCompatible(fromType, toType) || fromType === '') { + return 0; + } + throw new Error(`Unsupported class type conversion: ${fromType} -> ${toType}`) + } + + const conversionKey = `${fromType}->${toType}` + if (conversionKey in typeConversionsImplicit) { + cg.code.push(typeConversionsImplicit[conversionKey]) + if (!(fromType in ['J', 'D']) && toType in ['J', 'D']) { + return 1; + } else if (!(toType in ['J', 'D']) && fromType in ['J', 'D']) { + return -1; + } else { + return 0; + } + } 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}`); +// } +// } + +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); +} + +// 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' } @@ -245,13 +403,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) + 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 { - maxStack = Math.max(maxStack, compile(vi, cg).stackSize) + const { stackSize: initializerStackSize, resultType: initializerType } = compile(vi, cg) + const stackSizeChange = handleImplicitTypeConversion(initializerType, variableInfo.typeDescriptor, cg) + maxStack = Math.max(maxStack, initializerStackSize + stackSizeChange) cg.code.push( variableInfo.typeDescriptor in normalStoreOp ? normalStoreOp[variableInfo.typeDescriptor] @@ -429,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') } } } @@ -572,6 +738,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 @@ -594,9 +764,30 @@ 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) + + const expectedType = params?.[i] // Expected parameter type + const stackSizeChange = handleImplicitTypeConversion(argCompileResult.resultType, expectedType ?? '', cg) + maxStack = Math.max(maxStack, i + 1 + argCompileResult.stackSize + stackSizeChange) + argTypes.push(argCompileResult.resultType) }) const argDescriptor = '(' + argTypes.join('') + ')' @@ -632,7 +823,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 } }, @@ -662,15 +855,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) + const arrayElemType = arrayType.slice(1) + 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' && !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) + 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 @@ -693,7 +891,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) + const stackSizeChange = handleImplicitTypeConversion(rhsType, fieldInfo.typeDescriptor, cg) + + maxStack = Math.max(maxStack, maxStack + rhsSize + stackSizeChange) cg.code.push( fieldInfo.accessFlags & FIELD_FLAGS.ACC_STATIC ? OPCODE.PUTSTATIC : OPCODE.PUTFIELD, 0, @@ -737,33 +939,113 @@ 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 insertConversionIndex = cg.code.length; + cg.code.push(OPCODE.NOP); + 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 + 1, size2 + 1), // Max stack size plus one for the concatenation + resultType: 'Ljava/lang/String;' + }; + } + + 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') { + cg.code.fill(typeConversionsImplicit[conversionKeyLeft], + insertConversionIndex, insertConversionIndex + 1) + finalType = 'D'; + } else if (leftType === 'D' && rightType !== 'D') { + cg.code.push(typeConversionsImplicit[conversionKeyRight]) + finalType = 'D'; + } else if (leftType !== 'F' && rightType === 'F') { + // handleImplicitTypeConversion(leftType, 'F', cg); + cg.code.fill(typeConversionsImplicit[conversionKeyLeft], + insertConversionIndex, insertConversionIndex + 1) + finalType = 'F'; + } else if (leftType === 'F' && rightType !== 'F') { + 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') { + cg.code.fill(typeConversionsImplicit[conversionKeyLeft], + insertConversionIndex, insertConversionIndex + 1) + } else if (leftType === 'J' && rightType !== 'J') { + 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') { + cg.code.fill(typeConversionsImplicit[conversionKeyLeft], + insertConversionIndex, insertConversionIndex + 1) + } + if (rightType !== 'I') { + cg.code.push(typeConversionsImplicit[conversionKeyRight]) + } + finalType = 'I'; + } + } - switch (type) { + // Perform the operation + switch (finalType) { 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(finalType) ? 1 : 0) + size2), + resultType: finalType } }, @@ -799,7 +1081,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) diff --git a/src/jvm/utils/index.ts b/src/jvm/utils/index.ts index ea115a7..091dbc4 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 value; } export function asFloat(value: number): number { diff --git a/src/types/types/methods.ts b/src/types/types/methods.ts index 3a4adb6..7c8c936 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