logo

Tutorial @ PyCon USA 2022. April 28, 2022. Salt Lake, UT, USA.

Check out the slides for this tutorial.

1. Introduction

Wasm is a binary code format specification first released in 2017. This technology can be implemented in web browsers or standalone applications in a secure, open, portable, and efficient fashion. More precisely, Wasm is an intermediate language for a stack-based virtual machine that uses a just-in-time (JIT) compiler to produce native machine code. Although Wasm was primarily designed as a compilation target for languages such as C/C++ or Rust, it can be integrated with Python in interesting ways. And that’s what we’ll be focusing on during this tutorial. Some experience with JavaScript and web development might come in handy but is not strictly required. At the end, we’ll show how to develop a tiny compiler that has Wasm as its compilation target.

2. Preparing the Gitpod Workspace

2.1. Create the Workspace

You must have a GitHub account in order to proceed. Please sign up if you don’t have one.

When the option appears, press “Continue with GitHub” and authorize Gitpod to access your account. A few moments later a small notification will appear in the bottom right corner asking “Do you want to open this workspace in VS Code Desktop?”. Press “Don’t Show Again” because we’ll be using VS Code in the browser.

From now on the gitpod.io URL will take you to your Gitpod dashboard as long as you’re logged-in.

2.2. Using the Terminal

In this tutorial we’ll make frequent use of the terminal. To open a terminal window on your workspace press Ctrl-J (Windows and Linux) or Cmd-J (macOS). When using this Gitpod workspace every time you open a terminal you’ll need to type:

source pycon2022

This command sets the environment variables that are required to correctly run the software we’ll be using during the tutorial.

2.3. About Gitpod Free Plan

The Gitpod free plan (called the “Open Source” plan) includes 50 Gitpod hours per month. One Gitpod hour allows you to use one workspace for one hour. To see your remaining hours go to your Gitpod dashboard and select the “Settings” tab and then select “Plans” from the left-hand menu. If you want to stop the workspace at any moment, press the “Gitpod” button in the bottom left corner and select “Gitpod: Stop Workspace” in the dropdown menu. Otherwise, workspaces in the free plan have a 30 minute inactivity timeout.


With all this in mind, you are now ready to continue with the rest of the tutorial.

3. Pyodide: Python on the Browser

Pyodide uses the Emscripten compiler toolchain to port CPython to WebAssembly. This means that we are able to execute Python code directly from within a web browser. Pyodide was created in 2018 by Michael Droettboom at Mozilla.

Try Pyodide in a REPL directly in your browser (no installation needed).

3.1. A Simple Demo

Let’s demonstrate how to use Pyodide with some ordinary Python code. Open the pyodide/some_python_code.py, which has the following content:

File: pyodide/some_python_code.py
# Copyright (C) 2022 Ariel Ortiz

"""Some Python code to test Pyodide."""

from datetime import datetime
from random import choice
from sys import version

ONELINERS = [
    'A data scientist is a person who is better at '
        'statistics than any programmer and better at '
        'programming than any statistician.',
    "!false (It's funny because it's true.)",
    'Why do programmers always mix up Christmas and '
        'Halloween? Because Dec 25 is Oct 31.',
    'Have you tried turning it off and on again?'
]


def get_version():
    """Returns a string with the Python version used by the
    Pyodide runtime.
    """
    return 'Python ' + version.split()[0]


def get_date_and_time():
    """Returns a string with the current date and time using the
    ISO 8601 format.
    """
    now = datetime.now()
    result = now.isoformat()
    return result


def get_quote():
    """Obtains and returns a string with a funny random oneline
    quote.
    """
    return choice(ONELINERS)


if __name__ == '__main__':
    # Some simple tests to make sure that the code works as expected.
    print(get_version())
    print(get_date_and_time())
    print(get_quote())

Let’s run this Python code from the terminal to make sure that it works. Change the current working directory to pyodide by typing the following at the terminal:

