Compare commits

..

1 commit
dev ... master

Author SHA1 Message Date
Lauchmelder
9636514dfc update readme 2021-11-26 22:55:12 +01:00
16 changed files with 75 additions and 512 deletions

View file

@ -1,3 +1,3 @@
# lauchpioos # lauchpioos
Under construction A JavaScript geometry sketching library.

View file

@ -1,10 +1,3 @@
point(200, 300) -> A point(3 | 4)
point(400, 300) -> B
[point(300, 128) -> C]
line(A, B) -> AB point(6 | 7)
line(B, C)
line(C, A)
circle(A, len(AB))
circle(B, len(AB))

View file

@ -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)

View file

@ -1,31 +1,16 @@
# Syntax # Syntax
``` ```
[point(3, 4) -> A] point(3 | 4) -> A
point(6, 7) -> B point(6 | 7) -> B
line[color=red, weight=4](A, B) -> AB line(A, B) -> AB
line(point(0, 0), point(100, 100)) line(0 | 0, 100 | 100)
circle(A, len(AB)) circle(A, len(AB))
``` ```
## Behaviour ## Primitives
Every line is one instruction. It is possible to assign instructions names to re-use them later. * `Point point(x, y)` is a 2D point. It returns an element of type `Point`
These variables are immutable. Objects do not exist in this script, in fact, variables are more similar to C-style macros than actual variables. * `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`
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)]
```

View file

@ -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

View file

@ -1,11 +1,10 @@
/// <reference path="vector.ts" /> import { Vector2D } from "./vector.js"
/// <reference path="gfx/polygon.ts" /> import * as shape from "./shapes.js"
/// <reference path="parser/parser.ts" />
function loadScript(filepath: string): string function loadScript(filepath: string): string
{ {
let result = null; var result = null;
let xmlhttp = new XMLHttpRequest(); var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", filepath, false); xmlhttp.open("GET", filepath, false);
xmlhttp.send(); xmlhttp.send();
if (xmlhttp.status==200) { if (xmlhttp.status==200) {
@ -17,7 +16,6 @@ function loadScript(filepath: string): string
class Geometry extends HTMLElement class Geometry extends HTMLElement
{ {
private shapes: Shape[];
private canvas: HTMLCanvasElement; private canvas: HTMLCanvasElement;
private context: CanvasRenderingContext2D; private context: CanvasRenderingContext2D;
private sourceFile: string; private sourceFile: string;
@ -25,7 +23,7 @@ class Geometry extends HTMLElement
constructor() constructor()
{ {
super(); super();
console.log("constructor")
if(!this.hasAttribute("src")) if(!this.hasAttribute("src"))
{ {
return; return;
@ -34,16 +32,37 @@ class Geometry extends HTMLElement
let sourceFile = this.getAttribute("src"); let sourceFile = this.getAttribute("src");
let content = loadScript(sourceFile); let content = loadScript(sourceFile);
let parser = new Parser(content); let lines = content.split("\n");
if(!parser.good()) for(let line of lines)
{ {
console.error("Failed to create parser for script " + sourceFile); if(line === "\r")
return; {
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"}); this.attachShadow({mode: "open"});
let canvas = document.createElement("canvas"); let canvas = document.createElement("canvas");
canvas.width = 700; canvas.width = 500;
canvas.height = 500; canvas.height = 500;
let context = canvas.getContext("2d"); let context = canvas.getContext("2d");
@ -52,38 +71,13 @@ class Geometry extends HTMLElement
this.shadowRoot.append(this.canvas); 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(); this.redraw();
} }
private redraw() private redraw()
{ {
for (let shape of this.shapes) shape.line(this.context, new Vector2D(), new Vector2D(300, 300));
{ shape.circle(this.context, new Vector2D(150, 150), 100);
shape.draw()
}
} }
} }

View file

@ -1,30 +0,0 @@
/// <reference path="./shapes.ts" />
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();
}
}

View file

@ -1,64 +0,0 @@
/// <reference path="../vector.ts" />
/// <reference path="../shapeStyle.ts" />
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();
}
}

View file

@ -1,88 +0,0 @@
/// <reference path="../vector.ts" />
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));
}
}

View file

@ -1,19 +0,0 @@
/// <reference path="instruction.ts" />
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]();
}
}

View file

@ -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();
}
}

View file

@ -1,166 +0,0 @@
/// <reference path="parameter.ts" />
/// <reference path="instructionFactory.ts" />
/**
* @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;
}
}

View file

@ -1,4 +1,5 @@
class ShapeStyle
export class ShapeStyle
{ {
public strokeWidth: number; public strokeWidth: number;
public strokeColor: string; public strokeColor: string;

26
src/shapes.ts Normal file
View file

@ -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();
}

View file

@ -1,4 +1,4 @@
class Vector2D export class Vector2D
{ {
public x: number; public x: number;
public y: number; public y: number;

View file

@ -1,7 +1,8 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es6", "target": "es6",
"outFile": "./out/geometry.js", // "outFile": "./out/lauchpioos.js",
"outDir": "./out",
"sourceRoot": "./src", "sourceRoot": "./src",
"rootDir": "./src", "rootDir": "./src",
"sourceMap": true "sourceMap": true