Convert Figma logo to code with AI

rui314 logominilisp

A readable lisp in less than 1k lines of C

1,452
172
1,452
7

Top Related Projects

9,998

mal - Make a Lisp

Quick Overview

The rui314/minilisp repository is a simple implementation of the Lisp programming language, written in C. It serves as a learning resource for understanding the core concepts and implementation details of a Lisp interpreter.

Pros

  • Simplicity: The project is designed to be a minimal and straightforward implementation of a Lisp interpreter, making it easy to understand and modify.
  • Educational Value: The codebase can be used as a learning resource for those interested in understanding the inner workings of a Lisp interpreter.
  • Portability: The interpreter is written in C, which allows it to be compiled and run on a variety of platforms.
  • Extensibility: The project's modular design makes it relatively easy to extend the language with additional features or functionality.

Cons

  • Limited Functionality: As a minimal implementation, the interpreter lacks many of the advanced features and capabilities found in more mature Lisp implementations.
  • Lack of Documentation: The project's documentation is relatively sparse, which may make it challenging for newcomers to get started.
  • Potential Performance Issues: As a simple interpreter, the project may not be optimized for performance, which could be a concern for larger or more complex programs.
  • Lack of Community Support: The project does not appear to have a large or active community, which could make it difficult to find help or resources for extending or troubleshooting the interpreter.

Code Examples

Here are a few short code examples demonstrating the usage of the minilisp interpreter:

  1. Evaluating a Simple Expression:
#include "minilisp.h"

int main() {
    lval* result = eval(lval_read(lval_add(lval_sym("+"), lval_num(2), lval_num(3))));
    lval_println(result);
    lval_del(result);
    return 0;
}

This code creates a simple expression (+ 2 3), evaluates it using the eval() function, and prints the result.

  1. Defining a Function:
#include "minilisp.h"

int main() {
    lenv* env = lenv_new();
    lval* func = lval_lambda(lval_add(lval_sym("x"), lval_sym("y")), lval_add(lval_sym("+"), lval_sym("x"), lval_sym("y")));
    lenv_put(env, "add", func);
    lval* result = eval(lval_read(lval_add(lval_sym("add"), lval_num(2), lval_num(3))), env);
    lval_println(result);
    lval_del(result);
    lenv_del(env);
    return 0;
}

This code defines a simple function add that takes two arguments and returns their sum, then calls the function with the arguments 2 and 3.

  1. Defining a Recursive Function:
#include "minilisp.h"

int main() {
    lenv* env = lenv_new();
    lval* func = lval_lambda(lval_add(lval_sym("n")), lval_add(
        lval_cond(lval_eq(lval_sym("n"), lval_num(0)), lval_num(1)),
        lval_cond(lval_lt(lval_sym("n"), lval_num(0)), lval_num(0)),
        lval_cond(lval_gt(lval_sym("n"), lval_num(0)), lval_mul(lval_sym("n"), lval_call(lval_sym("fact"), lval_sub(lval_sym("n"), lval_num(1)))))));
    lenv_put(env, "fact", func);
    lval* result = eval(lval_read(lval_add(lval_sym("fact"), lval_num(5))), env);
    lval_println(result);
    lval_del(result);
    lenv_del(env);
    return 0;
}

This

Competitor Comparisons

9,998

mal - Make a Lisp

Pros of Mal

  • Mal is a more feature-rich and comprehensive Lisp implementation, supporting a wider range of language features and functionality.
  • Mal has a larger and more active community, with more contributors and more extensive documentation.
  • Mal is implemented in multiple programming languages, making it more accessible and portable.

Cons of Mal

  • Mal has a larger codebase and may be more complex to understand and modify for beginners.
  • Mal may have a steeper learning curve compared to the more minimal and focused MiniLisp.

Code Comparison

MiniLisp (rui314/minilisp):

(define (fib n)
  (if (< n 2)
    n
    (+ (fib (- n 1)) (fib (- n 2)))))

(print (fib 10))

Mal (kanaka/mal):

(defn fib [n]
  (if (< n 2)
    n
    (+ (fib (- n 1)) (fib (- n 2)))))

(println (fib 10))

The code for the Fibonacci function is very similar in both MiniLisp and Mal, demonstrating the core Lisp syntax and functionality. The main difference is the use of define vs. defn for function definition, and print vs. println for output.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

MiniLisp

One day I wanted to see what I can do with 1k lines of C and decided to write a Lisp interpreter. That turned to be a fun weekend project, and the outcome is a mini lisp implementation that supports

  • integers, symbols, cons cells,
  • global variables,
  • lexically-scoped local variables,
  • closures,
  • if conditional,
  • primitive functions, such as +, =, <, or list,
  • user-defined functions,
  • a macro system,
  • and a copying garbage collector.

All those in 1000 lines of C. I didn't sacrifice readability for size. The code is in my opinion heavily commented to help the reader understand how all these features work.

Compile

$ make

MiniLisp has been tested on Linux x86/x86-64 and 64 bit Mac OS. The code is not very architecture dependent, so you should be able to compile and run on other Unix-like operating systems.

Test

MiniLisp comes with a comprehensive test suite. In order to run the tests, give "test" argument to make.

$ make test

Language features

MiniLisp is a traditional Lisp interpreter. It reads one expression at a time from the standard input, evaluates it, and then prints out the return value of the expression. Here is an example of a valid input.

(+ 1 2)

The above expression prints "3".

Literals

