Forth in the Browser

Table of Contents

Introduction

Forth is a simplistic programming language similar to BASIC and SH. It’s intended as an interpreted language that can run on minimal hardware, and is surprisingly easy to implement from scratch.

However I found very few guides and examples of how to actually do so using modern languages and without worrying about the nitty-gritty details. So I decided to figure it out and write my own guide!

This guide will go through implementing a Forth interpreter in JavaScript that can run in the browser. Additionally we will add features such as simulated memory and filesystem, and maybe even things like a display!

A simple GUI version of the interpreter is available here for you to play with! The code for this GUI is at the end of this guide.

Forth Primer

Forth is an imperative stack-based programming language. Each “word” is a call to a function, either one built in to the interpreter or one defined by the user.

These words operate on a stack of numbers, pushing and popping from it. Some words can also be used to access memory or other features that the interpreter supports.

An example of some Forth code that defines a simple Hello World function (from Wikipedia).

: HELLO  ( -- )  CR ." Hello, world!" ;

Interpreter Expectations

Before we set out I want to set some expectations for the interpreter we are about to write. I intentionally wrote this as a sort of fake simulator for some non-existant retro hardware. There are intentional and odd restrictions that I made to this goal and because they sounded fun to me.

It is

  • A learning exercise for both me (the author) and you (the reader)
  • A vague simulator for some non-existant hardware
  • An interesting little computing environment in the browser

It isn’t

  • A fully standards compliant Forth interpreter
  • Suitable in any way for production use
  • A Forth compiler, there is no compilation step. This means there are no restrictions on words and no IMMEDIATE words.

How to Use It

I intend to write a nice interface for the interpreter, but for now you can access it from the browser console since it gets loaded into this page. Press F12 and use it like this…

var st = new State();
st.eval_string("1 2 3 4 + .");

The state can be inspected by simply evaluating it in the console by entering a line that’s just st (or whatever you named the variable).

Literate Program

This whole interpreter has been written as a Literate Program using Org-Mode. This HTML page has been exported from the original .org file, and the code has been exported to a .js file which has been loaded into this page.

Useful Links

Here are some links that I found useful while writing this interpreter. These are roughly ranked in order from what I considered most to least useful.

“Hardware”

In an effort to recreate an environment similar to retro computers I have intentionally restricted the interpreter in a number of ways as if this were the limits of the hardware. I think this has interesting ramifications and I plan to expand on it further at a later time.

  • 128 stack limit
  • 128 variable limit
  • 16kb of general memory
  • Block based file system of 128 blocks each 1kb large

Preamble

First off some things need to go at the beginning of the JavaScript file.

Enable JavaScript Strict Mode which turns a number of warnings and other possible mistakes into hard errors, which can prevent some bugs.

'use strict';

Set some constants related to the hardware limitations listed above. STACK_MAX will be re-used for other limits too, like the variable count.

const STACK_MAX = 128;
const MEM_MAX = 1024 * 16;

State Class

The core of the interpreter is the State class, which contains all of the state of the interpreter. The interpreter will then go through each word, evaluating it and altering the state.

I have chosen the approach of having each word modify a single state object, since I believe it’s a bit better when running in the browser. But it’s fairly easy to adapt to a Pure Function (or close enough).

Constructor

First off the constructor simply sets all the variables to default empty values.

constructor() {
    this.stack = [];
    this.mem = new Map();
    this.vars = new Map();
    this.index = 0;
    this.words = new Map();
    this.compiling = false;
    this.commenting = false;
    this.funcname = "";
    this.input = [];
    this.count = 0;
}

While we’re here lets go through them all, since each one serves and important purpose.

  • stack is the stack of integers that words operate on.
  • mem is the simulated memory of the interpreter, stored as a map for efficiency.
  • vars is a map of variable names to memory addresses.
  • index will keep track of the next unused memory address for variables.
  • words are the words defined by the user, stored as a map of their name to body.
  • compiling keeps track if we’re in compiling mode.
  • commenting keeps track if we’re in commenting mode.
  • funcname stores the current/last defined function.
  • input is the list of words that have been input but not evaluated yet.
  • count the number of words processed so far.

Utility Functions

State also provides a number of functions to make modifying itself easier, mostly with the addition of checks for the restrictions.

Pop Stack

pop() {
    if (this.stack.length >= 0) {
        return this.stack.pop();
    } else {
        throw "Stack Underflow";
    }
}

Pops two numbers off the stack and returns them, useful for math functions among others.

pop2() {
    return [this.pop(), this.pop()];
}

Push Stack

push(val) {
    if (this.stack.length < STACK_MAX) {
        this.stack.push(val);
    } else {
        throw "Stack Overflow";
    }
}

Top of Stack

top() {
    return this.stack[this.stack.length - 1];
}

Access Memory

access(adr) {
    if (adr >= 0 && adr < MEM_MAX) {
        return this.mem.get(adr) || 0;
    } else {
        throw "Memory Out of Bounds";
    }
}

