Skip to main content

oxihuman_core/
lua.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3//! Lua 5.4-compatible tree-walk interpreter for plugin and automation support.
4//! Implements lexer, recursive-descent parser, and evaluator with built-ins.
5//! The evaluator is split into lua_interp.rs included below.
6
7use std::cell::RefCell;
8use std::collections::HashMap;
9use std::hash::{Hash, Hasher};
10use std::rc::Rc;
11use std::time::Instant;
12
13// ===== Public Types =====
14
15/// A Lua value, including tables and functions.
16#[derive(Debug, Clone)]
17pub enum LuaValue {
18    Nil,
19    Bool(bool),
20    Int(i64),
21    Float(f64),
22    Str(String),
23    Table(Rc<RefCell<HashMap<TableKey, LuaValue>>>),
24    Function(Rc<LuaFunction>),
25}
26
27/// Hash-safe table key (Nil cannot be a key in Lua).
28#[derive(Debug, Clone, PartialEq, Eq, Hash)]
29pub enum TableKey {
30    Bool(bool),
31    Int(i64),
32    /// Float stored as `to_bits()` for consistent Hash/Eq — NaN == NaN.
33    Float(u64),
34    Str(String),
35    /// Table identity by pointer.
36    TablePtr(usize),
37    /// Function identity by pointer.
38    FuncPtr(usize),
39}
40
41impl TableKey {
42    pub fn from_value(v: &LuaValue) -> Option<TableKey> {
43        match v {
44            LuaValue::Nil => None,
45            LuaValue::Bool(b) => Some(TableKey::Bool(*b)),
46            LuaValue::Int(i) => Some(TableKey::Int(*i)),
47            LuaValue::Float(f) => Some(TableKey::Float(f.to_bits())),
48            LuaValue::Str(s) => Some(TableKey::Str(s.clone())),
49            LuaValue::Table(rc) => Some(TableKey::TablePtr(Rc::as_ptr(rc) as usize)),
50            LuaValue::Function(rc) => Some(TableKey::FuncPtr(Rc::as_ptr(rc) as usize)),
51        }
52    }
53}
54
55impl PartialEq for LuaValue {
56    fn eq(&self, other: &Self) -> bool {
57        match (self, other) {
58            (LuaValue::Nil, LuaValue::Nil) => true,
59            (LuaValue::Bool(a), LuaValue::Bool(b)) => a == b,
60            (LuaValue::Int(a), LuaValue::Int(b)) => a == b,
61            (LuaValue::Float(a), LuaValue::Float(b)) => a.to_bits() == b.to_bits(),
62            (LuaValue::Str(a), LuaValue::Str(b)) => a == b,
63            (LuaValue::Table(a), LuaValue::Table(b)) => Rc::ptr_eq(a, b),
64            (LuaValue::Function(a), LuaValue::Function(b)) => Rc::ptr_eq(a, b),
65            _ => false,
66        }
67    }
68}
69
70impl Eq for LuaValue {}
71
72impl Hash for LuaValue {
73    fn hash<H: Hasher>(&self, state: &mut H) {
74        match self {
75            LuaValue::Nil => 0u8.hash(state),
76            LuaValue::Bool(b) => {
77                1u8.hash(state);
78                b.hash(state);
79            }
80            LuaValue::Int(i) => {
81                2u8.hash(state);
82                i.hash(state);
83            }
84            LuaValue::Float(f) => {
85                3u8.hash(state);
86                f.to_bits().hash(state);
87            }
88            LuaValue::Str(s) => {
89                4u8.hash(state);
90                s.hash(state);
91            }
92            LuaValue::Table(rc) => {
93                5u8.hash(state);
94                (Rc::as_ptr(rc) as usize).hash(state);
95            }
96            LuaValue::Function(rc) => {
97                6u8.hash(state);
98                (Rc::as_ptr(rc) as usize).hash(state);
99            }
100        }
101    }
102}
103
104/// Lua function representation (opaque to external users).
105#[derive(Debug)]
106#[non_exhaustive]
107pub enum LuaFunction {
108    #[doc(hidden)]
109    Closure(LuaClosure),
110    #[doc(hidden)]
111    Builtin(String),
112}
113
114/// Closure data — internal; fields are crate-accessible only.
115#[derive(Debug)]
116pub struct LuaClosure {
117    pub(crate) params: Vec<String>,
118    pub(crate) body: Vec<Stmt>,
119    pub(crate) env: Rc<RefCell<Env>>,
120}
121
122/// Interpreter configuration.
123#[derive(Debug, Clone)]
124pub struct LuaConfig {
125    pub max_stack_depth: usize,
126    pub timeout_ms: u64,
127    pub sandbox: bool,
128}
129
130/// A Lua script with source text and a name for error messages.
131#[derive(Debug, Clone)]
132pub struct LuaScript {
133    pub source: String,
134    pub name: String,
135}
136
137/// Result of executing a Lua script.
138#[derive(Debug, Clone)]
139pub struct LuaResult {
140    pub return_values: Vec<LuaValue>,
141    pub error: Option<String>,
142    pub executed: bool,
143    /// Captured output from `print(...)` calls.
144    pub print_output: Vec<String>,
145}
146
147/// Main stub holding configuration and interpreter state.
148pub struct LuaStub {
149    pub config: LuaConfig,
150    pub globals: HashMap<String, LuaValue>,
151    pub call_count: u64,
152    /// Output collected from print() calls during last execution.
153    pub last_print_output: Vec<String>,
154}
155
156// ===== Internal types used by evaluator =====
157
158/// Execution signal — break out of a block with a reason.
159#[derive(Debug)]
160pub(crate) enum Signal {
161    None,
162    Return(Vec<LuaValue>),
163    Break,
164}
165
166/// Lexical environment.
167#[derive(Debug)]
168pub(crate) struct Env {
169    pub(crate) vars: HashMap<String, LuaValue>,
170    pub(crate) parent: Option<Rc<RefCell<Env>>>,
171}
172
173/// The tree-walk interpreter state.
174pub(crate) struct Interpreter {
175    max_stack_depth: usize,
176    timeout_ms: u64,
177    start_time: Instant,
178    call_depth: usize,
179    stmt_count: u64,
180    print_buf: Rc<RefCell<Vec<String>>>,
181}
182
183// ===== Public API =====
184
185/// Create default configuration.
186pub fn default_lua_config() -> LuaConfig {
187    LuaConfig {
188        max_stack_depth: 200,
189        timeout_ms: 5000,
190        sandbox: true,
191    }
192}
193
194/// Create a new stub with given configuration.
195pub fn new_lua_stub(cfg: LuaConfig) -> LuaStub {
196    LuaStub {
197        config: cfg,
198        globals: HashMap::new(),
199        call_count: 0,
200        last_print_output: Vec::new(),
201    }
202}
203
204/// Set a global variable.
205pub fn lua_set_global(stub: &mut LuaStub, name: &str, val: LuaValue) {
206    stub.globals.insert(name.to_string(), val);
207}
208
209/// Get a global variable.
210pub fn lua_get_global<'a>(stub: &'a LuaStub, name: &str) -> Option<&'a LuaValue> {
211    stub.globals.get(name)
212}
213
214/// Count globals.
215pub fn lua_global_count(stub: &LuaStub) -> usize {
216    stub.globals.len()
217}
218
219/// Execute a Lua script.
220pub fn lua_execute(stub: &mut LuaStub, script: &LuaScript) -> LuaResult {
221    stub.call_count += 1;
222
223    let tokens = match lex(&script.source) {
224        Ok(t) => t,
225        Err(e) => {
226            return LuaResult {
227                return_values: Vec::new(),
228                error: Some(format!("[{}] lexer: {}", script.name, e)),
229                executed: false,
230                print_output: Vec::new(),
231            };
232        }
233    };
234
235    let stmts = match parse(&tokens) {
236        Ok(s) => s,
237        Err(e) => {
238            return LuaResult {
239                return_values: Vec::new(),
240                error: Some(format!("[{}] parse: {}", script.name, e)),
241                executed: false,
242                print_output: Vec::new(),
243            };
244        }
245    };
246
247    let print_buf: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
248    let env = lua_interp::build_global_env(&stub.globals, Rc::clone(&print_buf));
249    let mut interp = Interpreter {
250        max_stack_depth: stub.config.max_stack_depth,
251        timeout_ms: stub.config.timeout_ms,
252        start_time: Instant::now(),
253        call_depth: 0,
254        stmt_count: 0,
255        print_buf: Rc::clone(&print_buf),
256    };
257
258    let result = interp.exec_block(&stmts, &env);
259    let captured = print_buf.borrow().clone();
260    stub.last_print_output = captured.clone();
261
262    match result {
263        Ok(sig) => {
264            let return_values = match sig {
265                Signal::Return(vals) => vals,
266                _ => Vec::new(),
267            };
268            LuaResult {
269                return_values,
270                error: None,
271                executed: true,
272                print_output: captured,
273            }
274        }
275        Err(e) => LuaResult {
276            return_values: Vec::new(),
277            error: Some(format!("[{}] runtime: {}", script.name, e)),
278            executed: true,
279            print_output: captured,
280        },
281    }
282}
283
284/// Create a new script.
285pub fn new_lua_script(name: &str, source: &str) -> LuaScript {
286    LuaScript {
287        source: source.to_string(),
288        name: name.to_string(),
289    }
290}
291
292/// Return the Lua type name for a value.
293pub fn lua_value_type_name(v: &LuaValue) -> &'static str {
294    match v {
295        LuaValue::Nil => "nil",
296        LuaValue::Bool(_) => "boolean",
297        LuaValue::Int(_) => "integer",
298        LuaValue::Float(_) => "float",
299        LuaValue::Str(_) => "string",
300        LuaValue::Table(_) => "table",
301        LuaValue::Function(_) => "function",
302    }
303}
304
305/// Serialize a value to JSON.
306pub fn lua_value_to_json(v: &LuaValue) -> String {
307    lua_value_to_json_depth(v, 0)
308}
309
310fn lua_value_to_json_depth(v: &LuaValue, depth: usize) -> String {
311    if depth > 16 {
312        return "\"[max depth]\"".to_string();
313    }
314    match v {
315        LuaValue::Nil => "null".to_string(),
316        LuaValue::Bool(b) => format!("{}", b),
317        LuaValue::Int(i) => format!("{}", i),
318        LuaValue::Float(f) => format!("{}", f),
319        LuaValue::Str(s) => {
320            format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
321        }
322        LuaValue::Table(rc) => {
323            let map = rc.borrow();
324            let entries: Vec<String> = map
325                .iter()
326                .map(|(k, val)| {
327                    let key_str = match k {
328                        TableKey::Int(i) => format!("\"{}\"", i),
329                        TableKey::Str(s) => format!("\"{}\"", s),
330                        TableKey::Bool(b) => format!("\"{}\"", b),
331                        TableKey::Float(bits) => {
332                            format!("\"{}\"", f64::from_bits(*bits))
333                        }
334                        TableKey::TablePtr(p) => format!("\"table@{}\"", p),
335                        TableKey::FuncPtr(p) => format!("\"func@{}\"", p),
336                    };
337                    format!("{}:{}", key_str, lua_value_to_json_depth(val, depth + 1))
338                })
339                .collect();
340            format!("{{{}}}", entries.join(","))
341        }
342        LuaValue::Function(_) => "\"[function]\"".to_string(),
343    }
344}
345
346/// Serialize a result to JSON.
347pub fn lua_result_to_json(r: &LuaResult) -> String {
348    let vals: Vec<String> = r.return_values.iter().map(lua_value_to_json).collect();
349    let vals_str = vals.join(",");
350    let err_str = match &r.error {
351        Some(e) => format!("\"{}\"", e.replace('"', "\\\"")),
352        None => "null".to_string(),
353    };
354    format!(
355        "{{\"return_values\":[{}],\"error\":{},\"executed\":{}}}",
356        vals_str, err_str, r.executed
357    )
358}
359
360/// Serialize a stub to JSON.
361pub fn lua_stub_to_json(stub: &LuaStub) -> String {
362    let globals_count = stub.globals.len();
363    format!(
364        "{{\"call_count\":{},\"globals_count\":{},\"sandbox\":{}}}",
365        stub.call_count, globals_count, stub.config.sandbox
366    )
367}
368
369// ===== Lexer =====
370
371#[derive(Debug, Clone, PartialEq)]
372enum Token {
373    Number(f64),
374    LuaStr(String),
375    True,
376    False,
377    Nil,
378    Ident(String),
379    Plus,
380    Minus,
381    Star,
382    Slash,
383    Percent,
384    Caret,
385    EqEq,
386    NotEq,
387    Lt,
388    Le,
389    Gt,
390    Ge,
391    Assign,
392    And,
393    Or,
394    Not,
395    Hash,
396    DotDot,
397    LParen,
398    RParen,
399    LBrace,
400    RBrace,
401    LBracket,
402    RBracket,
403    Dot,
404    Comma,
405    Semi,
406    Colon,
407    If,
408    Then,
409    Else,
410    Elseif,
411    End,
412    While,
413    Do,
414    For,
415    In,
416    Return,
417    Local,
418    Function,
419    Repeat,
420    Until,
421    Break,
422    Eof,
423}
424
425fn lex(src: &str) -> Result<Vec<Token>, String> {
426    let chars: Vec<char> = src.chars().collect();
427    let mut pos = 0;
428    let mut tokens = Vec::new();
429
430    while pos < chars.len() {
431        if chars[pos].is_whitespace() {
432            pos += 1;
433            continue;
434        }
435
436        // Line / long comments
437        if pos + 1 < chars.len() && chars[pos] == '-' && chars[pos + 1] == '-' {
438            pos += 2;
439            if pos + 1 < chars.len() && chars[pos] == '[' && chars[pos + 1] == '[' {
440                pos += 2;
441                loop {
442                    if pos + 1 >= chars.len() {
443                        return Err("unterminated long comment".to_string());
444                    }
445                    if chars[pos] == ']' && chars[pos + 1] == ']' {
446                        pos += 2;
447                        break;
448                    }
449                    pos += 1;
450                }
451            } else {
452                while pos < chars.len() && chars[pos] != '\n' {
453                    pos += 1;
454                }
455            }
456            continue;
457        }
458
459        // Decimal numbers (including leading-dot like .5)
460        if chars[pos].is_ascii_digit()
461            || (chars[pos] == '.' && pos + 1 < chars.len() && chars[pos + 1].is_ascii_digit())
462        {
463            let start = pos;
464            while pos < chars.len() && (chars[pos].is_ascii_digit() || chars[pos] == '.') {
465                pos += 1;
466            }
467            if pos < chars.len() && (chars[pos] == 'e' || chars[pos] == 'E') {
468                pos += 1;
469                if pos < chars.len() && (chars[pos] == '+' || chars[pos] == '-') {
470                    pos += 1;
471                }
472                while pos < chars.len() && chars[pos].is_ascii_digit() {
473                    pos += 1;
474                }
475            }
476            let s: String = chars[start..pos].iter().collect();
477            let n: f64 = s.parse().map_err(|_| format!("invalid number: {}", s))?;
478            tokens.push(Token::Number(n));
479            continue;
480        }
481
482        // Hex numbers
483        if chars[pos] == '0'
484            && pos + 1 < chars.len()
485            && (chars[pos + 1] == 'x' || chars[pos + 1] == 'X')
486        {
487            pos += 2;
488            let start = pos;
489            while pos < chars.len() && chars[pos].is_ascii_hexdigit() {
490                pos += 1;
491            }
492            let s: String = chars[start..pos].iter().collect();
493            let n = i64::from_str_radix(&s, 16).map_err(|_| format!("invalid hex: {}", s))?;
494            tokens.push(Token::Number(n as f64));
495            continue;
496        }
497
498        // Quoted strings
499        if chars[pos] == '"' || chars[pos] == '\'' {
500            let quote = chars[pos];
501            pos += 1;
502            let mut s = String::new();
503            while pos < chars.len() && chars[pos] != quote {
504                if chars[pos] == '\\' {
505                    pos += 1;
506                    if pos >= chars.len() {
507                        return Err("unterminated string escape".to_string());
508                    }
509                    match chars[pos] {
510                        'n' => s.push('\n'),
511                        't' => s.push('\t'),
512                        'r' => s.push('\r'),
513                        '\\' => s.push('\\'),
514                        '"' => s.push('"'),
515                        '\'' => s.push('\''),
516                        '0' => s.push('\0'),
517                        c => {
518                            s.push('\\');
519                            s.push(c);
520                        }
521                    }
522                } else {
523                    s.push(chars[pos]);
524                }
525                pos += 1;
526            }
527            if pos >= chars.len() {
528                return Err("unterminated string literal".to_string());
529            }
530            pos += 1;
531            tokens.push(Token::LuaStr(s));
532            continue;
533        }
534
535        // Long strings [[...]]
536        if chars[pos] == '[' && pos + 1 < chars.len() && chars[pos + 1] == '[' {
537            pos += 2;
538            let mut s = String::new();
539            loop {
540                if pos + 1 >= chars.len() {
541                    return Err("unterminated long string".to_string());
542                }
543                if chars[pos] == ']' && chars[pos + 1] == ']' {
544                    pos += 2;
545                    break;
546                }
547                s.push(chars[pos]);
548                pos += 1;
549            }
550            tokens.push(Token::LuaStr(s));
551            continue;
552        }
553
554        // Identifiers and keywords
555        if chars[pos].is_alphabetic() || chars[pos] == '_' {
556            let start = pos;
557            while pos < chars.len() && (chars[pos].is_alphanumeric() || chars[pos] == '_') {
558                pos += 1;
559            }
560            let word: String = chars[start..pos].iter().collect();
561            let tok = match word.as_str() {
562                "true" => Token::True,
563                "false" => Token::False,
564                "nil" => Token::Nil,
565                "and" => Token::And,
566                "or" => Token::Or,
567                "not" => Token::Not,
568                "if" => Token::If,
569                "then" => Token::Then,
570                "else" => Token::Else,
571                "elseif" => Token::Elseif,
572                "end" => Token::End,
573                "while" => Token::While,
574                "do" => Token::Do,
575                "for" => Token::For,
576                "in" => Token::In,
577                "return" => Token::Return,
578                "local" => Token::Local,
579                "function" => Token::Function,
580                "repeat" => Token::Repeat,
581                "until" => Token::Until,
582                "break" => Token::Break,
583                _ => Token::Ident(word),
584            };
585            tokens.push(tok);
586            continue;
587        }
588
589        // Two-character operators
590        if pos + 1 < chars.len() {
591            let two: String = chars[pos..pos + 2].iter().collect();
592            let tok = match two.as_str() {
593                "==" => Some(Token::EqEq),
594                "~=" => Some(Token::NotEq),
595                "<=" => Some(Token::Le),
596                ">=" => Some(Token::Ge),
597                ".." => Some(Token::DotDot),
598                _ => None,
599            };
600            if let Some(t) = tok {
601                tokens.push(t);
602                pos += 2;
603                continue;
604            }
605        }
606
607        // Single-character operators
608        let tok = match chars[pos] {
609            '+' => Token::Plus,
610            '-' => Token::Minus,
611            '*' => Token::Star,
612            '/' => Token::Slash,
613            '%' => Token::Percent,
614            '^' => Token::Caret,
615            '<' => Token::Lt,
616            '>' => Token::Gt,
617            '=' => Token::Assign,
618            '#' => Token::Hash,
619            '(' => Token::LParen,
620            ')' => Token::RParen,
621            '{' => Token::LBrace,
622            '}' => Token::RBrace,
623            '[' => Token::LBracket,
624            ']' => Token::RBracket,
625            '.' => Token::Dot,
626            ',' => Token::Comma,
627            ';' => Token::Semi,
628            ':' => Token::Colon,
629            c => return Err(format!("unexpected character: {:?}", c)),
630        };
631        tokens.push(tok);
632        pos += 1;
633    }
634
635    tokens.push(Token::Eof);
636    Ok(tokens)
637}
638
639// ===== AST =====
640
641#[derive(Debug, Clone)]
642pub(crate) enum Expr {
643    Nil,
644    True,
645    False,
646    Number(f64),
647    Str(String),
648    Var(String),
649    BinOp {
650        op: BinOp,
651        left: Box<Expr>,
652        right: Box<Expr>,
653    },
654    UnOp {
655        op: UnOp,
656        operand: Box<Expr>,
657    },
658    TableCtor(Vec<TableField>),
659    FieldAccess {
660        table: Box<Expr>,
661        field: String,
662    },
663    IndexAccess {
664        table: Box<Expr>,
665        key: Box<Expr>,
666    },
667    FuncCall {
668        func: Box<Expr>,
669        args: Vec<Expr>,
670    },
671    MethodCall {
672        obj: Box<Expr>,
673        method: String,
674        args: Vec<Expr>,
675    },
676    FuncDef {
677        params: Vec<String>,
678        body: Vec<Stmt>,
679    },
680}
681
682#[derive(Debug, Clone)]
683pub(crate) enum TableField {
684    Indexed { key: Expr, val: Expr },
685    Named { name: String, val: Expr },
686    Positional(Expr),
687}
688
689#[derive(Debug, Clone, Copy, PartialEq)]
690pub(crate) enum BinOp {
691    Add,
692    Sub,
693    Mul,
694    Div,
695    Mod,
696    Pow,
697    Eq,
698    Ne,
699    Lt,
700    Le,
701    Gt,
702    Ge,
703    And,
704    Or,
705    Concat,
706}
707
708#[derive(Debug, Clone, Copy, PartialEq)]
709pub(crate) enum UnOp {
710    Neg,
711    Not,
712    Len,
713}
714
715#[derive(Debug, Clone)]
716pub(crate) enum Stmt {
717    Assign {
718        targets: Vec<Expr>,
719        values: Vec<Expr>,
720    },
721    Local {
722        names: Vec<String>,
723        values: Vec<Expr>,
724    },
725    Do(Vec<Stmt>),
726    While {
727        cond: Expr,
728        body: Vec<Stmt>,
729    },
730    Repeat {
731        body: Vec<Stmt>,
732        cond: Expr,
733    },
734    If {
735        cond: Expr,
736        then_block: Vec<Stmt>,
737        elseif_blocks: Vec<(Expr, Vec<Stmt>)>,
738        else_block: Option<Vec<Stmt>>,
739    },
740    NumFor {
741        var: String,
742        start: Expr,
743        limit: Expr,
744        step: Option<Expr>,
745        body: Vec<Stmt>,
746    },
747    GenFor {
748        vars: Vec<String>,
749        iterators: Vec<Expr>,
750        body: Vec<Stmt>,
751    },
752    FuncDef {
753        name: Vec<String>,
754        method: Option<String>,
755        params: Vec<String>,
756        body: Vec<Stmt>,
757    },
758    LocalFunc {
759        name: String,
760        params: Vec<String>,
761        body: Vec<Stmt>,
762    },
763    Return(Vec<Expr>),
764    Break,
765    Expr(Expr),
766}
767
768// ===== Parser =====
769
770struct Parser<'a> {
771    tokens: &'a [Token],
772    pos: usize,
773}
774
775impl<'a> Parser<'a> {
776    fn new(tokens: &'a [Token]) -> Self {
777        Parser { tokens, pos: 0 }
778    }
779
780    fn peek(&self) -> &Token {
781        &self.tokens[self.pos]
782    }
783
784    fn peek_at(&self, offset: usize) -> &Token {
785        let idx = self.pos + offset;
786        if idx < self.tokens.len() {
787            &self.tokens[idx]
788        } else {
789            &Token::Eof
790        }
791    }
792
793    fn advance(&mut self) -> &Token {
794        let t = &self.tokens[self.pos];
795        if self.pos + 1 < self.tokens.len() {
796            self.pos += 1;
797        }
798        t
799    }
800
801    fn expect(&mut self, expected: &Token) -> Result<(), String> {
802        let t = self.advance();
803        if t == expected {
804            Ok(())
805        } else {
806            Err(format!("expected {:?}, got {:?}", expected, t))
807        }
808    }
809
810    fn expect_ident(&mut self) -> Result<String, String> {
811        match self.advance().clone() {
812            Token::Ident(name) => Ok(name),
813            t => Err(format!("expected identifier, got {:?}", t)),
814        }
815    }
816
817    fn parse_block(&mut self) -> Result<Vec<Stmt>, String> {
818        let mut stmts = Vec::new();
819        loop {
820            while self.peek() == &Token::Semi {
821                self.advance();
822            }
823            match self.peek() {
824                Token::Eof | Token::End | Token::Else | Token::Elseif | Token::Until => break,
825                Token::Return => {
826                    self.advance();
827                    let vals = self.parse_expr_list()?;
828                    if self.peek() == &Token::Semi {
829                        self.advance();
830                    }
831                    stmts.push(Stmt::Return(vals));
832                    break;
833                }
834                _ => {
835                    let stmt = self.parse_stmt()?;
836                    stmts.push(stmt);
837                }
838            }
839        }
840        Ok(stmts)
841    }
842
843    fn parse_stmt(&mut self) -> Result<Stmt, String> {
844        match self.peek().clone() {
845            Token::Local => {
846                self.advance();
847                if self.peek() == &Token::Function {
848                    self.advance();
849                    let name = self.expect_ident()?;
850                    let (params, body) = self.parse_func_body()?;
851                    Ok(Stmt::LocalFunc { name, params, body })
852                } else {
853                    let mut names = vec![self.expect_ident()?];
854                    while self.peek() == &Token::Comma {
855                        self.advance();
856                        names.push(self.expect_ident()?);
857                    }
858                    let values = if self.peek() == &Token::Assign {
859                        self.advance();
860                        self.parse_expr_list()?
861                    } else {
862                        Vec::new()
863                    };
864                    Ok(Stmt::Local { names, values })
865                }
866            }
867            Token::Function => {
868                self.advance();
869                let mut name_parts = vec![self.expect_ident()?];
870                let mut method = None;
871                loop {
872                    match self.peek() {
873                        Token::Dot => {
874                            self.advance();
875                            name_parts.push(self.expect_ident()?);
876                        }
877                        Token::Colon => {
878                            self.advance();
879                            method = Some(self.expect_ident()?);
880                            break;
881                        }
882                        _ => break,
883                    }
884                }
885                let (mut params, body) = self.parse_func_body()?;
886                if method.is_some() {
887                    params.insert(0, "self".to_string());
888                }
889                Ok(Stmt::FuncDef {
890                    name: name_parts,
891                    method,
892                    params,
893                    body,
894                })
895            }
896            Token::If => self.parse_if(),
897            Token::While => {
898                self.advance();
899                let cond = self.parse_expr()?;
900                self.expect(&Token::Do)?;
901                let body = self.parse_block()?;
902                self.expect(&Token::End)?;
903                Ok(Stmt::While { cond, body })
904            }
905            Token::Repeat => {
906                self.advance();
907                let body = self.parse_block()?;
908                self.expect(&Token::Until)?;
909                let cond = self.parse_expr()?;
910                Ok(Stmt::Repeat { body, cond })
911            }
912            Token::For => {
913                self.advance();
914                let first_var = self.expect_ident()?;
915                if self.peek() == &Token::Assign {
916                    self.advance();
917                    let start = self.parse_expr()?;
918                    self.expect(&Token::Comma)?;
919                    let limit = self.parse_expr()?;
920                    let step = if self.peek() == &Token::Comma {
921                        self.advance();
922                        Some(self.parse_expr()?)
923                    } else {
924                        None
925                    };
926                    self.expect(&Token::Do)?;
927                    let body = self.parse_block()?;
928                    self.expect(&Token::End)?;
929                    Ok(Stmt::NumFor {
930                        var: first_var,
931                        start,
932                        limit,
933                        step,
934                        body,
935                    })
936                } else {
937                    let mut vars = vec![first_var];
938                    while self.peek() == &Token::Comma {
939                        self.advance();
940                        vars.push(self.expect_ident()?);
941                    }
942                    self.expect(&Token::In)?;
943                    let iterators = self.parse_expr_list()?;
944                    self.expect(&Token::Do)?;
945                    let body = self.parse_block()?;
946                    self.expect(&Token::End)?;
947                    Ok(Stmt::GenFor {
948                        vars,
949                        iterators,
950                        body,
951                    })
952                }
953            }
954            Token::Do => {
955                self.advance();
956                let block = self.parse_block()?;
957                self.expect(&Token::End)?;
958                Ok(Stmt::Do(block))
959            }
960            Token::Break => {
961                self.advance();
962                Ok(Stmt::Break)
963            }
964            _ => {
965                let expr = self.parse_suffix_expr()?;
966                let mut targets = vec![expr];
967                while self.peek() == &Token::Comma {
968                    self.advance();
969                    targets.push(self.parse_suffix_expr()?);
970                }
971                if self.peek() == &Token::Assign {
972                    self.advance();
973                    let values = self.parse_expr_list()?;
974                    Ok(Stmt::Assign { targets, values })
975                } else if targets.len() == 1 {
976                    match &targets[0] {
977                        Expr::FuncCall { .. } | Expr::MethodCall { .. } => {
978                            Ok(Stmt::Expr(targets.remove(0)))
979                        }
980                        _ => Err(format!(
981                            "unexpected expression as statement: {:?}",
982                            targets[0]
983                        )),
984                    }
985                } else {
986                    Err("multi-target without assignment".to_string())
987                }
988            }
989        }
990    }
991
992    fn parse_if(&mut self) -> Result<Stmt, String> {
993        self.expect(&Token::If)?;
994        let cond = self.parse_expr()?;
995        self.expect(&Token::Then)?;
996        let then_block = self.parse_block()?;
997        let mut elseif_blocks = Vec::new();
998        let mut else_block = None;
999        loop {
1000            match self.peek() {
1001                Token::Elseif => {
1002                    self.advance();
1003                    let ec = self.parse_expr()?;
1004                    self.expect(&Token::Then)?;
1005                    let eb = self.parse_block()?;
1006                    elseif_blocks.push((ec, eb));
1007                }
1008                Token::Else => {
1009                    self.advance();
1010                    else_block = Some(self.parse_block()?);
1011                    break;
1012                }
1013                _ => break,
1014            }
1015        }
1016        self.expect(&Token::End)?;
1017        Ok(Stmt::If {
1018            cond,
1019            then_block,
1020            elseif_blocks,
1021            else_block,
1022        })
1023    }
1024
1025    fn parse_func_body(&mut self) -> Result<(Vec<String>, Vec<Stmt>), String> {
1026        self.expect(&Token::LParen)?;
1027        let mut params = Vec::new();
1028        if self.peek() != &Token::RParen {
1029            params.push(self.expect_ident()?);
1030            while self.peek() == &Token::Comma {
1031                self.advance();
1032                if let Token::DotDot = self.peek() {
1033                    self.advance();
1034                    if self.peek() == &Token::Dot {
1035                        self.advance();
1036                    }
1037                    break;
1038                }
1039                params.push(self.expect_ident()?);
1040            }
1041        }
1042        self.expect(&Token::RParen)?;
1043        let body = self.parse_block()?;
1044        self.expect(&Token::End)?;
1045        Ok((params, body))
1046    }
1047
1048    fn parse_expr_list(&mut self) -> Result<Vec<Expr>, String> {
1049        let first = self.parse_expr()?;
1050        let mut exprs = vec![first];
1051        while self.peek() == &Token::Comma {
1052            self.advance();
1053            exprs.push(self.parse_expr()?);
1054        }
1055        Ok(exprs)
1056    }
1057
1058    fn parse_expr(&mut self) -> Result<Expr, String> {
1059        self.parse_or_expr()
1060    }
1061
1062    fn parse_or_expr(&mut self) -> Result<Expr, String> {
1063        let mut left = self.parse_and_expr()?;
1064        while self.peek() == &Token::Or {
1065            self.advance();
1066            let right = self.parse_and_expr()?;
1067            left = Expr::BinOp {
1068                op: BinOp::Or,
1069                left: Box::new(left),
1070                right: Box::new(right),
1071            };
1072        }
1073        Ok(left)
1074    }
1075
1076    fn parse_and_expr(&mut self) -> Result<Expr, String> {
1077        let mut left = self.parse_compare_expr()?;
1078        while self.peek() == &Token::And {
1079            self.advance();
1080            let right = self.parse_compare_expr()?;
1081            left = Expr::BinOp {
1082                op: BinOp::And,
1083                left: Box::new(left),
1084                right: Box::new(right),
1085            };
1086        }
1087        Ok(left)
1088    }
1089
1090    fn parse_compare_expr(&mut self) -> Result<Expr, String> {
1091        let left = self.parse_concat_expr()?;
1092        let op = match self.peek() {
1093            Token::EqEq => BinOp::Eq,
1094            Token::NotEq => BinOp::Ne,
1095            Token::Lt => BinOp::Lt,
1096            Token::Le => BinOp::Le,
1097            Token::Gt => BinOp::Gt,
1098            Token::Ge => BinOp::Ge,
1099            _ => return Ok(left),
1100        };
1101        self.advance();
1102        let right = self.parse_concat_expr()?;
1103        Ok(Expr::BinOp {
1104            op,
1105            left: Box::new(left),
1106            right: Box::new(right),
1107        })
1108    }
1109
1110    fn parse_concat_expr(&mut self) -> Result<Expr, String> {
1111        let left = self.parse_add_expr()?;
1112        if self.peek() == &Token::DotDot {
1113            self.advance();
1114            let right = self.parse_concat_expr()?;
1115            return Ok(Expr::BinOp {
1116                op: BinOp::Concat,
1117                left: Box::new(left),
1118                right: Box::new(right),
1119            });
1120        }
1121        Ok(left)
1122    }
1123
1124    fn parse_add_expr(&mut self) -> Result<Expr, String> {
1125        let mut left = self.parse_mul_expr()?;
1126        loop {
1127            let op = match self.peek() {
1128                Token::Plus => BinOp::Add,
1129                Token::Minus => BinOp::Sub,
1130                _ => break,
1131            };
1132            self.advance();
1133            let right = self.parse_mul_expr()?;
1134            left = Expr::BinOp {
1135                op,
1136                left: Box::new(left),
1137                right: Box::new(right),
1138            };
1139        }
1140        Ok(left)
1141    }
1142
1143    fn parse_mul_expr(&mut self) -> Result<Expr, String> {
1144        let mut left = self.parse_unary_expr()?;
1145        loop {
1146            let op = match self.peek() {
1147                Token::Star => BinOp::Mul,
1148                Token::Slash => BinOp::Div,
1149                Token::Percent => BinOp::Mod,
1150                _ => break,
1151            };
1152            self.advance();
1153            let right = self.parse_unary_expr()?;
1154            left = Expr::BinOp {
1155                op,
1156                left: Box::new(left),
1157                right: Box::new(right),
1158            };
1159        }
1160        Ok(left)
1161    }
1162
1163    fn parse_unary_expr(&mut self) -> Result<Expr, String> {
1164        match self.peek().clone() {
1165            Token::Minus => {
1166                self.advance();
1167                let e = self.parse_unary_expr()?;
1168                Ok(Expr::UnOp {
1169                    op: UnOp::Neg,
1170                    operand: Box::new(e),
1171                })
1172            }
1173            Token::Not => {
1174                self.advance();
1175                let e = self.parse_unary_expr()?;
1176                Ok(Expr::UnOp {
1177                    op: UnOp::Not,
1178                    operand: Box::new(e),
1179                })
1180            }
1181            Token::Hash => {
1182                self.advance();
1183                let e = self.parse_unary_expr()?;
1184                Ok(Expr::UnOp {
1185                    op: UnOp::Len,
1186                    operand: Box::new(e),
1187                })
1188            }
1189            _ => self.parse_power_expr(),
1190        }
1191    }
1192
1193    fn parse_power_expr(&mut self) -> Result<Expr, String> {
1194        let base = self.parse_suffix_expr()?;
1195        if self.peek() == &Token::Caret {
1196            self.advance();
1197            let exp = self.parse_unary_expr()?;
1198            return Ok(Expr::BinOp {
1199                op: BinOp::Pow,
1200                left: Box::new(base),
1201                right: Box::new(exp),
1202            });
1203        }
1204        Ok(base)
1205    }
1206
1207    fn parse_suffix_expr(&mut self) -> Result<Expr, String> {
1208        let mut expr = self.parse_primary_expr()?;
1209        loop {
1210            match self.peek().clone() {
1211                Token::Dot => {
1212                    self.advance();
1213                    let field = self.expect_ident()?;
1214                    expr = Expr::FieldAccess {
1215                        table: Box::new(expr),
1216                        field,
1217                    };
1218                }
1219                Token::LBracket => {
1220                    self.advance();
1221                    let key = self.parse_expr()?;
1222                    self.expect(&Token::RBracket)?;
1223                    expr = Expr::IndexAccess {
1224                        table: Box::new(expr),
1225                        key: Box::new(key),
1226                    };
1227                }
1228                Token::Colon => {
1229                    self.advance();
1230                    let method = self.expect_ident()?;
1231                    let args = self.parse_call_args()?;
1232                    expr = Expr::MethodCall {
1233                        obj: Box::new(expr),
1234                        method,
1235                        args,
1236                    };
1237                }
1238                Token::LParen | Token::LBrace | Token::LuaStr(_) => {
1239                    let args = self.parse_call_args()?;
1240                    expr = Expr::FuncCall {
1241                        func: Box::new(expr),
1242                        args,
1243                    };
1244                }
1245                _ => break,
1246            }
1247        }
1248        Ok(expr)
1249    }
1250
1251    fn parse_call_args(&mut self) -> Result<Vec<Expr>, String> {
1252        match self.peek().clone() {
1253            Token::LParen => {
1254                self.advance();
1255                if self.peek() == &Token::RParen {
1256                    self.advance();
1257                    return Ok(Vec::new());
1258                }
1259                let args = self.parse_expr_list()?;
1260                self.expect(&Token::RParen)?;
1261                Ok(args)
1262            }
1263            Token::LBrace => {
1264                let tbl = self.parse_table_ctor()?;
1265                Ok(vec![tbl])
1266            }
1267            Token::LuaStr(s) => {
1268                let s = s.clone();
1269                self.advance();
1270                Ok(vec![Expr::Str(s)])
1271            }
1272            t => Err(format!("expected function call args, got {:?}", t)),
1273        }
1274    }
1275
1276    fn parse_primary_expr(&mut self) -> Result<Expr, String> {
1277        match self.peek().clone() {
1278            Token::Nil => {
1279                self.advance();
1280                Ok(Expr::Nil)
1281            }
1282            Token::True => {
1283                self.advance();
1284                Ok(Expr::True)
1285            }
1286            Token::False => {
1287                self.advance();
1288                Ok(Expr::False)
1289            }
1290            Token::Number(n) => {
1291                let num = n;
1292                self.advance();
1293                Ok(Expr::Number(num))
1294            }
1295            Token::LuaStr(s) => {
1296                let s = s.clone();
1297                self.advance();
1298                Ok(Expr::Str(s))
1299            }
1300            Token::Ident(name) => {
1301                let name = name.clone();
1302                self.advance();
1303                Ok(Expr::Var(name))
1304            }
1305            Token::LParen => {
1306                self.advance();
1307                let e = self.parse_expr()?;
1308                self.expect(&Token::RParen)?;
1309                Ok(e)
1310            }
1311            Token::LBrace => self.parse_table_ctor(),
1312            Token::Function => {
1313                self.advance();
1314                let (params, body) = self.parse_func_body()?;
1315                Ok(Expr::FuncDef { params, body })
1316            }
1317            t => Err(format!("unexpected token in expression: {:?}", t)),
1318        }
1319    }
1320
1321    fn parse_table_ctor(&mut self) -> Result<Expr, String> {
1322        self.expect(&Token::LBrace)?;
1323        let mut fields = Vec::new();
1324        while self.peek() != &Token::RBrace {
1325            let field = match self.peek().clone() {
1326                Token::LBracket => {
1327                    self.advance();
1328                    let key = self.parse_expr()?;
1329                    self.expect(&Token::RBracket)?;
1330                    self.expect(&Token::Assign)?;
1331                    let val = self.parse_expr()?;
1332                    TableField::Indexed { key, val }
1333                }
1334                Token::Ident(_) if self.peek_at(1) == &Token::Assign => {
1335                    if let Token::Ident(name) = self.advance().clone() {
1336                        self.advance(); // '='
1337                        let val = self.parse_expr()?;
1338                        TableField::Named { name, val }
1339                    } else {
1340                        unreachable!()
1341                    }
1342                }
1343                _ => {
1344                    let val = self.parse_expr()?;
1345                    TableField::Positional(val)
1346                }
1347            };
1348            fields.push(field);
1349            match self.peek() {
1350                Token::Comma | Token::Semi => {
1351                    self.advance();
1352                }
1353                _ => break,
1354            }
1355        }
1356        self.expect(&Token::RBrace)?;
1357        Ok(Expr::TableCtor(fields))
1358    }
1359}
1360
1361fn parse(tokens: &[Token]) -> Result<Vec<Stmt>, String> {
1362    let mut p = Parser::new(tokens);
1363    let block = p.parse_block()?;
1364    if p.peek() != &Token::Eof {
1365        return Err(format!("unexpected token at end: {:?}", p.peek()));
1366    }
1367    Ok(block)
1368}
1369
1370// ===== Evaluator (included from lua_interp.rs) =====
1371
1372#[path = "lua_interp.rs"]
1373mod lua_interp;
1374
1375// ===== Tests =====
1376
1377#[cfg(test)]
1378mod tests {
1379    use super::*;
1380
1381    fn run(src: &str) -> LuaResult {
1382        let cfg = default_lua_config();
1383        let mut stub = new_lua_stub(cfg);
1384        let script = new_lua_script("test", src);
1385        lua_execute(&mut stub, &script)
1386    }
1387
1388    #[test]
1389    fn test_default_config() {
1390        let cfg = default_lua_config();
1391        assert_eq!(cfg.max_stack_depth, 200);
1392        assert_eq!(cfg.timeout_ms, 5000);
1393        assert!(cfg.sandbox);
1394    }
1395
1396    #[test]
1397    fn test_new_stub() {
1398        let cfg = default_lua_config();
1399        let stub = new_lua_stub(cfg);
1400        assert_eq!(stub.call_count, 0);
1401        assert_eq!(lua_global_count(&stub), 0);
1402    }
1403
1404    #[test]
1405    fn test_set_get_global() {
1406        let cfg = default_lua_config();
1407        let mut stub = new_lua_stub(cfg);
1408        lua_set_global(&mut stub, "x", LuaValue::Int(42));
1409        let v = lua_get_global(&stub, "x");
1410        assert!(v.is_some());
1411        if let Some(LuaValue::Int(i)) = v {
1412            assert_eq!(*i, 42);
1413        } else {
1414            panic!("expected Int");
1415        }
1416    }
1417
1418    #[test]
1419    fn test_execute_increments_count() {
1420        let cfg = default_lua_config();
1421        let mut stub = new_lua_stub(cfg);
1422        let script = new_lua_script("test", "return 1");
1423        let result = lua_execute(&mut stub, &script);
1424        assert!(result.executed);
1425        assert!(result.error.is_none());
1426        assert_eq!(stub.call_count, 1);
1427    }
1428
1429    #[test]
1430    fn test_value_type_names() {
1431        assert_eq!(lua_value_type_name(&LuaValue::Nil), "nil");
1432        assert_eq!(lua_value_type_name(&LuaValue::Bool(true)), "boolean");
1433        assert_eq!(lua_value_type_name(&LuaValue::Int(0)), "integer");
1434        assert_eq!(lua_value_type_name(&LuaValue::Float(0.0)), "float");
1435        assert_eq!(lua_value_type_name(&LuaValue::Str(String::new())), "string");
1436    }
1437
1438    #[test]
1439    fn test_global_count_and_update() {
1440        let cfg = default_lua_config();
1441        let mut stub = new_lua_stub(cfg);
1442        lua_set_global(&mut stub, "a", LuaValue::Bool(false));
1443        lua_set_global(&mut stub, "b", LuaValue::Float(std::f64::consts::PI));
1444        assert_eq!(lua_global_count(&stub), 2);
1445        lua_set_global(&mut stub, "a", LuaValue::Bool(true));
1446        assert_eq!(lua_global_count(&stub), 2);
1447    }
1448
1449    #[test]
1450    fn test_result_to_json() {
1451        let r = LuaResult {
1452            return_values: vec![LuaValue::Nil],
1453            error: None,
1454            executed: true,
1455            print_output: Vec::new(),
1456        };
1457        let json = lua_result_to_json(&r);
1458        assert!(json.contains("return_values"));
1459        assert!(json.contains("null"));
1460    }
1461
1462    #[test]
1463    fn test_stub_to_json() {
1464        let cfg = default_lua_config();
1465        let stub = new_lua_stub(cfg);
1466        let json = lua_stub_to_json(&stub);
1467        assert!(json.contains("call_count"));
1468        assert!(json.contains("sandbox"));
1469    }
1470
1471    #[test]
1472    fn test_execute_empty_script() {
1473        let r = run("");
1474        assert!(r.error.is_none());
1475        assert!(r.executed);
1476    }
1477
1478    #[test]
1479    fn test_execute_literal_return() {
1480        let r = run("return 42");
1481        assert!(r.error.is_none());
1482        assert_eq!(r.return_values.len(), 1);
1483        assert!(matches!(r.return_values[0], LuaValue::Int(42)));
1484    }
1485
1486    #[test]
1487    fn test_execute_arithmetic() {
1488        let r = run("return 2 + 3 * 4");
1489        assert!(r.error.is_none());
1490        assert_eq!(r.return_values.len(), 1);
1491        assert!(matches!(r.return_values[0], LuaValue::Int(14)));
1492    }
1493
1494    #[test]
1495    fn test_execute_string_concat() {
1496        let r = run(r#"return "hello" .. " " .. "world""#);
1497        assert!(r.error.is_none());
1498        if let LuaValue::Str(s) = &r.return_values[0] {
1499            assert_eq!(s, "hello world");
1500        } else {
1501            panic!("expected string");
1502        }
1503    }
1504
1505    #[test]
1506    fn test_execute_if_then_else() {
1507        let r = run("if 1 > 0 then return 1 else return 0 end");
1508        assert!(r.error.is_none());
1509        assert!(matches!(r.return_values[0], LuaValue::Int(1)));
1510    }
1511
1512    #[test]
1513    fn test_execute_while_loop() {
1514        let r = run("local s = 0\nfor i = 1, 10 do s = s + i end\nreturn s");
1515        assert!(r.error.is_none(), "error: {:?}", r.error);
1516        assert!(matches!(r.return_values[0], LuaValue::Int(55)));
1517    }
1518
1519    #[test]
1520    fn test_global_set_get() {
1521        let cfg = default_lua_config();
1522        let mut stub = new_lua_stub(cfg);
1523        lua_set_global(&mut stub, "myval", LuaValue::Int(99));
1524        let script = new_lua_script("test", "return myval");
1525        let r = lua_execute(&mut stub, &script);
1526        assert!(r.error.is_none());
1527        assert!(matches!(r.return_values[0], LuaValue::Int(99)));
1528    }
1529
1530    #[test]
1531    fn test_execute_function_def_call() {
1532        let r = run("function add(a, b) return a + b end\nreturn add(3, 4)");
1533        assert!(r.error.is_none(), "error: {:?}", r.error);
1534        assert!(matches!(r.return_values[0], LuaValue::Int(7)));
1535    }
1536
1537    #[test]
1538    fn test_error_on_syntax_error() {
1539        let r = run("return )(garbage");
1540        assert!(r.error.is_some());
1541        assert!(!r.executed);
1542    }
1543
1544    #[test]
1545    fn test_max_stack_depth() {
1546        // Use a very low stack depth to test graceful overflow detection
1547        // without causing Rust's own stack overflow.
1548        let cfg = LuaConfig {
1549            max_stack_depth: 20,
1550            timeout_ms: 5000,
1551            sandbox: true,
1552        };
1553        let mut stub = new_lua_stub(cfg);
1554        let script = new_lua_script("test", "function inf() return inf() end\ninf()");
1555        let r = lua_execute(&mut stub, &script);
1556        assert!(r.error.is_some());
1557        let err = r.error.as_deref().unwrap_or("");
1558        assert!(
1559            err.contains("stack overflow") || err.contains("stack"),
1560            "error was: {}",
1561            err
1562        );
1563    }
1564
1565    #[test]
1566    fn test_print_output_captured() {
1567        let r = run(r#"print("hello")"#);
1568        assert!(r.error.is_none(), "error: {:?}", r.error);
1569        assert_eq!(r.print_output.len(), 1);
1570        assert!(r.print_output[0].contains("hello"));
1571    }
1572
1573    #[test]
1574    fn test_table_operations() {
1575        let r = run("local t = {10, 20, 30}\nreturn t[1] + t[2] + t[3]");
1576        assert!(r.error.is_none(), "error: {:?}", r.error);
1577        assert!(matches!(r.return_values[0], LuaValue::Int(60)));
1578    }
1579
1580    #[test]
1581    fn test_elseif_chain() {
1582        let r = run(
1583            "local x = 5\nif x == 1 then return 1\nelseif x == 5 then return 5\nelse return 0 end",
1584        );
1585        assert!(r.error.is_none());
1586        assert!(matches!(r.return_values[0], LuaValue::Int(5)));
1587    }
1588
1589    #[test]
1590    fn test_string_concat_and_tostring() {
1591        let r = run(r#"return tostring(42) .. " bottles""#);
1592        assert!(r.error.is_none(), "error: {:?}", r.error);
1593        if let LuaValue::Str(s) = &r.return_values[0] {
1594            assert_eq!(s, "42 bottles");
1595        } else {
1596            panic!("expected string");
1597        }
1598    }
1599
1600    #[test]
1601    fn test_math_floor_ceil() {
1602        let r = run("return math.floor(3.7)");
1603        assert!(r.error.is_none(), "error: {:?}", r.error);
1604        assert!(matches!(r.return_values[0], LuaValue::Int(3)));
1605    }
1606
1607    #[test]
1608    fn test_ipairs_loop() {
1609        let r = run(
1610            "local s = 0\nlocal t = {1,2,3,4,5}\nfor i,v in ipairs(t) do s = s + v end\nreturn s",
1611        );
1612        assert!(r.error.is_none(), "error: {:?}", r.error);
1613        assert!(matches!(r.return_values[0], LuaValue::Int(15)));
1614    }
1615}