[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/Numidia27/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 
#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 

Web Proxy Viewer  |  New URL  |  Original Page