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