• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cjrh / misu / 25558672154

08 May 2026 01:37PM UTC coverage: 65.526%. First build
25558672154

Pull #55

github

web-flow
Merge b5a977811 into 13535dd84
Pull Request #55: Rewritten in rust

747 of 1140 new or added lines in 7 files covered. (65.53%)

747 of 1140 relevant lines covered (65.53%)

111.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.77
/rust/src/parser.rs
1
//! Tiny parser for `quantity_from_string`.
2
//!
3
//! Accepts strings like:
4
//!   `1 m`, `1.0 m`, `-1.158e+05 m/s kg^6.0`, `1 m^2 s^-1`, `1 m^2     s^-1`.
5
//!
6
//! All multiplications are done with raw `f64` + `Dim` ops in Rust — far
7
//! faster than the Cython implementation, which round-tripped through
8
//! Python's `eval()` on a regex-rewritten string.
9

10
use pyo3::prelude::*;
11

12
use crate::dim::Dim;
13
use crate::quantity::Quantity;
14
use crate::registry::UNIT_REGISTRY;
15

16
pub fn parse<'py>(py: Python<'py>, input: &str) -> PyResult<Bound<'py, PyAny>> {
3✔
17
    let s = input.trim();
3✔
18
    let mut iter = s.chars().peekable();
3✔
19

20
    // 1. Optional leading sign + numeric magnitude.
21
    let mut buf = String::new();
3✔
22
    if matches!(iter.peek(), Some('+') | Some('-')) {
3✔
23
        buf.push(iter.next().unwrap());
1✔
24
    }
2✔
25
    while let Some(&c) = iter.peek() {
14✔
26
        if c.is_ascii_digit() || c == '.' || c == 'e' || c == 'E' {
14✔
27
            buf.push(c);
10✔
28
            iter.next();
10✔
29
        } else if (c == '+' || c == '-') && matches!(buf.chars().last(), Some('e') | Some('E')) {
10✔
30
            buf.push(c);
1✔
31
            iter.next();
1✔
32
        } else {
1✔
33
            break;
3✔
34
        }
35
    }
36
    let mut magnitude: f64 = buf.parse().map_err(|_| {
3✔
NEW
37
        pyo3::exceptions::PyValueError::new_err(format!(
×
38
            "Cannot parse number from {:?}",
39
            input
40
        ))
NEW
41
    })?;
×
42
    let mut dim = Dim::DIMENSIONLESS;
3✔
43

44
    skip_ws(&mut iter);
3✔
45
    while let Some(&c) = iter.peek() {
10✔
46
        let op_char = if c == '/' || c == '*' {
7✔
47
            let ch = iter.next().unwrap();
1✔
48
            skip_ws(&mut iter);
1✔
49
            ch
1✔
50
        } else {
51
            '*' // implicit multiplication between adjacent symbols
6✔
52
        };
53
        let (sym, exp) = read_term(&mut iter)?;
7✔
54
        let q = {
7✔
55
            let reg = UNIT_REGISTRY.read();
7✔
56
            reg.get(&sym)
7✔
57
                .map(|q| q.bind(py).borrow().clone())
7✔
58
                .ok_or_else(|| {
7✔
NEW
59
                    pyo3::exceptions::PyValueError::new_err(format!("Unknown unit: {}", sym))
×
NEW
60
                })?
×
61
        };
62
        let (q_mag, q_dim) = if let Some(e) = exp {
7✔
63
            (q.magnitude.powf(e), q.dim.scale(e))
5✔
64
        } else {
65
            (q.magnitude, q.dim)
2✔
66
        };
67
        if op_char == '/' {
7✔
68
            magnitude /= q_mag;
1✔
69
            dim = dim.sub(&q_dim);
1✔
70
        } else {
6✔
71
            magnitude *= q_mag;
6✔
72
            dim = dim.add(&q_dim);
6✔
73
        }
6✔
74
        skip_ws(&mut iter);
7✔
75
    }
76
    Ok(Quantity::new(magnitude, dim).into_pyobject(py)?.into_any())
3✔
77
}
3✔
78

79
fn skip_ws(iter: &mut std::iter::Peekable<std::str::Chars<'_>>) {
11✔
80
    while let Some(&c) = iter.peek() {
21✔
81
        if c.is_whitespace() {
18✔
82
            iter.next();
10✔
83
        } else {
10✔
84
            break;
8✔
85
        }
86
    }
87
}
11✔
88

89
fn read_term(
7✔
90
    iter: &mut std::iter::Peekable<std::str::Chars<'_>>,
7✔
91
) -> PyResult<(String, Option<f64>)> {
7✔
92
    let mut sym = String::new();
7✔
93
    while let Some(&c) = iter.peek() {
15✔
94
        if c.is_ascii_alphabetic() || c == '_' || (sym.len() > 0 && c.is_ascii_digit()) {
15✔
95
            sym.push(c);
8✔
96
            iter.next();
8✔
97
        } else {
8✔
98
            break;
7✔
99
        }
100
    }
101
    if sym.is_empty() {
7✔
NEW
102
        return Err(pyo3::exceptions::PyValueError::new_err(
×
NEW
103
            "Expected unit symbol",
×
NEW
104
        ));
×
105
    }
7✔
106
    let exp = if iter.peek() == Some(&'^') {
7✔
107
        iter.next();
5✔
108
        let mut buf = String::new();
5✔
109
        if matches!(iter.peek(), Some('+') | Some('-')) {
5✔
110
            buf.push(iter.next().unwrap());
2✔
111
        }
3✔
112
        while let Some(&c) = iter.peek() {
12✔
113
            if c.is_ascii_digit() || c == '.' {
9✔
114
                buf.push(c);
7✔
115
                iter.next();
7✔
116
            } else {
7✔
117
                break;
2✔
118
            }
119
        }
120
        Some(buf.parse::<f64>().map_err(|_| {
5✔
NEW
121
            pyo3::exceptions::PyValueError::new_err(format!("Bad exponent: {:?}", buf))
×
NEW
122
        })?)
×
123
    } else {
124
        None
2✔
125
    };
126
    Ok((sym, exp))
7✔
127
}
7✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc