Skip to content

Commit

Permalink
feat(otherwise): Added otherwise expression for null value handling
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasBoda committed Dec 3, 2023
1 parent c56ba7d commit f49f8d0
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 41 deletions.
2 changes: 1 addition & 1 deletion ast.json
Original file line number Diff line number Diff line change
@@ -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}}]}]}
{"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}}}]}]}
36 changes: 11 additions & 25 deletions code.txt
Original file line number Diff line number Diff line change
@@ -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;
}
24 changes: 21 additions & 3 deletions src/parser/parser.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions src/parser/parser.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export enum NodeType {
CallExpression = "CallExpression",
LambdaExpression = "LambdaExpression",
MemberExpression = "MemberExpression",
OtherwiseExpression = "OtherwiseExpression",
}

export interface ParserValue {
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/parser/optimizer.ts → src/parser/topology/optimizer.ts
Original file line number Diff line number Diff line change
@@ -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[] = [];

Expand Down
2 changes: 1 addition & 1 deletion src/parser/topology.ts → src/parser/topology/topology.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ErrorParser } from "../utils/errors";
import { ErrorParser } from "../../utils/errors";

export type DependencyGraph = { [key: string]: Node };

Expand Down
52 changes: 52 additions & 0 deletions src/runtime/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
NodeType,
NumericLiteral,
ObjectDeclaration,
OtherwiseExpression,
ParserValue,
Program,
UnaryExpression,
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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}'`);
}
Expand Down Expand Up @@ -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 === "!=") &&
(
Expand All @@ -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");
Expand Down Expand Up @@ -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;

Expand All @@ -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");
}
Expand Down Expand Up @@ -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'");
}
Expand All @@ -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;
}
Expand Down
6 changes: 5 additions & 1 deletion src/runtime/runtime.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export enum ValueType {
Number = "Number",
Boolean = "Boolean",
Function = "Function",
Void = "Void",
Null = "Null",
Agent = "Agent",
Agents = "Agents",
Lambda = "Lambda",
Expand Down Expand Up @@ -43,6 +43,10 @@ export interface RuntimeAgent {
variables: Map<string, RuntimeValue>;
}

export interface NullValue extends RuntimeValue {
type: ValueType.Null;
}

export interface AgentValue extends RuntimeValue {
type: ValueType.Agent;
value: RuntimeAgent;
Expand Down
Loading

0 comments on commit f49f8d0

Please sign in to comment.