Repository navigation
feat: add Elixir and Erlang (Wasm) support #1076
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
HMH-3080
wants to merge
10
commits into
live-codes:develop
Choose a base branch
from
HMH-3080:feat/elixir-wasm-support
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+3,154
−3
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
b0d3ed0
feat: add Elixir (Wasm) with persistent AtomVM runtime
HMH-3080 6c4f759
feat: use the official Elixir logo in the starter
HMH-3080 e1a6fe7
chore: annotate the Elixir worker's intentional eval
HMH-3080 a6d122f
feat: run Beam Wasm without cross-origin isolation and add Erlang
HMH-3080 e86cd91
test: drop cross-origin-isolated allow expectation
HMH-3080 73f719a
style: format language info HTML
HMH-3080 8f1c892
docs: move Wasm benchmark notes to browser-elixir
HMH-3080 a093ff4
docs: clarify when loaded resolves for empty programs
HMH-3080 f4da50a
Merge remote-tracking branch 'upstream/develop' into feat/erlang-wasm
HMH-3080 61159a8
test: fix flaky Elixir Wasm spec due to live reload race
HMH-3080 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| # Elixir (Wasm) | ||
|
|
||
| Elixir is a dynamic, functional language for building scalable and maintainable applications, powered by the Erlang VM. | ||
|
|
||
| In LiveCodes, Elixir runs in the browser using WebAssembly: the Elixir compiler and standard library execute on [AtomVM](https://atomvm.org/), a lightweight Erlang VM with a WebAssembly target. No server is involved. | ||
|
|
||
| ## Usage | ||
|
|
||
| Demo: | ||
|
|
||
| import LiveCodes from '../../src/components/LiveCodes.tsx'; | ||
|
|
||
| export const elixirConfig = { | ||
| activeEditor: 'script', | ||
| script: { | ||
| language: 'elixir-wasm', | ||
| content: `IO.puts("Hello, Elixir!")`, | ||
| }, | ||
| mode: 'simple', | ||
| editor: 'auto', | ||
| tools: { | ||
| status: 'full', | ||
| }, | ||
| }; | ||
|
|
||
| <LiveCodes config={elixirConfig}></LiveCodes> | ||
|
|
||
| ### Communication with JavaScript | ||
|
|
||
| The program runs in a persistent worker owned by the result page, not in the result page's JavaScript | ||
| context. The browser global `livecodes.elixir` object is the result page's interface to that worker: | ||
|
|
||
| - `livecodes.elixir.input`: the standard input passed to the program. | ||
| - `livecodes.elixir.loaded`: a promise that resolves after the initial program run has completed. | ||
| For a non-empty program, that includes downloading and booting the runtime; an empty initial | ||
| program resolves before the runtime is downloaded, and initialization starts on the first | ||
| non-empty run. It rejects if initialization fails. Other helpers should be used after it resolves. | ||
| - `livecodes.elixir.output`: the program's standard output. | ||
| - `livecodes.elixir.value`: the inspected return value of the program. | ||
| - `livecodes.elixir.warnings`: warnings emitted while compiling or running the program. | ||
| - `livecodes.elixir.error`: the syntax or runtime error, if the program failed. | ||
| - `livecodes.elixir.exitCode`: the exit code of the program. | ||
| - `livecodes.elixir.run`: runs the current code with new input, reusing the booted VM, and returns a | ||
| promise that resolves with `output`, `value`, `warnings`, `error` and `exitCode`. | ||
| - `livecodes.elixir.retry`: restarts the runtime after a failed initialization. A VM crash in a | ||
| normal run is recovered automatically on the next run. | ||
|
|
||
| Example: | ||
|
|
||
| <LiveCodes template="elixir-wasm" params={{ activeEditor: 'markup' }} height="80vh"></LiveCodes> | ||
|
|
||
| ```elixir | ||
| IO.puts("Hello, Elixir!") | ||
|
|
||
| squares = Enum.map(1..5, &(&1 * &1)) | ||
| IO.inspect(squares, label: "squares") | ||
| ``` | ||
|
|
||
| ### Runtime lifecycle | ||
|
|
||
| The VM runs in a persistent worker owned by the result window. Updating the code | ||
| rewrites the result document but does not destroy the worker, so normal edits | ||
| reuse the booted VM instead of downloading and initializing it again. | ||
|
|
||
| Each worker boot compiles a small Elixir prelude. That takes a few seconds on | ||
| the first run; subsequent runs only evaluate the user code. Evaluations time out | ||
| after 60 seconds. If a newer run is waiting and the active evaluation does not | ||
| finish within 10 seconds, the worker is terminated and recreated so the newer | ||
| run is not blocked. Pending edits are coalesced to the latest one. A VM crash is | ||
| reported in `error`; the next run starts a fresh worker automatically. Use | ||
| `retry()` when initialization itself failed. | ||
|
|
||
| ## Language Info | ||
|
|
||
| ### Name | ||
|
|
||
| `elixir-wasm` | ||
|
|
||
| ### Aliases / Extensions | ||
|
|
||
| `ex`, `exs`, `elixir`, `elixir-wasm`, `wasm.ex` | ||
|
|
||
| ### Editor | ||
|
|
||
| `script` | ||
|
|
||
| ## Compiler | ||
|
|
||
| The Elixir toolchain compiled to WebAssembly, running on | ||
| [AtomVM](https://github.com/atomvm/AtomVM) with the | ||
| [Popcorn](https://popcorn.swmansion.com/) browser runtime. The single-threaded runtime is published | ||
| as [`@hmh-3080/atomvm-wasm`](https://www.npmjs.com/package/@hmh-3080/atomvm-wasm); the Elixir | ||
| and Erlang bytecode bundle is pinned to | ||
| `live-codes/elixir-browser-eval@6b354dab35fb7454ba2812f935b52b398ca1bec8`. | ||
|
|
||
| ### Version | ||
|
|
||
| Elixir 1.17.3 on OTP 27, with AtomVM 0.7.0-dev (32-bit). | ||
|
|
||
| ### Limitations | ||
|
|
||
| AtomVM implements a subset of the Erlang VM and its standard library. Features that depend on a full | ||
| operating system (for example file I/O and OS processes) may be missing or behave differently. | ||
| Examples include `File.write!/2` (`not_implemented`), `Date.utc_today/0`, `Task.async/await`, | ||
| `Regex.match?/2`, and the `JSON` module. `System.halt/1` terminates the VM instead of returning an | ||
| exit status; the runtime reports the crash and the next run boots a fresh worker. Frameworks that | ||
| require a server, such as Phoenix and LiveView, are out of scope for the playground. | ||
|
|
||
| The runtime is a single-threaded AtomVM build. It does not use WebAssembly threads, | ||
| `SharedArrayBuffer`, or `Cross-Origin-Opener-Policy` / `Cross-Origin-Embedder-Policy` | ||
| headers, so it runs in normal (non-isolated) result pages and keeps popup-based OAuth working. | ||
|
|
||
| ## Code Formatting | ||
|
|
||
| Not currently supported. | ||
|
|
||
| ## Live Reload | ||
|
|
||
| By default, new code changes are sent to the result page for re-evaluation without a full page reload | ||
| or a VM restart. This behavior can be disabled by adding the code comment `# __livecodes_reload__` to | ||
| the Elixir code, which forces a full page reload. | ||
|
|
||
| This comment can be added in the `hiddenContent` property of the editor for embedded playgrounds. | ||
|
|
||
| ## Starter Template | ||
|
|
||
| https://livecodes.io/?template=elixir-wasm | ||
|
|
||
| ## Links | ||
|
|
||
| - [Elixir official website](https://elixir-lang.org/) | ||
| - [Elixir documentation](https://hexdocs.pm/elixir/) | ||
| - [Learn X in Y minutes, where X=Elixir](https://learnxinyminutes.com/elixir/) | ||
| - [AtomVM](https://atomvm.org/) | ||
| - [@hmh-3080/atomvm-wasm](https://www.npmjs.com/package/@hmh-3080/atomvm-wasm) | ||
| - [Popcorn: Elixir and Wasm](https://popcorn.swmansion.com/) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| # Erlang (Wasm) | ||
|
|
||
| Erlang is a functional, concurrent language for building scalable and fault-tolerant systems. | ||
|
|
||
| In LiveCodes, Erlang runs in the browser using WebAssembly on [AtomVM](https://atomvm.org/), the same lightweight Erlang VM used by [Elixir (Wasm)](./elixir-wasm.mdx). No server is involved. | ||
|
|
||
| ## Usage | ||
|
|
||
| Demo: | ||
|
|
||
| import LiveCodes from '../../src/components/LiveCodes.tsx'; | ||
|
|
||
| export const erlangConfig = { | ||
| activeEditor: 'script', | ||
| script: { | ||
| language: 'erlang-wasm', | ||
| content: `io:format("Hello, Erlang!~n").`, | ||
| }, | ||
| mode: 'simple', | ||
| editor: 'auto', | ||
| tools: { | ||
| status: 'full', | ||
| }, | ||
| }; | ||
|
|
||
| <LiveCodes config={erlangConfig}></LiveCodes> | ||
|
|
||
| ### Communication with JavaScript | ||
|
|
||
| The program runs in a persistent worker owned by the result page, not in the result page's JavaScript | ||
| context. The browser global `livecodes.erlang` object is the result page's interface to that worker: | ||
|
|
||
| - `livecodes.erlang.input`: the standard input passed to the program. | ||
| - `livecodes.erlang.loaded`: a promise that resolves after the runtime has downloaded and the first | ||
| program run has completed. It rejects if the environment fails to load. Other helpers should be | ||
| used after it resolves. | ||
| - `livecodes.erlang.output`: the program's standard output. | ||
| - `livecodes.erlang.value`: the inspected return value of the program. | ||
| - `livecodes.erlang.warnings`: warnings emitted while compiling or running the program. | ||
| - `livecodes.erlang.error`: the syntax or runtime error, if the program failed. | ||
| - `livecodes.erlang.exitCode`: the exit code of the program. | ||
| - `livecodes.erlang.run`: runs the current code with new input, reusing the booted VM, and returns a | ||
| promise that resolves with `output`, `value`, `warnings`, `error` and `exitCode`. | ||
| - `livecodes.erlang.retry`: restarts the runtime after a failed initialization. A VM crash in a | ||
| normal run is recovered automatically on the next run. | ||
|
|
||
| Example: | ||
|
|
||
| <LiveCodes template="erlang-wasm" params={{ activeEditor: 'markup' }} height="80vh"></LiveCodes> | ||
|
|
||
| ```erlang | ||
| io:format("Hello, Erlang!~n"), | ||
| [X * X || X <- lists:seq(1, 5)]. | ||
| ``` | ||
|
|
||
| ### Modules and stdin | ||
|
|
||
| Erlang expressions are comma-separated and must end with a dot. Code that starts with `-module(` | ||
| is compiled and loaded into the running VM, so it stays callable in later runs: | ||
|
|
||
| ```erlang | ||
| -module(m). | ||
| -export([double/1]). | ||
|
|
||
| double(X) -> X * 2. | ||
| ``` | ||
|
|
||
| Then run `m:double(21).` in a later run. `io:get_line("")` reads the standard input, and | ||
| `erlang:halt(0)` (or a bare `halt(0)`) sets the exit code. | ||
|
|
||
| The shared stdin device returns binaries. Use `binary_to_list/1` when an API needs a charlist, | ||
| and remove the trailing newline with `binary:replace(Value, <<"\n">>, <<>>, [global])` | ||
| (`string:trim/1` is not reliable on AtomVM). | ||
|
|
||
| ### Runtime lifecycle | ||
|
|
||
| The VM runs in a persistent worker owned by the result window. Updating the code | ||
| rewrites the result document but does not destroy the worker, so normal edits | ||
| reuse the booted VM instead of downloading and initializing it again. | ||
|
|
||
| Each worker boot compiles a small Elixir prelude that provides the shared I/O | ||
| device for Erlang and Elixir. That takes a few seconds on the first run; | ||
| subsequent runs only evaluate the user code. Evaluations time out after 60 | ||
| seconds. If a newer run is waiting and the active evaluation does not finish | ||
| within 10 seconds, the worker is terminated and recreated so the newer run is | ||
| not blocked. Pending edits are coalesced to the latest one. A VM crash is | ||
| reported in `error`; the next run starts a fresh worker automatically. Use | ||
| `retry()` when initialization itself failed. | ||
|
|
||
| ## Language Info | ||
|
|
||
| ### Name | ||
|
|
||
| `erlang-wasm` | ||
|
|
||
| ### Aliases / Extensions | ||
|
|
||
| `erl`, `erlang`, `erlang-wasm`, `wasm.erl` | ||
|
|
||
| ### Editor | ||
|
|
||
| `script` | ||
|
|
||
| ## Compiler | ||
|
|
||
| The Erlang runtime compiled to WebAssembly, running on | ||
| [AtomVM](https://github.com/atomvm/AtomVM) with the | ||
| [Popcorn](https://popcorn.swmansion.com/) browser runtime. The single-threaded runtime is published | ||
| as [`@hmh-3080/atomvm-wasm`](https://www.npmjs.com/package/@hmh-3080/atomvm-wasm); it shares the | ||
| pinned `live-codes/elixir-browser-eval@6b354dab35fb7454ba2812f935b52b398ca1bec8` bytecode bundle | ||
| with Elixir (Wasm). | ||
|
|
||
| ### Version | ||
|
|
||
| AtomVM 0.7.0-dev (32-bit), with the Erlang runtime bundled in the shared Elixir/Erlang Wasm bundle. | ||
|
|
||
| ### Limitations | ||
|
|
||
| AtomVM implements a subset of the Erlang VM and its standard library. Features that depend on a full | ||
| operating system (for example file I/O and OS processes) may be missing or behave differently. | ||
| Frameworks and applications that require a server are out of scope for the playground. | ||
|
|
||
| The runtime is a single-threaded AtomVM build. It does not use WebAssembly threads, | ||
| `SharedArrayBuffer`, or `Cross-Origin-Opener-Policy` / `Cross-Origin-Embedder-Policy` | ||
| headers, so it runs in normal (non-isolated) result pages and keeps popup-based OAuth working. | ||
|
|
||
| ## Code Formatting | ||
|
|
||
| Not currently supported. | ||
|
|
||
| ## Live Reload | ||
|
|
||
| By default, new code changes are sent to the result page for re-evaluation without a full page reload | ||
| or a VM restart. This behavior can be disabled by adding the code comment `% __livecodes_reload__` to | ||
| the Erlang code, which forces a full page reload. | ||
|
|
||
| This comment can be added in the `hiddenContent` property of the editor for embedded playgrounds. | ||
|
|
||
| ## Starter Template | ||
|
|
||
| https://livecodes.io/?template=erlang-wasm | ||
|
|
||
| ## Links | ||
|
|
||
| - [Erlang official website](https://www.erlang.org/) | ||
| - [Erlang documentation](https://www.erlang.org/docs) | ||
| - [Learn X in Y minutes, where X=Erlang](https://learnxinyminutes.com/erlang/) | ||
| - [AtomVM](https://atomvm.org/) | ||
| - [@hmh-3080/atomvm-wasm](https://www.npmjs.com/package/@hmh-3080/atomvm-wasm) | ||
| - [Popcorn: Elixir and Wasm](https://popcorn.swmansion.com/) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.