Set Memory

set(adr, val) {
    if (adr >= 0 && adr < MEM_MAX) {
        this.mem.set(adr, val);
    } else {
        throw "Memory Out of Bounds";
    }
}

Free Memory

free(adr) {
    if (adr >= 0 && adr < MEM_MAX) {
        this.mem.delete(adr);
    } else {
        throw "Memory Out of Bounds";
    }
}

Assign Variable

assign_var(name, val) {
    if (this.vars.length < STACK_MAX) {
        this.vars.set(name, val);
    } else {
        throw "Variable Overflow";
    }
}

Emit Output

emit_output(output) {
    console.log(output);
    const event = new CustomEvent(
        'forthoutput',
        { detail: output }
    );
    window.dispatchEvent(event);
}

Evaluation

The evaluator is the core of the interpreter, it arguably is the interpreter. Here I have broken it into two major functions next and eval_word, along with a couple of helper functions.

Evaluate Next

This function advances the interpreter by taking the first word from input and evaluating it. Each time it is called the interpreters state advances, so you can step through a Forth program word by word. Continually calling this function (or using process below) will run the whole program.

This function returns true if evaluation happened, or false if there were no words left to evaluate.

Primarily it’s an if-else chain going through the following cases:

  • End commenting when the ) word is reached.
  • If commenting don’t do anything and return early.
  • End compiling when the ; word is reached
  • If compiling add to current function.
  • Else evaluate the word using eval_word if neither of the flags is true.
next() {
    const word = this.input.shift();
    if (!word) {
        return false;
    }
    this.count += 1;

    if (this.commenting && word === ")") {
        this.commenting = false;
    } else if (this.commenting) {
        return true;
    } else if (this.compiling && word === ";") {
        this.compiling = false;
    } else if (this.compiling) {
        this.words.get(this.funcname).push(word);
    } else {
        this.eval_word(word);
    }
    return true;
}

Evaluate Word

This function evaluates a single word by going through all the possible ways that it can be interpreted, finding the first one that is valid.

  • As an integer to add to the stack
  • As a variable that’s value is added to the stack
  • As a user defined word
  • As a builtin word

As a convenience the return value of builtin words is immediately added to the stack. Array return values are also supported and will be flattened.

eval_word(word) {
    if (!isNaN(parseInt(word))) {
        this.push(parseInt(word))
    } else if (this.vars.has(word)) {
        this.push(this.vars[word]);
    } else if (this.words.has(word)) {
        this.eval_array(this.words.get(word));
    } else if (BUILTIN_WORDS.has(word)) {
        let v = BUILTIN_WORDS.get(word)(this);
        if (v !== undefined) {
            this.push(v);
            this.stack = this.stack.flat();
        }
    } else {
        throw "Unknown Word: " + word;
    }
}

Helper Functions

These functions are just basic helpers around the main evaluation functions.

eval_array pushes an array of words to the front of the input so that they can be evaluated later. Doing it this way seems unintuitive, but is a key property of concatenative languages like Forth and prevents a number of errors.

eval_array(arr) {
    this.input = arr.concat(this.input);
}

process calls next in a loop until it returns false.

process() {
    while (this.next());
}

Input

Input is added to the state using the add_string function. This means that input can be repeatedly added, which makes writing a REPL environment easier.

add_string takes the string as input, breaks it up by lines and splits them into words. Any line beginning with the word \ is removed, since that indicates line comments. Everything is flattened into a single array and concatenated at the end of input.

add_string(str) {
    this.input = this.input.concat(
        str.split("\n")
           .map((l) => l.trim())
           .filter((l) => !l.startsWith("\\"))
           .map((l) => l.split(/\s+/g))
           .flat()
           .filter((l) => l)
    );
}

eval_string calls the above add_string and then calls process to immediately evaluate it.

eval_string(str) {
    this.add_string(str);
    this.process();
}

Putting It All Together

After all that we can put it together into a single State class. It’s pretty long so I collapsed it, click to open it up and view the whole class.

