mirror of
https://github.com/fish-shell/fish-shell
synced 2024-11-10 15:14:44 +00:00
Port test_new_parser_ll2
This commit is contained in:
parent
af4b8ccc91
commit
d5cfa0e346
3 changed files with 126 additions and 123 deletions
|
@ -110,7 +110,7 @@ mod parse_constants_ffi {
|
|||
}
|
||||
|
||||
// Statement decorations like 'command' or 'exec'.
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum StatementDecoration {
|
||||
none,
|
||||
command,
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use crate::ast::{Ast, List, Node};
|
||||
use crate::ast::{self, Ast, List, Node, Traversal};
|
||||
use crate::builtins::shared::{STATUS_CMD_OK, STATUS_UNMATCHED_WILDCARD};
|
||||
use crate::expand::ExpandFlags;
|
||||
use crate::io::{IoBufferfill, IoChain};
|
||||
use crate::parse_constants::StatementDecoration;
|
||||
use crate::parse_constants::{ParseTreeFlags, ParserTestErrorBits};
|
||||
use crate::parse_util::{parse_util_detect_errors, parse_util_detect_errors_in_argument};
|
||||
use crate::parser::Parser;
|
||||
|
@ -10,6 +11,7 @@ use crate::signal::{signal_clear_cancel, signal_reset_handlers, signal_set_handl
|
|||
use crate::tests::prelude::*;
|
||||
use crate::threads::{iothread_drain_all, iothread_perform};
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::wcstringutil::join_strings;
|
||||
use libc::SIGINT;
|
||||
use std::time::Duration;
|
||||
|
||||
|
@ -375,6 +377,128 @@ add_test!("test_new_parser_correctness", || {
|
|||
}
|
||||
});
|
||||
|
||||
// Test the LL2 (two token lookahead) nature of the parser by exercising the special builtin and
|
||||
// command handling. In particular, 'command foo' should be a decorated statement 'foo' but 'command
|
||||
// -help' should be an undecorated statement 'command' with argument '--help', and NOT attempt to
|
||||
// run a command called '--help'.
|
||||
add_test!("test_new_parser_ll2", || {
|
||||
// Parse a statement, returning the command, args (joined by spaces), and the decoration. Returns
|
||||
// true if successful.
|
||||
fn test_1_parse_ll2(src: &wstr) -> Option<(WString, WString, StatementDecoration)> {
|
||||
let ast = Ast::parse(src, ParseTreeFlags::default(), None);
|
||||
if ast.errored() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Get the statement. Should only have one.
|
||||
let mut statement = None;
|
||||
for n in Traversal::new(ast.top()) {
|
||||
if let Some(tmp) = n.as_decorated_statement() {
|
||||
assert!(
|
||||
statement.is_none(),
|
||||
"More than one decorated statement found in '{}'",
|
||||
src
|
||||
);
|
||||
statement = Some(tmp);
|
||||
}
|
||||
}
|
||||
let statement = statement.expect("No decorated statement found");
|
||||
|
||||
// Return its decoration and command.
|
||||
let out_deco = statement.decoration();
|
||||
let out_cmd = statement.command.source(src).to_owned();
|
||||
|
||||
// Return arguments separated by spaces.
|
||||
let out_joined_args = join_strings(
|
||||
&statement
|
||||
.args_or_redirs
|
||||
.iter()
|
||||
.filter(|a| a.is_argument())
|
||||
.map(|a| a.source(src))
|
||||
.collect::<Vec<_>>(),
|
||||
' ',
|
||||
);
|
||||
|
||||
Some((out_cmd, out_joined_args, out_deco))
|
||||
}
|
||||
macro_rules! validate {
|
||||
($src:expr, $cmd:expr, $args:expr, $deco:expr) => {
|
||||
let (cmd, args, deco) = test_1_parse_ll2(L!($src)).unwrap();
|
||||
assert_eq!(cmd, L!($cmd));
|
||||
assert_eq!(args, L!($args));
|
||||
assert_eq!(deco, $deco);
|
||||
};
|
||||
}
|
||||
|
||||
validate!("echo hello", "echo", "hello", StatementDecoration::none);
|
||||
validate!(
|
||||
"command echo hello",
|
||||
"echo",
|
||||
"hello",
|
||||
StatementDecoration::command
|
||||
);
|
||||
validate!(
|
||||
"exec echo hello",
|
||||
"echo",
|
||||
"hello",
|
||||
StatementDecoration::exec
|
||||
);
|
||||
validate!(
|
||||
"command command hello",
|
||||
"command",
|
||||
"hello",
|
||||
StatementDecoration::command
|
||||
);
|
||||
validate!(
|
||||
"builtin command hello",
|
||||
"command",
|
||||
"hello",
|
||||
StatementDecoration::builtin
|
||||
);
|
||||
validate!(
|
||||
"command --help",
|
||||
"command",
|
||||
"--help",
|
||||
StatementDecoration::none
|
||||
);
|
||||
validate!("command -h", "command", "-h", StatementDecoration::none);
|
||||
validate!("command", "command", "", StatementDecoration::none);
|
||||
validate!("command -", "command", "-", StatementDecoration::none);
|
||||
validate!("command --", "command", "--", StatementDecoration::none);
|
||||
validate!(
|
||||
"builtin --names",
|
||||
"builtin",
|
||||
"--names",
|
||||
StatementDecoration::none
|
||||
);
|
||||
validate!("function", "function", "", StatementDecoration::none);
|
||||
validate!(
|
||||
"function --help",
|
||||
"function",
|
||||
"--help",
|
||||
StatementDecoration::none
|
||||
);
|
||||
|
||||
// Verify that 'function -h' and 'function --help' are plain statements but 'function --foo' is
|
||||
// not (issue #1240).
|
||||
macro_rules! check_function_help {
|
||||
($src:expr, $typ:expr) => {
|
||||
let ast = Ast::parse(L!($src), ParseTreeFlags::default(), None);
|
||||
assert!(!ast.errored());
|
||||
assert_eq!(
|
||||
Traversal::new(ast.top())
|
||||
.filter(|n| n.typ() == $typ)
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
};
|
||||
}
|
||||
check_function_help!("function -h", ast::Type::decorated_statement);
|
||||
check_function_help!("function --help", ast::Type::decorated_statement);
|
||||
check_function_help!("function --foo; end", ast::Type::function_header);
|
||||
check_function_help!("function foo; end", ast::Type::function_header);
|
||||
});
|
||||
|
||||
add_test!("test_eval_recursion_detection", || {
|
||||
// Ensure that we don't crash on infinite self recursion and mutual recursion. These must use
|
||||
// the principal parser because we cannot yet execute jobs on other parsers.
|
||||
|
|
|
@ -1077,126 +1077,6 @@ static void test_input() {
|
|||
}
|
||||
}
|
||||
|
||||
// todo!("port this")
|
||||
// Parse a statement, returning the command, args (joined by spaces), and the decoration. Returns
|
||||
// true if successful.
|
||||
static bool test_1_parse_ll2(const wcstring &src, wcstring *out_cmd, wcstring *out_joined_args,
|
||||
statement_decoration_t *out_deco) {
|
||||
using namespace ast;
|
||||
out_cmd->clear();
|
||||
out_joined_args->clear();
|
||||
*out_deco = statement_decoration_t::none;
|
||||
|
||||
auto ast = ast_parse(src);
|
||||
if (ast->errored()) return false;
|
||||
|
||||
// Get the statement. Should only have one.
|
||||
const decorated_statement_t *statement = nullptr;
|
||||
for (auto ast_traversal = new_ast_traversal(*ast->top());;) {
|
||||
auto n = ast_traversal->next();
|
||||
if (!n->has_value()) break;
|
||||
if (const auto *tmp = n->try_as_decorated_statement()) {
|
||||
if (statement) {
|
||||
say(L"More than one decorated statement found in '%ls'", src.c_str());
|
||||
return false;
|
||||
}
|
||||
statement = tmp;
|
||||
}
|
||||
}
|
||||
if (!statement) {
|
||||
say(L"No decorated statement found in '%ls'", src.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return its decoration and command.
|
||||
*out_deco = statement->decoration();
|
||||
*out_cmd = *statement->command().source(src);
|
||||
|
||||
// Return arguments separated by spaces.
|
||||
bool first = true;
|
||||
for (size_t i = 0; i < statement->args_or_redirs().count(); i++) {
|
||||
const ast::argument_or_redirection_t &arg = *statement->args_or_redirs().at(i);
|
||||
if (!arg.is_argument()) continue;
|
||||
if (!first) out_joined_args->push_back(L' ');
|
||||
out_joined_args->append(*arg.ptr()->source(src));
|
||||
first = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Verify that 'function -h' and 'function --help' are plain statements but 'function --foo' is
|
||||
// not (issue #1240).
|
||||
template <ast::type_t Type>
|
||||
static void check_function_help(const wchar_t *src) {
|
||||
using namespace ast;
|
||||
auto ast = ast_parse(src);
|
||||
if (ast->errored()) {
|
||||
err(L"Failed to parse '%ls'", src);
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (auto ast_traversal = new_ast_traversal(*ast->top());;) {
|
||||
auto node = ast_traversal->next();
|
||||
if (!node->has_value()) break;
|
||||
count += (node->typ() == Type);
|
||||
}
|
||||
if (count == 0) {
|
||||
err(L"Failed to find node of type '%ls'", ast_type_to_string(Type));
|
||||
} else if (count > 1) {
|
||||
err(L"Found too many nodes of type '%ls'", ast_type_to_string(Type));
|
||||
}
|
||||
}
|
||||
|
||||
// todo!("port this")
|
||||
// Test the LL2 (two token lookahead) nature of the parser by exercising the special builtin and
|
||||
// command handling. In particular, 'command foo' should be a decorated statement 'foo' but 'command
|
||||
// -help' should be an undecorated statement 'command' with argument '--help', and NOT attempt to
|
||||
// run a command called '--help'.
|
||||
static void test_new_parser_ll2() {
|
||||
say(L"Testing parser two-token lookahead");
|
||||
|
||||
const struct {
|
||||
wcstring src;
|
||||
wcstring cmd;
|
||||
wcstring args;
|
||||
statement_decoration_t deco;
|
||||
} tests[] = {{L"echo hello", L"echo", L"hello", statement_decoration_t::none},
|
||||
{L"command echo hello", L"echo", L"hello", statement_decoration_t::command},
|
||||
{L"exec echo hello", L"echo", L"hello", statement_decoration_t::exec},
|
||||
{L"command command hello", L"command", L"hello", statement_decoration_t::command},
|
||||
{L"builtin command hello", L"command", L"hello", statement_decoration_t::builtin},
|
||||
{L"command --help", L"command", L"--help", statement_decoration_t::none},
|
||||
{L"command -h", L"command", L"-h", statement_decoration_t::none},
|
||||
{L"command", L"command", L"", statement_decoration_t::none},
|
||||
{L"command -", L"command", L"-", statement_decoration_t::none},
|
||||
{L"command --", L"command", L"--", statement_decoration_t::none},
|
||||
{L"builtin --names", L"builtin", L"--names", statement_decoration_t::none},
|
||||
{L"function", L"function", L"", statement_decoration_t::none},
|
||||
{L"function --help", L"function", L"--help", statement_decoration_t::none}};
|
||||
|
||||
for (const auto &test : tests) {
|
||||
wcstring cmd, args;
|
||||
statement_decoration_t deco = statement_decoration_t::none;
|
||||
bool success = test_1_parse_ll2(test.src, &cmd, &args, &deco);
|
||||
if (!success) err(L"Parse of '%ls' failed on line %ld", test.cmd.c_str(), (long)__LINE__);
|
||||
if (cmd != test.cmd)
|
||||
err(L"When parsing '%ls', expected command '%ls' but got '%ls' on line %ld",
|
||||
test.src.c_str(), test.cmd.c_str(), cmd.c_str(), (long)__LINE__);
|
||||
if (args != test.args)
|
||||
err(L"When parsing '%ls', expected args '%ls' but got '%ls' on line %ld",
|
||||
test.src.c_str(), test.args.c_str(), args.c_str(), (long)__LINE__);
|
||||
if (deco != test.deco)
|
||||
err(L"When parsing '%ls', expected decoration %d but got %d on line %ld",
|
||||
test.src.c_str(), (int)test.deco, (int)deco, (long)__LINE__);
|
||||
}
|
||||
|
||||
check_function_help<ast::type_t::decorated_statement>(L"function -h");
|
||||
check_function_help<ast::type_t::decorated_statement>(L"function --help");
|
||||
check_function_help<ast::type_t::function_header>(L"function --foo; end");
|
||||
check_function_help<ast::type_t::function_header>(L"function foo; end");
|
||||
}
|
||||
|
||||
// todo!("port this")
|
||||
static void test_new_parser_ad_hoc() {
|
||||
using namespace ast;
|
||||
|
@ -1713,7 +1593,6 @@ static const test_t s_tests[]{
|
|||
{TEST_GROUP("enum"), test_enum_set},
|
||||
{TEST_GROUP("enum"), test_enum_array},
|
||||
{TEST_GROUP("autosuggestion"), test_autosuggestion_combining},
|
||||
{TEST_GROUP("new_parser_ll2"), test_new_parser_ll2},
|
||||
{TEST_GROUP("test_abbreviations"), test_abbreviations},
|
||||
{TEST_GROUP("new_parser_ad_hoc"), test_new_parser_ad_hoc},
|
||||
{TEST_GROUP("new_parser_errors"), test_new_parser_errors},
|
||||
|
|
Loading…
Reference in a new issue