cd /workspace/pycon2022-wasm/pyodide

Now, to run the Python file type:

python some_python_code.py

The output should de someting like this:

Python 3.10.4
2022-04-14T23:01:24.389322
A data scientist is a person who is better at statistics than any programmer and better at programming than any statistician.

Now, let’s see how to integrate it in a web page using Pyodide. Open the pyodide/index.html file and review its contents:

File: pyodide/index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Pyodide Test</title>
    <script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"> (1)
    </script>
  </head>
  <body>
    <h1>Pyodide Test</h1>
    <p id="id_1"><em>Wait for it...</em></p> (2)
    <p id="id_2"></p>
    <p id="id_3"></p>
    </ul>
    <script> (3)
      async function main() { (4)
        let pyodide = await loadPyodide(); (5)
        pyodide.runPython( (6)
          await (await fetch("some_python_code.py")).text()
        );
        let version = pyodide.runPython('get_version()'); (7)
        let date_and_time = pyodide.runPython('get_date_and_time()'); (8)
        let quote = pyodide.runPython('get_quote()') (9)
        document.getElementById("id_1").innerHTML = version; (10)
        document.getElementById("id_2").innerHTML = date_and_time;
        document.getElementById("id_3").innerHTML = quote;
      }

      main(); (11)
    </script>
  </body>
</html>
1 To include Pyodide runtime in a project you must use this Content Delivery Network (CDN) link.
2 We define three HTML <p> elements uniquely identified with id_1, id_2, and id_3. These will be used to place the results of our Python code.
3 We add some JavaScript code in order to interact with the Pyodide runtime.
4 An asynchronous function is required to load the Pyodide runtime.
5 The pyodide.js file defines a single async function called loadPyodide which sets up the Python environment and returns the Pyodide top level namespace.
6 Python code is executed using the pyodide.runPython function. It takes as input a string of Python code. If the code ends in an expression, it returns the result of the expression, converted to an equivalent JavaScript object. Here, using the fetch function, we get the full contents of the some_python_code.py Python source file.
7 This Python code obtains and returns the Python version used by the Pyodide runtime.
8 This Python code obtains and returns the current date and time using the ISO 8601 format.
9 This Python code obtains and returns a funny random quote.
10 With the obtained results, we now update the corresponding HTML elements in our page.
11 We call our asynchronous function.

To see this working we need to run a web server. At the terminal type:

python -m http.server

Click on the “Open Browser” button that appears in the bottom right-hand side of the screen. After a few seconds you should see something like this:

index

Press Ctrl-C at the terminal where the web server is running in order to stop it. For the moment keep it running.

3.2. Exercise A ★

Modify the get_date_and_time function in the pyodide/some_python_code.py file so that the returned time and date string is concatenated with one of the following texts:

  • "Happy birthday to me!", if today happens to be your birthday

  • "A very merry unbirthday to me!", if today is not your birthday

Note that you will need to hard code your personal birthdate information into the program.

Test your code first at the terminal. Type:

python some_python_code.py

If the code produces the expected output you can now test it on the web page by refreshing it. Make sure the web server is still running.

Once the variable now has been initialized with today’s date, use the attributes now.day and now.month to get today’s integer day and month values.

4. Hand coding WebAssembly

WebAssembly, or WASM, is a portable binary code format that is delivered to the browser or other runtime system as modules using the .wasm file extension. It also defines a corresponding textual assembly language, called WebAssembly Text Format or WAT, which uses the .wat extension. There is a close correspondence between the binary and textual formats, with the latter simply making it easier for humans to read .wasm modules.

4.1. S-expressions

In both the binary and textual formats, the fundamental unit of code in WebAssembly is a module. In the text format, a module is represented as one big S-expression. S-expressions are a fairly old and simple textual format for representing trees, and thus we can think of a module as a tree of nodes that describe the module’s structure and its code.

