Skip to content

Enable switch statements#67

Open
AprupKale wants to merge 16 commits into
mainfrom
switch-statements
Open

Enable switch statements#67
AprupKale wants to merge 16 commits into
mainfrom
switch-statements

Conversation

@AprupKale
Copy link
Copy Markdown
Contributor

@AprupKale AprupKale commented Feb 18, 2025

Enable switch statements to support code as follows:

public class Main {
    public static void main(String[] args) {
        int x = 2;
        
        switch (x) {
            case 1:
                System.out.println("One");
                break;
            case 2:
                System.out.println("Two");
                break;
            case 3:
                System.out.println("Three");
                break;
            default:
                System.out.println("Default");
        }
    }
}

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 18, 2025

Coverage report

St.
Category Percentage Covered / Total
🟡 Statements
74.37% (+0.42% 🔼)
7510/10098
🟡 Branches
60.74% (+0.35% 🔼)
2519/4147
🟡 Functions
70.24% (+0.79% 🔼)
1336/1902
🟡 Lines
75.3% (+0.36% 🔼)
7080/9403
Show new covered files 🐣
St.
File Statements Branches Functions Lines
🟢
... / assignmentExpression.test.ts
100% 100% 100% 100%
🟢
... / castExpression.test.ts
100% 100% 100% 100%
🟢
... / switch.test.ts
100% 100% 100% 100%
Show files with reduced coverage 🔻
St.
File Statements Branches Functions Lines
🟡
... / index.ts
70.8% (-3.57% 🔻)
48% (-5.01% 🔻)
91.67% (-3.99% 🔻)
79.3% (-4.94% 🔻)
🟢
... / code-generator.ts
92.24% (-2.17% 🔻)
77.23% (-7.42% 🔻)
95.89% (+2.71% 🔼)
92.33% (-2.42% 🔻)

Test suite run success

1159 tests passing in 64 suites.

Report generated by 🧪jest coverage report action from 91691ad

@AprupKale AprupKale self-assigned this Feb 18, 2025
@AprupKale AprupKale added the enhancement New feature or request label Feb 18, 2025
@AprupKale AprupKale marked this pull request as ready for review February 18, 2025 08:39
@martin-henz
Copy link
Copy Markdown
Member

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for parsing, type-checking, and compiling Java-style CastExpression and SwitchStatement constructs. Key changes include updating the PEG.js grammar, extending AST types, implementing AST extractors, handling implicit and explicit type conversions, and generating JVM bytecode for switch statements (including TABLESWITCH, LOOKUPSWITCH, and string switches). Comprehensive test suites have also been added. The review feedback highlights several critical issues: a bug in the implicit type conversion logic where the in operator is incorrectly used instead of Array.prototype.includes(), unsafe deep property access in the expression extractor that could lead to crashes, and a parsing issue where parseInt is incorrectly applied to character literals in switch cases. Additionally, it is recommended to clean up leftover console.log statements and commented-out code blocks.

Comment on lines +221 to +225
if (!(fromType in ['J', 'D']) && toType in ['J', 'D']) {
return 1;
} else if (!(toType in ['J', 'D']) && fromType in ['J', 'D']) {
return -1;
} else {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The in operator in JavaScript/TypeScript checks for the presence of a property/index in an object or array, not the presence of a value in an array. For example, 'J' in ['J', 'D'] evaluates to false because the array only has indices '0' and '1'. This will cause the implicit type conversion logic to fail or behave incorrectly.

Use Array.prototype.includes() instead to check if the array contains the type.

Suggested change
if (!(fromType in ['J', 'D']) && toType in ['J', 'D']) {
return 1;
} else if (!(toType in ['J', 'D']) && fromType in ['J', 'D']) {
return -1;
} else {
if (!['J', 'D'].includes(fromType) && ['J', 'D'].includes(toType)) {
return 1;
} else if (!['J', 'D'].includes(toType) && ['J', 'D'].includes(fromType)) {
return -1;
} else {

cases.forEach((caseGroup, index) => {
caseGroup.labels.forEach((label) => {
if (label.kind === "CaseLabel") {
const value = parseInt((label.expression as Literal).literalType.value);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using parseInt directly on the literal value will fail for character literals (e.g., 'A'), returning NaN. This will break switch statements over char types.

You should check if the literal is a CharacterLiteral and use charCodeAt(0) to get its integer value, or handle it appropriately based on the literal kind.

Suggested change
const value = parseInt((label.expression as Literal).literalType.value);
const literal = label.expression as Literal;
const value = literal.literalType.kind === "CharacterLiteral"
? literal.literalType.value.charCodeAt(0)
: parseInt(literal.literalType.value);

Comment on lines +234 to +236
if (ctx.primary[0].children.primaryPrefix[0].children.castExpression) {
return this.visit(ctx.primary[0].children.primaryPrefix[0].children.castExpression);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This expression performs deep nested property access (ctx.primary[0].children.primaryPrefix[0].children.castExpression) without checking if the intermediate arrays or objects exist. If ctx.primary is empty, or primaryPrefix is empty/undefined, this will throw a TypeError and crash the parser.

Use optional chaining to safely check for the existence of these properties.

Suggested change
if (ctx.primary[0].children.primaryPrefix[0].children.castExpression) {
return this.visit(ctx.primary[0].children.primaryPrefix[0].children.castExpression);
}
if (ctx.primary?.[0]?.children?.primaryPrefix?.[0]?.children?.castExpression) {
return this.visit(ctx.primary[0].children.primaryPrefix[0].children.castExpression);
}

switchBlockStatementGroup(ctx: SwitchBlockStatementGroupCtx): SwitchCase {
const blockStatementExtractor = new BlockStatementExtractor();

console.log(ctx.switchLabel)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Remove leftover console.log used for debugging to keep the codebase clean.

Comment on lines +158 to +186
// switchLabel(ctx: SwitchLabelCtx): CaseLabel | DefaultLabel {
// // Check if the context contains a "case" label
// if (ctx.caseOrDefaultLabel?.[0]?.children?.Case) {
// const expressionExtractor = new ExpressionExtractor();
// // @ts-ignore
// const expressionCtx = ctx.caseOrDefaultLabel[0].children.caseLabelElement[0]
// .children.caseConstant[0].children.ternaryExpression[0].children;
//
// // Ensure the expression context is valid before proceeding
// if (!expressionCtx) {
// throw new Error("Invalid Case expression in switch label");
// }
//
// const expression = expressionExtractor.ternaryExpression(expressionCtx);
//
// return {
// kind: "CaseLabel",
// expression: expression,
// };
// }
//
// // Check if the context contains a "default" label
// if (ctx.caseOrDefaultLabel?.[0]?.children?.Default) {
// return { kind: "DefaultLabel" };
// }
//
// // Throw an error if the context does not match expected patterns
// throw new Error("Invalid switch label: Neither 'case' nor 'default' found");
// }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid leaving large blocks of commented-out code in the codebase. If this implementation of switchLabel is no longer needed, it should be removed entirely.

Comment on lines +282 to +336
// 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
// }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid leaving large blocks of commented-out code in the codebase. If generateBooleanConversion is no longer needed, it should be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants