Add struct literals, generics, log/print stdlib registration, activate DB wiring

- Register print/println/log/print_err in TypeEnv::with_builtins() with polymorphic (Unknown) param type so any value type is accepted without spurious warnings
- Add StructLit expr to AST + parser (uppercase-IDENT { ... } syntax), BuildStruct bytecode instruction + Struct value to runtime
- Add type_params to FnDef AST node and TypeParam variant to TypeExpr; parser parses <T, E> generics; type checker treats TypeParam as Unknown (universal type)
- Rewrite interpreter to support user-defined function calls via call stack (Frame + return_ip); dispatch_builtin handles print/println/log/print_err/__build_list__
- Fix engram_activate_search to unwrap { results: [...] } response envelope from /search; use std::net for sync HTTP to avoid reqwest dependency
- Add run-file command to CLI for single-file execution without el.toml
- Fix worktree engram-crypto path dep
This commit is contained in:
Will Anderson
2026-04-28 11:36:25 -05:00
parent 0a36a454f9
commit 977a2cd654
11 changed files with 532 additions and 34 deletions
+11 -1
View File
@@ -55,6 +55,8 @@ pub enum TypeExpr {
Optional(Box<TypeExpr>),
/// A function type: `fn(A, B) -> C`
Fn { params: Vec<TypeExpr>, return_type: Box<TypeExpr> },
/// A generic type parameter: `T`, `E` — used inside generic function signatures.
TypeParam(String),
}
// ── Patterns (for match arms) ─────────────────────────────────────────────────
@@ -103,6 +105,12 @@ pub enum Expr {
Path { segments: Vec<String> },
/// Index expression: `arr[0]`
Index { object: Box<Expr>, index: Box<Expr> },
/// Struct literal: `Point { x: 10, y: 20 }`
StructLit {
type_name: String,
fields: Vec<(String, Expr)>,
span: Span,
},
}
// ── Match arm ─────────────────────────────────────────────────────────────────
@@ -154,9 +162,11 @@ pub enum Stmt {
Return(Expr, Span),
/// A bare expression used as a statement (usually a call).
Expr(Expr, Span),
/// `fn name(params) -> ReturnType { body }`
/// `fn name<T, E>(params) -> ReturnType { body }`
FnDef {
name: String,
/// Generic type parameters, e.g. `["T", "E"]` for `fn foo<T, E>`.
type_params: Vec<String>,
params: Vec<Param>,
return_type: TypeExpr,
body: Vec<Stmt>,
+49 -13
View File
@@ -323,24 +323,42 @@ impl Parser {
fn parse_fn_def(&mut self, start: Span) -> Result<Stmt, ParseError> {
self.expect(&Token::Fn)?;
let (name, _) = self.expect_ident()?;
// Optional generic type parameters: `<T, E>`
let type_params = if self.eat(&Token::Lt) {
let mut tps = Vec::new();
while !matches!(self.peek(), Token::Gt | Token::Eof) {
let (tp, _) = self.expect_ident()?;
tps.push(tp);
if !self.eat(&Token::Comma) { break; }
}
self.expect(&Token::Gt)?;
tps
} else {
Vec::new()
};
self.expect(&Token::LParen)?;
let params = self.parse_param_list()?;
let params = self.parse_param_list_with_type_params(&type_params)?;
self.expect(&Token::RParen)?;
self.expect(&Token::Arrow)?;
let return_type = self.parse_type_expr()?;
let return_type = self.parse_type_expr_with_params(&type_params)?;
self.expect(&Token::LBrace)?;
let body = self.parse_block_body()?;
self.expect(&Token::RBrace)?;
Ok(Stmt::FnDef { name, params, return_type, body, span: start })
Ok(Stmt::FnDef { name, type_params, params, return_type, body, span: start })
}
#[allow(dead_code)]
fn parse_param_list(&mut self) -> Result<Vec<Param>, ParseError> {
self.parse_param_list_with_type_params(&[])
}
fn parse_param_list_with_type_params(&mut self, type_params: &[String]) -> Result<Vec<Param>, ParseError> {
let mut params = Vec::new();
while !matches!(self.peek(), Token::RParen | Token::Eof) {
let span = self.peek_span();
let (name, _) = self.expect_ident()?;
self.expect(&Token::Colon)?;
let type_ann = self.parse_type_expr()?;
let type_ann = self.parse_type_expr_with_params(type_params)?;
params.push(Param { name, type_ann, span });
if !self.eat(&Token::Comma) {
break;
@@ -405,17 +423,16 @@ impl Parser {
// ── Type expressions ──────────────────────────────────────────────────────
fn parse_type_expr(&mut self) -> Result<TypeExpr, ParseError> {
self.parse_type_expr_with_params(&[])
}
fn parse_type_expr_with_params(&mut self, type_params: &[String]) -> Result<TypeExpr, ParseError> {
let span = self.peek_span();
// Array type: [T]
if self.eat(&Token::LBracket) {
let inner = self.parse_type_expr()?;
let inner = self.parse_type_expr_with_params(type_params)?;
self.expect(&Token::RBracket)?;
let te = TypeExpr::Array(Box::new(inner));
// Optional array: [T]?
if self.eat(&Token::Not) {
// Not actually "!", we need "?" — but we don't have that token.
// We'll use Optional postfix via the Ident "?" — skip for now.
}
return Ok(te);
}
// Named type
@@ -431,14 +448,18 @@ impl Parser {
self.expect(&Token::LParen)?;
let mut params = Vec::new();
while !matches!(self.peek(), Token::RParen | Token::Eof) {
params.push(self.parse_type_expr()?);
params.push(self.parse_type_expr_with_params(type_params)?);
if !self.eat(&Token::Comma) { break; }
}
self.expect(&Token::RParen)?;
self.expect(&Token::Arrow)?;
let ret = self.parse_type_expr()?;
let ret = self.parse_type_expr_with_params(type_params)?;
return Ok(TypeExpr::Fn { params, return_type: Box::new(ret) });
}
// If the name is in the current generic type params list, emit TypeParam
if type_params.contains(&name) {
return Ok(TypeExpr::TypeParam(name));
}
Ok(TypeExpr::Named(name))
}
@@ -653,7 +674,7 @@ impl Parser {
Ok(Expr::If { cond: Box::new(cond), then: Box::new(then), else_ })
}
// Identifier — could be plain name or path (Foo::Bar)
// Identifier — could be plain name, path (Foo::Bar), or struct literal (Foo { ... })
Token::Ident(name) => {
self.advance();
// Check for path: Foo::Bar or Foo::Bar::Baz
@@ -664,6 +685,21 @@ impl Parser {
segments.push(seg);
}
Ok(Expr::Path { segments })
} else if matches!(self.peek(), Token::LBrace)
&& name.chars().next().map(|c| c.is_uppercase()).unwrap_or(false)
{
// Struct literal: TypeName { field: expr, ... }
self.advance(); // consume `{`
let mut fields = Vec::new();
while !matches!(self.peek(), Token::RBrace | Token::Eof) {
let (field_name, _) = self.expect_ident()?;
self.expect(&Token::Colon)?;
let field_expr = self.parse_expr()?;
fields.push((field_name, field_expr));
if !self.eat(&Token::Comma) { break; }
}
self.expect(&Token::RBrace)?;
Ok(Expr::StructLit { type_name: name, fields, span })
} else {
Ok(Expr::Ident(name))
}