@lokascript/framework 4.7.0 → 4.8.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/dist/api/create-dsl.d.ts +5 -2
  3. package/dist/api/create-dsl.d.ts.map +1 -1
  4. package/dist/api/index.js +45 -5
  5. package/dist/api/index.js.map +1 -1
  6. package/dist/core/index.js +10 -0
  7. package/dist/core/index.js.map +1 -1
  8. package/dist/core/matching/stock-reader.d.ts +8 -1
  9. package/dist/core/matching/stock-reader.d.ts.map +1 -1
  10. package/dist/core/pattern-matching/index.js.map +1 -1
  11. package/dist/core/types.d.ts +5 -0
  12. package/dist/core/types.d.ts.map +1 -1
  13. package/dist/core/types.js.map +1 -1
  14. package/dist/generation/index.js +12 -2
  15. package/dist/generation/index.js.map +1 -1
  16. package/dist/index.cjs +45 -5
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.js +45 -5
  19. package/dist/index.js.map +1 -1
  20. package/dist/ir/index.js.map +1 -1
  21. package/dist/parsing/index.js.map +1 -1
  22. package/dist/testing/gate/index.js +44 -12
  23. package/dist/testing/gate/index.js.map +1 -1
  24. package/dist/testing/gate/round-trip.d.ts +24 -3
  25. package/dist/testing/gate/round-trip.d.ts.map +1 -1
  26. package/package.json +2 -2
  27. package/src/api/create-dsl-engine.test.ts +65 -1
  28. package/src/api/create-dsl.test.ts +7 -0
  29. package/src/api/create-dsl.ts +32 -4
  30. package/src/core/matching/stock-reader.test.ts +87 -3
  31. package/src/core/matching/stock-reader.ts +20 -1
  32. package/src/core/types.ts +5 -0
  33. package/src/generation/schema-patterns.test.ts +41 -1
  34. package/src/generation/schema-patterns.ts +12 -0
  35. package/src/testing/gate/round-trip.test.ts +99 -9
  36. package/src/testing/gate/round-trip.ts +76 -11
package/dist/index.cjs CHANGED
@@ -1876,6 +1876,11 @@ function readGreedy(tokens, stopMarkers) {
1876
1876
  }
1877
1877
  return values.length > 0 ? { type: "expression", raw: values.join(" ") } : null;
1878
1878
  }
