Add grammar in type system

This commit is contained in:
ridiculousfish 2018-01-07 13:34:04 -08:00
parent 0a4883a6b8
commit a012320a9a
4 changed files with 198 additions and 0 deletions

View file

@ -703,6 +703,7 @@
D025C02815D1FEA100B9DB63 /* functions */ = {isa = PBXFileReference; lastKnownFileType = folder; name = functions; path = share/functions; sourceTree = "<group>"; };
D025C02915D1FEA100B9DB63 /* tools */ = {isa = PBXFileReference; lastKnownFileType = folder; name = tools; path = share/tools; sourceTree = "<group>"; };
D02960E51FBD726100CA3985 /* builtin_wait.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = builtin_wait.cpp; sourceTree = "<group>"; };
D0301C1D2002B90500B1F463 /* parse_grammar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = parse_grammar.h; sourceTree = "<group>"; };
D031890915E36D9800D9CC39 /* base */ = {isa = PBXFileReference; lastKnownFileType = text; path = base; sourceTree = BUILT_PRODUCTS_DIR; };
D03238891849D1980032CF2C /* pager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pager.cpp; sourceTree = "<group>"; };
D032388A1849D1980032CF2C /* pager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pager.h; sourceTree = "<group>"; };
@ -1232,6 +1233,7 @@
D03238891849D1980032CF2C /* pager.cpp */,
D0A0851B13B3ACEE0099B651 /* parse_util.h */,
D0A0855213B3ACEE0099B651 /* parse_util.cpp */,
D0301C1D2002B90500B1F463 /* parse_grammar.h */,
D0A0851C13B3ACEE0099B651 /* parser_keywords.h */,
D0A0855313B3ACEE0099B651 /* parser_keywords.cpp */,
D0A0851D13B3ACEE0099B651 /* parser.h */,

163
src/parse_grammar.h Normal file
View file

