Skip to content

Commit 9930425

Browse files
interpreter: read the leading number in strtonum and in Str values
1 parent 8f678ae commit 9930425

3 files changed

Lines changed: 47 additions & 13 deletions

File tree

‎interpreter/src/builtins.rs‎

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ use parser::{AriadneSpan, BuiltinFunction};
1717

1818
use crate::{
1919
InterpreterError,
20-
vm::{Interpreter, types::Value},
20+
vm::{
21+
Interpreter,
22+
types::{Value, str_to_num},
23+
},
2124
};
2225

2326
use rc_vec::RcVec as Vec;
@@ -242,18 +245,27 @@ fn to_bits(v: &Value) -> u64 {
242245

243246
fn strtonum(v: &Value) -> f64 {
244247
let bytes = value_bytes(v);
245-
let Ok(s) = std::str::from_utf8(&bytes) else {
246-
return 0.;
247-
};
248-
let s = s.trim_start();
249-
if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
250-
return u64::from_str_radix(hex, 16).map_or(0., |n| n as f64);
251-
}
252-
if s.len() > 1 && s.starts_with('0') && s.bytes().all(|b| (b'0'..=b'7').contains(&b)) {
253-
// Leading zero and only octal digits → octal, as in gawk `strtonum`.
254-
return u64::from_str_radix(s, 8).map_or(0., |n| n as f64);
248+
// As in gawk, a hex or octal prefix only counts at the very start; an 8,
249+
// 9, `.` or exponent after the leading 0 makes it a decimal number.
250+
match bytes.as_slice() {
251+
[b'0', b'x' | b'X', rest @ ..] => rest
252+
.iter()
253+
.map_while(|&b| char::from(b).to_digit(16))
254+
.fold(0., |n, d| n * 16. + f64::from(d)),
255+
[b'0', rest @ ..] => {
256+
let octal = rest
257+
.iter()
258+
.take_while(|b| (b'0'..=b'7').contains(*b))
259+
.count();
260+
match rest.get(octal) {
261+
Some(b'8' | b'9' | b'.' | b'e' | b'E') => str_to_num(&bytes),
262+
_ => rest[..octal]
263+
.iter()
264+
.fold(0., |n, &d| n * 8. + f64::from(d - b'0')),
265+
}
266+
}
267+
_ => str_to_num(&bytes),
255268
}
256-
s.parse().unwrap_or(0.)
257269
}
258270

259271
fn value_bytes(v: &Value) -> Vec<u8> {
@@ -278,6 +290,11 @@ mod tests {
278290
f(&mut intrp);
279291
}
280292

293+
#[test]
294+
fn str_value_reads_the_leading_number() {
295+
assert_eq!(Value::new_str(b" 3x").to_num(), 3.);
296+
}
297+
281298
#[test]
282299
fn int_truncates_toward_zero() {
283300
with_intrp(|intrp| {

‎interpreter/src/vm/types.rs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ pub struct AwkNum(f64);
6060
/// are skipped and the longest prefix that is a decimal number counts, so
6161
/// `" 12 "` is 12, `"3x"` is 3 and `"x"` is 0. Like gawk, `inf` and `nan`
6262
/// are only recognized with a sign and nothing else but blanks around them.
63-
fn str_to_num(s: &[u8]) -> f64 {
63+
pub(crate) fn str_to_num(s: &[u8]) -> f64 {
6464
let is_blank = |b: &u8| matches!(b, b' ' | b'\t' | b'\n' | 0x0B | 0x0C | b'\r');
6565
let s = &s[s.iter().take_while(|b| is_blank(b)).count()..];
6666
let digits = |from: usize| {
@@ -334,6 +334,8 @@ impl<'a> AwkValue<'a> {
334334
&Self::Int(AwkInt(n)) => n as f64,
335335
&Self::Bool(AwkBool(b)) => b as usize as f64,
336336
Self::String(s) => str_to_num(s),
337+
Self::Str(s) => str_to_num(s),
338+
Self::StrNum(s) => s.num.0,
337339
_ => 0.,
338340
}
339341
}

‎tests/by-util/test_awk.rs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,3 +1052,18 @@ fn fields_with_blanks_around_numbers_sum() {
10521052
.succeeds()
10531053
.stdout_only("26\n");
10541054
}
1055+
1056+
#[test]
1057+
fn strtonum_reads_the_leading_number() {
1058+
ucmd()
1059+
.arg(
1060+
r#"BEGIN {
1061+
print strtonum("3x"), strtonum(" 12 "), strtonum("-3x"), strtonum(".5x"), strtonum("1e3x")
1062+
print strtonum("0x1Fz"), strtonum("0X1g"), strtonum("0x"), strtonum(" 0x10"), strtonum("+0x10")
1063+
print strtonum("017x"), strtonum("0778"), strtonum("0189"), strtonum("01.5"), strtonum("017e1")
1064+
print strtonum("x"), strtonum(""), strtonum("0"), strtonum("00"), strtonum("0xFFFFFFFF")
1065+
}"#,
1066+
)
1067+
.succeeds()
1068+
.stdout_only("3 12 -3 0.5 1000\n31 1 0 0 0\n15 778 189 1.5 170\n0 0 0 0 4294967295\n");
1069+
}

0 commit comments

Comments
 (0)