brushed up

This commit is contained in:
Shautvast 2026-04-03 10:52:21 +02:00
parent a92f19180e
commit 8f8a860dbb
6 changed files with 163 additions and 129 deletions

1
.gitignore vendored
View file

@ -5,3 +5,4 @@ node_modules
dist dist
.cache .cache
.idea/ .idea/
.DS_Store

17
Dockerfile Normal file
View file

@ -0,0 +1,17 @@
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View file

@ -1,14 +1,27 @@
html { html {
color: #222;
background: rgb(249,235,213); background: rgb(249,235,213);
font-size: 1em; font-size: 1em;
line-height: 1.4; line-height: 1.4;
overflow: hidden; overflow: hidden;
} }
canvas {
color: black;
}
a {
font: 13px monospace, sans-serif;
color: black;
position: fixed;
left: 10px;
bottom: 10px;
}
#console { #console {
font: 13px monospace, sans-serif; font: 13px monospace, sans-serif;
padding: 5px; padding: 5px;
color: black; color: #222;
background: rgb(255,241,219); background: rgb(255,241,219);
position: fixed; position: fixed;
right: 1px; right: 1px;
@ -18,6 +31,7 @@ html {
border: 2px solid darkgray; border: 2px solid darkgray;
border-radius: 10px; border-radius: 10px;
z-index: 0; z-index: 0;
overflow: auto;
} }
#help{ #help{
@ -45,7 +59,7 @@ html {
#log{ #log{
font: 13px monospace, sans-serif; font: 13px monospace, sans-serif;
padding: 5px; padding: 5px;
color: black; color: #222;
background: rgb(255,241,219); background: rgb(255,241,219);
position: fixed; position: fixed;
right: 1px; right: 1px;

View file

@ -28,6 +28,7 @@ moving_pillars(5, 10,1, false);
</textarea> </textarea>
</label> </label>
</div> </div>
<a href="https://git.sanderhautvast.nl/sander/szpakowski-lang">https://git.sanderhautvast.nl/sander/szpakowski-lang</a>
<span id="help" style="visibility: hidden"></span> <span id="help" style="visibility: hidden"></span>

253
js/app.js
View file

@ -2,146 +2,149 @@
import {interpret} from "./interpreter.js"; import {interpret} from "./interpreter.js";
(function () { (function () {
const command_input_element = document.getElementById('command_input'); const command_input_element = document.getElementById('command_input');
const canvas_element = document.getElementById('canvas'); const canvas_element = document.getElementById('canvas');
const canvas = canvas_element.getContext('2d'); const canvas = canvas_element.getContext('2d');
const width = window.innerWidth; const width = window.innerWidth;
const height = window.innerHeight; const height = window.innerHeight;
canvas.clearRect(0, 0, width, height); canvas.clearRect(0, 0, width, height);
canvas.lineWidth = 2; canvas.lineWidth = 2;
canvas.globalAlpha = 1; canvas.globalAlpha = 1;
canvas.strokeStyle = 'black'; canvas.strokeStyle = '#000';
canvas.fillStyle = 'black'; canvas_element.width = width;
canvas_element.width = width; canvas_element.height = height;
canvas_element.height = height;
const help_element = document.getElementById('help'); const help_element = document.getElementById('help');
let script; let script;
let tx = 0; let tx = 0;
let ty = 0; let ty = 0;
let tangle = 0; let tangle = 0;
document.body.onkeydown = function (event) { document.body.onkeydown = function (event) {
if (event.key === 'Tab') { if (event.key === 'Tab') {
event.preventDefault(); event.preventDefault();
} }
}
const count = function (string, substring) {
if (!substring) {
return 0;
}
let count = 0;
let pos = string.indexOf(substring);
while (pos !== -1) {
count++;
pos = string.indexOf(substring, pos + substring.length);
} }
const count = function (string, substring) { return count;
if (!substring) { };
return 0;
}
let count = 0;
let pos = string.indexOf(substring);
while (pos !== -1) { const help = [
count++; {v1: "start(x,y)", v2: "start(0,0);"},
pos = string.indexOf(substring, pos + substring.length); {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 = 1, direction = DOWN)", v2: "moving_pillars(3, 10, 1, DOWN);"},
{v1: "staircase(number, size, direction = DOWN)", v2: "staircase(3, 1, DOWN);"},
{v1: "repeat([start], end){...}", v2: "repeat(5){\n \n}"},
{v1: "random(range-start, range-end)", v2: "random(0,1);"},
{v1: "sin(alpha)", v2: "sin();"},
{v1: "cos(alpha)", v2: "cos();"},
{v1: "tan(alpha)", v2: "tan();"},
{v1: "atan(tangent)", v2: "atan();"},
];
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]);
return count;
};
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 = 1, direction = DOWN)", v2: "moving_pillars(3, 10, 1, DOWN);"},
{v1: "staircase(number, size, direction = DOWN)", v2: "staircase(3, 1, DOWN);"},
{v1: "repeat([start], end){...}", v2: "repeat(5){\n \n}"},
{v1: "random(range-start, range-end)", v2: "random(0,1);"},
{v1: "sin(alpha)", v2: "sin();"},
{v1: "cos(alpha)", v2: "cos();"},
{v1: "tan(alpha)", v2: "tan();"},
{v1: "atan(tangent)", v2: "atan();"},
];
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]];
} }
slices[help[i].v1] = [help[i]];
}
function refresh() { function refresh() {
script = command_input_element.value; canvas.beginPath();
try { canvas.lineWidth = 1.8;
canvas.clearRect(0, 0, width, height); canvas.moveTo(0, 0);
interpret({canvas: canvas, x: tx, y: ty, angle: tangle, unit: 10, width: width, height: height}, script); script = command_input_element.value;
} catch (e) { try {
document.getElementById('log').innerHTML = e; canvas.clearRect(0, 0, width, height);
} interpret({canvas: canvas, x: tx, y: ty, angle: tangle, unit: 10, width: width, height: height}, script);
canvas.stroke();
} catch (e) {
document.getElementById('log').innerHTML = e;
} }
}
command_input_element.onkeyup = function handle_key_input(event) { command_input_element.onkeyup = function handle_key_input(event) {
if (event.key === 'Tab') { if (event.key === 'Tab') {
const script = command_input_element.value; const script = command_input_element.value;
event.preventDefault(); event.preventDefault();
const cursor_position = command_input_element.selectionStart; const cursor_position = command_input_element.selectionStart;
const textUpToCursor = script.slice(0, cursor_position); const textUpToCursor = script.slice(0, cursor_position);
const startOfLine = textUpToCursor.lastIndexOf('\n') + 1; const startOfLine = textUpToCursor.lastIndexOf('\n') + 1;
const endOfLine = script.indexOf('\n', cursor_position); const endOfLine = script.indexOf('\n', cursor_position);
const lineEndPosition = endOfLine === -1 ? script.length : endOfLine; const lineEndPosition = endOfLine === -1 ? script.length : endOfLine;
const currentLineText = script.slice(startOfLine, lineEndPosition).trim(); const currentLineText = script.slice(startOfLine, lineEndPosition).trim();
if (slices[currentLineText]) { if (slices[currentLineText]) {
const helpOptions = slices[currentLineText].map(x => x.v2); const helpOptions = slices[currentLineText].map(x => x.v2);
const firstOption = helpOptions[0]; 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();
}
}
if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
script = command_input_element.value;
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'; 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);
let help_text = "";
for (let x = 0; x < help.length; x++) {
help_text += `<div id="l-${x}">${help[x]}</div>`;
}
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 + (count(script, '\n') + 4) * lineHeight;
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();
} }
} }
//main if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
refresh(); 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);
let help_text = "";
for (let x = 0; x < help.length; x++) {
help_text += `<div id="l-${x}">${help[x]}</div>`;
}
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 + (count(script, '\n') + 4) * lineHeight;
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();
}
}
//main
refresh();
})
(); ();

View file

@ -133,7 +133,6 @@ export function interpret(init_env, code) {
tx = x * unit; tx = x * unit;
ty = y * unit; ty = y * unit;
tangle = 0; tangle = 0;
canvas.beginPath();
canvas.moveTo(x0 + tx, y0 + ty); canvas.moveTo(x0 + tx, y0 + ty);
}, },
@ -141,7 +140,6 @@ export function interpret(init_env, code) {
tx += distance * unit * Math.cos(tangle); tx += distance * unit * Math.cos(tangle);
ty += distance * unit * Math.sin(tangle); ty += distance * unit * Math.sin(tangle);
canvas.lineTo(x0 + tx, y0 + ty); canvas.lineTo(x0 + tx, y0 + ty);
canvas.stroke();
}, },
turn: (degrees) => { turn: (degrees) => {