@ -0,0 +1,163 @@
// Programmatic representation of fish grammar
#ifndef FISH_PARSE_GRAMMAR_H
#define FISH_PARSE_GRAMMAR_H
#include "parse_constants.h"
#include "tokenizer.h"
namespace grammar {
using production_element_t = uint8_t;
// Forward declarations.
#define ELEM(T) struct T;
#include "parse_grammar_elements.inc"
// A production is a sequence of production elements.
template <int WHICH, uint8_t count = 0>
struct production_t {
production_element_t elems[count];
};
template <int TOKEN>
struct prim {};
using tok_end = prim<TOK_END>;
using tok_string = prim<TOK_STRING>;
template <int WHICH>
struct keyword {};
// A producer holds various productions.
template <typename T>
struct producer {};
// Empty produces nothing.
struct empty : public producer<production_t<0>> {};
// Not sure if we need this.
template <class A>
struct single {};
template <class A>
using produces_single = producer<single<A>>;
// Alternative represents a choice.
template <class A1, class A2, class A3 = empty, class A4 = empty, class A5 = empty>
struct alternative {};
template <class... Args>
using produces_alternative = producer<alternative<Args...>>;
// Sequence represents a list of productions.
template <class A1, class A2, class A3 = empty, class A4 = empty, class A5 = empty,
class A6 = empty>
struct seq {};
template <class... Args>
using produces_sequence = producer<seq<Args...>>;
// Following are the grammar productions.
#define DEF(T) struct T : public
// A job_list is a list of jobs, separated by semicolons or newlines
DEF(job_list)
produces_alternative<empty, //
seq<job, job_list>, //
seq<job, job_list>>{};
// 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
// statement, followed by a possibly empty job_continuation, and then optionally a background
// specifier '&'
DEF(job) produces_sequence<statement, job_continuation, optional_background>{};
DEF(job_continuation)
produces_alternative<empty, //
seq<prim<TOK_PIPE>, statement, job_continuation>>{};
// A statement is a normal command, or an if / while / and etc
DEF(statement)
produces_alternative<boolean_statement, //
block_statement, //
if_statement, //
switch_statement, //
decorated_statement>{};
// A block is a conditional, loop, or begin/end
DEF(if_statement)
produces_sequence<if_clause, else_clause, end_command, arguments_or_redirections_list>{};
DEF(if_clause)
produces_sequence<keyword<parse_keyword_if>, job, tok_end, andor_job_list, job_list>{};
DEF(else_clause) produces_alternative<empty, seq<keyword<parse_keyword_else>, else_continuation>>{};
DEF(else_continuation)
produces_alternative<seq<if_clause, else_clause>, //
seq<tok_end, job_list>>{};
DEF(switch_statement)
produces_sequence<keyword<parse_keyword_switch>, argument, tok_end, case_item_list, end_command,
arguments_or_redirections_list>{};
DEF(case_item_list)
produces_alternative<empty, //
seq<case_item, case_item_list>, //
seq<tok_end, case_item_list>>{};
DEF(case_item) produces_sequence<keyword<parse_keyword_case>, argument_list, tok_end, job_list>{};
DEF(block_statement)
produces_sequence<block_header, job_list, end_command, arguments_or_redirections_list>{};
DEF(block_header) produces_alternative<for_header, while_header, function_header, begin_header>{};
DEF(for_header)
produces_sequence<keyword<parse_keyword_for>, tok_string, keyword<parse_keyword_in>, argument_list,
tok_end>{};
DEF(while_header) produces_sequence<keyword<parse_keyword_while>, job, tok_end, andor_job_list>{};
struct begin_header : produces_single<keyword<parse_keyword_begin>> {};
// Functions take arguments, and require at least one (the name). No redirections allowed.
DEF(function_header)
produces_sequence<keyword<parse_keyword_function>, argument, argument_list, tok_end>{};
// A boolean statement is AND or OR or NOT
DEF(boolean_statement)
produces_alternative<seq<keyword<parse_keyword_and>, statement>, //
seq<keyword<parse_keyword_or>, statement>, //
seq<keyword<parse_keyword_not>, statement>>{};
// An andor_job_list is zero or more job lists, where each starts with an `and` or `or` boolean
// statement.
DEF(andor_job_list)
produces_alternative<empty, //
seq<job, andor_job_list>, //
seq<tok_end, andor_job_list>>{};
// A decorated_statement is a command with a list of arguments_or_redirections, possibly with
// "builtin" or "command" or "exec"
DEF(decorated_statement)
produces_alternative<plain_statement, //
seq<keyword<parse_keyword_command>, plain_statement>, //
seq<keyword<parse_keyword_builtin>, plain_statement>, //
seq<keyword<parse_keyword_exec>, plain_statement>>{};
DEF(plain_statement) produces_sequence<tok_string, arguments_or_redirections_list>{};
DEF(argument_list) produces_alternative<empty, seq<argument, argument_list>>{};
DEF(arguments_or_redirections_list)
produces_alternative<empty, seq<argument_or_redirection, arguments_or_redirections_list>>{};
DEF(argument_or_redirection) produces_alternative<argument, redirection>{};
DEF(argument) produces_single<tok_string>{};
DEF(optional_background) produces_alternative<empty, prim<TOK_BACKGROUND>>{};
DEF(end_command) produces_single<keyword<parse_keyword_end>>{};
// A freestanding_argument_list is equivalent to a normal argument list, except it may contain
// TOK_END (newlines, and even semicolons, for historical reasons)
DEF(freestanding_argument_list)
produces_alternative<empty, //
seq<argument, freestanding_argument_list>, //
seq<tok_end, freestanding_argument_list>>{};
}
#endif

View file

@ -0,0 +1,32 @@
// Define ELEM before including this file.
ELEM(job_list)
ELEM(job)
ELEM(job_continuation)
ELEM(statement)
ELEM(if_statement)
ELEM(if_clause)
ELEM(else_clause)
ELEM(else_continuation)
ELEM(switch_statement)
ELEM(case_item_list)
ELEM(case_item)
ELEM(block_statement)
ELEM(block_header)
ELEM(for_header)
ELEM(while_header)
ELEM(begin_header)
ELEM(function_header)
ELEM(boolean_statement)
ELEM(andor_job_list)
ELEM(decorated_statement)
ELEM(plain_statement)
ELEM(argument_list)
ELEM(arguments_or_redirections_list)
ELEM(argument_or_redirection)
ELEM(argument)
ELEM(redirection)
ELEM(optional_background)
ELEM(end_command)
ELEM(freestanding_argument_list)
#undef ELEM

View file

@ -4,6 +4,7 @@
#include "common.h"
#include "parse_constants.h"
#include "parse_grammar.h"
#include "parse_productions.h"
#include "parse_tree.h"