From 4076440ff3910d0e9895b217376ffd23ae3b6bdf Mon Sep 17 00:00:00 2001 From: Sander Hautvast Date: Fri, 12 Feb 2021 17:42:15 +0100 Subject: [PATCH] supports vectors with functions, methods, properties --- .DS_Store | Bin 0 -> 6148 bytes .gitignore | 2 + README.md | 20 ++++ screenshot-1.png | Bin 0 -> 85956 bytes src/app.css | 81 +++++++++++++ src/console.js | 183 +++++++++++++++++++++++++++++ src/index.html | 20 ++++ src/index.js | 297 +++++++++++++++++++++++++++++++++++++++++++++++ src/parser.js | 185 +++++++++++++++++++++++++++++ src/scanner.js | 181 +++++++++++++++++++++++++++++ 10 files changed, 969 insertions(+) create mode 100644 .DS_Store create mode 100644 .gitignore create mode 100644 README.md create mode 100644 screenshot-1.png create mode 100644 src/app.css create mode 100644 src/console.js create mode 100644 src/index.html create mode 100644 src/index.js create mode 100644 src/parser.js create mode 100644 src/scanner.js diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + +The repl has the following syntax (It's work in progress, new capabilities will be added) +* simple arithmetic expressions: + ** add, subtract, divide, multiply + ** variable declaration eg: a= ... + ** vector(x0,y0,x,y) adds a vector + ** remove(x) removes bindings (when it's an object (eg vector), removes it from the matrix) + ** method calls: + *** a = vector(0,0,12,1) + *** a.type() + *** > vector + ** property lookup + *** a.x + *** 12 + \ No newline at end of file diff --git a/screenshot-1.png b/screenshot-1.png new file mode 100644 index 0000000000000000000000000000000000000000..c101a206aae2d692fee6ccf06e4de4616e32cd75 GIT binary patch literal 85956 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 z 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 literal 0 HcmV?d00001 diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..ac1dbb1 --- /dev/null +++ b/src/app.css @@ -0,0 +1,81 @@ +body { + font: 13px Arial, sans-serif; + background: black; + overflow: hidden; +} + +.background { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; +} + +svg { + position: absolute; + width: 100%; + height: 100%; + z-index: -1; +} + +.grid { + fill: none; + stroke: #4682b4; + stroke-width: 3; +} + +.bg-grid { + fill: none; + stroke: gray; + stroke-width: 0.5; +} + +#console { + padding: 5px; + color: greenyellow; + background: black; + position: absolute; + right: 10px; + bottom: 0; + width: 40%; + height: 10em; + border: 2px solid darkgray; + border-radius: 10px; + z-index: 10; +} + +.axis { + stroke-width: 1.5; + stroke: lightpink; +} + +.vector { + stroke-width: 2.5; + stroke: yellow; + stroke-linecap: round; +} + +#prompt{ + position: absolute; + bottom: 0; +} + +#command_input{ + border: none transparent; + background: black; + color: greenyellow; + outline: none; + width: 39vw; +} + +#command_history{ + font-size: 12px; + color: greenyellow; + position: absolute; + bottom: 1.5em; + max-height: 9em; + width: 100%; + overflow-y: visible; + overflow-x: hidden; +} \ No newline at end of file diff --git a/src/console.js b/src/console.js new file mode 100644 index 0000000..1b38c84 --- /dev/null +++ b/src/console.js @@ -0,0 +1,183 @@ +/** + * handles user input from the console div + */ +(function () { + const state = {}; + const command_input_element = document.getElementById('command_input'); + const command_history_element = document.getElementById('command_history'); + const bottom = document.getElementById('bottom'); + command_input_element.value = ''; + let command_history = []; + let command_history_index = 0; + + command_input_element.onkeyup = function handle_key_input(event) { + if (event.key === 'ArrowUp') { + if (command_history_index > -1) { + command_input_element.value = command_history[command_history_index]; + if (command_history_index > 0) { + command_history_index -= 1; + } + } + } + if (event.key === 'ArrowDown') { + if (command_history_index < command_history.length - 1) { + command_history_index += 1; + command_input_element.value = command_history[command_history_index]; + } else { + command_input_element.value = ''; + } + } + if (event.key === 'Enter') { + let command = command_input_element.value; + command_history_element.innerText += command + "\n"; + command_input_element.value = ''; + command_history_index = command_history.length; + let tokens = scan(command); + let statement = parse(tokens); + let result; + try { + result = visit_expression(statement); + if (result.description) { + result = result.description; + } + } catch (e) { + result = e.message; + } + command_history_element.innerText += result + "\n"; + command_history.push(command); + command_history_element.scrollTo(0, command_history_element.scrollHeight); + } + }; + + let visit_expression = function (expr) { + switch (expr.type) { + case 'declaration': + let value = visit_expression(expr.initializer); + let existing_value = state[expr.var_name.value]; + if (existing_value) { + if (existing_value.type === 'vector') { + remove_vector(existing_value.object); // remove from screen + } + } + value.binding = expr.var_name.value; + state[expr.var_name.value] = value; + let description = state[expr.var_name.value].description; + if (!description) { + description = state[expr.var_name.value]; //questionable. use toString instead of message? + } + return {description: expr.var_name.value + ':' + description}; + case 'group': + return visit_expression(expr.expression); + case 'unary': + let right_operand = visit_expression(expr.right); + if (expr.operator === token_types.MINUS) { + return -right_operand; + } else if (expr.operator === token_types.NOT) { + return !right_operand; + } else { + throw {message: 'illegal unary operator'}; + } + case 'binary': + let left = visit_expression(expr.left); + let right = visit_expression(expr.right); + switch (expr.operator) { + case token_types.MINUS: + return left - right; + case token_types.PLUS: + return addition(left, right); + case token_types.STAR: + return multiplication(left, right); + case token_types.SLASH: + return left / right; + case token_types.DOT: + return method_call(left, expr.right); + } + throw {message: 'illegal binary operator'} + case 'identifier': { + if (state[expr.name]) { + return state[expr.name]; + } else { + break; + } + } + case 'literal': + return expr.value; + case 'call': + return call(expr.name, expr.arguments); + } + } + + const call = function (function_name, argument_exprs) { + let arguments = []; + for (let i = 0; i < argument_exprs.length; i++) { + arguments.push(visit_expression(argument_exprs[i])); + } + if (functions[function_name]) { + return functions[function_name](arguments); + } else { + let arg_list = ''; + for (let i = 0; i < argument_exprs.length; i++) { + if (i > 0) { + arg_list += ','; + } + arg_list += argument_exprs[i].value_type; + } + return 'unimplemented: ' + function_name + '(' + arg_list + ')'; + } + } + + const method_call = function (object_wrapper, method_or_property) { + if (object_wrapper) { + if (method_or_property.type === 'call') { // method + if (typeof object_wrapper.object[method_or_property.name] !== 'function') { + throw {message: `method ${method_or_property.name} not found on ${object_wrapper.type}`}; + } + return object_wrapper.object[method_or_property.name].apply(object_wrapper, method_or_property.arguments); + + } else { // property + if (!object_wrapper.object.hasOwnProperty(method_or_property.name)){ + throw {message: `property ${method_or_property.name} not found on ${object_wrapper.type}`}; + } + return object_wrapper.object[method_or_property.name]; + } + } else { + throw {message: `not found: ${object_wrapper}`}; + } + } + + const functions = { + help: () => help(), + vector: (args) => add_vector({x0: args[0], y0: args[1], x: args[2], y: args[3]}), + remove: (args) => { + if (args[0].hasOwnProperty('binding')) { + delete this.state[args[0].binding]; + return remove_vector(args[0].object); // by binding value + } else { + return remove_vector(args[0]); // by index (@...) + } + + }, + } + + const help = function () { + return {message: 'vector(x0, y0, x, y): draws a vector from x0,y0 to x,y'} + } + + const multiplication = function (left, right) { + if (left.object && left.type === 'vector' && !right.object) { + return left.object.multiply(right); + } + if (right.object && right.type === 'vector' && !left.object) { + return right.object.multiply(left); + } + return left * right; + } + + const addition = function (left, right) { + if (left.object && left.type === 'vector' && right.object && right.type === 'vector') { + return left.object.add(right.object); + } + return left + right; + } + } +)(); \ No newline at end of file diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..cfbe7ce --- /dev/null +++ b/src/index.html @@ -0,0 +1,20 @@ + + + + + Interactive Linear Algebra + + + +
+
+ +
+ + + + + + \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..c415c77 --- /dev/null +++ b/src/index.js @@ -0,0 +1,297 @@ +let add_vector, + remove_vector; + +/** + * Main entry. draws the matrix + */ +(function () { + const SVG_NS = 'http://www.w3.org/2000/svg'; // program needs these to create svg elements + let grid_size = 100; // this is the nr of pixels for the basis vector (1,0) (0,1) + let half_grid_size = grid_size >> 1; // used to position the grid lines + let vectors = []; // collection of added vectors + let moving_vector; // user can move vector arrows. when moving, this refers to the arrow + let width = window.innerWidth, height = window.innerHeight; + let origin_x = Math.floor((width / grid_size) / 2) * grid_size + half_grid_size, + origin_y = Math.floor((height / grid_size) / 2) * grid_size + half_grid_size; + /** + * Creates an svg element + * @param element_type path,g, etc + * @returns SVG element + */ + const create = function (element_type) { + return document.createElementNS(SVG_NS, element_type); + } + + /** + * creates the d attribute string + * @param x0 start_x + * @param y0 start_y + * @param x1 end_x + * @param y1 end y + * @returns {string} to put in an SVG path + */ + const calculate_d = function (x0, y0, x1, y1) { + return "M" + x0 + " " + y0 + " L" + x1 + " " + y1; + } + + /** + * creates a SVG line (path) + * @param x0 start_x + * @param y0 start_y + * @param x1 end_x + * @param y1 end_y + * @param css_class the css class to make up the element + * @returns an SVG path element + */ + const create_line = function (x0, y0, x1, y1, css_class) { + let path = create('path'); + path.setAttribute('d', calculate_d(x0, y0, x1, y1)); + path.setAttribute('class', css_class); + return path; + } + + /** + * creates the arrow path element + * @param id attribute + * @param x0 start_x + * @param y0 start_y + * @param x1 end_x + * @param y1 end_y + * @param css_class class attribute + * @returns {SVGPathElement} + */ + const arrow = function (id, x0, y0, x1, y1, css_class) { + let path = create('path'); + + path.setAttribute('d', calculate_d(x0, y0, x1, y1)); + path.id = id; + path.setAttribute('class', css_class); + path.setAttribute('marker-end', 'url(#arrow)'); + return path; + } + + /** + * Draws the background grid of the space + * @param css_class class for the lines that are 'multiples of the basis vector' + * @param bg_css_class class for in between lines + * @returns {SVGGElement} + */ + const create_grid = function (css_class, bg_css_class) { + const group = create('g'); + group.setAttribute('id', 'grid'); + const horizontal = create('g'); + horizontal.setAttribute('id', 'horizontal'); + for (let y = 0; y < height; y += grid_size) { + horizontal.appendChild(create_line(0, y + half_grid_size, width, y + half_grid_size, css_class)); + horizontal.appendChild(create_line(0, y, width, y, bg_css_class)); + } + group.appendChild(horizontal); + const vertical = create('g'); + vertical.setAttribute('id', 'vertical'); + for (let x = 0; x < width; x += grid_size) { + vertical.appendChild(create_line(x + half_grid_size, 0, x + half_grid_size, height, css_class)); + vertical.appendChild(create_line(x, 0, x, height, bg_css_class)); + } + group.appendChild(vertical); + return group; + } + + /** + * removes child from element by id if found + * @param element + * @param child_id id to remove + */ + const remove_child = function (element, child_id) { + let node = element.firstChild; + while (node && child_id !== node.id) { + node = node.nextSibling; + } + if (node) { + element.removeChild(node); + } + } + + /** + * removes the grid from the DOM and adds an updated one. + */ + const redraw_grid = function () { + remove_child(svg, "grid"); + svg.appendChild(create_grid('grid', 'bg-grid')); + svg.appendChild(create_axes()); + } + + /** + * Adds a vector to the set. + * @param vector + */ + add_vector = function (vector) { + vector.id = vectors.length; + vectors.push(vector); + redraw(); + vector.add = (other) => add_vector({ + x0: vector.x0 + other.x0, + y0: vector.x0 + other.x0, + x: vector.x + other.x, + y: vector.y + other.y + }); + vector.multiply = (scalar) => add_vector({ + x0: vector.x0 * scalar, + y0: vector.y0 * scalar, + x: vector.x * scalar, + y: vector.y * scalar + }); + vector.is_vector = true; + vector.type = () => 'vector'; + return { //object_wrapper + type: 'vector', + object: vector, + description: `vector@${vector.id}{x0:${vector.x0},y0:${vector.y0} x:${vector.x},y:${vector.y}}`, + }; + + } + + remove_vector = function (vector_or_index) { + let index; + if (vector_or_index.is_vector) { + for (let i = 0; i < vectors.length; i++) { + if (vectors[i].id === vector_or_index.id) { + index = i; + break; + } + } + } else { + index = vector_or_index; + } + + if (!vectors[index]) { + throw {message: `vector@${index} not found`}; + } + + vectors.splice(index, 1); + redraw(); + return {description: `vector@${index} removed`}; + } + + /** + * The moving operation. Called by onmousemove on the svg ('canvas') + * @param event + */ + const move = function (event) { + if (moving_vector) { + let current_x = event.clientX; + let current_y = event.clientY; + vectors[moving_vector.id].x = (current_x - origin_x) / grid_size; + vectors[moving_vector.id].y = (origin_y - current_y) / grid_size; + moving_vector.setAttribute('d', calculate_d(origin_x, origin_y, current_x, current_y)); + } + } + + /** + * Draws all the vectors. + * + * vector { + * x0,y0 origin + * x,y coordinates + * } + */ + const draw_vectors = function () { + const vector_group = create("g"); + vector_group.id = 'vectors'; + + for (let i = 0; i < vectors.length; i++) { + let vector_arrow = arrow(vectors[i].id, + origin_x + vectors[i].x0 * grid_size, + origin_y - vectors[i].y0 * grid_size, + origin_x + vectors[i].x * grid_size, + origin_y - vectors[i].y * grid_size, + 'vector'); + vector_arrow.onmousedown = function start_moving_vector(event) { + moving_vector = event.target; + }; + vector_group.appendChild(vector_arrow); + } + svg.appendChild(vector_group); + } + + /** + * Removes all vectors in the svg and calls draw_vectors to draw updated versions. + */ + const redraw_vectors = function () { + remove_child(svg, 'vectors'); + draw_vectors(); + } + + /** + * (re)draws all + */ + const redraw = function () { + redraw_grid(); + redraw_vectors(); + } + + const create_axes = function () { + let axes_group = create('g'); + let x = create_line(0, origin_y, width, origin_y, 'axis'); + x.id = 'x-axis'; + axes_group.appendChild(x); + let y = create_line(origin_x, 0, origin_x, height, 'axis'); + y.id = 'y-axis'; + axes_group.appendChild(y); + return axes_group; + } + + /** + * setup the arrow head for the vector + * @returns {SVGDefsElement} + */ + function create_defs() { + let defs = create('defs'); + let marker = create('marker'); + marker.id = 'arrow'; + marker.setAttribute('orient', 'auto'); + marker.setAttribute('viewBox', '0 0 10 10'); + marker.setAttribute('markerWidth', '3'); + marker.setAttribute('markerHeight', '4'); + marker.setAttribute('markerUnits', 'strokeWidth'); + marker.setAttribute('refX', '6'); + marker.setAttribute('refY', '5'); + let polyline = create('polyline'); + polyline.setAttribute('points', '0,0 10,5 0,10 1,5'); + polyline.setAttribute('fill', 'yellow'); + marker.appendChild(polyline); + defs.appendChild(marker); + return defs; + } + + /** + * Creates the SVG + * @returns {SVGElement} + */ + const create_svg = function () { + let svg = create('svg'); + + svg.onmousemove = move; + svg.onmouseup = function stop_moving_vector() { + moving_vector = undefined; + }; + + let defs = create_defs(); + svg.appendChild(defs); + return svg; + } + + document.body.onresize = function recalculate_window_dimensions() { + width = window.innerWidth; + height = window.innerHeight; + origin_x = Math.floor((width / grid_size) / 2) * grid_size + half_grid_size; + origin_y = Math.floor((height / grid_size) / 2) * grid_size + half_grid_size; + redraw(); + } + + const svg = create_svg(); + document.body.appendChild(svg); + + svg.appendChild(create_grid('grid', 'bg-grid')); + svg.appendChild(create_axes()); +}) +(); \ No newline at end of file diff --git a/src/parser.js b/src/parser.js new file mode 100644 index 0000000..a3f8d74 --- /dev/null +++ b/src/parser.js @@ -0,0 +1,185 @@ +const parse = function (tokens) { + let token_index = 0; + + return statement(); + + function statement() { + if (check(token_types.IDENTIFIER, token_index) && check(token_types.EQUALS, token_index + 1)) { + let var_name = current_token(); + advance(); + advance(); + return {type: 'declaration', var_name: var_name, initializer: expression()}; + } else { + return expression(); + } + } + + function expression() { + return equality(); + } + + function equality() { + let expr = comparison() + + while (match([token_types.EQUALS_EQUALS, token_types.NOT_EQUALS])) { + let operator = previous_token(); + let right = unary(); + expr = {type: 'binary', left: expr, operator: operator, right: right}; + } + + return expr; + } + + function comparison() { + let expr = addition(); + + while (match([token_types.LESS, token_types.LESS_OR_EQUAL, token_types.GREATER, token_types.GREATER_OR_EQUAL])) { + let operator = previous_token(); + let right = addition(); + expr = {type: 'binary', left: expr, operator: operator, right: right}; + } + + return expr; + } + + function addition() { + let expr = multiplication(); + + while (match([token_types.MINUS, token_types.PLUS])) { + let operator = previous_token(); + let right = multiplication(); + expr = {type: 'binary', left: expr, operator: operator, right: right}; + } + + return expr; + } + + function multiplication() { + let expr = unary(); + + while (match([token_types.SLASH, token_types.STAR, token_types.DOT])) { + let operator = previous_token(); + let right = unary(); + expr = {type: 'binary', left: expr, operator: operator, right: right}; + } + + return expr; + } + + function unary() { + if (match([token_types.NOT, token_types.MINUS])) { + let operator = previous_token(); + let right = unary(); + return {type: 'unary', operator: operator, right: right}; + } else { + return call(); + } + } + + function call() { + let expr = primary(); + + while (true) { + if (match([token_types.LEFT_PAREN])) { + expr = finish_call(expr.name); + } else { + break; + } + } + + return expr; + } + + function finish_call(callee) { + let arguments = []; + if (!check(token_types.RIGHT_PAREN, token_index)) { + do { + arguments.push(expression()); + } while (match([token_types.COMMA])); + } + if (!match([token_types.RIGHT_PAREN])) { + throw {message: "Expect ')' after arguments."}; + } + + return {type: 'call', name: callee, arguments: arguments}; + } + + + function primary() { + if (match([token_types.NUMERIC, token_types.STRING])) { + return {type: 'literal', value: previous_token().value, value_type: previous_token().type}; + } else if (match([token_types.LEFT_PAREN])) { + let expr = expression(); + if (expr && match([token_types.RIGHT_PAREN])) { + return {type: 'group', expression: expr}; + } else { + throw {message: 'expected expression or )'}; + } + } else if (check(token_types.IDENTIFIER, token_index)) { + let identifier = {type: 'identifier', name: current_token().value}; + advance(); + return identifier; + } + } + + /** + * matches token against array of tokens to check for equality (matching type) + * @param tokens_to_match array of tokens + * @returns {boolean} + */ + function match(tokens_to_match) { + for (let i = 0; i < tokens_to_match.length; i++) { + if (are_same(tokens_to_match[i], current_token())) { + advance() + return true; + } + } + return false; + } + + /** + * Checks if token at position index matches the given + * @param token_to_check expected token type + * @param index of token to check + * @returns {boolean} + */ + function check(token_to_check, index) { + let token = tokens[index]; + if (!token) { + return false; + } + return are_same(token_to_check, token); + + } + + /** + * checks if 2 tokens have same type + * @param token_1 + * @param token_2 + * @returns {boolean} + */ + function are_same(token_1, token_2) { + if (is_at_end()) { + return false; + } else { + return token_1.type === token_2.type; + } + + } + + function is_at_end() { + return token_index >= tokens.length; + } + + function advance() { + token_index += 1; + } + + function previous_token() { + return tokens[token_index - 1]; + } + + function current_token() { + return tokens[token_index]; + } +} \ No newline at end of file diff --git a/src/scanner.js b/src/scanner.js new file mode 100644 index 0000000..92be530 --- /dev/null +++ b/src/scanner.js @@ -0,0 +1,181 @@ +/** + * Creates an array of tokens from a line of input. + * + * @param command: string + * @returns {token_type[]} + */ +const scan = function(command) { + let current_index = 0, // current index of char to look at in the command string + word_start_index = 0, // marker for start of a literal or identifier + tokens = []; + + while (!is_at_end()) { + word_start_index = current_index; + let token = scan_token(); + if (token) { // undefined mostly means whitespace + tokens.push(token); + } + } + return tokens; + + function scan_token() { + let next_char = advance(); + switch (next_char) { + case '(': + return token_types.LEFT_PAREN; + case ')': + return token_types.RIGHT_PAREN; + case '[': + return token_types.LEFT_BRACKET; + case ']': + return token_types.RIGHT_BRACKET; + case ',': + return token_types.COMMA; + case '.': + return token_types.DOT; + case '-': + return token_types.MINUS; + case '+': + return token_types.PLUS; + case '*': + return token_types.STAR; + case '/': + return token_types.SLASH; + case '>': + if (expect('=')) { + return token_types.GREATER_OR_EQUAL; + } else { + return token_types.GREATER; + } + case '<': + if (expect('=')) { + return token_types.LESS_OR_EQUAL; + } else { + return token_types.LESS; + } + case '!': + if (expect('=')) { + return token_types.NOT_EQUALS; + } else { + return token_types.NOT; + } + case '=': + if (expect('=')) { + return token_types.EQUALS_EQUALS; + } else { + return token_types.EQUALS; + } + case '\'': + return string('\''); + case '\"': + return string('\"'); + } + if (is_digit(next_char)) { + let token = Object.assign({}, token_types.NUMERIC); + token.value = parse_number(); + return token; + } else { + if (is_alpha_or_underscore(next_char)) { + let token = Object.assign({}, token_types.IDENTIFIER); + token.value = parse_identifier(); + return token; + } + } + } + + function expect(expected_char) { + if (is_at_end()) { + return false; + } + if (current_char() === expected_char) { + advance(); + return true; + } else { + return false; + } + } + + function advance() { + if (current_index < command.length) { + current_index += 1; + } + return command[current_index - 1]; + } + + function is_at_end() { + return current_index >= command.length; + } + + function current_char() { + return command[current_index]; + } + + function is_digit(char) { + return char >= '0' && char <= '9'; + } + + function is_part_of_number(char) { + return is_digit(char) || char === '.'; // no scientific notation for now + } + + function parse_number() { + while (is_part_of_number(current_char())) { + advance(); + } + let number_string = command.substring(word_start_index, current_index); + return Number.parseFloat(number_string); + } + + function is_alpha_or_underscore(char) { + return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || char === '_'; + } + + function is_alphanumeric_or_underscore(char) { + return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || is_digit(char) || char === '_'; + } + + function parse_identifier() { + while (is_alphanumeric_or_underscore(current_char())) { + advance(); + } + return command.substring(word_start_index, current_index); + } + + function string(quote) { // as of yet strings may not unclude escaped quotes that are also the start/end quote + while (current_char() !== quote && !is_at_end()) { + advance(); + } + if (is_at_end() && current_char() !== quote) { + throw {message: 'unterminated string'} + } else { + let string_token = Object.assign({}, token_types.STRING); + string_token.value = command.substring(word_start_index + 1, current_index); + advance(); + return string_token; + } + } +}; + +const token_types = { + LEFT_PAREN: {type: 'left_paren'}, + RIGHT_PAREN: {type: 'right_paren'}, + LEFT_BRACKET: {type: 'left_bracket'}, + RIGHT_BRACKET: {type: 'right_bracket'}, + COMMA: {type: 'comma'}, + DOT: {type: 'dot'}, + MINUS: {type: 'minus'}, + PLUS: {type: 'plus'}, + STAR: {type: 'star'}, + SLASH: {type: 'slash'}, + EQUALS: {type: 'equals'}, + EQUALS_EQUALS: {type: 'equals_equals'}, + NOT_EQUALS: {type: 'not_equals'}, + NOT: {type: 'not'}, + GREATER: {type: 'greater'}, + GREATER_OR_EQUAL: {type: 'greater_or_equal'}, + LESS: {type: 'less'}, + LESS_OR_EQUAL: {type: 'less_or_equal'}, + NUMERIC: {type: 'number', value: undefined}, + IDENTIFIER: {type: 'identifier', value: undefined}, + STRING: {type: 'string', value: undefined} +};