forked from extern/nushell
Initial support for parse-time constants (#7436)
This commit is contained in:
parent
fa8629300f
commit
3a2c7900d6
104
crates/nu-command/src/core_commands/const_.rs
Normal file
104
crates/nu-command/src/core_commands/const_.rs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Const;
|
||||||
|
|
||||||
|
impl Command for Const {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"const"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Create a parse-time constant."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("const")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
.required("const_name", SyntaxShape::VarWithOptType, "constant name")
|
||||||
|
.required(
|
||||||
|
"initial_value",
|
||||||
|
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||||
|
"equals sign followed by constant value",
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"This command is a parser keyword. For details, check:
|
||||||
|
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["set", "let"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let var_id = call
|
||||||
|
.positional_nth(0)
|
||||||
|
.expect("checked through parser")
|
||||||
|
.as_var()
|
||||||
|
.expect("internal error: missing variable");
|
||||||
|
|
||||||
|
if let Some(constval) = engine_state.find_constant(var_id, &[]) {
|
||||||
|
// Instead of creating a second copy of the value in the stack, we could change
|
||||||
|
// stack.get_var() to check engine_state.find_constant().
|
||||||
|
stack.add_var(var_id, constval.clone());
|
||||||
|
|
||||||
|
Ok(PipelineData::empty())
|
||||||
|
} else {
|
||||||
|
Err(ShellError::NushellFailedSpanned(
|
||||||
|
"Missing Constant".to_string(),
|
||||||
|
"constant not added by the parser".to_string(),
|
||||||
|
call.head,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Create a new parse-time constant.",
|
||||||
|
example: "let x = 10",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Create a composite constant value",
|
||||||
|
example: "let x = { a: 10, b: 20 }",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use nu_protocol::engine::CommandType;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(Const {})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_command_type() {
|
||||||
|
assert!(matches!(Const.command_type(), CommandType::Keyword));
|
||||||
|
}
|
||||||
|
}
|
@ -70,9 +70,8 @@ impl Command for Let {
|
|||||||
)?
|
)?
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
//println!("Adding: {:?} to {}", rhs, var_id);
|
|
||||||
|
|
||||||
stack.add_var(var_id, rhs.into_value(call.head));
|
stack.add_var(var_id, rhs.into_value(call.head));
|
||||||
|
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ mod alias;
|
|||||||
mod ast;
|
mod ast;
|
||||||
mod break_;
|
mod break_;
|
||||||
mod commandline;
|
mod commandline;
|
||||||
|
mod const_;
|
||||||
mod continue_;
|
mod continue_;
|
||||||
mod debug;
|
mod debug;
|
||||||
mod def;
|
mod def;
|
||||||
@ -40,6 +41,7 @@ pub use alias::Alias;
|
|||||||
pub use ast::Ast;
|
pub use ast::Ast;
|
||||||
pub use break_::Break;
|
pub use break_::Break;
|
||||||
pub use commandline::Commandline;
|
pub use commandline::Commandline;
|
||||||
|
pub use const_::Const;
|
||||||
pub use continue_::Continue;
|
pub use continue_::Continue;
|
||||||
pub use debug::Debug;
|
pub use debug::Debug;
|
||||||
pub use def::Def;
|
pub use def::Def;
|
||||||
|
@ -84,25 +84,8 @@ impl Command for OverlayUse {
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let overlay_name = if let Some(kw_expression) = call.positional_nth(1) {
|
let overlay_name = if let Some(name) = call.opt(engine_state, caller_stack, 1)? {
|
||||||
// If renamed via the 'as' keyword, use the new name as the overlay name
|
name
|
||||||
if let Some(new_name_expression) = kw_expression.as_keyword() {
|
|
||||||
if let Some(new_name) = new_name_expression.as_string() {
|
|
||||||
new_name
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::NushellFailedSpanned(
|
|
||||||
"Wrong keyword type".to_string(),
|
|
||||||
"keyword argument not a string".to_string(),
|
|
||||||
new_name_expression.span,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::NushellFailedSpanned(
|
|
||||||
"Wrong keyword type".to_string(),
|
|
||||||
"keyword argument not a keyword".to_string(),
|
|
||||||
kw_expression.span,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else if engine_state
|
} else if engine_state
|
||||||
.find_overlay(name_arg.item.as_bytes())
|
.find_overlay(name_arg.item.as_bytes())
|
||||||
.is_some()
|
.is_some()
|
||||||
|
@ -32,6 +32,7 @@ pub fn create_default_context() -> EngineState {
|
|||||||
Ast,
|
Ast,
|
||||||
Break,
|
Break,
|
||||||
Commandline,
|
Commandline,
|
||||||
|
Const,
|
||||||
Continue,
|
Continue,
|
||||||
Debug,
|
Debug,
|
||||||
Def,
|
Def,
|
||||||
|
@ -293,3 +293,25 @@ fn source_env_is_scoped() {
|
|||||||
assert!(actual.err.contains("executable was not found"));
|
assert!(actual.err.contains("executable was not found"));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn source_env_const_file() {
|
||||||
|
Playground::setup("source_env_const_file", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
let-env FOO = 'foo'
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let inp = &[
|
||||||
|
r#"const file = 'spam.nu'"#,
|
||||||
|
r#"source-env $file"#,
|
||||||
|
r#"$env.FOO"#,
|
||||||
|
];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -378,6 +378,19 @@ pub enum ParseError {
|
|||||||
#[diagnostic(code(nu::shell::error_reading_file), url(docsrs))]
|
#[diagnostic(code(nu::shell::error_reading_file), url(docsrs))]
|
||||||
ReadingFile(String, #[label("{0}")] Span),
|
ReadingFile(String, #[label("{0}")] Span),
|
||||||
|
|
||||||
|
/// Tried assigning non-constant value to a constant
|
||||||
|
///
|
||||||
|
/// ## Resolution
|
||||||
|
///
|
||||||
|
/// Only a subset of expressions are allowed to be assigned as a constant during parsing.
|
||||||
|
#[error("Not a constant.")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(nu::parser::not_a_constant),
|
||||||
|
url(docsrs),
|
||||||
|
help("Only a subset of expressions are allowed constants during parsing. Try using the 'let' command or typing the value literally.")
|
||||||
|
)]
|
||||||
|
NotAConstant(#[label = "Value is not a parse-time constant"] Span),
|
||||||
|
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
#[diagnostic()]
|
#[diagnostic()]
|
||||||
LabeledError(String, String, #[label("{1}")] Span),
|
LabeledError(String, String, #[label("{1}")] Span),
|
||||||
@ -450,6 +463,7 @@ impl ParseError {
|
|||||||
ParseError::ShellErrRedirect(s) => *s,
|
ParseError::ShellErrRedirect(s) => *s,
|
||||||
ParseError::ShellOutErrRedirect(s) => *s,
|
ParseError::ShellOutErrRedirect(s) => *s,
|
||||||
ParseError::UnknownOperator(_, _, s) => *s,
|
ParseError::UnknownOperator(_, _, s) => *s,
|
||||||
|
ParseError::NotAConstant(s) => *s,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
124
crates/nu-parser/src/eval.rs
Normal file
124
crates/nu-parser/src/eval.rs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
use crate::ParseError;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::{Expr, Expression},
|
||||||
|
engine::StateWorkingSet,
|
||||||
|
Span, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Evaluate a constant value at parse time
|
||||||
|
///
|
||||||
|
/// Based off eval_expression() in the engine
|
||||||
|
pub fn eval_constant(
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
expr: &Expression,
|
||||||
|
) -> Result<Value, ParseError> {
|
||||||
|
match &expr.expr {
|
||||||
|
Expr::Bool(b) => Ok(Value::boolean(*b, expr.span)),
|
||||||
|
Expr::Int(i) => Ok(Value::int(*i, expr.span)),
|
||||||
|
Expr::Float(f) => Ok(Value::float(*f, expr.span)),
|
||||||
|
Expr::Binary(b) => Ok(Value::Binary {
|
||||||
|
val: b.clone(),
|
||||||
|
span: expr.span,
|
||||||
|
}),
|
||||||
|
Expr::Var(var_id) => match working_set.find_constant(*var_id) {
|
||||||
|
Some(val) => Ok(val.clone()),
|
||||||
|
None => Err(ParseError::NotAConstant(expr.span)),
|
||||||
|
},
|
||||||
|
Expr::CellPath(cell_path) => Ok(Value::CellPath {
|
||||||
|
val: cell_path.clone(),
|
||||||
|
span: expr.span,
|
||||||
|
}),
|
||||||
|
Expr::FullCellPath(cell_path) => {
|
||||||
|
let value = eval_constant(working_set, &cell_path.head)?;
|
||||||
|
|
||||||
|
match value.follow_cell_path(&cell_path.tail, false) {
|
||||||
|
Ok(val) => Ok(val),
|
||||||
|
// TODO: Better error conversion
|
||||||
|
Err(shell_error) => Err(ParseError::LabeledError(
|
||||||
|
"Error when following cell path".to_string(),
|
||||||
|
format!("{:?}", shell_error),
|
||||||
|
expr.span,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::DateTime(dt) => Ok(Value::Date {
|
||||||
|
val: *dt,
|
||||||
|
span: expr.span,
|
||||||
|
}),
|
||||||
|
Expr::List(x) => {
|
||||||
|
let mut output = vec![];
|
||||||
|
for expr in x {
|
||||||
|
output.push(eval_constant(working_set, expr)?);
|
||||||
|
}
|
||||||
|
Ok(Value::List {
|
||||||
|
vals: output,
|
||||||
|
span: expr.span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Expr::Record(fields) => {
|
||||||
|
let mut cols = vec![];
|
||||||
|
let mut vals = vec![];
|
||||||
|
for (col, val) in fields {
|
||||||
|
// avoid duplicate cols.
|
||||||
|
let col_name = value_as_string(eval_constant(working_set, col)?, expr.span)?;
|
||||||
|
let pos = cols.iter().position(|c| c == &col_name);
|
||||||
|
match pos {
|
||||||
|
Some(index) => {
|
||||||
|
vals[index] = eval_constant(working_set, val)?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
cols.push(col_name);
|
||||||
|
vals.push(eval_constant(working_set, val)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::Record {
|
||||||
|
cols,
|
||||||
|
vals,
|
||||||
|
span: expr.span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Expr::Table(headers, vals) => {
|
||||||
|
let mut output_headers = vec![];
|
||||||
|
for expr in headers {
|
||||||
|
output_headers.push(value_as_string(
|
||||||
|
eval_constant(working_set, expr)?,
|
||||||
|
expr.span,
|
||||||
|
)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut output_rows = vec![];
|
||||||
|
for val in vals {
|
||||||
|
let mut row = vec![];
|
||||||
|
for expr in val {
|
||||||
|
row.push(eval_constant(working_set, expr)?);
|
||||||
|
}
|
||||||
|
output_rows.push(Value::Record {
|
||||||
|
cols: output_headers.clone(),
|
||||||
|
vals: row,
|
||||||
|
span: expr.span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(Value::List {
|
||||||
|
vals: output_rows,
|
||||||
|
span: expr.span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Expr::Keyword(_, _, expr) => eval_constant(working_set, expr),
|
||||||
|
Expr::String(s) => Ok(Value::String {
|
||||||
|
val: s.clone(),
|
||||||
|
span: expr.span,
|
||||||
|
}),
|
||||||
|
Expr::Nothing => Ok(Value::Nothing { span: expr.span }),
|
||||||
|
_ => Err(ParseError::NotAConstant(expr.span)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the value as a string
|
||||||
|
pub fn value_as_string(value: Value, span: Span) -> Result<String, ParseError> {
|
||||||
|
match value {
|
||||||
|
Value::String { val, .. } => Ok(val),
|
||||||
|
_ => Err(ParseError::NotAConstant(span)),
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
mod deparse;
|
mod deparse;
|
||||||
mod errors;
|
mod errors;
|
||||||
|
mod eval;
|
||||||
mod flatten;
|
mod flatten;
|
||||||
mod known_external;
|
mod known_external;
|
||||||
mod lex;
|
mod lex;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::eval::{eval_constant, value_as_string};
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_path::canonicalize_with;
|
use nu_path::canonicalize_with;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
@ -20,8 +21,8 @@ use crate::{
|
|||||||
lex,
|
lex,
|
||||||
parser::{
|
parser::{
|
||||||
check_call, check_name, garbage, garbage_pipeline, lite_parse, parse, parse_internal_call,
|
check_call, check_name, garbage, garbage_pipeline, lite_parse, parse, parse_internal_call,
|
||||||
parse_multispan_value, parse_signature, parse_string, parse_var_with_opt_type, trim_quotes,
|
parse_multispan_value, parse_signature, parse_string, parse_value, parse_var_with_opt_type,
|
||||||
LiteCommand, LiteElement, ParsedInternalCall,
|
trim_quotes, LiteCommand, LiteElement, ParsedInternalCall,
|
||||||
},
|
},
|
||||||
unescape_unquote_string, ParseError,
|
unescape_unquote_string, ParseError,
|
||||||
};
|
};
|
||||||
@ -1636,8 +1637,7 @@ pub fn parse_use(
|
|||||||
|
|
||||||
// TODO: Add checking for importing too long import patterns, e.g.:
|
// TODO: Add checking for importing too long import patterns, e.g.:
|
||||||
// > use spam foo non existent names here do not throw error
|
// > use spam foo non existent names here do not throw error
|
||||||
let (import_pattern, module) =
|
let (import_pattern, module) = if let Some(module_id) = import_pattern.head.id {
|
||||||
if let Some(module_id) = working_set.find_module(&import_pattern.head.name) {
|
|
||||||
(import_pattern, working_set.get_module(module_id).clone())
|
(import_pattern, working_set.get_module(module_id).clone())
|
||||||
} else {
|
} else {
|
||||||
// It could be a file
|
// It could be a file
|
||||||
@ -2386,16 +2386,16 @@ pub fn parse_overlay_use(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (overlay_name, overlay_name_span) = if let Some(expr) = call.positional_nth(0) {
|
let (overlay_name, overlay_name_span) = if let Some(expr) = call.positional_nth(0) {
|
||||||
if let Some(s) = expr.as_string() {
|
match eval_constant(working_set, expr) {
|
||||||
(s, expr.span)
|
Ok(val) => match value_as_string(val, expr.span) {
|
||||||
} else {
|
Ok(s) => (s, expr.span),
|
||||||
return (
|
Err(err) => {
|
||||||
garbage_pipeline(spans),
|
return (garbage_pipeline(spans), Some(err));
|
||||||
Some(ParseError::UnknownState(
|
}
|
||||||
"internal error: Module name not a string".into(),
|
},
|
||||||
expr.span,
|
Err(err) => {
|
||||||
)),
|
return (garbage_pipeline(spans), Some(err));
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
@ -2409,20 +2409,15 @@ pub fn parse_overlay_use(
|
|||||||
|
|
||||||
let new_name = if let Some(kw_expression) = call.positional_nth(1) {
|
let new_name = if let Some(kw_expression) = call.positional_nth(1) {
|
||||||
if let Some(new_name_expression) = kw_expression.as_keyword() {
|
if let Some(new_name_expression) = kw_expression.as_keyword() {
|
||||||
if let Some(new_name) = new_name_expression.as_string() {
|
match eval_constant(working_set, new_name_expression) {
|
||||||
Some(Spanned {
|
Ok(val) => match value_as_string(val, new_name_expression.span) {
|
||||||
item: new_name,
|
Ok(s) => Some(Spanned {
|
||||||
|
item: s,
|
||||||
span: new_name_expression.span,
|
span: new_name_expression.span,
|
||||||
})
|
}),
|
||||||
} else {
|
Err(err) => return (garbage_pipeline(spans), Some(err)),
|
||||||
return (
|
},
|
||||||
garbage_pipeline(spans),
|
Err(err) => return (garbage_pipeline(spans), Some(err)),
|
||||||
Some(ParseError::TypeMismatch(
|
|
||||||
Type::String,
|
|
||||||
new_name_expression.ty.clone(),
|
|
||||||
new_name_expression.span,
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
@ -2751,19 +2746,23 @@ pub fn parse_overlay_hide(
|
|||||||
(pipeline, None)
|
(pipeline, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_let(
|
pub fn parse_let_or_const(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
spans: &[Span],
|
spans: &[Span],
|
||||||
expand_aliases_denylist: &[usize],
|
expand_aliases_denylist: &[usize],
|
||||||
) -> (Pipeline, Option<ParseError>) {
|
) -> (Pipeline, Option<ParseError>) {
|
||||||
let name = working_set.get_span_contents(spans[0]);
|
let name = working_set.get_span_contents(spans[0]);
|
||||||
|
|
||||||
if name == b"let" {
|
if name == b"let" || name == b"const" {
|
||||||
|
let is_const = &name == b"const";
|
||||||
|
|
||||||
if let Some((span, err)) = check_name(working_set, spans) {
|
if let Some((span, err)) = check_name(working_set, spans) {
|
||||||
return (Pipeline::from_vec(vec![garbage(*span)]), Some(err));
|
return (Pipeline::from_vec(vec![garbage(*span)]), Some(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(decl_id) = working_set.find_decl(b"let", &Type::Any) {
|
if let Some(decl_id) =
|
||||||
|
working_set.find_decl(if is_const { b"const" } else { b"let" }, &Type::Any)
|
||||||
|
{
|
||||||
let cmd = working_set.get_decl(decl_id);
|
let cmd = working_set.get_decl(decl_id);
|
||||||
let call_signature = cmd.signature().call_signature();
|
let call_signature = cmd.signature().call_signature();
|
||||||
|
|
||||||
@ -2815,6 +2814,15 @@ pub fn parse_let(
|
|||||||
|
|
||||||
if let Some(var_id) = var_id {
|
if let Some(var_id) = var_id {
|
||||||
working_set.set_variable_type(var_id, rhs_type);
|
working_set.set_variable_type(var_id, rhs_type);
|
||||||
|
|
||||||
|
if is_const {
|
||||||
|
match eval_constant(working_set, &rvalue) {
|
||||||
|
Ok(val) => {
|
||||||
|
working_set.add_constant(var_id, val);
|
||||||
|
}
|
||||||
|
Err(err) => error = error.or(Some(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let call = Box::new(Call {
|
let call = Box::new(Call {
|
||||||
@ -2866,7 +2874,7 @@ pub fn parse_let(
|
|||||||
(
|
(
|
||||||
garbage_pipeline(spans),
|
garbage_pipeline(spans),
|
||||||
Some(ParseError::UnknownState(
|
Some(ParseError::UnknownState(
|
||||||
"internal error: let statement unparseable".into(),
|
"internal error: let or const statement unparseable".into(),
|
||||||
span(spans),
|
span(spans),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
@ -3036,10 +3044,45 @@ pub fn parse_source(
|
|||||||
|
|
||||||
// Command and one file name
|
// Command and one file name
|
||||||
if spans.len() >= 2 {
|
if spans.len() >= 2 {
|
||||||
let name_expr = working_set.get_span_contents(spans[1]);
|
let (expr, err) = parse_value(
|
||||||
let (filename, err) = unescape_unquote_string(name_expr, spans[1]);
|
working_set,
|
||||||
|
spans[1],
|
||||||
|
&SyntaxShape::Any,
|
||||||
|
expand_aliases_denylist,
|
||||||
|
);
|
||||||
|
|
||||||
|
error = error.or(err);
|
||||||
|
|
||||||
|
let val = match eval_constant(working_set, &expr) {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(err) => {
|
||||||
|
return (
|
||||||
|
Pipeline::from_vec(vec![Expression {
|
||||||
|
expr: Expr::Call(call),
|
||||||
|
span: span(&spans[1..]),
|
||||||
|
ty: Type::Any,
|
||||||
|
custom_completion: None,
|
||||||
|
}]),
|
||||||
|
Some(err),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let filename = match value_as_string(val, spans[1]) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(err) => {
|
||||||
|
return (
|
||||||
|
Pipeline::from_vec(vec![Expression {
|
||||||
|
expr: Expr::Call(call),
|
||||||
|
span: span(&spans[1..]),
|
||||||
|
ty: Type::Any,
|
||||||
|
custom_completion: None,
|
||||||
|
}]),
|
||||||
|
Some(err),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if err.is_none() {
|
|
||||||
if let Some(path) = find_in_dirs(&filename, working_set, &cwd, LIB_DIRS_ENV) {
|
if let Some(path) = find_in_dirs(&filename, working_set, &cwd, LIB_DIRS_ENV) {
|
||||||
if let Ok(contents) = std::fs::read(&path) {
|
if let Ok(contents) = std::fs::read(&path) {
|
||||||
// Change currently parsed directory
|
// Change currently parsed directory
|
||||||
@ -3107,9 +3150,6 @@ pub fn parse_source(
|
|||||||
} else {
|
} else {
|
||||||
error = error.or(Some(ParseError::SourcedFileNotFound(filename, spans[1])));
|
error = error.or(Some(ParseError::SourcedFileNotFound(filename, spans[1])));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return (garbage_pipeline(spans), Some(ParseError::NonUtf8(spans[1])));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
Pipeline::from_vec(vec![Expression {
|
Pipeline::from_vec(vec![Expression {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
|
eval::{eval_constant, value_as_string},
|
||||||
lex, parse_mut,
|
lex, parse_mut,
|
||||||
type_check::{math_result_type, type_compatible},
|
type_check::{math_result_type, type_compatible},
|
||||||
ParseError, Token, TokenContents,
|
ParseError, Token, TokenContents,
|
||||||
@ -17,8 +18,8 @@ use nu_protocol::{
|
|||||||
|
|
||||||
use crate::parse_keywords::{
|
use crate::parse_keywords::{
|
||||||
parse_alias, parse_def, parse_def_predecl, parse_export_in_block, parse_extern, parse_for,
|
parse_alias, parse_def, parse_def_predecl, parse_export_in_block, parse_extern, parse_for,
|
||||||
parse_hide, parse_let, parse_module, parse_overlay, parse_source, parse_use, parse_where,
|
parse_hide, parse_let_or_const, parse_module, parse_overlay, parse_source, parse_use,
|
||||||
parse_where_expr,
|
parse_where, parse_where_expr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@ -2832,11 +2833,8 @@ pub fn parse_import_pattern(
|
|||||||
) -> (Expression, Option<ParseError>) {
|
) -> (Expression, Option<ParseError>) {
|
||||||
let mut error = None;
|
let mut error = None;
|
||||||
|
|
||||||
let (head, head_span) = if let Some(head_span) = spans.get(0) {
|
let head_span = if let Some(head_span) = spans.get(0) {
|
||||||
(
|
head_span
|
||||||
working_set.get_span_contents(*head_span).to_vec(),
|
|
||||||
head_span,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
garbage(span(spans)),
|
garbage(span(spans)),
|
||||||
@ -2844,7 +2842,25 @@ pub fn parse_import_pattern(
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let maybe_module_id = working_set.find_module(&head);
|
let (head_expr, err) = parse_value(
|
||||||
|
working_set,
|
||||||
|
*head_span,
|
||||||
|
&SyntaxShape::Any,
|
||||||
|
expand_aliases_denylist,
|
||||||
|
);
|
||||||
|
error = error.or(err);
|
||||||
|
|
||||||
|
let (maybe_module_id, head_name) = match eval_constant(working_set, &head_expr) {
|
||||||
|
Ok(val) => match value_as_string(val, head_expr.span) {
|
||||||
|
Ok(s) => (working_set.find_module(s.as_bytes()), s.into_bytes()),
|
||||||
|
Err(err) => {
|
||||||
|
return (garbage(span(spans)), error.or(Some(err)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
return (garbage(span(spans)), error.or(Some(err)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let (import_pattern, err) = if let Some(tail_span) = spans.get(1) {
|
let (import_pattern, err) = if let Some(tail_span) = spans.get(1) {
|
||||||
// FIXME: expand this to handle deeper imports once we support module imports
|
// FIXME: expand this to handle deeper imports once we support module imports
|
||||||
@ -2853,7 +2869,7 @@ pub fn parse_import_pattern(
|
|||||||
(
|
(
|
||||||
ImportPattern {
|
ImportPattern {
|
||||||
head: ImportPatternHead {
|
head: ImportPatternHead {
|
||||||
name: head,
|
name: head_name,
|
||||||
id: maybe_module_id,
|
id: maybe_module_id,
|
||||||
span: *head_span,
|
span: *head_span,
|
||||||
},
|
},
|
||||||
@ -2886,7 +2902,7 @@ pub fn parse_import_pattern(
|
|||||||
(
|
(
|
||||||
ImportPattern {
|
ImportPattern {
|
||||||
head: ImportPatternHead {
|
head: ImportPatternHead {
|
||||||
name: head,
|
name: head_name,
|
||||||
id: maybe_module_id,
|
id: maybe_module_id,
|
||||||
span: *head_span,
|
span: *head_span,
|
||||||
},
|
},
|
||||||
@ -2899,7 +2915,7 @@ pub fn parse_import_pattern(
|
|||||||
_ => (
|
_ => (
|
||||||
ImportPattern {
|
ImportPattern {
|
||||||
head: ImportPatternHead {
|
head: ImportPatternHead {
|
||||||
name: head,
|
name: head_name,
|
||||||
id: maybe_module_id,
|
id: maybe_module_id,
|
||||||
span: *head_span,
|
span: *head_span,
|
||||||
},
|
},
|
||||||
@ -2914,7 +2930,7 @@ pub fn parse_import_pattern(
|
|||||||
(
|
(
|
||||||
ImportPattern {
|
ImportPattern {
|
||||||
head: ImportPatternHead {
|
head: ImportPatternHead {
|
||||||
name: head,
|
name: head_name,
|
||||||
id: maybe_module_id,
|
id: maybe_module_id,
|
||||||
span: *head_span,
|
span: *head_span,
|
||||||
},
|
},
|
||||||
@ -2931,7 +2947,7 @@ pub fn parse_import_pattern(
|
|||||||
(
|
(
|
||||||
ImportPattern {
|
ImportPattern {
|
||||||
head: ImportPatternHead {
|
head: ImportPatternHead {
|
||||||
name: head,
|
name: head_name,
|
||||||
id: maybe_module_id,
|
id: maybe_module_id,
|
||||||
span: *head_span,
|
span: *head_span,
|
||||||
},
|
},
|
||||||
@ -4898,7 +4914,7 @@ pub fn parse_expression(
|
|||||||
.0,
|
.0,
|
||||||
Some(ParseError::BuiltinCommandInPipeline("for".into(), spans[0])),
|
Some(ParseError::BuiltinCommandInPipeline("for".into(), spans[0])),
|
||||||
),
|
),
|
||||||
b"let" => (
|
b"let" | b"const" => (
|
||||||
parse_call(
|
parse_call(
|
||||||
working_set,
|
working_set,
|
||||||
&spans[pos..],
|
&spans[pos..],
|
||||||
@ -5166,7 +5182,9 @@ pub fn parse_builtin_commands(
|
|||||||
match name {
|
match name {
|
||||||
b"def" | b"def-env" => parse_def(working_set, lite_command, expand_aliases_denylist),
|
b"def" | b"def-env" => parse_def(working_set, lite_command, expand_aliases_denylist),
|
||||||
b"extern" => parse_extern(working_set, lite_command, expand_aliases_denylist),
|
b"extern" => parse_extern(working_set, lite_command, expand_aliases_denylist),
|
||||||
b"let" => parse_let(working_set, &lite_command.parts, expand_aliases_denylist),
|
b"let" | b"const" => {
|
||||||
|
parse_let_or_const(working_set, &lite_command.parts, expand_aliases_denylist)
|
||||||
|
}
|
||||||
b"mut" => parse_mut(working_set, &lite_command.parts, expand_aliases_denylist),
|
b"mut" => parse_mut(working_set, &lite_command.parts, expand_aliases_denylist),
|
||||||
b"for" => {
|
b"for" => {
|
||||||
let (expr, err) = parse_for(working_set, &lite_command.parts, expand_aliases_denylist);
|
let (expr, err) = parse_for(working_set, &lite_command.parts, expand_aliases_denylist);
|
||||||
|
@ -173,6 +173,9 @@ impl EngineState {
|
|||||||
for item in delta_overlay.vars.into_iter() {
|
for item in delta_overlay.vars.into_iter() {
|
||||||
existing_overlay.vars.insert(item.0, item.1);
|
existing_overlay.vars.insert(item.0, item.1);
|
||||||
}
|
}
|
||||||
|
for item in delta_overlay.constants.into_iter() {
|
||||||
|
existing_overlay.constants.insert(item.0, item.1);
|
||||||
|
}
|
||||||
for item in delta_overlay.aliases.into_iter() {
|
for item in delta_overlay.aliases.into_iter() {
|
||||||
existing_overlay.aliases.insert(item.0, item.1);
|
existing_overlay.aliases.insert(item.0, item.1);
|
||||||
}
|
}
|
||||||
@ -626,6 +629,16 @@ impl EngineState {
|
|||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_constant(&self, var_id: VarId, removed_overlays: &[Vec<u8>]) -> Option<&Value> {
|
||||||
|
for overlay_frame in self.active_overlays(removed_overlays).iter().rev() {
|
||||||
|
if let Some(val) = overlay_frame.constants.get(&var_id) {
|
||||||
|
return Some(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_span_contents(&self, span: &Span) -> &[u8] {
|
pub fn get_span_contents(&self, span: &Span) -> &[u8] {
|
||||||
for (contents, start, finish) in &self.file_contents {
|
for (contents, start, finish) in &self.file_contents {
|
||||||
if span.start >= *start && span.end <= *finish {
|
if span.start >= *start && span.end <= *finish {
|
||||||
@ -1654,6 +1667,29 @@ impl<'a> StateWorkingSet<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_constant(&mut self, var_id: VarId, val: Value) {
|
||||||
|
self.last_overlay_mut().constants.insert(var_id, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_constant(&self, var_id: VarId) -> Option<&Value> {
|
||||||
|
let mut removed_overlays = vec![];
|
||||||
|
|
||||||
|
for scope_frame in self.delta.scope.iter().rev() {
|
||||||
|
for overlay_frame in scope_frame
|
||||||
|
.active_overlays(&mut removed_overlays)
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
{
|
||||||
|
if let Some(val) = overlay_frame.constants.get(&var_id) {
|
||||||
|
return Some(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.permanent_state
|
||||||
|
.find_constant(var_id, &removed_overlays)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_variable(&self, var_id: VarId) -> &Variable {
|
pub fn get_variable(&self, var_id: VarId) -> &Variable {
|
||||||
let num_permanent_vars = self.permanent_state.num_vars();
|
let num_permanent_vars = self.permanent_state.num_vars();
|
||||||
if var_id < num_permanent_vars {
|
if var_id < num_permanent_vars {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{AliasId, DeclId, ModuleId, OverlayId, Type, VarId};
|
use crate::{AliasId, DeclId, ModuleId, OverlayId, Type, Value, VarId};
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
@ -199,6 +199,7 @@ impl ScopeFrame {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct OverlayFrame {
|
pub struct OverlayFrame {
|
||||||
pub vars: HashMap<Vec<u8>, VarId>,
|
pub vars: HashMap<Vec<u8>, VarId>,
|
||||||
|
pub constants: HashMap<VarId, Value>,
|
||||||
pub predecls: HashMap<Vec<u8>, DeclId>, // temporary storage for predeclarations
|
pub predecls: HashMap<Vec<u8>, DeclId>, // temporary storage for predeclarations
|
||||||
pub decls: HashMap<(Vec<u8>, Type), DeclId>,
|
pub decls: HashMap<(Vec<u8>, Type), DeclId>,
|
||||||
pub aliases: HashMap<Vec<u8>, AliasId>,
|
pub aliases: HashMap<Vec<u8>, AliasId>,
|
||||||
@ -212,6 +213,7 @@ impl OverlayFrame {
|
|||||||
pub fn from_origin(origin: ModuleId, prefixed: bool) -> Self {
|
pub fn from_origin(origin: ModuleId, prefixed: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
vars: HashMap::new(),
|
vars: HashMap::new(),
|
||||||
|
constants: HashMap::new(),
|
||||||
predecls: HashMap::new(),
|
predecls: HashMap::new(),
|
||||||
decls: HashMap::new(),
|
decls: HashMap::new(),
|
||||||
aliases: HashMap::new(),
|
aliases: HashMap::new(),
|
||||||
|
103
tests/const_/mod.rs
Normal file
103
tests/const_/mod.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
use nu_test_support::{nu, pipeline};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_bool() {
|
||||||
|
let inp = &[r#"const x = false"#, r#"$x"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: "tests/const_", pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_int() {
|
||||||
|
let inp = &[r#"const x = 10"#, r#"$x"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: "tests/const_", pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "10");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_float() {
|
||||||
|
let inp = &[r#"const x = 1.234"#, r#"$x"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: "tests/const_", pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "1.234");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_binary() {
|
||||||
|
let inp = &[r#"const x = 0x[12]"#, r#"$x"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: "tests/const_", pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert!(actual.out.contains("12"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_datetime() {
|
||||||
|
let inp = &[r#"const x = 2021-02-27T13:55:40+00:00"#, r#"$x"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: "tests/const_", pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert!(actual.out.contains("Sat, 27 Feb 2021 13:55:40"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_list() {
|
||||||
|
let inp = &[r#"const x = [ a b c ]"#, r#"$x | describe"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: "tests/const_", pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "list<string>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_record() {
|
||||||
|
let inp = &[r#"const x = { a: 10, b: 20, c: 30 }"#, r#"$x | describe"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: "tests/const_", pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "record<a: int, b: int, c: int>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_table() {
|
||||||
|
let inp = &[
|
||||||
|
r#"const x = [[a b c]; [10 20 30] [100 200 300]]"#,
|
||||||
|
r#"$x | describe"#,
|
||||||
|
];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: "tests/const_", pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "table<a: int, b: int, c: int>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_string() {
|
||||||
|
let inp = &[r#"const x = "abc""#, r#"$x"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: "tests/const_", pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "abc");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_nothing() {
|
||||||
|
let inp = &[r#"const x = $nothing"#, r#"$x | describe"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: "tests/const_", pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "nothing");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn const_unsupported() {
|
||||||
|
let inp = &[r#"const x = ('abc' | str length)"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: "tests/const_", pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert!(actual.err.contains("not_a_constant"));
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
extern crate nu_test_support;
|
extern crate nu_test_support;
|
||||||
|
|
||||||
|
mod const_;
|
||||||
mod hooks;
|
mod hooks;
|
||||||
mod modules;
|
mod modules;
|
||||||
mod overlays;
|
mod overlays;
|
||||||
|
@ -433,3 +433,44 @@ fn module_cyclical_imports_3() {
|
|||||||
assert!(actual.err.contains("cyclical"));
|
assert!(actual.err.contains("cyclical"));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_import_const_file() {
|
||||||
|
Playground::setup("module_import_const_file", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
export def foo [] { "foo" }
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let inp = &[r#"const file = 'spam.nu'"#, r#"use $file foo"#, r#"foo"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_import_const_module_name() {
|
||||||
|
Playground::setup("module_import_const_file", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
export def foo [] { "foo" }
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let inp = &[
|
||||||
|
r#"module spam { export def foo [] { "foo" } }"#,
|
||||||
|
r#"const mod = 'spam'"#,
|
||||||
|
r#"use $mod foo"#,
|
||||||
|
r#"foo"#,
|
||||||
|
];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -169,6 +169,33 @@ fn add_overlay_from_file_decl() {
|
|||||||
assert_eq!(actual_repl.out, "foo");
|
assert_eq!(actual_repl.out, "foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_overlay_from_const_file_decl() {
|
||||||
|
let inp = &[
|
||||||
|
r#"const file = 'samples/spam.nu'"#,
|
||||||
|
r#"overlay use $file"#,
|
||||||
|
r#"foo"#,
|
||||||
|
];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: "tests/overlays", pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_overlay_from_const_module_name_decl() {
|
||||||
|
let inp = &[
|
||||||
|
r#"module spam { export def foo [] { "foo" } }"#,
|
||||||
|
r#"const mod = 'spam'"#,
|
||||||
|
r#"overlay use $mod"#,
|
||||||
|
r#"foo"#,
|
||||||
|
];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: "tests/overlays", pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
}
|
||||||
|
|
||||||
// This one tests that the `nu_repl()` loop works correctly
|
// This one tests that the `nu_repl()` loop works correctly
|
||||||
#[test]
|
#[test]
|
||||||
fn add_overlay_from_file_decl_cd() {
|
fn add_overlay_from_file_decl_cd() {
|
||||||
@ -693,6 +720,23 @@ fn overlay_add_renamed() {
|
|||||||
assert_eq!(actual_repl.out, "foo");
|
assert_eq!(actual_repl.out, "foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn overlay_add_renamed_const() {
|
||||||
|
let inp = &[
|
||||||
|
r#"module spam { export def foo [] { "foo" } }"#,
|
||||||
|
r#"const name = 'spam'"#,
|
||||||
|
r#"const new_name = 'eggs'"#,
|
||||||
|
r#"overlay use $name as $new_name --prefix"#,
|
||||||
|
r#"eggs foo"#,
|
||||||
|
];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: "tests/overlays", pipeline(&inp.join("; ")));
|
||||||
|
let actual_repl = nu!(cwd: "tests/overlays", nu_repl_code(inp));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
assert_eq!(actual_repl.out, "foo");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn overlay_add_renamed_from_file() {
|
fn overlay_add_renamed_from_file() {
|
||||||
let inp = &[
|
let inp = &[
|
||||||
|
@ -11,6 +11,17 @@ fn source_file_relative_to_file() {
|
|||||||
assert_eq!(actual.out, "5");
|
assert_eq!(actual.out, "5");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn source_const_file() {
|
||||||
|
let actual = nu!(cwd: "tests/parsing/samples",
|
||||||
|
r#"
|
||||||
|
const file = 'single_line.nu'
|
||||||
|
source $file
|
||||||
|
"#);
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "5");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn run_nu_script_single_line() {
|
fn run_nu_script_single_line() {
|
||||||
let actual = nu!(cwd: "tests/parsing/samples", r#"
|
let actual = nu!(cwd: "tests/parsing/samples", r#"
|
||||||
|
Loading…
Reference in New Issue
Block a user