mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
commit
e325a226c2
68 changed files with 4092 additions and 1148 deletions
474
Cargo.lock
generated
474
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
11
Cargo.toml
11
Cargo.toml
|
@ -9,8 +9,7 @@ edition = "2018"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
#rustyline = "4.1.0"
|
||||
rustyline = { git ="https://github.com/kkawakam/rustyline.git" }
|
||||
rustyline = "5.0.0"
|
||||
sysinfo = "0.8.6"
|
||||
chrono = { version = "0.4.6", features = ["serde"] }
|
||||
chrono-tz = "0.5.1"
|
||||
|
@ -19,7 +18,7 @@ prettytable-rs = "0.8.0"
|
|||
itertools = "0.8.0"
|
||||
ansi_term = "0.11.0"
|
||||
conch-parser = "0.1.1"
|
||||
nom = "5.0.0-beta1"
|
||||
nom = "5.0.0-beta2"
|
||||
dunce = "1.0.0"
|
||||
indexmap = { version = "1.0.2", features = ["serde-1"] }
|
||||
chrono-humanize = "0.0.11"
|
||||
|
@ -59,8 +58,12 @@ ctrlc = "3.1.3"
|
|||
ptree = "0.2"
|
||||
clipboard = "0.5"
|
||||
reqwest = "0.9"
|
||||
roxmltree = "0.6.0"
|
||||
roxmltree = "0.6.1"
|
||||
pretty = "0.5.2"
|
||||
nom_locate = { git = "https://github.com/wycats/nom_locate.git", branch = "nom5" }
|
||||
derive_more = "0.15.0"
|
||||
enum-utils = "0.1.0"
|
||||
unicode-xid = "0.1.0"
|
||||
serde_ini = "0.2.0"
|
||||
subprocess = "0.1.18"
|
||||
sys-info = "0.5.7"
|
||||
|
|
201
src/cli.rs
201
src/cli.rs
|
@ -11,14 +11,14 @@ use crate::commands::classified::{
|
|||
use crate::context::Context;
|
||||
crate use crate::errors::ShellError;
|
||||
use crate::evaluate::Scope;
|
||||
use crate::parser::parse2::span::Spanned;
|
||||
use crate::parser::registry;
|
||||
use crate::parser::{Pipeline, PipelineElement, TokenNode};
|
||||
|
||||
use crate::git::current_branch;
|
||||
use crate::object::Value;
|
||||
use crate::parser::ast::{Expression, Leaf, RawExpression};
|
||||
use crate::parser::lexer::Spanned;
|
||||
use crate::parser::{Args, Pipeline};
|
||||
|
||||
use log::debug;
|
||||
use log::{debug, trace};
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::{self, ColorMode, Config, Editor};
|
||||
|
||||
|
@ -62,19 +62,21 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
|
|||
command("from-xml", from_xml::from_xml),
|
||||
command("from-yaml", from_yaml::from_yaml),
|
||||
command("get", get::get),
|
||||
command("open", open::open),
|
||||
command("enter", enter::enter),
|
||||
command("exit", exit::exit),
|
||||
command("lines", lines::lines),
|
||||
command("pick", pick::pick),
|
||||
command("split-column", split_column::split_column),
|
||||
command("split-row", split_row::split_row),
|
||||
command("lines", lines::lines),
|
||||
command("reject", reject::reject),
|
||||
command("trim", trim::trim),
|
||||
command("to-array", to_array::to_array),
|
||||
command("to-ini", to_ini::to_ini),
|
||||
command("to-json", to_json::to_json),
|
||||
command("to-toml", to_toml::to_toml),
|
||||
command("sort-by", sort_by::sort_by),
|
||||
Arc::new(Open),
|
||||
Arc::new(Where),
|
||||
Arc::new(Config),
|
||||
Arc::new(SkipWhile),
|
||||
|
@ -154,45 +156,60 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
|
|||
|
||||
LineResult::Error(mut line, err) => {
|
||||
rl.add_history_entry(line.clone());
|
||||
match err {
|
||||
ShellError::Diagnostic(diag) => {
|
||||
let host = context.host.lock().unwrap();
|
||||
let writer = host.err_termcolor();
|
||||
line.push_str(" ");
|
||||
let files = crate::parser::span::Files::new(line);
|
||||
|
||||
language_reporting::emit(
|
||||
&mut writer.lock(),
|
||||
&files,
|
||||
&diag.diagnostic,
|
||||
&language_reporting::DefaultConfig,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
let diag = err.to_diagnostic();
|
||||
let host = context.host.lock().unwrap();
|
||||
let writer = host.err_termcolor();
|
||||
line.push_str(" ");
|
||||
let files = crate::parser::Files::new(line);
|
||||
|
||||
ShellError::TypeError(desc) => context
|
||||
.host
|
||||
.lock()
|
||||
.unwrap()
|
||||
.stdout(&format!("TypeError: {}", desc)),
|
||||
language_reporting::emit(
|
||||
&mut writer.lock(),
|
||||
&files,
|
||||
&diag,
|
||||
&language_reporting::DefaultConfig,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
ShellError::MissingProperty { subpath, .. } => context
|
||||
.host
|
||||
.lock()
|
||||
.unwrap()
|
||||
.stdout(&format!("Missing property {}", subpath)),
|
||||
// match err {
|
||||
// ShellError::Diagnostic(diag) => {
|
||||
// let host = context.host.lock().unwrap();
|
||||
// let writer = host.err_termcolor();
|
||||
// line.push_str(" ");
|
||||
// let files = crate::parser::Files::new(line);
|
||||
|
||||
ShellError::String(_) => {
|
||||
context.host.lock().unwrap().stdout(&format!("{}", err))
|
||||
}
|
||||
}
|
||||
// language_reporting::emit(
|
||||
// &mut writer.lock(),
|
||||
// &files,
|
||||
// &diag.diagnostic,
|
||||
// &language_reporting::DefaultConfig,
|
||||
// )
|
||||
// .unwrap();
|
||||
// }
|
||||
|
||||
// ShellError::TypeError(desc) => context
|
||||
// .host
|
||||
// .lock()
|
||||
// .unwrap()
|
||||
// .stdout(&format!("TypeError: {}", desc)),
|
||||
|
||||
// ShellError::MissingProperty { subpath, .. } => context
|
||||
// .host
|
||||
// .lock()
|
||||
// .unwrap()
|
||||
// .stdout(&format!("Missing property {}", subpath)),
|
||||
|
||||
// ShellError::String(_) => {
|
||||
// context.host.lock().unwrap().stdout(&format!("{}", err))
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
LineResult::Break => {
|
||||
break;
|
||||
}
|
||||
|
||||
LineResult::FatalError(err) => {
|
||||
LineResult::FatalError(_, err) => {
|
||||
context
|
||||
.host
|
||||
.lock()
|
||||
|
@ -214,24 +231,24 @@ enum LineResult {
|
|||
Break,
|
||||
|
||||
#[allow(unused)]
|
||||
FatalError(ShellError),
|
||||
FatalError(String, ShellError),
|
||||
}
|
||||
|
||||
impl std::ops::Try for LineResult {
|
||||
type Ok = Option<String>;
|
||||
type Error = ShellError;
|
||||
type Error = (String, ShellError);
|
||||
|
||||
fn into_result(self) -> Result<Option<String>, ShellError> {
|
||||
fn into_result(self) -> Result<Option<String>, (String, ShellError)> {
|
||||
match self {
|
||||
LineResult::Success(s) => Ok(Some(s)),
|
||||
LineResult::Error(_, s) => Err(s),
|
||||
LineResult::Error(string, err) => Err((string, err)),
|
||||
LineResult::Break => Ok(None),
|
||||
LineResult::CtrlC => Ok(None),
|
||||
LineResult::FatalError(err) => Err(err),
|
||||
LineResult::FatalError(string, err) => Err((string, err)),
|
||||
}
|
||||
}
|
||||
fn from_error(v: ShellError) -> Self {
|
||||
LineResult::Error(String::new(), v)
|
||||
fn from_error(v: (String, ShellError)) -> Self {
|
||||
LineResult::Error(v.0, v.1)
|
||||
}
|
||||
|
||||
fn from_ok(v: Option<String>) -> Self {
|
||||
|
@ -258,7 +275,8 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
|
|||
debug!("=== Parsed ===");
|
||||
debug!("{:#?}", result);
|
||||
|
||||
let mut pipeline = classify_pipeline(&result, ctx)?;
|
||||
let mut pipeline = classify_pipeline(&result, ctx, &Text::from(line))
|
||||
.map_err(|err| (line.clone(), err))?;
|
||||
|
||||
match pipeline.commands.last() {
|
||||
Some(ClassifiedCommand::Sink(_)) => {}
|
||||
|
@ -266,9 +284,9 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
|
|||
_ => pipeline.commands.push(ClassifiedCommand::Sink(SinkCommand {
|
||||
command: sink("autoview", autoview::autoview),
|
||||
name_span: None,
|
||||
args: Args {
|
||||
positional: vec![],
|
||||
named: indexmap::IndexMap::new(),
|
||||
args: registry::Args {
|
||||
positional: None,
|
||||
named: None,
|
||||
},
|
||||
})),
|
||||
}
|
||||
|
@ -371,14 +389,17 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
|
|||
}
|
||||
|
||||
fn classify_pipeline(
|
||||
pipeline: &Pipeline,
|
||||
pipeline: &TokenNode,
|
||||
context: &Context,
|
||||
source: &Text,
|
||||
) -> Result<ClassifiedPipeline, ShellError> {
|
||||
let commands: Result<Vec<_>, _> = pipeline
|
||||
.commands
|
||||
let pipeline = pipeline.as_pipeline()?;
|
||||
|
||||
let Pipeline { parts, .. } = pipeline;
|
||||
|
||||
let commands: Result<Vec<_>, ShellError> = parts
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|item| classify_command(&item, context))
|
||||
.map(|item| classify_command(&item, context, &source))
|
||||
.collect();
|
||||
|
||||
Ok(ClassifiedPipeline {
|
||||
|
@ -387,85 +408,79 @@ fn classify_pipeline(
|
|||
}
|
||||
|
||||
fn classify_command(
|
||||
command: &Expression,
|
||||
command: &PipelineElement,
|
||||
context: &Context,
|
||||
source: &Text,
|
||||
) -> Result<ClassifiedCommand, ShellError> {
|
||||
// let command_name = &command.name[..];
|
||||
// let args = &command.args;
|
||||
let call = command.call();
|
||||
|
||||
if let Expression {
|
||||
expr: RawExpression::Call(call),
|
||||
..
|
||||
} = command
|
||||
{
|
||||
match (&call.name, &call.args) {
|
||||
(
|
||||
Expression {
|
||||
expr: RawExpression::Leaf(Leaf::Bare(name)),
|
||||
span,
|
||||
},
|
||||
args,
|
||||
) => match context.has_command(&name.to_string()) {
|
||||
match call {
|
||||
call if call.head().is_bare() => {
|
||||
let head = call.head();
|
||||
let name = head.source(source);
|
||||
|
||||
match context.has_command(name) {
|
||||
true => {
|
||||
let command = context.get_command(&name.to_string());
|
||||
let command = context.get_command(name);
|
||||
let config = command.config();
|
||||
let scope = Scope::empty();
|
||||
|
||||
let args = match args {
|
||||
Some(args) => config.evaluate_args(args.iter(), &scope)?,
|
||||
None => Args::default(),
|
||||
};
|
||||
trace!("classifying {:?}", config);
|
||||
|
||||
let args = config.evaluate_args(call, context, &scope, source)?;
|
||||
|
||||
Ok(ClassifiedCommand::Internal(InternalCommand {
|
||||
command,
|
||||
name_span: Some(span.clone()),
|
||||
name_span: Some(head.span().clone()),
|
||||
args,
|
||||
}))
|
||||
}
|
||||
false => match context.has_sink(&name.to_string()) {
|
||||
false => match context.has_sink(name) {
|
||||
true => {
|
||||
let command = context.get_sink(&name.to_string());
|
||||
let command = context.get_sink(name);
|
||||
let config = command.config();
|
||||
let scope = Scope::empty();
|
||||
|
||||
let args = match args {
|
||||
Some(args) => config.evaluate_args(args.iter(), &scope)?,
|
||||
None => Args::default(),
|
||||
};
|
||||
let args = config.evaluate_args(call, context, &scope, source)?;
|
||||
|
||||
Ok(ClassifiedCommand::Sink(SinkCommand {
|
||||
command,
|
||||
name_span: Some(span.clone()),
|
||||
name_span: Some(head.span().clone()),
|
||||
args,
|
||||
}))
|
||||
}
|
||||
false => {
|
||||
let arg_list_strings: Vec<Spanned<String>> = match args {
|
||||
let arg_list_strings: Vec<Spanned<String>> = match call.children() {
|
||||
//Some(args) => args.iter().map(|i| i.as_external_arg(source)).collect(),
|
||||
Some(args) => args
|
||||
.iter()
|
||||
.map(|i| Spanned::from_item(i.as_external_arg(), i.span))
|
||||
.filter_map(|i| match i {
|
||||
TokenNode::Whitespace(_) => None,
|
||||
other => Some(Spanned::from_item(
|
||||
other.as_external_arg(source),
|
||||
other.span(),
|
||||
)),
|
||||
})
|
||||
.collect(),
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
Ok(ClassifiedCommand::External(ExternalCommand {
|
||||
name: name.to_string(),
|
||||
name_span: Some(span.clone()),
|
||||
name_span: Some(head.span().clone()),
|
||||
args: arg_list_strings,
|
||||
}))
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
(_, None) => Err(ShellError::string(
|
||||
"Unimplemented command that is just an expression (1)",
|
||||
)),
|
||||
(_, Some(_)) => Err(ShellError::string("Unimplemented dynamic command")),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::string(&format!(
|
||||
"Unimplemented command that is just an expression (2) -- {:?}",
|
||||
command
|
||||
)))
|
||||
|
||||
call => Err(ShellError::diagnostic(
|
||||
language_reporting::Diagnostic::new(
|
||||
language_reporting::Severity::Error,
|
||||
"Invalid command",
|
||||
)
|
||||
.with_label(language_reporting::Label::new_primary(call.head().span())),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,6 @@ crate mod where_;
|
|||
|
||||
crate use command::command;
|
||||
crate use config::Config;
|
||||
|
||||
crate use open::Open;
|
||||
crate use where_::Where;
|
||||
crate use skip_while::SkipWhile;
|
||||
|
|
|
@ -10,7 +10,8 @@ pub fn cd(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
match latest.obj {
|
||||
Value::Filesystem => {
|
||||
let cwd = latest.path().to_path_buf();
|
||||
let path = match args.positional.first() {
|
||||
|
||||
let path = match args.nth(0) {
|
||||
None => match dirs::home_dir() {
|
||||
Some(o) => o,
|
||||
_ => {
|
||||
|
@ -22,14 +23,14 @@ pub fn cd(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
}
|
||||
},
|
||||
Some(v) => {
|
||||
let target = v.as_string()?.clone();
|
||||
match dunce::canonicalize(cwd.join(&target).as_path()) {
|
||||
let target = v.as_string()?;
|
||||
match dunce::canonicalize(cwd.join(target).as_path()) {
|
||||
Ok(p) => p,
|
||||
Err(_) => {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
return Err(ShellError::labeled_error(
|
||||
"Can not change to directory",
|
||||
"directory not found",
|
||||
Some(args.positional[0].span.clone()),
|
||||
v.span.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -40,11 +41,11 @@ pub fn cd(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
match env::set_current_dir(&path) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
if args.positional.len() > 0 {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
if args.len() > 0 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Can not change to directory",
|
||||
"directory not found",
|
||||
Some(args.positional[0].span.clone()),
|
||||
args.nth(0).unwrap().span.clone(),
|
||||
));
|
||||
} else {
|
||||
return Err(ShellError::string("Can not change to directory"));
|
||||
|
@ -56,7 +57,7 @@ pub fn cd(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
}
|
||||
_ => {
|
||||
let mut stream = VecDeque::new();
|
||||
match args.positional.first() {
|
||||
match args.nth(0) {
|
||||
None => {
|
||||
stream.push_back(ReturnValue::change_cwd(PathBuf::from("/")));
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
use crate::commands::command::Sink;
|
||||
use crate::parser::ast::Expression;
|
||||
use crate::parser::lexer::{Span, Spanned};
|
||||
use crate::parser::registry::Args;
|
||||
use crate::parser::{registry::Args, Span, Spanned, TokenNode};
|
||||
use crate::prelude::*;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures::stream::StreamExt;
|
||||
use futures_codec::{Decoder, Encoder, Framed};
|
||||
use log::{log_enabled, trace};
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use subprocess::Exec;
|
||||
|
||||
/// A simple `Codec` implementation that splits up data into lines.
|
||||
pub struct LinesCodec {}
|
||||
|
||||
|
@ -80,7 +80,7 @@ crate struct ClassifiedPipeline {
|
|||
|
||||
crate enum ClassifiedCommand {
|
||||
#[allow(unused)]
|
||||
Expr(Expression),
|
||||
Expr(TokenNode),
|
||||
Internal(InternalCommand),
|
||||
Sink(SinkCommand),
|
||||
External(ExternalCommand),
|
||||
|
@ -110,18 +110,30 @@ impl InternalCommand {
|
|||
context: &mut Context,
|
||||
input: ClassifiedInputStream,
|
||||
) -> Result<InputStream, ShellError> {
|
||||
let mut result = context.run_command(
|
||||
self.command,
|
||||
self.name_span.clone(),
|
||||
self.args,
|
||||
input.objects,
|
||||
)?;
|
||||
let objects = if log_enabled!(log::Level::Trace) {
|
||||
trace!("->");
|
||||
trace!("{}", self.command.name());
|
||||
trace!("{:?}", self.args.debug());
|
||||
let objects: Vec<_> = input.objects.collect().await;
|
||||
trace!(
|
||||
"input = {:#?}",
|
||||
objects.iter().map(|o| o.debug()).collect::<Vec<_>>(),
|
||||
);
|
||||
VecDeque::from(objects).boxed()
|
||||
} else {
|
||||
input.objects
|
||||
};
|
||||
|
||||
let mut result =
|
||||
context.run_command(self.command, self.name_span.clone(), self.args, objects)?;
|
||||
|
||||
let mut stream = VecDeque::new();
|
||||
while let Some(item) = result.next().await {
|
||||
match item {
|
||||
ReturnValue::Value(Value::Error(err)) => {
|
||||
return Err(*err);
|
||||
}
|
||||
|
||||
ReturnValue::Action(action) => match action {
|
||||
CommandAction::ChangePath(path) => {
|
||||
context.env.lock().unwrap().back_mut().map(|x| {
|
||||
|
@ -177,10 +189,11 @@ impl ExternalCommand {
|
|||
) -> Result<ClassifiedInputStream, ShellError> {
|
||||
let inputs: Vec<Value> = input.objects.collect().await;
|
||||
|
||||
trace!("{:?} -> {}", inputs, self.name);
|
||||
|
||||
let mut arg_string = format!("{}", self.name);
|
||||
for arg in &self.args {
|
||||
arg_string.push_str(" ");
|
||||
arg_string.push_str(&arg.item);
|
||||
arg_string.push_str(&arg);
|
||||
}
|
||||
|
||||
let mut process;
|
||||
|
@ -191,6 +204,7 @@ impl ExternalCommand {
|
|||
|
||||
if arg_string.contains("$it") {
|
||||
let mut first = true;
|
||||
|
||||
for i in &inputs {
|
||||
if i.as_string().is_err() {
|
||||
let mut span = None;
|
||||
|
@ -217,6 +231,10 @@ impl ExternalCommand {
|
|||
}
|
||||
|
||||
for arg in &self.args {
|
||||
if arg.chars().all(|c| c.is_whitespace()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
process = process.arg(&arg.replace("$it", &i.as_string().unwrap()));
|
||||
}
|
||||
}
|
||||
|
@ -254,6 +272,10 @@ impl ExternalCommand {
|
|||
}
|
||||
|
||||
for arg in &self.args {
|
||||
if arg.chars().all(|c| c.is_whitespace()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
new_arg_string.push_str(" ");
|
||||
new_arg_string.push_str(&arg.replace("$it", &i.as_string().unwrap()));
|
||||
}
|
||||
|
|
|
@ -1,25 +1,53 @@
|
|||
use crate::errors::ShellError;
|
||||
use crate::object::Value;
|
||||
use crate::parser::lexer::Span;
|
||||
use crate::parser::lexer::Spanned;
|
||||
use crate::parser::CommandConfig;
|
||||
use crate::parser::{
|
||||
registry::{self, Args},
|
||||
Span, Spanned,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use getset::Getters;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Getters)]
|
||||
#[get = "crate"]
|
||||
pub struct CommandArgs {
|
||||
pub host: Arc<Mutex<dyn Host + Send>>,
|
||||
pub env: Arc<Mutex<VecDeque<Environment>>>,
|
||||
pub name_span: Option<Span>,
|
||||
pub positional: Vec<Spanned<Value>>,
|
||||
pub named: indexmap::IndexMap<String, Value>,
|
||||
pub args: Args,
|
||||
pub input: InputStream,
|
||||
}
|
||||
|
||||
impl CommandArgs {
|
||||
pub fn nth(&self, pos: usize) -> Option<&Spanned<Value>> {
|
||||
self.args.nth(pos)
|
||||
}
|
||||
|
||||
pub fn positional_iter(&self) -> impl Iterator<Item = &Spanned<Value>> {
|
||||
self.args.positional_iter()
|
||||
}
|
||||
|
||||
pub fn expect_nth(&self, pos: usize) -> Result<&Spanned<Value>, ShellError> {
|
||||
self.args.expect_nth(pos)
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.args.len()
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Option<&Spanned<Value>> {
|
||||
self.args.get(name)
|
||||
}
|
||||
|
||||
pub fn has(&self, name: &str) -> bool {
|
||||
self.args.has(name)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SinkCommandArgs {
|
||||
pub ctx: Context,
|
||||
pub name_span: Option<Span>,
|
||||
pub positional: Vec<Spanned<Value>>,
|
||||
pub named: indexmap::IndexMap<String, Value>,
|
||||
pub args: Args,
|
||||
pub input: Vec<Value>,
|
||||
}
|
||||
|
||||
|
@ -46,8 +74,8 @@ pub trait Command {
|
|||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError>;
|
||||
fn name(&self) -> &str;
|
||||
|
||||
fn config(&self) -> CommandConfig {
|
||||
CommandConfig {
|
||||
fn config(&self) -> registry::CommandConfig {
|
||||
registry::CommandConfig {
|
||||
name: self.name().to_string(),
|
||||
mandatory_positional: vec![],
|
||||
optional_positional: vec![],
|
||||
|
@ -61,8 +89,8 @@ pub trait Sink {
|
|||
fn run(&self, args: SinkCommandArgs) -> Result<(), ShellError>;
|
||||
fn name(&self) -> &str;
|
||||
|
||||
fn config(&self) -> CommandConfig {
|
||||
CommandConfig {
|
||||
fn config(&self) -> registry::CommandConfig {
|
||||
registry::CommandConfig {
|
||||
name: self.name().to_string(),
|
||||
mandatory_positional: vec![],
|
||||
optional_positional: vec![],
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use crate::errors::ShellError;
|
||||
use crate::object::config;
|
||||
use crate::object::Value;
|
||||
use crate::parser::registry::{NamedType, NamedValue};
|
||||
use crate::parser::CommandConfig;
|
||||
use crate::parser::registry::{CommandConfig, NamedType, NamedValue};
|
||||
use crate::prelude::*;
|
||||
use indexmap::IndexMap;
|
||||
use log::trace;
|
||||
|
@ -19,7 +18,7 @@ impl Command for Config {
|
|||
|
||||
fn config(&self) -> CommandConfig {
|
||||
let mut named: IndexMap<String, NamedType> = IndexMap::new();
|
||||
named.insert("set".to_string(), NamedType::Optional(NamedValue::Tuple));
|
||||
named.insert("set".to_string(), NamedType::Optional(NamedValue::Single));
|
||||
named.insert("get".to_string(), NamedType::Optional(NamedValue::Single));
|
||||
named.insert("clear".to_string(), NamedType::Switch);
|
||||
|
||||
|
@ -41,10 +40,10 @@ impl Command for Config {
|
|||
pub fn config(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let mut result = crate::object::config::config()?;
|
||||
|
||||
trace!("{:#?}", args.positional);
|
||||
trace!("{:#?}", args.named);
|
||||
trace!("{:#?}", args.args.positional);
|
||||
trace!("{:#?}", args.args.named);
|
||||
|
||||
if let Some(v) = args.named.get("get") {
|
||||
if let Some(v) = args.get("get") {
|
||||
let key = v.as_string()?;
|
||||
let value = result
|
||||
.get(&key)
|
||||
|
@ -56,9 +55,9 @@ pub fn config(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
);
|
||||
}
|
||||
|
||||
if let Some(v) = args.named.get("set") {
|
||||
if let Some(v) = args.get("set") {
|
||||
if let Ok((key, value)) = v.as_pair() {
|
||||
result.insert(key.as_string()?, value.clone());
|
||||
result.insert(key.as_string()?.to_string(), value.clone());
|
||||
|
||||
config::write_config(&result)?;
|
||||
|
||||
|
@ -71,7 +70,7 @@ pub fn config(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(_) = args.named.get("clear") {
|
||||
if let Some(_) = args.get("clear") {
|
||||
result.clear();
|
||||
|
||||
config::write_config(&result)?;
|
||||
|
@ -84,7 +83,7 @@ pub fn config(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
);
|
||||
}
|
||||
|
||||
if let Some(v) = args.named.get("remove") {
|
||||
if let Some(v) = args.get("remove") {
|
||||
let key = v.as_string()?;
|
||||
|
||||
if result.contains_key(&key) {
|
||||
|
@ -104,7 +103,7 @@ pub fn config(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
);
|
||||
}
|
||||
|
||||
if args.positional.len() == 0 {
|
||||
if args.len() == 0 {
|
||||
return Ok(
|
||||
futures::stream::once(futures::future::ready(ReturnValue::Value(Value::Object(
|
||||
result.into(),
|
||||
|
|
|
@ -1,32 +1,35 @@
|
|||
use crate::commands::command::CommandAction;
|
||||
use crate::errors::ShellError;
|
||||
use crate::object::{Primitive, Value};
|
||||
use crate::parser::lexer::Spanned;
|
||||
use crate::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn enter(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.positional.len() == 0 {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
"open requires a path or url",
|
||||
"missing path",
|
||||
args.name_span,
|
||||
));
|
||||
}
|
||||
let path = match args.nth(0) {
|
||||
None => {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
"open requires a path or url",
|
||||
"missing path",
|
||||
args.name_span,
|
||||
))
|
||||
}
|
||||
Some(p) => p,
|
||||
};
|
||||
|
||||
let span = args.name_span;
|
||||
|
||||
let cwd = args
|
||||
.env
|
||||
.env()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.front()
|
||||
.unwrap()
|
||||
.path()
|
||||
.to_path_buf();
|
||||
|
||||
let mut full_path = PathBuf::from(cwd);
|
||||
|
||||
let (file_extension, contents) = match &args.positional[0].item {
|
||||
let (file_extension, contents) = match path.item() {
|
||||
Value::Primitive(Primitive::String(s)) => {
|
||||
if s.starts_with("http:") || s.starts_with("https:") {
|
||||
let response = reqwest::get(s);
|
||||
|
@ -49,7 +52,7 @@ pub fn enter(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
return Err(ShellError::labeled_error(
|
||||
"Web page contents corrupt",
|
||||
"received garbled data",
|
||||
args.positional[0].span,
|
||||
args.expect_nth(0)?.span,
|
||||
));
|
||||
}
|
||||
},
|
||||
|
@ -57,12 +60,12 @@ pub fn enter(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
return Err(ShellError::labeled_error(
|
||||
"URL could not be opened",
|
||||
"url not found",
|
||||
args.positional[0].span,
|
||||
args.expect_nth(0)?.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
full_path.push(Path::new(&s));
|
||||
full_path.push(Path::new(s));
|
||||
match std::fs::read_to_string(&full_path) {
|
||||
Ok(s) => (
|
||||
full_path
|
||||
|
@ -74,7 +77,7 @@ pub fn enter(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
return Err(ShellError::labeled_error(
|
||||
"File cound not be opened",
|
||||
"file not found",
|
||||
args.positional[0].span,
|
||||
args.expect_nth(0)?.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -84,39 +87,38 @@ pub fn enter(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
return Err(ShellError::labeled_error(
|
||||
"Expected string value for filename",
|
||||
"expected filename",
|
||||
args.positional[0].span,
|
||||
args.expect_nth(0)?.span,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let mut stream = VecDeque::new();
|
||||
|
||||
let file_extension = match args.positional.get(1) {
|
||||
Some(Spanned {
|
||||
item: Value::Primitive(Primitive::String(s)),
|
||||
span,
|
||||
}) => {
|
||||
if s == "--raw" {
|
||||
None
|
||||
} else if s == "--json" {
|
||||
Some("json".to_string())
|
||||
} else if s == "--xml" {
|
||||
Some("xml".to_string())
|
||||
} else if s == "--ini" {
|
||||
Some("ini".to_string())
|
||||
} else if s == "--yaml" {
|
||||
Some("yaml".to_string())
|
||||
} else if s == "--toml" {
|
||||
Some("toml".to_string())
|
||||
} else {
|
||||
let file_extension = if args.has("raw") {
|
||||
None
|
||||
} else if args.has("json") {
|
||||
Some("json".to_string())
|
||||
} else if args.has("xml") {
|
||||
Some("xml".to_string())
|
||||
} else if args.has("ini") {
|
||||
Some("ini".to_string())
|
||||
} else if args.has("yaml") {
|
||||
Some("yaml".to_string())
|
||||
} else if args.has("toml") {
|
||||
Some("toml".to_string())
|
||||
} else {
|
||||
if let Some(ref named_args) = args.args.named {
|
||||
for named in named_args.iter() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Unknown flag for open",
|
||||
"Unknown flag for enter",
|
||||
"unknown flag",
|
||||
span.clone(),
|
||||
named.1.span.clone(),
|
||||
));
|
||||
}
|
||||
file_extension
|
||||
} else {
|
||||
file_extension
|
||||
}
|
||||
_ => file_extension,
|
||||
};
|
||||
|
||||
match file_extension {
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::prelude::*;
|
|||
// TODO: "Amount remaining" wrapper
|
||||
|
||||
pub fn first(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.positional.len() == 0 {
|
||||
if args.len() == 0 {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
"First requires an amount",
|
||||
"needs parameter",
|
||||
|
@ -12,7 +12,7 @@ pub fn first(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
));
|
||||
}
|
||||
|
||||
let amount = args.positional[0].as_i64();
|
||||
let amount = args.expect_nth(0)?.as_i64();
|
||||
|
||||
let amount = match amount {
|
||||
Ok(o) => o,
|
||||
|
@ -20,7 +20,7 @@ pub fn first(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
return Err(ShellError::labeled_error(
|
||||
"Value is not a number",
|
||||
"expected integer",
|
||||
args.positional[0].span,
|
||||
args.expect_nth(0)?.span,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
|
|
@ -4,12 +4,12 @@ use crate::prelude::*;
|
|||
|
||||
fn convert_json_value_to_nu_value(v: &serde_hjson::Value) -> Value {
|
||||
match v {
|
||||
serde_hjson::Value::Null => Value::Primitive(Primitive::String("".to_string())),
|
||||
serde_hjson::Value::Null => Value::Primitive(Primitive::String(String::from(""))),
|
||||
serde_hjson::Value::Bool(b) => Value::Primitive(Primitive::Boolean(*b)),
|
||||
serde_hjson::Value::F64(n) => Value::Primitive(Primitive::Float(OF64::from(*n))),
|
||||
serde_hjson::Value::U64(n) => Value::Primitive(Primitive::Int(*n as i64)),
|
||||
serde_hjson::Value::I64(n) => Value::Primitive(Primitive::Int(*n as i64)),
|
||||
serde_hjson::Value::String(s) => Value::Primitive(Primitive::String(s.clone())),
|
||||
serde_hjson::Value::String(s) => Value::Primitive(Primitive::String(String::from(s))),
|
||||
serde_hjson::Value::Array(a) => Value::List(
|
||||
a.iter()
|
||||
.map(|x| convert_json_value_to_nu_value(x))
|
||||
|
|
|
@ -7,7 +7,7 @@ fn convert_toml_value_to_nu_value(v: &toml::Value) -> Value {
|
|||
toml::Value::Boolean(b) => Value::Primitive(Primitive::Boolean(*b)),
|
||||
toml::Value::Integer(n) => Value::Primitive(Primitive::Int(*n)),
|
||||
toml::Value::Float(n) => Value::Primitive(Primitive::Float(OF64::from(*n))),
|
||||
toml::Value::String(s) => Value::Primitive(Primitive::String(s.clone())),
|
||||
toml::Value::String(s) => Value::Primitive(Primitive::String(String::from(s))),
|
||||
toml::Value::Array(a) => Value::List(
|
||||
a.iter()
|
||||
.map(|x| convert_toml_value_to_nu_value(x))
|
||||
|
|
|
@ -32,13 +32,13 @@ fn from_node_to_value<'a, 'd>(n: &roxmltree::Node<'a, 'd>) -> Value {
|
|||
|
||||
Value::Object(collected)
|
||||
} else if n.is_comment() {
|
||||
Value::Primitive(Primitive::String("<comment>".to_string()))
|
||||
Value::string("<comment>")
|
||||
} else if n.is_pi() {
|
||||
Value::Primitive(Primitive::String("<processing_instruction>".to_string()))
|
||||
Value::string("<processing_instruction>")
|
||||
} else if n.is_text() {
|
||||
Value::Primitive(Primitive::String(n.text().unwrap().to_string()))
|
||||
Value::string(n.text().unwrap())
|
||||
} else {
|
||||
Value::Primitive(Primitive::String("<unknown>".to_string()))
|
||||
Value::string("<unknown>")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ fn convert_yaml_value_to_nu_value(v: &serde_yaml::Value) -> Value {
|
|||
serde_yaml::Value::Number(n) if n.is_f64() => {
|
||||
Value::Primitive(Primitive::Float(OF64::from(n.as_f64().unwrap())))
|
||||
}
|
||||
serde_yaml::Value::String(s) => Value::Primitive(Primitive::String(s.clone())),
|
||||
serde_yaml::Value::String(s) => Value::string(s),
|
||||
serde_yaml::Value::Sequence(a) => Value::List(
|
||||
a.iter()
|
||||
.map(|x| convert_yaml_value_to_nu_value(x))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::errors::ShellError;
|
||||
use crate::object::Value;
|
||||
use crate::parser::lexer::Span;
|
||||
use crate::parser::Span;
|
||||
use crate::prelude::*;
|
||||
|
||||
fn get_member(path: &str, span: Span, obj: &Value) -> Option<Value> {
|
||||
|
@ -22,7 +22,7 @@ fn get_member(path: &str, span: Span, obj: &Value) -> Option<Value> {
|
|||
}
|
||||
|
||||
pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.positional.len() == 0 {
|
||||
if args.len() == 0 {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
"Get requires a field or field path",
|
||||
"needs parameter",
|
||||
|
@ -30,7 +30,7 @@ pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
));
|
||||
}
|
||||
|
||||
let amount = args.positional[0].as_i64();
|
||||
let amount = args.expect_nth(0)?.as_i64();
|
||||
|
||||
// If it's a number, get the row instead of the column
|
||||
if let Ok(amount) = amount {
|
||||
|
@ -43,10 +43,10 @@ pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
}
|
||||
|
||||
let fields: Result<Vec<(String, Span)>, _> = args
|
||||
.positional
|
||||
.iter()
|
||||
.positional_iter()
|
||||
.map(|a| (a.as_string().map(|x| (x, a.span))))
|
||||
.collect();
|
||||
|
||||
let fields = fields?;
|
||||
|
||||
let stream = args
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use crate::errors::ShellError;
|
||||
use crate::object::{Primitive, Value};
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
|
||||
// TODO: "Amount remaining" wrapper
|
||||
|
||||
pub fn lines(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let input = args.input;
|
||||
|
@ -11,10 +14,12 @@ pub fn lines(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
Value::Primitive(Primitive::String(s)) => {
|
||||
let split_result: Vec<_> = s.lines().filter(|s| s.trim() != "").collect();
|
||||
|
||||
trace!("split result = {:?}", split_result);
|
||||
|
||||
let mut result = VecDeque::new();
|
||||
for s in split_result {
|
||||
result.push_back(ReturnValue::Value(Value::Primitive(Primitive::String(
|
||||
s.to_string(),
|
||||
s.into(),
|
||||
))));
|
||||
}
|
||||
result
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::errors::ShellError;
|
||||
use crate::object::{dir_entry_dict, Primitive, Value};
|
||||
use crate::parser::lexer::Spanned;
|
||||
use crate::parser::Spanned;
|
||||
use crate::prelude::*;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -10,11 +10,11 @@ pub fn ls(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
let path = env.back().unwrap().path.to_path_buf();
|
||||
let obj = &env.back().unwrap().obj;
|
||||
let mut full_path = PathBuf::from(path);
|
||||
match &args.positional.get(0) {
|
||||
match &args.nth(0) {
|
||||
Some(Spanned {
|
||||
item: Value::Primitive(Primitive::String(s)),
|
||||
..
|
||||
}) => full_path.push(Path::new(s)),
|
||||
}) => full_path.push(Path::new(&s)),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ pub fn ls(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
|
||||
let entries = match entries {
|
||||
Err(e) => {
|
||||
if let Some(s) = args.positional.get(0) {
|
||||
if let Some(s) = args.nth(0) {
|
||||
return Err(ShellError::labeled_error(
|
||||
e.to_string(),
|
||||
e.to_string(),
|
||||
|
@ -97,7 +97,7 @@ pub fn ls(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
return Err(ShellError::maybe_labeled_error(
|
||||
"Index not closed",
|
||||
format!("path missing closing ']'"),
|
||||
if args.positional.len() > 0 { Some(args.positional[0].span) } else { args.name_span },
|
||||
if args.len() > 0 { Some(args.nth(0).unwrap().span) } else { args.name_span },
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,36 @@
|
|||
use crate::errors::ShellError;
|
||||
use crate::object::{Primitive, Value};
|
||||
use crate::parser::lexer::Spanned;
|
||||
use crate::parser::registry::{CommandConfig, NamedType};
|
||||
use crate::prelude::*;
|
||||
use indexmap::IndexMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn open(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.positional.len() == 0 {
|
||||
pub struct Open;
|
||||
|
||||
impl Command for Open {
|
||||
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
open(args)
|
||||
}
|
||||
fn name(&self) -> &str {
|
||||
"open"
|
||||
}
|
||||
|
||||
fn config(&self) -> CommandConfig {
|
||||
let mut named: IndexMap<String, NamedType> = IndexMap::new();
|
||||
named.insert("raw".to_string(), NamedType::Switch);
|
||||
|
||||
CommandConfig {
|
||||
name: self.name().to_string(),
|
||||
mandatory_positional: vec![],
|
||||
optional_positional: vec![],
|
||||
rest_positional: false,
|
||||
named,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.len() == 0 {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
"Open requires a path or url",
|
||||
"needs path or url",
|
||||
|
@ -25,7 +50,7 @@ pub fn open(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
.to_path_buf();
|
||||
let mut full_path = PathBuf::from(cwd);
|
||||
|
||||
let (file_extension, contents) = match &args.positional[0].item {
|
||||
let (file_extension, contents) = match &args.expect_nth(0)?.item {
|
||||
Value::Primitive(Primitive::String(s)) => {
|
||||
if s.starts_with("http:") || s.starts_with("https:") {
|
||||
let response = reqwest::get(s);
|
||||
|
@ -48,7 +73,7 @@ pub fn open(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
return Err(ShellError::labeled_error(
|
||||
"Web page contents corrupt",
|
||||
"received garbled data",
|
||||
args.positional[0].span,
|
||||
args.expect_nth(0)?.span,
|
||||
));
|
||||
}
|
||||
},
|
||||
|
@ -56,12 +81,12 @@ pub fn open(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
return Err(ShellError::labeled_error(
|
||||
"URL could not be opened",
|
||||
"url not found",
|
||||
args.positional[0].span,
|
||||
args.expect_nth(0)?.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
full_path.push(Path::new(&s));
|
||||
full_path.push(Path::new(s));
|
||||
match std::fs::read_to_string(&full_path) {
|
||||
Ok(s) => (
|
||||
full_path
|
||||
|
@ -71,9 +96,9 @@ pub fn open(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
),
|
||||
Err(_) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"File could not be opened",
|
||||
"could not be opened",
|
||||
args.positional[0].span,
|
||||
"File cound not be opened",
|
||||
"file not found",
|
||||
args.expect_nth(0)?.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -83,41 +108,39 @@ pub fn open(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
return Err(ShellError::labeled_error(
|
||||
"Expected string value for filename",
|
||||
"expected filename",
|
||||
args.positional[0].span,
|
||||
args.expect_nth(0)?.span,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let mut stream = VecDeque::new();
|
||||
|
||||
let file_extension = match args.positional.get(1) {
|
||||
Some(Spanned {
|
||||
item: Value::Primitive(Primitive::String(s)),
|
||||
span,
|
||||
}) => {
|
||||
if s == "--raw" {
|
||||
None
|
||||
} else if s == "--json" {
|
||||
Some("json".to_string())
|
||||
} else if s == "--xml" {
|
||||
Some("xml".to_string())
|
||||
} else if s == "--ini" {
|
||||
Some("ini".to_string())
|
||||
} else if s == "--yaml" {
|
||||
Some("yaml".to_string())
|
||||
} else if s == "--toml" {
|
||||
Some("toml".to_string())
|
||||
} else {
|
||||
let file_extension = if args.has("raw") {
|
||||
None
|
||||
} else if args.has("json") {
|
||||
Some("json".to_string())
|
||||
} else if args.has("xml") {
|
||||
Some("xml".to_string())
|
||||
} else if args.has("ini") {
|
||||
Some("ini".to_string())
|
||||
} else if args.has("yaml") {
|
||||
Some("yaml".to_string())
|
||||
} else if args.has("toml") {
|
||||
Some("toml".to_string())
|
||||
} else {
|
||||
if let Some(ref named_args) = args.args.named {
|
||||
for named in named_args.iter() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Unknown flag for open",
|
||||
"unknown flag",
|
||||
span.clone(),
|
||||
named.1.span.clone(),
|
||||
));
|
||||
}
|
||||
file_extension
|
||||
} else {
|
||||
file_extension
|
||||
}
|
||||
_ => file_extension,
|
||||
};
|
||||
|
||||
match file_extension {
|
||||
Some(x) if x == "toml" => {
|
||||
stream.push_back(ReturnValue::Value(
|
||||
|
@ -198,9 +221,7 @@ pub fn open(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
));
|
||||
}
|
||||
_ => {
|
||||
stream.push_back(ReturnValue::Value(Value::Primitive(Primitive::String(
|
||||
contents,
|
||||
))));
|
||||
stream.push_back(ReturnValue::Value(Value::string(contents)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::object::Value;
|
|||
use crate::prelude::*;
|
||||
|
||||
pub fn pick(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.positional.len() == 0 {
|
||||
if args.len() == 0 {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
"Pick requires fields",
|
||||
"needs parameter",
|
||||
|
@ -12,7 +12,7 @@ pub fn pick(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
));
|
||||
}
|
||||
|
||||
let fields: Result<Vec<String>, _> = args.positional.iter().map(|a| a.as_string()).collect();
|
||||
let fields: Result<Vec<String>, _> = args.positional_iter().map(|a| a.as_string()).collect();
|
||||
let fields = fields?;
|
||||
|
||||
let objects = args
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::object::Value;
|
|||
use crate::prelude::*;
|
||||
|
||||
pub fn reject(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.positional.len() == 0 {
|
||||
if args.len() == 0 {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
"Reject requires fields",
|
||||
"needs parameter",
|
||||
|
@ -12,7 +12,7 @@ pub fn reject(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
));
|
||||
}
|
||||
|
||||
let fields: Result<Vec<String>, _> = args.positional.iter().map(|a| a.as_string()).collect();
|
||||
let fields: Result<Vec<String>, _> = args.positional_iter().map(|a| a.as_string()).collect();
|
||||
let fields = fields?;
|
||||
|
||||
let stream = args
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use crate::commands::command::SinkCommandArgs;
|
||||
use crate::errors::ShellError;
|
||||
use crate::object::{Primitive, Value};
|
||||
use crate::parser::lexer::Spanned;
|
||||
use crate::parser::Spanned;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn save(args: SinkCommandArgs) -> Result<(), ShellError> {
|
||||
if args.positional.len() == 0 {
|
||||
if args.args.positional.is_none() {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
"Save requires a filepath",
|
||||
"needs path",
|
||||
|
@ -13,6 +13,11 @@ pub fn save(args: SinkCommandArgs) -> Result<(), ShellError> {
|
|||
));
|
||||
}
|
||||
|
||||
let positional = match args.args.positional {
|
||||
None => return Err(ShellError::string("save requires a filepath")),
|
||||
Some(p) => p,
|
||||
};
|
||||
|
||||
let cwd = args
|
||||
.ctx
|
||||
.env
|
||||
|
@ -23,12 +28,12 @@ pub fn save(args: SinkCommandArgs) -> Result<(), ShellError> {
|
|||
.path()
|
||||
.to_path_buf();
|
||||
let mut full_path = PathBuf::from(cwd);
|
||||
match &(args.positional[0].item) {
|
||||
match &(positional[0].item) {
|
||||
Value::Primitive(Primitive::String(s)) => full_path.push(Path::new(s)),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let save_raw = match args.positional.get(1) {
|
||||
let save_raw = match positional.get(1) {
|
||||
Some(Spanned {
|
||||
item: Value::Primitive(Primitive::String(s)),
|
||||
..
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::fs::File;
|
|||
use std::io::prelude::*;
|
||||
|
||||
pub fn size(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.positional.len() == 0 {
|
||||
if args.len() == 0 {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
"Size requires a filepath",
|
||||
"needs path",
|
||||
|
@ -25,7 +25,7 @@ pub fn size(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
let mut contents = String::new();
|
||||
|
||||
let mut list = VecDeque::new();
|
||||
for name in args.positional {
|
||||
for name in args.positional_iter() {
|
||||
let name = name.as_string()?;
|
||||
let path = cwd.join(&name);
|
||||
let mut file = File::open(path)?;
|
||||
|
@ -63,7 +63,7 @@ fn count(name: &str, contents: &str) -> ReturnValue {
|
|||
}
|
||||
|
||||
let mut dict = Dictionary::default();
|
||||
dict.add("name", Value::string(name.to_owned()));
|
||||
dict.add("name", Value::string(name));
|
||||
dict.add("lines", Value::int(lines));
|
||||
dict.add("words", Value::int(words));
|
||||
dict.add("chars", Value::int(chars));
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::errors::ShellError;
|
|||
use crate::prelude::*;
|
||||
|
||||
pub fn skip(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.positional.len() == 0 {
|
||||
if args.len() == 0 {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
"Skip requires an amount",
|
||||
"needs parameter",
|
||||
|
@ -10,7 +10,7 @@ pub fn skip(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
));
|
||||
}
|
||||
|
||||
let amount = args.positional[0].as_i64();
|
||||
let amount = args.expect_nth(0)?.as_i64();
|
||||
|
||||
let amount = match amount {
|
||||
Ok(o) => o,
|
||||
|
@ -18,7 +18,7 @@ pub fn skip(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
return Err(ShellError::labeled_error(
|
||||
"Value is not a number",
|
||||
"expected integer",
|
||||
args.positional[0].span,
|
||||
args.expect_nth(0)?.span,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::errors::ShellError;
|
||||
use crate::parser::registry::CommandConfig;
|
||||
use crate::parser::registry::PositionalType;
|
||||
use crate::parser::CommandConfig;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct SkipWhile;
|
||||
|
@ -25,7 +25,7 @@ impl Command for SkipWhile {
|
|||
}
|
||||
|
||||
pub fn skip_while(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.positional.len() == 0 {
|
||||
if args.len() == 0 {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
"Where requires a condition",
|
||||
"needs condition",
|
||||
|
@ -33,7 +33,7 @@ pub fn skip_while(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
));
|
||||
}
|
||||
|
||||
let block = args.positional[0].as_block()?;
|
||||
let block = args.nth(0).unwrap().as_block()?;
|
||||
let input = args.input;
|
||||
|
||||
let objects = input.skip_while(move |item| {
|
||||
|
@ -48,4 +48,4 @@ pub fn skip_while(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
});
|
||||
|
||||
Ok(objects.map(|x| ReturnValue::Value(x)).boxed())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::errors::ShellError;
|
|||
use crate::prelude::*;
|
||||
|
||||
pub fn sort_by(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let fields: Result<Vec<_>, _> = args.positional.iter().map(|a| a.as_string()).collect();
|
||||
let fields: Result<Vec<_>, _> = args.positional_iter().map(|a| a.as_string()).collect();
|
||||
let fields = fields?;
|
||||
|
||||
let output = args.input.collect::<Vec<_>>();
|
||||
|
|
|
@ -6,56 +6,55 @@ use log::trace;
|
|||
// TODO: "Amount remaining" wrapper
|
||||
|
||||
pub fn split_column(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.positional.len() == 0 {
|
||||
let positional: Vec<_> = args.positional_iter().cloned().collect();
|
||||
let span = args.name_span;
|
||||
|
||||
if positional.len() == 0 {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
"Split-column needs more information",
|
||||
"needs parameter (eg split-column \",\")",
|
||||
args.name_span,
|
||||
));
|
||||
}
|
||||
|
||||
let input = args.input;
|
||||
let span = args.name_span;
|
||||
let args = args.positional;
|
||||
|
||||
Ok(input
|
||||
.map(move |v| match v {
|
||||
Value::Primitive(Primitive::String(s)) => {
|
||||
let splitter = args[0].as_string().unwrap().replace("\\n", "\n");
|
||||
let splitter = positional[0].as_string().unwrap().replace("\\n", "\n");
|
||||
trace!("splitting with {:?}", splitter);
|
||||
let split_result: Vec<_> = s.split(&splitter).filter(|s| s.trim() != "").collect();
|
||||
|
||||
trace!("split result = {:?}", split_result);
|
||||
|
||||
// If they didn't provide column names, make up our own
|
||||
if (args.len() - 1) == 0 {
|
||||
if (positional.len() - 1) == 0 {
|
||||
let mut gen_columns = vec![];
|
||||
for i in 0..split_result.len() {
|
||||
gen_columns.push(format!("Column{}", i + 1));
|
||||
}
|
||||
|
||||
let mut dict = crate::object::Dictionary::default();
|
||||
for (k, v) in split_result.iter().zip(gen_columns.iter()) {
|
||||
dict.add(
|
||||
v.clone(),
|
||||
Value::Primitive(Primitive::String(k.to_string())),
|
||||
);
|
||||
for (&k, v) in split_result.iter().zip(gen_columns.iter()) {
|
||||
dict.add(v.clone(), Value::Primitive(Primitive::String(k.into())));
|
||||
}
|
||||
ReturnValue::Value(Value::Object(dict))
|
||||
} else if split_result.len() == (args.len() - 1) {
|
||||
} else if split_result.len() == (positional.len() - 1) {
|
||||
let mut dict = crate::object::Dictionary::default();
|
||||
for (k, v) in split_result.iter().zip(args.iter().skip(1)) {
|
||||
for (&k, v) in split_result.iter().zip(positional.iter().skip(1)) {
|
||||
dict.add(
|
||||
v.as_string().unwrap(),
|
||||
Value::Primitive(Primitive::String(k.to_string())),
|
||||
Value::Primitive(Primitive::String(k.into())),
|
||||
);
|
||||
}
|
||||
ReturnValue::Value(Value::Object(dict))
|
||||
} else {
|
||||
let mut dict = crate::object::Dictionary::default();
|
||||
for k in args.iter().skip(1) {
|
||||
for k in positional.iter().skip(1) {
|
||||
dict.add(
|
||||
k.as_string().unwrap().trim(),
|
||||
Value::Primitive(Primitive::String("".to_string())),
|
||||
Value::Primitive(Primitive::String("".into())),
|
||||
);
|
||||
}
|
||||
ReturnValue::Value(Value::Object(dict))
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
use crate::errors::ShellError;
|
||||
use crate::object::{Primitive, Value};
|
||||
use crate::parser::Spanned;
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
|
||||
// TODO: "Amount remaining" wrapper
|
||||
|
||||
pub fn split_row(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.positional.len() == 0 {
|
||||
let positional: Vec<Spanned<Value>> = args.positional_iter().cloned().collect();
|
||||
let span = args.name_span;
|
||||
|
||||
if positional.len() == 0 {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
"Split-row needs more information",
|
||||
"needs parameter (eg split-row \"\\n\")",
|
||||
|
@ -15,13 +19,11 @@ pub fn split_row(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
}
|
||||
|
||||
let input = args.input;
|
||||
let span = args.name_span;
|
||||
let args = args.positional;
|
||||
|
||||
let stream = input
|
||||
.map(move |v| match v {
|
||||
Value::Primitive(Primitive::String(s)) => {
|
||||
let splitter = args[0].as_string().unwrap().replace("\\n", "\n");
|
||||
let splitter = positional[0].as_string().unwrap().replace("\\n", "\n");
|
||||
trace!("splitting with {:?}", splitter);
|
||||
let split_result: Vec<_> = s.split(&splitter).filter(|s| s.trim() != "").collect();
|
||||
|
||||
|
@ -30,7 +32,7 @@ pub fn split_row(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
let mut result = VecDeque::new();
|
||||
for s in split_result {
|
||||
result.push_back(ReturnValue::Value(Value::Primitive(Primitive::String(
|
||||
s.to_string(),
|
||||
s.into(),
|
||||
))));
|
||||
}
|
||||
result
|
||||
|
|
|
@ -11,7 +11,7 @@ pub fn trim(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
Ok(input
|
||||
.map(move |v| match v {
|
||||
Value::Primitive(Primitive::String(s)) => {
|
||||
ReturnValue::Value(Value::Primitive(Primitive::String(s.trim().to_string())))
|
||||
ReturnValue::Value(Value::Primitive(Primitive::String(s.trim().into())))
|
||||
}
|
||||
_ => ReturnValue::Value(Value::Error(Box::new(ShellError::maybe_labeled_error(
|
||||
"Expected string values from pipeline",
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::prelude::*;
|
|||
use prettyprint::PrettyPrinter;
|
||||
|
||||
pub fn view(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.positional.len() == 0 {
|
||||
if args.len() == 0 {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
"View requires a filename",
|
||||
"needs parameter",
|
||||
|
@ -11,7 +11,7 @@ pub fn view(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
));
|
||||
}
|
||||
|
||||
let target = match args.positional[0].as_string() {
|
||||
let target = match args.expect_nth(0)?.as_string() {
|
||||
Ok(s) => s.clone(),
|
||||
Err(e) => {
|
||||
if let Some(span) = args.name_span {
|
||||
|
@ -42,7 +42,7 @@ pub fn view(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
.build()
|
||||
.map_err(|e| ShellError::string(e))?;
|
||||
|
||||
let file = cwd.join(target);
|
||||
let file = cwd.join(&target);
|
||||
|
||||
let _ = printer.file(file.display().to_string());
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::errors::ShellError;
|
||||
use crate::parser::registry::PositionalType;
|
||||
use crate::parser::CommandConfig;
|
||||
use crate::parser::registry::{CommandConfig, PositionalType};
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Where;
|
||||
|
@ -25,7 +24,7 @@ impl Command for Where {
|
|||
}
|
||||
|
||||
pub fn r#where(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||
if args.positional.len() == 0 {
|
||||
if args.len() == 0 {
|
||||
return Err(ShellError::maybe_labeled_error(
|
||||
"Where requires a condition",
|
||||
"needs condition",
|
||||
|
@ -33,7 +32,7 @@ pub fn r#where(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
));
|
||||
}
|
||||
|
||||
let block = args.positional[0].as_block()?;
|
||||
let block = args.expect_nth(0)?.as_block()?;
|
||||
let input = args.input;
|
||||
|
||||
let objects = input.filter_map(move |item| {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use crate::commands::command::Sink;
|
||||
use crate::commands::command::SinkCommandArgs;
|
||||
use crate::parser::lexer::Span;
|
||||
use crate::parser::Args;
|
||||
use crate::commands::command::{Sink, SinkCommandArgs};
|
||||
use crate::parser::{
|
||||
registry::{Args, CommandConfig, CommandRegistry},
|
||||
Span,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
|
@ -58,8 +59,7 @@ impl Context {
|
|||
let command_args = SinkCommandArgs {
|
||||
ctx: self.clone(),
|
||||
name_span,
|
||||
positional: args.positional,
|
||||
named: args.named,
|
||||
args,
|
||||
input,
|
||||
};
|
||||
|
||||
|
@ -89,11 +89,16 @@ impl Context {
|
|||
host: self.host.clone(),
|
||||
env: self.env.clone(),
|
||||
name_span,
|
||||
positional: args.positional,
|
||||
named: args.named,
|
||||
args,
|
||||
input,
|
||||
};
|
||||
|
||||
command.run(command_args)
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandRegistry for Context {
|
||||
fn get(&self, name: &str) -> Option<CommandConfig> {
|
||||
self.commands.get(name).map(|c| c.config())
|
||||
}
|
||||
}
|
||||
|
|
137
src/errors.rs
137
src/errors.rs
|
@ -1,40 +1,89 @@
|
|||
use crate::parser::lexer::{Span, SpannedToken};
|
||||
#[allow(unused)]
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::parser::{Span, Spanned};
|
||||
use derive_new::new;
|
||||
use language_reporting::{Diagnostic, Label, Severity};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
|
||||
pub enum Description {
|
||||
Source(Spanned<String>),
|
||||
Synthetic(String),
|
||||
}
|
||||
|
||||
impl Description {
|
||||
pub fn from(item: Spanned<impl Into<String>>) -> Description {
|
||||
match item {
|
||||
Spanned {
|
||||
span: Span { start: 0, end: 0 },
|
||||
item,
|
||||
} => Description::Synthetic(item.into()),
|
||||
Spanned { span, item } => Description::Source(Spanned::from_item(item.into(), span)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Description {
|
||||
fn into_label(self) -> Result<Label<Span>, String> {
|
||||
match self {
|
||||
Description::Source(s) => Ok(Label::new_primary(s.span).with_message(s.item)),
|
||||
Description::Synthetic(s) => Err(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
|
||||
pub enum ShellError {
|
||||
String(StringError),
|
||||
TypeError(String),
|
||||
MissingProperty { subpath: String, expr: String },
|
||||
TypeError(Spanned<String>),
|
||||
MissingProperty {
|
||||
subpath: Description,
|
||||
expr: Description,
|
||||
},
|
||||
Diagnostic(ShellDiagnostic),
|
||||
CoerceError {
|
||||
left: Spanned<String>,
|
||||
right: Spanned<String>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ShellError {
|
||||
crate fn parse_error(
|
||||
error: lalrpop_util::ParseError<usize, SpannedToken, ShellError>,
|
||||
error: nom::Err<(nom_locate::LocatedSpan<&str>, nom::error::ErrorKind)>,
|
||||
) -> ShellError {
|
||||
use lalrpop_util::ParseError;
|
||||
use language_reporting::*;
|
||||
|
||||
match error {
|
||||
ParseError::UnrecognizedToken {
|
||||
token: (start, SpannedToken { token, .. }, end),
|
||||
expected,
|
||||
} => {
|
||||
let diagnostic = Diagnostic::new(
|
||||
Severity::Error,
|
||||
format!("Unexpected {:?}, expected {:?}", token, expected),
|
||||
)
|
||||
.with_label(Label::new_primary(Span::from((start, end))));
|
||||
nom::Err::Incomplete(_) => unreachable!(),
|
||||
nom::Err::Failure(span) | nom::Err::Error(span) => {
|
||||
let diagnostic =
|
||||
Diagnostic::new(Severity::Error, format!("Parse Error"))
|
||||
.with_label(Label::new_primary(Span::from(span.0)));
|
||||
|
||||
ShellError::diagnostic(diagnostic)
|
||||
// nom::Context::Code(span, kind) => {
|
||||
// let diagnostic =
|
||||
// Diagnostic::new(Severity::Error, format!("{}", kind.description()))
|
||||
// .with_label(Label::new_primary(Span::from(span)));
|
||||
|
||||
// ShellError::diagnostic(diagnostic)
|
||||
// }
|
||||
}
|
||||
ParseError::User { error } => error,
|
||||
other => ShellError::string(format!("{:?}", other)),
|
||||
// ParseError::UnrecognizedToken {
|
||||
// token: (start, SpannedToken { token, .. }, end),
|
||||
// expected,
|
||||
// } => {
|
||||
// let diagnostic = Diagnostic::new(
|
||||
// Severity::Error,
|
||||
// format!("Unexpected {:?}, expected {:?}", token, expected),
|
||||
// )
|
||||
// .with_label(Label::new_primary(Span::from((start, end))));
|
||||
|
||||
// ShellError::diagnostic(diagnostic)
|
||||
// }
|
||||
// ParseError::User { error } => error,
|
||||
// other => ShellError::string(format!("{:?}", other)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,6 +91,41 @@ impl ShellError {
|
|||
ShellError::Diagnostic(ShellDiagnostic { diagnostic })
|
||||
}
|
||||
|
||||
crate fn to_diagnostic(self) -> Diagnostic<Span> {
|
||||
match self {
|
||||
ShellError::String(StringError { title, .. }) => {
|
||||
Diagnostic::new(Severity::Error, title)
|
||||
}
|
||||
ShellError::TypeError(s) => Diagnostic::new(Severity::Error, "Type Error")
|
||||
.with_label(Label::new_primary(s.span).with_message(s.item)),
|
||||
|
||||
ShellError::MissingProperty { subpath, expr } => {
|
||||
let subpath = subpath.into_label();
|
||||
let expr = expr.into_label();
|
||||
|
||||
let mut diag = Diagnostic::new(Severity::Error, "Missing property");
|
||||
|
||||
match subpath {
|
||||
Ok(label) => diag = diag.with_label(label),
|
||||
Err(ty) => diag.message = format!("Missing property (for {})", ty),
|
||||
}
|
||||
|
||||
if let Ok(label) = expr {
|
||||
diag = diag.with_label(label);
|
||||
}
|
||||
|
||||
diag
|
||||
}
|
||||
|
||||
ShellError::Diagnostic(diag) => diag.diagnostic,
|
||||
ShellError::CoerceError { left, right } => {
|
||||
Diagnostic::new(Severity::Error, "Coercion error")
|
||||
.with_label(Label::new_primary(left.span).with_message(left.item))
|
||||
.with_label(Label::new_secondary(right.span).with_message(right.item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crate fn labeled_error(
|
||||
msg: impl Into<String>,
|
||||
label: impl Into<String>,
|
||||
|
@ -75,6 +159,10 @@ impl ShellError {
|
|||
ShellError::string(&format!("Unimplemented: {}", title.into()))
|
||||
}
|
||||
|
||||
crate fn unexpected(title: impl Into<String>) -> ShellError {
|
||||
ShellError::string(&format!("Unexpected: {}", title.into()))
|
||||
}
|
||||
|
||||
crate fn copy_error(&self) -> ShellError {
|
||||
self.clone()
|
||||
}
|
||||
|
@ -158,6 +246,7 @@ impl std::fmt::Display for ShellError {
|
|||
ShellError::TypeError { .. } => write!(f, "TypeError"),
|
||||
ShellError::MissingProperty { .. } => write!(f, "MissingProperty"),
|
||||
ShellError::Diagnostic(_) => write!(f, "<diagnostic>"),
|
||||
ShellError::CoerceError { .. } => write!(f, "CoerceError"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -191,14 +280,14 @@ 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::String(StringError {
|
||||
title: format!("{:?}", input),
|
||||
error: Value::nothing(),
|
||||
})
|
||||
}
|
||||
}
|
||||
// impl std::convert::From<nom::Err<(&str, nom::ErrorKind)>> for ShellError {
|
||||
// fn from(input: nom::Err<(&str, nom::ErrorKind)>) -> ShellError {
|
||||
// ShellError::String(StringError {
|
||||
// title: format!("{:?}", input),
|
||||
// error: Value::nothing(),
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
impl std::convert::From<toml::ser::Error> for ShellError {
|
||||
fn from(input: toml::ser::Error) -> ShellError {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use crate::object::Primitive;
|
||||
use crate::parser::ast;
|
||||
use crate::parser::lexer::Spanned;
|
||||
use crate::errors::Description;
|
||||
use crate::object::base::Block;
|
||||
use crate::parser::{
|
||||
hir::{self, Expression, RawExpression},
|
||||
CommandRegistry, Spanned, Text,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use derive_new::new;
|
||||
use indexmap::IndexMap;
|
||||
|
@ -21,96 +24,83 @@ impl Scope {
|
|||
}
|
||||
}
|
||||
|
||||
crate fn evaluate_expr(
|
||||
expr: &ast::Expression,
|
||||
crate fn evaluate_baseline_expr(
|
||||
expr: &Expression,
|
||||
registry: &dyn CommandRegistry,
|
||||
scope: &Scope,
|
||||
source: &Text,
|
||||
) -> Result<Spanned<Value>, ShellError> {
|
||||
use ast::*;
|
||||
match &expr.expr {
|
||||
RawExpression::Call(_) => Err(ShellError::unimplemented("Evaluating call expression")),
|
||||
RawExpression::Leaf(l) => Ok(Spanned::from_item(evaluate_leaf(l), expr.span.clone())),
|
||||
RawExpression::Parenthesized(p) => evaluate_expr(&p.expr, scope),
|
||||
RawExpression::Flag(f) => Ok(Spanned::from_item(
|
||||
Value::Primitive(Primitive::String(f.print())),
|
||||
expr.span.clone(),
|
||||
)),
|
||||
RawExpression::Block(b) => evaluate_block(&b, scope),
|
||||
RawExpression::Path(p) => evaluate_path(&p, scope),
|
||||
RawExpression::Binary(b) => evaluate_binary(b, scope),
|
||||
RawExpression::VariableReference(r) => {
|
||||
evaluate_reference(r, scope).map(|x| Spanned::from_item(x, expr.span.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
match &expr.item {
|
||||
RawExpression::Literal(literal) => Ok(evaluate_literal(expr.copy_span(*literal), source)),
|
||||
RawExpression::Variable(var) => evaluate_reference(var, scope, source),
|
||||
RawExpression::Binary(binary) => {
|
||||
let left = evaluate_baseline_expr(binary.left(), registry, scope, source)?;
|
||||
let right = evaluate_baseline_expr(binary.right(), registry, scope, source)?;
|
||||
|
||||
fn evaluate_leaf(leaf: &ast::Leaf) -> Value {
|
||||
use ast::*;
|
||||
|
||||
match leaf {
|
||||
Leaf::String(s) => Value::string(s),
|
||||
Leaf::Bare(path) => Value::string(path.to_string()),
|
||||
Leaf::Boolean(b) => Value::boolean(*b),
|
||||
Leaf::Int(i) => Value::int(*i),
|
||||
Leaf::Unit(i, unit) => unit.compute(*i),
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_reference(r: &ast::Variable, scope: &Scope) -> Result<Value, ShellError> {
|
||||
use ast::Variable::*;
|
||||
|
||||
match r {
|
||||
It => Ok(scope.it.copy()),
|
||||
Other(s) => Ok(scope
|
||||
.vars
|
||||
.get(s)
|
||||
.map(|v| v.copy())
|
||||
.unwrap_or_else(|| Value::nothing())),
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_binary(binary: &ast::Binary, scope: &Scope) -> Result<Spanned<Value>, ShellError> {
|
||||
let left = evaluate_expr(&binary.left, scope)?;
|
||||
let right = evaluate_expr(&binary.right, scope)?;
|
||||
|
||||
match left.compare(&binary.operator, &right) {
|
||||
Some(v) => Ok(Spanned::from_item(
|
||||
Value::boolean(v),
|
||||
binary.operator.span.clone(),
|
||||
)),
|
||||
None => Err(ShellError::TypeError(format!(
|
||||
"Can't compare {} and {}",
|
||||
left.type_name(),
|
||||
right.type_name()
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_block(block: &ast::Block, _scope: &Scope) -> Result<Spanned<Value>, ShellError> {
|
||||
Ok(Spanned::from_item(
|
||||
Value::block(block.expr.clone()),
|
||||
block.expr.span.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
fn evaluate_path(path: &ast::Path, scope: &Scope) -> Result<Spanned<Value>, ShellError> {
|
||||
let head = path.head();
|
||||
let mut value = evaluate_expr(head, scope)?;
|
||||
let mut seen = vec![];
|
||||
|
||||
for name in path.tail() {
|
||||
let next = value.get_data_by_key(&name.item);
|
||||
seen.push(name.item.clone());
|
||||
|
||||
match next {
|
||||
None => {
|
||||
return Err(ShellError::MissingProperty {
|
||||
expr: path.print(),
|
||||
subpath: itertools::join(seen, "."),
|
||||
});
|
||||
match left.compare(binary.op(), &*right) {
|
||||
Ok(result) => Ok(Spanned::from_item(Value::boolean(result), *expr.span())),
|
||||
Err((left_type, right_type)) => Err(ShellError::CoerceError {
|
||||
left: binary.left().copy_span(left_type),
|
||||
right: binary.right().copy_span(right_type),
|
||||
}),
|
||||
}
|
||||
Some(v) => value = Spanned::from_item(v.copy(), name.span.clone()),
|
||||
}
|
||||
}
|
||||
RawExpression::Block(block) => Ok(Spanned::from_item(
|
||||
Value::Block(Block::new(*block.clone(), source.clone())),
|
||||
block.span(),
|
||||
)),
|
||||
RawExpression::Path(path) => {
|
||||
let value = evaluate_baseline_expr(path.head(), registry, scope, source)?;
|
||||
let mut item = value;
|
||||
|
||||
Ok(value)
|
||||
for name in path.tail() {
|
||||
let next = item.get_data_by_key(name);
|
||||
|
||||
match next {
|
||||
None => {
|
||||
return Err(ShellError::MissingProperty {
|
||||
subpath: Description::from(item.spanned_type_name()),
|
||||
expr: Description::from(name.clone()),
|
||||
})
|
||||
}
|
||||
Some(next) => {
|
||||
item =
|
||||
Spanned::from_item(next.clone(), (expr.span().start, name.span().end))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Spanned::from_item(item.item().clone(), expr.span()))
|
||||
}
|
||||
RawExpression::Boolean(_boolean) => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_literal(literal: Spanned<hir::Literal>, source: &Text) -> Spanned<Value> {
|
||||
let result = match literal.item {
|
||||
hir::Literal::Integer(int) => Value::int(int),
|
||||
hir::Literal::Size(_int, _unit) => unimplemented!(),
|
||||
hir::Literal::String(span) => Value::string(span.slice(source)),
|
||||
hir::Literal::Bare => Value::string(literal.span().slice(source)),
|
||||
};
|
||||
|
||||
literal.map(|_| result)
|
||||
}
|
||||
|
||||
fn evaluate_reference(
|
||||
name: &hir::Variable,
|
||||
scope: &Scope,
|
||||
source: &Text,
|
||||
) -> Result<Spanned<Value>, ShellError> {
|
||||
match name {
|
||||
hir::Variable::It(span) => Ok(Spanned::from_item(scope.it.copy(), span)),
|
||||
hir::Variable::Other(span) => Ok(Spanned::from_item(
|
||||
scope
|
||||
.vars
|
||||
.get(span.slice(source))
|
||||
.map(|v| v.copy())
|
||||
.unwrap_or_else(|| Value::nothing()),
|
||||
span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
crate mod evaluator;
|
||||
|
||||
crate use evaluator::{evaluate_expr, Scope};
|
||||
crate use evaluator::{evaluate_baseline_expr, Scope};
|
||||
|
|
|
@ -31,7 +31,7 @@ impl RenderView for GenericView<'value> {
|
|||
|
||||
b @ Value::Block(_) => {
|
||||
let printed = b.format_leaf(None);
|
||||
let view = EntriesView::from_value(&Value::string(&printed));
|
||||
let view = EntriesView::from_value(&Value::string(printed));
|
||||
view.render_view(host)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ use clap::{App, Arg};
|
|||
use log::LevelFilter;
|
||||
use std::error::Error;
|
||||
|
||||
crate use parser::parse2::text::Text;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let matches = App::new("nu shell")
|
||||
.version("0.5")
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
use crate::errors::ShellError;
|
||||
use crate::evaluate::{evaluate_expr, Scope};
|
||||
use crate::evaluate::{evaluate_baseline_expr, Scope};
|
||||
use crate::object::DataDescriptor;
|
||||
use crate::parser::ast::{self, Operator};
|
||||
use crate::parser::lexer::Spanned;
|
||||
use crate::parser::{hir, Operator, Spanned};
|
||||
use crate::prelude::*;
|
||||
use crate::Text;
|
||||
use ansi_term::Color;
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono_humanize::Humanize;
|
||||
use derive_new::new;
|
||||
use ordered_float::OrderedFloat;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt;
|
||||
use std::time::SystemTime;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, new)]
|
||||
pub struct OF64 {
|
||||
|
@ -75,6 +75,20 @@ impl Primitive {
|
|||
.to_string()
|
||||
}
|
||||
|
||||
crate fn debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use Primitive::*;
|
||||
|
||||
match self {
|
||||
Nothing => write!(f, "Nothing"),
|
||||
Int(int) => write!(f, "{}", int),
|
||||
Float(float) => write!(f, "{:?}", float),
|
||||
Bytes(bytes) => write!(f, "{}", bytes),
|
||||
String(string) => write!(f, "{:?}", string),
|
||||
Boolean(boolean) => write!(f, "{}", boolean),
|
||||
Date(date) => write!(f, "{}", date),
|
||||
}
|
||||
}
|
||||
|
||||
crate fn format(&self, field_name: Option<&DataDescriptor>) -> String {
|
||||
match self {
|
||||
Primitive::Nothing => format!("{}", Color::Black.bold().paint("-")),
|
||||
|
@ -117,7 +131,8 @@ pub struct Operation {
|
|||
|
||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, new)]
|
||||
pub struct Block {
|
||||
crate expression: ast::Expression,
|
||||
crate expression: hir::Expression,
|
||||
crate source: Text,
|
||||
}
|
||||
|
||||
impl Serialize for Block {
|
||||
|
@ -125,7 +140,7 @@ impl Serialize for Block {
|
|||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.expression.print())
|
||||
serializer.serialize_str(&self.expression.source(&self.source.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,25 +149,28 @@ impl Deserialize<'de> for Block {
|
|||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let mut builder = ast::ExpressionBuilder::new();
|
||||
let expr: ast::Expression = builder.string("Unserializable block");
|
||||
|
||||
Ok(Block::new(expr))
|
||||
unimplemented!("deserialize block")
|
||||
// let s = "\"unimplemented deserialize block\"";
|
||||
// Ok(Block::new(
|
||||
// TokenTreeBuilder::spanned_string((1, s.len() - 1), (0, s.len())),
|
||||
// Text::from(s),
|
||||
// ))
|
||||
}
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn invoke(&self, value: &Value) -> Result<Spanned<Value>, ShellError> {
|
||||
let scope = Scope::new(value.copy());
|
||||
evaluate_expr(&self.expression, &scope)
|
||||
evaluate_baseline_expr(&self.expression, &(), &scope, &self.source)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone)]
|
||||
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone)]
|
||||
pub enum Value {
|
||||
Primitive(Primitive),
|
||||
Object(crate::object::Dictionary),
|
||||
List(Vec<Value>),
|
||||
#[allow(unused)]
|
||||
Block(Block),
|
||||
Filesystem,
|
||||
|
||||
|
@ -160,6 +178,46 @@ pub enum Value {
|
|||
Error(Box<ShellError>),
|
||||
}
|
||||
|
||||
pub fn debug_list(values: &'a Vec<Value>) -> ValuesDebug<'a> {
|
||||
ValuesDebug { values }
|
||||
}
|
||||
|
||||
pub struct ValuesDebug<'a> {
|
||||
values: &'a Vec<Value>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ValuesDebug<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_list()
|
||||
.entries(self.values.iter().map(|i| i.debug()))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ValueDebug<'a> {
|
||||
value: &'a Value,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ValueDebug<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.value {
|
||||
Value::Primitive(p) => p.debug(f),
|
||||
Value::Object(o) => o.debug(f),
|
||||
Value::List(l) => debug_list(l).fmt(f),
|
||||
Value::Block(_) => write!(f, "[[block]]"),
|
||||
Value::Error(err) => write!(f, "[[error :: {} ]]", err),
|
||||
Value::Filesystem => write!(f, "[[filesystem]]"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned<Value> {
|
||||
crate fn spanned_type_name(&self) -> Spanned<String> {
|
||||
let name = self.type_name();
|
||||
Spanned::from_item(name, self.span)
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
crate fn type_name(&self) -> String {
|
||||
match self {
|
||||
|
@ -172,6 +230,10 @@ impl Value {
|
|||
}
|
||||
}
|
||||
|
||||
crate fn debug(&'a self) -> ValueDebug<'a> {
|
||||
ValueDebug { value: self }
|
||||
}
|
||||
|
||||
crate fn data_descriptors(&self) -> Vec<DataDescriptor> {
|
||||
match self {
|
||||
Value::Primitive(_) => vec![DataDescriptor::value_of()],
|
||||
|
@ -237,7 +299,7 @@ impl Value {
|
|||
crate fn format_leaf(&self, desc: Option<&DataDescriptor>) -> String {
|
||||
match self {
|
||||
Value::Primitive(p) => p.format(desc),
|
||||
Value::Block(b) => b.expression.print(),
|
||||
Value::Block(b) => b.expression.source(&b.source).to_string(),
|
||||
Value::Object(_) => format!("[object Object]"),
|
||||
Value::List(_) => format!("[list List]"),
|
||||
Value::Error(e) => format!("{}", e),
|
||||
|
@ -245,7 +307,8 @@ impl Value {
|
|||
}
|
||||
}
|
||||
|
||||
crate fn compare(&self, operator: &ast::Operator, other: &Value) -> Option<bool> {
|
||||
#[allow(unused)]
|
||||
crate fn compare(&self, operator: &Operator, other: &Value) -> Result<bool, (String, String)> {
|
||||
match operator {
|
||||
_ => {
|
||||
let coerced = coerce_compare(self, other)?;
|
||||
|
@ -266,7 +329,7 @@ impl Value {
|
|||
_ => false,
|
||||
};
|
||||
|
||||
Some(result)
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -291,12 +354,11 @@ impl Value {
|
|||
|
||||
crate fn as_string(&self) -> Result<String, ShellError> {
|
||||
match self {
|
||||
Value::Primitive(Primitive::String(x)) => Ok(format!("{}", x)),
|
||||
Value::Primitive(Primitive::String(s)) => Ok(s.clone()),
|
||||
Value::Primitive(Primitive::Boolean(x)) => Ok(format!("{}", x)),
|
||||
Value::Primitive(Primitive::Float(x)) => Ok(format!("{}", x.into_inner())),
|
||||
Value::Primitive(Primitive::Int(x)) => Ok(format!("{}", x)),
|
||||
Value::Primitive(Primitive::Bytes(x)) => Ok(format!("{}", x)),
|
||||
//Value::Primitive(Primitive::String(s)) => Ok(s.clone()),
|
||||
// TODO: this should definitely be more general with better errors
|
||||
other => Err(ShellError::string(format!(
|
||||
"Expected string, got {:?}",
|
||||
|
@ -328,18 +390,6 @@ impl Value {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
crate fn as_bool(&self) -> Result<bool, ShellError> {
|
||||
match self {
|
||||
Value::Primitive(Primitive::Boolean(b)) => Ok(*b),
|
||||
// TODO: this should definitely be more general with better errors
|
||||
other => Err(ShellError::string(format!(
|
||||
"Expected integer, got {:?}",
|
||||
other
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
crate fn is_true(&self) -> bool {
|
||||
match self {
|
||||
Value::Primitive(Primitive::Boolean(true)) => true,
|
||||
|
@ -347,8 +397,9 @@ impl Value {
|
|||
}
|
||||
}
|
||||
|
||||
crate fn block(e: ast::Expression) -> Value {
|
||||
Value::Block(Block::new(e))
|
||||
#[allow(unused)]
|
||||
crate fn block(e: hir::Expression, source: Text) -> Value {
|
||||
Value::Block(Block::new(e, source))
|
||||
}
|
||||
|
||||
crate fn string(s: impl Into<String>) -> Value {
|
||||
|
@ -535,24 +586,27 @@ impl CompareValues {
|
|||
}
|
||||
}
|
||||
|
||||
fn coerce_compare(left: &Value, right: &Value) -> Option<CompareValues> {
|
||||
fn coerce_compare(left: &Value, right: &Value) -> Result<CompareValues, (String, String)> {
|
||||
match (left, right) {
|
||||
(Value::Primitive(left), Value::Primitive(right)) => coerce_compare_primitive(left, right),
|
||||
|
||||
_ => None,
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}
|
||||
}
|
||||
|
||||
fn coerce_compare_primitive(left: &Primitive, right: &Primitive) -> Option<CompareValues> {
|
||||
fn coerce_compare_primitive(
|
||||
left: &Primitive,
|
||||
right: &Primitive,
|
||||
) -> Result<CompareValues, (String, String)> {
|
||||
use Primitive::*;
|
||||
|
||||
match (left, right) {
|
||||
(Int(left), Int(right)) => Some(CompareValues::Ints(*left, *right)),
|
||||
(Float(left), Int(right)) => Some(CompareValues::Floats(*left, (*right as f64).into())),
|
||||
(Int(left), Float(right)) => Some(CompareValues::Floats((*left as f64).into(), *right)),
|
||||
(Int(left), Bytes(right)) => Some(CompareValues::Bytes(*left as i128, *right as i128)),
|
||||
(Bytes(left), Int(right)) => Some(CompareValues::Bytes(*left as i128, *right as i128)),
|
||||
(String(left), String(right)) => Some(CompareValues::String(left.clone(), right.clone())),
|
||||
_ => None,
|
||||
}
|
||||
Ok(match (left, right) {
|
||||
(Int(left), Int(right)) => CompareValues::Ints(*left, *right),
|
||||
(Float(left), Int(right)) => CompareValues::Floats(*left, (*right as f64).into()),
|
||||
(Int(left), Float(right)) => CompareValues::Floats((*left as f64).into(), *right),
|
||||
(Int(left), Bytes(right)) => CompareValues::Bytes(*left as i128, *right as i128),
|
||||
(Bytes(left), Int(right)) => CompareValues::Bytes(*left as i128, *right as i128),
|
||||
(String(left), String(right)) => CompareValues::String(left.clone(), right.clone()),
|
||||
_ => return Err((left.type_name(), right.type_name())),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::object::types::Type;
|
||||
use crate::Text;
|
||||
use derive_new::new;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
|
@ -16,6 +17,13 @@ impl DescriptorName {
|
|||
}
|
||||
}
|
||||
|
||||
crate fn debug(&self) -> &str {
|
||||
match self {
|
||||
DescriptorName::String(s) => s,
|
||||
DescriptorName::ValueOf => "[[value]]",
|
||||
}
|
||||
}
|
||||
|
||||
crate fn as_string(&self) -> Option<&str> {
|
||||
match self {
|
||||
DescriptorName::String(s) => Some(s),
|
||||
|
@ -31,7 +39,7 @@ impl DescriptorName {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Eq, PartialEq, Hash, new)]
|
||||
#[derive(Debug, Deserialize, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, new)]
|
||||
pub struct DataDescriptor {
|
||||
crate name: DescriptorName,
|
||||
crate readonly: bool,
|
||||
|
@ -83,9 +91,19 @@ impl From<String> for DataDescriptor {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Text> for DataDescriptor {
|
||||
fn from(input: Text) -> DataDescriptor {
|
||||
DataDescriptor {
|
||||
name: DescriptorName::String(input.to_string()),
|
||||
readonly: true,
|
||||
ty: Type::Any,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DescriptorName {
|
||||
crate fn for_string_name(name: impl Into<String>) -> DescriptorName {
|
||||
DescriptorName::String(name.into())
|
||||
crate fn for_string_name(name: impl AsRef<str>) -> DescriptorName {
|
||||
DescriptorName::String(name.as_ref().into())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +124,7 @@ impl DataDescriptor {
|
|||
}
|
||||
}
|
||||
|
||||
crate fn for_string_name(name: impl Into<String>) -> DataDescriptor {
|
||||
crate fn for_string_name(name: impl AsRef<str>) -> DataDescriptor {
|
||||
DataDescriptor::for_name(DescriptorName::for_string_name(name))
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ use indexmap::IndexMap;
|
|||
use serde::ser::{Serialize, SerializeMap, Serializer};
|
||||
use serde_derive::Deserialize;
|
||||
use std::cmp::{Ordering, PartialOrd};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Default, Eq, PartialEq, Deserialize, Clone, new)]
|
||||
pub struct Dictionary {
|
||||
|
@ -14,9 +15,18 @@ pub struct Dictionary {
|
|||
}
|
||||
|
||||
impl PartialOrd for Dictionary {
|
||||
// TODO: FIXME
|
||||
fn partial_cmp(&self, _other: &Dictionary) -> Option<Ordering> {
|
||||
Some(Ordering::Less)
|
||||
fn partial_cmp(&self, other: &Dictionary) -> Option<Ordering> {
|
||||
let this: Vec<&DataDescriptor> = self.entries.keys().collect();
|
||||
let that: Vec<&DataDescriptor> = other.entries.keys().collect();
|
||||
|
||||
if this != that {
|
||||
return this.partial_cmp(&that);
|
||||
}
|
||||
|
||||
let this: Vec<&Value> = self.entries.values().collect();
|
||||
let that: Vec<&Value> = self.entries.values().collect();
|
||||
|
||||
this.partial_cmp(&that)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,9 +65,18 @@ impl From<IndexMap<String, Value>> for Dictionary {
|
|||
}
|
||||
|
||||
impl Ord for Dictionary {
|
||||
// TODO: FIXME
|
||||
fn cmp(&self, _other: &Dictionary) -> Ordering {
|
||||
Ordering::Less
|
||||
fn cmp(&self, other: &Dictionary) -> Ordering {
|
||||
let this: Vec<&DataDescriptor> = self.entries.keys().collect();
|
||||
let that: Vec<&DataDescriptor> = other.entries.keys().collect();
|
||||
|
||||
if this != that {
|
||||
return this.cmp(&that);
|
||||
}
|
||||
|
||||
let this: Vec<&Value> = self.entries.values().collect();
|
||||
let that: Vec<&Value> = self.entries.values().collect();
|
||||
|
||||
this.cmp(&that)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,7 +87,6 @@ impl PartialOrd<Value> for Dictionary {
|
|||
}
|
||||
|
||||
impl PartialEq<Value> for Dictionary {
|
||||
// TODO: FIXME
|
||||
fn eq(&self, other: &Value) -> bool {
|
||||
match other {
|
||||
Value::Object(d) => self == d,
|
||||
|
@ -113,4 +131,14 @@ impl Dictionary {
|
|||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
crate fn debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut debug = f.debug_struct("Dictionary");
|
||||
|
||||
for (desc, value) in self.entries.iter() {
|
||||
debug.field(desc.name.debug(), &value.debug());
|
||||
}
|
||||
|
||||
debug.finish()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use derive_new::new;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash, new)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, new)]
|
||||
pub enum Type {
|
||||
Any,
|
||||
}
|
||||
|
|
290
src/parser.rs
290
src/parser.rs
|
@ -1,278 +1,30 @@
|
|||
crate mod ast;
|
||||
crate mod completer;
|
||||
crate mod lexer;
|
||||
crate mod parser;
|
||||
crate mod hir;
|
||||
crate mod parse2;
|
||||
crate mod parse_command;
|
||||
crate mod registry;
|
||||
crate mod span;
|
||||
|
||||
crate use ast::Pipeline;
|
||||
crate use registry::{Args, CommandConfig};
|
||||
|
||||
use crate::errors::ShellError;
|
||||
use ast::Module;
|
||||
use lexer::Lexer;
|
||||
use log::trace;
|
||||
use parser::{ModuleParser, ReplLineParser};
|
||||
|
||||
pub fn parse(input: &str) -> Result<Pipeline, ShellError> {
|
||||
crate use hir::baseline_parse_tokens::baseline_parse_tokens;
|
||||
crate use parse2::call_node::CallNode;
|
||||
crate use parse2::files::Files;
|
||||
crate use parse2::flag::Flag;
|
||||
crate use parse2::operator::Operator;
|
||||
crate use parse2::parser::{nom_input, pipeline};
|
||||
crate use parse2::pipeline::{Pipeline, PipelineElement};
|
||||
crate use parse2::span::{Span, Spanned};
|
||||
crate use parse2::text::Text;
|
||||
crate use parse2::token_tree::TokenNode;
|
||||
crate use parse2::tokens::{RawToken, Token};
|
||||
crate use parse2::unit::Unit;
|
||||
crate use parse_command::parse_command;
|
||||
crate use registry::CommandRegistry;
|
||||
|
||||
pub fn parse(input: &str) -> Result<TokenNode, ShellError> {
|
||||
let _ = pretty_env_logger::try_init();
|
||||
|
||||
let parser = ReplLineParser::new();
|
||||
let tokens = Lexer::new(input, false);
|
||||
|
||||
trace!(
|
||||
"Tokens: {:?}",
|
||||
tokens.clone().collect::<Result<Vec<_>, _>>()
|
||||
);
|
||||
|
||||
match parser.parse(tokens) {
|
||||
Ok(val) => Ok(val),
|
||||
match pipeline(nom_input(input)) {
|
||||
Ok((_rest, val)) => Ok(val),
|
||||
Err(err) => Err(ShellError::parse_error(err)),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn parse_module(input: &str) -> Result<Module, ShellError> {
|
||||
let _ = pretty_env_logger::try_init();
|
||||
|
||||
let parser = ModuleParser::new();
|
||||
let tokens = Lexer::new(input, false);
|
||||
|
||||
trace!(
|
||||
"Tokens: {:?}",
|
||||
tokens.clone().collect::<Result<Vec<_>, _>>()
|
||||
);
|
||||
|
||||
match parser.parse(tokens) {
|
||||
Ok(val) => Ok(val),
|
||||
Err(err) => Err(ShellError::parse_error(err)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::parser::ast::Pipeline;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn assert_parse(source: &str, expected: Pipeline) {
|
||||
let parsed = match parse(source) {
|
||||
Ok(p) => p,
|
||||
Err(ShellError::Diagnostic(diag)) => {
|
||||
use language_reporting::termcolor;
|
||||
|
||||
let writer = termcolor::StandardStream::stdout(termcolor::ColorChoice::Auto);
|
||||
let files = crate::parser::span::Files::new(source.to_string());
|
||||
|
||||
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),
|
||||
};
|
||||
|
||||
let printed = parsed.print();
|
||||
|
||||
assert_eq!(parsed, expected);
|
||||
assert_eq!(printed, source);
|
||||
|
||||
let span = expected.span;
|
||||
|
||||
let expected_module = ModuleBuilder::spanned_items(
|
||||
vec![Spanned::from_item(RawItem::Expression(expected), span)],
|
||||
span.start,
|
||||
span.end,
|
||||
);
|
||||
|
||||
assert_parse_module(source, expected_module);
|
||||
}
|
||||
|
||||
fn assert_parse_module(source: &str, expected: Module) {
|
||||
let parsed = match parse_module(source) {
|
||||
Ok(p) => p,
|
||||
Err(ShellError::Diagnostic(diag)) => {
|
||||
use language_reporting::termcolor;
|
||||
|
||||
let writer = termcolor::StandardStream::stdout(termcolor::ColorChoice::Auto);
|
||||
let files = crate::parser::span::Files::new(source.to_string());
|
||||
|
||||
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),
|
||||
};
|
||||
|
||||
let printed = parsed.print();
|
||||
|
||||
assert_eq!(parsed, expected);
|
||||
assert_eq!(printed, source);
|
||||
}
|
||||
|
||||
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![
|
||||
$(
|
||||
(command!($name $($command($arg))*) as (&dyn Fn(&mut ExpressionBuilder) -> Expression))
|
||||
),*
|
||||
])
|
||||
}}
|
||||
}
|
||||
|
||||
macro_rules! command {
|
||||
($name:ident) => {
|
||||
&|b: &mut $crate::parser::ast::ExpressionBuilder| b.call((
|
||||
&|b: &mut $crate::parser::ast::ExpressionBuilder| b.bare(stringify!($name)),
|
||||
vec![]
|
||||
))
|
||||
};
|
||||
|
||||
($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) ),* ])
|
||||
};
|
||||
|
||||
($name:tt $( $command:ident ( $body:expr ) )*) => {
|
||||
&|b: &mut $crate::parser::ast::ExpressionBuilder| b.call((&|b| b.bare($name), vec![ $( &|b| b.$command($body) ),* ]))
|
||||
};
|
||||
}
|
||||
|
||||
#[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(" "))
|
||||
],
|
||||
);
|
||||
|
||||
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"))],
|
||||
);
|
||||
|
||||
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")],
|
||||
);
|
||||
|
||||
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))],
|
||||
);
|
||||
|
||||
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"))
|
||||
| (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"))))
|
||||
],
|
||||
);
|
||||
|
||||
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"))))
|
||||
]
|
||||
);
|
||||
|
||||
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")))))
|
||||
],
|
||||
);
|
||||
|
||||
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)))))
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
use crate::parser::ast::{ModuleBuilder, RawItem};
|
||||
use crate::parser::lexer::Spanned;
|
||||
|
||||
}
|
||||
|
|
|
@ -524,6 +524,7 @@ impl Call {
|
|||
#[derive(new, Debug, Eq, PartialEq, Clone)]
|
||||
pub struct Pipeline {
|
||||
crate commands: Vec<Expression>,
|
||||
crate trailing: Span,
|
||||
crate span: Span,
|
||||
}
|
||||
|
||||
|
|
91
src/parser/hir.rs
Normal file
91
src/parser/hir.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
crate mod baseline_parse;
|
||||
crate mod baseline_parse_tokens;
|
||||
crate mod binary;
|
||||
crate mod named;
|
||||
crate mod path;
|
||||
|
||||
use crate::parser::{Span, Spanned, Unit};
|
||||
use derive_new::new;
|
||||
use getset::Getters;
|
||||
|
||||
crate use baseline_parse::baseline_parse_single_token;
|
||||
crate use baseline_parse_tokens::{baseline_parse_next_expr, ExpressionKindHint};
|
||||
crate use binary::Binary;
|
||||
crate use named::NamedArguments;
|
||||
crate use path::Path;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Getters, new)]
|
||||
pub struct Call {
|
||||
#[get = "crate"]
|
||||
head: Box<Expression>,
|
||||
#[get = "crate"]
|
||||
positional: Option<Vec<Expression>>,
|
||||
#[get = "crate"]
|
||||
named: Option<NamedArguments>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum RawExpression {
|
||||
Literal(Literal),
|
||||
Variable(Variable),
|
||||
Binary(Box<Binary>),
|
||||
Block(Box<Expression>),
|
||||
Path(Box<Path>),
|
||||
|
||||
#[allow(unused)]
|
||||
Boolean(bool),
|
||||
}
|
||||
|
||||
pub type Expression = Spanned<RawExpression>;
|
||||
|
||||
impl Expression {
|
||||
fn int(i: impl Into<i64>, span: impl Into<Span>) -> Expression {
|
||||
Spanned::from_item(RawExpression::Literal(Literal::Integer(i.into())), span)
|
||||
}
|
||||
|
||||
fn size(i: impl Into<i64>, unit: impl Into<Unit>, span: impl Into<Span>) -> Expression {
|
||||
Spanned::from_item(
|
||||
RawExpression::Literal(Literal::Size(i.into(), unit.into())),
|
||||
span,
|
||||
)
|
||||
}
|
||||
|
||||
fn string(inner: impl Into<Span>, outer: impl Into<Span>) -> Expression {
|
||||
Spanned::from_item(
|
||||
RawExpression::Literal(Literal::String(inner.into())),
|
||||
outer.into(),
|
||||
)
|
||||
}
|
||||
|
||||
fn bare(span: impl Into<Span>) -> Expression {
|
||||
Spanned::from_item(RawExpression::Literal(Literal::Bare), span.into())
|
||||
}
|
||||
|
||||
fn variable(inner: impl Into<Span>, outer: impl Into<Span>) -> Expression {
|
||||
Spanned::from_item(
|
||||
RawExpression::Variable(Variable::Other(inner.into())),
|
||||
outer.into(),
|
||||
)
|
||||
}
|
||||
|
||||
fn it_variable(inner: impl Into<Span>, outer: impl Into<Span>) -> Expression {
|
||||
Spanned::from_item(
|
||||
RawExpression::Variable(Variable::It(inner.into())),
|
||||
outer.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum Literal {
|
||||
Integer(i64),
|
||||
Size(i64, Unit),
|
||||
String(Span),
|
||||
Bare,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum Variable {
|
||||
It(Span),
|
||||
Other(Span),
|
||||
}
|
15
src/parser/hir/baseline_parse.rs
Normal file
15
src/parser/hir/baseline_parse.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
use crate::parser::{hir, RawToken, Token};
|
||||
use crate::Text;
|
||||
|
||||
pub fn baseline_parse_single_token(token: &Token, source: &Text) -> hir::Expression {
|
||||
match *token.item() {
|
||||
RawToken::Integer(int) => hir::Expression::int(int, token.span),
|
||||
RawToken::Size(int, unit) => hir::Expression::size(int, unit, token.span),
|
||||
RawToken::String(span) => hir::Expression::string(span, token.span),
|
||||
RawToken::Variable(span) if span.slice(source) == "it" => {
|
||||
hir::Expression::it_variable(span, token.span)
|
||||
}
|
||||
RawToken::Variable(span) => hir::Expression::variable(span, token.span),
|
||||
RawToken::Bare => hir::Expression::bare(token.span),
|
||||
}
|
||||
}
|
176
src/parser/hir/baseline_parse_tokens.rs
Normal file
176
src/parser/hir/baseline_parse_tokens.rs
Normal file
|
@ -0,0 +1,176 @@
|
|||
use crate::errors::ShellError;
|
||||
use crate::parser::registry::CommandRegistry;
|
||||
use crate::parser::{hir, hir::baseline_parse_single_token, Span, Spanned, TokenNode};
|
||||
use crate::Text;
|
||||
|
||||
pub fn baseline_parse_tokens(
|
||||
token_nodes: &[TokenNode],
|
||||
registry: &dyn CommandRegistry,
|
||||
source: &Text,
|
||||
) -> Result<Vec<hir::Expression>, ShellError> {
|
||||
let mut exprs: Vec<hir::Expression> = vec![];
|
||||
let mut rest = token_nodes;
|
||||
|
||||
loop {
|
||||
if rest.len() == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let (expr, remainder) = baseline_parse_next_expr(rest, registry, source, None)?;
|
||||
exprs.push(expr);
|
||||
rest = remainder;
|
||||
}
|
||||
|
||||
Ok(exprs)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug)]
|
||||
pub enum ExpressionKindHint {
|
||||
Literal,
|
||||
Variable,
|
||||
Binary,
|
||||
Block,
|
||||
Boolean,
|
||||
}
|
||||
|
||||
pub fn baseline_parse_next_expr(
|
||||
token_nodes: &'nodes [TokenNode],
|
||||
_registry: &dyn CommandRegistry,
|
||||
source: &Text,
|
||||
coerce_hint: Option<ExpressionKindHint>,
|
||||
) -> Result<(hir::Expression, &'nodes [TokenNode]), ShellError> {
|
||||
let mut tokens = token_nodes.iter().peekable();
|
||||
|
||||
let first = next_token(&mut tokens);
|
||||
|
||||
let first = match first {
|
||||
None => return Err(ShellError::string("Expected token, found none")),
|
||||
Some(token) => baseline_parse_semantic_token(token, source)?,
|
||||
};
|
||||
|
||||
let possible_op = tokens.peek();
|
||||
|
||||
let op = match possible_op {
|
||||
Some(TokenNode::Operator(op)) => op,
|
||||
_ => return Ok((first, &token_nodes[1..])),
|
||||
};
|
||||
|
||||
tokens.next();
|
||||
|
||||
let second = match tokens.next() {
|
||||
None => {
|
||||
return Err(ShellError::unimplemented(
|
||||
"Expected op followed by another expr, found nothing",
|
||||
))
|
||||
}
|
||||
Some(token) => baseline_parse_semantic_token(token, source)?,
|
||||
};
|
||||
|
||||
// We definitely have a binary expression here -- let's see if we should coerce it into a block
|
||||
|
||||
match coerce_hint {
|
||||
None => {
|
||||
let span = (first.span.start, second.span.end);
|
||||
let binary = hir::Binary::new(first, *op, second);
|
||||
let binary = hir::RawExpression::Binary(Box::new(binary));
|
||||
let binary = Spanned::from_item(binary, span);
|
||||
|
||||
Ok((binary, &token_nodes[3..]))
|
||||
}
|
||||
|
||||
Some(hint) => match hint {
|
||||
ExpressionKindHint::Block => {
|
||||
let span = (first.span.start, second.span.end);
|
||||
|
||||
let path: Spanned<hir::RawExpression> = match first {
|
||||
Spanned {
|
||||
item: hir::RawExpression::Literal(hir::Literal::Bare),
|
||||
span,
|
||||
} => {
|
||||
let string = Spanned::from_item(span.slice(source).to_string(), span);
|
||||
let path = hir::Path::new(
|
||||
Spanned::from_item(
|
||||
// TODO: Deal with synthetic nodes that have no representation at all in source
|
||||
hir::RawExpression::Variable(hir::Variable::It(Span::from((0, 0)))),
|
||||
(0, 0),
|
||||
),
|
||||
vec![string],
|
||||
);
|
||||
let path = hir::RawExpression::Path(Box::new(path));
|
||||
Spanned {
|
||||
item: path,
|
||||
span: first.span,
|
||||
}
|
||||
}
|
||||
Spanned {
|
||||
item: hir::RawExpression::Literal(hir::Literal::String(inner)),
|
||||
span,
|
||||
} => {
|
||||
let string = Spanned::from_item(inner.slice(source).to_string(), span);
|
||||
let path = hir::Path::new(
|
||||
Spanned::from_item(
|
||||
// TODO: Deal with synthetic nodes that have no representation at all in source
|
||||
hir::RawExpression::Variable(hir::Variable::It(Span::from((0, 0)))),
|
||||
(0, 0),
|
||||
),
|
||||
vec![string],
|
||||
);
|
||||
let path = hir::RawExpression::Path(Box::new(path));
|
||||
Spanned {
|
||||
item: path,
|
||||
span: first.span,
|
||||
}
|
||||
}
|
||||
Spanned {
|
||||
item: hir::RawExpression::Variable(..),
|
||||
..
|
||||
} => first,
|
||||
_ => {
|
||||
return Err(ShellError::unimplemented(
|
||||
"The first part of a block must be a string",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let binary = hir::Binary::new(path, *op, second);
|
||||
let binary = hir::RawExpression::Binary(Box::new(binary));
|
||||
let binary = Spanned::from_item(binary, span);
|
||||
|
||||
let block = hir::RawExpression::Block(Box::new(binary));
|
||||
let block = Spanned::from_item(block, span);
|
||||
|
||||
Ok((block, &token_nodes[3..]))
|
||||
}
|
||||
|
||||
other => unimplemented!("coerce hint {:?}", other),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn baseline_parse_semantic_token(
|
||||
token: &TokenNode,
|
||||
source: &Text,
|
||||
) -> Result<hir::Expression, ShellError> {
|
||||
match token {
|
||||
TokenNode::Token(token) => Ok(baseline_parse_single_token(token, source)),
|
||||
TokenNode::Call(_call) => unimplemented!(),
|
||||
TokenNode::Delimited(_delimited) => unimplemented!(),
|
||||
TokenNode::Pipeline(_pipeline) => unimplemented!(),
|
||||
TokenNode::Operator(_op) => unreachable!(),
|
||||
TokenNode::Flag(_flag) => unimplemented!(),
|
||||
TokenNode::Identifier(_span) => unreachable!(),
|
||||
TokenNode::Whitespace(_span) => unreachable!(),
|
||||
TokenNode::Error(error) => Err(*error.item.clone()),
|
||||
TokenNode::Path(_path) => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn next_token(nodes: &mut impl Iterator<Item = &'a TokenNode>) -> Option<&'a TokenNode> {
|
||||
loop {
|
||||
match nodes.next() {
|
||||
Some(TokenNode::Whitespace(_)) => continue,
|
||||
other => return other,
|
||||
}
|
||||
}
|
||||
}
|
11
src/parser/hir/binary.rs
Normal file
11
src/parser/hir/binary.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use crate::parser::{hir::Expression, Operator, Spanned};
|
||||
use derive_new::new;
|
||||
use getset::Getters;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Getters, new)]
|
||||
#[get = "crate"]
|
||||
pub struct Binary {
|
||||
left: Expression,
|
||||
op: Spanned<Operator>,
|
||||
right: Expression,
|
||||
}
|
44
src/parser/hir/named.rs
Normal file
44
src/parser/hir/named.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use crate::parser::hir::Expression;
|
||||
use crate::parser::{Flag, Span};
|
||||
use derive_new::new;
|
||||
use indexmap::IndexMap;
|
||||
use log::trace;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum NamedValue {
|
||||
AbsentSwitch,
|
||||
PresentSwitch(Span),
|
||||
AbsentValue,
|
||||
Value(Expression),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, new)]
|
||||
pub struct NamedArguments {
|
||||
#[new(default)]
|
||||
crate named: IndexMap<String, NamedValue>,
|
||||
}
|
||||
|
||||
impl NamedArguments {
|
||||
pub fn insert_switch(&mut self, name: impl Into<String>, switch: Option<Flag>) {
|
||||
let name = name.into();
|
||||
trace!("Inserting switch -- {} = {:?}", name, switch);
|
||||
|
||||
match switch {
|
||||
None => self.named.insert(name.into(), NamedValue::AbsentSwitch),
|
||||
Some(flag) => self
|
||||
.named
|
||||
.insert(name, NamedValue::PresentSwitch(*flag.name())),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn insert_optional(&mut self, name: impl Into<String>, expr: Option<Expression>) {
|
||||
match expr {
|
||||
None => self.named.insert(name.into(), NamedValue::AbsentValue),
|
||||
Some(expr) => self.named.insert(name.into(), NamedValue::Value(expr)),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn insert_mandatory(&mut self, name: impl Into<String>, expr: Expression) {
|
||||
self.named.insert(name.into(), NamedValue::Value(expr));
|
||||
}
|
||||
}
|
10
src/parser/hir/path.rs
Normal file
10
src/parser/hir/path.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
use crate::parser::{hir::Expression, Spanned};
|
||||
use derive_new::new;
|
||||
use getset::Getters;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Getters, new)]
|
||||
#[get = "crate"]
|
||||
pub struct Path {
|
||||
head: Expression,
|
||||
tail: Vec<Spanned<String>>,
|
||||
}
|
13
src/parser/parse2.rs
Normal file
13
src/parser/parse2.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
crate mod call_node;
|
||||
crate mod files;
|
||||
crate mod flag;
|
||||
crate mod operator;
|
||||
crate mod parser;
|
||||
crate mod pipeline;
|
||||
crate mod span;
|
||||
crate mod text;
|
||||
crate mod token_tree;
|
||||
crate mod token_tree_builder;
|
||||
crate mod tokens;
|
||||
crate mod unit;
|
||||
crate mod util;
|
26
src/parser/parse2/call_node.rs
Normal file
26
src/parser/parse2/call_node.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use crate::parser::TokenNode;
|
||||
use getset::Getters;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Getters)]
|
||||
pub struct CallNode {
|
||||
#[get = "crate"]
|
||||
head: Box<TokenNode>,
|
||||
#[get = "crate"]
|
||||
children: Option<Vec<TokenNode>>,
|
||||
}
|
||||
|
||||
impl CallNode {
|
||||
pub fn new(head: Box<TokenNode>, children: Vec<TokenNode>) -> CallNode {
|
||||
if children.len() == 0 {
|
||||
CallNode {
|
||||
head,
|
||||
children: None,
|
||||
}
|
||||
} else {
|
||||
CallNode {
|
||||
head,
|
||||
children: Some(children),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
77
src/parser/parse2/files.rs
Normal file
77
src/parser/parse2/files.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use crate::parser::parse2::span::Span;
|
||||
use derive_new::new;
|
||||
use language_reporting::{FileName, Location};
|
||||
|
||||
#[derive(new, Debug, Clone)]
|
||||
pub struct Files {
|
||||
snippet: String,
|
||||
}
|
||||
|
||||
impl language_reporting::ReportingFiles for Files {
|
||||
type Span = Span;
|
||||
type FileId = usize;
|
||||
|
||||
fn byte_span(
|
||||
&self,
|
||||
_file: Self::FileId,
|
||||
from_index: usize,
|
||||
to_index: usize,
|
||||
) -> Option<Self::Span> {
|
||||
Some(Span::from((from_index, to_index)))
|
||||
}
|
||||
fn file_id(&self, _span: Self::Span) -> Self::FileId {
|
||||
0
|
||||
}
|
||||
fn file_name(&self, _file: Self::FileId) -> FileName {
|
||||
FileName::Verbatim(format!("<eval>"))
|
||||
}
|
||||
fn byte_index(&self, _file: Self::FileId, _line: usize, _column: usize) -> Option<usize> {
|
||||
unimplemented!("byte_index")
|
||||
}
|
||||
fn location(&self, _file: Self::FileId, byte_index: usize) -> Option<Location> {
|
||||
let source = &self.snippet;
|
||||
let mut seen_lines = 0;
|
||||
let mut seen_bytes = 0;
|
||||
|
||||
for (pos, _) in source.match_indices('\n') {
|
||||
if pos > byte_index {
|
||||
return Some(language_reporting::Location::new(
|
||||
seen_lines,
|
||||
byte_index - seen_bytes,
|
||||
));
|
||||
} else {
|
||||
seen_lines += 1;
|
||||
seen_bytes = pos;
|
||||
}
|
||||
}
|
||||
|
||||
if seen_lines == 0 {
|
||||
Some(language_reporting::Location::new(0, byte_index))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn line_span(&self, _file: Self::FileId, lineno: usize) -> Option<Self::Span> {
|
||||
let source = &self.snippet;
|
||||
let mut seen_lines = 0;
|
||||
let mut seen_bytes = 0;
|
||||
|
||||
for (pos, _) in source.match_indices('\n') {
|
||||
if seen_lines == lineno {
|
||||
return Some(Span::from((seen_bytes, pos)));
|
||||
} else {
|
||||
seen_lines += 1;
|
||||
seen_bytes = pos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if seen_lines == 0 {
|
||||
Some(Span::from((0, self.snippet.len() - 1)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn source(&self, span: Self::Span) -> Option<String> {
|
||||
Some(self.snippet[span.start..span.end].to_string())
|
||||
}
|
||||
}
|
17
src/parser/parse2/flag.rs
Normal file
17
src/parser/parse2/flag.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use crate::parser::Span;
|
||||
use derive_new::new;
|
||||
use getset::Getters;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
|
||||
pub enum FlagKind {
|
||||
Shorthand,
|
||||
Longhand,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Getters, new)]
|
||||
#[get = "crate"]
|
||||
pub struct Flag {
|
||||
kind: FlagKind,
|
||||
name: Span,
|
||||
}
|
51
src/parser/parse2/operator.rs
Normal file
51
src/parser/parse2/operator.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
|
||||
pub enum Operator {
|
||||
Equal,
|
||||
NotEqual,
|
||||
LessThan,
|
||||
GreaterThan,
|
||||
LessThanOrEqual,
|
||||
GreaterThanOrEqual,
|
||||
}
|
||||
|
||||
impl Operator {
|
||||
#[allow(unused)]
|
||||
pub fn print(&self) -> String {
|
||||
self.as_str().to_string()
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
match *self {
|
||||
Operator::Equal => "==",
|
||||
Operator::NotEqual => "!=",
|
||||
Operator::LessThan => "<",
|
||||
Operator::GreaterThan => ">",
|
||||
Operator::LessThanOrEqual => "<=",
|
||||
Operator::GreaterThanOrEqual => ">=",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Operator {
|
||||
fn from(input: &str) -> Operator {
|
||||
Operator::from_str(input).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
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(()),
|
||||
}
|
||||
}
|
||||
}
|
1032
src/parser/parse2/parser.rs
Normal file
1032
src/parser/parse2/parser.rs
Normal file
File diff suppressed because it is too large
Load diff
18
src/parser/parse2/pipeline.rs
Normal file
18
src/parser/parse2/pipeline.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use crate::parser::{CallNode, Span, Spanned};
|
||||
use derive_new::new;
|
||||
use getset::Getters;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, new)]
|
||||
pub struct Pipeline {
|
||||
crate parts: Vec<PipelineElement>,
|
||||
crate post_ws: Option<Span>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Getters, new)]
|
||||
pub struct PipelineElement {
|
||||
pub pre_ws: Option<Span>,
|
||||
#[get = "crate"]
|
||||
call: Spanned<CallNode>,
|
||||
pub post_ws: Option<Span>,
|
||||
pub post_pipe: Option<Span>,
|
||||
}
|
129
src/parser/parse2/span.rs
Normal file
129
src/parser/parse2/span.rs
Normal file
|
@ -0,0 +1,129 @@
|
|||
use crate::Text;
|
||||
use derive_new::new;
|
||||
use getset::Getters;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(
|
||||
new, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash, Getters,
|
||||
)]
|
||||
#[get = "crate"]
|
||||
pub struct Spanned<T> {
|
||||
crate span: Span,
|
||||
crate item: T,
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for Spanned<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
&self.item
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Spanned<T> {
|
||||
crate fn from_item(item: T, span: impl Into<Span>) -> Spanned<T> {
|
||||
Spanned {
|
||||
span: span.into(),
|
||||
item,
|
||||
}
|
||||
}
|
||||
|
||||
crate fn map<U>(self, input: impl FnOnce(T) -> U) -> Spanned<U> {
|
||||
let Spanned { span, item } = self;
|
||||
|
||||
let mapped = input(item);
|
||||
Spanned { span, item: mapped }
|
||||
}
|
||||
|
||||
crate fn copy_span<U>(&self, output: U) -> Spanned<U> {
|
||||
let Spanned { span, .. } = self;
|
||||
|
||||
Spanned {
|
||||
span: *span,
|
||||
item: output,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn source(&self, source: &Text) -> Text {
|
||||
Text::from(self.span().slice(source))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize, Hash)]
|
||||
pub struct Span {
|
||||
crate start: usize,
|
||||
crate end: usize,
|
||||
// source: &'source str,
|
||||
}
|
||||
|
||||
impl From<&Span> for Span {
|
||||
fn from(input: &Span) -> Span {
|
||||
*input
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nom_locate::LocatedSpan<&str>> for Span {
|
||||
fn from(input: nom_locate::LocatedSpan<&str>) -> Span {
|
||||
Span {
|
||||
start: input.offset,
|
||||
end: input.offset + input.fragment.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<(nom_locate::LocatedSpan<T>, nom_locate::LocatedSpan<T>)> for Span {
|
||||
fn from(input: (nom_locate::LocatedSpan<T>, nom_locate::LocatedSpan<T>)) -> Span {
|
||||
Span {
|
||||
start: input.0.offset,
|
||||
end: input.1.offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(usize, usize)> for Span {
|
||||
fn from(input: (usize, usize)) -> Span {
|
||||
Span {
|
||||
start: input.0,
|
||||
end: input.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&std::ops::Range<usize>> for Span {
|
||||
fn from(input: &std::ops::Range<usize>) -> Span {
|
||||
Span {
|
||||
start: input.start,
|
||||
end: input.end,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Span {
|
||||
pub fn slice(&self, source: &'a str) -> &'a str {
|
||||
&source[self.start..self.end]
|
||||
}
|
||||
}
|
||||
|
||||
impl language_reporting::ReportingSpan for Span {
|
||||
fn with_start(&self, start: usize) -> Self {
|
||||
Span {
|
||||
start,
|
||||
end: self.end,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_end(&self, end: usize) -> Self {
|
||||
Span {
|
||||
start: self.start,
|
||||
end,
|
||||
}
|
||||
}
|
||||
|
||||
fn start(&self) -> usize {
|
||||
self.start
|
||||
}
|
||||
|
||||
fn end(&self) -> usize {
|
||||
self.end
|
||||
}
|
||||
}
|
204
src/parser/parse2/text.rs
Normal file
204
src/parser/parse2/text.rs
Normal file
|
@ -0,0 +1,204 @@
|
|||
use std::cmp::Ordering;
|
||||
use std::hash::Hash;
|
||||
use std::hash::Hasher;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A "Text" is like a string except that it can be cheaply cloned.
|
||||
/// You can also "extract" subtexts quite cheaply. You can also deref
|
||||
/// an `&Text` into a `&str` for interoperability.
|
||||
///
|
||||
/// Used to represent the value of an input file.
|
||||
#[derive(Clone)]
|
||||
pub struct Text {
|
||||
text: Arc<String>,
|
||||
start: usize,
|
||||
end: usize,
|
||||
}
|
||||
|
||||
impl Text {
|
||||
/// Modifies this restrict to a subset of its current range.
|
||||
pub fn select(&mut self, range: Range<usize>) {
|
||||
let len = range.end - range.start;
|
||||
let new_start = self.start + range.start;
|
||||
let new_end = new_start + len;
|
||||
assert!(new_end <= self.end);
|
||||
|
||||
self.start = new_start;
|
||||
self.end = new_end;
|
||||
}
|
||||
|
||||
/// Extract a new `Text` that is a subset of an old `Text`
|
||||
/// -- `text.extract(1..3)` is similar to `&foo[1..3]` except that
|
||||
/// it gives back an owned value instead of a borrowed value.
|
||||
pub fn slice(&self, range: Range<usize>) -> Self {
|
||||
let mut result = self.clone();
|
||||
result.select(range);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<String>> for Text {
|
||||
fn from(text: Arc<String>) -> Self {
|
||||
let end = text.len();
|
||||
Self {
|
||||
text,
|
||||
start: 0,
|
||||
end,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for Text {
|
||||
fn as_ref(&self) -> &str {
|
||||
&*self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Text {
|
||||
fn from(text: String) -> Self {
|
||||
Text::from(Arc::new(text))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&String> for Text {
|
||||
fn from(text: &String) -> Self {
|
||||
Text::from(text.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Text {
|
||||
fn from(text: &str) -> Self {
|
||||
Text::from(text.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::borrow::Borrow<str> for Text {
|
||||
fn borrow(&self) -> &str {
|
||||
&*self
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Text {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &str {
|
||||
&self.text[self.start..self.end]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Text {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
<str as std::fmt::Display>::fmt(self, fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Text {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
<str as std::fmt::Debug>::fmt(self, fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Text> for Text {
|
||||
fn eq(&self, other: &Text) -> bool {
|
||||
let this: &str = self;
|
||||
let other: &str = other;
|
||||
this == other
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Text {}
|
||||
|
||||
impl PartialEq<str> for Text {
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
let this: &str = self;
|
||||
this == other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<String> for Text {
|
||||
fn eq(&self, other: &String) -> bool {
|
||||
let this: &str = self;
|
||||
let other: &str = other;
|
||||
this == other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Text> for str {
|
||||
fn eq(&self, other: &Text) -> bool {
|
||||
other == self
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Text> for String {
|
||||
fn eq(&self, other: &Text) -> bool {
|
||||
other == self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> PartialEq<&T> for Text
|
||||
where
|
||||
Text: PartialEq<T>,
|
||||
{
|
||||
fn eq(&self, other: &&T) -> bool {
|
||||
self == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Text {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
<str as Hash>::hash(self, state)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<Text> for Text {
|
||||
fn partial_cmp(&self, other: &Text) -> Option<Ordering> {
|
||||
let this: &str = self;
|
||||
let other: &str = other;
|
||||
this.partial_cmp(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Text {
|
||||
fn cmp(&self, other: &Text) -> Ordering {
|
||||
let this: &str = self;
|
||||
let other: &str = other;
|
||||
this.cmp(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<str> for Text {
|
||||
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
|
||||
let this: &str = self;
|
||||
this.partial_cmp(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<String> for Text {
|
||||
fn partial_cmp(&self, other: &String) -> Option<Ordering> {
|
||||
let this: &str = self;
|
||||
let other: &str = other;
|
||||
this.partial_cmp(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<Text> for str {
|
||||
fn partial_cmp(&self, other: &Text) -> Option<Ordering> {
|
||||
other.partial_cmp(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<Text> for String {
|
||||
fn partial_cmp(&self, other: &Text) -> Option<Ordering> {
|
||||
other.partial_cmp(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> PartialOrd<&T> for Text
|
||||
where
|
||||
Text: PartialOrd<T>,
|
||||
{
|
||||
fn partial_cmp(&self, other: &&T) -> Option<Ordering> {
|
||||
self.partial_cmp(*other)
|
||||
}
|
||||
}
|
93
src/parser/parse2/token_tree.rs
Normal file
93
src/parser/parse2/token_tree.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use crate::errors::ShellError;
|
||||
use crate::parser::parse2::{call_node::*, flag::*, operator::*, pipeline::*, span::*, tokens::*};
|
||||
use crate::Text;
|
||||
use derive_new::new;
|
||||
use enum_utils::FromStr;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum TokenNode {
|
||||
Token(Token),
|
||||
#[allow(unused)]
|
||||
Call(Spanned<CallNode>),
|
||||
Delimited(Spanned<DelimitedNode>),
|
||||
Pipeline(Spanned<Pipeline>),
|
||||
Operator(Spanned<Operator>),
|
||||
Flag(Spanned<Flag>),
|
||||
Identifier(Span),
|
||||
Whitespace(Span),
|
||||
#[allow(unused)]
|
||||
Error(Spanned<Box<ShellError>>),
|
||||
Path(Spanned<PathNode>),
|
||||
}
|
||||
|
||||
impl TokenNode {
|
||||
pub fn span(&self) -> Span {
|
||||
match self {
|
||||
TokenNode::Token(t) => t.span,
|
||||
TokenNode::Call(s) => s.span,
|
||||
TokenNode::Delimited(s) => s.span,
|
||||
TokenNode::Pipeline(s) => s.span,
|
||||
TokenNode::Operator(s) => s.span,
|
||||
TokenNode::Flag(s) => s.span,
|
||||
TokenNode::Identifier(s) => *s,
|
||||
TokenNode::Whitespace(s) => *s,
|
||||
TokenNode::Error(s) => s.span,
|
||||
TokenNode::Path(s) => s.span,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_external_arg(&self, source: &Text) -> String {
|
||||
self.span().slice(source).to_string()
|
||||
}
|
||||
|
||||
pub fn source(&self, source: &'a Text) -> &'a str {
|
||||
self.span().slice(source)
|
||||
}
|
||||
|
||||
pub fn is_bare(&self) -> bool {
|
||||
match self {
|
||||
TokenNode::Token(Spanned {
|
||||
item: RawToken::Bare,
|
||||
..
|
||||
}) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
crate fn as_flag(&self, value: &str, source: &Text) -> Option<Spanned<Flag>> {
|
||||
match self {
|
||||
TokenNode::Flag(
|
||||
flag @ Spanned {
|
||||
item: Flag { .. }, ..
|
||||
},
|
||||
) if value == flag.name().slice(source) => Some(*flag),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_pipeline(&self) -> Result<Pipeline, ShellError> {
|
||||
match self {
|
||||
TokenNode::Pipeline(Spanned { item, .. }) => Ok(item.clone()),
|
||||
_ => Err(ShellError::string("unimplemented")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, new)]
|
||||
pub struct DelimitedNode {
|
||||
delimiter: Delimiter,
|
||||
children: Vec<TokenNode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, FromStr)]
|
||||
pub enum Delimiter {
|
||||
Paren,
|
||||
Brace,
|
||||
Square,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, new)]
|
||||
pub struct PathNode {
|
||||
head: Box<TokenNode>,
|
||||
tail: Vec<TokenNode>,
|
||||
}
|
402
src/parser/parse2/token_tree_builder.rs
Normal file
402
src/parser/parse2/token_tree_builder.rs
Normal file
|
@ -0,0 +1,402 @@
|
|||
#[allow(unused)]
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::parser::parse2::flag::{Flag, FlagKind};
|
||||
use crate::parser::parse2::operator::Operator;
|
||||
use crate::parser::parse2::pipeline::{Pipeline, PipelineElement};
|
||||
use crate::parser::parse2::span::{Span, Spanned};
|
||||
use crate::parser::parse2::token_tree::{DelimitedNode, Delimiter, PathNode, TokenNode};
|
||||
use crate::parser::parse2::tokens::{RawToken, Token};
|
||||
use crate::parser::parse2::unit::Unit;
|
||||
use crate::parser::CallNode;
|
||||
use derive_new::new;
|
||||
|
||||
#[derive(new)]
|
||||
pub struct TokenTreeBuilder {
|
||||
#[new(default)]
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub type CurriedNode<T> = Box<dyn FnOnce(&mut TokenTreeBuilder) -> T + 'static>;
|
||||
pub type CurriedToken = Box<dyn FnOnce(&mut TokenTreeBuilder) -> TokenNode + 'static>;
|
||||
pub type CurriedCall = Box<dyn FnOnce(&mut TokenTreeBuilder) -> Spanned<CallNode> + 'static>;
|
||||
|
||||
#[allow(unused)]
|
||||
impl TokenTreeBuilder {
|
||||
pub fn build(block: impl FnOnce(&mut Self) -> TokenNode) -> TokenNode {
|
||||
let mut builder = TokenTreeBuilder::new();
|
||||
block(&mut builder)
|
||||
}
|
||||
|
||||
pub fn pipeline(input: Vec<(Option<&str>, CurriedCall, Option<&str>)>) -> CurriedToken {
|
||||
let input: Vec<(Option<String>, CurriedCall, Option<String>)> = input
|
||||
.into_iter()
|
||||
.map(|(pre, call, post)| {
|
||||
(
|
||||
pre.map(|s| s.to_string()),
|
||||
call,
|
||||
post.map(|s| s.to_string()),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Box::new(move |b| {
|
||||
let start = b.pos;
|
||||
|
||||
let mut out: Vec<PipelineElement> = vec![];
|
||||
|
||||
let mut input = input.into_iter().peekable();
|
||||
let (pre, call, post) = input
|
||||
.next()
|
||||
.expect("A pipeline must contain at least one element");
|
||||
|
||||
let pre_span = pre.map(|pre| b.consume(&pre));
|
||||
let call = call(b);
|
||||
let post_span = post.map(|post| b.consume(&post));
|
||||
let pipe = input.peek().map(|_| Span::from(b.consume("|")));
|
||||
out.push(PipelineElement::new(
|
||||
pre_span.map(Span::from),
|
||||
call,
|
||||
post_span.map(Span::from),
|
||||
pipe,
|
||||
));
|
||||
|
||||
loop {
|
||||
match input.next() {
|
||||
None => break,
|
||||
Some((pre, call, post)) => {
|
||||
let pre_span = pre.map(|pre| b.consume(&pre));
|
||||
let call = call(b);
|
||||
let post_span = post.map(|post| b.consume(&post));
|
||||
|
||||
let pipe = input.peek().map(|_| Span::from(b.consume("|")));
|
||||
|
||||
out.push(PipelineElement::new(
|
||||
pre_span.map(Span::from),
|
||||
call,
|
||||
post_span.map(Span::from),
|
||||
pipe,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let end = b.pos;
|
||||
|
||||
TokenTreeBuilder::spanned_pipeline((out, None), (start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_pipeline(
|
||||
input: (Vec<PipelineElement>, Option<Span>),
|
||||
span: impl Into<Span>,
|
||||
) -> TokenNode {
|
||||
TokenNode::Pipeline(Spanned::from_item(
|
||||
Pipeline::new(input.0, input.1.into()),
|
||||
span,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn op(input: impl Into<Operator>) -> CurriedToken {
|
||||
let input = input.into();
|
||||
|
||||
Box::new(move |b| {
|
||||
let (start, end) = b.consume(input.as_str());
|
||||
|
||||
b.pos = end;
|
||||
|
||||
TokenTreeBuilder::spanned_op(input, (start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_op(input: impl Into<Operator>, span: impl Into<Span>) -> TokenNode {
|
||||
TokenNode::Operator(Spanned::from_item(input.into(), span.into()))
|
||||
}
|
||||
|
||||
pub fn string(input: impl Into<String>) -> CurriedToken {
|
||||
let input = input.into();
|
||||
|
||||
Box::new(move |b| {
|
||||
let (start, _) = b.consume("\"");
|
||||
let (inner_start, inner_end) = b.consume(&input);
|
||||
let (_, end) = b.consume("\"");
|
||||
b.pos = end;
|
||||
|
||||
TokenTreeBuilder::spanned_string((inner_start, inner_end), (start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_string(input: impl Into<Span>, span: impl Into<Span>) -> TokenNode {
|
||||
TokenNode::Token(Spanned::from_item(
|
||||
RawToken::String(input.into()),
|
||||
span.into(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn bare(input: impl Into<String>) -> CurriedToken {
|
||||
let input = input.into();
|
||||
|
||||
Box::new(move |b| {
|
||||
let (start, end) = b.consume(&input);
|
||||
b.pos = end;
|
||||
|
||||
TokenTreeBuilder::spanned_bare((start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_bare(input: impl Into<Span>) -> TokenNode {
|
||||
TokenNode::Token(Spanned::from_item(RawToken::Bare, input.into()))
|
||||
}
|
||||
|
||||
pub fn int(input: impl Into<i64>) -> CurriedToken {
|
||||
let int = input.into();
|
||||
|
||||
Box::new(move |b| {
|
||||
let (start, end) = b.consume(&int.to_string());
|
||||
b.pos = end;
|
||||
|
||||
TokenTreeBuilder::spanned_int(int, (start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_int(input: impl Into<i64>, span: impl Into<Span>) -> TokenNode {
|
||||
TokenNode::Token(Token::from_item(RawToken::Integer(input.into()), span))
|
||||
}
|
||||
|
||||
pub fn size(int: impl Into<i64>, unit: impl Into<Unit>) -> CurriedToken {
|
||||
let int = int.into();
|
||||
let unit = unit.into();
|
||||
|
||||
Box::new(move |b| {
|
||||
let (start, _) = b.consume(&int.to_string());
|
||||
let (_, end) = b.consume(unit.as_str());
|
||||
b.pos = end;
|
||||
|
||||
TokenTreeBuilder::spanned_size((int, unit), (start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_size(
|
||||
input: (impl Into<i64>, impl Into<Unit>),
|
||||
span: impl Into<Span>,
|
||||
) -> TokenNode {
|
||||
let (int, unit) = (input.0.into(), input.1.into());
|
||||
|
||||
TokenNode::Token(Spanned::from_item(RawToken::Size(int, unit), span))
|
||||
}
|
||||
|
||||
pub fn path(head: CurriedToken, tail: Vec<CurriedToken>) -> CurriedToken {
|
||||
Box::new(move |b| {
|
||||
let start = b.pos;
|
||||
let head = head(b);
|
||||
|
||||
let mut output = vec![];
|
||||
|
||||
for item in tail {
|
||||
b.consume(".");
|
||||
|
||||
output.push(item(b));
|
||||
}
|
||||
|
||||
let end = b.pos;
|
||||
|
||||
TokenTreeBuilder::spanned_path((head, output), (start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_path(input: (TokenNode, Vec<TokenNode>), span: impl Into<Span>) -> TokenNode {
|
||||
TokenNode::Path(Spanned::from_item(
|
||||
PathNode::new(Box::new(input.0), input.1),
|
||||
span,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn var(input: impl Into<String>) -> CurriedToken {
|
||||
let input = input.into();
|
||||
|
||||
Box::new(move |b| {
|
||||
let (start, _) = b.consume("$");
|
||||
let (inner_start, end) = b.consume(&input);
|
||||
|
||||
TokenTreeBuilder::spanned_var((inner_start, end), (start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_var(input: impl Into<Span>, span: impl Into<Span>) -> TokenNode {
|
||||
TokenNode::Token(Spanned::from_item(
|
||||
RawToken::Variable(input.into()),
|
||||
span.into(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn flag(input: impl Into<String>) -> CurriedToken {
|
||||
let input = input.into();
|
||||
|
||||
Box::new(move |b| {
|
||||
let (start, _) = b.consume("--");
|
||||
let (inner_start, end) = b.consume(&input);
|
||||
|
||||
TokenTreeBuilder::spanned_flag((inner_start, end), (start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_flag(input: impl Into<Span>, span: impl Into<Span>) -> TokenNode {
|
||||
TokenNode::Flag(Spanned::from_item(
|
||||
Flag::new(FlagKind::Longhand, input.into()),
|
||||
span.into(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn shorthand(input: impl Into<String>) -> CurriedToken {
|
||||
let input = input.into();
|
||||
|
||||
Box::new(move |b| {
|
||||
let (start, _) = b.consume("-");
|
||||
let (inner_start, end) = b.consume(&input);
|
||||
|
||||
TokenTreeBuilder::spanned_shorthand((inner_start, end), (start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_shorthand(input: impl Into<Span>, span: impl Into<Span>) -> TokenNode {
|
||||
TokenNode::Flag(Spanned::from_item(
|
||||
Flag::new(FlagKind::Shorthand, input.into()),
|
||||
span.into(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn ident(input: impl Into<String>) -> CurriedToken {
|
||||
let input = input.into();
|
||||
|
||||
Box::new(move |b| {
|
||||
let (start, end) = b.consume(&input);
|
||||
TokenTreeBuilder::spanned_ident((start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_ident(span: impl Into<Span>) -> TokenNode {
|
||||
TokenNode::Identifier(span.into())
|
||||
}
|
||||
|
||||
pub fn call(head: CurriedToken, input: Vec<CurriedToken>) -> CurriedCall {
|
||||
Box::new(move |b| {
|
||||
let start = b.pos;
|
||||
|
||||
let head_node = head(b);
|
||||
|
||||
let mut nodes = vec![head_node];
|
||||
for item in input {
|
||||
nodes.push(item(b));
|
||||
}
|
||||
|
||||
let end = b.pos;
|
||||
|
||||
TokenTreeBuilder::spanned_call(nodes, (start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_call(input: Vec<TokenNode>, span: impl Into<Span>) -> Spanned<CallNode> {
|
||||
if input.len() == 0 {
|
||||
panic!("BUG: spanned call (TODO)")
|
||||
}
|
||||
|
||||
let mut input = input.into_iter();
|
||||
|
||||
let head = input.next().unwrap();
|
||||
let tail = input.collect();
|
||||
|
||||
Spanned::from_item(CallNode::new(Box::new(head), tail), span)
|
||||
}
|
||||
|
||||
pub fn parens(input: Vec<CurriedToken>) -> CurriedToken {
|
||||
Box::new(move |b| {
|
||||
let (start, _) = b.consume("(");
|
||||
let mut output = vec![];
|
||||
for item in input {
|
||||
output.push(item(b));
|
||||
}
|
||||
|
||||
let (_, end) = b.consume(")");
|
||||
|
||||
TokenTreeBuilder::spanned_parens(output, (start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_parens(input: impl Into<Vec<TokenNode>>, span: impl Into<Span>) -> TokenNode {
|
||||
TokenNode::Delimited(Spanned::from_item(
|
||||
DelimitedNode::new(Delimiter::Paren, input.into()),
|
||||
span,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn square(input: Vec<CurriedToken>) -> CurriedToken {
|
||||
Box::new(move |b| {
|
||||
let (start, _) = b.consume("[");
|
||||
let mut output = vec![];
|
||||
for item in input {
|
||||
output.push(item(b));
|
||||
}
|
||||
|
||||
let (_, end) = b.consume("]");
|
||||
|
||||
TokenTreeBuilder::spanned_square(output, (start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_square(input: impl Into<Vec<TokenNode>>, span: impl Into<Span>) -> TokenNode {
|
||||
TokenNode::Delimited(Spanned::from_item(
|
||||
DelimitedNode::new(Delimiter::Square, input.into()),
|
||||
span,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn braced(input: Vec<CurriedToken>) -> CurriedToken {
|
||||
Box::new(move |b| {
|
||||
let (start, _) = b.consume("{ ");
|
||||
let mut output = vec![];
|
||||
for item in input {
|
||||
output.push(item(b));
|
||||
}
|
||||
|
||||
let (_, end) = b.consume(" }");
|
||||
|
||||
TokenTreeBuilder::spanned_brace(output, (start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_brace(input: impl Into<Vec<TokenNode>>, span: impl Into<Span>) -> TokenNode {
|
||||
TokenNode::Delimited(Spanned::from_item(
|
||||
DelimitedNode::new(Delimiter::Brace, input.into()),
|
||||
span,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn sp() -> CurriedToken {
|
||||
Box::new(|b| {
|
||||
let (start, end) = b.consume(" ");
|
||||
TokenNode::Whitespace(Span::from((start, end)))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ws(input: impl Into<String>) -> CurriedToken {
|
||||
let input = input.into();
|
||||
|
||||
Box::new(move |b| {
|
||||
let (start, end) = b.consume(&input);
|
||||
TokenTreeBuilder::spanned_ws((start, end))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spanned_ws(span: impl Into<Span>) -> TokenNode {
|
||||
let span = span.into();
|
||||
|
||||
TokenNode::Whitespace(span.into())
|
||||
}
|
||||
|
||||
fn consume(&mut self, input: &str) -> (usize, usize) {
|
||||
let start = self.pos;
|
||||
self.pos += input.len();
|
||||
(start, self.pos)
|
||||
}
|
||||
}
|
13
src/parser/parse2/tokens.rs
Normal file
13
src/parser/parse2/tokens.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
use crate::parser::parse2::span::*;
|
||||
use crate::parser::parse2::unit::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum RawToken {
|
||||
Integer(i64),
|
||||
Size(i64, Unit),
|
||||
String(Span),
|
||||
Variable(Span),
|
||||
Bare,
|
||||
}
|
||||
|
||||
pub type Token = Spanned<RawToken>;
|
46
src/parser/parse2/unit.rs
Normal file
46
src/parser/parse2/unit.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
|
||||
pub enum Unit {
|
||||
B,
|
||||
KB,
|
||||
MB,
|
||||
GB,
|
||||
TB,
|
||||
PB,
|
||||
}
|
||||
|
||||
impl Unit {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match *self {
|
||||
Unit::B => "B",
|
||||
Unit::KB => "KB",
|
||||
Unit::MB => "MB",
|
||||
Unit::GB => "GB",
|
||||
Unit::TB => "TB",
|
||||
Unit::PB => "PB",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Unit {
|
||||
fn from(input: &str) -> Unit {
|
||||
Unit::from_str(input).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Unit {
|
||||
type Err = ();
|
||||
fn from_str(input: &str) -> Result<Self, <Self as std::str::FromStr>::Err> {
|
||||
match input {
|
||||
"B" => Ok(Unit::B),
|
||||
"KB" => Ok(Unit::KB),
|
||||
"MB" => Ok(Unit::MB),
|
||||
"GB" => Ok(Unit::GB),
|
||||
"TB" => Ok(Unit::TB),
|
||||
"PB" => Ok(Unit::PB),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
1
src/parser/parse2/util.rs
Normal file
1
src/parser/parse2/util.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
251
src/parser/parse_command.rs
Normal file
251
src/parser/parse_command.rs
Normal file
|
@ -0,0 +1,251 @@
|
|||
use crate::errors::ShellError;
|
||||
use crate::parser::registry::{CommandConfig, CommandRegistry, NamedType};
|
||||
use crate::parser::{baseline_parse_tokens, CallNode, Spanned};
|
||||
use crate::parser::{
|
||||
hir::{self, NamedArguments},
|
||||
Flag, RawToken, TokenNode,
|
||||
};
|
||||
use crate::Text;
|
||||
use log::trace;
|
||||
|
||||
pub fn parse_command(
|
||||
config: &CommandConfig,
|
||||
registry: &dyn CommandRegistry,
|
||||
call: &Spanned<CallNode>,
|
||||
source: &Text,
|
||||
) -> Result<hir::Call, ShellError> {
|
||||
let Spanned { item: call, .. } = call;
|
||||
|
||||
trace!("Processing {:?}", config);
|
||||
|
||||
let head = parse_command_head(call.head())?;
|
||||
|
||||
let children: Option<Vec<TokenNode>> = call.children().as_ref().map(|nodes| {
|
||||
nodes
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|node| match node {
|
||||
TokenNode::Whitespace(_) => false,
|
||||
_ => true,
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
match parse_command_tail(&config, registry, children, source)? {
|
||||
None => Ok(hir::Call::new(Box::new(head), None, None)),
|
||||
Some((positional, named)) => Ok(hir::Call::new(Box::new(head), positional, named)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_command_head(head: &TokenNode) -> Result<hir::Expression, ShellError> {
|
||||
match head {
|
||||
TokenNode::Token(
|
||||
spanned @ Spanned {
|
||||
item: RawToken::Bare,
|
||||
..
|
||||
},
|
||||
) => Ok(spanned.map(|_| hir::RawExpression::Literal(hir::Literal::Bare))),
|
||||
|
||||
TokenNode::Token(Spanned {
|
||||
item: RawToken::String(inner_span),
|
||||
span,
|
||||
}) => Ok(Spanned::from_item(
|
||||
hir::RawExpression::Literal(hir::Literal::String(*inner_span)),
|
||||
*span,
|
||||
)),
|
||||
|
||||
other => Err(ShellError::unexpected(&format!(
|
||||
"command head -> {:?}",
|
||||
other
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_command_tail(
|
||||
config: &CommandConfig,
|
||||
registry: &dyn CommandRegistry,
|
||||
tail: Option<Vec<TokenNode>>,
|
||||
source: &Text,
|
||||
) -> Result<Option<(Option<Vec<hir::Expression>>, Option<NamedArguments>)>, ShellError> {
|
||||
let mut tail = match tail {
|
||||
None => return Ok(None),
|
||||
Some(tail) => tail,
|
||||
};
|
||||
|
||||
let mut named = NamedArguments::new();
|
||||
|
||||
for (name, kind) in config.named() {
|
||||
trace!("looking for {} : {:?}", name, kind);
|
||||
|
||||
match kind {
|
||||
NamedType::Switch => {
|
||||
let (rest, flag) = extract_switch(name, tail, source);
|
||||
|
||||
tail = rest;
|
||||
|
||||
named.insert_switch(name, flag);
|
||||
}
|
||||
NamedType::Mandatory(kind) => match extract_mandatory(name, tail, source) {
|
||||
Err(err) => return Err(err), // produce a correct diagnostic
|
||||
Ok((rest, pos, _flag)) => {
|
||||
let (expr, rest) = hir::baseline_parse_next_expr(
|
||||
&rest[pos..],
|
||||
registry,
|
||||
source,
|
||||
kind.to_coerce_hint(),
|
||||
)?;
|
||||
tail = rest.to_vec();
|
||||
|
||||
named.insert_mandatory(name, expr);
|
||||
}
|
||||
},
|
||||
NamedType::Optional(kind) => match extract_optional(name, tail, source) {
|
||||
Err(err) => return Err(err), // produce a correct diagnostic
|
||||
Ok((rest, Some((pos, _flag)))) => {
|
||||
let (expr, rest) = hir::baseline_parse_next_expr(
|
||||
&rest[pos..],
|
||||
registry,
|
||||
source,
|
||||
kind.to_coerce_hint(),
|
||||
)?;
|
||||
tail = rest.to_vec();
|
||||
|
||||
named.insert_optional(name, Some(expr));
|
||||
}
|
||||
|
||||
Ok((rest, None)) => {
|
||||
tail = rest;
|
||||
|
||||
named.insert_optional(name, None);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let mut positional = vec![];
|
||||
let mandatory = config.mandatory_positional();
|
||||
|
||||
for arg in mandatory {
|
||||
if tail.len() == 0 {
|
||||
return Err(ShellError::unimplemented("Missing mandatory argument"));
|
||||
}
|
||||
|
||||
let (result, rest) =
|
||||
hir::baseline_parse_next_expr(&tail, registry, source, arg.to_coerce_hint())?;
|
||||
|
||||
positional.push(result);
|
||||
|
||||
tail = rest.to_vec();
|
||||
}
|
||||
|
||||
let optional = config.optional_positional();
|
||||
|
||||
for arg in optional {
|
||||
if tail.len() == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let (result, rest) =
|
||||
hir::baseline_parse_next_expr(&tail, registry, source, arg.to_coerce_hint())?;
|
||||
|
||||
positional.push(result);
|
||||
|
||||
tail = rest.to_vec();
|
||||
}
|
||||
|
||||
// TODO: Only do this if rest params are specified
|
||||
let remainder = baseline_parse_tokens(&tail, registry, source)?;
|
||||
positional.extend(remainder);
|
||||
|
||||
trace!("Constructed positional={:?} named={:?}", positional, named);
|
||||
|
||||
let positional = match positional {
|
||||
positional if positional.len() == 0 => None,
|
||||
positional => Some(positional),
|
||||
};
|
||||
|
||||
let named = match named {
|
||||
named if named.named.is_empty() => None,
|
||||
named => Some(named),
|
||||
};
|
||||
|
||||
trace!("Normalized positional={:?} named={:?}", positional, named);
|
||||
|
||||
Ok(Some((positional, named)))
|
||||
}
|
||||
|
||||
fn extract_switch(
|
||||
name: &str,
|
||||
mut tokens: Vec<TokenNode>,
|
||||
source: &Text,
|
||||
) -> (Vec<TokenNode>, Option<Flag>) {
|
||||
let pos = tokens
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, t)| t.as_flag(name, source).map(|f| (i, f)))
|
||||
.nth(0);
|
||||
|
||||
match pos {
|
||||
None => (tokens, None),
|
||||
Some((pos, flag)) => {
|
||||
tokens.remove(pos);
|
||||
(tokens, Some(*flag))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_mandatory(
|
||||
name: &str,
|
||||
mut tokens: Vec<TokenNode>,
|
||||
source: &Text,
|
||||
) -> Result<(Vec<TokenNode>, usize, Flag), ShellError> {
|
||||
let pos = tokens
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, t)| t.as_flag(name, source).map(|f| (i, f)))
|
||||
.nth(0);
|
||||
|
||||
match pos {
|
||||
None => Err(ShellError::unimplemented(
|
||||
"Better error: mandatory flags must be present",
|
||||
)),
|
||||
Some((pos, flag)) => {
|
||||
if tokens.len() <= pos {
|
||||
return Err(ShellError::unimplemented(
|
||||
"Better errors: mandatory flags must be followed by values",
|
||||
));
|
||||
}
|
||||
|
||||
tokens.remove(pos);
|
||||
|
||||
Ok((tokens, pos, *flag))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_optional(
|
||||
name: &str,
|
||||
mut tokens: Vec<TokenNode>,
|
||||
source: &Text,
|
||||
) -> Result<(Vec<TokenNode>, Option<(usize, Flag)>), ShellError> {
|
||||
let pos = tokens
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, t)| t.as_flag(name, source).map(|f| (i, f)))
|
||||
.nth(0);
|
||||
|
||||
match pos {
|
||||
None => Ok((tokens, None)),
|
||||
Some((pos, flag)) => {
|
||||
if tokens.len() <= pos {
|
||||
return Err(ShellError::unimplemented(
|
||||
"Better errors: optional flags must be followed by values",
|
||||
));
|
||||
}
|
||||
|
||||
tokens.remove(pos);
|
||||
|
||||
Ok((tokens, Some((pos, *flag))))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,11 @@
|
|||
use crate::evaluate::{evaluate_expr, Scope};
|
||||
use crate::parser::lexer::Spanned;
|
||||
use crate::evaluate::{evaluate_baseline_expr, Scope};
|
||||
use crate::parser::{hir, hir::ExpressionKindHint, parse_command, CallNode, Spanned};
|
||||
use crate::prelude::*;
|
||||
use derive_new::new;
|
||||
use getset::Getters;
|
||||
use indexmap::IndexMap;
|
||||
use log::trace;
|
||||
use std::fmt;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug)]
|
||||
|
@ -14,13 +18,18 @@ pub enum NamedType {
|
|||
#[derive(Debug)]
|
||||
pub enum NamedValue {
|
||||
Single,
|
||||
Tuple,
|
||||
|
||||
#[allow(unused)]
|
||||
Block,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
Array,
|
||||
impl NamedValue {
|
||||
crate fn to_coerce_hint(&self) -> Option<ExpressionKindHint> {
|
||||
match self {
|
||||
NamedValue::Single => None,
|
||||
NamedValue::Block => Some(ExpressionKindHint::Block),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
|
@ -31,61 +40,16 @@ pub enum PositionalType {
|
|||
}
|
||||
|
||||
impl PositionalType {
|
||||
crate fn name(&self) -> String {
|
||||
crate fn to_coerce_hint(&self) -> Option<ExpressionKindHint> {
|
||||
match self {
|
||||
PositionalType::Value(s) => s.clone(),
|
||||
PositionalType::Block(s) => s.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
crate fn evaluate(
|
||||
&self,
|
||||
arg: ast::Expression,
|
||||
scope: &Scope,
|
||||
) -> Result<Spanned<Value>, ShellError> {
|
||||
match self {
|
||||
PositionalType::Value(_) => evaluate_expr(&arg, scope),
|
||||
PositionalType::Block(_) => match arg {
|
||||
ast::Expression {
|
||||
expr: ast::RawExpression::Block(b),
|
||||
..
|
||||
} => Ok(Spanned::from_item(Value::block(b.expr), arg.span.clone())),
|
||||
ast::Expression {
|
||||
expr: ast::RawExpression::Binary(binary),
|
||||
..
|
||||
} => {
|
||||
// TODO: Use original spans
|
||||
let mut b = ast::ExpressionBuilder::new();
|
||||
if let Some(s) = binary.left.as_string() {
|
||||
Ok(Spanned::from_item(
|
||||
Value::block(b.binary((
|
||||
&|b| b.path((&|b| b.var("it"), vec![s.clone()])),
|
||||
&|_| binary.operator.clone(),
|
||||
&|_| binary.right.clone(),
|
||||
))),
|
||||
arg.span.clone(),
|
||||
))
|
||||
} else {
|
||||
let mut b = ast::ExpressionBuilder::new();
|
||||
let expr = b.binary((
|
||||
&|_| binary.left.clone(),
|
||||
&|_| binary.operator.clone(),
|
||||
&|_| binary.right.clone(),
|
||||
));
|
||||
|
||||
Ok(Spanned::from_item(Value::block(expr), arg.span.clone()))
|
||||
}
|
||||
}
|
||||
other => {
|
||||
let span = other.span.clone();
|
||||
Ok(Spanned::from_item(Value::block(other), span))
|
||||
}
|
||||
},
|
||||
PositionalType::Value(_) => None,
|
||||
PositionalType::Block(_) => Some(ExpressionKindHint::Block),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Getters)]
|
||||
#[get = "crate"]
|
||||
pub struct CommandConfig {
|
||||
crate name: String,
|
||||
crate mandatory_positional: Vec<PositionalType>,
|
||||
|
@ -94,90 +58,218 @@ pub struct CommandConfig {
|
|||
crate named: IndexMap<String, NamedType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, new)]
|
||||
pub struct Args {
|
||||
pub positional: Vec<Spanned<Value>>,
|
||||
pub named: IndexMap<String, Value>,
|
||||
pub positional: Option<Vec<Spanned<Value>>>,
|
||||
pub named: Option<IndexMap<String, Spanned<Value>>>,
|
||||
}
|
||||
|
||||
#[derive(new)]
|
||||
pub struct DebugPositional<'a> {
|
||||
positional: &'a Option<Vec<Spanned<Value>>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for DebugPositional<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match &self.positional {
|
||||
None => write!(f, "None"),
|
||||
Some(positional) => f
|
||||
.debug_list()
|
||||
.entries(positional.iter().map(|p| p.item().debug()))
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(new)]
|
||||
pub struct DebugNamed<'a> {
|
||||
named: &'a Option<IndexMap<String, Spanned<Value>>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for DebugNamed<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match &self.named {
|
||||
None => write!(f, "None"),
|
||||
Some(named) => f
|
||||
.debug_map()
|
||||
.entries(named.iter().map(|(k, v)| (k, v.item().debug())))
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DebugArgs<'a> {
|
||||
args: &'a Args,
|
||||
}
|
||||
|
||||
impl fmt::Debug for DebugArgs<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut s = f.debug_struct("Args");
|
||||
|
||||
s.field("positional", &DebugPositional::new(&self.args.positional));
|
||||
s.field("named", &DebugNamed::new(&self.args.named));
|
||||
|
||||
s.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Args {
|
||||
pub fn debug(&'a self) -> DebugArgs<'a> {
|
||||
DebugArgs { args: self }
|
||||
}
|
||||
|
||||
pub fn nth(&self, pos: usize) -> Option<&Spanned<Value>> {
|
||||
match &self.positional {
|
||||
None => None,
|
||||
Some(array) => array.iter().nth(pos),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_nth(&self, pos: usize) -> Result<&Spanned<Value>, ShellError> {
|
||||
match &self.positional {
|
||||
None => Err(ShellError::unimplemented("Better error: expect_nth")),
|
||||
Some(array) => match array.iter().nth(pos) {
|
||||
None => Err(ShellError::unimplemented("Better error: expect_nth")),
|
||||
Some(item) => Ok(item),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
match &self.positional {
|
||||
None => 0,
|
||||
Some(array) => array.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has(&self, name: &str) -> bool {
|
||||
match &self.named {
|
||||
None => false,
|
||||
Some(named) => named.contains_key(name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Option<&Spanned<Value>> {
|
||||
match &self.named {
|
||||
None => None,
|
||||
Some(named) => named.get(name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn positional_iter(&'a self) -> PositionalIter<'a> {
|
||||
match &self.positional {
|
||||
None => PositionalIter::Empty,
|
||||
Some(v) => {
|
||||
let iter = v.iter();
|
||||
PositionalIter::Array(iter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum PositionalIter<'a> {
|
||||
Empty,
|
||||
Array(std::slice::Iter<'a, Spanned<Value>>),
|
||||
}
|
||||
|
||||
impl Iterator for PositionalIter<'a> {
|
||||
type Item = &'a Spanned<Value>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
PositionalIter::Empty => None,
|
||||
PositionalIter::Array(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandConfig {
|
||||
crate fn evaluate_args(
|
||||
&self,
|
||||
args: impl Iterator<Item = &'expr ast::Expression>,
|
||||
call: &Spanned<CallNode>,
|
||||
registry: &dyn CommandRegistry,
|
||||
scope: &Scope,
|
||||
source: &Text,
|
||||
) -> Result<Args, ShellError> {
|
||||
let mut positional: Vec<Spanned<Value>> = vec![];
|
||||
let mut named: IndexMap<String, Value> = IndexMap::default();
|
||||
let args = parse_command(self, registry, call, source)?;
|
||||
|
||||
let mut args: Vec<ast::Expression> = args.cloned().collect();
|
||||
trace!("parsed args: {:?}", args);
|
||||
|
||||
for (key, ty) in self.named.iter() {
|
||||
let index = args.iter().position(|a| a.is_flag(&key));
|
||||
evaluate_args(args, registry, scope, source)
|
||||
|
||||
match (index, ty) {
|
||||
(Some(i), NamedType::Switch) => {
|
||||
args.remove(i);
|
||||
named.insert(key.clone(), Value::boolean(true));
|
||||
}
|
||||
// let mut positional: Vec<Spanned<Value>> = vec![];
|
||||
// let mut named: IndexMap<String, Value> = IndexMap::default();
|
||||
|
||||
(None, NamedType::Switch) => {}
|
||||
// let mut args: Vec<TokenNode> = args.cloned().collect();
|
||||
|
||||
(Some(i), NamedType::Optional(v)) => {
|
||||
args.remove(i);
|
||||
named.insert(key.clone(), extract_named(&mut args, i, v)?);
|
||||
}
|
||||
// for (key, ty) in self.named.iter() {
|
||||
// let index = args.iter().position(|a| a.is_flag(&key, source));
|
||||
|
||||
(None, NamedType::Optional(_)) => {}
|
||||
// match (index, ty) {
|
||||
// (Some(i), NamedType::Switch) => {
|
||||
// args.remove(i);
|
||||
// named.insert(key.clone(), Value::boolean(true));
|
||||
// }
|
||||
|
||||
(Some(i), NamedType::Mandatory(v)) => {
|
||||
args.remove(i);
|
||||
named.insert(key.clone(), extract_named(&mut args, i, v)?);
|
||||
}
|
||||
// (None, NamedType::Switch) => {}
|
||||
|
||||
(None, NamedType::Mandatory(_)) => {
|
||||
return Err(ShellError::string(&format!(
|
||||
"Expected mandatory argument {}, but it was missing",
|
||||
key
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
// (Some(i), NamedType::Optional(v)) => {
|
||||
// args.remove(i);
|
||||
// named.insert(key.clone(), extract_named(&mut args, i, v)?);
|
||||
// }
|
||||
|
||||
let mut args = args.into_iter();
|
||||
// (None, NamedType::Optional(_)) => {}
|
||||
|
||||
for param in &self.mandatory_positional {
|
||||
let arg = args.next();
|
||||
// (Some(i), NamedType::Mandatory(v)) => {
|
||||
// args.remove(i);
|
||||
// named.insert(key.clone(), extract_named(&mut args, i, v)?);
|
||||
// }
|
||||
|
||||
let value = match arg {
|
||||
None => {
|
||||
return Err(ShellError::string(format!(
|
||||
"expected mandatory positional argument {}",
|
||||
param.name()
|
||||
)))
|
||||
}
|
||||
// (None, NamedType::Mandatory(_)) => {
|
||||
// return Err(ShellError::string(&format!(
|
||||
// "Expected mandatory argument {}, but it was missing",
|
||||
// key
|
||||
// )))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
Some(arg) => param.evaluate(arg.clone(), scope)?,
|
||||
};
|
||||
// let mut args = args.into_iter();
|
||||
|
||||
positional.push(value);
|
||||
}
|
||||
// for param in &self.mandatory_positional {
|
||||
// let arg = args.next();
|
||||
|
||||
if self.rest_positional {
|
||||
let rest: Result<Vec<Spanned<Value>>, _> =
|
||||
args.map(|i| evaluate_expr(&i, &Scope::empty())).collect();
|
||||
positional.extend(rest?);
|
||||
} else {
|
||||
let rest: Vec<ast::Expression> = args.collect();
|
||||
// let value = match arg {
|
||||
// None => {
|
||||
// return Err(ShellError::string(format!(
|
||||
// "expected mandatory positional argument {}",
|
||||
// param.name()
|
||||
// )))
|
||||
// }
|
||||
|
||||
if rest.len() > 0 {
|
||||
return Err(ShellError::string(&format!(
|
||||
"Too many arguments, extras: {:?}",
|
||||
rest
|
||||
)));
|
||||
}
|
||||
}
|
||||
// Some(arg) => param.evaluate(arg.clone(), scope, source)?,
|
||||
// };
|
||||
|
||||
Ok(Args { positional, named })
|
||||
// positional.push(value);
|
||||
// }
|
||||
|
||||
// if self.rest_positional {
|
||||
// let rest: Result<Vec<Spanned<Value>>, _> = args
|
||||
// .map(|i| evaluate_baseline_expr(&i, &Scope::empty(), source))
|
||||
// .collect();
|
||||
// positional.extend(rest?);
|
||||
// } else {
|
||||
// let rest: Vec<TokenNode> = args.collect();
|
||||
|
||||
// if rest.len() > 0 {
|
||||
// return Err(ShellError::string(&format!(
|
||||
// "Too many arguments, extras: {:?}",
|
||||
// rest
|
||||
// )));
|
||||
// }
|
||||
// }
|
||||
|
||||
// Ok(Args { positional, named })
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
|
@ -186,50 +278,64 @@ impl CommandConfig {
|
|||
}
|
||||
}
|
||||
|
||||
fn extract_named(
|
||||
v: &mut Vec<ast::Expression>,
|
||||
position: usize,
|
||||
ty: &NamedValue,
|
||||
) -> Result<Value, ShellError> {
|
||||
match ty {
|
||||
NamedValue::Single => {
|
||||
let expr = v.remove(position);
|
||||
expect_simple_expr(expr)
|
||||
}
|
||||
fn evaluate_args(
|
||||
args: hir::Call,
|
||||
registry: &dyn CommandRegistry,
|
||||
scope: &Scope,
|
||||
source: &Text,
|
||||
) -> Result<Args, ShellError> {
|
||||
let positional: Result<Option<Vec<_>>, _> = args
|
||||
.positional()
|
||||
.as_ref()
|
||||
.map(|p| {
|
||||
p.iter()
|
||||
.map(|e| evaluate_baseline_expr(e, &(), scope, source))
|
||||
.collect()
|
||||
})
|
||||
.transpose();
|
||||
|
||||
NamedValue::Tuple => {
|
||||
let expr = v.remove(position);
|
||||
let next = v.remove(position);
|
||||
let positional = positional?;
|
||||
|
||||
let list = vec![expect_simple_expr(expr)?, expect_simple_expr(next)?];
|
||||
Ok(Value::List(list))
|
||||
}
|
||||
let named: Result<Option<IndexMap<String, Spanned<Value>>>, ShellError> = args
|
||||
.named()
|
||||
.as_ref()
|
||||
.map(|n| {
|
||||
let mut results = IndexMap::new();
|
||||
|
||||
other => Err(ShellError::string(&format!(
|
||||
"Unimplemented named argument {:?}",
|
||||
other
|
||||
))),
|
||||
}
|
||||
}
|
||||
for (name, value) in n.named.iter() {
|
||||
match value {
|
||||
hir::named::NamedValue::PresentSwitch(span) => {
|
||||
results.insert(
|
||||
name.clone(),
|
||||
Spanned::from_item(Value::boolean(true), *span),
|
||||
);
|
||||
}
|
||||
hir::named::NamedValue::Value(expr) => {
|
||||
results.insert(
|
||||
name.clone(),
|
||||
evaluate_baseline_expr(expr, registry, scope, source)?,
|
||||
);
|
||||
}
|
||||
|
||||
fn expect_simple_expr(expr: ast::Expression) -> Result<Value, ShellError> {
|
||||
match &*expr {
|
||||
ast::RawExpression::Leaf(l) => Ok(match l {
|
||||
ast::Leaf::Bare(s) => Value::string(s.to_string()),
|
||||
ast::Leaf::String(s) => Value::string(s),
|
||||
ast::Leaf::Boolean(b) => Value::boolean(*b),
|
||||
ast::Leaf::Int(i) => Value::int(*i),
|
||||
ast::Leaf::Unit(i, unit) => unit.compute(*i),
|
||||
}),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Diagnostic
|
||||
other => Err(ShellError::string(&format!(
|
||||
"Expected a value, found {}",
|
||||
other.print()
|
||||
))),
|
||||
}
|
||||
Ok(results)
|
||||
})
|
||||
.transpose();
|
||||
|
||||
let named = named?;
|
||||
|
||||
Ok(Args::new(positional, named))
|
||||
}
|
||||
|
||||
pub trait CommandRegistry {
|
||||
fn get(&self, name: &str) -> CommandConfig;
|
||||
fn get(&self, name: &str) -> Option<CommandConfig>;
|
||||
}
|
||||
|
||||
impl CommandRegistry for () {
|
||||
fn get(&self, _name: &str) -> Option<CommandConfig> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ crate use crate::env::host::handle_unexpected;
|
|||
crate use crate::env::{Environment, Host};
|
||||
crate use crate::errors::ShellError;
|
||||
crate use crate::object::Value;
|
||||
crate use crate::parser::ast;
|
||||
crate use crate::stream::{single_output, InputStream, OutputStream};
|
||||
crate use crate::Text;
|
||||
crate use futures::{FutureExt, StreamExt};
|
||||
crate use std::collections::VecDeque;
|
||||
crate use std::pin::Pin;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use crate::shell::completer::NuCompleter;
|
||||
|
||||
use crate::parser::lexer::SpannedToken;
|
||||
use crate::parser::nom_input;
|
||||
use crate::parser::parse2::span::Spanned;
|
||||
use crate::parser::parse2::token_tree::TokenNode;
|
||||
use crate::parser::parse2::tokens::RawToken;
|
||||
use crate::parser::{Pipeline, PipelineElement};
|
||||
use crate::prelude::*;
|
||||
use crate::shell::completer::NuCompleter;
|
||||
use ansi_term::Color;
|
||||
use log::trace;
|
||||
use rustyline::completion::{self, Completer, FilenameCompleter};
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::highlight::Highlighter;
|
||||
|
@ -47,7 +49,7 @@ impl Hinter for Helper {
|
|||
}
|
||||
|
||||
impl Highlighter for Helper {
|
||||
fn highlight_prompt<'b, 's: 'b, 'p:'b>(&'s self, prompt: &'p str, _: bool) -> Cow<'b, str> {
|
||||
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(&'s self, prompt: &'p str, _: bool) -> Cow<'b, str> {
|
||||
Owned("\x1b[32m".to_owned() + &prompt[0..prompt.len() - 2] + "\x1b[m> ")
|
||||
}
|
||||
|
||||
|
@ -56,30 +58,31 @@ impl Highlighter for Helper {
|
|||
}
|
||||
|
||||
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
|
||||
let tokens = crate::parser::lexer::Lexer::new(line, true);
|
||||
let tokens: Result<Vec<(usize, SpannedToken, usize)>, _> = tokens.collect();
|
||||
let tokens = crate::parser::pipeline(nom_input(line));
|
||||
|
||||
match tokens {
|
||||
Err(_) => Cow::Borrowed(line),
|
||||
Ok(v) => {
|
||||
Ok((_rest, v)) => {
|
||||
let mut out = String::new();
|
||||
let mut iter = v.iter();
|
||||
let pipeline = match v.as_pipeline() {
|
||||
Err(_) => return Cow::Borrowed(line),
|
||||
Ok(v) => v,
|
||||
};
|
||||
|
||||
let mut state = State::Command;
|
||||
let Pipeline { parts, post_ws } = pipeline;
|
||||
let mut iter = parts.into_iter();
|
||||
|
||||
loop {
|
||||
match iter.next() {
|
||||
None => return Cow::Owned(out),
|
||||
Some((start, token, end)) => {
|
||||
let (style, new_state) = token_style(&token, state);
|
||||
None => {
|
||||
if let Some(ws) = post_ws {
|
||||
out.push_str(ws.slice(line));
|
||||
}
|
||||
|
||||
trace!("token={:?}", token);
|
||||
trace!("style={:?}", style);
|
||||
trace!("new_state={:?}", new_state);
|
||||
|
||||
state = new_state;
|
||||
let slice = &line[*start..*end];
|
||||
let styled = style.paint(slice);
|
||||
return Cow::Owned(out);
|
||||
}
|
||||
Some(token) => {
|
||||
let styled = paint_pipeline_element(&token, line);
|
||||
out.push_str(&styled.to_string());
|
||||
}
|
||||
}
|
||||
|
@ -93,41 +96,66 @@ impl Highlighter for Helper {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum State {
|
||||
Command,
|
||||
Flag,
|
||||
Var,
|
||||
Bare,
|
||||
None,
|
||||
fn paint_token_node(token_node: &TokenNode, line: &str) -> String {
|
||||
let styled = match token_node {
|
||||
TokenNode::Call(..) => Color::Cyan.bold().paint(token_node.span().slice(line)),
|
||||
TokenNode::Whitespace(..) => Color::White.normal().paint(token_node.span().slice(line)),
|
||||
TokenNode::Flag(..) => Color::Black.bold().paint(token_node.span().slice(line)),
|
||||
TokenNode::Identifier(..) => Color::Yellow.bold().paint(token_node.span().slice(line)),
|
||||
TokenNode::Path(..) => Color::Green.bold().paint(token_node.span().slice(line)),
|
||||
TokenNode::Error(..) => Color::Red.bold().paint(token_node.span().slice(line)),
|
||||
TokenNode::Delimited(..) => Color::White.paint(token_node.span().slice(line)),
|
||||
TokenNode::Operator(..) => Color::Purple.bold().paint(token_node.span().slice(line)),
|
||||
TokenNode::Pipeline(..) => Color::Blue.normal().paint(token_node.span().slice(line)),
|
||||
TokenNode::Token(Spanned {
|
||||
item: RawToken::Integer(..),
|
||||
..
|
||||
}) => Color::Purple.bold().paint(token_node.span().slice(line)),
|
||||
TokenNode::Token(Spanned {
|
||||
item: RawToken::Size(..),
|
||||
..
|
||||
}) => Color::Purple.bold().paint(token_node.span().slice(line)),
|
||||
TokenNode::Token(Spanned {
|
||||
item: RawToken::String(..),
|
||||
..
|
||||
}) => Color::Green.normal().paint(token_node.span().slice(line)),
|
||||
TokenNode::Token(Spanned {
|
||||
item: RawToken::Variable(..),
|
||||
..
|
||||
}) => Color::Yellow.bold().paint(token_node.span().slice(line)),
|
||||
TokenNode::Token(Spanned {
|
||||
item: RawToken::Bare,
|
||||
..
|
||||
}) => Color::Green.normal().paint(token_node.span().slice(line)),
|
||||
};
|
||||
|
||||
styled.to_string()
|
||||
}
|
||||
|
||||
fn token_style(
|
||||
token: &crate::parser::lexer::SpannedToken,
|
||||
state: State,
|
||||
) -> (ansi_term::Style, State) {
|
||||
use crate::parser::lexer::Token::*;
|
||||
fn paint_pipeline_element(pipeline_element: &PipelineElement, line: &str) -> String {
|
||||
let mut styled = String::new();
|
||||
|
||||
match (state, &token.token) {
|
||||
(State::Command, Bare) => (Color::Cyan.bold(), State::None),
|
||||
(State::Command, Whitespace) => (Color::White.normal(), State::Command),
|
||||
|
||||
(State::Flag, Bare) => (Color::Black.bold(), State::None),
|
||||
|
||||
(State::Var, Variable) => (Color::Yellow.bold(), State::None),
|
||||
|
||||
(State::Bare, PathDot) => (Color::Green.normal(), State::Bare),
|
||||
(State::Bare, Member) => (Color::Green.normal(), State::Bare),
|
||||
|
||||
(_, Dash) | (_, DashDash) => (Color::Black.bold(), State::Flag),
|
||||
(_, Dollar) => (Color::Yellow.bold(), State::Var),
|
||||
(_, Bare) => (Color::Green.normal(), State::Bare),
|
||||
(_, Member) => (Color::Cyan.normal(), State::None),
|
||||
(_, Num) => (Color::Purple.bold(), State::None),
|
||||
(_, DQString) | (_, SQString) => (Color::Green.normal(), State::None),
|
||||
(_, Pipe) => (Color::White.normal(), State::Command),
|
||||
_ => (Color::White.normal(), State::None),
|
||||
if let Some(ws) = pipeline_element.pre_ws {
|
||||
styled.push_str(&Color::White.normal().paint(ws.slice(line)));
|
||||
}
|
||||
|
||||
styled.push_str(&paint_token_node(pipeline_element.call().head(), line));
|
||||
|
||||
if let Some(children) = pipeline_element.call().children() {
|
||||
for child in children {
|
||||
styled.push_str(&paint_token_node(child, line));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ws) = pipeline_element.post_ws {
|
||||
styled.push_str(&Color::White.normal().paint(ws.slice(line)));
|
||||
}
|
||||
|
||||
if let Some(_) = pipeline_element.post_pipe {
|
||||
styled.push_str(&Color::Purple.paint("|"));
|
||||
}
|
||||
|
||||
styled.to_string()
|
||||
}
|
||||
|
||||
impl rustyline::Helper for Helper {}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
cd tests
|
||||
open test.toml --raw | split-row "\n" | skip 1 | first 4 | split-column "=" | sort-by Column1 | skip 1 | first 1 | get Column1 | trim | echo $it
|
||||
open test.toml --raw | lines | skip 1 | first 4 | split-column "=" | sort-by Column1 | skip 1 | first 1 | get Column1 | trim | echo $it
|
||||
exit
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
cd tests
|
||||
open test.toml --raw | split-row "\n" | skip 1 | first 1 | split-column "=" | get Column1 | trim | echo $it
|
||||
open test.toml --raw | lines | skip 1 | first 1 | split-column "=" | get Column1 | trim | echo $it
|
||||
exit
|
||||
|
|
Loading…
Reference in a new issue