module-based literate programming
This commit is contained in:
parent
9ef9d57f13
commit
b9218c8ace
|
@ -270,38 +270,42 @@ styles = ["tairu.css"]
|
||||||
% id = "01HPQCCV4R557T2SN7ES7Z4EJ7"
|
% id = "01HPQCCV4R557T2SN7ES7Z4EJ7"
|
||||||
- we can verify this logic with a bit of code; with a bit of luck, we should be able to narrow down our tileset into something a lot more manageable.
|
- we can verify this logic with a bit of code; with a bit of luck, we should be able to narrow down our tileset into something a lot more manageable.
|
||||||
|
|
||||||
|
% id = "01HPSY4Y19NQ6DZN10BP1KQEZN"
|
||||||
+ we'll start off by defining a bunch of variables to represent our ordinal directions:
|
+ we'll start off by defining a bunch of variables to represent our ordinal directions:
|
||||||
|
|
||||||
```javascript ordinal-directions
|
```javascript ordinal-directions
|
||||||
const E = 0b00000001;
|
export const E = 0b0000_0001;
|
||||||
const SE = 0b00000010;
|
export const SE = 0b0000_0010;
|
||||||
const S = 0b00000100;
|
export const S = 0b0000_0100;
|
||||||
const SW = 0b00001000;
|
export const SW = 0b0000_1000;
|
||||||
const W = 0b00010000;
|
export const W = 0b0001_0000;
|
||||||
const NW = 0b00100000;
|
export const NW = 0b0010_0000;
|
||||||
const N = 0b01000000;
|
export const N = 0b0100_0000;
|
||||||
const NE = 0b10000000;
|
export const NE = 0b1000_0000;
|
||||||
const ALL = E | SE | S | SW | W | NW | N | NE;
|
export const ALL = E | SE | S | SW | W | NW | N | NE;
|
||||||
```
|
```
|
||||||
|
|
||||||
as I've already said, we represent each direction using a single bit.
|
as I've already said, we represent each direction using a single bit.
|
||||||
|
|
||||||
|
% id = "01HPSY4Y19AW70YX8PPA7AS4DH"
|
||||||
- I'm using JavaScript by the way, because it's the native programming language of your web browser. read on to the end of this tangent to see why.
|
- I'm using JavaScript by the way, because it's the native programming language of your web browser. read on to the end of this tangent to see why.
|
||||||
|
|
||||||
|
% id = "01HPSY4Y19HPNXC54VP6TFFHXN"
|
||||||
- now I don't know about you, but I find the usual C-style way of checking whether a bit is set extremely hard to read, so let's take care of that:
|
- now I don't know about you, but I find the usual C-style way of checking whether a bit is set extremely hard to read, so let's take care of that:
|
||||||
|
|
||||||
```javascript ordinal-directions
|
```javascript ordinal-directions
|
||||||
function isSet(integer, bit) {
|
export function isSet(integer, bit) {
|
||||||
return (integer & bit) == bit;
|
return (integer & bit) == bit;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
% id = "01HPSY4Y1984H2FX6QY6K2KHKF"
|
||||||
- now we can write a function that will remove the aforementioned redundancies.
|
- now we can write a function that will remove the aforementioned redundancies.
|
||||||
the logic is quite simple - for southeast, we only allow it to be set if both south and east are also set, and so on and so forth.
|
the logic is quite simple - for southeast, we only allow it to be set if both south and east are also set, and so on and so forth.
|
||||||
|
|
||||||
```javascript ordinal-directions
|
```javascript ordinal-directions
|
||||||
// t is a tile index; variable name is short for brevity
|
// t is a tile index; variable name is short for brevity
|
||||||
function removeRedundancies(t) {
|
export function removeRedundancies(t) {
|
||||||
if (isSet(t, SE) && (!isSet(t, S) || !isSet(t, E))) {
|
if (isSet(t, SE) && (!isSet(t, S) || !isSet(t, E))) {
|
||||||
t &= ~SE;
|
t &= ~SE;
|
||||||
}
|
}
|
||||||
|
@ -318,10 +322,11 @@ styles = ["tairu.css"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
% id = "01HPSY4Y19HWQQ9XBW1DDGW68T"
|
||||||
- with that, we can find a set of all unique non-redundant combinations:
|
- with that, we can find a set of all unique non-redundant combinations:
|
||||||
|
|
||||||
```javascript ordinal-directions
|
```javascript ordinal-directions
|
||||||
function ordinalDirections() {
|
export function ordinalDirections() {
|
||||||
let unique = new Set();
|
let unique = new Set();
|
||||||
for (let i = 0; i <= ALL; ++i) {
|
for (let i = 0; i <= ALL; ++i) {
|
||||||
unique.add(removeRedundancies(i));
|
unique.add(removeRedundancies(i));
|
||||||
|
@ -330,11 +335,13 @@ styles = ["tairu.css"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
% id = "01HPSY4Y19KG8DC4SYXR1DJJ5F"
|
||||||
- by the way, I find it quite funny how JavaScript's [`Array.prototype.sort`] defaults to ASCII ordering *for all types.*
|
- by the way, I find it quite funny how JavaScript's [`Array.prototype.sort`] defaults to ASCII ordering *for all types.*
|
||||||
even numbers! ain't that silly?
|
even numbers! ain't that silly?
|
||||||
|
|
||||||
[`Array.prototype.sort`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
|
[`Array.prototype.sort`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
|
||||||
|
|
||||||
|
% id = "01HPSY4Y19V62YKTGK3TTKEB38"
|
||||||
- and now it's time to _Let It Cook™_:
|
- and now it's time to _Let It Cook™_:
|
||||||
|
|
||||||
```javascript ordinal-directions
|
```javascript ordinal-directions
|
||||||
|
@ -346,18 +353,62 @@ styles = ["tairu.css"]
|
||||||
47
|
47
|
||||||
```
|
```
|
||||||
|
|
||||||
|
% id = "01HPSY4Y194DYYDGSAT83MPQFR"
|
||||||
- forty seven! that's how many unique tiles we actually need.
|
- forty seven! that's how many unique tiles we actually need.
|
||||||
|
|
||||||
|
% id = "01HPSY4Y19C303Z595KNVXYYVS"
|
||||||
- you may find pixel art tutorials saying you need forty *eight* and not forty *seven*, but that is not quite correct -
|
- you may find pixel art tutorials saying you need forty *eight* and not forty *seven*, but that is not quite correct -
|
||||||
the forty eighth tile is actually just the empty tile! saying it's part of the tileset is quite misleading IMO.
|
the forty eighth tile is actually just the empty tile! saying it's part of the tileset is quite misleading IMO.
|
||||||
|
|
||||||
|
% id = "01HPSY4Y19TM2K2WN06HHEM3D0"
|
||||||
- phew... the nesting's getting quite unwieldy, let's wrap up this tangent and return back to doing some bitwise autotiling!
|
- phew... the nesting's getting quite unwieldy, let's wrap up this tangent and return back to doing some bitwise autotiling!
|
||||||
|
|
||||||
|
% id = "01HPSY4Y192FZ37K3KXZM90K9J"
|
||||||
- so in reality we actually only need 47 tiles and not 256 - that's a whole lot less, that's 81.640625% less tiles we have to draw!
|
- so in reality we actually only need 47 tiles and not 256 - that's a whole lot less, that's 81.640625% less tiles we have to draw!
|
||||||
|
|
||||||
|
% id = "01HPSY4Y19HEBWBTNMDMM0AZSC"
|
||||||
- and it's even possible to autogenerate most of them given just a few smaller 4x4 pieces - but for now, let's not go down that path.\
|
- and it's even possible to autogenerate most of them given just a few smaller 4x4 pieces - but for now, let's not go down that path.\
|
||||||
maybe another time.
|
maybe another time.
|
||||||
|
|
||||||
|
- so we only need to draw 47 tiles, but to actually display them in a game we still need to pack them into an image.
|
||||||
|
|
||||||
|
- we *could* use a similar approach to the 16 tile version, but that would leave us with lots of wasted space!
|
||||||
|
|
||||||
|
- think that with this redundancy elimination approach most of the tiles will never even be looked up by the renderer, because the bit combinations will be collapsed into a more canonical form before the lookup.
|
||||||
|
|
||||||
|
- we could also use the approach I mentioned briefly [here][branch:01HPQCCV4RB65D5Q4RANJKGC0D], which involves introducing a lookup table - which sounds reasonable, so let's do it!
|
||||||
|
|
||||||
|
- I don't want to write the lookup table by hand, so let's generate it! I'll reuse the redundancy elimination code from before to make this easier.
|
||||||
|
|
||||||
|
- we'll start by obtaining our ordinal directions array again:
|
||||||
|
|
||||||
|
```javascript ordinal-directions
|
||||||
|
export let xToConnectionBitSet = ordinalDirections();
|
||||||
|
```
|
||||||
|
|
||||||
|
- then we'll turn that array upside down... in other words, invert the index-value relationship, so that we can look up which X position in the tile strip to use for a specific connection combination.
|
||||||
|
|
||||||
|
remember that our array has only 256 values, so it should be pretty cheap to represent using a `Uint8Array`:
|
||||||
|
|
||||||
|
```javascript ordinal-directions
|
||||||
|
export let connectionBitSetToX = new Uint8Array(256);
|
||||||
|
for (let i = 0; i < xToConnectionBitSet.length; ++i) {
|
||||||
|
connectionBitSetToX[xToConnectionBitSet[i]] = i;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- and there we go! we now have a mapping from our bitset to positions within the tile strip. try to play around with the code example to see which bitsets correspond to which position!
|
||||||
|
|
||||||
|
```javascript ordinal-directions
|
||||||
|
console.log(connectionBitSetToX[E | SE | S]);
|
||||||
|
```
|
||||||
|
```output ordinal-directions
|
||||||
|
4
|
||||||
|
```
|
||||||
|
|
||||||
|
TODO: The value from the previous output should not leak into this one. how do we do this? do we emit extra `pushMessage` calls inbetween the editors so that they know when to end?
|
||||||
|
maybe use a `classic` context instead of a module? or maybe have a way of sharing data between outputs? (return value?)
|
||||||
|
|
||||||
% id = "01HPD4XQPWT9N8X9BD9GKWD78F"
|
% id = "01HPD4XQPWT9N8X9BD9GKWD78F"
|
||||||
- bitwise autotiling is a really cool technique that I've used in plenty of games in the past.
|
- bitwise autotiling is a really cool technique that I've used in plenty of games in the past.
|
||||||
|
|
||||||
|
@ -423,6 +474,7 @@ styles = ["tairu.css"]
|
||||||
% id = "01HPD4XQPWP847T0EAM0FJ88T4"
|
% id = "01HPD4XQPWP847T0EAM0FJ88T4"
|
||||||
- then vines
|
- then vines
|
||||||
|
|
||||||
|
% id = "01HPSY4Y19FA2HGYE4F3Y9NJ57"
|
||||||
- well... it's even simpler than that in terms of graphical presentation, but we'll get to that.
|
- well... it's even simpler than that in terms of graphical presentation, but we'll get to that.
|
||||||
|
|
||||||
% id = "01HPD4XQPWK58Z63X6962STADR"
|
% id = "01HPD4XQPWK58Z63X6962STADR"
|
||||||
|
|
|
@ -240,8 +240,12 @@ where
|
||||||
program_name,
|
program_name,
|
||||||
} => {
|
} => {
|
||||||
self.write(match kind {
|
self.write(match kind {
|
||||||
LiterateCodeKind::Input => "<th-literate-editor ",
|
LiterateCodeKind::Input => {
|
||||||
LiterateCodeKind::Output => "<th-literate-output ",
|
"<th-literate-program data-mode=\"input\" "
|
||||||
|
}
|
||||||
|
LiterateCodeKind::Output => {
|
||||||
|
"<th-literate-program data-mode=\"output\" "
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
self.write("data-program=\"")?;
|
self.write("data-program=\"")?;
|
||||||
escape_html(&mut self.writer, program_name)?;
|
escape_html(&mut self.writer, program_name)?;
|
||||||
|
@ -368,14 +372,7 @@ where
|
||||||
Tag::CodeBlock(kind) => {
|
Tag::CodeBlock(kind) => {
|
||||||
self.write(match kind {
|
self.write(match kind {
|
||||||
CodeBlockKind::Fenced(language) => match CodeBlockMode::parse(&language) {
|
CodeBlockKind::Fenced(language) => match CodeBlockMode::parse(&language) {
|
||||||
CodeBlockMode::LiterateProgram {
|
CodeBlockMode::LiterateProgram { .. } => "</th-literate-program>",
|
||||||
kind: LiterateCodeKind::Input,
|
|
||||||
..
|
|
||||||
} => "</th-literate-editor>",
|
|
||||||
CodeBlockMode::LiterateProgram {
|
|
||||||
kind: LiterateCodeKind::Output,
|
|
||||||
..
|
|
||||||
} => "</th-literate-output>",
|
|
||||||
_ => "</code></pre>",
|
_ => "</code></pre>",
|
||||||
},
|
},
|
||||||
_ => "</code></pre>\n",
|
_ => "</code></pre>\n",
|
||||||
|
|
|
@ -171,8 +171,7 @@ h4 {
|
||||||
pre,
|
pre,
|
||||||
code,
|
code,
|
||||||
kbd,
|
kbd,
|
||||||
th-literate-editor,
|
th-literate-program {
|
||||||
th-literate-output {
|
|
||||||
--recursive-mono: 1.0;
|
--recursive-mono: 1.0;
|
||||||
--recursive-casl: 0.0;
|
--recursive-casl: 0.0;
|
||||||
--recursive-slnt: 0.0;
|
--recursive-slnt: 0.0;
|
||||||
|
@ -213,13 +212,13 @@ body {
|
||||||
/* Make code examples a little prettier by giving them visual separation from the rest of the page */
|
/* Make code examples a little prettier by giving them visual separation from the rest of the page */
|
||||||
|
|
||||||
code,
|
code,
|
||||||
th-literate-editor {
|
th-literate-program {
|
||||||
padding: 3px 4px;
|
padding: 3px 4px;
|
||||||
background-color: var(--shaded-background);
|
background-color: var(--shaded-background);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
th-literate-editor,
|
th-literate-program,
|
||||||
th-literate-output {
|
th-literate-output {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
@ -231,8 +230,7 @@ kbd {
|
||||||
}
|
}
|
||||||
|
|
||||||
pre,
|
pre,
|
||||||
th-literate-editor,
|
th-literate-program {
|
||||||
th-literate-output {
|
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
margin: 12px 0;
|
margin: 12px 0;
|
||||||
background-color: var(--shaded-against-background);
|
background-color: var(--shaded-against-background);
|
||||||
|
@ -241,22 +239,20 @@ th-literate-output {
|
||||||
transition: background-color var(--transition-duration);
|
transition: background-color var(--transition-duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
th-literate-editor,
|
th-literate-program {
|
||||||
th-literate-output {
|
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree summary:hover {
|
.tree summary:hover {
|
||||||
|
|
||||||
& pre,
|
& pre,
|
||||||
& th-literate-editor,
|
& th-literate-program {
|
||||||
& th-literate-output {
|
|
||||||
background-color: var(--shaded-against-background-twice);
|
background-color: var(--shaded-against-background-twice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pre>code,
|
pre>code,
|
||||||
th-literate-output>code {
|
th-literate-program>code {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: none;
|
background: none;
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
|
@ -518,13 +514,13 @@ img[is="th-emoji"] {
|
||||||
|
|
||||||
/* Literate programming support */
|
/* Literate programming support */
|
||||||
|
|
||||||
th-literate-editor {
|
th-literate-program[data-mode="input"] {
|
||||||
/* Override the cursor with an I-beam, because the editor captures clicks and does not bubble
|
/* Override the cursor with an I-beam, because the editor captures clicks and does not bubble
|
||||||
them back up to the caller */
|
them back up to the caller */
|
||||||
cursor: text;
|
cursor: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
th-literate-output {
|
th-literate-program[data-mode="output"] {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
& code {
|
& code {
|
||||||
|
|
|
@ -5,74 +5,68 @@ let literatePrograms = new Map();
|
||||||
function getLiterateProgram(name) {
|
function getLiterateProgram(name) {
|
||||||
if (literatePrograms.get(name) == null) {
|
if (literatePrograms.get(name) == null) {
|
||||||
literatePrograms.set(name, {
|
literatePrograms.set(name, {
|
||||||
editors: [],
|
frames: [],
|
||||||
onChanged: [],
|
onChanged: [],
|
||||||
|
|
||||||
|
outputCount: 0,
|
||||||
|
|
||||||
|
nextOutputIndex() {
|
||||||
|
let index = this.outputCount;
|
||||||
|
++this.outputCount;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return literatePrograms.get(name);
|
return literatePrograms.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLiterateProgramSourceCode(name) {
|
function getLiterateProgramWorkerCommands(name) {
|
||||||
let sources = [];
|
let commands = [];
|
||||||
let literateProgram = getLiterateProgram(name);
|
let literateProgram = getLiterateProgram(name);
|
||||||
for (let editor of literateProgram.editors) {
|
for (let frame of literateProgram.frames) {
|
||||||
sources.push(editor.textContent);
|
if (frame.mode == "input") {
|
||||||
|
commands.push({ kind: "module", source: frame.textContent });
|
||||||
|
} else if (frame.mode == "output") {
|
||||||
|
commands.push({ kind: "output", expected: frame.textContent });
|
||||||
}
|
}
|
||||||
return sources.join("\n");
|
}
|
||||||
|
return commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
class LiterateEditor extends HTMLElement {
|
class InputMode {
|
||||||
constructor() {
|
constructor(frame) {
|
||||||
super();
|
this.frame = frame;
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
this.codeJar = CodeJar(frame, InputMode.highlight);
|
||||||
this.literateProgramName = this.getAttribute("data-program");
|
|
||||||
getLiterateProgram(this.literateProgramName).editors.push(this);
|
|
||||||
|
|
||||||
this.codeJar = CodeJar(this, LiterateEditor.highlight);
|
|
||||||
this.codeJar.onUpdate(() => {
|
this.codeJar.onUpdate(() => {
|
||||||
let literateProgram = getLiterateProgram(this.literateProgramName);
|
for (let handler of frame.program.onChanged) {
|
||||||
for (let handler of literateProgram.onChanged) {
|
handler(frame.programName);
|
||||||
handler(this.literateProgramName);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.addEventListener("click", event => event.preventDefault());
|
frame.addEventListener("click", event => event.preventDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
static highlight(editor) {
|
static highlight(frame) {
|
||||||
// TODO: Syntax highlighting
|
// TODO: Syntax highlighting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("th-literate-editor", LiterateEditor);
|
class OutputMode {
|
||||||
|
constructor(frame) {
|
||||||
function debounce(callback, timeout) {
|
|
||||||
let timeoutId = 0;
|
|
||||||
return (...args) => {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeoutId = window.setTimeout(() => callback(...args), timeout);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class LiterateOutput extends HTMLElement {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.clearResultsOnNextOutput = false;
|
this.clearResultsOnNextOutput = false;
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
this.frame = frame;
|
||||||
this.literateProgramName = this.getAttribute("data-program");
|
|
||||||
|
this.frame.program.onChanged.push(_ => this.evaluate());
|
||||||
|
this.outputIndex = this.frame.program.nextOutputIndex();
|
||||||
|
|
||||||
this.evaluate();
|
this.evaluate();
|
||||||
|
|
||||||
getLiterateProgram(this.literateProgramName).onChanged.push(_ => this.evaluate());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluate = () => {
|
evaluate() {
|
||||||
// This is a small bit of debouncing. If we cleared the output right away, the page would
|
// This is a small bit of debouncing. If we cleared the output right away, the page would
|
||||||
// jitter around irritatingly
|
// jitter around irritatingly.
|
||||||
this.clearResultsOnNextOutput = true;
|
this.clearResultsOnNextOutput = true;
|
||||||
|
|
||||||
if (this.worker != null) {
|
if (this.worker != null) {
|
||||||
|
@ -80,23 +74,23 @@ class LiterateOutput extends HTMLElement {
|
||||||
}
|
}
|
||||||
this.worker = new Worker(`${TREEHOUSE_SITE}/static/js/components/literate-programming/worker.js`, {
|
this.worker = new Worker(`${TREEHOUSE_SITE}/static/js/components/literate-programming/worker.js`, {
|
||||||
type: "module",
|
type: "module",
|
||||||
name: `evaluate LiterateOutput ${this.literateProgramName}`
|
name: `evaluate LiterateOutput ${this.frame.programName}`
|
||||||
});
|
});
|
||||||
|
|
||||||
this.worker.addEventListener("message", event => {
|
this.worker.addEventListener("message", event => {
|
||||||
let message = event.data;
|
let message = event.data;
|
||||||
if (message.kind == "evalComplete") {
|
if (message.kind == "evalComplete") {
|
||||||
this.worker.terminate();
|
this.worker.terminate();
|
||||||
} else if (message.kind == "output") {
|
} else if (message.kind == "output" && message.outputIndex == this.outputIndex) {
|
||||||
this.addOutput(message.output);
|
this.addOutput(message.output);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.worker.postMessage({
|
this.worker.postMessage({
|
||||||
action: "eval",
|
action: "eval",
|
||||||
input: getLiterateProgramSourceCode(this.literateProgramName),
|
input: getLiterateProgramWorkerCommands(this.frame.programName),
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
addOutput(output) {
|
addOutput(output) {
|
||||||
if (this.clearResultsOnNextOutput) {
|
if (this.clearResultsOnNextOutput) {
|
||||||
|
@ -112,10 +106,13 @@ class LiterateOutput extends HTMLElement {
|
||||||
line.classList.add("output");
|
line.classList.add("output");
|
||||||
line.classList.add(output.kind);
|
line.classList.add(output.kind);
|
||||||
|
|
||||||
line.textContent = output.message.map(x => {
|
// One day this will be more fancy. Today is not that day.
|
||||||
|
line.textContent = output.message
|
||||||
|
.map(x => {
|
||||||
if (typeof x === "object") return JSON.stringify(x);
|
if (typeof x === "object") return JSON.stringify(x);
|
||||||
else return x + "";
|
else return x + "";
|
||||||
}).join(" ");
|
})
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
if (output.kind == "result") {
|
if (output.kind == "result") {
|
||||||
let returnValueText = document.createElement("span");
|
let returnValueText = document.createElement("span");
|
||||||
|
@ -124,12 +121,30 @@ class LiterateOutput extends HTMLElement {
|
||||||
line.insertBefore(returnValueText, line.firstChild);
|
line.insertBefore(returnValueText, line.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.appendChild(line);
|
this.frame.appendChild(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearResults() {
|
clearResults() {
|
||||||
this.replaceChildren();
|
this.frame.replaceChildren();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("th-literate-output", LiterateOutput);
|
class LiterateProgram extends HTMLElement {
|
||||||
|
connectedCallback() {
|
||||||
|
this.programName = this.getAttribute("data-program");
|
||||||
|
this.program.frames.push(this);
|
||||||
|
|
||||||
|
this.mode = this.getAttribute("data-mode");
|
||||||
|
if (this.mode == "input") {
|
||||||
|
this.modeImpl = new InputMode(this);
|
||||||
|
} else if (this.mode == "output") {
|
||||||
|
this.modeImpl = new OutputMode(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get program() {
|
||||||
|
return getLiterateProgram(this.programName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("th-literate-program", LiterateProgram);
|
||||||
|
|
|
@ -1,26 +1,50 @@
|
||||||
console = {
|
let outputIndex = 0;
|
||||||
|
|
||||||
|
let debugLog = console.log;
|
||||||
|
|
||||||
|
globalThis.console = {
|
||||||
log(...message) {
|
log(...message) {
|
||||||
postMessage({
|
postMessage({
|
||||||
kind: "output",
|
kind: "output",
|
||||||
output: {
|
output: {
|
||||||
kind: "log",
|
kind: "log",
|
||||||
message: [...message],
|
message: [...message],
|
||||||
}
|
},
|
||||||
|
outputIndex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
addEventListener("message", event => {
|
async function withTemporaryGlobalScope(callback) {
|
||||||
|
let state = {
|
||||||
|
oldValues: {},
|
||||||
|
set(key, value) {
|
||||||
|
this.oldValues[key] = globalThis[key];
|
||||||
|
globalThis[key] = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await callback(state);
|
||||||
|
for (let key in state.oldValues) {
|
||||||
|
globalThis[key] = state.oldValues[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListener("message", async event => {
|
||||||
let message = event.data;
|
let message = event.data;
|
||||||
if (message.action == "eval") {
|
if (message.action == "eval") {
|
||||||
|
outputIndex = 0;
|
||||||
try {
|
try {
|
||||||
let func = new Function(message.input);
|
await withTemporaryGlobalScope(async scope => {
|
||||||
let result = func.apply({});
|
for (let command of message.input) {
|
||||||
postMessage({
|
if (command.kind == "module") {
|
||||||
kind: "output",
|
let blobUrl = URL.createObjectURL(new Blob([command.source], { type: "text/javascript" }));
|
||||||
output: {
|
let module = await import(blobUrl);
|
||||||
kind: "result",
|
for (let exportedKey in module) {
|
||||||
message: [result],
|
scope.set(exportedKey, module[exportedKey]);
|
||||||
|
}
|
||||||
|
} else if (command.kind == "output") {
|
||||||
|
++outputIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -29,7 +53,8 @@ addEventListener("message", event => {
|
||||||
output: {
|
output: {
|
||||||
kind: "error",
|
kind: "error",
|
||||||
message: [error.toString()],
|
message: [error.toString()],
|
||||||
}
|
},
|
||||||
|
outputIndex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,6 @@ export class TileEditor extends Frame {
|
||||||
this.tileSize = parseInt(this.getAttribute("data-tile-size"));
|
this.tileSize = parseInt(this.getAttribute("data-tile-size"));
|
||||||
|
|
||||||
let tilemapId = this.getAttribute("data-tilemap-id");
|
let tilemapId = this.getAttribute("data-tilemap-id");
|
||||||
console.log(tilemapRegistry);
|
|
||||||
if (tilemapId != null) {
|
if (tilemapId != null) {
|
||||||
this.tilemap = tilemapRegistry[this.getAttribute("data-tilemap-id")];
|
this.tilemap = tilemapRegistry[this.getAttribute("data-tilemap-id")];
|
||||||
} else {
|
} else {
|
||||||
|
@ -188,5 +187,3 @@ export class TileEditor extends Frame {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defineFrame("tairu-editor", TileEditor);
|
defineFrame("tairu-editor", TileEditor);
|
||||||
|
|
||||||
console.log("tairu editor loaded");
|
|
||||||
|
|
|
@ -163,15 +163,12 @@ function expandDetailsRecursively(element) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigateToPage(page) {
|
function navigateToPage(page) {
|
||||||
console.log(page);
|
|
||||||
window.location.pathname = `${page}.html`
|
window.location.pathname = `${page}.html`
|
||||||
}
|
}
|
||||||
|
|
||||||
async function navigateToBranch(fragment) {
|
async function navigateToBranch(fragment) {
|
||||||
if (fragment.length == 0) return;
|
if (fragment.length == 0) return;
|
||||||
|
|
||||||
console.log(`nagivating to branch: ${fragment}`);
|
|
||||||
|
|
||||||
let element = document.getElementById(fragment);
|
let element = document.getElementById(fragment);
|
||||||
if (element !== null) {
|
if (element !== null) {
|
||||||
// If the element is already loaded on the page, we're good.
|
// If the element is already loaded on the page, we're good.
|
||||||
|
@ -179,7 +176,6 @@ async function navigateToBranch(fragment) {
|
||||||
rehash();
|
rehash();
|
||||||
} else {
|
} else {
|
||||||
// The element is not loaded, we need to load the tree that has it.
|
// The element is not loaded, we need to load the tree that has it.
|
||||||
console.log("element is not loaded");
|
|
||||||
let parts = fragment.split(':');
|
let parts = fragment.split(':');
|
||||||
if (parts.length >= 2) {
|
if (parts.length >= 2) {
|
||||||
let [page, _id] = parts;
|
let [page, _id] = parts;
|
||||||
|
@ -189,7 +185,6 @@ async function navigateToBranch(fragment) {
|
||||||
// navigation maps with roots other than index. Currently though only index is
|
// navigation maps with roots other than index. Currently though only index is
|
||||||
// generated so that doesn't matter.
|
// generated so that doesn't matter.
|
||||||
let [_root, ...path] = fullPath;
|
let [_root, ...path] = fullPath;
|
||||||
console.log(`_root: ${_root}, path: ${path}`)
|
|
||||||
if (path !== undefined) {
|
if (path !== undefined) {
|
||||||
let isNotAtIndexHtml =
|
let isNotAtIndexHtml =
|
||||||
window.location.pathname != "" &&
|
window.location.pathname != "" &&
|
||||||
|
|
Loading…
Reference in a new issue