@rsconcept/domain 2.0.2 → 2.1.1

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 (59) hide show
  1. package/README.md +1 -1
  2. package/dist/{analyzer-iqAXyROE.d.ts → analyzer-fNhkwO-6.d.ts} +2 -2
  3. package/dist/{ast-annotations-D6KZDu12.d.ts → ast-annotations-CiIx8qBM.d.ts} +2 -2
  4. package/dist/{calculator-k8BGIKmj.d.ts → calculator-DE89LtIM.d.ts} +2 -2
  5. package/dist/cctext/language-api.js.map +1 -1
  6. package/dist/cctext/language.d.ts +7 -4
  7. package/dist/cctext/language.js.map +1 -1
  8. package/dist/{diagnostics-D7SiHAwW.js → diagnostics-CY9sKggB.js} +152 -12
  9. package/dist/diagnostics-CY9sKggB.js.map +1 -0
  10. package/dist/{diagnostics-Clo3mkba.d.ts → diagnostics-EaIt3zIw.d.ts} +28 -6
  11. package/dist/{error-CzIbnP-N.d.ts → error-B0EkIx4K.d.ts} +2 -1
  12. package/dist/graph/graph.d.ts +1 -1
  13. package/dist/graph/graph.js +93 -1
  14. package/dist/graph/graph.js.map +1 -1
  15. package/dist/graph/index.d.ts +1 -1
  16. package/dist/{graph-DR8rL2o3.d.ts → graph-CL73WdW1.d.ts} +15 -2
  17. package/dist/index.d.ts +7 -7
  18. package/dist/index.js +2 -2
  19. package/dist/library/diagnostics.d.ts +2 -2
  20. package/dist/library/diagnostics.js +2 -2
  21. package/dist/library/index.d.ts +2 -2
  22. package/dist/library/index.js +2 -2
  23. package/dist/library/oss-api.d.ts +1 -1
  24. package/dist/library/oss.d.ts +1 -1
  25. package/dist/library/rsengine.d.ts +2 -2
  26. package/dist/library/rsengine.js +1 -1
  27. package/dist/library/rsform-api.d.ts +14 -4
  28. package/dist/library/rsform-api.js +2 -2
  29. package/dist/library/rsform.d.ts +1 -1
  30. package/dist/library/rsmodel-api.d.ts +1 -1
  31. package/dist/library/rsmodel-api.js +1 -1
  32. package/dist/library/rsmodel.d.ts +1 -1
  33. package/dist/library/structure-planner.d.ts +1 -1
  34. package/dist/library/structure-planner.js +1 -1
  35. package/dist/rslang/ast-annotations.d.ts +1 -1
  36. package/dist/rslang/error.d.ts +1 -1
  37. package/dist/rslang/error.js +2 -1
  38. package/dist/rslang/error.js.map +1 -1
  39. package/dist/rslang/eval/calculator.d.ts +1 -1
  40. package/dist/rslang/eval/evaluator.d.ts +1 -1
  41. package/dist/rslang/index.d.ts +4 -4
  42. package/dist/rslang/parser/syntax-errors/index.d.ts +1 -1
  43. package/dist/rslang/semantic/analyzer.d.ts +1 -1
  44. package/dist/rslang/semantic/type-auditor.d.ts +1 -1
  45. package/dist/rslang/semantic/value-auditor.d.ts +1 -1
  46. package/package.json +14 -13
  47. package/src/cctext/language-api.test.ts +5 -0
  48. package/src/cctext/language-api.ts +6 -6
  49. package/src/cctext/language.ts +3 -4
  50. package/src/graph/graph.test.ts +22 -0
  51. package/src/graph/graph.ts +115 -1
  52. package/src/index.ts +2 -0
  53. package/src/library/diagnostics.test.ts +34 -0
  54. package/src/library/diagnostics.ts +81 -2
  55. package/src/library/index.ts +2 -0
  56. package/src/library/rsform-api.test.ts +99 -3
  57. package/src/library/rsform-api.ts +155 -7
  58. package/src/rslang/error.ts +4 -1
  59. package/dist/diagnostics-D7SiHAwW.js.map +0 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Shared TypeScript domain layer for the [Concept Portal](https://portal.acconcept.ru) stack.
4
4
 
5
- This package implements the formal language **RSLang**, the schema model **RSForm**, the evaluation engine **RSEngine**, the operation-of-schemas model **OSS**, and supporting helpers (graphs, parsing utilities, concept-text grammemes). It is consumed both by the Portal frontend and by [`@rsconcept/rstool`](https://www.npmjs.com/package/@rsconcept/rstool).
5
+ This package implements the formal language **RSLang** (language of structural species explication), the schema model **RSForm**, the evaluation engine **RSEngine**, the operation-of-schemas model **OSS**, and supporting helpers (graphs, parsing utilities, concept-text grammemes). It is consumed both by the Portal frontend and by [`@rsconcept/rstool`](https://www.npmjs.com/package/@rsconcept/rstool).
6
6
 
7
7
  ## Install
8
8
 
@@ -1,6 +1,6 @@
1
1
  import { t as AstNode } from "./ast-C8sIpKdL.js";
2
2
  import { f as ExpressionType, g as TypeClass } from "./typification-9lNu9R4k.js";
3
- import { i as RSErrorDescription } from "./error-CzIbnP-N.js";
3
+ import { i as RSErrorDescription } from "./error-B0EkIx4K.js";
4
4
  import { t as ValueClass } from "./value-class-CNI-lqXJ.js";
5
5
 
6
6
  //#region src/rslang/semantic/analyzer.d.ts
@@ -35,4 +35,4 @@ declare class RSLangAnalyzer {
35
35
  }
36
36
  //#endregion
37
37
  export { RSLangAnalyzer as a, AnalysisOptions as i, AnalysisFast as n, AnalysisFull as r, AnalysisBase as t };
38
- //# sourceMappingURL=analyzer-iqAXyROE.d.ts.map
38
+ //# sourceMappingURL=analyzer-fNhkwO-6.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import { t as AstNode } from "./ast-C8sIpKdL.js";
2
2
  import { f as ExpressionType } from "./typification-9lNu9R4k.js";
3
- import { a as RSErrorInfo, r as RSErrorCode } from "./error-CzIbnP-N.js";
3
+ import { a as RSErrorInfo, r as RSErrorCode } from "./error-B0EkIx4K.js";
4
4
 
5
5
  //#region src/rslang/ast-annotations.d.ts
6
6
  /** Appends {@link ExpressionType} onto the node's `annotation.rsType`. */
@@ -13,4 +13,4 @@ declare function annotateError(node: AstNode, code: RSErrorCode, params?: readon
13
13
  declare function readErrorAnnotation(node: AstNode): RSErrorInfo | null;
14
14
  //#endregion
15
15
  export { readTypeAnnotation as i, annotateType as n, readErrorAnnotation as r, annotateError as t };
16
- //# sourceMappingURL=ast-annotations-D6KZDu12.d.ts.map
16
+ //# sourceMappingURL=ast-annotations-CiIx8qBM.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import { t as AstNode } from "./ast-C8sIpKdL.js";
2
2
  import { f as ExpressionType } from "./typification-9lNu9R4k.js";
3
- import { i as RSErrorDescription } from "./error-CzIbnP-N.js";
3
+ import { i as RSErrorDescription } from "./error-B0EkIx4K.js";
4
4
  import { l as Value } from "./value-BduA_ZGp.js";
5
5
 
6
6
  //#region src/rslang/eval/calculator.d.ts
@@ -36,4 +36,4 @@ declare class RSCalculator {
36
36
  }
37
37
  //#endregion
38
38
  export { CalculatorResult as n, RSCalculator as r, CalculatorEvaluateOptions as t };
39
- //# sourceMappingURL=calculator-k8BGIKmj.d.ts.map
39
+ //# sourceMappingURL=calculator-DE89LtIM.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"language-api.js","names":[],"sources":["../../src/cctext/language-api.ts"],"sourcesContent":["/**\n * Module: Natural language model API.\n */\n\nimport {\n Case,\n type EntityReference,\n Grammeme,\n type IReference,\n Plurality,\n ReferenceType,\n type SyntacticReference,\n type TermContext,\n type TermContextItem,\n type WordForm\n} from './language';\n\ninterface Position {\n from: number;\n to: number;\n}\n\ninterface ResolvedReference {\n ref: IReference;\n resolved: string;\n posInput: Position;\n}\n\nconst REFERENCE_PATTERN = /@{[^{}]*?}/g;\nconst ENTITY_REFERENCE_PATTERN = /@{([^0-9\\-][^}|{]*?)\\|([^}|{]*?)}/g;\n\n/** Represents generated lexeme forms. */\nexport interface Lexeme {\n items: { text: string; grams: string }[];\n}\n\n/** Equality comparator for {@link WordForm}. Compares a set of Grammemes attached to wordforms. */\nexport function wordFormEquals(left: WordForm, right: WordForm): boolean {\n if (left.grams.length !== right.grams.length) {\n return false;\n }\n for (let index = 0; index < left.grams.length; ++index) {\n if (left.grams[index] !== right.grams[index]) {\n return false;\n }\n }\n return true;\n}\n\n/** Transforms {@link Grammeme} enumeration to {@link Grammeme}. */\nexport function parseGrammemes(termForm: string): Grammeme[] {\n const result: Grammeme[] = [];\n for (const chunk of termForm.split(',')) {\n const gram = chunk.trim();\n if (gram !== '') {\n result.push(gram as Grammeme);\n }\n }\n return result.sort(grammemeCompare);\n}\n\n/** Generates all supported noun forms using nominal text for every form. */\nexport function generateNominalLexeme(data: { text: string }): Lexeme {\n return {\n items: Plurality.flatMap(plurality =>\n Case.map(gramCase => ({\n text: data.text,\n grams: `${plurality},${gramCase}`\n }))\n )\n };\n}\n\n/**\n * Extracts {@link EntityReference} from string representation.\n *\n * @param text - Reference text in a valid pattern. Must fit format '\\@\\{GLOBAL_ID|GRAMMEMES\\}'\n */\nexport function parseEntityReference(text: string): EntityReference {\n const ref = parseReference(text);\n if (ref?.type !== ReferenceType.ENTITY) {\n throw new Error(`Invalid entity reference: ${text}`);\n }\n return ref.data as EntityReference;\n}\n\n/**\n * Extracts {@link SyntacticReference} from string representation.\n *\n * @param text - Reference text in a valid pattern. Must fit format '\\@\\{OFFSET|NOMINAL_FORM\\}'\n */\nexport function parseSyntacticReference(text: string): SyntacticReference {\n const ref = parseReference(text);\n if (ref?.type !== ReferenceType.SYNTACTIC) {\n throw new Error(`Invalid syntactic reference: ${text}`);\n }\n return ref.data as SyntacticReference;\n}\n\n/** Extracts a validated reference from string representation. */\nexport function parseReference(text: string): IReference | null {\n if (text.length < 4 || !text.startsWith('@{') || !text.endsWith('}')) {\n return null;\n }\n\n const blocks = text\n .slice(2, text.length - 1)\n .split('|')\n .map(block => block.trim());\n if (blocks.length !== 2 || blocks[0] === '' || blocks[0].startsWith('0')) {\n return null;\n }\n\n if (blocks[0].startsWith('-') || /^[1-9]/.test(blocks[0])) {\n const offset = Number(blocks[0]);\n if (!Number.isInteger(offset) || offset === 0 || blocks[1] === '') {\n return null;\n }\n return {\n type: ReferenceType.SYNTACTIC,\n data: { offset, nominal: blocks[1] }\n };\n }\n\n return {\n type: ReferenceType.ENTITY,\n data: { entity: blocks[0], tags: parseGrammemes(blocks[1].replaceAll(' ', '')) }\n };\n}\n\n/** Apply alias mapping to entity references in terminological text. */\nexport function applyEntityReferenceMapping(text: string, mapping: Record<string, string>): string {\n if (text === '' || Object.keys(mapping).length === 0) {\n return text;\n }\n\n let posInput = 0;\n let output = '';\n for (const segment of text.matchAll(ENTITY_REFERENCE_PATTERN)) {\n const entity = segment[1];\n const entityKey = entity.trim();\n const start = segment.index ?? 0;\n if (entityKey in mapping) {\n output += text.substring(posInput, start + 2);\n output += mapping[entityKey];\n output += text.substring(start + 2 + entity.length, start + segment[0].length);\n posInput = start + segment[0].length;\n }\n }\n output += text.substring(posInput);\n return output;\n}\n\n/** Extracts unique entity aliases referenced by text. */\nexport function extractEntities(text: string): string[] {\n const result: string[] = [];\n for (const segment of text.matchAll(ENTITY_REFERENCE_PATTERN)) {\n const entity = segment[1].trim();\n if (!result.includes(entity)) {\n result.push(entity);\n }\n }\n return result;\n}\n\n/** Resolves text references using nominal terms and optional manually edited forms. */\nexport function resolveTextReferences(text: string, context: TermContext): string {\n const references = parseReferences(text);\n if (references.length === 0) {\n return text;\n }\n\n for (const ref of references) {\n if (ref.ref.type === ReferenceType.ENTITY) {\n ref.resolved = resolveEntity(ref.ref.data as EntityReference, context);\n }\n }\n references.forEach((ref, index) => {\n if (ref.ref.type === ReferenceType.SYNTACTIC) {\n ref.resolved = resolveSyntactic(ref.ref.data as SyntacticReference, index, references);\n }\n });\n\n let posInput = 0;\n let output = '';\n for (const ref of references) {\n output += text.substring(posInput, ref.posInput.from);\n output += ref.resolved;\n posInput = ref.posInput.to;\n }\n output += text.substring(posInput);\n return output;\n}\n\n/** Transforms {@link IReference} to string representation. */\nexport function referenceToString(ref: IReference): string {\n switch (ref.type) {\n case ReferenceType.ENTITY: {\n const entity = ref.data as EntityReference;\n return `@{${entity.entity}|${entity.tags.join(',')}}`;\n }\n case ReferenceType.SYNTACTIC: {\n const syntactic = ref.data as SyntacticReference;\n return `@{${syntactic.offset}|${syntactic.nominal}}`;\n }\n }\n}\n\n// ===== Internals =======\n\n/** Compares {@link Grammeme} based on Grammeme enum and alpha order for strings. */\nfunction grammemeCompare(left: Grammeme, right: Grammeme): number {\n const indexLeft = Object.values(Grammeme).findIndex(gram => gram === left);\n const indexRight = Object.values(Grammeme).findIndex(gram => gram === right);\n if (indexLeft === -1 && indexRight === -1) {\n return left.localeCompare(right);\n } else if (indexLeft === -1 && indexRight !== -1) {\n return 1;\n } else if (indexLeft !== -1 && indexRight === -1) {\n return -1;\n } else {\n return indexLeft - indexRight;\n }\n}\n\nfunction parseReferences(text: string): ResolvedReference[] {\n const result: ResolvedReference[] = [];\n for (const segment of text.matchAll(REFERENCE_PATTERN)) {\n const ref = parseReference(segment[0]);\n if (ref) {\n result.push({\n ref,\n resolved: '',\n posInput: { from: segment.index ?? 0, to: (segment.index ?? 0) + segment[0].length }\n });\n }\n }\n return result;\n}\n\nfunction resolveEntity(ref: EntityReference, context: TermContext): string {\n const entity = context[ref.entity];\n if (!entity) {\n return `!Неизвестная сущность: ${ref.entity}!`;\n }\n\n const resolved = getEntityForm(entity, ref.tags);\n return resolved === '' ? `!Отсутствует термин: ${ref.entity}!` : resolved;\n}\n\nfunction getEntityForm(entity: TermContextItem, grams: Grammeme[]): string {\n if (grams.length === 0) {\n return entity.nominal;\n }\n\n const manual = entity.forms?.find(form => matchGrams(grams, form.grams));\n return manual?.text ?? entity.nominal;\n}\n\nfunction matchGrams(query: Grammeme[], candidate: Grammeme[]): boolean {\n for (const gram of candidate) {\n if (!query.includes(gram)) {\n return false;\n }\n }\n return true;\n}\n\nfunction resolveSyntactic(ref: SyntacticReference, index: number, references: ResolvedReference[]): string {\n const master = findSyntacticMaster(ref.offset, index, references);\n if (!master) {\n return `!Некорректное смещение: ${ref.offset}!`;\n }\n return ref.nominal;\n}\n\nfunction findSyntacticMaster(\n offset: number,\n index: number,\n references: ResolvedReference[]\n): ResolvedReference | undefined {\n if (offset > 0) {\n let position = index + 1;\n let left = offset;\n while (position < references.length) {\n if (references[position].ref.type === ReferenceType.ENTITY) {\n if (left === 1) {\n return references[position];\n }\n left -= 1;\n }\n position += 1;\n }\n } else {\n let position = index - 1;\n let left = offset;\n while (position >= 0) {\n if (references[position].ref.type === ReferenceType.ENTITY) {\n if (left === -1) {\n return references[position];\n }\n left += 1;\n }\n position -= 1;\n }\n }\n return undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"language-api.js","names":[],"sources":["../../src/cctext/language-api.ts"],"sourcesContent":["/**\n * Module: Natural language model API.\n */\n\nimport {\n Case,\n type EntityReference,\n Grammeme,\n type IReference,\n Plurality,\n ReferenceType,\n type SyntacticReference,\n type TermContext,\n type TermContextItem,\n type WordForm\n} from './language';\n\ninterface Position {\n from: number;\n to: number;\n}\n\ninterface ResolvedReference {\n ref: IReference;\n resolved: string;\n posInput: Position;\n}\n\nconst REFERENCE_PATTERN = /@{[^{}]*?}/g;\nconst ENTITY_REFERENCE_PATTERN = /@{([^0-9\\-][^}|{]*?)\\|([^}|{]*?)}/g;\n\n/** Represents generated lexeme forms. */\nexport interface Lexeme {\n items: { text: string; grams: string }[];\n}\n\n/** Equality comparator for {@link WordForm}. Compares a set of Grammemes attached to wordforms. */\nexport function wordFormEquals(left: WordForm, right: WordForm): boolean {\n if (left.grams.length !== right.grams.length) {\n return false;\n }\n for (let index = 0; index < left.grams.length; ++index) {\n if (left.grams[index] !== right.grams[index]) {\n return false;\n }\n }\n return true;\n}\n\n/** Transforms {@link Grammeme} enumeration to {@link Grammeme}. */\nexport function parseGrammemes(termForm: string): Grammeme[] {\n const result: Grammeme[] = [];\n for (const chunk of termForm.split(',')) {\n const gram = chunk.trim();\n if (gram !== '') {\n result.push(gram as Grammeme);\n }\n }\n return result.sort(grammemeCompare);\n}\n\n/** Generates all supported noun forms using nominal text for every form. */\nexport function generateNominalLexeme(data: { text: string }): Lexeme {\n return {\n items: Plurality.flatMap(plurality =>\n Case.map(gramCase => ({\n text: data.text,\n grams: `${plurality},${gramCase}`\n }))\n )\n };\n}\n\n/**\n * Extracts {@link EntityReference} from string representation.\n *\n * @param text - Reference text in a valid pattern. Must fit format '\\@\\{GLOBAL_ID|GRAMMEMES\\}'\n */\nexport function parseEntityReference(text: string): EntityReference {\n const ref = parseReference(text);\n if (ref?.type !== ReferenceType.ENTITY) {\n throw new Error(`Invalid entity reference: ${text}`);\n }\n return ref.data;\n}\n\n/**\n * Extracts {@link SyntacticReference} from string representation.\n *\n * @param text - Reference text in a valid pattern. Must fit format '\\@\\{OFFSET|NOMINAL_FORM\\}'\n */\nexport function parseSyntacticReference(text: string): SyntacticReference {\n const ref = parseReference(text);\n if (ref?.type !== ReferenceType.SYNTACTIC) {\n throw new Error(`Invalid syntactic reference: ${text}`);\n }\n return ref.data;\n}\n\n/** Extracts a validated reference from string representation. */\nexport function parseReference(text: string): IReference | null {\n if (text.length < 4 || !text.startsWith('@{') || !text.endsWith('}')) {\n return null;\n }\n\n const blocks = text\n .slice(2, text.length - 1)\n .split('|')\n .map(block => block.trim());\n if (blocks.length !== 2 || blocks[0] === '' || blocks[0].startsWith('0')) {\n return null;\n }\n\n if (blocks[0].startsWith('-') || /^[1-9]/.test(blocks[0])) {\n const offset = Number(blocks[0]);\n if (!Number.isInteger(offset) || offset === 0 || blocks[1] === '') {\n return null;\n }\n return {\n type: ReferenceType.SYNTACTIC,\n data: { offset, nominal: blocks[1] }\n };\n }\n\n return {\n type: ReferenceType.ENTITY,\n data: { entity: blocks[0], tags: parseGrammemes(blocks[1].replaceAll(' ', '')) }\n };\n}\n\n/** Apply alias mapping to entity references in terminological text. */\nexport function applyEntityReferenceMapping(text: string, mapping: Record<string, string>): string {\n if (text === '' || Object.keys(mapping).length === 0) {\n return text;\n }\n\n let posInput = 0;\n let output = '';\n for (const segment of text.matchAll(ENTITY_REFERENCE_PATTERN)) {\n const entity = segment[1];\n const entityKey = entity.trim();\n const start = segment.index ?? 0;\n if (entityKey in mapping) {\n output += text.substring(posInput, start + 2);\n output += mapping[entityKey];\n output += text.substring(start + 2 + entity.length, start + segment[0].length);\n posInput = start + segment[0].length;\n }\n }\n output += text.substring(posInput);\n return output;\n}\n\n/** Extracts unique entity aliases referenced by text. */\nexport function extractEntities(text: string): string[] {\n const result: string[] = [];\n for (const segment of text.matchAll(ENTITY_REFERENCE_PATTERN)) {\n const entity = segment[1].trim();\n if (!result.includes(entity)) {\n result.push(entity);\n }\n }\n return result;\n}\n\n/** Resolves text references using nominal terms and optional manually edited forms. */\nexport function resolveTextReferences(text: string, context: TermContext): string {\n const references = parseReferences(text);\n if (references.length === 0) {\n return text;\n }\n\n for (const ref of references) {\n if (ref.ref.type === ReferenceType.ENTITY) {\n ref.resolved = resolveEntity(ref.ref.data, context);\n }\n }\n references.forEach((ref, index) => {\n if (ref.ref.type === ReferenceType.SYNTACTIC) {\n ref.resolved = resolveSyntactic(ref.ref.data, index, references);\n }\n });\n\n let posInput = 0;\n let output = '';\n for (const ref of references) {\n output += text.substring(posInput, ref.posInput.from);\n output += ref.resolved;\n posInput = ref.posInput.to;\n }\n output += text.substring(posInput);\n return output;\n}\n\n/** Transforms {@link IReference} to string representation. */\nexport function referenceToString(ref: IReference): string {\n switch (ref.type) {\n case ReferenceType.ENTITY: {\n const entity = ref.data;\n return `@{${entity.entity}|${entity.tags.join(',')}}`;\n }\n case ReferenceType.SYNTACTIC: {\n const syntactic = ref.data;\n return `@{${syntactic.offset}|${syntactic.nominal}}`;\n }\n }\n}\n\n// ===== Internals =======\n\n/** Compares {@link Grammeme} based on Grammeme enum and alpha order for strings. */\nfunction grammemeCompare(left: Grammeme, right: Grammeme): number {\n const indexLeft = Object.values(Grammeme).findIndex(gram => gram === left);\n const indexRight = Object.values(Grammeme).findIndex(gram => gram === right);\n if (indexLeft === -1 && indexRight === -1) {\n return left.localeCompare(right);\n } else if (indexLeft === -1 && indexRight !== -1) {\n return 1;\n } else if (indexLeft !== -1 && indexRight === -1) {\n return -1;\n } else {\n return indexLeft - indexRight;\n }\n}\n\nfunction parseReferences(text: string): ResolvedReference[] {\n const result: ResolvedReference[] = [];\n for (const segment of text.matchAll(REFERENCE_PATTERN)) {\n const ref = parseReference(segment[0]);\n if (ref) {\n result.push({\n ref,\n resolved: '',\n posInput: { from: segment.index ?? 0, to: (segment.index ?? 0) + segment[0].length }\n });\n }\n }\n return result;\n}\n\nfunction resolveEntity(ref: EntityReference, context: TermContext): string {\n const entity = context[ref.entity];\n if (!entity) {\n return `!Неизвестная сущность: ${ref.entity}!`;\n }\n\n const resolved = getEntityForm(entity, ref.tags);\n return resolved === '' ? `!Отсутствует термин: ${ref.entity}!` : resolved;\n}\n\nfunction getEntityForm(entity: TermContextItem, grams: Grammeme[]): string {\n if (grams.length === 0) {\n return entity.nominal;\n }\n\n const manual = entity.forms?.find(form => matchGrams(grams, form.grams));\n return manual?.text ?? entity.nominal;\n}\n\nfunction matchGrams(query: Grammeme[], candidate: Grammeme[]): boolean {\n for (const gram of candidate) {\n if (!query.includes(gram)) {\n return false;\n }\n }\n return true;\n}\n\nfunction resolveSyntactic(ref: SyntacticReference, index: number, references: ResolvedReference[]): string {\n const master = findSyntacticMaster(ref.offset, index, references);\n if (!master) {\n return `!Некорректное смещение: ${ref.offset}!`;\n }\n return ref.nominal;\n}\n\nfunction findSyntacticMaster(\n offset: number,\n index: number,\n references: ResolvedReference[]\n): ResolvedReference | undefined {\n if (offset > 0) {\n let position = index + 1;\n let left = offset;\n while (position < references.length) {\n if (references[position].ref.type === ReferenceType.ENTITY) {\n if (left === 1) {\n return references[position];\n }\n left -= 1;\n }\n position += 1;\n }\n } else {\n let position = index - 1;\n let left = offset;\n while (position >= 0) {\n if (references[position].ref.type === ReferenceType.ENTITY) {\n if (left === -1) {\n return references[position];\n }\n left += 1;\n }\n position -= 1;\n }\n }\n return undefined;\n}\n"],"mappings":"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"}
@@ -51,10 +51,13 @@ interface SyntacticReference {
51
51
  nominal: string;
52
52
  }
53
53
  /** Represents abstract reference data. */
54
- interface IReference {
55
- type: ReferenceType;
56
- data: EntityReference | SyntacticReference;
57
- }
54
+ type IReference = {
55
+ type: typeof ReferenceType.ENTITY;
56
+ data: EntityReference;
57
+ } | {
58
+ type: typeof ReferenceType.SYNTACTIC;
59
+ data: SyntacticReference;
60
+ };
58
61
  //#endregion
59
62
  export { Case, EntityReference, Grammeme, IReference, Plurality, ReferenceType, SyntacticReference, TermContext, TermContextItem, WordForm, supportedGrammemes };
60
63
  //# sourceMappingURL=language.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"language.js","names":[],"sources":["../../src/cctext/language.ts"],"sourcesContent":["/**\n * Module: Natural language model declarations.\n */\n\n/** Represents single unit of language Morphology. */\n// prettier-ignore\nexport const Grammeme = {\n // Число\n sing: 'sing', plur: 'plur',\n\n // Падеж\n nomn: 'nomn', gent: 'gent', datv: 'datv',\n accs: 'accs', ablt: 'ablt', loct: 'loct',\n} as const;\nexport type Grammeme = (typeof Grammeme)[keyof typeof Grammeme];\n\n/** Represents case language concept. */\nexport const Case: Grammeme[] = [\n Grammeme.nomn,\n Grammeme.gent,\n Grammeme.datv,\n Grammeme.accs,\n Grammeme.ablt,\n Grammeme.loct\n] as const;\n\n/** Represents plurality language concept. */\nexport const Plurality: Grammeme[] = [Grammeme.sing, Grammeme.plur] as const;\n\n/** Represents specific wordform attached to {@link Grammeme}s. */\nexport interface WordForm {\n text: string;\n grams: Grammeme[];\n}\n\n/** Represents a term available for text reference resolution. */\nexport interface TermContextItem {\n nominal: string;\n forms?: WordForm[];\n}\n\n/** Represents term lookup context keyed by entity alias. */\nexport type TermContext = Record<string, TermContextItem>;\n\n/**\n * Represents list of {@link Grammeme}s available in reference construction.\n */\n// prettier-ignore\nexport const supportedGrammemes = [\n Grammeme.sing, Grammeme.plur,\n Grammeme.nomn, Grammeme.gent, Grammeme.datv,\n Grammeme.accs, Grammeme.ablt, Grammeme.loct,\n] as const;\n\n// ====== Reference resolution =====\n\n/** Represents text reference type. */\nexport const ReferenceType = {\n ENTITY: 'entity',\n SYNTACTIC: 'syntax'\n} as const;\nexport type ReferenceType = (typeof ReferenceType)[keyof typeof ReferenceType];\n\n/** Represents entity reference payload. */\nexport interface EntityReference {\n entity: string;\n tags: Grammeme[];\n}\n\n/** Represents syntactic reference payload. */\nexport interface SyntacticReference {\n offset: number;\n nominal: string;\n}\n\n/** Represents abstract reference data. */\nexport interface IReference {\n type: ReferenceType;\n data: EntityReference | SyntacticReference;\n}\n"],"mappings":";;;;;AAMA,MAAa,WAAW;CAEtB,MAAM;CAAQ,MAAM;CAGpB,MAAM;CAAQ,MAAM;CAAQ,MAAM;CAClC,MAAM;CAAQ,MAAM;CAAQ,MAAM;AACpC;;AAIA,MAAa,OAAmB;CAC9B,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;AACX;;AAGA,MAAa,YAAwB,CAAC,SAAS,MAAM,SAAS,IAAI;;;;AAqBlE,MAAa,qBAAqB;CAChC,SAAS;CAAM,SAAS;CACxB,SAAS;CAAM,SAAS;CAAM,SAAS;CACvC,SAAS;CAAM,SAAS;CAAM,SAAS;AACzC;;AAKA,MAAa,gBAAgB;CAC3B,QAAQ;CACR,WAAW;AACb"}
1
+ {"version":3,"file":"language.js","names":[],"sources":["../../src/cctext/language.ts"],"sourcesContent":["/**\n * Module: Natural language model declarations.\n */\n\n/** Represents single unit of language Morphology. */\n// prettier-ignore\nexport const Grammeme = {\n // Число\n sing: 'sing', plur: 'plur',\n\n // Падеж\n nomn: 'nomn', gent: 'gent', datv: 'datv',\n accs: 'accs', ablt: 'ablt', loct: 'loct',\n} as const;\nexport type Grammeme = (typeof Grammeme)[keyof typeof Grammeme];\n\n/** Represents case language concept. */\nexport const Case: Grammeme[] = [\n Grammeme.nomn,\n Grammeme.gent,\n Grammeme.datv,\n Grammeme.accs,\n Grammeme.ablt,\n Grammeme.loct\n] as const;\n\n/** Represents plurality language concept. */\nexport const Plurality: Grammeme[] = [Grammeme.sing, Grammeme.plur] as const;\n\n/** Represents specific wordform attached to {@link Grammeme}s. */\nexport interface WordForm {\n text: string;\n grams: Grammeme[];\n}\n\n/** Represents a term available for text reference resolution. */\nexport interface TermContextItem {\n nominal: string;\n forms?: WordForm[];\n}\n\n/** Represents term lookup context keyed by entity alias. */\nexport type TermContext = Record<string, TermContextItem>;\n\n/**\n * Represents list of {@link Grammeme}s available in reference construction.\n */\n// prettier-ignore\nexport const supportedGrammemes = [\n Grammeme.sing, Grammeme.plur,\n Grammeme.nomn, Grammeme.gent, Grammeme.datv,\n Grammeme.accs, Grammeme.ablt, Grammeme.loct,\n] as const;\n\n// ====== Reference resolution =====\n\n/** Represents text reference type. */\nexport const ReferenceType = {\n ENTITY: 'entity',\n SYNTACTIC: 'syntax'\n} as const;\nexport type ReferenceType = (typeof ReferenceType)[keyof typeof ReferenceType];\n\n/** Represents entity reference payload. */\nexport interface EntityReference {\n entity: string;\n tags: Grammeme[];\n}\n\n/** Represents syntactic reference payload. */\nexport interface SyntacticReference {\n offset: number;\n nominal: string;\n}\n\n/** Represents abstract reference data. */\nexport type IReference =\n | { type: typeof ReferenceType.ENTITY; data: EntityReference }\n | { type: typeof ReferenceType.SYNTACTIC; data: SyntacticReference };\n"],"mappings":";;;;;AAMA,MAAa,WAAW;CAEtB,MAAM;CAAQ,MAAM;CAGpB,MAAM;CAAQ,MAAM;CAAQ,MAAM;CAClC,MAAM;CAAQ,MAAM;CAAQ,MAAM;AACpC;;AAIA,MAAa,OAAmB;CAC9B,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;AACX;;AAGA,MAAa,YAAwB,CAAC,SAAS,MAAM,SAAS,IAAI;;;;AAqBlE,MAAa,qBAAqB;CAChC,SAAS;CAAM,SAAS;CACxB,SAAS;CAAM,SAAS;CAAM,SAAS;CACvC,SAAS;CAAM,SAAS;CAAM,SAAS;AACzC;;AAKA,MAAa,gBAAgB;CAC3B,QAAQ;CACR,WAAW;AACb"}
@@ -3,7 +3,7 @@ import { labelType } from "./rslang/labels.js";
3
3
  import { applyEntityReferenceMapping } from "./cctext/language-api.js";
4
4
  import { RSErrorCode } from "./rslang/error.js";
5
5
  import { generateExpressionFromAst } from "./rslang/parser/expression-generator.js";
6
- import { applyAliasMapping, applyTypificationMapping, parseRSLangExpression } from "./rslang/api.js";
6
+ import { applyAliasMapping, applyTypificationMapping, extractGlobals, parseRSLangExpression } from "./rslang/api.js";
7
7
  import { applyPath } from "./rslang/semantic/typification-api.js";
8
8
  import { ValueClass } from "./rslang/semantic/value-class.js";
9
9
  import "./rslang/index.js";
@@ -281,7 +281,6 @@ function typeClassForCstType(cstType) {
281
281
  function canHaveFormalDefinition(cstType) {
282
282
  return cstType !== CstType.BASE && cstType !== CstType.CONSTANT;
283
283
  }
284
- /** Analyze expression for {@link RSForm}. */
285
284
  function getAnalysisFor(expression, cstType, schema, alias) {
286
285
  if (!canHaveFormalDefinition(cstType)) {
287
286
  if (expression.trim().length === 0) {
@@ -307,10 +306,79 @@ function getAnalysisFor(expression, cstType, schema, alias) {
307
306
  ast: null
308
307
  };
309
308
  }
310
- return schema.analyzer.checkFull(expression, {
309
+ return refineAnalysisForDependencyCycles(schema.analyzer.checkFull(expression, {
311
310
  expected: typeClassForCstType(cstType),
312
311
  isDomain: cstType === CstType.STRUCTURED
312
+ }), expression, schema, alias);
313
+ }
314
+ /**
315
+ * When an untyped / bare F·P reference names a constituent on a definition cycle with the
316
+ * current expression, replace that error with {@link RSDiagnosticCode.schemaDependencyCycle}.
317
+ *
318
+ * Covers both semantic `globalNotTyped` (e.g. `F3[σ]`) and parser `globalFuncWithoutArgs`
319
+ * (bare `P1` / `F1` treated as a call without arguments).
320
+ *
321
+ * Uses a draft dependency graph: incoming edges of the edited constituent are taken from
322
+ * `expression`, so Check works before Save.
323
+ */
324
+ function refineAnalysisForDependencyCycles(analysis, expression, schema, alias) {
325
+ if (analysis.errors.length === 0 || !schema.graph) return analysis;
326
+ const currentId = alias ? schema.cstByAlias.get(alias)?.id : void 0;
327
+ const expressionGlobals = extractGlobals(expression);
328
+ const draftGraph = currentId !== void 0 ? buildDraftDependencyGraph(schema, currentId, expressionGlobals) : schema.graph;
329
+ let changed = false;
330
+ const errors = analysis.errors.map((error) => {
331
+ if (error.code !== RSErrorCode.globalNotTyped && error.code !== RSErrorCode.globalFuncWithoutArgs) return error;
332
+ const missingAlias = error.params?.[0];
333
+ if (!missingAlias) return error;
334
+ const missing = schema.cstByAlias.get(missingAlias);
335
+ if (!missing) return error;
336
+ const cycleText = resolveDependencyCycleText(schema, draftGraph, currentId, missing.id);
337
+ if (!cycleText) return error;
338
+ changed = true;
339
+ return {
340
+ ...error,
341
+ code: RSErrorCode.schemaDependencyCycle,
342
+ params: [cycleText]
343
+ };
313
344
  });
345
+ return changed ? {
346
+ ...analysis,
347
+ errors
348
+ } : analysis;
349
+ }
350
+ /**
351
+ * Clone the schema graph and replace dependencies of `currentId` with globals from the
352
+ * expression under check (unsaved draft).
353
+ */
354
+ function buildDraftDependencyGraph(schema, currentId, expressionGlobals) {
355
+ const graph = schema.graph.clone();
356
+ const node = graph.at(currentId);
357
+ if (node) for (const parentId of [...node.inputs]) graph.removeEdge(parentId, currentId);
358
+ else graph.addNode(currentId);
359
+ for (const globalAlias of expressionGlobals) {
360
+ const source = schema.cstByAlias.get(globalAlias);
361
+ if (source) graph.addEdge(source.id, currentId);
362
+ }
363
+ return graph;
364
+ }
365
+ /**
366
+ * Resolve formatted cycle text for an untyped global reference in analysis.
367
+ *
368
+ * Uses `graph` (often a draft graph built from the expression under check). When
369
+ * `currentId` is omitted, falls back to schema diagnostics on `missingId`.
370
+ *
371
+ * @returns Closed alias path such as `F3 → P1 → F3`, or `null` when no shared cycle exists.
372
+ */
373
+ function resolveDependencyCycleText(schema, graph, currentId, missingId) {
374
+ for (const cycle of graph.findCycles()) {
375
+ const members = new Set(cycle);
376
+ if (!members.has(missingId)) continue;
377
+ if (currentId !== void 0 && !members.has(currentId)) continue;
378
+ return formatDependencyCycle(cycle.map((id) => schema.cstByID.get(id)?.alias ?? String(id)));
379
+ }
380
+ if (currentId === void 0) return (schema.cstByID.get(missingId)?.diagnostics.find((item) => item.code === RSDiagnosticCode.schemaDependencyCycle))?.params?.[0] ?? null;
381
+ return null;
314
382
  }
315
383
  /** Calculate statistics for {@link RSForm}. */
316
384
  function calculateSchemaStats(target) {
@@ -345,8 +413,10 @@ function calculateSchemaStats(target) {
345
413
  }
346
414
  /** Finds {@link Constituenta} by structure path. */
347
415
  function findCstByStructure(schema, target, path) {
348
- for (const cst of schema.items) if (cst.spawner === target.id && cst.spawner_path) {
349
- if (cst.spawner_path.length === path.length && cst.spawner_path.every((v, i) => v === path[i])) return cst;
416
+ for (const cst of schema.items) {
417
+ if (!cst.spawner_path) continue;
418
+ const absolutePath = resolveAbsoluteSpawnerPath(schema, cst, target.id);
419
+ if (absolutePath?.length === path.length && absolutePath.every((v, i) => v === path[i])) return cst;
350
420
  }
351
421
  return null;
352
422
  }
@@ -391,6 +461,24 @@ function isMissingConvention(cst) {
391
461
  }
392
462
  return false;
393
463
  }
464
+ function resolveAbsoluteSpawnerPath(schema, cst, targetId) {
465
+ if (!cst.spawner_path || cst.spawner == null) return null;
466
+ if (cst.spawner === targetId) return cst.spawner_path;
467
+ const byId = schema.cstByID ?? new Map(schema.items.map((item) => [item.id, item]));
468
+ const visited = /* @__PURE__ */ new Set([cst.id]);
469
+ let currentPath = [...cst.spawner_path];
470
+ let currentSpawnerId = cst.spawner;
471
+ while (currentSpawnerId != null) {
472
+ if (currentSpawnerId === targetId) return currentPath;
473
+ if (visited.has(currentSpawnerId)) return null;
474
+ visited.add(currentSpawnerId);
475
+ const parent = byId.get(currentSpawnerId);
476
+ if (!parent?.spawner_path || parent.spawner == null) return null;
477
+ currentPath = [...parent.spawner_path, ...currentPath];
478
+ currentSpawnerId = parent.spawner;
479
+ }
480
+ return null;
481
+ }
394
482
  /** Evaluate if {@link Typification} can be used to produce structure. */
395
483
  function typeCanProduceStructure(type) {
396
484
  if (type.typeID === TypeID.basic || type.typeID === TypeID.integer || type.typeID === TypeID.anyTypification) return false;
@@ -399,9 +487,6 @@ function typeCanProduceStructure(type) {
399
487
  }
400
488
  //#endregion
401
489
  //#region src/library/diagnostics.ts
402
- /**
403
- * Module: Compact diagnostics for constituents (schema and model issue codes).
404
- */
405
490
  /** Origin of a diagnostic. */
406
491
  const DiagnosticKind = {
407
492
  EXPRESSION: "expression",
@@ -415,6 +500,7 @@ const RSDiagnosticCode = {
415
500
  schemaMissingConvention: 35075,
416
501
  schemaMissingTerm: 35076,
417
502
  schemaTypeMismatch: 35077,
503
+ schemaDependencyCycle: 35078,
418
504
  modelEmpty: 35088,
419
505
  modelAxiomFalse: 35089,
420
506
  modelInvalidData: 35090,
@@ -425,15 +511,19 @@ function hasCstDiagnostic(cst, code) {
425
511
  return cst.diagnostics?.some((item) => item.code === code) ?? false;
426
512
  }
427
513
  /**
428
- * Populate `diagnostics` on each constituent (homonyms, duplicates, metadata, type mismatch).
514
+ * Populate `diagnostics` on each constituent (homonyms, duplicates, metadata, type mismatch, cycles).
429
515
  *
430
516
  * Requires normalized formal definitions for duplicate detection (same as Portal loader).
517
+ * When `graph` is provided, dependency cycles are reported as {@link RSDiagnosticCode.schemaDependencyCycle}.
431
518
  */
432
- function assignSchemaDiagnostics(items, resolveAlias, normalizedDefinitions) {
519
+ function assignSchemaDiagnostics(items, resolveAlias, normalizedDefinitions, graph) {
433
520
  const homonymById = detectHomonymDiagnostics(items, resolveAlias);
434
521
  const duplicateById = detectFormalDuplicateDiagnostics(items, resolveAlias, normalizedDefinitions);
522
+ const cycleById = graph ? detectDependencyCycleDiagnostics(graph, resolveAlias) : /* @__PURE__ */ new Map();
435
523
  for (const cst of items) {
436
524
  const diagnostics = [];
525
+ const cycle = cycleById.get(cst.id);
526
+ if (cycle) diagnostics.push(cycle);
437
527
  const homonym = homonymById.get(cst.id);
438
528
  if (homonym) diagnostics.push(homonym);
439
529
  const duplicate = duplicateById.get(cst.id);
@@ -442,6 +532,56 @@ function assignSchemaDiagnostics(items, resolveAlias, normalizedDefinitions) {
442
532
  cst.diagnostics = diagnostics;
443
533
  }
444
534
  }
535
+ /**
536
+ * Format a closed dependency cycle as `A → B → A`.
537
+ *
538
+ * Rotates the path to start at the lexicographically smallest alias so the same
539
+ * cycle always renders identically regardless of graph traversal order.
540
+ *
541
+ * @param aliases Closed or open alias path from {@link Graph.findCycles}.
542
+ * @returns Human-readable cycle text joined with ` → `.
543
+ */
544
+ function formatDependencyCycle(aliases) {
545
+ if (aliases.length < 2) return aliases.join(" → ");
546
+ const closed = aliases[0] === aliases[aliases.length - 1];
547
+ const open = closed ? aliases.slice(0, -1) : [...aliases];
548
+ if (open.length === 0) return aliases.join(" → ");
549
+ let bestStart = 0;
550
+ for (let index = 1; index < open.length; index++) {
551
+ const alias = open[index];
552
+ const bestAlias = open[bestStart];
553
+ if (alias !== void 0 && bestAlias !== void 0 && alias < bestAlias) bestStart = index;
554
+ }
555
+ const rotated = [...open.slice(bestStart), ...open.slice(0, bestStart)];
556
+ if (closed) {
557
+ const first = rotated[0];
558
+ if (first !== void 0) rotated.push(first);
559
+ }
560
+ return rotated.join(" → ");
561
+ }
562
+ /**
563
+ * Map each node id that participates in a dependency cycle to a schema diagnostic.
564
+ *
565
+ * `params[0]` is the closed cycle path (`F3 → P1 → F3`).
566
+ *
567
+ * @param graph Constituent dependency graph (provider → dependent edges).
568
+ * @param resolveAlias Maps node id to display alias for cycle formatting.
569
+ * @returns One {@link CstDiagnostic} per node on each cyclic SCC.
570
+ */
571
+ function detectDependencyCycleDiagnostics(graph, resolveAlias) {
572
+ const result = /* @__PURE__ */ new Map();
573
+ for (const cycle of graph.findCycles()) {
574
+ if (cycle.length < 2) continue;
575
+ const cycleText = formatDependencyCycle(cycle.map((id) => resolveAlias(id)));
576
+ const memberIds = new Set(cycle);
577
+ for (const id of memberIds) result.set(id, {
578
+ kind: DiagnosticKind.SCHEMA,
579
+ code: RSDiagnosticCode.schemaDependencyCycle,
580
+ params: [cycleText]
581
+ });
582
+ }
583
+ return result;
584
+ }
445
585
  /** Map a model evaluation status to a compact diagnostic, or `null` when not an issue. */
446
586
  function modelStatusCstDiagnostic(status, cstType) {
447
587
  const code = modelStatusToCode(status, cstType);
@@ -513,6 +653,6 @@ function modelStatusToCode(status, cstType) {
513
653
  }
514
654
  }
515
655
  //#endregion
516
- export { isTemplateCst as A, inferTemplate as C, isFunctional as D, isBasicConcept as E, validateAliasFormat as F, validateNewAlias as I, removeAliasReference as M, sortItemsForInlineSynthesis as N, isLogical as O, typeClassForCstType as P, inferStatus as S, isBaseSet as T, getCstTypePrefix as _, modelStatusCstDiagnostic as a, inferClass as b, applyMappingToConstituents as c, canHaveFormalDefinition as d, canHaveManualTypification as f, getAnalysisFor as g, generateAlias as h, hasCstDiagnostic as i, normalizeExpression as j, isSchemaIssue as k, argumentValuesToMapping as l, findCstByStructure as m, RSDiagnosticCode as n, addAliasReference as o, canProduceStructure as p, assignSchemaDiagnostics as r, applyFilterCategory as s, DiagnosticKind as t, calculateSchemaStats as u, getStructureName as v, inferTemplatedType as w, inferNewSpawnPosition as x, guessCstType as y };
656
+ export { isLogical as A, inferNewSpawnPosition as C, isBaseSet as D, inferTemplatedType as E, removeAliasReference as F, sortItemsForInlineSynthesis as I, typeClassForCstType as L, isTemplateCst as M, normalizeExpression as N, isBasicConcept as O, refineAnalysisForDependencyCycles as P, validateAliasFormat as R, inferClass as S, inferTemplate as T, generateAlias as _, formatDependencyCycle as a, getStructureName as b, addAliasReference as c, argumentValuesToMapping as d, calculateSchemaStats as f, findCstByStructure as g, canProduceStructure as h, detectDependencyCycleDiagnostics as i, isSchemaIssue as j, isFunctional as k, applyFilterCategory as l, canHaveManualTypification as m, RSDiagnosticCode as n, hasCstDiagnostic as o, canHaveFormalDefinition as p, assignSchemaDiagnostics as r, modelStatusCstDiagnostic as s, DiagnosticKind as t, applyMappingToConstituents as u, getAnalysisFor as v, inferStatus as w, guessCstType as x, getCstTypePrefix as y, validateNewAlias as z };
517
657
 
518
- //# sourceMappingURL=diagnostics-D7SiHAwW.js.map
658
+ //# sourceMappingURL=diagnostics-CY9sKggB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics-CY9sKggB.js","names":[],"sources":["../src/library/rsform-api.ts","../src/library/diagnostics.ts"],"sourcesContent":["/**\n * Module: API for formal representation for systems of concepts.\n */\n\nimport { applyEntityReferenceMapping } from '../cctext/language-api';\nimport { type Graph } from '../graph';\nimport { type AstNode } from '../parsing';\nimport {\n type AnalysisFull,\n RSErrorCode,\n TypeClass,\n TypeID,\n type TypePath,\n type Typification,\n ValueClass\n} from '../rslang';\nimport {\n type AliasMapping,\n applyAliasMapping,\n applyTypificationMapping,\n extractGlobals,\n generateExpressionFromAst,\n parseRSLangExpression\n} from '../rslang/api';\nimport { basic, bool, constant, type EchelonFunctional, isTypification } from '../rslang/semantic/typification';\nimport { applyPath } from '../rslang/semantic/typification-api';\n\nimport { formatDependencyCycle, hasCstDiagnostic, RSDiagnosticCode } from './diagnostics';\nimport { type LibraryItem } from './library';\nimport {\n type ArgumentValue,\n type Constituenta,\n CstClass,\n CstStatus,\n CstType,\n type RSForm,\n type RSFormStats\n} from './rsform';\n\n/** Record of {@link CstType} prefixes. */\nconst CST_TYPE_PREFIX: Record<CstType, string> = {\n [CstType.NOMINAL]: 'N',\n [CstType.BASE]: 'X',\n [CstType.CONSTANT]: 'C',\n [CstType.STRUCTURED]: 'S',\n [CstType.AXIOM]: 'A',\n [CstType.TERM]: 'D',\n [CstType.FUNCTION]: 'F',\n [CstType.PREDICATE]: 'P',\n [CstType.STATEMENT]: 'T'\n};\n\n/** Record of {@link CstType} to {@link CstClass} mapping. */\nconst CST_TYPE_TO_CLASS: Record<CstType, CstClass> = {\n [CstType.NOMINAL]: CstClass.NOMINAL,\n [CstType.BASE]: CstClass.BASIC,\n [CstType.CONSTANT]: CstClass.BASIC,\n [CstType.STRUCTURED]: CstClass.BASIC,\n [CstType.TERM]: CstClass.DERIVED,\n [CstType.FUNCTION]: CstClass.DERIVED,\n [CstType.AXIOM]: CstClass.STATEMENT,\n [CstType.PREDICATE]: CstClass.DERIVED,\n [CstType.STATEMENT]: CstClass.STATEMENT\n};\n\n/** Checks if {@link Constituenta} is a schema issue. */\nexport function isSchemaIssue(cst: Constituenta): boolean {\n return (\n (cst.diagnostics?.length ?? 0) > 0 || cst.status === CstStatus.INCORRECT || cst.status === CstStatus.INCALCULABLE\n );\n}\n\n/** Infers the status of an expression based on parsing and value information. */\nexport function inferStatus(parse: boolean, value?: ValueClass | null): CstStatus {\n if (!parse) {\n return CstStatus.INCORRECT;\n }\n if (value === null) {\n return CstStatus.INCALCULABLE;\n }\n if (value === ValueClass.PROPERTY) {\n return CstStatus.PROPERTY;\n }\n return CstStatus.VERIFIED;\n}\n\n/** Infers type of constituent for a given template and arguments. */\nexport function inferTemplatedType(templateType: CstType, args: ArgumentValue[]): CstType {\n if (args.length === 0 || args.some(arg => !arg.value)) {\n return templateType;\n } else if (templateType === CstType.PREDICATE) {\n return CstType.AXIOM;\n } else {\n return CstType.TERM;\n }\n}\n\n/** Build alias mapping from template argument bindings. */\nexport function argumentValuesToMapping(args: ArgumentValue[]): AliasMapping {\n const mapping: AliasMapping = {};\n for (const arg of args) {\n if (arg.value) {\n mapping[arg.alias] = arg.value;\n }\n }\n return mapping;\n}\n\n/**\n * Normalize a RSLang expression for duplicate comparison.\n *\n * Returns `null` for basic concepts without formal definitions. Pass a pre-parsed `ast`\n * when the expression was already analyzed to avoid re-parsing.\n */\nexport function normalizeExpression(expression: string, cst_type: CstType, ast?: AstNode | null): string | null {\n if (isBasicConcept(cst_type) && cst_type !== CstType.AXIOM) {\n return null;\n }\n const parsed = ast ?? parseRSLangExpression(expression);\n let normalized = '';\n if (parsed && !parsed.hasError) {\n normalized = generateExpressionFromAst(parsed, { normalize: true });\n } else {\n normalized = expression;\n }\n normalized = normalized.replace(/\\s+/g, '');\n return normalized === '' ? null : normalized;\n}\n\n/** Checks if given expression is a template. */\nexport function inferTemplate(expression: string): boolean {\n const match = expression.match(/R\\d+/g);\n return (match && match?.length > 0) ?? false;\n}\n\n/** Infers the {@link CstClass} based on the provided {@link CstType} and template status. */\nexport function inferClass(type: CstType, isTemplate: boolean = false): CstClass {\n if (isTemplate) {\n return CstClass.TEMPLATE;\n }\n return CST_TYPE_TO_CLASS[type];\n}\n\n/** Check if {@link Constituenta} is a template or a category. */\nexport function isTemplateCst(cst: Constituenta): boolean {\n return cst.cst_type === CstType.FUNCTION || cst.cst_type === CstType.PREDICATE || cst.cst_type === CstType.STATEMENT;\n}\n\n/** Apply filter based on start {@link Constituenta} type. */\nexport function applyFilterCategory(start: Constituenta, items: Constituenta[]): Constituenta[] {\n const startIndex = items.indexOf(start);\n if (startIndex === -1) {\n return [];\n }\n const nextCategoryIndex = items.findIndex((cst, index) => index > startIndex && cst.cst_type === CstType.STATEMENT);\n\n return items.filter((_, index) => index >= startIndex && (nextCategoryIndex === -1 || index < nextCategoryIndex));\n}\n\n/** Prefix for alias indicating {@link CstType}. */\nexport function getCstTypePrefix(type: CstType): string {\n return CST_TYPE_PREFIX[type];\n}\n\n/** Guess {@link CstType} from user input hint. */\nexport function guessCstType(hint: string): CstType | null {\n if (hint.length !== 1) {\n return null;\n }\n for (const [type, prefix] of Object.entries(CST_TYPE_PREFIX)) {\n if (hint === prefix) {\n return type as CstType;\n }\n }\n return null;\n}\n\n/** Evaluate if {@link CstType} is basic concept. */\nexport function isBasicConcept(type: CstType): boolean {\n switch (type) {\n case CstType.NOMINAL:\n case CstType.BASE:\n case CstType.CONSTANT:\n case CstType.STRUCTURED:\n case CstType.AXIOM:\n return true;\n\n case CstType.TERM:\n case CstType.FUNCTION:\n case CstType.PREDICATE:\n case CstType.STATEMENT:\n return false;\n }\n}\n\n/** Evaluate if {@link CstType} is base set or constant set. */\nexport function isBaseSet(type: CstType): boolean {\n switch (type) {\n case CstType.BASE:\n case CstType.CONSTANT:\n return true;\n\n case CstType.NOMINAL:\n case CstType.STRUCTURED:\n case CstType.AXIOM:\n case CstType.TERM:\n case CstType.FUNCTION:\n case CstType.PREDICATE:\n case CstType.STATEMENT:\n return false;\n }\n}\n\n/** Evaluate if {@link CstType} is a function. */\nexport function isFunctional(type: CstType): boolean {\n switch (type) {\n case CstType.FUNCTION:\n case CstType.PREDICATE:\n return true;\n\n case CstType.NOMINAL:\n case CstType.BASE:\n case CstType.CONSTANT:\n case CstType.STRUCTURED:\n case CstType.AXIOM:\n case CstType.TERM:\n case CstType.STATEMENT:\n return false;\n }\n}\n\n/** Evaluate if {@link CstType} is logical. */\nexport function isLogical(type: CstType): boolean {\n switch (type) {\n case CstType.AXIOM:\n case CstType.STATEMENT:\n return true;\n\n case CstType.NOMINAL:\n case CstType.BASE:\n case CstType.CONSTANT:\n case CstType.STRUCTURED:\n case CstType.TERM:\n case CstType.FUNCTION:\n case CstType.PREDICATE:\n return false;\n }\n}\n\n/** Evaluate if {@link Constituenta} can be used produce structure. */\nexport function canProduceStructure(cst: Constituenta): boolean {\n switch (cst.cst_type) {\n case CstType.NOMINAL:\n case CstType.BASE:\n case CstType.CONSTANT:\n case CstType.AXIOM:\n case CstType.STATEMENT:\n case CstType.PREDICATE:\n return false;\n }\n if (!cst.effectiveType) {\n return false;\n }\n if (cst.cst_type === CstType.FUNCTION) {\n const result = (cst.effectiveType as EchelonFunctional).result;\n return typeCanProduceStructure(result);\n }\n if (!isTypification(cst.effectiveType)) {\n return false;\n }\n return typeCanProduceStructure(cst.effectiveType as Typification);\n}\n\n/** Evaluate if {@link CstType} can have manual typification. */\nexport function canHaveManualTypification(type: CstType): boolean {\n switch (type) {\n case CstType.STRUCTURED:\n case CstType.TERM:\n case CstType.FUNCTION:\n case CstType.PREDICATE:\n return true;\n\n case CstType.NOMINAL:\n case CstType.BASE:\n case CstType.CONSTANT:\n case CstType.AXIOM:\n case CstType.STATEMENT:\n return false;\n }\n}\n\n/** Validate alias format for a given {@link CstType} (prefix + digits, min length 2). */\nexport function validateAliasFormat(alias: string, type: CstType): boolean {\n if (alias.length < 2) {\n return false;\n }\n const prefix = getCstTypePrefix(type);\n if (!alias.startsWith(prefix)) {\n return false;\n }\n if (!/^\\d+$/.exec(alias.substring(prefix.length))) {\n return false;\n }\n return true;\n}\n\n/** Validate new alias against {@link CstType} and {@link RSForm}. */\nexport function validateNewAlias(alias: string, type: CstType, schema: RSForm): boolean {\n if (!validateAliasFormat(alias, type)) {\n return false;\n }\n if (schema.cstByAlias.has(alias)) {\n return false;\n }\n return true;\n}\n\n/** Generate alias for new {@link Constituenta} of a given {@link CstType} for current {@link RSForm}. */\nexport function generateAlias(type: CstType, schema: RSForm, takenAliases: string[] = []): string {\n const prefix = getCstTypePrefix(type);\n if (schema.items.length <= 0) {\n return `${prefix}1`;\n }\n let index = schema.items.reduce((prev, cst, index) => {\n if (cst.cst_type !== type) {\n return prev;\n }\n index = Number(cst.alias.slice(1 - cst.alias.length)) + 1;\n return Math.max(prev, index);\n }, 1);\n let alias = `${prefix}${index}`;\n while (takenAliases.includes(alias)) {\n index = index + 1;\n alias = `${prefix}${index}`;\n }\n return alias;\n}\n\n/**\n * Id to pass as `insert_after` when creating a new {@link Constituenta} spawned by `targetId`:\n * after the last existing child of that spawner, or after the spawner itself if none.\n */\nexport function inferNewSpawnPosition(schema: RSForm, targetId: number): number {\n let last: number | null = null;\n for (const cst of schema.items) {\n if (cst.spawner === targetId) {\n last = cst.id;\n }\n }\n return last ?? targetId;\n}\n\n/** Sorts library items relevant for InlineSynthesis with specified {@link RSForm}. */\nexport function sortItemsForInlineSynthesis(receiver: RSForm, items: readonly LibraryItem[]): LibraryItem[] {\n const result = items.filter(item => item.location === receiver.location);\n for (const item of items) {\n if (item.visible && item.owner === item.owner && !result.includes(item)) {\n result.push(item);\n }\n }\n for (const item of items) {\n if (item.visible && !result.includes(item)) {\n result.push(item);\n }\n }\n for (const item of items) {\n if (!result.includes(item)) {\n result.push(item);\n }\n }\n return result;\n}\n\n/** Fields of {@link Constituenta} updated in place by alias mapping. */\nexport interface ConstituentaMappableFields {\n alias: string;\n definition_formal: string;\n typification_manual: string;\n term_raw: string;\n definition_raw: string;\n}\n\n/**\n * Remap formal expressions and terminological entity references in constituent fields.\n * Mutates each element of `items` in place; pass owned or cloned data only.\n */\nexport function applyMappingToConstituents<T extends ConstituentaMappableFields>(\n items: T[],\n mapping: AliasMapping,\n changeAliases: boolean\n): void {\n for (const cst of items) {\n if (changeAliases && cst.alias in mapping) {\n cst.alias = mapping[cst.alias];\n }\n cst.definition_formal = applyAliasMapping(cst.definition_formal, mapping);\n cst.typification_manual = applyTypificationMapping(cst.typification_manual, mapping);\n cst.term_raw = applyEntityReferenceMapping(cst.term_raw, mapping);\n cst.definition_raw = applyEntityReferenceMapping(cst.definition_raw, mapping);\n }\n}\n\n/** Remove alias from expression. */\nexport function removeAliasReference(expression: string, alias: string): string {\n const result = expression.replaceAll(new RegExp(`\\\\b${alias}\\\\b`, 'g'), 'DEL');\n return result === 'DEL' ? '' : result;\n}\n\n/** Add alias to expression. */\nexport function addAliasReference(expression: string, alias: string): string {\n return expression + ' ' + alias;\n}\n\n/** Returns expected {@link TypeClass} of formal definition for {@link CstType}. */\nexport function typeClassForCstType(cstType: CstType): TypeClass {\n switch (cstType) {\n case CstType.NOMINAL:\n case CstType.BASE:\n case CstType.CONSTANT:\n case CstType.STRUCTURED:\n case CstType.TERM:\n return TypeClass.typification;\n case CstType.FUNCTION:\n return TypeClass.function;\n case CstType.PREDICATE:\n return TypeClass.predicate;\n case CstType.AXIOM:\n case CstType.STATEMENT:\n return TypeClass.logic;\n }\n}\n\n/** Checks whether a constituenta type supports formal definitions. */\nexport function canHaveFormalDefinition(cstType: CstType): boolean {\n return cstType !== CstType.BASE && cstType !== CstType.CONSTANT;\n}\n\nexport function getAnalysisFor(expression: string, cstType: CstType, schema: RSForm, alias?: string): AnalysisFull {\n if (!canHaveFormalDefinition(cstType)) {\n if (expression.trim().length === 0) {\n const fallbackAlias = alias && alias.length > 0 ? alias : 'X0';\n const type = cstType === CstType.BASE ? bool(basic(fallbackAlias)) : bool(constant(fallbackAlias));\n return {\n success: true,\n type,\n valueClass: ValueClass.VALUE,\n errors: [],\n ast: null\n };\n }\n return {\n success: false,\n type: null,\n valueClass: null,\n errors: [\n {\n code: RSErrorCode.definitionNotAllowed,\n from: 0,\n to: Math.max(expression.length, 1),\n params: [cstType, alias ?? '']\n }\n ],\n ast: null\n };\n }\n const analysis = schema.analyzer.checkFull(expression, {\n expected: typeClassForCstType(cstType),\n isDomain: cstType === CstType.STRUCTURED\n });\n return refineAnalysisForDependencyCycles(analysis, expression, schema, alias);\n}\n\n/**\n * When an untyped / bare F·P reference names a constituent on a definition cycle with the\n * current expression, replace that error with {@link RSDiagnosticCode.schemaDependencyCycle}.\n *\n * Covers both semantic `globalNotTyped` (e.g. `F3[σ]`) and parser `globalFuncWithoutArgs`\n * (bare `P1` / `F1` treated as a call without arguments).\n *\n * Uses a draft dependency graph: incoming edges of the edited constituent are taken from\n * `expression`, so Check works before Save.\n */\nexport function refineAnalysisForDependencyCycles(\n analysis: AnalysisFull,\n expression: string,\n schema: RSForm,\n alias?: string\n): AnalysisFull {\n if (analysis.errors.length === 0 || !schema.graph) {\n return analysis;\n }\n\n const currentId = alias ? schema.cstByAlias.get(alias)?.id : undefined;\n const expressionGlobals = extractGlobals(expression);\n const draftGraph =\n currentId !== undefined ? buildDraftDependencyGraph(schema, currentId, expressionGlobals) : schema.graph;\n\n let changed = false;\n const errors = analysis.errors.map(error => {\n if (error.code !== RSErrorCode.globalNotTyped && error.code !== RSErrorCode.globalFuncWithoutArgs) {\n return error;\n }\n const missingAlias = error.params?.[0];\n if (!missingAlias) {\n return error;\n }\n const missing = schema.cstByAlias.get(missingAlias);\n if (!missing) {\n return error;\n }\n const cycleText = resolveDependencyCycleText(schema, draftGraph, currentId, missing.id);\n if (!cycleText) {\n return error;\n }\n changed = true;\n return {\n ...error,\n code: RSErrorCode.schemaDependencyCycle,\n params: [cycleText]\n };\n });\n return changed ? { ...analysis, errors } : analysis;\n}\n\n/**\n * Clone the schema graph and replace dependencies of `currentId` with globals from the\n * expression under check (unsaved draft).\n */\nfunction buildDraftDependencyGraph(schema: RSForm, currentId: number, expressionGlobals: ReadonlySet<string>): Graph {\n const graph = schema.graph.clone();\n const node = graph.at(currentId);\n if (node) {\n for (const parentId of [...node.inputs]) {\n graph.removeEdge(parentId, currentId);\n }\n } else {\n graph.addNode(currentId);\n }\n for (const globalAlias of expressionGlobals) {\n const source = schema.cstByAlias.get(globalAlias);\n if (source) {\n graph.addEdge(source.id, currentId);\n }\n }\n return graph;\n}\n\n/**\n * Resolve formatted cycle text for an untyped global reference in analysis.\n *\n * Uses `graph` (often a draft graph built from the expression under check). When\n * `currentId` is omitted, falls back to schema diagnostics on `missingId`.\n *\n * @returns Closed alias path such as `F3 → P1 → F3`, or `null` when no shared cycle exists.\n */\nfunction resolveDependencyCycleText(\n schema: RSForm,\n graph: Graph,\n currentId: number | undefined,\n missingId: number\n): string | null {\n for (const cycle of graph.findCycles()) {\n const members = new Set(cycle);\n if (!members.has(missingId)) {\n continue;\n }\n if (currentId !== undefined && !members.has(currentId)) {\n continue;\n }\n return formatDependencyCycle(cycle.map(id => schema.cstByID.get(id)?.alias ?? String(id)));\n }\n\n if (currentId === undefined) {\n const cycleDiag = schema.cstByID\n .get(missingId)\n ?.diagnostics.find(item => item.code === RSDiagnosticCode.schemaDependencyCycle);\n return cycleDiag?.params?.[0] ?? null;\n }\n\n return null;\n}\n\n/** Calculate statistics for {@link RSForm}. */\nexport function calculateSchemaStats(target: RSForm): RSFormStats {\n const items = target.items;\n return {\n count_all: items.length,\n count_crucial: items.reduce((sum, cst) => sum + (cst.crucial ? 1 : 0), 0),\n\n step_complexity: items.reduce((sum, cst) => sum + calculateStepComplexity(cst), 0),\n\n count_problematic: items.reduce((sum, cst) => sum + (isSchemaIssue(cst) ? 1 : 0), 0),\n count_homonyms: items.reduce(\n (sum, cst) => sum + (hasCstDiagnostic(cst, RSDiagnosticCode.schemaHomonym) ? 1 : 0),\n 0\n ),\n count_formal_duplicates: items.reduce(\n (sum, cst) => sum + (hasCstDiagnostic(cst, RSDiagnosticCode.schemaFormalDuplicate) ? 1 : 0),\n 0\n ),\n count_missing_convention: items.reduce((sum, cst) => sum + (isMissingConvention(cst) ? 1 : 0), 0),\n count_type_mismatch: items.reduce((sum, cst) => sum + (cst.is_type_mismatch ? 1 : 0), 0),\n\n count_incorrect: items.reduce((sum, cst) => sum + (cst.status === CstStatus.INCORRECT ? 1 : 0), 0),\n count_property: items.reduce((sum, cst) => sum + (cst.analysis?.valueClass === ValueClass.PROPERTY ? 1 : 0), 0),\n count_incalculable: items.reduce(\n (sum, cst) => sum + (cst.analysis?.success && cst.analysis.valueClass === null ? 1 : 0),\n 0\n ),\n count_inherited: items.reduce((sum, cst) => sum + (cst.is_inherited ? 1 : 0), 0),\n\n count_text_term: items.reduce((sum, cst) => sum + (cst.term_raw ? 1 : 0), 0),\n count_definition: items.reduce((sum, cst) => sum + (cst.definition_raw ? 1 : 0), 0),\n count_convention: items.reduce((sum, cst) => sum + (isBasicConcept(cst.cst_type) && cst.convention ? 1 : 0), 0),\n count_comment: items.reduce((sum, cst) => sum + (!isBasicConcept(cst.cst_type) && !cst.convention ? 1 : 0), 0),\n\n count_base: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.BASE ? 1 : 0), 0),\n count_constant: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.CONSTANT ? 1 : 0), 0),\n count_structured: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.STRUCTURED ? 1 : 0), 0),\n count_axiom: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.AXIOM ? 1 : 0), 0),\n count_term: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.TERM ? 1 : 0), 0),\n count_function: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.FUNCTION ? 1 : 0), 0),\n count_predicate: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.PREDICATE ? 1 : 0), 0),\n count_statement: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.STATEMENT ? 1 : 0), 0),\n count_nominal: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.NOMINAL ? 1 : 0), 0)\n };\n}\n\n/** Finds {@link Constituenta} by structure path. */\nexport function findCstByStructure(schema: RSForm, target: Constituenta, path: TypePath): Constituenta | null {\n for (const cst of schema.items) {\n if (!cst.spawner_path) {\n continue;\n }\n const absolutePath = resolveAbsoluteSpawnerPath(schema, cst, target.id);\n if (absolutePath?.length === path.length && absolutePath.every((v, i) => v === path[i])) {\n return cst;\n }\n }\n return null;\n}\n\n/** Retrieves name for piece of target {@link Constituenta} structure. */\nexport function getStructureName(schema: RSForm, target: Constituenta, path: TypePath): string {\n const representation = findCstByStructure(schema, target, path);\n if (representation) {\n return `${representation.alias}: ${representation.term_resolved}`;\n }\n if (!isTypification(target.effectiveType)) {\n return '';\n }\n const type = applyPath(target.effectiveType as Typification, path);\n if (type?.typeID === TypeID.basic) {\n const cst = schema.cstByAlias.get(type.baseID);\n if (cst?.term_resolved) {\n return `${cst.alias}: ${cst.term_resolved}`;\n }\n }\n return '';\n}\n\n// ========= Internals =====\nfunction calculateStepComplexity(cst: Constituenta): number {\n if (cst.cst_type === CstType.AXIOM || cst.cst_type === CstType.NOMINAL || !isBasicConcept(cst.cst_type)) {\n return 0;\n }\n if (cst.cst_type === CstType.BASE || cst.cst_type === CstType.CONSTANT || !cst.effectiveType) {\n return 1;\n }\n\n const type = cst.effectiveType as Typification;\n return calculateTypificationComplexity(type) + 1;\n}\n\nfunction calculateTypificationComplexity(type: Typification): number {\n switch (type.typeID) {\n case TypeID.basic:\n case TypeID.integer:\n case TypeID.anyTypification:\n return 0;\n case TypeID.tuple:\n return (\n type.factors.length + type.factors.reduce((sum, factor) => sum + calculateTypificationComplexity(factor), 0)\n );\n case TypeID.collection:\n if (type.base.typeID === TypeID.tuple) {\n let sum = 0;\n type.base.factors.forEach(factor => {\n sum += calculateTypificationComplexity(factor);\n });\n return sum + type.base.factors.length;\n } else if (type.base.typeID === TypeID.collection) {\n return calculateTypificationComplexity(type.base) + 1;\n }\n return 0;\n }\n}\n\nfunction isMissingConvention(cst: Constituenta): boolean {\n if (isBasicConcept(cst.cst_type) && !isLogical(cst.cst_type)) {\n if (!cst.convention || !cst.term_resolved) {\n return true;\n }\n }\n return false;\n}\n\nfunction resolveAbsoluteSpawnerPath(schema: RSForm, cst: Constituenta, targetId: number): TypePath | null {\n if (!cst.spawner_path || cst.spawner == null) {\n return null;\n }\n if (cst.spawner === targetId) {\n return cst.spawner_path;\n }\n\n const byId = schema.cstByID ?? new Map(schema.items.map(item => [item.id, item]));\n const visited = new Set<number>([cst.id]);\n let currentPath: number[] = [...cst.spawner_path];\n let currentSpawnerId: number | null = cst.spawner;\n\n while (currentSpawnerId != null) {\n if (currentSpawnerId === targetId) {\n return currentPath as TypePath;\n }\n if (visited.has(currentSpawnerId)) {\n return null;\n }\n visited.add(currentSpawnerId);\n\n const parent = byId.get(currentSpawnerId);\n if (!parent?.spawner_path || parent.spawner == null) {\n return null;\n }\n\n currentPath = [...parent.spawner_path, ...currentPath];\n currentSpawnerId = parent.spawner;\n }\n\n return null;\n}\n\n/** Evaluate if {@link Typification} can be used to produce structure. */\nfunction typeCanProduceStructure(type: Typification): boolean {\n if (type.typeID === TypeID.basic || type.typeID === TypeID.integer || type.typeID === TypeID.anyTypification) {\n return false;\n } else if (type.typeID === TypeID.tuple) {\n return true;\n } else {\n return (\n type.base.typeID !== TypeID.basic &&\n type.base.typeID !== TypeID.integer &&\n type.base.typeID !== TypeID.anyTypification\n );\n }\n}\n","/**\n * Module: Compact diagnostics for constituents (schema and model issue codes).\n */\n\nimport { type Graph } from '../graph';\nimport { labelType } from '../rslang/labels';\n\nimport { type Constituenta, type CstType } from './rsform';\nimport { isBasicConcept, isLogical } from './rsform-api';\nimport { EvalStatus } from './rsmodel';\n\n/** Origin of a diagnostic. */\nexport const DiagnosticKind = {\n EXPRESSION: 'expression',\n SCHEMA: 'schema',\n MODEL: 'model'\n} as const;\nexport type DiagnosticKind = (typeof DiagnosticKind)[keyof typeof DiagnosticKind];\n\n/** Schema and model diagnostic codes (same numeric format as {@link RSErrorCode}). */\nexport const RSDiagnosticCode = {\n schemaHomonym: 0x8901,\n schemaFormalDuplicate: 0x8902,\n schemaMissingConvention: 0x8903,\n schemaMissingTerm: 0x8904,\n schemaTypeMismatch: 0x8905,\n schemaDependencyCycle: 0x8906,\n\n modelEmpty: 0x8910,\n modelAxiomFalse: 0x8911,\n modelInvalidData: 0x8912,\n modelEvalFail: 0x8913\n} as const;\nexport type RSDiagnosticCode = (typeof RSDiagnosticCode)[keyof typeof RSDiagnosticCode];\n\n/** Compact diagnostic stored on {@link Constituenta} after schema loading. */\nexport interface CstDiagnostic {\n kind: DiagnosticKind;\n code: number;\n /** Positional message arguments (per-code; homonyms/duplicates use `[aliases]`). */\n params?: readonly string[];\n}\n\n/** Whether a constituent has a schema/model diagnostic with the given code. */\nexport function hasCstDiagnostic(cst: Constituenta, code: RSDiagnosticCode): boolean {\n return cst.diagnostics?.some(item => item.code === code) ?? false;\n}\n\n/**\n * Populate `diagnostics` on each constituent (homonyms, duplicates, metadata, type mismatch, cycles).\n *\n * Requires normalized formal definitions for duplicate detection (same as Portal loader).\n * When `graph` is provided, dependency cycles are reported as {@link RSDiagnosticCode.schemaDependencyCycle}.\n */\nexport function assignSchemaDiagnostics(\n items: Constituenta[],\n resolveAlias: (id: number) => string,\n normalizedDefinitions: ReadonlyMap<number, string>,\n graph?: Graph\n): void {\n const homonymById = detectHomonymDiagnostics(items, resolveAlias);\n const duplicateById = detectFormalDuplicateDiagnostics(items, resolveAlias, normalizedDefinitions);\n const cycleById = graph ? detectDependencyCycleDiagnostics(graph, resolveAlias) : new Map<number, CstDiagnostic>();\n\n for (const cst of items) {\n const diagnostics: CstDiagnostic[] = [];\n const cycle = cycleById.get(cst.id);\n if (cycle) {\n diagnostics.push(cycle);\n }\n const homonym = homonymById.get(cst.id);\n if (homonym) {\n diagnostics.push(homonym);\n }\n const duplicate = duplicateById.get(cst.id);\n if (duplicate) {\n diagnostics.push(duplicate);\n }\n diagnostics.push(...collectLocalSchemaDiagnostics(cst));\n cst.diagnostics = diagnostics;\n }\n}\n\n/**\n * Format a closed dependency cycle as `A → B → A`.\n *\n * Rotates the path to start at the lexicographically smallest alias so the same\n * cycle always renders identically regardless of graph traversal order.\n *\n * @param aliases Closed or open alias path from {@link Graph.findCycles}.\n * @returns Human-readable cycle text joined with ` → `.\n */\nexport function formatDependencyCycle(aliases: readonly string[]): string {\n if (aliases.length < 2) {\n return aliases.join(' → ');\n }\n\n const closed = aliases[0] === aliases[aliases.length - 1];\n const open = closed ? aliases.slice(0, -1) : [...aliases];\n if (open.length === 0) {\n return aliases.join(' → ');\n }\n\n let bestStart = 0;\n for (let index = 1; index < open.length; index++) {\n const alias = open[index];\n const bestAlias = open[bestStart];\n if (alias !== undefined && bestAlias !== undefined && alias < bestAlias) {\n bestStart = index;\n }\n }\n const rotated = [...open.slice(bestStart), ...open.slice(0, bestStart)];\n if (closed) {\n const first = rotated[0];\n if (first !== undefined) {\n rotated.push(first);\n }\n }\n return rotated.join(' → ');\n}\n\n/**\n * Map each node id that participates in a dependency cycle to a schema diagnostic.\n *\n * `params[0]` is the closed cycle path (`F3 → P1 → F3`).\n *\n * @param graph Constituent dependency graph (provider → dependent edges).\n * @param resolveAlias Maps node id to display alias for cycle formatting.\n * @returns One {@link CstDiagnostic} per node on each cyclic SCC.\n */\nexport function detectDependencyCycleDiagnostics(\n graph: Graph,\n resolveAlias: (id: number) => string\n): Map<number, CstDiagnostic> {\n const result = new Map<number, CstDiagnostic>();\n for (const cycle of graph.findCycles()) {\n if (cycle.length < 2) {\n continue;\n }\n const aliases = cycle.map(id => resolveAlias(id));\n const cycleText = formatDependencyCycle(aliases);\n const memberIds = new Set(cycle);\n for (const id of memberIds) {\n result.set(id, {\n kind: DiagnosticKind.SCHEMA,\n code: RSDiagnosticCode.schemaDependencyCycle,\n params: [cycleText]\n });\n }\n }\n return result;\n}\n\n/** Map a model evaluation status to a compact diagnostic, or `null` when not an issue. */\nexport function modelStatusCstDiagnostic(status: EvalStatus, cstType: CstType): CstDiagnostic | null {\n const code = modelStatusToCode(status, cstType);\n if (code === null) {\n return null;\n }\n return { kind: DiagnosticKind.MODEL, code, params: [String(status)] };\n}\n\nfunction collectLocalSchemaDiagnostics(cst: Constituenta): CstDiagnostic[] {\n const result: CstDiagnostic[] = [];\n if (cst.is_type_mismatch) {\n result.push({\n kind: DiagnosticKind.SCHEMA,\n code: RSDiagnosticCode.schemaTypeMismatch,\n params: [cst.typification_manual, labelType(cst.analysis.type)]\n });\n }\n if (isBasicConcept(cst.cst_type) && !isLogical(cst.cst_type)) {\n if (!cst.convention.trim()) {\n result.push({ kind: DiagnosticKind.SCHEMA, code: RSDiagnosticCode.schemaMissingConvention });\n }\n if (!cst.term_resolved.trim()) {\n result.push({ kind: DiagnosticKind.SCHEMA, code: RSDiagnosticCode.schemaMissingTerm });\n }\n }\n return result;\n}\n\nfunction detectGroupedAliasDiagnostics(\n items: Constituenta[],\n resolveAlias: (id: number) => string,\n groupKey: (cst: Constituenta) => string | undefined,\n code: RSDiagnosticCode\n): Map<number, CstDiagnostic> {\n const groups = new Map<string, Constituenta[]>();\n for (const cst of items) {\n const key = groupKey(cst);\n if (!key) {\n continue;\n }\n const group = groups.get(key) ?? [];\n group.push(cst);\n groups.set(key, group);\n }\n\n const result = new Map<number, CstDiagnostic>();\n for (const group of groups.values()) {\n if (group.length <= 1) {\n continue;\n }\n for (const cst of group) {\n const aliases = group\n .filter(other => other.id !== cst.id)\n .map(other => resolveAlias(other.id))\n .join(', ');\n result.set(cst.id, {\n kind: DiagnosticKind.SCHEMA,\n code,\n params: [aliases]\n });\n }\n }\n return result;\n}\n\nfunction detectHomonymDiagnostics(\n items: Constituenta[],\n resolveAlias: (id: number) => string\n): Map<number, CstDiagnostic> {\n return detectGroupedAliasDiagnostics(\n items,\n resolveAlias,\n cst => {\n const key = cst.term_resolved.trim().toLocaleLowerCase();\n return key === '' ? undefined : key;\n },\n RSDiagnosticCode.schemaHomonym\n );\n}\n\nfunction detectFormalDuplicateDiagnostics(\n items: Constituenta[],\n resolveAlias: (id: number) => string,\n normalizedDefinitions: ReadonlyMap<number, string>\n): Map<number, CstDiagnostic> {\n return detectGroupedAliasDiagnostics(\n items,\n resolveAlias,\n cst => normalizedDefinitions.get(cst.id),\n RSDiagnosticCode.schemaFormalDuplicate\n );\n}\n\nfunction modelStatusToCode(status: EvalStatus, cstType: CstType): RSDiagnosticCode | null {\n switch (status) {\n case EvalStatus.EMPTY:\n return isBasicConcept(cstType) ? RSDiagnosticCode.modelEmpty : null;\n case EvalStatus.AXIOM_FALSE:\n return RSDiagnosticCode.modelAxiomFalse;\n case EvalStatus.INVALID_DATA:\n return RSDiagnosticCode.modelInvalidData;\n case EvalStatus.EVAL_FAIL:\n return RSDiagnosticCode.modelEvalFail;\n default:\n return null;\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { t as Graph } from "./graph-DR8rL2o3.js";
1
+ import { t as Graph } from "./graph-CL73WdW1.js";
2
2
  import { f as ExpressionType, y as TypePath } from "./typification-9lNu9R4k.js";
3
3
  import { l as Value } from "./value-BduA_ZGp.js";
4
- import { a as RSLangAnalyzer, t as AnalysisBase } from "./analyzer-iqAXyROE.js";
4
+ import { a as RSLangAnalyzer, t as AnalysisBase } from "./analyzer-fNhkwO-6.js";
5
5
  import { i as LibraryItemReference, n as CurrentVersion, r as LibraryItem, s as VersionInfo } from "./library-CYun28Xz.js";
6
6
 
7
7
  //#region src/library/rsform.d.ts
@@ -251,6 +251,7 @@ declare const RSDiagnosticCode: {
251
251
  readonly schemaMissingConvention: 35075;
252
252
  readonly schemaMissingTerm: 35076;
253
253
  readonly schemaTypeMismatch: 35077;
254
+ readonly schemaDependencyCycle: 35078;
254
255
  readonly modelEmpty: 35088;
255
256
  readonly modelAxiomFalse: 35089;
256
257
  readonly modelInvalidData: 35090;
@@ -267,13 +268,34 @@ interface CstDiagnostic {
267
268
  /** Whether a constituent has a schema/model diagnostic with the given code. */
268
269
  declare function hasCstDiagnostic(cst: Constituenta, code: RSDiagnosticCode): boolean;
269
270
  /**
270
- * Populate `diagnostics` on each constituent (homonyms, duplicates, metadata, type mismatch).
271
+ * Populate `diagnostics` on each constituent (homonyms, duplicates, metadata, type mismatch, cycles).
271
272
  *
272
273
  * Requires normalized formal definitions for duplicate detection (same as Portal loader).
274
+ * When `graph` is provided, dependency cycles are reported as {@link RSDiagnosticCode.schemaDependencyCycle}.
273
275
  */
274
- declare function assignSchemaDiagnostics(items: Constituenta[], resolveAlias: (id: number) => string, normalizedDefinitions: ReadonlyMap<number, string>): void;
276
+ declare function assignSchemaDiagnostics(items: Constituenta[], resolveAlias: (id: number) => string, normalizedDefinitions: ReadonlyMap<number, string>, graph?: Graph): void;
277
+ /**
278
+ * Format a closed dependency cycle as `A → B → A`.
279
+ *
280
+ * Rotates the path to start at the lexicographically smallest alias so the same
281
+ * cycle always renders identically regardless of graph traversal order.
282
+ *
283
+ * @param aliases Closed or open alias path from {@link Graph.findCycles}.
284
+ * @returns Human-readable cycle text joined with ` → `.
285
+ */
286
+ declare function formatDependencyCycle(aliases: readonly string[]): string;
287
+ /**
288
+ * Map each node id that participates in a dependency cycle to a schema diagnostic.
289
+ *
290
+ * `params[0]` is the closed cycle path (`F3 → P1 → F3`).
291
+ *
292
+ * @param graph Constituent dependency graph (provider → dependent edges).
293
+ * @param resolveAlias Maps node id to display alias for cycle formatting.
294
+ * @returns One {@link CstDiagnostic} per node on each cyclic SCC.
295
+ */
296
+ declare function detectDependencyCycleDiagnostics(graph: Graph, resolveAlias: (id: number) => string): Map<number, CstDiagnostic>;
275
297
  /** Map a model evaluation status to a compact diagnostic, or `null` when not an issue. */
276
298
  declare function modelStatusCstDiagnostic(status: EvalStatus, cstType: CstType): CstDiagnostic | null;
277
299
  //#endregion
278
- export { TypeInfo as C, Substitution as S, CstClass as _, hasCstDiagnostic as a, RSForm as b, BasicsContext as c, RSModel as d, RSModelStats as f, Constituenta as g, Attribution as h, assignSchemaDiagnostics as i, DEFAULT_VALUE_TEXT as l, ArgumentValue as m, DiagnosticKind as n, modelStatusCstDiagnostic as o, TYPE_BASIC as p, RSDiagnosticCode as r, BasicBinding as s, CstDiagnostic as t, EvalStatus as u, CstStatus as v, RSFormStats as x, CstType as y };
279
- //# sourceMappingURL=diagnostics-Clo3mkba.d.ts.map
300
+ export { RSFormStats as C, RSForm as S, TypeInfo as T, Attribution as _, detectDependencyCycleDiagnostics as a, CstStatus as b, modelStatusCstDiagnostic as c, DEFAULT_VALUE_TEXT as d, EvalStatus as f, ArgumentValue as g, TYPE_BASIC as h, assignSchemaDiagnostics as i, BasicBinding as l, RSModelStats as m, DiagnosticKind as n, formatDependencyCycle as o, RSModel as p, RSDiagnosticCode as r, hasCstDiagnostic as s, CstDiagnostic as t, BasicsContext as u, Constituenta as v, Substitution as w, CstType as x, CstClass as y };
301
+ //# sourceMappingURL=diagnostics-EaIt3zIw.d.ts.map
@@ -98,6 +98,7 @@ declare const RSErrorCode: {
98
98
  readonly calcInvalidData: 33032;
99
99
  readonly cstEmptyDerived: 34913;
100
100
  readonly definitionNotAllowed: 34914;
101
+ readonly schemaDependencyCycle: 35078;
101
102
  };
102
103
  type RSErrorCode = (typeof RSErrorCode)[keyof typeof RSErrorCode];
103
104
  /** Generate ErrorID label. */
@@ -106,4 +107,4 @@ declare function getRSErrorPrefix(code: RSErrorCode): string;
106
107
  declare function isCritical(code: RSErrorCode): boolean;
107
108
  //#endregion
108
109
  export { RSErrorInfo as a, RSErrorDescription as i, RSErrorClass as n, getRSErrorPrefix as o, RSErrorCode as r, isCritical as s, ErrorReporter as t };
109
- //# sourceMappingURL=error-CzIbnP-N.d.ts.map
110
+ //# sourceMappingURL=error-B0EkIx4K.d.ts.map
@@ -1,2 +1,2 @@
1
- import { n as GraphNode, t as Graph } from "../graph-DR8rL2o3.js";
1
+ import { n as GraphNode, t as Graph } from "../graph-CL73WdW1.js";
2
2
  export { Graph, GraphNode };