summaryrefslogtreecommitdiffhomepage
path: root/64x0/cc2/source/to_cpp1.h
diff options
context:
space:
mode:
Diffstat (limited to '64x0/cc2/source/to_cpp1.h')
-rw-r--r--64x0/cc2/source/to_cpp1.h6750
1 files changed, 0 insertions, 6750 deletions
diff --git a/64x0/cc2/source/to_cpp1.h b/64x0/cc2/source/to_cpp1.h
deleted file mode 100644
index a7b6782..0000000
--- a/64x0/cc2/source/to_cpp1.h
+++ /dev/null
@@ -1,6750 +0,0 @@
-
-// 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.
-
-
-//===========================================================================
-// Lowering to Cpp1 syntax
-//===========================================================================
-
-#ifndef CPP2_TO_CPP1_H
-#define CPP2_TO_CPP1_H
-
-#include "sema.h"
-#include <iostream>
-#include <cstdio>
-#include <optional>
-
-namespace cpp2 {
-
-// Defined out of line here just to avoid bringing <iostream> in before this,
-// so that we can't accidentally start depending on iostreams in earlier phases
-auto cmdline_processor::print(std::string_view s, int width)
- -> void
-{
- if (width > 0) {
- std::cout << std::setw(width) << std::left;
- }
- std::cout << s;
-}
-
-
-//-----------------------------------------------------------------------
-//
-// Stringingizing helpers
-//
-//-----------------------------------------------------------------------
-
-auto pad(int padding)
- -> std::string_view
-{
- if (padding < 1) {
- return "";
- }
-
- return {
- indent_str.c_str(),
- _as<size_t>( std::min( padding, _as<int>(std::ssize(indent_str))) )
- };
-}
-
-
-//-----------------------------------------------------------------------
-//
-// positional_printer: a Syntax 1 pretty printer
-//
-//-----------------------------------------------------------------------
-//
-static auto flag_emit_cppfront_info = false;
-static cmdline_processor::register_flag cmd_emit_cppfront_info(
- 9,
- "emit-cppfront-info",
- "Emit cppfront version/build in output file",
- []{ flag_emit_cppfront_info = true; }
-);
-
-static auto flag_clean_cpp1 = false;
-static cmdline_processor::register_flag cmd_clean_cpp1(
- 9,
- "clean-cpp1",
- "Emit clean Cpp1 without #line directives",
- []{ flag_clean_cpp1 = true; }
-);
-
-static auto flag_import_std = false;
-static cmdline_processor::register_flag cmd_import_std(
- 0,
- "import-std",
- "import all std:: via 'import std;' - ignored if -include-std is set",
- []{ flag_import_std = true; }
-);
-
-static auto flag_include_std = false;
-static cmdline_processor::register_flag cmd_include_std(
- 0,
- "include-std",
- "#include all std:: headers",
- []{ flag_include_std = true; }
-);
-
-static auto flag_cpp2_only = false;
-static cmdline_processor::register_flag cmd_cpp2_only(
- 0,
- "pure-cpp2",
- "Allow Cpp2 syntax only - also sets -import-std",
- []{ flag_cpp2_only = true; flag_import_std = true; }
-);
-
-static auto flag_safe_null_pointers = true;
-static cmdline_processor::register_flag cmd_safe_null_pointers(
- 2,
- "no-null-checks",
- "Disable null safety checks",
- []{ flag_safe_null_pointers = false; }
-);
-
-static auto flag_safe_subscripts = true;
-static cmdline_processor::register_flag cmd_safe_subscripts(
- 2,
- "no-subscript-checks",
- "Disable subscript safety checks",
- []{ flag_safe_subscripts = false; }
-);
-
-static auto flag_safe_comparisons = true;
-static cmdline_processor::register_flag cmd_safe_comparisons(
- 2,
- "no-comparison-checks",
- "Disable mixed-sign comparison safety checks",
- []{ flag_safe_comparisons = false; }
-);
-
-static auto flag_use_source_location = false;
-static cmdline_processor::register_flag cmd_enable_source_info(
- 2,
- "add-source-info",
- "Enable source_location information for contract checks",
- []{ flag_use_source_location = true; }
-);
-
-static auto flag_cpp1_filename = std::string{};
-static cmdline_processor::register_flag cmd_cpp1_filename(
- 9,
- "output filename",
- "Output to 'filename' (can be 'stdout') - default is *.cpp/*.h",
- nullptr,
- [](std::string const& name) { flag_cpp1_filename = name; }
-);
-
-static auto flag_print_colon_errors = false;
-static cmdline_processor::register_flag cmd_print_colon_errors(
- 9,
- "format-colon-errors",
- "Emit ':line:col:' format for messages - lights up some tools",
- []{ flag_print_colon_errors = true; }
-);
-
-static auto flag_verbose = false;
-static cmdline_processor::register_flag cmd_verbose(
- 9,
- "verbose",
- "Print verbose statistics and -debug output",
- []{ flag_verbose = true; }
-);
-
-static auto flag_no_exceptions = false;
-static cmdline_processor::register_flag cmd_no_exceptions(
- 4,
- "fno-exceptions",
- "Disable C++ EH - failed 'as' for 'variant' will assert",
- []{ flag_no_exceptions = true; }
-);
-
-static auto flag_no_rtti = false;
-static cmdline_processor::register_flag cmd_no_rtti(
- 4,
- "fno-rtti",
- "Disable C++ RTTI - using 'as' for '*'/'std::any' will assert",
- []{ flag_no_rtti = true; }
-);
-
-struct text_with_pos{
- std::string text;
- source_position pos;
- text_with_pos(std::string const& t, source_position p) : text{t}, pos{p} { }
-};
-
-// Defined out of line so we can use flag_print_colon_errors.
-auto error_entry::print(
- auto& o,
- std::string const& file
-) const
- -> void
-{
- o << file ;
- if (where.lineno > 0) {
- if (flag_print_colon_errors) {
- o << ":" << (where.lineno);
- if (where.colno >= 0) {
- o << ":" << where.colno;
- }
- }
- else {
- o << "("<< (where.lineno);
- if (where.colno >= 0) {
- o << "," << where.colno;
- }
- o << ")";
- }
- }
- o << ":";
- if (internal) {
- o << " internal compiler";
- }
- o << " error: " << msg << "\n";
-}
-
-class positional_printer
-{
- // Core information
- std::ofstream out_file = {}; // Cpp1 syntax output file
- std::ostream* out = {}; // will point to out_file or cout
- std::string cpp2_filename = {};
- std::string cpp1_filename = {};
- std::vector<comment> const* pcomments = {}; // Cpp2 comments data
- source const* psource = {};
- parser const* pparser = {};
-
- source_position curr_pos = {}; // current (line,col) in output
- lineno_t generated_pos_line = {}; // current line in generated output
- int last_line_indentation = {};
- int next_comment = 0; // index of the next comment not yet printed
- bool last_was_empty = false;
- int empty_lines_suppressed = 0;
- bool just_printed_line_directive = false;
- bool printed_extra = false;
- char last_printed_char = {};
-
- struct req_act_info {
- colno_t requested;
- colno_t offset;
-
- req_act_info(colno_t r, colno_t o) : requested{r}, offset{o} { }
- };
- struct {
- lineno_t line = {};
- std::vector<req_act_info> requests = {};
- } prev_line_info = {};
-
- // Position override information
- std::vector<source_position> preempt_pos = {}; // use this position instead of the next supplied one
- int pad_for_this_line = 0; // extra padding to add/subtract for this line only
- bool ignore_align = false;
- int ignore_align_indent = 0;
- lineno_t ignore_align_lineno = 0;
- bool enable_indent_heuristic = true;
-
-public:
- // Modal information
- enum phases {
- phase0_type_decls = 0,
- phase1_type_defs_func_decls = 1,
- phase2_func_defs = 2
- };
- auto get_phase() const { return phase; }
-
-private:
- phases phase = phase0_type_decls;
-
- auto inc_phase() -> void {
- switch (phase) {
- break;case phase0_type_decls : phase = phase1_type_defs_func_decls;
- break;case phase1_type_defs_func_decls: phase = phase2_func_defs;
- break;default : assert(!"ICE: invalid lowering phase");
- }
- curr_pos = {};
- next_comment = 0; // start over with the comments
- }
-
- std::vector<std::string*> emit_string_targets; // option to emit to string instead of out file
- std::vector<std::vector<text_with_pos>*> emit_text_chunks_targets; // similar for vector<text_pos>
-
- enum class target_type { string, chunks };
- std::vector<target_type> emit_target_stack; // to interleave them sensibly
-
-
- //-----------------------------------------------------------------------
- // Print text
- //
- auto print(
- std::string_view s,
- source_position pos = source_position{},
- bool track_curr_pos = true,
- bool is_known_empty = false
- )
- -> void
- {
- // Take ownership of (and reset) just_printed_line_directive value
- auto line_directive_already_done = std::exchange(just_printed_line_directive, false);
-
- // If the caller is capturing this output, emit to the
- // current target instead and skip most positioning logic
- if (!emit_target_stack.empty())
- {
- // If capturing to a string, emit to the specified string
- if (emit_target_stack.back() == target_type::string) {
- assert(!emit_string_targets.empty());
- *emit_string_targets.back() += s;
- }
-
- // If capturing to a vector of chunks, emit to that
- else {
- assert(!emit_text_chunks_targets.empty());
- emit_text_chunks_targets.back()->insert( emit_text_chunks_targets.back()->begin(), text_with_pos(std::string(s), pos) );
- }
-
- return;
- }
-
- // Otherwise, we'll actually print the string to the output file
- // and update our curr_pos position
-
- if (s.length() > 0) {
- last_printed_char = s.back();
- }
-
- // Reject consecutive empty lines: If this line is empty
- if (
- ( s == "\n" || is_known_empty )
- && curr_pos.colno <= 1
- )
- {
- // And so was the last one, update logical position only
- // and increment empty_lines_suppressed instead of printing
- if (last_was_empty) {
- if (track_curr_pos) {
- ++curr_pos.lineno;
- curr_pos.colno = 1;
- }
- ++empty_lines_suppressed;
- return;
- }
- // If this is the first consecutive empty, remember and continue
- last_was_empty = true;
- }
- // Otherwise, if this line is not empty
- else {
- // Remember that this line was not empty
- last_was_empty = false;
-
- // And if we did suppress any empties, emit a #line to resync
- if (empty_lines_suppressed > 0) {
- if (!line_directive_already_done) {
- print_line_directive(curr_pos.lineno);
- }
- empty_lines_suppressed = 0;
- }
- }
-
- // Output the string
- assert (out);
- *out << s;
-
- // Update curr_pos by finding how many line breaks s contained,
- // and where the last one was which determines our current colno
- if (track_curr_pos)
- {
- auto last_newline = std::string::npos; // the last newline we found in the string
- auto newline_pos = std::size_t(0); // the current newline we found in the string
- while ((newline_pos = s.find('\n', newline_pos)) != std::string::npos)
- {
- // For each line break we find, reset pad and inc current lineno
- pad_for_this_line = 0;
- ++curr_pos.lineno;
- last_newline = newline_pos;
- ++newline_pos;
- }
-
- // Now also adjust the colno
- if (last_newline != std::string::npos) {
- // If we found a newline, it's the distance from the last newline to EOL
- curr_pos.colno = s.length() - last_newline;
- }
- else {
- // Else add the length of the string
- curr_pos.colno += s.length();
- }
- }
- }
-
-
- //-----------------------------------------------------------------------
- // Internal helpers
-
- // Start a new line if we're not in col 1 already
- //
- auto ensure_at_start_of_new_line()
- -> void
- {
- if (curr_pos.colno > 1) {
- auto old_pos = curr_pos;
- print( "\n" );
- assert(curr_pos.lineno == old_pos.lineno+1);
- assert(curr_pos.colno == 1);
- }
- }
-
- // Print a #line directive
- //
- auto print_line_directive( lineno_t line )
- -> void
- {
- // Ignore requests from generated code (negative line numbers)
- if (line < 1) {
- return;
- }
-
- // Otherwise, implement the request
- prev_line_info = { curr_pos.lineno, { } };
- ensure_at_start_of_new_line();
-
- // Not using print() here because this is transparent to the curr_pos
- if (!flag_clean_cpp1) {
- assert (out);
- *out << "#line " << line << " " << std::quoted(cpp2_filename) << "\n";
- }
- just_printed_line_directive = true;
- }
-
- // Catch up with comment/blank lines
- //
- auto print_comment(comment const& c)
- -> void
- {
- // For a line comment, start it at the right indentation and print it
- // with a newline end
- if (c.kind == comment::comment_kind::line_comment) {
- print( pad( c.start.colno - curr_pos.colno + 1 ) );
- print( c.text );
- assert( c.text.find("\n") == std::string::npos ); // we shouldn't have newlines
- print("\n");
- }
-
- // For a stream comment, pad out to its column (if we haven't passed it already)
- // and emit it there
- else {
- print( pad( c.start.colno - curr_pos.colno ) );
- print( c.text );
- }
-
- c.dbg_was_printed = true;
- }
-
- auto flush_comments( source_position pos )
- -> void
- {
- if (!pcomments) {
- return;
- }
-
- // For convenience
- auto& comments = *pcomments;
-
- // Add unprinted comments and blank lines as needed to catch up vertically
- //
- while (curr_pos.lineno < pos.lineno)
- {
- // If a comment goes on this line, print it
- if (
- next_comment < std::ssize(comments)
- && comments[next_comment].start.lineno <= curr_pos.lineno
- )
- {
- // Emit non-function body comments in phase1_type_defs_func_decls,
- // and emit function body comments in phase2_func_defs
- assert(pparser);
- if (
- (
- phase == phase1_type_defs_func_decls
- && !pparser->is_within_function_body( comments[next_comment].start.lineno )
- )
- ||
- (
- phase == phase2_func_defs
- && pparser->is_within_function_body( comments[next_comment].start.lineno )
- )
- )
- {
- print_comment( comments[next_comment] );
- assert(curr_pos.lineno <= pos.lineno); // we shouldn't have overshot
- }
-
- ++next_comment;
- }
-
- // Otherwise, just print a blank line
- else {
- print("\n");
- }
- }
- }
-
- auto print_unprinted_comments()
- {
- for (auto const& c : *pcomments) {
- if (!c.dbg_was_printed) {
- print_comment(c);
- }
- }
- }
-
- // Position ourselves as close to pos as possible,
- // and catch up with displaying comments
- //
- auto align_to( source_position pos )
- -> void
- {
- auto on_same_line = curr_pos.lineno == pos.lineno;
-
- // Ignoring this logic is used when we're generating new code sections,
- // such as return value structs, and emitting raw string literals
- if (ignore_align) {
- print( pad( ignore_align_indent - curr_pos.colno ) );
- return;
- }
-
- // Otherwise, we need to apply our usual alignment logic
-
- // Catch up with displaying comments
- flush_comments( pos );
-
- // If we're not on the right line
- if (
- printed_extra
- && !on_same_line
- )
- {
- print_line_directive(pos.lineno);
- curr_pos.lineno = pos.lineno;
- printed_extra = false;
- }
- else if (curr_pos.lineno < pos.lineno)
- {
- // In case we're just one away, try a blank line
- // (this might get ignored and we'll get the line directive)
- print( "\n" );
- if (curr_pos.lineno != pos.lineno) {
- print_line_directive(pos.lineno);
- }
- curr_pos.lineno = pos.lineno;
- }
-
- // Finally, align to the target column, if we're on the right line
- // and not one-past-the-end on the extra line at section end)
- assert(
- psource
- && 0 <= curr_pos.lineno
- && curr_pos.lineno < std::ssize(psource->get_lines())+1
- );
- if (
- curr_pos.lineno == pos.lineno
- && curr_pos.lineno < std::ssize(psource->get_lines())
- )
- {
- // Record this line's indentation as the 'last' line for next time
- last_line_indentation = psource->get_lines()[curr_pos.lineno].indent();
-
- // If this line was originally densely spaced (had <2 whitespace
- // between all tokens), then the programmer likely wasn't doing a lot
- // of special formatting...
- if (psource->get_lines()[curr_pos.lineno].all_tokens_are_densely_spaced)
- {
- // For the first token in a line, use the line's original indentation
- if (curr_pos.colno <= 1)
- {
- print( pad( psource->get_lines()[curr_pos.lineno].indent() ) );
- }
- // For later tokens, don't try to add padding
- else {
- if (
- last_printed_char == ';'
- && on_same_line
- )
- {
- print( " " );
- }
- }
- }
- // Otherwise, make a best effort to adjust position with some padding
- else
- {
- pos.colno = std::max( 1, pos.colno + pad_for_this_line );
- print( pad( pos.colno - curr_pos.colno ) );
- }
- }
- }
-
-
-public:
- //-----------------------------------------------------------------------
- // Finalize phase
- //
- auto finalize_phase(bool print_remaining_comments = false)
- {
- if (
- is_open()
- && psource
- && psource->has_cpp2()
- )
- {
- flush_comments( {curr_pos.lineno+1, 1} );
-
- if (print_remaining_comments) {
- print_unprinted_comments();
- }
-
- // Always make sure the very last line ends with a newline
- // (not really necessary but makes some tools quieter)
- // -- but only if there's any Cpp2, otherwise don't
- // because passing through all-Cpp1 code should always
- // remain diff-identical
- if (phase == phase2_func_defs) {
- print_extra("\n");
- }
- }
- }
-
-
- //-----------------------------------------------------------------------
- // Open
- //
- auto open(
- std::string cpp2_filename_,
- std::string cpp1_filename_,
- std::vector<comment> const& comments,
- cpp2::source const& source,
- cpp2::parser const& parser
- )
- -> void
- {
- cpp2_filename = cpp2_filename_;
- assert(
- !is_open()
- && !pcomments
- && "ICE: tried to call .open twice"
- );
- cpp1_filename = cpp1_filename_;
- if (cpp1_filename == "stdout") {
- out = &std::cout;
- }
- else {
- out_file.open(cpp1_filename);
- out = &out_file;
- }
- pcomments = &comments;
- psource = &source;
- pparser = &parser;
- }
-
- auto reopen()
- -> void
- {
- assert(
- is_open()
- && "ICE: tried to call .reopen without first calling .open"
- );
- assert(cpp1_filename.ends_with(".h"));
- out_file.close();
- out_file.open(cpp1_filename + "pp");
- }
-
- auto is_open()
- -> bool
- {
- if (out) {
- assert(
- pcomments
- && "ICE: if is_open, pcomments should also be set"
- );
- }
- return out;
- }
-
-
- //-----------------------------------------------------------------------
- // Abandon: close and delete
- //
- auto abandon()
- -> void
- {
- if (!is_open()) {
- return;
- }
- if (out_file.is_open()) {
- out_file.close();
- std::remove(cpp1_filename.c_str());
- }
- }
-
-
- //-----------------------------------------------------------------------
- // Print extra text and don't track positions
- // Used for Cpp2 boundary comment and prelude and final newline
- //
- auto print_extra( std::string_view s )
- -> void
- {
- assert(
- is_open()
- && "ICE: printer must be open before printing"
- );
- print( s, source_position{}, false );
- printed_extra = true;
- }
-
-
- //-----------------------------------------------------------------------
- // Print a Cpp1 line, which should be at lineno
- //
- auto print_cpp1( std::string_view s, lineno_t line )
- -> void
- {
- assert(
- is_open()
- && line >= 0
- && "ICE: printer must be open before printing, and line number must not be negative (Cpp1 code is never generated)"
- );
-
- // 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" );
- }
-
-
- //-----------------------------------------------------------------------
- // Used when we start a new Cpp2 section, or when we emit the same item
- // more than once (notably when we emit operator= more than once)
- //
- auto reset_line_to(lineno_t line, bool force = false)
- -> void
- {
- // 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 (
- force
- || 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,
- bool leave_newlines_alone = false,
- bool is_known_empty = false
-
- )
- -> void
- {
- // If we're printing for real (not to a string target)
- if (emit_target_stack.empty())
- {
- // If we're in a generated text region (signified by negative
- // line numbers), then shunt this call to print_extra instead
- if (pos.lineno < 1) {
- if (generated_pos_line != pos.lineno) {
- *out << "\n" + std::string(last_line_indentation, ' ');
- generated_pos_line = pos.lineno;
- }
- print_extra(s);
- return;
- }
-
- // Otherwise, we're no longer in generated code, so reset the
- // generated code counter
- generated_pos_line = {};
- }
-
- assert(
- is_open()
- && "ICE: printer must be open before printing"
- );
-
- // If there are any embedded newlines, split this string into
- // separate print_cpp2 calls
- if (auto newline_pos = s.find('\n');
- !leave_newlines_alone
- && s.length() > 1
- && newline_pos != s.npos
- )
- {
- while (newline_pos != std::string_view::npos)
- {
- // Print the text before the next newline
- if (newline_pos > 0) {
- print_cpp2( s.substr(0, newline_pos), pos );
- }
-
- // Emit the newline as a positioned empty string
- assert (s[newline_pos] == '\n');
- ++pos.lineno;
- pos.colno = 1;
- print_cpp2( "", pos, false, curr_pos.colno <= 1 );
-
- s.remove_prefix( newline_pos+1 );
- newline_pos = s.find('\n');
- }
- // Print any tail following the last newline
- if (!s.empty()) {
- print_cpp2( s, pos );
- }
- return;
- }
-
- // The rest of this call handles a single chunk that's either a
- // standalone "\n" or a piece of text that doesn't have a newline
-
- // 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 <= pos.colno;
- ++i
- )
- {
- if (prev_line_info.requests[i].requested == pos.colno)
- {
- last_line_offset = prev_line_info.requests[i].offset;
- break;
- }
- }
-
- // If there was one, apply the actual column number offset
- if (last_line_offset > 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, true, is_known_empty );
- }
-
-
- //-----------------------------------------------------------------------
- // 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;
- curr_pos.lineno = ignore_align_lineno; // pop state
- }
- }
-
-
- //-----------------------------------------------------------------------
- // Modal state control functions
- //
-
- auto next_phase()
- -> void
- {
- inc_phase();
- }
-
- // 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<text_with_pos>, 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<text_with_pos>* 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
-//
-//-----------------------------------------------------------------------
-//
-struct function_prolog {
- std::vector<std::string> mem_inits = {};
- std::vector<std::string> statements = {};
-};
-
-class cppfront
-{
- std::string sourcefile;
- std::vector<error_entry> 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;
-
- declaration_node const* having_signature_emitted = {};
-
- declaration_node const* generating_assignment_from = {};
- declaration_node const* generating_move_from = {};
- declaration_node const* generating_postfix_inc_dec_from = {};
- bool emitting_that_function = false;
- bool emitting_move_that_function = false;
- std::vector<token const*> already_moved_that_members = {};
-
- struct arg_info {
- passing_style pass = passing_style::in;
- token const* ptoken = {};
- };
- std::vector<arg_info> current_args = { {} };
-
- struct active_using_declaration {
- token const* identifier = {};
-
- explicit active_using_declaration(using_statement_node const& n) {
- if (auto id = get_if<id_expression_node::qualified>(&n.id->id)) {
- identifier = (*id)->ids.back().id->identifier;
- }
- }
- };
-
- using source_order_name_lookup_res =
- std::optional<std::variant<declaration_node const*, active_using_declaration>>;
-
- // Stack of the currently active nested declarations we're inside
- std::vector<declaration_node const*> current_declarations = { {} };
-
- // Stack of the currently active names for source order name lookup:
- // Like 'current_declarations' + also parameters and using declarations
- std::vector<source_order_name_lookup_res::value_type> current_names = { {} };
-
- // Maintain a stack of the functions we're currently processing, which can
- // be up to MaxNestedFunctions in progress (if we run out, bump the Max).
- // The main reason for this is to be able to pass function_info's, especially
- // their .epilog, by reference for performance while still having lifetime safety
- struct function_info
- {
- declaration_node const* decl = {};
- function_type_node const* func = {};
- declaration_node::declared_value_set_funcs declared_value_set_functions = {};
- function_prolog prolog = {};
- std::vector<std::string> epilog = {};
-
- function_info(
- declaration_node const* decl_,
- function_type_node const* func_,
- declaration_node::declared_value_set_funcs declared_value_set_functions_
- )
- : decl{decl_}
- , func{func_}
- , declared_value_set_functions{declared_value_set_functions_}
- { }
- };
- class current_functions_
- {
- std::deque<function_info> list = { {} };
- public:
- auto push(
- declaration_node const* decl,
- function_type_node const* func,
- declaration_node::declared_value_set_funcs thats
- ) {
- list.emplace_back(decl, func, thats);
- }
-
- auto pop() {
- list.pop_back();
- }
-
- auto back() -> function_info& {
- assert(!empty());
- return list.back();
- }
-
- auto empty() -> bool {
- return list.empty();
- }
- };
- current_functions_ current_functions;
-
- // For lowering
- //
- positional_printer printer;
- bool in_definite_init = false;
- bool in_parameter_list = false;
-
- std::string function_return_name;
- struct function_return {
- parameter_declaration_list_node* param_list;
- passing_style pass;
- bool is_deduced;
-
- function_return(
- parameter_declaration_list_node* param_list_,
- passing_style pass_ = passing_style::invalid,
- bool is_deduced_ = false
- )
- : param_list{param_list_}
- , pass{pass_}
- , is_deduced{is_deduced_}
- { }
- };
- std::vector<function_return> 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<std::string> function_requires_conditions;
-
- struct iter_info {
- iteration_statement_node const* stmt;
- bool used = false;
- };
- std::vector<iter_info> iteration_statements;
-
- std::vector<bool> in_non_rvalue_context = { false };
- std::vector<bool> 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",
- false,
- true // a noisy fallback error message
- );
- }
- }
-
- // 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,
- parser
- );
- 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);
-
-
- //---------------------------------------------------------------------
- // Do lowered file prolog
- //
- // Only emit extra lines if we actually have Cpp2, because
- // we want Cpp1-only files to pass through with zero changes
- // (unless the user requested import/include of std)
- if (
- source.has_cpp2()
- || flag_import_std
- || flag_include_std
- )
- {
- if (flag_emit_cppfront_info) {
- printer.print_extra(
- "\n// Generated by cppfront "
- #include "version.info"
- " build "
- #include "build.info"
- );
- }
- 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_include_std) {
- printer.print_extra( "#define CPP2_INCLUDE_STD Yes\n" );
- }
- else if (flag_import_std) {
- printer.print_extra( "#define CPP2_IMPORT_STD 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{};
-
-
- //---------------------------------------------------------------------
- // Do phase0_type_decls
- assert(printer.get_phase() == printer.phase0_type_decls);
-
- if (
- source.has_cpp2()
- && !flag_clean_cpp1
- )
- {
- printer.print_extra( "\n//=== Cpp2 type declarations ====================================================\n\n" );
- }
-
- if (
- !tokens.get_map().empty()
- || flag_import_std
- || flag_include_std
- )
- {
- printer.print_extra( "\n#include \"cpp2util.h\"\n\n" );
- }
-
- if (
- source.has_cpp2()
- && !flag_clean_cpp1
- )
- {
- printer.reset_line_to(1, true);
- }
-
- for (auto& section : tokens.get_map())
- {
- assert (!section.second.empty());
-
- // Get the parse tree for this section and emit each forward declaration
- auto decls = parser.get_parse_tree_declarations_in_range(section.second);
- for (auto& decl : decls) {
- assert(decl);
- emit(*decl);
- }
- }
-
-
- //---------------------------------------------------------------------
- // Do phase1_type_defs_func_decls
- //
- printer.finalize_phase();
- printer.next_phase();
-
- if (
- source.has_cpp2()
- && !flag_clean_cpp1
- )
- {
- printer.print_extra( "\n//=== Cpp2 type definitions and function declarations ===========================\n\n" );
- printer.reset_line_to(1, true);
- }
-
- assert (printer.get_phase() == positional_printer::phase1_type_defs_func_decls);
- 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)
- {
- if (
- source.has_cpp2()
- && line.cat != source_line::category::preprocessor
- )
- {
- ++ret.cpp2_lines;
- }
- else
- {
- ++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;
-
- // We should be in a position to emit a set of Cpp2 declarations
- if (
- map_iter != tokens.get_map().cend()
- && map_iter->first /*line*/ <= curr_lineno
- )
- {
- // We should be here only when we're at exactly the first line of a Cpp2 section
- assert (map_iter->first == curr_lineno);
- assert (!map_iter->second.empty());
-
- // Get the parse tree for this section and emit each forward declaration
- auto decls = parser.get_parse_tree_declarations_in_range(map_iter->second);
- for (auto& decl : decls) {
- assert(decl);
- emit(*decl);
- }
- ++map_iter;
- }
- }
- }
- ++curr_lineno;
- }
-
- // We can stop here if there's no Cpp2 code -- a file with no Cpp2
- // should have perfect passthrough verifiable with diff, including
- // that we didn't misidentify anything as Cpp2 (even in the
- // presence of nonstandard vendor extensions)
- //
- if (!source.has_cpp2()) {
- assert(ret.cpp2_lines == 0);
- return ret;
- }
-
- // If there is Cpp2 code, we have more to do...
-
- // First, if this is a .h2 and in a -pure-cpp2 compilation,
- // we need to switch filenames
- if (
- cpp1_filename.back() == 'h'
- && flag_cpp2_only
- )
- {
- printer.print_extra( "\n#endif\n" );
-
- printer.reopen();
- if (!printer.is_open()) {
- errors.emplace_back(
- source_position{},
- "could not open second output file " + cpp1_filename
- );
- return {};
- }
-
- printer.print_extra( "\n#ifndef " + cpp1_FILENAME+"_CPP2" );
- printer.print_extra( "\n#error This file is part of a '.h2' header compiled to be consumed from another -pure-cpp2 file. To use this file, write '#include \"" + cpp1_filename + "2\"' in a '.h2' or '.cpp2' file compiled with -pure-cpp2." );
- printer.print_extra( "\n#endif\n" );
-
- cpp1_FILENAME += "PP";
- printer.print_extra( "\n#ifndef " + cpp1_FILENAME+"_CPP2" );
- printer.print_extra( "\n#define " + cpp1_FILENAME+"_CPP2" + "\n\n" );
-
- printer.print_extra( hpp_includes );
- }
-
-
- //---------------------------------------------------------------------
- // Do phase2_func_defs
- //
- printer.finalize_phase();
- printer.next_phase();
-
- if (
- source.has_cpp2()
- && !flag_clean_cpp1
- )
- {
- printer.print_extra( "\n//=== Cpp2 function definitions =================================================\n\n" );
- printer.reset_line_to(1, true);
- }
-
- for (auto& section : tokens.get_map())
- {
- assert (!section.second.empty());
-
- // Get the parse tree for this section and emit each forward declaration
- auto decls = parser.get_parse_tree_declarations_in_range(section.second);
- for (auto& decl : decls) {
- assert(decl);
- emit(*decl);
- }
- }
-
- if (cpp1_filename.back() == 'h') {
- printer.print_extra( "\n#endif" );
- }
-
- printer.finalize_phase( true );
-
- // Finally, some debug checks
- assert(
- (!errors.empty() || tokens.num_unprinted_comments() == 0)
- && "ICE: not all comments were printed"
- );
-
- return ret;
- }
-
-
- //-----------------------------------------------------------------------
- //
- // emit() functions - each emits a kind of node
- //
- // The body often mirrors the node's visit() function, unless customization
- // is needed where Cpp1 and Cpp2 have different grammar orders
- //
-
- void print_to_string(
- std::string* str,
- auto& i,
- auto... more
- )
- {
- // Quick special-purpose state preservation... this tactical hack
- // is fine for now, but if needed more then generalize this
- auto state1 = need_expression_list_parens;
- auto state2 = already_moved_that_members;
-
- printer.emit_to_string(str);
- emit(i, more...);
- printer.emit_to_string();
-
- // Restore state
- need_expression_list_parens.swap(state1);
- already_moved_that_members .swap(state2);
- };
-
- auto print_to_string(
- auto& i,
- auto... more
- )
- -> std::string
- {
- auto print = std::string{};
- print_to_string(&print, i, more...);
- return print;
- };
-
- //-----------------------------------------------------------------------
- // try_emit
- //
- // Helper to emit whatever is in a variant where each
- // alternative is a smart pointer
- //
- template <int I>
- auto try_emit(
- auto& v,
- auto&&... more
- )
- -> void
- {
- if (v.index() == I) {
- auto const& alt = std::get<I>(v);
- assert (alt);
- emit (*alt, CPP2_FORWARD(more)...);
- }
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(
- token const& n,
- bool is_qualified = false,
- source_position pos = {}
- )
- -> void
- { STACKINSTR
- if (pos == source_position{}) {
- pos = n.position();
- }
-
- // Implicit "cpp2::" qualification of Cpp2 fixed-width type aliases
- // and cpp2::finally
- if (
- !is_qualified
- && (
- n.type() == lexeme::Cpp2FixedType
- || n == "finally"
- )
- )
- {
- printer.print_cpp2("cpp2::", pos);
- }
-
- // 'this' is not a pointer
- if (n == "this") {
- printer.print_cpp2("(*this)", pos);
- }
- // Reclaim the alternative names and some keywords for users
- else if (
- n == "and"
- || n == "and_eq"
- || n == "bitand"
- || n == "bitor"
- || n == "compl"
- || n == "not"
- || n == "not_eq"
- || n == "or"
- || n == "or_eq"
- || n == "xor"
- || n == "xor_eq"
- || n == "new"
- || n == "class"
- || n == "struct"
- || n == "enum"
- || n == "union"
- )
- {
- printer.print_cpp2("cpp2_"+n.to_string(), pos);
- }
- else {
- printer.print_cpp2(n, pos, true);
- }
-
- in_definite_init = is_definite_initialization(&n);
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(
- literal_node const& n,
- source_position pos = {}
- )
- -> void
- { STACKINSTR
- if (pos == source_position{}) {
- pos = n.position();
- }
-
- assert(n.literal);
- emit(*n.literal);
- if (n.user_defined_suffix) {
- emit(*n.user_defined_suffix);
- }
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(
- unqualified_id_node const& n,
- bool in_synthesized_multi_return = false,
- bool is_local_name = true,
- bool is_qualified = false
- )
- -> void
- { STACKINSTR
- auto last_use = is_definite_last_use(n.identifier);
-
- bool add_forward =
- last_use
- && last_use->is_forward
- && !in_non_rvalue_context.back();
-
- bool add_move =
- !add_forward
- && (
- in_synthesized_multi_return
- || (last_use && !suppress_move_from_last_use)
- )
- && !in_non_rvalue_context.back();
-
- if (
- add_move
- && *(n.identifier - 1) == "return"
- && *(n.identifier + 1) == ";"
- )
- {
- add_move = false;
- }
-
- if (
- emitting_move_that_function
- && *n.identifier == "that"
- )
- {
- add_move = true;
- }
-
- // For an explicit 'forward' apply forwarding to correct identifier
- assert (!current_args.empty());
- if (current_args.back().pass == passing_style::forward) {
- add_forward = current_args.back().ptoken == n.identifier;
- }
-
- if (add_move) {
- printer.print_cpp2("std::move(", n.position());
- }
- if (add_forward) {
- printer.print_cpp2("CPP2_FORWARD(", {n.position().lineno, n.position().colno - 8});
- }
-
- assert(n.identifier);
- emit(*n.identifier, is_qualified); // inform the identifier if we know this is qualified
-
- if (n.open_angle != source_position{}) {
- printer.print_cpp2("<", n.open_angle);
- auto first = true;
- for (auto& a : n.template_args) {
- if (!first) {
- printer.print_cpp2(",", a.comma);
- }
- first = false;
- try_emit<template_argument::expression>(a.arg);
- try_emit<template_argument::type_id >(a.arg);
- }
- printer.print_cpp2(">", n.close_angle);
- }
-
- in_definite_init = is_definite_initialization(n.identifier);
- if (
- !in_definite_init
- && !in_parameter_list
- )
- {
- if (auto decl = sema.get_declaration_of(*n.identifier);
- is_local_name
- && !(*n.identifier == "this")
- && !(*n.identifier == "that")
- && decl
- && (
- in_synthesized_multi_return
- // note pointer equality: if we're not in the actual declaration of n.identifier
- || decl->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,
- bool include_unqualified_id = true
- )
- -> void
- { STACKINSTR
- if (!sema.check(n)) {
- return;
- }
-
- // 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 : std::span{n.ids}.first(n.ids.size() - !include_unqualified_id))
- {
- 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
- { STACKINSTR
- if (pos == source_position{}) {
- pos = n.position();
- }
-
- if (n.is_wildcard()) {
- printer.print_cpp2("auto", pos);
- }
- else {
- try_emit<type_id_node::unqualified>(n.id, false, false);
- try_emit<type_id_node::qualified >(n.id);
- try_emit<type_id_node::keyword >(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
- { STACKINSTR
- try_emit<id_expression_node::qualified >(n.id);
- try_emit<id_expression_node::unqualified>(n.id, false, is_local_name);
- }
-
-
- auto emit_prolog_mem_inits(
- function_prolog const& prolog,
- colno_t indent
- )
- -> void
- { STACKINSTR
- for (auto& line : prolog.mem_inits) {
- printer.print_extra("\n");
- printer.print_extra(pad(indent-1));
- printer.print_extra(line);
- }
- }
-
- auto emit_prolog_statements(
- function_prolog const& prolog,
- colno_t indent
- )
- -> void
- { STACKINSTR
- for (auto& line : prolog.statements) {
- printer.print_extra("\n");
- printer.print_extra(pad(indent-1));
- printer.print_extra(line);
- }
- }
-
- auto emit_epilog_statements(
- std::vector<std::string> const& epilog,
- colno_t indent
- )
- -> void
- { STACKINSTR
- for (auto& line : epilog) {
- printer.print_extra("\n");
- printer.print_extra(pad(indent-1));
- printer.print_extra(line);
- }
- }
-
- //-----------------------------------------------------------------------
- //
- auto emit(
- compound_statement_node const& n,
- function_prolog const& function_prolog = {},
- std::vector<std::string> const& function_epilog = {}
- )
- -> void
- { STACKINSTR
- emit_prolog_mem_inits(function_prolog, n.body_indent+1);
-
- printer.print_cpp2( "{", n.open_brace );
-
- emit_prolog_statements(function_prolog, n.body_indent+1);
-
- for (auto const& x : n.statements) {
- assert(x);
- emit(*x);
- }
-
- emit_epilog_statements( function_epilog, n.body_indent+1);
-
- printer.print_cpp2( "}", n.close_brace );
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(
- inspect_expression_node const& n,
- bool is_expression
- )
- -> void
- { STACKINSTR
- 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_<", "cpp2::as<" );
-
- // If this is an inspect-expression, we'll have to wrap each alternative
- // in an 'if constexpr' so that its type is ignored for mismatches with
- // the inspect-expression's type
- auto return_prefix = std::string{};
- auto return_suffix = std::string{";"}; // use this to tack the ; back on in the alternative body
- if (is_expression) {
- return_prefix = "{ if constexpr( requires{" + statement + ";} ) if constexpr( std::is_convertible_v<CPP2_TYPEOF((" + statement + "))," + result_type + "> ) return ";
- return_suffix += " }";
- }
-
- if (id == "auto") {
- found_wildcard = true;
- if (is_expression) {
- printer.print_cpp2("return ", alt->position());
- }
- }
- else {
- printer.print_cpp2("if " + constexpr_qualifier, alt->position());
- if (alt->type_id) {
- printer.print_cpp2("(cpp2::is<" + id + ">(_expr)) ", alt->position());
- }
- else {
- assert (alt->value);
- printer.print_cpp2("(cpp2::is(_expr, " + id + ")) ", alt->position());
- }
- printer.print_cpp2(return_prefix, alt->position());
- }
-
- printer.print_cpp2(statement, alt->position());
-
- if (
- is_expression
- && id != "auto"
- )
- {
- assert(alt->statement->is_expression());
- printer.print_cpp2("; else return " + result_type + "{}", alt->position());
- printer.print_cpp2("; else return " + result_type + "{}", alt->position());
- }
-
- printer.print_cpp2(return_suffix, alt->position());
- }
- else {
- errors.emplace_back(
- alt->position(),
- "(temporary alpha limitation) cppfront is still learning 'inspect' - only simple 'is' alternatives are currently supported"
- );
- return;
- }
- }
-
- if (is_expression) {
- if (!found_wildcard) {
- errors.emplace_back(
- n.position(),
- "an inspect expression must have an `is _` match-anything wildcard alternative"
- );
- return;
- }
- }
- else {
- printer.print_cpp2("}", n.close_brace);
- }
-
- // If this is an expression, finally actually invoke the lambda
- if (is_expression) {
- printer.print_cpp2("()", n.close_brace);
- }
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(selection_statement_node const& n)
- -> void
- { STACKINSTR
- assert(n.identifier);
- emit(*n.identifier);
-
- if (n.is_constexpr) {
- printer.print_cpp2(" constexpr", n.position());
- }
-
- printer.print_cpp2(" (", n.position());
- printer.add_pad_in_this_line(1);
-
- assert(n.expression);
- emit(*n.expression);
-
- printer.print_cpp2(") ", n.position());
- printer.add_pad_in_this_line(1);
-
- assert(n.true_branch);
- emit(*n.true_branch);
-
- if (n.has_source_false_branch) {
- printer.print_cpp2("else ", n.else_pos);
- emit(*n.false_branch);
- }
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(iteration_statement_node const& n)
- -> void
- { STACKINSTR
- assert(n.identifier);
- in_non_rvalue_context.push_back(true);
- auto guard = finally([&]{ in_non_rvalue_context.pop_back(); });
-
- iteration_statements.push_back({ &n, false});
- auto labelname = labelized_position(n.label);
-
- // Handle while
- //
- if (*n.identifier == "while") {
- assert(
- n.condition
- && n.statements
- && !n.range
- && !n.body
- );
-
- // We emit Cpp2 while loops as Cpp2 for loops if there's a "next" clause
- if (!n.next_expression) {
- printer.print_cpp2("while( ", n.position());
- emit(*n.condition);
- }
- else {
- printer.print_cpp2("for( ; ", n.position());
- emit(*n.condition);
- printer.print_cpp2("; ", n.position());
- printer.add_pad_in_this_line(-10);
- emit(*n.next_expression);
- }
- printer.print_cpp2(" ) ", n.position());
- if (!labelname.empty()) {
- printer.print_extra("{");
- }
- emit(*n.statements);
- if (!labelname.empty()) {
- printer.print_extra(" CPP2_CONTINUE_BREAK("+labelname+") }");
- }
- }
-
- // Handle do
- //
- else if (*n.identifier == "do") {
- assert(
- n.condition
- && n.statements
- && !n.range
- && !n.body
- );
-
- printer.print_cpp2("do ", n.position());
- if (!labelname.empty()) {
- printer.print_extra("{");
- }
- emit(*n.statements);
- if (!labelname.empty()) {
- printer.print_extra(" CPP2_CONTINUE_BREAK("+labelname+") }");
- }
- printer.print_cpp2(" while ( ", n.position());
- if (n.next_expression) {
- // Gotta say, this feels kind of nifty... short-circuit eval
- // and smuggling work into a condition via a lambda, O my...
- printer.print_cpp2("[&]{ ", n.position());
- emit(*n.next_expression);
- printer.print_cpp2(" ; return true; }() && ", n.position());
- }
- emit(*n.condition);
- printer.print_cpp2(");", n.position());
- }
-
- // Handle for
- //
- else if (*n.identifier == "for") {
- assert(
- !n.condition
- && !n.statements
- && n.range
- && n.parameter
- && n.body
- );
-
- // Note: This used to emit cpp2_range as a range-for-loop scope variable,
- // but some major compilers seem to have random troubles with that;
- // the workaround to avoid their bugs for now is to emit a { } block
- // around the Cpp1 range-for and make the scope variable a normal local
-
- printer.print_cpp2("for ( ", n.position());
-
- emit(*n.parameter);
-
- printer.print_cpp2(" : ", n.position());
-
- // If this expression is just a single expression-list, we can
- // take over direct control of emitting it without needing to
- // go through the whole grammar, and surround it with braces
- if (n.range->is_expression_list()) {
- printer.print_cpp2( "{ ", n.position() );
- emit(*n.range->get_expression_list(), false);
- printer.print_cpp2( " }", n.position() );
- }
- // Otherwise, just emit the general expression as usual
- else {
- emit(*n.range);
- }
-
- printer.print_cpp2(" ) ", n.position());
- if (!labelname.empty()) {
- printer.print_extra("{");
- }
-
- // If there's a next-expression, smuggle it in via a nested do/while(false) loop
- // (nested "continue" will work, but "break" won't until we do extra work to implement
- // that using a flag and implementing "break" as "_for_break = true; continue;")
- if (n.next_expression) {
- printer.print_cpp2(" { do ", n.position());
- }
-
- assert(n.body);
- emit(*n.body);
-
- if (n.next_expression) {
- printer.print_cpp2(" while (false); ", n.position());
- emit(*n.next_expression);
- printer.print_cpp2("; }", n.position());
- }
-
- printer.print_cpp2("", n.position());
- if (!labelname.empty()) {
- printer.print_extra(" CPP2_CONTINUE_BREAK("+labelname+") }");
- }
- }
-
- else {
- assert(!"ICE: unexpected case");
- }
-
- assert (iteration_statements.back().stmt);
- if (
- iteration_statements.back().stmt->label
- && !iteration_statements.back().used
- )
- {
- auto name = iteration_statements.back().stmt->label->to_string();
- errors.emplace_back(
- iteration_statements.back().stmt->position(),
- name + ": a named loop must have its name used (did you forget 'break " + name + ";' or 'continue " + name + "';?)"
- );
- }
-
- iteration_statements.pop_back();
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(return_statement_node const& n)
- -> void
- { STACKINSTR
- assert (!current_functions.empty());
- if (current_functions.back().func->has_postconditions()) {
- printer.print_cpp2( "cpp2_finally_presuccess.run(); ", n.position() );
- }
-
- assert(n.identifier);
- assert(*n.identifier == "return");
- printer.print_cpp2("return ", n.position());
-
- // Return with expression == single anonymous return type
- //
- if (n.expression)
- {
- assert(!current_functions.empty());
- auto is_forward_return =
- !function_returns.empty()
- && function_returns.back().pass == passing_style::forward;
- auto is_deduced_return =
- !function_returns.empty()
- && function_returns.back().is_deduced;
-
- // If we're doing a forward return of a single-token name
- if (auto tok = n.expression->expr->get_postfix_expression_node()->expr->get_token();
- tok
- && is_forward_return
- )
- {
- // Ensure we're not returning a local or an in/move parameter
- auto is_parameter_name = current_functions.back().decl->has_parameter_named(*tok);
- if (
- is_parameter_name
- && (
- current_functions.back().decl->has_in_parameter_named(*tok)
- || current_functions.back().decl->has_move_parameter_named(*tok)
- )
- )
- {
- errors.emplace_back(
- n.position(),
- "a 'forward' return type cannot return an 'in' or 'move' parameter"
- );
- return;
- }
- else if (
- !is_parameter_name
- && sema.get_declaration_of(*tok)
- )
- {
- errors.emplace_back(
- n.position(),
- "a 'forward' return type cannot return a local variable"
- );
- return;
- } else if (
- is_literal(tok->type()) || n.expression->expr->is_result_a_temporary_variable()
- )
- {
- errors.emplace_back(
- n.position(),
- "a 'forward' return type cannot return a temporary variable"
- );
- return;
- }
- }
-
- // If this expression is just a single expression-list, we can
- // take over direct control of emitting it without needing to
- // go through the whole grammar, and surround it with braces
- if (n.expression->is_expression_list()) {
- if (!is_deduced_return) {
- printer.print_cpp2( "{ ", n.position() );
- }
- emit(*n.expression->get_expression_list(), false);
- if (!is_deduced_return) {
- printer.print_cpp2( " }", n.position() );
- }
- }
- // Otherwise, just emit the general expression as usual
- else {
- emit(*n.expression);
- }
-
- if (
- function_returns.empty()
- || function_returns.back().param_list != &single_anon
- )
- {
- errors.emplace_back(
- n.position(),
- "return statement with expression must be in a function with a single anonymous return value"
- );
- return;
- }
- }
-
- else if (
- !function_returns.empty()
- && function_returns.back().param_list == &single_anon
- )
- {
- errors.emplace_back(
- n.position(),
- "return statement must have an expression in a function with a single anonymous return value"
- );
- }
-
- // Return without expression, could be assignment operator
- //
- else if (generating_assignment_from == current_functions.back().decl)
- {
- printer.print_cpp2("*this", n.position());
- }
-
- // Otherwise, zero or named return values
- //
- else if (
- !function_returns.empty()
- && function_returns.back().param_list
- )
- {
- auto& parameters = function_returns.back().param_list->parameters;
-
- auto stmt = std::string{};
-
- // Put braces only around multiple named returns, which are a struct
- // - single named returns are emitted as ordinary returns, and extra
- // { } would be legal but generate a noisy warning on some compilers
- if (std::ssize(parameters) > 1) {
- stmt += std::string(" { ");
- }
-
- for (bool first = true; auto& param : parameters) {
- if (!first) {
- stmt += ", ";
- }
- first = false;
- assert(param->declaration->identifier);
-
- printer.emit_to_string(&stmt);
- emit(*param->declaration->identifier, true);
- printer.emit_to_string();
- }
-
- if (std::ssize(parameters) > 1) {
- stmt += std::string(" }");
- }
-
- printer.print_cpp2(stmt, n.position());
- }
-
- printer.print_cpp2("; ", n.position());
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(jump_statement_node const& n)
- -> void
- { STACKINSTR
- assert(n.keyword);
-
- if (n.label) {
- auto iter_stmt =
- std::find_if(
- iteration_statements.begin(),
- iteration_statements.end(),
- [&](auto& s){
- assert(s.stmt);
- return
- s.stmt->label
- && std::string_view{*s.stmt->label} == std::string_view{*n.label}
- ;
- }
- );
- if (iter_stmt == iteration_statements.end())
- {
- errors.emplace_back(
- n.position(),
- "a named " + n.keyword->to_string() + " must use the name of an enclosing local loop label"
- );
- return;
- }
- iter_stmt->used = true;
- assert((*iter_stmt).stmt->label);
- printer.print_cpp2(
- "goto " + to_upper_and_underbar(*n.keyword) + "_" + labelized_position((*iter_stmt).stmt->label) + ";",
- n.position()
- );
- }
- else {
- emit(*n.keyword);
- printer.print_cpp2(";", n.position());
- }
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(using_statement_node const& n)
- -> void
- { STACKINSTR
- assert(n.keyword);
- emit(*n.keyword);
-
- if (n.for_namespace) {
- printer.print_cpp2(" namespace", n.position());
- } else {
- current_names.push_back(active_using_declaration{n});
- }
-
- printer.print_cpp2(" " + print_to_string(*n.id) + ";", n.position());
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto build_capture_lambda_intro_for(
- capture_group& captures,
- source_position pos,
- bool include_default_reference_capture = false
- )
- -> std::string
- {
- // First calculate the stringized version of each capture expression
- // This will let us compare and de-duplicate repeated capture expressions
- for (auto& cap : captures.members)
- {
- assert(cap.capture_expr->cap_grp == &captures);
- if (cap.str.empty()) {
- print_to_string(&cap.str, *cap.capture_expr, true);
- suppress_move_from_last_use = true;
- print_to_string(&cap.str_suppressed_move, *cap.capture_expr, true);
- suppress_move_from_last_use = false;
- }
- }
-
- // If move from last use was used on the variable we need to rewrite the str to add std::move
- // to earlier use of the variable. That will save us from capturing one variable two times
- // (one with copy and one with std::move).
- for (auto rit = captures.members.rbegin(); rit != captures.members.rend(); ++rit)
- {
- auto is_same_str_suppressed_move = [s=rit->str_suppressed_move](auto& cap){
- return cap.str_suppressed_move == s;
- };
-
- auto rit2 = std::find_if(rit+1, captures.members.rend(), is_same_str_suppressed_move);
- while (rit2 != captures.members.rend())
- {
- rit2->str = rit->str;
- rit2 = std::find_if(rit2+1, captures.members.rend(), is_same_str_suppressed_move);
- }
- }
-
- // Then build the capture list, ignoring duplicated expressions
- auto lambda_intro = std::string("[");
- auto num_captures = 0;
-
- if (
- (!current_functions.empty()
- && current_functions.back().decl->is_function_with_this()
- && !current_functions.back().decl->parent_is_namespace()
- )
- || include_default_reference_capture
- )
- {
- // Note: & is needed (when allowed, not at namespace scope) because a
- // nested UFCS might be viewed as trying to capture 'this'
- lambda_intro += "&";
- ++num_captures;
- }
-
- printer.emit_to_string(&lambda_intro);
-
- auto handled = std::vector<std::string>{};
- for (auto& cap : captures.members)
- {
- // If we haven't handled a capture that looks like this one
- if (std::find(handled.begin(), handled.end(), cap.str) == handled.end())
- {
- // Remember it
- handled.push_back(cap.str);
-
- // And handle it
- if (num_captures != 0) { // not first
- lambda_intro += ", ";
- }
- cap.cap_sym = "_"+std::to_string(num_captures);
- printer.print_cpp2(cap.cap_sym + " = " + cap.str, pos);
- }
- ++num_captures;
- }
- printer.emit_to_string();
- lambda_intro += "]";
-
- return lambda_intro;
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(primary_expression_node const& n)
- -> void
- { STACKINSTR
- try_emit<primary_expression_node::identifier >(n.expr);
- try_emit<primary_expression_node::expression_list>(n.expr);
- try_emit<primary_expression_node::id_expression >(n.expr);
- try_emit<primary_expression_node::inspect >(n.expr, true);
- try_emit<primary_expression_node::literal >(n.expr);
-
- if (n.expr.index() == primary_expression_node::declaration)
- {
- // This must be an anonymous declaration
- auto& decl = std::get<primary_expression_node::declaration>(n.expr);
- assert(
- decl
- && !decl->identifier
- );
-
- // Handle an anonymous function
- if (decl->is_function()) {
- auto lambda_intro = build_capture_lambda_intro_for(decl->captures, n.position());
-
- // Handle an anonymous generic function with explicit type list
- if (decl->template_parameters) {
- print_to_string(&lambda_intro, *decl->template_parameters, false, true);
- }
-
- emit(*decl, lambda_intro);
- }
- // Else an anonymous object as 'typeid { initializer }'
- else {
- assert(decl->is_object());
- auto& type_id = std::get<declaration_node::an_object>(decl->type);
-
- printer.add_pad_in_this_line( -5 );
-
- emit(*type_id);
- printer.print_cpp2("{", decl->position());
-
- if (!decl->initializer) {
- errors.emplace_back(
- decl->position(),
- "an anonymous object declaration must have '=' and an initializer"
- );
- return;
- }
-
- emit(*decl->initializer, false);
-
- printer.print_cpp2("}", decl->position());
- }
- }
- }
-
- // Not yet implemented. TODO: finalize deducing pointer types from parameter lists
- auto is_pointer_declaration(
- parameter_declaration_list_node const*,
- int,
- int
- )
- -> bool
- {
- return false;
- }
-
- auto is_pointer_declaration(
- declaration_node const* decl_node,
- int deref_cnt,
- int addr_cnt
- )
- -> bool
- {
- if (!decl_node) {
- return false;
- }
- if (addr_cnt > deref_cnt) {
- return true;
- }
-
- return std::visit([&](auto const& type){
- return is_pointer_declaration(type.get(), deref_cnt, addr_cnt);
- }, decl_node->type);
- }
-
- auto is_pointer_declaration(
- function_type_node const* fun_node,
- int deref_cnt,
- int addr_cnt
- )
- -> bool
- {
- if (!fun_node) {
- return false;
- }
- if (addr_cnt > deref_cnt) {
- return true;
- }
-
- return std::visit([&]<typename T>(T const& type){
- if constexpr (std::is_same_v<T, std::monostate>) {
- return false;
- }
- else if constexpr (std::is_same_v<T, function_type_node::single_type_id>) {
- return is_pointer_declaration(type.type.get(), deref_cnt, addr_cnt);
- }
- else {
- return is_pointer_declaration(type.get(), deref_cnt, addr_cnt);
- }
- }, fun_node->returns);
- }
-
- auto is_pointer_declaration(
- type_id_node const* type_id_node,
- int deref_cnt,
- int addr_cnt
- )
- -> bool
- {
- if (!type_id_node) {
- return false;
- }
- if (addr_cnt > deref_cnt) {
- return true;
- }
-
- if ( type_id_node->dereference_of ) {
- return is_pointer_declaration(type_id_node->dereference_of, deref_cnt + type_id_node->dereference_cnt, addr_cnt);
- } else if ( type_id_node->address_of ) {
- return is_pointer_declaration(type_id_node->address_of, deref_cnt, addr_cnt + 1);
- }
-
- int pointer_declarators_cnt = std::count_if(std::cbegin(type_id_node->pc_qualifiers), std::cend(type_id_node->pc_qualifiers), [](auto* q) {
- return q->type() == lexeme::Multiply;
- });
-
- if (
- pointer_declarators_cnt == 0
- && type_id_node->suspicious_initialization
- )
- {
- return is_pointer_declaration(type_id_node->suspicious_initialization, deref_cnt, addr_cnt);
- }
-
- return (pointer_declarators_cnt + addr_cnt - deref_cnt) > 0;
- }
-
- auto is_pointer_declaration(
- type_node const*,
- int,
- int
- )
- -> bool
- {
- return false;
- }
-
- auto is_pointer_declaration(
- namespace_node const*,
- int,
- int
- )
- -> bool
- {
- return false;
- }
-
- auto is_pointer_declaration(
- alias_node const*,
- int,
- int
- )
- -> bool
- {
- return false;
- }
-
- auto is_pointer_declaration(
- declaration_sym const* decl,
- int deref_cnt,
- int addr_cnt
- )
- -> bool
- {
- if (!decl) {
- return false;
- }
- if (addr_cnt > deref_cnt) {
- return true;
- }
- return is_pointer_declaration(decl->declaration, deref_cnt, addr_cnt);
- }
-
- auto is_pointer_declaration(
- token const* t,
- int deref_cnt = 0,
- int addr_cnt = 0
- )
- -> bool
- {
- if (!t) {
- return false;
- }
- if (addr_cnt > deref_cnt) {
- return true;
- }
- auto decl = sema.get_declaration_of(*t, true);
- return is_pointer_declaration(decl, deref_cnt, addr_cnt);
- }
-
-
- auto source_order_name_lookup(unqualified_id_node const& id)
- -> source_order_name_lookup_res
- {
- for (
- auto first = current_names.rbegin(), last = current_names.rend() - 1;
- first != last;
- ++first
- )
- {
- if (
- auto decl = get_if<declaration_node const*>(&*first);
- decl
- && *decl
- && (*decl)->has_name(*id.identifier)
- )
- {
- return *decl;
- }
- else if (
- auto using_ = get_if<active_using_declaration>(&*first);
- using_
- && using_->identifier
- && *using_->identifier == *id.identifier
- )
- {
- return *using_;
- }
- }
-
- return {};
- }
-
-
- auto lookup_finds_variable_with_placeholder_type_under_initialization(id_expression_node const& n)
- -> bool
- {
- if (!n.is_unqualified())
- {
- return false;
- }
-
- auto const& id = *get<id_expression_node::unqualified>(n.id);
- auto lookup = source_order_name_lookup(id);
-
- if (
- !lookup
- || get_if<active_using_declaration>(&*lookup)
- )
- {
- return false;
- }
-
- auto decl = get<declaration_node const*>(*lookup);
- if (
- decl
- && decl->has_name(*id.identifier)
- )
- {
- if (
- !decl->is_object()
- && !decl->is_object_alias()
- )
- {
- return false;
- }
-
- if (decl->is_object()) {
- auto type = &**get_if<declaration_node::an_object>(&decl->type);
- return type->is_wildcard()
- && contains(current_declarations, decl);
- }
- auto const& type = (**get_if<declaration_node::an_alias>(&decl->type)).type_id;
- return (
- !type
- || type->is_wildcard()
- )
- && contains(current_declarations, decl);
- }
-
- return false;
- }
-
- //-----------------------------------------------------------------------
- //
- auto emit(
- // Note: parameter is not const as we'll fill in the capture .str info
- postfix_expression_node& n,
- bool for_lambda_capture = false
- )
- -> void
- { STACKINSTR
- if (!sema.check(n)) {
- return;
- }
-
- assert(n.expr);
- last_postfix_expr_was_pointer = false;
-
- // For a 'move that' parameter, track the members we already moved from
- // so we can diagnose attempts to move from the same member twice
- if (
- emitting_move_that_function
- && n.expr->get_token()
- && *n.expr->get_token() == "that"
- )
- {
- if (n.ops.empty()) {
- if (!already_moved_that_members.empty()) {
- errors.emplace_back(
- n.position(),
- "attempting to move from whole 'that' object after a 'that.member' was already moved from"
- );
- return;
- }
- // push a sentinel for "all members"
- already_moved_that_members.push_back(nullptr);
- }
- else {
- auto member = n.ops[0].id_expr->get_token();
- assert(member);
-
- for (
- auto i = already_moved_that_members.begin();
- i != already_moved_that_members.end();
- ++i
- )
- {
- if (
- !*i
- || **i == *member
- )
- {
- errors.emplace_back(
- n.position(),
- "attempting to move twice from 'that." + member->to_string() + "'"
- );
- return;
- }
- }
-
- already_moved_that_members.push_back(member);
- }
- }
-
- // Ensure that forwarding postfix-expressions start with a forwarded parameter name
- //
- assert (!current_args.empty());
- if (current_args.back().pass == passing_style::forward)
- {
- assert (n.expr->get_token());
- assert (!current_args.back().ptoken);
- current_args.back().ptoken = n.expr->get_token();
- auto decl = sema.get_declaration_of(*current_args.back().ptoken);
- if (!(decl && decl->parameter && decl->parameter->pass == passing_style::forward))
- {
- errors.emplace_back(
- n.position(),
- n.expr->get_token()->to_string() + " is not a forwarding parameter name"
- );
- }
- }
-
- // Check that this isn't pointer arithmentic
- // (initial partial implementation)
- if (n.expr->expr.index() == primary_expression_node::id_expression)
- {
- auto& id = std::get<primary_expression_node::id_expression>(n.expr->expr);
- assert(id);
- if (id->id.index() == id_expression_node::unqualified)
- {
- auto& unqual = std::get<id_expression_node::unqualified>(id->id);
- assert(unqual);
- // TODO: Generalize this:
- // - we don't recognize pointer types from Cpp1
- // - we don't deduce pointer types from parameter_declaration_list_node
- if ( is_pointer_declaration(unqual->identifier) ) {
- if (n.ops.empty()) {
- last_postfix_expr_was_pointer = true;
- }
- else
- {
- auto op = [&]{
- if (
- n.ops.size() >= 2
- && n.ops[0].op->type() == lexeme::LeftParen
- )
- {
- return n.ops[1].op;
- }
- else
- {
- return n.ops.front().op;
- }
- }();
-
- if (
- op->type() == lexeme::PlusPlus
- || op->type() == lexeme::MinusMinus
- || op->type() == lexeme::LeftBracket
- )
- {
- errors.emplace_back(
- op->position(),
- op->to_string() + " - pointer arithmetic is illegal - use std::span or gsl::span instead"
- );
- violates_bounds_safety = true;
- }
- else if (
- op->type() == lexeme::Tilde
- )
- {
- errors.emplace_back(
- op->position(),
- op->to_string() + " - pointer bitwise manipulation is illegal - use std::bit_cast to convert to raw bytes first"
- );
- }
- }
- }
- }
- }
-
- // Simple case: If there are no .ops, just emit the expression
- if (n.ops.empty()) {
- emit(*n.expr);
- return;
- }
-
- // Check to see if it's a capture expression that contains $,
- // and if we're not capturing the expression for the lambda
- // introducer replace it with the capture name
- auto captured_part = std::string{};
- if (
- n.cap_grp
- && !for_lambda_capture
- )
- {
- // First stringize ourselves so that we compare equal against
- // the first *cap_grp .str_suppressed_move that matches us (which is what the
- // lambda introducer generator used to create a lambda capture)
- suppress_move_from_last_use = true;
- auto my_sym = print_to_string(n, true);
- suppress_move_from_last_use = false;
-
- auto found = std::find_if(n.cap_grp->members.cbegin(), n.cap_grp->members.cend(), [my_sym](auto& cap) {
- return cap.str_suppressed_move == my_sym;
- });
-
- assert(
- found != n.cap_grp->members.cend()
- && "ICE: could not find this postfix-expression in capture group"
- );
- // And then emit that capture symbol with number
- assert (!found->cap_sym.empty());
- captured_part += found->cap_sym;
- }
-
- // Otherwise, we're going to have to potentially do some work to change
- // some Cpp2 postfix operators to Cpp1 prefix operators, so let's set up...
- auto prefix = std::vector<text_with_pos>{};
- auto suffix = std::vector<text_with_pos>{};
-
- auto last_was_prefixed = false;
- auto saw_dollar = false;
-
- struct text_chunks_with_parens_position {
- std::vector<text_with_pos> text_chunks;
- source_position open_pos;
- source_position close_pos;
- };
-
- auto args = std::optional<text_chunks_with_parens_position>{};
-
- auto flush_args = [&] {
- if (args) {
- suffix.emplace_back(")", args.value().close_pos);
- for (auto&& e: args.value().text_chunks) {
- suffix.push_back(e);
- }
- suffix.emplace_back("(", args.value().open_pos);
- args.reset();
- }
- };
-
- auto print_to_text_chunks = [&](auto& i, auto... more) {
- auto text = std::vector<text_with_pos>{};
- printer.emit_to_text_chunks(&text);
- push_need_expression_list_parens(false);
- emit(i, more...);
- pop_need_expression_list_parens();
- printer.emit_to_text_chunks();
- return text;
- };
-
- for (auto i = n.ops.rbegin(); i != n.ops.rend(); ++i)
- {
- assert(i->op);
-
- // If we already captured a part as a _## lambda capture,
- // skip the part of this expression before the $ symbol
- //
- if (!captured_part.empty()) {
- if (i->op->type() == lexeme::Dollar) {
- break;
- }
- }
- // Else skip the part of this expression after the $ symbol
- else if (for_lambda_capture) {
- if (i->op->type() == lexeme::Dollar) {
- saw_dollar = true;
- continue;
- }
- if (!saw_dollar) {
- continue;
- }
- }
-
- // Going backwards if we found LeftParen it might be UFCS
- // expr_list is emitted to 'args' for future use
- if (i->op->type() == lexeme::LeftParen) {
-
- assert(i->op);
- assert(i->op_close);
- auto local_args = text_chunks_with_parens_position{{}, i->op->position(), i->op_close->position()};
-
- assert (i->expr_list);
- if (!i->expr_list->expressions.empty()) {
- local_args.text_chunks = print_to_text_chunks(*i->expr_list);
- }
-
- flush_args();
- args.emplace(std::move(local_args));
- }
- // Going backwards if we found Dot and there is args variable
- // it means that it should be handled by UFCS
- else if(
- i->op->type() == lexeme::Dot
- && args
- // Disable UFCS if name lookup would hard-error (#550).
- // That happens when it finds that the function identifier being called is the name
- // of a variable with deduced type and we are in its initializer (e.g., x := y.x();)
- // So lower it to a member call instead, the only possible valid meaning.
- && !lookup_finds_variable_with_placeholder_type_under_initialization(*i->id_expr)
- )
- {
- // The function name is the argument to the macro
- auto funcname = print_to_string(*i->id_expr);
-
- // First, build the UFCS macro name
-
- auto ufcs_string = std::string("CPP2_UFCS");
-
- // If there are template arguments, use the _TEMPLATE version
- if (std::ssize(i->id_expr->template_arguments()) > 0) {
- // If it is qualified, use the _QUALIFIED version
- if (i->id_expr->is_qualified()) {
- ufcs_string += "_QUALIFIED";
- // And split the unqualified id in the function name as two macro arguments
- auto& id = *get<id_expression_node::qualified>(i->id_expr->id);
- funcname =
- "("
- + print_to_string(id, false)
- + "::),"
- + print_to_string(*cpp2::assert_not_null(id.ids.back().id), false, true, true);
- }
- ufcs_string += "_TEMPLATE";
- }
-
- // If we're in an object declaration (i.e., initializer)
- // at namespace scope, use the _NONLOCAL version
- //
- // Note: If there are other cases where code could execute
- // in a non-local scope where a capture-default for the UFCS
- // lambda would not be allowed, then add them here
- if (
- current_declarations.back()->is_namespace()
- || (
- current_declarations.back()->is_object()
- && current_declarations.back()->parent_is_namespace()
- )
- || (
- (
- current_declarations.back()->is_alias()
- || (
- current_declarations.back()->is_function()
- && current_declarations.back() == having_signature_emitted
- )
- )
- && (
- current_declarations.back()->parent_is_namespace()
- || current_declarations.back()->parent_is_type()
- )
- )
- )
- {
- ufcs_string += "_NONLOCAL";
- }
-
- // Second, emit the UFCS argument list
-
- prefix.emplace_back(ufcs_string + "(" + funcname + ")(", args.value().open_pos );
- suffix.emplace_back(")", args.value().close_pos );
- if (!args.value().text_chunks.empty()) {
- for (auto&& e: args.value().text_chunks) {
- suffix.push_back(e);
- }
- suffix.emplace_back(", ", i->op->position());
- }
- args.reset();
- }
-
- // Handle the Cpp2 postfix operators that are prefix in Cpp1
- //
- else if (
- i->op->type() == lexeme::MinusMinus
- || i->op->type() == lexeme::PlusPlus
- || i->op->type() == lexeme::Multiply
- || i->op->type() == lexeme::Ampersand
- || i->op->type() == lexeme::Tilde
- )
- {
- // omit some needless parens
- if (
- !last_was_prefixed
- && i != n.ops.rbegin()
- )
- {
- prefix.emplace_back( "(", i->op->position() );
- }
- prefix.emplace_back( i->op->to_string(), i->op->position());
-
- // Enable null dereference checks
- if (
- flag_safe_null_pointers
- && i->op->type() == lexeme::Multiply
- )
- {
- prefix.emplace_back( "cpp2::assert_not_null(", i->op->position() );
- }
- if (
- flag_safe_null_pointers
- && i->op->type() == lexeme::Multiply
- )
- {
- suffix.emplace_back( ")", i->op->position() );
- }
-
- // omit some needless parens
- if (
- !last_was_prefixed
- && i != n.ops.rbegin()
- )
- {
- suffix.emplace_back( ")", i->op->position() );
- }
- last_was_prefixed = true;
- }
-
- // Handle the other Cpp2 postfix operators that stay postfix in Cpp1 (currently: '...')
- else if (is_postfix_operator(i->op->type())) {
- flush_args();
- suffix.emplace_back( i->op->to_string(), i->op->position());
- }
-
- // Handle the suffix operators that remain suffix
- //
- else {
- assert(i->op);
- last_was_prefixed = false;
-
- // Enable subscript bounds checks
- if (
- flag_safe_subscripts
- && i->op->type() == lexeme::LeftBracket
- && std::ssize(i->expr_list->expressions) == 1
- )
- {
- suffix.emplace_back( ")", i->op->position() );
- }
- else if (i->op_close) {
- suffix.emplace_back( i->op_close->to_string(), i->op_close->position() );
- }
-
- if (i->id_expr)
- {
- if (args) {
- // If args are stored it means that this is function or method
- // that is not handled by UFCS and args need to be printed
- suffix.emplace_back(")", args.value().close_pos);
- for (auto&& e: args.value().text_chunks) {
- suffix.push_back(e);
- }
- suffix.emplace_back("(", args.value().open_pos);
- args.reset();
- }
-
- auto print = print_to_string(*i->id_expr, false /*not a local name*/);
- suffix.emplace_back( print, i->id_expr->position() );
- }
-
- if (i->expr_list) {
- auto text = print_to_text_chunks(*i->expr_list);
- for (auto&& e: text) {
- suffix.push_back(e);
- }
- }
-
- // Enable subscript bounds checks
- if (
- flag_safe_subscripts
- && i->op->type() == lexeme::LeftBracket
- && std::ssize(i->expr_list->expressions) == 1
- )
- {
- prefix.emplace_back( "CPP2_ASSERT_IN_BOUNDS(", i->op->position() );
- suffix.emplace_back( ", ", i->op->position() );
- }
- else {
- suffix.emplace_back( i->op->to_string(), i->op->position() );
- }
- }
- }
-
- // Print the prefixes (in forward order)
- for (auto& e : prefix) {
- printer.print_cpp2(e.text, n.position());
- }
-
- // If this is an --, ++, or &, don't add std::move on the lhs
- // even if this is a definite last use (only do that when an rvalue is okay)
- if (
- n.ops.front().op->type() == lexeme::MinusMinus
- || n.ops.front().op->type() == lexeme::PlusPlus
- || n.ops.front().op->type() == lexeme::Ampersand
- )
- {
- suppress_move_from_last_use = true;
- }
-
- // Now print the core expression -- or the captured_part in its place
- if (captured_part.empty()) {
- emit(*n.expr);
- }
- else {
- printer.print_cpp2(captured_part, n.position());
- }
- suppress_move_from_last_use = false;
-
- flush_args();
-
- // Print the suffixes (in reverse order)
- while (!suffix.empty()) {
- printer.print_cpp2(suffix.back().text, suffix.back().pos);
- suffix.pop_back();
- }
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(prefix_expression_node const& n)
- -> void
- { STACKINSTR
- auto suffix = std::string{};
- for (auto const& x : n.ops) {
- assert(x);
- if (x->type() == lexeme::Not) {
- printer.print_cpp2("!(", n.position());
- printer.add_pad_in_this_line(-3);
- suffix += ")";
- }
- else {
- printer.print_cpp2(*x, x->position());
- }
- }
- assert(n.expr);
- push_need_expression_list_parens(true);
- emit(*n.expr);
- pop_need_expression_list_parens();
- printer.print_cpp2(suffix, n.position());
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(is_as_expression_node const& n)
- -> void
- { STACKINSTR
- std::string prefix = {};
- std::string suffix = {};
-
- auto wildcard_found = false;
- bool as_on_literal = false;
-
- assert(
- n.expr
- && n.expr->get_postfix_expression_node()
- && n.expr->get_postfix_expression_node()->expr
- );
- {
- auto& p = n.expr->get_postfix_expression_node()->expr;
- if (auto t = p->get_token();
- t
- && is_literal(t->type())
- && t->type() != lexeme::StringLiteral
- && t->type() != lexeme::FloatLiteral
- && !std::get<primary_expression_node::literal>(p->expr)->user_defined_suffix
- && std::ssize(n.ops) > 0
- && *n.ops[0].op == "as"
- )
- {
- as_on_literal = true;
- }
- }
-
- for (
- auto i = n.ops.rbegin();
- i != n.ops.rend();
- ++i
- )
- {
- // If it's "ISORAS type", emit "cpp2::ISORAS<type>(expr)"
- if (i->type)
- {
- if (i->type->is_wildcard()) {
- wildcard_found = true;
- if (*i->op != "is") {
- errors.emplace_back(
- n.position(),
- "'as _' wildcard is not allowed, specify a concrete target type instead"
- );
- }
- else if (std::ssize(n.ops) > 1) {
- errors.emplace_back(
- n.position(),
- "an 'is _' wildcard may only be used on its own, not in a chain with other 'is'/'as' in the same subexpression"
- );
- }
- }
- else {
- auto op_name = i->op->to_string();
- if (op_name == "as") {
- op_name = "as_"; // use the static_assert-checked 'as' by default...
- } // we'll override this inside inspect-expressions
- prefix += "cpp2::" + op_name + "<" + print_to_string(*i->type) + ">(";
- suffix = ")" + suffix;
- }
- }
- // Else it's "is value", emit "cpp2::is(expr, value)"
- else
- {
- assert(i->expr);
- prefix += "cpp2::" + i->op->to_string() + "(";
- 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<Name,Term> const& n)
- -> void
- { STACKINSTR
- 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:
- printer.print_cpp2( " ", n.position() );
- emit(op);
- printer.print_cpp2( " ", n.position() );
- 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 </<=, all >/>=, or all ==
- auto found_lt = 0; // < and <=
- auto found_gt = 0; // > 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 <=, all > 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;
- }
- // If it's "_ =" then emit static_cast<void>()
- bool emit_discard = false;
- if (
- !n.terms.empty()
- && n.terms.front().op->type() == lexeme::Assignment
- && n.expr->get_postfix_expression_node()
- && n.expr->get_postfix_expression_node()->get_first_token_ignoring_this()
- && *n.expr->get_postfix_expression_node()->get_first_token_ignoring_this() == "_"
- )
- {
- printer.print_cpp2( "static_cast<void>(", n.position() );
- emit_discard = true;
- }
- else
- {
- 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((rhs_tok->as_string_view())[0])
- )
- )
- {
- errors.emplace_back(
- n.terms.front().op->position(),
- n.terms.front().op->to_string() + " - 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() + " - pointer arithmetic is illegal - use std::span or gsl::span instead"
- );
- violates_bounds_safety = true;
- }
- }
-
- auto first = 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
- {
- // For the first operator only, if we are emitting a "_ =" discard
- // then we don't need the =
- if (
- !emit_discard
- || !first
- ) {
- printer.print_cpp2(" ", n.position());
- emit(*x.op);
- printer.print_cpp2(" ", n.position());
- }
-
- // When assigning a single expression-list, we can
- // take over direct control of emitting it without needing to
- // go through the whole grammar, and surround it with braces
- if (
- x.op->type() == lexeme::Assignment
- && x.expr->is_expression_list()
- )
- {
- printer.print_cpp2( "{ ", n.position() );
- emit(*x.expr->get_expression_list(), false);
- printer.print_cpp2( " }", n.position() );
- }
- // Otherwise, just emit the general expression as usual
- else {
- emit(*x.expr);
- }
- }
-
- first = false;
- }
- // Finish emitting the "_ =" discard.
- if (emit_discard) {
- printer.print_cpp2( ")", n.position() );
- }
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(expression_node const& n)
- -> void
- { STACKINSTR
- assert(n.expr);
- push_need_expression_list_parens(true);
- emit(*n.expr);
- pop_need_expression_list_parens();
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(
- expression_list_node const& n,
- bool parens_ok = true
- )
- -> void
- { STACKINSTR
- auto add_parens =
- should_add_expression_list_parens()
- && !n.inside_initializer
- && parens_ok
- ;
- add_parens |=
- n.is_fold_expression() &&
- !(n.inside_initializer && current_declarations.back()->initializer->position() != n.open_paren->position())
- ;
- 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 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("cpp2::out(&", n.position());
- }
- else if (x.pass == passing_style::move) {
- printer.print_cpp2("std::move(", n.position());
- }
- }
-
- if (is_out) {
- in_non_rvalue_context.push_back(true);
- }
-
- assert(x.expr);
- current_args.push_back( {x.pass} );
- emit(*x.expr);
- current_args.pop_back();
-
- if (is_out) {
- in_non_rvalue_context.pop_back();
- }
-
- if (
- x.pass == passing_style::move
- || x.pass == passing_style::out
- )
- {
- 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,
- function_prolog const& function_prolog = {},
- std::vector<std::string> const& function_epilog = {},
- bool emitted = false
- )
- -> void
- { STACKINSTR
- assert(n.expr);
- auto generating_return = false;
-
- if (function_body_start != source_position{}) {
- emit_prolog_mem_inits(function_prolog, n.position().colno);
- printer.print_cpp2(" { ", function_body_start);
- emit_prolog_statements(function_prolog, n.position().colno);
- if (!function_void_ret) {
- printer.print_cpp2("return ", n.position());
- generating_return = true;
- }
- }
-
- if (!emitted) {
- // When generating 'return' of a single expression-list, we can
- // take over direct control of emitting it without needing to
- // go through the whole grammar, and surround it with braces
- if (
- generating_return
- && n.expr->is_expression_list()
- && !n.expr->get_expression_list()->is_fold_expression()
- )
- {
- auto is_deduced_return =
- !function_returns.empty()
- && function_returns.back().is_deduced;
-
- if (!is_deduced_return) {
- printer.print_cpp2( "{ ", n.position() );
- }
- emit(*n.expr->get_expression_list(), false);
- if (!is_deduced_return) {
- printer.print_cpp2( " }", n.position() );
- }
- }
- // Otherwise, just emit the general expression as usual
- else {
- emit(*n.expr);
- }
- if (can_have_semicolon) {
- printer.print_cpp2(";", n.position());
- }
- }
-
- if (function_body_start != source_position{}) {
- emit_epilog_statements( function_epilog, n.position().colno);
- printer.print_cpp2(" }", n.position());
- }
- }
-
-
- // Consider moving these `stack` functions to `common.h` to enable more general use.
-
- template<typename T>
- auto stack_value(
- T& var,
- std::type_identity_t<T> const& value
- )
- -> auto
- {
- return finally([&var, old = std::exchange(var, value)]() {
- var = old;
- });
- }
-
- template<typename T>
- auto stack_element(
- std::vector<T>& cont,
- std::type_identity_t<T> const& value
- )
- -> auto
- {
- cont.push_back(value);
- return finally([&]{ cont.pop_back(); });
- }
-
- template<typename T>
- auto stack_size(std::vector<T>& cont)
- -> auto
- {
- return finally([&, size = cont.size()]{ cont.resize(size); });
- }
-
- template<typename T>
- auto stack_size_if(
- std::vector<T>& cont,
- bool cond
- )
- -> std::optional<decltype(stack_size(cont))>
- {
- if (cond) {
- return stack_size(cont);
- }
- return {};
- }
-
- //-----------------------------------------------------------------------
- //
- auto emit(
- statement_node const& n,
- bool can_have_semicolon = true,
- source_position function_body_start = {},
- bool function_void_ret = false,
- function_prolog const& function_prolog = {},
- std::vector<std::string> const& function_epilog = {}
- )
- -> void
- { STACKINSTR
- if (!sema.check(n)) {
- return;
- }
-
- auto emit_parameters =
- !n.emitted
- && n.parameters
- ;
-
- auto guard = stack_size_if(current_names, emit_parameters);
- if (emit_parameters) {
- printer.print_extra( "\n");
- printer.print_extra( "{");
- for (auto& param : n.parameters->parameters) {
- printer.print_extra( "\n");
- printer.print_extra( print_to_string(*param) );
- }
- }
-
- // Do expression statement case first... it's the most complex
- // because it's used for single-statement function bodies
- try_emit<statement_node::expression >(
- n.statement,
- can_have_semicolon,
- function_body_start,
- function_void_ret,
- function_prolog,
- function_epilog,
- n.emitted
- );
-
- // Otherwise, skip this one if it was already handled earlier (i.e., a constructor member init)
- if (n.emitted) {
- return;
- }
-
- printer.disable_indent_heuristic_for_next_text();
-
- try_emit<statement_node::compound >(n.statement, function_prolog, function_epilog);
-
- // 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<statement_node::selection >(n.statement);
- try_emit<statement_node::declaration>(n.statement);
- try_emit<statement_node::return_ >(n.statement);
- try_emit<statement_node::iteration >(n.statement);
- try_emit<statement_node::using_ >(n.statement);
- try_emit<statement_node::contract >(n.statement);
- try_emit<statement_node::inspect >(n.statement, false);
- try_emit<statement_node::jump >(n.statement);
-
- printer.preempt_position_pop();
-
- if (emit_parameters) {
- printer.print_extra( "\n");
- printer.print_extra( "}");
- }
- }
-
-
- //-----------------------------------------------------------------------
- // Within a type scope implementation, disallow declaring a name that
- // is the same as (i.e., shadows) a type scope name... this is a
- // convenient place to check because we have the decls stack
- //
- auto check_shadowing_of_type_scope_names(
- declaration_node const& decl
- )
- -> bool
- {
- if (
- decl.has_name() // this is a named declaration
- && !decl.has_name("this") // that's not 'this'
- && !decl.parent_is_type() // and the type isn't the direct parent
- && is_name_declared_in_current_type_scope(*decl.name())
- ) // and it shadows a name
- {
- errors.emplace_back(
- decl.position(),
- "a type's implementation may not declare a name that is the same as (i.e., shadows) a type scope name - for example, a type scope function's local variable may not have the same as one of the type's members"
- );
- return false;
- }
-
- return true;
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(
- parameter_declaration_node const& n,
- bool is_returns = false,
- bool is_template_parameter = false
- )
- -> void
- { STACKINSTR
- if (!sema.check(n)) {
- return;
- }
-
- // Can't declare functions as parameters -- only pointers to functions which are objects
- assert( n.declaration );
- assert( !n.declaration->is_function() );
-
- if (!check_shadowing_of_type_scope_names(*n.declaration)) {
- return;
- }
-
- assert( n.declaration->identifier );
- auto identifier = print_to_string( *n.declaration->identifier );
- auto identifier_pos = n.position();
-
- if (n.mod == parameter_declaration_node::modifier::implicit)
- {
- assert(!current_functions.empty());
- if (
- n.pass != passing_style::out
- || !current_functions.back().decl->has_name("operator=")
- )
- {
- errors.emplace_back(
- n.position(),
- "only an 'out this' parameter of an 'operator=' function may be declared implicit"
- );
- }
- }
-
- current_names.push_back(&*n.declaration);
-
- //-----------------------------------------------------------------------
- // Skip 'this' parameters
-
- if (n.declaration->has_name("this"))
- {
- // Since we're skipping "out this," plus possibly "implicit " and
- // whitespace, any following parameters on the same line can shift left
- printer.add_pad_in_this_line(-18);
-
- return;
- }
-
- //-----------------------------------------------------------------------
- // Handle 'that' parameters
-
- if (n.declaration->has_name("that"))
- {
- emitting_that_function = true;
- assert(
- n.pass == passing_style::in
- || n.pass == passing_style::move
- );
- auto pass = std::string{" const&"};
- if (
- n.pass == passing_style::move
- || emitting_move_that_function
- )
- {
- pass = "&&";
- }
-
- auto func_name = get_enclosing_function_name();
- assert(func_name);
-
- auto type_name = get_enclosing_type_name();
- assert(type_name);
-
- // If we're in an empty type that has no member object, mark 'that' as
- // [[maybe_unused]] to silence Cpp1 compiler warnings
- assert(!current_functions.empty());
- auto maybe_unused = std::string{};
- if (current_functions.back().decl->get_parent()->get_type_scope_declarations(declaration_node::objects).empty()) {
- maybe_unused = "[[maybe_unused]] ";
- }
-
- printer.print_cpp2(
- maybe_unused + print_to_string( *type_name ) + pass + " that",
- n.position()
- );
- return;
- }
-
- //-----------------------------------------------------------------------
- // Handle type parameters
-
- if (n.declaration->is_type()) {
- assert( is_template_parameter );
- printer.print_cpp2("typename ", identifier_pos);
- if (n.declaration->is_variadic) {
- printer.print_cpp2(
- "...",
- identifier_pos
- );
- }
-
- if (identifier == "_") {
- printer.print_cpp2( "UnnamedTypeParam" + std::to_string(n.ordinal), identifier_pos );
- }
- else {
- printer.print_cpp2( identifier, identifier_pos );
- }
-
- return;
- }
-
- //-----------------------------------------------------------------------
- // Else handle template non-type parameters
-
- assert( n.declaration->is_object() );
- auto const& type_id = *std::get<declaration_node::an_object>(n.declaration->type);
-
- if (is_template_parameter) {
- emit( type_id );
- printer.print_cpp2(" ", type_id.position());
- printer.print_cpp2( identifier, identifier_pos );
- return;
- }
-
- //-----------------------------------------------------------------------
- // Else handle ordinary parameters
-
- auto param_type = print_to_string(type_id);
-
- // If there are template parameters on this function or its enclosing
- // type, see if this parameter's name is an unqualified-id with a
- // template parameter name, or mentions a template parameter as a
- // template argument
- auto has_template_parameter_type_named = [](
- declaration_node const& decl,
- std::string_view name
- )
- -> bool
- {
- if (decl.template_parameters) {
- for (auto& tparam : decl.template_parameters->parameters)
- {
- assert(
- tparam
- && tparam->name()
- );
- // For now just do a quick string match
- auto tparam_name = tparam->name()->to_string();
- if (
- tparam->declaration->is_type()
- && (
- name == tparam_name
- || name.find("<"+tparam_name) != std::string_view::npos
- || name.find(","+tparam_name) != std::string_view::npos
- )
- )
- {
- return true;
- }
- }
- }
- return false;
- };
-
- assert( current_declarations.back() );
- auto is_dependent_parameter_type =
- has_template_parameter_type_named( *current_declarations.back(), param_type )
- || (
- current_declarations.back()->parent_is_type()
- && current_declarations.back()->has_name("operator=")
- && has_template_parameter_type_named( *current_declarations.back()->get_parent(), param_type)
- )
- ;
-
- // First any prefix
-
- if (identifier == "_") {
- printer.print_cpp2( "[[maybe_unused]] ", identifier_pos );
- identifier = "unnamed_param_" + std::to_string(n.ordinal);
- }
-
- if (
- !is_returns
- && !n.declaration->is_variadic
- && !type_id.is_wildcard()
- && !is_dependent_parameter_type
- && !type_id.is_pointer_qualified()
- )
- {
- switch (n.pass) {
- break;case passing_style::in : printer.print_cpp2( "cpp2::in<", n.position() );
- break;case passing_style::out : printer.print_cpp2( "cpp2::out<", n.position() );
- break;default: ;
- }
- }
-
- printer.preempt_position_push( n.position() );
-
- if (
- type_id.is_pointer_qualified()
- && n.pass == passing_style::in
- )
- {
- printer.print_cpp2( param_type, n.position() );
- }
- else if (
- type_id.is_wildcard()
- || is_dependent_parameter_type
- || n.declaration->is_variadic
- )
- {
- auto name = std::string{"auto"};
- if (is_dependent_parameter_type) {
- name = param_type;
- }
- else if (
- n.declaration->is_variadic
- && !type_id.is_wildcard()
- )
- {
- auto name = n.declaration->identifier->get_token();
- assert(name);
- auto req = std::string{"(std::is_convertible_v<CPP2_TYPEOF("};
- req += *name;
- req += "), ";
- req += param_type;
- req += "> && ...)";
- function_requires_conditions.push_back(req);
- }
-
- switch (n.pass) {
- break;case passing_style::in : printer.print_cpp2( name+" const&", n.position() );
- break;case passing_style::copy : printer.print_cpp2( name, n.position() );
- break;case passing_style::inout : printer.print_cpp2( name+"&", n.position() );
-
- // For generic out parameters, we take a pointer to anything with paramater named "identifier_"
- // and then generate the out<> as a stack local with the expected name "identifier"
- break;case passing_style::out : printer.print_cpp2( name, n.position() );
- current_functions.back().prolog.statements.push_back(
- "auto " + identifier + " = cpp2::out(" + identifier + "_); "
- );
- identifier += "_";
-
- break;case passing_style::move : printer.print_cpp2( name+"&&", n.position() );
- break;case passing_style::forward: printer.print_cpp2( name+"&&", n.position() );
- break;default: ;
- }
- }
- else if (n.pass == passing_style::forward) {
- printer.print_cpp2("auto", n.position());
-
- auto name = n.declaration->identifier->get_token();
- assert(name);
- auto req = std::string{"std::is_same_v<CPP2_TYPEOF("};
- req += *name;
- req += "), ";
- req += param_type;
- req += ">";
- function_requires_conditions.push_back(req);
- }
- else {
- if (is_returns) {
- printer.print_extra( param_type );
- }
- else {
- printer.print_cpp2( param_type, type_id.position() );
- }
- }
-
- printer.preempt_position_pop();
-
- // Then any suffix
-
- if (
- !is_returns
- && !type_id.is_wildcard()
- && !is_dependent_parameter_type
- && !type_id.is_pointer_qualified()
- && !n.declaration->is_variadic
- )
- {
- switch (n.pass) {
- break;case passing_style::in : printer.print_cpp2( ">", 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: ;
- }
- }
-
- if (is_returns) {
- printer.print_extra( " " + identifier );
- }
- else {
- printer.print_cpp2( " ", identifier_pos );
- if (n.declaration->is_variadic)
- {
- if (n.direction() == passing_style::out) {
- errors.emplace_back(
- n.declaration->position(),
- "a variadic parameter cannot be 'out'"
- );
- return;
- }
-
- printer.print_cpp2(
- "...",
- identifier_pos
- );
- }
- printer.print_cpp2( identifier, identifier_pos );
- }
-
- if (
- !is_returns
- && n.declaration->initializer
- )
- {
- auto guard = stack_element(current_declarations, &*n.declaration);
- printer.print_cpp2( " = ", n.declaration->initializer->position() );
- emit(*n.declaration->initializer);
- }
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(
- parameter_declaration_list_node const& n,
- bool is_returns = false,
- bool is_template_parameter = false,
- bool generating_postfix_inc_dec = false
- )
- -> void
- { STACKINSTR
- in_parameter_list = true;
-
- if (is_returns) {
- printer.print_extra( "{ " );
- }
- else {
- assert(n.open_paren);
- emit(*n.open_paren);
- }
-
- // So we don't get cute about text-aligning the first parameter when it's on a new line
- printer.disable_indent_heuristic_for_next_text();
-
- auto prev_pos = n.position();
- auto first = true;
- for (auto const& x : n.parameters) {
- if (
- !first
- && !is_returns
- )
- {
- printer.print_cpp2( ", ", prev_pos );
- }
- prev_pos = x->position();
- assert(x);
- emit(*x, is_returns, is_template_parameter);
- if (!x->declaration->has_name("this")) {
- first = false;
- }
- if (is_returns) {
- printer.print_extra( "; " );
- }
- }
-
- if (is_returns) {
- printer.print_extra( "};\n" );
- }
- else {
- // If we're generating Cpp1 postfix ++ or --, add the dummy int parameter
- if (generating_postfix_inc_dec) {
- if (!first) {
- printer.print_cpp2( ",", n.position() );
- }
- printer.print_cpp2( "int", n.position() );
- }
-
- // Position heuristic (aka hack): Avoid emitting extra whitespace before )
- // beyond column 10
- assert(n.close_paren);
- auto col = std::min( n.close_paren->position().colno, colno_t{10});
- printer.preempt_position_push({ n.close_paren->position().lineno, col});
- emit(*n.close_paren);
- printer.preempt_position_pop();
- }
-
- in_parameter_list = false;
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(
- // note: parameter is deliberately not const because we will fill
- // in the capture .str information
- contract_node& n
- )
- -> void
- { STACKINSTR
- assert (n.kind);
-
- // If this is one of Cpp2's predefined contract groups,
- // make it convenient to use without cpp2:: qualification
- auto name = std::string{"cpp2::Default"};
- if (n.group)
- {
- auto group = print_to_string(*n.group);
- if (group != "_") {
- name = group;
- }
- if (
- name == "Default"
- || name == "Bounds"
- || name == "Null"
- || name == "Type"
- || name == "Testing"
- )
- {
- name.insert(0, "cpp2::");
- }
- }
-
- // "Unevaluated" is for static analysis only, and are never evaluated, so just skip them
- // (The only requirement for an Unevaluated condition is that it parses; and even that's
- // easy to relax if we ever want to allow arbitrary tokens in an Unevaluated condition)
- if (n.group && n.group->to_string() == "Unevaluated") {
- return;
- }
-
- // For a postcondition, we'll wrap it in a lambda and register it
- //
- if (*n.kind == "post") {
- auto lambda_intro = build_capture_lambda_intro_for(n.captures, n.position(), true);
- printer.print_cpp2(
- "cpp2_finally_presuccess.add(" +
- lambda_intro + "{",
- n.position()
- );
- }
-
- // Emit the contract group name, and report any violation to that group
- //
- assert(n.condition);
- auto message = std::string{"\"\""};
- if (n.message) {
- message = "CPP2_CONTRACT_MSG(" + print_to_string(*n.message) + ")";
- }
-
- printer.print_cpp2(
- "if (" + name + ".has_handler()",
- n.position()
- );
- for (auto const& flag : n.flags) {
- printer.print_cpp2(
- " && " + print_to_string(*flag),
- n.position()
- );
- }
- printer.print_cpp2(
- " && !(" + print_to_string(*n.condition) + ") ) " +
- "{ " + name + ".report_violation(" + message + "); }",
- n.position()
- );
-
- // For a postcondition, close out the lambda
- //
- if (*n.kind == "post") {
- printer.print_cpp2( "} );", n.position()
- );
- }
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(
- function_type_node const& n,
- token const* ident,
- bool is_main = false,
- bool is_ctor_or_dtor = false,
- std::string suffix1 = {},
- bool generating_postfix_inc_dec = false
- )
- -> void
- { STACKINSTR
- if (!sema.check(n)) {
- return;
- }
-
- if (
- is_main
- && n.parameters->parameters.size() > 0
- )
- {
- printer.print_cpp2(
- "(int const argc_, char** argv_)",
- n.parameters->position()
- );
- current_functions.back().prolog.statements.push_back(
- "auto const args = cpp2::make_args(argc_, argv_); "
- );
- }
- else {
- emit(*n.parameters, false, false, generating_postfix_inc_dec);
- }
-
- // For an anonymous function, the emitted lambda is 'constexpr' or 'mutable'
- if (!n.my_decl->has_name())
- {
- if (n.my_decl->is_constexpr) {
- // The current design path we're trying out is for all '==' functions to be
- // emitted as Cpp1 'constexpr', including anonymous functions. For anonymous
- // functions that have captures, the intent is that '==' implies "the result
- // always the same (depends only on the arguments)." Specifically, the result
- // doesn't depend on the captured state, so the captured state should be const.
- // But until we want to take a dependency on post-C++20 constexpr relaxation
- // to make more code work with 'constexpr' even when not invoked in constexpr
- // contexts, we will emit it as const/whitespace instead for now.
- //
- // printer.print_cpp2( " constexpr", n.position() );
- // // consider enabling when P2242, P2280, and similar papers are widely implemented
- }
- else {
- printer.print_cpp2( " mutable", n.position() );
- }
- }
-
- // For now, adding implicit noexcept only for move/swap/dtor functions
- if (
- n.is_move()
- || n.is_swap()
- || n.is_destructor()
- || generating_move_from == n.my_decl
- )
- {
- printer.print_cpp2( " noexcept", n.position() );
- }
-
- printer.print_cpp2( suffix1, n.position() );
-
- // Handle a special member function
- if (
- n.is_assignment()
- || generating_assignment_from == n.my_decl
- )
- {
- assert(
- n.returns.index() == function_type_node::empty
- && n.my_decl->parent_declaration->name()
- );
- printer.print_cpp2(
- " -> " + print_to_string( *n.my_decl->parent_declaration->name() ) + "& ",
- n.position()
- );
- }
-
- // Otherwise, handle a default return type
- else if (n.returns.index() == function_type_node::empty)
- {
- if (is_main)
- {
- printer.print_cpp2( " -> int", n.position() );
- }
- else if(!is_ctor_or_dtor)
- {
- printer.print_cpp2( " -> void", n.position() );
- }
- }
-
- // Otherwise, handle a single anonymous return type
- else if (n.returns.index() == function_type_node::id)
- {
- auto is_type_scope_function_with_in_this =
- n.my_decl->parent_is_type()
- && n.parameters->ssize() > 0
- && (*n.parameters)[0]->direction() == passing_style::in
- ;
-
- printer.print_cpp2( " -> ", n.position() );
- auto& r = std::get<function_type_node::id>(n.returns);
- assert(r.type);
-
- auto return_type = print_to_string(*r.type);
-
- if (r.pass == passing_style::forward) {
- if (r.type->is_wildcard()) {
- printer.print_cpp2( "auto&&", n.position() );
- }
- else {
- printer.print_cpp2( return_type, n.position() );
- if (is_type_scope_function_with_in_this) {
- printer.print_cpp2( " const&", n.position() );
- }
- else if (!generating_postfix_inc_dec) {
- printer.print_cpp2( "&", n.position() );
- }
- }
- }
- else {
- printer.print_cpp2( return_type, n.position() );
- }
- }
-
- // Otherwise, handle multiple/named returns
- 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 is_name_declared_in_current_type_scope(std::string_view s)
- -> bool
- {
- if (!s.empty())
- {
- // Navigate to the enclosing type, if there is one...
- for (auto parent = current_declarations.rbegin();
- parent != current_declarations.rend();
- ++parent
- )
- {
- if (
- *parent
- && (*parent)->is_namespace()
- )
- {
- break;
- }
- // ... and here it is, so...
- if (
- *parent
- && (*parent)->is_type()
- )
- {
- // ... for each of its type scope decls...
- for (auto const& decl : (*parent)->get_type_scope_declarations())
- {
- // ... check the name
- if (decl->has_name(s))
- {
- return true;
- }
- }
- break;
- }
- }
- }
- return false;
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto get_enclosing_type_name()
- -> token const*
- {
- // Navigate to the enclosing type, if there is one...
- for (auto parent = current_declarations.rbegin();
- parent != current_declarations.rend();
- ++parent
- )
- {
- if (
- *parent
- && (*parent)->is_namespace()
- )
- {
- break;
- }
- // ... and here it is, so...
- if (
- *parent
- && (*parent)->is_type()
- )
- {
- return (*parent)->name();
- }
- }
- return {};
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto get_enclosing_function_name()
- -> token const*
- {
- // Navigate to the enclosing function, if there is one...
- for (auto parent = current_declarations.rbegin();
- parent != current_declarations.rend();
- ++parent
- )
- {
- if (
- *parent
- && (*parent)->is_namespace()
- )
- {
- break;
- }
- // ... and here it is, so...
- if (
- *parent
- && (*parent)->is_function()
- )
- {
- return (*parent)->name();
- }
- }
- return {};
- }
-
-
- //-----------------------------------------------------------------------
- // Helper to emit type-qualified names for member functions
- //
- auto type_qualification_if_any_for(
- declaration_node const& n
- )
- -> std::string
- {
- auto ret = std::string{};
-
- if (
- printer.get_phase() == printer.phase2_func_defs
- && n.parent_is_type()
-// && !n.name()->as_string_view().starts_with("operator")
- )
- {
- // If this function is inside templated type(s),
- // emit those outer template parameter lists too
- auto parent = n.parent_declaration;
- while (
- parent
- && parent->is_type()
- )
- {
- auto list = std::string{""};
- if (parent->template_parameters) {
- auto separator = std::string{"<"};
- for (auto& tparam : parent->template_parameters->parameters) {
- assert (tparam->has_name());
- list += separator + tparam->name()->to_string();
- separator = ",";
- }
- list += ">";
- }
- ret = print_to_string(*parent->identifier) + list + "::" + ret;
- parent = parent->parent_declaration;
- }
- }
-
- return ret;
- }
-
- //-----------------------------------------------------------------------
- // Constructors and assignment operators
- //
- auto emit_special_member_function(
- declaration_node const& n,
- std::string prefix
- )
- -> void
- { STACKINSTR
- assert(n.is_function());
- auto& func = std::get<declaration_node::a_function>(n.type);
- assert(func);
-
- auto is_assignment =
- generating_assignment_from == &n
- || (*func->parameters)[0]->pass == passing_style::inout;
-
- if (
- func->parameters->ssize() > 1
- && (*func->parameters)[1]->has_name("that")
- )
- {
- emitting_that_function = true;
- if (
- (*func->parameters)[1]->pass == passing_style::move
- || generating_move_from == &n
- )
- {
- emitting_move_that_function = true;
- }
- }
-
- // Do the 'out' param and member init work only in the definition phase
- if (printer.get_phase() == printer.phase2_func_defs)
- {
- auto canonize_object_name = [&]( declaration_node const* obj )
- -> std::string
- {
- assert(obj->has_name());
- auto ret = obj->name()->to_string();
- if (ret == "this") {
- ret = print_to_string( *obj->get_object_type() );
- }
- return ret;
- };
-
- // We'll use this common guidance in several errors,
- // so write it once to keep the guidance consistent
- assert (n.parent_declaration && n.parent_declaration->name());
- auto error_msg = "an operator= body must start with a series of 'member = value;' initialization statements for each of the type-scope objects in the same order they are declared, or the member must have a default initializer (in type '" + n.parent_declaration->name()->to_string() + "')";
-
- // If this constructor's type has data members, handle their initialization
- // - objects is the list of this type's declarations
- // - statements is the list of this constructor's statements
- auto objects = n.parent_declaration->get_type_scope_declarations(n.objects);
- auto statements = n.get_initializer_statements();
- auto out_inits = std::vector<std::string>{};
-
- auto object = objects.begin();
- auto statement = statements.begin();
- auto separator = std::string{": "};
-
- while (object != objects.end())
- {
- auto object_name = canonize_object_name(*object);
-
- auto is_object_before_base =
- n.get_decl_if_type_scope_object_name_before_a_base_type(*(*object)->name());
-
- auto found_explicit_init = false;
- auto found_default_init = false;
- auto stmt_pos = n.position();
-
- auto initializer = std::string{};
-
- // If we're at an assignment statement, get the lhs and rhs
- if (statement != statements.end())
- {
- assert (*statement);
- stmt_pos = (*statement)->position();
- if (stmt_pos.lineno < 0) {
- stmt_pos = n.position();
- }
-
- auto lhs = std::string{};
- auto rhs = std::string{};
- {
- auto exprs = (*statement)->get_lhs_rhs_if_simple_assignment();
- if (exprs.lhs) {
- if (auto tok = exprs.lhs->get_first_token_ignoring_this()) {
- lhs = *tok;
- }
- else {
- lhs = print_to_string( *exprs.lhs );
- }
- }
- if (exprs.rhs) {
- rhs = print_to_string( *exprs.rhs );
- }
- }
-
- // If this is an initialization of an 'out' parameter, stash it
- if (n.has_out_parameter_named(lhs)){
- out_inits.push_back( print_to_string(**statement, false) );
- (*statement)->emitted = true;
- ++statement;
- continue;
- }
-
- // Now we're ready to check whether this is an assignment to *object
-
- if (!lhs.empty())
- {
- // First, see if it's an assignment 'name = something'
- found_explicit_init = object_name == lhs;
-
- // Otherwise, see if it's 'this.name = something'
- if (!found_explicit_init)
- {
- // If it's of the form 'this.name', check 'name'
- if (
- starts_with( lhs, "(*this).")
- && object_name == lhs.substr(8)
- )
- {
- found_explicit_init = true;
- }
- }
-
- if (found_explicit_init)
- {
- initializer = rhs;
-
- // We've used this statement, so note it
- // and move 'statement' forward
- (*statement)->emitted = true;
- ++statement;
- }
- }
- }
-
- // Otherwise, use a default... for a non-copy/move that's the member initializer
- // (for which we don't need to emit anything special because it will get used),
- // and for a copy/move function we default to "= that.same_member" (or, if this
- // is a base type, to assigning from the lowered base subobject)
- if (!found_explicit_init)
- {
- if (emitting_that_function && (*object)->has_name("this"))
- {
- auto pass = std::string{" const&"};
- if (emitting_move_that_function) {
- pass = "&&";
- }
- initializer =
- "static_cast<"
- + object_name
- + pass
- + ">(that)";
- found_default_init = true;
- }
- else if (emitting_move_that_function)
- {
- initializer =
- "std::move(that)."
- + object_name;
- found_default_init = true;
- }
- else if (emitting_that_function)
- {
- initializer =
- "that."
- + object_name;
- found_default_init = true;
- }
- else if ((*object)->initializer)
- {
- initializer = print_to_string(*(*object)->initializer, false);
- found_default_init = true;
- }
- }
-
- // If this is not an assignment to *object,
- // and there was no member initializer, complain
- if (
- !found_explicit_init
- && !found_default_init
- )
- {
- errors.emplace_back(
- stmt_pos,
- "in operator=, expected '" + object_name + " = ...' initialization statement (because type scope object '" + object_name + "' does not have a default initializer)"
- );
- errors.emplace_back(
- (*object)->position(),
- "see declaration for '" + object_name + "' here"
- );
- errors.emplace_back(
- stmt_pos,
- error_msg
- );
- return;
- }
-
- assert(
- found_explicit_init
- || found_default_init
- );
-
- // Emit the initializer if it it isn't '_' (don't care) and ...
- if (initializer == "_") {
- // And on to the next data member...
- ++object;
- continue;
- }
-
- if (initializer.empty()) {
- initializer = "{}";
- }
-
- // (a) ... if this is assignment, emit it in all cases
- if (is_assignment)
- {
- assert ((*object)->name());
-
- // Flush any 'out' parameter initializations
- for (auto& init : out_inits) {
- current_functions.back().prolog.statements.push_back(init + ";");
- }
- out_inits = {};
-
- // Then add this statement
-
- // Use ::operator= for base classes
- if ((*object)->has_name("this")) {
- current_functions.back().prolog.statements.push_back(
- print_to_string( *(*object)->get_object_type() ) +
- "::operator= ( " +
- initializer +
- " );"
- );
- }
- // Else just use infix assignment
- else {
- current_functions.back().prolog.statements.push_back(
- object_name +
- " = " +
- initializer +
- ";"
- );
- }
- }
- // (b) ... if this isn't assignment, only need to emit it if it was
- // explicit, or is a base type or 'that' initializer
- else if (
- found_explicit_init
- || is_object_before_base
- || (
- (*object)->has_name("this")
- && !initializer.empty()
- )
- || emitting_that_function
- )
- {
- if (is_object_before_base) {
- assert (is_object_before_base->name());
- object_name =
- print_to_string( *is_object_before_base->parent_declaration->name() )
- + "_"
- + (*object)->name()->to_string()
- + "_as_base";
- }
-
- // Flush any 'out' parameter initializations
- auto out_inits_with_commas = [&]() -> std::string {
- auto ret = std::string{};
- for (auto& init : out_inits) {
- ret += init + ", ";
- }
- out_inits = {};
- return ret;
- }();
-
- // If there were any, wedge them into this initializer
- // using (holds nose) the comma operator and extra parens
- // as we add this statement
- if (!out_inits_with_commas.empty()) {
- current_functions.back().prolog.mem_inits.push_back(
- separator +
- object_name +
- "{(" +
- out_inits_with_commas +
- initializer +
- " )}"
- );
- }
- else {
- if (initializer == "{}") {
- initializer = "";
- }
- current_functions.back().prolog.mem_inits.push_back(
- separator +
- object_name +
- "{ " +
- initializer +
- " }"
- );
- }
- separator = ", ";
- }
-
- // And on to the next data member...
- ++object;
- }
-
- // Now no data members should be left over
- if (object != objects.end())
- {
- errors.emplace_back(
- (*object)->position(),
- canonize_object_name(*object) + " was not initialized - did you forget to write a default initializer, or assign to it in the operator= body?"
- );
- errors.emplace_back(
- (*object)->position(),
- "see declaration for '" + canonize_object_name(*object) + "' here"
- );
- errors.emplace_back(
- (*object)->position(),
- error_msg
- );
- return;
- }
-
- // Flush any possible remaining 'out' parameters
- for (auto& init : out_inits) {
- current_functions.back().prolog.statements.push_back(init + ";");
- }
- }
-
- // For a constructor, print the type name instead of the operator= function name
- assert(n.parent_is_type());
- if (!is_assignment)
- {
- printer.print_cpp2( prefix, n.position() );
- printer.print_cpp2( type_qualification_if_any_for(n), n.position() );
- printer.print_cpp2( print_to_string( *n.parent_declaration->name() ), n.position() );
- emit( *func, n.name(), false, true );
- }
- // For an assignment operator, similar to emitting an ordinary function
- else
- {
- assert (!current_functions.empty());
- current_functions.back().epilog.push_back( "return *this;");
- printer.print_cpp2( prefix, n.position() );
- printer.print_cpp2( "auto " + type_qualification_if_any_for(n) + print_to_string( *n.name() ), n.position());
- emit( *func, n.name() );
- }
- }
-
-
- //-----------------------------------------------------------------------
- //
- auto emit(
- declaration_node const& n,
- std::string const& capture_intro = {}
- )
- -> void
- { STACKINSTR
- // Helper for declarations with parent *template-head*s.
- auto const emit_parent_template_parameters = [&]() {
- auto parent_template_parameters = std::string{};
- auto parent = n.parent_declaration;
- while (
- parent
- && parent->is_type()
- )
- {
- if (parent->requires_clause_expression) {
- parent_template_parameters =
- "requires( " + print_to_string(*parent->requires_clause_expression) + " )\n"
- + parent_template_parameters;
- }
- if (parent->template_parameters) {
- parent_template_parameters =
- "template " + print_to_string( *parent->template_parameters, false, true )
- + " " + parent_template_parameters;
- }
- parent = parent->parent_declaration;
- }
- printer.print_cpp2(parent_template_parameters, n.position());
- };
-
- // Helper for declarations that can have requires-clauses
- auto const emit_requires_clause = [&]() {
- if (
- n.requires_clause_expression
- || !function_requires_conditions.empty()
- )
- {
- printer.print_extra("\n");
- printer.ignore_alignment( true, n.position().colno + 4 );
- if (printer.get_phase() == printer.phase1_type_defs_func_decls) {
- // Workaround GCC 10 not supporting requires in forward declarations in some cases.
- // See commit 5a0d77f8e297902c0b9712c5aafb6208cfa4c139.
- if (n.is_object() || n.parent_is_type()) {
- printer.print_extra("CPP2_REQUIRES_ (");
- }
- else {
- printer.print_extra("CPP2_REQUIRES (");
- }
- }
- else {
- printer.print_extra("requires (");
- }
-
- if (n.requires_clause_expression) {
- emit(*n.requires_clause_expression);
- if (!function_requires_conditions.empty()) {
- printer.print_extra(" && ");
- }
- }
-
- if (!function_requires_conditions.empty()) {
- printer.print_extra(function_requires_conditions.front());
- for (auto it = std::cbegin(function_requires_conditions)+1; it != std::cend(function_requires_conditions); ++it) {
- printer.print_extra(" && " + *it);
- }
- }
-
- printer.print_extra(") ");
- function_requires_conditions = {};
- printer.ignore_alignment( false );
- }
- };
-
-
- // Declarations are handled in multiple passes,
- // but we only want to do the sema checks once
- if (
- printer.get_phase() == printer.phase2_func_defs
- && !sema.check(n)
- )
- {
- return;
- }
-
- // In phase 0, only need to consider namespaces and types
-
- if (
- printer.get_phase() == printer.phase0_type_decls
- && !n.is_namespace()
- && !n.is_type()
- )
- {
- return;
- }
-
- // If this is a generated declaration (negative source line number),
- // add a line break before
- if (
- printer.get_phase() == printer.phase2_func_defs
- && n.position().lineno < 1
- )
- {
- printer.print_extra("\n");
- }
-
- auto guard0 = stack_value(having_signature_emitted, &n);
- auto guard1 = stack_element(current_declarations, &n);
- current_names.push_back(&n);
- auto guard2 = stack_size_if(current_names, n.is_function());
-
- // Handle aliases
-
- if (n.is_alias())
- {
- auto& a = std::get<declaration_node::an_alias>(n.type);
- assert(a);
-
- // Namespace-scope aliases are emitted in phase 1,
- // type-scope object aliases in both phases 1 and 2, and
- // function-scope aliases in phase 2
- if (
- (
- !n.parent_is_function()
- && printer.get_phase() == printer.phase1_type_defs_func_decls
- )
- ||
- (
- n.parent_is_type()
- && n.is_object_alias()
- && printer.get_phase() == printer.phase2_func_defs
- )
- ||
- (
- n.parent_is_function()
- && printer.get_phase() == printer.phase2_func_defs
- )
- )
- {
- assert(
- a->is_type_alias()
- || a->is_namespace_alias()
- || a->is_object_alias()
- );
-
- // If we're in a type scope, handle the access specifier
- if (
- n.parent_is_type()
- && printer.get_phase() == printer.phase1_type_defs_func_decls
- )
- {
- if (!n.is_default_access()) {
- printer.print_cpp2(to_string(n.access) + ": ", n.position());
- }
- else {
- printer.print_cpp2("public: ", n.position());
- }
- }
-
- // Emit template parameters if any
- if (
- a->is_object_alias()
- && n.parent_is_type()
- && printer.get_phase() == printer.phase2_func_defs
- )
- {
- emit_parent_template_parameters();
- }
-
- if (n.template_parameters) {
- printer.print_cpp2("template", n.position());
- emit(*n.template_parameters, false, true);
- printer.print_cpp2(" ", n.position());
- }
-
- // Emit requires clause if any
- emit_requires_clause();
-
- // Handle type aliases
- if (a->is_type_alias()) {
- printer.print_cpp2(
- "using "
- + print_to_string(*n.identifier)
- + " = "
- + print_to_string( *std::get<alias_node::a_type>(a->initializer) )
- + ";\n",
- n.position()
- );
- }
-
- // Handle namespace aliases
- else if (a->is_namespace_alias()) {
- printer.print_cpp2(
- "namespace "
- + print_to_string(*n.identifier)
- + " = "
- + print_to_string( *std::get<alias_node::a_namespace>(a->initializer) )
- + ";\n",
- n.position()
- );
- }
-
- // Handle object aliases:
- // - at function scope, it's const&
- // - at namespace scope, it's inline constexpr
- // - at type scope, it's also inline constexpr but see note (*) below
- else if (a->is_object_alias())
- {
- auto type = std::string{"auto"};
- if (a->type_id) {
- type = print_to_string(*a->type_id);
- }
-
- // (*) If this is at type scope, Cpp1 requires an out-of-line declaration dance
- // for some cases to work - see https://stackoverflow.com/questions/11928089/
- if (n.parent_is_type())
- {
- assert (n.parent_declaration->name());
-
- if (printer.get_phase() == printer.phase1_type_defs_func_decls) {
- printer.print_cpp2(
- "static const "
- + type + " "
- + print_to_string(*n.identifier)
- + ";\n",
- n.position()
- );
- }
- else if (printer.get_phase() == printer.phase2_func_defs) {
- // The following logic is not yet complete, so give a diagnostic for now
- if (n.parent_declaration->parent_is_type()) {
- errors.emplace_back(
- n.position(),
- "(temporary alpha limitation) an object alias cannot yet appear inside a nested type"
- );
- return;
- }
-
- printer.print_cpp2(
- "inline CPP2_CONSTEXPR "
- + type
- + " "
- + type_qualification_if_any_for(n)
- + print_to_string(*n.identifier)
- + " = "
- + print_to_string( *std::get<alias_node::an_object>(a->initializer) )
- + ";\n",
- n.position()
- );
- }
- }
- // Otherwise, at function and namespace scope we can just define
- else
- {
- auto intro = std::string{};
- if (n.parent_is_function()) {
- intro = "constexpr";
- }
- else if (n.parent_is_namespace()) {
- intro = "inline constexpr";
- }
-
- printer.print_cpp2(
- type + " "
- + intro + " "
- + print_to_string(*n.identifier)
- + " = "
- + print_to_string( *std::get<alias_node::an_object>(a->initializer) )
- + ";\n",
- n.position()
- );
- }
- }
-
- else {
- assert(!"ICE: should be unreachable - invalid alias");
- }
-
- return;
- }
- }
-
-
- // Handle other declarations
-
- auto need_to_generate_assignment = false;
- auto need_to_generate_move = false;
- auto need_to_generate_postfix_inc_dec = false;
-
- if (
- n.is_function()
- && n.has_name()
- )
- { // reset the 'that' flags
- emitting_that_function = false;
- emitting_move_that_function = false;
- already_moved_that_members = {};
- }
-
- auto is_main =
- !n.parent_declaration
- && n.has_name("main")
- ;
- auto is_in_type = n.parent_is_type();
-
- if (!check_shadowing_of_type_scope_names(n)) {
- return;
- }
-
-
- // If this is a function that has multiple return values,
- // first we need to emit the struct that contains the returns
- if (
- printer.get_phase() == printer.phase1_type_defs_func_decls
- && n.is_function()
- )
- {
- auto& func = std::get<declaration_node::a_function>(n.type);
- assert(func);
-
- if (func->returns.index() == function_type_node::list)
- {
- auto& r = std::get<function_type_node::list>(func->returns);
- assert(r);
- assert(std::ssize(r->parameters) > 0);
-
- auto func_name = n.name()->to_string();
-
- // If it's a single named value, emit it as an anonymous return value
- if (std::ssize(r->parameters) == 1)
- {
- printer.print_extra(
- "\nusing "
- + func_name + "_ret = "
- + r->parameters[0]->declaration->get_object_type()->to_string()
- + ";"
- );
- }
- // Else just emit it as an ordinary struct
- else
- {
- printer.print_extra(
- "\nstruct "
- + n.name()->to_string()
- + "_ret "
- );
- emit(*r, true);
- }
- printer.print_extra( "\n" );
- }
- }
-
- // If this is a class definition that has data members before bases,
- // first we need to emit the aggregate that contains the members
- if (
- n.is_type()
- && printer.get_phase() == printer.phase1_type_defs_func_decls
- )
- {
- assert(
- n.initializer
- && n.initializer->is_compound()
- );
- auto& compound_stmt = std::get<statement_node::compound>(n.initializer->statement);
-
- assert(compound_stmt);
- auto found = false;
-
- for (auto& stmt : compound_stmt->statements)
- {
- if (stmt->is_declaration())
- {
- auto& decl = std::get<statement_node::declaration>(stmt->statement);
- assert(decl);
- assert(decl->name());
-
- auto emit_as_base =
- decl->get_decl_if_type_scope_object_name_before_a_base_type(*decl->name());
-
- if (emit_as_base) {
- printer.print_extra(
- "\nstruct "
- + print_to_string(*decl->parent_declaration->name())
- + "_"
- + decl->name()->to_string()
- + "_as_base { "
- + print_to_string( *decl->get_object_type() )
- + " "
- + decl->name()->to_string()
- + "; };"
- );
- found = true;
- }
- }
- }
-
- if (found) {
- printer.print_extra("\n");
- }
- }
-
- // In class definitions, emit the explicit access specifier if there
- // is one, or default to private for data and public for functions
- if (printer.get_phase() == printer.phase1_type_defs_func_decls)
- {
- if (!n.is_default_access()) {
- assert (is_in_type);
- printer.print_cpp2(to_string(n.access) + ": ", n.position());
- }
- else if (is_in_type) {
- if (n.is_object()) {
- printer.print_cpp2("private: ", n.position());
- }
- else {
- printer.print_cpp2("public: ", n.position());
- }
- }
- }
-
- // If this is a function definition and the function is inside
- // type(s) that have template parameters and/or requires clauses,
- // emit those outer template parameters and requires clauses too
- if (
- printer.get_phase() == printer.phase2_func_defs
- && n.is_function()
- && n.initializer // only if the function has a definition (is not abstract)
- )
- {
- emit_parent_template_parameters();
- }
-
- // Now, emit our own template parameters
- if (
- n.template_parameters
- && (
- printer.get_phase() < printer.phase2_func_defs
- || n.is_object()
- || (
- n.is_function()
- && n.has_name() // only if it is not unnamed function aka lambda
- && n.initializer // only if the function has a definition (is not abstract)
- && printer.get_phase() == printer.phase2_func_defs
- )
- )
- && (
- !n.is_concept()
- || printer.get_phase() == printer.phase1_type_defs_func_decls
- )
- )
- {
- printer.print_cpp2("template", n.position());
- emit(*n.template_parameters, false, true);
- printer.print_cpp2(" ", n.position());
- }
-
- // User-defined type
- if (n.is_type())
- {
- assert(
- n.initializer
- && n.initializer->is_compound()
- );
- auto& compound_stmt = std::get<statement_node::compound>(n.initializer->statement);
-
- if (printer.get_phase() != printer.phase2_func_defs)
- {
- if (n.requires_clause_expression) {
- printer.print_cpp2("requires( ", n.requires_pos);
- emit(*n.requires_clause_expression);
- printer.print_cpp2(" )\n", n.requires_pos);
- }
-
- printer.print_cpp2("class ", n.position());
- emit(*n.identifier);
-
- // Type declaration
- if (printer.get_phase() == printer.phase0_type_decls) {
- printer.print_cpp2( ";\n", n.position() );
- return;
- }
- }
-
- if (
- n.is_type_final()
- && printer.get_phase() == printer.phase1_type_defs_func_decls
- )
- {
- printer.print_cpp2( " final", n.position() );
- }
-
- // Type definition
- auto separator = std::string{":"};
- auto started_body = false;
- auto saved_for_body = std::vector<std::pair<std::string, source_position>>{};
- auto found_constructor = false;
- auto found_that_constructor = false;
- assert(compound_stmt);
-
- auto start_body = [&]{
- if (!started_body) {
- printer.print_cpp2(" {", compound_stmt->position());
- started_body = true;
- for (auto& [line, pos] : saved_for_body) {
- printer.print_cpp2(line + "\n", pos);
- }
- }
- };
-
- for (auto& stmt : compound_stmt->statements)
- {
- assert(stmt);
- if (
- !stmt->is_declaration()
- && !stmt->is_using()
- )
- {
- // We will already have emitted an error for this in sema.check
- return;
- }
-
- // If it's a using statement, save it up if we haven't started the body yet
-
- if (stmt->is_using()) {
- auto& use = std::get<statement_node::using_>(stmt->statement);
- assert(use);
- if (started_body) {
- emit(*use);
- }
- else {
- saved_for_body.emplace_back( print_to_string(*use), use->position() );
- }
- continue;
- }
-
- // Else it's a declaration...
-
- auto& decl = std::get<statement_node::declaration>(stmt->statement);
- assert(decl);
-
- if (
- decl->is_alias()
- && printer.get_phase() == printer.phase1_type_defs_func_decls
- )
- {
- if (started_body) {
- emit(*decl);
- }
- else {
- saved_for_body.emplace_back( print_to_string(*decl), decl->position() );
- }
- continue;
- }
-
- if (decl->is_constructor()) {
- found_constructor = true;
- }
- if (decl->is_constructor_with_that()) {
- found_that_constructor = true;
- }
-
- // First we'll encounter the base types == subobjects named "this"
- // and any data members declared before them that we push into private bases
- assert(decl->name());
- auto emit_as_base =
- decl->get_decl_if_type_scope_object_name_before_a_base_type(*decl->name())
- || decl->has_name("this")
- ;
- if (emit_as_base)
- {
- // Do the sema check for these declarations here, because we're
- // handling them here instead of going through emit() for them
- if (!sema.check(*decl)) {
- return;
- }
-
- if (decl->has_name("this")) {
- if (printer.get_phase() == printer.phase1_type_defs_func_decls) {
- printer.print_cpp2(
- separator + " public " + print_to_string(*decl->get_object_type()),
- compound_stmt->position()
- );
- separator = ",";
- }
- }
- else
- {
- if (printer.get_phase() == printer.phase1_type_defs_func_decls) {
- printer.print_cpp2(
- separator
- + " public "
- + print_to_string(*decl->parent_declaration->name())
- + "_"
- + decl->name()->to_string()
- + "_as_base",
- compound_stmt->position()
- );
- separator = ",";
- }
- }
- }
- // Then we'll switch to start the body == other members
- else
- {
- if (printer.get_phase() == printer.phase1_type_defs_func_decls) {
- start_body();
- }
- emit(*decl);
- }
- }
-
- if (printer.get_phase() == printer.phase1_type_defs_func_decls)
- {
- // Ensure we emit the { even if there are only bases in the type
- start_body();
-
- auto id = print_to_string(*n.identifier);
- auto indent = static_cast<size_t>(
- std::clamp(
- compound_stmt->body_indent,
- n.position().colno,
- n.position().colno + 5 // sanity check
- )
- );
- auto prefix = "\n" + std::string( indent, ' ' ) + "public: ";
-
- if (n.member_function_generation)
- {
- // If no constructor was defined, there should only be
- // a default constructor, so generate that
- if (!found_constructor) {
- printer.print_extra( prefix + id + "() = default;" );
- }
-
- // If no 'that' constructor was defined, disable copy/move
- // so that Cpp1 doesn't silently generate it anyway
- if (!found_that_constructor) {
- printer.print_extra( prefix + id + "(" + id + " const&) = delete; /* No 'that' constructor, suppress copy */" );
- printer.print_extra( prefix + "auto operator=(" + id + " const&) -> void = delete;" );
- }
-
- if (!found_constructor || !found_that_constructor) {
- printer.print_extra( "\n" );
- }
- }
-
- printer.print_cpp2("};\n", compound_stmt->close_brace);
- }
- }
-
-
- // Namespace
- if (n.is_namespace())
- {
- printer.print_cpp2("namespace ", n.position());
-
- // "_" is the anonymous namespace, which is just whitespace in Cpp1
- if (auto tok = n.identifier->get_token();
- tok
- && *tok != "_"
- )
- {
- emit(*n.identifier);
- }
-
- assert(
- n.initializer
- && n.initializer->is_compound()
- );
- auto& compound_stmt = std::get<statement_node::compound>(n.initializer->statement);
-
- printer.print_cpp2(" {", compound_stmt->position());
-
- assert(compound_stmt);
- for (auto& stmt : compound_stmt->statements) {
- assert(stmt);
- if (stmt->is_declaration()) {
- auto& decl = std::get<statement_node::declaration>(stmt->statement);
- assert(decl);
- emit(*decl);
- }
- else if (stmt->is_using()) {
- auto& use = std::get<statement_node::using_>(stmt->statement);
- assert(use);
- emit(*use);
- }
- else {
- errors.emplace_back(
- stmt->position(),
- "a namespace scope must contain only declarations or 'using' statements, not other code"
- );
- return;
- }
- }
-
- printer.print_cpp2("}\n", compound_stmt->close_brace);
- }
-
- // Function
- else if (
- n.is_function()
- && (
- printer.get_phase() < printer.phase2_func_defs
- || n.initializer // only emit definition if the function has one (is not abstract)
- || n.is_defaultable_function()
- )
- )
- {
- auto is_streaming_operator = [](std::string_view sv) {
- return
- sv == "operator<<"
- || sv == "operator>>"
- ;
- };
-
- auto is_binary_arithmetic_operator = [](std::string_view sv) {
- return
- sv == "operator+"
- || sv == "operator-"
- || sv == "operator*"
- || sv == "operator/"
- || sv == "operator%"
- ;
- };
-
- auto emit_as_friend =
- n.name()
- && (
- is_streaming_operator( n.name()->as_string_view() )
- || (!n.is_function_with_this() && is_binary_arithmetic_operator( n.name()->as_string_view() ))
- )
- ;
-
- // Start fresh (there may be one spurious leftover
- // requires-condition created during the declarations pass)
- function_requires_conditions = {};
-
- auto& func = std::get<declaration_node::a_function>(n.type);
- assert(func);
-
- current_functions.push(
- &n,
- func.get(),
- n.find_parent_declared_value_set_functions()
- );
- auto guard0 = finally([&]{ current_functions.pop(); });
-
- auto guard1 = stack_size(current_names);
-
- // If this is at expression scope, we can't emit "[[nodiscard]] auto name"
- // so print the provided intro instead, which will be a Cpp1 lambda-introducer
- if (capture_intro != "")
- {
- assert (!n.identifier);
- printer.print_cpp2(capture_intro, n.position());
- emit( *func, nullptr, is_main);
- }
-
- // Else start introducing a normal function
- else
- {
- assert (n.identifier);
-
- // Handle member functions
- std::string prefix = {};
- std::string suffix1 = {};
- std::string suffix2 = {};
-
- if (n.is_constexpr) {
- prefix += "constexpr ";
- }
-
- if (
- !n.has_initializer()
- && n.is_defaultable_function()
- )
- {
- suffix2 += " = default";
- }
-
- // If there's a 'this' parameter, handle it here (the parameter emission will skip it)
- // because Cpp1 syntax requires its information to be spread around the declaration syntax
- assert (func->parameters);
- if (
- !func->parameters->parameters.empty()
- && func->parameters->parameters[0]->declaration->has_name("this")
- )
- {
- assert (is_in_type);
- auto& this_ = func->parameters->parameters[0];
-
- switch (this_->pass) {
- break;case passing_style::in:
- suffix1 += " const";
- // Cpp1 ref-qualifiers don't belong on virtual functions
- if (!this_->is_polymorphic()) {
- suffix1 += "&";
- }
- break;case passing_style::inout:
- // Cpp1 ref-qualifiers don't belong on virtual functions
- if (!this_->is_polymorphic()) {
- suffix1 += " &";
- }
- break;case passing_style::out:
- ; // constructor is handled below
- break;case passing_style::move:
- suffix1 += " &&";
-
- // We shouldn't be able to get into a state where these values
- // exist here, if we did it's our compiler bug
- break;case passing_style::copy:
- case passing_style::forward:
- default:
- errors.emplace_back( n.position(), "ICE: invalid parameter passing style, should have been rejected", true);
- }
-
- // Note: Include a phase check because Cpp1 does not allow
- // these on out-of-line definitions
- if (printer.get_phase() != printer.phase2_func_defs)
- {
- switch (this_->mod) {
- break;case parameter_declaration_node::modifier::implicit:
- ;
- break;case parameter_declaration_node::modifier::virtual_:
- prefix += "virtual ";
- if (!n.initializer) {
- suffix2 += " = 0";
- }
- break;case parameter_declaration_node::modifier::override_:
- suffix2 += " override";
- break;case parameter_declaration_node::modifier::final_:
- suffix2 += " final";
- break;default:
- if (
- func->is_constructor()
- && !func->is_constructor_with_that()
- && generating_assignment_from != &n
- )
- {
- prefix += "explicit ";
- }
- }
- }
- }
- // Else if there isn't a 'this' parameter, but this function is in a type scope,
- // it's a Cpp1 non-member function so we need to say so (on the declaration only)
- else if (
- is_in_type
- && printer.get_phase() != printer.phase2_func_defs
- ) {
- if (emit_as_friend) {
- prefix += "friend ";
- }
- else {
- prefix += "static ";
- }
- }
-
- // If there's a return type, it's [[nodiscard]] implicitly and all the time
- // -- for now there's no opt-out, wait and see whether we actually need one
- if (
- func->has_non_void_return_type()
- && !func->is_assignment()
- && !func->is_compound_assignment()
- && !func->is_increment_or_decrement()
- && (
- printer.get_phase() == printer.phase1_type_defs_func_decls
- || n.has_initializer() // so we're printing it in phase 2
- )
- && (
- !emit_as_friend // can't have an attribute on a friend declaration-not-definition
- || printer.get_phase() != printer.phase1_type_defs_func_decls
- )
- && !(
- n.name()
- && is_streaming_operator(n.name()->as_string_view())
- )
- )
- {
- printer.print_cpp2( "[[nodiscard]] ", n.position() );
- }
-
- // Now we have all the pieces we need for the Cpp1 function declaration
-
- // For a special member function, we need to do more work to translate
- // in-body initialization statements to the Cpp1 mem-init-list syntax
- if (
- n.is_constructor()
- || n.is_assignment()
- )
- {
- assert(
- !is_main
- && suffix2.empty()
- && "ICE: an operator= shouldn't have been able to generate a suffix (or be main)"
- );
-
- emit_special_member_function(
- n,
- prefix
- );
-
- // If there's no inheritance and this operator= has two parameters,
- // it's setting from a single value -- either from the same type
- // (aka copy/move) or another type (a conversion) -- so recurse to
- // emit related functions if the user didn't write them by hand
- if (
- !n.parent_is_polymorphic()
- && func->parameters->ssize() == 2
- && generating_assignment_from != &n
- )
- {
- assert(!current_functions.empty());
-
- // A) Generate (A)ssignment from a constructor,
- // if the user didn't write the assignment function themselves
- if (
- // A1) This is '(out this, that)'
- // and no '(inout this, that)' was written by the user
- (
- &n == current_functions.back().declared_value_set_functions.out_this_in_that
- && !current_functions.back().declared_value_set_functions.inout_this_in_that
- )
- ||
- // A2) This is '(out this, move that)'
- // and no '(inout this, move that)' was written by the user
- // (*) and no '(inout this, that)' was written by the user (*)
- //
- // (*) This third test is to tie-break M2 and A2 in favor of M2. Both M2 and A2
- // can generate a missing '(inout this, move that)', and if we have both
- // options then we should prefer to use M2 (generate move assignment from
- // copy assignment) rather than A2 (generate move assignment from move
- // construction) as M2 is a better fit (move assignment is more like copy
- // assignment than like move construction, because assignments are designed
- // structurally to set the value of an existing 'this' object)
- (
- &n == current_functions.back().declared_value_set_functions.out_this_move_that
- && !current_functions.back().declared_value_set_functions.inout_this_move_that
- && !current_functions.back().declared_value_set_functions.inout_this_in_that
- )
- ||
- // A3) This is '(out this, something-other-than-that)'
- (
- n.is_constructor()
- && !n.is_constructor_with_that()
- && !contains( current_functions.back().declared_value_set_functions.assignments_from, n.nth_parameter_type_name(2) )
- )
- )
- {
- need_to_generate_assignment = true;
- }
-
- if (generating_move_from != &n) {
-
- // M) Generate (M)ove from copy,
- // if the user didn't write the move function themselves
- if (
- // M1) This is '(out this, that)'
- // and no '(out this, move that)' was written by the user
- (
- &n == current_functions.back().declared_value_set_functions.out_this_in_that
- && !current_functions.back().declared_value_set_functions.out_this_move_that
- )
- ||
- // M2) This is '(inout this, that)'
- // and no '(inout this, move that)' was written by the user
- (
- &n == current_functions.back().declared_value_set_functions.inout_this_in_that
- && !current_functions.back().declared_value_set_functions.inout_this_move_that
- )
- )
- {
- need_to_generate_move = true;
- }
-
- }
- }
- }
-
- // For a destructor, we need to translate
- else if (n.is_destructor())
- {
- assert(
- !is_main
- // prefix can be "virtual"
- // suffix1 will be " &&" though we'll ignore that
- // suffix2 can be "= 0"
- );
-
- // Print the ~-prefixed type name instead of the operator= function name
- assert(
- n.parent_is_type()
- && n.parent_declaration->name()
- );
- printer.print_cpp2(
- prefix
- + type_qualification_if_any_for(n)
- + "~" + print_to_string(*n.parent_declaration->name()),
- n.position()
- );
- emit( *func, n.name(), false, true);
- printer.print_cpp2( suffix2, n.position() );
- }
-
- // Ordinary functions are easier, do all their declarations except
- // don't emit abstract virtual functions in phase 2
- else if (
- n.initializer
- || printer.get_phase() < printer.phase2_func_defs
- )
- {
- printer.print_cpp2( prefix, n.position() );
- printer.print_cpp2( "auto ", n.position() );
- if (
- !emit_as_friend
- || printer.get_phase() != printer.phase2_func_defs
- )
- {
- printer.print_cpp2( type_qualification_if_any_for(n), n.position() );
- }
-
- emit( *n.name() );
- emit( *func, n.name(), is_main, false, suffix1, generating_postfix_inc_dec_from != nullptr );
- printer.print_cpp2( suffix2, n.position() );
-
- // If this is ++ or --, also generate a Cpp1 postfix version of the operator
- if (func->is_increment_or_decrement())
- {
- if (generating_postfix_inc_dec_from) {
- assert (generating_postfix_inc_dec_from == &n);
- }
- else {
- need_to_generate_postfix_inc_dec = true;
- }
- }
- }
- }
-
- // If we're only emitting declarations, end the function declaration
- if (
- printer.get_phase() == printer.phase1_type_defs_func_decls
- && !n.is_function_expression()
- )
- {
- emit_requires_clause();
- if (n.position().lineno < 0) {
- printer.print_cpp2( ";\n", n.position() );
- }
- else {
- printer.print_cpp2( ";", n.position() );
- }
-
- // Note: Not just early "return;" here because we may need
- // to recurse to emit generated operator declarations too,
- // so all the definition work goes into a big 'else' branch
- }
-
- // Else emit the definition
- else if (n.initializer)
- {
- if (func->returns.index() == function_type_node::list) {
- auto& r = std::get<function_type_node::list>(func->returns);
- function_returns.emplace_back(r.get());
- }
- else if (func->returns.index() == function_type_node::id) {
- function_returns.emplace_back(
- &single_anon, // use special value as a note
- std::get<function_type_node::id>(func->returns).pass,
- std::get<function_type_node::id>(func->returns).type->is_wildcard()
- );
- }
- else {
- function_returns.emplace_back(nullptr); // no return type at all
- }
-
- if (func->has_postconditions()) {
- current_functions.back().prolog.statements.push_back("cpp2::finally_presuccess cpp2_finally_presuccess;");
- }
-
- if (func->returns.index() == function_type_node::list)
- {
- auto& r = std::get<function_type_node::list>(func->returns);
- assert(r);
- for (auto& param : r->parameters)
- {
- assert(param && param->declaration);
- auto& decl = *param->declaration;
-
- assert(decl.is_object());
- auto& id_expr = std::get<declaration_node::an_object>(decl.type);
- assert(id_expr);
-
- auto loc = std::string{};
- if (!decl.initializer) {
- loc += (" cpp2::deferred_init<");
- }
-
- // For convenience, just capture the id-expression as a string
- printer.emit_to_string(&loc);
- emit(*id_expr);
- printer.emit_to_string();
-
- if (!decl.initializer) {
- loc += (">");
- }
- loc += " ";
- loc += decl.name()->as_string_view();
- if (decl.initializer)
- {
- std::string init;
- printer.emit_to_string(&init);
- printer.print_cpp2 ( " {", decl.initializer->position() );
- if (!decl.initializer->is_expression()) {
- errors.emplace_back(
- decl.initializer->position(),
- "return value initializer must be an expression"
- );
- return;
- }
- auto& expr = std::get<statement_node::expression>(decl.initializer->statement);
- assert(expr);
-
- emit(*expr, false);
- printer.print_cpp2 ( "}", decl.initializer->position() );
- printer.emit_to_string();
-
- loc += init;
- }
- loc += ";";
- current_functions.back().prolog.statements.push_back(loc);
- }
- }
-
- for (auto&& c : func->contracts)
- {
- auto print = std::string();
- printer.emit_to_string(&print);
- auto guard = stack_value(having_signature_emitted, nullptr);
- emit(*c);
- printer.emit_to_string();
- current_functions.back().prolog.statements.push_back(print);
- }
-
- printer.preempt_position_push( n.equal_sign );
-
- emit_requires_clause();
-
- having_signature_emitted = nullptr;
-
- // If this is ++ or --, also generate a Cpp1 postfix version of the operator
- if (generating_postfix_inc_dec_from)
- {
- assert (generating_postfix_inc_dec_from == &n);
-
- auto param1 = std::string{"*this"};
- if (
- !n.parent_declaration
- || !n.parent_declaration->is_type()
- )
- {
- param1 = n.first_parameter_name();
- }
-
- printer.print_cpp2(
- " { auto ret = " + param1 + "; ++" + param1 + "; return ret; }",
- n.position()
- );
- }
- // Else just emit the normal function body
- else {
- emit(
- *n.initializer,
- true, func->position(), func->returns.index() == function_type_node::empty,
- current_functions.back().prolog,
- current_functions.back().epilog
- );
- }
-
- printer.preempt_position_pop();
-
- function_returns.pop_back();
- }
-
- // Finally, do the potential recursions...
-
- // If this was a constructor and we want also want to emit
- // it as an assignment operator, do it via a recursive call
- if (need_to_generate_assignment)
- {
- // Reset the 'emitted' flags
- for (auto& statement : n.get_initializer_statements()) {
- statement->emitted = false;
- }
-
- // Then reposition and do the recursive call
- printer.reset_line_to(n.position().lineno);
- generating_assignment_from = &n;
- emit( n, capture_intro );
- generating_assignment_from = {};
- }
-
- // If this was a constructor and we want also want to emit
- // it as an assignment operator, do it via a recursive call
- if (need_to_generate_move)
- {
- // Reset the 'emitted' flags
- for (auto& statement : n.get_initializer_statements()) {
- statement->emitted = false;
- }
-
- // Then reposition and do the recursive call
- printer.reset_line_to(n.position().lineno);
- generating_move_from = &n;
- emit( n, capture_intro );
- generating_move_from = {};
- }
-
- // If this is ++ or --, emit the Cpp1 postfix version via a recursive call
- if (need_to_generate_postfix_inc_dec)
- {
- // Reset the 'emitted' flags
- for (auto& statement : n.get_initializer_statements()) {
- statement->emitted = false;
- }
-
- // Then reposition and do the recursive call
- printer.reset_line_to(n.position().lineno);
- generating_postfix_inc_dec_from = &n;
- emit( n, capture_intro );
- generating_postfix_inc_dec_from = {};
- }
- }
-
- // Object with optional initializer
- else if (
- n.is_object()
- && (
- (
- n.parent_is_namespace()
- && printer.get_phase() >= printer.phase1_type_defs_func_decls
- )
- ||
- (
- n.parent_is_type()
- && printer.get_phase() == printer.phase1_type_defs_func_decls
- )
- ||
- (
- n.parent_is_function()
- && printer.get_phase() == printer.phase2_func_defs
- )
- ||
- (
- n.is_inside_global_unnamed_function()
- && printer.get_phase() == printer.phase1_type_defs_func_decls
- )
- )
- )
- {
- auto& type = std::get<declaration_node::an_object>(n.type);
- if (
- printer.get_phase() == printer.phase2_func_defs
- && type->is_concept()
- )
- {
- return;
- }
-
- emit_requires_clause();
-
- if (
- printer.get_phase() != printer.phase2_func_defs
- && n.parent_is_namespace()
- && !type->is_concept()
- )
- {
- printer.print_cpp2( "extern ", n.position() );
- }
-
- // Emit "auto" for deduced types (of course)
- if (type->is_wildcard()) {
- assert(n.initializer);
- emit( *type, n.position() );
- }
- // Otherwise, emit the type
- else {
- // If there isn't an initializer, use cpp2::deferred_init<T>
- if (!n.initializer) {
- if (n.parent_is_function()) {
- printer.print_cpp2( "cpp2::deferred_init<", n.position() );
- }
- else if (!n.parent_is_type()) {
- errors.emplace_back(
- n.position(),
- "a namespace-scope object must have an initializer"
- );
- return;
- }
- }
- printer.preempt_position_push(n.position());
- emit( *type );
- printer.preempt_position_pop();
- // one pointer is enough for now, pointer-to-function fun can be later
- if (
- !n.initializer
- && n.parent_is_function()
- )
- {
- printer.print_cpp2( ">", n.position() );
- }
- }
-
- printer.print_cpp2( " ", n.position());
- assert(n.identifier);
-
- // If this is anonymous object (named "_"), generate a unique name
- if (n.has_name("_")) {
- if (n.has_wildcard_type()) {
- errors.emplace_back(
- n.identifier->position(),
- "an object can have an anonymous name or an anonymous type, but not both at the same type (rationale: if '_ := f();' were allowed to keep the returned object alive, that syntax would be dangerously close to '_ = f();' to discard the returned object, and such importantly opposite meanings deserve more than a one-character typo distance; and explicit discarding gets the nice syntax because it's likely more common)"
- );
- return;
- }
-
- printer.print_cpp2(
- "auto_" + labelized_position(n.identifier->get_token()),
- n.identifier->position()
- );
- }
- else {
- emit(*n.identifier);
- }
-
- if (
- n.parent_is_namespace()
- && printer.get_phase() != printer.phase2_func_defs
- && !type->is_concept()
- )
- {
- printer.print_cpp2( ";", n.position());
- return;
- }
-
- // If there's an initializer, emit it
- if (n.initializer)
- {
- printer.add_pad_in_this_line(-100);
- if (type->is_concept()) {
- printer.print_cpp2( " = ", n.position() );
- } else {
- printer.print_cpp2( " {", n.position() );
- }
-
- push_need_expression_list_parens(false);
- assert( n.initializer );
- emit( *n.initializer, false );
- pop_need_expression_list_parens();
-
- if (!type->is_concept()) {
- printer.print_cpp2( "}", n.position() );
- }
- }
-
- printer.print_cpp2( "; ", n.position() );
- }
- }
-
-
- //-----------------------------------------------------------------------
- // print_errors
- //
- auto print_errors()
- -> void
- {
- if (!errors.empty()) {
- // Delete the output file
- printer.abandon();
- }
-
- error_entry const* prev = {};
- bool print_fallback_errors = true; // true until we find a non-fallback message
-
- for (auto&& error : errors)
- {
- // Only print fallback error messages if we
- // haven't found a better (non-fallback) one yet
- if (!error.fallback) {
- print_fallback_errors = false;
- }
- if (error.fallback && !print_fallback_errors) {
- continue;
- }
-
- // Suppress adjacent duplicates (e.g., can arise when we
- // reenter operator= to emit it as an assignment operator)
- if (
- !prev
- || error != *prev
- )
- {
- error.print(std::cerr, strip_path(sourcefile));
- }
- prev = &error;
- }
-
- if (violates_lifetime_safety) {
- std::cerr << " ==> program violates lifetime safety guarantee - see previous errors\n";
- }
- if (violates_bounds_safety) {
- std::cerr << " ==> program violates bounds safety guarantee - see previous errors\n";
- }
- if (violates_initialization_safety) {
- std::cerr << " ==> program violates initialization safety guarantee - see previous errors\n";
- }
- }
-
- auto had_no_errors()
- -> 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" };
- parser.debug_print( out_parse );
-
- 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();
- }
-};
-
-}
-
-
-#endif