export class State {
    // [[[[file:~/Code/my-languages/forth/forth.org::constructor][constructor]]][constructor]]
    constructor() {
        this.stack = [];
        this.mem = new Map();
        this.vars = new Map();
        this.index = 0;
        this.words = new Map();
        this.compiling = false;
        this.commenting = false;
        this.funcname = "";
        this.input = [];
        this.count = 0;
    }
    // constructor ends here
    // [[[[file:~/Code/my-languages/forth/forth.org::pop-stack][pop-stack]]][pop-stack]]
    pop() {
        if (this.stack.length >= 0) {
            return this.stack.pop();
        } else {
            throw "Stack Underflow";
        }
    }
    // pop-stack ends here
    // [[[[file:~/Code/my-languages/forth/forth.org::pop2-stack][pop2-stack]]][pop2-stack]]
    pop2() {
        return [this.pop(), this.pop()];
    }
    // pop2-stack ends here
    // [[[[file:~/Code/my-languages/forth/forth.org::push-stack][push-stack]]][push-stack]]
    push(val) {
        if (this.stack.length < STACK_MAX) {
            this.stack.push(val);
        } else {
            throw "Stack Overflow";
        }
    }
    // push-stack ends here
    // [[[[file:~/Code/my-languages/forth/forth.org::top-stack][top-stack]]][top-stack]]
    top() {
        return this.stack[this.stack.length - 1];
    }
    // top-stack ends here
    // [[[[file:~/Code/my-languages/forth/forth.org::access-mem][access-mem]]][access-mem]]
    access(adr) {
        if (adr >= 0 && adr < MEM_MAX) {
            return this.mem.get(adr) || 0;
        } else {
            throw "Memory Out of Bounds";
        }
    }
    // access-mem ends here
    // [[[[file:~/Code/my-languages/forth/forth.org::set-mem][set-mem]]][set-mem]]
    set(adr, val) {
        if (adr >= 0 && adr < MEM_MAX) {
            this.mem.set(adr, val);
        } else {
            throw "Memory Out of Bounds";
        }
    }
    // set-mem ends here
    // [[[[file:~/Code/my-languages/forth/forth.org::free-mem][free-mem]]][free-mem]]
    free(adr) {
        if (adr >= 0 && adr < MEM_MAX) {
            this.mem.delete(adr);
        } else {
            throw "Memory Out of Bounds";
        }
    }
    // free-mem ends here
    // [[[[file:~/Code/my-languages/forth/forth.org::assign-var][assign-var]]][assign-var]]
    assign_var(name, val) {
        if (this.vars.length < STACK_MAX) {
            this.vars.set(name, val);
        } else {
            throw "Variable Overflow";
        }
    }
    // assign-var ends here
    // [[[[file:~/Code/my-languages/forth/forth.org::emit-output][emit-output]]][emit-output]]
    emit_output(output) {
        console.log(output);
        const event = new CustomEvent(
            'forthoutput',
            { detail: output }
        );
        window.dispatchEvent(event);
    }
    // emit-output ends here
    // [[[[file:~/Code/my-languages/forth/forth.org::eval-word][eval-word]]][eval-word]]
    eval_word(word) {
        if (!isNaN(parseInt(word))) {
            this.push(parseInt(word))
        } else if (this.vars.has(word)) {
            this.push(this.vars[word]);
        } else if (this.words.has(word)) {
            this.eval_array(this.words.get(word));
        } else if (BUILTIN_WORDS.has(word)) {
            let v = BUILTIN_WORDS.get(word)(this);
            if (v !== undefined) {
                this.push(v);
                this.stack = this.stack.flat();
            }
        } else {
            throw "Unknown Word: " + word;
        }
    }
    // eval-word ends here
    // [[[[file:~/Code/my-languages/forth/forth.org::eval-array][eval-array]]][eval-array]]
    eval_array(arr) {
        this.input = arr.concat(this.input);
    }
    // eval-array ends here
    // [[[[file:~/Code/my-languages/forth/forth.org::next][next]]][next]]
    next() {
        const word = this.input.shift();
        if (!word) {
            return false;
        }
        this.count += 1;
    
        if (this.commenting && word === ")") {
            this.commenting = false;
        } else if (this.commenting) {
            return true;
        } else if (this.compiling && word === ";") {
            this.compiling = false;
        } else if (this.compiling) {
            this.words.get(this.funcname).push(word);
        } else {
            this.eval_word(word);
        }
        return true;
    }
    // next ends here
    // [[[[file:~/Code/my-languages/forth/forth.org::process][process]]][process]]
    process() {
        while (this.next());
    }
    // process ends here
    // [[[[file:~/Code/my-languages/forth/forth.org::add-string][add-string]]][add-string]]
    add_string(str) {
        this.input = this.input.concat(
            str.split("\n")
               .map((l) => l.trim())
               .filter((l) => !l.startsWith("\\"))
               .map((l) => l.split(/\s+/g))
               .flat()
               .filter((l) => l)
        );
    }
    // add-string ends here
    // [[[[file:~/Code/my-languages/forth/forth.org::eval-string][eval-string]]][eval-string]]
    eval_string(str) {
        this.add_string(str);
        this.process();
    }
    // eval-string ends here
}

Builtin Words

Builtin words are the words that the interpreter provides directly. Since they never change during runtime, they’re not actually part of the state so instead are out here as a constant map of the word and the function itself.

const BUILTIN_WORDS = new Map();

Functions are usually added to this map directly using JavaScript Arrow Functions. Each function takes a single argument, the state of the interpreter, which they can edit to change the state.

As a convenience the return value of builtin words is immediately added to the stack. Array return values are also supported and will be flattened.

Central Words

“Central” words as I’m calling them are words that are very important to the functionality of the interpreter itself.

Compiling

Compiling is split into two words to start and end compilation. This is done to allow for word definitions to span multiple lines or be unfinished at the time of evaluation, which is important for a REPL. The end word is part of the evaluator itself since it needs to be handled separately from normal words.

