From 2f4753974c0f3c5b5db812d178b694dea5d7eb2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Bo=C4=8Fa?= Date: Thu, 18 Jan 2024 22:14:26 +0100 Subject: [PATCH] refactor(all): Complete interpreter refactor --- src/interpreter/interpreter.ts | 45 ++-- src/lexer/lexer.ts | 81 ++++--- src/parser/parser.ts | 342 +++++++++++++++++------------ src/runtime/model/values.ts | 12 +- src/runtime/runtime.ts | 281 ++++++++++++------------ src/symbolizer/symbolizer.types.ts | 0 src/utils/error/error.ts | 6 +- src/utils/formatter.ts | 2 +- 8 files changed, 421 insertions(+), 348 deletions(-) delete mode 100644 src/symbolizer/symbolizer.types.ts diff --git a/src/interpreter/interpreter.ts b/src/interpreter/interpreter.ts index c9337c3..5c2d5ae 100644 --- a/src/interpreter/interpreter.ts +++ b/src/interpreter/interpreter.ts @@ -2,10 +2,9 @@ import { Observable, Subject, Subscription, interval, takeWhile } from "rxjs"; import { Symbol, Symbolizer } from "../symbolizer"; import { Lexer, Token } from "../lexer"; import { Parser, ParserValue, Program } from "../parser"; -import { Runtime, Environment, FunctionCall, NumberValue, RuntimeAgent, RuntimeOutput, RuntimeValue, ValueType } from "../runtime"; +import { Runtime, Environment, FunctionCall, NumberValue, RuntimeAgent, RuntimeOutput, RuntimeValue, ValueType, createGlobalFunction } from "../runtime"; import { Agent, InterpreterConfiguration, InterpreterOutput } from "./model"; import { ErrorModel, ErrorRuntime } from "../utils"; -import { createGlobalFunction } from "../runtime/functions/utils"; export class Interpreter { @@ -22,6 +21,26 @@ export class Interpreter { private program?: Program; + // subscribes to interpreter output + public get(sourceCode: string, config: InterpreterConfiguration): Observable { + this.build(sourceCode, config); + return this.dataSubject.asObservable(); + } + + // returns current program + public getProgram(): Program { + return this.program!; + } + + // sets new program + public setProgram(program: Program): void { + this.program = program; + } + + public updateAgentValue(agentIndex: number, propertyIdentifier: string, value: number): void { + this.runtime?.updateAgentValue(agentIndex, propertyIdentifier, value); + } + public build(sourceCode: string, config: InterpreterConfiguration): void { this.sourceCode = sourceCode; this.config = config; @@ -32,7 +51,7 @@ export class Interpreter { // generate source code tokens this.lexer = new Lexer(symbols); - let tokens: Token[] =this.lexer.tokenize(); + let tokens: Token[] = this.lexer.tokenize(); // generate source code abstract syntax tree this.parser = new Parser(tokens); @@ -52,22 +71,6 @@ export class Interpreter { this.reset(); } - // subscribes to interpreter output - public get(sourceCode: string, config: InterpreterConfiguration): Observable { - this.build(sourceCode, config); - return this.dataSubject.asObservable(); - } - - // returns current program - public getProgram(): Program { - return this.program!; - } - - // sets new program - public setProgram(program: Program): void { - this.program = program; - } - // rebuilds current interpreter step public rebuild(): void { this.runtime?.setProgram(this.program!); @@ -180,8 +183,4 @@ export class Interpreter { return heightFunction; } - - public updateAgentValue(agentIndex: number, propertyIdentifier: string, value: number): void { - this.runtime?.updateAgentValue(agentIndex, propertyIdentifier, value); - } } \ No newline at end of file diff --git a/src/lexer/lexer.ts b/src/lexer/lexer.ts index d61d028..ac348a5 100644 --- a/src/lexer/lexer.ts +++ b/src/lexer/lexer.ts @@ -38,13 +38,21 @@ export class Lexer { break; case "<": { const operator = this.next(); - if (this.isNext("=")) operator.value += this.next().value; + + if (this.isNext("=")) { + operator.value += this.next().value; + } + this.token(TokenType.RelationalOperator, operator); break; } case ">": { const operator = this.next(); - if (this.isNext("=")) operator.value += this.next().value; + + if (this.isNext("=")) { + operator.value += this.next().value; + } + this.token(TokenType.RelationalOperator, operator); break; } @@ -94,33 +102,12 @@ export class Lexer { const { position } = this.getNext(); if (this.isNumber()) { - let number = ""; - let foundDecimalPoint = false; - - while (this.hasNext() && (this.isNumber() || this.isNext("."))) { - if (this.isNext(".")) { - if (foundDecimalPoint) { - throw new ErrorLexer("Number cannot contain more than one decimal point", position); - } - - foundDecimalPoint = true; - } - - number += this.next().value; - } - - this.token(TokenType.Number, { value: number, position }); + this.tokenizeNumber(position); break; } if (this.isAlpha()) { - let identifier = ""; - - while (this.hasNext() && (this.isAlpha() || this.getNext().value === "_")) { - identifier += this.next().value; - } - - this.token(this.getIdentifierTokenType(identifier), { value: identifier, position }); + this.tokenizeIdentifier(position); break; } @@ -139,6 +126,35 @@ export class Lexer { return this.tokens; } + private tokenizeNumber(position: Position): void { + let number = ""; + let foundDecimalPoint = false; + + while (this.hasNext() && (this.isNumber() || this.isNext("."))) { + if (this.isNext(".")) { + if (foundDecimalPoint) { + throw new ErrorLexer("Number cannot contain more than one decimal point", position); + } + + foundDecimalPoint = true; + } + + number += this.next().value; + } + + this.token(TokenType.Number, { value: number, position }); + } + + private tokenizeIdentifier(position: Position): void { + let identifier = ""; + + while (this.hasNext() && (this.isAlpha() || this.getNext().value === "_")) { + identifier += this.next().value; + } + + this.token(this.getKeywordOrIdentifierTokenType(identifier), { value: identifier, position }); + } + private hasNext(): boolean { return this.symbols.length > 0; } @@ -152,7 +168,13 @@ export class Lexer { } private next(): Symbol { - return this.symbols.shift() ?? {} as Symbol; + const symbol = this.symbols.shift(); + + if (!symbol) { + throw new ErrorLexer("Cannot retrieve next token, since it does not exist"); + } + + return symbol; } private token(type: TokenType, symbol = this.next()): void { @@ -171,7 +193,7 @@ export class Lexer { this.token(TokenType.EOF, { value: "EOF", position: eofPosition }); } - private getIdentifierTokenType(identifier: string): TokenType { + private getKeywordOrIdentifierTokenType(identifier: string): TokenType { const keyword = ReservedKeywords[identifier]; return keyword ? keyword : TokenType.Identifier; } @@ -181,13 +203,14 @@ export class Lexer { } private isNumber(): boolean { - const symbol: number = this.getNext().value.charCodeAt(0); + const symbol = this.getNext().value.charCodeAt(0); const bounds = { lower: "0".charCodeAt(0), upper: "9".charCodeAt(0) }; return symbol >= bounds.lower && symbol <= bounds.upper; } private isSkippable(): boolean { - return this.getNext().value === " " || this.getNext().value === "\n" || this.getNext().value === "\t"; + const skippableCharacters = [ " ", "\n", "\t" ]; + return skippableCharacters.includes(this.getNext().value); } private clearTokens(): void { diff --git a/src/parser/parser.ts b/src/parser/parser.ts index 94c3ef5..dc21cc9 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -13,11 +13,11 @@ export class Parser { } public parse(): Program { - const program: Program = this.createEmptyProgram(); + const program = this.createEmptyProgram(); while (this.notEndOfFile()) { - const statement: ParserValue = this.parseStatement(); - program.body.push(statement as Statement); + const statement = this.parseStatement(); + program.body.push(statement); } return getProgram(program); @@ -25,65 +25,67 @@ export class Parser { private parseStatement(): Statement { switch (this.at().type) { - case TokenType.Agent: - return this.parseObjectDeclaration(); case TokenType.Define: return this.parseDefineDeclaration(); + case TokenType.Agent: + return this.parseObjectDeclaration(); default: - throw new ErrorParser("Only agent and define declarations are allowed in program scope", this.position()); + throw new ErrorParser(`Only agent and define declarations are allowed in program scope, '${this.at().type}' provided`, this.position()); } } private parseDefineDeclaration(): DefineDeclaration { if (this.isNotOf(TokenType.Define)) { - throw new ErrorParser("Expected define keyword in program scope", this.position()); + throw new ErrorParser("Expected define keyword in define declaration", this.position()); } const { position } = this.next(); if (this.isNotOf(TokenType.Identifier)) { - throw new ErrorParser("Expected define identifier after define keyword", this.position()); + throw new ErrorParser("Expected identifier after define keyword in define declaration", this.position()); } const identifier = this.next().value; if (this.isNotOf(TokenType.AssignmentOperator)) { - throw new ErrorParser("Expected equals sign after identifier in define declaration", this.position()); + throw new ErrorParser("Expected assignment symbol after identifier in define declaration", this.position()); } this.next(); - const value: ParserValue = this.parseExpression(); + const value = this.parseExpression(); if (this.isNotOf(TokenType.Semicolon)) { - throw new ErrorParser("Expected a semicolon after variable declaration", this.position()); + throw new ErrorParser("Expected a semicolon after value in define declaration", this.position()); } this.next(); - return { + const defineDeclaration: DefineDeclaration = { type: NodeType.DefineDeclaration, identifier, value, position - } as DefineDeclaration; + }; + + return defineDeclaration; } private parseObjectDeclaration(): ObjectDeclaration { if (this.isNotOf(TokenType.Agent)) { - throw new ErrorParser("Expected agent keyword in program scope", this.position()); + throw new ErrorParser("Expected agent keyword in agent declaration", this.position()); } const { position } = this.next(); if (this.isNotOf(TokenType.Identifier)) { - throw new ErrorParser("Expected agent identifier after agent keyword", this.position()); + throw new ErrorParser("Expected agent identifier after agent keyword in agent declaration", this.position()); } const identifier = this.next().value; if (this.isNotOf(TokenType.Number) && this.isNotOf(TokenType.Identifier)) { - throw new ErrorParser("Expected number of agents after agent identifier", this.position()); + throw new ErrorParser("Expected number of agents after agent identifier in agent declaration", this.position()); } const count = this.parseExpression(); @@ -96,50 +98,52 @@ export class Parser { const body: VariableDeclaration[] = []; - while (this.at().type !== TokenType.CloseBrace) { + while (this.isNotOf(TokenType.CloseBrace)) { switch (this.at().type) { case TokenType.Property: case TokenType.Const: - const declaration: ParserValue = this.parseVariableDeclaration(); - body.push(declaration as VariableDeclaration); + const declaration = this.parseVariableDeclaration(); + body.push(declaration); break; default: - throw new ErrorParser("Only variable declarations are allowed in agent body", this.position()); + throw new ErrorParser("Only property and const declarations are allowed in agent body in agent declaration", this.position()); } } if (this.isNotOf(TokenType.CloseBrace)) { - throw new ErrorParser("Expected a close brace after agent body declaration", this.position()); + throw new ErrorParser("Expected a close brace after agent body in agent declaration", this.position()); } this.next(); - return { + const objectDeclaration: ObjectDeclaration = { type: NodeType.ObjectDeclaration, identifier, count, body, position - } as ObjectDeclaration; + }; + + return objectDeclaration; } public parseVariableDeclaration(): VariableDeclaration { if (this.isNotOf(TokenType.Property) && this.isNotOf(TokenType.Const)) { - throw new ErrorParser("Expected property or const keyword at the beginning of variable declaration", this.position()); + throw new ErrorParser("Expected property or const keyword in property declaration", this.position()); } const { position, type } = this.next(); if (this.isNotOf(TokenType.Identifier)) { - throw new ErrorParser("Expected identifier after variable type in variable declaration", this.position()); + throw new ErrorParser("Expected identifier after property type in property declaration", this.position()); } const identifier = this.next().value; - let defaultValue: ParserValue | undefined; + let defaultValue: Expression | undefined; if (this.at().type === TokenType.Colon) { if (type === TokenType.Const) { - throw new ErrorParser("Const properties cannot have a default value", this.at().position); + throw new ErrorParser("Const property cannot have a default value", this.at().position); } this.next(); @@ -147,7 +151,7 @@ export class Parser { } if (this.isNotOf(TokenType.AssignmentOperator)) { - throw new ErrorParser("Expected equals sign after identifier in variable declaration", this.position()); + throw new ErrorParser("Expected assignment symbol after identifier in property declaration", this.position()); } this.next(); @@ -155,27 +159,21 @@ export class Parser { const value: ParserValue = this.parseExpression(); if (this.isNotOf(TokenType.Semicolon)) { - throw new ErrorParser("Expected a semicolon after variable declaration", this.position()); + throw new ErrorParser("Expected a semicolon after value in property declaration", this.position()); } this.next(); - function getVariableType() { - if (type === TokenType.Property) { - return VariableType.Property; - } else if (type === TokenType.Const) { - return VariableType.Const; - } - } - - return { + const variableDeclaration: VariableDeclaration = { type: NodeType.VariableDeclaration, - variableType: getVariableType(), + variableType: this.getVariableType(type), identifier, value, default: defaultValue, position - } as VariableDeclaration; + }; + + return variableDeclaration; } private parseExpression(): Expression { @@ -187,15 +185,16 @@ export class Parser { if (this.at().type === TokenType.Otherwise) { const { position } = this.next(); - const right = this.parseLambdaExpression(); - return { + const otherwiseExpression: OtherwiseExpression = { type: NodeType.OtherwiseExpression, left, right, position - } as OtherwiseExpression; + } + + return otherwiseExpression; } return left; @@ -209,20 +208,21 @@ export class Parser { const param = this.next().value; if (this.isNotOf(TokenType.LambdaArrow)) { - throw new ErrorParser("Expected a lambda arrow after param in lambda expression", this.position()); + throw new ErrorParser("Expected a lambda arrow after parameter in lambda expression", this.position()); } const { position } = this.next(); - const value = this.parseConditionalExpression(); - return { + const lambdaExpression: LambdaExpression = { type: NodeType.LambdaExpression, base, param, value, position, - } as LambdaExpression; + }; + + return lambdaExpression; } return base; @@ -235,7 +235,7 @@ export class Parser { const condition = this.parseExpression(); if (this.isNotOf(TokenType.Then)) { - throw new ErrorParser("Expected then keyword after if condition", this.position()); + throw new ErrorParser("Expected then keyword after condition in conditional expression", this.position()); } this.next(); @@ -243,20 +243,22 @@ export class Parser { const consequent = this.parseExpression(); if (this.isNotOf(TokenType.Else)) { - throw new ErrorParser("Expected else keyword after then consequent", this.position()); + throw new ErrorParser("Expected else keyword after consequent in conditional expression", this.position()); } this.next(); const alternate = this.parseExpression(); - return { + const conditionalExpression: ConditionalExpression = { type: NodeType.ConditionalExpression, condition, consequent, alternate, position - } as ConditionalExpression; + }; + + return conditionalExpression; } return this.parseLogicalExpression(); @@ -358,12 +360,14 @@ export class Parser { const value = this.parseCallExpression(); - return { + const memberExpression: MemberExpression = { type: NodeType.MemberExpression, caller, value, position - } as MemberExpression; + }; + + return memberExpression; } return caller; @@ -373,37 +377,38 @@ export class Parser { const caller = this.parsePrimaryExpression(); if (this.at().type === TokenType.OpenParen) { - const args: ParserValue[] = this.parseArguments(); + const args: Expression[] = this.parseCallExpressionArguments(); - return { + const callExpression: CallExpression = { type: NodeType.CallExpression, caller, args, position: caller.position - } as CallExpression; + }; + + return callExpression; } return caller; } - private parseArguments(): Expression[] { + private parseCallExpressionArguments(): Expression[] { if (this.isNotOf(TokenType.OpenParen)) { - throw new ErrorParser("Expected an open parenthesis before function arguments", this.position()); + throw new ErrorParser("Expected an open parenthesis before function arguments in call expression", this.position()); } this.next(); - let args: ParserValue[]; + let args: Expression[]; if (this.at().type === TokenType.CloseParen) { args = []; } else { - const argsList = this.parseArgumentsList(); - args = argsList; + args = this.parseCallExpressionArgumentsList(); } if (this.isNotOf(TokenType.CloseParen)) { - throw new ErrorParser("Expected a closing parenthesis after function arguments", this.position()); + throw new ErrorParser("Expected a closing parenthesis after function arguments in call expression", this.position()); } this.next(); @@ -411,103 +416,130 @@ export class Parser { return args; } - private parseArgumentsList(): Expression[] { + private parseCallExpressionArgumentsList(): Expression[] { const firstArg = this.parseExpression(); - const args = [firstArg]; while (this.at().type === TokenType.Comma && this.next()) { - const nextArg = this.parseExpression(); - - args.push(nextArg); + const arg = this.parseExpression(); + args.push(arg); } return args; } private parsePrimaryExpression(): Expression { - const token = this.at(); + switch (this.at().type) { + case TokenType.Identifier: + return this.parseIdentifier(); + case TokenType.Number: + return this.parseNumericLiteral(); + case TokenType.Boolean: + return this.parseBooleanLiteral(); + case TokenType.BinaryOperator: + return this.parseNegativeNumericLiteral(); + case TokenType.UnaryOperator: + return this.parseNegativeBooleanLiteral(); + case TokenType.OpenParen: + return this.parseParenthesisedExpression(); + default: + throw new ErrorParser("Unexpected token found during parsing: " + this.at().value, this.position()); + } + } - switch (token.type) { - case TokenType.Identifier: { - this.next(); + private parseIdentifier(): Identifier { + const { value, position } = this.next(); - return { - type: NodeType.Identifier, - identifier: token.value, - position: token.position, - } as Identifier; - } - case TokenType.Number: { - this.next(); - - return { - type: NodeType.NumericLiteral, - value: parseFloat(token.value), - position: token.position - } as NumericLiteral; - } - // parse negative numbers - case TokenType.BinaryOperator: { - if (token.value !== "+" && token.value !== "-") { - throw new ErrorParser("Unary expression requires operator + or -.", this.position()); - } - - if (this.getNext().type !== TokenType.Number && this.getNext().type !== TokenType.Identifier) { - throw new ErrorParser("Unary expression requires value of type number or identifier", this.position()); - } - - this.next(); - - return { - type: NodeType.UnaryExpression, - operator: token.value, - value: this.parsePrimaryExpression(), - position: token.position - } as UnaryExpression; - } - case TokenType.UnaryOperator: { - if (token.value !== "!") { - throw new ErrorParser("Unary expression requires operator !.", this.position()); - } - - if (this.getNext().type !== TokenType.Boolean && this.getNext().type !== TokenType.Identifier) { - throw new ErrorParser("Unary expression requires value of type boolean or identifier", this.position()); - } - - this.next(); - - return { - type: NodeType.UnaryExpression, - operator: token.value, - value: this.parsePrimaryExpression(), - position: token.position - } as UnaryExpression; - } - case TokenType.Boolean: { - this.next(); - - return { - type: NodeType.BooleanLiteral, - value: token.value === "true", - position: token.position - } as BooleanLiteral; - } - case TokenType.OpenParen: { - this.next(); - const value: ParserValue = this.parseExpression(); + const identifier: Identifier = { + type: NodeType.Identifier, + identifier: value, + position, + }; + + return identifier; + } - if (this.isNotOf(TokenType.CloseParen)) { - throw new ErrorParser("Expected a closing parenthesis after an opening parenthesis", this.position()); - } + private parseNumericLiteral(): NumericLiteral { + const { value, position } = this.next(); - this.next(); - - return value; - } - default: - throw new ErrorParser("Unexpected token found during parsing: " + this.at().value, this.position()); + const numericLiteral: NumericLiteral = { + type: NodeType.NumericLiteral, + value: parseFloat(value), + position: position + }; + + return numericLiteral; + } + + private parseNegativeNumericLiteral(): UnaryExpression { + const { value, position } = this.at(); + + if (value !== "+" && value !== "-") { + throw new ErrorParser("Unary expression requires operator + or -", this.position()); + } + + if (this.getNext().type !== TokenType.Number && this.getNext().type !== TokenType.Identifier) { + throw new ErrorParser("Unary expression requires value of type number or identifier", this.position()); + } + + this.next(); + + const unaryExpression: UnaryExpression = { + type: NodeType.UnaryExpression, + operator: value, + value: this.parsePrimaryExpression(), + position + }; + + return unaryExpression; + } + + private parseBooleanLiteral(): BooleanLiteral { + const { value, position } = this.next(); + + const booleanLiteral: BooleanLiteral = { + type: NodeType.BooleanLiteral, + value: value === "true", + position: position + }; + + return booleanLiteral; + } + + private parseNegativeBooleanLiteral(): UnaryExpression { + const { value, position } = this.at(); + + if (value !== "!") { + throw new ErrorParser("Unary expression requires operator !", this.position()); + } + + if (this.getNext().type !== TokenType.Boolean && this.getNext().type !== TokenType.Identifier) { + throw new ErrorParser("Unary expression requires value of type boolean or identifier", this.position()); + } + + this.next(); + + const unaryExpression: UnaryExpression = { + type: NodeType.UnaryExpression, + operator: value, + value: this.parsePrimaryExpression(), + position + }; + + return unaryExpression; + } + + private parseParenthesisedExpression(): Expression { + this.next(); + const value: Expression = this.parseExpression(); + + if (this.isNotOf(TokenType.CloseParen)) { + throw new ErrorParser("Expected a closing parenthesis after an opening parenthesis", this.position()); } + + this.next(); + + return value; } private at(): Token { @@ -535,10 +567,28 @@ export class Parser { } private notEndOfFile() { - return this.tokens[0].type !== TokenType.EOF; + return this.at().type !== TokenType.EOF; + } + + private getVariableType(tokenType: TokenType): VariableType { + switch (tokenType) { + case TokenType.Property: + return VariableType.Property; + case TokenType.Const: + return VariableType.Const; + default: + throw new ErrorParser(`Unkown property type '${tokenType}'`); + } } private createEmptyProgram(): Program { - return { type: NodeType.Program, body: [], position: { line: 0, character: 0 } }; + return { + type: NodeType.Program, + body: [], + position: { + line: 0, + character: 0 + } + }; } } \ No newline at end of file diff --git a/src/runtime/model/values.ts b/src/runtime/model/values.ts index 21def13..3a11ccc 100644 --- a/src/runtime/model/values.ts +++ b/src/runtime/model/values.ts @@ -26,12 +26,6 @@ export interface FunctionValue extends RuntimeValue { call: FunctionCall; } -export interface RuntimeAgent { - id: string; - identifier: string; - variables: Map; -} - export interface NullValue extends RuntimeValue { type: ValueType.Null; } @@ -52,6 +46,12 @@ export interface LambdaValue extends RuntimeValue { results: RuntimeValue[]; } +export interface RuntimeAgent { + id: string; + identifier: string; + variables: Map; +} + export interface RuntimeOutput extends RuntimeValue { type: ValueType.Output; step: number; diff --git a/src/runtime/runtime.ts b/src/runtime/runtime.ts index 3801bef..a5b8b1c 100644 --- a/src/runtime/runtime.ts +++ b/src/runtime/runtime.ts @@ -28,7 +28,6 @@ export class Runtime { this.lambdaEnvironment = new Environment(); } - // run code public run(step: number): RuntimeValue { this.output.step = step; this.provideDataToStepFunction(step); @@ -43,83 +42,82 @@ export class Runtime { return result; } - // set new program - public setProgram(program: Program): void { - this.program = program; - } - - // reset output - public reset(): void { - this.output = { type: ValueType.Output, step: 0, agents: [] }; - } - - // update specific agent variable value - public updateAgentValue(agentIndex: number, propertyIdentifier: string, value: number): void { - this.previousAgents[agentIndex].variables.set(propertyIdentifier, { type: ValueType.Number, value } as NumberValue); - } - private evaluateProgram(program: Program): RuntimeValue { for (const statement of program.body) { switch (statement.type) { - case NodeType.DefineDeclaration: { + case NodeType.DefineDeclaration: const defineDeclaration: DefineDeclaration = this.getDefineDeclaration(statement); - const { identifier, value, position } = defineDeclaration; - - let result: RuntimeValue; - - switch (value.type) { - case NodeType.NumericLiteral: - result = this.evaluateNumericLiteral(value as NumericLiteral); - break - case NodeType.BooleanLiteral: - result = this.evaluateBooleanLiteral(value as BooleanLiteral); - break; - default: - throw new ErrorRuntime(`Only numeric and boolean literals are allowed in define declaration`, position); - } - - this.globalEnvironment.declareVariable(identifier, result); + this.evaluateDefineDeclaration(defineDeclaration); break; - } - case NodeType.ObjectDeclaration: { + case NodeType.ObjectDeclaration: const objectDeclaration: ObjectDeclaration = this.getObjectDeclaration(statement); + this.evaluateObjectDeclarationList(objectDeclaration); + break; + default: + throw new ErrorRuntime(`Only object and define declarations are allowed in program body`, statement.position); + } + } - const count = this.evaluateAgentCount(objectDeclaration.count); + return this.output; + } - if (count.type !== ValueType.Number) { - throw new ErrorRuntime("Agent count is not a number", objectDeclaration.position); - } + private evaluateDefineDeclaration(declaration: DefineDeclaration): void { + const { identifier, value, position } = declaration; - for (let i = 0; i < (count as NumberValue).value; i++) { - const agentId = this.generateAgentId(objectDeclaration.identifier, i); + let result: RuntimeValue; - this.provideDataToIndexFunction(i); - this.provideDataToAgentsFunction(this.previousAgents, agentId); + switch (value.type) { + case NodeType.NumericLiteral: + result = this.evaluateNumericLiteral(value as NumericLiteral); + break; + case NodeType.BooleanLiteral: + result = this.evaluateBooleanLiteral(value as BooleanLiteral); + break; + default: + throw new ErrorRuntime(`Only numeric and boolean literals are allowed in define declaration`, position); + } - this.evaluateObjectDeclaration(objectDeclaration, agentId); - } + this.globalEnvironment.declareVariable(identifier, result); + } - break; - } - default: - throw new ErrorRuntime(`Only object and define declarations are allowed in program body`, statement.position); - } + private evaluateObjectDeclarationList(declaration: ObjectDeclaration): void { + let count: RuntimeValue; + + switch (declaration.count.type) { + case NodeType.NumericLiteral: + count = this.evaluateNumericLiteral(declaration.count as NumericLiteral); + break; + case NodeType.Identifier: + count = this.evaluateGlobalIdentifier(declaration.count as Identifier); + break; + default: + throw new ErrorRuntime(`Unknown runtime node '${declaration.count.type}' in agent count`, declaration.count.position); } - return this.output; + if (count.type !== ValueType.Number) { + throw new ErrorRuntime("Agent count is not a number", declaration.position); + } + + for (let i = 0; i < (count as NumberValue).value; i++) { + const agentId = this.generateAgentId(declaration.identifier, i); + + this.provideDataToIndexFunction(i); + this.provideDataToAgentsFunction(this.previousAgents, agentId); + + this.evaluateObjectDeclaration(declaration, agentId); + } } private evaluateObjectDeclaration(declaration: ObjectDeclaration, id: string): void { - const identifier = declaration.identifier; - const variables = new Map(); + const objectIdentifier = declaration.identifier; + const objectVariables = new Map(); - // declare agent identifier as global variable for later use in 'agents' method - this.globalEnvironment.declareVariable(identifier, { type: ValueType.Identifier, value: identifier } as IdentifierValue); + this.globalEnvironment.declareVariable(objectIdentifier, { type: ValueType.Identifier, value: objectIdentifier } as IdentifierValue); if (this.output.step === 0) { - this.previousAgents.push({ id, identifier, variables } as RuntimeAgent); + this.previousAgents.push({ id, identifier: objectIdentifier, variables: objectVariables } as RuntimeAgent); } else { - this.output.agents.push({ id, identifier, variables } as RuntimeAgent); + this.output.agents.push({ id, identifier: objectIdentifier, variables: objectVariables } as RuntimeAgent); } for (const statement of declaration.body) { @@ -128,50 +126,53 @@ export class Runtime { const variableIdentifier = variableDeclaration.identifier; const variableValue = this.evaluateVariableDeclaration(variableDeclaration, id); - variables.set(variableIdentifier, variableValue); + objectVariables.set(variableIdentifier, variableValue); } } private evaluateVariableDeclaration(declaration: VariableDeclaration, id: string): RuntimeValue { switch (declaration.variableType) { - case VariableType.Property: { - // handle default value in step 0 - if (declaration.default && this.output.step === 0) { - return this.evaluateRuntimeValue(declaration.default, id); - } - - return this.evaluateRuntimeValue(declaration.value, id); + case VariableType.Property: + return this.evaluatePropertyDeclaration(declaration, id); + case VariableType.Const: + return this.evaluateConstDeclaration(declaration, id); + default: { + throw new ErrorRuntime("Unrecognized property type in property declaration", declaration.position); } - case VariableType.Const: { - // evaluate const in step 0 - if (this.output.step === 0) { - return this.evaluateRuntimeValue(declaration.value, id); - } + } + } - const agent = this.findAgent(id); - const previousConstValue: RuntimeValue | undefined = agent.variables.get(declaration.identifier); + private evaluatePropertyDeclaration(declaration: VariableDeclaration, id: string): RuntimeValue { + if (declaration.default && this.output.step === 0) { + return this.evaluateRuntimeValue(declaration.default, id); + } - if (!previousConstValue) { - throw new ErrorRuntime("Previous value of const `${declaration.identifier}` in agent `${id}` not found", declaration.position); - } + return this.evaluateRuntimeValue(declaration.value, id); + } - return previousConstValue; - } - default: { - throw new ErrorRuntime("Unrecognized variable type in variable declaration", declaration.position); - } + private evaluateConstDeclaration(declaration: VariableDeclaration, id: string): RuntimeValue { + if (this.output.step === 0) { + return this.evaluateRuntimeValue(declaration.value, id); + } + + const agent = this.findAgent(id); + const previousConstValue = agent.variables.get(declaration.identifier); + + if (!previousConstValue) { + throw new ErrorRuntime(`Previous value of const '${declaration.identifier}' in agent '${id}' not found`, declaration.position); } + + return previousConstValue; } - // main method private evaluateRuntimeValue(node: ParserValue, id: string): RuntimeValue { switch (node.type) { + case NodeType.Identifier: + return this.evaluateIdentifier(node as Identifier, id); case NodeType.NumericLiteral: return this.evaluateNumericLiteral(node as NumericLiteral); case NodeType.BooleanLiteral: return this.evaluateBooleanLiteral(node as BooleanLiteral); - case NodeType.Identifier: - return this.evaluateIdentifier(node as Identifier, id); case NodeType.BinaryExpression: return this.evaluateBinaryExpression(node as BinaryExpression, id); case NodeType.UnaryExpression: @@ -193,52 +194,42 @@ export class Runtime { } } - private evaluateAgentCount(node: ParserValue): RuntimeValue { - switch (node.type) { - case NodeType.NumericLiteral: - return this.evaluateNumericLiteral(node as NumericLiteral); - case NodeType.Identifier: - return this.evaluateGlobalIdentifier(node as Identifier); - default: - throw new ErrorRuntime(`Unknown runtime node '${node.type}' in agent count`, node.position); - } - } - private evaluateNumericLiteral(numericLiteral: NumericLiteral): RuntimeValue { - return { + const numberValue: NumberValue = { type: ValueType.Number, value: normalizeNumber(numericLiteral.value) - } as NumberValue; + }; + + return numberValue; } private evaluateBooleanLiteral(booleanLiteral: BooleanLiteral): RuntimeValue { - return { + const booleanValue: BooleanValue = { type: ValueType.Boolean, value: booleanLiteral.value - } as BooleanValue; + }; + + return booleanValue; } private evaluateIdentifier(identifier: Identifier, id: string): RuntimeValue { - // check whether identifier is a lambda variable const lambdaLookup = this.lambdaEnvironment.lookupVariable(identifier.identifier); if (lambdaLookup) { return lambdaLookup; } - // check whether identifier is an agent variable const variableLookup = this.globalEnvironment.lookupVariable(identifier.identifier); if (variableLookup) { return variableLookup; } const agent = this.findAgent(id); - const variableValue: RuntimeValue | undefined = agent.variables.get(identifier.identifier); - - if (!variableValue) { - throw new ErrorRuntime(`Variable '${identifier.identifier}' in agent '${id}' does not exist`, identifier.position); + const variableValue = agent.variables.get(identifier.identifier); + if (variableValue) { + return variableValue; } - return variableValue; + throw new ErrorRuntime(`Variable '${identifier.identifier}' in agent '${id}' does not exist`, identifier.position); } private evaluateGlobalIdentifier(identifier: Identifier): RuntimeValue { @@ -255,14 +246,14 @@ export class Runtime { const leftHandSide = this.evaluateRuntimeValue(expression.left, id); const rightHandSide = this.evaluateRuntimeValue(expression.right, id); - // handle otherwise expression if (this.inOtherwiseExpression && leftHandSide.type === ValueType.Null) { - return leftHandSide; - } - - // handle otherwise expression - if (this.inOtherwiseExpression && rightHandSide.type === ValueType.Null) { - return rightHandSide; + if (leftHandSide.type === ValueType.Null) { + return leftHandSide; + } + + if (rightHandSide.type === ValueType.Null) { + return rightHandSide; + } } const isValid = ( @@ -288,10 +279,9 @@ export class Runtime { } private evaluateUnaryExpression(expression: UnaryExpression, id: string): RuntimeValue { - const operator = expression.operator; + const { operator } = expression; const value = this.evaluateRuntimeValue(expression.value, id); - // handle otherwise expression if (this.inOtherwiseExpression && value.type === ValueType.Null) { return value; } @@ -330,6 +320,8 @@ export class Runtime { result = leftHandSide.value === rightHandSide.value; } else if (operator === "!=") { result = leftHandSide.value !== rightHandSide.value; + } else { + throw new ErrorRuntime(`Unsupported operator '${operator}' in binary expression`); } return { type: ValueType.Boolean, value: result } as BooleanValue; @@ -365,39 +357,38 @@ export class Runtime { const leftHandSide = this.evaluateRuntimeValue(expression.left, id); const rightHandSide = this.evaluateRuntimeValue(expression.right, id); - // handle otherwise expression if (this.inOtherwiseExpression && leftHandSide.type === ValueType.Null) { - return leftHandSide; + if (leftHandSide.type === ValueType.Null) { + return leftHandSide; + } + + if (rightHandSide.type === ValueType.Null) { + return rightHandSide; + } } - // handle otherwise expression - if (this.inOtherwiseExpression && rightHandSide.type === ValueType.Null) { - return rightHandSide; + if (leftHandSide.type !== ValueType.Boolean || rightHandSide.type !== ValueType.Boolean) { + throw new ErrorRuntime("Logical expression requires boolean operands", expression.position); } - if (leftHandSide.type === ValueType.Boolean && rightHandSide.type === ValueType.Boolean) { - let result; + let result; - if (expression.operator === "and") { - const leftValue = (leftHandSide as BooleanValue).value; - const rightValue = (rightHandSide as BooleanValue).value; - result = leftValue && rightValue; - } else if (expression.operator === "or") { - const leftValue = (leftHandSide as BooleanValue).value; - const rightValue = (rightHandSide as BooleanValue).value; - result = leftValue || rightValue; - } - - return { type: ValueType.Boolean, value: result } as BooleanValue; + if (expression.operator === "and") { + const leftValue = (leftHandSide as BooleanValue).value; + const rightValue = (rightHandSide as BooleanValue).value; + result = leftValue && rightValue; + } else if (expression.operator === "or") { + const leftValue = (leftHandSide as BooleanValue).value; + const rightValue = (rightHandSide as BooleanValue).value; + result = leftValue || rightValue; } - throw new ErrorRuntime("Logical expression requires boolean operands", expression.position); + return { type: ValueType.Boolean, value: result } as BooleanValue; } private evaluateConditionalExpression(expression: ConditionalExpression, id: string): RuntimeValue { const condition = this.evaluateRuntimeValue(expression.condition, id); - // handle otherwise expression if (this.inOtherwiseExpression && condition.type === ValueType.Null) { return condition; } @@ -407,12 +398,13 @@ export class Runtime { } const result = (condition as BooleanValue).value ? expression.consequent : expression.alternate; + return this.evaluateRuntimeValue(result, id); } private evaluateCallExpression(expression: CallExpression, id: string): RuntimeValue { if (expression.caller.type !== NodeType.Identifier) { - throw new ErrorRuntime("Function call must be an identifier", expression.position); + throw new ErrorRuntime("Function caller must be an identifier", expression.position); } const identifier = (expression.caller as Identifier).identifier; @@ -447,17 +439,18 @@ export class Runtime { results.push(this.evaluateRuntimeValue(expression.value, id)); } - return { + const lambdaValue: LambdaValue = { type: ValueType.Lambda, agents: runtimeAgents, results - } as LambdaValue; + }; + + return lambdaValue; } private evaluateMemberExpression(expression: MemberExpression, id: string): RuntimeValue { const caller = this.evaluateRuntimeValue(expression.caller, id); - // handle otherwise expression if (this.inOtherwiseExpression && caller.type === ValueType.Null) { return caller; } @@ -541,6 +534,18 @@ export class Runtime { return `${identifier}-${id}`; } + public setProgram(program: Program): void { + this.program = program; + } + + public reset(): void { + this.output = { type: ValueType.Output, step: 0, agents: [] }; + } + + public updateAgentValue(agentIndex: number, propertyIdentifier: string, value: number): void { + this.previousAgents[agentIndex].variables.set(propertyIdentifier, { type: ValueType.Number, value } as NumberValue); + } + // runtime functions private provideDataToIndexFunction(index: number): void { diff --git a/src/symbolizer/symbolizer.types.ts b/src/symbolizer/symbolizer.types.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/utils/error/error.ts b/src/utils/error/error.ts index e810ef4..14265cc 100644 --- a/src/utils/error/error.ts +++ b/src/utils/error/error.ts @@ -4,9 +4,9 @@ type ErrorType = "Lexer" | "Parser" | "Runtime"; export class ErrorModel extends Error { + private type: ErrorType; private about: string; private position?: Position; - private type: ErrorType; constructor(type: ErrorType, about: string, position?: Position) { super(about); @@ -23,8 +23,4 @@ export class ErrorModel extends Error { return `${this.type} Error (line ${this.position.line}, character ${this.position.character}): ${this.about}`; } - - public throw(): void { - console.log(this.toString()); - } } \ No newline at end of file diff --git a/src/utils/formatter.ts b/src/utils/formatter.ts index 2da8e3c..4e80b18 100644 --- a/src/utils/formatter.ts +++ b/src/utils/formatter.ts @@ -11,7 +11,7 @@ export class Formatter { public static getFormatted(sourceCode: string): string { Formatter.line = 1; - const program: Program = Formatter.getProgram(sourceCode); + const program = Formatter.getProgram(sourceCode); return Formatter.nodeToSourceCode(program) }