S-expression stands for “symbolic expression”, and it is commonly encountered in the different Lisp language dialects such as Common Lisp, Scheme, Racket, and Clojure. These programming languages use s-expressions to represent the computer program and also the program’s data.

Let’s see what an S-expression looks like. Each node in the tree goes inside a pair of parentheses: ( …​ ). The first label inside the parentheses tells you what type of node it is, and after that there is a space-separated list of either attributes or child nodes. For example:

(module
  (func
    (export "myFunction")
    return))

In this example the node types are module, func and export. The root of the s-expression is the module node. This node has func as its only child. The func node has two children: the export node and the return attribute. Finally, the export node has only one child, which is the "myFunction" attribute. Indentation is used here to clarify the tree-structure of the s-expression.

4.2. Data Types

WebAssembly programs support only four data types:

  • i32: 32-bit integer

  • i64: 64-bit integer

  • f32: 32-bit float

  • f64: 64-bit float

These types are used as prefixes in most WebAssembly instructions. They are also used when declaring variables, parameters, and function return types.

4.3. Stack Machine

A stack is a data structure with two operations: push and pop. Items are pushed (inserted) onto the stack and subsequently popped (removed) from the stack in last in, first out (LIFO) order.

A real-life example is a stack of pancakes: you can only take a pancake (at least easily) from the top of the stack, and you can only add a pancake to the top of the stack.

pancakes

WebAssembly execution is defined in terms of a stack machine where the basic idea is that every instruction pushes and/or pops a certain number to/from a stack. For example, to add the numbers 7 and 5, the following three steps need to be carried out on the stack:

  1. Push the 7.

  2. Push the 5.

  3. Pop the two top values (5 and 7), perform the addition (7 + 5), and finally push the result (12).

The following figure illustrates the three steps described above:

stack example

The actual WAT code to do this arithmetic operation would look like this:

i32.const 7 ;; Push 7 
i32.const 5 ;; Push 5 
i32.add     ;; Pop two values (5 & 7), add them up, push result (12)

4.4. A Complete Example

The following code, available as wat/examples.wat, is a complete WebAssembly module with one function that computes the average of two 64-bit floating point numbers.

File: wat/examples.wat

(module

  (; 
     This function computes the average of two 64-bit floating point
     numbers. It's equivalent to the following Python function:

     def avg2(a: float, b: float) -> float:
         return (a + b) / 2.0
  ;)
  (func $avg2       ;; Define a function called $avg2
    (export "avg2") ;; Export it outside this module as "avg2"
    (param $a f64)  ;; Declare parameter $a as 64-bit float 
    (param $b f64)  ;; Declare parameter $b as 64-bit float
    (result f64)    ;; Function returns 64-bit float
    local.get $a    ;; Push $a
    local.get $b    ;; Push $b
    f64.add         ;; Pop two values, add them up, push result
    f64.const 2.0   ;; Push 2.0
    f64.div         ;; Pop two values, divide them, push result
  )

)

A couple of things worth noting:

  • Line comments start with two semicolons (;;), block comments start with (; and end with ;). Hopefully, the comments should make clear what each line of code does.

  • The names of the function ($avg2) and its parameters ($a and $b) must start with a dollar sign ($).

To run this WAT code we first need to translate it into a WASM binary. There are several ways we can do this. For now, let’s use the wat2wasm tool available as part of the WABT project (pronounced "wabbit").

At the terminal, make wat your current working directory by typing:

cd /workspace/pycon2022-wasm/wat

Run the WAT to WASM translator. At the terminal type:

wat2wasm examples.wat

The previous command creates the examples.wasm file. There are several options to execute this file, but, for simplicity’s sake, let’s use a program called Wasm3, which proclaims to be “the fastest WebAssembly interpreter, and the most universal runtime”. Although it’s not a just-in-time (JIT) compiler, it’s simpler to use for our current purpose.

At the terminal type:

wasm3 --repl examples.wasm

At the wasm3> prompt call the avg2 function by typing:

