From 6dade59bccbde73b0c95e0bcc564dcbe6cb10d2e Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 21 Aug 2024 21:29:38 +0200 Subject: [PATCH] Add initial version of Numbat editor interface --- numbat-wasm/www/editor.css | 115 ++++++++++++++++++++++++++ numbat-wasm/www/editor.html | 31 +++++++ numbat-wasm/www/editor.js | 160 ++++++++++++++++++++++++++++++++++++ 3 files changed, 306 insertions(+) create mode 100644 numbat-wasm/www/editor.css create mode 100644 numbat-wasm/www/editor.html create mode 100644 numbat-wasm/www/editor.js diff --git a/numbat-wasm/www/editor.css b/numbat-wasm/www/editor.css new file mode 100644 index 00000000..4869109f --- /dev/null +++ b/numbat-wasm/www/editor.css @@ -0,0 +1,115 @@ +@import url('https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;700&family=Lustria&family=Lato:wght@700&display=swap'); + +body, +html { + margin: 0; + padding: 0; + height: 100%; + font-size: 30px; +} + +.split { + width: 100%; + height: 100%; + display: flex; + flex-direction: row; +} + +.gutter { + background-color: #eee; + background-repeat: no-repeat; + background-position: 50%; +} + +.gutter.gutter-horizontal { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); + cursor: col-resize; +} + +#editor { + font-family: 'Fira Mono', monospace; + font-size: 1em; +} + + +.type-expr { + font-family: 'Fira Mono', monospace; + font-size: 1em; + margin-top: 0em; + margin-bottom: 0; + padding: 1em; + overflow: auto; + display: block; +} + +#results { + font-family: 'Fira Mono', monospace; + font-size: 1em; + padding: 0.1em; + background-color: #f6f8f6; + overflow: auto; +} + +.numbat-type-identifier { + color: #ca3b63; +} + +.ace_type { + color: #ca3b63 !important; +} + +.numbat-value { + color: #0040ff; +} + +.ace_numeric { + color: #0040ff !important; +} + +.numbat-keyword { + color: #c802ff !important; +} + +.ace_keyword { + color: #c802ff !important; +} + +.ace_comment { + color: #8c8c8c !important; +} + +.ace_string { + color: #27a85f !important; +} + +.ace_decorator { + color: #27a85f !important; +} + +.numbat-string { + color: #27a85f; +} + +.ace_operator { + color: #db2828 !important; +} + +.numbat-unit { + color: #5c12a6; +} + +.numbat-dimmed { + color: #888; +} + +.numbat-diagnostic-red { + color: #cc3b0a; +} + +.numbat-diagnostic-blue { + color: #0040ff; +} + +.numbat-diagnostic-bold { + font-weight: bold; +} diff --git a/numbat-wasm/www/editor.html b/numbat-wasm/www/editor.html new file mode 100644 index 00000000..c9926b41 --- /dev/null +++ b/numbat-wasm/www/editor.html @@ -0,0 +1,31 @@ + + + + + + Numbat + + + +
+
8 km / (1 h + 25 min) + +atan2(30 cm, 1 m) -> deg + +let ω = 2π c / 660 nm +ℏ ω -> eV + + +fn braking_distance(v) = v t_reaction + v² / 2 µ g0 + where t_reaction = 1 s # driver reaction time + and µ = 0.7 # coefficient of friction + +braking_distance(50 km/h) -> m
+
+
+ + + + + + diff --git a/numbat-wasm/www/editor.js b/numbat-wasm/www/editor.js new file mode 100644 index 00000000..d5520de6 --- /dev/null +++ b/numbat-wasm/www/editor.js @@ -0,0 +1,160 @@ +import init, { + setup_panic_hook, + Numbat, + FormatType, +} from "./pkg/numbat_wasm.js"; + +async function main() { + await init(); + + setup_panic_hook(); + + initializeEditor(); + initializeSplit(); +} + +main(); + +function interpret(input) { + var numbat = Numbat.new(true, false, FormatType.Html); + + let parts = input.split("\n\n"); + + let results = parts.map((part) => { + let res = part.match(/\n/g); + let num_newlines = res ? res.length : 0; + + let brs = "
".repeat(num_newlines); + + return brs + numbat.interpret(part).output; + }); + + return results.join("

"); +} + +ace.config.set("basePath", "https://cdnjs.cloudflare.com/ajax/libs/ace/1.9.6/"); + +ace.define( + "ace/mode/numbat_highlight_rules", + function (require, exports, module) { + var oop = require("ace/lib/oop"); + var TextHighlightRules = + require("ace/mode/text_highlight_rules").TextHighlightRules; + + var NumbatHighlightRules = function () { + this.$rules = { + start: [ + { + token: "comment", + regex: "#.*$", + }, + { + token: "keyword", + regex: + "\\b(?:per|to|let|fn|where|and|dimension|unit|use|long|short|both|none|print|assert|assert_eq|type|if|then|else|true|false)\\b", + }, + { + token: "constant.numeric", + regex: + "\\b(?:[0-9]+(?:[._][0-9]+)*" + + "|0x[0-9A-Fa-f]+|0o[0-7]+|0b[01]+(?:_[01]+)*" + + "|\\.[0-9]+(?:[eE][-+]?[0-9]+)?" + + "|[0-9]+[eE][-+]?[0-9]+)\\b", + }, + // { + // token: "variable", + // regex: "\\b[a-z\u00B0\u0394\u03C0\u00B5][A-Za-z0-9_\u00B0\u0394\u03C0\u00B5]*\\b" + // }, + { + token: "support.type", + regex: "\\b[A-Z][A-Za-z]*\\b", + }, + { + token: "string", + regex: '"[^"]*"', + }, + { + token: "keyword.operator", + regex: "\\+|-|\\*|/|\\^|➞|→|:|÷|×|≤|≥|≠|<|>|²|³|\\(|\\)", + }, + { + token: "meta.decorator", + regex: "@[\\w_]+", + }, + ], + }; + }; + + oop.inherits(NumbatHighlightRules, TextHighlightRules); + + exports.NumbatHighlightRules = NumbatHighlightRules; + }, +); + +ace.define("ace/mode/numbat", function (require, exports, module) { + var oop = require("ace/lib/oop"); + var TextMode = require("ace/mode/text").Mode; + var NumbatHighlightRules = + require("ace/mode/numbat_highlight_rules").NumbatHighlightRules; + + var Mode = function () { + this.HighlightRules = NumbatHighlightRules; + }; + oop.inherits(Mode, TextMode); + + (function () { + this.lineCommentStart = "#"; + + this.$id = "ace/mode/numbat"; + }).call(Mode.prototype); + + exports.Mode = Mode; +}); + +function debounce(func, wait, immediate) { + var timeout; + return function () { + var context = this, + args = arguments; + var later = function () { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; +} + +function initializeEditor() { + const editor = ace.edit("editor", { + mode: "ace/mode/numbat", + showPrintMargin: false, + showGutter: true, + highlightActiveLine: false, + highlightGutterLine: false, + }); + + function evaluate() { + let code = editor.getValue(); + + let output = interpret(code); + + document.getElementById("results").innerHTML = output; + } + + var debouncedEvaluate = debounce(evaluate, 500); + + editor.getSession().on("change", debouncedEvaluate); + + evaluate(); + + editor.focus(); +} + +function initializeSplit() { + Split(['#editor', '#results'], { + gutterSize: 15, + }); +}