// file : cli/context.cxx // author : Boris Kolpackov // copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC // license : MIT; see accompanying LICENSE file #include #include // strncmp() #include #include "context.hxx" using namespace std; namespace { char const* keywords[] = { "NULL", "and", "asm", "auto", "bitand", "bitor", "bool", "break", "case", "catch", "char", "class", "compl", "const", "const_cast", "continue", "default", "delete", "do", "double", "dynamic_cast", "else", "end_eq", "enum", "explicit", "export", "extern", "false", "float", "for", "friend", "goto", "if", "inline", "int", "long", "mutable", "namespace", "new", "not", "not_eq", "operator", "or", "or_eq", "private", "protected", "public", "register", "reinterpret_cast", "return", "short", "signed", "sizeof", "static", "static_cast", "struct", "switch", "template", "this", "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq" }; } context:: context (ostream& os_, semantics::cli_unit& unit_, options_type const& ops) : data_ (new (shared) data), os (os_), unit (unit_), options (ops), modifier (options.generate_modifier ()), specifier (options.generate_specifier ()), inl (data_->inl_), opt_prefix (options.option_prefix ()), opt_sep (options.option_separator ()), cli (data_->cli_), reserved_name_map (options.reserved_name ()), keyword_set (data_->keyword_set_) { if (options.suppress_usage ()) usage = ut_none; else { if (options.long_usage ()) usage = options.short_usage () ? ut_both : ut_long; else usage = ut_short; } if (!options.suppress_inline ()) data_->inl_ = "inline "; data_->cli_ = options.cli_namespace (); if (!cli.empty () && cli[0] != ':') data_->cli_ = "::" + data_->cli_; for (size_t i (0); i < sizeof (keywords) / sizeof (char*); ++i) data_->keyword_set_.insert (keywords[i]); } context:: context (context& c) : data_ (c.data_), os (c.os), unit (c.unit), options (c.options), modifier (c.modifier), specifier (c.specifier), usage (c.usage), inl (c.inl), opt_prefix (c.opt_prefix), opt_sep (c.opt_sep), cli (c.cli), reserved_name_map (c.reserved_name_map), keyword_set (c.keyword_set) { } string context:: escape (string const& name) const { typedef string::size_type size; string r; size n (name.size ()); // In most common cases we will have that many characters. // r.reserve (n); for (size i (0); i < n; ++i) { char c (name[i]); if (i == 0) { if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) r = (c >= '0' && c <= '9') ? "cxx_" : "cxx"; } if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) r += '_'; else r += c; } if (r.empty ()) r = "cxx"; // Custom reserved words. // reserved_name_map_type::const_iterator i (reserved_name_map.find (r)); if (i != reserved_name_map.end ()) { if (!i->second.empty ()) return i->second; else r += L'_'; } // Keywords // if (keyword_set.find (r) != keyword_set.end ()) { r += '_'; // Re-run custom words. // i = reserved_name_map.find (r); if (i != reserved_name_map.end ()) { if (!i->second.empty ()) return i->second; else r += L'_'; } } return r; } string context:: translate_arg (string const& s, std::set& set) { string r; r.reserve (s.size ()); set.clear (); size_t p (string::npos); for (size_t i (0), n (s.size ()); i < n; ++i) { if (p == string::npos && s[i] == '<') { p = i; r += "\\i{"; continue; } if (p != string::npos && s[i] == '>') { set.insert (string (s, p + 1, i - p - 1)); r += '}'; p = string::npos; continue; } if (p != string::npos && s[i] == '}' && s[i - 1] != '\\') { r += "\\}"; continue; } r += s[i]; } return r; } string context:: translate (string const& s, std::set const& set) { string r; r.reserve (s.size ()); size_t p (string::npos); for (size_t i (0), n (s.size ()); i < n; ++i) { if (p == string::npos && s[i] == '<') { p = i; continue; } if (p != string::npos) { if (s[i] == '>') { string a (s, p + 1, i - p - 1); if (set.find (a) != set.end ()) { r += "\\i{"; for (size_t j (0), n (a.size ()); j < n; ++j) { if (a[j] == '}' && (j == 0 || a[j - 1] != '\\')) r += "\\}"; else r += a[j]; } r += '}'; } else { r += '<'; r += a; r += '>'; } p = string::npos; } continue; } r += s[i]; } // If we found the opening '<' but no closing '>', add the rest. // if (p != string::npos) r.append (s, p, string::npos); return r; } void context:: format_line (output_type ot, string& r, const char* s, size_t n) { bool escape (false); stack blocks; // Bit 0: code; 1: italic; 2: bold. for (size_t i (0); i < n; ++i) { char c (s[i]); if (escape) { bool block (false); switch (c) { case '\\': { switch (ot) { case ot_man: { r += "\\e"; break; } default: { r += '\\'; break; } } break; } case '"': { r += '"'; break; } case '\'': { r += '\''; break; } case 'c': { unsigned char b (1); size_t j (i + 1); if (j < n) { if (s[j] == 'i') { b |= 2; j++; if (j < n && s[j] == 'b') { b |= 4; j++; } } else if (s[j] == 'b') { b |= 4; j++; if (j < n && s[j] == 'i') { b |= 2; j++; } } } if (j < n && s[j] == '{') { i = j; blocks.push (b); block = true; break; } r += 'c'; break; } case 'i': { unsigned char b (2); size_t j (i + 1); if (j < n) { if (s[j] == 'c') { b |= 1; j++; if (j < n && s[j] == 'b') { b |= 4; j++; } } else if (s[j] == 'b') { b |= 4; j++; if (j < n && s[j] == 'c') { b |= 1; j++; } } } if (j < n && s[j] == '{') { i = j; blocks.push (b); block = true; break; } r += 'i'; break; } case 'b': { unsigned char b (4); size_t j (i + 1); if (j < n) { if (s[j] == 'c') { b |= 1; j++; if (j < n && s[j] == 'i') { b |= 2; j++; } } else if (s[j] == 'i') { b |= 2; j++; if (j < n && s[j] == 'c') { b |= 1; j++; } } } if (j < n && s[j] == '{') { i = j; blocks.push (b); block = true; break; } r += 'b'; break; } case '}': { r += '}'; break; } case '|': { r += '|'; break; } default: { cerr << "error: unknown escape sequence '\\" << c << "' in " << "documentation paragraph '" << string (s, 0, n) << "'" << endl; throw generation_failed (); } } // If we just added a new block, add corresponding output markup. // if (block) { unsigned char b (blocks.top ()); switch (ot) { case ot_plain: { if (b & 1) r += "'"; break; } case ot_html: { if (b & 1) r += ""; if (b & 2) r += ""; if (b & 4) r += ""; break; } case ot_man: { if ((b & 6) == 6) r += "\\f(BI"; else if (b & 2) r += "\\fI"; else if (b & 4) r += "\\fB"; break; } } } escape = false; } else // Not escape. { switch (c) { case '\\': { escape = true; break; } case '.': { if (ot == ot_man) r += "\\."; else r += '.'; break; } case '}': { if (!blocks.empty ()) { unsigned char b (blocks.top ()); switch (ot) { case ot_plain: { if (b & 1) r += "'"; break; } case ot_html: { if (b & 4) r += ""; if (b & 2) r += ""; if (b & 1) r += ""; break; } case ot_man: { if (b & 6) r += "\\fP"; break; } } blocks.pop (); break; } // Fall through. } default: r += c; break; } } } if (escape) { cerr << "error: unterminated escape sequence in documentation " << "paragraph '" << string (s, 0, n) << "'" << endl; throw generation_failed (); } if (!blocks.empty ()) { unsigned char b (blocks.top ()); string bs; if (b & 1) bs += 'c'; if (b & 2) bs += 'i'; if (b & 4) bs += 'b'; cerr << "error: unterminated formatting block '\\" << bs << "' " << "in documentation paragraph '" << string (s, 0, n) << "'" << endl; throw generation_failed (); } } struct block { enum value {h, ul, ol, dl, li, text} v_; block (value v = text): v_ (v) {} operator value () const {return v_;} }; const char* block_str[] = {"\\h", "\\ul", "\\ol", "\\dl", "\\li", "text"}; inline ostream& operator<< (ostream& os, block b) { return os << block_str[b]; } string context:: format (output_type ot, string const& s, bool first_para) { string r; stack blocks; // Flag that indicates whether the next fragment of text should start // in its own paragraph. // bool para (first_para); // Iterate over lines (paragraphs) or pre-formatted sections. // for (size_t b (0), e; ; b = e + 1) { bool pre (s[b] == 0x02); bool last; if (pre) { e = s.find (0x03, ++b); assert (e != string::npos); last = (e + 1 == s.size ()); } else { e = s.find ('\n', b); last = (e == string::npos); } const char* l (s.c_str () + b); size_t n ((e != string::npos ? e : s.size ()) - b); if (pre) { ++e; // Skip newline that follows 0x03. if (ot == ot_html) r += "
";

      r.append (l, n);

      if (ot == ot_html)
        r += "
