diff --git a/examples/test.gs b/examples/test.gs
index 7c0d49b..49f1677 100644
--- a/examples/test.gs
+++ b/examples/test.gs
@@ -1,3 +1,10 @@
-point(3 | 4)
+point(200, 300) -> A
+point(400, 300) -> B
+[point(300, 128) -> C]
-point(6 | 7)
\ No newline at end of file
+line(A, B) -> AB
+line(B, C)
+line(C, A)
+
+circle(A, len(AB))
+circle(B, len(AB))
\ No newline at end of file
diff --git a/examples/test.gs.disable b/examples/test.gs.disable
new file mode 100644
index 0000000..8b2a065
--- /dev/null
+++ b/examples/test.gs.disable
@@ -0,0 +1,9 @@
+point(-1, 0) -> A
+point(1, 0) -> B
+line(A, B) -> AB
+len(AB) -> length
+circle(A, len(AB)) -> circleA
+circle(B, len(AB)) -> circleB
+intersection(circleA, circleB, 0) -> C
+line(A, C)
+line(B, C)
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/doc/todo.md b/src/doc/todo.md
new file mode 100644
index 0000000..e11c816
--- /dev/null
+++ b/src/doc/todo.md
@@ -0,0 +1,9 @@
+# TODO (Parser)
+* Type checking
+* Ignore case in instruction names
+* Implement remaining functions
+* Abort parsing on error
+
+# TODO (Renderer)
+* Implement shape classes
+* Render shape classes
\ No newline at end of file
diff --git a/src/geometry.ts b/src/geometry.ts
index 58abec9..584b633 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) {
@@ -16,6 +17,7 @@ function loadScript(filepath: string): string
class Geometry extends HTMLElement
{
+ private shapes: Shape[];
private canvas: HTMLCanvasElement;
private context: CanvasRenderingContext2D;
private sourceFile: string;
@@ -23,7 +25,7 @@ class Geometry extends HTMLElement
constructor()
{
super();
-
+ console.log("constructor")
if(!this.hasAttribute("src"))
{
return;
@@ -32,37 +34,16 @@ 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);
+ if(!parser.good())
{
- 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.error("Failed to create parser for script " + sourceFile);
+ return;
}
this.attachShadow({mode: "open"});
let canvas = document.createElement("canvas");
- canvas.width = 500;
+ canvas.width = 700;
canvas.height = 500;
let context = canvas.getContext("2d");
@@ -71,13 +52,38 @@ class Geometry extends HTMLElement
this.shadowRoot.append(this.canvas);
+
+ this.shapes = []
+ for(let instruction of parser.instructions)
+ {
+ let value = instruction.eval();
+ switch(instruction.getType())
+ {
+ case InstructionType.Line:
+ {
+ console.log("New line " + value)
+ this.shapes.push(new Line(this.context, new Vector2D(value[0].x, value[0].y), new Vector2D(value[1].x, value[1].y)));
+ break;
+ }
+
+ case InstructionType.Circle:
+ {
+ console.log("New circle " + value)
+ this.shapes.push(new Circle(this.context, new Vector2D(value[0].x, value[0].y), value[1]));
+ break;
+ }
+ }
+ }
+
this.redraw();
}
private redraw()
{
- shape.line(this.context, new Vector2D(), new Vector2D(300, 300));
- shape.circle(this.context, new Vector2D(150, 150), 100);
+ for (let shape of this.shapes)
+ {
+ shape.draw()
+ }
}
}
diff --git a/src/gfx/polygon.ts b/src/gfx/polygon.ts
new file mode 100644
index 0000000..5ce58d4
--- /dev/null
+++ b/src/gfx/polygon.ts
@@ -0,0 +1,30 @@
+///
+
+class Polygon extends Shape
+{
+ private points: Vector2D[]
+
+
+ constructor(ctx, points) {
+ super(ctx);
+ if (points.length <3)
+ {
+ console.error("cant draw polygon, need min 3 points")
+ }
+ this.points = points
+ }
+
+ public draw()
+ {
+ let last_element = this.points[this.points.length-1]
+ this.ctx.beginPath();
+ this.ctx.moveTo(last_element.x, last_element.y);
+ for (let point of this.points)
+ {
+ this.ctx.lineTo(point.x, point.y);
+ }
+ this.ctx.lineWidth = this.style.strokeWidth;
+ this.ctx.strokeStyle = this.style.strokeColor;
+ this.ctx.stroke();
+ }
+}
\ No newline at end of file
diff --git a/src/gfx/shapes.ts b/src/gfx/shapes.ts
new file mode 100644
index 0000000..4131d79
--- /dev/null
+++ b/src/gfx/shapes.ts
@@ -0,0 +1,64 @@
+///
+///
+
+abstract class Shape
+{
+ protected ctx: CanvasRenderingContext2D
+ protected style: ShapeStyle
+
+ constructor(ctx) {
+ this.ctx = ctx
+ this.style = new ShapeStyle()
+ }
+
+ abstract draw()
+}
+
+class Line extends Shape
+{
+ private from: Vector2D
+ private to: Vector2D
+
+ constructor(ctx,from, to) {
+ super(ctx)
+ this.from = from
+ this.to = to
+ }
+
+ public draw()
+ {
+ this.ctx.beginPath();
+ this.ctx.moveTo(this.from.x, this.from.y);
+ this.ctx.lineTo(this.to.x, this.to.y);
+
+ this.ctx.lineWidth = this.style.strokeWidth;
+ this.ctx.strokeStyle = this.style.strokeColor;
+ this.ctx.stroke();
+ }
+}
+
+class Circle extends Shape
+{
+
+ private center: Vector2D
+ private radius: number
+
+ constructor(ctx,center, radius) {
+ super(ctx)
+ this.center = center
+ this.radius = radius
+ }
+
+ public draw()
+ {
+ this.ctx.beginPath();
+ this.ctx.arc(this.center.x, this.center.y, this.radius, 0, 2 * Math.PI, false);
+
+ this.ctx.fillStyle = this.style.fillColor;
+ this.ctx.fill();
+
+ this.ctx.lineWidth = this.style.strokeWidth;
+ this.ctx.strokeStyle = this.style.strokeColor;
+ this.ctx.stroke();
+ }
+}
\ No newline at end of file
diff --git a/src/parser/instruction.ts b/src/parser/instruction.ts
new file mode 100644
index 0000000..87848b9
--- /dev/null
+++ b/src/parser/instruction.ts
@@ -0,0 +1,88 @@
+///
+
+enum InstructionType
+{
+ Point,
+ Line,
+ Circle,
+ Length
+}
+
+abstract class Instruction
+{
+ private type: InstructionType;
+ public params :Parameter[];
+ private argc: number;
+
+ constructor(type: InstructionType, argc: number)
+ {
+ this.type = type;
+ this.argc = argc;
+ this.params = [];
+ }
+
+ abstract eval();
+ public getParameterCount(): number { return this.argc; }
+ public getType(): InstructionType { return this.type; }
+}
+
+class PointInstruction extends Instruction
+{
+ constructor()
+ {
+ super(InstructionType.Point, 2);
+ }
+
+ eval()
+ {
+ return new Vector2D(this.params[0].eval(), this.params[1].eval());
+ }
+}
+
+class LineInstruction extends Instruction
+{
+ constructor()
+ {
+ super(InstructionType.Line, 2);
+ }
+
+ eval()
+ {
+ return [
+ this.params[0].eval(),
+ this.params[1].eval()
+ ];
+ }
+}
+
+class CircleInstruction extends Instruction
+{
+ constructor()
+ {
+ super(InstructionType.Circle, 2);
+ }
+
+ eval()
+ {
+ return [
+ this.params[0].eval(),
+ this.params[1].eval()
+ ];
+ }
+}
+
+class LengthInstruction extends Instruction
+{
+ constructor()
+ {
+ super(InstructionType.Line, 1);
+ }
+
+ eval()
+ {
+ let line = this.params[0].eval();
+ let dx = line[1].x - line[0].x;
+ let dy = line[1].y - line[0].y;
+ return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
+ }
+}
\ No newline at end of file
diff --git a/src/parser/instructionFactory.ts b/src/parser/instructionFactory.ts
new file mode 100644
index 0000000..e1e9d5a
--- /dev/null
+++ b/src/parser/instructionFactory.ts
@@ -0,0 +1,19 @@
+///
+
+abstract class InstructionFactory
+{
+ private static symbolDict: { [name: string] : Function } = {
+ "point": (): PointInstruction => { return new PointInstruction(); },
+ "line": (): LineInstruction => { return new LineInstruction(); },
+ "circle": (): CircleInstruction => { return new CircleInstruction(); },
+ "len": (): LengthInstruction => { return new LengthInstruction(); }
+ }
+
+ public static createInstruction(name: string): Instruction
+ {
+ if(!(name in InstructionFactory.symbolDict))
+ return null;
+
+ return InstructionFactory.symbolDict[name]();
+ }
+}
\ 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..3dd1fb5
--- /dev/null
+++ b/src/parser/parser.ts
@@ -0,0 +1,166 @@
+///
+///
+
+/**
+ * @brief Turns a .gs script into a list of instructions to be passed to the renderer
+ */
+class Parser
+{
+ public instructions: Instruction[];
+ private macros: { [id: string] : InstructionParameter};
+ private success: boolean;
+
+ /**
+ * Parses each line of the script and turns them into instructions or adds them to the macro list
+ *
+ * @param source The source code of the script
+ */
+ constructor(source: string)
+ {
+ this.instructions = [];
+ this.macros = {};
+ this.success = false;
+
+ let lines = source.split(/\r?\n/);
+ let currentLine = 1;
+ for(let line of lines)
+ {
+ let instr;
+ try
+ {
+ instr = this.parseInstruction(line);
+ }
+ catch(e)
+ {
+ console.error("Error in line " + currentLine);
+ console.error(e);
+
+ return;
+ }
+
+ // If the instruction is null it means that the instruction was a function and not a primitive
+ if(instr !== null)
+ if(!instr[0])
+ this.instructions.push(instr[1]);
+
+ currentLine++;
+ }
+
+ this.success = true;
+ }
+
+ public good() { return this.success; }
+
+ private parseInstruction(instruction: string): [boolean, Instruction]
+ {
+ // If the instruction is an empty line, do nothing for now
+ if(instruction === "")
+ return null;
+
+ // Handle [] syntax. Lines in [] will be processed but not rendered
+ 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
+ throw new Error("Line does not contain a valid instruction.");
+
+ if(matches.length > 1) // more than one match
+ throw new Error("Line may only contain one instruction");
+
+ 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
+
+ // Construct the 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);
+
+ // Create appropriate instruction
+ let newInstruction = InstructionFactory.createInstruction(symbol);
+ if(newInstruction === null)
+ throw new Error("Unknown instruction: \"" + symbol + "\"");
+
+ // Check that the number of arguments passed to the function is correct
+ let expectedArgs = newInstruction.getParameterCount();
+ if(expectedArgs !== params.length)
+ throw new Error("Wrong number of arguments for instruction \"" + symbol + "\". Expected " + expectedArgs + " arguments but received " + params.length + " instead.");
+
+ // Parse the individual parameters
+ for(let param of params)
+ {
+ if(!this.parseParameter(newInstruction, param))
+ throw new Error("Error during parameter parsing: \"" + param + "\" failed to be parsed.");
+ }
+
+ // In case there is an assignment, add the instruction to the macro list
+ let assignment = instruction.search(/->/);
+ if(assignment !== -1)
+ {
+ let variableName = instruction.substring(assignment + 2, instruction.length);
+ if(variableName in this.macros)
+ throw new Error("Redefinition of variable \"" + variableName + "\" is not allowed.");
+
+ this.macros[variableName] = new InstructionParameter(newInstruction);
+ }
+
+ return [hidden, newInstruction];
+ }
+
+ private parseParameter(instr: Instruction, parameter: string): boolean
+ {
+ // Parameter is a number
+ 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;
+ }
+
+ // Parameter is an identifier (macro)
+ match = parameter.match(/[A-Za-z]*/)
+ if(match !== null && match[0] === parameter && match.index === 0)
+ {
+ let paramObj = this.macros[parameter];
+ if(paramObj === undefined)
+ {
+ console.error("Variable \"" + parameter + "\" is not defined");
+ return false;
+ }
+
+ instr.params.push(paramObj);
+ return true;
+ }
+
+ // Parameter is another instruction
+ 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
deleted file mode 100644
index 29454ff..0000000
--- a/src/shapes.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Vector2D } from "./vector.js"
-import { ShapeStyle } from "./shapeStyle.js";
-
-export function line(ctx: CanvasRenderingContext2D, from: Vector2D , to: Vector2D, style: ShapeStyle = new ShapeStyle())
-{
- ctx.beginPath();
- ctx.moveTo(from.x, from.y);
- ctx.lineTo(to.x, to.y);
-
- ctx.lineWidth = style.strokeWidth;
- ctx.strokeStyle = style.strokeColor;
- ctx.stroke();
-}
-
-export function circle(ctx: CanvasRenderingContext2D, center: Vector2D, radius: number, style: ShapeStyle = new ShapeStyle())
-{
- ctx.beginPath();
- ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI, false);
-
- ctx.fillStyle = style.fillColor;
- ctx.fill();
-
- ctx.lineWidth = style.strokeWidth;
- ctx.strokeStyle = style.strokeColor;
- ctx.stroke();
-}
\ No newline at end of file
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