From 903c77a665f789dfd942648fc36d4f3d3c15526a Mon Sep 17 00:00:00 2001 From: Brummel Date: Thu, 26 Mar 2026 16:04:45 +0100 Subject: [PATCH] Update series syntax and binder scope handling Refine the BNF for `series` to explicitly include the `lookback_limit` and `schema`. Introduce scope management within the `bind` function for `if/else` branches to ensure correct context handling during compilation. Add `Again` and `GetField` node kinds to the `Specializer`. Improve lexer to ignore invisible characters within identifiers, demonstrated by a new test case. --- docs/BNF.md | 3 ++- src/ast/compiler/binder.rs | 5 +++++ src/ast/compiler/specializer.rs | 8 ++++++++ src/ast/lexer.rs | 23 +++++++++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/docs/BNF.md b/docs/BNF.md index 7c68267..62ec763 100644 --- a/docs/BNF.md +++ b/docs/BNF.md @@ -92,7 +92,8 @@ This document defines the syntax (BNF) and semantics of the Myc language, a Lisp - **Vectors (Tuples):** Enclosed in square brackets `[1 2 3]`. Evaluates to a vector/tuple structure. - **Records:** Enclosed in curly braces with keyword keys and any expression as values `{:id 101 :name "Alice"}`. They provide O(1) field access using internal memory layouts. Structural equality `(= {:a 1} {:a 1})` is `true`. - **Series:** A core concept for financial analysis. Series are "infinite" queues with a maximum length (lookback). - - They are created via the `(series ...)` function specifying a record layout (e.g., `(series {:price :float :volume :int})`). + - They are created via the `(series lookback_limit schema)` function. The `schema` can be a record layout (e.g., `{:price :float}`) or a type keyword (e.g., `:float`). + - Example: `(series 100 {:price :float :volume :int})`. - **Indexing:** You access items in a series by calling it or an extracted field like a function with an integer index: `(my_series 0)`. **Crucially, index `0` represents the most recently pushed item.** Index `1` is the second most recent, and so on (lookback indexing). ## 4. Special Forms and Evaluation logic diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index 355f3fd..ac0f06f 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -243,11 +243,16 @@ impl Binder { else_br, } => { let cond = self.bind(cond.as_ref(), ExprContext::Expression, diag); + + self.functions.last_mut().unwrap().push_scope(); let then_br = self.bind(then_br.as_ref(), ctx, diag); + self.functions.last_mut().unwrap().pop_scope(); let mut else_br_bound = None; if let Some(e) = else_br { + self.functions.last_mut().unwrap().push_scope(); else_br_bound = Some(Rc::new(self.bind(e, ctx, diag))); + self.functions.last_mut().unwrap().pop_scope(); } self.make_node( diff --git a/src/ast/compiler/specializer.rs b/src/ast/compiler/specializer.rs index 9d54469..53d2bbf 100644 --- a/src/ast/compiler/specializer.rs +++ b/src/ast/compiler/specializer.rs @@ -143,6 +143,14 @@ impl Specializer { node.ty.clone(), ) } + NodeKind::Again { args } => { + let args = Rc::new(self.visit_node(args.as_ref().clone())); + (NodeKind::Again { args }, node.ty.clone()) + } + NodeKind::GetField { rec, field } => { + let rec = Rc::new(self.visit_node(rec.as_ref().clone())); + (NodeKind::GetField { rec, field }, node.ty.clone()) + } k => (k, node.ty.clone()), }; diff --git a/src/ast/lexer.rs b/src/ast/lexer.rs index 062d0f5..170a489 100644 --- a/src/ast/lexer.rs +++ b/src/ast/lexer.rs @@ -235,6 +235,11 @@ impl<'a> Lexer<'a> { { let mut s = String::new(); while let Some(&c) = self.peek() { + if is_invisible(c) { + self.input.next(); + self.col += 1; + continue; + } if predicate(c) { s.push(self.input.next().unwrap()); self.col += 1; @@ -296,3 +301,21 @@ fn is_invisible(c: char) -> bool { _ => false, } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_lex_invisible_chars() { + // U+200B Zero Width Space should be ignored inside identifiers + let source = "a​b"; + let mut lexer = Lexer::new(source); + let token = lexer.next_token().unwrap(); + if let TokenKind::Identifier(id) = token.kind { + assert_eq!(id.as_ref(), "ab"); + } else { + panic!("Expected identifier, got {:?}", token.kind); + } + } +}