Parcourir la source

Implement fnmatch.h

jD91mZM2 il y a 6 ans
Parent
commit
b20307dca0

+ 9 - 0
Cargo.lock

@@ -141,6 +141,14 @@ dependencies = [
  "platform 0.1.0",
 ]
 
+[[package]]
+name = "fnmatch"
+version = "0.1.0"
+dependencies = [
+ "cbindgen 0.5.2",
+ "platform 0.1.0",
+]
+
 [[package]]
 name = "fuchsia-zircon"
 version = "0.3.3"
@@ -329,6 +337,7 @@ dependencies = [
  "fcntl 0.1.0",
  "fenv 0.1.0",
  "float 0.1.0",
+ "fnmatch 0.1.0",
  "grp 0.1.0",
  "inttypes 0.1.0",
  "locale 0.1.0",

+ 1 - 0
Cargo.toml

@@ -21,6 +21,7 @@ errno = { path = "src/errno" }
 fcntl = { path = "src/fcntl" }
 fenv = { path = "src/fenv" }
 float = { path = "src/float" }
+fnmatch = { path = "src/fnmatch" }
 grp = { path = "src/grp" }
 inttypes = { path = "src/inttypes" }
 locale = { path = "src/locale" }

+ 10 - 0
src/fnmatch/Cargo.toml

@@ -0,0 +1,10 @@
+[package]
+name = "fnmatch"
+version = "0.1.0"
+authors = ["jD91mZM2 <[email protected]>"]
+
+[build-dependencies]
+cbindgen = { path = "../../cbindgen" }
+
+[dependencies]
+platform = { path = "../platform" }

+ 11 - 0
src/fnmatch/build.rs

@@ -0,0 +1,11 @@
+extern crate cbindgen;
+
+use std::{env, fs};
+
+fn main() {
+    let crate_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
+    fs::create_dir_all("../../target/include").expect("failed to create include directory");
+    cbindgen::generate(crate_dir)
+      .expect("failed to generate bindings")
+      .write_to_file("../../target/include/fnmatch.h");
+}

+ 6 - 0
src/fnmatch/cbindgen.toml

@@ -0,0 +1,6 @@
+include_guard = "_FNMATCH_H"
+language = "C"
+style = "Tag"
+
+[enum]
+prefix_with_name = true

+ 166 - 0
src/fnmatch/src/lib.rs

@@ -0,0 +1,166 @@
+//! fnmatch implementation
+#![no_std]
+#![feature(alloc)]
+
+extern crate alloc;
+extern crate platform;
+
+use alloc::vec::Vec;
+use platform::types::*;
+
+pub const FNM_NOMATCH: c_int = 1;
+
+pub const FNM_NOESCAPE: c_int = 1;
+pub const FNM_PATHNAME: c_int = 2;
+pub const FNM_PERIOD:   c_int = 4;
+pub const FNM_CASEFOLD: c_int = 8;
+
+#[derive(Debug)]
+enum Token {
+    Any,
+    Char(u8),
+    Match(bool, Vec<u8>),
+    Wildcard,
+    // TODO: FNM_EXTMATCH, which is basically a whole another custom regex
+    // format that's ambigious and ugh. The C standard library is really bloaty
+    // and I sure hope we can get away with delaying this as long as possible.
+    // If you need to implement this, you can contact jD91mZM2 for assistance
+    // in reading this code in case it's ugly.
+}
+
+unsafe fn next_token(pattern: &mut *const c_char, flags: c_int) -> Option<Token> {
+    let c = **pattern as u8;
+    if c == 0 {
+        return None;
+    }
+    *pattern = pattern.offset(1);
+    Some(match c {
+        b'\\' if flags & FNM_NOESCAPE == FNM_NOESCAPE => {
+            let c = **pattern as u8;
+            if c == 0 {
+                // Trailing backslash. Maybe error here?
+                return None;
+            }
+            *pattern = pattern.offset(1);
+            Token::Char(c)
+        },
+        b'?' => Token::Any,
+        b'*' => Token::Wildcard,
+        b'[' => {
+            let mut matches = Vec::new();
+            let invert = if **pattern as u8 == b'!' {
+                *pattern = pattern.offset(1);
+                true
+            } else { false };
+
+            loop {
+                let mut c = **pattern as u8;
+                if c == 0 {
+                    break;
+                }
+                *pattern = pattern.offset(1);
+                match c {
+                    b']' => break,
+                    b'\\' => {
+                        c = **pattern as u8;
+                        *pattern = pattern.offset(1);
+                        if c == 0 {
+                            // Trailing backslash. Maybe error?
+                            break;
+                        }
+                    },
+                    _ => ()
+                }
+                if matches.len() >= 2 && matches[matches.len() - 1] == b'-' {
+                    let len = matches.len();
+                    let start = matches[len - 2];
+                    matches.drain(len - 2..);
+                    // Exclusive range because we'll push C later
+                    for c in start..c {
+                        matches.push(c);
+                    }
+                }
+                matches.push(c);
+            }
+            // Otherwise, there was no closing ]. Maybe error?
+
+            Token::Match(invert, matches)
+        },
+        c => Token::Char(c)
+    })
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fnmatch(mut pattern: *const c_char, mut input: *const c_char, flags: c_int) -> c_int {
+    let pathname = flags & FNM_PATHNAME == FNM_PATHNAME;
+    let casefold = flags & FNM_CASEFOLD == FNM_CASEFOLD;
+
+    let mut leading = true;
+
+    loop {
+        if *input == 0 {
+            return if *pattern == 0 { 0 } else { FNM_NOMATCH };
+        }
+        if leading && flags & FNM_PERIOD == FNM_PERIOD {
+            if *input as u8 == b'.' && *pattern as u8 != b'.' {
+                return FNM_NOMATCH;
+            }
+        }
+        leading = false;
+        match next_token(&mut pattern, flags) {
+            Some(Token::Any) => {
+                if pathname && *input as u8 == b'/' {
+                    return FNM_NOMATCH;
+                }
+                input = input.offset(1);
+            },
+            Some(Token::Char(c)) => {
+                let mut a = *input as u8;
+                if casefold && a >= b'a' && a <= b'z' {
+                    a -= b'a' - b'A';
+                }
+                let mut b = c;
+                if casefold && b >= b'a' && b <= b'z' {
+                    b -= b'a' - b'A';
+                }
+                if a != b {
+                    return FNM_NOMATCH;
+                }
+                if pathname && a == b'/' {
+                    leading = true;
+                }
+                input = input.offset(1);
+            },
+            Some(Token::Match(invert, matches)) => {
+                if (pathname && *input as u8 == b'/') || matches.contains(&(*input as u8)) == invert {
+                    // Found it, but it's inverted! Or vise versa.
+                    return FNM_NOMATCH;
+                }
+                input = input.offset(1);
+            },
+            Some(Token::Wildcard) => {
+                loop {
+                    let c = *input as u8;
+                    if c == 0 {
+                        return if *pattern == 0 { 0 } else { FNM_NOMATCH };
+                    }
+
+                    let ret = fnmatch(pattern, input, flags);
+                    if ret == FNM_NOMATCH {
+                        input = input.offset(1);
+                    } else {
+                        // Either an error or a match. Forward the return.
+                        return ret;
+                    }
+
+                    if pathname && c == b'/' {
+                        // End of segment, no match yet
+                        return FNM_NOMATCH;
+                    }
+                }
+                unreachable!("nothing should be able to break out of the loop");
+            },
+            None => return FNM_NOMATCH // Pattern ended but there's still some input
+        }
+    }
+}

+ 1 - 0
src/lib.rs

@@ -13,6 +13,7 @@ pub extern crate errno;
 pub extern crate fcntl;
 pub extern crate fenv;
 pub extern crate float;
+pub extern crate fnmatch;
 pub extern crate grp;
 pub extern crate locale;
 pub extern crate netinet;

+ 0 - 1
src/stdio/src/scanf.rs

@@ -2,7 +2,6 @@ use alloc::String;
 use alloc::Vec;
 use platform::types::*;
 use platform::Read;
-#[cfg(not(test))]
 use vl::VaList;
 
 #[derive(PartialEq, Eq)]

+ 1 - 0
src/sys_times/cbindgen.toml

@@ -1,3 +1,4 @@
+sys_includes = ["sys/types.h"]
 include_guard = "_SYS_TIMES_H"
 language = "C"
 style = "Tag"

+ 12 - 4
src/time/src/strftime.rs

@@ -24,11 +24,15 @@ pub unsafe fn strftime<W: Write>(
         }};
         (byte $b:expr) => {{
             w!(reserve 1);
-            w.write_u8($b);
+            if w.write_u8($b).is_err() {
+                return !0;
+            }
         }};
         (char $chr:expr) => {{
             w!(reserve $chr.len_utf8());
-            w.write_char($chr);
+            if w.write_char($chr).is_err() {
+                return !0;
+            }
         }};
         (recurse $fmt:expr) => {{
             let mut fmt = String::with_capacity($fmt.len() + 1);
@@ -44,7 +48,9 @@ pub unsafe fn strftime<W: Write>(
         }};
         ($str:expr) => {{
             w!(reserve $str.len());
-            w.write_str($str);
+            if w.write_str($str).is_err() {
+                return !0;
+            }
         }};
         ($fmt:expr, $($args:expr),+) => {{
             // Would use write!() if I could get the length written
@@ -140,7 +146,9 @@ pub unsafe fn strftime<W: Write>(
     }
     if toplevel {
         // nul byte is already counted in written
-        w.write_u8(0);
+        if w.write_u8(0).is_err() {
+            return !0;
+        }
     }
     written
 }

+ 1 - 0
tests/Makefile

@@ -7,6 +7,7 @@ EXPECT_BINS=\
 	error \
 	fcntl/create \
 	fcntl/fcntl \
+	fnmatch \
 	locale \
 	math \
 	select \

+ 0 - 0
tests/expected/fnmatch.stderr


+ 31 - 0
tests/expected/fnmatch.stdout

@@ -0,0 +1,31 @@
+Should succeed:
+"*World" matches "Hello World"
+"*World" matches "World"
+"Hello*" matches "Hello World"
+"H[ae]llo?World" matches "Hallo+World"
+"H[ae]llo?World" matches "Hello_World"
+"[0-9][!a]" matches "1b"
+"/a/*/d" matches "/a/b/c/d"
+"/a/*/d" matches "/a/bc/d"
+"*hello" matches ".hello"
+"/*hello" matches "/.hello"
+"[a!][a!]" matches "!a"
+"[\]]" matches "]"
+"[\\]" matches "\"
+"hello[/+]world" matches "hello/world"
+"hello world" matches "HELLO WORLD"
+
+Should fail:
+"*World" doesn't match "Hello Potato"
+"*World" doesn't match "Potato"
+"H[ae]llo?World" doesn't match "Hillo+World"
+"H[ae]llo?World" doesn't match "Hello__World"
+"[0-9][!a]" doesn't match "ab"
+"[0-9][!a]" doesn't match "2a"
+"/a/*/d" doesn't match "/a/b/c/d"
+"/a/*/d" doesn't match "/a/bc/d/"
+"*hello" doesn't match ".hello"
+"/*hello" doesn't match "/.hello"
+"[a!][a!]" doesn't match "ab"
+"hello[/+]world" doesn't match "hello/world"
+"hello world" doesn't match "HELLO WORLD"

+ 45 - 0
tests/fnmatch.c

@@ -0,0 +1,45 @@
+#include <fnmatch.h>
+#include <stdio.h>
+
+void test(char* pattern, char* input, int flags) {
+    if (!fnmatch(pattern, input, flags)) {
+        printf("\"%s\" matches \"%s\"\n", pattern, input);
+    } else {
+        printf("\"%s\" doesn't match \"%s\"\n", pattern, input);
+    }
+}
+
+int main() {
+    puts("Should succeed:");
+    test("*World", "Hello World", 0);
+    test("*World", "World", 0);
+    test("Hello*", "Hello World", 0);
+    test("H[ae]llo?World", "Hallo+World", 0);
+    test("H[ae]llo?World", "Hello_World", 0);
+    test("[0-9][!a]", "1b", 0);
+    test("/a/*/d", "/a/b/c/d", 0);
+    test("/a/*/d", "/a/bc/d", FNM_PATHNAME);
+    test("*hello", ".hello", 0);
+    test("/*hello", "/.hello", FNM_PERIOD);
+    test("[a!][a!]", "!a", 0);
+    test("[\\]]", "]", 0);
+    test("[\\\\]", "\\", 0);
+    test("hello[/+]world", "hello/world", 0);
+    test("hello world", "HELLO WORLD", FNM_CASEFOLD);
+
+    puts("");
+    puts("Should fail:");
+    test("*World", "Hello Potato", 0);
+    test("*World", "Potato", 0);
+    test("H[ae]llo?World", "Hillo+World", 0);
+    test("H[ae]llo?World", "Hello__World", 0);
+    test("[0-9][!a]", "ab", 0);
+    test("[0-9][!a]", "2a", 0);
+    test("/a/*/d", "/a/b/c/d", FNM_PATHNAME);
+    test("/a/*/d", "/a/bc/d/", FNM_PATHNAME);
+    test("*hello", ".hello", FNM_PERIOD);
+    test("/*hello", "/.hello", FNM_PERIOD | FNM_PATHNAME);
+    test("[a!][a!]", "ab", 0);
+    test("hello[/+]world", "hello/world", FNM_PATHNAME);
+    test("hello world", "HELLO WORLD", 0);
+}

+ 1 - 0
tests/stdio/scanf.c

@@ -1,3 +1,4 @@
+#include <stdarg.h>
 #include <stdio.h>
 
 struct params {