Skip to content

Commit 9025ecb

Browse files
committed
Better error spans in calc
1 parent e11bd2a commit 9025ecb

File tree

3 files changed

+92
-88
lines changed

3 files changed

+92
-88
lines changed

library/src/compute/calc.rs

Lines changed: 55 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -94,28 +94,27 @@ pub fn pow(
9494
/// The exponent of the power. Must be non-negative.
9595
exponent: Spanned<Num>,
9696
) -> Value {
97-
let Spanned { v: exp, span } = exponent;
98-
match exp {
99-
_ if exp.float() == 0.0 && base.float() == 0.0 => {
97+
match exponent.v {
98+
_ if exponent.v.float() == 0.0 && base.float() == 0.0 => {
10099
bail!(args.span, "zero to the power of zero is undefined")
101100
}
102101
Num::Int(i) if i32::try_from(i).is_err() => {
103-
bail!(span, "exponent is too large")
102+
bail!(exponent.span, "exponent is too large")
104103
}
105104
Num::Float(f) if !f.is_normal() && f != 0.0 => {
106-
bail!(span, "exponent may not be infinite, subnormal, or NaN")
105+
bail!(exponent.span, "exponent may not be infinite, subnormal, or NaN")
107106
}
108107
_ => {}
109108
};
110109

111-
let result = match (base, exp) {
110+
let result = match (base, exponent.v) {
112111
(Num::Int(a), Num::Int(b)) if b >= 0 => Num::Int(a.pow(b as u32)),
113112
(a, Num::Int(b)) => Num::Float(a.float().powi(b as i32)),
114113
(a, b) => Num::Float(a.float().powf(b.float())),
115114
};
116115

117116
if result.float().is_nan() {
118-
bail!(span, "the result is not a real number")
117+
bail!(args.span, "the result is not a real number")
119118
}
120119

121120
result.value()
@@ -381,28 +380,28 @@ pub fn log(
381380
value: Spanned<Num>,
382381
/// The base of the logarithm. Defaults to `{10}` and may not be zero.
383382
#[named]
384-
#[default(10.0)]
385-
base: f64,
383+
#[default(Spanned::new(10.0, Span::detached()))]
384+
base: Spanned<f64>,
386385
) -> Value {
387386
let number = value.v.float();
388387
if number <= 0.0 {
389388
bail!(value.span, "value must be strictly positive")
390389
}
391390

392-
if !base.is_normal() {
393-
bail!(value.span, "base may not be zero, NaN, infinite, or subnormal")
391+
if !base.v.is_normal() {
392+
bail!(base.span, "base may not be zero, NaN, infinite, or subnormal")
394393
}
395394

396-
let result = if base == 2.0 {
395+
let result = if base.v == 2.0 {
397396
number.log2()
398-
} else if base == 10.0 {
397+
} else if base.v == 10.0 {
399398
number.log10()
400399
} else {
401-
number.log(base)
400+
number.log(base.v)
402401
};
403402

404403
if result.is_infinite() || result.is_nan() {
405-
bail!(value.span, "the result is not a real number")
404+
bail!(args.span, "the result is not a real number")
406405
}
407406

408407
Value::Float(result)
@@ -420,114 +419,103 @@ pub fn log(
420419
/// Returns: integer
421420
#[func]
422421
pub fn fact(
423-
/// The number whose factorial to calculate. Must be positive.
424-
number: Spanned<u64>,
422+
/// The number whose factorial to calculate. Must be non-negative.
423+
number: u64,
425424
) -> Value {
426-
let result = factorial_range(1, number.v).and_then(|r| i64::try_from(r).ok());
427-
428-
match result {
429-
None => bail!(number.span, "the factorial result is too large"),
430-
Some(s) => Value::Int(s),
431-
}
425+
factorial_range(1, number)
426+
.map(Value::Int)
427+
.ok_or("the result is too large")
428+
.at(args.span)?
432429
}
433430

434-
/// Calculates the product of a range of numbers. Used to calculate permutations.
435-
/// Returns None if the result is larger than `u64::MAX`
436-
fn factorial_range(start: u64, end: u64) -> Option<u64> {
431+
/// Calculates the product of a range of numbers. Used to calculate
432+
/// permutations. Returns None if the result is larger than `i64::MAX`
433+
fn factorial_range(start: u64, end: u64) -> Option<i64> {
437434
// By convention
438435
if end + 1 < start {
439436
return Some(0);
440437
}
441438

442-
let mut count: u64 = 1;
443439
let real_start: u64 = cmp::max(1, start);
444-
440+
let mut count: u64 = 1;
445441
for i in real_start..=end {
446442
count = count.checked_mul(i)?;
447443
}
448-
Some(count)
444+
445+
i64::try_from(count).ok()
449446
}
450447

451448
/// Calculate a permutation.
452449
///
453450
/// ## Example
454451
/// ```example
455-
/// #calc.perm(10,5)
452+
/// #calc.perm(10, 5)
456453
/// ```
457454
///
458455
/// Display: Permutation
459456
/// Category: calculate
460457
/// Returns: integer
461458
#[func]
462459
pub fn perm(
463-
/// The base number. Must be positive.
464-
base: Spanned<u64>,
465-
/// The number of permutations. Must be positive.
466-
numbers: Spanned<u64>,
460+
/// The base number. Must be non-negative.
461+
base: u64,
462+
/// The number of permutations. Must be non-negative.
463+
numbers: u64,
467464
) -> Value {
468-
let base_parsed = base.v;
469-
let numbers_parsed = numbers.v;
470-
471-
let result = if base_parsed + 1 > numbers_parsed {
472-
factorial_range(base_parsed - numbers_parsed + 1, base_parsed)
473-
.and_then(|value| i64::try_from(value).ok())
474-
} else {
475-
// By convention
476-
Some(0)
477-
};
478-
479-
match result {
480-
None => bail!(base.span, "the permutation result is too large"),
481-
Some(s) => Value::Int(s),
465+
// By convention.
466+
if base + 1 <= numbers {
467+
return Ok(Value::Int(0));
482468
}
469+
470+
factorial_range(base - numbers + 1, base)
471+
.map(Value::Int)
472+
.ok_or("the result is too large")
473+
.at(args.span)?
483474
}
484475

485476
/// Calculate a binomial coefficient.
486477
///
487478
/// ## Example
488479
/// ```example
489-
/// #calc.binom(10,5)
480+
/// #calc.binom(10, 5)
490481
/// ```
491482
///
492-
/// Display: Permutation
483+
/// Display: Binomial
493484
/// Category: calculate
494485
/// Returns: integer
495486
#[func]
496487
pub fn binom(
497-
/// The upper coefficient. Must be positive
498-
n: Spanned<u64>,
499-
/// The lower coefficient. Must be positive.
500-
k: Spanned<u64>,
488+
/// The upper coefficient. Must be non-negative.
489+
n: u64,
490+
/// The lower coefficient. Must be non-negative.
491+
k: u64,
501492
) -> Value {
502-
let result = binomial(n.v, k.v).and_then(|raw| i64::try_from(raw).ok());
503-
504-
match result {
505-
None => bail!(n.span, "the binomial result is too large"),
506-
Some(r) => Value::Int(r),
507-
}
493+
binomial(n, k)
494+
.map(Value::Int)
495+
.ok_or("the result is too large")
496+
.at(args.span)?
508497
}
509498

510-
/// Calculates a binomial coefficient, with `n` the upper coefficient and `k` the lower coefficient.
511-
/// Returns `None` if the result is larger than `u64::MAX`
512-
fn binomial(n: u64, k: u64) -> Option<u64> {
499+
/// Calculates a binomial coefficient, with `n` the upper coefficient and `k`
500+
/// the lower coefficient. Returns `None` if the result is larger than
501+
/// `i64::MAX`
502+
fn binomial(n: u64, k: u64) -> Option<i64> {
513503
if k > n {
514504
return Some(0);
515505
}
516506

517507
// By symmetry
518508
let real_k = cmp::min(n - k, k);
519-
520509
if real_k == 0 {
521510
return Some(1);
522511
}
523512

524513
let mut result: u64 = 1;
525-
526514
for i in 0..real_k {
527-
result = result.checked_mul(n - i).and_then(|r| r.checked_div(i + 1))?;
515+
result = result.checked_mul(n - i)?.checked_div(i + 1)?;
528516
}
529517

530-
Some(result)
518+
i64::try_from(result).ok()
531519
}
532520

533521
/// Round a number down to the nearest integer.

src/eval/cast.rs

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
pub use typst_macros::{cast_from_value, cast_to_value, Cast};
22

3-
use std::num::{NonZeroI64, NonZeroUsize};
3+
use std::num::{NonZeroI64, NonZeroU64, NonZeroUsize};
44
use std::ops::Add;
55

66
use ecow::EcoString;
@@ -95,12 +95,8 @@ cast_to_value! {
9595
v: u32 => Value::Int(v as i64)
9696
}
9797

98-
cast_to_value! {
99-
v: i32 => Value::Int(v as i64)
100-
}
101-
10298
cast_from_value! {
103-
usize,
99+
u64,
104100
int: i64 => int.try_into().map_err(|_| {
105101
if int < 0 {
106102
"number must be at least zero"
@@ -111,11 +107,11 @@ cast_from_value! {
111107
}
112108

113109
cast_to_value! {
114-
v: usize => Value::Int(v as i64)
110+
v: u64 => Value::Int(v as i64)
115111
}
116112

117113
cast_from_value! {
118-
u64,
114+
usize,
119115
int: i64 => int.try_into().map_err(|_| {
120116
if int < 0 {
121117
"number must be at least zero"
@@ -126,14 +122,32 @@ cast_from_value! {
126122
}
127123

128124
cast_to_value! {
129-
v: u64 => Value::Int(v as i64)
125+
v: usize => Value::Int(v as i64)
126+
}
127+
128+
cast_to_value! {
129+
v: i32 => Value::Int(v as i64)
130130
}
131131

132132
cast_from_value! {
133-
NonZeroUsize,
133+
NonZeroI64,
134+
int: i64 => int.try_into()
135+
.map_err(|_| if int == 0 {
136+
"number must not be zero"
137+
} else {
138+
"number too large"
139+
})?,
140+
}
141+
142+
cast_to_value! {
143+
v: NonZeroI64 => Value::Int(v.get())
144+
}
145+
146+
cast_from_value! {
147+
NonZeroU64,
134148
int: i64 => int
135149
.try_into()
136-
.and_then(|int: usize| int.try_into())
150+
.and_then(|int: u64| int.try_into())
137151
.map_err(|_| if int <= 0 {
138152
"number must be positive"
139153
} else {
@@ -142,12 +156,14 @@ cast_from_value! {
142156
}
143157

144158
cast_to_value! {
145-
v: NonZeroUsize => Value::Int(v.get() as i64)
159+
v: NonZeroU64 => Value::Int(v.get() as i64)
146160
}
147161

148162
cast_from_value! {
149-
NonZeroI64,
150-
int: i64 => int.try_into()
163+
NonZeroUsize,
164+
int: i64 => int
165+
.try_into()
166+
.and_then(|int: usize| int.try_into())
151167
.map_err(|_| if int <= 0 {
152168
"number must be positive"
153169
} else {
@@ -156,7 +172,7 @@ cast_from_value! {
156172
}
157173

158174
cast_to_value! {
159-
v: NonZeroI64 => Value::Int(v.get())
175+
v: NonZeroUsize => Value::Int(v.get() as i64)
160176
}
161177

162178
cast_from_value! {

tests/typ/compute/calc.typ

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
#calc.pow(2, calc.pow(2.0, 10000.0))
9696

9797
---
98-
// Error: 15-18 the result is not a real number
98+
// Error: 10-19 the result is not a real number
9999
#calc.pow(-1, 0.5)
100100

101101
---
@@ -107,11 +107,11 @@
107107
#calc.log(-1)
108108

109109
---
110-
// Error: 11-12 base may not be zero, NaN, infinite, or subnormal
110+
// Error: 20-21 base may not be zero, NaN, infinite, or subnormal
111111
#calc.log(1, base: 0)
112112

113113
---
114-
// Error: 11-13 the result is not a real number
114+
// Error: 10-24 the result is not a real number
115115
#calc.log(10, base: -1)
116116

117117
---
@@ -120,7 +120,7 @@
120120
#test(calc.fact(5), 120)
121121

122122
---
123-
// Error: 12-14 the factorial result is too large
123+
// Error: 11-15 the result is too large
124124
#calc.fact(21)
125125

126126
---
@@ -131,7 +131,7 @@
131131
#test(calc.perm(5, 6), 0)
132132

133133
---
134-
// Error: 12-14 the permutation result is too large
134+
// Error: 11-19 the result is too large
135135
#calc.perm(21, 21)
136136

137137
---
@@ -175,5 +175,5 @@
175175
#range(4, step: "one")
176176

177177
---
178-
// Error: 18-19 number must be positive
178+
// Error: 18-19 number must not be zero
179179
#range(10, step: 0)

0 commit comments

Comments
 (0)