grassleaff/vlisp
1
0
Fork
You've already forked vlisp
0
vlisp is a minimal Lisp dialect designed to be simple, fast and embeddable
  • C 98.7%
  • Makefile 1.3%
2026年05月12日 10:12:55 -03:00
assets Initial commit fr 2026年04月14日 14:22:12 -03:00
examples ADD [v0.3.0]: Refactor memory management and enhance error handling + changing file extension to <file>.vlsp -> <file>.vl 2026年05月04日 10:13:00 -03:00
src ADD [v0.3.2]: New performance updates with bytecode + VM for vlisp 2026年05月12日 10:12:55 -03:00
.gitignore ADD [v0.3.2]: New performance updates with bytecode + VM for vlisp 2026年05月12日 10:12:55 -03:00
LICENSE Initial commit 2026年04月14日 19:19:48 +02:00
Makefile ADD [v0.3.2]: New performance updates with bytecode + VM for vlisp 2026年05月12日 10:12:55 -03:00
README.md FIX [v0.3.1]: Fixing README.md release flag 2026年05月04日 10:57:11 -03:00
vlisp.h ADD [v0.3.2]: New performance updates with bytecode + VM for vlisp 2026年05月12日 10:12:55 -03:00

vlisp

The Vanguard Lisp — a lightweight, embeddable functional language written in C.

vLisp is a minimal Lisp dialect designed to be embedded in C projects. It features a clean functional core, optional static type annotations, first-class functions, and a tiny footprint — making it ideal as a scripting or configuration layer for C applications.


Features

  • Functional core — lambdas, closures, currying, and partial application
  • Optional type annotations — gradual typing with compile-time-like checking at runtime
  • Higher-order functionsmap, filter, foldl, apply, compose, flip, const
  • Tail-call optimizationif, cond, let, letrec, and begin all support TCO
  • Lexically scoped environments — closures capture their defining environment
  • REPL and file execution — run scripts or explore interactively
  • Simple runtime allocation — runtime objects use standard heap allocation for predictable desktop performance
  • Easy to embed — the entire interpreter fits in a handful of .c files with a single header

Building

vLisp requires a C11-compatible compiler, the standard C library, libm for math builtins, and readline for the interactive REPL.

The default release build is balanced for speed and size (-Os, LTO, dead-section elimination, and stripping):

make release

For a speed-focused build:

make fast

For debug builds:

make debug

Manual build equivalent:

cc -std=c11 -Wall -O2 -flto -ffunction-sections -fdata-sections \
 src/value.c src/env.c src/parser.c src/eval.c src/builtins.c src/main.c \
 -Wl,--gc-sections -lm -lreadline -o vlisp

Installing

Build vLisp before installing:

make release

To use the freshly built binary from the project directory without installing it system-wide:

./vlisp
./vlisp examples/ex.vl

To install the vlisp executable into /usr/local/bin:

sudo make install

After that, vlisp should be available from your shell:

vlisp
vlisp examples/ex.vl

If /usr/local/bin is not in your PATH, add it in your shell configuration or install to a prefix that already is:

make install PREFIX="$HOME/.local"

For package builds or staged installs, use DESTDIR:

make install DESTDIR="$PWD/pkg" PREFIX=/usr

To remove an installed binary from the selected prefix:

sudo make uninstall

Use the same PREFIX when uninstalling if you installed somewhere other than /usr/local:

make uninstall PREFIX="$HOME/.local"

Usage

Interactive REPL

./vlisp
vLisp v0.3.0 - The Vanguard Lisp
Type (quit) or Ctrl+D to exit.
vlisp> (+ 1 2)
3
vlisp> (define (square n) (* n n))
<lambda>
vlisp> (square 7)
49

Run a script file

./vlisp examples/ex.vl

Language Reference

Primitive Types

Type Example Description
Int 42, -7 64-bit signed integer
Float 3.14, -0.5 IEEE 754 double
Bool #t, #f Boolean
String "hello" Immutable string
Nil nil Empty / unit value
List (list 1 2 3) Singly-linked cons list

Special Forms

define — bind a name or define a function

(define x 10)
(define (square n)
 (* n n))

Common Lisp-style definitions are also accepted:

(defvar x 10)
(defparameter y 20)
(defun square (n) (* n n))

