module-based literate programming
This commit is contained in:
		
							parent
							
								
									9ef9d57f13
								
							
						
					
					
						commit
						b9218c8ace
					
				
					 7 changed files with 183 additions and 106 deletions
				
			
		| 
						 | 
				
			
			@ -270,38 +270,42 @@ styles = ["tairu.css"]
 | 
			
		|||
            % 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.
 | 
			
		||||
 | 
			
		||||
                % id = "01HPSY4Y19NQ6DZN10BP1KQEZN"
 | 
			
		||||
                + we'll start off by defining a bunch of variables to represent our ordinal directions:
 | 
			
		||||
 | 
			
		||||
                ```javascript ordinal-directions
 | 
			
		||||
                const E = 0b00000001;
 | 
			
		||||
                const SE = 0b00000010;
 | 
			
		||||
                const S = 0b00000100;
 | 
			
		||||
                const SW = 0b00001000;
 | 
			
		||||
                const W = 0b00010000;
 | 
			
		||||
                const NW = 0b00100000;
 | 
			
		||||
                const N = 0b01000000;
 | 
			
		||||
                const NE = 0b10000000;
 | 
			
		||||
                const ALL = E | SE | S | SW | W | NW | N | NE;
 | 
			
		||||
                export const E = 0b0000_0001;
 | 
			
		||||
                export const SE = 0b0000_0010;
 | 
			
		||||
                export const S = 0b0000_0100;
 | 
			
		||||
                export const SW = 0b0000_1000;
 | 
			
		||||
                export const W = 0b0001_0000;
 | 
			
		||||
                export const NW = 0b0010_0000;
 | 
			
		||||
                export const N = 0b0100_0000;
 | 
			
		||||
                export const NE = 0b1000_0000;
 | 
			
		||||
                export const ALL = E | SE | S | SW | W | NW | N | NE;
 | 
			
		||||
                ```
 | 
			
		||||
 | 
			
		||||
                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.
 | 
			
		||||
 | 
			
		||||
                % 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:
 | 
			
		||||
 | 
			
		||||
                ```javascript ordinal-directions
 | 
			
		||||
                function isSet(integer, bit) {
 | 
			
		||||
                export function isSet(integer, bit) {
 | 
			
		||||
                    return (integer & bit) == bit;
 | 
			
		||||
                }
 | 
			
		||||
                ```
 | 
			
		||||
 | 
			
		||||
                % id = "01HPSY4Y1984H2FX6QY6K2KHKF"
 | 
			
		||||
                - 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.
 | 
			
		||||
 | 
			
		||||
                ```javascript ordinal-directions
 | 
			
		||||
                // 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))) {
 | 
			
		||||
                        t &= ~SE;
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			@ -318,10 +322,11 @@ styles = ["tairu.css"]
 | 
			
		|||
                }
 | 
			
		||||
                ```
 | 
			
		||||
 | 
			
		||||
                % id = "01HPSY4Y19HWQQ9XBW1DDGW68T"
 | 
			
		||||
                - with that, we can find a set of all unique non-redundant combinations:
 | 
			
		||||
 | 
			
		||||
                ```javascript ordinal-directions
 | 
			
		||||
                function ordinalDirections() {
 | 
			
		||||
                export function ordinalDirections() {
 | 
			
		||||
                    let unique = new Set();
 | 
			
		||||
                    for (let i = 0; i <= ALL; ++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.*
 | 
			
		||||
                    even numbers! ain't that silly?
 | 
			
		||||
 | 
			
		||||
                    [`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™_:
 | 
			
		||||
 | 
			
		||||
                ```javascript ordinal-directions
 | 
			
		||||
| 
						 | 
				
			
			@ -346,18 +353,62 @@ styles = ["tairu.css"]
 | 
			
		|||
                47
 | 
			
		||||
                ```
 | 
			
		||||
 | 
			
		||||
                    % id = "01HPSY4Y194DYYDGSAT83MPQFR"
 | 
			
		||||
                    - 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 -
 | 
			
		||||
                    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!
 | 
			
		||||
 | 
			
		||||
        % 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!
 | 
			
		||||
 | 
			
		||||
            % 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.\
 | 
			
		||||
            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"
 | 
			
		||||
