@lokascript/framework 4.5.0 → 4.6.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.
@@ -0,0 +1,8 @@
1
+ /**
2
+ * How a read-back is compared (internal: not exported from the package). `translate` and the
3
+ * registry's translate tool both require a rendering to read back as its source; this is their rule.
4
+ */
5
+ import type { SemanticNode } from '../core/types';
6
+ /** How `back` differs from `source`; empty when it reads the same. */
7
+ export declare function readingDifference(source: SemanticNode, back: SemanticNode): string;
8
+ //# sourceMappingURL=reading.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reading.d.ts","sourceRoot":"","sources":["../../src/api/reading.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAYlD,sEAAsE;AACtE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,GAAG,MAAM,CAWlF"}
package/dist/index.cjs CHANGED
@@ -44,7 +44,9 @@ __export(index_exports, {
44
44
  PunctuationExtractor: () => PunctuationExtractor,
45
45
  StringLiteralExtractor: () => StringLiteralExtractor,
46
46
  TokenStreamImpl: () => TokenStreamImpl,
47
+ TranslationReadBackError: () => TranslationReadBackError,
47
48
  UnicodeIdentifierExtractor: () => UnicodeIdentifierExtractor,
49
+ UnreadInputError: () => UnreadInputError,
48
50
  WhitespaceExtractor: () => WhitespaceExtractor,
49
51
  accumulateBlocks: () => accumulateBlocks,
50
52
  buildDisambiguation: () => buildDisambiguation,
@@ -2954,7 +2956,53 @@ function semanticValueToAST(value) {
2954
2956
  }
2955
2957
  }
2956
2958
 
2959
+ // src/api/reading.ts
2960
+ function roleValues(node) {
2961
+ const out = /* @__PURE__ */ new Map();
2962
+ for (const [role, value] of node.roles) {
2963
+ const v = value;
2964
+ out.set(role, String(v?.value ?? v?.raw ?? value));
2965
+ }
2966
+ return out;
2967
+ }
2968
+ function readingDifference(source, back) {
2969
+ if (back.action !== source.action) return `reads as ${back.action}`;
2970
+ const want = roleValues(source);
2971
+ const got = roleValues(back);
2972
+ const parts = [];
2973
+ for (const [role, value] of want) {
2974
+ if (!got.has(role)) parts.push(`${role} lost`);
2975
+ else if (got.get(role) !== value) parts.push(`${role}: ${value} \u2192 ${got.get(role)}`);
2976
+ }
2977
+ for (const role of got.keys()) if (!want.has(role)) parts.push(`${role} added`);
2978
+ return parts.join(", ");
2979
+ }
2980
+
2957
2981
  // src/api/create-dsl.ts
2982
+ var UnreadInputError = class extends Error {
2983
+ constructor(input, language, reading, unread) {
2984
+ super(`"${input}" (${language}) reads as ${reading.action} only by leaving "${unread}" unread`);
2985
+ this.input = input;
2986
+ this.language = language;
2987
+ this.reading = reading;
2988
+ this.unread = unread;
2989
+ this.code = "unread-input";
2990
+ this.name = "UnreadInputError";
2991
+ }
2992
+ };
2993
+ var TranslationReadBackError = class extends Error {
2994
+ constructor(partial, from, to, source, reading, why) {
2995
+ super(`translation ${from} \u2192 ${to} does not read back as the source (${why}): "${partial}"`);
2996
+ this.partial = partial;
2997
+ this.from = from;
2998
+ this.to = to;
2999
+ this.source = source;
3000
+ this.reading = reading;
3001
+ this.why = why;
3002
+ this.code = "translation-does-not-read-back";
3003
+ this.name = "TranslationReadBackError";
3004
+ }
3005
+ };
2958
3006
  var DSLRegistry = class {
2959
3007
  /**
2960
3008
  * @param partiallyTranslatedActions Actions permitted to lack a keyword in
@@ -3039,25 +3087,50 @@ var MultilingualDSLImpl = class {
3039
3087
  const profile = {
3040
3088
  code: language
3041
3089
  };
3090
+ const total = tokens.tokens.length;
3091
+ let best = null;
3092
+ let longestPartial = null;
3042
3093
  for (const pattern of patterns) {
3094
+ const mark = tokens.mark();
3043
3095
  const match = this.matcher.matchPattern(tokens, pattern, profile);
3044
- if (match) {
3045
- const node = {
3046
- kind: "command",
3047
- action: pattern.command,
3048
- roles: match.captured,
3049
- metadata: {
3050
- sourceLanguage: language,
3051
- sourceText: input,
3052
- patternId: pattern.id,
3053
- confidence: match.confidence
3054
- }
3055
- };
3056
- return { node, confidence: match.confidence };
3096
+ tokens.reset(mark);
3097
+ if (!match) continue;
3098
+ if (match.consumedTokens < total) {
3099
+ if (!longestPartial || match.consumedTokens > longestPartial.consumedTokens)
3100
+ longestPartial = match;
3101
+ continue;
3057
3102
  }
3103
+ if (!best || match.pattern.priority > best.pattern.priority || match.pattern.priority === best.pattern.priority && match.confidence > best.confidence + 1e-3)
3104
+ best = match;
3105
+ }
3106
+ if (best) {
3107
+ const node = this.nodeOf(best, input, language);
3108
+ return { node, confidence: best.confidence };
3109
+ }
3110
+ if (longestPartial) {
3111
+ const unread = tokens.tokens.slice(longestPartial.consumedTokens).map((t) => t.value).join(" ");
3112
+ throw new UnreadInputError(
3113
+ input,
3114
+ language,
3115
+ this.nodeOf(longestPartial, input, language),
3116
+ unread
3117
+ );
3058
3118
  }
3059
3119
  throw new Error(`No pattern matched for input: ${input}`);
3060
3120
  }
3121
+ nodeOf(match, input, language) {
3122
+ return {
3123
+ kind: "command",
3124
+ action: match.pattern.command,
3125
+ roles: match.captured,
3126
+ metadata: {
3127
+ sourceLanguage: language,
3128
+ sourceText: input,
3129
+ patternId: match.pattern.id,
3130
+ confidence: match.confidence
3131
+ }
3132
+ };
3133
+ }
3061
3134
  validate(input, language) {
3062
3135
  try {
3063
3136
  const result = this.parseWithConfidence(input, language);
@@ -3068,11 +3141,12 @@ var MultilingualDSLImpl = class {
3068
3141
  } catch (error) {
3069
3142
  return {
3070
3143
  valid: false,
3071
- errors: [error instanceof Error ? error.message : String(error)]
3144
+ errors: [error instanceof Error ? error.message : String(error)],
3145
+ ...error instanceof UnreadInputError ? { errorCode: error.code } : {}
3072
3146
  };
3073
3147
  }
3074
3148
  }
3075
- translate(input, fromLanguage, toLanguage) {
3149
+ translate(input, fromLanguage, toLanguage, options) {
3076
3150
  if ((0, import_intent3.isExplicitSyntax)(input)) return input;
3077
3151
  let node;
3078
3152
  try {
@@ -3084,14 +3158,46 @@ var MultilingualDSLImpl = class {
3084
3158
  throw parseError;
3085
3159
  }
3086
3160
  const rendered = this.registry.getSupportedLanguages().includes(toLanguage) ? this.render(node, toLanguage) : null;
3087
- if (rendered != null) return rendered;
3161
+ if (rendered != null) return this.readsBack(rendered, node, fromLanguage, toLanguage, options);
3088
3162
  if (this.hasGrammarProfile(fromLanguage) && this.hasGrammarProfile(toLanguage)) {
3089
- return this.transformer.transform(input, fromLanguage, toLanguage);
3163
+ return this.readsBack(
3164
+ this.transformer.transform(input, fromLanguage, toLanguage),
3165
+ node,
3166
+ fromLanguage,
3167
+ toLanguage,
3168
+ options
3169
+ );
3090
3170
  }
3091
3171
  throw new Error(
3092
3172
  `translate() could not render action "${node.action}" in language "${toLanguage}": no custom renderer, domain renderer, or schema pattern covers it, and no grammar profile is configured for the transformer fallback. Either add "${toLanguage}" to the DSL's languages/renderer, or set 'grammarProfile' on its LanguageConfig in createMultilingualDSL() (parse/validate/compile do not need it).`
3093
3173
  );
3094
3174
  }
3175
+ /**
3176
+ * `rendered`, read in `to`, must be `source`: the same action and role values, or, when the DSL
3177
+ * compiles, the same code (a parser may keep a translated word its generator normalizes: bdd ja
3178
+ * `表示` is `visible`). The rule is the domain gate's (lokascript-domains). A translation used to be
3179
+ * returned whatever it read as: ja/ko bdd `select` dropped its value, and nothing said so.
3180
+ */
3181
+ readsBack(rendered, source, from, to, options) {
3182
+ if (options?.lossy === "allow") return rendered;
3183
+ let back;
3184
+ try {
3185
+ back = this.parseWithConfidence(rendered, to).node;
3186
+ } catch (error) {
3187
+ const why = error instanceof Error ? error.message : String(error);
3188
+ throw new TranslationReadBackError(rendered, from, to, source, null, why);
3189
+ }
3190
+ const difference = readingDifference(source, back);
3191
+ if (!difference) return rendered;
3192
+ if (this.codeGenerator) {
3193
+ try {
3194
+ if (this.codeGenerator.generate(back) === this.codeGenerator.generate(source))
3195
+ return rendered;
3196
+ } catch {
3197
+ }
3198
+ }
3199
+ throw new TranslationReadBackError(rendered, from, to, source, back, difference);
3200
+ }
3095
3201
  hasGrammarProfile(language) {
3096
3202
  return this.profileProvider.getProfile(language) != null;
3097
3203
  }
@@ -3117,7 +3223,8 @@ var MultilingualDSLImpl = class {
3117
3223
  } catch (error) {
3118
3224
  return {
3119
3225
  ok: false,
3120
- errors: [error instanceof Error ? error.message : String(error)]
3226
+ errors: [error instanceof Error ? error.message : String(error)],
3227
+ ...error instanceof UnreadInputError ? { errorCode: error.code } : {}
3121
3228
  };
3122
3229
  }
3123
3230
  }
@@ -4044,6 +4151,8 @@ var DomainRegistry = class {
4044
4151
  return jsonResponse({ error: `Unknown operation: ${parsed.operation}` }, true);
4045
4152
  }
4046
4153
  } catch (error) {
4154
+ if (error instanceof UnreadInputError) return unreadInputResponse(error);
4155
+ if (error instanceof TranslationReadBackError) return readBackResponse(error);
4047
4156
  const message = error instanceof Error ? error.message : String(error);
4048
4157
  return jsonResponse({ error: `${descriptor.name} tool error: ${message}` }, true);
4049
4158
  }
