Allow parsing modules as scripts (#6357)

* Allow parsing modules as scripts

* Remove 'export env' from the supported keywords

* Add test for export in blocks; Allow "export use"

* Allow evaluating "export alias"

* Fmt; Clippy

* Allow running "export extern" in scripts
This commit is contained in:
Jakub Žádník 2022-08-23 00:19:47 +03:00 committed by GitHub
parent 646aace05b
commit 7ef4e5f940
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 231 additions and 95 deletions

View File

@ -274,8 +274,16 @@ pub fn parse_def(
// Checking that the function is used with the correct name // Checking that the function is used with the correct name
// Maybe this is not necessary but it is a sanity check // Maybe this is not necessary but it is a sanity check
// Note: "export def" is treated the same as "def"
let def_call = working_set.get_span_contents(spans[0]).to_vec(); let (name_span, split_id) =
if spans.len() > 1 && working_set.get_span_contents(spans[0]) == b"export" {
(spans[1], 2)
} else {
(spans[0], 1)
};
let def_call = working_set.get_span_contents(name_span).to_vec();
if def_call != b"def" && def_call != b"def-env" { if def_call != b"def" && def_call != b"def-env" {
return ( return (
garbage_pipeline(spans), garbage_pipeline(spans),
@ -301,14 +309,15 @@ pub fn parse_def(
} }
Some(decl_id) => { Some(decl_id) => {
working_set.enter_scope(); working_set.enter_scope();
let (command_spans, rest_spans) = spans.split_at(split_id);
let ParsedInternalCall { let ParsedInternalCall {
call, call,
error: mut err, error: mut err,
output, output,
} = parse_internal_call( } = parse_internal_call(
working_set, working_set,
spans[0], span(command_spans),
&spans[1..], rest_spans,
decl_id, decl_id,
expand_aliases_denylist, expand_aliases_denylist,
); );
@ -381,7 +390,7 @@ pub fn parse_def(
error = error.or_else(|| { error = error.or_else(|| {
Some(ParseError::InternalError( Some(ParseError::InternalError(
"Predeclaration failed to add declaration".into(), "Predeclaration failed to add declaration".into(),
spans[1], name_expr.span,
)) ))
}); });
}; };
@ -415,7 +424,7 @@ pub fn parse_extern(
lite_command: &LiteCommand, lite_command: &LiteCommand,
expand_aliases_denylist: &[usize], expand_aliases_denylist: &[usize],
) -> (Pipeline, Option<ParseError>) { ) -> (Pipeline, Option<ParseError>) {
let spans = &lite_command.parts[..]; let spans = &lite_command.parts;
let mut error = None; let mut error = None;
let usage = build_usage(working_set, &lite_command.comments); let usage = build_usage(working_set, &lite_command.comments);
@ -423,7 +432,14 @@ pub fn parse_extern(
// Checking that the function is used with the correct name // Checking that the function is used with the correct name
// Maybe this is not necessary but it is a sanity check // Maybe this is not necessary but it is a sanity check
let extern_call = working_set.get_span_contents(spans[0]).to_vec(); let (name_span, split_id) =
if spans.len() > 1 && working_set.get_span_contents(spans[0]) == b"export" {
(spans[1], 2)
} else {
(spans[0], 1)
};
let extern_call = working_set.get_span_contents(name_span).to_vec();
if extern_call != b"extern" { if extern_call != b"extern" {
return ( return (
garbage_pipeline(spans), garbage_pipeline(spans),
@ -449,12 +465,15 @@ pub fn parse_extern(
} }
Some(decl_id) => { Some(decl_id) => {
working_set.enter_scope(); working_set.enter_scope();
let (command_spans, rest_spans) = spans.split_at(split_id);
let ParsedInternalCall { let ParsedInternalCall {
call, error: err, .. call, error: err, ..
} = parse_internal_call( } = parse_internal_call(
working_set, working_set,
spans[0], span(command_spans),
&spans[1..], rest_spans,
decl_id, decl_id,
expand_aliases_denylist, expand_aliases_denylist,
); );
@ -491,7 +510,7 @@ pub fn parse_extern(
error = error.or_else(|| { error = error.or_else(|| {
Some(ParseError::InternalError( Some(ParseError::InternalError(
"Predeclaration failed to add declaration".into(), "Predeclaration failed to add declaration".into(),
spans[1], spans[split_id],
)) ))
}); });
}; };
@ -525,7 +544,14 @@ pub fn parse_alias(
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_span, split_id) =
if spans.len() > 1 && working_set.get_span_contents(spans[0]) == b"export" {
(spans[1], 2)
} else {
(spans[0], 1)
};
let name = working_set.get_span_contents(name_span);
if name == b"alias" { if name == b"alias" {
if let Some((span, err)) = check_name(working_set, spans) { if let Some((span, err)) = check_name(working_set, spans) {
@ -533,10 +559,12 @@ pub fn parse_alias(
} }
if let Some(decl_id) = working_set.find_decl(b"alias", &Type::Any) { if let Some(decl_id) = working_set.find_decl(b"alias", &Type::Any) {
let (command_spans, rest_spans) = spans.split_at(split_id);
let ParsedInternalCall { call, output, .. } = parse_internal_call( let ParsedInternalCall { call, output, .. } = parse_internal_call(
working_set, working_set,
spans[0], span(command_spans),
&spans[1..], rest_spans,
decl_id, decl_id,
expand_aliases_denylist, expand_aliases_denylist,
); );
@ -553,8 +581,8 @@ pub fn parse_alias(
); );
} }
if spans.len() >= 4 { if spans.len() >= split_id + 3 {
let alias_name = working_set.get_span_contents(spans[1]); let alias_name = working_set.get_span_contents(spans[split_id]);
let alias_name = if alias_name.starts_with(b"\"") let alias_name = if alias_name.starts_with(b"\"")
&& alias_name.ends_with(b"\"") && alias_name.ends_with(b"\"")
@ -564,9 +592,9 @@ pub fn parse_alias(
} else { } else {
alias_name.to_vec() alias_name.to_vec()
}; };
let _equals = working_set.get_span_contents(spans[2]); let _equals = working_set.get_span_contents(spans[split_id + 1]);
let replacement = spans[3..].to_vec(); let replacement = spans[(split_id + 2)..].to_vec();
working_set.add_alias(alias_name, replacement); working_set.add_alias(alias_name, replacement);
} }
@ -574,7 +602,7 @@ pub fn parse_alias(
let err = if spans.len() < 4 { let err = if spans.len() < 4 {
Some(ParseError::IncorrectValue( Some(ParseError::IncorrectValue(
"Incomplete alias".into(), "Incomplete alias".into(),
spans[0], span(&spans[..split_id]),
"incomplete alias".into(), "incomplete alias".into(),
)) ))
} else { } else {
@ -602,7 +630,107 @@ pub fn parse_alias(
) )
} }
pub fn parse_export( // This one will trigger if `export` appears during eval, e.g., in a script
pub fn parse_export_in_block(
working_set: &mut StateWorkingSet,
lite_command: &LiteCommand,
expand_aliases_denylist: &[usize],
) -> (Pipeline, Option<ParseError>) {
let call_span = span(&lite_command.parts);
let full_name = if lite_command.parts.len() > 1 {
let sub = working_set.get_span_contents(lite_command.parts[1]);
match sub {
b"alias" | b"def" | b"def-env" | b"extern" | b"use" => [b"export ", sub].concat(),
_ => b"export".to_vec(),
}
} else {
b"export".to_vec()
};
if let Some(decl_id) = working_set.find_decl(&full_name, &Type::Any) {
let ParsedInternalCall {
call,
error: mut err,
output,
..
} = parse_internal_call(
working_set,
if full_name == b"export" {
lite_command.parts[0]
} else {
span(&lite_command.parts[0..2])
},
if full_name == b"export" {
&lite_command.parts[1..]
} else {
&lite_command.parts[2..]
},
decl_id,
expand_aliases_denylist,
);
let decl = working_set.get_decl(decl_id);
err = check_call(call_span, &decl.signature(), &call).or(err);
if err.is_some() || call.has_flag("help") {
return (
Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: call_span,
ty: output,
custom_completion: None,
}]),
err,
);
}
} else {
return (
garbage_pipeline(&lite_command.parts),
Some(ParseError::UnknownState(
format!(
"internal error: '{}' declaration not found",
String::from_utf8_lossy(&full_name)
),
span(&lite_command.parts),
)),
);
};
if &full_name == b"export" {
// export by itself is meaningless
return (
garbage_pipeline(&lite_command.parts),
Some(ParseError::UnexpectedKeyword(
"export".into(),
lite_command.parts[0],
)),
);
}
match full_name.as_slice() {
b"export alias" => parse_alias(working_set, &lite_command.parts, expand_aliases_denylist),
b"export def" | b"export def-env" => {
parse_def(working_set, lite_command, expand_aliases_denylist)
}
b"export use" => {
let (pipeline, _, err) =
parse_use(working_set, &lite_command.parts, expand_aliases_denylist);
(pipeline, err)
}
b"export extern" => parse_extern(working_set, lite_command, expand_aliases_denylist),
_ => (
garbage_pipeline(&lite_command.parts),
Some(ParseError::UnexpectedKeyword(
String::from_utf8_lossy(&full_name).to_string(),
lite_command.parts[0],
)),
),
}
}
// This one will trigger only in a module
pub fn parse_export_in_module(
working_set: &mut StateWorkingSet, working_set: &mut StateWorkingSet,
lite_command: &LiteCommand, lite_command: &LiteCommand,
expand_aliases_denylist: &[usize], expand_aliases_denylist: &[usize],
@ -1163,7 +1291,7 @@ pub fn parse_module_block(
// will work only if you call `use foo *; b` but not with `use foo; foo b` // will work only if you call `use foo *; b` but not with `use foo; foo b`
// since in the second case, the name of the env var would be $env."foo a". // since in the second case, the name of the env var would be $env."foo a".
b"export" => { b"export" => {
let (pipe, exportables, err) = parse_export( let (pipe, exportables, err) = parse_export_in_module(
working_set, working_set,
&pipeline.commands[0], &pipeline.commands[0],
expand_aliases_denylist, expand_aliases_denylist,
@ -1309,7 +1437,26 @@ pub fn parse_use(
spans: &[Span], spans: &[Span],
expand_aliases_denylist: &[usize], expand_aliases_denylist: &[usize],
) -> (Pipeline, Vec<Exportable>, Option<ParseError>) { ) -> (Pipeline, Vec<Exportable>, Option<ParseError>) {
if working_set.get_span_contents(spans[0]) != b"use" { let (name_span, split_id) =
if spans.len() > 1 && working_set.get_span_contents(spans[0]) == b"export" {
(spans[1], 2)
} else {
(spans[0], 1)
};
let use_call = working_set.get_span_contents(name_span).to_vec();
if use_call != b"use" {
return (
garbage_pipeline(spans),
vec![],
Some(ParseError::UnknownState(
"internal error: Wrong call name for 'use' command".into(),
span(spans),
)),
);
}
if working_set.get_span_contents(name_span) != b"use" {
return ( return (
garbage_pipeline(spans), garbage_pipeline(spans),
vec![], vec![],
@ -1322,14 +1469,16 @@ pub fn parse_use(
let (call, call_span, use_decl_id) = match working_set.find_decl(b"use", &Type::Any) { let (call, call_span, use_decl_id) = match working_set.find_decl(b"use", &Type::Any) {
Some(decl_id) => { Some(decl_id) => {
let (command_spans, rest_spans) = spans.split_at(split_id);
let ParsedInternalCall { let ParsedInternalCall {
call, call,
error: mut err, error: mut err,
output, output,
} = parse_internal_call( } = parse_internal_call(
working_set, working_set,
spans[0], span(command_spans),
&spans[1..], rest_spans,
decl_id, decl_id,
expand_aliases_denylist, expand_aliases_denylist,
); );
@ -1420,7 +1569,7 @@ pub fn parse_use(
custom_completion: None, custom_completion: None,
}]), }]),
vec![], vec![],
Some(ParseError::ModuleNotFound(spans[1])), Some(ParseError::ModuleNotFound(import_pattern.head.span)),
); );
}; };
@ -1458,7 +1607,7 @@ pub fn parse_use(
head: ImportPatternHead { head: ImportPatternHead {
name: module_name.into(), name: module_name.into(),
id: Some(module_id), id: Some(module_id),
span: spans[1], span: import_pattern.head.span,
}, },
members: import_pattern.members, members: import_pattern.members,
hidden: HashSet::new(), hidden: HashSet::new(),
@ -1474,14 +1623,16 @@ pub fn parse_use(
custom_completion: None, custom_completion: None,
}]), }]),
vec![], vec![],
Some(ParseError::ModuleNotFound(spans[1])), Some(ParseError::ModuleNotFound(import_pattern.head.span)),
); );
} }
} else { } else {
error = error.or(Some(ParseError::ModuleNotFound(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(); let mut import_pattern = ImportPattern::new();
import_pattern.head.span = spans[1]; import_pattern.head.span = old_span;
(import_pattern, Module::new()) (import_pattern, Module::new())
} }
@ -1489,7 +1640,7 @@ pub fn parse_use(
return ( return (
garbage_pipeline(spans), garbage_pipeline(spans),
vec![], vec![],
Some(ParseError::NonUtf8(spans[1])), Some(ParseError::NonUtf8(import_pattern.head.span)),
); );
} }
}; };
@ -1565,7 +1716,7 @@ pub fn parse_use(
}; };
let call = Box::new(Call { let call = Box::new(Call {
head: spans[0], head: span(spans.split_at(split_id).0),
decl_id: use_decl_id, decl_id: use_decl_id,
arguments: vec![Argument::Positional(import_pattern_expr)], arguments: vec![Argument::Positional(import_pattern_expr)],
redirect_stdout: true, redirect_stdout: true,

View File

@ -1,6 +1,7 @@
use crate::{ use crate::{
lex, lite_parse, lex, lite_parse,
lite_parse::LiteCommand, lite_parse::LiteCommand,
parse_export_in_block,
parse_keywords::{parse_extern, parse_for, parse_source}, parse_keywords::{parse_extern, parse_for, parse_source},
type_check::{math_result_type, type_compatible}, type_check::{math_result_type, type_compatible},
LiteBlock, ParseError, Token, TokenContents, LiteBlock, ParseError, Token, TokenContents,
@ -231,30 +232,42 @@ pub fn check_name<'a>(
working_set: &mut StateWorkingSet, working_set: &mut StateWorkingSet,
spans: &'a [Span], spans: &'a [Span],
) -> Option<(&'a Span, ParseError)> { ) -> Option<(&'a Span, ParseError)> {
let command_len = if !spans.is_empty() {
if working_set.get_span_contents(spans[0]) == b"export" {
2
} else {
1
}
} else {
return None;
};
if spans.len() == 1 { if spans.len() == 1 {
None None
} else if spans.len() < 4 { } else if spans.len() < command_len + 3 {
if working_set.get_span_contents(spans[1]) == b"=" { if working_set.get_span_contents(spans[command_len]) == b"=" {
let name = String::from_utf8_lossy(working_set.get_span_contents(spans[0])); let name =
String::from_utf8_lossy(working_set.get_span_contents(span(&spans[..command_len])));
Some(( Some((
&spans[1], &spans[command_len],
ParseError::AssignmentMismatch( ParseError::AssignmentMismatch(
format!("{} missing name", name), format!("{} missing name", name),
"missing name".into(), "missing name".into(),
spans[1], spans[command_len],
), ),
)) ))
} else { } else {
None None
} }
} else if working_set.get_span_contents(spans[2]) != b"=" { } else if working_set.get_span_contents(spans[command_len + 1]) != b"=" {
let name = String::from_utf8_lossy(working_set.get_span_contents(spans[0])); let name =
String::from_utf8_lossy(working_set.get_span_contents(span(&spans[..command_len])));
Some(( Some((
&spans[2], &spans[command_len + 1],
ParseError::AssignmentMismatch( ParseError::AssignmentMismatch(
format!("{} missing sign", name), format!("{} missing sign", name),
"missing equal sign".into(), "missing equal sign".into(),
spans[2], spans[command_len + 1],
), ),
)) ))
} else { } else {
@ -4793,64 +4806,7 @@ pub fn parse_builtin_commands(
} }
b"overlay" => parse_overlay(working_set, &lite_command.parts, expand_aliases_denylist), b"overlay" => parse_overlay(working_set, &lite_command.parts, expand_aliases_denylist),
b"source" => parse_source(working_set, &lite_command.parts, expand_aliases_denylist), b"source" => parse_source(working_set, &lite_command.parts, expand_aliases_denylist),
b"export" => { b"export" => parse_export_in_block(working_set, lite_command, expand_aliases_denylist),
let full_decl = if lite_command.parts.len() > 1 {
let sub = working_set.get_span_contents(lite_command.parts[1]);
match sub {
b"alias" | b"def" | b"def-env" | b"env" | b"extern" | b"use" => {
[b"export ", sub].concat()
}
_ => b"export".to_vec(),
}
} else {
b"export".to_vec()
};
if let Some(decl_id) = working_set.find_decl(&full_decl, &Type::Any) {
let parsed_call = parse_internal_call(
working_set,
if full_decl == b"export" {
lite_command.parts[0]
} else {
span(&lite_command.parts[0..2])
},
if full_decl == b"export" {
&lite_command.parts[1..]
} else {
&lite_command.parts[2..]
},
decl_id,
expand_aliases_denylist,
);
if parsed_call.call.has_flag("help") {
(
Pipeline::from_vec(vec![Expression {
expr: Expr::Call(parsed_call.call),
span: span(&lite_command.parts),
ty: parsed_call.output,
custom_completion: None,
}]),
None,
)
} else {
(
garbage_pipeline(&lite_command.parts),
Some(ParseError::UnexpectedKeyword(
"export".into(),
lite_command.parts[0],
)),
)
}
} else {
(
garbage_pipeline(&lite_command.parts),
Some(ParseError::UnexpectedKeyword(
"export".into(),
lite_command.parts[0],
)),
)
}
}
b"hide" => parse_hide(working_set, &lite_command.parts, expand_aliases_denylist), b"hide" => parse_hide(working_set, &lite_command.parts, expand_aliases_denylist),
#[cfg(feature = "plugin")] #[cfg(feature = "plugin")]
b"register" => parse_register(working_set, &lite_command.parts, expand_aliases_denylist), b"register" => parse_register(working_set, &lite_command.parts, expand_aliases_denylist),

View File

@ -170,3 +170,32 @@ fn nu_lib_dirs_relative_script() {
assert_eq!(actual_repl.out, "foo"); assert_eq!(actual_repl.out, "foo");
}) })
} }
#[test]
fn run_script_that_looks_like_module() {
Playground::setup("run_script_that_looks_like_module", |dirs, _| {
let inp_lines = &[
r#"module spam { export def eggs [] { 'eggs' } }"#,
r#"export use spam eggs"#,
r#"export def foo [] { eggs }"#,
r#"export alias bar = foo"#,
r#"export def-env baz [] { bar }"#,
r#"baz"#,
];
let actual = nu!(cwd: dirs.test(), inp_lines.join("; "));
assert_eq!(actual.out, "eggs");
})
}
#[test]
fn run_export_extern() {
Playground::setup("run_script_that_looks_like_module", |dirs, _| {
let inp_lines = &[r#"export extern foo []"#, r#"help foo"#];
let actual = nu!(cwd: dirs.test(), inp_lines.join("; "));
assert!(actual.out.contains("Usage"));
})
}