@lokascript/framework 4.4.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.
- package/CHANGELOG.md +197 -5
- package/dist/api/create-dsl.d.ts +46 -1
- package/dist/api/create-dsl.d.ts.map +1 -1
- package/dist/api/domain-registry.d.ts.map +1 -1
- package/dist/api/index.js +187 -19
- package/dist/api/index.js.map +1 -1
- package/dist/api/reading.d.ts +8 -0
- package/dist/api/reading.d.ts.map +1 -0
- package/dist/index.cjs +189 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +187 -19
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/__test__/fixtures/sql-dsl.ts +3 -0
- package/src/__test__/sql-integration.test.ts +2 -2
- package/src/api/create-dsl.test.ts +120 -0
- package/src/api/create-dsl.ts +173 -21
- package/src/api/domain-registry.test.ts +70 -5
- package/src/api/domain-registry.ts +82 -1
- package/src/api/reading.ts +29 -0
|
@@ -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
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
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.
|
|
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,
|