@@ -4169,6 +4278,7 @@ var DomainRegistry = class {
4169
4278
  ok: result.ok,
4170
4279
  code: result.code,
4171
4280
  errors: result.errors,
4281
+ ...result.errorCode === "unread-input" ? { diagnostics: [UNREAD_INPUT(result.errors)] } : {},
4172
4282
  language,
4173
4283
  input
4174
4284
  });
@@ -4181,6 +4291,7 @@ var DomainRegistry = class {
4181
4291
  return jsonResponse({
4182
4292
  valid: result.valid,
4183
4293
  errors: result.errors,
4294
+ ...result.errorCode === "unread-input" ? { diagnostics: [UNREAD_INPUT(result.errors)] } : {},
4184
4295
  language,
4185
4296
  [descriptor.inputLabel]: input
4186
4297
  });
@@ -4211,7 +4322,24 @@ var DomainRegistry = class {
4211
4322
  for (const [key, value] of node.roles) {
4212
4323
  roles[key] = value;
4213
4324
  }
4325
+ if (rendered != null) {
4326
+ let back = null;
4327
+ let why = "";
4328
+ try {
4329
+ back = dsl.parse(rendered, to);
4330
+ why = readingDifference(node, back);
4331
+ if (why && compiled.ok) {
4332
+ const again = dsl.compile(rendered, to);
4333
+ if (again.ok && again.code === compiled.code) why = "";
4334
+ }
4335
+ } catch (error) {
4336
+ why = error instanceof Error ? error.message : String(error);
4337
+ }
4338
+ if (why)
4339
+ return readBackResponse(new TranslationReadBackError(rendered, from, to, node, back, why));
4340
+ }
4214
4341
  return jsonResponse({
4342
+ ok: true,
4215
4343
  input: { [descriptor.inputLabel]: input, language: from },
4216
4344
  ...rendered != null && { rendered: { text: rendered, language: to } },
4217
4345
  semantic: { action: node.action, roles },
@@ -4291,7 +4419,7 @@ function generateToolDefinitions(desc) {
4291
4419
  case "translate":
4292
4420
  tools.push({
4293
4421
  name: `translate_${desc.name}`,
4294
- description: `Translate ${desc.description} input between natural languages (requires 'from' and 'to' codes). Supports: ${langList}.`,
4422
+ description: `Translate ${desc.description} input between natural languages (requires 'from' and 'to' codes). Supports: ${langList}. A rendering that does not read back as the source is REFUSED: ok is false, the diagnostic is TRANSLATION_DOES_NOT_READ_BACK, and its text is under \`partial\`, never \`rendered\`: do not present it as the translation. Input left unread is refused as UNREAD_INPUT.`,
4295
4423
  inputSchema: {
4296
4424
  type: "object",
4297
4425
  properties: {
@@ -4325,6 +4453,46 @@ function getString(args, name, defaultValue = "") {
4325
4453
  const value = args[name];
4326
4454
  return typeof value === "string" ? value : defaultValue;
4327
4455
  }
4456
+ function readingOf(node) {
4457
+ const roles = {};
4458
+ for (const [key, value] of node.roles) roles[key] = value;
4459
+ return { action: node.action, roles };
4460
+ }
4461
+ var UNREAD_INPUT = (errors) => ({
4462
+ severity: "error",
4463
+ code: "UNREAD_INPUT",
4464
+ message: errors?.[0] ?? "part of the input was not read",
4465
+ suggestion: "Part of the input belongs to no role of the command it reads as: remove it, or quote a value of several words."
4466
+ });
4467
+ function unreadInputResponse(error) {
4468
+ return jsonResponse(
4469
+ {
4470
+ ok: false,
4471
+ unread: error.unread,
4472
+ reading: readingOf(error.reading),
4473
+ diagnostics: [UNREAD_INPUT([error.message])]
4474
+ },
4475
+ true
4476
+ );
4477
+ }
4478
+ function readBackResponse(error) {
4479
+ return jsonResponse(
4480
+ {
4481
+ ok: false,
4482
+ partial: error.partial,
4483
+ ...error.reading ? { reading: readingOf(error.reading) } : {},
4484
+ diagnostics: [
4485
+ {
4486
+ severity: "error",
4487
+ code: "TRANSLATION_DOES_NOT_READ_BACK",
4488
+ message: error.message,
4489
+ suggestion: "The rendering would not mean the source in the target language: do not present `partial` as the translation."
4490
+ }
4491
+ ]
4492
+ },
4493
+ true
4494
+ );
4495
+ }
4328
4496
  function jsonResponse(data, isError) {
4329
4497
  return {
4330
4498
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
@@ -6736,7 +6904,9 @@ function accumulateIndented(statements, config) {
6736
6904
  PunctuationExtractor,
6737
6905
  StringLiteralExtractor,
6738
6906
  TokenStreamImpl,
6907
+ TranslationReadBackError,
6739
6908
  UnicodeIdentifierExtractor,
6909
+ UnreadInputError,
6740
6910
  WhitespaceExtractor,
6741
6911
  accumulateBlocks,
6742
6912
  buildDisambiguation,