funcity 0.6.0 → 0.7.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/index.cjs CHANGED
@@ -1,11 +1,11 @@
1
1
  /*!
2
2
  * name: funcity
3
- * version: 0.6.0
3
+ * version: 0.7.0
4
4
  * description: A functional language interpreter with text processing
5
5
  * author: Kouji Matsui (@kekyo@mi.kekyo.net)
6
6
  * license: MIT
7
7
  * repository.url: https://github.com/kekyo/funcity
8
- * git.commit.hash: 0ead9e15b1d6951acb7bf50d4850def586a23c47
8
+ * git.commit.hash: fc92f38c817d11189397d8e458b1d25db727f766
9
9
  */
10
10
 
11
11
  "use strict";
@@ -139,11 +139,10 @@ const tokenizeNumber = (context) => {
139
139
  }
140
140
  };
141
141
  const firstVariableChars = "_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
142
- const variableChars = "_-.?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
142
+ const variableChars = "_-?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
143
143
  const tokenizeIdentity = (context) => {
144
144
  const start = context.cursor.location("start");
145
145
  let index = 1;
146
- let lastCh = "";
147
146
  while (true) {
148
147
  if (context.cursor.eot()) {
149
148
  break;
@@ -152,11 +151,14 @@ const tokenizeIdentity = (context) => {
152
151
  if (variableChars.indexOf(ch) < 0) {
153
152
  break;
154
153
  }
155
- if (lastCh === "?") {
156
- index--;
154
+ if (ch === "?") {
155
+ const next = context.cursor.getChar(index + 1);
156
+ if (next === ".") {
157
+ break;
158
+ }
159
+ index++;
157
160
  break;
158
161
  }
159
- lastCh = ch;
160
162
  index++;
161
163
  }
162
164
  return {
@@ -256,6 +258,24 @@ const tokenizeCodeTokens = (context, stopOnClose, finalizeUnknownOnEot) => {
256
258
  range: { start: location, end: location }
257
259
  });
258
260
  context.cursor.skip(1);
261
+ } else if (ch === "?" && context.cursor.getChar(1) === ".") {
262
+ finalizeUnknown();
263
+ const start = context.cursor.location("start");
264
+ context.cursor.skip(2);
265
+ tokens.push({
266
+ kind: "dot",
267
+ optional: true,
268
+ range: { start, end: context.cursor.location("end") }
269
+ });
270
+ } else if (ch === ".") {
271
+ finalizeUnknown();
272
+ const location = context.cursor.location("start");
273
+ tokens.push({
274
+ kind: "dot",
275
+ optional: false,
276
+ range: { start: location, end: location }
277
+ });
278
+ context.cursor.skip(1);
259
279
  } else if (firstVariableChars.indexOf(ch) >= 0) {
260
280
  finalizeUnknown();
261
281
  tokens.push(tokenizeIdentity(context));
@@ -686,6 +706,52 @@ const combineIntoScopeMultipleExpressions = (expressionList, ...outerRanges) =>
686
706
  }
687
707
  }
688
708
  };
709
+ const parseDotChain = (cursor, logs, baseNode) => {
710
+ const segments = [];
711
+ const ranges = [baseNode.range];
712
+ while (true) {
713
+ const dotToken = cursor.peekToken();
714
+ if (!dotToken || dotToken.kind !== "dot") {
715
+ break;
716
+ }
717
+ const actualDotToken = cursor.takeToken();
718
+ ranges.push(actualDotToken.range);
719
+ const nextToken = cursor.peekToken();
720
+ if (!nextToken) {
721
+ logs.push({
722
+ type: "error",
723
+ description: "Could not find member identity after dot",
724
+ range: actualDotToken.range
725
+ });
726
+ break;
727
+ }
728
+ if (nextToken.kind !== "identity") {
729
+ logs.push({
730
+ type: "error",
731
+ description: "Required member identity after dot",
732
+ range: widerRange(actualDotToken.range, nextToken.range)
733
+ });
734
+ break;
735
+ }
736
+ const memberToken = cursor.takeToken();
737
+ segments.push({
738
+ name: memberToken.name,
739
+ optional: actualDotToken.optional,
740
+ range: memberToken.range,
741
+ operatorRange: actualDotToken.range
742
+ });
743
+ ranges.push(memberToken.range);
744
+ }
745
+ if (segments.length === 0) {
746
+ return baseNode;
747
+ }
748
+ return {
749
+ kind: "dot",
750
+ base: baseNode,
751
+ segments,
752
+ range: widerRange(...ranges)
753
+ };
754
+ };
689
755
  const parsePartialExpression = (cursor, logs) => {
690
756
  const token = cursor.peekToken();
691
757
  if (!token) {
@@ -694,15 +760,24 @@ const parsePartialExpression = (cursor, logs) => {
694
760
  switch (token.kind) {
695
761
  case "number": {
696
762
  const node = parseNumber(cursor);
697
- return node;
763
+ return parseDotChain(cursor, logs, node);
698
764
  }
699
765
  case "string": {
700
766
  const node = parseString(cursor);
701
- return node;
767
+ return parseDotChain(cursor, logs, node);
702
768
  }
703
769
  case "identity": {
704
770
  const node = parseIdentity(cursor);
705
- return node;
771
+ return parseDotChain(cursor, logs, node);
772
+ }
773
+ case "dot": {
774
+ const dotToken = cursor.takeToken();
775
+ logs.push({
776
+ type: "error",
777
+ description: "Invalid dot at this location",
778
+ range: dotToken.range
779
+ });
780
+ return void 0;
706
781
  }
707
782
  case "open": {
708
783
  cursor.skipToken();
@@ -747,7 +822,7 @@ const parsePartialExpression = (cursor, logs) => {
747
822
  };
748
823
  } else {
749
824
  const node = combineIntoScopeMultipleExpressions(innerNodes, range);
750
- return node;
825
+ return node ? parseDotChain(cursor, logs, node) : node;
751
826
  }
752
827
  }
753
828
  // Bracket surrounding expression list `[ ... ]` (Iterable list)
@@ -758,7 +833,7 @@ const parsePartialExpression = (cursor, logs) => {
758
833
  if (!closeToken) {
759
834
  range = widerRange(
760
835
  token.range,
761
- ...itemNodes.map((node) => node.range)
836
+ ...itemNodes.map((node2) => node2.range)
762
837
  );
763
838
  logs.push({
764
839
  type: "error",
@@ -769,7 +844,7 @@ const parsePartialExpression = (cursor, logs) => {
769
844
  cursor.skipToken();
770
845
  range = widerRange(
771
846
  token.range,
772
- ...itemNodes.map((node) => node.range),
847
+ ...itemNodes.map((node2) => node2.range),
773
848
  closeToken.range
774
849
  );
775
850
  if (closeToken.kind !== "close" || closeToken.symbol !== "]") {
@@ -780,11 +855,12 @@ const parsePartialExpression = (cursor, logs) => {
780
855
  });
781
856
  }
782
857
  }
783
- return {
858
+ const node = {
784
859
  kind: "list",
785
860
  items: itemNodes,
786
861
  range
787
862
  };
863
+ return parseDotChain(cursor, logs, node);
788
864
  }
789
865
  default: {
790
866
  logs.push({
@@ -1417,33 +1493,54 @@ const deconstructConditionalCombine = (name) => {
1417
1493
  canIgnore: false
1418
1494
  };
1419
1495
  };
1420
- const traverseVariable = (context, name, signal) => {
1421
- const names = name.name.split(".");
1422
- const n0 = names[0];
1423
- const n0r = deconstructConditionalCombine(n0);
1424
- const result0 = context.getValue(n0r.name, signal);
1425
- if (!result0.isFound) {
1426
- if (!n0r.canIgnore) {
1496
+ const resolveVariable = (context, name, signal) => {
1497
+ const result = deconstructConditionalCombine(name.name);
1498
+ const valueResult = context.getValue(result.name, signal);
1499
+ if (!valueResult.isFound) {
1500
+ if (!result.canIgnore) {
1427
1501
  throwError({
1428
- description: `variable is not bound: ${names[0]}`,
1502
+ description: `variable is not bound: ${result.name}`,
1429
1503
  range: name.range
1430
1504
  });
1431
1505
  }
1432
1506
  return void 0;
1433
1507
  }
1434
- let value = result0.value;
1508
+ return valueResult.value;
1509
+ };
1510
+ const resolveDotNode = async (context, node, signal) => {
1511
+ var _a, _b;
1512
+ signal == null ? void 0 : signal.throwIfAborted();
1513
+ const firstSegmentOptional = (_b = (_a = node.segments[0]) == null ? void 0 : _a.optional) != null ? _b : false;
1514
+ let value;
1515
+ if (node.base.kind === "variable") {
1516
+ const baseResult = deconstructConditionalCombine(node.base.name);
1517
+ const valueResult = context.getValue(baseResult.name, signal);
1518
+ if (!valueResult.isFound) {
1519
+ if (!baseResult.canIgnore && !firstSegmentOptional) {
1520
+ throwError({
1521
+ description: `variable is not bound: ${baseResult.name}`,
1522
+ range: node.base.range
1523
+ });
1524
+ }
1525
+ return void 0;
1526
+ }
1527
+ value = valueResult.value;
1528
+ } else {
1529
+ value = await reduceExpressionNode(context, node.base, signal);
1530
+ }
1435
1531
  let parent;
1436
- for (const n of names.slice(1)) {
1437
- const nr = deconstructConditionalCombine(n);
1438
- if (value !== null && typeof value === "object") {
1439
- const r = value;
1532
+ for (const segment of node.segments) {
1533
+ const result = deconstructConditionalCombine(segment.name);
1534
+ const isOptional = segment.optional || result.canIgnore;
1535
+ if (value !== null && (typeof value === "object" || typeof value === "function")) {
1536
+ const record = value;
1440
1537
  parent = value;
1441
- value = r[nr.name];
1538
+ value = record[result.name];
1442
1539
  } else {
1443
- if (!nr.canIgnore) {
1540
+ if (!isOptional) {
1444
1541
  throwError({
1445
- description: `variable is not bound: ${n}`,
1446
- range: name.range
1542
+ description: `variable is not bound: ${result.name}`,
1543
+ range: segment.range
1447
1544
  });
1448
1545
  }
1449
1546
  return void 0;
@@ -1464,16 +1561,20 @@ const applyFunction = async (context, node, signal) => {
1464
1561
  });
1465
1562
  return void 0;
1466
1563
  }
1467
- const args = isFunCityFunction(func) ? node.args : await Promise.all(
1564
+ const isSpecial = isFunCityFunction(func);
1565
+ const args = isSpecial ? node.args : await Promise.all(
1468
1566
  node.args.map(async (argNode) => {
1469
1567
  const arg = await reduceExpressionNode(context, argNode, signal);
1470
1568
  return arg;
1471
1569
  })
1472
1570
  );
1473
- const thisProxy = context.createFunctionContext(node, signal);
1571
+ const shouldConstruct = !isSpecial && context.isConstructable(func);
1474
1572
  try {
1475
- const value = await func.call(thisProxy, ...args);
1476
- return value;
1573
+ if (shouldConstruct) {
1574
+ return Reflect.construct(func, args);
1575
+ }
1576
+ const thisProxy = context.createFunctionContext(node, signal);
1577
+ return await func.call(thisProxy, ...args);
1477
1578
  } catch (e) {
1478
1579
  if (e instanceof FunCityReducerError) {
1479
1580
  throw e;
@@ -1495,7 +1596,10 @@ const reduceExpressionNode = async (context, node, signal) => {
1495
1596
  return node.value;
1496
1597
  }
1497
1598
  case "variable": {
1498
- return traverseVariable(context, node, signal);
1599
+ return resolveVariable(context, node, signal);
1600
+ }
1601
+ case "dot": {
1602
+ return await resolveDotNode(context, node, signal);
1499
1603
  }
1500
1604
  case "apply": {
1501
1605
  return await applyFunction(context, node, signal);
@@ -1606,7 +1710,6 @@ const createScopedReducerContext = (parent, signal) => {
1606
1710
  }
1607
1711
  thisVars.set(name, value);
1608
1712
  };
1609
- const getBoundFunction = parent.getBoundFunction;
1610
1713
  const createFunctionContext = (thisNode, signal2) => {
1611
1714
  return {
1612
1715
  thisNode,
@@ -1614,6 +1717,7 @@ const createScopedReducerContext = (parent, signal) => {
1614
1717
  getValue: (name) => getValue(name, signal2),
1615
1718
  setValue: (name, value) => setValue(name, value, signal2),
1616
1719
  appendWarning: parent.appendWarning,
1720
+ getBoundFunction: parent.getBoundFunction,
1617
1721
  newScope: () => createScopedReducerContext(thisContext, signal2),
1618
1722
  convertToString: parent.convertToString,
1619
1723
  reduce: (node) => reduceExpressionNode(thisContext, node, signal2)
@@ -1622,10 +1726,11 @@ const createScopedReducerContext = (parent, signal) => {
1622
1726
  thisContext = {
1623
1727
  getValue,
1624
1728
  setValue,
1625
- getBoundFunction,
1729
+ getBoundFunction: parent.getBoundFunction,
1626
1730
  appendWarning: parent.appendWarning,
1627
1731
  newScope: (signal2) => createScopedReducerContext(thisContext, signal2),
1628
1732
  convertToString: parent.convertToString,
1733
+ isConstructable: parent.isConstructable,
1629
1734
  createFunctionContext
1630
1735
  };
1631
1736
  return thisContext;
@@ -1633,24 +1738,22 @@ const createScopedReducerContext = (parent, signal) => {
1633
1738
  const createReducerContext = (variables, warningLogs) => {
1634
1739
  let thisVars;
1635
1740
  let thisContext;
1636
- const createBoundFunctionResolver = () => {
1637
- const cache = /* @__PURE__ */ new WeakMap();
1638
- return (owner, fn) => {
1639
- let ownerCache = cache.get(owner);
1640
- if (!ownerCache) {
1641
- ownerCache = /* @__PURE__ */ new WeakMap();
1642
- cache.set(owner, ownerCache);
1643
- }
1644
- const cached = ownerCache.get(fn);
1645
- if (cached) {
1646
- return cached;
1647
- }
1648
- const bound = fn.bind(owner);
1649
- ownerCache.set(fn, bound);
1650
- return bound;
1651
- };
1741
+ const boundFunctionCache = /* @__PURE__ */ new WeakMap();
1742
+ const getBoundFunction = (owner, fn) => {
1743
+ let ownerCache = boundFunctionCache.get(owner);
1744
+ if (!ownerCache) {
1745
+ ownerCache = /* @__PURE__ */ new WeakMap();
1746
+ boundFunctionCache.set(owner, ownerCache);
1747
+ }
1748
+ const cached = ownerCache.get(fn);
1749
+ if (cached) {
1750
+ return cached;
1751
+ }
1752
+ const bound = fn.bind(owner);
1753
+ ownerCache.set(fn, bound);
1754
+ return bound;
1652
1755
  };
1653
- const getBoundFunction = createBoundFunctionResolver();
1756
+ const constructorCache = /* @__PURE__ */ new WeakMap();
1654
1757
  const getValue = (name, signal) => {
1655
1758
  signal == null ? void 0 : signal.throwIfAborted();
1656
1759
  if (thisVars == null ? void 0 : thisVars.has(name)) {
@@ -1671,6 +1774,20 @@ const createReducerContext = (variables, warningLogs) => {
1671
1774
  const appendWarning = (warning) => {
1672
1775
  warningLogs.push(warning);
1673
1776
  };
1777
+ const isConstructable = (fn) => {
1778
+ if (constructorCache.has(fn)) {
1779
+ return constructorCache.get(fn);
1780
+ }
1781
+ let result = false;
1782
+ try {
1783
+ Reflect.construct(Object, [], fn);
1784
+ result = true;
1785
+ } catch (e) {
1786
+ result = false;
1787
+ }
1788
+ constructorCache.set(fn, result);
1789
+ return result;
1790
+ };
1674
1791
  const getFuncId = internalCreateFunctionIdGenerator();
1675
1792
  const convertToString2 = (v) => {
1676
1793
  return internalConvertToString(v, getFuncId);
@@ -1682,6 +1799,7 @@ const createReducerContext = (variables, warningLogs) => {
1682
1799
  getValue: (name) => getValue(name, signal),
1683
1800
  setValue: (name, value) => setValue(name, value, signal),
1684
1801
  appendWarning,
1802
+ getBoundFunction,
1685
1803
  newScope: () => createScopedReducerContext(thisContext, signal),
1686
1804
  convertToString: convertToString2,
1687
1805
  reduce: (node) => reduceExpressionNode(thisContext, node, signal)
@@ -1694,6 +1812,7 @@ const createReducerContext = (variables, warningLogs) => {
1694
1812
  appendWarning,
1695
1813
  newScope: (signal) => createScopedReducerContext(thisContext, signal),
1696
1814
  convertToString: convertToString2,
1815
+ isConstructable,
1697
1816
  createFunctionContext
1698
1817
  };
1699
1818
  return thisContext;
@@ -1905,7 +2024,7 @@ const _ge = async (arg0, arg1) => {
1905
2024
  return r;
1906
2025
  };
1907
2026
  const _now = async () => {
1908
- return Date.now();
2027
+ return /* @__PURE__ */ new Date();
1909
2028
  };
1910
2029
  const concatInner = (args) => {
1911
2030
  let v = "";
@@ -2268,8 +2387,7 @@ const standardVariables = Object.freeze({
2268
2387
  regex: _regex,
2269
2388
  bind: _bind,
2270
2389
  url: _url,
2271
- delay: _delay,
2272
- math: Math
2390
+ delay: _delay
2273
2391
  });
2274
2392
  const buildCandidateVariables = (...variablesList) => {
2275
2393
  return combineVariables(standardVariables, ...variablesList);
@@ -2335,6 +2453,15 @@ const runScriptOnceToText = async (script, props, signal) => {
2335
2453
  const text = resultList.map((result) => reducerContext.convertToString(result)).join("");
2336
2454
  return text;
2337
2455
  };
2456
+ const objectVariables = Object.freeze({
2457
+ Object,
2458
+ Array,
2459
+ String,
2460
+ Number,
2461
+ Function,
2462
+ Math,
2463
+ Date
2464
+ });
2338
2465
  const getFetch = () => {
2339
2466
  const fetchFn = globalThis.fetch;
2340
2467
  if (!fetchFn) {
@@ -2393,6 +2520,7 @@ exports.fetchVariables = fetchVariables;
2393
2520
  exports.isConditionalTrue = isConditionalTrue;
2394
2521
  exports.isFunCityFunction = isFunCityFunction;
2395
2522
  exports.makeFunCityFunction = makeFunCityFunction;
2523
+ exports.objectVariables = objectVariables;
2396
2524
  exports.outputErrors = outputErrors;
2397
2525
  exports.parseBlock = parseBlock;
2398
2526
  exports.parseExpression = parseExpression;