summitjs 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/dist/summit.js CHANGED
@@ -339,6 +339,35 @@ function createScope(raw) {
339
339
  }
340
340
 
341
341
  // src/evaluator/lexer.ts
342
+ var REGEX_PRECEDING_KEYWORDS = /* @__PURE__ */ new Set([
343
+ "return",
344
+ "typeof",
345
+ "void",
346
+ "in",
347
+ "instanceof",
348
+ "new",
349
+ "of",
350
+ "delete",
351
+ "do",
352
+ "else"
353
+ ]);
354
+ function regexAllowed(prev) {
355
+ if (!prev) return true;
356
+ switch (prev.type) {
357
+ case "num":
358
+ case "str":
359
+ case "tmpl":
360
+ case "regex":
361
+ return false;
362
+ // a value precedes -> division
363
+ case "ident":
364
+ return REGEX_PRECEDING_KEYWORDS.has(prev.value);
365
+ case "punc":
366
+ return prev.value !== ")" && prev.value !== "]";
367
+ default:
368
+ return true;
369
+ }
370
+ }
342
371
  var PUNCTUATORS = [
343
372
  "...",
344
373
  "===",
@@ -485,6 +514,35 @@ function tokenize(input) {
485
514
  tokens2.push({ type: "ident", value: input.slice(start2, i), start: start2, end: i });
486
515
  continue;
487
516
  }
517
+ if (ch === "/" && regexAllowed(tokens2[tokens2.length - 1])) {
518
+ const start2 = i;
519
+ i++;
520
+ let body = "";
521
+ let inClass = false;
522
+ while (i < n) {
523
+ const c = input[i];
524
+ if (c === "\\") {
525
+ body += c + (input[i + 1] ?? "");
526
+ i += 2;
527
+ continue;
528
+ }
529
+ if (c === "\n") throw new LexError(`Unterminated regex in expression: ${input}`);
530
+ if (c === "[") inClass = true;
531
+ else if (c === "]") inClass = false;
532
+ else if (c === "/" && !inClass) break;
533
+ body += c;
534
+ i++;
535
+ }
536
+ if (input[i] !== "/") throw new LexError(`Unterminated regex in expression: ${input}`);
537
+ i++;
538
+ let flags = "";
539
+ while (i < n && /[a-z]/i.test(input[i])) {
540
+ flags += input[i];
541
+ i++;
542
+ }
543
+ tokens2.push({ type: "regex", value: body, flags, start: start2, end: i });
544
+ continue;
545
+ }
488
546
  let matched = false;
489
547
  for (const p of PUNCTUATORS) {
490
548
  if (input.startsWith(p, i)) {
@@ -828,8 +886,34 @@ var Parser = class {
828
886
  }
829
887
  return node;
830
888
  }
889
+ parseNewExpr() {
890
+ this.pos++;
891
+ let callee = this.parsePrimary();
892
+ for (; ; ) {
893
+ if (this.isPunc(".")) {
894
+ this.pos++;
895
+ const name = this.next();
896
+ callee = {
897
+ type: "MemberExpression",
898
+ object: callee,
899
+ property: { type: "Identifier", name: name.value },
900
+ computed: false,
901
+ optional: false
902
+ };
903
+ } else if (this.isPunc("[")) {
904
+ this.pos++;
905
+ const property = this.parseExpression();
906
+ this.eatPunc("]");
907
+ callee = { type: "MemberExpression", object: callee, property, computed: true, optional: false };
908
+ } else {
909
+ break;
910
+ }
911
+ }
912
+ const args = this.isPunc("(") ? this.parseArguments() : [];
913
+ return { type: "NewExpression", callee, arguments: args };
914
+ }
831
915
  parseCallMember() {
832
- let node = this.parsePrimary();
916
+ let node = this.isKeyword("new") ? this.parseNewExpr() : this.parsePrimary();
833
917
  for (; ; ) {
834
918
  if (this.isPunc(".")) {
835
919
  this.pos++;
@@ -904,6 +988,10 @@ var Parser = class {
904
988
  this.pos++;
905
989
  return this.parseTemplate(t.raw ?? "");
906
990
  }
991
+ if (t.type === "regex") {
992
+ this.pos++;
993
+ return { type: "RegexLiteral", pattern: t.value, flags: t.flags ?? "" };
994
+ }
907
995
  if (t.type === "ident") {
908
996
  if (t.value === "true" || t.value === "false") {
909
997
  this.pos++;
@@ -985,6 +1073,11 @@ var Parser = class {
985
1073
  return { type: "ObjectExpression", properties };
986
1074
  }
987
1075
  parseObjectProperty() {
1076
+ if (this.isKeyword("async") && this.peek(1).type === "ident") {
1077
+ throw new ParseError(
1078
+ "async methods are not supported in Summit expressions. Use a regular method that returns a Promise and chain .then()."
1079
+ );
1080
+ }
988
1081
  if ((this.isKeyword("get") || this.isKeyword("set")) && !this.isPunc(":", 1) && !this.isPunc(",", 1) && !this.isPunc("(", 1) && !this.isPunc("}", 1)) {
989
1082
  const kind = this.next().value;
990
1083
  const { key: key2, computed: computed3 } = this.parsePropertyKey();
@@ -1268,6 +1361,10 @@ var GLOBAL_ALLOW = /* @__PURE__ */ new Set([
1268
1361
  "requestAnimationFrame",
1269
1362
  "cancelAnimationFrame",
1270
1363
  "fetch",
1364
+ "AbortController",
1365
+ "Headers",
1366
+ "Request",
1367
+ "Response",
1271
1368
  "alert",
1272
1369
  "confirm",
1273
1370
  "prompt",
@@ -1395,6 +1492,8 @@ var Interpreter = class {
1395
1492
  return null;
1396
1493
  case "UndefinedLiteral":
1397
1494
  return void 0;
1495
+ case "RegexLiteral":
1496
+ return new RegExp(node.pattern, node.flags);
1398
1497
  case "ThisExpression":
1399
1498
  return thisVal;
1400
1499
  case "Identifier":
@@ -1464,6 +1563,14 @@ var Interpreter = class {
1464
1563
  }
1465
1564
  case "CallExpression":
1466
1565
  return this.evalCall(node, scope, thisVal);
1566
+ case "NewExpression": {
1567
+ const ctor = this.evalExpr(node.callee, scope, thisVal);
1568
+ const args = this.evalArguments(node.arguments, scope, thisVal);
1569
+ if (typeof ctor !== "function") {
1570
+ throw new TypeError("Expression value is not a constructor");
1571
+ }
1572
+ return Reflect.construct(ctor, args);
1573
+ }
1467
1574
  case "ArrowFunction":
1468
1575
  return this.makeFunction(
1469
1576
  node,
@@ -1705,6 +1812,10 @@ function evaluateAction(source, root, thisVal) {
1705
1812
  const program = parse(source, "program");
1706
1813
  return new Interpreter(root).run(program, thisVal);
1707
1814
  }
1815
+ function compileExpression(source) {
1816
+ const program = parse(source, "expression");
1817
+ return (root, thisVal) => new Interpreter(root).run(program, thisVal);
1818
+ }
1708
1819
 
1709
1820
  // src/errors.ts
1710
1821
  var DOCS = "https://velofy.github.io/summit";
@@ -2043,15 +2154,26 @@ function initData(el, dmeta, parentScopes) {
2043
2154
  const parentEnv = makeEnv(el);
2044
2155
  const expr = dmeta.expression.trim();
2045
2156
  let raw;
2046
- const match = expr.match(DATA_PROVIDER_RE);
2047
- if (match && getData(match[1])) {
2048
- const provider = getData(match[1]);
2049
- const args = match[2] ? parentEnv.evaluate("[" + (match[3] ?? "") + "]") : [];
2050
- raw = provider(...args);
2051
- } else if (expr === "") {
2157
+ try {
2158
+ const match = expr.match(DATA_PROVIDER_RE);
2159
+ if (match && getData(match[1])) {
2160
+ const provider = getData(match[1]);
2161
+ const args = match[2] ? parentEnv.evaluate("[" + (match[3] ?? "") + "]") : [];
2162
+ raw = provider(...args);
2163
+ } else if (expr === "") {
2164
+ raw = {};
2165
+ } else {
2166
+ raw = parentEnv.evaluate("(" + expr + ")");
2167
+ }
2168
+ } catch (err) {
2169
+ fail("E104", "s-data could not be evaluated; the component starts with empty state.", {
2170
+ el,
2171
+ expression: expr,
2172
+ doc: "s-data",
2173
+ hint: "Check the object for unsupported syntax, e.g. an async method.",
2174
+ cause: err
2175
+ });
2052
2176
  raw = {};
2053
- } else {
2054
- raw = parentEnv.evaluate("(" + expr + ")");
2055
2177
  }
2056
2178
  if (raw === null || typeof raw !== "object") raw = {};
2057
2179
  const scope = createScope(raw);
@@ -2771,11 +2893,13 @@ var sFor = (el, meta2, utils) => {
2771
2893
  el.remove();
2772
2894
  const parentScopes = utils.scopes;
2773
2895
  let blocks = /* @__PURE__ */ new Map();
2774
- const keyFor = (it, block) => {
2775
- if (!keyExpr) return it.objectKey ?? it.index;
2776
- const env = makeEnv(el, block).env;
2896
+ const keyEval = keyExpr ? compileExpression(keyExpr) : null;
2897
+ const keyFor = (it) => {
2898
+ if (!keyEval) return it.objectKey ?? it.index;
2899
+ const data = { [itemName]: it.value };
2900
+ if (indexName) data[indexName] = it.objectKey ?? it.index;
2777
2901
  try {
2778
- return evaluateExpression(keyExpr, env);
2902
+ return keyEval(makeEnv(el, data).env);
2779
2903
  } catch {
2780
2904
  return it.index;
2781
2905
  }
@@ -2797,8 +2921,7 @@ var sFor = (el, meta2, utils) => {
2797
2921
  const next = /* @__PURE__ */ new Map();
2798
2922
  const orderedKeys = [];
2799
2923
  for (const it of items) {
2800
- let scope = makeBlockScope(it);
2801
- const key = keyFor(it, scope);
2924
+ const key = keyFor(it);
2802
2925
  orderedKeys.push(key);
2803
2926
  const existing = blocks.get(key);
2804
2927
  if (existing) {
@@ -2807,7 +2930,9 @@ var sFor = (el, meta2, utils) => {
2807
2930
  next.set(key, existing);
2808
2931
  blocks.delete(key);
2809
2932
  } else {
2933
+ const scope = makeBlockScope(it);
2810
2934
  const node = blueprint.cloneNode(true);
2935
+ parent.insertBefore(node, anchor2);
2811
2936
  utils.initTree(node, [...parentScopes, scope]);
2812
2937
  next.set(key, { node, scope });
2813
2938
  }
@@ -2816,9 +2941,13 @@ var sFor = (el, meta2, utils) => {
2816
2941
  utils.destroyTree(block.node);
2817
2942
  block.node.remove();
2818
2943
  }
2819
- for (const key of orderedKeys) {
2820
- const block = next.get(key);
2821
- parent.insertBefore(block.node, anchor2);
2944
+ let expected = anchor2;
2945
+ for (let i = orderedKeys.length - 1; i >= 0; i--) {
2946
+ const node = next.get(orderedKeys[i]).node;
2947
+ if (node.nextSibling !== expected) {
2948
+ parent.insertBefore(node, expected);
2949
+ }
2950
+ expected = node;
2822
2951
  }
2823
2952
  blocks = next;
2824
2953
  });