Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: check

on:
push:
branches:
- master
- main
pull_request:

jobs:
check:
strategy:
fail-fast: false
runs-on: ubuntu-latest
continue-on-error: false
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'

- name: install
run: |
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash
echo "$HOME/.moon/bin" >> $GITHUB_PATH

- name: moon version
run: |
moon version --all

- name: moon check
run: |
moon update
moon check --deny-warn

- name: moon info
run: |
moon info --target all
git diff --exit-code

- name: format diff
run: |
moon fmt
git diff --exit-code

- name: moon test
run: |
moon test --target all
105 changes: 77 additions & 28 deletions README.mbt.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ A Lisp interpreter implementation in MoonBit, featuring S-expression parsing and

### Basic Arithmetic

```moonbit
```moonbit nocheck
///|
test {
eval_string("(+ 1 2 3)", content=6)
eval_string("(* (+ 2 3) (- 8 2))", content=30)
Expand All @@ -26,15 +27,17 @@ test {

### Variables and Definitions

```moonbit
```moonbit nocheck
///|
test {
eval_string("(begin (define x 10) (define y 5) (+ x y))", content=15)
}
```

### Conditional Expressions

```moonbit
```moonbit nocheck
///|
test "if conditions" {
eval_string("(if (> 5 2) 42 0)", content=42)
eval_string("(= 5 5)", content=true)
Expand All @@ -47,22 +50,33 @@ test "if conditions" {

### Lambda Functions

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

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)

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)
eval_string(
"(begin (define make-adder (lambda (n) (lambda (x) (+ x n)))) (define add5 (make-adder 5)) (add5 10))",
content=15,
)
}
```

### Function Definition Shorthand

```moonbit
```moonbit nocheck
///|
test {
// Alternative function definition syntax
let factorial =
Expand All @@ -71,39 +85,64 @@ test {
#| (if (= n 0) 1
#| (* n (fact (- n 1)))))
#| (fact 5))
eval_string(factorial, content=120)
eval_string(factorial, content=120)
}
```

## S-expression Parsing

The parser handles various Lisp syntax constructs:

```moonbit
```moonbit nocheck
///|
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")]))

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")])
]))

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("👋🏻")]))
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:

```moonbit
```moonbit nocheck
///|
test {
// Parse errors
try {
Expand All @@ -112,7 +151,7 @@ test {
} catch {
_ => () // Expected parse error
}

// Evaluation errors
try {
let _ = @lisp_interpreter.evaluate(@lisp_interpreter.parse_sexp("(+ x 1)")) // unbound variable
Expand Down Expand Up @@ -142,26 +181,36 @@ The interpreter consists of two main modules:
## Data Types

### S-expressions
```moonbit
```moonbit nocheck
///|
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")])
let _list = @lisp_interpreter.Sexp::List([
@lisp_interpreter.Sexp::Atom("+"),
@lisp_interpreter.Sexp::Atom("1"),
@lisp_interpreter.Sexp::Atom("2"),
])
}
```

### Runtime Values
```moonbit
```moonbit nocheck
///|
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)
let _func = @lisp_interpreter.Value::Function(
["x"],
@lisp_interpreter.parse_sexp("(* x x)"),
env,
)
}
```

Expand Down
7 changes: 4 additions & 3 deletions lisp_interpreter_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -618,9 +618,10 @@ test "let vs define semantics comparison" {
#| (get-x)))))
#| (define result2
#| ((lambda (x)
#| (define get-x (lambda () x))
#| (define x 20)
#| (get-x))
#| (begin
#| (define get-x (lambda () x))
#| (define x 20)
#| (get-x)))
#| 10))
#| (+ result1 result2))
eval_string(key_difference, content=30) // 10 + 20 = 30
Expand Down
11 changes: 10 additions & 1 deletion sexp.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ fn Lexer::update_view_with_token(self : Lexer, view : StringView) -> String {
end_offset=view.start_offset(),
)
self.pos = view.start_offset()
consumed.to_string()
consumed.to_owned()
}

///|
Expand All @@ -118,6 +118,15 @@ fn Lexer::decode(self : Lexer) -> Array[Token] raise {
continue rest
}
[' ' | '\n' | '\t', .. rest] => continue rest
[';', .. rest] => {
let next = for view = rest {
match view {
[] | ['\n', ..] as next => break next
Comment thread
myfreess marked this conversation as resolved.
[_, .. rest] => continue rest
}
}
continue next
}
['"', ..] as view => {
self.update_view(view) // mark theh position, do the work
tokens.push(self.decode_string())
Expand Down
Loading