Switches the interpreter to compiling mode and sets some things up. Also checks to ensure that we weren’t already in compiling mode, and throws and error if we were.

BUILTIN_WORDS.set(":",
    (state) => {
        if (state.compiling) {
            throw "Already Compiling";
        }

        state.compiling = true;
        state.funcname = state.input.shift();
        state.words.set(state.funcname, []);
    }
);

Commenting

Switches the interpreter in or out of commenting mode. Anything inside the parentheses is considered a comment and ignored. This is commonly used to describe the inputs and outputs of a word.

Split into two words for the same reason as compilation.

BUILTIN_WORDS.set("(",
    (state) => state.commenting = true
);

Math Words

These words perform basic arithmetic and math operations. They’re all relatively straightforward.

All math operations perform on the numbers in the stack in the same order they were placed on the stack. In other words it’s as if you moved the operator over one, so the Forth code 2 1 - is the same as 2 - 1.

Arithmetic

BUILTIN_WORDS.set("+",
    (state) => {
        let [a, b] = state.pop2();
        return b + a;
    }
);
BUILTIN_WORDS.set("-",
    (state) => {
        let [a, b] = state.pop2();
        return b - a;
    }
);
BUILTIN_WORDS.set("*",
    (state) => {
        let [a, b] = state.pop2();
        return b * a;
    }
);
BUILTIN_WORDS.set("/",
    (state) => {
        let [a, b] = state.pop2();
        return b / a;
    }
);
BUILTIN_WORDS.set("MOD",
    (state) => {
        let [a, b] = state.pop2();
        return b % a;
    }
);

Signedness

BUILTIN_WORDS.set("NEGATE",
    (state) => {
        return -state.pop();
    }
);
BUILTIN_WORDS.set("ABS",
    (state) => {
        return Math.abs(state.pop());
    }
);

Comparison Words

All numbers other than zero (including negative numbers) are considered true in Forth.

This follows the same ordering rules as the math functions above.

Most of these functions also make use of JavaScripts + unary operator, which converts a boolean to a one or zero.

Boolean Comparison

These words compare the top two numbers on the stack and place either a 1 if true or a 0 if false.

BUILTIN_WORDS.set("=",
    (state) => {
        let [a, b] = state.pop2();
        return + b === a;
    }
);
BUILTIN_WORDS.set(">",
    (state) => {
        let [a, b] = state.pop2();
        return + (b > a);
    }
);
BUILTIN_WORDS.set(">=",
    (state) => {
        let [a, b] = state.pop2();
        return + (b >= a);
    }
);
BUILTIN_WORDS.set("<",
    (state) => {
        let [a, b] = state.pop2();
        return + (b < a);
    }
);
BUILTIN_WORDS.set("<=",
    (state) => {
        let [a, b] = state.pop2();
        return + (b <= a);
    }
);

Relative Operators

These words compare the top two numbers on the stack in a relative fashion.

Put the smaller of the two numbers back on the stack.

BUILTIN_WORDS.set("MIN",
    (state) => {
        let [a, b] = state.pop2();
        return Math.min(a, b);
    }
);

Put the larger of the two numbers back on the stack.

BUILTIN_WORDS.set("MAX",
    (state) => {
        let [a, b] = state.pop2();
        return Math.max(a, b);
    }
);

Compare the two numbers returning 0, 1, or -1 depending on their status.

BUILTIN_WORDS.set("CMP",
    (state) => {
        let [a, b] = state.pop2();
        if (b < a) {
            return -1
        } else if (b > a) {
            return 1;
        } else {
            return 0;
        }
    }
);

Stack Words

These words manipulate the stack itself, duplicating and moving values. These can be incredibly useful while programming in Forth, since everything is based on the stack.

Remove the top element and output it

BUILTIN_WORDS.set(".",
    (state) => state.emit_output(state.pop())
);

Duplicate the top item

BUILTIN_WORDS.set("DUP", (state) => state.top());

Swap the top with the second element

BUILTIN_WORDS.set("SWAP",
    (state) => [state.pop(), state.pop()]
);

Remove the top item

BUILTIN_WORDS.set("DROP",
    (state) => {
        const _ = state.pop();
    }
);

Rotate the top 3 elements

BUILTIN_WORDS.set("ROT", (state) => {
    const a = state.pop();
    const b = state.pop();
    const c = state.pop;
    return [b, a, c];
});

Remove the second item

BUILTIN_WORDS.set("NIP", (state) => {
    const a = state.pop();
    const _ = state.pop();
    return a;
});

Duplicate the top item below the second slot

BUILTIN_WORDS.set("TUCK", (state) => {
    const a = state.pop();
    const b = state.pop();
    return [a, b, a];
});

Duplicate the second item to the top

BUILTIN_WORDS.set("OVER", (state) => {
    const a = state.pop();
    const b = state.pop();
    return [b, a, b];
});

