use crate::eval_ptr::EvalPtr;
use crate::interp::Interp;
use crate::list;
use crate::parser::Word;
use crate::tokenizer::Tokenizer;
use crate::*;
type DatumResult = Result<Datum, Exception>;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub(crate) enum Type {
    Int,
    Float,
    String,
}
#[derive(Debug, PartialEq)]
pub(crate) struct Datum {
    vtype: Type,
    int: MoltInt,
    flt: MoltFloat,
    str: String,
}
impl Datum {
    fn none() -> Self {
        Self {
            vtype: Type::String,
            int: 0,
            flt: 0.0,
            str: String::new(),
        }
    }
    pub(crate) fn int(int: MoltInt) -> Self {
        Self {
            vtype: Type::Int,
            int,
            flt: 0.0,
            str: String::new(),
        }
    }
    pub(crate) fn float(flt: MoltFloat) -> Self {
        Self {
            vtype: Type::Float,
            int: 0,
            flt,
            str: String::new(),
        }
    }
    fn string(string: &str) -> Self {
        Self {
            vtype: Type::String,
            int: 0,
            flt: 0.0,
            str: string.to_string(),
        }
    }
    
    fn is_true(&self) -> bool {
        match self.vtype {
            Type::Int => self.int != 0,
            _ => {
                panic!("Datum::is_true called for non-integer");
            }
        }
    }
}
const MAX_MATH_ARGS: usize = 2;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
enum ArgType {
    None,
    Float,  
    Int,    
    Number, 
}
type MathFunc = fn(args: &[Datum; MAX_MATH_ARGS]) -> DatumResult;
struct BuiltinFunc {
    name: &'static str,
    num_args: usize,
    arg_types: [ArgType; MAX_MATH_ARGS],
    func: MathFunc,
}
const FUNC_TABLE: [BuiltinFunc; 4] = [
    BuiltinFunc {
        name: "abs",
        num_args: 1,
        arg_types: [ArgType::Number, ArgType::None],
        func: expr_abs_func,
    },
    BuiltinFunc {
        name: "double",
        num_args: 1,
        arg_types: [ArgType::Number, ArgType::None],
        func: expr_double_func,
    },
    BuiltinFunc {
        name: "int",
        num_args: 1,
        arg_types: [ArgType::Number, ArgType::None],
        func: expr_int_func,
    },
    BuiltinFunc {
        name: "round",
        num_args: 1,
        arg_types: [ArgType::Number, ArgType::None],
        func: expr_round_func,
    },
];
struct ExprInfo<'a> {
    
    original_expr: String,
    
    expr: Tokenizer<'a>,
    
    token: i32,
    
    no_eval: i32,
}
impl<'a> ExprInfo<'a> {
    fn new(expr: &'a str) -> Self {
        Self {
            original_expr: expr.to_string(),
            expr: Tokenizer::new(expr),
            token: -1,
            no_eval: 0,
        }
    }
}
const VALUE: i32 = 0;
const OPEN_PAREN: i32 = 1;
const CLOSE_PAREN: i32 = 2;
const COMMA: i32 = 3;
const END: i32 = 4;
const UNKNOWN: i32 = 5;
const MULT: i32 = 8;
const DIVIDE: i32 = 9;
const MOD: i32 = 10;
const PLUS: i32 = 11;
const MINUS: i32 = 12;
const LEFT_SHIFT: i32 = 13;
const RIGHT_SHIFT: i32 = 14;
const LESS: i32 = 15;
const GREATER: i32 = 16;
const LEQ: i32 = 17;
const GEQ: i32 = 18;
const EQUAL: i32 = 19;
const NEQ: i32 = 20;
const STRING_EQ: i32 = 21;
const STRING_NE: i32 = 22;
const IN: i32 = 23;
const NI: i32 = 24;
const BIT_AND: i32 = 25;
const BIT_XOR: i32 = 26;
const BIT_OR: i32 = 27;
const AND: i32 = 28;
const OR: i32 = 29;
const QUESTY: i32 = 30;
const COLON: i32 = 31;
const UNARY_MINUS: i32 = 32;
const UNARY_PLUS: i32 = 33;
const NOT: i32 = 34;
const BIT_NOT: i32 = 35;
const PREC_TABLE: [i32; 36] = [
    0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 
    13, 13, 
    12, 12, 
    11, 11, 11, 11, 
    10, 10, 
    9, 9, 
    8, 8, 
    7, 
    6, 
    5, 
    4, 
    3, 
    2, 
    1, 
    13, 13, 13, 13, 
];
const OP_STRINGS: [&str; 36] = [
    "VALUE", "(", ")", ",", "END", "UNKNOWN", "6", "7", "*", "/", "%", "+", "-", "<<", ">>", "<",
    ">", "<=", ">=", "==", "!=", "eq", "ne", "in", "ni", "&", "^", "|", "&&", "||", "?", ":", "-",
    "+", "!", "~",
];
pub fn expr(interp: &mut Interp, expr: &Value) -> MoltResult {
    let value = expr_top_level(interp, expr.as_str())?;
    match value.vtype {
        Type::Int => molt_ok!(Value::from(value.int)),
        Type::Float => molt_ok!(Value::from(value.flt)),
        Type::String => molt_ok!(Value::from(value.str)),
    }
}
fn expr_top_level<'a>(interp: &mut Interp, string: &'a str) -> DatumResult {
    let info = &mut ExprInfo::new(string);
    let result = expr_get_value(interp, info, -1);
    match result {
        Ok(value) => {
            if info.token != END {
                return molt_err!("syntax error in expression \"{}\"", string);
            }
            if value.vtype == Type::Float {
                
            }
            Ok(value)
        }
        Err(exception) => match exception.code() {
            ResultCode::Break => molt_err!("invoked \"break\" outside of a loop"),
            ResultCode::Continue => molt_err!("invoked \"continue\" outside of a loop"),
            _ => Err(exception),
        },
    }
}
#[allow(clippy::collapsible_if)]
#[allow(clippy::cognitive_complexity)]
#[allow(clippy::float_cmp)]
fn expr_get_value<'a>(interp: &mut Interp, info: &'a mut ExprInfo, prec: i32) -> DatumResult {
    
    
    let mut got_op = false;
    let mut value = expr_lex(interp, info)?;
    let mut value2: Datum;
    let mut operator: i32;
    if info.token == OPEN_PAREN {
        
        value = expr_get_value(interp, info, -1)?;
        if info.token != CLOSE_PAREN {
            return molt_err!(
                "unmatched parentheses in expression \"{}\"",
                info.original_expr
            );
        }
    } else {
        if info.token == MINUS {
            info.token = UNARY_MINUS;
        }
        if info.token == PLUS {
            info.token = UNARY_PLUS;
        }
        if info.token >= UNARY_MINUS {
            
            operator = info.token;
            value = expr_get_value(interp, info, PREC_TABLE[info.token as usize])?;
            if info.no_eval == 0 {
                match operator {
                    UNARY_MINUS => match value.vtype {
                        Type::Int => {
                            value.int = -value.int;
                        }
                        Type::Float => {
                            value.flt = -value.flt;
                        }
                        _ => {
                            return illegal_type(value.vtype, operator);
                        }
                    },
                    UNARY_PLUS => {
                        if !value.is_numeric() {
                            return illegal_type(value.vtype, operator);
                        }
                    }
                    NOT => {
                        match value.vtype {
                            Type::Int => {
                                
                                
                                if value.int == 0 {
                                    value.int = 1;
                                } else {
                                    value.int = 0;
                                }
                            }
                            Type::Float => {
                                if value.flt == 0.0 {
                                    value = Datum::int(1);
                                } else {
                                    value = Datum::int(0);
                                }
                            }
                            _ => {
                                return illegal_type(value.vtype, operator);
                            }
                        }
                    }
                    BIT_NOT => {
                        if let Type::Int = value.vtype {
                            
                            value.int = !value.int;
                        } else {
                            return illegal_type(value.vtype, operator);
                        }
                    }
                    _ => {
                        return molt_err!("unknown unary op: \"{}\"", operator);
                    }
                }
            }
            got_op = true;
        } else if info.token != VALUE {
            return syntax_error(info);
        }
    }
    
    if !got_op {
        
        
        
        let _ = expr_lex(interp, info)?;
    }
    loop {
        operator = info.token;
        
        if operator < MULT || operator >= UNARY_MINUS {
            if operator == END || operator == CLOSE_PAREN || operator == COMMA {
                return Ok(value);
            } else {
                return syntax_error(info);
            }
        }
        if PREC_TABLE[operator as usize] <= prec {
            return Ok(value);
        }
        
        
        
        if operator == AND || operator == OR || operator == QUESTY {
            
            
            match value.vtype {
                Type::Float => {
                    if value.flt == 0.0 {
                        value = Datum::int(0);
                    } else {
                        value = Datum::int(1);
                    }
                }
                Type::String => {
                    if info.no_eval == 0 {
                        return illegal_type(value.vtype, operator);
                    }
                    value = Datum::int(0);
                }
                _ => {}
            }
            if (operator == AND && !value.is_true()) || (operator == OR && value.is_true()) {
                
                
                info.no_eval += 1;
                let _ = expr_get_value(interp, info, PREC_TABLE[operator as usize])?;
                info.no_eval -= 1;
                if operator == OR {
                    value = Datum::int(1);
                }
                
                continue;
            } else if operator == QUESTY {
                
                
                
                if value.int != 0 {
                    value = expr_get_value(interp, info, PREC_TABLE[QUESTY as usize] - 1)?;
                    if info.token != COLON {
                        return syntax_error(info);
                    }
                    info.no_eval += 1;
                    value2 = expr_get_value(interp, info, PREC_TABLE[QUESTY as usize] - 1)?;
                    info.no_eval -= 1;
                } else {
                    info.no_eval += 1;
                    value2 = expr_get_value(interp, info, PREC_TABLE[QUESTY as usize] - 1)?;
                    info.no_eval -= 1;
                    if info.token != COLON {
                        return syntax_error(info);
                    }
                    value = expr_get_value(interp, info, PREC_TABLE[QUESTY as usize] - 1)?;
                }
            } else {
                value2 = expr_get_value(interp, info, PREC_TABLE[operator as usize])?;
            }
        } else {
            value2 = expr_get_value(interp, info, PREC_TABLE[operator as usize])?;
        }
        if info.token < MULT
            && info.token != VALUE
            && info.token != END
            && info.token != COMMA
            && info.token != CLOSE_PAREN
        {
            return syntax_error(info);
        }
        if info.no_eval > 0 {
            continue;
        }
        
        
        
        match operator {
            
            
            MULT | DIVIDE | PLUS | MINUS => {
                if value.vtype == Type::String || value2.vtype == Type::String {
                    return illegal_type(Type::String, operator);
                }
                if value.vtype == Type::Float {
                    if value2.vtype == Type::Int {
                        value2.flt = value2.int as MoltFloat;
                        value2.vtype = Type::Float;
                    }
                } else if value2.vtype == Type::Float {
                    if value.vtype == Type::Int {
                        value.flt = value.int as MoltFloat;
                        value.vtype = Type::Float;
                    }
                }
            }
            
            MOD | LEFT_SHIFT | RIGHT_SHIFT | BIT_AND | BIT_XOR | BIT_OR => {
                if value.vtype != Type::Int {
                    return illegal_type(value.vtype, operator);
                } else if value2.vtype != Type::Int {
                    return illegal_type(value2.vtype, operator);
                }
            }
            
            
            LESS | GREATER | LEQ | GEQ | EQUAL | NEQ => {
                if value.vtype == Type::String {
                    if value2.vtype != Type::String {
                        value2 = expr_as_str(value2);
                    }
                } else if value2.vtype == Type::String {
                    if value.vtype != Type::String {
                        value = expr_as_str(value);
                    }
                } else if value.vtype == Type::Float {
                    if value2.vtype == Type::Int {
                        value2 = Datum::float(value2.int as MoltFloat);
                    }
                } else if value2.vtype == Type::Float {
                    if value.vtype == Type::Int {
                        value = Datum::float(value.int as MoltFloat);
                    }
                }
            }
            
            
            
            STRING_EQ | STRING_NE | IN | NI => {
                if value.vtype != Type::String {
                    value = expr_as_str(value);
                }
                if value2.vtype != Type::String {
                    value2 = expr_as_str(value2);
                }
            }
            
            
            AND | OR => {
                if value.vtype == Type::String {
                    return illegal_type(value.vtype, operator);
                }
                if value2.vtype == Type::String {
                    return illegal_type(value2.vtype, operator);
                }
            }
            
            
            QUESTY | COLON => {
                
            }
            _ => return molt_err!("unknown operator in expression"),
        }
        
        match operator {
            MULT => {
                if value.vtype == Type::Int {
                    
                    if let Some(int) = value.int.checked_mul(value2.int) {
                        value.int = int;
                    } else {
                        return molt_err!("integer overflow");
                    }
                } else {
                    value.flt *= value2.flt;
                }
            }
            DIVIDE => {
                if value.vtype == Type::Int {
                    if value2.int == 0 {
                        return molt_err!("divide by zero");
                    }
                    if let Some(int) = value.int.checked_div(value2.int) {
                        value.int = int;
                    } else {
                        return molt_err!("integer overflow");
                    }
                } else {
                    if value2.flt == 0.0 {
                        
                        return molt_err!("divide by zero");
                    }
                    value.flt /= value2.flt;
                }
            }
            MOD => {
                assert!(value.vtype == Type::Int);
                if value2.int == 0 {
                    return molt_err!("divide by zero");
                }
                if let Some(int) = value.int.checked_rem(value2.int) {
                    value.int = int;
                } else {
                    return molt_err!("integer overflow");
                }
            }
            PLUS => {
                if value.vtype == Type::Int {
                    
                    if let Some(int) = value.int.checked_add(value2.int) {
                        value.int = int;
                    } else {
                        return molt_err!("integer overflow");
                    }
                } else {
                    value.flt += value2.flt;
                }
            }
            MINUS => {
                if value.vtype == Type::Int {
                    
                    if let Some(int) = value.int.checked_sub(value2.int) {
                        value.int = int;
                    } else {
                        return molt_err!("integer overflow");
                    }
                } else {
                    value.flt -= value2.flt;
                }
            }
            LEFT_SHIFT => {
                
                value.int <<= value2.int;
            }
            RIGHT_SHIFT => {
                
                
                
                
                
                if value.int < 0 {
                    value.int = !((!value.int) >> value2.int)
                } else {
                    value.int >>= value2.int;
                }
            }
            LESS => {
                let flag = match value.vtype {
                    Type::Int => value.int < value2.int,
                    Type::Float => value.flt < value2.flt,
                    Type::String => value.str < value2.str,
                };
                value = if flag { Datum::int(1) } else { Datum::int(0) };
            }
            GREATER => {
                let flag = match value.vtype {
                    Type::Int => value.int > value2.int,
                    Type::Float => value.flt > value2.flt,
                    Type::String => value.str > value2.str,
                };
                value = if flag { Datum::int(1) } else { Datum::int(0) };
            }
            LEQ => {
                let flag = match value.vtype {
                    Type::Int => value.int <= value2.int,
                    Type::Float => value.flt <= value2.flt,
                    Type::String => value.str <= value2.str,
                };
                value = if flag { Datum::int(1) } else { Datum::int(0) };
            }
            GEQ => {
                let flag = match value.vtype {
                    Type::Int => value.int >= value2.int,
                    Type::Float => value.flt >= value2.flt,
                    Type::String => value.str >= value2.str,
                };
                value = if flag { Datum::int(1) } else { Datum::int(0) };
            }
            EQUAL => {
                
                
                let flag = match value.vtype {
                    Type::Int => value.int == value2.int,
                    Type::Float => value.flt == value2.flt,
                    Type::String => value.str == value2.str,
                };
                value = if flag { Datum::int(1) } else { Datum::int(0) };
            }
            NEQ => {
                
                
                let flag = match value.vtype {
                    Type::Int => value.int != value2.int,
                    Type::Float => value.flt != value2.flt,
                    Type::String => value.str != value2.str,
                };
                value = if flag { Datum::int(1) } else { Datum::int(0) };
            }
            STRING_EQ => {
                value = if value.str == value2.str {
                    Datum::int(1)
                } else {
                    Datum::int(0)
                };
            }
            STRING_NE => {
                value = if value.str != value2.str {
                    Datum::int(1)
                } else {
                    Datum::int(0)
                };
            }
            IN => {
                let list = list::get_list(&value2.str)?;
                
                value = if list.contains(&Value::from(&value.str)) {
                    Datum::int(1)
                } else {
                    Datum::int(0)
                };
            }
            NI => {
                let list = list::get_list(&value2.str)?;
                
                value = if list.contains(&Value::from(&value.str)) {
                    Datum::int(0)
                } else {
                    Datum::int(1)
                };
            }
            BIT_AND => {
                value.int &= value2.int;
            }
            BIT_XOR => {
                value.int ^= value2.int;
            }
            BIT_OR => {
                value.int |= value2.int;
            }
            
            
            
            AND => {
                if value2.vtype == Type::Float {
                    value2.vtype = Type::Int;
                    value2.int = if value2.flt != 0.0 { 1 } else { 0 };
                }
                value.int = if value.int != 0 && value2.int != 0 {
                    1
                } else {
                    0
                };
            }
            OR => {
                if value2.vtype == Type::Float {
                    value2.vtype = Type::Int;
                    value2.int = if value2.flt != 0.0 { 1 } else { 0 };
                }
                value.int = if value.int != 0 || value2.int != 0 {
                    1
                } else {
                    0
                };
            }
            COLON => {
                return molt_err!("can't have : operator without ? first");
            }
            _ => {
                
            }
        }
    }
}
fn expr_lex(interp: &mut Interp, info: &mut ExprInfo) -> DatumResult {
    
    let mut p = info.expr.clone();
    p.skip_while(|c| c.is_whitespace());
    if p.at_end() {
        info.token = END;
        info.expr = p;
        return Ok(Datum::none());
    }
    
    
    
    
    if !p.is('+') && !p.is('-') {
        if expr_looks_like_int(&p) {
            
            let token = util::read_int(&mut p).unwrap();
            let int = Value::get_int(&token)?;
            info.token = VALUE;
            info.expr = p;
            return Ok(Datum::int(int));
        } else if let Some(token) = util::read_float(&mut p) {
            info.token = VALUE;
            info.expr = p;
            return Ok(Datum::float(Value::get_float(&token)?));
        }
    }
    
    info.expr = p.clone();
    info.expr.skip();
    match p.peek() {
        Some('$') => {
            let mut ctx = EvalPtr::from_tokenizer(&p);
            ctx.set_no_eval(info.no_eval > 0);
            let var_val = parse_and_eval_variable(interp, &mut ctx)?;
            info.token = VALUE;
            info.expr = ctx.to_tokenizer();
            if info.no_eval > 0 {
                Ok(Datum::none())
            } else {
                expr_parse_value(&var_val)
            }
        }
        Some('[') => {
            let mut ctx = EvalPtr::from_tokenizer(&p);
            ctx.set_no_eval(info.no_eval > 0);
            let script_val = parse_and_eval_script(interp, &mut ctx)?;
            info.token = VALUE;
            info.expr = ctx.to_tokenizer();
            if info.no_eval > 0 {
                Ok(Datum::none())
            } else {
                expr_parse_value(&script_val)
            }
        }
        Some('"') => {
            let mut ctx = EvalPtr::from_tokenizer(&p);
            ctx.set_no_eval(info.no_eval > 0);
            let val = parse_and_eval_quoted_word(interp, &mut ctx)?;
            info.token = VALUE;
            info.expr = ctx.to_tokenizer();
            if info.no_eval > 0 {
                Ok(Datum::none())
            } else {
                
                
                expr_parse_string(val.as_str())
            }
        }
        Some('{') => {
            let mut ctx = EvalPtr::from_tokenizer(&p);
            ctx.set_no_eval(info.no_eval > 0);
            let val = parse_and_eval_braced_word(&mut ctx)?;
            info.token = VALUE;
            info.expr = ctx.to_tokenizer();
            if info.no_eval > 0 {
                Ok(Datum::none())
            } else {
                
                
                expr_parse_string(val.as_str())
            }
        }
        Some('(') => {
            info.token = OPEN_PAREN;
            Ok(Datum::none())
        }
        Some(')') => {
            info.token = CLOSE_PAREN;
            Ok(Datum::none())
        }
        Some(',') => {
            info.token = COMMA;
            Ok(Datum::none())
        }
        Some('*') => {
            info.token = MULT;
            Ok(Datum::none())
        }
        Some('/') => {
            info.token = DIVIDE;
            Ok(Datum::none())
        }
        Some('%') => {
            info.token = MOD;
            Ok(Datum::none())
        }
        Some('+') => {
            info.token = PLUS;
            Ok(Datum::none())
        }
        Some('-') => {
            info.token = MINUS;
            Ok(Datum::none())
        }
        Some('?') => {
            info.token = QUESTY;
            Ok(Datum::none())
        }
        Some(':') => {
            info.token = COLON;
            Ok(Datum::none())
        }
        Some('<') => {
            p.skip();
            match p.peek() {
                Some('<') => {
                    info.token = LEFT_SHIFT;
                    p.skip();
                    info.expr = p;
                    Ok(Datum::none())
                }
                Some('=') => {
                    info.token = LEQ;
                    p.skip();
                    info.expr = p;
                    Ok(Datum::none())
                }
                _ => {
                    info.token = LESS;
                    Ok(Datum::none())
                }
            }
        }
        Some('>') => {
            p.skip();
            match p.peek() {
                Some('>') => {
                    info.token = RIGHT_SHIFT;
                    p.skip();
                    info.expr = p;
                    Ok(Datum::none())
                }
                Some('=') => {
                    info.token = GEQ;
                    p.skip();
                    info.expr = p;
                    Ok(Datum::none())
                }
                _ => {
                    info.token = GREATER;
                    Ok(Datum::none())
                }
            }
        }
        Some('=') => {
            p.skip();
            if let Some('=') = p.peek() {
                info.token = EQUAL;
                p.skip();
                info.expr = p;
            } else {
                info.token = UNKNOWN;
            }
            Ok(Datum::none())
        }
        Some('!') => {
            p.skip();
            if let Some('=') = p.peek() {
                info.token = NEQ;
                p.skip();
                info.expr = p;
            } else {
                info.token = NOT;
            }
            Ok(Datum::none())
        }
        Some('&') => {
            p.skip();
            if let Some('&') = p.peek() {
                info.token = AND;
                p.skip();
                info.expr = p;
            } else {
                info.token = BIT_AND;
            }
            Ok(Datum::none())
        }
        Some('^') => {
            info.token = BIT_XOR;
            Ok(Datum::none())
        }
        Some('|') => {
            p.skip();
            if let Some('|') = p.peek() {
                info.token = OR;
                p.skip();
                info.expr = p;
            } else {
                info.token = BIT_OR;
            }
            Ok(Datum::none())
        }
        Some('~') => {
            info.token = BIT_NOT;
            Ok(Datum::none())
        }
        Some(_) => {
            if p.has(|c| c.is_alphabetic()) {
                let mut str = String::new();
                while p.has(|c| c.is_alphabetic() || c.is_digit(10)) {
                    str.push(p.next().unwrap());
                }
                
                
                match str.as_ref() {
                    "true" | "yes" | "on" => {
                        info.expr = p;
                        info.token = VALUE;
                        Ok(Datum::int(1))
                    }
                    "false" | "no" | "off" => {
                        info.expr = p;
                        info.token = VALUE;
                        Ok(Datum::int(0))
                    }
                    "eq" => {
                        info.expr = p;
                        info.token = STRING_EQ;
                        Ok(Datum::none())
                    }
                    "ne" => {
                        info.expr = p;
                        info.token = STRING_NE;
                        Ok(Datum::none())
                    }
                    "in" => {
                        info.expr = p;
                        info.token = IN;
                        Ok(Datum::none())
                    }
                    "ni" => {
                        info.expr = p;
                        info.token = NI;
                        Ok(Datum::none())
                    }
                    _ => {
                        info.expr = p;
                        expr_math_func(interp, info, &str)
                    }
                }
            } else {
                p.skip();
                info.expr = p;
                info.token = UNKNOWN;
                Ok(Datum::none())
            }
        }
        None => {
            p.skip();
            info.expr = p;
            info.token = UNKNOWN;
            Ok(Datum::none())
        }
    }
}
fn parse_and_eval_variable(interp: &mut Interp, ctx: &mut EvalPtr) -> MoltResult {
    
    ctx.skip_char('$');
    
    if !ctx.next_is_varname_char() && !ctx.next_is('{') {
        return molt_err!("invalid character \"$\"");
    }
    
    let word = parser::parse_varname(ctx)?;
    if ctx.is_no_eval() {
        Ok(Value::empty())
    } else {
        interp.eval_word(&word)
    }
}
fn parse_and_eval_script(interp: &mut Interp, ctx: &mut EvalPtr) -> MoltResult {
    
    ctx.skip_char('[');
    
    let old_flag = ctx.is_bracket_term();
    ctx.set_bracket_term(true);
    let script = parser::parse_script(ctx)?;
    let result = if ctx.is_no_eval() {
        Ok(Value::empty())
    } else {
        interp.eval_script(&script)
    };
    ctx.set_bracket_term(old_flag);
    
    if result.is_ok() {
        if ctx.next_is(']') {
            ctx.next();
        } else {
            return molt_err!("missing close-bracket");
        }
    }
    result
}
fn parse_and_eval_quoted_word(interp: &mut Interp, ctx: &mut EvalPtr) -> MoltResult {
    let word = parser::parse_quoted_word(ctx)?;
    if ctx.is_no_eval() {
        Ok(Value::empty())
    } else {
        interp.eval_word(&word)
    }
}
fn parse_and_eval_braced_word(ctx: &mut EvalPtr) -> MoltResult {
    if let Word::Value(val) = parser::parse_braced_word(ctx)? {
        Ok(val)
    } else {
        unreachable!()
    }
}
#[allow(clippy::needless_range_loop)]
fn expr_math_func(interp: &mut Interp, info: &mut ExprInfo, func_name: &str) -> DatumResult {
    
    
    
    
    let bfunc = expr_find_func(func_name)?;
    
    let _ = expr_lex(interp, info)?;
    if info.token != OPEN_PAREN {
        return syntax_error(info);
    }
    
    let mut args: [Datum; MAX_MATH_ARGS] = [Datum::none(), Datum::none()];
    if bfunc.num_args == 0 {
        let _ = expr_lex(interp, info)?;
        if info.token != OPEN_PAREN {
            return syntax_error(info);
        }
    } else {
        for i in 0..bfunc.num_args {
            let arg = expr_get_value(interp, info, -1)?;
            
            if arg.vtype == Type::String {
                return molt_err!("argument to math function didn't have numeric value");
            }
            
            if arg.vtype == Type::Int {
                if bfunc.arg_types[i] == ArgType::Float {
                    args[i] = Datum::float(arg.int as MoltFloat);
                } else {
                    args[i] = arg;
                }
            } else {
                
                if bfunc.arg_types[i] == ArgType::Int {
                    
                    args[i] = Datum::int(arg.flt as MoltInt);
                } else {
                    args[i] = arg;
                }
            }
            
            
            if i == bfunc.num_args - 1 {
                if info.token == CLOSE_PAREN {
                    break;
                }
                if info.token == COMMA {
                    return molt_err!("too many arguments for math function");
                } else {
                    return syntax_error(info);
                }
            }
            if info.token != COMMA {
                if info.token == CLOSE_PAREN {
                    return molt_err!("too few arguments for math function");
                } else {
                    return syntax_error(info);
                }
            }
        }
    }
    
    if info.no_eval > 0 {
        return Ok(Datum::none());
    }
    
    info.token = VALUE;
    (bfunc.func)(&args)
}
fn expr_find_func(func_name: &str) -> Result<&'static BuiltinFunc, Exception> {
    for bfunc in &FUNC_TABLE {
        if bfunc.name == func_name {
            return Ok(bfunc);
        }
    }
    molt_err!("unknown math function \"{}\"", func_name)
}
fn expr_parse_value(value: &Value) -> DatumResult {
    match value.already_number() {
        Some(datum) => Ok(datum),
        _ => expr_parse_string(value.as_str()),
    }
}
fn expr_parse_string(string: &str) -> DatumResult {
    if !string.is_empty() {
        let mut p = Tokenizer::new(string);
        if expr_looks_like_int(&p) {
            
            p.skip_while(|c| c.is_whitespace());
            
            
            let token = util::read_int(&mut p).unwrap();
            
            
            p.skip_while(|c| c.is_whitespace());
            if p.at_end() {
                
                
                let int = Value::get_int(&token)?;
                return Ok(Datum::int(int));
            }
        } else {
            
            p.skip_while(|c| c.is_whitespace());
            
            if let Some(token) = util::read_float(&mut p) {
                
                
                p.skip_while(|c| c.is_whitespace());
                if p.at_end() {
                    
                    
                    let flt = Value::get_float(&token)?;
                    return Ok(Datum::float(flt));
                }
            }
        }
    }
    Ok(Datum::string(string))
}
fn expr_as_str(value: Datum) -> Datum {
    match value.vtype {
        Type::Int => Datum::string(&format!("{}", value.int)),
        Type::Float => Datum::string(&format!("{}", value.flt)),
        _ => value,
    }
}
fn expr_looks_like_int<'a>(ptr: &Tokenizer<'a>) -> bool {
    
    let mut p = ptr.clone();
    p.skip_while(|c| c.is_whitespace());
    if p.is('+') || p.is('-') {
        p.skip();
    }
    if !p.has(|ch| ch.is_digit(10)) {
        return false;
    }
    p.skip();
    while p.has(|ch| ch.is_digit(10)) {
        p.skip();
    }
    !p.is('.') && !p.is('e') && !p.is('E')
}
impl Datum {
    fn is_numeric(&self) -> bool {
        match self.vtype {
            Type::Int => true,
            Type::Float => true,
            Type::String => false,
        }
    }
}
#[allow(clippy::collapsible_if)]
fn expr_abs_func(args: &[Datum; MAX_MATH_ARGS]) -> DatumResult {
    let arg = &args[0];
    if arg.vtype == Type::Float {
        if arg.flt < 0.0 {
            Ok(Datum::float(-arg.flt))
        } else {
            Ok(Datum::float(arg.flt))
        }
    } else {
        
        if arg.int < 0 {
            Ok(Datum::int(-arg.int))
        } else {
            Ok(Datum::int(arg.int))
        }
    }
}
fn expr_double_func(args: &[Datum; MAX_MATH_ARGS]) -> DatumResult {
    let arg = &args[0];
    if arg.vtype == Type::Float {
        Ok(Datum::float(arg.flt))
    } else {
        Ok(Datum::float(arg.int as MoltFloat))
    }
}
fn expr_int_func(args: &[Datum; MAX_MATH_ARGS]) -> DatumResult {
    let arg = &args[0];
    if arg.vtype == Type::Int {
        Ok(Datum::int(arg.int))
    } else {
        
        Ok(Datum::int(arg.flt as MoltInt))
    }
}
fn expr_round_func(args: &[Datum; MAX_MATH_ARGS]) -> DatumResult {
    
    let arg = &args[0];
    if arg.vtype == Type::Int {
        Ok(Datum::int(arg.int))
    } else if arg.flt < 0.0 {
        Ok(Datum::int((arg.flt - 0.5) as MoltInt))
    } else {
        Ok(Datum::int((arg.flt + 0.5) as MoltInt))
    }
}
fn syntax_error(info: &mut ExprInfo) -> DatumResult {
    molt_err!("syntax error in expression \"{}\"", info.original_expr)
}
fn illegal_type(bad_type: Type, op: i32) -> DatumResult {
    let type_str = if bad_type == Type::Float {
        "floating-point value"
    } else {
        "non-numeric string"
    };
    molt_err!(
        "can't use {} as operand of \"{}\"",
        type_str,
        OP_STRINGS[op as usize]
    )
}
#[cfg(test)]
mod tests {
    use super::*;
    fn call_expr_looks_like_int(str: &str) -> bool {
        let p = Tokenizer::new(str);
        expr_looks_like_int(&p)
    }
    
    
    
