% Copyright 2024 Remy Blank <remy@c-space.org>
% SPDX-License-Identifier: MIT

# Code execution

The {rst:dir}`exec` directive allows executing code from the browser.

```{exec} sql
:name: sql-countries
:when: never
:class: hidden
create table countries (
  country text not null,
  country_code text not null,
  dial_code text not null,
  capital text not null,
  population int not null,
  food text
);
insert into countries values
  ('Switzerland', 'CH', '+41', 'Bern', 8776000, 'fondue!'),
  ('France', 'FR', '+33', 'Paris', 67970000, null),
  ('Germany', 'DE', '+49', 'Berlin', 83800000, null),
  ('Italy', 'IT', '+39', 'Rome', 58940000, null),
  ('Austria', 'AT', '+43', 'Vienna', 9042000, 'Kaiserschmarrn'),
  ('Lichtenstein', 'LI', '+423', 'Vaduz', 39327, null);
```

## Directive

````{rst:directive} .. exec:: runner [env]
This directive is a {rst:dir}`code-block` that allows executing code in the
browser. It supports most of the options of {rst:dir}`code-block`, and a few
more described below.

`runner` is one of the supported code runners ([`html`](#html),
[`micropython`](#micropython), [`python`](#python), [`sql`](#sql)). `env` is an
optional environment name. Code executed in distinct environments is isolated
from each other. The default environment name is the empty string.

{.rubric}
Options
```{rst:directive:option} after: name [name...]
Execute one or more {rst:dir}`exec` blocks before this block, in the same
environment.
```
```{rst:directive:option} editor: [ID]
Display the {rst:dir}`exec` block in an editor. If `ID` is provided, the
content of the editor is saved in browser local storage and restored on reload.
`ID` must be unique across all documents, e.g. a
[UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier).
```
```{rst:directive:option} include: path [path...]
:type: relative paths
Prepend the content of one or more files to the block's content.
```
```{rst:directive:option} output-style: property: value; [property: value; ...]
CSS styles to apply to the output generated by the code block, e.g.
`max-height: 10rem`. To which element the styles are applied is
runner-specific.
```
```{rst:directive:option} style: property: value; [property: value; ...]
CSS styles to apply to the code block and editor, e.g. `max-height: 20rem`.
```
```{rst:directive:option} then: name [name...]
Execute one or more {rst:dir}`exec` blocks after this block, in the same
environment.
```
```{rst:directive:option} when: value
:type: click | load | never
Determine when the block's code is executed: on user request (`click`, the
default), when the page loads (`load`) or not at all (`never`).
```
````

## Trigger

By default, {rst:dir}`exec` blocks are executed on click
({rst:dir}`:when: click <exec:when>`), with controls displayed next to the
  block.

```{exec} sql
:after: sql-countries
select * from countries where country_code = 'LI';
```

They can also be executed immediately on load
({rst:dir}`:when: load <exec:when>`) or not at all
({rst:dir}`:when: never <exec:when>`, useful for definitions that are referenced
by other blocks). The controls displayed depend on the type of block.

```{exec} sql
:after: sql-countries
:when: load
select * from countries where country_code = 'LI';
```

## Editor

Blocks can be made editable with the {rst:dir}`:editor: <exec:editor>` option.

```{exec} sql
:after: sql-countries
:editor:
select * from countries
  where population > 10000000
  order by country_code;
```

The option takes an optional editor ID. If provided, the content of the editor
is saved in browser local storage, and restored on page reload.

```{exec} sql
:after: sql-countries
:editor: a38c47e0-22fe-4d7a-8c0b-4d38e7d55ee1
```

## Sequencing

The {rst:dir}`:after: <exec:after>` option allows referencing one or more
{rst:dir}`exec` blocks on the same page to be executed before the block, in the
same environment. The referenced blocks can themselves have
{rst:dir}`:after: <exec:after>` options, forming a dependency graph.

Similarly, the {rst:dir}`:then: <exec:then>` option allows referencing one or
more {rst:dir}`exec` blocks to be executed after the block. Unlike
{rst:dir}`:after: <exec:after>`, only the blocks referenced by the
{rst:dir}`:then: <exec:then>` option of the block itself are executed; the
{rst:dir}`:then: <exec:then>` options of referenced blocks are ignored.

If a block appears more than once in the graph, only the first occurrence is
executed.

```{exec} sql
:name: sql-people
:when: never
-- :name: sql-people
create table people (first_name text not null, last_name text not null);
```

```{exec} sql
:name: sql-people-select
:when: never
-- :name: sql-people-select
select * from people;
```

```{exec} sql
:after: sql-people
:then: sql-people-select
:editor:
-- :after: sql-people
-- :then: sql-people-select
insert into people values ('Joe', 'Bar'), ('Jack', 'Sparrow');
```

## Include file content

The {rst:dir}`:include: <exec:include>` option allows including the content of
one or more external files. The content of the files is prepended to the block's
content.

```{exec} sql
:include: people.sql
select * from people;
```

## Runners

### HTML

The {rst:dir}`{exec} html <exec>` runner displays a complete HTML document as an
`<iframe>`, with limited browsing functionality.

### MicroPython

The {rst:dir}`{exec} micropython <exec>` runner connects to an embedded system
running [MicroPython](https://micropython.org). The code can either be run from
RAM (transient, programs disappear on reset) or be written to the file `main.py`
in flash memory (permanent, runs at boot-time).

```{admonition} Note
:class: note
The `micropython` runner only works on devices supporting the
[WebSerial API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API).
Currently, this limits the use to
[Chromium-based browsers](https://en.wikipedia.org/wiki/Chromium_(web_browser)#Browsers_based_on_Chromium)
(e.g. Chrome, Edge).
```

The target device must already be programmed with a MicroPython firmware. The
procedure depends on the target device type.

- **BBC micro:bit V2:** Download the `.hex` file for the latest
  [release](https://github.com/microbit-foundation/micropython-microbit-v2/releases).
  Connect the target and mount its filesystem, then copy the file to it.
- **Raspberry Pi Pico:** Follow the
  [documentation](https://www.raspberrypi.com/documentation/microcontrollers/micropython.html#drag-and-drop-micropython)
  to download the appropriate firmware file and program the device.

To enable connecting to a target device, it must first be **paired with the
browser**. Connect the target via USB, select
"<span class="tdoc fa-plug"></span>&nbsp;Connect" in the "Tools" menu
(<button class="tdoc fa-screwdriver-wrench"></button>), and select the device
in the list. This needs to be done only once; once a device is paired, it will
be connected automatically when the page loads, unless multiple paired devices
are available.

To **run a program from RAM**, click the "Run" button
(<button class="tdoc fa-play"></button>). Text input can be sent to the target
via the input field, and the program can be interrupted with the "Stop" button
(<button class="tdoc fa-stop"></button>). When the program terminates, the
target returns to the
[REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) and
accepts further commands.

To **write a program permanently to a target's flash memory**, select
"<span class="tdoc fa-file-arrow-up"></span>&nbsp;Write&nbsp;to&nbsp;<code>main.py</code>"
in the "Tools" menu. The program can be removed again with
"<span class="tdoc fa-trash"></span>&nbsp;Remove&nbsp;<code>main.py</code>".

### Python

The {rst:dir}`{exec} python <exec>` runner executes code through
[Pyodide](https://pyodide.org/). Each environment uses a distinct,
single-threaded interpreter, and all Python blocks specifying the same
environment execute on the same interpreter.

The interpreter for the `main` environment runs on the main browser thread.
Interpreter initialization, as well as blocking Python code, will therefore
block the main thread and affect rendering, user input, etc. It should be used
only when really necessary, e.g. for
[code using Pygame](../demo/python/pygame.md). All other interpreters run in
their own web worker, and don't block user interactions.

Pyodide can be configured via the `exec:python:` {rst:dir}`metadata`. This
enables the following functionality:

- **[Load packages](https://docs.pyscript.net/latest/user-guide/configuration/#packages):**
  The `packages` key is a list of package references, either package names or
  URLs referencing wheels.
- **[Copy files](https://docs.pyscript.net/latest/user-guide/configuration/#files)
  to the filesystem:** The `files` key is a mapping of URL to target path.
  Relative URLs are resolved relative to the `_static` directory. Relative
  target paths are resolved relative to `$HOME` (`/home/pyodide`). If the target
  path ends with a `/`, the filename part of the URL is used as the target
  filename.

```{code-block} yaml
exec:
  python:
    packages: [sqlite3]
    files:
      input.txt:                    # .../_static/input.txt => $HOME/input.txt
      db/init.sql: /tmp/            # .../_static/db/init.sql => /tmp/init.sql
      ../index.html: homepage.html  # .../index.html => $HOME/homepage.html
```

{#run-sync}
#### Synchronous calls to `async` functions

`async` functions can be invoked synchronously with `pyodide.ffi.run_sync()`.
This requires the experimental
[WebAssembly JavaScript promise integration API](https://github.com/WebAssembly/js-promise-integration)
(JSPI), which isn't widely supported yet.

- **Chromium-based browsers (Google Chrome, Microsoft Edge):** The serving
  domain must be registered for the
  [origin trial](https://developer.chrome.com/origintrials/#/register_trial/1603844417297317889).
  For local testing, the feature can be enabled with a flag
  (`chrome://flags/#enable-experimental-webassembly-jspi`).
- **Firefox:** Firefox doesn't have an
  [origin trial](https://wiki.mozilla.org/Origin_Trials) yet, and is still
  working on the [implementation](https://bugzilla.mozilla.org/show_bug.cgi?id=1897981).
  For local testing, the feature can be enabled with a config
  (`javascript.options.wasm_js_promise_integration`), but it doesn't work with
  Pyodide yet.
- **Safari:** Safari doesn't currently support JSPI.

```{note}
Using `pyodide.ffi.run_sync()` instead of `async` concurrency prevents
interrupting running code via the <button class="tdoc fa-stop"></button> button.
```

### SQL

The {rst:dir}`{exec} sql <exec>` runner uses a WebAssembly build of
[SQLite](https://sqlite.org/). Each block execution is performed against a new,
empty database.
