Default values (#4770)

This commit is contained in:
JT 2022-03-07 15:08:56 -05:00 committed by GitHub
parent a3df2e5631
commit 1837bf775c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 289 additions and 19 deletions

View file

@ -62,6 +62,9 @@ fn eval_call(
if let Some(arg) = call.positional.get(param_idx) {
let result = eval_expression(engine_state, caller_stack, arg)?;
callee_stack.add_var(var_id, result);
} else if let Some(arg) = &param.default_value {
let result = eval_expression(engine_state, caller_stack, arg)?;
callee_stack.add_var(var_id, result);
} else {
callee_stack.add_var(var_id, Value::nothing(call.head));
}
@ -103,6 +106,10 @@ fn eval_call(
if let Some(arg) = &call_named.1 {
let result = eval_expression(engine_state, caller_stack, arg)?;
callee_stack.add_var(var_id, result);
} else if let Some(arg) = &named.default_value {
let result = eval_expression(engine_state, caller_stack, arg)?;
callee_stack.add_var(var_id, result);
} else {
callee_stack.add_var(
@ -126,6 +133,10 @@ fn eval_call(
span: call.head,
},
)
} else if let Some(arg) = &named.default_value {
let result = eval_expression(engine_state, caller_stack, arg)?;
callee_stack.add_var(var_id, result);
} else {
callee_stack.add_var(var_id, Value::Nothing { span: call.head })
}

View file

@ -19,6 +19,13 @@ pub enum ParseError {
#[diagnostic(code(nu::parser::extra_positional), url(docsrs), help("Usage: {0}"))]
ExtraPositional(String, #[label = "extra positional argument"] Span),
#[error("Require positional parameter after optional parameter")]
#[diagnostic(code(nu::parser::required_after_optional), url(docsrs))]
RequiredAfterOptional(
String,
#[label = "required parameter {0} after optional parameter"] Span,
),
#[error("Unexpected end of code.")]
#[diagnostic(code(nu::parser::unexpected_eof), url(docsrs))]
UnexpectedEof(String, #[label("expected closing {0}")] Span),
@ -246,6 +253,7 @@ impl ParseError {
ParseError::UnknownCommand(s) => *s,
ParseError::NonUtf8(s) => *s,
ParseError::UnknownFlag(_, _, s) => *s,
ParseError::RequiredAfterOptional(_, s) => *s,
ParseError::UnknownType(s) => *s,
ParseError::MissingFlagParam(_, s) => *s,
ParseError::ShortFlagBatchCantTakeArg(s) => *s,

View file

@ -176,6 +176,7 @@ pub fn parse_for(
desc: String::new(),
shape: SyntaxShape::Any,
var_id: Some(*var_id),
default_value: None,
},
);
}

View file

@ -2677,6 +2677,7 @@ pub fn parse_row_condition(
desc: "row condition".into(),
shape: SyntaxShape::Any,
var_id: Some(var_id),
default_value: None,
});
working_set.add_block(block)
@ -2742,11 +2743,14 @@ pub fn parse_signature_helper(
working_set: &mut StateWorkingSet,
span: Span,
) -> (Box<Signature>, Option<ParseError>) {
#[allow(clippy::enum_variant_names)]
enum ParseMode {
ArgMode,
TypeMode,
DefaultValueMode,
}
#[derive(Debug)]
enum Arg {
Positional(PositionalArg, bool), // bool - required
RestPositional(PositionalArg),
@ -2756,7 +2760,13 @@ pub fn parse_signature_helper(
let mut error = None;
let source = working_set.get_span_contents(span);
let (output, err) = lex(source, span.start, &[b'\n', b'\r', b','], &[b':'], false);
let (output, err) = lex(
source,
span.start,
&[b'\n', b'\r', b','],
&[b':', b'='],
false,
);
error = error.or(err);
let mut args: Vec<Arg> = vec![];
@ -2776,12 +2786,24 @@ pub fn parse_signature_helper(
ParseMode::ArgMode => {
parse_mode = ParseMode::TypeMode;
}
ParseMode::TypeMode => {
ParseMode::TypeMode | ParseMode::DefaultValueMode => {
// We're seeing two types for the same thing for some reason, error
error =
error.or_else(|| Some(ParseError::Expected("type".into(), span)));
}
}
} else if contents == b"=" {
match parse_mode {
ParseMode::ArgMode | ParseMode::TypeMode => {
parse_mode = ParseMode::DefaultValueMode;
}
ParseMode::DefaultValueMode => {
// We're seeing two default values for some reason, error
error = error.or_else(|| {
Some(ParseError::Expected("default value".into(), span))
});
}
}
} else {
match parse_mode {
ParseMode::ArgMode => {
@ -2802,6 +2824,7 @@ pub fn parse_signature_helper(
short: None,
required: false,
var_id: Some(var_id),
default_value: None,
}));
} else {
let short_flag = &flags[1];
@ -2832,6 +2855,7 @@ pub fn parse_signature_helper(
short: Some(chars[0]),
required: false,
var_id: Some(var_id),
default_value: None,
}));
} else {
error = error.or_else(|| {
@ -2864,6 +2888,7 @@ pub fn parse_signature_helper(
short: Some(chars[0]),
required: false,
var_id: Some(var_id),
default_value: None,
}));
} else if contents.starts_with(b"(-") {
let short_flag = &contents[2..];
@ -2921,6 +2946,7 @@ pub fn parse_signature_helper(
name,
shape: SyntaxShape::Any,
var_id: Some(var_id),
default_value: None,
},
false,
))
@ -2935,6 +2961,7 @@ pub fn parse_signature_helper(
name,
shape: SyntaxShape::Any,
var_id: Some(var_id),
default_value: None,
}));
} else {
let name = String::from_utf8_lossy(contents).to_string();
@ -2949,6 +2976,7 @@ pub fn parse_signature_helper(
name,
shape: SyntaxShape::Any,
var_id: Some(var_id),
default_value: None,
},
true,
))
@ -2982,6 +3010,97 @@ pub fn parse_signature_helper(
}
parse_mode = ParseMode::ArgMode;
}
ParseMode::DefaultValueMode => {
if let Some(last) = args.last_mut() {
let (expression, err) =
parse_value(working_set, span, &SyntaxShape::Any);
error = error.or(err);
//TODO check if we're replacing a custom parameter already
match last {
Arg::Positional(
PositionalArg {
shape,
var_id,
default_value,
..
},
required,
) => {
let var_id = var_id.expect("internal error: all custom parameters must have var_ids");
let var_type = working_set.get_variable(var_id);
match var_type {
Type::Unknown => {
working_set.set_variable_type(
var_id,
expression.ty.clone(),
);
}
t => {
if t != &expression.ty {
error = error.or_else(|| {
Some(ParseError::AssignmentMismatch(
"Default value wrong type".into(),
format!("default value not {}", t),
expression.span,
))
})
}
}
}
*shape = expression.ty.to_shape();
*default_value = Some(expression);
*required = false;
}
Arg::RestPositional(..) => {
error = error.or_else(|| {
Some(ParseError::AssignmentMismatch(
"Rest parameter given default value".into(),
"can't have default value".into(),
expression.span,
))
})
}
Arg::Flag(Flag {
arg,
var_id,
default_value,
..
}) => {
let var_id = var_id.expect("internal error: all custom parameters must have var_ids");
let var_type = working_set.get_variable(var_id);
let expression_ty = expression.ty.clone();
let expression_span = expression.span;
*default_value = Some(expression);
// Flags with a boolean type are just present/not-present switches
if var_type != &Type::Bool {
match var_type {
Type::Unknown => {
*arg = Some(expression_ty.to_shape());
working_set
.set_variable_type(var_id, expression_ty);
}
t => {
if t != &expression_ty {
error = error.or_else(|| {
Some(ParseError::AssignmentMismatch(
"Default value wrong type".into(),
format!("default value not {}", t),
expression_span,
))
})
}
}
}
}
}
}
}
parse_mode = ParseMode::ArgMode;
}
}
}
}
@ -3030,6 +3149,14 @@ pub fn parse_signature_helper(
match arg {
Arg::Positional(positional, required) => {
if required {
if !sig.optional_positional.is_empty() {
error = error.or_else(|| {
Some(ParseError::RequiredAfterOptional(
positional.name.clone(),
span,
))
})
}
sig.required_positional.push(positional)
} else {
sig.optional_positional.push(positional)
@ -3379,6 +3506,7 @@ pub fn parse_block_expression(
name: "$it".into(),
desc: String::new(),
shape: SyntaxShape::Any,
default_value: None,
});
output.signature = Box::new(signature);
}
@ -4503,6 +4631,7 @@ fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression)
name: "$in".into(),
desc: String::new(),
shape: SyntaxShape::Any,
default_value: None,
});
let mut expr = expr.clone();

