mirror of
https://github.com/nushell/nushell
synced 2024-12-26 13:03:07 +00:00
Initial support for parse-time constants (#7436)
This commit is contained in:
parent
fa8629300f
commit
3a2c7900d6
18 changed files with 788 additions and 242 deletions
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;
|
||||
|
||||
//println!("Adding: {:?} to {}", rhs, var_id);
|
||||
|
||||
stack.add_var(var_id, rhs.into_value(call.head));
|
||||
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ mod alias;
|
|||
mod ast;
|
||||
mod break_;
|
||||
mod commandline;
|
||||
mod const_;
|
||||
mod continue_;
|
||||
mod debug;
|
||||
mod def;
|
||||
|
@ -40,6 +41,7 @@ pub use alias::Alias;
|
|||
pub use ast::Ast;
|
||||
pub use break_::Break;
|
||||
pub use commandline::Commandline;
|
||||
pub use const_::Const;
|
||||
pub use continue_::Continue;
|
||||
pub use debug::Debug;
|
||||
pub use def::Def;
|
||||
|
|
|
@ -84,25 +84,8 @@ impl Command for OverlayUse {
|
|||
));
|
||||
};
|
||||
|
||||
let overlay_name = if let Some(kw_expression) = call.positional_nth(1) {
|
||||
// If renamed via the 'as' keyword, use the new name as the overlay 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,
|
||||
));
|
||||
}
|
||||
let overlay_name = if let Some(name) = call.opt(engine_state, caller_stack, 1)? {
|
||||
name
|
||||
} else if engine_state
|
||||
.find_overlay(name_arg.item.as_bytes())
|
||||
.is_some()
|
||||
|
|
|
@ -32,6 +32,7 @@ pub fn create_default_context() -> EngineState {
|
|||
Ast,
|
||||
Break,
|
||||
Commandline,
|
||||
Const,
|
||||
Continue,
|
||||
Debug,
|
||||
Def,
|
||||
|
|
|
@ -293,3 +293,25 @@ fn source_env_is_scoped() {
|
|||
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))]
|
||||
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}")]
|
||||
#[diagnostic()]
|
||||
LabeledError(String, String, #[label("{1}")] Span),
|
||||
|
@ -450,6 +463,7 @@ impl ParseError {
|
|||
ParseError::ShellErrRedirect(s) => *s,
|
||||
ParseError::ShellOutErrRedirect(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 errors;
|
||||
mod eval;
|
||||
mod flatten;
|
||||
mod known_external;
|
||||
mod lex;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::eval::{eval_constant, value_as_string};
|
||||
use log::trace;
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::{
|
||||
|
@ -20,8 +21,8 @@ use crate::{
|
|||
lex,
|
||||
parser::{
|
||||
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,
|
||||
LiteCommand, LiteElement, ParsedInternalCall,
|
||||
parse_multispan_value, parse_signature, parse_string, parse_value, parse_var_with_opt_type,
|
||||
trim_quotes, LiteCommand, LiteElement, ParsedInternalCall,
|
||||
},
|
||||
unescape_unquote_string, ParseError,
|
||||
};
|
||||
|
@ -1636,144 +1637,143 @@ pub fn parse_use(
|
|||
|
||||
// TODO: Add checking for importing too long import patterns, e.g.:
|
||||
// > use spam foo non existent names here do not throw error
|
||||
let (import_pattern, module) =
|
||||
if let Some(module_id) = working_set.find_module(&import_pattern.head.name) {
|
||||
(import_pattern, working_set.get_module(module_id).clone())
|
||||
} else {
|
||||
// It could be a file
|
||||
// TODO: Do not close over when loading module from file?
|
||||
let (import_pattern, module) = if let Some(module_id) = import_pattern.head.id {
|
||||
(import_pattern, working_set.get_module(module_id).clone())
|
||||
} else {
|
||||
// It could be a file
|
||||
// TODO: Do not close over when loading module from file?
|
||||
|
||||
let (module_filename, err) =
|
||||
unescape_unquote_string(&import_pattern.head.name, import_pattern.head.span);
|
||||
let (module_filename, err) =
|
||||
unescape_unquote_string(&import_pattern.head.name, import_pattern.head.span);
|
||||
|
||||
if err.is_none() {
|
||||
if let Some(module_path) =
|
||||
find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_ENV)
|
||||
if err.is_none() {
|
||||
if let Some(module_path) =
|
||||
find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_ENV)
|
||||
{
|
||||
if let Some(i) = working_set
|
||||
.parsed_module_files
|
||||
.iter()
|
||||
.rposition(|p| p == &module_path)
|
||||
{
|
||||
if let Some(i) = working_set
|
||||
let mut files: Vec<String> = working_set
|
||||
.parsed_module_files
|
||||
.split_off(i)
|
||||
.iter()
|
||||
.rposition(|p| p == &module_path)
|
||||
{
|
||||
let mut files: Vec<String> = working_set
|
||||
.parsed_module_files
|
||||
.split_off(i)
|
||||
.iter()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.collect();
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.collect();
|
||||
|
||||
files.push(module_path.to_string_lossy().to_string());
|
||||
files.push(module_path.to_string_lossy().to_string());
|
||||
|
||||
let msg = files.join("\nuses ");
|
||||
let msg = files.join("\nuses ");
|
||||
|
||||
return (
|
||||
Pipeline::from_vec(vec![Expression {
|
||||
expr: Expr::Call(call),
|
||||
span: call_span,
|
||||
ty: Type::Any,
|
||||
custom_completion: None,
|
||||
}]),
|
||||
vec![],
|
||||
Some(ParseError::CyclicalModuleImport(
|
||||
msg,
|
||||
import_pattern.head.span,
|
||||
)),
|
||||
);
|
||||
}
|
||||
return (
|
||||
Pipeline::from_vec(vec![Expression {
|
||||
expr: Expr::Call(call),
|
||||
span: call_span,
|
||||
ty: Type::Any,
|
||||
custom_completion: None,
|
||||
}]),
|
||||
vec![],
|
||||
Some(ParseError::CyclicalModuleImport(
|
||||
msg,
|
||||
import_pattern.head.span,
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
let module_name = if let Some(stem) = module_path.file_stem() {
|
||||
stem.to_string_lossy().to_string()
|
||||
let module_name = if let Some(stem) = module_path.file_stem() {
|
||||
stem.to_string_lossy().to_string()
|
||||
} else {
|
||||
return (
|
||||
Pipeline::from_vec(vec![Expression {
|
||||
expr: Expr::Call(call),
|
||||
span: call_span,
|
||||
ty: Type::Any,
|
||||
custom_completion: None,
|
||||
}]),
|
||||
vec![],
|
||||
Some(ParseError::ModuleNotFound(import_pattern.head.span)),
|
||||
);
|
||||
};
|
||||
|
||||
if let Ok(contents) = std::fs::read(&module_path) {
|
||||
let span_start = working_set.next_span_start();
|
||||
working_set.add_file(module_filename, &contents);
|
||||
let span_end = working_set.next_span_start();
|
||||
|
||||
// Change the currently parsed directory
|
||||
let prev_currently_parsed_cwd = if let Some(parent) = module_path.parent() {
|
||||
let prev = working_set.currently_parsed_cwd.clone();
|
||||
|
||||
working_set.currently_parsed_cwd = Some(parent.into());
|
||||
|
||||
prev
|
||||
} else {
|
||||
return (
|
||||
Pipeline::from_vec(vec![Expression {
|
||||
expr: Expr::Call(call),
|
||||
span: call_span,
|
||||
ty: Type::Any,
|
||||
custom_completion: None,
|
||||
}]),
|
||||
vec![],
|
||||
Some(ParseError::ModuleNotFound(import_pattern.head.span)),
|
||||
);
|
||||
working_set.currently_parsed_cwd.clone()
|
||||
};
|
||||
|
||||
if let Ok(contents) = std::fs::read(&module_path) {
|
||||
let span_start = working_set.next_span_start();
|
||||
working_set.add_file(module_filename, &contents);
|
||||
let span_end = working_set.next_span_start();
|
||||
// Add the file to the stack of parsed module files
|
||||
working_set.parsed_module_files.push(module_path);
|
||||
|
||||
// Change the currently parsed directory
|
||||
let prev_currently_parsed_cwd = if let Some(parent) = module_path.parent() {
|
||||
let prev = working_set.currently_parsed_cwd.clone();
|
||||
// Parse the module
|
||||
let (block, module, err) = parse_module_block(
|
||||
working_set,
|
||||
Span::new(span_start, span_end),
|
||||
expand_aliases_denylist,
|
||||
);
|
||||
error = error.or(err);
|
||||
|
||||
working_set.currently_parsed_cwd = Some(parent.into());
|
||||
// Remove the file from the stack of parsed module files
|
||||
working_set.parsed_module_files.pop();
|
||||
|
||||
prev
|
||||
} else {
|
||||
working_set.currently_parsed_cwd.clone()
|
||||
};
|
||||
// Restore the currently parsed directory back
|
||||
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
||||
|
||||
// Add the file to the stack of parsed module files
|
||||
working_set.parsed_module_files.push(module_path);
|
||||
let _ = working_set.add_block(block);
|
||||
let module_id = working_set.add_module(&module_name, module.clone());
|
||||
|
||||
// Parse the module
|
||||
let (block, module, err) = parse_module_block(
|
||||
working_set,
|
||||
Span::new(span_start, span_end),
|
||||
expand_aliases_denylist,
|
||||
);
|
||||
error = error.or(err);
|
||||
|
||||
// Remove the file from the stack of parsed module files
|
||||
working_set.parsed_module_files.pop();
|
||||
|
||||
// Restore the currently parsed directory back
|
||||
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
||||
|
||||
let _ = working_set.add_block(block);
|
||||
let module_id = working_set.add_module(&module_name, module.clone());
|
||||
|
||||
(
|
||||
ImportPattern {
|
||||
head: ImportPatternHead {
|
||||
name: module_name.into(),
|
||||
id: Some(module_id),
|
||||
span: import_pattern.head.span,
|
||||
},
|
||||
members: import_pattern.members,
|
||||
hidden: HashSet::new(),
|
||||
(
|
||||
ImportPattern {
|
||||
head: ImportPatternHead {
|
||||
name: module_name.into(),
|
||||
id: Some(module_id),
|
||||
span: import_pattern.head.span,
|
||||
},
|
||||
module,
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
Pipeline::from_vec(vec![Expression {
|
||||
expr: Expr::Call(call),
|
||||
span: call_span,
|
||||
ty: Type::Any,
|
||||
custom_completion: None,
|
||||
}]),
|
||||
vec![],
|
||||
Some(ParseError::ModuleNotFound(import_pattern.head.span)),
|
||||
);
|
||||
}
|
||||
members: import_pattern.members,
|
||||
hidden: HashSet::new(),
|
||||
},
|
||||
module,
|
||||
)
|
||||
} else {
|
||||
error = error.or(Some(ParseError::ModuleNotFound(import_pattern.head.span)));
|
||||
|
||||
let old_span = import_pattern.head.span;
|
||||
|
||||
let mut import_pattern = ImportPattern::new();
|
||||
import_pattern.head.span = old_span;
|
||||
|
||||
(import_pattern, Module::new())
|
||||
return (
|
||||
Pipeline::from_vec(vec![Expression {
|
||||
expr: Expr::Call(call),
|
||||
span: call_span,
|
||||
ty: Type::Any,
|
||||
custom_completion: None,
|
||||
}]),
|
||||
vec![],
|
||||
Some(ParseError::ModuleNotFound(import_pattern.head.span)),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
garbage_pipeline(spans),
|
||||
vec![],
|
||||
Some(ParseError::NonUtf8(import_pattern.head.span)),
|
||||
);
|
||||
error = error.or(Some(ParseError::ModuleNotFound(import_pattern.head.span)));
|
||||
|
||||
let old_span = import_pattern.head.span;
|
||||
|
||||
let mut import_pattern = ImportPattern::new();
|
||||
import_pattern.head.span = old_span;
|
||||
|
||||
(import_pattern, Module::new())
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return (
|
||||
garbage_pipeline(spans),
|
||||
vec![],
|
||||
Some(ParseError::NonUtf8(import_pattern.head.span)),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let (decls_to_use, aliases_to_use) = if import_pattern.members.is_empty() {
|
||||
(
|
||||
|
@ -2386,16 +2386,16 @@ pub fn parse_overlay_use(
|
|||
};
|
||||
|
||||
let (overlay_name, overlay_name_span) = if let Some(expr) = call.positional_nth(0) {
|
||||
if let Some(s) = expr.as_string() {
|
||||
(s, expr.span)
|
||||
} else {
|
||||
return (
|
||||
garbage_pipeline(spans),
|
||||
Some(ParseError::UnknownState(
|
||||
"internal error: Module name not a string".into(),
|
||||
expr.span,
|
||||
)),
|
||||
);
|
||||
match eval_constant(working_set, expr) {
|
||||
Ok(val) => match value_as_string(val, expr.span) {
|
||||
Ok(s) => (s, expr.span),
|
||||
Err(err) => {
|
||||
return (garbage_pipeline(spans), Some(err));
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
return (garbage_pipeline(spans), Some(err));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
|
@ -2409,20 +2409,15 @@ pub fn parse_overlay_use(
|
|||
|
||||
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) = new_name_expression.as_string() {
|
||||
Some(Spanned {
|
||||
item: new_name,
|
||||
span: new_name_expression.span,
|
||||
})
|
||||
} else {
|
||||
return (
|
||||
garbage_pipeline(spans),
|
||||
Some(ParseError::TypeMismatch(
|
||||
Type::String,
|
||||
new_name_expression.ty.clone(),
|
||||
new_name_expression.span,
|
||||
)),
|
||||
);
|
||||
match eval_constant(working_set, new_name_expression) {
|
||||
Ok(val) => match value_as_string(val, new_name_expression.span) {
|
||||
Ok(s) => Some(Spanned {
|
||||
item: s,
|
||||
span: new_name_expression.span,
|
||||
}),
|
||||
Err(err) => return (garbage_pipeline(spans), Some(err)),
|
||||
},
|
||||
Err(err) => return (garbage_pipeline(spans), Some(err)),
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
|
@ -2751,19 +2746,23 @@ pub fn parse_overlay_hide(
|
|||
(pipeline, None)
|
||||
}
|
||||
|
||||
pub fn parse_let(
|
||||
pub fn parse_let_or_const(
|
||||
working_set: &mut StateWorkingSet,
|
||||
spans: &[Span],
|
||||
expand_aliases_denylist: &[usize],
|
||||
) -> (Pipeline, Option<ParseError>) {
|
||||
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) {
|
||||
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 call_signature = cmd.signature().call_signature();
|
||||
|
||||
|
@ -2815,6 +2814,15 @@ pub fn parse_let(
|
|||
|
||||
if let Some(var_id) = var_id {
|
||||
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 {
|
||||
|
@ -2866,7 +2874,7 @@ pub fn parse_let(
|
|||
(
|
||||
garbage_pipeline(spans),
|
||||
Some(ParseError::UnknownState(
|
||||
"internal error: let statement unparseable".into(),
|
||||
"internal error: let or const statement unparseable".into(),
|
||||
span(spans),
|
||||
)),
|
||||
)
|
||||
|
@ -3036,79 +3044,111 @@ pub fn parse_source(
|
|||
|
||||
// Command and one file name
|
||||
if spans.len() >= 2 {
|
||||
let name_expr = working_set.get_span_contents(spans[1]);
|
||||
let (filename, err) = unescape_unquote_string(name_expr, spans[1]);
|
||||
let (expr, err) = parse_value(
|
||||
working_set,
|
||||
spans[1],
|
||||
&SyntaxShape::Any,
|
||||
expand_aliases_denylist,
|
||||
);
|
||||
|
||||
if err.is_none() {
|
||||
if let Some(path) = find_in_dirs(&filename, working_set, &cwd, LIB_DIRS_ENV) {
|
||||
if let Ok(contents) = std::fs::read(&path) {
|
||||
// Change currently parsed directory
|
||||
let prev_currently_parsed_cwd = if let Some(parent) = path.parent() {
|
||||
let prev = working_set.currently_parsed_cwd.clone();
|
||||
error = error.or(err);
|
||||
|
||||
working_set.currently_parsed_cwd = Some(parent.into());
|
||||
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),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
prev
|
||||
} else {
|
||||
working_set.currently_parsed_cwd.clone()
|
||||
};
|
||||
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),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// This will load the defs from the file into the
|
||||
// working set, if it was a successful parse.
|
||||
let (block, err) = parse(
|
||||
working_set,
|
||||
path.file_name().and_then(|x| x.to_str()),
|
||||
&contents,
|
||||
scoped,
|
||||
expand_aliases_denylist,
|
||||
);
|
||||
if let Some(path) = find_in_dirs(&filename, working_set, &cwd, LIB_DIRS_ENV) {
|
||||
if let Ok(contents) = std::fs::read(&path) {
|
||||
// Change currently parsed directory
|
||||
let prev_currently_parsed_cwd = if let Some(parent) = path.parent() {
|
||||
let prev = working_set.currently_parsed_cwd.clone();
|
||||
|
||||
// Restore the currently parsed directory back
|
||||
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
||||
working_set.currently_parsed_cwd = Some(parent.into());
|
||||
|
||||
if err.is_some() {
|
||||
// Unsuccessful parse of file
|
||||
return (
|
||||
Pipeline::from_vec(vec![Expression {
|
||||
expr: Expr::Call(call),
|
||||
span: span(&spans[1..]),
|
||||
ty: Type::Any,
|
||||
custom_completion: None,
|
||||
}]),
|
||||
// Return the file parse error
|
||||
err,
|
||||
);
|
||||
} else {
|
||||
// Save the block into the working set
|
||||
let block_id = working_set.add_block(block);
|
||||
prev
|
||||
} else {
|
||||
working_set.currently_parsed_cwd.clone()
|
||||
};
|
||||
|
||||
let mut call_with_block = call;
|
||||
// This will load the defs from the file into the
|
||||
// working set, if it was a successful parse.
|
||||
let (block, err) = parse(
|
||||
working_set,
|
||||
path.file_name().and_then(|x| x.to_str()),
|
||||
&contents,
|
||||
scoped,
|
||||
expand_aliases_denylist,
|
||||
);
|
||||
|
||||
// FIXME: Adding this expression to the positional creates a syntax highlighting error
|
||||
// after writing `source example.nu`
|
||||
call_with_block.add_positional(Expression {
|
||||
expr: Expr::Int(block_id as i64),
|
||||
span: spans[1],
|
||||
// Restore the currently parsed directory back
|
||||
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
||||
|
||||
if err.is_some() {
|
||||
// Unsuccessful parse of file
|
||||
return (
|
||||
Pipeline::from_vec(vec![Expression {
|
||||
expr: Expr::Call(call),
|
||||
span: span(&spans[1..]),
|
||||
ty: Type::Any,
|
||||
custom_completion: None,
|
||||
});
|
||||
}]),
|
||||
// Return the file parse error
|
||||
err,
|
||||
);
|
||||
} else {
|
||||
// Save the block into the working set
|
||||
let block_id = working_set.add_block(block);
|
||||
|
||||
return (
|
||||
Pipeline::from_vec(vec![Expression {
|
||||
expr: Expr::Call(call_with_block),
|
||||
span: span(spans),
|
||||
ty: Type::Any,
|
||||
custom_completion: None,
|
||||
}]),
|
||||
None,
|
||||
);
|
||||
}
|
||||
let mut call_with_block = call;
|
||||
|
||||
// FIXME: Adding this expression to the positional creates a syntax highlighting error
|
||||
// after writing `source example.nu`
|
||||
call_with_block.add_positional(Expression {
|
||||
expr: Expr::Int(block_id as i64),
|
||||
span: spans[1],
|
||||
ty: Type::Any,
|
||||
custom_completion: None,
|
||||
});
|
||||
|
||||
return (
|
||||
Pipeline::from_vec(vec![Expression {
|
||||
expr: Expr::Call(call_with_block),
|
||||
span: span(spans),
|
||||
ty: Type::Any,
|
||||
custom_completion: None,
|
||||
}]),
|
||||
None,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
error = error.or(Some(ParseError::SourcedFileNotFound(filename, spans[1])));
|
||||
}
|
||||
} else {
|
||||
return (garbage_pipeline(spans), Some(ParseError::NonUtf8(spans[1])));
|
||||
error = error.or(Some(ParseError::SourcedFileNotFound(filename, spans[1])));
|
||||
}
|
||||
}
|
||||
return (
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::{
|
||||
eval::{eval_constant, value_as_string},
|
||||
lex, parse_mut,
|
||||
type_check::{math_result_type, type_compatible},
|
||||
ParseError, Token, TokenContents,
|
||||
|
@ -17,8 +18,8 @@ use nu_protocol::{
|
|||
|
||||
use crate::parse_keywords::{
|
||||
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_where_expr,
|
||||
parse_hide, parse_let_or_const, parse_module, parse_overlay, parse_source, parse_use,
|
||||
parse_where, parse_where_expr,
|
||||
};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
@ -2832,11 +2833,8 @@ pub fn parse_import_pattern(
|
|||
) -> (Expression, Option<ParseError>) {
|
||||
let mut error = None;
|
||||
|
||||
let (head, head_span) = if let Some(head_span) = spans.get(0) {
|
||||
(
|
||||
working_set.get_span_contents(*head_span).to_vec(),
|
||||
head_span,
|
||||
)
|
||||
let head_span = if let Some(head_span) = spans.get(0) {
|
||||
head_span
|
||||
} else {
|
||||
return (
|
||||
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) {
|
||||
// FIXME: expand this to handle deeper imports once we support module imports
|
||||
|
@ -2853,7 +2869,7 @@ pub fn parse_import_pattern(
|
|||
(
|
||||
ImportPattern {
|
||||
head: ImportPatternHead {
|
||||
name: head,
|
||||
name: head_name,
|
||||
id: maybe_module_id,
|
||||
span: *head_span,
|
||||
},
|
||||
|
@ -2886,7 +2902,7 @@ pub fn parse_import_pattern(
|
|||
(
|
||||
ImportPattern {
|
||||
head: ImportPatternHead {
|
||||
name: head,
|
||||
name: head_name,
|
||||
id: maybe_module_id,
|
||||
span: *head_span,
|
||||
},
|
||||
|
@ -2899,7 +2915,7 @@ pub fn parse_import_pattern(
|
|||
_ => (
|
||||
ImportPattern {
|
||||
head: ImportPatternHead {
|
||||
name: head,
|
||||
name: head_name,
|
||||
id: maybe_module_id,
|
||||
span: *head_span,
|
||||
},
|
||||
|
@ -2914,7 +2930,7 @@ pub fn parse_import_pattern(
|
|||
(
|
||||
ImportPattern {
|
||||
head: ImportPatternHead {
|
||||
name: head,
|
||||
name: head_name,
|
||||
id: maybe_module_id,
|
||||
span: *head_span,
|
||||
},
|
||||
|
@ -2931,7 +2947,7 @@ pub fn parse_import_pattern(
|
|||
(
|
||||
ImportPattern {
|
||||
head: ImportPatternHead {
|
||||
name: head,
|
||||
name: head_name,
|
||||
id: maybe_module_id,
|
||||
span: *head_span,
|
||||
},
|
||||
|
@ -4898,7 +4914,7 @@ pub fn parse_expression(
|
|||
.0,
|
||||
Some(ParseError::BuiltinCommandInPipeline("for".into(), spans[0])),
|
||||
),
|
||||
b"let" => (
|
||||
b"let" | b"const" => (
|
||||
parse_call(
|
||||
working_set,
|
||||
&spans[pos..],
|
||||
|
@ -5166,7 +5182,9 @@ pub fn parse_builtin_commands(
|
|||
match name {
|
||||
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"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"for" => {
|
||||
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() {
|
||||
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() {
|
||||
existing_overlay.aliases.insert(item.0, item.1);
|
||||
}
|
||||
|
@ -626,6 +629,16 @@ impl EngineState {
|
|||
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] {
|
||||
for (contents, start, finish) in &self.file_contents {
|
||||
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 {
|
||||
let num_permanent_vars = self.permanent_state.num_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::collections::HashMap;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
@ -199,6 +199,7 @@ impl ScopeFrame {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct OverlayFrame {
|
||||
pub vars: HashMap<Vec<u8>, VarId>,
|
||||
pub constants: HashMap<VarId, Value>,
|
||||
pub predecls: HashMap<Vec<u8>, DeclId>, // temporary storage for predeclarations
|
||||
pub decls: HashMap<(Vec<u8>, Type), DeclId>,
|
||||
pub aliases: HashMap<Vec<u8>, AliasId>,
|
||||
|
@ -212,6 +213,7 @@ impl OverlayFrame {
|
|||
pub fn from_origin(origin: ModuleId, prefixed: bool) -> Self {
|
||||
Self {
|
||||
vars: HashMap::new(),
|
||||
constants: HashMap::new(),
|
||||
predecls: HashMap::new(),
|
||||
decls: 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;
|
||||
|
||||
mod const_;
|
||||
mod hooks;
|
||||
mod modules;
|
||||
mod overlays;
|
||||
|
|
|
@ -433,3 +433,44 @@ fn module_cyclical_imports_3() {
|
|||
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");
|
||||
}
|
||||
|
||||
#[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
|
||||
#[test]
|
||||
fn add_overlay_from_file_decl_cd() {
|
||||
|
@ -693,6 +720,23 @@ fn overlay_add_renamed() {
|
|||
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]
|
||||
fn overlay_add_renamed_from_file() {
|
||||
let inp = &[
|
||||
|
|
|
@ -11,6 +11,17 @@ fn source_file_relative_to_file() {
|
|||
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]
|
||||
fn run_nu_script_single_line() {
|
||||
let actual = nu!(cwd: "tests/parsing/samples", r#"
|
||||
|
|
Loading…
Reference in a new issue