avg2 5 10

The 5 and 10 are the arguments sent to the function. The following line should be printed with the expected result:

Result: 7.500000

Type Ctrl-D or Ctrl-C to exit wasm3.

4.5. Exercise B ★

Add to the wat/examples.wat file a new function called avg3 that takes three 64-bit floating point arguments \(a\), \(b\), and \(c\), and returns their average.

Translate your code using wat2wasm and test it using wasm3. Check that the computed results are correct using the following values:

a b c Result

\(1\)

\(2\)

\(3\)

\(2.0\)

\(14.5\)

\(-7.75\)

\(9.12\)

\(5.29\)

\(6\)

\(6\)

\(6\)

\(6.0\)

To solve this exercise use these Wasm instructions:

4.6. Exercise C ★

Add to the wat/examples.wat file a new function called fah_to_cel that takes as argument a 64-bit floating point value that corresponds to a temperature \(F\) in degrees Fahrenheit and converts it to degrees Celsius.

If \(F\) is a temperature in degrees Fahrenheit, to convert it to \(C\) degrees Celsius you should use the following formula:

\[C = \frac{ 5.0 \times (F - 32.0) }{ 9.0 }\]

Translate your code using wat2wasm and test it using wasm3. Check that the computed results are correct using the following values:

°F °C

\(212.0\)

\(100.0\)

\(32.0\)

\(0.0\)

\(-40.0\)

\(-40.0\)

To solve this exercise use these Wasm instructions:

5. The chiqui_forth Compiler

WebAssembly provides a portable compilation target for languages such as C/C++, Rust, C#, AssemblyScript, and many others. Although writing a compiler for a full blown programming language can be a daunting task, it’s possible to write fairly quickly a WebAssembly targeting compiler for a very small language. This tiny language is called chiqui_forth, a subset of the Forth programming language.

Chiqui (pronounced like “cheeky”) is an informal word in Spanish that refers to something small or tiny. In this case a tiny version of the Forth language.

5.1. The chiqui_forth Language

5.1.1. Overview

Forth is a language unlike most others. It’s not functional or object oriented, it doesn’t have type-checking, and it basically has zero syntax. It was written in the 70s, but is still used today mainly in embedded systems and system controllers.

Forth is a stack-oriented language that is programmed using the Reverse Polish Notation (RPN), which is fairly easy to convert into WebAssembly. The syntax of Forth is extremely straightforward. It consists of a series of space-delimited words. Forth implements a stack machine, just like WebAssembly. The words are processed from left to right. When a number word is found (numbers are represented as 32-bit integers), it’s pushed into the stack. When an operation word is found some values from the top of the stack are popped, the operation is performed with those values, and the result (if any) is pushed back into the stack.

Using a formal notation, the \(\texttt{+}\) operation word can be expressed like this:

\[\begin{matrix} y \gets \textit{pop} \\ x \gets \textit{pop} \\ \textit{push}(x + y) \end{matrix}\]

And the \(\texttt{*}\) operation word can be expressed like this:

\[\begin{matrix} y \gets \textit{pop} \\ x \gets \textit{pop} \\ \textit{push}(x \times y) \end{matrix}\]

A more elaborate example:

1 2 + 3 4 + * .

This is what is happening: - Push 1. - Push 2. - Pop two values, add them, push result (3). - Push 3. - Push 4. - Pop two values, add them, push result (7). - Pop two values (7 and 3), multiply them, push result (21). - The dot (.) word pops a value and prints it.

At the end of the program the stack must be empty, otherwise you’ll get a validation error from the WebAssembly runtime.

The above Forth program is equivalent to the following Python code:

print((1 + 2) * (3 + 4))

5.1.2. Comments

Anything contained between a pair of opening and closing parentheses is a comment and it is therefore ignored by the compiler.

( This is a comment. )

5.1.3. Variables

