lisp_interpreter

moon add bobzhang/lisp_interpreter@0.1.1
Download zip
Author
Version
0.1.1
License
Apache-2.0
Last updated
3 months ago
Downloads
25
README

#Lisp Machine - MoonPilot

A Lisp interpreter implementation in MoonBit, featuring S-expression parsing and evaluation with support for arithmetic operations, variables, functions, and control flow.

#Features

  • S-expression parsing: Robust tokenization and parsing of Lisp syntax with proper error handling
  • Arithmetic operations: Support for +, -, *, / operations
  • Comparison operations: Support for =, <, >, <=, >= comparisons
  • Variables: Define and use variables with define
  • Functions: Create lambda functions and call them
  • Control flow: Conditional expressions with if
  • Sequential evaluation: Multiple expressions with begin
  • Unicode support: Full support for Unicode characters including emojis

#Usage

#Basic Arithmetic

test {
eval_string("(+ 1 2 3)", content=6)
eval_string("(* (+ 2 3) (- 8 2))", content=30)
}

#Variables and Definitions

test {
eval_string("(begin (define x 10) (define y 5) (+ x y))", content=15)
}

#Conditional Expressions

test "if conditions" {
eval_string("(if (> 5 2) 42 0)", content=42)
eval_string("(= 5 5)", content=true)
eval_string("(< 3 7)", content=true)
eval_string("(> 5 4)", content=true)
eval_string("(>= 5 5)", content=true)
eval_string("(<= 3 7)", content=true)
}

#Lambda Functions

test {
// Basic lambda
eval_string("(begin (define square (lambda (x) (* x x))) (square 4))", content=16)

// Higher-order functions
eval_string("(begin (define apply-twice (lambda (f x) (f (f x)))) (define add1 (lambda (x) (+ x 1))) (apply-twice add1 5))", content=7)

// Closures
eval_string("(begin (define make-adder (lambda (n) (lambda (x) (+ x n)))) (define add5 (make-adder 5)) (add5 10))", content=15)
}

#Function Definition Shorthand

test {
// Alternative function definition syntax
let factorial =
#| (begin
#| (define (fact n)
#| (if (= n 0) 1
#| (* n (fact (- n 1)))))
#| (fact 5))
eval_string(factorial, content=120)
}

#S-expression Parsing

The parser handles various Lisp syntax constructs:

test {
// Simple expressions
let simple = @lisp_interpreter.parse_sexp("(hello world)")
assert_eq(simple, @lisp_interpreter.Sexp::List([@lisp_interpreter.Sexp::Atom("hello"), @lisp_interpreter.Sexp::Atom("world")]))

// Nested expressions
let nested = @lisp_interpreter.parse_sexp("(define (square x) (* x x))")
assert_eq(nested, @lisp_interpreter.Sexp::List([
@lisp_interpreter.Sexp::Atom("define"),
@lisp_interpreter.Sexp::List([@lisp_interpreter.Sexp::Atom("square"), @lisp_interpreter.Sexp::Atom("x")]),
@lisp_interpreter.Sexp::List([@lisp_interpreter.Sexp::Atom("*"), @lisp_interpreter.Sexp::Atom("x"), @lisp_interpreter.Sexp::Atom("x")])
]))

// Unicode support
let unicode = @lisp_interpreter.parse_sexp("(你好 世界 👋🏻)")
assert_eq(unicode, @lisp_interpreter.Sexp::List([@lisp_interpreter.Sexp::Atom("你好"), @lisp_interpreter.Sexp::Atom("世界"), @lisp_interpreter.Sexp::Atom("👋🏻")]))
}

#Error Handling

The interpreter provides comprehensive error handling:

test {
// Parse errors
try {
let _ = @lisp_interpreter.parse_sexp("(hello (world")
fail("Should have failed with parse error")
} catch {
_ => () // Expected parse error
}

// Evaluation errors
try {
let _ = @lisp_interpreter.evaluate(@lisp_interpreter.parse_sexp("(+ x 1)")) // unbound variable
fail("Should have failed with unbound variable error")
} catch {
_ => () // Expected error for unbound variable
}
}

#Architecture

The interpreter consists of two main modules:

  1. sexp.mbt: S-expression parsing and tokenization
    • Sexp enum representing atoms and lists
    • @lisp_interpreter.parse_sexp() function for parsing strings into S-expressions
    • tokenize() function for lexical analysis
    • Comprehensive error handling with ParseError types

  2. lisp_interpreter.mbt: Expression evaluation and runtime
    • Value enum representing runtime values (numbers, booleans, symbols, functions)
    • Environment for variable bindings
    • @lisp_interpreter.evaluate() function for expression evaluation
    • Built-in functions and special forms

#Data Types

#S-expressions

test {
// Atoms represent symbols, numbers, and literals
let _atom = @lisp_interpreter.Sexp::Atom("hello")

// Lists represent function calls and data structures
let _list = @lisp_interpreter.Sexp::List([@lisp_interpreter.Sexp::Atom("+"), @lisp_interpreter.Sexp::Atom("1"), @lisp_interpreter.Sexp::Atom("2")])
}

#Runtime Values

test {
// The interpreter supports various value types:
let _number = @lisp_interpreter.Value::Number(42)
let _boolean = @lisp_interpreter.Value::Boolean(true)
let _symbol = @lisp_interpreter.Value::Symbol("nil")
// Functions store parameters, body, and closure environment
let env = @lisp_interpreter.Env::builtin()
let _func = @lisp_interpreter.Value::Function(["x"], @lisp_interpreter.parse_sexp("(* x x)"), env)
}

#Built-in Operations

  • Arithmetic: +, -, *, /
  • Comparison: =, <, >, <=, >=
  • Special Forms: if, define, lambda, begin

#License

Apache-2.0

#
ParseError

pub suberror ParseError {
UnmatchedParens(String)
UnexpectedParens(String)
UnexpectedEOF
} derive(ToJson,
Debug
)

TODO(upstream): Warning: The type 'ParseError' does not occur in public signature of current package, consider marking it as priv The above message will be misleading because ParseError is actually used in the public signature. Figure out the pub/abstract meaning of suberror This generally makes sense but doesnot make sense for suberror since It is an existential type
impl Show for ParseError

#
Env

impl Show for Env

#
Env::builtin

fn Env::builtin() -> Env

#
Sexp

pub(all) enum Sexp {
Atom(String)
List(Array[Sexp])
} derive(Eq, ToJson,
Debug
)

impl Show for Sexp

#
Value

pub(all) enum Value {
Number(Int)
Boolean(Bool)
Symbol(String)
Function(Array[String], Sexp, Env)
BuiltinFunction(String)
} derive(
Debug
)

impl Show for Value
impl ToJson for Value

#
eval

fn eval(s : String) -> Value raise

#
evaluate

fn evaluate(expr : Sexp) -> Value raise

#
parse_sexp

fn parse_sexp(input : String) -> Sexp raise