use super::error::CalcError;
use super::opcodes::{ControlOp, 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,
Subrange {
in_place: bool,
runtime_effect: i32,
},
VarargFunc {
func: FuncName,
in_stack_pri: u8,
nargs: u8,
},
CondEnd,
Store(StoreTarget),
UntilEnd {
until_pc: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum StoreTarget {
Var(u8),
DoubleVar(u8),
Dyn,
DynDouble,
}
impl StackEntry {
fn in_stack_pri(&self) -> u8 {
match self {
StackEntry::Op { in_stack_pri, .. } => *in_stack_pri,
StackEntry::LParen => 0,
StackEntry::Subrange { .. } => 0,
StackEntry::VarargFunc { in_stack_pri, .. } => *in_stack_pri,
StackEntry::CondEnd => 0,
StackEntry::Store(_) => 1,
StackEntry::UntilEnd { .. } => 0,
}
}
}
fn binary_op(token: &Token) -> Option<(u8, u8)> {
match token {
Token::OrOr | Token::BitOr | Token::BitXor => Some((2, 2)),
Token::AndAnd | Token::BitAnd | 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 => CoreOp::And,
Token::OrOr => 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
| FuncName::FitQ
| FuncName::FitMQ
)
}
fn func_arity(func: &FuncName) -> u8 {
match func {
FuncName::ARndm | FuncName::Ix => 0,
FuncName::Atan2 | FuncName::Fmod => 2,
FuncName::Printf | FuncName::Sscanf | FuncName::BinRead | FuncName::BinWrite => 2,
FuncName::NSmoo | FuncName::NDeriv | FuncName::Cat | FuncName::FitMPoly => 2,
FuncName::FitPoly => 1,
_ => 1,
}
}
fn is_nullary_func(func: &FuncName) -> bool {
func_arity(func) == 0
}
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::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::ANeg => return Opcode::Array(super::opcodes::ArrayOp::ANeg),
FuncName::APos => return Opcode::Array(super::opcodes::ArrayOp::APos),
FuncName::DynFetch => return Opcode::Array(super::opcodes::ArrayOp::DynFetch),
FuncName::DynAFetch => return Opcode::Array(super::opcodes::ArrayOp::DynAFetch),
FuncName::SDynFetch => return Opcode::String(super::opcodes::StringOp::DynFetch),
FuncName::SDynSFetch => return Opcode::String(super::opcodes::StringOp::DynSFetch),
FuncName::ALenNoop => return Opcode::Array(super::opcodes::ArrayOp::LenNoop),
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(nargs)),
FuncName::FitMQ => return Opcode::Array(super::opcodes::ArrayOp::FitMQ(nargs)),
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>, kind: ExprKind) {
match entry {
StackEntry::Subrange { in_place, .. } => {
output.push(match (kind, in_place) {
(ExprKind::Array, false) => Opcode::Array(super::opcodes::ArrayOp::ArraySubrange),
(ExprKind::Array, true) => {
Opcode::Array(super::opcodes::ArrayOp::ArraySubrangeInPlace)
}
(_, false) => Opcode::String(super::opcodes::StringOp::Subrange),
(_, true) => Opcode::String(super::opcodes::StringOp::Replace),
});
}
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::UntilEnd { until_pc } => {
let end_pc = output.len();
output.push(Opcode::Control(ControlOp::UntilEnd(*until_pc)));
output[*until_pc] = Opcode::Control(ControlOp::Until(end_pc));
}
StackEntry::Store(target) => {
use super::opcodes::{ArrayOp, StringOp};
output.push(match target {
StoreTarget::Var(idx) => Opcode::Core(CoreOp::StoreVar(*idx)),
StoreTarget::DoubleVar(idx) => Opcode::Core(CoreOp::StoreDoubleVar(*idx)),
StoreTarget::Dyn => match kind {
ExprKind::Array => Opcode::Array(ArrayOp::DynStore),
_ => Opcode::String(StringOp::DynStore),
},
StoreTarget::DynDouble => match kind {
ExprKind::Array => Opcode::Array(ArrayOp::DynAStore),
_ => Opcode::String(StringOp::DynSStore),
},
});
}
StackEntry::LParen => {}
}
}
pub fn compile(tokens: &[Token], kind: ExprKind) -> Result<CompiledExpr, CalcError> {
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;
let mut uses_string = false;
let stack_size = super::token::runtime_stack_size(kind);
while pos < tokens.len() {
let token = &tokens[pos];
pos += 1;
if kind == ExprKind::String && token_is_string_element(token) {
uses_string = true;
}
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::FetchAval => {
output.push(Opcode::Array(super::opcodes::ArrayOp::FetchAval));
runtime_depth += 1;
operand_needed = false;
}
Token::FetchSval => {
output.push(Opcode::Core(CoreOp::FetchSval));
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)),
ConstName::S2R => output.push(Opcode::Core(CoreOp::S2R)),
ConstName::R2S => output.push(Opcode::Core(CoreOp::R2S)),
}
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;
}
Token::Minus => {
pop_higher_or_equal(&mut stack, 10, &mut output, &mut runtime_depth, kind);
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, kind);
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, kind);
stack.push(StackEntry::Op {
token: Token::Tilde,
in_stack_pri: 9,
});
}
Token::LParen => {
stack.push(StackEntry::LParen);
}
Token::UntilKeyword => {
pop_higher_or_equal(&mut stack, 10, &mut output, &mut runtime_depth, kind);
let until_pc = output.len();
output.push(Opcode::Control(ControlOp::Until(0)));
stack.push(StackEntry::UntilEnd { until_pc });
}
Token::Func(func) => {
pop_higher_or_equal(&mut stack, 10, &mut output, &mut runtime_depth, kind);
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,
});
}
if !is_vararg(func) && is_nullary_func(func) {
operand_needed = false;
}
}
_ => 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, kind);
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, kind);
}
}
},
Token::Comma => {
loop {
match stack.last() {
None => return Err(CalcError::BadSeparator),
Some(e) if is_barrier(e) => break,
_ => {
let entry = stack.pop().unwrap();
runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, &mut output, kind);
}
}
}
match stack.last_mut() {
Some(StackEntry::Subrange { runtime_effect, .. }) => *runtime_effect -= 1,
Some(StackEntry::LParen) => {
let lparen_idx = stack.len() - 1;
if lparen_idx > 0 {
if let StackEntry::VarargFunc { nargs, .. } =
&mut stack[lparen_idx - 1]
{
*nargs += 1;
}
}
}
_ => unreachable!("the loop above only breaks on a barrier"),
}
operand_needed = true;
}
Token::Question => {
pop_higher_strict(&mut stack, 0, &mut output, &mut runtime_depth, kind);
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, kind);
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, kind);
}
if cond_count != 0 {
return Err(CalcError::Conditional);
}
if runtime_depth > 1 {
return Err(CalcError::TooMany);
}
operand_needed = true;
}
Token::Assign => {
let pending_dyn = stack.iter().rposition(|e| {
matches!(
e,
StackEntry::Op {
token: Token::Func(
FuncName::SDynFetch
| FuncName::SDynSFetch
| FuncName::DynFetch
| FuncName::DynAFetch
),
..
}
)
});
match pending_dyn {
Some(at) => {
while stack.len() > at + 1 {
let entry = stack.pop().unwrap();
runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, &mut output, kind);
}
let StackEntry::Op {
token: Token::Func(f),
..
} = &stack[at]
else {
unreachable!("rposition matched a Func entry")
};
let target = match f {
FuncName::SDynFetch | FuncName::DynFetch => StoreTarget::Dyn,
_ => StoreTarget::DynDouble,
};
stack[at] = StackEntry::Store(target);
}
None => {
let target = match output.last() {
Some(Opcode::Core(CoreOp::PushVar(idx))) => StoreTarget::Var(*idx),
Some(Opcode::Core(CoreOp::PushDoubleVar(idx))) => {
StoreTarget::DoubleVar(*idx)
}
_ => return Err(CalcError::BadAssignment),
};
output.pop();
stack.push(StackEntry::Store(target));
}
}
runtime_depth -= 1;
operand_needed = true;
}
Token::LBracket | Token::LBrace => {
let in_place = matches!(token, Token::LBrace);
pop_higher_or_equal(&mut stack, 11, &mut output, &mut runtime_depth, kind);
stack.push(StackEntry::Subrange {
in_place,
runtime_effect: -1,
});
operand_needed = true;
}
Token::RBracket | Token::RBrace => {
let want_in_place = matches!(token, Token::RBrace);
let not_open = if want_in_place {
CalcError::BraceNotOpen
} else {
CalcError::BracketNotOpen
};
loop {
match stack.last() {
None => return Err(not_open),
Some(StackEntry::Subrange { in_place, .. })
if *in_place == want_in_place =>
{
let entry = stack.pop().unwrap();
runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, &mut output, kind);
break;
}
_ => {
let entry = stack.pop().unwrap();
runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, &mut output, kind);
}
}
}
}
Token::PipeMinus => {
pop_higher_or_equal(&mut stack, 6, &mut output, &mut runtime_depth, kind);
stack.push(StackEntry::Op {
token: Token::PipeMinus,
in_stack_pri: 6,
});
operand_needed = true;
}
_ => return Err(CalcError::Syntax),
}
}
if runtime_depth < 0 {
return Err(CalcError::Underflow);
}
if runtime_depth >= stack_size {
return Err(CalcError::Overflow);
}
}
while let Some(entry) = stack.pop() {
match entry {
StackEntry::LParen => return Err(CalcError::ParenOpen),
_ => {
runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, &mut output, kind);
}
}
}
if cond_count != 0 {
return Err(CalcError::Conditional);
}
if operand_needed || runtime_depth != 1 {
return Err(CalcError::Incomplete);
}
output.push(Opcode::Core(CoreOp::End));
Ok(CompiledExpr {
code: output,
kind,
uses_string,
})
}
fn token_is_string_element(token: &Token) -> bool {
match token {
Token::DoubleVar(_) | Token::FetchSval | Token::StringLiteral(_) => true,
Token::LBracket | Token::LBrace => true,
Token::Func(f) => matches!(
f,
FuncName::Str | FuncName::Printf | FuncName::BinWrite | FuncName::Sscanf | FuncName::BinRead | FuncName::SDynSFetch | FuncName::TrEsc | FuncName::Esc | FuncName::Crc16 | FuncName::ModBus | FuncName::Lrc | FuncName::AModBus | FuncName::Xor8 | FuncName::AddXor8 | FuncName::Len ),
_ => false,
}
}
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),
..
} => {
1 - func_arity(f) as i32
}
StackEntry::Op { .. } => -1,
StackEntry::VarargFunc { nargs, .. } => 1 - (*nargs as i32),
StackEntry::CondEnd => 0,
StackEntry::Store(_) => -1,
StackEntry::LParen => 0,
StackEntry::Subrange { runtime_effect, .. } => *runtime_effect,
StackEntry::UntilEnd { .. } => 0,
}
}
fn is_barrier(entry: &StackEntry) -> bool {
matches!(entry, StackEntry::LParen | StackEntry::Subrange { .. })
}
fn pop_higher_or_equal(
stack: &mut Vec<StackEntry>,
incoming_pri: u8,
output: &mut Vec<Opcode>,
runtime_depth: &mut i32,
kind: ExprKind,
) {
while let Some(entry) = stack.last() {
if is_barrier(entry) || entry.in_stack_pri() < incoming_pri {
break;
}
let entry = stack.pop().unwrap();
*runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, output, kind);
}
}
fn pop_higher_strict(
stack: &mut Vec<StackEntry>,
incoming_pri: u8,
output: &mut Vec<Opcode>,
runtime_depth: &mut i32,
kind: ExprKind,
) {
while let Some(entry) = stack.last() {
if is_barrier(entry) || entry.in_stack_pri() <= incoming_pri {
break;
}
let entry = stack.pop().unwrap();
*runtime_depth += stack_effect(&entry);
flush_stack_entry(&entry, output, kind);
}
}