/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
deps/merve/merve.cpp
2 137 строк
67 KB
Node.js GitHub Bot
deps: update merve to 1.2.2
13 мар 2026, 20:21
Не верифицирован
13 мар 2026, 20:21
aac5b68
Код
Авторство
О чём код?
/* auto-generated on 2026-03-11 12:53:21 -0400. Do not edit! */ #include "merve.h" /* begin file src/parser.cpp */ #include <array> #include <cstdint> #include <limits> #ifdef MERVE_USE_SIMDUTF #include <simdutf.h> #endif namespace lexer { // ============================================================================ // Compile-time lookup tables for character classification // ============================================================================ // Hex digit lookup table: maps char -> hex value (0-15), or 255 if invalid static constexpr std::array<uint8_t, 256> kHexTable = []() consteval { std::array<uint8_t, 256> table{}; for (int i = 0; i < 256; ++i) table[i] = 255; for (int i = '0'; i <= '9'; ++i) table[i] = static_cast<uint8_t>(i - '0'); for (int i = 'a'; i <= 'f'; ++i) table[i] = static_cast<uint8_t>(i - 'a' + 10); for (int i = 'A'; i <= 'F'; ++i) table[i] = static_cast<uint8_t>(i - 'A' + 10); return table; }(); // Simple escape lookup table: maps escape char -> result char // Uses 0xFF as "not a simple escape" marker since '\0' is a valid escape result static constexpr std::array<uint8_t, 256> kSimpleEscapeTable = []() consteval { std::array<uint8_t, 256> table{}; for (int i = 0; i < 256; ++i) table[i] = 0xFF; table['n'] = '\n'; table['r'] = '\r'; table['t'] = '\t'; table['b'] = '\b'; table['f'] = '\f'; table['v'] = '\v'; table['0'] = '\0'; table['\\'] = '\\'; table['\''] = '\''; table['"'] = '"'; return table; }(); // Punctuator lookup table static constexpr std::array<bool, 256> kPunctuatorTable = []() consteval { std::array<bool, 256> table{}; table['!'] = true; table['%'] = true; table['&'] = true; // ch > 39 && ch < 48: '(' ')' '*' '+' ',' '-' '.' '/' for (int i = 40; i < 48; ++i) table[i] = true; // ch > 57 && ch < 64: ':' ';' '<' '=' '>' '?' for (int i = 58; i < 64; ++i) table[i] = true; table['['] = true; table[']'] = true; table['^'] = true; // ch > 122 && ch < 127: '{' '|' '}' '~' for (int i = 123; i < 127; ++i) table[i] = true; return table; }(); // Expression punctuator lookup table (similar but excludes ')' and '}') static constexpr std::array<bool, 256> kExpressionPunctuatorTable = []() consteval { std::array<bool, 256> table{}; table['!'] = true; table['%'] = true; table['&'] = true; // ch > 39 && ch < 47 && ch != 41: '(' '*' '+' ',' '-' '.' for (int i = 40; i < 47; ++i) { if (i != 41) table[i] = true; // Skip ')' } // ch > 57 && ch < 64: ':' ';' '<' '=' '>' '?' for (int i = 58; i < 64; ++i) table[i] = true; table['['] = true; table['^'] = true; // ch > 122 && ch < 127 && ch != '}': '{' '|' '~' for (int i = 123; i < 127; ++i) { if (i != 125) table[i] = true; // Skip '}' } return table; }(); // Identifier start lookup table (a-z, A-Z, _, $, >= 0x80) static constexpr std::array<bool, 256> kIdentifierStartTable = []() consteval { std::array<bool, 256> table{}; for (int i = 'a'; i <= 'z'; ++i) table[i] = true; for (int i = 'A'; i <= 'Z'; ++i) table[i] = true; table['_'] = true; table['$'] = true; // UTF-8 continuation bytes and lead bytes (>= 0x80) for (int i = 0x80; i < 256; ++i) table[i] = true; return table; }(); // Identifier char lookup table (identifier start + digits) static constexpr std::array<bool, 256> kIdentifierCharTable = []() consteval { std::array<bool, 256> table{}; for (int i = 'a'; i <= 'z'; ++i) table[i] = true; for (int i = 'A'; i <= 'Z'; ++i) table[i] = true; table['_'] = true; table['$'] = true; for (int i = 0x80; i < 256; ++i) table[i] = true; for (int i = '0'; i <= '9'; ++i) table[i] = true; return table; }(); // Whitespace/line break lookup table static constexpr std::array<bool, 256> kBrOrWsTable = []() consteval { std::array<bool, 256> table{}; // c > 8 && c < 14: \t \n \v \f \r for (int i = 9; i < 14; ++i) table[i] = true; table[32] = true; // space return table; }(); // ============================================================================ // Inline functions using lookup tables // ============================================================================ // Parse a hex digit, returns -1 if invalid inline int hexDigit(unsigned char c) { uint8_t val = kHexTable[c]; return val == 255 ? -1 : static_cast<int>(val); } // Encode a Unicode code point as UTF-8 into the output string inline void encodeUtf8(std::string& out, uint32_t codepoint) { #ifdef MERVE_USE_SIMDUTF // Use simdutf for optimized UTF-32 to UTF-8 conversion char buf[4]; size_t len = simdutf::convert_utf32_to_utf8( reinterpret_cast<const char32_t*>(&codepoint), 1, buf); out.append(buf, len); #else if (codepoint <= 0x7F) { out.push_back(static_cast<char>(codepoint)); } else if (codepoint <= 0x7FF) { out.push_back(static_cast<char>(0xC0 | (codepoint >> 6))); out.push_back(static_cast<char>(0x80 | (codepoint & 0x3F))); } else if (codepoint <= 0xFFFF) { out.push_back(static_cast<char>(0xE0 | (codepoint >> 12))); out.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F))); out.push_back(static_cast<char>(0x80 | (codepoint & 0x3F))); } else if (codepoint <= 0x10FFFF) { out.push_back(static_cast<char>(0xF0 | (codepoint >> 18))); out.push_back(static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F))); out.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F))); out.push_back(static_cast<char>(0x80 | (codepoint & 0x3F))); } #endif } // Unescape JavaScript string escape sequences // Returns empty optional on invalid escape sequences (like lone surrogates) std::optional<std::string> unescapeJsString(std::string_view str) { std::string result; result.reserve(str.size()); for (size_t i = 0; i < str.size(); ++i) { if (str[i] != '\\') { result.push_back(str[i]); continue; } if (++i >= str.size()) { return std::nullopt; // Trailing backslash } // Check simple escape table first (single character escapes) uint8_t simple = kSimpleEscapeTable[static_cast<unsigned char>(str[i])]; if (simple != 0xFF) { result.push_back(static_cast<char>(simple)); continue; } // Handle complex escapes switch (str[i]) { case 'x': { // \xHH - two hex digits if (i + 2 >= str.size()) return std::nullopt; int h1 = hexDigit(static_cast<unsigned char>(str[i + 1])); int h2 = hexDigit(static_cast<unsigned char>(str[i + 2])); if (h1 < 0 || h2 < 0) return std::nullopt; result.push_back(static_cast<char>((h1 << 4) | h2)); i += 2; break; } case 'u': { if (i + 1 >= str.size()) return std::nullopt; if (str[i + 1] == '{') { // \u{XXXX} - variable length hex size_t start = i + 2; size_t end_brace = str.find('}', start); if (end_brace == std::string_view::npos || end_brace == start) return std::nullopt; uint32_t codepoint = 0; for (size_t j = start; j < end_brace; ++j) { int digit = hexDigit(static_cast<unsigned char>(str[j])); if (digit < 0) return std::nullopt; codepoint = (codepoint << 4) | static_cast<uint32_t>(digit); if (codepoint > 0x10FFFF) return std::nullopt; // Invalid codepoint } // Handle surrogate pairs in \u{XXXX} format if (codepoint >= 0xD800 && codepoint <= 0xDBFF) { // High surrogate - check for low surrogate \u{XXXX} if (end_brace + 3 < str.size() && str[end_brace + 1] == '\\' && str[end_brace + 2] == 'u' && str[end_brace + 3] == '{') { size_t low_start = end_brace + 4; size_t low_end = str.find('}', low_start); if (low_end != std::string_view::npos && low_end > low_start) { uint32_t low = 0; bool valid_low = true; for (size_t j = low_start; j < low_end; ++j) { int digit = hexDigit(static_cast<unsigned char>(str[j])); if (digit < 0) { valid_low = false; break; } low = (low << 4) | static_cast<uint32_t>(digit); } if (valid_low && low >= 0xDC00 && low <= 0xDFFF) { // Valid surrogate pair - combine into single codepoint codepoint = 0x10000 + ((codepoint - 0xD800) << 10) + (low - 0xDC00); end_brace = low_end; // Skip past the low surrogate } else { // Lone high surrogate return std::nullopt; } } else { // Lone high surrogate return std::nullopt; } } else { // Lone high surrogate return std::nullopt; } } else if (codepoint >= 0xDC00 && codepoint <= 0xDFFF) { // Lone low surrogate return std::nullopt; } encodeUtf8(result, codepoint); i = end_brace; } else { // \uHHHH - exactly four hex digits if (i + 4 >= str.size()) return std::nullopt; uint32_t codepoint = 0; for (int j = 1; j <= 4; ++j) { int digit = hexDigit(static_cast<unsigned char>(str[i + static_cast<size_t>(j)])); if (digit < 0) return std::nullopt; codepoint = (codepoint << 4) | static_cast<uint32_t>(digit); } // Handle surrogate pairs if (codepoint >= 0xD800 && codepoint <= 0xDBFF) { // High surrogate - check for low surrogate if (i + 10 < str.size() && str[i + 5] == '\\' && str[i + 6] == 'u') { uint32_t low = 0; bool valid_low = true; for (int j = 7; j <= 10; ++j) { int digit = hexDigit(static_cast<unsigned char>(str[i + static_cast<size_t>(j)])); if (digit < 0) { valid_low = false; break; } low = (low << 4) | static_cast<uint32_t>(digit); } if (valid_low && low >= 0xDC00 && low <= 0xDFFF) { // Valid surrogate pair - combine into single codepoint codepoint = 0x10000 + ((codepoint - 0xD800) << 10) + (low - 0xDC00); i += 6; // Skip the low surrogate } else { // Lone high surrogate return std::nullopt; } } else { // Lone high surrogate return std::nullopt; } } else if (codepoint >= 0xDC00 && codepoint <= 0xDFFF) { // Lone low surrogate return std::nullopt; } encodeUtf8(result, codepoint); i += 4; } break; } default: // Unknown escape - just include the character as-is result.push_back(str[i]); break; } } return result; } // Stack depth limits constexpr size_t STACK_DEPTH = 2048; constexpr size_t MAX_STAR_EXPORTS = 256; // RequireType enum for parsing require statements enum class RequireType { Import, ExportAssign, ExportStar }; // StarExportBinding structure for tracking star export bindings struct StarExportBinding { std::string_view specifier; std::string_view id; }; // Thread-local state for error tracking (safe for concurrent parse calls). thread_local std::optional<lexer_error> last_error; thread_local std::optional<error_location> last_error_location; static error_location makeErrorLocation(const char* source, const char* end, const char* at) { const char* target = at; if (target < source) target = source; if (target > end) target = end; uint32_t line = 1; uint32_t column = 1; const char* cur = source; while (cur < target) { const char ch = *cur++; if (ch == '\n') { line++; column = 1; continue; } if (ch == '\r') { line++; column = 1; if (cur < target && *cur == '\n') { cur++; } continue; } column++; } error_location loc{}; loc.line = line; loc.column = column; return loc; } // Lexer state class class CJSLexer { private: const char* source; const char* pos; const char* end; const char* lastTokenPos; uint16_t templateStackDepth; uint16_t openTokenDepth; uint16_t templateDepth; uint32_t line; bool lastSlashWasDivision; bool nextBraceIsClass; std::array<uint16_t, STACK_DEPTH> templateStack_; std::array<const char*, STACK_DEPTH> openTokenPosStack_; std::array<char, STACK_DEPTH> openTokenTypeStack_; std::array<bool, STACK_DEPTH> openClassPosStack; std::array<StarExportBinding, MAX_STAR_EXPORTS> starExportStack_; StarExportBinding* starExportStack; const StarExportBinding* STAR_EXPORT_STACK_END; std::vector<export_entry>& exports; std::vector<export_entry>& re_exports; // Increments `line` when consuming a line terminator. // - Counts '\n' as a newline. // - Counts '\r' as a newline only when it is not part of a CRLF sequence. // (i.e., the next character is not '\n' or we're at end-of-input.) void countNewline(char ch) { line += (ch == '\n') || (ch == '\r' && (pos + 1 >= end || *(pos + 1) != '\n')); } // Character classification helpers using lookup tables static bool isBr(char c) { return c == '\r' || c == '\n'; } static bool isBrOrWs(unsigned char c) { return kBrOrWsTable[c]; } static bool isBrOrWsOrPunctuatorNotDot(unsigned char c) { return kBrOrWsTable[c] || (kPunctuatorTable[c] && c != '.'); } static bool isPunctuator(unsigned char ch) { return kPunctuatorTable[ch]; } static bool isExpressionPunctuator(unsigned char ch) { return kExpressionPunctuatorTable[ch]; } // String comparison helpers using string_view for cleaner, more maintainable code static constexpr bool matchesAt(const char* p, const char* end_pos, std::string_view expected) { size_t available = static_cast<size_t>(end_pos - p); if (available < expected.size()) return false; for (size_t i = 0; i < expected.size(); ++i) { if (p[i] != expected[i]) return false; } return true; } // Character type detection using lookup tables static bool isIdentifierStart(uint8_t ch) { return kIdentifierStartTable[ch]; } static bool isIdentifierChar(uint8_t ch) { return kIdentifierCharTable[ch]; } constexpr bool keywordStart(const char* p) const { return p == source || isBrOrWsOrPunctuatorNotDot(*(p - 1)); } constexpr bool readPrecedingKeyword(const char* p, std::string_view keyword) const { if (p - static_cast<ptrdiff_t>(keyword.size()) + 1 < source) return false; const char* start = p - keyword.size() + 1; return matchesAt(start, end, keyword) && (start == source || isBrOrWsOrPunctuatorNotDot(*(start - 1))); } // Keyword detection constexpr bool isExpressionKeyword(const char* p) const { switch (*p) { case 'd': switch (*(p - 1)) { case 'i': return readPrecedingKeyword(p - 2, "vo"); case 'l': return readPrecedingKeyword(p - 2, "yie"); default: return false; } case 'e': switch (*(p - 1)) { case 's': switch (*(p - 2)) { case 'l': return p - 3 >= source && *(p - 3) == 'e' && keywordStart(p - 3); case 'a': return p - 3 >= source && *(p - 3) == 'c' && keywordStart(p - 3); default: return false; } case 't': return readPrecedingKeyword(p - 2, "dele"); default: return false; } case 'f': if (*(p - 1) != 'o' || *(p - 2) != 'e') return false; switch (*(p - 3)) { case 'c': return readPrecedingKeyword(p - 4, "instan"); case 'p': return readPrecedingKeyword(p - 4, "ty"); default: return false; } case 'n': return (p - 1 >= source && *(p - 1) == 'i' && keywordStart(p - 1)) || readPrecedingKeyword(p - 1, "retur"); case 'o': return p - 1 >= source && *(p - 1) == 'd' && keywordStart(p - 1); case 'r': return readPrecedingKeyword(p - 1, "debugge"); case 't': return readPrecedingKeyword(p - 1, "awai"); case 'w': switch (*(p - 1)) { case 'e': return p - 2 >= source && *(p - 2) == 'n' && keywordStart(p - 2); case 'o': return readPrecedingKeyword(p - 2, "thr"); default: return false; } } return false; } constexpr bool isParenKeyword(const char* curPos) const { return readPrecedingKeyword(curPos, "while") || readPrecedingKeyword(curPos, "for") || readPrecedingKeyword(curPos, "if"); } constexpr bool isExpressionTerminator(const char* curPos) const { switch (*curPos) { case '>': return *(curPos - 1) == '='; case ';': case ')': return true; case 'h': return readPrecedingKeyword(curPos - 1, "catc"); case 'y': return readPrecedingKeyword(curPos - 1, "finall"); case 'e': return readPrecedingKeyword(curPos - 1, "els"); } return false; } // Parsing utilities void syntaxError(lexer_error code, const char* at = nullptr) { if (!last_error) { last_error = code; const char* error_pos = at ? at : pos; last_error_location = makeErrorLocation(source, end, error_pos); } pos = end + 1; } char commentWhitespace() { char ch; do { if (pos >= end) return '\0'; ch = *pos; if (ch == '/') { char next_ch = pos + 1 < end ? *(pos + 1) : '\0'; if (next_ch == '/') lineComment(); else if (next_ch == '*') blockComment(); else return ch; } else if (!isBrOrWs(ch)) { return ch; } else { countNewline(ch); } } while (pos++ < end); return ch; } void lineComment() { while (pos++ < end) { char ch = *pos; if (ch == '\n' || ch == '\r') { countNewline(ch); return; } } } void blockComment() { pos++; while (pos++ < end) { char ch = *pos; if (ch == '*' && *(pos + 1) == '/') { pos++; return; } countNewline(ch); } } void stringLiteral(char quote) { while (pos++ < end) { char ch = *pos; if (ch == quote) return; if (ch == '\\') { if (pos + 1 >= end) break; ch = *++pos; if (ch == '\r') { ++line; if (*(pos + 1) == '\n') pos++; } else if (ch == '\n') { ++line; } } else if (isBr(ch)) break; } syntaxError(lexer_error::UNTERMINATED_STRING_LITERAL); } void regularExpression() { while (pos++ < end) { char ch = *pos; if (ch == '/') return; if (ch == '[') { regexCharacterClass(); } else if (ch == '\\') { if (pos + 1 < end) pos++; } else if (ch == '\n' || ch == '\r') break; } syntaxError(lexer_error::UNTERMINATED_REGEX); } void regexCharacterClass() { while (pos++ < end) { char ch = *pos; if (ch == ']') return; if (ch == '\\') { if (pos + 1 < end) pos++; } else if (ch == '\n' || ch == '\r') break; } syntaxError(lexer_error::UNTERMINATED_REGEX_CHARACTER_CLASS); } void templateString() { while (pos++ < end) { char ch = *pos; if (ch == '$' && *(pos + 1) == '{') { pos++; if (templateStackDepth >= STACK_DEPTH) { syntaxError(lexer_error::TEMPLATE_NEST_OVERFLOW); return; } templateStack_[templateStackDepth++] = templateDepth; templateDepth = ++openTokenDepth; return; } if (ch == '`') return; if (ch == '\\' && pos + 1 < end) { pos++; countNewline(*pos); } else { countNewline(ch); } } syntaxError(lexer_error::UNTERMINATED_TEMPLATE_STRING); } bool identifier(char startCh) { if (!isIdentifierStart(static_cast<uint8_t>(startCh))) return false; pos++; while (pos < end) { char ch = *pos; if (isIdentifierChar(static_cast<uint8_t>(ch))) { pos++; } else { break; } } return true; } // Check if string contains escape sequences static bool needsUnescaping(std::string_view str) { #ifdef MERVE_USE_SIMDUTF // simdutf provides fast SIMD-based ASCII validation // If the string is valid ASCII without high bytes, we can use a faster path // But we still need to check for backslash const char* ptr = simdutf::find(str.data(), str.data() + str.size(), '\\'); return ptr != str.data() + str.size(); #else return str.find('\\') != std::string_view::npos; #endif } void addExport(std::string_view export_name, uint32_t at_line) { // Skip surrounding quotes if present if (!export_name.empty() && (export_name.front() == '\'' || export_name.front() == '"')) { export_name.remove_prefix(1); export_name.remove_suffix(1); } // Fast path: no escaping needed, use string_view directly if (!needsUnescaping(export_name)) { // Check if this export already exists (avoid duplicates) for (const auto& existing : exports) { if (get_string_view(existing.name) == export_name) { return; // Already exists, skip } } exports.push_back(export_entry{export_name, at_line}); return; } // Slow path: unescape the export name (handles \u{XXXX}, \uHHHH, etc.) // Returns nullopt for invalid sequences like lone surrogates auto unescaped = unescapeJsString(export_name); if (!unescaped.has_value()) { return; // Skip invalid escape sequences } const std::string& name = unescaped.value(); // Check if this export already exists (avoid duplicates) for (const auto& existing : exports) { if (get_string_view(existing.name) == name) { return; // Already exists, skip } } exports.push_back(export_entry{std::move(unescaped.value()), at_line}); } void addReexport(std::string_view reexport_name, uint32_t at_line) { // Skip surrounding quotes if present if (!reexport_name.empty() && (reexport_name.front() == '\'' || reexport_name.front() == '"')) { reexport_name.remove_prefix(1); reexport_name.remove_suffix(1); } // Fast path: no escaping needed, use string_view directly if (!needsUnescaping(reexport_name)) { re_exports.push_back(export_entry{reexport_name, at_line}); return; } // Slow path: unescape the reexport name auto unescaped = unescapeJsString(reexport_name); if (!unescaped.has_value()) { return; // Skip invalid escape sequences } re_exports.push_back(export_entry{std::move(unescaped.value()), at_line}); } bool readExportsOrModuleDotExports(char ch) { const char* revertPos = pos; if (ch == 'm' && matchesAt(pos + 1, end, "odule")) { pos += 6; ch = commentWhitespace(); if (ch != '.') { pos = revertPos; return false; } pos++; ch = commentWhitespace(); } if (ch == 'e' && matchesAt(pos + 1, end, "xports")) { pos += 7; return true; } pos = revertPos; return false; } bool tryParseRequire(RequireType requireType) { const char* revertPos = pos; if (!matchesAt(pos + 1, end, "equire")) { return false; } pos += 7; char ch = commentWhitespace(); if (ch == '(') { pos++; ch = commentWhitespace(); const char* reexportStart = pos; if (ch == '\'' || ch == '"') { stringLiteral(ch); const char* reexportEnd = ++pos; ch = commentWhitespace(); if (ch == ')') { switch (requireType) { case RequireType::ExportStar: case RequireType::ExportAssign: addReexport(std::string_view(reexportStart, reexportEnd - reexportStart), line); return true; default: if (starExportStack < STAR_EXPORT_STACK_END) { starExportStack->specifier = std::string_view(reexportStart, reexportEnd - reexportStart); } return true; } } } } pos = revertPos; return false; } // Helper to parse property value in object literal (identifier or require()) bool tryParsePropertyValue(char& ch) { if (ch == 'r' && tryParseRequire(RequireType::ExportAssign)) { ch = *pos; return true; } if (identifier(ch)) { ch = *pos; return true; } return false; } void tryParseLiteralExports() { const char* revertPos = pos - 1; while (pos++ < end) { char ch = commentWhitespace(); const char* startPos = pos; if (identifier(ch)) { const char* endPos = pos; ch = commentWhitespace(); // Check if this is a getter syntax: get identifier() { ... } if (ch != ':' && endPos - startPos == 3 && matchesAt(startPos, end, "get") && identifier(ch)) { ch = commentWhitespace(); if (ch == '(') { // This is a getter, stop parsing here (early termination) pos = revertPos; return; } } if (ch == ':') { pos++; ch = commentWhitespace(); if (!tryParsePropertyValue(ch)) { pos = revertPos; return; } } addExport(std::string_view(startPos, endPos - startPos), line); } else if (ch == '\'' || ch == '"') { const char* start = pos; stringLiteral(ch); const char* end_pos = ++pos; ch = commentWhitespace(); if (ch == ':') { pos++; ch = commentWhitespace(); if (!tryParsePropertyValue(ch)) { pos = revertPos; return; } addExport(std::string_view(start, end_pos - start), line); } } else if (ch == '.' && matchesAt(pos + 1, end, "..")) { pos += 3; if (pos < end && *pos == 'r' && tryParseRequire(RequireType::ExportAssign)) { pos++; } else if (pos < end && !identifier(*pos)) { pos = revertPos; return; } ch = commentWhitespace(); } else { pos = revertPos; return; } if (ch == '}') return; if (ch != ',') { pos = revertPos; return; } } } void tryParseExportsDotAssign(bool assign) { pos += 7; const char* revertPos = pos - 1; char ch = commentWhitespace(); switch (ch) { case '.': { pos++; ch = commentWhitespace(); const char* startPos = pos; if (identifier(ch)) { const char* endPos = pos; ch = commentWhitespace(); if (ch == '=') { addExport(std::string_view(startPos, endPos - startPos), line); return; } } break; } case '[': { pos++; ch = commentWhitespace(); if (ch == '\'' || ch == '"') { const char* startPos = pos; stringLiteral(ch); const char* endPos = ++pos; ch = commentWhitespace(); if (ch != ']') break; pos++; ch = commentWhitespace(); if (ch != '=') break; addExport(std::string_view(startPos, endPos - startPos), line); } break; } case '=': { if (assign) { re_exports.clear(); pos++; ch = commentWhitespace(); if (ch == '{') { tryParseLiteralExports(); return; } if (ch == 'r') tryParseRequire(RequireType::ExportAssign); } break; } } pos = revertPos; } void tryParseModuleExportsDotAssign() { pos += 6; const char* revertPos = pos - 1; char ch = commentWhitespace(); if (ch == '.') { pos++; ch = commentWhitespace(); if (ch == 'e' && matchesAt(pos + 1, end, "xports")) { tryParseExportsDotAssign(true); return; } } pos = revertPos; } bool tryParseObjectHasOwnProperty(std::string_view it_id) { char ch = commentWhitespace(); if (ch != 'O' || !matchesAt(pos + 1, end, "bject")) return false; pos += 6; ch = commentWhitespace(); if (ch != '.') return false; pos++; ch = commentWhitespace(); if (ch == 'p') { if (!matchesAt(pos + 1, end, "rototype")) return false; pos += 9; ch = commentWhitespace(); if (ch != '.') return false; pos++; ch = commentWhitespace(); } if (ch != 'h' || !matchesAt(pos + 1, end, "asOwnProperty")) return false; pos += 14; ch = commentWhitespace(); if (ch != '.') return false; pos++; ch = commentWhitespace(); if (ch != 'c' || !matchesAt(pos + 1, end, "all")) return false; pos += 4; ch = commentWhitespace(); if (ch != '(') return false; pos++; ch = commentWhitespace(); if (!identifier(ch)) return false; ch = commentWhitespace(); if (ch != ',') return false; pos++; ch = commentWhitespace(); if (!matchesAt(pos, end, it_id)) return false; pos += it_id.size(); ch = commentWhitespace(); if (ch != ')') return false; pos++; return true; } void tryParseObjectDefineOrKeys(bool keys) { pos += 6; const char* revertPos = pos - 1; char ch = commentWhitespace(); if (ch == '.') { pos++; ch = commentWhitespace(); if (ch == 'd' && matchesAt(pos + 1, end, "efineProperty")) { const char* exportStart = nullptr; const char* exportEnd = nullptr; while (true) { pos += 14; revertPos = pos - 1; ch = commentWhitespace(); if (ch != '(') break; pos++; ch = commentWhitespace(); if (!readExportsOrModuleDotExports(ch)) break; ch = commentWhitespace(); if (ch != ',') break; pos++; ch = commentWhitespace(); if (ch != '\'' && ch != '"') break; exportStart = pos; stringLiteral(ch); exportEnd = ++pos; ch = commentWhitespace(); if (ch != ',') break; pos++; ch = commentWhitespace(); if (ch != '{') break; pos++; ch = commentWhitespace(); if (ch == 'e') { if (!matchesAt(pos + 1, end, "numerable")) break; pos += 10; ch = commentWhitespace(); if (ch != ':') break; pos++; ch = commentWhitespace(); if (ch != 't' || !matchesAt(pos + 1, end, "rue")) break; pos += 4; ch = commentWhitespace(); if (ch != ',') break; pos++; ch = commentWhitespace(); } if (ch == 'v') { if (!matchesAt(pos + 1, end, "alue")) break; pos += 5; ch = commentWhitespace(); if (ch != ':') break; if (exportStart && exportEnd) addExport(std::string_view(exportStart, exportEnd - exportStart), line); pos = revertPos; return; } else if (ch == 'g') { if (!matchesAt(pos + 1, end, "et")) break; pos += 3; ch = commentWhitespace(); if (ch == ':') { pos++; ch = commentWhitespace(); if (ch != 'f') break; if (!matchesAt(pos + 1, end, "unction")) break; pos += 8; const char* lastPos = pos; ch = commentWhitespace(); if (ch != '(' && (lastPos == pos || !identifier(ch))) break; ch = commentWhitespace(); } if (ch != '(') break; pos++; ch = commentWhitespace(); if (ch != ')') break; pos++; ch = commentWhitespace(); if (ch != '{') break; pos++; ch = commentWhitespace(); if (ch != 'r') break; if (!matchesAt(pos + 1, end, "eturn")) break; pos += 6; ch = commentWhitespace(); if (!identifier(ch)) break; ch = commentWhitespace(); if (ch == '.') { pos++; ch = commentWhitespace(); if (!identifier(ch)) break; ch = commentWhitespace(); } else if (ch == '[') { pos++; ch = commentWhitespace(); if (ch == '\'' || ch == '"') { stringLiteral(ch); } else { break; } pos++; ch = commentWhitespace(); if (ch != ']') break; pos++; ch = commentWhitespace(); } if (ch == ';') { pos++; ch = commentWhitespace(); } if (ch != '}') break; pos++; ch = commentWhitespace(); if (ch == ',') { pos++; ch = commentWhitespace(); } if (ch != '}') break; pos++; ch = commentWhitespace(); if (ch != ')') break; if (exportStart && exportEnd) addExport(std::string_view(exportStart, exportEnd - exportStart), line); return; } break; } } else if (keys && ch == 'k' && matchesAt(pos + 1, end, "eys")) { while (true) { pos += 4; revertPos = pos - 1; ch = commentWhitespace(); if (ch != '(') break; pos++; ch = commentWhitespace(); const char* id_pos = pos; if (!identifier(ch)) break; std::string_view id(id_pos, static_cast<size_t>(pos - id_pos)); ch = commentWhitespace(); if (ch != ')') break; revertPos = pos++; ch = commentWhitespace(); if (ch != '.') break; pos++; ch = commentWhitespace(); if (ch != 'f' || !matchesAt(pos + 1, end, "orEach")) break; pos += 7; ch = commentWhitespace(); revertPos = pos - 1; if (ch != '(') break; pos++; ch = commentWhitespace(); if (ch != 'f' || !matchesAt(pos + 1, end, "unction")) break; pos += 8; ch = commentWhitespace(); if (ch != '(') break; pos++; ch = commentWhitespace(); const char* it_id_pos = pos; if (!identifier(ch)) break; std::string_view it_id(it_id_pos, static_cast<size_t>(pos - it_id_pos)); ch = commentWhitespace(); if (ch != ')') break; pos++; ch = commentWhitespace(); if (ch != '{') break; pos++; ch = commentWhitespace(); if (ch != 'i' || *(pos + 1) != 'f') break; pos += 2; ch = commentWhitespace(); if (ch != '(') break; pos++; ch = commentWhitespace(); if (!matchesAt(pos, end, it_id)) break; pos += it_id.size(); ch = commentWhitespace(); if (ch == '=') { if (!matchesAt(pos + 1, end, "==")) break; pos += 3; ch = commentWhitespace(); if (ch != '"' && ch != '\'') break; char quot = ch; if (!matchesAt(pos + 1, end, "default")) break; pos += 8; ch = commentWhitespace(); if (ch != quot) break; pos++; ch = commentWhitespace(); if (ch != '|' || *(pos + 1) != '|') break; pos += 2; ch = commentWhitespace(); if (!matchesAt(pos, end, it_id)) break; pos += it_id.size(); ch = commentWhitespace(); if (ch != '=' || !matchesAt(pos + 1, end, "==")) break; pos += 3; ch = commentWhitespace(); if (ch != '"' && ch != '\'') break; quot = ch; if (!matchesAt(pos + 1, end, "__esModule")) break; pos += 11; ch = commentWhitespace(); if (ch != quot) break; pos++; ch = commentWhitespace(); if (ch != ')') break; pos++; ch = commentWhitespace(); if (ch != 'r' || !matchesAt(pos + 1, end, "eturn")) break; pos += 6; ch = commentWhitespace(); if (ch == ';') pos++; ch = commentWhitespace(); if (ch == 'i' && *(pos + 1) == 'f') { bool inIf = true; pos += 2; ch = commentWhitespace(); if (ch != '(') break; pos++; const char* ifInnerPos = pos; if (tryParseObjectHasOwnProperty(it_id)) { ch = commentWhitespace(); if (ch != ')') break; pos++; ch = commentWhitespace(); if (ch != 'r' || !matchesAt(pos + 1, end, "eturn")) break; pos += 6; ch = commentWhitespace(); if (ch == ';') pos++; ch = commentWhitespace(); if (ch == 'i' && *(pos + 1) == 'f') { pos += 2; ch = commentWhitespace(); if (ch != '(') break; pos++; } else { inIf = false; } } else { pos = ifInnerPos; } if (inIf) { if (!matchesAt(pos, end, it_id)) break; pos += it_id.size(); ch = commentWhitespace(); if (ch != 'i' || !matchesAt(pos + 1, end, "n ")) break; pos += 3; ch = commentWhitespace(); if (!readExportsOrModuleDotExports(ch)) break; ch = commentWhitespace(); if (ch != '&' || *(pos + 1) != '&') break; pos += 2; ch = commentWhitespace(); if (!readExportsOrModuleDotExports(ch)) break; ch = commentWhitespace(); if (ch != '[') break; pos++; ch = commentWhitespace(); if (!matchesAt(pos, end, it_id)) break; pos += it_id.size(); ch = commentWhitespace(); if (ch != ']') break; pos++; ch = commentWhitespace(); if (ch != '=' || !matchesAt(pos + 1, end, "==")) break; pos += 3; ch = commentWhitespace(); if (!matchesAt(pos, end, id)) break; pos += id.size(); ch = commentWhitespace(); if (ch != '[') break; pos++; ch = commentWhitespace(); if (!matchesAt(pos, end, it_id)) break; pos += it_id.size(); ch = commentWhitespace(); if (ch != ']') break; pos++; ch = commentWhitespace(); if (ch != ')') break; pos++; ch = commentWhitespace(); if (ch != 'r' || !matchesAt(pos + 1, end, "eturn")) break; pos += 6; ch = commentWhitespace(); if (ch == ';') pos++; ch = commentWhitespace(); } } } else if (ch == '!') { if (!matchesAt(pos + 1, end, "==")) break; pos += 3; ch = commentWhitespace(); if (ch != '"' && ch != '\'') break; char quot = ch; if (!matchesAt(pos + 1, end, "default")) break; pos += 8; ch = commentWhitespace(); if (ch != quot) break; pos++; ch = commentWhitespace(); if (ch == '&') { if (*(pos + 1) != '&') break; pos += 2; ch = commentWhitespace(); if (ch != '!') break; pos++; ch = commentWhitespace(); if (ch == 'O' && matchesAt(pos + 1, end, "bject.")) { if (!tryParseObjectHasOwnProperty(it_id)) break; } else if (identifier(ch)) { ch = commentWhitespace(); if (ch != '.') break; pos++; ch = commentWhitespace(); if (ch != 'h' || !matchesAt(pos + 1, end, "asOwnProperty")) break; pos += 14; ch = commentWhitespace(); if (ch != '(') break; pos++; ch = commentWhitespace(); if (!matchesAt(pos, end, it_id)) break; pos += it_id.size(); ch = commentWhitespace(); if (ch != ')') break; pos++; } ch = commentWhitespace(); } if (ch != ')') break; pos++; ch = commentWhitespace(); } else { break; } if (readExportsOrModuleDotExports(ch)) { ch = commentWhitespace(); if (ch != '[') break; pos++; ch = commentWhitespace(); if (!matchesAt(pos, end, it_id)) break; pos += it_id.size(); ch = commentWhitespace(); if (ch != ']') break; pos++; ch = commentWhitespace(); if (ch != '=') break; pos++; ch = commentWhitespace(); if (!matchesAt(pos, end, id)) break; pos += id.size(); ch = commentWhitespace(); if (ch != '[') break; pos++; ch = commentWhitespace(); if (!matchesAt(pos, end, it_id)) break; pos += it_id.size(); ch = commentWhitespace(); if (ch != ']') break; pos++; ch = commentWhitespace(); if (ch == ';') { pos++; ch = commentWhitespace(); } } else if (ch == 'O') { if (!matchesAt(pos + 1, end, "bject")) break; pos += 6; ch = commentWhitespace(); if (ch != '.') break; pos++; ch = commentWhitespace(); if (ch != 'd' || !matchesAt(pos + 1, end, "efineProperty")) break; pos += 14; ch = commentWhitespace(); if (ch != '(') break; pos++; ch = commentWhitespace(); if (!readExportsOrModuleDotExports(ch)) break; ch = commentWhitespace(); if (ch != ',') break; pos++; ch = commentWhitespace(); if (!matchesAt(pos, end, it_id)) break; pos += it_id.size(); ch = commentWhitespace(); if (ch != ',') break; pos++; ch = commentWhitespace(); if (ch != '{') break; pos++; ch = commentWhitespace(); if (ch != 'e' || !matchesAt(pos + 1, end, "numerable")) break; pos += 10; ch = commentWhitespace(); if (ch != ':') break; pos++; ch = commentWhitespace(); if (ch != 't' || !matchesAt(pos + 1, end, "rue")) break; pos += 4; ch = commentWhitespace(); if (ch != ',') break; pos++; ch = commentWhitespace(); if (ch != 'g' || !matchesAt(pos + 1, end, "et")) break; pos += 3; ch = commentWhitespace(); if (ch == ':') { pos++; ch = commentWhitespace(); if (ch != 'f') break; if (!matchesAt(pos + 1, end, "unction")) break; pos += 8; const char* lastPos = pos; ch = commentWhitespace(); if (ch != '(' && (lastPos == pos || !identifier(ch))) break; ch = commentWhitespace(); } if (ch != '(') break; pos++; ch = commentWhitespace(); if (ch != ')') break; pos++; ch = commentWhitespace(); if (ch != '{') break; pos++; ch = commentWhitespace(); if (ch != 'r' || !matchesAt(pos + 1, end, "eturn")) break; pos += 6; ch = commentWhitespace(); if (!matchesAt(pos, end, id)) break; pos += id.size(); ch = commentWhitespace(); if (ch != '[') break; pos++; ch = commentWhitespace(); if (!matchesAt(pos, end, it_id)) break; pos += it_id.size(); ch = commentWhitespace(); if (ch != ']') break; pos++; ch = commentWhitespace(); if (ch == ';') { pos++; ch = commentWhitespace(); } if (ch != '}') break; pos++; ch = commentWhitespace(); if (ch == ',') { pos++; ch = commentWhitespace(); } if (ch != '}') break; pos++; ch = commentWhitespace(); if (ch != ')') break; pos++; ch = commentWhitespace(); if (ch == ';') { pos++; ch = commentWhitespace(); } } else { break; } if (ch != '}') break; pos++; ch = commentWhitespace(); if (ch != ')') break; // Search through export bindings to see if this is a star export StarExportBinding* curCheckBinding = &starExportStack_[0]; while (curCheckBinding != starExportStack) { if (curCheckBinding->id == id) { addReexport(curCheckBinding->specifier, line); pos = revertPos; return; } curCheckBinding++; } return; } } } pos = revertPos; } void tryBacktrackAddStarExportBinding(const char* bPos) { if (bPos < source) return; while (bPos > source && *bPos == ' ') bPos--; if (*bPos == '=') { if (bPos <= source) return; bPos--; while (bPos > source && *bPos == ' ') bPos--; const char* id_end = bPos; bool identifierStart = false; while (bPos > source) { char ch = *bPos; if (!isIdentifierChar(static_cast<uint8_t>(ch))) break; identifierStart = isIdentifierStart(static_cast<uint8_t>(ch)); bPos--; } if (identifierStart && *bPos == ' ') { if (starExportStack == STAR_EXPORT_STACK_END) return; starExportStack->id = std::string_view(bPos + 1, static_cast<size_t>(id_end - bPos)); while (bPos > source && *bPos == ' ') bPos--; switch (*bPos) { case 'r': if (!readPrecedingKeyword(bPos - 1, "va")) return; break; case 't': if (!readPrecedingKeyword(bPos - 1, "le") && !readPrecedingKeyword(bPos - 1, "cons")) return; break; default: return; } starExportStack++; } } } void throwIfImportStatement() { const char* startPos = pos; pos += 6; char ch = commentWhitespace(); switch (ch) { case '(': openTokenTypeStack_[openTokenDepth] = '('; openTokenPosStack_[openTokenDepth++] = startPos; return; case '.': // Check if followed by 'meta' (possibly with whitespace) pos++; ch = commentWhitespace(); // Use str_eq4 for more efficient comparison if (ch == 'm' && pos + 4 <= end && matchesAt(pos + 1, end, "eta")) { // Check that 'meta' is not followed by an identifier character if (pos + 4 < end && isIdentifierChar(static_cast<uint8_t>(pos[4]))) { // It's something like import.metaData, not import.meta return; } syntaxError(lexer_error::UNEXPECTED_ESM_IMPORT_META, startPos); } return; default: if (pos == startPos + 6) break; [[fallthrough]]; case '"': case '\'': case '{': case '*': if (openTokenDepth != 0) { pos--; return; } syntaxError(lexer_error::UNEXPECTED_ESM_IMPORT, startPos); } } void throwIfExportStatement() { const char* startPos = pos; pos += 6; const char* curPos = pos; char ch = commentWhitespace(); if (pos == curPos && !isPunctuator(ch)) return; syntaxError(lexer_error::UNEXPECTED_ESM_EXPORT, startPos); } public: CJSLexer(std::vector<export_entry>& out_exports, std::vector<export_entry>& out_re_exports) : source(nullptr), pos(nullptr), end(nullptr), lastTokenPos(nullptr), templateStackDepth(0), openTokenDepth(0), templateDepth(0), line(1), lastSlashWasDivision(false), nextBraceIsClass(false), templateStack_{}, openTokenPosStack_{}, openTokenTypeStack_{}, openClassPosStack{}, starExportStack_{}, starExportStack(nullptr), STAR_EXPORT_STACK_END(nullptr), exports(out_exports), re_exports(out_re_exports) {} bool parse(std::string_view file_contents) { source = file_contents.data(); pos = source - 1; end = source + file_contents.size(); // Initialize lastTokenPos to before source to detect start-of-input condition // when checking if '/' should be treated as regex vs division operator lastTokenPos = source - 1; templateStackDepth = 0; openTokenDepth = 0; templateDepth = std::numeric_limits<uint16_t>::max(); line = 1; lastSlashWasDivision = false; starExportStack = &starExportStack_[0]; STAR_EXPORT_STACK_END = &starExportStack_[MAX_STAR_EXPORTS - 1]; nextBraceIsClass = false; char ch = '\0'; // Handle shebang if (file_contents.size() >= 2 && source[0] == '#' && source[1] == '!') { if (file_contents.size() == 2) return true; pos += 2; while (pos < end) { ch = *pos; if (ch == '\n' || ch == '\r') break; pos++; } lastTokenPos = pos; // Update lastTokenPos after shebang } while (pos++ < end) { ch = *pos; if (ch == ' ' || (ch < 14 && ch > 8)) { countNewline(ch); continue; } if (openTokenDepth == 0) { switch (ch) { case 'i': if (pos + 6 < end && matchesAt(pos + 1, end, "mport") && keywordStart(pos)) throwIfImportStatement(); lastTokenPos = pos; continue; case 'r': { const char* startPos = pos; if (tryParseRequire(RequireType::Import) && keywordStart(startPos)) tryBacktrackAddStarExportBinding(startPos - 1); lastTokenPos = pos; continue; } case '_': if (pos + 23 < end && matchesAt(pos + 1, end, "interopRequireWildcard") && (keywordStart(pos) || *(pos - 1) == '.')) { const char* startPos = pos; pos += 23; if (*pos == '(') { pos++; openTokenTypeStack_[openTokenDepth] = '('; openTokenPosStack_[openTokenDepth++] = lastTokenPos; if (tryParseRequire(RequireType::Import) && keywordStart(startPos)) tryBacktrackAddStarExportBinding(startPos - 1); } } else if (pos + 8 < end && matchesAt(pos + 1, end, "_export") && (keywordStart(pos) || *(pos - 1) == '.')) { pos += 8; if (pos + 4 < end && matchesAt(pos, end, "Star")) pos += 4; if (*pos == '(') { openTokenTypeStack_[openTokenDepth] = '('; openTokenPosStack_[openTokenDepth++] = lastTokenPos; if (*(pos + 1) == 'r') { pos++; tryParseRequire(RequireType::ExportStar); } } } lastTokenPos = pos; continue; } } switch (ch) { case 'e': if (pos + 6 < end && matchesAt(pos + 1, end, "xport") && keywordStart(pos)) { if (pos + 7 < end && *(pos + 6) == 's') tryParseExportsDotAssign(false); else if (openTokenDepth == 0) throwIfExportStatement(); } break; case 'c': if (keywordStart(pos) && matchesAt(pos + 1, end, "lass") && isBrOrWs(*(pos + 5))) nextBraceIsClass = true; break; case 'm': if (pos + 6 < end && matchesAt(pos + 1, end, "odule") && keywordStart(pos)) tryParseModuleExportsDotAssign(); break; case 'O': if (pos + 6 < end && matchesAt(pos + 1, end, "bject") && keywordStart(pos)) tryParseObjectDefineOrKeys(openTokenDepth == 0); break; case '(': openTokenTypeStack_[openTokenDepth] = '('; openTokenPosStack_[openTokenDepth++] = lastTokenPos; break; case ')': if (openTokenDepth == 0) { syntaxError(lexer_error::UNEXPECTED_PAREN); return false; } openTokenDepth--; break; case '{': openClassPosStack[openTokenDepth] = nextBraceIsClass; nextBraceIsClass = false; openTokenTypeStack_[openTokenDepth] = '{'; openTokenPosStack_[openTokenDepth++] = lastTokenPos; break; case '}': if (openTokenDepth == 0) { syntaxError(lexer_error::UNEXPECTED_BRACE); return false; } if (openTokenDepth-- == templateDepth) { templateDepth = templateStack_[--templateStackDepth]; templateString(); } else { if (templateDepth != std::numeric_limits<uint16_t>::max() && openTokenDepth < templateDepth) { syntaxError(lexer_error::UNTERMINATED_TEMPLATE_STRING); return false; } } break; case '\'': case '"': stringLiteral(ch); break; case '/': { char next_ch = pos + 1 < end ? *(pos + 1) : '\0'; if (next_ch == '/') { lineComment(); continue; } else if (next_ch == '*') { blockComment(); continue; } else { // Check if lastTokenPos is before the source (start of input) bool isStartOfInput = lastTokenPos < source; char lastToken = isStartOfInput ? '\0' : *lastTokenPos; if ((isExpressionPunctuator(lastToken) && !(lastToken == '.' && lastTokenPos > source && *(lastTokenPos - 1) >= '0' && *(lastTokenPos - 1) <= '9') && !(lastToken == '+' && lastTokenPos > source && *(lastTokenPos - 1) == '+') && !(lastToken == '-' && lastTokenPos > source && *(lastTokenPos - 1) == '-')) || (lastToken == ')' && isParenKeyword(openTokenPosStack_[openTokenDepth])) || (lastToken == '}' && (openTokenPosStack_[openTokenDepth] < source || isExpressionTerminator(openTokenPosStack_[openTokenDepth]) || openClassPosStack[openTokenDepth])) || (lastToken == '/' && lastSlashWasDivision) || (!isStartOfInput && isExpressionKeyword(lastTokenPos)) || !lastToken || isStartOfInput) { regularExpression(); lastSlashWasDivision = false; } else { lastSlashWasDivision = true; } } break; } case '`': if (templateDepth == std::numeric_limits<uint16_t>::max() - 1) { syntaxError(lexer_error::TEMPLATE_NEST_OVERFLOW); return false; } templateString(); break; } lastTokenPos = pos; } if (!last_error) { if (templateDepth != std::numeric_limits<uint16_t>::max()) { syntaxError(lexer_error::UNTERMINATED_TEMPLATE_STRING, end); } else if (openTokenDepth != 0) { const char open_ch = openTokenTypeStack_[openTokenDepth - 1]; if (open_ch == '{') { syntaxError(lexer_error::UNTERMINATED_BRACE, end); } else { syntaxError(lexer_error::UNTERMINATED_PAREN, end); } } } if (templateDepth != std::numeric_limits<uint16_t>::max() || openTokenDepth || last_error) { return false; } return true; } }; std::optional<lexer_analysis> parse_commonjs(std::string_view file_contents) { last_error.reset(); last_error_location.reset(); lexer_analysis result; CJSLexer lexer(result.exports, result.re_exports); if (lexer.parse(file_contents)) { return result; // NRVO or implicit move applies } return std::nullopt; } const std::optional<lexer_error>& get_last_error() { return last_error; } const std::optional<error_location>& get_last_error_location() { return last_error_location; } } // namespace lexer /* end file src/parser.cpp */ /* begin file src/merve_c.cpp */ /* begin file include/merve_c.h */ /** * @file merve_c.h * @brief Includes the C definitions for merve. This is a C file, not C++. */ #ifndef MERVE_C_H #define MERVE_C_H #include <stdbool.h> #include <stddef.h> #include <stdint.h> /** * @brief Non-owning string reference. * * The data pointer is NOT null-terminated. Always use the length field. * * The data is valid as long as: * - The merve_analysis handle that produced it has not been freed. * - For string_view-backed exports: the original source buffer is alive. */ typedef struct { const char* data; size_t length; } merve_string; /** * @brief Opaque handle to a CommonJS parse result. * * Created by merve_parse_commonjs(). Must be freed with merve_free(). */ typedef void* merve_analysis; /** * @brief Version number components. */ typedef struct { int major; int minor; int revision; } merve_version_components; /** * @brief Source location for a parse error. * * - line and column are 1-based. * - column is byte-oriented. * * A zeroed location (`{0, 0}`) means the location is unavailable. */ typedef struct { uint32_t line; uint32_t column; } merve_error_loc; /* Error codes corresponding to lexer::lexer_error values. */ #define MERVE_ERROR_TODO 0 #define MERVE_ERROR_UNEXPECTED_PAREN 1 #define MERVE_ERROR_UNEXPECTED_BRACE 2 #define MERVE_ERROR_UNTERMINATED_PAREN 3 #define MERVE_ERROR_UNTERMINATED_BRACE 4 #define MERVE_ERROR_UNTERMINATED_TEMPLATE_STRING 5 #define MERVE_ERROR_UNTERMINATED_STRING_LITERAL 6 #define MERVE_ERROR_UNTERMINATED_REGEX_CHARACTER_CLASS 7 #define MERVE_ERROR_UNTERMINATED_REGEX 8 #define MERVE_ERROR_UNEXPECTED_ESM_IMPORT_META 9 #define MERVE_ERROR_UNEXPECTED_ESM_IMPORT 10 #define MERVE_ERROR_UNEXPECTED_ESM_EXPORT 11 #define MERVE_ERROR_TEMPLATE_NEST_OVERFLOW 12 #ifdef __cplusplus extern "C" { #endif /** * Parse CommonJS source code and optionally return error location. * * The source buffer must remain valid while accessing string_view-backed * export names from the returned handle. * * If @p out_err is non-NULL, it is always written: * - On success: set to {0, 0}. * - On parse failure with known location: set to that location. * - On parse failure without available location: set to {0, 0}. * * You must call merve_free() on the returned handle when done. * * @param input Pointer to the JavaScript source (need not be * null-terminated). NULL is treated as an empty string. * @param length Length of the input in bytes. * @param out_err Optional output pointer for parse error location. * @return A handle to the parse result, or NULL on out-of-memory. * Use merve_is_valid() to check if parsing succeeded. */ #ifdef __cplusplus merve_analysis merve_parse_commonjs(const char* input, size_t length, merve_error_loc* out_err = nullptr); #else merve_analysis merve_parse_commonjs(const char* input, size_t length, merve_error_loc* out_err); #endif /** * Check whether the parse result is valid (parsing succeeded). * * @param result Handle returned by merve_parse_commonjs(). NULL returns false. * @return true if parsing succeeded, false otherwise. */ bool merve_is_valid(merve_analysis result); /** * Free a parse result and all associated memory. * * @param result Handle returned by merve_parse_commonjs(). NULL is a no-op. */ void merve_free(merve_analysis result); /** * Get the number of named exports found. * * @param result A parse result handle. NULL returns 0. * @return Number of exports, or 0 if result is NULL or invalid. */ size_t merve_get_exports_count(merve_analysis result); /** * Get the number of re-export module specifiers found. * * @param result A parse result handle. NULL returns 0. * @return Number of re-exports, or 0 if result is NULL or invalid. */ size_t merve_get_reexports_count(merve_analysis result); /** * Get the name of an export at the given index. * * @param result A valid parse result handle. * @param index Zero-based index (must be < merve_get_exports_count()). * @return Non-owning string reference. Returns {NULL, 0} on error. */ merve_string merve_get_export_name(merve_analysis result, size_t index); /** * Get the 1-based source line number of an export. * * @param result A valid parse result handle. * @param index Zero-based index (must be < merve_get_exports_count()). * @return 1-based line number, or 0 on error. */ uint32_t merve_get_export_line(merve_analysis result, size_t index); /** * Get the module specifier of a re-export at the given index. * * @param result A valid parse result handle. * @param index Zero-based index (must be < merve_get_reexports_count()). * @return Non-owning string reference. Returns {NULL, 0} on error. */ merve_string merve_get_reexport_name(merve_analysis result, size_t index); /** * Get the 1-based source line number of a re-export. * * @param result A valid parse result handle. * @param index Zero-based index (must be < merve_get_reexports_count()). * @return 1-based line number, or 0 on error. */ uint32_t merve_get_reexport_line(merve_analysis result, size_t index); /** * Get the error code from the last merve_parse_commonjs() call. * * @return One of the MERVE_ERROR_* constants, or -1 if the last parse * succeeded. * @note This is global state, overwritten by each merve_parse_commonjs() call. */ int merve_get_last_error(void); /** * Get the merve library version string. * * @return Null-terminated version string (e.g. "1.0.1"). Never NULL. */ const char* merve_get_version(void); /** * Get the merve library version as individual components. * * @return Struct with major, minor, and revision fields. */ merve_version_components merve_get_version_components(void); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* MERVE_C_H */ /* end file include/merve_c.h */ #include <new> struct merve_analysis_impl { std::optional<lexer::lexer_analysis> result{}; }; static merve_string merve_string_create(const char* data, size_t length) { merve_string out{}; out.data = data; out.length = length; return out; } static void merve_error_loc_clear(merve_error_loc* out_err) { if (!out_err) return; out_err->line = 0; out_err->column = 0; } static void merve_error_loc_set(merve_error_loc* out_err, const lexer::error_location& loc) { if (!out_err) return; out_err->line = loc.line; out_err->column = loc.column; } extern "C" { merve_analysis merve_parse_commonjs(const char* input, size_t length, merve_error_loc* out_err) { merve_error_loc_clear(out_err); merve_analysis_impl* impl = new (std::nothrow) merve_analysis_impl(); if (!impl) return nullptr; if (input != nullptr) { impl->result = lexer::parse_commonjs(std::string_view(input, length)); } else { impl->result = lexer::parse_commonjs(std::string_view("", 0)); } if (!impl->result.has_value() && out_err) { const std::optional<lexer::error_location>& err_loc = lexer::get_last_error_location(); if (err_loc.has_value()) { merve_error_loc_set(out_err, err_loc.value()); } } return static_cast<merve_analysis>(impl); } bool merve_is_valid(merve_analysis result) { if (!result) return false; return static_cast<merve_analysis_impl*>(result)->result.has_value(); } void merve_free(merve_analysis result) { if (!result) return; delete static_cast<merve_analysis_impl*>(result); } size_t merve_get_exports_count(merve_analysis result) { if (!result) return 0; merve_analysis_impl* impl = static_cast<merve_analysis_impl*>(result); if (!impl->result.has_value()) return 0; return impl->result->exports.size(); } size_t merve_get_reexports_count(merve_analysis result) { if (!result) return 0; merve_analysis_impl* impl = static_cast<merve_analysis_impl*>(result); if (!impl->result.has_value()) return 0; return impl->result->re_exports.size(); } merve_string merve_get_export_name(merve_analysis result, size_t index) { if (!result) return merve_string_create(nullptr, 0); merve_analysis_impl* impl = static_cast<merve_analysis_impl*>(result); if (!impl->result.has_value()) return merve_string_create(nullptr, 0); if (index >= impl->result->exports.size()) return merve_string_create(nullptr, 0); std::string_view sv = lexer::get_string_view(impl->result->exports[index]); return merve_string_create(sv.data(), sv.size()); } uint32_t merve_get_export_line(merve_analysis result, size_t index) { if (!result) return 0; merve_analysis_impl* impl = static_cast<merve_analysis_impl*>(result); if (!impl->result.has_value()) return 0; if (index >= impl->result->exports.size()) return 0; return impl->result->exports[index].line; } merve_string merve_get_reexport_name(merve_analysis result, size_t index) { if (!result) return merve_string_create(nullptr, 0); merve_analysis_impl* impl = static_cast<merve_analysis_impl*>(result); if (!impl->result.has_value()) return merve_string_create(nullptr, 0); if (index >= impl->result->re_exports.size()) return merve_string_create(nullptr, 0); std::string_view sv = lexer::get_string_view(impl->result->re_exports[index]); return merve_string_create(sv.data(), sv.size()); } uint32_t merve_get_reexport_line(merve_analysis result, size_t index) { if (!result) return 0; merve_analysis_impl* impl = static_cast<merve_analysis_impl*>(result); if (!impl->result.has_value()) return 0; if (index >= impl->result->re_exports.size()) return 0; return impl->result->re_exports[index].line; } int merve_get_last_error(void) { const std::optional<lexer::lexer_error>& err = lexer::get_last_error(); if (!err.has_value()) return -1; return static_cast<int>(err.value()); } const char* merve_get_version(void) { return MERVE_VERSION; } merve_version_components merve_get_version_components(void) { merve_version_components vc{}; vc.major = lexer::MERVE_VERSION_MAJOR; vc.minor = lexer::MERVE_VERSION_MINOR; vc.revision = lexer::MERVE_VERSION_REVISION; return vc; } } /* extern "C" */ /* end file src/merve_c.cpp */