Skip to content

Commit

Permalink
feat(variables): Replaced variable, dynamic and const with property k…
Browse files Browse the repository at this point in the history
…eyword + added topology sort
  • Loading branch information
TomasBoda committed Nov 13, 2023
1 parent 358e2af commit 436d07d
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 162 deletions.
35 changes: 1 addition & 34 deletions ast.json
Original file line number Diff line number Diff line change
@@ -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
}
}
}
]
}
]
}
{"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}]}]}}]}]}
6 changes: 3 additions & 3 deletions code.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
agent person 10 {
const i = index();
agent person 1 {

property x: round(random(0, 10)) = x + choice(-1, 1);
}
3 changes: 3 additions & 0 deletions src/interpreter/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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));
}
Expand Down
4 changes: 1 addition & 3 deletions src/lexer/lexer.keywords.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import { TokenType } from "./lexer.types";
export const ReservedKeywords: Record<string, TokenType> = {
"agent": TokenType.Agent,

"variable": TokenType.VariableType,
"const": TokenType.VariableType,
"dynamic": TokenType.VariableType,
"property": TokenType.Property,

"if": TokenType.If,
"then": TokenType.Then,
Expand Down
3 changes: 1 addition & 2 deletions src/lexer/lexer.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { Position } from "../symbolizer/symbolizer.types";

export enum TokenType {
Agent = "Agent",

VariableType = "VariableType",
Property = "Property",

If = "If",
Then = "Then",
Expand Down
126 changes: 126 additions & 0 deletions src/parser/optimizer.ts
Original file line number Diff line number Diff line change
@@ -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;
}
84 changes: 16 additions & 68 deletions src/parser/parser.ts
Original file line number Diff line number Diff line change
@@ -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 {

Expand All @@ -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 {
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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;
Expand All @@ -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)) {
Expand All @@ -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;
}
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
}
}
Loading

0 comments on commit 436d07d

Please sign in to comment.