    #[allow(clippy::float_cmp)]
    fn veq(val1: &Datum, val2: &Datum) -> bool {
        if val1.vtype != val2.vtype {
            return false;
        }
        match &val1.vtype {
            Type::Int => val1.int == val2.int,
            Type::Float => val1.flt == val2.flt,
            Type::String => val1.str == val2.str,
        }
    }
    #[test]
    fn test_expr_looks_like_int() {
        assert!(call_expr_looks_like_int("1"));
        assert!(call_expr_looks_like_int("+1"));
        assert!(call_expr_looks_like_int("-1"));
        assert!(call_expr_looks_like_int("123"));
        assert!(call_expr_looks_like_int("123a"));
        assert!(!call_expr_looks_like_int(""));
        assert!(!call_expr_looks_like_int("a"));
        assert!(!call_expr_looks_like_int("123."));
        assert!(!call_expr_looks_like_int("123e"));
        assert!(!call_expr_looks_like_int("123E"));
        assert!(!call_expr_looks_like_int("."));
        assert!(!call_expr_looks_like_int("e"));
        assert!(!call_expr_looks_like_int("E"));
    }
    #[test]
    fn test_expr_parse_string() {
        let result = expr_parse_string("");
        assert!(result.is_ok());
        assert!(veq(&result.unwrap(), &Datum::string("")));
        let result = expr_parse_string("abc");
        assert!(result.is_ok());
        assert!(veq(&result.unwrap(), &Datum::string("abc")));
        let result = expr_parse_string(" 123abc");
        assert!(result.is_ok());
        assert!(veq(&result.unwrap(), &Datum::string(" 123abc")));
        let result = expr_parse_string(" 123.0abc");
        assert!(result.is_ok());
        assert!(veq(&result.unwrap(), &Datum::string(" 123.0abc")));
        let result = expr_parse_string(" 123   ");
        assert!(result.is_ok());
        assert!(veq(&result.unwrap(), &Datum::int(123)));
        let result = expr_parse_string(" 1.0   ");
        assert!(result.is_ok());
        assert!(veq(&result.unwrap(), &Datum::float(1.0)));
        let result = expr_parse_string("1234567890123456789012345678901234567890");
        assert!(result.is_err());
        
        
    }
    #[test]
    fn call_expr() {
        let mut interp = Interp::new();
        let result = expr(&mut interp, &Value::from("1 + 1"));
        assert!(result.is_ok());
        assert_eq!(result.unwrap().as_int().unwrap(), 2);
        let result = expr(&mut interp, &Value::from("1.1 + 1.1"));
        assert!(result.is_ok());
        let flt: MoltFloat = result.unwrap().as_float().unwrap();
        assert!(near(flt, 2.2));
        let result = expr(&mut interp, &Value::from("[set x foo]"));
        assert!(result.is_ok());
        assert_eq!(result.unwrap().as_str(), "foo");
    }
    fn near(x: MoltFloat, target: MoltFloat) -> bool {
        x >= target - std::f64::EPSILON && x <= target + std::f64::EPSILON
    }
}