// Copyright (c) Herb Sutter // SPDX-License-Identifier: CC-BY-NC-ND-4.0 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. //=========================================================================== // Parser //=========================================================================== #ifndef __CPP2_PARSE #define __CPP2_PARSE #include "lex.h" #include #include #include namespace cpp2 { auto violates_lifetime_safety = false; //----------------------------------------------------------------------- // Operator categorization // //G prefix-operator: //G one of not //G auto is_prefix_operator(lexeme l) -> bool { switch (l) { break;case lexeme::Not: case lexeme::Minus: case lexeme::Plus: return true; break;default: return false; } } //G postfix-operator: //G one of ++ -- * & ~ $ //G auto is_postfix_operator(lexeme l) -> bool { switch (l) { break;case lexeme::PlusPlus: case lexeme::MinusMinus: case lexeme::Multiply: case lexeme::Ampersand: case lexeme::Tilde: case lexeme::Dollar: return true; break;default: return false; } } //G assignment-operator: //G one of = *= /= %= += -= >>= <<= //G auto is_assignment_operator(lexeme l) -> bool { switch (l) { break;case lexeme::Assignment: case lexeme::MultiplyEq: case lexeme::SlashEq: case lexeme::ModuloEq: case lexeme::PlusEq: case lexeme::MinusEq: case lexeme::RightShiftEq: case lexeme::LeftShiftEq: case lexeme::AmpersandEq: case lexeme::CaretEq: case lexeme::PipeEq: return true; break;default: return false; } } //----------------------------------------------------------------------- // // Parse tree node types // //----------------------------------------------------------------------- // //----------------------------------------------------------------------- // try_emit // // Helper to visit whatever is in a variant where each // alternative is a smart pointer // template auto try_visit(auto& variant, auto& visitor, int depth) -> void { if (variant.index() == I) { auto const& s = std::get(variant); assert (s); s->visit(visitor, depth+1); } } struct expression_list_node; struct id_expression_node; struct declaration_node; struct inspect_expression_node; struct primary_expression_node { enum active { empty=0, identifier, expression_list, id_expression, declaration, inspect }; std::variant< std::monostate, token const*, std::unique_ptr, std::unique_ptr, std::unique_ptr, std::unique_ptr > expr; auto get_token() -> token const*; auto position() const -> source_position; auto visit(auto& v, int depth) -> void; }; struct postfix_expression_node; struct prefix_expression_node { std::vector ops; std::unique_ptr expr; auto get_postfix_expression_node() const -> postfix_expression_node const* { assert(expr); return expr.get(); } auto position() const -> source_position; auto visit(auto& v, int depth) -> void; }; template< String Name, typename Term > struct binary_expression_node { std::unique_ptr expr; struct term { token const* op; std::unique_ptr expr; }; std::vector terms; // Get left-hand postfix-expression auto get_postfix_expression_node() const -> postfix_expression_node const* { assert(expr); return expr->get_postfix_expression_node(); } // Get first right-hand postfix-expression, if there is one auto get_second_postfix_expression_node() const -> postfix_expression_node const* { if (!terms.empty()) { assert(terms.front().expr); return terms.front().expr->get_postfix_expression_node(); } // else return {}; } auto position() const -> source_position { assert (expr); return expr->position(); } auto visit(auto& v, int depth) -> void { v.start(*this, depth); assert (expr); expr->visit(v, depth+1); for (auto const& x : terms) { assert (x.op); v.start(*x.op, depth+1); assert (x.expr); x.expr->visit(v, depth+1); } v.end(*this, depth); } }; using is_as_expression_node = binary_expression_node< "is-as" , prefix_expression_node >; using multiplicative_expression_node = binary_expression_node< "multiplicative" , is_as_expression_node >; using additive_expression_node = binary_expression_node< "additive" , multiplicative_expression_node >; using shift_expression_node = binary_expression_node< "shift" , additive_expression_node >; using compare_expression_node = binary_expression_node< "compare" , shift_expression_node >; using relational_expression_node = binary_expression_node< "relational" , compare_expression_node >; using equality_expression_node = binary_expression_node< "equality" , relational_expression_node >; using bit_and_expression_node = binary_expression_node< "bit-and" , equality_expression_node >; using bit_xor_expression_node = binary_expression_node< "bit-xor" , bit_and_expression_node >; using bit_or_expression_node = binary_expression_node< "bit-or" , bit_xor_expression_node >; using logical_and_expression_node = binary_expression_node< "logical-and" , bit_or_expression_node >; using logical_or_expression_node = binary_expression_node< "logical-or" , logical_and_expression_node >; using assignment_expression_node = binary_expression_node< "assignment" , logical_or_expression_node >; struct expression_node { std::unique_ptr expr; auto position() const -> source_position { assert (expr); return expr->position(); } auto visit(auto& v, int depth) -> void { v.start(*this, depth); assert (expr); expr->visit(v, depth+1); v.end(*this, depth); } }; enum class passing_style { in=0, copy, inout, out, move, forward }; auto to_string_view(passing_style pass) -> std::string_view { switch (pass) { break;case passing_style::in : return "in"; break;case passing_style::copy : return "copy"; break;case passing_style::inout : return "inout"; break;case passing_style::out : return "out"; break;case passing_style::move : return "move"; break;case passing_style::forward: return "forward"; break;default: return "INVALID passing_tyle"; } } struct expression_list_node { source_position open_paren = {}; source_position close_paren = {}; bool inside_initializer = false; struct term { passing_style pass = {}; std::unique_ptr expr; }; std::vector< term > expressions; auto position() const -> source_position { // Make sure this got set assert (open_paren != source_position()); return open_paren; } auto visit(auto& v, int depth) -> void { v.start(*this, depth); for (auto const& x : expressions) { assert(x.expr); x.expr->visit(v, depth+1); } v.end(*this, depth); } }; struct expression_statement_node { std::unique_ptr expr; bool has_semicolon = false; auto position() const -> source_position { assert (expr); return expr->position(); } auto visit(auto& v, int depth) -> void { v.start(*this, depth); assert (expr); expr->visit(v, depth+1); v.end(*this, depth); } }; struct capture { postfix_expression_node* capture_expr; std::string str; }; using capture_group = std::vector; struct postfix_expression_node { std::unique_ptr expr; struct term { token const* op; // This is used if *op is . - can be null std::unique_ptr id_expr; // These are used if *op is [ or ( - can be null std::unique_ptr expr_list; token const* op_close; }; std::vector ops; capture_group* cap_grp = {}; auto position() const -> source_position { assert (expr); return expr->position(); } auto visit(auto& v, int depth) -> void; }; auto prefix_expression_node::position() const -> source_position { if (std::ssize(ops) > 0) { return ops.front()->position(); } assert (expr); return expr->position(); } auto prefix_expression_node::visit(auto& v, int depth) -> void { v.start(*this, depth); for (auto const& x : ops) { assert (x); v.start(*x, depth+1); } assert (expr); expr->visit(v, depth+1); v.end(*this, depth); } struct unqualified_id_node { token const* const_qualifier = {}; // optional token const* identifier = {}; // required enum active { empty=0, expression, id_expression }; // These are used only if it's a template-id source_position open_angle = {}; source_position close_angle = {}; struct term { source_position comma; std::variant< std::monostate, std::unique_ptr, std::unique_ptr > arg; }; std::vector template_args; auto get_token() -> token const* { if (template_args.empty()) { assert (identifier); return identifier; } // else return {}; } auto position() const -> source_position { assert (identifier); return identifier->position(); } auto visit(auto& v, int depth) -> void { v.start(*this, depth); if (const_qualifier) { v.start(*const_qualifier, depth+1); } assert (identifier); v.start(*identifier, depth+1); if (!template_args.empty()) { assert(open_angle != source_position{}); assert(close_angle != source_position{}); assert(template_args.front().comma == source_position{}); for (auto& a : template_args) { try_visit< expression>(a.arg, v, depth+1); try_visit(a.arg, v, depth+1); } } v.end(*this, depth); } }; struct qualified_id_node { struct term { token const* scope_op; std::unique_ptr id = nullptr; term( token const* o ) : scope_op{o} { } }; std::vector ids; auto position() const -> source_position { assert (!ids.empty()); if (ids.front().scope_op) { return ids.front().scope_op->position(); } else { assert (ids.front().id); return ids.front().id->position(); } } auto visit(auto& v, int depth) -> void { v.start(*this, depth); for (auto const& x : ids) { if (x.scope_op) { x.scope_op->visit(v, depth+1); } assert(x.id); x.id->visit(v, depth+1); } v.end(*this, depth); } }; struct id_expression_node { source_position pos; enum active { empty=0, qualified, unqualified }; std::variant< std::monostate, std::unique_ptr, std::unique_ptr > id; auto get_token() -> token const* { if (id.index() == unqualified) { return std::get(id)->get_token(); } // else return {}; } auto position() const -> source_position { return pos; } auto visit(auto& v, int depth) -> void { v.start(*this, depth); try_visit(id, v, depth); try_visit(id, v, depth); v.end(*this, depth); } }; auto postfix_expression_node::visit(auto& v, int depth) -> void { v.start(*this, depth); assert (expr); expr->visit(v, depth+1); for (auto const& x : ops) { assert (x.op); v.start(*x.op, depth+1); if (x.id_expr) { x.id_expr->visit(v, depth+1); } if (x.expr_list) { x.expr_list->visit(v, depth+1); } } v.end(*this, depth); } struct statement_node; struct compound_statement_node { source_position open_brace; source_position close_brace; std::vector> statements; compound_statement_node(source_position o = source_position{}) : open_brace{o} { } auto position() const -> source_position { return open_brace; } auto visit(auto& v, int depth) -> void; }; struct selection_statement_node { bool is_constexpr = false; token const* identifier; source_position else_pos; std::unique_ptr expression; std::unique_ptr true_branch; std::unique_ptr false_branch; bool has_source_false_branch = false; auto position() const -> source_position { assert (identifier); return identifier->position(); } auto visit(auto& v, int depth) -> void { v.start(*this, depth); assert (identifier); v.start(*identifier, depth+1); assert (expression); expression->visit(v, depth+1); assert (true_branch); true_branch->visit(v, depth+1); if (false_branch) { false_branch->visit(v, depth+1); } v.end(*this, depth); } }; struct parameter_declaration_node; struct iteration_statement_node { token const* identifier; std::unique_ptr next_expression; // if used, else null std::unique_ptr condition; // used for "do" and "while", else null std::unique_ptr statement; // used for "do" and "while", else null std::unique_ptr range; // used for "for", else null std::unique_ptr body; // used for "for", else null auto get_for_parameter() const -> parameter_declaration_node const*; auto position() const -> source_position { assert(identifier); return identifier->position(); } auto visit(auto& v, int depth) -> void; }; struct return_statement_node { token const* identifier; std::unique_ptr expression; auto position() const -> source_position { assert(identifier); return identifier->position(); } auto visit(auto& v, int depth) -> void { v.start(*this, depth); if (expression) { expression->visit(v, depth+1); } v.end(*this, depth); } }; struct alternative_node { std::unique_ptr name; token const* is_as_keyword; std::unique_ptr id_expression; source_position equal_sign; std::unique_ptr statement; auto position() const -> source_position { assert(is_as_keyword); return is_as_keyword->position(); } auto visit(auto& v, int depth) -> void; }; struct inspect_expression_node { bool is_constexpr = false; token const* identifier; std::unique_ptr expression; std::unique_ptr result_type; source_position open_brace; source_position close_brace; std::vector> alternatives; auto position() const -> source_position { assert(identifier); return identifier->position(); } auto visit(auto& v, int depth) -> void { v.start(*this, depth); assert (identifier); v.start(*identifier, depth+1); assert (expression); expression->visit(v, depth+1); if (result_type) { result_type->visit(v, depth+1); } for (auto&& alt : alternatives) { alt->visit(v, depth+1); } v.end(*this, depth); } }; struct contract_node { source_position open_bracket; token const* kind = {}; std::unique_ptr group; std::unique_ptr condition; token const* message = {}; capture_group captures; contract_node( source_position pos ) : open_bracket{pos} { } auto position() const -> source_position { return open_bracket; } auto visit(auto& v, int depth) -> void { v.start(*this, depth); assert(kind); kind->visit(v, depth+1); if (group) { group->visit(v, depth+1); } assert(condition); condition->visit(v, depth+1); v.end(*this, depth); } }; struct parameter_declaration_list_node; struct statement_node { token const* let; std::unique_ptr let_params; enum active { expression=0, compound, selection, declaration, return_, iteration, contract, inspect }; std::variant< std::unique_ptr, std::unique_ptr, std::unique_ptr, std::unique_ptr, std::unique_ptr, std::unique_ptr, std::unique_ptr, std::unique_ptr > statement; auto position() const -> source_position; auto visit(auto& v, int depth) -> void; }; auto alternative_node::visit(auto& v, int depth) -> void { v.start(*this, depth); if (name) { v.start(*name, depth+1); } assert (is_as_keyword); v.start(*is_as_keyword, depth+1); assert (id_expression); id_expression->visit(v, depth+1); assert (statement); statement->visit(v, depth+1); v.end(*this, depth); } auto compound_statement_node::visit(auto& v, int depth) -> void { v.start(*this, depth); for (auto const& x : statements) { assert(x); x->visit(v, depth+1); } v.end(*this, depth); } struct parameter_declaration_node { source_position pos; passing_style pass = passing_style::in; enum class modifier { none=0, implicit, virtual_, override_, final_ }; modifier mod = modifier::none; std::unique_ptr declaration; auto position() const -> source_position; auto visit(auto& v, int depth) -> void; }; struct parameter_declaration_list_node { source_position pos_open_paren; source_position pos_close_paren; std::vector> parameters; auto position() const -> source_position { return pos_open_paren; } auto visit(auto& v, int depth) -> void { v.start(*this, depth); for (auto const& x : parameters) { assert(x); x->visit(v, depth+1); } v.end(*this, depth); } }; auto statement_node::visit(auto& v, int depth) -> void { v.start(*this, depth); if (let) { let->visit(v, depth+1); assert(let_params); let_params->visit(v, depth+1); } try_visit(statement, v, depth); try_visit(statement, v, depth); try_visit(statement, v, depth); try_visit(statement, v, depth); try_visit(statement, v, depth); try_visit(statement, v, depth); try_visit(statement, v, depth); try_visit(statement, v, depth); v.end(*this, depth); } struct function_returns_tag { }; struct function_type_node { std::unique_ptr parameters; bool throws = false; enum active { empty = 0, id, list }; std::variant< std::monostate, std::unique_ptr, std::unique_ptr > returns; std::vector> contracts; auto position() const -> source_position { assert (parameters); return parameters->position(); } auto visit(auto& v, int depth) -> void { v.start(*this, depth); assert(parameters); parameters->visit(v, depth+1); if (returns.index() == id) { auto& r = std::get(returns); assert(r); r->visit(v, depth+1); } else if (returns.index() == list) { auto& r = std::get(returns); assert(r); // Inform the visitor that this is a returns list v.start(function_returns_tag{}, depth); r->visit(v, depth+1); v.end(function_returns_tag{}, depth); } v.end(*this, depth); } }; struct declaration_node { source_position pos; std::unique_ptr identifier; token const* pointer_declarator = nullptr; enum active { function, object }; std::variant< std::unique_ptr, std::unique_ptr > type; source_position equal_sign = {}; source_position decl_end = {}; std::unique_ptr initializer; capture_group captures; // Shorthand for common query // auto is(active a) const { return type.index() == a; } auto position() const -> source_position { if (identifier) { return identifier->position(); } return pos; } auto visit(auto& v, int depth) -> void { v.start(*this, depth); if (identifier) { identifier->visit(v, depth+1); } try_visit(type, v, depth+1); try_visit(type, v, depth+1); if (initializer) { initializer->visit(v, depth+1); } v.end(*this, depth); } }; auto primary_expression_node::get_token() -> token const* { if (expr.index() == identifier) { return std::get(expr); } else if (expr.index() == id_expression) { return std::get(expr)->get_token(); } // else (because we're deliberately ignoring the other // options which are more than a single token) return {}; } auto primary_expression_node::position() const -> source_position { switch (expr.index()) { break;case empty: return { 0, 0 }; break;case identifier: { auto const& s = std::get(expr); assert (s); return s->position(); } break;case expression_list: { auto const& s = std::get(expr); assert (s); return s->position(); } break;case id_expression: { auto const& s = std::get(expr); assert (s); return s->position(); } break;case declaration: { auto const& s = std::get(expr); assert (s); return s->position(); } break;case inspect: { auto const& i = std::get(expr); assert (i); return i->position(); } break;default: assert (!"illegal primary_expression_node state"); return { 0, 0 }; } } auto primary_expression_node::visit(auto& v, int depth) -> void { v.start(*this, depth); try_visit(expr, v, depth); try_visit(expr, v, depth); try_visit(expr, v, depth); try_visit(expr, v, depth); try_visit(expr, v, depth); v.end(*this, depth); } auto iteration_statement_node::get_for_parameter() const -> parameter_declaration_node const* { assert(*identifier == "for"); auto func = std::get_if(&body->type); assert(func && *func && std::ssize((**func).parameters->parameters) == 1); return (**func).parameters->parameters[0].get(); } auto iteration_statement_node::visit(auto& v, int depth) -> void { v.start(*this, depth); if (statement) { statement->visit(v, depth+1); } if (next_expression) { next_expression->visit(v, depth+1); } if (condition) { assert(!range && !body); condition->visit(v, depth+1); } else { assert(range && body); range->visit(v, depth+1); body->visit(v, depth+1); } v.end(*this, depth); } auto statement_node::position() const -> source_position { switch (statement.index()) { break;case expression: { auto const& s = std::get(statement); assert (s); return s->position(); } break;case compound: { auto const& s = std::get(statement); assert (s); return s->position(); } break;case selection: { auto const& s = std::get(statement); assert (s); return s->position(); } break;case declaration: { auto const& s = std::get(statement); assert (s); return s->position(); } break;case return_: { auto const& s = std::get(statement); assert (s); return s->position(); } break;case iteration: { auto const& s = std::get(statement); assert (s); return s->position(); } break;case contract: { auto const& s = std::get(statement); assert (s); return s->position(); } break;default: assert (!"illegal statement_node state"); return { 0, 0 }; } } auto parameter_declaration_node::position() const -> source_position { assert (declaration); return pos; } auto parameter_declaration_node::visit(auto& v, int depth) -> void { v.start(*this, depth); assert (declaration); declaration->visit(v, depth+1); v.end(*this, depth); } struct translation_unit_node { std::vector< std::unique_ptr > declarations; auto position() const -> source_position { if (std::ssize(declarations) > 0) { return declarations.front()->position(); } return {}; } auto visit(auto& v, int depth) -> void { v.start(*this, depth); for (auto const& x : declarations) { assert(x); x->visit(v, depth+1); } v.end(*this, depth); } }; //----------------------------------------------------------------------- // // parser: parses a section of Cpp2 code // //----------------------------------------------------------------------- // class parser { std::vector& errors; std::unique_ptr parse_tree; // Keep a stack of current capture groups (contracts/decls still being parsed) std::vector current_capture_groups; struct capture_groups_stack_guard { parser* pars; capture_groups_stack_guard(parser* p, capture_group* cg) : pars{p} { assert(p); assert(cg); pars->current_capture_groups.push_back(cg); } ~capture_groups_stack_guard() { pars->current_capture_groups.pop_back(); } }; // Used only for the duration of each parse() call std::vector const* tokens_ = nullptr; int pos = 0; public: //----------------------------------------------------------------------- // Constructor // // errors error list // parser( std::vector& errors ) : errors{ errors } , parse_tree{std::make_unique()} { } //----------------------------------------------------------------------- // parse // // tokens input tokens for this section of Cpp2 source code // // Each call parses this section's worth of tokens and adds the // result to the stored parse tree. Call this repeatedly for the Cpp2 // sections in a TU to build the whole TU's parse tree // auto parse( std::vector const& tokens ) -> bool { // Generate parse tree for this section as if a standalone TU tokens_ = &tokens; pos = 0; auto tu = translation_unit(); // Then add it to the complete parse tree parse_tree->declarations.insert( parse_tree->declarations.end(), std::make_move_iterator(tu->declarations.begin()), std::make_move_iterator(tu->declarations.end()) ); if (!done()) { error("unexpected text at end of Cpp2 code section"); return false; } return true; } //----------------------------------------------------------------------- // get_parse_tree // // Get the entire parse tree, from the root (translation_unit_node) // auto get_parse_tree() -> translation_unit_node& { assert (parse_tree); return *parse_tree; } // Get a set of pointers to just the declarations in the given token map section // auto get_parse_tree(std::vector const& tokens) -> std::vector< declaration_node const* > { assert (parse_tree); assert (!tokens.empty()); auto first_line = tokens.front().position().lineno; auto last_line = tokens.back().position().lineno; auto ret = std::vector< declaration_node const* >{}; for (auto& decl : parse_tree->declarations) { assert(decl); // The grammar and the tokens are in lineno order, so we don't // need to look further once we pass the last lineno if (decl->position().lineno > last_line) { break; } if (decl->position().lineno >= first_line) { ret.push_back( decl.get() ); } } return ret; } //----------------------------------------------------------------------- // visit // auto visit(auto& v) -> void { parse_tree->visit(v, 0); } private: //----------------------------------------------------------------------- // Error reporting: Fed into the supplied this->error object // // msg message to be printed // // include_curr_token in this file (during parsing)_ we normally want // to show the current token as the unexpected text // we encountered, but some sema rules are applied // early during parsing and for those it doesn't // make sense to show the next token (e.g., when // we detect and reject a "std::move" qualified-id, // it's not relevant to add "at LeftParen: (" // just because ( happens to be the next token) // auto error(char const* msg, bool include_curr_token = true) const -> void { auto m = std::string{msg}; if (include_curr_token) { m += std::string(" (at '") + curr().to_string(true) + "')"; } errors.emplace_back( curr().position(), m ); } auto error(std::string const& msg, bool include_curr_token = true) const -> void { error(msg.c_str()); } //----------------------------------------------------------------------- // Token navigation: Only these functions should access this->token_ // auto curr() const -> token const& { if (done()) { throw std::runtime_error("unexpected end of source file"); } return (*tokens_)[pos]; } auto peek(int num) const -> token const* { assert (tokens_); if (pos + num >= 0 && pos + num < std::ssize(*tokens_)) { return &(*tokens_)[pos + num]; } return {}; } auto done() const -> bool { assert (tokens_); assert (pos <= std::ssize(*tokens_)); return pos == std::ssize(*tokens_); } auto next(int num = 1) -> void { assert (tokens_); pos = std::min( pos+num, as(std::ssize(*tokens_)) ); } //----------------------------------------------------------------------- // Parsers for unary expressions // //G primary-expression: //G literal //G ( expression-list ) //G id-expression //G unnamed-declaration //G inspect-expression //G auto primary_expression() -> std::unique_ptr { auto n = std::make_unique(); if (auto inspect = inspect_expression(true)) { n->expr = std::move(inspect); return n; } if (auto id = id_expression()) { n->expr = std::move(id); return n; } if (curr().type() == lexeme::Identifier || curr().type() == lexeme::DecimalLiteral || curr().type() == lexeme::FloatLiteral || curr().type() == lexeme::StringLiteral || curr().type() == lexeme::CharacterLiteral || curr().type() == lexeme::BinaryLiteral || curr().type() == lexeme::HexadecimalLiteral || curr().type() == lexeme::Keyword ) { n->expr = &curr(); next(); return n; } if (curr().type() == lexeme::LeftParen) { bool inside_initializer = (peek(-1)->type() == lexeme::Assignment); auto open_paren = curr().position(); next(); auto expr_list = expression_list(open_paren, inside_initializer); if (!expr_list) { error("unexpected text - ( is not followed by an expression-list"); next(); return {}; } if (curr().type() != lexeme::RightParen) { error("unexpected text - expression-list is not terminated by )"); next(); return {}; } expr_list->close_paren = curr().position(); next(); n->expr = std::move(expr_list); return n; } if (auto decl = unnamed_declaration(curr().position(), true, true)) // captures are allowed { assert (!decl->identifier && "ICE: declaration should have been unnamed"); if (!decl->is(declaration_node::function)) { error("an unnamed declaration at expression scope must be a function"); next(); return {}; } auto& func = std::get(decl->type); assert(func); if (func->returns.index() == function_type_node::list) { error("an unnamed function at expression scope currently cannot return multiple values"); next(); return {}; } if (!func->contracts.empty()) { error("an unnamed function at expression scope currently cannot have contracts"); next(); return {}; } n->expr = std::move(decl); return n; } return {}; } //G postfix-expression: //G primary-expression //G postfix-expression postfix-operator [Note: without whitespace before the operator] //G postfix-expression [ expression-list ] //G postfix-expression ( expression-list? ) //G postfix-expression . id-expression //G auto postfix_expression() -> std::unique_ptr { auto n = std::make_unique(); n->expr = primary_expression(); if (!(n->expr)) { return {}; } while ( (is_postfix_operator(curr().type()) // Postfix operators must be lexically adjacent && curr().position().lineno == peek(-1)->position().lineno && curr().position().colno == peek(-1)->position().colno + peek(-1)->length() ) || curr().type() == lexeme::LeftBracket || curr().type() == lexeme::LeftParen || curr().type() == lexeme::Dot ) { // * and & can't be a unary operator if followed by a (, identifier, or literal if ((curr().type() == lexeme::Multiply || curr().type() == lexeme::Ampersand) && peek(1) && (peek(1)->type() == lexeme::LeftParen || peek(1)->type() == lexeme::Identifier || is_literal(peek(1)->type()))) { break; } if (curr().type() == lexeme::Dollar) { // cap_grp must not already be set, or this is a multi-$ postfix-expression if (n->cap_grp) { error("$ (capture) can appear at most once in a single postfix-expression"); return {}; } if (current_capture_groups.empty()) { error("$ (capture) cannot appear here - it must appear in an anonymous expression function, a postcondition, or an interpolated string literal"); return {}; } n->cap_grp = current_capture_groups.back(); n->cap_grp->push_back({n.get()}); } auto term = postfix_expression_node::term{&curr()}; next(); if (term.op->type() == lexeme::LeftBracket) { term.expr_list = expression_list(term.op->position()); if (!term.expr_list) { error("subscript expression [ ] must not be empty"); return {}; } if (curr().type() != lexeme::RightBracket) { error("unexpected text - [ is not properly matched by ]"); return {}; } term.expr_list->close_paren = curr().position(); term.op_close = &curr(); next(); } else if (term.op->type() == lexeme::LeftParen) { term.expr_list = expression_list(term.op->position()); if (!term.expr_list) { error("( is not followed by a valid expression list"); return {}; } if (curr().type() != lexeme::RightParen) { error("unexpected text - ( is not properly matched by )"); return {}; } term.expr_list->close_paren = curr().position(); term.op_close = &curr(); next(); } else if (term.op->type() == lexeme::Dot) { term.id_expr = id_expression(); if (!term.id_expr) { error("'.' must be followed by a valid member name"); return {}; } } n->ops.push_back( std::move(term) ); } return n; } //G prefix-expression: //G postfix-expression //G prefix-operator prefix-expression //GTODO await-expression //GTODO sizeof ( type-id ) //GTODO sizeof ... ( identifier ) //GTODO alignof ( type-id ) //GTODO throws-expression //G auto prefix_expression() -> std::unique_ptr { auto n = std::make_unique(); for ( ; is_prefix_operator(curr().type()); next()) { n->ops.push_back(&curr()); } if ((n->expr = postfix_expression())) { return n; } return {}; } //----------------------------------------------------------------------- // Parsers for binary expressions // // The general /*binary*/-expression: // /*term*/-expression { { /* operators at this predecence level */ } /*term*/-expression }* // template< typename Binary, typename IsValidOp, typename TermFunc > auto binary_expression( IsValidOp is_valid_op, TermFunc term ) -> std::unique_ptr { auto n = std::make_unique(); if ( (n->expr = term()) ) { while (is_valid_op(curr())) { typename Binary::term t{}; t.op = &curr(); next(); if ( !(t.expr = term()) ) { error("invalid expression after " + peek(-1)->to_string()); return n; } n->terms.push_back( std::move(t) ); } return n; } return {}; } //G is-as-expression: //G prefix-expression //GTODO is-as-expression is-expression-constraint //GTODO is-as-expression as-type-cast //GTODO type-id is-type-constraint //G auto is_as_expression() { return binary_expression ( [](token const& t){ std::string_view s{t}; return t.type() == lexeme::Keyword && (s == "is" || s == "as"); }, [this]{ return prefix_expression(); } ); } //G multiplicative-expression: //G is-as-expression //G multiplicative-expression * is-as-expression //G multiplicative-expression / is-as-expression //G multiplicative-expression % is-as-expression //G auto multiplicative_expression() { return binary_expression ( [](token const& t){ return t.type() == lexeme::Multiply || t.type() == lexeme::Slash || t.type() == lexeme::Modulo; }, [this]{ return is_as_expression(); } ); } //G additive-expression: //G multiplicative-expression //G additive-expression + multiplicative-expression //G additive-expression - multiplicative-expression //G auto additive_expression() { return binary_expression ( [](token const& t){ return t.type() == lexeme::Plus || t.type() == lexeme::Minus; }, [this]{ return multiplicative_expression(); } ); } //G shift-expression: //G additive-expression //G shift-expression << additive-expression //G shift-expression >> additive-expression //G auto shift_expression() { return binary_expression ( [](token const& t){ return t.type() == lexeme::LeftShift || t.type() == lexeme::RightShift; }, [this]{ return additive_expression(); } ); } //G compare-expression: //G shift-expression //G compare-expression <=> shift-expression //G auto compare_expression() { return binary_expression ( [](token const& t){ return t.type() == lexeme::Spaceship; }, [this]{ return shift_expression(); } ); } //G relational-expression: //G compare-expression //G relational-expression < compare-expression //G relational-expression > compare-expression //G relational-expression <= compare-expression //G relational-expression >= compare-expression //G auto relational_expression(bool allow_relational_comparison = true) { if (allow_relational_comparison) { return binary_expression ( [](token const& t){ return t.type() == lexeme::Less || t.type() == lexeme::LessEq || t.type() == lexeme::Greater || t.type() == lexeme::GreaterEq; }, [this]{ return compare_expression(); } ); } else { return binary_expression ( [](token const& t){ return false; }, [this]{ return compare_expression(); } ); } } //G equality-expression: //G relational-expression //G equality-expression == relational-expression //G equality-expression != relational-expression //G auto equality_expression(bool allow_relational_comparison = true) { return binary_expression ( [](token const& t){ return t.type() == lexeme::EqualComparison || t.type() == lexeme::NotEqualComparison; }, [=,this]{ return relational_expression(allow_relational_comparison); } ); } //G bit-and-expression: //G equality-expression //G bit-and-expression & equality-expression //G auto bit_and_expression(bool allow_relational_comparison = true) { return binary_expression ( [](token const& t){ return t.type() == lexeme::Ampersand; }, [=,this]{ return equality_expression(allow_relational_comparison); } ); } //G bit-xor-expression: //G bit-and-expression //G bit-xor-expression & bit-and-expression //G auto bit_xor_expression(bool allow_relational_comparison = true) { return binary_expression ( [](token const& t){ return t.type() == lexeme::Caret; }, [=,this]{ return bit_and_expression(allow_relational_comparison); } ); } //G bit-or-expression: //G bit-xor-expression //G bit-or-expression & bit-xor-expression //G auto bit_or_expression(bool allow_relational_comparison = true) { return binary_expression ( [](token const& t){ return t.type() == lexeme::LogicalOr; }, [=,this]{ return bit_xor_expression(allow_relational_comparison); } ); } //G logical-and-expression: //G bit-or-expression //G logical-and-expression && bit-or-expression //G auto logical_and_expression(bool allow_relational_comparison = true) { return binary_expression ( [](token const& t){ return t.type() == lexeme::LogicalAnd; }, [=,this]{ return bit_or_expression(allow_relational_comparison); } ); } // constant-expression: // don't need intermediate production, just use: // conditional-expression: // don't need intermediate production, just use: //G logical-or-expression: //G logical-and-expression //G logical-or-expression || logical-and-expression //G auto logical_or_expression(bool allow_relational_comparison = true) { return binary_expression ( [](token const& t){ return t.type() == lexeme::LogicalOr; }, [=,this]{ return logical_and_expression(allow_relational_comparison); } ); } //G assignment-expression: //G logical-or-expression //G assignment-expression assignment-operator assignment-expression //G auto assignment_expression(bool allow_relational_comparison = true) -> std::unique_ptr { return binary_expression ( [](token const& t){ return is_assignment_operator(t.type()); }, [=,this]{ return logical_or_expression(allow_relational_comparison); } ); } //G expression: // eliminated condition: - use expression: //G assignment-expression //GTODO try expression //G auto expression(bool allow_relational_comparison = true) -> std::unique_ptr { auto n = std::make_unique(); if (!(n->expr = assignment_expression(allow_relational_comparison))) { return {}; } return n; } //G expression-list: //G expression //G expression-list , expression //G auto expression_list(source_position open_paren, bool inside_initializer = false) -> std::unique_ptr { auto pass = passing_style::in; auto n = std::make_unique(); n->open_paren = open_paren; n->inside_initializer = inside_initializer; if (curr().type() == lexeme::Identifier && curr() == "out") { pass = passing_style::out; next(); } else if (curr().type() == lexeme::Identifier && curr() == "move") { pass = passing_style::move; next(); } auto x = expression(); // If this is an empty expression_list, we're done if (!x) { return n; } // Otherwise remember the first expression n->expressions.push_back( { pass, std::move(x) } ); // and see if there are more... while (curr().type() == lexeme::Comma) { next(); pass = passing_style::in; if (curr().type() == lexeme::Identifier && curr() == "out") { pass = passing_style::out; next(); } else if (curr().type() == lexeme::Identifier && curr() == "move") { pass = passing_style::move; next(); } auto expr = expression(); if (!expr) { error("invalid text in expression list"); return {}; } n->expressions.push_back( { pass, std::move(expr) } ); } return n; } //G unqualified-id: //G const-opt identifier //G const-opt template-id //GTODO operator-function-id //G //G template-id: //G identifier < template-argument-list-opt > //G //G template-argument-list: //G template-argument-list , template-argument //G //G template-argument: //G expression //G id-expression //G auto unqualified_id() -> std::unique_ptr { // Handle the identifier if (curr().type() != lexeme::Identifier && curr().type() != lexeme::Keyword) // 'const', and fundamental types that are keywords { return {}; } auto n = std::make_unique(); if (curr().type() == lexeme::Keyword && curr() == "const") { n->const_qualifier = &curr(); next(); } n->identifier = &curr(); next(); // Handle the template-argument-list if there is one if (curr().type() == lexeme::Less) { // Remember current position, in case this < is isn't a template argument list auto start_pos = pos; // And since we'll do this in two places, factor it into a local function auto back_out_template_arg_list = [&]{ // Aha, this wasn't a template argument list after all, // so back out just that part and return the identifier n->open_angle = source_position{}; n->template_args.clear(); pos = start_pos; }; n->open_angle = curr().position(); next(); unqualified_id_node::term term; do { if (auto e = expression(false)) { // disallow unparenthesized relational comparisons in template args term.arg = std::move(e); } else if (auto i = id_expression()) { term.arg = std::move(i); } else { back_out_template_arg_list(); return n; } n->template_args.push_back( std::move(term) ); } // Use the lambda trick to jam in a "next" clause while ( curr().type() == lexeme::Comma && [&]{term.comma = curr().position(); next(); return true;}() ); // When this is rewritten in Cpp2, it will be: // while curr().type() == lexeme::Comma // next term.comma = curr().position(); if (curr().type() != lexeme::Greater) { back_out_template_arg_list(); return n; } n->close_angle = curr().position(); next(); } return n; } //G qualified-id: //G nested-name-specifier unqualified-id //G member-name-specifier unqualified-id //G //G nested-name-specifier: //G :: //G unqualified-id :: //G //G member-name-specifier: //G unqualified-id . //G auto qualified_id() -> std::unique_ptr { auto n = std::make_unique(); auto term = qualified_id_node::term{nullptr}; // Handle initial :: if present, else the first scope_op will be null if (curr().type() == lexeme::Scope) { term.scope_op = &curr(); next(); } // Remember current position, because we need to look ahead to the next :: auto start_pos = pos; // If we don't get a first id, or if the next thing isn't :: or ., // back out and report unsuccessful term.id = unqualified_id(); if (!term.id || curr().type() != lexeme::Scope) { pos = start_pos; // backtrack return {}; } // Reject "std" :: "move" / "forward" assert (term.id->identifier); auto first_uid_was_std = (*term.id->identifier == "std"); auto first_time_through_loop = true; n->ids.push_back( std::move(term) ); assert (curr().type() == lexeme::Scope); while (curr().type() == lexeme::Scope) { auto term = qualified_id_node::term{ &curr() }; next(); term.id = unqualified_id(); if (!term.id) { error("invalid text in qualified name"); return {}; } assert (term.id->identifier); if (first_time_through_loop && term.scope_op->type() == lexeme::Scope) { if (*term.id->identifier == "move") { error("std::move is not needed in Cpp2 - use 'move' parameters/arguments instead", false); return {}; } else if (*term.id->identifier == "forward") { error("std::forward is not needed in Cpp2 - use 'forward' parameters/arguments instead", false); return {}; } first_time_through_loop = false; } n->ids.push_back( std::move(term) ); } return n; } //G id-expression //G unqualified-id //G qualified-id //G auto id_expression() -> std::unique_ptr { auto n = std::make_unique(); if (auto id = qualified_id()) { n->pos = id->position(); n->id = std::move(id); assert (n->id.index() == id_expression_node::qualified); return n; } if (auto id = unqualified_id()) { n->pos = id->position(); n->id = std::move(id); assert (n->id.index() == id_expression_node::unqualified); return n; } return {}; } //G expression-statement: //G expression ; //G expression //G auto expression_statement(bool semicolon_required) -> std::unique_ptr { auto n = std::make_unique(); if (!(n->expr = expression())) { return {}; } if (semicolon_required && curr().type() != lexeme::Semicolon && peek(-1)->type() != lexeme::Semicolon // this last peek(-1)-condition is a hack (? or is it just // maybe elegant? I'm torn) so that code like // // callback := :(inout x:_) = x += "suffix"; ; // // doesn't need the redundant semicolon at the end of a decl... // there's probably a cleaner way to do it, but this works and // it doesn't destabilize any regression tests ) { error("expected ; at end of statement"); return {}; } if (curr().type() == lexeme::Semicolon) { n->has_semicolon = true; next(); } return n; } //G selection-statement: //G if constexpr-opt expression compound-statement //G if constexpr-opt expression compound-statement else compound-statement //G auto selection_statement() -> std::unique_ptr { if (curr().type() != lexeme::Keyword || curr() != "if") { return {}; } auto n = std::make_unique(); n->identifier = &curr(); next(); if (curr().type() == lexeme::Keyword && curr() == "constexpr") { n->is_constexpr = true; next(); } if (auto e = expression()) { n->expression = std::move(e); } else { error("invalid if condition"); return {}; } if (auto s = compound_statement()) { n->true_branch = std::move(s); } else { error("invalid if branch body"); return {}; } if (curr().type() != lexeme::Keyword || curr() != "else") { // Add empty else branch to simplify processing elsewhere // Note: Position (0,0) signifies it's implicit (no source location) n->false_branch = std::make_unique( source_position(0,0) ); } else { n->else_pos = curr().position(); next(); if (auto s = compound_statement()) { n->false_branch = std::move(s); n->has_source_false_branch = true; } else { error("invalid else branch body"); return {}; } } return n; } //G return-statement: //G return expression-opt ; //G auto return_statement() -> std::unique_ptr { if (curr().type() != lexeme::Keyword || curr() != "return") { return {}; } auto n = std::make_unique(); n->identifier = &curr(); next(); // If there's no optional return expression, we're done if (curr().type() == lexeme::Semicolon) { next(); return n; } // Handle the return expression auto x = expression(); if (!x) { error("invalid return expression"); return {}; } n->expression = std::move(x); // Final semicolon if (curr().type() != lexeme::Semicolon) { error("missing ; after return"); next(); return {}; } next(); return n; } //G iteration-statement: //G while logical-or-expression next-clause-opt compound-statement //G do compound-statement while logical-or-expression next-clause-opt ; //G for expression next-clause-opt do unnamed-declaration //G //G next-clause: //G next assignment-expression //G auto iteration_statement() -> std::unique_ptr { if (curr().type() != lexeme::Keyword || (curr() != "while" && curr() != "do" && curr() != "for") ) { return {}; } auto n = std::make_unique(); n->identifier = &curr(); next(); //----------------------------------------------------------------- // We'll do these same things in different orders, // so extract them into local functions... auto handle_optional_next_clause = [&]() -> bool { if (curr() != "next") { return true; // absent next clause is okay } next(); // don't bother remembering "next" token, shouldn't need its position info auto next = assignment_expression(); if (!next) { error("invalid expression after 'next'"); return false; } n->next_expression = std::move(next); return true; }; auto handle_logical_expression = [&]() -> bool { auto x = logical_or_expression(); if (!x) { error("a loop must have a valid conditional expression"); return false; } n->condition = std::move(x); return true; }; auto handle_compound_statement = [&]() -> bool { auto s = compound_statement(); if (!s) { error("invalid while loop body"); return false; } n->statement= std::move(s); return true; }; //----------------------------------------------------------------- // Handle "while" // if (*n->identifier == "while") { if (!handle_logical_expression ()) { return {}; } if (!handle_optional_next_clause()) { return {}; } if (!handle_compound_statement ()) { return {}; } return n; } // Handle "do" // else if (*n->identifier == "do") { if (!handle_compound_statement ()) { return {}; } if (curr() != "while") { error("do loop body must be followed by 'while'"); return {}; } next(); if (!handle_logical_expression ()) { return {}; } if (!handle_optional_next_clause()) { return {}; } if (curr().type() != lexeme::Semicolon) { error("missing ; after do..while loop condition"); next(); return {}; } next(); return n; } // Handle "for" // else if (*n->identifier == "for") { n->range = expression(); if (!n->range) { error("expected valid range expression after 'for'"); return {}; } if (!handle_optional_next_clause()) { return {}; } if (curr() != "do") { error("'for each of' must be followed by 'do'"); return {}; } next(); n->body = unnamed_declaration(curr().position()); auto func = n->body ? std::get_if(&n->body->type) : nullptr; if (!n->body || n->body->identifier || !func || !*func || std::ssize((**func).parameters->parameters) != 1 || (**func).returns.index() != function_type_node::empty ) { error("for..do loop body must be an unnamed function taking a single parameter and returning nothing", false); return {}; } return n; } assert(!"compiler bug: unexpected case"); return {}; } //G alternative: //G alt-name-opt is-type-constraint = statement //G alt-name-opt as-type-cast = statement //GTODO alt-name-opt is-expression-constraint = statement //G //G is-type-constraint //G is id-expression //G //G as-type-cast //G as id-expression //G //G alt-name: //G unqualified-id : //G auto alternative() -> std::unique_ptr { auto n = std::make_unique(); //// Check for an optional name (just one unqualified-id, no decomposition yet) //if (curr() != "is" && curr() != "as") { // if (auto id = unqualified_id()) { // n->name = std::move(id); // } // else { // error("expected unqualified-id, 'is', or 'as' to start an inspect alternative"); // return {}; // } // if (curr().type() != lexeme::Colon) { // error("expected : after the introduced name in an inspect alternative"); // return {}; // } // next(); //} // Now we should be as "is" or "as" // (initial partial implementation, just "is/as id-expression") if (curr() != "is" && curr() != "as") { return {}; } n->is_as_keyword = &curr(); next(); if (auto id = id_expression()) { n->id_expression = std::move(id); } else { error("expected id-expression after 'is' in inspect alternative"); return {}; } if (curr().type() != lexeme::Assignment) { error("expected = at start of inspect alternative body"); return {}; } n->equal_sign = curr().position(); next(); if (auto s = statement(true, n->equal_sign)) { n->statement = std::move(s); } else { error("expected statement after = in inspect alternative"); return {}; } return n; } //G inspect-expression: //G inspect constexpr-opt expression { alternative-seq-opt } //G inspect constexpr-opt expression -> id-expression { alternative-seq-opt } //G //G alternative-seq: //G alternative //G alternative-seq alternative //G auto inspect_expression(bool is_expression) -> std::unique_ptr { if (curr() != "inspect") { return {}; } if (!is_expression) { errors.emplace_back( curr().position(), "(temporary alpha limitation) cppfront is still learning 'inspect' - only inspect expressions are currently supported" ); return {}; } auto n = std::make_unique(); n->identifier = &curr(); next(); if (curr() == "constexpr") { n->is_constexpr = true; next(); } if (auto e = expression()) { n->expression = std::move(e); } else { error("invalid inspect expression"); return {}; } // Handle the optional explicit return type if (curr().type() == lexeme::Arrow) { if (!is_expression) { error("an inspect statement cannot have an explicit return type (whereas an inspect expression must have one)"); return {}; } next(); if (curr().type() == lexeme::LeftParen) { error("multiple/named returns are not currently allowed for inspect"); return {}; } auto id = id_expression(); if (!id) { error("expected a valid inspect return type after ->"); return {}; } n->result_type = std::move(id); } else if (is_expression) { error("an inspect expression must have an explicit '-> result_type'"); return {}; } // Now do the inspect body if (curr().type() != lexeme::LeftBrace) { error("expected { at start of inspect body"); return {}; } n->open_brace = curr().position(); next(); while (curr().type() != lexeme::RightBrace) { auto a = alternative(); if (!a) { error("invalid alternative in inspect"); return {}; } if (is_expression && a->statement->statement.index() != statement_node::expression) { error("an inspect expression alternative must be just an expression " "(not a braced block) that will be used as the value of the inspect expression"); return {}; } n->alternatives.push_back( std::move(a) ); } n->close_brace = curr().position(); next(); if (n->alternatives.empty()) { error("inspect body cannot be empty - add at least one alternative"); return {}; } return n; } //G statement: //G let parameter-list statement //G selection-statement //G inspect-expression //G return-statement //G iteration-statement //G compound-statement //G declaration-statement //G expression-statement //G contract // //GTODO jump-statement //GTODO try-block //G auto statement(bool semicolon_required, source_position equal_sign = source_position{}) -> std::unique_ptr { auto n = std::make_unique(); // Handle optional "let" before any statement if (curr() == "let" && peek(1) && *peek(1) == "(") { n->let = &curr(); next(); // now on the open paren if (auto params = parameter_declaration_list()) { n->let_params = std::move(params); } else { error("invalid parameter list after 'let'"); return {}; } } // Now handle the rest of the statement if (auto s = selection_statement()) { n->statement = std::move(s); assert (n->statement.index() == statement_node::selection); return n; } else if (auto i = inspect_expression(false)) { n->statement = std::move(i); assert (n->statement.index() == statement_node::inspect); return n; } else if (auto s = return_statement()) { n->statement = std::move(s); assert (n->statement.index() == statement_node::return_); return n; } else if (auto s = iteration_statement()) { n->statement = std::move(s); assert (n->statement.index() == statement_node::iteration); return n; } else if (auto s = compound_statement(equal_sign)) { n->statement = std::move(s); assert (n->statement.index() == statement_node::compound); return n; } else if (auto s = declaration()) { n->statement = std::move(s); assert (n->statement.index() == statement_node::declaration); return n; } else if (auto s = expression_statement(semicolon_required)) { n->statement = std::move(s); assert (n->statement.index() == statement_node::expression); return n; } else if (auto s = contract()) { if (*s->kind != "assert") { error("only 'assert' contracts are allowed at statement scope"); return {}; } n->statement = std::move(s); assert (n->statement.index() == statement_node::contract); return n; } else { //next(); return {}; } } //G compound-statement: //G { statement-seq-opt } //G //G statement-seq: //G statement //G statement-seq statement //G auto compound_statement(source_position equal_sign = source_position{}) -> std::unique_ptr { if (curr().type() != lexeme::LeftBrace) { return {}; } auto n = std::make_unique(); // In the case where this is a declaration initializer with // = { // on the same line, we want to remember our start position // as where the = was, not where the { was if (equal_sign.lineno == curr().position().lineno) { n->open_brace = equal_sign; } else { n->open_brace = curr().position(); } next(); auto s = std::unique_ptr(); while (curr().type() != lexeme::RightBrace) { auto s = statement(true); if (!s) { error("invalid statement in compound-statement"); return {}; } n->statements.push_back( std::move(s) ); } n->close_brace = curr().position(); next(); return n; } //G parameter-declaration: //G parameter-direction-opt declaration //G //G parameter-direction: one of //G in copy inout out move forward //G //G this-specifier: //G implicit //G virtual //G override //G final //G auto parameter_declaration( bool returns = false ) -> std::unique_ptr { auto n = std::make_unique(); n->pass = returns ? passing_style::out : passing_style::in; n->pos = curr().position(); if (curr().type() == lexeme::Identifier) { if (curr() == "in") { if (returns) { error("a return value cannot be 'in'"); return {}; } n->pass = passing_style::in; next(); } else if (curr() == "copy") { if (returns) { error("a return value cannot be 'copy'"); return {}; } n->pass = passing_style::copy; next(); } else if (curr() == "inout") { if (returns) { error("a return value cannot be 'inout'"); return {}; } n->pass = passing_style::inout; next(); } else if (curr() == "out") { n->pass = passing_style::out; next(); } else if (curr() == "move") { if (returns) { error("a return value cannot be 'move' (it is implicitly 'move'-out)"); return {}; } n->pass = passing_style::move; next(); } else if (curr() == "forward") { n->pass = passing_style::forward; next(); } } if (curr().type() == lexeme::Identifier) { if (curr() == "implicit") { n->mod = parameter_declaration_node::modifier::implicit; next(); } else if (curr() == "virtual") { n->mod = parameter_declaration_node::modifier::virtual_; next(); } else if (curr() == "override") { n->mod = parameter_declaration_node::modifier::override_; next(); } else if (curr() == "final") { n->mod = parameter_declaration_node::modifier::final_; next(); } } if (!(n->declaration = declaration(false))) { return {}; } return n; } //G parameter-declaration-list //G ( parameter-declaration-seq-opt ) //G //G parameter-declaration-seq: //G parameter-declaration //G parameter-declaration-seq , parameter-declaration //G auto parameter_declaration_list( bool returns = false ) -> std::unique_ptr { if (curr().type() != lexeme::LeftParen) { return {}; } auto n = std::make_unique(); n->pos_open_paren = curr().position(); next(); auto param = std::make_unique(); while ((param = parameter_declaration(returns)) != nullptr) { n->parameters.push_back( std::move(param) ); if (curr().type() == lexeme::RightParen) { break; } else if (curr().type() != lexeme::Comma) { error("expected , in parameter list"); return {}; } next(); } if (curr().type() != lexeme::RightParen) { error("invalid parameter list"); next(); return {}; } n->pos_close_paren = curr().position(); next(); return n; } //G contract: //G [ [ contract-kind id-expression-opt : logical-or-expression ] ] //G [ [ contract-kind id-expression-opt : logical-or-expression , string-literal ] ] //G //G contract-kind: one of //G pre post assert //G auto contract() -> std::unique_ptr { // Note: For now I'm using [[ ]] mainly so that existing Cpp1 syntax highlighters // don't get confused... I initially implemented single [ ], but then // my editor's default Cpp1 highlighter didn't colorize the following // multiline // comment correctly as a comment // If there's no [ [ then this isn't a contract if (curr().type() != lexeme::LeftBracket || !peek(1) || peek(1)->type() != lexeme::LeftBracket) { return {}; } auto n = std::make_unique(curr().position()); auto guard = capture_groups_stack_guard(this, &n->captures); next(); next(); if (curr() != "pre" && curr() != "post" && curr() != "assert") { error("[ begins a contract and must be followed by 'pre', 'post', or 'assert'"); return {}; } n->kind = &curr(); next(); if (auto id = id_expression()) { n->group = std::move(id); } if (curr().type() != lexeme::Colon) { error("expected : before the contract condition"); return {}; } next(); auto condition = logical_or_expression(); if (!condition) { error("invalid contract condition"); return {}; } n->condition = std::move(condition); // Now check for the optional string message if (curr().type() == lexeme::Comma) { next(); if (curr().type() != lexeme::StringLiteral) { error("expected contract message string"); return {}; } n->message = &curr(); next(); } if (curr().type() != lexeme::RightBracket || !peek(1) || peek(1)->type() != lexeme::RightBracket) { error("expected ]] at the end of the contract"); return {}; } next(); next(); return n; } //G function-type: //G parameter-declaration-list throws-specifier-opt return-list-opt contract-seq-opt //G //G throws-specifier: //G throws //G //G return-list: //G -> id-expression //G -> parameter_declaration_list //G //G contract-seq: //G contract //G contract-seq contract //G auto function_type() -> std::unique_ptr { auto n = std::make_unique(); // Parameters auto parameters = parameter_declaration_list(); if (!parameters) { return {}; } n->parameters = std::move(parameters); // Optional "throws" if (curr().type() == lexeme::Keyword && curr() == "throws") { n->throws = true; next(); } // Optional returns if (curr().type() == lexeme::Arrow) { next(); if (auto t = id_expression()) { auto is_void = false; if (auto u = std::get_if(&t->id)) { assert ((*u)->identifier); is_void = *(*u)->identifier == "void"; } if (!is_void) { n->returns = std::move(t); } } else if (auto returns_list = parameter_declaration_list(true)) { if (std::ssize(returns_list->parameters) < 1) { error("an explicit return value list cannot be empty"); return {}; } n->returns = std::move(returns_list); } else { error("missing function return after ->"); return {}; } } // Pre/post conditions while (auto c = contract()) { if (*c->kind != "pre" && *c->kind != "post") { error("only 'pre' and 'post' contracts are allowed on functions"); return {}; } n->contracts.push_back( std::move(c) ); } return n; } //G unnamed-declaration: //G : function-type = statement //G : id-expression-opt = statement //G : id-expression //G auto unnamed_declaration(source_position pos, bool semicolon_required = true, bool captures_allowed = false) -> std::unique_ptr { auto deduced_type = false; // The next token must be : if (curr().type() != lexeme::Colon) { return {}; } next(); auto n = std::make_unique(); n->pos = pos; auto guard = captures_allowed ? make_unique(this, &n->captures) : std::unique_ptr() ; // Remember current position, because we need to look ahead auto start_pos = pos; // Next is an an optional type // It could be a function type, declaring a function if (auto t = function_type()) { n->type = std::move(t); assert (n->type.index() == declaration_node::function); } // Or a pointer to a type, declaring a pointer object else if (curr().type() == lexeme::Multiply) { n->pointer_declarator = &curr(); next(); if (auto t = id_expression()) { n->type = std::move(t); assert (n->type.index() == declaration_node::object); } } // Or just a type, declaring a non-pointer object else if (auto t = id_expression()) { n->type = std::move(t); assert (n->type.index() == declaration_node::object); } // Or nothing, declaring an object of deduced type, // which we'll represent using an empty id-expression else { n->type = std::make_unique(); assert (n->type.index() == declaration_node::object); deduced_type = true; } // Next is optionally = followed by an initializer // If there is no = if (curr().type() != lexeme::Assignment) { if (deduced_type) { error("a deduced type must have an = initializer"); return {}; } if (n->type.index() == declaration_node::function) { error("missing = before function body"); return {}; } // Then there may be a semicolon // If there is a semicolon, eat it if (curr().type() == lexeme::Semicolon) { next(); } // But if there isn't one and it was required, diagnose an error else if (semicolon_required) { error("missing semicolon at end of declaration"); return {}; } } // There was an =, so eat it and continue else { n->equal_sign = curr().position(); next(); if (n->pointer_declarator) { if (curr() == "nullptr" || isdigit(std::string_view(curr())[0]) || (curr() == "(" && peek(1) && *peek(1) == ")") ) { error("pointer cannot be initialized to null or int - leave it uninitialized and then set it to a non-null value when you have one"); violates_lifetime_safety = true; throw std::runtime_error("null initialization detected"); } } if (!(n->initializer = statement(semicolon_required, n->equal_sign))) { error("ill-formed initializer"); next(); return {}; } } n->decl_end = peek(-1)->position(); return n; } //G declaration: //G identifier unnamed-declaration //G auto declaration(bool semicolon_required = true) -> std::unique_ptr { if (done()) { return {}; } // Remember current position, because we need to look ahead auto start_pos = pos; auto id = unqualified_id(); if (!id) { return {}; } auto n = unnamed_declaration(start_pos, semicolon_required); if (!n) { pos = start_pos; // backtrack return {}; } n->identifier = std::move(id); return n; } //G declaration-seq: //G declaration //G declaration-seq declaration //G //G translation-unit: //G declaration-seq-opt // auto translation_unit() -> std::unique_ptr { auto n = std::make_unique(); for (auto d = declaration(); d; d = declaration()) { n->declarations.push_back( std::move(d) ); } return n; } }; //----------------------------------------------------------------------- // // Common parts for printing visitors // //----------------------------------------------------------------------- // struct printing_visitor { //----------------------------------------------------------------------- // Constructor: remember a stream to write to // std::ostream& o; printing_visitor(std::ostream& out) : o{out} { } //----------------------------------------------------------------------- // pre: Get an indentation prefix // inline static int indent_spaces = 2; inline static std::string indent_str = std::string( 1024, ' ' ); // "1K should be enough for everyone" auto pre(int indent) -> std::string_view { assert (indent >= 0); return { indent_str.c_str(), as( std::min( indent*indent_spaces, as(std::ssize(indent_str))) ) }; } }; //----------------------------------------------------------------------- // // Visitor for printing a parse tree // //----------------------------------------------------------------------- // class parse_tree_printer : printing_visitor { using printing_visitor::printing_visitor; std::vector current_expression_list_term = {}; public: auto start(token const& n, int indent) -> void { o << pre(indent) << n.to_string() << "\n"; } auto start(expression_node const& n, int indent) -> void { o << pre(indent) << "expression\n"; // If we are in an expression-list if (!current_expression_list_term.empty()) { if (current_expression_list_term.back()->pass == passing_style::out) { o << pre(indent+1) << "out\n"; } ++current_expression_list_term.back(); } } auto start(expression_list_node const& n, int indent) -> void { // We're going to use the pointer as an iterator if (!n.expressions.empty()) { current_expression_list_term.push_back( &n.expressions[0] ); } else { current_expression_list_term.push_back( nullptr ); } o << pre(indent) << "expression-list\n"; } auto end(expression_list_node const& n, int indent) -> void { // If we're ending an expression list node, our pointer should be // pointing to one past the end of the expressions if (current_expression_list_term.back() == nullptr) { assert(n.expressions.empty()); } assert( current_expression_list_term.back() == nullptr || current_expression_list_term.back() == &n.expressions[0] + n.expressions.size() ); current_expression_list_term.pop_back(); } auto start(primary_expression_node const& n, int indent) -> void { o << pre(indent) << "primary-expression\n"; } auto start(prefix_expression_node const& n, int indent) -> void { o << pre(indent) << "prefix-expression\n"; } template auto start(binary_expression_node const& n, int indent) -> void { o << pre(indent) << Name.value << "-expression\n"; } auto start(expression_statement_node const& n, int indent) -> void { o << pre(indent) << "expression-statement\n"; } auto start(postfix_expression_node const& n, int indent) -> void { o << pre(indent) << "postfix-expression\n"; } auto start(unqualified_id_node const& n, int indent) -> void { o << pre(indent) << "unqualified-id\n"; } auto start(qualified_id_node const& n, int indent) -> void { o << pre(indent) << "qualified-id\n"; } auto start(id_expression_node const& n, int indent) -> void { o << pre(indent) << "id-expression\n"; } auto start(statement_node const& n, int indent) -> void { o << pre(indent) << "statement\n"; } auto start(compound_statement_node const& n, int indent) -> void { o << pre(indent) << "compound-statement\n"; } auto start(selection_statement_node const& n, int indent) -> void { o << pre(indent) << "selection-statement\n"; o << pre(indent+1) << "is_constexpr: " << as(n.is_constexpr) << "\n"; } auto start(alternative_node const& n, int indent) -> void { o << pre(indent) << "alternative\n"; } auto start(inspect_expression_node const& n, int indent) -> void { o << pre(indent) << "inspect-expression\n"; o << pre(indent+1) << "is_constexpr: " << as(n.is_constexpr) << "\n"; } auto start(return_statement_node const& n, int indent) -> void { o << pre(indent) << "return-statement\n"; } auto start(iteration_statement_node const& n, int indent) -> void { o << pre(indent) << "iteration-statement\n"; assert(n.identifier); o << pre(indent+1) << "identifier: " << std::string_view(*n.identifier) << "\n"; } auto start(contract_node const& n, int indent) -> void { o << pre(indent) << "contract\n"; assert(n.kind); o << pre(indent+1) << "kind: " << std::string_view(*n.kind) << "\n"; if (n.message) { o << pre(indent+1) << "message: " << std::string_view(*n.message) << "\n"; } if (!n.captures.empty()) { o << pre(indent+1) << "captures: " << n.captures.size() << "\n"; } } auto start(function_type_node const& n, int indent) -> void { o << pre(indent) << "function\n"; o << pre(indent+1) << "throws: " << as(n.throws) << "\n"; } auto start(function_returns_tag const& n, int indent) -> void { o << pre(indent) << "function returns\n"; } auto start(declaration_node const& n, int indent) -> void { o << pre(indent) << "declaration\n"; if (!n.captures.empty()) { o << pre(indent+1) << "captures: " << n.captures.size() << "\n"; } } auto start(parameter_declaration_node const& n, int indent) -> void { o << pre(indent) << "parameter-declaration\n"; o << pre(indent+1); switch (n.pass) { break;case passing_style::in : o << "in"; break;case passing_style::copy : o << "copy"; break;case passing_style::inout : o << "inout"; break;case passing_style::out : o << "out"; break;case passing_style::move : o << "move"; break;case passing_style::forward: o << "forward"; break;default: ; } o << pre(indent+1); switch (n.mod) { break;case parameter_declaration_node::modifier::implicit : o << "implicit"; break;case parameter_declaration_node::modifier::virtual_ : o << "virtual"; break;case parameter_declaration_node::modifier::override_ : o << "override"; break;case parameter_declaration_node::modifier::final_ : o << "final"; break;default: ; } o << "\n"; assert( n.declaration ); } auto start(parameter_declaration_list_node const& n, int indent) -> void { o << pre(indent) << "parameter-declaration-list\n"; } auto start(translation_unit_node const& n, int indent) -> void { o << pre(indent) << "translation-unit\n"; } auto start(auto const&, int indent) -> void { o << pre(indent) << "UNRECOGNIZED -- FIXME\n"; } auto end(auto const&, int indent) -> void { // Ignore other node types } }; //----------------------------------------------------------------------- // // Visitor for moving tokens that are to the right on the same line // and shifting their positions left 'n' spaces - used only at the // end when lowering to Cpp1, as a convenient way to adjust for other // positions shifts we create (e.g., moving some operators to prefix // notation, or inserting "std::move" prefixes) // //----------------------------------------------------------------------- // class adjust_remaining_token_columns_on_this_line_visitor { source_position line_to_adjust_pos; colno_t col_offset; public: adjust_remaining_token_columns_on_this_line_visitor( source_position start_pos, colno_t offset ) : line_to_adjust_pos{start_pos} , col_offset{offset} { } auto start(token& n, int indent) -> void { if (n.position().lineno == line_to_adjust_pos.lineno && n.position().colno >= line_to_adjust_pos.colno ) { n.position_col_shift(col_offset); } } auto start(auto const&, int indent) -> void { // Ignore other node types } auto end(auto const&, int indent) -> void { // Ignore other node types } }; } #endif