+ Interactive, Logo-like Turtle graphics language, using Forth (powered by + WAForth). +
+diff --git a/.github/workflows/publish-thurtle.yml b/.github/workflows/publish-thurtle.yml new file mode 100644 index 0000000..4fdb301 --- /dev/null +++ b/.github/workflows/publish-thurtle.yml @@ -0,0 +1,25 @@ +name: Publish Thurtle + +on: + workflow_dispatch: + +jobs: + build: + uses: ./.github/workflows/build.yml + + publish: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ./.github/actions/setup + - run: sudo apt-get install awscli + - run: yarnpkg build + - name: aws s3 sync + run: | + aws configure set region eu-central-1 + aws configure set aws_access_key_id ${{secrets.AWS_ACCESS_KEY_ID}} + aws configure set aws_secret_access_key ${{secrets.AWS_SECRET_ACCESS_KEY}} + aws s3 sync public/thurtle/ s3://${{secrets.AWS_SITE_BUCKET}}/thurtle/ + aws s3 sync public/waforth/dist/ s3://${{secrets.AWS_SITE_BUCKET}}/waforth/dist/ --exclude "*" --include "thurtle-*" --include "logo-*.svg" --include "turtle-*.svg" + diff --git a/.gitignore b/.gitignore index 60d7cc0..458f4d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store node_modules/ public/waforth/ +public/thurtle/ src/waforth.bulkmem.wat src/waforth.vanilla.wat src/web/benchmarks/sieve/sieve-c.js diff --git a/README.md b/README.md index a234f41..137c553 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ WAForth is a small bootstrapping Forth interpreter and dynamic compiler for [WebAssembly](https://webassembly.org). You can see it in action -[here](https://mko.re/waforth/). +[in an interactive Forth console](https://mko.re/waforth/), and in [a Logo-like Turtle graphics language](https://mko.re/thurtle/). It is [entirely written in (raw) WebAssembly](https://github.com/remko/waforth/blob/master/src/waforth.wat), and diff --git a/build-web.js b/build-web.js index dc856b4..a6b5b1d 100755 --- a/build-web.js +++ b/build-web.js @@ -30,9 +30,7 @@ function withWatcher(config, handleBuildFinished = () => {}, port = 8880) { if (error) { console.error(error); } else { - // Doing this first, because this may do some ES5 transformations await handleBuildFinished(result); - watchClients.forEach((res) => res.write("data: update\n\n")); watchClients.length = 0; } @@ -62,16 +60,20 @@ let buildConfig = { path.join(__dirname, "src", "web", "tests", "tests"), path.join(__dirname, "src", "web", "benchmarks", "benchmarks"), path.join(__dirname, "src", "web", "examples", "prompt", "prompt"), + path.join(__dirname, "src", "web", "thurtle", "thurtle"), ], entryNames: dev ? "[name]" : "[name]-c$[hash]", assetNames: "[name]-c$[hash]", // target: "es6", outdir: path.join(__dirname, "public/waforth/dist"), + publicPath: "/waforth/dist", external: ["fs", "stream", "util", "events"], minify: !dev, loader: { ".wasm": "binary", ".js": "jsx", + ".fs": "text", + ".svg": "file", }, define: { WAFORTH_VERSION: watch @@ -118,9 +120,19 @@ async function handleBuildFinished(result) { ["tests", "public/waforth/tests"], ["benchmarks", "public/waforth/benchmarks"], ["prompt", "public/waforth/examples/prompt"], + ["thurtle", "public/thurtle", true], ]; - for (const [base, outpath] of indexes) { + for (const [base, outpath, bs] of indexes) { let index = INDEX_TEMPLATE.replace(/\$BASE/g, base); + if (bs) { + index = index.replace( + "
", + ` + + +` + ); + } for (const [out] of Object.entries(result.metafile.outputs)) { const outfile = path.basename(out); const sourcefile = outfile.replace(/-c\$[^.]+\./, "."); @@ -145,7 +157,14 @@ if (watch) { } try { const data = await fs.promises.readFile(f); - res.writeHead(200); + res.writeHead( + 200, + req.url.endsWith(".svg") + ? { + "Content-Type": "image/svg+xml", + } + : undefined + ); res.end(data); } catch (err) { res.writeHead(404); diff --git a/src/web/thurtle/examples.ts b/src/web/thurtle/examples.ts new file mode 100644 index 0000000..5f51ded --- /dev/null +++ b/src/web/thurtle/examples.ts @@ -0,0 +1,83 @@ +export type Example = { + name: string; + program: string; +}; + +export default [ + { + name: "Square", + program: ` +200 FORWARD +90 RIGHT +200 FORWARD +90 RIGHT +200 FORWARD +90 RIGHT +200 FORWARD +90 RIGHT +`, + }, + { + name: "Square (w/ LOOP)", + program: ` +: SQUARE ( n -- ) + 4 0 DO + DUP FORWARD + 90 RIGHT + LOOP +; + +250 SQUARE +`, + }, + { + name: "Seeker", + program: ` +: SEEKER ( n -- ) + 4 0 DO + DUP FORWARD + PENUP + DUP FORWARD + PENDOWN + DUP FORWARD + 90 RIGHT + LOOP +; + +100 SEEKER +`, + }, + { + name: "Flower", + program: ` +: SQUARE ( n -- ) +4 0 DO + DUP FORWARD + 90 RIGHT +LOOP +; + +: FLOWER ( n -- ) + 12 0 DO + DUP SQUARE + 30 RIGHT + LOOP +; + +250 FLOWER +`, + }, + { + name: "Spiral (Recursive)", + program: ` +: SPIRAL ( n -- ) + DUP 1 < IF EXIT THEN + DUP FORWARD + 20 RIGHT + 95 100 */ RECURSE +; + +140 SPIRAL +`, + }, +].map((e) => ({ ...e, program: e.program.trimStart() })); diff --git a/src/web/thurtle/thurtle.css b/src/web/thurtle/thurtle.css new file mode 100644 index 0000000..2facf83 --- /dev/null +++ b/src/web/thurtle/thurtle.css @@ -0,0 +1,52 @@ +.root { + height: 100vh; +} + +.main { + height: calc(100% - 40px); +} + +textarea { + flex: 1; +} + +.navbar { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +.world { + border: thin solid rgb(171, 208, 166); + border-radius: 0.5em; + background-color: rgb(221, 248, 221); + width: 100%; + max-height: 350px; +} + +.world path { + stroke: black; + fill: white; + fill-opacity: 0; +} + +textarea.program { + font-family: monospace; + font-size: 0.8em; +} + +.output { + font-size: 0.8em; + overflow: scroll; +} + +label { + font-weight: 500; +} + +.left-pane { + flex: 1; +} + +.right-pane { + flex: 2; +} diff --git a/src/web/thurtle/thurtle.fs b/src/web/thurtle/thurtle.fs new file mode 100644 index 0000000..7423cf9 --- /dev/null +++ b/src/web/thurtle/thurtle.fs @@ -0,0 +1,9 @@ +: FORWARD ( n -- ) S" forward" SCALL ; +: BACKWARD ( n -- ) NEGATE FORWARD ; +: LEFT ( n -- ) S" rotate" SCALL ; +: RIGHT ( n -- ) NEGATE LEFT ; +: PENDOWN ( -- ) 1 S" pen" SCALL ; +: PENUP ( -- ) 0 S" pen" SCALL ; +: HIDETURTLE ( -- ) 0 S" turtle" SCALL ; +: SHOWTURTLE ( -- ) 1 S" turtle" SCALL ; +: SETPENSIZE ( n -- ) S" setpensize" SCALL ; \ No newline at end of file diff --git a/src/web/thurtle/thurtle.ts b/src/web/thurtle/thurtle.ts new file mode 100644 index 0000000..ea28634 --- /dev/null +++ b/src/web/thurtle/thurtle.ts @@ -0,0 +1,264 @@ +import WAForth from "waforth"; +import "./thurtle.css"; +import turtle from "./turtle.svg"; +import logo from "../../../doc/logo.svg"; +import thurtleFS from "./thurtle.fs"; +import examples from "./examples"; + +const rootEl = document.createElement("div"); +rootEl.className = "root"; +rootEl.innerHTML = ` ++ Interactive, Logo-like Turtle graphics language, using Forth (powered by + WAForth). +
+