Compare commits

...
Author SHA1 Message Date
262c55b91a
feat: add back single ampersand 2026-07-24 21:57:30 -07:00
704ef81816
feat: ignore and add back single equal sign
more than one is likely a user mistake and a panic still makes sense

should also add back '&' in the same way, if applicable
2026-07-24 21:50:58 -07:00
0dc140909f
docs: reflow comments 2026-07-24 14:23:28 -07:00
4dcbc94e0f
style: use Self where possible 2026-07-24 11:38:51 -07:00
442dc5fd91
fix: 1e-1 edge case
the "wrap up any unfinished parsing" section doesn't need to be changed,
since this edge case is only valid when there is a number after the '-'
which there never is in the case of only one extra character

in other words, 1e- is not valid:
`NumberParsing("1e-", ParseFloatError { kind: ExpNoDigits })`
2026-07-24 11:26:56 -07:00
431ea5b67e
feat: recognize dedicated minus sign
> As per Unicode 17.0.0, the ASCII hyphen-minus is not a mathematical symbol. To express the minus sign in math, U+2212 − MINUS SIGN is used instead.

https://en.wikipedia.org/wiki/Mathematical_operators_and_symbols_in_Unicode#cite_ref-7
2026-07-21 14:23:37 -07:00
3 changed files with 54 additions and 7 deletions

View file

@ -18,9 +18,47 @@ fn main() {
}
};
println!("{}", parse_and_calc_humanreadable(input));
}
pub fn parse_and_calc_humanreadable(mut input: String) -> String {
let should_add_back_equals = if let Some((i, _)) = input
.chars()
.enumerate()
.filter(|(_, c)| !matches!(c, ' ' | '&' | '\n'))
.find(|(_, c)| *c == '=')
{
input.remove(i);
true
} else {
false
};
// no need to remove the character here,
// as we ignore the ampersand in all other logic already
let should_add_back_alignment = input.contains('&');
let result = parse_and_calc(&input);
println!("{result:.0$}", get_significant_digits(&result));
format!(
"{0}{result:.1$}",
if should_add_back_equals && should_add_back_alignment {
"& = "
} else if should_add_back_equals {
"= "
} else if should_add_back_alignment {
"& "
} else {
""
},
get_significant_digits(&result),
)
}
#[test]
fn test_equals_readdition() {
assert_eq!(parse_and_calc_humanreadable(" = 11".into()), "= 11");
assert_eq!(parse_and_calc_humanreadable(" &= 11".into()), "& = 11");
assert_eq!(parse_and_calc_humanreadable(" & 11".into()), "& 11");
}
pub fn parse_and_calc(s: &str) -> Float {

View file

@ -22,7 +22,7 @@ impl TryFrom<char> for Operator {
fn try_from(value: char) -> Result<Self, Self::Error> {
match value {
'+' => Ok(Self::Plus),
'-' => Ok(Self::Minus),
'-' | '' => Ok(Self::Minus),
'*' | '×' => Ok(Self::Mult),
'/' | '÷' => Ok(Self::Div),
'^' => Ok(Self::Exp),
@ -79,11 +79,12 @@ impl FromStr for Nodes {
}
/// A function to check if the given `char` should be used by the parser.
///
/// We ignore whitespace since it has no semantic significance in typst aside from variables,
/// (which are not implemented)
/// We ignore whitespace since it has no semantic significance in typst
/// (aside from variables, which are not implemented)
/// and `&` because it is only used for visual alignment.
///
/// Newlines are also ignored because they're likely accidental; like in the case of `echo 1+1 | tylc`.
/// Newlines are also ignored because they're likely accidental;
/// like in the case of `echo 1+1 | tylc`.
fn is_not_ignored_char(c: &char) -> bool {
!matches!(c, ' ' | '&' | '\n')
}
@ -112,7 +113,10 @@ impl FromStr for Nodes {
}
}
ParsingState::ReadingNumber(start_index) => {
if !is_number_char(c) {
if !is_number_char(c)
// don't stop reading number on a special case, eg "1e-1"
&& !(filtered_string.chars().nth(i - 1) == Some('e') && c == '-')
{
nodes.push(Node::Number(
Float::parse(&filtered_string[start_index..i])
.map_err(|e| {
@ -181,6 +185,6 @@ impl FromStr for Nodes {
}
}
Ok(Nodes(nodes))
Ok(Self(nodes))
}
}

View file

@ -5,6 +5,11 @@ use rug::Float;
use crate::nodes::{Function, Node, Nodes, Operator, PREC};
#[test]
fn e_edge_case() {
assert_eq!(Nodes::from_str("1e-1").unwrap().evaluate().to_f64(), 0.1)
}
#[test]
fn implicit_mult() {
assert_eq!(