mirror of
https://github.com/remko/waforth
synced 2024-12-27 09:59:29 +01:00
vscode-extension: Import
This commit is contained in:
parent
cd6e712b75
commit
4f154243b5
20 changed files with 789 additions and 18 deletions
15
.github/workflows/build-vscode-extension.yml
vendored
Normal file
15
.github/workflows/build-vscode-extension.yml
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
name: Build VS Code Extension
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ./.github/actions/setup
|
||||
- run: make -C src/web/vscode-extension install-deps package
|
17
.vscode/launch.json
vendored
Normal file
17
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Run VS Code Extension",
|
||||
"type": "extensionHost",
|
||||
"debugWebviews": true,
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/src/web/vscode-extension",
|
||||
"${workspaceFolder}/src/web/vscode-extension/examples"
|
||||
],
|
||||
"outFiles": ["${workspaceFolder}/src/web/vscode-extension/dist/*.js"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
"@types/file-saver": "^2.0.5",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/node": "^17.0.31",
|
||||
"@types/vscode": "^1.73.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.5",
|
||||
"@typescript-eslint/parser": "^5.30.5",
|
||||
"chai": "^4.3.6",
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import * as jsx from "./jsx";
|
||||
import WAForth from "waforth";
|
||||
import WAForth, { ErrorCode } from "waforth";
|
||||
import thurtleFS from "./thurtle.fs";
|
||||
import turtle from "./turtle.svg";
|
||||
|
||||
const padding = 0.05;
|
||||
const padding = 0.025;
|
||||
|
||||
enum PenState {
|
||||
Up = 0,
|
||||
|
@ -18,14 +17,16 @@ type Path = {
|
|||
export default async function draw({
|
||||
program,
|
||||
drawEl,
|
||||
outputEl,
|
||||
onEmit,
|
||||
showTurtle = true,
|
||||
jsx,
|
||||
}: {
|
||||
program?: string;
|
||||
drawEl: SVGSVGElement;
|
||||
outputEl?: HTMLElement;
|
||||
onEmit?: (c: string) => void;
|
||||
showTurtle?: boolean;
|
||||
}) {
|
||||
jsx: any;
|
||||
}): Promise<ErrorCode | null> {
|
||||
// Initialize state
|
||||
let rotation = 270;
|
||||
const position = { x: 0, y: 0 };
|
||||
|
@ -52,6 +53,7 @@ export default async function draw({
|
|||
}
|
||||
|
||||
// Run program
|
||||
let result: ErrorCode | null = null;
|
||||
if (program != null) {
|
||||
const forth = new WAForth();
|
||||
await forth.load();
|
||||
|
@ -97,15 +99,10 @@ export default async function draw({
|
|||
});
|
||||
|
||||
forth.interpret(thurtleFS);
|
||||
if (outputEl != null) {
|
||||
forth.onEmit = (c) => {
|
||||
outputEl.appendChild(document.createTextNode(c));
|
||||
if (c === "\n") {
|
||||
outputEl.scrollTop = outputEl.scrollHeight;
|
||||
if (onEmit != null) {
|
||||
forth.onEmit = onEmit;
|
||||
}
|
||||
};
|
||||
}
|
||||
forth.interpret(program, true);
|
||||
result = forth.interpret(program, true);
|
||||
}
|
||||
|
||||
// Draw
|
||||
|
@ -119,6 +116,7 @@ export default async function draw({
|
|||
data-hook="turtle"
|
||||
width="50"
|
||||
height="50"
|
||||
style={{}}
|
||||
href={turtle}
|
||||
/>
|
||||
);
|
||||
|
@ -143,6 +141,23 @@ export default async function draw({
|
|||
} ${position.y - 25})`
|
||||
);
|
||||
|
||||
// If we have a turtle, expand the view so that the entire turtle is in sight
|
||||
// This looks better than just adjusting the bounding box
|
||||
if (showTurtle && visible) {
|
||||
const extendX = Math.max(
|
||||
boundingBox.minX - Math.min(boundingBox.minX, position.x - 25),
|
||||
Math.max(boundingBox.maxX, position.x + 25) - boundingBox.maxX
|
||||
);
|
||||
const extendY = Math.max(
|
||||
boundingBox.minY - Math.min(boundingBox.minY, position.y - 25),
|
||||
Math.max(boundingBox.maxY, position.y + 25) - boundingBox.maxY
|
||||
);
|
||||
boundingBox.maxX += extendX;
|
||||
boundingBox.minX -= extendX;
|
||||
boundingBox.maxY += extendY;
|
||||
boundingBox.minY -= extendY;
|
||||
}
|
||||
|
||||
const width = boundingBox.maxX - boundingBox.minX;
|
||||
const height = boundingBox.maxY - boundingBox.minY;
|
||||
if (width == 0 || height == 0) {
|
||||
|
@ -165,4 +180,6 @@ export default async function draw({
|
|||
if (showTurtle) {
|
||||
drawEl.appendChild(turtleEl);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
43
src/web/thurtle/jsjsx.ts
Normal file
43
src/web/thurtle/jsjsx.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
// Simple version of JSX that does not require a DOM
|
||||
|
||||
function elementToHTML(el: any) {
|
||||
const out: string[] = [];
|
||||
out.push("<", el._tag);
|
||||
for (const [k, v] of Object.entries(el)) {
|
||||
if (typeof v == "function" || k.startsWith("_") || k === "innerHTML") {
|
||||
continue;
|
||||
}
|
||||
if (k === "style" && typeof v !== "string") {
|
||||
// TODO
|
||||
continue;
|
||||
}
|
||||
out.push(" ", k, '="', v as string, '"');
|
||||
}
|
||||
out.push(">");
|
||||
for (const child of el._children) {
|
||||
out.push(elementToHTML(child));
|
||||
}
|
||||
out.push("</", el._tag, ">");
|
||||
return out.join("");
|
||||
}
|
||||
class JSJSX {
|
||||
createElement(tag: any, props: any = {}, ...children: any[]) {
|
||||
return {
|
||||
_tag: tag,
|
||||
_children: children ?? [],
|
||||
...props,
|
||||
appendChild(c: any) {
|
||||
this._children.push(c);
|
||||
},
|
||||
setAttribute(k: any, v: any) {
|
||||
this[k] = v;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
toHTML(el: any) {
|
||||
return elementToHTML(el);
|
||||
}
|
||||
}
|
||||
|
||||
export default JSJSX;
|
|
@ -458,7 +458,12 @@ async function getSVG(): Promise<{
|
|||
height: number;
|
||||
}> {
|
||||
const svgEl = <svg xmlns="http://www.w3.org/2000/svg" />;
|
||||
await draw({ program: editor.getValue(), drawEl: svgEl, showTurtle: false });
|
||||
await draw({
|
||||
program: editor.getValue(),
|
||||
drawEl: svgEl,
|
||||
showTurtle: false,
|
||||
jsx,
|
||||
});
|
||||
const viewBox = svgEl.getAttribute("viewBox")!.split(" ");
|
||||
svgEl.setAttribute("width", parseInt(viewBox[2]) + "");
|
||||
svgEl.setAttribute("height", parseInt(viewBox[3]) + "");
|
||||
|
@ -540,10 +545,22 @@ document.addEventListener("keydown", (ev) => {
|
|||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function output(c: string) {
|
||||
outputEl.appendChild(document.createTextNode(c));
|
||||
if (c === "\n") {
|
||||
outputEl.scrollTop = outputEl.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
runButtonEl.disabled = true;
|
||||
await draw({ program: editor.getValue(), drawEl: worldEl, outputEl });
|
||||
await draw({
|
||||
program: editor.getValue(),
|
||||
drawEl: worldEl,
|
||||
onEmit: output,
|
||||
jsx,
|
||||
});
|
||||
editor.focus();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
@ -553,7 +570,7 @@ async function run() {
|
|||
}
|
||||
|
||||
async function reset() {
|
||||
await draw({ drawEl: worldEl, outputEl });
|
||||
await draw({ drawEl: worldEl, onEmit: output, jsx });
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
|
4
src/web/vscode-extension/.gitignore
vendored
Normal file
4
src/web/vscode-extension/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
/dist/
|
||||
/LICENSE.txt
|
||||
*.vsix
|
||||
/icon.png
|
5
src/web/vscode-extension/.vscodeignore
Normal file
5
src/web/vscode-extension/.vscodeignore
Normal file
|
@ -0,0 +1,5 @@
|
|||
doc/
|
||||
src/
|
||||
Makefile
|
||||
build.js
|
||||
.gitignore
|
28
src/web/vscode-extension/Makefile
Normal file
28
src/web/vscode-extension/Makefile
Normal file
|
@ -0,0 +1,28 @@
|
|||
all:
|
||||
./build.js
|
||||
|
||||
.PHONY: install-deps
|
||||
install-deps:
|
||||
yarn global add vsce
|
||||
|
||||
.PHONY: prepackage
|
||||
prepackage: all LICENSE.txt icon.png
|
||||
|
||||
|
||||
.PHONY: package
|
||||
package: prepackage
|
||||
vsce package
|
||||
|
||||
.PHONY: publish
|
||||
publish:
|
||||
vsce publish
|
||||
|
||||
LICENSE.txt: ../../../LICENSE.txt
|
||||
cp $^ $@
|
||||
|
||||
icon.png: ../../../doc/logo.svg
|
||||
convert -background transparent $< $@
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
-rm -rf dist LICENSE.txt *.vsix icon.png
|
14
src/web/vscode-extension/README.md
Normal file
14
src/web/vscode-extension/README.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
# WAForth VS Code Extension
|
||||
|
||||
This extension provides support for [WAForth](https://github.com/remko/waforth) notebooks.
|
||||
|
||||
Choose between a standard (text-based) Forth kernel, and a
|
||||
[Thurtle](https://mko.re/thurtle/) kernel for drawing
|
||||
Logo-like Forth Turtle graphics.
|
||||
|
||||
<div align="center">
|
||||
<div>
|
||||
<a href="https://mko.re/waforth/"><img src="https://raw.githubusercontent.com/remko/waforth/master/src/web/vscode-extension/doc/notebook.gif" alt="WAForth notebook"></a>
|
||||
</div>
|
||||
<figcaption><em><a href="https://mko.re/waforth/">WAForth notebook</a></em></figcaption>
|
||||
</div>
|
36
src/web/vscode-extension/build.js
Executable file
36
src/web/vscode-extension/build.js
Executable file
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env node
|
||||
/* eslint-env node */
|
||||
/* eslint @typescript-eslint/no-var-requires:0 */
|
||||
|
||||
const esbuild = require("esbuild");
|
||||
const path = require("path");
|
||||
const { wasmTextPlugin } = require("../../../scripts/esbuild/wasm-text");
|
||||
|
||||
let dev = false;
|
||||
for (const arg of process.argv.slice(2)) {
|
||||
switch (arg) {
|
||||
case "--development":
|
||||
dev = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
esbuild
|
||||
.build({
|
||||
bundle: true,
|
||||
logLevel: "info",
|
||||
entryPoints: [path.join(__dirname, "src/extension.ts")],
|
||||
outfile: path.join(__dirname, "dist/extension.js"),
|
||||
format: "cjs",
|
||||
minify: !dev,
|
||||
sourcemap: true,
|
||||
platform: "node",
|
||||
external: ["vscode"],
|
||||
loader: {
|
||||
".wasm": "binary",
|
||||
".fs": "text",
|
||||
".svg": "dataurl",
|
||||
},
|
||||
plugins: [wasmTextPlugin({ debug: true })],
|
||||
})
|
||||
.catch(() => process.exit(1));
|
BIN
src/web/vscode-extension/doc/notebook.gif
Normal file
BIN
src/web/vscode-extension/doc/notebook.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 MiB |
BIN
src/web/vscode-extension/doc/notebook.mp4
Normal file
BIN
src/web/vscode-extension/doc/notebook.mp4
Normal file
Binary file not shown.
109
src/web/vscode-extension/examples/drawing-with-forth.wafnb
Normal file
109
src/web/vscode-extension/examples/drawing-with-forth.wafnb
Normal file
|
@ -0,0 +1,109 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "# Drawing with Forth\n\nIn this tutorial, you'll learn basic Forth by drawing graphics with a turtle.\n\n| *VS Code Note:* Make sure you select the *Thurtle* kernel in the top right above to run this notebook\n\n## The stack\n\nForth is a stack-based language. Numbers are put on the stack, and words pop them off the stack (and put new ones on the stack) again.\nFor example, to take the sum of 8 and 14, put both numbers on the stack, and call `+`. To pop the result of the stack and print it out, use `.`:"
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "waforth",
|
||||
"value": "8 14 +\n."
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "## Drawing lines\n\nInstead of printing numbers to output, we can also draw lines.\n\nThe `FORWARD` word pops the number of the stack, and moves a turtle forward while drawing a line:"
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "waforth",
|
||||
"value": "200 FORWARD"
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "Let's now also turn the turtle 90 degrees, and create a complete square:"
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "waforth",
|
||||
"value": "200 FORWARD\n90 RIGHT\n200 FORWARD\n90 RIGHT\n200 FORWARD\n90 RIGHT\n200 FORWARD\n90 RIGHT"
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "## Creating your own words\n\nWe can create our own parameterized word that draws a square of the given size:"
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "waforth",
|
||||
"value": ": SQUARE ( n -- )\n DUP FORWARD\n 90 RIGHT\n DUP FORWARD\n 90 RIGHT\n DUP FORWARD\n 90 RIGHT\n DUP FORWARD\n 90 RIGHT\n;\n\n500 SQUARE"
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "## Loops\n\nForth also has loops using `DO` and `LOOP`:"
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "waforth",
|
||||
"value": ": SQUARE ( n -- )\n 4 0 DO\n DUP FORWARD\n 90 RIGHT\n LOOP\n DROP\n;\n\n250 SQUARE"
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "# Combining words\n\nWe can create more complex figures by using the `SQUARE` word from above, and repeating it.\n\n| *Tip*: Play with the numbers in `FLOWER` to create variations of the flower "
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "waforth",
|
||||
"value": ": SQUARE ( n -- )\n 4 0 DO\n DUP FORWARD\n 90 RIGHT\n LOOP\n DROP\n;\n\n: FLOWER ( n -- )\n 24 0 DO\n DUP SQUARE\n 15 RIGHT\n LOOP\n DROP\n;\n\n250 FLOWER"
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "waforth",
|
||||
"value": " 4 0 DO\n DUP FORWARD\n 90 RIGHT\n LOOP\n DROP\n;\n\n: FLOWER ( n -- )\n 24 0 DO\n DUP SQUARE\n 15 RIGHT\n LOOP\n DROP\n;\n\n250 FLOWER"
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "## Recursion\n\nWords can also call themselves:"
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "waforth",
|
||||
"value": ": SPIRAL ( n -- )\n DUP 1 < IF DROP EXIT THEN \n DUP FORWARD\n 15 RIGHT\n 98 100 */ RECURSE\n;\n\n140 SPIRAL"
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "We can make a small variation of the above recursive program, where the lines become longer instead of shorter.\nTo avoid hard-coding some constants in the code, we use the word `CONSTANT` to define a new constant (`ANGLE`) to turn.\n\n| *Tip*: Change the constant `ANGLE` to 91 and see what happens."
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "waforth",
|
||||
"value": "90 CONSTANT ANGLE\n\n: SPIRAL ( n -- )\n DUP 800 > IF DROP EXIT THEN \n DUP FORWARD\n ANGLE RIGHT\n 10 +\n RECURSE\n;\n\n1 SPIRAL"
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "# Fractals\n\nYou can create more complex recursive drawings, called *fractals*. \n\nA famous fractal is the *Koch snowflake*.\n\n| *Tip*: Change the `DEPTH` constant to make a coarser or finer grained snowflake"
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "waforth",
|
||||
"value": "730 CONSTANT LENGTH\n6 CONSTANT DEPTH\n\n: SIDE ( length depth -- )\n DUP 0= IF \n DROP FORWARD EXIT \n THEN\n SWAP 3 / SWAP 1-\n 2DUP RECURSE\n 60 LEFT 2DUP RECURSE\n 120 RIGHT 2DUP RECURSE\n 60 LEFT RECURSE\n;\n\n: SNOWFLAKE ( -- )\n 3 0 DO \n LENGTH DEPTH SIDE\n 120 RIGHT\n LOOP\n;\n\n1 SETPENSIZE\nSNOWFLAKE"
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "You can also draw plants and trees using fractals:"
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "waforth",
|
||||
"value": "450 CONSTANT SIZE\n7 CONSTANT BRANCHES\n160 CONSTANT SPREAD\n\nVARIABLE RND\nHERE RND !\n\n: RANDOM ( -- n )\n RND @ 75 * 74 + 65537 MOD\n DUP RND !\n;\n\n: CHOOSE ( n1 -- n2 )\n RANDOM 65537 */MOD SWAP DROP \n; \n\n: PLANT ( size angle -- )\n OVER 10 < IF 2DROP EXIT THEN\n DUP RIGHT\n OVER FORWARD\n BRANCHES 0 DO\n OVER 2/\n SPREAD CHOOSE SPREAD 2/ -\n RECURSE\n LOOP\n PENUP SWAP BACKWARD PENDOWN\n LEFT\n;\n \n1 SETPENSIZE\nSIZE 0 PLANT"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"comments": {
|
||||
"lineComment": "\\"
|
||||
}
|
||||
}
|
175
src/web/vscode-extension/language/waforth.tmGrammar.json
Normal file
175
src/web/vscode-extension/language/waforth.tmGrammar.json
Normal file
|
@ -0,0 +1,175 @@
|
|||
{
|
||||
"fileTypes": ["f", "fs"],
|
||||
"scopeName": "source.waforth",
|
||||
"foldingStartMarker": "/\\*\\*|\\{\\s*$",
|
||||
"foldingStopMarker": "\\*\\*/|^\\s*\\}",
|
||||
"name": "WAForth",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#constant"
|
||||
},
|
||||
{
|
||||
"include": "#comment"
|
||||
},
|
||||
{
|
||||
"include": "#string"
|
||||
},
|
||||
{
|
||||
"include": "#word"
|
||||
},
|
||||
{
|
||||
"include": "#variable"
|
||||
},
|
||||
{
|
||||
"include": "#storage"
|
||||
},
|
||||
{
|
||||
"include": "#word-def"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"comment": {
|
||||
"patterns": [
|
||||
{
|
||||
"match": "(?<=^|\\s)(\\\\[\\s\\S]*$)",
|
||||
"name": "comment.line.backslash.waforth"
|
||||
},
|
||||
{
|
||||
"match": "(?<=^|\\s)(\\.?\\( [^)]*\\))",
|
||||
"name": "comment.line.parentheses.waforth"
|
||||
}
|
||||
]
|
||||
},
|
||||
"constant": {
|
||||
"patterns": [
|
||||
{
|
||||
"match": "(?i:(?<=^|\\s)(TRUE|FALSE|BL|PI|CELL|C/L|R/O|W/O|R/W)(?=\\s))",
|
||||
"name": "constant.language.waforth"
|
||||
},
|
||||
{
|
||||
"match": "(?<=^|\\s)([$#%]?[-+]?[0-9]+(\\.[0-9]*e(-?[0-9]+)|\\.?[0-9a-fA-F]*))(?=\\s)",
|
||||
"name": "constant.numeric.waforth"
|
||||
},
|
||||
{
|
||||
"match": "(?<=^|\\s)(([&^]\\S)|((\"|')\\S(\"|')))(?=\\s)",
|
||||
"name": "constant.character.waforth"
|
||||
}
|
||||
]
|
||||
},
|
||||
"storage": {
|
||||
"patterns": [
|
||||
{
|
||||
"comment": "",
|
||||
"match": "(?<=^|\\s)(?i:(2CONSTANT|2VARIABLE|ALIAS|CONSTANT|CREATE-INTERPRET/COMPILE[:]?|CREATE|DEFER|FCONSTANT|FIELD|FVARIABLE|USER|VALUE|VARIABLE|VOCABULARY))(?=\\s)",
|
||||
"name": "storage.type.waforth"
|
||||
}
|
||||
]
|
||||
},
|
||||
"string": {
|
||||
"patterns": [
|
||||
{
|
||||
"comment": "",
|
||||
"match": "(?i:((?<=ABORT\" )|(?<=BREAK\" )|(?<=\\.\" )|(C\" )|(0\")|(S\\\\?\" )))[^\"]+\"",
|
||||
"name": "string.quoted.double.waforth"
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"match": "(?i:((?<=INCLUDE)|(?<=NEEDS)|(?<=REQUIRE)|(?<=USE)))[ ]\\S+(?=\\s)",
|
||||
"name": "string.unquoted.waforth"
|
||||
}
|
||||
]
|
||||
},
|
||||
"variable": {
|
||||
"patterns": [
|
||||
{
|
||||
"match": "\\b(?i:I|J)\\b",
|
||||
"name": "variable.language.waforth"
|
||||
}
|
||||
]
|
||||
},
|
||||
"word": {
|
||||
"patterns": [
|
||||
{
|
||||
"match": "(?<=^|\\s)\\[(?i:(NIP|TUCK|ROT|SWAP|DUP|OVER|DROP|2SWAP|2DUP|2DROP|FNIP|FTUCK|FROT|FSWAP|FDUP|FOVER|FDROP|F2SWAP|F2DUP|F2DROP))\\](?=\\s)",
|
||||
"name": "keyword.other.word.waforth"
|
||||
},
|
||||
{
|
||||
"match": "(?<=^|\\s)\\[(?i:(F+|F-|F*|F**|FSQRT|F/|F.S))\\](?=\\s)",
|
||||
"name": "keyword.other.word.waforth"
|
||||
},
|
||||
{
|
||||
"match": "(?<=^|\\s)\\[(?i:(EXECUTE|ABORT|CATCH|C@|F@|>R|R>|!|/MOD|MOD))\\](?=\\s)",
|
||||
"name": "keyword.other.word.waforth"
|
||||
},
|
||||
{
|
||||
"match": "(?<=^|\\s)\\[(?i:(\\?DO|\\+LOOP|AGAIN|BEGIN|DEFINED|DO|ELSE|ENDIF|FOR|IF|IFDEF|IFUNDEF|LOOP|NEXT|REPEAT|THEN|UNTIL|WHILE))\\](?=\\s)",
|
||||
"name": "keyword.control.immediate.waforth"
|
||||
},
|
||||
{
|
||||
"match": "(?<=^|\\s)(?i:(COMPILE-ONLY|IMMEDIATE|IS|RESTRICT|TO|WHAT'S|]))(?=\\s)",
|
||||
"name": "keyword.operator.immediate.waforth"
|
||||
},
|
||||
{
|
||||
"match": "(?<=^|\\s)(?i:(-DO|\\-LOOP|\\?DO|\\?LEAVE|\\+DO|\\+LOOP|ABORT\\\"|AGAIN|AHEAD|BEGIN|CASE|DO|ELSE|ENDCASE|ENDIF|ENDOF|ENDTRY\\-IFERROR|ENDTRY|FOR|IF|IFERROR|LEAVE|LOOP|NEXT|RECOVER|REPEAT|RESTORE|THEN|TRY|U\\-DO|U\\+DO|UNTIL|WHILE))(?=\\s)",
|
||||
"name": "keyword.control.compile-only.waforth"
|
||||
},
|
||||
{
|
||||
"match": "(?<=^|\\s)(?i:(\\?DUP-0=-IF|\\?DUP-IF|\\)|\\[|\\['\\]|\\[CHAR\\]|\\[COMPILE\\]|\\[IS\\]|\\[TO\\]|<COMPILATION|<INTERPRETATION|ASSERT\\(|ASSERT0\\(|ASSERT1\\(|ASSERT2\\(|ASSERT3\\(|COMPILATION>|DEFERS|DOES>|INTERPRETATION>|OF|POSTPONE))(?=\\s)",
|
||||
"name": "keyword.other.compile-only.waforth"
|
||||
},
|
||||
{
|
||||
"match": "(?<=^|\\s)(?i:('|<IS>|<TO>|CHAR|END-STRUCT|INCLUDE[D]?|LOAD|NEEDS|REQUIRE[D]?|REVISION|SEE|STRUCT|THRU|USE))(?=\\s)",
|
||||
"name": "keyword.other.non-immediate.waforth"
|
||||
},
|
||||
{
|
||||
"match": "(?<=^|\\s)(?i:(~~|BREAK:|BREAK\"|DBG))(?=\\s)",
|
||||
"name": "keyword.other.warning.waforth"
|
||||
}
|
||||
]
|
||||
},
|
||||
"word-def": {
|
||||
"begin": "(^:|\\s:)\\s(\\S+)\\s|(?i:(:NONAME))",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "keyword.other.compile-only.waforth"
|
||||
},
|
||||
"2": {
|
||||
"name": "entity.name.function.waforth"
|
||||
},
|
||||
"3": {
|
||||
"name": "keyword.other.compile-only.waforth"
|
||||
},
|
||||
"4": {
|
||||
"name": "keyword.other.word.waforth"
|
||||
}
|
||||
},
|
||||
"end": "(;(?i:CODE)?)",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "keyword.other.compile-only.waforth"
|
||||
}
|
||||
},
|
||||
"name": "meta.block.waforth",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#constant"
|
||||
},
|
||||
{
|
||||
"include": "#comment"
|
||||
},
|
||||
{
|
||||
"include": "#string"
|
||||
},
|
||||
{
|
||||
"include": "#word"
|
||||
},
|
||||
{
|
||||
"include": "#variable"
|
||||
},
|
||||
{
|
||||
"include": "#storage"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
78
src/web/vscode-extension/package.json
Normal file
78
src/web/vscode-extension/package.json
Normal file
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"name": "waforth-vscode-extension",
|
||||
"version": "0.1.3",
|
||||
"displayName": "WAForth",
|
||||
"description": "WAForth interactive notebooks",
|
||||
"categories": [
|
||||
"Programming Languages",
|
||||
"Notebooks"
|
||||
],
|
||||
"keywords": [
|
||||
"forth"
|
||||
],
|
||||
"publisher": "remko",
|
||||
"license": "MIT",
|
||||
"repository": "github:remko/waforth",
|
||||
"icon": "icon.png",
|
||||
"main": "dist/extension.js",
|
||||
"browser": "dist/extension.js",
|
||||
"engines": {
|
||||
"vscode": "^1.61.0"
|
||||
},
|
||||
"capabilities": {
|
||||
"untrustedWorkspaces": {
|
||||
"supported": true
|
||||
},
|
||||
"virtualWorkspaces": true
|
||||
},
|
||||
"activationEvents": [
|
||||
"onNotebook:waforth-notebook",
|
||||
"onCommand:waforth-notebook.new"
|
||||
],
|
||||
"contributes": {
|
||||
"notebooks": [
|
||||
{
|
||||
"type": "waforth-notebook",
|
||||
"displayName": "WAForth Notebook",
|
||||
"selector": [
|
||||
{
|
||||
"filenamePattern": "*.wafnb"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"languages": [
|
||||
{
|
||||
"id": "waforth",
|
||||
"aliases": [
|
||||
"WAForth"
|
||||
],
|
||||
"configuration": "./language/language-configuration.json"
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
{
|
||||
"language": "waforth",
|
||||
"scopeName": "source.waforth",
|
||||
"path": "./language/waforth.tmGrammar.json"
|
||||
}
|
||||
],
|
||||
"commands": [
|
||||
{
|
||||
"title": "WAForth: New Notebook",
|
||||
"shortTitle": "WAForth Notebook",
|
||||
"command": "waforth-notebook.new"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"file/newFile": [
|
||||
{
|
||||
"command": "waforth-notebook.new"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "make prepackage"
|
||||
}
|
||||
}
|
197
src/web/vscode-extension/src/extension.ts
Normal file
197
src/web/vscode-extension/src/extension.ts
Normal file
|
@ -0,0 +1,197 @@
|
|||
import * as vscode from "vscode";
|
||||
import WAForth, { ErrorCode, isSuccess } from "../../waforth";
|
||||
import draw from "../../thurtle/draw";
|
||||
import JSJSX from "thurtle/jsjsx";
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.registerNotebookSerializer(
|
||||
"waforth-notebook",
|
||||
new NotebookSerializer(),
|
||||
{
|
||||
transientOutputs: true,
|
||||
}
|
||||
)
|
||||
);
|
||||
await createNotebookController("WAForth", "waforth-controller", false, false);
|
||||
await createNotebookController("Thurtle", "thurtle-controller", true, false);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("waforth-notebook.new", async function () {
|
||||
const newNotebook = await vscode.workspace.openNotebookDocument(
|
||||
"waforth-notebook",
|
||||
new vscode.NotebookData([
|
||||
new vscode.NotebookCellData(
|
||||
vscode.NotebookCellKind.Code,
|
||||
".( Hello world) CR",
|
||||
"waforth"
|
||||
),
|
||||
])
|
||||
);
|
||||
await vscode.commands.executeCommand("vscode.open", newNotebook.uri);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Notebook Serializer
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
interface RawNotebookData {
|
||||
cells: RawNotebookCell[];
|
||||
}
|
||||
|
||||
interface RawNotebookCell {
|
||||
language: string;
|
||||
value: string;
|
||||
kind: vscode.NotebookCellKind;
|
||||
editable?: boolean;
|
||||
}
|
||||
|
||||
export class NotebookSerializer implements vscode.NotebookSerializer {
|
||||
public readonly label: string = "WAForth Content Serializer";
|
||||
|
||||
public async deserializeNotebook(
|
||||
data: Uint8Array
|
||||
): Promise<vscode.NotebookData> {
|
||||
const contents = new TextDecoder().decode(data);
|
||||
if (contents.trim().length === 0) {
|
||||
return new vscode.NotebookData([]);
|
||||
}
|
||||
let raw: RawNotebookData;
|
||||
try {
|
||||
raw = <RawNotebookData>JSON.parse(contents);
|
||||
} catch (e) {
|
||||
vscode.window.showErrorMessage("Error parsing:", (e as any).message);
|
||||
raw = { cells: [] };
|
||||
}
|
||||
const cells = raw.cells.map(
|
||||
(item) =>
|
||||
new vscode.NotebookCellData(item.kind, item.value, item.language)
|
||||
);
|
||||
return new vscode.NotebookData(cells);
|
||||
}
|
||||
|
||||
public async serializeNotebook(
|
||||
data: vscode.NotebookData
|
||||
): Promise<Uint8Array> {
|
||||
const contents: RawNotebookData = { cells: [] };
|
||||
for (const cell of data.cells) {
|
||||
contents.cells.push({
|
||||
kind: cell.kind,
|
||||
language: cell.languageId,
|
||||
value: cell.value,
|
||||
});
|
||||
}
|
||||
return new TextEncoder().encode(JSON.stringify(contents, undefined, 2));
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Notebook Serializer
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
async function createNotebookController(
|
||||
name: string,
|
||||
id: string,
|
||||
turtleSupport: boolean,
|
||||
stateful: boolean
|
||||
) {
|
||||
let executionOrder = 0;
|
||||
|
||||
// Global instance (for non-Thurtle kernel)
|
||||
let globalForth: WAForth;
|
||||
if (stateful) {
|
||||
globalForth = await new WAForth().load();
|
||||
}
|
||||
|
||||
const controller = vscode.notebooks.createNotebookController(
|
||||
id,
|
||||
"waforth-notebook",
|
||||
name
|
||||
);
|
||||
controller.supportedLanguages = ["waforth"];
|
||||
controller.supportsExecutionOrder = stateful;
|
||||
controller.executeHandler = async (
|
||||
cells: vscode.NotebookCell[],
|
||||
_notebook: vscode.NotebookDocument,
|
||||
controller: vscode.NotebookController
|
||||
) => {
|
||||
for (const cell of cells) {
|
||||
const execution = controller.createNotebookCellExecution(cell);
|
||||
execution.executionOrder = ++executionOrder;
|
||||
execution.start(Date.now());
|
||||
execution.clearOutput();
|
||||
try {
|
||||
let outputBuffer: string[] = [];
|
||||
const flushOutputBuffer = () => {
|
||||
if (outputBuffer.length === 0) {
|
||||
return;
|
||||
}
|
||||
execution.appendOutput(
|
||||
new vscode.NotebookCellOutput([
|
||||
vscode.NotebookCellOutputItem.text(outputBuffer.join("")),
|
||||
])
|
||||
);
|
||||
outputBuffer = [];
|
||||
};
|
||||
const emit = (c: string) => {
|
||||
outputBuffer.push(c);
|
||||
if (c.endsWith("\n")) {
|
||||
flushOutputBuffer();
|
||||
}
|
||||
};
|
||||
|
||||
let result: ErrorCode;
|
||||
const program = execution.cell.document.getText();
|
||||
if (cell.document.languageId != "waforth") {
|
||||
throw new Error("can't happen");
|
||||
}
|
||||
if (!turtleSupport) {
|
||||
const forth = stateful ? globalForth : await new WAForth().load();
|
||||
forth.onEmit = emit;
|
||||
result = forth.interpret(program, true);
|
||||
} else {
|
||||
const jsx = new JSJSX();
|
||||
const svgEl = jsx.createElement("svg", {
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
});
|
||||
result = (await draw({
|
||||
program,
|
||||
drawEl: svgEl as any,
|
||||
onEmit: emit,
|
||||
showTurtle: true,
|
||||
jsx,
|
||||
}))!;
|
||||
const paths = (svgEl._children as any[]).find(
|
||||
(el) => el._tag === "g"
|
||||
)._children;
|
||||
if (paths.length > 1 || paths[0].d !== "M0 0") {
|
||||
svgEl.height = "300px";
|
||||
svgEl.style =
|
||||
"background-color: rgb(221, 248, 221); border-radius: 10px;";
|
||||
execution.appendOutput(
|
||||
new vscode.NotebookCellOutput([
|
||||
vscode.NotebookCellOutputItem.text(
|
||||
"<div style='width: 100%; display: flex; justify-content: center;'>" +
|
||||
jsx.toHTML(svgEl) +
|
||||
"</div>",
|
||||
"text/html"
|
||||
),
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
flushOutputBuffer();
|
||||
execution.end(isSuccess(result), Date.now());
|
||||
} catch (e) {
|
||||
vscode.window.showErrorMessage((e as any).message);
|
||||
execution.end(false, Date.now());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -36,7 +36,7 @@ function saveString(s: string, memory: WebAssembly.Memory, addr: number) {
|
|||
}
|
||||
}
|
||||
|
||||
enum ErrorCode {
|
||||
export enum ErrorCode {
|
||||
Unknown = 0x1, // Unknown error
|
||||
Quit = 0x2, // QUIT was called
|
||||
Abort = 0x3, // ABORT or ABORT" was called
|
||||
|
@ -44,6 +44,10 @@ enum ErrorCode {
|
|||
Bye = 0x5, // BYE was called
|
||||
}
|
||||
|
||||
export function isSuccess(code: ErrorCode) {
|
||||
return code !== ErrorCode.Abort && code !== ErrorCode.Unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* JavaScript shell around the WAForth WebAssembly module.
|
||||
*
|
||||
|
@ -191,6 +195,7 @@ class WAForth {
|
|||
this.core = instance.instance;
|
||||
const table = this.core.exports.table as WebAssembly.Table;
|
||||
const memory = this.core.exports.memory as WebAssembly.Memory;
|
||||
return this;
|
||||
}
|
||||
|
||||
memory(): WebAssembly.Memory {
|
||||
|
|
|
@ -117,6 +117,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.31.tgz#a5bb84ecfa27eec5e1c802c6bbf8139bdb163a5d"
|
||||
integrity sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==
|
||||
|
||||
"@types/vscode@^1.73.1":
|
||||
version "1.73.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.73.1.tgz#f2b198dca65c693c4570475aca04a34ad773609d"
|
||||
integrity sha512-eArfOrAoZVV+Ao9zQOCaFNaeXj4kTCD+bGS2gyNgIFZH9xVMuLMlRrEkhb22NyxycFWKV1UyTh03vhaVHmqVMg==
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^5.30.5":
|
||||
version "5.30.5"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.5.tgz#e9a0afd6eb3b1d663db91cf1e7bc7584d394503d"
|
||||
|
|
Loading…
Reference in a new issue