From 21daf0d4be7c8af1f1546e43461c70b00dda64ea Mon Sep 17 00:00:00 2001 From: Shautvast Date: Mon, 9 Dec 2024 22:22:57 +0100 Subject: [PATCH] lots of things --- css/style.css | 50 ++++++---- index.html | 4 +- js/app.js | 235 +++++++++++++++++++++++++--------------------- js/functions.js | 19 ++++ js/interpreter.js | 23 ++++- js/parser.js | 23 ++++- js/scanner.js | 29 ++++-- 7 files changed, 246 insertions(+), 137 deletions(-) create mode 100644 js/functions.js diff --git a/css/style.css b/css/style.css index 3ed000d..8c28db6 100644 --- a/css/style.css +++ b/css/style.css @@ -1,48 +1,64 @@ html { - color: #222; background: rgb(249,235,213); font-size: 1em; line-height: 1.4; + overflow: hidden; } #console { font: 13px monospace, sans-serif; padding: 5px; - color: greenyellow; - background: black; + color: black; + background: rgb(255,241,219); position: fixed; - right: 10px; - bottom: 0; - width: 30%; - height: 20em; + right: 1px; + bottom: 4em; + width: 30em; + height: 90%; 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; + background: rgb(249,235,213); + font: 13px monospace, sans-serif; + padding-left: 5px; + width: 90%; + color: black; z-index: 2; + scroll-behavior: auto; + overflow: auto; + border: none transparent; } #command_input { - background: black; - color: greenyellow; + background: rgb(255,241,219); + color: black; outline: none; width: 90%; - height: 20em; + height: 99%; resize: none; } +#log{ + font: 13px monospace, sans-serif; + padding: 5px; + color: black; + background: rgb(255,241,219); + position: fixed; + right: 1px; + bottom: 0; + width: 40em; + height: 2em; + border: 2px solid darkgray; + border-radius: 10px; + z-index: 0; +} + .multiline { border: none transparent; - border-top-left-radius: 3px; - border-top-right-radius: 3px; } .single_line { diff --git a/index.html b/index.html index 19f56e7..baa8267 100644 --- a/index.html +++ b/index.html @@ -29,10 +29,10 @@ moving_pillars(5, 10,1, false); - + +
- diff --git a/js/app.js b/js/app.js index f9031a5..d10cd90 100644 --- a/js/app.js +++ b/js/app.js @@ -2,121 +2,146 @@ 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 command_input_element = document.getElementById('command_input'); + const canvas_element = document.getElementById('canvas'); + const canvas = canvas_element.getContext('2d'); + const width = window.innerWidth; + const height = window.innerHeight; + canvas.clearRect(0, 0, width, height); + canvas.lineWidth = 2; + canvas.globalAlpha = 1; + canvas.strokeStyle = 'black'; + canvas.fillStyle = 'black'; + canvas_element.width = width; + canvas_element.height = height; - const help_element = document.getElementById('help'); - let script; + const help_element = document.getElementById('help'); + let script; + let tx = 0; + let ty = 0; + let tangle = 0; - 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);"}, - {v1: "repeat([start], end){...}", v2: "repeat(5){\n}"}, - ]; - 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] = []; + document.body.onkeydown = function (event) { + if (event.key === 'Tab') { + event.preventDefault(); } - slices[sub].push(help[i]); } - slices[help[i].v1] = [help[i]]; - } - let tx = 0; - let ty = 0; - let tangle = 0; + const count = function (string, substring) { + if (!substring) { + return 0; + } + let count = 0; + let pos = string.indexOf(substring); - 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(); + while (pos !== -1) { + count++; + pos = string.indexOf(substring, pos + substring.length); + } - if (slices[currentLineText]) { - const helpOptions = slices[currentLineText].map(x => x.v2); - const firstOption = helpOptions[0]; + 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]]; + } + + 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: width, height: height}, script); + } catch (e) { + document.getElementById('log').innerHTML = e; + } + } + + command_input_element.onkeyup = function handle_key_input(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(); + } + } + 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'; + 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 += `
${help[x]}
`; + } + + 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(); + }) +(); - 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:width, height: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(); -})(); diff --git a/js/functions.js b/js/functions.js new file mode 100644 index 0000000..8311be6 --- /dev/null +++ b/js/functions.js @@ -0,0 +1,19 @@ +export function random(s, e = 0) { + return Math.random() * (e - s) + s; +} + +export function cos(a) { + return Math.cos(a); +} + +export function sin(a) { + return Math.sin(a); +} + +export function tan(a) { + return Math.tan(a); +} + +export function atan(a) { + return Math.atan(a); +} diff --git a/js/interpreter.js b/js/interpreter.js index 2d2f95c..95edb9c 100644 --- a/js/interpreter.js +++ b/js/interpreter.js @@ -12,6 +12,7 @@ import { SLASH, STAR } from "./scanner"; +import {atan, cos, random, sin, tan} from "./functions"; export function interpret(init_env, code) { @@ -110,6 +111,22 @@ export function interpret(init_env, code) { } }, + visitCallFunction: (name, argList) => { + let args = argList.map(THIS.evaluate); + switch (name) { + case "random": + return random(...args); + case "cos": + return cos(...args); + case "sin": + return sin(...args); + case "tan": + return tan(...args); + case "atan": + return atan(...args); + } + }, + start: (x, y) => { tx = x * unit; ty = y * unit; @@ -159,11 +176,11 @@ export function interpret(init_env, code) { length += shift; for (let i = 0; i < n; i++) { THIS.turn(-90); - THIS.go(direction === DOWN ? -length2 : length2); + THIS.go(direction === UP ? -length2 : length2); THIS.turn(90); THIS.go(1); THIS.turn(90); - THIS.go(direction === DOWN ? -length : length); + THIS.go(direction === UP ? -length : length); THIS.turn(-90); THIS.go(1); } @@ -269,6 +286,8 @@ export function interpret(init_env, code) { const statements = parse(code); for (let i = 0; i < statements.length; i++) { + // console.log(statements[i]); THIS.execute(statements[i]); } } + diff --git a/js/parser.js b/js/parser.js index da3c61d..f416a3d 100644 --- a/js/parser.js +++ b/js/parser.js @@ -36,10 +36,12 @@ import { LEFT, RIGHT, TURN, - REPEAT + REPEAT, functions, } from "./scanner"; export function parse(code) { + const log_console = document.getElementById("log"); + const tokens = scan(code); // console.log(tokens); let current = 0; @@ -48,10 +50,12 @@ export function parse(code) { let statements = []; while (!is_at_end()) { + log_console.innerHTML = "OK"; try { statements.push(declaration()); } catch (e) { - console.log(e); + // console.log(e); + log_console.innerHTML = e; current = tokens.length - 1; // stop compiling } } @@ -163,7 +167,7 @@ export function parse(code) { const callStatement = (name) => { const args = arg_expressions(); consume(SEMICOLON, "Expected semicolon"); - return {class: "call", accept: (visitor) => visitor.visitCallStatement(name, args)}; + return {class: "call_stmt", accept: (visitor) => visitor.visitCallStatement(name, args)}; } const call_block = () => { @@ -271,6 +275,16 @@ export function parse(code) { accept: (visitor) => visitor.visitUnaryExpr(operator, right) }; } + return fun(); + } + + const fun = () => { + let function_name = peek().lexeme; + if (functions.has(function_name)) { + advance(); + let args = arg_expressions(); + return {class: "call_fun", accept: (visitor) => visitor.visitCallFunction(function_name, args)}; + } return primary(); } @@ -307,7 +321,7 @@ export function parse(code) { if (check(type)) { return advance(); } - throw error(peek(), message); + throw error(peek(), message + " but was " + peek().lexeme); } const match = (...types) => { @@ -322,7 +336,6 @@ export function parse(code) { } const check = (type) => { - // console.log(peek().type+"==="+type); if (is_at_end()) { return false; } diff --git a/js/scanner.js b/js/scanner.js index 4247590..9d5e5dc 100644 --- a/js/scanner.js +++ b/js/scanner.js @@ -10,7 +10,7 @@ export const scan = (source) => { scan_token(); } - tokens.push({type: EOF, lexeme: "", line: line}); + tokens.push({type: EOF, lexeme: "EOF", line: line}); return tokens; }; @@ -107,8 +107,10 @@ export const scan = (source) => { } let text = source.substring(start, current); let type = keywords.get(text); - - if (type == null) { + if (type === undefined) { + type = functions.get(text); + } + if (type === undefined) { type = IDENTIFIER; } add_token(type); @@ -153,7 +155,7 @@ export const scan = (source) => { advance(); let value = source.substring(start + 1, current - 1); - console.log("string "+value); + // console.log("string " + value); add_token(STRING, value); } @@ -172,7 +174,7 @@ export const scan = (source) => { return source.charAt(current); } - const match = (expected) => { + const match = (expected) => { if (is_at_end()) { return false; } @@ -242,6 +244,11 @@ export const PILLARS = 42; export const MOVING_PILLARS = 43; export const STAIRCASE = 44; export const REPEAT = 45; +export const RANDOM = 46; +export const COS = 47; +export const SIN = 48; +export const TAN = 49; +export const ATAN = 50; export const keywords = new Map([ ["and", AND], @@ -265,4 +272,14 @@ export const keywords = new Map([ ["moving_pillars", MOVING_PILLARS], ["staircase", STAIRCASE], ["repeat", REPEAT], -]) + ["random", RANDOM], +]); + +export const functions = new Map([ + ["random", RANDOM], + ["cos", COS], + ["sin", SIN], + ["tan", TAN], + ["atan", ATAN], +]); +