fixed bugs and added functions

This commit is contained in:
Sander Hautvast 2021-03-02 14:31:49 +01:00
parent ea36a7c5a3
commit 26d4749995
4 changed files with 235 additions and 151 deletions

View file

@ -1,57 +1,49 @@
import {scan, token_types} from './scanner'; import {scan, token_types} from './scanner';
import {parse} from './parser'; import {parse} from './parser';
import {add_vector_arrow_to_svg, remove_child, update_vector_arrow} from "./svg_functions"; import {
add_vector_arrow,
add_vector_arrow_to_svg,
remove_vector_arrow,
update_label_text,
update_vector_arrow
} from "./svg_functions";
export let vectors = []; // collection of added vectors // maybe move to console.js const state = {}; // binding -> value
const state = {}; const bindings = {}; // binding -> {name:binding_name, evaluated: evaluated_lazy_value, previous: previous_value}
const references = {};
const command_input_element = document.getElementById('command_input'); const command_input_element = document.getElementById('command_input');
const command_history_element = document.getElementById('command_history'); const command_history_element = document.getElementById('command_history');
command_input_element.value = ''; command_input_element.value = '';
let command_history = ['']; let command_history = [''];
let command_history_index = 0; let command_history_index = 0;
let vectors_index_sequence = 0; let index_sequence = 0;
export const remove_vector = function (vector_or_index) { const hide = function (vector) {
let index; remove_vector_arrow(vector);
if (vector_or_index.is_vector) { return {description: `vector@${vector.id} is hidden`};
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]) { const label = function (vector, text) {
throw {message: `vector@${index} not found`}; vector.label_text = text;
} update_label_text(vector.id, text);
return `label set to ${text} on vector@${vector.id}`;
}
vectors.splice(index, 1); const show = function (vector) {
remove_child(document.getElementById('vectors'), index.toString()); add_vector_arrow_to_svg(vector);
return {description: `vector@${index} removed`}; return {description: `vector@${vector.id} is visible`};
} }
export const update_lazy_objects = function () { export const update_lazy_objects = function () {
let lazy_objects = Object.values(state).filter(e => Object.prototype.hasOwnProperty.apply(e, ['lazy_expression'])); Object.values(bindings).forEach(binding => {
lazy_objects.forEach(object => { if (state[binding.name].lazy_expression) {
let value = visit_expression(object.lazy_expression); let value = visit(state[binding.name].lazy_expression);
let existing_value = state[object.binding]; let existing_value = bindings[binding.name].evaluated;
if (existing_value) { if (existing_value) {
update_vector_arrow(existing_value.id, value); update_vector_arrow(existing_value.id, value);
bindings[binding.name].evaluated = value;
} }
state[object.binding].x0 = value.x0;
state[object.binding].y0 = value.y0;
state[object.binding].x = value.x;
state[object.binding].y = value.y;
state[object.binding].id = value.id;
let description = state[object.binding].description;
if (!description) {
description = state[object.binding];
} }
return {description: object.binding + ':' + description};
}); });
} }
@ -103,17 +95,27 @@ const handle_enter = function () {
let statement = parse(tokens); let statement = parse(tokens);
let value; let value;
try { try {
value = visit_expression(statement); value = visit(statement);
let binding;
if (value.is_binding) { // if it's declaration work with the initializer
binding = value.name; // but we also need the name of the bound variable
value = state[binding];
}
while (value.lazy_expression) {
value = value.get();
}
if (binding) {
bindings[binding].evaluated = value; // store evaluation result
}
if (value.is_visual) { if (value.is_visual) {
value.label = value.binding;
if (value.is_vector) { if (value.is_vector) {
if (value.previous && value.previous.is_visual) { if (binding && bindings[binding].previous && bindings[binding].previous.is_visual) {
update_vector_arrow(value.previous.id, value); update_vector_arrow(value.previous.id, value);
} else { } else {
if (value.is_new) { if (value.is_new) {
value.label_text = binding ? binding : "";
value.is_new = false; value.is_new = false;
vectors.push(value); add_vector_arrow(value);
add_vector_arrow_to_svg(value);
} }
} }
} }
@ -124,43 +126,36 @@ const handle_enter = function () {
} catch (e) { } catch (e) {
value = e.message; value = e.message;
} }
command_history_element.innerText += value + "\n"; command_history_element.innerText += value.toString() + "\n";
command_history.push(command); command_history.push(command);
command_history_element.scrollTo(0, command_history_element.scrollHeight); command_history_element.scrollTo(0, command_history_element.scrollHeight);
} }
} }
} }
const visit_expression = function (expr) { const visit = function (expr) {
switch (expr.type) { switch (expr.type) {
case 'declaration': { case 'declaration': {
let value = visit_expression(expr.initializer); let value = visit(expr.initializer);
if (!value.is_visual) { // if it's a primitive value, let binding_name = expr.var_name.value;
value = { // wrap it into a object that returns the value if (bindings[binding_name]) { // do reassignment
_value: value, // references the original value bindings[binding_name].previous = state[binding_name]; // remember previous value, to remove it from the visualisation
toString: function () { } else {
return this._value; bindings[binding_name] = {
}, is_binding: true,
get: function () { name: binding_name,
return this._value; previous: null,
}, evaluated: null
is_visual: false
}; };
} }
value.binding = expr.var_name.value; // store the variable name with it, to handle reassignment. state[binding_name] = value; // assign new value to binding
if (state[value.binding]) { // do reassignment
value.previous = state[value.binding]; // remember previous value, to remove it from the visualisation
}
state[value.binding] = value; // assign new value to binding
update_lazy_objects(); // reevaluate any lazy expressions return bindings[binding_name];
return value;
} }
case 'group': // expression within parentheses case 'group': // expression within parentheses
return visit_expression(expr.expression); return visit(expr.expression);
case 'unary': { case 'unary': {
let right_operand = visit_expression(expr.right); let right_operand = visit(expr.right);
if (expr.operator === token_types.MINUS) { if (expr.operator === token_types.MINUS) {
return -right_operand; //TODO create negate function (because now it only works for numbers) return -right_operand; //TODO create negate function (because now it only works for numbers)
} else if (expr.operator === token_types.NOT) { } else if (expr.operator === token_types.NOT) {
@ -170,50 +165,63 @@ const visit_expression = function (expr) {
} }
} }
case 'binary': { case 'binary': {
let left = visit_expression(expr.left);
let right = visit_expression(expr.right);
switch (expr.operator) { switch (expr.operator) {
case token_types.MINUS: case token_types.MINUS:
return subtract(left, right); return subtract(visit(expr.left), visit(expr.right));
case token_types.PLUS: case token_types.PLUS:
return addition(left, right); return addition(visit(expr.left), visit(expr.right));
case token_types.STAR: case token_types.STAR:
return multiplication(left, right); return multiplication(visit(expr.left), visit(expr.right));
case token_types.SLASH: case token_types.SLASH:
return left / right; return visit(expr.left) / visit(expr.right);
case token_types.DOT: case token_types.DOT:
return method_call(left, expr.right); return method_call(visit(expr.left), expr.right); // right is not evaluated. It's the method name
// could also be evaluated to itself, BUT it's of type call which would invoke a function (see below)
} }
throw {message: 'illegal binary operator'}; throw {message: 'illegal binary operator'};
} }
case 'identifier': { case 'identifier': {
if (state[expr.name]) { if (state[expr.name]) {
let object = state[expr.name]; let value = state[expr.name];
let get = object.get(); while (value.lazy_expression) {
return get; value = value.get();
}
return value;
} else { } else {
break; break;
} }
} }
case 'literal': case 'literal': {
return expr.value; let value = expr.value;
while (value.lazy_expression) {
value = value.get();
}
return value;
}
case 'call': case 'call':
return function_call(expr.name, expr.arguments); return function_call(expr.name, expr.arguments);
case 'lazy': { case 'lazy': {
let r = visit_expression(expr.value); let expression = expr.expression;
r.lazy_expression = expr.value; let parsed_expression = expr.value;
return r; return {
lazy_expression: parsed_expression,
toString: function () {
return `"${expression}"`;
},
get: function () {
return visit(parsed_expression);
}
};
}
case 'reference': {
return references[expr.name];
} }
} }
} }
const function_call = function (function_name, argument_exprs) { const function_call = function (function_name, argument_exprs) {
let arguments_list = [];
for (let i = 0; i < argument_exprs.length; i++) {
arguments_list.push(visit_expression(argument_exprs[i]));
}
if (Object.prototype.hasOwnProperty.apply(functions, [function_name])) { if (Object.prototype.hasOwnProperty.apply(functions, [function_name])) {
return functions[function_name](arguments_list); return functions[function_name](resolve_arguments(argument_exprs));
} else { } else {
let arg_list = ''; let arg_list = '';
for (let i = 0; i < argument_exprs.length; i++) { for (let i = 0; i < argument_exprs.length; i++) {
@ -226,23 +234,23 @@ const function_call = function (function_name, argument_exprs) {
} }
} }
const method_call = function (object_wrapper, method_or_property) { const method_call = function (object, method_or_property) {
if (object_wrapper) { if (object) {
if (method_or_property.type === 'call') { // method if (method_or_property.type === 'call') { // method
if (typeof object_wrapper[method_or_property.name] !== 'function') { if (typeof object[method_or_property.name] !== 'function') {
throw {message: `method ${method_or_property.name} not found on ${object_wrapper.type}`}; throw {message: `method '${method_or_property.name}' not found on ${object.type}`};
} }
return object_wrapper[method_or_property.name].apply(object_wrapper, method_or_property.arguments); return object[method_or_property.name].apply(object, resolve_arguments(method_or_property.arguments));
} else { // property } else { // property
if (!Object.prototype.hasOwnProperty.call(object_wrapper, [method_or_property.name])) { if (!Object.prototype.hasOwnProperty.call(object, [method_or_property.name])) {
throw {message: `property ${method_or_property.name} not found on ${object_wrapper.type}`}; throw {message: `property '${method_or_property.name}' not found on ${object.type}`};
} }
return object_wrapper[method_or_property.name]; return object[method_or_property.name];
} }
} else { } else {
throw {message: `not found: ${object_wrapper}`}; throw {message: `not found: ${object}`};
} }
} }
@ -255,12 +263,15 @@ const functions = {
return create_vector({x0: args[0], y0: args[1], x: args[2], y: args[3]}); return create_vector({x0: args[0], y0: args[1], x: args[2], y: args[3]});
} }
}, },
remove: (args) => { hide: (args) => {
if (Object.prototype.hasOwnProperty.call(args[0], ['binding'])) { return hide(args[0]);
delete state[args[0].binding];
}
return remove_vector(args[0]);
}, },
label: (args) =>{
return label(args[0], args[1]);
},
show: (args) =>{
return show(args[0]);
}
} }
const help = function () { const help = function () {
@ -317,17 +328,31 @@ const subtract = function (left, right) {
} }
export const create_vector = function (vector) { //rename to create_vector export const create_vector = function (vector) { //rename to create_vector
vector.id = vectors_index_sequence++; vector.id = index_sequence++;
vector.is_visual = true; vector.is_visual = true;
vector.is_vector = true; vector.is_vector = true; // for comparison
vector.type = 'vector'; // for showing type to user
vector.is_new = true; vector.is_new = true;
vector.toString = function () { vector.toString = function () {
return `vector@${this.id}{x0:${vector.x0},y0:${vector.y0} x:${vector.x},y:${vector.y}}`; return `vector@${this.id}{x0:${vector.x0},y0:${vector.y0} x:${vector.x},y:${vector.y}}`;
}; };
vector.get = function () { references["@" + vector.id] = vector;
return vector; vector.hide = function () {
} return hide(this);
};
vector.label = function (text) {
return label(this, text);
};
vector.show = function () {
return show(this);
};
return vector; return vector;
} }
const resolve_arguments = function(argument_exprs) {
let arguments_list = [];
for (let i = 0; i < argument_exprs.length; i++) {
arguments_list.push(visit(argument_exprs[i]));
}
return arguments_list;
}

