diff --git a/blog/hello-wren.html b/blog/0-hello-wren.html similarity index 83% rename from blog/hello-wren.html rename to blog/0-hello-wren.html index d452f70d..00235cd5 100644 --- a/blog/hello-wren.html +++ b/blog/0-hello-wren.html @@ -3,6 +3,9 @@ Hello Wren – Wren + + + + + + +
+
+
+

wren

+

a classy little scripting language

+
+
+
+
+ + +
+

0.3.0 released!

+

5 June 2020

+
+

In this post we’ll cover 0.3.0 and the goals for 0.4.0 #.

+

About the 0.3.0 release #

+

Let’s revisit our goals from the last blog post,
+and mark what we managed to get done:

+ +

The details #

+

VM / CLI split #

+

With 0.3.0 we’ve separated the CLI from the Wren repo, +and updated the docs to make the distinction clearer.

+

The CLI now has it’s own corner of the docs, so that the modules +and API docs aren’t overlapped like before. This opens up space for the +CLI to get better, fuller documentation, and removes confusion about +built in modules vs ones that are in the CLI only.

+

The code structure is clearer, too, and all the tests and utils are now specific.

+

Build consistency/reliability #

+

Previously, builds on Windows could be a little fickle, and there was sometimes +issues with the dependencies on the CLI side.

+

To solve this, premake is now used to generate platform specific project files that +‘just work’, making it a one step process to build the VM or CLI. Both projects +now have a projects/ folder which includes ready to go project files for primary platforms.

+

The original Makefile and util/wren.mk no longer exist, so there might be some work needed +to reintegrate if you relied on those. You can find the updated makefile in projects/make/, or projects/make.mac/.

+

The amalgamated build was fixed too, so that embedding in your own project is as simple as +including a single c file (and the wren.h header).

+

On the CLI side, the pre-build steps were removed and dependencies vendored in repo, +so that the project just builds with less potential points of error, especially across platforms.

+

And finally the docs! Previously SASS was used, and code highlighting +was done at generation time using pygments, a python code highlighter. Both of these dependencies +have been removed, code highlighting is now done on the client side instead (see another reason why below). +The benefit here that it is now easy to edit the docs, just a simple python command, no setup!

+

Web build for embedding in docs #

+

The goal was two part here, one is to have a page to just try out Wren. +Type in some code, run it. That’s the first big step and we’ve now got that on the docs page.

+

Try Wren directly in your browser!

+ +

This should work on desktop or mobile, and will continue to be improved over time.

+

The second part of that goal is having the VM available to make examples on each page interactive. +This is implemented, but not activated on any pages yet.

+

In the near future inline doc examples will have a small button that you can +press to see the code result right there, live. Since there’s a lot of examples, +and sometimes they’re fragments of code that don’t run in isolation, +it will take time to propagate it through the pages.

+

Mainly, I didn’t want this to hold up 0.3.0, but expect to start seeing it soon.

+

Prebuilt releases #

+

In addition to the browser based build that removes a barrier to trying out Wren, +Wren CLI has prebuilt binaries for Mac, Windows and Linux now! This gives +an easy path to just tinkering with Wren before embedding it.

+
+

Goals for 0.4.0 #

+

With 0.4.0 the goal is to address a couple of bigger todos, but also to push the language +itself, and the embedding experience forward.

+

You can see some of the work in progress tasks here, +but there’s a few things I’d like to resolve in 0.4.0.

+

Compound operators
+I’ve really missed having += and friends,
+so I’ve been working on a (broken, wip) PR here. +I’ve since had a better idea to implement it and will hope to address that in 0.4.0.

+

Chained methods (‘fluent interfaces’)
+Currently in Wren it’s required that the period (.) be on the same line as the method. +

+  example.
+    some().
+    functions().
+    here()
+
+This isn’t as elegant as we’d want for this form of API, +so in 0.4.0 the goal is allowing a newline, as you’d expect: +
+  example
+    .some()
+    .functions()
+    .here()
+
+This doesn’t seem like a big deal but when your calls are wider, +longer and possibly accept block functions. It’s hard to read, +and can be less fun to track down a missing . in a big chunk of code. +
+  example.
+    some {|args, and, stuff|
+      …
+    }.
+    here()
+

