nushell/crates/nu-protocol/src/syntax_shape.rs

247 lines
8.3 KiB
Rust
Raw Normal View History

use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{DeclId, Type};
2021-09-02 01:29:43 +00:00
/// The syntactic shapes that values must match to be passed into a command. You can think of this as the type-checking that occurs when you call a function.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2021-09-02 01:29:43 +00:00
pub enum SyntaxShape {
/// Any syntactic form is allowed
Any,
/// A binary literal
Binary,
/// A block is allowed, eg `{start this thing}`
Block,
/// A boolean value, eg `true` or `false`
Boolean,
2021-09-02 01:29:43 +00:00
/// A dotted path to navigate the table
2021-09-06 22:02:24 +00:00
CellPath,
2021-09-02 01:29:43 +00:00
/// A closure is allowed, eg `{|| start this thing}`
Closure(Option<Vec<SyntaxShape>>),
2021-09-02 01:29:43 +00:00
/// A custom shape with custom completion logic
Custom(Box<SyntaxShape>, DeclId),
2021-09-02 01:29:43 +00:00
/// A datetime value, eg `2022-02-02` or `2019-10-12T07:20:50.52+00:00`
DateTime,
2021-09-02 01:29:43 +00:00
/// A decimal value, eg `1.0`
Decimal,
/// A directory is allowed
Directory,
/// A duration value is allowed, eg `19day`
Duration,
/// An error value
Error,
/// A general expression, eg `1 + 2` or `foo --bar`
Expression,
2021-09-02 01:29:43 +00:00
/// A filepath is allowed
2021-10-04 19:21:31 +00:00
Filepath,
2021-09-02 01:29:43 +00:00
/// A filesize value is allowed, eg `10kb`
Filesize,
/// A dotted path to navigate the table (including variable)
FullCellPath,
2021-09-02 01:29:43 +00:00
/// A glob pattern is allowed, eg `foo*`
GlobPattern,
/// Only an integer value is allowed
Int,
2021-09-26 18:39:19 +00:00
/// A module path pattern used for imports
ImportPattern,
/// A specific match to a word or symbol
Keyword(Vec<u8>, Box<SyntaxShape>),
2021-09-02 01:29:43 +00:00
/// A list is allowed, eg `[first second]`
2021-09-02 01:29:43 +00:00
List(Box<SyntaxShape>),
/// A general math expression, eg `1 + 2`
MathExpression,
2021-09-02 01:29:43 +00:00
Add pattern matching (#8590) # Description This adds `match` and basic pattern matching. An example: ``` match $x { 1..10 => { print "Value is between 1 and 10" } { foo: $bar } => { print $"Value has a 'foo' field with value ($bar)" } [$a, $b] => { print $"Value is a list with two items: ($a) and ($b)" } _ => { print "Value is none of the above" } } ``` Like the recent changes to `if` to allow it to be used as an expression, `match` can also be used as an expression. This allows you to assign the result to a variable, eg) `let xyz = match ...` I've also included a short-hand pattern for matching records, as I think it might help when doing a lot of record patterns: `{$foo}` which is equivalent to `{foo: $foo}`. There are still missing components, so consider this the first step in full pattern matching support. Currently missing: * Patterns for strings * Or-patterns (like the `|` in Rust) * Patterns for tables (unclear how we want to match a table, so it'll need some design) * Patterns for binary values * And much more # User-Facing Changes [see above] # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
2023-03-24 01:52:01 +00:00
/// A block of matches, used by `match`
MatchBlock,
/// A match pattern, eg `{a: $foo}`
MatchPattern,
/// Nothing
Nothing,
2021-09-02 01:29:43 +00:00
/// Only a numeric (integer or decimal) value is allowed
Number,
/// One of a list of possible items, checked in order
OneOf(Vec<SyntaxShape>),
/// An operator, eg `+`
2021-09-02 01:29:43 +00:00
Operator,
/// A range is allowed (eg, `1..3`)
Range,
/// A record value, eg `{x: 1, y: 2}`
allow records to have type annotations (#8914) # Description follow up to #8529 cleaned up version of #8892 - the original syntax is okay ```nu def okay [rec: record] {} ``` - you can now add type annotations for fields if you know them before hand ```nu def okay [rec: record<name: string>] {} ``` - you can specify multiple fields ```nu def okay [person: record<name: string age: int>] {} # an optional comma is allowed def okay [person: record<name: string, age: int>] {} ``` - if annotations are specified, any use of the command will be type checked against the specified type ```nu def unwrap [result: record<ok: bool, value: any>] {} unwrap {ok: 2, value: "value"} # errors with Error: nu::parser::type_mismatch × Type mismatch. ╭─[entry #4:1:1] 1 │ unwrap {ok: 2, value: "value"} · ───────┬───── · ╰── expected record<ok: bool, value: any>, found record<ok: int, value: string> ╰──── ``` > here the error is in the `ok` field, since `any` is coerced into any type > as a result `unwrap {ok: true, value: "value"}` is okay - the key must be a string, either quoted or unquoted ```nu def err [rec: record<{}: list>] {} # errors with Error: × `record` type annotations key not string ╭─[entry #7:1:1] 1 │ def unwrap [result: record<{}: bool, value: any>] {} · ─┬ · ╰── must be a string ╰──── ``` - a key doesn't have to have a type in which case it is assumed to be `any` ```nu def okay [person: record<name age>] {} def okay [person: record<name: string age>] {} ``` - however, if you put a colon, you have to specify a type ```nu def err [person: record<name: >] {} # errors with Error: nu::parser::parse_mismatch × Parse mismatch during operation. ╭─[entry #12:1:1] 1 │ def unwrap [res: record<name: >] { $res } · ┬ · ╰── expected type after colon ╰──── ``` # User-Facing Changes **[BREAKING CHANGES]** - this change adds a field to `SyntaxShape::Record` so any plugins that used it will have to update and include the field. though if you are unsure of the type the record expects, `SyntaxShape::Record(vec![])` will suffice
2023-04-26 13:16:55 +00:00
Record(Vec<(String, SyntaxShape)>),
2021-09-02 01:29:43 +00:00
/// A math expression which expands shorthand forms on the lefthand side, eg `foo > 1`
/// The shorthand allows us to more easily reach columns inside of the row being passed in
RowCondition,
/// A signature for a definition, `[x:int, --foo]`
Signature,
/// Strings and string-like bare words are allowed
String,
/// A table is allowed, eg `[[first, second]; [1, 2]]`
Table(Vec<(String, SyntaxShape)>),
/// A variable name, eg `$foo`
Variable,
/// A variable with optional type, `x` or `x: int`
VarWithOptType,
2021-09-02 01:29:43 +00:00
}
impl SyntaxShape {
pub fn to_type(&self) -> Type {
let mk_ty = |tys: &[(String, SyntaxShape)]| {
tys.iter()
.map(|(key, val)| (key.clone(), val.to_type()))
.collect()
};
2021-09-02 01:29:43 +00:00
match self {
SyntaxShape::Any => Type::Any,
SyntaxShape::Block => Type::Block,
SyntaxShape::Closure(_) => Type::Closure,
2022-02-28 23:31:53 +00:00
SyntaxShape::Binary => Type::Binary,
SyntaxShape::CellPath => Type::Any,
SyntaxShape::Custom(custom, _) => custom.to_type(),
SyntaxShape::DateTime => Type::Date,
2021-09-02 01:29:43 +00:00
SyntaxShape::Duration => Type::Duration,
SyntaxShape::Expression => Type::Any,
2021-10-04 19:21:31 +00:00
SyntaxShape::Filepath => Type::String,
SyntaxShape::Directory => Type::String,
SyntaxShape::Decimal => Type::Float,
2021-09-02 01:29:43 +00:00
SyntaxShape::Filesize => Type::Filesize,
SyntaxShape::FullCellPath => Type::Any,
2021-09-02 01:29:43 +00:00
SyntaxShape::GlobPattern => Type::String,
SyntaxShape::Error => Type::Error,
SyntaxShape::ImportPattern => Type::Any,
2021-09-02 01:29:43 +00:00
SyntaxShape::Int => Type::Int,
SyntaxShape::List(x) => {
let contents = x.to_type();
Type::List(Box::new(contents))
}
SyntaxShape::Keyword(_, expr) => expr.to_type(),
Add pattern matching (#8590) # Description This adds `match` and basic pattern matching. An example: ``` match $x { 1..10 => { print "Value is between 1 and 10" } { foo: $bar } => { print $"Value has a 'foo' field with value ($bar)" } [$a, $b] => { print $"Value is a list with two items: ($a) and ($b)" } _ => { print "Value is none of the above" } } ``` Like the recent changes to `if` to allow it to be used as an expression, `match` can also be used as an expression. This allows you to assign the result to a variable, eg) `let xyz = match ...` I've also included a short-hand pattern for matching records, as I think it might help when doing a lot of record patterns: `{$foo}` which is equivalent to `{foo: $foo}`. There are still missing components, so consider this the first step in full pattern matching support. Currently missing: * Patterns for strings * Or-patterns (like the `|` in Rust) * Patterns for tables (unclear how we want to match a table, so it'll need some design) * Patterns for binary values * And much more # User-Facing Changes [see above] # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
2023-03-24 01:52:01 +00:00
SyntaxShape::MatchBlock => Type::Any,
SyntaxShape::MatchPattern => Type::Any,
SyntaxShape::MathExpression => Type::Any,
SyntaxShape::Nothing => Type::Any,
2021-09-02 01:29:43 +00:00
SyntaxShape::Number => Type::Number,
SyntaxShape::OneOf(_) => Type::Any,
SyntaxShape::Operator => Type::Any,
SyntaxShape::Range => Type::Any,
SyntaxShape::Record(entries) => Type::Record(mk_ty(entries)),
2021-09-02 01:29:43 +00:00
SyntaxShape::RowCondition => Type::Bool,
2021-10-12 04:49:17 +00:00
SyntaxShape::Boolean => Type::Bool,
SyntaxShape::Signature => Type::Signature,
2021-09-02 01:29:43 +00:00
SyntaxShape::String => Type::String,
SyntaxShape::Table(columns) => Type::Table(mk_ty(columns)),
SyntaxShape::VarWithOptType => Type::Any,
SyntaxShape::Variable => Type::Any,
}
}
}
impl Display for SyntaxShape {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mk_fmt = |tys: &[(String, SyntaxShape)]| -> String {
tys.iter()
.map(|(x, y)| format!("{x}: {y}"))
.collect::<Vec<String>>()
.join(", ")
};
match self {
SyntaxShape::Keyword(kw, shape) => {
write!(f, "\"{}\" {}", String::from_utf8_lossy(kw), shape)
}
SyntaxShape::Any => write!(f, "any"),
SyntaxShape::String => write!(f, "string"),
SyntaxShape::CellPath => write!(f, "cellpath"),
SyntaxShape::FullCellPath => write!(f, "cellpath"),
SyntaxShape::Number => write!(f, "number"),
SyntaxShape::Range => write!(f, "range"),
SyntaxShape::Int => write!(f, "int"),
SyntaxShape::Decimal => write!(f, "decimal"),
SyntaxShape::Filepath => write!(f, "path"),
SyntaxShape::Directory => write!(f, "directory"),
SyntaxShape::GlobPattern => write!(f, "glob"),
SyntaxShape::ImportPattern => write!(f, "import"),
SyntaxShape::Block => write!(f, "block"),
SyntaxShape::Closure(args) => {
if let Some(args) = args {
let arg_vec: Vec<_> = args.iter().map(|x| x.to_string()).collect();
let arg_string = arg_vec.join(", ");
write!(f, "closure({arg_string})")
} else {
write!(f, "closure()")
}
}
2022-02-28 23:31:53 +00:00
SyntaxShape::Binary => write!(f, "binary"),
SyntaxShape::List(x) => write!(f, "list<{x}>"),
SyntaxShape::Table(columns) => {
if columns.is_empty() {
write!(f, "table")
} else {
write!(f, "table<{}>", mk_fmt(columns))
}
}
allow records to have type annotations (#8914) # Description follow up to #8529 cleaned up version of #8892 - the original syntax is okay ```nu def okay [rec: record] {} ``` - you can now add type annotations for fields if you know them before hand ```nu def okay [rec: record<name: string>] {} ``` - you can specify multiple fields ```nu def okay [person: record<name: string age: int>] {} # an optional comma is allowed def okay [person: record<name: string, age: int>] {} ``` - if annotations are specified, any use of the command will be type checked against the specified type ```nu def unwrap [result: record<ok: bool, value: any>] {} unwrap {ok: 2, value: "value"} # errors with Error: nu::parser::type_mismatch × Type mismatch. ╭─[entry #4:1:1] 1 │ unwrap {ok: 2, value: "value"} · ───────┬───── · ╰── expected record<ok: bool, value: any>, found record<ok: int, value: string> ╰──── ``` > here the error is in the `ok` field, since `any` is coerced into any type > as a result `unwrap {ok: true, value: "value"}` is okay - the key must be a string, either quoted or unquoted ```nu def err [rec: record<{}: list>] {} # errors with Error: × `record` type annotations key not string ╭─[entry #7:1:1] 1 │ def unwrap [result: record<{}: bool, value: any>] {} · ─┬ · ╰── must be a string ╰──── ``` - a key doesn't have to have a type in which case it is assumed to be `any` ```nu def okay [person: record<name age>] {} def okay [person: record<name: string age>] {} ``` - however, if you put a colon, you have to specify a type ```nu def err [person: record<name: >] {} # errors with Error: nu::parser::parse_mismatch × Parse mismatch during operation. ╭─[entry #12:1:1] 1 │ def unwrap [res: record<name: >] { $res } · ┬ · ╰── expected type after colon ╰──── ``` # User-Facing Changes **[BREAKING CHANGES]** - this change adds a field to `SyntaxShape::Record` so any plugins that used it will have to update and include the field. though if you are unsure of the type the record expects, `SyntaxShape::Record(vec![])` will suffice
2023-04-26 13:16:55 +00:00
SyntaxShape::Record(entries) => {
if entries.is_empty() {
write!(f, "record")
} else {
write!(f, "record<{}>", mk_fmt(entries))
allow records to have type annotations (#8914) # Description follow up to #8529 cleaned up version of #8892 - the original syntax is okay ```nu def okay [rec: record] {} ``` - you can now add type annotations for fields if you know them before hand ```nu def okay [rec: record<name: string>] {} ``` - you can specify multiple fields ```nu def okay [person: record<name: string age: int>] {} # an optional comma is allowed def okay [person: record<name: string, age: int>] {} ``` - if annotations are specified, any use of the command will be type checked against the specified type ```nu def unwrap [result: record<ok: bool, value: any>] {} unwrap {ok: 2, value: "value"} # errors with Error: nu::parser::type_mismatch × Type mismatch. ╭─[entry #4:1:1] 1 │ unwrap {ok: 2, value: "value"} · ───────┬───── · ╰── expected record<ok: bool, value: any>, found record<ok: int, value: string> ╰──── ``` > here the error is in the `ok` field, since `any` is coerced into any type > as a result `unwrap {ok: true, value: "value"}` is okay - the key must be a string, either quoted or unquoted ```nu def err [rec: record<{}: list>] {} # errors with Error: × `record` type annotations key not string ╭─[entry #7:1:1] 1 │ def unwrap [result: record<{}: bool, value: any>] {} · ─┬ · ╰── must be a string ╰──── ``` - a key doesn't have to have a type in which case it is assumed to be `any` ```nu def okay [person: record<name age>] {} def okay [person: record<name: string age>] {} ``` - however, if you put a colon, you have to specify a type ```nu def err [person: record<name: >] {} # errors with Error: nu::parser::parse_mismatch × Parse mismatch during operation. ╭─[entry #12:1:1] 1 │ def unwrap [res: record<name: >] { $res } · ┬ · ╰── expected type after colon ╰──── ``` # User-Facing Changes **[BREAKING CHANGES]** - this change adds a field to `SyntaxShape::Record` so any plugins that used it will have to update and include the field. though if you are unsure of the type the record expects, `SyntaxShape::Record(vec![])` will suffice
2023-04-26 13:16:55 +00:00
}
}
SyntaxShape::Filesize => write!(f, "filesize"),
SyntaxShape::Duration => write!(f, "duration"),
SyntaxShape::DateTime => write!(f, "datetime"),
SyntaxShape::Operator => write!(f, "operator"),
SyntaxShape::RowCondition => write!(f, "condition"),
SyntaxShape::MathExpression => write!(f, "variable"),
SyntaxShape::Variable => write!(f, "var"),
SyntaxShape::VarWithOptType => write!(f, "vardecl"),
SyntaxShape::Signature => write!(f, "signature"),
Add pattern matching (#8590) # Description This adds `match` and basic pattern matching. An example: ``` match $x { 1..10 => { print "Value is between 1 and 10" } { foo: $bar } => { print $"Value has a 'foo' field with value ($bar)" } [$a, $b] => { print $"Value is a list with two items: ($a) and ($b)" } _ => { print "Value is none of the above" } } ``` Like the recent changes to `if` to allow it to be used as an expression, `match` can also be used as an expression. This allows you to assign the result to a variable, eg) `let xyz = match ...` I've also included a short-hand pattern for matching records, as I think it might help when doing a lot of record patterns: `{$foo}` which is equivalent to `{foo: $foo}`. There are still missing components, so consider this the first step in full pattern matching support. Currently missing: * Patterns for strings * Or-patterns (like the `|` in Rust) * Patterns for tables (unclear how we want to match a table, so it'll need some design) * Patterns for binary values * And much more # User-Facing Changes [see above] # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
2023-03-24 01:52:01 +00:00
SyntaxShape::MatchPattern => write!(f, "matchpattern"),
SyntaxShape::MatchBlock => write!(f, "matchblock"),
SyntaxShape::Expression => write!(f, "expression"),
SyntaxShape::Boolean => write!(f, "bool"),
SyntaxShape::Error => write!(f, "error"),
SyntaxShape::Custom(x, _) => write!(f, "custom<{x}>"),
SyntaxShape::OneOf(list) => {
let arg_vec: Vec<_> = list.iter().map(|x| x.to_string()).collect();
let arg_string = arg_vec.join(", ");
write!(f, "one_of({arg_string})")
}
SyntaxShape::Nothing => write!(f, "nothing"),
}
}
}