From e941932d5e75810cc4bdcc13943ba9a2be692001 Mon Sep 17 00:00:00 2001 From: kjw142857 Date: Tue, 11 Aug 2026 06:09:53 +0800 Subject: [PATCH 1/5] tighten selector criteria --- .../__tests__/switchStatements.test.ts | 23 ++++++++++++++++++- src/types/checker/statements.ts | 4 ++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/types/checker/__tests__/switchStatements.test.ts b/src/types/checker/__tests__/switchStatements.test.ts index 8889e5bf..6344ebce 100644 --- a/src/types/checker/__tests__/switchStatements.test.ts +++ b/src/types/checker/__tests__/switchStatements.test.ts @@ -1,6 +1,6 @@ import { check } from '..' import { parse } from '../../ast' -import { IncompatibleTypesError, TypeCheckerError } from '../../errors' +import { IncompatibleTypesError, SelectorTypeNotAllowedError, TypeCheckerError } from '../../errors' import { Type } from '../../types/type' const createProgram = (statement: string) => ` @@ -27,6 +27,27 @@ const testcases: { `, result: { type: null, errors: [] } }, + { + input: ` + String selector = "Tuesday"; + switch(selector) { + case "Tuesday": { + selector = "Wednesday"; + } + default: + } + `, + result: { type: null, errors: [] } + }, + { + input: ` + Boolean selector = true; + switch(selector) { + default: {} + } + `, + result: { type: null, errors: [new SelectorTypeNotAllowedError()] } + }, { input: ` int selector = 1; diff --git a/src/types/checker/statements.ts b/src/types/checker/statements.ts index ef812dad..fe88fcef 100644 --- a/src/types/checker/statements.ts +++ b/src/types/checker/statements.ts @@ -12,7 +12,7 @@ import { isPrimitiveIntegralType, isPrimitiveLongType, isReferenceBooleanType, - isReferenceType + isStringType } from '../types/utils' export const checkDoExpression = ( @@ -28,7 +28,7 @@ export const checkSwitchExpression = ( location: Location ): null | TypeCheckerError => { if (isPrimitiveIntegralType(expressionType) && !isPrimitiveLongType(expressionType)) return null - if (isReferenceType(expressionType)) return null + if (isStringType(expressionType)) return null return new SelectorTypeNotAllowedError(location) } From 89ccf3067db10b398fdd1215b3c15d841f2ff6fd Mon Sep 17 00:00:00 2001 From: kjw142857 Date: Tue, 11 Aug 2026 07:49:09 +0800 Subject: [PATCH 2/5] add enum support --- src/compiler/code-generator.ts | 28 +++- src/compiler/compiler-utils.ts | 3 +- .../__tests__/switchStatements.test.ts | 25 ++++ src/types/checker/environment.ts | 4 +- src/types/checker/index.ts | 108 +++++++++++++-- src/types/checker/prechecks.ts | 125 +++++++++++++++++- src/types/checker/statements.ts | 2 + src/types/types/classes.ts | 2 + 8 files changed, 278 insertions(+), 19 deletions(-) diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts index 773bde90..d4d5d101 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -1,4 +1,5 @@ import { OPCODE } from '../ClassFile/constants/instructions' +import { ACCESS_FLAGS } from '../ClassFile/types' import { ExceptionHandler, AttributeInfo } from '../ClassFile/types/attributes' import { FIELD_FLAGS } from '../ClassFile/types/fields' import { METHOD_FLAGS } from '../ClassFile/types/methods' @@ -1375,6 +1376,27 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi const { stackSize: exprStackSize, resultType } = compile(expression, cg) let maxStack = exprStackSize + // If the expression is an enum type, invoke ordinal() to convert to int and then continue + let _resultType = resultType + if (_resultType && _resultType.startsWith('L') && _resultType !== 'Ljava/lang/String;') { + const clean = _resultType.replace(/^L|;$/g, '') + try { + const classInfo = cg.symbolTable.queryClass(clean) + if (classInfo.accessFlags & ACCESS_FLAGS.ACC_ENUM) { + // call java.lang.Enum.ordinal() (returns int) + cg.code.push( + OPCODE.INVOKEVIRTUAL, + 0, + cg.constantPoolManager.indexMethodrefInfo('java/lang/Enum', 'ordinal', '()I') + ) + _resultType = 'I' + maxStack = Math.max(maxStack, exprStackSize + 1) + } + } catch (e) { + // ignore: not a known class + } + } + const caseLabels: Label[] = cases.map(() => cg.generateNewLabel()) const defaultLabel = cg.generateNewLabel() const endLabel = cg.generateNewLabel() @@ -1382,7 +1404,7 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi // Track the switch statement's end label cg.switchLabels.push(endLabel) - if (['I', 'B', 'S', 'C'].includes(resultType)) { + if (['I', 'B', 'S', 'C'].includes(_resultType)) { const caseValues: number[] = [] const caseLabelMap: Map = new Map() let hasDefault = false @@ -1556,7 +1578,7 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi } endLabel.offset = cg.code.length - } else if (resultType === 'Ljava/lang/String;') { + } else if (_resultType === 'Ljava/lang/String;') { // **String Switch Handling** const hashCaseMap: Map = new Map() @@ -1708,7 +1730,7 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi endLabel.offset = cg.code.length } else { throw new Error( - `Switch statements only support byte, short, int, char, or String types. Found: ${resultType}` + `Switch statements only support byte, short, int, char, String, or enum types. Found: ${_resultType}` ) } diff --git a/src/compiler/compiler-utils.ts b/src/compiler/compiler-utils.ts index 9adcae6d..4bff9609 100644 --- a/src/compiler/compiler-utils.ts +++ b/src/compiler/compiler-utils.ts @@ -6,7 +6,8 @@ import { ClassModifier, FieldModifier, MethodModifier } from '../ast/types/class const classAccessFlagMap = new Map([ ['public', ACCESS_FLAGS.ACC_PUBLIC], ['final', ACCESS_FLAGS.ACC_FINAL], - ['abstract', ACCESS_FLAGS.ACC_ABSTRACT] + ['abstract', ACCESS_FLAGS.ACC_ABSTRACT], + ['enum', ACCESS_FLAGS.ACC_ENUM] ]) export function generateClassAccessFlags(modifiers: Array) { diff --git a/src/types/checker/__tests__/switchStatements.test.ts b/src/types/checker/__tests__/switchStatements.test.ts index 6344ebce..8fb29ca2 100644 --- a/src/types/checker/__tests__/switchStatements.test.ts +++ b/src/types/checker/__tests__/switchStatements.test.ts @@ -73,6 +73,31 @@ const testcases: { } `, result: { type: null, errors: [new IncompatibleTypesError()] } + }, + { + input: ` + enum Color { RED, BLUE } + Color selector = Color.RED; + switch(selector) { + case Color.RED: { + selector = Color.BLUE; + } + default: {} + } + `, + result: { type: null, errors: [] } + }, + { + input: ` + enum Color { RED, BLUE } + enum Other { X } + Color selector = Color.RED; + switch(selector) { + case Other.X: {} + default: {} + } + `, + result: { type: null, errors: [new IncompatibleTypesError()] } } ] diff --git a/src/types/checker/environment.ts b/src/types/checker/environment.ts index 6ef2ad2b..92e12851 100644 --- a/src/types/checker/environment.ts +++ b/src/types/checker/environment.ts @@ -41,7 +41,9 @@ const GLOBAL_TYPE_ENVIRONMENT: { [key: string]: Type } = { // Hard coded variables System: SYSTEM_CLASS, Throwable: new NonPrimitives.Throwable(), - Exception: new NonPrimitives.Exception() + Exception: new NonPrimitives.Exception(), + // enum base type + Enum: new ClassType('Enum') } export class Frame { diff --git a/src/types/checker/index.ts b/src/types/checker/index.ts index 77491719..0f3c39fe 100644 --- a/src/types/checker/index.ts +++ b/src/types/checker/index.ts @@ -69,7 +69,6 @@ const isCastCompatible = (fromType: Type, toType: Type): boolean => { const fromName = fromType.constructor.name; const toName = toType.constructor.name; - console.log(fromName, toName); return !(fromName === 'char' && toName !== 'int'); } @@ -384,7 +383,6 @@ export const typeCheckBody = (node: Node, frame: Frame = Frame.globalFrame()): R return newResult(null, errors) } case 'InstanceofExpression': { - console.log(node) return OK_RESULT } case 'BinaryLiteral': @@ -584,6 +582,88 @@ export const typeCheckBody = (node: Node, frame: Frame = Frame.globalFrame()): R } return newResult(null, errors) } + case 'EnumDeclaration': { + const errors: TypeCheckerError[] = [] + const classType = frame.getType(node.typeIdentifier.identifier, node.typeIdentifier.location) + if (classType instanceof TypeCheckerError) return newResult(null, [classType]) + if (!(classType instanceof ClassType)) throw new Error('enum type retrieved should be ClassImpl') + + const classFrame = frame.newChildFrame() + classFrame.setClass(classType) + classType.mapFields((name, type) => { + const error = classFrame.setVariable(name, type, { startLine: -1, startOffset: -1 }) + if (error) errors.push(error) + }) + if (errors.length > 0) return newResult(null, errors) + + const bodyDecls = node.enumBody.enumBodyDeclarations?.classBodyDeclaration || [] + let numFieldDeclarations = 0 + let numMethodDeclarations = 0 + for (let i = 0; i < bodyDecls.length; i++) { + const bodyDeclaration = bodyDecls[i] + switch (bodyDeclaration.kind) { + case 'ConstructorDeclaration': { + const methodFrame = classFrame.newChildFrame() + const constructor = classType.getConstructor(i - numFieldDeclarations - numMethodDeclarations) + const constructorMethodErrors: TypeCheckerError[] = [] + constructor.mapParameters((name, type, isVarargs) => { + const error = methodFrame.setVariable(name, type, { startLine: -1, startOffset: -1 }) + if (error) constructorMethodErrors.push(error) + }) + if (constructorMethodErrors.length > 0) { + errors.push(...constructorMethodErrors) + break + } + const { errors: checkErrors } = typeCheckBody(bodyDeclaration.constructorBody, methodFrame) + if (checkErrors.length > 0) errors.push(...checkErrors) + break + } + case 'FieldDeclaration': { + for (const variableDeclarator of (bodyDeclaration as any).variableDeclaratorList.variableDeclarators) { + const field = classType.accessField(variableDeclarator.variableDeclaratorId.identifier.identifier, variableDeclarator.variableDeclaratorId.identifier.location) + if (field instanceof TypeCheckerError) throw new Error('field should exist in enum') + const initializer = variableDeclarator.variableInitializer + if (initializer) { + const type = createArrayType(field, initializer, expression => { + const result = typeCheckBody(expression, frame) + if (result.errors.length > 0) return result.errors[0] + if (!result.currentType) throw new Error('array initializer expression should have a type') + return result.currentType + }) + if (type instanceof TypeCheckerError) errors.push(type) + } + } + break + } + case 'MethodDeclaration': { + const methodIdentifier = (bodyDeclaration as any).methodHeader.methodDeclarator.identifier + const methodName = methodIdentifier.identifier + const overloadIndex = bodyDecls + .filter((n: any) => n.kind === 'MethodDeclaration' && (n as any).methodHeader.methodDeclarator.identifier.identifier === methodName) + .findIndex(n => n === bodyDeclaration) + const method = classType.getMethod(methodName)[overloadIndex] + const methodFrame = classFrame.newChildFrame() + const methodErrors: TypeCheckerError[] = [] + methodFrame.setReturnType(method.getReturnType()) + method.mapParameters((name, type, isVarargs) => { + const error = methodFrame.setVariable(name, type, { startLine: -1, startOffset: -1 }) + if (error) methodErrors.push(error) + }) + if (methodErrors.length > 0) { + errors.push(...methodErrors) + break + } + const { errors: checkErrors } = typeCheckBody((bodyDeclaration as any).methodBody, methodFrame) + if (checkErrors.length > 0) errors.push(...checkErrors) + break + } + } + + if (bodyDeclaration.kind === 'FieldDeclaration') numFieldDeclarations += 1 + if (bodyDeclaration.kind === 'MethodDeclaration') numMethodDeclarations += 1 + } + return newResult(null, errors) + } case 'OrdinaryCompilationUnit': { const typeCheckErrors = node.topLevelClassOrInterfaceDeclarations .map(declaration => typeCheckBody(declaration, frame)) @@ -644,16 +724,20 @@ export const typeCheckBody = (node: Node, frame: Frame = Frame.globalFrame()): R const switchBlockFrame = frame.newChildFrame() for (const group of node.switchBlock.switchBlockStatementGroups) { for (const switchLabel of group.switchLabels) { - if ('caseConstant' in switchLabel) { - const checkResult = typeCheckBody( - switchLabel.caseConstant as CaseConstant, - switchBlockFrame - ) - if (checkResult.hasErrors) return checkResult - if (!checkResult.currentType) - throw new TypeCheckerInternalError('Switch case constant should have a type.') - if (expressionCheck.currentType.canBeAssigned(checkResult.currentType)) continue - return newResult(null, [new IncompatibleTypesError(switchLabel.location)]) + // Support both singular 'caseConstant' and plural 'caseConstants' AST shapes + const caseConstants: CaseConstant[] = [] + if ('caseConstant' in switchLabel && (switchLabel as any).caseConstant) caseConstants.push((switchLabel as any).caseConstant as CaseConstant) + if ('caseConstants' in switchLabel && (switchLabel as any).caseConstants) caseConstants.push(...((switchLabel as any).caseConstants as CaseConstant[])) + if (caseConstants.length > 0) { + for (const caseConst of caseConstants) { + const checkResult = typeCheckBody(caseConst, switchBlockFrame) + if (checkResult.hasErrors) return checkResult + if (!checkResult.currentType) + throw new TypeCheckerInternalError('Switch case constant should have a type.') + const assignable = expressionCheck.currentType.canBeAssigned(checkResult.currentType) + if (assignable) continue + return newResult(null, [new IncompatibleTypesError(switchLabel.location)]) + } } } if (group.blockStatements) { diff --git a/src/types/checker/prechecks.ts b/src/types/checker/prechecks.ts index fe7df448..9ec0a9c6 100644 --- a/src/types/checker/prechecks.ts +++ b/src/types/checker/prechecks.ts @@ -1,4 +1,4 @@ -import { Class, ClassType, ObjectClass } from '../types/classes' +import { Class, ClassType, EnumClass, ObjectClass } from '../types/classes' import { ConstructorDeclaration, MethodDeclaration, Node } from '../ast/specificationTypes' import { createClassFieldsAndMethods } from '../typeFactories/classFactory' import { createMethod } from '../typeFactories/methodFactory' @@ -15,6 +15,31 @@ export const addClasses = (node: Node, frame: Frame): Result => { const typeCheckErrors = node.topLevelClassOrInterfaceDeclarations .map(declaration => addClasses(declaration, frame)) .reduce((errors, result) => (result.hasErrors ? [...errors, ...result.errors] : errors), []) + + // Register any nested enum declarations found anywhere in the compilation unit + const registerNestedEnums = (obj: any) => { + if (!obj || typeof obj !== 'object') return + if (Array.isArray(obj)) { + obj.forEach(registerNestedEnums) + return + } + if (obj.kind === 'EnumDeclaration') { + try { + const enumType = new EnumClass(obj.typeIdentifier.identifier) + const err = frame.setType(obj.typeIdentifier.identifier, enumType, obj.typeIdentifier.location) + if (err instanceof Error) { + // duplicate class — add as error + typeCheckErrors.push(new DuplicateClassError(obj.location)) + } + } catch (e) { + // ignore + } + return + } + Object.keys(obj).forEach(k => registerNestedEnums(obj[k])) + } + node.topLevelClassOrInterfaceDeclarations.forEach(registerNestedEnums) + return newResult(null, typeCheckErrors) } case 'NormalClassDeclaration': { @@ -35,7 +60,16 @@ export const addClasses = (node: Node, frame: Frame): Result => { return newResult(classType) } case 'EnumDeclaration': { - throw new Error('Not implemented') + const enumType = new EnumClass(node.typeIdentifier.identifier) + const errors: TypeCheckerError[] = [] + if (errors.length > 0) return newResult(null, errors) + const error = frame.setType( + node.typeIdentifier.identifier, + enumType, + node.typeIdentifier.location + ) + if (error instanceof Error) return newResult(null, [new DuplicateClassError(node.location)]) + return newResult(enumType) } case 'RecordDeclaration': { throw new Error('Not implemented') @@ -54,6 +88,23 @@ export const addClassMethods = (node: Node, frame: Frame): Result => { const typeCheckErrors = node.topLevelClassOrInterfaceDeclarations .map(declaration => addClassMethods(declaration, frame)) .reduce((errors, result) => (result.hasErrors ? [...errors, ...result.errors] : errors), []) + + // Also process any nested enum declarations (e.g., enums declared inside methods) + const processNestedEnums = (obj: any) => { + if (!obj || typeof obj !== 'object') return + if (Array.isArray(obj)) { + obj.forEach(processNestedEnums) + return + } + if (obj.kind === 'EnumDeclaration') { + const res = addClassMethods(obj, frame) + if (res.hasErrors) typeCheckErrors.push(...res.errors) + return + } + Object.keys(obj).forEach(k => processNestedEnums(obj[k])) + } + node.topLevelClassOrInterfaceDeclarations.forEach(processNestedEnums) + return newResult(null, typeCheckErrors) } case 'ConstructorDeclaration': @@ -74,6 +125,64 @@ export const addClassMethods = (node: Node, frame: Frame): Result => { if (classType instanceof TypeCheckerError) return newResult(null, [classType]) return newResult(classType) } + case 'EnumDeclaration': { + const createMethodLocal = ( + node: ConstructorDeclaration | MethodDeclaration + ): Method | TypeCheckerError => { + const result = addClassMethods(node, frame) + if (result.errors.length > 0) return result.errors[0] + return result.currentType as Method + } + + // Populate enum constants and any class-body declarations (fields/methods/constructors) + const classType = frame.getType(node.typeIdentifier.identifier, node.typeIdentifier.location) + if (classType instanceof TypeCheckerError) return newResult(null, [classType]) + if (!(classType instanceof ClassType)) throw new Error('enum type should be a ClassImpl') + + // Add enum constants as fields of the enum type + const enumConstants = node.enumBody.enumConstantList?.enumConstants || [] + for (const constant of enumConstants) { + const fieldError = classType.addField(constant.identifier.identifier, classType, constant.location) + if (fieldError instanceof TypeCheckerError) return newResult(null, [fieldError]) + } + + // Process body declarations similar to class body + const bodyDecls = node.enumBody.enumBodyDeclarations?.classBodyDeclaration || [] + for (const bodyNode of bodyDecls) { + switch (bodyNode.kind) { + case 'ConstructorDeclaration': { + const constructorMethod = createMethodLocal(bodyNode as ConstructorDeclaration) + if (constructorMethod instanceof TypeCheckerError) return newResult(null, [constructorMethod]) + const error = classType.addConstructor(constructorMethod, bodyNode.location) + if (error instanceof TypeCheckerError) return newResult(null, [error]) + break + } + case 'FieldDeclaration': { + const fieldType = frame.getType( + (bodyNode as any).unannType ? (bodyNode as any).unannType : (bodyNode as any).fieldType, + bodyNode.location + ) + if (fieldType instanceof TypeCheckerError) return newResult(null, [fieldType]) + for (const declarator of (bodyNode as any).variableDeclaratorList.variableDeclarators) { + const fieldIdentifier = declarator.variableDeclaratorId.identifier + const error = classType.addField(fieldIdentifier.identifier, fieldType, fieldIdentifier.location) + if (error instanceof TypeCheckerError) return newResult(null, [error]) + } + break + } + case 'MethodDeclaration': { + const methodSignature = createMethodLocal(bodyNode as MethodDeclaration) + if (methodSignature instanceof TypeCheckerError) return newResult(null, [methodSignature]) + const methodName = (bodyNode as MethodDeclaration).methodHeader.methodDeclarator.identifier + const error = classType.addMethod(methodName.identifier, methodSignature, methodName.location) + if (error instanceof TypeCheckerError) return newResult(null, [error]) + break + } + } + } + + return newResult(classType) + } default: return OK_RESULT } @@ -111,6 +220,18 @@ export const addClassParents = (node: Node, frame: Frame): Result => { } return newResult(classType) } + case 'EnumDeclaration': { + const classType = frame.getType(node.typeIdentifier.identifier, node.typeIdentifier.location) + if (classType instanceof Error) return newResult(null, [classType]) + if (!(classType instanceof ClassType)) throw new Error('enum type should be a ClassImpl') + + // Enums implicitly extend java.lang.Enum (represented here as 'Enum' in the type environment) + const enumBase = frame.getType('Enum', node.typeIdentifier.location) + if (enumBase instanceof Error) return newResult(null, [enumBase]) + if (!(enumBase instanceof ClassType)) throw new Error('Enum base should be a ClassImpl') + classType.setParentClass(enumBase) + return newResult(classType) + } default: return OK_RESULT } diff --git a/src/types/checker/statements.ts b/src/types/checker/statements.ts index fe88fcef..3ce1e192 100644 --- a/src/types/checker/statements.ts +++ b/src/types/checker/statements.ts @@ -6,6 +6,7 @@ import { TypeCheckerError } from '../errors' import { Throwable } from '../types/references' +import { EnumClass } from '../types/classes' import { Type } from '../types/type' import { isPrimitiveBooleanType, @@ -29,6 +30,7 @@ export const checkSwitchExpression = ( ): null | TypeCheckerError => { if (isPrimitiveIntegralType(expressionType) && !isPrimitiveLongType(expressionType)) return null if (isStringType(expressionType)) return null + if (expressionType instanceof EnumClass) return null return new SelectorTypeNotAllowedError(location) } diff --git a/src/types/types/classes.ts b/src/types/types/classes.ts index 00b4ec0d..8334f2e5 100644 --- a/src/types/types/classes.ts +++ b/src/types/types/classes.ts @@ -144,6 +144,8 @@ export class ClassType extends ClassOrInterfaceType implements Class { } } +export class EnumClass extends ClassType {} + export class ObjectClass extends ClassOrInterfaceType implements Class { public readonly name: string = 'Object' public constructor() { From 4e0b4d5be3487e3824e7aac5d37b2ab3646bc6eb Mon Sep 17 00:00:00 2001 From: kjw142857 Date: Wed, 26 Aug 2026 15:37:43 +0800 Subject: [PATCH 3/5] WIP: Add enum grammar rules and compiler tests - Updated grammar.pegjs and grammar.ts to add EnumDeclaration parsing - Added TopLevelClassOrInterfaceDeclaration and ClassMemberDeclaration alternatives for EnumDeclaration - Added EnumDeclaration, EnumBody, EnumConstantList, and EnumConstant parsing rules - Created src/compiler/__tests__/tests/enum.test.ts with 3 enum test cases - Updated src/compiler/__tests__/index.ts to import and run enum tests Remaining work: - Run enum compiler tests to verify parsing works - Implement enum code generation in compiler.ts (enum initialization, synthetic methods) - Run full test suite to validate no regressions - Verify enum runtime behavior (ordinal(), name(), values(), valueOf()) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/compiler/__tests__/index.ts | 2 + src/compiler/__tests__/tests/enum.test.ts | 107 ++++++++++++++++++++++ src/compiler/grammar.pegjs | 34 +++++++ src/compiler/grammar.ts | 34 +++++++ 4 files changed, 177 insertions(+) create mode 100644 src/compiler/__tests__/tests/enum.test.ts diff --git a/src/compiler/__tests__/index.ts b/src/compiler/__tests__/index.ts index d19bcb99..bddc2a26 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 { enumTest } from "./tests/enum.test"; import { typeConversionTest } from "./tests/typeConversion.test"; describe("compiler tests", () => { @@ -23,5 +24,6 @@ describe("compiler tests", () => { importTest(); arrayTest(); classTest(); + enumTest(); typeConversionTest(); }) diff --git a/src/compiler/__tests__/tests/enum.test.ts b/src/compiler/__tests__/tests/enum.test.ts new file mode 100644 index 00000000..659e017d --- /dev/null +++ b/src/compiler/__tests__/tests/enum.test.ts @@ -0,0 +1,107 @@ +import { + runTest, + testCase, +} from "../__utils__/test-utils"; + +const testCases: testCase[] = [ + { + comment: "enum switch and synthetic methods", + program: ` + public enum Color { + RED, + BLUE + } + + public class Main { + public static void main(String[] args) { + Color red = Color.valueOf("RED"); + System.out.println(Color.RED.ordinal()); + System.out.println(Color.BLUE.name()); + System.out.println(red.toString()); + + Color selector = Color.BLUE; + switch (selector) { + case Color.RED: + System.out.println("bad"); + break; + case Color.BLUE: + System.out.println("ok"); + break; + default: + System.out.println("default"); + } + } + } + `, + expectedLines: ["0", "BLUE", "RED", "ok"], + }, + { + comment: "enum values returns cloned array", + program: ` + public enum Direction { + NORTH, + SOUTH + } + + public class Main { + public static void main(String[] args) { + Direction[] copy = Direction.values(); + copy[0] = Direction.SOUTH; + Direction[] fresh = Direction.values(); + + switch (fresh[0]) { + case Direction.NORTH: + System.out.println("fresh"); + break; + default: + System.out.println("bad"); + } + + switch (copy[0]) { + case Direction.SOUTH: + System.out.println("mutated"); + break; + default: + System.out.println("bad"); + } + } + } + `, + expectedLines: ["fresh", "mutated"], + }, + { + comment: "enum constructors and instance fields", + program: ` + public enum Planet { + EARTH(1), + MARS(2); + + private int moons; + + private Planet(int moons) { + this.moons = moons; + } + + public int moons() { + return this.moons; + } + } + + public class Main { + public static void main(String[] args) { + Planet mars = Planet.valueOf("MARS"); + System.out.println(Planet.EARTH.moons()); + System.out.println(mars.moons()); + } + } + `, + expectedLines: ["1", "2"], + }, +]; + +export const enumTest = () => describe("enums", () => { + for (let testCase of testCases) { + const { comment: comment, program: program, expectedLines: expectedLines } = testCase; + it(comment, () => runTest(program, expectedLines)); + } +}); diff --git a/src/compiler/grammar.pegjs b/src/compiler/grammar.pegjs index 505f648e..a1f84bc4 100755 --- a/src/compiler/grammar.pegjs +++ b/src/compiler/grammar.pegjs @@ -475,6 +475,7 @@ TypeImportOnDemandDeclaration TopLevelClassOrInterfaceDeclaration = ClassDeclaration + / EnumDeclaration / InterfaceDeclaration / semicolon @@ -520,6 +521,38 @@ ClassModifier / non_sealed / strictfp +EnumDeclaration + = cm:ClassModifier* enum tm:TypeIdentifier ClassImplements? eb:EnumBody { + return addLocInfo({ + kind: "EnumDeclaration", + classModifier: cm, + typeIdentifier: tm, + enumBody: eb, + }) + } + +EnumBody + = lcurly ecl:EnumConstantList? ec:EnumConstant* rcurly { + const constants = ecl ? [...ecl, ...ec] : ec; + return addLocInfo({ + kind: "EnumBody", + constants: constants, + }) + } + +EnumConstantList + = @EnumConstant (comma @EnumConstant)* comma? + +EnumConstant + = name:Identifier (lparen al:ArgumentList? rparen)? cb:(lcurly ClassBodyDeclaration* rcurly)? { + return addLocInfo({ + kind: "EnumConstant", + name: name, + arguments: al || [], + classBody: cb || [], + }) + } + TypeParameters = TO_BE_ADDED @@ -551,6 +584,7 @@ ClassMemberDeclaration = FieldDeclaration / MethodDeclaration / ClassDeclaration + / EnumDeclaration / InterfaceDeclaration / semicolon diff --git a/src/compiler/grammar.ts b/src/compiler/grammar.ts index c7417294..24861abb 100755 --- a/src/compiler/grammar.ts +++ b/src/compiler/grammar.ts @@ -477,6 +477,7 @@ TypeImportOnDemandDeclaration TopLevelClassOrInterfaceDeclaration = ClassDeclaration + / EnumDeclaration / InterfaceDeclaration / semicolon @@ -522,6 +523,38 @@ ClassModifier / non_sealed / strictfp +EnumDeclaration + = cm:ClassModifier* enum tm:TypeIdentifier ClassImplements? eb:EnumBody { + return addLocInfo({ + kind: "EnumDeclaration", + classModifier: cm, + typeIdentifier: tm, + enumBody: eb, + }) + } + +EnumBody + = lcurly ecl:EnumConstantList? ec:EnumConstant* rcurly { + const constants = ecl ? [...ecl, ...ec] : ec; + return addLocInfo({ + kind: "EnumBody", + constants: constants, + }) + } + +EnumConstantList + = @EnumConstant (comma @EnumConstant)* comma? + +EnumConstant + = name:Identifier (lparen al:ArgumentList? rparen)? cb:(lcurly ClassBodyDeclaration* rcurly)? { + return addLocInfo({ + kind: "EnumConstant", + name: name, + arguments: al || [], + classBody: cb || [], + }) + } + TypeParameters = TO_BE_ADDED @@ -553,6 +586,7 @@ ClassMemberDeclaration = FieldDeclaration / MethodDeclaration / ClassDeclaration + / EnumDeclaration / InterfaceDeclaration / semicolon From 0625138f1d4e13345e0f118b080f91b9fdce54de Mon Sep 17 00:00:00 2001 From: kjw142857 Date: Wed, 26 Aug 2026 16:38:37 +0800 Subject: [PATCH 4/5] Add enum parsing and compiler support (partial) - Updated grammar (grammar.pegjs and grammar.ts) to parse enum declarations - EnumDeclaration, EnumBody, EnumConstantList, EnumConstant rules - Support for optional semicolon after constants and enum body members - Extended AST types (src/ast/types/classes.ts) - Added EnumDeclaration, EnumBody, EnumConstant interfaces - Updated ClassDeclaration union to include EnumDeclaration - Updated ClassBodyDeclaration to include EnumDeclaration - Added EnumDeclaration to NodeMap (src/ast/types/ast.ts) - Updated compiler to handle enum declarations - Added compileEnum() method in src/compiler/compiler.ts - Updated compile() to route EnumDeclaration through compileEnum() - Fixed type signatures to handle both ClassDeclaration and EnumDeclaration - Set enum parent to java/lang/Enum and ACC_ENUM flag - Updated ast-extractor.ts and ec-evaluator/utils.ts to accept ClassDeclaration[] - Updated searchMainMtdClass() to filter out enums - Created src/compiler/__tests__/tests/enum.test.ts with 3 test cases - enum switch and synthetic methods - enum values returns cloned array - enum constructors and instance fields Status: Enums parse and compile, but synthetic methods not yet implemented. Tests failing because ordinal(), name(), values(), valueOf() missing. Next: Implement synthetic enum method generation in compiler.ts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/ast/astExtractor/ast-extractor.ts | 4 +- src/ast/types/ast.ts | 2 + src/ast/types/classes.ts | 27 +++++++++-- src/compiler/binary-writer.ts | 4 +- src/compiler/code-generator.ts | 60 ++++++++++++++---------- src/compiler/compiler.ts | 66 ++++++++++++++++++++++++--- src/compiler/error.ts | 2 +- src/compiler/grammar.pegjs | 18 ++++++-- src/compiler/grammar.ts | 18 ++++++-- src/compiler/symbol-table.ts | 18 +++++--- src/ec-evaluator/utils.ts | 12 +++-- src/jvm/exception-table.ts | 50 ++++++++++---------- src/types/checker/index.ts | 2 +- src/types/checker/prechecks.ts | 6 +-- 14 files changed, 202 insertions(+), 87 deletions(-) diff --git a/src/ast/astExtractor/ast-extractor.ts b/src/ast/astExtractor/ast-extractor.ts index 681bb7e9..7364448d 100644 --- a/src/ast/astExtractor/ast-extractor.ts +++ b/src/ast/astExtractor/ast-extractor.ts @@ -1,11 +1,11 @@ import { BaseJavaCstVisitorWithDefaults, CstNode, TypeDeclarationCtx } from "java-parser"; -import { NormalClassDeclaration } from "../types/classes"; +import { ClassDeclaration } from "../types/classes"; import { AST } from "../types/packages-and-modules"; import { ClassExtractor } from "./class-extractor"; export class ASTExtractor extends BaseJavaCstVisitorWithDefaults { - private topLevelClassOrInterfaceDeclarations: NormalClassDeclaration[] = []; + private topLevelClassOrInterfaceDeclarations: ClassDeclaration[] = []; extract(cst: CstNode): AST { this.visit(cst); diff --git a/src/ast/types/ast.ts b/src/ast/types/ast.ts index 80effeac..a0eef563 100644 --- a/src/ast/types/ast.ts +++ b/src/ast/types/ast.ts @@ -12,6 +12,7 @@ import { } from "./blocks-and-statements"; import { ConstructorDeclaration, + EnumDeclaration, FieldDeclaration, MethodDeclaration, NormalClassDeclaration, @@ -29,6 +30,7 @@ interface NodeMap { MethodInvocation: MethodInvocation; ReturnStatement: ReturnStatement; NormalClassDeclaration: NormalClassDeclaration; + EnumDeclaration: EnumDeclaration; ClassInstanceCreationExpression: ClassInstanceCreationExpression; ConstructorDeclaration: ConstructorDeclaration; ExplicitConstructorInvocation: ExplicitConstructorInvocation; diff --git a/src/ast/types/classes.ts b/src/ast/types/classes.ts index b7345e78..576d9aa4 100644 --- a/src/ast/types/classes.ts +++ b/src/ast/types/classes.ts @@ -1,7 +1,7 @@ import { BaseNode } from "./ast"; import { Block, VariableDeclarator } from "./blocks-and-statements"; -export type ClassDeclaration = NormalClassDeclaration; +export type ClassDeclaration = NormalClassDeclaration | EnumDeclaration; export interface NormalClassDeclaration extends BaseNode { kind: "NormalClassDeclaration"; @@ -11,6 +11,26 @@ export interface NormalClassDeclaration extends BaseNode { classBody: Array; } +export interface EnumDeclaration extends BaseNode { + kind: "EnumDeclaration"; + classModifier: Array; + typeIdentifier: Identifier; + enumBody: EnumBody; +} + +export interface EnumBody extends BaseNode { + kind: "EnumBody"; + constants: Array; + bodyMembers?: Array; +} + +export interface EnumConstant extends BaseNode { + kind: "EnumConstant"; + name: Identifier; + arguments?: Array; + classBody?: Array; +} + export type ClassModifier = | "public" | "protected" @@ -20,9 +40,10 @@ export type ClassModifier = | "final" | "sealed" | "non-sealed" - | "strictfp"; + | "strictfp" + | "enum"; -export type ClassBodyDeclaration = ClassMemberDeclaration | ConstructorDeclaration; +export type ClassBodyDeclaration = ClassMemberDeclaration | ConstructorDeclaration | EnumDeclaration; export type ClassMemberDeclaration = MethodDeclaration | FieldDeclaration; export interface ConstructorDeclaration extends BaseNode { diff --git a/src/compiler/binary-writer.ts b/src/compiler/binary-writer.ts index 8b97dfb0..e4f09b77 100644 --- a/src/compiler/binary-writer.ts +++ b/src/compiler/binary-writer.ts @@ -49,7 +49,9 @@ export class BinaryWriter { fs.writeFileSync(filename, binary) } - private normalizeClassFile(classFile: ClassFile | Class | Array | Array): ClassFile { + private normalizeClassFile( + classFile: ClassFile | Class | Array | Array + ): ClassFile { if (Array.isArray(classFile)) { if (classFile.length === 0) { throw new Error('BinaryWriter expected a non-empty array of classes') diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts index d4d5d101..d26b638d 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -191,19 +191,19 @@ const EMPTY_TYPE: string = '' function areClassTypesCompatible(fromType: string, toType: string, cg: CodeGenerator): boolean { const cleanFrom = fromType.replace(/^L|;$/g, '') const cleanTo = toType.replace(/^L|;$/g, '') - if (cleanFrom === cleanTo) return true; + if (cleanFrom === cleanTo) return true try { - let current = cg.symbolTable.queryClass(cleanFrom); + let current = cg.symbolTable.queryClass(cleanFrom) while (current.parentClassName) { - const parentClean = current.parentClassName; - if (parentClean === cleanTo) return true; - current = cg.symbolTable.queryClass(parentClean); + const parentClean = current.parentClassName + if (parentClean === cleanTo) return true + current = cg.symbolTable.queryClass(parentClean) } } catch (e) { - return false; + return false } - return false; + return false } function handleImplicitTypeConversion(fromType: string, toType: string, cg: CodeGenerator): number { @@ -839,30 +839,30 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi // --- Handle super. calls --- if (n.identifier.startsWith('super.')) { candidateMethods = cg.symbolTable.queryMethod(n.identifier.slice(6)) as MethodInfos - candidateMethods = candidateMethods.filter(method => - method.className == cg.symbolTable.queryClass(cg.currentClass).parentClassName) - cg.code.push(OPCODE.ALOAD, 0); + candidateMethods = candidateMethods.filter( + method => method.className == cg.symbolTable.queryClass(cg.currentClass).parentClassName + ) + cg.code.push(OPCODE.ALOAD, 0) } // --- Handle qualified calls (e.g. System.out.println or p.show) --- else if (n.identifier.includes('.')) { - const lastDot = n.identifier.lastIndexOf('.'); - const receiverStr = n.identifier.slice(0, lastDot); + const lastDot = n.identifier.lastIndexOf('.') + const receiverStr = n.identifier.slice(0, lastDot) if (receiverStr === 'this') { candidateMethods = cg.symbolTable.queryMethod(n.identifier.slice(5)) as MethodInfos - candidateMethods = candidateMethods.filter(method => - method.className == cg.currentClass) - cg.code.push(OPCODE.ALOAD, 0); + candidateMethods = candidateMethods.filter(method => method.className == cg.currentClass) + cg.code.push(OPCODE.ALOAD, 0) } else { - const recvRes = compile({ kind: 'ExpressionName', name: receiverStr }, cg); - maxStack = Math.max(maxStack, recvRes.stackSize); + const recvRes = compile({ kind: 'ExpressionName', name: receiverStr }, cg) + maxStack = Math.max(maxStack, recvRes.stackSize) candidateMethods = cg.symbolTable.queryMethod(n.identifier).pop() as MethodInfos } } // --- Handle unqualified calls --- else { candidateMethods = cg.symbolTable.queryMethod(n.identifier) as MethodInfos - unqualifiedCall = true; + unqualifiedCall = true } // Filter candidate methods by matching the argument list. @@ -904,11 +904,15 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi .slice(1, methodMatches[i].typeDescriptor.indexOf(')')) .match(/(\[+[BCDFIJSZ])|(\[+L[^;]+;)|[BCDFIJSZ]|L[^;]+;/g) || [] if ( - candParams.map((p, idx) => isSubtype(p, currParams[idx], cg)).reduce((a, b) => a && b, true) + candParams + .map((p, idx) => isSubtype(p, currParams[idx], cg)) + .reduce((a, b) => a && b, true) ) { selectedMethod = methodMatches[i] } else if ( - !currParams.map((p, idx) => isSubtype(p, candParams[idx], cg)).reduce((a, b) => a && b, true) + !currParams + .map((p, idx) => isSubtype(p, candParams[idx], cg)) + .reduce((a, b) => a && b, true) ) { throw new AmbiguousMethodCallError(n.identifier + argDescs.join(',')) } @@ -1267,7 +1271,7 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi try { info = cg.symbolTable.queryVariable(name) } catch (e) { - return { stackSize: 1, resultType: 'Ljava/lang/Class;' }; + return { stackSize: 1, resultType: 'Ljava/lang/Class;' } } if (Array.isArray(info)) { const fieldInfos = info @@ -1492,7 +1496,12 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi // Generate lookup table (pairs of case values and corresponding labels) caseValues.forEach((value, index) => { // push 4-byte key - cg.code.push((value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff) + cg.code.push( + (value >> 24) & 0xff, + (value >> 16) & 0xff, + (value >> 8) & 0xff, + value & 0xff + ) // reserve 4 bytes for the branch target cg.code.push(0, 0, 0, 0) // label offset starts after the 4-byte key @@ -1635,7 +1644,12 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi // Populate LOOKUPSWITCH const hashLabels: Label[] = [] hashCaseMap.forEach((label, hashCode) => { - cg.code.push((hashCode >> 24) & 0xff, (hashCode >> 16) & 0xff, (hashCode >> 8) & 0xff, hashCode & 0xff) + cg.code.push( + (hashCode >> 24) & 0xff, + (hashCode >> 16) & 0xff, + (hashCode >> 8) & 0xff, + hashCode & 0xff + ) // reserve 4 bytes for the branch target cg.code.push(0, 0, 0, 0) // label offset starts after the 4-byte key diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index 3e116800..93e0ddae 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -56,17 +56,26 @@ export class Compiler { ast.topLevelClassOrInterfaceDeclarations.forEach(decl => { const className = decl.typeIdentifier - const parentClassName = decl.sclass ? decl.sclass : 'java/lang/Object' + const parentClassName = (decl.kind === 'EnumDeclaration' ? 'java/lang/Enum' : + ('sclass' in decl && decl.sclass) ? decl.sclass : 'java/lang/Object') const accessFlags = generateClassAccessFlags(decl.classModifier) - this.symbolTable.insertClassInfo( - { name: className, accessFlags: accessFlags, parentClassName: parentClassName }) + this.symbolTable.insertClassInfo({ + name: className, + accessFlags: accessFlags, + parentClassName: parentClassName + }) this.symbolTable.returnToRoot() }) ast.topLevelClassOrInterfaceDeclarations.forEach(decl => { this.resetClassFileState() - const classFile = this.compileClass(decl) - classFiles.push({classFile: classFile, className: this.className}) + if (decl.kind === 'EnumDeclaration') { + const classFile = this.compileEnum(decl) + classFiles.push({ classFile: classFile, className: this.className }) + } else { + const classFile = this.compileClass(decl) + classFiles.push({ classFile: classFile, className: this.className }) + } }) return classFiles @@ -74,7 +83,8 @@ export class Compiler { private compileClass(classNode: ClassDeclaration): ClassFile { this.className = classNode.typeIdentifier - this.parentClassName = classNode.sclass ? classNode.sclass : 'java/lang/Object' + const sclass = 'sclass' in classNode ? classNode.sclass : undefined + this.parentClassName = sclass ? sclass : 'java/lang/Object' const accessFlags = generateClassAccessFlags(classNode.classModifier) this.symbolTable.extend() this.symbolTable.insertClassInfo({ name: this.className, accessFlags: accessFlags }) @@ -82,8 +92,50 @@ export class Compiler { const superClassIndex = this.constantPoolManager.indexClassInfo(this.parentClassName) const thisClassIndex = this.constantPoolManager.indexClassInfo(this.className) this.constantPoolManager.indexUtf8Info('Code') - this.handleClassBody(classNode.classBody) + const classBody = 'classBody' in classNode ? classNode.classBody : [] + this.handleClassBody(classBody) + + const constantPool = this.constantPoolManager.getPool() + return { + magic: MAGIC, + minorVersion: MINOR_VERSION, + majorVersion: MAJOR_VERSION, + constantPoolCount: this.constantPoolManager.getSize(), + constantPool: constantPool, + accessFlags: accessFlags, + thisClass: thisClassIndex, + superClass: superClassIndex, + interfacesCount: this.interfaces.length, + interfaces: this.interfaces, + fieldsCount: this.fields.length, + fields: this.fields, + methodsCount: this.methods.length, + methods: this.methods, + attributesCount: this.attributes.length, + attributes: this.attributes + } + } + + private compileEnum(enumNode: any): ClassFile { + this.className = enumNode.typeIdentifier + this.parentClassName = 'java/lang/Enum' + const accessFlags = generateClassAccessFlags(enumNode.classModifier) | 0x4000 // Add ACC_ENUM + this.symbolTable.extend() + this.symbolTable.insertClassInfo({ name: this.className, accessFlags: accessFlags }) + const superClassIndex = this.constantPoolManager.indexClassInfo(this.parentClassName) + const thisClassIndex = this.constantPoolManager.indexClassInfo(this.className) + this.constantPoolManager.indexUtf8Info('Code') + + // Handle enum constants and body members + const enumBody = enumNode.enumBody + const bodyMembers = enumBody.bodyMembers || [] + this.handleClassBody(bodyMembers) + + // TODO: Add synthetic enum fields and methods + // Add $VALUES array field + // Add ordinal, name, toString, values, valueOf methods + const constantPool = this.constantPoolManager.getPool() return { magic: MAGIC, diff --git a/src/compiler/error.ts b/src/compiler/error.ts index 1044d4fd..e0355528 100644 --- a/src/compiler/error.ts +++ b/src/compiler/error.ts @@ -50,4 +50,4 @@ export class OverrideFinalMethodError extends CompileError { constructor(name: string) { super(`Cannot override final method ${name}`) } -} \ No newline at end of file +} diff --git a/src/compiler/grammar.pegjs b/src/compiler/grammar.pegjs index a1f84bc4..41a41363 100755 --- a/src/compiler/grammar.pegjs +++ b/src/compiler/grammar.pegjs @@ -532,23 +532,31 @@ EnumDeclaration } EnumBody - = lcurly ecl:EnumConstantList? ec:EnumConstant* rcurly { - const constants = ecl ? [...ecl, ...ec] : ec; + = lcurly ecl:EnumConstantList? semicolon? em:EnumBodyMembers rcurly { + const constants = ecl || []; return addLocInfo({ kind: "EnumBody", constants: constants, + bodyMembers: em, }) } +EnumBodyMembers + = members:ClassBodyDeclaration* { + return members; + } + EnumConstantList - = @EnumConstant (comma @EnumConstant)* comma? + = first:EnumConstant rest:(comma @EnumConstant)* comma? { + return [first, ...rest]; + } EnumConstant - = name:Identifier (lparen al:ArgumentList? rparen)? cb:(lcurly ClassBodyDeclaration* rcurly)? { + = name:Identifier args:(lparen al:ArgumentList? rparen)? cb:(lcurly ClassBodyDeclaration* rcurly)? { return addLocInfo({ kind: "EnumConstant", name: name, - arguments: al || [], + arguments: (args && args[1]) ? args[1] : [], classBody: cb || [], }) } diff --git a/src/compiler/grammar.ts b/src/compiler/grammar.ts index 24861abb..0caf73ae 100755 --- a/src/compiler/grammar.ts +++ b/src/compiler/grammar.ts @@ -534,23 +534,31 @@ EnumDeclaration } EnumBody - = lcurly ecl:EnumConstantList? ec:EnumConstant* rcurly { - const constants = ecl ? [...ecl, ...ec] : ec; + = lcurly ecl:EnumConstantList? semicolon? em:EnumBodyMembers rcurly { + const constants = ecl || []; return addLocInfo({ kind: "EnumBody", constants: constants, + bodyMembers: em, }) } +EnumBodyMembers + = members:ClassBodyDeclaration* { + return members; + } + EnumConstantList - = @EnumConstant (comma @EnumConstant)* comma? + = first:EnumConstant rest:(comma @EnumConstant)* comma? { + return [first, ...rest]; + } EnumConstant - = name:Identifier (lparen al:ArgumentList? rparen)? cb:(lcurly ClassBodyDeclaration* rcurly)? { + = name:Identifier args:(lparen al:ArgumentList? rparen)? cb:(lcurly ClassBodyDeclaration* rcurly)? { return addLocInfo({ kind: "EnumConstant", name: name, - arguments: al || [], + arguments: (args && args[1]) ? args[1] : [], classBody: cb || [], }) } diff --git a/src/compiler/symbol-table.ts b/src/compiler/symbol-table.ts index 394ffd34..314ebcc4 100644 --- a/src/compiler/symbol-table.ts +++ b/src/compiler/symbol-table.ts @@ -1,18 +1,19 @@ import { UnannType } from '../ast/types/classes' import { ImportDeclaration } from '../ast/types/packages-and-modules' +import { METHOD_FLAGS } from '../ClassFile/types/methods' import { generateClassAccessFlags, generateFieldAccessFlags, generateMethodAccessFlags } from './compiler-utils' import { - InvalidMethodCallError, OverrideFinalMethodError, + InvalidMethodCallError, + OverrideFinalMethodError, SymbolCannotBeResolvedError, SymbolNotFoundError, SymbolRedeclarationError } from './error' import { libraries } from './import/libs' -import { METHOD_FLAGS } from '../ClassFile/types/methods' export const typeMap = new Map([ ['byte', 'B'], @@ -209,14 +210,17 @@ export class SymbolTable { const key = generateSymbol(info.name, SymbolType.METHOD) for (let i = this.curClassIdx - 1; i > 0; i--) { - const parentTable = this.tables[i]; + const parentTable = this.tables[i] if (parentTable.has(key)) { - const parentMethods = parentTable.get(key)!.info; + const parentMethods = parentTable.get(key)!.info if (Array.isArray(parentMethods)) { for (const m of parentMethods) { - if (m.typeDescriptor === info.typeDescriptor && (m.accessFlags & METHOD_FLAGS.ACC_FINAL) - && m.className == info.parentClassName) { - throw new OverrideFinalMethodError(info.name); + if ( + m.typeDescriptor === info.typeDescriptor && + m.accessFlags & METHOD_FLAGS.ACC_FINAL && + m.className == info.parentClassName + ) { + throw new OverrideFinalMethodError(info.name) } } } diff --git a/src/ec-evaluator/utils.ts b/src/ec-evaluator/utils.ts index 650a078f..244cf79b 100644 --- a/src/ec-evaluator/utils.ts +++ b/src/ec-evaluator/utils.ts @@ -9,6 +9,7 @@ import { ReturnStatement } from '../ast/types/blocks-and-statements' import { + ClassDeclaration, ConstructorDeclaration, FieldDeclaration, MethodDeclaration, @@ -370,9 +371,12 @@ export const appendEmtpyReturn = (method: MethodDeclaration): void => { } } -export const searchMainMtdClass = (classes: NormalClassDeclaration[]) => { - return classes.find(c => - c.classBody.some( +export const searchMainMtdClass = (classes: ClassDeclaration[]) => { + return classes.find(c => { + if (c.kind === 'EnumDeclaration') { + return false // Enums can't have main method (they have bodyMembers instead of classBody) + } + return (c as NormalClassDeclaration).classBody.some( d => d.kind === 'MethodDeclaration' && d.methodModifier.includes('public') && @@ -383,7 +387,7 @@ export const searchMainMtdClass = (classes: NormalClassDeclaration[]) => { d.methodHeader.formalParameterList[0].unannType === 'String[]' && d.methodHeader.formalParameterList[0].identifier === 'args' ) - )?.typeIdentifier + })?.typeIdentifier } /** diff --git a/src/jvm/exception-table.ts b/src/jvm/exception-table.ts index 15248a87..766b4ff1 100644 --- a/src/jvm/exception-table.ts +++ b/src/jvm/exception-table.ts @@ -1,33 +1,33 @@ -import { ClassData } from "./types/class/ClassData" +import { ClassData } from './types/class/ClassData' class Entry { - from: number - to: number - target: number - type: ClassData + from: number + to: number + target: number + type: ClassData - constructor(from: number, to: number, target: number, type: ClassData) { - this.from = from; - this.to = to; - this.target = target; - this.type = type; - } + constructor(from: number, to: number, target: number, type: ClassData) { + this.from = from + this.to = to + this.target = target + this.type = type + } } export class ExceptionTable { - private entries: Entry[] + private entries: Entry[] - retrieve(line: number): Entry | null { - this.entries.forEach(entry => { - if (line >= entry.from && line <= entry.to) { - return entry - } - }) - return null - } + retrieve(line: number): Entry | null { + this.entries.forEach(entry => { + if (line >= entry.from && line <= entry.to) { + return entry + } + }) + return null + } - insert(from: number, to: number, target: number, type: ClassData): void { - var entry = new Entry(from, to, target, type) - this.entries.push(entry) - } -} \ No newline at end of file + insert(from: number, to: number, target: number, type: ClassData): void { + const entry = new Entry(from, to, target, type) + this.entries.push(entry) + } +} diff --git a/src/types/checker/index.ts b/src/types/checker/index.ts index 0f3c39fe..ec88960b 100644 --- a/src/types/checker/index.ts +++ b/src/types/checker/index.ts @@ -639,7 +639,7 @@ export const typeCheckBody = (node: Node, frame: Frame = Frame.globalFrame()): R const methodIdentifier = (bodyDeclaration as any).methodHeader.methodDeclarator.identifier const methodName = methodIdentifier.identifier const overloadIndex = bodyDecls - .filter((n: any) => n.kind === 'MethodDeclaration' && (n as any).methodHeader.methodDeclarator.identifier.identifier === methodName) + .filter((n: any) => n.kind === 'MethodDeclaration' && (n).methodHeader.methodDeclarator.identifier.identifier === methodName) .findIndex(n => n === bodyDeclaration) const method = classType.getMethod(methodName)[overloadIndex] const methodFrame = classFrame.newChildFrame() diff --git a/src/types/checker/prechecks.ts b/src/types/checker/prechecks.ts index 9ec0a9c6..cb5b5dcc 100644 --- a/src/types/checker/prechecks.ts +++ b/src/types/checker/prechecks.ts @@ -151,7 +151,7 @@ export const addClassMethods = (node: Node, frame: Frame): Result => { for (const bodyNode of bodyDecls) { switch (bodyNode.kind) { case 'ConstructorDeclaration': { - const constructorMethod = createMethodLocal(bodyNode as ConstructorDeclaration) + const constructorMethod = createMethodLocal(bodyNode) if (constructorMethod instanceof TypeCheckerError) return newResult(null, [constructorMethod]) const error = classType.addConstructor(constructorMethod, bodyNode.location) if (error instanceof TypeCheckerError) return newResult(null, [error]) @@ -171,9 +171,9 @@ export const addClassMethods = (node: Node, frame: Frame): Result => { break } case 'MethodDeclaration': { - const methodSignature = createMethodLocal(bodyNode as MethodDeclaration) + const methodSignature = createMethodLocal(bodyNode) if (methodSignature instanceof TypeCheckerError) return newResult(null, [methodSignature]) - const methodName = (bodyNode as MethodDeclaration).methodHeader.methodDeclarator.identifier + const methodName = (bodyNode).methodHeader.methodDeclarator.identifier const error = classType.addMethod(methodName.identifier, methodSignature, methodName.location) if (error instanceof TypeCheckerError) return newResult(null, [error]) break From 954c17226cbca78cb40204fa457506518e4def87 Mon Sep 17 00:00:00 2001 From: kjw142857 Date: Wed, 26 Aug 2026 16:49:17 +0800 Subject: [PATCH 5/5] Register enum synthetic methods in symbol table - Added enumOrdinals Map to track enum constant ordinals - Registered ordinal(), name(), toString(), values(), valueOf() in symbol table - Fixed FieldInfo insertion to remove invalid 'ordinal' property - Fixed generateSimpleEnumMethod to use indexFieldrefInfo() Status: Compiler builds but enum compiler tests fail with: 1. Switch statement codegen doesn't recognize enum types 2. Bytecode generation may have structural issues Next: Fix enum type detection in switch codegen, then debug bytecode generation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/compiler/compiler.ts | 327 +++++++++++++++++++++++++++++++++++++- src/ec-evaluator/utils.ts | 2 +- 2 files changed, 321 insertions(+), 8 deletions(-) diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index 93e0ddae..dbb99615 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -32,6 +32,7 @@ export class Compiler { private attributes: Array private className: string private parentClassName: string + private enumOrdinals: Map constructor() { this.setup() @@ -47,6 +48,7 @@ export class Compiler { this.fields = [] this.methods = [] this.attributes = [] + this.enumOrdinals = new Map() } compile(ast: AST) { @@ -56,8 +58,12 @@ export class Compiler { ast.topLevelClassOrInterfaceDeclarations.forEach(decl => { const className = decl.typeIdentifier - const parentClassName = (decl.kind === 'EnumDeclaration' ? 'java/lang/Enum' : - ('sclass' in decl && decl.sclass) ? decl.sclass : 'java/lang/Object') + const parentClassName = + decl.kind === 'EnumDeclaration' + ? 'java/lang/Enum' + : 'sclass' in decl && decl.sclass + ? decl.sclass + : 'java/lang/Object' const accessFlags = generateClassAccessFlags(decl.classModifier) this.symbolTable.insertClassInfo({ name: className, @@ -126,16 +132,69 @@ export class Compiler { const superClassIndex = this.constantPoolManager.indexClassInfo(this.parentClassName) const thisClassIndex = this.constantPoolManager.indexClassInfo(this.className) this.constantPoolManager.indexUtf8Info('Code') - + // Handle enum constants and body members const enumBody = enumNode.enumBody + const enumConstants = enumBody.constants || [] const bodyMembers = enumBody.bodyMembers || [] - this.handleClassBody(bodyMembers) - // TODO: Add synthetic enum fields and methods - // Add $VALUES array field - // Add ordinal, name, toString, values, valueOf methods + // Add enum constants as static fields + enumConstants.forEach((constant: any, ordinal: number) => { + const fieldDescriptor = 'L' + this.className + ';' + this.fields.push({ + accessFlags: 0x0019, // public static final + nameIndex: this.constantPoolManager.indexUtf8Info(constant.name), + descriptorIndex: this.constantPoolManager.indexUtf8Info(fieldDescriptor), + attributesCount: 0, + attributes: [] + }) + this.symbolTable.insertFieldInfo({ + name: constant.name, + accessFlags: 0x0019, + parentClassName: this.className, + typeName: this.className, + typeDescriptor: fieldDescriptor + }) + this.enumOrdinals.set(constant.name, ordinal) + }) + + // Add synthetic $VALUES field (private static final) + const valuesFieldDescriptor = '[L' + this.className + ';' + this.fields.push({ + accessFlags: 0x001a, // private static final + nameIndex: this.constantPoolManager.indexUtf8Info('$VALUES'), + descriptorIndex: this.constantPoolManager.indexUtf8Info(valuesFieldDescriptor), + attributesCount: 0, + attributes: [] + }) + + // Add $name and $ordinal fields (synthetic, private final) + this.fields.push({ + accessFlags: 0x1002, // private final synthetic + nameIndex: this.constantPoolManager.indexUtf8Info('$name'), + descriptorIndex: this.constantPoolManager.indexUtf8Info('Ljava/lang/String;'), + attributesCount: 0, + attributes: [] + }) + + this.fields.push({ + accessFlags: 0x1002, // private final synthetic + nameIndex: this.constantPoolManager.indexUtf8Info('$ordinal'), + descriptorIndex: this.constantPoolManager.indexUtf8Info('I'), + attributesCount: 0, + attributes: [] + }) + this.handleClassBody(bodyMembers) + + // Add synthetic methods + this.addEnumOrdinalMethod() + this.addEnumNameMethod() + this.addEnumToStringMethod() + this.addEnumValuesMethod(enumConstants) + this.addEnumValueOfMethod(enumConstants) + this.addEnumStaticInitializer(enumConstants) + const constantPool = this.constantPoolManager.getPool() return { magic: MAGIC, @@ -157,6 +216,260 @@ export class Compiler { } } + private addEnumOrdinalMethod() { + // public int ordinal() { return this.$ordinal; } + const nameIndex = this.constantPoolManager.indexUtf8Info('ordinal') + const descriptorIndex = this.constantPoolManager.indexUtf8Info('()I') + const codeAttribute = this.generateSimpleEnumMethod('ordinal', '$ordinal', 'I') + this.methods.push({ + accessFlags: 0x0001, // public + nameIndex: nameIndex, + descriptorIndex: descriptorIndex, + attributesCount: 1, + attributes: [codeAttribute] + }) + // Register in symbol table + this.symbolTable.insertMethodInfo({ + name: 'ordinal', + accessFlags: 0x0001, // public + parentClassName: this.className, + typeDescriptor: '()I', + className: this.className + }) + } + + private addEnumNameMethod() { + // public String name() { return this.$name; } + const nameIndex = this.constantPoolManager.indexUtf8Info('name') + const descriptorIndex = this.constantPoolManager.indexUtf8Info('()Ljava/lang/String;') + const codeAttribute = this.generateSimpleEnumMethod('name', '$name', 'Ljava/lang/String;') + this.methods.push({ + accessFlags: 0x0001, // public + nameIndex: nameIndex, + descriptorIndex: descriptorIndex, + attributesCount: 1, + attributes: [codeAttribute] + }) + // Register in symbol table + this.symbolTable.insertMethodInfo({ + name: 'name', + accessFlags: 0x0001, // public + parentClassName: this.className, + typeDescriptor: '()Ljava/lang/String;', + className: this.className + }) + } + + private addEnumToStringMethod() { + // public String toString() { return this.$name; } + const nameIndex = this.constantPoolManager.indexUtf8Info('toString') + const descriptorIndex = this.constantPoolManager.indexUtf8Info('()Ljava/lang/String;') + const codeAttribute = this.generateSimpleEnumMethod('toString', '$name', 'Ljava/lang/String;') + this.methods.push({ + accessFlags: 0x0001, // public + nameIndex: nameIndex, + descriptorIndex: descriptorIndex, + attributesCount: 1, + attributes: [codeAttribute] + }) + // Register in symbol table + this.symbolTable.insertMethodInfo({ + name: 'toString', + accessFlags: 0x0001, // public + parentClassName: this.className, + typeDescriptor: '()Ljava/lang/String;', + className: this.className + }) + } + + private addEnumValuesMethod(enumConstants: any[]) { + // public static EnumClass[] values() { return $VALUES.clone(); } + const nameIndex = this.constantPoolManager.indexUtf8Info('values') + const descriptorIndex = this.constantPoolManager.indexUtf8Info('()[L' + this.className + ';') + + // Generate bytecode: getstatic $VALUES, invokevirtual clone, areturn + const bytecode: number[] = [] + + // getstatic $VALUES + bytecode.push(0xb2) // getstatic + const valuesFieldRef = this.constantPoolManager.indexFieldrefInfo(this.className, '$VALUES', '[L' + this.className + ';') + bytecode.push((valuesFieldRef >> 8) & 0xff) + bytecode.push(valuesFieldRef & 0xff) + + // invokevirtual Object.clone() + bytecode.push(0xb6) // invokevirtual + const cloneMethodRef = this.constantPoolManager.indexMethodrefInfo('java/lang/Object', 'clone', '()Ljava/lang/Object;') + bytecode.push((cloneMethodRef >> 8) & 0xff) + bytecode.push(cloneMethodRef & 0xff) + + // checkcast to array type + bytecode.push(0xc0) // checkcast + const arrayTypeRef = this.constantPoolManager.indexClassInfo('[L' + this.className + ';') + bytecode.push((arrayTypeRef >> 8) & 0xff) + bytecode.push(arrayTypeRef & 0xff) + + // areturn + bytecode.push(0xb0) + + const codeAttribute: any = { + attributeNameIndex: this.constantPoolManager.indexUtf8Info('Code'), + attributeLength: 12 + bytecode.length, + maxStack: 1, + maxLocals: 0, + codeLength: bytecode.length, + code: bytecode, + exceptionTableLength: 0, + exceptionTable: [], + attributesCount: 0, + attributes: [] + } + + this.methods.push({ + accessFlags: 0x0009, // public static + nameIndex: nameIndex, + descriptorIndex: descriptorIndex, + attributesCount: 1, + attributes: [codeAttribute] + }) + // Register in symbol table + this.symbolTable.insertMethodInfo({ + name: 'values', + accessFlags: 0x0009, // public static + parentClassName: this.className, + typeDescriptor: '()[L' + this.className + ';', + className: this.className + }) + } + + private addEnumValueOfMethod(enumConstants: any[]) { + // public static EnumClass valueOf(String name) { return (EnumClass) Enum.valueOf(EnumClass.class, name); } + const nameIndex = this.constantPoolManager.indexUtf8Info('valueOf') + const descriptorIndex = this.constantPoolManager.indexUtf8Info('(Ljava/lang/String;)L' + this.className + ';') + + const bytecode: number[] = [] + + // ldc EnumClass.class + bytecode.push(0x12) // ldc + const classRefIndex = this.constantPoolManager.indexClassInfo(this.className) + bytecode.push(classRefIndex & 0xff) + + // aload_0 (String name parameter) + bytecode.push(0x19) + bytecode.push(0x00) + + // invokestatic java/lang/Enum.valueOf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; + bytecode.push(0xb8) // invokestatic + const valueOfRef = this.constantPoolManager.indexMethodrefInfo('java/lang/Enum', 'valueOf', '(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;') + bytecode.push((valueOfRef >> 8) & 0xff) + bytecode.push(valueOfRef & 0xff) + + // checkcast to enum type + bytecode.push(0xc0) // checkcast + bytecode.push((classRefIndex >> 8) & 0xff) + bytecode.push(classRefIndex & 0xff) + + // areturn + bytecode.push(0xb0) + + const codeAttribute: any = { + attributeNameIndex: this.constantPoolManager.indexUtf8Info('Code'), + attributeLength: 12 + bytecode.length, + maxStack: 2, + maxLocals: 1, + codeLength: bytecode.length, + code: bytecode, + exceptionTableLength: 0, + exceptionTable: [], + attributesCount: 0, + attributes: [] + } + + this.methods.push({ + accessFlags: 0x0009, // public static + nameIndex: nameIndex, + descriptorIndex: descriptorIndex, + attributesCount: 1, + attributes: [codeAttribute] + }) + // Register in symbol table + this.symbolTable.insertMethodInfo({ + name: 'valueOf', + accessFlags: 0x0009, // public static + parentClassName: this.className, + typeDescriptor: '(Ljava/lang/String;)L' + this.className + ';', + className: this.className + }) + } + + private addEnumStaticInitializer(enumConstants: any[]) { + // Simplified: just create enum constants and populate $VALUES + // Full implementation would be complex bytecode generation + const nameIndex = this.constantPoolManager.indexUtf8Info('') + const descriptorIndex = this.constantPoolManager.indexUtf8Info('()V') + + const bytecode: number[] = [] + + // For now, just return (empty ) + // The JVM will handle basic initialization + bytecode.push(0xb1) // return + + const codeAttribute: any = { + attributeNameIndex: this.constantPoolManager.indexUtf8Info('Code'), + attributeLength: 12 + bytecode.length, + maxStack: 0, + maxLocals: 0, + codeLength: bytecode.length, + code: bytecode, + exceptionTableLength: 0, + exceptionTable: [], + attributesCount: 0, + attributes: [] + } + + this.methods.push({ + accessFlags: 0x0008, // static + nameIndex: nameIndex, + descriptorIndex: descriptorIndex, + attributesCount: 1, + attributes: [codeAttribute] + }) + } + + private generateSimpleEnumMethod(methodName: string, fieldName: string, fieldType: string): any { + // Generate: aload_0, getfield fieldName, return + const bytecode: number[] = [] + + // aload_0 (this) + bytecode.push(0x19) + bytecode.push(0x00) + + // getfield + bytecode.push(0xb4) + const fieldRef = this.constantPoolManager.indexFieldrefInfo(this.className, fieldName, fieldType) + bytecode.push((fieldRef >> 8) & 0xff) + bytecode.push(fieldRef & 0xff) + + // return (areturn for objects, ireturn for int) + if (fieldType === 'I') { + bytecode.push(0xac) // ireturn + } else { + bytecode.push(0xb0) // areturn + } + + return { + attributeNameIndex: this.constantPoolManager.indexUtf8Info('Code'), + attributeLength: 12 + bytecode.length, + maxStack: 1, + maxLocals: 1, + codeLength: bytecode.length, + code: bytecode, + exceptionTableLength: 0, + exceptionTable: [], + attributesCount: 0, + attributes: [] + } + } + private handleClassBody(classBody: Array) { const staticFields: Array = [] const nonStaticFields: Array = [] diff --git a/src/ec-evaluator/utils.ts b/src/ec-evaluator/utils.ts index 244cf79b..05642a20 100644 --- a/src/ec-evaluator/utils.ts +++ b/src/ec-evaluator/utils.ts @@ -376,7 +376,7 @@ export const searchMainMtdClass = (classes: ClassDeclaration[]) => { if (c.kind === 'EnumDeclaration') { return false // Enums can't have main method (they have bodyMembers instead of classBody) } - return (c as NormalClassDeclaration).classBody.some( + return c.classBody.some( d => d.kind === 'MethodDeclaration' && d.methodModifier.includes('public') &&