@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/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
- return at_start || prev != '=';
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 any whitespace, including newlines): if so the open tag
1240
- // continues on the next line (consumeWhitespaceIfBefore(",")).
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
- // raw lookahead beyond the committed token end
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
- // consumeWhitespaceIfBefore(","): skip to and through the comma.
2943
- while (!at_eof(lexer) && is_ws(lexer->lookahead)) skipc(s, lexer);
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", "ATTR_NAME", "ATTR_EQ", "ATTR_BOUND_EQ",
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",
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": ["marko"],
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.1.0",
19
+ "version": "0.3.0",
18
20
  "license": "MIT",
19
21
  "description": "Tree-sitter grammar for the Marko templating language",
20
22
  "links": {