diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 733024c..b0a1c49 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1 +1 @@
-Please see [The Coding Train Code of Conduct](https://github.com/CodingTrain/Code-of-Conduct)
+Please see [The Coding Train Code of Conduct](https://github.com/CodingTrain/Code-of-Conduct)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a099036..8a445d4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1 +1 @@
-TBD
+TBD
diff --git a/LICENSE b/LICENSE
index 02bc06e..651136a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,21 @@
-MIT License
-
-Copyright (c) 2018 Daniel Shiffman
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+MIT License
+
+Copyright (c) 2018 Daniel Shiffman
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index dbfbd4c..d6ee0e3 100644
--- a/README.md
+++ b/README.md
@@ -1,167 +1,167 @@
-# Logo
-Repo for implementation of Logo in JavaScript p5
-
-Supported commands
-
-
-> The description(s) and/or example(s) of the commands were taken (and corrected for this interpreter) from the [Berkeley Logo Reference Manual](https://people.eecs.berkeley.edu/~bh/v2ch14/manual.html) under MIT license.
-
-### Forward
-Moves the turtle forward, in the direction that it's facing, by the specified distance (measured in turtle steps).
-* `fd units`
-
-### Backward
-Moves the turtle backward, i.e., exactly opposite to the direction that it's facing, by the specified distance. (The heading of the turtle does not change.)
-* `bd units`
-
-### Right
-Turns the turtle clockwise by the specified angle, measured by default in degrees (1/360 of a circle).
-* `rt angle`
-
-### Left
-Turns the turtle counterclockwise by the specified angle, measured by default in degrees (1/360 of a circle).
-* `lt angle`
-
-### Penup
-Sets the pen's position to UP, without changing its mode.
-* `pu`
-
-### Pendown
-Sets the pen's position to DOWN, without changing its mode.
-* `pd`
-
-### Pensize
-Sets the thickness of the pen with the given the value. Note: If it's given a negative value, by default will be thickness of 1.
-* `pensize thicknessValue`
-
-### Set X Y
-Moves the turtle to an absolute position in the graphics window. The two inputs are numbers, the X and Y coordinates.
-* `setxy coordinateX coordinateY`
-
-### Set X
-Moves the turtle horizontally from its old position to a new absolute horizontal coordinate. The input is the new X coordinate.
-* `setx coordinateX`
-
-### Set Y
-Moves the turtle vertically from its old position to a new absolute vertical coordinate. The input is the new Y coordinate.
-* `sety coordinateY`
-
-### Home
-Moves the turtle to the center of the screen.
-* `home`
-
-### Radians
-Changes the angle values to be used as radians
-* `radians`
-
-### Degrees
-Changes the angle values to be used as degrees
-* `degrees`
-
-### Repeat
- uns the following instruction list repeatedly, num times. Can be nested.
-* `repeat num [instruction list]`
-
-### Color
-Sets the pen color given the hexadecimal value in format `#FFF / #FFFFFF`.
-* `color hexadecimalValue`
-
-### Color RGB
-Sets the pen color given the RGB value.
-* `colorrgb [red green blue]`
-
-### Author
-Prints the given in the developer console
-* `author [author website twitter]`
-
-
-
-
-## Useful links
-* [Live Web Demo](https://codingtrain.github.io/Logo/)
-* [Coding Challenge Part 1](https://thecodingtrain.com/CodingChallenges/121.1-logo-interpreter.html) and
-[Coding Challenge Part 2](https://thecodingtrain.com/CodingChallenges/121.2-logo-interpreter.html)
-* [Coding Challenge Part 1 on Youtube](https://www.youtu.be/i-k04yzfMpw) and
-[Coding Challenge Part 2 on Youtube](https://www.youtu.be/aOqEm101fms)
-* [Logo Wikipedia Page](https://en.wikipedia.org/wiki/Logo_(programming_language))
-* [More Information about Logo and some Examples](http://cs.brown.edu/courses/bridge/1997/Resources/LogoTutorial.html)
-
-## Getting Started
-
-Getting started just takes 3 simple steps, each one is a command in the terminal.
-
-1. Clone this GitHub repository.
-2. Navigate inside the repository directory on your machine.
-3. Host the directory with a local web server.
-
-There are a few easy ways to achieve step number 3 depending on what you already have installed:
-
-Node Package Manager (NPM)
-
-
-> If you have NPM installed you can use the [live-server](https://www.npmjs.com/package/live-server) NPM package.
-> The neat thing about live-server is that it automatically refreshes the web page every time you change a file.
->
-> If you don't have NPM installed you can download and install it [here](https://www.npmjs.com/get-npm).
->
-> Once you have NPM, install live-server globally with `npm install --global live-server`.
->
-> Then run the following commands in your terminal.
-> ```
-> git clone https://github.com/CodingTrain/Logo.git
-> cd Logo
-> live-server --port=8080 .
-> ```
-> Don't forget the dot at the end of the command on MacOS!
->
-> Finally, you can open [http://localhost:8080](http://localhost:8080) in your browser and you're away!
->
-> Note that when you close the terminal window, the web server will stop as well.
-
-
-
-
-
-
-Python 2.x
-
-
-> You can use the [SimpleHTTPServer](https://docs.python.org/2/library/simplehttpserver.html) python module.
->
-> If you don't have Python 2 installed you can download and install it [here](https://www.python.org/downloads/).
->
-> Then run the following commands in your terminal.
-> ```
-> git clone https://github.com/CodingTrain/Logo.git
-> cd Logo
-> python -m SimpleHTTPServer 8080
-> ```
-> Finally, you can open [http://localhost:8080](http://localhost:8080) in your browser and you're away!
->
-> Note that when you close the terminal window, the web server will stop as well.
-
-
-
-
-
-
-Python 3.x
-
-
-> You can use the [http.server](https://docs.python.org/3/library/http.server.html) python module.
->
-> If you don't have Python 3 installed you can download and install it [here](https://www.python.org/downloads/).
->
->
-> Then run the following commands in your terminal.
-> ```
-> git clone https://github.com/CodingTrain/Logo.git
-> cd Logo
-> python3 -m http.server 8080
-> ```
-> Finally, you can open [http://localhost:8080](http://localhost:8080) in your browser and you're away!
->
-> Note that when you close the terminal window, the web server will stop as well.
-
-
-
+# Logo
+Repo for implementation of Logo in JavaScript p5
+
+Supported commands
+
+
+> The description(s) and/or example(s) of the commands were taken (and corrected for this interpreter) from the [Berkeley Logo Reference Manual](https://people.eecs.berkeley.edu/~bh/v2ch14/manual.html) under MIT license.
+
+### Forward
+Moves the turtle forward, in the direction that it's facing, by the specified distance (measured in turtle steps).
+* `fd units`
+
+### Backward
+Moves the turtle backward, i.e., exactly opposite to the direction that it's facing, by the specified distance. (The heading of the turtle does not change.)
+* `bd units`
+
+### Right
+Turns the turtle clockwise by the specified angle, measured by default in degrees (1/360 of a circle).
+* `rt angle`
+
+### Left
+Turns the turtle counterclockwise by the specified angle, measured by default in degrees (1/360 of a circle).
+* `lt angle`
+
+### Penup
+Sets the pen's position to UP, without changing its mode.
+* `pu`
+
+### Pendown
+Sets the pen's position to DOWN, without changing its mode.
+* `pd`
+
+### Pensize
+Sets the thickness of the pen with the given the value. Note: If it's given a negative value, by default will be thickness of 1.
+* `pensize thicknessValue`
+
+### Set X Y
+Moves the turtle to an absolute position in the graphics window. The two inputs are numbers, the X and Y coordinates.
+* `setxy coordinateX coordinateY`
+
+### Set X
+Moves the turtle horizontally from its old position to a new absolute horizontal coordinate. The input is the new X coordinate.
+* `setx coordinateX`
+
+### Set Y
+Moves the turtle vertically from its old position to a new absolute vertical coordinate. The input is the new Y coordinate.
+* `sety coordinateY`
+
+### Home
+Moves the turtle to the center of the screen.
+* `home`
+
+### Radians
+Changes the angle values to be used as radians
+* `radians`
+
+### Degrees
+Changes the angle values to be used as degrees
+* `degrees`
+
+### Repeat
+ uns the following instruction list repeatedly, num times. Can be nested.
+* `repeat num [instruction list]`
+
+### Color
+Sets the pen color given the hexadecimal value in format `#FFF / #FFFFFF`.
+* `color hexadecimalValue`
+
+### Color RGB
+Sets the pen color given the RGB value.
+* `colorrgb [red green blue]`
+
+### Author
+Prints the given in the developer console
+* `author [author website twitter]`
+
+
+
+
+## Useful links
+* [Live Web Demo](https://codingtrain.github.io/Logo/)
+* [Coding Challenge Part 1](https://thecodingtrain.com/CodingChallenges/121.1-logo-interpreter.html) and
+[Coding Challenge Part 2](https://thecodingtrain.com/CodingChallenges/121.2-logo-interpreter.html)
+* [Coding Challenge Part 1 on Youtube](https://www.youtu.be/i-k04yzfMpw) and
+[Coding Challenge Part 2 on Youtube](https://www.youtu.be/aOqEm101fms)
+* [Logo Wikipedia Page](https://en.wikipedia.org/wiki/Logo_(programming_language))
+* [More Information about Logo and some Examples](http://cs.brown.edu/courses/bridge/1997/Resources/LogoTutorial.html)
+
+## Getting Started
+
+Getting started just takes 3 simple steps, each one is a command in the terminal.
+
+1. Clone this GitHub repository.
+2. Navigate inside the repository directory on your machine.
+3. Host the directory with a local web server.
+
+There are a few easy ways to achieve step number 3 depending on what you already have installed:
+
+Node Package Manager (NPM)
+
+
+> If you have NPM installed you can use the [live-server](https://www.npmjs.com/package/live-server) NPM package.
+> The neat thing about live-server is that it automatically refreshes the web page every time you change a file.
+>
+> If you don't have NPM installed you can download and install it [here](https://www.npmjs.com/get-npm).
+>
+> Once you have NPM, install live-server globally with `npm install --global live-server`.
+>
+> Then run the following commands in your terminal.
+> ```
+> git clone https://github.com/CodingTrain/Logo.git
+> cd Logo
+> live-server --port=8080 .
+> ```
+> Don't forget the dot at the end of the command on MacOS!
+>
+> Finally, you can open [http://localhost:8080](http://localhost:8080) in your browser and you're away!
+>
+> Note that when you close the terminal window, the web server will stop as well.
+
+
+
+
+
+
+Python 2.x
+
+
+> You can use the [SimpleHTTPServer](https://docs.python.org/2/library/simplehttpserver.html) python module.
+>
+> If you don't have Python 2 installed you can download and install it [here](https://www.python.org/downloads/).
+>
+> Then run the following commands in your terminal.
+> ```
+> git clone https://github.com/CodingTrain/Logo.git
+> cd Logo
+> python -m SimpleHTTPServer 8080
+> ```
+> Finally, you can open [http://localhost:8080](http://localhost:8080) in your browser and you're away!
+>
+> Note that when you close the terminal window, the web server will stop as well.
+
+
+
+
+
+
+Python 3.x
+
+
+> You can use the [http.server](https://docs.python.org/3/library/http.server.html) python module.
+>
+> If you don't have Python 3 installed you can download and install it [here](https://www.python.org/downloads/).
+>
+>
+> Then run the following commands in your terminal.
+> ```
+> git clone https://github.com/CodingTrain/Logo.git
+> cd Logo
+> python3 -m http.server 8080
+> ```
+> Finally, you can open [http://localhost:8080](http://localhost:8080) in your browser and you're away!
+>
+> Note that when you close the terminal window, the web server will stop as well.
+
+
+
diff --git a/animation.js b/animation.js
index 4783c39..0f6b3f8 100644
--- a/animation.js
+++ b/animation.js
@@ -1,99 +1,99 @@
-let Animation;
-
-// Use an anonymous closure to keep the AnimationManager private
-(() => {
-
- // From here: gist.github.com/gre/1650294
- const easingFunctions = {
- LINEAR : t => t,
- EASEINQUAD : t => t ** 2,
- EASEOUTQUAD : t => t * (2 - t),
- EASEINOUTQUAD : t => t < .5 ? 2 * t ** 2 : -1 + (4 - 2 * t) * t,
- EASEINCUBIC : t => t ** 3,
- EASEOUTCUBIC : t => --t ** 3 + 1,
- EASEINOUTCUBIC : t => t < .5 ? 4 * t ** 3 : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
- EASEINQUART : t => t ** 4,
- EASEOUTQUART : t => 1 - --t ** 4,
- EASEINOUTQUART : t => t < .5 ? 8 * t ** 4 : 1 - 8 * --t ** 4,
- EASEINQUINT : t => t ** 5,
- EASEOUTQUINT : t => 1 + --t ** 5,
- EASEINOUTQUINT : t => t < .5 ? 16 * t ** 5 : 1 + 16 * --t ** 5
- }
-
- function lerp(start, end, ratio) {
- return start + (end - start) * ratio;
- }
-
- class AnimationManager {
-
- constructor() {
- this.activeAnimations = [];
- this.update = this.update.bind(this); // Make sure 'this' is correct when invoked by requestAnimationFrame
- this.update();
- }
-
- update() {
- for (const anim of this.activeAnimations)
- anim.update();
- this.activeAnimations = this.activeAnimations.filter(anim => !anim.complete);
- requestAnimationFrame(this.update);
- }
-
- registerAnimation(anim) {
- this.activeAnimations.push(anim);
- }
-
- }
-
- const animationManager = new AnimationManager();
-
- Animation = class {
-
- constructor(startingValues, endingValues, duration, updateCallback, finishedCallback, easing = 'EASE_IN_OUT_QUART') {
-
- /*
- * Normalize the easing function name such that all of the following are valid:
- * "EASEINOUTCUBIC"
- * "EASE_IN_OUT_CUBIC"
- * "ease in out cubic"
- * Case insensitive, optionally allowing spaces or underscores
- */
- easing = easing.toUpperCase().replace(/[^A-Z]/g, '');
-
- this.easingFunction = easingFunctions[easing];
- this.duration = duration;
- this.startingValues = startingValues;
- this.endingValues = endingValues;
- this.values = Object.assign({}, this.startingValues);
- this.startTime = performance.now();
- this.complete = false;
- this.updateCallback = updateCallback;
- this.finishedCallback = finishedCallback;
-
- // Register this animation so that it gets updated periodically
- animationManager.registerAnimation(this);
- }
-
- update() {
- if (this.complete) return;
- const now = performance.now();
- const elapsed = now - this.startTime;
- let completionRatio = elapsed / this.duration;
- this.complete = completionRatio >= 1;
- completionRatio = Math.min(1, this.easingFunction(completionRatio));
- for (const val in this.values)
- this.values[val] = lerp(this.startingValues[val], this.endingValues[val], completionRatio);
- this.updateCallback(this.values);
- if (this.complete)
- this.finishedCallback(this.endingValues);
- }
-
- skip() {
- this.complete = true;
- this.updateCallback(this.endingValues);
- this.finishedCallback(this.endingValues);
- }
-
- }
-
+let Animation;
+
+// Use an anonymous closure to keep the AnimationManager private
+(() => {
+
+ // From here: gist.github.com/gre/1650294
+ const easingFunctions = {
+ LINEAR : t => t,
+ EASEINQUAD : t => t ** 2,
+ EASEOUTQUAD : t => t * (2 - t),
+ EASEINOUTQUAD : t => t < .5 ? 2 * t ** 2 : -1 + (4 - 2 * t) * t,
+ EASEINCUBIC : t => t ** 3,
+ EASEOUTCUBIC : t => --t ** 3 + 1,
+ EASEINOUTCUBIC : t => t < .5 ? 4 * t ** 3 : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
+ EASEINQUART : t => t ** 4,
+ EASEOUTQUART : t => 1 - --t ** 4,
+ EASEINOUTQUART : t => t < .5 ? 8 * t ** 4 : 1 - 8 * --t ** 4,
+ EASEINQUINT : t => t ** 5,
+ EASEOUTQUINT : t => 1 + --t ** 5,
+ EASEINOUTQUINT : t => t < .5 ? 16 * t ** 5 : 1 + 16 * --t ** 5
+ }
+
+ function lerp(start, end, ratio) {
+ return start + (end - start) * ratio;
+ }
+
+ class AnimationManager {
+
+ constructor() {
+ this.activeAnimations = [];
+ this.update = this.update.bind(this); // Make sure 'this' is correct when invoked by requestAnimationFrame
+ this.update();
+ }
+
+ update() {
+ for (const anim of this.activeAnimations)
+ anim.update();
+ this.activeAnimations = this.activeAnimations.filter(anim => !anim.complete);
+ requestAnimationFrame(this.update);
+ }
+
+ registerAnimation(anim) {
+ this.activeAnimations.push(anim);
+ }
+
+ }
+
+ const animationManager = new AnimationManager();
+
+ Animation = class {
+
+ constructor(startingValues, endingValues, duration, updateCallback, finishedCallback, easing = 'EASE_IN_OUT_QUART') {
+
+ /*
+ * Normalize the easing function name such that all of the following are valid:
+ * "EASEINOUTCUBIC"
+ * "EASE_IN_OUT_CUBIC"
+ * "ease in out cubic"
+ * Case insensitive, optionally allowing spaces or underscores
+ */
+ easing = easing.toUpperCase().replace(/[^A-Z]/g, '');
+
+ this.easingFunction = easingFunctions[easing];
+ this.duration = duration;
+ this.startingValues = startingValues;
+ this.endingValues = endingValues;
+ this.values = Object.assign({}, this.startingValues);
+ this.startTime = performance.now();
+ this.complete = false;
+ this.updateCallback = updateCallback;
+ this.finishedCallback = finishedCallback;
+
+ // Register this animation so that it gets updated periodically
+ animationManager.registerAnimation(this);
+ }
+
+ update() {
+ if (this.complete) return;
+ const now = performance.now();
+ const elapsed = now - this.startTime;
+ let completionRatio = elapsed / this.duration;
+ this.complete = completionRatio >= 1;
+ completionRatio = Math.min(1, this.easingFunction(completionRatio));
+ for (const val in this.values)
+ this.values[val] = lerp(this.startingValues[val], this.endingValues[val], completionRatio);
+ this.updateCallback(this.values);
+ if (this.complete)
+ this.finishedCallback(this.endingValues);
+ }
+
+ skip() {
+ this.complete = true;
+ this.updateCallback(this.endingValues);
+ this.finishedCallback(this.endingValues);
+ }
+
+ }
+
})();
\ No newline at end of file
diff --git a/bounding_box.js b/bounding_box.js
index a992e4b..20ceb18 100644
--- a/bounding_box.js
+++ b/bounding_box.js
@@ -1,36 +1,36 @@
-class BoundingBox {
-
- constructor() {
- this.reset();
- }
-
- reset() {
- // By default it's positioned with top-left corner at [0,0] with a width and height of 1.
- // The x and y values are always the center
- this.left = this.top = 0;
- this.right = this.bottom = 1;
- this.width = this.height = 1;
- this.x = this.y = .5;
- }
-
- move(x, y) {
- this.left += x;
- this.right += x;
- this.x += x;
- this.top += y;
- this.bottom += y;
- this.y += y;
- }
-
- includePoint(x, y) {
- this.left = Math.min(this.left, x);
- this.right = Math.max(this.right, x);
- this.top = Math.min(this.top, y);
- this.bottom = Math.max(this.bottom, y);
- this.width = this.right - this.left;
- this.height = this.bottom - this.top;
- this.x = this.left + this.width * .5;
- this.y = this.top + this.height * .5;
- }
-
+class BoundingBox {
+
+ constructor() {
+ this.reset();
+ }
+
+ reset() {
+ // By default it's positioned with top-left corner at [0,0] with a width and height of 1.
+ // The x and y values are always the center
+ this.left = this.top = 0;
+ this.right = this.bottom = 1;
+ this.width = this.height = 1;
+ this.x = this.y = .5;
+ }
+
+ move(x, y) {
+ this.left += x;
+ this.right += x;
+ this.x += x;
+ this.top += y;
+ this.bottom += y;
+ this.y += y;
+ }
+
+ includePoint(x, y) {
+ this.left = Math.min(this.left, x);
+ this.right = Math.max(this.right, x);
+ this.top = Math.min(this.top, y);
+ this.bottom = Math.max(this.bottom, y);
+ this.width = this.right - this.left;
+ this.height = this.bottom - this.top;
+ this.x = this.left + this.width * .5;
+ this.y = this.top + this.height * .5;
+ }
+
}
\ No newline at end of file
diff --git a/command.js b/command.js
index e23f696..760f086 100644
--- a/command.js
+++ b/command.js
@@ -1,220 +1,231 @@
-/**
- * This are the types a Command Argument can be:
- * - STR: String, as its parsed it will returned.
- * - INT: Integer, the parsed value will be casted into base10 Integer.
- * - FLOAT: Float, the parsed value will be casted into a float js object.
- * - COMMANDS: List of commands, the parsed value will be parse into Command
- * Executors that will be executed.
- * - PARAMETERS: Parameters, it almost doesnt make sense but is here to
- * demonstrate the power of this way of parsing the content.
- */
-const ARGUMENT_TYPES = {
- STR: "STR",
- INT: "INT",
- FLOAT: "FLOAT",
- COMMANDS: "COMMANDS",
- EXPRESSION: "EXPRESSION",
- PARAMETERS: "PARAMETERS" // Example
-};
-
-/**
- * Argument a command can accept. The most important of this class is the
- * type property.
- *
- * @class CommandArg
- */
-class CommandArg {
-
- /**
- * Creates an instance of CommandArg.
- * @param {String} name Name of the argument
- * @param {ARGUMENT_TYPES} type Type of the argument. Its important because
- * this will define how the token is parsed.
- * @param {*} validator Function used to check if the argument is valid
- * @memberof CommandArg
- */
- constructor(name, type, validator = undefined) {
- this.name = name;
- this.type = type;
- if (validator === undefined) {
- switch (type) {
- case ARGUMENT_TYPES.INT:
- this.validator = (str) => {
- return /^\d+$/.test(str);
- }
- break;
- case ARGUMENT_TYPES.FLOAT:
- this.validator = (str) => {
- return /^[-+]?[0-9]*\.?[0-9]*$/.test(str);
- }
- break;
- case ARGUMENT_TYPES.EXPRESSION:
- this.validator = (str) => {
- let res = /^[-+]?([0-9]+\.?[0-9]?|[0-9]*\.?[0-9]+)(\s[+-/*]{1}\s[-+]?([0-9]+\.?[0-9]?|[0-9]*\.?[0-9]+))*$/.test(str);
- return res;
- }
- break;
- case ARGUMENT_TYPES.COMMANDS:
- this.validator = (str,offset)=>{
- let p = new Parser(str,null,offset);
-
- p.parse()
- return true;
- }
- break;
- }
- } else
- this.validator = validator;
- }
-}
-
-/**
- * Command that can be executed.
- *
- * @class Command
- */
-class Command {
-
- /**
- * Creates an instance of Command.
- * @param {String} name Name of the command, againts this name the tokens will be
- * matched.
- * @param {[CommandArg]} args An array of CommandArg that this command needs in
- * order to work.
- * @param {*} func The JS function that will be executed when this command needs
- * to be executed.
- * @memberof Command
- */
- constructor(name, args, func) {
- this.name = name;
- this.argsTemplate = args;
- this.func = func;
- }
-}
-
-/**
- * The command executor purpose is to hold the command to execute and the parsed
- * arguments that has been read from the code.
- */
-class CommandExecutor {
-
- /**
- * Creates an instance of CommandExecutor.
- * @param {Command} command The command you will want to execute.
- * @param {[String]} values An array of string tokens, this array needs
- * to be the same length of the arguments the commands can accept.
- * These values will be casted to the argument type it corresponds to.
- * @param {Function} callback Function to execute after the command is executed.
- * @memberof CommandExecutor
- */
- constructor(command, values, callback) {
- this.callback = callback;
- this.command = command;
- this.values = [];
-
- for (let i = 0; i < values.length; i++) {
- let value = values[i];
- let argTemplate = this.command.argsTemplate[i].type;
-
- switch (argTemplate) {
- case ARGUMENT_TYPES.STR:
- this.values.push(value);
- break;
- case ARGUMENT_TYPES.INT:
- this.values.push(parseInt(value));
- break;
- case ARGUMENT_TYPES.FLOAT:
- this.values.push(parseFloat(value));
- break;
- case ARGUMENT_TYPES.COMMANDS:
- this.values.push(
- new Parser(value, this.callback).parse()
- );
- break;
- case ARGUMENT_TYPES.EXPRESSION:
- this.values.push(this.parseExpression(value).eval());
- break;
- case ARGUMENT_TYPES.PARAMETERS: // Example
- this.values.push(value.split(" "));
- break;
- default:
- console.log("Unknown argType: ", argTemplate);
- break;
- }
- }
- }
- parseExpression(ExpressionString) {
- let p = new Parser(ExpressionString,null);
- let token = p.nextToken();
- let next = p.nextToken();
- let e;
- let right;
- if (next == '/' || next == '*' || next == '+' || next == '-') {
- right = this.parseExpression(p.getArgs());
- e = new Expression(next,new Expression('$',token), right);
- } else
- return new Expression('$', token);
- if (right.lvl() > e.lvl()) {
- let new_left = new Expression(next, new Expression('$',token), right.left);
- e = new Expression(right.type, new_left, right.right);
- }
- return e;
- }
- /**
- * Executes the command with the values given at the creation of the
- * instance.
- *
- * @memberof CommandExecutor
- */
- execute(repcount) {
- this.command.func.apply(this, this.values);
- if (this.callback) {
- this.callback();
- }
- }
-}
-
-/**
- * It stores all the commands available.
- *
- * @class CommandLookUp
- */
-class CommandLookUp {
-
- /**
- * Creates an instance of CommandLookUp.
- * @memberokUp
- */
- constructor() {
- this.commands = [];
- }
-
- /**
- * Adding a new command to the list.
- *
- * @param {Command} command New command to add to the list.
- * @memberof CommandLookUp
- */
- add(command) {
- this.commands.push(command);
- }
-
- /**
- * Return a command that matches the name.
- *
- * @param {String} name The name of the command you want back.
- * @returns The command that matches the name, or null if it can't find it.
- * @memberof CommandLookUp
- */
- get(name) {
- let item = null;
- let index = 0;
- while (!item && index < this.commands.length) {
- if (this.commands[index].name === name) item = this.commands[index];
-
- index++;
- }
-
- return item;
- }
-}
+/**
+ * This are the types a Command Argument can be:
+ * - STR: String, as its parsed it will returned.
+ * - INT: Integer, the parsed value will be casted into base10 Integer.
+ * - FLOAT: Float, the parsed value will be casted into a float js object.
+ * - COMMANDS: List of commands, the parsed value will be parse into Command
+ * Executors that will be executed.
+ * - PARAMETERS: Parameters, it almost doesnt make sense but is here to
+ * demonstrate the power of this way of parsing the content.
+ */
+const ARGUMENT_TYPES = {
+ STR: "STR",
+ INT: "INT",
+ FLOAT: "FLOAT",
+ COMMANDS: "COMMANDS",
+ EXPRESSION: "EXPRESSION",
+ PARAMETERS: "PARAMETERS", // Example
+ COLOR: "COLOR"
+};
+
+/**
+ * Argument a command can accept. The most important of this class is the
+ * type property.
+ *
+ * @class CommandArg
+ */
+class CommandArg {
+
+ /**
+ * Creates an instance of CommandArg.
+ * @param {String} name Name of the argument
+ * @param {ARGUMENT_TYPES} type Type of the argument. Its important because
+ * this will define how the token is parsed.
+ * @param {*} validator Function used to check if the argument is valid
+ * @memberof CommandArg
+ */
+ constructor(name, type, validator = undefined) {
+ this.name = name;
+ this.type = type;
+ if (validator === undefined) {
+ switch (type) {
+ case ARGUMENT_TYPES.INT:
+ this.validator = (str) => {
+ return /^\d+$/.test(str);
+ }
+ break;
+ case ARGUMENT_TYPES.FLOAT:
+ this.validator = (str) => {
+ return /^[-+]?[0-9]*\.?[0-9]*$/.test(str);
+ }
+ break;
+ case ARGUMENT_TYPES.EXPRESSION:
+ this.validator = (str) => {
+ let res = /^[-+]?([0-9]+\.?[0-9]?|[0-9]*\.?[0-9]+)(\s[+-/*]{1}\s[-+]?([0-9]+\.?[0-9]?|[0-9]*\.?[0-9]+))*$/.test(str);
+ return res;
+ }
+ break;
+ case ARGUMENT_TYPES.STR:
+ this.validator = (str) => {
+ let res = /^[\s\S]+/.test(str);
+ print(res)
+ return res;
+ }
+ break;
+ case ARGUMENT_TYPES.COMMANDS:
+ this.validator = (str,offset)=>{
+ let p = new Parser(str,null,offset);
+
+ p.parse()
+ return true;
+ }
+ break;
+ }
+ } else
+ this.validator = validator;
+ }
+}
+
+/**
+ * Command that can be executed.
+ *
+ * @class Command
+ */
+class Command {
+
+ /**
+ * Creates an instance of Command.
+ * @param {String} name Name of the command, againts this name the tokens will be
+ * matched.
+ * @param {[CommandArg]} args An array of CommandArg that this command needs in
+ * order to work.
+ * @param {*} func The JS function that will be executed when this command needs
+ * to be executed.
+ * @memberof Command
+ */
+ constructor(name, args, func) {
+ this.name = name;
+ this.argsTemplate = args;
+ this.func = func;
+ }
+}
+
+/**
+ * The command executor purpose is to hold the command to execute and the parsed
+ * arguments that has been read from the code.
+ */
+class CommandExecutor {
+
+ /**
+ * Creates an instance of CommandExecutor.
+ * @param {Command} command The command you will want to execute.
+ * @param {[String]} values An array of string tokens, this array needs
+ * to be the same length of the arguments the commands can accept.
+ * These values will be casted to the argument type it corresponds to.
+ * @param {Function} callback Function to execute after the command is executed.
+ * @memberof CommandExecutor
+ */
+ constructor(command, values, callback) {
+ this.callback = callback;
+ this.command = command;
+ this.values = [];
+
+ for (let i = 0; i < values.length; i++) {
+ let value = values[i];
+ let argTemplate = this.command.argsTemplate[i].type;
+
+ switch (argTemplate) {
+ case ARGUMENT_TYPES.STR:
+ this.values.push(value.substring(1, value.length - 1));
+ break;
+ case ARGUMENT_TYPES.COLOR:
+ this.values.push(value);
+ break;
+ case ARGUMENT_TYPES.INT:
+ this.values.push(parseInt(value));
+ break;
+ case ARGUMENT_TYPES.FLOAT:
+ this.values.push(parseFloat(value));
+ break;
+ case ARGUMENT_TYPES.COMMANDS:
+ this.values.push(
+ new Parser(value, this.callback).parse()
+ );
+ break;
+ case ARGUMENT_TYPES.EXPRESSION:
+ this.values.push(this.parseExpression(value).eval());
+ break;
+ case ARGUMENT_TYPES.PARAMETERS: // Example
+ this.values.push(value.split(" "));
+ break;
+ default:
+ console.log("Unknown argType: ", argTemplate);
+ break;
+ }
+ }
+ }
+ parseExpression(ExpressionString) {
+ let p = new Parser(ExpressionString,null);
+ let token = p.nextToken();
+ let next = p.nextToken();
+ let e;
+ let right;
+ if (next == '/' || next == '*' || next == '+' || next == '-') {
+ right = this.parseExpression(p.getArgs());
+ e = new Expression(next,new Expression('$',token), right);
+ } else
+ return new Expression('$', token);
+ if (right.lvl() > e.lvl()) {
+ let new_left = new Expression(next, new Expression('$',token), right.left);
+ e = new Expression(right.type, new_left, right.right);
+ }
+ return e;
+ }
+ /**
+ * Executes the command with the values given at the creation of the
+ * instance.
+ *
+ * @memberof CommandExecutor
+ */
+ execute(repcount) {
+ this.command.func.apply(this, this.values);
+ if (this.callback) {
+ this.callback();
+ }
+ }
+}
+
+/**
+ * It stores all the commands available.
+ *
+ * @class CommandLookUp
+ */
+class CommandLookUp {
+
+ /**
+ * Creates an instance of CommandLookUp.
+ * @memberokUp
+ */
+ constructor() {
+ this.commands = [];
+ }
+
+ /**
+ * Adding a new command to the list.
+ *
+ * @param {Command} command New command to add to the list.
+ * @memberof CommandLookUp
+ */
+ add(command) {
+ this.commands.push(command);
+ }
+
+ /**
+ * Return a command that matches the name.
+ *
+ * @param {String} name The name of the command you want back.
+ * @returns The command that matches the name, or null if it can't find it.
+ * @memberof CommandLookUp
+ */
+ get(name) {
+ let item = null;
+ let index = 0;
+ while (!item && index < this.commands.length) {
+ if (this.commands[index].name === name) item = this.commands[index];
+
+ index++;
+ }
+
+ return item;
+ }
+}
diff --git a/commandList.js b/commandList.js
index 16fec75..ff26e1d 100644
--- a/commandList.js
+++ b/commandList.js
@@ -1,190 +1,219 @@
-/**
- * This instance of the CommandLookUp class will be the global variable
- * that will store all the commands available.
- */
-const commandLookUp = new CommandLookUp();
-
-/**
- * To add a new command, just need the name, the arguments,
- * and then the function to execute.
- * Note: When adding a new command, update the list of supported commands on the readme file
- */
-commandLookUp.add(
- new Command("fd", [new CommandArg("value", ARGUMENT_TYPES.EXPRESSION)], value => {
- turtle.forward(value);
- })
-);
-
-commandLookUp.add(
- new Command("bd", [new CommandArg("value", ARGUMENT_TYPES.EXPRESSION)], value => {
- turtle.forward(-value);
- })
-);
-
-commandLookUp.add(
- new Command("rt", [new CommandArg("value", ARGUMENT_TYPES.EXPRESSION)], value => {
- turtle.right(value);
- })
-);
-
-commandLookUp.add(
- new Command("lt", [new CommandArg("value", ARGUMENT_TYPES.EXPRESSION)], value => {
- turtle.right(-value);
- })
-);
-
-commandLookUp.add(
- new Command("pu", [], () => {
- turtle.pen = false;
- })
-);
-
-commandLookUp.add(
- new Command("pd", [], () => {
- turtle.pen = true;
- })
-);
-
-commandLookUp.add(
- new Command(
- "pensize",
- [new CommandArg("size", ARGUMENT_TYPES.EXPRESSION)],
- size => {
- turtle.strokeWeight = size;
- }
- )
-);
-
-commandLookUp.add(
- new Command(
- "setxy",
- [
- new CommandArg("x", ARGUMENT_TYPES.EXPRESSION),
- new CommandArg("y", ARGUMENT_TYPES.EXPRESSION)
- ],
- (x, y) => {
- turtle.x = x;
- turtle.y = y;
- }
- )
-);
-
-commandLookUp.add(
- new Command("setx", [new CommandArg("x", ARGUMENT_TYPES.EXPRESSION)], x => {
- turtle.x = x;
- })
-);
-
-commandLookUp.add(
- new Command("sety", [new CommandArg("y", ARGUMENT_TYPES.EXPRESSION)], y => {
- turtle.y = y;
- })
-);
-
-commandLookUp.add(
- new Command("home", [], () => {
- turtle["home"]();
- })
-);
-
-commandLookUp.add(
- new Command("radians", [], () => {
- angleMode(RADIANS);
- })
-);
-
-commandLookUp.add(
- new Command("degrees", [], () => {
- angleMode(DEGREES);
- })
-);
-
-commandLookUp.add(
- new Command(
- "repeat",
- [
- new CommandArg("lengthLoop", ARGUMENT_TYPES.INT),
- new CommandArg("commands", ARGUMENT_TYPES.COMMANDS)
- ],
- function(lengthLoop, commands) {
- for (let i = 0; i < lengthLoop; i++) {
- for (let cmd of commands) {
- cmd.execute(i + 1);
- }
- }
- }
- )
-);
-
-/**
- * Color, added as example. Given a value, it set the stroke.
- */
-commandLookUp.add(
- new Command("color", [new CommandArg("color", ARGUMENT_TYPES.STR,(str)=>{
- return /^#?([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})$/.test(str);
- })], color => {
- // sanity sake let you use hex without the need for #
- if (color[0] != "#") {
- color = "#" + color;
- }
-
- turtle.strokeColor = color;
- })
-);
-
-/*
- * Not apart of logo this allows us to use a RGB instead of HEX
- * Though not standard in logo this just gives us a slightly more fine grain color
- */
-commandLookUp.add(
- new Command(
- "colorrgb",
- [new CommandArg("params", ARGUMENT_TYPES.PARAMETERS)],
- params => {
- let [r, g, b] = params;
- r = parseInt(r);
- g = parseInt(g);
- b = parseInt(b);
-
- if (r > 255) {
- r = 255;
- }
- if (r < 0) {
- r = 0;
- }
-
- if (g > 255) {
- g = 255;
- }
- if (g < 0) {
- g = 0;
- }
-
- if (b > 255) {
- b = 255;
- }
- if (r < 0) {
- b = 0;
- }
-
- turtle.strokeColor = color(r, g, b);
- }
- )
-);
-
-/**
- * Added as example of taking [...] as not only commands
- * but strings you can later process.
- * This command expects 3 args separated by spaces.
- */
-commandLookUp.add(
- new Command(
- "author",
- [new CommandArg("params", ARGUMENT_TYPES.PARAMETERS)],
- params => {
- const [author, website, twitter] = params;
- console.log("This repository has been created by:");
- console.log(`${author} (@${twitter}) - ${website}`);
- }
- )
-);
+/**
+ * This instance of the CommandLookUp class will be the global variable
+ * that will store all the commands available.
+ */
+const commandLookUp = new CommandLookUp();
+
+/**
+ * To add a new command, just need the name, the arguments,
+ * and then the function to execute.
+ * Note: When adding a new command, update the list of supported commands on the readme file
+ */
+
+commandLookUp.add(
+ new Command("label", [new CommandArg("displayText", ARGUMENT_TYPES.PARAMETERS)], params => {
+ textAlign(CENTER);
+ let realText = "";
+ for (let param of params) {
+ if (param.charAt(0) == '"') {
+ realText += param.substring(1);
+ } else {
+ realText += param;
+ }
+ realText += " ";
+ }
+ turtle.label(realText);
+ })
+);
+
+commandLookUp.add(
+ new Command("cs", [], () => {
+ background(bgcolor);
+ })
+);
+
+commandLookUp.add(
+ new Command("setfontsize", [new CommandArg("size", ARGUMENT_TYPES.INT)], displayText => {
+ textSize(displayText);
+ })
+);
+
+commandLookUp.add(
+ new Command("fd", [new CommandArg("value", ARGUMENT_TYPES.EXPRESSION)], value => {
+ turtle.forward(value);
+ })
+);
+
+commandLookUp.add(
+ new Command("bd", [new CommandArg("value", ARGUMENT_TYPES.EXPRESSION)], value => {
+ turtle.forward(-value);
+ })
+);
+
+commandLookUp.add(
+ new Command("rt", [new CommandArg("value", ARGUMENT_TYPES.EXPRESSION)], value => {
+ turtle.right(value);
+ })
+);
+
+commandLookUp.add(
+ new Command("lt", [new CommandArg("value", ARGUMENT_TYPES.EXPRESSION)], value => {
+ turtle.right(-value);
+ })
+);
+
+commandLookUp.add(
+ new Command("pu", [], () => {
+ turtle.pen = false;
+ })
+);
+
+commandLookUp.add(
+ new Command("pd", [], () => {
+ turtle.pen = true;
+ })
+);
+
+commandLookUp.add(
+ new Command(
+ "pensize",
+ [new CommandArg("size", ARGUMENT_TYPES.EXPRESSION)],
+ size => {
+ turtle.strokeWeight = size;
+ }
+ )
+);
+
+commandLookUp.add(
+ new Command(
+ "setxy",
+ [
+ new CommandArg("x", ARGUMENT_TYPES.EXPRESSION),
+ new CommandArg("y", ARGUMENT_TYPES.EXPRESSION)
+ ],
+ (x, y) => {
+ turtle.x = x;
+ turtle.y = y;
+ }
+ )
+);
+
+commandLookUp.add(
+ new Command("setx", [new CommandArg("x", ARGUMENT_TYPES.EXPRESSION)], x => {
+ turtle.x = x;
+ })
+);
+
+commandLookUp.add(
+ new Command("sety", [new CommandArg("y", ARGUMENT_TYPES.EXPRESSION)], y => {
+ turtle.y = y;
+ })
+);
+
+commandLookUp.add(
+ new Command("home", [], () => {
+ turtle["home"]();
+ })
+);
+
+commandLookUp.add(
+ new Command("radians", [], () => {
+ angleMode(RADIANS);
+ })
+);
+
+commandLookUp.add(
+ new Command("degrees", [], () => {
+ angleMode(DEGREES);
+ })
+);
+
+commandLookUp.add(
+ new Command(
+ "repeat",
+ [
+ new CommandArg("lengthLoop", ARGUMENT_TYPES.INT),
+ new CommandArg("commands", ARGUMENT_TYPES.COMMANDS)
+ ],
+ function(lengthLoop, commands) {
+ for (let i = 0; i < lengthLoop; i++) {
+ for (let cmd of commands) {
+ cmd.execute(i + 1);
+ }
+ }
+ }
+ )
+);
+
+/**
+ * Color, added as example. Given a value, it set the stroke.
+ */
+commandLookUp.add(
+ new Command("color", [new CommandArg("color", ARGUMENT_TYPES.COLOR,(str)=>{
+ return /^#?([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})$/.test(str);
+ })], color => {
+ // sanity sake let you use hex without the need for #
+ if (color[0] != "#") {
+ color = "#" + color;
+ }
+
+ turtle.strokeColor = color;
+ })
+);
+
+/*
+ * Not apart of logo this allows us to use a RGB instead of HEX
+ * Though not standard in logo this just gives us a slightly more fine grain color
+ */
+commandLookUp.add(
+ new Command(
+ "colorrgb",
+ [new CommandArg("params", ARGUMENT_TYPES.PARAMETERS)],
+ params => {
+ let [r, g, b] = params;
+ r = parseInt(r);
+ g = parseInt(g);
+ b = parseInt(b);
+
+ if (r > 255) {
+ r = 255;
+ }
+ if (r < 0) {
+ r = 0;
+ }
+
+ if (g > 255) {
+ g = 255;
+ }
+ if (g < 0) {
+ g = 0;
+ }
+
+ if (b > 255) {
+ b = 255;
+ }
+ if (r < 0) {
+ b = 0;
+ }
+
+ turtle.strokeColor = color(r, g, b);
+ }
+ )
+);
+
+/**
+ * Added as example of taking [...] as not only commands
+ * but strings you can later process.
+ * This command expects 3 args separated by spaces.
+ */
+commandLookUp.add(
+ new Command(
+ "author",
+ [new CommandArg("params", ARGUMENT_TYPES.PARAMETERS)],
+ params => {
+ const [author, website, twitter] = params;
+ console.log("This repository has been created by:");
+ console.log(`${author} (@${twitter}) - ${website}`);
+ }
+ )
+);
diff --git a/expression.js b/expression.js
index e4d2e12..7e3222e 100644
--- a/expression.js
+++ b/expression.js
@@ -1,31 +1,31 @@
-class Expression {
- constructor(type, left, right=undefined) {
- this.type = type;
- this.left = left;
- this.right = right;
- }
-
- eval() {
- if(this.type == '$') {
- return parseFloat(this.left);
- } else if(this.type == '/') {
- return this.left.eval() / this.right.eval();
- } else if(this.type == '*') {
- return this.left.eval() * this.right.eval();
- } else if(this.type == '-') {
- return this.left.eval() - this.right.eval();
- } else if(this.type == '+') {
- return this.left.eval() + this.right.eval();
- }
- return 0;
- }
-
- lvl() {
- if(this.type == '+') return 4;
- if(this.type == '-') return 3;
- if(this.type == '*') return 2;
- if(this.type == '/') return 1;
- return 0;
- }
-
-}
+class Expression {
+ constructor(type, left, right=undefined) {
+ this.type = type;
+ this.left = left;
+ this.right = right;
+ }
+
+ eval() {
+ if(this.type == '$') {
+ return parseFloat(this.left);
+ } else if(this.type == '/') {
+ return this.left.eval() / this.right.eval();
+ } else if(this.type == '*') {
+ return this.left.eval() * this.right.eval();
+ } else if(this.type == '-') {
+ return this.left.eval() - this.right.eval();
+ } else if(this.type == '+') {
+ return this.left.eval() + this.right.eval();
+ }
+ return 0;
+ }
+
+ lvl() {
+ if(this.type == '+') return 4;
+ if(this.type == '-') return 3;
+ if(this.type == '*') return 2;
+ if(this.type == '/') return 1;
+ return 0;
+ }
+
+}
diff --git a/index.html b/index.html
index 26959f7..59d6034 100644
--- a/index.html
+++ b/index.html
@@ -1,57 +1,57 @@
-
-
-
-
-
-
-
- Logo
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- LOGO
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Icons made by Turtle from www.flaticon.com is licensed by CC 3.0 BY
-
-
-
-
-
-
+
+
+
+
+
+
+
+ Logo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ LOGO
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Icons made by Turtle from www.flaticon.com is licensed by CC 3.0 BY
+
+
+
+
+
+
diff --git a/parser.js b/parser.js
index 811130d..23753ff 100644
--- a/parser.js
+++ b/parser.js
@@ -1,139 +1,145 @@
-
-class Parser {
-
- /**
- * Creates an instance of Parser.
- * @param {String} text The text to parse
- * @param {Function} afterCmdCallback Function to execute after the commands are executed
- * @memberof Parser
- */
- constructor(text, afterCmdCallback,offset = 0) {
- if (!text) text = '';
-
- this.text = text.trimRight();
- this.index = 0;
- this.afterCmdCallback = afterCmdCallback
- this.offset = offset;
- }
-
- /**
- * Private method
- *
- * @returns Boolean If the index has surpased the length of the text or not.
- * @memberof Parser
- */
- remainingTokens() {
- return this.index < this.text.length;
- }
-
- /**
- * Private method
- *
- * @returns String The next token after the actual index.
- * @memberof Parser
- */
- nextToken() {
- let regWhitespace = /\s/;
-
- while (regWhitespace.test(this.text.charAt(this.index)) && this.remainingTokens()) this.index++;
-
- let firstChar = this.text.charAt(this.index);
-
- let token = '';
- let isTokenList = false;
- let depth = 0;
-
-
- if (firstChar === '[') {
- this.index++;
- depth++;
- isTokenList = true;
- }
-
- let actualChar = this.text.charAt(this.index);
-
- while (((regWhitespace.test(actualChar) && isTokenList) || !regWhitespace.test(actualChar)) && this.remainingTokens()) {
- this.index++;
-
- if (isTokenList) {
- if (actualChar === '[') depth++;
- else if (actualChar === ']') depth--;
-
- if (actualChar === ']' && depth === 0) return token;
- }
-
- token += actualChar;
- actualChar = this.text.charAt(this.index);
- }
-
- return token;
- }
-
- getArgs() {
- let ret = this.nextToken();
- let temp = this.index;
- let next = this.nextToken();
- if (next == '/' || next == '*' || next == '+' || next == '-') {
- return ret + ' ' + next + ' ' + this.getArgs();
- }
- else {
- this.index = temp;
- return ret;
- }
- }
-
- /**
- * Public method
- *
- * @returns [CommandExecutor] Parsed text converted into CommandExecutors
- * ready to be executed.
- * @memberof Parser
- */
- parse() {
- let cmdsExecutors = [];
- while (this.remainingTokens()) {
- let cmdStrat = this.index;
- let token = this.nextToken();
- let cmd = commandLookUp.get(token);
- let args = [];
- if (cmd) {
- for (let i = 0; i < cmd.argsTemplate.length; i++) {
- let startIndex = this.index;
- let arg = cmd.argsTemplate[i];
- let theArgToken = this.getArgs();
- let endIndex = this.index + 1;
- if (arg.validator !== undefined) {
- let valid;
- if(arg.type == ARGUMENT_TYPES.COMMANDS)
- {
- while(this.text.length>startIndex && this.text[startIndex++]!='[');
- console.log({arg:theArgToken,offset:startIndex});
- valid = arg.validator(theArgToken,startIndex+this.offset)
- }else
- valid = arg.validator(theArgToken);
- if (!valid) {
- console.error(`Argument number ${i} (${theArgToken}) is invalid for command ${token} parser offset ${this.offset}`);
- throw {
- startIndex: startIndex+this.offset,
- endIndex: endIndex+this.offset
- }
- }
- args.push(theArgToken);
- }
- else {
- args.push(theArgToken);
- }
-
- }
- cmdsExecutors.push(new CommandExecutor(cmd, args, this.afterCmdCallback));
- }else {
- let endIndex = this.index + 1;
- throw {
- startIndex: cmdStrat+this.offset,
- endIndex: endIndex+this.offset
- }
- }
- }
- return cmdsExecutors;
- }
-}
+
+class Parser {
+
+ /**
+ * Creates an instance of Parser.
+ * @param {String} text The text to parse
+ * @param {Function} afterCmdCallback Function to execute after the commands are executed
+ * @memberof Parser
+ */
+ constructor(text, afterCmdCallback,offset = 0) {
+ if (!text) text = '';
+
+ this.text = text.trimRight();
+ this.index = 0;
+ this.afterCmdCallback = afterCmdCallback
+ this.offset = offset;
+ }
+
+ /**
+ * Private method
+ *
+ * @returns Boolean If the index has surpased the length of the text or not.
+ * @memberof Parser
+ */
+ remainingTokens() {
+ return this.index < this.text.length;
+ }
+
+ /**
+ * Private method
+ *
+ * @returns String The next token after the actual index.
+ * @memberof Parser
+ */
+ nextToken() {
+ let regWhitespace = /\s/;
+
+ while (regWhitespace.test(this.text.charAt(this.index)) && this.remainingTokens()) this.index++;
+
+ let firstChar = this.text.charAt(this.index);
+
+ let token = '';
+ let isTokenList = false;
+ let depth = 0;
+
+
+ if (firstChar === '[') {
+ this.index++;
+ depth++;
+ isTokenList = true;
+ }
+
+ let actualChar = this.text.charAt(this.index);
+
+ while (((regWhitespace.test(actualChar) && isTokenList) || !regWhitespace.test(actualChar)) && this.remainingTokens()) {
+ this.index++;
+
+ if (isTokenList) {
+ if (actualChar === '[') depth++;
+ else if (actualChar === ']') depth--;
+
+ if (actualChar === ']' && depth === 0) return token;
+ }
+
+ token += actualChar;
+ actualChar = this.text.charAt(this.index);
+ }
+
+ return token;
+ }
+
+ getArgs() {
+ let ret = this.nextToken();
+ for (let i = 1; i < ret.length; i++) {
+ let char = ret.charAt(i);
+ if (char === '/' || char === '*' || char === '-' || char === '+') {
+ return ret.substring(0, i) + ' ' + char + ' ' + ret.substring(i + 1);
+ }
+ }
+ let temp = this.index;
+ let next = this.nextToken();
+ if (next == '/' || next == '*' || next == '+' || next == '-') {
+ return ret + ' ' + next + ' ' + this.getArgs();
+ }
+ else {
+ this.index = temp;
+ return ret;
+ }
+ }
+
+ /**
+ * Public method
+ *
+ * @returns [CommandExecutor] Parsed text converted into CommandExecutors
+ * ready to be executed.
+ * @memberof Parser
+ */
+ parse() {
+ let cmdsExecutors = [];
+ while (this.remainingTokens()) {
+ let cmdStrat = this.index;
+ let token = this.nextToken();
+ let cmd = commandLookUp.get(token);
+ let args = [];
+ if (cmd) {
+ for (let i = 0; i < cmd.argsTemplate.length; i++) {
+ let startIndex = this.index;
+ let arg = cmd.argsTemplate[i];
+ let theArgToken = this.getArgs();
+ let endIndex = this.index + 1;
+ if (arg.validator !== undefined) {
+ let valid;
+ if(arg.type == ARGUMENT_TYPES.COMMANDS)
+ {
+ while(this.text.length>startIndex && this.text[startIndex++]!='[');
+ console.log({arg:theArgToken,offset:startIndex});
+ valid = arg.validator(theArgToken,startIndex+this.offset)
+ }else
+ valid = arg.validator(theArgToken);
+ if (!valid) {
+ console.error(`Argument number ${i} (${theArgToken}) is invalid for command ${token} parser offset ${this.offset}`);
+ throw {
+ startIndex: startIndex+this.offset,
+ endIndex: endIndex+this.offset
+ }
+ }
+ args.push(theArgToken);
+ }
+ else {
+ args.push(theArgToken);
+ }
+
+ }
+ cmdsExecutors.push(new CommandExecutor(cmd, args, this.afterCmdCallback));
+ }else {
+ let endIndex = this.index + 1;
+ throw {
+ startIndex: cmdStrat+this.offset,
+ endIndex: endIndex+this.offset
+ }
+ }
+ }
+ return cmdsExecutors;
+ }
+}
diff --git a/sketch.js b/sketch.js
index 4e2255f..c24c1b5 100644
--- a/sketch.js
+++ b/sketch.js
@@ -1,361 +1,379 @@
-// Coding Challenge 121: Logo
-// https://youtu.be/i-k04yzfMpw
-
-let canvas;
-let editor;
-let editor_bg;
-let turtle;
-let recentreBtn;
-let bgcolorBtn;
-
-let draggingCanvas = false;
-let dragStartMousePos = new p5.Vector();
-let dragStartCanvasOffset = new p5.Vector();
-let allCases;
-let bgcolor = "#6040e6";
-
-let resizingEditor = false;
-
-// Used for scaling drawings to fit the canvas
-let canvasScrollX = 0;
-let canvasScrollY = 0;
-let canvasScaleX = 1;
-let canvasScaleY = 1;
-let drawingBounds = new BoundingBox();
-let drawingBoundsFirstPoint = true;
-let centerAnimation = null;
-
-let primary_keywords = ['fd', 'bd', 'rt', 'lt'];
-let secondary_keywords = ['color', 'colorrgb', 'pensize', 'repeat', 'pu', 'pd'];
-
-function syntaxHighlight(code) {
- let tokens = code.split(/(\[\s*\d+\s+\d+\s+\d+\s*\])|(\[)|(\])|(\s+)/).filter(x => (x !== undefined && x != ''));
- let newHTML = '';
-
- for (i of tokens) {
- if (primary_keywords.includes(i)) { // #f48771
- newHTML += `${i}`;
- }
-
- else if (secondary_keywords.includes(i)) {
- newHTML += `${i}`;
- }
-
- else if (['[', ']'].includes(i)) {
- newHTML += `${i}`;
- }
-
- else if (/#[a-zA-z0-9]/.test(i) || /\[\s*\d+\s+\d+\s+\d+\s*\]/.test(i)) {
- newHTML += `${i}`;
- }
-
- else if (parseInt(i) !== NaN) {
- newHTML += `${i}`;
- }
-
- else {
- newHTML += `${i}`;
- }
- }
-
- return newHTML;
-}
-
-function showError(start,end)
-{
- let text = editor.value();
- start = (start > 0) ? start + 1 : start;
- end = (start < text.length) ? end - 1 : end;
-
- let beforeErr= text.substring(0,start);
- let afterErr = text.substring(end);
- let err = text.substring(start,end);
- let bg_text = `${syntaxHighlight(beforeErr)}${err}${syntaxHighlight(afterErr)}`;
- editor_bg.html(bg_text);
-}
-
-/*
- * Returns a function that waits for `delay` milliseconds and if it had not been called again, invokes `func`
- * If it _is_ called again within `delay` milliseconds, `func` is *not* invoked (yet) and the timer is reset.
- */
-function debounce(func, delay) {
- let timeout;
- return () => {
- clearTimeout(timeout);
- timeout = setTimeout(() => {
- func();
- timeout = null;
- }, delay);
- }
-}
-
-function canvasResized() {
- scaleToFitBoundingBox(drawingBounds, true);
-}
-
-const debouncedCanvasResized = debounce(canvasResized, 300);
-
-function preload() {
- loadJSON("./assets/tests.json", createTestDataView);
-}
-
-function getCanvasSize() {
- const bounds = select("#logo-canvas").elt.getBoundingClientRect();
- return { width: bounds.width, height: bounds.height };
-}
-
-function updateEditorOverlayMargin() {
- const editorBounds = select('#editor-container').elt.getBoundingClientRect();
- editor_bg.elt.style.right = `${editorBounds.width - editor.elt.scrollWidth - 3}px`;
-}
-
-function windowResized() {
- const canvasSize = getCanvasSize();
- resizeCanvas(canvasSize.width, canvasSize.height);
- updateResizeHandlePosition();
- debouncedCanvasResized();
- goTurtle();
- updateEditorOverlayMargin();
-}
-
-function setup() {
- const canvasSize = getCanvasSize();
- canvas = createCanvas(canvasSize.width, canvasSize.height);
- div = document.querySelector("#logo-canvas");
-
- div.appendChild(canvas.elt);
-
- // Use a setTimeout with length 0 to call windowResized immediately after the DOM has updated (since we are dynamically injecting a canvas element)
- setTimeout(() => {
- windowResized();
- scaleToFitBoundingBox(drawingBounds);
- }, 0);
-
- angleMode(DEGREES);
- background(bgcolor);
-
- canvas.mousePressed(function () {
- if (mouseIsPressed) {
- draggingCanvas = true;
- dragStartMousePos = new p5.Vector(mouseX, mouseY);
- dragStartCanvasOffset = new p5.Vector(canvasScrollX, canvasScrollY);
- }
- });
-
- canvas.mouseMoved(function () {
- if (mouseIsPressed && draggingCanvas) {
- canvasScrollX = dragStartCanvasOffset.x + dragStartMousePos.x - mouseX;
- canvasScrollY = dragStartCanvasOffset.y + dragStartMousePos.y - mouseY;
- goTurtle();
- }
- });
-
- canvas.mouseReleased(function () {
- draggingCanvas = false;
- });
-
- recentreBtn = document.querySelector("#recentre");
- bgcolorBtn = document.querySelector("#bgcolor");
-
- recentreBtn.onclick = function () {
- scaleToFitBoundingBox(drawingBounds, true);
- }
-
- bgcolorBtn.onclick = function () {
- let r = floor(random(0, 255));
- let g = floor(random(0, 255));
- let b = floor(random(0, 255));
-
- let hexR = `${r <= 15 ? '0' : ''}${r.toString(16)}`;
- let hexG = `${g <= 15 ? '0' : ''}${g.toString(16)}`;
- let hexB = `${b <= 15 ? '0' : ''}${b.toString(16)}`;
-
- let col = `#${hexR}${hexG}${hexB}`;
- bgcolor = col;
- goTurtle();
- }
-
- editor = select("#code");
- setDefaultDrawing();
- editor.input(() => {
- updateEditorContent();
- goTurtle();
- });
- editor_bg = select("#code_bg");
- scaleToFitBoundingBox(drawingBounds); // This also redraws (it has to in order to measure the size of the drawing)
- updateEditorContent();
-
- editor.elt.addEventListener('scroll', ev => {
- select('#code_bg').elt.scrollTop = editor.elt.scrollTop;
- }, { passive: true }); // The 'passive: true' parameter increases performance when scrolling by making it impossible to cancel the scroll events
-}
-
-function scaleToFitBoundingBox(boundingBox, animate = false) {
- // If it's already animating, wait for it to finish
- if (centerAnimation !== null) return;
-
- // Run this once first so we can measure the size of the drawing
- goTurtle();
-
- // 15% padding around the drawing in the canvas
- let drawingPadding = Math.min(width, height) * 0.15;
- let scale = Math.min((width - drawingPadding) / (boundingBox.width), (height - drawingPadding) / (boundingBox.height));
-
- centerAnimation = new Animation(
- { // Starting values
- scaleX: canvasScaleX,
- scaleY: canvasScaleY,
- scrollX: canvasScrollX,
- scrollY: canvasScrollY
- },
- { // Ending values
- scaleX: scale,
- scaleY: scale,
- scrollX: (drawingBounds.x * scale - width * .5),
- scrollY: (drawingBounds.y * scale - height * .5)
- },
- 500, // Duration
- values => { // Update callback
- canvasScaleX = values.scaleX;
- canvasScaleY = values.scaleY;
- canvasScrollX = values.scrollX;
- canvasScrollY = values.scrollY;
- goTurtle();
- },
- () => { // Finished callback
- centerAnimation = null;
- },
- 'EASE_IN_OUT_QUART' // Easing function
- );
-
- if (!animate)
- centerAnimation.skip();
-}
-
-function afterCommandExecuted() {
- if (turtle.pen) {
- if (drawingBoundsFirstPoint) {
- drawingBounds.reset();
- drawingBounds.move(turtle.x, turtle.y);
- drawingBoundsFirstPoint = false;
- } else {
- drawingBounds.includePoint(turtle.x, turtle.y);
- drawingBounds.includePoint(turtle.prevX, turtle.prevY);
- }
- }
-}
-
-function goTurtle() {
- turtle = new Turtle(0, 0, 0);
- drawingBoundsFirstPoint = true;
- background(bgcolor);
-
- push();
- translate(-canvasScrollX, -canvasScrollY);
- scale(canvasScaleX, canvasScaleY);
-
- push();
- turtle.reset();
- let code = editor.value();
- let parser = new Parser(code, afterCommandExecuted);
- try {
- let commands = parser.parse();
- for (let cmd of commands) {
- cmd.execute();
- }
- } catch (err) {
- showError(err.startIndex,err.endIndex);
- }
-
- pop();
-
- pop();
-}
-
-/**
- * Writes the Logo code for the default drawing to the textarea
- * Called on page load
- * Also called when selecting the default item from the #testdata dropdown
- */
-function setDefaultDrawing() {
- editor.value("pu lt 90 fd 100 lt 90 fd 250 rt 90 rt 90 pd fd 500 rt 90 fd 150 rt 90 fd 500 rt 90 fd 150");
-}
-
-function createTestDataView(cases) {
- let selector = select("#testdata");
-
- selector.option("Logo Default", -1);
-
- for (i = 0; i < cases.length; i++) {
- selector.option(cases[i].name, i);
- }
-
- // because why not do it here
- selector.changed(function () {
- let val = parseInt(selector.value());
- if (val < 0) {
- // Use the default drawing
- setDefaultDrawing();
- } else {
- // Use a drawing from tests.json
- editor.value(cases[val].code);
- }
-
- // Reset default parameters for turtle
- turtle.strokeColor = 255;
- turtle.dir = 0;
- turtle.x = 0;
- turtle.y = 0;
-
- // Reset default parameters for camera
- canvasScrollX = 0;
- canvasScrollY = 0;
- canvasScaleX = 1;
- canvasScaleY = 1;
-
- // Move and scale the drawing to fit on-screen
- scaleToFitBoundingBox(drawingBounds);
-
- updateEditorOverlayMargin();
- updateEditorContent();
- });
-}
-
-function updateResizeHandlePosition() {
- const resizeHandle = select('#resize-handle').elt;
- const editorBounds = select('#editor-container').elt.getBoundingClientRect();
- resizeHandle.style.top = `${editorBounds.top - 8}px`;
- resizeHandle.style.width = `${editorBounds.width}px`;
-}
-
-function updateEditorSize(mouseY) {
- const resizeHandleBounds = select('#resize-handle').elt.getBoundingClientRect();
- const editorBounds = select('#editor-container').elt.getBoundingClientRect();
- select('#editor-container').elt.style.height = `${editorBounds.bottom - mouseY - 8}px`;
- windowResized();
- updateResizeHandlePosition();
-}
-
-function updateEditorContent() {
- editor_bg.html(syntaxHighlight(editor.value()));
-}
-
-function resizeHandleMouseDown(ev) {
- resizingEditor = true;
- ev.preventDefault();
- return false;
-}
-
-function windowMouseMove(ev) {
- if (resizingEditor)
- updateEditorSize(ev.clientY);
-}
-
-function windowMouseUp() {
- resizingEditor = false;
- updateResizeHandlePosition();
-}
-
-document.getElementById('resize-handle').addEventListener('mousedown', resizeHandleMouseDown);
-document.addEventListener('mousemove', windowMouseMove);
-document.addEventListener('mouseup', windowMouseUp);
+// Coding Challenge 121: Logo
+// https://youtu.be/i-k04yzfMpw
+
+let canvas;
+let editor;
+let editor_bg;
+let turtle;
+let recentreBtn;
+let bgcolorBtn;
+
+let draggingCanvas = false;
+let dragStartMousePos = new p5.Vector();
+let dragStartCanvasOffset = new p5.Vector();
+let allCases;
+let bgcolor = "#6040e6";
+
+let resizingEditor = false;
+
+// Used for scaling drawings to fit the canvas
+let canvasScrollX = 0;
+let canvasScrollY = 0;
+let canvasScaleX = 1;
+let canvasScaleY = 1;
+let drawingBounds = new BoundingBox();
+let drawingBoundsFirstPoint = true;
+let centerAnimation = null;
+
+let primary_keywords = ['fd', 'bd', 'rt', 'lt'];
+let secondary_keywords = ['color', 'colorrgb', 'pensize', 'repeat', 'pu', 'pd'];
+
+function syntaxHighlight(code) {
+ let tokens = code.split(/(\[\s*\d+\s+\d+\s+\d+\s*\])|(\[)|(\])|(\s+)/).filter(x => (x !== undefined && x != ''));
+ let newHTML = '';
+
+ for (i of tokens) {
+ if (primary_keywords.includes(i)) { // #f48771
+ newHTML += `${i}`;
+ }
+
+ else if (secondary_keywords.includes(i)) {
+ newHTML += `${i}`;
+ }
+
+ else if (['[', ']'].includes(i)) {
+ newHTML += `${i}`;
+ }
+
+ else if (/#[a-zA-z0-9]/.test(i) || /\[\s*\d+\s+\d+\s+\d+\s*\]/.test(i)) {
+ newHTML += `${i}`;
+ }
+
+ else if (parseInt(i) !== NaN) {
+ newHTML += `${i}`;
+ }
+
+ else {
+ newHTML += `${i}`;
+ }
+ }
+
+ return newHTML;
+}
+
+function showError(start,end)
+{
+ let text = editor.value();
+ start = (start > 0) ? start + 1 : start;
+ end = (start < text.length) ? end - 1 : end;
+
+ let beforeErr= text.substring(0,start);
+ let afterErr = text.substring(end);
+ let err = text.substring(start,end);
+ let bg_text = `${syntaxHighlight(beforeErr)}${err}${syntaxHighlight(afterErr)}`;
+ editor_bg.html(bg_text);
+}
+
+/*
+ * Returns a function that waits for `delay` milliseconds and if it had not been called again, invokes `func`
+ * If it _is_ called again within `delay` milliseconds, `func` is *not* invoked (yet) and the timer is reset.
+ */
+function debounce(func, delay) {
+ let timeout;
+ return () => {
+ clearTimeout(timeout);
+ timeout = setTimeout(() => {
+ func();
+ timeout = null;
+ }, delay);
+ }
+}
+
+function canvasResized() {
+ scaleToFitBoundingBox(drawingBounds, true);
+}
+
+const debouncedCanvasResized = debounce(canvasResized, 300);
+
+function preload() {
+ loadJSON("./assets/tests.json", createTestDataView);
+}
+
+function getCanvasSize() {
+ const bounds = select("#logo-canvas").elt.getBoundingClientRect();
+ return { width: bounds.width, height: bounds.height };
+}
+
+function updateEditorOverlayMargin() {
+ const editorBounds = select('#editor-container').elt.getBoundingClientRect();
+ editor_bg.elt.style.right = `${editorBounds.width - editor.elt.scrollWidth - 3}px`;
+}
+
+function windowResized() {
+ const canvasSize = getCanvasSize();
+ resizeCanvas(canvasSize.width, canvasSize.height);
+ updateResizeHandlePosition();
+ debouncedCanvasResized();
+ goTurtle();
+ updateEditorOverlayMargin();
+}
+
+function setup() {
+ const canvasSize = getCanvasSize();
+ canvas = createCanvas(canvasSize.width, canvasSize.height);
+ div = document.querySelector("#logo-canvas");
+
+ div.appendChild(canvas.elt);
+
+ // Use a setTimeout with length 0 to call windowResized immediately after the DOM has updated (since we are dynamically injecting a canvas element)
+ setTimeout(() => {
+ windowResized();
+ scaleToFitBoundingBox(drawingBounds);
+ }, 0);
+
+ angleMode(DEGREES);
+ background(bgcolor);
+
+ canvas.mousePressed(function () {
+ if (mouseIsPressed) {
+ draggingCanvas = true;
+ dragStartMousePos = new p5.Vector(mouseX, mouseY);
+ dragStartCanvasOffset = new p5.Vector(canvasScrollX, canvasScrollY);
+ }
+ });
+
+ canvas.mouseMoved(function () {
+ if (mouseIsPressed && draggingCanvas) {
+ canvasScrollX = dragStartCanvasOffset.x + dragStartMousePos.x - mouseX;
+ canvasScrollY = dragStartCanvasOffset.y + dragStartMousePos.y - mouseY;
+ goTurtle();
+ }
+ });
+
+ canvas.mouseReleased(function () {
+ draggingCanvas = false;
+ });
+
+ recentreBtn = document.querySelector("#recentre");
+ bgcolorBtn = document.querySelector("#bgcolor");
+
+ recentreBtn.onclick = function () {
+ scaleToFitBoundingBox(drawingBounds, true);
+ }
+
+ bgcolorBtn.onclick = function () {
+ let r = floor(random(0, 255));
+ let g = floor(random(0, 255));
+ let b = floor(random(0, 255));
+
+ let hexR = `${r <= 15 ? '0' : ''}${r.toString(16)}`;
+ let hexG = `${g <= 15 ? '0' : ''}${g.toString(16)}`;
+ let hexB = `${b <= 15 ? '0' : ''}${b.toString(16)}`;
+
+ let col = `#${hexR}${hexG}${hexB}`;
+ bgcolor = col;
+ goTurtle();
+ }
+
+ editor = select("#code");
+
+ setDefaultDrawing();
+
+ let editorValue = localStorage.getItem("logo");
+ if (editorValue) {
+ editor.value(editorValue);
+ }
+ editor.input(() => {
+ updateEditorContent();
+ goTurtle();
+ });
+ editor_bg = select("#code_bg");
+ scaleToFitBoundingBox(drawingBounds); // This also redraws (it has to in order to measure the size of the drawing)
+ updateEditorContent();
+
+ editor.elt.addEventListener('scroll', ev => {
+ select('#code_bg').elt.scrollTop = editor.elt.scrollTop;
+ }, { passive: true }); // The 'passive: true' parameter increases performance when scrolling by making it impossible to cancel the scroll events
+}
+
+function scaleToFitBoundingBox(boundingBox, animate = false) {
+ // If it's already animating, wait for it to finish
+ if (centerAnimation !== null) return;
+
+ // Run this once first so we can measure the size of the drawing
+ goTurtle();
+
+ // 15% padding around the drawing in the canvas
+ let drawingPadding = Math.min(width, height) * 0.15;
+ let scale = Math.min((width - drawingPadding) / (boundingBox.width), (height - drawingPadding) / (boundingBox.height));
+
+ centerAnimation = new Animation(
+ { // Starting values
+ scaleX: canvasScaleX,
+ scaleY: canvasScaleY,
+ scrollX: canvasScrollX,
+ scrollY: canvasScrollY
+ },
+ { // Ending values
+ scaleX: scale,
+ scaleY: scale,
+ scrollX: (drawingBounds.x * scale - width * .5),
+ scrollY: (drawingBounds.y * scale - height * .5)
+ },
+ 500, // Duration
+ values => { // Update callback
+ canvasScaleX = values.scaleX;
+ canvasScaleY = values.scaleY;
+ canvasScrollX = values.scrollX;
+ canvasScrollY = values.scrollY;
+ goTurtle();
+ },
+ () => { // Finished callback
+ centerAnimation = null;
+ },
+ 'EASE_IN_OUT_QUART' // Easing function
+ );
+
+ if (!animate)
+ centerAnimation.skip();
+}
+
+function afterCommandExecuted() {
+ if (turtle.pen) {
+ if (drawingBoundsFirstPoint) {
+ drawingBounds.reset();
+ drawingBounds.move(turtle.x, turtle.y);
+ drawingBoundsFirstPoint = false;
+ } else {
+ drawingBounds.includePoint(turtle.x, turtle.y);
+ drawingBounds.includePoint(turtle.prevX, turtle.prevY);
+ }
+ }
+}
+
+function goTurtle() {
+ turtle = new Turtle(0, 0, 0);
+ drawingBoundsFirstPoint = true;
+ background(bgcolor);
+
+ push();
+ translate(-canvasScrollX, -canvasScrollY);
+ scale(canvasScaleX, canvasScaleY);
+
+ push();
+ turtle.reset();
+ let code = editor.value();
+ let parser = new Parser(code, afterCommandExecuted);
+ try {
+ let commands = parser.parse();
+ for (let cmd of commands) {
+ cmd.execute();
+ }
+ } catch (err) {
+ showError(err.startIndex,err.endIndex);
+ }
+
+ pop();
+
+ pop();
+}
+
+/**
+ * Writes the Logo code for the default drawing to the textarea
+ * Called on page load
+ * Also called when selecting the default item from the #testdata dropdown
+ */
+function setDefaultDrawing() {
+ editor.value("pu lt 90 fd 100 lt 90 fd 250 rt 90 rt 90 pd fd 500 rt 90 fd 150 rt 90 fd 500 rt 90 fd 150");
+}
+
+function createTestDataView(cases) {
+ let selector = select("#testdata");
+
+ selector.option("Logo Default", -1);
+
+ for (i = 0; i < cases.length; i++) {
+ selector.option(cases[i].name, i);
+ }
+
+ // because why not do it here
+ selector.changed(function () {
+ let val = parseInt(selector.value());
+ if (val < 0) {
+ // Use the default drawing
+ setDefaultDrawing();
+ } else {
+ // Use a drawing from tests.json
+ editor.value(cases[val].code);
+ }
+
+ // Reset default parameters for turtle
+ turtle.strokeColor = 255;
+ turtle.dir = 0;
+ turtle.x = 0;
+ turtle.y = 0;
+
+ // Reset default parameters for camera
+ canvasScrollX = 0;
+ canvasScrollY = 0;
+ canvasScaleX = 1;
+ canvasScaleY = 1;
+
+ // Move and scale the drawing to fit on-screen
+ scaleToFitBoundingBox(drawingBounds);
+
+ updateEditorOverlayMargin();
+ updateEditorContent();
+ });
+}
+
+function updateResizeHandlePosition() {
+ const resizeHandle = select('#resize-handle').elt;
+ const editorBounds = select('#editor-container').elt.getBoundingClientRect();
+ resizeHandle.style.top = `${editorBounds.top - 8}px`;
+ resizeHandle.style.width = `${editorBounds.width}px`;
+}
+
+function updateEditorSize(mouseY) {
+ const resizeHandleBounds = select('#resize-handle').elt.getBoundingClientRect();
+ const editorBounds = select('#editor-container').elt.getBoundingClientRect();
+ select('#editor-container').elt.style.height = `${editorBounds.bottom - mouseY - 8}px`;
+ windowResized();
+ updateResizeHandlePosition();
+}
+
+function updateEditorContent() {
+ editor_bg.html(syntaxHighlight(editor.value()));
+}
+
+function resizeHandleMouseDown(ev) {
+ resizingEditor = true;
+ ev.preventDefault();
+ return false;
+}
+
+function windowMouseMove(ev) {
+ if (resizingEditor)
+ updateEditorSize(ev.clientY);
+}
+
+function windowMouseUp() {
+ resizingEditor = false;
+ updateResizeHandlePosition();
+}
+
+function keyPressed(e) {
+ if (e.key == 'd' && e.ctrlKey) {
+ saveCanvas('logo', 'png');
+ e.preventDefault();
+ } else if (e.key == 's' && e.ctrlKey) {
+ // save the code to local storage
+ localStorage.setItem('logo', editor.value());
+
+ e.preventDefault();
+ }
+}
+
+document.getElementById('resize-handle').addEventListener('mousedown', resizeHandleMouseDown);
+document.addEventListener('mousemove', windowMouseMove);
+document.addEventListener('mouseup', windowMouseUp);
diff --git a/style.css b/style.css
index fafa0f1..c17e1f9 100644
--- a/style.css
+++ b/style.css
@@ -1,204 +1,204 @@
-body {
- padding: 0;
- margin: 0;
- background: rgba(0, 0, 0, 0.7);
- overflow: hidden;
- width: Calc(100vw - 1em);
- height: 100vh;
- display: grid;
- grid-gap: 0.5em;
- grid-template-columns: 100vw;
- grid-template-rows: 4em 3.5fr auto 1.5em;
- grid-template-areas: 'banner'
- 'canvas'
- 'editor'
- 'footer';
-}
-
-#banner {
- grid-area: banner;
- background: #F5F5F5;
- height: 20px;
- padding: 10px 20px 35px 20px;
- font-weight: 800;
- display: grid;
- grid-template-columns: 1.5fr 18fr 3.5fr 1fr 1.4fr;
- font-size: 20px;
- font-family: 'Montserrat', 'Open Sans';
-}
-
-#LOGO {
- padding-top: 10px;
- padding-left: 10px;
-}
-
-#banner select {
- margin-right: 10px;
-}
-
-span[tooltip]::before {
- content: '';
- opacity: 0;
-}
-
-span[tooltip]:hover::before {
- content: attr(tooltip);
- opacity: 1;
- position: absolute;
- top: 60px;
- right: 2%;
- background: rgba(0, 0, 0, .7);
- color: #ffffff;
- font-size: 15px;
- padding: 5px 10px;
- border-radius: 10px;
-
- transition: opacity 1ms ease-in-out;
-}
-
-.ico_btn_dark img {
- border-radius: 100px;
- transition: all .2s ease-in;
-}
-
-.ico_btn_dark img:hover {
- background: #00000013;
-}
-
-.ico_btn_dark img:active {
- background: #00000038;
-}
-
-select {
- border: 2px solid #ad9bf8;
- border-radius: 10px;
- outline: none;
-}
-
-a {
- color: #ad9bf8;
- text-decoration: none;
-}
-
-.editor-container {
- grid-area: editor;
- position: relative;
- overflow: hidden;
- margin: 0 10px 0 9px;
- width: 99%;
- width: Calc(100% - 18px);
- min-height: 6em;
- outline: none;
- border-radius: 10px;
- box-shadow: 0px 5px 8px 1px rgba(0, 0, 0, .3);
- background-color:#1f2223;
-}
-
-.textarea-container {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- overflow: hidden;
-}
-
-#code {
- width: Calc(100% - 34px);
- height: Calc(100% - 11px);
- font-size: 20px;
- font-family: 'Courier New', Courier, monospace;
- padding: 5px 18px 0 10px;
- outline: none;
- border-radius: 10px;
- border: 3px solid rgba(83, 81, 81, 0.6);
- white-space: pre-wrap;
- word-wrap: break-word;
- color: #ffffff;
- background: transparent;
-
- transition: border 250ms ease-in;
-}
-
-#code_bg {
- position: absolute;
- top: 3px;
- left: 3px;
- right: 3px;
- bottom: 0;
- overflow: hidden;
- font-family: 'Courier New', Courier, monospace;
- resize: none;
- white-space: pre-wrap;
- word-wrap: break-word;
- font-size: 20px;
- padding: 5px 18px 0 10px;
- color: transparent;
- background-color:#1f2223;
-}
-
-mark {
- color: #ffffff;
- background-color: rgba(255, 0, 0, .6);
- border-radius: 5px;
-}
-
-textarea#code {
- resize: none;
- color: transparent;
- caret-color: #528bff; /* Browser Compatibility Warning */
-}
-
-#code:focus {
- border: 3px solid rgba(11, 44, 230, 0.5);
-}
-
-#logo-canvas {
- grid-area: canvas;
- margin: 0 0.5em;
- cursor: grab;
- overflow: hidden;
- border-radius: 10px;
- box-shadow: 0px 5px 8px 1px rgba(0, 0, 0, .3);
-}
-
-#logo-canvas:active {
- cursor: grabbing;
-}
-
-.copyright-footer {
- grid-area: footer;
- color: white;
- font-family: 'Montserrat';
- padding: 0 0.5em;
-}
-
-.resize-handle {
- position: fixed;
- left: 0;
- right: 0;
- width: 300px;
- height: 16px;
- margin: 0 auto;
- padding: 0;
- background-color: transparent;
- cursor: row-resize;
- z-index: 2;
-}
-
-.resize-handle div {
- position: relative;
- top: 50%;
- width: 90%;
- margin: 0 auto;
- height: 0;
- background-color: rgb(55, 55, 55);
- background-color: #f5f5f5;
- box-shadow: 0px 2px 16px rgba(0, 0, 0, .3);
- transition: all 150ms ease-in-out;
-}
-
-.resize-handle:hover div {
- top: 0;
- height: 16px;
-}
+body {
+ padding: 0;
+ margin: 0;
+ background: rgba(0, 0, 0, 0.7);
+ overflow: hidden;
+ width: Calc(100vw - 1em);
+ height: 100vh;
+ display: grid;
+ grid-gap: 0.5em;
+ grid-template-columns: 100vw;
+ grid-template-rows: 4em 3.5fr auto 1.5em;
+ grid-template-areas: 'banner'
+ 'canvas'
+ 'editor'
+ 'footer';
+}
+
+#banner {
+ grid-area: banner;
+ background: #F5F5F5;
+ height: 20px;
+ padding: 10px 20px 35px 20px;
+ font-weight: 800;
+ display: grid;
+ grid-template-columns: 1.5fr 18fr 3.5fr 1fr 1.4fr;
+ font-size: 20px;
+ font-family: 'Montserrat', 'Open Sans';
+}
+
+#LOGO {
+ padding-top: 10px;
+ padding-left: 10px;
+}
+
+#banner select {
+ margin-right: 10px;
+}
+
+span[tooltip]::before {
+ content: '';
+ opacity: 0;
+}
+
+span[tooltip]:hover::before {
+ content: attr(tooltip);
+ opacity: 1;
+ position: absolute;
+ top: 60px;
+ right: 2%;
+ background: rgba(0, 0, 0, .7);
+ color: #ffffff;
+ font-size: 15px;
+ padding: 5px 10px;
+ border-radius: 10px;
+
+ transition: opacity 1ms ease-in-out;
+}
+
+.ico_btn_dark img {
+ border-radius: 100px;
+ transition: all .2s ease-in;
+}
+
+.ico_btn_dark img:hover {
+ background: #00000013;
+}
+
+.ico_btn_dark img:active {
+ background: #00000038;
+}
+
+select {
+ border: 2px solid #ad9bf8;
+ border-radius: 10px;
+ outline: none;
+}
+
+a {
+ color: #ad9bf8;
+ text-decoration: none;
+}
+
+.editor-container {
+ grid-area: editor;
+ position: relative;
+ overflow: hidden;
+ margin: 0 10px 0 9px;
+ width: 99%;
+ width: Calc(100% - 18px);
+ min-height: 6em;
+ outline: none;
+ border-radius: 10px;
+ box-shadow: 0px 5px 8px 1px rgba(0, 0, 0, .3);
+ background-color:#1f2223;
+}
+
+.textarea-container {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ overflow: hidden;
+}
+
+#code {
+ width: Calc(100% - 34px);
+ height: Calc(100% - 11px);
+ font-size: 20px;
+ font-family: 'Courier New', Courier, monospace;
+ padding: 5px 18px 0 10px;
+ outline: none;
+ border-radius: 10px;
+ border: 3px solid rgba(83, 81, 81, 0.6);
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ color: #ffffff;
+ background: transparent;
+
+ transition: border 250ms ease-in;
+}
+
+#code_bg {
+ position: absolute;
+ top: 3px;
+ left: 3px;
+ right: 3px;
+ bottom: 0;
+ overflow: hidden;
+ font-family: 'Courier New', Courier, monospace;
+ resize: none;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ font-size: 20px;
+ padding: 5px 18px 0 10px;
+ color: transparent;
+ background-color:#1f2223;
+}
+
+mark {
+ color: #ffffff;
+ background-color: rgba(255, 0, 0, .6);
+ border-radius: 5px;
+}
+
+textarea#code {
+ resize: none;
+ color: transparent;
+ caret-color: #528bff; /* Browser Compatibility Warning */
+}
+
+#code:focus {
+ border: 3px solid rgba(11, 44, 230, 0.5);
+}
+
+#logo-canvas {
+ grid-area: canvas;
+ margin: 0 0.5em;
+ cursor: grab;
+ overflow: hidden;
+ border-radius: 10px;
+ box-shadow: 0px 5px 8px 1px rgba(0, 0, 0, .3);
+}
+
+#logo-canvas:active {
+ cursor: grabbing;
+}
+
+.copyright-footer {
+ grid-area: footer;
+ color: white;
+ font-family: 'Montserrat';
+ padding: 0 0.5em;
+}
+
+.resize-handle {
+ position: fixed;
+ left: 0;
+ right: 0;
+ width: 300px;
+ height: 16px;
+ margin: 0 auto;
+ padding: 0;
+ background-color: transparent;
+ cursor: row-resize;
+ z-index: 2;
+}
+
+.resize-handle div {
+ position: relative;
+ top: 50%;
+ width: 90%;
+ margin: 0 auto;
+ height: 0;
+ background-color: rgb(55, 55, 55);
+ background-color: #f5f5f5;
+ box-shadow: 0px 2px 16px rgba(0, 0, 0, .3);
+ transition: all 150ms ease-in-out;
+}
+
+.resize-handle:hover div {
+ top: 0;
+ height: 16px;
+}
diff --git a/turtle.js b/turtle.js
index dfbc84a..d7cd856 100644
--- a/turtle.js
+++ b/turtle.js
@@ -1,44 +1,52 @@
-class Turtle {
- constructor(x, y, angle) {
- this.x = x;
- this.y = y;
- this.homeX = x;
- this.homeY = y;
- this.prevX = x;
- this.prevY = y;
-
- this.dir = angle;
- this.strokeColor = 255;
- this.strokeWeight = 1;
- }
-
- reset() {
- this.pen = true;
- }
-
- forward(amt) {
- // Move the turtle
- this.x += cos(this.dir) * amt;
- this.y += sin(this.dir) * amt;
-
- // If the pen is down we should draw a line from the previous position to the new position
- if (this.pen) {
- stroke(this.strokeColor);
- strokeWeight(this.strokeWeight);
- line(this.prevX, this.prevY, this.x, this.y);
- }
-
- // The current position will become the next previous position
- this.prevX = this.x;
- this.prevY = this.y;
- }
-
- right(angle) {
- this.dir += angle;
- }
-
- home() {
- this.x = this.homeX;
- this.y = this.homeY;
- }
-}
+class Turtle {
+ constructor(x, y, angle) {
+ this.x = x;
+ this.y = y;
+ this.homeX = x;
+ this.homeY = y;
+ this.prevX = x;
+ this.prevY = y;
+
+ this.dir = angle;
+ this.strokeColor = 255;
+ this.strokeWeight = 1;
+ }
+
+ reset() {
+ this.pen = true;
+ }
+
+ forward(amt) {
+ // Move the turtle
+ this.x += cos(this.dir) * amt;
+ this.y += sin(this.dir) * amt;
+
+ // If the pen is down we should draw a line from the previous position to the new position
+ if (this.pen) {
+ stroke(this.strokeColor);
+ strokeWeight(this.strokeWeight);
+ line(this.prevX, this.prevY, this.x, this.y);
+ }
+
+ // The current position will become the next previous position
+ this.prevX = this.x;
+ this.prevY = this.y;
+ }
+
+ right(angle) {
+ this.dir += angle;
+ }
+
+ home() {
+ this.x = this.homeX;
+ this.y = this.homeY;
+ }
+
+ label(displayText) {
+ push();
+ translate(this.x, this.y);
+ rotate(this.dir);
+ text(displayText, 0, 0);
+ pop();
+ }
+}