Move the item at that position to the top (zero indexed)

BUILTIN_WORDS.set("ROLL", (state) => {
    const i = state.pop();
    return state.stack.splice(i, 1);
});

Duplicate the item at that position to the top (zero indexed)

BUILTIN_WORDS.set("PICK",
    (state) => state.stack[state.pop()]
);

Clear all items in the stack

BUILTIN_WORDS.set("CLEARSTACK",
    (state) => state.stack.clear()
);

Memory Words

This interpreter includes 16kb of virtual memory that is stored as a map of addresses to number values. Undefined addresses are the same as zero, so it is still possible to walk through the memory, with the added benefit of automatic zeroing.

Access the memory location on the top of the stack, and put the value in that address on the stack. Since the memory is implemented as a map, undefined will return zero.

BUILTIN_WORDS.set("@",
    (state) => state.access(state.pop())
);

At the given memory address write the given value.

BUILTIN_WORDS.set("!",
    (state) => {
        const v = state.pop();
        const adr = state.pop();
        state.set(adr, v);
    }
);

At the given memory address zero given number of values. So 10 2 ZERO will zero the next 2 values starting at address 10.

BUILTIN_WORDS.set("ZERO",
    (state) => {
        const len = state.pop();
        const adr = state.pop();
        for (let i = 0; i < len; i++) {
            state.free(adr + i);
        }
    }
);

Starting at the given address copy the given number of values to a new address.

BUILTIN_WORDS.set("COPY",
    (state) => {
        const adr2 = state.pop();
        const len = state.pop();
        const adr1 = state.pop();
        for (let i = 0; i < len; i++) {
            const v = state.access(adr1 + i)
            state.set(adr2 + i, v);
        }
    }
);

Variable Words

Variables in Forth are names for memory addresses. The interpreter keeps track of variables using a map of names to address, and index which keeps track of the next usable address. Complex data can also be stored using the ALLOT word to reserve more space for a variable’s data.

Get the current variable index, which keeps track of the memory location of the next variable.

BUILTIN_WORDS.set("IDX",
    (state) => state.index
);

Set the variable index in order to override the usual behavior.

BUILTIN_WORDS.set("SETIDX",
    (state) => state.index = state.pop()
);

Define a new variable with the given name like this VARIABLE NAME. Places the address of the new variable on the stack for immediate use.

BUILTIN_WORDS.set("VARIABLE",
    (state) => {
        const adr = state.index;
        state.assign_var(
            state.input.shift(),
        );
        state.index++;
        return adr;
    }
);

Creates a contiguous region with the given length and increases the variable index by that much. Used to create variables with complex data. Places the length of the allocated region at the variable’s memory address.

BUILTIN_WORDS.set("ALLOT",
    (state) => {
        const len = state.pop();
        const adr = state.pop();
        state.index += (len + 1);
        state.set(adr, len);
        return adr;
    }
);

Text Words

Text related words for working with strings and outputting them.

Outputs the top of the stack as a character.

BUILTIN_WORDS.set("EMIT",
    (state) => {
        state.emit_output(
            String.fromCharCode(state.pop())
        );
    }
);

A string that begins with the word " and ends with a word that ends with " as per usual Forth style. Places the memory address of the created string onto the stack. Strings are null-terminated like in C, meaning a string ends at the first 0 encountered.

BUILTIN_WORDS.set("\"",
    (state) => {
        let arr = [];
        let word = undefined;
        do {
            word = state.input.shift();
            arr.push(word);
        } while(!word.endsWith("\""));
        const str = arr.join(" ").slice(0, -1);

        let adr = state.index;
        for (let i = 0; i < str.length; i++) {
            state.set(adr + i, str.charCodeAt(i))
        }

        state.index += str.length + 1;
        return adr;
    }
);

Print the string at the given memory address to the output.

BUILTIN_WORDS.set("PRINT",
    (state) => {
        const adr = state.pop();

        const arr = [];
        for (let i = 0; state.access(adr+i) !== 0; i++) {
            arr.push(state.access(adr+i));
        }

        console.log(arr);
        state.emit_output(String.fromCharCode(...arr));
    }
);

Block Words

Blocks are persistent data for the interpreter and are sort of a “filesystem”. In this interpreter the blocks are stored in LocalStorage as a JSON array of numbers.

Loads into memory at the given address the block with the given number. In the process of loading this will also zero the 1024 values in memory following the address. Places the number of read values onto the stack.

BUILTIN_WORDS.set("LOAD",
    (state) => {
        const num = state.pop();
        const adr = state.pop();

        if (num < 0 || num > STACK_MAX) {
            throw "Block Index Out of Bounds";
        }

        const block = JSON.parse(
            localStorage.getItem(num)
        );

        for (let i = 0; i < 1024; i++) {
            state.mem.delete(adr + i);
        }

        for (let i = 0; i < block.length; i++) {
            if (block[i]) {
                state.set(adr + i, block[i]);
            }
        }

        return block.length;
    }
);