"; para = true; } else { const char* ol (l); // Original, full line, for diagnostics. size_t on (n); // First determine what kind of paragraph block this is (i.e., // handle the "prefix"). // block b (block::text); if (n >= 3 && strncmp (l, "\\h|", 3) == 0) { b = block::h; l += 3; n -= 3; } else if (n >= 4 && (strncmp (l, "\\ul|", 4) == 0 || strncmp (l, "\\ol|", 4) == 0 || strncmp (l, "\\dl|", 4) == 0)) { switch (l[1]) { case 'u': b = block::ul; break; case 'o': b = block::ol; break; case 'd': b = block::dl; break; } l += 4; n -= 4; } else if (n >= 4 && strncmp (l, "\\li|", 4) == 0) { b = block::li; l += 4; n -= 4; } // Skip leading spaces after opening '|'. // while (n != 0 && (*l == 0x20 || *l == 0x0D || *l == 0x09)) {l++; n--;} // Next figure out how many blocks we need to pop at the end of this // paragraph (i.e., handle the "suffix"). Things get a bit complicated // since '|' could be escaped. // size_t pc (0); // Pop count. for (; n - pc > 0 && l[n - pc - 1] == '|'; ++pc) ; if (pc != 0) { // To determine whether the first '|' is part of an escape sequence // we have to find the first non-backslash character and then figure // out who escapes whom. // size_t ec (0); // Escape count. for (; n - pc - ec > 0 && l[n - pc - ec - 1] == '\\'; ++ec) ; // If we have an odd number of backslashes, then the last '|' is // escaped. // if (ec % 2 != 0) --pc; n -= pc; // Number of special '|' at the end. // Skip trailing spaces before closing '|'. // while (n != 0 && (l[n - 1] == 0x20 || l[n - 1] == 0x0D || l[n - 1] == 0x09)) n--; } if (pc > blocks.size () + (b != block::text ? 1 : 0)) { cerr << "error: extraneous '|' at the end of paragraph '" << string (ol, 0, on) << "'" << endl; throw generation_failed (); } // Verify that this block type is valid in this context. Skip // empty text blocks (can happen if we just have '|'). // if (b != block::text || n != 0) { bool good (true); block ob (blocks.empty () ? block (block::text) : blocks.top ()); switch (ob) { case block::h: good = false; break; case block::ul: case block::ol: case block::dl: good = (b == block::li); break; case block::li: good = (b == block::text); break; case block::text: good = (b != block::li); break; } if (!good) { cerr << "error: " << b << " inside " << ob << " " << "in documentation string '" << s << "'" << endl; throw generation_failed (); } } // Verify the block itself. // switch (b) { case block::h: // \h blocks are only valid if we are required to start a new // paragraph (first_para is true). // if (!first_para) { cerr << "error: paragraph '" << string (ol, 0, on) << "' " << "not allowed in '" << s << "'" << endl; throw generation_failed (); } // \h must be single-paragraph. // if (pc == 0) { cerr << "error: '|' expected at the end of paragraph '" << string (ol, 0, on) << "'" << endl; throw generation_failed (); } // \h must not be empty. // if (n == 0) { cerr << "error: empty paragraph '" << string (ol, 0, on) << "' " << "in documentation string '" << s << "'" << endl; throw generation_failed (); } break; case block::ul: case block::ol: case block::dl: if (pc != 0) { cerr << "error: empty list '" << string (ol, 0, on) << "' " << "in documentation string '" << s << "'" << endl; throw generation_failed (); } if (n != 0) { cerr << "error: unexpected text after " << b << "| " << "in paragraph '" << string (ol, 0, on) << "'" << endl; throw generation_failed (); } break; case block::li: if (blocks.top () == block::dl) { if (n == 0) { cerr << "error: term text missing in paragraph '" << string (ol, 0, on) << "'" << endl; throw generation_failed (); } } break; case block::text: break; } // Output opening markup. // if (ot == ot_html) { switch (b) { case block::h: r += "

"; break; case block::ul: r += "
    "; break; case block::ol: r += "
      "; break; case block::dl: r += "
      "; break; case block::li: r += (blocks.top () == block::dl ? "
      " : "
    1. "); break; case block::text: if (n != 0 && para) r += "

      "; break; } } // Output paragraph text. // if (n != 0) format_line (ot, r, l, n); // Output intermediate markup, if any. // if (ot == ot_html) { switch (b) { case block::li: if (blocks.top () == block::dl) r += "

    2. \n
      "; break; case block::text: if (n != 0 && para) r += "

      "; break; default: break; } } // Set the para flag. // switch (b) { case block::li: para = (blocks.top () != block::dl); break; case block::text: para = para || (n != 0); break; default: para = true; break; } // Push the paragraph block. // if (b != block::text) blocks.push (b); // Pop paragraph blocks. // for (; pc != 0; --pc) { b = blocks.top (); blocks.pop (); if (ot == ot_html) { switch (b) { case block::h: r += "

"; break; case block::ul: r += ""; break; case block::ol: r += ""; break; case block::dl: r += ""; break; case block::li: r += blocks.top () == block::dl ? "" : ""; break; case block::text: break; } if (pc != 1) // Add empty line unless this is the last separator. r += "\n\n"; } para = true; // End of a block always means new paragraph. } } if (last) break; // Separate paragraphs with newline. // if (para) r += "\n\n"; } if (!blocks.empty ()) { cerr << "error: unterminated paragraph " << blocks.top () << " " << "in documentation string '" << s << "'" << endl; throw generation_failed (); } return r; } string context:: fq_name (semantics::nameable& n, bool cxx_name) { using namespace semantics; string r; if (dynamic_cast (&n)) { return ""; // Map to global namespace. } else { r = fq_name (n.scope ()); r += "::"; r += cxx_name ? escape (n.name ()) : n.name (); } return r; } void context:: cli_open () { string::size_type b (0), e; do { e = cli.find ("::", b); string n (cli, b, e == string::npos ? e : e - b); if (!n.empty ()) os << "namespace " << n << "{"; b = e; if (b == string::npos) break; b += 2; } while (true); } void context:: cli_close () { string::size_type b (0), e; do { e = cli.find ("::", b); string n (cli, b, e == string::npos ? e : e - b); if (!n.empty ()) os << "}"; b = e; if (b == string::npos) break; b += 2; } while (true); } // namespace // void namespace_:: pre (type& ns) { string name (ns.name ()); if (!name.empty ()) os << "namespace " << escape (name) << "{"; } void namespace_:: post (type& ns) { if (!ns.name ().empty ()) os << "}"; }