@mojir/dvala 0.0.16 → 0.0.18

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.
Files changed (2) hide show
  1. package/dist/cli/cli.js +365 -18
  2. package/package.json +1 -1
package/dist/cli/cli.js CHANGED
@@ -118,7 +118,8 @@ const NodeTypes = {
118
118
  SpecialBuiltinSymbol: 7,
119
119
  ReservedSymbol: 8,
120
120
  Binding: 9,
121
- Spread: 10
121
+ Spread: 10,
122
+ TemplateString: 11
122
123
  };
123
124
  const NodeTypesSet = new Set(Object.values(NodeTypes));
124
125
  function getNodeTypeName(type) {
@@ -477,7 +478,7 @@ function findAllOccurrences(input, pattern) {
477
478
  }
478
479
  //#endregion
479
480
  //#region package.json
480
- var version = "0.0.16";
481
+ var version = "0.0.18";
481
482
  //#endregion
482
483
  //#region src/typeGuards/string.ts
483
484
  function isString(value, options = {}) {
@@ -24998,8 +24999,13 @@ const datatype = {
24998
24999
  datatype: true,
24999
25000
  title: "string",
25000
25001
  category: "datatype",
25001
- description: "A `string`",
25002
- examples: ["\"hello\"", "\"\""]
25002
+ description: "A `string`. Strings are written with double quotes. Template strings use backticks and support `${...}` interpolation — any expression can appear inside the braces.",
25003
+ examples: [
25004
+ "\"hello\"",
25005
+ "\"\"",
25006
+ "`Hello, World!`",
25007
+ "`${2} * ${3} = ${2 * 3}`"
25008
+ ]
25003
25009
  },
25004
25010
  "-type-object": {
25005
25011
  datatype: true,
@@ -25810,6 +25816,94 @@ const tokenizeShebang = (input, position) => {
25810
25816
  }
25811
25817
  return NO_MATCH;
25812
25818
  };
25819
+ const tokenizeTemplateString = (input, position) => {
25820
+ if (input[position] !== "`") return NO_MATCH;
25821
+ let value = "`";
25822
+ let length = 1;
25823
+ while (position + length < input.length) {
25824
+ const char = input[position + length];
25825
+ if (char === "`") {
25826
+ value += "`";
25827
+ length += 1;
25828
+ return [length, ["TemplateString", value]];
25829
+ }
25830
+ if (char === "$" && input[position + length + 1] === "{") {
25831
+ value += "${";
25832
+ length += 2;
25833
+ let braceDepth = 1;
25834
+ while (position + length < input.length && braceDepth > 0) {
25835
+ const c = input[position + length];
25836
+ if (c === "{") {
25837
+ braceDepth += 1;
25838
+ value += c;
25839
+ length += 1;
25840
+ } else if (c === "}") {
25841
+ braceDepth -= 1;
25842
+ value += c;
25843
+ length += 1;
25844
+ } else if (c === "\"") {
25845
+ value += c;
25846
+ length += 1;
25847
+ let escaping = false;
25848
+ while (position + length < input.length) {
25849
+ const sc = input[position + length];
25850
+ value += sc;
25851
+ length += 1;
25852
+ if (escaping) escaping = false;
25853
+ else if (sc === "\\") escaping = true;
25854
+ else if (sc === "\"") break;
25855
+ }
25856
+ } else if (c === "'") {
25857
+ value += c;
25858
+ length += 1;
25859
+ let escaping = false;
25860
+ while (position + length < input.length) {
25861
+ const sc = input[position + length];
25862
+ value += sc;
25863
+ length += 1;
25864
+ if (escaping) escaping = false;
25865
+ else if (sc === "\\") escaping = true;
25866
+ else if (sc === "'") break;
25867
+ }
25868
+ } else if (c === "`") {
25869
+ const [nestedLength, nestedToken] = tokenizeTemplateString(input, position + length);
25870
+ if (nestedLength === 0 || !nestedToken) return [length, [
25871
+ "Error",
25872
+ value,
25873
+ void 0,
25874
+ `Unclosed nested template string at position ${position + length}`
25875
+ ]];
25876
+ if (nestedToken[0] === "Error") return [length + nestedLength, [
25877
+ "Error",
25878
+ value + nestedToken[1],
25879
+ void 0,
25880
+ nestedToken[3]
25881
+ ]];
25882
+ value += nestedToken[1];
25883
+ length += nestedLength;
25884
+ } else {
25885
+ value += c;
25886
+ length += 1;
25887
+ }
25888
+ }
25889
+ if (braceDepth > 0) return [length, [
25890
+ "Error",
25891
+ value,
25892
+ void 0,
25893
+ `Unclosed interpolation in template string at position ${position}`
25894
+ ]];
25895
+ } else {
25896
+ value += char;
25897
+ length += 1;
25898
+ }
25899
+ }
25900
+ return [length, [
25901
+ "Error",
25902
+ value,
25903
+ void 0,
25904
+ `Unclosed template string at position ${position}`
25905
+ ]];
25906
+ };
25813
25907
  const tokenizeSingleLineComment = (input, position) => {
25814
25908
  if (input[position] === "/" && input[position + 1] === "/") {
25815
25909
  let length = 2;
@@ -25834,6 +25928,7 @@ const tokenizers = [
25834
25928
  tokenizeLBrace,
25835
25929
  tokenizeRBrace,
25836
25930
  tokenizeString,
25931
+ tokenizeTemplateString,
25837
25932
  tokenizeRegexpShorthand,
25838
25933
  tokenizeBasePrefixedNumber,
25839
25934
  tokenizeNumber,
@@ -26628,6 +26723,11 @@ function findUnresolvedSymbolsInNode(node, contextStack, builtin) {
26628
26723
  });
26629
26724
  }
26630
26725
  case NodeTypes.Spread: return findUnresolvedSymbolsInNode(node[1], contextStack, builtin);
26726
+ case NodeTypes.TemplateString: {
26727
+ const unresolvedSymbols = /* @__PURE__ */ new Set();
26728
+ for (const segment of node[1]) findUnresolvedSymbolsInNode(segment, contextStack, builtin)?.forEach((symbol) => unresolvedSymbols.add(symbol));
26729
+ return unresolvedSymbols;
26730
+ }
26631
26731
  default: throw new DvalaError(`Unhandled node type: ${nodeType}`, node[2]);
26632
26732
  }
26633
26733
  }
@@ -26733,6 +26833,9 @@ function isStringToken(token) {
26733
26833
  function isA_BinaryOperatorToken(token) {
26734
26834
  return token?.[0] === "Operator" && isBinaryOperator(token[1]);
26735
26835
  }
26836
+ function isTemplateStringToken(token) {
26837
+ return token?.[0] === "TemplateString";
26838
+ }
26736
26839
  function throwUnexpectedToken(expected, expectedValue, actual) {
26737
26840
  throw new DvalaError(`Unexpected token: ${actual ? `${actual[0]} '${actual[1]}'` : "end of input"}, expected ${expected}${expectedValue ? ` '${expectedValue}'` : ""}`, actual?.[2]);
26738
26841
  }
@@ -27027,6 +27130,176 @@ function parseNumber(ctx) {
27027
27130
  return withSourceCodeInfo([NodeTypes.Number, negative ? -Number(numberString) : Number(numberString)], token[2]);
27028
27131
  }
27029
27132
  //#endregion
27133
+ //#region src/tokenizer/minifyTokenStream.ts
27134
+ function minifyTokenStream(tokenStream, { removeWhiteSpace }) {
27135
+ const tokens = tokenStream.tokens.filter((token) => {
27136
+ if (isSingleLineCommentToken(token) || isMultiLineCommentToken(token) || isShebangToken(token) || removeWhiteSpace && isWhitespaceToken(token)) return false;
27137
+ return true;
27138
+ });
27139
+ return {
27140
+ ...tokenStream,
27141
+ tokens
27142
+ };
27143
+ }
27144
+ //#endregion
27145
+ //#region src/parser/subParsers/parseTemplateString.ts
27146
+ /**
27147
+ * Scan from `start` inside a `${...}` interpolation until the matching `}`.
27148
+ * Returns the expression source text (without the outer `${` and `}`) and
27149
+ * the number of characters consumed (including the closing `}`).
27150
+ */
27151
+ function scanExpression(raw, start) {
27152
+ let i = start;
27153
+ let expr = "";
27154
+ let depth = 1;
27155
+ while (i < raw.length && depth > 0) {
27156
+ const c = raw[i];
27157
+ if (c === "{") {
27158
+ depth++;
27159
+ expr += c;
27160
+ i++;
27161
+ } else if (c === "}") {
27162
+ depth--;
27163
+ if (depth > 0) expr += c;
27164
+ i++;
27165
+ } else if (c === "\"") {
27166
+ const { str, consumed } = scanString(raw, i);
27167
+ expr += str;
27168
+ i += consumed;
27169
+ } else if (c === "'") {
27170
+ const { str, consumed } = scanQuotedSymbol(raw, i);
27171
+ expr += str;
27172
+ i += consumed;
27173
+ } else if (c === "`") {
27174
+ const { str, consumed } = scanNestedTemplate(raw, i);
27175
+ expr += str;
27176
+ i += consumed;
27177
+ } else {
27178
+ expr += c;
27179
+ i++;
27180
+ }
27181
+ }
27182
+ return {
27183
+ expr,
27184
+ consumed: i - start
27185
+ };
27186
+ }
27187
+ function scanString(raw, start) {
27188
+ let i = start + 1;
27189
+ let str = "\"";
27190
+ let escaping = false;
27191
+ while (i < raw.length) {
27192
+ const c = raw[i];
27193
+ str += c;
27194
+ i++;
27195
+ if (escaping) escaping = false;
27196
+ else if (c === "\\") escaping = true;
27197
+ else if (c === "\"") break;
27198
+ }
27199
+ return {
27200
+ str,
27201
+ consumed: i - start
27202
+ };
27203
+ }
27204
+ function scanQuotedSymbol(raw, start) {
27205
+ let i = start + 1;
27206
+ let str = "'";
27207
+ let escaping = false;
27208
+ while (i < raw.length) {
27209
+ const c = raw[i];
27210
+ str += c;
27211
+ i++;
27212
+ if (escaping) escaping = false;
27213
+ else if (c === "\\") escaping = true;
27214
+ else if (c === "'") break;
27215
+ }
27216
+ return {
27217
+ str,
27218
+ consumed: i - start
27219
+ };
27220
+ }
27221
+ /**
27222
+ * Scan a full nested template string starting at `start` (pointing at the opening backtick).
27223
+ * Handles ${...} spans inside the template recursively.
27224
+ */
27225
+ function scanNestedTemplate(raw, start) {
27226
+ let i = start + 1;
27227
+ let str = "`";
27228
+ while (i < raw.length) {
27229
+ const c = raw[i];
27230
+ if (c === "`") {
27231
+ str += c;
27232
+ i++;
27233
+ break;
27234
+ } else if (c === "$" && raw[i + 1] === "{") {
27235
+ str += "${";
27236
+ i += 2;
27237
+ const { expr, consumed } = scanExpression(raw, i);
27238
+ str += `${expr}}`;
27239
+ i += consumed;
27240
+ } else {
27241
+ str += c;
27242
+ i++;
27243
+ }
27244
+ }
27245
+ return {
27246
+ str,
27247
+ consumed: i - start
27248
+ };
27249
+ }
27250
+ /**
27251
+ * Split the raw content of a template string (between the surrounding backticks)
27252
+ * into alternating literal and expression segments.
27253
+ */
27254
+ function splitSegments(raw) {
27255
+ const segments = [];
27256
+ let i = 0;
27257
+ let literal = "";
27258
+ while (i < raw.length) if (raw[i] === "$" && raw[i + 1] === "{") {
27259
+ if (literal.length > 0) {
27260
+ segments.push({
27261
+ type: "literal",
27262
+ value: literal
27263
+ });
27264
+ literal = "";
27265
+ }
27266
+ i += 2;
27267
+ const { expr, consumed } = scanExpression(raw, i);
27268
+ i += consumed;
27269
+ segments.push({
27270
+ type: "expression",
27271
+ value: expr
27272
+ });
27273
+ } else {
27274
+ literal += raw[i];
27275
+ i++;
27276
+ }
27277
+ if (literal.length > 0) segments.push({
27278
+ type: "literal",
27279
+ value: literal
27280
+ });
27281
+ return segments;
27282
+ }
27283
+ function parseTemplateString(ctx, token) {
27284
+ ctx.advance();
27285
+ const sourceCodeInfo = token[2];
27286
+ const segments = splitSegments(token[1].slice(1, -1));
27287
+ if (segments.length === 0) return withSourceCodeInfo([NodeTypes.String, ""], sourceCodeInfo);
27288
+ if (segments.length === 1 && segments[0].type === "literal") return withSourceCodeInfo([NodeTypes.String, segments[0].value], sourceCodeInfo);
27289
+ const segmentNodes = [];
27290
+ for (const segment of segments) if (segment.type === "literal") {
27291
+ if (segment.value.length === 0) continue;
27292
+ segmentNodes.push(withSourceCodeInfo([NodeTypes.String, segment.value], sourceCodeInfo));
27293
+ } else {
27294
+ if (segment.value.trim().length === 0) throw new DvalaError("Empty interpolation in template string", sourceCodeInfo);
27295
+ const minified = minifyTokenStream(tokenize(segment.value, false, sourceCodeInfo?.filePath), { removeWhiteSpace: true });
27296
+ for (const t of minified.tokens) if (t[0] === "Error") throw new DvalaError(`Template string interpolation error: ${t[3]}`, sourceCodeInfo);
27297
+ const expr = parseExpression(createParserContext(minified), 0);
27298
+ segmentNodes.push(expr);
27299
+ }
27300
+ return withSourceCodeInfo([NodeTypes.TemplateString, segmentNodes], sourceCodeInfo);
27301
+ }
27302
+ //#endregion
27030
27303
  //#region src/parser/subParsers/parseBindingTarget.ts
27031
27304
  function parseBindingTarget(ctx, { requireDefaultValue, noRest, allowLiteralPatterns } = {}) {
27032
27305
  const firstToken = ctx.tryPeek();
@@ -27039,6 +27312,10 @@ function parseBindingTarget(ctx, { requireDefaultValue, noRest, allowLiteralPatt
27039
27312
  const node = parseNumber(ctx);
27040
27313
  return withSourceCodeInfo([bindingTargetTypes.literal, [node]], firstToken[2]);
27041
27314
  }
27315
+ if (isTemplateStringToken(firstToken)) {
27316
+ const node = parseTemplateString(ctx, firstToken);
27317
+ return withSourceCodeInfo([bindingTargetTypes.literal, [node]], firstToken[2]);
27318
+ }
27042
27319
  if (isStringToken(firstToken)) {
27043
27320
  const node = parseString(ctx, firstToken);
27044
27321
  return withSourceCodeInfo([bindingTargetTypes.literal, [node]], firstToken[2]);
@@ -27156,7 +27433,7 @@ function parseOptionalDefaulValue(ctx) {
27156
27433
  }
27157
27434
  }
27158
27435
  function isLiteralToken(token) {
27159
- return isNumberToken(token) || isBasePrefixedNumberToken(token) || isStringToken(token) || isReservedSymbolToken(token, "true") || isReservedSymbolToken(token, "false") || isReservedSymbolToken(token, "null");
27436
+ return isNumberToken(token) || isBasePrefixedNumberToken(token) || isStringToken(token) || isTemplateStringToken(token) || isReservedSymbolToken(token, "true") || isReservedSymbolToken(token, "false") || isReservedSymbolToken(token, "null");
27160
27437
  }
27161
27438
  //#endregion
27162
27439
  //#region src/parser/subParsers/parseLet.ts
@@ -27536,7 +27813,8 @@ function parseObject(ctx) {
27536
27813
  params.push(withSourceCodeInfo([NodeTypes.Spread, ctx.parseExpression()], ctx.peekSourceCodeInfo()));
27537
27814
  } else {
27538
27815
  const token = ctx.tryPeek();
27539
- if (isStringToken(token)) {
27816
+ if (isTemplateStringToken(token)) params.push(parseTemplateString(ctx, token));
27817
+ else if (isStringToken(token)) {
27540
27818
  const stringNode = parseString(ctx, token);
27541
27819
  params.push(withSourceCodeInfo([NodeTypes.String, stringNode[1]], token[2]));
27542
27820
  } else if (isSymbolToken(token)) {
@@ -27616,6 +27894,7 @@ function parseOperandPart(ctx) {
27616
27894
  case "Number":
27617
27895
  case "BasePrefixedNumber": return parseNumber(ctx);
27618
27896
  case "string": return parseString(ctx, token);
27897
+ case "TemplateString": return parseTemplateString(ctx, token);
27619
27898
  case "Symbol": {
27620
27899
  ctx.storePosition();
27621
27900
  const lamdaFunction = parseLambdaFunction(ctx);
@@ -27747,18 +28026,6 @@ function parse(tokenStream) {
27747
28026
  return nodes;
27748
28027
  }
27749
28028
  //#endregion
27750
- //#region src/tokenizer/minifyTokenStream.ts
27751
- function minifyTokenStream(tokenStream, { removeWhiteSpace }) {
27752
- const tokens = tokenStream.tokens.filter((token) => {
27753
- if (isSingleLineCommentToken(token) || isMultiLineCommentToken(token) || isShebangToken(token) || removeWhiteSpace && isWhitespaceToken(token)) return false;
27754
- return true;
27755
- });
27756
- return {
27757
- ...tokenStream,
27758
- tokens
27759
- };
27760
- }
27761
- //#endregion
27762
28029
  //#region src/evaluator/effectTypes.ts
27763
28030
  const SUSPENDED_MESSAGE = "Program suspended";
27764
28031
  /**
@@ -28439,9 +28706,33 @@ function stepNode(node, env, k) {
28439
28706
  };
28440
28707
  case NodeTypes.NormalExpression: return stepNormalExpression(node, env, k);
28441
28708
  case NodeTypes.SpecialExpression: return stepSpecialExpression(node, env, k);
28709
+ case NodeTypes.TemplateString: return stepTemplateString(node, env, k);
28442
28710
  default: throw new DvalaError(`${getNodeTypeName(node[0])}-node cannot be evaluated`, node[2]);
28443
28711
  }
28444
28712
  }
28713
+ function stepTemplateString(node, env, k) {
28714
+ const segments = node[1];
28715
+ const sourceCodeInfo = node[2];
28716
+ if (segments.length === 0) return {
28717
+ type: "Value",
28718
+ value: "",
28719
+ k
28720
+ };
28721
+ const frame = {
28722
+ type: "TemplateStringBuild",
28723
+ segments,
28724
+ index: 0,
28725
+ result: "",
28726
+ env,
28727
+ sourceCodeInfo
28728
+ };
28729
+ return {
28730
+ type: "Eval",
28731
+ node: segments[0],
28732
+ env,
28733
+ k: [frame, ...k]
28734
+ };
28735
+ }
28445
28736
  /**
28446
28737
  * Normal expressions: evaluate arguments left-to-right, then dispatch.
28447
28738
  * Push EvalArgsFrame + NanCheckFrame, then start evaluating the first arg.
@@ -29371,6 +29662,7 @@ function applyFrame(frame, value, k) {
29371
29662
  case "And": return applyAnd(frame, value, k);
29372
29663
  case "Or": return applyOr(frame, value, k);
29373
29664
  case "Qq": return applyQq(frame, value, k);
29665
+ case "TemplateStringBuild": return applyTemplateStringBuild(frame, value, k);
29374
29666
  case "ArrayBuild": return applyArrayBuild(frame, value, k);
29375
29667
  case "ObjectBuild": return applyObjectBuild(frame, value, k);
29376
29668
  case "LetBind": return applyLetBind(frame, value, k);
@@ -29657,6 +29949,27 @@ function advanceQq(frame, k) {
29657
29949
  function skipUndefinedQq(frame, k) {
29658
29950
  return advanceQq(frame, k);
29659
29951
  }
29952
+ function applyTemplateStringBuild(frame, value, k) {
29953
+ const { segments, env } = frame;
29954
+ const result = frame.result + String(value);
29955
+ const nextIndex = frame.index + 1;
29956
+ if (nextIndex >= segments.length) return {
29957
+ type: "Value",
29958
+ value: result,
29959
+ k
29960
+ };
29961
+ const newFrame = {
29962
+ ...frame,
29963
+ index: nextIndex,
29964
+ result
29965
+ };
29966
+ return {
29967
+ type: "Eval",
29968
+ node: segments[nextIndex],
29969
+ env,
29970
+ k: [newFrame, ...k]
29971
+ };
29972
+ }
29660
29973
  function applyArrayBuild(frame, value, k) {
29661
29974
  const { nodes, result, env, sourceCodeInfo } = frame;
29662
29975
  if (frame.isSpread) {
@@ -31702,6 +32015,13 @@ var Cache = class {
31702
32015
  * a Dvala instance.
31703
32016
  */
31704
32017
  /**
32018
+ * Tokenize a Dvala source string into a token stream.
32019
+ * Pass `debug: true` to capture source positions (needed for the debugger).
32020
+ */
32021
+ function tokenizeSource(source, debug = false, filePath) {
32022
+ return tokenize(source, debug, filePath);
32023
+ }
32024
+ /**
31705
32025
  * Get all undefined symbols in a Dvala program.
31706
32026
  *
31707
32027
  * @param source - Dvala source code
@@ -33441,11 +33761,38 @@ const getInlineCodeRule = (fmt) => (text, index) => {
33441
33761
  formattedText: ""
33442
33762
  };
33443
33763
  };
33764
+ function getTemplateStringRule(fmt) {
33765
+ return (text, index) => {
33766
+ if (text[index] !== "`") return noMatch;
33767
+ try {
33768
+ const { tokens } = tokenizeSource(text.slice(index));
33769
+ const first = tokens[0];
33770
+ if (!first || first[0] !== "TemplateString") return noMatch;
33771
+ const count = first[1].length;
33772
+ const segments = splitSegments(first[1].slice(1, -1));
33773
+ let formattedText = `${Colors.FgRed}\`${Colors.Reset}`;
33774
+ for (const seg of segments) if (seg.type === "literal") formattedText += Colors.FgRed + seg.value + Colors.Reset;
33775
+ else {
33776
+ formattedText += `${Colors.Bright}${Colors.FgWhite}\${${Colors.Reset}`;
33777
+ formattedText += getDvalaFormatter(fmt)(seg.value);
33778
+ formattedText += `${Colors.Bright}${Colors.FgWhite}}${Colors.Reset}`;
33779
+ }
33780
+ formattedText += `${Colors.FgRed}\`${Colors.Reset}`;
33781
+ return {
33782
+ count,
33783
+ formattedText
33784
+ };
33785
+ } catch {
33786
+ return noMatch;
33787
+ }
33788
+ };
33789
+ }
33444
33790
  function getDvalaExpressionRules(cli) {
33445
33791
  return [
33446
33792
  commentRule,
33447
33793
  stringRule,
33448
33794
  shortcutStringRule,
33795
+ getTemplateStringRule(cli),
33449
33796
  functionNameRule,
33450
33797
  getNumberRule(cli),
33451
33798
  dvalaKeywordRule,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mojir/dvala",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "dvala",
5
5
  "author": "Albert Mojir",
6
6
  "license": "MIT",