From fffb5932ee52127e03b9f7f5ccca83a421d69061 Mon Sep 17 00:00:00 2001 From: Ulya Trofimovich Date: Wed, 16 Mar 2016 09:41:02 +0000 Subject: [PATCH] Address skeleton nodes by indices rather than by pointers. This way it is more convenient to add various graph algorithms: each algorithm can keep its own data in array indexed by skeleton node numbers (instead of pushing all relevant data inside of the node). --- re2c/Makefile.am | 2 +- re2c/bootstrap/src/parse/lex.cc | 2 +- re2c/bootstrap/src/parse/parser.cc | 4 +- re2c/src/codegen/emit_action.cc | 2 +- re2c/src/codegen/emit_dfa.cc | 10 +- re2c/src/conf/warn.cc | 12 +- re2c/src/conf/warn.h | 3 +- re2c/src/ir/skeleton/control_flow.cc | 83 +++--- re2c/src/ir/skeleton/generate_code.cc | 90 +++--- re2c/src/ir/skeleton/generate_data.cc | 284 ++++++++++--------- re2c/src/ir/skeleton/maxlen.cc | 50 ---- re2c/src/ir/skeleton/maxpath.cc | 65 +++++ re2c/src/ir/skeleton/path.h | 44 +-- re2c/src/ir/skeleton/skeleton.cc | 146 ++++------ re2c/src/ir/skeleton/skeleton.h | 150 +++------- re2c/src/ir/skeleton/unreachable_nullable.cc | 78 +++-- re2c/src/parse/parser.ypp | 4 +- 17 files changed, 467 insertions(+), 562 deletions(-) delete mode 100644 re2c/src/ir/skeleton/maxlen.cc create mode 100644 re2c/src/ir/skeleton/maxpath.cc diff --git a/re2c/Makefile.am b/re2c/Makefile.am index 243bc036..8087739b 100644 --- a/re2c/Makefile.am +++ b/re2c/Makefile.am @@ -101,7 +101,7 @@ SRC = \ src/ir/skeleton/control_flow.cc \ src/ir/skeleton/generate_code.cc \ src/ir/skeleton/generate_data.cc \ - src/ir/skeleton/maxlen.cc \ + src/ir/skeleton/maxpath.cc \ src/ir/skeleton/skeleton.cc \ src/ir/skeleton/unreachable_nullable.cc \ src/main.cc \ diff --git a/re2c/bootstrap/src/parse/lex.cc b/re2c/bootstrap/src/parse/lex.cc index 8c5b6235..2b085838 100644 --- a/re2c/bootstrap/src/parse/lex.cc +++ b/re2c/bootstrap/src/parse/lex.cc @@ -1,4 +1,4 @@ -/* Generated by re2c 0.16 on Mon Mar 14 22:45:09 2016 */ +/* Generated by re2c 0.16 on Tue Mar 15 17:24:03 2016 */ #line 1 "../src/parse/lex.re" #include "src/util/c99_stdint.h" #include diff --git a/re2c/bootstrap/src/parse/parser.cc b/re2c/bootstrap/src/parse/parser.cc index 9f7e54ee..0a0ae13e 100644 --- a/re2c/bootstrap/src/parse/parser.cc +++ b/re2c/bootstrap/src/parse/parser.cc @@ -2260,7 +2260,7 @@ void parse(Scanner& i, Output & o) .wline_info (in->get_cline (), in->get_fname ().c_str ()); if (opts->target == opt_t::SKELETON) { - Skeleton::emit_prolog (o.source); + emit_prolog(o.source); } Enc encodingOld = opts->encoding; @@ -2429,7 +2429,7 @@ void parse(Scanner& i, Output & o) if (opts->target == opt_t::SKELETON) { - Skeleton::emit_epilog (o.source, o.skeletons); + emit_epilog(o.source, o.skeletons); } parse_cleanup(); diff --git a/re2c/src/codegen/emit_action.cc b/re2c/src/codegen/emit_action.cc index d091c55a..e41bbbaf 100644 --- a/re2c/src/codegen/emit_action.cc +++ b/re2c/src/codegen/emit_action.cc @@ -275,7 +275,7 @@ void emit_rule (OutputFile & o, uint32_t ind, const State * const s, const RuleI if (opts->target == opt_t::SKELETON) { - skeleton->emit_action (o, ind, rule->rank); + emit_action(*skeleton, o, ind, rule->rank); } else { diff --git a/re2c/src/codegen/emit_dfa.cc b/re2c/src/codegen/emit_dfa.cc index 16cccfa2..97b049b2 100644 --- a/re2c/src/codegen/emit_dfa.cc +++ b/re2c/src/codegen/emit_dfa.cc @@ -136,18 +136,18 @@ void DFA::emit(Output & output, uint32_t& ind, bool isLastCond, bool& bPrologBra head->action.set_initial (initial_label, head->action.type == Action::SAVE); - skeleton->warn_undefined_control_flow (); - skeleton->warn_unreachable_nullable_rules (); + warn_undefined_control_flow(*skeleton); + warn_unreachable_nullable_rules(*skeleton); if (opts->target == opt_t::SKELETON) { if (output.skeletons.insert (name).second) { - skeleton->emit_data (o.file_name); - skeleton->emit_start (o, max_fill, need_backup, need_backupctx, need_accept); + emit_data(*skeleton, o.file_name); + emit_start(*skeleton, o, max_fill, need_backup, need_backupctx, need_accept); uint32_t i = 2; emit_body (o, i, used_labels, initial_label); - skeleton->emit_end (o, need_backup, need_backupctx); + emit_end(*skeleton, o, need_backup, need_backupctx); } } else diff --git a/re2c/src/conf/warn.cc b/re2c/src/conf/warn.cc index 01f15915..275466d3 100644 --- a/re2c/src/conf/warn.cc +++ b/re2c/src/conf/warn.cc @@ -128,7 +128,7 @@ void Warn::swapped_range (uint32_t line, uint32_t l, uint32_t u) } } -void Warn::undefined_control_flow (uint32_t line, const std::string & cond, std::vector & paths, bool overflow) +void Warn::undefined_control_flow (const Skeleton &skel, std::vector & paths, bool overflow) { if (mask[UNDEFINED_CONTROL_FLOW] & WARNING) { @@ -136,21 +136,21 @@ void Warn::undefined_control_flow (uint32_t line, const std::string & cond, std: error_accuml |= e; // report shorter patterns first - std::sort (paths.begin (), paths.end (), compare_default_paths); + std::sort (paths.begin (), paths.end ()); - warning_start (line, e); - fprintf (stderr, "control flow %sis undefined for strings that match ", incond (cond).c_str ()); + warning_start (skel.line, e); + fprintf (stderr, "control flow %sis undefined for strings that match ", incond (skel.cond).c_str ()); const size_t count = paths.size (); if (count == 1) { - fprint_default_path (stderr, paths[0]); + fprint_default_path (stderr, skel, paths[0]); } else { for (size_t i = 0; i < count; ++i) { fprintf (stderr, "\n\t"); - fprint_default_path (stderr, paths[i]); + fprint_default_path (stderr, skel, paths[i]); } fprintf (stderr, "\n"); } diff --git a/re2c/src/conf/warn.h b/re2c/src/conf/warn.h index ac653fc9..f427dbc7 100644 --- a/re2c/src/conf/warn.h +++ b/re2c/src/conf/warn.h @@ -10,6 +10,7 @@ namespace re2c { struct path_t; +struct Skeleton; #define RE2C_WARNING_TYPES \ W (CONDITION_ORDER, "condition-order"), \ @@ -58,7 +59,7 @@ public: void empty_class (uint32_t line); void match_empty_string (uint32_t line); void swapped_range (uint32_t line, uint32_t l, uint32_t u); - void undefined_control_flow (uint32_t line, const std::string & cond, std::vector & paths, bool overflow); + void undefined_control_flow (const Skeleton &skel, std::vector & paths, bool overflow); void unreachable_rule (const std::string & cond, const RuleInfo *rule); void useless_escape (uint32_t line, uint32_t col, char c); }; diff --git a/re2c/src/ir/skeleton/control_flow.cc b/re2c/src/ir/skeleton/control_flow.cc index 31a98024..92f57c7e 100644 --- a/re2c/src/ir/skeleton/control_flow.cc +++ b/re2c/src/ir/skeleton/control_flow.cc @@ -1,5 +1,3 @@ -#include -#include #include #include "src/conf/warn.h" @@ -12,74 +10,60 @@ namespace re2c { +// See note [counting skeleton edges]. +// Type for counting arcs in paths that cause undefined behaviour. +// These paths are stored on heap, so the limit should be low. +// Most real-world cases have only a few short paths. +// We don't need all paths anyway, just some examples. +typedef u32lim_t<1024> nakeds_t; // ~1Kb + // We don't need all patterns that cause undefined behaviour. // We only need some examples, the shorter the better. -// See also note [counting skeleton edges]. -void Node::naked_paths( - path_t &prefix, +static void naked_paths( + Skeleton &skel, std::vector &paths, - nakeds_t &size) + path_t &prefix, + nakeds_t &size, + size_t i) { - if (rule) { + const Node &node = skel.nodes[i]; + uint8_t &loop = skel.loops[i]; + + if (node.rule) { return; - } else if (end()) { + } else if (node.end()) { paths.push_back(prefix); size = size + nakeds_t::from64(prefix.len()); } else if (loop < 2) { local_inc _(loop); - for (arcsets_t::iterator i = arcsets.begin(); - i != arcsets.end() && !size.overflow(); ++i) { - prefix.push(i->first); - i->first->naked_paths(prefix, paths, size); + Node::arcsets_t::const_iterator + arc = node.arcsets.begin(), + end = node.arcsets.end(); + for (; arc != end && !size.overflow(); ++arc) { + const size_t j = arc->first; + prefix.push(j); + naked_paths(skel, paths, prefix, size, j); prefix.pop(); } } } -void Skeleton::warn_undefined_control_flow() +void warn_undefined_control_flow(Skeleton &skel) { - path_t prefix(&nodes[0]); + path_t prefix(0); std::vector paths; - Node::nakeds_t size = Node::nakeds_t::from32(0u); + nakeds_t size = nakeds_t::from32(0u); - nodes->naked_paths(prefix, paths, size); + naked_paths(skel, paths, prefix, size, 0); if (!paths.empty()) { - warn.undefined_control_flow(line, cond, paths, size.overflow()); + warn.undefined_control_flow(skel, paths, size.overflow()); } else if (size.overflow()) { - warn.fail(Warn::UNDEFINED_CONTROL_FLOW, line, + warn.fail(Warn::UNDEFINED_CONTROL_FLOW, skel.line, "DFA is too large to check undefined control flow"); } } -// define strict weak ordering on patterns: -// 1st criterion is length (short patterns go first) -// 2nd criterion is lexicographical order (applies to patterns of equal length) -bool compare_default_paths(const path_t &p1, const path_t &p2) -{ - const size_t l1 = p1.len(); - const size_t l2 = p2.len(); - if (l1 == l2) { - for (size_t i = 0; i < l1; ++i) { - const Node::arcset_t - &a1 = p1.arcset(i), - &a2 = p2.arcset(i); - const size_t s1 = a1.size(); - const size_t s2 = a2.size(); - for (size_t j = 0; j < s1; ++j) { - if (j == s2 || a2[j] < a1[j]) { - return false; - } else if (a1[j] < a2[j]) { - return true; - } - } - } - return false; - } else { - return l1 < l2; - } -} - static void fprint_default_arc(FILE *f, const Node::arcset_t &arc) { const size_t ranges = arc.size(); @@ -99,7 +83,10 @@ static void fprint_default_arc(FILE *f, const Node::arcset_t &arc) } } -void fprint_default_path(FILE *f, const path_t &p) +void fprint_default_path( + FILE *f, + const Skeleton &skel, + const path_t &p) { fprintf(f, "'"); const size_t len = p.len(); @@ -107,7 +94,7 @@ void fprint_default_path(FILE *f, const path_t &p) if (i > 0) { fprintf(f, " "); } - const Node::arcset_t &arc = p.arcset(i); + const Node::arcset_t &arc = p.arcset(skel, i); fprint_default_arc(stderr, arc); } fprintf(f, "'"); diff --git a/re2c/src/ir/skeleton/generate_code.cc b/re2c/src/ir/skeleton/generate_code.cc index 38940ae7..1b38f286 100644 --- a/re2c/src/ir/skeleton/generate_code.cc +++ b/re2c/src/ir/skeleton/generate_code.cc @@ -15,27 +15,18 @@ namespace re2c { -static void exact_uint (OutputFile & o, size_t width) +static void exact_uint(OutputFile &o, size_t width) { - if (width == sizeof (char)) - { + if (width == sizeof(char)) { o.ws("unsigned char"); - } - else if (width == sizeof (short)) - { + } else if (width == sizeof(short)) { o.ws("unsigned short"); - } - else if (width == sizeof (int)) - { + } else if (width == sizeof(int)) { o.ws("unsigned int"); - } - else if (width == sizeof (long)) - { + } else if (width == sizeof(long)) { o.ws("unsigned long"); - } - else - { - o.ws("uint").wu64 (width * 8).ws("_t"); + } else { + o.ws("uint").wu64(width * 8).ws("_t"); } } @@ -44,14 +35,13 @@ static void from_le(OutputFile &o, uint32_t ind, size_t size, const char *expr) o.ws("\n").wind(ind).ws("/* from little-endian to host-endian */"); o.ws("\n").wind(ind).ws("unsigned char *p = (unsigned char*)&").ws(expr).ws(";"); o.ws("\n").wind(ind).ws(expr).ws(" = p[0]"); - for (uint32_t i = 1; i < size; ++i) - { + for (uint32_t i = 1; i < size; ++i) { o.ws(" + (p[").wu32(i).ws("] << ").wu32(i * 8).ws("u)"); } o.ws(";"); } -void Skeleton::emit_prolog (OutputFile & o) +void emit_prolog(OutputFile &o) { o.ws("\n#include "); o.ws("\n#include /* malloc, free */"); @@ -103,16 +93,14 @@ void Skeleton::emit_prolog (OutputFile & o) o.ws("\n"); } -void Skeleton::emit_start - ( OutputFile & o - , size_t maxfill - , bool backup - , bool backupctx - , bool accept - ) const +void emit_start(const Skeleton &skel, OutputFile &o, size_t maxfill, + bool backup, bool backupctx, bool accept) { - const size_t sizeof_cunit = opts->encoding.szCodeUnit(); - const uint32_t default_rule = rule2key (rule_rank_t::none ()); + const size_t + sizeof_cunit = opts->encoding.szCodeUnit(), + sizeof_key = skel.sizeof_key; + const uint32_t default_rule = skel.rule2key(rule_rank_t::none()); + const std::string &name = skel.name; o.ws("\n#define YYCTYPE "); exact_uint (o, sizeof_cunit); @@ -120,13 +108,11 @@ void Skeleton::emit_start exact_uint (o, sizeof_key); o.ws("\n#define YYPEEK() *cursor"); o.ws("\n#define YYSKIP() ++cursor"); - if (backup) - { + if (backup) { o.ws("\n#define YYBACKUP() marker = cursor"); o.ws("\n#define YYRESTORE() cursor = marker"); } - if (backupctx) - { + if (backupctx) { o.ws("\n#define YYBACKUPCTX() ctxmarker = cursor"); o.ws("\n#define YYRESTORECTX() cursor = ctxmarker"); } @@ -200,8 +186,7 @@ void Skeleton::emit_start o.ws("\n").wind(2).ws("goto end;"); o.ws("\n").wind(1).ws("}"); o.ws("\n"); - if (sizeof_cunit > 1) - { + if (sizeof_cunit > 1) { o.ws("\n").wind(1).ws("for (i = 0; i < input_len; ++i) {"); from_le(o, 2, sizeof_cunit, "input[i]"); o.ws("\n").wind(1).ws("}"); @@ -231,33 +216,27 @@ void Skeleton::emit_start o.ws("\n"); o.ws("\n").wind(1).ws("for (i = 0; status == 0 && i < keys_count; ++i) {"); o.ws("\n").wind(2).ws("token = cursor;"); - if (backup) - { + if (backup) { o.ws("\n").wind(2).ws("const YYCTYPE *marker = NULL;"); } - if (backupctx) - { + if (backupctx) { o.ws("\n").wind(2).ws("const YYCTYPE *ctxmarker = NULL;"); } o.ws("\n").wind(2).ws("YYCTYPE yych;"); - if (accept) - { + if (accept) { o.ws("\n").wind(2).ws("unsigned int yyaccept = 0;"); } o.ws("\n"); - if (opts->bFlag && BitMap::first) - { - BitMap::gen (o, 2, 0, std::min (0x100u, opts->encoding.nCodeUnits ())); + if (opts->bFlag && BitMap::first) { + BitMap::gen(o, 2, 0, std::min(0x100u, opts->encoding.nCodeUnits())); } o.ws("\n"); } -void Skeleton::emit_end - ( OutputFile & o - , bool backup - , bool backupctx - ) const +void emit_end(const Skeleton &skel, OutputFile &o, bool backup, bool backupctx) { + const std::string &name = skel.name; + o.ws("\n").wind(1).ws("}"); o.ws("\n").wind(1).ws("if (status == 0) {"); o.ws("\n").wind(2).ws("if (cursor != eof) {"); @@ -282,13 +261,11 @@ void Skeleton::emit_end o.ws("\n#undef YYKEYTYPE"); o.ws("\n#undef YYPEEK"); o.ws("\n#undef YYSKIP"); - if (backup) - { + if (backup) { o.ws("\n#undef YYBACKUP"); o.ws("\n#undef YYRESTORE"); } - if (backupctx) - { + if (backupctx) { o.ws("\n#undef YYBACKUPCTX"); o.ws("\n#undef YYRESTORECTX"); } @@ -297,13 +274,12 @@ void Skeleton::emit_end o.ws("\n"); } -void Skeleton::emit_epilog (OutputFile & o, const std::set & names) +void emit_epilog(OutputFile &o, const std::set &names) { o.ws("\n").ws("int main()"); o.ws("\n").ws("{"); - for (std::set::const_iterator i = names.begin (); i != names.end (); ++i) - { + for (std::set::const_iterator i = names.begin(); i != names.end(); ++i) { o.ws("\n").wind(1).ws("if(lex_").wstring(*i).ws("() != 0) {"); o.ws("\n").wind(2).ws("return 1;"); o.ws("\n").wind(1).ws("}"); @@ -314,9 +290,9 @@ void Skeleton::emit_epilog (OutputFile & o, const std::set & names) o.ws("\n"); } -void Skeleton::emit_action (OutputFile & o, uint32_t ind, rule_rank_t rank) const +void emit_action(const Skeleton &skel, OutputFile &o, uint32_t ind, rule_rank_t rank) { - o.wind(ind).ws("status = action_").wstring(name).ws("(i, keys, input, token, &cursor, ").wu32(rule2key (rank)).ws(");\n"); + o.wind(ind).ws("status = action_").wstring(skel.name).ws("(i, keys, input, token, &cursor, ").wu32(skel.rule2key(rank)).ws(");\n"); o.wind(ind).ws("continue;\n"); } diff --git a/re2c/src/ir/skeleton/generate_data.cc b/re2c/src/ir/skeleton/generate_data.cc index 36e75782..d4bb5947 100644 --- a/re2c/src/ir/skeleton/generate_data.cc +++ b/re2c/src/ir/skeleton/generate_data.cc @@ -20,8 +20,93 @@ namespace re2c { -template - static Node::covers_t cover_one (FILE * input, FILE * keys, const path_t & path); +/* + * note [counting skeleton edges] + * + * To avoid any possible overflows all size calculations are wrapped in + * a special truncated unsigned 32-bit integer type that checks overflow + * on each binary operation or conversion from another type. + * + * Two things contribute to size calculation: path length and the number + * of outgoing arcs in each node. Some considerations on why these values + * will not overflow before they are converted to truncated type: + * + * - Maximal number of outgoing arcs in each node cannot exceed 32 bits: + * it is bounded by the number of code units in current encoding, and + * re2c doesn't support any encoding with more than 2^32 code units. + * Conversion is safe. + * + * - Maximal path length cannot exceed 32 bits: we estimate it right + * after skeleton construction and check for overflow. If path length + * does overflow, an error is reported and re2c aborts. + */ + +// See note [counting skeleton edges]. +// Type for calculating the size of path cover. +// Paths are dumped to file as soon as generated and don't eat +// heap space. The total size of path cover (measured in edges) +// is O(N^2) where N is the number of edges in skeleton. +typedef u32lim_t<1024 * 1024 * 1024> covers_t; // ~1Gb + +template static uintn_t to_le(uintn_t n) +{ + uintn_t m; + uint8_t *p = reinterpret_cast(&m); + for (size_t i = 0; i < sizeof(uintn_t); ++i) { + p[i] = static_cast(n >> (i * 8)); + } + return m; +} + +template static void keygen(FILE *f, size_t count, + size_t len, size_t len_match, rule_rank_t match) +{ + const key_t m = Skeleton::rule2key(match); + + const size_t keys_size = 3 * count; + key_t * keys = new key_t[keys_size]; + for (uint32_t i = 0; i < keys_size;) { + keys[i++] = to_le(static_cast(len)); + keys[i++] = to_le(static_cast(len_match)); + keys[i++] = to_le(m); + } + fwrite(keys, sizeof(key_t), keys_size, f); + delete[] keys; +} + +template static covers_t cover_one( + Skeleton &skel, FILE *input, FILE * keys, const path_t & path) +{ + const size_t len = path.len(); + + size_t count = 0; + for (size_t i = 0; i < len; ++i) { + count = std::max (count, path.arc(skel, i).size()); + } + + const covers_t size = covers_t::from64(len) + * covers_t::from64(count); + if (!size.overflow()) { + // input + const size_t buffer_size = size.uint32(); + cunit_t *buffer = new cunit_t [buffer_size]; + for (size_t i = 0; i < len; ++i) { + const std::vector &arc = path.arc(skel, i); + const size_t width = arc.size(); + for (size_t j = 0; j < count; ++j) { + const size_t k = j % width; + buffer[j * len + i] = to_le(static_cast(arc[k])); + } + } + fwrite (buffer, sizeof(cunit_t), buffer_size, input); + delete[] buffer; + + // keys + keygen(keys, count, len, path.len_matching(skel), path.match(skel)); + } + + return size; +} /* * note [generating skeleton path cover] @@ -52,164 +137,101 @@ template * See also note [counting skeleton edges]. * */ -template - void Node::cover (path_t & prefix, FILE * input, FILE * keys, covers_t &size) +template static void cover( + Skeleton &skel, + FILE *input, + FILE *keys, + path_t &prefix, + covers_t &size, + size_t i) { - if (end () && suffix == NULL) - { - suffix = new path_t(this); + const Node &node = skel.nodes[i]; + uint8_t &loop = skel.loops[i]; + path_t *&suffix = skel.suffixes[i]; + + if (node.end() && suffix == NULL) { + suffix = new path_t(i); } + if (suffix != NULL) { - prefix.append (suffix); - size = size + cover_one (input, keys, prefix); - } - else if (loop < 2) - { - local_inc _ (loop); - for (arcs_t::iterator i = arcs.begin (); - i != arcs.end () && !size.overflow(); ++i) - { + prefix.append(suffix); + size = size + cover_one(skel, input, keys, prefix); + } else if (loop < 2) { + local_inc _(loop); + Node::arcs_t::const_iterator + arc = node.arcs.begin(), + end = node.arcs.end(); + for (; arc != end && !size.overflow(); ++arc) { + const size_t j = arc->first; + path_t new_prefix = prefix; - new_prefix.push (i->first); - i->first->cover (new_prefix, input, keys, size); - if (i->first->suffix != NULL && suffix == NULL) - { - suffix = new path_t(this); - suffix->push (i->first); - suffix->append (i->first->suffix); + new_prefix.push(j); + cover(skel, input, keys, new_prefix, size, j); + + const path_t *sfx = skel.suffixes[j]; + if (sfx != NULL && suffix == NULL) { + suffix = new path_t(i); + suffix->push(j); + suffix->append(sfx); } } } } -template - void Skeleton::generate_paths_cunit_key (FILE * input, FILE * keys) +template static void generate_paths_cunit_key( + Skeleton &skel, FILE *input, FILE *keys) { - path_t prefix (nodes); - Node::covers_t size = Node::covers_t::from32(0u); + path_t prefix(0); + covers_t size = covers_t::from32(0u); - nodes->cover (prefix, input, keys, size); + cover(skel, input, keys, prefix, size, 0); - if (size.overflow ()) - { - warning - ( NULL - , line - , false - , "DFA %sis too large: can only generate partial path cover" - , incond (cond).c_str () - ); + if (size.overflow()) { + warning(NULL, skel.line, false, + "DFA %sis too large: can only generate partial path cover", + incond(skel.cond).c_str()); } } -template - void Skeleton::generate_paths_cunit (FILE * input, FILE * keys) +template static void generate_paths_cunit( + Skeleton &skel, FILE *input, FILE *keys) { - switch (sizeof_key) - { - case 4: generate_paths_cunit_key (input, keys); break; - case 2: generate_paths_cunit_key (input, keys); break; - case 1: generate_paths_cunit_key (input, keys); break; + switch (skel.sizeof_key) { + case 4: generate_paths_cunit_key(skel, input, keys); break; + case 2: generate_paths_cunit_key(skel, input, keys); break; + case 1: generate_paths_cunit_key(skel, input, keys); break; } } -void Skeleton::generate_paths (FILE * input, FILE * keys) +static void generate_paths(Skeleton &skel, FILE *input, FILE *keys) { - switch (opts->encoding.szCodeUnit ()) - { - case 4: generate_paths_cunit (input, keys); break; - case 2: generate_paths_cunit (input, keys); break; - case 1: generate_paths_cunit (input, keys); break; - } -} - -void Skeleton::emit_data (const char * fname) -{ - const std::string input_name = std::string (fname) + "." + name + ".input"; - FILE * input = fopen (input_name.c_str (), "wb"); - if (!input) - { - error ("cannot open file: %s", input_name.c_str ()); - exit (1); - } - const std::string keys_name = std::string (fname) + "." + name + ".keys"; - FILE * keys = fopen (keys_name.c_str (), "wb"); - if (!keys) - { - error ("cannot open file: %s", keys_name.c_str ()); - exit (1); - } - - generate_paths (input, keys); - - fclose (input); - fclose (keys); -} - -template static uintn_t to_le(uintn_t n) -{ - uintn_t m; - uint8_t *p = reinterpret_cast(&m); - for (size_t i = 0; i < sizeof(uintn_t); ++i) - { - p[i] = static_cast(n >> (i * 8)); + switch (opts->encoding.szCodeUnit()) { + case 4: generate_paths_cunit(skel, input, keys); break; + case 2: generate_paths_cunit(skel, input, keys); break; + case 1: generate_paths_cunit(skel, input, keys); break; } - return m; } -template - static void keygen (FILE * f, size_t count, size_t len, size_t len_match, rule_rank_t match) +void emit_data(Skeleton &skel, const char *fname) { - const key_t m = Skeleton::rule2key (match); - - const size_t keys_size = 3 * count; - key_t * keys = new key_t [keys_size]; - for (uint32_t i = 0; i < keys_size;) - { - keys[i++] = to_le(static_cast (len)); - keys[i++] = to_le(static_cast (len_match)); - keys[i++] = to_le(m); + const std::string input_name = std::string(fname) + "." + skel.name + ".input"; + FILE *input = fopen(input_name.c_str(), "wb"); + if (!input) { + error("cannot open file: %s", input_name.c_str()); + exit(1); } - fwrite (keys, sizeof (key_t), keys_size, f); - delete [] keys; -} - -template - static Node::covers_t cover_one (FILE * input, FILE * keys, const path_t & path) -{ - const size_t len = path.len (); - - size_t count = 0; - for (size_t i = 0; i < len; ++i) - { - count = std::max (count, path.arc(i).size ()); + const std::string keys_name = std::string(fname) + "." + skel.name + ".keys"; + FILE *keys = fopen (keys_name.c_str(), "wb"); + if (!keys) { + error("cannot open file: %s", keys_name.c_str()); + exit(1); } - const Node::covers_t size = Node::covers_t::from64(len) * Node::covers_t::from64(count); - if (!size.overflow ()) - { - // input - const size_t buffer_size = size.uint32 (); - cunit_t * buffer = new cunit_t [buffer_size]; - for (size_t i = 0; i < len; ++i) - { - const std::vector & arc = path.arc(i); - const size_t width = arc.size (); - for (size_t j = 0; j < count; ++j) - { - const size_t k = j % width; - buffer[j * len + i] = to_le(static_cast (arc[k])); - } - } - fwrite (buffer, sizeof (cunit_t), buffer_size, input); - delete [] buffer; - - // keys - keygen (keys, count, len, path.len_matching (), path.match ()); - } + generate_paths(skel, input, keys); - return size; + fclose(input); + fclose(keys); } } // namespace re2c diff --git a/re2c/src/ir/skeleton/maxlen.cc b/re2c/src/ir/skeleton/maxlen.cc deleted file mode 100644 index 3f1d9331..00000000 --- a/re2c/src/ir/skeleton/maxlen.cc +++ /dev/null @@ -1,50 +0,0 @@ -#include "src/util/c99_stdint.h" -#include -#include -#include -#include - -#include "src/ir/skeleton/skeleton.h" - -namespace re2c -{ - -// 0 < DIST_MAX < DIST_ERROR <= std::numeric_limits::max() -const uint32_t Node::DIST_ERROR = std::numeric_limits::max(); -const uint32_t Node::DIST_MAX = DIST_ERROR - 1; - -// different from YYMAXFILL calculation -// in the way it handles loops and empty regexp -void Node::calc_dist () -{ - if (dist != DIST_ERROR) - { - return; - } - else if (end ()) - { - dist = 0; - } - else if (loop < 2) - { - local_inc _ (loop); - for (arcs_t::iterator i = arcs.begin (); i != arcs.end (); ++i) - { - i->first->calc_dist (); - if (i->first->dist != DIST_ERROR) - { - if (dist == DIST_ERROR) - { - dist = i->first->dist; - } - else - { - dist = std::max (dist, i->first->dist); - } - } - } - dist = std::min (dist + 1, DIST_MAX); - } -} - -} // namespace re2c diff --git a/re2c/src/ir/skeleton/maxpath.cc b/re2c/src/ir/skeleton/maxpath.cc new file mode 100644 index 00000000..a5ea7770 --- /dev/null +++ b/re2c/src/ir/skeleton/maxpath.cc @@ -0,0 +1,65 @@ +#include "src/util/c99_stdint.h" +#include +#include + +#include "src/conf/msg.h" +#include "src/ir/skeleton/skeleton.h" + +namespace re2c +{ + +// 0 < DIST_MAX < DIST_ERROR <= std::numeric_limits::max() +static const uint32_t DIST_ERROR = std::numeric_limits::max(); +static const uint32_t DIST_MAX = DIST_ERROR - 1; + +// maximal distance to end node (assuming one iteration per loop) +// different from YYMAXFILL calculation +// in the way it handles loops and empty regexp +static void calc_dist( + Skeleton &skel, + std::vector &dists, + size_t i) +{ + const Node &node = skel.nodes[i]; + uint8_t &loop = skel.loops[i]; + uint32_t &dist = dists[i]; + + if (dist != DIST_ERROR) { + return; + } else if (node.end()) { + dist = 0; + } else if (loop < 2) { + local_inc _(loop); + Node::arcs_t::const_iterator + arc = node.arcs.begin(), + end = node.arcs.end(); + for (; arc != end; ++arc) { + const size_t j = arc->first; + calc_dist(skel, dists, j); + uint32_t &d = dists[j]; + if (d != DIST_ERROR) { + if (dist == DIST_ERROR) { + dist = d; + } else { + dist = std::max(dist, d); + } + } + } + dist = std::min(dist + 1, DIST_MAX); + } +} + +// calculate maximal path length, check overflow +uint32_t maxpath(Skeleton &skel) +{ + std::vector dists(skel.nodes_count); + calc_dist(skel, dists, 0); + const uint32_t maxlen = dists[0]; + if (maxlen == DIST_MAX) { + error("DFA path %sis too long", incond(skel.cond).c_str()); + exit(1); + } + return maxlen; +} + +} // namespace re2c diff --git a/re2c/src/ir/skeleton/path.h b/re2c/src/ir/skeleton/path.h index 8f5fce87..a5865b22 100644 --- a/re2c/src/ir/skeleton/path.h +++ b/re2c/src/ir/skeleton/path.h @@ -11,30 +11,24 @@ namespace re2c class path_t { - std::vector arcs; + std::vector arcs; public: - explicit path_t(Node *n) : arcs() + explicit path_t(size_t i) : arcs() { - arcs.push_back(n); - } - path_t(const path_t &p) : arcs(p.arcs) {} - path_t &operator=(const path_t &p) - { - new (this) path_t(p); - return *this; + arcs.push_back(i); } size_t len() const { return arcs.size() - 1; } - size_t len_matching () const + size_t len_matching(const Skeleton &skel) const { - std::vector::const_reverse_iterator + std::vector::const_reverse_iterator tail = arcs.rbegin(), head = arcs.rend(); for (; tail != head; ++tail) { - RuleInfo *rule = (*tail)->rule; + RuleInfo *rule = skel.nodes[*tail].rule; if (rule == NULL) { continue; } @@ -44,7 +38,7 @@ public: return len; case ~0u: for (; tail != head; ++tail) { - if ((*tail)->ctx) { + if (skel.nodes[*tail].ctx) { return static_cast(head - tail) - 1; } } @@ -55,28 +49,28 @@ public: } return 0; } - rule_rank_t match() const + rule_rank_t match(const Skeleton &skel) const { - std::vector::const_reverse_iterator + std::vector::const_reverse_iterator tail = arcs.rbegin(), head = arcs.rend(); for (; tail != head; ++tail) { - RuleInfo *rule = (*tail)->rule; + RuleInfo *rule = skel.nodes[*tail].rule; if (rule != NULL) { return rule->rank; } } return rule_rank_t::none(); } - const Node::arc_t& arc(size_t i) const + const Node::arc_t& arc(const Skeleton &skel, size_t i) const { - return arcs[i]->arcs[arcs[i + 1]]; + return skel.nodes[arcs[i]].arcs[arcs[i + 1]]; } - const Node::arcset_t& arcset(size_t i) const + const Node::arcset_t& arcset(const Skeleton &skel, size_t i) const { - return arcs[i]->arcsets[arcs[i + 1]]; + return skel.nodes[arcs[i]].arcsets[arcs[i + 1]]; } - void push(Node *n) + void push(size_t n) { arcs.push_back(n); } @@ -89,6 +83,14 @@ public: assert(arcs.back() == p->arcs.front()); arcs.insert(arcs.end(), p->arcs.begin() + 1, p->arcs.end()); } + bool operator<(const path_t &p) const + { + const size_t + s1 = arcs.size(), + s2 = p.arcs.size(); + return (s1 == s2 && arcs < p.arcs) + || s1 < s2; + } }; } // namespace re2c diff --git a/re2c/src/ir/skeleton/skeleton.cc b/re2c/src/ir/skeleton/skeleton.cc index 91ca1288..7baa656a 100644 --- a/re2c/src/ir/skeleton/skeleton.cc +++ b/re2c/src/ir/skeleton/skeleton.cc @@ -1,40 +1,34 @@ -#include +#include #include -#include -#include "src/codegen/go.h" -#include "src/conf/msg.h" #include "src/ir/dfa/dfa.h" -#include "src/ir/regexp/regexp.h" #include "src/ir/skeleton/path.h" #include "src/ir/skeleton/skeleton.h" namespace re2c { -Node::Node () - : arcs () - , arcsets () - , loop (0) - , rule (NULL) - , ctx (false) - , dist (DIST_ERROR) - , reachable () - , suffix (NULL) +Node::Node() : + arcs(), + arcsets(), + rule(NULL), + ctx(false) {} -void Node::init(bool c, RuleInfo *r, const std::vector > &a) +void Node::init( + bool c, + RuleInfo *r, + const std::vector > &a) { rule = r; ctx = c; uint32_t lb = 0; - std::vector >::const_iterator + std::vector >::const_iterator i = a.begin(), e = a.end(); - for (; i != e; ++i) - { - Node *n = i->first; + for (; i != e; ++i) { + const size_t n = i->first; const uint32_t ub = i->second - 1; // pick at most 0x100 unique edges from this range @@ -43,115 +37,101 @@ void Node::init(bool c, RuleInfo *r, const std::vector > arcs; - for (size_t c = 0; c < nc;) - { + std::vector > arcs; + for (size_t c = 0; c < nc;) { const size_t j = s->arcs[c]; for (;++c < nc && s->arcs[c] == j;); - Node *to = j == dfa_t::NIL - ? nil - : &nodes[j]; + const size_t to = j == dfa_t::NIL + ? nodes_count - 1 + : j; arcs.push_back(std::make_pair(to, cs[c])); } // all arcs go to default node => this node is final, drop arcs - if (arcs.size() == 1 && arcs[0].first == nil) - { + if (arcs.size() == 1 && arcs[0].first == nodes_count - 1) { arcs.clear(); } nodes[i].init(s->ctx, s->rule, arcs); } - // calculate maximal path length, check overflow - nodes->calc_dist (); - const uint32_t maxlen = nodes->dist; - if (maxlen == Node::DIST_MAX) - { - error ("DFA path %sis too long", incond (cond).c_str ()); - exit (1); - } + const uint32_t maxlen = maxpath(*this); // calculate maximal rule rank (disregarding default and none rules) uint32_t maxrule = 0; - for (uint32_t i = 0; i < nodes_count; ++i) - { + for (uint32_t i = 0; i < nodes_count; ++i) { const RuleInfo *r = nodes[i].rule; - if (r && !r->rank.is_def()) - { - maxrule = std::max (maxrule, r->rank.uint32 ()); + if (r && !r->rank.is_def()) { + maxrule = std::max(maxrule, r->rank.uint32()); } } // two upper values reserved for default and none rules) maxrule += 2; // initialize size of key - const uint32_t max = std::max (maxlen, maxrule); - if (max <= std::numeric_limits::max()) - { + const uint32_t max = std::max(maxlen, maxrule); + if (max <= std::numeric_limits::max()) { sizeof_key = 1; - } - else if (max <= std::numeric_limits::max()) - { + } else if (max <= std::numeric_limits::max()) { sizeof_key = 2; } } -Skeleton::~Skeleton () +Skeleton::~Skeleton() { - delete [] nodes; + delete[] nodes; + delete[] loops; + for (size_t i = 0; i < nodes_count; ++i) { + delete suffixes[i]; + } + delete[] suffixes; } -uint32_t Skeleton::rule2key (rule_rank_t r) const +uint32_t Skeleton::rule2key(rule_rank_t r) const { - switch (sizeof_key) - { + switch (sizeof_key) { default: // shouldn't happen - case 4: return rule2key (r); - case 2: return rule2key (r); - case 1: return rule2key (r); + case 4: return rule2key(r); + case 2: return rule2key(r); + case 1: return rule2key(r); } } diff --git a/re2c/src/ir/skeleton/skeleton.h b/re2c/src/ir/skeleton/skeleton.h index 786fa754..93460ed8 100644 --- a/re2c/src/ir/skeleton/skeleton.h +++ b/re2c/src/ir/skeleton/skeleton.h @@ -2,7 +2,6 @@ #define _RE2C_IR_SKELETON_SKELETON_ #include "src/util/c99_stdint.h" -#include #include #include #include @@ -12,99 +11,38 @@ #include #include "src/ir/regexp/regexp.h" -#include "src/ir/rule_rank.h" #include "src/parse/rules.h" #include "src/util/local_increment.h" #include "src/util/forbid_copy.h" -#include "src/util/u32lim.h" namespace re2c { struct dfa_t; struct OutputFile; -class RuleInfo; struct path_t; +typedef local_increment_t local_inc; + struct Node { - /* - * note [counting skeleton edges] - * - * To avoid any possible overflows all size calculations are wrapped in - * a special truncated unsigned 32-bit integer type that checks overflow - * on each binary operation or conversion from another type. - * - * Two things contribute to size calculation: path length and the number - * of outgoing arcs in each node. Some considerations on why these values - * will not overflow before they are converted to truncated type: - * - * - Maximal number of outgoing arcs in each node cannot exceed 32 bits: - * it is bounded by the number of code units in current encoding, and - * re2c doesn't support any encoding with more than 2^32 code units. - * Conversion is safe. - * - * - Maximal path length cannot exceed 32 bits: we estimate it right - * after skeleton construction and check for overflow. If path length - * does overflow, an error is reported and re2c aborts. - */ - - // Type for calculating the size of path cover. - // Paths are dumped to file as soon as generated and don't eat - // heap space. The total size of path cover (measured in edges) - // is O(N^2) where N is the number of edges in skeleton. - typedef u32lim_t<1024 * 1024 * 1024> covers_t; // ~1Gb - - // Type for counting arcs in paths that cause undefined behaviour. - // These paths are stored on heap, so the limit should be low. - // Most real-world cases have only a few short paths. - // We don't need all paths anyway, just some examples. - typedef u32lim_t<1024> nakeds_t; // ~1Kb - typedef std::vector > arcset_t; - typedef std::map arcsets_t; + typedef std::map arcsets_t; typedef std::vector arc_t; - typedef std::map arcs_t; + typedef std::map arcs_t; - typedef local_increment_t local_inc; - - // outgoing arcs arcs_t arcs; arcsets_t arcsets; - - // how many times this node has been visited - // (controls looping in graph traversals) - uint8_t loop; - - // rule for corresponding DFA state (if any) RuleInfo *rule; - - // start of trailing context bool ctx; - // maximal distance to end node (assuming one iteration per loop) - static const uint32_t DIST_ERROR; - static const uint32_t DIST_MAX; - uint32_t dist; - - // rules reachable from this node (including absent rule) - std::set reachable; + Node(); + void init(bool b, RuleInfo *r, + const std::vector > &arcs); + bool end() const; - // path to end node (for constructing path cover) - path_t * suffix; - - Node (); - void init(bool b, RuleInfo *r, const std::vector > &arcs); - ~Node (); - bool end () const; - void calc_dist (); - void calc_reachable (); - template - void cover (path_t & prefix, FILE * input, FILE * keys, covers_t &size); - void naked_paths(path_t &prefix, std::vector &paths, nakeds_t &size); - - FORBID_COPY (Node); + FORBID_COPY(Node); }; struct Skeleton @@ -114,52 +52,28 @@ struct Skeleton const uint32_t line; const size_t nodes_count; - Node * nodes; + Node *nodes; + + // visit counters (for graph traversal) + uint8_t *loops; + + // paths to end node (for constructing path cover) + path_t **suffixes; + size_t sizeof_key; rules_t rules; - Skeleton - ( const dfa_t &dfa - , const charset_t &cs - , const rules_t & rs - , const std::string &dfa_name - , const std::string &dfa_cond - , uint32_t dfa_line - ); + Skeleton(const dfa_t &dfa, const charset_t &cs, const rules_t &rs, + const std::string &dfa_name, const std::string &dfa_cond, + uint32_t dfa_line); ~Skeleton (); - void warn_undefined_control_flow (); - void warn_unreachable_nullable_rules (); - void emit_data (const char * fname); - static void emit_prolog (OutputFile & o); - void emit_start - ( OutputFile & o - , size_t maxfill - , bool backup - , bool backupctx - , bool accept - ) const; - void emit_end - ( OutputFile & o - , bool backup - , bool backupctx - ) const; - static void emit_epilog (OutputFile & o, const std::set & names); - void emit_action (OutputFile & o, uint32_t ind, rule_rank_t rank) const; - - template static key_t rule2key (rule_rank_t r); - uint32_t rule2key (rule_rank_t r) const; - -private: - template - void generate_paths_cunit_key (FILE * input, FILE * keys); - template - void generate_paths_cunit (FILE * input, FILE * keys); - void generate_paths (FILE * input, FILE * keys); - - FORBID_COPY (Skeleton); + uint32_t rule2key(rule_rank_t r) const; + template static key_t rule2key(rule_rank_t r); + + FORBID_COPY(Skeleton); }; -template key_t Skeleton::rule2key (rule_rank_t r) +template key_t Skeleton::rule2key(rule_rank_t r) { if (r.is_none()) { return std::numeric_limits::max(); @@ -171,8 +85,18 @@ template key_t Skeleton::rule2key (rule_rank_t r) } } -bool compare_default_paths(const path_t &p1, const path_t &p2); -void fprint_default_path(FILE *f, const path_t &p); +uint32_t maxpath(Skeleton &skel); +void warn_undefined_control_flow(Skeleton &skel); +void fprint_default_path(FILE *f, const Skeleton &skel, const path_t &p); +void warn_unreachable_nullable_rules(Skeleton &skel); +void emit_data(Skeleton &skel, const char *fname); +void emit_prolog(OutputFile & o); +void emit_start(const Skeleton &skel, OutputFile &o, size_t maxfill, + bool backup, bool backupctx, bool accept); +void emit_end(const Skeleton &skel, OutputFile &o, bool backup, bool backupctx); +void emit_epilog(OutputFile &o, const std::set &names); +void emit_action(const Skeleton &skel, OutputFile &o, uint32_t ind, + rule_rank_t rank); } // namespace re2c diff --git a/re2c/src/ir/skeleton/unreachable_nullable.cc b/re2c/src/ir/skeleton/unreachable_nullable.cc index 7dbfd3dd..4c7111ce 100644 --- a/re2c/src/ir/skeleton/unreachable_nullable.cc +++ b/re2c/src/ir/skeleton/unreachable_nullable.cc @@ -1,7 +1,5 @@ #include "src/util/c99_stdint.h" -#include #include -#include #include "src/conf/warn.h" #include "src/globals.h" @@ -13,47 +11,51 @@ namespace re2c { -void Node::calc_reachable () +static void calc_reachable( + Skeleton &skel, + std::vector > &reachs, + size_t i) { - if (!reachable.empty ()) - { + const Node &node = skel.nodes[i]; + uint8_t &loop = skel.loops[i]; + std::set &reach = reachs[i]; + + if (!reach.empty()) { return; - } - else if (end ()) - { - reachable.insert (rule); - } - else if (loop < 2) - { - local_inc _ (loop); - for (arcs_t::iterator i = arcs.begin (); i != arcs.end (); ++i) - { - i->first->calc_reachable (); - reachable.insert (i->first->reachable.begin (), i->first->reachable.end ()); + } else if (node.end()) { + reach.insert(node.rule); + } else if (loop < 2) { + local_inc _(loop); + Node::arcs_t::const_iterator + arc = node.arcs.begin(), + end = node.arcs.end(); + for (; arc != end; ++arc) { + const size_t j = arc->first; + calc_reachable(skel, reachs, j); + reach.insert(reachs[j].begin(), reachs[j].end()); } } } -void Skeleton::warn_unreachable_nullable_rules () +void warn_unreachable_nullable_rules(Skeleton &skel) { // calculate reachable rules - nodes->calc_reachable(); - for (uint32_t i = 0; i < nodes_count; ++i) - { - RuleInfo *r1 = nodes[i].rule; + std::vector > reachs(skel.nodes_count); + calc_reachable(skel, reachs, 0); + + for (uint32_t i = 0; i < skel.nodes_count; ++i) { + RuleInfo *r1 = skel.nodes[i].rule; if (!r1) { continue; } - const std::set & rs = nodes[i].reachable; - for (std::set::const_iterator j = rs.begin(); j != rs.end(); ++j) - { - RuleInfo* r2 = *j; - if (!r2 || r1->rank == r2->rank) - { + std::set::const_iterator + rule = reachs[i].begin(), + end = reachs[i].end(); + for (; rule != end; ++rule) { + RuleInfo* r2 = *rule; + if (!r2 || r1->rank == r2->rank) { r1->reachable = true; - } - else - { + } else { r1->shadow.insert(r2->loc.line); } } @@ -64,12 +66,10 @@ void Skeleton::warn_unreachable_nullable_rules () // - infinite rules that consume infinitely many characters and fail on YYFILL, e.g. '[^]*' // - rules that contain never-matching link, e.g. '[]' with option '--empty-class match-none' // default rule '*' should not be reported - for (rules_t::const_iterator i = rules.begin(); i != rules.end(); ++i) - { + for (rules_t::const_iterator i = skel.rules.begin(); i != skel.rules.end(); ++i) { const RuleInfo *r = *i; - if (!r->rank.is_def() && !r->reachable) - { - warn.unreachable_rule(cond, r); + if (!r->rank.is_def() && !r->reachable) { + warn.unreachable_rule(skel.cond, r); } } @@ -78,11 +78,9 @@ void Skeleton::warn_unreachable_nullable_rules () // - rules that match empty strins with nonempty trailing context // false positives on partially shadowed (yet reachable) rules, e.g.: // [^]? - for (rules_t::const_iterator i = rules.begin(); i != rules.end(); ++i) - { + for (rules_t::const_iterator i = skel.rules.begin(); i != skel.rules.end(); ++i) { const RuleInfo *r = *i; - if (r->nullable && r->reachable) - { + if (r->nullable && r->reachable) { warn.match_empty_string(r->loc.line); } } diff --git a/re2c/src/parse/parser.ypp b/re2c/src/parse/parser.ypp index 7f46af1b..8db2c44a 100644 --- a/re2c/src/parse/parser.ypp +++ b/re2c/src/parse/parser.ypp @@ -572,7 +572,7 @@ void parse(Scanner& i, Output & o) .wline_info (in->get_cline (), in->get_fname ().c_str ()); if (opts->target == opt_t::SKELETON) { - Skeleton::emit_prolog (o.source); + emit_prolog(o.source); } Enc encodingOld = opts->encoding; @@ -741,7 +741,7 @@ void parse(Scanner& i, Output & o) if (opts->target == opt_t::SKELETON) { - Skeleton::emit_epilog (o.source, o.skeletons); + emit_epilog(o.source, o.skeletons); } parse_cleanup(); -- 2.40.0