Skip to content

Repository files navigation

Ensemble

A Lisp-family programming language for the web with angle-bracket syntax

A line drawing of a jazz ensemble

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.

Language Features

  • 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

Syntax Examples

Basic Fibonacci Function (Angle Bracket Syntax)

<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>>

Core Language Reference

Variable Declaration

  • <var name value> - Define a global variable
  • <const [bindings] body> - Local bindings (immutable)

Functions

  • <function [params] body> - Define anonymous function
  • <fn* [params] body> - Alternative function syntax

Control Flow

  • <if condition then else> - Conditional expression
  • <do expr1 expr2 ...> - Sequential evaluation
  • <cond test1 result1 test2 result2 ...> - Multi-way conditional

Data Structures

  • Lists: <list 1 2 3> or (1 2 3)
  • Vectors: [1 2 3]
  • Maps: {"key" value, :keyword value}
  • Keywords: :keyword or keyword:

Arithmetic (Explicit Function Names)

  • <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

Quoting and Macros

  • <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

Error Handling

  • <try expr <catch var handler>> - Exception handling
  • <throw value> - Throw exception

File I/O

  • <slurp "filename"> - Read file contents
  • <spit "filename" content> - Write to file
  • <load-file "filename"> - Load and evaluate file

Collection Operations

  • <first coll>, <rest coll>, <nth coll index>
  • <count coll>, <empty? coll>
  • <map func coll>, <filter func coll>
  • <cons item coll>, <concat coll1 coll2>

Map Operations

  • <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>

Comments

Ensemble supports both Lisp-style and C-style comments:

;; Lisp-style comment
// C-style comment (preferred)

Usage

Interactive REPL

./bin/ensemble

Run a File

./bin/ensemble path/to/file.ensmbl

Example Session

user> <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

Development

Directory Structure

root
├── bin
├── src
│   ├── benchmark
│   │   ├── results
│   │   └── scripts
│   ├── ensemble
│   │   ├── build
│   │   └── tests
│   ├── examples
│   └── extension
├── var
│   ├── data
│   ├── generated
│   └── images
└── Makefile

Prerequisites

You'll need NodeJS, QuickJS, and Make.

Install NodeJS

  1. Download and Install NodeJS.
  2. Run npm install -g typescript esbuild from your terminal of choice.
  3. Follow the QuickJS installation instructions below.

Install or Update QuickJS

  1. Download the latest QuickJS binary for your system.
  2. Gently place it into the top level bin folder and name it qjs.

Building Ensemble

The following commands will build the Ensemble interpreter as a standalone JavaScript file (using ESBuild and QuickJS).

make

Running Unit and Integration Tests

To start the unit and integration tests, run the test script from the root of the project.

make test-unit-fun

Note 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.

Test Coverage

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

Running End-to-End Tests

  1. Download and Install Python
  2. Run the following from the root of the project.
make test-e2e

Running benchmarks

  1. Install hyperfine (e.g. sudo apt install hyperfine)
  2. Run make benchmark
  3. The results will be in src/benchmark/results

Implementation Details

Architecture

  • 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

Key Design Decisions

  • No Reader Macros: Uses explicit forms like (quote x) instead of 'x
  • Descriptive Function Names: add, multiply instead of +, *
  • Immutable Data: All data structures are immutable by default
  • Web Integration: Built-in HTML/CSS generation and DOM manipulation

Performance Features

  • 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

Standards

Credits

License

See LICENSE

About

Ensemble: Novel Synthesis of Eich, McCarthy, Berners-Lee, Lie, etc.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages