From b10fa984f36786466476bc325c310f21b8c662d1 Mon Sep 17 00:00:00 2001
From: jD91mZM2 <me@krake.one>
Date: Thu, 9 Aug 2018 16:34:28 +0200
Subject: [PATCH] Implement strtod

---
 src/stdlib/src/lib.rs               | 54 ++++++++++++++++++++++++-----
 tests/Makefile                      |  1 +
 tests/expected/stdio/all.stdout     |  1 +
 tests/expected/stdio/setvbuf.stderr |  0
 tests/expected/stdio/setvbuf.stdout |  6 ++++
 tests/expected/stdlib/strtod.stderr |  0
 tests/expected/stdlib/strtod.stdout |  9 +++++
 tests/stdlib/strtod.c               | 17 +++++++++
 8 files changed, 79 insertions(+), 9 deletions(-)
 create mode 100644 tests/expected/stdio/setvbuf.stderr
 create mode 100644 tests/expected/stdio/setvbuf.stdout
 create mode 100644 tests/expected/stdlib/strtod.stderr
 create mode 100644 tests/expected/stdlib/strtod.stdout
 create mode 100644 tests/stdlib/strtod.c

diff --git a/src/stdlib/src/lib.rs b/src/stdlib/src/lib.rs
index d60ffc8a..9a9cfe8e 100644
--- a/src/stdlib/src/lib.rs
+++ b/src/stdlib/src/lib.rs
@@ -635,19 +635,55 @@ pub extern "C" fn srandom(seed: c_uint) {
 }
 
 #[no_mangle]
-pub unsafe extern "C" fn strtod(s: *const c_char, endptr: *mut *mut c_char) -> c_double {
-    // TODO: endptr
+pub unsafe extern "C" fn strtod(mut s: *const c_char, endptr: *mut *mut c_char) -> c_double {
+    while ctype::isspace(*s as c_int) != 0 {
+        s = s.offset(1);
+    }
 
-    use core::str::FromStr;
+    let mut result = 0.0;
+    let mut radix = 10;
 
-    let s_str = str::from_utf8_unchecked(platform::c_str(s));
-    match f64::from_str(s_str) {
-        Ok(ok) => ok as c_double,
-        Err(_err) => {
-            platform::errno = EINVAL;
-            0.0
+    let negative = match *s as u8 {
+        b'-' => { s = s.offset(1); true },
+        b'+' => { s = s.offset(1); false },
+        _ => false
+    };
+
+    if *s as u8 == b'0' && *s.offset(1) as u8 == b'x' {
+        s = s.offset(2);
+        radix = 16;
+    }
+
+    while let Some(digit) = (*s as u8 as char).to_digit(radix) {
+        result *= radix as c_double;
+        result += digit as c_double;
+        s = s.offset(1);
+    }
+
+    if *s as u8 == b'.' {
+        s = s.offset(1);
+
+        let mut i = 1.0;
+        while let Some(digit) = (*s as u8 as char).to_digit(radix) {
+            i *= radix as c_double;
+            result += digit as c_double / i;
+            s = s.offset(1);
         }
     }
+
+    if !endptr.is_null() {
+        // This is stupid, but apparently strto* functions want
+        // const input but mut output, yet the man page says
+        // "stores the address of the first invalid character in *endptr"
+        // so obviously it doesn't want us to clone it.
+        *endptr = s as *mut _;
+    }
+
+    if negative {
+        -result
+    } else {
+        result
+    }
 }
 
 pub fn is_positive(ch: c_char) -> Option<(bool, isize)> {
diff --git a/tests/Makefile b/tests/Makefile
index b6eacd63..33fd0564 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -28,6 +28,7 @@ EXPECT_BINS=\
 	stdlib/env \
 	stdlib/mkostemps \
 	stdlib/rand \
+	stdlib/strtod \
 	stdlib/strtol \
 	stdlib/strtoul \
 	stdlib/system \
diff --git a/tests/expected/stdio/all.stdout b/tests/expected/stdio/all.stdout
index 23bfb625..ebda98d0 100644
--- a/tests/expected/stdio/all.stdout
+++ b/tests/expected/stdio/all.stdout
@@ -1,3 +1,4 @@
 H
 Hello World!
 
+Hello
diff --git a/tests/expected/stdio/setvbuf.stderr b/tests/expected/stdio/setvbuf.stderr
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/expected/stdio/setvbuf.stdout b/tests/expected/stdio/setvbuf.stdout
new file mode 100644
index 00000000..7f04b787
--- /dev/null
+++ b/tests/expected/stdio/setvbuf.stdout
@@ -0,0 +1,6 @@
+H
+ello World!
+
+Line 2
+
+Hello
diff --git a/tests/expected/stdlib/strtod.stderr b/tests/expected/stdlib/strtod.stderr
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/expected/stdlib/strtod.stdout b/tests/expected/stdlib/strtod.stdout
new file mode 100644
index 00000000..c3013b85
--- /dev/null
+++ b/tests/expected/stdlib/strtod.stdout
@@ -0,0 +1,9 @@
+d: 0 Endptr: "a 1 hello"
+d: 1 Endptr: " hello"
+d: 1 Endptr: " hello 2"
+d: 10.123 Endptr: ""
+d: 10.123 Endptr: ""
+d: -5.3 Endptr: ""
+d: 16.071044921875 Endptr: ""
+d: 1.13671875 Endptr: ""
+d: 3.12890625 Endptr: ""
diff --git a/tests/stdlib/strtod.c b/tests/stdlib/strtod.c
new file mode 100644
index 00000000..21a72805
--- /dev/null
+++ b/tests/stdlib/strtod.c
@@ -0,0 +1,17 @@
+#include <stdlib.h>
+#include <stdio.h>
+
+int main() {
+    char* endptr = 0;
+    double d;
+
+    char* inputs[] = {
+        "a 1 hello", " 1 hello", "1 hello 2",
+        "10.123", "010.123", "-5.3",
+        "0x10.123", "0x1.23", "0x3.21"
+    };
+    for (int i = 0; i < sizeof(inputs) / sizeof(char*); i += 1) {
+        d = strtod(inputs[i], &endptr);
+        printf("d: %f Endptr: \"%s\"\n", d, endptr);
+    }
+}
-- 
GitLab