From 2aee857d19826b1a0cc9ec0c31a6e40bf09fc1ce Mon Sep 17 00:00:00 2001 From: Xavier L'Heureux <xavier.lheureux@icloud.com> Date: Tue, 25 Jun 2019 13:44:49 -0400 Subject: [PATCH] Add simple documentation to the parser --- src/lib/expansion/words/mod.rs | 4 +- src/lib/parser/lexers/arguments.rs | 70 +++++++++++++------ src/lib/parser/lexers/assignments/keys.rs | 8 +++ src/lib/parser/lexers/assignments/operator.rs | 12 ++++ .../parser/lexers/assignments/primitive.rs | 11 +++ src/lib/parser/lexers/mod.rs | 2 + src/lib/parser/mod.rs | 2 + src/lib/parser/pipelines.rs | 22 ++++-- src/lib/parser/quotes.rs | 5 +- src/lib/parser/statement/parse.rs | 15 +++- src/lib/parser/statement/splitter.rs | 11 +++ src/lib/types.rs | 1 + 12 files changed, 127 insertions(+), 36 deletions(-) diff --git a/src/lib/expansion/words/mod.rs b/src/lib/expansion/words/mod.rs index 2f3a50ab..75439a60 100644 --- a/src/lib/expansion/words/mod.rs +++ b/src/lib/expansion/words/mod.rs @@ -155,8 +155,8 @@ impl<'a, E: Expander + 'a> WordIterator<'a, E> { b'[' if self.quotes == Quotes::None => level += 1, b']' if self.quotes == Quotes::None => { if level == 0 { - let elements = ArgumentSplitter::new(&self.data[start..self.read]) - .collect::<Vec<&str>>(); + let elements = + ArgumentSplitter::new(&self.data[start..self.read]).collect::<Vec<_>>(); self.read += 1; return if let Some(&b'[') = self.data.as_bytes().get(self.read) { diff --git a/src/lib/parser/lexers/arguments.rs b/src/lib/parser/lexers/arguments.rs index 37b235a3..781971fa 100644 --- a/src/lib/parser/lexers/arguments.rs +++ b/src/lib/parser/lexers/arguments.rs @@ -7,39 +7,54 @@ enum Comm { None, } +/// A type of paired token #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Field { + /// Processes (ex: `$(..)`) Proc, + /// Literal array (ex: `[ 1 .. 3 ]`) Array, + /// Brace expansion (ex: `{a,b,c,d}`) Braces, } use self::Field::*; +/// The depth of various paired structures #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Levels { - parens: i32, - array: i32, - braces: i32, + /// Parentheses + parens: u8, + /// Array literals + array: u8, + /// Braces + braces: u8, } +/// Error with paired tokens #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Error)] pub enum LevelsError { - // paired - #[error(display = "unmatched left paren")] + /// Unmatched opening parenthese + #[error(display = "unmatched opening parenthese")] UnmatchedParen, - #[error(display = "unmatched left bracket")] + /// Unmatched opening bracket + #[error(display = "unmatched opening bracket")] UnmatchedBracket, - #[error(display = "unmatched left brace")] + /// Unmatched opening brace + #[error(display = "unmatched opening brace")] UnmatchedBrace, - #[error(display = "extra right paren(s)")] + /// Extra closing parenthese(s) + #[error(display = "extra closing parenthese(s)")] ExtraParen, - #[error(display = "extra right bracket(s)")] + /// Extra closing bracket(s) + #[error(display = "extra closing bracket(s)")] ExtraBracket, - #[error(display = "extra right brace(s)")] + /// Extra closing brace(s) + #[error(display = "extra closing brace(s)")] ExtraBrace, } impl Levels { + /// Add a new depth level pub fn up(&mut self, field: Field) { let level = match field { Proc => &mut self.parens, @@ -49,17 +64,26 @@ impl Levels { *level += 1; } - pub fn down(&mut self, field: Field) { + /// Close paired tokens + pub fn down(&mut self, field: Field) -> Result<(), LevelsError> { let level = match field { - Proc => &mut self.parens, - Array => &mut self.array, - Braces => &mut self.braces, + Proc if self.parens > 0 => &mut self.parens, + Array if self.array > 0 => &mut self.array, + Braces if self.braces > 0 => &mut self.braces, + + // errors + Proc => return Err(LevelsError::ExtraParen), + Array => return Err(LevelsError::ExtraBracket), + Braces => return Err(LevelsError::ExtraBrace), }; *level -= 1; + Ok(()) } + /// Check if all parens where matched pub fn are_rooted(&self) -> bool { self.parens == 0 && self.array == 0 && self.braces == 0 } + /// Check if all is ok pub fn check(&self) -> Result<(), LevelsError> { if self.parens > 0 { Err(LevelsError::UnmatchedParen) @@ -67,12 +91,6 @@ impl Levels { Err(LevelsError::UnmatchedBracket) } else if self.braces > 0 { Err(LevelsError::UnmatchedBrace) - } else if self.parens < 0 { - Err(LevelsError::ExtraParen) - } else if self.array < 0 { - Err(LevelsError::ExtraBracket) - } else if self.braces < 0 { - Err(LevelsError::ExtraBrace) } else { Ok(()) } @@ -93,6 +111,7 @@ pub struct ArgumentSplitter<'a> { } impl<'a> ArgumentSplitter<'a> { + /// Create a new argument splitter based on the provided data pub fn new(data: &'a str) -> ArgumentSplitter<'a> { ArgumentSplitter { data, @@ -156,9 +175,14 @@ impl<'a> Iterator for ArgumentSplitter<'a> { continue; } b'[' => levels.up(Array), - b']' => levels.down(Array), + b']' => { + let _ = levels.down(Array); + } b'{' => levels.up(Braces), - b'}' => levels.down(Braces), + b'}' => { + // TODO: handle errors here + let _ = levels.down(Braces); + } b'(' => { // Disable VARIAB + ARRAY and enable METHOD. // if variab or array are set @@ -171,7 +195,7 @@ impl<'a> Iterator for ArgumentSplitter<'a> { } b')' => { self.method = false; - levels.down(Proc) + let _ = levels.down(Proc); } // Toggle double quote rules. diff --git a/src/lib/parser/lexers/assignments/keys.rs b/src/lib/parser/lexers/assignments/keys.rs index 02bebadf..10366f9a 100644 --- a/src/lib/parser/lexers/assignments/keys.rs +++ b/src/lib/parser/lexers/assignments/keys.rs @@ -5,7 +5,9 @@ use err_derive::Error; /// types are being assigned. #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct Key<'a> { + /// What should be the type of the literal pub kind: Primitive, + /// What name should be given to the variable pub name: &'a str, } @@ -13,14 +15,19 @@ pub struct Key<'a> { /// by eliminating the lifetime requirements via allocating a `String`. #[derive(Debug, PartialEq, Clone)] pub struct KeyBuf { + /// What type should the literal be pub kind: Primitive, + /// What name should be given to the variable pub name: String, } +/// Failed to parse the literal as a variable corresponding to key #[derive(Debug, PartialEq, Error, Eq)] pub enum TypeError { + /// The value supplied is invalid #[error(display = "invalid type supplied: {}", _0)] Invalid(String), + /// The primitive type does not correspond to that of the function argument #[error(display = "expected {}", _0)] BadValue(Primitive), } @@ -114,6 +121,7 @@ impl<'a> KeyIterator<'a> { } } + /// Create a new iterator based on given data pub fn new(data: &'a str) -> KeyIterator<'a> { KeyIterator { data, read: 0 } } } diff --git a/src/lib/parser/lexers/assignments/operator.rs b/src/lib/parser/lexers/assignments/operator.rs index c1c1e6ff..4c75956e 100644 --- a/src/lib/parser/lexers/assignments/operator.rs +++ b/src/lib/parser/lexers/assignments/operator.rs @@ -1,17 +1,29 @@ use std::fmt::{self, Display, Formatter}; +/// An operation to do on a value #[derive(Debug, PartialEq, Clone, Copy)] pub enum Operator { + /// Addition (only works on numeric types) Add, + /// Concatenation (will also concat numeric types) Concatenate, + /// Prepend the value (will also concat numeric types) ConcatenateHead, + /// Division (only works on numeric types) Divide, + /// Assignment Equal, + /// Assign a default value OptionalEqual, + /// Exponent (only works on numeric types) Exponent, + /// Filter the array to remove the matching values (only works on array and map-like types) Filter, + /// Euclidian Division (only available on numeric types, and works on floats too) IntegerDivide, + /// Muliplication (only works on numeric types) Multiply, + /// Substraction (only works on numeric types) Subtract, } diff --git a/src/lib/parser/lexers/assignments/primitive.rs b/src/lib/parser/lexers/assignments/primitive.rs index e98c471e..4358e936 100644 --- a/src/lib/parser/lexers/assignments/primitive.rs +++ b/src/lib/parser/lexers/assignments/primitive.rs @@ -3,16 +3,27 @@ use std::fmt::{self, Display, Formatter}; /// A primitive defines the type that a requested value should satisfy. #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum Primitive { + /// A plain string (ex: `"a string"`) Str, + /// An array of string (ex: `["-" b c d]`) StrArray, + /// A true-false value Boolean, + /// An array of booleans BooleanArray, + /// An integer numeric type Integer, + /// An array of integer numeric type IntegerArray, + /// A floating-point value Float, + /// A floating-point value array FloatArray, + /// A hash map HashMap(Box<Primitive>), + /// A btreemap BTreeMap(Box<Primitive>), + /// An index variable (ex: `$array[0]`) Indexed(String, Box<Primitive>), } diff --git a/src/lib/parser/lexers/mod.rs b/src/lib/parser/lexers/mod.rs index b7c92a97..406d3f86 100644 --- a/src/lib/parser/lexers/mod.rs +++ b/src/lib/parser/lexers/mod.rs @@ -1,4 +1,6 @@ +/// Check functions & methods arguments pub mod arguments; +/// Check assignements pub mod assignments; pub use self::{arguments::*, assignments::*}; diff --git a/src/lib/parser/mod.rs b/src/lib/parser/mod.rs index 943e9d0f..59a52745 100644 --- a/src/lib/parser/mod.rs +++ b/src/lib/parser/mod.rs @@ -1,4 +1,6 @@ +/// The terminal tokens associated with the parsing process pub mod lexers; +/// Parse the pipelines to a Pipeline struct pub mod pipelines; mod quotes; mod statement; diff --git a/src/lib/parser/pipelines.rs b/src/lib/parser/pipelines.rs index d1f429fc..6c3be406 100644 --- a/src/lib/parser/pipelines.rs +++ b/src/lib/parser/pipelines.rs @@ -11,25 +11,33 @@ use crate::{ const ARG_DEFAULT_SIZE: usize = 10; +/// An error produced during pipeline parsing #[derive(Debug, Error)] pub enum PipelineParsingError { // redirections + /// No file was provided after the redirection output #[error(display = "expected file argument after redirection for output")] NoRedirection, + /// Heredocs are deprecated and were used #[error(display = "heredocs are not a part of Ion. Use redirection and/or cat instead")] HeredocsDeprecated, + /// No string was given to the herestring #[error(display = "expected string argument after '<<<'")] NoHereStringArg, + /// No file was provided after the input redirection #[error(display = "expected file argument after redirection for input")] NoRedirectionArg, // quotes + /// Unterminated double quotes #[error(display = "unterminated double quote")] UnterminatedDoubleQuote, + /// Unterminated single quotes #[error(display = "unterminated single quote")] UnterminatedSingleQuote, // paired + /// Error with paired tokens (parens, brackets & braces) #[error(display = "{}", _0)] Paired(#[error(cause)] LevelsError), } @@ -65,7 +73,8 @@ impl<'a> AddItem<'a> for Pipeline<'a> { } } -#[derive(Debug)] +/// Collect pipelines in the input +#[derive(Debug, Clone)] pub struct Collector<'a> { data: &'a str, } @@ -107,7 +116,7 @@ impl<'a> Collector<'a> { .map(|file| outputs.push(Redirection { from, file: file.into(), append })) } - pub fn parse<'builtins>( + fn parse<'builtins>( &self, builtins: &BuiltinMap<'builtins>, ) -> Result<Pipeline<'builtins>, PipelineParsingError> { @@ -264,7 +273,7 @@ impl<'a> Collector<'a> { bytes.next(); } b')' => { - levels.down(Field::Proc); + levels.down(Field::Proc)?; bytes.next(); } b'[' => { @@ -273,7 +282,7 @@ impl<'a> Collector<'a> { bytes.next(); } b']' => { - levels.down(Field::Array); + levels.down(Field::Array)?; if array_brace_counter % 2 == 1 { array_brace_counter = (array_brace_counter - 1) / 2; bytes.next(); @@ -288,7 +297,7 @@ impl<'a> Collector<'a> { } b'}' => { if array_brace_counter % 2 == 0 { - levels.down(Field::Braces); + levels.down(Field::Braces)?; array_brace_counter /= 2; bytes.next(); } else { @@ -404,6 +413,7 @@ impl<'a> Collector<'a> { } } + /// Collect a pipeline on the given data pub fn run<'builtins>( data: &'a str, builtins: &BuiltinMap<'builtins>, @@ -411,7 +421,7 @@ impl<'a> Collector<'a> { Collector::new(data).parse(builtins) } - pub fn new(data: &'a str) -> Self { Collector { data } } + fn new(data: &'a str) -> Self { Collector { data } } } #[cfg(test)] diff --git a/src/lib/parser/quotes.rs b/src/lib/parser/quotes.rs index 171b57c8..3bf692a1 100644 --- a/src/lib/parser/quotes.rs +++ b/src/lib/parser/quotes.rs @@ -1,6 +1,6 @@ use std::{iter::Peekable, str}; -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum Quotes { Single, Double, @@ -9,8 +9,6 @@ enum Quotes { /// Serves as a buffer for storing a string until that string can be terminated. /// -/// # Examples -/// /// This example comes from the shell's REPL, which ensures that the user's input /// will only be submitted for execution once a terminated command is supplied. #[derive(Debug)] @@ -185,6 +183,7 @@ impl<I: Iterator<Item = u8>> Terminator<I> { } } + /// Create a new reader on the provided input pub fn new(inner: I) -> Terminator<I> { Terminator { inner: RearPeekable { iter: inner.peekable(), now: None, last: None }, diff --git a/src/lib/parser/statement/parse.rs b/src/lib/parser/statement/parse.rs index ac3c9d83..2603eb19 100644 --- a/src/lib/parser/statement/parse.rs +++ b/src/lib/parser/statement/parse.rs @@ -16,34 +16,45 @@ use crate::{ use err_derive::Error; use std::char; +/// Check if the given name is valid for functions, aliases & variables pub fn is_valid_name(name: &str) -> bool { let mut chars = name.chars(); chars.next().map_or(false, |b| char::is_alphabetic(b) || b == '_') && chars.all(|b| b.is_alphanumeric() || b == '_') } +/// An Error occured during parsing #[derive(Debug, Error)] pub enum ParseError { + /// The blocks were in a wrong order #[error(display = "incomplete control flow statement")] IncompleteFlowControl, + /// No keys were supplied for assignment #[error(display = "no key supplied for assignment")] NoKeySupplied, + /// No operator was supplied for assignment #[error(display = "no operator supplied for assignment")] NoOperatorSupplied, + /// No value supplied for assignment #[error(display = "no values supplied for assignment")] NoValueSupplied, + /// No value given for iteration in a for loop #[error(display = "no value supplied for iteration in for loop")] NoInKeyword, + /// Error with match statements #[error(display = "case error: {}", _0)] CaseError(#[error(cause)] CaseError), + /// The provided function name was invalid #[error( - display = "'{}' is not a valid function name\n Function names may only contain \ - alphanumeric characters", + display = "'{}' is not a valid function name + Function names may only contain alphanumeric characters", _0 )] InvalidFunctionName(String), + /// The arguments did not match the function's signature #[error(display = "function argument error: {}", _0)] InvalidFunctionArgument(#[error(cause)] FunctionParseError), + /// Error occured during parsing of a pipeline #[error(display = "{}", _0)] PipelineParsingError(#[error(cause)] PipelineParsingError), } diff --git a/src/lib/parser/statement/splitter.rs b/src/lib/parser/statement/splitter.rs index e88d5d02..519681bb 100644 --- a/src/lib/parser/statement/splitter.rs +++ b/src/lib/parser/statement/splitter.rs @@ -11,22 +11,31 @@ enum LogicalOp { None, } +/// Error during statement parsing #[derive(Debug, PartialEq, Error)] pub enum StatementError { + /// The command name is illegal #[error(display = "illegal command name: {}", _0)] IllegalCommandName(String), + /// Invalid character found #[error(display = "syntax error: '{}' at position {} is out of place", _0, _1)] InvalidCharacter(char, usize), + /// Unterminated subshell #[error(display = "syntax error: unterminated subshell")] UnterminatedSubshell, + /// Unterminated namespaced variable #[error(display = "syntax error: unterminated brace")] UnterminatedBracedVar, + /// Unterminated brace expansion #[error(display = "syntax error: unterminated braced var")] UnterminatedBrace, + /// Unterminated method #[error(display = "syntax error: unterminated method")] UnterminatedMethod, + /// Unterminated arithmetic expression #[error(display = "syntax error: unterminated arithmetic subexpression")] UnterminatedArithmetic, + /// Expected command but found ... #[error(display = "expected command, but found {}", _0)] ExpectedCommandButFound(&'static str), } @@ -38,6 +47,7 @@ pub enum StatementVariant<'a> { Default(&'a str), } +/// Split an input data into a set of statements #[derive(Debug)] pub struct StatementSplitter<'a> { data: &'a str, @@ -53,6 +63,7 @@ pub struct StatementSplitter<'a> { } impl<'a> StatementSplitter<'a> { + /// Create a new statement splitter on data pub fn new(data: &'a str) -> Self { StatementSplitter { data, diff --git a/src/lib/types.rs b/src/lib/types.rs index a6488dda..6334645c 100644 --- a/src/lib/types.rs +++ b/src/lib/types.rs @@ -3,6 +3,7 @@ use smallvec::SmallVec; pub use types_rs::types::*; pub use crate::shell::flow_control::Function; +/// A owned version of a set of arguments for spawning a command pub type Args = SmallVec<[small::String; 4]>; /// Construct a new Array containing the given arguments /// -- GitLab