From f49f8d09665c6b74e30b416b423400fadd5aa54c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Bo=C4=8Fa?= Date: Sun, 3 Dec 2023 18:05:26 +0100 Subject: [PATCH] feat(otherwise): Added otherwise expression for null value handling --- ast.json | 2 +- code.txt | 36 ++++++------------ src/parser/parser.ts | 24 ++++++++++-- src/parser/parser.types.ts | 7 ++++ src/parser/{ => topology}/optimizer.ts | 4 +- src/parser/{ => topology}/topology.ts | 2 +- src/runtime/runtime.ts | 52 ++++++++++++++++++++++++++ src/runtime/runtime.types.ts | 6 ++- src/utils/functions.ts | 11 ++---- 9 files changed, 103 insertions(+), 41 deletions(-) rename src/parser/{ => topology}/optimizer.ts (97%) rename src/parser/{ => topology}/topology.ts (96%) diff --git a/ast.json b/ast.json index f5ada57..340da34 100644 --- a/ast.json +++ b/ast.json @@ -1 +1 @@ -{"type":"Program","body":[{"type":"ObjectDeclaration","identifier":"person","count":30,"body":[{"type":"VariableDeclaration","variableType":"Const","identifier":"speed","value":{"type":"NumericLiteral","value":4}},{"type":"VariableDeclaration","variableType":"Property","identifier":"angle","value":{"type":"BinaryExpression","left":{"type":"Identifier","identifier":"angle"},"right":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"choice"},"args":[{"type":"UnaryExpression","operator":"-","value":{"type":"NumericLiteral","value":0.1}},{"type":"NumericLiteral","value":0.1}]},"operator":"+"},"default":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"random"},"args":[{"type":"NumericLiteral","value":0},{"type":"BinaryExpression","left":{"type":"NumericLiteral","value":2},"right":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"pi"},"args":[]},"operator":"*"}]}},{"type":"VariableDeclaration","variableType":"Property","identifier":"shouldStay","value":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"prob"},"args":[{"type":"NumericLiteral","value":0.5}]}},{"type":"VariableDeclaration","variableType":"Property","identifier":"xNew","value":{"type":"BinaryExpression","left":{"type":"BinaryExpression","left":{"type":"Identifier","identifier":"x"},"right":{"type":"BinaryExpression","left":{"type":"Identifier","identifier":"speed"},"right":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"cos"},"args":[{"type":"Identifier","identifier":"angle"}]},"operator":"*"},"operator":"+"},"right":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"width"},"args":[]},"operator":"%"},"default":{"type":"NumericLiteral","value":0}},{"type":"VariableDeclaration","variableType":"Property","identifier":"yNew","value":{"type":"BinaryExpression","left":{"type":"BinaryExpression","left":{"type":"Identifier","identifier":"y"},"right":{"type":"BinaryExpression","left":{"type":"Identifier","identifier":"speed"},"right":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"sin"},"args":[{"type":"Identifier","identifier":"angle"}]},"operator":"*"},"operator":"+"},"right":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"height"},"args":[]},"operator":"%"},"default":{"type":"NumericLiteral","value":0}},{"type":"VariableDeclaration","variableType":"Property","identifier":"x","value":{"type":"ConditionalExpression","condition":{"type":"Identifier","identifier":"shouldStay"},"consequent":{"type":"Identifier","identifier":"x"},"alternate":{"type":"Identifier","identifier":"xNew"}},"default":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"random"},"args":[{"type":"NumericLiteral","value":50},{"type":"BinaryExpression","left":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"width"},"args":[]},"right":{"type":"NumericLiteral","value":50},"operator":"-"}]}},{"type":"VariableDeclaration","variableType":"Property","identifier":"y","value":{"type":"ConditionalExpression","condition":{"type":"Identifier","identifier":"shouldStay"},"consequent":{"type":"Identifier","identifier":"y"},"alternate":{"type":"Identifier","identifier":"yNew"}},"default":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"random"},"args":[{"type":"NumericLiteral","value":50},{"type":"BinaryExpression","left":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"height"},"args":[]},"right":{"type":"NumericLiteral","value":50},"operator":"-"}]}},{"type":"VariableDeclaration","variableType":"Const","identifier":"distance","value":{"type":"NumericLiteral","value":20}},{"type":"VariableDeclaration","variableType":"Property","identifier":"people","value":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"agents"},"args":[{"type":"Identifier","identifier":"person"}]}},{"type":"VariableDeclaration","variableType":"Property","identifier":"closePeople","value":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"filter"},"args":[{"type":"LambdaExpression","base":{"type":"Identifier","identifier":"people"},"param":"p","value":{"type":"BinaryExpression","left":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"sqrt"},"args":[{"type":"BinaryExpression","left":{"type":"BinaryExpression","left":{"type":"BinaryExpression","left":{"type":"MemberExpression","caller":{"type":"Identifier","identifier":"p"},"value":{"type":"Identifier","identifier":"x"}},"right":{"type":"Identifier","identifier":"x"},"operator":"-"},"right":{"type":"BinaryExpression","left":{"type":"MemberExpression","caller":{"type":"Identifier","identifier":"p"},"value":{"type":"Identifier","identifier":"x"}},"right":{"type":"Identifier","identifier":"x"},"operator":"-"},"operator":"*"},"right":{"type":"BinaryExpression","left":{"type":"BinaryExpression","left":{"type":"MemberExpression","caller":{"type":"Identifier","identifier":"p"},"value":{"type":"Identifier","identifier":"y"}},"right":{"type":"Identifier","identifier":"y"},"operator":"-"},"right":{"type":"BinaryExpression","left":{"type":"MemberExpression","caller":{"type":"Identifier","identifier":"p"},"value":{"type":"Identifier","identifier":"y"}},"right":{"type":"Identifier","identifier":"y"},"operator":"-"},"operator":"*"},"operator":"+"}]},"right":{"type":"Identifier","identifier":"distance"},"operator":"<="}}]}},{"type":"VariableDeclaration","variableType":"Property","identifier":"closeInfected","value":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"filter"},"args":[{"type":"LambdaExpression","base":{"type":"Identifier","identifier":"closePeople"},"param":"c","value":{"type":"BinaryExpression","left":{"type":"MemberExpression","caller":{"type":"Identifier","identifier":"c"},"value":{"type":"Identifier","identifier":"infected"}},"right":{"type":"BooleanLiteral","value":true},"operator":"=="}}]}},{"type":"VariableDeclaration","variableType":"Const","identifier":"timespan","value":{"type":"NumericLiteral","value":200}},{"type":"VariableDeclaration","variableType":"Property","identifier":"remaining","value":{"type":"ConditionalExpression","condition":{"type":"Identifier","identifier":"infected"},"consequent":{"type":"BinaryExpression","left":{"type":"Identifier","identifier":"remaining"},"right":{"type":"NumericLiteral","value":1},"operator":"-"},"alternate":{"type":"Identifier","identifier":"timespan"}},"default":{"type":"Identifier","identifier":"timespan"}},{"type":"VariableDeclaration","variableType":"Property","identifier":"shouldInfect","value":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"prob"},"args":[{"type":"NumericLiteral","value":0.4}]}},{"type":"VariableDeclaration","variableType":"Property","identifier":"infected","value":{"type":"LogicalExpression","left":{"type":"LogicalExpression","left":{"type":"Identifier","identifier":"infected"},"right":{"type":"BinaryExpression","left":{"type":"Identifier","identifier":"remaining"},"right":{"type":"NumericLiteral","value":0},"operator":">"},"operator":"and"},"right":{"type":"LogicalExpression","left":{"type":"BinaryExpression","left":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"count"},"args":[{"type":"Identifier","identifier":"closeInfected"}]},"right":{"type":"NumericLiteral","value":0},"operator":">"},"right":{"type":"Identifier","identifier":"shouldInfect"},"operator":"and"},"operator":"or"},"default":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"prob"},"args":[{"type":"NumericLiteral","value":0.5}]}},{"type":"VariableDeclaration","variableType":"Property","identifier":"coloured","value":{"type":"Identifier","identifier":"infected"},"default":{"type":"BooleanLiteral","value":false}}]}]} \ No newline at end of file +{"type":"Program","body":[{"type":"ObjectDeclaration","identifier":"person","count":10,"body":[{"type":"VariableDeclaration","variableType":"Const","identifier":"x","value":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"random"},"args":[{"type":"NumericLiteral","value":0},{"type":"CallExpression","caller":{"type":"Identifier","identifier":"width"},"args":[]}]}},{"type":"VariableDeclaration","variableType":"Const","identifier":"y","value":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"random"},"args":[{"type":"NumericLiteral","value":0},{"type":"CallExpression","caller":{"type":"Identifier","identifier":"height"},"args":[]}]}},{"type":"VariableDeclaration","variableType":"Property","identifier":"close","value":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"filter"},"args":[{"type":"LambdaExpression","base":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"agents"},"args":[{"type":"Identifier","identifier":"person"}]},"param":"p","value":{"type":"LogicalExpression","left":{"type":"BinaryExpression","left":{"type":"MemberExpression","caller":{"type":"Identifier","identifier":"p"},"value":{"type":"Identifier","identifier":"x"}},"right":{"type":"NumericLiteral","value":0},"operator":">="},"right":{"type":"BinaryExpression","left":{"type":"MemberExpression","caller":{"type":"Identifier","identifier":"p"},"value":{"type":"Identifier","identifier":"x"}},"right":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"width"},"args":[]},"operator":"<="},"operator":"and"}}]}},{"type":"VariableDeclaration","variableType":"Property","identifier":"closest","value":{"type":"CallExpression","caller":{"type":"Identifier","identifier":"min"},"args":[{"type":"LambdaExpression","base":{"type":"Identifier","identifier":"close"},"param":"c","value":{"type":"MemberExpression","caller":{"type":"Identifier","identifier":"c"},"value":{"type":"Identifier","identifier":"x"}}}]}},{"type":"VariableDeclaration","variableType":"Property","identifier":"color","value":{"type":"BooleanLiteral","value":true}},{"type":"VariableDeclaration","variableType":"Property","identifier":"coloured","value":{"type":"OtherwiseExpression","left":{"type":"MemberExpression","caller":{"type":"Identifier","identifier":"closest"},"value":{"type":"Identifier","identifier":"color"}},"right":{"type":"BooleanLiteral","value":false}}}]}]} \ No newline at end of file diff --git a/code.txt b/code.txt index 9da9cb1..a6e1d59 100644 --- a/code.txt +++ b/code.txt @@ -1,26 +1,12 @@ -agent person 30 { - const speed = 4; - property angle: random(0, 2 * pi()) = angle + choice(-0.1, 0.1); - - property shouldStay = prob(0.5); - - property xNew: 0 = (x + speed * cos(angle)) % width(); - property yNew: 0 = (y + speed * sin(angle)) % height(); - - property x: random(50, width() - 50) = if shouldStay then x else xNew; - property y: random(50, height() - 50) = if shouldStay then y else yNew; - - const distance = 20; - - property people = agents(person); - property closePeople = filter(people => p => sqrt((p.x - x) * (p.x - x) + (p.y - y) * (p.y - y)) <= distance); - property closeInfected = filter(closePeople => c => c.infected == true); - - const timespan = 200; - property remaining: timespan = if infected then remaining - 1 else timespan; - - property shouldInfect = prob(0.4); - property infected: prob(0.5) = (infected and remaining > 0) or (count(closeInfected) > 0 and shouldInfect); - - property coloured: false = infected; + +agent person 10 { + const x = random(0, width()); + const y = random(0, height()); + + property close = filter(agents(person) => p => p.x >= 0 and p.x <= width()); + property closest = min(close => c => c.x); + + property color = true; + + property coloured = closest.color otherwise false; } \ No newline at end of file diff --git a/src/parser/parser.ts b/src/parser/parser.ts index 42f3d2d..e107785 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -1,8 +1,8 @@ import { exit } from "process"; import { Token, TokenType } from "../lexer/lexer.types"; -import { BinaryExpression, BooleanLiteral, CallExpression, ConditionalExpression, Expression, Identifier, LambdaExpression, LogicalExpression, MemberExpression, NodeType, NumericLiteral, ObjectDeclaration, ParserValue, Program, Statement, UnaryExpression, VariableDeclaration, VariableType } from "./parser.types"; +import { BinaryExpression, BooleanLiteral, CallExpression, ConditionalExpression, Expression, Identifier, LambdaExpression, LogicalExpression, MemberExpression, NodeType, NumericLiteral, ObjectDeclaration, OtherwiseExpression, ParserValue, Program, Statement, UnaryExpression, VariableDeclaration, VariableType } from "./parser.types"; import { Position } from "../symbolizer/symbolizer.types"; -import { getProgram } from "./optimizer"; +import { getProgram } from "./topology/optimizer"; import { ErrorParser } from "../utils/errors"; export class Parser { @@ -141,7 +141,25 @@ export class Parser { } private parseExpression(): ParserValue { - return this.parseLambdaExpression(); + return this.parseOtherwiseExpression(); + } + + private parseOtherwiseExpression(): ParserValue { + const left = this.parseLambdaExpression(); + + if (this.at().type === TokenType.Otherwise) { + this.next(); + + const right = this.parseLambdaExpression(); + + return { + type: NodeType.OtherwiseExpression, + left, + right + } as OtherwiseExpression; + } + + return left; } private parseLambdaExpression(): ParserValue { diff --git a/src/parser/parser.types.ts b/src/parser/parser.types.ts index 703c7e8..16a8985 100644 --- a/src/parser/parser.types.ts +++ b/src/parser/parser.types.ts @@ -20,6 +20,7 @@ export enum NodeType { CallExpression = "CallExpression", LambdaExpression = "LambdaExpression", MemberExpression = "MemberExpression", + OtherwiseExpression = "OtherwiseExpression", } export interface ParserValue { @@ -96,6 +97,12 @@ export interface MemberExpression extends Expression { value: Expression; } +export interface OtherwiseExpression extends Expression { + type: NodeType.OtherwiseExpression; + left: Expression; + right: Expression; +} + export interface Identifier extends Expression { type: NodeType.Identifier; identifier: string; diff --git a/src/parser/optimizer.ts b/src/parser/topology/optimizer.ts similarity index 97% rename from src/parser/optimizer.ts rename to src/parser/topology/optimizer.ts index 3cd0360..6a39bc3 100644 --- a/src/parser/optimizer.ts +++ b/src/parser/topology/optimizer.ts @@ -1,6 +1,6 @@ -import { BinaryExpression, CallExpression, ConditionalExpression, Expression, Identifier, LambdaExpression, LogicalExpression, MemberExpression, NodeType, ObjectDeclaration, ParserValue, Program, UnaryExpression, VariableDeclaration } from "./parser.types"; +import { BinaryExpression, CallExpression, ConditionalExpression, Expression, Identifier, LambdaExpression, LogicalExpression, MemberExpression, NodeType, ObjectDeclaration, ParserValue, Program, UnaryExpression, VariableDeclaration } from "../parser.types"; import { DependencyGraph, Node, topologicalSort } from "./topology"; -import { ErrorParser } from "../utils/errors"; +import { ErrorParser } from "../../utils/errors"; const agentIdentifiers: string[] = []; diff --git a/src/parser/topology.ts b/src/parser/topology/topology.ts similarity index 96% rename from src/parser/topology.ts rename to src/parser/topology/topology.ts index 367f8cf..afcc922 100644 --- a/src/parser/topology.ts +++ b/src/parser/topology/topology.ts @@ -1,4 +1,4 @@ -import { ErrorParser } from "../utils/errors"; +import { ErrorParser } from "../../utils/errors"; export type DependencyGraph = { [key: string]: Node }; diff --git a/src/runtime/runtime.ts b/src/runtime/runtime.ts index dced727..95781b5 100644 --- a/src/runtime/runtime.ts +++ b/src/runtime/runtime.ts @@ -10,6 +10,7 @@ import { NodeType, NumericLiteral, ObjectDeclaration, + OtherwiseExpression, ParserValue, Program, UnaryExpression, @@ -43,6 +44,8 @@ export class Runtime { private output: RuntimeOutput = { type: ValueType.Output, step: 0, agents: [] }; + private inOtherwiseExpression = false; + constructor(program: Program, environment: Environment) { this.program = program; @@ -167,6 +170,8 @@ export class Runtime { return this.evaluateLambdaExpression(node as LambdaExpression, id); case NodeType.MemberExpression: return this.evaluateMemberExpression(node as MemberExpression, id); + case NodeType.OtherwiseExpression: + return this.evaluateOtherwiseExpression(node as OtherwiseExpression, id); default: throw new ErrorRuntime(`Unknown runtime node '${node.type}'`); } @@ -213,6 +218,16 @@ export class Runtime { const leftHandSide = this.evaluateRuntimeValue(expression.left, id); const rightHandSide = this.evaluateRuntimeValue(expression.right, id); + // handle otherwise expression + if (this.inOtherwiseExpression && leftHandSide.type === ValueType.Null) { + return leftHandSide; + } + + // handle otherwise expression + if (this.inOtherwiseExpression && rightHandSide.type === ValueType.Null) { + return rightHandSide; + } + const isValid = ( (expression.operator === "==" || expression.operator === "!=") && ( @@ -239,6 +254,11 @@ export class Runtime { const operator = expression.operator; const value = this.evaluateRuntimeValue(expression.value, id); + // handle otherwise expression + if (this.inOtherwiseExpression && value.type === ValueType.Null) { + return value; + } + if (operator === "-") { if (value.type !== ValueType.Number) { throw new ErrorRuntime("Unary expression with '-' operator requires numeric value as operand"); @@ -308,6 +328,16 @@ export class Runtime { const leftHandSide = this.evaluateRuntimeValue(expression.left, id); const rightHandSide = this.evaluateRuntimeValue(expression.right, id); + // handle otherwise expression + if (this.inOtherwiseExpression && leftHandSide.type === ValueType.Null) { + return leftHandSide; + } + + // handle otherwise expression + if (this.inOtherwiseExpression && rightHandSide.type === ValueType.Null) { + return rightHandSide; + } + if (leftHandSide.type === ValueType.Boolean && rightHandSide.type === ValueType.Boolean) { let result; @@ -330,6 +360,11 @@ export class Runtime { private evaluateConditionalExpression(expression: ConditionalExpression, id: string): RuntimeValue { const condition = this.evaluateRuntimeValue(expression.condition, id); + // handle otherwise expression + if (this.inOtherwiseExpression && condition.type === ValueType.Null) { + return condition; + } + if (condition.type !== ValueType.Boolean) { throw new ErrorRuntime("Conditional expression requires a boolean expression as its condition"); } @@ -385,6 +420,11 @@ export class Runtime { private evaluateMemberExpression(expression: MemberExpression, id: string): RuntimeValue { const caller = this.evaluateRuntimeValue(expression.caller, id); + // handle otherwise expression + if (this.inOtherwiseExpression && caller.type === ValueType.Null) { + return caller; + } + if (caller.type !== ValueType.Agent) { throw new ErrorRuntime("The caller of member expression must be of type 'agent'"); } @@ -405,6 +445,18 @@ export class Runtime { return value; } + private evaluateOtherwiseExpression(expression: OtherwiseExpression, id: string): RuntimeValue { + this.inOtherwiseExpression = true; + const left = this.evaluateRuntimeValue(expression.left, id); + this.inOtherwiseExpression = false; + + if (left.type === ValueType.Null) { + return this.evaluateRuntimeValue(expression.right, id); + } + + return left; + } + private customModulo(a: number, b: number): number { return ((a % b) + b) % b; } diff --git a/src/runtime/runtime.types.ts b/src/runtime/runtime.types.ts index dda53da..f0ebd77 100644 --- a/src/runtime/runtime.types.ts +++ b/src/runtime/runtime.types.ts @@ -3,7 +3,7 @@ export enum ValueType { Number = "Number", Boolean = "Boolean", Function = "Function", - Void = "Void", + Null = "Null", Agent = "Agent", Agents = "Agents", Lambda = "Lambda", @@ -43,6 +43,10 @@ export interface RuntimeAgent { variables: Map; } +export interface NullValue extends RuntimeValue { + type: ValueType.Null; +} + export interface AgentValue extends RuntimeValue { type: ValueType.Agent; value: RuntimeAgent; diff --git a/src/utils/functions.ts b/src/utils/functions.ts index d5f7519..2a61fc4 100644 --- a/src/utils/functions.ts +++ b/src/utils/functions.ts @@ -5,13 +5,12 @@ import { FunctionCall, FunctionValue, LambdaValue, + NullValue, NumberValue, RuntimeAgent, - RuntimeError, RuntimeValue, ValueType } from "../runtime/runtime.types"; -import {Error} from "./error"; import { ErrorRuntime } from "./errors"; export function createGlobalFunction(call: FunctionCall): FunctionValue { @@ -113,7 +112,7 @@ export function MIN(args: RuntimeValue[]): RuntimeValue { } } - return { type: ValueType.Agent, value: {} } as AgentValue; + return { type: ValueType.Null, value: {} } as NullValue; } export function MAX(args: RuntimeValue[]): RuntimeValue { @@ -150,7 +149,7 @@ export function MAX(args: RuntimeValue[]): RuntimeValue { } } - return { type: ValueType.Agent, value: {} } as AgentValue; + return { type: ValueType.Null, value: {} } as NullValue; } export function FILTER(args: RuntimeValue[]): RuntimeValue { @@ -203,10 +202,6 @@ export function COUNT(args: RuntimeValue[]): RuntimeValue { export function RANDOM(args: RuntimeValue[]): RuntimeValue { const numericArgs: RuntimeValue[] = expectNumericArgs(args, 2); - if (numericArgs.length === 1 && numericArgs[0].type === ValueType.Error) { - return numericArgs[0] as RuntimeError; - } - const min: NumberValue = numericArgs[0] as NumberValue; const max: NumberValue = numericArgs[1] as NumberValue;