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
}
}