packages feed

CPL-0.2.0: web/README.md

# CPL WebAssembly Edition

This directory contains the source files for the WebAssembly version of the CPL (Categorical Programming Language) interpreter.

Build outputs go to the `_site/` directory in the project root.

## Try Online

Visit https://msakai.github.io/cpl/ and start using CPL immediately!

### Commands to Try

```
cpl> edit
| right object 1 with !
| end object;
right object 1 defined
cpl> edit
| right object prod(a,b) with pair is
|   pi1: prod -> a
|   pi2: prod -> b
| end object;
right object prod(+,+) defined
cpl> edit
| right object exp(a,b) with curry is
|   eval: prod(exp,a) -> b
| end object;
right object exp(-,+) defined
cpl> edit
| left object nat with pr is
|   0: 1 -> nat
|   s: nat -> nat
| end object;
left object nat defined
cpl> show pair(pi2,eval)
pair(pi2,eval)
    : prod(exp(*a,*b),*a) -> prod(*a,*b)
cpl> let add=eval.prod(pr(curry(pi2), curry(s.eval)), I)
add : prod(nat,nat) -> nat  defined
cpl> simp add.pair(s.s.0, s.0)
s.s.s.0
    : 1 -> nat
```

## Files

### Source files (in this directory)

- **index.html** - Web interface with xterm.js terminal emulator
- **cpl-terminal.js** - JavaScript terminal controller and WASM loader
- **tutorial.css** - Stylesheet for tutorial pages

### Generated files (in `_site/`)

The build process outputs the following files to `_site/`:

- **cpl.wasm** - Compiled CPL interpreter
- **cpl.js** - JavaScript glue module (generated by post-link processing)
- **samples.js** - Sample files module
- Plus copies of the source files above

## Building

The WASM binary is built using GHC's WebAssembly backend. To build it yourself:

```bash
# From the project root
./scripts/build-wasm.sh
```

### Prerequisites

Install GHC WebAssembly cross-compiler (GHC 9.10.3 or later):

```bash
curl https://gitlab.haskell.org/haskell-wasm/ghc-wasm-meta/-/raw/master/bootstrap.sh | sh
source ~/.ghc-wasm/env
```

This provides `wasm32-wasi-ghc` and `wasm32-wasi-cabal` in `PATH`.

Build configuration:

```bash
wasm32-wasi-cabal configure -fWeb -f-Haskeline
```

To clean and rebuild:

```bash
./scripts/build-wasm.sh --clean
```

## Testing Locally

To test the WASM version locally:

```bash
# Build WASM and web files
./scripts/build-wasm.sh

# Optionally build tutorial pages
./scripts/build-tutorial.sh

# Start local server
python3 -m http.server -d _site 8000
```

Then open http://localhost:8000 in your browser.

## Deployment

### Automatic Deployment

- **Trigger:** Push to `master` branch
- **Build trigger:** Also runs on tags, pull requests, and manual dispatch (build only, no deploy)
- **Workflow:** `.github/workflows/wasm-deploy.yaml`
- **Destination:** GitHub Pages (https://msakai.github.io/cpl/)
- **Artifacts:** `cpl.wasm`, `cpl.js`, `index.html`, `cpl-terminal.js`, `samples.js`

### Manual Deployment

```bash
# Build WASM and tutorial pages
./scripts/build-wasm.sh
./scripts/build-tutorial.sh

# Deploy files from _site/ directory to your web server
```

## Browser Compatibility

Tested and working on:
- Chrome/Chromium 90+
- Firefox 90+
- Safari 15+
- Edge 90+

## Architecture

```
Browser
  ├── xterm.js (terminal UI)
  └── cpl-terminal.js (FFI bridge)
       └── cpl.wasm (CPL interpreter)
            └── Haskell runtime
```

### Console Abstraction

The Haskell code uses CPP macros to conditionally compile different Console implementations, allowing the same codebase to support multiple backends:

```haskell
#if defined(USE_WEB_BACKEND)
  -- JavaScript FFI implementation (GHC.Wasm.Prim / JSString)
#elif defined(USE_HASKELINE_PACKAGE)
  -- Haskeline implementation
#else
  -- Plain IO implementation
#endif
```

For WASM, four JavaScript FFI functions are exposed:
- `terminal_initialize()` - Initialize xterm.js terminal
- `terminal_printLine(text)` - Print a line to terminal
- `terminal_readLine(prompt)` - Read user input (async)
- `terminal_loadFile(filename)` - Load a file by name or open file picker

### JavaScript FFI Flow

1. **Haskell declares FFI imports/exports** in `src/Main.hs`:
   ```haskell
   foreign import javascript "terminal_readLine($1)"
     js_readLine :: JSString -> IO JSString

   foreign import javascript "terminal_loadFile($1)"
     js_loadFile :: JSString -> IO JSString

   foreign export javascript "hs_start" main :: IO ()
   ```

2. **JavaScript implements FFI functions** in `cpl-terminal.js`:
   ```javascript
   window.terminal_readLine = async (jsVal) => {
     const prompt = String(jsVal);
     const result = await cplTerminal.readLine(prompt);
     return result;
   };

   window.terminal_loadFile = async (jsVal) => {
     const filename = String(jsVal).trim();
     if (filename) {
       // Look up built-in sample files
       if (sampleFiles[filename] !== undefined) {
         return sampleFiles[filename];
       }
       throw new Error(`File not found: ${filename}`);
     }
     // No filename: open file picker dialog
     ...
   };
   ```

3. **JSFFI glue code (`cpl.js`) connects them** at instantiation using a knot-tying pattern:
   ```javascript
   const jsffiModule = await import('./cpl.js');
   const __exports = {};
   const jsffi = jsffiModule.default(__exports);

   const { instance } = await WebAssembly.instantiate(wasmBytes, {
     ghc_wasm_jsffi: jsffi,
     wasi_snapshot_preview1: wasi.wasiImport,
   });
   Object.assign(__exports, instance.exports);
   ```

### WASI Integration

The browser environment requires a WASI shim (`@bjorn3/browser_wasi_shim`) to satisfy the WASI snapshot preview1 imports that GHC's WASM backend emits:

```javascript
import { WASI, OpenFile, File, ConsoleStdout } from '@bjorn3/browser_wasi_shim';

const fds = [
  new OpenFile(new File([])),                                       // stdin
  ConsoleStdout.lineBuffered(msg => console.log(`[CPL] ${msg}`)),   // stdout
  ConsoleStdout.lineBuffered(msg => console.warn(`[CPL] ${msg}`)),  // stderr
];
const wasi = new WASI([], [], fds);
```

The WASI reactor is initialized before the Haskell RTS:

```javascript
wasi.initialize(instance);   // calls _initialize
instance.exports.hs_init();  // init Haskell RTS
instance.exports.hs_start(); // run main (exported via foreign export)
```

### JavaScript Side (cpl-terminal.js)

The JavaScript side provides:
- **CPLTerminal class** - Manages xterm.js terminal instance
- **Keyboard handling** - Enter, Backspace, Ctrl+C, Ctrl+D, Tab
- **WASM loading** - Fetches and instantiates WASM module
- **FFI bindings** - Connects Haskell to JavaScript

### Build Configuration (CPL.cabal)

The WASM flag in CPL.cabal enables WebAssembly-specific build options:

```cabal
Flag Web
  Description: Build for browser environment with WebAssembly + JavaScript FFI
  Default: False
  Manual: True
```

When enabled:
- Sets `-DUSE_WEB_BACKEND` CPP flag
- Uses `-no-hs-main` (no standard main entry point)
- Exports `hs_init` and `hs_start` symbols
- Enables `JavaScriptFFI` extension for GHC 9.10+
- Adds `ghc-experimental` dependency for `GHC.Wasm.Prim`

### Post-link Processing

GHC's WASM backend generates a `post-link.mjs` tool that extracts JSFFI information from the compiled `.wasm` binary and produces a JavaScript glue module (`cpl.js`). This glue code implements the knot-tying pattern required for bidirectional FFI:

```bash
node "$LIBDIR/post-link.mjs" -i _site/cpl.wasm -o _site/cpl.js
```

### Build Systems

Two parallel build systems are maintained:

1. **Stack** (for native builds)
   - Uses `stack.yaml`
   - Default for development
   - Supports haskeline

2. **Cabal** (for both native and WASM builds)
   - Uses `CPL.cabal` with flags
   - WASM builds: `wasm32-wasi-cabal configure -fWeb`
   - Native builds: `cabal configure`

## Known Limitations

- **Command history** - Basic line editing only
- **Tab completion** - Not implemented
- **Multi-line paste** - May have issues with large pastes

These features may be added in future versions.

## Troubleshooting

### WASM module fails to load

Check browser console for errors. Common issues:
- Missing or incorrect MIME type for `.wasm` files
- CORS issues (must serve from web server, not `file://`)
- Browser doesn't support WebAssembly

### Terminal not responding

- Ensure JavaScript is enabled
- Check browser console for errors
- Try refreshing the page

### Build errors

If `./scripts/build-wasm.sh` fails:
- Verify GHC WASM toolchain is installed correctly
- Check that `wasm32-wasi-ghc` and `wasm32-wasi-cabal` are in `PATH`
- Try `./scripts/build-wasm.sh --clean` to clean build artifacts

## Performance

The WASM version has similar performance to the native version for most operations, with slight overhead for JavaScript-Haskell FFI calls.

### Binary Size
- ~2.9 MB (includes GHC runtime system, libraries, and CPL interpreter)

### Load Time
- First load depends on network speed and browser WASM compilation
- Subsequent loads are faster due to browser caching

## Security

The WASM version runs in the browser's sandbox and has no access to:
- Local file system (except via the browser file picker dialog triggered by `load` with no arguments)
- Network (except for loading the WASM module itself)
- Other tabs or browser state

This makes it safe to run untrusted CPL programs in the browser.

## Contributing

To improve the WASM version:

1. Modify Haskell code in `src/Main.hs` (USE_WEB_BACKEND section)
2. Update JavaScript in `web/cpl-terminal.js`
3. Build and test locally with `./scripts/build-wasm.sh && python3 -m http.server -d _site 8000`
4. Submit pull request

See main README.md for general contribution guidelines.

## References

- [GHC WebAssembly Backend User's Guide](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/wasm.html)
- [ghc-wasm-meta](https://gitlab.haskell.org/haskell-wasm/ghc-wasm-meta) - GHC WASM toolchain bootstrap
- [xterm.js Documentation](https://xtermjs.org/)
- [@bjorn3/browser_wasi_shim](https://github.com/aspect-build/aspect-cli/tree/main/aspect-build/aspect-cli) - WASI shim for browsers
- [GitHub Pages Documentation](https://docs.github.com/pages)
- Tatsuya Hagino, "A Categorical Programming Language", PhD Thesis, University of Edinburgh, 1987