// Copyright 2022-2024 Herb Sutter
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// Part of the Cppfront Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://github.com/hsutter/cppfront/blob/main/LICENSE for license information.
//===========================================================================
// Parser
//===========================================================================
#ifndef CPP2_PARSE_H
#define CPP2_PARSE_H
#include "lex.h"
namespace cpp2 {
auto violates_lifetime_safety = false;
//-----------------------------------------------------------------------
// Operator categorization
//
//G prefix-operator:
//G one of '!' '-' '+'
//GT parameter-direction
//G
auto is_prefix_operator(token const& tok)
-> bool
{
switch (tok.type()) {
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:
case lexeme::Ellipsis:
case lexeme::EllipsisLess:
case lexeme::EllipsisEqual:
return true;
break;default:
return false;
}
}
//G assignment-operator:
//G one of '=' '*=' '/=' '%=' '+=' '-=' '>>=' ' each node with a capture_group should declare it as the first member
// before any other node that could own a postfix_expression that could
// point back up to that capture_group
}
auto prefix_expression_node::to_string() const
-> std::string
{
auto ret = std::string{};
for (auto const& x : ops) {
assert (x);
ret += x->as_string_view();
}
assert (expr);
return ret + expr->to_string();
}
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 type_id_node;
struct template_args_tag { };
struct template_argument
{
enum active : u8 { empty=0, expression, type_id };
source_position comma;
std::variant<
std::monostate,
std::unique_ptr,
std::unique_ptr
> arg;
// The type needs to be movable
// The copy ctor+operator are implicitly deleted due to the std::unique_ptr member
// Because a forward-declared type is used in a std::unique_ptr as a member an out-of-line dtor is necessary
// Because of the OOL dtor together with the fact that the copy ctor+operator are deleted
// the move ctor+operator need to be explicitly defaulted
// As a result the default constructor also needs to be explicitly defaulted
template_argument() = default;
template_argument(template_argument&&) = default;
template_argument& operator=(template_argument&&) = default;
~template_argument();
auto to_string() const
-> std::string;
};
// Used by functions that must return a reference to an empty arg list
inline std::vector const no_template_args;
struct unqualified_id_node
{
token const* identifier = {}; // required
// These are used only if it's a template-id
source_position open_angle = {};
source_position close_angle = {};
std::vector template_args;
auto template_arguments() const
-> std::vector const&
{
return template_args;
}
auto get_token() const
-> token const*
{
if (open_angle == source_position{}) {
assert (identifier);
return identifier;
}
// else
return {};
}
auto to_string() const
-> std::string;
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);
if (open_angle != source_position{}) {
// Inform the visitor that this is a template args list
v.start(template_args_tag{}, depth);
assert(open_angle != source_position{});
assert(close_angle != source_position{});
assert(template_args.empty()
|| template_args.front().comma == source_position{});
for (auto& a : template_args) {
try_visit(a.arg, v, depth+1);
try_visit(a.arg, v, depth+1);
}
v.end(template_args_tag{}, depth);
}
v.end(*this, depth);
}
};
struct qualified_id_node
{
struct term {
token const* scope_op;
std::unique_ptr id = {};
term( token const* o ) : scope_op{o} { }
};
std::vector ids;
auto template_arguments() const
-> std::vector const&
{
return ids.back().id->template_arguments();
}
auto get_token() const
-> token const*
{
if (
std::ssize(ids) == 1
&& !ids.front().scope_op
)
{
assert (ids.front().id);
return ids.front().id->get_token();
}
// else
return {};
}
auto to_string() const
-> std::string
{
auto ret = std::string{};
for (auto& term : ids) {
if (term.scope_op) {
ret += term.scope_op->as_string_view();
}
assert (term.id);
ret += term.id->to_string();
}
return ret;
}
auto get_first_token() const
-> token const*
{
assert (
!ids.empty()
&& ids.front().id
);
return ids.front().id->get_token();
}
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 function_type_node;
struct type_id_node
{
source_position pos;
std::vector pc_qualifiers;
token const* address_of = {};
token const* dereference_of = {};
int dereference_cnt = {};
token const* suspicious_initialization = {};
enum active : u8 { empty=0, postfix, qualified, unqualified, function, keyword };
std::variant<
std::monostate,
std::unique_ptr,
std::unique_ptr,
std::unique_ptr,
std::unique_ptr,
token const*
> id;
std::unique_ptr constraint = {};
// Out-of-line definition of the dtor is necessary due to the forward-declared
// type(s) used in a std::unique_ptr as a member
~type_id_node();
auto is_function_typeid() const
-> bool
{
return id.index() == function;
}
auto is_wildcard() const
-> bool
{
return
id.index() == type_id_node::empty
|| (get_token() && *get_token() == "_")
;
}
auto is_pointer_qualified() const
-> bool
{
for (auto q : pc_qualifiers) {
if (q->type() == lexeme::Multiply) {
return true;
}
}
return false;
}
auto is_concept() const
-> bool
{
auto tok = get_token();
return tok && *tok == "concept";
}
auto template_arguments() const
-> std::vector const&
{
if (id.index() == unqualified) {
return std::get(id)->template_arguments();
}
else if (id.index() != qualified) {
cpp2_default.report_violation("ICE: this type_id has no template arguments");
}
// else
return std::get(id)->template_arguments();
}
auto to_string() const
-> std::string;
auto get_token() const
-> token const*
{
switch (id.index()) {
break;case empty:
return {};
break;case postfix:
return {};
break;case qualified:
return {};
break;case unqualified:
return get(id)->get_token();
break;case function:
return {};
break;case keyword:
return get(id);
break;default:
assert(false && "ICE: invalid type_id state");
}
// else
return {};
}
auto position() const
-> source_position
{
return pos;
}
auto visit(auto& v, int depth)
-> void
{
v.start(*this, depth);
for (auto q : pc_qualifiers) {
v.start(*q, depth+1);
}
try_visit(id, v, depth);
try_visit(id, v, depth);
try_visit(id, v, depth);
try_visit(id, v, depth);
try_visit(id, v, depth);
if (constraint) {
constraint->visit(v, depth + 1);
}
v.end(*this, depth);
}
};
auto unqualified_id_node::to_string() const
-> std::string
{
assert(identifier);
auto ret = identifier->to_string();
if (open_angle != source_position{}) {
auto separator = std::string{"";
}
}
return ret;
}
auto template_argument::to_string() const
-> std::string
{
switch (arg.index()) {
break;case empty:
return {};
break;case expression:
return std::get(arg)->to_string();
break;case type_id:
return std::get(arg)->to_string();
break;default:
assert(false && "ICE: invalid template_argument state");
}
// else
return {};
}
struct is_as_expression_node
{
std::unique_ptr expr;
struct term
{
token const* op = {};
// This is used if *op is a type - can be null
std::unique_ptr type = {};
// This is used if *op is an expression - can be null
std::unique_ptr expr = {};
};
std::vector ops;
// API
//
auto is_fold_expression() const
-> bool
{
// This is a fold-expression if any subexpression
// has an identifier named "..."
return expr->is_fold_expression();
}
auto is_identifier() const
-> bool
{
return ops.empty() && expr->is_identifier();
}
auto is_id_expression() const
-> bool
{
return ops.empty() && expr->is_id_expression();
}
auto is_unqualified_id() const
-> bool
{
return ops.empty() && expr->is_unqualified_id();
}
auto is_expression_list() const
-> bool
{
return ops.empty() && expr->is_expression_list();
}
auto get_expression_list() const
-> expression_list_node const*
{
if (is_expression_list()) {
return expr->get_expression_list();
}
return {};
}
auto is_literal() const
-> bool
{
return get_literal();
}
auto get_literal() const
-> literal_node const*
{
if (!ops.empty()) {
return nullptr;
}
// Else
return expr->get_literal();
}
auto get_postfix_expression_node() const
-> postfix_expression_node *
{
assert(expr);
return expr->get_postfix_expression_node();
}
auto get_if_only_a_postfix_expression_node() const
-> postfix_expression_node *
{
if (ops.empty()) {
return expr->get_if_only_a_postfix_expression_node();
}
// Else
return {};
}
auto is_result_a_temporary_variable() const -> bool {
if (ops.empty()) {
assert(expr);
return expr->is_result_a_temporary_variable();
} else {
return true;
}
}
auto to_string() const
-> std::string
{
assert (expr);
auto ret = expr->to_string();
for (auto const& x : ops) {
assert (x.op);
ret += " " + x.op->to_string();
if (x.type) {
ret += " " + x.type->to_string();
}
if (x.expr) {
ret += " " + x.expr->to_string();
}
}
return ret;
}
// Internals
//
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 : ops) {
assert (x.op);
v.start(*x.op, depth+1);
if (x.type) {
x.type->visit(v, depth+1);
}
if (x.expr) {
x.expr->visit(v, depth+1);
}
}
v.end(*this, depth);
}
};
expression_node::expression_node()
{
if (!expression_statement_node::current_expression_statements.empty()) {
my_statement = expression_statement_node::current_expression_statements.back();
}
}
struct id_expression_node
{
source_position pos;
enum active : u8 { empty=0, qualified, unqualified };
std::variant<
std::monostate,
std::unique_ptr,
std::unique_ptr
> id;
auto template_arguments() const
-> std::vector const&
{
if (is_unqualified()) {
return std::get(id)->template_arguments();
}
// else
return std::get(id)->template_arguments();
}
auto is_fold_expression() const
-> bool
{
// This is a fold-expression if any subexpression has
// has an identifier named "..."
auto tok = get_token();
return tok && *tok == "...";
}
auto is_empty() const
-> bool
{
return id.index() == empty;
}
auto is_qualified() const
-> bool
{
return id.index() == qualified;
}
auto is_unqualified() const
-> bool
{
return id.index() == unqualified;
}
auto get_token() const
-> token const*
{
if (id.index() == unqualified) {
return std::get(id)->get_token();
}
// else
return {};
}
auto to_string() const
-> std::string
{
if (id.index() == qualified) {
return std::get(id)->to_string();
}
else if (id.index() == unqualified) {
return std::get(id)->to_string();
}
// 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);
}
};
postfix_expression_node::~postfix_expression_node()
{
if (cap_grp) {
cap_grp->remove(this);
}
}
auto primary_expression_node::is_unqualified_id() const
-> bool
{
if (is_identifier()) {
return true;
}
if (is_id_expression()) {
return std::get(expr)->is_unqualified();
}
return false;
}
auto primary_expression_node::is_fold_expression() const
-> bool
{
// This is a fold-expression if any subexpression has
// has an identifier named "..."
switch (expr.index()) {
break;case identifier:
return *std::get(expr) == "...";
break;case expression_list:
return expression_list_is_fold_expression;
break;case id_expression:
return std::get(expr)->is_fold_expression();
break;default: ; // the others can't contain folds
}
return false;
}
auto postfix_expression_node::get_first_token_ignoring_this() const
-> token const*
{
if (
expr->get_token()
&& *expr->get_token() == "this"
&& std::ssize(ops) == 1
&& (ops[0].op->type() == lexeme::Dot || ops[0].op->type() == lexeme::DotDot)
)
{
return ops[0].id_expr->get_token();
}
return expr->get_token();
}
auto postfix_expression_node::to_string() const
-> std::string
{
assert (expr);
auto ret = expr->to_string();
for (auto const& x : ops) {
assert (x.op);
ret += x.op->as_string_view();
if (x.id_expr) {
ret += x.id_expr->to_string();
}
if (x.expr_list) {
ret += x.expr_list->to_string();
}
}
return ret;
}
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);
}
if (x.last_expr) {
x.last_expr->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;
colno_t body_indent = 0;
compound_statement_node(source_position o = source_position{});
auto get_statements()
-> std::vector
{
auto ret = std::vector{};
for (auto const& stmt : statements) {
ret.push_back( stmt.get() );
}
return ret;
}
auto position() const
-> source_position
{
return open_brace;
}
auto add_statement(
std::unique_ptr&& statement,
int before_pos
)
-> bool
{
// Adopt this statement into our list of statements
statements.insert(
statements.begin() + std::clamp( before_pos, 0, unchecked_narrow(std::ssize(statements)) ),
std::move(statement)
);
return true;
}
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* label = {};
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 statements; // used for "do" and "while", else null
std::unique_ptr range; // used for "for", else null
std::unique_ptr parameter; // used for "for", else null
std::unique_ptr body; // used for "for", else null
// Out-of-line definition of the dtor is necessary due to the forward-declared
// type(s) used in a std::unique_ptr as a member
~iteration_statement_node();
auto position() const
-> source_position
{
if (label) {
return label->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 = {};
// One of these will be used
std::unique_ptr type_id;
std::unique_ptr value;
source_position equal_sign;
std::unique_ptr statement;
// Out-of-line definition of the dtor is necessary due to the forward-declared
// type(s) used in a std::unique_ptr as a member
~alternative_node();
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;
// Out-of-line definition of the dtor is necessary due to the forward-declared
// type(s) used in a std::unique_ptr as a member
~inspect_expression_node();
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
{
// Declared first, because it should outlive any owned
// postfix_expressions that could refer to it
capture_group captures;
source_position open_bracket;
token const* kind = {};
std::unique_ptr group;
std::vector flags;
std::unique_ptr condition;
std::unique_ptr message = {};
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);
}
for (auto const& f : flags) {
f->visit(v, depth+1);
}
assert(condition);
condition->visit(v, depth+1);
if (message) {
message->visit(v, depth+1);
}
v.end(*this, depth);
}
};
struct jump_statement_node
{
token const* keyword;
token const* label;
auto position() const
-> source_position
{
assert(keyword);
return keyword->position();
}
auto visit(auto& v, int depth)
-> void
{
v.start(*this, depth);
if (keyword) {
keyword->visit(v, depth+1);
}
if (label) {
label->visit(v, depth+1);
}
v.end(*this, depth);
}
};
struct using_statement_node
{
token const* keyword = {};
std::unique_ptr id;
auto for_namespace() const
-> bool
{
assert(id);
return id->to_string().ends_with("::_");
}
auto position() const
-> source_position
{
assert(keyword);
return keyword->position();
}
auto visit(auto& v, int depth)
-> void
{
v.start(*this, depth);
assert(id);
id->visit(v, depth+1);
v.end(*this, depth);
}
};
struct parameter_declaration_list_node;
struct statement_node
{
std::unique_ptr parameters;
compound_statement_node* compound_parent = nullptr;
statement_node(compound_statement_node* compound_parent_ = nullptr);
// Out-of-line definition of the dtor is necessary due to the forward-declared
// type(s) used in a std::unique_ptr as a member
~statement_node();
enum active : u8 { expression=0, compound, selection, declaration, return_, iteration, using_, contract, inspect, jump };
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,
std::unique_ptr,
std::unique_ptr
> statement;
bool emitted = false; // a note field that's used during lowering to Cpp1
bool marked_for_removal = false; // for use during metafunctions which may replace members
// API
//
auto is_expression () const -> bool { return statement.index() == expression; }
auto is_compound () const -> bool { return statement.index() == compound; }
auto is_selection () const -> bool { return statement.index() == selection; }
auto is_declaration() const -> bool { return statement.index() == declaration; }
auto is_return () const -> bool { return statement.index() == return_; }
auto is_iteration () const -> bool { return statement.index() == iteration; }
auto is_using () const -> bool { return statement.index() == using_; }
auto is_contract () const -> bool { return statement.index() == contract; }
auto is_inspect () const -> bool { return statement.index() == inspect; }
auto is_jump () const -> bool { return statement.index() == jump; }
template
auto get_if()
-> Node*
{
auto pnode = std::get_if(&statement);
if (pnode) {
return pnode->get();
}
// else
return nullptr;
}
template
auto get_if() const
-> Node const*
{
auto pnode = std::get_if(&statement);
if (pnode) {
return pnode->get();
}
// else
return nullptr;
}
auto get_lhs_rhs_if_simple_assignment() const
-> assignment_expression_lhs_rhs
{
if (is_expression()) {
return std::get(statement)->expr->get_lhs_rhs_if_simple_assignment();
}
// Else
return {};
}
auto to_string() const
-> std::string
{
switch (statement.index()) {
break;case expression:
return std::get(statement)->to_string();
break;default:
return "(*ERROR*) temporary alpha limitation: type metafunctions cannot stringize expressions that involve initializer statements other than expression-statements";
}
}
// Internals
//
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);
if (type_id) {
type_id->visit(v, depth+1);
}
else {
assert (value);
value->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
{
parameter_declaration_list_node const* my_list;
source_position pos = {};
passing_style pass = passing_style::in;
int ordinal = 1;
enum class modifier : u8 { none=0, implicit, virtual_, override_, final_ };
modifier mod = modifier::none;
std::unique_ptr declaration;
// Out-of-line definition of the ctor is necessary due to the forward-declared
// type(s) used in a std::unique_ptr as a member
parameter_declaration_node(parameter_declaration_list_node const* my);
// Out-of-line definition of the dtor is necessary due to the forward-declared
// type(s) used in a std::unique_ptr as a member
~parameter_declaration_node();
// API
//
auto to_string(
bool verbose = true
) const
-> std::string;
auto has_name() const
-> bool;
auto name() const
-> token const*;
auto has_name(std::string_view) const
-> bool;
auto direction() const
-> passing_style
{
return pass;
}
auto is_in_function_typeid() const
-> bool;
auto is_in_template_param_list() const
-> bool;
auto is_in_function_scope() const
-> bool;
auto is_implicit() const
-> bool
{
return mod == modifier::implicit;
}
auto is_virtual() const
-> bool
{
return mod == modifier::virtual_;
}
auto make_virtual()
-> void
{
mod = modifier::virtual_;
}
auto is_override() const
-> bool
{
return mod == modifier::override_;
}
auto is_final() const
-> bool
{
return mod == modifier::final_;
}
auto is_polymorphic() const
-> bool
{
switch (mod) {
break;case modifier::virtual_:
case modifier::override_:
case modifier::final_:
return true;
break;default:
return false;
}
}
// Internals
//
auto position() const
-> source_position;
auto visit(auto& v, int depth)
-> void;
};
struct parameter_declaration_list_node
{
token const* open_paren = {};
token const* close_paren = {};
bool in_function_typeid = false;
bool in_template_param_list = false;
bool in_statement_param_list = false;
std::vector parameters;
parameter_declaration_list_node(bool f = false, bool t = false, bool s = false)
: in_function_typeid{f}
, in_template_param_list{t}
, in_statement_param_list{s}
{ }
// API
//
auto to_string(
bool verbose = true
) const
-> std::string
{
assert(open_paren && close_paren);
auto ret = open_paren->to_string();
for (auto const& p: parameters) {
ret += p->to_string(verbose) + ", ";
}
if (
!verbose
&& std::ssize(ret) > 3
)
{
ret.resize( std::ssize(ret) - 2 ); // omit the final ", "
}
ret += close_paren->as_string_view();
return ret;
}
auto ssize() const -> auto {
return std::ssize(parameters);
}
auto operator[](int i)
-> parameter_declaration_node*
{
return parameters[i].get();
}
auto operator[](int i) const
-> parameter_declaration_node const*
{
return parameters[i].get();
}
// Internals
//
auto position() const
-> source_position
{
assert(open_paren);
return open_paren->position();
}
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 (parameters) {
parameters->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);
try_visit(statement, v, depth);
try_visit(statement, v, depth);
v.end(*this, depth);
}
struct function_returns_tag { };
struct function_type_node
{
declaration_node* my_decl;
std::unique_ptr parameters;
bool throws = false;
struct single_type_id {
std::unique_ptr type;
passing_style pass = passing_style::move;
};
enum active : u8 { empty = 0, id, list };
std::variant<
std::monostate,
single_type_id,
std::unique_ptr
> returns;
std::vector contracts;
function_type_node(declaration_node* decl);
// API
//
auto to_string() const
-> std::string
{
assert (parameters);
auto ret = parameters->to_string();
if (throws) {
ret += " throws";
}
if (auto t = std::get_if(&returns)) {
ret += " -> ";
ret += to_string_view(t->pass);
ret += " " + t->type->to_string();
}
else if (auto t = std::get_if(&returns)) {
ret += " -> " + (*t)->to_string();
}
return ret;
}
auto set_default_return_type_to_forward_wildcard()
-> void
{
if (returns.index() == empty) {
returns = single_type_id{ std::make_unique(), passing_style::forward };
assert(returns.index() == id);
}
}
auto has_postconditions() const
-> bool;
auto is_function_with_this() const
-> bool;
auto is_virtual_function() const
-> bool;
auto make_function_virtual()
-> bool;
auto is_defaultable() const
-> bool;
auto is_constructor() const
-> bool;
auto is_default_constructor() const
-> bool;
auto is_move() const
-> bool;
auto is_swap() const
-> bool;
auto is_constructor_with_that() const
-> bool;
auto is_constructor_with_in_that() const
-> bool;
auto is_constructor_with_move_that() const
-> bool;
auto is_comparison() const
-> bool;
auto is_increment_or_decrement() const
-> bool;
auto is_compound_assignment() const
-> bool;
auto is_assignment() const
-> bool;
auto is_assignment_with_that() const
-> bool;
auto is_assignment_with_in_that() const
-> bool;
auto is_assignment_with_move_that() const
-> bool;
auto is_destructor() const
-> bool;
auto has_declared_return_type() const
-> bool
{
return returns.index() != empty;
}
auto has_deduced_return_type() const
-> bool
{
return
returns.index() == empty
|| (
returns.index() == id
&& std::get(returns).type->is_wildcard()
)
;
}
auto unnamed_return_type_to_string() const
-> std::string
{
if (auto id = std::get_if(&returns)) {
return (*id).type->to_string();
}
return {};
}
auto parameters_to_string(
bool verbose = true
) const
-> std::string
{
assert (parameters);
return parameters->to_string(verbose);
}
auto has_bool_return_type() const
-> bool
{
if (auto id = std::get_if(&returns)) {
if (auto name = (*id).type->get_token()) {
return *name == "bool";
}
}
return false;
}
auto has_non_void_return_type() const
-> bool
{
if (auto id = std::get_if(&returns)) {
if (auto name = (*id).type->get_token()) {
return *name != "void";
}
}
return returns.index() != empty;
}
auto parameter_count() const
-> int
{
return unchecked_narrow(std::ssize(parameters->parameters));
}
auto index_of_parameter_named(std::string_view s) const
-> int
{
auto ret = 0;
for (auto& param : parameters->parameters) {
if (param->has_name(s)) {
return ret;
}
++ret;
}
return -1;
}
auto has_parameter_named(std::string_view s) const
-> bool
{
for (auto& param : parameters->parameters) {
if (param->has_name(s)) {
return true;
}
}
return false;
}
auto has_parameter_with_name_and_pass(
std::string_view s,
passing_style pass
) const
-> bool
{
for (auto& param : parameters->parameters) {
if (
param->has_name(s)
&& param->pass == pass
)
{
return true;
}
}
return false;
}
auto first_parameter_name() const
-> std::string;
auto nth_parameter_type_name(int n) const
-> std::string;
auto has_in_parameter_named(std::string_view s) const
-> bool
{
return has_parameter_with_name_and_pass(s, passing_style::in);
}
auto has_in_ref_parameter_named(std::string_view s) const
-> bool
{
return has_parameter_with_name_and_pass(s, passing_style::in_ref);
}
auto has_copy_parameter_named(std::string_view s) const
-> bool
{
return has_parameter_with_name_and_pass(s, passing_style::copy);
}
auto has_inout_parameter_named(std::string_view s) const
-> bool
{
return has_parameter_with_name_and_pass(s, passing_style::inout);
}
auto has_out_parameter_named(std::string_view s) const
-> bool
{
return has_parameter_with_name_and_pass(s, passing_style::out);
}
auto has_move_parameter_named(std::string_view s) const
-> bool
{
return has_parameter_with_name_and_pass(s, passing_style::move);
}
auto has_forward_parameter_named(std::string_view s) const
-> bool
{
return has_parameter_with_name_and_pass(s, passing_style::forward);
}
// Internals
//
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.type);
r.type->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);
}
for (auto const& c : contracts) {
c->visit(v, depth+1);
}
v.end(*this, depth);
}
};
auto type_id_node::to_string() const
-> std::string
{
auto ret = std::string{};
for (auto& qual : pc_qualifiers) {
assert(qual);
ret += qual->as_string_view();
ret += " ";
}
switch (id.index()) {
break;case empty:
ret += "_";
break;case postfix:
ret += std::get(id)->to_string();
break;case qualified:
ret += std::get(id)->to_string();
break;case unqualified:
ret += std::get(id)->to_string();
break;case function:
ret += std::get(id)->to_string();
break;case keyword:
ret += std::get(id)->to_string();
break;default:
assert(false && "ICE: invalid type_id state");
}
if (constraint) {
ret += "is " + constraint->to_string();
}
return ret;
}
struct type_node
{
token const* type;
bool final = false;
type_node(
token const* t,
bool final_ = false
)
: type{t}
, final{final_}
{ }
// API
//
auto is_final() const
-> bool
{
return final;
}
auto make_final()
-> void
{
final = true;
}
// Internals
//
auto position() const
-> source_position
{
assert(type);
return type->position();
}
auto visit(auto& v, int depth)
-> void
{
v.start(*this, depth);
v.end(*this, depth);
}
};
struct namespace_node
{
token const* namespace_;
namespace_node(token const* ns) : namespace_{ns} { }
auto position() const
-> source_position
{
assert(namespace_);
return namespace_->position();
}
auto visit(auto& v, int depth)
-> void
{
v.start(*this, depth);
v.end(*this, depth);
}
};
struct alias_node
{
token const* type = {};
std::unique_ptr type_id; // for objects
enum active : u8 { a_type, a_namespace, an_object };
std::variant<
std::unique_ptr,
std::unique_ptr,
std::unique_ptr
> initializer;
alias_node( token const* t ) : type{t} { }
// API
//
auto is_type_alias () const -> bool
{ return initializer.index() == a_type; }
auto is_namespace_alias() const -> bool
{ return initializer.index() == a_namespace; }
auto is_object_alias () const -> bool
{ return initializer.index() == an_object; }
// Internals
//
auto position() const
-> source_position
{
assert (type);
return type->position();
}
auto visit(auto& v, int depth)
-> void
{
v.start(*this, depth);
try_visit(initializer, v, depth+1);
try_visit(initializer, v, depth+1);
try_visit(initializer, v, depth+1);
v.end(*this, depth);
}
};
enum class accessibility : u8 { default_ = 0, public_, protected_, private_ };
auto to_string(accessibility a)
-> std::string
{
switch (a) {
break;case accessibility::public_ : return "public";
break;case accessibility::protected_: return "protected";
break;case accessibility::private_ : return "private";
break;default: assert(a == accessibility::default_);
}
return "default";
}
struct declaration_identifier_tag { };
struct declaration_node
{
// The capture_group is declared first, because it should outlive
// any owned postfix_expressions that could refer to it
capture_group captures;
source_position pos;
bool is_variadic = false;
bool is_constexpr = false;
std::unique_ptr identifier;
accessibility access = accessibility::default_;
enum active : u8 { a_function, an_object, a_type, a_namespace, an_alias };
std::variant<
std::unique_ptr,
std::unique_ptr,
std::unique_ptr,
std::unique_ptr,
std::unique_ptr
> type;
std::vector metafunctions;
std::unique_ptr template_parameters;
source_position requires_pos = {};
std::unique_ptr requires_clause_expression;
source_position equal_sign = {};
std::unique_ptr initializer;
declaration_node* parent_declaration = {};
statement_node* my_statement = {};
// Attributes currently configurable only via metafunction API,
// not directly in the base language grammar
bool member_function_generation = true;
// Cache some context
bool is_a_template_parameter = false;
bool is_a_parameter = false;
bool is_a_statement_parameter = false;
// Constructor
//
declaration_node(declaration_node* parent)
: parent_declaration{parent}
{ }
// Out-of-line definition of the dtor is necessary due to the forward-declared
// type(s) used in a std::unique_ptr as a member
~declaration_node();
// API
//
auto to_string(
bool verbose = true
) const
-> std::string;
auto signature_to_string() const
-> std::string;
auto is_template_parameter() const
-> bool
{
return is_a_template_parameter;
}
auto is_parameter() const
-> bool
{
return is_a_parameter;
}
auto is_statement_parameter() const
-> bool
{
return is_a_statement_parameter;
}
auto type_member_mark_for_removal()
-> bool
{
if (my_statement) {
my_statement->marked_for_removal = true;
return true;
}
return false;
}
auto type_remove_marked_members()
-> void
{
assert (is_type() && initializer && initializer->is_compound());
auto compound_stmt = initializer->get_if();
assert (compound_stmt);
// Note: This loop is a careful use of the brittle STL "erase" idiom. Do not change this
// loop without carefully ensuring it remains safe against iterator invalidation.
// (Especially don't change this to a for loop with a "++i" iteration-expression.)
auto i = compound_stmt->statements.begin();
while (i != compound_stmt->statements.end())
{
if ((*i)->marked_for_removal) {
i = compound_stmt->statements.erase(i); // these two branches ...
}
else {
++i; // ... must stay together
}
}
}
auto type_remove_all_members()
-> void
{
assert (is_type() && initializer && initializer->is_compound());
auto body = initializer->get_if();
assert (body);
// Drop all statements in the body, which should self-deregister all our 'captures'
// - (only) statements in the body should have been able to refer to 'captures'
body->statements.clear();
assert(captures.members.empty());
}
auto type_disable_member_function_generation()
-> void
{
member_function_generation = false;
}
auto object_type() const
-> std::string
{
if (!is_object()) {
return "(*ERROR*) not an object";
}
// Else
return std::get(type)->to_string();
}
auto object_initializer() const
-> std::string
{
if (!is_object()) {
return "(*ERROR*) not an object";
}
else if (initializer) {
return initializer->to_string();
}
// Else
return "";
}
auto get_parent() const
-> declaration_node*
{
return parent_declaration;
}
auto is_public() const
-> bool
{
return access == accessibility::public_;
}
auto is_protected() const
-> bool
{
return access == accessibility::protected_;
}
auto is_private() const
-> bool
{
return access == accessibility::private_;
}
auto is_default_access() const
-> bool
{
return access == accessibility::default_;
}
private:
auto set_access(accessibility a)
-> bool
{
if (is_default_access()) {
access = a;
}
return access == a;
}
public:
auto make_public()
-> bool
{
return set_access( accessibility::public_ );
}
auto make_protected()
-> bool
{
return set_access( accessibility::protected_ );
}
auto make_private()
-> bool
{
return set_access( accessibility::private_ );
}
auto has_name() const
-> bool
{
return
identifier
&& identifier->identifier
;
}
auto name() const
-> token const*
{
if (!identifier) {
return nullptr;
}
// Else
return identifier->identifier;
}
auto has_name(std::string_view s) const
-> bool
{
return
has_name()
&& *name() == s
;
}
auto has_initializer() const
-> bool
{
return initializer != nullptr;
}
auto parameter_count() const
-> int
{
if (!is_function()) {
return -1;
}
return std::get(type)->parameter_count();
}
auto index_of_parameter_named(std::string_view s) const
-> int
{
if (!is_function()) {
return -1;
}
return std::get(type)->index_of_parameter_named(s);
}
auto has_parameter_named(std::string_view s) const
-> bool
{
if (!is_function()) {
return false;
}
return std::get(type)->has_parameter_named(s);
}
auto has_in_parameter_named(std::string_view s) const
-> bool
{
if (!is_function()) {
return false;
}
return std::get(type)->has_in_parameter_named(s);
}
auto has_in_ref_parameter_named(std::string_view s) const
-> bool
{
if (!is_function()) {
return false;
}
return std::get(type)->has_in_ref_parameter_named(s);
}
auto has_copy_parameter_named(std::string_view s) const
-> bool
{
if (!is_function()) {
return false;
}
return std::get(type)->has_copy_parameter_named(s);
}
auto has_inout_parameter_named(std::string_view s) const
-> bool
{
if (!is_function()) {
return false;
}
return std::get(type)->has_inout_parameter_named(s);
}
auto has_out_parameter_named(std::string_view s) const
-> bool
{
if (!is_function()) {
return false;
}
return std::get(type)->has_out_parameter_named(s);
}
auto has_move_parameter_named(std::string_view s) const
-> bool
{
if (!is_function()) {
return false;
}
return std::get(type)->has_move_parameter_named(s);
}
auto has_forward_parameter_named(std::string_view s) const
-> bool
{
if (!is_function()) {
return false;
}
return std::get(type)->has_forward_parameter_named(s);
}
auto nth_parameter_type_name(int n) const
-> std::string
{
if (!is_function()) {
return "";
}
return std::get(type)->nth_parameter_type_name(n);
}
auto is_global () const -> bool
{ return !parent_declaration; }
auto is_function () const -> bool
{ return type.index() == a_function; }
auto is_object () const -> bool
{ return type.index() == an_object; }
auto is_object_with_function_typeid() const -> bool
{ return is_object() && std::get(type)->is_function_typeid(); }
auto is_base_object() const -> bool
{ return is_object() && has_name("this"); }
auto is_member_object() const -> bool
{ return is_object() && !has_name("this"); }
auto is_concept () const -> bool
{ return type.index() == an_object && get(type)->is_concept(); }
auto is_type () const -> bool
{ return type.index() == a_type; }
auto is_namespace() const -> bool
{ return type.index() == a_namespace; }
auto is_alias() const -> bool
{ return type.index() == an_alias; }
auto is_type_alias () const -> bool
{ return is_alias() && std::get(type)->is_type_alias(); }
auto is_namespace_alias() const -> bool
{ return is_alias() && std::get(type)->is_namespace_alias(); }
auto is_object_alias () const -> bool
{ return is_alias() && std::get(type)->is_object_alias(); }
auto is_function_expression () const -> bool
{ return is_function() && !identifier; }
auto is_polymorphic() const // has base types or virtual functions
-> bool
{
for (auto& decl : get_type_scope_declarations()) {
if (
decl->has_name("this")
|| decl->is_virtual_function()
)
{
return true;
}
}
return false;
}
// Do we know that this cannot be a copy constructible type?
auto cannot_be_a_copy_constructible_type() const
-> bool
{
// If we're not a type, we're not a copyable type
if (!is_type()) {
return true;
}
// Else if we're letting Cpp1 generate SMFs, we're likely copyable
if (!member_function_generation) {
return false;
}
// Else if we have a copy constructor, we're copyable
for (auto& decl : get_type_scope_declarations())
if (decl->is_constructor_with_that())
{
return false;
}
// Else there can't be a copy constructor
return true;
}
auto parent_is_function () const -> bool
{ return parent_declaration && parent_declaration->type.index() == a_function; }
auto parent_is_object () const -> bool
{ return parent_declaration && parent_declaration->type.index() == an_object; }
auto parent_is_type () const -> bool
{ return parent_declaration && parent_declaration->type.index() == a_type; }
auto parent_is_namespace () const -> bool
{ return !parent_declaration || parent_declaration->type.index() == a_namespace; }
auto parent_is_alias () const -> bool
{ return parent_declaration && parent_declaration->type.index() == an_alias; }
auto parent_is_type_alias () const -> bool
{ return parent_declaration && parent_declaration->is_alias() && std::get(parent_declaration->type)->is_type_alias(); }
auto parent_is_namespace_alias() const -> bool
{ return parent_declaration && parent_declaration->is_alias() && std::get(parent_declaration->type)->is_namespace_alias(); }
auto parent_is_object_alias () const -> bool
{ return parent_declaration && parent_declaration->is_alias() && std::get(parent_declaration->type)->is_object_alias(); }
auto is_inside_global_unnamed_function() const -> bool {
auto parent = parent_declaration;
// Get outside all nested function expressions
while (parent && parent->is_function() && !parent->has_name()) {
parent = parent->parent_declaration;
}
return !parent;
}
auto parent_is_polymorphic() const -> bool
{ return parent_declaration && parent_declaration->is_polymorphic(); }
enum which : u8 {
functions = 1,
objects = 2,
types = 4,
aliases = 8,
all = functions|objects|types|aliases
};
private:
// This helper is a const function that delivers pointers
// to non-const... because this is the best way I can
// think of right now to write the following two get_
// functions (without duplicating their bodies, and
// without resorting to const_casts)
auto gather_type_scope_declarations(which w) const
-> std::vector
{
if (
!is_type()
|| !initializer
|| !initializer->is_compound()
)
{
return {};
}
auto compound_stmt = initializer->get_if();
assert (compound_stmt);
auto ret = std::vector{};
for (auto& o : compound_stmt->statements)
{
auto decl = o->get_if();
if (decl)
{
assert(
!decl->is_namespace()
&& "ICE: a type shouldn't be able to contain a namespace"
);
if (
(w & functions && decl->is_function())
|| (w & objects && decl->is_object() )
|| (w & types && decl->is_type() )
|| (w & aliases && decl->is_alias() )
)
{
ret.push_back(decl);
}
}
}
return ret;
}
public:
auto get_type_scope_declarations(which w = all)
-> std::vector
{
// Only want to return the gather_ results as
// non-const* in a non-const function
return gather_type_scope_declarations(w);
}
auto get_type_scope_declarations(which w = all) const
-> std::vector
{
// Convert the gather_ results to const*
auto tmp = gather_type_scope_declarations(w);
return {tmp.begin(), tmp.end()};
}
auto add_type_member( std::unique_ptr&& statement )
-> bool
{
if (
!is_type()
|| !initializer
|| !initializer->is_compound()
|| !statement->is_declaration()
)
{
return false;
}
// Tell this declaration statement that we are its new parent
// and check to ensure that it doesn't already have a parent
// (that shouldn't happen because we should only get here for a
// generated statement that hasn't been added elsewhere yet)
auto decl = statement->get_if();
assert(
decl
&& !decl->parent_declaration
);
decl->parent_declaration = this;
// And actually adopt it into our list of statements
auto compound_stmt = initializer->get_if();
assert (compound_stmt);
compound_stmt->statements.push_back(std::move(statement));
return true;
}
auto add_function_initializer( std::unique_ptr&& statement )
-> bool
{
if (
!is_function()
|| initializer
)
{
return false;
}
// Adopt it as our initializer statement
initializer = std::move( statement );
return true;
}
auto get_decl_if_type_scope_object_name_before_a_base_type( std::string_view s ) const
-> declaration_node const*
{
declaration_node const* ret = {};
// If it's 'this' then it can't be an object name
if (s == "this") {
return {};
}
// Navigate to the nearest enclosing type
auto decl = this;
while (
!decl->is_type()
&& decl->parent_declaration
)
{
decl = decl->parent_declaration;
}
if (!decl->is_type()) {
return {};
}
// Look for a name match and if so remember the type,
// and look for a base type after that match
auto objects = decl->get_type_scope_declarations();
auto found_name = false;
auto found_later_base_type = false;
for (auto& o : objects) {
if (o->is_alias()) {
continue;
}
if (o->has_name(s)) {
found_name = true;
ret = o;
}
if (o->has_name("this")) {
if (found_name) {
found_later_base_type = true;
break;
}
}
}
// If we didn't find a later base type, discard any name match
if (!found_later_base_type) {
ret = {};
}
return ret;
}
auto get_initializer_statements() const
-> std::vector
{
if (!initializer) {
return {};
}
auto ret = std::vector{};
// For non-compound initializers, we want just that statement
if (!initializer->is_compound())
{
ret.push_back(initializer.get());
}
// Else for compound initializers, we want the compound_statement's statements
else
{
auto compound_stmt = initializer->get_if();
assert (compound_stmt);
for (auto& o : compound_stmt->statements) {
ret.push_back(o.get());
}
}
return ret;
}
auto is_function_with_compound_body() const
-> bool
{
return
initializer
&& initializer->is_compound()
;
}
auto get_compound_initializer() const
-> compound_statement_node*
{
return initializer->get_if();
}
auto is_function_with_this() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_function_with_this();
}
// else
return false;
}
auto is_virtual_function() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_virtual_function();
}
// else
return false;
}
auto is_type_final() const
-> bool
{
if (auto t = std::get_if(&type)) {
return (*t)->is_final();
}
// else
return false;
}
auto make_type_final()
-> bool
{
if (auto t = std::get_if(&type)) {
(*t)->make_final();
return true;
}
// else
return false;
}
auto make_function_virtual()
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->make_function_virtual();
}
// else
return false;
}
auto is_defaultable_function() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_defaultable();
}
// else
return false;
}
auto is_constructor() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_constructor();
}
// else
return false;
}
auto is_default_constructor() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_default_constructor();
}
// else
return false;
}
auto is_move() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_move();
}
// else
return false;
}
auto is_swap() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_swap();
}
// else
return false;
}
auto is_constructor_with_that() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_constructor_with_that();
}
// else
return false;
}
auto is_constructor_with_in_that() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_constructor_with_in_that();
}
// else
return false;
}
auto is_constructor_with_move_that() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_constructor_with_move_that();
}
// else
return false;
}
auto is_comparison() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_comparison();
}
// else
return false;
}
auto is_increment_or_decrement() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_increment_or_decrement();
}
// else
return false;
}
auto is_compound_assignment() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_compound_assignment();
}
// else
return false;
}
auto is_assignment() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_assignment();
}
// else
return false;
}
auto is_assignment_with_that() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_assignment_with_that();
}
// else
return false;
}
auto is_assignment_with_in_that() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_assignment_with_in_that();
}
// else
return false;
}
auto is_assignment_with_move_that() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_assignment_with_move_that();
}
// else
return false;
}
struct declared_value_set_funcs {
declaration_node const* out_this_in_that = {};
declaration_node const* out_this_move_that = {};
declaration_node const* inout_this_in_that = {};
declaration_node const* inout_this_move_that = {};
std::vector assignments_from = {};
};
auto find_declared_value_set_functions() const
-> declared_value_set_funcs
{
if (!initializer) {
return {};
}
auto compound_stmt = initializer->get_if();
assert (compound_stmt);
auto ret = declared_value_set_funcs{};
for (auto& o : compound_stmt->statements)
{
auto decl = o->get_if();
if (decl)
{
if (decl->is_constructor_with_in_that()) {
ret.out_this_in_that = decl;
}
if (decl->is_constructor_with_move_that()) {
ret.out_this_move_that = decl;
}
if (decl->is_assignment_with_in_that()) {
ret.inout_this_in_that = decl;
}
if (decl->is_assignment_with_move_that()) {
ret.inout_this_move_that = decl;
}
if (decl->is_assignment() && !decl->is_assignment_with_that()) {
ret.assignments_from.emplace_back( decl->nth_parameter_type_name(2) );
}
}
}
return ret;
}
auto find_parent_declared_value_set_functions() const
-> declared_value_set_funcs
{
if (parent_is_type()) {
return parent_declaration->find_declared_value_set_functions();
}
// else
return {};
}
auto is_destructor() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->is_destructor();
}
// else
return false;
}
auto has_declared_return_type() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->has_declared_return_type();
}
// else
return false;
}
auto has_deduced_return_type() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->has_deduced_return_type();
}
// else
return false;
}
auto get_function_parameters()
-> std::vector
{
if (!is_function()) {
return {};
}
// else
auto ret = std::vector{};
for (auto& param : std::get(type)->parameters->parameters) {
ret.push_back( param.get() );
}
return ret;
}
auto unnamed_return_type_to_string() const
-> std::string
{
if (auto func = std::get_if(&type)) {
return (*func)->unnamed_return_type_to_string();
}
// else
return {};
}
auto has_bool_return_type() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->has_bool_return_type();
}
// else
return false;
}
auto has_non_void_return_type() const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->has_non_void_return_type();
}
// else
return false;
}
auto has_parameter_with_name_and_pass(
std::string_view s,
passing_style pass
) const
-> bool
{
if (auto func = std::get_if(&type)) {
return (*func)->has_parameter_with_name_and_pass(s, pass);
}
// else
return false;
}
auto first_parameter_name() const
-> std::string
{
if (auto func = std::get_if(&type)) {
return (*func)->first_parameter_name();
}
// else
return "";
}
auto is_binary_comparison_function() const
-> bool
{
return
is_function()
&& (
has_name("operator==")
|| has_name("operator!=")
|| has_name("operator=")
);
}
auto is_const() const
-> bool
{
return
type.index() == an_object
&& !std::get(type)->pc_qualifiers.empty()
&& *std::get(type)->pc_qualifiers.front() == "const"
;
}
auto has_wildcard_type() const
-> bool
{
return
type.index() == an_object
&& std::get(type)->is_wildcard()
;
}
auto get_object_type() const
-> type_id_node const*
{
if (type.index() == an_object) {
return std::get(type).get();
}
// Else
return {};
}
auto set_default_return_type_to_forward_wildcard()
-> void
{
if (type.index() == a_function) {
std::get(type).get()->set_default_return_type_to_forward_wildcard();
}
}
// Internals
//
auto position() const
-> source_position
{
if (identifier) {
return identifier->position();
}
return pos;
}
auto visit(auto& v, int depth)
-> void
{
v.start(*this, depth);
v.start(declaration_identifier_tag{}, depth);
if (identifier) {
identifier->visit(v, depth+1);
}
v.end(declaration_identifier_tag{}, depth);
if (template_parameters) {
template_parameters->visit(v, depth+1);
}
try_visit(type, v, depth+1);
try_visit(type, v, depth+1);
try_visit(type, v, depth+1);
try_visit(type, v, depth+1);
try_visit(type, v, depth+1);
for (auto& m : metafunctions) {
assert(m);
m->visit(v, depth+1);
}
if (initializer) {
initializer->visit(v, depth+1);
}
v.end(*this, depth);
}
};
auto parameter_declaration_node::to_string(
bool verbose /*= true*/
) const
-> std::string
{
auto ret = std::string{};
switch (mod) {
break;case modifier::implicit:
ret += "implicit ";
break;case modifier::virtual_:
ret += "virtual ";
break;case modifier::override_:
ret += "override ";
break;case modifier::final_:
ret += "final ";
break;default:
;
}
if (
verbose
|| pass != passing_style::in
)
{
ret += to_string_view(pass);
ret += " ";
}
ret += declaration->to_string( verbose );
return ret;
}
compound_statement_node::compound_statement_node(source_position o)
: open_brace{o}
{ }
statement_node::statement_node(compound_statement_node* compound_parent_)
: compound_parent{ compound_parent_ }
{ }
function_type_node::function_type_node(declaration_node* decl)
: my_decl{decl}
{ }
auto parameter_declaration_node::has_name() const
-> bool
{
return declaration->has_name();
}
auto parameter_declaration_node::name() const
-> token const*
{
return declaration->name();
}
auto parameter_declaration_node::has_name(std::string_view s) const
-> bool
{
return declaration->has_name(s);
}
auto parameter_declaration_node::is_in_function_typeid() const
-> bool
{
return my_list && my_list->in_function_typeid;
}
auto parameter_declaration_node::is_in_template_param_list() const
-> bool
{
return my_list && my_list->in_template_param_list;
}
auto parameter_declaration_node::is_in_function_scope() const
-> bool
{
return
my_list->in_statement_param_list
|| (
declaration->parent_is_function()
&& !declaration->parent_declaration->parent_is_type()
&& !declaration->parent_declaration->parent_is_namespace()
)
;
}
auto function_type_node::first_parameter_name() const
-> std::string
{
if (std::ssize(parameters->parameters) > 0)
{
assert (parameters->parameters[0]->declaration->name());
return parameters->parameters[0]->declaration->name()->to_string();
}
// Else
return "";
}
auto function_type_node::nth_parameter_type_name(int n) const
-> std::string
{
if (std::ssize(parameters->parameters) >= n)
{
return parameters->parameters[n-1]->declaration->get_object_type()->to_string();
}
// Else
return "";
}
auto function_type_node::has_postconditions() const
-> bool
{
return
std::find_if(
contracts.begin(),
contracts.end(),
[](auto const& e){ return *e->kind == "post"; }
) != contracts.end();
}
auto function_type_node::is_function_with_this() const
-> bool
{
if (
(*parameters).ssize() > 0
&& (*parameters)[0]->has_name("this")
)
{
return true;
}
return false;
}
auto function_type_node::is_virtual_function() const
-> bool
{
if (
(*parameters).ssize() > 0
&& (*parameters)[0]->has_name("this")
&& (*parameters)[0]->is_virtual()
)
{
return true;
}
return false;
}
auto function_type_node::make_function_virtual()
-> bool
{
if (is_function_with_this()) {
(*parameters)[0]->make_virtual();
return true;
}
return false;
}
auto function_type_node::is_defaultable() const
-> bool
{
if (
my_decl
&& (
my_decl->has_name("operator==")
|| my_decl->has_name("operator")
)
)
{
return true;
}
return false;
}
auto function_type_node::is_constructor() const
-> bool
{
if (
(*parameters).ssize() > 0
&& (*parameters)[0]->has_name("this")
&& (*parameters)[0]->direction() == passing_style::out
)
{
assert(my_decl && my_decl->has_name("operator="));
return true;
}
return false;
}
auto function_type_node::is_default_constructor() const
-> bool
{
if (
is_constructor()
&& (*parameters).ssize() == 1
)
{
return true;
}
return false;
}
auto function_type_node::is_move() const
-> bool
{
if (
(is_constructor() || is_assignment())
&& (*parameters).ssize() == 2
&& (*parameters)[1]->has_name("that")
&& (*parameters)[1]->direction() == passing_style::move
)
{
return true;
}
return false;
}
auto function_type_node::is_swap() const
-> bool
{
if (
my_decl
&& my_decl->has_name("swap")
&& (*parameters).ssize() == 2
&& (*parameters)[1]->has_name("that")
)
{
return true;
}
return false;
}
auto function_type_node::is_constructor_with_that() const
-> bool
{
if (
is_constructor()
&& (*parameters).ssize() == 2
&& (*parameters)[1]->has_name("that")
)
{
return true;
}
return false;
}
auto function_type_node::is_assignment_with_that() const
-> bool
{
if (
is_assignment()
&& (*parameters).ssize() == 2
&& (*parameters)[1]->has_name("that")
)
{
return true;
}
return false;
}
auto function_type_node::is_constructor_with_in_that() const
-> bool
{
if (
is_constructor()
&& (*parameters).ssize() == 2
&& (*parameters)[1]->has_name("that")
&& (*parameters)[1]->direction() == passing_style::in
)
{
return true;
}
return false;
}
auto function_type_node::is_constructor_with_move_that() const
-> bool
{
if (
is_constructor()
&& (*parameters).ssize() == 2
&& (*parameters)[1]->has_name("that")
&& (*parameters)[1]->direction() == passing_style::move
)
{
return true;
}
return false;
}
auto function_type_node::is_comparison() const
-> bool
{
if (
my_decl
&& (
my_decl->has_name("operator==")
|| my_decl->has_name("operator!=")
|| my_decl->has_name("operator")
|| my_decl->has_name("operator>=")
|| my_decl->has_name("operator")
)
)
{
return true;
}
return false;
}
auto function_type_node::is_increment_or_decrement() const
-> bool
{
if (
my_decl
&& (
my_decl->has_name("operator++")
|| my_decl->has_name("operator--")
)
)
{
return true;
}
return false;
}
auto function_type_node::is_compound_assignment() const
-> bool
{
if (
my_decl
&& (
my_decl->has_name("operator+=")
|| my_decl->has_name("operator-=")
|| my_decl->has_name("operator*=")
|| my_decl->has_name("operator/=")
|| my_decl->has_name("operator%=")
|| my_decl->has_name("operator&=")
|| my_decl->has_name("operator|=")
|| my_decl->has_name("operator^=")
|| my_decl->has_name("operator>=")
)
&& (*parameters).ssize() > 1
&& (*parameters)[0]->has_name("this")
&& (*parameters)[0]->direction() == passing_style::inout
)
{
return true;
}
return false;
}
auto function_type_node::is_assignment() const
-> bool
{
if (
my_decl
&& my_decl->has_name("operator=")
&& (*parameters).ssize() > 1
&& (*parameters)[0]->has_name("this")
&& (*parameters)[0]->direction() == passing_style::inout
)
{
return true;
}
return false;
}
auto function_type_node::is_assignment_with_in_that() const
-> bool
{
if (
is_assignment()
&& (*parameters).ssize() == 2
&& (*parameters)[1]->has_name("that")
&& (*parameters)[1]->direction() == passing_style::in
)
{
return true;
}
return false;
}
auto function_type_node::is_assignment_with_move_that() const
-> bool
{
if (
is_assignment()
&& (*parameters).ssize() == 2
&& (*parameters)[1]->has_name("that")
&& (*parameters)[1]->direction() == passing_style::move
)
{
return true;
}
return false;
}
auto function_type_node::is_destructor() const
-> bool
{
if (
my_decl
&& my_decl->has_name("operator=")
&& (*parameters).ssize() == 1
&& (*parameters)[0]->has_name("this")
&& (*parameters)[0]->direction() == passing_style::move
)
{
return true;
}
return false;
}
auto primary_expression_node::template_arguments() const
-> std::vector const&
{
if (expr.index() == id_expression) {
return std::get(expr)->template_arguments();
}
// else
return no_template_args;
}
auto primary_expression_node::get_token() const
-> token const*
{
if (expr.index() == identifier) {
return std::get(expr);
}
else if (expr.index() == id_expression) {
return std::get(expr)->get_token();
}
else if (expr.index() == literal) {
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;case literal: {
auto const& i = std::get(expr);
assert (i);
return i->position();
}
break;default:
assert (false && "ICE: 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);
try_visit(expr, v, depth);
v.end(*this, depth);
}
struct next_expression_tag { };
struct loop_body_tag { iteration_statement_node const* n; };
auto iteration_statement_node::visit(auto& v, int depth)
-> void
{
v.start(*this, depth);
if (label) {
label->visit(v, depth+1);
}
if (identifier) {
identifier->visit(v, depth+1);
}
if (statements) {
statements->visit(v, depth+1);
}
if (next_expression) {
v.start(next_expression_tag{}, depth);
next_expression->visit(v, depth+1);
v.end(next_expression_tag{}, depth);
}
if (condition) {
assert(!range && !body);
condition->visit(v, depth+1);
}
else {
assert(range && parameter && body);
range->visit(v, depth+1);
v.start(loop_body_tag{this}, depth);
parameter->visit(v, depth+1);
body->visit(v, depth+1);
v.end(loop_body_tag{this}, depth);
}
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 using_: {
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;case inspect: {
auto const& s = std::get(statement);
assert (s);
return s->position();
}
break;case jump: {
auto const& s = std::get(statement);
assert (s);
return s->position();
}
break;default:
assert (false && "ICE: 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);
}
};
// Definitions of out-of-line ctors & dtors for nodes with unique_ptr members of forward-declared types
parameter_declaration_node::parameter_declaration_node(parameter_declaration_list_node const* my)
: my_list{my}
{ }
parameter_declaration_node::~parameter_declaration_node() = default;
type_id_node::~type_id_node() = default;
primary_expression_node::~primary_expression_node() = default;
prefix_expression_node::~prefix_expression_node() = default;
template<
String Name,
typename Term
>
binary_expression_node::~binary_expression_node() = default;
alternative_node::~alternative_node() = default;
iteration_statement_node::~iteration_statement_node() = default;
template_argument::~template_argument() = default;
inspect_expression_node::~inspect_expression_node() = default;
statement_node::~statement_node() = default;
declaration_node::~declaration_node() = default;
//-----------------------------------------------------------------------
//
// pretty_print_visualize: pretty-prints Cpp2 ASTs
//
//-----------------------------------------------------------------------
//
auto pretty_print_visualize(token const& n, int indent)
-> std::string;
auto pretty_print_visualize(primary_expression_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(literal_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(prefix_expression_node const& n, int indent)
-> std::string;
template<
String Name,
typename Term
>
auto pretty_print_visualize(binary_expression_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(expression_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(expression_list_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(expression_statement_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(postfix_expression_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(unqualified_id_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(qualified_id_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(type_id_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(is_as_expression_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(id_expression_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(compound_statement_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(selection_statement_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(iteration_statement_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(return_statement_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(alternative_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(inspect_expression_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(contract_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(jump_statement_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(using_statement_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(statement_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(parameter_declaration_node const& n, int indent, bool is_template_param = false)
-> std::string;
auto pretty_print_visualize(parameter_declaration_list_node const& n, int indent, bool is_template_param_list = false)
-> std::string;
auto pretty_print_visualize(function_type_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(type_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(namespace_node const& n, int indent)
-> std::string;
auto pretty_print_visualize(declaration_node const& n, int indent, bool include_metafunctions_list = false, bool verbose = true)
-> std::string;
auto declaration_node::to_string(
bool verbose /*= true*/
) const
-> std::string
{
// These need to be unified someday... let's not duplicate this long function...
return pretty_print_visualize(*this, 0, false, verbose);
}
auto declaration_node::signature_to_string() const
-> std::string
{
auto ret = std::string{};
if (auto fname = name()) {
ret += *fname;
}
if (auto func = std::get_if(&type)) {
assert ((*func)->parameters);
ret += (*func)->parameters->to_string(false);
}
return ret;
}
auto primary_expression_node::to_string() const
-> std::string
{
switch (expr.index())
{
break; case empty:
return {};
break; case identifier: {
auto const& s = std::get(expr);
assert(s);
return s->to_string();
}
break; case expression_list: {
auto const& s = std::get(expr);
assert(s);
return s->to_string();
}
break; case id_expression: {
auto const& s = std::get(expr);
assert(s);
return s->to_string();
}
break; case declaration: {
auto const& s = std::get(expr);
assert(s);
auto ret = pretty_print_visualize(*s, 0);
if (ret.ends_with(';')) {
ret.resize( ret.size()-1 );
}
return ret;
}
break; case literal: {
auto const& i = std::get(expr);
assert(i);
return i->to_string();
}
break; default:
return "(*ERROR*) temporary alpha limitation: type metafunctions cannot stringize expressions that involve nested inspect expressions";
}
}
//-----------------------------------------------------------------------
// 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(),
impl::as( std::min( indent*indent_spaces, _as(std::ssize(indent_str))) )
};
}
//-----------------------------------------------------------------------
// try_pretty_print_visualize
//
// Helper to emit whatever is in a variant where each
// alternative is a smart pointer
//
template
auto try_pretty_print_visualize(
auto& v,
auto&&... more
)
-> std::string
{
if (v.index() == I) {
auto const& alt = std::get(v);
assert (alt);
return pretty_print_visualize (*alt, CPP2_FORWARD(more)...);
}
return "";
}
auto pretty_print_visualize(token const& t, int)
-> std::string
{
return t.to_string();
}
auto pretty_print_visualize(primary_expression_node const& n, int indent)
-> std::string
{
auto ret = std::string{};
ret += try_pretty_print_visualize(n.expr, indent);
ret += try_pretty_print_visualize(n.expr, indent);
ret += try_pretty_print_visualize(n.expr, indent);
ret += try_pretty_print_visualize(n.expr, indent);
ret += try_pretty_print_visualize(n.expr, indent);
ret += try_pretty_print_visualize(n.expr, indent);
return ret;
}
auto pretty_print_visualize(literal_node const& n, int)
-> std::string
{
return n.to_string();
}
auto pretty_print_visualize(prefix_expression_node const& n, int indent)
-> std::string
{
assert(n.expr);
auto ret = std::string{};
for (auto& op : n.ops) {
assert(op);
ret += op->as_string_view();
}
ret += pretty_print_visualize(*n.expr, indent);
return ret;
}
template<
String Name,
typename Term
>
auto pretty_print_visualize(binary_expression_node const& n, int indent)
-> std::string
{
assert(n.expr);
auto ret = pretty_print_visualize(*n.expr, indent);
for (auto& term : n.terms) {
assert(term.op && term.expr);
ret += " " + term.op->to_string()
+ " " + pretty_print_visualize(*term.expr, indent);
}
return ret;
}
auto pretty_print_visualize(expression_node const& n, int indent)
-> std::string
{
assert(n.expr);
return pretty_print_visualize(*n.expr, indent);
}
auto pretty_print_visualize(expression_list_node const& n, int indent)
-> std::string
{
assert(n.open_paren && n.close_paren);
auto ret = n.open_paren->to_string();
for (auto i = 0; auto& expr : n.expressions) {
assert(expr.expr);
if (
expr.pass == passing_style::out
|| expr.pass == passing_style::move
|| expr.pass == passing_style::forward
)
{
ret += to_string_view(expr.pass) + std::string{" "};
}
ret += pretty_print_visualize(*expr.expr, indent);
if (++i < std::ssize(n.expressions)) {
ret += ", ";
}
}
ret += n.close_paren->as_string_view();
return ret;
}
auto pretty_print_visualize(expression_statement_node const& n, int indent)
-> std::string
{
assert(n.expr);
auto ret = pretty_print_visualize(*n.expr, indent);
if (n.has_semicolon && ret.back() != ';') {
ret += ";";
}
return ret;
}
auto pretty_print_visualize(postfix_expression_node const& n, int indent)
-> std::string
{
assert(n.expr);
auto ret = pretty_print_visualize(*n.expr, indent);
for (auto& op : n.ops)
{
assert(op.op);
if (op.expr_list) {
assert (op.op_close);
ret += pretty_print_visualize(*op.expr_list, indent);
}
else {
ret += op.op->as_string_view();
if (op.id_expr) {
ret += pretty_print_visualize(*op.id_expr, indent);
}
if (op.last_expr) {
ret += pretty_print_visualize(*op.last_expr, indent);
}
}
}
return ret;
}
auto pretty_print_visualize(unqualified_id_node const& n, int indent)
-> std::string
{
assert(n.identifier);
auto ret = n.identifier->to_string();
if (n.open_angle != source_position{})
{
ret += "";
}
return ret;
}
auto pretty_print_visualize(qualified_id_node const& n, int indent)
-> std::string
{
auto ret = std::string{};
for (auto& id : n.ids) {
if (id.scope_op) { ret += id.scope_op->as_string_view(); }
assert (id.id);
ret += pretty_print_visualize(*id.id, indent);
}
return ret;
}
auto pretty_print_visualize(type_id_node const& n, int indent)
-> std::string
{
auto ret = std::string{};
for (auto& qual : n.pc_qualifiers) {
assert(qual);
ret += qual->as_string_view();
ret += " ";
}
if (n.id.index() == type_id_node::empty) { ret += "_"; }
ret += try_pretty_print_visualize(n.id, indent);
ret += try_pretty_print_visualize(n.id, indent);
ret += try_pretty_print_visualize(n.id, indent);
ret += try_pretty_print_visualize(n.id, indent);
ret += try_pretty_print_visualize(n.id, indent);
if (n.constraint) {
ret += " is " + pretty_print_visualize(*n.constraint, indent);
}
return ret;
}
auto pretty_print_visualize(is_as_expression_node const& n, int indent)
-> std::string
{
assert (n.expr);
auto ret = pretty_print_visualize(*n.expr, indent);
for (auto& op : n.ops) {
if (op.op) { ret += " " + op.op->to_string() + " "; }
if (op.type) { ret += pretty_print_visualize(*op.type, indent); }
if (op.expr) { ret += pretty_print_visualize(*op.expr, indent); }
}
return ret;
}
auto pretty_print_visualize(id_expression_node const& n, int indent)
-> std::string
{
auto ret = std::string{};
ret += try_pretty_print_visualize(n.id, indent);
ret += try_pretty_print_visualize(n.id, indent);
return ret;
}
auto pretty_print_visualize(compound_statement_node const& n, int indent)
-> std::string
{
auto ret = std::string{"\n"} + pre(indent) + "{";
for (auto& stmt : n.statements) {
assert (stmt);
ret += pretty_print_visualize(*stmt, indent+1);
}
ret += std::string{"\n"} + pre(indent) + "}";
return ret;
}
auto pretty_print_visualize(selection_statement_node const& n, int indent)
-> std::string
{
assert (n.identifier && n.expression && n.true_branch && n.false_branch);
auto ret = std::string{};
ret += std::string{"\n"} + pre(indent) + n.identifier->as_string_view() + " ";
if (n.is_constexpr) {
ret += "constexpr ";
}
ret += pretty_print_visualize(*n.expression, indent)
+ pretty_print_visualize(*n.true_branch, indent);
if (n.has_source_false_branch) {
ret += std::string{"\n"} + pre(indent) + "else "
+ pretty_print_visualize(*n.false_branch, indent);
}
return ret;
}
auto pretty_print_visualize(iteration_statement_node const& n, int indent)
-> std::string
{
// First compute the common parts
auto next_expr = std::string{};
if (n.next_expression) {
next_expr += std::string{"\n"} + pre(indent) + "next " + pretty_print_visualize(*n.next_expression, indent);
}
auto stmts = std::string{};
if (n.statements) {
stmts += pretty_print_visualize(*n.statements, indent+1);
}
// Then slot them in where appropriate
auto ret = std::string{};
assert (n.identifier);
ret += std::string{"\n"} + pre(indent);
if (n.label) {
ret += n.label->to_string()
+ ": ";
}
if (*n.identifier == "while") {
assert (n.condition);
ret += "while "
+ pretty_print_visualize(*n.condition, indent) + next_expr + stmts;
}
else if (*n.identifier == "do") {
assert (n.condition);
ret += "do "
+ stmts
+ next_expr
+ "\n" + pre(indent) + "while "
+ pretty_print_visualize(*n.condition, indent);
if (ret.back() != ';') {
ret += ";";
}
}
else {
assert (n.range && n.parameter && n.body);
ret += "for "
+ pretty_print_visualize(*n.range, indent)
+ next_expr
+ "\n" + pre(indent) + "do (" + pretty_print_visualize(*n.parameter, indent + 1) + ")"
+ pretty_print_visualize(*n.body, indent+1);
}
return ret;
}
auto pretty_print_visualize(return_statement_node const& n, int indent)
-> std::string
{
auto ret = std::string{"\n"} + pre(indent) + "return";
if (n.expression) {
ret += " " + pretty_print_visualize(*n.expression, indent);
}
if (ret.back() != ';') {
ret += ";";
}
return ret;
}
auto pretty_print_visualize(alternative_node const& n, int indent)
-> std::string
{
auto ret = std::string{};
assert (n.is_as_keyword);
ret += std::string{"\n"} + pre(indent);
if (n.name) {
ret += pretty_print_visualize(*n.name, indent) + ": ";
}
ret += n.is_as_keyword->as_string_view();
if (n.type_id) {
ret += " " + pretty_print_visualize(*n.type_id, indent);
}
if (n.value) {
ret += " " + pretty_print_visualize(*n.value, indent);
}
ret += " = " + pretty_print_visualize(*n.statement, indent+1);
return ret;
}
auto pretty_print_visualize(inspect_expression_node const& n, int indent)
-> std::string
{
assert (n.expression);
auto ret = std::string{"inspect"};
if (n.is_constexpr) {
ret += " constexpr";
}
ret += " " + pretty_print_visualize(*n.expression, indent);
if (n.result_type) {
ret += " -> " + pretty_print_visualize(*n.result_type, indent);
}
ret += " {";
for (auto& alt : n.alternatives) {
assert(alt);
ret += pretty_print_visualize(*alt, indent+1);
}
ret += std::string{"\n"} + pre(indent) + "}";
return ret;
}
auto pretty_print_visualize(contract_node const& n, int indent)
-> std::string
{
assert (n.kind && n.condition);
auto ret = std::string{"\n"} + pre(indent) + n.kind->as_string_view();
if (n.group) {
ret += "";
}
ret += "( " + pretty_print_visualize(*n.condition, indent);
if (n.message) {
ret += ", " + pretty_print_visualize(*n.message, indent);
}
ret += " )";
if (*n.kind == "assert" && ret.back() != ';') {
ret += ";";
}
return ret;
}
auto pretty_print_visualize(jump_statement_node const& n, int indent)
-> std::string
{
assert (n.keyword);
auto ret = std::string{"\n"} + pre(indent) + n.keyword->as_string_view();
if (n.label) {
ret += " " + n.label->to_string();
}
if (ret.back() != ';') {
ret += ";";
}
return ret;
}
auto pretty_print_visualize(using_statement_node const& n, int indent)
-> std::string
{
assert (n.keyword);
auto ret = std::string{"\n"} + pre(indent) + n.keyword->as_string_view() + " ";
if (n.for_namespace()) {
ret += "namespace ";
}
ret += pretty_print_visualize(*n.id, indent);
if (n.for_namespace()) {
assert(ret.ends_with("::_"));
ret.resize( ret.size() - 3 ); // maybe someday: ret.remove_suffix(3)
}
if (ret.back() != ';') {
ret += ";";
}
return ret;
}
auto pretty_print_visualize(statement_node const& n, int indent)
-> std::string
{
auto ret = std::string{};
if (n.is_expression())
{
if (n.compound_parent) {
ret += std::string{"\n"} + pre(indent);
}
auto& expr = std::get(n.statement);
assert (expr);
ret += pretty_print_visualize(*expr, indent);
}
else
{
if (n.parameters) {
ret += std::string{"\n"} + pre(indent) + pretty_print_visualize(*n.parameters, indent);
}
ret += try_pretty_print_visualize(n.statement, indent);
ret += try_pretty_print_visualize(n.statement, indent);
ret += try_pretty_print_visualize(n.statement, indent);
ret += try_pretty_print_visualize(n.statement, indent);
ret += try_pretty_print_visualize(n.statement, indent);
ret += try_pretty_print_visualize(n.statement, indent);
ret += try_pretty_print_visualize(n.statement, indent);
ret += try_pretty_print_visualize(n.statement, indent);
ret += try_pretty_print_visualize(n.statement, indent);
}
return ret;
}
auto pretty_print_visualize(parameter_declaration_node const& n, int indent, bool is_template_param_list /* = false */ )
-> std::string
{
assert (n.declaration);
auto ret = std::string{};
if (!is_template_param_list) {
switch (n.mod) {
break;case parameter_declaration_node::modifier::implicit : ret += "implicit ";
break;case parameter_declaration_node::modifier::virtual_ : ret += "virtual ";
break;case parameter_declaration_node::modifier::override_: ret += "override ";
break;case parameter_declaration_node::modifier::final_ : ret += "final ";
break;default: ; // none
}
ret += to_string_view(n.pass);
ret += " ";
}
ret += pretty_print_visualize(*n.declaration, indent);
return ret;
}
auto pretty_print_visualize(parameter_declaration_list_node const& n, int indent, bool is_template_param_list /* = false */)
-> std::string
{
assert(n.open_paren && n.close_paren);
auto ret = n.open_paren->to_string();
auto space = std::string{};
if (std::ssize(n.parameters) > 1) {
space += std::string{"\n"} + pre(indent+1);
}
for (auto& param : n.parameters) {
ret += space + pretty_print_visualize(*param, indent+1, is_template_param_list) + ", ";
}
if (std::ssize(n.parameters) > 1) {
ret += std::string{"\n"} + pre(indent);
}
ret += n.close_paren->as_string_view();
return ret;
}
auto pretty_print_visualize(function_type_node const& n, int indent)
-> std::string
{
assert (n.parameters);
auto ret = pretty_print_visualize(*n.parameters, indent);
if (n.throws) {
ret += " throws";
}
if (n.has_non_void_return_type()) {
ret += " -> ";
ret += try_pretty_print_visualize(n.returns, indent+1);
if (n.returns.index() == function_type_node::id) {
auto& single = std::get(n.returns);
ret += to_string_view(single.pass)
+ std::string{" "} + pretty_print_visualize(*single.type, indent+1);
}
}
for (auto& contract: n.contracts) {
assert(contract);
ret += pretty_print_visualize(*contract, indent+1);
}
return ret;
}
auto pretty_print_visualize(type_node const& n)
-> std::string
{
assert (n.type);
auto ret = std::string{};
if (n.final) {
ret += "final ";
}
ret += "type";
return ret;
}
auto pretty_print_visualize(namespace_node const&)
-> std::string
{
return "namespace";
}
auto pretty_print_visualize(
declaration_node const& n,
int indent,
bool include_metafunctions_list /* = false */,
bool verbose /* = true */
)
-> std::string
{
indent_spaces = 4;
// First compute the common parts
auto metafunctions = std::string{};
{
auto as_comment =
!n.metafunctions.empty()
&& !include_metafunctions_list;
if (as_comment) {
metafunctions += "/*";
}
for (auto& meta : n.metafunctions) {
metafunctions += " @" + pretty_print_visualize(*meta, indent);
}
if (as_comment) {
metafunctions += " */";
}
}
auto template_params = std::string{};
if (n.template_parameters) {
template_params += " " + pretty_print_visualize(*n.template_parameters, indent + 1, true);
}
auto requires_clause = std::string{};
if (n.requires_clause_expression) {
requires_clause += " requires (" + pretty_print_visualize(*n.requires_clause_expression, indent) + ")";
}
auto initializer = std::string{};
if (verbose) {
if (n.initializer) {
auto adjusted_indent = indent;
if (!n.name()) {
++adjusted_indent;
}
initializer = " =";
if (n.is_function() && n.is_constexpr) {
initializer += "=";
}
initializer += " " + pretty_print_visualize(*n.initializer, adjusted_indent);
if (initializer.ends_with(";;")) {
initializer.pop_back();
}
}
else if (!n.is_parameter()) {
initializer = ";";
}
}
// Then slot them in where appropriate
auto ret = std::string{""};
// Add an extra newline for spacing, unless this declaration
// is within a function body or is the first member of a type
if (
!n.parent_is_function()
&& !n.parent_is_object()
&& !n.is_parameter()
)
{
static declaration_node const* last_parent_type = {};
if (n.parent_is_type()) {
if (last_parent_type != n.get_parent()) {
last_parent_type = n.get_parent();
}
else {
ret += "\n";
}
}
else {
ret += "\n";
}
}
if (!n.is_parameter() && n.name()) {
ret += std::string{"\n"} + pre(indent);
}
switch (n.access) {
break;case accessibility::public_ : ret += "public ";
break;case accessibility::protected_ : ret += "protected ";
break;case accessibility::private_ : ret += "private ";
break;default: ; // default accessibility
}
if (n.identifier) {
ret += pretty_print_visualize(*n.identifier, indent);
}
if (n.is_parameter() && (n.has_name("this") || n.has_name("that"))) {
return ret;
}
if (n.is_variadic) {
ret += "...";
}
ret += ":";
if (n.is_function()) {
auto& func = std::get(n.type);
assert(func);
ret += metafunctions
+ template_params
+ pretty_print_visualize(*func, indent)
+ requires_clause
+ initializer;
}
else if (n.is_object()) {
auto& type_id = std::get(n.type);
assert(type_id);
ret += metafunctions
+ template_params
+ " " + pretty_print_visualize(*type_id, indent)
+ requires_clause
+ initializer;
}
else if (n.is_type()) {
auto& t = std::get(n.type);
assert(t);
ret += metafunctions
+ template_params
+ " " + pretty_print_visualize(*t)
+ initializer;
}
else if (n.is_namespace()) {
auto& t = std::get(n.type);
assert(t);
ret += "namespace = "
+ initializer;
}
else if (n.is_alias()) {
auto& a = std::get(n.type);
assert(a);
auto object_type_id = std::string{};
if (a->type_id) {
object_type_id += " " + pretty_print_visualize(*a->type_id, indent);
}
ret += template_params;
if (a->is_type_alias()) {
auto& t = std::get(a->initializer);
ret += " type"
+ requires_clause
+ " == "
+ pretty_print_visualize(*t, indent);
if (ret.back() != ';') {
ret += ";";
}
}
else if (a->is_namespace_alias()) {
auto& id = std::get(a->initializer);
assert(id);
ret += " namespace == "
+ pretty_print_visualize(*id, indent);
if (ret.back() != ';') {
ret += ";";
}
}
else if (a->is_object_alias()) {
auto& expr = std::get(a->initializer);
assert(expr);
ret += object_type_id
+ requires_clause
+ " == "
+ pretty_print_visualize(*expr, indent);
if (ret.back() != ';') {
ret += ";";
}
}
}
return ret;
}
auto pretty_print_visualize(translation_unit_node const& n)
-> std::string
{
auto ret = std::string{};
for (auto& decl : n.declarations) {
assert(decl);
ret += pretty_print_visualize(*decl, 0);
}
return ret;
}
//-----------------------------------------------------------------------
//
// parser: parses a section of Cpp2 code
//
//-----------------------------------------------------------------------
//
class parser
{
std::vector& errors;
std::set& includes;
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();
}
};
// Keep a stack of currently active declarations (still being parsed)
std::vector current_declarations = { nullptr };
struct current_declarations_stack_guard
{
parser* pars;
current_declarations_stack_guard(parser* p, declaration_node* decl)
: pars{ p }
{
assert(p);
assert(decl);
pars->current_declarations.push_back(decl);
}
~current_declarations_stack_guard()
{
pars->current_declarations.pop_back();
}
};
std::vector const* tokens = {};
stable_vector* generated_tokens = {};
int pos = 0;
std::string parse_kind = {};
// Keep track of the function bodies' locations - used to emit comments
// in the right pass (decide whether it's a comment that belongs with
// the declaration or is part of the definition)
struct function_body_extent {
lineno_t first;
lineno_t last;
auto operator(function_body_extent const&) const = default;
auto operator(int i) const { return first i; }
function_body_extent( lineno_t f, lineno_t l ): first{f}, last{l} { }
};
mutable std::vector function_body_extents;
mutable bool is_function_body_extents_sorted = false;
bool is_inside_call_expr = false;
public:
auto is_within_function_body(source_position p) const
{
// Short circuit the empty case, so that the rest of the function
// can unconditionally decrement any non-.begin() iterator once
if (function_body_extents.empty()) {
return false;
}
// Ensure we are sorted
if (!is_function_body_extents_sorted) {
std::sort(
function_body_extents.begin(),
function_body_extents.end()
);
is_function_body_extents_sorted = true;
}
// Find the first entry that is beyond pos, and back up one to
// the last that could be a match; this also ensures iter is
// dereferenceable, not .end()
auto iter = std::lower_bound(
function_body_extents.begin(),
function_body_extents.end(),
p.lineno+1
);
if (iter != function_body_extents.begin()) {
--iter;
}
// Now go backwards through the preceding entries until
// one includes pos or we move before pos
while (
iter->first first bool
{
parse_kind = "source file";
// Set per-parse state for the duration of this call
tokens = &tokens_;
generated_tokens = &generated_tokens_;
// Generate parse tree for this section as if a standalone TU
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", true, {}, true);
return false;
}
return true;
}
//-----------------------------------------------------------------------
// parse_one_statement
//
// tokens input tokens for this section of Cpp2 source code
// generated_tokens a shared place to store generated tokens
//
// Each call parses one statement and returns its parse tree.
//
auto parse_one_declaration(
std::vector const& tokens_,
stable_vector& generated_tokens_
)
-> std::unique_ptr
{
parse_kind = "source string during code generation";
// Set per-parse state for the duration of this call
tokens = &tokens_;
generated_tokens = &generated_tokens_;
try {
// Parse one declaration - we succeed if the parse succeeded,
// and there were no new errors, and all tokens were consumed
auto errors_size = std::ssize(errors);
pos = 0;
if (auto d = statement();
d
&& std::ssize(errors) == errors_size
&& done()
)
{
return d;
}
}
catch(std::runtime_error& e) {
error(e.what(), true, {}, true);
}
return {};
}
//-----------------------------------------------------------------------
// Get a set of pointers to just the declarations in the given token map section
//
auto get_parse_tree_declarations_in_range(std::vector const& token_range) const
-> std::vector< declaration_node const* >
{
assert (parse_tree);
assert (!token_range.empty());
auto first_line = token_range.front().position().lineno;
auto last_line = token_range.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) const -> void
{
parse_tree->visit(v, 0);
}
private:
//-----------------------------------------------------------------------
// Error reporting: Fed into the supplied this->errors 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,
source_position err_pos = {},
bool fallback = false
) const
-> void
{
auto m = std::string{msg};
auto i = done() ? -1 : 0;
assert (peek(i));
if (include_curr_token) {
m += std::string(" (at '") + peek(i)->to_string() + "')";
}
if (
err_pos == source_position{}
) {
err_pos = peek(i)->position();
}
errors.emplace_back( err_pos, m, false, fallback );
}
auto error(
std::string const& msg,
bool include_curr_token = true,
source_position err_pos = {},
bool fallback = false
) const
-> void
{
error( msg.c_str(), include_curr_token, err_pos, fallback );
}
bool has_error() {
return !errors.empty();
}
//-----------------------------------------------------------------------
// Token navigation: Only these functions should access this->token_
//
auto curr() const
-> token const&
{
if (done()) {
throw std::runtime_error("unexpected end of " + parse_kind);
}
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 inspect-expression
//G id-expression
//G literal
//G '(' expression-list ','? ')'
//G unnamed-declaration
//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 (auto lit = literal()) {
n->expr = std::move(lit);
return n;
}
if (curr().type() == lexeme::LeftParen
// If in the future (not now) we decide to allow braced-expressions
// || curr().type() == lexeme::LeftBrace
)
{
// Remember current position, because we may need to backtrack
auto start_pos = pos;
const bool inside_initializer = (
peek(-1) && peek(-1)->type() == lexeme::Assignment
);
auto open_paren = &curr();
auto close = close_paren_type(open_paren->type());
auto close_text = [&] () -> std::string { if (close == lexeme::RightParen) { return ")"; } return "}"; }();
next();
auto expr_list = expression_list(open_paren, lexeme::RightParen, inside_initializer);
if (!expr_list) {
//error("unexpected text - ( is not followed by an expression-list");
pos = start_pos; // backtrack
return {};
}
if (curr().type() != close_paren_type(open_paren->type())) {
//error("unexpected text - expression-list is not terminated by " + close_text);
pos = start_pos; // backtrack
return {};
}
expr_list->close_paren = &curr();
next();
if (
curr().type() != lexeme::Semicolon
&& curr().type() != lexeme::RightParen
&& curr().type() != lexeme::RightBracket
&& curr().type() != lexeme::Greater
&& curr().type() != lexeme::Comma
) {
expr_list->inside_initializer = false;
}
n->expression_list_is_fold_expression = expr_list->is_fold_expression();
expr_list->default_initializer =
is_inside_call_expr && std::empty(expr_list->expressions);
n->expr = std::move(expr_list);
return n;
}
if (auto decl = unnamed_declaration(curr().position(), false, true)) // captures are allowed
{
assert (
!decl->has_name()
&& "ICE: declaration should have been unnamed"
);
if (auto obj = std::get_if(&decl->type)) {
if ((*obj)->is_wildcard()) {
error("an unnamed object at expression scope currently cannot have a deduced type (the reason to create an unnamed object is typically to create a temporary of a named type)");
return {};
}
}
else if (auto func = std::get_if(&decl->type)) {
if ((*func)->returns.index() == function_type_node::list) {
error("an unnamed function at expression scope currently cannot return multiple values");
return {};
}
if ( // check if a single-expression function is followed by an extra second semicolon
decl->initializer && decl->initializer->is_expression()
&& !done() && curr().type() == lexeme::Semicolon
) {
error("a single-expression function should end with a single semicolon");
}
if (!(*func)->contracts.empty()) {
error("an unnamed function at expression scope currently cannot have contracts");
return {};
}
}
else {
error("(temporary alpha limitation) an unnamed declaration at expression scope must be a function or an object");
return {};
}
if (
peek(-1) && peek(-1)->type() != lexeme::RightBrace // it is not a braced function expression
&& curr().type() != lexeme::LeftParen // not imediatelly called
&& curr().type() != lexeme::RightParen // not as a last argument to function
&& curr().type() != lexeme::Comma // not as first or in-the-middle, function argument
&& curr().type() != lexeme::Greater // not as the last argument to template
&& curr().type() != lexeme::RightBracket // not as the last index argument
&& curr() != "is" // not as the argument to is
&& curr() != "as" // not as the argument to as
&& curr() != "do" // not as `for`'s `next`.
) {
// this is a fix for a short function syntax that should have double semicolon used
// (check comment in expression_statement(bool semicolon_required))
// We simulate double semicolon by moving back to single semicolon.
next(-1);
}
n->expr = std::move(decl);
return n;
}
return {};
}
//G postfix-expression:
//G primary-expression
//G postfix-expression postfix-operator
//G postfix-expression '[' expression-list? ','? ']'
//G postfix-expression '(' expression-list? ','? ')'
//G postfix-expression '.' id-expression
//G postfix-expression '..' id-expression
//G postfix-expression '..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->add(n.get());
}
// Remember current position, in case we need to backtrack
auto term_pos = pos;
auto term = postfix_expression_node::term{&curr()};
next();
if (term.op->type() == lexeme::LeftBracket)
{
term.expr_list = expression_list(term.op, lexeme::RightBracket);
if (!term.expr_list)
{
error("[ is not followed by a valid expression list");
return {};
}
if (curr().type() != lexeme::RightBracket)
{
error("unexpected text - [ is not properly matched by ]", true, {}, true);
return {};
}
term.expr_list->close_paren = &curr();
term.op_close = &curr();
next();
}
else if (term.op->type() == lexeme::LeftParen)
{
// Next should be an expression-list followed by a ')'
// If not, then this wasn't a call expression so backtrack to
// the '(' which will be part of the next grammar production
is_inside_call_expr = true;
term.expr_list = expression_list(term.op, lexeme::RightParen);
is_inside_call_expr = false;
if (
term.expr_list
&& curr().type() == lexeme::RightParen
)
{
term.expr_list->close_paren = &curr();
term.op_close = &curr();
next();
}
else
{
pos = term_pos; // backtrack
break;
}
}
else if (
term.op->type() == lexeme::Dot
|| term.op->type() == lexeme::DotDot
)
{
term.id_expr = id_expression();
if (!term.id_expr) {
error("'.' must be followed by a valid member name");
return {};
}
}
else if (
(
term.op->type() == lexeme::EllipsisLess
|| term.op->type() == lexeme::EllipsisEqual
)
&& n->expr->to_string() != "sizeof"
)
{
term.last_expr = expression();
}
n->ops.push_back( std::move(term) );
}
if (auto tok = n->expr->get_token();
tok
&& *tok == "this"
&& curr().type() == lexeme::Arrow
)
{
auto next_word = std::string{};
if (peek(1)) {
next_word = peek(1)->to_string();
}
error("'this' is not a pointer - write 'this." + next_word + "' instead of 'this->" + next_word + "'");
return {};
}
for (auto& e : expression_node::current_expressions) {
e->num_subexpressions += unchecked_narrow(std::ssize(n->ops));
}
return n;
}
//G prefix-expression:
//G postfix-expression
//G prefix-operator prefix-expression
//G 'sizeof' '...' ( identifier ')'
//GTODO 'sizeof' '(' type-id ')'
//GTODO 'alignof' '(' type-id ')'
//GTODO await-expression
//GTODO throws-expression
//G
auto prefix_expression()
-> std::unique_ptr
{
auto n = std::make_unique();
for ( ;
is_prefix_operator(curr());
next()
)
{
n->ops.push_back(&curr());
}
if ((n->expr = postfix_expression())) {
return n;
}
switch (curr().type())
{
break; case lexeme::PlusPlus:
error("prefix '++var' is not valid Cpp2; use postfix 'var++' instead", false);
break; case lexeme::MinusMinus:
error("prefix '--var' is not valid Cpp2; use postfix 'var--' instead", false);
break; case lexeme::Multiply:
error("prefix '*ptr' dereference is not valid Cpp2; use postfix 'ptr*' instead", false);
break; case lexeme::Ampersand:
error("prefix '&var' address-of is not valid Cpp2; use postfix 'var&' instead", false);
break; case lexeme::Tilde:
error("prefix '~var' is not valid Cpp2; use postfix 'var~' instead", false);
break; default: ;
}
return {};
}
//-----------------------------------------------------------------------
// Parsers for binary expressions
//
// The general /*binary*/-expression:
// /*term*/-expression { { /* operators at this precedence level */ } /*term*/-expression }*
//
template<
typename Binary,
typename ValidateOp,
typename TermFunc
>
auto binary_expression(
ValidateOp validate_op,
TermFunc term
)
-> std::unique_ptr
{
auto n = std::make_unique();
if ( (n->expr = term()) )
{
while (!done())
{
typename Binary::term t{};
// Remember current position, because we may need to backtrack if this next
// t.op might be valid but isn't followed by a valid term and so isn't for us
auto term_pos = pos;
// Most of these predicates only look at the current token and return
// true/false == whether this is a valid operator for this production
if constexpr( requires{ bool{ validate_op(curr()) }; } ) {
if (!validate_op(curr())) {
break;
}
t.op = &curr();
next();
}
// But for shift-expression we may synthesize >> from > >
// which will return a token* == a valid operator for this production
// (possibly a synthesized new token) or nullptr otherwise
else if constexpr( requires{ validate_op(curr(), *peek(1)); } ) {
if (
peek(1) == nullptr
|| (t.op = validate_op(curr(), *peek(1))) == nullptr
)
{
break;
}
// If we didn't consume the next token, we consumed the next two
if (t.op != &curr()) {
next();
}
next();
}
// And for assignment-expression we may synthesize >>= from > > =
// which will return a token* == a valid operator for this production
// (possibly a synthesized new token) or nullptr otherwise
else if constexpr (requires{ validate_op(curr(), *peek(1), *peek(2)); }) {
if (
peek(1) == nullptr
|| peek(2) == nullptr
|| (t.op = validate_op(curr(), *peek(1), *peek(2))) == nullptr
)
{
break;
}
// If we didn't consume the next token, we consumed the next three
if (t.op != &curr()) {
next();
next();
}
next();
}
// And it shouldn't be anything else
else {
assert (false && "ICE: validate_op should take one token and return bool, or two tokens and return token const* ");
}
// At this point we may have a valid t.op, so try to parse the next term...
// If it's not a valid term, then this t.op wasn't for us, pop it and return
// what we found (e.g., with "requires expression = {...}" the = is a grammar
// element and not an operator, it isn't and can't be part of the expression)
if ( !(t.expr = term()) ) {
pos = term_pos; // backtrack
return n;
}
// We got a term, so this op + term was for us
n->terms.push_back( std::move(t) );
}
return n;
}
return {};
}
//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()
-> auto
{
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()
-> auto
{
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(bool allow_angle_operators = true)
-> auto
{
if (allow_angle_operators) {
return binary_expression (
[this](token const& t, token const& next) -> token const* {
if (t.type() == lexeme::LeftShift) {
return &t;
}
if (
t.type() == lexeme::Greater
&& next.type() == lexeme::Greater
&& t.position() == source_position{ next.position().lineno, next.position().colno-1 }
)
{
generated_tokens->emplace_back( ">>", t.position(), lexeme::RightShift);
return &generated_tokens->back();
}
return nullptr;
},
[this]{ return additive_expression(); }
);
}
else {
return binary_expression (
[](token const&, token const&) -> token const* { return nullptr; },
[this]{ return additive_expression(); }
);
}
}
//G compare-expression:
//G shift-expression
//G compare-expression '' shift-expression
//G
auto compare_expression(bool allow_angle_operators = true)
-> auto
{
return binary_expression (
[](token const& t){ return t.type() == lexeme::Spaceship; },
[=,this]{ return shift_expression(allow_angle_operators); }
);
}
//G relational-expression:
//G compare-expression
//G relational-expression '' compare-expression
//G relational-expression '=' compare-expression
//G
auto relational_expression(bool allow_angle_operators = true)
-> auto
{
if (allow_angle_operators) {
return binary_expression (
[this](token const& t, token const& next) -> token const* {
if (
t.type() == lexeme::Greater
&& next.type() == lexeme::Assignment
&& t.position() == source_position{ next.position().lineno, next.position().colno - 1 }
)
{
generated_tokens->emplace_back(">=", t.position(), lexeme::GreaterEq);
return &generated_tokens->back();
}
if (
t.type() == lexeme::Less
|| t.type() == lexeme::LessEq
|| t.type() == lexeme::Greater
) {
return &t;
}
return nullptr;
},
[=,this]{ return compare_expression(allow_angle_operators); }
);
}
else {
return binary_expression (
[](token const&, token const&) -> token const* { return nullptr; },
[=,this]{ return compare_expression(allow_angle_operators); }
);
}
}
//G equality-expression:
//G relational-expression
//G equality-expression '==' relational-expression
//G equality-expression '!=' relational-expression
//G
auto equality_expression(bool allow_angle_operators = true, bool allow_equality = true)
-> auto
{
if (allow_equality) {
return binary_expression (
[](token const& t){ return t.type() == lexeme::EqualComparison || t.type() == lexeme::NotEqualComparison; },
[=,this]{ return relational_expression(allow_angle_operators); }
);
}
else {
return binary_expression (
[](token const& t){ return t.type() == lexeme::NotEqualComparison; },
[=,this]{ return relational_expression(allow_angle_operators); }
);
}
}
//G bit-and-expression:
//G equality-expression
//G bit-and-expression '&' equality-expression
//G
auto bit_and_expression(bool allow_angle_operators = true, bool allow_equality = true)
-> auto
{
return binary_expression (
[](token const& t){ return t.type() == lexeme::Ampersand; },
[=,this]{ return equality_expression(allow_angle_operators, allow_equality); }
);
}
//G bit-xor-expression:
//G bit-and-expression
//G bit-xor-expression '^' bit-and-expression
//G
auto bit_xor_expression(bool allow_angle_operators = true, bool allow_equality = true)
-> auto
{
return binary_expression (
[](token const& t){ return t.type() == lexeme::Caret; },
[=,this]{ return bit_and_expression(allow_angle_operators, allow_equality); }
);
}
//G bit-or-expression:
//G bit-xor-expression
//G bit-or-expression '|' bit-xor-expression
//G
auto bit_or_expression(bool allow_angle_operators = true, bool allow_equality = true)
-> auto
{
return binary_expression (
[](token const& t){ return t.type() == lexeme::Pipe; },
[=,this]{ return bit_xor_expression(allow_angle_operators, allow_equality); }
);
}
//G logical-and-expression:
//G bit-or-expression
//G logical-and-expression '&&' bit-or-expression
//G
auto logical_and_expression(bool allow_angle_operators = true, bool allow_equality = true)
-> auto
{
return binary_expression (
[](token const& t){ return t.type() == lexeme::LogicalAnd; },
[=,this]{ return bit_or_expression(allow_angle_operators, allow_equality); }
);
}
// 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_angle_operators = true, bool allow_equality = true)
-> auto
{
return binary_expression (
[](token const& t){ return t.type() == lexeme::LogicalOr; },
[=,this]{ return logical_and_expression(allow_angle_operators, allow_equality); }
);
}
//G assignment-expression:
//G logical-or-expression
//G assignment-expression assignment-operator logical-or-expression
//G
auto assignment_expression(
bool allow_angle_operators = true
)
-> std::unique_ptr
{
auto ret = std::unique_ptr{};
if (allow_angle_operators)
{
ret = binary_expression (
[this](token const& t, token const& next, token const& third) -> token const* {
if (is_assignment_operator(t.type())) {
return &t;
}
if (
t.type() == lexeme::Greater
&& next.type() == lexeme::Greater
&& third.type() == lexeme::Assignment
&& t.position() == source_position{ next.position().lineno, next.position().colno-1 }
)
{
generated_tokens->emplace_back( ">>=", t.position(), lexeme::RightShiftEq);
return &generated_tokens->back();
}
return nullptr;
},
[=,this]{
return logical_or_expression(allow_angle_operators);
}
);
}
else
{
ret = binary_expression (
[](token const&, token const&) -> token const* { return nullptr; },
[=,this]{
return logical_or_expression(allow_angle_operators);
}
);
}
if (ret && ret->terms_size() > 1) {
error("assignment cannot be chained - instead of 'c = b = a;', write 'b = a; c = b;'", false);
return {};
}
return ret;
}
//G expression: // eliminated 'condition:' - just use 'expression:'
//G assignment-expression
//GTODO try expression
//G
auto expression(
bool allow_angle_operators = true,
bool check_arrow = true
)
-> std::unique_ptr
{
auto n = std::make_unique();
{
expression_node::current_expressions.push_back(n.get());
auto guard = finally([&]{ expression_node::current_expressions.pop_back(); });
if (!(n->expr = assignment_expression(allow_angle_operators))) {
return {};
}
if (
check_arrow
&& !done()
&& curr().type() == lexeme::Arrow
)
{
error("'->' is not Cpp2 dereference syntax - write '*.' instead");
return {};
}
}
for (auto& e : expression_node::current_expressions) {
++e->num_subexpressions;
}
return n;
}
//G expression-list:
//G parameter-direction? expression
//G expression-list ',' parameter-direction? expression
//G
auto expression_list(
token const* open_paren,
lexeme closer,
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;
auto consume_optional_passing_style = [&] {
pass = passing_style::in;
if (auto dir = to_passing_style(curr());
(
dir == passing_style::out
|| dir == passing_style::move
|| dir == passing_style::forward
)
&& peek(1)
&& peek(1)->type() == lexeme::Identifier
)
{
pass = dir;
next();
}
};
consume_optional_passing_style();
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();
// Allow a trailing comma in the list
if (curr().type() == closer) {
break;
}
consume_optional_passing_style();
auto expr = expression();
if (!expr) {
error("invalid text in expression list", true, {}, true);
return {};
}
n->expressions.push_back( { pass, std::move(expr) } );
}
return n;
}
//G type-id:
//G type-qualifier-seq? 'type_of' '(' expression ')' is-type-constraint?
//G type-qualifier-seq? 'decltype' '(' expression ')' is-type-constraint?
//G type-qualifier-seq? qualified-id is-type-constraint?
//G type-qualifier-seq? unqualified-id is-type-constraint?
//G type-qualifier-seq? function-type is-type-constraint?
//G
//G type-qualifier-seq:
//G type-qualifier
//G type-qualifier-seq type-qualifier
//G
//G type-qualifier:
//G 'const'
//G '*'
//G
auto type_id(
bool allow_omitting_type_name = false,
bool allow_constraint = false,
bool allow_function_type = false
)
-> std::unique_ptr
{
auto n = std::make_unique();
// Remember current position, because we need to look ahead
auto start_pos = pos;
while (
(curr().type() == lexeme::Keyword && curr() == "const")
|| curr().type() == lexeme::Multiply
)
{
if (
curr() == "const"
&& !n->pc_qualifiers.empty()
&& *n->pc_qualifiers.back() == "const"
)
{
error("consecutive 'const' not allowed");
return {};
}
n->pc_qualifiers.push_back( &curr() );
next();
}
if (auto& c = curr();
c == "type_of"
|| c == "decltype"
)
{
if (
c == "decltype"
&& peek(1) && peek(1)->type() == lexeme::LeftParen
&& peek(2) && *peek(2) == "auto"
&& peek(3) && peek(3)->type() == lexeme::RightParen)
{
error(
"decltype(auto) is not needed in Cpp2 - for return types, use '-> forward _' instead",
false,
c.position()
);
}
if (auto id = postfix_expression();
id
&& id->ops.size() == 1
&& id->ops[0].expr_list->expressions.size() == 1
&& id->ops[0].expr_list->open_paren->type() == lexeme::LeftParen
)
{
n->pos = id->position();
n->id = std::move(id);
assert (n->id.index() == type_id_node::postfix);
}
else
{
error("'" + std::string{c} + "' must be followed by a single parenthesized expression", false, c.position());
return {};
}
}
else if (auto id = qualified_id()) {
n->pos = id->position();
n->id = std::move(id);
assert (n->id.index() == type_id_node::qualified);
}
else if (auto id = unqualified_id()) {
n->pos = id->position();
n->id = std::move(id);
assert (n->id.index() == type_id_node::unqualified);
}
else if (std::unique_ptr id = {};
allow_function_type
&& (id = function_type({})) != nullptr
)
{
n->pos = id->position();
n->id = std::move(id);
assert (n->id.index() == type_id_node::function);
}
else if (!allow_omitting_type_name) {
return {};
}
if (
allow_constraint
&& n->is_wildcard()
&& curr() == "is"
)
{
next();
if (!(n->constraint = type_id())) {
pos = start_pos; // backtrack
return {};
}
}
return n;
}
//G is-as-expression:
//G prefix-expression
//G is-as-expression is-type-constraint
//G is-as-expression is-value-constraint
//G is-as-expression as-type-cast
//GTODO type-id is-type-constraint
//G
//G is-type-constraint:
//G 'is' type-id
//G
//G is-value-constraint:
//G 'is' expression
//G
//G as-type-cast:
//G 'as' type-id
//G
auto is_as_expression()
-> std::unique_ptr
{
auto n = std::make_unique();
n->expr = prefix_expression();
if (!(n->expr)) {
return {};
}
auto is_found = false;
auto as_found = false;
while (
!done()
&& (curr() == "is" || curr() == "as")
)
{
if (curr() == "is") {
if (is_found) {
error("repeated 'is' are not allowed");
return {};
}
is_found = true;
}
else {
as_found = true;
}
if (is_found && as_found) {
error("mixed 'is' and 'as' are not allowed");
return {};
}
auto term = is_as_expression_node::term{};
term.op = &curr();
next();
if ((term.type = type_id()) != nullptr) {
;
}
else if ((term.expr = expression()) != nullptr) {
;
}
if (
*term.op == "as"
&& term.expr
)
{
error("'as' must be followed by a type-id, not an expression", false);
return {};
}
if (
!term.type
&& !term.expr
)
{
if (*term.op == "is") {
error( "'is' must be followed by a type-id or an expression", false);
}
else {
error( "'as' must be followed by a type-id", false);
}
return {};
}
n->ops.push_back( std::move(term) );
}
return n;
}
//G unqualified-id:
//G identifier
//G keyword
//G template-id
//GTODO operator-function-id
//G ...
//G
//G template-id:
//G identifier ''
//G
//G template-arguments:
//G template-arguments ',' template-argument
//G
//G template-argument:
//G # note: < > > are not allowed in expressions until new ( is opened
//G 'const' type-id
//G expression
//G type-id
//G
auto unqualified_id()
-> std::unique_ptr
{
// Handle the identifier
if (
curr().type() != lexeme::Identifier
&& curr().type() != lexeme::Keyword
&& curr().type() != lexeme::Cpp2FixedType
&& curr().type() != lexeme::Ellipsis
)
{
return {};
}
auto n = std::make_unique();
n->identifier = &curr();
auto one_past_identifier_end_pos = curr().position();
one_past_identifier_end_pos.colno += curr().length();
next();
// Handle the template-arguments if there is one
if (
curr().type() == lexeme::Less
&& curr().position() == one_past_identifier_end_pos
)
{
// Remember current position, in case this < is isn't a template argument list
auto start_pos = pos;
n->open_angle = curr().position();
next();
auto term = template_argument{};
do {
// If it doesn't start with * or const or ()-> (which can only be a type id),
// try parsing it as an expression
if (auto e = [&]{
if (
curr().type() == lexeme::Multiply // '*'
|| curr() == "const" // 'const'
|| (
curr().type() == lexeme::LeftParen
&& peek(1) && peek(1)->type() == lexeme::RightParen
&& peek(2) && peek(2)->type() == lexeme::Arrow
)
)
{
return decltype(expression()){};
}
return expression(false); // false == disallow unparenthesized relational comparisons in template args
}()
)
{
term.arg = std::move(e);
}
// Else try parsing it as a type id
else if (auto i = type_id(false, false, true)) {
term.arg = std::move(i);
}
// Else if we already got at least one template-argument, this is a
// ',' followed by something that isn't a valid template-arg
else if (std::ssize(n->template_args) > 0) {
error( "expected a template argument after ','", false);
return {};
}
// Else this is an empty '' list which is okay
else {
break;
}
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) {
// 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;
return n;
}
n->close_angle = curr().position();
next();
}
else {
if (*n->identifier == "co_await" || *n->identifier == "co_yield") {
error( "(temporary alpha limitation) coroutines are not yet supported in Cpp2", false);
return {};
}
}
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 we didn't have a leading :: and
// the next thing isn't :: or ., back out and report unsuccessful
term.id = unqualified_id();
if (
!term.id
|| (!term.scope_op && 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");
n->ids.push_back( std::move(term) );
for (
auto first_time_through_loop = true;
curr().type() == lexeme::Scope;
first_time_through_loop = false
)
{
auto term = qualified_id_node::term{ &curr() };
next();
term.id = unqualified_id();
if (!term.id) {
error("invalid text in qualified name", true, {}, true);
return {};
}
assert (term.id->identifier);
if (
first_time_through_loop
&& first_uid_was_std
&& term.scope_op->type() == lexeme::Scope
&& *term.id->identifier == "forward"
)
{
error("std::forward is not needed in Cpp2 - use 'forward' parameters/arguments instead", false);
return {};
}
n->ids.push_back( std::move(term) );
}
return n;
}
//G id-expression:
//G qualified-id
//G unqualified-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 literal:
//G integer-literal ud-suffix?
//G character-literal ud-suffix?
//G floating-point-literal ud-suffix?
//G string-literal ud-suffix?
//G boolean-literal ud-suffix?
//G pointer-literal ud-suffix?
//G user-defined-literal ud-suffix?
//G
auto literal()
-> std::unique_ptr
{
if (is_literal(curr().type())) {
auto n = std::make_unique();
n->pieces.push_back( &curr() );
next();
if (curr().type() == lexeme::UserDefinedLiteralSuffix) {
n->pieces.push_back(&curr());
next();
}
// String literals can have multiple chunks, such as "xyzzy" "plugh"
// (in Cpp1 these are merged in the preprocessor, in Cpp2 they're in the grammar)
if (n->pieces.front()->type() == lexeme::StringLiteral) {
while (curr().type() == lexeme::StringLiteral) {
n->pieces.push_back(&curr());
next();
if (curr().type() == lexeme::UserDefinedLiteralSuffix) {
n->pieces.push_back(&curr());
next();
}
}
}
return n;
}
return {};
}
//G expression-statement:
//G expression ';'
//G expression
//G
auto expression_statement(
bool semicolon_required,
bool allow_angle_operators = true
)
-> std::unique_ptr
{
auto n = std::make_unique();
expression_statement_node::current_expression_statements.push_back(n.get());
auto guard = finally([&]{ expression_statement_node::current_expression_statements.pop_back(); });
// Remember current position, in case this isn't a valid expression-statement
auto start_pos = pos;
if (!(n->expr = expression(allow_angle_operators, true))) {
return {};
}
if (
semicolon_required
&& (done() || 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
)
{
pos = start_pos; // backtrack
return {};
}
if (
!done()
&& curr().type() == lexeme::Semicolon
)
{
n->has_semicolon = true;
next();
}
return n;
}
//G selection-statement:
//G 'if' 'constexpr'? logical-or-expression compound-statement
//G 'if' 'constexpr'? logical-or-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 = logical_or_expression()) {
n->expression = std::move(e);
}
else {
error("invalid if condition", true, {}, true);
return {};
}
if (curr().type() != lexeme::LeftBrace) {
error("an if branch body must be enclosed with { }");
return {};
}
if (auto s = compound_statement()) {
n->true_branch = std::move(s);
}
else {
error("invalid if branch body", true, {}, true);
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 (
curr().type() != lexeme::LeftBrace
&& curr() != "if"
)
{
error("an else branch body must be enclosed with { }");
return {};
}
if (auto s = compound_statement( source_position{}, true )) {
n->false_branch = std::move(s);
n->has_source_false_branch = true;
}
else {
error("invalid else branch body", true, {}, true);
return {};
}
}
return n;
}
//G return-statement:
//G return expression? ';'
//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", true, {}, true);
return {};
}
n->expression = std::move(x);
// Final semicolon
if (curr().type() != lexeme::Semicolon) {
error("missing ; after return");
return {};
}
next();
return n;
}
//G iteration-statement:
//G label? 'while' logical-or-expression next-clause? compound-statement
//G label? 'do' compound-statement next-clause? 'while' logical-or-expression ';'
//G label? 'for' expression next-clause? 'do' unnamed-declaration
//G
//G label:
//G identifier ':'
//G
//G next-clause:
//G 'next' assignment-expression
//G
auto iteration_statement()
-> std::unique_ptr
{
auto n = std::make_unique();
// If the next three tokens are:
// identifier ':' 'for/while/do'
// then it's a labeled iteration statement
if (
curr().type() == lexeme::Identifier
&& peek(1)
&& peek(1)->type() == lexeme::Colon
&& peek(2)
&& peek(2)->type() == lexeme::Keyword
&& (*peek(2) == "while" || *peek(2) == "do" || *peek(2) == "for")
)
{
n->label = &curr();
next();
next();
}
if (
curr().type() != lexeme::Keyword
|| (curr() != "while" && curr() != "do" && curr() != "for")
)
{
return {};
}
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'", true, {}, true);
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", true, {}, true);
return false;
}
n->statements = 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 {}; }
if (!done() && curr().type() == lexeme::Semicolon) {
error("a loop body may not be followed by a semicolon (empty statements are not allowed)");
return {};
}
return n;
}
// Handle "do"
//
else if (*n->identifier == "do")
{
if (!handle_compound_statement ()) { return {}; }
if (!handle_optional_next_clause()) { return {}; }
if (curr() != "while") {
error("do loop body must be followed by 'while'");
return {};
}
next();
if (!handle_logical_expression ()) { return {}; }
if (curr().type() != lexeme::Semicolon) {
error("missing ; after do..while loop condition");
return {};
}
next();
return n;
}
// Handle "for"
//
else if (*n->identifier == "for")
{
n->range = expression();
if (!n->range) {
error("expected valid range expression after 'for'", true, {}, true);
return {};
}
if (curr().type() == lexeme::Comma) {
error("iterating over multiple ranges at once is not currently supported");
return {};
}
if (!handle_optional_next_clause()) { return {}; }
if (
curr() != "do"
|| !peek(1)
|| peek(1)->type() != lexeme::LeftParen
)
{
next();
if (curr().type() == lexeme::Colon) {
error("alpha design change note: 'for range' syntax has changed - please remove ':' and '=', for example: for args do (arg) std::cout ");
return {};
}
n->result_type = std::move(type);
}
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", true, {}, true);
return {};
}
if (
is_expression
&& !a->statement->is_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 jump-statement:
//G 'break' identifier? ';'
//G 'continue' identifier? ';'
//G
auto jump_statement()
-> std::unique_ptr
{
auto n = std::make_unique();
if (
curr() != "break"
&& curr() != "continue"
)
{
return {};
}
n->keyword = &curr();
next();
if (curr().type() == lexeme::Identifier) {
n->label = &curr();
next();
}
if (curr().type() != lexeme::Semicolon) {
error("expected ';' at end of '" + n->keyword->to_string() + "' statement");
return {};
}
next();
return n;
}
//G using-statement:
//G 'using' qualified-id ';'
//G 'using' id-expression '::' '_' ';'
//G
auto using_statement()
-> std::unique_ptr
{
auto n = std::make_unique();
if (curr() != "using") {
return {};
}
if (
peek(1)
&& *peek(1) == "namespace"
)
{
error("in Cpp2, write 'using the_namespace_name::_' to bring all names in the namespace into scope using the '_' wildcard");
return {};
}
n->keyword = &curr();
next();
auto id = id_expression();
if (!id) {
error(std::string{"expected valid id-expression after 'using"} + (n->for_namespace() ? " namespace" : "") + "'");
return {};
}
n->id = std::move(id);
if (!n->for_namespace() && !n->id->is_qualified()) {
error("'using' must specify a qualified name", false);
return {};
}
if (curr().type() != lexeme::Semicolon) {
error("expected ; at end of using-statement");
return {};
}
next();
return n;
}
//G statement:
//G selection-statement
//G using-statement
//G inspect-expression
//G return-statement
//G jump-statement
//G iteration-statement
//G compound-statement
//G contract-statement
//G declaration
//G expression-statement
//G
//G contract-statement:
//G contract ';'
//
//GTODO try-block
//G
auto statement(
bool semicolon_required = true,
source_position equal_sign = source_position{},
bool parameters_allowed = false,
compound_statement_node* compound_parent = nullptr,
bool allow_angle_operators = true
)
-> std::unique_ptr
{
if (!done() && curr().type() == lexeme::Semicolon) {
error("empty statement is not allowed - remove extra semicolon");
return {};
}
auto n = std::make_unique(compound_parent);
// If a parameter list is allowed here, try to parse one
if (parameters_allowed) {
n->parameters = parameter_declaration_list(false, true, false, true);
if (n->parameters) {
for (auto& param : n->parameters->parameters) {
if (
param->direction() != passing_style::in
&& param->direction() != passing_style::inout
&& param->direction() != passing_style::copy
)
{
error("(temporary alpha limitation) parameters scoped to a block/statement must be 'in' (the default), 'copy', or 'inout'", false);
return {};
}
}
}
}
// Now handle the rest of the statement
if (auto s = selection_statement()) {
n->statement = std::move(s);
assert (n->is_selection());
return n;
}
else if (auto s = using_statement()) {
n->statement = std::move(s);
assert (n->is_using());
return n;
}
else if (auto i = inspect_expression(false)) {
n->statement = std::move(i);
assert (n->is_inspect());
return n;
}
else if (auto s = return_statement()) {
n->statement = std::move(s);
assert (n->is_return());
return n;
}
else if (auto s = jump_statement()) {
n->statement = std::move(s);
assert (n->is_jump());
return n;
}
else if (auto s = iteration_statement()) {
n->statement = std::move(s);
assert (n->is_iteration());
return n;
}
else if (auto s = compound_statement(equal_sign)) {
n->statement = std::move(s);
assert (n->is_compound());
return n;
}
else if (auto s = contract()) {
if (*s->kind != "assert") {
error("only 'assert' contracts are allowed at statement scope");
return {};
}
if (curr().type() != lexeme::Semicolon) {
error("missing ';' after contract-statement");
return {};
}
next();
n->statement = std::move(s);
assert (n->is_contract());
return n;
}
else if (auto s = declaration(semicolon_required, false, false, n.get())) {
n->statement = std::move(s);
assert (n->is_declaration());
return n;
}
else if (auto s = expression_statement(semicolon_required, allow_angle_operators)) {
n->statement = std::move(s);
assert (n->is_expression());
return n;
}
else {
if (
curr().type() == lexeme::Identifier
&& peek(1)
&& peek(1)->type() == lexeme::Comma
)
{
error("declaring multiple names at once is not currently supported");
}
return {};
}
}
//G compound-statement:
//G '{' statement-seq? '}'
//G
//G statement-seq:
//G statement
//G statement-seq statement
//G
auto compound_statement(
source_position equal_sign = source_position{},
bool allow_single_unbraced_statement = false
)
-> std::unique_ptr
{
const bool is_braced = curr().type() == lexeme::LeftBrace;
if (
!is_braced
&& !allow_single_unbraced_statement
)
{
return {};
}
auto n = std::make_unique();
if (!is_braced) {
n->body_indent = curr().position().colno-1;
}
else if (peek(1)) {
n->body_indent = peek(1)->position().colno-1;
}
// 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();
}
if (is_braced) {
next();
}
while (
curr().type() != lexeme::RightBrace
&& (
is_braced
|| std::ssize(n->statements) < 1
)
)
{
// Only inside a compound-statement, a
// contained statement() may have parameters
auto s = statement(true, source_position{}, true, n.get());
if (!s) {
// Only add a general error when no specific one already exists
if(!has_error()) {
error("invalid statement encountered inside a compound-statement", true);
}
return {};
}
n->statements.push_back( std::move(s) );
}
if (is_braced) {
assert(curr().type() == lexeme::RightBrace);
n->close_brace = curr().position();
next();
}
return n;
}
//G parameter-declaration:
//G this-specifier? parameter-direction? 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(
parameter_declaration_list_node const* my_list,
bool is_returns = false,
bool is_named = true,
bool is_template = true,
bool is_statement = false
)
-> std::unique_ptr
{
// Remember current position, because we may need to backtrack if this is just
// a parenthesized expression statement, not a statement parameter list
auto start_pos = pos;
auto n = std::make_unique(my_list);
n->pass =
is_returns ? passing_style::out :
passing_style::in;
n->pos = curr().position();
// Handle optional this-specifier
//
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();
}
// Handle optional parameter-direction
//
if (auto dir = to_passing_style(curr());
dir != passing_style::invalid
)
{
if (is_template) {
error("a template parameter cannot have a passing style (it is always implicitly 'in')");
return {};
}
if (is_returns)
{
if (dir == passing_style::in) {
error("a return value cannot be 'in'");
return {};
}
if (dir == passing_style::in_ref) {
error("a return value cannot be 'in_ref'");
return {};
}
if (dir == passing_style::copy) {
error("a return value cannot be 'copy'");
return {};
}
if (dir == passing_style::inout) {
error("a return value cannot be 'inout'");
return {};
}
if (dir == passing_style::move) {
error("a return value cannot be 'move' (it is implicitly 'move'-out)");
return {};
}
}
else {
if (dir == passing_style::forward_ref) {
error("a parameter cannot be 'forward_ref'");
return {};
}
}
if (
!is_named
&& dir == passing_style::out
)
{
error("(temporary alpha limitation) an unnamed function cannot have an 'out' parameter");
return {};
}
n->pass = dir;
next();
}
// Now the main declaration
//
if (!(n->declaration = declaration(false, true, is_template, {}, false))) {
pos = start_pos; // backtrack
return {};
}
n->declaration->is_a_statement_parameter = is_statement;
// And some error checks
//
if (n->declaration->is_function()) {
error("a parameter cannot be a function", false);
return {};
}
if (
n->mod != parameter_declaration_node::modifier::none
&& !n->declaration->has_name("this")
)
{
error( "only a 'this' parameter may be declared implicit, virtual, override, or final", false );
return {};
}
if (
n->declaration->has_name("this")
&& n->pass != passing_style::in
&& n->pass != passing_style::inout
&& n->pass != passing_style::out
&& n->pass != passing_style::move
)
{
error( "a 'this' parameter must be in, inout, out, or move", false );
return {};
}
if (
n->declaration->has_name("that")
&& n->pass != passing_style::in
&& n->pass != passing_style::move
)
{
error( "a 'that' parameter must be in or move", false );
return {};
}
// The only parameter type that could be const-qualified is a 'copy' parameter, because
// only it is always truly its own variable, so it makes sense to let the user qualify it;
// all the other parameter types are conceptually (usually actually) bound to their args
if (
!is_returns
&& n->declaration->is_const()
&& n->pass != passing_style::copy
)
{
switch (n->pass) {
break;case passing_style::in:
error( "an 'in' parameter is always const, 'const' isn't needed and isn't allowed", false );
break;case passing_style::in_ref:
error( "an 'in_ref' parameter is always const, 'const' isn't needed and isn't allowed", false );
break;case passing_style::inout:
error( "an 'inout' parameter can't be const, if you do want it to be const then use 'in' instead", false );
break;case passing_style::out:
error( "an 'out' parameter can't be const, otherwise it can't be initialized in the function body", false );
break;case passing_style::move:
error( "a 'move' parameter can't be const, otherwise it can't be moved from in the function body", false );
break;case passing_style::forward:
error( "a 'forward' parameter shouldn't be const, because it passes along the argument's actual const-ness (and actual value category)", false );
break;default:
assert (false && "ICE: missing case");
}
return {};
}
if (n->pass == passing_style::forward_ref) {
error("'forward_ref' is for a single anonymous deduced return value: -> forward_ref _", false);
return {};
}
if (is_named && is_returns) {
auto tok = n->name();
assert(tok);
if (tok->type() != lexeme::Identifier) {
error("expected identifier, not '" + tok->to_string() + "'",
false, tok->position());
return {};
}
else if (n->declaration->has_wildcard_type()) {
error("return parameter '" + tok->to_string() + "' must have a type",
false, tok->position());
return {};
}
}
return n;
}
//G parameter-declaration-list:
//G '(' parameter-declaration-seq? ','? ')'
//G
//G parameter-declaration-seq:
//G parameter-declaration
//G parameter-declaration-seq ',' parameter-declaration
//G
auto parameter_declaration_list(
bool is_returns = false,
bool is_named = true,
bool is_template = false,
bool is_statement = false,
bool is_function_typeid = false
)
-> std::unique_ptr
{
// Remember current position, because we need to look ahead in
// the case of seeing whether a local statement starts with a
// parameter list, since finding that it doesn't (it's some other
// parenthesized expression) is not an error, just backtrack
auto start_pos = pos;
auto opener = lexeme::LeftParen;
auto closer = lexeme::RightParen;
if (is_template) {
opener = lexeme::Less;
closer = lexeme::Greater;
}
if (curr().type() != opener) {
return {};
}
auto n = std::make_unique(is_function_typeid, is_template, is_statement);
n->open_paren = &curr();
next();
auto param = std::unique_ptr();
auto count = 1;
while ((param = parameter_declaration(n.get(), is_returns, is_named, is_template, is_statement)) != nullptr)
{
param->ordinal = count;
++count;
if (
std::ssize(n->parameters) > 1
&& n->parameters.back()->has_name("that")
)
{
error("'that' may not be followed by any additional parameters", false);
return {};
}
n->parameters.push_back( std::move(param) );
if (curr().type() == closer) {
break;
}
// Allow a trailing comma in the list
else if (
curr().type() == lexeme::Comma
&& peek(1)
&& peek(1)->type() == closer
)
{
next();
break;
}
else if (curr().type() != lexeme::Comma) {
if (is_statement) {
pos = start_pos; // backtrack
}
else {
error("expected ',' in parameter list", true, {}, true);
}
return {};
}
next();
}
if (curr().type() != closer) {
if (is_statement) {
pos = start_pos; // backtrack
}
else {
error("invalid parameter list", true, {}, true);
}
return {};
}
n->close_paren = &curr();
next();
return n;
}
//G contract:
//G contract-kind contract-group? ':' '(' logical-or-expression ')'
//G contract-kind contract-group? ':' '(' logical-or-expression ',' expression ')'
//G
//G contract-group:
//G ''
//G
//G contract-flags:
//G ',' id-expression contract-flags?
//G
//G contract-kind: one of
//G 'pre' 'post' 'assert'
//G
auto contract()
-> std::unique_ptr
{
auto n = std::make_unique(curr().position());
auto guard = capture_groups_stack_guard(this, &n->captures);
if (
curr() != "pre"
&& curr() != "post"
&& curr() != "assert"
)
{
return {};
}
n->kind = &curr();
next();
// Check if there's a
if (curr().type() == lexeme::Less) {
next();
if (auto id = id_expression()) {
n->group = std::move(id);
}
else {
error("invalid contract group after 'flags.push_back( std::move(id) );
}
else {
error("invalid contract tag in list");
return {};
}
}
if (curr().type() != lexeme::Greater) {
error("expected '>' after contract group");
return {};
}
next();
}
if (curr().type() != lexeme::LeftParen) {
error("expected '(' before the contract condition");
return {};
}
next();
auto condition = logical_or_expression();
if (!condition) {
error("invalid contract condition", true, {}, true);
return {};
}
n->condition = std::move(condition);
// Now check for the optional string message
if (curr().type() == lexeme::Comma) {
next();
n->message = expression();
if (!n->message) {
error("a contract violation message must be a valid string expression", true, {}, true);
return {};
}
}
// Consume trailing comma
if (curr().type() == lexeme::Comma) {
next();
}
if (curr().type() != lexeme::RightParen) {
error("expected ')' at the end of the contract");
return {};
}
next();
return n;
}
//G function-type:
//G parameter-declaration-list throws-specifier? return-list? contract-seq?
//G
//G throws-specifier:
//G 'throws'
//G
//G return-list:
//G expression-statement
//G '->' parameter-direction? type-id
//G '->' parameter-declaration-list
//G
//G contract-seq:
//G contract
//G contract-seq contract
//G
auto function_type(
declaration_node* my_decl, // if null, this is a type-id not a declaration
bool is_named = true
)
-> std::unique_ptr
{
auto n = std::make_unique( my_decl );
// Parameters
auto parameters = parameter_declaration_list(false, is_named, false, false, my_decl == nullptr);
if (!parameters) {
return {};
}
n->parameters = std::move(parameters);
// Optional "throws"
if (
curr().type() == lexeme::Keyword
&& curr() == "throws"
)
{
if (
n->is_move()
|| n->is_swap()
|| n->is_destructor()
)
{
error( "(experimental restriction) Cpp2 currently does not allow a move, swap, or destructor function to be designated 'throws'" );
return {};
}
n->throws = true;
next();
}
// If we're in an actual function declaration (not just a function type-id),
// and are not at a '->' or 'requires' or contract and what follows is
// an expression, this is a ":(params) expr" shorthand function syntax
if (
my_decl
&& curr().type() != lexeme::Arrow
&& curr() != "requires"
&& (curr() != "pre" && curr() != "post")
)
{
auto start_pos = pos;
auto at_an_expression = expression() != nullptr;
pos = start_pos; // backtrack no matter what, we're just peeking here
if (at_an_expression) {
error("an '=' is now required before every function body, including when the body is an individual expression - for example, change 'f: () expr;' to 'f: () = expr;'");
return {};
}
}
// Optional returns
if (curr().type() == lexeme::Arrow)
{
next();
if (auto pass = to_passing_style(curr());
pass != passing_style::invalid
)
{
if (
pass != passing_style::move
&& pass != passing_style::forward
&& pass != passing_style::forward_ref
)
{
error("only 'move' and 'forward' return passing style are allowed from functions");
return {};
}
next();
if (auto t = type_id()) {
if (
pass == passing_style::forward_ref
&& !t->is_wildcard()
)
{
error("'forward_ref' is for a single anonymous deduced return value: -> forward_ref _", false);
return {};
}
n->returns = function_type_node::single_type_id{ std::move(t), pass };
assert(n->returns.index() == function_type_node::id);
}
else {
auto msg = std::string("'");
msg += to_string_view(pass);
error(msg + "' must be followed by a type-id");
return {};
}
}
else if (auto t = type_id())
{
if (
t->get_token()
&& t->get_token()->to_string() == "auto"
)
{
auto name = std::string{"f"};
if (my_decl && my_decl->name()) {
name = my_decl->name()->to_string();
}
errors.emplace_back(
curr().position(),
"to define a function " + name + " with deduced return type, write '" + name + ": ( /* arguments */ ) -> _ = { /* function body */ }'"
);
return {};
}
n->returns = function_type_node::single_type_id{ std::move(t), passing_style::move };
assert(n->returns.index() == function_type_node::id);
}
else if (auto returns_list = parameter_declaration_list(true, is_named))
{
if (!my_decl) {
error("a function type alias with multiple/named return values is not yet supported");
return {};
}
if (std::ssize(returns_list->parameters) < 1) {
error("an explicit return value list cannot be empty", true, {}, true);
return {};
}
n->returns = std::move(returns_list);
assert(n->returns.index() == function_type_node::list);
}
else
{
error("missing function return after ->");
return {};
}
}
// Pre/post conditions
while (auto c = contract())
{
if (!my_decl) {
error("a function type alias with contracts is not yet supported");
return {};
}
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;
}
auto apply_type_metafunctions( declaration_node& decl )
-> bool;
//G unnamed-declaration:
//G ':' meta-functions? template-parameters? function-type requires-clause? '=' statement
//G ':' meta-functions? template-parameters? function-type statement
//G ':' meta-functions? template-parameters? type-id? requires-clause? '=' statement
//G ':' meta-functions? template-parameters? type-id
//G ':' meta-functions? template-parameters? 'final'? 'type' requires-clause? '=' statement
//G ':' 'namespace' '=' statement
//G
//G meta-functions:
//G '@' id-expression
//G meta-functions '@' id-expression
//G
//G requires-clause:
//G # note: for aliases, == is not allowed in expressions until new ( is opened
//G 'requires' logical-or-expression
//G
//G template-parameters:
//G ''
//G
auto unnamed_declaration(
source_position start,
bool semicolon_required = true,
bool captures_allowed = false,
bool named = false,
bool is_parameter = false,
bool is_template_parameter = false,
std::unique_ptr id = {},
accessibility access = {},
bool is_variadic = false,
statement_node* my_stmt = {},
bool semicolon_allowed = true
)
-> std::unique_ptr
{
auto n = std::make_unique( current_declarations.back() );
n->pos = start;
n->identifier = std::move(id);
n->access = access;
n->is_variadic = is_variadic;
n->my_statement = my_stmt;
// If we're in a type scope and the next token is ';', treat this as if
// ': _;' without an initializer.
// This is for type metafunctions that want to use the incomplete name-only
// declaration, and transform it to something else. If unchanged the
// incomplete declaration will be rejected later by sema.check rule.
if (
n->parent_is_type()
&& curr().type() == lexeme::Semicolon
)
{
n->type = std::make_unique();
assert (n->is_object());
next();
return n;
}
// For a template parameter, ':' is not required and
// we default to ': type'
if (
is_template_parameter
&& curr().type() != lexeme::Colon
)
{
// So invent the "type" token
generated_text.push_back("type");
generated_tokens->push_back({
generated_text.back().c_str(),
std::ssize(generated_text.back()),
start,
lexeme::Identifier
});
// So we can create the type_node
auto t = std::make_unique( &generated_tokens->back() );
n->type = std::move(t);
assert (n->is_type());
// That's it, we're done here
return n;
}
// For 'this' and 'that' parameters ':' is not allowed and we'll use the default ': _'
if (
n->identifier
&& is_parameter
&& (
*n->identifier->identifier == "this"
|| *n->identifier->identifier == "that"
)
&& curr().type() == lexeme::Colon
)
{
error("a 'this' or 'that' parameter knows its type, no ':' is allowed here", false);
return {};
}
// For an ordinary parameter, ':' is not required and
// we default to ': _' - i.e., deduced with no initializer
if (
is_parameter
&& curr().type() != lexeme::Colon
)
{
// So invent the "_" token
generated_text.push_back("_");
generated_tokens->push_back({
generated_text.back().c_str(),
std::ssize(generated_text.back()),
start,
lexeme::Identifier
});
// So we can create the typeid_id_node and its unqualified_id_node
auto gen_id = std::make_unique();
gen_id->identifier = &generated_tokens->back();
auto type = std::make_unique();
type->pos = start;
type->id = std::move(gen_id);
n->type = std::move(type);
assert (n->is_object());
// That's it, we're done here
return n;
}
// Otherwise, the next token must be ':'
if (curr().type() != lexeme::Colon) {
return {};
}
next();
if (curr() == "union") {
error("unsafe 'union' is not supported in Cpp2 - write '@union' to apply Cpp2's safe 'union' type metafunction instead, or use std::variant");
return {};
}
// Next is an optional metafunctions clause
while (curr() == "@") {
next();
auto idx = id_expression();
if (!idx) {
error("'@' must be followed by a metafunction name", false);
return {};
}
n->metafunctions.push_back( std::move(idx) );
}
auto guard =
captures_allowed
? std::make_unique(this, &n->captures)
: std::unique_ptr()
;
auto guard2 = current_declarations_stack_guard(this, n.get());
// Next is an optional template parameter list
if (curr().type() == lexeme::Less) {
auto template_parameters = parameter_declaration_list(false, false, true);
if (!template_parameters) {
error("invalid template parameter list");
return {};
}
n->template_parameters = std::move(template_parameters);
}
// Next is an an optional type
auto deduced_type = false;
// It could be "type", declaring a user-defined type
if (
curr() == "type"
|| (
curr() == "final"
&& peek(1) && *peek(1) == "type"
)
)
{
n->type = std::make_unique( &curr(), curr() == "final" );
if (curr() == "final") {
next();
}
next();
if (
is_parameter
&& !is_template_parameter
)
{
error("a normal parameter cannot be a 'type' - did you mean to put this in a < > template parameter list?");
return {};
}
assert (n->is_type());
}
// Or a function type, declaring a function - and tell the function whether it's in a user-defined type
else if (auto t = function_type(n.get(), named))
{
n->type = std::move(t);
assert (n->is_function());
if (!n->metafunctions.empty()) {
errors.emplace_back(
n->metafunctions.front()->position(),
"(temporary alpha limitation) metafunctions are currently not supported on functions, only on types"
);
return {};
}
}
// Or a namespace
else if (curr() == "namespace")
{
n->type = std::make_unique( &curr() );
assert (n->type.index() == declaration_node::a_namespace);
next();
if (!n->metafunctions.empty()) {
errors.emplace_back(
n->metafunctions.front()->position(),
"(temporary alpha limitation) metafunctions are currently not supported on namespaces, only on types"
);
return {};
}
}
// Or just a (possibly empty == deduced) type-id
else if (auto t = type_id(true, !is_template_parameter, true))
{
if (
t->get_token()
&& t->get_token()->to_string() == "auto"
)
{
auto name = std::string{"v"};
if (n->name()) {
name = n->name()->to_string();
}
errors.emplace_back(
curr().position(),
"to define a variable " + name + " with deduced type, write '" + name + " := /* initializer */;'"
);
return {};
}
n->type = std::move(t);
assert (n->is_object());
deduced_type = n->has_wildcard_type();
if (!n->metafunctions.empty()) {
errors.emplace_back(
n->metafunctions.front()->position(),
"(temporary alpha limitation) metafunctions are currently not supported on objects, only on types"
);
return {};
}
if (curr().type() == lexeme::LeftBracket) {
error("C-style array types are not allowed, use std::array instead");
return {};
}
}
else {
// Only add a general error when no specific one already exists
if (!has_error()) {
error("syntax error - unknown declaration");
}
return {};
}
{
// Next is optionally a requires clause
if (curr() == "requires")
{
if (
n->is_type()
&& !n->template_parameters
)
{
error("'requires' is not allowed on a type that does not have a template parameter list");
return {};
}
if (n->is_namespace())
{
error("'requires' is not allowed on a namespace");
return {};
}
n->requires_pos = curr().position();
next();
auto e = logical_or_expression(true, false);
if (!e) {
error("'requires' must be followed by an expression");
return {};
}
n->requires_clause_expression = std::move(e);
}
// Next is optionally = or == followed by an initializer
// If there is no = or ==
if (
!done()
&& curr().type() != lexeme::Assignment
&& curr().type() != lexeme::EqualComparison
)
{
if (
n->is_type()
&& !is_template_parameter
)
{
error("a user-defined type must have an = initializer");
return {};
}
// Then there may be a semicolon
// If there is a semicolon...
if (!done() && curr().type() == lexeme::Semicolon) {
// If it's allowed, eat it
if (semicolon_allowed) {
next();
}
// Otherwise, diagnose an error
else {
error("unexpected semicolon after declaration", {}, {}, {});
return {};
}
}
// Otherwise if there isn't one and it was required, diagnose an error
else if (semicolon_required) {
if (curr().type() == lexeme::LeftBrace) {
error("expected '=' before '{' - did you mean '= {' ?", true, {}, true);
}
else {
error("missing ';' at end of declaration or '=' at start of initializer", true, {}, true);
}
return {};
}
}
// There was an = or ==, so eat it and continue
else
{
n->equal_sign = curr().position();
if (curr().type() == lexeme::EqualComparison) {
if (!n->is_function()) {
error("syntax error at '==' - did you mean '='?");
return {};
}
n->is_constexpr = true;
}
next();
if (auto t = std::get_if(&n->type);
t
&& (*t)->is_pointer_qualified()
)
{
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");
}
}
// deduced_type == true means that the type will be deduced,
// represented using an empty type-id
if (
deduced_type
&& peek(1)
)
{
auto& type = std::get(n->type);
// object initialized by the address of the curr() object
if (peek(1)->type() == lexeme::Ampersand)
{
type->address_of = &curr();
}
// object initialized by (potentially multiple) dereference of the curr() object
else if (peek(1)->type() == lexeme::Multiply)
{
type->dereference_of = &curr();
for (int i = 1; peek(i) && peek(i)->type() == lexeme::Multiply; ++i) {
type->dereference_cnt += 1;
}
}
else if (
// object initialized by the result of the function call (and it is not unnamed function)
(peek(1)->type() == lexeme::LeftParen && curr().type() != lexeme::Colon)
|| curr().type() == lexeme::Identifier // or by the object (variable that the type need to be checked)
)
{
type->suspicious_initialization = &curr();
}
}
if (!(n->initializer = statement(
semicolon_required,
n->equal_sign,
false,
nullptr,
!is_template_parameter
)))
{
error(
"ill-formed initializer",
true, {}, true
);
return {};
}
}
}
// If this is a single-expression function, the default return type is '-> forward _ '
// Except for 'main' and 'operator=', whose return types are defined by the language
if (auto is_main = !n->parent_declaration && n->has_name("main");
n->is_function()
&& !is_main
&& !n->has_name("operator=")
&& n->initializer
&& !n->initializer->is_compound()
)
{
n->set_default_return_type_to_forward_wildcard();
}
// If this is a non-local named function with a single-statement body,
// followed by a non-declaration statement, they probably forgot { }
// so give a nicer diagnostic
if (
!done()
&& n->is_function()
&& n->has_name()
&& !n->parent_is_function()
&& n->initializer
&& !n->initializer->is_compound()
)
{
auto start_pos = pos;
auto stmt = statement();
auto at_a_statement = stmt != nullptr && !stmt->is_declaration();
pos = start_pos; // backtrack no matter what, we're just peeking here
if (at_a_statement) {
error("in this scope, a single-expression function body cannot be immediately followed by a statement - did you forget to put { } braces around a multi-statement function body?", false);
return {};
}
}
if (
n->is_type()
&& n->initializer
&& !done() && curr().type() == lexeme::Semicolon
)
{
if (n->initializer->is_compound() && n->has_name()) {
error("Cpp2 does not allow a semicolon after the closing brace of a type definition");
return {};
}
}
// A type initializer must be a compound expression
if (
n->is_type()
&& !is_parameter
&& (
!n->initializer
|| !n->initializer->is_compound()
)
)
{
errors.emplace_back(
n->position(),
"a user-defined type initializer must be a compound-expression consisting of declarations"
);
return {};
}
// If this is a type with metafunctions, apply those
if (n->is_type()) {
if (!apply_type_metafunctions(*n)) {
error(
"error encountered while applying type metafunctions",
false, {}, true
);
return {};
}
}
if (
n->is_function()
&& n->initializer
&& !done() && curr().type() == lexeme::Semicolon
)
{
if (n->initializer->is_compound() && n->has_name()) {
error("a braced function body may not be followed by a semicolon (empty statements are not allowed)");
return {};
} else if (n->initializer->is_expression()) {
error("a single-expression function should end with a single semicolon");
return {};
}
}
// If this is a function with a list of multiple/named return values,
// and the function body's end doesn't already have "return" as the
// last statement, then generate "return;" as the last statement
if (auto func = std::get_if(&n->type);
func
&& n->initializer
&& (*func)->returns.index() == function_type_node::list
)
{
if (!n->initializer->is_compound()) {
error(
"a function with named return value(s) must have a full { } body",
false,
{},
true
);
return {};
}
auto& body = std::get(n->initializer->statement);
if (
body->statements.empty()
|| !body->statements.back()->is_return()
)
{
auto last_pos = n->position();
if (!body->statements.empty()) {
last_pos = body->statements.back()->position();
}
++last_pos.lineno;
generated_tokens->emplace_back( "return", last_pos, lexeme::Keyword);
auto ret = std::make_unique();
ret->identifier = &generated_tokens->back();
auto stmt = std::make_unique();
stmt->statement = std::move(ret);
body->statements.push_back(std::move(stmt));
}
}
// If this is a function, record its extents
if (n->is_function()) {
function_body_extents.emplace_back(
n->equal_sign.lineno,
peek(-1)->position().lineno
);
}
return n;
}
//G alias:
//G ':' template-parameters? 'type' requires-clause? '==' type-id ';'
//G ':' 'namespace' '==' id-expression ';'
//G ':' template-parameters? type-id? requires-clause? '==' expression ';'
//G
//GT ':' function-type '==' expression ';'
//GT # See commit 63efa6ed21c4d4f4f136a7a73e9f6b2c110c81d7 comment
//GT # for why I don't see a need to enable this yet
//
auto alias()
-> std::unique_ptr
{
// Remember current position, because we need to look ahead
auto start_pos = pos;
auto n = std::make_unique( current_declarations.back() );
if (curr().type() != lexeme::Colon) {
return {};
}
next();
// Next is an optional template parameter list
if (curr().type() == lexeme::Less) {
auto template_parameters = parameter_declaration_list(false, false, true);
if (!template_parameters) {
pos = start_pos; // backtrack
return {};
}
n->template_parameters = std::move(template_parameters);
}
auto a = std::make_unique( &curr() );
// Next must be 'type', 'namespace', a type-id, or we're at the 'requires' or '=='
if (curr() == "type")
{
next();
}
else if (curr() == "namespace")
{
next();
if (n->template_parameters) {
errors.emplace_back(
curr().position(),
"a namespace or namespace alias cannot have template parameters"
);
return {};
}
}
else if (curr().type() != lexeme::EqualComparison && curr() != "requires")
{
a->type_id = type_id();
if (!a->type_id) {
pos = start_pos; // backtrack
return {};
}
}
// Next is optionally a requires clause
if (curr() == "requires")
{
if (
n->is_type_alias()
&& !n->template_parameters
)
{
error("'requires' is not allowed on a type alias that does not have a template parameter list");
return {};
}
if (n->is_namespace_alias())
{
error("'requires' is not allowed on a namespace alias");
return {};
}
n->requires_pos = curr().position();
next();
auto e = logical_or_expression(true, false);
if (!e) {
error("'requires' must be followed by an expression");
return {};
}
n->requires_clause_expression = std::move(e);
}
// Now we should be at the '==' if this is an alias
if (curr().type() == lexeme::EqualComparison) {
next();
}
else {
if (a->type->type() != lexeme::EqualComparison) {
pos = start_pos; // backtrack
return {};
}
}
assert(peek(-1)->type() == lexeme::EqualComparison);
if (
n->parent_is_type()
&& *a->type == "namespace"
)
{
errors.emplace_back(
curr().position(),
"a namespace alias cannot appear in a type scope"
);
return {};
}
// Finally, pick up the initializer
// Type alias
if (*a->type == "type")
{
auto t = type_id(false, false, true);
if (!t) {
errors.emplace_back(
curr().position(),
"a 'type ==' alias declaration must be followed by a type name"
);
return {};
}
if (
t->is_wildcard()
|| ( t->get_token() && t->get_token()->to_string() == "auto" )
) {
errors.emplace_back(
curr().position(),
"a 'type ==' alias declaration must be followed by a type name (not a wildcard _ nor auto)"
);
return {};
}
a->initializer = std::move(t);
}
// Namespace alias
else if (*a->type == "namespace")
{
if (auto qid = id_expression()) {
a->initializer = std::move(qid);
}
else {
errors.emplace_back(
curr().position(),
"a 'namespace ==' alias declaration must be followed by a namespace name (id-expression)"
);
return {};
}
}
// Object alias
else if (
a->type_id
|| a->type->type() == lexeme::EqualComparison
)
{
auto e = expression();
if (!e) {
errors.emplace_back(
curr().position(),
"an object '==' alias declaration must be followed by an expression"
);
return {};
}
a->initializer = std::move(e);
}
// Anything else shouldn't be possible
else {
assert(false && "ICE: should be unreachable - invalid alias declaration");
return {};
}
// And the final ceremonial semicolon
if (curr() != ";") {
errors.emplace_back(
curr().position(),
"';' expected at end of alias declaration"
);
return {};
}
next();
n->type = std::move(a);
return n;
}
//G declaration:
//G access-specifier? identifier '...'? unnamed-declaration
//G access-specifier? identifier alias
//G
//G access-specifier:
//G public
//G protected
//G private
//G
auto declaration(
bool semicolon_required = true,
bool is_parameter = false,
bool is_template_parameter = false,
statement_node* my_stmt = {},
bool semicolon_allowed = true
)
-> std::unique_ptr
{
if (done()) { return {}; }
// Remember current position, because we need to look ahead
auto start_pos = pos;
auto n = std::unique_ptr{};
// This scope is to ensure that once we've moved 'id' into the
// declaration_node, we don't access the moved-from local name
// (and similar hygiene for 'access' though that one doesn't matter as much)
// The reason to move 'id' into unnamed_declaration() is so that
// it can conveniently perform some checks that refer to the name
{
auto access = accessibility::default_;
if (curr() == "public") {
access = accessibility::public_;
next();
}
else if (curr() == "protected") {
access = accessibility::protected_;
next();
}
else if (curr() == "private") {
access = accessibility::private_;
next();
}
// If they wrote an access-specifier, see if they put a ':'
// after it out of Cpp1 habit (there's no colon in Cpp2)
if (
access != accessibility::default_
&& curr().type() == lexeme::Colon
)
{
errors.emplace_back(
curr().position(),
"':' is not allowed after an access-specifier"
);
return {};
}
auto id = unqualified_id();
if (!id) {
return {};
}
if (id->to_string() == "...") {
errors.emplace_back(
curr().position(),
"a variadic declaration must have a name - did you forget to write a name before '...'?"
);
return {};
}
auto is_variadic = false;
if (curr().type() == lexeme::Ellipsis) {
is_variadic = true;
next();
}
// Provide some useful Cpp1->Cpp2 migration diagnostics for common mistakes
//
if (
id->get_token()
&& *id->get_token() == "auto"
&& curr().type() != lexeme::Colon
)
{
auto name = std::string{"v"};
if (peek(0) && peek(0)->type() == lexeme::Identifier) {
name = peek(0)->to_string();
}
errors.emplace_back(
curr().position(),
"to define a variable " + name + " of type T, write '" + name + ": T = /* initializer */'"
);
return {};
}
if (
id->get_token()
&& *id->get_token() == "namespace"
&& curr().type() != lexeme::Colon
)
{
auto name = std::string{"N"};
if (peek(0)) {
name = peek(0)->to_string();
}
errors.emplace_back(
curr().position(),
"to define a namespace " + name + ", write '" + name + " : namespace = { /*contents*/ }'"
);
return {};
}
if (
id->get_token()
&& (
*id->get_token() == "class"
|| *id->get_token() == "struct"
)
&& curr().type() != lexeme::Colon
)
{
auto name = std::string{"C"};
if (peek(0)) {
name = peek(0)->to_string();
}
errors.emplace_back(
curr().position(),
"to define a type " + name + ", write '" + name + " : type = { /*body*/ }'"
);
return {};
}
// Now proceed...
//
// First see if it's an alias declaration
n = alias();
if (n) {
if (is_parameter) {
errors.emplace_back(
curr().position(),
"a parameter declaration may not be an alias declaration"
);
return {};
}
if (is_variadic) {
errors.emplace_back(
curr().position(),
"an alias declaration may not be variadic"
);
return {};
}
n->pos = start_pos;
n->identifier = std::move(id);
n->access = access;
return n;
}
// Otherwise, this is a normal declaration
n = unnamed_declaration(
start_pos,
semicolon_required,
false,
true,
is_parameter,
is_template_parameter,
std::move(id),
access,
is_variadic,
my_stmt,
semicolon_allowed
);
if (!n) {
pos = start_pos; // backtrack
return {};
}
}
// Note: Do this after trying to parse this as a declaration, for parse backtracking
if (
*n->identifier->identifier == "that"
&& (
!is_parameter
|| is_template_parameter
)
)
{
errors.emplace_back(
n->identifier->position(),
"'that' may only be declared as an ordinary function parameter"
);
return {};
}
// Cache some context
n->is_a_template_parameter = is_template_parameter;
n->is_a_parameter = is_parameter;
return n;
}
//G declaration-seq:
//G declaration
//G declaration-seq declaration
//G
//G translation-unit:
//G declaration-seq?
//
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;
}
public:
//-----------------------------------------------------------------------
// debug_print
//
auto debug_print(std::ostream& o) const
-> void;
};
//-----------------------------------------------------------------------
//
// 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} { indent_spaces = 2; }
};
//-----------------------------------------------------------------------
//
// Visitor for printing a parse tree
//
//-----------------------------------------------------------------------
//
class parse_tree_printer : printing_visitor
{
using printing_visitor::printing_visitor;
public:
auto start(token const& n, int indent) -> void
{
o