// Copyright (c) Herb Sutter
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//===========================================================================
// cppfront
//===========================================================================
#include "sema.h"
#include
#include
#include
namespace cpp2 {
// Defined out of line here just to avoid bringing into the headers,
// so that we can't accidentally start depending on iostreams in the compiler body
auto cmdline_processor::print(std::string_view s, int width) -> void
{
if (width > 0) {
std::cout void
{
assert (is_open() && "ICE: printer must be open before printing");
print( s );
}
//-----------------------------------------------------------------------
// Print a Cpp1 line, which should be at lineno
//
auto print_cpp1( std::string_view s, lineno_t line ) -> void
{
assert (is_open() && "ICE: printer must be open before printing");
// Keep track of whether the last thing we printed was Cpp2
last_was_cpp2 = false;
// Always start a Cpp1 line on its own new line
ensure_at_start_of_new_line();
// If we are out of sync with the current logical line number,
// emit a #line directive to re-sync
if (curr_pos.lineno != line) {
print_line_directive( line );
curr_pos.lineno = line;
}
// Print the line
assert (curr_pos.colno == 1);
print( s );
print( "\n" );
}
//-----------------------------------------------------------------------
// Start a new Cpp2 section, which should start at lineno
//
auto start_cpp2(lineno_t line) -> void
{
assert (is_open() && "ICE: printer must be open before printing");
// Because the blank/comment lines before a Cpp2 code section are part
// of the Cpp2 section, and not printed in thedeclarations-only pass
if (!last_was_cpp2 && declarations_only) {
print ("\n");
}
// Keep track of whether the last thing we printed was Cpp2
last_was_cpp2 = true;
// Always start a Cpp2 section on its own new line
ensure_at_start_of_new_line();
// If we are out of sync with the current logical line number,
// emit a #line directive to re-sync
if (curr_pos.lineno != line) {
print_line_directive( line );
curr_pos.lineno = line;
}
assert (curr_pos.colno == 1);
}
//-----------------------------------------------------------------------
// Print a Cpp2 item, which should be at pos
//
auto print_cpp2(std::string_view s, source_position pos) -> void
{
assert (is_open() && "ICE: printer must be open before printing");
// Keep track of whether the last thing we printed was Cpp2
// Note: We should have been switched to Cpp2 with a `start_cpp2` call
assert(last_was_cpp2 && "ICE: didn't call start_cpp2 to begin a Cpp2 section");
last_was_cpp2 = true;
// Skip alignment work if we're capturing emitted text
if (emit_target_stack.empty())
{
// Remember where we are
auto last_pos = curr_pos;
// We may want to adjust the position based on (1) a position preemption request
// or else (2) to repeat a similar adjustment we discovered on the previous line
auto adjusted_pos = pos;
// (1) See if there's a position preemption request, if so use it up
// For now, the preempt position use cases are about overriding colno
// and only on the same line. In the future, we might have more use cases.
if (!preempt_pos.empty()) {
if (preempt_pos.back().lineno == pos.lineno) {
adjusted_pos.colno = preempt_pos.back().colno;
}
}
// (2) Otherwise, see if there's a previous line's offset to repeat
// If we moved to a new line, then this is the first
// non-comment non-whitespace text on the new line
else if (last_pos.lineno == pos.lineno-1 && enable_indent_heuristic) {
// If the last line had a request for this colno, remember its actual offset
constexpr int sentinel = -100;
auto last_line_offset = sentinel;
for(auto i = 0;
i < std::ssize(prev_line_info.requests) && prev_line_info.requests[i].requested sentinel) {
adjusted_pos.colno += last_line_offset;
}
}
enable_indent_heuristic = true;
// If we're changing lines, start accumulating this new line's request/actual adjustment info
if (last_pos.lineno < adjusted_pos.lineno) {
prev_line_info = { curr_pos.lineno, { } };
}
align_to(adjusted_pos);
// Remember the requested and actual offset columns for this item
prev_line_info.requests.push_back( req_act_info( pos.colno /*requested*/ , curr_pos.colno /*actual*/ - pos.colno ) );
}
print(s, pos);
}
//-----------------------------------------------------------------------
// Position override control functions
//
// Use this position instead of the next supplied one
// Useful when Cpp1 syntax is emitted in a different order/verbosity
// than Cpp2 such as with declarations
//
auto preempt_position_push(source_position pos) -> void
{
preempt_pos.push_back( pos );
}
auto preempt_position_pop() -> void
{
assert(!preempt_pos.empty());
preempt_pos.pop_back();
}
// Add (or, if negative, subtract) padding for the current line only
//
auto add_pad_in_this_line(colno_t extra) -> void
{
pad_for_this_line += extra;
}
// Enable indent heuristic for just this line
//
auto disable_indent_heuristic_for_next_text() -> void {
enable_indent_heuristic = false;
}
// Ignore position information, usually when emitting generated code
// such as generated multi-return type structs
//
auto ignore_alignment(bool ignore, int indent = 0) -> void
{
// We'll only ever call this in local non-nested true/false pairs.
// If we ever want to generalize (support nesting, or make it non-brittle),
// wrap this in a push/pop stack.
if (ignore) {
ignore_align = true;
ignore_align_indent = indent;
ignore_align_lineno = curr_pos.lineno; // push state
}
else {
ignore_align = false;
ignore_align_indent = 0;
if (ignore_align_lineno != curr_pos.lineno) {
ensure_at_start_of_new_line();
print_line_directive(ignore_align_lineno+1);
}
curr_pos.lineno = ignore_align_lineno+1; // pop state
}
}
//-----------------------------------------------------------------------
// Modal state control functions
//
// In the first pass we will print only declarations (the default)
// For the second pass this function enables printing definitions
//
auto enable_definitions() -> void {
declarations_only = false;
}
auto doing_declarations_only() const -> bool {
return declarations_only;
}
// Provide an option to store to a given string instead, which is
// useful for capturing Cpp1-formatted output for generated code
//
auto emit_to_string( std::string* target = {} ) -> void {
if (target) {
emit_string_targets.push_back( target );
emit_target_stack.push_back(target_type::string);
}
else {
emit_string_targets.pop_back();
emit_target_stack.pop_back();
}
}
// Provide an option to store to a vector, which is
// useful for postfix expression which have to mix unwrapping operators
// with emitting sub-elements such as expression lists
//
auto emit_to_text_chunks( std::vector* target = {} ) -> void {
if (target) {
emit_text_chunks_targets.push_back( target );
emit_target_stack.push_back(target_type::chunks);
}
else {
emit_text_chunks_targets.pop_back();
emit_target_stack.pop_back();
}
}
};
//-----------------------------------------------------------------------
//
// cppfront: a compiler instance
//
//-----------------------------------------------------------------------
//
class cppfront
{
std::string sourcefile;
std::vector errors;
// For building
//
cpp2::source source;
cpp2::tokens tokens;
cpp2::parser parser;
cpp2::sema sema;
bool source_loaded = true;
bool last_postfix_expr_was_pointer = false;
bool violates_bounds_safety = false;
bool violates_initialization_safety = false;
bool suppress_move_from_last_use = false;
struct arg_info {
passing_style pass = passing_style::in;
token const* ptoken = {};
};
std::vector current_args = { {} };
// For lowering
//
positional_printer printer;
bool in_definite_init = false;
bool in_parameter_list = false;
std::string function_return_name;
std::vector function_returns;
parameter_declaration_list_node single_anon;
// special value - hack for now to note single-anon-return type kind in this function_returns working list
std::vector function_requires_conditions;
std::vector in_non_rvalue_context = { false };
std::vector need_expression_list_parens = { true };
auto push_need_expression_list_parens( bool b ) -> void { need_expression_list_parens.push_back(b); }
auto pop_need_expression_list_parens() -> void { assert(std::ssize(need_expression_list_parens) > 1);
need_expression_list_parens.pop_back(); }
auto should_add_expression_list_parens() -> bool { assert(!need_expression_list_parens.empty());
return need_expression_list_parens.back(); }
auto consumed_expression_list_parens() -> void { if( std::ssize(need_expression_list_parens) > 1 )
need_expression_list_parens.back() = false; }
public:
//-----------------------------------------------------------------------
// Constructor
//
// filename the source file to be processed
//
cppfront(std::string const& filename)
: sourcefile{ filename }
, source{ errors }
, tokens{ errors }
, parser{ errors }
, sema{ errors }
{
// "Constraints enable creativity in the right directions"
// sort of applies here
//
if (!sourcefile.ends_with(".cpp2") && !sourcefile.ends_with(".h2"))
{
errors.emplace_back(
source_position(-1, -1),
"source filename must end with .cpp2 or .h2: " + sourcefile
);
}
// Load the program file into memory
//
else if (!source.load(sourcefile))
{
if (errors.empty()) {
errors.emplace_back(
source_position(-1, -1),
"file not found: " + sourcefile
);
}
source_loaded = false;
}
else
{
// Tokenize
//
tokens.lex(source.get_lines());
// Parse
//
try
{
for (auto const& [line, entry] : tokens.get_map()) {
if (!parser.parse(entry, tokens.get_generated())) {
errors.emplace_back(
source_position(line, 0),
"parse failed for section starting here"
);
}
}
// Sema
parser.visit(sema);
if (!sema.apply_local_rules()) {
violates_initialization_safety = true;
}
}
catch (std::runtime_error& e) {
errors.emplace_back(
source_position(-1, -1),
e.what()
);
}
}
}
//-----------------------------------------------------------------------
// lower_to_cpp1
//
// Emits the target file with the last '2' stripped
//
struct lower_to_cpp1_ret {
lineno_t cpp1_lines = 0;
lineno_t cpp2_lines = 0;
};
auto lower_to_cpp1() -> lower_to_cpp1_ret
{
auto ret = lower_to_cpp1_ret{};
// Only lower to Cpp1 if we haven't already encountered errors
if (!errors.empty()) {
return {};
}
// Now we'll open the Cpp1 file
auto cpp1_filename = sourcefile.substr(0, std::ssize(sourcefile) - 1);
if (!flag_cpp1_filename.empty()) {
cpp1_filename = flag_cpp1_filename; // use override if present
}
printer.open(
sourcefile,
cpp1_filename,
tokens.get_comments(),
source.has_cpp2()
);
if (!printer.is_open()) {
errors.emplace_back(
source_position{},
"could not open output file " + cpp1_filename
);
return {};
}
// Only emit extra lines if we actually have Cpp2, because
// we want pure-Cpp1 files to pass through with zero changes
if (source.has_cpp2()) {
if (!flag_clean_cpp1) {
printer.print_extra( "// ----- Cpp2 support -----\n" );
}
if (flag_use_source_location) {
printer.print_extra( "#define CPP2_USE_SOURCE_LOCATION Yes\n" );
}
if (flag_cpp2_only) {
printer.print_extra( "#define CPP2_USE_MODULES Yes\n" );
}
if (flag_no_exceptions) {
printer.print_extra( "#define CPP2_NO_EXCEPTIONS Yes\n" );
}
if (flag_no_rtti) {
printer.print_extra( "#define CPP2_NO_RTTI Yes\n" );
}
printer.print_extra( "#include \"cpp2util.h\"\n\n" );
}
auto map_iter = tokens.get_map().cbegin();
auto hpp_includes = std::string{};
// First, echo the non-Cpp2 parts
//
for (
lineno_t curr_lineno = 0;
auto const& line : source.get_lines()
)
{
// Skip dummy line we added to make 0-vs-1-based offsets readable
if (curr_lineno != 0)
{
// If it's a Cpp1 line, emit it
if (line.cat != source_line::category::cpp2)
{
++ret.cpp1_lines;
if (flag_cpp2_only &&
!line.text.empty() &&
line.cat != source_line::category::comment &&
line.cat != source_line::category::import
)
{
if (line.cat == source_line::category::preprocessor) {
if (!line.text.ends_with(".h2\"")) {
errors.emplace_back(
source_position(curr_lineno, 1),
"pure-cpp2 switch disables the preprocessor, including #include (except of .h2 files) - use import instead (note: 'import std;' is implicit in -pure-cpp2)"
);
}
}
else {
errors.emplace_back(
source_position(curr_lineno, 1),
"pure-cpp2 switch disables Cpp1 syntax"
);
}
return {};
}
if (line.cat == source_line::category::preprocessor && line.text.ends_with(".h2\"")) {
// Strip off the 2"
auto h_include = line.text.substr(0, line.text.size()-2);
printer.print_cpp1( h_include + "\"", curr_lineno );
hpp_includes += h_include + "pp\"\n";
}
else {
printer.print_cpp1( line.text, curr_lineno );
}
}
// If it's a Cpp2 line...
else {
++ret.cpp2_lines;
// We should be in a position to emit a set of Cpp2 declarations
if (map_iter != tokens.get_map().cend() && map_iter->first /*line*/ identifier != n.identifier) &&
// and this variable was uninitialized
!decl->initializer &&
// and it's either a non-parameter or an out parameter
(!decl->parameter || (decl->parameter && decl->parameter->pass == passing_style::out))
)
{
printer.print_cpp2(".value()", n.position());
}
}
else if (in_synthesized_multi_return) {
printer.print_cpp2(".value()", n.position());
}
if (add_std_move || add_std_forward) {
printer.print_cpp2(")", n.position());
}
}
//-----------------------------------------------------------------------
//
auto emit(qualified_id_node const& n) -> void
{
// Implicit "cpp2::" qualification of "unique.new" and "shared.new"
if (n.ids.size() == 2 &&
(*n.ids[0].id->identifier == "unique" || *n.ids[0].id->identifier == "shared") &&
*n.ids[1].scope_op == "." &&
*n.ids[1].id->identifier == "new"
)
{
printer.print_cpp2("cpp2::", n.position());
}
auto ident = std::string{};
printer.emit_to_string(&ident);
for (auto const& id : n.ids)
{
if (id.scope_op) {
emit(*id.scope_op);
}
emit(*id.id, false, true, true); // inform the unqualified-id that it's qualified
}
printer.emit_to_string();
printer.print_cpp2( ident, n.position() );
}
//-----------------------------------------------------------------------
//
auto emit(type_id_node const& n, source_position pos = {}) -> void
{
if (pos == source_position{}) {
pos = n.position();
}
if (n.is_wildcard()) {
printer.print_cpp2("auto", pos);
}
else {
try_emit(n.id, false, false);
try_emit(n.id);
try_emit(n.id);
}
for (auto i = n.pc_qualifiers.rbegin(); i != n.pc_qualifiers.rend(); ++i) {
if ((**i) == "const") { printer.print_cpp2(" ", pos); }
emit(**i, false, pos);
}
}
//-----------------------------------------------------------------------
//
auto emit(id_expression_node const& n, bool is_local_name = true) -> void
{
try_emit(n.id);
try_emit(n.id, false, is_local_name);
}
//-----------------------------------------------------------------------
//
auto emit(
compound_statement_node const& n,
std::vector const& function_prolog = {},
std::vector const& function_epilog = {},
colno_t function_indent = 1
)
-> void
{
auto pos = n.open_brace;
pos.lineno -= std::ssize(function_prolog);
printer.print_cpp2( "{", pos );
if (!function_prolog.empty()) {
printer.ignore_alignment( true, function_indent + 4 );
auto pos = source_position{};
if (!n.statements.empty()) {
pos = n.statements.front()->position();
}
for (auto& loc : function_prolog) {
printer.print_cpp2("\n", pos);
printer.print_cpp2(loc, pos);
}
printer.ignore_alignment( false );
}
for (auto const& x : n.statements) {
assert(x);
emit(*x);
}
if (!function_epilog.empty()) {
printer.ignore_alignment( true, function_indent + 4 );
auto pos = source_position{};
if (!n.statements.empty()) {
pos = n.statements.front()->position();
}
for (auto& loc : function_epilog) {
printer.print_cpp2("\n", pos);
printer.print_cpp2(loc, pos);
}
printer.ignore_alignment( false );
}
printer.print_cpp2( "}", n.close_brace );
}
//-----------------------------------------------------------------------
//
auto emit(inspect_expression_node const& n, bool is_expression) -> void
{
auto constexpr_qualifier = std::string{};
if (n.is_constexpr) {
constexpr_qualifier = "constexpr ";
}
// If this is an expression, it will have an explicit result type,
// and we need to start the lambda that we'll immediately invoke
auto result_type = std::string{};
if (is_expression) {
assert(n.result_type);
printer.emit_to_string(&result_type);
emit(*n.result_type);
printer.emit_to_string();
printer.print_cpp2("[&] () -> " + result_type + " ", n.position());
}
printer.print_cpp2("{ " + constexpr_qualifier + "auto&& __expr = ", n.position());
assert(n.expression);
emit(*n.expression);
printer.print_cpp2(";", n.position());
assert(n.identifier && *n.identifier == "inspect");
assert(!n.alternatives.empty());
auto found_wildcard = false;
for (auto first = true; auto&& alt : n.alternatives)
{
assert(alt && alt->is_as_keyword);
if (!first) {
printer.print_cpp2("else ", alt->position());
}
first = false;
auto id = std::string{};
printer.emit_to_string(&id);
if (alt->type_id) {
emit(*alt->type_id);
}
else {
assert(alt->value);
emit(*alt->value);
}
printer.emit_to_string();
assert (*alt->is_as_keyword == "is" || *alt->is_as_keyword == "as");
// TODO: pick up 'as' next, for now just do 'is'
if (*alt->is_as_keyword == "is")
{
// Stringize the expression-statement now...
auto statement = std::string{};
printer.emit_to_string(&statement);
emit(*alt->statement);
printer.emit_to_string();
// ... and jettison the final ; for an expression-statement
while (!statement.empty() && (statement.back() == ';' || isspace(statement.back()))) {
statement.pop_back();
}
replace_all( statement, "cpp2::as_(";
suffix = ")" + suffix;
}
}
// Else it's "is value", emit "cpp2::is(expr, value)"
else
{
assert(i->expr);
prefix += "cpp2::" + i->op->to_string(true) + "(";
suffix = ", " + print_to_string(*i->expr) + ")" + suffix;
}
}
if (as_on_literal) {
auto last_pos = prefix.rfind('>'); assert(last_pos != prefix.npos);
prefix.insert(last_pos, ", " + print_to_string(*n.expr));
}
printer.print_cpp2(prefix, n.position());
if (wildcard_found) {
printer.print_cpp2("true", n.position());
}
else if(!as_on_literal) {
emit(*n.expr);
}
printer.print_cpp2(suffix, n.position());
}
//-----------------------------------------------------------------------
//
template<
String Name,
typename Term
>
auto emit(binary_expression_node const& n) -> void
{
assert(n.expr);
assert(n.terms.empty() || n.terms.front().op);
// If this is relational comparison
if (!n.terms.empty() &&
(
n.terms.front().op->type() == lexeme::Less ||
n.terms.front().op->type() == lexeme::LessEq ||
n.terms.front().op->type() == lexeme::Greater ||
n.terms.front().op->type() == lexeme::GreaterEq ||
n.terms.front().op->type() == lexeme::EqualComparison ||
n.terms.front().op->type() == lexeme::NotEqualComparison
)
)
{
auto const& op = *n.terms.front().op;
// If this is one (non-chained) comparison, just emit it directly
if (std::ssize(n.terms) < 2)
{
assert (std::ssize(n.terms) == 1);
// emit < = > as cmp_*(a,b) calls (if selected)
if (flag_safe_comparisons) {
switch (op.type()) {
break;case lexeme::Less:
printer.print_cpp2( "cpp2::cmp_less(", n.position());
break;case lexeme::LessEq:
printer.print_cpp2( "cpp2::cmp_less_eq(", n.position());
break;case lexeme::Greater:
printer.print_cpp2( "cpp2::cmp_greater(", n.position());
break;case lexeme::GreaterEq:
printer.print_cpp2( "cpp2::cmp_greater_eq(", n.position());
break;default:
;
}
}
emit(*n.expr);
// emit == and != as infix a @ b operators (since we don't have
// any checking/instrumentation we want to do for those)
if (flag_safe_comparisons) {
switch (op.type()) {
break;case lexeme::EqualComparison:
case lexeme::NotEqualComparison:
emit(op);
break;default:
printer.print_cpp2( ",", n.position() );
}
}
else {
emit(op);
}
emit(*n.terms.front().expr);
if (flag_safe_comparisons) {
switch (op.type()) {
break;case lexeme::Less:
case lexeme::LessEq:
case lexeme::Greater:
case lexeme::GreaterEq:
printer.print_cpp2( ")", n.position() );
break;default:
;
}
}
return;
}
// Else if this is a chained comparison, emit it as a lambda,
// to get single evaluation via the lambda capture
else
{
// To check for the valid chains: all =, or all ==
auto found_lt = 0; // < and and >=
auto found_eq = 0; // ==
auto count = 0;
auto const* lhs = n.expr.get();
auto lhs_name = "_" + std::to_string(count);
auto lambda_capture = lhs_name + " = " + print_to_string(*lhs);
auto lambda_body = std::string{};
for (auto const& term : n.terms)
{
assert(term.op && term.expr);
++count;
auto rhs_name = "_" + std::to_string(count);
// Not the first expression? Insert a "&&"
if (found_lt + found_gt + found_eq > 0) {
lambda_body += " && ";
}
// Remember what we've seen
switch (term.op->type()) {
break;case lexeme::Less:
case lexeme::LessEq:
found_lt = 1;
break;case lexeme::Greater:
case lexeme::GreaterEq:
found_gt = 1;
break;case lexeme::EqualComparison:
found_eq = 1;
break;default:
;
}
// emit < = > as cmp_*(a,b) calls (if selected)
if (flag_safe_comparisons) {
switch (term.op->type()) {
break;case lexeme::Less:
lambda_body += "cpp2::cmp_less(";
break;case lexeme::LessEq:
lambda_body += "cpp2::cmp_less_eq(";
break;case lexeme::Greater:
lambda_body += "cpp2::cmp_greater(";
break;case lexeme::GreaterEq:
lambda_body += "cpp2::cmp_greater_eq(";
break;default:
;
}
}
auto rhs_expr = print_to_string(*term.expr);
lambda_body += lhs_name;
// emit == and != as infix a @ b operators (since we don't have
// any checking/instrumentation we want to do for those)
if (flag_safe_comparisons) {
switch (term.op->type()) {
break;case lexeme::EqualComparison:
lambda_body += *term.op;
break;case lexeme::NotEqualComparison:
errors.emplace_back(
n.position(),
"!= comparisons cannot appear in a comparison chain (see https://wg21.link/p0893)"
);
return;
break;default:
lambda_body += ",";
}
}
else {
lambda_body += *term.op;
}
lambda_capture += ", " + rhs_name + " = " + rhs_expr;
lambda_body += rhs_name;
lhs = term.expr.get();
lhs_name = rhs_name;
if (flag_safe_comparisons) {
switch (term.op->type()) {
break;case lexeme::Less:
case lexeme::LessEq:
case lexeme::Greater:
case lexeme::GreaterEq:
lambda_body += ")";
break;default:
;
}
}
}
assert(found_lt + found_gt + found_eq > 0);
if (found_lt + found_gt + found_eq != 1) {
errors.emplace_back(
n.position(),
"a comparison chain must be all < and and >=, or all == (see https://wg21.link/p0893)"
);
return;
}
printer.print_cpp2( "[" + lambda_capture + "]{ return " + lambda_body + "; }()", n.position());
return;
}
}
// Else if this is an assignment expression, don't add std::move on the lhs
// even if this is a definite last use (only do that when an rvalue is okay)
if (!n.terms.empty() && is_assignment_operator(n.terms.front().op->type())) {
suppress_move_from_last_use = true;
}
emit(*n.expr);
suppress_move_from_last_use = false;
// Check that this isn't an illegal pointer operation
// (initial partial implementation)
if (!n.terms.empty() && last_postfix_expr_was_pointer)
{
auto rhs_post = n.get_second_postfix_expression_node();
assert(rhs_post && rhs_post->expr);
auto rhs_tok = rhs_post->expr->get_token();
if (is_assignment_operator(n.terms.front().op->type()) && rhs_tok &&
(*rhs_tok == "nullptr" || is_digit(((std::string_view)*rhs_tok)[0]))
)
{
errors.emplace_back(
n.terms.front().op->position(),
n.terms.front().op->to_string(true) + " - pointer assignment from null or integer is illegal"
);
violates_lifetime_safety = true;
}
else if (
*n.terms.front().op == "+" || *n.terms.front().op == "+=" ||
*n.terms.front().op == "-" || *n.terms.front().op == "-="
)
{
errors.emplace_back(
n.terms.front().op->position(),
n.terms.front().op->to_string(true) + " - pointer arithmetic is illegal - use std::span or gsl::span instead"
);
violates_bounds_safety = true;
}
}
for (auto const& x : n.terms) {
assert(x.op);
assert(x.expr);
// Normally we'll just emit the operator, but if this is an
// assignment that's a definite initialization, change it to
// a .construct() call
if (x.op->type() == lexeme::Assignment && in_definite_init) {
printer.print_cpp2( ".construct(", n.position() );
emit(*x.expr);
printer.print_cpp2( ")", n.position() );
}
else {
printer.print_cpp2(" ", n.position());
emit(*x.op);
printer.print_cpp2(" ", n.position());
emit(*x.expr);
}
}
}
//-----------------------------------------------------------------------
//
auto emit(expression_node const& n) -> void
{
assert(n.expr);
push_need_expression_list_parens(true);
emit(*n.expr);
pop_need_expression_list_parens();
}
//-----------------------------------------------------------------------
//
auto emit(expression_list_node const& n) -> void
{
auto add_parens = should_add_expression_list_parens() && !n.inside_initializer;
if (add_parens) {
printer.print_cpp2( *n.open_paren, n.position());
}
auto first = true;
for (auto const& x : n.expressions) {
if (!first) {
printer.print_cpp2(", ", n.position());
}
first = false;
auto offset = 0;
auto is_out = false;
if (x.pass != passing_style::in) {
assert(
x.pass == passing_style::out ||
x.pass == passing_style::move ||
x.pass == passing_style::forward
);
if (x.pass == passing_style::out) {
is_out = true;
printer.print_cpp2("&", n.position());
offset = -3; // because we're replacing "out " (followed by at least one space) with "&"
}
else if (x.pass == passing_style::move) {
printer.print_cpp2("std::move(", n.position());
offset = 6; // because we're replacing "move " (followed by at least one space) with "std::move("
}
}
if (is_out) {
in_non_rvalue_context.push_back(true);
}
assert(x.expr);
adjust_remaining_token_columns_on_this_line_visitor v(x.expr->position(), offset);
current_args.push_back( {x.pass} );
x.expr->visit(v, 0);
emit(*x.expr);
current_args.pop_back();
if (is_out) {
in_non_rvalue_context.pop_back();
}
if (x.pass == passing_style::move) {
printer.print_cpp2(")", n.position());
}
}
if (add_parens) {
printer.print_cpp2( *n.close_paren, n.position());
}
// We want to consume only one of these
consumed_expression_list_parens();
}
//-----------------------------------------------------------------------
//
auto emit(expression_statement_node const& n, bool can_have_semicolon, source_position function_body_start = {}, bool function_void_ret = false ) -> void
{
assert(n.expr);
if (function_body_start != source_position{}) {
if (!function_returns.empty() && function_returns.back() != nullptr && function_returns.back() != &single_anon) {
errors.emplace_back(
n.position(),
"a function with named return value(s) must have a full { } body"
);
return;
}
printer.print_cpp2(" { ", function_body_start);
if (!function_void_ret) {
printer.print_cpp2("return ", n.position());
}
}
emit(*n.expr);
if (can_have_semicolon) {
printer.print_cpp2(";", n.position());
}
if (function_body_start != source_position{}) {
printer.print_cpp2(" }", n.position());
}
}
//-----------------------------------------------------------------------
//
auto emit(
statement_node const& n,
bool can_have_semicolon = true,
source_position function_body_start = {},
bool function_void_ret = false,
std::vector const& function_prolog = {},
std::vector const& function_epilog = {},
colno_t function_indent = 1
)
-> void
{
printer.disable_indent_heuristic_for_next_text();
try_emit(n.statement, function_prolog, function_epilog, function_indent);
// NOTE: Reset preemption here because
// - for compound statements written as "= { ... }", we want to keep the
// preempted position which moves the { to where the = was
// - but for other statement types, we want to get rid of any leftover
// preemption (ideally there wouldn't be any, but sometimes there is
// and it should not apply to what we're about to emit)
printer.preempt_position_push({});
// This only has a whitespace effect in the generated Cpp1 code, but it's
// aesthetic and aesthetics are important in this case -- we want to keep
// the original source's personal whitespace formatting style as much as we can
try_emit(n.statement, can_have_semicolon, function_body_start, function_void_ret);
try_emit(n.statement);
try_emit(n.statement);
try_emit(n.statement);
try_emit(n.statement);
try_emit(n.statement);
try_emit(n.statement, false);
printer.preempt_position_pop();
}
//-----------------------------------------------------------------------
//
auto emit(parameter_declaration_node const& n, bool returns = false) -> void
{
// Can't declare functions as parameters -- only pointers to functions which are objects
assert( n.declaration );
assert( n.declaration->is(declaration_node::object) );
auto const& type_id = *std::get(n.declaration->type);
auto unqid = std::get_if(&type_id.id);
auto is_wildcard = unqid && *(*unqid)->identifier == "_";
// First any prefix
if (!returns && !is_wildcard)
{
switch (n.pass) {
break;case passing_style::in : printer.print_cpp2( "cpp2::in", n.position() );
break;case passing_style::copy : printer.print_cpp2( "", n.position() );
break;case passing_style::inout : printer.print_cpp2( "&", n.position() );
break;case passing_style::out : printer.print_cpp2( ">", n.position() );
break;case passing_style::move : printer.print_cpp2( "&&", n.position() );
break;case passing_style::forward: printer.print_cpp2( "&&", n.position() );
break;default: ;
}
}
printer.print_cpp2( " ", n.declaration->identifier->position() );
emit( *n.declaration->identifier );
if (!returns && n.declaration->initializer) {
printer.print_cpp2( " = ", n.declaration->initializer->position() );
emit(*n.declaration->initializer);
}
//TODO - when we get to classes and inheritance
//o position().lineno, col});
}
in_parameter_list = false;
}
//-----------------------------------------------------------------------
//
auto emit(contract_node& n) -> void
// note: parameter is deliberately not const because we will fill
// in the capture .str information
{
assert (n.kind);
// For a postcondition, we'll wrap it in a final_action_success lambda
//
if (*n.kind == "post") {
auto lambda_intro = build_capture_lambda_intro_for(n.captures, n.position());
printer.print_cpp2(
"auto post_" + std::to_string(n.position().lineno) + "_" +
std::to_string(n.position().colno) + " = cpp2::finally_success(" +
lambda_intro + "{",
n.position()
);
}
// Emit the contract group name (defaults to cpp2::Default)
//
if (n.group) {
// If this is one of Cpp2's predefined contract groups,
// make it convenient to use without cpp2:: qualification
if (auto uid = std::get_if(&n.group->id)) {
assert (*uid && (**uid).identifier);
if (
*(**uid).identifier == "Default" ||
*(**uid).identifier == "Bounds" ||
*(**uid).identifier == "Null" ||
*(**uid).identifier == "Type" ||
*(**uid).identifier == "Testing"
)
{
printer.print_cpp2("cpp2::", n.position());
}
}
printer.preempt_position_push(n.position());
printer.add_pad_in_this_line(-20);
emit(*n.group);
printer.preempt_position_pop();
}
else {
printer.print_cpp2("cpp2::Default", n.position());
printer.add_pad_in_this_line(-8);
}
// And invoke .expects on that contract group
//
printer.print_cpp2(".expects(", n.position());
assert(n.condition);
emit (*n.condition);
printer.print_cpp2(", ", n.position());
if (n.message) {
emit (*n.message);
}
else {
printer.print_cpp2("\"\"", n.position());
}
printer.print_cpp2(");", n.position());
// For a postcondition, close out the final_action_success lambda
//
if (*n.kind == "post") {
printer.print_cpp2( "} );", n.position()
);
}
}
//-----------------------------------------------------------------------
//
auto emit(function_type_node const& n, token const* ident) -> void
{
assert(n.parameters);
emit(*n.parameters);
// Add implicit noexcept when we implement proper EH
// to handle calling Cpp1 code that throws
//if (!n.throws) {
// printer.add_pad_in_this_line(-25);
// printer.print_cpp2( " noexcept", n.position() );
//}
if (n.returns.index() == function_type_node::empty) {
if (ident) {
printer.print_cpp2( " -> void", n.position() );
}
}
else if (n.returns.index() == function_type_node::id) {
printer.print_cpp2( " -> ", n.position() );
auto& r = std::get(n.returns);
assert(r);
emit(*r);
}
else {
printer.print_cpp2( " -> ", n.position() );
function_return_name = {};
printer.emit_to_string(&function_return_name);
assert(ident);
printer.print_cpp2( *ident, ident->position() );
printer.print_cpp2( "__ret", ident->position() );
printer.emit_to_string();
printer.print_cpp2( function_return_name, ident->position() );
}
}
//-----------------------------------------------------------------------
//
auto emit(declaration_node const& n, std::string const& capture_intro = {}) -> void
{
// If this is a function that has multiple return values,
// first we need to emit the struct that contains the returns
if (printer.doing_declarations_only() && n.is(declaration_node::function))
{
auto& func = std::get(n.type);
assert(func);
if (func->returns.index() == function_type_node::list) {
auto& r = std::get(func->returns);
assert(r);
assert(std::ssize(r->parameters) > 0);
printer.ignore_alignment( true, n.position().colno );
printer.print_cpp2( "struct ", n.position() );
printer.ignore_alignment( true, n.position().colno + 4 );
printer.print_cpp2( *n.identifier->identifier, n.position() );
printer.print_cpp2( "__ret ", n.position() );
emit(*r, true);
printer.print_cpp2( "\n", n.position() );
printer.ignore_alignment( false );
}
}
// Function
if (n.is(declaration_node::function))
{
// Start fresh (there may be one spurious leftover
// requires-condition created during the declarations pass)
function_requires_conditions = {};
auto& func = std::get(n.type);
assert(func);
// If this is at expression scope, we can't emit "[[nodiscard]] auto name"
// so print the provided intro instead, which will be a lambda-capture-list
if (capture_intro != "") {
assert (!n.identifier);
printer.print_cpp2(capture_intro, n.position());
emit( *func, nullptr );
}
else {
assert (n.identifier);
if (func->returns.index() != function_type_node::empty) {
printer.print_cpp2( "[[nodiscard]] ", n.position() );
}
printer.print_cpp2( "auto ", n.position() );
printer.print_cpp2( *n.identifier->identifier, n.identifier->position() );
emit( *func, n.identifier->identifier );
}
// Function declaration
if (printer.doing_declarations_only()) {
printer.print_cpp2( ";\n", n.position() );
return;
}
if (func->returns.index() == function_type_node::list) {
auto& r = std::get(func->returns);
function_returns.push_back(r.get());
}
else if (func->returns.index() == function_type_node::id) {
function_returns.push_back(&single_anon); // use special value as a note
}
else {
function_returns.push_back(nullptr); // no return type at all
}
// Function body
assert( n.initializer );
auto function_return_locals = std::vector{};
auto function_epilog = std::vector{};
for (auto&& c : func->contracts) {
auto print = std::string();
printer.emit_to_string(&print);
emit(*c);
printer.emit_to_string();
function_return_locals.push_back(print);
}
if (func->returns.index() == function_type_node::list)
{
auto& r = std::get(func->returns);
assert(r);
for (auto& param : r->parameters)
{
assert(param && param->declaration);
auto& decl = *param->declaration;
assert(decl.type.index() == declaration_node::object);
auto& id_expr = std::get(decl.type);
assert(id_expr);
auto loc = std::string{};
if (!decl.initializer) {
loc += (" cpp2::deferred_init");
}
loc += " ";
loc += ((std::string_view)*decl.identifier->identifier);
if (decl.initializer)
{
std::string init;
printer.emit_to_string(&init);
printer.print_cpp2 ( " {", decl.initializer->position() );
if (decl.initializer->statement.index() != statement_node::expression) {
errors.emplace_back(
decl.initializer->position(),
"return value initializer must be an expression"
);
return;
}
auto& expr = std::get(decl.initializer->statement);
assert(expr);
emit(*expr, false);
printer.print_cpp2 ( "}", decl.initializer->position() );
printer.emit_to_string();
loc += init;
}
loc += ";";
function_return_locals.push_back(loc);
}
}
//function_epilog.push_back("/*EPILOG-TEST*/");
printer.preempt_position_push( n.equal_sign );
// TODO: something like this to get rid of extra blank lines
// inside the start of bodies of functions that have
// multiple contracts
//printer.skip_lines( std::ssize(function_return_locals) );
// If processing the parameters generated any requires conditions,
// emit them here
if (!function_requires_conditions.empty()) {
printer.ignore_alignment( true, n.position().colno + 4 );
printer.print_extra("\n");
for (auto const& req : function_requires_conditions) {
printer.print_extra("requires " + req);
}
function_requires_conditions = {};
printer.ignore_alignment( false );
}
emit(
*n.initializer,
true, func->position(), n.identifier && func->returns.index() == function_type_node::empty,
function_return_locals, function_epilog, n.position().colno
);
printer.preempt_position_pop();
function_returns.pop_back();
}
// Object with optional initializer
else if (!printer.doing_declarations_only() && n.is(declaration_node::object))
{
auto& type = std::get(n.type);
// Emit "auto" for deduced types (of course)
if (type->is_wildcard()) {
assert(n.initializer);
//printer.print_cpp2("auto", n.position());
emit( *type, n.position() );
}
// Otherwise, emit the type
else {
// If there isn't an initializer, use cpp2::deferred_init
if (!n.initializer) {
if (n.parent_scope && n.parent_scope->is(declaration_node::function)) {
printer.print_cpp2( "cpp2::deferred_init", n.position() );
}
}
printer.print_cpp2( " ", n.position());
assert(n.identifier);
emit(*n.identifier);
// If there's an initializer, emit it
if (n.initializer)
{
in_non_rvalue_context.push_back(true);
printer.add_pad_in_this_line(-100);
printer.print_cpp2( " {", n.position() );
push_need_expression_list_parens(false);
assert( n.initializer );
emit( *n.initializer, false );
pop_need_expression_list_parens();
printer.print_cpp2( "}", n.position() );
in_non_rvalue_context.pop_back();
}
printer.print_cpp2( "; ", n.position() );
}
}
//-----------------------------------------------------------------------
// print_errors
//
auto print_errors() -> void
{
if (!errors.empty()) {
// Delete the output file
printer.abandon();
}
for (auto&& error : errors) {
error.print(std::cerr, strip_path(sourcefile));
}
if (violates_lifetime_safety) {
std::cerr bool
{
return errors.empty();
}
//-----------------------------------------------------------------------
// debug_print
//
auto debug_print() -> void
{
// Only create debug output files if we managed to load the source file.
//
if (source_loaded)
{
auto out_source = std::ofstream{ sourcefile+"-source" };
source.debug_print( out_source );
auto out_tokens = std::ofstream{ sourcefile+"-tokens" };
tokens.debug_print( out_tokens );
auto out_parse = std::ofstream{ sourcefile+"-parse" };
auto tree_printer = parse_tree_printer{out_parse };
parser.visit ( tree_printer );
auto out_symbols = std::ofstream{ sourcefile+"-symbols" };
sema.debug_print ( out_symbols );
}
}
//-----------------------------------------------------------------------
// has_cpp1: pass through
//
auto has_cpp1() const -> bool {
return source.has_cpp1();
}
//-----------------------------------------------------------------------
// has_cpp2: pass through
//
auto has_cpp2() const -> bool {
return source.has_cpp2();
}
};
}
//===========================================================================
// main - driver
//===========================================================================
using namespace std;
using namespace cpp2;
static auto enable_debug_output_files = false;
static cmdline_processor::register_flag cmd_debug(
9,
"debug",
"Emit compiler debug output files",
[]{ enable_debug_output_files = true; }
);
auto main(int argc, char* argv[]) -> int
{
cmdline.set_args(argc, argv);
cmdline.process_flags();
if (cmdline.help_was_requested()) {
return EXIT_SUCCESS;
}
if (cmdline.arguments().empty()) {
std::cerr