Skip to content

Commit 19133a6

Browse files
committed
feat: allow authoring module units
1 parent 9221682 commit 19133a6

File tree

4 files changed

+189
-45
lines changed

4 files changed

+189
-45
lines changed

regression-tests/test-results/pure2-bugfix-for-requires-clause-unbraced-function-initializer.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
//=== Cpp2 type definitions and function declarations ===========================
1212

1313
#line 1 "pure2-bugfix-for-requires-clause-unbraced-function-initializer.cpp2"
14-
template<typename T> auto f() -> void;
14+
template<typename T> auto f() -> void
15+
CPP2_REQUIRES (std::regular<T>)
16+
#line 1 "pure2-bugfix-for-requires-clause-unbraced-function-initializer.cpp2"
17+
;
1518
auto main() -> int;
1619

1720

source/common.h

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ struct source_line
4848
{
4949
std::string text;
5050

51-
enum class category { empty, preprocessor, comment, import, cpp1, cpp2, rawstring };
51+
enum class category { empty, preprocessor, comment, module_directive, module_declaration, import, cpp1, cpp2, rawstring };
5252
category cat;
5353

5454
bool all_tokens_are_densely_spaced = true; // to be overridden in lexing if they're not
@@ -73,13 +73,15 @@ struct source_line
7373
-> std::string
7474
{
7575
switch (cat) {
76-
break;case category::empty: return "/* */ ";
77-
break;case category::preprocessor: return "/* # */ ";
78-
break;case category::comment: return "/* / */ ";
79-
break;case category::import: return "/* i */ ";
80-
break;case category::cpp1: return "/* 1 */ ";
81-
break;case category::cpp2: return "/* 2 */ ";
82-
break;case category::rawstring: return "/* R */ ";
76+
break;case category::empty: return "/* */ ";
77+
break;case category::preprocessor: return "/* # */ ";
78+
break;case category::comment: return "/* / */ ";
79+
break;case category::module_directive: return "/* m#*/ ";
80+
break;case category::module_declaration: return "/* m */ ";
81+
break;case category::import: return "/* i */ ";
82+
break;case category::cpp1: return "/* 1 */ ";
83+
break;case category::cpp2: return "/* 2 */ ";
84+
break;case category::rawstring: return "/* R */ ";
8385
break;default: assert(!"illegal category"); abort();
8486
}
8587
}
@@ -127,7 +129,7 @@ struct string_parts {
127129

128130
string_parts(const std::string& beginseq,
129131
const std::string& endseq,
130-
adds_sequences strateg)
132+
adds_sequences strateg)
131133
: begin_seq{beginseq}
132134
, end_seq{endseq}
133135
, strategy{strateg}
@@ -144,16 +146,16 @@ struct string_parts {
144146
void clear() { parts.clear(); }
145147

146148
auto generate() const -> std::string {
147-
148-
if (parts.empty()) {
149-
return (strategy & on_the_beginning ? begin_seq : std::string{})
150-
+ (strategy & on_the_end ? end_seq : std::string{});
149+
150+
if (parts.empty()) {
151+
return (strategy & on_the_beginning ? begin_seq : std::string{})
152+
+ (strategy & on_the_end ? end_seq : std::string{});
151153
}
152154

153-
auto result = std::visit(begin_visit{begin_seq, strategy},
155+
auto result = std::visit(begin_visit{begin_seq, strategy},
154156
parts.front());
155157

156-
if (std::ssize(parts) > 1) {
158+
if (std::ssize(parts) > 1) {
157159
auto it1 = parts.cbegin();
158160
auto it2 = parts.cbegin()+1;
159161
for(;it2 != parts.cend(); ++it1, ++it2) {

source/cppfront.cpp

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,6 +1223,17 @@ class cppfront
12231223
// Generate a reasonable macroized name
12241224
auto cpp1_FILENAME = to_upper_and_underbar(cpp1_filename);
12251225

1226+
lineno_t curr_lineno = 0;
1227+
auto hpp_includes = std::string{};
1228+
1229+
auto print_cpp2util = [&](bool in_module_only) {
1230+
if (
1231+
in_module_only == source.is_module()
1232+
&& !tokens.get_map().empty()
1233+
) {
1234+
printer.print_extra( "\n#include \"cpp2util.h\"\n\n" );
1235+
}
1236+
};
12261237

12271238
//---------------------------------------------------------------------
12281239
// Do lowered file prolog
@@ -1236,6 +1247,12 @@ class cppfront
12361247
printer.print_extra( "#ifndef " + cpp1_FILENAME+"__CPP2\n");
12371248
printer.print_extra( "#define " + cpp1_FILENAME+"__CPP2" + "\n\n" );
12381249
}
1250+
else if (
1251+
source.is_module()
1252+
&& !source.has_module_directive()
1253+
) {
1254+
printer.print_extra( "module;\n" );
1255+
}
12391256

12401257
if (flag_use_source_location) {
12411258
printer.print_extra( "#define CPP2_USE_SOURCE_LOCATION Yes\n" );
@@ -1249,11 +1266,84 @@ class cppfront
12491266
if (flag_no_rtti) {
12501267
printer.print_extra( "#define CPP2_NO_RTTI Yes\n" );
12511268
}
1269+
print_cpp2util(true);
1270+
1271+
// Module lines.
1272+
auto printed_module_directive = false;
1273+
for (auto const& line : source.get_module_lines())
1274+
{
1275+
// Skip dummy line we added to make 0-vs-1-based offsets readable
1276+
if (curr_lineno != 0)
1277+
{
1278+
assert(line.cat != source_line::category::cpp2);
1279+
1280+
if (
1281+
source.has_cpp2()
1282+
&& line.cat == source_line::category::empty
1283+
)
1284+
{
1285+
++ret.cpp2_lines;
1286+
}
1287+
else
1288+
{
1289+
++ret.cpp1_lines;
1290+
}
1291+
1292+
if (
1293+
flag_cpp2_only
1294+
&& !line.text.empty()
1295+
&& line.cat != source_line::category::comment
1296+
&& line.cat != source_line::category::module_directive
1297+
&& line.cat != source_line::category::module_declaration
1298+
&& line.cat != source_line::category::import
1299+
)
1300+
{
1301+
if (line.cat == source_line::category::preprocessor) {
1302+
if (!line.text.ends_with(".h2\"")) {
1303+
errors.emplace_back(
1304+
source_position(curr_lineno, 1),
1305+
"pure-cpp2 switch disables the preprocessor, including #include (except of .h2 files) - use import instead (note: 'import std;' is implicit in -pure-cpp2)"
1306+
);
1307+
return {};
1308+
}
1309+
}
1310+
else {
1311+
errors.emplace_back(
1312+
source_position(curr_lineno, 1),
1313+
"pure-cpp2 switch disables Cpp1 syntax"
1314+
);
1315+
return {};
1316+
}
1317+
}
1318+
1319+
if (
1320+
line.cat == source_line::category::preprocessor
1321+
&& line.text.ends_with(".h2\"")
1322+
)
1323+
{
1324+
// Strip off the 2"
1325+
auto h_include = line.text.substr(0, line.text.size()-2);
1326+
printer.print_cpp1( h_include + "\"", curr_lineno );
1327+
hpp_includes += h_include + "pp\"\n";
1328+
}
1329+
else {
1330+
printer.print_cpp1( line.text, curr_lineno );
1331+
1332+
if (
1333+
!printed_module_directive
1334+
&& line.cat == source_line::category::module_directive
1335+
)
1336+
{
1337+
printed_module_directive = true;
1338+
print_cpp2util(true);
1339+
}
1340+
}
1341+
}
1342+
++curr_lineno;
1343+
}
12521344
}
12531345

12541346
auto map_iter = tokens.get_map().cbegin();
1255-
auto hpp_includes = std::string{};
1256-
12571347

12581348
//---------------------------------------------------------------------
12591349
// Do phase0_type_decls
@@ -1267,10 +1357,7 @@ class cppfront
12671357
printer.print_extra( "\n//=== Cpp2 type declarations ====================================================\n\n" );
12681358
}
12691359

1270-
if (!tokens.get_map().empty())
1271-
{
1272-
printer.print_extra( "\n#include \"cpp2util.h\"\n\n" );
1273-
}
1360+
print_cpp2util(false);
12741361

12751362
for (auto& section : tokens.get_map())
12761363
{
@@ -1300,10 +1387,7 @@ class cppfront
13001387
}
13011388

13021389
assert (printer.get_phase() == positional_printer::phase1_type_defs_func_decls);
1303-
for (
1304-
lineno_t curr_lineno = 0;
1305-
auto const& line : source.get_lines()
1306-
)
1390+
for (auto const& line : source.get_non_module_lines())
13071391
{
13081392
// Skip dummy line we added to make 0-vs-1-based offsets readable
13091393
if (curr_lineno != 0)
@@ -1327,6 +1411,8 @@ class cppfront
13271411
flag_cpp2_only
13281412
&& !line.text.empty()
13291413
&& line.cat != source_line::category::comment
1414+
&& line.cat != source_line::category::module_directive
1415+
&& line.cat != source_line::category::module_declaration
13301416
&& line.cat != source_line::category::import
13311417
)
13321418
{

source/io.h

Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include <ostream>
2424
#include <iterator>
2525
#include <cctype>
26+
#include <cstddef>
27+
#include <span>
2628

2729

2830
namespace cpp2 {
@@ -107,29 +109,35 @@ auto is_preprocessor(
107109

108110

109111
//---------------------------------------------------------------------------
110-
// starts_with_import: returns whether the line starts with "import"
112+
// starts_with_tokens: returns whether the line starts with the tokens
111113
//
112114
// line current line being processed
113115
//
114-
auto starts_with_import(std::string const& line)
116+
auto starts_with_tokens(std::string const& line, std::initializer_list<std::string_view> const tokens)
115117
-> bool
116118
{
117119
auto i = 0;
118120

119-
// find first non-whitespace character
120-
if (!move_next(line, i, isspace)) {
121-
return false;
122-
}
121+
for (auto token: tokens) {
122+
// find first non-whitespace character
123+
if (!move_next(line, i, isspace)) {
124+
return false;
125+
}
123126

124-
static constexpr auto import_keyword = std::string_view{"import"};
127+
// now must begin with the token
128+
if (!std::string_view(line).substr(i).starts_with(token)) {
129+
return false;
130+
}
125131

126-
// the first token must begin with 'import'
127-
if (!std::string_view(line).substr(i).starts_with(import_keyword)) {
128-
return false;
132+
// and not be immediately followed by an _identifier-continue_
133+
if (is_identifier_continue(line[i + token.size()])) {
134+
return false;
135+
}
136+
137+
i += token.size();
129138
}
130139

131-
// and not be immediately followed by an _identifier-continue_
132-
return !is_identifier_continue(line[i + import_keyword.size()]);
140+
return true;
133141
}
134142

135143

@@ -301,6 +309,9 @@ auto starts_with_identifier_colon(std::string const& line)
301309
else if (s.starts_with("private")) {
302310
j += 7;
303311
}
312+
else if (s.starts_with("export")) {
313+
j += 6;
314+
}
304315
while (
305316
j < std::ssize(s)
306317
&& isspace(s[j])
@@ -623,7 +634,7 @@ auto process_cpp_line(
623634
}
624635
}
625636
}
626-
637+
627638
break;case '\"':
628639
// If this isn't an escaped quote, toggle string literal state
629640
if (
@@ -772,8 +783,10 @@ class source
772783
{
773784
std::vector<error_entry>& errors;
774785
std::vector<source_line> lines;
775-
bool cpp1_found = false;
776-
bool cpp2_found = false;
786+
std::ptrdiff_t module_lines = 0;
787+
bool module_directive_found = false;
788+
bool cpp1_found = false;
789+
bool cpp2_found = false;
777790

778791
static const int max_line_len = 90'000;
779792
// do not reduce this - I encountered an 80,556-char
@@ -796,9 +809,27 @@ class source
796809
}
797810

798811

812+
//-----------------------------------------------------------------------
813+
// is_module: Returns true if this file is a module unit
814+
// (note: module, export, and import lines don't count toward Cpp1 or Cpp2)
815+
//
816+
auto is_module() const -> bool {
817+
return module_lines != 0;
818+
}
819+
820+
821+
//-----------------------------------------------------------------------
822+
// has_module_directive: Returns true if this file has a module directive
823+
// (note: module, export, and import lines don't count toward Cpp1 or Cpp2)
824+
//
825+
auto has_module_directive() const -> bool {
826+
return module_directive_found;
827+
}
828+
829+
799830
//-----------------------------------------------------------------------
800831
// has_cpp1: Returns true if this file has some Cpp1/preprocessor lines
801-
// (note: import lines don't count toward Cpp1 or Cpp2)
832+
// (note: module, export, and import lines don't count toward Cpp1 or Cpp2)
802833
//
803834
auto has_cpp1() const -> bool {
804835
return cpp1_found;
@@ -807,7 +838,7 @@ class source
807838

808839
//-----------------------------------------------------------------------
809840
// has_cpp2: Returns true if this file has some Cpp2 lines
810-
// (note: import lines don't count toward Cpp1 or Cpp2)
841+
// (note: module, export, and import lines don't count toward Cpp1 or Cpp2)
811842
//
812843
auto has_cpp2() const -> bool {
813844
return cpp2_found;
@@ -923,11 +954,23 @@ class source
923954
}
924955
}
925956

926-
// Else still in Cpp1 code, but could be a comment, empty, or import
957+
// Else still in Cpp1 code, but could be a comment, empty, module, export, or import
927958
//
928959
else
929960
{
930-
if (starts_with_import(lines.back().text)) {
961+
if (!is_module() && starts_with_tokens(lines.back().text, {"module", ";"})) {
962+
lines.back().cat = source_line::category::module_directive;
963+
module_directive_found = true;
964+
}
965+
else if (!is_module() && starts_with_tokens(lines.back().text, {"module"})) {
966+
lines.back().cat = source_line::category::module_declaration;
967+
module_lines = lines.size();
968+
}
969+
else if (!is_module() && starts_with_tokens(lines.back().text, {"export", "module"})) {
970+
lines.back().cat = source_line::category::module_declaration;
971+
module_lines = lines.size();
972+
}
973+
else if (starts_with_tokens(lines.back().text, {"import"})) {
931974
lines.back().cat = source_line::category::import;
932975
}
933976
else {
@@ -1002,6 +1045,16 @@ class source
10021045
return lines;
10031046
}
10041047

1048+
auto get_non_module_lines() const -> std::span<const source_line>
1049+
{
1050+
return std::span<const source_line>{lines.begin() + module_lines, lines.end()};
1051+
}
1052+
1053+
auto get_module_lines() const -> std::span<const source_line>
1054+
{
1055+
return std::span<const source_line>{lines.begin(), lines.begin() + module_lines};
1056+
}
1057+
10051058
//-----------------------------------------------------------------------
10061059
// debug_print
10071060
//

0 commit comments

Comments
 (0)