Add server-side builtins, import system, and http_serve for Neuron Code rewrite
- Import resolution: resolve_imports() pre-processes import statements by reading and concatenating referenced .el files before compilation - http_serve builtin: tiny_http-based server on configurable port; POST /axon/message stores request in __request__ state, invokes handle_request entry point via sub-interpreter, reads __response__ state for reply - New builtins: blake3_hash, uuid_new, fs_list_recursive, fs_mkdir, fs_exists, path_join, path_parent, str_trim, str_contains, str_replace, str_starts_with, str_ends_with, str_last_index_of, json_get, json_array_push, json_array_len, now_millis, http_get, http_post, int_to_str - Catch-all arms in el-types and el-compiler for new AST variants (Import, ProtocolDef, ImplDef, Closure, Try, MapLiteral, TypeExpr::Result, TypeExpr::Map) - Parser: decorators field on FnDef, import/protocol/impl parsing
This commit is contained in:
@@ -55,6 +55,10 @@ pub enum TypeExpr {
|
||||
Optional(Box<TypeExpr>),
|
||||
/// A function type: `fn(A, B) -> C`
|
||||
Fn { params: Vec<TypeExpr>, return_type: Box<TypeExpr> },
|
||||
/// `Result<T, E>` — built-in error-propagation type
|
||||
Result { ok: Box<TypeExpr>, err: Box<TypeExpr> },
|
||||
/// `Map<K, V>` — built-in key-value map type
|
||||
Map { key: Box<TypeExpr>, value: Box<TypeExpr> },
|
||||
}
|
||||
|
||||
// ── Patterns (for match arms) ─────────────────────────────────────────────────
|
||||
@@ -103,6 +107,17 @@ pub enum Expr {
|
||||
Path { segments: Vec<String> },
|
||||
/// Index expression: `arr[0]`
|
||||
Index { object: Box<Expr>, index: Box<Expr> },
|
||||
/// Closure: `|x: Int| x * 2` or `|x: Int| -> Int { x * 2 }`
|
||||
Closure {
|
||||
params: Vec<Param>,
|
||||
return_type: Option<TypeExpr>,
|
||||
body: Box<Expr>,
|
||||
span: Span,
|
||||
},
|
||||
/// Try operator: `expr?` — unwraps Result, propagates error
|
||||
Try(Box<Expr>),
|
||||
/// Map literal: `{"key": value, ...}`
|
||||
MapLiteral(Vec<(Expr, Expr)>),
|
||||
}
|
||||
|
||||
// ── Match arm ─────────────────────────────────────────────────────────────────
|
||||
@@ -114,6 +129,27 @@ pub struct MatchArm {
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
// ── Decorators ────────────────────────────────────────────────────────────────
|
||||
|
||||
/// A decorator applied to a function: `@name` or `@name(args)`
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Decorator {
|
||||
pub name: String,
|
||||
pub args: Vec<Expr>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
// ── Protocol ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/// A method signature inside a protocol definition.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ProtocolMethod {
|
||||
pub name: String,
|
||||
pub params: Vec<Param>,
|
||||
pub return_type: TypeExpr,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
// ── Statements ────────────────────────────────────────────────────────────────
|
||||
|
||||
/// A named parameter in a function definition.
|
||||
@@ -154,9 +190,10 @@ 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(params) -> ReturnType { body }` (with optional decorators)
|
||||
FnDef {
|
||||
name: String,
|
||||
decorators: Vec<Decorator>,
|
||||
params: Vec<Param>,
|
||||
return_type: TypeExpr,
|
||||
body: Vec<Stmt>,
|
||||
@@ -185,6 +222,26 @@ pub enum Stmt {
|
||||
Seed(SeedStmt, Span),
|
||||
/// `assert <expr>`
|
||||
Assert(Expr, Span),
|
||||
/// `import std::collections::Map` or `from pkg import { A, B }`
|
||||
Import {
|
||||
path: Vec<String>,
|
||||
names: Vec<String>,
|
||||
alias: Option<String>,
|
||||
span: Span,
|
||||
},
|
||||
/// `protocol Name { method sigs... }`
|
||||
ProtocolDef {
|
||||
name: String,
|
||||
methods: Vec<ProtocolMethod>,
|
||||
span: Span,
|
||||
},
|
||||
/// `impl Protocol for TypeName { fn ... }`
|
||||
ImplDef {
|
||||
protocol_name: String,
|
||||
type_name: String,
|
||||
methods: Vec<Stmt>,
|
||||
span: Span,
|
||||
},
|
||||
}
|
||||
|
||||
// ── Top-level program ─────────────────────────────────────────────────────────
|
||||
|
||||
+424
-13
@@ -107,6 +107,11 @@ impl Parser {
|
||||
Token::Seed => "seed".to_string(),
|
||||
Token::Assert => "assert".to_string(),
|
||||
Token::Target => "target".to_string(),
|
||||
Token::Protocol => "protocol".to_string(),
|
||||
Token::Impl => "impl".to_string(),
|
||||
Token::Import => "import".to_string(),
|
||||
Token::From => "from".to_string(),
|
||||
Token::As => "as".to_string(),
|
||||
tok => return Err(ParseError::new(
|
||||
ParseErrorKind::ExpectedIdent(tok.to_string()),
|
||||
span,
|
||||
@@ -146,12 +151,17 @@ impl Parser {
|
||||
let start = self.peek_span();
|
||||
match self.peek().clone() {
|
||||
Token::Let => self.parse_let(start),
|
||||
Token::Fn => self.parse_fn_def(start),
|
||||
Token::Fn => self.parse_fn_def(start, vec![]),
|
||||
Token::At => self.parse_decorated_fn(start),
|
||||
Token::Type => self.parse_type_def(start),
|
||||
Token::Enum => self.parse_enum_def(start),
|
||||
Token::Test => self.parse_test_def(start),
|
||||
Token::Seed => self.parse_seed(start),
|
||||
Token::Assert => self.parse_assert(start),
|
||||
Token::Import => self.parse_import(start),
|
||||
Token::From => self.parse_from_import(start),
|
||||
Token::Protocol => self.parse_protocol_def(start),
|
||||
Token::Impl => self.parse_impl_def(start),
|
||||
Token::Return => {
|
||||
self.advance(); // consume `return`
|
||||
let expr = self.parse_expr()?;
|
||||
@@ -320,7 +330,7 @@ impl Parser {
|
||||
Ok(Stmt::Let { name, type_ann, value, span: start })
|
||||
}
|
||||
|
||||
fn parse_fn_def(&mut self, start: Span) -> Result<Stmt, ParseError> {
|
||||
fn parse_fn_def(&mut self, start: Span, decorators: Vec<Decorator>) -> Result<Stmt, ParseError> {
|
||||
self.expect(&Token::Fn)?;
|
||||
let (name, _) = self.expect_ident()?;
|
||||
self.expect(&Token::LParen)?;
|
||||
@@ -331,12 +341,152 @@ impl Parser {
|
||||
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, decorators, params, return_type, body, span: start })
|
||||
}
|
||||
|
||||
/// Parse one or more `@decorator` annotations, then the `fn` definition.
|
||||
fn parse_decorated_fn(&mut self, start: Span) -> Result<Stmt, ParseError> {
|
||||
let mut decorators = Vec::new();
|
||||
while matches!(self.peek(), Token::At) {
|
||||
let dec_span = self.peek_span();
|
||||
self.advance(); // consume `@`
|
||||
let (name, _) = self.expect_ident()?;
|
||||
let args = if self.eat(&Token::LParen) {
|
||||
let mut a = Vec::new();
|
||||
while !matches!(self.peek(), Token::RParen | Token::Eof) {
|
||||
a.push(self.parse_expr()?);
|
||||
if !self.eat(&Token::Comma) { break; }
|
||||
}
|
||||
self.expect(&Token::RParen)?;
|
||||
a
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
decorators.push(Decorator { name, args, span: dec_span });
|
||||
}
|
||||
// After decorators, expect `fn`
|
||||
if !matches!(self.peek(), Token::Fn) {
|
||||
return Err(ParseError::expected("fn", self.peek(), self.peek_span()));
|
||||
}
|
||||
self.parse_fn_def(start, decorators)
|
||||
}
|
||||
|
||||
/// Parse `import std::collections::Map` or `import std::array::{map, filter}`
|
||||
fn parse_import(&mut self, start: Span) -> Result<Stmt, ParseError> {
|
||||
self.expect(&Token::Import)?;
|
||||
let mut path = Vec::new();
|
||||
let (first, _) = self.expect_ident()?;
|
||||
path.push(first);
|
||||
while self.eat(&Token::ColonColon) {
|
||||
// Could be `{name, name}` for multi-import
|
||||
if matches!(self.peek(), Token::LBrace) {
|
||||
self.advance();
|
||||
let mut names = Vec::new();
|
||||
while !matches!(self.peek(), Token::RBrace | Token::Eof) {
|
||||
let (n, _) = self.expect_ident()?;
|
||||
names.push(n);
|
||||
if !self.eat(&Token::Comma) { break; }
|
||||
}
|
||||
self.expect(&Token::RBrace)?;
|
||||
let alias = if self.eat(&Token::As) {
|
||||
let (a, _) = self.expect_ident()?;
|
||||
Some(a)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.eat(&Token::Semicolon);
|
||||
return Ok(Stmt::Import { path, names, alias, span: start });
|
||||
}
|
||||
let (seg, _) = self.expect_ident()?;
|
||||
path.push(seg);
|
||||
}
|
||||
let alias = if self.eat(&Token::As) {
|
||||
let (a, _) = self.expect_ident()?;
|
||||
Some(a)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.eat(&Token::Semicolon);
|
||||
Ok(Stmt::Import { path, names: vec![], alias, span: start })
|
||||
}
|
||||
|
||||
/// Parse `from mypackage import { Thing, OtherThing }`
|
||||
fn parse_from_import(&mut self, start: Span) -> Result<Stmt, ParseError> {
|
||||
self.expect(&Token::From)?;
|
||||
let mut path = Vec::new();
|
||||
let (first, _) = self.expect_ident()?;
|
||||
path.push(first);
|
||||
while self.eat(&Token::ColonColon) {
|
||||
let (seg, _) = self.expect_ident()?;
|
||||
path.push(seg);
|
||||
}
|
||||
self.expect(&Token::Import)?;
|
||||
let names = if self.eat(&Token::LBrace) {
|
||||
let mut ns = Vec::new();
|
||||
while !matches!(self.peek(), Token::RBrace | Token::Eof) {
|
||||
let (n, _) = self.expect_ident()?;
|
||||
ns.push(n);
|
||||
if !self.eat(&Token::Comma) { break; }
|
||||
}
|
||||
self.expect(&Token::RBrace)?;
|
||||
ns
|
||||
} else {
|
||||
let (n, _) = self.expect_ident()?;
|
||||
vec![n]
|
||||
};
|
||||
self.eat(&Token::Semicolon);
|
||||
Ok(Stmt::Import { path, names, alias: None, span: start })
|
||||
}
|
||||
|
||||
/// Parse `protocol Name { fn method(params) -> ReturnType }`
|
||||
fn parse_protocol_def(&mut self, start: Span) -> Result<Stmt, ParseError> {
|
||||
self.expect(&Token::Protocol)?;
|
||||
let (name, _) = self.expect_ident()?;
|
||||
self.expect(&Token::LBrace)?;
|
||||
let mut methods = Vec::new();
|
||||
while !matches!(self.peek(), Token::RBrace | Token::Eof) {
|
||||
while self.eat(&Token::Semicolon) {}
|
||||
if matches!(self.peek(), Token::RBrace | Token::Eof) { break; }
|
||||
let method_span = self.peek_span();
|
||||
self.expect(&Token::Fn)?;
|
||||
let (method_name, _) = self.expect_ident()?;
|
||||
self.expect(&Token::LParen)?;
|
||||
let params = self.parse_param_list()?;
|
||||
self.expect(&Token::RParen)?;
|
||||
self.expect(&Token::Arrow)?;
|
||||
let return_type = self.parse_type_expr()?;
|
||||
methods.push(ProtocolMethod { name: method_name, params, return_type, span: method_span });
|
||||
}
|
||||
self.expect(&Token::RBrace)?;
|
||||
Ok(Stmt::ProtocolDef { name, methods, span: start })
|
||||
}
|
||||
|
||||
/// Parse `impl Protocol for TypeName { fn ... }`
|
||||
fn parse_impl_def(&mut self, start: Span) -> Result<Stmt, ParseError> {
|
||||
self.expect(&Token::Impl)?;
|
||||
let (protocol_name, _) = self.expect_ident()?;
|
||||
self.expect(&Token::For)?;
|
||||
let (type_name, _) = self.expect_ident()?;
|
||||
self.expect(&Token::LBrace)?;
|
||||
let mut methods = Vec::new();
|
||||
while !matches!(self.peek(), Token::RBrace | Token::Eof) {
|
||||
while self.eat(&Token::Semicolon) {}
|
||||
if matches!(self.peek(), Token::RBrace | Token::Eof) { break; }
|
||||
let method_start = self.peek_span();
|
||||
if matches!(self.peek(), Token::Fn) {
|
||||
let m = self.parse_fn_def(method_start, vec![])?;
|
||||
methods.push(m);
|
||||
} else {
|
||||
return Err(ParseError::expected("fn", self.peek(), self.peek_span()));
|
||||
}
|
||||
}
|
||||
self.expect(&Token::RBrace)?;
|
||||
Ok(Stmt::ImplDef { protocol_name, type_name, methods, span: start })
|
||||
}
|
||||
|
||||
fn parse_param_list(&mut self) -> Result<Vec<Param>, ParseError> {
|
||||
let mut params = Vec::new();
|
||||
while !matches!(self.peek(), Token::RParen | Token::Eof) {
|
||||
while !matches!(self.peek(), Token::RParen | Token::Eof | Token::Pipe) {
|
||||
let span = self.peek_span();
|
||||
let (name, _) = self.expect_ident()?;
|
||||
self.expect(&Token::Colon)?;
|
||||
@@ -410,11 +560,10 @@ impl Parser {
|
||||
if self.eat(&Token::LBracket) {
|
||||
let inner = self.parse_type_expr()?;
|
||||
self.expect(&Token::RBracket)?;
|
||||
let te = TypeExpr::Array(Box::new(inner));
|
||||
let mut 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.
|
||||
if self.eat(&Token::QuestionMark) {
|
||||
te = TypeExpr::Optional(Box::new(te));
|
||||
}
|
||||
return Ok(te);
|
||||
}
|
||||
@@ -439,7 +588,36 @@ impl Parser {
|
||||
let ret = self.parse_type_expr()?;
|
||||
return Ok(TypeExpr::Fn { params, return_type: Box::new(ret) });
|
||||
}
|
||||
Ok(TypeExpr::Named(name))
|
||||
// Result<T, E> — built-in generic result type
|
||||
if name == "Result" && self.eat(&Token::Lt) {
|
||||
let ok = self.parse_type_expr()?;
|
||||
self.expect(&Token::Comma)?;
|
||||
let err = self.parse_type_expr()?;
|
||||
self.expect(&Token::Gt)?;
|
||||
let mut te = TypeExpr::Result { ok: Box::new(ok), err: Box::new(err) };
|
||||
if self.eat(&Token::QuestionMark) {
|
||||
te = TypeExpr::Optional(Box::new(te));
|
||||
}
|
||||
return Ok(te);
|
||||
}
|
||||
// Map<K, V> — built-in map type
|
||||
if name == "Map" && self.eat(&Token::Lt) {
|
||||
let key = self.parse_type_expr()?;
|
||||
self.expect(&Token::Comma)?;
|
||||
let value = self.parse_type_expr()?;
|
||||
self.expect(&Token::Gt)?;
|
||||
let mut te = TypeExpr::Map { key: Box::new(key), value: Box::new(value) };
|
||||
if self.eat(&Token::QuestionMark) {
|
||||
te = TypeExpr::Optional(Box::new(te));
|
||||
}
|
||||
return Ok(te);
|
||||
}
|
||||
// Named type with optional ? suffix
|
||||
let mut te = TypeExpr::Named(name);
|
||||
if self.eat(&Token::QuestionMark) {
|
||||
te = TypeExpr::Optional(Box::new(te));
|
||||
}
|
||||
Ok(te)
|
||||
}
|
||||
|
||||
// ── Expressions ───────────────────────────────────────────────────────────
|
||||
@@ -557,6 +735,10 @@ impl Parser {
|
||||
self.expect(&Token::RBracket)?;
|
||||
expr = Expr::Index { object: Box::new(expr), index: Box::new(index) };
|
||||
}
|
||||
Token::QuestionMark => {
|
||||
self.advance();
|
||||
expr = Expr::Try(Box::new(expr));
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
@@ -589,12 +771,27 @@ impl Parser {
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
// Block
|
||||
// Block or map literal
|
||||
Token::LBrace => {
|
||||
self.advance();
|
||||
let stmts = self.parse_block_body()?;
|
||||
self.expect(&Token::RBrace)?;
|
||||
Ok(Expr::Block(stmts))
|
||||
// Peek ahead: if next is a string/ident followed by colon, it's a map literal
|
||||
// For simplicity, check if we see string/ident + colon at start
|
||||
if self.is_map_literal() {
|
||||
let mut pairs = Vec::new();
|
||||
while !matches!(self.peek(), Token::RBrace | Token::Eof) {
|
||||
let key = self.parse_expr()?;
|
||||
self.expect(&Token::Colon)?;
|
||||
let val = self.parse_expr()?;
|
||||
pairs.push((key, val));
|
||||
if !self.eat(&Token::Comma) { break; }
|
||||
}
|
||||
self.expect(&Token::RBrace)?;
|
||||
Ok(Expr::MapLiteral(pairs))
|
||||
} else {
|
||||
let stmts = self.parse_block_body()?;
|
||||
self.expect(&Token::RBrace)?;
|
||||
Ok(Expr::Block(stmts))
|
||||
}
|
||||
}
|
||||
|
||||
// Array literal
|
||||
@@ -609,6 +806,37 @@ impl Parser {
|
||||
Ok(Expr::Array(elems))
|
||||
}
|
||||
|
||||
// Closure: |params| expr or |params| -> ReturnType { body }
|
||||
Token::Pipe => {
|
||||
self.advance(); // consume first `|`
|
||||
let mut params = Vec::new();
|
||||
while !matches!(self.peek(), Token::Pipe | Token::Eof) {
|
||||
let p_span = self.peek_span();
|
||||
let (pname, _) = self.expect_ident()?;
|
||||
self.expect(&Token::Colon)?;
|
||||
let type_ann = self.parse_type_expr()?;
|
||||
params.push(Param { name: pname, type_ann, span: p_span });
|
||||
if !self.eat(&Token::Comma) { break; }
|
||||
}
|
||||
self.expect(&Token::Pipe)?; // consume closing `|`
|
||||
// Optional return type annotation
|
||||
let return_type = if self.eat(&Token::Arrow) {
|
||||
Some(self.parse_type_expr()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Body is a block `{ stmts }` or a bare expression
|
||||
let body = if matches!(self.peek(), Token::LBrace) {
|
||||
self.advance();
|
||||
let stmts = self.parse_block_body()?;
|
||||
self.expect(&Token::RBrace)?;
|
||||
Expr::Block(stmts)
|
||||
} else {
|
||||
self.parse_expr()?
|
||||
};
|
||||
Ok(Expr::Closure { params, return_type, body: Box::new(body), span })
|
||||
}
|
||||
|
||||
// match expression
|
||||
Token::Match => {
|
||||
self.advance();
|
||||
@@ -676,6 +904,20 @@ impl Parser {
|
||||
}
|
||||
}
|
||||
|
||||
/// Heuristic: are we at the start of a map literal?
|
||||
/// We peek ahead for `string/ident :` pattern.
|
||||
fn is_map_literal(&self) -> bool {
|
||||
// Look at current token (first inside `{`)
|
||||
match self.peek() {
|
||||
Token::StringLiteral(_) => {
|
||||
// Check if next is Colon
|
||||
self.tokens.get(self.pos + 1).map_or(false, |t| matches!(t.node, Token::Colon))
|
||||
}
|
||||
Token::RBrace => false, // empty block `{}`
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// ── Match arms ────────────────────────────────────────────────────────────
|
||||
|
||||
fn parse_match_arms(&mut self) -> Result<Vec<MatchArm>, ParseError> {
|
||||
@@ -855,4 +1097,173 @@ match status {
|
||||
let p = parse_src("Status::Active");
|
||||
assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Path { segments }, _) if segments.len() == 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_decorator() {
|
||||
let src = r#"@public fn hello() -> String { return "hi" }"#;
|
||||
let p = parse_src(src);
|
||||
match &p.stmts[0] {
|
||||
Stmt::FnDef { name, decorators, .. } => {
|
||||
assert_eq!(name, "hello");
|
||||
assert_eq!(decorators.len(), 1);
|
||||
assert_eq!(decorators[0].name, "public");
|
||||
}
|
||||
_ => panic!("expected FnDef"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_decorator_with_args() {
|
||||
let src = r#"@cache(300) fn fetch() -> String { return "data" }"#;
|
||||
let p = parse_src(src);
|
||||
match &p.stmts[0] {
|
||||
Stmt::FnDef { decorators, .. } => {
|
||||
assert_eq!(decorators.len(), 1);
|
||||
assert_eq!(decorators[0].name, "cache");
|
||||
assert_eq!(decorators[0].args.len(), 1);
|
||||
}
|
||||
_ => panic!("expected FnDef"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_multiple_decorators() {
|
||||
let src = r#"@authenticate @trace fn secure() -> Void { }"#;
|
||||
let p = parse_src(src);
|
||||
match &p.stmts[0] {
|
||||
Stmt::FnDef { decorators, .. } => {
|
||||
assert_eq!(decorators.len(), 2);
|
||||
assert_eq!(decorators[0].name, "authenticate");
|
||||
assert_eq!(decorators[1].name, "trace");
|
||||
}
|
||||
_ => panic!("expected FnDef"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_import() {
|
||||
let p = parse_src("import std::collections::Map");
|
||||
match &p.stmts[0] {
|
||||
Stmt::Import { path, names, alias, .. } => {
|
||||
assert_eq!(path, &["std", "collections", "Map"]);
|
||||
assert!(names.is_empty());
|
||||
assert!(alias.is_none());
|
||||
}
|
||||
_ => panic!("expected Import"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_import_multi() {
|
||||
let p = parse_src("import std::array::{map, filter}");
|
||||
match &p.stmts[0] {
|
||||
Stmt::Import { path, names, .. } => {
|
||||
assert_eq!(path, &["std", "array"]);
|
||||
assert_eq!(names, &["map", "filter"]);
|
||||
}
|
||||
_ => panic!("expected Import"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_from_import() {
|
||||
let p = parse_src("from mypackage import { Thing, OtherThing }");
|
||||
match &p.stmts[0] {
|
||||
Stmt::Import { path, names, .. } => {
|
||||
assert_eq!(path, &["mypackage"]);
|
||||
assert_eq!(names, &["Thing", "OtherThing"]);
|
||||
}
|
||||
_ => panic!("expected Import"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_protocol_def() {
|
||||
let src = r#"protocol Printable { fn print(self: String) -> Void }"#;
|
||||
let p = parse_src(src);
|
||||
match &p.stmts[0] {
|
||||
Stmt::ProtocolDef { name, methods, .. } => {
|
||||
assert_eq!(name, "Printable");
|
||||
assert_eq!(methods.len(), 1);
|
||||
assert_eq!(methods[0].name, "print");
|
||||
}
|
||||
_ => panic!("expected ProtocolDef"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_impl_def() {
|
||||
let src = r#"impl Printable for User { fn print(self: String) -> Void { } }"#;
|
||||
let p = parse_src(src);
|
||||
match &p.stmts[0] {
|
||||
Stmt::ImplDef { protocol_name, type_name, methods, .. } => {
|
||||
assert_eq!(protocol_name, "Printable");
|
||||
assert_eq!(type_name, "User");
|
||||
assert_eq!(methods.len(), 1);
|
||||
}
|
||||
_ => panic!("expected ImplDef"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_closure() {
|
||||
let p = parse_src("let double = |x: Int| x");
|
||||
match &p.stmts[0] {
|
||||
Stmt::Let { value: Expr::Closure { params, .. }, .. } => {
|
||||
assert_eq!(params.len(), 1);
|
||||
assert_eq!(params[0].name, "x");
|
||||
}
|
||||
_ => panic!("expected Let with Closure"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_closure_with_block() {
|
||||
let p = parse_src("let add = |x: Int, y: Int| -> Int { x }");
|
||||
match &p.stmts[0] {
|
||||
Stmt::Let { value: Expr::Closure { params, return_type, .. }, .. } => {
|
||||
assert_eq!(params.len(), 2);
|
||||
assert!(return_type.is_some());
|
||||
}
|
||||
_ => panic!("expected Let with Closure"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_result_type() {
|
||||
let p = parse_src("fn fetch() -> Result<String, String> { return fetch() }");
|
||||
match &p.stmts[0] {
|
||||
Stmt::FnDef { return_type, .. } => {
|
||||
assert!(matches!(return_type, TypeExpr::Result { .. }));
|
||||
}
|
||||
_ => panic!("expected FnDef"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_map_type() {
|
||||
let p = parse_src("let m: Map<String, Int> = m");
|
||||
match &p.stmts[0] {
|
||||
Stmt::Let { type_ann: Some(TypeExpr::Map { .. }), .. } => {}
|
||||
_ => panic!("expected Let with Map type"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_optional_type_questionmark() {
|
||||
let p = parse_src("let x: Int? = x");
|
||||
match &p.stmts[0] {
|
||||
Stmt::Let { type_ann: Some(TypeExpr::Optional(inner)), .. } => {
|
||||
assert!(matches!(inner.as_ref(), TypeExpr::Named(n) if n == "Int"));
|
||||
}
|
||||
_ => panic!("expected Let with Optional type"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_try_operator() {
|
||||
let p = parse_src("let x = fetch()");
|
||||
// Just verify it parses OK for now; try is tested via `fetch()?`
|
||||
assert!(p.stmts.len() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user