use super::error::CalcError;
use super::opcodes::{CoreOp, Opcode};
use super::token::{ConstName, FuncName, Token};
use super::{CompiledExpr, ExprKind};
#[derive(Debug, Clone)]
enum StackEntry {
Op {
token: Token,
in_stack_pri: u8,
},
LParen,
VarargFunc {
func: FuncName,
in_stack_pri: u8,
nargs: u8,
},
CondEnd,
Store {
var_idx: u8,
is_double: bool,
},
}
impl StackEntry {
fn in_stack_pri(&self) -> u8 {
match self {
StackEntry::Op { in_stack_pri, .. } => *in_stack_pri,
StackEntry::LParen => 0,
StackEntry::VarargFunc { in_stack_pri, .. } => *in_stack_pri,
StackEntry::CondEnd => 0,
StackEntry::Store { .. } => 1,
}
}
}
fn binary_op(token: &Token) -> Option<(u8, u8)> {
match token {
Token::OrOr | Token::BitOr | Token::OrKeyword | Token::BitXor => Some((2, 2)),
Token::AndAnd
| Token::BitAnd
| Token::AndKeyword
| Token::Shr
| Token::ShrLogical
| Token::Shl => Some((3, 3)),
Token::MaxOp | Token::MinOp => Some((4, 4)),
Token::Eq | Token::Ne | Token::Lt | Token::Le | Token::Gt | Token::Ge => Some((5, 5)),
Token::Plus | Token::Minus => Some((6, 6)),
Token::Star | Token::Slash | Token::Percent => Some((7, 7)),
Token::Caret | Token::DoubleStar => Some((8, 8)),
_ => None,
}
}
fn token_to_binary_opcode(token: &Token) -> Opcode {
let core = match token {
Token::Plus => CoreOp::Add,
Token::Minus => CoreOp::Sub,
Token::Star => CoreOp::Mul,
Token::Slash => CoreOp::Div,
Token::Percent => CoreOp::Mod,
Token::Caret | Token::DoubleStar => CoreOp::Power,
Token::Eq => CoreOp::Eq,
Token::Ne => CoreOp::Ne,
Token::Lt => CoreOp::Lt,
Token::Le => CoreOp::Le,
Token::Gt => CoreOp::Gt,
Token::Ge => CoreOp::Ge,
Token::AndAnd | Token::AndKeyword => CoreOp::And,
Token::OrOr | Token::OrKeyword => CoreOp::Or,
Token::BitAnd => CoreOp::BitAnd,
Token::BitOr => CoreOp::BitOr,
Token::BitXor => CoreOp::BitXor,
Token::Shl => CoreOp::Shl,
Token::Shr => CoreOp::Shr,
Token::ShrLogical => CoreOp::ShrLogical,
Token::MaxOp => CoreOp::MaxVal,
Token::MinOp => CoreOp::MinVal,
Token::PipeMinus => {
return Opcode::String(super::opcodes::StringOp::SubLast);
}
_ => unreachable!(),
};
Opcode::Core(core)
}
fn is_vararg(func: &FuncName) -> bool {
matches!(
func,
FuncName::Min | FuncName::Max | FuncName::Finite | FuncName::IsNan
)
}
fn func_to_opcode(func: &FuncName, nargs: u8) -> Opcode {
let core = match func {
FuncName::Abs => CoreOp::Abs,
FuncName::Sqrt | FuncName::Sqr => CoreOp::Sqrt,
FuncName::Exp => CoreOp::Exp,
FuncName::Log10 => CoreOp::Log10,
FuncName::LogE | FuncName::Ln => CoreOp::LogE,
FuncName::Log2 => CoreOp::Log2,
FuncName::Sin => CoreOp::Sin,
FuncName::Cos => CoreOp::Cos,
FuncName::Tan => CoreOp::Tan,
FuncName::Asin => CoreOp::Asin,
FuncName::Acos => CoreOp::Acos,
FuncName::Atan => CoreOp::Atan,
FuncName::Atan2 => CoreOp::Atan2,
FuncName::Fmod => CoreOp::Fmod,
FuncName::Sinh => CoreOp::Sinh,
FuncName::Cosh => CoreOp::Cosh,
FuncName::Tanh => CoreOp::Tanh,
FuncName::Ceil => CoreOp::Ceil,
FuncName::Floor => CoreOp::Floor,
FuncName::Nint | FuncName::Int => CoreOp::Nint,
FuncName::IsNan => CoreOp::IsNan(nargs),
FuncName::IsInf => CoreOp::IsInf,
FuncName::Finite => CoreOp::Finite(nargs),
FuncName::Max => CoreOp::Max(nargs),
FuncName::Min => CoreOp::Min(nargs),
FuncName::Not => CoreOp::BitNot,
FuncName::Dbl => return Opcode::String(super::opcodes::StringOp::ToDouble),
FuncName::Str => return Opcode::String(super::opcodes::StringOp::ToString),
FuncName::Len => return Opcode::String(super::opcodes::StringOp::Len),
FuncName::Byte => return Opcode::String(super::opcodes::StringOp::Byte),
FuncName::TrEsc => return Opcode::String(super::opcodes::StringOp::TrEsc),
FuncName::Esc => return Opcode::String(super::opcodes::StringOp::Esc),
FuncName::Printf => return Opcode::String(super::opcodes::StringOp::Printf),
FuncName::Sscanf => return Opcode::String(super::opcodes::StringOp::Sscanf),
FuncName::BinRead => return Opcode::String(super::opcodes::StringOp::BinRead),
FuncName::BinWrite => return Opcode::String(super::opcodes::StringOp::BinWrite),
FuncName::Crc16 => return Opcode::String(super::opcodes::StringOp::Crc16),
FuncName::ModBus => return Opcode::String(super::opcodes::StringOp::Crc16Append),
FuncName::Lrc => return Opcode::String(super::opcodes::StringOp::Lrc),
FuncName::AModBus => return Opcode::String(super::opcodes::StringOp::LrcAppend),
FuncName::Xor8 => return Opcode::String(super::opcodes::StringOp::Xor8),
FuncName::AddXor8 => return Opcode::String(super::opcodes::StringOp::Xor8Append),
FuncName::Avg => return Opcode::Array(super::opcodes::ArrayOp::Average),
FuncName::Std => return Opcode::Array(super::opcodes::ArrayOp::StdDev),
FuncName::FwhmFunc => return Opcode::Array(super::opcodes::ArrayOp::Fwhm),
FuncName::Sum => return Opcode::Array(super::opcodes::ArrayOp::ArraySum),
FuncName::AMax => return Opcode::Array(super::opcodes::ArrayOp::ArrayMax),
FuncName::AMin => return Opcode::Array(super::opcodes::ArrayOp::ArrayMin),
FuncName::IxMax => return Opcode::Array(super::opcodes::ArrayOp::IndexMax),
FuncName::IxMin => return Opcode::Array(super::opcodes::ArrayOp::IndexMin),
FuncName::IxZ => return Opcode::Array(super::opcodes::ArrayOp::IndexZero),
FuncName::IxNz => return Opcode::Array(super::opcodes::ArrayOp::IndexNonZero),
FuncName::Arr => return Opcode::Array(super::opcodes::ArrayOp::ToArray),
FuncName::Ix => return Opcode::Array(super::opcodes::ArrayOp::ConstIndex),
FuncName::AToD => return Opcode::Array(super::opcodes::ArrayOp::ToDouble),
FuncName::Smoo => return Opcode::Array(super::opcodes::ArrayOp::Smooth),
FuncName::NSmoo => return Opcode::Array(super::opcodes::ArrayOp::NSmooth),
FuncName::Deriv => return Opcode::Array(super::opcodes::ArrayOp::Deriv),
FuncName::NDeriv => return Opcode::Array(super::opcodes::ArrayOp::NDeriv),
FuncName::FitPoly => return Opcode::Array(super::opcodes::ArrayOp::FitPoly),
FuncName::FitMPoly => return Opcode::Array(super::opcodes::ArrayOp::FitMPoly),
FuncName::FitQ => return Opcode::Array(super::opcodes::ArrayOp::FitQ),
FuncName::FitMQ => return Opcode::Array(super::opcodes::ArrayOp::FitMQ),
FuncName::Cum => return Opcode::Array(super::opcodes::ArrayOp::Cum),
FuncName::Cat => return Opcode::Array(super::opcodes::ArrayOp::Cat),
FuncName::ARndm => return Opcode::Array(super::opcodes::ArrayOp::ArrayRandom),
};
Opcode::Core(core)
}
fn flush_stack_entry(entry: &StackEntry, output: &mut Vec<Opcode>) {
match entry {
StackEntry::Op {
token: Token::Minus,
in_stack_pri: 9,
..
} => {
output.push(Opcode::Core(CoreOp::Neg));
}
StackEntry::Op {
token: Token::Bang, ..
} => {
output.push(Opcode::Core(CoreOp::Not));
}
StackEntry::Op {
token: Token::Tilde,
..
} => {
output.push(Opcode::Core(CoreOp::BitNot));
}
StackEntry::Op {
token: Token::Func(f),
..
} => {
output.push(func_to_opcode(f, 1));
}
StackEntry::Op { token, .. } => {
output.push(token_to_binary_opcode(token));
}
StackEntry::VarargFunc { func, nargs, .. } => {
output.push(func_to_opcode(func, *nargs));
}
StackEntry::CondEnd => {
output.push(Opcode::Core(CoreOp::CondEnd));
}
StackEntry::Store { var_idx, is_double } => {
if *is_double {
output.push(Opcode::Core(CoreOp::StoreDoubleVar(*var_idx)));
} else {
output.push(Opcode::Core(CoreOp::StoreVar(*var_idx)));
}
}
StackEntry::LParen => {}
}
}
pub fn compile(tokens: &[Token]) -> Result<CompiledExpr, CalcError> {
if tokens.is_empty() {
return Ok(CompiledExpr {
code: vec![Opcode::Core(CoreOp::End)],
kind: ExprKind::Numeric,
loop_pairs: Vec::new(),
});
}
let mut output: Vec<Opcode> = Vec::new();
let mut stack: Vec<StackEntry> = Vec::new();
let mut operand_needed = true;
let mut runtime_depth: i32 = 0;
let mut cond_count: i32 = 0;
let mut pos = 0;
#[allow(unused_mut)]
let mut has_string_ops = false;
#[allow(unused_mut)]
let mut has_array_ops = false;
let mut bracket_depth: i32 = 0;
let mut brace_depth: i32 = 0;
let mut until_stack: Vec<usize> = Vec::new();
while pos < tokens.len() {
let token = &tokens[pos];
pos += 1;
if operand_needed {
match token {
Token::Number(v) => {
output.push(Opcode::Core(CoreOp::PushConst(*v)));
runtime_depth += 1;
operand_needed = false;
}
Token::Var(idx) => {
output.push(Opcode::Core(CoreOp::PushVar(*idx)));
runtime_depth += 1;
operand_needed = false;
}
Token::DoubleVar(idx) => {
output.push(Opcode::Core(CoreOp::PushDoubleVar(*idx)));
runtime_depth += 1;
operand_needed = false;
}
Token::FetchVal => {
output.push(Opcode::Core(CoreOp::FetchVal));
runtime_depth += 1;
operand_needed = false;
}
Token::Rndm => {
output.push(Opcode::Core(CoreOp::Random));
runtime_depth += 1;
operand_needed = false;
}
Token::Nrndm => {
output.push(Opcode::Core(CoreOp::NormalRandom));
runtime_depth += 1;
operand_needed = false;
}
Token::Const(c) => {
match c {
ConstName::Pi => output.push(Opcode::Core(CoreOp::Pi)),
ConstName::D2R => output.push(Opcode::Core(CoreOp::D2R)),
ConstName::R2D => output.push(Opcode::Core(CoreOp::R2D)),
}
runtime_depth += 1;
operand_needed = false;
}
Token::StringLiteral(s) => {
output.push(Opcode::String(super::opcodes::StringOp::PushString(
s.clone(),
)));
runtime_depth += 1;
operand_needed = false;
has_string_ops = true;
}
Token::Minus => {
pop_higher_or_equal(&mut stack, 10, &mut output, &mut runtime_depth);
stack.push(StackEntry::Op {
token: Token::Minus,
in_stack_pri: 9,
});
}
Token::Bang => {
pop_higher_or_equal(&mut stack, 10, &mut output, &mut runtime_depth);
stack.push(StackEntry::Op {
token: Token::Bang,
in_stack_pri: 9,
});
}
Token::Tilde => {
pop_higher_or_equal(&mut stack, 10, &mut output, &mut runtime_depth);
stack.push(StackEntry::Op {
token: Token::Tilde,
in_stack_pri: 9,
});
}
Token::LParen => {
stack.push(StackEntry::LParen);
}
Token::UntilKeyword => {
let until_pc = output.len();
output.push(Opcode::Control(
super::opcodes::ControlOp::Until(0), ));
until_stack.push(until_pc);
has_string_ops = true;
}
Token::Func(func) => {
pop_higher_or_equal(&mut stack, 10, &mut output, &mut runtime_depth);
if is_vararg(func) {
stack.push(StackEntry::VarargFunc {
func: func.clone(),
in_stack_pri: 9,
nargs: 1,
});
} else {
stack.push(StackEntry::Op {
token: token.clone(),
in_stack_pri: 9,
});
}
}
_ => return Err(CalcError::Syntax),
}
} else {
match token {
t if binary_op(t).is_some() => {
let (isp, icp) = binary_op(t).unwrap();
pop_higher_or_equal(&mut stack, icp, &mut output, &mut runtime_depth);
stack.push(StackEntry::Op {
token: t.clone(),
in_stack_pri: isp,
});
operand_needed = true;
}
Token::RParen => {
loop {
match stack.last() {
None => return Err(CalcError::ParenNotOpen),
Some(StackEntry::LParen) => {
stack.pop();
break;
}
_ => {
let entry = stack.pop().unwrap();
runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, &mut output);
}
}
}
if let Some(StackEntry::VarargFunc { .. }) = stack.last() {
let entry = stack.pop().unwrap();
runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, &mut output);
} else if let Some(StackEntry::Op {
token: Token::Func(_),
..
}) = stack.last()
{
let entry = stack.pop().unwrap();
runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, &mut output);
}
}
Token::Comma => {
loop {
match stack.last() {
None => return Err(CalcError::BadSeparator),
Some(StackEntry::LParen) => break,
_ => {
let entry = stack.pop().unwrap();
runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, &mut output);
}
}
}
let lparen_idx = stack.len() - 1;
if lparen_idx > 0 {
if let StackEntry::VarargFunc { nargs, .. } = &mut stack[lparen_idx - 1] {
*nargs += 1;
}
}
operand_needed = true;
}
Token::Question => {
pop_higher_strict(&mut stack, 0, &mut output, &mut runtime_depth);
output.push(Opcode::Core(CoreOp::CondIf));
runtime_depth -= 1;
cond_count += 1;
operand_needed = true;
}
Token::Colon => {
pop_higher_strict(&mut stack, 0, &mut output, &mut runtime_depth);
output.push(Opcode::Core(CoreOp::CondElse));
runtime_depth -= 1;
cond_count -= 1;
if cond_count < 0 {
return Err(CalcError::Conditional);
}
stack.push(StackEntry::CondEnd);
operand_needed = true;
}
Token::Semicolon => {
while let Some(entry) = stack.last() {
if matches!(entry, StackEntry::LParen) {
break;
}
let entry = stack.pop().unwrap();
runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, &mut output);
}
if let Some(until_pc) = until_stack.pop() {
let end_pc = output.len();
output.push(Opcode::Control(super::opcodes::ControlOp::UntilEnd(
until_pc,
)));
output[until_pc] =
Opcode::Control(super::opcodes::ControlOp::Until(end_pc));
}
operand_needed = true;
}
Token::Assign => {
match output.last() {
Some(Opcode::Core(CoreOp::PushVar(idx))) => {
let idx = *idx;
output.pop();
runtime_depth -= 1;
while let Some(entry) = stack.last() {
if matches!(entry, StackEntry::LParen) {
break;
}
if entry.in_stack_pri() >= 1 {
let entry = stack.pop().unwrap();
runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, &mut output);
} else {
break;
}
}
stack.push(StackEntry::Store {
var_idx: idx,
is_double: false,
});
}
Some(Opcode::Core(CoreOp::PushDoubleVar(idx))) => {
let idx = *idx;
output.pop();
runtime_depth -= 1;
stack.push(StackEntry::Store {
var_idx: idx,
is_double: true,
});
}
_ => return Err(CalcError::BadAssignment),
}
operand_needed = true;
}
Token::LBracket => {
pop_higher_or_equal(&mut stack, 11, &mut output, &mut runtime_depth);
stack.push(StackEntry::LParen); operand_needed = true;
has_string_ops = true;
bracket_depth += 1;
}
Token::LBrace => {
pop_higher_or_equal(&mut stack, 11, &mut output, &mut runtime_depth);
stack.push(StackEntry::LParen);
operand_needed = true;
has_string_ops = true;
brace_depth += 1;
}
Token::RBracket => {
if bracket_depth == 0 {
return Err(CalcError::BracketNotOpen);
}
bracket_depth -= 1;
loop {
match stack.last() {
None => return Err(CalcError::BracketNotOpen),
Some(StackEntry::LParen) => {
stack.pop();
break;
}
_ => {
let entry = stack.pop().unwrap();
runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, &mut output);
}
}
}
output.push(Opcode::String(super::opcodes::StringOp::Subrange));
runtime_depth -= 2; }
Token::RBrace => {
if brace_depth == 0 {
return Err(CalcError::BraceNotOpen);
}
brace_depth -= 1;
loop {
match stack.last() {
None => return Err(CalcError::BraceNotOpen),
Some(StackEntry::LParen) => {
stack.pop();
break;
}
_ => {
let entry = stack.pop().unwrap();
runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, &mut output);
}
}
}
output.push(Opcode::String(super::opcodes::StringOp::Replace));
runtime_depth -= 2; }
Token::PipeMinus => {
pop_higher_or_equal(&mut stack, 6, &mut output, &mut runtime_depth);
stack.push(StackEntry::Op {
token: Token::PipeMinus,
in_stack_pri: 6,
});
operand_needed = true;
has_string_ops = true;
}
_ => return Err(CalcError::Syntax),
}
}
if runtime_depth < 0 {
return Err(CalcError::Underflow);
}
if runtime_depth >= 30 {
return Err(CalcError::Overflow);
}
}
if operand_needed && !output.is_empty() {
return Err(CalcError::Incomplete);
}
while let Some(entry) = stack.pop() {
match entry {
StackEntry::LParen => return Err(CalcError::ParenOpen),
_ => {
flush_stack_entry(&entry, &mut output);
}
}
}
if cond_count != 0 {
return Err(CalcError::Conditional);
}
output.push(Opcode::Core(CoreOp::End));
let kind = if has_array_ops {
ExprKind::Array
} else if has_string_ops {
ExprKind::String
} else {
ExprKind::Numeric
};
Ok(CompiledExpr {
code: output,
kind,
loop_pairs: Vec::new(),
})
}
fn stack_effect(entry: &StackEntry) -> i32 {
match entry {
StackEntry::Op {
token: Token::Minus,
in_stack_pri: 9,
..
} => 0,
StackEntry::Op {
token: Token::Bang, ..
} => 0,
StackEntry::Op {
token: Token::Tilde,
..
} => 0,
StackEntry::Op {
token: Token::Func(f),
..
} => match f {
FuncName::Atan2 | FuncName::Fmod => -1,
_ => 0,
},
StackEntry::Op { .. } => -1,
StackEntry::VarargFunc { nargs, .. } => 1 - (*nargs as i32),
StackEntry::CondEnd => 0,
StackEntry::Store { .. } => -1,
StackEntry::LParen => 0,
}
}
fn pop_higher_or_equal(
stack: &mut Vec<StackEntry>,
incoming_pri: u8,
output: &mut Vec<Opcode>,
runtime_depth: &mut i32,
) {
while let Some(entry) = stack.last() {
if matches!(entry, StackEntry::LParen) {
break;
}
if entry.in_stack_pri() >= incoming_pri {
let entry = stack.pop().unwrap();
*runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, output);
} else {
break;
}
}
}
fn pop_higher_strict(
stack: &mut Vec<StackEntry>,
incoming_pri: u8,
output: &mut Vec<Opcode>,
runtime_depth: &mut i32,
) {
while let Some(entry) = stack.last() {
if matches!(entry, StackEntry::LParen) {
break;
}
if entry.in_stack_pri() > incoming_pri {
let entry = stack.pop().unwrap();
*runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, output);
} else {
break;
}
}
}