Writes from memory at the given address the given number of values to the given block. Notably this means you can write to a block without loading it first. This will truncate the block and entirely replace its contents.

BUILTIN_WORDS.set("WRITE",
    (state) => {
        const num = state.pop();
        const len = state.pop();
        const adr = state.pop();

        if (num < 0 || num > STACK_MAX) {
            throw "Block Index Out of Bounds";
        }

        let data = [];
        for (let i = 0; i < len; i++) {
            data.push(state.access(adr + i));
        }

        localStorage.setItem(
            num,
            JSON.stringify(data)
        );
    }
);

Clear the given block. This removes it from LocalStorage.

BUILTIN_WORDS.set("CLEARBLOCK",
    (state) => {
        const num = state.pop();

        if (num < 0 || num > STACK_MAX) {
            throw "Block Index Out of Bounds";
        }

        localStorage.removeItem(num);
    }
);

Control Words

Words that control the execution of the program, primarily conditionals, loops, and similar. Note that unlike functions, control statements cannot span multiple evaluations. They must be valid when input.

The classic if-statement with an optional else block. If the number on the top of the stack is not zero it evaluates the first section, else it evaluates the second.

BUILTIN_WORDS.set("IF",
    (state) => {
        const if_words = [];
        const else_words = [];

        let word = undefined;
        let else_switch = false;
        while (word = state.input.shift()) {
            if (word === "THEN") {
                break;
            } else if (word === "ELSE") {
                else_switch = true;
                continue;
            }

            if (else_switch) {
                else_words.push(word);
            } else {
                if_words.push(word);
            }
        }

        if (state.pop() !== 0) {
            state.eval_array(if_words);
        } else {
            state.eval_array(else_words);
        }
    }
);

A loop that takes the end and the beginning and runs until the two have met. Optionally if you use +LOOP you can set the step of the loop, but note that this is not dynamic and does not pop from the stack and instead reads the previous word.

BUILTIN_WORDS.set("DO",
    (state) => {
        const body = [];
        let word = undefined;
        let step = 1;

        while (word = state.input.shift()) {
            if (word === "LOOP") {
                break;
            } else if (word === "+LOOP") {
                step = parseInt(body.pop()) || 1;
                break;
            }
            body.push(word);
        }

        let start = state.pop();
        let end = state.pop();

        for (let i = start; i < end; i += step) {
            state.eval_array(
                body.map((w) => (w === "I") ? i : w)
            );
        }
    }
);

Given a memory address it evaluates the string placed there as if it were Forth code. Can be used to load code from blocks.

BUILTIN_WORDS.set("EVAL",
    (state) => {
        const adr = state.pop();

        const arr = [];
        for (let i = 0; state.access(adr+i) !== 0; i++) {
            arr.push(state.access(adr+i));
        }

        state.eval_string(String.fromCharCode(...arr));
    }
);

Developer Tool Words

These words exist to help developers write programs, but aren’t particularly useful within the programs themselves.

Output the body of a word

BUILTIN_WORDS.set("SEE",
    (state) => {
        let word = state.input.shift();
        state.emit_output(
            state.words.get(word).join(" ")
        );
    }
);

Output the entire stack without popping it. The stack is printed bottom to top, so the last value is the top of the stack. This is the same as the order they were inputted.

BUILTIN_WORDS.set("DUMP",
    (state) => {
        state.emit_output(state.stack.join(" "));
    }
);

Word Library

A collection of words for you to use in your programs.

: ? ( adr -- ) @ . ;
: FREE ( adr -- ) DUP @ ZERO ;

User Interface

In order to make using the Forth interpreter more plesant I have created a simple user interface for it, which is available here. It’s a work in progress so there are still some problems.

JavaScript

import { h, Component, render } from 'https://cdn.skypack.dev/preact';
import htm from 'https://cdn.skypack.dev/htm';
import { State } from './forth.js';

const html = htm.bind(h);

class App extends Component {
    state = {
        input: [],
        output: [],
        error: undefined,
        lineVal: "",
        count: 0
    };

    forthState = undefined;

    constructor() {
        super();

        this.forthState = new State();

        window.addEventListener(
            "forthoutput",
            ({ detail }) => this.setState({
                output: [...this.state.output, detail]
            })
        );
    }

    lineInput = ({ target }) =>
        this.setState({lineVal: target.value});

    submit = (e) => {
        e.preventDefault();

        const val = this.state.lineVal;
        this.setState({ input: [...this.state.input, val], lineVal: "" });
        this.forthState.add_string(val);
        e.target.reset();
    }

    run = () => {
        try {
            this.forthState.process();
        } catch (e) {
            this.setState({ error: e.message || e });
        }
        this.setState({ count: this.forthState.count });
    };

    step = () => {
        try {
            if (this.forthState.next()) {
                this.setState({ count: this.state.count + 1 });
            }
        } catch (e) {
            this.setState({ error: e.message || e });
            this.setState({ count: this.forthState.count });
        }
    };