+

C Side APIs
+Some APIs for dealing with Map have been proposed several times, +it’s time to bring that into the API. There’s some additions for List as well, +like a helper to set an element in a list.

+

Other goals
+There’s a few more things but I’m still exploring their viability.
+Keep an eye on the PRs/issues or the 0.4.0 label to see when they’re discussed.

+

Till next time #

+
+ +
+
+ + + diff --git a/blog/index.html b/blog/index.html index 66cbe7d1..acfe8ef7 100644 --- a/blog/index.html +++ b/blog/index.html @@ -3,6 +3,9 @@ Development blogs – Wren + + + + + + +
+
+
+

wren

+

a classy little scripting language

+
+
+
+
+ + +
+

Wren CLI

+
+

What is it? #

+

The Wren Command-Line Interface is a tool you can run which gives you a way to run Wren code, and + also includes modules for talking to the operating system—file IO, + networking, stuff like that. It depends on libuv for that + functionality.

+

Wren as a language is intentionally designed to be minimal.
+That includes the built in language features, the standard library and the VM itself.

+

In order to access files, networks and other IO, you’d need to make a tool using the language VM. +That’s what the CLI project is! It is not bundled as part of the wren project, +instead it is it’s own project as a standalone tool you can run. +It exposes it’s own standard library and modules that may be of interest +if looking for a general purpose single binary scriptable tool.

+

Wren CLI is a work in progress, and contributions are welcome to make it more useful over time.

+

Why does it exist? #

+ +
+
+ + + diff --git a/cli/modules/index.html b/cli/modules/index.html new file mode 100644 index 00000000..5bae5b95 --- /dev/null +++ b/cli/modules/index.html @@ -0,0 +1,105 @@ + + + + +CLI Modules – Wren + + + + + + + + + +
+
+
+

wren

+

a classy little scripting language

+
+
+
+
+ + +
+

CLI Modules

+

The Wren CLI executable extends the built in language modules with it’s own, +which offer access to IO and other facilities for scripting.

+

The CLI modules are deeply tied to libuv, each other, and other internals +of the command-line app, so can’t easily be separated out and pulled into host +applications that want to embed Wren. Scripts written for the CLI then, +are specific to the CLI unless another host implements the same API.

+ +
+
+ + + diff --git a/modules/io/directory.html b/cli/modules/io/directory.html similarity index 76% rename from modules/io/directory.html rename to cli/modules/io/directory.html index 055cba05..82bdd7aa 100644 --- a/modules/io/directory.html +++ b/cli/modules/io/directory.html @@ -3,7 +3,10 @@ Directory Class – Wren - + + + + @@ -13,17 +16,18 @@
-

wren

+

wren

a classy little scripting language