View file

@ -218,6 +218,7 @@ fn deserialize_argument(reader: argument::Reader) -> Result<PositionalArg, Shell
desc: desc.to_string(),
shape,
var_id: None,
default_value: None,
})
}
@ -262,6 +263,7 @@ fn deserialize_flag(reader: flag::Reader) -> Result<Flag, ShellError> {
required,
desc: desc.to_string(),
var_id: None,
default_value: None,
})
}

View file

@ -1,7 +1,9 @@
use serde::{Deserialize, Serialize};
use super::Expression;
use crate::{DeclId, Span, Spanned};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Call {
/// identifier of the declaration to call
pub decl_id: DeclId,

View file

@ -41,7 +41,7 @@ impl CellPath {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FullCellPath {
pub head: Expression,
pub tail: Vec<PathMember>,

View file

@ -1,9 +1,10 @@
use chrono::FixedOffset;
use serde::{Deserialize, Serialize};
use super::{Call, CellPath, Expression, FullCellPath, Operator, RangeOperator};
use crate::{ast::ImportPattern, BlockId, Signature, Span, Spanned, Unit, VarId};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Expr {
Bool(bool),
Int(i64),

View file

@ -1,8 +1,10 @@
use serde::{Deserialize, Serialize};
use super::{Expr, Operator};
use crate::ast::ImportPattern;
use crate::{engine::StateWorkingSet, BlockId, Signature, Span, Type, VarId, IN_VARIABLE_ID};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Expression {
pub expr: Expr,
pub span: Span,

View file

@ -1,21 +1,23 @@
use serde::{Deserialize, Serialize};
use crate::{span, OverlayId, Span};
use std::collections::HashSet;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ImportPatternMember {
Glob { span: Span },
Name { name: Vec<u8>, span: Span },
List { names: Vec<(Vec<u8>, Span)> },
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ImportPatternHead {
pub name: Vec<u8>,
pub id: Option<OverlayId>,
pub span: Span,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ImportPattern {
pub head: ImportPatternHead,
pub members: Vec<ImportPatternMember>,

View file

@ -56,7 +56,7 @@ pub enum RangeInclusion {
RightExclusive,
}
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub struct RangeOperator {
pub inclusion: RangeInclusion,
pub span: Span,

View file

@ -2,6 +2,7 @@ use serde::Deserialize;
use serde::Serialize;
use crate::ast::Call;
use crate::ast::Expression;
use crate::engine::Command;
use crate::engine::EngineState;
use crate::engine::Stack;
@ -10,24 +11,28 @@ use crate::PipelineData;
use crate::SyntaxShape;
use crate::VarId;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Flag {
pub long: String,
pub short: Option<char>,
pub arg: Option<SyntaxShape>,
pub required: bool,
pub desc: String,
// For custom commands
pub var_id: Option<VarId>,
pub default_value: Option<Expression>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PositionalArg {
pub name: String,
pub desc: String,
pub shape: SyntaxShape,
// For custom commands
pub var_id: Option<VarId>,
pub default_value: Option<Expression>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -123,6 +128,7 @@ impl Signature {
desc: "Display this help message".into(),
required: false,
var_id: None,
default_value: None,
};
Signature {
@ -160,6 +166,7 @@ impl Signature {
desc: desc.into(),
shape: shape.into(),
var_id: None,
default_value: None,
});
self
@ -177,6 +184,7 @@ impl Signature {
desc: desc.into(),
shape: shape.into(),
var_id: None,
default_value: None,
});
self
@ -193,6 +201,7 @@ impl Signature {
desc: desc.into(),
shape: shape.into(),
var_id: None,
default_value: None,
});
self
@ -215,6 +224,7 @@ impl Signature {
required: false,
desc: desc.into(),
var_id: None,
default_value: None,
});
self
@ -237,6 +247,7 @@ impl Signature {
required: true,
desc: desc.into(),
var_id: None,
default_value: None,
});
self
@ -258,6 +269,7 @@ impl Signature {
required: false,
desc: desc.into(),
var_id: None,
default_value: None,
});
self

View file

@ -2,7 +2,7 @@ use miette::SourceSpan;
use serde::{Deserialize, Serialize};
/// A spanned area of interest, generic over what kind of thing is of interest
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Spanned<T>
where
T: Clone + std::fmt::Debug,

View file

@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize};
use std::fmt::Display;
use crate::SyntaxShape;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Type {
Int,
@ -27,6 +29,34 @@ pub enum Type {
Signature,
}
impl Type {
pub fn to_shape(&self) -> SyntaxShape {
match self {
Type::Int => SyntaxShape::Int,
Type::Float => SyntaxShape::Number,
Type::Range => SyntaxShape::Range,
Type::Bool => SyntaxShape::Boolean,
Type::String => SyntaxShape::String,
Type::Block => SyntaxShape::Block(None), // FIXME needs more accuracy
Type::CellPath => SyntaxShape::CellPath,
Type::Duration => SyntaxShape::Duration,
Type::Date => SyntaxShape::DateTime,
Type::Filesize => SyntaxShape::Filesize,
Type::List(x) => SyntaxShape::List(Box::new(x.to_shape())),
Type::Number => SyntaxShape::Number,
Type::Nothing => SyntaxShape::Any,
Type::Record(_) => SyntaxShape::Record,
Type::Table => SyntaxShape::Table,
Type::ListStream => SyntaxShape::List(Box::new(SyntaxShape::Any)),
Type::Unknown => SyntaxShape::Any,
Type::Error => SyntaxShape::Any,
Type::Binary => SyntaxShape::Binary,
Type::Custom => SyntaxShape::Custom(Box::new(SyntaxShape::Any), String::new()),
Type::Signature => SyntaxShape::Signature,
}
}
}
impl Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {

View file

@ -1,4 +1,6 @@
#[derive(Debug, Clone, Copy)]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum Unit {
// Filesize units: metric
Byte,

View file

@ -46,7 +46,8 @@ fn test_signature_chained() {
name: "required".to_string(),
desc: "required description".to_string(),
shape: SyntaxShape::String,
var_id: None
var_id: None,
default_value: None,
})
);
assert_eq!(
@ -55,7 +56,8 @@ fn test_signature_chained() {
name: "optional".to_string(),
desc: "optional description".to_string(),
shape: SyntaxShape::String,
var_id: None
var_id: None,
default_value: None,
})
);
assert_eq!(
@ -64,7 +66,8 @@ fn test_signature_chained() {
name: "rest".to_string(),
desc: "rest description".to_string(),
shape: SyntaxShape::String,
var_id: None
var_id: None,
default_value: None,
})
);
@ -76,7 +79,8 @@ fn test_signature_chained() {
arg: Some(SyntaxShape::String),
required: true,
desc: "required named description".to_string(),
var_id: None
var_id: None,
default_value: None,
})
);
@ -88,7 +92,8 @@ fn test_signature_chained() {
arg: Some(SyntaxShape::String),
required: true,
desc: "required named description".to_string(),
var_id: None
var_id: None,
default_value: None,
})
);
}