A variable name must start with a letter and can be followed by any additional letters or digits. When using a variable in a program, the variable name by itself pushes the variable’s value into the stack. A variable name that ends with an exclamation mark (!) pops a value from the top of the stack and assigns it to the said variable. For example:

1 2 + x! (1)
x x + . (2)
1 Push 1. Push 2. Pop two values (2 and 1), add them, push result (3). Pop value (3) and assign it to variable x.
2 Push value of x twice. Pop two values (3 and 3), add them, push result (6). Pop value (6) and print it.

Variables have a default value of 0 in case they’re used before being assigned for the first time.

5.1.4. Input/Output

These are the I/O related words provided by chiqui_forth:

Word Description

\(\texttt{.}\)

Pops the integer value at the top of the stack and prints it on the standard output followed by a single space.

\(\texttt{emit}\)

Similar to the dot word (.), but instead of printing a numeric value it prints a character with a Unicode code point equal to the value popped from the stack. No space is added afterwards.

\(\texttt{nl}\)

Print a newline character on the standard output. This is exactly the same as:

10 emit

\(\texttt{input}\)

Reads from the standard input an integer value and pushes into the stack. Pushes a zero if the value read is not a valid integer.

5.2. Compiler Implementation

An initial implementation of the chiqui_forth compiler is contained in the forth/chiqui_forth.py file.

5.2.1. The main function

The main function is a good place to check out the general steps carried out by the compiler.

The chiqui_forth main function
def main():
    """Control all the steps carried out by the compiler."""
    check_args() (1)
    full_source_name = argv[1] (2)
    words = read_words(full_source_name) (3)
    remove_comments(words) (4)
    result = [] (5)
    result.append(WAT_SOURCE_BEGIN) (6)
    declare_vars(result, find_vars_used(words)) (7)
    code_generation(result, words) (8)
    result.append(WAT_SOURCE_END) (9)
    file_content = '\n'.join(result) (10)
    file_name, _ = splitext(full_source_name) (11)
    create_wat_file(file_name, file_content) (12)
    create_wasm_file(file_name, file_content) (13)
1 Verify that the Python program received a command line argument at the terminal with the name of the input Forth source file. If not, display an error message and exit.
2 Get the name of the input file from the second command line argument (argv[1]). In case you were wondering, the first command line argument (argv[0]) contains the name of the Python program file being executed.
3 Read the content from the input file and split it into space-delimited words.
4 Remove all the words that constitute part of a comment.
5 The result variable starts as an empty list. All WAT instructions will be strings that get appended to this list.
6 Append to result all the code that goes at the start of a WAT source code.
7 Find all the variables used in the program and declare them at the beginning of the exported _start function.
8 Translate every word from the source code into its equivalent WAT instructions and append them to result. This is the core of the compiler.
9 Append to result all the code that goes at the end of a WAT source code.
10 Join all the strings of WAT instructions in result, delimiting individual instructions with newlines.
11 Remove the extension from the user provided input file name in order to create, in the next couple of steps, two new files using the same name but with different extensions.
12 Create a text file with the WAT code.
13 Create a binary file with the WASM code. To do this, we use the wat2wasm function from the wasmer Python package, which basically does the same thing as the WABT wat2wasm tool we saw before.
Wasmer-Python is a complete and mature WebAssembly runtime for Python based on Wasmer.

5.2.2. The OPERATION dictionary

Most of the chiqui_forth words that represent some kind of operation that needs to be translated into WAT are stored in a dictionary called OPERATION. This is how this dictionary is defined:

The chiqui_forth OPERATION dictionary
OPERATION = {
    '*': ['i32.mul'],
    '+': ['i32.add'],
    '.': ['call $print'],
    'emit': ['call $emit'],
    'input': ['call $input'],
    'nl': [
        'i32.const 10',
        'call $emit'
    ],
}

As can be observed, every dictionary key is a string that represents a chiqui_forth word, and its associated value is a list of one or more strings of WAT instructions. The functions $print, $emit, and $input are imported functions and their implementation will be provided by the runtime environment as explained later.

