@marko/tree-sitter 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/grammar.js +2 -0
- package/package.json +19 -13
- package/prebuilds/darwin-arm64/@marko+tree-sitter.node +0 -0
- package/prebuilds/darwin-x64/@marko+tree-sitter.node +0 -0
- package/prebuilds/linux-arm64/@marko+tree-sitter.node +0 -0
- package/prebuilds/linux-x64/@marko+tree-sitter.node +0 -0
- package/prebuilds/win32-x64/@marko+tree-sitter.node +0 -0
- package/src/grammar.json +8 -0
- package/src/node-types.json +12 -0
- package/src/parser.c +2151 -2050
- package/src/scanner.c +222 -10
- package/tree-sitter-marko.wasm +0 -0
- package/tree-sitter.json +4 -2
package/src/scanner.c
CHANGED
|
@@ -79,6 +79,7 @@ enum TokenType {
|
|
|
79
79
|
TYPES_CLOSE, // ">"
|
|
80
80
|
ATTR_GROUP_OPEN, // "["
|
|
81
81
|
ATTR_GROUP_CLOSE, // "]"
|
|
82
|
+
ATTR_METHOD_ASYNC, // "async" before a shorthand method
|
|
82
83
|
ATTR_NAME,
|
|
83
84
|
ATTR_EQ, // "="
|
|
84
85
|
ATTR_BOUND_EQ, // ":="
|
|
@@ -499,7 +500,11 @@ static bool should_terminate(EStream *es, ExprState *e, int32_t code,
|
|
|
499
500
|
return next == '>';
|
|
500
501
|
case '>':
|
|
501
502
|
// `=>` continues unless it is the very start of the expression.
|
|
502
|
-
|
|
503
|
+
if (at_start) return true;
|
|
504
|
+
if (prev == '=') return false;
|
|
505
|
+
// A whitespace preceded ">=" is always a comparison: a closed tag
|
|
506
|
+
// would have put the "=" in its body content.
|
|
507
|
+
return !(is_ws(prev) && next == '=');
|
|
503
508
|
default:
|
|
504
509
|
return false;
|
|
505
510
|
}
|
|
@@ -623,6 +628,7 @@ static int64_t look_behind_for_operator(EStream *es, ExprState *e,
|
|
|
623
628
|
}
|
|
624
629
|
|
|
625
630
|
default: {
|
|
631
|
+
if (code < 'a' || code > 'z') return -1;
|
|
626
632
|
const char *const *keywords =
|
|
627
633
|
e->in_type ? TS_UNARY_KEYWORDS : UNARY_KEYWORDS;
|
|
628
634
|
for (int i = 0; keywords[i]; i++) {
|
|
@@ -659,6 +665,7 @@ static int64_t look_ahead_for_operator(EStream *es, ExprState *e, uint32_t k) {
|
|
|
659
665
|
}
|
|
660
666
|
|
|
661
667
|
default: {
|
|
668
|
+
if (c < 'a' || c > 'z') return -1;
|
|
662
669
|
for (int i = 0; BINARY_KEYWORDS[i]; i++) {
|
|
663
670
|
const char *kw = BINARY_KEYWORDS[i];
|
|
664
671
|
size_t len = strlen(kw);
|
|
@@ -736,6 +743,11 @@ static bool check_for_operators(EStream *es, ExprState *e, bool eol) {
|
|
|
736
743
|
}
|
|
737
744
|
|
|
738
745
|
int32_t next_c = es_peek(es, k);
|
|
746
|
+
// In HTML mode a "</" is a close tag rather than a less-than operator.
|
|
747
|
+
if (!is_concise(s) && next_c == '<' && es_peek(es, k + 1) == '/') {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
|
|
739
751
|
if (next_c >= 0 && !should_terminate(es, e, next_c, k)) {
|
|
740
752
|
int64_t consume = look_ahead_for_operator(es, e, k);
|
|
741
753
|
if (consume != -1) {
|
|
@@ -880,6 +892,11 @@ static bool scan_expr_inner(EStream *es, ExprState *e) {
|
|
|
880
892
|
continue;
|
|
881
893
|
}
|
|
882
894
|
|
|
895
|
+
if (is_word_code(code)) {
|
|
896
|
+
es_next(es);
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
|
|
883
900
|
if (!e->group_len) {
|
|
884
901
|
if (e->cfg.terminated_by_whitespace && is_ws(code)) {
|
|
885
902
|
if (!check_for_operators(es, e, false)) return true;
|
|
@@ -1017,6 +1034,12 @@ static bool scan_expr_inner(EStream *es, ExprState *e) {
|
|
|
1017
1034
|
es_next(es);
|
|
1018
1035
|
break;
|
|
1019
1036
|
case '{':
|
|
1037
|
+
if (e->in_type && !e->force_type) {
|
|
1038
|
+
int64_t prev_pos = look_behind_while_ws(es, (int64_t)s->buf_len - 1);
|
|
1039
|
+
if (look_behind_for_operator(es, e, prev_pos) == -1) {
|
|
1040
|
+
e->in_type = false;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1020
1043
|
if (e->group_len >= sizeof(e->group)) return false;
|
|
1021
1044
|
e->group[e->group_len++] = '}';
|
|
1022
1045
|
es_next(es);
|
|
@@ -1235,14 +1258,53 @@ static bool scan_open_tag_es(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
|
1235
1258
|
static bool scan_frame_content(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
1236
1259
|
TSSymbol *result);
|
|
1237
1260
|
|
|
1261
|
+
static inline void skip_or_peek(Scanner *s, TSLexer *lexer, bool consume) {
|
|
1262
|
+
if (consume) skipc(s, lexer);
|
|
1263
|
+
else lexer->advance(lexer, false);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// Skip whitespace and `//`/`/* */` comments, returning true if a `,` follows.
|
|
1267
|
+
// `consume` skips them as trivia, otherwise it is raw lookahead beyond the
|
|
1268
|
+
// committed token end. Mirrors the concise-mode eol scan in OPEN_TAG.
|
|
1269
|
+
static bool scan_line_attr_trivia(Scanner *s, TSLexer *lexer, bool consume) {
|
|
1270
|
+
for (;;) {
|
|
1271
|
+
if (at_eof(lexer)) return false;
|
|
1272
|
+
int32_t c = lexer->lookahead;
|
|
1273
|
+
if (is_ws(c)) {
|
|
1274
|
+
skip_or_peek(s, lexer, consume);
|
|
1275
|
+
} else if (c == '/') {
|
|
1276
|
+
skip_or_peek(s, lexer, consume);
|
|
1277
|
+
if (lexer->lookahead == '/') {
|
|
1278
|
+
// line comment
|
|
1279
|
+
skip_or_peek(s, lexer, consume);
|
|
1280
|
+
while (!at_eof(lexer) && !is_line(lexer->lookahead))
|
|
1281
|
+
skip_or_peek(s, lexer, consume);
|
|
1282
|
+
} else if (lexer->lookahead == '*') {
|
|
1283
|
+
// block comment
|
|
1284
|
+
int32_t prev = 0;
|
|
1285
|
+
skip_or_peek(s, lexer, consume);
|
|
1286
|
+
while (!at_eof(lexer) && !(prev == '*' && lexer->lookahead == '/')) {
|
|
1287
|
+
prev = lexer->lookahead;
|
|
1288
|
+
skip_or_peek(s, lexer, consume);
|
|
1289
|
+
}
|
|
1290
|
+
if (at_eof(lexer)) return false;
|
|
1291
|
+
skip_or_peek(s, lexer, consume);
|
|
1292
|
+
} else {
|
|
1293
|
+
return false;
|
|
1294
|
+
}
|
|
1295
|
+
} else {
|
|
1296
|
+
break;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
return lexer->lookahead == ',';
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1238
1302
|
// After most tokens inside a concise open tag, look ahead to see whether a
|
|
1239
|
-
// `,` follows (over
|
|
1240
|
-
//
|
|
1303
|
+
// `,` follows (over whitespace and comments): if so the open tag continues on
|
|
1304
|
+
// the next line with another line attribute.
|
|
1241
1305
|
static void concise_tag_epilogue(Scanner *s, TSLexer *lexer) {
|
|
1242
1306
|
if (!s->tag_open || !is_concise(s) || s->in_attr_group) return;
|
|
1243
|
-
|
|
1244
|
-
while (!at_eof(lexer) && is_ws(lexer->lookahead)) lexer->advance(lexer, false);
|
|
1245
|
-
s->tag_comma_continue = !at_eof(lexer) && lexer->lookahead == ',';
|
|
1307
|
+
s->tag_comma_continue = scan_line_attr_trivia(s, lexer, false);
|
|
1246
1308
|
}
|
|
1247
1309
|
|
|
1248
1310
|
// --------------------------------------------------------------------------
|
|
@@ -1283,6 +1345,144 @@ static ExprCfg cfg_type_expr(void) {
|
|
|
1283
1345
|
return cfg;
|
|
1284
1346
|
}
|
|
1285
1347
|
|
|
1348
|
+
static inline bool is_attr_name_char(int32_t c) {
|
|
1349
|
+
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
|
1350
|
+
(c >= '0' && c <= '9') || c == '_' || c == '$' || c == '-';
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// Whitespace the async keyword may be separated from its method by. A newline
|
|
1354
|
+
// ends a concise mode attribute, so only indentation counts there.
|
|
1355
|
+
static inline bool skip_async_space(EStream *es, Scanner *s, uint32_t k) {
|
|
1356
|
+
int32_t c = es_peek(es, k);
|
|
1357
|
+
if (c < 0) return false;
|
|
1358
|
+
return is_concise(s) ? is_indent_code(c) : is_ws(c);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// A token cannot be taken back, so the whole signature is checked up front:
|
|
1362
|
+
// the parser defers instead, until the args close and a "{" follows.
|
|
1363
|
+
// The attribute name is exactly "async": a longer name ("asyncfoo") or one
|
|
1364
|
+
// carrying a modifier ("async:mod") is an ordinary attribute name.
|
|
1365
|
+
static bool is_async_keyword(EStream *es) {
|
|
1366
|
+
static const char kw[] = "async";
|
|
1367
|
+
for (uint32_t i = 0; i < 5; i++) {
|
|
1368
|
+
if (es_peek(es, i) != kw[i]) return false;
|
|
1369
|
+
}
|
|
1370
|
+
int32_t c = es_peek(es, 5);
|
|
1371
|
+
return c != ':' && !is_attr_name_char(c);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// Skips a balanced group, stepping over the literals and comments whose
|
|
1375
|
+
// contents would otherwise be counted as delimiters.
|
|
1376
|
+
static bool skip_async_group(EStream *es, uint32_t *pk, int32_t open,
|
|
1377
|
+
int32_t close) {
|
|
1378
|
+
uint32_t k = *pk;
|
|
1379
|
+
uint32_t depth = 0;
|
|
1380
|
+
int32_t prev = 0;
|
|
1381
|
+
|
|
1382
|
+
do {
|
|
1383
|
+
int32_t c = es_peek(es, k);
|
|
1384
|
+
if (c < 0) return false;
|
|
1385
|
+
|
|
1386
|
+
if (c == '"' || c == '\'' || c == '`') {
|
|
1387
|
+
int32_t quote = c;
|
|
1388
|
+
uint32_t tmpl_depth = 0;
|
|
1389
|
+
k++;
|
|
1390
|
+
for (;;) {
|
|
1391
|
+
c = es_peek(es, k);
|
|
1392
|
+
if (c < 0) return false;
|
|
1393
|
+
k++;
|
|
1394
|
+
if (c == '\\') {
|
|
1395
|
+
if (es_peek(es, k) < 0) return false;
|
|
1396
|
+
k++;
|
|
1397
|
+
} else if (quote == '`' && c == '$' && es_peek(es, k) == '{') {
|
|
1398
|
+
tmpl_depth++;
|
|
1399
|
+
k++;
|
|
1400
|
+
} else if (tmpl_depth && c == '}') {
|
|
1401
|
+
tmpl_depth--;
|
|
1402
|
+
} else if (c == quote && !tmpl_depth) {
|
|
1403
|
+
break;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
prev = quote;
|
|
1407
|
+
continue;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
if (c == '/') {
|
|
1411
|
+
int32_t next = es_peek(es, k + 1);
|
|
1412
|
+
if (!can_follow_division(prev)) { // a regular expression literal
|
|
1413
|
+
bool in_class = false;
|
|
1414
|
+
k++;
|
|
1415
|
+
for (;;) {
|
|
1416
|
+
c = es_peek(es, k);
|
|
1417
|
+
if (c < 0 || c == '\n') return false;
|
|
1418
|
+
k++;
|
|
1419
|
+
if (c == '\\') {
|
|
1420
|
+
if (es_peek(es, k) < 0) return false;
|
|
1421
|
+
k++;
|
|
1422
|
+
} else if (c == '[') in_class = true;
|
|
1423
|
+
else if (c == ']') in_class = false;
|
|
1424
|
+
else if (c == '/' && !in_class) break;
|
|
1425
|
+
}
|
|
1426
|
+
prev = '/';
|
|
1427
|
+
continue;
|
|
1428
|
+
}
|
|
1429
|
+
if (next == '/') {
|
|
1430
|
+
while ((c = es_peek(es, k)) >= 0 && c != '\n') k++;
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
if (next == '*') {
|
|
1434
|
+
k += 2;
|
|
1435
|
+
while ((c = es_peek(es, k)) >= 0 &&
|
|
1436
|
+
!(c == '*' && es_peek(es, k + 1) == '/')) {
|
|
1437
|
+
k++;
|
|
1438
|
+
}
|
|
1439
|
+
if (c < 0) return false;
|
|
1440
|
+
k += 2;
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
if (c == open) depth++;
|
|
1446
|
+
else if (c == close) depth--;
|
|
1447
|
+
if (!is_ws(c)) prev = c;
|
|
1448
|
+
k++;
|
|
1449
|
+
} while (depth);
|
|
1450
|
+
|
|
1451
|
+
*pk = k;
|
|
1452
|
+
return true;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
static bool is_async_method_ahead(EStream *es, Scanner *s) {
|
|
1456
|
+
uint32_t k = 0;
|
|
1457
|
+
|
|
1458
|
+
// In concise mode a newline ends the attribute, so the method cannot
|
|
1459
|
+
// continue onto the next line.
|
|
1460
|
+
bool had_space = false;
|
|
1461
|
+
while (skip_async_space(es, s, k)) {
|
|
1462
|
+
k++;
|
|
1463
|
+
had_space = true;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
// An optional method name, which must be separated from the keyword.
|
|
1467
|
+
if (is_attr_name_char(es_peek(es, k))) {
|
|
1468
|
+
if (!had_space) return false;
|
|
1469
|
+
while (is_attr_name_char(es_peek(es, k))) k++;
|
|
1470
|
+
while (skip_async_space(es, s, k)) k++;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// Optional type params, then the params, then the body opener.
|
|
1474
|
+
if (es_peek(es, k) == '<') {
|
|
1475
|
+
if (!skip_async_group(es, &k, '<', '>')) return false;
|
|
1476
|
+
while (skip_async_space(es, s, k)) k++;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
if (es_peek(es, k) != '(') return false;
|
|
1480
|
+
if (!skip_async_group(es, &k, '(', ')')) return false;
|
|
1481
|
+
|
|
1482
|
+
while (skip_async_space(es, s, k)) k++;
|
|
1483
|
+
return es_peek(es, k) == '{';
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1286
1486
|
static ExprCfg cfg_attr_name(Scanner *s) {
|
|
1287
1487
|
ExprCfg cfg;
|
|
1288
1488
|
memset(&cfg, 0, sizeof(cfg));
|
|
@@ -2939,9 +3139,8 @@ static bool scan_open_tag_es(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
|
2939
3139
|
continue;
|
|
2940
3140
|
}
|
|
2941
3141
|
if (s->tag_comma_continue) {
|
|
2942
|
-
//
|
|
2943
|
-
|
|
2944
|
-
if (!at_eof(lexer) && lexer->lookahead == ',') skipc(s, lexer);
|
|
3142
|
+
// comma continues the open tag with another line attribute
|
|
3143
|
+
if (scan_line_attr_trivia(s, lexer, true)) skipc(s, lexer);
|
|
2945
3144
|
while (!at_eof(lexer) && is_ws(lexer->lookahead)) skipc(s, lexer);
|
|
2946
3145
|
s->tag_comma_continue = 0;
|
|
2947
3146
|
continue;
|
|
@@ -3222,6 +3421,18 @@ static bool scan_open_tag_es(Scanner *s, TSLexer *lexer, const bool *valid,
|
|
|
3222
3421
|
s->tag_has_attrs = 1;
|
|
3223
3422
|
}
|
|
3224
3423
|
|
|
3424
|
+
// Both readings span the same text, so only the symbol depends on the
|
|
3425
|
+
// lookahead; marking first is what keeps the extent right while peeking.
|
|
3426
|
+
if (c == 'a' && is_async_keyword(es)) {
|
|
3427
|
+
for (int i = 0; i < 5; i++) es_next_mark(es); // "async"
|
|
3428
|
+
bool method = is_async_method_ahead(es, s);
|
|
3429
|
+
if (!valid[method ? ATTR_METHOD_ASYNC : ATTR_NAME]) return false;
|
|
3430
|
+
s->attr_stage = ATTR_NAME_STAGE;
|
|
3431
|
+
lexer->result_symbol = method ? ATTR_METHOD_ASYNC : ATTR_NAME;
|
|
3432
|
+
concise_tag_epilogue(s, lexer);
|
|
3433
|
+
return true;
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3225
3436
|
if (c == '<') return false; // INVALID_ATTRIBUTE_NAME
|
|
3226
3437
|
{
|
|
3227
3438
|
bool empty = false;
|
|
@@ -3499,7 +3710,8 @@ static const char *const TOKEN_NAMES[] = {
|
|
|
3499
3710
|
"PARAM_TYPE", "PARAM_EQ", "PARAM_DEFAULT", "PARAM_COMMA",
|
|
3500
3711
|
"PARAMS_CLOSE", "TYPE_ARGS_OPEN",
|
|
3501
3712
|
"TYPE_PARAMS_OPEN", "TYPE_EXPR", "TYPES_CLOSE", "ATTR_GROUP_OPEN",
|
|
3502
|
-
"ATTR_GROUP_CLOSE", "
|
|
3713
|
+
"ATTR_GROUP_CLOSE", "ATTR_METHOD_ASYNC", "ATTR_NAME", "ATTR_EQ",
|
|
3714
|
+
"ATTR_BOUND_EQ",
|
|
3503
3715
|
"ATTR_SPREAD_START", "ATTR_VALUE_EXPR", "METHOD_BODY_OPEN",
|
|
3504
3716
|
"METHOD_BODY_EXPR", "METHOD_BODY_CLOSE", "OPEN_TAG_END",
|
|
3505
3717
|
"OPEN_TAG_END_SELF", "CONCISE_OPEN_TAG_END", "CLOSE_TAG_START",
|
package/tree-sitter-marko.wasm
CHANGED
|
Binary file
|
package/tree-sitter.json
CHANGED
|
@@ -7,14 +7,16 @@
|
|
|
7
7
|
"title": "Marko",
|
|
8
8
|
"scope": "source.marko",
|
|
9
9
|
"path": ".",
|
|
10
|
-
"file-types": [
|
|
10
|
+
"file-types": [
|
|
11
|
+
"marko"
|
|
12
|
+
],
|
|
11
13
|
"injection-regex": "^marko$",
|
|
12
14
|
"highlights": "queries/highlights.scm",
|
|
13
15
|
"injections": "queries/injections.scm"
|
|
14
16
|
}
|
|
15
17
|
],
|
|
16
18
|
"metadata": {
|
|
17
|
-
"version": "0.
|
|
19
|
+
"version": "0.3.0",
|
|
18
20
|
"license": "MIT",
|
|
19
21
|
"description": "Tree-sitter grammar for the Marko templating language",
|
|
20
22
|
"links": {
|