nushell/src/parser.rs

218 lines
6.8 KiB
Rust
Raw Normal View History

2019-05-28 06:45:18 +00:00
crate mod ast;
2019-05-15 16:12:38 +00:00
crate mod completer;
2019-05-29 15:26:45 +00:00
crate mod lexer;
2019-05-26 06:54:41 +00:00
crate mod parser;
crate mod registry;
crate mod span;
2019-05-11 04:45:57 +00:00
crate use ast::Pipeline;
2019-06-02 16:28:40 +00:00
crate use registry::{Args, CommandConfig};
2019-05-26 06:54:41 +00:00
use crate::errors::ShellError;
use lexer::Lexer;
2019-06-04 21:42:31 +00:00
use log::trace;
2019-05-26 06:54:41 +00:00
use parser::PipelineParser;
2019-06-02 16:28:40 +00:00
pub fn parse(input: &str) -> Result<Pipeline, ShellError> {
2019-06-04 21:42:31 +00:00
let _ = pretty_env_logger::try_init();
2019-05-26 06:54:41 +00:00
let parser = PipelineParser::new();
2019-05-31 00:53:54 +00:00
let tokens = Lexer::new(input, false);
2019-05-26 06:54:41 +00:00
2019-06-04 21:42:31 +00:00
trace!(
"Tokens: {:?}",
tokens.clone().collect::<Result<Vec<_>, _>>()
);
match parser.parse(tokens) {
Ok(val) => Ok(val),
2019-06-07 22:35:07 +00:00
Err(err) => Err(ShellError::parse_error(err)),
}
2019-05-26 06:54:41 +00:00
}
2019-06-02 16:28:40 +00:00
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::ast::Pipeline;
2019-06-02 16:28:40 +00:00
use pretty_assertions::assert_eq;
fn assert_parse(source: &str, expected: Pipeline) {
2019-06-04 21:42:31 +00:00
let parsed = match parse(source) {
Ok(p) => p,
2019-06-07 22:35:07 +00:00
Err(ShellError::Diagnostic(diag)) => {
2019-06-04 21:42:31 +00:00
use language_reporting::termcolor;
let writer = termcolor::StandardStream::stdout(termcolor::ColorChoice::Auto);
2019-06-07 22:35:07 +00:00
let files = crate::parser::span::Files::new(source.to_string());
2019-06-04 21:42:31 +00:00
language_reporting::emit(
&mut writer.lock(),
&files,
&diag.diagnostic,
&language_reporting::DefaultConfig,
)
.unwrap();
panic!("Test failed")
}
Err(err) => panic!("Something went wrong during parse: {:#?}", err),
};
2019-06-02 16:28:40 +00:00
let printed = parsed.print();
2019-06-04 21:42:31 +00:00
2019-06-02 16:28:40 +00:00
assert_eq!(parsed, expected);
assert_eq!(printed, source);
2019-06-02 16:28:40 +00:00
}
macro_rules! commands {
( $( ( $name:tt $( $command:ident ( $arg:expr ) )* ) )|* ) => {{
use $crate::parser::ast::{Expression, ExpressionBuilder};
let mut builder = crate::parser::ast::ExpressionBuilder::new();
builder.pipeline(vec![
2019-06-02 16:28:40 +00:00
$(
(command!($name $($command($arg))*) as (&dyn Fn(&mut ExpressionBuilder) -> Expression))
2019-06-02 16:28:40 +00:00
),*
])
}}
2019-06-02 16:28:40 +00:00
}
macro_rules! command {
($name:ident) => {
&|b: &mut $crate::parser::ast::ExpressionBuilder| b.call((
&|b: &mut $crate::parser::ast::ExpressionBuilder| b.bare(stringify!($name)),
vec![]
))
2019-06-02 16:28:40 +00:00
};
($name:ident $( $command:ident ( $body:expr ) )*) => {{
use $crate::parser::ast::{Expression, ExpressionBuilder};
&|b: &mut ExpressionBuilder| b.call((
(&|b: &mut ExpressionBuilder| b.bare(stringify!($name))) as (&dyn Fn(&mut ExpressionBuilder) -> Expression),
vec![$( (&|b: &mut ExpressionBuilder| b.$command($body)) as &dyn Fn(&mut ExpressionBuilder) -> Expression ),* ]))
}};
($name:ident $( $command:ident ( $body:expr ) )*) => {
&|b: &mut $crate::parser::ast::ExpressionBuilder| b.call(|b| b.bare(stringify!($name)), vec![ $( |b| b.$command($body) ),* ])
2019-06-02 16:28:40 +00:00
};
($name:tt $( $command:ident ( $body:expr ) )*) => {
&|b: &mut $crate::parser::ast::ExpressionBuilder| b.call((&|b| b.bare($name), vec![ $( &|b| b.$command($body) ),* ]))
2019-06-02 16:28:40 +00:00
};
}
#[test]
fn parse_simple_command() {
assert_parse("ls", commands![(ls)]);
}
#[test]
fn parse_command_with_args() {
assert_parse(
r#"open Cargo.toml | select package.authors | split-row " ""#,
commands![
(open bare("Cargo.toml"))
| (select bare("package.authors"))
| ("split-row" string(" "))
2019-06-02 16:28:40 +00:00
],
);
assert_parse(r#"git add ."#, commands![("git" bare("add") bare("."))]);
assert_parse(
"open Cargo.toml | select package.version | echo $it",
commands![
(open bare("Cargo.toml"))
| (select bare("package.version"))
| (echo var("it"))
],
);
assert_parse(
"open Cargo.toml --raw",
commands![(open bare("Cargo.toml") flag("raw"))],
);
assert_parse(
"open Cargo.toml -r",
commands![(open bare("Cargo.toml") shorthand("r"))],
2019-06-02 16:28:40 +00:00
);
assert_parse(
"open Cargo.toml | from-toml | to-toml",
commands![(open bare("Cargo.toml")) | ("from-toml") | ("to-toml")],
);
assert_parse(
r#"config --get "ignore dups" | format-list"#,
commands![(config flag("get") string("ignore dups")) | ("format-list")],
2019-06-02 16:28:40 +00:00
);
assert_parse(
"open Cargo.toml | from-toml | select dependencies | column serde",
commands![
(open bare("Cargo.toml"))
| ("from-toml")
| (select bare("dependencies"))
| (column bare("serde"))
],
);
assert_parse(
"config --set tabs 2",
commands![(config flag("set") bare("tabs") int(2))],
2019-06-02 16:28:40 +00:00
);
assert_parse(
r#"ls | skip 1 | first 2 | select "file name" | rm $it"#,
commands![
(ls)
| (skip int(1))
| (first int(2))
| (select string("file name"))
2019-06-02 16:28:40 +00:00
| (rm var("it"))
],
);
assert_parse(
r#"git branch --merged | split-row "`n" | where $it != "* master""#,
commands![
// TODO: Handle escapes correctly. Should we do ` escape because of paths?
(git bare("branch") flag("merged")) | ("split-row" string("`n")) | (where binary((&|b| b.var("it"), &|b| b.op("!="), &|b| b.string("* master"))))
2019-06-02 16:28:40 +00:00
],
);
assert_parse(
r#"open input2.json | from-json | select glossary.GlossDiv.GlossList.GlossEntry.GlossDef.GlossSeeAlso | where $it > "GML""#,
commands![
(open bare("input2.json"))
| ("from-json")
| (select bare("glossary.GlossDiv.GlossList.GlossEntry.GlossDef.GlossSeeAlso"))
| (where binary((&|b| b.var("it"), &|b| b.op(">"), &|b| b.string("GML"))))
2019-06-02 16:28:40 +00:00
]
);
assert_parse(
r"cd ..\.cargo\",
commands![
(cd bare(r"..\.cargo\"))
],
);
assert_parse(
"ls | where size < 1KB",
commands![
(ls) | (where binary((&|b| b.bare("size"), &|b| b.op("<"), &|b| b.unit((1, "KB")))))
2019-06-02 16:28:40 +00:00
],
);
2019-06-04 21:42:31 +00:00
assert_parse(
"ls | where { $it.size > 100 }",
commands![
(ls) | (where block(&|b| b.binary((&|b| b.path((&|b| b.var("it"), vec!["size"])), &|b| b.op(">"), &|b| b.int(100)))))
],
)
2019-06-04 21:42:31 +00:00
}
2019-06-02 16:28:40 +00:00
}