vscode-extension: Import

This commit is contained in:
Remko Tronçon 2022-11-19 18:31:13 +01:00
parent cd6e712b75
commit 4f154243b5
20 changed files with 789 additions and 18 deletions

View 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
View 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"]
}
]
}

View file

@ -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",

View file

@ -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
View 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;

View file

@ -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
View file

@ -0,0 +1,4 @@
/dist/
/LICENSE.txt
*.vsix
/icon.png

View file

@ -0,0 +1,5 @@
doc/
src/
Makefile
build.js
.gitignore

View 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

View 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>

View 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));

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

View 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"
}
]
}

View file

@ -0,0 +1,5 @@
{
"comments": {
"lineComment": "\\"
}
}

View 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"
}
]
}
}
}

View 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"
}
}

View 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());
}
}
};
}

View file

@ -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 {

View file

@ -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"