[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/fpelliccioni/cppfront/main/source/cppfront.cpp [Back]  [Original]

//  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 


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 insert( emit_text_chunks_targets.back()->begin(), text_with_pos(std::string(s), pos) );
            }

            return;
        }

        //  Otherwise, we'll actually print the string to the output file
        //  and update our curr_pos position

        //  Output the string
        out  void
    {
        if (curr_pos.colno > 1) {
            auto old_pos = curr_pos;
            print( "\n" );
            assert(curr_pos.lineno == old_pos.lineno+1);
            assert(curr_pos.colno == 1);
        }
    }

    //  Print a #line directive
    //
    auto print_line_directive( lineno_t line ) -> void
    {
        prev_line_info = { curr_pos.lineno, { } };
        ensure_at_start_of_new_line();

        //  Not using print() here because this is transparent to the curr_pos
        if (!flag_clean_cpp1) {
            out 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());
            }
        }

        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);
        }

        printer.emit_to_string();
        printer.print_cpp2( ident, n.position() );
    }


    //-----------------------------------------------------------------------
    //
    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);
            assert(alt->id_expression);
            emit(*alt->id_expression);
            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();
                }

                //  If this is an inspect-expression, we'll have to wrap each alternative
                //  in an 'if constexpr' so that its type is ignored for mismatches with
                //  the inspect-expression's type
                auto return_prefix = std::string{};
                auto return_suffix = std::string{";"};   // use this to tack the ; back on in the alternative body
                if (is_expression) {
                    return_prefix = "{ if constexpr( requires{" + statement + ";} ) if constexpr( std::is_convertible_v ) return ";
                    return_suffix += " }";
                }

                if (id == "_") {
                    found_wildcard = true;
                    if (is_expression) {
                        printer.print_cpp2("return ", alt->position());
                    }
                }
                else {
                    printer.print_cpp2("if " + constexpr_qualifier + "(cpp2::is(__expr)) " + return_prefix, alt->position());
                }

                printer.print_cpp2(statement, alt->position());

                if (is_expression && id != "_") {
                    assert(alt->statement->statement.index() == statement_node::expression);
                    printer.print_cpp2("; else return " + result_type + "{}", alt->position());
                    printer.print_cpp2("; else return " + result_type + "{}", alt->position());
                }

                printer.print_cpp2(return_suffix, alt->position());
            }
            else {
                errors.emplace_back(
                    alt->position(),
                    "(temporary alpha limitation) cppfront is still learning 'inspect' - only simple 'is' alternatives are currently supported"
                );
                return;
            }
        }

        if (is_expression) {
            if (!found_wildcard) {
                errors.emplace_back(
                    n.position(),
                    "an inspect expression must have an `is _` match-anything wildcard alternative"
                );
                return;
            }
        }
        else {
            printer.print_cpp2("}", n.close_brace);
        }

        //  If this is an expression, finally actually invoke the lambda
        if (is_expression) {
            printer.print_cpp2("()", n.close_brace);
        }
        printer.print_cpp2("\n", n.close_brace);
    }


    //-----------------------------------------------------------------------
    //
    auto emit(selection_statement_node const& n) -> void
    {
        assert(n.identifier);
        emit(*n.identifier);

        printer.print_cpp2(" (", n.position());
        printer.add_pad_in_this_line(1);

        assert(n.expression);
        emit(*n.expression);

        printer.print_cpp2(") ", n.position());
        printer.add_pad_in_this_line(1);

        assert(n.true_branch);
        emit(*n.true_branch);

        if (n.has_source_false_branch) {
            printer.print_cpp2("else ", n.else_pos);
            emit(*n.false_branch);
        }
    }


    //-----------------------------------------------------------------------
    //
    auto emit(iteration_statement_node const& n) -> void
    {
        assert(n.identifier);

        //  Handle while
        //
        if (*n.identifier == "while") {
            assert(n.condition && n.statement && !n.range && !n.body);

            //  We emit Cpp2 while loops as Cpp2 for loops if there's a "next" clause
            if (!n.next_expression) {
                printer.print_cpp2("while( ", n.position());
                emit(*n.condition);
            }
            else {
                printer.print_cpp2("for( ; ", n.position());
                emit(*n.condition);
                printer.print_cpp2("; ", n.position());
                printer.add_pad_in_this_line(-10);
                emit(*n.next_expression);
            }
            printer.print_cpp2(" ) ", n.position());
            emit(*n.statement);
            return;
        }

        //  Handle do
        //
        if (*n.identifier == "do") {
            assert(n.condition && n.statement && !n.range && !n.body);

            printer.print_cpp2("do ", n.position());
            emit(*n.statement);
            printer.print_cpp2(" while ( ", n.position());
            emit(*n.condition);
            if (n.next_expression) {
                //  Gotta say, this feels kind of nifty... short-circuit eval
                //  and smuggling work into a condition via a lambda, O my...
                printer.print_cpp2(" && [&]{ ", n.position());
                emit(*n.next_expression);
                printer.print_cpp2(" ; return true; }() ", n.position());
            }
            printer.print_cpp2(");", n.position());
            return;
        }

        //  Handle for
        //
        if (*n.identifier == "for") {
            assert(!n.condition && !n.statement && n.range && n.body);

            //  TODO: break n.range into subexpressions and lifetime-extend
            //        each subexpression, because that's the right thing to do
            //  For now, just use a for-scope auto&&
            //  Also: using the name 'cpp2_range' for now because '__range' is reserved
            //        and common enough that it might clash with existing impls
            printer.print_cpp2("for ( auto&& cpp2_range = ", n.position());
            emit(*n.range);
            printer.print_cpp2(";  ", n.position());
            emit(*n.get_for_parameter());
            printer.print_cpp2(" : cpp2_range ) ", n.position());

            //  If there's a next-expression, smuggle it in via a nested do/while(false) loop
            //  (nested "continue" will work, but "break" won't until we do extra work to implement
            //  that using a flag and implementing "break" as "__for_break = true; continue;")
            if (n.next_expression) {
                printer.print_cpp2(" { do ", n.position());
            }

            assert(n.body->initializer);
            emit(*n.body->initializer);

            if (n.next_expression) {
                printer.print_cpp2(" while (false); ", n.position());
                emit(*n.next_expression);
                printer.print_cpp2("; }", n.position());
            }

            return;
        }

        assert(!"compiler bug: unexpected case");
    }


    //-----------------------------------------------------------------------
    //
    auto emit(return_statement_node const& n) -> void
    {
        assert(n.identifier);
        assert(*n.identifier == "return");
        printer.print_cpp2("return ", n.position());

        //  Return with expression == single anonymous return type
        //
        if (n.expression) {
            emit(*n.expression);
            if (function_returns.empty() || function_returns.back() != &single_anon) {
                errors.emplace_back(
                    n.position(),
                    "return statement with expression must be in a function with a single anonymous return value"
                );
                return;
            }
        }

        else if (!function_returns.empty() && function_returns.back() == &single_anon) {
            errors.emplace_back(
                n.position(),
                "return statement must have an expression in a function with a single anonymous return value"
            );
        }

        //  Return without expression == zero or named return values
        //
        else if (!function_returns.empty() && function_returns.back()) {
            //auto stmt = function_return_name + " { "; // we shouldn't need this with { } init
            auto stmt = std::string(" { ");
            auto& parameters = function_returns.back()->parameters;
            for (bool first = true; auto& param : parameters) {
                if (!first) {
                    stmt += ", ";
                }
                first = false;
                assert(param->declaration->identifier);

                printer.emit_to_string(&stmt);
                emit(*param->declaration->identifier, true);
                printer.emit_to_string();
            }
            stmt += " }";
            printer.print_cpp2(stmt, n.position());
        }

        printer.print_cpp2("; ", n.position());
    }


    //-----------------------------------------------------------------------
    //
    auto build_capture_lambda_intro_for( capture_group& captures, source_position pos ) -> std::string
    {
        //  First calculate the stringized version of each capture expression
        //  This will let us compare and de-duplicate repeated capture expressions
        for (auto& cap : captures)
        {
            assert(cap.capture_expr->cap_grp == &captures);
            printer.emit_to_string(&cap.str);
            emit(*cap.capture_expr, true);
            printer.emit_to_string();
        }

        //  Then build the capture list, ignoring duplicated expressions
        auto lambda_intro = std::string("[");
        printer.emit_to_string(&lambda_intro);

        auto num = 0;
        auto handled = std::vector{};
        for (auto& cap : captures)
        {
            //  If we haven't handled a capture that looks like this one
            if (std::find(handled.begin(), handled.end(), cap.str) == handled.end())
            {
                //  Remember it
                handled.push_back(cap.str);

                //  And handle it
                if (num != 0) { // not first
                    lambda_intro += ", ";
                }
                printer.print_cpp2("_"+std::to_string(num)+" = ", pos);
                emit(*cap.capture_expr, true);
            }
            ++num;
        }
        printer.emit_to_string();
        lambda_intro += "]";

        return lambda_intro;
    }


    //-----------------------------------------------------------------------
    //
    auto emit(primary_expression_node const& n) -> void
    {
        try_emit(n.expr);
        try_emit(n.expr);
        try_emit(n.expr);
        try_emit(n.expr, true);

        if (n.expr.index() == primary_expression_node::declaration)
        {
            auto& decl = std::get(n.expr);

            //  The usual non-null assertion, plus it should be an anonymous function
            assert(decl && !decl->identifier && decl->is(declaration_node::function));

            auto lambda_intro = build_capture_lambda_intro_for(decl->captures, n.position());

            emit(*decl, lambda_intro);
        }
    }


    //-----------------------------------------------------------------------
    //
    auto emit(postfix_expression_node& n, bool for_lambda_capture = false) -> void
        // note: parameter is deliberately not const because we we will fill
        //       in the capture .str information, and we may also adjust token
        //       column positions when moving operators to prefix notation
    {
        assert(n.expr);
        last_postfix_expr_was_pointer = false;

        //  Check that this isn't pointer arithmentic
        //      (initial partial implementation)
        if (n.expr->expr.index() == primary_expression_node::id_expression)
        {
            auto& id = std::get(n.expr->expr);
            assert(id);
            if (id->id.index() == id_expression_node::unqualified)
            {
                auto& unqual = std::get(id->id);
                assert(unqual);
                auto decl = sema.get_declaration_of(*unqual->identifier);
                //  TODO: Generalize this -- for now we detect only cases of the form "p: *int = ...;"
                //        We don't recognize pointer types that are deduced, multi-level, or from Cpp1
                if (decl && decl->declaration && decl->declaration->pointer_declarator) {
                    if (n.ops.empty()) {
                        last_postfix_expr_was_pointer = true;
                    }
                    else
                    {
                        if (n.ops.front().op->type() == lexeme::PlusPlus ||
                            n.ops.front().op->type() == lexeme::MinusMinus ||
                            n.ops.front().op->type() == lexeme::LeftBracket
                            ) {
                            errors.emplace_back(
                                n.ops.front().op->position(),
                                n.ops.front().op->to_string(true) + " - pointer arithmetic is illegal - use std::span or gsl::span instead"
                            );
                            violates_bounds_safety = true;
                        }
                        else if (n.ops.front().op->type() == lexeme::Tilde) {
                            errors.emplace_back(
                                n.ops.front().op->position(),
                                n.ops.front().op->to_string(true) + " - pointer bitwise manipulation is illegal - use std::bit_cast to convert to raw bytes first"
                            );
                        }
                    }
                }
            }
        }

        //  Simple case: If there are no .ops, just emit the expression
        if (n.ops.empty()) {
            emit(*n.expr);
            return;
        }

        //  Check to see if it's a capture expression that contains $,
        //  and if we're not capturing the expression for the lambda
        //  introducer replace it with the capture name
        auto captured_part = std::string{};
        if (n.cap_grp && !for_lambda_capture)
        {
            //  First stringize ourselves so that we compare equal against
            //  the first *cap_grp .str that matches us (which is what the
            //  lambda introducer generator used to create a lambda capture)
            auto my_str = std::string{};
            printer.emit_to_string(&my_str);
            emit(n, true);  // reentrant, but not in this 'if' because for_lambda_capture == true
            printer.emit_to_string();

            //  Look in the capture group to see which capture # we are
            auto mynum = 0;
            for (auto const& cap : *n.cap_grp) {
                if (cap.str == my_str) {
                    break;
                }
                ++mynum;
            }
            assert (mynum < n.cap_grp->size() && "could not find this postfix-expression in capture group");
            //  And then emit that capture number
            captured_part += "_" + std::to_string(mynum);
        }

        //  Check to see if it's just a function call with "." syntax,
        //  and if so use this path to convert it to UFCS
        if (// there's a single-token expression followed by . and (
            n.expr->get_token() &&                      //  if the base expression is a single token
            std::ssize(n.ops) >= 2 &&                   //  and we're of the form:
            n.ops[0].op->type() == lexeme::Dot &&       //       token . id-expr ( expr-list )
            n.ops[1].op->type() == lexeme::LeftParen &&
            // and either there's nothing after that, or there's just a $ after that
            (
                std::ssize(n.ops) == 2 ||
                (std::ssize(n.ops) == 3 && n.ops[2].op->type() == lexeme::Dollar)
            )
            )
        {
            //  If we already replaced this with a capture (which contains the UFCS
            //  work already done when the capture was computed), emit the capture
            if (!captured_part.empty()) {
                printer.print_cpp2(captured_part, n.position());
                return;
            }

            //  Otherwise, do the UFCS work...

            //  The . has its id_expr
            assert (n.ops[0].id_expr);

            //  The ( has its expr_list and op_close
            assert (n.ops[1].expr_list && n.ops[1].op_close);

            //--------------------------------------------------------------------
            //  TODO: When MSVC supports __VA_OPT__ in standard mode without the
            //        experimental /Zc:preprocessor switch, use this single line
            //        instead of the dual lines below that special-case _0 args
            //  AND:  Make the similarly noted change in cpp2util.h
            //
            //printer.print_cpp2("CPP2_UFCS(", n.position());

            //  If there are no additional arguments, use the CPP2_UFCS_0 version
            if (!n.ops[1].expr_list->expressions.empty()) {
                printer.print_cpp2("CPP2_UFCS(", n.position());
            }
            else {
                printer.print_cpp2("CPP2_UFCS_0(", n.position());
            }
            //--------------------------------------------------------------------

            //  Make the "funcname" the first argument to CPP2_UFCS
            emit(*n.ops[0].id_expr);
            printer.print_cpp2(", ", n.position());

            //  Then make the base expression the second argument
            emit(*n.expr);

            //  Then tack on any additional arguments
            if (!n.ops[1].expr_list->expressions.empty()) {
                printer.print_cpp2(", ", n.position());
                push_need_expression_list_parens(false);
                emit(*n.ops[1].expr_list);
                pop_need_expression_list_parens();
            }
            printer.print_cpp2(")", n.position());

            //  And we're done. This path has handled this node, so return...
            return;
        }

        //  Otherwise, we're going to have to potentially do some work to change
        //  some Cpp2 postfix operators to Cpp1 prefix operators, so let's set up...
        auto prefix            = std::vector{};
        auto suffix            = std::vector{};

        auto emitted_n         = false;
        auto last_was_prefixed = false;
        auto saw_dollar        = false;

        for (auto i = n.ops.rbegin(); i != n.ops.rend(); ++i)
        {
            assert(i->op);

            //  If we already captured a part as a _## lambda capture,
            //  skip the part of this expression before the $ symbol
            //
            if (!captured_part.empty()) {
                if (i->op->type() == lexeme::Dollar) {
                    break;
                }
            }
            //  Else skip the part of this expression after the $ symbol
            else if (for_lambda_capture) {
                if (i->op->type() == lexeme::Dollar) {
                    saw_dollar = true;
                    continue;
                }
                if (!saw_dollar) {
                    continue;
                }
            }

            //  Handle the Cpp2 postfix operators that are prefix in Cpp1
            //
            if (i->op->type() == lexeme::MinusMinus ||
                i->op->type() == lexeme::PlusPlus ||
                i->op->type() == lexeme::Multiply ||
                i->op->type() == lexeme::Ampersand ||
                i->op->type() == lexeme::Tilde
                )
            {
                adjust_remaining_token_columns_on_this_line_visitor v(i->op->position(), 0 - i->op->length());
                n.visit(v, 0);

                if (!last_was_prefixed && i != n.ops.rbegin()) {    // omit some needless parens
                    prefix.emplace_back( "(", i->op->position() );
                }
                prefix.emplace_back( i->op->to_string(true), i->op->position());

                //  Enable null dereference checks
                if (flag_safe_null_pointers && i->op->type() == lexeme::Multiply) {
                    prefix.emplace_back( "cpp2::assert_not_null(", i->op->position() );
                }
                if (flag_safe_null_pointers && i->op->type() == lexeme::Multiply) {
                    suffix.emplace_back( ")", i->op->position() );
                }

                if (!last_was_prefixed && i != n.ops.rbegin()) {    // omit some needless parens
                    suffix.emplace_back( ")", i->op->position() );
                }
                last_was_prefixed = true;
            }

            //  Handle the suffix operators that remain suffix
            //
            else {
                assert(i->op);
                last_was_prefixed = false;

                //  Enable subscript bounds checks
                if (flag_safe_subscripts && i->op->type() == lexeme::LeftBracket) {
                    suffix.emplace_back( ")", i->op->position() );
                }
                else if (i->op_close) {
                    suffix.emplace_back( i->op_close->to_string(true), i->op_close->position() );
                }

                if (i->id_expr) {
                    auto print = std::string{};
                    printer.emit_to_string(&print);
                    emit(*i->id_expr, false /*not a local name*/);
                    printer.emit_to_string();
                    suffix.emplace_back( print, i->id_expr->position() );
                }

                if (i->expr_list) {
                    auto text = std::vector{};
                    printer.emit_to_text_chunks(&text);
                    push_need_expression_list_parens(false);
                    emit(*i->expr_list);
                    pop_need_expression_list_parens();
                    printer.emit_to_text_chunks();
                    for (auto&& e: text) {
                        suffix.push_back(e);
                    }
                }

                //  Enable subscript bounds checks
                if (flag_safe_subscripts && i->op->type() == lexeme::LeftBracket) {
                    prefix.emplace_back( "cpp2::assert_in_bounds(", i->op->position() );
                    suffix.emplace_back( ", ", i->op->position() );
                }
                else {
                    suffix.emplace_back( i->op->to_string(true), i->op->position() );
                }
            }
        }

        //  Print the prefixes (in forward order)
        for (auto& e : prefix) {
            printer.print_cpp2(e.text, n.position());
        }

        //  If this is an --, ++, or &, 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.ops.front().op->type() == lexeme::MinusMinus ||
            n.ops.front().op->type() == lexeme::PlusPlus ||
            n.ops.front().op->type() == lexeme::Ampersand
            )
        {
            suppress_move_from_last_use = true;
        }

        //  Now print the core expression -- or the captured_part in its place
        if (captured_part.empty()) {
            emit(*n.expr);
        }
        else {
            printer.print_cpp2(captured_part, n.position());
        }
        suppress_move_from_last_use = false;

        //  Print the suffixes (in reverse order)
        while (!suffix.empty()) {
            printer.print_cpp2(suffix.back().text, suffix.back().pos);
            suffix.pop_back();
        }
    }


    //-----------------------------------------------------------------------
    //
    auto emit(prefix_expression_node const& n) -> void
    {
        auto suffix = std::string{};
        for (auto const& x : n.ops) {
            assert(x);
            if (x->type() == lexeme::Not) {
                printer.print_cpp2("!(", n.position());
                printer.add_pad_in_this_line(-3);
                suffix += ")";
            }
            else {
                printer.print_cpp2(*x, x->position());
            }
        }
        assert(n.expr);
        push_need_expression_list_parens(true);
        emit(*n.expr);
        pop_need_expression_list_parens();
        printer.print_cpp2(suffix, n.position());
    }


    //-----------------------------------------------------------------------
    //
    template<
        String   Name,
        typename Term
    >
    auto emit(binary_expression_node const& n) -> void
    {
        assert(n.expr);

        //  Handle is/as expressions
        //  TODO: Generalize
        if (!n.terms.empty() && *n.terms.front().op == "is")
        {
            if (n.terms.size() > 1) {
                errors.emplace_back(
                    n.position(),
                    "(temporary alpha limitation) this compiler is just starting to learn 'is' and only supports a single is-expression (no chaining with other is/as)"
                );
                return;
            }

            printer.print_cpp2("cpp2::is(", n.position());

            emit(*n.expr);

            printer.print_cpp2(")", n.position());
            return;
        }

        if (!n.terms.empty() && *n.terms.front().op == "as")
        {
            if (n.terms.size() > 1) {
                errors.emplace_back(
                    n.position(),
                    "(temporary alpha limitation) this compiler is just starting to learn 'as' and only supports a single as-expression (no chaining with other is/as)"
                );
                return;
            }

            printer.print_cpp2("cpp2::as(", n.position());

            emit(*n.expr);

            printer.print_cpp2(")", n.position());
            return;
        }

        //  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
    {
        if (should_add_expression_list_parens() && !n.expressions.empty() && !n.inside_initializer) {
            printer.print_cpp2("(", n.position());
        }

        auto first = true;
        for (auto const& x : n.expressions) {
            if (!first) {
                printer.print_cpp2(", ", n.position());
            }
            first = false;
            auto offset = 0;

            if (x.pass != passing_style::in) {
                assert(to_string_view(x.pass) == "out" || to_string_view(x.pass) == "move");
                if (to_string_view(x.pass) == "out") {
                    printer.print_cpp2("&", n.position());
                    offset = -3;   // because we're replacing "out " (followed by at least one space) with "&"
                }
                else if (to_string_view(x.pass) == "move") {
                    printer.print_cpp2("std::move(", n.position());
                    offset = 6;    // because we're replacing "move " (followed by at least one space) with "std::move("
                }
            }

            assert(x.expr);
            adjust_remaining_token_columns_on_this_line_visitor v(x.expr->position(), offset);
            x.expr->visit(v, 0);
            emit(*x.expr);

            if (to_string_view(x.pass) == "move") {
                printer.print_cpp2(")", n.position());
            }
        }

        if (should_add_expression_list_parens() && !n.expressions.empty() && !n.inside_initializer) {
            printer.print_cpp2(")", 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{}) {
            printer.print_cpp2(" { ", function_body_start);
            if (!function_void_ret) {
                printer.print_cpp2("return ", n.position());
            }
        }

        emit(*n.expr);
        if (n.has_semicolon && 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
    {
        //  TODO: If there's a let on this statement, generate a block scope
        if (n.let) {

            //  TODO

        }

        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({});
        //  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);
    }


    //-----------------------------------------------------------------------
    //
    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& id_expr = *std::get(n.declaration->type);

        auto unqid = std::get_if(&id_expr.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.preempt_position( n.position() );
        }

        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  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& in_expression_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 (in_expression_intro != "") {
                assert (!n.identifier);
                printer.print_cpp2(in_expression_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(*decl.initializer);
                        printer.emit_to_string();

                        loc += init;
                    }
                    loc += ";";
                    function_return_locals.push_back(loc);
                }
            }

            //function_epilog.push_back("/*EPILOG-TEST*/");

            printer.preempt_position( 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
            );

            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->id.index() == id_expression_node::empty) {
                assert(n.initializer);
                printer.print_cpp2("auto", n.position());
            }
            //  Otherwise, emit the type
            else {
                //  If there isn't an initializer, use cpp2::deferred_init
                if (!n.initializer) {
                    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)
            {
                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() );
            }

            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_noline(
    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 0;
    }

    if (cmdline.arguments().empty()) {
        std::cout 

Web Proxy Viewer  |  New URL  |  Original Page