From 82da6dd1ef9dbda9ac5e0894aef0a71750f7bdcd Mon Sep 17 00:00:00 2001
From: Michael Aaron Murphy <mmstickman@gmail.com>
Date: Sun, 30 Jul 2017 21:25:04 -0400
Subject: [PATCH] Eliminate PEG Dependency

---
 Cargo.lock                  |  16 ----
 Cargo.toml                  |   1 -
 build.rs                    |   3 -
 examples/builtin_piping.ion |   5 --
 examples/builtin_piping.out |   2 -
 src/parser/grammar.rustpeg  |  86 ------------------
 src/parser/peg.rs           | 173 ++++++++++++++++++++----------------
 7 files changed, 97 insertions(+), 189 deletions(-)
 delete mode 100644 src/parser/grammar.rustpeg

diff --git a/Cargo.lock b/Cargo.lock
index d5003ae2..fb67f68a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -12,7 +12,6 @@ dependencies = [
  "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)",
  "liner 0.4.0 (git+https://github.com/MovingtoMars/liner/)",
  "nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "peg 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "redox_syscall 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -130,24 +129,11 @@ dependencies = [
  "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
-[[package]]
-name = "peg"
-version = "0.5.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
 [[package]]
 name = "permutate"
 version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
-[[package]]
-name = "quote"
-version = "0.3.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
 [[package]]
 name = "redox_syscall"
 version = "0.1.28"
@@ -297,9 +283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4"
 "checksum nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "47e49f6982987135c5e9620ab317623e723bd06738fd85377e8d55f57c8b6487"
 "checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c"
-"checksum peg 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "36a474cba42744afe0f223e9d4263594b3387f172e512259c72d2011e477c4fb"
 "checksum permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53b7d5b19a715ffab38693a9dd44b067fdfa2b18eef65bd93562dfe507022fae"
-"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
 "checksum redox_syscall 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "ddab7acd8e7bf3e49dfdf78ac1209b992329eb2f66e0bf672ab49c70a76d1d68"
 "checksum redox_termios 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc495930de8d330f14856cface52561b7d79a072c76e438cf8f34d7233a35fa7"
 "checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b"
diff --git a/Cargo.toml b/Cargo.toml
index e56116bf..e172c336 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -65,7 +65,6 @@ users = "0.5.1"
 
 [build-dependencies]
 ansi_term = "0.9"
-peg = "0.5"
 version_check = "0.1.3"
 
 [profile.release]
diff --git a/build.rs b/build.rs
index 525c5949..d74a7c3f 100644
--- a/build.rs
+++ b/build.rs
@@ -1,5 +1,4 @@
 extern crate ansi_term;
-extern crate peg;
 extern crate version_check;
 
 use ansi_term::Color::{Red, Yellow, Blue, White};
@@ -46,6 +45,4 @@ fn main() {
             println!("cargo:warning={}", "Build may fail due to incompatible rustc version.");
         }
     }
-
-    peg::cargo_build("src/parser/grammar.rustpeg");
 }
diff --git a/examples/builtin_piping.ion b/examples/builtin_piping.ion
index 8d161a80..30a70e91 100644
--- a/examples/builtin_piping.ion
+++ b/examples/builtin_piping.ion
@@ -1,9 +1,4 @@
 matches Foo '([A-Z])\w+' && echo true
 matches foo '([A-Z])\w+' || echo false
-
-fn foobar x; end
-
-fn | tr '[a-z]' '[A-Z]'
-
 read foo <<< $(echo bar)
 echo $foo
diff --git a/examples/builtin_piping.out b/examples/builtin_piping.out
index 5a56044f..cd0c5dc5 100644
--- a/examples/builtin_piping.out
+++ b/examples/builtin_piping.out
@@ -1,5 +1,3 @@
 true
 false
-# FUNCTIONS
-    FOOBAR
 bar
