use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use crate::ported::utils::zerr;
use std::env;
use crate::ported::exec::{
self,
};
pub use crate::ported::zsh_h::MN_INTEGER;
pub use crate::ported::zsh_h::MN_FLOAT;
pub use crate::ported::zsh_h::MN_UNSET;
pub use crate::ported::zsh_h::mnumber;
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;
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;
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,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(non_camel_case_types)]
pub enum prec_type {
MPREC_TOP,
MPREC_ARG,
}
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) };
}
#[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] fn m_noeval() -> i32 { M_NOEVAL.with(|c| c.get()) }
#[inline] 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()) }
#[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()),
}
}
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));
}
#[derive(Clone)]
pub(crate) struct mathvalue {
pub val: mnumber,
pub lval: Option<String>,
pub pval: (),
}
impl Default for mathvalue {
fn default() -> Self {
mathvalue {
val: mnumber { l: 0, d: 0.0, type_: MN_INTEGER },
lval: None,
pval: (),
}
}
}
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 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 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('#') {
while let Some(c) = peek() {
if c == ']' {
advance();
break;
}
advance();
}
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;
}
}
}
}
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 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 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 idx_val = matheval(idx_str)
.map(|n| if n.type_ == crate::ported::zsh_h::MN_FLOAT { n.d as i64 } else { n.l })
.unwrap_or(0);
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_: crate::ported::zsh_h::MN_INTEGER };
}
if let Ok(f) = raw.parse::<f64>() {
return mnumber { l: 0, d: f, type_: crate::ported::zsh_h::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_: crate::ported::zsh_h::MN_INTEGER };
}
if let Ok(f) = v.parse::<f64>() {
return mnumber { l: 0, d: f, type_: crate::ported::zsh_h::MN_FLOAT };
}
}
}
}
return mnumber { l: 0, d: 0.0, type_: crate::ported::zsh_h::MN_INTEGER };
}
if let Some(raw) = crate::ported::params::getsparam(base_name) {
if let Ok(n) = raw.parse::<i64>() {
return mnumber { l: n, d: 0.0, type_: crate::ported::zsh_h::MN_INTEGER };
}
if let Ok(f) = raw.parse::<f64>() {
return mnumber { l: 0, d: f, type_: crate::ported::zsh_h::MN_FLOAT };
}
}
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 setmathvar(name: &str, val: mnumber) -> mnumber {
let base_name = if let Some(bracket) = name.find('[') {
&name[..bracket]
} else {
name
};
m_variables_insert(base_name.to_string(), val);
val
}
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(crate) fn top_prec() -> u8 {
m_prec()[Comma as usize] + 1
}
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();
}
}
pub(crate) fn callmathfunc(call: &str) -> mnumber {
let paren = call.find('(').unwrap_or(call.len());
let name = &call[..paren];
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().ok();
restore_state(saved);
result
})
.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" | "floor" | "ceil" | "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),
"floor" => args.first().map(|x| x.floor() as i64).unwrap_or(0),
"ceil" => args.first().map(|x| x.ceil() as i64).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 mathevall() -> Result<mnumber, String> {
m_prec_set(if m_c_precedences() { &C_PREC } else { &Z_PREC });
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 getmathparams() -> HashMap<String, mnumber> {
m_variables_clone()
}
pub fn matheval(s: &str) -> Result<mnumber, String> { new(s);
mathevall()
}
pub fn mathevali(s: &str) -> Result<i64, String> { matheval(s).map(|n| (if n.type_ == MN_FLOAT { n.d as i64 } else { n.l }))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_arithmetic() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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 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() {
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 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() {
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() {
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() {
assert!(matheval("1 / 0").is_err());
assert!(matheval("1 +").is_err());
assert!(matheval("()").is_err());
}
#[test]
fn test_underscore_in_numbers() {
assert_eq!(mathevali("1_000_000").unwrap(), 1000000);
assert_eq!(mathevali("0xFF_FF").unwrap(), 65535);
}
#[test]
fn test_comma_operator() {
assert_eq!(mathevali("1, 2, 3").unwrap(), 3);
assert_eq!(mathevali("(x = 1, y = 2, x + y)").unwrap(), 3);
}
}
#[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))
}
pub fn convbase(n: i64, base: u32) -> String { if !(2..=36).contains(&base) {
return n.to_string();
}
if n == 0 {
return format!("{}#0", base);
}
let neg = n < 0;
let mut v: u64 = n.unsigned_abs();
let mut digits = Vec::new();
while v > 0 {
let d = (v % base as u64) as u32;
let ch = if d < 10 {
(b'0' + d as u8) as char
} else {
(b'A' + (d - 10) as u8) as char
};
digits.push(ch);
v /= base as u64;
}
digits.reverse();
let body: String = digits.into_iter().collect();
if neg {
format!("-{}#{}", base, body)
} else {
format!("{}#{}", base, body)
}
}
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
}
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 mathevalarg(expr: &str) -> i64 {
matheval(expr).map(|n| (if n.type_ == MN_FLOAT { n.d as i64 } else { n.l })).unwrap_or(0)
}