+ + + diff --git a/codejar-linenumbers.js b/codejar-linenumbers.js new file mode 100644 index 00000000..de2b6c3d --- /dev/null +++ b/codejar-linenumbers.js @@ -0,0 +1,51 @@ +function withLineNumbers(highlight, options = {}) { + const opts = Object.assign({ class: "codejar-linenumbers", wrapClass: "codejar-wrap", width: "35px" }, options); + let lineNumbers; + return function (editor) { + highlight(editor); + if (!lineNumbers) { + lineNumbers = init(editor, opts); + } + const code = editor.textContent || ""; + const linesCount = code.replace(/\n+$/, "\n").split("\n").length + 1; + let text = ""; + for (let i = 1; i < linesCount; i++) { + text += `${i}\n`; + } + lineNumbers.innerText = text; + }; +} +function init(editor, opts) { + const css = getComputedStyle(editor); + const wrap = document.createElement("div"); + wrap.className = opts.wrapClass; + wrap.style.position = "relative"; + const lineNumbers = document.createElement("div"); + lineNumbers.className = opts.class; + wrap.appendChild(lineNumbers); + // Add own styles + lineNumbers.style.position = "absolute"; + lineNumbers.style.top = "0px"; + lineNumbers.style.left = "0px"; + lineNumbers.style.bottom = "0px"; + lineNumbers.style.width = opts.width; + lineNumbers.style.overflow = "hidden"; + lineNumbers.style.backgroundColor = "rgba(255, 255, 255, 0.05)"; + lineNumbers.style.color = "#fff"; + lineNumbers.style.setProperty("mix-blend-mode", "difference"); + // Copy editor styles + lineNumbers.style.fontFamily = css.fontFamily; + lineNumbers.style.fontSize = css.fontSize; + lineNumbers.style.lineHeight = css.lineHeight; + lineNumbers.style.paddingTop = css.paddingTop; + lineNumbers.style.paddingLeft = css.paddingLeft; + lineNumbers.style.borderTopLeftRadius = css.borderTopLeftRadius; + lineNumbers.style.borderBottomLeftRadius = css.borderBottomLeftRadius; + // Tweak editor styles + editor.style.paddingLeft = `calc(${opts.width} + ${lineNumbers.style.paddingLeft})`; + editor.style.whiteSpace = "pre"; + // Swap editor with a wrap + editor.parentNode.insertBefore(wrap, editor); + wrap.appendChild(editor); + return lineNumbers; +} diff --git a/codejar.js b/codejar.js new file mode 100644 index 00000000..028afa0d --- /dev/null +++ b/codejar.js @@ -0,0 +1,401 @@ +function CodeJar(editor, highlight, opt = {}) { + const options = Object.assign({ tab: "\t" }, opt); + let listeners = []; + let history = []; + let at = -1; + let focus = false; + let callback; + let prev; // code content prior keydown event + let isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1; + editor.setAttribute("contentEditable", isFirefox ? "true" : "plaintext-only"); + editor.setAttribute("spellcheck", "false"); + editor.style.outline = "none"; + editor.style.overflowWrap = "break-word"; + editor.style.overflowY = "auto"; + editor.style.resize = "vertical"; + editor.style.whiteSpace = "pre-wrap"; + highlight(editor); + const debounceHighlight = debounce(() => { + const pos = save(); + highlight(editor); + restore(pos); + }, 30); + let recording = false; + const shouldRecord = (event) => { + return !isUndo(event) && !isRedo(event) + && event.key !== "Meta" + && event.key !== "Control" + && event.key !== "Alt" + && !event.key.startsWith("Arrow"); + }; + const debounceRecordHistory = debounce((event) => { + if (shouldRecord(event)) { + recordHistory(); + recording = false; + } + }, 300); + const on = (type, fn) => { + listeners.push([type, fn]); + editor.addEventListener(type, fn); + }; + on("keydown", event => { + if (event.defaultPrevented) + return; + prev = toString(); + handleNewLine(event); + handleTabCharacters(event); + handleJumpToBeginningOfLine(event); + handleSelfClosingCharacters(event); + handleUndoRedo(event); + if (shouldRecord(event) && !recording) { + recordHistory(); + recording = true; + } + }); + on("keyup", event => { + if (event.defaultPrevented) + return; + if (event.isComposing) + return; + if (prev !== toString()) + debounceHighlight(); + debounceRecordHistory(event); + if (callback) + callback(toString()); + }); + on("focus", _event => { + focus = true; + }); + on("blur", _event => { + focus = false; + }); + on("paste", event => { + recordHistory(); + handlePaste(event); + recordHistory(); + if (callback) + callback(toString()); + }); + function save() { + const s = window.getSelection(); + const pos = { start: 0, end: 0, dir: undefined }; + visit(editor, el => { + if (el === s.anchorNode && el === s.focusNode) { + pos.start += s.anchorOffset; + pos.end += s.focusOffset; + pos.dir = s.anchorOffset <= s.focusOffset ? "->" : "<-"; + return "stop"; + } + if (el === s.anchorNode) { + pos.start += s.anchorOffset; + if (!pos.dir) { + pos.dir = "->"; + } + else { + return "stop"; + } + } + else if (el === s.focusNode) { + pos.end += s.focusOffset; + if (!pos.dir) { + pos.dir = "<-"; + } + else { + return "stop"; + } + } + if (el.nodeType === Node.TEXT_NODE) { + if (pos.dir != "->") + pos.start += el.nodeValue.length; + if (pos.dir != "<-") + pos.end += el.nodeValue.length; + } + }); + return pos; + } + function restore(pos) { + const s = window.getSelection(); + let startNode, startOffset = 0; + let endNode, endOffset = 0; + if (!pos.dir) + pos.dir = "->"; + if (pos.start < 0) + pos.start = 0; + if (pos.end < 0) + pos.end = 0; + // Flip start and end if the direction reversed + if (pos.dir == "<-") { + const { start, end } = pos; + pos.start = end; + pos.end = start; + } + let current = 0; + visit(editor, el => { + if (el.nodeType !== Node.TEXT_NODE) + return; + const len = (el.nodeValue || "").length; + if (current + len >= pos.start) { + if (!startNode) { + startNode = el; + startOffset = pos.start - current; + } + if (current + len >= pos.end) { + endNode = el; + endOffset = pos.end - current; + return "stop"; + } + } + current += len; + }); + // If everything deleted place cursor at editor + if (!startNode) + startNode = editor; + if (!endNode) + endNode = editor; + // Flip back the selection + if (pos.dir == "<-") { + [startNode, startOffset, endNode, endOffset] = [endNode, endOffset, startNode, startOffset]; + } + s.setBaseAndExtent(startNode, startOffset, endNode, endOffset); + } + function beforeCursor() { + const s = window.getSelection(); + const r0 = s.getRangeAt(0); + const r = document.createRange(); + r.selectNodeContents(editor); + r.setEnd(r0.startContainer, r0.startOffset); + return r.toString(); + } + function afterCursor() { + const s = window.getSelection(); + const r0 = s.getRangeAt(0); + const r = document.createRange(); + r.selectNodeContents(editor); + r.setStart(r0.endContainer, r0.endOffset); + return r.toString(); + } + function handleNewLine(event) { + if (event.key === "Enter") { + const before = beforeCursor(); + const after = afterCursor(); + let [padding] = findPadding(before); + let newLinePadding = padding; + // If last symbol is "{" ident new line + if (before[before.length - 1] === "{") { + newLinePadding += options.tab; + } + if (isFirefox) { + preventDefault(event); + insert("\n" + newLinePadding); + } + else { + // Normal browsers + if (newLinePadding.length > 0) { + preventDefault(event); + insert("\n" + newLinePadding); + } + } + // Place adjacent "}" on next line + if (newLinePadding !== padding && after[0] === "}") { + const pos = save(); + insert("\n" + padding); + restore(pos); + } + } + } + function handleSelfClosingCharacters(event) { + const open = `([{'"`; + const close = `)]}'"`; + const codeAfter = afterCursor(); + if (close.includes(event.key) && codeAfter.substr(0, 1) === event.key) { + const pos = save(); + preventDefault(event); + pos.start = ++pos.end; + restore(pos); + } + else if (open.includes(event.key)) { + const pos = save(); + preventDefault(event); + const text = event.key + close[open.indexOf(event.key)]; + insert(text); + pos.start = ++pos.end; + restore(pos); + } + } + function handleTabCharacters(event) { + if (event.key === "Tab") { + preventDefault(event); + if (event.shiftKey) { + const before = beforeCursor(); + let [padding, start,] = findPadding(before); + if (padding.length > 0) { + const pos = save(); + // Remove full length tab or just remaining padding + const len = Math.min(options.tab.length, padding.length); + restore({ start, end: start + len }); + document.execCommand("delete"); + pos.start -= len; + pos.end -= len; + restore(pos); + } + } + else { + insert(options.tab); + } + } + } + function handleJumpToBeginningOfLine(event) { + if (event.key === "ArrowLeft" && event.metaKey) { + preventDefault(event); + const before = beforeCursor(); + let [padding, start, end] = findPadding(before); + if (before.endsWith(padding)) { + if (event.shiftKey) { + const pos = save(); + restore({ start, end: pos.end }); // Select from line start. + } + else { + restore({ start, end: start }); // Jump to line start. + } + } + else { + if (event.shiftKey) { + const pos = save(); + restore({ start: end, end: pos.end }); // Select from beginning of text. + } + else { + restore({ start: end, end }); // Jump to beginning of text. + } + } + } + } + function handleUndoRedo(event) { + if (isUndo(event)) { + preventDefault(event); + at--; + const record = history[at]; + if (record) { + editor.innerHTML = record.html; + restore(record.pos); + } + if (at < 0) + at = 0; + } + if (isRedo(event)) { + preventDefault(event); + at++; + const record = history[at]; + if (record) { + editor.innerHTML = record.html; + restore(record.pos); + } + if (at >= history.length) + at--; + } + } + function recordHistory() { + if (!focus) + return; + const html = editor.innerHTML; + const pos = save(); + const lastRecord = history[at]; + if (lastRecord) { + if (lastRecord.html === html + && lastRecord.pos.start === pos.start + && lastRecord.pos.end === pos.end) + return; + } + at++; + history[at] = { html, pos }; + history.splice(at + 1); + const maxHistory = 300; + if (at > maxHistory) { + at = maxHistory; + history.splice(0, 1); + } + } + function handlePaste(event) { + preventDefault(event); + const text = (event.originalEvent || event).clipboardData.getData("text/plain"); + const pos = save(); + insert(text); + highlight(editor); + restore({ start: pos.end + text.length, end: pos.end + text.length }); + } + function visit(editor, visitor) { + const queue = []; + if (editor.firstChild) + queue.push(editor.firstChild); + let el = queue.pop(); + while (el) { + if (visitor(el) === "stop") + break; + if (el.nextSibling) + queue.push(el.nextSibling); + if (el.firstChild) + queue.push(el.firstChild); + el = queue.pop(); + } + } + function isCtrl(event) { + return event.metaKey || event.ctrlKey; + } + function isUndo(event) { + return isCtrl(event) && !event.shiftKey && event.code === "KeyZ"; + } + function isRedo(event) { + return isCtrl(event) && event.shiftKey && event.code === "KeyZ"; + } + function insert(text) { + text = text + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + document.execCommand("insertHTML", false, text); + } + function debounce(cb, wait) { + let timeout = 0; + return (...args) => { + clearTimeout(timeout); + timeout = window.setTimeout(() => cb(...args), wait); + }; + } + function findPadding(text) { + // Find beginning of previous line. + let i = text.length - 1; + while (i >= 0 && text[i] !== "\n") + i--; + i++; + // Find padding of the line. + let j = i; + while (j < text.length && /[ \t]/.test(text[j])) + j++; + return [text.substring(i, j) || "", i, j]; + } + function toString() { + return editor.textContent || ""; + } + function preventDefault(event) { + event.preventDefault(); + } + return { + updateOptions(options) { + options = Object.assign(Object.assign({}, options), options); + }, + updateCode(code) { + editor.textContent = code; + highlight(editor); + }, + onUpdate(cb) { + callback = cb; + }, + toString, + destroy() { + for (let [type, fn] of listeners) { + editor.removeEventListener(type, fn); + } + }, + }; +} diff --git a/concurrency.html b/concurrency.html index 0139b8d0..b735ef4a 100644 --- a/concurrency.html +++ b/concurrency.html @@ -3,6 +3,10 @@ Concurrency – Wren + + + + + + + +
+
+
+

wren

+

a classy little scripting language

+
+
+
+
+ + +
+

Try Wren

+
+
+examples:   +hello +loop +fractal +
+ +
+ +

enter code below

+ +
+ run +
+ + +
System.print("hello wren")
+ +

no errors

+

code output

+
...
+ +
+
+
+ + + + + + + diff --git a/values.html b/values.html index 2b53abb0..e2a536d5 100644 --- a/values.html +++ b/values.html @@ -3,6 +3,10 @@ Values – Wren + + + +