FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

GitHub Viewer

// 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; struct iter_info { iteration_statement_node const* stmt; bool used = false; }; std::vector iteration_statements; 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 {}; } // Generate a reasonable macroized name auto cpp1_FILENAME = to_upper_and_underbar(cpp1_filename); // 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()) { printer.print_extra( "\n" ); if (cpp1_filename.back() == 'h') { printer.print_extra( "#ifndef " + cpp1_FILENAME+"__CPP2\n"); printer.print_extra( "#define " + cpp1_FILENAME+"__CPP2" + "\n\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" ); } } 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)" ); return {}; } } 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; if (ret.cpp2_lines == 1) { printer.print_extra( "\n#include \"cpp2util.h\"\n\n" ); } // 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_move || add_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); try_emit(n.statement); 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.type); if (r.pass == passing_style::forward) { if (r.type->is_wildcard()) { printer.print_cpp2( "decltype(auto)", n.position() ); } else { emit(*r.type); printer.print_cpp2( "&", n.position() ); } } else { emit(*r.type); } } 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

Back | FazBrowse Home | New Git URL