Lines
63.78 %
Functions
22.94 %
Branches
100 %
use crate::ast::{Expr, LambdaParams, WasmType};
use crate::error::{Error, Result};
use crate::runtime::{Symbol, SymbolKind, SymbolTable};
use super::super::context::CompileContext;
use super::super::emit::FunctionEmitter;
use super::super::expr::{
compile_body, compile_body_for_stack, compile_expr, compile_for_effect, compile_for_stack,
eval_value,
};
pub(super) fn compile_defun(
ctx: &mut CompileContext,
emit: &mut FunctionEmitter,
symbols: &mut SymbolTable,
args: &[Expr],
) -> Result<()> {
let result = defun(symbols, args)?;
compile_expr(ctx, emit, symbols, &result)
}
pub(super) fn compile_defvar(
let result = defvar(symbols, args)?;
pub(super) fn compile_defparam(
let result = defparameter(symbols, args)?;
pub(super) fn compile_let(
if args.len() < 2 {
return Err(Error::Compile(
"LET requires a bindings list and at least one body form".to_string(),
));
let bindings = parse_bindings("LET", &args[0])?;
// Evaluate all bindings in the outer scope (parallel semantics)
let resolved: Vec<(String, Option<Expr>, Expr)> = bindings
.into_iter()
.map(|(name, init)| {
let (orig, val) = match init {
Some(expr) => {
let v = eval_value(symbols, &expr)?;
(Some(expr), v)
None => (None, Expr::Nil),
Ok((name, orig, val))
})
.collect::<Result<_>>()?;
let mut local = symbols.clone();
for (name, init_expr, val) in resolved {
let bound = bind_runtime_or_const(ctx, emit, symbols, init_expr.as_ref(), &val)?;
local.define(Symbol::new(&name, SymbolKind::Variable).with_value(bound));
compile_body(ctx, emit, &mut local, &args[1..])
pub(super) fn compile_let_for_stack(
) -> Result<WasmType> {
compile_body_for_stack(ctx, emit, &mut local, &args[1..])
pub(super) fn compile_let_star(
"LET* requires a bindings list and at least one body form".to_string(),
let bindings = parse_bindings("LET*", &args[0])?;
for (name, init) in bindings {
let (init_expr, val) = match init {
let v = eval_value(&mut local, &expr)?;
let bound = bind_runtime_or_const(ctx, emit, &mut local, init_expr.as_ref(), &val)?;
pub(super) fn compile_let_star_for_stack(
pub(super) fn compile_let_for_effect(
for arg in &args[1..] {
compile_for_effect(ctx, emit, &mut local, arg)?;
Ok(())
pub(super) fn compile_let_star_for_effect(
/// If val is `WasmRuntime`, compile the init expression to the WASM stack,
/// allocate a local, and return `WasmLocal`. Otherwise return the constant value.
fn bind_runtime_or_const(
init_expr: Option<&Expr>,
val: &Expr,
) -> Result<Expr> {
match val {
Expr::WasmRuntime(ty) => {
let expr = init_expr.ok_or_else(|| {
Error::Compile("runtime binding requires an init expression".to_string())
})?;
compile_for_stack(ctx, emit, symbols, expr)?;
let idx = ctx.alloc_local(*ty);
emit.local_set(idx);
Ok(Expr::WasmLocal(idx, *ty))
Expr::WasmLocal(_, _) => Ok(val.clone()),
_ => Ok(val.clone()),
pub(super) fn defun(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
if args.len() < 3 {
"DEFUN requires a name, parameter list, and body".to_string(),
let name = match &args[0] {
Expr::Symbol(s) => s.clone(),
other => {
return Err(Error::Compile(format!(
"DEFUN: expected symbol name, got {other:?}"
)));
let params = parse_lambda_params("DEFUN", &args[1])?;
let (doc, body_idx) = match args.get(2) {
Some(Expr::String(s)) if args.len() > 3 => (Some(s.clone()), 3),
_ => (None, 2),
if body_idx >= args.len() {
return Err(Error::Compile("DEFUN: missing body".to_string()));
let body = if args.len() == body_idx + 1 {
args[body_idx].clone()
} else {
let mut forms = Vec::with_capacity(args.len() - body_idx + 1);
forms.push(Expr::Symbol("BEGIN".to_string()));
forms.extend_from_slice(&args[body_idx..]);
Expr::List(forms)
let lambda = Expr::Lambda(params, Box::new(body));
if let Some(sym) = symbols.lookup_mut(&name) {
sym.set_function(lambda);
if let Some(d) = doc {
sym.set_doc(d);
let mut sym = Symbol::new(&name, SymbolKind::Variable).with_function(lambda);
sym = sym.with_doc(d);
symbols.define(sym);
Ok(Expr::Quote(Box::new(Expr::Symbol(name))))
pub(super) fn defvar(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
if args.is_empty() {
"DEFVAR requires at least a name".to_string(),
"DEFVAR: expected symbol name, got {other:?}"
let initial = args.get(1).map(|e| eval_value(symbols, e)).transpose()?;
let doc = match args.get(2) {
Some(Expr::String(s)) => Some(s.clone()),
_ => None,
if sym.value().is_none()
&& let Some(val) = initial
{
sym.set_value(val);
let mut sym = Symbol::new(&name, SymbolKind::Variable);
if let Some(val) = initial {
sym = sym.with_value(val);
pub(super) fn defparameter(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
"DEFPARAMETER requires a name and initial value".to_string(),
"DEFPARAMETER: expected symbol name, got {other:?}"
let value = eval_value(symbols, &args[1])?;
sym.set_value(value);
let mut sym = Symbol::new(&name, SymbolKind::Variable).with_value(value);
pub(super) fn let_form(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
let resolved: Vec<(String, Expr)> = bindings
let val = match init {
Some(expr) => eval_value(symbols, &expr)?,
None => Expr::Nil,
Ok((name, val))
let mut local_symbols = symbols.clone();
for (name, val) in resolved {
local_symbols.define(Symbol::new(&name, SymbolKind::Variable).with_value(val));
eval_body(&mut local_symbols, &args[1..])
pub(super) fn let_star(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
Some(expr) => eval_value(&mut local_symbols, &expr)?,
pub(super) fn eval_body(symbols: &mut SymbolTable, body: &[Expr]) -> Result<Expr> {
let mut has_runtime = false;
for expr in &body[..body.len() - 1] {
let val = eval_value(symbols, expr)?;
if val.is_wasm_runtime() {
has_runtime = true;
let result = eval_value(symbols, body.last().unwrap())?;
if has_runtime && !result.is_wasm_runtime() {
let ty = infer_runtime_type(&result);
return Ok(Expr::WasmRuntime(ty));
Ok(result)
fn infer_runtime_type(expr: &Expr) -> WasmType {
match expr {
Expr::Number(_) => WasmType::Ratio,
Expr::Bool(_) => WasmType::I32,
_ => WasmType::ConsRef,
pub(super) fn parse_bindings(context: &str, expr: &Expr) -> Result<Vec<(String, Option<Expr>)>> {
let list = match expr {
Expr::List(elems) => elems,
_ => {
"{context}: expected bindings list, got {expr:?}"
list.iter()
.map(|binding| match binding {
Expr::Symbol(name) => Ok((name.clone(), None)),
Expr::List(elems) if elems.len() == 1 => {
let name = elems[0].as_symbol().ok_or_else(|| {
Error::Compile(format!(
"{context}: binding name must be a symbol, got {:?}",
elems[0]
))
Ok((name.to_string(), None))
Expr::List(elems) if elems.len() == 2 => {
Ok((name.to_string(), Some(elems[1].clone())))
_ => Err(Error::Compile(format!(
"{context}: malformed binding: {binding:?}"
))),
.collect()
pub(super) fn parse_param_list(context: &str, expr: &Expr) -> Result<Vec<String>> {
let params = parse_lambda_params(context, expr)?;
if !params.optional.is_empty()
|| params.rest.is_some()
|| !params.key.is_empty()
|| !params.aux.is_empty()
"{context}: lambda list keywords not supported in this context"
Ok(params.required)
pub(super) fn parse_lambda_params(context: &str, expr: &Expr) -> Result<LambdaParams> {
Expr::List(ps) => {
let mut params = LambdaParams::simple(Vec::new());
let mut state = ParseState::Required;
for param in ps {
if let Some(sym) = param.as_symbol() {
if sym.eq_ignore_ascii_case("&optional") {
if matches!(
state,
ParseState::Optional
| ParseState::Rest
| ParseState::Key
| ParseState::Aux
) {
"{context}: &optional after {}",
state.name()
state = ParseState::Optional;
continue;
if sym.eq_ignore_ascii_case("&rest") {
if matches!(state, ParseState::Rest | ParseState::Key | ParseState::Aux) {
"{context}: &rest after {}",
state = ParseState::Rest;
if sym.eq_ignore_ascii_case("&key") {
if matches!(state, ParseState::Key | ParseState::Aux) {
"{context}: &key after {}",
state = ParseState::Key;
if sym.eq_ignore_ascii_case("&aux") {
if matches!(state, ParseState::Aux) {
return Err(Error::Compile(format!("{context}: &aux after aux")));
state = ParseState::Aux;
match state {
ParseState::Required => {
let name = param
.as_symbol()
.ok_or_else(|| {
"{context}: required parameter must be a symbol, got {param:?}"
})?
.to_string();
params.required.push(name);
ParseState::Optional => {
let (name, default) = parse_optional_param(context, param)?;
params.optional.push((name, default));
ParseState::Rest => {
"{context}: &rest parameter must be a symbol, got {param:?}"
if params.rest.is_some() {
"{context}: multiple &rest parameters"
params.rest = Some(name);
state = ParseState::PostRest;
ParseState::PostRest => {
"{context}: parameters after &rest must use &key or &aux"
ParseState::Key => {
params.key.push((name, default));
ParseState::Aux => {
params.aux.push((name, default));
Ok(params)
other => Err(Error::Compile(format!(
"{context}: expected parameter list, got {other:?}"
#[derive(Debug, Clone)]
pub(super) enum ParseState {
Required,
Optional,
Rest,
PostRest,
Key,
Aux,
impl ParseState {
fn name(&self) -> &'static str {
match self {
ParseState::Required => "required",
ParseState::Optional => "&optional",
ParseState::Rest => "&rest",
ParseState::PostRest => "post-&rest",
ParseState::Key => "&key",
ParseState::Aux => "&aux",
fn parse_optional_param(context: &str, param: &Expr) -> Result<(String, Option<Expr>)> {
match param {
let name = elems[0]
"{context}: parameter name must be a symbol, got {:?}",
Ok((name, Some(elems[1].clone())))
"{context}: optional parameter must be a symbol or (symbol default), got {param:?}"