1630 lines
54 KiB
Rust
1630 lines
54 KiB
Rust
use std::collections::HashMap;
|
|
|
|
// --- Values ---
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum Value {
|
|
Number(f64),
|
|
Bool(bool),
|
|
Str(String),
|
|
Array(Vec<Value>),
|
|
Void,
|
|
Error(String),
|
|
}
|
|
|
|
impl Value {
|
|
pub fn display(&self) -> String {
|
|
match self {
|
|
Value::Number(n) => format_number(*n),
|
|
Value::Bool(b) => b.to_string(),
|
|
Value::Str(s) => s.clone(),
|
|
Value::Array(items) => {
|
|
let inner: Vec<String> = items.iter().map(|v| match v {
|
|
Value::Str(s) => format!("\"{}\"", s),
|
|
other => other.display(),
|
|
}).collect();
|
|
format!("[{}]", inner.join(", "))
|
|
}
|
|
Value::Void => String::new(),
|
|
Value::Error(e) => format!("error: {}", e),
|
|
}
|
|
}
|
|
|
|
pub fn is_error(&self) -> bool {
|
|
matches!(self, Value::Error(_))
|
|
}
|
|
|
|
fn truthy(&self) -> bool {
|
|
match self {
|
|
Value::Bool(b) => *b,
|
|
Value::Number(n) => *n != 0.0,
|
|
Value::Str(s) => !s.is_empty(),
|
|
Value::Array(a) => !a.is_empty(),
|
|
Value::Void => false,
|
|
Value::Error(_) => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn format_number(n: f64) -> String {
|
|
if n == n.trunc() && n.abs() < 1e15 {
|
|
format!("{}", n as i64)
|
|
} else {
|
|
let s = format!("{:.10}", n);
|
|
let s = s.trim_end_matches('0');
|
|
let s = s.trim_end_matches('.');
|
|
s.to_string()
|
|
}
|
|
}
|
|
|
|
// --- Tokens ---
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
enum Token {
|
|
Number(f64),
|
|
Str(String),
|
|
Bool(bool),
|
|
Ident(String),
|
|
Plus,
|
|
Minus,
|
|
Star,
|
|
Slash,
|
|
Percent,
|
|
Caret,
|
|
LParen,
|
|
RParen,
|
|
LBrace,
|
|
RBrace,
|
|
LBracket,
|
|
RBracket,
|
|
Comma,
|
|
Eq,
|
|
EqEq,
|
|
BangEq,
|
|
Lt,
|
|
Gt,
|
|
LtEq,
|
|
GtEq,
|
|
And,
|
|
Or,
|
|
Bang,
|
|
Colon,
|
|
DotDot,
|
|
Let,
|
|
While,
|
|
Fn,
|
|
If,
|
|
Else,
|
|
For,
|
|
In,
|
|
Return,
|
|
Newline,
|
|
Eof,
|
|
}
|
|
|
|
fn tokenize(input: &str) -> Result<Vec<Token>, String> {
|
|
let mut tokens = Vec::new();
|
|
let chars: Vec<char> = input.chars().collect();
|
|
let len = chars.len();
|
|
let mut i = 0;
|
|
|
|
while i < len {
|
|
let c = chars[i];
|
|
match c {
|
|
' ' | '\t' | '\r' => { i += 1; }
|
|
'\n' => { tokens.push(Token::Newline); i += 1; }
|
|
'+' => { tokens.push(Token::Plus); i += 1; }
|
|
'-' => {
|
|
// negative number literal: only if preceded by operator/open/start/newline
|
|
if i + 1 < len && (chars[i + 1].is_ascii_digit() || chars[i + 1] == '.') {
|
|
let can_be_neg = if tokens.is_empty() {
|
|
true
|
|
} else {
|
|
matches!(tokens.last(), Some(
|
|
Token::Plus | Token::Minus | Token::Star | Token::Slash |
|
|
Token::Percent | Token::Caret | Token::LParen | Token::LBracket |
|
|
Token::Comma | Token::Eq | Token::EqEq | Token::BangEq |
|
|
Token::Lt | Token::Gt | Token::LtEq | Token::GtEq |
|
|
Token::And | Token::Or | Token::Bang | Token::Newline |
|
|
Token::Colon
|
|
))
|
|
};
|
|
if can_be_neg {
|
|
let start = i;
|
|
i += 1;
|
|
while i < len && (chars[i].is_ascii_digit() || (chars[i] == '.' && !(i + 1 < len && chars[i + 1] == '.'))) {
|
|
i += 1;
|
|
}
|
|
let s: String = chars[start..i].iter().collect();
|
|
let n: f64 = s.parse().map_err(|_| format!("invalid number: {}", s))?;
|
|
tokens.push(Token::Number(n));
|
|
} else {
|
|
tokens.push(Token::Minus);
|
|
i += 1;
|
|
}
|
|
} else {
|
|
tokens.push(Token::Minus);
|
|
i += 1;
|
|
}
|
|
}
|
|
'*' => { tokens.push(Token::Star); i += 1; }
|
|
'/' => {
|
|
if i + 1 < len && chars[i + 1] == '/' {
|
|
while i < len && chars[i] != '\n' { i += 1; }
|
|
} else {
|
|
tokens.push(Token::Slash);
|
|
i += 1;
|
|
}
|
|
}
|
|
'%' => { tokens.push(Token::Percent); i += 1; }
|
|
'^' => { tokens.push(Token::Caret); i += 1; }
|
|
'(' => { tokens.push(Token::LParen); i += 1; }
|
|
')' => { tokens.push(Token::RParen); i += 1; }
|
|
'{' => { tokens.push(Token::LBrace); i += 1; }
|
|
'}' => { tokens.push(Token::RBrace); i += 1; }
|
|
'[' => { tokens.push(Token::LBracket); i += 1; }
|
|
']' => { tokens.push(Token::RBracket); i += 1; }
|
|
',' => { tokens.push(Token::Comma); i += 1; }
|
|
':' => { tokens.push(Token::Colon); i += 1; }
|
|
'.' if i + 1 < len && chars[i + 1] == '.' => {
|
|
tokens.push(Token::DotDot); i += 2;
|
|
}
|
|
'!' => {
|
|
if i + 1 < len && chars[i + 1] == '=' {
|
|
tokens.push(Token::BangEq); i += 2;
|
|
} else {
|
|
tokens.push(Token::Bang); i += 1;
|
|
}
|
|
}
|
|
'=' => {
|
|
if i + 1 < len && chars[i + 1] == '=' {
|
|
tokens.push(Token::EqEq); i += 2;
|
|
} else {
|
|
tokens.push(Token::Eq); i += 1;
|
|
}
|
|
}
|
|
'<' => {
|
|
if i + 1 < len && chars[i + 1] == '=' {
|
|
tokens.push(Token::LtEq); i += 2;
|
|
} else {
|
|
tokens.push(Token::Lt); i += 1;
|
|
}
|
|
}
|
|
'>' => {
|
|
if i + 1 < len && chars[i + 1] == '=' {
|
|
tokens.push(Token::GtEq); i += 2;
|
|
} else {
|
|
tokens.push(Token::Gt); i += 1;
|
|
}
|
|
}
|
|
'&' => {
|
|
if i + 1 < len && chars[i + 1] == '&' {
|
|
tokens.push(Token::And); i += 2;
|
|
} else {
|
|
return Err("unexpected '&', did you mean '&&'?".into());
|
|
}
|
|
}
|
|
'|' => {
|
|
if i + 1 < len && chars[i + 1] == '|' {
|
|
tokens.push(Token::Or); i += 2;
|
|
} else {
|
|
return Err("unexpected '|', did you mean '||'?".into());
|
|
}
|
|
}
|
|
'"' => {
|
|
i += 1;
|
|
let mut s = String::new();
|
|
while i < len && chars[i] != '"' {
|
|
if chars[i] == '\\' && i + 1 < len {
|
|
i += 1;
|
|
match chars[i] {
|
|
'n' => s.push('\n'),
|
|
't' => s.push('\t'),
|
|
'\\' => s.push('\\'),
|
|
'"' => s.push('"'),
|
|
other => { s.push('\\'); s.push(other); }
|
|
}
|
|
} else {
|
|
s.push(chars[i]);
|
|
}
|
|
i += 1;
|
|
}
|
|
if i >= len {
|
|
return Err("unterminated string".into());
|
|
}
|
|
i += 1; // closing quote
|
|
tokens.push(Token::Str(s));
|
|
}
|
|
_ if c.is_ascii_digit() || (c == '.' && i + 1 < len && chars[i + 1].is_ascii_digit()) => {
|
|
let start = i;
|
|
while i < len && (chars[i].is_ascii_digit() || (chars[i] == '.' && !(i + 1 < len && chars[i + 1] == '.'))) {
|
|
i += 1;
|
|
}
|
|
let s: String = chars[start..i].iter().collect();
|
|
let n: f64 = s.parse().map_err(|_| format!("invalid number: {}", s))?;
|
|
tokens.push(Token::Number(n));
|
|
}
|
|
_ if c.is_alphabetic() || c == '_' => {
|
|
let start = i;
|
|
while i < len && (chars[i].is_alphanumeric() || chars[i] == '_') {
|
|
i += 1;
|
|
}
|
|
let word: String = chars[start..i].iter().collect();
|
|
match word.as_str() {
|
|
"let" => tokens.push(Token::Let),
|
|
"while" => tokens.push(Token::While),
|
|
"fn" => tokens.push(Token::Fn),
|
|
"if" => tokens.push(Token::If),
|
|
"else" => tokens.push(Token::Else),
|
|
"for" => tokens.push(Token::For),
|
|
"in" => tokens.push(Token::In),
|
|
"return" => tokens.push(Token::Return),
|
|
"true" => tokens.push(Token::Bool(true)),
|
|
"false" => tokens.push(Token::Bool(false)),
|
|
_ => tokens.push(Token::Ident(word)),
|
|
}
|
|
}
|
|
_ => {
|
|
return Err(format!("unexpected character: '{}'", c));
|
|
}
|
|
}
|
|
}
|
|
tokens.push(Token::Eof);
|
|
Ok(tokens)
|
|
}
|
|
|
|
// --- AST ---
|
|
|
|
#[derive(Debug, Clone)]
|
|
enum Op {
|
|
Add, Sub, Mul, Div, Mod, Pow,
|
|
Eq, Neq, Lt, Gt, Lte, Gte,
|
|
And, Or, Not, Neg,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
enum Stmt {
|
|
Let(String, Option<String>, Expr),
|
|
Assign(String, Expr),
|
|
While(Expr, Vec<Stmt>),
|
|
IfElse(Expr, Vec<Stmt>, Option<Vec<Stmt>>),
|
|
ForLoop(String, Expr, Vec<Stmt>),
|
|
FnDef(String, Vec<String>, Vec<Stmt>),
|
|
Return(Expr),
|
|
ExprStmt(Expr),
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
enum Expr {
|
|
Num(f64),
|
|
Str(String),
|
|
Bool(bool),
|
|
Ident(String),
|
|
BinOp(Op, Box<Expr>, Box<Expr>),
|
|
UnaryOp(Op, Box<Expr>),
|
|
Call(String, Vec<Expr>),
|
|
Array(Vec<Expr>),
|
|
Index(Box<Expr>, Box<Expr>),
|
|
Range(Box<Expr>, Box<Expr>),
|
|
}
|
|
|
|
// --- Parser ---
|
|
|
|
struct Parser {
|
|
tokens: Vec<Token>,
|
|
pos: usize,
|
|
}
|
|
|
|
impl Parser {
|
|
fn new(tokens: Vec<Token>) -> Self {
|
|
Parser { tokens, pos: 0 }
|
|
}
|
|
|
|
fn peek(&self) -> &Token {
|
|
self.tokens.get(self.pos).unwrap_or(&Token::Eof)
|
|
}
|
|
|
|
fn advance(&mut self) -> Token {
|
|
let tok = self.tokens.get(self.pos).cloned().unwrap_or(Token::Eof);
|
|
self.pos += 1;
|
|
tok
|
|
}
|
|
|
|
fn expect(&mut self, expected: &Token) -> Result<(), String> {
|
|
let tok = self.advance();
|
|
if &tok == expected {
|
|
Ok(())
|
|
} else {
|
|
Err(format!("expected {:?}, got {:?}", expected, tok))
|
|
}
|
|
}
|
|
|
|
fn skip_newlines(&mut self) {
|
|
while self.peek() == &Token::Newline {
|
|
self.advance();
|
|
}
|
|
}
|
|
|
|
fn parse_program(&mut self) -> Result<Vec<Stmt>, String> {
|
|
let mut stmts = Vec::new();
|
|
self.skip_newlines();
|
|
while self.peek() != &Token::Eof {
|
|
stmts.push(self.parse_stmt()?);
|
|
self.skip_newlines();
|
|
}
|
|
Ok(stmts)
|
|
}
|
|
|
|
fn parse_block(&mut self) -> Result<Vec<Stmt>, String> {
|
|
self.expect(&Token::LBrace)?;
|
|
self.skip_newlines();
|
|
let mut stmts = Vec::new();
|
|
while self.peek() != &Token::RBrace && self.peek() != &Token::Eof {
|
|
stmts.push(self.parse_stmt()?);
|
|
self.skip_newlines();
|
|
}
|
|
self.expect(&Token::RBrace)?;
|
|
Ok(stmts)
|
|
}
|
|
|
|
fn parse_stmt(&mut self) -> Result<Stmt, String> {
|
|
self.skip_newlines();
|
|
match self.peek().clone() {
|
|
Token::Let => self.parse_let(),
|
|
Token::While => self.parse_while(),
|
|
Token::If => self.parse_if(),
|
|
Token::For => self.parse_for(),
|
|
Token::Return => self.parse_return(),
|
|
Token::Fn => self.parse_fn_def(),
|
|
Token::Ident(_) => {
|
|
let saved = self.pos;
|
|
if let Token::Ident(name) = self.advance() {
|
|
// name(params) = expr (legacy cord-expr function syntax)
|
|
if self.peek() == &Token::LParen {
|
|
let paren_saved = self.pos;
|
|
self.advance();
|
|
let mut params = Vec::new();
|
|
let mut valid = true;
|
|
if self.peek() != &Token::RParen {
|
|
match self.peek() {
|
|
Token::Ident(_) => {
|
|
if let Token::Ident(p) = self.advance() { params.push(p); }
|
|
while self.peek() == &Token::Comma {
|
|
self.advance();
|
|
if let Token::Ident(p) = self.advance() {
|
|
params.push(p);
|
|
} else {
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
_ => { valid = false; }
|
|
}
|
|
}
|
|
if valid && self.peek() == &Token::RParen {
|
|
self.advance();
|
|
if self.peek() == &Token::Eq {
|
|
self.advance();
|
|
let body_expr = self.parse_expr()?;
|
|
self.skip_newlines();
|
|
return Ok(Stmt::FnDef(name, params, vec![Stmt::ExprStmt(body_expr)]));
|
|
}
|
|
}
|
|
self.pos = paren_saved;
|
|
// fall through: not a function def, might be assignment
|
|
}
|
|
if self.peek() == &Token::Eq {
|
|
self.advance();
|
|
let expr = self.parse_expr()?;
|
|
self.skip_newlines();
|
|
return Ok(Stmt::Assign(name, expr));
|
|
}
|
|
}
|
|
self.pos = saved;
|
|
let expr = self.parse_expr()?;
|
|
self.skip_newlines();
|
|
Ok(Stmt::ExprStmt(expr))
|
|
}
|
|
_ => {
|
|
let expr = self.parse_expr()?;
|
|
self.skip_newlines();
|
|
Ok(Stmt::ExprStmt(expr))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_let(&mut self) -> Result<Stmt, String> {
|
|
self.expect(&Token::Let)?;
|
|
let name = match self.advance() {
|
|
Token::Ident(n) => n,
|
|
t => return Err(format!("expected identifier after 'let', got {:?}", t)),
|
|
};
|
|
let type_ann = if self.peek() == &Token::Colon {
|
|
self.advance();
|
|
match self.advance() {
|
|
Token::Ident(t) => Some(t),
|
|
t => return Err(format!("expected type name after ':', got {:?}", t)),
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
self.expect(&Token::Eq)?;
|
|
let expr = self.parse_expr()?;
|
|
self.skip_newlines();
|
|
Ok(Stmt::Let(name, type_ann, expr))
|
|
}
|
|
|
|
fn parse_while(&mut self) -> Result<Stmt, String> {
|
|
self.expect(&Token::While)?;
|
|
let has_paren = if self.peek() == &Token::LParen {
|
|
self.advance();
|
|
true
|
|
} else {
|
|
false
|
|
};
|
|
let cond = self.parse_expr()?;
|
|
if has_paren {
|
|
self.expect(&Token::RParen)?;
|
|
}
|
|
self.skip_newlines();
|
|
let body = self.parse_block()?;
|
|
Ok(Stmt::While(cond, body))
|
|
}
|
|
|
|
fn parse_if(&mut self) -> Result<Stmt, String> {
|
|
self.expect(&Token::If)?;
|
|
let has_paren = if self.peek() == &Token::LParen {
|
|
self.advance();
|
|
true
|
|
} else {
|
|
false
|
|
};
|
|
let cond = self.parse_expr()?;
|
|
if has_paren {
|
|
self.expect(&Token::RParen)?;
|
|
}
|
|
self.skip_newlines();
|
|
let then_body = self.parse_block()?;
|
|
self.skip_newlines();
|
|
let else_body = if self.peek() == &Token::Else {
|
|
self.advance();
|
|
self.skip_newlines();
|
|
if self.peek() == &Token::If {
|
|
Some(vec![self.parse_if()?])
|
|
} else {
|
|
Some(self.parse_block()?)
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
Ok(Stmt::IfElse(cond, then_body, else_body))
|
|
}
|
|
|
|
fn parse_for(&mut self) -> Result<Stmt, String> {
|
|
self.expect(&Token::For)?;
|
|
let var = match self.advance() {
|
|
Token::Ident(n) => n,
|
|
t => return Err(format!("expected loop variable, got {:?}", t)),
|
|
};
|
|
self.expect(&Token::In)?;
|
|
let iter = self.parse_expr()?;
|
|
self.skip_newlines();
|
|
let body = self.parse_block()?;
|
|
Ok(Stmt::ForLoop(var, iter, body))
|
|
}
|
|
|
|
fn parse_return(&mut self) -> Result<Stmt, String> {
|
|
self.expect(&Token::Return)?;
|
|
if matches!(self.peek(), Token::Newline | Token::Eof | Token::RBrace) {
|
|
return Ok(Stmt::Return(Expr::Bool(false)));
|
|
}
|
|
let expr = self.parse_expr()?;
|
|
self.skip_newlines();
|
|
Ok(Stmt::Return(expr))
|
|
}
|
|
|
|
fn parse_fn_def(&mut self) -> Result<Stmt, String> {
|
|
self.expect(&Token::Fn)?;
|
|
let name = match self.advance() {
|
|
Token::Ident(n) => n,
|
|
t => return Err(format!("expected function name, got {:?}", t)),
|
|
};
|
|
self.expect(&Token::LParen)?;
|
|
let mut params = Vec::new();
|
|
if self.peek() != &Token::RParen {
|
|
match self.advance() {
|
|
Token::Ident(p) => params.push(p),
|
|
t => return Err(format!("expected parameter name, got {:?}", t)),
|
|
}
|
|
while self.peek() == &Token::Comma {
|
|
self.advance();
|
|
match self.advance() {
|
|
Token::Ident(p) => params.push(p),
|
|
t => return Err(format!("expected parameter name, got {:?}", t)),
|
|
}
|
|
}
|
|
}
|
|
self.expect(&Token::RParen)?;
|
|
self.skip_newlines();
|
|
let body = self.parse_block()?;
|
|
Ok(Stmt::FnDef(name, params, body))
|
|
}
|
|
|
|
fn parse_expr(&mut self) -> Result<Expr, String> {
|
|
let left = self.parse_or()?;
|
|
if self.peek() == &Token::DotDot {
|
|
self.advance();
|
|
let right = self.parse_or()?;
|
|
return Ok(Expr::Range(Box::new(left), Box::new(right)));
|
|
}
|
|
Ok(left)
|
|
}
|
|
|
|
fn parse_or(&mut self) -> Result<Expr, String> {
|
|
let mut left = self.parse_and()?;
|
|
while self.peek() == &Token::Or {
|
|
self.advance();
|
|
let right = self.parse_and()?;
|
|
left = Expr::BinOp(Op::Or, Box::new(left), Box::new(right));
|
|
}
|
|
Ok(left)
|
|
}
|
|
|
|
fn parse_and(&mut self) -> Result<Expr, String> {
|
|
let mut left = self.parse_comparison()?;
|
|
while self.peek() == &Token::And {
|
|
self.advance();
|
|
let right = self.parse_comparison()?;
|
|
left = Expr::BinOp(Op::And, Box::new(left), Box::new(right));
|
|
}
|
|
Ok(left)
|
|
}
|
|
|
|
fn parse_comparison(&mut self) -> Result<Expr, String> {
|
|
let mut left = self.parse_additive()?;
|
|
loop {
|
|
let op = match self.peek() {
|
|
Token::EqEq => Op::Eq,
|
|
Token::BangEq => Op::Neq,
|
|
Token::Lt => Op::Lt,
|
|
Token::Gt => Op::Gt,
|
|
Token::LtEq => Op::Lte,
|
|
Token::GtEq => Op::Gte,
|
|
_ => break,
|
|
};
|
|
self.advance();
|
|
let right = self.parse_additive()?;
|
|
left = Expr::BinOp(op, Box::new(left), Box::new(right));
|
|
}
|
|
Ok(left)
|
|
}
|
|
|
|
fn parse_additive(&mut self) -> Result<Expr, String> {
|
|
let mut left = self.parse_multiplicative()?;
|
|
loop {
|
|
let op = match self.peek() {
|
|
Token::Plus => Op::Add,
|
|
Token::Minus => Op::Sub,
|
|
_ => break,
|
|
};
|
|
self.advance();
|
|
let right = self.parse_multiplicative()?;
|
|
left = Expr::BinOp(op, Box::new(left), Box::new(right));
|
|
}
|
|
Ok(left)
|
|
}
|
|
|
|
fn parse_multiplicative(&mut self) -> Result<Expr, String> {
|
|
let mut left = self.parse_power()?;
|
|
loop {
|
|
let op = match self.peek() {
|
|
Token::Star => Op::Mul,
|
|
Token::Slash => Op::Div,
|
|
Token::Percent => Op::Mod,
|
|
_ => break,
|
|
};
|
|
self.advance();
|
|
let right = self.parse_power()?;
|
|
left = Expr::BinOp(op, Box::new(left), Box::new(right));
|
|
}
|
|
Ok(left)
|
|
}
|
|
|
|
fn parse_power(&mut self) -> Result<Expr, String> {
|
|
let base = self.parse_unary()?;
|
|
if self.peek() == &Token::Caret {
|
|
self.advance();
|
|
let exp = self.parse_power()?; // right-associative
|
|
Ok(Expr::BinOp(Op::Pow, Box::new(base), Box::new(exp)))
|
|
} else {
|
|
Ok(base)
|
|
}
|
|
}
|
|
|
|
fn parse_unary(&mut self) -> Result<Expr, String> {
|
|
match self.peek() {
|
|
Token::Bang => {
|
|
self.advance();
|
|
let expr = self.parse_unary()?;
|
|
Ok(Expr::UnaryOp(Op::Not, Box::new(expr)))
|
|
}
|
|
Token::Minus => {
|
|
// Only treat as unary neg if the minus wasn't already consumed as negative number
|
|
self.advance();
|
|
let expr = self.parse_unary()?;
|
|
Ok(Expr::UnaryOp(Op::Neg, Box::new(expr)))
|
|
}
|
|
_ => self.parse_call(),
|
|
}
|
|
}
|
|
|
|
fn parse_call(&mut self) -> Result<Expr, String> {
|
|
let mut expr = self.parse_atom()?;
|
|
if let Expr::Ident(ref name) = expr {
|
|
if self.peek() == &Token::LParen {
|
|
self.advance();
|
|
let mut args = Vec::new();
|
|
if self.peek() != &Token::RParen {
|
|
args.push(self.parse_expr()?);
|
|
while self.peek() == &Token::Comma {
|
|
self.advance();
|
|
args.push(self.parse_expr()?);
|
|
}
|
|
}
|
|
self.expect(&Token::RParen)?;
|
|
expr = Expr::Call(name.clone(), args);
|
|
}
|
|
}
|
|
while self.peek() == &Token::LBracket {
|
|
self.advance();
|
|
let index = self.parse_expr()?;
|
|
self.expect(&Token::RBracket)?;
|
|
expr = Expr::Index(Box::new(expr), Box::new(index));
|
|
}
|
|
Ok(expr)
|
|
}
|
|
|
|
fn parse_atom(&mut self) -> Result<Expr, String> {
|
|
match self.peek().clone() {
|
|
Token::Number(n) => { self.advance(); Ok(Expr::Num(n)) }
|
|
Token::Str(s) => { self.advance(); Ok(Expr::Str(s)) }
|
|
Token::Bool(b) => { self.advance(); Ok(Expr::Bool(b)) }
|
|
Token::Ident(name) => { self.advance(); Ok(Expr::Ident(name)) }
|
|
Token::LParen => {
|
|
self.advance();
|
|
let expr = self.parse_expr()?;
|
|
self.expect(&Token::RParen)?;
|
|
Ok(expr)
|
|
}
|
|
Token::LBracket => {
|
|
self.advance();
|
|
let mut items = Vec::new();
|
|
if self.peek() != &Token::RBracket {
|
|
items.push(self.parse_expr()?);
|
|
while self.peek() == &Token::Comma {
|
|
self.advance();
|
|
items.push(self.parse_expr()?);
|
|
}
|
|
}
|
|
self.expect(&Token::RBracket)?;
|
|
Ok(Expr::Array(items))
|
|
}
|
|
t => Err(format!("unexpected token: {:?}", t)),
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Interpreter ---
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct FnDef {
|
|
params: Vec<String>,
|
|
body: Vec<Stmt>,
|
|
}
|
|
|
|
pub struct Interpreter {
|
|
vars: HashMap<String, Value>,
|
|
fns: HashMap<String, FnDef>,
|
|
}
|
|
|
|
const MAX_ITERATIONS: usize = 10_000;
|
|
const MAX_CALL_DEPTH: u32 = 256;
|
|
|
|
impl Interpreter {
|
|
pub fn new() -> Self {
|
|
Interpreter {
|
|
vars: HashMap::new(),
|
|
fns: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn exec_line(&mut self, line: &str) -> Result<Option<Value>, String> {
|
|
let trimmed = line.trim();
|
|
if trimmed.is_empty() {
|
|
return Ok(None);
|
|
}
|
|
let tokens = tokenize(trimmed)?;
|
|
let mut parser = Parser::new(tokens);
|
|
let stmts = parser.parse_program()?;
|
|
let mut last = Value::Void;
|
|
for stmt in stmts {
|
|
last = self.exec_stmt(&stmt, 0)?;
|
|
}
|
|
match last {
|
|
Value::Void => Ok(None),
|
|
v => Ok(Some(v)),
|
|
}
|
|
}
|
|
|
|
pub fn eval_expr_str(&mut self, expr: &str) -> Result<Value, String> {
|
|
let trimmed = expr.trim();
|
|
if trimmed.is_empty() {
|
|
return Err("empty expression".into());
|
|
}
|
|
let tokens = tokenize(trimmed)?;
|
|
let mut parser = Parser::new(tokens);
|
|
let e = parser.parse_expr()?;
|
|
self.eval_expr(&e, 0)
|
|
}
|
|
|
|
fn exec_stmt(&mut self, stmt: &Stmt, depth: u32) -> Result<Value, String> {
|
|
match stmt {
|
|
Stmt::Let(name, type_ann, expr) => {
|
|
let val = self.eval_expr(expr, depth)?;
|
|
let val = apply_type_annotation(&val, type_ann.as_deref())?;
|
|
self.vars.insert(name.clone(), val);
|
|
Ok(Value::Void)
|
|
}
|
|
Stmt::Assign(name, expr) => {
|
|
let val = self.eval_expr(expr, depth)?;
|
|
self.vars.insert(name.clone(), val);
|
|
Ok(Value::Void)
|
|
}
|
|
Stmt::While(cond, body) => {
|
|
let mut iterations = 0;
|
|
loop {
|
|
let cv = self.eval_expr(cond, depth)?;
|
|
if !cv.truthy() { break; }
|
|
iterations += 1;
|
|
if iterations > MAX_ITERATIONS {
|
|
return Err(format!("loop exceeded {} iterations", MAX_ITERATIONS));
|
|
}
|
|
let mut last = Value::Void;
|
|
for s in body {
|
|
last = self.exec_stmt(s, depth)?;
|
|
}
|
|
drop(last);
|
|
}
|
|
Ok(Value::Void)
|
|
}
|
|
Stmt::IfElse(cond, then_body, else_body) => {
|
|
let cv = self.eval_expr(cond, depth)?;
|
|
let body = if cv.truthy() { then_body } else {
|
|
match else_body { Some(b) => b, None => return Ok(Value::Void) }
|
|
};
|
|
let mut last = Value::Void;
|
|
for s in body {
|
|
last = self.exec_stmt(s, depth)?;
|
|
}
|
|
Ok(last)
|
|
}
|
|
Stmt::ForLoop(var, iter_expr, body) => {
|
|
let iterable = self.eval_expr(iter_expr, depth)?;
|
|
let items = match iterable {
|
|
Value::Array(a) => a,
|
|
_ => return Err("for loop requires an array or range".into()),
|
|
};
|
|
let mut iterations = 0;
|
|
let mut last = Value::Void;
|
|
for item in &items {
|
|
iterations += 1;
|
|
if iterations > MAX_ITERATIONS {
|
|
return Err(format!("loop exceeded {} iterations", MAX_ITERATIONS));
|
|
}
|
|
self.vars.insert(var.clone(), item.clone());
|
|
for s in body {
|
|
last = self.exec_stmt(s, depth)?;
|
|
}
|
|
}
|
|
Ok(last)
|
|
}
|
|
Stmt::FnDef(name, params, body) => {
|
|
self.fns.insert(name.clone(), FnDef {
|
|
params: params.clone(),
|
|
body: body.clone(),
|
|
});
|
|
Ok(Value::Void)
|
|
}
|
|
Stmt::Return(expr) => {
|
|
let val = self.eval_expr(expr, depth)?;
|
|
Err(format!("\x00return:{}", encode_return(&val)))
|
|
}
|
|
Stmt::ExprStmt(expr) => {
|
|
self.eval_expr(expr, depth)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn eval_expr(&mut self, expr: &Expr, depth: u32) -> Result<Value, String> {
|
|
match expr {
|
|
Expr::Num(n) => Ok(Value::Number(*n)),
|
|
Expr::Str(s) => Ok(Value::Str(s.clone())),
|
|
Expr::Bool(b) => Ok(Value::Bool(*b)),
|
|
Expr::Ident(name) => {
|
|
self.vars.get(name).cloned()
|
|
.ok_or_else(|| format!("undefined variable '{}'", name))
|
|
}
|
|
Expr::Array(items) => {
|
|
let mut vals = Vec::new();
|
|
for item in items {
|
|
vals.push(self.eval_expr(item, depth)?);
|
|
}
|
|
Ok(Value::Array(vals))
|
|
}
|
|
Expr::UnaryOp(Op::Not, inner) => {
|
|
let v = self.eval_expr(inner, depth)?;
|
|
Ok(Value::Bool(!v.truthy()))
|
|
}
|
|
Expr::UnaryOp(Op::Neg, inner) => {
|
|
let v = self.eval_expr(inner, depth)?;
|
|
match v {
|
|
Value::Number(n) => Ok(Value::Number(-n)),
|
|
_ => Err("cannot negate non-number".into()),
|
|
}
|
|
}
|
|
Expr::UnaryOp(op, _) => Err(format!("invalid unary op: {:?}", op)),
|
|
Expr::BinOp(op, lhs, rhs) => self.eval_binop(op, lhs, rhs, depth),
|
|
Expr::Call(name, args) => self.eval_call(name, args, depth),
|
|
Expr::Index(target, index) => {
|
|
let target_val = self.eval_expr(target, depth)?;
|
|
let index_val = self.eval_expr(index, depth)?;
|
|
match (&target_val, &index_val) {
|
|
(Value::Array(arr), Value::Number(n)) => {
|
|
let i = *n as i64;
|
|
let idx = if i < 0 { (arr.len() as i64 + i) as usize } else { i as usize };
|
|
arr.get(idx).cloned().ok_or_else(|| format!("index {} out of bounds (len {})", i, arr.len()))
|
|
}
|
|
(Value::Str(s), Value::Number(n)) => {
|
|
let i = *n as i64;
|
|
let chars: Vec<char> = s.chars().collect();
|
|
let idx = if i < 0 { (chars.len() as i64 + i) as usize } else { i as usize };
|
|
chars.get(idx).map(|c| Value::Str(c.to_string()))
|
|
.ok_or_else(|| format!("index {} out of bounds (len {})", i, chars.len()))
|
|
}
|
|
_ => Err(format!("cannot index {} with {}", type_name(&target_val), type_name(&index_val))),
|
|
}
|
|
}
|
|
Expr::Range(start, end) => {
|
|
let sv = self.eval_expr(start, depth)?;
|
|
let ev = self.eval_expr(end, depth)?;
|
|
match (&sv, &ev) {
|
|
(Value::Number(a), Value::Number(b)) => {
|
|
let a = *a as i64;
|
|
let b = *b as i64;
|
|
let items: Vec<Value> = (a..b).map(|n| Value::Number(n as f64)).collect();
|
|
if items.len() > MAX_ITERATIONS {
|
|
return Err(format!("range too large ({} elements)", items.len()));
|
|
}
|
|
Ok(Value::Array(items))
|
|
}
|
|
_ => Err("range requires two numbers".into()),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn eval_binop(&mut self, op: &Op, lhs: &Expr, rhs: &Expr, depth: u32) -> Result<Value, String> {
|
|
// short-circuit for logical ops
|
|
if matches!(op, Op::And) {
|
|
let l = self.eval_expr(lhs, depth)?;
|
|
if !l.truthy() { return Ok(Value::Bool(false)); }
|
|
let r = self.eval_expr(rhs, depth)?;
|
|
return Ok(Value::Bool(r.truthy()));
|
|
}
|
|
if matches!(op, Op::Or) {
|
|
let l = self.eval_expr(lhs, depth)?;
|
|
if l.truthy() { return Ok(Value::Bool(true)); }
|
|
let r = self.eval_expr(rhs, depth)?;
|
|
return Ok(Value::Bool(r.truthy()));
|
|
}
|
|
|
|
let l = self.eval_expr(lhs, depth)?;
|
|
let r = self.eval_expr(rhs, depth)?;
|
|
|
|
match (op, &l, &r) {
|
|
// number arithmetic
|
|
(Op::Add, Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
|
|
(Op::Sub, Value::Number(a), Value::Number(b)) => Ok(Value::Number(a - b)),
|
|
(Op::Mul, Value::Number(a), Value::Number(b)) => Ok(Value::Number(a * b)),
|
|
(Op::Div, Value::Number(_, ), Value::Number(b)) if *b == 0.0 => Err("division by zero".into()),
|
|
(Op::Div, Value::Number(a), Value::Number(b)) => Ok(Value::Number(a / b)),
|
|
(Op::Mod, Value::Number(a), Value::Number(b)) => Ok(Value::Number(a % b)),
|
|
(Op::Pow, Value::Number(a), Value::Number(b)) => Ok(Value::Number(a.powf(*b))),
|
|
|
|
// string concatenation
|
|
(Op::Add, Value::Str(a), Value::Str(b)) => Ok(Value::Str(format!("{}{}", a, b))),
|
|
(Op::Add, Value::Str(a), Value::Number(b)) => Ok(Value::Str(format!("{}{}", a, format_number(*b)))),
|
|
(Op::Add, Value::Number(a), Value::Str(b)) => Ok(Value::Str(format!("{}{}", format_number(*a), b))),
|
|
(Op::Add, Value::Str(a), Value::Bool(b)) => Ok(Value::Str(format!("{}{}", a, b))),
|
|
(Op::Add, Value::Bool(a), Value::Str(b)) => Ok(Value::Str(format!("{}{}", a, b))),
|
|
|
|
// number comparisons
|
|
(Op::Lt, Value::Number(a), Value::Number(b)) => Ok(Value::Bool(a < b)),
|
|
(Op::Gt, Value::Number(a), Value::Number(b)) => Ok(Value::Bool(a > b)),
|
|
(Op::Lte, Value::Number(a), Value::Number(b)) => Ok(Value::Bool(a <= b)),
|
|
(Op::Gte, Value::Number(a), Value::Number(b)) => Ok(Value::Bool(a >= b)),
|
|
|
|
// equality
|
|
(Op::Eq, Value::Number(a), Value::Number(b)) => Ok(Value::Bool(a == b)),
|
|
(Op::Eq, Value::Str(a), Value::Str(b)) => Ok(Value::Bool(a == b)),
|
|
(Op::Eq, Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a == b)),
|
|
(Op::Eq, _, _) => Ok(Value::Bool(false)),
|
|
|
|
(Op::Neq, Value::Number(a), Value::Number(b)) => Ok(Value::Bool(a != b)),
|
|
(Op::Neq, Value::Str(a), Value::Str(b)) => Ok(Value::Bool(a != b)),
|
|
(Op::Neq, Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a != b)),
|
|
(Op::Neq, _, _) => Ok(Value::Bool(true)),
|
|
|
|
_ => Err(format!("type error: cannot apply {:?} to {:?} and {:?}", op, type_name(&l), type_name(&r))),
|
|
}
|
|
}
|
|
|
|
fn eval_call(&mut self, name: &str, args: &[Expr], depth: u32) -> Result<Value, String> {
|
|
if depth >= MAX_CALL_DEPTH {
|
|
return Err("maximum call depth exceeded".into());
|
|
}
|
|
|
|
// built-in math functions
|
|
match name {
|
|
"sin" | "cos" | "tan" | "asin" | "acos" | "atan" |
|
|
"sqrt" | "abs" | "floor" | "ceil" | "round" | "ln" | "log" => {
|
|
if args.len() != 1 {
|
|
return Err(format!("{}() expects 1 argument", name));
|
|
}
|
|
let v = self.eval_expr(&args[0], depth)?;
|
|
let n = match v {
|
|
Value::Number(n) => n,
|
|
_ => return Err(format!("{}() expects a number", name)),
|
|
};
|
|
let result = match name {
|
|
"sin" => n.sin(),
|
|
"cos" => n.cos(),
|
|
"tan" => n.tan(),
|
|
"asin" => n.asin(),
|
|
"acos" => n.acos(),
|
|
"atan" => n.atan(),
|
|
"sqrt" => n.sqrt(),
|
|
"abs" => n.abs(),
|
|
"floor" => n.floor(),
|
|
"ceil" => n.ceil(),
|
|
"round" => n.round(),
|
|
"ln" => n.ln(),
|
|
"log" => n.log10(),
|
|
_ => unreachable!(),
|
|
};
|
|
return Ok(Value::Number(result));
|
|
}
|
|
"len" => {
|
|
if args.len() != 1 {
|
|
return Err("len() expects 1 argument".into());
|
|
}
|
|
let v = self.eval_expr(&args[0], depth)?;
|
|
return match v {
|
|
Value::Str(s) => Ok(Value::Number(s.len() as f64)),
|
|
Value::Array(a) => Ok(Value::Number(a.len() as f64)),
|
|
_ => Err("len() expects a string or array".into()),
|
|
};
|
|
}
|
|
"range" => {
|
|
if args.len() != 2 {
|
|
return Err("range() expects 2 arguments".into());
|
|
}
|
|
let start = match self.eval_expr(&args[0], depth)? {
|
|
Value::Number(n) => n as i64,
|
|
_ => return Err("range() expects numbers".into()),
|
|
};
|
|
let end = match self.eval_expr(&args[1], depth)? {
|
|
Value::Number(n) => n as i64,
|
|
_ => return Err("range() expects numbers".into()),
|
|
};
|
|
let items: Vec<Value> = (start..end).map(|n| Value::Number(n as f64)).collect();
|
|
if items.len() > MAX_ITERATIONS {
|
|
return Err(format!("range too large ({} elements)", items.len()));
|
|
}
|
|
return Ok(Value::Array(items));
|
|
}
|
|
"push" => {
|
|
if args.len() != 2 {
|
|
return Err("push() expects 2 arguments (array, value)".into());
|
|
}
|
|
let arr = self.eval_expr(&args[0], depth)?;
|
|
let val = self.eval_expr(&args[1], depth)?;
|
|
return match arr {
|
|
Value::Array(mut a) => { a.push(val); Ok(Value::Array(a)) }
|
|
_ => Err("push() expects an array as first argument".into()),
|
|
};
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
let fdef = self.fns.get(name).cloned()
|
|
.ok_or_else(|| format!("undefined function '{}'", name))?;
|
|
|
|
if args.len() != fdef.params.len() {
|
|
return Err(format!("{}() expects {} arguments, got {}", name, fdef.params.len(), args.len()));
|
|
}
|
|
|
|
let mut arg_vals = Vec::new();
|
|
for arg in args {
|
|
arg_vals.push(self.eval_expr(arg, depth)?);
|
|
}
|
|
|
|
// save current vars, set up function scope
|
|
let saved_vars = self.vars.clone();
|
|
for (param, val) in fdef.params.iter().zip(arg_vals) {
|
|
self.vars.insert(param.clone(), val);
|
|
}
|
|
|
|
let mut result = Value::Void;
|
|
for stmt in &fdef.body {
|
|
match self.exec_stmt(stmt, depth + 1) {
|
|
Ok(v) => result = v,
|
|
Err(e) if e.starts_with('\x00') => {
|
|
self.vars = saved_vars;
|
|
return Ok(decode_return(&e));
|
|
}
|
|
Err(e) => {
|
|
self.vars = saved_vars;
|
|
return Err(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
self.vars = saved_vars;
|
|
Ok(result)
|
|
}
|
|
}
|
|
|
|
const RETURN_PREFIX: &str = "\x00return:";
|
|
|
|
fn encode_return(val: &Value) -> String {
|
|
match val {
|
|
Value::Number(n) => format!("n:{}", n),
|
|
Value::Bool(b) => format!("b:{}", b),
|
|
Value::Str(s) => format!("s:{}", s),
|
|
Value::Void => "v:".into(),
|
|
_ => format!("s:{}", val.display()),
|
|
}
|
|
}
|
|
|
|
fn decode_return(encoded: &str) -> Value {
|
|
let payload = &encoded[RETURN_PREFIX.len()..];
|
|
if let Some(rest) = payload.strip_prefix("n:") {
|
|
rest.parse::<f64>().map(Value::Number).unwrap_or(Value::Void)
|
|
} else if let Some(rest) = payload.strip_prefix("b:") {
|
|
Value::Bool(rest == "true")
|
|
} else if let Some(rest) = payload.strip_prefix("s:") {
|
|
Value::Str(rest.to_string())
|
|
} else {
|
|
Value::Void
|
|
}
|
|
}
|
|
|
|
fn type_name(v: &Value) -> &'static str {
|
|
match v {
|
|
Value::Number(_) => "number",
|
|
Value::Bool(_) => "bool",
|
|
Value::Str(_) => "str",
|
|
Value::Array(_) => "array",
|
|
Value::Void => "void",
|
|
Value::Error(_) => "error",
|
|
}
|
|
}
|
|
|
|
fn apply_type_annotation(val: &Value, ann: Option<&str>) -> Result<Value, String> {
|
|
let ann = match ann {
|
|
Some(a) => a,
|
|
None => return Ok(val.clone()),
|
|
};
|
|
match ann {
|
|
"int" => match val {
|
|
Value::Number(n) => Ok(Value::Number(n.trunc())),
|
|
_ => Err(format!("type error: expected int, got {}", type_name(val))),
|
|
},
|
|
"float" => match val {
|
|
Value::Number(_) => Ok(val.clone()),
|
|
_ => Err(format!("type error: expected float, got {}", type_name(val))),
|
|
},
|
|
"bool" => match val {
|
|
Value::Bool(_) => Ok(val.clone()),
|
|
Value::Number(n) => {
|
|
if *n == 0.0 { Ok(Value::Bool(false)) }
|
|
else if *n == 1.0 { Ok(Value::Bool(true)) }
|
|
else { Err(format!("{} is not a valid bool", format_number(*n))) }
|
|
}
|
|
_ => Err(format!("type error: expected bool, got {}", type_name(val))),
|
|
},
|
|
"str" => match val {
|
|
Value::Str(_) => Ok(val.clone()),
|
|
_ => Err(format!("type error: expected str, got {}", type_name(val))),
|
|
},
|
|
other => Err(format!("unknown type annotation: {}", other)),
|
|
}
|
|
}
|
|
|
|
// --- Public API for eval.rs integration ---
|
|
|
|
pub struct InterpResult {
|
|
pub line: usize,
|
|
pub value: Option<Value>,
|
|
pub format: EvalFormat,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum EvalFormat {
|
|
Inline,
|
|
Table,
|
|
Tree,
|
|
}
|
|
|
|
pub fn interpret_document(lines: &[(usize, &str, bool)]) -> Vec<InterpResult> {
|
|
// lines: (line_index, content, is_eval)
|
|
// Collect all cordial lines and eval lines in order.
|
|
// For each eval line, evaluate the expression and record the result.
|
|
let mut interp = Interpreter::new();
|
|
let mut results = Vec::new();
|
|
|
|
// First pass: collect the entire program from cordial lines
|
|
// and evaluate line by line, recording results for eval lines
|
|
let mut brace_depth: i32 = 0;
|
|
let mut block_acc: Vec<String> = Vec::new();
|
|
|
|
for &(idx, content, is_eval) in lines {
|
|
if is_eval {
|
|
if !block_acc.is_empty() {
|
|
let block_text = block_acc.join("\n");
|
|
block_acc.clear();
|
|
brace_depth = 0;
|
|
match interp.exec_line(&block_text) {
|
|
Ok(_) => {}
|
|
Err(e) => {
|
|
results.push(InterpResult { line: idx, value: Some(Value::Error(e)), format: EvalFormat::Inline });
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
let trimmed = content.trim();
|
|
let (format, expr) = if let Some(rest) = trimmed.strip_prefix("/=|") {
|
|
(EvalFormat::Table, rest.trim())
|
|
} else if let Some(rest) = trimmed.strip_prefix("/=\\") {
|
|
(EvalFormat::Tree, rest.trim())
|
|
} else {
|
|
(EvalFormat::Inline, trimmed.strip_prefix("/=").unwrap_or("").trim())
|
|
};
|
|
if expr.is_empty() {
|
|
results.push(InterpResult { line: idx, value: Some(Value::Error("empty expression".into())), format });
|
|
continue;
|
|
}
|
|
match interp.eval_expr_str(expr) {
|
|
Ok(val) => results.push(InterpResult { line: idx, value: Some(val), format }),
|
|
Err(e) => results.push(InterpResult { line: idx, value: Some(Value::Error(e)), format }),
|
|
}
|
|
} else {
|
|
let trimmed = content.trim();
|
|
// track brace depth for multi-line blocks
|
|
let opens = trimmed.matches('{').count() as i32;
|
|
let closes = trimmed.matches('}').count() as i32;
|
|
|
|
if brace_depth > 0 || !block_acc.is_empty() {
|
|
block_acc.push(trimmed.to_string());
|
|
brace_depth += opens - closes;
|
|
if brace_depth <= 0 {
|
|
let block_text = block_acc.join("\n");
|
|
block_acc.clear();
|
|
brace_depth = 0;
|
|
if let Err(e) = interp.exec_line(&block_text) {
|
|
results.push(InterpResult { line: idx, value: Some(Value::Error(e)), format: EvalFormat::Inline });
|
|
}
|
|
}
|
|
} else if opens > closes {
|
|
block_acc.push(trimmed.to_string());
|
|
brace_depth = opens - closes;
|
|
} else {
|
|
if let Err(e) = interp.exec_line(trimmed) {
|
|
results.push(InterpResult { line: idx, value: Some(Value::Error(e)), format: EvalFormat::Inline });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
results
|
|
}
|
|
|
|
// --- Display helpers for type-annotated int ---
|
|
|
|
pub fn display_value_with_type(val: &Value, type_ann: Option<&str>) -> String {
|
|
match (val, type_ann) {
|
|
(Value::Number(n), Some("int")) => format!("{}", *n as i64),
|
|
_ => val.display(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn eval(input: &str) -> String {
|
|
let lines: Vec<&str> = input.lines().collect();
|
|
let mut tagged: Vec<(usize, &str, bool)> = Vec::new();
|
|
for (i, line) in lines.iter().enumerate() {
|
|
let is_eval = line.trim().starts_with("/=");
|
|
let is_comment = line.trim().starts_with("//");
|
|
if !is_eval && !is_comment && !line.trim().is_empty() && !line.trim().starts_with('#') {
|
|
tagged.push((i, line, false));
|
|
} else if is_eval {
|
|
tagged.push((i, line, true));
|
|
}
|
|
}
|
|
let results = interpret_document(&tagged);
|
|
results.iter()
|
|
.filter_map(|r| r.value.as_ref().map(|v| v.display()))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
}
|
|
|
|
fn eval_one(input: &str) -> String {
|
|
let mut interp = Interpreter::new();
|
|
match interp.eval_expr_str(input) {
|
|
Ok(v) => v.display(),
|
|
Err(e) => format!("error: {}", e),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn basic_arithmetic() {
|
|
assert_eq!(eval_one("2 + 3"), "5");
|
|
assert_eq!(eval_one("10 - 4"), "6");
|
|
assert_eq!(eval_one("3 * 7"), "21");
|
|
assert_eq!(eval_one("15 / 3"), "5");
|
|
assert_eq!(eval_one("2 ^ 10"), "1024");
|
|
assert_eq!(eval_one("10 % 3"), "1");
|
|
}
|
|
|
|
#[test]
|
|
fn string_literals() {
|
|
assert_eq!(eval_one("\"hello\""), "hello");
|
|
assert_eq!(eval_one("\"hello\" + \" \" + \"world\""), "hello world");
|
|
}
|
|
|
|
#[test]
|
|
fn string_concatenation_mixed() {
|
|
assert_eq!(eval_one("\"val: \" + 42"), "val: 42");
|
|
assert_eq!(eval_one("100 + \" items\""), "100 items");
|
|
}
|
|
|
|
#[test]
|
|
fn boolean_literals() {
|
|
assert_eq!(eval_one("true"), "true");
|
|
assert_eq!(eval_one("false"), "false");
|
|
}
|
|
|
|
#[test]
|
|
fn comparison_operators() {
|
|
assert_eq!(eval_one("1 < 2"), "true");
|
|
assert_eq!(eval_one("2 > 3"), "false");
|
|
assert_eq!(eval_one("5 == 5"), "true");
|
|
assert_eq!(eval_one("5 != 3"), "true");
|
|
assert_eq!(eval_one("3 <= 3"), "true");
|
|
assert_eq!(eval_one("4 >= 5"), "false");
|
|
}
|
|
|
|
#[test]
|
|
fn logical_operators() {
|
|
assert_eq!(eval_one("true && false"), "false");
|
|
assert_eq!(eval_one("true || false"), "true");
|
|
assert_eq!(eval_one("!true"), "false");
|
|
assert_eq!(eval_one("!false"), "true");
|
|
}
|
|
|
|
#[test]
|
|
fn arrays() {
|
|
assert_eq!(eval_one("[1, 2, 3]"), "[1, 2, 3]");
|
|
assert_eq!(eval_one("[1, \"two\", true]"), "[1, \"two\", true]");
|
|
assert_eq!(eval_one("[]"), "[]");
|
|
}
|
|
|
|
#[test]
|
|
fn variable_binding() {
|
|
let input = "let x = 5\n/= x + 10";
|
|
assert_eq!(eval(input), "15");
|
|
}
|
|
|
|
#[test]
|
|
fn variable_reassignment() {
|
|
let input = "let x = 5\nx = 10\n/= x";
|
|
assert_eq!(eval(input), "10");
|
|
}
|
|
|
|
#[test]
|
|
fn while_loop() {
|
|
let input = "let i = 0\nlet sum = 0\nwhile (i < 10) {\n sum = sum + i\n i = i + 1\n}\n/= sum";
|
|
assert_eq!(eval(input), "45");
|
|
}
|
|
|
|
#[test]
|
|
fn while_loop_guard() {
|
|
let input = "let i = 0\nwhile (true) {\n i = i + 1\n}\n/= i";
|
|
let result = eval(input);
|
|
assert!(result.contains("error"), "should error on infinite loop: {}", result);
|
|
}
|
|
|
|
#[test]
|
|
fn function_def_and_call() {
|
|
let input = "fn add(a, b) {\n a + b\n}\n/= add(3, 4)";
|
|
assert_eq!(eval(input), "7");
|
|
}
|
|
|
|
#[test]
|
|
fn function_calling_function() {
|
|
let input = "fn double(x) {\n x * 2\n}\nfn quad(x) {\n double(double(x))\n}\n/= quad(5)";
|
|
assert_eq!(eval(input), "20");
|
|
}
|
|
|
|
#[test]
|
|
fn type_annotation_int() {
|
|
let input = "let x: int = 3.7\n/= x";
|
|
assert_eq!(eval(input), "3");
|
|
}
|
|
|
|
#[test]
|
|
fn type_annotation_bool_valid() {
|
|
let input = "let x: bool = 1\n/= x";
|
|
assert_eq!(eval(input), "true");
|
|
}
|
|
|
|
#[test]
|
|
fn type_annotation_bool_zero() {
|
|
let input = "let x: bool = 0\n/= x";
|
|
assert_eq!(eval(input), "false");
|
|
}
|
|
|
|
#[test]
|
|
fn type_annotation_bool_invalid() {
|
|
let input = "let x: bool = 2\n/= x";
|
|
let result = eval(input);
|
|
assert!(result.contains("error"), "should error: {}", result);
|
|
}
|
|
|
|
#[test]
|
|
fn type_annotation_str() {
|
|
let input = "let x: str = \"hello\"\n/= x";
|
|
assert_eq!(eval(input), "hello");
|
|
}
|
|
|
|
#[test]
|
|
fn type_annotation_str_mismatch() {
|
|
let input = "let x: str = 42\n/= x";
|
|
let result = eval(input);
|
|
assert!(result.contains("error"), "should error: {}", result);
|
|
}
|
|
|
|
#[test]
|
|
fn error_undefined_variable() {
|
|
let result = eval("/= undefined_var");
|
|
assert!(result.contains("error"), "should error: {}", result);
|
|
assert!(result.contains("undefined variable"), "{}", result);
|
|
}
|
|
|
|
#[test]
|
|
fn error_recovery() {
|
|
let input = "let x = bad_var\nlet y = 5\n/= y";
|
|
// x assignment fails, but y should still work
|
|
let result = eval(input);
|
|
assert!(result.contains("5"), "should recover and eval y: {}", result);
|
|
}
|
|
|
|
#[test]
|
|
fn error_undefined_function() {
|
|
let result = eval("/= nope(1, 2)");
|
|
assert!(result.contains("error"), "should error: {}", result);
|
|
}
|
|
|
|
#[test]
|
|
fn multiple_evals() {
|
|
let input = "let a = 3\n/= a\nlet b = 7\n/= a + b";
|
|
assert_eq!(eval(input), "3, 10");
|
|
}
|
|
|
|
#[test]
|
|
fn builtin_math_functions() {
|
|
assert_eq!(eval_one("abs(-5)"), "5");
|
|
assert_eq!(eval_one("floor(3.7)"), "3");
|
|
assert_eq!(eval_one("ceil(3.2)"), "4");
|
|
assert_eq!(eval_one("sqrt(16)"), "4");
|
|
}
|
|
|
|
#[test]
|
|
fn nested_expressions() {
|
|
assert_eq!(eval_one("(2 + 3) * (4 - 1)"), "15");
|
|
assert_eq!(eval_one("2 * (3 + 4 * 5)"), "46");
|
|
}
|
|
|
|
#[test]
|
|
fn string_variable() {
|
|
let input = "let x = \"hello\"\nlet y = \"world\"\n/= x + \" \" + y";
|
|
assert_eq!(eval(input), "hello world");
|
|
}
|
|
|
|
#[test]
|
|
fn division_by_zero() {
|
|
let result = eval_one("1 / 0");
|
|
assert!(result.contains("error"), "should error on div by zero: {}", result);
|
|
}
|
|
|
|
#[test]
|
|
fn len_function() {
|
|
assert_eq!(eval_one("len(\"hello\")"), "5");
|
|
assert_eq!(eval_one("len([1, 2, 3])"), "3");
|
|
}
|
|
|
|
#[test]
|
|
fn negative_numbers() {
|
|
assert_eq!(eval_one("-5"), "-5");
|
|
assert_eq!(eval_one("-3 + 7"), "4");
|
|
assert_eq!(eval_one("10 + -3"), "7");
|
|
}
|
|
|
|
#[test]
|
|
fn empty_array() {
|
|
assert_eq!(eval_one("len([])"), "0");
|
|
}
|
|
|
|
#[test]
|
|
fn complex_while_with_function() {
|
|
let input = "\
|
|
fn fib(n) {
|
|
let a = 0
|
|
let b = 1
|
|
let i = 0
|
|
while (i < n) {
|
|
let tmp = b
|
|
b = a + b
|
|
a = tmp
|
|
i = i + 1
|
|
}
|
|
a
|
|
}
|
|
/= fib(10)";
|
|
assert_eq!(eval(input), "55");
|
|
}
|
|
|
|
#[test]
|
|
fn if_true() {
|
|
let input = "let x = 10\nif (x > 5) {\n x = 100\n}\n/= x";
|
|
assert_eq!(eval(input), "100");
|
|
}
|
|
|
|
#[test]
|
|
fn if_false() {
|
|
let input = "let x = 3\nif (x > 5) {\n x = 100\n}\n/= x";
|
|
assert_eq!(eval(input), "3");
|
|
}
|
|
|
|
#[test]
|
|
fn if_else() {
|
|
let input = "let x = 3\nif (x > 5) {\n x = 100\n} else {\n x = 0\n}\n/= x";
|
|
assert_eq!(eval(input), "0");
|
|
}
|
|
|
|
#[test]
|
|
fn if_else_chain() {
|
|
let input = "\
|
|
let x = 5
|
|
let r = 0
|
|
if (x > 10) {
|
|
r = 3
|
|
} else if (x > 3) {
|
|
r = 2
|
|
} else {
|
|
r = 1
|
|
}
|
|
/= r";
|
|
assert_eq!(eval(input), "2");
|
|
}
|
|
|
|
#[test]
|
|
fn if_without_parens() {
|
|
let input = "let x = 10\nif x > 5 {\n x = 100\n}\n/= x";
|
|
assert_eq!(eval(input), "100");
|
|
}
|
|
|
|
#[test]
|
|
fn for_loop_array() {
|
|
let input = "let sum = 0\nfor x in [1, 2, 3, 4, 5] {\n sum = sum + x\n}\n/= sum";
|
|
assert_eq!(eval(input), "15");
|
|
}
|
|
|
|
#[test]
|
|
fn for_loop_range() {
|
|
let input = "let sum = 0\nfor i in 0..5 {\n sum = sum + i\n}\n/= sum";
|
|
assert_eq!(eval(input), "10");
|
|
}
|
|
|
|
#[test]
|
|
fn for_loop_range_fn() {
|
|
let input = "let sum = 0\nfor i in range(1, 6) {\n sum = sum + i\n}\n/= sum";
|
|
assert_eq!(eval(input), "15");
|
|
}
|
|
|
|
#[test]
|
|
fn array_index() {
|
|
assert_eq!(eval_one("[10, 20, 30][1]"), "20");
|
|
}
|
|
|
|
#[test]
|
|
fn array_index_variable() {
|
|
let input = "let arr = [10, 20, 30]\n/= arr[2]";
|
|
assert_eq!(eval(input), "30");
|
|
}
|
|
|
|
#[test]
|
|
fn array_negative_index() {
|
|
assert_eq!(eval_one("[10, 20, 30][-1]"), "30");
|
|
}
|
|
|
|
#[test]
|
|
fn string_index() {
|
|
assert_eq!(eval_one("\"hello\"[0]"), "h");
|
|
}
|
|
|
|
#[test]
|
|
fn array_index_out_of_bounds() {
|
|
let result = eval_one("[1, 2][5]");
|
|
assert!(result.contains("error"), "should error: {}", result);
|
|
}
|
|
|
|
#[test]
|
|
fn return_from_function() {
|
|
let input = "\
|
|
fn first_positive(a, b) {
|
|
if (a > 0) {
|
|
return a
|
|
}
|
|
if (b > 0) {
|
|
return b
|
|
}
|
|
return 0
|
|
}
|
|
/= first_positive(-1, 5)";
|
|
assert_eq!(eval(input), "5");
|
|
}
|
|
|
|
#[test]
|
|
fn return_early_from_loop() {
|
|
let input = "\
|
|
fn find(arr, target) {
|
|
for x in arr {
|
|
if (x == target) {
|
|
return x
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
/= find([1, 2, 3, 4], 3)";
|
|
assert_eq!(eval(input), "3");
|
|
}
|
|
|
|
#[test]
|
|
fn push_builtin() {
|
|
let input = "let arr = [1, 2]\nlet arr = push(arr, 3)\n/= arr";
|
|
assert_eq!(eval(input), "[1, 2, 3]");
|
|
}
|
|
|
|
#[test]
|
|
fn range_expression() {
|
|
assert_eq!(eval_one("0..5"), "[0, 1, 2, 3, 4]");
|
|
}
|
|
}
|