diff --git a/examples/test.gs b/examples/test.gs index 7c0d49b..fa68652 100644 --- a/examples/test.gs +++ b/examples/test.gs @@ -1,3 +1,2 @@ -point(3 | 4) - -point(6 | 7) \ No newline at end of file +[point(3, 4) -> A] +line(A, point(7, 8)) \ No newline at end of file diff --git a/src/doc/syntax.md b/src/doc/syntax.md index 14005e0..173f3ac 100644 --- a/src/doc/syntax.md +++ b/src/doc/syntax.md @@ -1,16 +1,31 @@ # Syntax ``` -point(3 | 4) -> A -point(6 | 7) -> B +[point(3, 4) -> A] +point(6, 7) -> B -line(A, B) -> AB -line(0 | 0, 100 | 100) +line[color=red, weight=4](A, B) -> AB +line(point(0, 0), point(100, 100)) circle(A, len(AB)) ``` -## Primitives -* `Point point(x, y)` is a 2D point. It returns an element of type `Point` -* `Line line(Point from, Point to)` is a straight line. It returns an element of type `Line`. -* `Circle circle(Point center, radius)` draws a circle at `center` and `radius` \ No newline at end of file +## Behaviour +Every line is one instruction. It is possible to assign instructions names to re-use them later. +These variables are immutable. Objects do not exist in this script, in fact, variables are more similar to C-style macros than actual variables. + +Lines in brackets `[]` are "hidden". They are parsed, but will not be rendered. + +It is possible to add an optional set of parameters in front of each parameter list, in order to specify the appearance of the element. + +## Primitives vs Functions +Primitives (e.g. `point`, `line`) and Functions (e.g. `len`, `intersection`) are syntactically indistinguishable. +Both can be used as parameters or instructions and can be assigned to variables. The only difference is that Primitives generate a visual output (unless they are surrounded by square brackets) + +## Grammar +``` +instruction ::= identifier({parameter, }) [-> identifer] +parameter ::= instruction | identifier | number +identifier ::= (A-Za-z) +number ::= (0-9)[.(0-9)] +``` diff --git a/src/geometry.ts b/src/geometry.ts index 58abec9..64da89e 100644 --- a/src/geometry.ts +++ b/src/geometry.ts @@ -1,10 +1,11 @@ -import { Vector2D } from "./vector.js" -import * as shape from "./shapes.js" +/// +/// +/// function loadScript(filepath: string): string { - var result = null; - var xmlhttp = new XMLHttpRequest(); + let result = null; + let xmlhttp = new XMLHttpRequest(); xmlhttp.open("GET", filepath, false); xmlhttp.send(); if (xmlhttp.status==200) { @@ -23,7 +24,7 @@ class Geometry extends HTMLElement constructor() { super(); - + console.log("constructor") if(!this.hasAttribute("src")) { return; @@ -32,32 +33,10 @@ class Geometry extends HTMLElement let sourceFile = this.getAttribute("src"); let content = loadScript(sourceFile); - let lines = content.split("\n"); - for(let line of lines) + let parser = new Parser(content); + for(let instr of parser.instructions) { - if(line === "\r") - { - console.log("empty"); - continue; - } - - let instruction = line.split("(")[0]; - - switch(instruction) - { - case instruction: - { - let coords = line.split("(")[1].split("|"); - console.log(coords); - break; - } - - default: - { - console.log("something else"); - break; - } - } + console.log(instr.eval()); } this.attachShadow({mode: "open"}); @@ -76,8 +55,8 @@ class Geometry extends HTMLElement private redraw() { - shape.line(this.context, new Vector2D(), new Vector2D(300, 300)); - shape.circle(this.context, new Vector2D(150, 150), 100); + line(this.context, new Vector2D(), new Vector2D(300, 300)); + circle(this.context, new Vector2D(150, 150), 100); } } diff --git a/src/parser/instruction.ts b/src/parser/instruction.ts new file mode 100644 index 0000000..67f49c6 --- /dev/null +++ b/src/parser/instruction.ts @@ -0,0 +1,51 @@ +/// + +enum InstructionType +{ + Point, + Line, + Circle +} + +abstract class Instruction +{ + public fn: InstructionType; + public params :Parameter[]; + + constructor(type: InstructionType) + { + this.fn = type; + this.params = []; + } + + abstract eval(); +} + +class PointInstruction extends Instruction +{ + constructor() + { + super(InstructionType.Point); + } + + eval() + { + return new Vector2D(this.params[0].eval(), this.params[1].eval()); + } +} + +class LineInstruction extends Instruction +{ + constructor() + { + super(InstructionType.Line); + } + + eval() + { + return [ + this.params[0].eval(), + this.params[1].eval() + ]; + } +} \ No newline at end of file diff --git a/src/parser/parameter.ts b/src/parser/parameter.ts new file mode 100644 index 0000000..efce571 --- /dev/null +++ b/src/parser/parameter.ts @@ -0,0 +1,52 @@ +enum ParameterType +{ + Instruction, + Identifier, + Number +} + +abstract class Parameter +{ + public type: ParameterType; + + constructor(type: ParameterType) + { + this.type = type; + } + + abstract eval(); +} + +class NumberParameter extends Parameter +{ + public val: number; + + constructor(val: number) + { + super(ParameterType.Number); + + this.val = val; + } + + eval() + { + return this.val; + } +} + +class InstructionParameter extends Parameter +{ + public instr: Instruction; + + constructor(instr: Instruction) + { + super(ParameterType.Identifier); + + this.instr = instr; + } + + eval() + { + return this.instr.eval(); + } +} \ No newline at end of file diff --git a/src/parser/parser.ts b/src/parser/parser.ts new file mode 100644 index 0000000..2a74445 --- /dev/null +++ b/src/parser/parser.ts @@ -0,0 +1,153 @@ +/// +/// + +class Parser +{ + public instructions: Instruction[]; + private variables: { [id: string] : InstructionParameter}; + + constructor(source: string) + { + this.instructions = []; + this.variables = {}; + + let lines = source.split(/\r?\n/); + for(let line of lines) + { + let instr = this.parseInstruction(line); + if(!instr[0]) + this.instructions.push(instr[1]); + } + } + + private parseInstruction(instruction: string): [boolean, Instruction] + { + // If the instruction is an empty line, do nothing for now + if(instruction === "") + return null; + + let hidden = false; + if(instruction[0] === "[" && instruction[instruction.length - 1] === "]") + { + hidden = true; + instruction = instruction.substring(1, instruction.length - 1); + } + + instruction = instruction.split(" ").join(""); // Remove spaces + + // match the pattern "text(text)" + let matches = instruction.match(/[A-Za-z]*\(.*\)/); + if(matches === null) // no match found + { + console.error("Invalid syntax"); + return null; + } + + if(matches.length > 1) // more than one match + { + console.error("Script may only contain one instruction per line"); + return null; + } + + let instr = matches[0]; // get the instruction + let paranthesisPos = instr.search(/\(/); // Find the position of the first opening paranthesis + + + let symbol = instr.substr(0, paranthesisPos); // get function name + let paramlist = instr.substring(paranthesisPos + 1, instr.length - 1); // get parameter list + + let match; + let params = []; + while((match = paramlist.search(/,(?![^\(]*\))/)) !== -1) + { + params.push(paramlist.substring(0, match)); + paramlist = paramlist.substring(match + 1, paramlist.length); + } + params.push(paramlist); + + let newInstruction; + switch(symbol) + { + case "point": + { + newInstruction = new PointInstruction(); + break; + } + + case "line": + { + newInstruction = new LineInstruction(); + break; + } + + default: + { + console.error("Unknown instruction \"" + symbol + "\""); + return null; + } + } + + for(let param of params) + { + if(!this.parseParameter(newInstruction, param)) + { + console.error("Error during parameter parsing: \"" + param + "\" failed to be parsed."); + return null; + } + } + + let assignment = instruction.search(/->/); + if(assignment !== -1) + { + let variableName = instruction.substring(assignment + 2, instruction.length); + + if(variableName in this.variables) + { + console.error("Redefinition of variable \"" + variableName + "\" is not allowed."); + return null; + } + + this.variables[variableName] = new InstructionParameter(newInstruction); + } + + return [hidden, newInstruction]; + } + + private parseParameter(instr: Instruction, parameter: string): boolean + { + let match = parameter.match(/\d*\.?\d*$/); + if(match !== null && match[0] === parameter && match.index === 0) + { + let val = parseFloat(parameter); + let paramObj = new NumberParameter(val); + + instr.params.push(paramObj); + return true; + } + + match = parameter.match(/[A-Za-z]/) + if(match !== null && match[0] === parameter && match.index === 0) + { + let paramObj = this.variables[parameter]; + if(paramObj === undefined) + { + console.error("Variable \"" + parameter + "\" is not defined"); + return false; + } + + instr.params.push(paramObj); + return true; + } + + match = parameter.match(/[A-Za-z]*\(.*\)/) + if(match !== null && match[0] === parameter && match.index === 0) + { + let paramObj = new InstructionParameter(this.parseInstruction(parameter)[1]); + + instr.params.push(paramObj); + return true; + } + + return false; + } +} diff --git a/src/shapeStyle.ts b/src/shapeStyle.ts index 174eaab..3f2ef5f 100644 --- a/src/shapeStyle.ts +++ b/src/shapeStyle.ts @@ -1,5 +1,4 @@ - -export class ShapeStyle +class ShapeStyle { public strokeWidth: number; public strokeColor: string; diff --git a/src/shapes.ts b/src/shapes.ts index ea26817..4d2fd2d 100644 --- a/src/shapes.ts +++ b/src/shapes.ts @@ -1,5 +1,5 @@ -import { Vector2D } from "./vector.js" -import { ShapeStyle } from "./shapeStyle.js"; +/// +/// abstract class Shape { diff --git a/src/vector.ts b/src/vector.ts index 699eb8e..64a8fdb 100644 --- a/src/vector.ts +++ b/src/vector.ts @@ -1,4 +1,4 @@ -export class Vector2D +class Vector2D { public x: number; public y: number; diff --git a/tsconfig.json b/tsconfig.json index 90de336..2adc248 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,7 @@ { "compilerOptions": { "target": "es6", - // "outFile": "./out/lauchpioos.js", - "outDir": "./out", + "outFile": "./out/geometry.js", "sourceRoot": "./src", "rootDir": "./src", "sourceMap": true