@mojir/dvala 0.0.17 → 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 +222 -175
  2. package/package.json +1 -1
package/dist/cli/cli.js CHANGED
@@ -478,7 +478,7 @@ function findAllOccurrences(input, pattern) {
478
478
  }
479
479
  //#endregion
480
480
  //#region package.json
481
- var version = "0.0.17";
481
+ var version = "0.0.18";
482
482
  //#endregion
483
483
  //#region src/typeGuards/string.ts
484
484
  function isString(value, options = {}) {
@@ -24999,8 +24999,13 @@ const datatype = {
24999
24999
  datatype: true,
25000
25000
  title: "string",
25001
25001
  category: "datatype",
25002
- description: "A `string`",
25003
- 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
+ ]
25004
25009
  },
25005
25010
  "-type-object": {
25006
25011
  datatype: true,
@@ -26828,6 +26833,9 @@ function isStringToken(token) {
26828
26833
  function isA_BinaryOperatorToken(token) {
26829
26834
  return token?.[0] === "Operator" && isBinaryOperator(token[1]);
26830
26835
  }
26836
+ function isTemplateStringToken(token) {
26837
+ return token?.[0] === "TemplateString";
26838
+ }
26831
26839
  function throwUnexpectedToken(expected, expectedValue, actual) {
26832
26840
  throw new DvalaError(`Unexpected token: ${actual ? `${actual[0]} '${actual[1]}'` : "end of input"}, expected ${expected}${expectedValue ? ` '${expectedValue}'` : ""}`, actual?.[2]);
26833
26841
  }
@@ -27122,6 +27130,176 @@ function parseNumber(ctx) {
27122
27130
  return withSourceCodeInfo([NodeTypes.Number, negative ? -Number(numberString) : Number(numberString)], token[2]);
27123
27131
  }
27124
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
27125
27303
  //#region src/parser/subParsers/parseBindingTarget.ts
27126
27304
  function parseBindingTarget(ctx, { requireDefaultValue, noRest, allowLiteralPatterns } = {}) {
27127
27305
  const firstToken = ctx.tryPeek();
@@ -27134,6 +27312,10 @@ function parseBindingTarget(ctx, { requireDefaultValue, noRest, allowLiteralPatt
27134
27312
  const node = parseNumber(ctx);
27135
27313
  return withSourceCodeInfo([bindingTargetTypes.literal, [node]], firstToken[2]);
27136
27314
  }
27315
+ if (isTemplateStringToken(firstToken)) {
27316
+ const node = parseTemplateString(ctx, firstToken);
27317
+ return withSourceCodeInfo([bindingTargetTypes.literal, [node]], firstToken[2]);
27318
+ }
27137
27319
  if (isStringToken(firstToken)) {
27138
27320
  const node = parseString(ctx, firstToken);
27139
27321
  return withSourceCodeInfo([bindingTargetTypes.literal, [node]], firstToken[2]);
@@ -27251,7 +27433,7 @@ function parseOptionalDefaulValue(ctx) {
27251
27433
  }
27252
27434
  }
27253
27435
  function isLiteralToken(token) {
27254
- 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");
27255
27437
  }
27256
27438
  //#endregion
27257
27439
  //#region src/parser/subParsers/parseLet.ts
@@ -27631,7 +27813,8 @@ function parseObject(ctx) {
27631
27813
  params.push(withSourceCodeInfo([NodeTypes.Spread, ctx.parseExpression()], ctx.peekSourceCodeInfo()));
27632
27814
  } else {
27633
27815
  const token = ctx.tryPeek();
27634
- if (isStringToken(token)) {
27816
+ if (isTemplateStringToken(token)) params.push(parseTemplateString(ctx, token));
27817
+ else if (isStringToken(token)) {
27635
27818
  const stringNode = parseString(ctx, token);
27636
27819
  params.push(withSourceCodeInfo([NodeTypes.String, stringNode[1]], token[2]));
27637
27820
  } else if (isSymbolToken(token)) {
@@ -27657,176 +27840,6 @@ function parseObject(ctx) {
27657
27840
  return withSourceCodeInfo([NodeTypes.SpecialExpression, [specialExpressionTypes.object, params]], firstToken[2]);
27658
27841
  }
27659
27842
  //#endregion
27660
- //#region src/tokenizer/minifyTokenStream.ts
27661
- function minifyTokenStream(tokenStream, { removeWhiteSpace }) {
27662
- const tokens = tokenStream.tokens.filter((token) => {
27663
- if (isSingleLineCommentToken(token) || isMultiLineCommentToken(token) || isShebangToken(token) || removeWhiteSpace && isWhitespaceToken(token)) return false;
27664
- return true;
27665
- });
27666
- return {
27667
- ...tokenStream,
27668
- tokens
27669
- };
27670
- }
27671
- //#endregion
27672
- //#region src/parser/subParsers/parseTemplateString.ts
27673
- /**
27674
- * Scan from `start` inside a `${...}` interpolation until the matching `}`.
27675
- * Returns the expression source text (without the outer `${` and `}`) and
27676
- * the number of characters consumed (including the closing `}`).
27677
- */
27678
- function scanExpression(raw, start) {
27679
- let i = start;
27680
- let expr = "";
27681
- let depth = 1;
27682
- while (i < raw.length && depth > 0) {
27683
- const c = raw[i];
27684
- if (c === "{") {
27685
- depth++;
27686
- expr += c;
27687
- i++;
27688
- } else if (c === "}") {
27689
- depth--;
27690
- if (depth > 0) expr += c;
27691
- i++;
27692
- } else if (c === "\"") {
27693
- const { str, consumed } = scanString(raw, i);
27694
- expr += str;
27695
- i += consumed;
27696
- } else if (c === "'") {
27697
- const { str, consumed } = scanQuotedSymbol(raw, i);
27698
- expr += str;
27699
- i += consumed;
27700
- } else if (c === "`") {
27701
- const { str, consumed } = scanNestedTemplate(raw, i);
27702
- expr += str;
27703
- i += consumed;
27704
- } else {
27705
- expr += c;
27706
- i++;
27707
- }
27708
- }
27709
- return {
27710
- expr,
27711
- consumed: i - start
27712
- };
27713
- }
27714
- function scanString(raw, start) {
27715
- let i = start + 1;
27716
- let str = "\"";
27717
- let escaping = false;
27718
- while (i < raw.length) {
27719
- const c = raw[i];
27720
- str += c;
27721
- i++;
27722
- if (escaping) escaping = false;
27723
- else if (c === "\\") escaping = true;
27724
- else if (c === "\"") break;
27725
- }
27726
- return {
27727
- str,
27728
- consumed: i - start
27729
- };
27730
- }
27731
- function scanQuotedSymbol(raw, start) {
27732
- let i = start + 1;
27733
- let str = "'";
27734
- let escaping = false;
27735
- while (i < raw.length) {
27736
- const c = raw[i];
27737
- str += c;
27738
- i++;
27739
- if (escaping) escaping = false;
27740
- else if (c === "\\") escaping = true;
27741
- else if (c === "'") break;
27742
- }
27743
- return {
27744
- str,
27745
- consumed: i - start
27746
- };
27747
- }
27748
- /**
27749
- * Scan a full nested template string starting at `start` (pointing at the opening backtick).
27750
- * Handles ${...} spans inside the template recursively.
27751
- */
27752
- function scanNestedTemplate(raw, start) {
27753
- let i = start + 1;
27754
- let str = "`";
27755
- while (i < raw.length) {
27756
- const c = raw[i];
27757
- if (c === "`") {
27758
- str += c;
27759
- i++;
27760
- break;
27761
- } else if (c === "$" && raw[i + 1] === "{") {
27762
- str += "${";
27763
- i += 2;
27764
- const { expr, consumed } = scanExpression(raw, i);
27765
- str += `${expr}}`;
27766
- i += consumed;
27767
- } else {
27768
- str += c;
27769
- i++;
27770
- }
27771
- }
27772
- return {
27773
- str,
27774
- consumed: i - start
27775
- };
27776
- }
27777
- /**
27778
- * Split the raw content of a template string (between the surrounding backticks)
27779
- * into alternating literal and expression segments.
27780
- */
27781
- function splitSegments(raw) {
27782
- const segments = [];
27783
- let i = 0;
27784
- let literal = "";
27785
- while (i < raw.length) if (raw[i] === "$" && raw[i + 1] === "{") {
27786
- if (literal.length > 0) {
27787
- segments.push({
27788
- type: "literal",
27789
- value: literal
27790
- });
27791
- literal = "";
27792
- }
27793
- i += 2;
27794
- const { expr, consumed } = scanExpression(raw, i);
27795
- i += consumed;
27796
- segments.push({
27797
- type: "expression",
27798
- value: expr
27799
- });
27800
- } else {
27801
- literal += raw[i];
27802
- i++;
27803
- }
27804
- if (literal.length > 0) segments.push({
27805
- type: "literal",
27806
- value: literal
27807
- });
27808
- return segments;
27809
- }
27810
- function parseTemplateString(ctx, token) {
27811
- ctx.advance();
27812
- const sourceCodeInfo = token[2];
27813
- const segments = splitSegments(token[1].slice(1, -1));
27814
- if (segments.length === 0) return withSourceCodeInfo([NodeTypes.String, ""], sourceCodeInfo);
27815
- if (segments.length === 1 && segments[0].type === "literal") return withSourceCodeInfo([NodeTypes.String, segments[0].value], sourceCodeInfo);
27816
- const segmentNodes = [];
27817
- for (const segment of segments) if (segment.type === "literal") {
27818
- if (segment.value.length === 0) continue;
27819
- segmentNodes.push(withSourceCodeInfo([NodeTypes.String, segment.value], sourceCodeInfo));
27820
- } else {
27821
- if (segment.value.trim().length === 0) throw new DvalaError("Empty interpolation in template string", sourceCodeInfo);
27822
- const minified = minifyTokenStream(tokenize(segment.value, false, sourceCodeInfo?.filePath), { removeWhiteSpace: true });
27823
- for (const t of minified.tokens) if (t[0] === "Error") throw new DvalaError(`Template string interpolation error: ${t[3]}`, sourceCodeInfo);
27824
- const expr = parseExpression(createParserContext(minified), 0);
27825
- segmentNodes.push(expr);
27826
- }
27827
- return withSourceCodeInfo([NodeTypes.TemplateString, segmentNodes], sourceCodeInfo);
27828
- }
27829
- //#endregion
27830
27843
  //#region src/parser/subParsers/parseOperand.ts
27831
27844
  function parseOperand(ctx) {
27832
27845
  let operand = parseOperandPart(ctx);
@@ -32002,6 +32015,13 @@ var Cache = class {
32002
32015
  * a Dvala instance.
32003
32016
  */
32004
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
+ /**
32005
32025
  * Get all undefined symbols in a Dvala program.
32006
32026
  *
32007
32027
  * @param source - Dvala source code
@@ -33741,11 +33761,38 @@ const getInlineCodeRule = (fmt) => (text, index) => {
33741
33761
  formattedText: ""
33742
33762
  };
33743
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
+ }
33744
33790
  function getDvalaExpressionRules(cli) {
33745
33791
  return [
33746
33792
  commentRule,
33747
33793
  stringRule,
33748
33794
  shortcutStringRule,
33795
+ getTemplateStringRule(cli),
33749
33796
  functionNameRule,
33750
33797
  getNumberRule(cli),
33751
33798
  dvalaKeywordRule,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mojir/dvala",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "description": "dvala",
5
5
  "author": "Albert Mojir",
6
6
  "license": "MIT",