pi-apexlang 0.2.2 → 0.4.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/README.md +22 -5
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/UPSTREAM.json +2 -2
- package/docs/assets/overview.en.png +0 -0
- package/docs/assets/overview.en.svg +89 -0
- package/docs/assets/overview.uk.png +0 -0
- package/docs/assets/overview.uk.svg +89 -0
- package/docs/en/README.md +41 -0
- package/docs/en/architecture.md +73 -0
- package/docs/en/getting-started.md +77 -0
- package/docs/en/maintenance.md +83 -0
- package/docs/en/tool-reference.md +85 -0
- package/docs/uk/README.md +41 -0
- package/docs/uk/architecture.md +73 -0
- package/docs/uk/getting-started.md +77 -0
- package/docs/uk/maintenance.md +83 -0
- package/docs/uk/tool-reference.md +85 -0
- package/extensions/apexlang/index.ts +19 -10
- package/extensions/lib/apexlang-cli.d.mts +1 -2
- package/extensions/lib/apexlang-cli.mjs +18 -13
- package/extensions/lib/apexlang-local-validate.mjs +83 -0
- package/extensions/lib/apexlang-local-validator.py +116 -0
- package/extensions/lib/apexlang-runtime-validate.mjs +76 -0
- package/package.json +2 -1
- package/skills/apexlang/APEXlang Skills and Workflows.md +325 -0
- package/skills/apexlang/README.md +6 -6
- package/skills/apexlang/RELEASE-NOTES.md +2 -2
- package/skills/apexlang/SKILL.md +24 -19
- package/skills/apexlang/agents/openai.yaml +4 -3
- package/skills/apexlang/assets/apex-generation/components.registry.json +1001 -16
- package/skills/apexlang/assets/apexlang/domains-catalog.json +6 -1
- package/skills/apexlang/assets/component-attributes.json +1924 -180
- package/skills/apexlang/assets/component-policies.json +19 -10
- package/skills/apexlang/assets/contracts/grammar-semantic-rules.json +134 -0
- package/skills/apexlang/assets/contracts/package-layers.json +56 -0
- package/skills/apexlang/assets/contracts/page-construction-packs.json +154 -0
- package/skills/apexlang/assets/contracts/page-patterns.json +119 -0
- package/skills/apexlang/assets/grammar/apexlang.ebnf +11743 -0
- package/skills/apexlang/assets/indexes/font-apex-icon-index.json +1699 -0
- package/skills/apexlang/assets/routing-catalog-main.json +11 -10
- package/skills/apexlang/assets/routing-load-policy.json +35 -9
- package/skills/apexlang/assets/rules-mapping.json +110 -2
- package/skills/apexlang/assets/rules.catalog.json +1298 -0
- package/skills/apexlang/assets/validator-fix-recipes.json +372 -17
- package/skills/apexlang/assets/workspace-intelligence.json +7 -10
- package/skills/apexlang/manifest.json +268 -12
- package/skills/apexlang/references/domains/README.md +1 -1
- package/skills/apexlang/references/domains/page-components/buttons.md +2 -2
- package/skills/apexlang/references/domains/page-components/page-items.md +3 -3
- package/skills/apexlang/references/domains/page-components/regions/region-display-selector/workflow-region-display-selector.md +69 -0
- package/skills/apexlang/references/domains/page-components/regions.md +1 -0
- package/skills/apexlang/references/domains/template-components/comments.md +61 -0
- package/skills/apexlang/references/domains/template-components/registry.md +2 -0
- package/skills/apexlang/references/domains/template-components/templates.md +47 -0
- package/skills/apexlang/references/domains/template-components/timeline.md +51 -0
- package/skills/apexlang/references/ops/one-message-router-contract.md +2 -2
- package/skills/apexlang/references/ops/runtime-gates/01-direct-sqlcl-import.md +1 -1
- package/skills/apexlang/references/ops/runtime-gates/02-direct-sqlcl-validate-gate.md +1 -1
- package/skills/apexlang/references/ops/runtime-gates.md +17 -16
- package/skills/apexlang/references/ops/sqlcl-agents/00-connection-gate.md +7 -7
- package/skills/apexlang/references/ops/sqlcl.md +7 -8
- package/skills/apexlang/references/policies/compiler-prop-map.md +6 -4
- package/skills/apexlang/references/policies/governance/00-governance.md +2 -2
- package/skills/apexlang/references/policies/memory-bank/00-guard/ai.guard.md +126 -922
- package/skills/apexlang/references/policies/memory-bank/20-data/apex.logic.md +3 -3
- package/skills/apexlang/references/policies/memory-bank/20-data/db.connection.md +9 -11
- package/skills/apexlang/references/policies/memory-bank/30-pages/apex.cards-page.md +95 -0
- package/skills/apexlang/references/policies/memory-bank/30-pages/apex.page.md +2 -2
- package/skills/apexlang/references/policies/memory-bank/30-pages/apex.smart-filter-search.md +145 -74
- package/skills/apexlang/references/policies/memory-bank/40-components/apex.region-interactions.md +9 -2
- package/skills/apexlang/references/policies/memory-bank/40-components/apex.region-media.md +6 -5
- package/skills/apexlang/references/policies/memory-bank/systemPatterns.md +9 -1
- package/skills/apexlang/references/workflows/apex-generation/agents/20-agent-draft.md +51 -232
- package/skills/apexlang/references/workflows/apex-generation/agents/30-agent-critique.md +49 -449
- package/skills/apexlang/references/workflows/apex-generation/agents/40-agent-revision.md +38 -238
- package/skills/apexlang/references/workflows/apex-generation/nlu/nlu-routing-test-matrix.md +8 -2
- package/skills/apexlang/references/workflows/apex-generation/nlu/nlu-routing-validation-notes.md +19 -3
- package/skills/apexlang/references/workflows/apex-generation/registry.md +15 -1
- package/skills/apexlang/references/workflows/apex-generation/templates.md +1 -1
- package/skills/apexlang/references/workflows/apex-generation.md +1 -1
- package/skills/apexlang/references/workflows/apexlang/apexlang-execution-model.md +5 -5
- package/skills/apexlang/references/workflows/apexlang/prompt-contracts.md +81 -10
- package/skills/apexlang/references/workflows/apexlang/workflow-create-app-from-fr-and-model.md +18 -7
- package/skills/apexlang/release-notes.json +156 -0
- package/skills/apexlang/runtime/grammar_contract.mjs +1166 -0
- package/skills/apexlang/runtime/internal/python/format_apexlang.py +210 -0
- package/skills/apexlang/runtime/internal/python/validate_apexlang.py +14142 -5843
- package/skills/apexlang/runtime/internal/python/validator_common.py +2 -0
- package/skills/apexlang/runtime/lib/catalogs.mjs +1026 -0
- package/skills/apexlang/runtime/lib/common.mjs +35 -0
- package/skills/apexlang/runtime/runtime.bundle.mjs +457 -57
- package/skills/apexlang/templates/items/combobox/combobox.explicit-defaults.md +1 -1
- package/skills/apexlang/templates/items/date-picker/date-picker._common.md +1 -1
- package/skills/apexlang/templates/items/date-picker/date-picker.explicit-defaults.md +1 -1
- package/skills/apexlang/templates/items/file-upload/file-upload._common.md +30 -24
- package/skills/apexlang/templates/items/file-upload/file-upload.compact-template.md +6 -0
- package/skills/apexlang/templates/items/file-upload/file-upload.explicit-defaults.md +23 -8
- package/skills/apexlang/templates/items/file-upload/file-upload.minimal.md +6 -0
- package/skills/apexlang/templates/items/hidden-item/hidden-item._common.md +1 -1
- package/skills/apexlang/templates/items/hidden-item/hidden-item.explicit-defaults.md +1 -1
- package/skills/apexlang/templates/items/items._common.md +3 -2
- package/skills/apexlang/templates/items/list-manager/list-manager.explicit-defaults.md +1 -1
- package/skills/apexlang/templates/items/markdown-editor/markdown-editor._common.md +1 -1
- package/skills/apexlang/templates/items/markdown-editor/markdown-editor.explicit-defaults.md +1 -1
- package/skills/apexlang/templates/items/number-field/number-field._common.md +8 -6
- package/skills/apexlang/templates/items/number-field/number-field.explicit-defaults.md +12 -2
- package/skills/apexlang/templates/items/popup-lov/popup-lov.explicit-defaults.md +1 -1
- package/skills/apexlang/templates/items/rich-text-editor/rich-text-editor._common.md +1 -1
- package/skills/apexlang/templates/items/rich-text-editor/rich-text-editor.explicit-defaults.md +1 -1
- package/skills/apexlang/templates/items/select-many/standard.md +1 -1
- package/skills/apexlang/templates/items/shuttle/shuttle.explicit-defaults.md +1 -1
- package/skills/apexlang/templates/items/switch/switch._common.md +8 -8
- package/skills/apexlang/templates/items/switch/switch.explicit-defaults.md +1 -1
- package/skills/apexlang/templates/items/text-area/text-area._common.md +2 -3
- package/skills/apexlang/templates/items/text-area/text-area.explicit-defaults.md +1 -1
- package/skills/apexlang/templates/items/text-area/text-area.required-length.md +1 -3
- package/skills/apexlang/templates/items/text-autocomplete/text-autocomplete.explicit-defaults.md +1 -1
- package/skills/apexlang/templates/items/text-autocomplete/text-autocomplete.required-length.md +1 -3
- package/skills/apexlang/templates/items/text-field/text-field._common.md +2 -3
- package/skills/apexlang/templates/items/text-field/text-field.explicit-defaults.md +1 -1
- package/skills/apexlang/templates/items/text-field/text-field.required-length.md +1 -3
- package/skills/apexlang/templates/page-examples/faceted-search/faceted-search.example.md +0 -5
- package/skills/apexlang/templates/page-examples/form-page/form-page.example.md +0 -9
- package/skills/apexlang/templates/page-examples/interactive-report-page/interactive-report-page.example.md +2 -2
- package/skills/apexlang/templates/page-examples/login-page/login-page.example.md +0 -8
- package/skills/apexlang/templates/page-examples/task-definition-initiated-tasks-page/task-definition-initiated-tasks-page.example.md +7 -17
- package/skills/apexlang/templates/page-examples/task-definition-my-tasks-page/task-definition-my-tasks-page.example.md +7 -65
- package/skills/apexlang/templates/page-examples/task-definition-task-details/task-definition-task-details.example.md +38 -201
- package/skills/apexlang/templates/region-components/calendar/calendar.custom-styling.md +3 -1
- package/skills/apexlang/templates/region-components/calendar/calendar.faceted-search.md +3 -2
- package/skills/apexlang/templates/region-components/cards/README.md +12 -4
- package/skills/apexlang/templates/region-components/cards/cards._common.md +176 -37
- package/skills/apexlang/templates/region-components/cards/cards._index.md +2 -1
- package/skills/apexlang/templates/region-components/cards/cards.refresh-after-dialog.md +46 -0
- package/skills/apexlang/templates/region-components/cards/cards.refresh-on-change.md +58 -0
- package/skills/apexlang/templates/region-components/cards/cards.rest-source.md +54 -18
- package/skills/apexlang/templates/region-components/cards/cards.standard.md +37 -21
- package/skills/apexlang/templates/region-components/chart/chart._series._common.md +1 -4
- package/skills/apexlang/templates/region-components/chart/chart.pie.md +6 -2
- package/skills/apexlang/templates/region-components/chart/chart.status-meter-gauge.md +1 -1
- package/skills/apexlang/templates/region-components/chart/chart.stock.md +2 -0
- package/skills/apexlang/templates/region-components/chart/features/chart.feature-chart-links.md +9 -7
- package/skills/apexlang/templates/region-components/faceted-search/faceted-search._common.md +1 -14
- package/skills/apexlang/templates/region-components/form/form._items._common.md +1 -5
- package/skills/apexlang/templates/region-components/interactive-report/interactive-report._columns._common.md +2 -6
- package/skills/apexlang/templates/region-components/interactive-report/interactive-report.page-header-actions.md +1 -7
- package/skills/apexlang/templates/region-components/list/README.md +1 -1
- package/skills/apexlang/templates/region-components/list/list._common.md +1 -1
- package/skills/apexlang/templates/region-components/map/map.backgrounds.md +12 -10
- package/skills/apexlang/templates/region-components/map/map.layer.extruded-polygons.md +16 -12
- package/skills/apexlang/templates/region-components/map/map.layer.heat-map.md +10 -7
- package/skills/apexlang/templates/region-components/map/map.layer.lines.md +10 -8
- package/skills/apexlang/templates/region-components/map/map.layer.polygons.md +21 -18
- package/skills/apexlang/templates/region-components/map/map.region.background-custom.md +25 -19
- package/skills/apexlang/templates/region-components/map/map.region.background-shared.md +19 -13
- package/skills/apexlang/templates/region-components/map/map.region.bbox-sql.md +13 -13
- package/skills/apexlang/templates/region-components/map/map.region.bbox-static.md +2 -2
- package/skills/apexlang/templates/region-components/map/map.region.browser-location.md +3 -7
- package/skills/apexlang/templates/region-components/map/map.region.init-position-sql.md +7 -7
- package/skills/apexlang/templates/region-components/region-display-selector/README.md +5 -2
- package/skills/apexlang/templates/region-components/region-display-selector/region-display-selector._common.md +60 -7
- package/skills/apexlang/templates/region-components/region-display-selector/region-display-selector._index.md +4 -1
- package/skills/apexlang/templates/region-components/region-display-selector/region-display-selector.permutations.md +55 -0
- package/skills/apexlang/templates/region-components/region-display-selector/region-display-selector.standard.md +91 -5
- package/skills/apexlang/templates/region-components/smart-filter-search/README.md +6 -2
- package/skills/apexlang/templates/region-components/smart-filter-search/smart-filter-search._common.md +107 -46
- package/skills/apexlang/templates/region-components/smart-filter-search/smart-filter-search._index.md +4 -3
- package/skills/apexlang/templates/region-components/smart-filter-search/smart-filter-search.standard.md +33 -17
- package/skills/apexlang/templates/shared-components/component-settings/component-settings._common.md +1 -0
- package/skills/apexlang/templates/shared-components/component-settings.example.md +1 -0
- package/skills/apexlang/templates/shared-components/email-templates.example.md +1 -1
- package/skills/apexlang/templates/shared-components/rest-data-sources/example_rest_data_source.example.md +40 -46
- package/skills/apexlang/templates/shared-components/task-definitions/action-task.example.md +2 -30
- package/skills/apexlang/templates/shared-components/task-definitions/approve-request.example.md +3 -31
- package/skills/apexlang/templates/template-components/README.md +27 -15
- package/skills/apexlang/templates/template-components/avatar/README.md +56 -0
- package/skills/apexlang/templates/template-components/avatar/avatar._common.md +149 -0
- package/skills/apexlang/templates/template-components/avatar/avatar._index.md +42 -0
- package/skills/apexlang/templates/template-components/{avatar._template_options.md → avatar/avatar._template_options.md} +1 -1
- package/skills/apexlang/templates/template-components/avatar/avatar.report-icon.md +48 -0
- package/skills/apexlang/templates/template-components/avatar/avatar.report-image-url-column.md +62 -0
- package/skills/apexlang/templates/template-components/avatar/avatar.report-initials.md +54 -0
- package/skills/apexlang/templates/template-components/badge/README.md +38 -0
- package/skills/apexlang/templates/template-components/badge/badge._common.md +183 -0
- package/skills/apexlang/templates/template-components/badge/badge._index.md +20 -0
- package/skills/apexlang/templates/template-components/badge/badge.report-link.md +66 -0
- package/skills/apexlang/templates/template-components/badge/badge.report-minimal.md +76 -0
- package/skills/apexlang/templates/template-components/comments/README.md +20 -0
- package/skills/apexlang/templates/template-components/comments/comments._common.md +43 -0
- package/skills/apexlang/templates/template-components/comments/comments._index.md +25 -0
- package/skills/apexlang/templates/template-components/{comments._template_options.md → comments/comments._template_options.md} +2 -1
- package/skills/apexlang/templates/template-components/comments/comments.apex-task-comments.md +77 -0
- package/skills/apexlang/templates/template-components/comments/comments.partial-column.md +120 -0
- package/skills/apexlang/templates/template-components/comments/comments.permutations.md +425 -0
- package/skills/apexlang/templates/template-components/content-row/README.md +1 -1
- package/skills/apexlang/templates/template-components/content-row/content-row._common.md +1 -1
- package/skills/apexlang/templates/template-components/content-row/content-row._index.md +1 -1
- package/skills/apexlang/templates/template-components/content-row/content-row._template_options.md +1 -1
- package/skills/apexlang/templates/template-components/flexbox-container/README.md +9 -0
- package/skills/apexlang/templates/template-components/flexbox-container/flexbox-container._index.md +13 -0
- package/skills/apexlang/templates/template-components/media-list/README.md +100 -0
- package/skills/apexlang/templates/template-components/media-list/media-list._common.md +174 -0
- package/skills/apexlang/templates/template-components/media-list/media-list._index.md +50 -0
- package/skills/apexlang/templates/template-components/{media-list._template_options.md → media-list/media-list._template_options.md} +8 -2
- package/skills/apexlang/templates/template-components/media-list/media-list.partial.md +50 -0
- package/skills/apexlang/templates/template-components/media-list/media-list.report-avatar-badge.md +76 -0
- package/skills/apexlang/templates/template-components/media-list/media-list.report-base.md +71 -0
- package/skills/apexlang/templates/template-components/media-list/media-list.report-grouped.md +40 -0
- package/skills/apexlang/templates/template-components/media-list/media-list.report-layouts.md +50 -0
- package/skills/apexlang/templates/template-components/media-list/media-list.report-link.md +53 -0
- package/skills/apexlang/templates/template-components/metric-card/README.md +19 -4
- package/skills/apexlang/templates/template-components/metric-card/metric-card._common.md +122 -29
- package/skills/apexlang/templates/template-components/metric-card/metric-card._index.md +6 -0
- package/skills/apexlang/templates/template-components/metric-card/metric-card._template_options.md +47 -2
- package/skills/apexlang/templates/template-components/metric-card/metric-card.partial-minimal.md +51 -0
- package/skills/apexlang/templates/template-components/metric-card/metric-card.permutations.md +56 -0
- package/skills/apexlang/templates/template-components/metric-card/metric-card.report-avatar-badge.md +98 -0
- package/skills/apexlang/templates/template-components/metric-card/metric-card.report-grouping-selection.md +131 -0
- package/skills/apexlang/templates/template-components/metric-card/metric-card.report-link.md +66 -0
- package/skills/apexlang/templates/template-components/metric-card/metric-card.report-minimal.md +97 -0
- package/skills/apexlang/templates/template-components/template-components.registry.json +90 -1
- package/skills/apexlang/templates/template-components/timeline/README.md +19 -0
- package/skills/apexlang/templates/template-components/timeline/timeline._common.md +43 -0
- package/skills/apexlang/templates/template-components/timeline/timeline._index.md +24 -0
- package/skills/apexlang/templates/template-components/{timeline._template_options.md → timeline/timeline._template_options.md} +2 -1
- package/skills/apexlang/templates/template-components/timeline/timeline.permutations.md +399 -0
- package/skills/apexlang/templates/template-components/timeline/timeline.report-sample-data.md +168 -0
- package/skills/apexlang/templates/workspace-components/credentials/credentials-for-open-ai.example.md +1 -1
- package/skills/apexlang/templates/workspace-components/generative-ai-services/open-ai.example.md +8 -6
- package/skills/apexlang/tools/apexctl.mjs +167 -4
- package/skills/apexlang/tools/compiler-truth-audit.mjs +95 -63
- package/skills/apexlang/tools/query-valid-props-context.mjs +30 -0
- package/skills/apexlang/tools/query-valid-props-normalize.mjs +1 -0
- package/skills/apexlang/tools/query-valid-props-runtime.mjs +13 -8
- package/skills/apexlang/tools/query-valid-props-semantics.mjs +391 -0
- package/skills/apexlang/tools/query-valid-props-template-components.mjs +15 -1
- package/skills/apexlang/tools/query-valid-props.mjs +198 -216
- /package/skills/apexlang/templates/template-components/{badge._template_options.md → badge/badge._template_options.md} +0 -0
- /package/skills/apexlang/templates/template-components/{flexbox-container._template_options.md → flexbox-container/flexbox-container._template_options.md} +0 -0
|
@@ -0,0 +1,1166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build compact, task-scoped contracts from the canonical APEXlang EBNF.
|
|
3
|
+
*/
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { promises as fs } from "node:fs";
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
|
|
10
|
+
const CONTRACT_SCHEMA_VERSION = 5;
|
|
11
|
+
const DEFAULT_CORE_PRODUCTIONS = [
|
|
12
|
+
"nl",
|
|
13
|
+
"blank-lines",
|
|
14
|
+
"ws",
|
|
15
|
+
"required-ws",
|
|
16
|
+
"indent",
|
|
17
|
+
"line-end",
|
|
18
|
+
"value",
|
|
19
|
+
"string-like-value",
|
|
20
|
+
"component-id",
|
|
21
|
+
"identifier",
|
|
22
|
+
"identifier-start",
|
|
23
|
+
"identifier-rest",
|
|
24
|
+
"string",
|
|
25
|
+
"multiline-string",
|
|
26
|
+
"reference",
|
|
27
|
+
"boolean",
|
|
28
|
+
"number",
|
|
29
|
+
"array",
|
|
30
|
+
"comment"
|
|
31
|
+
];
|
|
32
|
+
const COMPILER_VALUE_FALLBACKS = new Map([
|
|
33
|
+
["normal", "NORMAL"],
|
|
34
|
+
["modalDialog", "MODAL"],
|
|
35
|
+
["nonModalDialog", "NON_MODAL"],
|
|
36
|
+
["breadcrumb", "NATIVE_BREADCRUMB"],
|
|
37
|
+
["staticContent", "NATIVE_STATIC"],
|
|
38
|
+
["classicReport", "NATIVE_SQL_REPORT"],
|
|
39
|
+
["interactiveReport", "NATIVE_IR"],
|
|
40
|
+
["form", "NATIVE_FORM"],
|
|
41
|
+
["standard", "STANDARD"],
|
|
42
|
+
["redirectThisApp", "REDIRECT_PAGE"],
|
|
43
|
+
["databaseColumn", "DB_COLUMN"],
|
|
44
|
+
["true", "Y"],
|
|
45
|
+
["false", "N"]
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
const COMPILER_TO_DSL_PROPERTY_ALIASES = new Map([
|
|
49
|
+
["newCol", "newColumn"],
|
|
50
|
+
["databaseCol", "databaseColumn"]
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
export function normalizeCompilerPropertyName(propertyName) {
|
|
54
|
+
const normalized = String(propertyName ?? "").trim();
|
|
55
|
+
return COMPILER_TO_DSL_PROPERTY_ALIASES.get(normalized) || normalized;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizeCompilerDslValue(property, value) {
|
|
59
|
+
const normalized = String(value ?? "").trim();
|
|
60
|
+
if (String(property?.propertyId ?? "") === "94" && normalized.startsWith("themeTemplateComponent/")) {
|
|
61
|
+
return `TMPL_${normalized.slice("themeTemplateComponent/".length)}`;
|
|
62
|
+
}
|
|
63
|
+
return normalized;
|
|
64
|
+
}
|
|
65
|
+
const INSTANCE_POLICY_RULES = [
|
|
66
|
+
{
|
|
67
|
+
id: "VALIDATION_CANONICAL_STRUCTURE_001",
|
|
68
|
+
family: "page.validation",
|
|
69
|
+
declarationIdPrefix: "VAL_",
|
|
70
|
+
forbidBlocks: ["config"],
|
|
71
|
+
requiredBlocks: ["execution", "validation", "error"],
|
|
72
|
+
requiredProperties: ["execution.sequence", "error.errorMessage"],
|
|
73
|
+
bindings: [
|
|
74
|
+
{
|
|
75
|
+
whenAssumptionEndsWith: "validation.item",
|
|
76
|
+
target: "error.associatedItem",
|
|
77
|
+
transform: "reference"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
function resolveLayout() {
|
|
84
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
85
|
+
const packagedRoot = path.resolve(moduleDir, "..");
|
|
86
|
+
if (existsSync(path.join(packagedRoot, "assets", "grammar", "apexlang.ebnf"))) {
|
|
87
|
+
return {
|
|
88
|
+
packaged: true,
|
|
89
|
+
root: packagedRoot,
|
|
90
|
+
grammarPath: path.join(packagedRoot, "assets", "grammar", "apexlang.ebnf"),
|
|
91
|
+
semanticRulesPath: path.join(packagedRoot, "assets", "contracts", "grammar-semantic-rules.json")
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const repoRoot = path.resolve(moduleDir, "../../../..");
|
|
95
|
+
return {
|
|
96
|
+
packaged: false,
|
|
97
|
+
root: repoRoot,
|
|
98
|
+
grammarPath: path.join(repoRoot, "references/policies", "apexlang", "grammar", "apexlang.ebnf"),
|
|
99
|
+
semanticRulesPath: path.join(repoRoot, "skills", "apexlang", "contracts", "grammar-semantic-rules.json")
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function ensureDir(directoryPath) {
|
|
104
|
+
await fs.mkdir(directoryPath, { recursive: true });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function parseProductions(grammarText) {
|
|
108
|
+
const productions = new Map();
|
|
109
|
+
let currentName = "";
|
|
110
|
+
let currentLines = [];
|
|
111
|
+
|
|
112
|
+
const flush = () => {
|
|
113
|
+
if (currentName) {
|
|
114
|
+
productions.set(currentName, currentLines.join("\n").trimEnd());
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
for (const line of grammarText.split(/\r?\n/)) {
|
|
119
|
+
const match = line.match(/^<([^>]+)>\s*::=/);
|
|
120
|
+
if (match) {
|
|
121
|
+
flush();
|
|
122
|
+
currentName = match[1];
|
|
123
|
+
currentLines = [line];
|
|
124
|
+
} else if (currentName) {
|
|
125
|
+
currentLines.push(line);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
flush();
|
|
129
|
+
return productions;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function referencedProductions(definition) {
|
|
133
|
+
return [...definition.matchAll(/<([^>]+)>/g)].map((match) => match[1]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isChildSelector(name) {
|
|
137
|
+
return name.endsWith("-child-component");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function isGroupSelector(name) {
|
|
141
|
+
return name.endsWith("-group-block");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function selectorOptions(name, definition) {
|
|
145
|
+
return [...new Set(referencedProductions(definition).filter((reference) => reference !== name))];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function productionClosure(productions, roots, groups, coreProductions) {
|
|
149
|
+
const selectedRoots = new Set(roots);
|
|
150
|
+
const selectedGroups = new Set(groups);
|
|
151
|
+
const visited = new Set();
|
|
152
|
+
const pending = [...coreProductions, ...roots];
|
|
153
|
+
const allowedChildren = {};
|
|
154
|
+
const allowedGroups = {};
|
|
155
|
+
|
|
156
|
+
while (pending.length > 0) {
|
|
157
|
+
const name = pending.pop();
|
|
158
|
+
if (visited.has(name)) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const definition = productions.get(name);
|
|
162
|
+
if (!definition) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
visited.add(name);
|
|
166
|
+
|
|
167
|
+
const references = referencedProductions(definition).filter((reference) => reference !== name);
|
|
168
|
+
if (isChildSelector(name)) {
|
|
169
|
+
allowedChildren[name] = selectorOptions(name, definition);
|
|
170
|
+
for (const reference of references) {
|
|
171
|
+
if (selectedRoots.has(reference)) {
|
|
172
|
+
pending.push(reference);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (isGroupSelector(name)) {
|
|
178
|
+
allowedGroups[name] = selectorOptions(name, definition);
|
|
179
|
+
for (const reference of references) {
|
|
180
|
+
const groupSelected = [...selectedGroups].some((group) => reference === group || reference.endsWith(`-${group}`));
|
|
181
|
+
if (groupSelected) {
|
|
182
|
+
pending.push(reference);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
pending.push(...references);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
names: [...visited].sort(),
|
|
192
|
+
allowedChildren,
|
|
193
|
+
allowedGroups
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function normalizeComponents(components) {
|
|
198
|
+
return [...new Set(components.flatMap((value) => String(value).split(","))
|
|
199
|
+
.map((value) => value.trim())
|
|
200
|
+
.filter(Boolean))].sort();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function normalizeChildren(children, requestedComponents) {
|
|
204
|
+
const values = normalizeComponents(children || []);
|
|
205
|
+
for (const value of values) {
|
|
206
|
+
const [parent, child, ...extra] = value.split(".");
|
|
207
|
+
if (!parent || !child || extra.length > 0 || !requestedComponents.includes(parent)) {
|
|
208
|
+
throw new Error(`Invalid child selector '${value}'; expected <requested-component>.<child>`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return values;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function normalizeInstances(values, requestedChildren) {
|
|
215
|
+
const bySelector = new Map();
|
|
216
|
+
for (const rawValue of values.flatMap((value) => Array.isArray(value) ? value : [value])) {
|
|
217
|
+
const text = String(rawValue).trim();
|
|
218
|
+
if (!text) continue;
|
|
219
|
+
const match = text.match(/^([A-Za-z][A-Za-z0-9]*)\.([A-Za-z][A-Za-z0-9]*)\[([^\]]+)\]\.([A-Za-z][A-Za-z0-9]*(?:\.[A-Za-z][A-Za-z0-9]*)?)=(.+)$/);
|
|
220
|
+
if (!match) {
|
|
221
|
+
throw new Error(`Invalid --instance condition '${text}'; expected parent.child[id].[group.]property=value`);
|
|
222
|
+
}
|
|
223
|
+
const [, parent, child, rawId, propertyPath, rawConditionValue] = match;
|
|
224
|
+
const family = `${parent}.${child}`;
|
|
225
|
+
if (!requestedChildren.includes(family)) {
|
|
226
|
+
throw new Error(`Instance condition '${text}' requires --children ${family}`);
|
|
227
|
+
}
|
|
228
|
+
const id = rawId.trim();
|
|
229
|
+
const value = rawConditionValue.trim();
|
|
230
|
+
if (!id || !value) throw new Error(`Invalid --instance condition '${text}'`);
|
|
231
|
+
const segments = propertyPath.split(".");
|
|
232
|
+
const condition = {
|
|
233
|
+
selector: `${family}[${id}].${propertyPath}`,
|
|
234
|
+
family,
|
|
235
|
+
parent,
|
|
236
|
+
child,
|
|
237
|
+
id,
|
|
238
|
+
group: segments.length === 2 ? segments[0] : null,
|
|
239
|
+
property: segments.at(-1),
|
|
240
|
+
value
|
|
241
|
+
};
|
|
242
|
+
const existing = bySelector.get(condition.selector);
|
|
243
|
+
if (existing && existing.value !== value) {
|
|
244
|
+
throw new Error(`Conflicting --instance conditions for ${condition.selector}`);
|
|
245
|
+
}
|
|
246
|
+
bySelector.set(condition.selector, condition);
|
|
247
|
+
}
|
|
248
|
+
return [...bySelector.values()].sort((left, right) => left.selector.localeCompare(right.selector));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function normalizeConditions(values, requestedComponents) {
|
|
252
|
+
const componentOrder = [...requestedComponents].sort((left, right) => right.length - left.length);
|
|
253
|
+
const bySelector = new Map();
|
|
254
|
+
for (const rawValue of values.flatMap((value) => Array.isArray(value) ? value : [value])) {
|
|
255
|
+
const text = String(rawValue).trim();
|
|
256
|
+
if (!text) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const separator = text.indexOf("=");
|
|
260
|
+
if (separator <= 0 || separator === text.length - 1) {
|
|
261
|
+
throw new Error(`Invalid --when condition: ${text}; expected component[.group].property=value`);
|
|
262
|
+
}
|
|
263
|
+
const selector = text.slice(0, separator).trim();
|
|
264
|
+
const value = text.slice(separator + 1).trim();
|
|
265
|
+
const component = componentOrder.find((name) => selector === name || selector.startsWith(`${name}.`));
|
|
266
|
+
if (!component) {
|
|
267
|
+
throw new Error(`Condition selector must start with a requested component: ${selector}`);
|
|
268
|
+
}
|
|
269
|
+
const segments = selector.slice(component.length + 1).split(".").filter(Boolean);
|
|
270
|
+
if (segments.length < 1 || segments.length > 2) {
|
|
271
|
+
throw new Error(`Invalid condition selector: ${selector}; expected component[.group].property`);
|
|
272
|
+
}
|
|
273
|
+
const condition = {
|
|
274
|
+
selector,
|
|
275
|
+
component,
|
|
276
|
+
group: segments.length === 2 ? segments[0] : null,
|
|
277
|
+
property: segments.at(-1),
|
|
278
|
+
value
|
|
279
|
+
};
|
|
280
|
+
const existing = bySelector.get(selector);
|
|
281
|
+
if (existing && existing.value !== value) {
|
|
282
|
+
throw new Error(`Conflicting --when conditions for ${selector}: ${existing.value}, ${value}`);
|
|
283
|
+
}
|
|
284
|
+
bySelector.set(selector, condition);
|
|
285
|
+
}
|
|
286
|
+
return [...bySelector.values()].sort((left, right) => left.selector.localeCompare(right.selector));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function normalizedToken(value) {
|
|
290
|
+
return String(value).toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function compilerConditionState(condition, assumptions) {
|
|
294
|
+
if (!condition) return "active";
|
|
295
|
+
if (condition.conditions) {
|
|
296
|
+
const states = condition.conditions.map((child) => compilerConditionState(child, assumptions));
|
|
297
|
+
if ((condition.operator || "AND") === "AND") {
|
|
298
|
+
if (states.includes("inactive")) return "inactive";
|
|
299
|
+
return states.every((state) => state === "active") ? "active" : "conditional";
|
|
300
|
+
}
|
|
301
|
+
if (states.includes("active")) return "active";
|
|
302
|
+
return states.every((state) => state === "inactive") ? "inactive" : "conditional";
|
|
303
|
+
}
|
|
304
|
+
const actual = assumptions.get(String(condition.propertyId || ""))
|
|
305
|
+
?? assumptions.get(condition.propertyName || "");
|
|
306
|
+
if (actual === undefined) return "conditional";
|
|
307
|
+
const expected = String(condition.value ?? "");
|
|
308
|
+
const values = (condition.values || []).map(String);
|
|
309
|
+
switch (condition.type) {
|
|
310
|
+
case "EQUALS": return actual === expected ? "active" : "inactive";
|
|
311
|
+
case "NOT_EQUALS": return actual !== expected ? "active" : "inactive";
|
|
312
|
+
case "IN_LIST": return values.includes(actual) ? "active" : "inactive";
|
|
313
|
+
case "NOT_IN_LIST": return values.includes(actual) ? "inactive" : "active";
|
|
314
|
+
case "NOT_NULL": return actual ? "active" : "inactive";
|
|
315
|
+
case "NULL": return actual ? "inactive" : "active";
|
|
316
|
+
case "STARTS_WITH": return actual.startsWith(expected) ? "active" : "inactive";
|
|
317
|
+
case "NOT_STARTS_WITH": return actual.startsWith(expected) ? "inactive" : "active";
|
|
318
|
+
default: return "conditional";
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function compilerRecordProperties(record) {
|
|
323
|
+
return [
|
|
324
|
+
...(record.properties || []),
|
|
325
|
+
...Object.values(record.groups || {}).flat()
|
|
326
|
+
];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function grammarComponentKeyword(definition) {
|
|
330
|
+
return definition?.match(/::=\s*"([A-Za-z][A-Za-z0-9]*)"/)?.[1] || null;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function grammarVariantProperties(productions, root) {
|
|
334
|
+
const names = [...productions.keys()].filter((name) => name === root || name.startsWith(`${root}-`));
|
|
335
|
+
return new Set(names.flatMap((name) =>
|
|
336
|
+
[...productions.get(name).matchAll(/"([A-Za-z][A-Za-z0-9]*)"\s+":"\s+<ws>/g)].map((match) => match[1])));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function grammarPropertyGroup(productions, component, property) {
|
|
340
|
+
const dslProperty = normalizeCompilerPropertyName(property);
|
|
341
|
+
const directName = `${component}-direct-property`;
|
|
342
|
+
if (productions.get(directName)?.includes(`"${dslProperty}"`)) return null;
|
|
343
|
+
const prefix = `${component}-`;
|
|
344
|
+
const suffix = "-property";
|
|
345
|
+
const matches = [...productions.entries()]
|
|
346
|
+
.filter(([name, definition]) => name.startsWith(prefix)
|
|
347
|
+
&& name.endsWith(suffix)
|
|
348
|
+
&& definition.includes(`"${dslProperty}"`))
|
|
349
|
+
.map(([name]) => name.slice(prefix.length, -suffix.length));
|
|
350
|
+
return matches.length === 1 ? matches[0] : null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function matchGrammarVariant(productions, alternatives, record) {
|
|
354
|
+
const compilerProperties = new Set(compilerRecordProperties(record)
|
|
355
|
+
.map(({ propertyName }) => normalizeCompilerPropertyName(propertyName)));
|
|
356
|
+
const scored = alternatives.map((production) => {
|
|
357
|
+
const grammarProperties = grammarVariantProperties(productions, production);
|
|
358
|
+
const overlap = [...grammarProperties].filter((property) => compilerProperties.has(property)).length;
|
|
359
|
+
const union = new Set([...grammarProperties, ...compilerProperties]).size;
|
|
360
|
+
return { production, overlap, similarity: union ? overlap / union : 0 };
|
|
361
|
+
}).sort((left, right) => right.similarity - left.similarity || right.overlap - left.overlap);
|
|
362
|
+
if (!scored[0] || scored[0].overlap === 0) return null;
|
|
363
|
+
if (scored[1] && scored[0].similarity === scored[1].similarity && scored[0].overlap === scored[1].overlap) return null;
|
|
364
|
+
return scored[0];
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function compilerChildDeclaration(productions, grammarProduction, child, hasVariants) {
|
|
368
|
+
const directProperties = productions.get(`${grammarProduction}-direct-property`) || "";
|
|
369
|
+
const usesNameAsIdentity = /"name"\s*":"[^\n]*\(\*\s*required\b/.test(directProperties);
|
|
370
|
+
if (usesNameAsIdentity) {
|
|
371
|
+
return {
|
|
372
|
+
declaration: `${child} <name> (`,
|
|
373
|
+
componentIdPolicy: "required-as-name"
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
declaration: `${child} (`,
|
|
378
|
+
componentIdPolicy: hasVariants ? "omit-to-disambiguate-variant" : "optional"
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function resolveCompilerChildren({ map, productions, requestedChildren, assumptionIndex }) {
|
|
383
|
+
const resolved = [];
|
|
384
|
+
const childrenByParent = new Map();
|
|
385
|
+
for (const selector of requestedChildren) {
|
|
386
|
+
const [parent, child] = selector.split(".");
|
|
387
|
+
if (!childrenByParent.has(parent)) childrenByParent.set(parent, new Set());
|
|
388
|
+
childrenByParent.get(parent).add(child);
|
|
389
|
+
}
|
|
390
|
+
for (const [parent, selectedChildren] of childrenByParent) {
|
|
391
|
+
const selector = productions.get(`${parent}-child-component`);
|
|
392
|
+
if (!selector) continue;
|
|
393
|
+
const alternatives = selectorOptions(`${parent}-child-component`, selector);
|
|
394
|
+
const byKeyword = new Map();
|
|
395
|
+
for (const production of alternatives) {
|
|
396
|
+
const keyword = grammarComponentKeyword(productions.get(production));
|
|
397
|
+
if (!keyword) continue;
|
|
398
|
+
if (!byKeyword.has(keyword)) byKeyword.set(keyword, []);
|
|
399
|
+
byKeyword.get(keyword).push(production);
|
|
400
|
+
}
|
|
401
|
+
for (const [child, grammarAlternatives] of byKeyword) {
|
|
402
|
+
if (!selectedChildren.has(child)) continue;
|
|
403
|
+
const candidates = map.componentTypes.filter((record) =>
|
|
404
|
+
record.singular === child && record.parentComponentType === parent);
|
|
405
|
+
if (candidates.length === 0) continue;
|
|
406
|
+
const classified = candidates.map((record) => ({
|
|
407
|
+
record,
|
|
408
|
+
state: compilerConditionState(record.parentDependsOn, assumptionIndex)
|
|
409
|
+
})).filter(({ state }) => state !== "inactive");
|
|
410
|
+
const active = classified.filter(({ state }) => state === "active");
|
|
411
|
+
const selectable = active.length > 0 ? active : classified;
|
|
412
|
+
if (selectable.length !== 1) continue;
|
|
413
|
+
const selected = selectable[0];
|
|
414
|
+
const grammar = matchGrammarVariant(productions, grammarAlternatives, selected.record);
|
|
415
|
+
if (!grammar) continue;
|
|
416
|
+
const declaration = compilerChildDeclaration(
|
|
417
|
+
productions,
|
|
418
|
+
grammar.production,
|
|
419
|
+
child,
|
|
420
|
+
grammarAlternatives.length > 1
|
|
421
|
+
);
|
|
422
|
+
resolved.push({
|
|
423
|
+
parent,
|
|
424
|
+
child,
|
|
425
|
+
compilerState: selected.state,
|
|
426
|
+
grammarProduction: grammar.production,
|
|
427
|
+
grammarSimilarity: Number(grammar.similarity.toFixed(4)),
|
|
428
|
+
...declaration,
|
|
429
|
+
record: selected.record
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return resolved;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function buildInstanceContracts(resolvedChildren, requestedInstances, requestedGroups, baseAssumptionIndex) {
|
|
437
|
+
const families = new Map(resolvedChildren.map((child) => [`${child.parent}.${child.child}`, child]));
|
|
438
|
+
const grouped = new Map();
|
|
439
|
+
for (const condition of requestedInstances) {
|
|
440
|
+
const key = `${condition.family}[${condition.id}]`;
|
|
441
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
442
|
+
grouped.get(key).push(condition);
|
|
443
|
+
}
|
|
444
|
+
const contracts = [];
|
|
445
|
+
for (const [selector, conditions] of grouped) {
|
|
446
|
+
const resolved = families.get(conditions[0].family);
|
|
447
|
+
if (!resolved) {
|
|
448
|
+
throw new Error(`Instance '${selector}' has no unambiguous resolved child record`);
|
|
449
|
+
}
|
|
450
|
+
const assumptionIndex = new Map([...baseAssumptionIndex]
|
|
451
|
+
.filter(([key]) => /^\d+$/.test(String(key))));
|
|
452
|
+
const assumptions = [];
|
|
453
|
+
const assumedGroups = new Set();
|
|
454
|
+
for (const condition of conditions) {
|
|
455
|
+
const found = findCompilerProperty(resolved.record, condition);
|
|
456
|
+
if (!found) {
|
|
457
|
+
throw new Error(`Instance property '${condition.selector}' is not present in compiler metadata`);
|
|
458
|
+
}
|
|
459
|
+
const value = compilerValue(found.property, condition.value);
|
|
460
|
+
const path = [selector, found.group, normalizeCompilerPropertyName(found.property.propertyName)]
|
|
461
|
+
.filter(Boolean)
|
|
462
|
+
.join(".");
|
|
463
|
+
assumptions.push({
|
|
464
|
+
path,
|
|
465
|
+
dslValue: condition.value,
|
|
466
|
+
compilerPropertyId: found.property.propertyId || null,
|
|
467
|
+
compilerValue: value
|
|
468
|
+
});
|
|
469
|
+
if (found.group) assumedGroups.add(found.group);
|
|
470
|
+
assumptionIndex.set(found.property.propertyName, value);
|
|
471
|
+
if (found.property.propertyId) assumptionIndex.set(String(found.property.propertyId), value);
|
|
472
|
+
}
|
|
473
|
+
const groupedCandidates = Object.entries(resolved.record.groups || {})
|
|
474
|
+
.filter(([group]) => requestedGroups.includes(group))
|
|
475
|
+
.flatMap(([group, items]) => items.map((property) => ({ group, property })));
|
|
476
|
+
const groupedIds = new Set(groupedCandidates.map(({ property }) => String(property.propertyId || "")));
|
|
477
|
+
const candidates = [
|
|
478
|
+
...groupedCandidates,
|
|
479
|
+
...(resolved.record.properties || [])
|
|
480
|
+
.filter((property) => !groupedIds.has(String(property.propertyId || "")))
|
|
481
|
+
.map((property) => ({ group: null, property }))
|
|
482
|
+
];
|
|
483
|
+
const propertyStates = candidates.map(({ group, property }) => ({
|
|
484
|
+
group,
|
|
485
|
+
property,
|
|
486
|
+
state: property.dependsOn
|
|
487
|
+
? compilerConditionState(property.dependsOn, assumptionIndex)
|
|
488
|
+
: "active"
|
|
489
|
+
}));
|
|
490
|
+
const requiredActiveProperties = propertyStates
|
|
491
|
+
.filter(({ property, state }) => property.required && state === "active")
|
|
492
|
+
.map(({ group, property }) => ({
|
|
493
|
+
path: [selector, group, normalizeCompilerPropertyName(property.propertyName)].filter(Boolean).join("."),
|
|
494
|
+
compilerPropertyId: property.propertyId || null
|
|
495
|
+
}))
|
|
496
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
497
|
+
const omitProperties = [...new Set(propertyStates
|
|
498
|
+
.filter(({ group, property, state }) => group && assumedGroups.has(group)
|
|
499
|
+
&& property.required && state === "inactive")
|
|
500
|
+
.map(({ group, property }) =>
|
|
501
|
+
[selector, group, normalizeCompilerPropertyName(property.propertyName)].filter(Boolean).join(".")))]
|
|
502
|
+
.sort((left, right) => left.localeCompare(right));
|
|
503
|
+
const repositoryPolicies = INSTANCE_POLICY_RULES
|
|
504
|
+
.filter((rule) => rule.family === conditions[0].family)
|
|
505
|
+
.map((rule) => {
|
|
506
|
+
const bindings = rule.bindings.flatMap((binding) => {
|
|
507
|
+
const assumption = assumptions.find(({ path }) => path.endsWith(binding.whenAssumptionEndsWith));
|
|
508
|
+
if (!assumption) return [];
|
|
509
|
+
return [{
|
|
510
|
+
source: assumption.path,
|
|
511
|
+
target: `${selector}.${binding.target}`,
|
|
512
|
+
value: binding.transform === "reference" ? `@${assumption.dslValue}` : assumption.dslValue
|
|
513
|
+
}];
|
|
514
|
+
});
|
|
515
|
+
return {
|
|
516
|
+
ruleId: rule.id,
|
|
517
|
+
declarationId: rule.declarationIdPrefix
|
|
518
|
+
? `${rule.declarationIdPrefix}${conditions[0].id}`.toUpperCase()
|
|
519
|
+
: null,
|
|
520
|
+
exactDeclaration: rule.declarationIdPrefix
|
|
521
|
+
? `${resolved.child} ${`${rule.declarationIdPrefix}${conditions[0].id}`.toUpperCase()} (`
|
|
522
|
+
: resolved.declaration,
|
|
523
|
+
requiredBlocks: rule.requiredBlocks,
|
|
524
|
+
forbidBlocks: rule.forbidBlocks || [],
|
|
525
|
+
requiredProperties: rule.requiredProperties.map((property) => `${selector}.${property}`),
|
|
526
|
+
bindings
|
|
527
|
+
};
|
|
528
|
+
});
|
|
529
|
+
contracts.push({
|
|
530
|
+
selector,
|
|
531
|
+
family: conditions[0].family,
|
|
532
|
+
id: conditions[0].id,
|
|
533
|
+
grammarProduction: resolved.grammarProduction,
|
|
534
|
+
declaration: resolved.declaration,
|
|
535
|
+
componentIdPolicy: resolved.componentIdPolicy,
|
|
536
|
+
assumptions,
|
|
537
|
+
requiredActiveProperties,
|
|
538
|
+
omitProperties,
|
|
539
|
+
repositoryPolicies
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
return contracts.sort((left, right) => left.selector.localeCompare(right.selector));
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function findCompilerProperty(record, condition) {
|
|
546
|
+
const candidates = [
|
|
547
|
+
...(record.properties || []).map((property) => ({ group: null, property })),
|
|
548
|
+
...Object.entries(record.groups || {}).flatMap(([group, properties]) =>
|
|
549
|
+
properties.map((property) => ({ group, property })))
|
|
550
|
+
].filter(({ property }) => normalizeCompilerPropertyName(property.propertyName) === condition.property);
|
|
551
|
+
if (condition.group) {
|
|
552
|
+
return candidates.find(({ group }) => group === condition.group) || null;
|
|
553
|
+
}
|
|
554
|
+
return candidates.find(({ group }) => group === null) || candidates[0] || null;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function compilerValue(property, dslValue) {
|
|
558
|
+
const normalizedDslValue = normalizeCompilerDslValue(property, dslValue);
|
|
559
|
+
const values = property.lov?.values || [];
|
|
560
|
+
const match = values.find(({ name, returnValue }) =>
|
|
561
|
+
normalizedToken(name) === normalizedToken(normalizedDslValue)
|
|
562
|
+
|| normalizedToken(returnValue) === normalizedToken(normalizedDslValue));
|
|
563
|
+
return match?.returnValue || COMPILER_VALUE_FALLBACKS.get(normalizedDslValue) || normalizedDslValue;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function grammarRequiredPropertyNames(definition = "") {
|
|
567
|
+
return [...String(definition).split(/\r?\n/).map((line) => {
|
|
568
|
+
const match = line.match(/"([A-Za-z][A-Za-z0-9]*)"\s*":"\s*<ws>/);
|
|
569
|
+
return match && /\(\*\s*required\b/.test(line) ? match[1] : "";
|
|
570
|
+
}).filter(Boolean)];
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function grammarRequiredChildPaths(productions, grammarProduction, selector) {
|
|
574
|
+
const paths = new Set(grammarRequiredPropertyNames(productions.get(`${grammarProduction}-direct-property`) || "")
|
|
575
|
+
.map((property) => `${selector}.${property}`));
|
|
576
|
+
const groupDefinition = productions.get(`${grammarProduction}-group-block`) || "";
|
|
577
|
+
for (const reference of groupDefinition.matchAll(/<([^>]+)>/g)) {
|
|
578
|
+
const productionName = reference[1];
|
|
579
|
+
const blockDefinition = productions.get(productionName) || "";
|
|
580
|
+
const blockName = blockDefinition.match(/::=.*?"([A-Za-z][A-Za-z0-9]*)"\s*<ws>\s*"\{"/)?.[1];
|
|
581
|
+
if (!blockName) continue;
|
|
582
|
+
for (const property of grammarRequiredPropertyNames(productions.get(`${productionName}-property`) || "")) {
|
|
583
|
+
paths.add(`${selector}.${blockName}.${property}`);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return paths;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function grammarCompilerConformance(productions, compilerContract) {
|
|
590
|
+
if (compilerContract?.status !== "resolved") return [];
|
|
591
|
+
return (compilerContract.resolvedChildren || [])
|
|
592
|
+
.filter((child) => `${child.parent}.${child.child}` === "region.column")
|
|
593
|
+
.map((child) => {
|
|
594
|
+
const selector = `${child.parent}.${child.child}`;
|
|
595
|
+
const grammarRequired = grammarRequiredChildPaths(productions, child.grammarProduction, selector);
|
|
596
|
+
const compilerProperties = new Map((compilerContract.requiredActiveProperties || [])
|
|
597
|
+
.filter(({ path: propertyPath }) => propertyPath.startsWith(`${selector}.`))
|
|
598
|
+
.map((entry) => [entry.path, entry]));
|
|
599
|
+
// A compiler default does not make a property optional. The metadata keeps
|
|
600
|
+
// these facts separate: `isRequired` controls structural presence while
|
|
601
|
+
// `defaultValue` supplies the value used by the builder UI/compiler.
|
|
602
|
+
// Treating every defaulted property as optional caused a false mismatch for
|
|
603
|
+
// Media List column source.type (required, default DB_COLUMN).
|
|
604
|
+
const compilerRequired = new Set([...compilerProperties]
|
|
605
|
+
.filter(([, entry]) => entry.compilerRequired !== false)
|
|
606
|
+
.map(([propertyPath]) => propertyPath));
|
|
607
|
+
const compilerDefaulted = new Set([...compilerProperties]
|
|
608
|
+
.filter(([, entry]) => entry.defaulted)
|
|
609
|
+
.map(([propertyPath]) => propertyPath));
|
|
610
|
+
const sourceTypePath = `${selector}.source.type`;
|
|
611
|
+
// This probe owns the source.type contract. Other column groups are only
|
|
612
|
+
// comparable after their compiler metadata has been projected into the
|
|
613
|
+
// matching grammar group; treating an unselected group as a direct
|
|
614
|
+
// property would create unrelated false mismatches.
|
|
615
|
+
const mismatches = [sourceTypePath]
|
|
616
|
+
.filter((propertyPath) => grammarRequired.has(propertyPath) !== compilerRequired.has(propertyPath))
|
|
617
|
+
.map((propertyPath) => ({
|
|
618
|
+
path: propertyPath,
|
|
619
|
+
grammarRequired: grammarRequired.has(propertyPath),
|
|
620
|
+
compilerRequired: compilerRequired.has(propertyPath),
|
|
621
|
+
compilerDefaulted: compilerDefaulted.has(propertyPath)
|
|
622
|
+
}));
|
|
623
|
+
const sourceType = {
|
|
624
|
+
path: sourceTypePath,
|
|
625
|
+
grammarRequired: grammarRequired.has(sourceTypePath),
|
|
626
|
+
compilerRequired: compilerRequired.has(sourceTypePath),
|
|
627
|
+
compilerDefaulted: compilerDefaulted.has(sourceTypePath),
|
|
628
|
+
compilerDefaultValue: compilerProperties.get(sourceTypePath)?.defaultValue ?? null,
|
|
629
|
+
emission: compilerRequired.has(sourceTypePath) ? "required" : compilerDefaulted.has(sourceTypePath) ? "omit" : "unresolved"
|
|
630
|
+
};
|
|
631
|
+
return {
|
|
632
|
+
selector,
|
|
633
|
+
grammarProduction: child.grammarProduction,
|
|
634
|
+
status: mismatches.length ? "mismatch" : "matched",
|
|
635
|
+
blocking: mismatches.length > 0,
|
|
636
|
+
mismatches,
|
|
637
|
+
sourceType
|
|
638
|
+
};
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
export function probeMediaListGrammarConformance({ contract } = {}) {
|
|
643
|
+
return (contract?.compilerContract?.grammarConformance || [])
|
|
644
|
+
.filter(({ selector }) => selector === "region.column");
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function dslValue(property, value) {
|
|
648
|
+
if (value === "Y") return true;
|
|
649
|
+
if (value === "N") return false;
|
|
650
|
+
const fallback = [...COMPILER_VALUE_FALLBACKS].find(([, compilerToken]) => compilerToken === value);
|
|
651
|
+
if (fallback) return fallback[0];
|
|
652
|
+
const match = (property?.lov?.values || []).find((item) => String(item.returnValue) === String(value));
|
|
653
|
+
if (match?.name) return match.name;
|
|
654
|
+
return value;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function conditionLeaves(condition) {
|
|
658
|
+
if (!condition) return [];
|
|
659
|
+
if (condition.conditions) return condition.conditions.flatMap(conditionLeaves);
|
|
660
|
+
return [condition];
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function activationCondition(condition, siblingProperties) {
|
|
664
|
+
if (condition.conditions) {
|
|
665
|
+
const conditions = condition.conditions.map((child) => activationCondition(child, siblingProperties));
|
|
666
|
+
if (conditions.some((item) => !item)) return null;
|
|
667
|
+
return { operator: String(condition.operator || "AND").toLowerCase(), conditions };
|
|
668
|
+
}
|
|
669
|
+
const dependency = siblingProperties.find((property) =>
|
|
670
|
+
String(property.propertyId || "") === String(condition.propertyId || "")
|
|
671
|
+
|| normalizeCompilerPropertyName(property.propertyName) === normalizeCompilerPropertyName(condition.propertyName));
|
|
672
|
+
if (!dependency) return null;
|
|
673
|
+
const operator = {
|
|
674
|
+
EQUALS: "equals",
|
|
675
|
+
NOT_EQUALS: "notEquals",
|
|
676
|
+
IN_LIST: "in",
|
|
677
|
+
NOT_IN_LIST: "notIn",
|
|
678
|
+
NULL: "isNull",
|
|
679
|
+
NOT_NULL: "isNotNull"
|
|
680
|
+
}[condition.type];
|
|
681
|
+
if (!operator) return null;
|
|
682
|
+
const result = {
|
|
683
|
+
property: normalizeCompilerPropertyName(dependency.propertyName),
|
|
684
|
+
operator,
|
|
685
|
+
missingAllowed: condition.hasToExist === false
|
|
686
|
+
};
|
|
687
|
+
if (condition.value !== undefined) result.value = dslValue(dependency, condition.value);
|
|
688
|
+
if (condition.values) result.values = condition.values.map((value) => dslValue(dependency, value));
|
|
689
|
+
return result;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
async function buildCompilerContract(
|
|
693
|
+
layout,
|
|
694
|
+
productions,
|
|
695
|
+
requestedComponents,
|
|
696
|
+
requestedChildren,
|
|
697
|
+
requestedInstances,
|
|
698
|
+
requestedGroups,
|
|
699
|
+
selectedConditions,
|
|
700
|
+
compilerOracleHome
|
|
701
|
+
) {
|
|
702
|
+
const toolRoot = layout.packaged
|
|
703
|
+
? path.join(layout.root, "tools")
|
|
704
|
+
: path.join(layout.root, "references/policies", "apexlang", "compiler-prop-map");
|
|
705
|
+
try {
|
|
706
|
+
const [{ buildOracleNormalizedMap, readOracleRuntimeMetadata, ORACLE_NORMALIZER_VERSION }, { resolveOracleRuntime }] = await Promise.all([
|
|
707
|
+
import(pathToFileURL(path.join(toolRoot, "query-valid-props-normalize.mjs")).href),
|
|
708
|
+
import(pathToFileURL(path.join(toolRoot, "query-valid-props-runtime.mjs")).href)
|
|
709
|
+
]);
|
|
710
|
+
const runtime = resolveOracleRuntime(compilerOracleHome);
|
|
711
|
+
const { metadata, buildId, metadataHash } = readOracleRuntimeMetadata(runtime.compilerJarPath);
|
|
712
|
+
const map = buildOracleNormalizedMap({
|
|
713
|
+
metadata,
|
|
714
|
+
compilerJarPath: runtime.compilerJarPath,
|
|
715
|
+
oracleHome: runtime.oracleHome,
|
|
716
|
+
source: runtime.source
|
|
717
|
+
});
|
|
718
|
+
const records = new Map(requestedComponents.map((component) => [
|
|
719
|
+
component,
|
|
720
|
+
map.componentTypes.find((record) => record.singular === component && !record.parentComponentType)
|
|
721
|
+
|| map.componentTypes.find((record) => record.singular === component)
|
|
722
|
+
]));
|
|
723
|
+
const assumptions = [];
|
|
724
|
+
const assumptionIndex = new Map();
|
|
725
|
+
for (const condition of selectedConditions) {
|
|
726
|
+
const record = records.get(condition.component);
|
|
727
|
+
const found = record ? findCompilerProperty(record, condition) : null;
|
|
728
|
+
if (!found) continue;
|
|
729
|
+
const value = compilerValue(found.property, condition.value);
|
|
730
|
+
const grammarGroup = condition.group
|
|
731
|
+
|| grammarPropertyGroup(productions, condition.component, found.property.propertyName);
|
|
732
|
+
assumptions.push({
|
|
733
|
+
selector: condition.selector,
|
|
734
|
+
path: [condition.component, grammarGroup, normalizeCompilerPropertyName(found.property.propertyName)]
|
|
735
|
+
.filter(Boolean)
|
|
736
|
+
.join("."),
|
|
737
|
+
dslValue: condition.value,
|
|
738
|
+
compilerPropertyId: found.property.propertyId || null,
|
|
739
|
+
compilerValue: value
|
|
740
|
+
});
|
|
741
|
+
assumptionIndex.set(found.property.propertyName, value);
|
|
742
|
+
if (found.property.propertyId) assumptionIndex.set(String(found.property.propertyId), value);
|
|
743
|
+
}
|
|
744
|
+
const resolvedChildren = resolveCompilerChildren({
|
|
745
|
+
map,
|
|
746
|
+
productions,
|
|
747
|
+
requestedChildren,
|
|
748
|
+
assumptionIndex
|
|
749
|
+
});
|
|
750
|
+
const instanceContracts = buildInstanceContracts(
|
|
751
|
+
resolvedChildren,
|
|
752
|
+
requestedInstances,
|
|
753
|
+
requestedGroups,
|
|
754
|
+
assumptionIndex
|
|
755
|
+
);
|
|
756
|
+
for (const child of resolvedChildren) {
|
|
757
|
+
records.set(`${child.parent}.${child.child}`, child.record);
|
|
758
|
+
}
|
|
759
|
+
const properties = [];
|
|
760
|
+
const activationRules = [];
|
|
761
|
+
for (const [component, record] of records) {
|
|
762
|
+
if (!record) continue;
|
|
763
|
+
const groupedCandidates = Object.entries(record.groups || {})
|
|
764
|
+
.filter(([group]) => requestedGroups.includes(group))
|
|
765
|
+
.flatMap(([group, items]) => items.map((property) => ({ group, property })));
|
|
766
|
+
for (const [group, items] of Object.entries(record.groups || {})) {
|
|
767
|
+
if (!requestedGroups.includes(group)) continue;
|
|
768
|
+
for (const property of items) {
|
|
769
|
+
if (!property.required || !property.dependsOn) continue;
|
|
770
|
+
if (compilerConditionState(property.dependsOn, assumptionIndex) !== "conditional") continue;
|
|
771
|
+
const leaves = conditionLeaves(property.dependsOn);
|
|
772
|
+
if (!leaves.some((leaf) => leaf.hasToExist === false)) continue;
|
|
773
|
+
const siblingOnly = leaves.length > 0 && leaves.every((leaf) => items.some((candidate) =>
|
|
774
|
+
String(candidate.propertyId || "") === String(leaf.propertyId || "")
|
|
775
|
+
|| candidate.propertyName === leaf.propertyName));
|
|
776
|
+
if (!siblingOnly) continue;
|
|
777
|
+
const activeWhen = activationCondition(property.dependsOn, items);
|
|
778
|
+
if (!activeWhen) continue;
|
|
779
|
+
activationRules.push({
|
|
780
|
+
path: `${component}.${group}.${normalizeCompilerPropertyName(property.propertyName)}`,
|
|
781
|
+
activeWhen,
|
|
782
|
+
otherwise: "omit"
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
const groupedIds = new Set(groupedCandidates.map(({ property }) => String(property.propertyId || "")));
|
|
787
|
+
const candidates = [
|
|
788
|
+
...groupedCandidates,
|
|
789
|
+
...(record.properties || [])
|
|
790
|
+
.filter((property) => !groupedIds.has(String(property.propertyId || "")))
|
|
791
|
+
.map((property) => ({ group: null, property }))
|
|
792
|
+
];
|
|
793
|
+
for (const { group, property } of candidates) {
|
|
794
|
+
if (!property.required) continue;
|
|
795
|
+
const state = property.dependsOn
|
|
796
|
+
? compilerConditionState(property.dependsOn, assumptionIndex)
|
|
797
|
+
: "active";
|
|
798
|
+
if (state !== "active") continue;
|
|
799
|
+
const propertyPath = [component, group, normalizeCompilerPropertyName(property.propertyName)]
|
|
800
|
+
.filter(Boolean)
|
|
801
|
+
.join(".");
|
|
802
|
+
properties.push({
|
|
803
|
+
path: propertyPath,
|
|
804
|
+
compilerPropertyId: property.propertyId || null,
|
|
805
|
+
...(component === "region.column"
|
|
806
|
+
&& property.defaultValue !== null
|
|
807
|
+
&& property.defaultValue !== undefined
|
|
808
|
+
? { defaulted: true, defaultValue: dslValue(property, property.defaultValue) }
|
|
809
|
+
: {})
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
const uniqueProperties = [...new Map(properties.map((item) => [item.path, item])).values()]
|
|
814
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
815
|
+
return {
|
|
816
|
+
status: "resolved",
|
|
817
|
+
source: "compiler-runtime-metadata",
|
|
818
|
+
buildID: buildId,
|
|
819
|
+
metadataHash,
|
|
820
|
+
normalizerVersion: ORACLE_NORMALIZER_VERSION,
|
|
821
|
+
assumptions,
|
|
822
|
+
requiredActiveProperties: uniqueProperties,
|
|
823
|
+
resolvedChildren: resolvedChildren.map(({ record: _record, ...child }) => child),
|
|
824
|
+
instances: instanceContracts,
|
|
825
|
+
activationRules: [...new Map(activationRules.map((item) => [`${item.path}:${JSON.stringify(item.activeWhen)}`, item])).values()]
|
|
826
|
+
.sort((left, right) => left.path.localeCompare(right.path)
|
|
827
|
+
|| JSON.stringify(left.activeWhen).localeCompare(JSON.stringify(right.activeWhen)))
|
|
828
|
+
};
|
|
829
|
+
} catch (error) {
|
|
830
|
+
return {
|
|
831
|
+
status: "unavailable",
|
|
832
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
833
|
+
assumptions: [],
|
|
834
|
+
requiredActiveProperties: [],
|
|
835
|
+
resolvedChildren: [],
|
|
836
|
+
instances: [],
|
|
837
|
+
activationRules: []
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function productionContext(name, requestedComponents) {
|
|
843
|
+
const component = [...requestedComponents]
|
|
844
|
+
.sort((left, right) => right.length - left.length)
|
|
845
|
+
.find((candidate) => name === candidate || name.startsWith(`${candidate}-`));
|
|
846
|
+
if (!component) {
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
const suffix = name.slice(component.length + 1);
|
|
850
|
+
const group = suffix.endsWith("-property") ? suffix.slice(0, -"-property".length) : null;
|
|
851
|
+
return { component, group };
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function narrowDefinition(name, definition, conditions, requestedComponents) {
|
|
855
|
+
const context = productionContext(name, requestedComponents);
|
|
856
|
+
if (!context || !name.endsWith("-property")) {
|
|
857
|
+
return { definition, narrowedEnums: 0 };
|
|
858
|
+
}
|
|
859
|
+
let narrowedEnums = 0;
|
|
860
|
+
const nextDefinition = definition.split("\n").map((line) => {
|
|
861
|
+
const propertyMatch = line.match(/"([^"]+)"\s*":"\s*<ws>\s*\(([^)]+)\)/);
|
|
862
|
+
if (!propertyMatch) {
|
|
863
|
+
return line;
|
|
864
|
+
}
|
|
865
|
+
const property = propertyMatch[1];
|
|
866
|
+
const condition = conditions.find((item) => item.component === context.component
|
|
867
|
+
&& item.property === property
|
|
868
|
+
&& (!item.group || item.group === context.group || context.group === "direct"));
|
|
869
|
+
if (!condition) {
|
|
870
|
+
return line;
|
|
871
|
+
}
|
|
872
|
+
const acceptedValues = [...propertyMatch[2].matchAll(/"([^"]+)"/g)].map((match) => match[1]);
|
|
873
|
+
const selected = acceptedValues.find((value) => normalizedToken(value) === normalizedToken(condition.value));
|
|
874
|
+
if (!selected) {
|
|
875
|
+
return line;
|
|
876
|
+
}
|
|
877
|
+
narrowedEnums += 1;
|
|
878
|
+
return line.replace(`(${propertyMatch[2]})`, `"${selected}"`);
|
|
879
|
+
}).join("\n");
|
|
880
|
+
return { definition: nextDefinition, narrowedEnums };
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function selectSemanticRules(catalog, requestedComponents, selectedConditions) {
|
|
884
|
+
const componentSet = new Set(requestedComponents);
|
|
885
|
+
const conditionSet = new Set(selectedConditions.map(({ selector, value }) => `${selector}=${value}`));
|
|
886
|
+
return catalog.rules.filter(({ match = {} }) =>
|
|
887
|
+
(match.components || []).every((component) => componentSet.has(component))
|
|
888
|
+
&& (match.conditions || []).every((condition) => conditionSet.has(condition))
|
|
889
|
+
).map(({ match: _match, ...rule }) => rule);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function generationChecklist(rules) {
|
|
893
|
+
return rules.map(({ id, level, scope, check = null }) => ({ ruleId: id, level, scope, check }));
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
function componentBlocks(text, component) {
|
|
897
|
+
const pattern = new RegExp(`^([ \\t]*)${component}\\b[^\\n]*\\(\\s*$([\\s\\S]*?)^\\1\\)\\s*$`, "gm");
|
|
898
|
+
return [...text.matchAll(pattern)].map((match) => match[0]);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function cleanScalar(value) {
|
|
902
|
+
return String(value).trim().replace(/^"|"$/g, "");
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function checkCanonicalPageIdentity(artifactPath, text) {
|
|
906
|
+
const filenameMatch = path.basename(artifactPath).match(/^p(\d{5})-([a-z0-9-]+)\.apx$/i);
|
|
907
|
+
if (!filenameMatch) {
|
|
908
|
+
return { passed: false, message: "Page artifact filename must use pNNNNN-<slug>.apx." };
|
|
909
|
+
}
|
|
910
|
+
const expectedPage = Number.parseInt(filenameMatch[1], 10);
|
|
911
|
+
const expectedAlias = filenameMatch[2].toUpperCase();
|
|
912
|
+
const declarationMatch = text.match(/^page\s+(\d+)\s*\(/m);
|
|
913
|
+
const pagePropertyMatch = text.match(/^\s+page:\s*(\d+)\s*$/m);
|
|
914
|
+
const aliasMatch = text.match(/^\s+alias:\s*(.+?)\s*$/m);
|
|
915
|
+
const actualAlias = aliasMatch ? cleanScalar(aliasMatch[1]) : "";
|
|
916
|
+
const passed = Number.parseInt(declarationMatch?.[1] || "", 10) === expectedPage
|
|
917
|
+
&& Number.parseInt(pagePropertyMatch?.[1] || "", 10) === expectedPage
|
|
918
|
+
&& actualAlias === expectedAlias;
|
|
919
|
+
return {
|
|
920
|
+
passed,
|
|
921
|
+
message: passed
|
|
922
|
+
? `Page declaration, page property, and alias match ${path.basename(artifactPath)}.`
|
|
923
|
+
: `Expected page ${expectedPage} and alias ${expectedAlias} from ${path.basename(artifactPath)}.`
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function checkNormalStaticContentSlot(_artifactPath, text) {
|
|
928
|
+
const staticRegions = componentBlocks(text, "region").filter((block) => /^\s*type:\s*staticContent\s*$/m.test(block));
|
|
929
|
+
const passed = staticRegions.length > 0 && staticRegions.every((block) => /^\s*slot:\s*body\s*$/m.test(block));
|
|
930
|
+
return {
|
|
931
|
+
passed,
|
|
932
|
+
message: passed
|
|
933
|
+
? "Every selected static-content region uses layout.slot: body."
|
|
934
|
+
: "A normal-page static-content region is missing layout.slot: body."
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
function checkNormalPageBreadcrumbRegion(_artifactPath, text) {
|
|
939
|
+
const breadcrumbRegions = componentBlocks(text, "region").filter((block) => /^\s*type:\s*breadcrumb\s*$/m.test(block));
|
|
940
|
+
const passed = breadcrumbRegions.some((block) => /^\s*breadcrumb:\s*@breadcrumb\s*$/m.test(block)
|
|
941
|
+
&& /^\s*slot:\s*breadcrumbBar\s*$/m.test(block));
|
|
942
|
+
return {
|
|
943
|
+
passed,
|
|
944
|
+
message: passed
|
|
945
|
+
? "The page renders the required breadcrumb region."
|
|
946
|
+
: "Add a breadcrumb region with source.breadcrumb: @breadcrumb and layout.slot: breadcrumbBar."
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function checkNormalPageBodyRegions(_artifactPath, text) {
|
|
951
|
+
const bodyRegions = componentBlocks(text, "region")
|
|
952
|
+
.filter((block) => !/^\s*type:\s*breadcrumb\s*$/m.test(block));
|
|
953
|
+
const passed = bodyRegions.length === 0 || bodyRegions.every((block) => /^\s*slot:\s*body\s*$/m.test(block));
|
|
954
|
+
return {
|
|
955
|
+
passed,
|
|
956
|
+
message: passed
|
|
957
|
+
? "Every non-breadcrumb region on the normal page uses layout.slot: body."
|
|
958
|
+
: "A non-breadcrumb region on the normal page must use layout.slot: body."
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function checkSameAppButtonTarget(_artifactPath, text) {
|
|
963
|
+
const redirectButtons = componentBlocks(text, "button").filter((block) => /^\s*action:\s*redirectThisApp\s*$/m.test(block));
|
|
964
|
+
const passed = redirectButtons.length > 0 && redirectButtons.every((block) => /^\s*target:\s*\{\s*$/m.test(block));
|
|
965
|
+
return {
|
|
966
|
+
passed,
|
|
967
|
+
message: passed
|
|
968
|
+
? "Every redirectThisApp button uses an object-valued target."
|
|
969
|
+
: "A redirectThisApp button is missing an object-valued target: { ... } property."
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
const CONTRACT_CHECKS = {
|
|
974
|
+
"canonical-page-identity": checkCanonicalPageIdentity,
|
|
975
|
+
"normal-static-content-slot": checkNormalStaticContentSlot,
|
|
976
|
+
"normal-page-breadcrumb-region": checkNormalPageBreadcrumbRegion,
|
|
977
|
+
"normal-page-body-regions": checkNormalPageBodyRegions,
|
|
978
|
+
"same-app-button-target": checkSameAppButtonTarget
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
function cacheFileName(grammarSha256, semanticRulesSha256, compilerIdentity, components, children, instances, groups, conditions) {
|
|
982
|
+
const selectionHash = createHash("sha256")
|
|
983
|
+
.update(`${semanticRulesSha256}|${compilerIdentity}|${components.join(",")}|${children.join(",")}|${instances.map(({ selector, value }) => `${selector}=${value}`).join(",")}|${groups.join(",")}|${conditions.map(({ selector, value }) => `${selector}=${value}`).join(",")}`)
|
|
984
|
+
.digest("hex")
|
|
985
|
+
.slice(0, 16);
|
|
986
|
+
return `${grammarSha256}-${selectionHash}.json`;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* Return a compact EBNF closure for explicitly selected component productions.
|
|
991
|
+
* Child selector rules are retained as summaries but recursively expanded only
|
|
992
|
+
* for child components included in the explicit selection.
|
|
993
|
+
*/
|
|
994
|
+
export async function buildGrammarContract({
|
|
995
|
+
components,
|
|
996
|
+
children = [],
|
|
997
|
+
instances = [],
|
|
998
|
+
groups = [],
|
|
999
|
+
conditions = [],
|
|
1000
|
+
cacheDir = "",
|
|
1001
|
+
useCache = true,
|
|
1002
|
+
compilerOracleHome = ""
|
|
1003
|
+
} = {}) {
|
|
1004
|
+
const requestedComponents = normalizeComponents(components || []);
|
|
1005
|
+
const requestedGroups = normalizeComponents(groups || []);
|
|
1006
|
+
const requestedChildren = normalizeChildren(children || [], requestedComponents);
|
|
1007
|
+
const requestedInstances = normalizeInstances(instances || [], requestedChildren);
|
|
1008
|
+
if (requestedComponents.length === 0) {
|
|
1009
|
+
throw new Error("At least one component is required");
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const layout = resolveLayout();
|
|
1013
|
+
const sourcePath = layout.grammarPath;
|
|
1014
|
+
const grammarText = await fs.readFile(sourcePath, "utf8");
|
|
1015
|
+
const grammarSha256 = createHash("sha256").update(grammarText).digest("hex");
|
|
1016
|
+
const semanticRulesText = await fs.readFile(layout.semanticRulesPath, "utf8");
|
|
1017
|
+
const semanticRulesSha256 = createHash("sha256").update(semanticRulesText).digest("hex");
|
|
1018
|
+
const semanticRulesCatalog = JSON.parse(semanticRulesText);
|
|
1019
|
+
const productions = parseProductions(grammarText);
|
|
1020
|
+
const unknownComponents = requestedComponents.filter((name) => !productions.has(name));
|
|
1021
|
+
if (unknownComponents.length > 0) {
|
|
1022
|
+
throw new Error(`Unknown grammar component production(s): ${unknownComponents.join(", ")}`);
|
|
1023
|
+
}
|
|
1024
|
+
const selectedConditions = normalizeConditions(conditions || [], requestedComponents);
|
|
1025
|
+
const compilerContract = await buildCompilerContract(
|
|
1026
|
+
layout,
|
|
1027
|
+
productions,
|
|
1028
|
+
requestedComponents,
|
|
1029
|
+
requestedChildren,
|
|
1030
|
+
requestedInstances,
|
|
1031
|
+
requestedGroups,
|
|
1032
|
+
selectedConditions,
|
|
1033
|
+
compilerOracleHome
|
|
1034
|
+
);
|
|
1035
|
+
compilerContract.grammarConformance = grammarCompilerConformance(productions, compilerContract);
|
|
1036
|
+
const compilerIdentity = compilerContract.status === "resolved"
|
|
1037
|
+
? `${compilerContract.buildID}:${compilerContract.metadataHash}`
|
|
1038
|
+
: `unavailable:${compilerContract.reason}`;
|
|
1039
|
+
|
|
1040
|
+
const resolvedCacheDir = cacheDir
|
|
1041
|
+
? path.resolve(cacheDir)
|
|
1042
|
+
: layout.packaged
|
|
1043
|
+
? path.join(path.resolve(process.env.APEXLANG_OUTPUT_ROOT || process.cwd()), ".apexlang-cache", "grammar-contracts")
|
|
1044
|
+
: path.join(layout.root, "artifacts", "apexlang", "cache", "grammar-contracts");
|
|
1045
|
+
const cachePath = path.join(resolvedCacheDir, cacheFileName(grammarSha256, semanticRulesSha256, compilerIdentity, requestedComponents, requestedChildren, requestedInstances, requestedGroups, selectedConditions));
|
|
1046
|
+
if (useCache) {
|
|
1047
|
+
try {
|
|
1048
|
+
const cached = JSON.parse(await fs.readFile(cachePath, "utf8"));
|
|
1049
|
+
return { contract: cached, cachePath, cacheStatus: "hit" };
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
if (error?.code !== "ENOENT") {
|
|
1052
|
+
throw error;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const resolvedGrammarChildren = compilerContract.resolvedChildren.map(({ grammarProduction }) => grammarProduction);
|
|
1058
|
+
const closure = productionClosure(
|
|
1059
|
+
productions,
|
|
1060
|
+
[...requestedComponents, ...resolvedGrammarChildren],
|
|
1061
|
+
requestedGroups,
|
|
1062
|
+
DEFAULT_CORE_PRODUCTIONS
|
|
1063
|
+
);
|
|
1064
|
+
let narrowedEnumCount = 0;
|
|
1065
|
+
const contractProductions = closure.names.map((name) => {
|
|
1066
|
+
const narrowed = narrowDefinition(name, productions.get(name), selectedConditions, requestedComponents);
|
|
1067
|
+
narrowedEnumCount += narrowed.narrowedEnums;
|
|
1068
|
+
return { name, definition: narrowed.definition };
|
|
1069
|
+
});
|
|
1070
|
+
const semanticRules = selectSemanticRules(semanticRulesCatalog, requestedComponents, selectedConditions);
|
|
1071
|
+
const checklist = generationChecklist(semanticRules);
|
|
1072
|
+
const contract = {
|
|
1073
|
+
schemaVersion: CONTRACT_SCHEMA_VERSION,
|
|
1074
|
+
purpose: "Task-scoped APEXlang syntax contract; compiler truth remains authoritative for semantic legality.",
|
|
1075
|
+
grammar: {
|
|
1076
|
+
sha256: grammarSha256,
|
|
1077
|
+
source: layout.packaged
|
|
1078
|
+
? "assets/grammar/apexlang.ebnf"
|
|
1079
|
+
: "assets/grammar/apexlang.ebnf"
|
|
1080
|
+
},
|
|
1081
|
+
requestedComponents,
|
|
1082
|
+
requestedChildren,
|
|
1083
|
+
requestedInstances,
|
|
1084
|
+
requestedGroups,
|
|
1085
|
+
selectedConditions,
|
|
1086
|
+
semanticContract: {
|
|
1087
|
+
source: layout.packaged
|
|
1088
|
+
? "assets/contracts/grammar-semantic-rules.json"
|
|
1089
|
+
: "assets/contracts/grammar-semantic-rules.json",
|
|
1090
|
+
sha256: semanticRulesSha256,
|
|
1091
|
+
rules: semanticRules,
|
|
1092
|
+
generationChecklist: checklist
|
|
1093
|
+
},
|
|
1094
|
+
compilerContract,
|
|
1095
|
+
expansionPolicy: {
|
|
1096
|
+
mode: "compiler-resolved-child-closure",
|
|
1097
|
+
description: "Child selectors remain summaries; compiler-compatible child variants are resolved from parent assumptions and expanded only when one structural match is unambiguous."
|
|
1098
|
+
},
|
|
1099
|
+
allowedChildren: closure.allowedChildren,
|
|
1100
|
+
allowedGroups: closure.allowedGroups,
|
|
1101
|
+
productions: contractProductions,
|
|
1102
|
+
stats: {
|
|
1103
|
+
productionCount: contractProductions.length,
|
|
1104
|
+
definitionCharacters: contractProductions.reduce((total, item) => total + item.definition.length, 0),
|
|
1105
|
+
narrowedEnumCount,
|
|
1106
|
+
semanticRuleCount: semanticRules.length,
|
|
1107
|
+
checklistCount: checklist.length,
|
|
1108
|
+
semanticContractCharacters: JSON.stringify({ rules: semanticRules, generationChecklist: checklist }).length,
|
|
1109
|
+
compilerContractCharacters: JSON.stringify(compilerContract).length,
|
|
1110
|
+
fullGrammarCharacters: grammarText.length
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1114
|
+
if (useCache) {
|
|
1115
|
+
await ensureDir(resolvedCacheDir);
|
|
1116
|
+
await fs.writeFile(cachePath, `${JSON.stringify(contract, null, 2)}\n`, "utf8");
|
|
1117
|
+
}
|
|
1118
|
+
return { contract, cachePath, cacheStatus: useCache ? "miss" : "disabled" };
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/** Audit deterministic MUST checks for one generated artifact. */
|
|
1122
|
+
export async function auditGrammarContract({ contract, artifactPath } = {}) {
|
|
1123
|
+
if (!contract?.semanticContract?.generationChecklist) {
|
|
1124
|
+
throw new Error("A grammar contract with semanticContract.generationChecklist is required");
|
|
1125
|
+
}
|
|
1126
|
+
if (!artifactPath) {
|
|
1127
|
+
throw new Error("An artifact path is required");
|
|
1128
|
+
}
|
|
1129
|
+
const resolvedArtifactPath = path.resolve(artifactPath);
|
|
1130
|
+
const text = await fs.readFile(resolvedArtifactPath, "utf8");
|
|
1131
|
+
const checks = [];
|
|
1132
|
+
const outOfScope = [];
|
|
1133
|
+
const decisions = [];
|
|
1134
|
+
for (const item of contract.semanticContract.generationChecklist) {
|
|
1135
|
+
if (item.level === "decision") {
|
|
1136
|
+
decisions.push({ ...item, status: "intent-dependent" });
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
if (item.scope !== "page-local") {
|
|
1140
|
+
outOfScope.push({ ...item, status: "out-of-artifact-scope" });
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
const check = CONTRACT_CHECKS[item.check];
|
|
1144
|
+
if (!check) {
|
|
1145
|
+
checks.push({ ...item, status: "unverified", message: `No deterministic check is registered for ${item.check}.` });
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
const result = check(resolvedArtifactPath, text);
|
|
1149
|
+
checks.push({ ...item, status: result.passed ? "passed" : "failed", message: result.message });
|
|
1150
|
+
}
|
|
1151
|
+
const failed = checks.filter(({ status }) => status !== "passed");
|
|
1152
|
+
return {
|
|
1153
|
+
schemaVersion: 1,
|
|
1154
|
+
artifactPath: resolvedArtifactPath,
|
|
1155
|
+
status: failed.length === 0 ? "passed" : "failed",
|
|
1156
|
+
checks,
|
|
1157
|
+
decisions,
|
|
1158
|
+
outOfScope,
|
|
1159
|
+
summary: {
|
|
1160
|
+
passed: checks.filter(({ status }) => status === "passed").length,
|
|
1161
|
+
failed: failed.length,
|
|
1162
|
+
decisions: decisions.length,
|
|
1163
|
+
outOfScope: outOfScope.length
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
}
|