From c1aa3a1759a4cf8dcbd43c397a148952a6fb99d4 Mon Sep 17 00:00:00 2001 From: xyliew25 Date: Mon, 29 Jan 2024 14:48:13 +0800 Subject: [PATCH 1/7] Make variableInitiazlier optional E.g., int x; --- src/ast/types/blocks-and-statements.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast/types/blocks-and-statements.ts b/src/ast/types/blocks-and-statements.ts index 2ee5c51..0c616e4 100644 --- a/src/ast/types/blocks-and-statements.ts +++ b/src/ast/types/blocks-and-statements.ts @@ -63,7 +63,7 @@ export type LocalVariableType = UnannType; export interface VariableDeclarator { kind: "VariableDeclarator"; variableDeclaratorId: VariableDeclaratorId; - variableInitializer: VariableInitializer; + variableInitializer?: VariableInitializer; } export type VariableDeclaratorId = Identifier; From e1fd8fd858ac89e57ee437e626d33844db8b37c3 Mon Sep 17 00:00:00 2001 From: xyliew25 Date: Mon, 29 Jan 2024 14:50:53 +0800 Subject: [PATCH 2/7] Extract Assignment E.g., x = 1; y = 2 + 3; z = x; --- .../astExtractor/block-statement-extractor.ts | 56 +++++++++++++++---- src/ast/types/ast.ts | 2 + 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/ast/astExtractor/block-statement-extractor.ts b/src/ast/astExtractor/block-statement-extractor.ts index 10c1141..5994a7d 100644 --- a/src/ast/astExtractor/block-statement-extractor.ts +++ b/src/ast/astExtractor/block-statement-extractor.ts @@ -4,6 +4,9 @@ import { BinaryExpressionCtx, BlockStatementCstNode, ExpressionCtx, + FqnOrRefTypeCtx, + FqnOrRefTypePartCommonCtx, + FqnOrRefTypePartFirstCtx, IToken, IntegerLiteralCtx, IntegralTypeCtx, @@ -22,12 +25,14 @@ import { BinaryExpression, BlockStatement, Expression, + ExpressionName, } from "../types/blocks-and-statements"; export class BlockStatementExtractor extends BaseJavaCstVisitorWithDefaults { private type: UnannType; private identifier: Identifier; private value: Expression; + private name: ExpressionName; constructor() { super(); @@ -35,17 +40,26 @@ export class BlockStatementExtractor extends BaseJavaCstVisitorWithDefaults { extract(cst: BlockStatementCstNode): BlockStatement { this.visit(cst); - return { - kind: "LocalVariableDeclarationStatement", - localVariableType: this.type, - variableDeclaratorList: [ - { - kind: "VariableDeclarator", - variableDeclaratorId: this.identifier, - variableInitializer: this.value, - } - ], - }; + if (cst.children.localVariableDeclarationStatement) { + return { + kind: "LocalVariableDeclarationStatement", + localVariableType: this.type, + variableDeclaratorList: [ + { + kind: "VariableDeclarator", + variableDeclaratorId: this.identifier, + variableInitializer: this.value, + }, + ], + }; + } else { + return { + kind: "Assignment", + left: this.name, + operator: "=", + right: this.value, + }; + } } integralType(ctx: IntegralTypeCtx) { @@ -77,6 +91,9 @@ export class BlockStatementExtractor extends BaseJavaCstVisitorWithDefaults { binaryExpression(ctx: BinaryExpressionCtx) { if (ctx.BinaryOperator && ctx.BinaryOperator.length > 0) { return this.makeBinaryExpression(ctx.BinaryOperator, ctx.unaryExpression); + } else if (ctx.AssignmentOperator && ctx.expression) { + this.value = this.visit(ctx.expression); + this.name = this.visit(ctx.unaryExpression[0]); } else { return this.visit(ctx.unaryExpression[0]); } @@ -183,9 +200,26 @@ export class BlockStatementExtractor extends BaseJavaCstVisitorWithDefaults { return this.visit(ctx.literal); } else if (ctx.parenthesisExpression) { return this.visit(ctx.parenthesisExpression); + } else if (ctx.fqnOrRefType) { + return this.visit(ctx.fqnOrRefType); } } + fqnOrRefType(ctx: FqnOrRefTypeCtx) { + return this.visit(ctx.fqnOrRefTypePartFirst); + } + + fqnOrRefTypePartFirst(ctx: FqnOrRefTypePartFirstCtx) { + return this.visit(ctx.fqnOrRefTypePartCommon); + } + + fqnOrRefTypePartCommon(ctx: FqnOrRefTypePartCommonCtx) { + return ctx.Identifier && { + kind: "ExpressionName", + name: ctx.Identifier[0].image, + }; + } + literal(ctx: LiteralCtx) { if (ctx.integerLiteral) { return this.visit(ctx.integerLiteral); diff --git a/src/ast/types/ast.ts b/src/ast/types/ast.ts index 4197e0a..e708a85 100644 --- a/src/ast/types/ast.ts +++ b/src/ast/types/ast.ts @@ -1,5 +1,6 @@ import { CompilationUnit } from "./packages-and-modules"; import { + Assignment, Block, BlockStatement, Expression, @@ -10,6 +11,7 @@ interface NodeMap { Block: Block; BlockStatement: BlockStatement; Expression: Expression; + Assignment: Assignment; } export type Node = NodeMap[keyof NodeMap]; From 69ce7334fa5f61c2bef19e49d2de3154266a9a2e Mon Sep 17 00:00:00 2001 From: xyliew25 Date: Mon, 29 Jan 2024 17:50:13 +0800 Subject: [PATCH 3/7] Add yarn build --- .gitignore | 1 + package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 2f93cd5..394d454 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # compiled output /dist /node_modules +tsconfig.tsbuildinfo # Logs logs diff --git a/package.json b/package.json index 13907bd..c99347f 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "repository": "https://github.com/source-academy/java-slang.git", "license": "Apache-2.0", "scripts": { + "build": "tsc --build --force", "test": "jest", "test:watch": "jest --watch" }, From 547a621f555b02dc687dfc243aa33e55a35420d0 Mon Sep 17 00:00:00 2001 From: xyliew25 Date: Mon, 29 Jan 2024 18:13:05 +0800 Subject: [PATCH 4/7] Fix evaluateBinaryExpression() --- src/ec-evaluator/utils.ts | 48 +++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/ec-evaluator/utils.ts b/src/ec-evaluator/utils.ts index cb8de1c..8b5cfa4 100644 --- a/src/ec-evaluator/utils.ts +++ b/src/ec-evaluator/utils.ts @@ -169,32 +169,50 @@ export function defineVariable( return environment } +/** + * Binary Expressions + */ export const evaluateBinaryExpression = (operator: string, left: Literal, right: Literal) => { switch (operator) { - case '+': + case "+": return { - type: "Literal", - value: left.value + right.value + kind: "Literal", + literalType: { + kind: left.literalType.kind, + value: String(Number(left.literalType.value) + Number(right.literalType.value)), + }, }; - case '-': + case "-": return { - type: "Literal", - value: left.value - right.value + kind: "Literal", + literalType: { + kind: left.literalType.kind, + value: String(Number(left.literalType.value) - Number(right.literalType.value)), + }, }; - case '*': + case "*": return { - type: "Literal", - value: left.value * right.value + kind: "Literal", + literalType: { + kind: left.literalType.kind, + value: String(Number(left.literalType.value) * Number(right.literalType.value)), + }, }; - case '/': + case "/": return { - type: "Literal", - value: left.value / right.value + kind: "Literal", + literalType: { + kind: left.literalType.kind, + value: String(Number(left.literalType.value) / Number(right.literalType.value)), + }, }; - case '%': + case "%": return { - type: "Literal", - value: left.value % right.value + kind: "Literal", + literalType: { + kind: left.literalType.kind, + value: String(Number(left.literalType.value) % Number(right.literalType.value)), + }, }; default: return undefined; From 71e28ad97035fbc6369efbf073d881a5e90a799a Mon Sep 17 00:00:00 2001 From: xyliew25 Date: Mon, 29 Jan 2024 18:42:09 +0800 Subject: [PATCH 5/7] Add runtime errors --- src/ec-evaluator/errors.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/ec-evaluator/errors.ts diff --git a/src/ec-evaluator/errors.ts b/src/ec-evaluator/errors.ts new file mode 100644 index 0000000..bdc6bab --- /dev/null +++ b/src/ec-evaluator/errors.ts @@ -0,0 +1,27 @@ +export interface RuntimeError { + explain(): string; +}; + +export class UndeclaredVariableError implements RuntimeError { + constructor(private name: string) {}; + + public explain() { + return `Name ${this.name} not declared.`; + } +} + +export class UnassignedVariableError implements RuntimeError { + constructor(private name: string) {}; + + public explain() { + return `Name ${this.name} not definitely assigned.`; + } +} + +export class VariableRedeclarationError implements RuntimeError { + constructor(private name: string) {}; + + public explain() { + return `Name ${this.name} redeclared.`; + } +} From a0021316dbbebd75d26a12c0486297ce0f706872 Mon Sep 17 00:00:00 2001 From: xyliew25 Date: Mon, 29 Jan 2024 18:47:23 +0800 Subject: [PATCH 6/7] Evaluate Assignment - Rename Agenda to Control - Simplify Environment - Evaluate until target step - Support Assignment - Break down LocalVariableDeclarationStatement with VariableInitializer - Return Promise --- src/ec-evaluator/createContext.ts | 72 --------- src/ec-evaluator/index.ts | 36 +++++ src/ec-evaluator/instrCreator.ts | 8 +- src/ec-evaluator/interpreter.ts | 261 ++++++++++++++++-------------- src/ec-evaluator/nodeCreator.ts | 37 +++++ src/ec-evaluator/types.ts | 61 ++++--- src/ec-evaluator/utils.ts | 117 +++++--------- 7 files changed, 284 insertions(+), 308 deletions(-) delete mode 100644 src/ec-evaluator/createContext.ts create mode 100644 src/ec-evaluator/index.ts create mode 100644 src/ec-evaluator/nodeCreator.ts diff --git a/src/ec-evaluator/createContext.ts b/src/ec-evaluator/createContext.ts deleted file mode 100644 index 5b0e135..0000000 --- a/src/ec-evaluator/createContext.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Context, Environment } from "./types" - -export class EnvTree { - private _root: EnvTreeNode | null = null - private map = new Map() - - get root(): EnvTreeNode | null { - return this._root - } - - public insert(environment: Environment): void { - const tailEnvironment = environment.tail - if (tailEnvironment === null) { - if (this._root === null) { - this._root = new EnvTreeNode(environment, null) - this.map.set(environment, this._root) - } - } else { - const parentNode = this.map.get(tailEnvironment) - if (parentNode) { - const childNode = new EnvTreeNode(environment, parentNode) - parentNode.addChild(childNode) - this.map.set(environment, childNode) - } - } - } - - public getTreeNode(environment: Environment): EnvTreeNode | undefined { - return this.map.get(environment) - } -} - -export class EnvTreeNode { - private _children: EnvTreeNode[] = [] - - constructor(readonly environment: Environment, public parent: EnvTreeNode | null) {} - - get children(): EnvTreeNode[] { - return this._children - } - - public resetChildren(newChildren: EnvTreeNode[]): void { - this.clearChildren() - this.addChildren(newChildren) - newChildren.forEach(c => (c.parent = this)) - } - - private clearChildren(): void { - this._children = [] - } - - private addChildren(newChildren: EnvTreeNode[]): void { - this._children.push(...newChildren) - } - - public addChild(newChild: EnvTreeNode): EnvTreeNode { - this._children.push(newChild) - return newChild - } -} - -export const createContext = (): Context => { - return { - runtime: { - environmentTree: new EnvTree(), - environments: [], - nodes: [], - agenda: null, - stash: null, - }, - } -} diff --git a/src/ec-evaluator/index.ts b/src/ec-evaluator/index.ts new file mode 100644 index 0000000..5fa572f --- /dev/null +++ b/src/ec-evaluator/index.ts @@ -0,0 +1,36 @@ +import { parse } from "../ast/parser"; +import { Control, Environment, evaluate, Stash } from "./interpreter"; +import { Context, Error, Finished, Result } from "./types"; + +export const runECEvaluator = ( + files: Partial>, + entrypointFilePath: string, + context: Context, + targetStep: number = Infinity, +): Promise => { + try { + const code = files[entrypointFilePath]; + const compilationUnit = parse(code!); + + context.control.push(compilationUnit!); + const value = evaluate(context, targetStep); + + return new Promise((resolve, _) => { + resolve({ status: 'finished', context, value } as Finished); + }); + } catch { + return new Promise((resolve, _) => { + resolve({ status: 'error' } as Error); + }); + } +} + +export const createContext = (): Context => ({ + errors: [], + + control: new Control(), + stash: new Stash(), + environment: new Environment(), + + totalSteps: Infinity, +}); diff --git a/src/ec-evaluator/instrCreator.ts b/src/ec-evaluator/instrCreator.ts index 9e4b835..38a210e 100644 --- a/src/ec-evaluator/instrCreator.ts +++ b/src/ec-evaluator/instrCreator.ts @@ -1,16 +1,12 @@ -import { Node } from "../ast/types/ast" -import { AssmtInstr, BinOpInstr, Instr, InstrType } from "./types" +import { Node } from "../ast/types/ast"; +import { AssmtInstr, BinOpInstr, Instr, InstrType } from "./types"; export const assmtInstr = ( symbol: string, - constant: boolean, - declaration: boolean, srcNode: Node ): AssmtInstr => ({ instrType: InstrType.ASSIGNMENT, symbol, - constant, - declaration, srcNode }) diff --git a/src/ec-evaluator/interpreter.ts b/src/ec-evaluator/interpreter.ts index ece06f4..0406b45 100644 --- a/src/ec-evaluator/interpreter.ts +++ b/src/ec-evaluator/interpreter.ts @@ -1,175 +1,188 @@ import { - BinaryExpression, - Literal, - LocalVariableDeclarationStatement, - VariableDeclarator + Assignment, + BinaryExpression, + Expression, + ExpressionName, + Literal, + LocalVariableDeclarationStatement, + LocalVariableType, + VariableDeclarator, } from "../ast/types/blocks-and-statements"; +import { Identifier } from "../ast/types/classes"; import { CompilationUnit } from "../ast/types/packages-and-modules"; +import * as instr from './instrCreator'; +import * as node from './nodeCreator'; +import { + ControlItem, + AssmtInstr, + BinOpInstr, + Context, + Instr, + InstrType, + Value, + Name, +} from "./types"; import { - Stack, - createBlockEnvironment, - declareVariables, - defineVariable, - evaluateBinaryExpression, - handleSequence, - isNode, - pushEnvironment + Stack, + declareVariable, + evaluateBinaryExpression, + getVariable, + handleSequence, + isNode, + setVariable, } from "./utils"; -import { AgendaItem, AssmtInstr, BinOpInstr, Context, Instr, InstrType, Value } from "./types"; -import { Identifier } from "../ast/types/classes"; -import * as instr from './instrCreator' -import { createContext } from "./createContext"; type CmdEvaluator = ( - command: AgendaItem, + command: ControlItem, context: Context, - agenda: Agenda, + control: Control, stash: Stash, ) => void /** - * The agenda is a list of commands that still needs to be executed by the machine. - * It contains syntax tree nodes or instructions. + * Components of CSE Machine. */ -export class Agenda extends Stack { - public constructor(compilationUnit: CompilationUnit) { - super(); +export class Control extends Stack {}; +export class Stash extends Stack {}; +export class Environment extends Map {}; - // Load compilationUnit into agenda stack - this.push(compilationUnit); - } -} +export const evaluate = (context: Context, targetStep: number = Infinity): Value => { + const control = context.control; + const stash = context.stash; -/** - * The stash is a list of values that stores intermediate results. - */ -export class Stash extends Stack { - public constructor() { - super() - } -} - -/** - * The primary runner/loop of the explicit control evaluator. - * - * @param context The context to evaluate the program in. - * @param agenda Points to the current context.runtime.agenda - * @param stash Points to the current context.runtime.stash - * @returns A special break object if the program is interrupted by a breakpoint; - * else the top value of the stash. It is usually the return value of the program. - */ -export const evaluate = (compilationUnit: CompilationUnit): [any, AgendaItem[], any[]] => { - const context = createContext(); - const agenda = new Agenda(compilationUnit); - const stash = new Stash(); + let step = 1; - let command = agenda.peek() - - // console.log("Agenda: ", agenda); - // console.log("Stash: ", stash); - // console.log("Environment: ", context.runtime.environments) + let command = control.peek(); while (command) { - agenda.pop() + if (step === targetStep) { + return stash.peek(); + } + + control.pop(); if (isNode(command)) { - cmdEvaluators[command.kind](command, context, agenda, stash) + cmdEvaluators[command.kind](command, context, control, stash); } else { - // Command is an instrucion - cmdEvaluators[command.instrType](command, context, agenda, stash) + cmdEvaluators[command.instrType](command, context, control, stash); } - // console.log("----------------------------------------------------------------------------") - // console.log("Agenda: ", agenda); - // console.log("Stash: ", stash); - // console.log("Environment: ", context.runtime.environments) - - command = agenda.peek() + command = control.peek(); + step += 1; } - return [stash.peek(), agenda.getTrace(), stash.getTrace()] + context.totalSteps = step; + return stash.peek(); } -/** - * Dictionary of functions which handle the logic for the response of the three registers of - * the ASE machine to each AgendaItem. - */ const cmdEvaluators: { [type: string]: CmdEvaluator } = { - /** - * Statements - */ - - CompilationUnit: (command: CompilationUnit, context:Context, agenda: Agenda, stash: Stash) => { - // Create and push the environment only if it is non empty. - const environment = createBlockEnvironment(context, 'mainFuncEnvironment') - pushEnvironment(context, environment) - declareVariables(context, command, environment) - - if (command.topLevelClassOrInterfaceDeclarations[0].classBody[0].methodBody.length == 1) { + CompilationUnit: ( + command: CompilationUnit, + context: Context, + control: Control, + stash: Stash, + ) => { + if (command.topLevelClassOrInterfaceDeclarations[0].classBody[0].methodBody.blockStatements.length == 1) { // If program only consists of one statement, evaluate it immediately - const next = command.topLevelClassOrInterfaceDeclarations[0].classBody[0].methodBody[0] - cmdEvaluators[next.kind](next, context, agenda, stash) - - // console.log("----------------------------------------------------------------------------") - // console.log("Agenda: ", agenda); - // console.log("Stash: ", stash); - // console.log("Environment: ", context.runtime.environments) - + const next = command.topLevelClassOrInterfaceDeclarations[0].classBody[0].methodBody.blockStatements[0]; + cmdEvaluators[next.kind](next, context, control, stash) } else { // Push block body - agenda.push(...handleSequence(command.topLevelClassOrInterfaceDeclarations[0].classBody[0].methodBody)) + control.push(...handleSequence(command.topLevelClassOrInterfaceDeclarations[0].classBody[0].methodBody.blockStatements)); } }, - LocalVariableDeclarationStatement: function ( + LocalVariableDeclarationStatement: ( command: LocalVariableDeclarationStatement, - context:Context, - agenda: Agenda, + context: Context, + control: Control, stash: Stash, - ) { - const declaration: VariableDeclarator = command.variableDeclarationList - const id = declaration.variableDeclaratorId as Identifier - const init = declaration.variableInitializer - - agenda.push(instr.popInstr(command)) - agenda.push(instr.assmtInstr(id, false, true, command)) - agenda.push(init) + ) => { + const type: LocalVariableType = command.localVariableType; + const declaration: VariableDeclarator = command.variableDeclaratorList[0]; + const id: Identifier = declaration.variableDeclaratorId; + + // Break down LocalVariableDeclarationStatement with VariableInitializer into + // LocalVariableDeclarationStatement without VariableInitializer and Assignment. + const init: Expression | undefined = declaration?.variableInitializer; + if (init) { + control.push(node.assmtNode(id, init)); + control.push(node.localVarDeclNoInitNode(type, id)); + return; + } + + // Evaluating LocalVariableDeclarationStatement just declares the variable. + declareVariable(context, id); }, - Literal: (command: Literal, context:Context, agenda: Agenda, stash: Stash) => { + Assignment: ( + command: Assignment, + context: Context, + control: Control, + stash: Stash, + ) => { + // Assignment is an ExpressionStatement + control.push(instr.popInstr(command)); + control.push(instr.assmtInstr(command.left.name, command)); + // TODO: EVAL_VAR LeftHandSide + control.push(command.right); + }, + + Literal: ( + command: Literal, + context: Context, + control: Control, + stash: Stash, + ) => { stash.push(command); }, - BinaryExpression: function (command: BinaryExpression, context: Context, agenda: Agenda, stash: Stash) { - agenda.push(instr.binOpInstr(command.operator, command)) - agenda.push(command.right) - agenda.push(command.left) + ExpressionName: ( + command: ExpressionName, + context: Context, + control: Control, + stash: Stash, + ) => { + stash.push(getVariable(context, command.name)); + }, + + BinaryExpression: ( + command: BinaryExpression, + context: Context, + control: Control, + stash: Stash + ) => { + control.push(instr.binOpInstr(command.operator, command)); + control.push(command.right); + control.push(command.left); }, - /** - * Instructions - */ - [InstrType.POP]: function (command: Instr, context: Context, agenda: Agenda, stash: Stash) { - stash.pop() + [InstrType.POP]: ( + command: Instr, + context: Context, + control: Control, + stash: Stash, + ) => { + stash.pop(); }, - [InstrType.ASSIGNMENT]: function ( + [InstrType.ASSIGNMENT]: ( command: AssmtInstr, context: Context, - agenda: Agenda, - stash: Stash - ) { - defineVariable(context, command.symbol, stash.peek(), command.constant, - command.srcNode as LocalVariableDeclarationStatement) + control: Control, + stash: Stash, + ) => { + // TODO: LeftHandSide to be popped after implementing EVAL_VAR LeftHandSide + setVariable(context, command.symbol, stash.peek()); }, - [InstrType.BINARY_OP]: function ( + [InstrType.BINARY_OP]: ( command: BinOpInstr, context: Context, - agenda: Agenda, - stash: Stash - ) { - const right = stash.pop() - const left = stash.pop() - stash.push(evaluateBinaryExpression(command.symbol, left, right)) + control: Control, + stash: Stash, + ) => { + const right = stash.pop(); + const left = stash.pop(); + stash.push(evaluateBinaryExpression(command.symbol, left, right)); } -} +}; diff --git a/src/ec-evaluator/nodeCreator.ts b/src/ec-evaluator/nodeCreator.ts new file mode 100644 index 0000000..e993035 --- /dev/null +++ b/src/ec-evaluator/nodeCreator.ts @@ -0,0 +1,37 @@ +import { + Assignment, + Expression, + LeftHandSide, + LocalVariableDeclarationStatement, + VariableDeclarator, + VariableDeclaratorId, +} from "../ast/types/blocks-and-statements"; +import { UnannType } from "../ast/types/classes"; + +export const localVarDeclNoInitNode = ( + localVariableType: UnannType, + variableDeclaratorId: VariableDeclaratorId, +): LocalVariableDeclarationStatement => ({ + kind: "LocalVariableDeclarationStatement", + localVariableType, + variableDeclaratorList: [ + { + kind: "VariableDeclarator", + variableDeclaratorId, + } as VariableDeclarator, + ], +}); + +export const assmtNode = ( + left: string, + right: Expression, + operator: string = '=', +): Assignment => ({ + kind: "Assignment", + left: { + kind: "ExpressionName", + name: left, + } as LeftHandSide, + operator, + right, +}); diff --git a/src/ec-evaluator/types.ts b/src/ec-evaluator/types.ts index b0eb60c..c3a9e31 100644 --- a/src/ec-evaluator/types.ts +++ b/src/ec-evaluator/types.ts @@ -1,48 +1,34 @@ import { Node } from "../ast/types/ast"; -import { EnvTree } from "./createContext"; -import { Agenda, Stash } from "./interpreter" - -export interface Context { - /** Runtime Specific state */ - runtime: { - environmentTree: EnvTree - environments: Environment[] - nodes: Node[] - agenda: Agenda | null - stash: Stash | null - } -} +import { Control, Environment, Stash } from "./interpreter"; +import { RuntimeError } from "./errors"; -export interface Environment { - id: string - name: string - tail: Environment | null - head: Frame -} +export interface Context { + errors: RuntimeError[], -export interface Frame { - [name: string]: any -} + control: Control, + stash: Stash, + environment: Environment, + + totalSteps: number, +}; export enum InstrType { - ASSIGNMENT = 'Assignment', + ASSIGNMENT = 'Assign', BINARY_OP = 'BinaryOperation', POP = 'Pop', } interface BaseInstr { - instrType: InstrType - srcNode: Node + instrType: InstrType; + srcNode: Node; } export interface AssmtInstr extends BaseInstr { - symbol: string - constant: boolean - declaration: boolean + symbol: string; } export interface BinOpInstr extends BaseInstr { - symbol: string + symbol: string; } export type Instr = @@ -50,6 +36,19 @@ export type Instr = | AssmtInstr | BinOpInstr; -export type AgendaItem = Node | Instr +export type ControlItem = Node | Instr; + +export type Value = any; +export type Name = string; + +export interface Error { + status: 'error'; +} + +export interface Finished { + status: 'finished'; + context: Context; + value: Value; +} -export type Value = any +export type Result = Finished | Error; diff --git a/src/ec-evaluator/utils.ts b/src/ec-evaluator/utils.ts index 8b5cfa4..9e13f0b 100644 --- a/src/ec-evaluator/utils.ts +++ b/src/ec-evaluator/utils.ts @@ -1,12 +1,7 @@ -import { uniqueId } from "lodash" -import { AgendaItem, Context, Environment, Frame, Instr, Value } from "./types" -import { Node } from "../ast/types/ast" -import { - BlockStatement, - Literal, - LocalVariableDeclarationStatement -} from "../ast/types/blocks-and-statements" -import { CompilationUnit } from "../ast/types/packages-and-modules" +import { Node } from "../ast/types/ast"; +import { BlockStatement, Literal } from "../ast/types/blocks-and-statements"; +import * as errors from "./errors"; +import { ControlItem, Context, Instr, Value, Name } from "./types"; /** * Stack is implemented for agenda and stash registers. @@ -23,14 +18,10 @@ interface IStack { export class Stack implements IStack { // Bottom of the array is at index 0 private storage: T[] = [] - private trace: T[] = [] - - public constructor() {} public push(...items: T[]): void { for (const item of items) { this.storage.push(item); - this.trace.push(item); } } @@ -57,29 +48,25 @@ export class Stack implements IStack { // return a copy of the stack's contents return [...this.storage] } - - public getTrace(): T[] { - return [...this.trace]; - } } /** * Typeguard for Instr to distinguish between program statements and instructions. * - * @param command An AgendaItem - * @returns true if the AgendaItem is an instruction and false otherwise. + * @param command An ControlItem + * @returns true if the ControlItem is an instruction and false otherwise. */ -export const isInstr = (command: AgendaItem): command is Instr => { +export const isInstr = (command: ControlItem): command is Instr => { return (command as Instr).instrType !== undefined } /** * Typeguard for esNode to distinguish between program statements and instructions. * - * @param command An AgendaItem - * @returns true if the AgendaItem is an esNode and false if it is an instruction. + * @param command An ControlItem + * @returns true if the ControlItem is an esNode and false if it is an instruction. */ -export const isNode = (command: AgendaItem): command is Node => { +export const isNode = (command: ControlItem): command is Node => { return (command as Node).kind !== undefined } @@ -92,8 +79,8 @@ export const isNode = (command: AgendaItem): command is Node => { * @param seq Array of statements. * @returns Array of commands to be pushed into agenda. */ -export const handleSequence = (seq: BlockStatement[]): AgendaItem[] => { - const result: AgendaItem[] = [] +export const handleSequence = (seq: BlockStatement[]): ControlItem[] => { + const result: ControlItem[] = [] for (const command of seq) { result.push(command) } @@ -105,68 +92,48 @@ export const handleSequence = (seq: BlockStatement[]): AgendaItem[] => { * Environments */ -export const currentEnvironment = (context: Context) => context.runtime.environments[0] - -export const createBlockEnvironment = ( - context: Context, - name = 'blockEnvironment', - head: Frame = {} -): Environment => { - return { - name, - tail: currentEnvironment(context), - head, - id: uniqueId() +export const currentEnvironment = (context: Context) => context.environment; + +export const DECLARED_BUT_NOT_YET_ASSIGNED = Symbol("Used to implement block scope"); + +export const declareVariable = (context: Context, name: Name) => { + const currEnv = currentEnvironment(context); + + if (currEnv.has(name)) { + throw new errors.VariableRedeclarationError(name); } -} + currEnv.set(name, DECLARED_BUT_NOT_YET_ASSIGNED); -export const pushEnvironment = (context: Context, environment: Environment) => { - context.runtime.environments.unshift(environment) - context.runtime.environmentTree.insert(environment) + return currEnv; } -/** - * Variables - */ +export const getVariable = (context: Context, name: Name) => { + let currEnv = currentEnvironment(context); -const DECLARED_BUT_NOT_YET_ASSIGNED = Symbol('Used to implement block scope') - -export function declareVariables( - context: Context, - node: CompilationUnit, - environment: Environment -) { - for (const statement of node.topLevelClassOrInterfaceDeclarations[0].classBody[0].methodBody) { - if (statement.kind === 'LocalVariableDeclarationStatement') { - if (environment.head.hasOwnProperty(statement.variableDeclarationList.variableDeclaratorId)) { - throw new Error("Variable re-declared."); - } - environment.head[statement.variableDeclarationList.variableDeclaratorId] = DECLARED_BUT_NOT_YET_ASSIGNED + if (currEnv.has(name)) { + // Variables must be definitely assigned prior to access + if (currEnv.get(name) === DECLARED_BUT_NOT_YET_ASSIGNED) { + return handleRuntimeError(context, new errors.UnassignedVariableError(name)); } + return currEnv.get(name); } - return environment + + return handleRuntimeError(context, new errors.UndeclaredVariableError(name)); } -export function defineVariable( - context: Context, - name: string, - value: Value, - constant = false, - node: LocalVariableDeclarationStatement -) { - const environment = currentEnvironment(context) - - if (environment.head[name] !== DECLARED_BUT_NOT_YET_ASSIGNED) { - throw new Error("Variable not declared.") +export const setVariable = (context: Context, name: Name, value: Value) => { + let currEnv = currentEnvironment(context); + + if (!currEnv.has(name)) { + handleRuntimeError(context, new errors.UndeclaredVariableError(name)); } - Object.defineProperty(environment.head, name, { - value, - writable: !constant, - enumerable: true - }) + currEnv.set(name, value); +} - return environment +export const handleRuntimeError = (context: Context, error: errors.RuntimeError) => { + context.errors.push(error); + throw error; } /** From d6d6568149c28339df197ee6137d080a0c90c38a Mon Sep 17 00:00:00 2001 From: xyliew25 Date: Mon, 29 Jan 2024 18:53:52 +0800 Subject: [PATCH 7/7] Update tests - Create Context stubs - Check Environment --- src/ec-evaluator/__tests__/arithmetic.test.ts | 283 ++++++++++++++++++ src/ec-evaluator/__tests__/ec-evaluator.ts | 201 ------------- .../__tests__/local-var-assignment.test.ts | 192 ++++++++++++ src/ec-evaluator/__tests__/utils.ts | 48 +++ 4 files changed, 523 insertions(+), 201 deletions(-) create mode 100644 src/ec-evaluator/__tests__/arithmetic.test.ts delete mode 100644 src/ec-evaluator/__tests__/ec-evaluator.ts create mode 100644 src/ec-evaluator/__tests__/local-var-assignment.test.ts create mode 100644 src/ec-evaluator/__tests__/utils.ts diff --git a/src/ec-evaluator/__tests__/arithmetic.test.ts b/src/ec-evaluator/__tests__/arithmetic.test.ts new file mode 100644 index 0000000..cef631e --- /dev/null +++ b/src/ec-evaluator/__tests__/arithmetic.test.ts @@ -0,0 +1,283 @@ +import { evaluate } from "../interpreter"; +import { parse } from "../../ast/parser" +import { DECLARED_BUT_NOT_YET_ASSIGNED, isNode } from "../utils"; +import { ControlStub, EnvironmentStub, StashStub, createContextStub } from "./utils"; + +it("evaluate local variable declaration to a basic arithmetic operation correctly", () => { + const programStr = ` + public class Test { + public static void main(String[] args) { + int y = 10 % 2; + } + } + `; + + const compilationUnit = parse(programStr); + expect(compilationUnit).toBeTruthy(); + + const context = createContextStub(); + context.control.push(compilationUnit!); + + const result = evaluate(context); + + const expectedAgendaTrace = [ + "CompilationUnit", + "Assignment", + "LocalVariableDeclarationStatement", + "Pop", + "Assign", + "BinaryExpression", + "BinaryOperation", + "Literal", + "Literal" + ]; + const expectedStashTrace = ["10", "2", "0"]; + const expectedEnv = new Map([ + ["y", [ + DECLARED_BUT_NOT_YET_ASSIGNED, + { + kind: "Literal", + literalType: { + kind: "DecimalIntegerLiteral", + value: "0", + }, + }, + ]] + ]); + + expect(result).toEqual(undefined); + expect((context.control as ControlStub).getTrace().map(i => isNode(i) ? i.kind : i.instrType)).toEqual(expectedAgendaTrace); + expect((context.stash as StashStub).getTrace().map(i => i.literalType.value)).toEqual(expectedStashTrace); + expect((context.environment as EnvironmentStub).getTrace()).toEqual(expectedEnv); +}); + +it("evaluate local variable declaration to a complex arithmetic operation correctly", () => { + const programStr = ` + public class Test { + public static void main(String[] args) { + int z = 1 + (2 * 3) - 4; + } + } + `; + + const compilationUnit = parse(programStr); + expect(compilationUnit).toBeTruthy(); + + const context = createContextStub(); + context.control.push(compilationUnit!); + + const result = evaluate(context); + + const expectedAgendaTrace = [ + "CompilationUnit", + "Assignment", + "LocalVariableDeclarationStatement", + "Pop", + "Assign", + "BinaryExpression", + "BinaryOperation", + "Literal", + "BinaryExpression", + "BinaryOperation", + "BinaryExpression", + "Literal", + "BinaryOperation", + "Literal", + "Literal" + ]; + const expectedStashTrace = ["1", "2", "3", "6", "7", "4", "3"]; + const expectedEnv = new Map([ + ["z", [ + DECLARED_BUT_NOT_YET_ASSIGNED, + { + kind: "Literal", + literalType: { + kind: "DecimalIntegerLiteral", + value: "3", + } + }, + ]], + ]); + + expect(result).toEqual(undefined); + expect((context.control as ControlStub).getTrace().map(i => isNode(i) ? i.kind : i.instrType)).toEqual(expectedAgendaTrace); + expect((context.stash as StashStub).getTrace().map(i => i.literalType.value)).toEqual(expectedStashTrace); + expect((context.environment as EnvironmentStub).getTrace()).toEqual(expectedEnv); +}); + +it("evaluate multiple local variable declarations correctly", () => { + const programStr = ` + public class Test { + public static void main(String[] args) { + int x = 1; + int y = 10 % 2; + } + } + `; + + const compilationUnit = parse(programStr); + expect(compilationUnit).toBeTruthy(); + + const context = createContextStub(); + context.control.push(compilationUnit!); + + const result = evaluate(context); + + const expectedAgendaTrace = [ + "CompilationUnit", + "LocalVariableDeclarationStatement", + "LocalVariableDeclarationStatement", + "Assignment", + "LocalVariableDeclarationStatement", + "Pop", + "Assign", + "Literal", + "Assignment", + "LocalVariableDeclarationStatement", + "Pop", + "Assign", + "BinaryExpression", + "BinaryOperation", + "Literal", + "Literal" + ]; + const expectedStashTrace = ["1", "10", "2", "0"]; + const expectedEnv = new Map([ + ["x", [ + DECLARED_BUT_NOT_YET_ASSIGNED, + { + kind: "Literal", + literalType: { + kind: "DecimalIntegerLiteral", + value: "1", + }, + }, + ]], + ["y", [ + DECLARED_BUT_NOT_YET_ASSIGNED, + { + kind: "Literal", + literalType: { + kind: "DecimalIntegerLiteral", + value: "0", + }, + }, + ]], + ]); + + expect(result).toEqual(undefined); + expect((context.control as ControlStub).getTrace().map(i => isNode(i) ? i.kind : i.instrType)).toEqual(expectedAgendaTrace); + expect((context.stash as StashStub).getTrace().map(i => i.literalType.value)).toEqual(expectedStashTrace); + expect((context.environment as EnvironmentStub).getTrace()).toEqual(expectedEnv); +}); + +it("evaluate local variable declaration to a basic arithmetic expression without brackets to enforce precedence correctly", () => { + const programStr = ` + public class Test { + public static void main(String[] args) { + int x = 1 + 2 * 3; + } + } + `; + + const compilationUnit = parse(programStr); + expect(compilationUnit).toBeTruthy(); + + const context = createContextStub(); + context.control.push(compilationUnit!); + + const result = evaluate(context); + + const expectedAgendaTrace = [ + "CompilationUnit", + "Assignment", + "LocalVariableDeclarationStatement", + "Pop", + "Assign", + "BinaryExpression", // 1 + 2 * 3 + "BinaryOperation", // + + "BinaryExpression", // 2 * 3 + "Literal", // 1 + "BinaryOperation", // * + "Literal", // 3 + "Literal" // 2 + ]; + const expectedStashTrace = ["1", "2", "3", "6", "7"]; + const expectedEnv = new Map([ + ["x", [ + DECLARED_BUT_NOT_YET_ASSIGNED, + { + kind: "Literal", + literalType: { + kind: "DecimalIntegerLiteral", + value: "7", + }, + }, + ]], + ]); + + expect(result).toEqual(undefined); + expect((context.control as ControlStub).getTrace().map(i => isNode(i) ? i.kind : i.instrType)).toEqual(expectedAgendaTrace); + expect((context.stash as StashStub).getTrace().map(i => i.literalType.value)).toEqual(expectedStashTrace); + expect((context.environment as EnvironmentStub).getTrace()).toEqual(expectedEnv); +}); + +it("evaluate local variable declaration to a complex arithmetic expression without brackets to enforce precedence correctly", () => { + const programStr = ` + public class Test { + public static void main(String[] args) { + int x = 2 / 1 - 3 * (5 % 4) + 6; + } + } + `; + + const compilationUnit = parse(programStr); + expect(compilationUnit).toBeTruthy(); + + const context = createContextStub(); + context.control.push(compilationUnit!); + + const result = evaluate(context); + + const expectedAgendaTrace = [ + "CompilationUnit", + "Assignment", + "LocalVariableDeclarationStatement", + "Pop", + "Assign", + "BinaryExpression", // 2 / 1 - 3 * (5 % 4) + 6 + "BinaryOperation", // + + "Literal", // 6 + "BinaryExpression", // 2 / 1 - 3 * (5 % 4) + "BinaryOperation", // - + "BinaryExpression", // 3 * (5 % 4) + "BinaryExpression", // 2 / 1 + "BinaryOperation", // / + "Literal", // 1 + "Literal", // 2 + "BinaryOperation", // * + "BinaryExpression", // (5 % 4) + "Literal", // 3 + "BinaryOperation", // % + "Literal", // 4 + "Literal" // 5 + ]; + const expectedStashTrace = ["2", "1", "2", "3", "5", "4", "1", "3", "-1", "6", "5"]; + const expectedEnv = new Map([ + ["x", [ + DECLARED_BUT_NOT_YET_ASSIGNED, + { + kind: "Literal", + literalType: { + kind: "DecimalIntegerLiteral", + value: "5", + }, + }, + ]], + ]); + + expect(result).toEqual(undefined); + expect((context.control as ControlStub).getTrace().map(i => isNode(i) ? i.kind : i.instrType)).toEqual(expectedAgendaTrace); + expect((context.stash as StashStub).getTrace().map(i => i.literalType.value)).toEqual(expectedStashTrace); + expect((context.environment as EnvironmentStub).getTrace()).toEqual(expectedEnv); +}); diff --git a/src/ec-evaluator/__tests__/ec-evaluator.ts b/src/ec-evaluator/__tests__/ec-evaluator.ts deleted file mode 100644 index 35a03f6..0000000 --- a/src/ec-evaluator/__tests__/ec-evaluator.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { evaluate } from "../interpreter"; -import { parse } from "../../ast/parser" -import { isNode } from "../utils"; - -it('evaluate local variable declaration to a literal correctly', () => { - const programStr = ` - public class Test { - public static void main(String[] args) { - int x = 1; - } - } - `; - const compilationUnit = parse(programStr); - if (compilationUnit) { - const [result, agendaTrace, stashTrace] = evaluate(compilationUnit);; - - const expectedAgendaTrace = [ - 'CompilationUnit', - 'Pop', - 'Assignment', - 'Literal' - ]; - const expectedStashTrace = [1]; - - expect(result).toMatchInlineSnapshot(`undefined`); - expect(agendaTrace.map(i => isNode(i) ? i.kind : i.instrType)).toEqual(expectedAgendaTrace); - expect(stashTrace.map(i => i.value)).toEqual(expectedStashTrace); - } -}) - -it('evaluate local variable declaration to a basic arithmetic operation correctly', () => { - const programStr = ` - public class Test { - public static void main(String[] args) { - int y = 10 % 2; - } - } - `; - const compilationUnit = parse(programStr); - if (compilationUnit) { - const [result, agendaTrace, stashTrace] = evaluate(compilationUnit);; - - const expectedAgendaTrace = [ - 'CompilationUnit', - 'Pop', - 'Assignment', - 'BinaryExpression', - 'BinaryOperation', - 'Literal', - 'Literal' - ]; - const expectedStashTrace = [10, 2, 0]; - - expect(result).toMatchInlineSnapshot(`undefined`); - expect(agendaTrace.map(i => isNode(i) ? i.kind : i.instrType)).toEqual(expectedAgendaTrace); - expect(stashTrace.map(i => i.value)).toEqual(expectedStashTrace); - } -}) - -it('evaluate local variable declaration to a complex arithmetic operation correctly', () => { - const programStr = ` - public class Test { - public static void main(String[] args) { - int z = 1 + (2 * 3) - 4; - } - } - `; - const compilationUnit = parse(programStr); - if (compilationUnit) { - const [result, agendaTrace, stashTrace] = evaluate(compilationUnit);; - - const expectedAgendaTrace = [ - 'CompilationUnit', - 'Pop', - 'Assignment', - 'BinaryExpression', - 'BinaryOperation', - 'Literal', - 'BinaryExpression', - 'BinaryOperation', - 'BinaryExpression', - 'Literal', - 'BinaryOperation', - 'Literal', - 'Literal' - ]; - const expectedStashTrace = [1, 2, 3, 6, 7, 4, 3]; - - expect(result).toMatchInlineSnapshot(`undefined`); - expect(agendaTrace.map(i => isNode(i) ? i.kind : i.instrType)).toEqual(expectedAgendaTrace); - expect(stashTrace.map(i => i.value)).toEqual(expectedStashTrace); - } -}) - -it('evaluate multiple local variable declarations correctly', () => { - const programStr = ` - public class Test { - public static void main(String[] args) { - int x = 1; - int y = 10 % 2; - } - } - `; - const compilationUnit = parse(programStr); - if (compilationUnit) { - const [result, agendaTrace, stashTrace] = evaluate(compilationUnit);; - - const expectedAgendaTrace = [ - 'CompilationUnit', - 'LocalVariableDeclarationStatement', - 'LocalVariableDeclarationStatement', - 'Pop', - 'Assignment', - 'Literal', - 'Pop', - 'Assignment', - 'BinaryExpression', - 'BinaryOperation', - 'Literal', - 'Literal' - ]; - const expectedStashTrace = [1, 10, 2, 0]; - - expect(result).toMatchInlineSnapshot(`undefined`); - expect(agendaTrace.map(i => isNode(i) ? i.kind : i.instrType)).toEqual(expectedAgendaTrace); - expect(stashTrace.map(i => i.value)).toEqual(expectedStashTrace); - } -}) - -it('evaluate local variable declaration to a basic arithmetic expression without brackets to enforce precedence correctly', () => { - const programStr = ` - public class Test { - public static void main(String[] args) { - int x = 1 + 2 * 3; - } - } - `; - const compilationUnit = parse(programStr); - if (compilationUnit) { - const [result, agendaTrace, stashTrace] = evaluate(compilationUnit); - - const expectedAgendaTrace = [ - 'CompilationUnit', - 'Pop', - 'Assignment', - 'BinaryExpression', // 1 + 2 * 3 - 'BinaryOperation', // + - 'BinaryExpression', // 2 * 3 - 'Literal', // 1 - 'BinaryOperation', // * - 'Literal', // 3 - 'Literal' // 2 - ]; - const expectedStashTrace = [1, 2, 3, 6, 7]; - - expect(result).toMatchInlineSnapshot(`undefined`); - expect(agendaTrace.map(i => isNode(i) ? i.kind : i.instrType)).toEqual(expectedAgendaTrace); - expect(stashTrace.map(i => i.value)).toEqual(expectedStashTrace); - } -}) - -it('evaluate local variable declaration to a complex arithmetic expression without brackets to enforce precedence correctly', () => { - const programStr = ` - public class Test { - public static void main(String[] args) { - int x = 2 / 1 - 3 * (5 % 4) + 6; - } - } - `; - const compilationUnit = parse(programStr); - if (compilationUnit) { - const [result, agendaTrace, stashTrace] = evaluate(compilationUnit); - - const expectedAgendaTrace = [ - 'CompilationUnit', - 'Pop', - 'Assignment', - 'BinaryExpression', // 2 / 1 - 3 * (5 % 4) + 6 - 'BinaryOperation', // + - 'Literal', // 6 - 'BinaryExpression', // 2 / 1 - 3 * (5 % 4) - 'BinaryOperation', // - - 'BinaryExpression', // 3 * (5 % 4) - 'BinaryExpression', // 2 / 1 - 'BinaryOperation', // / - 'Literal', // 1 - 'Literal', // 2 - 'BinaryOperation', // * - 'BinaryExpression', // (5 % 4) - 'Literal', // 3 - 'BinaryOperation', // % - 'Literal', // 4 - 'Literal' // 5 - ]; - const expectedStashTrace = [2, 1, 2, 3, 5, 4, 1, 3, -1, 6, 5]; - - expect(result).toMatchInlineSnapshot(`undefined`); - expect(agendaTrace.map(i => isNode(i) ? i.kind : i.instrType)).toEqual(expectedAgendaTrace); - expect(stashTrace.map(i => i.value)).toEqual(expectedStashTrace); - } -}) diff --git a/src/ec-evaluator/__tests__/local-var-assignment.test.ts b/src/ec-evaluator/__tests__/local-var-assignment.test.ts new file mode 100644 index 0000000..01b4b66 --- /dev/null +++ b/src/ec-evaluator/__tests__/local-var-assignment.test.ts @@ -0,0 +1,192 @@ +import { parse } from "../../ast/parser"; +import { evaluate } from "../interpreter"; +import { Value } from "../types"; +import { DECLARED_BUT_NOT_YET_ASSIGNED, isNode } from "../utils"; +import { ControlStub, EnvironmentStub, StashStub, createContextStub } from "./utils"; + +it("evaluate LocalVariableDeclarationStatement without variableInitializer correctly", () => { + const programStr = ` + public class Test { + public static void main(String[] args) { + int x; + } + } + `; + + const compilationUnit = parse(programStr); + expect(compilationUnit).toBeTruthy(); + + const context = createContextStub(); + context.control.push(compilationUnit!); + + const result = evaluate(context); + + const expectedControlTrace = [ + "CompilationUnit", + ]; + const expectedStashTrace: Value[] = []; + const expectedEnv = new Map([ + ["x", [ + DECLARED_BUT_NOT_YET_ASSIGNED, + ]], + ]); + + expect(result).toEqual(undefined); + expect((context.control as ControlStub).getTrace().map(i => isNode(i) ? i.kind : i.instrType)).toEqual(expectedControlTrace); + expect((context.stash as StashStub).getTrace().map(i => i.literalType.value)).toEqual(expectedStashTrace); + expect((context.environment as EnvironmentStub).getTrace()).toEqual(expectedEnv); +}); + +it("evaluate LocalVariableDeclarationStatement with variableInitializer correctly", () => { + const programStr = ` + public class Test { + public static void main(String[] args) { + int x = 1; + } + } + `; + + const compilationUnit = parse(programStr); + expect(compilationUnit).toBeTruthy(); + + const context = createContextStub(); + context.control.push(compilationUnit!); + + const result = evaluate(context); + + const expectedControlTrace = [ + "CompilationUnit", + "Assignment", + "LocalVariableDeclarationStatement", + "Pop", + "Assign", + "Literal", + ]; + const expectedStashTrace = ["1"]; + const expectedEnv = new Map([ + ["x", [ + DECLARED_BUT_NOT_YET_ASSIGNED, + { + kind: "Literal", + literalType: { + kind: "DecimalIntegerLiteral", + value: "1", + }, + }, + ]], + ]); + + expect(result).toEqual(undefined); + expect((context.control as ControlStub).getTrace().map(i => isNode(i) ? i.kind : i.instrType)).toEqual(expectedControlTrace); + expect((context.stash as StashStub).getTrace().map(i => i.literalType.value)).toEqual(expectedStashTrace); + expect((context.environment as EnvironmentStub).getTrace()).toEqual(expectedEnv); +}); + +it("evaluate Assignment correctly", () => { + const programStr = ` + public class Test { + public static void main(String[] args) { + int x; + x = 1; + } + } + `; + + const compilationUnit = parse(programStr); + expect(compilationUnit).toBeTruthy(); + + const context = createContextStub(); + context.control.push(compilationUnit!); + + const result = evaluate(context); + + const expectedControlTrace = [ + "CompilationUnit", + "Assignment", + "LocalVariableDeclarationStatement", + "Pop", + "Assign", + "Literal", + ]; + const expectedStashTrace = ["1"]; + const expectedEnv = new Map([ + ["x", [ + DECLARED_BUT_NOT_YET_ASSIGNED, + { + kind: "Literal", + literalType: { + kind: "DecimalIntegerLiteral", + value: "1", + }, + }, + ]], + ]); + + expect(result).toEqual(undefined); + expect((context.control as ControlStub).getTrace().map(i => isNode(i) ? i.kind : i.instrType)).toEqual(expectedControlTrace); + expect((context.stash as StashStub).getTrace().map(i => i.literalType.value)).toEqual(expectedStashTrace); + expect((context.environment as EnvironmentStub).getTrace()).toEqual(expectedEnv); +}); + +it("evaluate LocalVariableDeclarationStatement with local variable as variableInitializer correctly", () => { + const programStr = ` + public class Test { + public static void main(String[] args) { + int x = 1; + int y = x; + } + } + `; + + const compilationUnit = parse(programStr); + expect(compilationUnit).toBeTruthy(); + + const context = createContextStub(); + context.control.push(compilationUnit!); + + const result = evaluate(context); + + const expectedControlTrace = [ + "CompilationUnit", + "LocalVariableDeclarationStatement", + "LocalVariableDeclarationStatement", + "Assignment", + "LocalVariableDeclarationStatement", + "Pop", + "Assign", + "Literal", + "Assignment", + "LocalVariableDeclarationStatement", + "Pop", + "Assign", + "ExpressionName", + ]; + const expectedStashTrace = ["1", "1"]; + const expectedEnv = new Map([ + ["x", [ + DECLARED_BUT_NOT_YET_ASSIGNED, + { + kind: "Literal", + literalType: { + kind: "DecimalIntegerLiteral", + value: "1", + }, + }, + ]], + ["y", [ + DECLARED_BUT_NOT_YET_ASSIGNED, + { + kind: "Literal", + literalType: { + kind: "DecimalIntegerLiteral", + value: "1", + }, + }, + ]], + ]); + + expect(result).toEqual(undefined); + expect((context.control as ControlStub).getTrace().map(i => isNode(i) ? i.kind : i.instrType)).toEqual(expectedControlTrace); + expect((context.stash as StashStub).getTrace().map(i => i.literalType.value)).toEqual(expectedStashTrace); + expect((context.environment as EnvironmentStub).getTrace()).toEqual(expectedEnv); +}); diff --git a/src/ec-evaluator/__tests__/utils.ts b/src/ec-evaluator/__tests__/utils.ts new file mode 100644 index 0000000..54a3053 --- /dev/null +++ b/src/ec-evaluator/__tests__/utils.ts @@ -0,0 +1,48 @@ +import { Environment } from "../interpreter"; +import { ControlItem, Context, Name, Value } from "../types"; +import { Stack } from "../utils"; + +export class StackStub extends Stack { + private trace: T[] = []; + + public push(...items: T[]): void { + for (const item of items) { + super.push(item); + this.trace.push(item); + } + }; + + public getTrace(): T[] { + return this.trace; + }; +}; +export class ControlStub extends StackStub {}; +export class StashStub extends StackStub {}; + +export class EnvironmentStub extends Environment { + private trace = new Map(); + + public set(key: Name, value: Value): this { + super.set(key, value); + + if (this.trace.has(key)) { + this.trace.set(key, [...this.trace.get(key)!, value]); + } else { + this.trace.set(key, [value]); + } + + return this; + } + + public getTrace(): Map { + return this.trace; + } +} + +export const createContextStub = (): Context => ({ + errors: [], + control: new ControlStub(), + stash: new StashStub(), + environment: new EnvironmentStub(), + totalSteps: Infinity, +});