Skip to main content

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

  1. 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/
  1. Add to docusaurus.config.js:
module.exports = {
scripts: [
{
src: '/wasm/wasm_exec.js',
async: false,
},
],
// ... rest of config
};
  1. 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 to ailang.wasm file

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.

Why Module Loading?

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 succeeded
  • exports (string[]): List of exported function names (on success)
  • error (string): Error message (on failure)
Type Annotations Required

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
Export Behavior (v0.7.1.2+)

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:

  1. Looks up the function from the module registry
  2. Converts JavaScript arguments to AILANG values
  3. 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 function
  • funcName (string): Function to call
  • ...args (any): Arguments (converted to AILANG values)

Returns: Object with:

  • success (boolean): Whether the call succeeded
  • result (string): Result with value and type (on success)
  • error (string): Error message (on failure)

Supported argument types:

JavaScript TypeAILANG Syntax
number42 or 3.14
string"text"
booleantrue / 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:

CommandDescription
:helpShow available commands
:type <expr>Display expression type
:instancesShow type class instances
:resetClear environment

Limitations

The browser version has these limitations compared to the CLI:

FeatureCLIWASM
Expression evaluationYesYes
Type inferenceYesYes
Pattern matchingYesYes
Type classesYesYes
Module loadingYesYes (v0.7.1+)
Standard libraryYesYes (v0.7.1+)*
Explicit exportsYesYes (v0.7.1.2+)
File I/O (FS effect)YesNo
Custom file importsYesNo
History persistenceYesNo

* 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

  1. Enable Compression:
# nginx.conf
gzip_types application/wasm;
  1. Set Cache Headers:
location ~* \.wasm$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
  1. 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:

  1. Check browser console for network errors
  2. Verify ailang.wasm path is correct
  3. Ensure wasm_exec.js loaded first
  4. 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:

  1. Enable gzip compression (reduces to ~1-2MB)
  2. Use CDN
  3. 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:

Next Steps