diff --git a/README.md b/README.md index 364eaa9..14b8967 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # lauchpioos -Under construction +A JavaScript geometry sketching library. diff --git a/examples/test.gs b/examples/test.gs index 49f1677..7c0d49b 100644 --- a/examples/test.gs +++ b/examples/test.gs @@ -1,10 +1,3 @@ -point(200, 300) -> A -point(400, 300) -> B -[point(300, 128) -> C] +point(3 | 4) -line(A, B) -> AB -line(B, C) -line(C, A) - -circle(A, len(AB)) -circle(B, len(AB)) \ No newline at end of file +point(6 | 7) \ No newline at end of file diff --git a/examples/test.gs.disable b/examples/test.gs.disable deleted file mode 100644 index 8b2a065..0000000 --- a/examples/test.gs.disable +++ /dev/null @@ -1,9 +0,0 @@ -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 173f3ac..14005e0 100644 --- a/src/doc/syntax.md +++ b/src/doc/syntax.md @@ -1,31 +1,16 @@ # Syntax ``` -[point(3, 4) -> A] -point(6, 7) -> B +point(3 | 4) -> A +point(6 | 7) -> B -line[color=red, weight=4](A, B) -> AB -line(point(0, 0), point(100, 100)) +line(A, B) -> AB +line(0 | 0, 100 | 100) circle(A, len(AB)) ``` -## 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)] -``` +## 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 diff --git a/src/doc/todo.md b/src/doc/todo.md deleted file mode 100644 index e11c816..0000000 --- a/src/doc/todo.md +++ /dev/null @@ -1,9 +0,0 @@ -# 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 584b633..58abec9 100644 --- a/src/geometry.ts +++ b/src/geometry.ts @@ -1,11 +1,10 @@ -/// -/// -/// +import { Vector2D } from "./vector.js" +import * as shape from "./shapes.js" function loadScript(filepath: string): string { - let result = null; - let xmlhttp = new XMLHttpRequest(); + var result = null; + var xmlhttp = new XMLHttpRequest(); xmlhttp.open("GET", filepath, false); xmlhttp.send(); if (xmlhttp.status==200) { @@ -17,7 +16,6 @@ function loadScript(filepath: string): string class Geometry extends HTMLElement { - private shapes: Shape[]; private canvas: HTMLCanvasElement; private context: CanvasRenderingContext2D; private sourceFile: string; @@ -25,7 +23,7 @@ class Geometry extends HTMLElement constructor() { super(); - console.log("constructor") + if(!this.hasAttribute("src")) { return; @@ -34,16 +32,37 @@ class Geometry extends HTMLElement let sourceFile = this.getAttribute("src"); let content = loadScript(sourceFile); - let parser = new Parser(content); - if(!parser.good()) + let lines = content.split("\n"); + for(let line of lines) { - console.error("Failed to create parser for script " + sourceFile); - return; + 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; + } + } } this.attachShadow({mode: "open"}); let canvas = document.createElement("canvas"); - canvas.width = 700; + canvas.width = 500; canvas.height = 500; let context = canvas.getContext("2d"); @@ -52,38 +71,13 @@ 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() { - for (let shape of this.shapes) - { - shape.draw() - } + shape.line(this.context, new Vector2D(), new Vector2D(300, 300)); + shape.circle(this.context, new Vector2D(150, 150), 100); } } diff --git a/src/gfx/polygon.ts b/src/gfx/polygon.ts deleted file mode 100644 index 5ce58d4..0000000 --- a/src/gfx/polygon.ts +++ /dev/null @@ -1,30 +0,0 @@ -/// - -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 deleted file mode 100644 index 4131d79..0000000 --- a/src/gfx/shapes.ts +++ /dev/null @@ -1,64 +0,0 @@ -/// -/// - -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 deleted file mode 100644 index 87848b9..0000000 --- a/src/parser/instruction.ts +++ /dev/null @@ -1,88 +0,0 @@ -/// - -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 deleted file mode 100644 index e1e9d5a..0000000 --- a/src/parser/instructionFactory.ts +++ /dev/null @@ -1,19 +0,0 @@ -/// - -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 deleted file mode 100644 index efce571..0000000 --- a/src/parser/parameter.ts +++ /dev/null @@ -1,52 +0,0 @@ -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 deleted file mode 100644 index 3dd1fb5..0000000 --- a/src/parser/parser.ts +++ /dev/null @@ -1,166 +0,0 @@ -/// -/// - -/** - * @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 3f2ef5f..174eaab 100644 --- a/src/shapeStyle.ts +++ b/src/shapeStyle.ts @@ -1,4 +1,5 @@ -class ShapeStyle + +export class ShapeStyle { public strokeWidth: number; public strokeColor: string; diff --git a/src/shapes.ts b/src/shapes.ts new file mode 100644 index 0000000..29454ff --- /dev/null +++ b/src/shapes.ts @@ -0,0 +1,26 @@ +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 64a8fdb..699eb8e 100644 --- a/src/vector.ts +++ b/src/vector.ts @@ -1,4 +1,4 @@ -class Vector2D +export class Vector2D { public x: number; public y: number; diff --git a/tsconfig.json b/tsconfig.json index 2adc248..90de336 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,8 @@ { "compilerOptions": { "target": "es6", - "outFile": "./out/geometry.js", + // "outFile": "./out/lauchpioos.js", + "outDir": "./out", "sourceRoot": "./src", "rootDir": "./src", "sourceMap": true