diff options
| author | Amlal El Mahrouss <amlal.elmahrouss@icloud.com> | 2024-01-09 21:47:33 +0100 |
|---|---|---|
| committer | Amlal El Mahrouss <amlal.elmahrouss@icloud.com> | 2024-01-09 21:47:33 +0100 |
| commit | 9cef856478cebe4bfe00e1d39c9e2d49015dd0e4 (patch) | |
| tree | f04c6b6b1156057748c7044a766120485c45c885 /64x0/cc2/source | |
| parent | a8a55bc93e06cd8f75f7d397c013f7a312ea29a4 (diff) | |
MP-UX/hCore Assembler for 64x0, Release I.
Signed-off-by: Amlal El Mahrouss <amlal.elmahrouss@icloud.com>
Diffstat (limited to '64x0/cc2/source')
| -rw-r--r-- | 64x0/cc2/source/build.info | 1 | ||||
| -rw-r--r-- | 64x0/cc2/source/common.h | 968 | ||||
| -rw-r--r-- | 64x0/cc2/source/cpp2util.h | 1 | ||||
| -rw-r--r-- | 64x0/cc2/source/cppfront.cpp | 115 | ||||
| -rw-r--r-- | 64x0/cc2/source/io.h | 1079 | ||||
| -rw-r--r-- | 64x0/cc2/source/lex.h | 1989 | ||||
| -rw-r--r-- | 64x0/cc2/source/parse.h | 9263 | ||||
| -rw-r--r-- | 64x0/cc2/source/reflect.h | 1965 | ||||
| -rw-r--r-- | 64x0/cc2/source/reflect.h2 | 1447 | ||||
| -rw-r--r-- | 64x0/cc2/source/sema.h | 1892 | ||||
| -rw-r--r-- | 64x0/cc2/source/to_cpp1.h | 6750 | ||||
| -rw-r--r-- | 64x0/cc2/source/version.info | 1 |
12 files changed, 0 insertions, 25471 deletions
diff --git a/64x0/cc2/source/build.info b/64x0/cc2/source/build.info deleted file mode 100644 index 1d47385..0000000 --- a/64x0/cc2/source/build.info +++ /dev/null @@ -1 +0,0 @@ -"8C20:1314"
\ No newline at end of file diff --git a/64x0/cc2/source/common.h b/64x0/cc2/source/common.h deleted file mode 100644 index fe0301c..0000000 --- a/64x0/cc2/source/common.h +++ /dev/null @@ -1,968 +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. - - -#ifdef _MSC_VER -#pragma warning(disable: 4456) -#endif - -#include "cpp2util.h" - - -//=========================================================================== -// Common types -//=========================================================================== - -#ifndef CPP2_COMMON_H -#define CPP2_COMMON_H - -#include <string> -#include <string_view> -#include <vector> -#include <cstdint> -#include <cctype> -#include <cassert> -#include <iomanip> -#include <compare> -#include <algorithm> -#include <unordered_map> - -namespace cpp2 { - -//----------------------------------------------------------------------- -// -// source_line: represents a source code line -// -//----------------------------------------------------------------------- -// -struct source_line -{ - std::string text; - - enum class category { empty, preprocessor, comment, import, cpp1, cpp2, rawstring }; - category cat; - - bool all_tokens_are_densely_spaced = true; // to be overridden in lexing if they're not - - source_line( - std::string_view t = {}, - category c = category::empty - ) - : text{t} - , cat{c} - { } - - auto indent() const - -> int - { - return - std::find_if_not( text.begin(), text.end(), &isspace ) - - text.begin(); - } - - auto prefix() const - -> std::string - { - switch (cat) { - break;case category::empty: return "/* */ "; - break;case category::preprocessor: return "/* # */ "; - break;case category::comment: return "/* / */ "; - break;case category::import: return "/* i */ "; - break;case category::cpp1: return "/* 1 */ "; - break;case category::cpp2: return "/* 2 */ "; - break;case category::rawstring: return "/* R */ "; - break;default: assert(!"illegal category"); abort(); - } - } -}; - - -using lineno_t = int32_t; -using colno_t = int32_t; // not int16_t... encountered >80,000 char line during testing - -struct source_position -{ - lineno_t lineno; // one-based offset into program source - colno_t colno; // one-based offset into line - - source_position(lineno_t l = 1, colno_t c = 1 ) - : lineno{ l }, colno{ c } - { - } - - auto operator<=>(source_position const&) const = default; - - auto to_string() const - -> std::string - { - return "(" + std::to_string(lineno) + "," + std::to_string(colno) + ")"; - } -}; - -struct comment -{ - enum class comment_kind { line_comment = 0, stream_comment }; - - comment_kind kind; - source_position start; - source_position end; - std::string text; - - mutable bool dbg_was_printed = false; -}; - -struct string_parts { - struct cpp_code { std::string text; }; - struct raw_string { std::string text; }; - enum adds_sequences { no_ends = 0, on_the_beginning = 1, on_the_end = 2, on_both_ends = 3 }; - - string_parts(const std::string& beginseq, - const std::string& endseq, - adds_sequences strateg) - : begin_seq{beginseq} - , end_seq{endseq} - , strategy{strateg} - { - if (!(strategy & on_the_beginning)) { - parts.push_back(raw_string{""}); - } - } - - void add_code(const std::string& text) { parts.push_back(cpp_code{text});} - void add_string(const std::string& text) { parts.push_back(raw_string{text});} - void add_string(const std::string_view& text) { parts.push_back(raw_string{std::string(text)});} - - void clear() { parts.clear(); } - - auto generate() const -> std::string { - - if (parts.empty()) { - return (strategy & on_the_beginning ? begin_seq : std::string{}) - + (strategy & on_the_end ? end_seq : std::string{}); - } - - auto result = std::visit(begin_visit{begin_seq, strategy}, - parts.front()); - - if (std::ssize(parts) > 1) { - auto it1 = parts.cbegin(); - auto it2 = parts.cbegin()+1; - for(;it2 != parts.cend(); ++it1, ++it2) { - result += std::visit(generator_visit{begin_seq, end_seq}, *it1, *it2); - } - } - - if (!(strategy & on_the_end)) { - result += std::visit([this](const auto& lhs) { - return generator_visit{begin_seq, end_seq}(lhs, raw_string{""}); - }, parts.back()); - } - - result += std::visit(end_visit{end_seq, strategy}, parts.back()); - - return result; - } - - auto is_expanded() const -> bool { - for (const auto& p : parts) { - if (std::holds_alternative<cpp_code>(p)) { - return true; - } - } - return false; - } - -private: - std::string begin_seq; - std::string end_seq; - adds_sequences strategy; - std::vector<std::variant<raw_string, cpp_code>> parts; - - struct begin_visit { - std::string begin_seq; - adds_sequences strategy; - - auto operator()(const raw_string& part) const -> std::string { - return (strategy & on_the_beginning ? begin_seq : "") + part.text; - } - auto operator()(const cpp_code& part) const -> std::string { - return part.text; - } - }; - - struct end_visit { - std::string end_seq; - adds_sequences strategy; - auto operator()(const raw_string&) const -> std::string { - return strategy & on_the_end ? end_seq : ""; - } - auto operator()(const cpp_code&) const -> std::string { - return {}; - } - }; - - struct generator_visit { - std::string begin_seq; - std::string end_seq; - - auto operator()(const raw_string&, const cpp_code& part ) const -> std::string { - return end_seq + " + " + part.text; - } - auto operator()(const cpp_code&, const raw_string& part ) const -> std::string { - return " + " + begin_seq + part.text; - } - auto operator()(const raw_string&, const raw_string& part ) const -> std::string { - return part.text; - } - auto operator()(const cpp_code&, const cpp_code& part ) const -> std::string { - return " + " + part.text; - } - }; -}; - -struct raw_string -{ - source_position start; - std::string text; - std::string opening_seq; - std::string closing_seq; - bool should_interpolate = false; -}; - -struct multiline_raw_string -{ - std::string text; - source_position end = {0, 0}; -}; - -//----------------------------------------------------------------------- -// -// error: represents a user-readable error message -// -//----------------------------------------------------------------------- -// -struct error_entry -{ - source_position where; - std::string msg; - bool internal = false; - bool fallback = false; // only emit this message if there was nothing better - - error_entry( - source_position w, - std::string_view m, - bool i = false, - bool f = false - ) - : where{w} - , msg{m} - , internal{i} - , fallback{f} - { } - - auto operator==(error_entry const& that) - -> bool - { - return - where == that.where - && msg == that.msg - ; - } - - auto print(auto& o, std::string const& file) const - -> void; -}; - - -//----------------------------------------------------------------------- -// -// Digit classification, with '\'' digit separators -// -//----------------------------------------------------------------------- -// - -//G binary-digit: -//G one of '0' '1' -//G -auto is_binary_digit(char c) - -> bool -{ - return - c == '0' - || c == '1' - ; -} - -//G digit: one of -//G binary-digit -//G one of '2' '3' '4' '5' '6' '7' '8' '9' -//G -auto is_digit(char c) - -> bool -{ - return isdigit(c); -} - -//G hexadecimal-digit: -//G digit -//G one of 'A' 'B' 'C' 'D' 'E' 'F' -//G -auto is_hexadecimal_digit(char c) - -> bool -{ - return isxdigit(c); -} - -//G nondigit: -//G one of 'a'..'z' -//G one of 'A'..'Z' -//G _ -//G -auto is_nondigit(char c) - -> bool -{ - return - isalpha(c) - || c == '_' - ; -}; - -//G identifier-start: -//G nondigit -//G -auto is_identifier_start(char c) - -> bool -{ - return is_nondigit(c); -} - -//G identifier-continue: -//G digit -//G nondigit -//G -auto is_identifier_continue(char c) - -> bool -{ - return - is_digit(c) - || is_nondigit(c) - ; -} - -//G identifier: -//G '__identifier__' keyword [Note: without whitespace before the keyword] -//G identifier-start -//G identifier identifier-continue -//G 'operator' operator -//G -auto starts_with_identifier(std::string_view s) - -> int -{ - if (is_identifier_start(s[0])) { - auto j = 1; - while ( - j < std::ssize(s) - && is_identifier_continue(s[j]) - ) - { - ++j; - } - return j; - } - return 0; -}; - - -// Helper to allow one of the above or a digit separator -// Example: is_separator_or( is_binary_digit (c) ) -// -auto is_separator_or(auto pred, char c) - -> bool -{ - return - c == '\'' - || pred(c) - ; -} - - -// Bool to string -// -template<typename T> - requires std::is_same_v<T, std::string> -auto _as(bool b) - -> T -{ - return b ? "true" : "false"; -} - - -// Explicit cast -// -template<typename T> -auto _as(auto x) - -> T -{ - return T(x); -} - - -// String path prefix from filename -// -auto strip_path(std::string const& file) - -> std::string -{ - auto i = std::ssize(file)-1; - while ( - i >= 0 - && file[i] != '\\' - && file[i] != '/' - ) - { - --i; - } - return {file, _as<size_t>(i+1)}; -} - - -//----------------------------------------------------------------------- -// -// Misc helpers -// -//----------------------------------------------------------------------- -// -auto replace_all(std::string& s, std::string_view what, std::string_view with) -{ - for ( - std::string::size_type pos{}; - s.npos != (pos = s.find(what.data(), pos, what.length())); - pos += with.length() - ) - { - s.replace(pos, what.length(), with.data(), with.length()); - } - return s; -} - - -auto to_upper(char c) - -> char -{ - // C toupper is only not-UB in [0,127] and returns the wrong type, - // so wrap the range check and the type cast here in one place... - // note the 126 (not 127) is intentional to avoid a GCC warning - if (0 <= c && c <= 126) { return static_cast<char>(std::toupper(c)); } - // else - return c; -} - - -auto to_upper_and_underbar(std::string_view s) - -> std::string -{ - auto ret = std::string{s}; - for (char& c : ret) { - if (std::isalnum(c)) { c = to_upper(c); } - else { c = '_'; } - } - return ret; -} - - -auto is_empty_or_a_decimal_number(std::string_view s) - -> bool -{ - auto size = std::ssize(s); - if (size == 0) { return true; } - - auto i = 0; - while (i < size && isspace(s[i]) ) { ++i; } - while (i < size && isdigit(s[i]) ) { ++i; } - while (i < size && isspace(s[i]) ) { ++i; } - return i == size; -} - - -auto starts_with( - std::string const& s, - std::string_view sv -) - -> bool -{ - return std::string_view(s).starts_with(sv); -} - - -auto contains( - auto const& range, - auto const& value -) - -> bool -{ - return std::find( - range.begin(), - range.end(), - value - ) - != range.end(); -} - -auto contains( - std::string const& s, - auto const& value -) - -> bool -{ - return s.find(value) != s.npos; -} - - -// In keep trying to write string+string_view, and it ought to Just Work without -// the current workarounds. Not having that is a minor impediment to using safe -// and efficient string_views, which we should be encouraging. So for my own use -// and to remove that minor impediment to writing safe and efficient code, I'm -// just going to add this until we get P2591 in C++26(?) -- See: wg21.link/p2591 -// -template<class charT, class traits, class Allocator> -[[nodiscard]] constexpr auto operator+( - std::basic_string<charT, traits, Allocator> lhs, - std::type_identity_t<std::basic_string_view<charT, traits>> rhs - ) - -> std::basic_string<charT, traits, Allocator> -{ - return lhs.append(rhs); -} - -template<class charT, class traits, class Allocator> -[[nodiscard]] constexpr auto operator+( - std::type_identity_t<std::basic_string_view<charT, traits>> lhs, - std::basic_string<charT, traits, Allocator> rhs - ) - -> std::basic_string<charT, traits, Allocator> -{ - return rhs.insert(0, lhs); -} - - -//----------------------------------------------------------------------- -// -// Command line handling -// -//----------------------------------------------------------------------- -// - -class cmdline_processor -{ - bool help_requested = false; - - struct arg - { - int pos; - std::string text; - - arg(int p, char* t) : pos{p}, text{t} { } - }; - std::vector<arg> args; - - using callback0 = void (*)(); - using callback1 = void (*)(std::string const&); - struct flag - { - int group = 0; - std::string name; - int unique_prefix = 0; - std::string description; - callback0 handler0; - callback1 handler1; - std::string synonym; - bool opt_out; - - flag(int g, std::string_view n, std::string_view d, callback0 h0, callback1 h1, std::string_view s, bool o) - : group{g}, name{n}, description{d}, handler0{h0}, handler1{h1}, synonym{s}, opt_out{o} - { } - }; - std::vector<flag> flags; - int max_flag_length = 0; - - std::unordered_map<int, std::string> labels = { - { 2, "Additional dynamic safety checks and contract information" }, - { 4, "Support for constrained target environments" }, - { 9, "Other options" } - }; - - // Define this in the main .cpp to avoid bringing <iostream> into the headers, - // so that we can't accidentally start depending on iostreams in the compiler body - static auto print(std::string_view, int width = 0) - -> void; - -public: - auto process_flags() - -> void - { - constexpr auto processed = -1; - - // Calculate the unique prefixes - for (auto flag1 = flags.begin(); flag1 != flags.end(); ++flag1) { - for (auto flag2 = flag1+1; flag2 != flags.end(); ++flag2) { - int i = 0; - while ( - i < std::ssize(flag1->name) - && i < std::ssize(flag2->name) - && flag1->name[i] != ' ' - && flag2->name[i] != ' ' - && flag1->name[i] == flag2->name[i] - ) - { - ++i; - } - // Record that we found the unique prefix must be at least this long - flag1->unique_prefix = std::max( flag1->unique_prefix, i+1 ); - flag2->unique_prefix = std::max( flag2->unique_prefix, i+1 ); - } - } - - // Look for matches - for (auto arg = args.begin(); arg != args.end(); ++arg) - { - // The arg should never be empty, but we're going to do a [0] - // subscript next so we should either check or assert - if (arg->text.empty()) { - continue; - } - - // Provide a way to ignore the rest of the command line - // for the purpose of looking for switches - if (arg->text == "--") { - arg->pos = processed; - break; - } - - for (auto& flag : flags) { - auto length_to_match = std::max(flag.unique_prefix, _as<int>(arg->text.length())-1); - if ( - flag.opt_out - && arg->text.ends_with("-") - ) - { - length_to_match = std::max(flag.unique_prefix, _as<int>(arg->text.length())-2); - } - - // Allow a switch to start with either - or / - if (arg->text.starts_with("-" + flag.name.substr(0, length_to_match)) - || arg->text.starts_with("/" + flag.name.substr(0, length_to_match)) - || arg->text == "-" + flag.synonym - || arg->text == "/" + flag.synonym - ) - { - assert(flag.handler0 || flag.handler1); - - // If this is a standalone switch, just process it - if (flag.handler0) { - flag.handler0(); - } - - // Else - else { - // If this is a switch that could be suffixed with "-" to opt out - if (flag.opt_out) { - flag.handler1( arg->text.ends_with("-") ? "-" : "" ); - } - // Else this is a switch that takes the next arg as its value, so pass that - else { - if (arg+1 == args.end()) { - print("Missing argument to option " + arg->text + " (try -help)\n"); - help_requested = true; - } - arg->pos = processed; - ++arg; // move to next argument, which is the argument to this switch - flag.handler1(arg->text); - } - } - - arg->pos = processed; - break; - } - } - } - - std::erase_if( args, [=](auto& arg){ return arg.pos == processed; } ); - } - - auto print_help() - -> void - { - help_requested = true; - - std::sort( - flags.begin(), - flags.end(), - [](auto& a, auto& b){ return a.group < b.group || (a.group == b.group && a.name < b.name); } - ); - - print("\nUsage: cppfront [options] file ...\n\nOptions:\n"); - int last_group = -1; - for (auto& flag : flags) { - // Skip hidden flags - if (flag.name.front() == '_') { - continue; - } - - if (last_group != flag.group) { - print("\n"); - last_group = flag.group; - if (!labels[flag.group].empty()) { - print( labels[flag.group] + "\n"); - } - } - print(" -"); - auto n = flag.name.substr(0, flag.unique_prefix); - if (flag.unique_prefix < std::ssize(flag.name)) { - auto name_length = _as<int>(std::min(flag.name.find(' '), flag.name.size())); - n += "["; - n += flag.name.substr(flag.unique_prefix, name_length - flag.unique_prefix); - n += "]"; - n += flag.name.substr(name_length); - } - if (flag.opt_out) { - n += "[-]"; - } - if (!flag.synonym.empty()) { - n += ", -" + flag.synonym; - } - print(n, max_flag_length + 3); - print(flag.description); - print("\n"); - } - } - - auto add_flag( - int group, - std::string_view name, - std::string_view description, - callback0 handler0, - callback1 handler1, - std::string_view synonym, - bool opt_out - ) - -> void - { - flags.emplace_back( group, name, description, handler0, handler1, synonym, opt_out ); - auto length = std::ssize(name); - if (opt_out) { length += 3; } // space to print "[-]" - if (max_flag_length < length) { - max_flag_length = length; - } - } - struct register_flag { - register_flag( - int group, - std::string_view name, - std::string_view description, - callback0 handler0, - callback1 handler1 = {}, - std::string_view synonym = {}, - bool opt_out = false - ); - }; - - auto set_args( - int argc, - char* argv[] - ) - -> void - { - for (auto i = 1; i < argc; ++i) { - args.emplace_back( i, argv[i] ); - } - } - - auto help_was_requested() - -> bool - { - return help_requested; - } - - auto arguments() - -> std::vector<arg>& - { - return args; - } - - // This is used only by the owner of the 'main' branch - // to generate stable build version strings - auto gen_version() - -> void - { - help_requested = true; - std::string_view a = __DATE__; - std::string_view b = __TIME__; - std::unordered_map<std::string_view, char> m = { {"Jan",'1'}, {"Feb",'2'}, {"Mar",'3'}, {"Apr",'4'}, {"May",'5'}, {"Jun",'6'}, {"Jul",'7'}, {"Aug",'8'}, {"Sep",'9'}, {"Oct",'A'}, {"Nov",'B'}, {"Dec",'C'} }; - - auto stamp = std::to_string(atoi(&a[9])-15); - stamp += m[a.substr(0, 3)]; - stamp += a.substr(4,2); - stamp += ":"; - stamp += b.substr(0,2); - stamp += b.substr(3,2); - for (auto& c : stamp) { if (c == ' ') { c = '0'; } } - print( "\"" + stamp + "\""); - } - - auto print_version() - -> void - { - help_requested = true; - print("\ncppfront compiler " - #include "version.info" - " Build " - #include "build.info" - ); - print("\nCopyright(c) Herb Sutter All rights reserved\n"); - print("\nSPDX-License-Identifier: CC-BY-NC-ND-4.0"); - print("\n No commercial use"); - print("\n No forks/derivatives"); - print("\n Note: This license emphasizes that this is a personal"); - print("\n experiment; it will be upgraded if that changes\n"); - print("\nAbsolutely no warranty - try at your own risk\n"); - } - -} cmdline; - -cmdline_processor::register_flag::register_flag( - int group, - std::string_view name, - std::string_view description, - callback0 handler0, - callback1 handler1, - std::string_view synonym, - bool opt_out -) -{ - cmdline.add_flag( group, name, description, handler0, handler1, synonym, opt_out ); -} - -static cmdline_processor::register_flag cmd_help ( - 0, - "help", - "Print help", - []{ cmdline.print_help(); }, - nullptr, - "?" -); - -static cmdline_processor::register_flag cmd_version( - 0, - "version", - "Print version information", - []{ cmdline.print_version(); } -); - -static cmdline_processor::register_flag cmd_gen_version( - 0, - "_gen_version", - "Generate version information", - []{ cmdline.gen_version(); } -); - -static auto flag_internal_debug = false; -static cmdline_processor::register_flag cmd_internal_debug( - 0, - "_debug", - "Generate internal debug instrumentation", - []{ flag_internal_debug = true; } -); - - -//----------------------------------------------------------------------- -// -// Internal instrumentation -// -//----------------------------------------------------------------------- -// - -class stackinstr -{ - struct entry - { - ptrdiff_t delta; - ptrdiff_t cumulative; - std::string_view func_name; - std::string_view file; - int line; - char* ptr; - - entry( - std::string_view n, - std::string_view f, - int l, - char* p - ) - : delta { entries.empty() ? 0 : std::abs(entries.back().ptr - p) } - , cumulative{ entries.empty() ? 0 : entries.back().cumulative + delta } - , func_name { n } - , file { f } - , line { l } - , ptr { p } - { } - }; - static std::vector<entry> entries; - static std::vector<entry> deepest; - static std::vector<entry> largest; - - static auto print(auto&& ee, std::string_view label) { - std::cout << "\n=== Stack debug information: " << label << " stack ===\n"; - for (auto& e: ee) - if (e.ptr) { - std::cout - << " " << std::setw(6) - << ((std::abs(e.delta) < 1000000)? std::to_string(e.delta) : "-----") << " " - << std::setw(8) - << ((std::abs(e.delta) < 1000000)? std::to_string(e.cumulative) : "-------") << " " - << e.func_name << " (" << e.file << ":" << e.line << ")\n"; - } - } - -public: - struct guard { - guard( std::string_view name, std::string_view file, int line, char* p ) { - if (flag_internal_debug) { - entries.emplace_back(name, file, line ,p); - if (ssize(deepest) < ssize(entries)) { - deepest = entries; - } - if (largest.empty() || largest.back().cumulative < entries.back().cumulative) { - largest = entries; - } - } - } - ~guard() { - if (flag_internal_debug) { - entries.pop_back(); - } - } - }; - - static auto print_entries() { print( entries, "Current" ); } - static auto print_deepest() { print( deepest, "Deepest" ); } - static auto print_largest() { print( largest, "Largest" ); } -}; - -std::vector<stackinstr::entry> stackinstr::entries; -std::vector<stackinstr::entry> stackinstr::deepest; -std::vector<stackinstr::entry> stackinstr::largest; - -#define STACKINSTR stackinstr::guard _s_guard{ __func__, __FILE__, __LINE__, reinterpret_cast<char*>(&_s_guard) }; - - -} - -#endif diff --git a/64x0/cc2/source/cpp2util.h b/64x0/cc2/source/cpp2util.h deleted file mode 100644 index a98d92a..0000000 --- a/64x0/cc2/source/cpp2util.h +++ /dev/null @@ -1 +0,0 @@ -#include "../include/cpp2util.h" diff --git a/64x0/cc2/source/cppfront.cpp b/64x0/cc2/source/cppfront.cpp deleted file mode 100644 index 989a6ad..0000000 --- a/64x0/cc2/source/cppfront.cpp +++ /dev/null @@ -1,115 +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. - - -//=========================================================================== -// main - driver -//=========================================================================== - -#include "to_cpp1.h" - -static auto flag_debug_output = false; -static cpp2::cmdline_processor::register_flag cmd_debug( - 9, - "debug", - "Emit compiler debug output", - []{ flag_debug_output = true; } -); - -auto main( - int argc, - char* argv[] -) - -> int -{ - using namespace cpp2; - - cmdline.set_args(argc, argv); - cmdline.process_flags(); - - if (cmdline.help_was_requested()) { - return EXIT_SUCCESS; - } - - if (cmdline.arguments().empty()) { - std::cerr << "cppfront: error: no input files (try -help)\n"; - return EXIT_FAILURE; - } - - // For each Cpp2 source file - int exit_status = EXIT_SUCCESS; - for (auto const& arg : cmdline.arguments()) - { - auto& out = flag_cpp1_filename != "stdout" ? std::cout : std::cerr; - - out << arg.text << "..."; - - // Load + lex + parse + sema - cppfront c(arg.text); - - // Generate Cpp1 (this may catch additional late errors) - auto count = c.lower_to_cpp1(); - - // If there were no errors, say so and generate Cpp1 - if (c.had_no_errors()) - { - if (!c.has_cpp1()) { - out << " ok (all Cpp2, passes safety checks)\n"; - } - else if (c.has_cpp2()) { - out << " ok (mixed Cpp1/Cpp2, Cpp2 code passes safety checks)\n"; - } - else { - out << " ok (all Cpp1)\n"; - } - - if (flag_verbose) { - out << " Cpp1: " << count.cpp1_lines << " line" << (count.cpp1_lines != 1 ? "s" : ""); - out << "\n Cpp2: " << count.cpp2_lines << " line" << (count.cpp2_lines != 1 ? "s" : ""); - auto total = count.cpp1_lines + count.cpp2_lines; - if (total > 0) { - out << " ("; - if (count.cpp2_lines / count.cpp1_lines > 25) { - out << std::setprecision(3) - << 100.0 * count.cpp2_lines / total; - } - else { - out << 100 * count.cpp2_lines / total; - } - out << "%)"; - } - } - - out << "\n"; - } - // Otherwise, print the errors - else - { - std::cerr << "\n"; - c.print_errors(); - std::cerr << "\n"; - exit_status = EXIT_FAILURE; - } - - // And, if requested, the debug information - if (flag_debug_output) { - c.debug_print(); - } - } - - if (flag_internal_debug) { - stackinstr::print_deepest(); - stackinstr::print_largest(); - } - - return exit_status; -} diff --git a/64x0/cc2/source/io.h b/64x0/cc2/source/io.h deleted file mode 100644 index cf016a3..0000000 --- a/64x0/cc2/source/io.h +++ /dev/null @@ -1,1079 +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. - - -//=========================================================================== -// Source loader -//=========================================================================== - -#ifndef CPP2_IO_H -#define CPP2_IO_H - -#include "common.h" -#include <fstream> -#include <ostream> -#include <iterator> -#include <cctype> - - -namespace cpp2 { - -//--------------------------------------------------------------------------- -// move_next: advances i as long as p(line[i]) is true or the end of line -// -// line current line being processed -// i current index -// p predicate to apply -// -auto move_next( - std::string const& line, - int& i, - auto p -) - -> bool -{ - while ( - i < ssize(line) - && line[i] - && p(line[i]) - ) - { - ++i; - } - return - i < ssize(line) - && line[i] - ; -} - - -//--------------------------------------------------------------------------- -// peek_first_non_whitespace: returns the first non-whitespace char in line -// -// line current line being processed -// -auto peek_first_non_whitespace(std::string const& line) - -> char -{ - auto i = 0; - - // find first non-whitespace character - if (!move_next(line, i, isspace)) { - return '\0'; - } - - return line[i]; -} - - -//--------------------------------------------------------------------------- -// is_preprocessor: returns whether this is a preprocessor line starting -// with #, and whether it will be followed by another preprocessor line -// -// line current line being processed -// first_line whether this is supposed to be the first line (start with #) -// -struct is_preprocessor_ret { - bool is_preprocessor; - bool has_continuation; -}; -auto is_preprocessor( - std::string const& line, - bool first_line -) - -> is_preprocessor_ret -{ - // see if the first non-whitespace is # - if ( - first_line - && peek_first_non_whitespace(line) != '#' - ) - { - return { false, false }; - } - - // return true iff last character is a \ continuation - return { true, line.back() == '\\' }; -} - - -//--------------------------------------------------------------------------- -// starts_with_import: returns whether the line starts with "import" -// -// line current line being processed -// -auto starts_with_import(std::string const& line) - -> bool -{ - auto i = 0; - - // find first non-whitespace character - if (!move_next(line, i, isspace)) { - return false; - } - - static constexpr auto import_keyword = std::string_view{"import"}; - - // the first token must begin with 'import' - if (!std::string_view(line).substr(i).starts_with(import_keyword)) { - return false; - } - - // and not be immediately followed by an _identifier-continue_ - return !is_identifier_continue(line[i + import_keyword.size()]); -} - - -//--------------------------------------------------------------------------- -// starts_with_whitespace_slash_slash: is this a "// comment" line -// -// line current line being processed -// -auto starts_with_whitespace_slash_slash(std::string const& line) - -> bool -{ - auto i = 0; - - // find first non-whitespace character - if (!move_next(line, i, isspace)) { - return false; - } - - return - i < ssize(line)-1 - && line[i] == '/' - && line[i+1] == '/' - ; -} - - -//--------------------------------------------------------------------------- -// starts_with_whitespace_slash_star_and_no_star_slash: is this a "/* comment" line -// -// line current line being processed -// -auto starts_with_whitespace_slash_star_and_no_star_slash(std::string const& line) - -> bool -{ - auto i = 0; - - // find first non-whitespace character - if (!move_next(line, i, isspace)) { - return false; - } - - if ( - i < ssize(line) - 1 - && line[i] == '/' - && line[i + 1] == '*' - ) - { - return line.find("*/", i) == std::string::npos; - } - else { - return false; - } -} - - -//--------------------------------------------------------------------------- -// starts_with_operator: returns whether the line starts with the string "operator" -// followed by the symbols of an operator -// -// line current line being processed -// -auto starts_with_operator(std::string_view s) - -> int -{ - if (s.starts_with("operator")) - { - auto j = 8; - - // skip any spaces - while ( - j < std::ssize(s) - && isspace(s[j]) - ) - { - ++j; - } - if (j >= std::ssize(s)) { - return 0; - } - - auto c1 = [&]{ if (j < std::ssize(s)) { return s[j ]; } return '\0'; }(); - auto c2 = [&]{ if (j+1 < std::ssize(s)) { return s[j+1]; } return '\0'; }(); - auto c3 = [&]{ if (j+2 < std::ssize(s)) { return s[j+2]; } return '\0'; }(); - - switch (c1) - { - // /= / - // == = - // ! != - // *= * - // %= % - // ^= ^ - // ~= ~ - break;case '/': - case '=': - case '!': - case '*': - case '%': - case '^': - case '~': - if (c2 == '=') { return j+2; } - return j+1; - - // ++ += + - break;case '+': - if (c2 == '=' || c2 == '+') { return j+2; } - return j+1; - - // -- -= -> - - break;case '-': - if (c2 == '=' || c2 == '-' || c2 == '>') { return j+2; } - return j+1; - - // ||= || |= | - // &&= && &= & - break;case '|': - case '&': - if (c2 == c1 && c3 == '=') { return j+3; } - if (c2 == c1 || c2 == '=') { return j+2; } - return j+1; - - // >>= >> >= > - break;case '>': - if (c2 == '>' && c3 == '=') { return j + 3; } - if (c2 == '>' || c2 == '=') { return j + 2; } - return j+1; - - // <<= << <=> <= < - break;case '<': - if (c2 == '<' && c3 == '=') { return j + 3; } - if (c2 == '=' && c3 == '>') { return j + 3; } - if (c2 == '<' || c2 == '=') { return j + 2; } - return j+1; - - break;default: - ; - } - } - - return 0; -} - -//--------------------------------------------------------------------------- -// starts_with_identifier_colon: returns whether the line starts with an -// identifier followed by one colon (not ::) (possibly preceded by an access specifier) -// -// line current line being processed -// -auto starts_with_identifier_colon(std::string const& line) - -> bool -{ - auto i = 0; - - // find first non-whitespace character - if (!move_next(line, i, isspace)) { - return false; - } - - // see if it's an access-specifier - auto s = std::string_view( &line[i], std::ssize(line) - i ); - auto j = 0; - assert (!isspace(s[j])); - if (s.starts_with("public")) { - j += 6; - } - else if (s.starts_with("protected")) { - j += 9; - } - else if (s.starts_with("private")) { - j += 7; - } - while ( - j < std::ssize(s) - && isspace(s[j]) - ) - { - ++j; - } - s.remove_prefix(j); - i += j; - - // see if it's an "operator @" name - j = starts_with_operator(s); - // else see if it's a single identifier - if (j == 0) { - j = starts_with_identifier(s); - } - // if it's neither, bail - if (j == 0) { - return false; - } - i += j; - - if (!move_next(line, i, isalnum)) { - return false; - } - - // find first non-whitespace character - if (!move_next(line, i, isspace)) { - return false; - } - - // it's Cpp2 iff what's here is : not followed by another : - // (e.g., not a Cpp1 "using ::something") - assert (i < ssize(line)); - return - line[i] == ':' - && (i == ssize(line)-1 || line[i+1] != ':') - ; -} - - -//--------------------------------------------------------------------------- -// braces_tracker: to track brace depth -// -// Normally we don't emit diagnostics for Cpp1 code, but we do for a -// brace mismatch since we're relying on balanced {()} to find Cpp2 code -// -class braces_tracker -{ - // to track preprocessor #if brace depth and brace counts - // - class pre_if_depth_info - { - int if_net_braces = 0; - bool found_else = false; - int else_net_braces = 0; - - public: - auto found_open_brace() -> void { - if (!found_else) { ++if_net_braces; } - else { ++else_net_braces; } - } - - auto found_close_brace() -> void { - if (!found_else) { --if_net_braces; } - else { --else_net_braces; } - } - - auto found_preprocessor_else() -> void { - assert (!found_else); - found_else = true; - } - - // If the "if" and "else" branches opened/closed the same net number - // of unbalanced braces, they were double-counted in the brace - // matching and to try to keep going we can apply this adjustment - auto braces_to_ignore() -> int { - if ( - if_net_braces >= 0 - && if_net_braces == else_net_braces - ) - { - return if_net_braces; - } - else { - return 0; - } - } - }; - std::vector<pre_if_depth_info> preprocessor = { {} }; // sentinel - char current_open_type = ' '; - std::vector<lineno_t> open_braces; - std::vector<error_entry>& errors; - -public: - braces_tracker( std::vector<error_entry>& errors ) - : errors{errors} - { } - - // --- Brace matching functions - { and }, or ( and ) - - auto found_open_brace(lineno_t lineno, char brace) -> void { - assert(std::ssize(preprocessor) > 0); - if (open_braces.empty()) { - current_open_type = brace; - } - if (current_open_type == brace) { - open_braces.push_back(lineno); - preprocessor.back().found_open_brace(); - } - } - - auto found_close_brace(source_position pos, char brace) -> void { - assert(std::ssize(preprocessor) > 0); - - if ( - (current_open_type == '{' && brace == '}') - || (current_open_type == '(' && brace == ')') - ) - { - if (std::ssize(open_braces) < 1) { - errors.emplace_back( - pos, - "closing } does not match a prior {" - ); - } - else { - open_braces.pop_back(); - } - - preprocessor.back().found_close_brace(); - } - } - - auto found_eof(source_position pos) const -> void { - // Emit diagnostic if braces didn't match - // - if (current_depth() != 0) { - std::string unmatched_brace_lines; - for (auto i = 0; i < std::ssize(open_braces); ++i) { - if (i > 0 && std::size(open_braces)>2) { unmatched_brace_lines += ","; }; - if (i > 0 && i == std::ssize(open_braces)-1) { unmatched_brace_lines += " and"; }; - unmatched_brace_lines += " " + std::to_string(open_braces[i]); - } - errors.emplace_back( - pos, - std::string("end of file reached with ") - + std::to_string(current_depth()) - + " missing } to match earlier { on line" - + (current_depth() > 1 ? "s" : "") - + unmatched_brace_lines - ); - } - } - - auto current_depth() const -> int { - return std::ssize(open_braces); - } - - // --- Preprocessor matching functions - #if/#else/#endif - - // Entering an #if - auto found_pre_if() -> void { - assert(std::ssize(preprocessor) > 0); - preprocessor.push_back({}); - } - - // Encountered an #else - auto found_pre_else() -> void { - assert(std::ssize(preprocessor) > 1); - preprocessor.back().found_preprocessor_else(); - } - - // Exiting an #endif - auto found_pre_endif() -> void { - assert(std::ssize(preprocessor) > 1); - - // If the #if/#else/#endif introduced the same net number of braces, - // then we will have recorded that number too many open braces, and - // braces_to_ignore() will be the positive number of those net open braces - // that this loop will now throw away - for (auto i = 0; i < preprocessor.back().braces_to_ignore(); ++i) { - found_close_brace( source_position{}, current_open_type == '{' ? '}' : ')' ); - } - - preprocessor.pop_back(); - } -}; - - -//--------------------------------------------------------------------------- -// starts_with_preprocessor_if_else_endif: the line starts with a preprocessor conditional -// -// line current line being processed -// -enum class preprocessor_conditional { - none = 0, pre_if, pre_else, pre_endif -}; -auto starts_with_preprocessor_if_else_endif( - std::string const& line -) - -> preprocessor_conditional -{ - auto i = 0; - - // find first non-whitespace character - if (!move_next(line, i, isspace)) { - return preprocessor_conditional::none; - } - - // if it's not #, this isn't an #if/#else/#endif - if (line[i] != '#') { - return preprocessor_conditional::none; - } - - // find next non-whitespace character - ++i; - if (!move_next(line, i, isspace)) { - return preprocessor_conditional::none; - } - - if (line.substr(i).starts_with("if")) { - return preprocessor_conditional::pre_if; - } - else if (line.substr(i).starts_with("else")) { - return preprocessor_conditional::pre_else; - } - else if (line.substr(i).starts_with("endif")) { - return preprocessor_conditional::pre_endif; - } - else { - return preprocessor_conditional::none; - } -} - - -//--------------------------------------------------------------------------- -// process_cpp_line: just enough to know what to skip over -// -// line current line being processed -// in_comment track whether we're in a comment -// in_string_literal track whether we're in a string literal -// -struct process_line_ret { - bool all_comment_line; - bool empty_line; - bool all_rawstring_line; -}; -auto process_cpp_line( - std::string const& line, - bool& in_comment, - bool& in_string_literal, - bool& in_raw_string_literal, - std::string& raw_string_closing_seq, - braces_tracker& braces, - lineno_t lineno -) - -> process_line_ret -{ - if ( - !in_comment - && !in_string_literal - && !in_raw_string_literal - ) - { - if (starts_with_whitespace_slash_slash(line)) { - return { true, false, false }; - } - else if (starts_with_whitespace_slash_star_and_no_star_slash(line)) { - in_comment = true; - return { true, false, false }; - } - } - - struct process_line_ret r { in_comment, true , in_raw_string_literal}; - - auto prev = ' '; - auto prev2 = ' '; - for (auto i = colno_t{0}; i < ssize(line); ++i) - { - // Local helper functions for readability - // Note: in_literal is for { and } and so doesn't have to work for escaped ' characters - // - auto peek = [&](int num) { return (i+num < std::ssize(line)) ? line[i+num] : '\0'; }; - auto in_literal = [&] { return in_string_literal || in_raw_string_literal || (prev == '\'' && peek(1) == '\''); }; - - // Process this source character - // - if (!isspace(line[i])) { - r.empty_line = false; - } - - if ( - in_comment - && !in_string_literal - && !in_raw_string_literal - ) - { - switch (line[i]) { - break;case '/': if (prev == '*') { in_comment = false; } - break;default: ; - } - } - else if (in_raw_string_literal) { - auto end_pos = line.find(raw_string_closing_seq, i); - if (end_pos == std::string::npos) { - return r; - } - in_raw_string_literal = false; - i = end_pos+raw_string_closing_seq.size()-1; - } - else { - r.all_comment_line = false; - r.all_rawstring_line = false; - switch (line[i]) { - break;case 'R': - if ( - !in_comment - && !in_string_literal - && !in_raw_string_literal - && peek(1) == '"' - ) - { - i+=2; - if (i < ssize(line) - 1) - { - if (auto paren_pos = line.find("(", i); - paren_pos != std::string::npos - ) - { - raw_string_closing_seq = ")"+line.substr(i, paren_pos-i)+"\""; - in_raw_string_literal = true; - } - } - } - - break;case '\"': - // If this isn't an escaped quote, toggle string literal state - if ( - !in_comment - && (prev != '\\' || prev2 == '\\') - && (in_string_literal || prev != '\'') - && !in_raw_string_literal - ) - { - in_string_literal = !in_string_literal; - } - - break;case '{': - if (!in_literal()) { - braces.found_open_brace(lineno, '{'); - } - - break;case '}': - if (!in_literal()) { - braces.found_close_brace(source_position(lineno, i), '}'); - } - - break;case '*': - if ( - !in_string_literal - && !in_raw_string_literal - && prev == '/' - ) - { - in_comment = true; - } - - break;case '/': - if ( - !in_string_literal - && !in_raw_string_literal - && prev == '/' - ) - { - in_comment = false; - return r; - } - - break;default: ; - } - } - - prev2 = prev; - prev = line[i]; - } - - return r; -} - - -//--------------------------------------------------------------------------- -// process_cpp2_line: to find the end of a Cpp2 definition -// - find first of ; and { -// - if ; we're done -// - if { find matching } -// - then there must be nothing else on the last line -// -// line current line being processed -// in_comment whether this line begins inside a multi-line comment -// -// Returns: whether additional lines should be inspected -// -auto process_cpp2_line( - std::string const& line, - bool& in_comment, - braces_tracker& braces, - lineno_t lineno, - std::vector<error_entry>& errors -) - -> bool -{ - auto found_end = false; - - auto prev = ' '; - auto prev2 = ' '; - auto in_string_literal = false; - auto in_char_literal = false; - - for (auto i = colno_t{0}; i < ssize(line); ++i) - { - if (in_comment) - { - switch (line[i]) { - break;case '/': if (prev == '*') { in_comment = false; } - break;default: ; - } - } - else if (in_string_literal) - { - switch (line[i]) { - break;case '"': if (prev != '\\' || prev2 == '\\') { in_string_literal = false; } - break;default: ; - } - } - else if (in_char_literal) - { - switch (line[i]) { - break;case '\'': if (prev != '\\' || prev2 == '\\') { in_char_literal = false; } - break;default: ; - } - } - else - { - switch (line[i]) - { - // For finding Cpp2 definition endings, count () as {} - break;case '{': - case '(': - braces.found_open_brace( lineno, line[i] ); - - break;case '}': - case ')': - braces.found_close_brace( source_position(lineno, i), line[i]); - if (braces.current_depth() < 1 && line[i] != ')') { - found_end = true; - } - - break;case ';': - if (braces.current_depth() < 1) { found_end = true; } - - break;case '*': - if (prev == '/') { - in_comment = true; - if (found_end) { - errors.emplace_back( - source_position(lineno, i), - std::string("alpha limitation:" - " after the closing ; or } of a definition, the rest" - " of the line cannot begin a /*...*/ comment") - ); - } - } - - break;case '/': - if (prev == '/') { in_comment = false; return found_end; } - - break;case '"': - if (prev != '\\' || prev2 == '\\') { in_string_literal = true; } - - break;case '\'': - if (prev != '\\' || prev2 == '\\') { - // Also check that this isn't a digit separator - in_char_literal = !is_hexadecimal_digit(prev); - } - - break;default: ; - } - } - - prev2 = prev; - prev = line[i]; - } - - if (in_char_literal) { - errors.emplace_back( - source_position(lineno, ssize(line)), - std::string("line ended before character literal was terminated") - ); - } - - return found_end; -} - - -//----------------------------------------------------------------------- -// -// source: Represents a program source file -// -//----------------------------------------------------------------------- -// -class source -{ - std::vector<error_entry>& errors; - std::vector<source_line> lines; - bool cpp1_found = false; - bool cpp2_found = false; - - static const int max_line_len = 90'000; - // do not reduce this - I encountered an 80,556-char - // line in real world code during testing - char buf[max_line_len]; - -public: - //----------------------------------------------------------------------- - // Constructor - // - // errors error list - // - source( - std::vector<error_entry>& errors_ - ) - : errors{ errors_ } - , lines( 1 ) // extra blank to avoid off-by-one everywhere - , buf{0} - { - } - - - //----------------------------------------------------------------------- - // has_cpp1: Returns true if this file has some Cpp1/preprocessor lines - // (note: import lines don't count toward Cpp1 or Cpp2) - // - auto has_cpp1() const -> bool { - return cpp1_found; - } - - - //----------------------------------------------------------------------- - // has_cpp2: Returns true if this file has some Cpp2 lines - // (note: import lines don't count toward Cpp1 or Cpp2) - // - auto has_cpp2() const -> bool { - return cpp2_found; - } - - - //----------------------------------------------------------------------- - // load: Read a line-by-line view of 'filename', preserving line breaks - // - // filename the source file to be loaded - // source program textual representation - // - auto load( - std::string const& filename - ) - -> bool - { - std::ifstream in{ filename }; - if (!in.is_open()) { - return false; - } - - auto in_comment = false; - auto in_string_literal = false; - auto in_raw_string_literal = false; - std::string raw_string_closing_seq; - - auto braces = braces_tracker(errors); - - auto add_preprocessor_line = [&] { - lines.push_back({ &buf[0], source_line::category::preprocessor }); - if (auto pre = starts_with_preprocessor_if_else_endif(lines.back().text); - pre != preprocessor_conditional::none - ) - { - switch (pre) { - break;case preprocessor_conditional::pre_if: - braces.found_pre_if(); - break;case preprocessor_conditional::pre_else: - braces.found_pre_else(); - break;case preprocessor_conditional::pre_endif: - braces.found_pre_endif(); - break;default: - assert(false); - } - } - }; - - while (in.getline(&buf[0], max_line_len)) { - - // Handle preprocessor source separately, they're outside the language - // - if (auto pre = is_preprocessor(buf, true); - pre.is_preprocessor - && !in_comment - && !in_raw_string_literal - ) - { - cpp1_found = true; - add_preprocessor_line(); - while ( - pre.has_continuation - && in.getline(&buf[0], max_line_len) - ) - { - add_preprocessor_line(); - pre = is_preprocessor(buf, false); - } - } - - else - { - lines.push_back({ &buf[0], source_line::category::cpp1 }); - - // Switch to cpp2 mode if we're not in a comment, not inside nested { }, - // and the line starts with "nonwhitespace :" but not "::" - // - if (!in_comment - && !in_raw_string_literal - && braces.current_depth() < 1 - && starts_with_identifier_colon(lines.back().text) - ) - { - cpp2_found= true; - - // Mark this line, and preceding comment/blank source, as cpp2 - lines.back().cat = source_line::category::cpp2; - if (std::ssize(lines) > 1) { - auto prev = --std::end(lines); - while ( - --prev != std::begin(lines) - && (prev->cat == source_line::category::empty - || prev->cat == source_line::category::comment) - ) - { - prev->cat = source_line::category::cpp2; - } - } - - // Find the end of the definition: - while ( - !process_cpp2_line( - lines.back().text, - in_comment, - braces, - std::ssize(lines)-1, - errors - ) - && in.getline(&buf[0], max_line_len) - ) - { - lines.push_back({ &buf[0], source_line::category::cpp2 }); - } - } - - // Else still in Cpp1 code, but could be a comment, empty, or import - // - else - { - if (starts_with_import(lines.back().text)) { - lines.back().cat = source_line::category::import; - } - else { - auto stats = process_cpp_line( - lines.back().text, - in_comment, - in_string_literal, - in_raw_string_literal, - raw_string_closing_seq, - braces, - std::ssize(lines) - 1 - ); - if (stats.all_comment_line) { - lines.back().cat = source_line::category::comment; - } - else if (stats.all_rawstring_line) { - lines.back().cat = source_line::category::rawstring; - } - else if (stats.empty_line) { - lines.back().cat = source_line::category::empty; - } - else { - cpp1_found = true; - } - } - } - - } - } - - // Because I encountered very long lines in real-world code during testing - // - if (in.gcount() >= max_line_len-1) - { - errors.emplace_back( - source_position(lineno_t(std::ssize(lines)), 0), - std::string("source line too long - length must be less than ") - + std::to_string(max_line_len) - ); - return false; - } - - // This shouldn't be possible, so check it anyway - // - if (!in.eof()) - { - errors.emplace_back( - source_position(lineno_t(std::ssize(lines)), 0), - std::string("unexpected error reading source lines - did not reach EOF"), - false, - true // a noisy fallback error - ); - return false; - } - - braces.found_eof( source_position(lineno_t(std::ssize(lines)), 0) ); - - return true; - } - - - //----------------------------------------------------------------------- - // get_lines: Access the source lines - // - auto get_lines() -> std::vector<source_line>& - { - return lines; - } - - auto get_lines() const -> std::vector<source_line> const& - { - return lines; - } - - //----------------------------------------------------------------------- - // debug_print - // - auto debug_print(std::ostream& o) const -> void - { - for (auto lineno = 0; auto const& line : lines) { - // Skip dummy first entry - if (lineno > 0) { - if (line.all_tokens_are_densely_spaced) { - o << "+"; - } - else { - o << " "; - } - o << line.prefix() << line.text << '\n'; - } - ++lineno; - } - } - - // No copying - // - source(source const&) = delete; - source& operator=(source const&) = delete; - source(source&&) = delete; - source& operator=(source&&) = delete; -}; - -} - -#endif diff --git a/64x0/cc2/source/lex.h b/64x0/cc2/source/lex.h deleted file mode 100644 index 478fe3d..0000000 --- a/64x0/cc2/source/lex.h +++ /dev/null @@ -1,1989 +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. - - -//=========================================================================== -// Lexer -//=========================================================================== - -#ifndef CPP2_LEX_H -#define CPP2_LEX_H - -#include "io.h" -#include <map> -#include <climits> -#include <deque> -#include <cstring> - - -namespace cpp2 { - -//----------------------------------------------------------------------- -// -// lexeme: represents the type of a token -// -//----------------------------------------------------------------------- -// - -enum class lexeme : std::int8_t { - SlashEq, - Slash, - LeftShiftEq, - LeftShift, - Spaceship, - LessEq, - Less, - RightShiftEq, - RightShift, - GreaterEq, - Greater, - PlusPlus, - PlusEq, - Plus, - MinusMinus, - MinusEq, - Arrow, - Minus, - LogicalOrEq, - LogicalOr, - PipeEq, - Pipe, - LogicalAndEq, - LogicalAnd, - MultiplyEq, - Multiply, - ModuloEq, - Modulo, - AmpersandEq, - Ampersand, - CaretEq, - Caret, - TildeEq, - Tilde, - EqualComparison, - Assignment, - NotEqualComparison, - Not, - LeftBrace, - RightBrace, - LeftParen, - RightParen, - LeftBracket, - RightBracket, - Scope, - Colon, - Semicolon, - Comma, - Dot, - Ellipsis, - QuestionMark, - At, - Dollar, - FloatLiteral, - BinaryLiteral, - DecimalLiteral, - HexadecimalLiteral, - StringLiteral, - CharacterLiteral, - UserDefinedLiteralSuffix, - Keyword, - Cpp1MultiKeyword, - Cpp2FixedType, - Identifier, - None = 127 -}; - -auto is_literal(lexeme l) -> bool { - switch (l) { - break;case lexeme::FloatLiteral: - case lexeme::BinaryLiteral: - case lexeme::DecimalLiteral: - case lexeme::HexadecimalLiteral: - case lexeme::StringLiteral: - case lexeme::CharacterLiteral: return true; - break;default: return false; - } -} - -auto close_paren_type(lexeme l) - -> lexeme -{ - switch (l) { - break;case lexeme::LeftBrace: return lexeme::RightBrace; - break;case lexeme::LeftBracket: return lexeme::RightBracket; - break;case lexeme::LeftParen: return lexeme::RightParen; - break;default: return lexeme::None; - } -} - - -template<typename T> - requires std::is_same_v<T, std::string> -auto _as(lexeme l) - -> std::string -{ - switch (l) { - break;case lexeme::SlashEq: return "SlashEq"; - break;case lexeme::Slash: return "Slash"; - break;case lexeme::LeftShiftEq: return "LeftShiftEq"; - break;case lexeme::LeftShift: return "LeftShift"; - break;case lexeme::Spaceship: return "Spaceship"; - break;case lexeme::LessEq: return "LessEq"; - break;case lexeme::Less: return "Less"; - break;case lexeme::RightShiftEq: return "RightShiftEq"; - break;case lexeme::RightShift: return "RightShift"; - break;case lexeme::GreaterEq: return "GreaterEq"; - break;case lexeme::Greater: return "Greater"; - break;case lexeme::PlusPlus: return "PlusPlus"; - break;case lexeme::PlusEq: return "PlusEq"; - break;case lexeme::Plus: return "Plus"; - break;case lexeme::MinusMinus: return "MinusMinus"; - break;case lexeme::MinusEq: return "MinusEq"; - break;case lexeme::Arrow: return "Arrow"; - break;case lexeme::Minus: return "Minus"; - break;case lexeme::LogicalOrEq: return "LogicalOrEq"; - break;case lexeme::LogicalOr: return "LogicalOr"; - break;case lexeme::PipeEq: return "PipeEq"; - break;case lexeme::Pipe: return "Pipe"; - break;case lexeme::LogicalAndEq: return "LogicalAndEq"; - break;case lexeme::LogicalAnd: return "LogicalAnd"; - break;case lexeme::MultiplyEq: return "MultiplyEq"; - break;case lexeme::Multiply: return "Multiply"; - break;case lexeme::ModuloEq: return "ModuloEq"; - break;case lexeme::Modulo: return "Modulo"; - break;case lexeme::AmpersandEq: return "AmpersandEq"; - break;case lexeme::Ampersand: return "Ampersand"; - break;case lexeme::CaretEq: return "CaretEq"; - break;case lexeme::Caret: return "Caret"; - break;case lexeme::TildeEq: return "TildeEq"; - break;case lexeme::Tilde: return "Tilde"; - break;case lexeme::EqualComparison: return "EqualComparison"; - break;case lexeme::Assignment: return "Assignment"; - break;case lexeme::NotEqualComparison: return "NotEqualComparison"; - break;case lexeme::Not: return "Not"; - break;case lexeme::LeftBrace: return "LeftBrace"; - break;case lexeme::RightBrace: return "RightBrace"; - break;case lexeme::LeftParen: return "LeftParen"; - break;case lexeme::RightParen: return "RightParen"; - break;case lexeme::LeftBracket: return "LeftBracket"; - break;case lexeme::RightBracket: return "RightBracket"; - break;case lexeme::Scope: return "Scope"; - break;case lexeme::Colon: return "Colon"; - break;case lexeme::Semicolon: return "Semicolon"; - break;case lexeme::Comma: return "Comma"; - break;case lexeme::Dot: return "Dot"; - break;case lexeme::Ellipsis: return "Ellipsis"; - break;case lexeme::QuestionMark: return "QuestionMark"; - break;case lexeme::At: return "At"; - break;case lexeme::Dollar: return "Dollar"; - break;case lexeme::FloatLiteral: return "FloatLiteral"; - break;case lexeme::BinaryLiteral: return "BinaryLiteral"; - break;case lexeme::DecimalLiteral: return "DecimalLiteral"; - break;case lexeme::HexadecimalLiteral: return "HexadecimalLiteral"; - break;case lexeme::StringLiteral: return "StringLiteral"; - break;case lexeme::CharacterLiteral: return "CharacterLiteral"; - break;case lexeme::UserDefinedLiteralSuffix: return "UserDefinedLiteralSuffix"; - break;case lexeme::Keyword: return "Keyword"; - break;case lexeme::Cpp1MultiKeyword: return "Cpp1MultiKeyword"; - break;case lexeme::Cpp2FixedType: return "Cpp2FixedType"; - break;case lexeme::Identifier: return "Identifier"; - break;case lexeme::None: return "(NONE)"; - break;default: return "INTERNAL-ERROR"; - } -}; - - -auto is_operator(lexeme l) - -> bool -{ - return l <= lexeme::Not; -} - - -//----------------------------------------------------------------------- -// -// token: represents a single token -// -// Note: by reference, thge test into the program's source lines -// -//----------------------------------------------------------------------- -// -class token -{ -public: - token( - char const* start, - auto count, - source_position pos, - lexeme type - ) - : sv {start, unsafe_narrow<ulong>(count)} - , pos {pos} - , lex_type{type} - { - } - - token( - char const* sz, - source_position pos, - lexeme type - ) - : sv {sz} - , pos {pos} - , lex_type{type} - { - } - - auto as_string_view() const - -> std::string_view - { - assert (sv.data()); - return sv; - } - - operator std::string_view() const - { - return as_string_view(); - } - - auto operator== (token const& t) const - -> bool - { - return operator std::string_view() == t.operator std::string_view(); - } - - auto operator== (std::string_view s) const - -> bool - { - return s == this->operator std::string_view(); - } - - auto to_string() const - -> std::string - { - return std::string{sv}; - } - - friend auto operator<< (auto& o, token const& t) - -> auto& - { - return o << std::string_view(t); - } - - auto position_col_shift( colno_t offset ) - -> void - { - assert (pos.colno + offset > 0); - pos.colno += offset; - } - - auto position() const -> source_position { return pos; } - - auto length () const -> int { return sv.size(); } - - auto type () const -> lexeme { return lex_type; } - - auto set_type(lexeme l) -> void { lex_type = l; } - - auto visit(auto& v, int depth) const - -> void - { - v.start(*this, depth); - } - - auto remove_prefix_if(std::string_view prefix) { - if ( - sv.size() > prefix.size() - && sv.starts_with(prefix) - ) - { - sv.remove_prefix(prefix.size()); - pos.colno += prefix.size(); - } - } - -private: - std::string_view sv; - source_position pos; - lexeme lex_type; -}; - -static_assert (CHAR_BIT == 8); - - -auto labelized_position(token const* t) - -> std::string -{ - auto ret = std::string{}; - if (t) { - ret += - std::to_string(t->position().lineno) + - "_" + - std::to_string(t->position().colno); - } - return ret; -} - - -//----------------------------------------------------------------------- -// -// A StringLiteral could include captures -// -auto expand_string_literal( - std::string_view text, - std::vector<error_entry>& errors, - source_position src_pos -) - -> std::string -{ - auto const length = std::ssize(text); - - assert(length >= 2); - if (text.back() != '"') { - errors.emplace_back( - source_position( src_pos ), - "not a legal string literal", - false, - true // a noisy fallback error message - ); - return {}; - } - - auto pos = 0; - - // Skip prefix to first non-" character - while ( - pos < length - && text[pos] != '"' - ) - { - ++pos; - } - assert( - pos < length - && text[pos] == '"' - ); - ++pos; - auto current_start = pos; // the current offset before which the string has been added to ret - - auto parts = string_parts{std::string(text.substr(0, current_start)), // begin sequence ", U", u8" depends on the string type - "\"", // end sequence - string_parts::on_both_ends}; // add opening and closing sequence to generated string - - bool escape = false; - // Now we're on the first character of the string itself - for ( - ; - pos < length && !(!escape && text[pos] == '"'); - ++pos - ) - { - escape = (text[pos] == '\\' && !escape); - // Find the next )$ - if ( - text[pos] == '$' - && text[pos-1] == ')' - ) - { - // Scan back to find the matching ( - auto paren_depth = 1; - auto open = pos - 2; - - for( ; open > current_start; --open) - { - if (text[open] == ')') { - ++paren_depth; - } - else if (text[open] == '(') { - --paren_depth; - if (paren_depth == 0) { - break; - } - } - } - if (text[open] != '(') - { - errors.emplace_back( - source_position( src_pos.lineno, src_pos.colno + pos ), - "no matching ( for string interpolation ending in )$" - ); - return {}; - } - - // 'open' is now at the matching ( - - // Put the next non-empty non-interpolated chunk straight into ret - if (open != current_start) { - parts.add_string(text.substr(current_start, open - current_start)); - } - - // Then put interpolated chunk into ret - auto chunk = std::string{text.substr(open, pos - open)}; - { // unescape chunk string - auto last_it = std::remove_if( - std::begin(chunk), - std::end(chunk), - [escape = false, prev = ' '](const auto& e) mutable { - escape = !escape && prev != '\'' && e == '\\'; - prev = e; - return escape; - } - ); - chunk.erase(last_it, std::end(chunk)); - } - - // This chunk string is now in the form "(some_capture_text)", - // which might include a :formatter suffix like "(capture_text:formatter)" - - if (std::ssize(chunk) < 1) - { - errors.emplace_back( - source_position( src_pos.lineno, src_pos.colno + pos ), - "string interpolation must not be empty" - ); - return {}; - } - if (chunk.ends_with(':')) - { - errors.emplace_back( - source_position( src_pos.lineno, src_pos.colno + pos ), - "string interpolation ':' must be followed by a std::formatter specifier" - ); - return {}; - } - - // If there's a :formatter suffix, decorate it as: ,"{:formatter}" - if (auto colon = chunk.find_last_of(':'); - colon != chunk.npos - && chunk[colon-1] != ':' // ignore :: scope resolution - ) - { - chunk.insert(colon, ",\"{"); - chunk.insert(chunk.size()-1, "}\""); - } - - parts.add_code("cpp2::to_string" + chunk); - - current_start = pos+1; - } - } - - // Now we should be on the the final " closing the string - assert( - pos == length-1 - && text[pos] == '"' - ); - - // Put the final non-interpolated chunk straight into ret - if (current_start < std::ssize(text)-1) { - parts.add_string(text.substr(current_start, std::ssize(text)-current_start-1)); - } - - return parts.generate(); -} - -auto expand_raw_string_literal( - const std::string& opening_seq, - const std::string& closing_seq, - string_parts::adds_sequences closing_strategy, - std::string_view text, - std::vector<error_entry>& errors, - source_position src_pos -) - -> string_parts -{ - auto const length = std::ssize(text); - auto pos = 0; - auto current_start = pos; // the current offset before which the string has been added to ret - string_parts parts{opening_seq, closing_seq, closing_strategy}; - - // Now we're on the first character of the string itself - for ( ; pos < length; ++pos ) - { - // Find the next )$ - if (text[pos] == '$' && text[pos-1] == ')') - { - // Scan back to find the matching ( - auto paren_depth = 1; - auto open = pos - 2; - - for( ; open > current_start; --open) - { - if (text[open] == ')') { - ++paren_depth; - } - else if (text[open] == '(') { - --paren_depth; - if (paren_depth == 0) { - break; - } - } - } - if (text[open] != '(') - { - errors.emplace_back( - source_position( src_pos.lineno, src_pos.colno + pos ), - "no matching ( for string interpolation ending in )$" - ); - return parts; - } - - // 'open' is now at the matching ( - - // Put the next non-empty non-interpolated chunk straight into ret - if (open != current_start) { - parts.add_string(text.substr(current_start, open - current_start)); - } - // Then put interpolated chunk into ret - parts.add_code("cpp2::to_string" + std::string{text.substr(open, pos - open)}); - - current_start = pos+1; - } - } - - // Put the final non-interpolated chunk straight into ret - if (current_start < std::ssize(text)) { - parts.add_string(text.substr(current_start)); - } - - return parts; -} - -//----------------------------------------------------------------------- -// lex: Tokenize a single line while maintaining inter-line state -// -// mutable_line the line to be tokenized -// lineno the current line number -// in_comment are we currently in a comment -// current_comment the current partial comment -// current_comment_start the current comment's start position -// tokens the token list to add to -// comments the comment token list to add to -// errors the error message list to use for reporting problems -// raw_string_multiline the current optional raw_string state -// - -// A stable place to store additional text for source tokens that are merged -// into a whitespace-containing token (to merge the Cpp1 multi-token keywords) -// -- this isn't about tokens generated later, that's tokens::generated_tokens -static auto generated_text = std::deque<std::string>{}; -static auto generated_lines = std::deque<std::vector<source_line>>{}; - - -static auto multiline_raw_strings = std::deque<multiline_raw_string>{}; - -auto lex_line( - std::string& mutable_line, - int const lineno, - bool& in_comment, - std::string& current_comment, - source_position& current_comment_start, - std::vector<token>& tokens, - std::vector<comment>& comments, - std::vector<error_entry>& errors, - std::optional<raw_string>& raw_string_multiline -) - -> bool -{ - auto const& line = mutable_line; // most accesses will be const, so give that the nice name - - auto original_size = std::ssize(tokens); - - auto i = colno_t{0}; - - // Token merging helpers - // - auto merge_cpp1_multi_token_fundamental_type_names = [&] - { - // If the last token is a non-Cpp1MultiKeyword, we might be at the end - // of a sequence of Cpp1MultiKeyword tokens that need to be merged - - // First, check the last token... only proceed if it is NOT one of those - auto i = std::ssize(tokens)-1; - if ( - i < 0 - || tokens[i].type() == lexeme::Cpp1MultiKeyword - ) - { - return; - } - - // Next, check the two tokens before that... only proceed if they ARE those - --i; - if ( - i < 1 - || tokens[i].type() != lexeme::Cpp1MultiKeyword - || tokens[i-1].type() != lexeme::Cpp1MultiKeyword - ) - { - // If this is just one such token, changed its type to regular ::Keyword - if ( - i >= 0 - && tokens[i].type() == lexeme::Cpp1MultiKeyword - ) - { - tokens[i].set_type( lexeme::Keyword ); - } - return; - } - - // OK, we have found the end of a sequence of 1 or more Cpp1MultiKeywords, so - // replace them with a single synthesized token that contains all their text - // - // Note: It's intentional that this is a kind of token that can contain whitespace - - // Remember the last (non-Cpp1MultiKeyword) token so we can put it back - auto last_token = tokens.back(); - tokens.pop_back(); - - assert(tokens.back().type() == lexeme::Cpp1MultiKeyword); - auto pos = tokens.back().position(); - - auto num_merged_tokens = 0; - auto is_char = 0; - auto is_short = 0; - auto is_int = 0; - auto is_long = 0; - auto is_double = 0; - auto is_signed = 0; - auto is_unsigned = 0; - generated_text.push_back( "" ); - while( - !tokens.empty() - && tokens.back().type() == lexeme::Cpp1MultiKeyword - ) - { - auto text = tokens.back().to_string(); - if (text == "char" ) { ++is_char ; } - if (text == "short" ) { ++is_short ; } - if (text == "int" ) { ++is_int ; } - if (text == "long" ) { ++is_long ; } - if (text == "double" ) { ++is_double ; } - if (text == "signed" ) { ++is_signed ; } - if (text == "unsigned") { ++is_unsigned; } - - if (num_merged_tokens > 0) { - generated_text.back() = " " + generated_text.back(); - } - generated_text.back() = text + generated_text.back(); - pos = tokens.back().position(); - tokens.pop_back(); - ++num_merged_tokens; - } - - tokens.push_back({ - &generated_text.back()[0], - std::ssize(generated_text.back()), - pos, - lexeme::Keyword - }); - - if (num_merged_tokens > 1) - { - auto alt = std::string{}; - if (is_char && is_signed) { alt = "'i8' (usually best) or 'cpp2::_schar'"; } - else if (is_char && is_unsigned) { alt = "'u8' (usually best) or 'cpp2::_uchar'"; } - else if (is_short && !is_unsigned) { alt = "'short'" ; } - else if (is_short && is_unsigned) { alt = "'ushort'" ; } - else if (is_long == 1 && !is_unsigned) { alt = "'long'" ; } - else if (is_long == 1 && is_unsigned) { alt = "'ulong'" ; } - else if (is_long > 1 && !is_unsigned) { alt = "'longlong'" ; } - else if (is_long > 1 && is_unsigned) { alt = "'ulonglong'" ; } - else if (is_int && !is_unsigned) { alt = "'int'" ; } - else if (is_int && is_unsigned) { alt = "'uint'" ; } - else if (is_double && is_long) { alt = "'longdouble'" ; } - - if (std::ssize(alt) > 0) { - errors.emplace_back( - pos, - "'" + tokens.back().to_string() + "' - did you mean " + alt + "?" - ); - } - errors.emplace_back( - pos, - "'" + tokens.back().to_string() + "' is an old-style C/C++ multi-word keyword type\n" - " - most such types should be used only for interoperability with older code\n" - " - using those when you need them is fine, but name them with these short names instead:\n" - " short, ushort, int, uint, long, ulong, longlong, ulonglong, longdouble, _schar, _uchar\n" - " - see also cpp2util.h > \"Convenience names for integer types\"" - ); - } - - tokens.push_back(last_token); - }; - - auto merge_operator_function_names = [&] - { - auto i = std::ssize(tokens)-1; - - // If the third-to-last token is "operator", we may need to - // merge an "operator?" name into a single identifier token - - if ( - i >= 2 - && tokens[i-2] == "operator" - ) - { - // If the tokens after "operator" are ">" and without whitespace one of ">=" ">" "=" - if ( - tokens[i-1].type() == lexeme::Greater - && (tokens[i-1].position() == source_position{tokens[i].position().lineno, tokens[i].position().colno-1}) - && (tokens[i].type() == lexeme::GreaterEq || tokens[i].type() == lexeme::Greater || tokens[i].type() == lexeme::Assignment)) - { - // Merge all three tokens into an identifier - generated_text.push_back( "operator" + tokens[i-1].to_string() + tokens[i].to_string() ); - tokens.pop_back(); - tokens.pop_back(); - auto pos = tokens.back().position(); - tokens.pop_back(); - tokens.push_back({ - &generated_text.back()[0], - std::ssize(generated_text.back()), - pos, - lexeme::Identifier - }); - } - - // Else if token after "operator" is a single-token operator symbol - else if (is_operator(tokens[i-1].type())) - { - // Merge just "operator" + the symbol into an identifier, - generated_text.push_back( "operator" + tokens[i-1].to_string() ); - // and preserve the last token separately - auto last_token = tokens.back(); - - tokens.pop_back(); - tokens.pop_back(); - auto pos = tokens.back().position(); - tokens.pop_back(); - tokens.push_back({ - &generated_text.back()[0], - std::ssize(generated_text.back()), - pos, - lexeme::Identifier - }); - tokens.push_back(last_token); - } - - // Else if token after "operator" is a two-token operator symbol - else if ( - (tokens[i-1].type() == lexeme::LeftParen && tokens[i].type() == lexeme::RightParen) - || (tokens[i-1].type() == lexeme::LeftBracket && tokens[i].type() == lexeme::RightBracket) - ) - { - // Merge just "operator" + the symbols into an identifier, - generated_text.push_back( "operator" + tokens[i-1].to_string() + tokens[i].to_string() ); - - tokens.pop_back(); - tokens.pop_back(); - auto pos = tokens.back().position(); - tokens.pop_back(); - tokens.push_back({ - &generated_text.back()[0], - std::ssize(generated_text.back()), - pos, - lexeme::Identifier - }); - } - - } - }; - - - // Local helper functions for readability - // - auto peek = [&](int num) { - return - (i+num < std::ssize(line) && i+num >= 0) - ? line[i+num] - : '\0'; - }; - - auto store = [&](auto num, lexeme type) - { - tokens.push_back({ - &line[i], - num, - source_position(lineno, i + 1), - type - }); - i += num-1; - - merge_cpp1_multi_token_fundamental_type_names(); - merge_operator_function_names(); - }; - - - //----------------------------------------------------- - // These functions return the length of sequence if - // present at the current location, else 0 - - //G simple-escape-sequence: - //G '\' { any member of the basic character set except u, U, or x } - //G - auto peek_is_simple_escape_sequence = [&](int offset) - { - auto peek1 = peek(offset); - auto peek2 = peek(1 + offset); - if ( - peek1 == '\\' - && peek2 != 'u' - && peek2 != 'U' - && peek2 != 'x' - ) - { - return 2; - } - return 0; - }; - - //G hexadecimal-escape-sequence: - //G '\x' hexadecimal-digit - //G hexadecimal-escape-sequence hexadecimal-digit - //G - auto peek_is_hexadecimal_escape_sequence = [&](int offset) - { - if ( - peek( offset) == '\\' - && peek(1+offset) == 'x' - && is_hexadecimal_digit(peek(2+offset)) - ) - { - auto j = 3; - while ( - peek(j+offset) - && is_hexadecimal_digit(peek(j+offset)) - ) - { - ++j; - } - return j; - } - return 0; - }; - - //G universal-character-name: - //G '\u' hexadecimal-digit hexadecimal-digit hexadecimal-digit hexadecimal-digit - //G '\U' hexadecimal-digit hexadecimal-digit hexadecimal-digit hexadecimal-digit hexadecimal-digit hexadecimal-digit hexadecimal-digit hexadecimal-digit - //G - auto peek_is_universal_character_name = [&](colno_t offset) - { - if ( - peek(offset) == '\\' - && peek(1 + offset) == 'u' - ) - { - auto j = 2; - while ( - j <= 5 - && is_hexadecimal_digit(peek(j + offset)) - ) - { - ++j; - } - if (j == 6) { return j; } - errors.emplace_back( - source_position( lineno, i + offset ), - "invalid universal character name (\\u must" - " be followed by 4 hexadecimal digits)" - ); - } - if ( - peek(offset) == '\\' - && peek(1+offset) == 'U' - ) - { - auto j = 2; - while ( - j <= 9 - && is_hexadecimal_digit(peek(j+offset)) - ) - { - ++j; - } - if (j == 10) { return j; } - errors.emplace_back( - source_position(lineno, i+offset), - "invalid universal character name (\\U must" - " be followed by 8 hexadecimal digits)" - ); - } - return 0; - }; - - //G escape-sequence: - //G hexadecimal-escape-sequence - //G simple-escape-sequence - //G - auto peek_is_escape_sequence = [&](int offset) - { - if (auto h = peek_is_hexadecimal_escape_sequence(offset)) { return h; } - return peek_is_simple_escape_sequence(offset); - }; - - //G s-char: - //G universal-character-name - //G escape-sequence - //G basic-s-char - //G - //G basic-s-char: - //G any member of the basic source character set except '"' '\' or new-line - //G - //G c-char: - //G universal-character-name - //G escape-sequence - //G basic-c-char - //G - //G basic-c-char: - //G any member of the basic source character set except ''' '\' or new-line - //G - auto peek_is_sc_char = [&](int offset, char quote) - { - if (auto u = peek_is_universal_character_name(offset)) { - return u; - } - if (auto e = peek_is_escape_sequence(offset)) { - return e; - } - if ( - peek(offset) - && peek(offset) != quote - && peek(offset) != '\\' - ) - { - return 1; - } - return 0; - }; - - //G keyword: - //G any Cpp1-and-Cpp2 keyword - //G one of: 'import' 'module' 'export' 'is' 'as' - //G - auto do_is_keyword = [&](std::vector<std::string_view> const& r) { - auto remaining_line = std::string_view(line).substr(unsafe_narrow<std::size_t>(i)); - auto m = std::find_if(r.begin(), r.end(), [&](std::string_view s) { - return remaining_line.starts_with(s); - }); - if (m != r.end()) { - // If we matched and what's next is EOL or a non-identifier char, we matched! - if ( - i+std::ssize(*m) == std::ssize(line) // EOL - || !is_identifier_continue(line[unsafe_narrow<std::size_t>(i)+std::size(*m)]) // non-identifier char - ) - { - return static_cast<int>(std::ssize(*m)); - } - } - return 0; - }; - - auto peek_is_keyword = [&] - { - // Cpp2 has a smaller set of the Cpp1 globally reserved keywords, but we continue to - // reserve all the ones Cpp1 has both for compatibility and to not give up a keyword - // Some keywords like "delete" and "union" are not in this list because we reject them elsewhere - // Cpp2 also adds a couple, notably "is" and "as" - static const auto keys = std::vector<std::string_view>{ - "alignas", "alignof", "asm", "as", "auto", - "bool", "break", - "case", "catch", "char16_t", "char32_t", "char8_t", "char", "co_await", "co_return", - "co_yield", "concept", "const_cast", "consteval", "constexpr", "constinit", "const", "continue", - "decltype", "default", "double", "do", "dynamic_cast", - "else", "enum", "explicit", "export", "extern", - "float", "for", "friend", - "goto", - "if", "import", "inline", "int", "is", - "long", - "module", "mutable", - "namespace", "noexcept", - "operator", - "private", "protected", "public", - "register", "reinterpret_cast", "requires", "return", - "short", "signed", "sizeof", "static_assert", "static_cast", "static", "switch", - "template", "this", "thread_local", "throws", "throw", "try", "typedef", "typeid", "typename", - "unsigned", "using", - "virtual", "void", "volatile", - "wchar_t", "while" - }; - - return do_is_keyword(keys); - }; - - auto peek_is_cpp2_fundamental_type_keyword = [&] - { - static const auto keys = std::vector<std::string_view>{ - "i8", "i16", "i32", "i64", "longdouble", "longlong", - "u8", "u16", "u32", "u64", "ulonglong", "ulong", "ushort", - "_schar", "_uchar" - }; - - return do_is_keyword(keys); - }; - - auto peek_is_cpp1_multi_token_fundamental_keyword = [&] - { - static const auto multi_keys = std::vector<std::string_view>{ - "char16_t", "char32_t", "char8_t", "char", - "double", "float", - "int", "long", "short", - "signed", "unsigned" - }; - return do_is_keyword(multi_keys); - }; - - auto reset_processing_of_the_line = [&]() { - // Redo processing of this whole line now that the string is expanded, - // which may have moved it in memory... move i back to the line start - // and _ = any tokens we already tokenized for this line - i = colno_t{-1}; - while ( - !tokens.empty() - && tokens.back().position().lineno == lineno - ) - { - tokens.pop_back(); - } - }; - - auto interpolate_raw_string = [&]( - const std::string& opening_seq, - const std::string& closing_seq, - string_parts::adds_sequences closing_strategy, - std::string_view part, - int pos_to_replace, - int size_to_replace - ) -> bool { - auto parts = expand_raw_string_literal(opening_seq, closing_seq, closing_strategy, part, errors, source_position(lineno, pos_to_replace + 1)); - auto new_part = parts.generate(); - mutable_line.replace( pos_to_replace, size_to_replace, new_part ); - i += std::ssize(new_part)-1; - - if (parts.is_expanded()) { - // raw string was expanded and we need to repeat the processing of this line - reset_processing_of_the_line(); - - // but skipping end of potential multiline raw string that ends on this line - if (!multiline_raw_strings.empty() && multiline_raw_strings.back().end.lineno == lineno) { - i = multiline_raw_strings.back().end.colno; - raw_string_multiline.reset(); - } else if (raw_string_multiline && raw_string_multiline->start.lineno == lineno) { - raw_string_multiline.reset(); - } - return true; - } - return false; - }; - - // - //----------------------------------------------------- - - for ( ; i < ssize(line); ++i) - { - auto peek1 = peek(1); - auto peek2 = peek(2); - auto peek3 = peek(3); - - //G encoding-prefix: one of - //G 'u8' 'u' 'uR' 'u8R' 'U' 'UR' 'L' 'LR' 'R' - //G - auto is_encoding_prefix_and = [&](char next) { - if (line[i] == next) { return 1; } // " - else if (line[i] == 'u') { - if (peek1 == next) { return 2; } // u" - else if (peek1 == '8' && peek2 == next) { return 3; } // u8" - else if (peek1 == 'R' && peek2 == next) { return 3; } // uR" - else if (peek1 == '8' && peek2 == 'R' && peek3 == next) { return 4; } // u8R" - } - else if (line[i] == 'U') { - if ( peek1 == next) { return 2; } // U" - else if (peek1 == 'R' && peek2 == next) { return 3; } // UR" - } - else if (line[i] == 'L') { - if ( peek1 == next ) { return 2; } // L" - else if (peek1 == 'R' && peek2 == next) { return 3; } // LR" - } - else if (line[i] == 'R' && peek1 == next) { return 2; } // R" - return 0; - }; - - // If we're currently in a multiline comment, - // the only thing to look for is the */ comment end - // - if (in_comment) { - switch (line[i]) { - // */ comment end - break;case '*': - if (peek1 == '/') { - current_comment += "*/"; - comments.push_back({ - comment::comment_kind::stream_comment, - current_comment_start, - source_position(lineno, i + 2), - current_comment - }); - in_comment = false; - ++i; - } - break;default: - current_comment += line[i]; - } - } - else if (raw_string_multiline) { - auto end_pos = line.find(raw_string_multiline.value().closing_seq, i); - auto part = line.substr(i, end_pos-i); - - if (const auto& rsm = raw_string_multiline.value(); rsm.should_interpolate) { - - auto closing_strategy = end_pos == line.npos ? string_parts::no_ends : string_parts::on_the_end; - auto size_to_replace = end_pos == line.npos ? std::ssize(line) - i : end_pos - i + std::ssize(rsm.closing_seq); - - if (interpolate_raw_string(rsm.opening_seq, rsm.closing_seq, closing_strategy, part, i, size_to_replace ) ) { - continue; - } - } - // raw string was not expanded - - raw_string_multiline.value().text += part; - if (end_pos == std::string::npos) { - raw_string_multiline.value().text += '\n'; - break; - } - - // here we know that we are dealing with finalized multiline raw string literal - // token needs to use multiline_raw_strings to store string that exists in multiple lines - raw_string_multiline.value().text += raw_string_multiline.value().closing_seq; - - // and position where multiline_raw_string ends (needed for reseting line parsing) - i = end_pos+std::ssize(raw_string_multiline.value().closing_seq)-1; - - const auto& text = raw_string_multiline.value().should_interpolate ? raw_string_multiline.value().text.substr(1) : raw_string_multiline.value().text; - multiline_raw_strings.emplace_back(multiline_raw_string{ text, {lineno, i} }); - - tokens.push_back({ - &multiline_raw_strings.back().text[0], - std::ssize(multiline_raw_strings.back().text), - raw_string_multiline.value().start, - lexeme::StringLiteral - }); - raw_string_multiline.reset(); - continue; - } - - // Otherwise, we will be at the start of a token, a comment, or whitespace - // - else { - //G token: - //G identifier - //G keyword - //G literal - //G operator-or-punctuator - //G - //G operator-or-punctuator: - //G operator - //G punctuator - //G - //G operator: one of - - switch (line[i]) { - - // .* ->* ? aren't currently used in Cpp2, and aren't needed - - // (we do need all the overloadable operators for Cpp1 compat, - // even if we may not keep their meanings for built-in types) - - // /* and // comment starts - //G '/=' '/' - break;case '/': - if (peek1 == '*') { - current_comment = "/*"; - current_comment_start = source_position(lineno, i+1); - in_comment = true; - ++i; - } - else if (peek1 == '/') { - comments.push_back({ - comment::comment_kind::line_comment, - {lineno, i}, - {lineno, _as<colno_t>(std::ssize(line))}, - std::string(&line[i], std::ssize(line) - i) - }); - in_comment = false; - goto END; - } - else if (peek1 == '=') { - store(2, lexeme::SlashEq); - } - else { - store(1, lexeme::Slash); - } - - //G '<<=' '<<' '<=>' '<=' '<' - break;case '<': - if (peek1 == '<') { - if (peek2 == '=') { store(3, lexeme::LeftShiftEq); } - else { store(2, lexeme::LeftShift); } - } - else if (peek1 == '=') { - if (peek2 == '>') { store(3, lexeme::Spaceship); } - else { store(2, lexeme::LessEq); } - } - else { store(1, lexeme::Less); } - - // Note: >> and >>= are not source tokens, they are synthesized from > > and > >= where legal - //G '>>=' '>>' '>=' '>' - break;case '>': - //--------------------------------------------------------- - // Do not uncomment, see above Note - // - //if (peek1 == '>') { - // if (peek2 == '=') { store(3, lexeme::RightShiftEq); } - // else { store(2, lexeme::RightShift); } - //} - //else - //--------------------------------------------------------- - if (peek1 == '=') { store(2, lexeme::GreaterEq); } - else { store(1, lexeme::Greater); } - - //G '++' '+=' '+' - break;case '+': - if (peek1 == '+') { store(2, lexeme::PlusPlus); } - else if (peek1 == '=') { store(2, lexeme::PlusEq); } - else { store(1, lexeme::Plus); } - - //G '--' '-=' '->' '-' - break;case '-': - if (peek1 == '-') { store(2, lexeme::MinusMinus); } - else if (peek1 == '=') { store(2, lexeme::MinusEq); } - else if (peek1 == '>') { store(2, lexeme::Arrow); } - else { store(1, lexeme::Minus); } - - //G '||=' '||' '|=' '|' - break;case '|': - if (peek1 == '|') { - if (peek2 == '=') { store(3, lexeme::LogicalOrEq); } - else { store(2, lexeme::LogicalOr); } - } - else if (peek1 == '=') { store(2, lexeme::PipeEq); } - else { store(1, lexeme::Pipe); } - - //G '&&=' '&&' '&=' '&' - break;case '&': - if (peek1 == '&') { - if (peek2 == '=') { store(3, lexeme::LogicalAndEq); } - else { store(2, lexeme::LogicalAnd); } - } - else if (peek1 == '=') { store(2, lexeme::AmpersandEq); } - else { store(1, lexeme::Ampersand); } - - // Next, all the other operators that have a compound assignment form - - //G '*=' '*' - break;case '*': - if (peek1 == '=') { store(2, lexeme::MultiplyEq); } - else { store(1, lexeme::Multiply); } - - //G '%=' '%' - break;case '%': - if (peek1 == '=') { store(2, lexeme::ModuloEq); } - else { store(1, lexeme::Modulo); } - - //G '^=' '^' - break;case '^': - if (peek1 == '=') { store(2, lexeme::CaretEq); } - else { store(1, lexeme::Caret); } - - //G '~=' '~' - break;case '~': - if (peek1 == '=') { store(2, lexeme::TildeEq); } - else { store(1, lexeme::Tilde); } - - //G '==' '=' - break;case '=': - if (peek1 == '=') { store(2, lexeme::EqualComparison); } - else { store(1, lexeme::Assignment); } - - //G '!=' '!' - break;case '!': - if (peek1 == '=') { store(2, lexeme::NotEqualComparison); } - else { store(1, lexeme::Not); } - - //G - //G punctuator: one of - //G '...' '.' - break;case '.': - if (peek1 == '.' && peek2 == '.') { store(3, lexeme::Ellipsis); } - else { store(1, lexeme::Dot); } - - //G '::' ':' - break;case ':': - if (peek1 == ':') { store(2, lexeme::Scope); } - else { store(1, lexeme::Colon); } - - // All the other single-character tokens - - //G '{' '}' '(' ')' '[' ']' ';' ',' '?' '$' - //G - - break;case '{': - store(1, lexeme::LeftBrace); - - break;case '}': - store(1, lexeme::RightBrace); - - break;case '(': - store(1, lexeme::LeftParen); - - break;case ')': - store(1, lexeme::RightParen); - - break;case '[': - store(1, lexeme::LeftBracket); - - break;case ']': - store(1, lexeme::RightBracket); - - break;case ';': - store(1, lexeme::Semicolon); - - break;case ',': - store(1, lexeme::Comma); - - break; case '?': - store(1, lexeme::QuestionMark); - - break; case '@': - store(1, lexeme::At); - - break;case '$': - if (peek1 == 'R' && peek2 == '"') { - // if peek(j-2) is 'R' it means that we deal with raw-string literal - auto R_pos = i + 1; - auto seq_pos = i + 3; - - if (auto paren_pos = line.find("(", seq_pos); paren_pos != std::string::npos) { - auto opening_seq = line.substr(i, paren_pos - i + 1); - auto closing_seq = ")"+line.substr(seq_pos, paren_pos-seq_pos)+"\""; - - if (auto closing_pos = line.find(closing_seq, paren_pos+1); closing_pos != line.npos) { - if (interpolate_raw_string( - opening_seq, - closing_seq, - string_parts::on_both_ends, - std::string_view(&line[paren_pos+1], closing_pos-paren_pos-1), i, closing_pos-i+std::ssize(closing_seq)) - ) { - continue; - } - - tokens.push_back({ - &line[R_pos], - i - R_pos + 1, - source_position(lineno, R_pos + 1), - lexeme::StringLiteral - }); - } else { - raw_string_multiline.emplace(raw_string{source_position{lineno, i}, opening_seq, opening_seq, closing_seq, true }); - - if (interpolate_raw_string( - opening_seq, - closing_seq, - string_parts::on_the_beginning, - std::string_view(&line[paren_pos+1], std::ssize(line)-(paren_pos+1)), i, std::ssize(line)-i) - ) { - continue; - } - // skip entire raw string opening sequence R" - i = paren_pos; - - // if we are on the end of the line we need to add new line char - if (i+1 == std::ssize(line)) { - raw_string_multiline.value().text += '\n'; - } - } - continue; - } - else { - errors.emplace_back( - source_position(lineno, i + 1), - "invalid new-line in raw string delimiter \"" + std::string(&line[i],3) - + "\" - stray 'R' in program \"" - ); - } - } else { - store(1, lexeme::Dollar); - } - - //G - //G literal: - //G integer-literal - //G character-literal - //G floating-point-literal - //G string-literal - //GTODO boolean-literal - //GTODO pointer-literal - //G - //G integer-literal: - //G binary-literal - //G hexadecimal-literal - //G decimal-literal - //G - //G binary-literal: - //G '0b' binary-digit - //G '0B' binary-digit - //G binary-literal binary-digit - //G binary-literal ''' binary-digit - //G - //G hexadecimal-literal: - //G '0x' hexadecimal-digit - //G '0X' hexadecimal-digit - //G hexadecimal-literal hexadecimal-digit - //G hexadecimal-literal ''' hexadecimal-digit - //G - break;case '0': { - auto j = 3; - if (peek1 == 'b' || peek1 == 'B') { - if (is_binary_digit(peek2)) { - while (is_separator_or(is_binary_digit,peek(j))) { ++j; } - store(j, lexeme::BinaryLiteral); - continue; - } - else { - errors.emplace_back( - source_position(lineno, i), - "binary literal cannot be empty (0B must be followed by binary digits)" - ); - ++i; - } - } - else if (peek1 == 'x' || peek1 == 'X') { - if (is_hexadecimal_digit(peek2)) { - while (is_separator_or(is_hexadecimal_digit,peek(j))) { ++j; } - store(j, lexeme::HexadecimalLiteral); - continue; - } - else { - errors.emplace_back( - source_position(lineno, i), - "hexadecimal literal cannot be empty (0X must be followed by hexadecimal digits)" - ); - ++i; - } - } - } - [[fallthrough]]; - - // NO BREAK: we want 0 to fall through to numeric literal case - // (this will be less kludgy to write with pattern matching) - default: - - if ( - line[i] == 'n' - && peek1 == 'o' - && peek2 == 't' - && isspace(peek3) - ) - { - store(3, lexeme::Not); - } - - //G - //G decimal-literal: - //G digit [uU][lL][lL] - //G decimal-literal digit [uU][lL][lL] - //G decimal-literal ''' digit [uU][lL][lL] - //G - //G floating-point-literal: - //G digit { ' | digit }* . digit ({ ' | digit }*)? ([eE][-+]?digit { ' | digit }*) [fFlL] - //G - //G TODO full grammar & refactor to utility functions with their - //G own unit test rather than inline everything here - //G - else if (is_digit(line[i])) { - auto j = 1; - while (is_separator_or(is_digit,peek(j))) { ++j; } - if ( - (peek(j) != '.' || !is_digit(peek(j+1))) - && peek(j) != 'f' - && peek(j) != 'F' - && peek(j) != 'e' - && peek(j) != 'E' - ) - { - // cf: https://en.cppreference.com/w/cpp/language/integer_literal - // - // TODO: This dumbly slurps the suffixs - // ull/ULL. Suffix parsing should move to a utility - // and be error checked. Best would be to slurp all - // [a-zA-Z] and then validate against a list of - // allowed suffixes. Ideally handle the C++23 size - // suffixes as well. - if (peek(j) == 'u' || peek(j) == 'U') { ++j; } - if (peek(j) == 'l' || peek(j) == 'L') { ++j; } - if (peek(j) == 'l' || peek(j) == 'L') { ++j; } - store(j, lexeme::DecimalLiteral); - } - else { - // cf: https://en.cppreference.com/w/cpp/language/floating_literal - - // slurps the digits after '.' - if (peek(j) == '.') { - ++j; - if (!is_digit(peek(j))) { - errors.emplace_back( - source_position(lineno, i), - "a floating point literal must have at least one digit after the decimal point (can be '.0')" - ); - } - while (is_separator_or(is_digit,peek(j))) { - ++j; - } - } - - // slurp the exponential form marker - if (peek(j) == 'e' || peek(j) == 'E') { - ++j; - if (peek(j) == '-' || peek(j) == '+') { ++j; } - while (is_separator_or(is_digit,peek(j))) { ++j; } - } - - // TODO: This dumbly slurps the suffixes fF or - // lL. Suffix parsing should move to a utility and be - // error checked. Best would be to slurp all [a-zA-Z] - // and then validate against a list of allowed - // suffixes. Ideally handle the C++23 suffixes aswell. - if (peek(j) == 'f' || peek(j) == 'F') { ++j; } - else if (peek(j) == 'l' || peek(j) == 'L') { ++j; } - store(j, lexeme::FloatLiteral); - } - } - - //G string-literal: - //G encoding-prefix? '"' s-char-seq? '"' - //G encoding-prefix? 'R"' d-char-seq? '(' s-char-seq? ')' d-char-seq? '"' - //G - //G s-char-seq: - //G interpolation? s-char - //G interpolation? s-char-seq s-char - //G - //G d-char-seq: - //G d-char - //G - //G interpolation: - //G '(' expression ')' '$' - //G - else if (auto j = is_encoding_prefix_and('\"')) { - // if peek(j-2) is 'R' it means that we deal with raw-string literal - if (peek(j-2) == 'R') { - auto seq_pos = i + j; - - if (auto paren_pos = line.find("(", seq_pos); paren_pos != std::string::npos) { - auto opening_seq = line.substr(i, paren_pos - i + 1); - auto closing_seq = ")"+line.substr(seq_pos, paren_pos-seq_pos)+"\""; - - if (auto closing_pos = line.find(closing_seq, paren_pos+1); closing_pos != line.npos) { - store(closing_pos+std::ssize(closing_seq)-i, lexeme::StringLiteral); - } else { - raw_string_multiline.emplace(raw_string{source_position{lineno, i}, opening_seq, opening_seq, closing_seq }); - // skip entire raw string opening sequence R" - i = paren_pos; - - // if we are on the end of the line we need to add new line char - if (i+1 == std::ssize(line)) { - raw_string_multiline.value().text += '\n'; - } - } - continue; - } - else { - errors.emplace_back( - source_position(lineno, i + j - 2), - "invalid new-line in raw string delimiter \"" + std::string(&line[i],j) - + "\" - stray 'R' in program \"" - ); - } - } - else { - while (auto len = peek_is_sc_char(j, '\"')) { j += len; } - if (peek(j) != '\"') { - errors.emplace_back( - source_position(lineno, i), - "string literal \"" + std::string(&line[i+1],j) - + "\" is missing its closing \"" - ); - } - - // At this point we have a string-literal, but it may contain - // captures/interpolations we want to tokenize - auto literal = std::string_view{ &line[i], std::size_t(j+1) }; - auto s = expand_string_literal( literal, errors, source_position(lineno, i + 1) ); - - // If there are no captures/interpolations, just store it directly and continue - if (std::ssize(s) == j+1) { - store(j+1, lexeme::StringLiteral); - } - // Otherwise, replace it with the expanded version and continue - else { - if (std::ssize(s) <= j + 1) { - errors.emplace_back( - source_position( lineno, i ), - "not a legal string literal", - false, - true // a noisy fallback error message - ); - return {}; - } - mutable_line.replace( i, j+1, s ); - - reset_processing_of_the_line(); - } - } - } - - //G character-literal: - //G encoding-prefix? ''' c-char-seq? ''' - //G - //G c-char-seq: - //G c-char - //G c-char-seq c-char - //G - else if (auto j = is_encoding_prefix_and('\'')) { - auto len = peek_is_sc_char(j, '\''); - if (len > 0) { - j += len; - if (peek(j) != '\'') { - assert (j > 1); - errors.emplace_back( - source_position(lineno, i), - "character literal '" + std::string(&line[i+1],j-1) - + "' is missing its closing '" - ); - } - store(j+1, lexeme::CharacterLiteral); - } - else { - errors.emplace_back( - source_position(lineno, i), - "character literal is empty" - ); - } - } - - // Cpp1 multi-token fundamental type keyword - // - else if (auto j = peek_is_cpp1_multi_token_fundamental_keyword()) { - store(j, lexeme::Cpp1MultiKeyword); - } - - // Cpp2 fixed-width type alias keyword - // - else if (auto j = peek_is_cpp2_fundamental_type_keyword()) { - store(j, lexeme::Cpp2FixedType); - } - - // Other keyword - // - else if (auto j = peek_is_keyword()) { - store(j, lexeme::Keyword); - - if (tokens.back() == "const_cast") { - errors.emplace_back( - source_position(lineno, i), - "'const_cast' is not supported in Cpp2 - the current C++ best practice is to never cast away const, and that is const_cast's only effective use" - ); - } - if (tokens.back() == "static_cast") { - errors.emplace_back( - source_position(lineno, i), - "'static_cast<T>(val)' is not supported in Cpp2 - use 'val as T' for safe conversions instead, or if necessary cpp2::unsafe_narrow<T>(val) for a possibly-lossy narrowing conversion" - ); - } - if (tokens.back() == "dynamic_cast") { - errors.emplace_back( - source_position(lineno, i), - "'dynamic_cast<Derived*>(pBase)' is not supported in Cpp2 - use 'pBase as *Derived' for safe dynamic conversions instead" - ); - } - } - - // Identifier - // - else if (auto j = starts_with_identifier({&line[i], std::size(line)-i})) - { - if ( - !isspace(peek(-1)) - && !tokens.empty() - && is_literal(tokens.back().type()) - ) - { - store(j, lexeme::UserDefinedLiteralSuffix); - } - else - { - store(j, lexeme::Identifier); - - tokens.back().remove_prefix_if("__identifier__"); - - if (tokens.back() == "NULL") { - errors.emplace_back( - source_position(lineno, i), - "'NULL' is not supported in Cpp2 - for a local pointer variable, leave it uninitialized instead, and set it to a non-null value when you have one" - ); - } - if (tokens.back() == "delete") { - errors.emplace_back( - source_position(lineno, i), - "'delete' and owning raw pointers are not supported in Cpp2 - use unique.new<T> or shared.new<T> instead in that order (or, in the future, gc.new<T>, but that is not yet implemented)" - ); - } - } - } - - // Anything else should be whitespace - // - else if (!isspace(line[i])) { - errors.emplace_back( - source_position(lineno, i), - std::string("unexpected text '") + line[i] + "'", - false, - true // a noisy fallback error message - ); - } - } - } - } - -END: - if (in_comment) { - current_comment += "\n"; - } - if (raw_string_multiline && line.size() == 0) { - raw_string_multiline.value().text += '\n'; - } - - assert (std::ssize(tokens) >= original_size); - return std::ssize(tokens) != original_size; -} - - -//----------------------------------------------------------------------- -// -// tokens: a map of the tokens of a source file -// -//----------------------------------------------------------------------- -// - -class tokens -{ - std::vector<error_entry>& errors; - - // All non-comment source tokens go here, which will be parsed in the parser - std::map<lineno_t, std::vector<token>> grammar_map; - - // All comment source tokens go here, which are applied in the lexer - // - // We could put all the tokens in the same map, but that would mean the - // parsing logic would have to remember to skip comments everywhere... - // simpler to keep comments separate, at the smaller cost of traversing - // a second token stream when lowering to Cpp1 to re-interleave comments - std::vector<comment> comments; - - // A stable place to store additional tokens that are synthesized later - std::deque<token> generated_tokens; - -public: - //----------------------------------------------------------------------- - // Constructor - // - // errors error list - // - tokens( - std::vector<error_entry>& errors_ - ) - : errors{ errors_ } - { - } - - - //----------------------------------------------------------------------- - // lex: Tokenize the Cpp2 lines - // - // lines tagged source lines - // is_generated is this generated code - // - auto lex( - std::vector<source_line>& lines, - bool is_generated = false - ) - -> void - { - auto in_comment = false; - auto raw_string_multiline = std::optional<raw_string>(); - - assert (std::ssize(lines) > 0); - auto line = std::begin(lines); - while (line != std::end(lines)) { - - // Skip over non-Cpp2 lines - if (line->cat != source_line::category::cpp2) { - ++line; - continue; - } - - // At this point, we're at the first line of a Cpp2 code section - - // Create new map entry for the section starting at this line, - // and populate its tokens with the tokens in this section - auto lineno = std::distance(std::begin(lines), line); - - // If this is generated code, use negative line numbers to - // inform and assist the printer - if (is_generated) { - lineno -= 10'000; - } - - auto& entry = grammar_map[lineno]; - auto current_comment = std::string{}; - auto current_comment_start = source_position{}; - - for ( - ; - line != std::end(lines) && line->cat == source_line::category::cpp2; - ++line, ++lineno - ) - { - lex_line( - line->text, lineno, - in_comment, current_comment, current_comment_start, - entry, comments, errors, - raw_string_multiline - ); - - // Check whether all the tokens on this line were consecutive - // w/o extra whitespace (separated by 0 or 1 whitespace chars) - if (!entry.empty()) { - for (auto i = std::ssize(entry) - 1; - i > 0; - --i - ) - { - if (entry[i-1].position().lineno != lineno) { - break; - } - - if ( - entry[i].position().lineno == lineno - && entry[i-1].position().colno + entry[i-1].length() + 1 - < entry[i].position().colno - ) - { - line->all_tokens_are_densely_spaced = false; - break; - } - } - } - } - } - } - - - //----------------------------------------------------------------------- - // get_map: Access the token map - // - auto get_map() const - -> auto const& - { - return grammar_map; - } - - - //----------------------------------------------------------------------- - // get_comments: Access the comment list - // - auto get_comments() const - -> auto const& - { - return comments; - } - - - //----------------------------------------------------------------------- - // get_generated: Access the generated tokens - // - auto get_generated() - -> auto& - { - return generated_tokens; - } - - - //----------------------------------------------------------------------- - // num_unprinted_comments: The number of not-yet-printed comments - // - auto num_unprinted_comments() - -> int - { - auto ret = 0; - for (auto const& c : comments) { - if (!c.dbg_was_printed) { - ++ret; - } - } - return ret; - } - - //----------------------------------------------------------------------- - // debug_print - // - auto debug_print(std::ostream& o) const - -> void - { - for (auto const& [lineno, entry] : grammar_map) { - - o << "--- Section starting at line " << lineno << "\n"; - for (auto const& token : entry) { - o << " " << token << " (" << token.position().lineno - << "," << token.position().colno << ") " - << _as<std::string>(token.type()) << "\n"; - } - - } - - o << "--- Comments\n"; - for (auto const& [kind, start, end, text, dbg_ignore] : comments) { - o << " " - << (kind == comment::comment_kind::line_comment ? "// " : "/* ") - << "(" << start.lineno << "," << start.colno << ")" - << "-(" << end.lineno << "," << end.colno << ")" - << " " << text << "\n"; - } - - o << "--- Generated tokens\n"; - for (auto const& token : generated_tokens) { - o << " " << token << " (" << token.position().lineno - << "," << token.position().colno << ") " - << _as<std::string>(token.type()) << "\n"; - } - - o << "--- Generated text\n"; - for (auto const& s : generated_text) { - o << " " << s << "\n"; - } - - } - -}; - -static auto generated_lexers = std::deque<tokens>{}; - -} - -#endif diff --git a/64x0/cc2/source/parse.h b/64x0/cc2/source/parse.h deleted file mode 100644 index e8e6daf..0000000 --- a/64x0/cc2/source/parse.h +++ /dev/null @@ -1,9263 +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. - - -//=========================================================================== -// Parser -//=========================================================================== - -#ifndef CPP2_PARSE_H -#define CPP2_PARSE_H - -#include "lex.h" -#include <memory> -#include <variant> -#include <iostream> - - -namespace cpp2 { - -auto violates_lifetime_safety = false; - -//----------------------------------------------------------------------- -// Operator categorization -// - -//G prefix-operator: -//G one of '!' '-' '+' -//GT parameter-direction -//G -auto is_prefix_operator(token const& tok) - -> bool -{ - //if (to_passing_style(tok) != passing_style::invalid) { - // return true; - //} - - switch (tok.type()) { - break;case lexeme::Not: - case lexeme::Minus: - case lexeme::Plus: - return true; - break;default: - return false; - } -} - - -//G postfix-operator: -//G one of '++' '--' '*' '&' '~' '$' '...' -//G -auto is_postfix_operator(lexeme l) - -> bool -{ - switch (l) { - break;case lexeme::PlusPlus: - case lexeme::MinusMinus: - case lexeme::Multiply: - case lexeme::Ampersand: - case lexeme::Tilde: - case lexeme::Dollar: - case lexeme::Ellipsis: - return true; - break;default: - return false; - } -} - - -//G assignment-operator: -//G one of '=' '*=' '/=' '%=' '+=' '-=' '>>=' '<<=' '&=' '^=' '|=' -//G -auto is_assignment_operator(lexeme l) - -> bool -{ - switch (l) { - break;case lexeme::Assignment: - case lexeme::MultiplyEq: - case lexeme::SlashEq: - case lexeme::ModuloEq: - case lexeme::PlusEq: - case lexeme::MinusEq: - case lexeme::RightShiftEq: - case lexeme::LeftShiftEq: - case lexeme::AmpersandEq: - case lexeme::CaretEq: - case lexeme::PipeEq: - return true; - break;default: - return false; - } -} - - -//----------------------------------------------------------------------- -// -// Parse tree node types -// -//----------------------------------------------------------------------- -// - -//----------------------------------------------------------------------- -// try_visit -// -// Helper to visit whatever is in a variant where each -// alternative is a smart pointer -// -template <int I> -auto try_visit(auto& variant, auto& visitor, int depth) - -> void -{ - if (variant.index() == I) { - auto const& s = std::get<I>(variant); - assert (s); - s->visit(visitor, depth+1); - } -} - - -struct expression_list_node; -struct id_expression_node; -struct declaration_node; -struct inspect_expression_node; -struct literal_node; -struct template_argument; - - -struct primary_expression_node -{ - enum active { empty=0, identifier, expression_list, id_expression, declaration, inspect, literal }; - std::variant< - std::monostate, - token const*, - std::unique_ptr<expression_list_node>, - std::unique_ptr<id_expression_node>, - std::unique_ptr<declaration_node>, - std::unique_ptr<inspect_expression_node>, - std::unique_ptr<literal_node> - > expr; - // Cache to work around <https://github.com/llvm/llvm-project/issues/73336>. - bool expression_list_is_fold_expression = false; - - - // API - // - auto is_fold_expression() const - -> bool; - - auto is_identifier() const - -> bool; - - auto is_id_expression() const - -> bool; - - auto is_expression_list() const - -> bool; - - auto get_expression_list() const - -> expression_list_node const*; - - auto is_literal() const - -> bool; - - auto template_arguments() const -> std::vector<template_argument> const&; - - auto get_token() const -> token const*; - - auto to_string() const - -> std::string; - - // Internals - // - auto position() const -> source_position; - auto visit(auto& v, int depth) -> void; -}; - - -struct literal_node { - token const* literal = {}; - token const* user_defined_suffix = {}; - - // API - // - auto get_token() const - -> token const* - { - return literal; - } - - auto to_string() const - -> std::string - { - assert (literal); - auto ret = literal->to_string(); - if (user_defined_suffix) { - ret += user_defined_suffix->to_string(); - } - return ret; - } - - // Internals - // - auto position() const - -> source_position - { - assert (literal); - return literal->position(); - } - - auto visit(auto& v, int depth) -> void - { - v.start(*this, depth); - assert (literal); - literal->visit(v, depth+1); - if (user_defined_suffix) { - user_defined_suffix->visit(v, depth+1); - } - v.end(*this, depth); - } -}; - - -struct postfix_expression_node; - -struct prefix_expression_node -{ - std::vector<token const*> ops; - std::unique_ptr<postfix_expression_node> expr; - - // API - // - auto is_fold_expression() const - -> bool; - - auto is_identifier() const - -> bool; - - auto is_id_expression() const - -> bool; - - auto is_expression_list() const - -> bool; - - auto get_expression_list() const - -> expression_list_node const*; - - auto get_postfix_expression_node() const - -> postfix_expression_node * - { - assert(expr); - return expr.get(); - } - - auto is_literal() const - -> bool; - - auto is_result_a_temporary_variable() const -> bool; - - auto to_string() const - -> std::string; - - // Internals - // - auto position() const -> source_position; - auto visit(auto& v, int depth) -> void; -}; - - -struct expression_node; - - -template< - String Name, - typename Term -> -struct binary_expression_node -{ - std::unique_ptr<Term> expr; - expression_node const* my_expression = {}; - - binary_expression_node(); - - struct term - { - token const* op; - std::unique_ptr<Term> expr; - }; - std::vector<term> terms; - - - // API - // - auto is_fold_expression() const - -> bool - { - // This is a fold-expression if any subexpression - // has an identifier named "..." - auto ret = expr->is_fold_expression(); - for (auto& x : terms) { - ret |= x.expr->is_fold_expression(); - } - return ret; - } - - auto lhs_is_id_expression() const - -> bool - { - return expr->is_id_expression(); - } - - auto is_standalone_expression() const - -> bool; - - auto terms_size() const - -> int - { - return std::ssize(terms); - } - - auto is_identifier() const - -> bool - { - return terms.empty() && expr->is_identifier(); - } - - auto is_id_expression() const - -> bool - { - return terms.empty() && expr->is_id_expression(); - } - - auto is_expression_list() const - -> bool - { - return terms.empty() && expr->is_expression_list(); - } - - auto get_expression_list() const - -> expression_list_node const* - { - if (is_expression_list()) { - return expr->get_expression_list(); - } - return {}; - } - - auto is_literal() const - -> bool - { - return terms.empty() && expr->is_literal(); - } - - // Get left-hand postfix-expression - auto get_postfix_expression_node() const - -> postfix_expression_node * - { - assert(expr); - return expr->get_postfix_expression_node(); - } - - // Get first right-hand postfix-expression, if there is one - auto get_second_postfix_expression_node() const - -> postfix_expression_node * - { - if (!terms.empty()) { - assert(terms.front().expr); - return terms.front().expr->get_postfix_expression_node(); - } - // else - return {}; - } - - // "Simple" means binary (size>0) and not chained (size<2) - struct get_lhs_rhs_if_simple_binary_expression_with_ret { - postfix_expression_node* lhs; - Term* rhs; - }; - auto get_lhs_rhs_if_simple_binary_expression_with(lexeme op) const - -> get_lhs_rhs_if_simple_binary_expression_with_ret - { - if ( - std::ssize(terms) == 1 - && terms[0].op->type() == op - ) - { - return { - get_postfix_expression_node(), - terms.front().expr.get() - }; - } - // Else - return { nullptr, nullptr }; - } - - auto is_result_a_temporary_variable() const -> bool { - if constexpr (std::string_view(Name.value) == "assignment") { - assert(expr); - return expr->is_result_a_temporary_variable(); - } else { - if (terms.empty()) { - assert(expr); - return expr->is_result_a_temporary_variable(); - } else { - return true; - } - } - } - - auto to_string() const - -> std::string - { - assert (expr); - auto ret = expr->to_string(); - for (auto const& x : terms) { - assert (x.op); - ret += " " + x.op->to_string(); - assert (x.expr); - ret += " " + x.expr->to_string(); - } - return ret; - } - - - // Internals - // - auto position() const - -> source_position - { - assert (expr); - return expr->position(); - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - assert (expr); - expr->visit(v, depth+1); - for (auto const& x : terms) { - assert (x.op); - v.start(*x.op, depth+1); - assert (x.expr); - x.expr->visit(v, depth+1); - } - v.end(*this, depth); - } -}; - - -struct is_as_expression_node; - -using multiplicative_expression_node = binary_expression_node< "multiplicative" , is_as_expression_node >; -using additive_expression_node = binary_expression_node< "additive" , multiplicative_expression_node >; -using shift_expression_node = binary_expression_node< "shift" , additive_expression_node >; -using compare_expression_node = binary_expression_node< "compare" , shift_expression_node >; -using relational_expression_node = binary_expression_node< "relational" , compare_expression_node >; -using equality_expression_node = binary_expression_node< "equality" , relational_expression_node >; -using bit_and_expression_node = binary_expression_node< "bit-and" , equality_expression_node >; -using bit_xor_expression_node = binary_expression_node< "bit-xor" , bit_and_expression_node >; -using bit_or_expression_node = binary_expression_node< "bit-or" , bit_xor_expression_node >; -using logical_and_expression_node = binary_expression_node< "logical-and" , bit_or_expression_node >; -using logical_or_expression_node = binary_expression_node< "logical-or" , logical_and_expression_node >; -using assignment_expression_node = binary_expression_node< "assignment" , logical_or_expression_node >; - - -struct assignment_expression_lhs_rhs { - postfix_expression_node* lhs; - logical_or_expression_node* rhs; -}; - - -struct expression_statement_node; - -struct expression_node -{ - static inline std::vector<expression_node*> current_expressions = {}; - - std::unique_ptr<assignment_expression_node> expr; - int num_subexpressions = 0; - expression_statement_node const* my_statement = {}; - - expression_node(); - - // API - // - auto is_fold_expression() const - -> bool - { - // This is a fold-expression if any subexpression - // has an identifier named "..." - return expr->is_fold_expression(); - } - - auto is_standalone_expression() const - -> bool; - - auto subexpression_count() const - -> int - { - return num_subexpressions; - } - - auto is_identifier() const - -> bool - { - return expr->is_identifier(); - } - - auto is_id_expression() const - -> bool - { - return expr->is_id_expression(); - } - - auto is_expression_list() const - -> bool - { - return expr->is_expression_list(); - } - - auto get_expression_list() const - -> expression_list_node const* - { - if (is_expression_list()) { - return expr->get_expression_list(); - } - return {}; - } - - auto is_literal() const - -> bool - { - return expr->is_literal(); - } - - auto get_lhs_rhs_if_simple_assignment() const - -> assignment_expression_lhs_rhs; - - auto to_string() const - -> std::string - { - assert (expr); - return expr->to_string(); - } - - // Internals - // - auto position() const -> source_position - { - assert (expr); - return expr->position(); - } - - auto visit(auto& v, int depth) -> void - { - v.start(*this, depth); - assert (expr); - expr->visit(v, depth+1); - v.end(*this, depth); - } -}; - - -template< - String Name, - typename Term -> -binary_expression_node<Name, Term>::binary_expression_node() { - if (!expression_node::current_expressions.empty()) { - my_expression = expression_node::current_expressions.back(); - } -} - - -template< - String Name, - typename Term -> -auto binary_expression_node<Name, Term>::is_standalone_expression() const - -> bool -{ - return - my_expression - && my_expression->is_standalone_expression() - ; -} - - -enum class passing_style { in=0, copy, inout, out, move, forward, invalid }; -auto to_passing_style(token const& t) -> passing_style { - if (t.type() == lexeme::Identifier) { - if (t == "in" ) { return passing_style::in; } - if (t == "copy" ) { return passing_style::copy; } - if (t == "inout" ) { return passing_style::inout; } - if (t == "out" ) { return passing_style::out; } - if (t == "move" ) { return passing_style::move; } - if (t == "forward") { return passing_style::forward; } - } - return passing_style::invalid; -} -auto to_string_view(passing_style pass) -> std::string_view { - switch (pass) { - break;case passing_style::in : return "in"; - break;case passing_style::copy : return "copy"; - break;case passing_style::inout : return "inout"; - break;case passing_style::out : return "out"; - break;case passing_style::move : return "move"; - break;case passing_style::forward: return "forward"; - break;default : return "INVALID passing_style"; - } -} - - -struct expression_list_node -{ - token const* open_paren = {}; - token const* close_paren = {}; - bool inside_initializer = false; - - struct term { - passing_style pass = {}; - std::unique_ptr<expression_node> expr; - - auto visit(auto& v, int depth) -> void - { - v.start(*this, depth); - assert(expr); - expr->visit(v, depth+1); - v.end(*this, depth); - } - }; - std::vector< term > expressions; - - - // API - // - auto is_fold_expression() const - -> bool - { - // This is a fold-expression if any subexpression - // has an identifier named "..." - auto ret = false; - for (auto& x : expressions) { - ret |= x.expr->is_fold_expression(); - } - return ret; - } - - - // Internals - // - auto position() const - -> source_position - { - // Make sure this got set - assert (open_paren); - return open_paren->position(); - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - for (auto& x : expressions) { - x.visit(v, depth+1); - } - v.end(*this, depth); - } -}; - -auto primary_expression_node::is_identifier() const - -> bool -{ - return expr.index() == identifier; -} - -auto primary_expression_node::is_id_expression() const - -> bool -{ - return expr.index() == id_expression; -} - -auto primary_expression_node::is_expression_list() const - -> bool -{ - return expr.index() == expression_list; -} - -auto primary_expression_node::get_expression_list() const - -> expression_list_node const* -{ - if (is_expression_list()) { - return std::get<expression_list>(expr).get(); - } - return {}; -} - -auto primary_expression_node::is_literal() const - -> bool -{ - return expr.index() == literal; -} - - -struct expression_statement_node -{ - static inline std::vector<expression_statement_node*> current_expression_statements = {}; - - std::unique_ptr<expression_node> expr; - bool has_semicolon = false; - - // API - // - auto subexpression_count() const - -> int - { - assert (expr); - return expr->subexpression_count(); - } - - auto to_string() const - -> std::string - { - assert (expr); - return expr->to_string(); - } - - // Internals - // - auto position() const - -> source_position - { - assert (expr); - return expr->position(); - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - assert (expr); - expr->visit(v, depth+1); - v.end(*this, depth); - } -}; - - -auto expression_node::is_standalone_expression() const - -> bool -{ - return - my_statement - && my_statement->subexpression_count() == subexpression_count() - ; -} - - -struct capture { - postfix_expression_node* capture_expr; - std::string cap_sym = {}; - std::string str = {}; - std::string str_suppressed_move = {}; - auto operator==(postfix_expression_node* p) { return capture_expr == p; } -}; - -struct capture_group { - std::vector<capture> members; - - auto add(postfix_expression_node* p) - -> void - { - members.push_back({p}); - } - - auto remove(postfix_expression_node* p) - -> void; - - ~capture_group(); -}; - - -struct postfix_expression_node -{ - std::unique_ptr<primary_expression_node> expr; - - struct term - { - token const* op; - - // This is used if *op is . - can be null - std::unique_ptr<id_expression_node> id_expr = {}; - - // These are used if *op is [ or ( - can be null - std::unique_ptr<expression_list_node> expr_list = {}; - token const* op_close = {}; - }; - std::vector<term> ops; - capture_group* cap_grp = {}; - - ~postfix_expression_node(); - - // API - // - auto is_fold_expression() const - -> bool - { - // This is a fold-expression if any subexpression - // has an identifier named "..." - return expr->is_fold_expression(); - } - - auto is_identifier() const - -> bool - { - return ops.empty() && expr->is_identifier(); - } - - auto is_id_expression() const - -> bool - { - return ops.empty() && expr->is_id_expression(); - } - - auto is_expression_list() const - -> bool - { - return ops.empty() && expr->is_expression_list(); - } - - auto get_expression_list() const - -> expression_list_node const* - { - if (is_expression_list()) { - return expr->get_expression_list(); - } - return {}; - } - - auto is_literal() const - -> bool - { - return ops.empty() && expr->is_literal(); - } - - auto get_first_token_ignoring_this() const - -> token const*; - - auto is_result_a_temporary_variable() const -> bool { - if (ops.empty()) { - return false; - } else { - return (ops.front().op->type() == lexeme::Ampersand - || ops.front().op->type() == lexeme::Tilde); - } - } - - auto to_string() const - -> std::string; - - // Internals - // - auto position() const -> source_position - { - assert (expr); - return expr->position(); - } - - auto visit(auto& v, int depth) -> void; -}; - -auto prefix_expression_node::is_fold_expression() const - -> bool -{ - // This is a fold-expression if any subexpression - // has an identifier named "..." - return expr->is_fold_expression(); -} - -auto prefix_expression_node::is_identifier() const - -> bool -{ - return ops.empty() && expr->is_identifier(); -} - -auto prefix_expression_node::is_id_expression() const - -> bool -{ - return ops.empty() && expr->is_id_expression(); -} - -auto prefix_expression_node::is_expression_list() const - -> bool -{ - return ops.empty() && expr->is_expression_list(); -} - -auto prefix_expression_node::get_expression_list() const - -> expression_list_node const* -{ - if (is_expression_list()) { - return expr->get_expression_list(); - } - return {}; -} - -auto prefix_expression_node::is_literal() const - -> bool -{ - return ops.empty() && expr->is_literal(); -} - -auto prefix_expression_node::is_result_a_temporary_variable() const -> bool { - if (ops.empty()) { - return expr->is_result_a_temporary_variable(); - } else { - return true; - } -} - - -auto expression_node::get_lhs_rhs_if_simple_assignment() const - -> assignment_expression_lhs_rhs -{ - auto ret = expr->get_lhs_rhs_if_simple_binary_expression_with(lexeme::Assignment); - return { ret.lhs, ret.rhs }; -} - - -auto capture_group::remove(postfix_expression_node* p) - -> void -{ - p->cap_grp = {}; - auto old_size = members.size(); - std::erase(members, p); - assert (members.size() == old_size-1); -} - - -capture_group::~capture_group() -{ - assert (members.empty()); - // We shouldn't need to do this: - // while (!members.empty()) { - // remove(members.front().capture_expr); - // } - // if the capture_group outlives the tree of things that can point to it - // => each node with a capture_group should declare it as the first member - // before any other node that could own a postfix_expression that could - // point back up to that capture_group -} - - -auto prefix_expression_node::to_string() const - -> std::string -{ - auto ret = std::string{}; - - for (auto const& x : ops) { - assert (x); - ret += x->as_string_view(); - } - - assert (expr); - return ret + expr->to_string(); -} - - -auto prefix_expression_node::position() const - -> source_position -{ - if (std::ssize(ops) > 0) { - return ops.front()->position(); - } - assert (expr); - return expr->position(); -} - - -auto prefix_expression_node::visit(auto& v, int depth) - -> void -{ - v.start(*this, depth); - for (auto const& x : ops) { - assert (x); - v.start(*x, depth+1); - } - assert (expr); - expr->visit(v, depth+1); - v.end(*this, depth); -} - - -struct type_id_node; -struct template_args_tag { }; - -struct template_argument -{ - enum active { empty=0, expression, type_id }; - source_position comma; - std::variant< - std::monostate, - std::unique_ptr<expression_node>, - std::unique_ptr<type_id_node> - > arg; - - auto to_string() const - -> std::string; -}; - -// Used by functions that must return a reference to an empty arg list -inline std::vector<template_argument> const no_template_args; - -struct unqualified_id_node -{ - token const* identifier = {}; // required - - // These are used only if it's a template-id - source_position open_angle = {}; - source_position close_angle = {}; - - std::vector<template_argument> template_args; - - auto template_arguments() const - -> std::vector<template_argument> const& - { - return template_args; - } - - auto get_token() const - -> token const* - { - if (open_angle == source_position{}) { - assert (identifier); - return identifier; - } - // else - return {}; - } - - auto to_string() const - -> std::string; - - auto position() const - -> source_position - { - assert (identifier); - return identifier->position(); - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - assert (identifier); - v.start(*identifier, depth+1); - - if (open_angle != source_position{}) { - // Inform the visitor that this is a template args list - v.start(template_args_tag{}, depth); - assert(open_angle != source_position{}); - assert(close_angle != source_position{}); - assert(template_args.empty() - || template_args.front().comma == source_position{}); - for (auto& a : template_args) { - try_visit<template_argument::expression>(a.arg, v, depth+1); - try_visit<template_argument::type_id >(a.arg, v, depth+1); - } - v.end(template_args_tag{}, depth); - } - - v.end(*this, depth); - } -}; - - -struct qualified_id_node -{ - struct term { - token const* scope_op; - std::unique_ptr<unqualified_id_node> id = {}; - - term( token const* o ) : scope_op{o} { } - }; - std::vector<term> ids; - - auto template_arguments() const - -> std::vector<template_argument> const& - { - return ids.back().id->template_arguments(); - } - - auto get_token() const - -> token const* - { - if ( - std::ssize(ids) == 1 - && !ids.front().scope_op - ) - { - assert (ids.front().id); - return ids.front().id->get_token(); - } - // else - return {}; - } - - auto to_string() const - -> std::string - { - auto ret = std::string{}; - for (auto& term : ids) { - if (term.scope_op) { - ret += term.scope_op->as_string_view(); - } - assert (term.id); - ret += term.id->to_string(); - } - return ret; - } - - auto get_first_token() const - -> token const* - { - assert ( - !ids.empty() - && ids.front().id - ); - return ids.front().id->get_token(); - } - - auto position() const - -> source_position - { - assert (!ids.empty()); - if (ids.front().scope_op) { - return ids.front().scope_op->position(); - } - else { - assert (ids.front().id); - return ids.front().id->position(); - } - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - for (auto const& x : ids) { - if (x.scope_op) { - x.scope_op->visit(v, depth+1); - } - assert(x.id); - x.id->visit(v, depth+1); - } - v.end(*this, depth); - } -}; - - -struct type_id_node -{ - source_position pos; - - std::vector<token const*> pc_qualifiers; - token const* address_of = {}; - token const* dereference_of = {}; - int dereference_cnt = {}; - token const* suspicious_initialization = {}; - - enum active { empty=0, qualified, unqualified, keyword }; - std::variant< - std::monostate, - std::unique_ptr<qualified_id_node>, - std::unique_ptr<unqualified_id_node>, - token const* - > id; - - auto is_wildcard() const - -> bool - { - return - id.index() == type_id_node::empty - || (get_token() && *get_token() == "_") - ; - } - - auto is_pointer_qualified() const - -> bool - { - for (auto q : pc_qualifiers) { - if (q->type() == lexeme::Multiply) { - return true; - } - } - return false; - } - - auto is_concept() const - -> bool - { - auto tok = get_token(); - return tok && *tok == "concept"; - } - - auto template_arguments() const - -> std::vector<template_argument> const& - { - if (id.index() == unqualified) { - return std::get<unqualified>(id)->template_arguments(); - } - // else - return std::get<qualified>(id)->template_arguments(); - } - - auto to_string() const - -> std::string - { - switch (id.index()) { - break;case empty: - return {}; - break;case qualified: - return std::get<qualified>(id)->to_string(); - break;case unqualified: - return std::get<unqualified>(id)->to_string(); - break;case keyword: - return std::get<keyword>(id)->to_string(); - break;default: - assert(!"ICE: invalid type_id state"); - } - // else - return {}; - } - - auto get_token() const - -> token const* - { - switch (id.index()) { - break;case empty: - return {}; - break;case qualified: - return {}; - break;case unqualified: - return get<unqualified>(id)->get_token(); - break;case keyword: - return get<keyword>(id); - break;default: - assert(!"ICE: invalid type_id state"); - } - // else - return {}; - } - - auto position() const - -> source_position - { - return pos; - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - for (auto q : pc_qualifiers) { - v.start(*q, depth+1); - } - try_visit<qualified >(id, v, depth); - try_visit<unqualified>(id, v, depth); - try_visit<keyword >(id, v, depth); - v.end(*this, depth); - } -}; - -auto unqualified_id_node::to_string() const - -> std::string -{ - assert(identifier); - auto ret = identifier->to_string(); - if (open_angle != source_position{}) { - auto separator = std::string{"<"}; - for (auto& t : template_args) { - ret += separator; - assert(t.arg.index() != template_argument::empty); - if (t.arg.index() == template_argument::expression) { - ret += std::get<template_argument::expression>(t.arg)->to_string(); - } - else if (t.arg.index() == template_argument::type_id) { - ret += std::get<template_argument::type_id>(t.arg)->to_string(); - } - separator = ","; - } - if (std::ssize(template_args) > 0) { - ret += ">"; - } - } - return ret; -} - -auto template_argument::to_string() const - -> std::string -{ - switch (arg.index()) { - break;case empty: - return {}; - break;case expression: - return std::get<expression>(arg)->to_string(); - break;case type_id: - return std::get<type_id>(arg)->to_string(); - break;default: - assert(!"ICE: invalid template_argument state"); - } - // else - return {}; -} - - -struct is_as_expression_node -{ - std::unique_ptr<prefix_expression_node> expr; - - struct term - { - token const* op = {}; - - // This is used if *op is a type - can be null - std::unique_ptr<type_id_node> type = {}; - - // This is used if *op is an expression - can be null - std::unique_ptr<expression_node> expr = {}; - }; - std::vector<term> ops; - - - // API - // - auto is_fold_expression() const - -> bool - { - // This is a fold-expression if any subexpression - // has an identifier named "..." - return expr->is_fold_expression(); - } - - auto is_identifier() const - -> bool - { - return ops.empty() && expr->is_identifier(); - } - - auto is_id_expression() const - -> bool - { - return ops.empty() && expr->is_id_expression(); - } - - auto is_expression_list() const - -> bool - { - return ops.empty() && expr->is_expression_list(); - } - - auto get_expression_list() const - -> expression_list_node const* - { - if (is_expression_list()) { - return expr->get_expression_list(); - } - return {}; - } - - auto is_literal() const - -> bool - { - return ops.empty() && expr->is_literal(); - } - - auto get_postfix_expression_node() const - -> postfix_expression_node * - { - assert(expr); - return expr->get_postfix_expression_node(); - } - - auto is_result_a_temporary_variable() const -> bool { - if (ops.empty()) { - assert(expr); - return expr->is_result_a_temporary_variable(); - } else { - return true; - } - } - - auto to_string() const - -> std::string - { - assert (expr); - auto ret = expr->to_string(); - for (auto const& x : ops) { - assert (x.op); - ret += " " + x.op->to_string(); - if (x.type) { - ret += " " + x.type->to_string(); - } - if (x.expr) { - ret += " " + x.expr->to_string(); - } - } - return ret; - } - - // Internals - // - auto position() const - -> source_position - { - assert (expr); - return expr->position(); - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - assert (expr); - expr->visit(v, depth+1); - for (auto const& x : ops) { - assert (x.op); - v.start(*x.op, depth+1); - if (x.type) { - x.type->visit(v, depth+1); - } - if (x.expr) { - x.expr->visit(v, depth+1); - } - } - v.end(*this, depth); - } -}; - - -expression_node::expression_node() -{ - if (!expression_statement_node::current_expression_statements.empty()) { - my_statement = expression_statement_node::current_expression_statements.back(); - } -} - - -struct id_expression_node -{ - source_position pos; - - enum active { empty=0, qualified, unqualified }; - std::variant< - std::monostate, - std::unique_ptr<qualified_id_node>, - std::unique_ptr<unqualified_id_node> - > id; - - auto template_arguments() const - -> std::vector<template_argument> const& - { - if (is_unqualified()) { - return std::get<unqualified>(id)->template_arguments(); - } - // else - return std::get<qualified>(id)->template_arguments(); - } - - auto is_fold_expression() const - -> bool - { - // This is a fold-expression if any subexpression has - // has an identifier named "..." - auto tok = get_token(); - return tok && *tok == "..."; - } - - auto is_empty() const - -> bool - { - return id.index() == empty; - } - - auto is_qualified() const - -> bool - { - return id.index() == qualified; - } - - auto is_unqualified() const - -> bool - { - return id.index() == unqualified; - } - - auto get_token() const - -> token const* - { - if (id.index() == unqualified) { - return std::get<unqualified>(id)->get_token(); - } - // else - return {}; - } - - auto to_string() const - -> std::string - { - if (id.index() == qualified) { - return std::get<qualified>(id)->to_string(); - } - else if (id.index() == unqualified) { - return std::get<unqualified>(id)->to_string(); - } - // else - return {}; - } - - auto position() const - -> source_position - { - return pos; - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - try_visit<qualified >(id, v, depth); - try_visit<unqualified>(id, v, depth); - v.end(*this, depth); - } -}; - - -postfix_expression_node::~postfix_expression_node() -{ - if (cap_grp) { - cap_grp->remove(this); - } -} - - -auto primary_expression_node::is_fold_expression() const - -> bool -{ - // This is a fold-expression if any subexpression has - // has an identifier named "..." - switch (expr.index()) { - break;case identifier: - return *std::get<identifier>(expr) == "..."; - break;case expression_list: - return expression_list_is_fold_expression; - break;case id_expression: - return std::get<id_expression>(expr)->is_fold_expression(); - break;default: ; // the others can't contain folds - } - return false; -} - - -auto postfix_expression_node::get_first_token_ignoring_this() const - -> token const* -{ - if ( - expr->get_token() - && *expr->get_token() == "this" - && std::ssize(ops) == 1 - && ops[0].op->type() == lexeme::Dot - ) - { - return ops[0].id_expr->get_token(); - } - return expr->get_token(); -} - - -auto postfix_expression_node::to_string() const - -> std::string -{ - assert (expr); - auto ret = expr->to_string(); - - for (auto const& x : ops) { - assert (x.op); - ret += x.op->as_string_view(); - if (x.id_expr) { - ret += x.id_expr->to_string(); - } - if (x.expr_list) { - return "(*ERROR*) temporary alpha limitation: type metafunctions cannot stringize expressions that involve nested expression-lists, declarations, or inspect expressions"; - } - } - - return ret; -} - - -auto postfix_expression_node::visit(auto& v, int depth) - -> void -{ - v.start(*this, depth); - assert (expr); - expr->visit(v, depth+1); - for (auto const& x : ops) { - assert (x.op); - v.start(*x.op, depth+1); - if (x.id_expr) { - x.id_expr->visit(v, depth+1); - } - if (x.expr_list) { - x.expr_list->visit(v, depth+1); - } - } - v.end(*this, depth); -} - - -struct statement_node; - -struct compound_statement_node -{ - source_position open_brace; - source_position close_brace; - std::vector<std::unique_ptr<statement_node>> statements; - - colno_t body_indent = 0; - - compound_statement_node(source_position o = source_position{}); - - auto position() const - -> source_position - { - return open_brace; - } - - auto visit(auto& v, int depth) -> void; -}; - - -struct selection_statement_node -{ - bool is_constexpr = false; - token const* identifier = {}; - source_position else_pos; - std::unique_ptr<logical_or_expression_node> expression; - std::unique_ptr<compound_statement_node> true_branch; - std::unique_ptr<compound_statement_node> false_branch; - bool has_source_false_branch = false; - - auto position() const - -> source_position - { - assert (identifier); - return identifier->position(); - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - assert (identifier); - v.start(*identifier, depth+1); - assert (expression); - expression->visit(v, depth+1); - assert (true_branch); - true_branch->visit(v, depth+1); - if (false_branch) { - false_branch->visit(v, depth+1); - } - v.end(*this, depth); - } -}; - - -struct parameter_declaration_node; - -struct iteration_statement_node -{ - token const* label = {}; - token const* identifier = {}; - std::unique_ptr<assignment_expression_node> next_expression; // if used, else null - std::unique_ptr<logical_or_expression_node> condition; // used for "do" and "while", else null - std::unique_ptr<compound_statement_node> statements; // used for "do" and "while", else null - std::unique_ptr<expression_node> range; // used for "for", else null - std::unique_ptr<parameter_declaration_node> parameter; // used for "for", else null - std::unique_ptr<statement_node> body; // used for "for", else null - bool for_with_in = false;// used for "for," says whether loop variable is 'in' - - auto position() const - -> source_position - { - if (label) { - return label->position(); - } - assert(identifier); - return identifier->position(); - } - - auto visit(auto& v, int depth) - -> void; -}; - - -struct return_statement_node -{ - token const* identifier = {}; - std::unique_ptr<expression_node> expression; - - auto position() const - -> source_position - { - assert(identifier); - return identifier->position(); - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - if (expression) { - expression->visit(v, depth+1); - } - v.end(*this, depth); - } -}; - - -struct alternative_node -{ - std::unique_ptr<unqualified_id_node> name; - token const* is_as_keyword = {}; - - // One of these will be used - std::unique_ptr<type_id_node> type_id; - std::unique_ptr<postfix_expression_node> value; - - source_position equal_sign; - std::unique_ptr<statement_node> statement; - - auto position() const - -> source_position - { - assert(is_as_keyword); - return is_as_keyword->position(); - } - - auto visit(auto& v, int depth) - -> void; -}; - - -struct inspect_expression_node -{ - bool is_constexpr = false; - token const* identifier = {}; - std::unique_ptr<expression_node> expression; - std::unique_ptr<type_id_node> result_type; - source_position open_brace; - source_position close_brace; - - std::vector<std::unique_ptr<alternative_node>> alternatives; - - auto position() const - -> source_position - { - assert(identifier); - return identifier->position(); - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - assert (identifier); - v.start(*identifier, depth+1); - assert (expression); - expression->visit(v, depth+1); - if (result_type) { - result_type->visit(v, depth+1); - } - for (auto&& alt : alternatives) { - alt->visit(v, depth+1); - } - v.end(*this, depth); - } -}; - - -struct contract_node -{ - // Declared first, because it should outlive any owned - // postfix_expressions that could refer to it - capture_group captures; - - source_position open_bracket; - token const* kind = {}; - std::unique_ptr<id_expression_node> group; - std::vector<std::unique_ptr<id_expression_node>> flags; - std::unique_ptr<logical_or_expression_node> condition; - std::unique_ptr<expression_node> message = {}; - - contract_node( source_position pos ) - : open_bracket{pos} - { } - - auto position() const - -> source_position - { - return open_bracket; - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - - assert(kind); - kind->visit(v, depth+1); - - if (group) { - group->visit(v, depth+1); - } - - for (auto const& f : flags) { - f->visit(v, depth+1); - } - - assert(condition); - condition->visit(v, depth+1); - - if (message) { - message->visit(v, depth+1); - } - - v.end(*this, depth); - } -}; - - -struct jump_statement_node -{ - token const* keyword; - token const* label; - - auto position() const - -> source_position - { - assert(keyword); - return keyword->position(); - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - if (keyword) { - keyword->visit(v, depth+1); - } - if (label) { - label->visit(v, depth+1); - } - v.end(*this, depth); - } -}; - - -struct using_statement_node -{ - token const* keyword = {}; - bool for_namespace = false; - std::unique_ptr<id_expression_node> id; - - auto position() const - -> source_position - { - assert(keyword); - return keyword->position(); - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - assert(id); - id->visit(v, depth+1); - v.end(*this, depth); - } -}; - - -struct parameter_declaration_list_node; - -struct statement_node -{ - std::unique_ptr<parameter_declaration_list_node> parameters; - compound_statement_node* compound_parent = nullptr; - - statement_node(compound_statement_node* compound_parent_ = nullptr); - - enum active { expression=0, compound, selection, declaration, return_, iteration, using_, contract, inspect, jump }; - std::variant< - std::unique_ptr<expression_statement_node>, - std::unique_ptr<compound_statement_node>, - std::unique_ptr<selection_statement_node>, - std::unique_ptr<declaration_node>, - std::unique_ptr<return_statement_node>, - std::unique_ptr<iteration_statement_node>, - std::unique_ptr<using_statement_node>, - std::unique_ptr<contract_node>, - std::unique_ptr<inspect_expression_node>, - std::unique_ptr<jump_statement_node> - > statement; - - bool emitted = false; // a note field that's used during lowering to Cpp1 - - bool marked_for_removal = false; // for use during metafunctions which may replace members - - // API - // - auto is_expression () const -> bool { return statement.index() == expression; } - auto is_compound () const -> bool { return statement.index() == compound; } - auto is_selection () const -> bool { return statement.index() == selection; } - auto is_declaration() const -> bool { return statement.index() == declaration; } - auto is_return () const -> bool { return statement.index() == return_; } - auto is_iteration () const -> bool { return statement.index() == iteration; } - auto is_using () const -> bool { return statement.index() == using_; } - auto is_contract () const -> bool { return statement.index() == contract; } - auto is_inspect () const -> bool { return statement.index() == inspect; } - auto is_jump () const -> bool { return statement.index() == jump; } - - template<typename Node> - auto get_if() - -> Node* - { - auto pnode = std::get_if<std::unique_ptr<Node>>(&statement); - if (pnode) { - return pnode->get(); - } - // else - return nullptr; - } - - template<typename Node> - auto get_if() const - -> Node const* - { - auto pnode = std::get_if<std::unique_ptr<Node>>(&statement); - if (pnode) { - return pnode->get(); - } - // else - return nullptr; - } - - auto get_lhs_rhs_if_simple_assignment() const - -> assignment_expression_lhs_rhs - { - if (is_expression()) { - return std::get<expression>(statement)->expr->get_lhs_rhs_if_simple_assignment(); - } - // Else - return {}; - } - - auto to_string() const - -> std::string - { - switch (statement.index()) { - break;case expression: - return std::get<expression>(statement)->to_string(); - break;default: - return "(*ERROR*) temporary alpha limitation: type metafunctions cannot stringize expressions that involve initializer statements other than expression-statements"; - } - } - - // Internals - // - auto position() const - -> source_position; - - auto visit(auto& v, int depth) - -> void; -}; - - -auto alternative_node::visit(auto& v, int depth) - -> void -{ - v.start(*this, depth); - if (name) { - v.start(*name, depth+1); - } - assert (is_as_keyword); - v.start(*is_as_keyword, depth+1); - if (type_id) { - type_id->visit(v, depth+1); - } - else { - assert (value); - value->visit(v, depth+1); - } - assert (statement); - statement->visit(v, depth+1); - v.end(*this, depth); -} - - -auto compound_statement_node::visit(auto& v, int depth) - -> void -{ - v.start(*this, depth); - for (auto const& x : statements) { - assert(x); - x->visit(v, depth+1); - } - v.end(*this, depth); -} - - -struct parameter_declaration_node -{ - source_position pos = {}; - passing_style pass = passing_style::in; - int ordinal = 1; - - enum class modifier { none=0, implicit, virtual_, override_, final_ }; - modifier mod = modifier::none; - - std::unique_ptr<declaration_node> declaration; - - // API - // - auto has_name() const - -> bool; - - auto name() const - -> token const*; - - auto has_name(std::string_view) const - -> bool; - - auto direction() const - -> passing_style - { - return pass; - } - - auto is_implicit() const - -> bool - { - return mod == modifier::implicit; - } - - auto is_virtual() const - -> bool - { - return mod == modifier::virtual_; - } - - auto make_virtual() - -> void - { - mod = modifier::virtual_; - } - - auto is_override() const - -> bool - { - return mod == modifier::override_; - } - - auto is_final() const - -> bool - { - return mod == modifier::final_; - } - - auto is_polymorphic() const - -> bool - { - switch (mod) { - break;case modifier::virtual_: - case modifier::override_: - case modifier::final_: - return true; - break;default: - return false; - } - } - - // Internals - // - auto position() const - -> source_position; - - auto visit(auto& v, int depth) - -> void; -}; - - -struct parameter_declaration_list_node -{ - token const* open_paren = {}; - token const* close_paren = {}; - - std::vector<std::unique_ptr<parameter_declaration_node>> parameters; - - // API - // - auto ssize() const -> auto { - return std::ssize(parameters); - } - - auto operator[](int i) - -> parameter_declaration_node* - { - return parameters[i].get(); - } - - auto operator[](int i) const - -> parameter_declaration_node const* - { - return parameters[i].get(); - } - - // Internals - // - auto position() const - -> source_position - { - assert(open_paren); - return open_paren->position(); - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - for (auto const& x : parameters) { - assert(x); - x->visit(v, depth+1); - } - v.end(*this, depth); - } -}; - - -auto statement_node::visit(auto& v, int depth) - -> void -{ - v.start(*this, depth); - if (parameters) { - parameters->visit(v, depth+1); - } - try_visit<expression >(statement, v, depth); - try_visit<compound >(statement, v, depth); - try_visit<selection >(statement, v, depth); - try_visit<declaration>(statement, v, depth); - try_visit<return_ >(statement, v, depth); - try_visit<iteration >(statement, v, depth); - try_visit<contract >(statement, v, depth); - try_visit<inspect >(statement, v, depth); - try_visit<jump >(statement, v, depth); - v.end(*this, depth); -} - - -struct function_returns_tag { }; - -struct function_type_node -{ - declaration_node* my_decl; - - std::unique_ptr<parameter_declaration_list_node> parameters; - bool throws = false; - - struct single_type_id { - std::unique_ptr<type_id_node> type; - passing_style pass = passing_style::move; - }; - - enum active { empty = 0, id, list }; - std::variant< - std::monostate, - single_type_id, - std::unique_ptr<parameter_declaration_list_node> - > returns; - - std::vector<std::unique_ptr<contract_node>> contracts; - - function_type_node(declaration_node* decl); - - // API - // - auto has_postconditions() const - -> bool; - - auto is_function_with_this() const - -> bool; - - auto is_virtual_function() const - -> bool; - - auto make_function_virtual() - -> bool; - - auto is_defaultable() const - -> bool; - - auto is_constructor() const - -> bool; - - auto is_default_constructor() const - -> bool; - - auto is_move() const - -> bool; - - auto is_swap() const - -> bool; - - auto is_constructor_with_that() const - -> bool; - - auto is_constructor_with_in_that() const - -> bool; - - auto is_constructor_with_move_that() const - -> bool; - - auto is_comparison() const - -> bool; - - auto is_increment_or_decrement() const - -> bool; - - auto is_compound_assignment() const - -> bool; - - auto is_assignment() const - -> bool; - - auto is_assignment_with_that() const - -> bool; - - auto is_assignment_with_in_that() const - -> bool; - - auto is_assignment_with_move_that() const - -> bool; - - auto is_destructor() const - -> bool; - - auto has_declared_return_type() const - -> bool - { - return returns.index() != empty; - } - - auto has_deduced_return_type() const - -> bool - { - return - returns.index() == empty - || ( - returns.index() == id - && std::get<function_type_node::id>(returns).type->is_wildcard() - ) - ; - } - - auto unnamed_return_type_to_string() const - -> std::string - { - if (auto id = std::get_if<function_type_node::id>(&returns)) { - return (*id).type->to_string(); - } - return {}; - } - - auto has_bool_return_type() const - -> bool - { - if (auto id = std::get_if<function_type_node::id>(&returns)) { - if (auto name = (*id).type->get_token()) { - return *name == "bool"; - } - } - return false; - } - - auto has_non_void_return_type() const - -> bool - { - if (auto id = std::get_if<function_type_node::id>(&returns)) { - if (auto name = (*id).type->get_token()) { - return *name != "void"; - } - } - return returns.index() != empty; - } - - auto parameter_count() const - -> int - { - return std::ssize(parameters->parameters); - } - - auto index_of_parameter_named(std::string_view s) const - -> int - { - auto ret = 0; - for (auto& param : parameters->parameters) { - if (param->has_name(s)) { - return ret; - } - ++ret; - } - return -1; - } - - auto has_parameter_named(std::string_view s) const - -> bool - { - for (auto& param : parameters->parameters) { - if (param->has_name(s)) { - return true; - } - } - return false; - } - - auto has_parameter_with_name_and_pass( - std::string_view s, - passing_style pass - ) const - -> bool - { - for (auto& param : parameters->parameters) { - if ( - param->has_name(s) - && param->pass == pass - ) - { - return true; - } - } - return false; - } - - auto first_parameter_name() const - -> std::string; - - auto nth_parameter_type_name(int n) const - -> std::string; - - auto has_in_parameter_named(std::string_view s) const - -> bool - { - return has_parameter_with_name_and_pass(s, passing_style::in); - } - - auto has_out_parameter_named(std::string_view s) const - -> bool - { - return has_parameter_with_name_and_pass(s, passing_style::out); - } - - auto has_move_parameter_named(std::string_view s) const - -> bool - { - return has_parameter_with_name_and_pass(s, passing_style::move); - } - - // Internals - // - auto position() const - -> source_position - { - assert (parameters); - return parameters->position(); - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - assert(parameters); - parameters->visit(v, depth+1); - - if (returns.index() == id) { - auto& r = std::get<id>(returns); - assert(r.type); - r.type->visit(v, depth+1); - } - else if (returns.index() == list) { - auto& r = std::get<list>(returns); - assert(r); - // Inform the visitor that this is a returns list - v.start(function_returns_tag{}, depth); - r->visit(v, depth+1); - v.end(function_returns_tag{}, depth); - } - v.end(*this, depth); - } -}; - - -struct type_node -{ - token const* type; - bool final = false; - - type_node( - token const* t, - bool final_ = false - ) - : type{t} - , final{final_} - { } - - // API - // - auto is_final() const - -> bool - { - return final; - } - - auto make_final() - -> void - { - final = true; - } - - // Internals - // - auto position() const - -> source_position - { - assert(type); - return type->position(); - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - v.end(*this, depth); - } -}; - - -struct namespace_node -{ - token const* namespace_; - - namespace_node(token const* ns) : namespace_{ns} { } - - auto position() const - -> source_position - { - assert(namespace_); - return namespace_->position(); - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - v.end(*this, depth); - } -}; - - -struct alias_node -{ - token const* type = {}; - std::unique_ptr<type_id_node> type_id; // for objects - - enum active : std::uint8_t { a_type, a_namespace, an_object }; - std::variant< - std::unique_ptr<type_id_node>, - std::unique_ptr<id_expression_node>, - std::unique_ptr<expression_node> - > initializer; - - alias_node( token const* t ) : type{t} { } - - // API - // - auto is_type_alias () const -> bool - { return initializer.index() == a_type; } - auto is_namespace_alias() const -> bool - { return initializer.index() == a_namespace; } - auto is_object_alias () const -> bool - { return initializer.index() == an_object; } - - // Internals - // - auto position() const - -> source_position - { - assert (type); - return type->position(); - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - - try_visit<a_type >(initializer, v, depth+1); - try_visit<a_namespace>(initializer, v, depth+1); - try_visit<an_object >(initializer, v, depth+1); - - v.end(*this, depth); - } -}; - - -enum class accessibility { default_ = 0, public_, protected_, private_ }; - -auto to_string(accessibility a) - -> std::string -{ - switch (a) { - break;case accessibility::public_ : return "public"; - break;case accessibility::protected_: return "protected"; - break;case accessibility::private_ : return "private"; - break;default: assert(a == accessibility::default_); - } - return "default"; -} - - -struct declaration_identifier_tag { }; - -struct declaration_node -{ - // The capture_group is declared first, because it should outlive - // any owned postfix_expressions that could refer to it - capture_group captures; - source_position pos; - bool is_variadic = false; - bool is_constexpr = false; - bool terse_no_equals = false; - std::unique_ptr<unqualified_id_node> identifier; - accessibility access = accessibility::default_; - - enum active : std::uint8_t { a_function, an_object, a_type, a_namespace, an_alias }; - std::variant< - std::unique_ptr<function_type_node>, - std::unique_ptr<type_id_node>, - std::unique_ptr<type_node>, - std::unique_ptr<namespace_node>, - std::unique_ptr<alias_node> - > type; - - std::vector<std::unique_ptr<id_expression_node>> metafunctions; - std::unique_ptr<parameter_declaration_list_node> template_parameters; - source_position requires_pos = {}; - std::unique_ptr<logical_or_expression_node> requires_clause_expression; - - source_position equal_sign = {}; - std::unique_ptr<statement_node> initializer; - - declaration_node* parent_declaration = {}; - statement_node* my_statement = {}; - - // Attributes currently configurable only via metafunction API, - // not directly in the base language grammar - bool member_function_generation = true; - - // Cache some context - bool is_template_parameter = false; - bool is_parameter = false; - - // Constructor - // - declaration_node(declaration_node* parent) - : parent_declaration{parent} - { } - - // API - // - auto type_member_mark_for_removal() - -> bool - { - if (my_statement) { - my_statement->marked_for_removal = true; - return true; - } - return false; - } - - auto type_remove_marked_members() - -> void - { - assert (is_type() && initializer && initializer->is_compound()); - auto compound_stmt = initializer->get_if<compound_statement_node>(); - assert (compound_stmt); - - // Note: This loop is a careful use of the brittle STL "erase" idiom. Do not change this - // loop without carefully ensuring it remains safe against iterator invalidation. - // (Especially don't change this to a for loop with a "++i" iteration-expression.) - auto i = compound_stmt->statements.begin(); - while (i != compound_stmt->statements.end()) - { - if ((*i)->marked_for_removal) { - i = compound_stmt->statements.erase(i); // these two branches ... - } - else { - ++i; // ... must stay together - } - } - } - - auto type_remove_all_members() - -> void - { - assert (is_type() && initializer && initializer->is_compound()); - auto body = initializer->get_if<compound_statement_node>(); - assert (body); - - // Drop all statements in the body, which should self-deregister all our 'captures' - // - (only) statements in the body should have been able to refer to 'captures' - body->statements.clear(); - assert(captures.members.empty()); - } - - auto type_disable_member_function_generation() - -> void - { - member_function_generation = false; - } - - auto object_type() const - -> std::string - { - if (!is_object()) { - return "(*ERROR*) not an object"; - } - // Else - return std::get<an_object>(type)->to_string(); - } - - auto object_initializer() const - -> std::string - { - if (!is_object()) { - return "(*ERROR*) not an object"; - } - else if (initializer) { - return initializer->to_string(); - } - // Else - return ""; - } - - auto get_parent() const - -> declaration_node* - { - return parent_declaration; - } - - auto is_public() const - -> bool - { - return access == accessibility::public_; - } - - auto is_protected() const - -> bool - { - return access == accessibility::protected_; - } - - auto is_private() const - -> bool - { - return access == accessibility::private_; - } - - auto is_default_access() const - -> bool - { - return access == accessibility::default_; - } - -private: - auto set_access(accessibility a) - -> bool - { - if (is_default_access()) { - access = a; - } - return access == a; - } - -public: - auto make_public() - -> bool - { - return set_access( accessibility::public_ ); - } - - auto make_protected() - -> bool - { - return set_access( accessibility::protected_ ); - } - - auto make_private() - -> bool - { - return set_access( accessibility::private_ ); - } - - auto has_name() const - -> bool - { - return - identifier - && identifier->identifier - ; - } - - auto name() const - -> token const* - { - if (!identifier) { - return nullptr; - } - // Else - return identifier->identifier; - } - - auto has_name(std::string_view s) const - -> bool - { - return - has_name() - && *name() == s - ; - } - - auto has_initializer() const - -> bool - { - return initializer != nullptr; - } - - auto parameter_count() const - -> int - { - if (!is_function()) { - return -1; - } - return std::get<a_function>(type)->parameter_count(); - } - - auto index_of_parameter_named(std::string_view s) const - -> int - { - if (!is_function()) { - return -1; - } - return std::get<a_function>(type)->index_of_parameter_named(s); - } - - auto has_parameter_named(std::string_view s) const - -> bool - { - if (!is_function()) { - return false; - } - return std::get<a_function>(type)->has_parameter_named(s); - } - - auto has_in_parameter_named(std::string_view s) const - -> bool - { - if (!is_function()) { - return false; - } - return std::get<a_function>(type)->has_in_parameter_named(s); - } - - auto has_out_parameter_named(std::string_view s) const - -> bool - { - if (!is_function()) { - return false; - } - return std::get<a_function>(type)->has_out_parameter_named(s); - } - - auto has_move_parameter_named(std::string_view s) const - -> bool - { - if (!is_function()) { - return false; - } - return std::get<a_function>(type)->has_move_parameter_named(s); - } - - auto nth_parameter_type_name(int n) const - -> std::string - { - if (!is_function()) { - return ""; - } - return std::get<a_function>(type)->nth_parameter_type_name(n); - } - - auto is_global () const -> bool - { return !parent_declaration; } - - auto is_function () const -> bool - { return type.index() == a_function; } - auto is_object () const -> bool - { return type.index() == an_object; } - auto is_base_object() const -> bool - { return is_object() && has_name("this"); } - auto is_member_object() const -> bool - { return is_object() && !has_name("this"); } - auto is_concept () const -> bool - { return type.index() == an_object && get<an_object>(type)->is_concept(); } - auto is_type () const -> bool - { return type.index() == a_type; } - auto is_namespace() const -> bool - { return type.index() == a_namespace; } - auto is_alias() const -> bool - { return type.index() == an_alias; } - - auto is_type_alias () const -> bool - { return is_alias() && std::get<an_alias>(type)->is_type_alias(); } - auto is_namespace_alias() const -> bool - { return is_alias() && std::get<an_alias>(type)->is_namespace_alias(); } - auto is_object_alias () const -> bool - { return is_alias() && std::get<an_alias>(type)->is_object_alias(); } - - auto is_function_expression () const -> bool - { return is_function() && !identifier; } - - auto is_polymorphic() const // has base types or virtual functions - -> bool - { - for (auto& decl : get_type_scope_declarations()) { - if ( - decl->has_name("this") - || decl->is_virtual_function() - ) - { - return true; - } - } - return false; - } - - // Do we know that this cannot be a copy constructible type? - auto cannot_be_a_copy_constructible_type() const - -> bool - { - // If we're not a type, we're not a copyable type - if (!is_type()) { - return true; - } - - // Else if we're letting Cpp1 generate SMFs, we're likely copyable - if (!member_function_generation) { - return false; - } - - // Else if we have a copy constructor, we're copyable - for (auto& decl : get_type_scope_declarations()) - if (decl->is_constructor_with_that()) - { - return false; - } - - // Else there can't be a copy constructor - return true; - } - - auto parent_is_function () const -> bool - { return parent_declaration && parent_declaration->type.index() == a_function; } - auto parent_is_object () const -> bool - { return parent_declaration && parent_declaration->type.index() == an_object; } - auto parent_is_type () const -> bool - { return parent_declaration && parent_declaration->type.index() == a_type; } - auto parent_is_namespace () const -> bool - { return !parent_declaration || parent_declaration->type.index() == a_namespace; } - auto parent_is_alias () const -> bool - { return parent_declaration && parent_declaration->type.index() == an_alias; } - - auto parent_is_type_alias () const -> bool - { return parent_declaration && parent_declaration->is_alias() && std::get<an_alias>(parent_declaration->type)->is_type_alias(); } - auto parent_is_namespace_alias() const -> bool - { return parent_declaration && parent_declaration->is_alias() && std::get<an_alias>(parent_declaration->type)->is_namespace_alias(); } - auto parent_is_object_alias () const -> bool - { return parent_declaration && parent_declaration->is_alias() && std::get<an_alias>(parent_declaration->type)->is_object_alias(); } - - auto is_inside_global_unnamed_function() const -> bool { - auto parent = parent_declaration; - // Get outside all nested function expressions - while (parent && parent->is_function() && !parent->has_name()) { - parent = parent->parent_declaration; - } - return !parent; - } - - auto parent_is_polymorphic() const -> bool - { return parent_declaration && parent_declaration->is_polymorphic(); } - - enum which { - functions = 1, - objects = 2, - types = 4, - aliases = 8, - all = functions|objects|types|aliases - }; - -private: - // This helper is a const function that delivers pointers - // to non-const... because this is the best way I can - // think of right now to write the following two get_ - // functions (without duplicating their bodies, and - // without resorting to const_casts) - auto gather_type_scope_declarations(which w) const - -> std::vector<declaration_node*> - { - if ( - !is_type() - || !initializer - || !initializer->is_compound() - ) - { - return {}; - } - - auto compound_stmt = initializer->get_if<compound_statement_node>(); - assert (compound_stmt); - - auto ret = std::vector<declaration_node*>{}; - for (auto& o : compound_stmt->statements) - { - auto decl = o->get_if<declaration_node>(); - if (decl) - { - assert( - !decl->is_namespace() - && "ICE: a type shouldn't be able to contain a namespace" - ); - if ( - (w & functions && decl->is_function()) - || (w & objects && decl->is_object() ) - || (w & types && decl->is_type() ) - || (w & aliases && decl->is_alias() ) - ) - { - ret.push_back(decl); - } - } - } - - return ret; - } - -public: - auto get_type_scope_declarations(which w = all) - -> std::vector<declaration_node*> - { - // Only want to return the gather_ results as - // non-const* in a non-const function - return gather_type_scope_declarations(w); - } - - auto get_type_scope_declarations(which w = all) const - -> std::vector<declaration_node const*> - { - // Convert the gather_ results to const* - auto tmp = gather_type_scope_declarations(w); - return std::vector<declaration_node const*>(tmp.begin(), tmp.end()); - } - - - auto add_type_member( std::unique_ptr<statement_node>&& statement ) - -> bool - { - if ( - !is_type() - || !initializer - || !initializer->is_compound() - || !statement->is_declaration() - ) - { - return false; - } - - // Tell this declaration statement that we are its new parent - // and check to ensure that it doesn't already have a parent - // (that shouldn't happen because we should only get here for a - // generated statement that hasn't been added elsewhere yet) - auto decl = statement->get_if<declaration_node>(); - assert( - decl - && !decl->parent_declaration - ); - decl->parent_declaration = this; - - // And actually adopt it into our list of statements - auto compound_stmt = initializer->get_if<compound_statement_node>(); - assert (compound_stmt); - compound_stmt->statements.push_back(std::move(statement)); - return true; - } - - - auto add_function_initializer( std::unique_ptr<statement_node>&& statement ) - -> bool - { - if ( - !is_function() - || initializer - ) - { - return false; - } - - // Adopt it as our initializer statement - initializer = std::move( statement ); - return true; - } - - - auto get_decl_if_type_scope_object_name_before_a_base_type( std::string_view s ) const - -> declaration_node const* - { - declaration_node const* ret = {}; - - // If it's 'this' then it can't be an object name - if (s == "this") { - return {}; - } - - // Navigate to the nearest enclosing type - auto decl = this; - while ( - !decl->is_type() - && decl->parent_declaration - ) - { - decl = decl->parent_declaration; - } - - if (!decl->is_type()) { - return {}; - } - - // Look for a name match and if so remember the type, - // and look for a base type after that match - auto objects = decl->get_type_scope_declarations(); - auto found_name = false; - auto found_later_base_type = false; - - for (auto& o : objects) { - if (o->is_alias()) { - continue; - } - if (o->has_name(s)) { - found_name = true; - ret = o; - } - if (o->has_name("this")) { - if (found_name) { - found_later_base_type = true; - break; - } - } - } - - // If we didn't find a later base type, discard any name match - if (!found_later_base_type) { - ret = {}; - } - - return ret; - } - - - auto get_initializer_statements() const - -> std::vector<statement_node*> - { - if (!initializer) { - return {}; - } - - auto ret = std::vector<statement_node*>{}; - // For non-compound initializers, we want just that statement - if (!initializer->is_compound()) - { - ret.push_back(initializer.get()); - } - - // Else for compound initializers, we want the compound_statement's statements - else - { - auto compound_stmt = initializer->get_if<compound_statement_node>(); - assert (compound_stmt); - for (auto& o : compound_stmt->statements) { - ret.push_back(o.get()); - } - } - - return ret; - } - - auto is_function_with_this() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_function_with_this(); - } - // else - return false; - } - - auto is_virtual_function() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_virtual_function(); - } - // else - return false; - } - - auto is_type_final() const - -> bool - { - if (auto t = std::get_if<a_type>(&type)) { - return (*t)->is_final(); - } - // else - return false; - } - - auto make_type_final() - -> bool - { - if (auto t = std::get_if<a_type>(&type)) { - (*t)->make_final(); - return true; - } - // else - return false; - } - - auto make_function_virtual() - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->make_function_virtual(); - } - // else - return false; - } - - auto is_defaultable_function() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_defaultable(); - } - // else - return false; - } - - auto is_constructor() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_constructor(); - } - // else - return false; - } - - auto is_default_constructor() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_default_constructor(); - } - // else - return false; - } - - auto is_move() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_move(); - } - // else - return false; - } - - auto is_swap() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_swap(); - } - // else - return false; - } - - auto is_constructor_with_that() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_constructor_with_that(); - } - // else - return false; - } - - auto is_constructor_with_in_that() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_constructor_with_in_that(); - } - // else - return false; - } - - auto is_constructor_with_move_that() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_constructor_with_move_that(); - } - // else - return false; - } - - auto is_comparison() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_comparison(); - } - // else - return false; - } - - auto is_increment_or_decrement() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_increment_or_decrement(); - } - // else - return false; - } - - auto is_compound_assignment() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_compound_assignment(); - } - // else - return false; - } - - auto is_assignment() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_assignment(); - } - // else - return false; - } - - auto is_assignment_with_that() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_assignment_with_that(); - } - // else - return false; - } - - auto is_assignment_with_in_that() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_assignment_with_in_that(); - } - // else - return false; - } - - auto is_assignment_with_move_that() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_assignment_with_move_that(); - } - // else - return false; - } - - struct declared_value_set_funcs { - declaration_node const* out_this_in_that = {}; - declaration_node const* out_this_move_that = {}; - declaration_node const* inout_this_in_that = {}; - declaration_node const* inout_this_move_that = {}; - std::vector<std::string> assignments_from = {}; - }; - - auto find_declared_value_set_functions() const - -> declared_value_set_funcs - { - if (!initializer) { - return {}; - } - - auto compound_stmt = initializer->get_if<compound_statement_node>(); - assert (compound_stmt); - - auto ret = declared_value_set_funcs{}; - for (auto& o : compound_stmt->statements) - { - auto decl = o->get_if<declaration_node>(); - if (decl) - { - if (decl->is_constructor_with_in_that()) { - ret.out_this_in_that = decl; - } - if (decl->is_constructor_with_move_that()) { - ret.out_this_move_that = decl; - } - if (decl->is_assignment_with_in_that()) { - ret.inout_this_in_that = decl; - } - if (decl->is_assignment_with_move_that()) { - ret.inout_this_move_that = decl; - } - if (decl->is_assignment() && !decl->is_assignment_with_that()) { - ret.assignments_from.emplace_back( decl->nth_parameter_type_name(2) ); - } - } - } - - return ret; - } - - auto find_parent_declared_value_set_functions() const - -> declared_value_set_funcs - { - if (parent_is_type()) { - return parent_declaration->find_declared_value_set_functions(); - } - // else - return {}; - } - - - auto is_destructor() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->is_destructor(); - } - // else - return false; - } - - auto has_declared_return_type() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->has_declared_return_type(); - } - // else - return false; - } - - auto has_deduced_return_type() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->has_deduced_return_type(); - } - // else - return false; - } - - auto get_function_parameters() - -> std::vector<parameter_declaration_node const*> - { - if (!is_function()) { - return {}; - } - // else - auto ret = std::vector<parameter_declaration_node const*>{}; - for (auto& param : std::get<a_function>(type)->parameters->parameters) { - ret.push_back( param.get() ); - } - return ret; - } - - auto unnamed_return_type_to_string() const - -> std::string - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->unnamed_return_type_to_string(); - } - // else - return {}; - } - - auto has_bool_return_type() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->has_bool_return_type(); - } - // else - return false; - } - - auto has_non_void_return_type() const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->has_non_void_return_type(); - } - // else - return false; - } - - auto has_parameter_with_name_and_pass( - std::string_view s, - passing_style pass - ) const - -> bool - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->has_parameter_with_name_and_pass(s, pass); - } - // else - return false; - } - - auto first_parameter_name() const - -> std::string - { - if (auto func = std::get_if<a_function>(&type)) { - return (*func)->first_parameter_name(); - } - // else - return ""; - } - - auto is_binary_comparison_function() const - -> bool - { - return - is_function() - && ( - has_name("operator==") - || has_name("operator!=") - || has_name("operator<") - || has_name("operator<=") - || has_name("operator>") - || has_name("operator>=") - ); - } - - auto is_const() const - -> bool - { - return - type.index() == an_object - && !std::get<an_object>(type)->pc_qualifiers.empty() - && *std::get<an_object>(type)->pc_qualifiers.front() == "const" - ; - } - - auto has_wildcard_type() const - -> bool - { - return - type.index() == an_object - && std::get<an_object>(type)->is_wildcard() - ; - } - - auto get_object_type() const - -> type_id_node const* - { - if (type.index() == an_object) { - return std::get<an_object>(type).get(); - } - // Else - return {}; - } - - // Internals - // - auto position() const - -> source_position - { - if (identifier) { - return identifier->position(); - } - return pos; - } - - auto visit(auto& v, int depth) - -> void - { - v.start(*this, depth); - - v.start(declaration_identifier_tag{}, depth); - if (identifier) { - identifier->visit(v, depth+1); - } - v.end(declaration_identifier_tag{}, depth); - - try_visit<a_function >(type, v, depth+1); - try_visit<an_object >(type, v, depth+1); - try_visit<a_type >(type, v, depth+1); - try_visit<a_namespace>(type, v, depth+1); - try_visit<an_alias >(type, v, depth+1); - - for (auto& m : metafunctions) { - assert(m); - m->visit(v, depth+1); - } - - if (initializer) { - initializer->visit(v, depth+1); - } - - v.end(*this, depth); - } -}; - - -compound_statement_node::compound_statement_node(source_position o) - : open_brace{o} -{ } - - -statement_node::statement_node(compound_statement_node* compound_parent_) - : compound_parent{ compound_parent_ } -{ } - - -function_type_node::function_type_node(declaration_node* decl) - : my_decl{decl} -{ } - - -auto parameter_declaration_node::has_name() const - -> bool -{ - return declaration->has_name(); -} - - -auto parameter_declaration_node::name() const - -> token const* -{ - return declaration->name(); -} - - -auto parameter_declaration_node::has_name(std::string_view s) const - -> bool -{ - return declaration->has_name(s); -} - - -auto function_type_node::first_parameter_name() const - -> std::string -{ - if (std::ssize(parameters->parameters) > 0) - { - assert (parameters->parameters[0]->declaration->name()); - return parameters->parameters[0]->declaration->name()->to_string(); - } - // Else - return ""; -} - -auto function_type_node::nth_parameter_type_name(int n) const - -> std::string -{ - if (std::ssize(parameters->parameters) >= n) - { - return parameters->parameters[n-1]->declaration->get_object_type()->to_string(); - } - // Else - return ""; -} - - -auto function_type_node::has_postconditions() const - -> bool -{ - return - std::find_if( - contracts.begin(), - contracts.end(), - [](auto const& e){ return *e->kind == "post"; } - ) != contracts.end(); -} - -auto function_type_node::is_function_with_this() const - -> bool -{ - if ( - (*parameters).ssize() > 0 - && (*parameters)[0]->has_name("this") - ) - { - return true; - } - return false; -} - - -auto function_type_node::is_virtual_function() const - -> bool -{ - if ( - (*parameters).ssize() > 0 - && (*parameters)[0]->has_name("this") - && (*parameters)[0]->is_virtual() - ) - { - return true; - } - return false; -} - - -auto function_type_node::make_function_virtual() - -> bool -{ - if (is_function_with_this()) { - (*parameters)[0]->make_virtual(); - return true; - } - return false; -} - - -auto function_type_node::is_defaultable() const - -> bool -{ - if ( - my_decl->has_name("operator==") - || my_decl->has_name("operator<=>") - ) - { - return true; - } - return false; -} - - -auto function_type_node::is_constructor() const - -> bool -{ - if ( - (*parameters).ssize() > 0 - && (*parameters)[0]->has_name("this") - && (*parameters)[0]->direction() == passing_style::out - ) - { - assert(my_decl->has_name("operator=")); - return true; - } - return false; -} - - -auto function_type_node::is_default_constructor() const - -> bool -{ - if ( - is_constructor() - && (*parameters).ssize() == 1 - ) - { - return true; - } - return false; -} - - -auto function_type_node::is_move() const - -> bool -{ - if ( - (is_constructor() || is_assignment()) - && (*parameters).ssize() == 2 - && (*parameters)[1]->has_name("that") - && (*parameters)[1]->direction() == passing_style::move - ) - { - return true; - } - return false; -} - - -auto function_type_node::is_swap() const - -> bool -{ - assert (my_decl); - if ( - my_decl->has_name("swap") - && (*parameters).ssize() == 2 - && (*parameters)[1]->has_name("that") - ) - { - return true; - } - return false; -} - - -auto function_type_node::is_constructor_with_that() const - -> bool -{ - if ( - is_constructor() - && (*parameters).ssize() == 2 - && (*parameters)[1]->has_name("that") - ) - { - return true; - } - return false; -} - - -auto function_type_node::is_assignment_with_that() const - -> bool -{ - if ( - is_assignment() - && (*parameters).ssize() == 2 - && (*parameters)[1]->has_name("that") - ) - { - return true; - } - return false; -} - - -auto function_type_node::is_constructor_with_in_that() const - -> bool -{ - if ( - is_constructor() - && (*parameters).ssize() == 2 - && (*parameters)[1]->has_name("that") - && (*parameters)[1]->direction() == passing_style::in - ) - { - return true; - } - return false; -} - - -auto function_type_node::is_constructor_with_move_that() const - -> bool -{ - if ( - is_constructor() - && (*parameters).ssize() == 2 - && (*parameters)[1]->has_name("that") - && (*parameters)[1]->direction() == passing_style::move - ) - { - return true; - } - return false; -} - - -auto function_type_node::is_comparison() const - -> bool -{ - if ( - ( - my_decl->has_name("operator==") - || my_decl->has_name("operator!=") - || my_decl->has_name("operator<") - || my_decl->has_name("operator<=") - || my_decl->has_name("operator>") - || my_decl->has_name("operator>=") - || my_decl->has_name("operator<=>") - ) - ) - { - return true; - } - return false; -} - - -auto function_type_node::is_increment_or_decrement() const - -> bool -{ - if ( - my_decl->has_name("operator++") - || my_decl->has_name("operator--") - ) - { - return true; - } - return false; -} - - -auto function_type_node::is_compound_assignment() const - -> bool -{ - if ( - ( - my_decl->has_name("operator+=") - || my_decl->has_name("operator-=") - || my_decl->has_name("operator*=") - || my_decl->has_name("operator/=") - || my_decl->has_name("operator%=") - || my_decl->has_name("operator&=") - || my_decl->has_name("operator|=") - || my_decl->has_name("operator^=") - || my_decl->has_name("operator<<=") - || my_decl->has_name("operator>>=") - ) - && (*parameters).ssize() > 1 - && (*parameters)[0]->has_name("this") - && (*parameters)[0]->direction() == passing_style::inout - ) - { - return true; - } - return false; -} - - -auto function_type_node::is_assignment() const - -> bool -{ - if ( - my_decl->has_name("operator=") - && (*parameters).ssize() > 1 - && (*parameters)[0]->has_name("this") - && (*parameters)[0]->direction() == passing_style::inout - ) - { - return true; - } - return false; -} - - -auto function_type_node::is_assignment_with_in_that() const - -> bool -{ - if ( - is_assignment() - && (*parameters).ssize() == 2 - && (*parameters)[1]->has_name("that") - && (*parameters)[1]->direction() == passing_style::in - ) - { - return true; - } - return false; -} - - -auto function_type_node::is_assignment_with_move_that() const - -> bool -{ - if ( - is_assignment() - && (*parameters).ssize() == 2 - && (*parameters)[1]->has_name("that") - && (*parameters)[1]->direction() == passing_style::move - ) - { - return true; - } - return false; -} - - -auto function_type_node::is_destructor() const - -> bool -{ - if ( - my_decl->has_name("operator=") - && (*parameters).ssize() == 1 - && (*parameters)[0]->has_name("this") - && (*parameters)[0]->direction() == passing_style::move - ) - { - return true; - } - return false; -} - - -auto primary_expression_node::template_arguments() const - -> std::vector<template_argument> const& -{ - if (expr.index() == id_expression) { - return std::get<id_expression>(expr)->template_arguments(); - } - // else - return no_template_args; -} - - -auto primary_expression_node::get_token() const - -> token const* -{ - if (expr.index() == identifier) { - return std::get<identifier>(expr); - } - else if (expr.index() == id_expression) { - return std::get<id_expression>(expr)->get_token(); - } - else if (expr.index() == literal) { - return std::get<literal>(expr)->get_token(); - } - // else (because we're deliberately ignoring the other - // options which are more than a single token) - return {}; -} - - -auto primary_expression_node::to_string() const - -> std::string -{ - switch (expr.index()) - { - break;case empty: - return {}; - - break;case identifier: { - auto const& s = std::get<identifier>(expr); - assert (s); - return s->to_string(); - } - - break;case id_expression: { - auto const& s = std::get<id_expression>(expr); - assert (s); - return s->to_string(); - } - - break;case literal: { - auto const& i = std::get<literal>(expr); - assert (i); - return i->to_string(); - } - - break;default: - return "(*ERROR*) temporary alpha limitation: type metafunctions cannot stringize expressions that involve nested expression-lists, declarations, or inspect expressions"; - } -} - - -auto primary_expression_node::position() const - -> source_position -{ - switch (expr.index()) - { - break;case empty: - return { 0, 0 }; - - break;case identifier: { - auto const& s = std::get<identifier>(expr); - assert (s); - return s->position(); - } - - break;case expression_list: { - auto const& s = std::get<expression_list>(expr); - assert (s); - return s->position(); - } - - break;case id_expression: { - auto const& s = std::get<id_expression>(expr); - assert (s); - return s->position(); - } - - break;case declaration: { - auto const& s = std::get<declaration>(expr); - assert (s); - return s->position(); - } - - break;case inspect: { - auto const& i = std::get<inspect>(expr); - assert (i); - return i->position(); - } - - break;case literal: { - auto const& i = std::get<literal>(expr); - assert (i); - return i->position(); - } - - break;default: - assert (!"illegal primary_expression_node state"); - return { 0, 0 }; - } -} - - -auto primary_expression_node::visit(auto& v, int depth) - -> void -{ - v.start(*this, depth); - try_visit<identifier >(expr, v, depth); - try_visit<expression_list>(expr, v, depth); - try_visit<id_expression >(expr, v, depth); - try_visit<declaration >(expr, v, depth); - try_visit<inspect >(expr, v, depth); - try_visit<literal >(expr, v, depth); - v.end(*this, depth); -} - - -struct next_expression_tag { }; -struct loop_body_tag { token const* identifier; }; - -auto iteration_statement_node::visit(auto& v, int depth) - -> void -{ - v.start(*this, depth); - if (label) { - label->visit(v, depth+1); - } - if (identifier) { - identifier->visit(v, depth+1); - } - if (statements) { - statements->visit(v, depth+1); - } - if (next_expression) { - v.start(next_expression_tag{}, depth); - next_expression->visit(v, depth+1); - v.end(next_expression_tag{}, depth); - } - if (condition) { - assert(!range && !body); - condition->visit(v, depth+1); - } - else { - assert(range && parameter && body); - range->visit(v, depth+1); - v.start(loop_body_tag{identifier}, depth); - parameter->visit(v, depth+1); - body->visit(v, depth+1); - } - v.end(*this, depth); -} - - -auto statement_node::position() const - -> source_position -{ - switch (statement.index()) - { - break;case expression: { - auto const& s = std::get<expression>(statement); - assert (s); - return s->position(); - } - - break;case compound: { - auto const& s = std::get<compound>(statement); - assert (s); - return s->position(); - } - - break;case selection: { - auto const& s = std::get<selection>(statement); - assert (s); - return s->position(); - } - - break;case declaration: { - auto const& s = std::get<declaration>(statement); - assert (s); - return s->position(); - } - - break;case return_: { - auto const& s = std::get<return_>(statement); - assert (s); - return s->position(); - } - - break;case iteration: { - auto const& s = std::get<iteration>(statement); - assert (s); - return s->position(); - } - - break;case using_: { - auto const& s = std::get<using_>(statement); - assert (s); - return s->position(); - } - - break;case contract: { - auto const& s = std::get<contract>(statement); - assert (s); - return s->position(); - } - - break;case inspect: { - auto const& s = std::get<inspect>(statement); - assert (s); - return s->position(); - } - - break;case jump: { - auto const& s = std::get<jump>(statement); - assert (s); - return s->position(); - } - - break;default: - assert (!"illegal statement_node state"); - return { 0, 0 }; - } -} - - -auto parameter_declaration_node::position() const - -> source_position -{ - assert (declaration); - return pos; -} - - -auto parameter_declaration_node::visit(auto& v, int depth) - -> void -{ - v.start(*this, depth); - assert(declaration); - declaration->visit(v, depth + 1); - v.end(*this, depth); -} - - -struct translation_unit_node -{ - std::vector< std::unique_ptr<declaration_node> > declarations; - - auto position() const -> source_position - { - if (std::ssize(declarations) > 0) { - return declarations.front()->position(); - } - return {}; - } - - auto visit(auto& v, int depth) -> void - { - v.start(*this, depth); - for (auto const& x : declarations) { - assert(x); - x->visit(v, depth + 1); - } - v.end(*this, depth); - } -}; - - -//----------------------------------------------------------------------- -// -// pretty_print_visualize: pretty-prints Cpp2 ASTs -// -//----------------------------------------------------------------------- -// -auto pretty_print_visualize(token const& n, int indent) - -> std::string; -auto pretty_print_visualize(primary_expression_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(literal_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(prefix_expression_node const& n, int indent) - -> std::string; -template< - String Name, - typename Term -> -auto pretty_print_visualize(binary_expression_node<Name,Term> const& n, int indent) - -> std::string; -auto pretty_print_visualize(expression_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(expression_list_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(expression_statement_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(postfix_expression_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(unqualified_id_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(qualified_id_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(type_id_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(is_as_expression_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(id_expression_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(compound_statement_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(selection_statement_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(iteration_statement_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(return_statement_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(alternative_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(inspect_expression_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(contract_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(jump_statement_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(using_statement_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(statement_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(parameter_declaration_node const& n, int indent, bool is_template_param = false) - -> std::string; -auto pretty_print_visualize(parameter_declaration_list_node const& n, int indent, bool is_template_param_list = false) - -> std::string; -auto pretty_print_visualize(function_type_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(type_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(namespace_node const& n, int indent) - -> std::string; -auto pretty_print_visualize(declaration_node const& n, int indent, bool include_metafunctions_list = false) - -> std::string; - - - -//----------------------------------------------------------------------- -// pre: Get an indentation prefix -// -inline static int indent_spaces = 2; -inline static std::string indent_str = std::string( 1024, ' ' ); // "1K should be enough for everyone" - -auto pre(int indent) - -> std::string_view -{ - assert (indent >= 0); - return { - indent_str.c_str(), - as<size_t>( std::min( indent*indent_spaces, _as<int>(std::ssize(indent_str))) ) - }; -} - - -//----------------------------------------------------------------------- -// try_pretty_print_visualize -// -// Helper to emit whatever is in a variant where each -// alternative is a smart pointer -// -template <int I> -auto try_pretty_print_visualize( - auto& v, - auto&&... more -) - -> std::string -{ - if (v.index() == I) { - auto const& alt = std::get<I>(v); - assert (alt); - return pretty_print_visualize (*alt, CPP2_FORWARD(more)...); - } - return ""; -} - - -auto pretty_print_visualize(token const& t, int) - -> std::string -{ - return t.to_string(); -} - - -auto pretty_print_visualize(primary_expression_node const& n, int indent) - -> std::string -{ - auto ret = std::string{}; - - ret += try_pretty_print_visualize<primary_expression_node::identifier >(n.expr, indent); - ret += try_pretty_print_visualize<primary_expression_node::expression_list>(n.expr, indent); - ret += try_pretty_print_visualize<primary_expression_node::id_expression >(n.expr, indent); - ret += try_pretty_print_visualize<primary_expression_node::declaration >(n.expr, indent); - ret += try_pretty_print_visualize<primary_expression_node::inspect >(n.expr, indent); - ret += try_pretty_print_visualize<primary_expression_node::literal >(n.expr, indent); - - return ret; -} - - -auto pretty_print_visualize(literal_node const& n, int) - -> std::string -{ - // TODO: This is an initial visualizer implementation, and still - // skips a few rarer things (such as raw string literals) - - assert(n.literal); - - auto ret = n.literal->to_string(); - - if (n.user_defined_suffix) { - ret += n.user_defined_suffix->as_string_view(); - } - - return ret; -} - - -auto pretty_print_visualize(prefix_expression_node const& n, int indent) - -> std::string -{ - assert(n.expr); - - auto ret = std::string{}; - - for (auto& op : n.ops) { - assert(op); - ret += op->as_string_view(); - } - - ret += pretty_print_visualize(*n.expr, indent); - - return ret; -} - - -template< - String Name, - typename Term -> -auto pretty_print_visualize(binary_expression_node<Name,Term> const& n, int indent) - -> std::string -{ - assert(n.expr); - - auto ret = pretty_print_visualize(*n.expr, indent); - for (auto& term : n.terms) { - assert(term.op && term.expr); - ret += " " + term.op->to_string() - + " " + pretty_print_visualize(*term.expr, indent); - } - return ret; -} - - -auto pretty_print_visualize(expression_node const& n, int indent) - -> std::string -{ - assert(n.expr); - return pretty_print_visualize(*n.expr, indent); -} - - -auto pretty_print_visualize(expression_list_node const& n, int indent) - -> std::string -{ - assert(n.open_paren && n.close_paren); - - auto ret = n.open_paren->to_string(); - - for (auto i = 0; auto& expr : n.expressions) { - assert(expr.expr); - if ( - expr.pass == passing_style::out - || expr.pass == passing_style::move - || expr.pass == passing_style::forward - ) - { - ret += to_string_view(expr.pass) + std::string{" "}; - } - ret += pretty_print_visualize(*expr.expr, indent); - if (++i < std::ssize(n.expressions)) { - ret += ", "; - } - } - - ret += n.close_paren->as_string_view(); - - return ret; -} - - -auto pretty_print_visualize(expression_statement_node const& n, int indent) - -> std::string -{ - assert(n.expr); - - auto ret = pretty_print_visualize(*n.expr, indent); - - if (n.has_semicolon && ret.back() != ';') { - ret += ";"; - } - - return ret; -} - - -auto pretty_print_visualize(postfix_expression_node const& n, int indent) - -> std::string -{ - assert(n.expr); - - auto ret = pretty_print_visualize(*n.expr, indent); - - for (auto& op : n.ops) - { - assert(op.op); - if (op.expr_list) { - assert (op.op_close); - ret += pretty_print_visualize(*op.expr_list, indent); - } - else { - ret += op.op->as_string_view(); - if (op.id_expr) { - ret += pretty_print_visualize(*op.id_expr, indent); - } - } - } - - return ret; -} - - -auto pretty_print_visualize(unqualified_id_node const& n, int indent) - -> std::string -{ - assert(n.identifier); - - auto ret = n.identifier->to_string(); - - if (n.open_angle != source_position{}) - { - ret += "<"; - for (bool first = true; auto& arg : n.template_args) - { - if (!first) { - ret += ", "; - } - first = false; - ret += try_pretty_print_visualize<template_argument::expression>(arg.arg, indent); - ret += try_pretty_print_visualize<template_argument::type_id >(arg.arg, indent); - } - ret += ">"; - } - - return ret; -} - - -auto pretty_print_visualize(qualified_id_node const& n, int indent) - -> std::string -{ - auto ret = std::string{}; - - for (auto& id : n.ids) { - if (id.scope_op) { ret += id.scope_op->as_string_view(); } - assert (id.id); - ret += pretty_print_visualize(*id.id, indent); - } - - return ret; -} - - -auto pretty_print_visualize(type_id_node const& n, int indent) - -> std::string -{ - auto ret = std::string{}; - - for (auto& qual : n.pc_qualifiers) { - assert(qual); - ret += qual->as_string_view(); - ret += " "; - } - - if (n.id.index() == type_id_node::empty) { ret += "_"; } - ret += try_pretty_print_visualize<type_id_node::qualified >(n.id, indent); - ret += try_pretty_print_visualize<type_id_node::unqualified>(n.id, indent); - ret += try_pretty_print_visualize<type_id_node::keyword >(n.id, indent); - - return ret; -} - - -auto pretty_print_visualize(is_as_expression_node const& n, int indent) - -> std::string -{ - assert (n.expr); - - auto ret = pretty_print_visualize(*n.expr, indent); - - for (auto& op : n.ops) { - if (op.op) { ret += " " + op.op->to_string() + " "; } - if (op.type) { ret += pretty_print_visualize(*op.type, indent); } - if (op.expr) { ret += pretty_print_visualize(*op.expr, indent); } - } - - return ret; -} - - -auto pretty_print_visualize(id_expression_node const& n, int indent) - -> std::string -{ - auto ret = std::string{}; - - ret += try_pretty_print_visualize<id_expression_node::qualified >(n.id, indent); - ret += try_pretty_print_visualize<id_expression_node::unqualified>(n.id, indent); - - return ret; -} - - -auto pretty_print_visualize(compound_statement_node const& n, int indent) - -> std::string -{ - auto ret = std::string{"\n"} + pre(indent) + "{"; - - for (auto& stmt : n.statements) { - assert (stmt); - ret += pretty_print_visualize(*stmt, indent+1); - } - - ret += std::string{"\n"} + pre(indent) + "}"; - - return ret; -} - - -auto pretty_print_visualize(selection_statement_node const& n, int indent) - -> std::string -{ - assert (n.identifier && n.expression && n.true_branch && n.false_branch); - - auto ret = std::string{}; - - ret += std::string{"\n"} + pre(indent) + n.identifier->as_string_view() + " "; - - if (n.is_constexpr) { - ret += "constexpr "; - } - - ret += pretty_print_visualize(*n.expression, indent) - + pretty_print_visualize(*n.true_branch, indent); - - if (n.has_source_false_branch) { - ret += std::string{"\n"} + pre(indent) + "else " - + pretty_print_visualize(*n.false_branch, indent); - } - - return ret; -} - - -auto pretty_print_visualize(iteration_statement_node const& n, int indent) - -> std::string -{ - // First compute the common parts - - auto next_expr = std::string{}; - if (n.next_expression) { - next_expr += std::string{"\n"} + pre(indent) + "next " + pretty_print_visualize(*n.next_expression, indent); - } - - auto stmts = std::string{}; - if (n.statements) { - stmts += pretty_print_visualize(*n.statements, indent+1); - } - - // Then slot them in where appropriate - - auto ret = std::string{}; - assert (n.identifier); - - ret += std::string{"\n"} + pre(indent); - if (n.label) { - ret += n.label->to_string() - + ": "; - } - - if (*n.identifier == "while") { - assert (n.condition); - ret += "while " - + pretty_print_visualize(*n.condition, indent) + next_expr + stmts; - } - else if (*n.identifier == "do") { - assert (n.condition); - ret += "do " - + stmts - + next_expr - + "\n" + pre(indent) + "while " - + pretty_print_visualize(*n.condition, indent); - if (ret.back() != ';') { - ret += ";"; - } - } - else { - assert (n.range && n.parameter && n.body); - ret += "for " - + pretty_print_visualize(*n.range, indent) - + next_expr - + "\n" + pre(indent) + "do (" + pretty_print_visualize(*n.parameter, indent + 1) + ")" - + pretty_print_visualize(*n.body, indent+1); - } - - return ret; -} - - -auto pretty_print_visualize(return_statement_node const& n, int indent) - -> std::string -{ - auto ret = std::string{"\n"} + pre(indent) + "return"; - - if (n.expression) { - ret += " " + pretty_print_visualize(*n.expression, indent); - } - - if (ret.back() != ';') { - ret += ";"; - } - - return ret; -} - - -auto pretty_print_visualize(alternative_node const& n, int indent) - -> std::string -{ - auto ret = std::string{}; - assert (n.is_as_keyword); - ret += std::string{"\n"} + pre(indent); - if (n.name) { - ret += pretty_print_visualize(*n.name, indent) + ": "; - } - ret += n.is_as_keyword->as_string_view(); - if (n.type_id) { - ret += " " + pretty_print_visualize(*n.type_id, indent); - } - if (n.value) { - ret += " " + pretty_print_visualize(*n.value, indent); - } - ret += " = " + pretty_print_visualize(*n.statement, indent+1); - return ret; -} - - -auto pretty_print_visualize(inspect_expression_node const& n, int indent) - -> std::string -{ - assert (n.expression); - - auto ret = std::string{"inspect"}; - - if (n.is_constexpr) { - ret += " constexpr"; - } - - ret += " " + pretty_print_visualize(*n.expression, indent); - - if (n.result_type) { - ret += " -> " + pretty_print_visualize(*n.result_type, indent); - } - - ret += " {"; - - for (auto& alt : n.alternatives) { - assert(alt); - ret += pretty_print_visualize(*alt, indent+1); - } - - ret += std::string{"\n"} + pre(indent) + "}"; - - return ret; -} - - -auto pretty_print_visualize(contract_node const& n, int indent) - -> std::string -{ - assert (n.kind && n.condition); - - auto ret = std::string{"\n"} + pre(indent) + n.kind->as_string_view(); - - if (n.group) { - ret += "<" + pretty_print_visualize(*n.group, indent); - for (auto const& flag : n.flags) { - ret += "," + pretty_print_visualize(*flag, indent); - } - ret += ">"; - } - - ret += "( " + pretty_print_visualize(*n.condition, indent); - - if (n.message) { - ret += ", " + pretty_print_visualize(*n.message, indent); - } - - ret += " )"; - - if (*n.kind == "assert" && ret.back() != ';') { - ret += ";"; - } - - return ret; -} - - -auto pretty_print_visualize(jump_statement_node const& n, int indent) - -> std::string -{ - assert (n.keyword); - - auto ret = std::string{"\n"} + pre(indent) + n.keyword->as_string_view(); - - if (n.label) { - ret += " " + n.label->to_string(); - } - - if (ret.back() != ';') { - ret += ";"; - } - - return ret; -} - - -auto pretty_print_visualize(using_statement_node const& n, int indent) - -> std::string -{ - assert (n.keyword); - - auto ret = std::string{"\n"} + pre(indent) + n.keyword->as_string_view() + " "; - - if (n.for_namespace) { - ret += "namespace "; - } - - ret += pretty_print_visualize(*n.id, indent); - if (ret.back() != ';') { - ret += ";"; - } - - return ret; -} - - -auto pretty_print_visualize(statement_node const& n, int indent) - -> std::string -{ - auto ret = std::string{}; - - if (n.is_expression()) - { - if (n.compound_parent) { - ret += std::string{"\n"} + pre(indent); - } - auto& expr = std::get<statement_node::expression>(n.statement); - assert (expr); - ret += pretty_print_visualize(*expr, indent); - } - else - { - if (n.parameters) { - ret += std::string{"\n"} + pre(indent) + pretty_print_visualize(*n.parameters, indent); - } - - ret += try_pretty_print_visualize<statement_node::compound >(n.statement, indent); - ret += try_pretty_print_visualize<statement_node::selection >(n.statement, indent); - ret += try_pretty_print_visualize<statement_node::declaration>(n.statement, indent); - ret += try_pretty_print_visualize<statement_node::return_ >(n.statement, indent); - ret += try_pretty_print_visualize<statement_node::iteration >(n.statement, indent); - ret += try_pretty_print_visualize<statement_node::using_ >(n.statement, indent); - ret += try_pretty_print_visualize<statement_node::contract >(n.statement, indent); - ret += try_pretty_print_visualize<statement_node::inspect >(n.statement, indent); - ret += try_pretty_print_visualize<statement_node::jump >(n.statement, indent); - } - - return ret; -} - - -auto pretty_print_visualize(parameter_declaration_node const& n, int indent, bool is_template_param_list /* = false */ ) - -> std::string -{ - assert (n.declaration); - - auto ret = std::string{}; - - if (!is_template_param_list) { - switch (n.mod) { - break;case parameter_declaration_node::modifier::implicit : ret += "implicit "; - break;case parameter_declaration_node::modifier::virtual_ : ret += "virtual "; - break;case parameter_declaration_node::modifier::override_: ret += "override "; - break;case parameter_declaration_node::modifier::final_ : ret += "final "; - break;default: ; // none - } - - ret += to_string_view(n.pass); - ret += " "; - } - - ret += pretty_print_visualize(*n.declaration, indent); - - return ret; -} - - -auto pretty_print_visualize(parameter_declaration_list_node const& n, int indent, bool is_template_param_list /* = false */) - -> std::string -{ - assert(n.open_paren && n.close_paren); - - auto ret = n.open_paren->to_string(); - - auto space = std::string{}; - if (std::ssize(n.parameters) > 1) { - space += std::string{"\n"} + pre(indent+1); - } - - for (auto i = 0; auto& param : n.parameters) { - ret += space + pretty_print_visualize(*param, indent+1, is_template_param_list); - if (++i < std::ssize(n.parameters)) { - ret += ", "; - } - } - - if (std::ssize(n.parameters) > 1) { - ret += std::string{"\n"} + pre(indent); - } - ret += n.close_paren->to_string(); - - return ret; -} - - -auto pretty_print_visualize(function_type_node const& n, int indent) - -> std::string -{ - assert (n.parameters); - - auto ret = pretty_print_visualize(*n.parameters, indent); - - if (n.throws) { - ret += " throws"; - } - - if (n.has_non_void_return_type()) { - ret += " -> "; - ret += try_pretty_print_visualize<function_type_node::list>(n.returns, indent+1); - if (n.returns.index() == function_type_node::id) { - auto& single = std::get<function_type_node::id>(n.returns); - ret += to_string_view(single.pass) - + std::string{" "} + pretty_print_visualize(*single.type, indent+1); - } - } - - for (auto& contract: n.contracts) { - assert(contract); - ret += pretty_print_visualize(*contract, indent+1); - } - - return ret; -} - - -auto pretty_print_visualize(type_node const& n) - -> std::string -{ - assert (n.type); - - auto ret = std::string{}; - - if (n.final) { - ret += "final "; - } - - ret += "type"; - - return ret; -} - - -auto pretty_print_visualize(namespace_node const&) - -> std::string -{ - return "namespace"; -} - - -auto pretty_print_visualize(declaration_node const& n, int indent, bool include_metafunctions_list /* = false */ ) - -> std::string -{ - indent_spaces = 4; - - // First compute the common parts - - auto metafunctions = std::string{}; - if (include_metafunctions_list) { - for (auto& meta : n.metafunctions) { - metafunctions += " @" + pretty_print_visualize(*meta, indent); - } - } - - auto template_params = std::string{}; - if (n.template_parameters) { - template_params += " " + pretty_print_visualize(*n.template_parameters, indent + 1, true); - } - - auto requires_clause = std::string{}; - if (n.requires_clause_expression) { - requires_clause += " requires (" + pretty_print_visualize(*n.requires_clause_expression, indent) + ")"; - } - - auto initializer = std::string{}; - if (n.initializer) { - auto adjusted_indent = indent; - if (!n.name()) { - ++adjusted_indent; - } - initializer = " ="; - if (n.is_function() && n.is_constexpr) { - initializer += "="; - } - initializer += " " + pretty_print_visualize(*n.initializer, adjusted_indent); - if (initializer.ends_with(";;")) { - initializer.pop_back(); - } - } - else if (!n.is_parameter) { - initializer = ";"; - } - - // Then slot them in where appropriate - - auto ret = std::string{""}; - - // Add an extra newline for spacing, unless this declaration - // is within a function body or is the first member of a type - if ( - !n.parent_is_function() - && !n.parent_is_object() - && !n.is_parameter - ) - { - static declaration_node const* last_parent_type = {}; - if (n.parent_is_type()) { - if (last_parent_type != n.get_parent()) { - last_parent_type = n.get_parent(); - } - else { - ret += "\n"; - } - } - else { - ret += "\n"; - } - } - if (!n.is_parameter && n.name()) { - ret += std::string{"\n"} + pre(indent); - } - - switch (n.access) { - break;case accessibility::public_ : ret += "public "; - break;case accessibility::protected_ : ret += "protected "; - break;case accessibility::private_ : ret += "private "; - break;default: ; // default accessibility - } - - if (n.identifier) { - ret += pretty_print_visualize(*n.identifier, indent); - } - - if (n.is_parameter && (n.has_name("this") || n.has_name("that"))) { - return ret; - } - - if (n.is_variadic) { - ret += "..."; - } - - ret += ":"; - - if (n.is_function()) { - auto& func = std::get<declaration_node::a_function>(n.type); - assert(func); - ret += metafunctions - + template_params - + pretty_print_visualize(*func, indent) - + requires_clause - + initializer; - } - else if (n.is_object()) { - auto& type_id = std::get<declaration_node::an_object>(n.type); - assert(type_id); - ret += metafunctions - + template_params; - if (!n.has_wildcard_type()) { - ret += " " + pretty_print_visualize(*type_id, indent); - } - ret += requires_clause - + initializer; - } - else if (n.is_type()) { - auto& t = std::get<declaration_node::a_type>(n.type); - assert(t); - ret += metafunctions - + template_params - + " " + pretty_print_visualize(*t) - + initializer; - } - else if (n.is_namespace()) { - auto& t = std::get<declaration_node::a_type>(n.type); - assert(t); - ret += "namespace = " - + initializer; - } - else if (n.is_alias()) { - auto& a = std::get<declaration_node::an_alias>(n.type); - assert(a); - - auto object_type_id = std::string{}; - if (a->type_id) { - object_type_id += " " + pretty_print_visualize(*a->type_id, indent); - } - - ret += template_params; - if (a->is_type_alias()) { - auto& t = std::get<alias_node::a_type>(a->initializer); - ret += " type" - + requires_clause - + " == " - + pretty_print_visualize(*t, indent); - if (ret.back() != ';') { - ret += ";"; - } - } - else if (a->is_namespace_alias()) { - auto& id = std::get<alias_node::a_namespace>(a->initializer); - assert(id); - ret += " namespace == " - + pretty_print_visualize(*id, indent); - if (ret.back() != ';') { - ret += ";"; - } - } - else if (a->is_object_alias()) { - auto& expr = std::get<alias_node::an_object>(a->initializer); - assert(expr); - ret += object_type_id - + requires_clause - + " == " - + pretty_print_visualize(*expr, indent); - if (ret.back() != ';') { - ret += ";"; - } - } - } - - return ret; -} - - -auto pretty_print_visualize(translation_unit_node const& n) - -> std::string -{ - auto ret = std::string{}; - - for (auto& decl : n.declarations) { - assert(decl); - ret += pretty_print_visualize(*decl, 0); - } - - return ret; -} - - -//----------------------------------------------------------------------- -// -// parser: parses a section of Cpp2 code -// -//----------------------------------------------------------------------- -// -class parser -{ - std::vector<error_entry>& errors; - - std::unique_ptr<translation_unit_node> parse_tree = {}; - - // Keep a stack of current capture groups (contracts/decls still being parsed) - std::vector<capture_group*> current_capture_groups = {}; - - struct capture_groups_stack_guard - { - parser* pars; - - capture_groups_stack_guard(parser* p, capture_group* cg) - : pars{ p } - { - assert(p); - assert(cg); - pars->current_capture_groups.push_back(cg); - } - - ~capture_groups_stack_guard() - { - pars->current_capture_groups.pop_back(); - } - }; - - // Keep a stack of currently active declarations (still being parsed) - std::vector<declaration_node*> current_declarations = { nullptr }; - - struct current_declarations_stack_guard - { - parser* pars; - - current_declarations_stack_guard(parser* p, declaration_node* decl) - : pars{ p } - { - assert(p); - assert(decl); - pars->current_declarations.push_back(decl); - } - - ~current_declarations_stack_guard() - { - pars->current_declarations.pop_back(); - } - }; - - std::vector<token> const* tokens = {}; - std::deque<token>* generated_tokens = {}; - int pos = 0; - std::string parse_kind = {}; - - // Keep track of the function bodies' locations - used to emit comments - // in the right pass (decide whether it's a comment that belongs with - // the declaration or is part of the definition) - struct function_body_extent { - lineno_t first; - lineno_t last; - auto operator<=>(function_body_extent const&) const = default; - auto operator<=>(int i) const { return first <=> i; } - - function_body_extent( lineno_t f, lineno_t l ): first{f}, last{l} { } - }; - mutable std::vector<function_body_extent> function_body_extents; - mutable bool is_function_body_extents_sorted = false; - -public: - auto is_within_function_body(source_position p) const - { - // Short circuit the empty case, so that the rest of the function - // can unconditionally decrement any non-.begin() iterator once - if (function_body_extents.empty()) { - return false; - } - - // Ensure we are sorted - if (!is_function_body_extents_sorted) { - std::sort( - function_body_extents.begin(), - function_body_extents.end() - ); - is_function_body_extents_sorted = true; - } - - // Find the first entry that is beyond pos, and back up one to - // the last that could be a match; this also ensures iter is - // dereferenceable, not .end() - auto iter = std::lower_bound( - function_body_extents.begin(), - function_body_extents.end(), - p.lineno+1 - ); - if (iter != function_body_extents.begin()) { - --iter; - } - - // Now go backwards through the preceding entries until - // one includes pos or we move before pos - while ( - iter->first <= p.lineno - ) - { - if ( - iter->first <= p.lineno - && p.lineno <= iter->last - ) - { - return true; - } - if (iter == function_body_extents.begin()) { - break; - } - --iter; - } - return false; - } - - -public: - //----------------------------------------------------------------------- - // Constructors - the copy constructor constructs a new instance with - // the same errors reference but otherwise a clean slate - // - // errors error list - // - parser( std::vector<error_entry>& errors_ ) - : errors{ errors_ } - , parse_tree{std::make_unique<translation_unit_node>()} - { } - - parser( parser const& that ) - : errors{ that.errors } - , parse_tree{std::make_unique<translation_unit_node>()} - { } - - - //----------------------------------------------------------------------- - // parse - // - // tokens input tokens for this section of Cpp2 source code - // generated_tokens a shared place to store generated tokens - // - // Each call parses this section's worth of tokens and adds the - // result to the stored parse tree. Call this repeatedly for the Cpp2 - // sections in a TU to build the whole TU's parse tree - // - auto parse( - std::vector<token> const& tokens_, - std::deque<token>& generated_tokens_ - ) - -> bool - { - parse_kind = "source file"; - - // Set per-parse state for the duration of this call - tokens = &tokens_; - generated_tokens = &generated_tokens_; - - // Generate parse tree for this section as if a standalone TU - pos = 0; - auto tu = translation_unit(); - - // Then add it to the complete parse tree - parse_tree->declarations.insert( - parse_tree->declarations.end(), - std::make_move_iterator(tu->declarations.begin()), - std::make_move_iterator(tu->declarations.end()) - ); - if (!done()) { - error("unexpected text at end of Cpp2 code section", true, {}, true); - return false; - } - return true; - } - - - //----------------------------------------------------------------------- - // parse_one_statement - // - // tokens input tokens for this section of Cpp2 source code - // generated_tokens a shared place to store generated tokens - // - // Each call parses one statement and returns its parse tree. - // - auto parse_one_declaration( - std::vector<token> const& tokens_, - std::deque<token>& generated_tokens_ - ) - -> std::unique_ptr<statement_node> - { - parse_kind = "source string during code generation"; - - // Set per-parse state for the duration of this call - tokens = &tokens_; - generated_tokens = &generated_tokens_; - - try { - // Parse one declaration - we succeed if the parse succeeded, - // and there were no new errors, and all tokens were consumed - auto errors_size = std::ssize(errors); - pos = 0; - if (auto d = statement(); - d - && std::ssize(errors) == errors_size - && done() - ) - { - return d; - } - } - catch(std::runtime_error& e) { - error(e.what(), true, {}, true); - } - - return {}; - } - - - //----------------------------------------------------------------------- - // Get a set of pointers to just the declarations in the given token map section - // - auto get_parse_tree_declarations_in_range(std::vector<token> const& token_range) const - -> std::vector< declaration_node const* > - { - assert (parse_tree); - assert (!token_range.empty()); - auto first_line = token_range.front().position().lineno; - auto last_line = token_range.back().position().lineno; - - auto ret = std::vector< declaration_node const* >{}; - for (auto& decl : parse_tree->declarations) - { - assert(decl); - - // The grammar and the tokens are in lineno order, so we don't - // need to look further once we pass the last lineno - if (decl->position().lineno > last_line) { - break; - } - if (decl->position().lineno >= first_line) { - ret.push_back( decl.get() ); - } - } - - return ret; - } - - - //----------------------------------------------------------------------- - // visit - // - auto visit(auto& v) -> void - { - parse_tree->visit(v, 0); - } - -private: - //----------------------------------------------------------------------- - // Error reporting: Fed into the supplied this->errors object - // - // msg message to be printed - // - // include_curr_token in this file (during parsing), we normally want - // to show the current token as the unexpected text - // we encountered, but some sema rules are applied - // early during parsing and for those it doesn't - // make sense to show the next token (e.g., when - // we detect and reject a "std::move" qualified-id, - // it's not relevant to add "at LeftParen: (" - // just because ( happens to be the next token) - // - auto error( - char const* msg, - bool include_curr_token = true, - source_position err_pos = {}, - bool fallback = false - ) const - -> void - { - auto m = std::string{msg}; - auto i = done() ? -1 : 0; - assert (peek(i)); - if (include_curr_token) { - m += std::string(" (at '") + peek(i)->to_string() + "')"; - } - if ( - err_pos == source_position{} - ) { - err_pos = peek(i)->position(); - } - errors.emplace_back( err_pos, m, false, fallback ); - } - - auto error( - std::string const& msg, - bool include_curr_token = true, - source_position err_pos = {}, - bool fallback = false - ) const - -> void - { - error( msg.c_str(), include_curr_token, err_pos, fallback ); - } - - bool has_error() { - return !errors.empty(); - } - - - //----------------------------------------------------------------------- - // Token navigation: Only these functions should access this->token_ - // - auto curr() const - -> token const& - { - if (done()) { - throw std::runtime_error("unexpected end of " + parse_kind); - } - - return (*tokens)[pos]; - } - - auto peek(int num) const - -> token const* - { - assert (tokens); - if ( - pos + num >= 0 - && pos + num < std::ssize(*tokens) - ) - { - return &(*tokens)[pos + num]; - } - return {}; - } - - auto done() const - -> bool - { - assert (tokens); - assert (pos <= std::ssize(*tokens)); - return pos == std::ssize(*tokens); - } - - auto next(int num = 1) - -> void - { - assert (tokens); - pos = std::min( pos+num, _as<int>(std::ssize(*tokens)) ); - } - - - //----------------------------------------------------------------------- - // Parsers for unary expressions - // - - //G primary-expression: - //G inspect-expression - //G id-expression - //G literal - //G '(' expression-list ')' - //GT '{' expression-list '}' - //G unnamed-declaration - //G - auto primary_expression() - -> std::unique_ptr<primary_expression_node> - { - auto n = std::make_unique<primary_expression_node>(); - - if (auto inspect = inspect_expression(true)) - { - n->expr = std::move(inspect); - return n; - } - - if (auto id = id_expression()) { - n->expr = std::move(id); - return n; - } - - if (auto lit = literal()) { - n->expr = std::move(lit); - return n; - } - - if (curr().type() == lexeme::LeftParen - // If in the future (not now) we decide to allow braced-expressions - // || curr().type() == lexeme::LeftBrace - ) - { - bool inside_initializer = ( - peek(-1) && peek(-1)->type() == lexeme::Assignment - ); - auto open_paren = &curr(); - auto close = close_paren_type(open_paren->type()); - auto close_text = [&] () -> std::string { if (close == lexeme::RightParen) { return ")"; } return "}"; }(); - next(); - auto expr_list = expression_list(open_paren, inside_initializer); - if (!expr_list) { - error("unexpected text - ( is not followed by an expression-list"); - next(); - return {}; - } - if (curr().type() != close_paren_type(open_paren->type())) { - error("unexpected text - expression-list is not terminated by " + close_text); - next(); - return {}; - } - expr_list->close_paren = &curr(); - next(); - if ( - curr().type() != lexeme::Semicolon - && curr().type() != lexeme::RightParen - && curr().type() != lexeme::RightBracket - && curr().type() != lexeme::Greater - && curr().type() != lexeme::Comma - ) { - expr_list->inside_initializer = false; - } - n->expression_list_is_fold_expression = expr_list->is_fold_expression(); - n->expr = std::move(expr_list); - return n; - } - - if (auto decl = unnamed_declaration(curr().position(), false, true)) // captures are allowed - { - assert ( - !decl->has_name() - && "ICE: declaration should have been unnamed" - ); - - if (auto obj = std::get_if<declaration_node::an_object>(&decl->type)) { - if ((*obj)->is_wildcard()) { - error("an unnamed object at expression scope currently cannot have a deduced type (the reason to create an unnamed object is typically to create a temporary of a named type)"); - next(); - return {}; - } - } - else if (auto func = std::get_if<declaration_node::a_function>(&decl->type)) { - if ((*func)->returns.index() == function_type_node::list) { - error("an unnamed function at expression scope currently cannot return multiple values"); - next(); - return {}; - } - if ( // check if a single-expression function is followed by an extra second semicolon - decl->initializer && decl->initializer->is_expression() - && !done() && curr().type() == lexeme::Semicolon - ) { - error("a single-expression function should end with a single semicolon"); - } - if (!(*func)->contracts.empty()) { - error("an unnamed function at expression scope currently cannot have contracts"); - next(); - return {}; - } - } - else { - error("(temporary alpha limitation) an unnamed declaration at expression scope must be a function or an object"); - next(); - return {}; - } - - if ( - peek(-1) && peek(-1)->type() != lexeme::RightBrace // it is not a braced function expression - && curr().type() != lexeme::LeftParen // not imediatelly called - && curr().type() != lexeme::RightParen // not as a last argument to function - && curr().type() != lexeme::Comma // not as first or in-the-middle, function argument - && curr().type() != lexeme::Greater // not as the last argument to template - && curr().type() != lexeme::RightBracket // not as the last index argument - && curr() != "is" // not as the argument to is - && curr() != "as" // not as the argument to as - && curr() != "do" // not as `for`'s `next`. - ) { - // this is a fix for a short function syntax that should have double semicolon used - // (check comment in expression_statement(bool semicolon_required)) - // We simulate double semicolon by moving back to single semicolon. - next(-1); - } - - n->expr = std::move(decl); - return n; - } - - return {}; - } - - - //G postfix-expression: - //G primary-expression - //G postfix-expression postfix-operator [Note: without whitespace before the operator] - //G postfix-expression '[' expression-list? ']' - //G postfix-expression '(' expression-list? ')' - //G postfix-expression '.' id-expression - //G - auto postfix_expression() - -> std::unique_ptr<postfix_expression_node> - { - auto n = std::make_unique<postfix_expression_node>(); - n->expr = primary_expression(); - if (!(n->expr)) { - return {}; - } - - while ( - !done() - && ( - (is_postfix_operator(curr().type()) - // Postfix operators must be lexically adjacent - && curr().position().lineno == peek(-1)->position().lineno - && curr().position().colno == peek(-1)->position().colno + peek(-1)->length() - ) - || curr().type() == lexeme::LeftBracket - || curr().type() == lexeme::LeftParen - || curr().type() == lexeme::Dot - ) - ) - { - // these can't be unary operators if followed by a (, identifier, or literal - if ( - ( - curr().type() == lexeme::Multiply - || curr().type() == lexeme::Ampersand - || curr().type() == lexeme::Tilde - ) - && peek(1) - && ( - peek(1)->type() == lexeme::LeftParen - || peek(1)->type() == lexeme::Identifier - || is_literal(peek(1)->type()) - ) - ) - { - auto op = curr().to_string(); - auto msg = "postfix unary " + op; - if (curr().type() == lexeme::Multiply ) { msg += " (dereference)" ; } - else if (curr().type() == lexeme::Ampersand) { msg += " (address-of)" ; } - else if (curr().type() == lexeme::Tilde ) { msg += " (unary bit-complement)" ; } - msg += " cannot be immediately followed by a (, identifier, or literal - add whitespace before " - + op + " here if you meant binary " + op; - if (curr().type() == lexeme::Multiply ) { msg += " (multiplication)" ; } - else if (curr().type() == lexeme::Ampersand) { msg += " (bitwise and)" ; } - else if (curr().type() == lexeme::Tilde ) { msg += " (binary bit-complement)"; } - - error(msg, false); - break; - } - - if (curr().type() == lexeme::Dollar) { - // cap_grp must not already be set, or this is a multi-$ postfix-expression - if (n->cap_grp) { - error("$ (capture) can appear at most once in a single postfix-expression"); - return {}; - } - if (current_capture_groups.empty()) { - error("$ (capture) cannot appear here - it must appear in an anonymous expression function, a postcondition, or an interpolated string literal"); - return {}; - } - n->cap_grp = current_capture_groups.back(); - n->cap_grp->add(n.get()); - } - - // Remember current position, in case we need to backtrack - auto term_pos = pos; - - auto term = postfix_expression_node::term{&curr()}; - next(); - - if (term.op->type() == lexeme::LeftBracket) - { - term.expr_list = expression_list(term.op); - if (!term.expr_list) - { - error("[ is not followed by a valid expression list"); - return {}; - } - if (curr().type() != lexeme::RightBracket) - { - error("unexpected text - [ is not properly matched by ]", true, {}, true); - return {}; - } - term.expr_list->close_paren = &curr(); - term.op_close = &curr(); - next(); - } - else if (term.op->type() == lexeme::LeftParen) - { - // Next should be an expression-list followed by a ')' - // If not, then this wasn't a call expression so backtrack to - // the '(' which will be part of the next grammar production - - term.expr_list = expression_list(term.op); - if ( - term.expr_list - && curr().type() == lexeme::RightParen - ) - { - term.expr_list->close_paren = &curr(); - term.op_close = &curr(); - next(); - } - else - { - pos = term_pos; // backtrack - break; - } - } - else if (term.op->type() == lexeme::Dot) - { - term.id_expr = id_expression(); - if (!term.id_expr) { - error("'.' must be followed by a valid member name"); - return {}; - } - } - - n->ops.push_back( std::move(term) ); - } - - if (auto tok = n->expr->get_token(); - tok - && *tok == "this" - && curr().type() == lexeme::Arrow - ) - { - auto next_word = std::string{}; - if (peek(1)) { - next_word = peek(1)->to_string(); - } - error("'this' is not a pointer - write 'this." + next_word + "' instead of 'this->" + next_word + "'"); - return {}; - } - - for (auto& e : expression_node::current_expressions) { - e->num_subexpressions += std::ssize(n->ops); - } - - return n; - } - - - //G prefix-expression: - //G postfix-expression - //G prefix-operator prefix-expression - //GTODO await-expression - //GTODO 'sizeof' '(' type-id ')' - //GTODO 'sizeof' '...' ( identifier ')' - //GTODO 'alignof' '(' type-id ')' - //GTODO throws-expression - //G - auto prefix_expression() - -> std::unique_ptr<prefix_expression_node> - { - auto n = std::make_unique<prefix_expression_node>(); - for ( ; - is_prefix_operator(curr()); - next() - ) - { - n->ops.push_back(&curr()); - } - if ((n->expr = postfix_expression())) { - return n; - } - switch (curr().type()) - { - break; case lexeme::PlusPlus: - error("prefix '++var' is not valid Cpp2; use postfix 'var++' instead", false); - break; case lexeme::MinusMinus: - error("prefix '--var' is not valid Cpp2; use postfix 'var--' instead", false); - break; case lexeme::Multiply: - error("prefix '*ptr' dereference is not valid Cpp2; use postfix 'ptr*' instead", false); - break; case lexeme::Ampersand: - error("prefix '&var' address-of is not valid Cpp2; use postfix 'var&' instead", false); - break; case lexeme::Tilde: - error("prefix '~var' is not valid Cpp2; use postfix 'var~' instead", false); - break; default: ; - } - return {}; - } - - - //----------------------------------------------------------------------- - // Parsers for binary expressions - // - - // The general /*binary*/-expression: - // /*term*/-expression { { /* operators at this precedence level */ } /*term*/-expression }* - // - template< - typename Binary, - typename ValidateOp, - typename TermFunc - > - auto binary_expression( - ValidateOp validate_op, - TermFunc term - ) - -> std::unique_ptr<Binary> - { - auto n = std::make_unique<Binary>(); - if ( (n->expr = term()) ) - { - while (!done()) - { - typename Binary::term t{}; - - // Remember current position, because we may need to backtrack if this next - // t.op might be valid but isn't followed by a valid term and so isn't for us - auto term_pos = pos; - - // Most of these predicates only look at the current token and return - // true/false == whether this is a valid operator for this production - if constexpr( requires{ bool{ validate_op(curr()) }; } ) { - if (!validate_op(curr())) { - break; - } - t.op = &curr(); - next(); - } - - // But for shift-expression we may synthesize >> from > > - // which will return a token* == a valid operator for this production - // (possibly a synthesized new token) or nullptr otherwise - else if constexpr( requires{ validate_op(curr(), *peek(1)); } ) { - if ( - peek(1) == nullptr - || (t.op = validate_op(curr(), *peek(1))) == nullptr - ) - { - break; - } - // If we didn't consume the next token, we consumed the next two - if (t.op != &curr()) { - next(); - } - next(); - } - - // And it shouldn't be anything else - else { - assert (!"ICE: validate_op should take one token and return bool, or two tokens and return token const* "); - } - - // At this point we may have a valid t.op, so try to parse the next term... - // If it's not a valid term, then this t.op wasn't for us, pop it and return - // what we found (e.g., with "requires expression = {...}" the = is a grammar - // element and not an operator, it isn't and can't be part of the expression) - if ( !(t.expr = term()) ) { - pos = term_pos; // backtrack - return n; - } - - // We got a term, so this op + term was for us - n->terms.push_back( std::move(t) ); - } - return n; - } - return {}; - } - - //G multiplicative-expression: - //G is-as-expression - //G multiplicative-expression '*' is-as-expression - //G multiplicative-expression '/' is-as-expression - //G multiplicative-expression '%' is-as-expression - //G - auto multiplicative_expression() - -> auto - { - return binary_expression<multiplicative_expression_node> ( - [](token const& t){ return t.type() == lexeme::Multiply || t.type() == lexeme::Slash || t.type() == lexeme::Modulo; }, - [this]{ return is_as_expression(); } - ); - } - - //G additive-expression: - //G multiplicative-expression - //G additive-expression '+' multiplicative-expression - //G additive-expression '-' multiplicative-expression - //G - auto additive_expression() - -> auto - { - return binary_expression<additive_expression_node> ( - [](token const& t){ return t.type() == lexeme::Plus || t.type() == lexeme::Minus; }, - [this]{ return multiplicative_expression(); } - ); - } - - //G shift-expression: - //G additive-expression - //G shift-expression '<<' additive-expression - //G shift-expression '>>' additive-expression - //G - auto shift_expression(bool allow_angle_operators = true) - -> auto - { - if (allow_angle_operators) { - return binary_expression<shift_expression_node> ( - [this](token const& t, token const& next) -> token const* { - if (t.type() == lexeme::LeftShift) { - return &t; - } - if ( - t.type() == lexeme::Greater - && next.type() == lexeme::Greater - && t.position() == source_position{ next.position().lineno, next.position().colno-1 } - ) - { - generated_tokens->emplace_back( ">>", t.position(), lexeme::RightShift); - return &generated_tokens->back(); - } - return nullptr; - }, - [this]{ return additive_expression(); } - ); - } - else { - return binary_expression<shift_expression_node> ( - [](token const&, token const&) -> token const* { return nullptr; }, - [this]{ return additive_expression(); } - ); - } - } - - //G compare-expression: - //G shift-expression - //G compare-expression '<=>' shift-expression - //G - auto compare_expression(bool allow_angle_operators = true) - -> auto - { - return binary_expression<compare_expression_node> ( - [](token const& t){ return t.type() == lexeme::Spaceship; }, - [=,this]{ return shift_expression(allow_angle_operators); } - ); - } - - //G relational-expression: - //G compare-expression - //G relational-expression '<' compare-expression - //G relational-expression '>' compare-expression - //G relational-expression '<=' compare-expression - //G relational-expression '>=' compare-expression - //G - auto relational_expression(bool allow_angle_operators = true) - -> auto - { - if (allow_angle_operators) { - return binary_expression<relational_expression_node> ( - [](token const& t, token const& next) -> token const* { - if ( - t.type() == lexeme::Less - || t.type() == lexeme::LessEq - || (t.type() == lexeme::Greater && next.type() != lexeme::GreaterEq) - || t.type() == lexeme::GreaterEq - ) { - return &t; - } - return nullptr; - }, - [=,this]{ return compare_expression(allow_angle_operators); } - ); - } - else { - return binary_expression<relational_expression_node> ( - [](token const&, token const&) -> token const* { return nullptr; }, - [=,this]{ return compare_expression(allow_angle_operators); } - ); - } - } - - //G equality-expression: - //G relational-expression - //G equality-expression '==' relational-expression - //G equality-expression '!=' relational-expression - //G - auto equality_expression(bool allow_angle_operators = true, bool allow_equality = true) - -> auto - { - if (allow_equality) { - return binary_expression<equality_expression_node> ( - [](token const& t){ return t.type() == lexeme::EqualComparison || t.type() == lexeme::NotEqualComparison; }, - [=,this]{ return relational_expression(allow_angle_operators); } - ); - } - else { - return binary_expression<equality_expression_node> ( - [](token const& t){ return t.type() == lexeme::NotEqualComparison; }, - [=,this]{ return relational_expression(allow_angle_operators); } - ); - } - } - - //G bit-and-expression: - //G equality-expression - //G bit-and-expression '&' equality-expression - //G - auto bit_and_expression(bool allow_angle_operators = true, bool allow_equality = true) - -> auto - { - return binary_expression<bit_and_expression_node> ( - [](token const& t){ return t.type() == lexeme::Ampersand; }, - [=,this]{ return equality_expression(allow_angle_operators, allow_equality); } - ); - } - - //G bit-xor-expression: - //G bit-and-expression - //G bit-xor-expression '^' bit-and-expression - //G - auto bit_xor_expression(bool allow_angle_operators = true, bool allow_equality = true) - -> auto - { - return binary_expression<bit_xor_expression_node> ( - [](token const& t){ return t.type() == lexeme::Caret; }, - [=,this]{ return bit_and_expression(allow_angle_operators, allow_equality); } - ); - } - - //G bit-or-expression: - //G bit-xor-expression - //G bit-or-expression '|' bit-xor-expression - //G - auto bit_or_expression(bool allow_angle_operators = true, bool allow_equality = true) - -> auto - { - return binary_expression<bit_or_expression_node> ( - [](token const& t){ return t.type() == lexeme::Pipe; }, - [=,this]{ return bit_xor_expression(allow_angle_operators, allow_equality); } - ); - } - - //G logical-and-expression: - //G bit-or-expression - //G logical-and-expression '&&' bit-or-expression - //G - auto logical_and_expression(bool allow_angle_operators = true, bool allow_equality = true) - -> auto - { - return binary_expression<logical_and_expression_node> ( - [](token const& t){ return t.type() == lexeme::LogicalAnd; }, - [=,this]{ return bit_or_expression(allow_angle_operators, allow_equality); } - ); - } - - // constant-expression: // don't need intermediate production, just use: - // conditional-expression: // don't need intermediate production, just use: - //G logical-or-expression: - //G logical-and-expression - //G logical-or-expression '||' logical-and-expression - //G - auto logical_or_expression(bool allow_angle_operators = true, bool allow_equality = true) - -> auto - { - return binary_expression<logical_or_expression_node> ( - [](token const& t){ return t.type() == lexeme::LogicalOr; }, - [=,this]{ return logical_and_expression(allow_angle_operators, allow_equality); } - ); - } - - //G assignment-expression: - //G logical-or-expression - //G assignment-expression assignment-operator logical-or-expression - //G - auto assignment_expression( - bool allow_angle_operators = true - ) - -> std::unique_ptr<assignment_expression_node> - { - auto ret = std::unique_ptr<assignment_expression_node>{}; - - if (allow_angle_operators) - { - ret = binary_expression<assignment_expression_node> ( - [this](token const& t, token const& next) -> token const* { - if (is_assignment_operator(t.type())) { - return &t; - } - if ( - t.type() == lexeme::Greater - && next.type() == lexeme::GreaterEq - && t.position() == source_position{ next.position().lineno, next.position().colno-1 } - ) - { - generated_tokens->emplace_back( ">>=", t.position(), lexeme::RightShiftEq); - return &generated_tokens->back(); - } - return nullptr; - }, - [=,this]{ - return logical_or_expression(allow_angle_operators); - } - ); - } - else - { - ret = binary_expression<assignment_expression_node> ( - [](token const&, token const&) -> token const* { return nullptr; }, - [=,this]{ - return logical_or_expression(allow_angle_operators); - } - ); - } - - if (ret && ret->terms_size() > 1) { - error("assignment cannot be chained - instead of 'c = b = a;', write 'b = a; c = b;'", false); - return {}; - } - - return ret; - } - - //G expression: // eliminated 'condition:' - just use 'expression:' - //G assignment-expression - //GTODO try expression - //G - auto expression( - bool allow_angle_operators = true, - bool check_arrow = true - ) - -> std::unique_ptr<expression_node> - { - auto n = std::make_unique<expression_node>(); - - { - expression_node::current_expressions.push_back(n.get()); - auto guard = finally([&]{ expression_node::current_expressions.pop_back(); }); - - if (!(n->expr = assignment_expression(allow_angle_operators))) { - return {}; - } - - if ( - check_arrow - && !done() - && curr().type() == lexeme::Arrow - ) - { - error("'->' is not Cpp2 deference syntax - write '*.' instead"); - return {}; - } - } - - for (auto& e : expression_node::current_expressions) { - ++e->num_subexpressions; - } - return n; - } - - //G expression-list: - //G parameter-direction? expression - //G expression-list ',' parameter-direction? expression - //G - auto expression_list( - token const* open_paren, - bool inside_initializer = false - ) - -> std::unique_ptr<expression_list_node> - { - auto pass = passing_style::in; - auto n = std::make_unique<expression_list_node>(); - n->open_paren = open_paren; - n->inside_initializer = inside_initializer; - - if (auto dir = to_passing_style(curr()); - ( - dir == passing_style::out - || dir == passing_style::move - || dir == passing_style::forward - ) - && peek(1) - && peek(1)->type() == lexeme::Identifier - ) - { - pass = dir; - next(); - } - auto x = expression(); - - // If this is an empty expression_list, we're done - if (!x) { - return n; - } - - // Otherwise remember the first expression - n->expressions.push_back( { pass, std::move(x) } ); - // and see if there are more... - while (curr().type() == lexeme::Comma) { - next(); - pass = passing_style::in; - if (auto dir = to_passing_style(curr()); - dir == passing_style::out - || dir == passing_style::move - || dir == passing_style::forward - ) - { - pass = dir; - next(); - } - auto expr = expression(); - if (!expr) { - error("invalid text in expression list", true, {}, true); - return {}; - } - n->expressions.push_back( { pass, std::move(expr) } ); - } - return n; - } - - - //G type-id: - //G type-qualifier-seq? qualified-id - //G type-qualifier-seq? unqualified-id - //G - //G type-qualifier-seq: - //G type-qualifier - //G type-qualifier-seq type-qualifier - //G - //G type-qualifier: - //G 'const' - //G '*' - //G - auto type_id() - -> std::unique_ptr<type_id_node> - { - auto n = std::make_unique<type_id_node>(); - - while ( - (curr().type() == lexeme::Keyword && curr() == "const") - || curr().type() == lexeme::Multiply - ) - { - if ( - curr() == "const" - && !n->pc_qualifiers.empty() - && *n->pc_qualifiers.back() == "const" - ) - { - error("consecutive 'const' not allowed"); - return {}; - } - n->pc_qualifiers.push_back( &curr() ); - next(); - } - - if (auto id = qualified_id()) { - n->pos = id->position(); - n->id = std::move(id); - assert (n->id.index() == type_id_node::qualified); - } - else if (auto id = unqualified_id()) { - n->pos = id->position(); - n->id = std::move(id); - assert (n->id.index() == type_id_node::unqualified); - } - else { - if (!n->pc_qualifiers.empty()) { - error("'*'/'const' type qualifiers must be followed by a type name or '_' wildcard"); - } - return {}; - } - if (curr().type() == lexeme::Multiply) { - error("'T*' is not a valid Cpp2 type; use '*T' for a pointer instead", false); - return {}; - } - - return n; - } - - - //G is-as-expression: - //G prefix-expression - //G is-as-expression is-type-constraint - //G is-as-expression is-value-constraint - //G is-as-expression as-type-cast - //GTODO type-id is-type-constraint - //G - //G is-type-constraint - //G 'is' type-id - //G - //G is-value-constraint - //G 'is' expression - //G - //G as-type-cast - //G 'as' type-id - //G - auto is_as_expression() - -> std::unique_ptr<is_as_expression_node> - { - auto n = std::make_unique<is_as_expression_node>(); - n->expr = prefix_expression(); - if (!(n->expr)) { - return {}; - } - - auto is_found = false; - auto as_found = false; - - while ( - !done() - && (curr() == "is" || curr() == "as") - ) - { - if (curr() == "is") { - if (is_found) { - error("repeated 'is' are not allowed"); - return {}; - } - is_found = true; - } - else { - as_found = true; - } - - if (is_found && as_found) { - error("mixed 'is' and 'as' are not allowed"); - return {}; - } - - auto term = is_as_expression_node::term{}; - term.op = &curr(); - next(); - - if ((term.type = type_id()) != nullptr) { - ; - } - else if ((term.expr = expression()) != nullptr) { - ; - } - - if ( - *term.op == "as" - && term.expr - ) - { - error("'as' must be followed by a type-id, not an expression", false); - return {}; - } - if ( - !term.type - && !term.expr - ) - { - if (*term.op == "is") { - error( "'is' must be followed by a type-id or an expression", false); - } - else { - error( "'as' must be followed by a type-id", false); - } - return {}; - } - - n->ops.push_back( std::move(term) ); - } - - return n; - } - - - //G unqualified-id: - //G identifier - //G keyword - //G template-id - //GTODO operator-function-id - //G ... - //G - //G template-id: - //G identifier '<' template-argument-list? '>' - //G - //G template-argument-list: - //G template-argument-list ',' template-argument - //G - //G template-argument: - //G # note: < > << >> are not allowed in expressions until new ( is opened - //G 'const' type-id - //G expression - //G type-id - //G - auto unqualified_id() - -> std::unique_ptr<unqualified_id_node> - { - // Handle the identifier - if ( - curr().type() != lexeme::Identifier - && curr().type() != lexeme::Keyword - && curr().type() != lexeme::Cpp2FixedType - && curr().type() != lexeme::Ellipsis - ) - { - return {}; - } - - auto n = std::make_unique<unqualified_id_node>(); - - n->identifier = &curr(); - auto one_past_identifier_end_pos = curr().position(); - one_past_identifier_end_pos.colno += curr().length(); - next(); - - // Handle the template-argument-list if there is one - if ( - curr().type() == lexeme::Less - && curr().position() == one_past_identifier_end_pos - ) - { - // Remember current position, in case this < is isn't a template argument list - auto start_pos = pos; - - n->open_angle = curr().position(); - next(); - - auto term = template_argument{}; - - do { - // If it doesn't start with * or const (which can only be a type id), - // try parsing it as an expression - if (auto e = [&]{ - if ( - curr().type() == lexeme::Multiply // '*' - || curr() == "const" // 'const' - ) - { - return decltype(expression()){}; - } - return expression(false); // false == disallow unparenthesized relational comparisons in template args - }() - ) - { - term.arg = std::move(e); - } - - // Else try parsing it as a type id - else if (auto i = type_id()) { - term.arg = std::move(i); - } - - // Else if we already got at least one template-argument, this is a - // ',' followed by something that isn't a valid template-arg - else if (std::ssize(n->template_args) > 0) { - error( "expected a template argument after ','", false); - return {}; - } - - // Else this is an empty '<>' list which is okay - else { - break; - } - - n->template_args.push_back( std::move(term) ); - } - // Use the lambda trick to jam in a "next" clause - while ( - curr().type() == lexeme::Comma - && [&]{term.comma = curr().position(); next(); return true;}() - ); - // When this is rewritten in Cpp2, it will be: - // while curr().type() == lexeme::Comma - // next term.comma = curr().position(); - - if (curr().type() != lexeme::Greater) { - // Aha, this wasn't a template argument list after all, - // so back out just that part and return the identifier - n->open_angle = source_position{}; - n->template_args.clear(); - pos = start_pos; - return n; - } - n->close_angle = curr().position(); - next(); - } - - else { - if (*n->identifier == "new") { - error( "use 'new<" + curr().to_string() + ">', not 'new " + curr().to_string() + "'", false); - return {}; - } - if (*n->identifier == "co_await" || *n->identifier == "co_yield") { - error( "(temporary alpha limitation) coroutines are not yet supported in Cpp2", false); - return {}; - } - } - - return n; - } - - - //G qualified-id: - //G nested-name-specifier unqualified-id - //G member-name-specifier unqualified-id - //G - //G nested-name-specifier: - //G '::' - //G unqualified-id '::' - //G - //G member-name-specifier: - //G unqualified-id '.' - //G - auto qualified_id() - -> std::unique_ptr<qualified_id_node> - { - auto n = std::make_unique<qualified_id_node>(); - - auto term = qualified_id_node::term{nullptr}; - - // Handle initial :: if present, else the first scope_op will be null - if (curr().type() == lexeme::Scope) { - term.scope_op = &curr(); - next(); - } - - // Remember current position, because we need to look ahead to the next :: - auto start_pos = pos; - - // If we don't get a first id, or if we didn't have a leading :: and - // the next thing isn't :: or ., back out and report unsuccessful - term.id = unqualified_id(); - if ( - !term.id - || (!term.scope_op && curr().type() != lexeme::Scope) - ) - { - pos = start_pos; // backtrack - return {}; - } - - // Reject "std" :: "move" / "forward" - assert (term.id->identifier); - auto first_uid_was_std = (*term.id->identifier == "std"); - auto first_time_through_loop = true; - - n->ids.push_back( std::move(term) ); - - while (curr().type() == lexeme::Scope) - { - auto term = qualified_id_node::term{ &curr() }; - next(); - term.id = unqualified_id(); - if (!term.id) { - error("invalid text in qualified name", true, {}, true); - return {}; - } - assert (term.id->identifier); - if ( - first_time_through_loop - && first_uid_was_std - && term.scope_op->type() == lexeme::Scope - ) - { - if (*term.id->identifier == "move") { - error("std::move is not needed in Cpp2 - use 'move' parameters/arguments instead", false); - return {}; - } - else if (*term.id->identifier == "forward") { - error("std::forward is not needed in Cpp2 - use 'forward' parameters/arguments instead", false); - return {}; - } - first_time_through_loop = false; - } - n->ids.push_back( std::move(term) ); - } - - return n; - } - - - //G id-expression: - //G qualified-id - //G unqualified-id - //G - auto id_expression() - -> std::unique_ptr<id_expression_node> - { - auto n = std::make_unique<id_expression_node>(); - if (auto id = qualified_id()) { - n->pos = id->position(); - n->id = std::move(id); - assert (n->id.index() == id_expression_node::qualified); - return n; - } - if (auto id = unqualified_id()) { - n->pos = id->position(); - n->id = std::move(id); - assert (n->id.index() == id_expression_node::unqualified); - return n; - } - return {}; - } - - //G literal: - //G integer-literal ud-suffix? - //G character-literal ud-suffix? - //G floating-point-literal ud-suffix? - //G string-literal ud-suffix? - //G boolean-literal ud-suffix? - //G pointer-literal ud-suffix? - //G user-defined-literal ud-suffix? - //G - auto literal() - -> std::unique_ptr<literal_node> - { - if (is_literal(curr().type())) { - auto n = std::make_unique<literal_node>(); - n->literal = &curr(); - next(); - if (curr().type() == lexeme::UserDefinedLiteralSuffix) { - n->user_defined_suffix = &curr(); - next(); - } - return n; - } - return {}; - } - - //G expression-statement: - //G expression ';' - //G expression - //G - auto expression_statement( - bool semicolon_required - ) - -> std::unique_ptr<expression_statement_node> - { - auto n = std::make_unique<expression_statement_node>(); - - expression_statement_node::current_expression_statements.push_back(n.get()); - auto guard = finally([&]{ expression_statement_node::current_expression_statements.pop_back(); }); - - if (!(n->expr = expression(true, true))) { - return {}; - } - - if ( - semicolon_required - && (done() || curr().type() != lexeme::Semicolon) - && peek(-1)->type() != lexeme::Semicolon - // this last peek(-1)-condition is a hack (? or is it just - // maybe elegant? I'm torn) so that code like - // - // callback := :(inout x:_) = x += "suffix"; ; - // - // doesn't need the redundant semicolon at the end of a decl... - // there's probably a cleaner way to do it, but this works and - // it doesn't destabilize any regression tests - ) - { - return {}; - } - if ( - !done() - && curr().type() == lexeme::Semicolon - ) - { - n->has_semicolon = true; - next(); - } - return n; - } - - - //G selection-statement: - //G 'if' 'constexpr'? logical-or-expression compound-statement - //G 'if' 'constexpr'? logical-or-expression compound-statement 'else' compound-statement - //G - auto selection_statement() - -> std::unique_ptr<selection_statement_node> - { - if ( - curr().type() != lexeme::Keyword - || curr() != "if" - ) - { - return {}; - } - auto n = std::make_unique<selection_statement_node>(); - n->identifier = &curr(); - next(); - - if ( - curr().type() == lexeme::Keyword - && curr() == "constexpr" - ) - { - n->is_constexpr = true; - next(); - } - - if (auto e = logical_or_expression()) { - n->expression = std::move(e); - } - else { - error("invalid if condition", true, {}, true); - return {}; - } - - if (curr().type() != lexeme::LeftBrace) { - error("an if branch body must be enclosed with { }"); - return {}; - } - - if (auto s = compound_statement()) { - n->true_branch = std::move(s); - } - else { - error("invalid if branch body", true, {}, true); - return {}; - } - - if ( - curr().type() != lexeme::Keyword - || curr() != "else" - ) - { - // Add empty else branch to simplify processing elsewhere - // Note: Position (0,0) signifies it's implicit (no source location) - n->false_branch = - std::make_unique<compound_statement_node>( source_position(0,0) ); - } - else { - n->else_pos = curr().position(); - next(); - - if ( - curr().type() != lexeme::LeftBrace - && curr() != "if" - ) - { - error("an else branch body must be enclosed with { }"); - return {}; - } - - if (auto s = compound_statement( source_position{}, true )) { - n->false_branch = std::move(s); - n->has_source_false_branch = true; - } - else { - error("invalid else branch body", true, {}, true); - return {}; - } - } - - return n; - } - - - //G return-statement: - //G return expression? ';' - //G - auto return_statement() - -> std::unique_ptr<return_statement_node> - { - if ( - curr().type() != lexeme::Keyword - || curr() != "return" - ) - { - return {}; - } - - auto n = std::make_unique<return_statement_node>(); - n->identifier = &curr(); - next(); - - // If there's no optional return expression, we're done - if (curr().type() == lexeme::Semicolon) { - next(); - return n; - } - - // Handle the return expression - auto x = expression(); - if (!x) { - error("invalid return expression", true, {}, true); - return {}; - } - n->expression = std::move(x); - - // Final semicolon - if (curr().type() != lexeme::Semicolon) { - error("missing ; after return"); - next(); - return {}; - } - - next(); - return n; - } - - - //G iteration-statement: - //G label? 'while' logical-or-expression next-clause? compound-statement - //G label? 'do' compound-statement next-clause? 'while' logical-or-expression ';' - //G label? 'for' expression next-clause? 'do' unnamed-declaration - //G - //G label: - //G identifier ':' - //G - //G next-clause: - //G 'next' assignment-expression - //G - auto iteration_statement() - -> std::unique_ptr<iteration_statement_node> - { - auto n = std::make_unique<iteration_statement_node>(); - - // If the next three tokens are: - // identifier ':' 'for/while/do' - // then it's a labeled iteration statement - if ( - curr().type() == lexeme::Identifier - && peek(1) - && peek(1)->type() == lexeme::Colon - && peek(2) - && peek(2)->type() == lexeme::Keyword - && (*peek(2) == "while" || *peek(2) == "do" || *peek(2) == "for") - ) - { - n->label = &curr(); - next(); - next(); - } - - if ( - curr().type() != lexeme::Keyword - || (curr() != "while" && curr() != "do" && curr() != "for") - ) - { - return {}; - } - - n->identifier = &curr(); - next(); - - //----------------------------------------------------------------- - // We'll do these same things in different orders, - // so extract them into local functions... - auto handle_optional_next_clause = [&]() -> bool { - if (curr() != "next") { - return true; // absent next clause is okay - } - next(); // don't bother remembering "next" token, shouldn't need its position info - auto next = assignment_expression(); - if (!next) { - error("invalid expression after 'next'", true, {}, true); - return false; - } - n->next_expression = std::move(next); - return true; - }; - - auto handle_logical_expression = [&]() -> bool { - auto x = logical_or_expression(); - if (!x) { - error("a loop must have a valid conditional expression"); - return false; - } - n->condition = std::move(x); - return true; - }; - - auto handle_compound_statement = [&]() -> bool { - auto s = compound_statement(); - if (!s) { - error("invalid while loop body", true, {}, true); - return false; - } - n->statements = std::move(s); - return true; - }; - //----------------------------------------------------------------- - - // Handle "while" - // - if (*n->identifier == "while") - { - if (!handle_logical_expression ()) { return {}; } - if (!handle_optional_next_clause()) { return {}; } - if (!handle_compound_statement ()) { return {}; } - if (!done() && curr().type() == lexeme::Semicolon) { - error("a loop body may not be followed by a semicolon (empty statements are not allowed)"); - return {}; - } - return n; - } - - // Handle "do" - // - else if (*n->identifier == "do") - { - if (!handle_compound_statement ()) { return {}; } - if (!handle_optional_next_clause()) { return {}; } - if (curr() != "while") { - error("do loop body must be followed by 'while'"); - return {}; - } - next(); - if (!handle_logical_expression ()) { return {}; } - if (curr().type() != lexeme::Semicolon) { - error("missing ; after do..while loop condition"); - next(); - return {}; - } - next(); - return n; - } - - // Handle "for" - // - else if (*n->identifier == "for") - { - n->range = expression(); - if (!n->range) { - error("expected valid range expression after 'for'", true, {}, true); - return {}; - } - - if (!handle_optional_next_clause()) { return {}; } - - if ( - curr() != "do" - || !peek(1) - || peek(1)->type() != lexeme::LeftParen - ) - { - next(); - if (curr().type() == lexeme::Colon) { - error("alpha design change note: 'for range' syntax has changed - please remove ':' and '=', for example: for args do (arg) std::cout << arg;"); - } - else { - error("'for range' must be followed by 'do ( parameter )'"); - } - return {}; - } - next(2); // eat 'do' and '(' - - n->parameter = parameter_declaration(false, false, false); - if (!n->parameter) { - error("'for range do (' must be followed by a parameter declaration", false, source_position{}, true); - return {}; - } - - if (curr().type() != lexeme::RightParen) { - error("expected ')' after 'for' parameter"); - return {}; - } - next(); // eat ')' - - n->body = statement(); - if (!n->body) { - error("invalid for..do loop body", false, source_position{}, true); - return {}; - } - // else - if (n->parameter->pass == passing_style::in) { - n->for_with_in = true; - } - - if (!done() && curr().type() == lexeme::Semicolon) { - error("a loop body may not be followed by a semicolon (empty statements are not allowed)"); - return {}; - } - - return n; - } - - assert(!"compiler bug: unexpected case"); - return {}; - } - - - //G alternative: - //G alt-name? is-type-constraint '=' statement - //G alt-name? is-value-constraint '=' statement - //G alt-name? as-type-cast '=' statement - //G - //GTODO alt-name: - //G unqualified-id ':' - //G - auto alternative() - -> std::unique_ptr<alternative_node> - { - auto n = std::make_unique<alternative_node>(); - - if ( - curr().type() == lexeme::Identifier - && peek(1) - && peek(1)->type() == lexeme::Colon - ) - { - error("(temporary alpha limitation) declaring an identifier is not supported yet"); - return {}; - } - - // Now we should be as "is" or "as" - // (initial partial implementation, just "is/as id-expression") - if ( - curr() != "is" - && curr() != "as" - ) - { - return {}; - } - - n->is_as_keyword = &curr(); - next(); - - if (auto id = type_id()) { - n->type_id = std::move(id); - } - else if (auto e = postfix_expression()) { - n->value = std::move(e); - } - else { - error("expected type-id or value after 'is' in inspect alternative", true, {}, true); - return {}; - } - - if (curr().type() != lexeme::Assignment) { - error("expected = at start of inspect alternative body", true, {}, true); - return {}; - } - n->equal_sign = curr().position(); - next(); - - if (auto s = statement(true, n->equal_sign)) { - n->statement = std::move(s); - } - else { - error("expected statement after = in inspect alternative", true, {}, true); - return {}; - } - - return n; - } - - - //G inspect-expression: - //G 'inspect' 'constexpr'? expression '{' alternative-seq? '}' - //G 'inspect' 'constexpr'? expression '->' type-id '{' alternative-seq? '}' - //G - //G alternative-seq: - //G alternative - //G alternative-seq alternative - //G - auto inspect_expression(bool is_expression) - -> std::unique_ptr<inspect_expression_node> - { - if (curr() != "inspect") { - return {}; - } - - if (!is_expression) { - errors.emplace_back( - curr().position(), - "(temporary alpha limitation) cppfront is still learning 'inspect' - only inspect expressions are currently supported" - ); - return {}; - } - - auto n = std::make_unique<inspect_expression_node>(); - n->identifier = &curr(); - next(); - - if (curr() == "constexpr") { - n->is_constexpr = true; - next(); - } - - if (auto e = expression(true, false)) { - n->expression = std::move(e); - } - else { - error("invalid inspect expression", true, {}, true); - return {}; - } - - // Handle the optional explicit return type - if (curr().type() == lexeme::Arrow) - { - if (!is_expression) { - error("an inspect statement cannot have an explicit return type (whereas an inspect expression must have one)"); - return {}; - } - next(); - if (curr().type() == lexeme::LeftParen) { - error("multiple/named returns are not currently allowed for inspect"); - return {}; - } - - auto type = type_id(); - if (!type) { - error("expected a valid inspect return type after ->"); - return {}; - } - n->result_type = std::move(type); - } - else if (is_expression) { - error("an inspect expression must have an explicit '-> result_type'"); - return {}; - } - - // Now do the inspect body - if (curr().type() != lexeme::LeftBrace) { - error("expected { at start of inspect body"); - return {}; - } - n->open_brace = curr().position(); - next(); - - while (curr().type() != lexeme::RightBrace) - { - auto a = alternative(); - if (!a) { - error("invalid alternative in inspect", true, {}, true); - return {}; - } - if ( - is_expression - && !a->statement->is_expression() - ) - { - error("an inspect expression alternative must be just an expression " - "(not a braced block) that will be used as the value of the inspect expression"); - return {}; - } - n->alternatives.push_back( std::move(a) ); - } - - n->close_brace = curr().position(); - next(); - - if (n->alternatives.empty()) { - error("inspect body cannot be empty - add at least one alternative"); - return {}; - } - - return n; - } - - - //G jump-statement: - //G 'break' identifier? ';' - //G 'continue' identifier? ';' - //G - auto jump_statement() - -> std::unique_ptr<jump_statement_node> - { - auto n = std::make_unique<jump_statement_node>(); - - if ( - curr() != "break" - && curr() != "continue" - ) - { - return {}; - } - - n->keyword = &curr(); - next(); - - if (curr().type() == lexeme::Identifier) { - n->label = &curr(); - next(); - } - - if (curr().type() != lexeme::Semicolon) { - error("expected ; at end of jump-statement"); - return {}; - } - next(); - - return n; - } - - - //G using-statement: - //G 'using' id-expression ';' - //G 'using' 'namespace' id-expression ';' - //G - auto using_statement() - -> std::unique_ptr<using_statement_node> - { - auto n = std::make_unique<using_statement_node>(); - - if (curr() != "using") { - return {}; - } - n->keyword = &curr(); - next(); - - if (curr() == "namespace") { - n->for_namespace = true; - next(); - } - - auto id = id_expression(); - if (!id) { - error(std::string{"expected valid id-expression after 'using"} + (n->for_namespace ? " namespace" : "") + "'"); - return {}; - } - - n->id = std::move(id); - - if (curr().type() != lexeme::Semicolon) { - error("expected ; at end of using-statement"); - return {}; - } - next(); - - return n; - } - - - //G statement: - //G selection-statement - //G using-statement - //G inspect-expression - //G return-statement - //G jump-statement - //G iteration-statement - //G compound-statement - //G contract-statement - //G declaration - //G expression-statement - //G - //G contract-statement - //G contract ';' - // - //GTODO try-block - //G - auto statement( - bool semicolon_required = true, - source_position equal_sign = source_position{}, - bool parameters_allowed = false, - compound_statement_node* compound_parent = nullptr - ) - -> std::unique_ptr<statement_node> - { - if (!done() && curr().type() == lexeme::Semicolon) { - error("empty statement is not allowed - remove extra semicolon"); - return {}; - } - - auto n = std::make_unique<statement_node>(compound_parent); - - // If a parameter list is allowed here, try to parse one - if (parameters_allowed) { - n->parameters = parameter_declaration_list(false, true, false, true); - if (n->parameters) { - for (auto& param : n->parameters->parameters) { - if ( - param->direction() != passing_style::in - && param->direction() != passing_style::inout - && param->direction() != passing_style::copy - ) - { - error("(temporary alpha limitation) parameters scoped to a block/statement must be 'in' (the default), 'copy', or 'inout'", false); - return {}; - } - } - } - } - - // Now handle the rest of the statement - - if (auto s = selection_statement()) { - n->statement = std::move(s); - assert (n->is_selection()); - return n; - } - - else if (auto s = using_statement()) { - n->statement = std::move(s); - assert (n->is_using()); - return n; - } - - else if (auto i = inspect_expression(false)) { - n->statement = std::move(i); - assert (n->is_inspect()); - return n; - } - - else if (auto s = return_statement()) { - n->statement = std::move(s); - assert (n->is_return()); - return n; - } - - else if (auto s = jump_statement()) { - n->statement = std::move(s); - assert (n->is_jump()); - return n; - } - - else if (auto s = iteration_statement()) { - n->statement = std::move(s); - assert (n->is_iteration()); - return n; - } - - else if (auto s = compound_statement(equal_sign)) { - n->statement = std::move(s); - assert (n->is_compound()); - return n; - } - - else if (auto s = contract()) { - if (*s->kind != "assert") { - error("only 'assert' contracts are allowed at statement scope"); - return {}; - } - if (curr().type() != lexeme::Semicolon) { - error("missing ';' after contract-statement"); - return {}; - } - next(); - n->statement = std::move(s); - assert (n->is_contract()); - return n; - } - - else if (auto s = declaration(true, false, false, n.get())) { - n->statement = std::move(s); - assert (n->is_declaration()); - return n; - } - - else if (auto s = expression_statement(semicolon_required)) { - n->statement = std::move(s); - assert (n->is_expression()); - return n; - } - - else { - return {}; - } - } - - - //G compound-statement: - //G '{' statement-seq? '}' - //G - //G statement-seq: - //G statement - //G statement-seq statement - //G - auto compound_statement( - source_position equal_sign = source_position{}, - bool allow_single_unbraced_statement = false - ) - -> std::unique_ptr<compound_statement_node> - { - bool is_braced = curr().type() == lexeme::LeftBrace; - if ( - !is_braced - && !allow_single_unbraced_statement - ) - { - return {}; - } - - auto n = std::make_unique<compound_statement_node>(); - if (!is_braced) { - n->body_indent = curr().position().colno-1; - } - else if (peek(1)) { - n->body_indent = peek(1)->position().colno-1; - } - - // Remember current position, in case this isn't a valid statement - auto start_pos = pos; - - // In the case where this is a declaration initializer with - // = { - // on the same line, we want to remember our start position - // as where the = was, not where the { was - if (equal_sign.lineno == curr().position().lineno) { - n->open_brace = equal_sign; - } - else { - n->open_brace = curr().position(); - } - - if (is_braced) { - next(); - } - - while ( - curr().type() != lexeme::RightBrace - && ( - is_braced - || std::ssize(n->statements) < 1 - ) - ) - { - // Only inside a compound-statement, a - // contained statement() may have parameters - auto s = statement(true, source_position{}, true, n.get()); - if (!s) { - - // Only add error when no specific one already exist - if(!has_error()) { - error("invalid statement encountered inside a compound-statement", true); - } - pos = start_pos; // backtrack - return {}; - } - n->statements.push_back( std::move(s) ); - } - - if (is_braced) { - assert(curr().type() == lexeme::RightBrace); - n->close_brace = curr().position(); - next(); - } - return n; - } - - - //G parameter-declaration: - //G this-specifier? parameter-direction? declaration - //G - //G parameter-direction: one of - //G 'in' 'copy' 'inout' 'out' 'move' 'forward' - //G - //G this-specifier: - //G 'implicit' - //G 'virtual' - //G 'override' - //G 'final' - //G - auto parameter_declaration( - bool is_returns = false, - bool is_named = true, - bool is_template = true, - bool is_statement = false - ) - -> std::unique_ptr<parameter_declaration_node> - { - // Remember current position, because we may need to backtrack if this is just - // a parenthesized expression statement, not a statement parameter list - auto start_pos = pos; - - auto n = std::make_unique<parameter_declaration_node>(); - n->pass = - is_returns ? passing_style::out : - passing_style::in; - n->pos = curr().position(); - - // Handle optional this-specifier - // - if (curr() == "implicit") { - n->mod = parameter_declaration_node::modifier::implicit; - next(); - } - else if (curr() == "virtual") { - n->mod = parameter_declaration_node::modifier::virtual_; - next(); - } - else if (curr() == "override") { - n->mod = parameter_declaration_node::modifier::override_; - next(); - } - else if (curr() == "final") { - n->mod = parameter_declaration_node::modifier::final_; - next(); - } - - // Handle optional parameter-direction - // - if (auto dir = to_passing_style(curr()); - dir != passing_style::invalid - ) - { - if (is_template) { - error("a template parameter cannot have a passing style (it is always implicitly 'in')"); - return {}; - } - - if (is_returns) - { - if (dir == passing_style::in) { - error("a return value cannot be 'in'"); - return {}; - } - if (dir == passing_style::copy) { - error("a return value cannot be 'copy'"); - return {}; - } - if (dir == passing_style::inout) { - error("a return value cannot be 'inout'"); - return {}; - } - if (dir == passing_style::move) { - error("a return value cannot be 'move' (it is implicitly 'move'-out)"); - return {}; - } - } - if ( - !is_named - && dir == passing_style::out - ) - { - error("(temporary alpha limitation) an unnamed function cannot have an 'out' parameter"); - return {}; - } - n->pass = dir; - next(); - } - - // Now the main declaration - // - if (!(n->declaration = declaration(false, true, is_template))) { - pos = start_pos; // backtrack - return {}; - } - - // And some error checks - // - if ( - n->mod != parameter_declaration_node::modifier::none - && !n->declaration->has_name("this") - ) - { - error( "only a 'this' parameter may be declared implicit, virtual, override, or final", false ); - } - - if ( - n->declaration->has_name("this") - && n->pass != passing_style::in - && n->pass != passing_style::inout - && n->pass != passing_style::out - && n->pass != passing_style::move - ) - { - error( "a 'this' parameter must be in, inout, out, or move", false ); - } - - if ( - n->declaration->has_name("that") - && n->pass != passing_style::in - && n->pass != passing_style::move - ) - { - error( "a 'that' parameter must be in or move", false ); - } - - // The only parameter type that could be const-qualified is a 'copy' parameter, because - // only it is always truly its own variable, so it makes sense to let the user qualify it; - // all the other parameter types are conceptually (usually actually) bound to their args - if ( - !is_returns - && n->declaration->is_const() - && n->pass != passing_style::copy - && n->pass != passing_style::inout - ) - { - switch (n->pass) { - break;case passing_style::in: - error( "an 'in' parameter is always const, 'const' isn't needed and isn't allowed", false ); - break;case passing_style::inout: - // error( "an 'inout' parameter can't be const, if you do want it to be const then use 'in' instead", false ); - break;case passing_style::out: - error( "an 'out' parameter can't be const, otherwise it can't be initialized in the function body", false ); - break;case passing_style::move: - error( "a 'move' parameter can't be const, otherwise it can't be moved from in the function body", false ); - break;case passing_style::forward: - error( "a 'forward' parameter shouldn't be const, because it passes along the argument's actual const-ness (and actual value category)", false ); - break;default: - assert (!"ICE: missing case"); - } - return {}; - } - - if ( - !is_returns - && !is_statement - && n->declaration->initializer - ) - { - error("Cpp2 is currently exploring the path of not allowing default arguments - use overloading instead", false); - return {}; - } - if (is_named && is_returns) { - auto tok = n->name(); - assert(tok); - if (tok->type() != lexeme::Identifier) { - error("expected identifier, not '" + tok->to_string() + "'", - false, tok->position()); - } - else if (n->declaration->has_wildcard_type()) { - error("return parameter '" + tok->to_string() + "' must have a type", - false, tok->position()); - } - } - return n; - } - - - //G parameter-declaration-list - //G '(' parameter-declaration-seq? ')' - //G - //G parameter-declaration-seq: - //G parameter-declaration - //G parameter-declaration-seq ',' parameter-declaration - //G - auto parameter_declaration_list( - bool is_returns = false, - bool is_named = true, - bool is_template = false, - bool is_statement = false - ) - -> std::unique_ptr<parameter_declaration_list_node> - { - // Remember current position, because we need to look ahead in - // the case of seeing whether a local statement starts with a - // parameter list, since finding that it doesn't (it's some other - // parenthesized expression) is not an error, just backtrack - auto start_pos = pos; - - auto opener = lexeme::LeftParen; - auto closer = lexeme::RightParen; - if (is_template) { - opener = lexeme::Less; - closer = lexeme::Greater; - } - - if (curr().type() != opener) { - return {}; - } - - auto n = std::make_unique<parameter_declaration_list_node>(); - n->open_paren = &curr(); - next(); - - auto param = std::make_unique<parameter_declaration_node>(); - - auto count = 1; - auto expect_another_param_decl = false; - - while ((param = parameter_declaration(is_returns, is_named, is_template, is_statement)) != nullptr) - { - expect_another_param_decl = false; - param->ordinal = count; - ++count; - - if ( - std::ssize(n->parameters) > 1 - && n->parameters.back()->has_name("that") - ) - { - error("'that' may not be followed by any additional parameters", false); - } - - n->parameters.push_back( std::move(param) ); - - if (curr().type() == closer) { - break; - } - else if (curr().type() != lexeme::Comma) { - if (is_statement) { - pos = start_pos; // backtrack - } - else { - error("expected ',' in parameter list", true, {}, true); - } - return {}; - } - - expect_another_param_decl = true; - next(); - } - - if (expect_another_param_decl) { - error("invalid parameter list: a comma must be followed by another parameter", true, {}, true); - } - - if (curr().type() != closer) { - if (is_statement) { - pos = start_pos; // backtrack - } - else { - error("invalid parameter list", true, {}, true); - } - return {}; - } - - n->close_paren = &curr(); - next(); - return n; - } - - - //G contract: - //G contract-kind contract-group? ':' '(' logical-or-expression ')' - //G contract-kind contract-group? ':' '(' logical-or-expression ',' expression ')' - //G - //G contract-group: - //G '<' id-expression contract-flags?'>' - //G - //G contract-flags: - //G ',' id-expression contract-flags? - //G - //G contract-kind: one of - //G 'pre' 'post' 'assert' - //G - auto contract() - -> std::unique_ptr<contract_node> - { - auto n = std::make_unique<contract_node>(curr().position()); - auto guard = capture_groups_stack_guard(this, &n->captures); - - if ( - curr() != "pre" - && curr() != "post" - && curr() != "assert" - ) - { - return {}; - } - n->kind = &curr(); - next(); - - // Check if there's a <group,flags> - if (curr().type() == lexeme::Less) { - next(); - if (auto id = id_expression()) { - n->group = std::move(id); - } - else { - error("invalid contract group after '<'"); - return {}; - } - - // Now check if there's a list of flags - while (curr().type() == lexeme::Comma) { - next(); - if (auto id = id_expression()) { - n->flags.push_back( std::move(id) ); - } - else { - error("invalid contract tag in list"); - return {}; - } - } - - if (curr().type() != lexeme::Greater) { - error("expected '>' after contract group"); - return {}; - } - next(); - } - - if (curr().type() != lexeme::LeftParen) { - error("expected '(' before the contract condition"); - return {}; - } - next(); - - auto condition = logical_or_expression(); - if (!condition) { - error("invalid contract condition", true, {}, true); - return {}; - } - n->condition = std::move(condition); - - // Now check for the optional string message - if (curr().type() == lexeme::Comma) { - next(); - n->message = expression(); - if (!n->message) { - error("a contract violation message must be a valid string expression", true, {}, true); - return {}; - } - } - - if (curr().type() != lexeme::RightParen) { - error("expected ')' at the end of the contract"); - return {}; - } - next(); - - return n; - } - - - //G function-type: - //G parameter-declaration-list throws-specifier? return-list? contract-seq? - //G - //G throws-specifier: - //G 'throws' - //G - //G return-list: - //G expression-statement - //G '->' parameter-direction? type-id - //G '->' parameter-declaration-list - //G - //G contract-seq: - //G contract - //G contract-seq contract - //G - auto function_type( - declaration_node* my_decl, - bool is_named = true - ) - -> std::unique_ptr<function_type_node> - { - auto n = std::make_unique<function_type_node>( my_decl ); - - // Parameters - auto parameters = parameter_declaration_list(false, is_named, false); - if (!parameters) { - return {}; - } - n->parameters = std::move(parameters); - - // Optional "throws" - if ( - curr().type() == lexeme::Keyword - && curr() == "throws" - ) - { - if ( - n->is_move() - || n->is_swap() - || n->is_destructor() - ) - { - error( "(experimental restriction) Cpp2 currently does not allow a move, swap, or destructor function to be designated 'throws'" ); - return {}; - } - - n->throws = true; - next(); - } - - - // If we're not at a '->' or 'requires' or contract and what follows is - // an expression, this is a ":(params) expr" shorthand function syntax - if ( - curr().type() != lexeme::Arrow - && curr() != "requires" - && (curr() != "pre" && curr() != "post") - ) - { - auto start_pos = pos; - auto at_an_expression = expression() != nullptr; - pos = start_pos; // backtrack no matter what, we're just peeking here - - if (at_an_expression) { - n->returns = function_type_node::single_type_id{ std::make_unique<type_id_node>() }; - assert(n->returns.index() == function_type_node::id); - n->my_decl->terse_no_equals = true; - return n; - } - } - - - // Optional returns - if (curr().type() == lexeme::Arrow) - { - next(); - - if (auto pass = to_passing_style(curr()); - pass != passing_style::invalid - ) - { - if ( - pass != passing_style::forward - && pass != passing_style::move - ) - { - error("only 'forward' and 'move' return passing style are allowed from functions"); - } - next(); - if (auto t = type_id()) { - n->returns = function_type_node::single_type_id{ std::move(t), pass }; - assert(n->returns.index() == function_type_node::id); - } - else { - auto msg = std::string("'"); - msg += to_string_view(pass); - error(msg + "' must be followed by a type-id"); - } - } - - else if (auto t = type_id()) - { - if ( - t->get_token() - && t->get_token()->to_string() == "auto" - ) - { - auto name = std::string{"v"}; - if (my_decl && my_decl->name()) { - name = my_decl->name()->to_string(); - } - errors.emplace_back( - curr().position(), - "to define a function " + name + " with deduced return type, write '" + name + ": ( /* arguments */ ) -> _ = { /* function body */ }'" - ); - return {}; - } - n->returns = function_type_node::single_type_id{ std::move(t), passing_style::move }; - assert(n->returns.index() == function_type_node::id); - } - - else if (auto returns_list = parameter_declaration_list(true, is_named)) - { - if (std::ssize(returns_list->parameters) < 1) { - error("an explicit return value list cannot be empty"); - return {}; - } - n->returns = std::move(returns_list); - assert(n->returns.index() == function_type_node::list); - } - - else - { - error("missing function return after ->"); - return {}; - } - } - - // Pre/post conditions - while (auto c = contract()) - { - if ( - *c->kind != "pre" - && *c->kind != "post" - ) - { - error("only 'pre' and 'post' contracts are allowed on functions"); - return {}; - } - n->contracts.push_back( std::move(c) ); - } - - return n; - } - - - auto apply_type_metafunctions( declaration_node& decl ) - -> bool; - - - //G unnamed-declaration: - //G ':' meta-functions-list? template-parameter-declaration-list? function-type requires-clause? '=' statement - //G ':' meta-functions-list? template-parameter-declaration-list? function-type statement - //G ':' meta-functions-list? template-parameter-declaration-list? type-id? requires-clause? '=' statement - //G ':' meta-functions-list? template-parameter-declaration-list? type-id - //G ':' meta-functions-list? template-parameter-declaration-list? 'final'? 'type' requires-clause? '=' statement - //G ':' 'namespace' '=' statement - //G - //G meta-functions-list: - //G '@' id-expression - //G meta-functions-list '@' id-expression - //G - //G requires-clause: - //G # note: for aliases, == is not allowed in expressions until new ( is opened - //G 'requires' logical-or-expression - //G - //G template-parameter-declaration-list - //G '<' parameter-declaration-seq '>' - //G - auto unnamed_declaration( - source_position start, - bool semicolon_required = true, - bool captures_allowed = false, - bool named = false, - bool is_parameter = false, - bool is_template_parameter = false, - std::unique_ptr<unqualified_id_node> id = {}, - accessibility access = {}, - bool is_variadic = false, - statement_node* my_stmt = {} - ) - -> std::unique_ptr<declaration_node> - { - auto n = std::make_unique<declaration_node>( current_declarations.back() ); - n->pos = start; - - n->identifier = std::move(id); - n->access = access; - n->is_variadic = is_variadic; - n->my_statement = my_stmt; - - // If we're in a type scope and the next token is ';', treat this as if - // ': _;' without an initializer. - // This is for type metafunctions that want to use the incomplete name-only - // declaration, and transform it to something else. If unchanged the - // incomplete declaration will be rejected later by sema.check rule. - if ( - n->parent_is_type() - && curr().type() == lexeme::Semicolon - ) - { - n->type = std::make_unique<type_id_node>(); - assert (n->is_object()); - next(); - return n; - } - - // For a template parameter, ':' is not required and - // we default to ': type' - if ( - is_template_parameter - && curr().type() != lexeme::Colon - ) - { - // So invent the "type" token - generated_text.push_back("type"); - generated_tokens->push_back({ - generated_text.back().c_str(), - std::ssize(generated_text.back()), - start, - lexeme::Identifier - }); - - // So we can create the type_node - - auto t = std::make_unique<type_node>( &generated_tokens->back() ); - - n->type = std::move(t); - assert (n->is_type()); - - // That's it, we're done here - return n; - } - - // For 'this' and 'that' parameters ':' is not allowed and we'll use the default ': _' - if ( - n->identifier - && is_parameter - && ( - *n->identifier->identifier == "this" - || *n->identifier->identifier == "that" - ) - && curr().type() == lexeme::Colon - ) - { - error("a 'this' or 'that' parameter knows its type, no ':' is allowed here", false); - return {}; - } - - // For an ordinary parameter, ':' is not required and - // we default to ': _' - i.e., deduced with no initializer - if ( - is_parameter - && curr().type() != lexeme::Colon - ) - { - // So invent the "_" token - generated_text.push_back("_"); - generated_tokens->push_back({ - generated_text.back().c_str(), - std::ssize(generated_text.back()), - start, - lexeme::Identifier - }); - - // So we can create the typeid_id_node and its unqualified_id_node - - auto gen_id = std::make_unique<unqualified_id_node>(); - gen_id->identifier = &generated_tokens->back(); - - auto type = std::make_unique<type_id_node>(); - type->pos = start; - type->id = std::move(gen_id); - - n->type = std::move(type); - assert (n->is_object()); - - // That's it, we're done here - return n; - } - - // Otherwise, the next token must be ':' - if (curr().type() != lexeme::Colon) { - return {}; - } - next(); - - if (curr() == "union") { - error("unsafe 'union' is not supported in Cpp2 - write '@union' to apply Cpp2's safe 'union' type metafunction instead, or use std::variant"); - } - - // Next is an optional metafunctions clause - while (curr() == "@") { - next(); - auto idx = id_expression(); - if (!idx) { - error("'@' must be followed by a metafunction name", false); - return {}; - } - n->metafunctions.push_back( std::move(idx) ); - } - - // Next is an optional template parameter list - if (curr().type() == lexeme::Less) { - auto template_parameters = parameter_declaration_list(false, false, true); - if (!template_parameters) { - error("invalid template parameter list"); - return {}; - } - n->template_parameters = std::move(template_parameters); - } - - auto guard = - captures_allowed - ? std::make_unique<capture_groups_stack_guard>(this, &n->captures) - : std::unique_ptr<capture_groups_stack_guard>() - ; - - auto guard2 = current_declarations_stack_guard(this, n.get()); - - // Next is an an optional type - - auto deduced_type = false; - - // It could be "type", declaring a user-defined type - if ( - curr() == "type" - || ( - curr() == "final" - && peek(1) && *peek(1) == "type" - ) - ) - { - n->type = std::make_unique<type_node>( &curr(), curr() == "final" ); - - if (curr() == "final") { - next(); - } - next(); - - if ( - is_parameter - && !is_template_parameter - ) - { - error("a normal parameter cannot be a 'type' - did you mean to put this in a < > template parameter list?"); - return {}; - } - assert (n->is_type()); - } - - // Or a function type, declaring a function - and tell the function whether it's in a user-defined type - else if (auto t = function_type(n.get(), named)) - { - n->type = std::move(t); - assert (n->is_function()); - - if (!n->metafunctions.empty()) { - errors.emplace_back( - n->metafunctions.front()->position(), - "(temporary alpha limitation) metafunctions are currently not supported on functions, only on types" - ); - return {}; - } - } - - // Or a namespace - else if (curr() == "namespace") - { - n->type = std::make_unique<namespace_node>( &curr() ); - assert (n->type.index() == declaration_node::a_namespace); - next(); - - if (!n->metafunctions.empty()) { - errors.emplace_back( - n->metafunctions.front()->position(), - "(temporary alpha limitation) metafunctions are currently not supported on namespaces, only on types" - ); - return {}; - } - } - - // Or just a type-id, declaring a non-pointer object - else if (auto t = type_id()) - { - if ( - t->get_token() - && t->get_token()->to_string() == "auto" - ) - { - auto name = std::string{"v"}; - if (n->name()) { - name = n->name()->to_string(); - } - errors.emplace_back( - curr().position(), - "to define a variable " + name + " with deduced type, write '" + name + " := /* initializer */;'" - ); - return {}; - } - - n->type = std::move(t); - assert (n->is_object()); - - if (!n->metafunctions.empty()) { - errors.emplace_back( - n->metafunctions.front()->position(), - "(temporary alpha limitation) metafunctions are currently not supported on objects, only on types" - ); - return {}; - } - - if (curr().type() == lexeme::LeftBracket) { - error("C-style array types are not allowed, use std::array instead"); - return {}; - } - } - - // Or nothing, declaring an object of deduced type, - // which we'll represent using an empty type-id - else { - n->type = std::make_unique<type_id_node>(); - assert (n->is_object()); - deduced_type = true; - } - - // If we've already validated that this is a function where the parameter - // list is followed by a valid expression-statement, parse that again - // (requiring a semicolon as we validated when determining terse_no_equals) - if (n->terse_no_equals) - { - n->equal_sign = curr().position(); - n->initializer = statement(/*ignore semicolon_required*/ false, n->equal_sign); - assert( n->initializer && "ICE: should have already validated that there's a valid expression-statement here" ); - } - - else - { - // Next is optionally a requires clause (if not using the "-> expr;" syntax) - if (curr() == "requires") - { - if ( - n->is_type() - && !n->template_parameters - ) - { - error("'requires' is not allowed on a type that does not have a template parameter list"); - return {}; - } - - if (n->is_namespace()) - { - error("'requires' is not allowed on a namespace"); - return {}; - } - - n->requires_pos = curr().position(); - next(); - auto e = logical_or_expression(); - if (!e) { - error("'requires' must be followed by an expression"); - return {}; - } - n->requires_clause_expression = std::move(e); - } - - // Next is optionally = or == followed by an initializer - - // If there is no = or == - if ( - !done() - && curr().type() != lexeme::Assignment - && curr().type() != lexeme::EqualComparison - ) - { - if ( - n->is_type() - && !is_template_parameter - ) - { - error("a user-defined type must have an = initializer"); - return {}; - } - - // Then there may be a semicolon - // If there is a semicolon, eat it - if (!done() && curr().type() == lexeme::Semicolon) { - next(); - } - // But if there isn't one and it was required, diagnose an error - else if (semicolon_required) { - if (curr().type() == lexeme::LeftBrace) { - error("expected '=' before '{' - did you mean '= {' ?", true, {}, true); - } - else { - error("missing ';' at end of declaration or '=' at start of initializer", true, {}, true); - } - return {}; - } - } - - // There was an = or ==, so eat it and continue - else - { - n->equal_sign = curr().position(); - - if (curr().type() == lexeme::EqualComparison) { - if (!n->is_function()) { - error("syntax error at '==' - did you mean '='?"); - } - n->is_constexpr = true; - } - - next(); - - if (auto t = std::get_if<declaration_node::an_object>(&n->type); - t - && (*t)->is_pointer_qualified() - ) - { - if ( - curr() == "nullptr" - || isdigit(std::string_view(curr())[0]) - || ( - curr() == "(" - && peek(1) - && *peek(1) == ")" - ) - ) - { - error("pointer cannot be initialized to null or int - leave it uninitialized and then set it to a non-null value when you have one"); - violates_lifetime_safety = true; - throw std::runtime_error("null initialization detected"); - } - } - - // deduced_type == true means that the type will be deduced, - // represented using an empty type-id - if ( - deduced_type - && peek(1) - ) - { - auto& type = std::get<declaration_node::an_object>(n->type); - // object initialized by the address of the curr() object - if (peek(1)->type() == lexeme::Ampersand) { - type->address_of = &curr(); - } - // object initialized by (potentially multiple) dereference of the curr() object - else if (peek(1)->type() == lexeme::Multiply) { - type->dereference_of = &curr(); - for (int i = 1; peek(i)->type() == lexeme::Multiply; ++i) - type->dereference_cnt += 1; - } - else if ( - // object initialized by the result of the function call (and it is not unnamed function) - (peek(1)->type() == lexeme::LeftParen && curr().type() != lexeme::Colon) - || curr().type() == lexeme::Identifier // or by the object (variable that the type need to be checked) - ) { - type->suspicious_initialization = &curr(); - } - } - - if (!(n->initializer = statement(semicolon_required, n->equal_sign))) { - error( - "ill-formed initializer", - true, {}, true - ); - next(); - return {}; - } - } - - } - - // A type initializer must be a compound expression - if ( - n->is_type() - && !is_parameter - && ( - !n->initializer - || !n->initializer->is_compound() - ) - ) - { - errors.emplace_back( - n->position(), - "a user-defined type initializer must be a compound-expression consisting of declarations" - ); - return {}; - } - - // If this is a type with metafunctions, apply those - if (n->is_type()) { - if (!apply_type_metafunctions(*n)) { - error( - "error encountered while applying type metafunctions", - false, {}, true - ); - return {}; - } - } - - if ( - n->is_function() - && n->initializer - && !done() && curr().type() == lexeme::Semicolon - ) - { - if (n->initializer->is_compound() && n->has_name()) { - error("a braced function body may not be followed by a semicolon (empty statements are not allowed)"); - return {}; - } else if (n->initializer->is_expression()) { - error("a single-expression function should end with a single semicolon"); - return {}; - } - } - - // If this is a function with a list of multiple/named return values, - // and the function body's end doesn't already have "return" as the - // last statement, then generate "return;" as the last statement - if (auto func = std::get_if<declaration_node::a_function>(&n->type); - func - && n->initializer - && (*func)->returns.index() == function_type_node::list - ) - { - if (!n->initializer->is_compound()) { - error( - "a function with named return value(s) must have a full { } body", - false, - {}, - true - ); - return {}; - } - - auto& body = std::get<statement_node::compound>(n->initializer->statement); - - if ( - body->statements.empty() - || !body->statements.back()->is_return() - ) - { - auto last_pos = n->position(); - if (!body->statements.empty()) { - last_pos = body->statements.back()->position(); - } - ++last_pos.lineno; - generated_tokens->emplace_back( "return", last_pos, lexeme::Keyword); - - auto ret = std::make_unique<return_statement_node>(); - ret->identifier = &generated_tokens->back(); - - auto stmt = std::make_unique<statement_node>(); - stmt->statement = std::move(ret); - - body->statements.push_back(std::move(stmt)); - } - } - - // If this is a function, record its extents - if (n->is_function()) { - function_body_extents.emplace_back( - n->equal_sign.lineno, - peek(-1)->position().lineno - ); - } - - return n; - } - - - //G alias: - //G ':' template-parameter-declaration-list? 'type' requires-clause? '==' type-id ';' - //G ':' 'namespace' '==' id-expression ';' - //G ':' template-parameter-declaration-list? type-id? requires-clause? '==' expression ';' - //G - //GT ':' function-type '==' expression ';' - //GT # See commit 63efa6ed21c4d4f4f136a7a73e9f6b2c110c81d7 comment - //GT # for why I don't see a need to enable this yet - // - auto alias() - -> std::unique_ptr<declaration_node> - { - // Remember current position, because we need to look ahead - auto start_pos = pos; - - auto n = std::make_unique<declaration_node>( current_declarations.back() ); - - if (curr().type() != lexeme::Colon) { - return {}; - } - next(); - - // Next is an optional template parameter list - if (curr().type() == lexeme::Less) { - auto template_parameters = parameter_declaration_list(false, false, true); - if (!template_parameters) { - pos = start_pos; // backtrack - return {}; - } - n->template_parameters = std::move(template_parameters); - } - - auto a = std::make_unique<alias_node>( &curr() ); - - // Next must be 'type', 'namespace', a type-id, or we're at the 'requires' or '==' - if (curr() == "type") - { - next(); - } - else if (curr() == "namespace") - { - next(); - if (n->template_parameters) { - errors.emplace_back( - curr().position(), - "a namespace or namespace alias cannot have template parameters" - ); - return {}; - } - } - else if (curr().type() != lexeme::EqualComparison && curr() != "requires") - { - a->type_id = type_id(); - if (!a->type_id) { - pos = start_pos; // backtrack - return {}; - } - } - - // Next is optionally a requires clause - if (curr() == "requires") - { - if ( - n->is_type_alias() - && !n->template_parameters - ) - { - error("'requires' is not allowed on a type alias that does not have a template parameter list"); - return {}; - } - - if (n->is_namespace_alias()) - { - error("'requires' is not allowed on a namespace alias"); - return {}; - } - - n->requires_pos = curr().position(); - next(); - auto e = logical_or_expression(true, false); - if (!e) { - error("'requires' must be followed by an expression"); - return {}; - } - n->requires_clause_expression = std::move(e); - } - - // Now we should be at the '==' if this is an alias - - if (curr().type() == lexeme::EqualComparison) { - next(); - } - else { - if (a->type->type() != lexeme::EqualComparison) { - pos = start_pos; // backtrack - return {}; - } - } - assert(peek(-1)->type() == lexeme::EqualComparison); - - if ( - n->parent_is_type() - && *a->type == "namespace" - ) - { - errors.emplace_back( - curr().position(), - "a namespace alias cannot appear in a type scope" - ); - return {}; - } - - // Finally, pick up the initializer - - // Type alias - if (*a->type == "type") - { - auto t = type_id(); - if (!t) { - errors.emplace_back( - curr().position(), - "a 'type ==' alias declaration must be followed by a type name" - ); - return {}; - } - if ( - t->is_wildcard() - || ( t->get_token() && t->get_token()->to_string() == "auto" ) - ) { - errors.emplace_back( - curr().position(), - "a 'type ==' alias declaration must be followed by a type name (not a wildcard _ nor auto)" - ); - return {}; - } - a->initializer = std::move(t); - } - - // Namespace alias - else if (*a->type == "namespace") - { - if (auto qid = id_expression()) { - a->initializer = std::move(qid); - } - else { - errors.emplace_back( - curr().position(), - "a 'namespace ==' alias declaration must be followed by a namespace name (id-expression)" - ); - return {}; - } - } - - // Object alias - else if ( - a->type_id - || a->type->type() == lexeme::EqualComparison - ) - { - auto e = expression(); - if (!e) { - errors.emplace_back( - curr().position(), - "an object '==' alias declaration must be followed by an expression" - ); - return {}; - } - a->initializer = std::move(e); - } - - // Anything else shouldn't be possible - else { - assert(!"ICE: should be unreachable - invalid alias declaration"); - return {}; - } - - // And the final ceremonial semicolon - if (curr() != ";") { - errors.emplace_back( - curr().position(), - "';' expected at end of alias declaration" - ); - return {}; - } - next(); - - n->type = std::move(a); - - return n; - } - - - //G declaration: - //G access-specifier? identifier '...'? unnamed-declaration - //G access-specifier? identifier alias - //G - //G access-specifier: - //G public - //G protected - //G private - //G - auto declaration( - bool semicolon_required = true, - bool is_parameter = false, - bool is_template_parameter = false, - statement_node* my_stmt = {} - ) - -> std::unique_ptr<declaration_node> - { - if (done()) { return {}; } - - // Remember current position, because we need to look ahead - auto start_pos = pos; - - auto n = std::unique_ptr<declaration_node>{}; - - // This scope is to ensure that once we've moved 'id' into the - // declaration_node, we don't access the moved-from local name - // (and similar hygiene for 'access' though that one doesn't matter as much) - // The reason to move 'id' into unnamed_declaration() is so that - // it can conveniently perform some checks that refer to the name - { - auto access = accessibility::default_; - if (curr() == "public") { - access = accessibility::public_; - next(); - } - else if (curr() == "protected") { - access = accessibility::protected_; - next(); - } - else if (curr() == "private") { - access = accessibility::private_; - next(); - } - - // If they wrote an access-specifier, see if they put a ':' - // after it out of Cpp1 habit (there's no colon in Cpp2) - if ( - access != accessibility::default_ - && curr().type() == lexeme::Colon - ) - { - errors.emplace_back( - curr().position(), - "':' is not allowed after an access-specifier" - ); - return {}; - } - - auto id = unqualified_id(); - if (!id) { - return {}; - } - - if (id->to_string() == "...") { - errors.emplace_back( - curr().position(), - "a variadic declaration must have a name - did you forget to write a name before '...'?" - ); - pos = start_pos; // backtrack - } - - auto is_variadic = false; - if (curr().type() == lexeme::Ellipsis) { - is_variadic = true; - next(); - } - - // Provide some useful Cpp1->Cpp2 migration diagnostics for common mistakes - // - if ( - id->get_token() - && *id->get_token() == "auto" - && curr().type() != lexeme::Colon - ) - { - auto name = std::string{"v"}; - if (peek(0) && peek(0)->type() == lexeme::Identifier) { - name = peek(0)->to_string(); - } - errors.emplace_back( - curr().position(), - "to define a variable " + name + " of type T, write '" + name + ": T = /* initializer */'" - ); - return {}; - } - if ( - id->get_token() - && *id->get_token() == "namespace" - && curr().type() != lexeme::Colon - ) - { - auto name = std::string{"N"}; - if (peek(0)) { - name = peek(0)->to_string(); - } - errors.emplace_back( - curr().position(), - "to define a namespace " + name + ", write '" + name + " : namespace = { /*contents*/ }'" - ); - return {}; - } - if ( - id->get_token() - && ( - *id->get_token() == "class" - || *id->get_token() == "struct" - ) - && curr().type() != lexeme::Colon - ) - { - auto name = std::string{"C"}; - if (peek(0)) { - name = peek(0)->to_string(); - } - errors.emplace_back( - curr().position(), - "to define a type " + name + ", write '" + name + " : type = { /*body*/ }'" - ); - return {}; - } - - // Now proceed... - // - - // First see if it's an alias declaration - n = alias(); - if (n) { - if (is_parameter) { - errors.emplace_back( - curr().position(), - "a parameter declaration may not be an alias declaration" - ); - return {}; - } - - if (is_variadic) { - errors.emplace_back( - curr().position(), - "an alias declaration may not be variadic" - ); - return {}; - } - - n->pos = start_pos; - n->identifier = std::move(id); - n->access = access; - return n; - } - - // Otherwise, this is a normal declaration - n = unnamed_declaration( - start_pos, - semicolon_required, - false, - true, - is_parameter, - is_template_parameter, - std::move(id), - access, - is_variadic, - my_stmt - ); - if (!n) { - pos = start_pos; // backtrack - return {}; - } - } - - // Note: Do this after trying to parse this as a declaration, for parse backtracking - - if ( - *n->identifier->identifier == "that" - && ( - !is_parameter - || is_template_parameter - ) - ) - { - errors.emplace_back( - n->identifier->position(), - "'that' may only be declared as an ordinary function parameter" - ); - return {}; - } - - // Cache some context - n->is_template_parameter = is_template_parameter; - n->is_parameter = is_parameter; - - return n; - } - - - //G declaration-seq: - //G declaration - //G declaration-seq declaration - //G - //G translation-unit: - //G declaration-seq? - // - auto translation_unit() - -> std::unique_ptr<translation_unit_node> - { - auto n = std::make_unique<translation_unit_node>(); - for (auto d = declaration(); d; d = declaration()) { - n->declarations.push_back( std::move(d) ); - } - return n; - } - -public: - //----------------------------------------------------------------------- - // debug_print - // - auto debug_print(std::ostream& o) - -> void; -}; - - -//----------------------------------------------------------------------- -// -// Common parts for printing visitors -// -//----------------------------------------------------------------------- -// -struct printing_visitor -{ - //----------------------------------------------------------------------- - // Constructor: remember a stream to write to - // - std::ostream& o; - - printing_visitor(std::ostream& out) : o{out} { indent_spaces = 2; } -}; - - -//----------------------------------------------------------------------- -// -// Visitor for printing a parse tree -// -//----------------------------------------------------------------------- -// -class parse_tree_printer : printing_visitor -{ - using printing_visitor::printing_visitor; - -public: - auto start(token const& n, int indent) -> void - { - o << pre(indent) << _as<std::string>(n.type()) << ": " << n.to_string() << "\n"; - } - - auto start(literal_node const&, int indent) -> void - { - o << pre(indent) << "literal" << "\n"; - } - - auto start(expression_node const& n, int indent) -> void - { - o << pre(indent) << "expression - " - << n.num_subexpressions << " subexpressions, my_statement [" - << static_cast<void const*>(n.my_statement) << "]\n"; - } - - auto start(expression_list_node::term const&n, int indent) -> void - { - o << pre(indent) << "expression-list term\n"; - if (n.pass == passing_style::out) { - o << pre(indent+1) << "out\n"; - } - } - - auto start(expression_list_node const&, int indent) -> void - { - o << pre(indent) << "expression-list\n"; - } - - auto start(primary_expression_node const&, int indent) -> void - { - o << pre(indent) << "primary-expression\n"; - } - - auto start(prefix_expression_node const&, int indent) -> void - { - o << pre(indent) << "prefix-expression\n"; - } - - auto start(is_as_expression_node const&, int indent) -> void - { - o << pre(indent) << "is-as-expression\n"; - } - - template<String Name, typename Term> - auto start(binary_expression_node<Name, Term> const&, int indent) -> void - { - o << pre(indent) << Name.value << "-expression\n"; - } - - auto start(expression_statement_node const& n, int indent) -> void - { - o << pre(indent) << "expression-statement - [" << static_cast<void const*>(&n) << "]\n"; - } - - auto start(postfix_expression_node const&, int indent) -> void - { - o << pre(indent) << "postfix-expression\n"; - } - - auto start(unqualified_id_node const&, int indent) -> void - { - o << pre(indent) << "unqualified-id\n"; - } - - auto start(qualified_id_node const&, int indent) -> void - { - o << pre(indent) << "qualified-id\n"; - } - - auto start(type_id_node const&, int indent) -> void - { - o << pre(indent) << "type-id\n"; - } - - auto start(id_expression_node const&, int indent) -> void - { - o << pre(indent) << "id-expression\n"; - } - - auto start(statement_node const&, int indent) -> void - { - o << pre(indent) << "statement\n"; - } - - auto start(compound_statement_node const&, int indent) -> void - { - o << pre(indent) << "compound-statement\n"; - } - - auto start(selection_statement_node const& n, int indent) -> void - { - o << pre(indent) << "selection-statement\n"; - o << pre(indent+1) << "is_constexpr: " << _as<std::string>(n.is_constexpr) << "\n"; - } - - auto start(alternative_node const&, int indent) -> void - { - o << pre(indent) << "alternative\n"; - } - - auto start(jump_statement_node const&, int indent) -> void - { - o << pre(indent) << "jump\n"; - } - - auto start(using_statement_node const& n, int indent) -> void - { - o << pre(indent) << "using" << (n.for_namespace? " namespace" : "") << "\n"; - } - - auto start(inspect_expression_node const& n, int indent) -> void - { - o << pre(indent) << "inspect-expression\n"; - o << pre(indent+1) << "is_constexpr: " << _as<std::string>(n.is_constexpr) << "\n"; - } - - auto start(return_statement_node const&, int indent) -> void - { - o << pre(indent) << "return-statement\n"; - } - - auto start(iteration_statement_node const& n, int indent) -> void - { - o << pre(indent) << "iteration-statement\n"; - assert(n.identifier); - o << pre(indent+1) << "identifier: " << std::string_view(*n.identifier) << "\n"; - } - - auto start(contract_node const& n, int indent) -> void - { - o << pre(indent) << "contract\n"; - assert(n.kind); - o << pre(indent+1) << "kind: " << std::string_view(*n.kind) << "\n"; - if (!n.captures.members.empty()) { - o << pre(indent+1) << "captures: " << n.captures.members.size() << "\n"; - } - } - - auto start(type_node const&, int indent) -> void - { - o << pre(indent) << "user-defined type\n"; - } - - auto start(namespace_node const&, int indent) -> void - { - o << pre(indent) << "namespace\n"; - } - - auto start(function_type_node const& n, int indent) -> void - { - o << pre(indent) << "function\n"; - o << pre(indent+1) << "throws: " << _as<std::string>(n.throws) << "\n"; - if (n.returns.index() == function_type_node::id) { - auto& r = std::get<function_type_node::id>(n.returns); - if (r.pass != passing_style::invalid) { - o << pre(indent+1) << "returns by: " << to_string_view(r.pass) << "\n"; - } - } - } - - auto start(function_returns_tag const&, int indent) -> void - { - o << pre(indent) << "function returns\n"; - } - - auto start(template_args_tag const&, int indent) -> void - { - o << pre(indent) << "template arguments\n"; - } - - auto start(declaration_identifier_tag const&, int indent) -> void - { - o << pre(indent) << "declaration identifier\n"; - } - - auto start(next_expression_tag const&, int indent) -> void - { - o << pre(indent) << "next expression\n"; - } - - auto start(alias_node const& n, int indent) -> void - { - o << pre(indent) << "alias\n"; - switch (n.initializer.index()) { - break;case alias_node::a_type: - o << pre(indent+1) << "type\n"; - break;case alias_node::a_namespace: - o << pre(indent+1) << "namespace\n"; - break;case alias_node::an_object: - o << pre(indent+1) << "object\n"; - break;default: - o << pre(indent+1) << "ICE - invalid variant state\n"; - } - } - - auto start(declaration_node const& n, int indent) -> void - { - o << pre(indent) << "declaration [" << &n << "]\n"; - o << pre(indent+1) << "parent: [" << n.parent_declaration << "]\n"; - o << pre(indent+1) << "is_variadic: [" << std::boolalpha << n.is_variadic << "]\n"; - o << pre(indent+1) << "is_constexpr: " << _as<std::string>(n.is_constexpr) << "\n"; - switch (n.type.index()) { - break;case declaration_node::a_function: - o << pre(indent+1) << "function\n"; - break;case declaration_node::an_object: - o << pre(indent+1) << "object\n"; - break;case declaration_node::a_type: - o << pre(indent+1) << "type\n"; - break;case declaration_node::a_namespace: - o << pre(indent+1) << "namespace\n"; - break;case declaration_node::an_alias: - o << pre(indent+1) << "alias\n"; - break;default: - o << pre(indent+1) << "ICE - invalid variant state\n"; - } - if (!n.is_default_access()) { - o << pre(indent+1) << "access: " << to_string(n.access) << "\n"; - } - if (!n.captures.members.empty()) { - o << pre(indent+1) << "captures: " << n.captures.members.size() << "\n"; - } - } - - auto start(parameter_declaration_node const& n, int indent) -> void - { - o << pre(indent) << "parameter-declaration\n"; - - o << pre(indent+1); - switch (n.pass) { - break;case passing_style::in : o << "in"; - break;case passing_style::copy : o << "copy"; - break;case passing_style::inout : o << "inout"; - break;case passing_style::out : o << "out"; - break;case passing_style::move : o << "move"; - break;case passing_style::forward: o << "forward"; - break;default: ; - } - - o << pre(indent+1); - switch (n.mod) { - break;case parameter_declaration_node::modifier::implicit : o << "implicit"; - break;case parameter_declaration_node::modifier::virtual_ : o << "virtual"; - break;case parameter_declaration_node::modifier::override_ : o << "override"; - break;case parameter_declaration_node::modifier::final_ : o << "final"; - break;default: ; - } - o << "\n"; - - assert( n.declaration ); - } - - auto start(parameter_declaration_list_node const&, int indent) -> void - { - o << pre(indent) << "parameter-declaration-list\n"; - } - - auto start(translation_unit_node const&, int indent) -> void - { - o << pre(indent) << "translation-unit\n"; - } - - auto start(auto const&, int indent) -> void - { - o << pre(indent) << "UNRECOGNIZED -- FIXME\n"; - } - - auto end(auto const&, int) -> void - { - // Ignore other node types - } -}; - - -auto parser::debug_print(std::ostream& o) - - -> void -{ - o << "\n\n--- Parse tree\n"; - - auto tree_printer = parse_tree_printer{o}; - visit( tree_printer ); - - o << "\n\n--- Function body extents\n"; - - for (auto const& f : function_body_extents) { - o << " " << f.first << "-" << f.last << "\n"; - } -} - - -} - -#endif diff --git a/64x0/cc2/source/reflect.h b/64x0/cc2/source/reflect.h deleted file mode 100644 index 1cb66f7..0000000 --- a/64x0/cc2/source/reflect.h +++ /dev/null @@ -1,1965 +0,0 @@ - -#ifndef REFLECT_H_CPP2 -#define REFLECT_H_CPP2 - - -//=== Cpp2 type declarations ==================================================== - - -#include "cpp2util.h" - -#line 1 "reflect.h2" - -#line 20 "reflect.h2" -namespace cpp2 { - -namespace meta { - -#line 32 "reflect.h2" -class compiler_services; - -#line 223 "reflect.h2" -class declaration_base; - -#line 249 "reflect.h2" -class declaration; - -#line 331 "reflect.h2" -class function_declaration; - -#line 418 "reflect.h2" -class object_declaration; - -#line 454 "reflect.h2" -class type_declaration; - -#line 589 "reflect.h2" -class alias_declaration; - -#line 928 "reflect.h2" -class value_member_info; - -#line 1445 "reflect.h2" -} - -} - - -//=== Cpp2 type definitions and function declarations =========================== - -#line 1 "reflect.h2" - -// 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. - - -//=========================================================================== -// Reflection and meta -//=========================================================================== - -#include "parse.h" - -#line 20 "reflect.h2" -namespace cpp2 { - -namespace meta { - -#line 25 "reflect.h2" -//----------------------------------------------------------------------- -// -// Compiler services -// -//----------------------------------------------------------------------- -// - -class compiler_services - { - // Common data members - // - private: std::vector<error_entry>* errors; - private: int errors_original_size; - private: std::deque<token>* generated_tokens; - private: cpp2::parser parser; - private: std::string metafunction_name {}; - private: std::vector<std::string> metafunction_args {}; - private: bool metafunctions_used {false}; - - // Constructor - // - public: explicit compiler_services( - - std::vector<error_entry>* errors_, - std::deque<token>* generated_tokens_ - ); - -#line 58 "reflect.h2" - // Common API - // - public: auto set_metafunction_name(cpp2::in<std::string_view> name, cpp2::in<std::vector<std::string>> args) & -> void; - -#line 66 "reflect.h2" - public: [[nodiscard]] auto get_metafunction_name() const& -> std::string_view; - - public: [[nodiscard]] auto get_argument(cpp2::in<int> index) & -> std::string; - -#line 76 "reflect.h2" - public: [[nodiscard]] auto get_arguments() & -> std::vector<std::string>; - -#line 81 "reflect.h2" - public: [[nodiscard]] auto arguments_were_used() const& -> bool; -using parse_statement_ret = std::unique_ptr<statement_node>; - - -#line 83 "reflect.h2" - protected: [[nodiscard]] auto parse_statement( - - std::string_view source - ) & -> parse_statement_ret; - -#line 136 "reflect.h2" - public: [[nodiscard]] virtual auto position() const -> source_position; - -#line 142 "reflect.h2" - // Error diagnosis and handling, integrated with compiler output - // Unlike a contract violation, .requires continues further processing - // - public: auto require( - - cpp2::in<bool> b, - cpp2::in<std::string_view> msg - ) const& -> void; - -#line 156 "reflect.h2" - public: auto error(cpp2::in<std::string_view> msg) const& -> void; - -#line 165 "reflect.h2" - // Enable custom contracts on this object, integrated with compiler output - // Unlike .requires, a contract violation stops further processing - // - public: auto report_violation(auto const& msg) const& -> void; - -#line 173 "reflect.h2" - public: [[nodiscard]] auto has_handler() const& -> auto; - public: virtual ~compiler_services() noexcept; -public: compiler_services(compiler_services const& that); - -#line 174 "reflect.h2" -}; - -#line 177 "reflect.h2" -/* -//----------------------------------------------------------------------- -// -// Type IDs -// -//----------------------------------------------------------------------- -// - -// All type_ids are wrappers around a pointer to node -// -type_id: @polymorphic_base @copyable type = -{ - this: compiler_services = (); - - n: type_id_node; - - protected operator=: ( - out this, - n_: type_id_node, - s : compiler_services - ) - = { - compiler_services = s; - n = n_; - assert( n, "a meta::type_id must point to a valid type_id_node, not null" ); - } - - is_wildcard : (this) -> bool = n.is_wildcard(); - is_pointer_qualified: (this) -> bool = n.is_pointer_qualified(); - template_args_count : (this) -> int = n.template_arguments().ssize(); - to_string : (this) -> std::string = n.to_string(); - - position: (override this) -> source_position = n.position(); -} -*/ - -#line 214 "reflect.h2" -//----------------------------------------------------------------------- -// -// Declarations -// -//----------------------------------------------------------------------- -// - -// All declarations are wrappers around a pointer to node -// -class declaration_base -: public compiler_services { - -#line 227 "reflect.h2" - protected: declaration_node* n; - - protected: explicit declaration_base( - - declaration_node* n_, - cpp2::in<compiler_services> s - ); - -#line 240 "reflect.h2" - public: [[nodiscard]] auto position() const -> source_position override; - - public: [[nodiscard]] auto print() const& -> std::string; - public: virtual ~declaration_base() noexcept; -public: declaration_base(declaration_base const& that); - -#line 243 "reflect.h2" -}; - -#line 246 "reflect.h2" -//----------------------------------------------------------------------- -// All declarations -// -class declaration -: public declaration_base { - -#line 253 "reflect.h2" - public: explicit declaration( - - declaration_node* n_, - cpp2::in<compiler_services> s - ); - -#line 262 "reflect.h2" - public: [[nodiscard]] auto is_public() const& -> bool; - public: [[nodiscard]] auto is_protected() const& -> bool; - public: [[nodiscard]] auto is_private() const& -> bool; - public: [[nodiscard]] auto is_default_access() const& -> bool; - - public: auto default_to_public() & -> void; - public: auto default_to_protected() & -> void; - public: auto default_to_private() & -> void; - - public: [[nodiscard]] auto make_public() & -> bool; - public: [[nodiscard]] auto make_protected() & -> bool; - public: [[nodiscard]] auto make_private() & -> bool; - - public: [[nodiscard]] auto has_name() const& -> bool; - public: [[nodiscard]] auto has_name(cpp2::in<std::string_view> s) const& -> bool; - - public: [[nodiscard]] auto name() const& -> std::string_view; - -#line 283 "reflect.h2" - public: [[nodiscard]] auto has_initializer() const& -> bool; - - public: [[nodiscard]] auto is_global() const& -> bool; - public: [[nodiscard]] auto is_function() const& -> bool; - public: [[nodiscard]] auto is_object() const& -> bool; - public: [[nodiscard]] auto is_base_object() const& -> bool; - public: [[nodiscard]] auto is_member_object() const& -> bool; - public: [[nodiscard]] auto is_type() const& -> bool; - public: [[nodiscard]] auto is_namespace() const& -> bool; - public: [[nodiscard]] auto is_alias() const& -> bool; - - public: [[nodiscard]] auto is_type_alias() const& -> bool; - public: [[nodiscard]] auto is_namespace_alias() const& -> bool; - public: [[nodiscard]] auto is_object_alias() const& -> bool; - - public: [[nodiscard]] auto is_function_expression() const& -> bool; - - public: [[nodiscard]] auto as_function() const& -> function_declaration; - public: [[nodiscard]] auto as_object() const& -> object_declaration; - public: [[nodiscard]] auto as_type() const& -> type_declaration; - public: [[nodiscard]] auto as_alias() const& -> alias_declaration; - - public: [[nodiscard]] auto get_parent() const& -> declaration; - - public: [[nodiscard]] auto parent_is_function() const& -> bool; - public: [[nodiscard]] auto parent_is_object() const& -> bool; - public: [[nodiscard]] auto parent_is_type() const& -> bool; - public: [[nodiscard]] auto parent_is_namespace() const& -> bool; - public: [[nodiscard]] auto parent_is_alias() const& -> bool; - - public: [[nodiscard]] auto parent_is_type_alias() const& -> bool; - public: [[nodiscard]] auto parent_is_namespace_alias() const& -> bool; - public: [[nodiscard]] auto parent_is_object_alias() const& -> bool; - - public: [[nodiscard]] auto parent_is_polymorphic() const& -> bool; - - public: auto mark_for_removal_from_enclosing_type() & -> void; - public: virtual ~declaration() noexcept; -public: declaration(declaration const& that); - - // this precondition should be sufficient ... - -#line 325 "reflect.h2" -}; - -#line 328 "reflect.h2" -//----------------------------------------------------------------------- -// Function declarations -// -class function_declaration -: public declaration { - -#line 335 "reflect.h2" - public: explicit function_declaration( - - declaration_node* n_, - cpp2::in<compiler_services> s - ); - -#line 345 "reflect.h2" - public: [[nodiscard]] auto index_of_parameter_named(cpp2::in<std::string_view> s) const& -> int; - public: [[nodiscard]] auto has_parameter_named(cpp2::in<std::string_view> s) const& -> bool; - public: [[nodiscard]] auto has_in_parameter_named(cpp2::in<std::string_view> s) const& -> bool; - public: [[nodiscard]] auto has_out_parameter_named(cpp2::in<std::string_view> s) const& -> bool; - public: [[nodiscard]] auto has_move_parameter_named(cpp2::in<std::string_view> s) const& -> bool; - public: [[nodiscard]] auto first_parameter_name() const& -> std::string; - - public: [[nodiscard]] auto has_parameter_with_name_and_pass(cpp2::in<std::string_view> s, cpp2::in<passing_style> pass) const& -> bool; - - public: [[nodiscard]] auto is_function_with_this() const& -> bool; - public: [[nodiscard]] auto is_virtual() const& -> bool; - public: [[nodiscard]] auto is_defaultable() const& -> bool; - public: [[nodiscard]] auto is_constructor() const& -> bool; - public: [[nodiscard]] auto is_default_constructor() const& -> bool; - public: [[nodiscard]] auto is_move() const& -> bool; - public: [[nodiscard]] auto is_swap() const& -> bool; - public: [[nodiscard]] auto is_constructor_with_that() const& -> bool; - public: [[nodiscard]] auto is_constructor_with_in_that() const& -> bool; - public: [[nodiscard]] auto is_constructor_with_move_that() const& -> bool; - public: [[nodiscard]] auto is_assignment() const& -> bool; - public: [[nodiscard]] auto is_assignment_with_that() const& -> bool; - public: [[nodiscard]] auto is_assignment_with_in_that() const& -> bool; - public: [[nodiscard]] auto is_assignment_with_move_that() const& -> bool; - public: [[nodiscard]] auto is_destructor() const& -> bool; - - public: [[nodiscard]] auto is_copy_or_move() const& -> bool; - - public: [[nodiscard]] auto has_declared_return_type() const& -> bool; - public: [[nodiscard]] auto has_deduced_return_type() const& -> bool; - public: [[nodiscard]] auto has_bool_return_type() const& -> bool; - public: [[nodiscard]] auto has_non_void_return_type() const& -> bool; - - public: [[nodiscard]] auto unnamed_return_type() const& -> std::string; - - public: [[nodiscard]] auto get_parameters() const& -> std::vector<object_declaration>; - -#line 389 "reflect.h2" - public: [[nodiscard]] auto is_binary_comparison_function() const& -> bool; - - public: auto default_to_virtual() & -> void; - - public: [[nodiscard]] auto make_virtual() & -> bool; - - public: auto add_initializer(cpp2::in<std::string_view> source) & -> void; - public: function_declaration(function_declaration const& that); - - -#line 412 "reflect.h2" -}; - -#line 415 "reflect.h2" -//----------------------------------------------------------------------- -// Object declarations -// -class object_declaration -: public declaration { - -#line 422 "reflect.h2" - public: explicit object_declaration( - - declaration_node* n_, - cpp2::in<compiler_services> s - ); - -#line 432 "reflect.h2" - public: [[nodiscard]] auto is_const() const& -> bool; - public: [[nodiscard]] auto has_wildcard_type() const& -> bool; - - public: [[nodiscard]] auto type() const& -> std::string; - -#line 442 "reflect.h2" - public: [[nodiscard]] auto initializer() const& -> std::string; - public: object_declaration(object_declaration const& that); - - -#line 448 "reflect.h2" -}; - -#line 451 "reflect.h2" -//----------------------------------------------------------------------- -// Type declarations -// -class type_declaration -: public declaration { - -#line 458 "reflect.h2" - public: explicit type_declaration( - - declaration_node* n_, - cpp2::in<compiler_services> s - ); - -#line 468 "reflect.h2" - public: auto reserve_names(cpp2::in<std::string_view> name, auto&& ...etc) const& -> void; - -#line 480 "reflect.h2" - public: [[nodiscard]] auto is_polymorphic() const& -> bool; - public: [[nodiscard]] auto is_final() const& -> bool; - public: [[nodiscard]] auto make_final() & -> bool; - - public: [[nodiscard]] auto get_member_functions() const& -> std::vector<function_declaration>; - -#line 495 "reflect.h2" - public: [[nodiscard]] auto get_member_functions_needing_initializer() const& -> std::vector<function_declaration>; - -#line 510 "reflect.h2" - public: [[nodiscard]] auto get_member_objects() const& -> std::vector<object_declaration>; - -#line 520 "reflect.h2" - public: [[nodiscard]] auto get_member_types() const& -> std::vector<type_declaration>; - -#line 530 "reflect.h2" - public: [[nodiscard]] auto get_member_aliases() const& -> std::vector<alias_declaration>; - -#line 540 "reflect.h2" - public: [[nodiscard]] auto get_members() const& -> std::vector<declaration>; -struct query_declared_value_set_functions_ret { bool out_this_in_that; bool out_this_move_that; bool inout_this_in_that; bool inout_this_move_that; }; - - - -#line 550 "reflect.h2" - public: [[nodiscard]] auto query_declared_value_set_functions() const& -> query_declared_value_set_functions_ret; - -#line 565 "reflect.h2" - public: auto add_member(cpp2::in<std::string_view> source) & -> void; - -#line 579 "reflect.h2" - public: auto remove_marked_members() & -> void; - public: auto remove_all_members() & -> void; - - public: auto disable_member_function_generation() & -> void; - public: type_declaration(type_declaration const& that); - -#line 583 "reflect.h2" -}; - -#line 586 "reflect.h2" -//----------------------------------------------------------------------- -// Alias declarations -// -class alias_declaration -: public declaration { - -#line 593 "reflect.h2" - public: explicit alias_declaration( - - declaration_node* n_, - cpp2::in<compiler_services> s - ); - public: alias_declaration(alias_declaration const& that); - - -#line 602 "reflect.h2" -}; - -#line 605 "reflect.h2" -//----------------------------------------------------------------------- -// -// Metafunctions - these are hardwired for now until we get to the -// step of writing a Cpp2 interpreter to run inside the compiler -// -//----------------------------------------------------------------------- -// - -//----------------------------------------------------------------------- -// Some common metafunction helpers (metafunctions are just functions, -// so they can be factored as usual) -// -auto add_virtual_destructor(meta::type_declaration& t) -> void; - -#line 623 "reflect.h2" -//----------------------------------------------------------------------- -// -// "... an abstract base class defines an interface ..." -// -// -- Stroustrup (The Design and Evolution of C++, 12.3.1) -// -//----------------------------------------------------------------------- -// -// interface -// -// an abstract base class having only pure virtual functions -// -auto interface(meta::type_declaration& t) -> void; - -#line 662 "reflect.h2" -//----------------------------------------------------------------------- -// -// "C.35: A base class destructor should be either public and -// virtual, or protected and non-virtual." -// -// "[C.43] ... a base class should not be copyable, and so does not -// necessarily need a default constructor." -// -// -- Stroustrup, Sutter, et al. (C++ Core Guidelines) -// -//----------------------------------------------------------------------- -// -// polymorphic_base -// -// A pure polymorphic base type that is not copyable, and whose -// destructor is either public and virtual or protected and nonvirtual. -// -// Unlike an interface, it can have nonpublic and nonvirtual functions. -// -auto polymorphic_base(meta::type_declaration& t) -> void; - -#line 706 "reflect.h2" -//----------------------------------------------------------------------- -// -// "... A totally ordered type ... requires operator<=> that -// returns std::strong_ordering. If the function is not -// user-written, a lexicographical memberwise implementation -// is generated by default..." -// -// -- P0707R4, section 3 -// -// Note: This feature derived from Cpp2 was already adopted -// into Standard C++ via paper P0515, so most of the -// heavy lifting is done by the Cpp1 C++20/23 compiler, -// including the memberwise default semantics -// (In contrast, cppfront has to do the work itself for -// default memberwise semantics for operator= assignment -// as those aren't yet part of Standard C++) -// -//----------------------------------------------------------------------- -// - -auto ordered_impl( - meta::type_declaration& t, - cpp2::in<std::string_view> ordering// must be "strong_ordering" etc. -) -> void; - -#line 750 "reflect.h2" -//----------------------------------------------------------------------- -// ordered - a totally ordered type -// -// Note: the ordering that should be encouraged as default gets the nice name -// -auto ordered(meta::type_declaration& t) -> void; - -#line 760 "reflect.h2" -//----------------------------------------------------------------------- -// weakly_ordered - a weakly ordered type -// -auto weakly_ordered(meta::type_declaration& t) -> void; - -#line 768 "reflect.h2" -//----------------------------------------------------------------------- -// partially_ordered - a partially ordered type -// -auto partially_ordered(meta::type_declaration& t) -> void; - -#line 777 "reflect.h2" -//----------------------------------------------------------------------- -// -// "A value is ... a regular type. It must have all public -// default construction, copy/move construction/assignment, -// and destruction, all of which are generated by default -// if not user-written; and it must not have any protected -// or virtual functions (including the destructor)." -// -// -- P0707R4, section 3 -// -//----------------------------------------------------------------------- -// -// copyable -// -// A type with (copy and move) x (construction and assignment) -// -auto copyable(meta::type_declaration& t) -> void; - -#line 814 "reflect.h2" -//----------------------------------------------------------------------- -// -// basic_value -// -// A regular type: copyable, plus has public default construction -// and no protected or virtual functions -// -auto basic_value(meta::type_declaration& t) -> void; - -#line 839 "reflect.h2" -//----------------------------------------------------------------------- -// -// "A 'value' is a totally ordered basic_value..." -// -// -- P0707R4, section 3 -// -// value - a value type that is totally ordered -// -// Note: the ordering that should be encouraged as default gets the nice name -// -auto value(meta::type_declaration& t) -> void; - -#line 855 "reflect.h2" -auto weakly_ordered_value(meta::type_declaration& t) -> void; - -#line 861 "reflect.h2" -auto partially_ordered_value(meta::type_declaration& t) -> void; - -#line 868 "reflect.h2" -//----------------------------------------------------------------------- -// -// "By definition, a `struct` is a `class` in which members -// are by default `public`; that is, -// -// struct s { ... -// -// is simply shorthand for -// -// class s { public: ... -// -// ... Which style you use depends on circumstances and taste. -// I usually prefer to use `struct` for classes that have all -// data `public`." -// -// -- Stroustrup (The C++ Programming Language, 3rd ed., p. 234) -// -//----------------------------------------------------------------------- -// -// struct -// -// a type with only public bases, objects, and functions, -// no virtual functions, and no user-defined constructors -// (i.e., no invariants) or assignment or destructors. -// -auto cpp2_struct(meta::type_declaration& t) -> void; - -#line 911 "reflect.h2" -//----------------------------------------------------------------------- -// -// "C enumerations constitute a curiously half-baked concept. ... -// the cleanest way out was to deem each enumeration a separate type." -// -// -- Stroustrup (The Design and Evolution of C++, 11.7) -// -// "An enumeration is a distinct type ... with named constants" -// -// -- ISO C++ Standard -// -//----------------------------------------------------------------------- -// -// basic_enum -// -// a type together with named constants that are its possible values -// -class value_member_info { - public: std::string name; - public: std::string type; - public: std::string value; -}; - -auto basic_enum( - meta::type_declaration& t, - auto const& nextval, - cpp2::in<bool> bitwise - ) -> void; - -#line 1117 "reflect.h2" -//----------------------------------------------------------------------- -// -// "An enum[...] is a totally ordered value type that stores a -// value of its enumerators's type, and otherwise has only public -// member variables of its enumerator's type, all of which are -// naturally scoped because they are members of a type." -// -// -- P0707R4, section 3 -// -auto cpp2_enum(meta::type_declaration& t) -> void; - -#line 1143 "reflect.h2" -//----------------------------------------------------------------------- -// -// "flag_enum expresses an enumeration that stores values -// corresponding to bitwise-or'd enumerators. The enumerators must -// be powers of two, and are automatically generated [...] A none -// value is provided [...] Operators | and & are provided to -// combine and extract values." -// -// -- P0707R4, section 3 -// -auto flag_enum(meta::type_declaration& t) -> void; - -#line 1175 "reflect.h2" -//----------------------------------------------------------------------- -// -// "As with void*, programmers should know that unions [...] are -// inherently dangerous, should be avoided wherever possible, -// and should be handled with special care when actually needed." -// -// -- Stroustrup (The Design and Evolution of C++, 14.3.4.1) -// -// "C++17 needs a type-safe union... The implications of the -// consensus `variant` design are well understood and have been -// explored over several LEWG discussions, over a thousand emails, -// a joint LEWG/EWG session, and not to mention 12 years of -// experience with Boost and other libraries." -// -// -- Axel Naumann, in P0088 (wg21.link/p0088), -// the adopted proposal for C++17 std::variant -// -//----------------------------------------------------------------------- -// -// union -// -// a type that contains exactly one of a fixed set of values at a time -// - -auto cpp2_union(meta::type_declaration& t) -> void; - -#line 1331 "reflect.h2" -//----------------------------------------------------------------------- -// -// print - output a pretty-printed visualization of t -// -auto print(cpp2::in<meta::type_declaration> t) -> void; - -#line 1341 "reflect.h2" -//----------------------------------------------------------------------- -// -// apply_metafunctions -// -[[nodiscard]] auto apply_metafunctions( - declaration_node& n, - type_declaration& rtype, - auto const& error - ) -> bool; - -#line 1445 "reflect.h2" -} - -} - - -//=== Cpp2 function definitions ================================================= - -#line 1 "reflect.h2" - -#line 20 "reflect.h2" -namespace cpp2 { - -namespace meta { - -#line 46 "reflect.h2" - compiler_services::compiler_services( - - std::vector<error_entry>* errors_, - std::deque<token>* generated_tokens_ - ) - : errors{ errors_ } - , errors_original_size{ cpp2::unsafe_narrow<int>(std::ssize(*cpp2::assert_not_null(errors))) } - , generated_tokens{ generated_tokens_ } - , parser{ *cpp2::assert_not_null(errors) } -#line 51 "reflect.h2" - { - -#line 56 "reflect.h2" - } - -#line 60 "reflect.h2" - auto compiler_services::set_metafunction_name(cpp2::in<std::string_view> name, cpp2::in<std::vector<std::string>> args) & -> void{ - metafunction_name = name; - metafunction_args = args; - metafunctions_used = CPP2_UFCS(empty)(args); - } - - [[nodiscard]] auto compiler_services::get_metafunction_name() const& -> std::string_view { return metafunction_name; } - - [[nodiscard]] auto compiler_services::get_argument(cpp2::in<int> index) & -> std::string{ - metafunctions_used = true; - if (([_0 = 0, _1 = index, _2 = CPP2_UFCS(ssize)(metafunction_args)]{ return cpp2::cmp_less_eq(_0,_1) && cpp2::cmp_less(_1,_2); }())) { - return CPP2_ASSERT_IN_BOUNDS(metafunction_args, index); - } - return ""; - } - - [[nodiscard]] auto compiler_services::get_arguments() & -> std::vector<std::string>{ - metafunctions_used = true; - return metafunction_args; - } - - [[nodiscard]] auto compiler_services::arguments_were_used() const& -> bool { return metafunctions_used; } - - [[nodiscard]] auto compiler_services::parse_statement( - - std::string_view source - ) & -> parse_statement_ret - - { - cpp2::deferred_init<std::unique_ptr<statement_node>> ret; -#line 89 "reflect.h2" - auto original_source {source}; - - CPP2_UFCS(push_back)(generated_lines, std::vector<source_line>()); - auto lines {&CPP2_UFCS(back)(generated_lines)}; - - auto add_line {[&, _1 = lines](cpp2::in<std::string_view> s) mutable -> void{ - static_cast<void>(CPP2_UFCS(emplace_back)((*cpp2::assert_not_null(_1)), s, source_line::category::cpp2)); - }}; -{ -auto newline_pos = CPP2_UFCS(find)(source, '\n'); - - // First split this string into source_lines - // - -#line 101 "reflect.h2" - if ( cpp2::cmp_greater(CPP2_UFCS(ssize)(source),1) - && newline_pos != source.npos) - { - while( newline_pos != std::string_view::npos ) - { - add_line(CPP2_UFCS(substr)(source, 0, newline_pos)); - CPP2_UFCS(remove_prefix)(source, newline_pos + 1); - newline_pos = CPP2_UFCS(find)(source, '\n'); - } - } -} - -#line 112 "reflect.h2" - if (!(CPP2_UFCS(empty)(source))) { - std::move(add_line)(std::move(source)); - } - - // Now lex this source fragment to generate - // a single grammar_map entry, whose .second - // is the vector of tokens - static_cast<void>(CPP2_UFCS(emplace_back)(generated_lexers, *cpp2::assert_not_null(errors))); - auto tokens {&CPP2_UFCS(back)(generated_lexers)}; - CPP2_UFCS(lex)((*cpp2::assert_not_null(tokens)), *cpp2::assert_not_null(std::move(lines)), true); - - if (cpp2::Default.has_handler() && !(std::ssize(CPP2_UFCS(get_map)((*cpp2::assert_not_null(tokens)))) == 1) ) { cpp2::Default.report_violation(""); } - - // Now parse this single declaration from - // the lexed tokens - ret.construct(CPP2_UFCS(parse_one_declaration)(parser, - (*cpp2::assert_not_null(CPP2_UFCS(begin)(CPP2_UFCS(get_map)(*cpp2::assert_not_null(std::move(tokens)))))).second, - *cpp2::assert_not_null(generated_tokens) - )); - if (!(ret.value())) { - error("parse failed - the source string is not a valid statement:\n" + cpp2::to_string(std::move(original_source))); - }return std::move(ret.value()); - } - - [[nodiscard]] auto compiler_services::position() const -> source_position - - { - return { }; - } - -#line 145 "reflect.h2" - auto compiler_services::require( - - cpp2::in<bool> b, - cpp2::in<std::string_view> msg - ) const& -> void - { - if (!(b)) { - error(msg); - } - } - - auto compiler_services::error(cpp2::in<std::string_view> msg) const& -> void - { - auto message {cpp2::as_<std::string>(msg)}; - if (!(CPP2_UFCS(empty)(metafunction_name))) { - message = "while applying @" + cpp2::to_string(metafunction_name) + " - " + cpp2::to_string(message); - } - static_cast<void>(CPP2_UFCS(emplace_back)((*cpp2::assert_not_null(errors)), position(), std::move(message))); - } - -#line 168 "reflect.h2" - auto compiler_services::report_violation(auto const& msg) const& -> void{ - error(msg); - throw(std::runtime_error(" ==> programming bug found in metafunction @" + cpp2::to_string(metafunction_name) + " - contract violation - see previous errors")); - } - - [[nodiscard]] auto compiler_services::has_handler() const& -> auto { return true; } - - compiler_services::~compiler_services() noexcept{} -compiler_services::compiler_services(compiler_services const& that) - : errors{ that.errors } - , errors_original_size{ that.errors_original_size } - , generated_tokens{ that.generated_tokens } - , parser{ that.parser } - , metafunction_name{ that.metafunction_name } - , metafunction_args{ that.metafunction_args } - , metafunctions_used{ that.metafunctions_used }{} - -#line 229 "reflect.h2" - declaration_base::declaration_base( - - declaration_node* n_, - cpp2::in<compiler_services> s - ) - : compiler_services{ s } - , n{ n_ } -#line 234 "reflect.h2" - { - -#line 237 "reflect.h2" - if (cpp2::Default.has_handler() && !(n) ) { cpp2::Default.report_violation(CPP2_CONTRACT_MSG("a meta::declaration must point to a valid declaration_node, not null")); } - } - - [[nodiscard]] auto declaration_base::position() const -> source_position { return CPP2_UFCS(position)((*cpp2::assert_not_null(n))); } - - [[nodiscard]] auto declaration_base::print() const& -> std::string { return CPP2_UFCS(pretty_print_visualize)((*cpp2::assert_not_null(n)), 0); } - - declaration_base::~declaration_base() noexcept{} -declaration_base::declaration_base(declaration_base const& that) - : compiler_services{ static_cast<compiler_services const&>(that) } - , n{ that.n }{} - -#line 253 "reflect.h2" - declaration::declaration( - - declaration_node* n_, - cpp2::in<compiler_services> s - ) - : declaration_base{ n_, s } -#line 258 "reflect.h2" - { - - } - - [[nodiscard]] auto declaration::is_public() const& -> bool { return CPP2_UFCS(is_public)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::is_protected() const& -> bool { return CPP2_UFCS(is_protected)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::is_private() const& -> bool { return CPP2_UFCS(is_private)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::is_default_access() const& -> bool { return CPP2_UFCS(is_default_access)((*cpp2::assert_not_null(n))); } - - auto declaration::default_to_public() & -> void { static_cast<void>(CPP2_UFCS(make_public)((*cpp2::assert_not_null(n)))); } - auto declaration::default_to_protected() & -> void { static_cast<void>(CPP2_UFCS(make_protected)((*cpp2::assert_not_null(n)))); } - auto declaration::default_to_private() & -> void { static_cast<void>(CPP2_UFCS(make_private)((*cpp2::assert_not_null(n)))); } - - [[nodiscard]] auto declaration::make_public() & -> bool { return CPP2_UFCS(make_public)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::make_protected() & -> bool { return CPP2_UFCS(make_protected)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::make_private() & -> bool { return CPP2_UFCS(make_private)((*cpp2::assert_not_null(n))); } - - [[nodiscard]] auto declaration::has_name() const& -> bool { return CPP2_UFCS(has_name)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::has_name(cpp2::in<std::string_view> s) const& -> bool { return CPP2_UFCS(has_name)((*cpp2::assert_not_null(n)), s); } - - [[nodiscard]] auto declaration::name() const& -> std::string_view{ - if (has_name()) {return CPP2_UFCS(as_string_view)((*cpp2::assert_not_null(CPP2_UFCS(name)(*cpp2::assert_not_null(n))))); } - else { return ""; } - } - - [[nodiscard]] auto declaration::has_initializer() const& -> bool { return CPP2_UFCS(has_initializer)((*cpp2::assert_not_null(n))); } - - [[nodiscard]] auto declaration::is_global() const& -> bool { return CPP2_UFCS(is_global)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::is_function() const& -> bool { return CPP2_UFCS(is_function)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::is_object() const& -> bool { return CPP2_UFCS(is_object)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::is_base_object() const& -> bool { return CPP2_UFCS(is_base_object)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::is_member_object() const& -> bool { return CPP2_UFCS(is_member_object)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::is_type() const& -> bool { return CPP2_UFCS(is_type)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::is_namespace() const& -> bool { return CPP2_UFCS(is_namespace)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::is_alias() const& -> bool { return CPP2_UFCS(is_alias)((*cpp2::assert_not_null(n))); } - - [[nodiscard]] auto declaration::is_type_alias() const& -> bool { return CPP2_UFCS(is_type_alias)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::is_namespace_alias() const& -> bool { return CPP2_UFCS(is_namespace_alias)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::is_object_alias() const& -> bool { return CPP2_UFCS(is_object_alias)((*cpp2::assert_not_null(n))); } - - [[nodiscard]] auto declaration::is_function_expression() const& -> bool { return CPP2_UFCS(is_function_expression)((*cpp2::assert_not_null(n))); } - - [[nodiscard]] auto declaration::as_function() const& -> function_declaration { return function_declaration(n, (*this)); } - [[nodiscard]] auto declaration::as_object() const& -> object_declaration { return object_declaration(n, (*this)); } - [[nodiscard]] auto declaration::as_type() const& -> type_declaration { return type_declaration(n, (*this)); } - [[nodiscard]] auto declaration::as_alias() const& -> alias_declaration { return alias_declaration(n, (*this)); } - - [[nodiscard]] auto declaration::get_parent() const& -> declaration { return declaration((*cpp2::assert_not_null(n)).parent_declaration, (*this)); } - - [[nodiscard]] auto declaration::parent_is_function() const& -> bool { return CPP2_UFCS(parent_is_function)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::parent_is_object() const& -> bool { return CPP2_UFCS(parent_is_object)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::parent_is_type() const& -> bool { return CPP2_UFCS(parent_is_type)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::parent_is_namespace() const& -> bool { return CPP2_UFCS(parent_is_namespace)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::parent_is_alias() const& -> bool { return CPP2_UFCS(parent_is_alias)((*cpp2::assert_not_null(n))); } - - [[nodiscard]] auto declaration::parent_is_type_alias() const& -> bool { return CPP2_UFCS(parent_is_type_alias)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::parent_is_namespace_alias() const& -> bool { return CPP2_UFCS(parent_is_namespace_alias)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto declaration::parent_is_object_alias() const& -> bool { return CPP2_UFCS(parent_is_object_alias)((*cpp2::assert_not_null(n))); } - - [[nodiscard]] auto declaration::parent_is_polymorphic() const& -> bool { return CPP2_UFCS(parent_is_polymorphic)((*cpp2::assert_not_null(n))); } - - auto declaration::mark_for_removal_from_enclosing_type() & -> void - - { - if (cpp2::Type.has_handler() && !(parent_is_type()) ) { cpp2::Type.report_violation(""); } -#line 322 "reflect.h2" - auto test {CPP2_UFCS(type_member_mark_for_removal)((*cpp2::assert_not_null(n)))}; - if (cpp2::Default.has_handler() && !(std::move(test)) ) { cpp2::Default.report_violation(""); }// ... to ensure this assert is true - } - - declaration::~declaration() noexcept{} -declaration::declaration(declaration const& that) - : declaration_base{ static_cast<declaration_base const&>(that) }{} - -#line 335 "reflect.h2" - function_declaration::function_declaration( - - declaration_node* n_, - cpp2::in<compiler_services> s - ) - : declaration{ n_, s } -#line 340 "reflect.h2" - { - - if (cpp2::Default.has_handler() && !(CPP2_UFCS(is_function)((*cpp2::assert_not_null(n)))) ) { cpp2::Default.report_violation(""); } - } - - [[nodiscard]] auto function_declaration::index_of_parameter_named(cpp2::in<std::string_view> s) const& -> int { return CPP2_UFCS(index_of_parameter_named)((*cpp2::assert_not_null(n)), s); } - [[nodiscard]] auto function_declaration::has_parameter_named(cpp2::in<std::string_view> s) const& -> bool { return CPP2_UFCS(has_parameter_named)((*cpp2::assert_not_null(n)), s); } - [[nodiscard]] auto function_declaration::has_in_parameter_named(cpp2::in<std::string_view> s) const& -> bool { return CPP2_UFCS(has_in_parameter_named)((*cpp2::assert_not_null(n)), s); } - [[nodiscard]] auto function_declaration::has_out_parameter_named(cpp2::in<std::string_view> s) const& -> bool { return CPP2_UFCS(has_out_parameter_named)((*cpp2::assert_not_null(n)), s); } - [[nodiscard]] auto function_declaration::has_move_parameter_named(cpp2::in<std::string_view> s) const& -> bool { return CPP2_UFCS(has_move_parameter_named)((*cpp2::assert_not_null(n)), s); } - [[nodiscard]] auto function_declaration::first_parameter_name() const& -> std::string { return CPP2_UFCS(first_parameter_name)((*cpp2::assert_not_null(n))); } - - [[nodiscard]] auto function_declaration::has_parameter_with_name_and_pass(cpp2::in<std::string_view> s, cpp2::in<passing_style> pass) const& -> bool { - return CPP2_UFCS(has_parameter_with_name_and_pass)((*cpp2::assert_not_null(n)), s, pass); } - [[nodiscard]] auto function_declaration::is_function_with_this() const& -> bool { return CPP2_UFCS(is_function_with_this)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::is_virtual() const& -> bool { return CPP2_UFCS(is_virtual_function)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::is_defaultable() const& -> bool { return CPP2_UFCS(is_defaultable_function)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::is_constructor() const& -> bool { return CPP2_UFCS(is_constructor)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::is_default_constructor() const& -> bool { return CPP2_UFCS(is_default_constructor)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::is_move() const& -> bool { return CPP2_UFCS(is_move)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::is_swap() const& -> bool { return CPP2_UFCS(is_swap)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::is_constructor_with_that() const& -> bool { return CPP2_UFCS(is_constructor_with_that)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::is_constructor_with_in_that() const& -> bool { return CPP2_UFCS(is_constructor_with_in_that)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::is_constructor_with_move_that() const& -> bool { return CPP2_UFCS(is_constructor_with_move_that)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::is_assignment() const& -> bool { return CPP2_UFCS(is_assignment)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::is_assignment_with_that() const& -> bool { return CPP2_UFCS(is_assignment_with_that)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::is_assignment_with_in_that() const& -> bool { return CPP2_UFCS(is_assignment_with_in_that)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::is_assignment_with_move_that() const& -> bool { return CPP2_UFCS(is_assignment_with_move_that)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::is_destructor() const& -> bool { return CPP2_UFCS(is_destructor)((*cpp2::assert_not_null(n))); } - - [[nodiscard]] auto function_declaration::is_copy_or_move() const& -> bool { return is_constructor_with_that() || is_assignment_with_that(); } - - [[nodiscard]] auto function_declaration::has_declared_return_type() const& -> bool { return CPP2_UFCS(has_declared_return_type)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::has_deduced_return_type() const& -> bool { return CPP2_UFCS(has_deduced_return_type)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::has_bool_return_type() const& -> bool { return CPP2_UFCS(has_bool_return_type)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto function_declaration::has_non_void_return_type() const& -> bool { return CPP2_UFCS(has_non_void_return_type)((*cpp2::assert_not_null(n))); } - - [[nodiscard]] auto function_declaration::unnamed_return_type() const& -> std::string { return CPP2_UFCS(unnamed_return_type_to_string)((*cpp2::assert_not_null(n))); } - - [[nodiscard]] auto function_declaration::get_parameters() const& -> std::vector<object_declaration> - - { - std::vector<object_declaration> ret {}; - for ( auto const& param : CPP2_UFCS(get_function_parameters)((*cpp2::assert_not_null(n))) ) { - static_cast<void>(CPP2_UFCS(emplace_back)(ret, &*cpp2::assert_not_null((*cpp2::assert_not_null(param)).declaration), (*this))); - } - return ret; - } - - [[nodiscard]] auto function_declaration::is_binary_comparison_function() const& -> bool { return CPP2_UFCS(is_binary_comparison_function)((*cpp2::assert_not_null(n))); } - - auto function_declaration::default_to_virtual() & -> void { static_cast<void>(CPP2_UFCS(make_function_virtual)((*cpp2::assert_not_null(n)))); } - - [[nodiscard]] auto function_declaration::make_virtual() & -> bool { return CPP2_UFCS(make_function_virtual)((*cpp2::assert_not_null(n))); } - - auto function_declaration::add_initializer(cpp2::in<std::string_view> source) & -> void - -#line 398 "reflect.h2" - { - if ((*this).has_handler() && !(!(has_initializer())) ) { (*this).report_violation(CPP2_CONTRACT_MSG("cannot add an initializer to a function that already has one")); } - if ((*this).has_handler() && !(parent_is_type()) ) { (*this).report_violation(CPP2_CONTRACT_MSG("cannot add an initializer to a function that isn't in a type scope")); } - //require( !has_initializer(), - // "cannot add an initializer to a function that already has one"); - //require( parent_is_type(), - // "cannot add an initializer to a function that isn't in a type scope"); - -#line 404 "reflect.h2" - auto stmt {parse_statement(source)}; - if (!((cpp2::as_<bool>(stmt)))) { - error("cannot add an initializer that is not a valid statement"); - return ; - } - require(CPP2_UFCS(add_function_initializer)((*cpp2::assert_not_null(n)), std::move(stmt)), - std::string("unexpected error while attempting to add initializer")); - } - - function_declaration::function_declaration(function_declaration const& that) - : declaration{ static_cast<declaration const&>(that) }{} - -#line 422 "reflect.h2" - object_declaration::object_declaration( - - declaration_node* n_, - cpp2::in<compiler_services> s - ) - : declaration{ n_, s } -#line 427 "reflect.h2" - { - - if (cpp2::Default.has_handler() && !(CPP2_UFCS(is_object)((*cpp2::assert_not_null(n)))) ) { cpp2::Default.report_violation(""); } - } - - [[nodiscard]] auto object_declaration::is_const() const& -> bool { return CPP2_UFCS(is_const)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto object_declaration::has_wildcard_type() const& -> bool { return CPP2_UFCS(has_wildcard_type)((*cpp2::assert_not_null(n))); } - - [[nodiscard]] auto object_declaration::type() const& -> std::string{ - auto ret {CPP2_UFCS(object_type)((*cpp2::assert_not_null(n)))}; - require(!(contains(ret, "(*ERROR*)")), - "cannot to_string this type: " + ret); - return ret; - } - - [[nodiscard]] auto object_declaration::initializer() const& -> std::string{ - auto ret {CPP2_UFCS(object_initializer)((*cpp2::assert_not_null(n)))}; - require(!(contains(ret, "(*ERROR*)")), - "cannot to_string this initializer: " + ret); - return ret; - } - - object_declaration::object_declaration(object_declaration const& that) - : declaration{ static_cast<declaration const&>(that) }{} - -#line 458 "reflect.h2" - type_declaration::type_declaration( - - declaration_node* n_, - cpp2::in<compiler_services> s - ) - : declaration{ n_, s } -#line 463 "reflect.h2" - { - - if (cpp2::Default.has_handler() && !(CPP2_UFCS(is_type)((*cpp2::assert_not_null(n)))) ) { cpp2::Default.report_violation(""); } - } - - auto type_declaration::reserve_names(cpp2::in<std::string_view> name, auto&& ...etc) const& -> void - { // etc is not declared ':string_view' for compatibility with GCC 10.x - for ( - auto const& m : get_members() ) { - CPP2_UFCS(require)(m, !(CPP2_UFCS(has_name)(m, name)), - "in a '" + cpp2::to_string(get_metafunction_name()) + "' type, the name '" + cpp2::to_string(name) + "' is reserved for use by the '" + cpp2::to_string(get_metafunction_name()) + "' implementation"); - } - if constexpr (!(CPP2_PACK_EMPTY(etc))) { - reserve_names(CPP2_FORWARD(etc)...); - } - } - - [[nodiscard]] auto type_declaration::is_polymorphic() const& -> bool { return CPP2_UFCS(is_polymorphic)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto type_declaration::is_final() const& -> bool { return CPP2_UFCS(is_type_final)((*cpp2::assert_not_null(n))); } - [[nodiscard]] auto type_declaration::make_final() & -> bool { return CPP2_UFCS(make_type_final)((*cpp2::assert_not_null(n))); } - - [[nodiscard]] auto type_declaration::get_member_functions() const& -> std::vector<function_declaration> - - { - std::vector<function_declaration> ret {}; - for ( - auto const& d : CPP2_UFCS(get_type_scope_declarations)((*cpp2::assert_not_null(n)), declaration_node::functions) ) { - static_cast<void>(CPP2_UFCS(emplace_back)(ret, d, (*this))); - } - return ret; - } - - [[nodiscard]] auto type_declaration::get_member_functions_needing_initializer() const& -> std::vector<function_declaration> - - { - std::vector<function_declaration> ret {}; - for ( - auto const& d : CPP2_UFCS(get_type_scope_declarations)((*cpp2::assert_not_null(n)), declaration_node::functions) ) - if ( !(CPP2_UFCS(has_initializer)((*cpp2::assert_not_null(d)))) - && !(CPP2_UFCS(is_virtual_function)((*cpp2::assert_not_null(d)))) - && !(CPP2_UFCS(is_defaultable_function)((*cpp2::assert_not_null(d))))) - { - static_cast<void>(CPP2_UFCS(emplace_back)(ret, d, (*this))); - } - return ret; - } - - [[nodiscard]] auto type_declaration::get_member_objects() const& -> std::vector<object_declaration> - - { - std::vector<object_declaration> ret {}; - for ( auto const& d : CPP2_UFCS(get_type_scope_declarations)((*cpp2::assert_not_null(n)), declaration_node::objects) ) { - static_cast<void>(CPP2_UFCS(emplace_back)(ret, d, (*this))); - } - return ret; - } - - [[nodiscard]] auto type_declaration::get_member_types() const& -> std::vector<type_declaration> - - { - std::vector<type_declaration> ret {}; - for ( auto const& d : CPP2_UFCS(get_type_scope_declarations)((*cpp2::assert_not_null(n)), declaration_node::types) ) { - static_cast<void>(CPP2_UFCS(emplace_back)(ret, d, (*this))); - } - return ret; - } - - [[nodiscard]] auto type_declaration::get_member_aliases() const& -> std::vector<alias_declaration> - - { - std::vector<alias_declaration> ret {}; - for ( auto const& d : CPP2_UFCS(get_type_scope_declarations)((*cpp2::assert_not_null(n)), declaration_node::aliases) ) { - static_cast<void>(CPP2_UFCS(emplace_back)(ret, d, (*this))); - } - return ret; - } - - [[nodiscard]] auto type_declaration::get_members() const& -> std::vector<declaration> - - { - std::vector<declaration> ret {}; - for ( auto const& d : CPP2_UFCS(get_type_scope_declarations)((*cpp2::assert_not_null(n)), declaration_node::all) ) { - static_cast<void>(CPP2_UFCS(emplace_back)(ret, d, (*this))); - } - return ret; - } - - [[nodiscard]] auto type_declaration::query_declared_value_set_functions() const& -> query_declared_value_set_functions_ret - -#line 557 "reflect.h2" - { - cpp2::deferred_init<bool> out_this_in_that; - cpp2::deferred_init<bool> out_this_move_that; - cpp2::deferred_init<bool> inout_this_in_that; - cpp2::deferred_init<bool> inout_this_move_that; -#line 558 "reflect.h2" - auto declared {CPP2_UFCS(find_declared_value_set_functions)((*cpp2::assert_not_null(n)))}; - out_this_in_that.construct(declared.out_this_in_that != nullptr); - out_this_move_that.construct(declared.out_this_move_that != nullptr); - inout_this_in_that.construct(declared.inout_this_in_that != nullptr); - inout_this_move_that.construct(std::move(declared).inout_this_move_that != nullptr); - return { std::move(out_this_in_that.value()), std::move(out_this_move_that.value()), std::move(inout_this_in_that.value()), std::move(inout_this_move_that.value()) }; } - - auto type_declaration::add_member(cpp2::in<std::string_view> source) & -> void - { - auto decl {parse_statement(source)}; - if (!((cpp2::as_<bool>(decl)))) { - error("the provided source string is not a valid statement"); - return ; - } - if (!(CPP2_UFCS(is_declaration)((*cpp2::assert_not_null(decl))))) { - error("cannot add a member that is not a declaration"); - } - require(CPP2_UFCS(add_type_member)((*cpp2::assert_not_null(n)), std::move(decl)), - std::string("unexpected error while attempting to add member:\n") + source); - } - - auto type_declaration::remove_marked_members() & -> void { CPP2_UFCS(type_remove_marked_members)((*cpp2::assert_not_null(n))); } - auto type_declaration::remove_all_members() & -> void { CPP2_UFCS(type_remove_all_members)((*cpp2::assert_not_null(n))); } - - auto type_declaration::disable_member_function_generation() & -> void { CPP2_UFCS(type_disable_member_function_generation)((*cpp2::assert_not_null(n))); } - - type_declaration::type_declaration(type_declaration const& that) - : declaration{ static_cast<declaration const&>(that) }{} - -#line 593 "reflect.h2" - alias_declaration::alias_declaration( - - declaration_node* n_, - cpp2::in<compiler_services> s - ) - : declaration{ n_, s } -#line 598 "reflect.h2" - { - - if (cpp2::Default.has_handler() && !(CPP2_UFCS(is_alias)((*cpp2::assert_not_null(n)))) ) { cpp2::Default.report_violation(""); } - } - - alias_declaration::alias_declaration(alias_declaration const& that) - : declaration{ static_cast<declaration const&>(that) }{} - -#line 617 "reflect.h2" -auto add_virtual_destructor(meta::type_declaration& t) -> void -{ - CPP2_UFCS(add_member)(t, "operator=: (virtual move this) = { }"); -} - -#line 635 "reflect.h2" -auto interface(meta::type_declaration& t) -> void -{ - auto has_dtor {false}; - - for ( auto& m : CPP2_UFCS(get_members)(t) ) - { - CPP2_UFCS(require)(m, !(CPP2_UFCS(is_object)(m)), - "interfaces may not contain data objects"); - if (CPP2_UFCS(is_function)(m)) { - auto mf {CPP2_UFCS(as_function)(m)}; - CPP2_UFCS(require)(mf, !(CPP2_UFCS(is_copy_or_move)(mf)), - "interfaces may not copy or move; consider a virtual clone() instead"); - CPP2_UFCS(require)(mf, !(CPP2_UFCS(has_initializer)(mf)), - "interface functions must not have a function body; remove the '=' initializer"); - CPP2_UFCS(require)(mf, CPP2_UFCS(make_public)(mf), - "interface functions must be public"); - CPP2_UFCS(default_to_virtual)(mf); - has_dtor |= CPP2_UFCS(is_destructor)(mf); - } - } - - if (!(std::move(has_dtor))) { - CPP2_UFCS(add_virtual_destructor)(t); - } -} - -#line 681 "reflect.h2" -auto polymorphic_base(meta::type_declaration& t) -> void -{ - auto has_dtor {false}; - - for ( auto& mf : CPP2_UFCS(get_member_functions)(t) ) - { - if (CPP2_UFCS(is_default_access)(mf)) { - CPP2_UFCS(default_to_public)(mf); - } - CPP2_UFCS(require)(mf, !(CPP2_UFCS(is_copy_or_move)(mf)), - "polymorphic base types may not copy or move; consider a virtual clone() instead"); - if (CPP2_UFCS(is_destructor)(mf)) { - has_dtor = true; - CPP2_UFCS(require)(mf, ((CPP2_UFCS(is_public)(mf) || CPP2_UFCS(is_default_access)(mf)) && CPP2_UFCS(is_virtual)(mf)) - || (CPP2_UFCS(is_protected)(mf) && !(CPP2_UFCS(is_virtual)(mf))), - "a polymorphic base type destructor must be public and virtual, or protected and nonvirtual"); - } - } - - if (!(std::move(has_dtor))) { - CPP2_UFCS(add_virtual_destructor)(t); - } -} - -#line 726 "reflect.h2" -auto ordered_impl( - meta::type_declaration& t, - cpp2::in<std::string_view> ordering -) -> void -{ - auto has_spaceship {false}; - - for ( auto& mf : CPP2_UFCS(get_member_functions)(t) ) - { - if (CPP2_UFCS(has_name)(mf, "operator<=>")) { - has_spaceship = true; - auto return_name {CPP2_UFCS(unnamed_return_type)(mf)}; - if (CPP2_UFCS(find)(return_name, ordering) == return_name.npos) - { - CPP2_UFCS(error)(mf, "operator<=> must return std::" + cpp2::as_<std::string>(ordering)); - } - } - } - - if (!(std::move(has_spaceship))) { - CPP2_UFCS(add_member)(t, "operator<=>: (this, that) -> std::" + (cpp2::as_<std::string>(ordering)) + ";"); - } -} - -#line 755 "reflect.h2" -auto ordered(meta::type_declaration& t) -> void -{ - ordered_impl(t, "strong_ordering"); -} - -#line 763 "reflect.h2" -auto weakly_ordered(meta::type_declaration& t) -> void -{ - ordered_impl(t, "weak_ordering"); -} - -#line 771 "reflect.h2" -auto partially_ordered(meta::type_declaration& t) -> void -{ - ordered_impl(t, "partial_ordering"); -} - -#line 793 "reflect.h2" -auto copyable(meta::type_declaration& t) -> void -{ - // If the user explicitly wrote any of the copy/move functions, - // they must also have written the most general one - we can't - // assume we can safely generate it for them since they've opted - // into customized semantics - auto smfs {CPP2_UFCS(query_declared_value_set_functions)(t)}; - if ( !(smfs.out_this_in_that) - && ( - smfs.out_this_move_that - || smfs.inout_this_in_that - || smfs.inout_this_move_that)) - - { - CPP2_UFCS(error)(t, "this type is partially copyable/movable - when you provide any of the more-specific operator= signatures, you must also provide the one with the general signature (out this, that); alternatively, consider removing all the operator= functions and let them all be generated for you with default memberwise semantics"); - } - else {if (!(std::move(smfs).out_this_in_that)) { - CPP2_UFCS(add_member)(t, "operator=: (out this, that) = { }"); - }} -} - -#line 821 "reflect.h2" -auto basic_value(meta::type_declaration& t) -> void -{ - CPP2_UFCS(copyable)(t); - - auto has_default_ctor {false}; - for ( auto& mf : CPP2_UFCS(get_member_functions)(t) ) { - has_default_ctor |= CPP2_UFCS(is_default_constructor)(mf); - CPP2_UFCS(require)(mf, !(CPP2_UFCS(is_protected)(mf)) && !(CPP2_UFCS(is_virtual)(mf)), - "a value type may not have a protected or virtual function"); - CPP2_UFCS(require)(mf, !(CPP2_UFCS(is_destructor)(mf)) || CPP2_UFCS(is_public)(mf) || CPP2_UFCS(is_default_access)(mf), - "a value type may not have a non-public destructor"); - } - - if (!(std::move(has_default_ctor))) { - CPP2_UFCS(add_member)(t, "operator=: (out this) = { }"); - } -} - -#line 849 "reflect.h2" -auto value(meta::type_declaration& t) -> void -{ - CPP2_UFCS(ordered)(t); - CPP2_UFCS(basic_value)(t); -} - -auto weakly_ordered_value(meta::type_declaration& t) -> void -{ - CPP2_UFCS(weakly_ordered)(t); - CPP2_UFCS(basic_value)(t); -} - -auto partially_ordered_value(meta::type_declaration& t) -> void -{ - CPP2_UFCS(partially_ordered)(t); - CPP2_UFCS(basic_value)(t); -} - -#line 893 "reflect.h2" -auto cpp2_struct(meta::type_declaration& t) -> void -{ - for ( auto& m : CPP2_UFCS(get_members)(t) ) - { - CPP2_UFCS(require)(m, CPP2_UFCS(make_public)(m), - "all struct members must be public"); - if (CPP2_UFCS(is_function)(m)) { - auto mf {CPP2_UFCS(as_function)(m)}; - CPP2_UFCS(require)(t, !(CPP2_UFCS(is_virtual)(mf)), - "a struct may not have a virtual function"); - CPP2_UFCS(require)(t, !(CPP2_UFCS(has_name)(mf, "operator=")), - "a struct may not have a user-defined operator="); - } - } - CPP2_UFCS(disable_member_function_generation)(t); -} - -#line 934 "reflect.h2" -auto basic_enum( - meta::type_declaration& t, - auto const& nextval, - cpp2::in<bool> bitwise - ) -> void -{ - std::vector<value_member_info> enumerators {}; - cpp2::i64 min_value {}; - cpp2::i64 max_value {}; - cpp2::deferred_init<std::string> underlying_type; - - CPP2_UFCS(reserve_names)(t, "operator=", "operator<=>"); - if (bitwise) { - CPP2_UFCS(reserve_names)(t, "has", "set", "clear", "to_string", "get_raw_value", "none"); - } - - // 1. Gather: The names of all the user-written members, and find/compute the type - - underlying_type.construct(CPP2_UFCS(get_argument)(t, 0));// use the first template argument, if there was one - - auto found_non_numeric {false}; -{ -std::string value = "-1"; - -#line 957 "reflect.h2" - for ( - auto const& m : CPP2_UFCS(get_members)(t) ) - if ( CPP2_UFCS(is_member_object)(m)) - { - CPP2_UFCS(require)(m, CPP2_UFCS(is_public)(m) || CPP2_UFCS(is_default_access)(m), - "an enumerator cannot be protected or private"); - - auto mo {CPP2_UFCS(as_object)(m)}; - if (!(CPP2_UFCS(has_wildcard_type)(mo))) { - CPP2_UFCS(error)(mo, "an explicit underlying type should be specified as a template argument to the metafunction - try 'enum<u16>' or 'flag_enum<u64>'"); - } - - auto init {CPP2_UFCS(initializer)(mo)}; - - auto is_default_or_numeric {is_empty_or_a_decimal_number(init)}; - found_non_numeric |= !(CPP2_UFCS(empty)(init)) && !(is_default_or_numeric); - CPP2_UFCS(require)(m, !(is_default_or_numeric) || !(found_non_numeric) || CPP2_UFCS(has_name)(mo, "none"), - cpp2::to_string(CPP2_UFCS(name)(mo)) + ": enumerators with non-numeric values must come after all default and numeric values"); - - nextval(value, init); - - auto v {std::strtoll(&CPP2_ASSERT_IN_BOUNDS(value, 0), nullptr, 10)}; // for non-numeric values we'll just get 0 which is okay for now - if (cpp2::cmp_less(v,min_value)) { - min_value = v; - } - if (cpp2::cmp_greater(v,max_value)) { - max_value = v; - } - - // Adding local variable 'e' to work around a Clang warning - value_member_info e {cpp2::as_<std::string>(CPP2_UFCS(name)(mo)), "", value}; - CPP2_UFCS(push_back)(enumerators, e); - - CPP2_UFCS(mark_for_removal_from_enclosing_type)(mo); - } -} - -#line 993 "reflect.h2" - if ((CPP2_UFCS(empty)(enumerators))) { - CPP2_UFCS(error)(t, "an enumeration must contain at least one enumerator value"); - return ; - } - - // Compute the default underlying type, if it wasn't explicitly specified - if (underlying_type.value() == "") - { - CPP2_UFCS(require)(t, !(std::move(found_non_numeric)), - "if you write an enumerator with a non-numeric-literal value, you must specify the enumeration's underlying type"); - - if (!(bitwise)) { - if (cpp2::cmp_greater_eq(min_value,std::numeric_limits<cpp2::i8>::min()) && cpp2::cmp_less_eq(max_value,std::numeric_limits<cpp2::i8>::max())) { - underlying_type.value() = "i8"; - } - else {if (cpp2::cmp_greater_eq(min_value,std::numeric_limits<cpp2::i16>::min()) && cpp2::cmp_less_eq(max_value,std::numeric_limits<cpp2::i16>::max())) { - underlying_type.value() = "i16"; - } - else {if (cpp2::cmp_greater_eq(min_value,std::numeric_limits<cpp2::i32>::min()) && cpp2::cmp_less_eq(max_value,std::numeric_limits<cpp2::i32>::max())) { - underlying_type.value() = "i32"; - } - else {if (cpp2::cmp_greater_eq(std::move(min_value),std::numeric_limits<cpp2::i64>::min()) && cpp2::cmp_less_eq(max_value,std::numeric_limits<cpp2::i64>::max())) { - underlying_type.value() = "i64"; - } - else { - CPP2_UFCS(error)(t, "values are outside the range representable by the largest supported underlying signed type (i64)"); - }}}} - } - else { - auto umax {std::move(max_value) * cpp2::as_<cpp2::u64, 2>()}; - if (cpp2::cmp_less_eq(umax,std::numeric_limits<cpp2::u8>::max())) { - underlying_type.value() = "u8"; - } - else {if (cpp2::cmp_less_eq(umax,std::numeric_limits<cpp2::u16>::max())) { - underlying_type.value() = "u16"; - } - else {if (cpp2::cmp_less_eq(std::move(umax),std::numeric_limits<cpp2::u32>::max())) { - underlying_type.value() = "u32"; - } - else { - underlying_type.value() = "u64"; - }}} - } - } - -#line 1039 "reflect.h2" - // 2. Replace: Erase the contents and replace with modified contents - // - // Note that most values and functions are declared as '==' compile-time values, i.e. Cpp1 'constexpr' - - CPP2_UFCS(remove_marked_members)(t); - - // Generate the 'none' value if appropriate, and use that or - // else the first enumerator as the default-constructed value - auto default_value {CPP2_ASSERT_IN_BOUNDS(enumerators, 0).name}; - if (bitwise) { - default_value = "none"; - value_member_info e {"none", "", "0"}; - CPP2_UFCS(push_back)(enumerators, std::move(e)); - } - - // Generate all the private implementation - CPP2_UFCS(add_member)(t, " _value : " + cpp2::to_string(underlying_type.value()) + ";"); - CPP2_UFCS(add_member)(t, " private operator= : (implicit out this, _val: i64) == _value = cpp2::unsafe_narrow<" + cpp2::to_string(underlying_type.value()) + ">(_val);"); - - // Generate the bitwise operations - if (bitwise) { - CPP2_UFCS(add_member)(t, " operator|=: ( inout this, that ) == _value |= that._value;"); - CPP2_UFCS(add_member)(t, " operator&=: ( inout this, that ) == _value &= that._value;"); - CPP2_UFCS(add_member)(t, " operator^=: ( inout this, that ) == _value ^= that._value;"); - CPP2_UFCS(add_member)(t, " operator| : ( this, that ) -> " + cpp2::to_string(CPP2_UFCS(name)(t)) + " == _value | that._value;"); - CPP2_UFCS(add_member)(t, " operator& : ( this, that ) -> " + cpp2::to_string(CPP2_UFCS(name)(t)) + " == _value & that._value;"); - CPP2_UFCS(add_member)(t, " operator^ : ( this, that ) -> " + cpp2::to_string(CPP2_UFCS(name)(t)) + " == _value ^ that._value;"); - CPP2_UFCS(add_member)(t, " has : ( inout this, that ) -> bool == _value & that._value;"); - CPP2_UFCS(add_member)(t, " set : ( inout this, that ) == _value |= that._value;"); - CPP2_UFCS(add_member)(t, " clear : ( inout this, that ) == _value &= that._value~;"); - } - - // Add the enumerators - for ( auto const& e : enumerators ) { - CPP2_UFCS(add_member)(t, " " + cpp2::to_string(e.name) + " : " + cpp2::to_string(CPP2_UFCS(name)(t)) + " == " + cpp2::to_string(e.value) + ";"); - } - - // Generate the common functions - CPP2_UFCS(add_member)(t, " get_raw_value : (this) -> " + cpp2::to_string(std::move(underlying_type.value())) + " == _value;"); - CPP2_UFCS(add_member)(t, " operator= : (out this) == { _value = " + cpp2::to_string(std::move(default_value)) + "._value; }"); - CPP2_UFCS(add_member)(t, " operator= : (out this, that) == { }"); - CPP2_UFCS(add_member)(t, " operator<=> : (this, that) -> std::strong_ordering;"); -{ -std::string to_string = " to_string: (this) -> std::string = { \n"; - - // Provide a 'to_string' function to print enumerator name(s) - -#line 1084 "reflect.h2" - { - if (bitwise) { - to_string += " _ret : std::string = \"(\";\n"; - to_string += " _comma : std::string = ();\n"; - to_string += " if this == none { return \"(none)\"; }\n"; - } - - for ( - auto const& e : enumerators ) { - if (e.name != "_") {// ignore unnamed values - if (bitwise) { - if (e.name != "none") { - to_string += " if (this & " + cpp2::to_string(e.name) + ") == " + cpp2::to_string(e.name) + " { _ret += _comma + \"" + cpp2::to_string(e.name) + "\"; _comma = \", \"; }\n"; - } - } - else { - to_string += " if this == " + cpp2::to_string(e.name) + " { return \"" + cpp2::to_string(e.name) + "\"; }\n"; - } - } - } - - if (bitwise) { - to_string += " return _ret+\")\";\n}\n"; - } - else { - to_string += " return \"invalid " + cpp2::to_string(CPP2_UFCS(name)(t)) + " value\";\n}\n"; - } - - CPP2_UFCS(add_member)(t, std::move(to_string)); - } -} -#line 1114 "reflect.h2" -} - -#line 1126 "reflect.h2" -auto cpp2_enum(meta::type_declaration& t) -> void -{ - // Let basic_enum do its thing, with an incrementing value generator - CPP2_UFCS(basic_enum)(t, - [](std::string& value, cpp2::in<std::string> specified_value) mutable -> void{ - if (!(CPP2_UFCS(empty)(specified_value))) { - value = specified_value; - }else { - auto v {std::strtoll(&CPP2_ASSERT_IN_BOUNDS(value, 0), nullptr, 10)}; - value = cpp2::as_<std::string>((std::move(v) + 1)); - } - }, - false // disable bitwise operations - ); -} - -#line 1153 "reflect.h2" -auto flag_enum(meta::type_declaration& t) -> void -{ - // Let basic_enum do its thing, with a power-of-two value generator - CPP2_UFCS(basic_enum)(t, - [](std::string& value, cpp2::in<std::string> specified_value) mutable -> void{ - if (!(CPP2_UFCS(empty)(specified_value))) { - value = specified_value; - }else { - auto v {std::strtoll(&CPP2_ASSERT_IN_BOUNDS(value, 0), nullptr, 10)}; - if (cpp2::cmp_less(v,1)) { - value = "1"; - } - else { - value = cpp2::as_<std::string>((std::move(v) * 2)); - } - } - }, - true // enable bitwise operations - ); -} - -#line 1199 "reflect.h2" -auto cpp2_union(meta::type_declaration& t) -> void -{ - std::vector<value_member_info> alternatives {}; -{ -auto value = 0; - - // 1. Gather: All the user-written members, and find/compute the max size - -#line 1206 "reflect.h2" - for ( - - auto const& m : CPP2_UFCS(get_members)(t) ) { do - if ( CPP2_UFCS(is_member_object)(m)) - { - CPP2_UFCS(require)(m, CPP2_UFCS(is_public)(m) || CPP2_UFCS(is_default_access)(m), - "a union alternative cannot be protected or private"); - - CPP2_UFCS(require)(m, !(CPP2_UFCS(starts_with)(CPP2_UFCS(name)(m), "is_")) - && !(CPP2_UFCS(starts_with)(CPP2_UFCS(name)(m), "set_")), - "a union alternative's name cannot start with 'is_' or 'set_' - that could cause user confusion with the 'is_alternative' and 'set_alternative' generated functions"); - - auto mo {CPP2_UFCS(as_object)(m)}; - CPP2_UFCS(require)(mo, CPP2_UFCS(empty)(CPP2_UFCS(initializer)(mo)), - "a union alternative cannot have an initializer"); - - // Adding local variable 'e' to work around a Clang warning - value_member_info e {cpp2::as_<std::string>(CPP2_UFCS(name)(mo)), CPP2_UFCS(type)(mo), cpp2::as_<std::string>(value)}; - CPP2_UFCS(push_back)(alternatives, e); - - CPP2_UFCS(mark_for_removal_from_enclosing_type)(mo); - } while (false); ++value; } -} - -#line 1229 "reflect.h2" - std::string discriminator_type {}; - if (cpp2::cmp_less(CPP2_UFCS(ssize)(alternatives),std::numeric_limits<cpp2::i8>::max())) { - discriminator_type = "i8"; - } - else {if (cpp2::cmp_less(CPP2_UFCS(ssize)(alternatives),std::numeric_limits<cpp2::i16>::max())) { - discriminator_type = "i16"; - } - else {if (cpp2::cmp_less(CPP2_UFCS(ssize)(alternatives),std::numeric_limits<cpp2::i32>::max())) { - discriminator_type = "i32"; - } - else { - discriminator_type = "i64"; - }}} - -#line 1244 "reflect.h2" - // 2. Replace: Erase the contents and replace with modified contents - - CPP2_UFCS(remove_marked_members)(t); -{ -std::string storage = " _storage: cpp2::aligned_storage<cpp2::max( "; - - // Provide storage - -#line 1250 "reflect.h2" - { -{ -std::string comma = ""; - -#line 1252 "reflect.h2" - for ( - - auto const& e : alternatives ) { do { - storage += comma + "sizeof(" + cpp2::to_string(e.type) + ")"; - } while (false); comma = ", "; } -} - -#line 1258 "reflect.h2" - storage += "), cpp2::max( "; -{ -std::string comma = ""; - -#line 1261 "reflect.h2" - for ( - - auto const& e : alternatives ) { do { - storage += comma + "alignof(" + cpp2::to_string(e.type) + ")"; - } while (false); comma = ", "; } -} - -#line 1267 "reflect.h2" - storage += " )> = ();\n"; - CPP2_UFCS(add_member)(t, std::move(storage)); - } -} - - // Provide discriminator -#line 1272 "reflect.h2" - CPP2_UFCS(add_member)(t, " _discriminator: " + cpp2::to_string(std::move(discriminator_type)) + " = -1;\n"); - - // Add the alternatives: is_alternative, get_alternative, and set_alternative - for ( - auto const& a : alternatives ) - { - CPP2_UFCS(add_member)(t, " is_" + cpp2::to_string(a.name) + ": (this) -> bool = _discriminator == " + cpp2::to_string(a.value) + ";\n"); - - CPP2_UFCS(add_member)(t, " " + cpp2::to_string(a.name) + ": (this) -> forward " + cpp2::to_string(a.type) + " pre(is_" + cpp2::to_string(a.name) + "()) = reinterpret_cast<* const " + cpp2::to_string(a.type) + ">(_storage&)*;\n"); - - CPP2_UFCS(add_member)(t, " " + cpp2::to_string(a.name) + ": (inout this) -> forward " + cpp2::to_string(a.type) + " pre(is_" + cpp2::to_string(a.name) + "()) = reinterpret_cast<*" + cpp2::to_string(a.type) + ">(_storage&)*;\n"); - - CPP2_UFCS(add_member)(t, " set_" + cpp2::to_string(a.name) + ": (inout this, _value: " + cpp2::to_string(a.type) + ") = { if !is_" + cpp2::to_string(a.name) + "() { _destroy(); std::construct_at( reinterpret_cast<*" + cpp2::to_string(a.type) + ">(_storage&), _value); } else { reinterpret_cast<*" + cpp2::to_string(a.type) + ">(_storage&)* = _value; } _discriminator = " + cpp2::to_string(a.value) + "; }\n"); - - CPP2_UFCS(add_member)(t, " set_" + cpp2::to_string(a.name) + ": (inout this, forward _args...: _) = { if !is_" + cpp2::to_string(a.name) + "() { _destroy(); std::construct_at( reinterpret_cast<*" + cpp2::to_string(a.type) + ">(_storage&), _args...); } else { reinterpret_cast<*" + cpp2::to_string(a.type) + ">(_storage&)* = :" + cpp2::to_string(a.type) + " = (_args...); } _discriminator = " + cpp2::to_string(a.value) + "; }\n"); - } -{ -std::string destroy = " private _destroy: (inout this) = {\n"; - - // Add destroy - -#line 1291 "reflect.h2" - { - for ( - auto const& a : alternatives ) { - destroy += " if _discriminator == " + cpp2::to_string(a.value) + " { std::destroy_at( reinterpret_cast<*" + cpp2::to_string(a.type) + ">(_storage&) ); }\n"; - } - - destroy += " _discriminator = -1;\n"; - destroy += " }\n"; - CPP2_UFCS(add_member)(t, std::move(destroy)); - } -} - - // Add the destructor -#line 1303 "reflect.h2" - CPP2_UFCS(add_member)(t, " operator=: (move this) = { _destroy(); }"); - - // Add default constructor - CPP2_UFCS(add_member)(t, " operator=: (out this) = { }"); -{ -std::string value_set = ""; - - // Add copy/move construction and assignment - -#line 1310 "reflect.h2" - { - for ( - auto const& a : alternatives ) { - value_set += " if that.is_" + cpp2::to_string(a.name) + "() { set_" + cpp2::to_string(a.name) + "( that." + cpp2::to_string(a.name) + "() ); }\n"; - } - value_set += " }\n"; - - CPP2_UFCS(add_member)(t, std::string(" operator=: (out this, that) = {\n") - + " _storage = ();\n" - + " _discriminator = -1;\n" - + value_set - ); - CPP2_UFCS(add_member)(t, std::string(" operator=: (inout this, that) = {\n") - + " _storage = _;\n" - + " _discriminator = _;\n" - + std::move(value_set) - ); - } -} -#line 1328 "reflect.h2" -} - -#line 1335 "reflect.h2" -auto print(cpp2::in<meta::type_declaration> t) -> void -{ - std::cout << CPP2_UFCS(print)(t) << "\n"; -} - -#line 1345 "reflect.h2" -[[nodiscard]] auto apply_metafunctions( - declaration_node& n, - type_declaration& rtype, - auto const& error - ) -> bool - -{ - if (cpp2::Default.has_handler() && !(CPP2_UFCS(is_type)(n)) ) { cpp2::Default.report_violation(""); } - - // Check for _names reserved for the metafunction implementation - for ( - auto const& m : CPP2_UFCS(get_members)(rtype) ) - { - CPP2_UFCS(require)(m, !(CPP2_UFCS(starts_with)(CPP2_UFCS(name)(m), "_")) || cpp2::cmp_greater(CPP2_UFCS(ssize)(CPP2_UFCS(name)(m)),1), - "a type that applies a metafunction cannot have a body that declares a name that starts with '_' - those names are reserved for the metafunction implementation"); - } - - // For each metafunction, apply it - for ( - auto const& meta : n.metafunctions ) - { - // Convert the name and any template arguments to strings - // and record that in rtype - auto name {CPP2_UFCS(to_string)((*cpp2::assert_not_null(meta)))}; - name = CPP2_UFCS(substr)(name, 0, CPP2_UFCS(find)(name, '<')); - - std::vector<std::string> args {}; - for ( - auto const& arg : CPP2_UFCS(template_arguments)((*cpp2::assert_not_null(meta))) ) - CPP2_UFCS(push_back)(args, CPP2_UFCS(to_string)(arg)); - - CPP2_UFCS(set_metafunction_name)(rtype, name, args); - - // Dispatch - // - if (name == "interface") { - interface(rtype); - } - else {if (name == "polymorphic_base") { - polymorphic_base(rtype); - } - else {if (name == "ordered") { - ordered(rtype); - } - else {if (name == "weakly_ordered") { - weakly_ordered(rtype); - } - else {if (name == "partially_ordered") { - partially_ordered(rtype); - } - else {if (name == "copyable") { - copyable(rtype); - } - else {if (name == "basic_value") { - basic_value(rtype); - } - else {if (name == "value") { - value(rtype); - } - else {if (name == "weakly_ordered_value") { - weakly_ordered_value(rtype); - } - else {if (name == "partially_ordered_value") { - partially_ordered_value(rtype); - } - else {if (name == "struct") { - cpp2_struct(rtype); - } - else {if (name == "enum") { - cpp2_enum(rtype); - } - else {if (name == "flag_enum") { - flag_enum(rtype); - } - else {if (name == "union") { - cpp2_union(rtype); - } - else {if (name == "print") { - print(rtype); - } - else { - error("unrecognized metafunction name: " + name); - error("(temporary alpha limitation) currently the supported names are: interface, polymorphic_base, ordered, weakly_ordered, partially_ordered, copyable, basic_value, value, weakly_ordered_value, partially_ordered_value, struct, enum, flag_enum, union, print"); - return false; - }}}}}}}}}}}}}}} - - if (( - !(CPP2_UFCS(empty)(args)) - && !(CPP2_UFCS(arguments_were_used)(rtype)))) - - { - error(name + " did not use its template arguments - did you mean to write '" + name + " <" + CPP2_ASSERT_IN_BOUNDS(args, 0) + "> type' (with the spaces)?"); - return false; - } - } - - return true; -} - -#line 1445 "reflect.h2" -} - -} - -#endif diff --git a/64x0/cc2/source/reflect.h2 b/64x0/cc2/source/reflect.h2 deleted file mode 100644 index 072087e..0000000 --- a/64x0/cc2/source/reflect.h2 +++ /dev/null @@ -1,1447 +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. - - -//=========================================================================== -// Reflection and meta -//=========================================================================== - -#include "parse.h" - -cpp2: namespace = { - -meta: namespace = { - - -//----------------------------------------------------------------------- -// -// Compiler services -// -//----------------------------------------------------------------------- -// - -compiler_services: @polymorphic_base @copyable type = -{ - // Common data members - // - errors : *std::vector<error_entry>; - errors_original_size : int; - generated_tokens : *std::deque<token>; - parser : cpp2::parser; - metafunction_name : std::string = (); - metafunction_args : std::vector<std::string> = (); - metafunctions_used : bool = false; - - // Constructor - // - operator=: ( - out this, - errors_ : *std::vector<error_entry>, - generated_tokens_: *std::deque<token> - ) - = { - errors = errors_; - errors_original_size = cpp2::unsafe_narrow<int>(std::ssize(errors*)); - generated_tokens = generated_tokens_; - parser = errors*; - } - - // Common API - // - set_metafunction_name: (inout this, name: std::string_view, args: std::vector<std::string>) = { - metafunction_name = name; - metafunction_args = args; - metafunctions_used = args.empty(); - } - - get_metafunction_name: (this) -> std::string_view = metafunction_name; - - get_argument: (inout this, index: int) -> std::string = { - metafunctions_used = true; - if (0 <= index < metafunction_args.ssize()) { - return metafunction_args[index]; - } - return ""; - } - - get_arguments: (inout this) -> std::vector<std::string> = { - metafunctions_used = true; - return metafunction_args; - } - - arguments_were_used: (this) -> bool = metafunctions_used; - - protected parse_statement: ( - inout this, - copy source: std::string_view - ) - -> (ret: std::unique_ptr<statement_node>) - = { - original_source := source; - - generated_lines.push_back( std::vector<source_line>() ); - lines := generated_lines.back()&; - - add_line := :(s: std::string_view) = { - _ = lines$*.emplace_back( s, source_line::category::cpp2 ); - }; - - // First split this string into source_lines - // - (copy newline_pos := source.find('\n')) - if source.ssize() > 1 - && newline_pos != source.npos - { - while newline_pos != std::string_view::npos - { - add_line( source.substr(0, newline_pos) ); - source.remove_prefix( newline_pos+1 ); - newline_pos = source.find('\n'); - } - } - - if !source.empty() { - add_line( source ); - } - - // Now lex this source fragment to generate - // a single grammar_map entry, whose .second - // is the vector of tokens - _ = generated_lexers.emplace_back( errors* ); - tokens := generated_lexers.back()&; - tokens*.lex( lines*, true ); - - assert( std::ssize(tokens* .get_map()) == 1 ); - - // Now parse this single declaration from - // the lexed tokens - ret = parser.parse_one_declaration( - tokens*.get_map().begin()*.second, - generated_tokens* - ); - if !ret { - error( "parse failed - the source string is not a valid statement:\n(original_source)$"); - } - } - - position: (virtual this) - -> source_position - = { - return (); - } - - // Error diagnosis and handling, integrated with compiler output - // Unlike a contract violation, .requires continues further processing - // - require:( - this, - b : bool, - msg : std::string_view - ) - = { - if !b { - error( msg ); - } - } - - error: (this, msg: std::string_view) - = { - message := msg as std::string; - if !metafunction_name.empty() { - message = "while applying @(metafunction_name)$ - (message)$"; - } - _ = errors*.emplace_back( position(), message); - } - - // Enable custom contracts on this object, integrated with compiler output - // Unlike .requires, a contract violation stops further processing - // - report_violation: (this, msg) = { - error(msg); - throw( std::runtime_error(" ==> programming bug found in metafunction @(metafunction_name)$ - contract violation - see previous errors") ); - } - - has_handler:(this) true; -} - - -/* -//----------------------------------------------------------------------- -// -// Type IDs -// -//----------------------------------------------------------------------- -// - -// All type_ids are wrappers around a pointer to node -// -type_id: @polymorphic_base @copyable type = -{ - this: compiler_services = (); - - n: *type_id_node; - - protected operator=: ( - out this, - n_: *type_id_node, - s : compiler_services - ) - = { - compiler_services = s; - n = n_; - assert( n, "a meta::type_id must point to a valid type_id_node, not null" ); - } - - is_wildcard : (this) -> bool = n*.is_wildcard(); - is_pointer_qualified: (this) -> bool = n*.is_pointer_qualified(); - template_args_count : (this) -> int = n*.template_arguments().ssize(); - to_string : (this) -> std::string = n*.to_string(); - - position: (override this) -> source_position = n*.position(); -} -*/ - - -//----------------------------------------------------------------------- -// -// Declarations -// -//----------------------------------------------------------------------- -// - -// All declarations are wrappers around a pointer to node -// -declaration_base: @polymorphic_base @copyable type = -{ - this: compiler_services = (); - - protected n: *declaration_node; - - protected operator=: ( - out this, - n_: *declaration_node, - s : compiler_services - ) - = { - compiler_services = s; - n = n_; - assert( n, "a meta::declaration must point to a valid declaration_node, not null" ); - } - - position: (override this) -> source_position = n*.position(); - - print: (this) -> std::string = n*.pretty_print_visualize(0); -} - - -//----------------------------------------------------------------------- -// All declarations -// -declaration: @polymorphic_base @copyable type = -{ - this: declaration_base = (); - - operator=: ( - out this, - n_: *declaration_node, - s : compiler_services - ) - = { - declaration_base = (n_, s); - } - - is_public : (this) -> bool = n*.is_public(); - is_protected : (this) -> bool = n*.is_protected(); - is_private : (this) -> bool = n*.is_private(); - is_default_access: (this) -> bool = n*.is_default_access(); - - default_to_public : (inout this) = _ = n*.make_public(); - default_to_protected: (inout this) = _ = n*.make_protected(); - default_to_private : (inout this) = _ = n*.make_private(); - - make_public : (inout this) -> bool = n*.make_public(); - make_protected : (inout this) -> bool = n*.make_protected(); - make_private : (inout this) -> bool = n*.make_private(); - - has_name : (this) -> bool = n*.has_name(); - has_name : (this, s: std::string_view) -> bool = n*.has_name(s); - - name: (this) -> std::string_view = { - if has_name() { return n*.name()*.as_string_view(); } - else { return ""; } - } - - has_initializer: (this) -> bool = n*.has_initializer(); - - is_global : (this) -> bool = n*.is_global(); - is_function : (this) -> bool = n*.is_function(); - is_object : (this) -> bool = n*.is_object(); - is_base_object : (this) -> bool = n*.is_base_object(); - is_member_object : (this) -> bool = n*.is_member_object(); - is_type : (this) -> bool = n*.is_type(); - is_namespace : (this) -> bool = n*.is_namespace(); - is_alias : (this) -> bool = n*.is_alias(); - - is_type_alias : (this) -> bool = n*.is_type_alias(); - is_namespace_alias : (this) -> bool = n*.is_namespace_alias(); - is_object_alias : (this) -> bool = n*.is_object_alias(); - - is_function_expression : (this) -> bool = n*.is_function_expression(); - - as_function : (this) -> function_declaration = function_declaration(n, this); - as_object : (this) -> object_declaration = object_declaration(n, this); - as_type : (this) -> type_declaration = type_declaration(n, this); - as_alias : (this) -> alias_declaration = alias_declaration(n, this); - - get_parent : (this) -> declaration = declaration(n*.parent_declaration, this); - - parent_is_function : (this) -> bool = n*.parent_is_function(); - parent_is_object : (this) -> bool = n*.parent_is_object(); - parent_is_type : (this) -> bool = n*.parent_is_type(); - parent_is_namespace : (this) -> bool = n*.parent_is_namespace(); - parent_is_alias : (this) -> bool = n*.parent_is_alias(); - - parent_is_type_alias : (this) -> bool = n*.parent_is_type_alias(); - parent_is_namespace_alias : (this) -> bool = n*.parent_is_namespace_alias(); - parent_is_object_alias : (this) -> bool = n*.parent_is_object_alias(); - - parent_is_polymorphic: (this) -> bool = n*.parent_is_polymorphic(); - - mark_for_removal_from_enclosing_type: (inout this) - pre<Type>( parent_is_type() ) // this precondition should be sufficient ... - = { - test := n*.type_member_mark_for_removal(); - assert( test ); // ... to ensure this assert is true - } -} - - -//----------------------------------------------------------------------- -// Function declarations -// -function_declaration: @copyable type = -{ - this: declaration = (); - - operator=: ( - out this, - n_: *declaration_node, - s : compiler_services - ) = - { - declaration = (n_, s); - assert( n*.is_function() ); - } - - index_of_parameter_named : (this, s: std::string_view) -> int = n*.index_of_parameter_named(s); - has_parameter_named : (this, s: std::string_view) -> bool = n*.has_parameter_named(s); - has_in_parameter_named : (this, s: std::string_view) -> bool = n*.has_in_parameter_named(s); - has_out_parameter_named : (this, s: std::string_view) -> bool = n*.has_out_parameter_named(s); - has_move_parameter_named : (this, s: std::string_view) -> bool = n*.has_move_parameter_named(s); - first_parameter_name : (this) -> std::string = n*.first_parameter_name(); - - has_parameter_with_name_and_pass: (this, s: std::string_view, pass: passing_style) -> bool - = n*.has_parameter_with_name_and_pass(s, pass); - is_function_with_this : (this) -> bool = n*.is_function_with_this(); - is_virtual : (this) -> bool = n*.is_virtual_function(); - is_defaultable : (this) -> bool = n*.is_defaultable_function(); - is_constructor : (this) -> bool = n*.is_constructor(); - is_default_constructor : (this) -> bool = n*.is_default_constructor(); - is_move : (this) -> bool = n*.is_move(); - is_swap : (this) -> bool = n*.is_swap(); - is_constructor_with_that : (this) -> bool = n*.is_constructor_with_that(); - is_constructor_with_in_that : (this) -> bool = n*.is_constructor_with_in_that(); - is_constructor_with_move_that: (this) -> bool = n*.is_constructor_with_move_that(); - is_assignment : (this) -> bool = n*.is_assignment(); - is_assignment_with_that : (this) -> bool = n*.is_assignment_with_that(); - is_assignment_with_in_that : (this) -> bool = n*.is_assignment_with_in_that(); - is_assignment_with_move_that : (this) -> bool = n*.is_assignment_with_move_that(); - is_destructor : (this) -> bool = n*.is_destructor(); - - is_copy_or_move : (this) -> bool = is_constructor_with_that() || is_assignment_with_that(); - - has_declared_return_type : (this) -> bool = n*.has_declared_return_type(); - has_deduced_return_type : (this) -> bool = n*.has_deduced_return_type(); - has_bool_return_type : (this) -> bool = n*.has_bool_return_type(); - has_non_void_return_type : (this) -> bool = n*.has_non_void_return_type(); - - unnamed_return_type : (this) -> std::string = n*.unnamed_return_type_to_string(); - - get_parameters: (this) - -> std::vector<object_declaration> - = { - ret: std::vector<object_declaration> = (); - for n*.get_function_parameters() do (param) { - _ = ret.emplace_back( param*.declaration*&, this ); - } - return ret; - } - - is_binary_comparison_function: (this) -> bool = n*.is_binary_comparison_function(); - - default_to_virtual : (inout this) = _ = n*.make_function_virtual(); - - make_virtual : (inout this) -> bool = n*.make_function_virtual(); - - add_initializer: (inout this, source: std::string_view) - pre<this> (!has_initializer(), "cannot add an initializer to a function that already has one") - pre<this> (parent_is_type(), "cannot add an initializer to a function that isn't in a type scope") - = { - //require( !has_initializer(), - // "cannot add an initializer to a function that already has one"); - //require( parent_is_type(), - // "cannot add an initializer to a function that isn't in a type scope"); - - stmt := parse_statement(source); - if !(stmt as bool) { - error( "cannot add an initializer that is not a valid statement"); - return; - } - require (n*.add_function_initializer(stmt), - std::string("unexpected error while attempting to add initializer")); - } -} - - -//----------------------------------------------------------------------- -// Object declarations -// -object_declaration: @copyable type = -{ - this: declaration = (); - - operator=: ( - out this, - n_: *declaration_node, - s : compiler_services - ) = - { - declaration = (n_, s); - assert( n*.is_object() ); - } - - is_const : (this) -> bool = n*.is_const(); - has_wildcard_type: (this) -> bool = n*.has_wildcard_type(); - - type: (this) -> std::string = { - ret := n*.object_type(); - require( !contains(ret, "(*ERROR*)"), - "cannot to_string this type: " + ret); - return ret; - } - - initializer: (this) -> std::string = { - ret := n*.object_initializer(); - require( !contains(ret, "(*ERROR*)"), - "cannot to_string this initializer: " + ret); - return ret; - } -} - - -//----------------------------------------------------------------------- -// Type declarations -// -type_declaration: @copyable type = -{ - this: declaration = (); - - operator=: ( - out this, - n_: *declaration_node, - s : compiler_services - ) = - { - declaration = (n_, s); - assert( n*.is_type() ); - } - - reserve_names: (this, name: std::string_view, forward etc...) = - { // etc is not declared ':string_view' for compatibility with GCC 10.x - for get_members() - do (m) { - m.require( !m.has_name( name ), - "in a '(get_metafunction_name())$' type, the name '(name)$' is reserved for use by the '(get_metafunction_name())$' implementation"); - } - if constexpr !CPP2_PACK_EMPTY(etc) { - reserve_names( etc... ); - } - } - - is_polymorphic: (this) -> bool = n*.is_polymorphic(); - is_final : (this) -> bool = n*.is_type_final(); - make_final : (inout this) -> bool = n*.make_type_final(); - - get_member_functions: (this) - -> std::vector<function_declaration> - = { - ret: std::vector<function_declaration> = (); - for n*.get_type_scope_declarations(declaration_node::functions) - do (d) { - _ = ret.emplace_back( d, this ); - } - return ret; - } - - get_member_functions_needing_initializer: (this) - -> std::vector<function_declaration> - = { - ret: std::vector<function_declaration> = (); - for n*.get_type_scope_declarations(declaration_node::functions) - do (d) - if !d*.has_initializer() - && !d*.is_virtual_function() - && !d*.is_defaultable_function() - { - _ = ret.emplace_back( d, this ); - } - return ret; - } - - get_member_objects: (this) - -> std::vector<object_declaration> - = { - ret: std::vector<object_declaration> = (); - for n*.get_type_scope_declarations(declaration_node::objects) do (d) { - _ = ret.emplace_back( d, this ); - } - return ret; - } - - get_member_types: (this) - -> std::vector<type_declaration> - = { - ret: std::vector<type_declaration> = (); - for n*.get_type_scope_declarations(declaration_node::types) do (d) { - _ = ret.emplace_back( d, this ); - } - return ret; - } - - get_member_aliases: (this) - -> std::vector<alias_declaration> - = { - ret: std::vector<alias_declaration> = (); - for n*.get_type_scope_declarations(declaration_node::aliases) do (d) { - _ = ret.emplace_back( d, this ); - } - return ret; - } - - get_members: (this) - -> std::vector<declaration> - = { - ret: std::vector<declaration> = (); - for n*.get_type_scope_declarations(declaration_node::all) do (d) { - _ = ret.emplace_back( d, this ); - } - return ret; - } - - query_declared_value_set_functions: (this) - -> ( - out_this_in_that : bool, - out_this_move_that : bool, - inout_this_in_that : bool, - inout_this_move_that : bool - ) - = { - declared := n*.find_declared_value_set_functions(); - out_this_in_that = declared.out_this_in_that != nullptr; - out_this_move_that = declared.out_this_move_that != nullptr; - inout_this_in_that = declared.inout_this_in_that != nullptr; - inout_this_move_that = declared.inout_this_move_that != nullptr; - } - - add_member: (inout this, source: std::string_view) - = { - decl := parse_statement(source); - if !(decl as bool) { - error("the provided source string is not a valid statement"); - return; - } - if !decl*.is_declaration() { - error("cannot add a member that is not a declaration"); - } - require( n*.add_type_member(decl), - std::string("unexpected error while attempting to add member:\n") + source ); - } - - remove_marked_members: (inout this) = n*.type_remove_marked_members(); - remove_all_members : (inout this) = n*.type_remove_all_members(); - - disable_member_function_generation: (inout this) = n*.type_disable_member_function_generation(); -} - - -//----------------------------------------------------------------------- -// Alias declarations -// -alias_declaration: @copyable type = -{ - this: declaration = (); - - operator=: ( - out this, - n_: *declaration_node, - s : compiler_services - ) = - { - declaration = (n_, s); - assert( n*.is_alias() ); - } -} - - -//----------------------------------------------------------------------- -// -// Metafunctions - these are hardwired for now until we get to the -// step of writing a Cpp2 interpreter to run inside the compiler -// -//----------------------------------------------------------------------- -// - -//----------------------------------------------------------------------- -// Some common metafunction helpers (metafunctions are just functions, -// so they can be factored as usual) -// -add_virtual_destructor: (inout t: meta::type_declaration) = -{ - t.add_member( "operator=: (virtual move this) = { }"); -} - - -//----------------------------------------------------------------------- -// -// "... an abstract base class defines an interface ..." -// -// -- Stroustrup (The Design and Evolution of C++, 12.3.1) -// -//----------------------------------------------------------------------- -// -// interface -// -// an abstract base class having only pure virtual functions -// -interface: (inout t: meta::type_declaration) = -{ - has_dtor := false; - - for t.get_members() do (inout m) - { - m.require( !m.is_object(), - "interfaces may not contain data objects"); - if m.is_function() { - mf := m.as_function(); - mf.require( !mf.is_copy_or_move(), - "interfaces may not copy or move; consider a virtual clone() instead"); - mf.require( !mf.has_initializer(), - "interface functions must not have a function body; remove the '=' initializer"); - mf.require( mf.make_public(), - "interface functions must be public"); - mf.default_to_virtual(); - has_dtor |= mf.is_destructor(); - } - } - - if !has_dtor { - t.add_virtual_destructor(); - } -} - - -//----------------------------------------------------------------------- -// -// "C.35: A base class destructor should be either public and -// virtual, or protected and non-virtual." -// -// "[C.43] ... a base class should not be copyable, and so does not -// necessarily need a default constructor." -// -// -- Stroustrup, Sutter, et al. (C++ Core Guidelines) -// -//----------------------------------------------------------------------- -// -// polymorphic_base -// -// A pure polymorphic base type that is not copyable, and whose -// destructor is either public and virtual or protected and nonvirtual. -// -// Unlike an interface, it can have nonpublic and nonvirtual functions. -// -polymorphic_base: (inout t: meta::type_declaration) = -{ - has_dtor := false; - - for t.get_member_functions() do (inout mf) - { - if mf.is_default_access() { - mf.default_to_public(); - } - mf.require( !mf.is_copy_or_move(), - "polymorphic base types may not copy or move; consider a virtual clone() instead"); - if mf.is_destructor() { - has_dtor = true; - mf.require( ((mf.is_public() || mf.is_default_access()) && mf.is_virtual()) - || (mf.is_protected() && !mf.is_virtual()), - "a polymorphic base type destructor must be public and virtual, or protected and nonvirtual"); - } - } - - if !has_dtor { - t.add_virtual_destructor(); - } -} - - -//----------------------------------------------------------------------- -// -// "... A totally ordered type ... requires operator<=> that -// returns std::strong_ordering. If the function is not -// user-written, a lexicographical memberwise implementation -// is generated by default..." -// -// -- P0707R4, section 3 -// -// Note: This feature derived from Cpp2 was already adopted -// into Standard C++ via paper P0515, so most of the -// heavy lifting is done by the Cpp1 C++20/23 compiler, -// including the memberwise default semantics -// (In contrast, cppfront has to do the work itself for -// default memberwise semantics for operator= assignment -// as those aren't yet part of Standard C++) -// -//----------------------------------------------------------------------- -// - -ordered_impl: ( - inout t: meta::type_declaration, - ordering: std::string_view // must be "strong_ordering" etc. -) = -{ - has_spaceship := false; - - for t.get_member_functions() do (inout mf) - { - if mf.has_name("operator<=>") { - has_spaceship = true; - return_name := mf.unnamed_return_type(); - if return_name.find(ordering) == return_name.npos - { - mf.error( "operator<=> must return std::" + ordering as std::string ); - } - } - } - - if !has_spaceship { - t.add_member( "operator<=>: (this, that) -> std::" + (ordering as std::string) + ";" ); - } -} - -//----------------------------------------------------------------------- -// ordered - a totally ordered type -// -// Note: the ordering that should be encouraged as default gets the nice name -// -ordered: (inout t: meta::type_declaration) = -{ - ordered_impl( t, "strong_ordering" ); -} - -//----------------------------------------------------------------------- -// weakly_ordered - a weakly ordered type -// -weakly_ordered: (inout t: meta::type_declaration) = -{ - ordered_impl( t, "weak_ordering" ); -} - -//----------------------------------------------------------------------- -// partially_ordered - a partially ordered type -// -partially_ordered: (inout t: meta::type_declaration) = -{ - ordered_impl( t, "partial_ordering" ); -} - - -//----------------------------------------------------------------------- -// -// "A value is ... a regular type. It must have all public -// default construction, copy/move construction/assignment, -// and destruction, all of which are generated by default -// if not user-written; and it must not have any protected -// or virtual functions (including the destructor)." -// -// -- P0707R4, section 3 -// -//----------------------------------------------------------------------- -// -// copyable -// -// A type with (copy and move) x (construction and assignment) -// -copyable: (inout t: meta::type_declaration) = -{ - // If the user explicitly wrote any of the copy/move functions, - // they must also have written the most general one - we can't - // assume we can safely generate it for them since they've opted - // into customized semantics - smfs := t.query_declared_value_set_functions(); - if !smfs.out_this_in_that - && ( - smfs.out_this_move_that - || smfs.inout_this_in_that - || smfs.inout_this_move_that - ) - { - t.error( "this type is partially copyable/movable - when you provide any of the more-specific operator= signatures, you must also provide the one with the general signature (out this, that); alternatively, consider removing all the operator= functions and let them all be generated for you with default memberwise semantics" ); - } - else if !smfs.out_this_in_that { - t.add_member( "operator=: (out this, that) = { }"); - } -} - -//----------------------------------------------------------------------- -// -// basic_value -// -// A regular type: copyable, plus has public default construction -// and no protected or virtual functions -// -basic_value: (inout t: meta::type_declaration) = -{ - t.copyable(); - - has_default_ctor := false; - for t.get_member_functions() do (inout mf) { - has_default_ctor |= mf.is_default_constructor(); - mf.require( !mf.is_protected() && !mf.is_virtual(), - "a value type may not have a protected or virtual function"); - mf.require( !mf.is_destructor() || mf.is_public() || mf.is_default_access(), - "a value type may not have a non-public destructor"); - } - - if !has_default_ctor { - t.add_member( "operator=: (out this) = { }"); - } -} - -//----------------------------------------------------------------------- -// -// "A 'value' is a totally ordered basic_value..." -// -// -- P0707R4, section 3 -// -// value - a value type that is totally ordered -// -// Note: the ordering that should be encouraged as default gets the nice name -// -value: (inout t: meta::type_declaration) = -{ - t.ordered(); - t.basic_value(); -} - -weakly_ordered_value: (inout t: meta::type_declaration) = -{ - t.weakly_ordered(); - t.basic_value(); -} - -partially_ordered_value: (inout t: meta::type_declaration) = -{ - t.partially_ordered(); - t.basic_value(); -} - - -//----------------------------------------------------------------------- -// -// "By definition, a `struct` is a `class` in which members -// are by default `public`; that is, -// -// struct s { ... -// -// is simply shorthand for -// -// class s { public: ... -// -// ... Which style you use depends on circumstances and taste. -// I usually prefer to use `struct` for classes that have all -// data `public`." -// -// -- Stroustrup (The C++ Programming Language, 3rd ed., p. 234) -// -//----------------------------------------------------------------------- -// -// struct -// -// a type with only public bases, objects, and functions, -// no virtual functions, and no user-defined constructors -// (i.e., no invariants) or assignment or destructors. -// -struct: (inout t: meta::type_declaration) = -{ - for t.get_members() do (inout m) - { - m.require( m.make_public(), - "all struct members must be public"); - if m.is_function() { - mf := m.as_function(); - t.require( !mf.is_virtual(), - "a struct may not have a virtual function"); - t.require( !mf.has_name("operator="), - "a struct may not have a user-defined operator="); - } - } - t.disable_member_function_generation(); -} - - -//----------------------------------------------------------------------- -// -// "C enumerations constitute a curiously half-baked concept. ... -// the cleanest way out was to deem each enumeration a separate type." -// -// -- Stroustrup (The Design and Evolution of C++, 11.7) -// -// "An enumeration is a distinct type ... with named constants" -// -// -- ISO C++ Standard -// -//----------------------------------------------------------------------- -// -// basic_enum -// -// a type together with named constants that are its possible values -// -value_member_info: @struct type = { - name : std::string; - type : std::string; - value : std::string; -} - -basic_enum: ( - inout t : meta::type_declaration, - nextval , - bitwise : bool - ) -= { - enumerators : std::vector<value_member_info> = (); - min_value : i64 = (); - max_value : i64 = (); - underlying_type : std::string; - - t.reserve_names( "operator=", "operator<=>" ); - if bitwise { - t.reserve_names( "has", "set", "clear", "to_string", "get_raw_value", "none" ); - } - - // 1. Gather: The names of all the user-written members, and find/compute the type - - underlying_type = t.get_argument(0); // use the first template argument, if there was one - - found_non_numeric := false; - - (copy value: std::string = "-1") - for t.get_members() - do (m) - if m.is_member_object() - { - m.require( m.is_public() || m.is_default_access(), - "an enumerator cannot be protected or private"); - - mo := m.as_object(); - if !mo.has_wildcard_type() { - mo.error( "an explicit underlying type should be specified as a template argument to the metafunction - try 'enum<u16>' or 'flag_enum<u64>'"); - } - - init := mo.initializer(); - - is_default_or_numeric := is_empty_or_a_decimal_number(init); - found_non_numeric |= !init.empty() && !is_default_or_numeric; - m.require( !is_default_or_numeric || !found_non_numeric || mo.has_name("none"), - "(mo.name())$: enumerators with non-numeric values must come after all default and numeric values"); - - nextval( value, init ); - - v := std::strtoll(value[0]&, nullptr, 10); // for non-numeric values we'll just get 0 which is okay for now - if v < min_value { - min_value = v; - } - if v > max_value { - max_value = v; - } - - // Adding local variable 'e' to work around a Clang warning - e: value_member_info = ( mo.name() as std::string, "", value ); - enumerators.push_back( e ); - - mo.mark_for_removal_from_enclosing_type(); - } - - if (enumerators.empty()) { - t.error( "an enumeration must contain at least one enumerator value"); - return; - } - - // Compute the default underlying type, if it wasn't explicitly specified - if underlying_type == "" - { - t.require( !found_non_numeric, - "if you write an enumerator with a non-numeric-literal value, you must specify the enumeration's underlying type"); - - if !bitwise { - if min_value >= std::numeric_limits<i8>::min() && max_value <= std::numeric_limits<i8>::max() { - underlying_type = "i8"; - } - else if min_value >= std::numeric_limits<i16>::min() && max_value <= std::numeric_limits<i16>::max() { - underlying_type = "i16"; - } - else if min_value >= std::numeric_limits<i32>::min() && max_value <= std::numeric_limits<i32>::max() { - underlying_type = "i32"; - } - else if min_value >= std::numeric_limits<i64>::min() && max_value <= std::numeric_limits<i64>::max() { - underlying_type = "i64"; - } - else { - t.error( "values are outside the range representable by the largest supported underlying signed type (i64)" ); - } - } - else { - umax := max_value * 2 as u64; - if umax <= std::numeric_limits<u8>::max() { - underlying_type = "u8"; - } - else if umax <= std::numeric_limits<u16>::max() { - underlying_type = "u16"; - } - else if umax <= std::numeric_limits<u32>::max() { - underlying_type = "u32"; - } - else { - underlying_type = "u64"; - } - } - } - - - // 2. Replace: Erase the contents and replace with modified contents - // - // Note that most values and functions are declared as '==' compile-time values, i.e. Cpp1 'constexpr' - - t.remove_marked_members(); - - // Generate the 'none' value if appropriate, and use that or - // else the first enumerator as the default-constructed value - default_value := enumerators[0].name; - if bitwise{ - default_value = "none"; - e: value_member_info = ( "none", "", "0"); - enumerators.push_back( e ); - } - - // Generate all the private implementation - t.add_member( " _value : (underlying_type)$;"); - t.add_member( " private operator= : (implicit out this, _val: i64) == _value = cpp2::unsafe_narrow<(underlying_type)$>(_val);"); - - // Generate the bitwise operations - if bitwise { - t.add_member( " operator|=: ( inout this, that ) == _value |= that._value;"); - t.add_member( " operator&=: ( inout this, that ) == _value &= that._value;"); - t.add_member( " operator^=: ( inout this, that ) == _value ^= that._value;"); - t.add_member( " operator| : ( this, that ) -> (t.name())$ == _value | that._value;"); - t.add_member( " operator& : ( this, that ) -> (t.name())$ == _value & that._value;"); - t.add_member( " operator^ : ( this, that ) -> (t.name())$ == _value ^ that._value;"); - t.add_member( " has : ( inout this, that ) -> bool == _value & that._value;"); - t.add_member( " set : ( inout this, that ) == _value |= that._value;"); - t.add_member( " clear : ( inout this, that ) == _value &= that._value~;"); - } - - // Add the enumerators - for enumerators do (e) { - t.add_member( " (e.name)$ : (t.name())$ == (e.value)$;"); - } - - // Generate the common functions - t.add_member( " get_raw_value : (this) -> (underlying_type)$ == _value;"); - t.add_member( " operator= : (out this) == { _value = (default_value)$._value; }"); - t.add_member( " operator= : (out this, that) == { }"); - t.add_member( " operator<=> : (this, that) -> std::strong_ordering;"); - - // Provide a 'to_string' function to print enumerator name(s) - (copy to_string: std::string = " to_string: (this) -> std::string = { \n") - { - if bitwise { - to_string += " _ret : std::string = \"(\";\n"; - to_string += " _comma : std::string = ();\n"; - to_string += " if this == none { return \"(none)\"; }\n"; - } - - for enumerators - do (e) { - if e.name != "_" { // ignore unnamed values - if bitwise { - if e.name != "none" { - to_string += " if (this & (e.name)$) == (e.name)$ { _ret += _comma + \"(e.name)$\"; _comma = \", \"; }\n"; - } - } - else { - to_string += " if this == (e.name)$ { return \"(e.name)$\"; }\n"; - } - } - } - - if bitwise { - to_string += " return _ret+\")\";\n}\n"; - } - else { - to_string += " return \"invalid (t.name())$ value\";\n}\n"; - } - - t.add_member( to_string ); - } -} - - -//----------------------------------------------------------------------- -// -// "An enum[...] is a totally ordered value type that stores a -// value of its enumerators's type, and otherwise has only public -// member variables of its enumerator's type, all of which are -// naturally scoped because they are members of a type." -// -// -- P0707R4, section 3 -// -enum: (inout t: meta::type_declaration) = -{ - // Let basic_enum do its thing, with an incrementing value generator - t.basic_enum( - :(inout value: std::string, specified_value: std::string) = { - if !specified_value.empty() { - value = specified_value; - } else { - v := std::strtoll(value[0]&, nullptr, 10); - value = (v + 1) as std::string; - } - }, - false // disable bitwise operations - ); -} - - -//----------------------------------------------------------------------- -// -// "flag_enum expresses an enumeration that stores values -// corresponding to bitwise-or'd enumerators. The enumerators must -// be powers of two, and are automatically generated [...] A none -// value is provided [...] Operators | and & are provided to -// combine and extract values." -// -// -- P0707R4, section 3 -// -flag_enum: (inout t: meta::type_declaration) = -{ - // Let basic_enum do its thing, with a power-of-two value generator - t.basic_enum( - :(inout value: std::string, specified_value: std::string) = { - if !specified_value.empty() { - value = specified_value; - } else { - v := std::strtoll(value[0]&, nullptr, 10); - if v < 1 { - value = "1"; - } - else { - value = (v * 2) as std::string; - } - } - }, - true // enable bitwise operations - ); -} - - -//----------------------------------------------------------------------- -// -// "As with void*, programmers should know that unions [...] are -// inherently dangerous, should be avoided wherever possible, -// and should be handled with special care when actually needed." -// -// -- Stroustrup (The Design and Evolution of C++, 14.3.4.1) -// -// "C++17 needs a type-safe union... The implications of the -// consensus `variant` design are well understood and have been -// explored over several LEWG discussions, over a thousand emails, -// a joint LEWG/EWG session, and not to mention 12 years of -// experience with Boost and other libraries." -// -// -- Axel Naumann, in P0088 (wg21.link/p0088), -// the adopted proposal for C++17 std::variant -// -//----------------------------------------------------------------------- -// -// union -// -// a type that contains exactly one of a fixed set of values at a time -// - -union: (inout t : meta::type_declaration) -= { - alternatives : std::vector<value_member_info> = (); - - // 1. Gather: All the user-written members, and find/compute the max size - - (copy value := 0) - for t.get_members() - next value++ - do (m) - if m.is_member_object() - { - m.require( m.is_public() || m.is_default_access(), - "a union alternative cannot be protected or private"); - - m.require( !m.name().starts_with("is_") - && !m.name().starts_with("set_"), - "a union alternative's name cannot start with 'is_' or 'set_' - that could cause user confusion with the 'is_alternative' and 'set_alternative' generated functions"); - - mo := m.as_object(); - mo.require( mo.initializer().empty(), - "a union alternative cannot have an initializer"); - - // Adding local variable 'e' to work around a Clang warning - e: value_member_info = ( mo.name() as std::string, mo.type(), value as std::string ); - alternatives.push_back( e ); - - mo.mark_for_removal_from_enclosing_type(); - } - - discriminator_type: std::string = (); - if alternatives.ssize() < std::numeric_limits<i8>::max() { - discriminator_type = "i8"; - } - else if alternatives.ssize() < std::numeric_limits<i16>::max() { - discriminator_type = "i16"; - } - else if alternatives.ssize() < std::numeric_limits<i32>::max() { - discriminator_type = "i32"; - } - else { - discriminator_type = "i64"; - } - - - // 2. Replace: Erase the contents and replace with modified contents - - t.remove_marked_members(); - - // Provide storage - (copy storage: std::string = " _storage: cpp2::aligned_storage<cpp2::max( ") - { - (copy comma: std::string = "") - for alternatives - next comma = ", " - do (e) { - storage += comma + "sizeof((e.type)$)"; - } - - storage += "), cpp2::max( "; - - (copy comma: std::string = "") - for alternatives - next comma = ", " - do (e) { - storage += comma + "alignof((e.type)$)"; - } - - storage += " )> = ();\n"; - t.add_member( storage ); - } - - // Provide discriminator - t.add_member( " _discriminator: (discriminator_type)$ = -1;\n"); - - // Add the alternatives: is_alternative, get_alternative, and set_alternative - for alternatives - do (a) - { - t.add_member( " is_(a.name)$: (this) -> bool = _discriminator == (a.value)$;\n"); - - t.add_member( " (a.name)$: (this) -> forward (a.type)$ pre(is_(a.name)$()) = reinterpret_cast<* const (a.type)$>(_storage&)*;\n"); - - t.add_member( " (a.name)$: (inout this) -> forward (a.type)$ pre(is_(a.name)$()) = reinterpret_cast<*(a.type)$>(_storage&)*;\n"); - - t.add_member( " set_(a.name)$: (inout this, _value: (a.type)$) = { if !is_(a.name)$() { _destroy(); std::construct_at( reinterpret_cast<*(a.type)$>(_storage&), _value); } else { reinterpret_cast<*(a.type)$>(_storage&)* = _value; } _discriminator = (a.value)$; }\n"); - - t.add_member( " set_(a.name)$: (inout this, forward _args...: _) = { if !is_(a.name)$() { _destroy(); std::construct_at( reinterpret_cast<*(a.type)$>(_storage&), _args...); } else { reinterpret_cast<*(a.type)$>(_storage&)* = :(a.type)$ = (_args...); } _discriminator = (a.value)$; }\n"); - } - - // Add destroy - (copy destroy: std::string = " private _destroy: (inout this) = {\n") - { - for alternatives - do (a) { - destroy += " if _discriminator == (a.value)$ { std::destroy_at( reinterpret_cast<*(a.type)$>(_storage&) ); }\n"; - } - - destroy += " _discriminator = -1;\n"; - destroy += " }\n"; - t.add_member( destroy ); - } - - // Add the destructor - t.add_member( " operator=: (move this) = { _destroy(); }" ); - - // Add default constructor - t.add_member( " operator=: (out this) = { }" ); - - // Add copy/move construction and assignment - (copy value_set: std::string = "") - { - for alternatives - do (a) { - value_set += " if that.is_(a.name)$() { set_(a.name)$( that.(a.name)$() ); }\n"; - } - value_set += " }\n"; - - t.add_member( std::string(" operator=: (out this, that) = {\n") - + " _storage = ();\n" - + " _discriminator = -1;\n" - + value_set - ); - t.add_member( std::string(" operator=: (inout this, that) = {\n") - + " _storage = _;\n" - + " _discriminator = _;\n" - + value_set - ); - } -} - - -//----------------------------------------------------------------------- -// -// print - output a pretty-printed visualization of t -// -print: (t: meta::type_declaration) = -{ - std::cout << t.print() << "\n"; -} - - -//----------------------------------------------------------------------- -// -// apply_metafunctions -// -apply_metafunctions: ( - inout n : declaration_node, - inout rtype : type_declaration, - error - ) - -> bool -= { - assert( n.is_type() ); - - // Check for _names reserved for the metafunction implementation - for rtype.get_members() - do (m) - { - m.require( !m.name().starts_with("_") || m.name().ssize() > 1, - "a type that applies a metafunction cannot have a body that declares a name that starts with '_' - those names are reserved for the metafunction implementation"); - } - - // For each metafunction, apply it - for n.metafunctions - do (meta) - { - // Convert the name and any template arguments to strings - // and record that in rtype - name := meta*.to_string(); - name = name.substr(0, name.find('<')); - - args: std::vector<std::string> = (); - for meta*.template_arguments() - do (arg) - args.push_back( arg.to_string() ); - - rtype.set_metafunction_name( name, args ); - - // Dispatch - // - if name == "interface" { - interface( rtype ); - } - else if name == "polymorphic_base" { - polymorphic_base( rtype ); - } - else if name == "ordered" { - ordered( rtype ); - } - else if name == "weakly_ordered" { - weakly_ordered( rtype ); - } - else if name == "partially_ordered" { - partially_ordered( rtype ); - } - else if name == "copyable" { - copyable( rtype ); - } - else if name == "basic_value" { - basic_value( rtype ); - } - else if name == "value" { - value( rtype ); - } - else if name == "weakly_ordered_value" { - weakly_ordered_value( rtype ); - } - else if name == "partially_ordered_value" { - partially_ordered_value( rtype ); - } - else if name == "struct" { - cpp2_struct( rtype ); - } - else if name == "enum" { - cpp2_enum( rtype ); - } - else if name == "flag_enum" { - flag_enum( rtype ); - } - else if name == "union" { - cpp2_union( rtype ); - } - else if name == "print" { - print( rtype ); - } - else { - error( "unrecognized metafunction name: " + name ); - error( "(temporary alpha limitation) currently the supported names are: interface, polymorphic_base, ordered, weakly_ordered, partially_ordered, copyable, basic_value, value, weakly_ordered_value, partially_ordered_value, struct, enum, flag_enum, union, print" ); - return false; - } - - if ( - !args.empty() - && !rtype.arguments_were_used() - ) - { - error( name + " did not use its template arguments - did you mean to write '" + name + " <" + args[0] + "> type' (with the spaces)?"); - return false; - } - } - - return true; -} - - -} - -} diff --git a/64x0/cc2/source/sema.h b/64x0/cc2/source/sema.h deleted file mode 100644 index d339659..0000000 --- a/64x0/cc2/source/sema.h +++ /dev/null @@ -1,1892 +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. - - -//=========================================================================== -// Semantic analysis -//=========================================================================== - -#ifndef CPP2_SEMA_H -#define CPP2_SEMA_H - -#include "reflect.h" - - -namespace cpp2 { - -auto parser::apply_type_metafunctions( declaration_node& n ) - -> bool -{ - assert(n.is_type()); - - // Get the reflection state ready to pass to the function - auto cs = meta::compiler_services{ &errors, generated_tokens }; - auto rtype = meta::type_declaration{ &n, cs }; - - return apply_metafunctions( - n, - rtype, - [&](std::string const& msg) { error( msg, false ); } - ); -} - - -//----------------------------------------------------------------------- -// -// Symbol/scope table -// -//----------------------------------------------------------------------- -// -struct declaration_sym { - bool start = false; - declaration_node const* declaration = {}; - token const* identifier = {}; - statement_node const* initializer = {}; - parameter_declaration_node const* parameter = {}; - bool member = false; - - declaration_sym( - bool s = false, - declaration_node const* decl = {}, - token const* id = {}, - statement_node const* init = {}, - parameter_declaration_node const* param = {}, - bool mem = false - ) - : start{s} - , declaration{decl} - , identifier{id} - , initializer{init} - , parameter{param} - , member{mem} - { } - - auto position() const - -> source_position - { - assert (declaration); - return declaration->position(); - } -}; - -struct identifier_sym { - bool standalone_assignment_to = false; - token const* identifier = {}; - - identifier_sym( - bool a, - token const* id - ) - : standalone_assignment_to{a} - , identifier{id} - { } - - auto position() const - -> source_position - { - assert (identifier); - return identifier->position(); - } -}; - -struct selection_sym { - bool start = false; - selection_statement_node const* selection = {}; - - selection_sym( - bool s, - selection_statement_node const* sel - ) - : start{s} - , selection{sel} - { } - - auto position() const - -> source_position - { - assert (selection); - return selection->position(); - } -}; - -struct compound_sym { - bool start = false; - compound_statement_node const* compound = {}; - enum kind { is_scope, is_true, is_false } kind_ = is_scope; - - compound_sym( - bool s, - compound_statement_node const* c, - kind k - ) - : start{s} - , compound{c} - , kind_{k} - { } - - auto position() const - -> source_position - { - assert (compound); - return compound->position(); - } -}; - -struct symbol { - int depth = -1; - - enum active { declaration=0, identifier, selection, compound }; - std::variant < - declaration_sym, - identifier_sym, - selection_sym, - compound_sym - > sym; - - bool start = true; - - symbol(int depth, declaration_sym const& sym) : depth{depth}, sym{sym}, start{sym.start} { } - symbol(int depth, identifier_sym const& sym) : depth{depth}, sym{sym} { } - symbol(int depth, selection_sym const& sym) : depth{depth}, sym{sym}, start{sym.start} { } - symbol(int depth, compound_sym const& sym) : depth{depth}, sym{sym}, start{sym.start} { } - - auto position() const - -> source_position - { - switch (sym.index()) - { - break;case declaration: { - auto const& s = std::get<declaration>(sym); - return s.position(); - } - - break;case identifier: { - auto const& s = std::get<identifier>(sym); - return s.position(); - } - - break;case selection: { - auto const& s = std::get<selection>(sym); - return s.position(); - } - - break;case compound: { - auto const& s = std::get<compound>(sym); - return s.position(); - } - - break;default: - assert (!"illegal symbol state"); - return { 0, 0 }; - } - } -}; - - -// Keep a list of all token*'s found that are definite first uses -// of the form "x = expr;" for an uninitialized local variable x, -// which we will rewrite to construct the local variable. -// -std::vector<token const*> definite_initializations; - -auto is_definite_initialization(token const* t) - -> bool -{ - return - std::find( - definite_initializations.begin(), - definite_initializations.end(), - t - ) - != definite_initializations.end(); -} - - -// Keep a list of all token*'s found that are definite last uses -// for a local variable or copy or forward parameter x, which we -// will rewrite to move or forward from the variable. -// -struct last_use { - token const* t; - bool is_forward; - - last_use( - token const* t_, - bool is_forward_ = false - ) - : t{t_} - , is_forward{is_forward_} - { } - - bool operator==(last_use const& that) { return t == that.t; } -}; -std::vector<last_use> definite_last_uses; - -auto is_definite_last_use(token const* t) - -> last_use const* -{ - auto iter = std::find( - definite_last_uses.begin(), - definite_last_uses.end(), - t - ); - if (iter != definite_last_uses.end()) { - return &*iter; - } - else { - return {}; - } -} - - -//----------------------------------------------------------------------- -// -// sema: Semantic analysis -// -//----------------------------------------------------------------------- -// -class sema -{ -public: - std::vector<error_entry>& errors; - std::vector<symbol> symbols; - - std::vector<selection_statement_node const*> active_selections; - -public: - //----------------------------------------------------------------------- - // Constructor - // - // errors error list - // - sema( - std::vector<error_entry>& errors_ - ) - : errors{ errors_ } - { - } - - // Get the declaration of t within the same named function or beyond it - // - auto get_declaration_of( - token const* t, - bool look_beyond_current_function = false - ) - -> declaration_sym const* - { - if (!t) { - return {}; - } - return get_declaration_of(*t, look_beyond_current_function); - } - - auto get_declaration_of( - token const& t, - bool look_beyond_current_function = false - ) - -> declaration_sym const* - { - // First find the position the query is coming from - // and remember its depth - auto i = symbols.cbegin(); - while ( - i != symbols.cend() - && i->position() < t.position() - ) - { - ++i; - } - - while ( - i == symbols.cend() - || !i->start - ) - { - if (i == symbols.cbegin()) { - return nullptr; - } - --i; - } - - auto depth = i->depth; - - // Then look backward to find the first declaration of - // this name that is not deeper (in a nested scope) - // and is in the same function - for ( - auto ri = std::make_reverse_iterator(i+1); - ri != symbols.crend() && ri->position() <= t.position(); // TODO: See pure2-deducing-pointers-error.cpp2 - ++ri - ) - { - if ( - ri->sym.index() == symbol::active::declaration - && ri->depth <= depth - ) - { - auto const& decl = std::get<symbol::active::declaration>(ri->sym); - - // Conditionally look beyond the start of the current named (has identifier) function - // (an unnamed function is ok to look beyond) - assert(decl.declaration); - if ( - decl.declaration->type.index() == declaration_node::a_function - && decl.declaration->identifier - && !look_beyond_current_function - ) - { - return nullptr; - } - - // If the name matches, this is it - if ( - decl.identifier - && *decl.identifier == t - ) - { - return &decl; - } - depth = ri->depth; - } - } - - return nullptr; - } - - - //----------------------------------------------------------------------- - // Factor out the uninitialized var decl test - // - auto is_uninitialized_decl(declaration_sym const& sym) - -> bool - { - return - sym.start - && !(sym.identifier && *sym.identifier == "this") - && !sym.initializer - && !(sym.parameter && sym.parameter->pass != passing_style::out) - ; - } - - - auto debug_print(std::ostream& o) - -> void - { - for (auto const& s : symbols) - { - o << std::setw(3) << s.depth << " |"; - o << std::setw(s.depth*2+1) << " "; - - switch (s.sym.index()) { - - break;case symbol::active::declaration: { - auto const& sym = std::get<symbol::active::declaration>(s.sym); - - assert (sym.declaration); - if (sym.declaration->is_function()) { - if (sym.start) { - o << "function "; - } - else { - o << "/function"; - } - } - else if (sym.declaration->is_object()) { - if (sym.start) { - o << "var "; - } - else { - o << "/var "; - } - } - - if (sym.start && sym.identifier) { - o << sym.identifier->to_string(); - } - - if (is_uninitialized_decl(sym)) { - o << " *** UNINITIALIZED"; - } - } - - break;case symbol::active::identifier: { - auto const& sym = std::get<symbol::active::identifier>(s.sym); - assert (sym.identifier); - if (auto use = is_definite_last_use(sym.identifier)) { - o << "*** " << sym.identifier->position().to_string() - << " DEFINITE LAST " - << (use->is_forward ? "FORWARDING" : "POTENTIALLY MOVING") - << " USE OF "; - } - - if (is_definite_initialization(sym.identifier)) { - o << "*** " << sym.identifier->position().to_string() - << " DEFINITE INITIALIZATION OF "; - } - else if (sym.standalone_assignment_to) { - o << "*** assignment to "; - } - else { - o << "*** use of "; - } - o << sym.identifier->to_string(); - } - - break;case symbol::active::selection: { - auto const& sym = std::get<symbol::active::selection>(s.sym); - if (!sym.start) { - o << "/"; - } - o << "selection"; - } - - break;case symbol::active::compound: { - auto const& sym = std::get<symbol::active::compound>(s.sym); - if (!sym.start) { - o << "/"; - --scope_depth; - } - if (sym.kind_ == sym.is_true) { - o << "true branch"; - } - else if (sym.kind_ == sym.is_false) { - o << "false branch"; - } - else { - o << "scope"; - } - - } - - break;default: - o << "ERROR"; - } - - o << "\n"; - } - } - - - //----------------------------------------------------------------------- - // Apply local first- and last-use rules - // - auto apply_local_rules() - -> bool - { - auto ret = true; - - //----------------------------------------------------------------------- - // Helpers for readability - - // It's an uninitialized variable (incl. named return values) if it's - // a non-namespace-scope non-parameter object with no initializer - // - auto is_uninitialized_variable_decl = [&](symbol const& s) - -> declaration_sym const* - { - if (auto const* sym = std::get_if<symbol::active::declaration>(&s.sym)) { - assert (sym); - if (is_uninitialized_decl(*sym)) { - if ( - sym->declaration->is_object() - && !sym->declaration->parent_is_namespace() - ) - { - return sym; - } - else { - return {}; - } - } - } - return {}; - }; - - // It's a local (incl. named return value or copy or move or forward parameter) - // - auto is_potentially_movable_local = [&](symbol const& s) - -> declaration_sym const* - { - if (auto const* sym = std::get_if<symbol::active::declaration>(&s.sym)) { - if ( - sym->start - && sym->declaration->is_object() - && (!sym->parameter - || sym->parameter->pass == passing_style::copy - || sym->parameter->pass == passing_style::move - || sym->parameter->pass == passing_style::forward - ) - ) - { - // Must be in function scope - if ( - sym->declaration->parent_declaration - && sym->declaration->parent_declaration->is_function() - ) - { - return sym; - } - else { - return {}; - } - } - } - return {}; - }; - - //----------------------------------------------------------------------- - // Function logic: For each entry in the table... - // - for (auto sympos = std::ssize(symbols) - 1; sympos >= 0; --sympos) - { - // If this is an uninitialized local variable, - // ensure it is definitely initialized and tag those initializations - // - if (auto decl = is_uninitialized_variable_decl(symbols[sympos])) { - assert( - decl->identifier - && !decl->initializer - ); - ret = ret - && ensure_definitely_initialized(decl, sympos+1, symbols[sympos].depth) - ; - } - - // If this is a copy, move, or forward parameter or a local variable, - // identify and tag its definite last uses to `std::move` from them - // - if (auto decl = is_potentially_movable_local(symbols[sympos])) { - assert (decl->identifier); - find_definite_last_uses( - decl->identifier, - sympos, - decl->parameter && decl->parameter->pass == passing_style::forward - ); - } - } - - return ret; - } - -private: - // Find the definite last uses for local variable *id starting at the - // given position and depth in the symbol/scope table - // - auto find_definite_last_uses( - token const* id, - int pos, - bool is_forward - ) const - -> void - { - auto i = pos; - auto depth = symbols[pos].depth; - - // Maintain a stack of the depths of the most recently seen - // selection statements, using the current depth-2 as a sentinel - auto selections = std::vector<int>{depth-2}; - - // Scan forward to the end of this scope, keeping track of - // the trailing nest of selection statements - while ( - i+1 < std::ssize(symbols) - && symbols[i+1].depth >= depth - ) - { - assert (std::ssize(symbols) > 1); - if (symbols[i].sym.index() == symbol::selection) { - auto const& s = std::get<symbol::selection>(symbols[i].sym); - if (s.start) { - selections.push_back(symbols[i].depth); - } - //else { - // assert (symbols[i].depth-1 == selections.back()); - // selections.pop_back(); - //} - } - ++i; - } - - // i is now at the end of id's scope, so start scanning backwards - // until we find the first definite last use - for (auto found = false; i > pos; --i) - { - // Once we find something, don't continue back further - // than the closest enclosing selection statement - if ( - found - && symbols[i].depth <= selections.back() - ) - { - break; - } - - if (symbols[i].sym.index() == symbol::active::identifier) - { - auto const& sym = std::get<symbol::active::identifier>(symbols[i].sym); - assert (sym.identifier); - - // If we find a use of this identifier - if (*sym.identifier == *id) - { - if ( - !found - || symbols[i].depth > selections.back()+1 - ) - { - definite_last_uses.emplace_back( sym.identifier, is_forward ); - found = true; - } - - // Pop any of the last branches that we're outside of - while (symbols[i].depth <= selections.back()) { - selections.pop_back(); - assert (!selections.empty()); // won't remove sentinel - } - // Then skip over the earlier part of the current branch - while ( - i > pos - && symbols[i].depth > selections.back() + 1 - ) - { - --i; - } - } - } - } - - // If we arrived back at the declaration without finding a use - // and this isn't generated code (ignore that for now) - // and this is a user-named object (not 'this', 'that', or '_') - if ( - i == pos - && id->position().lineno > 0 - && *id != "this" - && *id != "that" - && *id != "_" - ) - { - errors.emplace_back( - id->position(), - "local variable " + id->to_string() + " is not used; consider changing its name to '_' to make it explicitly anonymous, or removing it entirely if its side effects are not needed" - ); - } - } - - - // Check that local variable *id is initialized before use on all paths - // starting at the given position and depth in the symbol/scope table - // - // TODO: After writing the first version of this, I realized that it could be - // simplified a lot by using a sentinel value to represent the base case like - // the others instead of as a special case. It's tempting to rewrite this now - // to do that cleanup, but the code is working and fully localized, so - // rewriting it wouldn't give any benefit, and I need to resist the urge to - // be distracted by goldplating when I could be implementing a new feature. - // - auto ensure_definitely_initialized( - declaration_sym const* decl, - int pos, - int depth - ) const - -> bool - { - // If this is a member variable in a constructor, the name doesn't - // appear lexically right in the constructor, so prepending "this." - // to the printed name might make the error more readable to the programmer - auto name = decl->identifier->to_string(); - if (decl->declaration->parent_is_type()) { - name += " (aka this." + name + ")"; - } - - struct stack_entry{ - int pos; // start of this selection statement - - struct branch { - int start; - bool result = false; - - branch(int s, bool r) : start{s}, result{r} { } - }; - std::vector<branch> branches; - - stack_entry(int p) : pos{p} { } - - auto debug_print(std::ostream& o) const -> void - { - o << "Stack entry: " << pos << "\n"; - for (auto const& e : branches) { - o << " ( " << e.start << " , " << e.result << " )\n"; - } - } - }; - std::vector<stack_entry> selection_stack; - - for ( - ; - pos < std::ssize(symbols) && symbols[pos].depth >= depth; - ++pos - ) - { - switch (symbols[pos].sym.index()) { - - break;case symbol::active::declaration: { - auto const& sym = std::get<symbol::active::declaration>(symbols[pos].sym); - if ( - sym.start - && sym.identifier - && *sym.identifier == *decl->identifier - ) - { - errors.emplace_back( - sym.identifier->position(), - "local variable " + sym.identifier->to_string() - + " cannot have the same name as an uninitialized" - " variable in the same function"); - } - } - - break;case symbol::active::identifier: { - auto const& sym = std::get<symbol::active::identifier>(symbols[pos].sym); - assert (sym.identifier); - - if (is_definite_initialization(sym.identifier)) { - errors.emplace_back( - sym.identifier->position(), - "local variable " + name - + " must be initialized before " + sym.identifier->to_string() - + " (local variables must be initialized in the order they are declared)" - ); - return false; - } - - // If we find a use of this identifier - if (*sym.identifier == *decl->identifier) { - - // If we're not inside a selection statement, we're at the top level -- - // just return true if it's an assignment to it, else return false - if (std::ssize(selection_stack) == 0) { - if (sym.standalone_assignment_to) { - definite_initializations.push_back( sym.identifier ); - } - else { - errors.emplace_back( - sym.identifier->position(), - "local variable " + name - + " is used before it was initialized"); - } - return sym.standalone_assignment_to; - } - - // Else if we're inside a selection statement but still in the condition - // portion (there are no branches entered yet) - else if (std::ssize(selection_stack.back().branches) == 0) { - // If this is a top-level selection statement, handle it the same as - // if we weren't an a selection statement - if (std::ssize(selection_stack) == 1) { - if (sym.standalone_assignment_to) { - definite_initializations.push_back( sym.identifier ); - } - else { - errors.emplace_back( - sym.identifier->position(), - "local variable " + name - + " is used in a condition before it was initialized"); - } - return sym.standalone_assignment_to; - } - // Else we can skip the rest of this selection statement, and record - // this as the result of the next outer selection statement's current branch - else { - selection_stack.pop_back(); - assert (std::ssize(selection_stack.back().branches) > 0); - selection_stack.back().branches.back().result = sym.standalone_assignment_to; - - int this_depth = symbols[pos].depth; - while (symbols[pos + 1].depth >= this_depth) { - ++pos; - } - } - } - - // Else we're in a selection branch and can skip the rest of this branch - // and record this as the result for the current branch - else { - if (sym.standalone_assignment_to) { - definite_initializations.push_back( sym.identifier ); - } - else { - errors.emplace_back( - sym.identifier->position(), - "local variable " + name - + " is used in a branch before it was initialized"); - } - selection_stack.back().branches.back().result = sym.standalone_assignment_to; - - // The depth of this branch should always be the depth of - // the current selection statement + 1 - int branch_depth = symbols[selection_stack.back().pos].depth + 1; - while (symbols[pos + 1].depth > branch_depth) { - ++pos; - } - } - - } - } - - break;case symbol::active::selection: { - auto const& sym = std::get<symbol::active::selection>(symbols[pos].sym); - - // If we're starting a new selection statement, add a stack entry for it - if (sym.start) { - selection_stack.emplace_back( pos ); - } - - // If we're ending a selection statement, look at the partial results -- - // they must all be false or all true, if they're a mix we are missing - // initializations on some path(s) - else { - assert (std::ssize(selection_stack) > 0); - - auto true_branches = std::string{}; - auto false_branches = std::string{}; - for (auto const& b : selection_stack.back().branches) - { - // If this is not an implicit 'else' branch (i.e., if lineno > 0) - if (symbols[b.start].position().lineno > 0) { - (b.result ? true_branches : false_branches) - += "\n branch starting at line " - + std::to_string(symbols[b.start].position().lineno); - } - else { - (b.result ? true_branches : false_branches) - += "\n implicit else branch"; - } - } - - // If none of the branches was true - if (true_branches.length() == 0) - { - selection_stack.pop_back(); - // Nothing else to do, just continue - } - - // Else if all of the branches were true - else if (false_branches.length() == 0) - { - // If this is a top-level selection statement, handle it the same as - // if we weren't an a selection statement - if (std::ssize(selection_stack) == 1) { - return true; - } - // Else pop this selection statement, and record this as the result - // of the next outer selection statement's current branch - else { - selection_stack.pop_back(); - assert (std::ssize(selection_stack.back().branches) > 0); - selection_stack.back().branches.back().result = true; - - // And skip the rest of this branch - auto skip_depth = symbols[pos].depth - 1; - while (symbols[pos + 1].depth >= skip_depth) { - ++pos; - } - } - } - - // Else we found a missing initializion, report it and return false - else - { - errors.emplace_back( - decl->identifier->position(), - "local variable " + name - + " must be initialized on both branches or neither branch"); - - assert (symbols[selection_stack.back().pos].sym.index() == symbol::active::selection); - auto const& sym = std::get<symbol::active::selection>(symbols[pos].sym); - errors.emplace_back( - sym.selection->identifier->position(), - "\"" + sym.selection->identifier->to_string() - + "\" initializes " + name - + " on:" + true_branches - + "\nbut not on:" + false_branches - ); - - return false; - } - - } - } - - break;case symbol::active::compound: { - auto const& sym = std::get<symbol::active::compound>(symbols[pos].sym); - - // If we're in a selection - if (std::ssize(selection_stack) > 0) { - // If this is a compound start with the current selection's depth - // plus one, it's the start of one of the branches of that selection - if ( - sym.start - && symbols[pos].depth == symbols[selection_stack.back().pos].depth+1 - ) - { - selection_stack.back().branches.emplace_back( pos, false ); - } - } - } - - break;default: - assert (!"illegal symbol"); - } - - } - - errors.emplace_back( - decl->identifier->position(), - name - + " - variable must be initialized on every branch path"); - return false; - } - - -public: - //----------------------------------------------------------------------- - // Per-node sema rules - // - - auto check(qualified_id_node const& n) - { - // Check for some incorrect uses of . - if (auto decl = get_declaration_of(n.get_first_token(), true); - decl && std::ssize(n.ids) > 1 - ) - { - assert (decl->declaration); - - if ( - decl->declaration->is_object() - && n.ids[1].scope_op - && n.ids[1].scope_op->type() == lexeme::Scope - ) - { - errors.emplace_back( - n.position(), - "use '" + decl->identifier->to_string() + ".' to refer to an object member" - ); - return false; - } - } - - return true; - } - - - auto check(postfix_expression_node const& n) - { - // Check for some incorrect uses of :: or . - if (auto decl = get_declaration_of(n.get_first_token_ignoring_this(), true); - decl && !n.ops.empty() - ) - { - assert (decl->declaration); - - if ( - decl->declaration->is_type() - && n.ops[0].op - && n.ops[0].op->type() == lexeme::Dot - ) - { - errors.emplace_back( - n.position(), - "use '" + decl->identifier->to_string() + "::' to refer to a type member" - ); - return false; - } - } - - return true; - } - - - auto check(parameter_declaration_node const& n) - -> bool - { - auto type_name = std::string{}; - if (n.declaration->has_declared_return_type()) { - type_name = n.declaration->get_object_type()->to_string(); - } - - if ( - n.ordinal == 2 - && !n.has_name("that") - && n.declaration->parent_declaration - && n.declaration->parent_declaration->has_name("operator=") - && n.declaration->parent_declaration->parent_declaration - && n.declaration->parent_declaration->parent_declaration->name() - && type_name == *n.declaration->parent_declaration->parent_declaration->name() - ) - { - errors.emplace_back( - n.position(), - "if an 'operator=' second parameter is of the same type (here '" + type_name + "'), it must be named 'that'" - ); - return false; - } - - return true; - } - - auto check(declaration_node const& n) - -> bool - { - // An object of deduced type must have an initializer - if ( - n.is_object() - && n.has_wildcard_type() - && !n.has_initializer() - ) - { - errors.emplace_back( - n.position(), - "an object with a deduced type must have an = initializer" - ); - return false; - } - - // An object initializer must be an expression - if ( - n.is_object() - && n.initializer - && !n.initializer->is_expression() - ) - { - errors.emplace_back( - n.position(), - "an object initializer must be an expression" - ); - return false; - } - - // A namespace must be initialized with a compound expression - if ( - n.is_namespace() - && ( - !n.initializer - || !n.initializer->is_compound() - ) - ) - { - errors.emplace_back( - n.position(), - "a namespace must be = initialized with a { } body containing declarations" - ); - return false; - } - - // A function body must be an expression-statement or a compound-statement - if ( - n.is_function() - && n.initializer - && n.initializer->is_return() - ) - { - errors.emplace_back( - n.position(), - "a function with a single-expression body doesn't need to say 'return' - either omit 'return' or write a full { }-enclosed function body" - ); - return false; - } - - // A nonvirtual and nondefaultable function must have an initializer - if ( - n.is_function() - && !n.is_virtual_function() - && !n.is_defaultable_function() - && !n.has_initializer() - ) - { - errors.emplace_back( - n.position(), - "a function must have a body ('=' initializer), unless it is virtual (has a 'virtual this' parameter) or is defaultable (operator== or operator<=>)" - ); - return false; - } - - if ( - n.is_type() - && !n.parent_is_namespace() - && !n.parent_is_type() - ) - { - errors.emplace_back( - n.position(), - "(temporary alpha limitation) a type must be in a namespace or type scope - function-local types are not yet supported" - ); - return false; - } - - // A type scope variable must have a declared type - if ( - n.parent_is_type() - && n.has_wildcard_type() - ) - { - errors.emplace_back( - n.position(), - "a type scope variable must have a declared type" - ); - return false; - } - - // A 'this' declaration must be an ordinary parameter or a type-scope object - if (n.identifier && *n.identifier->identifier == "this") - { - if ( - n.is_template_parameter - || ( - !n.is_parameter - && !n.parent_is_type() - ) - ) - { - errors.emplace_back( - n.identifier->position(), - "'this' may only be declared as an ordinary function parameter or type-scope (base) object" - ); - return {}; - } - } - - { - auto this_index = n.index_of_parameter_named("this"); - auto that_index = n.index_of_parameter_named("that"); - - if (this_index >= 0) { - if (!n.parent_is_type()) { - errors.emplace_back( - n.position(), - "'this' must be the first parameter of a type-scope function" - ); - return false; - } - if (this_index != 0) { - errors.emplace_back( - n.position(), - "'this' must be the first parameter" - ); - return false; - } - } - - if (that_index >= 0) { - if (!n.parent_is_type()) { - errors.emplace_back( - n.position(), - "'that' must be the second parameter of a type-scope function" - ); - return false; - } - if (that_index != 1) { - errors.emplace_back( - n.position(), - "'that' must be the second parameter" - ); - return false; - } - } - } - - if ( - n.is_object() - && n.has_wildcard_type() - && n.parent_is_namespace() - ) - { - errors.emplace_back( - n.identifier->position(), - "namespace scope objects must have a concrete type, not a deduced type" - ); - return false; - } - - if ( - n.has_name("_") - && !n.is_object() - && !n.is_namespace() - && !n.is_object_alias() - ) - { - errors.emplace_back( - n.identifier->position(), - "'_' (wildcard) may not be the name of a function or type - it may only be used as the name of an anonymous object, object alias, or namespace" - ); - return false; - } - - if ( - n.has_name("this") - && n.parent_is_type() - ) - { - if (!n.is_object()) { - errors.emplace_back( - n.position(), - "a member named 'this' declares a base subobject, and must be followed by a base type name" - ); - return false; - } - - if ( - !n.is_public() - && !n.is_default_access() - ) - { - errors.emplace_back( - n.position(), - "a base type must be public (the default)" - ); - return false; - } - - if (n.has_wildcard_type()) - { - errors.emplace_back( - n.position(), - "a base type must be a specific type, not a deduced type (omitted or '_'-wildcarded)" - ); - return false; - } - } - - if ( - n.access != accessibility::default_ - && !n.parent_is_type() - ) - { - errors.emplace_back( - n.position(), - "an access-specifier is only allowed on a type-scope (member) declaration" - ); - return false; - } - - if (n.is_constructor()) - { - auto& func = std::get<declaration_node::a_function>(n.type); - assert( - func->parameters->ssize() > 0 - && (*func->parameters)[0]->has_name("this") - ); - if ((*func->parameters)[0]->is_polymorphic()) { - errors.emplace_back( - n.position(), - "a constructor may not be declared virtual, override, or final" - ); - return false; - } - } - - if ( - n.is_function() - && n.has_name() - && n.parent_is_function() - ) - { - assert (n.identifier->get_token()); - auto name = n.identifier->get_token()->to_string(); - errors.emplace_back( - n.position(), - "(temporary alpha limitation) local functions like '" + name + ": (/*params*/) = {/*body*/}' are not currently supported - write a local variable initialized with an unnamed function like '" + name + " := :(/*params*/) = {/*body*/};' instead (add '=' and ';')" - ); - return false; - } - - // Ban overloading operators &&, ||, and , (comma) - if ( - n.identifier - && n.is_function() - && ( - n.has_name("operator&&") - || n.has_name("operator||") - || (n.has_name("operator&") && n.parameter_count() < 2) - || n.has_name("operator,") - ) - ) - { - errors.emplace_back( - n.position(), - "overloading '" + n.name()->to_string() + "' is not allowed" - ); - return false; - } - - // Require that ~/comparison/assignment operators must be members - if ( - n.identifier - && !n.is_function_with_this() - && ( - // Note re comparisons: The reason I'm restricting comparisons to be members - // is because with comparison symmetry (since C++20, derived from Cpp2) - // there's no longer a need for a type author to write them as nonmembers, - // and I want to discourage that habit by banning nonmembers. However, there - // could be a motivation to write them as nonmembers in the case where the - // type author doesn't provide them -- if that turns out to be important we - // can remove the restriction on nonmember comparisons here - n.is_comparison() - - // The following would be rejected anyway by the Cpp1 compiler, - // but including them here gives nicer and earlier error messages - || n.has_name("operator~") - || n.is_compound_assignment() - ) - ) - { - errors.emplace_back( - n.position(), - n.name()->to_string() + " must have 'this' as the first parameter" - ); - return false; - } - - // If this is the main function, it must be 'main: ()' or 'main: (args)' - if ( - n.identifier - && n.has_name("main") - && n.is_function() - && n.is_global() - ) - { - auto& func = std::get<declaration_node::a_function>(n.type); - auto& params = func->parameters->parameters; - - // It's more readable to express this as positive condition here... - if ( - // There are no parameters - params.empty() - // Or there's a single wildcard in-param named 'args' - || ( - params.size() == 1 - && params[0]->has_name("args") - && params[0]->pass == passing_style::in - && params[0]->declaration->is_object() - && std::get<declaration_node::an_object>(params[0]->declaration->type)->is_wildcard() - ) - ) - { - ; // ok - } - // ... and if it isn't that, then complain - else - { - errors.emplace_back( - params[0]->position(), - "'main' must be declared as 'main: ()' with zero parameters, or 'main: (args)' with one parameter named 'args' for which the type 'std::vector<std::string_view>' will be deduced" - ); - return false; - } - } - - if (n.has_name("operator=")) - { - if (!n.is_function()) - { - errors.emplace_back( - n.position(), - "'operator=' must be a function" - ); - return false; - } - auto& func = std::get<declaration_node::a_function>(n.type); - - if (func->has_declared_return_type()) - { - errors.emplace_back( - func->parameters->parameters[0]->position(), - "'operator=' may not have a declared return type" - ); - return false; - } - - if (func->parameters->ssize() == 0) - { - errors.emplace_back( - n.position(), - "an operator= function must have a parameter" - ); - return false; - } - else if ( - (*func->parameters)[0]->has_name("this") - && (*func->parameters)[0]->pass != passing_style::inout - && (*func->parameters)[0]->pass != passing_style::out - && (*func->parameters)[0]->pass != passing_style::move - ) - { - errors.emplace_back( - n.position(), - "an operator= function's 'this' parameter must be inout, out, or move" - ); - return false; - } - - if ( - func->parameters->ssize() > 1 - && (*func->parameters)[1]->has_name("that") - && (*func->parameters)[1]->pass != passing_style::in - && (*func->parameters)[1]->pass != passing_style::move - ) - { - errors.emplace_back( - n.position(), - "an operator= function's 'that' parameter must be in or move" - ); - return false; - } - - if ( - func->parameters->ssize() > 1 - && (*func->parameters)[0]->has_name("this") - && (*func->parameters)[0]->pass == passing_style::move - ) - { - errors.emplace_back( - n.position(), - "a destructor may not have other parameters besides 'this'" - ); - return false; - } - } - - for (auto& decl : n.get_type_scope_declarations()) - { - if (decl->has_name("that")) - { - errors.emplace_back( - n.position(), - "'that' may not be used as a type scope name" - ); - return false; - } - } - - if ( - n.is_binary_comparison_function() - && !n.has_bool_return_type() - ) - { - errors.emplace_back( - n.position(), - n.name()->to_string() + " must return bool" - ); - return false; - } - - if (n.has_name("operator<=>")) { - auto return_name = n.unnamed_return_type_to_string(); - if ( - return_name != "_" - && return_name.find("strong_ordering" ) == return_name.npos - && return_name.find("weak_ordering" ) == return_name.npos - && return_name.find("partial_ordering") == return_name.npos - ) - { - errors.emplace_back( - n.position(), - "operator<=> must return std::strong_ordering, std::weak_ordering, or std::partial_ordering" - ); - return false; - } - } - - if (n.is_type()) { - auto compound_stmt = n.initializer->get_if<compound_statement_node>(); - assert (compound_stmt); - for (auto& stmt : compound_stmt->statements) { - if ( - !stmt->is_declaration() - && !stmt->is_using() - ) - { - errors.emplace_back( - stmt->position(), - "a user-defined type body must contain only declarations or 'using' statements, not other code" - ); - return false; - } - } - } - - return true; - } - - - auto check(function_type_node const& n) - -> bool - { - assert(n.parameters); - - // An increment/decrement function must have a single 'inout' parameter, - // and if it's a member flag it if we know the type is not copyable - if ( - n.my_decl->has_name("operator++") - || n.my_decl->has_name("operator--") - ) - { - if ( - (*n.parameters).ssize() != 1 - || (*n.parameters)[0]->direction() != passing_style::inout - ) - { - errors.emplace_back( - n.position(), - "a user-defined " + n.my_decl->name()->to_string() + " must have a single 'inout' parameter" - ); - return false; - } - - if (n.has_deduced_return_type()) { - errors.emplace_back( - n.position(), - "a user-defined " + n.my_decl->name()->to_string() + " must have a specific (not deduced) return type" - ); - return false; - } - - if ( - n.my_decl->parent_declaration - && n.my_decl->parent_declaration->cannot_be_a_copy_constructible_type() - ) - { - errors.emplace_back( - n.position(), - "a user-defined " + n.my_decl->name()->to_string() + " in type scope must be a member of a copyable type" - ); - return false; - } - } - - return true; - } - - - auto check(statement_node const& n) - -> bool - { - if (auto expr_stmt = n.get_if<expression_statement_node>(); - expr_stmt - && n.compound_parent - && ( - expr_stmt->expr->is_identifier() - || expr_stmt->expr->is_id_expression() - || expr_stmt->expr->is_literal() - ) - ) - { - errors.emplace_back( - n.position(), - "unused literal or identifier" - ); - return false; - } - - return true; - } - - - //----------------------------------------------------------------------- - // Visitor functions - // - int scope_depth = 0; - bool started_standalone_assignment_expression = false; - bool started_postfix_expression = false; - bool is_out_expression = false; - bool inside_next_expression = false; - bool inside_parameter_list = false; - bool inside_parameter_identifier = false; - bool inside_returns_list = false; - bool just_entered_for = false; - parameter_declaration_node const* inside_out_parameter = {}; - - auto start(next_expression_tag const&, int) -> void - { - inside_next_expression = true; - } - - auto end(next_expression_tag const&, int) -> void - { - inside_next_expression = false; - } - - auto start(parameter_declaration_list_node const&, int) -> void - { - inside_parameter_list = true; - } - - auto end(parameter_declaration_list_node const&, int) -> void - { - inside_parameter_list = false; - } - - auto start(declaration_identifier_tag const&, int) -> void - { - inside_parameter_identifier = inside_parameter_list; - } - - auto end(declaration_identifier_tag const&, int) -> void - { - inside_parameter_identifier = false; - } - - auto start(parameter_declaration_node const& n, int) -> void - { - if ( - // If it's an 'out' parameter - ( - !inside_returns_list - && n.pass == passing_style::out - ) - // Or it's an uninitialized 'out' return value - || ( - inside_returns_list - && n.pass == passing_style::out - && !n.declaration->initializer - ) - ) - { - inside_out_parameter = &n; - } - - if ( - n.pass == passing_style::copy - || n.pass == passing_style::move - || n.pass == passing_style::forward - ) - { - // Handle variables in unnamed functions. For such cases scope_depth is increased by +1 - auto depth = scope_depth + ((n.declaration->parent_is_function() && n.declaration->parent_declaration->name() == nullptr) ? 1 : 0 ); - symbols.emplace_back( depth, declaration_sym( true, n.declaration.get(), n.declaration->name(), n.declaration->initializer.get(), &n)); - } - } - - auto end(parameter_declaration_node const&, int) -> void - { - inside_out_parameter = {}; - } - - auto start(expression_list_node::term const&n, int) -> void - { - is_out_expression = (n.pass == passing_style::out); - } - - auto start(function_returns_tag const&, int) -> void - { - inside_returns_list = true; - } - - auto end(function_returns_tag const&, int) -> void - { - inside_returns_list = false; - } - - auto start(loop_body_tag const &n, int) -> void - { - if (*n.identifier == "for") { - just_entered_for = true; - } - } - - auto start(declaration_node const& n, int) -> void - { - // Skip the first declaration after entering a 'for', - // which is the for loop parameter - it's always - // guaranteed to be initialized by the language - if (just_entered_for) { - just_entered_for = false; - return; - } - - if ( - !n.is_alias() - // Skip type scope (member) variables - && !(n.parent_is_type() && n.is_object()) - // Skip unnamed variables - && n.identifier - // Skip non-out parameters - && ( - !inside_parameter_list - || inside_out_parameter - ) - ) - { - symbols.emplace_back( scope_depth, declaration_sym( true, &n, n.name(), n.initializer.get(), inside_out_parameter ) ); - if (!n.is_object()) { - ++scope_depth; - } - } - } - - auto end(declaration_node const& n, int) -> void - { - if ( - !n.is_alias() - // Skip type scope (member) variables - && !(n.parent_is_type() && n.is_object()) - // Skip unnamed variables - && n.identifier - // Skip non-out parameters - && ( - !inside_parameter_list - || inside_out_parameter - ) - ) - { - symbols.emplace_back( scope_depth, declaration_sym( false, &n, nullptr, nullptr, inside_out_parameter ) ); - if (!n.is_object()) { - --scope_depth; - } - } - } - - auto start(token const& t, int) -> void - { - // We currently only care to look at identifiers - if (t.type() != lexeme::Identifier) { - return; - } - - // If this is the first identifier since we started a new assignment, - // expression, then it's the left-hand side (target) of the assignment - else if (started_standalone_assignment_expression) - { - symbols.emplace_back( scope_depth, identifier_sym( true, &t ) ); - started_standalone_assignment_expression = false; // we were the consumer for this information - } - - // If this is the first identifier since we saw an `out` expression, - // then it's the argument of the `out` expression - // TODO: for now we just take the first identifier, and we should make - // this an id-expression and add a sema rule to disallow complex expressions - else if (is_out_expression) - { - symbols.emplace_back( scope_depth, identifier_sym( true, &t ) ); - is_out_expression = false; - } - - // Otherwise it's just an identifier use (if it's not a parameter name) and - // it's the first identifier of a postfix_expressions (not a member name or something else) - else if (started_postfix_expression) - { - started_postfix_expression = false; - if (!inside_parameter_identifier && !inside_next_expression) - { - // Put this into the table if it's a use of an object in scope - // or it's a 'copy' parameter (but to be a use it must be after - // the declaration, not the token in the decl's name itself) - if (auto decl = get_declaration_of(t); - decl - && decl->declaration->name() != &t - ) - { - symbols.emplace_back( scope_depth, identifier_sym( false, &t ) ); - } - } - } - } - - auto start(selection_statement_node const& n, int) -> void - { - active_selections.push_back( &n ); - symbols.emplace_back( scope_depth, selection_sym{ true, active_selections.back() } ); - ++scope_depth; - } - - auto end(selection_statement_node const&, int) -> void - { - symbols.emplace_back( scope_depth, selection_sym{ false, active_selections.back() } ); - active_selections.pop_back(); - --scope_depth; - } - - auto kind_of(compound_statement_node const& n) - -> compound_sym::kind - { - auto kind = compound_sym::is_scope; - if (!active_selections.empty()) - { - assert(active_selections.back()->true_branch); - if (active_selections.back()->true_branch.get() == &n) - { - kind = compound_sym::is_true; - } - if ( - active_selections.back()->false_branch - && active_selections.back()->false_branch.get() == &n - ) - { - kind = compound_sym::is_false; - } - } - return kind; - } - - auto start(compound_statement_node const& n, int) -> void - { - symbols.emplace_back( - scope_depth, - compound_sym{ true, &n, kind_of(n) } - ); - ++scope_depth; - } - - auto end(compound_statement_node const& n, int) -> void - { - symbols.emplace_back( - scope_depth, - compound_sym{ false, &n, kind_of(n) } - ); - --scope_depth; - } - - auto start(assignment_expression_node const& n, int) - { - if ( - n.is_standalone_expression() - && n.lhs_is_id_expression() - && std::ssize(n.terms) > 0 - ) - { - assert (n.terms.front().op); - if (n.terms.front().op->type() == lexeme::Assignment) { - started_standalone_assignment_expression = true; - } - } - } - - auto start(postfix_expression_node const&, int) { - started_postfix_expression = true; - } - - auto start(auto const&, int) -> void - { - // Ignore other node types - } - - auto end(auto const&, int) -> void - { - // Ignore other node types - } -}; - - -} - -#endif 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 diff --git a/64x0/cc2/source/version.info b/64x0/cc2/source/version.info deleted file mode 100644 index 87fe59a..0000000 --- a/64x0/cc2/source/version.info +++ /dev/null @@ -1 +0,0 @@ -"v0.3.0"
\ No newline at end of file |
