Iter 16b.1 — local recursive let (no-capture)
Add `(let-rec NAME (params ...) (type ...) (body ...) (in ...))` as a form-A surface and a `Term::LetRec` AST variant. The 16a desugar pass lifts each LetRec whose body has no captures from the enclosing scope to a synthetic top-level fn `<hint>$lr_N` and substitutes the original name; typecheck and codegen never see LetRec. Capture detection panics at desugar time, queued for 16b.2. Tests: 95 → 99 (+1 e2e local_rec_factorial_demo, +2 desugar unit, +1 parse unit). The new fixture `examples/local_rec_demo` runs `fact` at n=1, 3, 5 → prints 1, 6, 120. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,7 +36,7 @@
|
||||
//! term ::= var-ref | int-lit | str-lit | bool-lit | unit-lit
|
||||
//! | app-term | tail-app-term | match-term | ctor-term
|
||||
//! | do-term | tail-do-term | seq-term | lam-term | if-term
|
||||
//! | let-term
|
||||
//! | let-term | let-rec-term
|
||||
//! var-ref ::= ident ; reserved: true/false → bool-lit
|
||||
//! int-lit ::= integer ; numeric atom
|
||||
//! str-lit ::= string ; string atom
|
||||
@@ -56,6 +56,11 @@
|
||||
//! typed-param ::= "(" "typed" ident type ")"
|
||||
//! if-term ::= "(" "if" term term term ")"
|
||||
//! let-term ::= "(" "let" ident term term ")"
|
||||
//! let-rec-term ::= "(" "let-rec" ident
|
||||
//! "(" "params" ident* ")"
|
||||
//! type-attr
|
||||
//! body-attr
|
||||
//! "(" "in" term ")" ")"
|
||||
//!
|
||||
//! pattern ::= pat-var | pat-ctor | pat-lit | pat-wild
|
||||
//! pat-var ::= ident
|
||||
@@ -690,13 +695,14 @@ impl<'a> Parser<'a> {
|
||||
"lam" => self.parse_lam(),
|
||||
"if" => self.parse_if(),
|
||||
"let" => self.parse_let(),
|
||||
"let-rec" => self.parse_let_rec(),
|
||||
other => {
|
||||
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
|
||||
Err(ParseError::Production {
|
||||
production: "term",
|
||||
message: format!(
|
||||
"unknown term head `{other}`; expected one of \
|
||||
`app`, `tail-app`, `lam`, `let`, `if`, `match`, `do`, \
|
||||
`app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, \
|
||||
`tail-do`, `seq`, `term-ctor`, `lit-unit`"
|
||||
),
|
||||
pos,
|
||||
@@ -943,6 +949,34 @@ impl<'a> Parser<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Iter 16b.1: `(let-rec NAME (params PARAM*) (type T) (body TERM)
|
||||
/// (in TERM))` — local recursive fn-shaped binding. Eliminated by
|
||||
/// the desugar pass (lifted to a synthetic top-level fn) before
|
||||
/// typecheck. The body's recursive references to `NAME` are
|
||||
/// rewritten to the lifted name; the `in`-clause becomes the term
|
||||
/// that replaces the LetRec.
|
||||
fn parse_let_rec(&mut self) -> Result<Term, ParseError> {
|
||||
self.expect_lparen("let-rec-term")?;
|
||||
self.expect_keyword("let-rec")?;
|
||||
let name = self.expect_ident("let-rec name")?;
|
||||
let params = self.parse_params_attr()?;
|
||||
let ty = self.parse_type_attr()?;
|
||||
let body = self.parse_body_attr()?;
|
||||
// (in TERM)
|
||||
self.expect_lparen("let-rec in-clause")?;
|
||||
self.expect_keyword("in")?;
|
||||
let in_term = self.parse_term()?;
|
||||
self.expect_rparen("let-rec in-clause")?;
|
||||
self.expect_rparen("let-rec-term")?;
|
||||
Ok(Term::LetRec {
|
||||
name,
|
||||
ty,
|
||||
params,
|
||||
body: Box::new(body),
|
||||
in_term: Box::new(in_term),
|
||||
})
|
||||
}
|
||||
|
||||
// ---- patterns -------------------------------------------------------
|
||||
|
||||
fn parse_pattern(&mut self) -> Result<Pattern, ParseError> {
|
||||
@@ -1081,4 +1115,45 @@ mod tests {
|
||||
.unwrap();
|
||||
assert!(matches!(m.defs.len(), 1));
|
||||
}
|
||||
|
||||
/// Iter 16b.1: minimal `(let-rec ...)` round-trips through the
|
||||
/// parser into a `Term::LetRec` whose `name`, `params`, `body` and
|
||||
/// `in_term` line up with the source.
|
||||
#[test]
|
||||
fn parses_minimal_let_rec() {
|
||||
let m = parse(
|
||||
r#"
|
||||
(module m
|
||||
(fn main
|
||||
(type (fn-type (params) (ret (con Int))))
|
||||
(params)
|
||||
(body
|
||||
(let-rec f
|
||||
(params x)
|
||||
(type (fn-type (params (con Int)) (ret (con Int))))
|
||||
(body x)
|
||||
(in (app f 1))))))
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let body = match &m.defs[0] {
|
||||
Def::Fn(fd) => &fd.body,
|
||||
_ => panic!("expected fn"),
|
||||
};
|
||||
match body {
|
||||
Term::LetRec { name, params, body, in_term, .. } => {
|
||||
assert_eq!(name, "f");
|
||||
assert_eq!(params, &vec!["x".to_string()]);
|
||||
assert!(matches!(body.as_ref(), Term::Var { name } if name == "x"));
|
||||
match in_term.as_ref() {
|
||||
Term::App { callee, args, .. } => {
|
||||
assert!(matches!(callee.as_ref(), Term::Var { name } if name == "f"));
|
||||
assert_eq!(args.len(), 1);
|
||||
}
|
||||
other => panic!("expected App in in-clause, got {other:?}"),
|
||||
}
|
||||
}
|
||||
other => panic!("expected LetRec, got {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user