aboutsummaryrefslogtreecommitdiff
path: root/libbuild2
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2020-06-05 09:02:05 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2020-06-05 09:04:29 +0200
commite4a9ccadf751b88f5508ce9f890484bae33d1aaf (patch)
tree7c3045ce2a7dc450f4271a6f8fc7e7cc32fa1019 /libbuild2
parent9ec2bdd87659438b4aa021a10c4a4977ef77118e (diff)
Add ability to split ad hoc C++ recipe into global and local fragments
Specifically, now we can write: {{ c++ 1 -- #include <map> -- recipe apply (action, target&) const override { ... } }}
Diffstat (limited to 'libbuild2')
-rw-r--r--libbuild2/parser.cxx40
-rw-r--r--libbuild2/rule.cxx116
-rw-r--r--libbuild2/rule.hxx5
3 files changed, 139 insertions, 22 deletions
diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx
index 6fb20fe..4b958e8 100644
--- a/libbuild2/parser.cxx
+++ b/libbuild2/parser.cxx
@@ -1103,6 +1103,10 @@ namespace build2
optional<string> lang;
location lloc;
+
+ // Use value mode to minimize the number of special characters.
+ //
+ mode (lexer_mode::value, '@');
if (next (t, tt) == type::newline)
;
else if (tt == type::word)
@@ -1132,12 +1136,12 @@ namespace build2
//
ar.reset (new adhoc_script_rule (loc, st.value.size ()));
}
- else if (*lang == "c++")
+ else if (icasecmp (*lang, "c++") == 0)
{
// C++
//
- // Parse recipe version.
+ // Parse recipe version and optional fragment separator.
//
if (tt == type::newline || tt == type::eos)
fail (t) << "expected c++ recipe version instead of " << t;
@@ -1145,18 +1149,42 @@ namespace build2
location nloc (get_location (t));
names ns (parse_names (t, tt, pattern_mode::ignore));
- uint64_t v;
+ uint64_t ver;
try
{
- v = convert<uint64_t> (move (ns));
+ if (ns.empty ())
+ throw invalid_argument ("empty");
+
+ if (ns[0].pair)
+ throw invalid_argument ("pair in value");
+
+ ver = convert<uint64_t> (move (ns[0]));
+ }
+ catch (const invalid_argument& e)
+ {
+ fail (nloc) << "invalid c++ recipe version: " << e << endf;
+ }
+
+ optional<string> sep;
+ if (ns.size () != 1)
+ try
+ {
+ if (ns.size () != 2)
+ throw invalid_argument ("multiple names");
+
+ sep = convert<string> (move (ns[1]));
+
+ if (sep->empty ())
+ throw invalid_argument ("empty");
}
catch (const invalid_argument& e)
{
- fail (nloc) << "invalid c++ recipe version value: " << e
+ fail (nloc) << "invalid c++ recipe fragment separator: " << e
<< endf;
}
- ar.reset (new adhoc_cxx_rule (loc, st.value.size (), v));
+ ar.reset (
+ new adhoc_cxx_rule (loc, st.value.size (), ver, move (sep)));
}
else
fail (lloc) << "unknown recipe language '" << *lang << "'";
diff --git a/libbuild2/rule.cxx b/libbuild2/rule.cxx
index 0b9f066..d9c07cb 100644
--- a/libbuild2/rule.cxx
+++ b/libbuild2/rule.cxx
@@ -786,8 +786,8 @@ namespace build2
// adhoc_cxx_rule
//
adhoc_cxx_rule::
- adhoc_cxx_rule (const location& l, size_t b, uint64_t v)
- : adhoc_rule (l, b), version (v), impl (nullptr)
+ adhoc_cxx_rule (const location& l, size_t b, uint64_t v, optional<string> s)
+ : adhoc_rule (l, b), version (v), separator (move (s)), impl (nullptr)
{
if (v != 1)
fail (l) << "unsupported c++ recipe version " << v;
@@ -905,6 +905,7 @@ namespace build2
{
sha256 cs;
cs.append ("c++");
+ cs.append (separator ? *separator : "");
cs.append (code);
id = cs.abbreviated_string (12);
}
@@ -948,6 +949,72 @@ namespace build2
}
};
+ // Calculate (and cache) the global/local fragments split.
+ //
+ struct fragments
+ {
+ size_t global_p; // Start position.
+ size_t global_n; // Length (0 if no global fragment).
+ location global_l; // Position.
+
+ size_t local_p;
+ size_t local_n;
+ location local_l;
+ };
+
+ auto split = [this, f = optional<fragments> ()] () mutable ->
+ const fragments&
+ {
+ if (f)
+ return *f;
+
+ // Note that the code starts from the next line thus +1.
+ //
+ location gl (loc.file, loc.line + 1, 1);
+
+ if (!separator)
+ {
+ f = fragments {0, 0, location (), 0, code.size (), gl};
+ return *f;
+ }
+
+ // Iterate over lines (keeping track of the current line) looking
+ // for the separator.
+ //
+ uint64_t l (gl.line);
+ for (size_t b (0), e (b), n (code.size ()); b < n; b = e + 1, l++)
+ {
+ if ((e = code.find ('\n', b)) == string::npos)
+ e = n;
+
+ // Trim the line.
+ //
+ size_t tb (b), te (e);
+ auto ws = [] (char c) {return c == ' ' || c == '\t' || c == '\r';};
+ for (; tb != te && ws (code[tb ]); ++tb) ;
+ for (; te != tb && ws (code[te - 1]); --te) ;
+
+ // text << "'" << string (code, tb, te - tb) << "'";
+
+ if (code.compare (tb, te - tb, *separator) == 0)
+ {
+ // End the global fragment at the previous newline and start the
+ // local fragment at the beginning of the next line.
+ //
+ location ll (loc.file, l + 1, 1);
+
+ if (++e >= n)
+ fail (ll) << "empty c++ recipe local fragment";
+
+ f = fragments {0, b, gl, e, n - e, ll};
+ return *f;
+ }
+ }
+
+ fail (loc) << "c++ recipe fragment separator '" << *separator
+ << "' not found" << endf;
+ };
+
bool nested (ctx.module_context == &ctx);
// Create the build context if necessary.
@@ -993,6 +1060,8 @@ namespace build2
if (create)
try
{
+ const fragments& frag (split ());
+
// Write ad hoc config.build that loads the ~build2 configuration.
// This way the configuration will be always in sync with ~build2
// and we can update the recipe manually (e.g., for debugging).
@@ -1043,6 +1112,21 @@ namespace build2
<< "#include <libbuild2/diagnostics.hxx>" << '\n'
<< '\n';
+ // Write the global fragment, if any. Note that it always includes the
+ // trailing newline.
+ //
+ if (frag.global_n != 0)
+ {
+ // Use the #line directive to point diagnostics to the code in the
+ // buildfile. Note that there is no easy way to restore things to
+ // point back to the source file (other than another #line with a
+ // line and a file). Let's not bother for now.
+ //
+ ofs << "#line RECIPE_GLOBAL_LINE RECIPE_FILE" << '\n';
+ ofs.write (code.c_str () + frag.global_p, frag.global_n);
+ ofs << '\n';
+ }
+
// Normally the recipe code will have one level of indentation so
// let's not indent the namespace level to match.
//
@@ -1090,17 +1174,14 @@ namespace build2
<< '\n';
// Use the #line directive to point diagnostics to the code in the
- // buildfile. Note that there is no easy way to restore things to
- // point back to the source file (other than another #line with a line
- // and a file). Seeing that we don't have much after, let's not bother
- // for now.
+ // buildfile similar to the global fragment above.
//
- ofs << "#line RECIPE_LINE RECIPE_FILE" << '\n';
+ ofs << "#line RECIPE_LOCAL_LINE RECIPE_FILE" << '\n';
- // Note that the code always includes trailing newline.
+ // Note that the local fragment always includes the trailing newline.
//
- ofs << code
- << "};" << '\n'
+ ofs.write (code.c_str () + frag.local_p, frag.local_n);
+ ofs << "};" << '\n'
<< '\n';
// Add an alias that we can use unambiguously in the load function.
@@ -1193,6 +1274,8 @@ namespace build2
if (!check_sig (of, lsig))
try
{
+ const fragments& frag (split ());
+
entry_time et (file_time (of));
if (verb >= verbosity)
@@ -1200,12 +1283,15 @@ namespace build2
ofs.open (of);
- // Recipe file and line for the #line directive above. Note that the
- // code starts from the next line thus +1. We also need to escape
- // backslashes (Windows paths).
+ // Recipe file and line for the #line directive above. We also need
+ // to escape backslashes (Windows paths).
//
- ofs << "#define RECIPE_FILE \"" << sanitize_strlit (lf) << '"'<< '\n'
- << "#define RECIPE_LINE " << loc.line + 1 << '\n'
+ ofs << "#define RECIPE_FILE \"" << sanitize_strlit (lf) << '"'<< '\n';
+
+ if (frag.global_n != 0)
+ ofs << "#define RECIPE_GLOBAL_LINE " << frag.global_l.line << '\n';
+
+ ofs << "#define RECIPE_LOCAL_LINE " << frag.local_l.line << '\n'
<< '\n'
<< lsig << '\n';
diff --git a/libbuild2/rule.hxx b/libbuild2/rule.hxx
index c98c29e..aa2f003 100644
--- a/libbuild2/rule.hxx
+++ b/libbuild2/rule.hxx
@@ -253,7 +253,9 @@ namespace build2
virtual recipe
apply (action, target&) const override;
- adhoc_cxx_rule (const location&, size_t, uint64_t version);
+ adhoc_cxx_rule (const location&, size_t,
+ uint64_t ver,
+ optional<string> sep);
virtual bool
recipe_text (context&, const target&, string&& t, attributes&) override;
@@ -269,6 +271,7 @@ namespace build2
// targets which could all be matched in parallel.
//
uint64_t version;
+ optional<string> separator;
string code;
mutable atomic<cxx_rule*> impl;
};