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