5.2.3. The code_generation function

As mentioned before, the core of the compiler is the code_generation function.

The chiqui_forth code_generation function
def code_generation(result, words): (1)
    for word in words: (2)
        if is_number(word): (3)
            result.append(INDENTATION + f'i32.const {word}')
        elif word in OPERATION: (4)
            for statement in OPERATION[word]:
                result.append(INDENTATION + statement)
        elif is_var_name(word): (5)
            result.append(INDENTATION + f'local.get ${word}')
        elif word[-1] == '!' and is_var_name(word[:-1]): (6)
            result.append(INDENTATION + f'local.set ${word[:-1]}')
        else: (7)
            raise ValueError(f"'{word}' is not a valid word")
1 All the strings that represent WAT instructions will be placed in the result list. The words list contains all the program words as strings.
2 Iterate over all the program words.
3 If the current word is an integer number, add to result the WAT instruction that pushes said number into the stack.
4 If the current word is a key in the OPERATION dictionary, add to result all the associated WAT instructions.
5 If the current word is a variable (not ending in !), add to result the WAT instruction that pushes the variable’s value into the stack.
6 If the current word is a variable ending in !, add to result the WAT instruction that pops the stack and assigns the resulting value to the variable.
7 Any word not recognized will produce an error.
INDENTATION is a string with four spaces. It’s concatenated before each WAT instruction in order to make the resulting code more legible.

5.3. The Execution Script

The file forth/execute.py is a Python script that must be used when running the WASM code produced by our compiler. This is so because WASM doesn’t directly support any I/O facilities. These have to be provided by the runtime system according to our needs. We use the wasmer Python package mentioned earlier to handle the required steps to instantiate our WASM module, call its _start function, and also import the functions $print, $emit, and $input which our generated code depends on. These functions, which are actually quite small and simple, are written in Python and can be found in this script.

5.4. Putting Everything Together

Let’s see how to compile and run the chiqui_forth program.

First, make forth our current working directory. At the terminal type:

cd /workspace/pycon2022-wasm/forth

Now, to run the compiler, type ./chiqui_forth.py at the terminal followed by the name of a chiqui_forth source code file. The forth/examples directory contains several chiqui_forth programs, a couple of them should work with the current version of our compiler. At the terminal type:

./chiqui_forth.py examples/numbers.4th

This creates two new files contained in the forth/examples directory: numbers.wat and numbers.wasm. You can open the forth/examples/numbers.wat file in the editor to inspect the generated WAT code. Use the execution script to run the WASM binary code. At the terminal type:

./execute.py examples/numbers.wasm

The numbers program expects the user to type in two numbers and then prints the result of adding and multiplying them together.

Follow these same steps to compile and execute the forth/examples/hello_world.4th program.

5.5. Exercise D ★

Modify the OPERATION dictionary in forth/chiqui_forth.py so that the compiler supports all the operation words presented in the following table.

chiqui_forth Word Description

\(\texttt{-}\)

Subtraction

\[\begin{matrix} y \gets \textit{pop} \\ x \gets \textit{pop} \\ \textit{push}(x - y) \end{matrix}\]

WAT instruction: i32.sub

\(\texttt{/}\)

Division

\[\begin{matrix} y \gets \textit{pop} \\ x \gets \textit{pop} \\ \textit{push}(x \div y) \end{matrix}\]

WAT instruction: i32.div_s

\(\texttt{=}\)

Equal

\[\begin{matrix} y \gets \textit{pop} \\ x \gets \textit{pop} \\ \textrm{if} \; x = y \; \textrm{then} \; \textit{push}(1) \; \textrm{else} \; \textit{push}(0) \end{matrix}\]

WAT instruction: i32.eq

\(\texttt{<>}\)

Not Equal

