A real parser (lalrpop)

This commit is contained in:
Yehuda Katz 2019-05-25 23:54:41 -07:00
parent cd92f579c9
commit b74daa2e60
24 changed files with 2599 additions and 216 deletions

0
.cargo/config Normal file
View file

53
Cargo.lock generated
View file

@ -639,6 +639,18 @@ dependencies = [
"syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "env_logger"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "error-chain"
version = "0.12.1"
@ -828,6 +840,11 @@ dependencies = [
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lalrpop-util"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "1.3.0"
@ -985,11 +1002,15 @@ dependencies = [
"futures_codec 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lalrpop-util 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 5.0.0-beta1 (registry+https://github.com/rust-lang/crates.io-index)",
"ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"pancurses 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty_env_logger 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"prettyprint 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"prettytable-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"subprocess 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
"sysinfo 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1179,6 +1200,16 @@ dependencies = [
"xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pretty_env_logger"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "prettyprint"
version = "0.6.0"
@ -1656,6 +1687,14 @@ dependencies = [
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termcolor"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termion"
version = "1.5.2"
@ -1854,6 +1893,15 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "wincolor"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winreg"
version = "0.5.1"
@ -1953,6 +2001,7 @@ dependencies = [
"checksum enum-map-internals 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "38b0bacf3ea7aba18ce84032efc3f0fa29f5c814048b742ab3e64d07d83ac3e8"
"checksum enumset 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac0a22e173f6570a7d69a2ab9e3fe79cf0dcdd0fdb162bfc932b97158f2b2a7"
"checksum enumset_derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "01d93b926a992a4a526c2a14e2faf734fdef5bf9d0a52ba69a2ca7d4494c284b"
"checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a"
"checksum error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9"
"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
@ -1976,6 +2025,7 @@ dependencies = [
"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358"
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lalrpop-util 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9768f55211206d3c17181108d8facb80bdffc1f1e674a67b1dddb2743529ca19"
"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
"checksum lexical-core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e82e023e062f1d25f807ad182008fba1b46538e999f908a08cc0c29e084462e"
@ -2014,6 +2064,7 @@ dependencies = [
"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c"
"checksum plist 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f4739851c08dd9a62a78beff2edf1a438517268b2c563c42fc6d9d3139e42d2a"
"checksum pretty_env_logger 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df8b3f4e0475def7d9c2e5de8e5a1306949849761e107b360d03e98eafaffd61"
"checksum prettyprint 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2705417f8aa07cb6308db42e55623479c1c9667942a4d5e4174c684e5da5590d"
"checksum prettytable-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd04b170004fa2daccf418a7f8253aaf033c27760b5f225889024cf66d7ac2e"
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
@ -2069,6 +2120,7 @@ dependencies = [
"checksum sysinfo 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "65f0e28a49b7bf142cee89befd7077b40627d7cc70aa8a8acfe03afc26016c33"
"checksum term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42"
"checksum term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327"
"checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f"
"checksum termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dde0593aeb8d47accea5392b39350015b5eccb12c0d98044d856983d89548dea"
"checksum termios 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72b620c5ea021d75a735c943269bb07d30c9b77d6ac6b236bc8b5c496ef05625"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
@ -2096,6 +2148,7 @@ dependencies = [
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba"
"checksum winreg 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a"
"checksum xi-unicode 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "12ea8eda4b1eb72f02d148402e23832d56a33f55d8c1b2d5bcdde91d79d47cb1"
"checksum xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "541b12c998c5b56aa2b4e6f18f03664eef9a4fd0a246a55594efae6cc2d964b5"

View file

@ -33,6 +33,10 @@ tokio-fs = "0.1.6"
futures_codec = "0.2.2"
term = "0.5.2"
bytes = "0.4.12"
log = "0.4.6"
pretty_env_logger = "0.3.0"
lalrpop-util = "0.17.0"
regex = "1.1.6"
[dependencies.pancurses]
version = "0.16"

9
Makefile.toml Normal file
View file

@ -0,0 +1,9 @@
[tasks.lalrpop]
install_crate = { crate_name = "lalrpop", binary = "lalrpop", test_arg = "--help" }
command = "lalrpop"
args = ["src/parser/parser.lalrpop"]
[tasks.build]
command = "cargo"
args = ["build"]
dependencies = ["lalrpop"]

View file

@ -1,14 +1,17 @@
use crate::prelude::*;
use crate::commands::classified::{
ClassifiedCommand, ClassifiedInputStream, ExternalCommand, InternalCommand, StreamNext,
ClassifiedCommand, ClassifiedInputStream, ClassifiedPipeline, ExternalCommand, InternalCommand,
StreamNext,
};
use crate::context::Context;
crate use crate::errors::ShellError;
crate use crate::format::{EntriesListView, GenericView};
use crate::object::Value;
use crate::parser::{ParsedCommand, Pipeline};
use crate::stream::empty_stream;
use log::debug;
use rustyline::error::ReadlineError;
use rustyline::{self, ColorMode, Config, Editor};
use std::collections::VecDeque;
@ -32,20 +35,6 @@ impl<T> MaybeOwned<'a, T> {
}
pub async fn cli() -> Result<(), Box<Error>> {
let config = Config::builder().color_mode(ColorMode::Forced).build();
let h = crate::shell::Helper::new();
let mut rl: Editor<crate::shell::Helper> = Editor::with_config(config);
#[cfg(windows)]
{
let _ = ansi_term::enable_ansi_support();
}
rl.set_helper(Some(h));
if rl.load_history("history.txt").is_err() {
println!("No previous history.");
}
let mut context = Context::basic()?;
{
@ -68,6 +57,20 @@ pub async fn cli() -> Result<(), Box<Error>> {
]);
}
let config = Config::builder().color_mode(ColorMode::Forced).build();
let h = crate::shell::Helper::new(context.clone_commands());
let mut rl: Editor<crate::shell::Helper> = Editor::with_config(config);
#[cfg(windows)]
{
let _ = ansi_term::enable_ansi_support();
}
rl.set_helper(Some(h));
if rl.load_history("history.txt").is_err() {
println!("No previous history.");
}
loop {
let readline = rl.readline(&format!(
"{}> ",
@ -141,7 +144,7 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
Ok(line) if line.trim() == "" => LineResult::Success(line.clone()),
Ok(line) => {
let result = match crate::parser::shell_parser(&line) {
let result = match crate::parser::parse(&line, &ctx.registry()) {
Err(err) => {
return LineResult::Error(format!("{:?}", err));
}
@ -149,17 +152,14 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
Ok(val) => val,
};
let parsed: Result<Vec<_>, _> = result
.1
.into_iter()
.map(|item| classify_command(&item, ctx))
.collect();
debug!("=== Parsed ===");
debug!("{:#?}", result);
let parsed = parsed?;
let pipeline = classify_pipeline(&result, ctx)?;
let mut input = ClassifiedInputStream::new();
let mut iter = parsed.into_iter().peekable();
let mut iter = pipeline.commands.into_iter().peekable();
loop {
let item: Option<ClassifiedCommand> = iter.next();
@ -194,7 +194,7 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
(
Some(ClassifiedCommand::Internal(_)),
Some(ClassifiedCommand::External(_)),
) => unimplemented!(),
) => return LineResult::Error(format!("Unimplemented Internal -> External",)),
(
Some(ClassifiedCommand::External(left)),
@ -245,17 +245,34 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
}
}
fn classify_pipeline(
pipeline: &Pipeline,
context: &Context,
) -> Result<ClassifiedPipeline, ShellError> {
let commands: Result<Vec<_>, _> = pipeline
.commands
.iter()
.cloned()
.map(|item| classify_command(&item, context))
.collect();
Ok(ClassifiedPipeline {
commands: commands?,
})
}
fn classify_command(
command: &[crate::parser::Item],
command: &ParsedCommand,
context: &Context,
) -> Result<ClassifiedCommand, ShellError> {
let command_name = &command[0].name()?;
let command_name = &command.name[..];
let args = &command.args;
let arg_list: Vec<Value> = command[1..].iter().map(|i| i.as_value()).collect();
let arg_list_strings: Vec<String> = command[1..].iter().map(|i| i.print()).collect();
let arg_list: Vec<Value> = args.iter().map(|i| Value::from_expr(i)).collect();
let arg_list_strings: Vec<String> = args.iter().map(|i| i.print()).collect();
match *command_name {
other => match context.has_command(*command_name) {
match command_name {
other => match context.has_command(command_name) {
true => {
let command = context.get_command(command_name);
Ok(ClassifiedCommand::Internal(InternalCommand {

View file

@ -1,10 +1,9 @@
use crate::prelude::*;
use futures::TryStreamExt;
use futures_codec::{Encoder, Decoder, Framed};
use bytes::{BufMut, BytesMut};
use futures_codec::{Decoder, Encoder, Framed};
use std::io::{Error, ErrorKind};
use std::sync::Arc;
use subprocess::Exec;
use std::io::{Error, ErrorKind};
use bytes::{BufMut, BytesMut};
/// A simple `Codec` implementation that splits up data into lines.
pub struct LinesCodec {}
@ -37,7 +36,7 @@ impl Decoder for LinesCodec {
.map(Some)
.map_err(|e| Error::new(ErrorKind::InvalidData, e))
}
_ => Ok(None)
_ => Ok(None),
}
}
}
@ -70,6 +69,10 @@ impl ClassifiedInputStream {
}
}
crate struct ClassifiedPipeline {
crate commands: Vec<ClassifiedCommand>,
}
crate enum ClassifiedCommand {
Internal(InternalCommand),
External(ExternalCommand),
@ -127,8 +130,7 @@ impl ExternalCommand {
cmd.push_str(" ");
cmd.push_str(&arg);
}
let process = Exec::shell(&cmd)
.cwd(context.env.lock().unwrap().cwd());
let process = Exec::shell(&cmd).cwd(context.env.lock().unwrap().cwd());
let mut process = match stream_next {
StreamNext::Last => process,

View file

@ -1,6 +1,7 @@
use crate::errors::ShellError;
use crate::object::Value;
use crate::object::{Primitive, Value};
use crate::prelude::*;
use log::debug;
// TODO: "Amount remaining" wrapper
@ -13,18 +14,27 @@ pub fn split(args: CommandArgs) -> Result<OutputStream, ShellError> {
.map(move |v| match v {
Value::Primitive(Primitive::String(s)) => {
let splitter = args[0].as_string().unwrap();
debug!("splitting with {:?}", splitter);
let split_result: Vec<_> = s.split(&splitter).filter(|s| s.trim() != "").collect();
debug!("split result = {:?}", split_result);
if split_result.len() == (args.len() - 1) {
let mut dict = crate::object::Dictionary::default();
for (k, v) in split_result.iter().zip(args.iter().skip(1)) {
dict.add(v.as_string().unwrap(), Value::Primitive(Primitive::String(k.to_string())));
dict.add(
v.as_string().unwrap(),
Value::Primitive(Primitive::String(k.to_string())),
);
}
ReturnValue::Value(Value::Object(dict))
} else {
let mut dict = crate::object::Dictionary::default();
for k in args.iter().skip(1) {
dict.add(k.as_string().unwrap().trim(), Value::Primitive(Primitive::String("".to_string())));
dict.add(
k.as_string().unwrap().trim(),
Value::Primitive(Primitive::String("".to_string())),
);
}
ReturnValue::Value(Value::Object(dict))
}

View file

@ -7,14 +7,11 @@ pub fn r#where(args: CommandArgs) -> Result<OutputStream, ShellError> {
return Err(ShellError::string("select requires a field"));
}
let field: Result<String, _> = args.args[0].as_string();
let field = field?;
let operation = args.args[0].as_operation()?;
let field = operation.left.as_string()?;
let operator = operation.operator;
let right = operation.right;
let input = args.input;
let operator = args.args[1].copy();
match operator {
Value::Primitive(Primitive::Operator(operator)) => {
let right = args.args[2].copy();
let objects = input
.filter(move |item| futures::future::ready(find(&item, &field, &operator, &right)))
@ -22,9 +19,3 @@ pub fn r#where(args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(objects.boxed())
}
x => {
println!("{:?}", x);
Err(ShellError::string("expected a comparison operator"))
}
}
}

View file

@ -1,10 +1,12 @@
use crate::parser::{CommandConfig, CommandRegistry};
use crate::prelude::*;
use indexmap::IndexMap;
use std::error::Error;
use std::sync::Arc;
pub struct Context {
commands: indexmap::IndexMap<String, Arc<dyn Command>>,
commands: IndexMap<String, Arc<dyn Command>>,
crate host: Arc<Mutex<dyn Host + Send>>,
crate env: Arc<Mutex<Environment>>,
}
@ -24,6 +26,16 @@ impl Context {
}
}
pub fn clone_commands(&self) -> indexmap::IndexMap<String, Arc<dyn Command>> {
self.commands.clone()
}
pub fn registry(&self) -> CommandMap {
CommandMap {
commands: self.clone_commands(),
}
}
crate fn has_command(&self, name: &str) -> bool {
self.commands.contains_key(name)
}
@ -48,3 +60,20 @@ impl Context {
command.run(command_args)
}
}
pub struct CommandMap {
#[allow(unused)]
commands: IndexMap<String, Arc<dyn Command>>,
}
impl CommandRegistry for CommandMap {
fn get(&self, name: &str) -> CommandConfig {
CommandConfig {
name: name.to_string(),
mandatory_positional: vec![],
optional_positional: vec![],
rest_positional: true,
named: IndexMap::new(),
}
}
}

View file

@ -3,7 +3,7 @@ use crate::prelude::*;
use derive_new::new;
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, new)]
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, new, Clone)]
pub struct ShellError {
title: String,
error: Value,
@ -60,3 +60,12 @@ impl std::convert::From<subprocess::PopenError> for ShellError {
}
}
}
impl std::convert::From<nom::Err<(&str, nom::error::ErrorKind)>> for ShellError {
fn from(input: nom::Err<(&str, nom::error::ErrorKind)>) -> ShellError {
ShellError {
title: format!("{:?}", input),
error: Value::nothing(),
}
}
}

View file

@ -43,6 +43,14 @@ impl RenderView for GenericView<'value> {
Ok(())
}
Value::Operation(o) => {
host.stdout(&format!(
"Unexpectedly trying to print an operation: {:?}",
o
));
Ok(())
}
Value::Error(e) => {
host.stdout(&format!("{:?}", e));
Ok(())

View file

@ -19,6 +19,7 @@ mod stream;
use std::error::Error;
fn main() -> Result<(), Box<Error>> {
pretty_env_logger::init();
futures::executor::block_on(crate::cli::cli())?;
Ok(())
}

View file

@ -1,10 +1,11 @@
use crate::errors::ShellError;
use crate::object::DataDescriptor;
use crate::parser::parse::Operator;
use crate::parser::tokens::{self, Operator};
use crate::prelude::*;
use ansi_term::Color;
use chrono::{DateTime, Utc};
use chrono_humanize::Humanize;
use derive_new::new;
use ordered_float::OrderedFloat;
use std::time::SystemTime;
@ -20,7 +21,6 @@ pub enum Primitive {
String(String),
Boolean(bool),
Date(DateTime<Utc>),
Operator(Operator),
}
impl Primitive {
@ -51,27 +51,64 @@ impl Primitive {
(false, Some(_)) => format!(""),
},
Primitive::Date(d) => format!("{}", d.humanize()),
Primitive::Operator(o) => o.print(),
}
}
}
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, new)]
pub struct Operation {
crate left: Value,
crate operator: Operator,
crate right: Value,
}
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone)]
pub enum Value {
Primitive(Primitive),
Object(crate::object::Dictionary),
List(Vec<Value>),
Operation(Box<Operation>),
#[allow(unused)]
Error(Box<ShellError>),
}
impl Value {
crate fn from_leaf(leaf: &tokens::Leaf) -> Value {
use tokens::*;
match leaf {
Leaf::String(s) => Value::string(s),
Leaf::Bare(s) => Value::string(s),
Leaf::Boolean(b) => Value::boolean(*b),
Leaf::Int(i) => Value::int(*i),
}
}
crate fn from_expr(expr: &tokens::Expression) -> Value {
use tokens::*;
match expr {
Expression::Leaf(leaf) => Value::from_leaf(leaf),
Expression::Binary(Binary {
left,
operator,
right,
}) => Value::Operation(Box::new(Operation::new(
Value::from_leaf(left),
*operator,
Value::from_leaf(right),
))),
}
}
crate fn data_descriptors(&self) -> Vec<DataDescriptor> {
match self {
Value::Primitive(_) => vec![DataDescriptor::value_of()],
Value::Object(o) => o.data_descriptors(),
Value::List(_) => vec![],
Value::Operation(_) => vec![],
Value::Error(_) => vec![],
}
}
@ -81,6 +118,7 @@ impl Value {
Value::Primitive(_) => MaybeOwned::Owned(Value::nothing()),
Value::Object(o) => o.get_data_by_key(name),
Value::List(_) => MaybeOwned::Owned(Value::nothing()),
Value::Operation(_) => MaybeOwned::Owned(Value::nothing()),
Value::Error(_) => MaybeOwned::Owned(Value::nothing()),
}
}
@ -90,6 +128,7 @@ impl Value {
p @ Value::Primitive(_) => MaybeOwned::Borrowed(p),
Value::Object(o) => o.get_data(desc),
Value::List(_) => MaybeOwned::Owned(Value::nothing()),
Value::Operation(_) => MaybeOwned::Owned(Value::nothing()),
Value::Error(_) => MaybeOwned::Owned(Value::nothing()),
}
}
@ -102,6 +141,7 @@ impl Value {
let list = l.iter().map(|i| i.copy()).collect();
Value::List(list)
}
Value::Operation(o) => Value::Operation(o.clone()),
Value::Error(e) => Value::Error(Box::new(e.copy_error())),
}
}
@ -111,6 +151,7 @@ impl Value {
Value::Primitive(p) => p.format(field_name),
Value::Object(_) => format!("[object Object]"),
Value::List(_) => format!("[list List]"),
Value::Operation(_) => format!("[operation Operation]"),
Value::Error(e) => format!("{}", e),
}
}
@ -127,6 +168,18 @@ impl Value {
}
}
crate fn as_operation(&self) -> Result<Operation, ShellError> {
match self {
Value::Operation(o) => Ok(*o.clone()),
// TODO: this should definitely be more general with better errors
other => Err(ShellError::string(format!(
"Expected operation, got {:?}",
other
))),
}
}
crate fn as_int(&self) -> Result<i64, ShellError> {
match self {
Value::Primitive(Primitive::Int(i)) => Ok(*i),

View file

@ -79,6 +79,16 @@ impl DescriptorName {
}
}
impl Clone for DataDescriptor {
fn clone(&self) -> DataDescriptor {
DataDescriptor {
name: self.name.clone(),
readonly: self.readonly,
ty: self.ty.copy(),
}
}
}
impl DataDescriptor {
crate fn value_of() -> DataDescriptor {
DataDescriptor {

View file

@ -5,7 +5,7 @@ use crate::object::{Primitive, Value};
use indexmap::IndexMap;
use std::cmp::{Ordering, PartialOrd};
#[derive(Debug, Default, Eq, PartialEq)]
#[derive(Debug, Default, Eq, PartialEq, Clone)]
pub struct Dictionary {
entries: IndexMap<DataDescriptor, Value>,
}

View file

@ -1,4 +1,18 @@
crate mod completer;
crate mod parse;
crate mod parser;
crate mod registry;
crate mod tokens;
crate use self::parse::{shell_parser, Item};
crate use registry::{CommandConfig, CommandRegistry};
crate use tokens::{ParsedCommand, Pipeline};
use crate::errors::ShellError;
use parser::PipelineParser;
pub fn parse(input: &str, _registry: &dyn CommandRegistry) -> Result<Pipeline, ShellError> {
let parser = PipelineParser::new();
parser
.parse(input)
.map_err(|e| ShellError::string(format!("{:?}", e)))
}

View file

@ -1,144 +0,0 @@
use crate::prelude::*;
use nom::branch::alt;
use nom::bytes::complete::{escaped, is_a, is_not, tag};
use nom::character::complete::one_of;
use nom::multi::separated_list;
use nom::sequence::{preceded, terminated};
use nom::IResult;
use nom::{complete, named, ws};
use std::str::FromStr;
#[derive(Debug, Clone)]
pub enum Item {
Quoted(String),
Bare(String),
Int(i64),
Boolean(bool),
Operator(Operator),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Operator {
Equal,
NotEqual,
LessThan,
GreaterThan,
LessThanOrEqual,
GreaterThanOrEqual,
}
impl Operator {
pub fn print(&self) -> String {
match *self {
Operator::Equal => "==".to_string(),
Operator::NotEqual => "!=".to_string(),
Operator::LessThan => "<".to_string(),
Operator::GreaterThan => ">".to_string(),
Operator::LessThanOrEqual => "<=".to_string(),
Operator::GreaterThanOrEqual => ">=".to_string(),
}
}
}
impl FromStr for Operator {
type Err = ();
fn from_str(input: &str) -> Result<Self, <Self as std::str::FromStr>::Err> {
match input {
"==" => Ok(Operator::Equal),
"!=" => Ok(Operator::NotEqual),
"<" => Ok(Operator::LessThan),
">" => Ok(Operator::GreaterThan),
"<=" => Ok(Operator::LessThanOrEqual),
">=" => Ok(Operator::GreaterThanOrEqual),
_ => Err(()),
}
}
}
impl Item {
crate fn as_value(&self) -> Value {
match self {
Item::Quoted(s) => Value::Primitive(Primitive::String(s.clone())),
Item::Bare(s) => Value::Primitive(Primitive::String(s.clone())),
Item::Int(i) => Value::Primitive(Primitive::Int(*i)),
Item::Boolean(b) => Value::Primitive(Primitive::Boolean(*b)),
Item::Operator(o) => Value::Primitive(Primitive::Operator(o.clone())),
}
}
pub fn print(&self) -> String {
match self {
Item::Bare(s) => format!("{}", s),
Item::Quoted(s) => format!("{}", s),
Item::Int(i) => format!("{:?}", i),
Item::Boolean(b) => format!("{:?}", b),
Item::Operator(o) => o.print(),
}
}
}
impl Item {
crate fn name(&self) -> Result<&str, ShellError> {
match self {
Item::Quoted(s) => Ok(s),
Item::Bare(s) => Ok(s),
Item::Boolean(i) => Err(ShellError::string(format!("{} is not a valid command", i))),
Item::Int(i) => Err(ShellError::string(format!("{} is not a valid command", i))),
Item::Operator(x) => Err(ShellError::string(format!(
"{:?} is not a valid command",
x
))),
}
}
}
fn esc(s: &str) -> IResult<&str, &str> {
escaped(is_not("\\\""), '\\', one_of("\"n\\"))(s)
}
fn quoted(s: &str) -> IResult<&str, Item> {
terminated(preceded(tag("\""), esc), tag("\""))(s)
.map(|(a, b)| (a, Item::Quoted(b.to_string())))
}
fn unquoted(s: &str) -> IResult<&str, Item> {
is_not(" |")(s).map(|(a, b)| (a, Item::Bare(b.to_string())))
}
fn operator(s: &str) -> IResult<&str, Item> {
alt((
tag("=="),
tag("!="),
tag("<"),
tag(">"),
tag("<="),
tag(">="),
))(s)
.map(|(a, b)| (a, Item::Operator(FromStr::from_str(b).unwrap())))
}
fn int(s: &str) -> IResult<&str, Item> {
is_a("1234567890")(s).map(|(a, b)| (a, Item::Int(FromStr::from_str(b).unwrap())))
}
fn boolean(s: &str) -> IResult<&str, Item> {
alt((tag("true"), tag("false")))(s)
.map(|(a, b)| (a, Item::Boolean(FromStr::from_str(b).unwrap())))
}
fn command_token(s: &str) -> IResult<&str, Item> {
alt((boolean, int, operator, quoted, unquoted))(s)
}
fn command_args(s: &str) -> IResult<&str, Vec<Item>> {
separated_list(tag(" "), command_token)(s)
}
named!(
pub shell_parser(&str) -> Vec<Vec<Item>>,
complete!(
ws!(
separated_list!(tag("|"), command_args)
)
)
);

56
src/parser/parser.lalrpop Normal file
View file

@ -0,0 +1,56 @@
use std::str::FromStr;
use crate::parser::tokens::*;
grammar;
pub Pipeline: Pipeline = {
<first:Command> => Pipeline::new(vec![first]),
<first:Command> <rest: ( "|" <Command> )+> => Pipeline::from_parts(first, rest),
}
Command: ParsedCommand = {
<command:RawBareWord> <expr:Expr*> => ParsedCommand::new(command, expr)
}
Expr: Expression = {
<Leaf> => Expression::Leaf(<>),
<Binary> => Expression::Binary(<>),
}
Operator: Operator = {
"==" => Operator::Equal,
"!=" => Operator::NotEqual,
"<" => Operator::LessThan,
">" => Operator::GreaterThan,
"<=" => Operator::LessThanOrEqual,
">=" => Operator::GreaterThanOrEqual
}
Binary: Binary = {
<left:Leaf> <op:Operator> <right:Leaf> => Binary::new(left, op, right),
}
Flag: Flag = {
"-" <RawBareWord> => Flag::Shorthand(<>.to_string()),
"--" <RawBareWord> => Flag::Longhand(<>.to_string()),
}
String: String = {
SQString,
DQString,
}
Leaf: Leaf = {
<String> => Leaf::String(<>),
<Num> => Leaf::Int(<>),
<RawBareWord> => match <>.as_ref() {
"true" => Leaf::Boolean(true),
"false" => Leaf::Boolean(false),
_ => Leaf::Bare(<>),
}
}
RawBareWord: String = <s:r#"[^0-9"'\-][^\s]*"#> => <>.to_string();
DQString: String = <s:r#""([^"]|\\")*""#> => s[1..s.len() - 1].to_string();
SQString: String = <s:r#"'([^']|\\')*'"#> => s[1..s.len() - 1].to_string();
Num: i64 = <s:r"-?[0-9]+"> => i64::from_str(s).unwrap();

2109
src/parser/parser.rs Normal file

File diff suppressed because it is too large Load diff

21
src/parser/registry.rs Normal file
View file

@ -0,0 +1,21 @@
use indexmap::IndexMap;
#[allow(unused)]
pub enum CommandType {
Switch,
Single,
Array,
}
#[allow(unused)]
pub struct CommandConfig {
crate name: String,
crate mandatory_positional: Vec<String>,
crate optional_positional: Vec<String>,
crate rest_positional: bool,
crate named: IndexMap<String, CommandType>,
}
pub trait CommandRegistry {
fn get(&self, name: &str) -> CommandConfig;
}

128
src/parser/tokens.rs Normal file
View file

@ -0,0 +1,128 @@
use derive_new::new;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Operator {
Equal,
NotEqual,
LessThan,
GreaterThan,
LessThanOrEqual,
GreaterThanOrEqual,
}
impl Operator {
pub fn print(&self) -> String {
match *self {
Operator::Equal => "==".to_string(),
Operator::NotEqual => "!=".to_string(),
Operator::LessThan => "<".to_string(),
Operator::GreaterThan => ">".to_string(),
Operator::LessThanOrEqual => "<=".to_string(),
Operator::GreaterThanOrEqual => ">=".to_string(),
}
}
}
impl FromStr for Operator {
type Err = ();
fn from_str(input: &str) -> Result<Self, <Self as std::str::FromStr>::Err> {
match input {
"==" => Ok(Operator::Equal),
"!=" => Ok(Operator::NotEqual),
"<" => Ok(Operator::LessThan),
">" => Ok(Operator::GreaterThan),
"<=" => Ok(Operator::LessThanOrEqual),
">=" => Ok(Operator::GreaterThanOrEqual),
_ => Err(()),
}
}
}
#[derive(Debug, Clone)]
pub enum Expression {
Leaf(Leaf),
Binary(Binary),
}
impl Expression {
crate fn print(&self) -> String {
match self {
Expression::Leaf(l) => l.print(),
Expression::Binary(b) => b.print(),
}
}
}
#[derive(Debug, Clone)]
pub enum Leaf {
String(String),
Bare(String),
Boolean(bool),
Int(i64),
}
impl Leaf {
fn print(&self) -> String {
match self {
Leaf::String(s) => format!("{:?}", s),
Leaf::Bare(s) => format!("{}", s),
Leaf::Boolean(b) => format!("{}", b),
Leaf::Int(i) => format!("{}", i),
}
}
}
#[derive(Debug, Clone, new)]
pub struct Binary {
crate left: Leaf,
crate operator: Operator,
crate right: Leaf,
}
impl Binary {
fn print(&self) -> String {
format!(
"{} {} {}",
self.left.print(),
self.operator.print(),
self.right.print()
)
}
}
#[derive(Debug, Clone)]
pub enum Flag {
Shorthand(String),
Longhand(String),
}
impl Flag {
#[allow(unused)]
fn print(&self) -> String {
match self {
Flag::Shorthand(s) => format!("-{}", s),
Flag::Longhand(s) => format!("--{}", s),
}
}
}
#[derive(new, Debug, Clone)]
pub struct ParsedCommand {
crate name: String,
crate args: Vec<Expression>,
}
#[derive(new, Debug)]
pub struct Pipeline {
crate commands: Vec<ParsedCommand>,
}
impl Pipeline {
crate fn from_parts(command: ParsedCommand, rest: Vec<ParsedCommand>) -> Pipeline {
let mut commands = vec![command];
commands.extend(rest);
Pipeline { commands }
}
}

View file

@ -4,7 +4,7 @@ crate use crate::context::Context;
crate use crate::env::host::handle_unexpected;
crate use crate::env::{Environment, Host};
crate use crate::errors::ShellError;
crate use crate::object::{Primitive, Value};
crate use crate::object::Value;
crate use crate::stream::{single_output, InputStream, OutputStream};
crate use futures::{FutureExt, SinkExt, StreamExt};
crate use std::collections::VecDeque;

View file

@ -1,9 +1,13 @@
use crate::prelude::*;
use derive_new::new;
use rustyline::completion::Completer;
use rustyline::completion::{self, FilenameCompleter};
use rustyline::line_buffer::LineBuffer;
#[derive(new)]
crate struct NuCompleter {
pub file_completer: FilenameCompleter,
pub commands: indexmap::IndexMap<String, Arc<dyn Command>>,
}
impl Completer for NuCompleter {
@ -15,10 +19,7 @@ impl Completer for NuCompleter {
pos: usize,
context: &rustyline::Context,
) -> rustyline::Result<(usize, Vec<completion::Pair>)> {
let commands = [
"ps", "ls", "cd", "view", "skip", "take", "select", "reject", "to-array", "where",
"sort-by",
];
let commands: Vec<String> = self.commands.keys().cloned().collect();
let mut completions = self.file_completer.complete(line, pos, context)?.1;

View file

@ -1,5 +1,6 @@
use crate::shell::completer::NuCompleter;
use crate::prelude::*;
use rustyline::completion::{self, Completer, FilenameCompleter};
use rustyline::error::ReadlineError;
use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
@ -13,10 +14,11 @@ crate struct Helper {
}
impl Helper {
crate fn new() -> Helper {
crate fn new(commands: indexmap::IndexMap<String, Arc<dyn Command>>) -> Helper {
Helper {
completer: NuCompleter {
file_completer: FilenameCompleter::new(),
commands,
},
highlighter: MatchingBracketHighlighter::new(),
hinter: HistoryHinter {},