diff --git a/src/parser/grammar.rustpeg b/src/parser/grammar.rustpeg
deleted file mode 100644
index f22b2131..00000000
--- a/src/parser/grammar.rustpeg
+++ /dev/null
@@ -1,86 +0,0 @@
-use parser::pipelines;
-use parser::peg::get_function_args;
-use shell::flow_control::*;
-
-#[pub]
-parse_ -> Statement
-      = fn_
-      / match_
-      / case_
-      / pipelines
-
-#[pub]
-fn_ -> Statement
-    = "fn " n:_name whitespace* args:_fn_args whitespace* description:_description? {?
-        get_function_args(args).map(|args| Statement::Function {
-            description: description.unwrap_or("".into()),
-            name: n.into(),
-            args: args,
-            statements: Vec::new(),
-        }).ok_or("ion: invalid function argument\n")
-    }
-
-_description -> String
-      = "--" whitespace* description:$([^\r\n]*) { description.into() }
-
-_name -> String
-      = n:$([A-z0-9_]+) { n.into() }
-
-_fn_args -> Vec<String>
-    = _fn_arg ** " "
-
-_args -> Vec<String>
-      = _arg ** " "
-
-_arg -> String
-     = n:$([A-z0-9_]+) { n.into() }
-
-_fn_arg -> String
-    = n:$([A-z0-9_:]+) { n.into()}
-
-wildcard_ -> Option<String> = "_" { None }
-value_ -> Option<String> = contents:$(.*) { Some(contents.into())}
-
-pattern_ -> Option<String> = wildcard_ / value_
-
-case_ -> Statement
-    = "case" whitespace+ p:pattern_ {
-    Statement::Case(Case { value: p, statements: Vec::new() })
-  }
-
-#[pub]
-match_ -> Statement = "match" whitespace+ expression:$(.*) {
-  Statement::Match {
-    expression: expression.into(),
-    cases: Vec::new()
-  }
-}
-
-pub pipelines -> Statement
-    = (unused* newline)* [#] .* { Statement::Default }
-    / [ \n\t\r]* pipeline:_pipelines { pipeline }
-    / (unused*) ** newline { Statement::Default }
-
-
-// Converts the pipeline string into a statement, handling redirection, piping, and backgrounds.
-_pipelines -> Statement
-    = command:$(.+) {?
-    pipelines::Collector::run(command).map(Statement::Pipeline)
-}
-
-unused -> ()
-    = whitespace comment? { () }
-    / comment { () }
-
-comment -> ()
-    = [#] [^\r\n]*
-
-whitespace -> ()
-    = [ \t]+
-
-job_ending -> ()
-    = [;]
-    / newline
-
-newline -> ()
-    = [\r\n]
diff --git a/src/parser/peg.rs b/src/parser/peg.rs
index 93e2ecb3..3ca5e69f 100644
--- a/src/parser/peg.rs
+++ b/src/parser/peg.rs
@@ -1,14 +1,12 @@
 use std::char;
 use std::fmt;
-use std::io::{Write, stderr};
 
-use self::grammar::parse_;
 use super::{ArgumentSplitter, pipelines};
 use super::{ExpanderFunctions, Select, expand_string};
 use super::assignments::parse_assignment;
 use shell::{Job, JobKind};
 use shell::directory_stack::DirectoryStack;
-use shell::flow_control::{ElseIf, FunctionArgument, Statement, Type};
+use shell::flow_control::{Case, ElseIf, FunctionArgument, Statement, Type};
 use shell::variables::Variables;
 
 #[derive(Debug, PartialEq, Clone, Copy)]
@@ -128,12 +126,20 @@ fn collect<F>(arguments: &str, statement: F) -> Statement
     }
 }
 