\[\begin{matrix} y \gets \textit{pop} \\ x \gets \textit{pop} \\ \textrm{if} \; x \ne y \; \textrm{then} \; \textit{push}(1) \; \textrm{else} \; \textit{push}(0) \end{matrix}\]

WAT instruction: i32.ne

\(\texttt{<}\)

Less Than

\[\begin{matrix} y \gets \textit{pop} \\ x \gets \textit{pop} \\ \textrm{if} \; x < y \; \textrm{then} \; \textit{push}(1) \; \textrm{else} \; \textit{push}(0) \end{matrix}\]

WAT instruction: i32.lt_s

\(\texttt{<=}\)

Less or Equal

\[\begin{matrix} y \gets \textit{pop} \\ x \gets \textit{pop} \\ \textrm{if} \; x \le y \; \textrm{then} \; \textit{push}(1) \; \textrm{else} \; \textit{push}(0) \end{matrix}\]

WAT instruction: i32.le_s

\(\texttt{>}\)

Greater Than

\[\begin{matrix} y \gets \textit{pop} \\ x \gets \textit{pop} \\ \textrm{if} \; x > y \; \textrm{then} \; \textit{push}(1) \; \textrm{else} \; \textit{push}(0) \end{matrix}\]

WAT instruction: i32.gt_s

\(\texttt{>=}\)

Greater or Equal

\[\begin{matrix} y \gets \textit{pop} \\ x \gets \textit{pop} \\ \textrm{if} \; x \ge y \; \textrm{then} \; \textit{push}(1) \; \textrm{else} \; \textit{push}(0) \end{matrix}\]

WAT instruction: i32.ge_s

Test your code by compiling and executing the forth/examples/operators.4th program. The expected output should be:

Everything is working fine!

5.6. Exercise E ★

The do and loop words will allow our chiqui_forth programs to have a repetition construct similar to Python’s while statement. It’s syntax is as follows:

\[\texttt{do} \; \textit{condition} \; \texttt{?} \; \textit{body} \; \texttt{loop}\]

The \(\textit{condition}\) part is first evaluated. If the result is zero (false) the looping construct ends and program execution continues after the loop word. Otherwise the \(\textit{body}\) is executed, the \(\textit{condition}\) is evaluated once again, and the whole process is repeated. Note that the question mark (?) word is required to establish where \(\textit{condition}\) ends and \(\textit{body}\) begins.

The following example shows how to display the numbers 1 to 10.

File: forth/examples/1_to_10.4th
( File: 1_to_10.4th )
( Display numbers 1 to 10, each number its own line. )

1 x!          ( Initialize x with 1. )
do
    x 10 <= ? ( Continue in loop while x is less than or equal to 10. )
    x . nl    ( Print current value of x on its own line. )
    x 1 + x!  ( Increment in one the value of x. )
loop

Modify the OPERATION dictionary in forth/chiqui_forth.py so that the compiler supports the do, ?, and loop words as detailed in the next table:

chiqui_forth Word Corresponding WAT Code

\(\texttt{do}\)

block
loop

\(\texttt{?}\)

i32.eqz
br_if 1

\(\texttt{loop}\)

br 0
end
end

Check the documentation for the block, loop, br, and i32.eqz instructions to understand how they work.

The chiqui_forth do/loop construct creates and uses a new independent stack. This new stack is initially empty and must be empty when the construct ends. Otherwise you’ll get a validation error from the WebAssembly runtime.

After solving exercise D and exercise E compile and execute the following three programs from the forth/examples directory to make sure everything works as expected:

  • 1_to_10.4th

  • triangle.4th (type at the prompt a value from 5 to 20)

  • pow2.4th (type at the prompt a value from 5 to 20)

6. Additional Resources

6.1. Books

7. Acknowledgements

Special thanks to the Tecnológico de Monterrey students from the Development and Implementation of Software Systems course, sections 501 and 502 of the 2022 spring semester, for reviewing these notes and providing valuable feedback.

8. License and Credits