nushell/src/parser/registry.rs

349 lines
9.0 KiB
Rust
Raw Normal View History

2019-07-24 00:22:11 +02:00
// TODO: Temporary redirect
crate use crate::context::CommandRegistry;
2019-06-22 03:36:57 +02:00
use crate::evaluate::{evaluate_baseline_expr, Scope};
2019-08-01 03:58:42 +02:00
use crate::parser::{hir, hir::SyntaxType, parse_command, CallNode};
2019-05-28 08:45:18 +02:00
use crate::prelude::*;
2019-06-22 03:36:57 +02:00
use derive_new::new;
2019-05-26 08:54:41 +02:00
use indexmap::IndexMap;
2019-06-22 03:36:57 +02:00
use log::trace;
2019-07-02 09:56:20 +02:00
use serde::{Deserialize, Serialize};
2019-06-22 03:36:57 +02:00
use std::fmt;
2019-05-26 08:54:41 +02:00
#[allow(unused)]
2019-07-16 09:08:35 +02:00
#[derive(Debug, Serialize, Deserialize, Clone)]
2019-05-28 08:45:18 +02:00
pub enum NamedType {
Switch,
2019-07-15 23:16:27 +02:00
Mandatory(SyntaxType),
Optional(SyntaxType),
2019-05-26 08:54:41 +02:00
}
#[allow(unused)]
2019-07-02 09:56:20 +02:00
#[derive(Debug, Clone, Serialize, Deserialize)]
2019-05-28 08:45:18 +02:00
pub enum PositionalType {
2019-07-15 23:16:27 +02:00
Mandatory(String, SyntaxType),
Optional(String, SyntaxType),
2019-05-28 08:45:18 +02:00
}
impl PositionalType {
2019-07-15 23:16:27 +02:00
pub fn mandatory(name: &str, ty: SyntaxType) -> PositionalType {
PositionalType::Mandatory(name.to_string(), ty)
}
pub fn mandatory_any(name: &str) -> PositionalType {
PositionalType::Mandatory(name.to_string(), SyntaxType::Any)
2019-07-13 04:07:06 +02:00
}
2019-07-16 21:10:25 +02:00
pub fn mandatory_block(name: &str) -> PositionalType {
PositionalType::Mandatory(name.to_string(), SyntaxType::Block)
}
2019-07-16 09:25:48 +02:00
pub fn optional(name: &str, ty: SyntaxType) -> PositionalType {
PositionalType::Optional(name.to_string(), ty)
}
pub fn optional_any(name: &str) -> PositionalType {
PositionalType::Optional(name.to_string(), SyntaxType::Any)
}
2019-07-16 09:08:35 +02:00
#[allow(unused)]
2019-07-15 23:16:27 +02:00
crate fn to_coerce_hint(&self) -> Option<SyntaxType> {
2019-05-28 08:45:18 +02:00
match self {
2019-07-15 23:16:27 +02:00
PositionalType::Mandatory(_, SyntaxType::Block)
| PositionalType::Optional(_, SyntaxType::Block) => Some(SyntaxType::Block),
_ => None,
2019-05-28 08:45:18 +02:00
}
}
crate fn name(&self) -> &str {
match self {
PositionalType::Mandatory(s, _) => s,
PositionalType::Optional(s, _) => s,
}
}
2019-07-15 23:16:27 +02:00
crate fn syntax_type(&self) -> SyntaxType {
match *self {
PositionalType::Mandatory(_, t) => t,
PositionalType::Optional(_, t) => t,
}
}
2019-05-28 08:45:18 +02:00
}
2019-08-02 21:15:07 +02:00
#[derive(Debug, Serialize, Deserialize, Clone, new)]
pub struct Signature {
2019-07-02 09:56:20 +02:00
pub name: String,
2019-08-02 21:15:07 +02:00
#[new(default)]
2019-07-13 04:07:06 +02:00
pub positional: Vec<PositionalType>,
Add support for ~ expansion This ended up being a bit of a yak shave. The basic idea in this commit is to expand `~` in paths, but only in paths. The way this is accomplished is by doing the expansion inside of the code that parses literal syntax for `SyntaxType::Path`. As a quick refresher: every command is entitled to expand its arguments in a custom way. While this could in theory be used for general-purpose macros, today the expansion facility is limited to syntactic hints. For example, the syntax `where cpu > 0` expands under the hood to `where { $it.cpu > 0 }`. This happens because the first argument to `where` is defined as a `SyntaxType::Block`, and the parser coerces binary expressions whose left-hand-side looks like a member into a block when the command is expecting one. This is mildly more magical than what most programming languages would do, but we believe that it makes sense to allow commands to fine-tune the syntax because of the domain nushell is in (command-line shells). The syntactic expansions supported by this facility are relatively limited. For example, we don't allow `$it` to become a bare word, simply because the command asks for a string in the relevant position. That would quickly become more confusing than it's worth. This PR adds a new `SyntaxType` rule: `SyntaxType::Path`. When a command declares a parameter as a `SyntaxType::Path`, string literals and bare words passed as an argument to that parameter are processed using the path expansion rules. Right now, that only means that `~` is expanded into the home directory, but additional rules are possible in the future. By restricting this expansion to a syntactic expansion when passed as an argument to a command expecting a path, we avoid making `~` a generally reserved character. This will also allow us to give good tab completion for paths with `~` characters in them when a command is expecting a path. In order to accomplish the above, this commit changes the parsing functions to take a `Context` instead of just a `CommandRegistry`. From the perspective of macro expansion, you can think of the `CommandRegistry` as a dictionary of in-scope macros, and the `Context` as the compile-time state used in expansion. This could gain additional functionality over time as we find more uses for the expansion system.
2019-08-26 21:21:03 +02:00
#[new(value = "None")]
pub rest_positional: Option<SyntaxType>,
2019-08-02 21:15:07 +02:00
#[new(default)]
2019-07-02 09:56:20 +02:00
pub named: IndexMap<String, NamedType>,
2019-08-02 21:15:07 +02:00
#[new(value = "false")]
2019-07-02 09:56:20 +02:00
pub is_filter: bool,
2019-05-28 08:45:18 +02:00
}
2019-08-02 21:15:07 +02:00
impl Signature {
pub fn build(name: impl Into<String>) -> Signature {
Signature::new(name.into())
2019-07-24 06:10:48 +02:00
}
2019-08-02 21:15:07 +02:00
pub fn required(mut self, name: impl Into<String>, ty: impl Into<SyntaxType>) -> Signature {
2019-07-24 06:10:48 +02:00
self.positional
.push(PositionalType::Mandatory(name.into(), ty.into()));
self
}
2019-08-02 21:15:07 +02:00
pub fn optional(mut self, name: impl Into<String>, ty: impl Into<SyntaxType>) -> Signature {
2019-07-24 06:10:48 +02:00
self.positional
.push(PositionalType::Optional(name.into(), ty.into()));
self
}
2019-08-02 21:15:07 +02:00
pub fn named(mut self, name: impl Into<String>, ty: impl Into<SyntaxType>) -> Signature {
self.named
.insert(name.into(), NamedType::Optional(ty.into()));
self
}
pub fn required_named(
mut self,
name: impl Into<String>,
ty: impl Into<SyntaxType>,
) -> Signature {
self.named
.insert(name.into(), NamedType::Mandatory(ty.into()));
self
}
pub fn switch(mut self, name: impl Into<String>) -> Signature {
self.named.insert(name.into(), NamedType::Switch);
2019-07-24 06:10:48 +02:00
self
}
2019-08-02 21:15:07 +02:00
pub fn filter(mut self) -> Signature {
self.is_filter = true;
self
}
Add support for ~ expansion This ended up being a bit of a yak shave. The basic idea in this commit is to expand `~` in paths, but only in paths. The way this is accomplished is by doing the expansion inside of the code that parses literal syntax for `SyntaxType::Path`. As a quick refresher: every command is entitled to expand its arguments in a custom way. While this could in theory be used for general-purpose macros, today the expansion facility is limited to syntactic hints. For example, the syntax `where cpu > 0` expands under the hood to `where { $it.cpu > 0 }`. This happens because the first argument to `where` is defined as a `SyntaxType::Block`, and the parser coerces binary expressions whose left-hand-side looks like a member into a block when the command is expecting one. This is mildly more magical than what most programming languages would do, but we believe that it makes sense to allow commands to fine-tune the syntax because of the domain nushell is in (command-line shells). The syntactic expansions supported by this facility are relatively limited. For example, we don't allow `$it` to become a bare word, simply because the command asks for a string in the relevant position. That would quickly become more confusing than it's worth. This PR adds a new `SyntaxType` rule: `SyntaxType::Path`. When a command declares a parameter as a `SyntaxType::Path`, string literals and bare words passed as an argument to that parameter are processed using the path expansion rules. Right now, that only means that `~` is expanded into the home directory, but additional rules are possible in the future. By restricting this expansion to a syntactic expansion when passed as an argument to a command expecting a path, we avoid making `~` a generally reserved character. This will also allow us to give good tab completion for paths with `~` characters in them when a command is expecting a path. In order to accomplish the above, this commit changes the parsing functions to take a `Context` instead of just a `CommandRegistry`. From the perspective of macro expansion, you can think of the `CommandRegistry` as a dictionary of in-scope macros, and the `Context` as the compile-time state used in expansion. This could gain additional functionality over time as we find more uses for the expansion system.
2019-08-26 21:21:03 +02:00
pub fn rest(mut self, ty: SyntaxType) -> Signature {
self.rest_positional = Some(ty);
2019-08-02 21:15:07 +02:00
self
}
2019-07-24 06:10:48 +02:00
}
#[derive(Debug, Default, new, Serialize, Deserialize, Clone)]
2019-07-24 00:22:11 +02:00
pub struct EvaluatedArgs {
2019-08-01 03:58:42 +02:00
pub positional: Option<Vec<Tagged<Value>>>,
pub named: Option<IndexMap<String, Tagged<Value>>>,
2019-06-22 03:36:57 +02:00
}
impl EvaluatedArgs {
pub fn slice_from(&self, from: usize) -> Vec<Tagged<Value>> {
let positional = &self.positional;
match positional {
None => vec![],
Some(list) => list[from..].to_vec(),
}
}
}
2019-06-22 03:36:57 +02:00
#[derive(new)]
2019-07-24 00:22:11 +02:00
pub struct DebugEvaluatedPositional<'a> {
2019-08-01 03:58:42 +02:00
positional: &'a Option<Vec<Tagged<Value>>>,
2019-06-22 03:36:57 +02:00
}
2019-07-24 00:22:11 +02:00
impl fmt::Debug for DebugEvaluatedPositional<'a> {
2019-06-22 03:36:57 +02:00
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.positional {
None => write!(f, "None"),
Some(positional) => f
.debug_list()
2019-07-09 06:31:26 +02:00
.entries(positional.iter().map(|p| p.debug()))
2019-06-22 03:36:57 +02:00
.finish(),
}
}
}
#[derive(new)]
2019-07-24 00:22:11 +02:00
pub struct DebugEvaluatedNamed<'a> {
2019-08-01 03:58:42 +02:00
named: &'a Option<IndexMap<String, Tagged<Value>>>,
2019-06-22 03:36:57 +02:00
}
2019-07-24 00:22:11 +02:00
impl fmt::Debug for DebugEvaluatedNamed<'a> {
2019-06-22 03:36:57 +02:00
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.named {
None => write!(f, "None"),
Some(named) => f
.debug_map()
2019-07-09 06:31:26 +02:00
.entries(named.iter().map(|(k, v)| (k, v.debug())))
2019-06-22 03:36:57 +02:00
.finish(),
}
}
}
2019-07-24 00:22:11 +02:00
pub struct DebugEvaluatedArgs<'a> {
args: &'a EvaluatedArgs,
2019-06-22 03:36:57 +02:00
}
2019-07-24 00:22:11 +02:00
impl fmt::Debug for DebugEvaluatedArgs<'a> {
2019-06-22 03:36:57 +02:00
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut s = f.debug_struct("Args");
2019-07-24 00:22:11 +02:00
s.field(
"positional",
&DebugEvaluatedPositional::new(&self.args.positional),
);
s.field("named", &DebugEvaluatedNamed::new(&self.args.named));
2019-06-22 03:36:57 +02:00
s.finish()
}
}
2019-07-24 00:22:11 +02:00
impl EvaluatedArgs {
pub fn debug(&'a self) -> DebugEvaluatedArgs<'a> {
DebugEvaluatedArgs { args: self }
2019-06-22 03:36:57 +02:00
}
2019-08-01 03:58:42 +02:00
pub fn nth(&self, pos: usize) -> Option<&Tagged<Value>> {
2019-06-22 03:36:57 +02:00
match &self.positional {
None => None,
Some(array) => array.iter().nth(pos),
}
}
2019-08-01 03:58:42 +02:00
pub fn expect_nth(&self, pos: usize) -> Result<&Tagged<Value>, ShellError> {
2019-06-22 03:36:57 +02:00
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),
}
}
2019-08-01 03:58:42 +02:00
pub fn get(&self, name: &str) -> Option<&Tagged<Value>> {
2019-06-22 03:36:57 +02:00
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,
2019-08-01 03:58:42 +02:00
Array(std::slice::Iter<'a, Tagged<Value>>),
2019-06-22 03:36:57 +02:00
}
impl Iterator for PositionalIter<'a> {
2019-08-01 03:58:42 +02:00
type Item = &'a Tagged<Value>;
2019-06-22 03:36:57 +02:00
fn next(&mut self) -> Option<Self::Item> {
match self {
PositionalIter::Empty => None,
PositionalIter::Array(iter) => iter.next(),
}
}
}
2019-08-02 21:15:07 +02:00
impl Signature {
2019-07-24 00:22:11 +02:00
crate fn parse_args(
2019-05-28 08:45:18 +02:00
&self,
2019-08-01 03:58:42 +02:00
call: &Tagged<CallNode>,
Add support for ~ expansion This ended up being a bit of a yak shave. The basic idea in this commit is to expand `~` in paths, but only in paths. The way this is accomplished is by doing the expansion inside of the code that parses literal syntax for `SyntaxType::Path`. As a quick refresher: every command is entitled to expand its arguments in a custom way. While this could in theory be used for general-purpose macros, today the expansion facility is limited to syntactic hints. For example, the syntax `where cpu > 0` expands under the hood to `where { $it.cpu > 0 }`. This happens because the first argument to `where` is defined as a `SyntaxType::Block`, and the parser coerces binary expressions whose left-hand-side looks like a member into a block when the command is expecting one. This is mildly more magical than what most programming languages would do, but we believe that it makes sense to allow commands to fine-tune the syntax because of the domain nushell is in (command-line shells). The syntactic expansions supported by this facility are relatively limited. For example, we don't allow `$it` to become a bare word, simply because the command asks for a string in the relevant position. That would quickly become more confusing than it's worth. This PR adds a new `SyntaxType` rule: `SyntaxType::Path`. When a command declares a parameter as a `SyntaxType::Path`, string literals and bare words passed as an argument to that parameter are processed using the path expansion rules. Right now, that only means that `~` is expanded into the home directory, but additional rules are possible in the future. By restricting this expansion to a syntactic expansion when passed as an argument to a command expecting a path, we avoid making `~` a generally reserved character. This will also allow us to give good tab completion for paths with `~` characters in them when a command is expecting a path. In order to accomplish the above, this commit changes the parsing functions to take a `Context` instead of just a `CommandRegistry`. From the perspective of macro expansion, you can think of the `CommandRegistry` as a dictionary of in-scope macros, and the `Context` as the compile-time state used in expansion. This could gain additional functionality over time as we find more uses for the expansion system.
2019-08-26 21:21:03 +02:00
context: &Context,
2019-06-22 22:46:16 +02:00
source: &Text,
2019-07-24 00:22:11 +02:00
) -> Result<hir::Call, ShellError> {
Add support for ~ expansion This ended up being a bit of a yak shave. The basic idea in this commit is to expand `~` in paths, but only in paths. The way this is accomplished is by doing the expansion inside of the code that parses literal syntax for `SyntaxType::Path`. As a quick refresher: every command is entitled to expand its arguments in a custom way. While this could in theory be used for general-purpose macros, today the expansion facility is limited to syntactic hints. For example, the syntax `where cpu > 0` expands under the hood to `where { $it.cpu > 0 }`. This happens because the first argument to `where` is defined as a `SyntaxType::Block`, and the parser coerces binary expressions whose left-hand-side looks like a member into a block when the command is expecting one. This is mildly more magical than what most programming languages would do, but we believe that it makes sense to allow commands to fine-tune the syntax because of the domain nushell is in (command-line shells). The syntactic expansions supported by this facility are relatively limited. For example, we don't allow `$it` to become a bare word, simply because the command asks for a string in the relevant position. That would quickly become more confusing than it's worth. This PR adds a new `SyntaxType` rule: `SyntaxType::Path`. When a command declares a parameter as a `SyntaxType::Path`, string literals and bare words passed as an argument to that parameter are processed using the path expansion rules. Right now, that only means that `~` is expanded into the home directory, but additional rules are possible in the future. By restricting this expansion to a syntactic expansion when passed as an argument to a command expecting a path, we avoid making `~` a generally reserved character. This will also allow us to give good tab completion for paths with `~` characters in them when a command is expecting a path. In order to accomplish the above, this commit changes the parsing functions to take a `Context` instead of just a `CommandRegistry`. From the perspective of macro expansion, you can think of the `CommandRegistry` as a dictionary of in-scope macros, and the `Context` as the compile-time state used in expansion. This could gain additional functionality over time as we find more uses for the expansion system.
2019-08-26 21:21:03 +02:00
let args = parse_command(self, context, call, source)?;
2019-06-22 03:36:57 +02:00
trace!("parsed args: {:?}", args);
2019-07-24 00:22:11 +02:00
Ok(args)
2019-05-28 08:45:18 +02:00
}
#[allow(unused)]
crate fn signature(&self) -> String {
format!("TODO")
}
2019-05-26 08:54:41 +02:00
}
2019-07-24 00:22:11 +02:00
crate fn evaluate_args(
call: &hir::Call,
registry: &CommandRegistry,
2019-06-22 03:36:57 +02:00
scope: &Scope,
2019-06-22 22:46:16 +02:00
source: &Text,
2019-07-24 00:22:11 +02:00
) -> Result<EvaluatedArgs, ShellError> {
let positional: Result<Option<Vec<_>>, _> = call
2019-06-22 03:36:57 +02:00
.positional()
.as_ref()
.map(|p| {
p.iter()
2019-08-15 07:02:02 +02:00
.map(|e| evaluate_baseline_expr(e, registry, scope, source))
2019-06-22 03:36:57 +02:00
.collect()
})
.transpose();
let positional = positional?;
2019-08-09 06:51:21 +02:00
let named: Result<Option<IndexMap<String, Tagged<Value>>>, ShellError> = call
2019-06-22 03:36:57 +02:00
.named()
.as_ref()
.map(|n| {
let mut results = IndexMap::new();
for (name, value) in n.named.iter() {
match value {
hir::named::NamedValue::PresentSwitch(span) => {
results.insert(
name.clone(),
Tagged::from_simple_spanned_item(Value::boolean(true), *span),
2019-06-22 03:36:57 +02:00
);
}
hir::named::NamedValue::Value(expr) => {
results.insert(
name.clone(),
evaluate_baseline_expr(expr, registry, scope, source)?,
);
}
2019-06-22 03:36:57 +02:00
_ => {}
};
}
2019-06-22 03:36:57 +02:00
Ok(results)
})
.transpose();
2019-06-22 03:36:57 +02:00
let named = named?;
2019-07-24 00:22:11 +02:00
Ok(EvaluatedArgs::new(positional, named))
2019-05-26 08:54:41 +02:00
}