View file

@ -287,3 +287,66 @@ fn bool_variable() -> TestResult {
fn bool_variable2() -> TestResult {
run_test(r#"$false"#, "false")
}
#[test]
fn default_value1() -> TestResult {
run_test(r#"def foo [x = 3] { $x }; foo"#, "3")
}
#[test]
fn default_value2() -> TestResult {
run_test(r#"def foo [x: int = 3] { $x }; foo"#, "3")
}
#[test]
fn default_value3() -> TestResult {
run_test(r#"def foo [--x = 3] { $x }; foo"#, "3")
}
#[test]
fn default_value4() -> TestResult {
run_test(r#"def foo [--x: int = 3] { $x }; foo"#, "3")
}
#[test]
fn default_value5() -> TestResult {
run_test(r#"def foo [x = 3] { $x }; foo 10"#, "10")
}
#[test]
fn default_value6() -> TestResult {
run_test(r#"def foo [x: int = 3] { $x }; foo 10"#, "10")
}
#[test]
fn default_value7() -> TestResult {
run_test(r#"def foo [--x = 3] { $x }; foo --x 10"#, "10")
}
#[test]
fn default_value8() -> TestResult {
run_test(r#"def foo [--x: int = 3] { $x }; foo --x 10"#, "10")
}
#[test]
fn default_value9() -> TestResult {
fail_test(r#"def foo [--x = 3] { $x }; foo --x a"#, "expected int")
}
#[test]
fn default_value10() -> TestResult {
fail_test(r#"def foo [x = 3] { $x }; foo a"#, "expected int")
}
#[test]
fn default_value11() -> TestResult {
fail_test(
r#"def foo [x = 3, y] { $x }; foo a"#,
"after optional parameter",
)
}
#[test]
fn default_value12() -> TestResult {
fail_test(r#"def foo [--x:int = "a"] { $x }"#, "default value not int")
}