use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use crate::ported::options::opt_state_set;
use crate::ported::params::{convbase, getsparam, unsetparam};
use crate::ported::utils::zerr;
pub use crate::ported::zsh_h::{Nularg, MN_FLOAT, MN_INTEGER, MN_UNSET, mnumber};
use crate::zsh_h::{PM_EFLOAT, PM_FFLOAT, PM_INTEGER};
#[derive(Clone)]
pub(crate) struct mathvalue {
pub val: mnumber,
pub lval: Option<String>,
pub pval: (),
}
const LR: u16 = 0x0000; const RL: u16 = 0x0001; const BOOL: u16 = 0x0002;
const OP_A2: u16 = 0x0004; const OP_A2IR: u16 = 0x0008; const OP_A2IO: u16 = 0x0010; const OP_E2: u16 = 0x0020; const OP_E2IO: u16 = 0x0040; const OP_OP: u16 = 0x0080; const OP_OPF: u16 = 0x0100;
pub const M_INPAR: i32 = 0; pub const M_OUTPAR: i32 = 1; pub const NOT: i32 = 2; pub const COMP: i32 = 3; pub const POSTPLUS: i32 = 4; pub const POSTMINUS: i32 = 5; pub const UPLUS: i32 = 6; pub const UMINUS: i32 = 7; pub const AND: i32 = 8; pub const XOR: i32 = 9; pub const OR: i32 = 10; pub const MUL: i32 = 11; pub const DIV: i32 = 12; pub const MOD: i32 = 13; pub const PLUS: i32 = 14; pub const MINUS: i32 = 15; pub const SHLEFT: i32 = 16; pub const SHRIGHT: i32 = 17; pub const LES: i32 = 18; pub const LEQ: i32 = 19; pub const GRE: i32 = 20; pub const GEQ: i32 = 21; pub const DEQ: i32 = 22; pub const NEQ: i32 = 23; pub const DAND: i32 = 24; pub const DOR: i32 = 25; pub const DXOR: i32 = 26; pub const QUEST: i32 = 27; pub const COLON: i32 = 28; pub const EQ: i32 = 29; pub const PLUSEQ: i32 = 30; pub const MINUSEQ: i32 = 31; pub const MULEQ: i32 = 32; pub const DIVEQ: i32 = 33; pub const MODEQ: i32 = 34; pub const ANDEQ: i32 = 35; pub const XOREQ: i32 = 36; pub const OREQ: i32 = 37; pub const SHLEFTEQ: i32 = 38; pub const SHRIGHTEQ: i32 = 39; pub const DANDEQ: i32 = 40; pub const DOREQ: i32 = 41; pub const DXOREQ: i32 = 42; pub const COMMA: i32 = 43; pub const EOI: i32 = 44; pub const PREPLUS: i32 = 45; pub const PREMINUS: i32 = 46; pub const NUM: i32 = 47; pub const ID: i32 = 48; pub const POWER: i32 = 49; pub const CID: i32 = 50; pub const POWEREQ: i32 = 51; pub const FUNC: i32 = 52; pub const TOKCOUNT: usize = 53;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(non_camel_case_types)]
pub enum prec_type {
MPREC_TOP,
MPREC_ARG,
}
pub(crate) fn getmathparam(name: &str) -> mnumber {
let base_name = if let Some(bracket) = name.find('[') {
&name[..bracket]
} else {
name
};
if let Some(v) = m_variables_get(base_name) {
return v;
}
if let Some(bracket) = name.find('[') {
let close = name.rfind(']').unwrap_or(name.len());
let arr_name = &name[..bracket];
let idx_str = &name[bracket + 1..close];
let saved = save_state();
let idx_val = matheval(idx_str)
.map(|n| {
if n.type_ == MN_FLOAT {
n.d as i64
} else {
n.l
}
})
.unwrap_or(0);
restore_state(saved);
if let Ok(tab) = crate::ported::params::paramtab().read() {
if let Some(pm) = tab.get(arr_name) {
if let Some(arr) = &pm.u_arr {
let len = arr.len() as i64;
let pos = if idx_val < 0 {
len + idx_val
} else {
idx_val - 1
};
if pos >= 0 && (pos as usize) < arr.len() {
let raw = &arr[pos as usize];
if let Ok(n) = raw.parse::<i64>() {
return mnumber {
l: n,
d: 0.0,
type_: MN_INTEGER,
};
}
if let Ok(f) = raw.parse::<f64>() {
return mnumber {
l: 0,
d: f,
type_: MN_FLOAT,
};
}
}
}
}
}
if let Ok(m) = crate::ported::params::paramtab_hashed_storage().lock() {
if let Some(map) = m.get(arr_name) {
if let Some(v) = map.get(idx_str) {
if let Ok(n) = v.parse::<i64>() {
return mnumber {
l: n,
d: 0.0,
type_: MN_INTEGER,
};
}
if let Ok(f) = v.parse::<f64>() {
return mnumber {
l: 0,
d: f,
type_: MN_FLOAT,
};
}
}
}
}
return mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
};
}
if let Some(raw) = getsparam(base_name) {
if let Ok(n) = raw.parse::<i64>() {
return mnumber {
l: n,
d: 0.0,
type_: MN_INTEGER,
};
}
if let Ok(f) = raw.parse::<f64>() {
return mnumber {
l: 0,
d: f,
type_: MN_FLOAT,
};
}
let saved = save_state();
let inherited_strs = saved.string_variables.clone();
new(&raw);
m_variables_set(saved.variables.clone());
let mut strs = inherited_strs;
strs.remove(base_name);
m_string_variables_set(strs);
m_prec_set(saved.prec);
m_c_precedences_set(saved.c_precedences);
let result = mathevall();
restore_state(saved);
if let Ok(r) = result {
return r;
}
}
if let Some(raw) = m_string_variables_get(base_name) {
let saved = save_state();
let inherited_vars = saved.variables.clone();
let mut inherited_strs = saved.string_variables.clone();
inherited_strs.remove(base_name);
let inherited_prec = saved.prec;
let inherited_c_prec = saved.c_precedences;
new(&raw);
m_variables_set(inherited_vars);
m_string_variables_set(inherited_strs);
m_prec_set(inherited_prec);
m_c_precedences_set(inherited_c_prec);
let result = mathevall();
restore_state(saved);
if let Ok(r) = result {
return r;
}
}
mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
}
}
pub(crate) fn mathevall() -> Result<mnumber, String> {
m_prec_set(if m_c_precedences() { &C_PREC } else { &Z_PREC });
reset_output_format();
while let Some(c) = peek() {
if c.is_whitespace() || c == '\u{a1}' {
advance();
} else {
break;
}
}
if m_pos() >= m_input_len() {
return Ok(mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
});
}
mathparse(top_prec());
if let Some(err) = m_error_take() {
return Err(err);
}
while let Some(c) = peek() {
if c.is_whitespace() {
advance();
} else if c == ')' {
return Err("bad math expression: unexpected ')'".to_string());
} else {
return Err(format!("illegal character: {}", c));
}
}
if m_stack_is_empty() {
return Ok(mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
});
}
let mv = m_stack_pop().unwrap();
let result = if (mv.val.type_ == MN_UNSET) {
if let Some(ref name) = mv.lval {
getmathparam(name)
} else {
mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
}
}
} else {
mv.val
};
Ok(result)
}
pub(crate) fn lexconstant() -> i32 {
let _start = m_pos();
let mut is_neg = false;
if peek() == Some('-') {
is_neg = true;
advance();
}
if peek() == Some('0') {
advance();
match peek().map(|c| c.to_ascii_lowercase()) {
Some('x') => {
advance();
let hex_start = m_pos();
while let Some(c) = peek() {
if c.is_ascii_hexdigit() || c == '_' {
advance();
} else {
break;
}
}
let hex_str: String = m_input_clone()[hex_start..m_pos()]
.chars()
.filter(|&c| c != '_')
.collect();
let val = i64::from_str_radix(&hex_str, 16).unwrap_or(0);
m_lastbase_set(16);
m_yyval_set(if m_force_float() {
mnumber {
l: 0,
d: if is_neg { -(val as f64) } else { val as f64 },
type_: MN_FLOAT,
}
} else {
mnumber {
l: if is_neg { -val } else { val },
d: 0.0,
type_: MN_INTEGER,
}
});
return NUM;
}
Some('b') => {
advance();
let bin_start = m_pos();
while let Some(c) = peek() {
if c == '0' || c == '1' || c == '_' {
advance();
} else {
break;
}
}
let bin_str: String = m_input_clone()[bin_start..m_pos()]
.chars()
.filter(|&c| c != '_')
.collect();
let val = i64::from_str_radix(&bin_str, 2).unwrap_or(0);
m_lastbase_set(2);
m_yyval_set(if m_force_float() {
mnumber {
l: 0,
d: if is_neg { -(val as f64) } else { val as f64 },
type_: MN_FLOAT,
}
} else {
mnumber {
l: if is_neg { -val } else { val },
d: 0.0,
type_: MN_INTEGER,
}
});
return NUM;
}
Some('o') | Some('O') => {
m_error_set(format!(
"bad math expression: operator expected at `{}'",
&m_input_clone()[m_pos()..]
));
m_yyval_set(mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
});
return NUM;
}
_ => {
if m_octal_zeroes() {
let oct_start = m_pos();
let mut is_octal = true;
while let Some(c) = peek() {
if c.is_ascii_digit() || c == '_' {
if ('8'..='9').contains(&c) {
is_octal = false;
}
advance();
} else if c == '.' || c == 'e' || c == 'E' || c == '#' {
is_octal = false;
break;
} else {
break;
}
}
if is_octal && m_pos() > oct_start {
let oct_str: String = m_input_clone()[oct_start..m_pos()]
.chars()
.filter(|&c| c != '_')
.collect();
let val = i64::from_str_radix(&oct_str, 8).unwrap_or(0);
m_lastbase_set(8);
m_yyval_set(if m_force_float() {
mnumber {
l: 0,
d: if is_neg { -(val as f64) } else { val as f64 },
type_: MN_FLOAT,
}
} else {
mnumber {
l: if is_neg { -val } else { val },
d: 0.0,
type_: MN_INTEGER,
}
});
return NUM;
}
m_pos_set(oct_start);
}
m_pos_sub(1);
}
}
}
let num_start = m_pos();
while let Some(c) = peek() {
if is_digit(c) || c == '_' {
advance();
} else {
break;
}
}
if peek() == Some('.') || peek() == Some('e') || peek() == Some('E') {
if peek() == Some('.') {
advance();
while let Some(c) = peek() {
if is_digit(c) || c == '_' {
advance();
} else {
break;
}
}
}
if peek() == Some('e') || peek() == Some('E') {
advance();
if peek() == Some('+') || peek() == Some('-') {
advance();
}
while let Some(c) = peek() {
if is_digit(c) || c == '_' {
advance();
} else {
break;
}
}
}
let float_str: String = m_input_clone()[num_start..m_pos()]
.chars()
.filter(|&c| c != '_')
.collect();
let val: f64 = float_str.parse().unwrap_or(0.0);
m_yyval_set(mnumber {
l: 0,
d: if is_neg { -val } else { val },
type_: MN_FLOAT,
});
return NUM;
}
if peek() == Some('#') {
advance();
let base_str: String = m_input_clone()[num_start..m_pos() - 1]
.chars()
.filter(|&c| c != '_')
.collect();
let base: u32 = base_str.parse().unwrap_or(10);
if !(2..=36).contains(&base) {
m_error_set(format!(
"invalid base (must be 2 to 36 inclusive): {}",
base
));
m_yyval_set(mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
});
return NUM;
}
m_lastbase_set(base as i32);
let mut val: i64 = 0;
let base_i64 = base as i64;
while let Some(c) = peek() {
if c == '_' {
advance();
continue;
}
let digit_val: Option<u32> = if c.is_ascii_digit() {
Some(c as u32 - '0' as u32)
} else if c.is_ascii_alphabetic() {
Some(c.to_ascii_lowercase() as u32 - 'a' as u32 + 10)
} else {
None
};
let Some(d) = digit_val else {
break;
};
if d >= base {
break;
}
val = val.saturating_mul(base_i64).saturating_add(d as i64);
advance();
}
m_yyval_set(if m_force_float() {
mnumber {
l: 0,
d: if is_neg { -(val as f64) } else { val as f64 },
type_: MN_FLOAT,
}
} else {
mnumber {
l: if is_neg { -val } else { val },
d: 0.0,
type_: MN_INTEGER,
}
});
return NUM;
}
let int_str: String = m_input_clone()[num_start..m_pos()]
.chars()
.filter(|&c| c != '_')
.collect();
let val: i64 = int_str.parse().unwrap_or(0);
m_yyval_set(if m_force_float() {
mnumber {
l: 0,
d: if is_neg { -(val as f64) } else { val as f64 },
type_: MN_FLOAT,
}
} else {
mnumber {
l: if is_neg { -val } else { val },
d: 0.0,
type_: MN_INTEGER,
}
});
NUM
}
pub(crate) fn isinf(x: f64) -> bool {
x.is_infinite()
}
pub(crate) fn isnan(x: f64) -> bool {
store(x) != store(x) || x.is_nan()
}
pub(crate) fn notzero(a: mnumber) -> bool {
if (a.type_ == MN_UNSET) {
return false;
}
if (a.type_ == MN_INTEGER) {
return a.l != 0;
}
true
}
thread_local! {
static M_INPUT: RefCell<String> = const { RefCell::new(String::new()) };
static M_POS: Cell<usize> = const { Cell::new(0) };
static M_TOK_START: Cell<usize> = const { Cell::new(0) };
static M_YYVAL: Cell<mnumber> = const { Cell::new(mnumber { l: 0, d: 0.0, type_: MN_INTEGER }) };
static M_YYLVAL: RefCell<String> = const { RefCell::new(String::new()) };
static M_STACK: RefCell<Vec<mathvalue >> = const { RefCell::new(Vec::new()) };
static M_MTOK: Cell<i32> = const { Cell::new(EOI) };
static M_UNARY: Cell<bool> = const { Cell::new(true) };
static M_NOEVAL: Cell<i32> = const { Cell::new(0) }; static M_LASTBASE: Cell<i32> = const { Cell::new(-1) }; static M_PREC: Cell<&'static [u8; TOKCOUNT]> = const { Cell::new(&Z_PREC) };
static M_C_PRECEDENCES: Cell<bool> = const { Cell::new(false) };
static M_FORCE_FLOAT: Cell<bool> = const { Cell::new(false) };
static M_OCTAL_ZEROES: Cell<bool> = const { Cell::new(false) };
static M_VARIABLES: RefCell<HashMap<String, mnumber>> = RefCell::new(HashMap::new());
static M_STRING_VARIABLES: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new());
static M_LASTVAL: Cell<i32> = const { Cell::new(0) };
static M_PID: Cell<i64> = const { Cell::new(0) };
static M_ERROR: RefCell<Option<String>> = const { RefCell::new(None) };
static M_OUTPUTRADIX: Cell<i32> = const { Cell::new(0) }; static M_OUTPUTUNDERSCORE: Cell<i32> = const { Cell::new(0) }; }
pub fn outputradix() -> i32 {
M_OUTPUTRADIX.with(|c| c.get())
}
pub fn outputunderscore() -> i32 {
M_OUTPUTUNDERSCORE.with(|c| c.get())
}
pub fn reset_output_format() {
M_OUTPUTRADIX.with(|c| c.set(0));
M_OUTPUTUNDERSCORE.with(|c| c.set(0));
}
fn m_outputradix_set(v: i32) {
M_OUTPUTRADIX.with(|c| c.set(v));
}
fn m_outputunderscore_set(v: i32) {
M_OUTPUTUNDERSCORE.with(|c| c.set(v));
}
#[inline]
fn m_input_clone() -> String {
M_INPUT.with(|c| c.borrow().clone())
}
#[inline]
fn m_input_set(v: String) {
M_INPUT.with(|c| *c.borrow_mut() = v)
}
#[inline]
fn m_input_len() -> usize {
M_INPUT.with(|c| c.borrow().len())
}
#[inline]
fn m_input_byte(i: usize) -> u8 {
M_INPUT.with(|c| c.borrow().as_bytes().get(i).copied().unwrap_or(0))
}
#[inline]
fn m_input_slice_from(start: usize) -> String {
M_INPUT.with(|c| c.borrow()[start..].to_string())
}
#[inline]
fn m_input_slice(start: usize, end: usize) -> String {
M_INPUT.with(|c| c.borrow()[start..end].to_string())
}
#[inline]
fn m_pos() -> usize {
M_POS.with(|c| c.get())
}
#[inline]
fn m_pos_set(v: usize) {
M_POS.with(|c| c.set(v))
}
#[inline]
fn m_pos_sub(n: usize) {
M_POS.with(|c| c.set(c.get() - n))
}
#[inline]
fn m_pos_add(n: usize) {
M_POS.with(|c| c.set(c.get() + n))
}
#[inline]
fn m_tok_start() -> usize {
M_TOK_START.with(|c| c.get())
}
#[inline]
fn m_tok_start_set(v: usize) {
M_TOK_START.with(|c| c.set(v))
}
#[inline]
fn m_yyval() -> mnumber {
M_YYVAL.with(|c| c.get())
}
#[inline]
fn m_yyval_set(v: mnumber) {
M_YYVAL.with(|c| c.set(v))
}
#[inline]
fn m_yylval_clone() -> String {
M_YYLVAL.with(|c| c.borrow().clone())
}
#[inline]
fn m_yylval_set(v: String) {
M_YYLVAL.with(|c| *c.borrow_mut() = v)
}
#[inline]
fn m_mtok() -> i32 {
M_MTOK.with(|c| c.get())
}
#[inline]
fn m_mtok_set(t: i32) {
M_MTOK.with(|c| c.set(t))
}
#[inline]
fn m_unary() -> bool {
M_UNARY.with(|c| c.get())
}
#[inline]
fn m_unary_set(v: bool) {
M_UNARY.with(|c| c.set(v))
}
#[inline]
pub fn m_noeval() -> i32 {
M_NOEVAL.with(|c| c.get())
}
#[inline]
pub fn m_noeval_set(v: i32) {
M_NOEVAL.with(|c| c.set(v))
}
#[inline]
fn m_noeval_inc() {
M_NOEVAL.with(|c| c.set(c.get() + 1))
}
#[inline]
fn m_noeval_dec() {
M_NOEVAL.with(|c| c.set(c.get() - 1))
}
#[inline]
fn m_lastbase_set(v: i32) {
M_LASTBASE.with(|c| c.set(v))
}
pub fn lastbase() -> i32 {
M_LASTBASE.with(|c| c.get())
}
pub fn set_lastbase(base: i32) {
m_lastbase_set(base)
}
#[inline]
fn m_prec() -> &'static [u8; TOKCOUNT] {
M_PREC.with(|c| c.get())
}
#[inline]
fn m_prec_set(p: &'static [u8; TOKCOUNT]) {
M_PREC.with(|c| c.set(p))
}
#[inline]
fn m_c_precedences() -> bool {
M_C_PRECEDENCES.with(|c| c.get())
}
#[inline]
fn m_c_precedences_set(v: bool) {
M_C_PRECEDENCES.with(|c| c.set(v))
}
#[inline]
fn m_force_float() -> bool {
M_FORCE_FLOAT.with(|c| c.get())
}
#[inline]
fn m_force_float_set(v: bool) {
M_FORCE_FLOAT.with(|c| c.set(v))
}
#[inline]
fn m_octal_zeroes() -> bool {
M_OCTAL_ZEROES.with(|c| c.get())
}
#[inline]
fn m_octal_zeroes_set(v: bool) {
M_OCTAL_ZEROES.with(|c| c.set(v))
}
#[inline]
fn m_lastval_set(v: i32) {
M_LASTVAL.with(|c| c.set(v))
}
#[inline]
fn m_lastval() -> i32 {
M_LASTVAL.with(|c| c.get())
}
#[inline]
fn m_pid() -> i64 {
M_PID.with(|c| c.get())
}
#[inline]
fn m_pid_set(v: i64) {
M_PID.with(|c| c.set(v))
}
#[inline]
fn m_error_take() -> Option<String> {
M_ERROR.with(|c| c.borrow_mut().take())
}
#[inline]
fn m_error_some() -> bool {
M_ERROR.with(|c| c.borrow().is_some())
}
#[inline]
fn m_error_set(msg: String) {
M_ERROR.with(|c| {
if c.borrow().is_none() {
*c.borrow_mut() = Some(msg);
}
})
}
#[inline]
fn m_error_set_force(msg: String) {
M_ERROR.with(|c| *c.borrow_mut() = Some(msg))
}
#[inline]
fn m_error_clear() {
M_ERROR.with(|c| *c.borrow_mut() = None)
}
#[inline]
fn m_stack_push(v: mathvalue) {
M_STACK.with(|c| c.borrow_mut().push(v))
}
#[inline]
fn m_stack_pop() -> Option<mathvalue> {
M_STACK.with(|c| c.borrow_mut().pop())
}
#[inline]
fn m_stack_len() -> usize {
M_STACK.with(|c| c.borrow().len())
}
#[inline]
fn m_stack_is_empty() -> bool {
M_STACK.with(|c| c.borrow().is_empty())
}
#[inline]
fn m_stack_top_clone() -> Option<mathvalue> {
M_STACK.with(|c| c.borrow().last().cloned())
}
#[inline]
fn m_variables_get(name: &str) -> Option<mnumber> {
M_VARIABLES.with(|c| c.borrow().get(name).copied())
}
#[inline]
fn m_variables_insert(k: String, v: mnumber) {
M_VARIABLES.with(|c| {
c.borrow_mut().insert(k, v);
})
}
#[inline]
fn m_variables_clone() -> HashMap<String, mnumber> {
M_VARIABLES.with(|c| c.borrow().clone())
}
#[inline]
fn m_variables_set(map: HashMap<String, mnumber>) {
M_VARIABLES.with(|c| *c.borrow_mut() = map)
}
#[inline]
fn m_string_variables_get(name: &str) -> Option<String> {
M_STRING_VARIABLES.with(|c| c.borrow().get(name).cloned())
}
#[inline]
fn m_string_variables_remove(name: &str) {
M_STRING_VARIABLES.with(|c| {
c.borrow_mut().remove(name);
})
}
#[inline]
fn m_string_variables_clone() -> HashMap<String, String> {
M_STRING_VARIABLES.with(|c| c.borrow().clone())
}
#[inline]
fn m_string_variables_set(map: HashMap<String, String>) {
M_STRING_VARIABLES.with(|c| *c.borrow_mut() = map)
}
#[inline]
fn m_string_variables_insert(k: String, v: String) {
M_STRING_VARIABLES.with(|c| {
c.borrow_mut().insert(k, v);
})
}
#[allow(non_camel_case_types)]
struct xyy_locals {
input: String,
pos: usize,
tok_start: usize,
yyval: mnumber,
yylval: String,
stack: Vec<mathvalue>,
mtok: i32,
unary: bool,
noeval: i32,
error: Option<String>,
variables: HashMap<String, mnumber>,
string_variables: HashMap<String, String>,
prec: &'static [u8; TOKCOUNT],
c_precedences: bool,
force_float: bool,
octal_zeroes: bool,
lastbase: i32,
}
fn save_state() -> xyy_locals {
xyy_locals {
input: m_input_clone(),
pos: m_pos(),
tok_start: m_tok_start(),
yyval: m_yyval(),
yylval: m_yylval_clone(),
stack: M_STACK.with(|c| c.borrow().clone()),
mtok: m_mtok(),
unary: m_unary(),
noeval: m_noeval(),
error: M_ERROR.with(|c| c.borrow().clone()),
variables: m_variables_clone(),
string_variables: m_string_variables_clone(),
prec: m_prec(),
c_precedences: m_c_precedences(),
force_float: m_force_float(),
octal_zeroes: m_octal_zeroes(),
lastbase: M_LASTBASE.with(|c| c.get()),
}
}
pub(crate) fn store(x: f64) -> f64 {
x
}
pub(crate) fn getcvar(name: &str) -> mnumber {
if let Some(raw) = m_string_variables_get(name) {
return mnumber {
l: raw.chars().next().map(|c| c as i64).unwrap_or(0),
d: 0.0,
type_: MN_INTEGER,
};
}
if let Some(v) = m_variables_get(name) {
let s = match v.type_ {
MN_INTEGER => v.l.to_string(),
MN_FLOAT => {
let f = v.d;
if isnan(f) {
"NaN".to_string()
} else if isinf(f) {
if f > 0.0 {
"Inf".to_string()
} else {
"-Inf".to_string()
}
} else {
format!("{:.10}", f)
}
}
_ => "0".to_string(),
};
return mnumber {
l: s.chars().next().map(|c| c as i64).unwrap_or(0),
d: 0.0,
type_: MN_INTEGER,
};
}
mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
}
}
pub(crate) fn zzlex() -> i32 {
m_yyval_set(mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
});
loop {
let pre_pos = m_pos();
let c = match advance() {
Some(c) => c,
None => {
m_tok_start_set(pre_pos);
return EOI;
}
};
if matches!(c, ' ' | '\t' | '\n' | '"') {
continue;
}
m_tok_start_set(pre_pos);
match c {
'+' => {
if peek() == Some('+') {
advance();
return if m_unary() { PREPLUS } else { POSTPLUS };
}
if peek() == Some('=') {
advance();
return PLUSEQ;
}
return if m_unary() { UPLUS } else { PLUS };
}
'-' => {
if peek() == Some('-') {
advance();
return if m_unary() { PREMINUS } else { POSTMINUS };
}
if peek() == Some('=') {
advance();
return MINUSEQ;
}
if m_unary() {
if let Some(next) = peek() {
if is_digit(next) || next == '.' {
m_pos_sub(1); return lexconstant();
}
}
return UMINUS;
}
return MINUS;
}
'(' => return M_INPAR,
')' => return M_OUTPAR,
'!' => {
if peek() == Some('=') {
advance();
return NEQ;
}
return NOT;
}
'~' => return COMP,
'&' => {
if peek() == Some('&') {
advance();
if peek() == Some('=') {
advance();
return DANDEQ;
}
return DAND;
}
if peek() == Some('=') {
advance();
return ANDEQ;
}
return AND;
}
'|' => {
if peek() == Some('|') {
advance();
if peek() == Some('=') {
advance();
return DOREQ;
}
return DOR;
}
if peek() == Some('=') {
advance();
return OREQ;
}
return OR;
}
'^' => {
if peek() == Some('^') {
advance();
if peek() == Some('=') {
advance();
return DXOREQ;
}
return DXOR;
}
if peek() == Some('=') {
advance();
return XOREQ;
}
return XOR;
}
'*' => {
if peek() == Some('*') {
advance();
if peek() == Some('=') {
advance();
return POWEREQ;
}
return POWER;
}
if peek() == Some('=') {
advance();
return MULEQ;
}
return MUL;
}
'/' => {
if peek() == Some('=') {
advance();
return DIVEQ;
}
return DIV;
}
'%' => {
if peek() == Some('=') {
advance();
return MODEQ;
}
return MOD;
}
'<' => {
if peek() == Some('<') {
advance();
if peek() == Some('=') {
advance();
return SHLEFTEQ;
}
return SHLEFT;
}
if peek() == Some('=') {
advance();
return LEQ;
}
return LES;
}
'>' => {
if peek() == Some('>') {
advance();
if peek() == Some('=') {
advance();
return SHRIGHTEQ;
}
return SHRIGHT;
}
if peek() == Some('=') {
advance();
return GEQ;
}
return GRE;
}
'=' => {
if peek() == Some('=') {
advance();
return DEQ;
}
return EQ;
}
'$' => {
m_yyval_set(mnumber {
l: m_pid(),
d: 0.0,
type_: MN_INTEGER,
});
return NUM;
}
'?' => {
if m_unary() {
m_yyval_set(mnumber {
l: m_lastval() as i64,
d: 0.0,
type_: MN_INTEGER,
});
return NUM;
}
return QUEST;
}
':' => return COLON,
',' => return COMMA,
'[' => {
if is_digit(peek().unwrap_or('\0')) {
let base_start = m_pos();
while let Some(c) = peek() {
if is_digit(c) {
advance();
} else {
break;
}
}
if peek() != Some(']') {
m_error_set("bad base syntax".to_string());
return EOI;
}
let base_str: String = m_input_clone()[base_start..m_pos()].to_string();
let base: u32 = base_str.parse().unwrap_or(10);
advance();
if !is_digit(peek().unwrap_or('\0')) && !is_ident_start(peek().unwrap_or('\0'))
{
m_error_set("bad base syntax".to_string());
return EOI;
}
if !(2..=36).contains(&base) {
m_error_set(format!(
"invalid base (must be 2 to 36 inclusive): {}",
base
));
m_yyval_set(mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
});
return NUM;
}
let val_start = m_pos();
while let Some(c) = peek() {
if c.is_ascii_alphanumeric() {
advance();
} else {
break;
}
}
let val_str = &m_input_clone()[val_start..m_pos()];
let val = i64::from_str_radix(val_str, base).unwrap_or(0);
m_lastbase_set(base as i32);
m_yyval_set(mnumber {
l: val,
d: 0.0,
type_: MN_INTEGER,
});
return NUM;
}
if peek() == Some('#') {
advance(); let mut n: i32 = 1; if peek() == Some('#') {
n = -1; advance(); }
let p_now = peek().unwrap_or('\0');
if !is_digit(p_now) && p_now != '_' {
m_error_set("bad output format specification".to_string());
return EOI;
}
let mut checkradix = false;
if is_digit(p_now) {
let rstart = m_pos();
while let Some(c) = peek() {
if is_digit(c) {
advance();
} else {
break;
}
}
let radix_str: String =
m_input_clone()[rstart..m_pos()].to_string();
let radix: i32 = radix_str.parse().unwrap_or(10);
m_outputradix_set(n * radix); checkradix = true; }
if peek() == Some('_') {
advance(); let us_now = peek().unwrap_or('\0');
if is_digit(us_now) {
let ustart = m_pos();
while let Some(c) = peek() {
if is_digit(c) {
advance();
} else {
break;
}
}
let us_str: String =
m_input_clone()[ustart..m_pos()].to_string();
m_outputunderscore_set(us_str.parse().unwrap_or(3));
} else {
m_outputunderscore_set(3); }
}
if peek() != Some(']') {
m_error_set("bad output format specification".to_string());
return EOI;
}
advance(); if checkradix {
let abs_n = M_OUTPUTRADIX.with(|c| c.get()).abs();
if !(2..=36).contains(&abs_n) {
m_error_set(format!(
"invalid base (must be 2 to 36 inclusive): {}",
M_OUTPUTRADIX.with(|c| c.get())
));
return EOI;
}
}
continue;
}
m_error_set("bad output format specification".to_string());
return EOI;
}
'#' => {
if peek() == Some('\\') || peek() == Some('#') {
advance();
if let Some(ch) = advance() {
m_yyval_set(mnumber {
l: ch as i64,
d: 0.0,
type_: MN_INTEGER,
});
return NUM;
}
}
let id_start = m_pos();
while let Some(c) = peek() {
if is_ident(c) {
advance();
} else {
break;
}
}
if m_pos() > id_start {
m_yylval_set(m_input_clone()[id_start..m_pos()].to_string());
return CID;
}
continue;
}
_ => {
if is_digit(c) || (c == '.' && is_digit(peek().unwrap_or('\0'))) {
m_pos_sub(c.len_utf8());
return lexconstant();
}
if is_ident_start(c) {
let id_start = m_pos() - c.len_utf8();
while let Some(c) = peek() {
if is_ident(c) {
advance();
} else {
break;
}
}
let id = &m_input_clone()[id_start..m_pos()];
let id_lower = id.to_lowercase();
if id_lower == "nan" {
m_yyval_set(mnumber {
l: 0,
d: f64::NAN,
type_: MN_FLOAT,
});
return NUM;
}
if id_lower == "inf" {
m_yyval_set(mnumber {
l: 0,
d: f64::INFINITY,
type_: MN_FLOAT,
});
return NUM;
}
if peek() == Some('(') {
let func_start = id_start;
advance(); let mut depth = 1;
while let Some(c) = peek() {
advance();
if c == '(' {
depth += 1;
} else if c == ')' {
depth -= 1;
if depth == 0 {
break;
}
}
}
m_yylval_set(m_input_clone()[func_start..m_pos()].to_string());
return FUNC;
}
if peek() == Some('[') {
advance(); let mut depth = 1;
while let Some(c) = peek() {
advance();
if c == '[' {
depth += 1;
} else if c == ']' {
depth -= 1;
if depth == 0 {
break;
}
}
}
}
m_yylval_set(m_input_clone()[id_start..m_pos()].to_string());
return ID;
}
return EOI;
}
}
}
}
impl Default for mathvalue {
fn default() -> Self {
mathvalue {
val: mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
},
lval: None,
pval: (),
}
}
}
pub(crate) fn push(val: mnumber, lval: Option<String>) {
m_stack_push(mathvalue {
val,
lval,
pval: (),
});
}
pub(crate) fn pop() -> mnumber {
if let Some(mv) = m_stack_pop() {
if (mv.val.type_ == MN_UNSET) {
if let Some(ref name) = mv.lval {
return getmathparam(name);
}
}
mv.val
} else {
m_error_set("stack underflow".to_string());
mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
}
}
}
pub(crate) fn setmathvar(name: &str, val: mnumber) -> mnumber {
if name.is_empty() {
zerr("bad math expression: lvalue required");
return mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
};
}
if M_NOEVAL.with(|n| n.get()) != 0 {
return val;
}
let base_name = if let Some(bracket) = name.find('[') {
&name[..bracket]
} else {
name
};
m_variables_insert(base_name.to_string(), val);
let pm = crate::ported::params::setnparam(base_name, val);
if let Some(pm) = pm {
let flags = pm.node.flags as u32;
if flags & PM_INTEGER != 0 {
let l = if val.type_ == MN_FLOAT {
val.d as i64
} else {
val.l
};
return mnumber {
l,
d: 0.0,
type_: MN_INTEGER,
};
}
if flags & (PM_EFLOAT | PM_FFLOAT) != 0 {
let d = if val.type_ == MN_INTEGER {
val.l as f64
} else {
val.d
};
return mnumber {
l: 0,
d,
type_: MN_FLOAT,
};
}
}
val
}
pub(crate) fn callmathfunc(call: &str) -> mnumber {
let paren = call.find('(').unwrap_or(call.len());
let name = &call[..paren];
let is_module_func = matches!(
name,
"abs" | "acos" | "acosh" | "asin" | "asinh" | "atan" | "atan2"
| "atanh" | "cbrt" | "ceil" | "cos" | "cosh" | "erf" | "erfc"
| "exp" | "expm1" | "fabs" | "float" | "floor" | "gamma"
| "hypot" | "ilogb" | "int" | "j0" | "j1" | "lgamma" | "log"
| "log10" | "log1p" | "log2" | "logb" | "max" | "min"
| "nextafter" | "pow" | "rand" | "round" | "sin" | "sinh"
| "sqrt" | "tan" | "tanh" | "trunc" | "y0" | "y1"
);
let module_loaded = crate::ported::module::MODULESTAB
.lock()
.ok()
.and_then(|tab| {
tab.modules.get("zsh/mathfunc").map(|m| {
let flags = m.node.flags;
(flags & crate::ported::zsh_h::MOD_INIT_B) != 0
&& (flags & crate::ported::zsh_h::MOD_UNLOAD) == 0
})
})
.unwrap_or(false);
if is_module_func && !module_loaded {
crate::ported::utils::zerr(&format!("unknown function: {}", name));
crate::ported::utils::errflag.fetch_or(
crate::ported::zsh_h::ERRFLAG_ERROR,
std::sync::atomic::Ordering::Relaxed,
);
return mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
};
}
let args_str = if paren < call.len() {
&call[paren + 1..call.len() - 1]
} else {
""
};
let arg_nums: Vec<mnumber> = if args_str.is_empty() {
vec![]
} else {
args_str
.split(',')
.filter_map(|arg| {
let saved = save_state();
let inherited_vars = saved.variables.clone();
new(arg.trim());
m_variables_set(inherited_vars);
let result = mathevall();
restore_state(saved);
match result {
Ok(n) => Some(n),
Err(msg) => {
crate::ported::utils::zerr(&msg);
None
}
}
})
.collect()
};
let args: Vec<f64> = arg_nums
.iter()
.map(|n| (if n.type_ == MN_FLOAT { n.d } else { n.l as f64 }))
.collect();
let all_int = !arg_nums.is_empty() && arg_nums.iter().all(|n| (n.type_ == MN_INTEGER));
let always_int = matches!(name, "int" | "trunc");
if always_int {
let i = match name {
"int" | "trunc" => arg_nums
.first()
.map(|n| (if n.type_ == MN_FLOAT { n.d as i64 } else { n.l }))
.unwrap_or(0),
_ => 0,
};
return mnumber {
l: i,
d: 0.0,
type_: MN_INTEGER,
};
}
let int_preserving = matches!(name, "abs" | "min" | "max");
if all_int && int_preserving {
let i = match name {
"abs" => arg_nums
.first()
.map(|n| (if n.type_ == MN_FLOAT { n.d as i64 } else { n.l }).abs())
.unwrap_or(0),
"min" => arg_nums
.iter()
.map(|n| (if n.type_ == MN_FLOAT { n.d as i64 } else { n.l }))
.min()
.unwrap_or(0),
"max" => arg_nums
.iter()
.map(|n| (if n.type_ == MN_FLOAT { n.d as i64 } else { n.l }))
.max()
.unwrap_or(0),
_ => 0,
};
return mnumber {
l: i,
d: 0.0,
type_: MN_INTEGER,
};
}
let result = match name {
"abs" => args.first().map(|x| x.abs()).unwrap_or(0.0),
"acos" => args.first().map(|x| x.acos()).unwrap_or(0.0),
"asin" => args.first().map(|x| x.asin()).unwrap_or(0.0),
"atan" => args.first().map(|x| x.atan()).unwrap_or(0.0),
"atan2" => {
let y = args.first().copied().unwrap_or(0.0);
let x = args.get(1).copied().unwrap_or(1.0);
y.atan2(x)
}
"ceil" => args.first().map(|x| x.ceil()).unwrap_or(0.0),
"cos" => args.first().map(|x| x.cos()).unwrap_or(1.0),
"cosh" => args.first().map(|x| x.cosh()).unwrap_or(1.0),
"exp" => args.first().map(|x| x.exp()).unwrap_or(1.0),
"floor" => args.first().map(|x| x.floor()).unwrap_or(0.0),
"hypot" => {
let x = args.first().copied().unwrap_or(0.0);
let y = args.get(1).copied().unwrap_or(0.0);
x.hypot(y)
}
"int" => args.first().map(|x| x.trunc()).unwrap_or(0.0),
"log" => args.first().map(|x| x.ln()).unwrap_or(0.0),
"log10" => args.first().map(|x| x.log10()).unwrap_or(0.0),
"log2" => args.first().map(|x| x.log2()).unwrap_or(0.0),
"max" => args.iter().copied().fold(f64::NEG_INFINITY, f64::max),
"min" => args.iter().copied().fold(f64::INFINITY, f64::min),
"pow" => {
let base = args.first().copied().unwrap_or(0.0);
let exp = args.get(1).copied().unwrap_or(1.0);
base.powf(exp)
}
"rand" => rand::random::<f64>(),
"round" => args.first().map(|x| x.round()).unwrap_or(0.0),
"sin" => args.first().map(|x| x.sin()).unwrap_or(0.0),
"sinh" => args.first().map(|x| x.sinh()).unwrap_or(0.0),
"sqrt" => args.first().map(|x| x.sqrt()).unwrap_or(0.0),
"tan" => args.first().map(|x| x.tan()).unwrap_or(0.0),
"tanh" => args.first().map(|x| x.tanh()).unwrap_or(0.0),
"trunc" => args.first().map(|x| x.trunc()).unwrap_or(0.0),
"float" => args.first().copied().unwrap_or(0.0),
_ => {
m_error_set(format!("unknown function: {}", name));
0.0
}
};
mnumber {
l: 0,
d: result,
type_: MN_FLOAT,
}
}
pub(crate) fn op(what: i32) {
if m_error_some() {
return;
}
let tp = OP_TYPE[what as usize];
if (tp & (OP_A2 | OP_A2IR | OP_A2IO | OP_E2 | OP_E2IO)) != 0 {
if m_stack_len() < 2 {
m_error_set("bad math expression: operand expected at end of string".to_string());
return;
}
let b = pop();
let mv_a = pop_with_lval();
let a = if (mv_a.val.type_ == MN_UNSET) {
if let Some(ref name) = mv_a.lval {
getmathparam(name)
} else {
mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
}
}
} else {
mv_a.val
};
let (a, b) = if (tp & (OP_A2IO | OP_E2IO)) != 0 {
(
mnumber {
l: (if a.type_ == MN_FLOAT { a.d as i64 } else { a.l }),
d: 0.0,
type_: MN_INTEGER,
},
mnumber {
l: (if b.type_ == MN_FLOAT { b.d as i64 } else { b.l }),
d: 0.0,
type_: MN_INTEGER,
},
)
} else if (a.type_ == MN_FLOAT) != (b.type_ == MN_FLOAT) && what != COMMA {
(
mnumber {
l: 0,
d: (if a.type_ == MN_FLOAT { a.d } else { a.l as f64 }),
type_: MN_FLOAT,
},
mnumber {
l: 0,
d: (if b.type_ == MN_FLOAT { b.d } else { b.l as f64 }),
type_: MN_FLOAT,
},
)
} else {
(a, b)
};
let result = if m_noeval() > 0 {
mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
}
} else {
let is_float = (a.type_ == MN_FLOAT);
match what {
AND | ANDEQ => mnumber {
l: (if a.type_ == MN_FLOAT { a.d as i64 } else { a.l })
& (if b.type_ == MN_FLOAT { b.d as i64 } else { b.l }),
d: 0.0,
type_: MN_INTEGER,
},
XOR | XOREQ => mnumber {
l: (if a.type_ == MN_FLOAT { a.d as i64 } else { a.l })
^ (if b.type_ == MN_FLOAT { b.d as i64 } else { b.l }),
d: 0.0,
type_: MN_INTEGER,
},
OR | OREQ => mnumber {
l: (if a.type_ == MN_FLOAT { a.d as i64 } else { a.l })
| (if b.type_ == MN_FLOAT { b.d as i64 } else { b.l }),
d: 0.0,
type_: MN_INTEGER,
},
MUL | MULEQ => {
if is_float {
mnumber {
l: 0,
d: (if a.type_ == MN_FLOAT { a.d } else { a.l as f64 })
* (if b.type_ == MN_FLOAT { b.d } else { b.l as f64 }),
type_: MN_FLOAT,
}
} else {
mnumber {
l: (if a.type_ == MN_FLOAT { a.d as i64 } else { a.l })
.wrapping_mul((if b.type_ == MN_FLOAT { b.d as i64 } else { b.l })),
d: 0.0,
type_: MN_INTEGER,
}
}
}
DIV | DIVEQ => {
if is_float {
mnumber {
l: 0,
d: (if a.type_ == MN_FLOAT { a.d } else { a.l as f64 })
/ (if b.type_ == MN_FLOAT { b.d } else { b.l as f64 }),
type_: MN_FLOAT,
}
} else {
if !notzero(b) {
m_error_set("division by zero".to_string());
return;
}
let bi = (if b.type_ == MN_FLOAT { b.d as i64 } else { b.l });
if bi == -1 {
mnumber {
l: (if a.type_ == MN_FLOAT { a.d as i64 } else { a.l })
.wrapping_neg(),
d: 0.0,
type_: MN_INTEGER,
}
} else {
mnumber {
l: (if a.type_ == MN_FLOAT { a.d as i64 } else { a.l }) / bi,
d: 0.0,
type_: MN_INTEGER,
}
}
}
}
MOD | MODEQ => {
if is_float {
mnumber {
l: 0,
d: (if a.type_ == MN_FLOAT { a.d } else { a.l as f64 })
% (if b.type_ == MN_FLOAT { b.d } else { b.l as f64 }),
type_: MN_FLOAT,
}
} else if !notzero(b) {
m_error_set("division by zero".to_string());
return;
} else {
let bi = (if b.type_ == MN_FLOAT { b.d as i64 } else { b.l });
if bi == -1 {
mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
}
} else {
mnumber {
l: (if a.type_ == MN_FLOAT { a.d as i64 } else { a.l }) % bi,
d: 0.0,
type_: MN_INTEGER,
}
}
}
}
PLUS | PLUSEQ => {
if is_float {
mnumber {
l: 0,
d: (if a.type_ == MN_FLOAT { a.d } else { a.l as f64 })
+ (if b.type_ == MN_FLOAT { b.d } else { b.l as f64 }),
type_: MN_FLOAT,
}
} else {
mnumber {
l: (if a.type_ == MN_FLOAT { a.d as i64 } else { a.l })
.wrapping_add((if b.type_ == MN_FLOAT { b.d as i64 } else { b.l })),
d: 0.0,
type_: MN_INTEGER,
}
}
}
MINUS | MINUSEQ => {
if is_float {
mnumber {
l: 0,
d: (if a.type_ == MN_FLOAT { a.d } else { a.l as f64 })
- (if b.type_ == MN_FLOAT { b.d } else { b.l as f64 }),
type_: MN_FLOAT,
}
} else {
mnumber {
l: (if a.type_ == MN_FLOAT { a.d as i64 } else { a.l })
.wrapping_sub((if b.type_ == MN_FLOAT { b.d as i64 } else { b.l })),
d: 0.0,
type_: MN_INTEGER,
}
}
}
SHLEFT | SHLEFTEQ => mnumber {
l: (if a.type_ == MN_FLOAT { a.d as i64 } else { a.l })
<< ((if b.type_ == MN_FLOAT { b.d as i64 } else { b.l }) as u32 & 63),
d: 0.0,
type_: MN_INTEGER,
},
SHRIGHT | SHRIGHTEQ => mnumber {
l: (if a.type_ == MN_FLOAT { a.d as i64 } else { a.l })
>> ((if b.type_ == MN_FLOAT { b.d as i64 } else { b.l }) as u32 & 63),
d: 0.0,
type_: MN_INTEGER,
},
LES => mnumber {
l: if is_float {
((if a.type_ == MN_FLOAT { a.d } else { a.l as f64 })
< (if b.type_ == MN_FLOAT { b.d } else { b.l as f64 }))
as i64
} else {
((if a.type_ == MN_FLOAT { a.d as i64 } else { a.l })
< (if b.type_ == MN_FLOAT { b.d as i64 } else { b.l }))
as i64
},
d: 0.0,
type_: MN_INTEGER,
},
LEQ => mnumber {
l: if is_float {
((if a.type_ == MN_FLOAT { a.d } else { a.l as f64 })
<= (if b.type_ == MN_FLOAT { b.d } else { b.l as f64 }))
as i64
} else {
((if a.type_ == MN_FLOAT { a.d as i64 } else { a.l })
<= (if b.type_ == MN_FLOAT { b.d as i64 } else { b.l }))
as i64
},
d: 0.0,
type_: MN_INTEGER,
},
GRE => mnumber {
l: if is_float {
((if a.type_ == MN_FLOAT { a.d } else { a.l as f64 })
> (if b.type_ == MN_FLOAT { b.d } else { b.l as f64 }))
as i64
} else {
((if a.type_ == MN_FLOAT { a.d as i64 } else { a.l })
> (if b.type_ == MN_FLOAT { b.d as i64 } else { b.l }))
as i64
},
d: 0.0,
type_: MN_INTEGER,
},
GEQ => mnumber {
l: if is_float {
((if a.type_ == MN_FLOAT { a.d } else { a.l as f64 })
>= (if b.type_ == MN_FLOAT { b.d } else { b.l as f64 }))
as i64
} else {
((if a.type_ == MN_FLOAT { a.d as i64 } else { a.l })
>= (if b.type_ == MN_FLOAT { b.d as i64 } else { b.l }))
as i64
},
d: 0.0,
type_: MN_INTEGER,
},
DEQ => mnumber {
l: if is_float {
((if a.type_ == MN_FLOAT { a.d } else { a.l as f64 })
== (if b.type_ == MN_FLOAT { b.d } else { b.l as f64 }))
as i64
} else {
((if a.type_ == MN_FLOAT { a.d as i64 } else { a.l })
== (if b.type_ == MN_FLOAT { b.d as i64 } else { b.l }))
as i64
},
d: 0.0,
type_: MN_INTEGER,
},
NEQ => mnumber {
l: if is_float {
((if a.type_ == MN_FLOAT { a.d } else { a.l as f64 })
!= (if b.type_ == MN_FLOAT { b.d } else { b.l as f64 }))
as i64
} else {
((if a.type_ == MN_FLOAT { a.d as i64 } else { a.l })
!= (if b.type_ == MN_FLOAT { b.d as i64 } else { b.l }))
as i64
},
d: 0.0,
type_: MN_INTEGER,
},
DAND | DANDEQ => mnumber {
l: ((if a.type_ == MN_FLOAT { a.d as i64 } else { a.l }) != 0
&& (if b.type_ == MN_FLOAT { b.d as i64 } else { b.l }) != 0)
as i64,
d: 0.0,
type_: MN_INTEGER,
},
DOR | DOREQ => mnumber {
l: ((if a.type_ == MN_FLOAT { a.d as i64 } else { a.l }) != 0
|| (if b.type_ == MN_FLOAT { b.d as i64 } else { b.l }) != 0)
as i64,
d: 0.0,
type_: MN_INTEGER,
},
DXOR | DXOREQ => {
let ai = (if a.type_ == MN_FLOAT { a.d as i64 } else { a.l }) != 0;
let bi = (if b.type_ == MN_FLOAT { b.d as i64 } else { b.l }) != 0;
mnumber {
l: (ai != bi) as i64,
d: 0.0,
type_: MN_INTEGER,
}
}
POWER | POWEREQ => {
let bi = (if b.type_ == MN_FLOAT { b.d as i64 } else { b.l });
if !is_float && bi >= 0 {
let mut result = 1i64;
let base = (if a.type_ == MN_FLOAT { a.d as i64 } else { a.l });
for _ in 0..bi {
result = result.wrapping_mul(base);
}
mnumber {
l: result,
d: 0.0,
type_: MN_INTEGER,
}
} else {
let af = (if a.type_ == MN_FLOAT { a.d } else { a.l as f64 });
let bf = (if b.type_ == MN_FLOAT { b.d } else { b.l as f64 });
if bf <= 0.0 && af == 0.0 {
m_error_set("division by zero".to_string());
return;
}
if af < 0.0 && bf != bf.trunc() {
m_error_set("imaginary power".to_string());
return;
}
mnumber {
l: 0,
d: af.powf(bf),
type_: MN_FLOAT,
}
}
}
COMMA => b,
EQ => b,
_ => mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
},
}
};
if (tp & (OP_E2 | OP_E2IO)) != 0 {
if let Some(ref name) = mv_a.lval {
let final_val = setmathvar(name, result);
push(final_val, Some(name.clone()));
} else {
m_error_set("lvalue required".to_string());
push(
mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
},
None,
);
}
} else {
push(result, None);
}
return;
}
if m_stack_is_empty() {
m_error_set("bad math expression: operand expected at end of string".to_string());
return;
}
let mv = pop_with_lval();
let val = if (mv.val.type_ == MN_UNSET) {
if let Some(ref name) = mv.lval {
getmathparam(name)
} else {
mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
}
}
} else {
mv.val
};
match what {
NOT => {
let result = mnumber {
l: if ((val.type_ == MN_INTEGER && val.l == 0)
|| (val.type_ == MN_FLOAT && val.d == 0.0)
|| val.type_ == MN_UNSET)
{
1
} else {
0
},
d: 0.0,
type_: MN_INTEGER,
};
push(result, None);
}
COMP => {
let result = mnumber {
l: !(if val.type_ == MN_FLOAT {
val.d as i64
} else {
val.l
}),
d: 0.0,
type_: MN_INTEGER,
};
push(result, None);
}
UPLUS => {
push(val, None);
}
UMINUS => {
let result = if (val.type_ == MN_FLOAT) {
mnumber {
l: 0,
d: -(if val.type_ == MN_FLOAT {
val.d
} else {
val.l as f64
}),
type_: MN_FLOAT,
}
} else {
mnumber {
l: -(if val.type_ == MN_FLOAT {
val.d as i64
} else {
val.l
}),
d: 0.0,
type_: MN_INTEGER,
}
};
push(result, None);
}
POSTPLUS => {
if mv.lval.is_none() {
m_error_set("bad math expression: lvalue required".to_string());
return;
}
let name = mv.lval.as_ref().unwrap();
let new_val = if (val.type_ == MN_FLOAT) {
mnumber {
l: 0,
d: (if val.type_ == MN_FLOAT {
val.d
} else {
val.l as f64
}) + 1.0,
type_: MN_FLOAT,
}
} else {
mnumber {
l: (if val.type_ == MN_FLOAT {
val.d as i64
} else {
val.l
}) + 1,
d: 0.0,
type_: MN_INTEGER,
}
};
setmathvar(name, new_val);
push(val, None); }
POSTMINUS => {
if mv.lval.is_none() {
m_error_set("bad math expression: lvalue required".to_string());
return;
}
let name = mv.lval.as_ref().unwrap();
let new_val = if (val.type_ == MN_FLOAT) {
mnumber {
l: 0,
d: (if val.type_ == MN_FLOAT {
val.d
} else {
val.l as f64
}) - 1.0,
type_: MN_FLOAT,
}
} else {
mnumber {
l: (if val.type_ == MN_FLOAT {
val.d as i64
} else {
val.l
}) - 1,
d: 0.0,
type_: MN_INTEGER,
}
};
setmathvar(name, new_val);
push(val, None);
}
PREPLUS => {
if mv.lval.is_none() {
m_error_set("bad math expression: lvalue required".to_string());
return;
}
let name = mv.lval.as_ref().unwrap();
let new_val = if (val.type_ == MN_FLOAT) {
mnumber {
l: 0,
d: (if val.type_ == MN_FLOAT {
val.d
} else {
val.l as f64
}) + 1.0,
type_: MN_FLOAT,
}
} else {
mnumber {
l: (if val.type_ == MN_FLOAT {
val.d as i64
} else {
val.l
}) + 1,
d: 0.0,
type_: MN_INTEGER,
}
};
setmathvar(name, new_val);
push(new_val, mv.lval);
}
PREMINUS => {
if mv.lval.is_none() {
m_error_set("bad math expression: lvalue required".to_string());
return;
}
let name = mv.lval.as_ref().unwrap();
let new_val = if (val.type_ == MN_FLOAT) {
mnumber {
l: 0,
d: (if val.type_ == MN_FLOAT {
val.d
} else {
val.l as f64
}) - 1.0,
type_: MN_FLOAT,
}
} else {
mnumber {
l: (if val.type_ == MN_FLOAT {
val.d as i64
} else {
val.l
}) - 1,
d: 0.0,
type_: MN_INTEGER,
}
};
setmathvar(name, new_val);
push(new_val, mv.lval);
}
QUEST => {
if m_stack_len() < 2 {
m_error_set("?: needs 3 operands".to_string());
return;
}
let false_val = val;
let true_val = pop();
let cond = pop();
let result = if !((cond.type_ == MN_INTEGER && cond.l == 0)
|| (cond.type_ == MN_FLOAT && cond.d == 0.0)
|| cond.type_ == MN_UNSET)
{
true_val
} else {
false_val
};
push(result, None);
}
COLON => {
m_error_set("':' without '?'".to_string());
}
_ => {
m_error_set("unknown operator".to_string());
}
}
}
pub(crate) fn bop(tk: i32) {
if m_stack_is_empty() {
return;
}
let mv = m_stack_top_clone().unwrap();
let val = if (mv.val.type_ == MN_UNSET) {
if let Some(ref name) = mv.lval {
getmathparam(name)
} else {
mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
}
}
} else {
mv.val
};
let tst = !((val.type_ == MN_INTEGER && val.l == 0)
|| (val.type_ == MN_FLOAT && val.d == 0.0)
|| val.type_ == MN_UNSET);
match tk {
DAND | DANDEQ if !tst => {
m_noeval_inc();
}
DOR | DOREQ if tst => {
m_noeval_inc();
}
_ => {}
}
}
pub fn matheval(s: &str) -> Result<mnumber, String> {
let xmtok = M_MTOK.with(|c| c.get());
let s = if let Some(rest) = s.strip_prefix(Nularg) {
rest
} else {
s
};
if s.is_empty() {
return Ok(mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
}); }
new(s);
let result = mathevall();
M_MTOK.with(|c| c.set(xmtok)); result
}
pub fn mathevali(s: &str) -> Result<i64, String> {
matheval(s).map(|n| if (n.type_ & MN_FLOAT) != 0 { n.d as i64 } else { n.l }) }
pub(crate) fn mathevalarg(expr: &str) -> i64 {
let xmtok = M_MTOK.with(|c| c.get()); let s = if let Some(rest) = expr.strip_prefix(Nularg) {
rest
} else {
expr
};
if s.is_empty() {
zerr("bad math expression: empty string"); return 0; }
let result = matheval(s).map(|n| if (n.type_ & MN_FLOAT) != 0 { n.d as i64 } else { n.l }
).unwrap_or(0);
M_MTOK.with(|c| c.set(xmtok)); result
}
pub(crate) fn checkunary() {
let tp = OP_TYPE[m_mtok() as usize];
let is_op_token = (tp & (OP_A2 | OP_A2IR | OP_A2IO | OP_E2 | OP_E2IO | OP_OP)) != 0;
let errmsg = if is_op_token {
if m_unary() {
1
} else {
0
}
} else if !m_unary() {
2
} else {
0
};
if errmsg != 0 && !m_error_some() {
let errtype = if errmsg == 2 { "operator" } else { "operand" };
let input_owned = m_input_clone();
let bytes = input_owned.as_bytes();
let mut start = m_tok_start();
while start < bytes.len() && matches!(bytes[start], b' ' | b'\t' | b'\n') {
start += 1;
}
let remaining = m_input_slice_from(start);
let (ctx, over) = if remaining.chars().count() > 10 {
let truncated: String = remaining.chars().take(10).collect();
(truncated, true)
} else {
(remaining.to_string(), false)
};
if ctx.is_empty() {
m_error_set(format!(
"bad math expression: {} expected at end of string",
errtype
));
} else {
m_error_set(format!(
"bad math expression: {} expected at `{}{}'",
errtype,
ctx,
if over { "..." } else { "" }
));
}
}
m_unary_set((tp & OP_OPF) == 0);
}
pub(crate) fn mathparse(pc: u8) {
if m_error_some() {
return;
}
m_mtok_set(zzlex());
if pc == top_prec() && m_mtok() == EOI {
return;
}
checkunary();
while m_prec()[m_mtok() as usize] <= pc {
if m_error_some() {
return;
}
match m_mtok() {
NUM => {
push(m_yyval(), None);
}
ID => {
let lval = m_yylval_clone();
if m_noeval() > 0 {
push(
mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
},
Some(lval),
);
} else {
push(
mnumber {
l: 0,
d: 0.0,
type_: MN_UNSET,
},
Some(lval),
);
}
}
CID => {
let lval = m_yylval_clone();
let val = if m_noeval() > 0 {
mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
}
} else {
getcvar(&lval)
};
push(val, Some(lval));
}
FUNC => {
let func_call = m_yylval_clone();
let val = if m_noeval() > 0 {
mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
}
} else {
callmathfunc(&func_call)
};
push(val, None);
}
M_INPAR => {
mathparse(top_prec());
if m_mtok() != M_OUTPAR {
if !m_error_some() {
m_error_set("bad math expression: ')' expected".to_string());
}
return;
}
}
QUEST => {
if m_stack_is_empty() {
m_error_set("bad math expression".to_string());
return;
}
let mv = m_stack_top_clone().unwrap();
let cond = get_value(&mv);
let q = !((cond.type_ == MN_INTEGER && cond.l == 0)
|| (cond.type_ == MN_FLOAT && cond.d == 0.0)
|| cond.type_ == MN_UNSET);
if !q {
m_noeval_inc();
}
let colon_prec = m_prec()[COLON as usize];
let stack_before = m_stack_len();
mathparse(colon_prec - 1);
if !q {
m_noeval_dec();
}
if m_mtok() != COLON {
if !m_error_some() {
if m_stack_len() > stack_before {
m_error_set("bad math expression: ':' expected".to_string());
} else {
m_error_set(
"bad math expression: operand expected at end of string"
.to_string(),
);
}
}
return;
}
if q {
m_noeval_inc();
}
let quest_prec = m_prec()[QUEST as usize];
mathparse(quest_prec);
if q {
m_noeval_dec();
}
op(QUEST);
continue;
}
_ => {
let otok = m_mtok();
let onoeval = m_noeval();
let tp = OP_TYPE[otok as usize];
let is_binary = (tp & (OP_A2 | OP_A2IR | OP_A2IO | OP_E2 | OP_E2IO)) != 0;
if m_stack_is_empty() && is_binary {
let remaining = m_input_slice_from(m_tok_start());
m_error_set(format!(
"bad math expression: operand expected at `{}'",
remaining
));
return;
}
if (tp & 0x03) == BOOL {
bop(otok);
}
let otok_prec = m_prec()[otok as usize];
let adjust = if (tp & 0x01) != RL { 1 } else { 0 };
mathparse(otok_prec - adjust);
m_noeval_set(onoeval);
op(otok);
continue;
}
}
m_mtok_set(zzlex());
checkunary();
}
}
static Z_PREC: [u8; TOKCOUNT] = [
1, 137, 2, 2, 2, 2, 2, 2, 4, 5, 6, 8, 8, 8, 9, 9, 3, 3, 10, 10, 10, 10, 11, 11, 12, 13, 13, 14, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 200, 2, 2, 0, 0, 7, 0, 16, 0, ];
static C_PREC: [u8; TOKCOUNT] = [
1, 137, 2, 2, 2, 2, 2, 2, 9, 10, 11, 4, 4, 4, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 12, 14, 13, 15, 16,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 200, 2, 2, 0, 0, 3, 0, 17, 0,
];
static OP_TYPE: [u16; TOKCOUNT] = [
LR,
LR | OP_OP | OP_OPF,
RL,
RL,
RL | OP_OP | OP_OPF,
RL | OP_OP | OP_OPF,
RL,
RL,
LR | OP_A2IO,
LR | OP_A2IO,
LR | OP_A2IO,
LR | OP_A2,
LR | OP_A2,
LR | OP_A2,
LR | OP_A2,
LR | OP_A2,
LR | OP_A2IO,
LR | OP_A2IO,
LR | OP_A2IR,
LR | OP_A2IR,
LR | OP_A2IR,
LR | OP_A2IR,
LR | OP_A2IR,
LR | OP_A2IR,
BOOL | OP_A2IO,
BOOL | OP_A2IO,
LR | OP_A2IO,
RL | OP_OP,
RL | OP_OP,
RL | OP_E2,
RL | OP_E2,
RL | OP_E2,
RL | OP_E2,
RL | OP_E2,
RL | OP_E2,
RL | OP_E2IO,
RL | OP_E2IO,
RL | OP_E2IO,
RL | OP_E2IO,
RL | OP_E2IO,
BOOL | OP_E2IO,
BOOL | OP_E2IO,
RL | OP_A2IO,
RL | OP_A2,
RL | OP_OP,
RL,
RL,
LR | OP_OPF,
LR | OP_OPF,
RL | OP_A2,
LR | OP_OPF,
RL | OP_E2,
LR | OP_OPF,
];
fn restore_state(saved: xyy_locals) {
m_input_set(saved.input);
m_pos_set(saved.pos);
m_tok_start_set(saved.tok_start);
m_yyval_set(saved.yyval);
m_yylval_set(saved.yylval);
M_STACK.with(|c| *c.borrow_mut() = saved.stack);
m_mtok_set(saved.mtok);
m_unary_set(saved.unary);
m_noeval_set(saved.noeval);
M_ERROR.with(|c| *c.borrow_mut() = saved.error);
m_variables_set(saved.variables);
m_string_variables_set(saved.string_variables);
m_prec_set(saved.prec);
m_c_precedences_set(saved.c_precedences);
m_force_float_set(saved.force_float);
m_octal_zeroes_set(saved.octal_zeroes);
M_LASTBASE.with(|c| c.set(saved.lastbase));
}
pub(crate) fn new(input: &str) {
m_input_set(input.to_string());
m_pos_set(0);
m_tok_start_set(0);
m_yyval_set(mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
});
m_yylval_set(String::new());
M_STACK.with(|c| {
c.borrow_mut().clear();
});
m_mtok_set(EOI);
m_unary_set(true);
m_noeval_set(0);
m_lastbase_set(-1);
m_prec_set(&Z_PREC);
m_c_precedences_set(false);
m_force_float_set(false);
m_octal_zeroes_set(false);
m_variables_set(HashMap::new());
m_string_variables_set(HashMap::new());
m_lastval_set(0);
m_pid_set(std::process::id() as i64);
m_error_clear();
}
pub(crate) fn with_variables(vars: HashMap<String, mnumber>) {
m_variables_set(vars);
}
pub(crate) fn with_string_variables(vars: &HashMap<String, String>) {
for (k, v) in vars {
if let Ok(i) = v.parse::<i64>() {
m_variables_insert(
k.clone(),
mnumber {
l: i,
d: 0.0,
type_: MN_INTEGER,
},
);
} else if let Ok(f) = v.parse::<f64>() {
m_variables_insert(
k.clone(),
mnumber {
l: 0,
d: f,
type_: MN_FLOAT,
},
);
} else if !v.is_empty() {
m_string_variables_insert(k.clone(), v.clone());
}
}
}
pub(crate) fn extract_string_variables() -> HashMap<String, String> {
M_VARIABLES.with(|c| {
c.borrow()
.iter()
.map(|(k, v)| {
(
k.clone(),
match v.type_ {
MN_INTEGER => v.l.to_string(),
MN_FLOAT => {
let f = v.d;
if isnan(f) {
"NaN".to_string()
} else if isinf(f) {
if f > 0.0 {
"Inf".to_string()
} else {
"-Inf".to_string()
}
} else {
format!("{:.10}", f)
}
}
_ => "0".to_string(),
},
)
})
.collect()
})
}
pub(crate) fn with_c_precedences(enable: bool) {
m_c_precedences_set(enable);
m_prec_set(if enable { &C_PREC } else { &Z_PREC });
}
pub(crate) fn with_force_float(enable: bool) {
m_force_float_set(enable);
}
pub(crate) fn with_octal_zeroes(enable: bool) {
m_octal_zeroes_set(enable);
}
pub(crate) fn with_lastval(val: i32) {
m_lastval_set(val);
}
pub(crate) fn peek() -> Option<char> {
m_input_clone()[m_pos()..].chars().next()
}
pub(crate) fn advance() -> Option<char> {
let c = peek()?;
m_pos_add(c.len_utf8());
Some(c)
}
fn is_digit(c: char) -> bool {
c.is_ascii_digit()
}
fn is_ident_start(c: char) -> bool {
c.is_ascii_alphabetic() || c == '_'
}
fn is_ident(c: char) -> bool {
c.is_ascii_alphanumeric() || c == '_'
}
pub(crate) fn pop_with_lval() -> mathvalue {
m_stack_pop().unwrap_or_default()
}
pub(crate) fn get_value(mv: &mathvalue) -> mnumber {
if (mv.val.type_ == MN_UNSET) {
if let Some(ref name) = mv.lval {
return getmathparam(name);
}
}
mv.val
}
pub(crate) fn top_prec() -> u8 {
m_prec()[COMMA as usize] + 1
}
pub(crate) fn getmathparams() -> HashMap<String, mnumber> {
m_variables_clone()
}
#[inline]
pub(crate) fn parse_compound(expr: &str) -> Option<(String, String, String, String)> {
let trimmed = expr.trim();
let bytes = trimmed.as_bytes();
if bytes.is_empty() || !(bytes[0] == b'_' || bytes[0].is_ascii_alphabetic()) {
return None;
}
let mut i = 1;
while i < bytes.len() && (bytes[i] == b'_' || bytes[i].is_ascii_alphanumeric()) {
i += 1;
}
let name = trimmed[..i].to_string();
if i >= bytes.len() || bytes[i] != b'[' {
return None;
}
let idx_start = i + 1;
let mut depth = 1;
let mut j = idx_start;
while j < bytes.len() && depth > 0 {
match bytes[j] {
b'[' => depth += 1,
b']' => {
depth -= 1;
if depth == 0 {
break;
}
}
_ => {}
}
j += 1;
}
if j >= bytes.len() {
return None;
}
let idx_expr = trimmed[idx_start..j].to_string();
let mut k = j + 1;
while k < bytes.len() && bytes[k].is_ascii_whitespace() {
k += 1;
}
if k >= bytes.len() {
return None;
}
let rest = &bytes[k..];
let (op, op_len) = match rest {
[b'<', b'<', b'=', ..] => ("<<=", 3),
[b'>', b'>', b'=', ..] => (">>=", 3),
[b'*', b'*', b'=', ..] => ("**=", 3),
[b'+', b'+', ..] => ("++", 2),
[b'-', b'-', ..] => ("--", 2),
[b'+', b'=', ..] => ("+=", 2),
[b'-', b'=', ..] => ("-=", 2),
[b'*', b'=', ..] => ("*=", 2),
[b'/', b'=', ..] => ("/=", 2),
[b'%', b'=', ..] => ("%=", 2),
[b'&', b'=', ..] => ("&=", 2),
[b'|', b'=', ..] => ("|=", 2),
[b'^', b'=', ..] => ("^=", 2),
_ => return None,
};
let rhs = trimmed[k + op_len..].trim().to_string();
if (op == "++" || op == "--") && !rhs.is_empty() {
return None;
}
Some((name, idx_expr, op.to_string(), rhs))
}
pub(crate) fn parse_pre_inc(expr: &str) -> Option<(String, String, String)> {
let trimmed = expr.trim();
let (after_op, pre_op) = if let Some(s) = trimmed.strip_prefix("++") {
(s, "++")
} else if let Some(s) = trimmed.strip_prefix("--") {
(s, "--")
} else {
return None;
};
let after_op = after_op.trim_start();
let bytes = after_op.as_bytes();
if bytes.is_empty() || !(bytes[0] == b'_' || bytes[0].is_ascii_alphabetic()) {
return None;
}
let mut i = 1;
while i < bytes.len() && (bytes[i] == b'_' || bytes[i].is_ascii_alphanumeric()) {
i += 1;
}
let name = after_op[..i].to_string();
if i >= bytes.len() || bytes[i] != b'[' {
return None;
}
let idx_start = i + 1;
let mut depth = 1;
let mut j = idx_start;
while j < bytes.len() && depth > 0 {
match bytes[j] {
b'[' => depth += 1,
b']' => {
depth -= 1;
if depth == 0 {
break;
}
}
_ => {}
}
j += 1;
}
if j >= bytes.len() {
return None;
}
let idx_expr = after_op[idx_start..j].to_string();
let mut k = j + 1;
while k < bytes.len() && bytes[k].is_ascii_whitespace() {
k += 1;
}
if k != bytes.len() {
return None;
}
Some((name, idx_expr, pre_op.to_string()))
}
pub(crate) fn parse_assign(expr: &str) -> Option<(String, String, String)> {
let trimmed = expr.trim();
let bytes = trimmed.as_bytes();
if bytes.is_empty() || !(bytes[0] == b'_' || bytes[0].is_ascii_alphabetic()) {
return None;
}
let mut i = 1;
while i < bytes.len() && (bytes[i] == b'_' || bytes[i].is_ascii_alphanumeric()) {
i += 1;
}
let name = trimmed[..i].to_string();
if i >= bytes.len() || bytes[i] != b'[' {
return None;
}
let idx_start = i + 1;
let mut depth = 1;
let mut j = idx_start;
while j < bytes.len() && depth > 0 {
match bytes[j] {
b'[' => depth += 1,
b']' => {
depth -= 1;
if depth == 0 {
break;
}
}
_ => {}
}
j += 1;
}
if j >= bytes.len() {
return None;
}
let idx_expr = trimmed[idx_start..j].to_string();
let mut k = j + 1;
while k < bytes.len() && bytes[k].is_ascii_whitespace() {
k += 1;
}
if k >= bytes.len() || bytes[k] != b'=' {
return None;
}
if k + 1 < bytes.len() && (bytes[k + 1] == b'=' || bytes[k + 1] == b'~') {
return None;
}
let rhs = trimmed[k + 1..].trim().to_string();
Some((name, idx_expr, rhs))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn setmathvar_writes_to_paramtab() {
let _g = crate::test_util::global_state_lock();
opt_state_set("exec", true);
unsetparam("mvar1_baseline");
crate::ported::params::setiparam("mvar1_baseline", 42);
let baseline = getsparam("mvar1_baseline");
assert_eq!(
baseline.as_deref(),
Some("42"),
"baseline setiparam path; got {:?}",
baseline
);
unsetparam("mvar1_baseline");
unsetparam("mvar1");
let v = mnumber {
l: 42,
d: 0.0,
type_: MN_INTEGER,
};
let returned = setmathvar("mvar1", v);
assert_eq!(returned.l, 42);
let stored = getsparam("mvar1");
assert_eq!(
stored.as_deref(),
Some("42"),
"setmathvar should write through; got {:?}",
stored
);
unsetparam("mvar1");
opt_state_set("exec", false);
}
#[test]
fn setmathvar_empty_name_returns_zero() {
let _g = crate::test_util::global_state_lock();
let v = mnumber {
l: 99,
d: 0.0,
type_: MN_INTEGER,
};
let returned = setmathvar("", v);
assert_eq!(returned.l, 0);
assert_eq!(returned.type_, MN_INTEGER);
}
#[test]
fn setmathvar_getmathparam_roundtrip() {
let _g = crate::test_util::global_state_lock();
opt_state_set("exec", true);
unsetparam("rt_int");
unsetparam("rt_float");
let n_in = mnumber {
l: 123,
d: 0.0,
type_: MN_INTEGER,
};
setmathvar("rt_int", n_in);
let n_out = getmathparam("rt_int");
assert_eq!(n_out.type_, MN_INTEGER);
assert_eq!(n_out.l, 123);
let f_in = mnumber {
l: 0,
d: 3.14,
type_: MN_FLOAT,
};
setmathvar("rt_float", f_in);
let f_out = getmathparam("rt_float");
assert_eq!(f_out.type_, MN_FLOAT);
assert!(
(f_out.d - 3.14).abs() < 1e-9,
"expected ~3.14, got {}",
f_out.d
);
unsetparam("rt_int");
unsetparam("rt_float");
opt_state_set("exec", false);
}
#[test]
fn setmathvar_subscript_writes_to_base_name() {
let _g = crate::test_util::global_state_lock();
opt_state_set("exec", true);
unsetparam("mvar2");
let v = mnumber {
l: 7,
d: 0.0,
type_: MN_INTEGER,
};
setmathvar("mvar2[5]", v);
let stored = getsparam("mvar2");
assert!(stored.is_some());
unsetparam("mvar2");
opt_state_set("exec", false);
}
#[test]
fn setmathvar_noeval_skips_paramtab_write() {
let _g = crate::test_util::global_state_lock();
opt_state_set("exec", true);
unsetparam("ne_var");
M_NOEVAL.with(|n| n.set(1));
let v = mnumber {
l: 42,
d: 0.0,
type_: MN_INTEGER,
};
let ret = setmathvar("ne_var", v);
assert_eq!(
ret.l, 42,
"c:1003 — `return v` so the stack still sees the value"
);
assert!(
getsparam("ne_var").is_none(),
"c:1002-1003 — noeval suppresses the paramtab write"
);
M_NOEVAL.with(|n| n.set(0));
opt_state_set("exec", false);
}
#[test]
fn setmathvar_float_into_integer_coerces_return_type() {
let _g = crate::test_util::global_state_lock();
opt_state_set("exec", true);
unsetparam("intvar");
crate::ported::params::setiparam("intvar", 0);
let v = mnumber {
l: 0,
d: 3.7,
type_: MN_FLOAT,
};
let ret = setmathvar("intvar", v);
assert_eq!(
ret.type_, MN_INTEGER,
"c:1016-1020 — PM_INTEGER target must return MN_INTEGER"
);
assert_eq!(
ret.l, 3,
"c:1018 — float→int truncates (3.7 → 3, not rounded)"
);
unsetparam("intvar");
opt_state_set("exec", false);
}
#[test]
fn mathevalarg_empty_emits_error_and_returns_zero() {
let _g = crate::test_util::global_state_lock();
let r = mathevalarg("");
assert_eq!(r, 0, "c:1532 — empty input returns 0");
let nularg_only: String = "\u{a1}".to_string();
let r = mathevalarg(&nularg_only);
assert_eq!(
r, 0,
"c:1528-1532 — Nularg-only is empty after skip, returns 0"
);
let r = mathevalarg("1 + 2");
assert_eq!(r, 3, "c:1534 — non-empty expression evaluates normally");
let nularg_plus: String = "\u{a1}5 * 5".to_string();
let r = mathevalarg(&nularg_plus);
assert_eq!(
r, 25,
"c:1529 — Nularg skipped, then `5 * 5` evaluates to 25"
);
}
#[test]
fn matheval_empty_input_returns_zero_int() {
let _g = crate::test_util::global_state_lock();
let r = matheval("").expect("empty string must return 0, not error");
assert_eq!(
r.type_, MN_INTEGER,
"c:1493 — empty input returns MN_INTEGER"
);
assert_eq!(r.l, 0, "c:1494 — empty input value is 0");
let nularg_only: String = "\u{a1}".to_string();
let r = matheval(&nularg_only)
.expect("Nularg-only must return 0 (treated as empty after skip)");
assert_eq!(
r.type_, MN_INTEGER,
"c:1489-1493 — Nularg-only input treated as empty → MN_INTEGER"
);
assert_eq!(r.l, 0, "c:1494 — Nularg-only input value is 0");
let nularg_plus: String = "\u{a1}1 + 2".to_string();
let r =
matheval(&nularg_plus).expect("Nularg prefix must be skipped and expression evaluated");
let v = if r.type_ == MN_FLOAT { r.d as i64 } else { r.l };
assert_eq!(v, 3, "c:1490 — Nularg skipped, then `1 + 2` evaluates to 3");
}
#[test]
fn test_basic_arithmetic() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("1 + 2").unwrap(), 3);
assert_eq!(mathevali("10 - 3").unwrap(), 7);
assert_eq!(mathevali("4 * 5").unwrap(), 20);
assert_eq!(mathevali("20 / 4").unwrap(), 5);
assert_eq!(mathevali("17 % 5").unwrap(), 2);
}
#[test]
fn test_precedence() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("2 + 3 * 4").unwrap(), 14);
assert_eq!(mathevali("(2 + 3) * 4").unwrap(), 20);
assert_eq!(mathevali("2 ** 3 ** 2").unwrap(), 512); }
#[test]
fn test_comparison() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("5 > 3").unwrap(), 1);
assert_eq!(mathevali("5 < 3").unwrap(), 0);
assert_eq!(mathevali("5 == 5").unwrap(), 1);
assert_eq!(mathevali("5 != 3").unwrap(), 1);
assert_eq!(mathevali("5 >= 5").unwrap(), 1);
assert_eq!(mathevali("5 <= 5").unwrap(), 1);
}
#[test]
fn test_logical() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("1 && 1").unwrap(), 1);
assert_eq!(mathevali("1 && 0").unwrap(), 0);
assert_eq!(mathevali("1 || 0").unwrap(), 1);
assert_eq!(mathevali("0 || 0").unwrap(), 0);
assert_eq!(mathevali("!0").unwrap(), 1);
assert_eq!(mathevali("!1").unwrap(), 0);
}
#[test]
fn test_bitwise() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("5 & 3").unwrap(), 1);
assert_eq!(mathevali("5 | 3").unwrap(), 7);
assert_eq!(mathevali("5 ^ 3").unwrap(), 6);
assert_eq!(mathevali("~0").unwrap(), -1);
assert_eq!(mathevali("1 << 4").unwrap(), 16);
assert_eq!(mathevali("16 >> 2").unwrap(), 4);
}
#[test]
fn test_ternary() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("1 ? 10 : 20").unwrap(), 10);
assert_eq!(mathevali("0 ? 10 : 20").unwrap(), 20);
assert_eq!(mathevali("(5 > 3) ? 100 : 200").unwrap(), 100);
}
#[test]
fn test_power() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("2 ** 10").unwrap(), 1024);
assert_eq!(mathevali("3 ** 3").unwrap(), 27);
assert!(
(matheval("2.0 ** 0.5")
.map(|n| (if n.type_ == MN_FLOAT { n.d } else { n.l as f64 }))
.unwrap()
- std::f64::consts::SQRT_2)
.abs()
< 0.0001
);
}
#[test]
fn test_float() {
let _g = crate::test_util::global_state_lock();
assert!(
(matheval("3.14 + 0.01")
.map(|n| (if n.type_ == MN_FLOAT { n.d } else { n.l as f64 }))
.unwrap()
- 3.15)
.abs()
< 0.0001
);
assert!(
(matheval("1.5 * 2.0")
.map(|n| (if n.type_ == MN_FLOAT { n.d } else { n.l as f64 }))
.unwrap()
- 3.0)
.abs()
< 0.0001
);
}
#[test]
fn test_unary() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("-5").unwrap(), -5);
assert_eq!(mathevali("- -5").unwrap(), 5); assert_eq!(mathevali("+5").unwrap(), 5);
assert_eq!(mathevali("-(-5)").unwrap(), 5);
}
#[test]
fn test_base() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("0xFF").unwrap(), 255);
assert_eq!(mathevali("0b1010").unwrap(), 10);
assert_eq!(mathevali("16#FF").unwrap(), 255);
assert_eq!(mathevali("2#1010").unwrap(), 10);
assert_eq!(mathevali("[16]FF").unwrap(), 255);
}
#[test]
fn test_variables() {
let _g = crate::test_util::global_state_lock();
let mut vars = HashMap::new();
vars.insert(
"x".to_string(),
mnumber {
l: 10,
d: 0.0,
type_: MN_INTEGER,
},
);
vars.insert(
"y".to_string(),
mnumber {
l: 20,
d: 0.0,
type_: MN_INTEGER,
},
);
new("x + y");
with_variables(vars);
assert_eq!(
({
let __m = mathevall().unwrap();
if __m.type_ == MN_FLOAT {
__m.d as i64
} else {
__m.l
}
}),
30
);
}
#[test]
fn test_assignment() {
let _g = crate::test_util::global_state_lock();
new("x = 5");
mathevall().unwrap();
assert_eq!(
({
let __m = m_variables_get("x").unwrap();
if __m.type_ == MN_FLOAT {
__m.d as i64
} else {
__m.l
}
}),
5
);
new("x = 5, x += 3");
let result = mathevall().unwrap();
assert_eq!(
(if result.type_ == MN_FLOAT {
result.d as i64
} else {
result.l
}),
8
);
}
#[test]
fn test_increment() {
let _g = crate::test_util::global_state_lock();
let mut vars = HashMap::new();
vars.insert(
"x".to_string(),
mnumber {
l: 5,
d: 0.0,
type_: MN_INTEGER,
},
);
new("++x");
with_variables(vars.clone());
assert_eq!(
({
let __m = mathevall().unwrap();
if __m.type_ == MN_FLOAT {
__m.d as i64
} else {
__m.l
}
}),
6
);
assert_eq!(
({
let __m = m_variables_get("x").unwrap();
if __m.type_ == MN_FLOAT {
__m.d as i64
} else {
__m.l
}
}),
6
);
new("x++");
with_variables(vars.clone());
assert_eq!(
({
let __m = mathevall().unwrap();
if __m.type_ == MN_FLOAT {
__m.d as i64
} else {
__m.l
}
}),
5
);
assert_eq!(
({
let __m = m_variables_get("x").unwrap();
if __m.type_ == MN_FLOAT {
__m.d as i64
} else {
__m.l
}
}),
6
);
}
#[test]
fn test_functions() {
let _g = crate::test_util::global_state_lock();
crate::ported::module::MODULESTAB
.lock()
.unwrap()
.load_module("zsh/mathfunc");
assert!(
(matheval("sqrt(4)")
.map(|n| (if n.type_ == MN_FLOAT { n.d } else { n.l as f64 }))
.unwrap()
- 2.0)
.abs()
< 0.0001
);
assert!(
(matheval("sin(0)")
.map(|n| (if n.type_ == MN_FLOAT { n.d } else { n.l as f64 }))
.unwrap())
.abs()
< 0.0001
);
assert!(
(matheval("cos(0)")
.map(|n| (if n.type_ == MN_FLOAT { n.d } else { n.l as f64 }))
.unwrap()
- 1.0)
.abs()
< 0.0001
);
assert!(
(matheval("abs(-5)")
.map(|n| (if n.type_ == MN_FLOAT { n.d } else { n.l as f64 }))
.unwrap()
- 5.0)
.abs()
< 0.0001
);
assert!(
(matheval("floor(3.7)")
.map(|n| (if n.type_ == MN_FLOAT { n.d } else { n.l as f64 }))
.unwrap()
- 3.0)
.abs()
< 0.0001
);
assert!(
(matheval("ceil(3.2)")
.map(|n| (if n.type_ == MN_FLOAT { n.d } else { n.l as f64 }))
.unwrap()
- 4.0)
.abs()
< 0.0001
);
}
#[test]
fn test_special_values() {
let _g = crate::test_util::global_state_lock();
assert!(matheval("Inf")
.map(|n| (if n.type_ == MN_FLOAT { n.d } else { n.l as f64 }))
.unwrap()
.is_infinite());
assert!(matheval("NaN")
.map(|n| (if n.type_ == MN_FLOAT { n.d } else { n.l as f64 }))
.unwrap()
.is_nan());
}
#[test]
fn test_errors() {
let _g = crate::test_util::global_state_lock();
assert!(matheval("1 / 0").is_err());
assert!(matheval("1 +").is_err());
assert!(matheval("()").is_err());
}
#[test]
fn test_underscore_in_numbers() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("1_000_000").unwrap(), 1000000);
assert_eq!(mathevali("0xFF_FF").unwrap(), 65535);
}
#[test]
fn test_comma_operator() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("1, 2, 3").unwrap(), 3);
assert_eq!(mathevali("(x = 1, y = 2, x + y)").unwrap(), 3);
}
#[test]
fn mathevali_divide_by_zero_errors() {
let _g = crate::test_util::global_state_lock();
assert!(mathevali("1/0").is_err());
assert!(mathevali("5/(2-2)").is_err());
}
#[test]
fn mathevali_truncates_float_via_bitmask_not_strict_eq() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("3.7").unwrap(), 3);
assert_eq!(mathevali("-3.7").unwrap(), -3);
assert_eq!(mathevali("42").unwrap(), 42);
}
#[test]
fn mathevali_mod_by_zero_errors() {
let _g = crate::test_util::global_state_lock();
assert!(mathevali("5 % 0").is_err());
}
#[test]
fn mathevali_respects_multiplicative_over_additive_precedence() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("1 + 2 * 3").unwrap(), 7);
assert_eq!(mathevali("(1 + 2) * 3").unwrap(), 9);
assert_eq!(mathevali("10 - 2 * 3").unwrap(), 4);
}
#[test]
fn mathevali_bitshift_operators() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("1 << 4").unwrap(), 16);
assert_eq!(mathevali("256 >> 3").unwrap(), 32);
}
#[test]
fn mathevali_logical_and_short_circuits_on_zero_lhs() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("0 && 1/0").unwrap(), 0);
}
#[test]
fn mathevali_logical_or_short_circuits_on_nonzero_lhs() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("1 || 1/0").unwrap(), 1);
}
#[test]
fn mathevali_ternary_evaluates_only_selected_branch() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("1 ? 42 : 1/0").unwrap(), 42);
assert_eq!(mathevali("0 ? 1/0 : 42").unwrap(), 42);
}
#[test]
fn mathevali_parses_hex_and_binary_literals() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("0xff").unwrap(), 255);
assert_eq!(mathevali("0x10").unwrap(), 16);
assert_eq!(mathevali("0xff + 1").unwrap(), 256);
assert_eq!(mathevali("0b1010").unwrap(), 10);
assert_eq!(mathevali("0b11111111").unwrap(), 255);
assert_eq!(
mathevali("0777").unwrap(),
777,
"c:489 — leading-zero parses as decimal when OCTALZEROES off"
);
}
#[test]
fn mathevali_bitwise_operators_and_or_xor() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("12 & 10").unwrap(), 8, "1100 & 1010 = 1000");
assert_eq!(mathevali("12 | 10").unwrap(), 14, "1100 | 1010 = 1110");
assert_eq!(mathevali("12 ^ 10").unwrap(), 6, "1100 ^ 1010 = 0110");
assert_eq!(
mathevali("12 & 10 | 1").unwrap(),
9,
"c:1505 — & binds tighter than | : (12 & 10) | 1 = 8 | 1 = 9"
);
}
#[test]
fn mathevali_comparison_operators_return_zero_or_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("1 < 2").unwrap(), 1);
assert_eq!(mathevali("2 < 1").unwrap(), 0);
assert_eq!(mathevali("2 > 1").unwrap(), 1);
assert_eq!(mathevali("1 > 2").unwrap(), 0);
assert_eq!(mathevali("2 <= 2").unwrap(), 1);
assert_eq!(mathevali("2 >= 2").unwrap(), 1);
assert_eq!(mathevali("2 == 2").unwrap(), 1);
assert_eq!(mathevali("2 != 2").unwrap(), 0);
}
#[test]
fn mathevali_unary_minus_and_bitwise_not() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("-5").unwrap(), -5);
assert_eq!(mathevali("-(2 + 3)").unwrap(), -5);
assert_eq!(mathevali("1 + -2").unwrap(), -1);
assert_eq!(mathevali("~0").unwrap(), -1, "two's-complement: ~0 = -1");
assert_eq!(mathevali("~5").unwrap(), -6);
}
#[test]
fn mathevali_logical_not() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("!0").unwrap(), 1);
assert_eq!(mathevali("!1").unwrap(), 0);
assert_eq!(mathevali("!42").unwrap(), 0);
assert_eq!(mathevali("!!42").unwrap(), 1);
assert_eq!(mathevali("!!0").unwrap(), 0);
}
#[test]
fn math_token_ids_match_c_source_position_for_position() {
let _g = crate::test_util::global_state_lock();
let table = [
("M_INPAR", M_INPAR, 0),
("M_OUTPAR", M_OUTPAR, 1),
("NOT", NOT, 2),
("COMP", COMP, 3),
("POSTPLUS", POSTPLUS, 4),
("POSTMINUS", POSTMINUS, 5),
("UPLUS", UPLUS, 6),
("UMINUS", UMINUS, 7),
("AND", AND, 8),
("XOR", XOR, 9),
("OR", OR, 10),
("MUL", MUL, 11),
("DIV", DIV, 12),
("MOD", MOD, 13),
("PLUS", PLUS, 14),
("MINUS", MINUS, 15),
("SHLEFT", SHLEFT, 16),
("SHRIGHT", SHRIGHT, 17),
("LES", LES, 18),
("LEQ", LEQ, 19),
("GRE", GRE, 20),
("GEQ", GEQ, 21),
("DEQ", DEQ, 22),
("NEQ", NEQ, 23),
("DAND", DAND, 24),
("DOR", DOR, 25),
("DXOR", DXOR, 26),
("QUEST", QUEST, 27), ("COLON", COLON, 28),
("EQ", EQ, 29),
("PLUSEQ", PLUSEQ, 30),
("MINUSEQ", MINUSEQ, 31),
("MULEQ", MULEQ, 32),
("DIVEQ", DIVEQ, 33),
("MODEQ", MODEQ, 34),
("ANDEQ", ANDEQ, 35),
("XOREQ", XOREQ, 36),
("OREQ", OREQ, 37),
("SHLEFTEQ", SHLEFTEQ, 38),
("SHRIGHTEQ", SHRIGHTEQ, 39),
("DANDEQ", DANDEQ, 40),
("DOREQ", DOREQ, 41),
("DXOREQ", DXOREQ, 42),
("COMMA", COMMA, 43), ("EOI", EOI, 44),
("PREPLUS", PREPLUS, 45),
("PREMINUS", PREMINUS, 46),
("NUM", NUM, 47),
("ID", ID, 48),
("POWER", POWER, 49),
("CID", CID, 50),
("POWEREQ", POWEREQ, 51),
("FUNC", FUNC, 52),
];
for (name, got, want) in table {
assert_eq!(
got, want,
"c:109-161 — {} must be {} (C source value)",
name, want
);
}
assert_eq!(
TOKCOUNT,
table.len() + 0,
"c:162 — TOKCOUNT must match the number of tokens"
);
assert_eq!(QUEST, DXOR + 1, "c:136 — QUEST immediately follows DXOR");
assert_eq!(COLON, QUEST + 1, "c:137 — COLON immediately follows QUEST");
assert_eq!(
COMMA,
DXOREQ + 1,
"c:152 — COMMA immediately follows DXOREQ"
);
assert_eq!(EOI, COMMA + 1, "c:153 — EOI immediately follows COMMA");
}
#[test]
fn math_dispatch_tables_match_tokcount() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
Z_PREC.len(),
TOKCOUNT,
"Z_PREC must have one slot per math token"
);
assert_eq!(
C_PREC.len(),
TOKCOUNT,
"C_PREC must have one slot per math token"
);
assert_eq!(
OP_TYPE.len(),
TOKCOUNT,
"OP_TYPE must have one slot per math token"
);
}
fn mi(expr: &str) -> i64 {
let _g = crate::test_util::global_state_lock();
mathevali(expr).unwrap_or_else(|e| panic!("mathevali({expr:?}) → Err({e})"))
}
#[test]
fn matheval_decimal_literal() {
assert_eq!(mi("42"), 42);
}
#[test]
fn matheval_unary_minus_literal() {
assert_eq!(mi("-7"), -7);
}
#[test]
fn matheval_hex_literal_lowercase() {
assert_eq!(mi("0xff"), 255);
}
#[test]
fn matheval_hex_literal_uppercase() {
assert_eq!(mi("0XDEAD"), 0xDEAD);
}
#[test]
fn matheval_base_hash_hex() {
assert_eq!(mi("16#FF"), 255);
}
#[test]
fn matheval_base_hash_binary() {
assert_eq!(mi("2#1010"), 10);
}
#[test]
fn matheval_base_hash_octal() {
assert_eq!(mi("8#17"), 15);
}
#[test]
fn matheval_leading_zero_is_decimal_not_octal() {
assert_eq!(mi("010"), 10);
}
#[test]
fn matheval_addition_chain() {
assert_eq!(mi("1 + 2 + 3"), 6);
}
#[test]
fn matheval_precedence_mul_over_add() {
assert_eq!(mi("2 * 3 + 4"), 10);
}
#[test]
fn matheval_parens_override_precedence() {
assert_eq!(mi("2 * (3 + 4)"), 14);
}
#[test]
fn matheval_integer_division_truncates() {
assert_eq!(mi("17 / 5"), 3);
}
#[test]
fn matheval_integer_division_below_one() {
assert_eq!(mi("1 / 4"), 0);
}
#[test]
fn matheval_modulo() {
assert_eq!(mi("17 % 5"), 2);
}
#[test]
fn matheval_power() {
assert_eq!(mi("2 ** 10"), 1024);
}
#[test]
fn matheval_power_small_cubed() {
assert_eq!(mi("3 ** 3"), 27);
}
#[test]
fn matheval_unary_binds_tighter_than_power() {
assert_eq!(mi("-2 ** 2"), 4);
}
#[test]
fn matheval_bitand() {
assert_eq!(mi("0xff & 0x0f"), 15);
}
#[test]
fn matheval_bitor() {
assert_eq!(mi("0xff | 0x100"), 511);
}
#[test]
fn matheval_bitxor() {
assert_eq!(mi("0xff ^ 0x0f"), 240);
}
#[test]
fn matheval_bitnot_zero_is_minus_one() {
assert_eq!(mi("~0"), -1);
}
#[test]
fn matheval_left_shift() {
assert_eq!(mi("1 << 8"), 256);
}
#[test]
fn matheval_right_shift() {
assert_eq!(mi("256 >> 4"), 16);
}
#[test]
fn matheval_arithmetic_right_shift_preserves_sign() {
assert_eq!(mi("-1 >> 1"), -1);
}
#[test]
fn matheval_eq_true() {
assert_eq!(mi("5 == 5"), 1);
}
#[test]
fn matheval_ne_true() {
assert_eq!(mi("5 != 6"), 1);
}
#[test]
fn matheval_lt_true() {
assert_eq!(mi("3 < 5"), 1);
}
#[test]
fn matheval_le_true_on_equal() {
assert_eq!(mi("5 <= 5"), 1);
}
#[test]
fn matheval_logand_both_true() {
assert_eq!(mi("1 && 1"), 1);
}
#[test]
fn matheval_logor_one_true() {
assert_eq!(mi("0 || 1"), 1);
}
#[test]
fn matheval_lognot_false() {
assert_eq!(mi("!0"), 1);
}
#[test]
fn matheval_lognot_truthy() {
assert_eq!(mi("!5"), 0);
}
#[test]
fn matheval_ternary_true_branch() {
assert_eq!(mi("1 ? 10 : 20"), 10);
}
#[test]
fn matheval_ternary_false_branch() {
assert_eq!(mi("0 ? 10 : 20"), 20);
}
#[test]
fn matheval_comma_returns_last() {
assert_eq!(mi("(1,2,3)"), 3);
}
#[test]
fn matheval_float_div_returns_mn_float_type() {
let _g = crate::test_util::global_state_lock();
let n = matheval("1.0 / 4").expect("matheval");
assert_ne!(
n.type_ & MN_FLOAT,
0,
"1.0 / 4 must carry MN_FLOAT in type; got type_=0x{:x}",
n.type_
);
assert!(
(n.d - 0.25).abs() < 1e-9,
"1.0 / 4 d-field should be 0.25; got {}",
n.d
);
}
#[test]
fn matheval_integer_literal_returns_mn_integer_type() {
let _g = crate::test_util::global_state_lock();
let n = matheval("42").expect("matheval");
assert_eq!(
n.type_ & MN_FLOAT,
0,
"42 must NOT carry MN_FLOAT; got type_=0x{:x}",
n.type_
);
assert_eq!(n.l, 42);
}
#[test]
fn matheval_empty_input_returns_zero() {
let _g = crate::test_util::global_state_lock();
let n = matheval("").expect("matheval(\"\") must succeed");
assert_eq!(n.l, 0, "empty input → 0");
assert_eq!(
n.type_, MN_INTEGER,
"empty input → MN_INTEGER (c:1491-1495)"
);
}
#[test]
fn mathevali_truncates_float_toward_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("7.9").unwrap(), 7);
assert_eq!(mathevali("-7.9").unwrap(), -7);
}
#[test]
fn zsh_corpus_basic_integer_literal() {
let _g = crate::test_util::global_state_lock();
assert_eq!(matheval("42").unwrap().l, 42, "ztst:9 — int literal");
}
#[test]
fn zsh_corpus_float_modulo_then_int_truncation() {
let _g = crate::test_util::global_state_lock();
let r = mathevali("(29.1 % 13.0 * 10) + 0.5");
assert_eq!(r.ok(), Some(31), "ztst:25 — float % then int truncation");
}
#[test]
fn zsh_corpus_multi_base_input() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
mathevali("0x10 + 0X01 + 2#1010").unwrap(), 27,
"ztst:29 — hex + 2#binary sum",
);
}
#[test]
fn zsh_corpus_float_truncates_in_integer_context() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mathevali("32.5").unwrap(), 32, "ztst:44 — truncate, not round");
}
#[test]
fn zsh_corpus_zsh_precedence_chain() {
let _g = crate::test_util::global_state_lock();
let r = mathevali("4 - - 3 * 7 << 1 & 7 ^ 1 | 16 ** 2");
assert_eq!(r.ok(), Some(1591), "ztst:50 — zsh-default precedence");
}
#[test]
fn zsh_corpus_mixed_int_float_promotes_to_float() {
let _g = crate::test_util::global_state_lock();
let n = matheval("3 + 5 * 1.75").unwrap();
assert!((n.d - 11.75).abs() < 1e-9,
"ztst:96 — 3+5*1.75 = 11.75 (float promotion)");
}
#[test]
fn zsh_corpus_logical_precedence_or_low_and_high() {
let _g = crate::test_util::global_state_lock();
let n = mathevali("1 < 2 || 2 < 2 && 3 > 4").unwrap();
assert_eq!(n, 1, "ztst:64 — || lower than &&");
}
#[test]
fn zsh_corpus_ternary_right_associative_nested() {
let _g = crate::test_util::global_state_lock();
let n = mathevali("1+4 ? 3+2 ? 4+3 ? 5+6 ? 4*8 : 0 : 0 : 0 : 0").unwrap();
assert_eq!(n, 32, "ztst:68 — nested ternary right-associative");
}
#[test]
fn zsh_corpus_comma_returns_last_value() {
let _g = crate::test_util::global_state_lock();
let n = mathevali("0, 4 ? 3 : 1, 5").unwrap();
assert_eq!(n, 5, "ztst:80 — comma operator returns last");
}
#[test]
fn zsh_corpus_integer_precedence_mul_before_add() {
let _g = crate::test_util::global_state_lock();
let n = mathevali("1 + 2 * 3").unwrap();
assert_eq!(n, 7, "ztst:9 — *,/ before +,-");
}
#[test]
fn zsh_corpus_basic_float_add() {
let _g = crate::test_util::global_state_lock();
let n = matheval("1.5 + 2.5").unwrap();
assert!((n.d - 4.0).abs() < 1e-9, "ztst:18 — 1.5+2.5=4.0");
}
#[test]
fn zsh_corpus_float_modulo_exact_division() {
let _g = crate::test_util::global_state_lock();
let n = matheval("7.5 % 2.5").unwrap();
assert!(n.d.abs() < 1e-9, "ztst:24 — 7.5%2.5=0.0");
}
#[test]
fn zsh_corpus_full_zsh_precedence_chain() {
let _g = crate::test_util::global_state_lock();
let n = mathevali("4 - - 3 * 7 << 1 & 7 ^ 1 | 16 ** 2").unwrap();
assert_eq!(n, 1591, "ztst:50 — default zsh precedence = 1591");
}
}