Swiftly/core/src/interp.rs

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]");
}
}