// 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 = *= /= %= += -= >>= 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 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
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
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