1879
+ function isListedWord(token, words) {
1880
+ if (!token) return false;
1881
+ const forms = [token.value, token.normalized].filter((f) => typeof f === "string");
1882
+ return forms.some((form) => words.some((word) => word.toLowerCase() === form.toLowerCase()));
1883
+ }
1879
1884
  var StockValueReader = class {
1880
1885
  constructor(view, profiles = {}) {
1881
1886
  this.view = view;
@@ -1908,6 +1913,11 @@ var StockValueReader = class {
1908
1913
  const token = tokens.peek();
1909
1914
  if (token && this.view.wouldMatch(next, token)) return false;
1910
1915
  }
1916
+ if (slot.words && !isListedWord(tokens.peek(), slot.words)) return slot.optional ?? false;
1917
+ if (slot.optional && !slot.greedy) {
1918
+ const token = tokens.peek();
1919
+ if (token && isStopMarker(token, greedyStopMarkers(this.view.patternRest()))) return true;
1920
+ }
1911
1921
  if (slot.greedy) {
1912
1922
  const value = readGreedy(tokens, greedyStopMarkers(this.view.patternRest()));
1913
1923
  if (value) {
@@ -2313,11 +2323,13 @@ function generateVerbFirstPattern(schema, profile, config = defaultConfig2) {
2313
2323
  const verbToken = keyword.alternatives ? { type: "literal", value: keyword.primary, alternatives: keyword.alternatives } : { type: "literal", value: keyword.primary };
2314
2324
  const roleTokens = requiredRoles.flatMap((r) => {
2315
2325
  const prefix = r.valuePrefixLiteral?.[profile.code];
2326
+ const words = wordsFor(r, profile.code);
2316
2327
  const roleToken = {
2317
2328
  type: "role",
2318
2329
  role: r.role,
2319
2330
  optional: false,
2320
- expectedTypes: r.expectedTypes
2331
+ expectedTypes: r.expectedTypes,
2332
+ ...words ? { words } : {}
2321
2333
  };
2322
2334
  return prefix ? [{ type: "literal", value: prefix }, roleToken] : [roleToken];
2323
2335
  });
@@ -2487,11 +2499,17 @@ function buildRoleTokens(grammar, schema, profile) {
2487
2499
  }
2488
2500
  return tokens;
2489
2501
  }
2502
+ function wordsFor(roleSpec, language) {
2503
+ if (!roleSpec.words) return void 0;
2504
+ const words = [...roleSpec.words["*"] ?? [], ...roleSpec.words[language] ?? []];
2505
+ return words.length > 0 ? words : void 0;
2506
+ }
2490
2507
  function buildRoleToken(roleSpec, profile) {
2491
2508
  const tokens = [];
2492
2509
  const overrideMarker = roleSpec.markerOverride?.[profile.code];
2493
2510
  const defaultMarker = profile.roleMarkers[roleSpec.role];
2494
2511
  const suppressMarker = roleSpec.renderOverride?.[profile.code] === "";
2512
+ const words = wordsFor(roleSpec, profile.code);
2495
2513
  const roleValueToken = {
2496
2514
  type: "role",
2497
2515
  role: roleSpec.role,
@@ -2503,7 +2521,9 @@ function buildRoleToken(roleSpec, profile) {
2503
2521
  ...roleSpec.valueShape !== void 0 ? { valueShape: roleSpec.valueShape } : {},
2504
2522
  // A greedy role reads up to the next marker the pattern owes (a domain's
2505
2523
  // SQL condition); its value reader does the reading.
2506
- ...roleSpec.greedy ? { greedy: true } : {}
2524
+ ...roleSpec.greedy ? { greedy: true } : {},
2525
+ // A role with a word list reads only one of its words.
2526
+ ...words ? { words } : {}
2507
2527
  };
2508
2528
  const prefixLiteral = roleSpec.valuePrefixLiteral?.[profile.code];
2509
2529
  const pushPrefixed = () => {
@@ -4044,12 +4064,15 @@ function readingDifference(source, back) {
4044
4064
 
4045
4065
  // src/api/create-dsl.ts
4046
4066
  var UnreadInputError = class extends Error {
4047
- constructor(input, language, reading, unread) {
4048
- super(`"${input}" (${language}) reads as ${reading.action} only by leaving "${unread}" unread`);
4067
+ constructor(input, language, reading, unread, hint) {
4068
+ super(
4069
+ `"${input}" (${language}) reads as ${reading.action} only by leaving "${unread}" unread` + (hint ? `; ${hint}` : "")
4070
+ );
4049
4071
  this.input = input;
4050
4072
  this.language = language;
4051
4073
  this.reading = reading;
4052
4074
  this.unread = unread;
4075
+ this.hint = hint;
4053
4076
  this.code = "unread-input";
4054
4077
  this.name = "UnreadInputError";
4055
4078
  }
@@ -4168,15 +4191,32 @@ var MultilingualDSLImpl = class {
4168
4191
  }
4169
4192
  if (longestPartial) {
4170
4193
  const unread = tokens.tokens.slice(longestPartial.consumedTokens).map((t) => t.value).join(" ");
4194
+ const hint = this.quoteHint(longestPartial, tokens.tokens);
4171
4195
  throw new UnreadInputError(
4172
4196
  input,
4173
4197
  language,
4174
4198
  this.nodeOf(longestPartial, input, language),
4175
- unread
4199
+ unread,
4200
+ ...hint ? [hint] : []
4176
4201
  );
4177
4202
  }
4178
4203
  throw new Error(`No pattern matched for input: ${input}`);
4179
4204
  }
4205
+ /**
4206
+ * `if "buy milk" is one value, quote it`, where the last token a match read is a bare word it took
4207
+ * as a role's value (a role with no word list) and the first it left is a bare word too.
4208
+ */
4209
+ quoteHint(match, tokens) {
4210
+ const last = tokens[match.consumedTokens - 1];
4211
+ const next = tokens[match.consumedTokens];
4212
+ if (last?.kind !== "identifier" || next?.kind !== "identifier") return void 0;
4213
+ const schema = this.schemaLookup.getSchema(match.pattern.command);
4214
+ const role = [...match.captured].find(
4215
+ ([, v]) => v.raw === last.value
4216
+ )?.[0];
4217
+ if (!role || schema?.roles.find((r) => r.role === role)?.words) return void 0;
4218
+ return `if "${last.value} ${next.value}" is one value, quote it`;
4219
+ }
4180
4220
  nodeOf(match, input, language) {
4181
4221
  return {
4182
4222
  kind: "command",