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.
This commit is contained in:
+2
-1
@@ -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.
|
- **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`.
|
- **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).
|
- **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).
|
- **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
|
## 4. Special Forms and Evaluation logic
|
||||||
|
|||||||
@@ -243,11 +243,16 @@ impl Binder {
|
|||||||
else_br,
|
else_br,
|
||||||
} => {
|
} => {
|
||||||
let cond = self.bind(cond.as_ref(), ExprContext::Expression, diag);
|
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);
|
let then_br = self.bind(then_br.as_ref(), ctx, diag);
|
||||||
|
self.functions.last_mut().unwrap().pop_scope();
|
||||||
|
|
||||||
let mut else_br_bound = None;
|
let mut else_br_bound = None;
|
||||||
if let Some(e) = else_br {
|
if let Some(e) = else_br {
|
||||||
|
self.functions.last_mut().unwrap().push_scope();
|
||||||
else_br_bound = Some(Rc::new(self.bind(e, ctx, diag)));
|
else_br_bound = Some(Rc::new(self.bind(e, ctx, diag)));
|
||||||
|
self.functions.last_mut().unwrap().pop_scope();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.make_node(
|
self.make_node(
|
||||||
|
|||||||
@@ -143,6 +143,14 @@ impl Specializer {
|
|||||||
node.ty.clone(),
|
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()),
|
k => (k, node.ty.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -235,6 +235,11 @@ impl<'a> Lexer<'a> {
|
|||||||
{
|
{
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
while let Some(&c) = self.peek() {
|
while let Some(&c) = self.peek() {
|
||||||
|
if is_invisible(c) {
|
||||||
|
self.input.next();
|
||||||
|
self.col += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if predicate(c) {
|
if predicate(c) {
|
||||||
s.push(self.input.next().unwrap());
|
s.push(self.input.next().unwrap());
|
||||||
self.col += 1;
|
self.col += 1;
|
||||||
@@ -296,3 +301,21 @@ fn is_invisible(c: char) -> bool {
|
|||||||
_ => false,
|
_ => 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 = "ab";
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user