    inputElem = () => {
      let c = 0;
      return this.state.input.map((i) => {
          const row = i.split(/\s+/g).map((w) => {
              let cl;
              if (c === this.state.count) {
                  cl = "active";
              } else if (c > this.state.count) {
                  cl = "uneval";
              }
              c += 1;
              return html`<span class=${cl}>${w}</span> `;
          });
        return html`<div>${row}</div>`;
      }).reverse();
    }

    render(props) {
        return html`
          <section class="forth">
              <h4>Input Log</h4>
              <div class="input">
                ${this.inputElem()}
              </div>
              <small>
                Grey words haven't been evaluated yet.
                Green is the next word to be evaluated.
              </small>
          
              <form onSubmit=${this.submit}>
                  <input type="text"
                      value=${this.state.value}
                      onInput=${this.lineInput}/>
                  <button type="submit">Submit</button>
              </form>
          
              <div class="buttons">
                  <button onClick=${this.run}>Run</button>
                  <button onClick=${this.step}>Step</button>
              </div>
          
              <small>
                Keeping track of the current word may break with <code>IF</code> or <code>DO</code>
              </small>
          
              <h4>Output Log</h4>
              <div class="output">
                  ${this.state.output.map(o => html`<div>${o}</div>`).reverse()}
              </div>
          
              ${(this.state.error) ? html`<div class="error">${this.state.error}</div>` : undefined }
          
              <a href="./forth.html">Return to Guide</a>
          </section>
        `;
    }
}

const target = document.getElementById("app");
render(html`<${App}/>`, target);

App Template

<section class="forth">
    <h4>Input Log</h4>
    <div class="input">
      ${this.inputElem()}
    </div>
    <small>
      Grey words haven't been evaluated yet.
      Green is the next word to be evaluated.
    </small>

    <form onSubmit=${this.submit}>
        <input type="text"
            value=${this.state.value}
            onInput=${this.lineInput}/>
        <button type="submit">Submit</button>
    </form>

    <div class="buttons">
        <button onClick=${this.run}>Run</button>
        <button onClick=${this.step}>Step</button>
    </div>

    <small>
      Keeping track of the current word may break with <code>IF</code> or <code>DO</code>
    </small>

    <h4>Output Log</h4>
    <div class="output">
        ${this.state.output.map(o => html`<div>${o}</div>`).reverse()}
    </div>

    ${(this.state.error) ? html`<div class="error">${this.state.error}</div>` : undefined }

    <a href="./forth.html">Return to Guide</a>
</section>

CSS Styles

.forth {
    max-width: 50rem;
    margin: auto;
}
.forth form {
    display: flex;
}
.forth form input {
    flex-grow: 1;
}
.input, .output {
    border: 0.2rem inset whitesmoke;
    padding: 1rem;
    overflow-y: scroll;
    background-color: whitesmoke;
}
.input {
    height: 15rem;
}
.output {
    height: 5rem;
}
.active {
    color: lightgreen;
}
.uneval {
    color: gray;
}
.error {
    background-color: lightcoral;
    padding: 0.5rem;
    margin: 0.5rem 0;
}

