A Lisp-family programming language for the web with angle-bracket syntax
Ensemble is a web programming language that combines JavaScript, HTML, and CSS in a Lisp-like language with HTML-style syntax using angle brackets. The language supports both angle bracket syntax (preferred) and traditional parentheses for developers familiar with Lisp.
- HTML-style syntax: Uses angle brackets
<>as the primary syntax (though parentheses are also supported) - Functional programming: Based on Lisp principles with immutable data structures
- Web-focused: Built-in support for HTML, CSS, and JavaScript interop
- Tail call optimization: Supports recursive functions without stack overflow
- Macro system: Powerful macro capabilities with quasiquote/unquote
- Error handling: Try/catch exception handling
- Rich data types: Lists, vectors, maps, atoms, keywords, and more
<var binet
<function <n>
<const [
sqrt5 <Math.sqrt 5>,
phi <divide <add 1 sqrt5> 2>,
psi <divide <subtract 1 sqrt5> 2>
]
<Math.round
<divide
<subtract
<power phi n>
<power psi n>
>
sqrt5
>
>
>
>
>
<console.log <binet 7>>
<var name value>- Define a global variable<const [bindings] body>- Local bindings (immutable)
<function [params] body>- Define anonymous function<fn* [params] body>- Alternative function syntax
<if condition then else>- Conditional expression<do expr1 expr2 ...>- Sequential evaluation<cond test1 result1 test2 result2 ...>- Multi-way conditional
- Lists:
<list 1 2 3>or(1 2 3) - Vectors:
[1 2 3] - Maps:
{"key" value, :keyword value} - Keywords:
:keywordorkeyword:
<add a b>- Addition<subtract a b>- Subtraction<multiply a b>- Multiplication<divide a b>- Division<eq a b>- Equality comparison<lt a b>,<gt a b>,<lte a b>,<gte a b>- Comparisons
<quote expr>- Prevent evaluation<quasiquote expr>- Template with selective evaluation<unquote expr>- Evaluate within quasiquote<splice-unquote expr>- Splice list into quasiquote<defmacro! name params body>- Define macro
<try expr <catch var handler>>- Exception handling<throw value>- Throw exception
<slurp "filename">- Read file contents<spit "filename" content>- Write to file<load-file "filename">- Load and evaluate file
<first coll>,<rest coll>,<nth coll index><count coll>,<empty? coll><map func coll>,<filter func coll><cons item coll>,<concat coll1 coll2>
<hash-map key1 val1 key2 val2>- Create map<assoc map key val>- Add/update key<dissoc map key>- Remove key<get map key>,<contains? map key><keys map>,<vals map>
Ensemble supports both Lisp-style and C-style comments:
;; Lisp-style comment
// C-style comment (preferred)
./bin/ensemble./bin/ensemble path/to/file.ensmbluser> <var greeting "Hello, World!">
"Hello, World!"
user> <console.log greeting>
Hello, World!
nil
user> <var square <function <x> <multiply x x>>>
#<function>
user> <square 5>
25
root
├── bin
├── src
│ ├── benchmark
│ │ ├── results
│ │ └── scripts
│ ├── ensemble
│ │ ├── build
│ │ └── tests
│ ├── examples
│ └── extension
├── var
│ ├── data
│ ├── generated
│ └── images
└── Makefile
You'll need NodeJS, QuickJS, and Make.
- Download and Install NodeJS.
- Run
npm install -g typescript esbuildfrom your terminal of choice. - Follow the QuickJS installation instructions below.
- Download the latest QuickJS binary for your system.
- Gently place it into the top level bin folder and name it qjs.
The following commands will build the Ensemble interpreter as a standalone JavaScript file (using ESBuild and QuickJS).
makeTo start the unit and integration tests, run the test script from the root of the project.
make test-unit-funNote About Parallel Testing It is not possible to run the tests in parallel at the moment because when this code was ported from Deno to QuickJS there were tests with steps. These tests were not modified during conversion and remain order-dependent.
The test suite includes comprehensive coverage of:
- Core functions: Arithmetic, comparison, type checking
- Data structures: Lists, vectors, maps, atoms
- Control flow: Conditionals, loops, function calls
- Macros: Quasiquote, unquote, splice-unquote, custom macros
- Error handling: Try/catch, throw, error propagation
- File I/O: Reading, writing, loading files
- Tail call optimization: Recursive function performance
- JavaScript interop: Array operations, DOM manipulation
- Download and Install Python
- Run the following from the root of the project.
make test-e2e- Install hyperfine (e.g.
sudo apt install hyperfine) - Run
make benchmark - The results will be in
src/benchmark/results
- Runtime: Built on QuickJS for fast JavaScript execution
- Parser: Custom reader that supports both angle brackets and parentheses
- Evaluator: Tree-walking interpreter with tail call optimization
- Environment: Lexical scoping with closure support
- Error System: Structured error handling with source location tracking
- No Reader Macros: Uses explicit forms like
(quote x)instead of'x - Descriptive Function Names:
add,multiplyinstead of+,* - Immutable Data: All data structures are immutable by default
- Web Integration: Built-in HTML/CSS generation and DOM manipulation
- Tail Call Optimization: Recursive functions don't cause stack overflow
- Lazy Evaluation: Some operations are evaluated lazily for performance
- Efficient Data Structures: Optimized implementations of core types
- TypeScript Do's and Don'ts: Don't use
any - Making TypeScript Truly "Strongly Typed"
- The power of 10: rules for developing safety-critical code
- JPL Institutional Coding Standard for the C Programming Language
- This project is based on Make-a-Lisp (MAL)
- With data from the MDN content repository
- And learnings from Crafting Interpreters
See LICENSE
