WebAssembly Integration Guide
AILANG can run entirely in the browser using WebAssembly, enabling interactive demonstrations and online playgrounds without requiring server-side execution.
Overview
The AILANG WebAssembly build provides:
- Full Language Support: Complete AILANG interpreter compiled to WASM
- Standard Library Included: All 20 stdlib modules (
std/list,std/json, etc.) embedded and auto-loaded - Client-Side Execution: No server needed after initial load
- Small Bundle Size: ~33MB uncompressed (~5-8MB with gzip)
- React Integration: Ready-made component for easy integration
- Offline Capable: Works offline after first load
Quick Start
1. Build WASM Binary
cd ailang
make build-wasm
This produces bin/ailang.wasm.
2. Integration Options
Option A: Docusaurus (Recommended)
- Copy assets:
cp bin/ailang.wasm docs/static/wasm/
# Download Go's WASM support file (more reliable than GOROOT path)
curl -sL -o docs/static/wasm/wasm_exec.js \
https://raw.githubusercontent.com/golang/go/go1.22.0/misc/wasm/wasm_exec.js
cp web/ailang-repl.js docs/src/components/
cp web/AilangRepl.jsx docs/src/components/
- Add to
docusaurus.config.js:
module.exports = {
scripts: [
{
src: '/wasm/wasm_exec.js',
async: false,
},
],
// ... rest of config
};
- Use in MDX:
---
title: Try AILANG
---
import AilangRepl from '@site/src/components/AilangRepl';
<AilangRepl />
Option B: Vanilla HTML
<!DOCTYPE html>
<html>
<head>
<title>AILANG REPL</title>
<script src="wasm_exec.js"></script>
<script src="ailang-repl.js"></script>
</head>
<body>
<div id="repl-container"></div>
<script>
const repl = new AilangREPL();
repl.init('/path/to/ailang.wasm').then(() => {
console.log('AILANG ready!');
// Evaluate expressions
const result = repl.eval('1 + 2');
console.log(result); // "3 :: Int"
});
</script>
</body>
</html>
Option C: React (Custom)
import { useEffect, useState } from 'react';
import AilangREPL from './ailang-repl';
export default function MyReplComponent() {
const [repl, setRepl] = useState(null);
const [result, setResult] = useState('');
useEffect(() => {
const replInstance = new AilangREPL();
replInstance.init('/wasm/ailang.wasm').then(() => {
setRepl(replInstance);
});
}, []);
const handleEval = (input) => {
if (repl) {
const output = repl.eval(input);
setResult(output);
}
};
return (
<div>
<input onKeyDown={(e) => {
if (e.key === 'Enter') handleEval(e.target.value);
}} />
<pre>{result}</pre>
</div>
);
}
JavaScript API
AilangREPL Class
const repl = new AilangREPL();
Methods
init(wasmPath)
Initialize the WASM module.
await repl.init('/wasm/ailang.wasm');
Parameters:
wasmPath(string): Path toailang.wasmfile
Returns: Promise that resolves when REPL is ready
eval(input)
Evaluate an AILANG expression.
const result = repl.eval('1 + 2');
// Returns: "3 :: Int"
Parameters:
input(string): AILANG code to evaluate
Returns: Result string (includes value and type)
command(cmd)
Execute a REPL command.
const type = repl.command(':type \x. x');
// Returns: "\x. x :: a -> a"
Parameters:
cmd(string): REPL command (e.g.,:type,:help)
Returns: Command output string
reset()
Reset the REPL environment.
repl.reset();
Returns: Status message
onReady(callback)
Register callback for when REPL is ready.
repl.onReady(() => {
console.log('REPL initialized!');
});
getVersion()
Get version information.
const version = repl.getVersion();
// Returns: "v0.7.2" (or null if not ready)
Returns: Version string or null
Module Loading API (v0.7.1+)
The WASM REPL supports loading complete AILANG modules, enabling complex browser-based demos with multiple function definitions.
The REPL evaluates expressions line-by-line, so function definitions like let add = \x. \y. x + y don't persist in scope for subsequent calls. The Module Loading API solves this by compiling entire modules and storing their exports for later use.
loadModule(name, code)
Load an AILANG module into the registry.
const result = repl.loadModule('math', `
let add: Int -> Int -> Int = \\x. \\y. x + y
let mul: Int -> Int -> Int = \\x. \\y. x * y
`);
if (result.success) {
console.log('Exports:', result.exports);
// Exports: ["add", "mul"]
} else {
console.error('Error:', result.error);
}
Parameters:
name(string): Module name (e.g.,"math","invoice_processor")code(string): AILANG source code
Returns: Object with:
success(boolean): Whether loading succeededexports(string[]): List of exported function names (on success)error(string): Error message (on failure)
For functions with numeric operations, you must include explicit type annotations:
// ✅ Works
let add: Int -> Int -> Int = \\x. \\y. x + y
// ❌ Fails with "ambiguous type variable"
let add = \\x. \\y. x + y
Modules can use explicit exports or export all bindings:
Explicit exports (recommended for libraries):
// Only public_func is exported, private_func is hidden
repl.loadModule('mylib', `
module mylib
export pure func public_func(x: int) -> int = x * 2
pure func private_func(x: int) -> int = x * 3
`);
// result.exports: ["public_func"]
Implicit exports (for REPL-style code):
// All top-level bindings are exported
repl.loadModule('utils', `
let double: Int -> Int = \\x. x * 2
let triple: Int -> Int = \\x. x * 3
`);
// result.exports: ["double", "triple"]
listModules() (v0.7.1+)
List all loaded modules.
const modules = repl.listModules();
// Returns: ["math", "utils"]
Returns: Array of module names
importModule(moduleName) (v0.7.1+)
Import a module's exports into the REPL environment.
repl.importModule('math');
// Now you can use: repl.eval('add(2)(3)')
Parameters:
moduleName(string): Name of a loaded module
Returns: Import result message
call(moduleName, funcName, ...args)
Call a function from a loaded module. This is a convenience method that:
- Looks up the function from the module registry
- Converts JavaScript arguments to AILANG values
- Invokes the function directly (no eval)
// Load a module
repl.loadModule('math', `
let add: Int -> Int -> Int = \\x. \\y. x + y
let greet = \\name. "Hello, " <> name
`);
// Call functions
const sum = repl.call('math', 'add', 2, 3);
if (sum.success) {
console.log(sum.result); // "5 :: Int"
}
const greeting = repl.call('math', 'greet', 'World');
if (greeting.success) {
console.log(greeting.result); // "\"Hello, World\" :: String"
}
Parameters:
moduleName(string): Module containing the functionfuncName(string): Function to call...args(any): Arguments (converted to AILANG values)
Returns: Object with:
success(boolean): Whether the call succeededresult(string): Result with value and type (on success)error(string): Error message (on failure)
Supported argument types:
| JavaScript Type | AILANG Syntax |
|---|---|
number | 42 or 3.14 |
string | "text" |
boolean | true / false |
array | [1, 2, 3] |
Module Loading Example
Here's a complete example building an invoice processor demo:
<!DOCTYPE html>
<html>
<head>
<title>Invoice Processor Demo</title>
<script src="wasm_exec.js"></script>
<script src="ailang-repl.js"></script>
</head>
<body>
<script>
const repl = new AilangREPL();
repl.init('/wasm/ailang.wasm').then(() => {
// Load the invoice processor module
const result = repl.loadModule('invoice', `
-- Invoice processing functions
let calculateTotal: Int -> Float -> Float = \\quantity. \\unitPrice.
int_to_float(quantity) * unitPrice
let applyDiscount: Float -> Float -> Float = \\total. \\discountPercent.
total * (1.0 - discountPercent / 100.0)
let formatCurrency: Float -> String = \\amount.
"$" <> float_to_string(amount)
`);
if (!result.success) {
console.error('Failed to load module:', result.error);
return;
}
console.log('Loaded exports:', result.exports);
// ["calculateTotal", "applyDiscount", "formatCurrency"]
// Process an invoice
const subtotal = repl.call('invoice', 'calculateTotal', 5, 19.99);
if (subtotal.success) {
console.log('Subtotal:', subtotal.result);
// "99.95 :: Float"
}
const total = repl.call('invoice', 'applyDiscount', 99.95, 10);
if (total.success) {
console.log('After 10% discount:', total.result);
// "89.955 :: Float"
}
// Or use eval directly after importing
repl.importModule('invoice');
const formatted = repl.eval('formatCurrency(89.95)');
console.log('Formatted:', formatted);
// "\"$89.95\" :: String"
});
</script>
</body>
</html>
REPL Commands
The WebAssembly REPL supports the same commands as the CLI:
| Command | Description |
|---|---|
:help | Show available commands |
:type <expr> | Display expression type |
:instances | Show type class instances |
:reset | Clear environment |
Limitations
The browser version has these limitations compared to the CLI:
| Feature | CLI | WASM |
|---|---|---|
| Expression evaluation | Yes | Yes |
| Type inference | Yes | Yes |
| Pattern matching | Yes | Yes |
| Type classes | Yes | Yes |
| Module loading | Yes | Yes (v0.7.1+) |
| Standard library | Yes | Yes (v0.7.1+)* |
| Explicit exports | Yes | Yes (v0.7.1.2+) |
File I/O (FS effect) | Yes | No |
| Custom file imports | Yes | No |
| History persistence | Yes | No |
* The standard library (20 modules including std/list, std/json, std/string) is embedded in the WASM binary and auto-loaded on init. Use importModule('std/list') to bring stdlib exports into scope. Custom user file imports require loadModule() instead.
Deployment
Static Hosting
WASM files work on any static host:
# Build and deploy
make build-wasm
cp bin/ailang.wasm your-site/static/wasm/
# Deploy your-site/ to Netlify/Vercel/GitHub Pages
CDN Optimization
- Enable Compression:
# nginx.conf
gzip_types application/wasm;
- Set Cache Headers:
location ~* \.wasm$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
- Use HTTP/2: WASM benefits from HTTP/2 multiplexing for faster loading.
Performance Tips
- Lazy Loading: Only load WASM when user navigates to playground
- Service Worker: Cache WASM for offline use
- CDN: Serve from edge locations
- Preload: Add
<link rel="preload" href="ailang.wasm" as="fetch">
CI/CD Integration
GitHub Actions
WASM is automatically built and released:
# .github/workflows/release.yml (excerpt)
- name: Build WASM binary
run: make build-wasm
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: bin/ailang-wasm.tar.gz
Docusaurus Deployment
WASM is included in documentation builds:
# .github/workflows/docusaurus-deploy.yml (excerpt)
- name: Build WASM binary
run: make build-wasm
- name: Copy static assets
run: |
cp bin/ailang.wasm docs/static/wasm/
cp web/ailang-repl.js docs/src/components/
Troubleshooting
"WebAssembly not supported"
Solution: Use a modern browser:
- Chrome 57+
- Firefox 52+
- Safari 11+
- Edge 16+
"Failed to load AILANG WASM"
Solutions:
- Check browser console for network errors
- Verify
ailang.wasmpath is correct - Ensure
wasm_exec.jsloaded first - Check CORS headers if serving from different domain
"REPL not initialized"
Solution: Wait for init() promise or use onReady():
repl.init('/wasm/ailang.wasm').then(() => {
// Safe to use repl here
repl.eval('1 + 2');
});
Slow Loading
Solutions:
- Enable gzip compression (reduces to ~1-2MB)
- Use CDN
- Add preload hints:
<link rel="preload" href="/wasm/ailang.wasm" as="fetch" crossorigin>
Module Loading Errors
"ambiguous type variable α with classes [Num]"
Cause: Functions with numeric operations need explicit type annotations.
Solution: Add type annotations to function definitions:
// ❌ Fails
let add = \\x. \\y. x + y
// ✅ Works
let add: Int -> Int -> Int = \\x. \\y. x + y
"module X not loaded (use loadModule first)"
Cause: Trying to call or import a module that hasn't been loaded.
Solution: Load the module before using it:
// First load
repl.loadModule('mymodule', code);
// Then import or call
repl.importModule('mymodule');
"parse error" or "type error" from loadModule
Cause: Invalid AILANG syntax or type errors in module code.
Solution: Check the error message and fix the module code:
const result = repl.loadModule('test', badCode);
if (!result.success) {
console.error('Module error:', result.error);
// e.g., "parse error: expected expression at line 3"
}
Examples
See:
- Live Playground - Try it now
- Integration Example
- Component Source