Putting it All Together

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="x-ua-compatible"
              content="ie=edge">
        <title>Forth GUI</title>
        <meta name="viewport"
              content="width=device-width,
                       initial-scale=1">
        <style>
          .forth {
              max-width: 50rem;
              margin: auto;
          }
          .forth form {
              display: flex;
          }
          .forth form input {
              flex-grow: 1;
          }
          .input, .output {
              border: 0.2rem inset whitesmoke;
              padding: 1rem;
              overflow-y: scroll;
              background-color: whitesmoke;
          }
          .input {
              height: 15rem;
          }
          .output {
              height: 5rem;
          }
          .active {
              color: lightgreen;
          }
          .uneval {
              color: gray;
          }
          .error {
              background-color: lightcoral;
              padding: 0.5rem;
              margin: 0.5rem 0;
          }
        </style>
    </head>
    <body>
        <div id="app"></div>
        <script type="module">
          import { h, Component, render } from 'https://cdn.skypack.dev/preact';
          import htm from 'https://cdn.skypack.dev/htm';
          import { State } from './forth.js';
          
          const html = htm.bind(h);
          
          class App extends Component {
              state = {
                  input: [],
                  output: [],
                  error: undefined,
                  lineVal: "",
                  count: 0
              };
          
              forthState = undefined;
          
              constructor() {
                  super();
          
                  this.forthState = new State();
          
                  window.addEventListener(
                      "forthoutput",
                      ({ detail }) => this.setState({
                          output: [...this.state.output, detail]
                      })
                  );
              }
          
              lineInput = ({ target }) =>
                  this.setState({lineVal: target.value});
          
              submit = (e) => {
                  e.preventDefault();
          
                  const val = this.state.lineVal;
                  this.setState({ input: [...this.state.input, val], lineVal: "" });
                  this.forthState.add_string(val);
                  e.target.reset();
              }
          
              run = () => {
                  try {
                      this.forthState.process();
                  } catch (e) {
                      this.setState({ error: e.message || e });
                  }
                  this.setState({ count: this.forthState.count });
              };
          
              step = () => {
                  try {
                      if (this.forthState.next()) {
                          this.setState({ count: this.state.count + 1 });
                      }
                  } catch (e) {
                      this.setState({ error: e.message || e });
                      this.setState({ count: this.forthState.count });
                  }
              };
          
              inputElem = () => {
                let c = 0;
                return this.state.input.map((i) => {
                    const row = i.split(/\s+/g).map((w) => {
                        let cl;
                        if (c === this.state.count) {
                            cl = "active";
                        } else if (c > this.state.count) {
                            cl = "uneval";
                        }
                        c += 1;
                        return html`<span class=${cl}>${w}</span> `;
                    });
                  return html`<div>${row}</div>`;
                }).reverse();
              }
          
              render(props) {
                  return html`
                    <section class="forth">
                        <h4>Input Log</h4>
                        <div class="input">
                          ${this.inputElem()}
                        </div>
                        <small>
                          Grey words haven't been evaluated yet.
                          Green is the next word to be evaluated.
                        </small>
                    
                        <form onSubmit=${this.submit}>
                            <input type="text"
                                value=${this.state.value}
                                onInput=${this.lineInput}/>
                            <button type="submit">Submit</button>
                        </form>
                    
                        <div class="buttons">
                            <button onClick=${this.run}>Run</button>
                            <button onClick=${this.step}>Step</button>
                        </div>
                    
                        <small>
                          Keeping track of the current word may break with <code>IF</code> or <code>DO</code>
                        </small>
                    
                        <h4>Output Log</h4>
                        <div class="output">
                            ${this.state.output.map(o => html`<div>${o}</div>`).reverse()}
                        </div>
                    
                        ${(this.state.error) ? html`<div class="error">${this.state.error}</div>` : undefined }
                    
                        <a href="./forth.html">Return to Guide</a>
                    </section>
                  `;
              }
          }
          
          const target = document.getElementById("app");
          render(html`<${App}/>`, target);
        </script>
    </body>
</html>

Licenses

The code of this document is under the MIT License, and the text and graphics of this document are under the CC-BY License. Both are presented in their entirety below.

MIT License

Copyright (c) 2020 Steven vanZyl

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

CC-BY License

Attribution 4.0 International

=====================================================================

Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.

Using Creative Commons Public Licenses

Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.

Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC- licensed material, or material used under an exception or limitation to copyright. More considerations for licensors: wiki.creativecommons.org/Considerationsforlicensors

Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public: wiki.creativecommons.org/Considerationsforlicensees

=====================================================================

Creative Commons Attribution 4.0 International Public License

By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License (“Public License”). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.

Section 1 – Definitions.

  1. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
  2. Adapter’s License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
  3. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
  4. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
  5. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
  6. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
  7. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
  8. Licensor means the individual(s) or entity(ies) granting rights under this Public License.
  9. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
  10. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
  11. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.

Section 2 – Scope.

  1. License grant.
    1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
      1. reproduce and Share the Licensed Material, in whole or in part; and
      2. produce, reproduce, and Share Adapted Material.
    2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
    3. Term. The term of this Public License is specified in Section 6(a).
    4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a) (4) never produces Adapted Material.
    5. Downstream recipients.
      1. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
      2. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
    6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
  2. Other rights.
    1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
    2. Patent and trademark rights are not licensed under this Public License.
    3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties.

Section 3 – License Conditions.

Your exercise of the Licensed Rights is expressly made subject to the following conditions.

  1. Attribution.
    1. If You Share the Licensed Material (including in modified form), You must:
      1. retain the following if it is supplied by the Licensor with the Licensed Material:

        1. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);

        ii. a copyright notice;

        iii. a notice that refers to this Public License;

        iv. a notice that refers to the disclaimer of warranties;

        1. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
      2. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
      3. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
    2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
    3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
    4. If You Share Adapted Material You produce, the Adapter’s License You apply must not prevent recipients of the Adapted Material from complying with this Public License.

Section 4 – Sui Generis Database Rights.

Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:

  1. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database;
  2. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and
  3. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.

For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.

Section 5 – Disclaimer of Warranties and Limitation of Liability.

  1. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
  2. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
  3. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.

Section 6 – Term and Termination.

  1. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
  2. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:

    1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
    2. upon express reinstatement by the Licensor.

    For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.

  3. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
  4. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.

Section 7 – Other Terms and Conditions.

  1. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
  2. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.

Section 8 – Interpretation.

  1. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
  2. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
  3. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
  4. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.

=====================================================================

Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” The text of the Creative Commons public licenses is dedicated to the public domain under the CC0 Public Domain Dedication. Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.

Creative Commons may be contacted at creativecommons.org.

Author: Steven vanZyl

Created: 2020-11-14 Sat 15:53