From e41e21a42f7e179aacc92dede59f50d2a8255910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Bo=C4=8Fa?= Date: Wed, 17 Apr 2024 21:28:40 +0200 Subject: [PATCH] chore(docs): Added docstrings to all methods, classes and interfaces --- src/interpreter/interpreter.ts | 109 +++++++-- src/interpreter/model/agent.ts | 5 + src/interpreter/model/configuration.ts | 7 + src/interpreter/model/exit-status.ts | 5 + src/interpreter/model/output.ts | 10 + src/lexer/keywords.ts | 3 + src/lexer/lexer.ts | 51 ++++- src/lexer/model/token.ts | 6 + src/parser/model/dependency-graph.ts | 8 + src/parser/model/node-type.ts | 2 - src/parser/model/nodes.ts | 97 +++++++- src/parser/model/variable-type.ts | 5 + src/parser/parser-util.ts | 40 +++- src/parser/parser.ts | 177 +++++++++++++- src/parser/topology.ts | 26 +-- src/parser/validation.ts | 8 +- src/runtime/environment.ts | 12 +- src/runtime/functions/utils.ts | 64 ++++++ src/runtime/model/values.ts | 43 ++++ src/runtime/runtime.ts | 304 +++++++++++++++++++++---- src/symbolizer/model/position.ts | 5 + src/symbolizer/model/symbol.ts | 5 + src/utils/error/error.ts | 5 + src/utils/formatter.ts | 21 +- 24 files changed, 915 insertions(+), 103 deletions(-) diff --git a/src/interpreter/interpreter.ts b/src/interpreter/interpreter.ts index 03a2cb8..8079bc5 100644 --- a/src/interpreter/interpreter.ts +++ b/src/interpreter/interpreter.ts @@ -25,11 +25,11 @@ export class Interpreter { private program?: Program; /** - * Generates an observable used for retrieving the interpreter's output + * Returns an observable emitting interpreter's output on each step * - * @param sourceCode - source code of the simulation - * @param config - configuration of the interpreter - * @returns observable holding the interpreter's output + * @param sourceCode source code of the simulation + * @param config configuration of the interpreter + * @returns observable holding the interpreter's output subscription */ public get(sourceCode: string, config: InterpreterConfiguration): Observable { try { @@ -40,7 +40,7 @@ export class Interpreter { code: 1, message: (error as ErrorModel).toString() } - }) + }); } return this.dataSubject.asObservable(); @@ -49,8 +49,8 @@ export class Interpreter { /** * Builds the interpreter and initializes the simulation * - * @param sourceCode - source code to be initialized and built upon - * @param config - configuration of the interpreter + * @param sourceCode source code to be initialized and built + * @param config configuration of the interpreter */ public build(sourceCode: string, config: InterpreterConfiguration): void { this.sourceCode = sourceCode; @@ -81,32 +81,50 @@ export class Interpreter { this.reset(); } + /** + * Rebuilds the interpreter + */ public rebuild(): void { this.runtime?.setProgram(this.program!); this.currentStep--; this.step(); } - public start() { + /** + * Starts the interpreter + */ + public start(): void { this.currentStep = 0; this.subscribe(); } - public reset() { + /** + * Resets the interpreter + */ + public reset(): void { this.unsubscribe(); this.currentStep = 0; this.runtime?.reset(); } - public pause() { + /** + * Pauses the interpreter + */ + public pause(): void { this.unsubscribe(); } - public resume() { + /** + * Resumes the interpreter + */ + public resume(): void { this.subscribe(); } - public step() { + /** + * Steps through the interpreter, performs one step + */ + public step(): void { if (this.currentStep >= this.config.steps + 1) { return; } @@ -114,29 +132,58 @@ export class Interpreter { this.dataSubject.next(this.getInterpreterOutput(this.currentStep++)); } + /** + * Returns the parsed program AST node + * + * @returns program AST node + */ public getProgram(): Program { return this.program!; } + /** + * Updates the program AST node in the interpreter + * + * @param program new program AST node + */ public setProgram(program: Program): void { this.program = program; } + /** + * Updates an agent's value in the simulation + * + * @param agentIndex index of the agent to update + * @param propertyIdentifier identifier of the agent's property to update + * @param value new value to update + */ public updateAgentValue(agentIndex: number, propertyIdentifier: string, value: number): void { this.runtime?.updateAgentValue(agentIndex, propertyIdentifier, value); } + /** + * Subscribes to the interpreter's runtime changes + */ private subscribe(): void { this.subscription = interval(this.config.delay).pipe( takeWhile(() => this.currentStep <= this.config.steps), ).subscribe(() => this.dataSubject.next(this.getInterpreterOutput(this.currentStep++))); } + /** + * Unsubscribes from the interpreter's runtime changes + */ private unsubscribe(): void { this.subscription?.unsubscribe(); this.subscription = undefined; } + /** + * Returns the output of the interpreter's runtime of the given step + * + * @param step step to evaluate + * @returns output of the interpreter's runtime + */ private getInterpreterOutput(step: number): InterpreterOutput { try { const value: RuntimeValue = this.runtime!.run(step); @@ -146,6 +193,12 @@ export class Interpreter { } } + /** + * Maps the runtime output to the interpreter output + * + * @param output output of the runtime + * @returns output of the interpreter + */ private getRuntimeOutput(output: RuntimeOutput): InterpreterOutput { return { status: { code: 0 }, @@ -156,6 +209,12 @@ export class Interpreter { } as InterpreterOutput; } + /** + * Maps the runtime error to the interpreter output + * + * @param error error of the runtime + * @returns output of the interpreter + */ private getRuntimeError(error: ErrorModel): InterpreterOutput { return { status: { @@ -165,10 +224,22 @@ export class Interpreter { }; } + /** + * Maps the runtime agent list to the output agent list + * + * @param agents list of runtime agents + * @returns list of output agents + */ private getAgents(agents: RuntimeAgent[]): Agent[] { return agents.map((agent: RuntimeAgent) => this.getAgent(agent)); } + /** + * Maps the runtime agent to the output agents + * + * @param agent runtime agent + * @returns output agent + */ private getAgent(agent: RuntimeAgent): Agent { const variables: { [key: string]: RuntimeValue } = {}; agent.variables.forEach((value, key) => { @@ -182,8 +253,12 @@ export class Interpreter { return { identifier: agent.identifier, variables } as Agent; } - // interpreter's functions initialization - + /** + * Creates the global width function + * + * @param width numeric width value + * @returns runtime function call + */ private createWidthFunction(width: number): FunctionCall { function widthFunction(args: RuntimeValue[]): RuntimeValue { if (args.length !== 0) { @@ -196,6 +271,12 @@ export class Interpreter { return widthFunction; } + /** + * Creates the global height function + * + * @param height numeric height value + * @returns runtime function call + */ private createHeightFunction(height: number): FunctionCall { function heightFunction(args: RuntimeValue[]): RuntimeValue { if (args.length !== 0) { diff --git a/src/interpreter/model/agent.ts b/src/interpreter/model/agent.ts index 163f905..a8d0acf 100644 --- a/src/interpreter/model/agent.ts +++ b/src/interpreter/model/agent.ts @@ -1,5 +1,10 @@ +/** + * Object representing an output agent + */ export interface Agent { + /** identifier of the agent */ identifier: string; + /** map of agent variables */ variables: Object; } \ No newline at end of file diff --git a/src/interpreter/model/configuration.ts b/src/interpreter/model/configuration.ts index dd1d639..d21717c 100644 --- a/src/interpreter/model/configuration.ts +++ b/src/interpreter/model/configuration.ts @@ -1,7 +1,14 @@ +/** + * Object representing the interpeter's configuration + */ export interface InterpreterConfiguration { + /** number of the simulation steps */ steps: number; + /** delay between each step in milliseconds */ delay: number; + /** width of the visualisation window */ width: number; + /** height of the visualisation window */ height: number; } \ No newline at end of file diff --git a/src/interpreter/model/exit-status.ts b/src/interpreter/model/exit-status.ts index 18ee32b..a7f7a6b 100644 --- a/src/interpreter/model/exit-status.ts +++ b/src/interpreter/model/exit-status.ts @@ -1,5 +1,10 @@ +/** + * Object representing the exit status of the interpreter's output + */ export interface ExitStatus { + /** exit code number */ code: number; + /** exit status message */ message?: string; } \ No newline at end of file diff --git a/src/interpreter/model/output.ts b/src/interpreter/model/output.ts index cf819be..520208d 100644 --- a/src/interpreter/model/output.ts +++ b/src/interpreter/model/output.ts @@ -1,12 +1,22 @@ import { Agent } from "./agent.ts"; import { ExitStatus } from "./exit-status.ts"; +/** + * Object representing the output of the simulation + */ export interface Output { + /** value of the current step */ step: number; + /** list of current agents */ agents: Agent[]; } +/** + * Object representing the output of the interpreter + */ export interface InterpreterOutput { + /** exit status of the interpreter */ status: ExitStatus; + /** output values of the simulation */ output?: Output; } \ No newline at end of file diff --git a/src/lexer/keywords.ts b/src/lexer/keywords.ts index dd00982..9c7056b 100644 --- a/src/lexer/keywords.ts +++ b/src/lexer/keywords.ts @@ -1,5 +1,8 @@ import { TokenType } from "./model/index.ts"; +/** + * Map of the reserved keywords and their types + */ export const ReservedKeywords: Record = { "agent": TokenType.Agent, diff --git a/src/lexer/lexer.ts b/src/lexer/lexer.ts index 9104205..5206326 100644 --- a/src/lexer/lexer.ts +++ b/src/lexer/lexer.ts @@ -144,7 +144,7 @@ export class Lexer { /** * Produces a numeric literal token * - * @param position - position in source code + * @param position position in source code */ private tokenizeNumber(position: Position): void { let number = ""; @@ -180,18 +180,40 @@ export class Lexer { this.token(this.getKeywordOrIdentifierTokenType(identifier), { value: identifier, position }); } + /** + * Checks if there are symbols left in the stream + * + * @returns true if the stream has next symbols + */ private hasNext(): boolean { return this.symbols.length > 0; } + /** + * Returns the current symbol in the stream + * + * @returns current symbol + */ private getNext(): Symbol { return this.symbols[0]; } + /** + * Checks if the current symbol value equals the given value + * + * @param character + * @returns true if the current symbol value equals the given value, otherwise false + */ private isNext(character: string): boolean { return character === this.getNext().value; } + /** + * Returns the current symbol and removes it from the stream + * + * @throws lexer error if there is no symbol in the stream + * @returns current symbol + */ private next(): Symbol { const symbol = this.symbols.shift(); @@ -205,15 +227,15 @@ export class Lexer { /** * Generates a new token with specified type, value and position * - * @param type - token type - * @param symbol - token symbol (value and position) + * @param type token type + * @param symbol token symbol (value and position) */ private token(type: TokenType, symbol = this.next()): void { this.tokens.push({ type, value: symbol.value, position: symbol.position }); } /** - * Generates the end-of-file (EOF) token + * Generates the EOF (end-of-file) token */ private generateEOFToken(): void { if (this.tokens.length === 0) { @@ -236,15 +258,31 @@ export class Lexer { }); } + /** + * Tries to retrieve a reserved keyword type by its identifier + * + * @param identifier identifier to retrieve the reserved keyword type by + * @returns reserved keyword if it was found, otherwise the plain identifier type + */ private getKeywordOrIdentifierTokenType(identifier: string): TokenType { const keyword = ReservedKeywords[identifier]; return keyword ?? TokenType.Identifier; } + /** + * Checks whether the value of the current symbol is alpha + * + * @returns true if the value of the current token is alpha, otherwise false + */ private isAlpha(): boolean { return this.getNext().value.toUpperCase() != this.getNext().value.toLowerCase(); } + /** + * Checks whether the value of the current symbol is numeric + * + * @returns true if the value of the current symbol is numeric, otherwise false + */ private isNumber(): boolean { const symbol = this.getNext().value.charCodeAt(0); const bounds = { @@ -255,6 +293,11 @@ export class Lexer { return symbol >= bounds.lower && symbol <= bounds.upper; } + /** + * Checks whether the value of the current symbol is skippable + * + * @returns true if the value of the current token is skippable, otherwise false + */ private isSkippable(): boolean { return [ " ", "\n", "\t" ].includes(this.getNext().value); } diff --git a/src/lexer/model/token.ts b/src/lexer/model/token.ts index f238638..5e519f8 100644 --- a/src/lexer/model/token.ts +++ b/src/lexer/model/token.ts @@ -1,8 +1,14 @@ import { Position } from "../../symbolizer/index.ts"; import { TokenType } from "./token-type.ts"; +/** + * Object representing a lexical token + */ export interface Token { + /** value of the token */ value: string; + /** type of the token */ type: TokenType; + /** position of the token in the source code */ position: Position; } \ No newline at end of file diff --git a/src/parser/model/dependency-graph.ts b/src/parser/model/dependency-graph.ts index 1c4d00f..4f2bc72 100644 --- a/src/parser/model/dependency-graph.ts +++ b/src/parser/model/dependency-graph.ts @@ -1,4 +1,7 @@ +/** + * Node of the dependency graph + */ export class Node { public identifier: string; @@ -8,6 +11,11 @@ export class Node { this.identifier = identifier; } + /** + * Adds a new dependency (directed edge) from the current node to the given node + * + * @param node node to add the directed edge to + */ public addDependency(node: Node): void { this.dependencies.push(node); } diff --git a/src/parser/model/node-type.ts b/src/parser/model/node-type.ts index e4a6676..74d79de 100644 --- a/src/parser/model/node-type.ts +++ b/src/parser/model/node-type.ts @@ -1,11 +1,9 @@ export enum NodeType { - // Statements Program = "Program", ObjectDeclaration = "ObjectDeclaration", VariableDeclaration = "VariableDeclaration", DefineDeclaration = "DefineDeclaration", - // Expressions NumericLiteral = "NumericLiteral", BooleanLiteral = "BooleanLiteral", Identifier = "Identifier", diff --git a/src/parser/model/nodes.ts b/src/parser/model/nodes.ts index 699c141..19436ac 100644 --- a/src/parser/model/nodes.ts +++ b/src/parser/model/nodes.ts @@ -2,106 +2,193 @@ import { Position } from "../../symbolizer/index.ts"; import { NodeType } from "./node-type.ts"; import { VariableType } from "./variable-type.ts"; +/** + * Generic AST node + */ export interface ParserValue { + /** type of the AST node */ type: NodeType; + /** position of the AST node in source code */ position: Position; } -export interface Statement extends ParserValue { - position: Position; -} +/** + * AST node representing a generic statement + */ +export interface Statement extends ParserValue {} +/** + * AST node representing a generic expression + */ export interface Expression extends Statement {} +/** + * AST node representing a program + */ export interface Program extends Statement { type: NodeType.Program; + /** list of statements in the body of the program */ body: Statement[]; } +/** + * AST node representing an object declaration + */ export interface ObjectDeclaration extends Statement { type: NodeType.ObjectDeclaration; + /** identifier of the object declaration */ identifier: string; + /** count of the object declaration instances to generate */ count: Expression; + /** list of variable declarations in the body of the object declaration */ body: Expression[]; } +/** + * AST node representing a variable declaration + */ export interface VariableDeclaration extends Statement { type: NodeType.VariableDeclaration; + /** type of the variable */ variableType: VariableType; + /** identifier of the variable declaration */ identifier: string; + /** value of the variable declaration */ value: Expression; + /** default initial value of the variable declaration (if it is of type property) */ default?: Expression; } +/** + * AST node representing a define declaration + */ export interface DefineDeclaration extends Statement { type: NodeType.DefineDeclaration; + /** identifier of the define declaration */ identifier: string; + /** value of the define declaration */ value: Expression; } +/** + * AST node representing a binary expression + */ export interface BinaryExpression extends Expression { type: NodeType.BinaryExpression; + /** operator of the binary expression */ + operator: string; + /** left-hand side of the binary expression */ left: Expression; + /** right-hand side of the binary expression */ right: Expression; - operator: string; } +/** + * AST node representing a unary expression + */ export interface UnaryExpression extends Expression { type: NodeType.UnaryExpression; + /** operator of the unary expression */ operator: string; + /** value of the unary expression */ value: Expression; } +/** + * AST node representing a logical expression + */ export interface LogicalExpression extends Expression { type: NodeType.LogicalExpression; + /** operator of the logical expresion */ + operator: string; + /** left-hand side of the logical expression */ left: Expression; + /** right-hand side of the logical expression */ right: Expression; - operator: string; } +/** + * AST node representing a conditional expression + */ export interface ConditionalExpression extends Expression { type: NodeType.ConditionalExpression; + /** condition based on which to evaluate the conditional expression */ condition: Expression; + /** expression to evaluate if the condition was evaluated to true */ consequent: Expression; + /** expression to evaluate if the condition was evaluated to false */ alternate: Expression; } +/** + * AST node representing a call expression + */ export interface CallExpression extends Expression { type: NodeType.CallExpression; + /** caller of the expression */ caller: Expression; + /** argument list of the call expression */ args: Expression[]; } +/** + * AST node representing a set comprehension expression + */ export interface SetComprehensionExpression extends Expression { type: NodeType.SetComprehensionExpression; + /** base of the set comprehension expression */ base: Expression; + /** param identifier */ param: string; + /** value based on which to evaluate the set comprehension expression */ value: Expression; } +/** + * AST node representing a member expression + */ export interface MemberExpression extends Expression { type: NodeType.MemberExpression; + /** caller of the member expression */ caller: Expression; + /** value of the member expression */ value: Expression; } +/** + * AST node representing an otherwise expression + */ export interface OtherwiseExpression extends Expression { type: NodeType.OtherwiseExpression; + /** left-hand side of the otherwise expression */ left: Expression; + /** right-hand side of the otherwise expression */ right: Expression; } +/** + * AST node representing an identifier + */ export interface Identifier extends Expression { type: NodeType.Identifier; + /** value of the identifier */ identifier: string; } +/** + * AST node representing a numeric literal + */ export interface NumericLiteral extends Expression { type: NodeType.NumericLiteral; + /** value of the numeric literal */ value: number; } +/** + * AST node representing a boolean literal + */ export interface BooleanLiteral extends Expression { type: NodeType.BooleanLiteral; + /** value of the boolean literal */ value: boolean; } \ No newline at end of file diff --git a/src/parser/model/variable-type.ts b/src/parser/model/variable-type.ts index 7ea80a4..0774355 100644 --- a/src/parser/model/variable-type.ts +++ b/src/parser/model/variable-type.ts @@ -1,5 +1,10 @@ +/** + * Type of the variable declaration + */ export enum VariableType { + /** represent an constant value that cannot be changed */ Const = "const", + /** represents a variable value that is updated in each evaluation */ Property = "property" } \ No newline at end of file diff --git a/src/parser/parser-util.ts b/src/parser/parser-util.ts index 8dbae7a..4ca903a 100644 --- a/src/parser/parser-util.ts +++ b/src/parser/parser-util.ts @@ -5,6 +5,12 @@ import { BinaryExpression, BooleanLiteral, CallExpression, ConditionalExpression export class ParserUtil { + /** + * Converts a generic AST node to source code + * + * @param ast generic AST node to convert + * @returns source code of the generic AST node + */ public static astToCode(ast: ParserValue): string { let code = ""; @@ -159,13 +165,27 @@ export class ParserUtil { return code; } - public static codeToAst(code: string): VariableDeclaration { - const symbols = new Symbolizer(code).symbolize(); + /** + * Converts the source code to a variable declaration AST node + * + * @param sourceCode source code to convert + * @returns variable declaration AST node + */ + public static codeToAst(sourceCode: string): VariableDeclaration { + const symbols = new Symbolizer(sourceCode).symbolize(); const tokens = new Lexer(symbols).tokenize(); const result = new Parser(tokens).parseVariableDeclaration() as VariableDeclaration; return result; } + /** + * Finds a variable declaration AST node in a program AST node and converts it to source code + * + * @param program program AST node to search in + * @param agentIdentifier identifier of the object declaration + * @param variableIdentifier identifier of the variable declaration + * @returns source code of the variable declaration AST node if found, otherwise undefined + */ public static getVariableCode(program: Program, agentIdentifier: string, variableIdentifier: string): string | undefined { const objectDeclarationStatement = program.body.find(object => (object as ObjectDeclaration).identifier === agentIdentifier); @@ -182,6 +202,15 @@ export class ParserUtil { return ParserUtil.astToCode(variableDeclarationStatement as VariableDeclaration); } + /** + * Finds a variable declaration AST node in a program AST node and replaces it with a new variable declaration AST node + * + * @param program program AST node to search in + * @param newVariableDeclaration variable declaration AST node to use as a replacement + * @param agentIdentifier identifier of the object declaration + * @param variableIdentifier identifier of the variable declaration + * @returns new program AST node with the replaced variable declaration AST node + */ public static updateVariableInProgram(program: Program, newVariableDeclaration: VariableDeclaration, agentIdentifier: string, variableIdentifier: string): Program { program.body = program.body.map(objectStatement => { const objectDeclaration = objectStatement as ObjectDeclaration; @@ -204,6 +233,13 @@ export class ParserUtil { return program; } + /** + * Asserts whether a generic AST node is of any of the given types + * + * @param node generic AST node to assert + * @param types list of types to assert against + * @returns true of the given generic AST node is of any of the given types, otherwise false + */ private static isOfType(node: ParserValue, ...types: NodeType[]): boolean { for (const type of types) { if (node.type === type) { diff --git a/src/parser/parser.ts b/src/parser/parser.ts index ff7b583..d22bbde 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -12,9 +12,9 @@ export class Parser { } /** - * Parses the array of tokens into an AST structure representing the program + * Parses the array of tokens into a program AST node * - * @returns program structure in form of AST + * @returns program AST node */ public parse(): Program { const program = this.createEmptyProgram(); @@ -27,6 +27,11 @@ export class Parser { return program; } + /** + * Parser a generic statement AST node + * + * @returns statement AST node + */ private parseStatement(): Statement { switch (this.at().type) { case TokenType.Define: @@ -38,6 +43,11 @@ export class Parser { } } + /** + * Parses a define declaration AST node + * + * @returns define declaration AST node + */ private parseDefineDeclaration(): DefineDeclaration { const position = this.assert(TokenType.Define, "Expected define keyword in define declaration", this.position()).position; const identifier = this.assert(TokenType.Identifier, "Expected identifier after define keyword in define declaration", this.position()).value; @@ -60,6 +70,11 @@ export class Parser { }; } + /** + * Parses an object declaration AST node + * + * @returns object declaration AST node + */ private parseObjectDeclaration(): ObjectDeclaration { const { position } = this.assert(TokenType.Agent, "Expected agent keyword in agent declaration", this.position()); const identifier = this.assert(TokenType.Identifier, "Expected agent identifier after agent keyword in agent declaration", this.position()).value; @@ -95,6 +110,11 @@ export class Parser { }; } + /** + * Parses a variable declaration AST node + * + * @returns variable declaration AST node + */ public parseVariableDeclaration(): VariableDeclaration { const { position, type } = this.assertMulti(TokenType.Property, TokenType.Const, "Expected property or const keyword in property declaration", this.position()); const identifier = this.assert(TokenType.Identifier, "Expected identifier after property type in property declaration", this.position()).value; @@ -126,10 +146,20 @@ export class Parser { }; } + /** + * Parses a generic expression AST node + * + * @returns expression AST node + */ private parseExpression(): Expression { return this.parseOtherwiseExpression(); } + /** + * Parses an otherwise expression AST node + * + * @returns expression AST node + */ private parseOtherwiseExpression(): Expression { const left = this.parseSetComprehensionExpression(); @@ -150,6 +180,11 @@ export class Parser { return left; } + /** + * Parses a set comprehension expression AST node + * + * @returns expression AST node + */ private parseSetComprehensionExpression(): Expression { const base = this.parseConditionalExpression(); @@ -174,6 +209,11 @@ export class Parser { return base; } + /** + * Parses a conditional expression AST node + * + * @returns expression AST node + */ private parseConditionalExpression(): Expression { if (this.at().type === TokenType.If) { const { position } = this.next(); @@ -202,6 +242,11 @@ export class Parser { return this.parseLogicalExpression(); } + /** + * Parses a logical expression AST node + * + * @returns expression AST node + */ private parseLogicalExpression(): Expression { let left = this.parseComparisonExpression(); @@ -224,6 +269,11 @@ export class Parser { return left; } + /** + * Parses a comparison binary expression AST node + * + * @returns expression AST node + */ private parseComparisonExpression(): Expression { let left = this.parseAdditiveExpression(); @@ -246,6 +296,11 @@ export class Parser { return left; } + /** + * Parses an additive binary expression AST node + * + * @returns expression AST node + */ private parseAdditiveExpression(): Expression { let left = this.parseMultiplicativeExpression(); @@ -268,6 +323,11 @@ export class Parser { return left; } + /** + * Parses a multiplicative expression AST node + * + * @returns expression AST node + */ private parseMultiplicativeExpression(): Expression { let left = this.parseMemberExpression(); @@ -290,6 +350,11 @@ export class Parser { return left; } + /** + * Parses a member expression AST node + * + * @returns expression AST node + */ private parseMemberExpression(): Expression { const caller = this.parseCallExpression(); @@ -311,6 +376,11 @@ export class Parser { return caller; } + /** + * Parses a call expression AST node + * + * @returns expression AST node + */ private parseCallExpression(): Expression { const caller = this.parsePrimaryExpression(); @@ -330,6 +400,11 @@ export class Parser { return caller; } + /** + * Parses a call expression AST node's arguments + * + * @returns arguments of the call expression + */ private parseCallExpressionArguments(): Expression[] { this.assert(TokenType.OpenParen, "Expected an open parenthesis before function arguments in call expression", this.position()); @@ -340,6 +415,11 @@ export class Parser { return args; } + /** + * Parses a list of arguments of a call expression AST node + * + * @returns arguments of the call expression + */ private parseCallExpressionArgumentsList(): Expression[] { const firstArg = this.parseExpression(); const args = [firstArg]; @@ -352,6 +432,11 @@ export class Parser { return args; } + /** + * Parses a generic primary expression AST node + * + * @returns expression AST node + */ private parsePrimaryExpression(): Expression { switch (this.at().type) { case TokenType.Identifier: @@ -371,6 +456,11 @@ export class Parser { } } + /** + * Parses an identifier AST node + * + * @returns identifier AST node + */ private parseIdentifier(): Identifier { const { value, position } = this.next(); @@ -383,6 +473,11 @@ export class Parser { return identifier; } + /** + * Parses a numeric literal AST node + * + * @returns numeric literal AST node + */ private parseNumericLiteral(): NumericLiteral { const { value, position } = this.next(); @@ -395,6 +490,11 @@ export class Parser { return numericLiteral; } + /** + * Parses a unary expression AST node representing a negative numeric literal + * + * @returns unary expression AST node + */ private parseNegativeNumericLiteral(): UnaryExpression { const { value, position } = this.at(); @@ -414,6 +514,11 @@ export class Parser { return unaryExpression; } + /** + * Parses a boolean literal AST node + * + * @returns boolean literal AST node + */ private parseBooleanLiteral(): BooleanLiteral { const { value, position } = this.next(); @@ -426,6 +531,11 @@ export class Parser { return booleanLiteral; } + /** + * Parses a unary expression AST node representing a negative boolean literal + * + * @returns unary expression AST node + */ private parseNegativeBooleanLiteral(): UnaryExpression { const { value, position } = this.at(); @@ -445,6 +555,11 @@ export class Parser { return unaryExpression; } + /** + * Parses a parenthesised generic expression AST node + * + * @returns expression AST node + */ private parseParenthesisedExpression(): Expression { this.next(); const value: Expression = this.parseExpression(); @@ -454,6 +569,15 @@ export class Parser { return value; } + /** + * Asserts the current token to be of a given type + * + * @param type expected type of the current token + * @param message error message in case of incorrect type + * @param position position of the current token in the source code + * @param next whether to move to the next token after successful assertion + * @returns the current or next token + */ private assert(type: TokenType, message: string, position: Position, next = true): Token { if (this.isNotOf(type)) { throw new ErrorParser(message, position); @@ -462,6 +586,16 @@ export class Parser { return next ? this.next() : this.at(); } + /** + * Asserts the current token type to be of either of the given types + * + * @param type1 first expected type of the current token + * @param type2 second expected type of the current token + * @param message error message in case of incorrect type + * @param position position of the current token in the source code + * @param next whether to move to the next token after successful assertion + * @returns the current or next token + */ private assertMulti(type1: TokenType, type2: TokenType, message: string, position: Position, next = true): Token { if (this.isNotOf(type1) && this.isNotOf(type2)) { throw new ErrorParser(message, position); @@ -470,26 +604,58 @@ export class Parser { return next ? this.next() : this.at(); } + /** + * Returns the current token + * + * @returns current token + */ private at(): Token { return this.tokens[0]; } + /** + * Returns the current token and removes it from the stack + * + * @returns current token + */ private next(): Token { return this.tokens.shift() as Token; } + /** + * Returns the position of the current token in the source code + * + * @returns position of the current token + */ private position(): Position { return this.at().position; } + /** + * Asserts the current token's type to not be of the given type + * + * @param type type to assert + * @returns true if the types do not match, otherwise false + */ private isNotOf(type: TokenType): boolean { return this.at().type !== type; } - private notEndOfFile() { + /** + * Checks whether the current token is not the EOF (end-of-file) token + * + * @returns true if the current token is not the EOF token, otherwise false + */ + private notEndOfFile(): boolean { return this.at().type !== TokenType.EOF; } + /** + * Converts the token type to variable type (property, const) + * + * @param tokenType type of the token to convert + * @returns variable type + */ private getVariableType(tokenType: TokenType): VariableType { switch (tokenType) { case TokenType.Property: @@ -501,6 +667,11 @@ export class Parser { } } + /** + * Creates an empty program AST node + * + * @returns empty program AST node + */ private createEmptyProgram(): Program { return { type: NodeType.Program, diff --git a/src/parser/topology.ts b/src/parser/topology.ts index 7283570..17d70fe 100644 --- a/src/parser/topology.ts +++ b/src/parser/topology.ts @@ -8,7 +8,7 @@ export class Topology { /** * Sorts the objects' variable declarations topologically in order to minimize runtime errors * - * @param program - parsed program in AST form + * @param program parsed program in AST form * @returns topologically sorted AST */ public getSortedProgram(program: Program): Program { @@ -19,7 +19,7 @@ export class Topology { /** * Sorts the top-level declarations (objects, global variables) in order to optimize program runtime * - * @param program - parsed program in AST form + * @param program parsed program in AST form * @returns sorted AST */ private getSortedAgentAndDefineDeclarations(program: Program): Program { @@ -36,7 +36,7 @@ export class Topology { /** * Sorts variable declarations of an object topologically * - * @param objectDeclaration - object declaration whose properties to sort topologically + * @param objectDeclaration object declaration whose properties to sort topologically * @returns sorted object declaration */ private getSortedAgentDeclaration(objectDeclaration: ObjectDeclaration): ObjectDeclaration { @@ -68,8 +68,8 @@ export class Topology { /** * Sorts variable declarations based on nodes of an already topologically sorted dependency graph * - * @param declarations - an array of variable declarations to sort - * @param nodes - topologically sorted nodes of a dependency graph + * @param declarations an array of variable declarations to sort + * @param nodes topologically sorted nodes of a dependency graph * @returns topologically sorted array of variable declarations */ private getSortedPropertyDeclarations(declarations: VariableDeclaration[], nodes: Node[]): Expression[] { @@ -90,7 +90,7 @@ export class Topology { /** * Finds all variable dependencies of the current variable declaration * - * @param variableDeclaration - variable declaration whose variable dependencies to find + * @param variableDeclaration variable declaration whose variable dependencies to find * @returns an array of variable dependencies identifiers */ private getPropertyDependencies(variableDeclaration: VariableDeclaration): string[] { @@ -169,8 +169,8 @@ export class Topology { /** * Produces a dependency graph of all variable dependencies of an object * - * @param identifiers - identifiers of the object's variable declarations - * @param dependencies - variable dependencies of the object's variable declarations + * @param identifiers identifiers of the object's variable declarations + * @param dependencies variable dependencies of the object's variable declarations * @returns dependency graph representing variable dependencies of an object */ private getDependencyGraph(identifiers: string[], dependencies: string[][]): DependencyGraph { @@ -194,7 +194,7 @@ export class Topology { /** * Sorts a dependency graph topologically * - * @param graph - dependency graph to sort topologically + * @param graph dependency graph to sort topologically * @returns array of topologically sorted nodes of the dependency graph */ private topologicalSort(graph: DependencyGraph): Node[] { @@ -245,7 +245,7 @@ export class Topology { /** * Finds all global variable declarations in a program * - * @param program - program to find global variable declarations in + * @param program program to find global variable declarations in * @returns array of program's global variable declarations */ private getDefineDeclarations(program: Program): DefineDeclaration[] { @@ -257,7 +257,7 @@ export class Topology { /** * Finds all object declarations in a program * - * @param program - program to find object declarations in + * @param program program to find object declarations in * @returns array of program's object declarations */ private getAgentDeclarations(program: Program): ObjectDeclaration[] { @@ -269,7 +269,7 @@ export class Topology { /** * Finds all variable declarations in an object declaration * - * @param objectDeclaration - object declaration to find variable declarations in + * @param objectDeclaration object declaration to find variable declarations in * @returns array of object declaration's variable declarations */ private getPropertyDeclarations(objectDeclaration: ObjectDeclaration): VariableDeclaration[] { @@ -281,7 +281,7 @@ export class Topology { /** * Finds all identifiers of all object declarations in a program * - * @param program - program to find all object declarations' identifiers in + * @param program program to find all object declarations' identifiers in * @returns array of all object declarations' identifiers */ private getAgentDeclarationIdentifiers(program: Program): string[] { diff --git a/src/parser/validation.ts b/src/parser/validation.ts index b3ba66b..bf3a2a4 100644 --- a/src/parser/validation.ts +++ b/src/parser/validation.ts @@ -6,7 +6,7 @@ export class Validation { /** * Statically validates the agent, property and define declarations in a program * - * @param program - program to validate + * @param program program to validate */ public static validate(program: Program): void { Validation.validateDefineDeclarationIdentifiers(program); @@ -18,7 +18,7 @@ export class Validation { * Checks for duplicate define declarations in a program * Throws an exception if duplicate define declarations were found * - * @param program - program to check duplicate define declarations in + * @param program program to check duplicate define declarations in */ private static validateDefineDeclarationIdentifiers(program: Program): void { const defineDeclarations = program.body @@ -42,7 +42,7 @@ export class Validation { * Checks for duplicate agent declarations in a program * Throws an exception if duplicate agent declarations were found * - * @param program - program to check duplicate agent declarations in + * @param program program to check duplicate agent declarations in */ private static validateAgentDeclarationIdentifiers(program: Program): void { const agentDeclarations = program.body @@ -66,7 +66,7 @@ export class Validation { * Checks for duplicate property declarations in a program * Throws an exception if duplicate property declarations were found * - * @param program - program to check duplicate property declarations in + * @param program program to check duplicate property declarations in */ private static validatePropertyDeclarationIdentifiers(program: Program): void { const agentDeclarations = program.body diff --git a/src/runtime/environment.ts b/src/runtime/environment.ts index b56e18a..9c36a21 100644 --- a/src/runtime/environment.ts +++ b/src/runtime/environment.ts @@ -15,8 +15,8 @@ export class Environment { /** * Declares a new variable in the current environment * - * @param identifier - identifier of the new variable - * @param value - value of the new variable + * @param identifier identifier of the new variable + * @param value value of the new variable * @returns value that has been declared */ public declareVariable(identifier: string, value: RuntimeValue): RuntimeValue { @@ -32,8 +32,8 @@ export class Environment { /** * Assing an existing variable in the current environment a new value * - * @param identifier - identifier of the existing variable - * @param value - value to be reassigned to the existing variable + * @param identifier identifier of the existing variable + * @param value value to be reassigned to the existing variable * @returns value that has been reassigned */ public assignVariable(identifier: string, value: RuntimeValue): RuntimeValue { @@ -50,7 +50,7 @@ export class Environment { /** * Retrieves the value of a variable from the current or any of the parent environments by its identifier * - * @param identifier - identifier of the variable + * @param identifier identifier of the variable * @returns variable value or undefined if not found */ public lookupVariable(identifier: string): RuntimeValue | undefined { @@ -66,7 +66,7 @@ export class Environment { /** * Searches the current and all parent environments for a variable by its identifier and returns this environment if found * - * @param identifier - identifier of the searched variable + * @param identifier identifier of the searched variable * @returns environment where the variable has been declared */ public resolve(identifier: string): Environment | undefined { diff --git a/src/runtime/functions/utils.ts b/src/runtime/functions/utils.ts index 73cd35e..1be54f8 100644 --- a/src/runtime/functions/utils.ts +++ b/src/runtime/functions/utils.ts @@ -3,46 +3,110 @@ import { AgentValue, AgentsValue, BooleanValue, ColourValue, FunctionCall, Funct export const NUMERIC_LITERAL_MAX_DECIMAL_PLACES = 8; +/** + * Creates a global function + * + * @param call function call to execute on using the function + * @returns function call value + */ export function createGlobalFunction(call: FunctionCall): FunctionValue { return { type: ValueType.Function, call } as FunctionValue; } +/** + * Asserts the correct argument count of a function + * + * @param identifier identifier of the function + * @param expected expected argument count + * @param provided provided argument count + */ export function expectArgumentCount(identifier: string, expected: number, provided: number): void { if (expected !== provided) { throw new ErrorRuntime(`Function '${identifier}' expected ${expected} arguments, ${provided} provided`); } } +/** + * Asserts the correct argument type of a function + * + * @param identifier identifier of the function + * @param argument argument to assert + * @param type expected type of the argument + */ export function expectArgumentType(identifier: string, argument: RuntimeValue, type: ValueType): void { if (argument.type !== type) { throw new ErrorRuntime(`Function '${identifier}' expected argument of type '${type}', '${argument.type}' provided`); } } +/** + * Creates a runtime value of type number + * + * @param value numeric value to provide + * @returns number runtime value + */ export function createNumberValue(value: number): NumberValue { return { type: ValueType.Number, value: value }; } +/** + * Creates a runtime value of type boolean + * + * @param value boolean value to provide + * @returns boolean runtime value + */ export function createBooleanValue(value: boolean): BooleanValue { return { type: ValueType.Boolean, value }; } +/** + * Creates a runtime value of type agent + * + * @param value agent value to provide + * @returns agent runtime value + */ export function createAgentValue(value: RuntimeAgent): AgentValue { return { type: ValueType.Agent, value }; } +/** + * Creates a runtime value of type agent list + * + * @param value agent list value to provide + * @returns agent list runtime value + */ export function createAgentsValue(value: RuntimeAgent[]): AgentsValue { return { type: ValueType.Agents, value }; } +/** + * Creates a runtime value of type null + * + * @returns null runtime value + */ export function createNullValue(): NullValue { return { type: ValueType.Null }; } +/** + * Creates a runtime value of type colour + * + * @param red red colour value + * @param green green colour value + * @param blue blue colour value + * @returns colour runtime value + */ export function createColourValue(red: number, green: number, blue: number): ColourValue { return { type: ValueType.Colour, value: { red, green, blue } }; } +/** + * Normalizes the given decimal number to given number of decimal digits + * + * @param value numeric value to normalize + * @param digits number of digits to keep + * @returns normalized numeric value + */ export function normalizeNumber(value: number, digits: number = NUMERIC_LITERAL_MAX_DECIMAL_PLACES): number { const pow = Math.pow(10, digits); return Math.round(value * pow) / pow; diff --git a/src/runtime/model/values.ts b/src/runtime/model/values.ts index e3dc7ad..45cb207 100644 --- a/src/runtime/model/values.ts +++ b/src/runtime/model/values.ts @@ -1,19 +1,31 @@ import { ValueType } from "./value-type.ts"; +/** + * Generic runtime value + */ export interface RuntimeValue { type: ValueType; } +/** + * Runtime value representing a number + */ export interface NumberValue extends RuntimeValue { type: ValueType.Number; value: number; } +/** + * Runtime value representing a boolean + */ export interface BooleanValue extends RuntimeValue { type: ValueType.Boolean; value: boolean; } +/** + * Runtime value representing an identifier + */ export interface IdentifierValue extends RuntimeValue { type: ValueType.Identifier; value: string; @@ -21,31 +33,49 @@ export interface IdentifierValue extends RuntimeValue { export type FunctionCall = (args: RuntimeValue[]) => RuntimeValue; +/** + * Runtime value representing a function + */ export interface FunctionValue extends RuntimeValue { type: ValueType.Function; call: FunctionCall; } +/** + * Runtime value representing a null value + */ export interface NullValue extends RuntimeValue { type: ValueType.Null; } +/** + * Runtime value representing an agent + */ export interface AgentValue extends RuntimeValue { type: ValueType.Agent; value: RuntimeAgent; } +/** + * Runtime value representing a list of agents + */ export interface AgentsValue extends RuntimeValue { type: ValueType.Agents; value: RuntimeAgent[]; } +/** + * Runtime value representing a set comprehension + */ export interface SetComprehensionValue extends RuntimeValue { type: ValueType.SetComprehension; agents: RuntimeAgent[]; results: RuntimeValue[]; } +/** + * Runtime value representing an RGB colour + */ export interface ColourValue extends RuntimeValue { type: ValueType.Colour; value: { @@ -55,14 +85,27 @@ export interface ColourValue extends RuntimeValue { } } +/** + * Runtime value representing a runtime agent instance + * This object is returned as part of the interpreter output + */ export interface RuntimeAgent { + /** unique id of the runtime agent instance */ id: string; + /** identifier of the agent */ identifier: string; + /** hash map containing the runtime values of the agent's properties */ variables: Map; } +/** + * Runtime value representing a runtime output + * This object is returned as part of the interpreter output + */ export interface RuntimeOutput extends RuntimeValue { type: ValueType.Output; + /** the value of the current step */ step: number; + /** the list of runtime agent instances */ agents: RuntimeAgent[]; } \ No newline at end of file diff --git a/src/runtime/runtime.ts b/src/runtime/runtime.ts index 1ad6593..a62d196 100644 --- a/src/runtime/runtime.ts +++ b/src/runtime/runtime.ts @@ -24,10 +24,10 @@ export class Runtime { } /** - * Runs one step of the program evaluation and returns the program's output + * Evaluates one step of the simulation * - * @param step - step of the simulation to be evaluated - * @returns output of the simulation + * @param step step of the simulation to be evaluated + * @returns output of the given step of the simulation */ public run(step: number): RuntimeOutput { this.output.step = step; @@ -38,6 +38,12 @@ export class Runtime { return evaluation; } + /** + * Evaluates the program AST node + * + * @param program program AST node + * @returns output of the given step of the simulation + */ private evaluateProgram(program: Program): RuntimeOutput { for (const statement of program.body) { switch (statement.type) { @@ -57,6 +63,11 @@ export class Runtime { return this.output; } + /** + * Evaluates the define declaration AST node + * + * @param declaration define declaration AST node + */ private evaluateDefineDeclaration(declaration: DefineDeclaration): void { if (this.output.step > 0) { return; @@ -80,6 +91,11 @@ export class Runtime { this.globalEnvironment.declareVariable(identifier, defineDeclarationValue); } + /** + * Evaluates the object declaration AST node + * + * @param declaration object declaration AST node + */ private evaluateObjectDeclarationList(declaration: ObjectDeclaration): void { const count = this.evaluateAgentCount(declaration); @@ -93,6 +109,12 @@ export class Runtime { } } + /** + * Evaluates the agent count of the object declaration AST node + * + * @param declaration define declaration AST node + * @returns numeric runtime value + */ private evaluateAgentCount(declaration: ObjectDeclaration): NumberValue { let count: RuntimeValue; @@ -114,6 +136,11 @@ export class Runtime { return count as NumberValue; } + /** + * Evaluates the object declaration AST node + * + * @param declaration object declaration AST node + */ private evaluateObjectDeclaration(declaration: ObjectDeclaration, id: string): void { const objectIdentifier = declaration.identifier; const objectVariables = new Map(); @@ -144,6 +171,13 @@ export class Runtime { } } + /** + * Evaluates the variable declaration AST node + * + * @param declaration variable declaration AST node + * @param id id of the current agent + * @returns runtime value of the variable declaration + */ private evaluateVariableDeclaration(declaration: VariableDeclaration, id: string): RuntimeValue { switch (declaration.variableType) { case VariableType.Property: @@ -156,6 +190,13 @@ export class Runtime { } } + /** + * Evaluates the variable declaration AST node representing a property declaration + * + * @param declaration variable declaration AST node representing a property declaration + * @param id id of the current agent + * @returns runtime value of the variable declaration representing a property declaration + */ private evaluatePropertyDeclaration(declaration: VariableDeclaration, id: string): RuntimeValue { let expression = declaration.value; @@ -166,6 +207,13 @@ export class Runtime { return this.evaluateRuntimeValue(expression, id); } + /** + * Evaluates the variable declaration AST node representing a const declaration + * + * @param declaration variable declaration AST node representing a const declaration + * @param id id of the current agent + * @returns runtime value of the variable declaration representing a const declaration + */ private evaluateConstDeclaration(declaration: VariableDeclaration, id: string): RuntimeValue { if (this.output.step === 0) { return this.evaluateRuntimeValue(declaration.value, id); @@ -181,6 +229,13 @@ export class Runtime { return value; } + /** + * Evaluates a generic AST node + * + * @param node generic AST node + * @param id id of the current agent + * @returns runtime value of the given AST node + */ private evaluateRuntimeValue(node: ParserValue, id: string): RuntimeValue { switch (node.type) { case NodeType.Identifier: @@ -210,6 +265,12 @@ export class Runtime { } } + /** + * Evaluates a numeric literal AST node + * + * @param numericLiteral numeric literal AST node + * @returns runtime value of the numeric literal AST node + */ private evaluateNumericLiteral(numericLiteral: NumericLiteral): RuntimeValue { const numberValue: NumberValue = { type: ValueType.Number, @@ -219,6 +280,12 @@ export class Runtime { return numberValue; } + /** + * Evaluates a boolean literal AST node + * + * @param booleanLiteral boolean literal AST node + * @returns runtime value of the boolean literal AST node + */ private evaluateBooleanLiteral(booleanLiteral: BooleanLiteral): RuntimeValue { const booleanValue: BooleanValue = { type: ValueType.Boolean, @@ -228,6 +295,12 @@ export class Runtime { return booleanValue; } + /** + * Evaluates an identifier AST node + * + * @param identifier identifier AST node + * @returns runtime value of the identifier AST node + */ private evaluateIdentifier(identifier: Identifier, id: string): RuntimeValue { const setComprehensionLookup = this.setComprehensionEnvironment.lookupVariable(identifier.identifier); if (setComprehensionLookup) { @@ -249,6 +322,12 @@ export class Runtime { return value; } + /** + * Evaluates an identifier AST node representing a global variable declaration + * + * @param identifier identifier AST node + * @returns runtime value of the identifier AST node + */ private evaluateGlobalIdentifier(identifier: Identifier): RuntimeValue { const variableLookup = this.globalEnvironment.lookupVariable(identifier.identifier); @@ -259,6 +338,13 @@ export class Runtime { return variableLookup; } + /** + * Evaluates a binary expression AST node + * + * @param expression binary expression AST node + * @param id id of the current agent + * @returns runtime value of the binary expression AST node + */ private evaluateBinaryExpression(expression: BinaryExpression, id: string): RuntimeValue { const leftHandSide = this.evaluateRuntimeValue(expression.left, id); const rightHandSide = this.evaluateRuntimeValue(expression.right, id); @@ -289,33 +375,13 @@ export class Runtime { return this.evaluateNumericBinaryExpression(leftHandSide as NumberValue, rightHandSide as NumberValue, expression.operator, expression.position); } - private evaluateUnaryExpression(expression: UnaryExpression, id: string): RuntimeValue { - const { operator } = expression; - const value = this.evaluateRuntimeValue(expression.value, id); - - if (this.inOtherwiseExpression && value.type === ValueType.Null) { - return { type: ValueType.Null }; - } - - if (operator === "-") { - if (value.type !== ValueType.Number) { - throw new ErrorRuntime("Unary expression with '-' operator requires numeric value as operand", expression.position); - } - - return { type: ValueType.Number, value: -(value as NumberValue).value } as NumberValue; - } - - if (operator === "!") { - if (value.type !== ValueType.Boolean) { - throw new ErrorRuntime("Unary expression with '!' requires boolean value as operand", expression.position); - } - - return { type: ValueType.Boolean, value: !(value as BooleanValue).value } as BooleanValue; - } - - throw new ErrorRuntime("Unary expression requires operator '-' or '!', but '${operator}' was provided", expression.position); - } - + /** + * Evaluates a comparison binary expression AST node + * + * @param expression comparison binary expression AST node + * @param id id of the current agent + * @returns runtime value of the comparison binary expression AST node + */ private evaluateComparisonBinaryExpression(leftHandSide: NumberValue, rightHandSide: NumberValue, operator: string): RuntimeValue { let result: boolean = false; @@ -338,6 +404,13 @@ export class Runtime { return { type: ValueType.Boolean, value: result } as BooleanValue; } + /** + * Evaluates a numeric binary expression AST node + * + * @param expression numeric binary expression AST node + * @param id id of the current agent + * @returns runtime value of the numeric binary expression AST node + */ private evaluateNumericBinaryExpression(leftHandSide: NumberValue, rightHandSide: NumberValue, operator: string, position: Position): RuntimeValue { let result = 0; @@ -358,12 +431,53 @@ export class Runtime { throw new ErrorRuntime("Modulo by zero not allowed", position); } - result = this.customModulo(leftHandSide.value, rightHandSide.value); + result = this.modulo(leftHandSide.value, rightHandSide.value); } return { type: ValueType.Number, value: result } as NumberValue; } + /** + * Evaluates a unary expression AST node + * + * @param expression unary expression AST node + * @param id id of the current agent + * @returns runtime value of the unary expression AST node + */ + private evaluateUnaryExpression(expression: UnaryExpression, id: string): RuntimeValue { + const { operator } = expression; + const value = this.evaluateRuntimeValue(expression.value, id); + + if (this.inOtherwiseExpression && value.type === ValueType.Null) { + return { type: ValueType.Null }; + } + + if (operator === "-") { + if (value.type !== ValueType.Number) { + throw new ErrorRuntime("Unary expression with '-' operator requires numeric value as operand", expression.position); + } + + return { type: ValueType.Number, value: -(value as NumberValue).value } as NumberValue; + } + + if (operator === "!") { + if (value.type !== ValueType.Boolean) { + throw new ErrorRuntime("Unary expression with '!' requires boolean value as operand", expression.position); + } + + return { type: ValueType.Boolean, value: !(value as BooleanValue).value } as BooleanValue; + } + + throw new ErrorRuntime("Unary expression requires operator '-' or '!', but '${operator}' was provided", expression.position); + } + + /** + * Evaluates a logical expression AST node + * + * @param expression logical expression AST node + * @param id id of the current agent + * @returns runtime value of the logical expression AST node + */ private evaluateLogicalExpression(expression: LogicalExpression, id: string): RuntimeValue { const leftHandSide = this.evaluateRuntimeValue(expression.left, id); const rightHandSide = this.evaluateRuntimeValue(expression.right, id); @@ -396,6 +510,13 @@ export class Runtime { return { type: ValueType.Boolean, value: result } as BooleanValue; } + /** + * Evaluates a conditional expression AST node + * + * @param expression conditional expression AST node + * @param id id of the current agent + * @returns runtime value of the conditional expression AST node + */ private evaluateConditionalExpression(expression: ConditionalExpression, id: string): RuntimeValue { const condition = this.evaluateRuntimeValue(expression.condition, id); @@ -412,6 +533,13 @@ export class Runtime { return this.evaluateRuntimeValue(result, id); } + /** + * Evaluates a call expression AST node + * + * @param expression call expression AST node + * @param id id of the current agent + * @returns runtime value of the call expression AST node + */ private evaluateCallExpression(expression: CallExpression, id: string): RuntimeValue { if (expression.caller.type !== NodeType.Identifier) { throw new ErrorRuntime("Function caller must be an identifier", expression.position); @@ -433,6 +561,13 @@ export class Runtime { return (func as FunctionValue).call(args); } + /** + * Evaluates a set comprehension expression AST node + * + * @param expression set comprehension expression AST node + * @param id id of the current agent + * @returns runtime value of the set comprehension expression AST node + */ private evaluateSetComprehensionExpression(expression: SetComprehensionExpression, id: string): RuntimeValue { const agents: RuntimeValue = this.evaluateRuntimeValue(expression.base, id); const param: IdentifierValue = { @@ -461,6 +596,13 @@ export class Runtime { return setComprehensionValue; } + /** + * Evaluates a member expression AST node + * + * @param expression member expression AST node + * @param id id of the current agent + * @returns runtime value of the member expression AST node + */ private evaluateMemberExpression(expression: MemberExpression, id: string): RuntimeValue { const caller = this.evaluateRuntimeValue(expression.caller, id); @@ -488,6 +630,13 @@ export class Runtime { return value; } + /** + * Evaluates an otherwise expression AST node + * + * @param expression otherwise expression AST node + * @param id id of the current agent + * @returns runtime value of the otherwise expression AST node + */ private evaluateOtherwiseExpression(expression: OtherwiseExpression, id: string): RuntimeValue { this.inOtherwiseExpression = true; const left = this.evaluateRuntimeValue(expression.left, id); @@ -500,8 +649,12 @@ export class Runtime { return left; } - // utility functions - + /** + * Asserts a statement AST node to be a define declaration AST node + * + * @param statement statement AST node to assert + * @returns define declaration AST node + */ private getDefineDeclaration(statement: Statement): DefineDeclaration { if (statement.type !== NodeType.DefineDeclaration) { throw new ErrorRuntime("Only object or define declarations are allowed in program body", statement.position); @@ -510,6 +663,12 @@ export class Runtime { return statement as DefineDeclaration; } + /** + * Asserts a statement AST node to be an object declaration AST node + * + * @param statement statement AST node to assert + * @returns object declaration AST node + */ private getObjectDeclaration(statement: Statement): ObjectDeclaration { if (statement.type !== NodeType.ObjectDeclaration) { throw new ErrorRuntime("Only object or define declarations are allowed in program body", statement.position); @@ -518,6 +677,12 @@ export class Runtime { return statement as ObjectDeclaration; } + /** + * Asserts a statement AST node to be a variable declaration AST node + * + * @param statement statement AST node to assert + * @returns variable declaration AST node + */ private getVariableDeclaration(statement: Statement): VariableDeclaration { if (statement.type !== NodeType.VariableDeclaration) { throw new ErrorRuntime("Only variable declarations are allowed in object declaration body", statement.position); @@ -526,14 +691,22 @@ export class Runtime { return statement as VariableDeclaration; } - private customModulo(a: number, b: number): number { + /** + * Custom modulo used in modulo binary expression evaluation + * + * @param a left numeric operand + * @param b right numeric operand + * @returns result of the modulo arithmetic operation + */ + private modulo(a: number, b: number): number { return ((a % b) + b) % b; } /** * Finds an agent in the array of agents from the previous step * - * @param id - id of the agent that is searched + * @param id id of the agent + * @throws runtime error if the agent with the given id was not found * @returns agent with the specified id */ private findAgent(id: string): RuntimeAgent { @@ -547,10 +720,10 @@ export class Runtime { } /** - * Generates a unique agent identifier + * Generates a unique agent identifier (id) * - * @param identifier - object declaration identifier key - * @param id - numeric identifier key + * @param identifier object declaration identifier + * @param id numeric identifier (index) * @returns unique agent identifier */ private generateAgentId(identifier: string, id: number): string { @@ -558,9 +731,9 @@ export class Runtime { } /** - * Replaces the current AST with the specified AST + * Replaces the current program AST node with the given program AST node * - * @param program - program to use as replacement + * @param program program AST node to use as replacement */ public setProgram(program: Program): void { this.program = program; @@ -574,16 +747,19 @@ export class Runtime { } /** - * Updates a specific property value in a specific agent + * Updates a specific property value in a specific agent instance * - * @param agentIndex - index of the agent - * @param propertyIdentifier - identifier of the property - * @param value - new value to be used for the agent's property value + * @param agentIndex index of the agent + * @param propertyIdentifier identifier of the property + * @param value new value to be used for the agent's property value */ public updateAgentValue(agentIndex: number, propertyIdentifier: string, value: number): void { this.previousAgents[agentIndex].variables.set(propertyIdentifier, { type: ValueType.Number, value } as NumberValue); } + /** + * Updates the previous and current output at the end of a step + */ private updateOutput(): void { if (this.output.step === 0) { return; @@ -593,26 +769,49 @@ export class Runtime { this.output.agents = []; } - // runtime functions - + /** + * Initializes runtime functions (step, index, agents) + */ private initializeRuntimeFunctions(): void { this.globalEnvironment.declareVariable("step", createGlobalFunction(this.createStepFunction(0))); this.globalEnvironment.declareVariable("agents", createGlobalFunction(this.createAgentsFunction([], ""))); this.globalEnvironment.declareVariable("index", createGlobalFunction(this.createIndexFunction(0))); } + /** + * Provides new data to the index function + * + * @param index index value to provide + */ private updateIndexFunction(index: number): void { this.globalEnvironment.assignVariable("index", createGlobalFunction(this.createIndexFunction(index))); } + /** + * Provides new data to the agents function + * + * @param agents agents value to provide + * @param id id of the current agent + */ private updateAgentsFunction(agents: RuntimeAgent[], id: string): void { this.globalEnvironment.assignVariable("agents", createGlobalFunction(this.createAgentsFunction(agents, id))); } + /** + * Provides new data to the step function + * + * @param step step value to provide + */ private updateStepFunction(step: number): void { this.globalEnvironment.assignVariable("step", createGlobalFunction(this.createStepFunction(step))); } + /** + * Creates the global index function + * + * @param index initial index value + * @returns reference to the index function + */ private createIndexFunction(index: number): FunctionCall { function indexFunction(args: RuntimeValue[]): RuntimeValue { if (args.length !== 0) { @@ -625,6 +824,13 @@ export class Runtime { return indexFunction; } + /** + * Creates the global agents function + * + * @param index initial agents value + * @param id id of the current agent + * @returns reference to the agents function + */ private createAgentsFunction(agents: RuntimeAgent[], id: string): FunctionCall { function agentsFunction(args: RuntimeValue[]): RuntimeValue { if (args.length !== 1) { @@ -646,6 +852,12 @@ export class Runtime { return agentsFunction; } + /** + * Creates the global step function + * + * @param index initial step value + * @returns reference to the step function + */ private createStepFunction(step: number): FunctionCall { function stepFunction(args: RuntimeValue[]): RuntimeValue { if (args.length !== 0) { diff --git a/src/symbolizer/model/position.ts b/src/symbolizer/model/position.ts index 4c3346f..ad71db4 100644 --- a/src/symbolizer/model/position.ts +++ b/src/symbolizer/model/position.ts @@ -1,5 +1,10 @@ +/** + * An object representing position in the source code + */ export interface Position { + /** line index */ line: number; + /** character index */ character: number; } \ No newline at end of file diff --git a/src/symbolizer/model/symbol.ts b/src/symbolizer/model/symbol.ts index 6aca24a..80f2bf2 100644 --- a/src/symbolizer/model/symbol.ts +++ b/src/symbolizer/model/symbol.ts @@ -1,6 +1,11 @@ import { Position } from "./position.ts"; +/** + * An object representing one symbol in the source code + */ export interface Symbol { + /** value of the symbol */ value: string; + /** position of the symbol in source code */ position: Position; } \ No newline at end of file diff --git a/src/utils/error/error.ts b/src/utils/error/error.ts index c42dc01..51a56f7 100644 --- a/src/utils/error/error.ts +++ b/src/utils/error/error.ts @@ -16,6 +16,11 @@ export class ErrorModel extends Error { this.position = position; } + /** + * Converts the error to a string representation suitable for presenting to the user + * + * @returns string representation of the error + */ public toString(): string { if (!this.position) { return `${this.type} Error: ${this.about}`; diff --git a/src/utils/formatter.ts b/src/utils/formatter.ts index e992c31..56d90d2 100644 --- a/src/utils/formatter.ts +++ b/src/utils/formatter.ts @@ -8,6 +8,12 @@ export class Formatter { private static binaryOperatorPrecedence: { [key: string]: number } = { "+": 1, "-": 1, "*": 2, "/": 2, "%": 2 }; private static logicalOperatorPrecedence: { [key: string]: number } = { "and": 1, "or": 2 }; + /** + * Formats the source code based on pre-defined AgentLang formatting guidelines + * + * @param sourceCode source code to format + * @returns formatted source code + */ public static getFormatted(sourceCode: string): string { Formatter.line = 1; @@ -15,6 +21,11 @@ export class Formatter { return Formatter.nodeToSourceCode(program) } + /** + * + * @param ast AST node to convert to source code + * @returns source code of the formatted AST node + */ public static nodeToSourceCode(ast: ParserValue): string { let sourceCode = ""; @@ -75,13 +86,11 @@ export class Formatter { let left = Formatter.nodeToSourceCode(binaryExpression.left); let right = Formatter.nodeToSourceCode(binaryExpression.right); - // handle left parentheses if (binaryExpression.left.type === NodeType.BinaryExpression) { const needsLeftParentheses = Formatter.binaryOperatorPrecedence[operator] > Formatter.binaryOperatorPrecedence[(binaryExpression.left as BinaryExpression).operator]; left = needsLeftParentheses ? `(${left})` : left; } - // handle right parentheses if (binaryExpression.right.type === NodeType.BinaryExpression) { const needsLeftParentheses = Formatter.binaryOperatorPrecedence[operator] > Formatter.binaryOperatorPrecedence[(binaryExpression.right as BinaryExpression).operator]; right = needsLeftParentheses ? `(${right})` : right; @@ -111,13 +120,11 @@ export class Formatter { } else if (logicalExpression.left.type === NodeType.LogicalExpression && logicalExpression.right.type !== NodeType.LogicalExpression) { // Do nothing } else { - // handle left parentheses if (logicalExpression.left.type === NodeType.LogicalExpression) { const needsLeftParentheses = Formatter.logicalOperatorPrecedence[operator] > Formatter.logicalOperatorPrecedence[(logicalExpression.left as LogicalExpression).operator]; left = needsLeftParentheses ? `(${left})` : left; } - // handle right parentheses if (logicalExpression.right.type === NodeType.LogicalExpression) { const needsLeftParentheses = Formatter.logicalOperatorPrecedence[operator] > Formatter.logicalOperatorPrecedence[(logicalExpression.right as LogicalExpression).operator]; right = needsLeftParentheses ? `(${right})` : right; @@ -208,6 +215,12 @@ export class Formatter { return offset; } + /** + * Converts source code to program AST node + * + * @param sourceCode source code to converts + * @returns program AST node + */ private static getProgram(sourceCode: string): Program { const symbolizer: Symbolizer = new Symbolizer(sourceCode); const symbols: Symbol[] = symbolizer.symbolize();