use super::ExprKind;
use super::error::CalcError;
use super::strtod;
#[derive(Debug, Clone, PartialEq)]
pub enum FuncName {
Abs,
Sqrt,
Sqr,
Exp,
Log10,
LogE,
Ln,
Sin,
Cos,
Tan,
Asin,
Acos,
Atan,
Atan2,
Fmod,
Sinh,
Cosh,
Tanh,
Ceil,
Floor,
Nint,
Int,
IsNan,
IsInf,
Finite,
Max,
Min,
Not, Dbl,
Str,
Len,
Byte,
TrEsc,
Esc,
ANeg,
APos,
DynFetch,
DynAFetch,
SDynFetch,
SDynSFetch,
ALenNoop,
Printf,
Sscanf,
BinRead,
BinWrite,
Crc16,
ModBus,
Lrc,
AModBus,
Xor8,
AddXor8,
Avg,
Std,
FwhmFunc,
Sum,
AMax,
AMin,
IxMax,
IxMin,
IxZ,
IxNz,
Arr,
Ix,
AToD,
Smoo,
NSmoo,
Deriv,
NDeriv,
FitPoly,
FitMPoly,
FitQ,
FitMQ,
Cum,
Cat,
ARndm,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ConstName {
Pi,
D2R,
R2D,
S2R,
R2S,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Token {
Number(f64),
Var(u8), DoubleVar(u8), Rndm,
Nrndm,
FetchVal,
FetchSval,
StringLiteral(Vec<u8>),
Plus,
Minus,
FetchAval,
Star,
Slash,
Percent,
Caret,
DoubleStar,
Eq, Ne, Lt,
Le,
Gt,
Ge,
AndAnd, OrOr, BitAnd, BitOr, BitXor, Tilde, Shl, Shr, ShrLogical,
Bang, Question,
Colon,
LParen,
RParen,
Comma,
Semicolon,
LBracket,
RBracket,
LBrace,
RBrace,
PipeMinus,
Func(FuncName),
Const(ConstName),
Assign,
MaxOp, MinOp,
UntilKeyword,
}
struct ElementTable {
symbols: &'static [(&'static str, Token)],
last_var: u8,
last_double_var: Option<u8>,
string_literals: bool,
literal_words: &'static [&'static str],
literals: LiteralReader,
stack_size: i32,
}
static INF_NAN: &[&str] = &["INF", "NAN"];
#[derive(Clone, Copy, PartialEq)]
enum LiteralReader {
ParseDouble,
Strtod,
}
static BASE_TABLE: ElementTable = ElementTable {
symbols: &[
("ABS", Token::Func(FuncName::Abs)),
("ACOS", Token::Func(FuncName::Acos)),
("ASIN", Token::Func(FuncName::Asin)),
("ATAN", Token::Func(FuncName::Atan)),
("ATAN2", Token::Func(FuncName::Atan2)),
("CEIL", Token::Func(FuncName::Ceil)),
("COS", Token::Func(FuncName::Cos)),
("COSH", Token::Func(FuncName::Cosh)),
("D2R", Token::Const(ConstName::D2R)),
("EXP", Token::Func(FuncName::Exp)),
("FINITE", Token::Func(FuncName::Finite)),
("FLOOR", Token::Func(FuncName::Floor)),
("FMOD", Token::Func(FuncName::Fmod)),
("ISINF", Token::Func(FuncName::IsInf)),
("ISNAN", Token::Func(FuncName::IsNan)),
("LN", Token::Func(FuncName::Ln)),
("LOG", Token::Func(FuncName::Log10)),
("LOGE", Token::Func(FuncName::LogE)),
("MAX", Token::Func(FuncName::Max)),
("MIN", Token::Func(FuncName::Min)),
("NINT", Token::Func(FuncName::Nint)),
("NOT", Token::Func(FuncName::Not)),
("PI", Token::Const(ConstName::Pi)),
("R2D", Token::Const(ConstName::R2D)),
("RNDM", Token::Rndm),
("SIN", Token::Func(FuncName::Sin)),
("SINH", Token::Func(FuncName::Sinh)),
("SQR", Token::Func(FuncName::Sqr)),
("SQRT", Token::Func(FuncName::Sqrt)),
("TAN", Token::Func(FuncName::Tan)),
("TANH", Token::Func(FuncName::Tanh)),
("VAL", Token::FetchVal),
("AND", Token::BitAnd),
("OR", Token::BitOr),
("XOR", Token::BitXor),
("!=", Token::Ne),
("#", Token::Ne),
("%", Token::Percent),
("&", Token::BitAnd),
("&&", Token::AndAnd),
("(", Token::LParen),
(")", Token::RParen),
("*", Token::Star),
("**", Token::DoubleStar),
("+", Token::Plus),
(",", Token::Comma),
("-", Token::Minus),
("/", Token::Slash),
(":", Token::Colon),
(":=", Token::Assign),
(";", Token::Semicolon),
("<", Token::Lt),
("<<", Token::Shl),
("<=", Token::Le),
("=", Token::Eq),
("==", Token::Eq),
(">", Token::Gt),
(">=", Token::Ge),
(">>", Token::Shr),
(">>>", Token::ShrLogical),
("?", Token::Question),
("^", Token::Caret),
("|", Token::BitOr),
("||", Token::OrOr),
("!", Token::Bang),
("~", Token::Tilde),
],
last_var: b'U' - b'A',
last_double_var: None,
string_literals: false,
literal_words: INF_NAN,
literals: LiteralReader::ParseDouble,
stack_size: 80,
};
static SCALC_TABLE: ElementTable = ElementTable {
symbols: &[
("ABS", Token::Func(FuncName::Abs)),
("ACOS", Token::Func(FuncName::Acos)),
("ADD_XOR8", Token::Func(FuncName::AddXor8)),
("AMODBUS", Token::Func(FuncName::AModBus)),
("ASIN", Token::Func(FuncName::Asin)),
("ATAN", Token::Func(FuncName::Atan)),
("ATAN2", Token::Func(FuncName::Atan2)),
("BYTE", Token::Func(FuncName::Byte)),
("CEIL", Token::Func(FuncName::Ceil)),
("COS", Token::Func(FuncName::Cos)),
("COSH", Token::Func(FuncName::Cosh)),
("CRC16", Token::Func(FuncName::Crc16)),
("DBL", Token::Func(FuncName::Dbl)),
("D2R", Token::Const(ConstName::D2R)),
("ESC", Token::Func(FuncName::Esc)),
("$E", Token::Func(FuncName::Esc)),
("$P", Token::Func(FuncName::Printf)),
("$R", Token::Func(FuncName::BinRead)),
("$S", Token::Func(FuncName::Sscanf)),
("$T", Token::Func(FuncName::TrEsc)),
("$W", Token::Func(FuncName::BinWrite)),
("EXP", Token::Func(FuncName::Exp)),
("FINITE", Token::Func(FuncName::Finite)),
("FLOOR", Token::Func(FuncName::Floor)),
("INT", Token::Func(FuncName::Int)),
("ISINF", Token::Func(FuncName::IsInf)),
("ISNAN", Token::Func(FuncName::IsNan)),
("LEN", Token::Func(FuncName::Len)),
("LN", Token::Func(FuncName::Ln)),
("LOG", Token::Func(FuncName::Log10)),
("LOGE", Token::Func(FuncName::LogE)),
("LRC", Token::Func(FuncName::Lrc)),
("MAX", Token::Func(FuncName::Max)),
("MIN", Token::Func(FuncName::Min)),
("MODBUS", Token::Func(FuncName::ModBus)),
("NINT", Token::Func(FuncName::Nint)),
("NOT", Token::Func(FuncName::Not)),
("NRNDM", Token::Nrndm),
("PI", Token::Const(ConstName::Pi)),
("PRINTF", Token::Func(FuncName::Printf)),
("R2D", Token::Const(ConstName::R2D)),
("R2S", Token::Const(ConstName::R2S)),
("S2R", Token::Const(ConstName::S2R)),
("READ", Token::Func(FuncName::BinRead)),
("RNDM", Token::Rndm),
("SIN", Token::Func(FuncName::Sin)),
("SINH", Token::Func(FuncName::Sinh)),
("SQR", Token::Func(FuncName::Sqr)),
("SQRT", Token::Func(FuncName::Sqrt)),
("SSCANF", Token::Func(FuncName::Sscanf)),
("STR", Token::Func(FuncName::Str)),
("SVAL", Token::FetchSval),
("@", Token::Func(FuncName::SDynFetch)),
("@@", Token::Func(FuncName::SDynSFetch)),
("TAN", Token::Func(FuncName::Tan)),
("TANH", Token::Func(FuncName::Tanh)),
("TR_ESC", Token::Func(FuncName::TrEsc)),
("UNTIL", Token::UntilKeyword),
("VAL", Token::FetchVal),
("WRITE", Token::Func(FuncName::BinWrite)),
("XOR8", Token::Func(FuncName::Xor8)),
("AND", Token::BitAnd),
("OR", Token::BitOr),
("XOR", Token::BitXor),
("!=", Token::Ne),
("#", Token::Ne),
("%", Token::Percent),
("&", Token::BitAnd),
("&&", Token::AndAnd),
("(", Token::LParen),
(")", Token::RParen),
("[", Token::LBracket),
("]", Token::RBracket),
("{", Token::LBrace),
("}", Token::RBrace),
("*", Token::Star),
("**", Token::DoubleStar),
("+", Token::Plus),
(",", Token::Comma),
("-", Token::Minus),
("/", Token::Slash),
(":", Token::Colon),
(":=", Token::Assign),
(";", Token::Semicolon),
("<", Token::Lt),
("<<", Token::Shl),
("<=", Token::Le),
("=", Token::Eq),
("==", Token::Eq),
(">", Token::Gt),
(">=", Token::Ge),
(">>", Token::Shr),
("?", Token::Question),
("^", Token::Caret),
("|", Token::BitOr),
("||", Token::OrOr),
("|-", Token::PipeMinus),
("-|", Token::Minus),
(">?", Token::MaxOp),
("<?", Token::MinOp),
("!", Token::Bang),
("~", Token::Tilde),
],
last_var: b'P' - b'A',
last_double_var: Some(b'L' - b'A'),
string_literals: true,
literal_words: INF_NAN,
literals: LiteralReader::Strtod,
stack_size: 30,
};
static ACALC_TABLE: ElementTable = ElementTable {
symbols: &[
("ABS", Token::Func(FuncName::Abs)),
("ACOS", Token::Func(FuncName::Acos)),
("AMAX", Token::Func(FuncName::AMax)),
("AMIN", Token::Func(FuncName::AMin)),
("ARNDM", Token::Func(FuncName::ARndm)),
("ARR", Token::Func(FuncName::Arr)),
("ASIN", Token::Func(FuncName::Asin)),
("ATAN", Token::Func(FuncName::Atan)),
("ATAN2", Token::Func(FuncName::Atan2)),
("AVG", Token::Func(FuncName::Avg)),
("@", Token::Func(FuncName::DynFetch)),
("@@", Token::Func(FuncName::DynAFetch)),
("AVAL", Token::FetchAval),
("ANEG", Token::Func(FuncName::ANeg)),
("APOS", Token::Func(FuncName::APos)),
("LEN", Token::Func(FuncName::ALenNoop)),
("CAT", Token::Func(FuncName::Cat)),
("CEIL", Token::Func(FuncName::Ceil)),
("COS", Token::Func(FuncName::Cos)),
("COSH", Token::Func(FuncName::Cosh)),
("CUM", Token::Func(FuncName::Cum)),
("DBL", Token::Func(FuncName::AToD)),
("DERIV", Token::Func(FuncName::Deriv)),
("D2R", Token::Const(ConstName::D2R)),
("EXP", Token::Func(FuncName::Exp)),
("FINITE", Token::Func(FuncName::Finite)),
("FITMPOLY", Token::Func(FuncName::FitMPoly)),
("FITMQ", Token::Func(FuncName::FitMQ)),
("FITPOLY", Token::Func(FuncName::FitPoly)),
("FITQ", Token::Func(FuncName::FitQ)),
("FLOOR", Token::Func(FuncName::Floor)),
("FWHM", Token::Func(FuncName::FwhmFunc)),
("INT", Token::Func(FuncName::Int)),
("ISINF", Token::Func(FuncName::IsInf)),
("ISNAN", Token::Func(FuncName::IsNan)),
("IX", Token::Func(FuncName::Ix)),
("IXMAX", Token::Func(FuncName::IxMax)),
("IXMIN", Token::Func(FuncName::IxMin)),
("IXNZ", Token::Func(FuncName::IxNz)),
("IXZ", Token::Func(FuncName::IxZ)),
("LN", Token::Func(FuncName::Ln)),
("LOG", Token::Func(FuncName::Log10)),
("LOGE", Token::Func(FuncName::LogE)),
("MAX", Token::Func(FuncName::Max)),
("MIN", Token::Func(FuncName::Min)),
("NDERIV", Token::Func(FuncName::NDeriv)),
("NINT", Token::Func(FuncName::Nint)),
("NOT", Token::Func(FuncName::Not)),
("NRNDM", Token::Nrndm),
("NSMOO", Token::Func(FuncName::NSmoo)),
("PI", Token::Const(ConstName::Pi)),
("R2D", Token::Const(ConstName::R2D)),
("R2S", Token::Const(ConstName::R2S)),
("S2R", Token::Const(ConstName::S2R)),
("RNDM", Token::Rndm),
("SIN", Token::Func(FuncName::Sin)),
("SINH", Token::Func(FuncName::Sinh)),
("SMOO", Token::Func(FuncName::Smoo)),
("SQR", Token::Func(FuncName::Sqr)),
("SQRT", Token::Func(FuncName::Sqrt)),
("STD", Token::Func(FuncName::Std)),
("SUM", Token::Func(FuncName::Sum)),
("TAN", Token::Func(FuncName::Tan)),
("TANH", Token::Func(FuncName::Tanh)),
("UNTIL", Token::UntilKeyword),
("VAL", Token::FetchVal),
("AND", Token::BitAnd),
("OR", Token::BitOr),
("XOR", Token::BitXor),
("!=", Token::Ne),
("#", Token::Ne),
("%", Token::Percent),
("&", Token::BitAnd),
("&&", Token::AndAnd),
("(", Token::LParen),
(")", Token::RParen),
("[", Token::LBracket),
("]", Token::RBracket),
("{", Token::LBrace),
("}", Token::RBrace),
("*", Token::Star),
("**", Token::DoubleStar),
("+", Token::Plus),
(",", Token::Comma),
("-", Token::Minus),
("/", Token::Slash),
(":", Token::Colon),
(":=", Token::Assign),
(";", Token::Semicolon),
("<", Token::Lt),
("<<", Token::Shl),
("<=", Token::Le),
("=", Token::Eq),
("==", Token::Eq),
(">", Token::Gt),
(">=", Token::Ge),
(">>", Token::Shr),
("?", Token::Question),
("^", Token::Caret),
("|", Token::BitOr),
("||", Token::OrOr),
(">?", Token::MaxOp),
("<?", Token::MinOp),
("!", Token::Bang),
("~", Token::Tilde),
],
last_var: b'P' - b'A',
last_double_var: Some(b'L' - b'A'),
string_literals: false,
literal_words: &[],
literals: LiteralReader::Strtod,
stack_size: 20,
};
fn table_for(kind: &ExprKind) -> &'static ElementTable {
match kind {
ExprKind::Numeric => &BASE_TABLE,
ExprKind::String => &SCALC_TABLE,
ExprKind::Array => &ACALC_TABLE,
}
}
pub(crate) fn runtime_stack_size(kind: ExprKind) -> i32 {
table_for(&kind).stack_size
}
struct Tokenizer<'a> {
input: &'a [u8],
pos: usize,
table: &'static ElementTable,
}
impl<'a> Tokenizer<'a> {
fn new(input: &'a str, table: &'static ElementTable) -> Self {
Tokenizer {
input: input.as_bytes(),
pos: 0,
table,
}
}
fn peek(&self) -> Option<u8> {
self.input.get(self.pos).copied()
}
fn advance(&mut self) -> Option<u8> {
let b = self.input.get(self.pos).copied()?;
self.pos += 1;
Some(b)
}
fn skip_whitespace(&mut self) {
while self.pos < self.input.len() && self.input[self.pos].is_ascii_whitespace() {
self.pos += 1;
}
}
fn read_string_literal(&mut self, quote: u8) -> Vec<u8> {
let start = self.pos;
while let Some(b) = self.peek() {
if b == quote {
break;
}
self.pos += 1;
}
let raw = self.input[start..self.pos].to_vec();
if self.peek() == Some(quote) {
self.pos += 1;
}
raw
}
fn match_symbol(&mut self) -> Option<Token> {
let rem = &self.input[self.pos..];
let mut best: Option<(usize, &Token)> = None;
for (name, tok) in self.table.symbols {
if !starts_with_ci(rem, name) {
continue;
}
let n = name.len();
if best.is_none_or(|(blen, _)| n > blen) {
best = Some((n, tok));
}
}
let (len, tok) = best?;
self.pos += len;
Some(tok.clone())
}
fn match_var(&mut self) -> Option<Token> {
let rem = &self.input[self.pos..];
if let Some(last) = self.table.last_double_var {
if rem.len() >= 2 {
let a = rem[0].to_ascii_uppercase();
let b = rem[1].to_ascii_uppercase();
if a == b && a.is_ascii_uppercase() && a - b'A' <= last {
self.pos += 2;
return Some(Token::DoubleVar(a - b'A'));
}
}
}
let c = rem.first()?.to_ascii_uppercase();
if c.is_ascii_uppercase() && c - b'A' <= self.table.last_var {
self.pos += 1;
return Some(Token::Var(c - b'A'));
}
None
}
fn at_literal(&self) -> bool {
let rem = &self.input[self.pos..];
match rem.first() {
None => false,
Some(b) if b.is_ascii_digit() || *b == b'.' => true,
_ => self
.table
.literal_words
.iter()
.any(|w| starts_with_ci(rem, w)),
}
}
fn read_literal(&mut self) -> Result<f64, CalcError> {
let start = self.pos;
let rem = &self.input[start..];
let is_hex = rem.first() == Some(&b'0')
&& matches!(rem.get(1), Some(b'x' | b'X'))
&& rem.get(2).is_some_and(u8::is_ascii_hexdigit);
if is_hex && self.table.literals == LiteralReader::ParseDouble {
let end = 2 + rem[2..]
.iter()
.take_while(|b| b.is_ascii_hexdigit())
.count();
self.pos = start + end;
let digits = std::str::from_utf8(&rem[2..end]).unwrap();
return u32::from_str_radix(digits, 16)
.map(f64::from)
.map_err(|_| CalcError::BadLiteral);
}
let n = strtod::strtod(rem);
if n.len == 0 {
return Err(CalcError::BadLiteral);
}
if n.erange && self.table.literals == LiteralReader::ParseDouble {
return Err(CalcError::BadLiteral);
}
self.pos += n.len;
Ok(n.value)
}
}
fn starts_with_ci(text: &[u8], name: &str) -> bool {
strtod::starts_with_ci(text, name.as_bytes())
}
pub fn tokenize(input: &str, kind: ExprKind) -> Result<Vec<Token>, CalcError> {
let table = table_for(&kind);
let mut tokenizer = Tokenizer::new(input, table);
let mut tokens = Vec::new();
loop {
tokenizer.skip_whitespace();
let Some(b) = tokenizer.peek() else { break };
if tokenizer.at_literal() {
let n = tokenizer.read_literal()?;
tokens.push(Token::Number(n));
continue;
}
if (b == b'"' || b == b'\'') && table.string_literals {
tokenizer.advance();
let s = tokenizer.read_string_literal(b);
tokens.push(Token::StringLiteral(s));
continue;
}
if let Some(tok) = tokenizer.match_symbol() {
tokens.push(tok);
continue;
}
if let Some(tok) = tokenizer.match_var() {
tokens.push(tok);
continue;
}
return Err(CalcError::Syntax);
}
Ok(tokens)
}
#[cfg(test)]
mod tests {
use super::*;
fn base(expr: &str) -> Result<Vec<Token>, CalcError> {
tokenize(expr, ExprKind::Numeric)
}
fn scalc(expr: &str) -> Result<Vec<Token>, CalcError> {
tokenize(expr, ExprKind::String)
}
fn acalc(expr: &str) -> Result<Vec<Token>, CalcError> {
tokenize(expr, ExprKind::Array)
}
#[test]
fn test_basic_tokens() {
assert_eq!(
base("A+B*3").unwrap(),
vec![
Token::Var(0),
Token::Plus,
Token::Var(1),
Token::Star,
Token::Number(3.0)
]
);
}
#[test]
fn test_functions() {
assert_eq!(
base("SIN(A)").unwrap(),
vec![
Token::Func(FuncName::Sin),
Token::LParen,
Token::Var(0),
Token::RParen,
]
);
}
#[test]
fn test_double_vars() {
assert_eq!(
scalc("AA+BB").unwrap(),
vec![Token::DoubleVar(0), Token::Plus, Token::DoubleVar(1)]
);
assert_eq!(base("AA").unwrap(), vec![Token::Var(0), Token::Var(0)]);
}
#[test]
fn test_constants() {
assert_eq!(
base("PI+D2R").unwrap(),
vec![
Token::Const(ConstName::Pi),
Token::Plus,
Token::Const(ConstName::D2R),
]
);
}
#[test]
fn test_case_insensitive() {
assert_eq!(
base("sin(a)+Cos(b)").unwrap(),
vec![
Token::Func(FuncName::Sin),
Token::LParen,
Token::Var(0),
Token::RParen,
Token::Plus,
Token::Func(FuncName::Cos),
Token::LParen,
Token::Var(1),
Token::RParen,
]
);
}
#[test]
fn test_assign() {
assert_eq!(
base("A:=5").unwrap(),
vec![Token::Var(0), Token::Assign, Token::Number(5.0)]
);
}
#[test]
fn test_ternary() {
assert_eq!(
base("A?B:C").unwrap(),
vec![
Token::Var(0),
Token::Question,
Token::Var(1),
Token::Colon,
Token::Var(2),
]
);
}
#[test]
fn test_hex() {
assert_eq!(base("0xFF").unwrap(), vec![Token::Number(255.0)]);
assert_eq!(scalc("0xFF").unwrap(), vec![Token::Number(255.0)]);
}
#[test]
fn test_float_literal() {
assert_eq!(base("3.14e2").unwrap(), vec![Token::Number(314.0)]);
}
#[test]
fn test_operand_range_per_table() {
assert_eq!(base("U").unwrap(), vec![Token::Var(20)]);
assert_eq!(scalc("P").unwrap(), vec![Token::Var(15)]);
assert_eq!(scalc("Q"), Err(CalcError::Syntax));
assert_eq!(acalc("U"), Err(CalcError::Syntax));
assert_eq!(scalc("LL").unwrap(), vec![Token::DoubleVar(11)]);
assert_eq!(scalc("MM").unwrap(), vec![Token::Var(12), Token::Var(12)]);
}
#[test]
fn test_symbols_outside_a_table() {
assert_eq!(base("FMOD(A,B)").unwrap()[0], Token::Func(FuncName::Fmod));
assert_eq!(base("A>>>1").unwrap()[1], Token::ShrLogical);
assert_eq!(scalc("A>>>1").unwrap()[1], Token::Shr);
assert_eq!(base("INF").unwrap(), vec![Token::Number(f64::INFINITY)]);
assert_eq!(scalc("INF").unwrap(), vec![Token::Number(f64::INFINITY)]);
assert_eq!(
acalc("INF").unwrap(),
vec![Token::Var(8), Token::Var(13), Token::Var(5)]
);
assert_eq!(
scalc("DBL(AA)").unwrap()[0],
Token::Func(FuncName::Dbl),
"sCalc DBL is the string->double op"
);
assert_eq!(
acalc("DBL(AA)").unwrap()[0],
Token::Func(FuncName::AToD),
"aCalc DBL is the array->double op"
);
}
}