MiniLisp supports integer literals, (), t, symbols, and list literals.

  • Integer literals are positive or negative integers.
  • () is the only false value. It also represents the empty list.
  • t is a predefined variable evaluated to itself. It's a preferred way to represent a true value, while any non-() value is considered to be true.
  • Symbols are objects with unique name. They are used to represent identifiers. Because MiniLisp does not have string type, symbols are sometimes used as a substitute for strings too.
  • List literals are cons cells. It's either a regular list whose last element's cdr is () or an dotted list ending with any non-() value. A dotted list is written as (a . b).

List operators

cons takes two arguments and returns a new cons cell, making the first argument the car, and the second the cdr.

(cons 'a 'b)   ; -> (a . b)
(cons 'a '(b)) ; -> (a b)

car and cdr are accessors for cons cells. car returns the car, and cdr returns the cdr.

(car '(a . b)) ; -> a
(cdr '(a . b)) ; -> b

setcar mutates a cons cell. setcar takes two arguments, assuming the first argument is a cons cell. It sets the second argument's value to the cons cell's car.

(define cell (cons 'a 'b))
cell  ; -> (a . b)
(setcar cell 'x)
cell  ; -> (x . b)

Numeric operators

+ returns the sum of the arguments.

(+ 1)      ; -> 1
(+ 1 2)    ; -> 3
(+ 1 2 3)  ; -> 6

- negates the value of the argument if only one argument is given.

(- 3)      ; -> -3
(- -5)     ; -> 5

If multiple arguments are given, - subtracts each argument from the first one.

(- 5 2)    ; -> 3
(- 5 2 7)  ; -> -4

= takes two arguments and returns t if the two are the same integer.

(= 11 11)  ; -> t
(= 11 6)   ; -> ()

< takes two arguments and returns t if the first argument is smaller than the second.

(< 2 3)    ; -> t
(< 3 3)    ; -> ()
(< 4 3)    ; -> ()

Conditionals

(if cond then else) is the only conditional in the language. It first evaluates cond. If the result is a true value, then is evaluated. Otherwise else is evaluated.

Loops

(while cond expr ...) executes expr ... until cond is evaluated to (). This is the only loop supported by MiniLisp.

If you are familiar with Scheme, you might be wondering if you could write a loop by tail recursion in MiniLisp. The answer is no. Tail calls consume stack space in MiniLisp, so a loop written as recursion will fail with the memory exhaustion error.

Equivalence test operators

eq takes two arguments and returns t if the objects are the same. What eq really does is a pointer comparison, so two objects happened to have the same contents but actually different are considered to not be the same by eq.

Output operators

println prints a given object to the standard output.

(println 3)               ; prints "3"
(println '(hello world))  ; prints "(hello world)"

Definitions

MiniLisp supports variables and functions. They can be defined using define.

(define a (+ 1 2))
(+ a a)   ; -> 6

There are two ways to define a function. One way is to use a special form lambda. (lambda (args ...) expr ...) returns a function object which you can assign to a variable using define.

(define double (lambda (x) (+ x x)))
(double 6)                ; -> 12
((lambda (x) (+ x x)) 6)  ; do the same thing without assignment

The other way is defun. (defun fn (args ...) expr ...) is short for (define fn (lambda (args ...) expr ...).

;; Define "double" using defun
(defun double (x) (+ x x))

You can write a function that takes variable number of arguments. If the parameter list is a dotted list, the remaining arguments are bound to the last parameter as a list.

(defun fn (expr . rest) rest)
(fn 1)     ; -> ()
(fn 1 2 3) ; -> (2 3)

Variables are lexically scoped and have indefinite extent. References to "outer" variables remain valid even after the function that created the variables returns.

;; A countup function. We use lambda to introduce local variables because we
;; do not have "let" and the like.
(define counter
  ((lambda (count)
     (lambda ()
       (setq count (+ count 1))
       count))
   0))

(counter)  ; -> 1
(counter)  ; -> 2

;; This will not return 12345 but 3. Variable "count" in counter function
;; is resolved based on its lexical context rather than dynamic context.
((lambda (count) (counter)) 12345)  ; -> 3

setq sets a new value to an existing variable. It's an error if the variable is not defined.

(define val (+ 3 5))
(setq val (+ val 1))  ; increment "val"

Macros

Macros look similar to functions, but they are different that macros take an expression as input and returns a new expression as output. (defmacro macro-name (args ...) body ...) defines a macro. Here is an example.

(defmacro unless (condition expr)
  (list 'if condition () expr))

The above defmacro defines a new macro unless. unless is a new conditional which evaluates expr unless condition is a true value. You cannot do the same thing with a function because all the arguments would be evaluated before the control is passed to the function.

(define x 0)
(unless (= x 0) '(x is not 0))  ; -> ()
(unless (= x 1) '(x is not 1))  ; -> (x is not 1)

macroexpand is a convenient special form to see the expanded form of a macro.

(macroexpand (unless (= x 1) '(x is not 1)))
;; -> (if (= x 1) () (quote (x is not 1)))

gensym creates a new symbol which will never be eq to any other symbol other than itself. Useful for writing a macro that introduces new identifiers.

(gensym)   ; -> a new symbol

Comments

As in the traditional Lisp syntax, ; (semicolon) starts a single line comment. The comment continues to the end of line.

No GC Branch

There is a MiniLisp branch from which the code for garbage collection has been stripped. The accepted language is the same, but the code is simpler than the master branch's one. The reader might want to read the nogc branch first, then proceed to the master branch, to understand the code step by step.

The nogc branch is available at nogc. The original is available at master.