Skip to content

Commit

Permalink
Implement named let, set-cdr and add better examples (#28)
Browse files Browse the repository at this point in the history
* implement named let, set-cdr and add better examples

* More readme update

* Cutoff output
  • Loading branch information
maplant authored Dec 5, 2024
1 parent 5c7543b commit 4949784
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 23 deletions.
32 changes: 17 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,42 @@ That is obviously a long way away.

- Tail-call optimizations are fully supported
- Garbage Collected via [Bacon-Rajan Concurrent Cycle Collection](https://pages.cs.wisc.edu/~cymen/misc/interests/Bacon01Concurrent.pdf)
- Most key forms (let/let*/define)
- Most key forms (let/let*/letrec/lambda/define etc)
- Call by current continuation
- Transformers
- Transformers (define-syntax, syntax-case, make-variable-transformer)
- Spawning tasks and awaiting futures

## Features currently unsupported by scheme-rs:

- Records, and therefore conditions and error handling
- Ports and IO operations
- Generally, most API functions are not currently implemented
- set-car! and set-cdr!
- Most API functions are not implemented
- A large portion of lexical structures are missing; there's no way to specify recursive data structures
- Let loop
- And many more that I cannot think of off the top of my head.
- And many more that I cannot think of off the top of my head

## Usage:

### Running a REPL:

A REPL is the default entry point for scheme-rs at the current moment. You can access it by running `cargo run`
in the repo's root directory:
in the repo's root directory (examples taken from wikipedia):

```
~/scheme-rs> cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s
Running `target/debug/scheme-rs`
>>> (+ 5 5)
$1 = 10
>>> (let loop ((n 1))
... (if (> n 10)
... '()
... (cons n
... (loop (+ n 1)))))
$1 = (1 2 3 4 5 6 7 8 9 10)
>>> (let* ((yin
... ((lambda (cc) (display "@") cc) (call-with-current-continuation (lambda (c) c))))
... (yang
... ((lambda (cc) (display "*") cc) (call-with-current-continuation (lambda (c) c)))))
... (yin yang))
@*@**@***@****@*****@******@*******@********@*********@**********@***********@**********...^C
```

### Creating Builtin Functions:
Expand All @@ -59,9 +67,3 @@ pub async fn is_number(
Ok(Gc::new(Value::Boolean(matches!(&*arg, Value::Number(_)))))
}
```

## Why not just use Guile?

In the end, it all comes down to [call with current continuation](https://en.wikipedia.org/wiki/Call-with-current-continuation). Guile implements this feature by copying the stack, an action which is obviously problematic when considering
ref counts and locks. Scheme-rs remedies this by providing all functions with the current continuation so that they
may handle it properly. Additionally, the current continuation only contains references and not locked variables.
32 changes: 26 additions & 6 deletions src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,16 +126,21 @@ impl Compile for ast::Let {
span: &Span,
) -> Result<Self, CompileLetError> {
match expr {
[Syntax::Null { .. }, body @ ..] => compile_let(&[], body, env, cont, span).await,
[Syntax::Null { .. }, body @ ..] => compile_let(None, &[], body, env, cont, span).await,
[Syntax::List { list: bindings, .. }, body @ ..] => {
compile_let(bindings, body, env, cont, span).await
compile_let(None, bindings, body, env, cont, span).await
}
// Named let:
[Syntax::Identifier { ident, .. }, Syntax::List { list: bindings, .. }, body @ ..] => {
compile_let(Some(ident), bindings, body, env, cont, span).await
}
_ => Err(CompileLetError::BadForm(span.clone())),
}
}
}

async fn compile_let(
name: Option<&Identifier>,
bindings: &[Syntax],
body: &[Syntax],
env: &Env,
Expand All @@ -161,11 +166,26 @@ async fn compile_let(
let body = ast::Body::compile(body, &Env::from(env.clone()), cont, span)
.await
.map_err(CompileLetError::CompileBodyError)?;

let mut bindings: Vec<_> = compiled_bindings
.into_iter()
.map(|binding| (binding.ident, binding.expr))
.collect();

// If this is a named let, add a binding for a procedure with the same
// body and args of the formals:
if let Some(name) = name {
let lambda = ast::Lambda {
args: ast::Formals::FixedArgs(
bindings.iter().map(|(ident, _)| ident.clone()).collect(),
),
body: body.clone(),
};
bindings.push((name.clone(), Arc::new(lambda)));
}

Ok(ast::Let {
bindings: compiled_bindings
.into_iter()
.map(|binding| (binding.ident, binding.expr))
.collect(),
bindings: Arc::from(bindings),
body,
})
}
Expand Down
14 changes: 14 additions & 0 deletions src/lists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,20 @@ pub async fn set_car(
Ok(vec![Gc::new(Value::Null)])
}

#[builtin("set-cdr!")]
pub async fn set_cdr(
_cont: &Option<Arc<Continuation>>,
var: &Gc<Value>,
val: &Gc<Value>,
) -> Result<Vec<Gc<Value>>, RuntimeError> {
let mut var = var.write().await;
match &mut *var {
Value::Pair(_car, ref mut cdr) => *cdr = val.clone(),
_ => todo!(),
}
Ok(vec![Gc::new(Value::Null)])
}

#[builtin("length")]
pub async fn length(
_cont: &Option<Arc<Continuation>>,
Expand Down
2 changes: 1 addition & 1 deletion src/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub enum Syntax {
span: Span,
},
/// A nested grouping of pairs. If the expression is a proper list, then the
/// last element of expression will be Nil. This vector is guaranteed to contain
/// last element of expression will be Null. This vector is guaranteed to contain
/// at least two elements.
List {
list: Vec<Syntax>,
Expand Down
2 changes: 1 addition & 1 deletion src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl Value {
Self::Boolean(true) => "#t".to_string(),
Self::Boolean(false) => "#f".to_string(),
Self::Number(number) => number.to_string(),
Self::String(string) => format!("\"{string}\""),
Self::String(string) => string.to_string(),
Self::Symbol(symbol) => symbol.clone(),
Self::Pair(car, cdr) => crate::lists::fmt_list(car, cdr).await,
Self::Vector(vec) => {
Expand Down

0 comments on commit 4949784

Please sign in to comment.