- 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"
 | 
			
		||||
            - then vines
 | 
			
		||||
 | 
			
		||||
    % id = "01HPSY4Y19FA2HGYE4F3Y9NJ57"
 | 
			
		||||
    - well... it's even simpler than that in terms of graphical presentation, but we'll get to that.
 | 
			
		||||
 | 
			
		||||
    % id = "01HPD4XQPWK58Z63X6962STADR"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -240,8 +240,12 @@ where
 | 
			
		|||
                            program_name,
 | 
			
		||||
                        } => {
 | 
			
		||||
                            self.write(match kind {
 | 
			
		||||
                                LiterateCodeKind::Input => "<th-literate-editor ",
 | 
			
		||||
                                LiterateCodeKind::Output => "<th-literate-output ",
 | 
			
		||||
                                LiterateCodeKind::Input => {
 | 
			
		||||
                                    "<th-literate-program data-mode=\"input\" "
 | 
			
		||||
                                }
 | 
			
		||||
                                LiterateCodeKind::Output => {
 | 
			
		||||
                                    "<th-literate-program data-mode=\"output\" "
 | 
			
		||||
                                }
 | 
			
		||||
                            })?;
 | 
			
		||||
                            self.write("data-program=\"")?;
 | 
			
		||||
                            escape_html(&mut self.writer, program_name)?;
 | 
			
		||||
| 
						 | 
				
			
			@ -368,14 +372,7 @@ where
 | 
			
		|||
            Tag::CodeBlock(kind) => {
 | 
			
		||||
                self.write(match kind {
 | 
			
		||||
                    CodeBlockKind::Fenced(language) => match CodeBlockMode::parse(&language) {
 | 
			
		||||
                        CodeBlockMode::LiterateProgram {
 | 
			
		||||
                            kind: LiterateCodeKind::Input,
 | 
			
		||||
                            ..
 | 
			
		||||
                        } => "</th-literate-editor>",
 | 
			
		||||
                        CodeBlockMode::LiterateProgram {
 | 
			
		||||
                            kind: LiterateCodeKind::Output,
 | 
			
		||||
                            ..
 | 
			
		||||
                        } => "</th-literate-output>",
 | 
			
		||||
                        CodeBlockMode::LiterateProgram { .. } => "</th-literate-program>",
 | 
			
		||||
                        _ => "</code></pre>",
 | 
			
		||||
                    },
 | 
			
		||||
                    _ => "</code></pre>\n",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -171,8 +171,7 @@ h4 {
 | 
			
		|||
pre,
 | 
			
		||||
code,
 | 
			
		||||
kbd,
 | 
			
		||||
th-literate-editor,
 | 
			
		||||
th-literate-output {
 | 
			
		||||
th-literate-program {
 | 
			
		||||
    --recursive-mono: 1.0;
 | 
			
		||||
    --recursive-casl: 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 */
 | 
			
		||||
 | 
			
		||||
code,
 | 
			
		||||
th-literate-editor {
 | 
			
		||||
th-literate-program {
 | 
			
		||||
    padding: 3px 4px;
 | 
			
		||||
    background-color: var(--shaded-background);
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
th-literate-editor,
 | 
			
		||||
th-literate-program,
 | 
			
		||||
th-literate-output {
 | 
			
		||||
    display: block;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -231,8 +230,7 @@ kbd {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
pre,
 | 
			
		||||
th-literate-editor,
 | 
			
		||||
th-literate-output {
 | 
			
		||||
th-literate-program {
 | 
			
		||||
    padding: 8px 12px;
 | 
			
		||||
    margin: 12px 0;
 | 
			
		||||
    background-color: var(--shaded-against-background);
 | 
			
		||||
| 
						 | 
				
			
			@ -241,22 +239,20 @@ th-literate-output {
 | 
			
		|||
    transition: background-color var(--transition-duration);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
th-literate-editor,
 | 
			
		||||
th-literate-output {
 | 
			
		||||
th-literate-program {
 | 
			
		||||
    white-space: pre;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tree summary:hover {
 | 
			
		||||
 | 
			
		||||
    & pre,
 | 
			
		||||
    & th-literate-editor,
 | 
			
		||||
    & th-literate-output {
 | 
			
		||||
    & th-literate-program {
 | 
			
		||||
        background-color: var(--shaded-against-background-twice);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pre>code,
 | 
			
		||||
th-literate-output>code {
 | 
			
		||||
th-literate-program>code {
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    background: none;
 | 
			
		||||
    border-radius: 0px;
 | 
			
		||||
| 
						 | 
				
			
			@ -518,13 +514,13 @@ img[is="th-emoji"] {
 | 
			
		|||
 | 
			
		||||
/* 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
 | 
			
		||||
       them back up to the caller */
 | 
			
		||||
    cursor: text;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
th-literate-output {
 | 
			
		||||
th-literate-program[data-mode="output"] {
 | 
			
		||||
    position: relative;
 | 
			
		||||
 | 
			
		||||
    & code {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,74 +5,68 @@ let literatePrograms = new Map();
 | 
			
		|||
function getLiterateProgram(name) {
 | 
			
		||||
    if (literatePrograms.get(name) == null) {
 | 
			
		||||
        literatePrograms.set(name, {
 | 
			
		||||
            editors: [],
 | 
			
		||||
            frames: [],
 | 
			
		||||
            onChanged: [],
 | 
			
		||||
 | 
			
		||||
            outputCount: 0,
 | 
			
		||||
 | 
			
		||||
            nextOutputIndex() {
 | 
			
		||||
                let index = this.outputCount;
 | 
			
		||||
                ++this.outputCount;
 | 
			
		||||
                return index;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    return literatePrograms.get(name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getLiterateProgramSourceCode(name) {
 | 
			
		||||
    let sources = [];
 | 
			
		||||
function getLiterateProgramWorkerCommands(name) {
 | 
			
		||||
    let commands = [];
 | 
			
		||||
    let literateProgram = getLiterateProgram(name);
 | 
			
		||||
    for (let editor of literateProgram.editors) {
 | 
			
		||||
        sources.push(editor.textContent);
 | 
			
		||||
    for (let frame of literateProgram.frames) {
 | 
			
		||||
        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 {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
    }
 | 
			
		||||
class InputMode {
 | 
			
		||||
    constructor(frame) {
 | 
			
		||||
        this.frame = frame;
 | 
			
		||||
 | 
			
		||||
    connectedCallback() {
 | 
			
		||||
        this.literateProgramName = this.getAttribute("data-program");
 | 
			
		||||
        getLiterateProgram(this.literateProgramName).editors.push(this);
 | 
			
		||||
 | 
			
		||||
        this.codeJar = CodeJar(this, LiterateEditor.highlight);
 | 
			
		||||
        this.codeJar = CodeJar(frame, InputMode.highlight);
 | 
			
		||||
        this.codeJar.onUpdate(() => {
 | 
			
		||||
            let literateProgram = getLiterateProgram(this.literateProgramName);
 | 
			
		||||
            for (let handler of literateProgram.onChanged) {
 | 
			
		||||
                handler(this.literateProgramName);
 | 
			
		||||
            for (let handler of frame.program.onChanged) {
 | 
			
		||||
                handler(frame.programName);
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        this.addEventListener("click", event => event.preventDefault());
 | 
			
		||||
        frame.addEventListener("click", event => event.preventDefault());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static highlight(editor) {
 | 
			
		||||
    static highlight(frame) {
 | 
			
		||||
        // TODO: Syntax highlighting
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
customElements.define("th-literate-editor", LiterateEditor);
 | 
			
		||||
 | 
			
		||||
function debounce(callback, timeout) {
 | 
			
		||||
    let timeoutId = 0;
 | 
			
		||||
    return (...args) => {
 | 
			
		||||
        clearTimeout(timeout);
 | 
			
		||||
        timeoutId = window.setTimeout(() => callback(...args), timeout);
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class LiterateOutput extends HTMLElement {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
class OutputMode {
 | 
			
		||||
    constructor(frame) {
 | 
			
		||||
        this.clearResultsOnNextOutput = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    connectedCallback() {
 | 
			
		||||
        this.literateProgramName = this.getAttribute("data-program");
 | 
			
		||||
        this.frame = frame;
 | 
			
		||||
 | 
			
		||||
        this.frame.program.onChanged.push(_ => this.evaluate());
 | 
			
		||||
        this.outputIndex = this.frame.program.nextOutputIndex();
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
        // jitter around irritatingly
 | 
			
		||||
        // jitter around irritatingly.
 | 
			
		||||
        this.clearResultsOnNextOutput = true;
 | 
			
		||||
 | 
			
		||||
        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`, {
 | 
			
		||||
            type: "module",
 | 
			
		||||
            name: `evaluate LiterateOutput ${this.literateProgramName}`
 | 
			
		||||
            name: `evaluate LiterateOutput ${this.frame.programName}`
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.worker.addEventListener("message", event => {
 | 
			
		||||
            let message = event.data;
 | 
			
		||||
            if (message.kind == "evalComplete") {
 | 
			
		||||
                this.worker.terminate();
 | 
			
		||||
            } else if (message.kind == "output") {
 | 
			
		||||
            } else if (message.kind == "output" && message.outputIndex == this.outputIndex) {
 | 
			
		||||
                this.addOutput(message.output);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.worker.postMessage({
 | 
			
		||||
            action: "eval",
 | 
			
		||||
            input: getLiterateProgramSourceCode(this.literateProgramName),
 | 
			
		||||
            input: getLiterateProgramWorkerCommands(this.frame.programName),
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addOutput(output) {
 | 
			
		||||
        if (this.clearResultsOnNextOutput) {
 | 
			
		||||
| 
						 | 
				
			
			@ -112,10 +106,13 @@ class LiterateOutput extends HTMLElement {
 | 
			
		|||
        line.classList.add("output");
 | 
			
		||||
        line.classList.add(output.kind);
 | 
			
		||||
 | 
			
		||||
        line.textContent = output.message.map(x => {
 | 
			
		||||
            if (typeof x === "object") return JSON.stringify(x);
 | 
			
		||||
            else return x + "";
 | 
			
		||||
        }).join(" ");
 | 
			
		||||
        // 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);
 | 
			
		||||
                else return x + "";
 | 
			
		||||
            })
 | 
			
		||||
            .join(" ");
 | 
			
		||||
 | 
			
		||||
        if (output.kind == "result") {
 | 
			
		||||
            let returnValueText = document.createElement("span");
 | 
			
		||||
| 
						 | 
				
			
			@ -124,12 +121,30 @@ class LiterateOutput extends HTMLElement {
 | 
			
		|||
            line.insertBefore(returnValueText, line.firstChild);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.appendChild(line);
 | 
			
		||||
        this.frame.appendChild(line);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
        postMessage({
 | 
			
		||||
            kind: "output",
 | 
			
		||||
            output: {
 | 
			
		||||
                kind: "log",
 | 
			
		||||
                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;
 | 
			
		||||
    if (message.action == "eval") {
 | 
			
		||||
        outputIndex = 0;
 | 
			
		||||
        try {
 | 
			
		||||
            let func = new Function(message.input);
 | 
			
		||||
            let result = func.apply({});
 | 
			
		||||
            postMessage({
 | 
			
		||||
                kind: "output",
 | 
			
		||||
                output: {
 | 
			
		||||
                    kind: "result",
 | 
			
		||||
                    message: [result],
 | 
			
		||||
            await withTemporaryGlobalScope(async scope => {
 | 
			
		||||
                for (let command of message.input) {
 | 
			
		||||
                    if (command.kind == "module") {
 | 
			
		||||
                        let blobUrl = URL.createObjectURL(new Blob([command.source], { type: "text/javascript" }));
 | 
			
		||||
                        let module = await import(blobUrl);
 | 
			
		||||
                        for (let exportedKey in module) {
 | 
			
		||||
                            scope.set(exportedKey, module[exportedKey]);
 | 
			
		||||
                        }
 | 
			
		||||
                    } else if (command.kind == "output") {
 | 
			
		||||
                        ++outputIndex;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
| 
						 | 
				
			
			@ -29,7 +53,8 @@ addEventListener("message", event => {
 | 
			
		|||
                output: {
 | 
			
		||||
                    kind: "error",
 | 
			
		||||
                    message: [error.toString()],
 | 
			
		||||
                }
 | 
			
		||||
                },
 | 
			
		||||
                outputIndex,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,6 @@ export class TileEditor extends Frame {
 | 
			
		|||
        this.tileSize = parseInt(this.getAttribute("data-tile-size"));
 | 
			
		||||
 | 
			
		||||
        let tilemapId = this.getAttribute("data-tilemap-id");
 | 
			
		||||
        console.log(tilemapRegistry);
 | 
			
		||||
        if (tilemapId != null) {
 | 
			
		||||
            this.tilemap = tilemapRegistry[this.getAttribute("data-tilemap-id")];
 | 
			
		||||
        } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -188,5 +187,3 @@ export class TileEditor extends Frame {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
defineFrame("tairu-editor", TileEditor);
 | 
			
		||||
 | 
			
		||||
console.log("tairu editor loaded");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -163,15 +163,12 @@ function expandDetailsRecursively(element) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
function navigateToPage(page) {
 | 
			
		||||
    console.log(page);
 | 
			
		||||
    window.location.pathname = `${page}.html`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function navigateToBranch(fragment) {
 | 
			
		||||
    if (fragment.length == 0) return;
 | 
			
		||||
 | 
			
		||||
    console.log(`nagivating to branch: ${fragment}`);
 | 
			
		||||
 | 
			
		||||
    let element = document.getElementById(fragment);
 | 
			
		||||
    if (element !== null) {
 | 
			
		||||
        // If the element is already loaded on the page, we're good.
 | 
			
		||||
| 
						 | 
				
			
			@ -179,7 +176,6 @@ async function navigateToBranch(fragment) {
 | 
			
		|||
        rehash();
 | 
			
		||||
    } else {
 | 
			
		||||
        // The element is not loaded, we need to load the tree that has it.
 | 
			
		||||
        console.log("element is not loaded");
 | 
			
		||||
        let parts = fragment.split(':');
 | 
			
		||||
        if (parts.length >= 2) {
 | 
			
		||||
            let [page, _id] = parts;
 | 
			
		||||
| 
						 | 
				
			
			@ -189,7 +185,6 @@ async function navigateToBranch(fragment) {
 | 
			
		|||
                // navigation maps with roots other than index. Currently though only index is
 | 
			
		||||
                // generated so that doesn't matter.
 | 
			
		||||
                let [_root, ...path] = fullPath;
 | 
			
		||||
                console.log(`_root: ${_root}, path: ${path}`)
 | 
			
		||||
                if (path !== undefined) {
 | 
			
		||||
                    let isNotAtIndexHtml =
 | 
			
		||||
                        window.location.pathname != "" &&
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue