@marko/tree-sitter 0.2.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
  }
@@ -738,6 +743,11 @@ static bool check_for_operators(EStream *es, ExprState *e, bool eol) {
738
743
  }
739
744
 
740
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
+
741
751
  if (next_c >= 0 && !should_terminate(es, e, next_c, k)) {
742
752
  int64_t consume = look_ahead_for_operator(es, e, k);
743
753
  if (consume != -1) {
@@ -1335,6 +1345,144 @@ static ExprCfg cfg_type_expr(void) {
1335
1345
  return cfg;
1336
1346
  }
1337
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
+
1338
1486
  static ExprCfg cfg_attr_name(Scanner *s) {
1339
1487
  ExprCfg cfg;
1340
1488
  memset(&cfg, 0, sizeof(cfg));
@@ -3273,6 +3421,18 @@ static bool scan_open_tag_es(Scanner *s, TSLexer *lexer, const bool *valid,
3273
3421
  s->tag_has_attrs = 1;
3274
3422
  }
3275
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
+
3276
3436
  if (c == '<') return false; // INVALID_ATTRIBUTE_NAME
3277
3437
  {
3278
3438
  bool empty = false;
@@ -3550,7 +3710,8 @@ static const char *const TOKEN_NAMES[] = {
3550
3710
  "PARAM_TYPE", "PARAM_EQ", "PARAM_DEFAULT", "PARAM_COMMA",
3551
3711
  "PARAMS_CLOSE", "TYPE_ARGS_OPEN",
3552
3712
  "TYPE_PARAMS_OPEN", "TYPE_EXPR", "TYPES_CLOSE", "ATTR_GROUP_OPEN",
3553
- "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",
3554
3715
  "ATTR_SPREAD_START", "ATTR_VALUE_EXPR", "METHOD_BODY_OPEN",
3555
3716
  "METHOD_BODY_EXPR", "METHOD_BODY_CLOSE", "OPEN_TAG_END",
3556
3717
  "OPEN_TAG_END_SELF", "CONCISE_OPEN_TAG_END", "CLOSE_TAG_START",
Binary file
package/tree-sitter.json CHANGED
@@ -16,7 +16,7 @@
16
16
  }
17
17
  ],
18
18
  "metadata": {
19
- "version": "0.2.0",
19
+ "version": "0.3.0",
20
20
  "license": "MIT",
21
21
  "description": "Tree-sitter grammar for the Marko templating language",
22
22
  "links": {