From 436d07d90e6df838a42319c3ad97e26d67c3f9ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Bo=C4=8Fa?= Date: Mon, 13 Nov 2023 15:31:51 +0100 Subject: [PATCH] feat(variables): Replaced variable, dynamic and const with property keyword + added topology sort --- ast.json | 35 +-------- code.txt | 6 +- src/interpreter/interpreter.ts | 3 + src/lexer/lexer.keywords.ts | 4 +- src/lexer/lexer.types.ts | 3 +- src/parser/optimizer.ts | 126 +++++++++++++++++++++++++++++++++ src/parser/parser.ts | 84 +++++----------------- src/parser/parser.types.ts | 21 ++---- src/parser/topology.ts | 63 +++++++++++++++++ src/runtime/runtime.ts | 42 ++--------- 10 files changed, 225 insertions(+), 162 deletions(-) create mode 100644 src/parser/optimizer.ts create mode 100644 src/parser/topology.ts diff --git a/ast.json b/ast.json index 5f5ee52..fbb567b 100644 --- a/ast.json +++ b/ast.json @@ -1,34 +1 @@ -{ - "type": "Program", - "body": [ - { - "type": "ObjectDeclaration", - "identifier": "person", - "count": 10, - "body": [ - { - "type": "VariableDeclaration", - "variableType": "Const", - "identifier": "age", - "value": { - "type": "NumericLiteral", - "value": 10 - } - }, - { - "type": "VariableDeclaration", - "variableType": "Dynamic", - "identifier": "hello", - "value": { - "type": "UnaryExpression", - "operator": "-", - "value": { - "type": "NumericLiteral", - "value": 20 - } - } - } - ] - } - ] -} \ No newline at end of file +{"type":"Program","body":[{"type":"ObjectDeclaration","identifier":"person","count":1,"body":[{"type":"VariableDeclaration","identifier":"x","value":{"type":"BinaryExpression","left":{"type":"Identifier","identifier":"x"},"right":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"choice"},"args":[{"type":"UnaryExpression","operator":"-","value":{"type":"NumericLiteral","value":1}},{"type":"NumericLiteral","value":1}]},"operator":"+"},"default":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"round"},"args":[{"type":"CallExpression","caller":{"type":"Identifier","identifier":"random"},"args":[{"type":"NumericLiteral","value":0},{"type":"NumericLiteral","value":10}]}]}}]}]} \ No newline at end of file diff --git a/code.txt b/code.txt index e969460..ed92ed1 100644 --- a/code.txt +++ b/code.txt @@ -1,4 +1,4 @@ -agent person 10 { - - const i = index(); +agent person 1 { + + property x: round(random(0, 10)) = x + choice(-1, 1); } \ No newline at end of file diff --git a/src/interpreter/interpreter.ts b/src/interpreter/interpreter.ts index f64ea0f..bbea208 100644 --- a/src/interpreter/interpreter.ts +++ b/src/interpreter/interpreter.ts @@ -18,6 +18,7 @@ import { Error } from "../utils/error"; import { createGlobalFunction } from "../utils/functions"; import { Symbolizer } from "../symbolizer/symbolizer"; import { Symbol } from "../symbolizer/symbolizer.types"; +import { writeFileSync } from "fs"; export class Interpreter { @@ -37,6 +38,8 @@ export class Interpreter { const parser: Parser = new Parser(lexerOutput.tokens); const program: ParserValue = parser.parse(); + writeFileSync("ast.json", JSON.stringify(program)); + if (program.type === NodeType.Error) { return of(Error.interpreter((program as ParserError).message)); } diff --git a/src/lexer/lexer.keywords.ts b/src/lexer/lexer.keywords.ts index 4213dba..620abf8 100644 --- a/src/lexer/lexer.keywords.ts +++ b/src/lexer/lexer.keywords.ts @@ -3,9 +3,7 @@ import { TokenType } from "./lexer.types"; export const ReservedKeywords: Record = { "agent": TokenType.Agent, - "variable": TokenType.VariableType, - "const": TokenType.VariableType, - "dynamic": TokenType.VariableType, + "property": TokenType.Property, "if": TokenType.If, "then": TokenType.Then, diff --git a/src/lexer/lexer.types.ts b/src/lexer/lexer.types.ts index 1ff5a7b..33b1e09 100644 --- a/src/lexer/lexer.types.ts +++ b/src/lexer/lexer.types.ts @@ -3,8 +3,7 @@ import { Position } from "../symbolizer/symbolizer.types"; export enum TokenType { Agent = "Agent", - - VariableType = "VariableType", + Property = "Property", If = "If", Then = "Then", diff --git a/src/parser/optimizer.ts b/src/parser/optimizer.ts new file mode 100644 index 0000000..ad63802 --- /dev/null +++ b/src/parser/optimizer.ts @@ -0,0 +1,126 @@ +import { BinaryExpression, CallExpression, ConditionalExpression, Expression, Identifier, LambdaExpression, LogicalExpression, MemberExpression, NodeType, ObjectDeclaration, ParserError, ParserValue, Program, UnaryExpression, VariableDeclaration } from "./parser.types"; +import { Error } from "../utils/error"; +import { DependencyGraph, Node, topologicalSort } from "./topology"; + +export function getProgram(program: Program): Program | ParserError { + for (let i = 0; i < program.body.length; i++) { + const declaration: ObjectDeclaration | ParserError = getObjectDeclaration(program.body[i] as ObjectDeclaration); + + if (declaration.type === NodeType.Error) { + return declaration as ParserError; + } + + program.body[i] = declaration; + } + + return program; +} + +function getObjectDeclaration(declaration: ObjectDeclaration): ObjectDeclaration | ParserError { + const objectIdentifiers: string[] = []; + const objectDependencies: string[][] = []; + + for (let i = 0; i < declaration.body.length; i++) { + const variableDeclaration: VariableDeclaration = declaration.body[i] as VariableDeclaration; + + const identifier: string = variableDeclaration.identifier; + const dependencies: string[] = getVariableDependencies(variableDeclaration); + + if (dependencies.includes(identifier) && variableDeclaration.default === undefined) { + return Error.parser("Agent variable depends on itself, but has no default value provided"); + } + + objectIdentifiers.push(identifier); + objectDependencies.push(dependencies); + } + + const nodes: Node[] | ParserError = getSortedDependencies(objectIdentifiers, objectDependencies); + + if (!Array.isArray(nodes)) { + return nodes as ParserError; + } + + const sortedBody: Expression[] = []; + + for (const node of nodes) { + for (const expression of declaration.body) { + if ((expression as VariableDeclaration).identifier === node.identifier) { + sortedBody.push(expression); + break; + } + } + } + + return { ...declaration, body: sortedBody } as ObjectDeclaration; +} + +function getSortedDependencies(identifiers: string[], dependencies: string[][]): Node[] | ParserError { + const graph: DependencyGraph = {}; + + for (const identifier of identifiers) { + graph[identifier] = new Node(identifier); + } + + dependencies.forEach((items: string[], index: number) => { + const identifier = identifiers[index]; + + items.forEach((dependency: string) => { + graph[identifier].addDependency(graph[dependency]); + }); + }); + + return topologicalSort(graph); +} + +function getVariableDependencies(variableDeclaration: VariableDeclaration): string[] { + const dependencies: string[] = []; + + function getDependencies(expression: Expression): void { + switch (expression.type) { + case NodeType.BinaryExpression: { + getDependencies((expression as BinaryExpression).left); + getDependencies((expression as BinaryExpression).right); + break; + } + case NodeType.UnaryExpression: { + getDependencies((expression as UnaryExpression).value); + break; + } + case NodeType.LogicalExpression: { + getDependencies((expression as LogicalExpression).left); + getDependencies((expression as LogicalExpression).right); + break; + } + case NodeType.ConditionalExpression: { + getDependencies((expression as ConditionalExpression).condition); + getDependencies((expression as ConditionalExpression).consequent); + getDependencies((expression as ConditionalExpression).alternate); + break; + } + case NodeType.CallExpression: { + for (const arg of (expression as CallExpression).args) { + getDependencies(arg); + } + break; + } + case NodeType.LambdaExpression: { + getDependencies((expression as LambdaExpression).base); + getDependencies((expression as LambdaExpression).value); + break; + } + case NodeType.MemberExpression: { + getDependencies((expression as MemberExpression).caller); + getDependencies((expression as MemberExpression).value); + break; + } + case NodeType.Identifier: { + dependencies.push((expression as Identifier).identifier); + break; + } + } + } + + getDependencies(variableDeclaration.value); + + return dependencies; +} \ No newline at end of file diff --git a/src/parser/parser.ts b/src/parser/parser.ts index 34043d3..3d1839d 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -1,8 +1,9 @@ import { exit } from "process"; import { Token, TokenType } from "../lexer/lexer.types"; -import { BinaryExpression, BooleanLiteral, CallExpression, ConditionalExpression, Identifier, LambdaExpression, LogicalExpression, MemberExpression, NodeType, NumericLiteral, ObjectDeclaration, ParserError, ParserValue, Program, Statement, UnaryExpression, VariableDeclaration, VariableType } from "./parser.types"; +import { BinaryExpression, BooleanLiteral, CallExpression, ConditionalExpression, Expression, Identifier, LambdaExpression, LogicalExpression, MemberExpression, NodeType, NumericLiteral, ObjectDeclaration, ParserError, ParserValue, Program, Statement, UnaryExpression, VariableDeclaration } from "./parser.types"; import { Error } from "../utils/error"; import { Position } from "../symbolizer/symbolizer.types"; +import { getProgram } from "./optimizer"; export class Parser { @@ -25,7 +26,13 @@ export class Parser { program.body.push(statement as Statement); } - return program; + const updated: Program | ParserError = getProgram(program); + + if (updated.type === NodeType.Error) { + return updated as ParserError; + } + + return updated; } private parseStatement(): ParserValue { @@ -66,7 +73,7 @@ export class Parser { while (this.at().type !== TokenType.CloseBrace) { switch (this.at().type) { - case TokenType.VariableType: + case TokenType.Property: const declaration: ParserValue = this.parseVariableDeclaration(); if (this.isError(declaration)) { @@ -95,11 +102,11 @@ export class Parser { } private parseVariableDeclaration(): ParserValue { - if (this.isNotOf(TokenType.VariableType)) { - return Error.parser("Expected variable type at the beginning of variable declaration", this.position()) as ParserError; + if (this.isNotOf(TokenType.Property)) { + return Error.parser("Expected prop keyword at the beginning of variable declaration", this.position()) as ParserError; } - const variableType = this.next().value; + this.next(); if (this.isNotOf(TokenType.Identifier)) { return Error.parser("Expected identifier after variable type in variable declaration", this.position()) as ParserError; @@ -108,13 +115,8 @@ export class Parser { const identifier = this.next().value; let defaultValue: ParserValue | undefined; - if (variableType === "variable") { - if (this.isNotOf(TokenType.Colon)) { - return Error.parser("Expected a colon after identifier in variable type declaration", this.position()) as ParserError; - } - + if (this.at().type === TokenType.Colon) { this.next(); - defaultValue = this.parseExpression(); if (this.isError(defaultValue)) { @@ -130,10 +132,6 @@ export class Parser { const value: ParserValue = this.parseExpression(); - if (variableType === "const" && !this.isConstValueValid(value)) { - return Error.parser("Const cannot contain identifiers", this.position()); - } - if (this.isError(value)) { return value as ParserError; } @@ -144,22 +142,11 @@ export class Parser { this.next(); - function getVariableType() { - if (variableType === "variable") { - return VariableType.Variable; - } else if (variableType === "dynamic") { - return VariableType.Dynamic; - } else { - return VariableType.Const; - } - } - return { type: NodeType.VariableDeclaration, - variableType: getVariableType(), identifier, - default: defaultValue, - value + value, + default: defaultValue } as VariableDeclaration; } @@ -550,43 +537,4 @@ export class Parser { private createEmptyProgram(): Program { return { type: NodeType.Program, body: [] }; } - - private isConstValueValid(value: ParserValue): boolean { - if (value.type === NodeType.Identifier) { - return false; - } - - if (value.type === NodeType.BinaryExpression) { - const left = this.isConstValueValid((value as BinaryExpression).left); - const right = this.isConstValueValid((value as BinaryExpression).right); - return left && right; - } - - if (value.type === NodeType.LogicalExpression) { - const left = this.isConstValueValid((value as LogicalExpression).left); - const right = this.isConstValueValid((value as LogicalExpression).right); - return left && right; - } - - if (value.type === NodeType.ConditionalExpression) { - const condition = this.isConstValueValid((value as ConditionalExpression).condition); - const consequent = this.isConstValueValid((value as ConditionalExpression).consequent); - const alternate = this.isConstValueValid((value as ConditionalExpression).alternate); - return condition && consequent && alternate; - } - - if (value.type === NodeType.CallExpression) { - for (const arg of (value as CallExpression).args) { - const result = this.isConstValueValid(arg as ParserValue); - - if (!result) { - return false; - } - } - - return true; - } - - return true; - } } \ No newline at end of file diff --git a/src/parser/parser.types.ts b/src/parser/parser.types.ts index 3a616ef..1503b1a 100644 --- a/src/parser/parser.types.ts +++ b/src/parser/parser.types.ts @@ -19,12 +19,6 @@ export enum NodeType { MemberExpression = "MemberExpression", } -export enum VariableType { - Const = "Const", - Variable = "Variable", - Dynamic = "Dynamic" -} - export interface ParserValue { type: NodeType; } @@ -43,14 +37,6 @@ export interface Program extends Statement { body: Statement[]; } -export interface VariableDeclaration extends Statement { - type: NodeType.VariableDeclaration; - variableType: VariableType; - identifier: string; - default?: Expression; - value: Expression; -} - export interface ObjectDeclaration extends Statement { type: NodeType.ObjectDeclaration; identifier: string; @@ -58,6 +44,13 @@ export interface ObjectDeclaration extends Statement { body: Expression[]; } +export interface VariableDeclaration extends Statement { + type: NodeType.VariableDeclaration; + identifier: string; + value: Expression; + default?: Expression; +} + export interface BinaryExpression extends Expression { type: NodeType.BinaryExpression; left: Expression; diff --git a/src/parser/topology.ts b/src/parser/topology.ts new file mode 100644 index 0000000..e956bad --- /dev/null +++ b/src/parser/topology.ts @@ -0,0 +1,63 @@ +import { ParserError } from "./parser.types"; +import { Error } from "../utils/error"; + +export type DependencyGraph = { [key: string]: Node }; + +export class Node { + public identifier: string; + public dependencies: Node[]; + + constructor(identifier: string) { + this.identifier = identifier; + this.dependencies = []; + } + + public addDependency(node: Node): void { + this.dependencies.push(node); + } +} + +export function topologicalSort(graph: DependencyGraph): Node[] | ParserError { + const visited: { [key: string]: boolean } = {}; + const recursionStack: { [key: string]: boolean } = {}; + const result: Node[] = []; + + let containsCycle = false; + + function isSelfLoop(node: Node): boolean { + return node.dependencies.some(dep => dep === node); + } + + function visit(node: Node) { + if (recursionStack[node.identifier]) { + containsCycle = true; + return; + } + + if (visited[node.identifier]) { + return; + } + + visited[node.identifier] = true; + recursionStack[node.identifier] = true; + + for (const dependency of node.dependencies) { + if (!isSelfLoop(dependency)) { + visit(dependency); + } + } + + recursionStack[node.identifier] = false; + result.push(node); + } + + for (const key in graph) { + visit(graph[key]); + } + + if (containsCycle) { + return Error.parser("Agent variables contain a dependency loop"); + } + + return result; +} \ No newline at end of file diff --git a/src/runtime/runtime.ts b/src/runtime/runtime.ts index dc825fd..deee889 100644 --- a/src/runtime/runtime.ts +++ b/src/runtime/runtime.ts @@ -14,7 +14,6 @@ import { Program, UnaryExpression, VariableDeclaration, - VariableType } from "../parser/parser.types"; import { AgentsValue, @@ -137,44 +136,11 @@ export class Runtime { } private evaluateVariableDeclaration(declaration: VariableDeclaration, id: string): RuntimeValue { - switch (declaration.variableType) { - case VariableType.Variable: { - if (declaration.default && this.output.step === 0) { - return this.evaluateRuntimeValue(declaration.default, id); - } - - return this.evaluateRuntimeValue(declaration.value, id); - } - case VariableType.Dynamic: { - if (this.output.step === 0) { - return { type: ValueType.Void } as VoidValue; - } - - return this.evaluateRuntimeValue(declaration.value, id); - } - case VariableType.Const: { - if (this.output.step === 0) { - return this.evaluateRuntimeValue(declaration.value, id); - } - - const agent = this.getAgent(id, this.output); - - if (!agent) { - return Error.runtime("Agent with the provided id not found"); - } - - const previousConstValue: RuntimeValue | undefined = agent.variables.get(declaration.identifier); - - if (!previousConstValue) { - return Error.runtime("Previous agent value does not exist"); - } - - return previousConstValue; - } - default: { - return Error.runtime("Unrecognized variable type in variable declaration") as RuntimeError; - } + if (declaration.default && this.output.step === 0) { + return this.evaluateRuntimeValue(declaration.default, id); } + + return this.evaluateRuntimeValue(declaration.value, id); } private evaluateRuntimeValue(node: ParserValue, id: string): RuntimeValue {