Add && and || to fish grammar

This teaches the parser about && and ||, implemented via a new
production "job_conjunction".

These do not yet have execution support.
This commit is contained in:
ridiculousfish 2018-03-01 13:39:39 -08:00
parent 8ded041352
commit 23d4f93556
6 changed files with 96 additions and 22 deletions

View file

@ -838,6 +838,38 @@ static void test_parser() {
err(L"backgrounded 'while' conditional not reported as error"); err(L"backgrounded 'while' conditional not reported as error");
} }
if (!parse_util_detect_errors(L"true | || false")) {
err(L"bogus boolean statement error not detected on line %d", __LINE__);
}
if (!parse_util_detect_errors(L"|| false")) {
err(L"bogus boolean statement error not detected on line %d", __LINE__);
}
if (!parse_util_detect_errors(L"&& false")) {
err(L"bogus boolean statement error not detected on line %d", __LINE__);
}
if (!parse_util_detect_errors(L"true ; && false")) {
err(L"bogus boolean statement error not detected on line %d", __LINE__);
}
if (!parse_util_detect_errors(L"true ; || false")) {
err(L"bogus boolean statement error not detected on line %d", __LINE__);
}
if (!parse_util_detect_errors(L"true || && false")) {
err(L"bogus boolean statement error not detected on line %d", __LINE__);
}
if (!parse_util_detect_errors(L"true && || false")) {
err(L"bogus boolean statement error not detected on line %d", __LINE__);
}
if (!parse_util_detect_errors(L"true && && false")) {
err(L"bogus boolean statement error not detected on line %d", __LINE__);
}
say(L"Testing basic evaluation"); say(L"Testing basic evaluation");
// Ensure that we don't crash on infinite self recursion and mutual recursion. These must use // Ensure that we don't crash on infinite self recursion and mutual recursion. These must use
@ -3414,6 +3446,11 @@ static void test_new_parser_correctness() {
{L"begin; end", true}, {L"begin; end", true},
{L"begin if true; end; end;", true}, {L"begin if true; end; end;", true},
{L"begin if true ; echo hi ; end; end", true}, {L"begin if true ; echo hi ; end; end", true},
{L"true && false || false", true},
{L"true || false; and true", true},
{L"true || ||", false},
{L"|| true", false},
{L"true || \n\n false", true},
}; };
for (size_t i = 0; i < sizeof parser_tests / sizeof *parser_tests; i++) { for (size_t i = 0; i < sizeof parser_tests / sizeof *parser_tests; i++) {

View file

@ -17,6 +17,7 @@ enum parse_token_type_t {
token_type_invalid = 1, token_type_invalid = 1,
// Non-terminal tokens // Non-terminal tokens
symbol_job_list, symbol_job_list,
symbol_job_conjunction,
symbol_job, symbol_job,
symbol_job_continuation, symbol_job_continuation,
symbol_statement, symbol_statement,
@ -52,6 +53,8 @@ enum parse_token_type_t {
parse_token_type_pipe, parse_token_type_pipe,
parse_token_type_redirection, parse_token_type_redirection,
parse_token_type_background, parse_token_type_background,
parse_token_type_andand,
parse_token_type_oror,
parse_token_type_end, parse_token_type_end,
// Special terminal type that means no more tokens forthcoming. // Special terminal type that means no more tokens forthcoming.
parse_token_type_terminate, parse_token_type_terminate,
@ -77,6 +80,8 @@ const enum_map<parse_token_type_t> token_enum_map[] = {
{parse_token_type_pipe, L"parse_token_type_pipe"}, {parse_token_type_pipe, L"parse_token_type_pipe"},
{parse_token_type_redirection, L"parse_token_type_redirection"}, {parse_token_type_redirection, L"parse_token_type_redirection"},
{parse_token_type_string, L"parse_token_type_string"}, {parse_token_type_string, L"parse_token_type_string"},
{parse_token_type_andand, L"parse_token_type_andand"},
{parse_token_type_oror, L"parse_token_type_oror"},
{parse_token_type_terminate, L"parse_token_type_terminate"}, {parse_token_type_terminate, L"parse_token_type_terminate"},
{symbol_andor_job_list, L"symbol_andor_job_list"}, {symbol_andor_job_list, L"symbol_andor_job_list"},
{symbol_argument, L"symbol_argument"}, {symbol_argument, L"symbol_argument"},
@ -98,6 +103,7 @@ const enum_map<parse_token_type_t> token_enum_map[] = {
{symbol_if_clause, L"symbol_if_clause"}, {symbol_if_clause, L"symbol_if_clause"},
{symbol_if_statement, L"symbol_if_statement"}, {symbol_if_statement, L"symbol_if_statement"},
{symbol_job, L"symbol_job"}, {symbol_job, L"symbol_job"},
{symbol_job_conjunction, L"symbol_job_conjunction"},
{symbol_job_continuation, L"symbol_job_continuation"}, {symbol_job_continuation, L"symbol_job_continuation"},
{symbol_job_list, L"symbol_job_list"}, {symbol_job_list, L"symbol_job_list"},
{symbol_optional_newlines, L"symbol_optional_newlines"}, {symbol_optional_newlines, L"symbol_optional_newlines"},

View file

@ -35,6 +35,8 @@ using tok_string = primitive<parse_token_type_string>;
using tok_pipe = primitive<parse_token_type_pipe>; using tok_pipe = primitive<parse_token_type_pipe>;
using tok_background = primitive<parse_token_type_background>; using tok_background = primitive<parse_token_type_background>;
using tok_redirection = primitive<parse_token_type_redirection>; using tok_redirection = primitive<parse_token_type_redirection>;
using tok_andand = primitive<parse_token_type_andand>;
using tok_oror = primitive<parse_token_type_oror>;
// Define keyword types. // Define keyword types.
template <parse_keyword_t Keyword> template <parse_keyword_t Keyword>
@ -197,12 +199,20 @@ struct alternative {};
// A job_list is a list of jobs, separated by semicolons or newlines // A job_list is a list of jobs, separated by semicolons or newlines
DEF_ALT(job_list) { DEF_ALT(job_list) {
using normal = seq<job, job_list>; using normal = seq<job, job_conjunction, job_list>;
using empty_line = seq<tok_end, job_list>; using empty_line = seq<tok_end, job_list>;
using empty = grammar::empty; using empty = grammar::empty;
ALT_BODY(job_list, normal, empty_line, empty); ALT_BODY(job_list, normal, empty_line, empty);
}; };
// A job_conjunction is a || or && continuation of a job
DEF_ALT(job_conjunction) {
using andands = seq<tok_andand, optional_newlines, job, job_conjunction>;
using orors = seq<tok_oror, optional_newlines, job, job_conjunction>;
using empty = grammar::empty;
ALT_BODY(job_conjunction, andands, orors, empty);
};
// A job is a non-empty list of statements, separated by pipes. (Non-empty is useful for cases // A job is a non-empty list of statements, separated by pipes. (Non-empty is useful for cases
// like if statements, where we require a command). To represent "non-empty", we require a // like if statements, where we require a command). To represent "non-empty", we require a
// statement, followed by a possibly empty job_continuation, and then optionally a background // statement, followed by a possibly empty job_continuation, and then optionally a background
@ -291,9 +301,9 @@ produces_sequence<keyword<parse_keyword_function>, argument, argument_list, tok_
// A boolean statement is AND or OR or NOT // A boolean statement is AND or OR or NOT
DEF_ALT(boolean_statement) { DEF_ALT(boolean_statement) {
using ands = seq<keyword<parse_keyword_and>, statement>; using ands = seq<keyword<parse_keyword_and>, statement>; // foo ; and bar
using ors = seq<keyword<parse_keyword_or>, statement>; using ors = seq<keyword<parse_keyword_or>, statement>; // foo ; or bar
using nots = seq<keyword<parse_keyword_not>, statement>; using nots = seq<keyword<parse_keyword_not>, statement>; // not foo
ALT_BODY(boolean_statement, ands, ors, nots); ALT_BODY(boolean_statement, ands, ors, nots);
}; };

View file

@ -1,6 +1,7 @@
// Define ELEM before including this file. // Define ELEM before including this file.
ELEM(job_list) ELEM(job_list)
ELEM(job) ELEM(job)
ELEM(job_conjunction)
ELEM(job_continuation) ELEM(job_continuation)
ELEM(statement) ELEM(statement)
ELEM(if_statement) ELEM(if_statement)

View file

@ -61,6 +61,19 @@ RESOLVE(job_list) {
} }
} }
RESOLVE(job_conjunction) {
UNUSED(token2);
UNUSED(out_tag);
switch (token1.type) {
case parse_token_type_andand:
return production_for<andands>();
case parse_token_type_oror:
return production_for<orors>();
default:
return production_for<empty>();
}
}
RESOLVE(job_continuation) { RESOLVE(job_continuation) {
UNUSED(token2); UNUSED(token2);
UNUSED(out_tag); UNUSED(out_tag);
@ -106,6 +119,10 @@ RESOLVE(statement) {
} }
switch (token1.type) { switch (token1.type) {
case parse_token_type_andand:
case parse_token_type_oror:
return production_for<boolean>();
case parse_token_type_string: { case parse_token_type_string: {
switch (token1.keyword) { switch (token1.keyword) {
case parse_keyword_and: case parse_keyword_and:
@ -356,6 +373,8 @@ const production_element_t *parse_productions::production_for_token(parse_token_
case parse_token_type_pipe: case parse_token_type_pipe:
case parse_token_type_redirection: case parse_token_type_redirection:
case parse_token_type_background: case parse_token_type_background:
case parse_token_type_andand:
case parse_token_type_oror:
case parse_token_type_end: case parse_token_type_end:
case parse_token_type_terminate: { case parse_token_type_terminate: {
debug(0, "Terminal token type %ls passed to %s", token_type_description(node_type), debug(0, "Terminal token type %ls passed to %s", token_type_description(node_type),

View file

@ -138,30 +138,29 @@ static wcstring token_type_user_presentable_description(
switch (type) { switch (type) {
// Hackish. We only support the following types. // Hackish. We only support the following types.
case symbol_statement: { case symbol_statement:
return L"a command"; return L"a command";
} case symbol_argument:
case symbol_argument: {
return L"an argument"; return L"an argument";
} case symbol_job:
case parse_token_type_string: { case symbol_job_list:
return L"a job";
case parse_token_type_string:
return L"a string"; return L"a string";
} case parse_token_type_pipe:
case parse_token_type_pipe: {
return L"a pipe"; return L"a pipe";
} case parse_token_type_redirection:
case parse_token_type_redirection: {
return L"a redirection"; return L"a redirection";
} case parse_token_type_background:
case parse_token_type_background: {
return L"a '&'"; return L"a '&'";
} case parse_token_type_andand:
case parse_token_type_end: { return L"'&&'";
case parse_token_type_oror:
return L"'||'";
case parse_token_type_end:
return L"end of the statement"; return L"end of the statement";
} case parse_token_type_terminate:
case parse_token_type_terminate: {
return L"end of the input"; return L"end of the input";
}
default: { return format_string(L"a %ls", token_type_description(type)); } default: { return format_string(L"a %ls", token_type_description(type)); }
} }
} }
@ -222,9 +221,9 @@ static inline parse_token_type_t parse_token_type_from_tokenizer_token(
case TOK_PIPE: case TOK_PIPE:
return parse_token_type_pipe; return parse_token_type_pipe;
case TOK_ANDAND: case TOK_ANDAND:
return parse_token_type_andand;
case TOK_OROR: case TOK_OROR:
// Temporary while && and || support is brought up. return parse_token_type_oror;
return parse_special_type_comment;
case TOK_END: case TOK_END:
return parse_token_type_end; return parse_token_type_end;
case TOK_BACKGROUND: case TOK_BACKGROUND:
@ -731,6 +730,8 @@ static bool type_is_terminal_type(parse_token_type_t type) {
case parse_token_type_redirection: case parse_token_type_redirection:
case parse_token_type_background: case parse_token_type_background:
case parse_token_type_end: case parse_token_type_end:
case parse_token_type_andand:
case parse_token_type_oror:
case parse_token_type_terminate: { case parse_token_type_terminate: {
return true; return true;
} }