diff --git a/README.md b/README.md index 4ec8997..2f498ce 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ Open-source Implementation of the Java language in TypeScript. ( { printlnTest(); variableDeclarationTest(); arithmeticExpressionTest(); - unaryExpressionTest(); ifElseTest(); whileTest(); forTest(); + unaryExpressionTest(); methodInvocationTest(); importTest(); arrayTest(); classTest(); + typeConversionTest(); }) \ No newline at end of file diff --git a/src/compiler/__tests__/tests/typeConversion.test.ts b/src/compiler/__tests__/tests/typeConversion.test.ts new file mode 100644 index 0000000..de3ae73 --- /dev/null +++ b/src/compiler/__tests__/tests/typeConversion.test.ts @@ -0,0 +1,27 @@ +import { + runTest, + testCase, +} from "../__utils__/test-utils"; + +const testCases: testCase[] = [ + { + comment: "int to float widening type", + program: ` + public class Main { + public static void main(String[] args) { + float f = 1.0f; + int x = (int) f; + System.out.println(x); + } + } + `, + expectedLines: ["1"], + } +]; + +export const typeConversionTest = () => describe("type conversion", () => { + 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 1b0d8c8..63eb288 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -24,7 +24,8 @@ import { ClassInstanceCreationExpression, ExpressionStatement, TernaryExpression, - LeftHandSide + LeftHandSide, + CastExpression } from '../ast/types/blocks-and-statements' import { MethodDeclaration, UnannType } from '../ast/types/classes' import { ConstantPoolManager } from './constant-pool-manager' @@ -516,6 +517,79 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi return f(node, cg.labels[cg.labels.length - 1], false) }, + CastExpression: (node: Node, cg: CodeGenerator) => { + const { castType: ct, expression: expr, isPrimitiveCast: b } = node as CastExpression + if (b) { + const res = compile(expr, cg) + const { stackSize: size, resultType: rt } = res + switch (ct) { + case 'double': + switch (rt) { + case 'F': + cg.code.push(OPCODE.F2D) + break + case 'J': + cg.code.push(OPCODE.L2D) + break + case 'I': + cg.code.push(OPCODE.I2D) + break + default: + break + } + return { stackSize: Math.max(size, 2), resultType: 'D' } + case 'float': + switch(rt) { + case 'D': + cg.code.push(OPCODE.D2F) + break + case 'J': + cg.code.push(OPCODE.L2F) + break + case 'I': + cg.code.push(OPCODE.I2F) + break + default: + } + return { stackSize: Math.max(size, 1), resultType: 'F' } + case 'long': + switch(rt) { + case 'D': + cg.code.push(OPCODE.D2L) + break + case 'F': + cg.code.push(OPCODE.F2L) + break + case 'I': + cg.code.push(OPCODE.I2L) + break + default: + break + } + return { stackSize: Math.max(size, 2), resultType: 'L' } + case 'int': + switch (rt) { + case 'D': + cg.code.push(OPCODE.D2I) + break + case 'F': + cg.code.push(OPCODE.F2I) + break + case 'J': + cg.code.push(OPCODE.L2I) + break + default: + break + } + return { stackSize: Math.max(size, 1), resultType: 'I' } + } + } + const res = compile(expr, cg); + const classInfoIndex = cg.constantPoolManager.indexClassInfo(ct as string); + cg.code.push(OPCODE.CHECKCAST, 0, classInfoIndex); + return res; + }, + ClassInstanceCreationExpression: (node: Node, cg: CodeGenerator) => { const { identifier: id, argumentList: argLst } = node as ClassInstanceCreationExpression let maxStack = 2 diff --git a/src/compiler/grammar.ts b/src/compiler/grammar.ts index 4b6f1ee..013d564 100755 --- a/src/compiler/grammar.ts +++ b/src/compiler/grammar.ts @@ -1109,8 +1109,22 @@ PostfixExpression } CastExpression - = lparen PrimitiveType rparen UnaryExpression - / lparen ReferenceType rparen (LambdaExpression / !(PlusMinus) UnaryExpression) + = lparen t:PrimitiveType rparen expr:UnaryExpression { + return addLocInfo({ + kind: "CastExpression", + castType: t, + expression: expr, + isPrimitiveCast: true, + }); + } + / lparen t:ReferenceType rparen expr:(LambdaExpression / !(PlusMinus) UnaryExpression) { + return addLocInfo({ + kind: "CastExpression", + castType: t, + expression: expr, + isPrimitiveCast: false, + }); + } SwitchExpression = SwitchStatement diff --git a/src/types/ast/utils.ts b/src/types/ast/utils.ts index 1a703b7..f00fa7f 100644 --- a/src/types/ast/utils.ts +++ b/src/types/ast/utils.ts @@ -1,5 +1,6 @@ import { IToken } from 'java-parser' import { + ArrayType, ClassOrInterfaceType, Dim, Identifier, @@ -67,9 +68,11 @@ export const isIdentifier = (object: Record): boolean => { * @deprecated */ export const unannTypeToString = ( - type: LocalVariableType | ClassOrInterfaceType | Result + type: LocalVariableType | ClassOrInterfaceType | ArrayType | Result ): string => { switch (type.kind) { + case 'ArrayType': + return unannTypeToString(type.type) + '[]'.repeat(type.dims.dims.length) case 'Boolean': return 'boolean' case 'FloatingPointType': diff --git a/src/types/checker/index.ts b/src/types/checker/index.ts index 005286e..43c6ffc 100644 --- a/src/types/checker/index.ts +++ b/src/types/checker/index.ts @@ -192,6 +192,34 @@ export const typeCheckBody = (node: Node, frame: Frame = Frame.globalFrame()): R case 'BreakStatement': { return OK_RESULT } + case 'CastExpression': { + let castTypeNode, expressionNode; + if ('primitiveType' in node) { + castTypeNode = node.primitiveType; + expressionNode = node.unaryExpression; + } else if ('referenceType' in node && 'unaryExpressionNotPlusMinus' in node) { + castTypeNode = node.referenceType; + expressionNode = node.unaryExpressionNotPlusMinus; + } else if ('referenceType' in node && 'lambdaExpression' in node) { + castTypeNode = node.referenceType; + expressionNode = node.lambdaExpression; + } else { + throw new Error('Invalid typecast.'); + } + + const castType = frame.getType(unannTypeToString(castTypeNode), castTypeNode.location); + if (castType instanceof TypeCheckerError) return newResult(null, [castType]); + + const { currentType, errors } = typeCheckBody(expressionNode, frame); + if (errors.length > 0) return newResult(null, errors); + if (!currentType) throw new Error('Target of cast expression should return a type.'); + + if (!castType.canBeAssigned(currentType) && !currentType.canBeAssigned(castType)) { + return newResult(null, [new IncompatibleTypesError(node.location)]); + } + + return newResult(castType); + } case 'ClassInstanceCreationExpression': { const classIdentifier = node.unqualifiedClassInstanceCreationExpression.classOrInterfaceTypeToInstantiate