+fn is_valid_name(name: &str) -> bool {
+    !name.chars().any(|c| !(c.is_alphanumeric() || c == '_'))
+}
+
 pub fn parse(code: &str) -> Statement {
     let cmd = code.trim();
     match cmd {
         "end" => return Statement::End,
         "break" => return Statement::Break,
         "continue" => return Statement::Continue,
+        "for" | "match" | "case" => {
+            eprintln!("ion: syntax error: incomplete control flow statement");
+            return Statement::Default;
+        }
         _ if cmd.starts_with("let ") => return Statement::Let { expression: parse_assignment(cmd[4..].trim_left()) },
         _ if cmd.starts_with("export ") => return Statement::Export(parse_assignment(cmd[7..].trim_left())),
         _ if cmd.starts_with("if ") => {
@@ -181,30 +187,106 @@ pub fn parse(code: &str) -> Statement {
             let variable = &cmd[..pos];
             cmd = &cmd[pos..].trim_left();
 
-            if cmd.starts_with("in ") {
-                cmd = cmd[3..].trim_left();
-            } else {
+            if !cmd.starts_with("in ") {
                 eprintln!("ion: syntax error: incorrect for loop syntax");
                 return Statement::Default;
             }
 
             return Statement::For {
                 variable: variable.into(),
-                values: ArgumentSplitter::new(cmd).map(String::from).collect(),
+                values: ArgumentSplitter::new(cmd[3..].trim_left()).map(String::from).collect(),
                 statements: Vec::new(),
             };
         }
-        _ => (),
-    }
+        _ if cmd.starts_with("case ") => {
+            let value = match cmd[5..].trim_left() {
+                "_"       => None,
+                value @ _ => Some(value.into())
+            };
+            return Statement::Case(Case { value: value, statements: Vec::new() });
+        }
+        _ if cmd.starts_with("match ") => {
+            return Statement::Match {
+                expression: cmd[6..].trim_left().into(),
+                cases: Vec::new()
+            };
+        }
+        _ if cmd.starts_with("fn ") => {
+            let cmd = cmd[3..].trim_left();
+            let pos = cmd.find(char::is_whitespace).unwrap_or(cmd.len());
+            let name = &cmd[..pos];
+            if !is_valid_name(name) {
+                eprintln!("ion: syntax error: {} is not a valid function name\n     \
+                    Function names may only contain alphanumeric characters", name);
+                return Statement::Default;
+            }
 
-    match parse_(cmd) {
-        Ok(code_ok) => code_ok,
-        Err(err) => {
-            let stderr = stderr();
-            let _ = writeln!(stderr.lock(), "ion: Syntax {}", err);
-            Statement::Default
+            let mut args_iter = cmd[pos..].split_whitespace();
+            let mut args = Vec::new();
+            let mut description = String::new();
+            let mut description_flag = 0u8;
+
+            while let Some(arg) = args_iter.next() {
+                if arg.starts_with("--") {
+                    if arg.len() > 2 {
+                        description.push_str(&arg[2..]);
+                        description_flag |= 1;
+                    }
+                    description_flag |= 2;
+                    break
+                } else {
+                    args.push(arg.to_owned());
+                }
+            }
+
+            if description_flag & 2 != 0 {
+                if description_flag & 1 != 0 {
+                    if let Some(arg) = args_iter.next() {
+                        description.push(' ');
+                        description.push_str(&arg);
+                    }
+
+                    for argument in args_iter {
+                        description.push(' ');
+                        description.push_str(&argument);
+                    }
+                } else {
+                    if let Some(arg) = args_iter.next() {
+                        description.push_str(&arg);
+                    }
+
+                    for argument in args_iter {
+                        description.push(' ');
+                        description.push_str(&argument);
+                    }
+                }
+            }
+
+            match get_function_args(args) {
+                Some(args) => {
+                    return Statement::Function {
+                        description: description,
+                        name: name.into(),
+                        args: args,
+                        statements: Vec::new(),
+                    };
+                }
+                None => {
+                    eprintln!("ion: syntax error: invalid arguments");
+                    return Statement::Default;
+                }
+            }
         }
+        _ => ()
+    }
+
+
+    if cmd.is_empty() || cmd.starts_with('#') {
+        Statement::Default
+    } else {
+        collect(cmd, Statement::Pipeline)
     }
+
 }
 
 pub fn get_function_args(args: Vec<String>) -> Option<Vec<FunctionArgument>> {
@@ -247,73 +329,12 @@ pub fn get_function_args(args: Vec<String>) -> Option<Vec<FunctionArgument>> {
     Some(fn_args)
 }
 
-mod grammar {
-    include!(concat!(env!("OUT_DIR"), "/grammar.rs"));
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
-    use super::grammar::*;
     use shell::JobKind;
     use shell::flow_control::Statement;
 
-    #[test]
-    fn full_script() {
-        pipelines(
-            r#"if a == a
-  echo true a == a
-
-  if b != b
-    echo true b != b
-  else
-    echo false b != b
-
-    if 3 > 2
-      echo true 3 > 2
-    else
-      echo false 3 > 2
-    fi
-  fi
-else
-  echo false a == a
-fi
-"#,
-        ).unwrap(); // Make sure it parses
-    }
-
-    #[test]
-    fn leading_and_trailing_junk() {
-        pipelines(
-            r#"
-
-# comment
-   # comment
-
-
-    if a == a
-  echo true a == a  # Line ending commment
-
-  if b != b
-    echo true b != b
-  else
-    echo false b != b
-
-    if 3 > 2
-      echo true 3 > 2
-    else
-      echo false 3 > 2
-    fi
-  fi
-else
-  echo false a == a
-      fi
-
-# comment
-
-"#,
-        ).unwrap(); // Make sure it parses
-    }
     #[test]
     fn parsing_ifs() {
         // Default case where spaced normally
-- 
GitLab