forked from extern/nushell
commit
e325a226c2
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 New Issue
Block a user