View file

@ -1,5 +1,4 @@
import {token_types} from './scanner'; import {scan, token_types} from './scanner';
import {scan} from './scanner';
export const parse = function (tokens) { export const parse = function (tokens) {
let token_index = 0; let token_index = 0;
@ -82,7 +81,7 @@ export const parse = function (tokens) {
function call() { function call() {
let expr = primary(); let expr = primary();
for (;;){ for (; ;) {
if (match([token_types.LEFT_PAREN])) { if (match([token_types.LEFT_PAREN])) {
expr = finish_call(expr.name); expr = finish_call(expr.name);
} else { } else {
@ -112,20 +111,38 @@ export const parse = function (tokens) {
if (match([token_types.NUMERIC, token_types.STRING])) { if (match([token_types.NUMERIC, token_types.STRING])) {
return {type: 'literal', value: previous_token().value, value_type: previous_token().type}; return {type: 'literal', value: previous_token().value, value_type: previous_token().type};
} else if (match([token_types.LAZY])) { } else if (match([token_types.LAZY])) {
let tokens = scan(previous_token().expression); let expression = previous_token().expression;
let expression = parse(tokens); let tokens = scan(expression);
return {type: 'lazy', value: expression}; let parsed_expression = parse(tokens);
return {
type: 'lazy',
expression: expression,
value: parsed_expression
};
} else if (match([token_types.LEFT_PAREN])) { } else if (match([token_types.LEFT_PAREN])) {
let expr = expression(); let expr = expression();
if (expr && match([token_types.RIGHT_PAREN])) { if (expr && match([token_types.RIGHT_PAREN])) {
return {type: 'group', expression: expr}; return {
type: 'group',
expression: expr
};
} else { } else {
throw {message: 'expected expression or )'}; throw {message: 'expected expression or )'};
} }
} else if (check(token_types.IDENTIFIER, token_index)) { } else if (check(token_types.IDENTIFIER, token_index)) {
let identifier = {type: 'identifier', name: current_token().value}; let identifier = {
type: 'identifier',
name: current_token().value
};
advance(); advance();
return identifier; return identifier;
} else if (check(token_types.AT, token_index)) {
let result = {
type: 'reference',
name: current_token().value
};
advance();
return result;
} }
} }

View file

@ -69,6 +69,11 @@ export const scan = function (command) {
return string(); return string();
case '"': case '"':
return lazy_expression(); return lazy_expression();
case '@': {
let reference = Object.assign({}, token_types.AT);
reference.value = parse_reference();
return reference;
}
} }
if (is_digit(next_char)) { if (is_digit(next_char)) {
let token = Object.assign({}, token_types.NUMERIC); let token = Object.assign({}, token_types.NUMERIC);
@ -118,6 +123,13 @@ export const scan = function (command) {
return is_digit(char) || char === '.'; // no scientific notation for now return is_digit(char) || char === '.'; // no scientific notation for now
} }
function parse_reference() {
while (current_char() === '@' || is_digit(current_char())) {
advance();
}
return command.substring(word_start_index, current_index);
}
function parse_number() { function parse_number() {
while (is_part_of_number(current_char())) { while (is_part_of_number(current_char())) {
advance(); advance();
@ -192,5 +204,6 @@ export const token_types = {
NUMERIC: {type: 'number', value: undefined}, NUMERIC: {type: 'number', value: undefined},
IDENTIFIER: {type: 'identifier', value: undefined}, IDENTIFIER: {type: 'identifier', value: undefined},
STRING: {type: 'string', value: undefined}, STRING: {type: 'string', value: undefined},
LAZY: {type: 'lazy', expression: undefined, parsed_expression:undefined} LAZY: {type: 'lazy', expression: undefined, parsed_expression: undefined},
AT: {type: 'reference', value: undefined}
}; };

View file

@ -1,5 +1,14 @@
import {update_lazy_objects, vectors} from "./index"; import {update_lazy_objects} from "./index";
let vectors_by_id = {};
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 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 * Creates an svg element
@ -15,7 +24,7 @@ const create_svg_element = function (element_type) {
* @param element * @param element
* @param child_id id to remove * @param child_id id to remove
*/ */
export const remove_child = function (element, child_id) { const remove_child = function (element, child_id) {
let node = element.firstChild; let node = element.firstChild;
while (node && child_id !== node.id) { while (node && child_id !== node.id) {
node = node.nextSibling; node = node.nextSibling;
@ -92,6 +101,12 @@ const create_arrow = function (id, x0, y0, x1, y1, css_class) {
return path; return path;
} }
export const remove_vector_arrow = function (vector) {
delete vectors_by_id[vector.id];
remove_child(document.getElementById('vectors'), vector.id.toString());
remove_child(document.getElementById('vectors'), "l" + vector.id.toString()); //
}
/** /**
* Draws the background grid of the space * Draws the background grid of the space
* @param css_class class for the lines that are 'multiples of the basis vector' * @param css_class class for the lines that are 'multiples of the basis vector'
@ -118,6 +133,37 @@ export const create_grid = function (css_class, bg_css_class) {
return group; return group;
} }
export const add_vector_arrow = function (vector) {
vectors_by_id[vector.id] = vector;
add_vector_arrow_to_svg(vector);
}
function create_label(vector) {
let label = create_svg_element('text');
label.setAttribute('x', (calc_screen_x(vector.x) + 5).toString());
label.setAttribute('y', (calc_screen_y(vector.y) + 5).toString());
label.setAttribute('fill', 'yellow');
label.setAttribute('id', 'l' + vector.id);
let text_node = document.createTextNode(vector.label_text);
label.appendChild(text_node);
return label;
}
const update_label = function (id, new_id, x, y) {
let label = document.getElementById('l' + id);
label.setAttribute('x', x.toString());
label.setAttribute('y', y.toString());
label.id = 'l' + new_id;
}
export const update_label_text = function (id, text) {
if (text) {
let label = document.getElementById('l' + id);
label.firstChild.textContent = text;
}
}
export const add_vector_arrow_to_svg = function (vector) { export const add_vector_arrow_to_svg = function (vector) {
let vector_group = get_or_create_vector_group(); let vector_group = get_or_create_vector_group();
let vector_arrow = create_arrow(vector.id, vector.x0, vector.y0, vector.x, vector.y, 'vector'); let vector_arrow = create_arrow(vector.id, vector.x0, vector.y0, vector.x, vector.y, 'vector');
@ -126,16 +172,10 @@ export const add_vector_arrow_to_svg = function (vector) {
}; };
vector_group.appendChild(vector_arrow); vector_group.appendChild(vector_arrow);
let label = create_svg_element('text');
label.setAttribute('x', (calc_screen_x(vector.x) + 5).toString()); let label = create_label(vector);
label.setAttribute('y', (calc_screen_y(vector.y) + 5).toString());
label.setAttribute('fill', 'yellow');
label.setAttribute('id', 'l' + vector.id);
let text = document.createTextNode(vector.label);
label.appendChild(text);
vector_group.appendChild(label); vector_group.appendChild(label);
} }
/** /**
@ -149,6 +189,7 @@ export const add_vector_arrow_to_svg = function (vector) {
const draw_vectors = function () { const draw_vectors = function () {
const vector_group = get_or_create_vector_group(); const vector_group = get_or_create_vector_group();
let vectors = Object.values(vectors_by_id);
for (let i = 0; i < vectors.length; i++) { for (let i = 0; i < vectors.length; i++) {
add_vector_arrow_to_svg(vectors[i]); add_vector_arrow_to_svg(vectors[i]);
} }
@ -183,13 +224,13 @@ const redraw_grid = function () {
svg.appendChild(create_axes()); svg.appendChild(create_axes());
} }
export const update_vector_arrow = function (id, vector) { export const update_vector_arrow = function (existing_id, new_vector) {
let d = calculate_d(vector.x0, vector.y0, vector.x, vector.y); let d = calculate_d(new_vector.x0, new_vector.y0, new_vector.x, new_vector.y);
let arrow = document.getElementById(id.toString()); let arrow = document.getElementById(existing_id.toString());
if (arrow) { if (arrow) {
arrow.setAttribute('d', d); arrow.setAttribute('d', d);
arrow.id = vector.id; arrow.id = new_vector.id;
update_label_position(id, vector.id, calc_screen_x(vector.x) + 5, calc_screen_y(vector.y) + 5); update_label(existing_id, new_vector.id, calc_screen_x(new_vector.x) + 5, calc_screen_y(new_vector.y) + 5);
} }
} }
@ -236,13 +277,6 @@ const create_defs = function () {
return defs; return defs;
} }
const update_label_position = function (id, new_id, x, y) {
let label = document.getElementById('l' + id);
label.setAttribute('x', x.toString());
label.setAttribute('y', y.toString());
label.id = 'l' + new_id;
}
/** /**
* The moving operation. Called by onmousemove on the svg ('canvas') * The moving operation. Called by onmousemove on the svg ('canvas')
* @param event * @param event
@ -251,12 +285,15 @@ const move_vector = function (event) {
if (moving_vector) { if (moving_vector) {
let current_x = event.clientX; let current_x = event.clientX;
let current_y = event.clientY; let current_y = event.clientY;
vectors[moving_vector.id].x = (current_x - origin_x) / grid_size; let vector = vectors_by_id[parseInt(moving_vector.id)];
vectors[moving_vector.id].y = (origin_y - current_y) / grid_size; if (vector) {
vector.x = (current_x - origin_x) / grid_size;
vector.y = (origin_y - current_y) / grid_size;
moving_vector.setAttribute('d', create_d(origin_x, origin_y, current_x, current_y)); moving_vector.setAttribute('d', create_d(origin_x, origin_y, current_x, current_y));
update_label_position(moving_vector.id,moving_vector.id, current_x + 5, current_y + 5); update_label(moving_vector.id, moving_vector.id, current_x + 5, current_y + 5);
update_lazy_objects(); update_lazy_objects();
} }
}
} }
/** /**
* Creates the SVG * Creates the SVG
@ -283,14 +320,6 @@ document.body.onresize = function recalculate_window_dimensions() {
redraw(); redraw();
} }
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 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;
const svg = create_svg(); const svg = create_svg();
document.body.appendChild(svg); document.body.appendChild(svg);
svg.appendChild(create_grid('grid', 'bg-grid')); svg.appendChild(create_grid('grid', 'bg-grid'));