don't be eval
This commit is contained in:
commit
e94671c85e
20 changed files with 5984 additions and 0 deletions
11
.editorconfig
Normal file
11
.editorconfig
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
194
.gitattributes
vendored
Normal file
194
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
## GITATTRIBUTES FOR WEB PROJECTS
|
||||
#
|
||||
# These settings are for any web project.
|
||||
#
|
||||
# Details per file setting:
|
||||
# text These files should be normalized (i.e. convert CRLF to LF).
|
||||
# binary These files are binary and should be left untouched.
|
||||
#
|
||||
# Note that binary is a macro for -text -diff.
|
||||
######################################################################
|
||||
|
||||
## AUTO-DETECT
|
||||
## Handle line endings automatically for files detected as
|
||||
## text and leave all files detected as binary untouched.
|
||||
## This will handle all files NOT defined below.
|
||||
* text=auto
|
||||
|
||||
## SOURCE CODE
|
||||
*.bat text eol=crlf
|
||||
*.coffee text
|
||||
*.css text
|
||||
*.htm text
|
||||
*.html text
|
||||
*.inc text
|
||||
*.ini text
|
||||
*.js text
|
||||
*.json text
|
||||
*.jsx text
|
||||
*.less text
|
||||
*.od text
|
||||
*.onlydata text
|
||||
*.php text
|
||||
*.pl text
|
||||
*.py text
|
||||
*.rb text
|
||||
*.sass text
|
||||
*.scm text
|
||||
*.scss text
|
||||
*.sh text eol=lf
|
||||
*.sql text
|
||||
*.styl text
|
||||
*.tag text
|
||||
*.ts text
|
||||
*.tsx text
|
||||
*.xml text
|
||||
*.xhtml text
|
||||
|
||||
## DOCKER
|
||||
*.dockerignore text
|
||||
Dockerfile text
|
||||
|
||||
## DOCUMENTATION
|
||||
*.markdown text
|
||||
*.md text
|
||||
*.mdwn text
|
||||
*.mdown text
|
||||
*.mkd text
|
||||
*.mkdn text
|
||||
*.mdtxt text
|
||||
*.mdtext text
|
||||
*.txt text
|
||||
AUTHORS text
|
||||
CHANGELOG text
|
||||
CHANGES text
|
||||
CONTRIBUTING text
|
||||
COPYING text
|
||||
copyright text
|
||||
*COPYRIGHT* text
|
||||
INSTALL text
|
||||
license text
|
||||
LICENSE text
|
||||
NEWS text
|
||||
readme text
|
||||
*README* text
|
||||
TODO text
|
||||
|
||||
## TEMPLATES
|
||||
*.dot text
|
||||
*.ejs text
|
||||
*.haml text
|
||||
*.handlebars text
|
||||
*.hbs text
|
||||
*.hbt text
|
||||
*.jade text
|
||||
*.latte text
|
||||
*.mustache text
|
||||
*.njk text
|
||||
*.phtml text
|
||||
*.tmpl text
|
||||
*.tpl text
|
||||
*.twig text
|
||||
|
||||
## LINTERS
|
||||
.babelrc text
|
||||
.csslintrc text
|
||||
.eslintrc text
|
||||
.htmlhintrc text
|
||||
.jscsrc text
|
||||
.jshintrc text
|
||||
.jshintignore text
|
||||
.prettierrc text
|
||||
.stylelintrc text
|
||||
|
||||
## CONFIGS
|
||||
*.bowerrc text
|
||||
*.cnf text
|
||||
*.conf text
|
||||
*.config text
|
||||
.browserslistrc text
|
||||
.editorconfig text
|
||||
.gitattributes text
|
||||
.gitconfig text
|
||||
.gitignore text
|
||||
.htaccess text
|
||||
*.npmignore text
|
||||
*.yaml text
|
||||
*.yml text
|
||||
browserslist text
|
||||
Makefile text
|
||||
makefile text
|
||||
|
||||
## HEROKU
|
||||
Procfile text
|
||||
.slugignore text
|
||||
|
||||
## GRAPHICS
|
||||
*.ai binary
|
||||
*.bmp binary
|
||||
*.eps binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.jng binary
|
||||
*.jp2 binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.jpx binary
|
||||
*.jxr binary
|
||||
*.pdf binary
|
||||
*.png binary
|
||||
*.psb binary
|
||||
*.psd binary
|
||||
*.svg text
|
||||
*.svgz binary
|
||||
*.tif binary
|
||||
*.tiff binary
|
||||
*.wbmp binary
|
||||
*.webp binary
|
||||
|
||||
## AUDIO
|
||||
*.kar binary
|
||||
*.m4a binary
|
||||
*.mid binary
|
||||
*.midi binary
|
||||
*.mp3 binary
|
||||
*.ogg binary
|
||||
*.ra binary
|
||||
|
||||
## VIDEO
|
||||
*.3gpp binary
|
||||
*.3gp binary
|
||||
*.as binary
|
||||
*.asf binary
|
||||
*.asx binary
|
||||
*.fla binary
|
||||
*.flv binary
|
||||
*.m4v binary
|
||||
*.mng binary
|
||||
*.mov binary
|
||||
*.mp4 binary
|
||||
*.mpeg binary
|
||||
*.mpg binary
|
||||
*.ogv binary
|
||||
*.swc binary
|
||||
*.swf binary
|
||||
*.webm binary
|
||||
|
||||
## ARCHIVES
|
||||
*.7z binary
|
||||
*.gz binary
|
||||
*.jar binary
|
||||
*.rar binary
|
||||
*.tar binary
|
||||
*.zip binary
|
||||
|
||||
## FONTS
|
||||
*.ttf binary
|
||||
*.eot binary
|
||||
*.otf binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
|
||||
## EXECUTABLES
|
||||
*.exe binary
|
||||
*.pyc binary
|
||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Include your project-specific ignores in this file
|
||||
# Read about how to use .gitignore: https://help.github.com/articles/ignoring-files
|
||||
# Useful .gitignore templates: https://github.com/github/gitignore
|
||||
node_modules
|
||||
dist
|
||||
.cache
|
||||
.idea/
|
||||
38
README.md
Normal file
38
README.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
__Szpakowski__
|
||||
|
||||
An algorithmic tribute to the Polish artist [Wacław Szpakowski](https://de.wikipedia.org/wiki/Wac%C5%82aw_Szpakowski)
|
||||
|
||||
 
|
||||
|
||||
at this moment still work in progress...
|
||||
|
||||
What is it?
|
||||
* Think _Logo_, the 'turtle' language from the eighties.
|
||||
* Because Szpakowski's drawings all follow the principle of the single line.
|
||||
* it has the basic commands to move the turtle:
|
||||
- start(x,y)
|
||||
- go(distance)
|
||||
- left(degrees)
|
||||
- right(degrees)
|
||||
|
||||
* on top of that it has:
|
||||
- pillars(number, length, shift, direction=DOWN): the turtle follows a pillary pattern
|
||||
- number: the number of pillars (running back and forth)
|
||||
- length: the distance per run
|
||||
- shift: in-/decrease in length
|
||||
- direction: UO|DOWN|BOTH
|
||||
- moving_pillars(n, length, shift=0, direction=DOWN): the pillars move
|
||||
- number: the number of pillars (running back and forth)
|
||||
- length: the distance per run
|
||||
- shift: shift per run
|
||||
- direction: UP|DOWN
|
||||
- staircase(number, size, direction = DOWN)
|
||||
- number: the number of steps
|
||||
- size: the step size
|
||||
- direction: UP|DOWN
|
||||
|
||||
... more functions will follow
|
||||
|
||||
|
||||
### Run
|
||||
* npm run start
|
||||
203
css/style.css
Normal file
203
css/style.css
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
html {
|
||||
color: #222;
|
||||
font-size: 1em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
#console {
|
||||
font: 13px monospace, sans-serif;
|
||||
padding: 5px;
|
||||
color: greenyellow;
|
||||
background: black;
|
||||
position: fixed;
|
||||
right: 10px;
|
||||
bottom: 0;
|
||||
width: 30%;
|
||||
height: 20em;
|
||||
border: 2px solid darkgray;
|
||||
border-radius: 10px;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#help{
|
||||
background: black;
|
||||
font: 13px monospace, sans-serif;
|
||||
padding: 5px;
|
||||
width: 90%;
|
||||
color: greenyellow;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#command_input {
|
||||
background: black;
|
||||
color: greenyellow;
|
||||
outline: none;
|
||||
width: 90%;
|
||||
height: 20em;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.multiline {
|
||||
border: none transparent;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
|
||||
.single_line {
|
||||
border: none transparent;
|
||||
}
|
||||
|
||||
.hidden,
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hide only visually, but have it available for screen readers:
|
||||
* https://snook.ca/archives/html_and_css/hiding-content-for-accessibility
|
||||
*
|
||||
* 1. For long content, line feeds are not interpreted as spaces and small width
|
||||
* causes content to wrap 1 word per line:
|
||||
* https://medium.com/@jessebeach/beware-smushed-off-screen-accessible-text-5952a4c2cbfe
|
||||
*/
|
||||
|
||||
.visually-hidden {
|
||||
border: 0;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
width: 1px;
|
||||
/* 1 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Extends the .visually-hidden class to allow the element
|
||||
* to be focusable when navigated to via the keyboard:
|
||||
* https://www.drupal.org/node/897638
|
||||
*/
|
||||
|
||||
.visually-hidden.focusable:active,
|
||||
.visually-hidden.focusable:focus {
|
||||
clip: auto;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
position: static;
|
||||
white-space: inherit;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hide visually and from screen readers, but maintain layout
|
||||
*/
|
||||
|
||||
.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clearfix: contain floats
|
||||
*
|
||||
* The use of `table` rather than `block` is only necessary if using
|
||||
* `::before` to contain the top-margins of child elements.
|
||||
*/
|
||||
|
||||
.clearfix::before,
|
||||
.clearfix::after {
|
||||
content: "";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.clearfix::after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
EXAMPLE Media Queries for Responsive Design.
|
||||
These examples override the primary ('mobile first') styles.
|
||||
Modify as content requires.
|
||||
========================================================================== */
|
||||
|
||||
@media only screen and (min-width: 35em) {
|
||||
/* Style adjustments for viewports that meet the condition */
|
||||
}
|
||||
|
||||
@media print,
|
||||
(-webkit-min-device-pixel-ratio: 1.25),
|
||||
(min-resolution: 1.25dppx),
|
||||
(min-resolution: 120dpi) {
|
||||
/* Style adjustments for high resolution devices */
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Print styles.
|
||||
Inlined to avoid the additional HTTP request:
|
||||
https://www.phpied.com/delay-loading-your-print-css/
|
||||
========================================================================== */
|
||||
|
||||
@media print {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
background: #fff !important;
|
||||
color: #000 !important;
|
||||
/* Black prints faster */
|
||||
box-shadow: none !important;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
a,
|
||||
a:visited {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a[href]::after {
|
||||
content: " (" attr(href) ")";
|
||||
}
|
||||
|
||||
abbr[title]::after {
|
||||
content: " (" attr(title) ")";
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't show links that are fragment identifiers,
|
||||
* or use the `javascript:` pseudo protocol
|
||||
*/
|
||||
a[href^="#"]::after,
|
||||
a[href^="javascript:"]::after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
|
||||
pre,
|
||||
blockquote {
|
||||
border: 1px solid #999;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
tr,
|
||||
img {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
p,
|
||||
h2,
|
||||
h3 {
|
||||
orphans: 3;
|
||||
widows: 3;
|
||||
}
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
page-break-after: avoid;
|
||||
}
|
||||
}
|
||||
|
||||
0
img/.gitkeep
Normal file
0
img/.gitkeep
Normal file
51
index.html
Normal file
51
index.html
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<!doctype html>
|
||||
<html class="no-js" lang="">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title></title>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<meta name="description" content="">
|
||||
|
||||
<meta property="og:title" content="">
|
||||
<meta property="og:type" content="">
|
||||
<meta property="og:url" content="">
|
||||
<meta property="og:image" content="">
|
||||
<meta property="og:image:alt" content="">
|
||||
|
||||
<link rel="icon" href="/favicon.ico" sizes="any">
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="icon.png">
|
||||
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<meta name="theme-color" content="#fafafa">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
|
||||
<div id="console">
|
||||
<label id="prompt">
|
||||
<textarea id="command_input" class="multiline" autofocus>
|
||||
start(0, 0);
|
||||
go(1);
|
||||
moving_pillars(5, 10,1, false);
|
||||
turn(90);
|
||||
moving_pillars(5, 10,1, false);
|
||||
turn(90);
|
||||
go(1);
|
||||
moving_pillars(5, 10,1, false);
|
||||
turn(90);
|
||||
moving_pillars(5, 10,1, false);
|
||||
</textarea>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<textarea id="help" style="visibility: hidden"></textarea>
|
||||
|
||||
<script src="js/app.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
121
js/app.js
Normal file
121
js/app.js
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
"use strict";
|
||||
import {interpret} from "./interpreter.js";
|
||||
|
||||
(function () {
|
||||
const command_input_element = document.getElementById('command_input');
|
||||
const canvas_element = document.getElementById('canvas');
|
||||
const canvas = canvas_element.getContext('2d');
|
||||
const width = canvas_element.width = window.innerWidth;
|
||||
const height = canvas_element.height = window.innerHeight;
|
||||
canvas.fillStyle = 'white';
|
||||
canvas.strokeStyle = 'black';
|
||||
canvas.lineWidth = 2;
|
||||
|
||||
const help_element = document.getElementById('help');
|
||||
let script;
|
||||
|
||||
document.body.onkeydown = function (event) {
|
||||
if (event.key === 'Tab') {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
const help = [
|
||||
{v1: "start(x,y)", v2: "start(0,0);"},
|
||||
{v1: "go(distance)", v2: "go(1);"},
|
||||
{v1: "turn(angle)", v2: "turn(90);"},
|
||||
{v1: "left(angle)", v2: "left(90);"},
|
||||
{v1: "right(angle)", v2: "right(90);"},
|
||||
{v1: "pillars(number, length, shift = 0, direction = UP)", v2: "pillars(3, 10, 0, DOWN);"},
|
||||
{v1: "moving_pillars(number, length, shift = 0, direction = DOWN)", v2: "moving_pillars(3, 10, 0, DOWN);"},
|
||||
{v1: "staircase(number, size, direction = DOWN)", v2: "staircase(3, 1, DOWN);"},
|
||||
];
|
||||
const slices = {};
|
||||
for (let i = 0; i < help.length; i++) {
|
||||
for (let j = 1; j < help[i].v1.length; j++) {
|
||||
const sub = help[i].v1.substring(0, j);
|
||||
if (!slices[sub]) {
|
||||
slices[sub] = [];
|
||||
}
|
||||
slices[sub].push(help[i]);
|
||||
}
|
||||
slices[help[i].v1] = [help[i]];
|
||||
}
|
||||
|
||||
let tx = 0;
|
||||
let ty = 0;
|
||||
let tangle = 0;
|
||||
|
||||
document.onkeyup = function (event) {
|
||||
if (event.key === 'Tab') {
|
||||
const script = command_input_element.value;
|
||||
event.preventDefault();
|
||||
const cursor_position = command_input_element.selectionStart;
|
||||
const textUpToCursor = script.slice(0, cursor_position);
|
||||
const startOfLine = textUpToCursor.lastIndexOf('\n') + 1;
|
||||
const endOfLine = script.indexOf('\n', cursor_position);
|
||||
const lineEndPosition = endOfLine === -1 ? script.length : endOfLine;
|
||||
const currentLineText = script.slice(startOfLine, lineEndPosition).trim();
|
||||
|
||||
if (slices[currentLineText]) {
|
||||
const helpOptions = slices[currentLineText].map(x => x.v2);
|
||||
const firstOption = helpOptions[0];
|
||||
|
||||
command_input_element.value = script.slice(0, startOfLine) + firstOption + script.slice(lineEndPosition);
|
||||
command_input_element.setSelectionRange(startOfLine + firstOption.length, startOfLine + firstOption.length);
|
||||
command_input_element.focus();
|
||||
help_element.style.visibility = 'hidden';
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function refresh() {
|
||||
script = command_input_element.value;
|
||||
|
||||
try {
|
||||
canvas.clearRect(0, 0, width, height);
|
||||
interpret({canvas: canvas, x: tx, y: ty, angle: tangle, unit: 10, width, height}, script);
|
||||
} catch (Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
command_input_element.onkeyup = function handle_key_input(event) {
|
||||
if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
|
||||
script = command_input_element.value;
|
||||
|
||||
help_element.style.visibility = 'hidden';
|
||||
const cursor_position = command_input_element.selectionStart;
|
||||
const textUpToCursor = script.slice(0, cursor_position);
|
||||
const startOfLine = textUpToCursor.lastIndexOf('\n') + 1;
|
||||
|
||||
const endOfLine = script.indexOf('\n', cursor_position);
|
||||
const lineEndPosition = endOfLine === -1 ? script.length : endOfLine;
|
||||
|
||||
const currentLineText = script.slice(startOfLine, lineEndPosition);
|
||||
if (currentLineText.length > 0) {
|
||||
const command = currentLineText.trim();
|
||||
if (slices[command]) {
|
||||
const help = slices[command].map(x => x.v1);
|
||||
const help_text = help.join('\n');
|
||||
|
||||
const computedStyle = window.getComputedStyle(command_input_element);
|
||||
const fontSize = parseFloat(computedStyle.fontSize);
|
||||
const lineHeight = computedStyle.lineHeight === 'normal' ? fontSize * 1.2 : parseFloat(computedStyle.lineHeight);
|
||||
|
||||
const textBounding = command_input_element.getBoundingClientRect();
|
||||
const topPosition = textBounding.top + window.scrollY;// + (cursor_position - startOfLine) * lineHeight/ script.split('\n').length;
|
||||
|
||||
help_element.style.visibility = 'visible';
|
||||
help_element.style.left = `${textBounding.left + window.scrollX}px`;
|
||||
help_element.style.top = `${topPosition - help_element.offsetHeight}px`;
|
||||
help_element.innerHTML = help_text;
|
||||
help_element.style.display = 'block';
|
||||
}
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
};
|
||||
|
||||
refresh();
|
||||
})();
|
||||
241
js/interpreter.js
Normal file
241
js/interpreter.js
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
import {parse} from "./parser";
|
||||
import {
|
||||
BANG,
|
||||
BANG_EQUAL,
|
||||
EQUAL_EQUAL,
|
||||
GREATER,
|
||||
GREATER_EQUAL,
|
||||
LESS,
|
||||
LESS_EQUAL,
|
||||
MINUS,
|
||||
PLUS,
|
||||
SLASH,
|
||||
STAR
|
||||
} from "./scanner";
|
||||
|
||||
export function interpret(init_env, code) {
|
||||
|
||||
let {canvas, tx, ty, tangle, unit, width, height} = init_env;
|
||||
const UP = "UP";
|
||||
const DOWN = "DOWN";
|
||||
const LEFT = "LEFT";
|
||||
const RIGHT = "RIGHT";
|
||||
const BOTH = "BOTH";
|
||||
|
||||
Object.assign(init_env, {UP, DOWN, LEFT, RIGHT, BOTH});
|
||||
const x0 = width / 2;
|
||||
const y0 = height / 4;
|
||||
|
||||
let THIS = {
|
||||
current_environment: init_env,
|
||||
|
||||
execute: (statement) => {
|
||||
statement.accept(THIS);
|
||||
},
|
||||
|
||||
visitBlockStatement: (statements) => {
|
||||
THIS.executeBlock(statements, {enclosing: THIS.current_environment});
|
||||
},
|
||||
|
||||
visitExpressionStatement: (expression) => {
|
||||
THIS.evaluate(expression);
|
||||
},
|
||||
|
||||
visitVariableStatement: (name, initializer) => {
|
||||
let value = undefined;
|
||||
if (initializer) {
|
||||
value = THIS.evaluate(initializer);
|
||||
}
|
||||
THIS.current_environment[name.lexeme] = value;
|
||||
},
|
||||
|
||||
visitPrintStatement: (expression) => {
|
||||
console.log(THIS.current_environment);
|
||||
let value = THIS.evaluate(expression);
|
||||
console.log(THIS.stringify(value));
|
||||
},
|
||||
|
||||
visitCallStatement: (fun, argList) => {
|
||||
let args = argList.map(THIS.evaluate);
|
||||
switch (fun){
|
||||
case "start": THIS.start(...args); break;
|
||||
case "go": THIS.go(...args); break;
|
||||
case "turn": THIS.turn(...args); break;
|
||||
case "left": THIS.left(...args); break;
|
||||
case "right": THIS.right(...args); break;
|
||||
case "pillars": THIS.pillars(...args); break;
|
||||
case "moving_pillars": THIS.moving_pillars(...args); break;
|
||||
case "staircase": THIS.staircase(...args); break;
|
||||
default: throw "Unknown function: " + fun;
|
||||
}
|
||||
},
|
||||
|
||||
start: (x,y) => {
|
||||
tx = x * unit;
|
||||
ty = y * unit;
|
||||
tangle = 0;
|
||||
canvas.beginPath();
|
||||
canvas.moveTo(x0 + tx, y0 + ty);
|
||||
},
|
||||
|
||||
go: (distance) => {
|
||||
tx += distance * unit * Math.cos(tangle);
|
||||
ty += distance * unit * Math.sin(tangle);
|
||||
canvas.lineTo(x0 + tx, y0 + ty);
|
||||
canvas.stroke();
|
||||
},
|
||||
|
||||
turn: (degrees) => {
|
||||
tangle += degrees * Math.PI / 180;
|
||||
},
|
||||
|
||||
left: (degrees) => {
|
||||
THIS.turn(-degrees);
|
||||
},
|
||||
|
||||
right: (degrees) => {
|
||||
THIS.turn(degrees);
|
||||
},
|
||||
|
||||
pillars: (number, length, shift = 0, direction = UP) => {
|
||||
for (let i = 0; i < number; i++) {
|
||||
THIS.turn(-90);
|
||||
THIS.go(direction === DOWN ? -length : length);
|
||||
THIS.turn(90);
|
||||
THIS.go(1);
|
||||
THIS.turn(90);
|
||||
if (direction === BOTH) {
|
||||
length += shift;
|
||||
}
|
||||
THIS.go(direction === DOWN ? -length : length);
|
||||
THIS.turn(-90);
|
||||
THIS.go(1);
|
||||
length += shift;
|
||||
}
|
||||
},
|
||||
|
||||
moving_pillars: (n, length, shift = 0, direction = DOWN) => {
|
||||
let length2 = length;
|
||||
length += shift;
|
||||
for (let i = 0; i < n; i++) {
|
||||
THIS.turn(-90);
|
||||
THIS.go(direction === DOWN ? -length2 : length2);
|
||||
THIS.turn(90);
|
||||
THIS.go(1);
|
||||
THIS.turn(90);
|
||||
THIS.go(direction === DOWN ? -length : length);
|
||||
THIS.turn(-90);
|
||||
THIS.go(1);
|
||||
}
|
||||
},
|
||||
|
||||
staircase: (number_of_steps, size, direction = DOWN) =>{
|
||||
const angle = direction === DOWN ? 90 : -90;
|
||||
for (let i = 0; i < number_of_steps; i++) {
|
||||
THIS.go(size);
|
||||
THIS.turn(angle);
|
||||
THIS.go(size);
|
||||
THIS.turn(-angle);
|
||||
}
|
||||
},
|
||||
|
||||
executeBlock: (statements, environment) => {
|
||||
let previous = THIS.current_environment;
|
||||
try {
|
||||
THIS.current_environment = environment;
|
||||
for (let i = 0; i < statements.length; i++) {
|
||||
THIS.execute(statements[i]);
|
||||
}
|
||||
} finally {
|
||||
THIS.current_environment = previous;
|
||||
}
|
||||
},
|
||||
|
||||
visitBinaryExpr: (operator, _left, _right) => {
|
||||
let left = THIS.evaluate(_left);
|
||||
let right = THIS.evaluate(_right);
|
||||
|
||||
switch (operator.type) {
|
||||
case MINUS:
|
||||
return left - right;
|
||||
case SLASH:
|
||||
return left / right;
|
||||
case STAR:
|
||||
return left * right;
|
||||
case PLUS:
|
||||
if (typeof left === 'number' && typeof right === 'number') {
|
||||
return left + right;
|
||||
} else {
|
||||
return THIS.stringify(left) + THIS.stringify(right);
|
||||
}
|
||||
case GREATER:
|
||||
return left > right;
|
||||
case GREATER_EQUAL:
|
||||
return left >= right;
|
||||
case LESS:
|
||||
return left < right;
|
||||
case LESS_EQUAL:
|
||||
return left <= right;
|
||||
case BANG_EQUAL:
|
||||
return left !== right;
|
||||
case EQUAL_EQUAL:
|
||||
return left === right;
|
||||
}
|
||||
throw "?";
|
||||
},
|
||||
|
||||
visitGroupingExpr: (expr) => {
|
||||
return THIS.evaluate(expr);
|
||||
},
|
||||
|
||||
visitLiteralExpr: (value) => {
|
||||
return value;
|
||||
},
|
||||
|
||||
visitUnaryExpr: (operator, right_expr) => {
|
||||
let right = THIS.evaluate(right_expr);
|
||||
|
||||
switch (operator.type) {
|
||||
case MINUS:
|
||||
return -right;
|
||||
case BANG:
|
||||
return !right;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
visitVariableExpr: (name) => {
|
||||
return THIS.current_environment[name.lexeme];
|
||||
},
|
||||
|
||||
visitAssignExpr: (name, value_expr) => {
|
||||
let value = THIS.evaluate(value_expr);
|
||||
THIS.current_environment[name] = value;
|
||||
return value;
|
||||
},
|
||||
|
||||
evaluate: (expr) => {
|
||||
return expr.accept(THIS);
|
||||
},
|
||||
|
||||
stringify: (value) => {
|
||||
if (value === undefined || value === null) {
|
||||
return "nil";
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const statements = parse(code);
|
||||
for (let i = 0; i < statements.length; i++) {
|
||||
|
||||
THIS.execute(statements[i]);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
258
js/parser.js
Normal file
258
js/parser.js
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
import "./scanner";
|
||||
import {
|
||||
scan, error,
|
||||
BANG, BANG_EQUAL, EOF,
|
||||
EQUAL, EQUAL_EQUAL,
|
||||
FALSE, GREATER,
|
||||
GREATER_EQUAL, IDENTIFIER,
|
||||
LEFT_BRACE, LEFT_PAREN, LESS,
|
||||
LESS_EQUAL, MINUS, NUMBER, PLUS,
|
||||
PRINT, RIGHT_BRACE, RIGHT_PAREN,
|
||||
SEMICOLON, SLASH, STAR, STRING, TRUE, VAR, START, COMMA, GO, STAIRCASE, PILLARS, MOVING_PILLARS, LEFT, RIGHT, TURN
|
||||
} from "./scanner";
|
||||
|
||||
export function parse(code) {
|
||||
const tokens = scan(code);
|
||||
// console.log(tokens);
|
||||
let current = 0;
|
||||
|
||||
const parse = () => {
|
||||
let statements = [];
|
||||
while (!is_at_end()) {
|
||||
statements.push(declaration());
|
||||
}
|
||||
return statements;
|
||||
}
|
||||
|
||||
const declaration = () => {
|
||||
if (match(VAR)) {
|
||||
return varDeclaration();
|
||||
}
|
||||
return statement();
|
||||
}
|
||||
|
||||
const varDeclaration = () => {
|
||||
const name = consume(IDENTIFIER, "Expected a variable name");
|
||||
let initializer;
|
||||
if (match(EQUAL)) {
|
||||
initializer = expression();
|
||||
}
|
||||
consume(SEMICOLON, "Expected semicolon");
|
||||
return {accept: (visitor) => visitor.visitVariableStatement(name, initializer)};
|
||||
}
|
||||
|
||||
const statement = () => {
|
||||
if (match(PRINT)) {
|
||||
return printStatement();
|
||||
}
|
||||
if (match(START)) {
|
||||
return callStatement("start");
|
||||
}
|
||||
if (match(GO)) {
|
||||
return callStatement("go");
|
||||
}
|
||||
if (match(LEFT)) {
|
||||
return callStatement("left");
|
||||
}
|
||||
if (match(RIGHT)) {
|
||||
return callStatement("right");
|
||||
}
|
||||
if (match(TURN)) {
|
||||
return callStatement("turn");
|
||||
}
|
||||
if (match(STAIRCASE)) {
|
||||
return callStatement("staircase");
|
||||
}
|
||||
if (match(PILLARS)) {
|
||||
return callStatement("pillars");
|
||||
}
|
||||
if (match(MOVING_PILLARS)) {
|
||||
return callStatement("moving_pillars");
|
||||
}
|
||||
if (match(LEFT_BRACE)) {
|
||||
return {accept: (visitor) => visitor.visitBlockStatement(block())};
|
||||
}
|
||||
|
||||
return expressionStatement();
|
||||
}
|
||||
|
||||
const expression = () => {
|
||||
return assignment();
|
||||
}
|
||||
|
||||
const expressions = () => {
|
||||
consume(LEFT_PAREN, "Expect '('");
|
||||
let exprs = [expression()];
|
||||
while (match(COMMA)) {
|
||||
exprs.push(expression());
|
||||
}
|
||||
consume(RIGHT_PAREN, "Expect ')'");
|
||||
return exprs;
|
||||
}
|
||||
|
||||
const assignment = () => {
|
||||
let expr = equality();
|
||||
if (match(EQUAL)) {
|
||||
let equals = previous();
|
||||
let value = assignment();
|
||||
|
||||
if (expr.class === 'Variable') {
|
||||
let name = expr.name;
|
||||
return {accept: (visitor) => visitor.visitAssignExpr(name, value)};
|
||||
}
|
||||
|
||||
throw error(equals, "Invalid assignment target.");
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
const printStatement = () => {
|
||||
const value = expression();
|
||||
consume(SEMICOLON, "Expected semicolon");
|
||||
return {accept: (visitor) => visitor.visitPrintStatement(value)};
|
||||
}
|
||||
|
||||
const callStatement = (name) => {
|
||||
const values = expressions();
|
||||
consume(SEMICOLON, "Expected semicolon");
|
||||
return {accept: (visitor) => visitor.visitCallStatement(name, values)};
|
||||
}
|
||||
|
||||
const expressionStatement = () => {
|
||||
const value = expression();
|
||||
consume(SEMICOLON, "Expected semicolon");
|
||||
return {accept: (visitor) => visitor.visitExpressionStatement(value)};
|
||||
}
|
||||
|
||||
const block = () => {
|
||||
let statements = [];
|
||||
while (!check(RIGHT_BRACE) && !is_at_end()) {
|
||||
statements.push(declaration());
|
||||
}
|
||||
consume(RIGHT_BRACE, "Expect '}' after block.");
|
||||
return statements;
|
||||
}
|
||||
|
||||
const equality = () => {
|
||||
let expr = comparison();
|
||||
while (match(BANG_EQUAL, EQUAL_EQUAL)) {
|
||||
let operator = previous();
|
||||
let right = comparison();
|
||||
expr = {accept: (visitor) => visitor.visitBinaryExpr(operator, expr, right)};
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
const comparison = () => {
|
||||
let expr = term();
|
||||
while (match(GREATER, GREATER_EQUAL, LESS, LESS_EQUAL)) {
|
||||
let operator = previous();
|
||||
let right = term();
|
||||
expr = {accept: (visitor) => visitor.visitBinaryExpr(operator, expr, right)};
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
const term = () => {
|
||||
let expr = factor();
|
||||
while (match(MINUS, PLUS)) {
|
||||
let operator = previous();
|
||||
let right = factor();
|
||||
expr = {accept: (visitor) => visitor.visitBinaryExpr(operator, expr, right)};
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
const factor = () => {
|
||||
let expr = unary();
|
||||
while (match(SLASH, STAR)) {
|
||||
let operator = previous();
|
||||
let right = unary();
|
||||
expr = {accept: (visitor) => visitor.visitBinaryExpr(operator, expr, right)};
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
const unary = () => {
|
||||
if (match(BANG, MINUS)) {
|
||||
let operator = previous();
|
||||
let right = unary();
|
||||
return {accept: (visitor) => visitor.visitUnaryExpr(operator, right)};
|
||||
}
|
||||
return primary();
|
||||
}
|
||||
|
||||
const primary = () => {
|
||||
if (match(FALSE)) {
|
||||
return {accept: (visitor) => visitor.visitLiteralExpr(false)};
|
||||
}
|
||||
if (match(TRUE)) {
|
||||
return {accept: (visitor) => visitor.visitLiteralExpr(true)};
|
||||
}
|
||||
if (check(NUMBER)) {
|
||||
advance();
|
||||
let number = parseFloat(previous().literal);
|
||||
return {accept: (visitor) => visitor.visitLiteralExpr(number)};
|
||||
}
|
||||
if (check(STRING)) {
|
||||
advance();
|
||||
let string = previous().literal;
|
||||
return {accept: (visitor) => visitor.visitLiteralExpr(string)};
|
||||
}
|
||||
if (match(IDENTIFIER)) {
|
||||
let identifier = previous();
|
||||
return {class: "Variable", accept: (visitor) => visitor.visitVariableExpr(identifier)};
|
||||
}
|
||||
if (match(LEFT_PAREN)) {
|
||||
let expr = expression();
|
||||
consume(RIGHT_PAREN, "Expect ')' after expression.");
|
||||
return {accept: (visitor) => visitor.visitGroupingExpr(expr)};
|
||||
}
|
||||
throw error(peek(), "Expect expression.");
|
||||
}
|
||||
|
||||
const consume = (type, message) => {
|
||||
if (check(type)) {
|
||||
return advance();
|
||||
}
|
||||
throw error(peek(), message);
|
||||
}
|
||||
|
||||
const match = (...types) => {
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
if (check(types[i])) {
|
||||
advance();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const check = (type) => {
|
||||
if (is_at_end()) {
|
||||
return false;
|
||||
}
|
||||
return peek().type === type;
|
||||
}
|
||||
|
||||
const advance = () => {
|
||||
if (!is_at_end()) {
|
||||
current++;
|
||||
}
|
||||
return previous();
|
||||
}
|
||||
|
||||
const is_at_end = () => {
|
||||
return peek().type === EOF;
|
||||
}
|
||||
|
||||
const peek = () => {
|
||||
return tokens[current];
|
||||
}
|
||||
|
||||
const previous = () => {
|
||||
return tokens[current - 1];
|
||||
}
|
||||
|
||||
return parse();
|
||||
}
|
||||
266
js/scanner.js
Normal file
266
js/scanner.js
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
export const scan = (source) => {
|
||||
const tokens = [];
|
||||
let start = 0;
|
||||
let current = 0;
|
||||
let line = 1;
|
||||
|
||||
const scan_tokens = () => {
|
||||
while (!is_at_end()) {
|
||||
start = current;
|
||||
scan_token();
|
||||
}
|
||||
|
||||
tokens.push({type: EOF, lexeme: "", line: line});
|
||||
return tokens;
|
||||
};
|
||||
|
||||
const is_at_end = () => {
|
||||
return current >= source.length;
|
||||
}
|
||||
|
||||
const advance = () => {
|
||||
return source.charAt(current++);
|
||||
}
|
||||
|
||||
const add_token = (type, literal) => {
|
||||
tokens.push({type: type, lexeme: source.substring(start, current), literal: literal, line: line});
|
||||
}
|
||||
|
||||
const scan_token = () => {
|
||||
let c = advance();
|
||||
switch (c) {
|
||||
case '(':
|
||||
add_token(LEFT_PAREN);
|
||||
break;
|
||||
case ')':
|
||||
add_token(RIGHT_PAREN);
|
||||
break;
|
||||
case '{':
|
||||
add_token(LEFT_BRACE);
|
||||
break;
|
||||
case '}':
|
||||
add_token(RIGHT_BRACE);
|
||||
break;
|
||||
case ',':
|
||||
add_token(COMMA);
|
||||
break;
|
||||
case '.':
|
||||
add_token(DOT);
|
||||
break;
|
||||
case '-':
|
||||
add_token(MINUS);
|
||||
break;
|
||||
case '+':
|
||||
add_token(PLUS);
|
||||
break;
|
||||
case ';':
|
||||
add_token(SEMICOLON);
|
||||
break;
|
||||
case '*':
|
||||
add_token(STAR);
|
||||
break;
|
||||
case '!':
|
||||
add_token(match('=') ? BANG_EQUAL : BANG);
|
||||
break;
|
||||
case '=':
|
||||
add_token(match('=') ? EQUAL_EQUAL : EQUAL);
|
||||
break;
|
||||
case '<':
|
||||
add_token(match('=') ? LESS_EQUAL : LESS);
|
||||
break;
|
||||
case '>':
|
||||
add_token(match('=') ? GREATER_EQUAL : GREATER);
|
||||
break;
|
||||
case '/':
|
||||
if (match('/')) {
|
||||
while (peek() !== '\n' && !is_at_end()) {
|
||||
advance();
|
||||
}
|
||||
} else {
|
||||
add_token(SLASH);
|
||||
}
|
||||
break;
|
||||
case ' ':
|
||||
case '\r':
|
||||
case '\t':
|
||||
break;
|
||||
case '\n':
|
||||
line++;
|
||||
break;
|
||||
case '"':
|
||||
string();
|
||||
break;
|
||||
default:
|
||||
if (is_digit(c)) {
|
||||
number();
|
||||
} else if (is_alpha(c)) {
|
||||
identifier();
|
||||
} else {
|
||||
throw error(line, "Unexpected character");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const identifier = () => {
|
||||
while (is_alphaNumeric(peek())) {
|
||||
advance();
|
||||
}
|
||||
let text = source.substring(start, current);
|
||||
let type = keywords.get(text);
|
||||
|
||||
if (type == null) {
|
||||
type = IDENTIFIER;
|
||||
}
|
||||
add_token(type);
|
||||
}
|
||||
|
||||
const is_alphaNumeric = (c) => {
|
||||
return is_alpha(c) || is_digit(c);
|
||||
}
|
||||
|
||||
const is_alpha = (c) => {
|
||||
return typeof c === 'string' && (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c === '_';
|
||||
}
|
||||
|
||||
const number = () => {
|
||||
while (is_digit(peek())) {
|
||||
advance();
|
||||
}
|
||||
|
||||
if (peek() === '.' && is_digit(peekNext())) {
|
||||
advance();
|
||||
}
|
||||
|
||||
while (is_digit(peek())) {
|
||||
advance();
|
||||
}
|
||||
|
||||
add_token(NUMBER, source.substring(start, current));
|
||||
}
|
||||
|
||||
const string = () => {
|
||||
while (peek() !== '"' && !is_at_end()) {
|
||||
if (peek() === '\n') {
|
||||
line++;
|
||||
}
|
||||
advance();
|
||||
}
|
||||
|
||||
if (is_at_end()) {
|
||||
throw new Error(error(line, "Unterminated string"));
|
||||
}
|
||||
|
||||
advance();
|
||||
|
||||
let value = source.substring(start + 1, current - 1);
|
||||
console.log("string "+value);
|
||||
add_token(STRING, value);
|
||||
}
|
||||
|
||||
const peekNext = () => {
|
||||
return source.charAt(current + 1);
|
||||
}
|
||||
|
||||
const is_digit = (c) => {
|
||||
return typeof c === 'string' && c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
const peek = () => {
|
||||
if (is_at_end()) {
|
||||
return '\0';
|
||||
}
|
||||
return source.charAt(current);
|
||||
}
|
||||
|
||||
const match = (expected) => {
|
||||
if (is_at_end()) {
|
||||
return false;
|
||||
}
|
||||
if (source.charAt(current) !== expected) {
|
||||
return false;
|
||||
}
|
||||
current++;
|
||||
return true;
|
||||
}
|
||||
|
||||
return scan_tokens();
|
||||
}
|
||||
|
||||
export const error = (token, message) => {
|
||||
if (token.type === EOF) {
|
||||
return report(token.line, " at end", message);
|
||||
} else {
|
||||
return report(token.line, "", message);
|
||||
}
|
||||
}
|
||||
|
||||
export const report = (line, where, message) => {
|
||||
return ("[line " + line + "] Error" + where + ": " + message);
|
||||
}
|
||||
|
||||
export const LEFT_PAREN = 1;
|
||||
export const RIGHT_PAREN = 2;
|
||||
export const LEFT_BRACE = 3;
|
||||
export const RIGHT_BRACE = 4;
|
||||
export const COMMA = 5;
|
||||
export const DOT = 6;
|
||||
export const MINUS = 7;
|
||||
export const PLUS = 8;
|
||||
export const SEMICOLON = 9;
|
||||
export const SLASH = 10;
|
||||
export const STAR = 11;
|
||||
export const BANG = 12;
|
||||
export const BANG_EQUAL = 13;
|
||||
export const EQUAL = 14;
|
||||
export const EQUAL_EQUAL = 15;
|
||||
export const GREATER = 16;
|
||||
export const GREATER_EQUAL = 17;
|
||||
export const LESS = 18;
|
||||
export const LESS_EQUAL = 19;
|
||||
export const IDENTIFIER = 20;
|
||||
export const STRING = 21;
|
||||
export const NUMBER = 22;
|
||||
export const AND = 23;
|
||||
export const ELSE = 24;
|
||||
export const FALSE = 25;
|
||||
export const FUN = 26;
|
||||
export const FOR = 27;
|
||||
export const IF = 28;
|
||||
export const OR = 30;
|
||||
export const PRINT = 31;
|
||||
export const RETURN = 32;
|
||||
export const TRUE = 33;
|
||||
export const VAR = 34;
|
||||
export const WHILE = 35;
|
||||
export const EOF = 36;
|
||||
export const START = 37;
|
||||
export const GO = 38;
|
||||
export const TURN = 39;
|
||||
export const LEFT = 40;
|
||||
export const RIGHT = 41;
|
||||
export const PILLARS = 42;
|
||||
export const MOVING_PILLARS = 43;
|
||||
export const STAIRCASE = 44;
|
||||
|
||||
export const keywords = new Map([
|
||||
["and", AND],
|
||||
["else", ELSE],
|
||||
["false", FALSE],
|
||||
["fun", FUN],
|
||||
["for", FOR],
|
||||
["if", IF],
|
||||
["or", OR],
|
||||
["print", PRINT],
|
||||
["return", RETURN],
|
||||
["true", TRUE],
|
||||
["var", VAR],
|
||||
["while", WHILE],
|
||||
["start", START],
|
||||
["go", GO],
|
||||
["turn", TURN],
|
||||
["left", LEFT],
|
||||
["right", RIGHT],
|
||||
["pillars", PILLARS],
|
||||
["moving_pillars", MOVING_PILLARS],
|
||||
["staircase", STAIRCASE]
|
||||
])
|
||||
0
js/vendor/.gitkeep
vendored
Normal file
0
js/vendor/.gitkeep
vendored
Normal file
4494
package-lock.json
generated
Normal file
4494
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
24
package.json
Normal file
24
package.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": " ",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"keywords": [
|
||||
""
|
||||
],
|
||||
"license": "",
|
||||
"author": "",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "webpack serve --open --config webpack.config.dev.js",
|
||||
"build": "webpack --config webpack.config.prod.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^5.0.4",
|
||||
"webpack-merge": "^5.10.0"
|
||||
}
|
||||
}
|
||||
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
szpakowski.png
Normal file
BIN
szpakowski.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
26
szpaks/radiator.szpak
Normal file
26
szpaks/radiator.szpak
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
start(-54,20);
|
||||
go(1);
|
||||
for (let i=0; i<4; i++){
|
||||
pillars(13, 2, 1, UP);
|
||||
}
|
||||
left(90);
|
||||
go(18);
|
||||
left(90);
|
||||
go(1)
|
||||
for (let i=0; i<4; i++){
|
||||
pillars(13, 2, 1, UP);
|
||||
}
|
||||
left(90);
|
||||
go(37);
|
||||
left(90);
|
||||
go(1)
|
||||
for (let i=0; i<4; i++){
|
||||
pillars(13, 2, 1, UP);
|
||||
}
|
||||
left(90);
|
||||
go(18);
|
||||
left(90);
|
||||
go(1)
|
||||
for (let i=0; i<4; i++){
|
||||
pillars(13, 2, 1, UP);
|
||||
}
|
||||
12
webpack.common.js
Normal file
12
webpack.common.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
app: './js/app.js',
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
clean: true,
|
||||
filename: './js/app.js',
|
||||
},
|
||||
};
|
||||
13
webpack.config.dev.js
Normal file
13
webpack.config.dev.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
const { merge } = require('webpack-merge');
|
||||
const common = require('./webpack.common.js');
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'development',
|
||||
devtool: 'inline-source-map',
|
||||
devServer: {
|
||||
liveReload: true,
|
||||
hot: true,
|
||||
open: true,
|
||||
static: ['./'],
|
||||
},
|
||||
});
|
||||
25
webpack.config.prod.js
Normal file
25
webpack.config.prod.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
const { merge } = require('webpack-merge');
|
||||
const common = require('./webpack.common.js');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'production',
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: './index.html',
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{ from: 'img', to: 'img' },
|
||||
{ from: 'css', to: 'css' },
|
||||
{ from: 'js/vendor', to: 'js/vendor' },
|
||||
{ from: 'icon.svg', to: 'icon.svg' },
|
||||
{ from: 'favicon.ico', to: 'favicon.ico' },
|
||||
{ from: 'robots.txt', to: 'robots.txt' },
|
||||
{ from: 'icon.png', to: 'icon.png' },
|
||||
{ from: 'site.webmanifest', to: 'site.webmanifest' },
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue