A demo project exploring tagless-final DSL implementation in OCaml with Python bindings.
pyfinalo
is an experimental domain-specific language (DSL) implementation that showcases the feasibility to implement powerful eDSL usable in Python and JavaScript/TypeScript backed by implementation in OCaml in the tagless-final style. The project provides multiple interpreters including direct evaluation, AST construction, and explanatory type checking.
This project was originally intended as a demonstration of Python eDSL implementation using OCaml in the tagless-final style - hence the portmanteau "pyfinalo" (Python + Final + OCaml). JavaScript/TypeScript support was added later completely by accident, making the project name somewhat of a misnomer.
- Tagless-Final Architecture: Clean DSL implementation without intermediate abstract syntax trees
- Multiple Interpreters: Direct evaluation, AST construction, and explanatory type checking
- Python Integration: Seamless Python bindings via pyml
- JavaScript/TypeScript Integration: JavaScript bindings via js_of_ocaml
- Docker Support: Containerized development and execution environment
Try pyfinalo in your browser! We've deployed an experimental web REPL that lets you experiment with both Python and JavaScript bindings without any installation:
🌐 https://pyfinalo-webapp.pages.dev/
The web REPL features:
- Dual Runtime Support: Switch between Python (via Pyodide) and JavaScript execution
- Interactive Code Editor: Syntax highlighting and responsive design
- Real-time Execution: Run pyfinalo expressions directly in your browser
- No Installation Required: Everything runs client-side
Note: The web REPL currently provides only the type-checked direct evaluation interpreter. AST construction and explanatory interpreters are available in the native installations below.
⚠️ OCaml 5.3 Compatibility WarningAvoid OCaml 5.3 (or later) due to compatibility issues with pyml. This is caused by stdcompat issue #36 in stdcompat, a dependency of pyml. After this issue is resolved, a new version of stdcompat containing the fix needs to be released to opam before OCaml 5.3+ can be supported.
- Python: Version 3.9+ (tested with 3.9.13) - Note: as warned above, Python bindings require OCaml < 5.3 for now
- OCaml: Version 5.1 or compatible with pyml and js_of_ocaml
- OPAM: OCaml package manager
- Bun: JavaScript runtime and package manager (for JavaScript bindings)
-
Install OPAM: Follow the official installation guide
-
Setup OCaml Environment:
opam switch create 5.1 eval $(opam env --switch=5.1 --set-switch)
-
Install Dependencies:
opam install . --deps-only --with-test --with-doc
-
Build and Install:
dune build && dune install
-
Run:
# Interactive mode (with readline support) rlwrap pyfinalo_py # Or run a script cat src/bin_pyfinalo_py/test/suite1.py | pyfinalo_py - # Without installation dune exec src/bin_pyfinalo_py/bin_pyfinalo_py.exe
-
Clone and Build:
git clone https://github.com/haochenx/pyfinalo.git cd pyfinalo ./docker/build.sh
-
Run Interactive Session:
docker run -it pyfinalo # Inside container: pyfinalo_py
-
Execute Scripts:
cat your_script.py | docker run -i --rm pyfinalo pyfinalo_py -
-
Development Environment:
./docker/build-dev.sh docker run -it pyfinalo-dev
Note: The development container includes Bun for JavaScript/TypeScript development.
import pyfinalo as p
result = p.show(p.add(p.len(p.str("hello")), p.int(3)))
print(result) # Output: 8
from pyfinalo import *
result = show(add(len(str("hello")), int(3)))
print(result) # Output: 8
import pyfinalo_ast as p
ast = p.show(p.add(p.len(p.str("hello")), p.int(3)))
print(ast) # Output: len("hello")+3
import pyfinalo_explain as p
# Well-typed expression
result = p.show(p.add(p.len(p.str("hello")), p.int(3)))
print(result) # Output: len("hello")+3 evaluates to integer 8
# Ill-typed expression
error = p.show(p.add(p.len(p.int(5)), p.int(3)))
print(error) # Output: len(5)+3 contains ill-typed subterm len(5)
import pyfinalo from 'pyfinalo_js';
// or alias for conciseness
import p from 'pyfinalo_js';
// Direct evaluation
const result = pyfinalo.show(p.add(p.int(5), p.int(3)));
console.log(result); // Output: "8 : int"
// String operations
const strResult = pyfinalo.show(p.len(p.str("hello")));
console.log(strResult); // Output: "5 : int"
// Complex expressions
const complex = pyfinalo.show(
p.add(
p.mul(p.int(2), p.int(3)),
p.int(4)
)
);
console.log(complex); // Output: "10 : int"
pyfinalo
: Direct value interpreter (DirectValInterp
) - evaluates expressions immediatelypyfinalo_ast
: AST interpreter (UntypedAstInterp
) - generates abstract syntax treespyfinalo_explain
: Explanatory interpreter (ExplainInterpUntyped
) - provides detailed type checking
pyfinalo_js
: Type checking interpreter available as an npm package (note: not published to npmjs.com)
To use the JavaScript module in your project after building:
# After building in src/lib_pyfinalo_js
cd your-project
bun add file:../path/to/pyfinalo/src/lib_pyfinalo_js
# or
npm link ../path/to/pyfinalo/src/lib_pyfinalo_js
# Install dependencies
cd src/lib_pyfinalo_js
bun install
# Build the JavaScript module
bun run build
# Run JavaScript tests
bun run test
# OCaml tests (includes Python tests - cram tests under lib_pyfinalo_js/test)
dune runtest
# JavaScript tests
cd src/lib_pyfinalo_js && bun test
pyfinalo/
├── src/ # Source code
│ ├── lib_pyfinalo/ # Core DSL implementation
│ │ ├── lang.ml # Language definitions
│ │ ├── interp.ml # Interpreter implementations
│ │ └── test/ # Unit tests
│ ├── bin_pyfinalo_py/ # Python bindings
│ │ ├── bin_pyfinalo_py.ml # Main executable
│ │ └── test/ # Integration tests
│ └── lib_pyfinalo_js/ # JavaScript/TypeScript bindings
│ ├── lib_pyfinalo_js.ml # js_of_ocaml entry point
│ ├── wrapper.js # JavaScript wrapper
│ ├── package.json # NPM package configuration
│ ├── tsup.config.ts # Build configuration
│ └── test/ # JavaScript tests
├── webapp/ # Web REPL application
│ ├── app/ # Main Vite app
│ ├── repl-js/ # JavaScript REPL implementation
│ └── repl-python/ # Python (Pyodide) REPL implementation
├── docker/ # Docker configurations
│ ├── Dockerfile # Production container
│ ├── Dockerfile.dev # Development container
│ ├── build.sh # Build script
│ └── build-dev.sh # Development build script
├── scripts/ # Utility scripts
├── dune-project # Dune project configuration
├── pyfinalo.opam # OPAM package definition
├── Makefile # Build automation
└── README.md # This file