#ifndef BARE_MODULE_LEXER_H
#define BARE_MODULE_LEXER_H
#include <assert.h>
#include <js.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <utf.h>
enum {
bare_module_lexer_require = 0x1,
bare_module_lexer_import = 0x2,
bare_module_lexer_dynamic = 0x4,
bare_module_lexer_addon = 0x8,
bare_module_lexer_asset = 0x10,
bare_module_lexer_resolve = 0x20,
bare_module_lexer_reexport = 0x40,
};
// Previous significant token classification - just enough context to (a)
// reject keyword matches that are actually member accesses and (b) decide
// whether '/' starts a regular expression or is a division operator.
enum {
bare_module_lexer__prev_op = 0, // After an operator or statement boundary - an expression follows
bare_module_lexer__prev_expr = 1, // After an identifier, literal, ')', or ']' - an expression just ended
bare_module_lexer__prev_dot = 2 // After '.' - a property name follows
};
// Maximum number of nested template substitutions tracked. Deeper nesting
// degrades to plain brace counting rather than failing - the lexer must
// never throw.
#define BARE_MODULE_LEXER__TEMPLATE_DEPTH 64
static inline bool
bare_module_lexer__is_line_terminator(uint8_t c) {
return c == 0xa || c == 0xd;
}
static inline bool
bare_module_lexer__is_whitespace(uint8_t c) {
return c == ' ' || c == '\t' || c == 0xb || c == 0xc || bare_module_lexer__is_line_terminator(c);
}
static inline bool
bare_module_lexer__is_id_start(uint8_t c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$';
}
static inline bool
bare_module_lexer__is_id(uint8_t c) {
return bare_module_lexer__is_id_start(c) || (c >= '0' && c <= '9');
}
// True when the byte can continue an identifier, including any non-ASCII
// byte: Unicode identifiers are not validated, only treated as opaque runs
// so keyword matches can't begin inside them.
static inline bool
bare_module_lexer__is_id_like(uint8_t c) {
return bare_module_lexer__is_id(c) || c >= 0x80;
}
// True when the byte can start an identifier, with the same opaque treatment
// of non-ASCII bytes.
static inline bool
bare_module_lexer__is_id_like_start(uint8_t c) {
return bare_module_lexer__is_id_start(c) || c >= 0x80;
}
static inline int
bare_module_lexer__add_position(js_env_t *env, js_value_t *entry, size_t statement_start, size_t input_start, size_t input_end) {
int err;
js_value_t *position;
err = js_create_array_with_length(env, 3, &position);
assert(err == 0);
#define V(i, n) \
{ \
js_value_t *val; \
err = js_create_int64(env, n, &val); \
assert(err == 0); \
err = js_set_element(env, position, i, val); \
assert(err == 0); \
}
V(0, statement_start);
V(1, input_start);
V(2, input_end);
#undef V
err = js_set_named_property(env, entry, "position", position);
assert(err == 0);
return 0;
}
static inline int
bare_module_lexer__add_import(js_env_t *env, js_value_t *imports, uint32_t *i, const utf8_t *source, size_t import_start, size_t specifier_start, size_t specifier_end, int type, js_value_t *names, js_value_t *attributes) {
assert(specifier_end >= specifier_start);
int err;
js_value_t *entry;
err = js_create_object(env, &entry);
assert(err == 0);
err = js_set_element(env, imports, *i, entry);
assert(err == 0);
#define V(key, fn, ...) \
{ \
js_value_t *val; \
err = fn(env, __VA_ARGS__, &val); \
assert(err == 0); \
err = js_set_named_property(env, entry, key, val); \
assert(err == 0); \
}
V("specifier", js_create_string_utf8, &source[specifier_start], specifier_end - specifier_start);
V("type", js_create_uint32, type);
#undef V
if (names == NULL) {
err = js_create_array(env, &names);
assert(err == 0);
}
err = js_set_named_property(env, entry, "names", names);
assert(err == 0);
if (attributes == NULL) {
err = js_create_object(env, &attributes);
assert(err == 0);
}
err = js_set_named_property(env, entry, "attributes", attributes);
assert(err == 0);
err = bare_module_lexer__add_position(env, entry, import_start, specifier_start, specifier_end);
assert(err == 0);
*i += 1;
return 0;
}
static inline int
bare_module_lexer__add_export(js_env_t *env, js_value_t *exports, uint32_t *i, const utf8_t *source, size_t export_start, size_t name_start, size_t name_end) {
assert(name_end >= name_start);
int err;
js_value_t *entry;
err = js_create_object(env, &entry);
assert(err == 0);
err = js_set_element(env, exports, *i, entry);
assert(err == 0);
#define V(key, fn, ...) \
{ \
js_value_t *val; \
err = fn(env, __VA_ARGS__, &val); \
assert(err == 0); \
err = js_set_named_property(env, entry, key, val); \
assert(err == 0); \
}
V("name", js_create_string_utf8, &source[name_start], name_end - name_start);
#undef V
err = bare_module_lexer__add_position(env, entry, export_start, name_start, name_end);
assert(err == 0);
*i += 1;
return 0;
}
static inline int
bare_module_lexer__add_name(js_env_t *env, js_value_t **names, uint32_t *i, const utf8_t *name, size_t name_len) {
int err;
if (*names == NULL) {
err = js_create_array(env, names);
assert(err == 0);
*i = 0;
}
js_value_t *string;
err = js_create_string_utf8(env, name, name_len, &string);
assert(err == 0);
err = js_set_element(env, *names, *i, string);
assert(err == 0);
*i += 1;
return 0;
}
static inline int
bare_module_lexer__add_attribute(js_env_t *env, js_value_t **attributes, const utf8_t *key, size_t key_len, const utf8_t *value, size_t value_len) {
int err;
if (*attributes == NULL) {
err = js_create_object(env, attributes);
assert(err == 0);
}
js_value_t *property;
err = js_create_property_key_utf8(env, key, key_len, &property);
assert(err == 0);
js_value_t *string;
err = js_create_string_utf8(env, value, value_len, &string);
assert(err == 0);
err = js_set_property(env, *attributes, property, string);
assert(err == 0);
return 0;
}
// Current character, unchecked
#define u(offset) ((uint8_t) s[i + offset])
// Current character, checked
#define c(offset) (i + offset < n ? u(offset) : -1)
#define lt bare_module_lexer__is_line_terminator
#define ws bare_module_lexer__is_whitespace
#define ids bare_module_lexer__is_id_start
#define id bare_module_lexer__is_id
#define idl bare_module_lexer__is_id_like
#define idsl bare_module_lexer__is_id_like_start
// True iff the bytes at i spell the keyword `kw` of length `kwl` with a word
// boundary after it (end of input or a non-identifier byte).
static inline bool
bare_module_lexer__at_kw(const utf8_t *s, size_t n, size_t i, const char *kw, size_t kwl) {
return i + kwl <= n && memcmp(&s[i], kw, kwl) == 0 && (i + kwl == n || !idl(s[i + kwl]));
}
// True iff the identifier span s[ks..ks+kl] equals the keyword `kw` of
// length `kwl`.
static inline bool
bare_module_lexer__match_kw(const utf8_t *s, size_t ks, size_t kl, const char *kw, size_t kwl) {
return kl == kwl && memcmp(&s[ks], kw, kwl) == 0;
}
// Skip whitespace and comments.
static inline size_t
bare_module_lexer__skip_trivia(const utf8_t *s, size_t n, size_t i) {
while (i < n) {
if (ws(u(0))) {
i++;
} else if (i + 1 < n && u(0) == '/' && u(1) == '/') {
i += 2;
while (i < n && !lt(u(0))) i++;
} else if (i + 1 < n && u(0) == '/' && u(1) == '*') {
i += 2;
while (i + 1 < n && !(u(0) == '*' && u(1) == '/')) i++;
if (i + 1 < n) i += 2;
else i = n;
} else {
break;
}
}
return i;
}
// Lex a string literal at *result, which must point at the opening quote.
// Fills *start/*end with the content bounds and leaves the cursor past the
// closing quote. Returns false when the string is unterminated, with the
// cursor at end of input.
static inline bool
bare_module_lexer__lex_string(const utf8_t *s, size_t n, size_t *result, size_t *start, size_t *end) {
size_t i = *result;
uint8_t e = s[i];
i++;
*start = i;
while (i < n) {
if (s[i] == '\\') {
i += 2;
continue;
}
if (s[i] == e) {
*end = i;
*result = i + 1;
return true;
}
i++;
}
*start = *end = n;
*result = n;
return false;
}
// Scan template literal content from just past the opening '`' (or just
// past the '}' that closed a substitution). Stops after the terminating '`'
// and returns false, or after a '${' and returns true so the caller can lex
// the substitution as ordinary code.
static inline bool
bare_module_lexer__lex_template(const utf8_t *s, size_t n, size_t *result) {
size_t i = *result;
while (i < n) {
if (u(0) == '\\') {
i += 2;
continue;
}
if (u(0) == '`') {
i++;
break;
}
if (u(0) == '$' && i + 1 < n && u(1) == '{') {
*result = i + 2;
return true;
}
i++;
}
*result = i > n ? n : i;
return false;
}
// Skip a template literal wholesale. Assumes s[i] == '`'. Substitutions are
// tracked by brace counting only, which is good enough for a skip.
static inline size_t
bare_module_lexer__skip_template(const utf8_t *s, size_t n, size_t i) {
int braces = 0;
bool subst = false;
i++;
while (i < n) {
uint8_t ch = u(0);
if (ch == '\\') {
i += 2;
continue;
}
if (!subst) {
if (ch == '`') return i + 1;
if (ch == '$' && i + 1 < n && u(1) == '{') {
subst = true;
braces = 0;
i += 2;
continue;
}
i++;
continue;
}
if (ch == '\'' || ch == '"') {
size_t vs, ve;
bare_module_lexer__lex_string(s, n, &i, &vs, &ve);
continue;
}
if (ch == '{') braces++;
else if (ch == '}') {
if (braces == 0) subst = false;
else braces--;
}
i++;
}
return i;
}
// Skip a property value expression inside an object literal: Everything up
// to the next ',' or closing bracket at the current depth. Strings,
// templates, comments, and nested brackets are skipped wholesale.
static inline size_t
bare_module_lexer__skip_value(const utf8_t *s, size_t n, size_t i) {
int depth = 0;
while (i < n) {
uint8_t ch = u(0);
if (ws(ch) || (ch == '/' && i + 1 < n && (u(1) == '/' || u(1) == '*'))) {
size_t j = bare_module_lexer__skip_trivia(s, n, i);
if (j == i) i++;
else i = j;
continue;
}
if (ch == '\'' || ch == '"') {
size_t vs, ve;
bare_module_lexer__lex_string(s, n, &i, &vs, &ve);
continue;
}
if (ch == '`') {
i = bare_module_lexer__skip_template(s, n, i);
continue;
}
if (ch == '(' || ch == '[' || ch == '{') {
depth++;
i++;
continue;
}
if (ch == ')' || ch == ']' || ch == '}') {
if (depth == 0) return i;
depth--;
i++;
continue;
}
if (ch == ',' && depth == 0) return i;
i++;
}
return i;
}
// True when any byte in [a, b) is a line terminator.
static inline bool
bare_module_lexer__has_line_terminator(const utf8_t *s, size_t a, size_t b) {
for (size_t k = a; k < b; k++) {
if (lt(s[k])) return true;
}
return false;
}
// Heuristic ASI for type expressions: a line terminator at the outer nesting
// level continues the type only when the previous significant byte is a
// trailing operator or the next one is a leading operator. Being wrong here
// only ever stops a type early - the remainder is then lexed as ordinary
// code - so it never swallows a following statement's require()/import().
static inline bool
bare_module_lexer__type_continues(uint8_t last, uint8_t next) {
switch (last) {
case '=':
case '|':
case '&':
case ',':
case '<':
case '(':
case '[':
case '{':
case '.':
case '?':
case ':':
return true;
}
switch (next) {
case '|':
case '&':
case '.':
case '?':
case ':':
return true;
}
return false;
}
// Skip a TypeScript type expression starting at i - a type alias right-hand
// side or a ':' annotation. Types are erased at compile time, so any
// import(...) inside one is not a runtime dependency and must not be lexed as
// a module import. Strings, templates, and comments are skipped wholesale;
// '()[]{}' and '<>' nesting is tracked so commas and arrows inside generics
// and function types don't end the scan early. Stops at the outer level on
// ';', ',', or '=' (but not '=>'), on a closing bracket that would underflow
// an enclosing list, on a non-continuing line terminator, or at end of input.
static inline size_t
bare_module_lexer__skip_type(const utf8_t *s, size_t n, size_t i) {
int depth = 0; // () [] {} nesting
int angle = 0; // <> nesting
uint8_t last = 0; // Previous significant byte, for ASI continuation
while (i < n) {
uint8_t ch = u(0);
// Line terminator - at the outer level it may end the type.
if (lt(ch)) {
if (depth == 0 && angle == 0) {
size_t p = bare_module_lexer__skip_trivia(s, n, i);
if (!bare_module_lexer__type_continues(last, p < n ? s[p] : 0)) break;
i = p;
continue;
}
i++;
continue;
}
// Other whitespace and comments.
if (ws(ch) || (ch == '/' && i + 1 < n && (u(1) == '/' || u(1) == '*'))) {
size_t j = bare_module_lexer__skip_trivia(s, n, i);
if (j == i) i++;
else i = j;
continue;
}
// Strings and templates are opaque.
if (ch == '\'' || ch == '"') {
size_t vs, ve;
bare_module_lexer__lex_string(s, n, &i, &vs, &ve);
last = '"';
continue;
}
if (ch == '`') {
i = bare_module_lexer__skip_template(s, n, i);
last = '`';
continue;
}
if (depth == 0 && angle == 0) {
// A ';', ',', or bare '=' at the outer level ends the type.
if (ch == ';' || ch == ',') break;
if (ch == '=' && !(i + 1 < n && u(1) == '>')) break;
// A closing bracket at the outer level belongs to an enclosing list.
if (ch == ')' || ch == ']' || ch == '}') break;
}
// '=>' arrow - consume both bytes so the '>' isn't taken as a '<>' close.
if (ch == '=' && i + 1 < n && u(1) == '>') {
last = '>';
i += 2;
continue;
}
if (ch == '(' || ch == '[' || ch == '{') depth++;
else if (ch == ')' || ch == ']' || ch == '}') depth--;
else if (ch == '<') angle++;
else if (ch == '>' && angle > 0) angle--;
last = ch;
i++;
}
return i;
}
// Maximum destructuring pattern nesting tracked. Deeper nesting has its
// entries skipped rather than failing - the lexer must never throw.
#define BARE_MODULE_LEXER__PATTERN_DEPTH 32
// Lex a destructuring pattern at *result (which must point at '{' or '['),
// emitting an export for each binding name. Best effort: String and
// computed keys and patterns nested deeper than the cap have their entries
// skipped wholesale.
static inline int
bare_module_lexer__lex_pattern(js_env_t *env, js_value_t *exports, uint32_t *el, const utf8_t *s, size_t n, size_t es, size_t *result, int depth) {
int err;
size_t i = *result;
bool object = s[i] == '{';
uint8_t close = object ? '}' : ']';
i++;
while (i < n) {
i = bare_module_lexer__skip_trivia(s, n, i);
if (i >= n) break;
if (u(0) == close) {
i++;
break;
}
if (u(0) == ',') {
i++;
continue;
}
// Rest element '...name'.
if (i + 2 < n && u(0) == '.' && u(1) == '.' && u(2) == '.') {
i = bare_module_lexer__skip_trivia(s, n, i + 3);
if (i < n && idsl(u(0))) {
size_t ns = i++;
while (i < n && idl(u(0))) i++;
err = bare_module_lexer__add_export(env, exports, el, s, es, ns, i);
if (err < 0) return err;
}
continue;
}
// Binding name or 'key: target'.
if (idsl(u(0))) {
size_t ns = i++;
while (i < n && idl(u(0))) i++;
size_t ne = i;
i = bare_module_lexer__skip_trivia(s, n, i);
// 'key: target' - the target holds the binding.
if (object && i < n && u(0) == ':') {
i = bare_module_lexer__skip_trivia(s, n, i + 1);
if (i < n && (u(0) == '{' || u(0) == '[')) {
if (depth < BARE_MODULE_LEXER__PATTERN_DEPTH) {
err = bare_module_lexer__lex_pattern(env, exports, el, s, n, es, &i, depth + 1);
if (err < 0) return err;
} else {
i = bare_module_lexer__skip_value(s, n, i);
}
} else if (i < n && idsl(u(0))) {
ns = i++;
while (i < n && idl(u(0))) i++;
ne = i;
err = bare_module_lexer__add_export(env, exports, el, s, es, ns, ne);
if (err < 0) return err;
}
}
// Shorthand binding.
else {
err = bare_module_lexer__add_export(env, exports, el, s, es, ns, ne);
if (err < 0) return err;
}
i = bare_module_lexer__skip_trivia(s, n, i);
// Default value - skip to the next entry.
if (i < n && u(0) == '=') {
i = bare_module_lexer__skip_value(s, n, i + 1);
}
continue;
}
// Nested pattern as an array element.
if (!object && (u(0) == '{' || u(0) == '[')) {
if (depth < BARE_MODULE_LEXER__PATTERN_DEPTH) {
err = bare_module_lexer__lex_pattern(env, exports, el, s, n, es, &i, depth + 1);
if (err < 0) return err;
} else {
i = bare_module_lexer__skip_value(s, n, i);
}
i = bare_module_lexer__skip_trivia(s, n, i);
if (i < n && u(0) == '=') {
i = bare_module_lexer__skip_value(s, n, i + 1);
}
continue;
}
// String or computed key, holes, anything else - skip the entry.
{
size_t j = bare_module_lexer__skip_value(s, n, i);
if (j == i) i++;
else i = j;
}
}
*result = i;
return 0;
}
// Keywords after which a '/' starts a regular expression rather than a
// division - they end a statement or expect an expression to follow.
typedef struct {
const char *kw;
uint8_t len;
} bare_module_lexer__kw_t;
static const bare_module_lexer__kw_t bare_module_lexer__op_kws[] = {
{"in", 2},
{"of", 2},
{"do", 2},
{"new", 3},
{"else", 4},
{"case", 4},
{"void", 4},
{"yield", 5},
{"await", 5},
{"throw", 5},
{"delete", 6},
{"return", 6},
{"typeof", 6},
{"instanceof", 10},
};
// Classify an identifier span: Keywords that expect an expression to follow
// leave `prev` at op so a following '/' lexes as a regular expression;
// everything else ends an expression.
static inline int
bare_module_lexer__classify_kw(const utf8_t *s, size_t ks, size_t kl) {
if (kl >= 2 && kl <= 10) {
uint8_t c0 = s[ks];
for (size_t k = 0; k < sizeof(bare_module_lexer__op_kws) / sizeof(bare_module_lexer__op_kws[0]); k++) {
const bare_module_lexer__kw_t *kw = &bare_module_lexer__op_kws[k];
if (kw->len == kl && (uint8_t) kw->kw[0] == c0 && memcmp(&s[ks], kw->kw, kl) == 0) {
return bare_module_lexer__prev_op;
}
}
}
return bare_module_lexer__prev_expr;
}
static inline int
bare_module_lexer__lex_regex(const utf8_t *s, size_t n, size_t i, size_t *result) {
i++;
bool in_character_class = false;
while (i < n) {
if (in_character_class) {
if (u(0) == ']') {
in_character_class = false;
} else if (c(0) == '\\') {
i++;
}
} else if (u(0) == '/') {
i++;
while (i < n && u(0) >= 'a' && u(0) <= 'z') i++;
*result = i;
return 0;
} else if (u(0) == '[') {
in_character_class = true;
} else if (u(0) == '\\') {
i++;
} else if (u(0) == ';' || lt(u(0))) {
return -1;
}
i++;
}
return -1;
}
// Parse a '.addon[.resolve]', '.asset', or '.resolve' member suffix at i
// (s[i] == '.'), OR'ing the matching type flags. Returns the index past the
// suffix and any trailing trivia, or i unchanged when the members don't
// match - the caller then lexes the '.' as ordinary member access.
static inline size_t
bare_module_lexer__lex_member_suffix(const utf8_t *s, size_t n, size_t i, int *type) {
size_t p = bare_module_lexer__skip_trivia(s, n, i + 1);
if (bare_module_lexer__at_kw(s, n, p, "addon", 5)) {
*type |= bare_module_lexer_addon;
p = bare_module_lexer__skip_trivia(s, n, p + 5);
if (p < n && s[p] == '.') {
size_t q = bare_module_lexer__skip_trivia(s, n, p + 1);
if (!bare_module_lexer__at_kw(s, n, q, "resolve", 7)) return i;
*type |= bare_module_lexer_resolve;
p = bare_module_lexer__skip_trivia(s, n, q + 7);
}
return p;
}
if (bare_module_lexer__at_kw(s, n, p, "asset", 5)) {
*type |= bare_module_lexer_asset;
return bare_module_lexer__skip_trivia(s, n, p + 5);
}
if (bare_module_lexer__at_kw(s, n, p, "resolve", 7)) {
*type |= bare_module_lexer_resolve;
return bare_module_lexer__skip_trivia(s, n, p + 7);
}
return i;
}
// Detect a TypeScript inline `type` modifier at the start of an
// import/export specifier. The modifier is the keyword `type` followed by
// another binding name - an identifier or string literal - rather than by
// `as`, `,`, or the closing brace. In `{ type }`, `{ type, x }`, and
// `{ type as x }` the word `type` is itself the imported/exported name, not a
// modifier. On a match, returns the index of the binding name that follows so
// the specifier can be skipped; otherwise returns i unchanged.
static inline size_t
bare_module_lexer__lex_type_modifier(const utf8_t *s, size_t n, size_t i) {
if (!bare_module_lexer__at_kw(s, n, i, "type", 4)) return i;
size_t p = bare_module_lexer__skip_trivia(s, n, i + 4);
if (p >= n) return i;
if (bare_module_lexer__at_kw(s, n, p, "as", 2)) return i;
if (idsl(s[p]) || s[p] == '\'' || s[p] == '"') return p;
return i;
}
static inline int
bare_module_lexer__lex_import_attributes(js_env_t *env, js_value_t **attributes, const utf8_t *s, size_t n, size_t i, size_t *result) {
int err;
size_t ks; // Key start
size_t ke; // Key end
size_t vs; // Value start
size_t ve; // Value end
i = bare_module_lexer__skip_trivia(s, n, i);
if (c(0) == '{') {
i++;
while (c(0) != '}') {
i = bare_module_lexer__skip_trivia(s, n, i);
if (i < n && idsl(u(0))) {
ks = i++;
while (i < n && idl(u(0))) i++;
ke = i;
} else if (c(0) == '\'' || c(0) == '"') {
if (!bare_module_lexer__lex_string(s, n, &i, &ks, &ke)) break;
} else break;
i = bare_module_lexer__skip_trivia(s, n, i);
if (c(0) != ':') break;
i++;
i = bare_module_lexer__skip_trivia(s, n, i);
if (c(0) != '\'' && c(0) != '"') break;
if (!bare_module_lexer__lex_string(s, n, &i, &vs, &ve)) break;
err = bare_module_lexer__add_attribute(env, attributes, &s[ks], ke - ks, &s[vs], ve - vs);
if (err < 0) return err;
i = bare_module_lexer__skip_trivia(s, n, i);
if (c(0) != ',') break;
i++;
}
}
*result = i;
return 0;
}
// Lex the '{ ... }' of an import or re-export specifier list, emitting a name
// for each value binding. TypeScript's inline `type` modifier erases the
// binding it precedes. When the list is non-empty and every binding is
// type-only, *only_type is set so the caller can erase the whole declaration;
// it is left false for an empty list, which is a bindingless side-effect
// import. *only_type may be NULL when the caller has a value binding of its
// own (a default or namespace import) and so never erases.
static inline int
bare_module_lexer__lex_import_names(js_env_t *env, js_value_t **names, uint32_t *nl, const utf8_t *s, size_t n, size_t i, size_t *result, bool *only_type) {
int err;
size_t ns; // Name start
size_t ne; // Name end
bool any = false; // Saw at least one binding
bool value = false; // Saw at least one value (non-type) binding
i = bare_module_lexer__skip_trivia(s, n, i);
if (c(0) == '{') {
i++;
while (c(0) != '}') {
i = bare_module_lexer__skip_trivia(s, n, i);
// A leading `type` modifier erases the binding it precedes.
size_t p = bare_module_lexer__lex_type_modifier(s, n, i);
bool is_type = p != i;
if (is_type) i = p;
// Name is an identifier or a string literal (arbitrary module
// namespace names, e.g. 'import { "x" as y }').
if (i < n && idsl(u(0))) {
ns = i++;
while (i < n && idl(u(0))) i++;
ne = i;
} else if (c(0) == '\'' || c(0) == '"') {
if (!bare_module_lexer__lex_string(s, n, &i, &ns, &ne)) break;
} else break;
any = true;
// A type-only binding is erased - keep no name for it.
if (!is_type) {
value = true;
err = bare_module_lexer__add_name(env, names, nl, &s[ns], ne - ns);
assert(err == 0);
}
i = bare_module_lexer__skip_trivia(s, n, i);
// [name] as
if (bare_module_lexer__at_kw(s, n, i, "as", 2)) {
i = bare_module_lexer__skip_trivia(s, n, i + 2);
// [name] as [alias] - the alias is an identifier or string literal.
if (c(0) == '\'' || c(0) == '"') {
size_t als, ale;
if (!bare_module_lexer__lex_string(s, n, &i, &als, &ale)) break;
} else {
while (i < n && idl(u(0))) i++;
}
i = bare_module_lexer__skip_trivia(s, n, i);
}
if (c(0) != ',') break;
i++;
}
while (i < n && u(0) != '}') i++;
if (c(0) == '}') i++;
}
if (only_type != NULL) *only_type = any && !value;
*result = i;
return 0;
}
// Parse a call tail '([\s]*<string>[, { with: { ... } }][^)]*)' at *result,
// which must point at the '('. On a match, fills the specifier bounds and
// any import attributes and leaves the cursor past the ')'. When the
// argument list doesn't start with a string literal, the cursor is left
// just inside the '(' so the arguments lex as ordinary code.
static inline int
bare_module_lexer__lex_call(js_env_t *env, js_value_t **attributes, const utf8_t *s, size_t n, size_t *result, size_t *ss, size_t *se, bool *matched) {
int err;
size_t i = *result;
*matched = false;
i++;
i = bare_module_lexer__skip_trivia(s, n, i);
if (c(0) == '\'' || c(0) == '"') {
if (bare_module_lexer__lex_string(s, n, &i, ss, se)) {
i = bare_module_lexer__skip_trivia(s, n, i);
if (c(0) == ',') {
i++;
i = bare_module_lexer__skip_trivia(s, n, i);
// An options object '{ with: { ... } }'.
if (c(0) == '{') {
size_t p = bare_module_lexer__skip_trivia(s, n, i + 1);
if (bare_module_lexer__at_kw(s, n, p, "with", 4)) {
p = bare_module_lexer__skip_trivia(s, n, p + 4);
if (p < n && s[p] == ':') {
err = bare_module_lexer__lex_import_attributes(env, attributes, s, n, p + 1, &i);
if (err < 0) return err;
}
}
}
}
while (i < n && u(0) != ')') i++;
if (c(0) == ')') {
i++;
*matched = true;
}
}
}
*result = i;
return 0;
}
static inline int
bare_module_lexer__lex(js_env_t *env, js_value_t *imports, js_value_t *exports, const utf8_t *s, size_t n) {
int err;
size_t i = 0;
size_t is; // Import start
uint32_t il = 0; // Import count
size_t es; // Export start
uint32_t el = 0; // Export count
size_t ss; // Source start
size_t se; // Source end
int type;
js_value_t *names;
uint32_t nl; // Names count
js_value_t *attributes;
bool matched;
// Set when an import's binding list is entirely TypeScript type-only
// specifiers, so the declaration is erased rather than emitted.
bool type_only;
// Previous significant token classification - see enum at top.
int prev = bare_module_lexer__prev_op;
// Open template substitutions: The brace depth at which each '${' was
// entered, so the matching '}' can resume template content scanning.
size_t template_stack[BARE_MODULE_LEXER__TEMPLATE_DEPTH];
int template_depth = 0;
int brace_depth = 0;
int paren_depth = 0;
int bracket_depth = 0;
// An exported declarator list ('export const a = 1, b = 2') whose
// initializer is being lexed as ordinary code. The list resumes at the
// next ',' at the recorded depths, so initializers may contain require()
// and import() and still be seen.
bool declarators = false;
int d_brace = 0;
int d_paren = 0;
int d_bracket = 0;
// A 'module.exports = { ... }' object literal whose property values are
// being lexed as ordinary code. The property list resumes at the next ','
// at the recorded depths, so values may contain require() and import()
// and still be seen.
bool properties = false;
int p_brace = 0;
int p_paren = 0;
int p_bracket = 0;
while (i < n) {
size_t j = bare_module_lexer__skip_trivia(s, n, i);
// A line terminator after a complete initializer ends a pending
// declarator list (the lexical approximation of ASI) - unless the next
// token is the ',' continuing it (comma-first style).
if (declarators && prev == bare_module_lexer__prev_expr && brace_depth == d_brace && paren_depth == d_paren && bracket_depth == d_bracket) {
while (i < j && !lt(u(0))) i++;
if (i < j && (j >= n || s[j] != ',')) declarators = false;
}
i = j;
if (i >= n) break;
uint8_t ch = u(0);
// String literal.
if (ch == '\'' || ch == '"') {
bare_module_lexer__lex_string(s, n, &i, &ss, &se);
prev = bare_module_lexer__prev_expr;
continue;
}
// Template literal. Content is inert, but each '${' substitution hands
// control back to this loop so the code inside is lexed normally; the
// matching '}' resumes content scanning via the substitution stack.
if (ch == '`') {
i++;
template : if (bare_module_lexer__lex_template(s, n, &i)) {
if (template_depth < BARE_MODULE_LEXER__TEMPLATE_DEPTH) {
template_stack[template_depth++] = brace_depth;
prev = bare_module_lexer__prev_op;
continue;
}
// Nested too deeply to track - skip the rest of the template
// wholesale rather than failing.
i = bare_module_lexer__skip_template(s, n, i - 1);
}
prev = bare_module_lexer__prev_expr;
continue;
}
// Regular expression or division.
if (ch == '/') {
if (prev == bare_module_lexer__prev_expr) {
i++;
prev = bare_module_lexer__prev_op;
continue;
}
err = bare_module_lexer__lex_regex(s, n, i, &i);
if (err < 0) {
i++;
prev = bare_module_lexer__prev_op;
} else {
prev = bare_module_lexer__prev_expr;
}
continue;
}
// Numeric literal.
if (ch >= '0' && ch <= '9') {
while (i < n && (id(u(0)) || u(0) == '.')) i++;
prev = bare_module_lexer__prev_expr;
continue;
}
// Identifier or keyword.
if (ids(ch) || ch >= 0x80) {
size_t ks = i;
while (i < n && idl(u(0))) i++;
size_t ke = i;
size_t kl = ke - ks;
// Member property - never a keyword, except the '__exportStar' family
// of helpers, which transpilers invoke through a module object
// ('tslib_1.__exportStar(require(...), exports)').
if (prev == bare_module_lexer__prev_dot && !bare_module_lexer__match_kw(s, ks, kl, "__export", 8) && !bare_module_lexer__match_kw(s, ks, kl, "__exportStar", 12)) {
prev = bare_module_lexer__prev_expr;
continue;
}
type = 0;
names = NULL;
attributes = NULL;
type_only = false;
if (bare_module_lexer__match_kw(s, ks, kl, "require", 7)) {
is = ks;
prev = bare_module_lexer__prev_expr;
goto require;
}
if (bare_module_lexer__match_kw(s, ks, kl, "import", 6)) {
type |= bare_module_lexer_import;
is = ks;
prev = bare_module_lexer__prev_expr;
size_t after = bare_module_lexer__skip_trivia(s, n, i);
bool separated = after > i;
i = after;
// import \*
if (c(0) == '*') {
i++;
i = bare_module_lexer__skip_trivia(s, n, i);
// import \* as
if (bare_module_lexer__at_kw(s, n, i, "as", 2)) {
i = bare_module_lexer__skip_trivia(s, n, i + 2);
// import \* as [^\s]+
while (i < n && !ws(u(0))) i++;
i = bare_module_lexer__skip_trivia(s, n, i);
// import \* as [^\s]+ from
if (bare_module_lexer__at_kw(s, n, i, "from", 4)) {
i += 4;
err = bare_module_lexer__add_name(env, &names, &nl, (const utf8_t *) "*", -1);
assert(err == 0);
goto from;
}
}
}
// import {
else if (c(0) == '{') {
err = bare_module_lexer__lex_import_names(env, &names, &nl, s, n, i, &i, &type_only);
if (err < 0) goto err;
i = bare_module_lexer__skip_trivia(s, n, i);
// import {[^}]*} from
if (bare_module_lexer__at_kw(s, n, i, "from", 4)) {
i += 4;
goto from;
}
}
// import ['"]
else if (c(0) == '\'' || c(0) == '"') {
if (bare_module_lexer__lex_string(s, n, &i, &ss, &se)) {
err = bare_module_lexer__add_import(env, imports, &il, s, is, ss, se, type, names, attributes);
if (err < 0) goto err;
}
}
// import\(
else if (c(0) == '(') {
type |= bare_module_lexer_dynamic;
err = bare_module_lexer__lex_call(env, &attributes, s, n, &i, &ss, &se, &matched);
if (err < 0) goto err;
if (matched) {
err = bare_module_lexer__add_import(env, imports, &il, s, is, ss, se, type, names, attributes);
if (err < 0) goto err;
}
}
// import\.
else if (c(0) == '.') {
i++;
i = bare_module_lexer__skip_trivia(s, n, i);
// import\.meta
if (bare_module_lexer__at_kw(s, n, i, "meta", 4)) {
i += 4;
i = bare_module_lexer__skip_trivia(s, n, i);
// import\.meta\.(addon(\.resolve)?|asset|resolve)
if (c(0) == '.') {
size_t p = bare_module_lexer__lex_member_suffix(s, n, i, &type);
if (p == i) continue;
i = p;
if (c(0) == '(') {
err = bare_module_lexer__lex_call(env, &attributes, s, n, &i, &ss, &se, &matched);
if (err < 0) goto err;
if (matched) {
err = bare_module_lexer__add_import(env, imports, &il, s, is, ss, se, type, names, attributes);
if (err < 0) goto err;
}
// import\.meta\.addon(\.resolve)?\(\)
else if (c(0) == ')' && (type & bare_module_lexer_addon)) {
ss = se = i++;
err = bare_module_lexer__add_import(env, imports, &il, s, is, ss, se, type, names, attributes);
if (err < 0) goto err;
}
}
}
}
}
// import [default]
else if (separated) {
size_t j = i;
// import [^\s,]+
while (i < n && !ws(u(0)) && u(0) != ',') i++;
if (j < i) {
i = bare_module_lexer__skip_trivia(s, n, i);
// import [^\s,]+ from
if (bare_module_lexer__at_kw(s, n, i, "from", 4)) {
i += 4;
err = bare_module_lexer__add_name(env, &names, &nl, (const utf8_t *) "default", -1);
assert(err == 0);
goto from;
}
// import [^\s,]+,
else if (c(0) == ',') {
i++;
i = bare_module_lexer__skip_trivia(s, n, i);
// import [^\s,]+, {
if (c(0) == '{') {
err = bare_module_lexer__add_name(env, &names, &nl, (const utf8_t *) "default", -1);
assert(err == 0);
err = bare_module_lexer__lex_import_names(env, &names, &nl, s, n, i, &i, NULL);
if (err < 0) goto err;
i = bare_module_lexer__skip_trivia(s, n, i);
// import [^\s,]+, {[^}]*} from
if (bare_module_lexer__at_kw(s, n, i, "from", 4)) {
i += 4;
goto from;
}
}
// import [^\s,]+, \*
else if (c(0) == '*') {
i++;
i = bare_module_lexer__skip_trivia(s, n, i);
// import [^\s,]+, \* as
if (bare_module_lexer__at_kw(s, n, i, "as", 2)) {
i = bare_module_lexer__skip_trivia(s, n, i + 2);
// import [^\s,]+, \* as [^\s]+
while (i < n && !ws(u(0))) i++;
i = bare_module_lexer__skip_trivia(s, n, i);
// import [^\s,]+, \* as [^\s]+ from
if (bare_module_lexer__at_kw(s, n, i, "from", 4)) {
i += 4;
err = bare_module_lexer__add_name(env, &names, &nl, (const utf8_t *) "default", -1);
assert(err == 0);
err = bare_module_lexer__add_name(env, &names, &nl, (const utf8_t *) "*", -1);
assert(err == 0);
goto from;
}
}
}
}
}
}
continue;
}
if (bare_module_lexer__match_kw(s, ks, kl, "module", 6)) {
es = ks;
prev = bare_module_lexer__prev_expr;
// Peek for '.exports' or '.require' without consuming, so other
// members ('module.id', ...) lex as plain member access.
size_t p = bare_module_lexer__skip_trivia(s, n, ke);
if (p < n && s[p] == '.') {
size_t q = bare_module_lexer__skip_trivia(s, n, p + 1);
// module\.exports
if (bare_module_lexer__at_kw(s, n, q, "exports", 7)) {
i = q + 7;
goto exports;
}
// module\.require - behaves like a plain require.
if (bare_module_lexer__at_kw(s, n, q, "require", 7)) {
is = q;
i = q + 7;
goto require;
}
}
continue;
}
if (bare_module_lexer__match_kw(s, ks, kl, "exports", 7)) {
es = ks;
prev = bare_module_lexer__prev_expr;
goto exports;
}
if (bare_module_lexer__match_kw(s, ks, kl, "export", 6)) {
es = ks;
prev = bare_module_lexer__prev_expr;
i = bare_module_lexer__skip_trivia(s, n, i);
// export \*
if (c(0) == '*') {
i++;
i = bare_module_lexer__skip_trivia(s, n, i);
// export \* as
if (bare_module_lexer__at_kw(s, n, i, "as", 2)) {
i = bare_module_lexer__skip_trivia(s, n, i + 2);
// export \* as [^\s]+
while (i < n && !ws(u(0))) i++;
i = bare_module_lexer__skip_trivia(s, n, i);
// export \* as [^\s]+ from
if (bare_module_lexer__at_kw(s, n, i, "from", 4)) {
type |= bare_module_lexer_import | bare_module_lexer_reexport;
is = es;
i += 4;
err = bare_module_lexer__add_name(env, &names, &nl, (const utf8_t *) "*", -1);
assert(err == 0);
goto from;
}
}
// export \* from
else if (bare_module_lexer__at_kw(s, n, i, "from", 4)) {
type |= bare_module_lexer_import | bare_module_lexer_reexport;
is = es;
i += 4;
err = bare_module_lexer__add_name(env, &names, &nl, (const utf8_t *) "*", -1);
assert(err == 0);
goto from;
}
}
// export {
else if (c(0) == '{') {
size_t bs = ++i;
// export {[^}]*
while (i < n && u(0) != '}') i++;
// export {[^}]*}
if (c(0) == '}') {
size_t be = i;
i++;
i = bare_module_lexer__skip_trivia(s, n, i);
// export {[^}]*} from
if (bare_module_lexer__at_kw(s, n, i, "from", 4)) {
type |= bare_module_lexer_import | bare_module_lexer_reexport;
is = es;
size_t resume = i + 4;
i = bs - 1;
err = bare_module_lexer__lex_import_names(env, &names, &nl, s, n, i, &i, &type_only);
if (err < 0) goto err;
i = resume;
goto from;
}
// export {[^}]*}
size_t resume = i;
i = bs;
while (i < be) {
i = bare_module_lexer__skip_trivia(s, be, i);
// A leading `type` modifier erases the binding it precedes.
size_t p = bare_module_lexer__lex_type_modifier(s, be, i);
bool is_type = p != i;
if (is_type) i = p;
if (i < be && idsl(u(0))) {
ss = i++;
while (i < be && idl(u(0))) i++;
se = i;
i = bare_module_lexer__skip_trivia(s, be, i);
// export {[^,}]+ as
if (bare_module_lexer__at_kw(s, be, i, "as", 2)) {
i = bare_module_lexer__skip_trivia(s, be, i + 2);
// export {[^,}]+ as [^\s,}]+ - the alias is the exported
// name, either an identifier or a string literal.
if (i < be && idsl(u(0))) {
ss = i++;
while (i < be && idl(u(0))) i++;
se = i;
} else if (i < be && (u(0) == '\'' || u(0) == '"')) {
if (!bare_module_lexer__lex_string(s, be, &i, &ss, &se)) break;
}
}
// A type-only binding is erased - export no name for it.
if (!is_type) {
err = bare_module_lexer__add_export(env, exports, &el, s, es, ss, se);
if (err < 0) goto err;
}
}
i = bare_module_lexer__skip_trivia(s, be, i);
if (c(0) != ',') break;
i++;
}
i = resume;
}
}
// export = require('id') - a TypeScript export assignment that
// re-exports the required module wholesale, like 'module.exports =
// require('id')'.
else if (c(0) == '=') {
i = bare_module_lexer__skip_trivia(s, n, i + 1);
// export = require
if (bare_module_lexer__at_kw(s, n, i, "require", 7)) {
type |= bare_module_lexer_reexport;
is = i;
i += 7;
goto require;
}
}
// export import name = require('id') - a CommonJS re-export via a
// TypeScript import assignment. The bound name is re-exported, so the
// require is flagged as a re-export.
else if (bare_module_lexer__at_kw(s, n, i, "import", 6)) {
i = bare_module_lexer__skip_trivia(s, n, i + 6);
// export import [name]
if (i < n && idsl(u(0))) {
while (i < n && idl(u(0))) i++;
i = bare_module_lexer__skip_trivia(s, n, i);
// export import [name] =
if (c(0) == '=') {
i = bare_module_lexer__skip_trivia(s, n, i + 1);
// export import [name] = require
if (bare_module_lexer__at_kw(s, n, i, "require", 7)) {
type |= bare_module_lexer_reexport;
is = i;
i += 7;
goto require;
}
}
}
}
// export const
else if (bare_module_lexer__at_kw(s, n, i, "const", 5)) {
i += 5;
goto declarations;
}
// export let
else if (bare_module_lexer__at_kw(s, n, i, "let", 3)) {
i += 3;
goto declarations;
}
// export var
else if (bare_module_lexer__at_kw(s, n, i, "var", 3)) {
i += 3;
goto declarations;
}
// export function
else if (bare_module_lexer__at_kw(s, n, i, "function", 8)) {
i += 8;
i = bare_module_lexer__skip_trivia(s, n, i);
// export function\*
if (c(0) == '*') i++;
goto declaration;
}
// export async function
else if (bare_module_lexer__at_kw(s, n, i, "async", 5)) {
size_t p = bare_module_lexer__skip_trivia(s, n, i + 5);
if (bare_module_lexer__at_kw(s, n, p, "function", 8)) {
i = bare_module_lexer__skip_trivia(s, n, p + 8);
// export async function\*
if (c(0) == '*') i++;
goto declaration;
}
}
// export class
else if (bare_module_lexer__at_kw(s, n, i, "class", 5)) {
i += 5;
goto declaration;
}
// export default
else if (bare_module_lexer__at_kw(s, n, i, "default", 7)) {
ss = i;
se = i + 7;
i += 7;
err = bare_module_lexer__add_export(env, exports, &el, s, es, ss, se);
if (err < 0) goto err;
}
continue;
}
if (bare_module_lexer__match_kw(s, ks, kl, "__export", 8) || bare_module_lexer__match_kw(s, ks, kl, "__exportStar", 12)) {
prev = bare_module_lexer__prev_expr;
i = bare_module_lexer__skip_trivia(s, n, i);
// __export(Star)?\(
if (c(0) == '(') {
i++;
i = bare_module_lexer__skip_trivia(s, n, i);
// __export(Star)?\(require
if (bare_module_lexer__at_kw(s, n, i, "require", 7)) {
type |= bare_module_lexer_reexport;
is = i;
i += 7;
goto require;
}
}
continue;
}
// TypeScript type alias 'type Name [<...>] = <type>'. The whole
// declaration is a type, so it is skipped and any import(...) inside it
// is not lexed as a module import. The alias name must sit on the same
// line as 'type' so a plain 'type' expression before a line break isn't
// mistaken for an alias.
if (bare_module_lexer__match_kw(s, ks, kl, "type", 4) && prev != bare_module_lexer__prev_dot) {
size_t p = bare_module_lexer__skip_trivia(s, n, ke);
if (p < n && idsl(s[p]) && !bare_module_lexer__has_line_terminator(s, ke, p)) {
size_t eq = bare_module_lexer__skip_type(s, n, p);
// 'type Name ... =' (but not '=>') confirms a type alias.
if (eq < n && s[eq] == '=' && !(eq + 1 < n && s[eq + 1] == '>')) {
i = bare_module_lexer__skip_type(s, n, eq + 1);
prev = bare_module_lexer__prev_op;
continue;
}
}
// Not an alias - fall through and treat 'type' as a plain identifier.
}
// TypeScript variable type annotation '(const|let|var) name: <type>'.
// The annotation is skipped so import(...) inside it isn't lexed as a
// module import, while an '=' initializer is left for the main loop so
// require()/import() in it are still seen.
if (prev == bare_module_lexer__prev_op && (bare_module_lexer__match_kw(s, ks, kl, "const", 5) || bare_module_lexer__match_kw(s, ks, kl, "let", 3) || bare_module_lexer__match_kw(s, ks, kl, "var", 3))) {
size_t p = bare_module_lexer__skip_trivia(s, n, ke);
if (p < n && idsl(s[p])) {
size_t q = p + 1;
while (q < n && idl(s[q])) q++;
size_t r = bare_module_lexer__skip_trivia(s, n, q);
if (r < n && s[r] == ':') {
i = bare_module_lexer__skip_type(s, n, r + 1);
prev = bare_module_lexer__prev_op;
continue;
}
}
// Fall through - a plain declaration lexed as ordinary code.
}
// Plain identifier or a keyword that only affects classification.
prev = bare_module_lexer__classify_kw(s, ks, kl);
continue;
}
// Punctuation relevant to context tracking.
if (ch == '.') {
// Spread '...' - an expression follows.
if (i + 2 < n && u(1) == '.' && u(2) == '.') {
i += 3;
prev = bare_module_lexer__prev_op;
continue;
}
i++;
prev = bare_module_lexer__prev_dot;
continue;
}
if (ch == '(') {
paren_depth++;
i++;
prev = bare_module_lexer__prev_op;
continue;
}
if (ch == ')') {
if (paren_depth > 0) paren_depth--;
i++;
prev = bare_module_lexer__prev_expr;
continue;
}
if (ch == '[') {
bracket_depth++;
i++;
prev = bare_module_lexer__prev_op;
continue;
}
if (ch == ']') {
if (bracket_depth > 0) bracket_depth--;
i++;
prev = bare_module_lexer__prev_expr;
continue;
}
if (ch == '{') {
brace_depth++;
i++;
prev = bare_module_lexer__prev_op;
continue;
}
if (ch == '}') {
// The '}' closing an open template substitution resumes template
// content scanning.
if (template_depth > 0 && brace_depth == template_stack[template_depth - 1]) {
template_depth--;
i++;
goto template;
}
if (brace_depth > 0) brace_depth--;
if (declarators && brace_depth < d_brace) declarators = false;
if (properties && brace_depth < p_brace) properties = false;
i++;
prev = bare_module_lexer__prev_op;
continue;
}
if (ch == ',') {
i++;
prev = bare_module_lexer__prev_op;
// A pending exported declarator list resumes at a ',' at its own
// nesting depth.
if (declarators && brace_depth == d_brace && paren_depth == d_paren && bracket_depth == d_bracket) {
goto declarations;
}
// A pending 'module.exports = { ... }' resumes at a ',' at its own
// nesting depth.
if (properties && brace_depth == p_brace && paren_depth == p_paren && bracket_depth == p_bracket) {
goto properties;
}
continue;
}
if (ch == ';') {
declarators = false;
i++;
prev = bare_module_lexer__prev_op;
continue;
}
i++;
prev = bare_module_lexer__prev_op;
continue;
require:
type |= bare_module_lexer_require;
i = bare_module_lexer__skip_trivia(s, n, i);
// require\.(addon(\.resolve)?|asset|resolve)
if (c(0) == '.') {
size_t p = bare_module_lexer__lex_member_suffix(s, n, i, &type);
if (p == i) continue;
i = p;
}
// require(\.(resolve|addon(\.resolve)?|asset))?\(
if (c(0) == '(') {
err = bare_module_lexer__lex_call(env, &attributes, s, n, &i, &ss, &se, &matched);
if (err < 0) goto err;
if (matched) {
err = bare_module_lexer__add_import(env, imports, &il, s, is, ss, se, type, names, attributes);
if (err < 0) goto err;
}
// require\.addon(\.resolve)?\(\)
else if (c(0) == ')' && (type & bare_module_lexer_addon)) {
ss = se = i++;
err = bare_module_lexer__add_import(env, imports, &il, s, is, ss, se, type, names, attributes);
if (err < 0) goto err;
}
}
continue;
exports:
i = bare_module_lexer__skip_trivia(s, n, i);
// exports =
if (c(0) == '=') {
i++;
i = bare_module_lexer__skip_trivia(s, n, i);
// exports = \{
if (c(0) == '{') {
i++;
brace_depth++;
prev = bare_module_lexer__prev_op;
properties = true;
p_brace = brace_depth;
p_paren = paren_depth;
p_bracket = bracket_depth;
goto properties;
}
// exports = require
else if (bare_module_lexer__at_kw(s, n, i, "require", 7)) {
type |= bare_module_lexer_reexport;
is = i;
i += 7;
goto require;
}
}
// exports\.
else if (c(0) == '.') {
i++;
i = bare_module_lexer__skip_trivia(s, n, i);
ss = i;
while (i < n && !ws(u(0)) && u(0) != '=') i++;
se = i;
i = bare_module_lexer__skip_trivia(s, n, i);
// exports\.[^\s=] =
if (c(0) == '=') {
i++;
err = bare_module_lexer__add_export(env, exports, &el, s, es, ss, se);
if (err < 0) goto err;
}
}
// exports\[
else if (c(0) == '[') {
i++;
i = bare_module_lexer__skip_trivia(s, n, i);
// exports\[['"]
if (c(0) == '\'' || c(0) == '"') {
if (bare_module_lexer__lex_string(s, n, &i, &ss, &se)) {
i = bare_module_lexer__skip_trivia(s, n, i);
while (i < n && u(0) != ']') i++;
// exports\[['"].*['"][^\]]*\]
if (c(0) == ']') {
i++;
i = bare_module_lexer__skip_trivia(s, n, i);
// exports\[['"].*['"][^\]]*\] =
if (c(0) == '=') {
i++;
err = bare_module_lexer__add_export(env, exports, &el, s, es, ss, se);
if (err < 0) goto err;
}
}
}
}
}
continue;
from:
i = bare_module_lexer__skip_trivia(s, n, i);
// from ['"]
if (c(0) == '\'' || c(0) == '"') {
if (bare_module_lexer__lex_string(s, n, &i, &ss, &se)) {
i = bare_module_lexer__skip_trivia(s, n, i);
if (bare_module_lexer__at_kw(s, n, i, "with", 4)) {
i += 4;
err = bare_module_lexer__lex_import_attributes(env, &attributes, s, n, i, &i);
if (err < 0) goto err;
}
// A type-only specifier list is erased along with its declaration.
if (!type_only) {
err = bare_module_lexer__add_import(env, imports, &il, s, is, ss, se, type, names, attributes);
if (err < 0) goto err;
}
}
}
continue;
declaration:
i = bare_module_lexer__skip_trivia(s, n, i);
// ((async )?function\*?|class) [identifier]
if (i < n && idsl(u(0))) {
ss = i++;
while (i < n && idl(u(0))) i++;
se = i;
err = bare_module_lexer__add_export(env, exports, &el, s, es, ss, se);
if (err < 0) goto err;
}
continue;
declarations:
declarators = false;
i = bare_module_lexer__skip_trivia(s, n, i);
// (const|let|var) followed by a destructuring pattern - the bindings
// are the exported names.
if (c(0) == '{' || c(0) == '[') {
err = bare_module_lexer__lex_pattern(env, exports, &el, s, n, es, &i, 0);
if (err < 0) goto err;
}
// (const|let|var) followed by a plain binding.
else if (i < n && idsl(u(0))) {
ss = i++;
while (i < n && idl(u(0))) i++;
se = i;
err = bare_module_lexer__add_export(env, exports, &el, s, es, ss, se);
if (err < 0) goto err;
}
else continue;
i = bare_module_lexer__skip_trivia(s, n, i);
// ',' - the next declarator follows immediately.
if (c(0) == ',') {
i++;
goto declarations;
}
// '=' - an initializer follows. Let the main loop lex it as ordinary
// code (it may contain require() or import()) and pick the declarator
// list back up at the next ',' at this nesting depth.
if (c(0) == '=') {
i++;
declarators = true;
d_brace = brace_depth;
d_paren = paren_depth;
d_bracket = bracket_depth;
}
continue;
properties:
i = bare_module_lexer__skip_trivia(s, n, i);
// End of object - let the outer loop consume the '}', which clears the
// properties state via brace depth tracking.
if (i >= n || u(0) == '}') continue;
// Stray ',' or leading ',' (comma-first style after the opening brace).
if (u(0) == ',') {
i++;
goto properties;
}
// Identifier key: shorthand, 'name: value', or a method.
if (idsl(u(0))) {
ss = i++;
while (i < n && idl(u(0))) i++;
se = i;
i = bare_module_lexer__skip_trivia(s, n, i);
// 'get name()', 'async name()' - the exported name follows the
// modifier.
if (i < n && idsl(u(0))) {
ss = i++;
while (i < n && idl(u(0))) i++;
se = i;
i = bare_module_lexer__skip_trivia(s, n, i);
}
err = bare_module_lexer__add_export(env, exports, &el, s, es, ss, se);
if (err < 0) goto err;
// 'name:' - let the outer loop lex the value and resume at the next
// ',' at this nesting depth.
if (c(0) == ':') {
i++;
prev = bare_module_lexer__prev_op;
continue;
}
// 'name(' - a method definition. Let the outer loop lex the parameter
// list and body as ordinary code (it may contain require() or
// import()) and resume at the next ',' at this nesting depth.
if (c(0) == '(') {
prev = bare_module_lexer__prev_op;
continue;
}
// Shorthand - the next entry follows.
goto properties;
}
// Spread of require(...) - 'module.exports = { ...require("x") }' spreads
// the required module's exports onto 'module.exports', which is the same
// re-export as 'module.exports = require("x")'.
if (i + 2 < n && u(0) == '.' && u(1) == '.' && u(2) == '.') {
size_t p = bare_module_lexer__skip_trivia(s, n, i + 3);
if (bare_module_lexer__at_kw(s, n, p, "require", 7)) {
type = bare_module_lexer_reexport;
names = NULL;
attributes = NULL;
is = p;
i = p + 7;
prev = bare_module_lexer__prev_expr;
goto require;
}
}
// String key - 'module.exports = { "foo": ... }'. The string literal is a
// statically known export name, used when the name isn't a valid
// identifier (e.g. a kebab-cased name).
if (u(0) == '\'' || u(0) == '"') {
size_t ns; // Name start
size_t ne; // Name end
if (bare_module_lexer__lex_string(s, n, &i, &ns, &ne)) {
i = bare_module_lexer__skip_trivia(s, n, i);
// '"foo":' (property) or '"foo"(' (method) - both name an export.
if (c(0) == ':' || c(0) == '(') {
err = bare_module_lexer__add_export(env, exports, &el, s, es, ns, ne);
if (err < 0) goto err;
// Consume the ':' but leave the '(' for the outer loop to lex the
// method's parameter list and body.
if (c(0) == ':') i++;
prev = bare_module_lexer__prev_op;
continue;
}
}
// Unterminated string or not a key. Resume at the next entry.
prev = bare_module_lexer__prev_expr;
continue;
}
// Computed key with a string literal - 'module.exports = { ["foo"]: ... }'.
// The name is still statically known. Other computed keys (identifiers,
// expressions, or a require()) can't be resolved and fall through to be
// lexed as ordinary code below.
if (u(0) == '[') {
size_t p = bare_module_lexer__skip_trivia(s, n, i + 1);
if (p < n && (s[p] == '\'' || s[p] == '"')) {
size_t ns; // Name start
size_t ne; // Name end
size_t q = p;
if (bare_module_lexer__lex_string(s, n, &q, &ns, &ne)) {
q = bare_module_lexer__skip_trivia(s, n, q);
if (q < n && s[q] == ']') {
q = bare_module_lexer__skip_trivia(s, n, q + 1);
// '["foo"]:' (property) or '["foo"](' (method).
if (q < n && (s[q] == ':' || s[q] == '(')) {
err = bare_module_lexer__add_export(env, exports, &el, s, es, ns, ne);
if (err < 0) goto err;
i = s[q] == ':' ? q + 1 : q;
prev = bare_module_lexer__prev_op;
continue;
}
}
}
}
}
// Numeric key, generator method, a spread of anything other than
// require(), or any other computed key. No export name can be extracted
// (a numeric key would require canonicalization, e.g. '0xff' names the
// property "255"), but the entry may still contain require() or import().
// Let the outer loop lex it as ordinary code and resume at the next ','
// at this nesting depth.
prev = bare_module_lexer__prev_op;
continue;
}
#undef u
#undef c
#undef lt
#undef ws
#undef ids
#undef id
#undef idl
#undef idsl
return 0;
err:
return -1;
}
#endif // BARE_MODULE_LEXER_H