This repository is organized as a monorepo containing:
| Package | Description | Language |
|---|---|---|
| interpreter | MLisp interpreter implementation | OCaml |
| vscode-ext | VSCode language extension (OCaml + js_of_ocaml) | OCaml |
| shared | Shared language resources | JSON/TextMate |
mlisp/
├── packages/
│ ├── interpreter/ # OCaml interpreter (core)
│ │ ├── bin/ # Entry point (mlisp.ml)
│ │ ├── lib/ # Core: ast, lexer, eval, object, macro, primitives
│ │ ├── stdlib/ # Standard library (.mlisp files)
│ │ └── test/ # Test suite
│ │
│ ├── vscode-ext/ # VSCode extension (OCaml → JavaScript)
│ │ ├── src/ # Extension code (vscode_mlisp.ml)
│ │ ├── src-bindings/ # VSCode API bindings (gen_js_api)
│ │ ├── syntaxes/ # TextMate grammar (from shared/)
│ │ └── package.json # Extension manifest
│ │
│ └── shared/ # Shared resources
│ └── syntax/ # mlisp.tmLanguage.json, language-config.json
│
├── package.json # Root orchestration scripts
└── docs/ # Documentation
┌─────────────────────────────────────────────────────────────────┐
│ Dependency Graph │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ shared/ │ ──────────────┐ │
│ │ (syntax files) │ │ │
│ └─────────────────┘ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ vscode-ext/ │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ src/vscode_mlisp.ml │ │ │
│ │ └────────────────────────────────┘ │ │
│ │ │ uses VSCode API bindings │ │ │ │
│ │ └────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ src-bindings/vscode/ │ │ │
│ │ │ (gen_js_api → js_of_ocaml) │ │ │
│ │ └────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ dune-project │ │ │
│ │ │ depends: mlisp (optional) │──┼──► can embed REPL │ │
│ │ └────────────────────────────────┘ │ │
│ └─────────────────────────────────────┘ │ │
│ │ │
│ ┌─────────────────┐ │ │
│ │ interpreter/ │ │ │
│ │ └─────────────────┘ │ │
│ │ (standalone) │ │ │
│ └───────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Note: See docs/monorepo-migration.md for migration details.
# Build the interpreter
npm run build:interpreter
# Build VSCode extension
npm run build:vscode && npm run bundle:vscode
# Start the REPL
cd packages/interpreter && dune exec mlispThe packages/interpreter/ package is the core MLisp interpreter and can be developed independently:
cd packages/interpreter
# Build
dune build
# Run tests
./run_tests.sh
# Start REPL
dune exec mlisp
# Run a specific file
dune exec mlisp -- test/example.mlispKey files:
lib/eval/eval.ml- Expression evaluatorlib/lexer/lexer.ml- Lexical analyzerlib/object/object.ml- MLisp object typeslib/primitives/ocaml.ml- OCaml standard library bindings
The packages/vscode-ext/ package is a VSCode extension written entirely in OCaml, compiled to JavaScript via js_of_ocaml:
cd packages/vscode-ext
# Install OCaml dependencies
opam install . --deps-only
# Install npm dependencies (esbuild, vsce)
npm install
# Build OCaml → JavaScript bytecode
dune build
# Bundle with esbuild (external:vscode)
npm run bundle
# Package as .vsix
npm run package
# Install locally for testing
npm run install:extUnique architecture:
OCaml Source (vscode_mlisp.ml)
↓ js_of_ocaml (compile OCaml to JS)
_build/default/src/vscode_mlisp.bc.js
↓ esbuild (bundle, minify, external:vscode)
dist/vscode_mlisp.bc.js
↓ VSCode Extension Host
Loaded as VSCode extension
Key files:
src/vscode_mlisp.ml- Extension entry point (exportsactivate/deactivate)src-bindings/vscode/vscode.ml- VSCode API bindings (using gen_js_api)syntaxes/mlisp.tmLanguage.json- Syntax highlighting grammar
The packages/shared/syntax/ package contains language definition files used by editor extensions:
mlisp.tmLanguage.json- TextMate grammar for syntax highlightinglanguage-configuration.json- Comment style, bracket pairs, indentation
When updating grammar files, the VSCode extension will automatically pick up the changes (files are copied during build).
Each package has independent versioning:
| Package | Version File | Published To |
|---|---|---|
| interpreter | packages/interpreter/dune-project |
opam |
| vscode-ext | packages/vscode-ext/package.json |
VSCode Marketplace |
| shared | (none, resource only) | N/A |
# Just the interpreter
cd packages/interpreter && dune build && dune runtest
# Just the VSCode extension
cd packages/vscode-ext && dune build && npm run bundle
# Both (from root)
npm run build# Test interpreter
cd packages/interpreter && ./run_tests.sh
# Test VSCode extension (manual - press F5 in VSCode)
code --extensionDevelopmentPath=$PWD/packages/vscode-ext- Introduction
- Installation
- Language Overview
- Data Types
- Expressions and Control Flow
- Functions and Closures
- Variable Bindings
- Modules
- Macros
- Standard Library
- OCaml Standard Library Bindings
- VSCode Extension
- Examples
- License
MLisp is a Lisp dialect implemented in OCaml, featuring a clean syntax, lexical scoping, closures, modules, and a macro system. It provides a REPL (Read-Eval-Print Loop) for interactive development and supports both interactive and file-based execution.
- OCaml 5.0 or later
- Dune build system
- Core library and dependencies (see
mlisp.opam)
# Clone the repository
git clone <repository-url>
cd mlisp
# Install dependencies
opam install . --deps-only
# Build the project
npm run build
# or
cd packages/interpreter && dune build
# Install globally (optional)
npm run install
# or
cd packages/interpreter && dune install# Start the REPL
cd packages/interpreter && dune exec mlisp
# Run a MLisp file
cd packages/interpreter && dune exec mlisp -- <file.mlisp>MLisp uses S-expression syntax with prefix notation. All expressions are evaluated in a functional style with lexical scoping.
- Comments: Lines starting with
;;are comments - S-expressions:
(function arg1 arg2 ...) - Quoting: Use
`for quoting expressions:`fooor(quote foo)
MLisp provides clear, actionable error messages to help diagnose issues:
| Error | Code | Description |
|---|---|---|
| Argument Count | E207 | Wrong number of arguments passed to a function |
| Argument Type | E208 | Argument has wrong type (e.g., string instead of int) |
| Value Error | E209 | Invalid value (e.g., negative index, out of bounds) |
;; Argument count error
(String.length)
;; Error: Argument count error: 'String.length'
;; Expected 1 argument(s), but got 0.
;; Type error
(String.length 123)
;; Error: Argument type error: 'String.length'
;; Parameter 'string' expects type string.
;; Value error
(String.sub "hello" 10 3)
;; Error: Value error: 'String.sub'
;; substring out of bounds (string length: 5, requested: 10 + 3)MLisp supports integers and floating-point numbers:
42 ;; Integer
-17 ;; Negative integer
3.14 ;; Float#t ;; True
#f ;; False"hello" ;; String literal
"world" ;; Another string
"" ;; Empty string
(@ "Hello" " " "World") ;; String concatenation: "Hello World"Symbols are identifiers used for variable names and function names:
foo ;; Symbol (when evaluated)
`foo ;; Quoted symbolLists are constructed using cons pairs, and nil represents the empty list:
nil ;; Empty list
(cons 1 (cons 2 nil)) ;; List: (1 2)
(list 1 2 3) ;; List constructor: (1 2 3)
(car '(1 2 3)) ;; 1 (first element)
(cdr '(1 2 3)) ;; (2 3) (rest of list)
(atom? x) ;; #t if x is an atom, #f if it's a pairRecords provide structured data:
(record 'point (list (list 'x 10) (list 'y 20)))
(record-get point-record 'x) ;; Access fieldAll arithmetic operators support variadic arguments:
(+ 1 2) ;; 3
(+ 10 20 30) ;; 60
(- 10 5) ;; 5
(- 100 30 20) ;; 50
(* 3 4) ;; 12
(* 2 3 4) ;; 24
(/ 10 2) ;; 5
(/ 100 10 2) ;; 5
(% 10 3) ;; 1 (modulo)(== 5 5) ;; #t (equality)
(!= 5 6) ;; #t (inequality)
(< 5 10) ;; #t (less than)
(<= 5 10) ;; #t (less than or equal)
(> 10 5) ;; #t (greater than)
(>= 10 5) ;; #t (greater than or equal)(if #t 1 2) ;; Returns 1
(if #f 1 2) ;; Returns 2
(if (> 5 3) "yes" "no") ;; Returns "yes"(cond ((< 5 3) 1)
((> 5 3) 2)
((== 5 3) 3)) ;; Returns 2(and #t #t) ;; #t
(and #t #f) ;; #f
(or #t #f) ;; #t
(or #f #f) ;; #fThe begin form sequences multiple expressions and returns the value of the last one:
(begin
(print "First")
(print "Second")
42) ;; Returns 42, prints "First" and "Second"Use quote or ' to prevent evaluation:
(quote foo) ;; Symbol foo (unevaluated)
'foo ;; Same as (quote foo)
(quote (1 2 3)) ;; List (1 2 3) (unevaluated)Quasiquote (backtick) allows constructing S-expressions with selective evaluation:
`expr- Quasiquote: most content is literal (like quote),expr- Unquote: evaluate and insert the value,@expr- Unquote-splicing: evaluate and splice a list into the surrounding list
;; Basic quasiquote behaves like quote
`(1 2 3) ;; (1 2 3)
;; Unquote - insert evaluated value
(define x 42)
`(1 ,x 3) ;; (1 42 3)
;; Multiple unquotes
(define y 10)
`(,x ,y) ;; (42 10)
;; Unquote-splicing - splice a list into the result
(define nums '(2 3 4))
`(1 ,@nums 5) ;; (1 2 3 4 5)
;; Nested quasiquotes (comma-comma evaluates at level 2)
`` `(1 ,,x) ;; `(1 ,42)Quasiquote is especially useful for writing macros that generate code.
(lambda (x y) (+ x y)) ;; Anonymous function
(define add (lambda (x y) (+ x y)))
(add 5 3) ;; 8The defun form provides a convenient way to define named functions:
(defun square (n) (* n n))
(square 5) ;; 25(defun factorial (n)
(if (== n 0)
1
(* n (factorial (- n 1)))))
(factorial 5) ;; 120MLisp supports lexical scoping and closures:
(define make-adder (lambda (n)
(lambda (x) (+ x n))))
(define add5 (make-adder 5))
(add5 10) ;; 15(define apply-twice (lambda (f x) (f (f x))))
(defun inc (n) (+ n 1))
(apply-twice inc 5) ;; 7The apply function applies a function to a list of arguments:
(define nums '(1 2 3 4 5))
(apply + nums) ;; 15
(define args '(6 7))
(apply * args) ;; 42Global variable definition:
(define x 42)
(define y (+ 5 3))
(define greeting "Hello")Parallel bindings (evaluated simultaneously):
(let ((x 5)
(y 10))
(+ x y)) ;; 15Sequential bindings (evaluated in order):
(let* ((x 5)
(y (* x 2)))
y) ;; 10Recursive bindings for mutually recursive functions:
(letrec ((is-even
(lambda (n)
(if (== n 0)
#t
(is-odd (- n 1)))))
(is-odd
(lambda (n)
(if (== n 0)
#f
(is-even (- n 1))))))
(is-even 10)) ;; #tMLisp provides a module system for code organization and encapsulation.
(module math-constants (export pi e)
(define pi 3.14159)
(define e 2.71828));; Import all exports
(import math-constants)
pi ;; 3.14159
;; Selective import
(import arithmetic add subtract)
(add 10 5) ;; 15
;; Import with alias
(import string-utils :as str)(module arithmetic (export add subtract multiply)
(define add (lambda (x y) (+ x y)))
(define subtract (lambda (x y) (- x y)))
(define multiply (lambda (x y) (* x y))))MLisp supports macros for metaprogramming and code generation. Macros receive their arguments as unevaluated S-expressions and return S-expressions that are then evaluated.
(defmacro double (x)
`(+ ,x ,x))
(define result (double 5)) ;; Expands to (+ 5 5) = 10Using quasiquote makes macros more readable:
;; When macro - execute body only if condition is true
(defmacro when (condition body)
`(if ,condition ,body nil))
(when #t (print "Hello")) ;; Prints "Hello"
(when #f (print "Hello")) ;; Does nothing
;; Unless macro - opposite of when
(defmacro unless (condition body)
`(if (not ,condition) ,body nil))
(unless #f (print "Hi")) ;; Prints "Hi"Use ,@ to splice a list into the generated code:
;; Macro that creates a function comparing to multiple values
(defmacro member-of (x)
`(lambda (y) (or ,@(map (lambda (v) `(== ,y ,v)) x))))
(define is-digit (member-of '(0 1 2 3 4 5 6 7 8 9)))
(is-digit 5) ;; #t
(is-digit 42) ;; #fUse gensym to generate unique symbols and avoid variable capture:
;; Generate unique symbols
(gensym) ;; => g1
(gensym "temp") ;; => temp_1
(gensym "temp") ;; => temp_2
;; Hygienic swap macro using gensym
(defmacro swap (a b)
(let ((temp (gensym "temp")))
`(let ((,temp ,a))
(setq ,a ,b)
(setq ,b ,temp))))
(define x 10)
(define y 20)
(swap x y)
x ;; 20
y ;; 10Macros can accept variable numbers of arguments using the &rest parameter. The &rest parameter collects any remaining arguments into a list:
;; Macro that collects all arguments
(defmacro list-all (&rest args)
`(quote ,args))
(list-all 1 2 3 4) ;; (1 2 3 4)
;; Fixed parameters followed by rest parameter
(defmacro with-fixed (first second &rest rest)
`(quote (,first ,second ,@rest)))
(with-fixed 1 2 3 4) ;; (1 2 3 4)
(with-fixed 1 2) ;; (1 2) - rest is empty listThe &rest parameter must be the last parameter in the parameter list. Any arguments beyond the fixed parameters are packed into a list and bound to the rest parameter name.
This is especially useful for creating generic wrapper macros:
;; Generic OCaml function call macro
(defmacro ocall (mod method &rest args)
`((record-get ,mod (quote ,method)) ,@args))
(ocall String length "hello") ;; 5
(ocall String concat "hello" " world") ;; "hello world"
(ocall List length '(1 2 3)) ;; 3For completeness, macros can also be written using list and quote:
;; Same when macro without quasiquote
(defmacro when-alt (condition body)
(list 'if condition body (quote nil)))However, the quasiquote style is generally preferred for readability.
MLisp provides tools to inspect macro expansion, which are helpful for understanding how macros transform code.
Expand a macro call by a single level, without recursively expanding nested macros:
(defmacro square (x)
`(* ,x ,x))
(defmacro double-square (x)
`(double (square ,x)))
;; Single-step expansion - only the outer macro is expanded
(macroexpand-1 '(double-square 5))
;; => (double (square 5))Fully expand all macros recursively until no macro calls remain:
;; Full expansion - all macros are expanded
(macroexpand '(double-square 5))
;; => (* (* 5 5) (* 5 5))These tools are invaluable for debugging complex macros that expand to other macros.
MLisp provides helper macros that simplify writing hygienic macros that avoid variable capture.
Generate a unique symbol for a single name:
(defmacro make-multiplier (value)
(with-gensym temp
`(lambda (x)
(* x ,value))))Generate unique symbols for multiple names:
(defmacro make-composed (f g)
(with-gensyms2 x y
`(lambda (,x)
(,f (,g ,x)))))For convenience, variants are provided for different arities:
with-gensym- 1 symbolwith-gensyms2- 2 symbolswith-gensyms3- 3 symbols
With &rest support, you can also create your own variadic helper macros for more symbols:
Helper macros ensure unique temporary variables in macro expansion:
(defmacro safe-square (x)
(with-gensym result
`(let ((,result (* ,x ,x)))
,result)))
;; Expands to code with a unique variable, avoiding captureMLisp includes a comprehensive standard library loaded automatically.
(null? '()) ;; #t
(length '(1 2 3)) ;; 3
(append. '(1 2) '(3 4)) ;; (1 2 3 4)
(take 2 '(1 2 3 4)) ;; (1 2)
(drop 2 '(1 2 3 4)) ;; (3 4)
(mergesort '(3 1 4 2)) ;; (1 2 3 4)
(zip. '(1 2) '(a b)) ;; ((1 a) (2 b))
(map (lambda (x) (* x 2)) '(1 2 3)) ;; (2 4 6)
(pair? '(1 2)) ;; #t (checks if x is a pair/cons cell)(null. x) ;; Check if list is empty
(and. x y) ;; Logical AND
(not. x) ;; Logical NOT
(caar ls) ;; (car (car ls))
(cadr ls) ;; (car (cdr ls))(print "Hello") ;; Print to stdout
(println "Hello") ;; Print with newline
(getline) ;; Read a line from stdin
(getchar) ;; Read a character (returns integer)(int->char 65) ;; Convert integer to character symbol
(symbol-concat 'a 'b) ;; Concatenate two symbols(assert (= result 10)) ;; Assert conditionMLisp provides bindings to selected OCaml standard library functions through the String and List modules. These modules are exposed as Record objects accessible via record-get.
;; Get a function from a module
((record-get String (quote length)) "hello") ;; 5
;; Define a helper for repeated use
(define string-length (record-get String (quote length)))
(string-length "world") ;; 5The ocall macro handles any number of arguments:
;; Single argument
(ocall String length "hello") ;; 5
;; Two arguments
(ocall String concat "hello" " world") ;; "hello world"
;; Three arguments
(ocall String sub "hello" 1 3) ;; "ell"
;; More arguments as needed
(ocall YourModule your-method arg1 arg2 arg3 arg4 ...)The String module provides string manipulation functions:
;; String.length - get string length
(ocall String length "hello") ;; 5
;; String.concat - concatenate two strings
(ocall String concat "hello" "world") ;; "helloworld"
;; String.split - split by separator (single char)
(ocall String split "a,b,c" ",") ;; ("a" "b" "c")
;; String.upper - convert to uppercase
(ocall String upper "hello") ;; "HELLO"
;; String.lower - convert to lowercase
(ocall String lower "HELLO") ;; "hello"
;; String.sub - extract substring
(ocall String sub "hello" 1 3) ;; "ell" (pos=1, len=3)
;; String.contains? - check if substring exists
(ocall String contains? "hello" "ell") ;; #t
(ocall String contains? "hello" "xyz") ;; #f
;; String.trim - strip leading/trailing whitespace
(ocall String trim " hello ") ;; "hello"The List module provides list manipulation functions:
;; List.length - get list length
(ocall List length '(1 2 3)) ;; 3
;; List.append - concatenate two lists
(ocall List append '(1 2) '(3 4)) ;; (1 2 3 4)
;; List.rev - reverse a list
(ocall List rev '(1 2 3)) ;; (3 2 1)
;; List.nth - get element at index
(ocall List nth '(10 20 30) 1) ;; 20
;; List.mem - check membership
(ocall List mem 2 '(1 2 3)) ;; #t
(ocall List mem 5 '(1 2 3)) ;; #f
;; List.flatten - flatten one level of nesting
(ocall List flatten '((1 2) (3 4))) ;; (1 2 3 4)
;; List.concat - concatenate a list of lists
(ocall List concat '((1 2) (3 4))) ;; (1 2 3 4)
;; List.sort - sort numbers ascending
(ocall List sort '(3 1 4 1 5)) ;; (1 1 3 4 5)OCaml bindings are implemented in lib/primitives/ocaml.ml using the following architecture:
(* 1. Validation helper functions from Mlisp_primitives__Validate *)
let check_arg_count = Mlisp_primitives__Validate.check_arg_count
let require_string = Mlisp_primitives__Validate.require_string
let require_int = Mlisp_primitives__Validate.require_int
let require_list = Mlisp_primitives__Validate.require_list
(* 2. Each binding function validates arguments and returns MLisp objects *)
let string_length args =
check_arg_count "String.length" args 1;
let s = require_string "String.length" "string" (List.hd_exn args) in
Object.Fixnum (String.length s)
(* 3. Modules are created as Record objects containing function bindings *)
let string_module =
make_module "String"
[ "length", string_length
; "concat", string_concat
; ...
]
(* 4. All modules are exported via the basis list *)
let basis = [ "String", string_module; "List", list_module ]This section explains how to add new OCaml standard library bindings to MLisp.
Add your function to lib/primitives/ocaml.ml:
(** String.replace - replaces all occurrences of pattern.
(String.replace "hello world" "world" "there") -> "hello there" *)
let string_replace args =
check_arg_count "String.replace" args 3;
let s = require_string "String.replace" "string" (List.nth_exn args 0) in
let pattern = require_string "String.replace" "pattern" (List.nth_exn args 1) in
let replacement = require_string "String.replace" "replacement" (List.nth_exn args 2) in
Object.String (String.replace_all ~substr:pattern ~with_:replacement s)
;;Add the function name to the module's binding list:
let string_module =
make_module "String"
[ "length", string_length
; "concat", string_concat
; ...
; "replace", string_replace (* New function *)
]
;;npm run build:interpreter
# or
cd packages/interpreter && dune build(ocall String.replace "hello world" "world" "there")
;; => "hello there"(** <Function description>
(<Module>.<name> <example-args>) -> <return-value> *)
let function_name args =
(* 1. Validate argument count *)
check_arg_count "<Module>.<name>" args <expected-count>;
(* 2. Extract and validate each argument *)
let arg1 = require_<type> "<Module>.<name>" "<param-name>" (List.nth_exn args 0) in
let arg2 = require_<type> "<Module>.<name>" "<param-name>" (List.nth_exn args 1) in
(* 3. Perform OCaml operations *)
let result = (* OCaml code *) in
(* 4. Return as MLisp object *)
Object.<type> result
;;| Function | Purpose |
|---|---|
check_arg_count name args n |
Validate exact argument count |
require_string name param value |
Extract string, raise error if not string |
require_int name param value |
Extract integer, raise error if not int |
require_list name param value |
Extract list, raise error if not list |
check_int_range name param value ~min_value ~max_value |
Validate integer is within range |
| MLisp Type | OCaml Constructor |
|---|---|
| String | Object.String "value" |
| Integer | Object.Fixnum (Int.of_int 42) |
| Float | Object.Float 3.14 |
| Boolean | Object.Boolean true |
| List | Object.list_to_pair [ocaml_list] |
| Pair | Object.Pair (car, cdr) |
To add a completely new module (e.g., Array):
(** Array module bindings *)
(** Array.of-list - creates array from list *)
let array_of_list args =
check_arg_count "Array.of-list" args 1;
let lst = require_list "Array.of-list" "list" (List.hd_exn args) in
let arr = Array.of_list lst in
Object.Array arr
(** Array.length - returns array length *)
let array_length args =
check_arg_count "Array.length" args 1;
(match List.hd_exn args with
| Object.Array arr -> Object.Fixnum (Array.length arr)
| _ -> raise (Errors.Runtime_error_exn
(Errors.Argument_type_error ("Array.length", "array", "array"))))
(** Create the module *)
let array_module =
make_module "Array"
[ "of-list", array_of_list
; "length", array_length
]
(** Add to basis *)
let basis = [
"String", string_module;
"List", list_module;
"Array", array_module (* New *)
]After rebuilding, use it in MLisp:
(ocall Array.of-list (quote (1 2 3)))
;; => #[1 2 3]
(ocall Array.length #[1 2 3])
;; => 3MLisp includes a VSCode language extension written entirely in OCaml using js_of_ocaml. This extension provides syntax highlighting, REPL integration, and code evaluation support for .mlisp files.
- Open VSCode
- Go to Extensions (Ctrl+Shift+X)
- Search for "MLisp"
- Click Install
cd packages/vscode-ext
# Install dependencies
opam install . --deps-only
npm install
# Build and package
npm run build
npm run package
# Install locally
code --install-extension mlisp-vscode.vsix- Full syntax highlighting for MLisp S-expressions
- Keywords (
if,cond,define,lambda, etc.) - Booleans (
#t,#f), numbers, and strings - Comments (
;;) - Quasiquoting (
`,,, `,@``)
- Open the Command Palette (Ctrl+Shift+P)
- Type "MLisp: Start REPL"
- Results appear in the "MLisp REPL" output channel
- Select code in the editor
- Press
Ctrl+Enterto evaluate - Results display in the output panel
- Bracket matching for parentheses, brackets, and braces
- Auto-closing pairs
- Comment toggling
- Code folding with region markers (
;(...;))
| Keybinding | Command |
|---|---|
Ctrl+Enter |
MLisp: Evaluate Selection |
The extension is written entirely in OCaml and compiled to JavaScript:
OCaml Source (vscode_mlisp.ml)
↓ js_of_ocaml
JavaScript Bytecode
↓ esbuild
Bundled Extension
This architecture allows:
- Type-safe OCaml development
- Potential to embed the MLisp interpreter directly
- Code sharing with the core interpreter
For contributing to the extension, see packages/vscode-ext/README.md and packages/vscode-ext/DEVELOPMENT.md.
cd packages/vscode-ext
# Watch mode for development
npm run dev
# Run linter
npm run check
# Fix formatting
npm run fix(defun factorial (n)
(if (== n 0)
1
(* n (factorial (- n 1)))))
(factorial 5) ;; 120(defun fib (n)
(if (< n 2)
n
(+ (fib (- n 1)) (fib (- n 2)))))
(fib 8) ;; 21(define make-counter (lambda ()
(let ((count 0))
(lambda ()
(define count (+ count 1))
count))))
(define counter1 (make-counter))
(counter1) ;; 1
(counter1) ;; 2
(counter1) ;; 3(module counter-mod (export increment get-count reset)
(define count 0)
(define increment (lambda ()
(define count (+ count 1))
count))
(define get-count (lambda () count))
(define reset (lambda ()
(define count 0)
count)))
(import counter-mod)
(increment) ;; 1
(increment) ;; 2
(get-count) ;; 2
(reset) ;; 0This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