lambda — anonymous function

(lambda (x y) (+ x y))
; with parameter type annotation
(lambda ((n Int)) (* n n))

if — conditional (tail-call optimized)

(if (> x 0) "positive" "non-positive")

cond — multi-branch conditional

(cond
 ((< n 0) "negative")
 ((= n 0) "zero")
 (else "positive"))

let — local bindings

(let ((x 3)
 (y 4))
 (+ x y))

letrec — mutually recursive local bindings

(letrec ((fact (lambda (n)
 (if (<= n 1)
 1
 (* n (fact (- n 1)))))))
 (fact 6))

begin — sequential evaluation

(begin
 (define x 1)
 (define y 2)
 (+ x y))

progn is available as a Common Lisp-compatible alias.

quote / ' — suppress evaluation

'(1 2 3)
(quote hello)

set! — mutate an existing binding

(set! x 99)

setq is available as a Common Lisp-compatible alias.

function — refer to a function value

(apply (function +) (list 1 2 3))

Type Annotations

vLisp supports optional, gradual type annotations via the : form. Type errors are caught at runtime before evaluation proceeds.

(: answer Int)
(define answer 42)
(: inc (-> Int Int))
(define (inc n) (+ n 1))
(: xs (List Int))
(define xs (list 1 2 3))

Type syntax:

Annotation Meaning
Int Integer
Float Floating-point
Bool Boolean
String String
Nil Nil / empty
Number Int or Float
Any Unconstrained
(List T) List of T
(-> T1 T2 Ret) Function from T1, T2 → Ret

Arithmetic

(+ 1 2 3) ; => 6
(- 10 3) ; => 7
(* 4 5) ; => 20
(/ 10 2) ; => 5 (integer division when both are Int)
(/ 10 3.0) ; => 3.333...
(mod 10 3) ; => 1
(abs -7) ; => 7
(sqrt 2.0) ; => 1.4142...

Comparison & Logic

(= 1 1) ; => #t
(< 2 5) ; => #t
(>= 3 3) ; => #t
(not #f) ; => #t
(and #t #t) ; => #t
(or #f #t) ; => #t

List Operations

(cons 1 (list 2 3)) ; => (1 2 3)
(car (list 1 2 3)) ; => 1
(cdr (list 1 2 3)) ; => (2 3)
(head (list 1 2 3)) ; => 1 (alias for car)
(tail (list 1 2 3)) ; => (2 3) (alias for cdr)
(first (list 1 2 3)) ; => 1
(rest (list 1 2 3)) ; => (2 3)
(second (list 1 2 3)) ; => 2
(third (list 1 2 3)) ; => 3
(nth 1 (list 1 2 3)) ; => 2
(last (list 1 2 3)) ; => 3
(list 1 2 3) ; => (1 2 3)
(length (list 1 2 3)) ; => 3
(null? nil) ; => #t
(null nil) ; => #t
(empty? nil) ; => #t
(append (list 1 2) (list 3 4)) ; => (1 2 3 4)
(reverse (list 1 2 3)) ; => (3 2 1)
(member 2 (list 1 2 3)) ; => (2 3)
(assoc 'b (list (cons 'a 1) (cons 'b 2))) ; => (b . 2)
(range 5) ; => (0 1 2 3 4)
(range 1 6) ; => (1 2 3 4 5)
(range 0 10 2) ; => (0 2 4 6 8)

Higher-Order Functions

(map (lambda (n) (* n n)) (list 1 2 3 4))
; => (1 4 9 16)
(filter (lambda (n) (> n 2)) (list 1 2 3 4))
; => (3 4)
(foldl + 0 (list 1 2 3 4 5))
; => 15
(apply + (list 1 2 3 4 5))
; => 15
(apply + 1 2 (list 3 4))
; => 10

Functional Combinators

Currying & Partial Application

Functions in vLisp support partial application automatically — calling a function with fewer arguments than it expects returns a new function.

(define add (lambda (x y) (+ x y)))
(define add5 (add 5))
(add5 10) ; => 15

compose — function composition (right to left)

(define f (compose
 (lambda (x) (* x 2))
 (lambda (x) (+ x 3))))
(f 10) ; => 26 — (10+3)*2

flip — swap first two arguments

((flip -) 3 10) ; => 7 — (10 - 3)

const — constant function

((const "always this") 99) ; => "always this"

id — identity function

(id 42) ; => 42

Type Predicates

(int? 1) ; => #t
(integerp 1) ; => #t
(float? 1.0) ; => #t
(floatp 1.0) ; => #t
(numberp 1.0) ; => #t
(bool? #t) ; => #t
(booleanp #t) ; => #t
(string? "hi") ; => #t
(stringp "hi") ; => #t
(symbolp 'hi) ; => #t
(list? (list)) ; => #t
(listp nil) ; => #t
(consp (cons 1 nil)) ; => #t
(atom 'x) ; => #t
(null? nil) ; => #t
(empty? nil) ; => #t
(zerop 0) ; => #t
(plusp 10) ; => #t
(minusp -1) ; => #t
(evenp 4) ; => #t
(oddp 5) ; => #t

Type Conversions

(int->float 3) ; => 3.0
(float->int 3.9) ; => 3
(to-string 42) ; => "42"

Strings

(str-len "hello") ; => 5
(str-concat "foo" "bar") ; => "foobar"
(to-string 3.14) ; => "3.14"

I/O

(print "hello") ; prints without newline
(println "hello") ; prints with newline
(println 1 2 3) ; => 1 2 3

Constants

Name Value
#t Boolean true
#f Boolean false
nil Empty / nil
NIL Empty / nil
t / T Boolean true
pi 3.14159265358979...

Embedding in C

vLisp is designed to be embedded. The full public API is declared in vlisp.h, and the default allocator uses the standard C heap for runtime objects.

#include "vlisp.h"
int main(void) {
 Env *env = env_new(NULL);
 register_builtins(env);
 // Evaluate a string
 Value *exprs = parse_all("(define (square n) (* n n)) (square 9)");
 Value *cur = exprs;
 Value *result = NULL;
 while (cur && cur->type == TYPE_CONS) {
 result = eval(cur->cons.head, env);
 cur = cur->cons.tail;
 }
 println_value(result); // => 81
 return 0;
}

Adding custom builtins

static Value *my_hello(Value *args, Env *env) {
 (void)args; (void)env;
 printf("Hello from C!\n");
 return make_nil();
}
// Register it
env_set(env, "hello!", make_builtin(my_hello));

Example Script

; examples/ex.vl
(: answer Int)
(define answer 42)
(println answer)
(: inc (-> Int Int))
(define (inc n) (+ n 1))
(println (inc 9))
(define square (lambda ((n Int)) (* n n)))
(println (square 7))
; Partial application
(define add (lambda (x y) (+ x y)))
(define inc2 (add 2))
(println (inc2 40))
; Local recursion with letrec
(letrec ((fact
 (lambda ((n Int))
 (if (<= n 1)
 1
 (* n (fact (- n 1)))))))
 (println (fact 6)))
; Functional pipeline
(define nums (range 1 8))
(println (map square nums))
(println (filter (lambda ((n Int)) (>= n 4)) nums))
(println (foldl + 0 nums))
; Combinators
(define twice (compose (lambda (x) (* x 2))
 (lambda (x) (+ x 3))))
(println (twice 10))
(println ((flip -) 3 10))
(println ((const "always this") 99))
(println (apply + (list 1 2 3 4 5)))

Project Structure

vlisp/
├── vlisp.h — Public API and type definitions
├── src/
│ ├── value.c — Value constructors, type system, printing
│ ├── env.c — Lexical environment (symbol tables)
│ ├── parser.c — Lexer and S-expression parser
│ ├── eval.c — Evaluator with TCO and type checking
│ ├── builtins.c — Standard library of built-in functions
│ └── main.c — REPL and file execution entry point
└── examples/
 ├── ex.vl — Example script
 ├── compat.vl — Common Lisp compatibility smoke test
 └── sqrt.vl — Integer square-root example

Limitations

  • No garbage collection — runtime objects are heap allocated and are not reclaimed individually
  • Environment capacity is fixed at 128 bindings per scope (ENV_CAPACITY)
  • List/array operations use fixed-size internal buffers (512 elements for map, filter, append, etc.)
  • No tail-call optimization for foldl or map (these are iterative internally)
  • No macro system

License

MIT License — Copyright (c) 2026 grassleaff. All Rights Reserved.