Added webpack, eslint and first cypress test. Improved file structure and added imports
This commit is contained in:
parent
9dabe938f9
commit
b04bf181a1
16 changed files with 780 additions and 519 deletions
18
.eslintrc.js
Normal file
18
.eslintrc.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
"SharedArrayBuffer": "readonly"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": ["error", { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }]
|
||||
}
|
||||
};
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,3 +1,5 @@
|
|||
.idea/
|
||||
*.iml
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
dist/
|
||||
node_modules/
|
||||
1
cypress.json
Normal file
1
cypress.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
8
cypress/.eslintrc.json
Normal file
8
cypress/.eslintrc.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"plugins": [
|
||||
"cypress"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:cypress/recommended"
|
||||
]
|
||||
}
|
||||
10
cypress/integration/console.spec.js
Normal file
10
cypress/integration/console.spec.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
describe('draw a vector', () => {
|
||||
it('adds the svg vector', () => {
|
||||
cy.visit('http://localhost:8080');
|
||||
|
||||
cy.get('#command_input').type("a = vector(0,0,1,1){enter}");
|
||||
cy.get('#0').invoke('attr','d').should('eq','M550 350 L650 250');
|
||||
cy.get('#0').invoke('attr','class').should('eq','vector');
|
||||
cy.get('#0').invoke('attr','marker-end').should('eq','url(#arrow)');
|
||||
})
|
||||
})
|
||||
115
dist/bundle.js
vendored
Normal file
115
dist/bundle.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Interactive Linear Algebra</title>
|
||||
<link rel="stylesheet" href="app.css">
|
||||
<link rel="stylesheet" href="src/css/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="console">
|
||||
|
|
@ -12,9 +12,6 @@
|
|||
<textarea id="command_input" class="single_line" autofocus></textarea>
|
||||
</label>
|
||||
</div>
|
||||
<script type="application/ecmascript" src="index.js"></script>
|
||||
<script type="application/ecmascript" src="console.js"></script>
|
||||
<script type="application/ecmascript" src="scanner.js"></script>
|
||||
<script type="application/ecmascript" src="parser.js"></script>
|
||||
<script type="application/ecmascript" src="bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
34
package.json
Normal file
34
package.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "matrepl",
|
||||
"version": "1.0.0",
|
||||
"description": "MatRepl is a Matrix * and a REPL. The Print part does operations on vectors and matrices in a visual environment.",
|
||||
"main": "src/js/index.js",
|
||||
"scripts": {
|
||||
"test": "jest --config=jest.config.js",
|
||||
"build": "webpack build",
|
||||
"watch": "webpack --watch",
|
||||
"start": "webpack serve --open --host 0.0.0.0 --port 8080"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/shautvast/matrepl.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/shautvast/matrepl/issues"
|
||||
},
|
||||
"homepage": "https://github.com/shautvast/matrepl#readme",
|
||||
"devDependencies": {
|
||||
"css-loader": "^5.0.2",
|
||||
"cypress": "^6.4.0",
|
||||
"eslint": "^7.20.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-cypress": "^2.11.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"webpack": "^5.22.0",
|
||||
"webpack-cli": "^4.5.0",
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
}
|
||||
}
|
||||
201
src/console.js
201
src/console.js
|
|
@ -1,201 +0,0 @@
|
|||
/**
|
||||
* 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');
|
||||
command_input_element.value = '';
|
||||
let command_history = [''];
|
||||
let command_history_index = 0;
|
||||
|
||||
let adjust_input_element_height = function(){
|
||||
let num_lines=command_input_element.value.split(/\n/).length;
|
||||
command_input_element.setAttribute('style', 'height: ' + num_lines + 'em');
|
||||
if (num_lines>1){
|
||||
command_input_element.setAttribute('class','multiline');
|
||||
} else {
|
||||
command_input_element.setAttribute('class','single_line');
|
||||
}
|
||||
}
|
||||
|
||||
command_input_element.onkeyup = function handle_key_input(event) {
|
||||
adjust_input_element_height();
|
||||
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 commands = command_input_element.value;
|
||||
command_input_element.value='';
|
||||
adjust_input_element_height();
|
||||
let command_array = commands.split(/\n/);
|
||||
for (let i = 0; i < command_array.length; i++) {
|
||||
let command = command_array[i];
|
||||
if (command.length > 0) {
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
)();
|
||||
297
src/index.js
297
src/index.js
|
|
@ -1,297 +0,0 @@
|
|||
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());
|
||||
})
|
||||
();
|
||||
213
src/js/console.js
Normal file
213
src/js/console.js
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
import {scan, token_types} from './scanner';
|
||||
import {parse} from './parser';
|
||||
import {add_vector, remove_vector} from "./index";
|
||||
|
||||
/**
|
||||
* handles user input from the console div
|
||||
*/
|
||||
const state = {};
|
||||
const command_input_element = document.getElementById('command_input');
|
||||
const command_history_element = document.getElementById('command_history');
|
||||
command_input_element.value = '';
|
||||
let command_history = [''];
|
||||
let command_history_index = 0;
|
||||
|
||||
export const adjust_input_element_height = function () {
|
||||
let num_lines = command_input_element.value.split(/\n/).length;
|
||||
command_input_element.setAttribute('style', 'height: ' + num_lines + 'em');
|
||||
if (num_lines > 1) {
|
||||
command_input_element.setAttribute('class', 'multiline');
|
||||
} else {
|
||||
command_input_element.setAttribute('class', 'single_line');
|
||||
}
|
||||
}
|
||||
|
||||
command_input_element.onkeyup = function handle_key_input(event) {
|
||||
adjust_input_element_height();
|
||||
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 commands = command_input_element.value;
|
||||
command_input_element.value = '';
|
||||
adjust_input_element_height();
|
||||
let command_array = commands.split(/\n/);
|
||||
for (let i = 0; i < command_array.length; i++) {
|
||||
let command = command_array[i];
|
||||
if (command.length > 0) {
|
||||
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);
|
||||
case 'lazy':
|
||||
console.log(expr.value);
|
||||
return visit_expression(expr.value);
|
||||
}
|
||||
}
|
||||
|
||||
const 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 (functions[function_name]) {
|
||||
return functions[function_name](arguments_list);
|
||||
} 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.prototype.hasOwnProperty.call(object_wrapper.object, 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 (Object.prototype.hasOwnProperty.call(args[0],'binding')){
|
||||
delete 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 {
|
||||
description:
|
||||
`- vector(<x0>, <y0>, <x>, <y>): draws a vector from x0,y0 to x,y
|
||||
- remove(<identifier>|<ref>): removes an object,
|
||||
a ref is @n where n is the reference number asigned to the object`
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
297
src/js/index.js
Normal file
297
src/js/index.js
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
import './console.js';
|
||||
import './scanner.js';
|
||||
import './parser.js';
|
||||
|
||||
export let add_vector, remove_vector;
|
||||
|
||||
/**
|
||||
* Main entry. draws the matrix
|
||||
*/
|
||||
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_vector = 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_vector();
|
||||
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());
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
const parse = function (tokens) {
|
||||
import {token_types} from './scanner';
|
||||
import {scan} from './scanner';
|
||||
|
||||
export const parse = function (tokens) {
|
||||
let token_index = 0;
|
||||
|
||||
return statement();
|
||||
|
|
@ -79,7 +82,7 @@ const parse = function (tokens) {
|
|||
function call() {
|
||||
let expr = primary();
|
||||
|
||||
while (true) {
|
||||
for (;;){
|
||||
if (match([token_types.LEFT_PAREN])) {
|
||||
expr = finish_call(expr.name);
|
||||
} else {
|
||||
|
|
@ -91,23 +94,27 @@ const parse = function (tokens) {
|
|||
}
|
||||
|
||||
function finish_call(callee) {
|
||||
let arguments = [];
|
||||
let arguments_list = [];
|
||||
if (!check(token_types.RIGHT_PAREN, token_index)) {
|
||||
do {
|
||||
arguments.push(expression());
|
||||
arguments_list.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};
|
||||
return {type: 'call', name: callee, arguments: arguments_list};
|
||||
}
|
||||
|
||||
|
||||
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.LAZY])) {
|
||||
let tokens = scan(previous_token().expression);
|
||||
let expression = parse(tokens);
|
||||
return {type: 'lazy', value: expression};
|
||||
} else if (match([token_types.LEFT_PAREN])) {
|
||||
let expr = expression();
|
||||
if (expr && match([token_types.RIGHT_PAREN])) {
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
* Creates an array of tokens from a line of input.
|
||||
*
|
||||
* @param command: string
|
||||
* @returns {token_type[]}
|
||||
* @returns {token_types[]}
|
||||
*/
|
||||
const scan = function(command) {
|
||||
export 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 = [];
|
||||
|
|
@ -66,9 +66,9 @@ const scan = function(command) {
|
|||
return token_types.EQUALS;
|
||||
}
|
||||
case '\'':
|
||||
return string('\'');
|
||||
case '\"':
|
||||
return string('\"');
|
||||
return string();
|
||||
case '"':
|
||||
return lazy_expression();
|
||||
}
|
||||
if (is_digit(next_char)) {
|
||||
let token = Object.assign({}, token_types.NUMERIC);
|
||||
|
|
@ -141,11 +141,11 @@ const scan = function(command) {
|
|||
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()) {
|
||||
function string() { // as of yet strings may not unclude escaped quotes that are also the start/end quote
|
||||
while (current_char() !== '\'' && !is_at_end()) {
|
||||
advance();
|
||||
}
|
||||
if (is_at_end() && current_char() !== quote) {
|
||||
if (is_at_end() && current_char() !== '\'') {
|
||||
throw {message: 'unterminated string'}
|
||||
} else {
|
||||
let string_token = Object.assign({}, token_types.STRING);
|
||||
|
|
@ -154,9 +154,23 @@ const scan = function(command) {
|
|||
return string_token;
|
||||
}
|
||||
}
|
||||
|
||||
function lazy_expression() {
|
||||
while (current_char() !== '"' && !is_at_end()) {
|
||||
advance();
|
||||
}
|
||||
if (is_at_end() && current_char() !== '"') {
|
||||
throw {message: 'unterminated string'}
|
||||
} else {
|
||||
let lazy_token = Object.assign({}, token_types.LAZY);
|
||||
lazy_token.expression = command.substring(word_start_index + 1, current_index);
|
||||
advance();
|
||||
return lazy_token;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const token_types = {
|
||||
export const token_types = {
|
||||
LEFT_PAREN: {type: 'left_paren'},
|
||||
RIGHT_PAREN: {type: 'right_paren'},
|
||||
LEFT_BRACKET: {type: 'left_bracket'},
|
||||
|
|
@ -177,5 +191,6 @@ const token_types = {
|
|||
LESS_OR_EQUAL: {type: 'less_or_equal'},
|
||||
NUMERIC: {type: 'number', 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}
|
||||
};
|
||||
42
webpack.config.js
Normal file
42
webpack.config.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
entry: './src/js/index.js',
|
||||
|
||||
output: {
|
||||
filename: 'bundle.js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'eslint-loader',
|
||||
options: {
|
||||
emitError: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
'css-loader',
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(png|svg|jpg|gif)$/,
|
||||
use: [
|
||||
'file-loader',
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(woff|woff2|eot|ttf|otf)$/,
|
||||
use: [
|
||||
'file-loader',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue