use crate::deck::Card;
use crate::span::Span;
#[derive(Clone, Copy, Debug)]
pub(crate) struct Token<'c> {
pub text: &'c str,
pub start: usize,
pub end: usize,
}
impl Token<'_> {
pub fn lc(&self) -> String {
self.text.to_ascii_lowercase()
}
pub fn eq_ic(&self, other: &str) -> bool {
self.text.eq_ignore_ascii_case(other)
}
}
fn is_sep(b: u8) -> bool {
matches!(b, b' ' | b'\t' | b'=' | b'(' | b')' | b',')
}
#[derive(Clone)]
pub(crate) struct Scanner<'c> {
card: &'c Card,
pos: usize,
}
impl<'c> Scanner<'c> {
pub fn new(card: &'c Card) -> Self {
Scanner { card, pos: 0 }
}
pub fn span(&self, t: &Token) -> Span {
self.card.span_of(t.start, t.end)
}
pub fn here(&self) -> Span {
let p = self.pos.min(self.card.text.len());
self.card.span_of(p.saturating_sub(1), p)
}
fn skip_seps(&mut self) {
let bytes = self.card.text.as_bytes();
while self.pos < bytes.len() && is_sep(bytes[self.pos]) {
self.pos += 1;
}
}
pub fn next_tok(&mut self) -> Option<Token<'c>> {
self.skip_seps();
let bytes = self.card.text.as_bytes();
if self.pos >= bytes.len() {
return None;
}
let start = self.pos;
while self.pos < bytes.len() && !is_sep(bytes[self.pos]) {
self.pos += 1;
}
Some(Token {
text: &self.card.text[start..self.pos],
start,
end: self.pos,
})
}
pub fn seek(&mut self, pos: usize) {
self.pos = pos.min(self.card.text.len());
}
pub fn peek_tok(&self) -> Option<Token<'c>> {
self.clone().next_tok()
}
pub fn try_value(&mut self) -> Option<(f64, Token<'c>)> {
let mut probe = self.clone();
probe.skip_seps();
let bytes = probe.card.text.as_bytes();
if probe.pos >= bytes.len() {
return None;
}
let start = probe.pos;
let end = start + utok_len(&bytes[start..]);
let text = &self.card.text[start..end];
let value = evaluate(text)?;
self.pos = end;
Some((value, Token { text, start, end }))
}
}
fn utok_len(bytes: &[u8]) -> usize {
let mut signstate = 0u8;
for (i, &b) in bytes.iter().enumerate() {
if is_sep(b) {
return i;
}
match b {
b'+' if signstate == 1 || signstate == 3 => return i,
b'-' => {
if signstate == 1 || signstate == 3 {
return i;
}
signstate += 1;
continue;
}
b'*' | b'/' | b'^' => return i,
_ => {}
}
if b.is_ascii_digit() || b == b'.' {
signstate = if signstate > 1 { 3 } else { 1 };
} else if (b == b'e' || b == b'E') && signstate == 1 {
signstate = 2;
} else {
signstate = 3;
}
}
bytes.len()
}
pub(crate) fn evaluate(token: &str) -> Option<f64> {
evaluate_prefix(token.as_bytes()).map(|(v, _)| v)
}
pub(crate) fn evaluate_prefix(b: &[u8]) -> Option<(f64, usize)> {
let mut i = 0usize;
let mut mantis: f64 = 0.0;
let mut expo1: i32 = 0;
let mut expo2: i32 = 0;
let mut sign: f64 = 1.0;
let mut expsgn: i32 = 1;
if i < b.len() && b[i] == b'+' {
i += 1;
}
if i < b.len() && b[i] == b'-' {
i += 1;
sign = -1.0;
}
if i >= b.len() || (!b[i].is_ascii_digit() && b[i] != b'.') {
return None; }
while i < b.len() && b[i].is_ascii_digit() {
mantis = 10.0 * mantis + f64::from(b[i] - b'0');
i += 1;
}
if i >= b.len() {
return Some((mantis * sign, i));
}
if b[i] == b':' {
return None;
}
if b[i] == b'.' {
i += 1;
if i >= b.len() {
return Some((mantis * sign, i));
}
while i < b.len() && b[i].is_ascii_digit() {
mantis = 10.0 * mantis + f64::from(b[i] - b'0');
expo1 -= 1;
i += 1;
}
}
if i < b.len() && matches!(b[i], b'E' | b'e' | b'D' | b'd') {
i += 1;
if i < b.len() && b[i] == b'+' {
i += 1;
}
if i < b.len() && b[i] == b'-' {
i += 1;
expsgn = -1;
}
while i < b.len() && b[i].is_ascii_digit() {
expo2 = expo2
.saturating_mul(10)
.saturating_add(i32::from(b[i] - b'0'));
i += 1;
}
}
if i < b.len() {
let scale_len = match b[i].to_ascii_lowercase() {
b't' => {
expo1 += 12;
1
}
b'g' => {
expo1 += 9;
1
}
b'k' => {
expo1 += 3;
1
}
b'u' => {
expo1 -= 6;
1
}
b'n' => {
expo1 -= 9;
1
}
b'p' => {
expo1 -= 12;
1
}
b'f' => {
expo1 -= 15;
1
}
b'm' => {
let two = b.get(i + 1).map(u8::to_ascii_lowercase);
let three = b.get(i + 2).map(u8::to_ascii_lowercase);
match (two, three) {
(Some(b'e'), Some(b'g')) => {
expo1 += 6;
3
}
(Some(b'i'), Some(b'l')) => {
expo1 -= 6;
mantis *= 25.4;
3
}
_ => {
expo1 -= 3;
1
}
}
}
_ => 0,
};
i += scale_len;
}
Some((
sign * mantis * pow10(expo1.saturating_add(expsgn * expo2)),
i,
))
}
fn pow10(k: i32) -> f64 {
if (-400..=400).contains(&k) {
format!("1e{k}")
.parse()
.unwrap_or_else(|_| 10f64.powf(f64::from(k)))
} else {
10f64.powf(f64::from(k))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pow10_is_correctly_rounded() {
assert_eq!(pow10(-5), 1e-5);
assert_eq!(pow10(-23), 1e-23);
assert_eq!(pow10(22), 1e22);
assert_eq!(pow10(308), 1e308);
assert_eq!(pow10(-323), 1e-323);
assert_eq!(evaluate("4.00000E+000"), Some(4.0));
assert_eq!(pow10(-1000), 0.0);
assert_eq!(pow10(1000), f64::INFINITY);
assert_eq!(pow10(i32::MIN), 0.0);
}
use crate::deck::{Card, read_with_loader};
use std::path::Path;
fn card(line: &str) -> Card {
struct NoInc;
impl crate::deck::IncludeLoader for NoInc {
fn load(&mut self, _: &Path, _: u64) -> Result<String, String> {
Err("none".into())
}
}
let mut deck = read_with_loader(Path::new("t.cir"), format!("t\n{line}\n"), &mut NoInc);
deck.cards.remove(0)
}
#[test]
fn separators_vanish() {
let c = card("v1 (1,2) dc=5");
let mut s = Scanner::new(&c);
let toks: Vec<String> = std::iter::from_fn(|| s.next_tok().map(|t| t.lc())).collect();
assert_eq!(toks, ["v1", "1", "2", "dc", "5"]);
}
fn approx(token: &str, expect: f64) {
let got = evaluate(token).unwrap_or_else(|| panic!("{token} did not parse"));
assert!(
(got - expect).abs() <= expect.abs() * 1e-12,
"{token}: got {got:e}, expected ~{expect:e}"
);
}
#[test]
fn numbers_match_the_manual() {
for t in ["1000", "1000.0", "1000Hz", "1e3", "1.0e3", "1KHz", "1K"] {
assert_eq!(evaluate(t), Some(1000.0), "{t}");
}
approx("2.2meg", 2.2e6);
approx("10u", 1e-5);
approx("1e-12", 1e-12);
approx("10kohm", 1e4);
approx("1mil", 25.4e-6);
for t in ["5M", "5MA", "5MSec", "5MMhos", "5m"] {
approx(t, 5e-3);
}
assert_eq!(evaluate("1d3"), Some(1000.0));
approx("-1.5e-2", -0.015);
assert_eq!(evaluate("+3"), Some(3.0));
assert_eq!(evaluate("."), Some(0.0));
assert_eq!(evaluate("1."), Some(1.0));
assert_eq!(evaluate("-"), None);
assert_eq!(evaluate("abc"), None);
assert_eq!(evaluate("1:2"), None);
assert_eq!(evaluate(""), None);
}
#[test]
fn try_value_consumes_whole_token_or_nothing() {
let c = card("r1 1 0 10kohm w=5u");
let mut s = Scanner::new(&c);
assert!(s.try_value().is_none()); s.next_tok(); assert_eq!(s.try_value().map(|v| v.0), Some(1.0));
assert_eq!(s.try_value().map(|v| v.0), Some(0.0));
let kohm = s.try_value().unwrap().0; assert!((kohm - 1e4).abs() < 1e-6);
assert!(s.try_value().is_none()); assert_eq!(s.next_tok().unwrap().lc(), "w");
let w = s.try_value().unwrap().0;
assert!((w - 5e-6).abs() <= 5e-18);
assert!(s.peek_tok().is_none());
}
#[test]
fn utok_cuts_at_operators_like_the_c() {
let c = card("x 3-2");
let mut s = Scanner::new(&c);
s.next_tok();
assert_eq!(s.try_value().map(|v| v.0), Some(3.0));
assert_eq!(s.next_tok().unwrap().text, "-2");
let c = card("x 1e+5 1e-5");
let mut s = Scanner::new(&c);
s.next_tok();
assert_eq!(s.try_value().map(|v| v.0), Some(1e5));
assert_eq!(s.try_value().map(|v| v.0), Some(1e-5));
}
#[test]
fn spans_point_at_the_source() {
let c = card("r1 1 0 1k");
let mut s = Scanner::new(&c);
let t = s.next_tok().unwrap();
assert_eq!((t.start, t.end), (0, 2));
}
}