domainforge 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (481) hide show
  1. package/.cargo/config.toml +6 -0
  2. package/.claude/settings.local.json +18 -0
  3. package/.coderabbit.yml +43 -0
  4. package/.codex/skills/release-management/SKILL.md +151 -0
  5. package/.codex/skills/release-management/agents/openai.yaml +4 -0
  6. package/.github/actions/decrypt-secrets/action.yml +121 -0
  7. package/.github/agents/Coder.agent.md +97 -0
  8. package/.github/agents/DeepResearch.agent.md +61 -0
  9. package/.github/chatmodes/tdd.vibepro.chatmode.md +1183 -0
  10. package/.github/copilot-instructions.md +13 -0
  11. package/.github/dependabot.yml +68 -0
  12. package/.github/workflows/README.md +165 -0
  13. package/.github/workflows/ci.yml +335 -0
  14. package/.github/workflows/dependabot-automerge.yml +114 -0
  15. package/.github/workflows/dependency-review.yml +27 -0
  16. package/.github/workflows/deploy.yml +87 -0
  17. package/.github/workflows/prepare-release.yml +168 -0
  18. package/.github/workflows/release-crates.yml +42 -0
  19. package/.github/workflows/release-npm.yml +137 -0
  20. package/.github/workflows/release-please.yml +29 -0
  21. package/.github/workflows/release-pypi.yml +96 -0
  22. package/.gitkeep +1 -0
  23. package/.release-please-manifest.json +5 -0
  24. package/.sea-registry.toml +10 -0
  25. package/.serena/project.yml +133 -0
  26. package/.sops.yaml +10 -0
  27. package/AGENTS.md +216 -0
  28. package/CHANGELOG.md +400 -0
  29. package/CLAUDE.md +62 -0
  30. package/CONTRIBUTING.md +323 -0
  31. package/Cargo.lock +3612 -0
  32. package/Cargo.toml +12 -0
  33. package/LICENSE +201 -0
  34. package/README.md +660 -0
  35. package/README_PYTHON.md +256 -0
  36. package/README_TYPESCRIPT.md +305 -0
  37. package/README_WASM.md +329 -0
  38. package/RELEASE_NOTES.md +41 -0
  39. package/bun.lock +378 -0
  40. package/bunfig.toml +11 -0
  41. package/check_output.txt +83 -0
  42. package/clippy_output.txt +80 -0
  43. package/commitlint.config.cjs +8 -0
  44. package/deny.toml +42 -0
  45. package/devbox.json +14 -0
  46. package/devbox.lock +76 -0
  47. package/docs/RELEASE_PROCESS.md +360 -0
  48. package/docs/diagnostics.md +161 -0
  49. package/docs/doc_guidelines.md +53 -0
  50. package/docs/explanations/README.md +21 -0
  51. package/docs/explanations/architecture-overview.md +109 -0
  52. package/docs/explanations/cross-language-binding-strategy.md +68 -0
  53. package/docs/explanations/graph-store-design.md +47 -0
  54. package/docs/explanations/performance-benchmarks.md +63 -0
  55. package/docs/explanations/policy-evaluation-logic.md +106 -0
  56. package/docs/explanations/semantic-modeling-concepts.md +109 -0
  57. package/docs/explanations/three-valued-logic.md +66 -0
  58. package/docs/explanations/versioning-strategy.md +45 -0
  59. package/docs/governance.md +168 -0
  60. package/docs/how-tos/README.md +46 -0
  61. package/docs/how-tos/ci-cd-validation.md +93 -0
  62. package/docs/how-tos/create-custom-units.md +125 -0
  63. package/docs/how-tos/define-policies.md +119 -0
  64. package/docs/how-tos/export-to-calm.md +110 -0
  65. package/docs/how-tos/export-to-protobuf.md +312 -0
  66. package/docs/how-tos/extend-grammar.md +133 -0
  67. package/docs/how-tos/generate-rdf-turtle.md +106 -0
  68. package/docs/how-tos/import-from-calm.md +114 -0
  69. package/docs/how-tos/import-from-sbvr.md +249 -0
  70. package/docs/how-tos/install-cli.md +126 -0
  71. package/docs/how-tos/parse-sea-files.md +132 -0
  72. package/docs/how-tos/policy-evaluation-modes.md +30 -0
  73. package/docs/how-tos/run-cross-language-tests.md +115 -0
  74. package/docs/how-tos/troubleshoot-napi-builds.md +55 -0
  75. package/docs/how-tos/use-modules-imports.md +285 -0
  76. package/docs/index.md +13 -0
  77. package/docs/plans/canonical-normalizer.md +121 -0
  78. package/docs/plans/cd_improvement.md +112 -0
  79. package/docs/plans/cli-ast.md +29 -0
  80. package/docs/plans/expression-bindings-and-normalizer-integration.md +174 -0
  81. package/docs/plans/protobuf_advanced_features_plan.md +597 -0
  82. package/docs/plans/protobuf_plan.yml +525 -0
  83. package/docs/plans/refactor_dsl_architecture.md +131 -0
  84. package/docs/plans/release-plan.md +163 -0
  85. package/docs/plans/sea_fmt_implementation_plan.md +516 -0
  86. package/docs/playbooks/README.md +18 -0
  87. package/docs/playbooks/adding-new-primitive.md +68 -0
  88. package/docs/playbooks/debugging-parser-failures.md +42 -0
  89. package/docs/playbooks/local-release-preparation.md +139 -0
  90. package/docs/playbooks/migrating-schema-versions.md +43 -0
  91. package/docs/playbooks/onboarding-contributors.md +64 -0
  92. package/docs/playbooks/releasing-beta.md +86 -0
  93. package/docs/playbooks/secret-management.md +64 -0
  94. package/docs/reference/README.md +199 -0
  95. package/docs/reference/ast-json-api.md +427 -0
  96. package/docs/reference/calm-mapping.md +519 -0
  97. package/docs/reference/cli-commands.md +588 -0
  98. package/docs/reference/configuration.md +202 -0
  99. package/docs/reference/error-codes.md +664 -0
  100. package/docs/reference/generated-artifacts-policy.md +53 -0
  101. package/docs/reference/grammar-spec.md +255 -0
  102. package/docs/reference/primitives-api.md +317 -0
  103. package/docs/reference/protobuf-api.md +426 -0
  104. package/docs/reference/python-api.md +485 -0
  105. package/docs/reference/registry.md +50 -0
  106. package/docs/reference/sea-dsl-ai-cheatsheet.yaml +913 -0
  107. package/docs/reference/security-model.md +74 -0
  108. package/docs/reference/typescript-api.md +508 -0
  109. package/docs/reference/wasm-api.md +420 -0
  110. package/docs/semantic-pack-review.md +144 -0
  111. package/docs/semantic-pack-signing.md +234 -0
  112. package/docs/semantic-packs.md +284 -0
  113. package/docs/specs/ADR-001-sea-dsl-semantic-source-of-truth.md +33 -0
  114. package/docs/specs/ADR-002-projection-first-class-construct.md +50 -0
  115. package/docs/specs/ADR-003-protobuf-projection-target.md +51 -0
  116. package/docs/specs/ADR-004-projection-compatibility-semantics.md +57 -0
  117. package/docs/specs/ADR-005-multi-language-support-strategy.md +112 -0
  118. package/docs/specs/ADR-006-error-handling-strategy.md +115 -0
  119. package/docs/specs/ADR-007-policy-evaluation-engine.md +95 -0
  120. package/docs/specs/ADR-008-knowledge-graph-integration.md +90 -0
  121. package/docs/specs/ADR-009-module-resolution-strategy.md +115 -0
  122. package/docs/specs/ADR-010-unit-system.md +106 -0
  123. package/docs/specs/PRD-001-sea-projection-framework.md +155 -0
  124. package/docs/specs/PRD-002-sea-cli-tooling.md +169 -0
  125. package/docs/specs/PRD-003-dsl-core-capabilities.md +275 -0
  126. package/docs/specs/README.md +62 -0
  127. package/docs/specs/SDS-001-protobuf-projection-engine.md +451 -0
  128. package/docs/specs/SDS-002-sea-core-architecture.md +268 -0
  129. package/docs/specs/SDS-003-parser-semantic-graph.md +377 -0
  130. package/docs/specs/SDS-004-policy-engine-design.md +362 -0
  131. package/docs/specs/SDS-005-knowledge-graph-module.md +364 -0
  132. package/docs/specs/SDS-006-calm-integration.md +367 -0
  133. package/docs/specs/SDS-007-sbvr-import.md +347 -0
  134. package/docs/templates/template_explanation.md +14 -0
  135. package/docs/templates/template_howto.md +21 -0
  136. package/docs/templates/template_playbook.md +21 -0
  137. package/docs/templates/template_reference.md +17 -0
  138. package/docs/templates/template_tutorial.md +24 -0
  139. package/docs/tutorials/README.md +12 -0
  140. package/docs/tutorials/first-sea-model.md +85 -0
  141. package/docs/tutorials/getting-started.md +98 -0
  142. package/docs/tutorials/python-binding-quickstart.md +107 -0
  143. package/docs/tutorials/typescript-binding-quickstart.md +91 -0
  144. package/docs/tutorials/wasm-in-browser.md +75 -0
  145. package/domainforge-core/CHANGELOG.md +138 -0
  146. package/domainforge-core/Cargo.toml +101 -0
  147. package/domainforge-core/MIGRATING.md +32 -0
  148. package/domainforge-core/README.md +197 -0
  149. package/domainforge-core/benchmark_results.txt +51 -0
  150. package/domainforge-core/build.rs +6 -0
  151. package/domainforge-core/deny.toml +31 -0
  152. package/domainforge-core/docs/specs/projections/sbvr_kg_mapping.md +43 -0
  153. package/domainforge-core/examples/basic.sea +7 -0
  154. package/domainforge-core/examples/cli/import_export_workflow.sh +38 -0
  155. package/domainforge-core/examples/cli/validate_example.sh +30 -0
  156. package/domainforge-core/examples/evolution_semantics.sea +31 -0
  157. package/domainforge-core/examples/parser_demo.rs +203 -0
  158. package/domainforge-core/grammar/sea.pest +408 -0
  159. package/domainforge-core/schemas/calm-v1.schema.json +170 -0
  160. package/domainforge-core/schemas/shacl/sea_shapes.ttl +19 -0
  161. package/domainforge-core/src/authority/compiler.rs +309 -0
  162. package/domainforge-core/src/authority/environment.rs +203 -0
  163. package/domainforge-core/src/authority/error.rs +164 -0
  164. package/domainforge-core/src/authority/fact_resolver.rs +224 -0
  165. package/domainforge-core/src/authority/mod.rs +25 -0
  166. package/domainforge-core/src/authority/pack.rs +133 -0
  167. package/domainforge-core/src/authority/policy.rs +224 -0
  168. package/domainforge-core/src/authority/resolver.rs +446 -0
  169. package/domainforge-core/src/authority/trace.rs +217 -0
  170. package/domainforge-core/src/authority/transform.rs +168 -0
  171. package/domainforge-core/src/authority/types.rs +617 -0
  172. package/domainforge-core/src/bin/domainforge.rs +25 -0
  173. package/domainforge-core/src/calm/export.rs +538 -0
  174. package/domainforge-core/src/calm/import.rs +1220 -0
  175. package/domainforge-core/src/calm/mod.rs +9 -0
  176. package/domainforge-core/src/calm/models.rs +108 -0
  177. package/domainforge-core/src/calm/sbvr_import.rs +9 -0
  178. package/domainforge-core/src/cli/authority.rs +149 -0
  179. package/domainforge-core/src/cli/format.rs +85 -0
  180. package/domainforge-core/src/cli/import.rs +133 -0
  181. package/domainforge-core/src/cli/mod.rs +64 -0
  182. package/domainforge-core/src/cli/normalize.rs +180 -0
  183. package/domainforge-core/src/cli/pack.rs +904 -0
  184. package/domainforge-core/src/cli/parse.rs +112 -0
  185. package/domainforge-core/src/cli/project.rs +294 -0
  186. package/domainforge-core/src/cli/registry.rs +41 -0
  187. package/domainforge-core/src/cli/test.rs +12 -0
  188. package/domainforge-core/src/cli/validate.rs +195 -0
  189. package/domainforge-core/src/cli/validate_kg.rs +80 -0
  190. package/domainforge-core/src/concept_id.rs +89 -0
  191. package/domainforge-core/src/error/diagnostics.rs +426 -0
  192. package/domainforge-core/src/error/fuzzy.rs +253 -0
  193. package/domainforge-core/src/error/mod.rs +13 -0
  194. package/domainforge-core/src/formatter/comments.rs +223 -0
  195. package/domainforge-core/src/formatter/config.rs +114 -0
  196. package/domainforge-core/src/formatter/mod.rs +22 -0
  197. package/domainforge-core/src/formatter/printer.rs +906 -0
  198. package/domainforge-core/src/graph/mod.rs +858 -0
  199. package/domainforge-core/src/graph/to_ast.rs +66 -0
  200. package/domainforge-core/src/kg.rs +1476 -0
  201. package/domainforge-core/src/kg_import.rs +251 -0
  202. package/domainforge-core/src/lib.rs +203 -0
  203. package/domainforge-core/src/module/mod.rs +1 -0
  204. package/domainforge-core/src/module/resolver.rs +260 -0
  205. package/domainforge-core/src/parser/ast.rs +2919 -0
  206. package/domainforge-core/src/parser/ast_convert.rs +494 -0
  207. package/domainforge-core/src/parser/ast_schema.rs +491 -0
  208. package/domainforge-core/src/parser/error.rs +291 -0
  209. package/domainforge-core/src/parser/lint.rs +39 -0
  210. package/domainforge-core/src/parser/mod.rs +193 -0
  211. package/domainforge-core/src/parser/printer.rs +702 -0
  212. package/domainforge-core/src/parser/profiles.rs +71 -0
  213. package/domainforge-core/src/parser/string_utils.rs +138 -0
  214. package/domainforge-core/src/patterns.rs +68 -0
  215. package/domainforge-core/src/policy/core.rs +1148 -0
  216. package/domainforge-core/src/policy/expression.rs +399 -0
  217. package/domainforge-core/src/policy/mod.rs +18 -0
  218. package/domainforge-core/src/policy/normalize.rs +1028 -0
  219. package/domainforge-core/src/policy/quantifier.rs +940 -0
  220. package/domainforge-core/src/policy/three_valued.rs +140 -0
  221. package/domainforge-core/src/policy/three_valued_microbench.rs +104 -0
  222. package/domainforge-core/src/policy/type_inference.rs +67 -0
  223. package/domainforge-core/src/policy/violation.rs +36 -0
  224. package/domainforge-core/src/primitives/concept_change.rs +61 -0
  225. package/domainforge-core/src/primitives/entity.rs +224 -0
  226. package/domainforge-core/src/primitives/flow.rs +111 -0
  227. package/domainforge-core/src/primitives/instance.rs +93 -0
  228. package/domainforge-core/src/primitives/mapping_contract.rs +50 -0
  229. package/domainforge-core/src/primitives/metric.rs +79 -0
  230. package/domainforge-core/src/primitives/mod.rs +25 -0
  231. package/domainforge-core/src/primitives/projection_contract.rs +50 -0
  232. package/domainforge-core/src/primitives/quantity.rs +56 -0
  233. package/domainforge-core/src/primitives/relation.rs +68 -0
  234. package/domainforge-core/src/primitives/resource.rs +237 -0
  235. package/domainforge-core/src/primitives/resource_instance.rs +88 -0
  236. package/domainforge-core/src/primitives/role.rs +49 -0
  237. package/domainforge-core/src/projection/buf.rs +404 -0
  238. package/domainforge-core/src/projection/contracts.rs +22 -0
  239. package/domainforge-core/src/projection/engine.rs +19 -0
  240. package/domainforge-core/src/projection/mod.rs +16 -0
  241. package/domainforge-core/src/projection/protobuf.rs +3331 -0
  242. package/domainforge-core/src/projection/registry.rs +43 -0
  243. package/domainforge-core/src/python/authority.rs +253 -0
  244. package/domainforge-core/src/python/error.rs +227 -0
  245. package/domainforge-core/src/python/formatter.rs +86 -0
  246. package/domainforge-core/src/python/graph.rs +366 -0
  247. package/domainforge-core/src/python/mod.rs +9 -0
  248. package/domainforge-core/src/python/policy.rs +651 -0
  249. package/domainforge-core/src/python/primitives.rs +796 -0
  250. package/domainforge-core/src/python/registry.rs +98 -0
  251. package/domainforge-core/src/python/semantic_pack.rs +619 -0
  252. package/domainforge-core/src/python/units.rs +96 -0
  253. package/domainforge-core/src/registry/mod.rs +432 -0
  254. package/domainforge-core/src/registry/tests.rs +210 -0
  255. package/domainforge-core/src/sbvr.rs +744 -0
  256. package/domainforge-core/src/semantic_pack/builder.rs +470 -0
  257. package/domainforge-core/src/semantic_pack/canonical_json.rs +184 -0
  258. package/domainforge-core/src/semantic_pack/diagnostics.rs +214 -0
  259. package/domainforge-core/src/semantic_pack/diff.rs +216 -0
  260. package/domainforge-core/src/semantic_pack/mod.rs +31 -0
  261. package/domainforge-core/src/semantic_pack/pack_set.rs +240 -0
  262. package/domainforge-core/src/semantic_pack/resolver.rs +437 -0
  263. package/domainforge-core/src/semantic_pack/review.rs +125 -0
  264. package/domainforge-core/src/semantic_pack/schema.rs +342 -0
  265. package/domainforge-core/src/semantic_pack/signing.rs +105 -0
  266. package/domainforge-core/src/semantic_pack/validator.rs +368 -0
  267. package/domainforge-core/src/semantic_version.rs +140 -0
  268. package/domainforge-core/src/test_utils.rs +12 -0
  269. package/domainforge-core/src/typescript/authority.rs +184 -0
  270. package/domainforge-core/src/typescript/error.rs +146 -0
  271. package/domainforge-core/src/typescript/formatter.rs +76 -0
  272. package/domainforge-core/src/typescript/graph.rs +391 -0
  273. package/domainforge-core/src/typescript/mod.rs +9 -0
  274. package/domainforge-core/src/typescript/policy.rs +564 -0
  275. package/domainforge-core/src/typescript/primitives.rs +784 -0
  276. package/domainforge-core/src/typescript/registry.rs +88 -0
  277. package/domainforge-core/src/typescript/semantic_pack.rs +470 -0
  278. package/domainforge-core/src/typescript/units.rs +76 -0
  279. package/domainforge-core/src/units/mod.rs +462 -0
  280. package/domainforge-core/src/uuid_module.rs +42 -0
  281. package/domainforge-core/src/validation_error.rs +818 -0
  282. package/domainforge-core/src/validation_result.rs +30 -0
  283. package/domainforge-core/src/wasm/authority.rs +192 -0
  284. package/domainforge-core/src/wasm/error.rs +145 -0
  285. package/domainforge-core/src/wasm/formatter.rs +69 -0
  286. package/domainforge-core/src/wasm/graph.rs +471 -0
  287. package/domainforge-core/src/wasm/mod.rs +16 -0
  288. package/domainforge-core/src/wasm/policy.rs +607 -0
  289. package/domainforge-core/src/wasm/primitives.rs +295 -0
  290. package/domainforge-core/src/wasm/semantic_pack.rs +471 -0
  291. package/domainforge-core/src/wasm/units.rs +62 -0
  292. package/domainforge-core/std/aws.sea +6 -0
  293. package/domainforge-core/std/core.sea +6 -0
  294. package/domainforge-core/std/http.sea +27 -0
  295. package/domainforge-core/tests/aggregation_enhanced_tests.rs +162 -0
  296. package/domainforge-core/tests/aggregation_eval_tests.rs +248 -0
  297. package/domainforge-core/tests/aggregation_integration_tests.rs +379 -0
  298. package/domainforge-core/tests/aggregation_parser_tests.rs +92 -0
  299. package/domainforge-core/tests/aggregation_tests.rs +102 -0
  300. package/domainforge-core/tests/authority_conformance_tests.rs +1173 -0
  301. package/domainforge-core/tests/calm_round_trip_tests.rs +283 -0
  302. package/domainforge-core/tests/calm_schema_validation_tests.rs +137 -0
  303. package/domainforge-core/tests/cast_operator_tests.rs +85 -0
  304. package/domainforge-core/tests/cli_binary_check.rs +37 -0
  305. package/domainforge-core/tests/cli_import_tests.rs +291 -0
  306. package/domainforge-core/tests/cli_path_traversal_tests.rs +124 -0
  307. package/domainforge-core/tests/cli_tests.rs +63 -0
  308. package/domainforge-core/tests/diagnostics_tests.rs +203 -0
  309. package/domainforge-core/tests/dimension_unit_tests.rs +80 -0
  310. package/domainforge-core/tests/entity_tests.rs +69 -0
  311. package/domainforge-core/tests/evolution_semantics_tests.rs +157 -0
  312. package/domainforge-core/tests/flow_tests.rs +78 -0
  313. package/domainforge-core/tests/flow_unit_validation_tests.rs +31 -0
  314. package/domainforge-core/tests/graph_integration_tests.rs +218 -0
  315. package/domainforge-core/tests/graph_tests.rs +626 -0
  316. package/domainforge-core/tests/import_parsing_tests.rs +23 -0
  317. package/domainforge-core/tests/instance_integration_tests.rs +98 -0
  318. package/domainforge-core/tests/instance_parsing_tests.rs +58 -0
  319. package/domainforge-core/tests/instance_tests.rs +61 -0
  320. package/domainforge-core/tests/kg_uri_encoding_tests.rs +53 -0
  321. package/domainforge-core/tests/lint_tests.rs +19 -0
  322. package/domainforge-core/tests/metric_tests.rs +143 -0
  323. package/domainforge-core/tests/module_resolution_tests.rs +100 -0
  324. package/domainforge-core/tests/namespace_registry_tests.rs +247 -0
  325. package/domainforge-core/tests/null_handling_tests.rs +26 -0
  326. package/domainforge-core/tests/parser_ast_v3.rs +53 -0
  327. package/domainforge-core/tests/parser_dimension_registry_tests.rs +20 -0
  328. package/domainforge-core/tests/parser_integration_tests.rs +294 -0
  329. package/domainforge-core/tests/parser_metadata_tests.rs +97 -0
  330. package/domainforge-core/tests/parser_resource_domain_only_graph_test.rs +21 -0
  331. package/domainforge-core/tests/parser_resource_limits_tests.rs +122 -0
  332. package/domainforge-core/tests/parser_tests.rs +512 -0
  333. package/domainforge-core/tests/pattern_semantics_tests.rs +87 -0
  334. package/domainforge-core/tests/phase_14_determinism_tests.rs +166 -0
  335. package/domainforge-core/tests/phase_15_validation_error_tests.rs +136 -0
  336. package/domainforge-core/tests/phase_16_unicode_tests.rs +248 -0
  337. package/domainforge-core/tests/phase_17_export_tests.rs +285 -0
  338. package/domainforge-core/tests/phase_17_round_trip_tests.rs +264 -0
  339. package/domainforge-core/tests/policy_tests.rs +635 -0
  340. package/domainforge-core/tests/primitives_integration_tests.rs +151 -0
  341. package/domainforge-core/tests/print_rdf_xml.rs +14 -0
  342. package/domainforge-core/tests/printer_tests.rs +204 -0
  343. package/domainforge-core/tests/profile_tests.rs +35 -0
  344. package/domainforge-core/tests/projection_contracts_tests.rs +154 -0
  345. package/domainforge-core/tests/protobuf_projection_tests.rs +199 -0
  346. package/domainforge-core/tests/quantity_tests.rs +41 -0
  347. package/domainforge-core/tests/rdf_xml_typed_literal_tests.rs +105 -0
  348. package/domainforge-core/tests/registry_schema_tests.rs +33 -0
  349. package/domainforge-core/tests/resource_tests.rs +50 -0
  350. package/domainforge-core/tests/resource_unit_tests.rs +24 -0
  351. package/domainforge-core/tests/roles_relations_tests.rs +61 -0
  352. package/domainforge-core/tests/round_trip_tests.rs +34 -0
  353. package/domainforge-core/tests/runtime_toggle_tests.rs +70 -0
  354. package/domainforge-core/tests/sbvr_fact_schema_tests.rs +60 -0
  355. package/domainforge-core/tests/sbvr_flow_facts_tests.rs +55 -0
  356. package/domainforge-core/tests/sbvr_parsing_tests.rs +53 -0
  357. package/domainforge-core/tests/semantic_pack_alias_resolution.rs +197 -0
  358. package/domainforge-core/tests/semantic_pack_build.rs +302 -0
  359. package/domainforge-core/tests/semantic_pack_consumer_smoke.rs +150 -0
  360. package/domainforge-core/tests/semantic_pack_pack_set.rs +160 -0
  361. package/domainforge-core/tests/semantic_pack_signing.rs +157 -0
  362. package/domainforge-core/tests/semantic_pack_three_valued.rs +250 -0
  363. package/domainforge-core/tests/semantic_pack_validate.rs +196 -0
  364. package/domainforge-core/tests/std_lib_tests.rs +37 -0
  365. package/domainforge-core/tests/temporal_evaluation_tests.rs +159 -0
  366. package/domainforge-core/tests/temporal_semantics_tests.rs +214 -0
  367. package/domainforge-core/tests/three_valued_quantifiers_tests.rs +164 -0
  368. package/domainforge-core/tests/turtle_entity_export_tests.rs +38 -0
  369. package/domainforge-core/tests/turtle_escaping_tests.rs +53 -0
  370. package/domainforge-core/tests/turtle_resource_export_tests.rs +34 -0
  371. package/domainforge-core/tests/type_inference_tests.rs +40 -0
  372. package/domainforge-core/tests/unicode_validation_tests.rs +169 -0
  373. package/domainforge-core/tests/unit_tests.rs +81 -0
  374. package/domainforge-core/tests/validate_tests.rs +38 -0
  375. package/domainforge-core/tests/validation_unit_mismatch_tests.rs +83 -0
  376. package/domainforge-core/tests/wasm_tests.rs +229 -0
  377. package/domainforge-python/CHANGELOG-python.md +12 -0
  378. package/domainforge-python/MIGRATING.md +24 -0
  379. package/domainforge-python/README.md +256 -0
  380. package/domainforge-python/domainforge/__init__.py +95 -0
  381. package/domainforge-python/domainforge/domainforge.pyi +519 -0
  382. package/domainforge-python/pyproject.toml +36 -0
  383. package/domainforge-typescript/CHANGELOG-typescript.md +12 -0
  384. package/domainforge-typescript/LICENSE +201 -0
  385. package/domainforge-typescript/MIGRATING.md +24 -0
  386. package/domainforge-typescript/README.md +305 -0
  387. package/domainforge-typescript/index.d.ts +452 -0
  388. package/domainforge-typescript/index.js +361 -0
  389. package/domainforge-typescript/package.json +60 -0
  390. package/example.js +61 -0
  391. package/examples/browser.html +366 -0
  392. package/examples/namespaces/finance/cashflow.sea +5 -0
  393. package/examples/namespaces/logistics/core.sea +7 -0
  394. package/examples/observability_metrics.sea +38 -0
  395. package/fixtures/semantic_packs/acme_procurement/domain/entities.sea +39 -0
  396. package/fixtures/semantic_packs/acme_procurement/domain/metrics.sea +11 -0
  397. package/fixtures/semantic_packs/acme_procurement/domain/relations.sea +7 -0
  398. package/fixtures/semantic_packs/acme_procurement/domain/resources.sea +9 -0
  399. package/fixtures/semantic_packs/acme_procurement/review/acme.procurement.semantic-review.jsonl +7 -0
  400. package/fixtures/semantic_packs/acme_procurement/tests/ambiguous_vendor_alias.sea +8 -0
  401. package/fixtures/semantic_packs/acme_procurement/tests/deprecated_vendor_alias.sea +8 -0
  402. package/fixtures/semantic_packs/acme_procurement/tests/invalid_relation.sea +3 -0
  403. package/fixtures/semantic_packs/acme_procurement/tests/proposed_concept.sea +8 -0
  404. package/fixtures/semantic_packs/acme_procurement/tests/rejected_concept.sea +8 -0
  405. package/fixtures/semantic_packs/acme_procurement/tests/unit_mismatch.sea +7 -0
  406. package/fixtures/semantic_packs/acme_procurement/tests/unknown_vendor_policy.sea +8 -0
  407. package/fixtures/semantic_packs/acme_procurement/tests/valid_purchase_policy.sea +8 -0
  408. package/index.d.ts +2 -0
  409. package/index.js +8 -0
  410. package/justfile +200 -0
  411. package/lefthook.yml +13 -0
  412. package/lib/validate_native_exports.d.ts +4 -0
  413. package/lib/validate_native_exports.js +12 -0
  414. package/package.json +22 -0
  415. package/pytest.ini +5 -0
  416. package/python/tests/test_registry.py +75 -0
  417. package/python/tests/test_units.py +18 -0
  418. package/release-please-config.json +49 -0
  419. package/requirements-dev.txt +3 -0
  420. package/requirements.txt +3 -0
  421. package/rust-toolchain.toml +3 -0
  422. package/schemas/ast-v1.schema.json +72 -0
  423. package/schemas/ast-v2.schema.json +1200 -0
  424. package/schemas/ast-v3.schema.json +1200 -0
  425. package/schemas/sea-registry.schema.json +45 -0
  426. package/scripts/build-python.sh +37 -0
  427. package/scripts/build-release.sh +279 -0
  428. package/scripts/build-typescript.sh +13 -0
  429. package/scripts/build-wasm.sh +113 -0
  430. package/scripts/bump-version.sh +245 -0
  431. package/scripts/check_unused_test_imports.py +85 -0
  432. package/scripts/ci_tasks.py +379 -0
  433. package/scripts/clear_debug_test.sh +10 -0
  434. package/scripts/create-github-release.sh +262 -0
  435. package/scripts/create-tag.sh +203 -0
  436. package/scripts/find_and_link_test_binary.sh +70 -0
  437. package/scripts/generate-changelog.sh +271 -0
  438. package/scripts/generate-release-notes.sh +205 -0
  439. package/scripts/lint_release_security.py +96 -0
  440. package/scripts/lint_release_workflows.py +82 -0
  441. package/scripts/lint_workflow_gates.py +113 -0
  442. package/scripts/optimized-wasm-build.sh +61 -0
  443. package/scripts/patch_napi_types.py +62 -0
  444. package/scripts/pre-release-check.sh +289 -0
  445. package/scripts/prepare_rust_debug.sh +52 -0
  446. package/scripts/release.sh +373 -0
  447. package/scripts/resolve_rust_binary.py +230 -0
  448. package/scripts/run_commitlint.sh +29 -0
  449. package/scripts/test-all.sh +77 -0
  450. package/scripts/update_launch_program.py +93 -0
  451. package/secrets/README.md +27 -0
  452. package/secrets/secrets.yaml +21 -0
  453. package/test_integration.py +67 -0
  454. package/tests/test_authority.py +328 -0
  455. package/tests/test_ci_tasks.py +143 -0
  456. package/tests/test_expression.py +256 -0
  457. package/tests/test_golden_payment_flow.py +42 -0
  458. package/tests/test_graph.py +127 -0
  459. package/tests/test_instance.py +136 -0
  460. package/tests/test_parser.py +82 -0
  461. package/tests/test_primitives.py +68 -0
  462. package/tests/test_role_relation_parity.py +56 -0
  463. package/tests/test_runtime_toggle.py +156 -0
  464. package/tests/test_semantic_pack.py +639 -0
  465. package/tests/test_three_valued_eval.py +159 -0
  466. package/tsconfig.json +30 -0
  467. package/typescript-tests/advanced.test.ts +165 -0
  468. package/typescript-tests/authority.test.ts +216 -0
  469. package/typescript-tests/expression.test.ts +228 -0
  470. package/typescript-tests/golden-payment-flow.test.ts +51 -0
  471. package/typescript-tests/graph.test.ts +142 -0
  472. package/typescript-tests/native-binding.test.ts +20 -0
  473. package/typescript-tests/primitives.test.ts +88 -0
  474. package/typescript-tests/registry.test.ts +122 -0
  475. package/typescript-tests/role_relation.test.ts +63 -0
  476. package/typescript-tests/runtime_toggle.test.ts +141 -0
  477. package/typescript-tests/semantic-pack.test.ts +556 -0
  478. package/typescript-tests/three_valued_eval.test.ts +135 -0
  479. package/typescript-tests/units.test.ts +36 -0
  480. package/vitest.config.ts +13 -0
  481. package/wasm_demo.html +225 -0
@@ -0,0 +1,2919 @@
1
+ use crate::graph::Graph;
2
+ use crate::parser::error::{ParseError, ParseResult};
3
+ use crate::parser::{ParseOptions, Rule, SeaParser};
4
+ use crate::patterns::Pattern;
5
+ use crate::policy::{
6
+ AggregateFunction, BinaryOp, Expression, Policy, PolicyKind as CorePolicyKind,
7
+ PolicyModality as CorePolicyModality, Quantifier as PolicyQuantifier, UnaryOp, WindowSpec,
8
+ };
9
+ use crate::primitives::{ConceptChange, Entity, Flow, RelationType, Resource, Role, Severity};
10
+ use crate::units::unit_from_string;
11
+ use crate::SemanticVersion;
12
+ use chrono::Duration;
13
+ use pest::iterators::{Pair, Pairs};
14
+ use pest::{Parser, Span};
15
+ use rust_decimal::prelude::ToPrimitive;
16
+ use rust_decimal::Decimal;
17
+ use serde::{Deserialize, Serialize};
18
+ use serde_json::json;
19
+ use serde_json::Value as JsonValue;
20
+ use std::collections::HashMap;
21
+
22
+ /// File-level metadata from header annotations
23
+ #[derive(Debug, Clone, PartialEq, Default)]
24
+ pub struct FileMetadata {
25
+ pub namespace: Option<String>,
26
+ pub version: Option<String>,
27
+ pub owner: Option<String>,
28
+ pub profile: Option<String>,
29
+ pub imports: Vec<ImportDecl>,
30
+ }
31
+
32
+ /// Import declaration for a module file
33
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34
+ pub struct ImportDecl {
35
+ pub specifier: ImportSpecifier,
36
+ pub from_module: String,
37
+ }
38
+
39
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40
+ pub enum ImportSpecifier {
41
+ Named(Vec<ImportItem>),
42
+ Wildcard(String),
43
+ }
44
+
45
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46
+ pub struct ImportItem {
47
+ pub name: String,
48
+ pub alias: Option<String>,
49
+ }
50
+
51
+ /// Policy metadata
52
+ #[derive(Debug, Clone, PartialEq)]
53
+ pub struct PolicyMetadata {
54
+ pub kind: Option<PolicyKind>,
55
+ pub modality: Option<PolicyModality>,
56
+ pub priority: Option<i32>,
57
+ pub rationale: Option<String>,
58
+ pub tags: Vec<String>,
59
+ }
60
+
61
+ #[derive(Debug, Clone, PartialEq)]
62
+ pub enum PolicyKind {
63
+ Constraint,
64
+ Derivation,
65
+ Obligation,
66
+ }
67
+
68
+ impl std::fmt::Display for PolicyKind {
69
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70
+ match self {
71
+ PolicyKind::Constraint => write!(f, "Constraint"),
72
+ PolicyKind::Derivation => write!(f, "Derivation"),
73
+ PolicyKind::Obligation => write!(f, "Obligation"),
74
+ }
75
+ }
76
+ }
77
+
78
+ #[derive(Debug, Clone, PartialEq)]
79
+ pub enum PolicyModality {
80
+ Obligation,
81
+ Prohibition,
82
+ Permission,
83
+ }
84
+
85
+ impl std::fmt::Display for PolicyModality {
86
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87
+ match self {
88
+ PolicyModality::Obligation => write!(f, "Obligation"),
89
+ PolicyModality::Prohibition => write!(f, "Prohibition"),
90
+ PolicyModality::Permission => write!(f, "Permission"),
91
+ }
92
+ }
93
+ }
94
+
95
+ /// Metric declaration AST node
96
+ #[derive(Debug, Clone, PartialEq)]
97
+ pub struct MetricMetadata {
98
+ pub refresh_interval: Option<Duration>,
99
+ pub unit: Option<String>,
100
+ pub threshold: Option<Decimal>,
101
+ pub severity: Option<Severity>,
102
+ pub target: Option<Decimal>,
103
+ pub window: Option<Duration>,
104
+ }
105
+
106
+ #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
107
+ pub enum TargetFormat {
108
+ Calm,
109
+ Kg,
110
+ Sbvr,
111
+ Protobuf,
112
+ }
113
+
114
+ impl std::fmt::Display for TargetFormat {
115
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116
+ match self {
117
+ TargetFormat::Calm => write!(f, "CALM"),
118
+ TargetFormat::Kg => write!(f, "KG"),
119
+ TargetFormat::Sbvr => write!(f, "SBVR"),
120
+ TargetFormat::Protobuf => write!(f, "Protobuf"),
121
+ }
122
+ }
123
+ }
124
+
125
+ #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
126
+ pub struct MappingRule {
127
+ pub primitive_type: String,
128
+ pub primitive_name: String,
129
+ pub target_type: String,
130
+ pub fields: HashMap<String, JsonValue>,
131
+ }
132
+
133
+ #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
134
+ pub struct ProjectionOverride {
135
+ pub primitive_type: String,
136
+ pub primitive_name: String,
137
+ pub fields: HashMap<String, JsonValue>,
138
+ }
139
+
140
+ /// Abstract Syntax Tree for SEA DSL
141
+ /// Abstract Syntax Tree for SEA DSL
142
+ #[derive(Debug, Clone, PartialEq)]
143
+ pub struct Spanned<T> {
144
+ pub node: T,
145
+ pub line: usize,
146
+ pub column: usize,
147
+ }
148
+
149
+ #[derive(Debug, Clone, PartialEq)]
150
+ pub struct Ast {
151
+ pub metadata: FileMetadata,
152
+ pub declarations: Vec<Spanned<AstNode>>,
153
+ }
154
+
155
+ /// AST Node types
156
+ #[derive(Debug, Clone, PartialEq)]
157
+ pub enum AstNode {
158
+ Export(Box<Spanned<AstNode>>),
159
+ Entity {
160
+ name: String,
161
+ version: Option<String>,
162
+ annotations: HashMap<String, JsonValue>,
163
+ domain: Option<String>,
164
+ },
165
+ Resource {
166
+ name: String,
167
+ annotations: HashMap<String, JsonValue>,
168
+ unit_name: Option<String>,
169
+ domain: Option<String>,
170
+ },
171
+ Flow {
172
+ resource_name: String,
173
+ annotations: HashMap<String, JsonValue>,
174
+ from_entity: String,
175
+ to_entity: String,
176
+ quantity: Option<Decimal>,
177
+ },
178
+ Pattern {
179
+ name: String,
180
+ regex: String,
181
+ },
182
+ Role {
183
+ name: String,
184
+ domain: Option<String>,
185
+ },
186
+ Relation {
187
+ name: String,
188
+ subject_role: String,
189
+ predicate: String,
190
+ object_role: String,
191
+ via_flow: Option<String>,
192
+ },
193
+ Dimension {
194
+ name: String,
195
+ },
196
+ UnitDeclaration {
197
+ symbol: String,
198
+ dimension: String,
199
+ factor: Decimal,
200
+ base_unit: String,
201
+ },
202
+ Policy {
203
+ name: String,
204
+ version: Option<String>,
205
+ metadata: PolicyMetadata,
206
+ expression: Expression,
207
+ },
208
+ Instance {
209
+ name: String,
210
+ entity_type: String,
211
+ fields: HashMap<String, Expression>,
212
+ },
213
+ ConceptChange {
214
+ name: String,
215
+ from_version: String,
216
+ to_version: String,
217
+ migration_policy: String,
218
+ breaking_change: bool,
219
+ },
220
+ Metric {
221
+ name: String,
222
+ expression: Expression,
223
+ metadata: MetricMetadata,
224
+ },
225
+ MappingDecl {
226
+ name: String,
227
+ target: TargetFormat,
228
+ rules: Vec<MappingRule>,
229
+ },
230
+ ProjectionDecl {
231
+ name: String,
232
+ target: TargetFormat,
233
+ overrides: Vec<ProjectionOverride>,
234
+ },
235
+ }
236
+
237
+ /// Parse source code into an AST
238
+ pub fn parse_source(source: &str) -> ParseResult<Ast> {
239
+ let pairs = SeaParser::parse(Rule::program, source)?;
240
+ build_ast(pairs)
241
+ }
242
+
243
+ /// Build AST from pest pairs
244
+ fn build_ast(pairs: Pairs<Rule>) -> ParseResult<Ast> {
245
+ let mut metadata = FileMetadata::default();
246
+ let mut declarations = Vec::new();
247
+
248
+ for pair in pairs {
249
+ match pair.as_rule() {
250
+ Rule::program => {
251
+ for inner in pair.into_inner() {
252
+ match inner.as_rule() {
253
+ Rule::file_header => {
254
+ metadata = parse_file_header(inner)?;
255
+ }
256
+ Rule::declaration => {
257
+ for decl in inner.into_inner() {
258
+ let node = parse_declaration(decl)?;
259
+ declarations.push(node);
260
+ }
261
+ }
262
+ Rule::EOI => {}
263
+ _ => {}
264
+ }
265
+ }
266
+ }
267
+ Rule::EOI => {}
268
+ _ => {}
269
+ }
270
+ }
271
+
272
+ Ok(Ast {
273
+ metadata,
274
+ declarations,
275
+ })
276
+ }
277
+
278
+ /// Parse file header annotations
279
+ fn parse_file_header(pair: Pair<Rule>) -> ParseResult<FileMetadata> {
280
+ let mut metadata = FileMetadata::default();
281
+
282
+ for annotation in pair.into_inner() {
283
+ match annotation.as_rule() {
284
+ Rule::annotation => {
285
+ let mut inner = annotation.into_inner();
286
+ let name = inner.next().ok_or_else(|| {
287
+ ParseError::GrammarError("Expected annotation name".to_string())
288
+ })?;
289
+ let value = inner.next().ok_or_else(|| {
290
+ ParseError::GrammarError("Expected annotation value".to_string())
291
+ })?;
292
+
293
+ let name_str = name.as_str().to_lowercase();
294
+ let value_str = parse_string_literal(value)?;
295
+
296
+ match name_str.as_str() {
297
+ "namespace" => metadata.namespace = Some(value_str),
298
+ "version" => metadata.version = Some(value_str),
299
+ "owner" => metadata.owner = Some(value_str),
300
+ "profile" => metadata.profile = Some(value_str),
301
+ _ => {
302
+ return Err(ParseError::GrammarError(format!(
303
+ "Unknown annotation: {}",
304
+ name_str
305
+ )))
306
+ }
307
+ }
308
+ }
309
+ Rule::import_decl => {
310
+ metadata.imports.push(parse_import_decl(annotation)?);
311
+ }
312
+ _ => {}
313
+ }
314
+ }
315
+
316
+ Ok(metadata)
317
+ }
318
+
319
+ /// Parse a single declaration
320
+ fn parse_declaration(pair: Pair<Rule>) -> ParseResult<Spanned<AstNode>> {
321
+ let (line, column) = pair.line_col();
322
+ let node = match pair.as_rule() {
323
+ Rule::export_decl => {
324
+ let mut inner = pair.into_inner();
325
+ let wrapped = inner.next().ok_or_else(|| {
326
+ ParseError::GrammarError("Expected declaration after export".to_string())
327
+ })?;
328
+ let node = parse_declaration(wrapped)?;
329
+ Ok(AstNode::Export(Box::new(node)))
330
+ }
331
+ Rule::declaration_inner => {
332
+ let inner = pair
333
+ .into_inner()
334
+ .next()
335
+ .ok_or_else(|| ParseError::GrammarError("Empty declaration".to_string()))?;
336
+ // Recursively call parse_declaration, but we need to unwrap the result if we want to avoid double spanning?
337
+ // Actually declaration_inner is just a wrapper. The inner parse_declaration returns Spanned<AstNode>.
338
+ // We should just return that directly.
339
+ return parse_declaration(inner);
340
+ }
341
+ Rule::dimension_decl => parse_dimension(pair),
342
+ Rule::unit_decl => parse_unit_declaration(pair),
343
+ Rule::entity_decl => parse_entity(pair),
344
+ Rule::resource_decl => parse_resource(pair),
345
+ Rule::flow_decl => parse_flow(pair),
346
+ Rule::pattern_decl => parse_pattern(pair),
347
+ Rule::role_decl => parse_role(pair),
348
+ Rule::relation_decl => parse_relation(pair),
349
+ Rule::instance_decl => parse_instance(pair),
350
+ Rule::policy_decl => parse_policy(pair),
351
+ Rule::concept_change_decl => parse_concept_change(pair),
352
+ Rule::metric_decl => parse_metric(pair),
353
+ Rule::mapping_decl => parse_mapping(pair),
354
+ Rule::projection_decl => parse_projection(pair),
355
+ _ => Err(ParseError::GrammarError(format!(
356
+ "Unexpected rule: {:?}",
357
+ pair.as_rule()
358
+ ))),
359
+ }?;
360
+
361
+ Ok(Spanned { node, line, column })
362
+ }
363
+
364
+ fn parse_import_decl(pair: Pair<Rule>) -> ParseResult<ImportDecl> {
365
+ let mut inner = pair.into_inner();
366
+ let specifier_pair = inner
367
+ .next()
368
+ .ok_or_else(|| ParseError::GrammarError("Missing import specifier".to_string()))?;
369
+ let from_pair = inner
370
+ .next()
371
+ .ok_or_else(|| ParseError::GrammarError("Missing import source".to_string()))?;
372
+
373
+ let specifier = match specifier_pair.as_rule() {
374
+ Rule::import_specifier | Rule::import_named | Rule::import_wildcard => {
375
+ parse_import_specifier(specifier_pair)?
376
+ }
377
+ _ => {
378
+ return Err(ParseError::GrammarError(format!(
379
+ "Unexpected import specifier: {:?}",
380
+ specifier_pair.as_rule()
381
+ )))
382
+ }
383
+ };
384
+
385
+ Ok(ImportDecl {
386
+ specifier,
387
+ from_module: parse_string_literal(from_pair)?,
388
+ })
389
+ }
390
+
391
+ fn parse_import_specifier(pair: Pair<Rule>) -> ParseResult<ImportSpecifier> {
392
+ match pair.as_rule() {
393
+ Rule::import_wildcard => {
394
+ let mut inner = pair.into_inner();
395
+ let alias = parse_identifier(inner.next().ok_or_else(|| {
396
+ ParseError::GrammarError("Expected alias for wildcard import".to_string())
397
+ })?)?;
398
+ Ok(ImportSpecifier::Wildcard(alias))
399
+ }
400
+ Rule::import_specifier | Rule::import_named => {
401
+ let mut items = Vec::new();
402
+ for item in pair.into_inner() {
403
+ if item.as_rule() == Rule::import_item {
404
+ items.push(parse_import_item(item)?);
405
+ }
406
+ }
407
+ Ok(ImportSpecifier::Named(items))
408
+ }
409
+ _ => Err(ParseError::GrammarError(
410
+ "Invalid import specifier".to_string(),
411
+ )),
412
+ }
413
+ }
414
+
415
+ fn parse_import_item(pair: Pair<Rule>) -> ParseResult<ImportItem> {
416
+ let mut inner = pair.into_inner();
417
+ let name_pair = inner
418
+ .next()
419
+ .ok_or_else(|| ParseError::GrammarError("Expected import item name".to_string()))?;
420
+ let alias = inner.next().map(parse_identifier).transpose()?;
421
+
422
+ Ok(ImportItem {
423
+ name: parse_identifier(name_pair)?,
424
+ alias,
425
+ })
426
+ }
427
+
428
+ /// Parse dimension declaration
429
+ fn parse_dimension(pair: Pair<Rule>) -> ParseResult<AstNode> {
430
+ let mut inner = pair.into_inner();
431
+ let name = parse_string_literal(
432
+ inner
433
+ .next()
434
+ .ok_or_else(|| ParseError::GrammarError("Expected dimension name".to_string()))?,
435
+ )?;
436
+
437
+ Ok(AstNode::Dimension { name })
438
+ }
439
+
440
+ /// Parse unit declaration
441
+ fn parse_unit_declaration(pair: Pair<Rule>) -> ParseResult<AstNode> {
442
+ let mut inner = pair.into_inner();
443
+
444
+ let symbol = parse_string_literal(
445
+ inner
446
+ .next()
447
+ .ok_or_else(|| ParseError::GrammarError("Expected unit symbol".to_string()))?,
448
+ )?;
449
+
450
+ let dimension = parse_string_literal(
451
+ inner
452
+ .next()
453
+ .ok_or_else(|| ParseError::GrammarError("Expected dimension name".to_string()))?,
454
+ )?;
455
+
456
+ let factor_pair = inner
457
+ .next()
458
+ .ok_or_else(|| ParseError::GrammarError("Expected factor".to_string()))?;
459
+ let factor = parse_decimal(factor_pair)?;
460
+
461
+ let base_unit = parse_string_literal(
462
+ inner
463
+ .next()
464
+ .ok_or_else(|| ParseError::GrammarError("Expected base unit".to_string()))?,
465
+ )?;
466
+
467
+ Ok(AstNode::UnitDeclaration {
468
+ symbol,
469
+ dimension,
470
+ factor,
471
+ base_unit,
472
+ })
473
+ }
474
+
475
+ /// Parse entity declaration
476
+ fn parse_entity(pair: Pair<Rule>) -> ParseResult<AstNode> {
477
+ let mut inner = pair.into_inner();
478
+
479
+ let name = parse_name(
480
+ inner
481
+ .next()
482
+ .ok_or_else(|| ParseError::GrammarError("Expected entity name".to_string()))?,
483
+ )?;
484
+
485
+ let mut version = None;
486
+ let mut annotations = HashMap::new();
487
+ let mut domain = None;
488
+
489
+ for part in inner {
490
+ match part.as_rule() {
491
+ Rule::version => {
492
+ version = Some(part.as_str().to_string());
493
+ }
494
+ Rule::entity_annotation => {
495
+ let mut annotation_inner = part.into_inner();
496
+ let key_pair = annotation_inner
497
+ .next()
498
+ .ok_or_else(|| ParseError::GrammarError("Empty annotation".to_string()))?;
499
+
500
+ match key_pair.as_rule() {
501
+ Rule::ea_replaces => {
502
+ let target_name =
503
+ parse_name(annotation_inner.next().ok_or_else(|| {
504
+ ParseError::GrammarError("Expected name in replaces".to_string())
505
+ })?)?;
506
+ // Check for optional version
507
+ let mut target_version = None;
508
+ if let Some(next) = annotation_inner.next() {
509
+ if next.as_rule() == Rule::version {
510
+ target_version = Some(next.as_str().to_string());
511
+ }
512
+ }
513
+
514
+ let value = if let Some(v) = target_version {
515
+ format!("{} v{}", target_name, v)
516
+ } else {
517
+ target_name
518
+ };
519
+ annotations.insert("replaces".to_string(), JsonValue::String(value));
520
+ }
521
+ Rule::ea_changes => {
522
+ let array_pair = annotation_inner.next().ok_or_else(|| {
523
+ ParseError::GrammarError("Expected string array in changes".to_string())
524
+ })?;
525
+ let mut changes = Vec::new();
526
+ for item in array_pair.into_inner() {
527
+ changes.push(parse_string_literal(item)?);
528
+ }
529
+ annotations.insert(
530
+ "changes".to_string(),
531
+ JsonValue::Array(changes.into_iter().map(JsonValue::String).collect()),
532
+ );
533
+ }
534
+ Rule::identifier => {
535
+ let key = parse_identifier(key_pair)?.to_lowercase();
536
+ let value_pair = annotation_inner.next().ok_or_else(|| {
537
+ ParseError::GrammarError("Expected annotation value".to_string())
538
+ })?;
539
+ let value = parse_annotation_value(value_pair)?;
540
+ println!("debug insert annotation {} = {:?}", key, value);
541
+ annotations.insert(key, value);
542
+ }
543
+ _ => {
544
+ return Err(ParseError::GrammarError(format!(
545
+ "Unexpected flow annotation: {}",
546
+ key_pair.as_str()
547
+ )));
548
+ }
549
+ }
550
+ }
551
+ Rule::in_keyword => {
552
+ // Skip "in"
553
+ }
554
+ Rule::identifier => {
555
+ // This must be the domain
556
+ domain = Some(parse_identifier(part)?);
557
+ }
558
+ _ => {}
559
+ }
560
+ }
561
+
562
+ Ok(AstNode::Entity {
563
+ name,
564
+ version,
565
+ annotations,
566
+ domain,
567
+ })
568
+ }
569
+
570
+ /// Parse concept change declaration
571
+ fn parse_concept_change(pair: Pair<Rule>) -> ParseResult<AstNode> {
572
+ let mut inner = pair.into_inner();
573
+
574
+ let name =
575
+ parse_name(inner.next().ok_or_else(|| {
576
+ ParseError::GrammarError("Expected concept change name".to_string())
577
+ })?)?;
578
+
579
+ let mut from_version = String::new();
580
+ let mut to_version = String::new();
581
+ let mut migration_policy = String::new();
582
+ let mut breaking_change = false;
583
+
584
+ for part in inner {
585
+ if part.as_rule() == Rule::concept_change_annotation {
586
+ let mut annotation_inner = part.into_inner();
587
+
588
+ let key_pair = annotation_inner
589
+ .next()
590
+ .ok_or_else(|| ParseError::GrammarError("Expected annotation key".to_string()))?;
591
+ let value_pair = annotation_inner
592
+ .next()
593
+ .ok_or_else(|| ParseError::GrammarError("Expected annotation value".to_string()))?;
594
+
595
+ match key_pair.as_rule() {
596
+ Rule::cc_from_version => from_version = parse_version(value_pair)?,
597
+ Rule::cc_to_version => to_version = parse_version(value_pair)?,
598
+ Rule::cc_migration_policy => migration_policy = parse_identifier(value_pair)?,
599
+ Rule::cc_breaking_change => {
600
+ breaking_change = value_pair.as_str() == "true";
601
+ }
602
+ _ => {}
603
+ }
604
+ }
605
+ }
606
+
607
+ if from_version.is_empty() {
608
+ return Err(ParseError::GrammarError(
609
+ "Missing cc_from_version annotation".to_string(),
610
+ ));
611
+ }
612
+
613
+ if to_version.is_empty() {
614
+ return Err(ParseError::GrammarError(
615
+ "Missing cc_to_version annotation".to_string(),
616
+ ));
617
+ }
618
+
619
+ if migration_policy.is_empty() {
620
+ return Err(ParseError::GrammarError(
621
+ "Missing cc_migration_policy annotation".to_string(),
622
+ ));
623
+ }
624
+
625
+ Ok(AstNode::ConceptChange {
626
+ name,
627
+ from_version,
628
+ to_version,
629
+ migration_policy,
630
+ breaking_change,
631
+ })
632
+ }
633
+
634
+ /// Parse resource declaration
635
+ fn parse_resource(pair: Pair<Rule>) -> ParseResult<AstNode> {
636
+ let mut inner = pair.into_inner();
637
+
638
+ let name = parse_name(
639
+ inner
640
+ .next()
641
+ .ok_or_else(|| ParseError::GrammarError("Expected resource name".to_string()))?,
642
+ )?;
643
+
644
+ let mut annotations = HashMap::new();
645
+ let mut unit_name = None;
646
+ let mut domain = None;
647
+ let mut saw_in_keyword = false;
648
+
649
+ for part in inner {
650
+ match part.as_rule() {
651
+ Rule::resource_annotation => {
652
+ let mut annotation_inner = part.into_inner();
653
+ let key_pair = annotation_inner
654
+ .next()
655
+ .ok_or_else(|| ParseError::GrammarError("Empty annotation".to_string()))?;
656
+
657
+ match key_pair.as_rule() {
658
+ Rule::ea_replaces => {
659
+ let target_name =
660
+ parse_name(annotation_inner.next().ok_or_else(|| {
661
+ ParseError::GrammarError("Expected name in replaces".to_string())
662
+ })?)?;
663
+ // Check for optional version
664
+ let mut target_version = None;
665
+ if let Some(next) = annotation_inner.next() {
666
+ if next.as_rule() == Rule::version {
667
+ target_version = Some(next.as_str().to_string());
668
+ }
669
+ }
670
+
671
+ let value = if let Some(v) = target_version {
672
+ format!("{} v{}", target_name, v)
673
+ } else {
674
+ target_name
675
+ };
676
+ annotations.insert("replaces".to_string(), JsonValue::String(value));
677
+ }
678
+ Rule::ea_changes => {
679
+ let array_pair = annotation_inner.next().ok_or_else(|| {
680
+ ParseError::GrammarError("Expected string array in changes".to_string())
681
+ })?;
682
+ let mut changes = Vec::new();
683
+ for item in array_pair.into_inner() {
684
+ changes.push(parse_string_literal(item)?);
685
+ }
686
+ annotations.insert(
687
+ "changes".to_string(),
688
+ JsonValue::Array(changes.into_iter().map(JsonValue::String).collect()),
689
+ );
690
+ }
691
+ Rule::identifier => {
692
+ let key = parse_identifier(key_pair)?.to_lowercase();
693
+ let value_pair = annotation_inner.next().ok_or_else(|| {
694
+ ParseError::GrammarError("Expected annotation value".to_string())
695
+ })?;
696
+ let value = parse_annotation_value(value_pair)?;
697
+ annotations.insert(key, value);
698
+ }
699
+ _ => {
700
+ return Err(ParseError::GrammarError(format!(
701
+ "Unexpected flow annotation: {}",
702
+ key_pair.as_str()
703
+ )));
704
+ }
705
+ }
706
+ }
707
+ Rule::in_keyword => {
708
+ // Mark that we've seen 'in', next identifier is domain
709
+ saw_in_keyword = true;
710
+ }
711
+ Rule::identifier => {
712
+ // If we've seen 'in' keyword, this is domain; otherwise it's unit
713
+ if saw_in_keyword {
714
+ domain = Some(parse_identifier(part)?);
715
+ saw_in_keyword = false; // Reset for safety
716
+ } else {
717
+ unit_name = Some(parse_identifier(part)?);
718
+ }
719
+ }
720
+ _ => {}
721
+ }
722
+ }
723
+
724
+ Ok(AstNode::Resource {
725
+ name,
726
+ annotations,
727
+ unit_name,
728
+ domain,
729
+ })
730
+ }
731
+
732
+ /// Parse flow declaration
733
+ fn parse_flow(pair: Pair<Rule>) -> ParseResult<AstNode> {
734
+ let mut inner = pair.into_inner();
735
+
736
+ let resource_name = parse_string_literal(
737
+ inner
738
+ .next()
739
+ .ok_or_else(|| ParseError::GrammarError("Expected resource name".to_string()))?,
740
+ )?;
741
+
742
+ let mut annotations = HashMap::new();
743
+ let mut from_entity = None;
744
+ let mut to_entity = None;
745
+ let mut quantity = None;
746
+ let mut pending_annotation_key: Option<String> = None;
747
+
748
+ for part in inner {
749
+ match part.as_rule() {
750
+ Rule::flow_annotation => {
751
+ let mut annotation_inner = part.into_inner();
752
+ let key_pair = annotation_inner
753
+ .next()
754
+ .ok_or_else(|| ParseError::GrammarError("Empty annotation".to_string()))?;
755
+
756
+ match key_pair.as_rule() {
757
+ Rule::ea_replaces => {
758
+ let target_name =
759
+ parse_name(annotation_inner.next().ok_or_else(|| {
760
+ ParseError::GrammarError("Expected name in replaces".to_string())
761
+ })?)?;
762
+ // Check for optional version
763
+ let mut target_version = None;
764
+ if let Some(next) = annotation_inner.next() {
765
+ if next.as_rule() == Rule::version {
766
+ target_version = Some(next.as_str().to_string());
767
+ }
768
+ }
769
+
770
+ let value = if let Some(v) = target_version {
771
+ format!("{} v{}", target_name, v)
772
+ } else {
773
+ target_name
774
+ };
775
+ annotations.insert("replaces".to_string(), JsonValue::String(value));
776
+ }
777
+ Rule::ea_changes => {
778
+ let array_pair = annotation_inner.next().ok_or_else(|| {
779
+ ParseError::GrammarError("Expected string array in changes".to_string())
780
+ })?;
781
+ let mut changes = Vec::new();
782
+ for item in array_pair.into_inner() {
783
+ changes.push(parse_string_literal(item)?);
784
+ }
785
+ annotations.insert(
786
+ "changes".to_string(),
787
+ JsonValue::Array(changes.into_iter().map(JsonValue::String).collect()),
788
+ );
789
+ }
790
+ Rule::identifier => {
791
+ let key = parse_identifier(key_pair)?.to_lowercase();
792
+ let value_pair = annotation_inner.next().ok_or_else(|| {
793
+ ParseError::GrammarError("Expected annotation value".to_string())
794
+ })?;
795
+ let value = parse_annotation_value(value_pair)?;
796
+ annotations.insert(key, value);
797
+ }
798
+ _ => {
799
+ return Err(ParseError::GrammarError(format!(
800
+ "Unexpected flow annotation: {}",
801
+ key_pair.as_str()
802
+ )));
803
+ }
804
+ }
805
+ }
806
+ Rule::identifier => {
807
+ if pending_annotation_key.is_some() {
808
+ return Err(ParseError::GrammarError(
809
+ "Unexpected annotation key without value".to_string(),
810
+ ));
811
+ }
812
+ pending_annotation_key = Some(parse_identifier(part)?.to_lowercase());
813
+ }
814
+ Rule::annotation_value
815
+ | Rule::object_literal
816
+ | Rule::string_array
817
+ | Rule::boolean
818
+ | Rule::number
819
+ | Rule::string_literal => {
820
+ if let Some(key) = pending_annotation_key.take() {
821
+ let value = parse_annotation_value(part)?;
822
+ annotations.insert(key, value);
823
+ continue;
824
+ }
825
+
826
+ if part.as_rule() == Rule::string_literal {
827
+ // These are from_entity and to_entity in order
828
+ let parsed = parse_string_literal(part)?;
829
+ if from_entity.is_none() {
830
+ from_entity = Some(parsed);
831
+ } else if to_entity.is_none() {
832
+ to_entity = Some(parsed);
833
+ }
834
+ } else if part.as_rule() == Rule::number {
835
+ quantity = Some(parse_decimal(part)?);
836
+ }
837
+ }
838
+ _ => {}
839
+ }
840
+ }
841
+
842
+ Ok(AstNode::Flow {
843
+ resource_name,
844
+ annotations,
845
+ from_entity: from_entity
846
+ .ok_or_else(|| ParseError::GrammarError("Expected from entity".to_string()))?,
847
+ to_entity: to_entity
848
+ .ok_or_else(|| ParseError::GrammarError("Expected to entity".to_string()))?,
849
+ quantity,
850
+ })
851
+ }
852
+
853
+ /// Parse pattern declaration
854
+ fn parse_pattern(pair: Pair<Rule>) -> ParseResult<AstNode> {
855
+ let mut inner = pair.into_inner();
856
+
857
+ let name = parse_name(
858
+ inner
859
+ .next()
860
+ .ok_or_else(|| ParseError::GrammarError("Expected pattern name".to_string()))?,
861
+ )?;
862
+
863
+ let regex_literal = inner
864
+ .next()
865
+ .ok_or_else(|| ParseError::GrammarError("Expected regex for pattern".to_string()))?;
866
+ let regex = parse_string_literal(regex_literal)?;
867
+
868
+ Ok(AstNode::Pattern { name, regex })
869
+ }
870
+
871
+ /// Parse role declaration
872
+ fn parse_role(pair: Pair<Rule>) -> ParseResult<AstNode> {
873
+ let mut inner = pair.into_inner();
874
+
875
+ let name = parse_name(
876
+ inner
877
+ .next()
878
+ .ok_or_else(|| ParseError::GrammarError("Expected role name".to_string()))?,
879
+ )?;
880
+
881
+ let mut domain = None;
882
+ for part in inner {
883
+ match part.as_rule() {
884
+ Rule::in_keyword => {
885
+ // Skip "in"
886
+ }
887
+ Rule::identifier => {
888
+ domain = Some(parse_identifier(part)?);
889
+ }
890
+ _ => {}
891
+ }
892
+ }
893
+
894
+ Ok(AstNode::Role { name, domain })
895
+ }
896
+
897
+ /// Parse relation declaration
898
+ fn parse_relation(pair: Pair<Rule>) -> ParseResult<AstNode> {
899
+ let mut inner = pair.into_inner();
900
+
901
+ let name = parse_name(
902
+ inner
903
+ .next()
904
+ .ok_or_else(|| ParseError::GrammarError("Expected relation name".to_string()))?,
905
+ )?;
906
+
907
+ let subject_literal = inner
908
+ .next()
909
+ .ok_or_else(|| ParseError::GrammarError("Expected subject role in relation".to_string()))?;
910
+ let subject_role = parse_string_literal(subject_literal)?;
911
+
912
+ let predicate_literal = inner
913
+ .next()
914
+ .ok_or_else(|| ParseError::GrammarError("Expected predicate in relation".to_string()))?;
915
+ let predicate = parse_string_literal(predicate_literal)?;
916
+
917
+ let object_literal = inner
918
+ .next()
919
+ .ok_or_else(|| ParseError::GrammarError("Expected object role in relation".to_string()))?;
920
+ let object_role = parse_string_literal(object_literal)?;
921
+
922
+ let via_flow = if let Some(via_pair) = inner.next() {
923
+ match via_pair.as_rule() {
924
+ Rule::string_literal => Some(parse_string_literal(via_pair)?),
925
+ _ => {
926
+ let mut via_inner = via_pair.into_inner();
927
+ let flow_literal = via_inner.next().ok_or_else(|| {
928
+ ParseError::GrammarError("Expected flow name after via".to_string())
929
+ })?;
930
+ Some(parse_string_literal(flow_literal)?)
931
+ }
932
+ }
933
+ } else {
934
+ None
935
+ };
936
+
937
+ Ok(AstNode::Relation {
938
+ name,
939
+ subject_role,
940
+ predicate,
941
+ object_role,
942
+ via_flow,
943
+ })
944
+ }
945
+
946
+ /// Parse instance declaration
947
+ fn parse_instance(pair: Pair<Rule>) -> ParseResult<AstNode> {
948
+ let mut inner = pair.into_inner();
949
+
950
+ let name = parse_identifier(
951
+ inner
952
+ .next()
953
+ .ok_or_else(|| ParseError::GrammarError("Expected instance name".to_string()))?,
954
+ )?;
955
+
956
+ let entity_type = parse_string_literal(
957
+ inner
958
+ .next()
959
+ .ok_or_else(|| ParseError::GrammarError("Expected entity type".to_string()))?,
960
+ )?;
961
+
962
+ let mut fields = HashMap::new();
963
+
964
+ // Parse optional instance body
965
+ if let Some(body_pair) = inner.next() {
966
+ if body_pair.as_rule() == Rule::instance_body {
967
+ for field_pair in body_pair.into_inner() {
968
+ if field_pair.as_rule() == Rule::instance_field {
969
+ let span = field_pair.as_span();
970
+ let mut field_inner = field_pair.into_inner();
971
+
972
+ let field_name = parse_identifier(field_inner.next().ok_or_else(|| {
973
+ ParseError::GrammarError("Expected field name".to_string())
974
+ })?)?;
975
+
976
+ if fields.contains_key(&field_name) {
977
+ let (line, column) = span.start_pos().line_col();
978
+ return Err(ParseError::GrammarError(format!(
979
+ "Duplicate field name '{}' at line {}, column {}",
980
+ field_name, line, column
981
+ )));
982
+ }
983
+
984
+ let field_value = parse_expression(field_inner.next().ok_or_else(|| {
985
+ ParseError::GrammarError("Expected field value".to_string())
986
+ })?)?;
987
+
988
+ fields.insert(field_name, field_value);
989
+ }
990
+ }
991
+ }
992
+ }
993
+
994
+ Ok(AstNode::Instance {
995
+ name,
996
+ entity_type,
997
+ fields,
998
+ })
999
+ }
1000
+
1001
+ /// Parse policy declaration
1002
+ fn parse_policy(pair: Pair<Rule>) -> ParseResult<AstNode> {
1003
+ let mut inner = pair.into_inner();
1004
+
1005
+ let name = parse_identifier(
1006
+ inner
1007
+ .next()
1008
+ .ok_or_else(|| ParseError::GrammarError("Expected policy name".to_string()))?,
1009
+ )?;
1010
+
1011
+ let mut metadata = PolicyMetadata {
1012
+ kind: None,
1013
+ modality: None,
1014
+ priority: None,
1015
+ rationale: None,
1016
+ tags: Vec::new(),
1017
+ };
1018
+ let mut version: Option<String> = None;
1019
+
1020
+ for next_pair in inner {
1021
+ match next_pair.as_rule() {
1022
+ Rule::policy_kind => {
1023
+ metadata.kind = Some(match next_pair.as_str().to_lowercase().as_str() {
1024
+ "constraint" => PolicyKind::Constraint,
1025
+ "derivation" => PolicyKind::Derivation,
1026
+ "obligation" => PolicyKind::Obligation,
1027
+ _ => {
1028
+ return Err(ParseError::GrammarError(format!(
1029
+ "Unknown policy kind: {}",
1030
+ next_pair.as_str()
1031
+ )))
1032
+ }
1033
+ });
1034
+ }
1035
+ Rule::policy_modality => {
1036
+ metadata.modality = Some(match next_pair.as_str().to_lowercase().as_str() {
1037
+ "obligation" => PolicyModality::Obligation,
1038
+ "prohibition" => PolicyModality::Prohibition,
1039
+ "permission" => PolicyModality::Permission,
1040
+ _ => {
1041
+ return Err(ParseError::GrammarError(format!(
1042
+ "Unknown policy modality: {}",
1043
+ next_pair.as_str()
1044
+ )))
1045
+ }
1046
+ });
1047
+ }
1048
+ Rule::number => {
1049
+ metadata.priority = Some(parse_number(next_pair)?);
1050
+ }
1051
+ Rule::policy_annotation => {
1052
+ parse_policy_annotation(next_pair, &mut metadata)?;
1053
+ }
1054
+ Rule::version => {
1055
+ version = Some(next_pair.as_str().to_string());
1056
+ }
1057
+ Rule::expression => {
1058
+ let expression = parse_expression(next_pair)?;
1059
+ return Ok(AstNode::Policy {
1060
+ name,
1061
+ version,
1062
+ metadata,
1063
+ expression,
1064
+ });
1065
+ }
1066
+ _ => {}
1067
+ }
1068
+ }
1069
+
1070
+ Err(ParseError::GrammarError(
1071
+ "Policy missing expression".to_string(),
1072
+ ))
1073
+ }
1074
+
1075
+ fn parse_policy_annotation(pair: Pair<Rule>, metadata: &mut PolicyMetadata) -> ParseResult<()> {
1076
+ let mut inner = pair.into_inner();
1077
+
1078
+ let name = inner
1079
+ .next()
1080
+ .ok_or_else(|| ParseError::GrammarError("Expected annotation name".to_string()))?;
1081
+ let value = inner
1082
+ .next()
1083
+ .ok_or_else(|| ParseError::GrammarError("Expected annotation value".to_string()))?;
1084
+
1085
+ let name_str = name.as_str().to_lowercase();
1086
+
1087
+ match name_str.as_str() {
1088
+ "rationale" => {
1089
+ metadata.rationale = Some(parse_string_literal(value)?);
1090
+ }
1091
+ "tags" => {
1092
+ if value.as_rule() == Rule::string_array {
1093
+ for tag_pair in value.into_inner() {
1094
+ if tag_pair.as_rule() == Rule::string_literal {
1095
+ metadata.tags.push(parse_string_literal(tag_pair)?);
1096
+ }
1097
+ }
1098
+ } else {
1099
+ metadata.tags.push(parse_string_literal(value)?);
1100
+ }
1101
+ }
1102
+ _ => {
1103
+ return Err(ParseError::GrammarError(format!(
1104
+ "Unknown policy annotation: {}",
1105
+ name_str
1106
+ )))
1107
+ }
1108
+ }
1109
+
1110
+ Ok(())
1111
+ }
1112
+
1113
+ /// Parse expression
1114
+ fn parse_expression(pair: Pair<Rule>) -> ParseResult<Expression> {
1115
+ match pair.as_rule() {
1116
+ Rule::expression => {
1117
+ let inner = pair
1118
+ .into_inner()
1119
+ .next()
1120
+ .ok_or_else(|| ParseError::GrammarError("Empty expression".to_string()))?;
1121
+ parse_expression(inner)
1122
+ }
1123
+ Rule::or_expr => parse_or_expr(pair),
1124
+ Rule::and_expr => parse_and_expr(pair),
1125
+ Rule::not_expr => parse_not_expr(pair),
1126
+ Rule::comparison_expr => parse_comparison_expr(pair),
1127
+ Rule::additive_expr => parse_additive_expr(pair),
1128
+ Rule::multiplicative_expr => parse_multiplicative_expr(pair),
1129
+ Rule::unary_expr => parse_unary_expr(pair),
1130
+ Rule::cast_expr => parse_cast_expr(pair),
1131
+ Rule::primary_expr => parse_primary_expr(pair),
1132
+ _ => Err(ParseError::InvalidExpression(format!(
1133
+ "Unexpected expression rule: {:?}",
1134
+ pair.as_rule()
1135
+ ))),
1136
+ }
1137
+ }
1138
+
1139
+ /// Parse a single expression string into an Expression AST node.
1140
+ pub fn parse_expression_from_str(source: &str) -> ParseResult<Expression> {
1141
+ let pairs = SeaParser::parse(Rule::expression, source)
1142
+ .map_err(|e| ParseError::GrammarError(format!("Parse error: {}", e)))?;
1143
+ let parsed_pairs: Vec<_> = pairs.collect();
1144
+
1145
+ if parsed_pairs.is_empty() {
1146
+ return Err(ParseError::GrammarError("Empty expression".to_string()));
1147
+ }
1148
+ if parsed_pairs.len() > 1 {
1149
+ return Err(ParseError::GrammarError(
1150
+ "Trailing input detected after expression".to_string(),
1151
+ ));
1152
+ }
1153
+
1154
+ let pair = parsed_pairs.into_iter().next().unwrap();
1155
+
1156
+ // Check that the entire input was consumed (no trailing characters)
1157
+ let consumed = pair.as_span().end();
1158
+ let trimmed_source = source.trim_end();
1159
+ if consumed < trimmed_source.len() {
1160
+ return Err(ParseError::GrammarError(format!(
1161
+ "Trailing input after expression: '{}'",
1162
+ &source[consumed..]
1163
+ )));
1164
+ }
1165
+
1166
+ parse_expression(pair)
1167
+ }
1168
+
1169
+ /// Parse OR expression
1170
+ fn parse_or_expr(pair: Pair<Rule>) -> ParseResult<Expression> {
1171
+ let mut inner = pair.into_inner();
1172
+ let mut left =
1173
+ parse_expression(inner.next().ok_or_else(|| {
1174
+ ParseError::GrammarError("Expected left expression in OR".to_string())
1175
+ })?)?;
1176
+
1177
+ for right_pair in inner {
1178
+ let right = parse_expression(right_pair)?;
1179
+ left = Expression::Binary {
1180
+ left: Box::new(left),
1181
+ op: BinaryOp::Or,
1182
+ right: Box::new(right),
1183
+ };
1184
+ }
1185
+
1186
+ Ok(left)
1187
+ }
1188
+
1189
+ /// Parse AND expression
1190
+ fn parse_and_expr(pair: Pair<Rule>) -> ParseResult<Expression> {
1191
+ let mut inner = pair.into_inner();
1192
+ let mut left =
1193
+ parse_expression(inner.next().ok_or_else(|| {
1194
+ ParseError::GrammarError("Expected left expression in AND".to_string())
1195
+ })?)?;
1196
+
1197
+ for right_pair in inner {
1198
+ let right = parse_expression(right_pair)?;
1199
+ left = Expression::Binary {
1200
+ left: Box::new(left),
1201
+ op: BinaryOp::And,
1202
+ right: Box::new(right),
1203
+ };
1204
+ }
1205
+
1206
+ Ok(left)
1207
+ }
1208
+
1209
+ /// Parse NOT expression
1210
+ fn parse_not_expr(pair: Pair<Rule>) -> ParseResult<Expression> {
1211
+ let mut inner = pair.into_inner();
1212
+ let first = inner
1213
+ .next()
1214
+ .ok_or_else(|| ParseError::GrammarError("Expected expression in NOT".to_string()))?;
1215
+
1216
+ // Check if this is actually a NOT expression or just a comparison_expr
1217
+ if first.as_rule() == Rule::not_expr {
1218
+ // This is a recursive NOT, parse it
1219
+ let expr = parse_expression(first)?;
1220
+ Ok(Expression::Unary {
1221
+ op: UnaryOp::Not,
1222
+ operand: Box::new(expr),
1223
+ })
1224
+ } else {
1225
+ // This is just a comparison_expr, parse it directly
1226
+ parse_expression(first)
1227
+ }
1228
+ }
1229
+
1230
+ /// Parse comparison expression
1231
+ fn parse_comparison_expr(pair: Pair<Rule>) -> ParseResult<Expression> {
1232
+ let mut inner = pair.into_inner();
1233
+ let mut left = parse_expression(inner.next().ok_or_else(|| {
1234
+ ParseError::GrammarError("Expected left expression in comparison".to_string())
1235
+ })?)?;
1236
+
1237
+ if let Some(op_pair) = inner.next() {
1238
+ let op = parse_comparison_op(op_pair)?;
1239
+ let right = parse_expression(inner.next().ok_or_else(|| {
1240
+ ParseError::GrammarError("Expected right expression in comparison".to_string())
1241
+ })?)?;
1242
+ left = Expression::Binary {
1243
+ left: Box::new(left),
1244
+ op,
1245
+ right: Box::new(right),
1246
+ };
1247
+ }
1248
+
1249
+ Ok(left)
1250
+ }
1251
+
1252
+ /// Parse comparison operator
1253
+ fn parse_comparison_op(pair: Pair<Rule>) -> ParseResult<BinaryOp> {
1254
+ let op_str = pair.as_str();
1255
+ match op_str {
1256
+ "=" => Ok(BinaryOp::Equal),
1257
+ "!=" => Ok(BinaryOp::NotEqual),
1258
+ ">" => Ok(BinaryOp::GreaterThan),
1259
+ "<" => Ok(BinaryOp::LessThan),
1260
+ ">=" => Ok(BinaryOp::GreaterThanOrEqual),
1261
+ "<=" => Ok(BinaryOp::LessThanOrEqual),
1262
+ _ if op_str.eq_ignore_ascii_case("contains") => Ok(BinaryOp::Contains),
1263
+ _ if op_str.eq_ignore_ascii_case("startswith") => Ok(BinaryOp::StartsWith),
1264
+ _ if op_str.eq_ignore_ascii_case("endswith") => Ok(BinaryOp::EndsWith),
1265
+ _ if op_str.eq_ignore_ascii_case("matches") => Ok(BinaryOp::Matches),
1266
+ _ if op_str.eq_ignore_ascii_case("before") => Ok(BinaryOp::Before),
1267
+ _ if op_str.eq_ignore_ascii_case("after") => Ok(BinaryOp::After),
1268
+ _ if op_str.eq_ignore_ascii_case("during") => Ok(BinaryOp::During),
1269
+ _ if op_str.eq_ignore_ascii_case("has_role") => Ok(BinaryOp::HasRole),
1270
+ _ => Err(ParseError::InvalidExpression(format!(
1271
+ "Unknown comparison operator: {}",
1272
+ op_str
1273
+ ))),
1274
+ }
1275
+ }
1276
+
1277
+ /// Parse additive expression
1278
+ fn parse_additive_expr(pair: Pair<Rule>) -> ParseResult<Expression> {
1279
+ let mut inner = pair.into_inner();
1280
+ let mut left = parse_expression(inner.next().ok_or_else(|| {
1281
+ ParseError::GrammarError("Expected left expression in additive".to_string())
1282
+ })?)?;
1283
+
1284
+ while let Some(op_pair) = inner.next() {
1285
+ let op = match op_pair.as_str() {
1286
+ "+" => BinaryOp::Plus,
1287
+ "-" => BinaryOp::Minus,
1288
+ _ => {
1289
+ return Err(ParseError::InvalidExpression(
1290
+ "Invalid additive operator".to_string(),
1291
+ ))
1292
+ }
1293
+ };
1294
+ let right = parse_expression(inner.next().ok_or_else(|| {
1295
+ ParseError::GrammarError("Expected right expression in additive".to_string())
1296
+ })?)?;
1297
+ left = Expression::Binary {
1298
+ left: Box::new(left),
1299
+ op,
1300
+ right: Box::new(right),
1301
+ };
1302
+ }
1303
+
1304
+ Ok(left)
1305
+ }
1306
+
1307
+ /// Parse multiplicative expression
1308
+ fn parse_multiplicative_expr(pair: Pair<Rule>) -> ParseResult<Expression> {
1309
+ let mut inner = pair.into_inner();
1310
+ let mut left = parse_expression(inner.next().ok_or_else(|| {
1311
+ ParseError::GrammarError("Expected left expression in multiplicative".to_string())
1312
+ })?)?;
1313
+
1314
+ while let Some(op_pair) = inner.next() {
1315
+ let op = match op_pair.as_str() {
1316
+ "*" => BinaryOp::Multiply,
1317
+ "/" => BinaryOp::Divide,
1318
+ _ => {
1319
+ return Err(ParseError::InvalidExpression(
1320
+ "Invalid multiplicative operator".to_string(),
1321
+ ))
1322
+ }
1323
+ };
1324
+ let right = parse_expression(inner.next().ok_or_else(|| {
1325
+ ParseError::GrammarError("Expected right expression in multiplicative".to_string())
1326
+ })?)?;
1327
+ left = Expression::Binary {
1328
+ left: Box::new(left),
1329
+ op,
1330
+ right: Box::new(right),
1331
+ };
1332
+ }
1333
+
1334
+ Ok(left)
1335
+ }
1336
+
1337
+ /// Parse cast expression
1338
+ fn parse_cast_expr(pair: Pair<Rule>) -> ParseResult<Expression> {
1339
+ let mut inner = pair.into_inner();
1340
+ let primary = parse_expression(inner.next().ok_or_else(|| {
1341
+ ParseError::GrammarError("Expected primary expression in cast".to_string())
1342
+ })?)?;
1343
+
1344
+ if let Some(as_pair) = inner.next() {
1345
+ let target_type = parse_string_literal(as_pair)?;
1346
+ Ok(Expression::cast(primary, target_type))
1347
+ } else {
1348
+ Ok(primary)
1349
+ }
1350
+ }
1351
+
1352
+ /// Parse unary expression
1353
+ fn parse_unary_expr(pair: Pair<Rule>) -> ParseResult<Expression> {
1354
+ let mut inner = pair.into_inner();
1355
+ let first = inner
1356
+ .next()
1357
+ .ok_or_else(|| ParseError::GrammarError("Expected expression in unary".to_string()))?;
1358
+
1359
+ if first.as_str() == "-" {
1360
+ // Unary minus
1361
+ let expr = parse_expression(inner.next().ok_or_else(|| {
1362
+ ParseError::GrammarError("Expected expression after unary minus".to_string())
1363
+ })?)?;
1364
+ Ok(Expression::Unary {
1365
+ op: UnaryOp::Negate,
1366
+ operand: Box::new(expr),
1367
+ })
1368
+ } else {
1369
+ parse_expression(first)
1370
+ }
1371
+ }
1372
+
1373
+ /// Parse primary expression
1374
+ fn parse_primary_expr(pair: Pair<Rule>) -> ParseResult<Expression> {
1375
+ let inner = pair.into_inner().next().ok_or_else(|| {
1376
+ ParseError::GrammarError("Expected inner expression in primary".to_string())
1377
+ })?;
1378
+
1379
+ match inner.as_rule() {
1380
+ Rule::expression => parse_expression(inner),
1381
+ Rule::group_by_expr => parse_group_by_expr(inner),
1382
+ Rule::aggregation_expr => parse_aggregation_expr(inner),
1383
+ Rule::quantified_expr => parse_quantified_expr(inner),
1384
+ Rule::member_access => parse_member_access(inner),
1385
+ Rule::literal => parse_literal_expr(inner),
1386
+ Rule::identifier => {
1387
+ let name = parse_identifier(inner)?;
1388
+ Ok(Expression::Variable(name))
1389
+ }
1390
+ _ => Err(ParseError::InvalidExpression(format!(
1391
+ "Unexpected primary expression: {:?}",
1392
+ inner.as_rule()
1393
+ ))),
1394
+ }
1395
+ }
1396
+
1397
+ /// Parse aggregation expression
1398
+ fn parse_aggregation_expr(pair: Pair<Rule>) -> ParseResult<Expression> {
1399
+ let mut inner = pair.into_inner();
1400
+
1401
+ let function_pair = inner.next().ok_or_else(|| {
1402
+ ParseError::GrammarError(
1403
+ "Expected aggregate function in aggregation expression".to_string(),
1404
+ )
1405
+ })?;
1406
+ let function = parse_aggregate_fn(function_pair)?;
1407
+
1408
+ let collection_pair = inner.next().ok_or_else(|| {
1409
+ ParseError::GrammarError("Expected collection in aggregation expression".to_string())
1410
+ })?;
1411
+
1412
+ // Aggregation can be a simple form (e.g., sum(flows), sum(flows.quantity))
1413
+ // or the comprehension form (e.g., sum(f in flows where f.resource = "Money": f.quantity as "USD")).
1414
+ if collection_pair.as_rule() == Rule::aggregation_comprehension {
1415
+ // Parse aggregation comprehension
1416
+ let mut comp_inner = collection_pair.into_inner();
1417
+ let variable_pair = comp_inner.next().ok_or_else(|| {
1418
+ ParseError::GrammarError("Expected variable in aggregation comprehension".to_string())
1419
+ })?;
1420
+ let variable = parse_identifier(variable_pair)?;
1421
+ // Next token should be collection
1422
+ let collection_token = comp_inner.next().ok_or_else(|| {
1423
+ ParseError::GrammarError("Expected collection in aggregation comprehension".to_string())
1424
+ })?;
1425
+ let collection_name = parse_collection(collection_token)?;
1426
+ let collection = Box::new(Expression::Variable(collection_name));
1427
+
1428
+ let mut next_pair = comp_inner.next().ok_or_else(|| {
1429
+ ParseError::GrammarError(
1430
+ "Expected predicate expression, projection, or window in aggregation comprehension"
1431
+ .to_string(),
1432
+ )
1433
+ })?;
1434
+
1435
+ let mut window = None;
1436
+ if next_pair.as_rule() == Rule::window_clause {
1437
+ window = Some(parse_window_clause(next_pair)?);
1438
+ next_pair = comp_inner.next().ok_or_else(|| {
1439
+ ParseError::GrammarError(
1440
+ "Expected predicate or projection expression in aggregation comprehension"
1441
+ .to_string(),
1442
+ )
1443
+ })?;
1444
+ }
1445
+
1446
+ let remaining_pairs: Vec<Pair<Rule>> =
1447
+ std::iter::once(next_pair).chain(comp_inner).collect();
1448
+
1449
+ let mut expr_pairs: Vec<Pair<Rule>> = Vec::new();
1450
+ let mut target_unit: Option<String> = None;
1451
+ for pair in remaining_pairs {
1452
+ match pair.as_rule() {
1453
+ Rule::expression => expr_pairs.push(pair),
1454
+ Rule::string_literal => {
1455
+ target_unit = Some(parse_string_literal(pair)?);
1456
+ }
1457
+ Rule::identifier if pair.as_str().eq_ignore_ascii_case("as") => {
1458
+ // skip explicit AS token
1459
+ }
1460
+ other => {
1461
+ return Err(ParseError::GrammarError(format!(
1462
+ "Unexpected token {:?} in aggregation comprehension",
1463
+ other
1464
+ )))
1465
+ }
1466
+ }
1467
+ }
1468
+
1469
+ let (predicate, projection) = match expr_pairs.len() {
1470
+ 2 => {
1471
+ let mut expr_iter = expr_pairs.into_iter();
1472
+ let predicate_expr = parse_expression(expr_iter.next().ok_or_else(|| {
1473
+ ParseError::GrammarError(
1474
+ "Expected predicate expression in aggregation comprehension".to_string(),
1475
+ )
1476
+ })?)?;
1477
+ let projection_expr = parse_expression(expr_iter.next().ok_or_else(|| {
1478
+ ParseError::GrammarError(
1479
+ "Expected projection expression in aggregation comprehension".to_string(),
1480
+ )
1481
+ })?)?;
1482
+ (predicate_expr, projection_expr)
1483
+ }
1484
+ 1 => {
1485
+ let projection_expr =
1486
+ parse_expression(expr_pairs.into_iter().next().ok_or_else(|| {
1487
+ ParseError::GrammarError(
1488
+ "Expected projection expression in aggregation comprehension"
1489
+ .to_string(),
1490
+ )
1491
+ })?)?;
1492
+ (Expression::Literal(JsonValue::Bool(true)), projection_expr)
1493
+ }
1494
+ other => {
1495
+ return Err(ParseError::GrammarError(format!(
1496
+ "Unexpected number of expressions in aggregation comprehension: {}",
1497
+ other
1498
+ )))
1499
+ }
1500
+ };
1501
+
1502
+ return Ok(Expression::AggregationComprehension {
1503
+ function,
1504
+ variable,
1505
+ collection,
1506
+ window,
1507
+ predicate: Box::new(predicate),
1508
+ projection: Box::new(projection),
1509
+ target_unit,
1510
+ });
1511
+ }
1512
+
1513
+ // Parse aggregation_simple
1514
+ let mut simple_inner = collection_pair.into_inner();
1515
+
1516
+ // First item is either collection or identifier
1517
+ let first_pair = simple_inner.next().ok_or_else(|| {
1518
+ ParseError::GrammarError(
1519
+ "Expected collection or identifier in aggregation_simple".to_string(),
1520
+ )
1521
+ })?;
1522
+
1523
+ let collection = match first_pair.as_rule() {
1524
+ Rule::collection => parse_collection(first_pair)?,
1525
+ Rule::identifier => parse_identifier(first_pair)?,
1526
+ _ => {
1527
+ return Err(ParseError::GrammarError(format!(
1528
+ "Expected collection or identifier, got {:?}",
1529
+ first_pair.as_rule()
1530
+ )))
1531
+ }
1532
+ };
1533
+
1534
+ let mut field: Option<String> = None;
1535
+ let mut filter: Option<Expression> = None;
1536
+
1537
+ // Parse optional field and filter
1538
+ for item in simple_inner {
1539
+ match item.as_rule() {
1540
+ Rule::identifier => {
1541
+ field = Some(parse_identifier(item)?);
1542
+ }
1543
+ Rule::expression => {
1544
+ filter = Some(parse_expression(item)?);
1545
+ }
1546
+ _ => {}
1547
+ }
1548
+ }
1549
+
1550
+ Ok(Expression::aggregation(
1551
+ function,
1552
+ Expression::Variable(collection),
1553
+ field,
1554
+ filter,
1555
+ ))
1556
+ }
1557
+
1558
+ /// Parse aggregate function
1559
+ fn parse_aggregate_fn(pair: Pair<Rule>) -> ParseResult<AggregateFunction> {
1560
+ let fn_str = pair.as_str();
1561
+ match fn_str.to_lowercase().as_str() {
1562
+ "count" => Ok(AggregateFunction::Count),
1563
+ "sum" => Ok(AggregateFunction::Sum),
1564
+ "min" => Ok(AggregateFunction::Min),
1565
+ "max" => Ok(AggregateFunction::Max),
1566
+ "avg" => Ok(AggregateFunction::Avg),
1567
+ _ => Err(ParseError::InvalidExpression(format!(
1568
+ "Unknown aggregate function: {}",
1569
+ fn_str
1570
+ ))),
1571
+ }
1572
+ }
1573
+
1574
+ /// Parse quantified expression
1575
+ fn parse_quantified_expr(pair: Pair<Rule>) -> ParseResult<Expression> {
1576
+ let mut inner = pair.into_inner();
1577
+
1578
+ let quantifier = parse_quantifier(inner.next().ok_or_else(|| {
1579
+ ParseError::GrammarError("Expected quantifier in quantified expression".to_string())
1580
+ })?)?;
1581
+ let variable = parse_identifier(inner.next().ok_or_else(|| {
1582
+ ParseError::GrammarError(
1583
+ "Expected variable identifier in quantified expression".to_string(),
1584
+ )
1585
+ })?)?;
1586
+ let collection = parse_collection(inner.next().ok_or_else(|| {
1587
+ ParseError::GrammarError(
1588
+ "Expected collection identifier in quantified expression".to_string(),
1589
+ )
1590
+ })?)?;
1591
+ let condition = parse_expression(inner.next().ok_or_else(|| {
1592
+ ParseError::GrammarError("Expected quantified condition expression".to_string())
1593
+ })?)?;
1594
+
1595
+ Ok(Expression::Quantifier {
1596
+ quantifier,
1597
+ variable,
1598
+ collection: Box::new(Expression::Variable(collection)),
1599
+ condition: Box::new(condition),
1600
+ })
1601
+ }
1602
+
1603
+ /// Parse quantifier
1604
+ fn parse_quantifier(pair: Pair<Rule>) -> ParseResult<PolicyQuantifier> {
1605
+ let q_str = pair.as_str();
1606
+ match q_str.to_lowercase().as_str() {
1607
+ "forall" => Ok(PolicyQuantifier::ForAll),
1608
+ "exists" => Ok(PolicyQuantifier::Exists),
1609
+ "exists_unique" => Ok(PolicyQuantifier::ExistsUnique),
1610
+ _ => Err(ParseError::InvalidExpression(format!(
1611
+ "Unknown quantifier: {}",
1612
+ q_str
1613
+ ))),
1614
+ }
1615
+ }
1616
+
1617
+ /// Parse collection type
1618
+ fn parse_collection(pair: Pair<Rule>) -> ParseResult<String> {
1619
+ Ok(pair.as_str().to_lowercase())
1620
+ }
1621
+
1622
+ /// Parse member access
1623
+ fn parse_member_access(pair: Pair<Rule>) -> ParseResult<Expression> {
1624
+ let mut inner = pair.into_inner();
1625
+ let object = parse_identifier(inner.next().ok_or_else(|| {
1626
+ ParseError::GrammarError("Expected object identifier in member access".to_string())
1627
+ })?)?;
1628
+ let member = parse_identifier(inner.next().ok_or_else(|| {
1629
+ ParseError::GrammarError("Expected member identifier in member access".to_string())
1630
+ })?)?;
1631
+
1632
+ Ok(Expression::MemberAccess { object, member })
1633
+ }
1634
+
1635
+ /// Parse literal expression
1636
+ fn parse_literal_expr(pair: Pair<Rule>) -> ParseResult<Expression> {
1637
+ let inner = pair
1638
+ .into_inner()
1639
+ .next()
1640
+ .ok_or_else(|| ParseError::GrammarError("Expected literal content".to_string()))?;
1641
+
1642
+ match inner.as_rule() {
1643
+ Rule::string_literal => {
1644
+ let s = parse_string_literal(inner)?;
1645
+ Ok(Expression::Literal(JsonValue::String(s)))
1646
+ }
1647
+ Rule::multiline_string => {
1648
+ let s = parse_multiline_string(inner)?;
1649
+ Ok(Expression::Literal(JsonValue::String(s)))
1650
+ }
1651
+ Rule::quantity_literal => {
1652
+ let mut parts = inner.into_inner();
1653
+ let number_part = parts.next().ok_or_else(|| {
1654
+ ParseError::GrammarError("Expected number in quantity literal".to_string())
1655
+ })?;
1656
+ let unit_part = parts.next().ok_or_else(|| {
1657
+ ParseError::GrammarError("Expected unit string in quantity literal".to_string())
1658
+ })?;
1659
+ let value = parse_decimal(number_part)?;
1660
+ let unit = parse_string_literal(unit_part)?;
1661
+ Ok(Expression::QuantityLiteral { value, unit })
1662
+ }
1663
+ Rule::time_literal => {
1664
+ // Parse ISO 8601 timestamp (already includes quotes in grammar)
1665
+ let timestamp = inner.as_str();
1666
+ // Remove surrounding quotes
1667
+ let timestamp = timestamp.trim_start_matches('"').trim_end_matches('"');
1668
+ Ok(Expression::TimeLiteral(timestamp.to_string()))
1669
+ }
1670
+ Rule::interval_literal => {
1671
+ // Parse interval("start", "end")
1672
+ let mut parts = inner.into_inner();
1673
+ let start_part = parts.next().ok_or_else(|| {
1674
+ ParseError::GrammarError("Expected start time in interval literal".to_string())
1675
+ })?;
1676
+ let end_part = parts.next().ok_or_else(|| {
1677
+ ParseError::GrammarError("Expected end time in interval literal".to_string())
1678
+ })?;
1679
+ let start = parse_string_literal(start_part)?;
1680
+ let end = parse_string_literal(end_part)?;
1681
+ Ok(Expression::IntervalLiteral { start, end })
1682
+ }
1683
+ Rule::number => {
1684
+ let n = parse_decimal(inner)?;
1685
+ // Convert Decimal to f64 for JSON Number representation
1686
+ let f = n.to_f64().ok_or_else(|| {
1687
+ ParseError::InvalidQuantity(format!(
1688
+ "Decimal value {} cannot be represented as f64",
1689
+ n
1690
+ ))
1691
+ })?;
1692
+ // Ensure the value is finite
1693
+ if !f.is_finite() {
1694
+ return Err(ParseError::InvalidQuantity(format!(
1695
+ "Decimal value {} converts to non-finite f64: {}",
1696
+ n, f
1697
+ )));
1698
+ }
1699
+ let num = serde_json::Number::from_f64(f).ok_or_else(|| {
1700
+ ParseError::InvalidQuantity(format!(
1701
+ "Cannot create JSON Number from f64 value: {}",
1702
+ f
1703
+ ))
1704
+ })?;
1705
+ Ok(Expression::Literal(JsonValue::Number(num)))
1706
+ }
1707
+ Rule::boolean => {
1708
+ let b = inner.as_str().eq_ignore_ascii_case("true");
1709
+ Ok(Expression::Literal(JsonValue::Bool(b)))
1710
+ }
1711
+ _ => Err(ParseError::InvalidExpression(format!(
1712
+ "Unknown literal type: {:?}",
1713
+ inner.as_rule()
1714
+ ))),
1715
+ }
1716
+ }
1717
+
1718
+ /// Parse name (handles both string_literal and multiline_string)
1719
+ fn parse_name(pair: Pair<Rule>) -> ParseResult<String> {
1720
+ let inner = pair.into_inner().next().ok_or_else(|| {
1721
+ ParseError::GrammarError("Expected inner token for name but got empty pair".to_string())
1722
+ })?;
1723
+ match inner.as_rule() {
1724
+ Rule::string_literal => parse_string_literal(inner),
1725
+ Rule::multiline_string => parse_multiline_string(inner),
1726
+ _ => Err(ParseError::GrammarError(format!(
1727
+ "Expected string or multiline string for name, got {:?}",
1728
+ inner.as_rule()
1729
+ ))),
1730
+ }
1731
+ }
1732
+
1733
+ /// Parse string literal (handles escape sequences)
1734
+ fn parse_string_literal(pair: Pair<Rule>) -> ParseResult<String> {
1735
+ let s = pair.as_str();
1736
+ if s.len() < 2 || !s.starts_with('"') || !s.ends_with('"') {
1737
+ return Err(ParseError::GrammarError(format!(
1738
+ "Invalid string literal: {}",
1739
+ s
1740
+ )));
1741
+ }
1742
+
1743
+ // Use serde_json to properly parse and unescape the string
1744
+ // The string already has quotes, so pass it directly
1745
+ match serde_json::from_str(s) {
1746
+ Ok(unescaped) => Ok(unescaped),
1747
+ Err(e) => Err(ParseError::GrammarError(format!(
1748
+ "Invalid string literal escape sequences: {} - {}",
1749
+ s, e
1750
+ ))),
1751
+ }
1752
+ }
1753
+
1754
+ /// Parse multiline string (removes triple quotes and handles escape sequences)
1755
+ fn parse_multiline_string(pair: Pair<Rule>) -> ParseResult<String> {
1756
+ let s = pair.as_str();
1757
+ if s.len() < 6 || !s.starts_with("\"\"\"") || !s.ends_with("\"\"\"") {
1758
+ return Err(ParseError::GrammarError(format!(
1759
+ "Invalid multiline string: {}",
1760
+ s
1761
+ )));
1762
+ }
1763
+
1764
+ let content = &s[3..s.len() - 3];
1765
+
1766
+ // Escape special characters for JSON compatibility
1767
+ let escaped = content
1768
+ .replace('\\', "\\\\") // Backslash must be first
1769
+ .replace('"', "\\\"") // Double quotes
1770
+ .replace('\n', "\\n") // Newlines
1771
+ .replace('\r', "\\r") // Carriage returns
1772
+ .replace('\t', "\\t"); // Tabs
1773
+
1774
+ // Create a JSON string and parse it to handle escape sequences
1775
+ let json_string = format!("\"{}\"", escaped);
1776
+ match serde_json::from_str(&json_string) {
1777
+ Ok(unescaped) => Ok(unescaped),
1778
+ Err(e) => Err(ParseError::GrammarError(format!(
1779
+ "Invalid multiline string escape sequences in '{}': {}",
1780
+ s, e
1781
+ ))),
1782
+ }
1783
+ }
1784
+
1785
+ /// Parse identifier
1786
+ fn parse_identifier(pair: Pair<Rule>) -> ParseResult<String> {
1787
+ Ok(pair.as_str().to_string())
1788
+ }
1789
+
1790
+ /// Parse semantic version (validated by grammar)
1791
+ fn parse_version(pair: Pair<Rule>) -> ParseResult<String> {
1792
+ Ok(pair.as_str().to_string())
1793
+ }
1794
+
1795
+ /// Parse number as i32
1796
+ fn parse_number(pair: Pair<Rule>) -> ParseResult<i32> {
1797
+ pair.as_str()
1798
+ .parse()
1799
+ .map_err(|_| ParseError::InvalidQuantity(format!("Invalid number: {}", pair.as_str())))
1800
+ }
1801
+
1802
+ /// Parse number as Decimal
1803
+ fn parse_decimal(pair: Pair<Rule>) -> ParseResult<Decimal> {
1804
+ pair.as_str()
1805
+ .parse()
1806
+ .map_err(|_| ParseError::InvalidQuantity(format!("Invalid decimal: {}", pair.as_str())))
1807
+ }
1808
+
1809
+ /// Parse group_by expression
1810
+ fn parse_group_by_expr(pair: Pair<Rule>) -> ParseResult<Expression> {
1811
+ let mut inner = pair.into_inner();
1812
+
1813
+ let variable_pair = inner
1814
+ .next()
1815
+ .ok_or_else(|| ParseError::GrammarError("Expected variable in group_by".to_string()))?;
1816
+ let variable = parse_identifier(variable_pair)?;
1817
+
1818
+ let collection_pair = inner
1819
+ .next()
1820
+ .ok_or_else(|| ParseError::GrammarError("Expected collection in group_by".to_string()))?;
1821
+ let collection_name = parse_collection(collection_pair)?;
1822
+ let collection = Box::new(Expression::Variable(collection_name));
1823
+
1824
+ let next_pair = inner.next().ok_or_else(|| {
1825
+ ParseError::GrammarError("Expected key or where clause in group_by".to_string())
1826
+ })?;
1827
+
1828
+ // Collect remaining pairs to determine structure without cloning
1829
+ let remaining: Vec<Pair<Rule>> = std::iter::once(next_pair).chain(inner).collect();
1830
+
1831
+ let (filter_expr, key_expr, condition_expr) = match remaining.len() {
1832
+ 3 => {
1833
+ let mut iter = remaining.into_iter();
1834
+ let filter = parse_expression(iter.next().ok_or_else(|| {
1835
+ ParseError::GrammarError("Expected filter expression in group_by".to_string())
1836
+ })?)?;
1837
+ let key = parse_expression(iter.next().ok_or_else(|| {
1838
+ ParseError::GrammarError("Expected key expression in group_by".to_string())
1839
+ })?)?;
1840
+ let condition = parse_expression(iter.next().ok_or_else(|| {
1841
+ ParseError::GrammarError("Expected condition expression in group_by".to_string())
1842
+ })?)?;
1843
+ (Some(filter), key, condition)
1844
+ }
1845
+ 2 => {
1846
+ let mut iter = remaining.into_iter();
1847
+ let key = parse_expression(iter.next().ok_or_else(|| {
1848
+ ParseError::GrammarError("Expected key expression in group_by".to_string())
1849
+ })?)?;
1850
+ let condition = parse_expression(iter.next().ok_or_else(|| {
1851
+ ParseError::GrammarError("Expected condition expression in group_by".to_string())
1852
+ })?)?;
1853
+ (None, key, condition)
1854
+ }
1855
+ other => {
1856
+ return Err(ParseError::GrammarError(format!(
1857
+ "Unexpected number of expressions in group_by: {}",
1858
+ other
1859
+ )))
1860
+ }
1861
+ };
1862
+
1863
+ Ok(Expression::GroupBy {
1864
+ variable,
1865
+ collection,
1866
+ filter: filter_expr.map(Box::new),
1867
+ key: Box::new(key_expr),
1868
+ condition: Box::new(condition_expr),
1869
+ })
1870
+ }
1871
+
1872
+ /// Parse window clause
1873
+ fn parse_window_clause(pair: Pair<Rule>) -> ParseResult<WindowSpec> {
1874
+ let mut inner = pair.into_inner();
1875
+
1876
+ let duration_pair = inner.next().ok_or_else(|| {
1877
+ ParseError::GrammarError("Expected duration in window clause".to_string())
1878
+ })?;
1879
+ let duration_i32 = parse_number(duration_pair)?;
1880
+ if duration_i32 < 0 {
1881
+ return Err(ParseError::InvalidQuantity(
1882
+ "Window duration must be non-negative".to_string(),
1883
+ ));
1884
+ }
1885
+ let duration = duration_i32 as u64;
1886
+
1887
+ let unit_pair = inner
1888
+ .next()
1889
+ .ok_or_else(|| ParseError::GrammarError("Expected unit in window clause".to_string()))?;
1890
+ let unit = parse_string_literal(unit_pair)?;
1891
+
1892
+ Ok(WindowSpec { duration, unit })
1893
+ }
1894
+
1895
+ fn expression_kind(expr: &Expression) -> &'static str {
1896
+ match expr {
1897
+ Expression::Literal(_) => "literal",
1898
+ Expression::QuantityLiteral { .. } => "quantity_literal",
1899
+ Expression::TimeLiteral(_) => "time_literal",
1900
+ Expression::IntervalLiteral { .. } => "interval_literal",
1901
+ Expression::Variable(_) => "variable",
1902
+ Expression::GroupBy { .. } => "group_by",
1903
+ Expression::Binary { .. } => "binary",
1904
+ Expression::Unary { .. } => "unary",
1905
+ Expression::Cast { .. } => "cast",
1906
+ Expression::Quantifier { .. } => "quantifier",
1907
+ Expression::MemberAccess { .. } => "member_access",
1908
+ Expression::Aggregation { .. } => "aggregation",
1909
+ Expression::AggregationComprehension { .. } => "aggregation_comprehension",
1910
+ }
1911
+ }
1912
+
1913
+ /// Convert an Expression to a JSON Value for instance fields
1914
+ fn expression_to_json(expr: &Expression) -> ParseResult<JsonValue> {
1915
+ match expr {
1916
+ Expression::Literal(v) => Ok(v.clone()),
1917
+ Expression::Variable(name) => Ok(JsonValue::String(name.clone())),
1918
+ Expression::QuantityLiteral { value, unit } => Ok(json!({
1919
+ "value": value.to_string(),
1920
+ "unit": unit
1921
+ })),
1922
+ Expression::TimeLiteral(timestamp) => Ok(JsonValue::String(timestamp.clone())),
1923
+ _ => Err(ParseError::UnsupportedExpression {
1924
+ kind: expression_kind(expr).to_string(),
1925
+ span: None,
1926
+ }),
1927
+ }
1928
+ }
1929
+
1930
+ /// Parse mapping declaration
1931
+ fn parse_mapping(pair: Pair<Rule>) -> ParseResult<AstNode> {
1932
+ let mut inner = pair.into_inner();
1933
+
1934
+ let name = parse_string_literal(
1935
+ inner
1936
+ .next()
1937
+ .ok_or_else(|| ParseError::GrammarError("Expected mapping name".to_string()))?,
1938
+ )?;
1939
+
1940
+ let target_pair = inner
1941
+ .next()
1942
+ .ok_or_else(|| ParseError::GrammarError("Expected target format".to_string()))?;
1943
+ let target = parse_target_format(target_pair)?;
1944
+
1945
+ let mut rules = Vec::new();
1946
+
1947
+ for rule_pair in inner {
1948
+ if rule_pair.as_rule() == Rule::mapping_rule {
1949
+ rules.push(parse_mapping_rule(rule_pair)?);
1950
+ }
1951
+ }
1952
+
1953
+ Ok(AstNode::MappingDecl {
1954
+ name,
1955
+ target,
1956
+ rules,
1957
+ })
1958
+ }
1959
+
1960
+ fn parse_target_format(pair: Pair<Rule>) -> ParseResult<TargetFormat> {
1961
+ match pair.as_str().to_lowercase().as_str() {
1962
+ "calm" => Ok(TargetFormat::Calm),
1963
+ "kg" => Ok(TargetFormat::Kg),
1964
+ "sbvr" => Ok(TargetFormat::Sbvr),
1965
+ "protobuf" | "proto" => Ok(TargetFormat::Protobuf),
1966
+ _ => Err(ParseError::GrammarError(format!(
1967
+ "Unknown target format: {}",
1968
+ pair.as_str()
1969
+ ))),
1970
+ }
1971
+ }
1972
+
1973
+ fn parse_mapping_rule(pair: Pair<Rule>) -> ParseResult<MappingRule> {
1974
+ let mut inner = pair.into_inner();
1975
+
1976
+ let primitive_type = inner
1977
+ .next()
1978
+ .ok_or_else(|| ParseError::GrammarError("Expected primitive type".to_string()))?
1979
+ .as_str()
1980
+ .to_string();
1981
+
1982
+ let primitive_name = parse_string_literal(
1983
+ inner
1984
+ .next()
1985
+ .ok_or_else(|| ParseError::GrammarError("Expected primitive name".to_string()))?,
1986
+ )?;
1987
+
1988
+ let target_structure = inner
1989
+ .next()
1990
+ .ok_or_else(|| ParseError::GrammarError("Expected target structure".to_string()))?;
1991
+
1992
+ let mut target_inner = target_structure.into_inner();
1993
+ let target_type = parse_identifier(
1994
+ target_inner
1995
+ .next()
1996
+ .ok_or_else(|| ParseError::GrammarError("Expected target type".to_string()))?,
1997
+ )?;
1998
+
1999
+ let mut fields = HashMap::new();
2000
+ for field_pair in target_inner {
2001
+ if field_pair.as_rule() == Rule::mapping_field {
2002
+ let mut field_inner = field_pair.into_inner();
2003
+ let key = parse_identifier(
2004
+ field_inner
2005
+ .next()
2006
+ .ok_or_else(|| ParseError::GrammarError("Expected field key".to_string()))?,
2007
+ )?;
2008
+ let value_pair = field_inner
2009
+ .next()
2010
+ .ok_or_else(|| ParseError::GrammarError("Expected field value".to_string()))?;
2011
+
2012
+ let value = match value_pair.as_rule() {
2013
+ Rule::string_literal => JsonValue::String(parse_string_literal(value_pair)?),
2014
+ Rule::boolean => JsonValue::Bool(value_pair.as_str().eq_ignore_ascii_case("true")),
2015
+ Rule::object_literal => parse_object_literal(value_pair)?,
2016
+ _ => {
2017
+ return Err(ParseError::GrammarError(
2018
+ "Unexpected mapping field value".to_string(),
2019
+ ))
2020
+ }
2021
+ };
2022
+ fields.insert(key, value);
2023
+ }
2024
+ }
2025
+
2026
+ Ok(MappingRule {
2027
+ primitive_type,
2028
+ primitive_name,
2029
+ target_type,
2030
+ fields,
2031
+ })
2032
+ }
2033
+
2034
+ fn parse_object_literal(pair: Pair<Rule>) -> ParseResult<JsonValue> {
2035
+ let mut map = serde_json::Map::new();
2036
+ let mut inner = pair.into_inner();
2037
+ while let Some(key_pair) = inner.next() {
2038
+ let key = parse_string_literal(key_pair)?;
2039
+ let value_pair = inner.next().ok_or_else(|| {
2040
+ ParseError::GrammarError("Expected value in object literal".to_string())
2041
+ })?;
2042
+ let value = parse_annotation_value(value_pair)?;
2043
+ map.insert(key, value);
2044
+ }
2045
+ Ok(JsonValue::Object(map))
2046
+ }
2047
+
2048
+ fn parse_annotation_value(pair: Pair<Rule>) -> ParseResult<JsonValue> {
2049
+ match pair.as_rule() {
2050
+ Rule::annotation_value => {
2051
+ let mut inner = pair.into_inner();
2052
+ let value_pair = inner
2053
+ .next()
2054
+ .ok_or_else(|| ParseError::GrammarError("Expected annotation value".to_string()))?;
2055
+ parse_annotation_value(value_pair)
2056
+ }
2057
+ Rule::string_literal => Ok(JsonValue::String(parse_string_literal(pair)?)),
2058
+ Rule::string_array => {
2059
+ let mut values = Vec::new();
2060
+ for item in pair.into_inner() {
2061
+ values.push(JsonValue::String(parse_string_literal(item)?));
2062
+ }
2063
+ Ok(JsonValue::Array(values))
2064
+ }
2065
+ Rule::boolean => Ok(JsonValue::Bool(pair.as_str().eq_ignore_ascii_case("true"))),
2066
+ Rule::number => {
2067
+ let d = parse_decimal(pair)?;
2068
+ let f = d.to_f64().ok_or_else(|| {
2069
+ ParseError::InvalidQuantity(format!(
2070
+ "Decimal value {} cannot be represented as f64",
2071
+ d
2072
+ ))
2073
+ })?;
2074
+ if !f.is_finite() {
2075
+ return Err(ParseError::InvalidQuantity(format!(
2076
+ "Decimal value {} converts to non-finite f64",
2077
+ d
2078
+ )));
2079
+ }
2080
+ let num = serde_json::Number::from_f64(f).ok_or_else(|| {
2081
+ ParseError::InvalidQuantity(format!("Cannot create JSON Number from decimal {}", d))
2082
+ })?;
2083
+ Ok(JsonValue::Number(num))
2084
+ }
2085
+ Rule::object_literal => parse_object_literal(pair),
2086
+ _ => Err(ParseError::GrammarError(format!(
2087
+ "Unexpected annotation value: {}",
2088
+ pair.as_str()
2089
+ ))),
2090
+ }
2091
+ }
2092
+
2093
+ /// Parse projection declaration
2094
+ fn parse_projection(pair: Pair<Rule>) -> ParseResult<AstNode> {
2095
+ let mut inner = pair.into_inner();
2096
+
2097
+ let name = parse_string_literal(
2098
+ inner
2099
+ .next()
2100
+ .ok_or_else(|| ParseError::GrammarError("Expected projection name".to_string()))?,
2101
+ )?;
2102
+
2103
+ let target_pair = inner
2104
+ .next()
2105
+ .ok_or_else(|| ParseError::GrammarError("Expected target format".to_string()))?;
2106
+ let target = parse_target_format(target_pair)?;
2107
+
2108
+ let mut overrides = Vec::new();
2109
+
2110
+ for rule_pair in inner {
2111
+ if rule_pair.as_rule() == Rule::projection_rule {
2112
+ overrides.push(parse_projection_rule(rule_pair)?);
2113
+ }
2114
+ }
2115
+
2116
+ Ok(AstNode::ProjectionDecl {
2117
+ name,
2118
+ target,
2119
+ overrides,
2120
+ })
2121
+ }
2122
+
2123
+ fn parse_projection_rule(pair: Pair<Rule>) -> ParseResult<ProjectionOverride> {
2124
+ let mut inner = pair.into_inner();
2125
+
2126
+ let primitive_type = inner
2127
+ .next()
2128
+ .ok_or_else(|| ParseError::GrammarError("Expected primitive type".to_string()))?
2129
+ .as_str()
2130
+ .to_string();
2131
+
2132
+ let primitive_name = parse_string_literal(
2133
+ inner
2134
+ .next()
2135
+ .ok_or_else(|| ParseError::GrammarError("Expected primitive name".to_string()))?,
2136
+ )?;
2137
+
2138
+ let mut fields = HashMap::new();
2139
+ for field_pair in inner {
2140
+ if field_pair.as_rule() == Rule::projection_field {
2141
+ let mut field_inner = field_pair.into_inner();
2142
+ let key = parse_identifier(
2143
+ field_inner
2144
+ .next()
2145
+ .ok_or_else(|| ParseError::GrammarError("Expected field key".to_string()))?,
2146
+ )?;
2147
+ let value_pair = field_inner
2148
+ .next()
2149
+ .ok_or_else(|| ParseError::GrammarError("Expected field value".to_string()))?;
2150
+
2151
+ let value = match value_pair.as_rule() {
2152
+ Rule::string_literal => JsonValue::String(parse_string_literal(value_pair)?),
2153
+ Rule::property_mapping => parse_property_mapping(value_pair)?,
2154
+ _ => {
2155
+ return Err(ParseError::GrammarError(
2156
+ "Unexpected projection field value".to_string(),
2157
+ ))
2158
+ }
2159
+ };
2160
+ fields.insert(key, value);
2161
+ }
2162
+ }
2163
+
2164
+ Ok(ProjectionOverride {
2165
+ primitive_type,
2166
+ primitive_name,
2167
+ fields,
2168
+ })
2169
+ }
2170
+
2171
+ fn parse_property_mapping(pair: Pair<Rule>) -> ParseResult<JsonValue> {
2172
+ let mut map = serde_json::Map::new();
2173
+ let mut inner = pair.into_inner();
2174
+ while let Some(key_pair) = inner.next() {
2175
+ let key = parse_string_literal(key_pair)?;
2176
+ let value_pair = inner.next().ok_or_else(|| {
2177
+ ParseError::GrammarError("Expected value in property mapping".to_string())
2178
+ })?;
2179
+ let value = parse_string_literal(value_pair)?;
2180
+ map.insert(key, JsonValue::String(value));
2181
+ }
2182
+ Ok(JsonValue::Object(map))
2183
+ }
2184
+
2185
+ fn unwrap_export(spanned: &Spanned<AstNode>) -> &AstNode {
2186
+ match &spanned.node {
2187
+ AstNode::Export(inner) => &inner.node,
2188
+ other => other,
2189
+ }
2190
+ }
2191
+
2192
+ /// Resolve a name in a namespace-keyed map.
2193
+ /// Tries (default_namespace, name) first, then scans all namespaces for an unqualified match.
2194
+ /// Returns None if not found, or the concept ID if exactly one match exists.
2195
+ fn resolve_by_name(
2196
+ map: &HashMap<(String, String), crate::ConceptId>,
2197
+ name: &str,
2198
+ default_namespace: &str,
2199
+ ) -> Result<Option<crate::ConceptId>, ParseError> {
2200
+ // Try exact match in default namespace first
2201
+ if let Some(id) = map.get(&(default_namespace.to_string(), name.to_string())) {
2202
+ return Ok(Some(id.clone()));
2203
+ }
2204
+ // Fall back to any namespace with this name
2205
+ let matches: Vec<(&str, &crate::ConceptId)> = map
2206
+ .iter()
2207
+ .filter(|((_, n), _)| n == name)
2208
+ .map(|((namespace, _), id)| (namespace.as_str(), id))
2209
+ .collect();
2210
+ if matches.len() == 1 {
2211
+ return Ok(Some(matches[0].1.clone()));
2212
+ }
2213
+ if matches.len() > 1 {
2214
+ let mut namespaces: Vec<&str> = matches
2215
+ .into_iter()
2216
+ .map(|(namespace, _)| namespace)
2217
+ .collect();
2218
+ namespaces.sort_unstable();
2219
+ namespaces.dedup();
2220
+ return Err(ParseError::Validation(format!(
2221
+ "Ambiguous reference '{}' found in namespaces: {}",
2222
+ name,
2223
+ namespaces.join(", ")
2224
+ )));
2225
+ }
2226
+ Ok(None)
2227
+ }
2228
+
2229
+ /// Convert AST to Graph
2230
+ pub fn ast_to_graph(ast: Ast) -> ParseResult<Graph> {
2231
+ ast_to_graph_with_options(ast, &ParseOptions::default())
2232
+ }
2233
+
2234
+ pub fn ast_to_graph_with_options(mut ast: Ast, options: &ParseOptions) -> ParseResult<Graph> {
2235
+ use crate::parser::profiles::ProfileRegistry;
2236
+ let registry = ProfileRegistry::global();
2237
+ let active_profile = ast
2238
+ .metadata
2239
+ .profile
2240
+ .clone()
2241
+ .or_else(|| options.active_profile.clone())
2242
+ .unwrap_or_else(|| "default".to_string());
2243
+ ast.metadata.profile.get_or_insert(active_profile.clone());
2244
+
2245
+ if registry.get(&active_profile).is_none() {
2246
+ let available = registry.list_names().join(", ");
2247
+ let message = format!(
2248
+ "Unknown profile: '{}'. Available profiles: {}",
2249
+ active_profile, available
2250
+ );
2251
+ if options.tolerate_profile_warnings {
2252
+ log::warn!("{}", message);
2253
+ } else {
2254
+ return Err(ParseError::Validation(message));
2255
+ }
2256
+ }
2257
+
2258
+ let mut graph = Graph::new();
2259
+ let mut entity_map: HashMap<(String, String), crate::ConceptId> = HashMap::new();
2260
+ let mut role_map: HashMap<(String, String), crate::ConceptId> = HashMap::new();
2261
+ let mut resource_map: HashMap<(String, String), crate::ConceptId> = HashMap::new();
2262
+ let mut relation_map: HashMap<String, crate::ConceptId> = HashMap::new();
2263
+
2264
+ let default_namespace = ast
2265
+ .metadata
2266
+ .namespace
2267
+ .clone()
2268
+ .or_else(|| options.default_namespace.clone())
2269
+ .unwrap_or_else(|| "default".to_string());
2270
+
2271
+ // First pass: Register dimensions and units
2272
+ {
2273
+ use crate::units::{Dimension, Unit, UnitError, UnitRegistry};
2274
+ let registry = UnitRegistry::global();
2275
+ let mut registry = registry.write().map_err(|e| {
2276
+ ParseError::GrammarError(format!("Failed to lock unit registry: {}", e))
2277
+ })?;
2278
+
2279
+ for node in &ast.declarations {
2280
+ let node = unwrap_export(node);
2281
+ match node {
2282
+ AstNode::Dimension { name } => {
2283
+ let dim = Dimension::parse(name);
2284
+ registry.register_dimension(dim);
2285
+ }
2286
+ AstNode::UnitDeclaration {
2287
+ symbol,
2288
+ dimension,
2289
+ factor,
2290
+ base_unit,
2291
+ } => {
2292
+ let dim = Dimension::parse(dimension);
2293
+ let unit = Unit::new(
2294
+ symbol.clone(),
2295
+ symbol.clone(),
2296
+ dim,
2297
+ *factor,
2298
+ base_unit.clone(),
2299
+ );
2300
+ match registry.get_unit(symbol) {
2301
+ Ok(existing) => {
2302
+ if existing != &unit {
2303
+ return Err(ParseError::GrammarError(format!(
2304
+ "Conflicting unit '{}' already registered (existing: dimension={}, base_factor={}, base_unit={}; new: dimension={}, base_factor={}, base_unit={})",
2305
+ symbol,
2306
+ existing.dimension(),
2307
+ existing.base_factor(),
2308
+ existing.base_unit(),
2309
+ unit.dimension(),
2310
+ unit.base_factor(),
2311
+ unit.base_unit(),
2312
+ )));
2313
+ }
2314
+ }
2315
+ Err(UnitError::UnitNotFound(_)) => {
2316
+ registry.register(unit).map_err(|e| {
2317
+ ParseError::GrammarError(format!("Failed to register unit: {}", e))
2318
+ })?;
2319
+ }
2320
+ Err(err) => {
2321
+ return Err(ParseError::GrammarError(format!(
2322
+ "Failed to inspect unit '{}': {}",
2323
+ symbol, err
2324
+ )));
2325
+ }
2326
+ }
2327
+ }
2328
+ _ => {}
2329
+ }
2330
+ }
2331
+ }
2332
+
2333
+ // Register patterns with eager regex validation
2334
+ for node in &ast.declarations {
2335
+ let node = unwrap_export(node);
2336
+ if let AstNode::Pattern { name, regex } = node {
2337
+ let namespace = default_namespace.clone();
2338
+ let pattern = Pattern::new(name.clone(), namespace, regex.clone())
2339
+ .map_err(ParseError::GrammarError)?;
2340
+
2341
+ graph
2342
+ .add_pattern(pattern)
2343
+ .map_err(ParseError::GrammarError)?;
2344
+ }
2345
+ }
2346
+
2347
+ // Register concept changes
2348
+ for node in &ast.declarations {
2349
+ let node = unwrap_export(node);
2350
+ if let AstNode::ConceptChange {
2351
+ name,
2352
+ from_version,
2353
+ to_version,
2354
+ migration_policy,
2355
+ breaking_change,
2356
+ } = node
2357
+ {
2358
+ let change = ConceptChange::new(
2359
+ name.clone(),
2360
+ from_version.clone(),
2361
+ to_version.clone(),
2362
+ migration_policy.clone(),
2363
+ *breaking_change,
2364
+ );
2365
+ graph.add_concept_change(change).map_err(|e| {
2366
+ ParseError::GrammarError(format!("Failed to add concept change: {}", e))
2367
+ })?;
2368
+ }
2369
+ }
2370
+
2371
+ // Second pass: Add roles, entities, and resources
2372
+ for node in &ast.declarations {
2373
+ let node = unwrap_export(node);
2374
+ match node {
2375
+ AstNode::Role { name, domain } => {
2376
+ let namespace = domain.as_ref().unwrap_or(&default_namespace).clone();
2377
+ let key = (namespace.clone(), name.clone());
2378
+ if role_map.contains_key(&key) {
2379
+ return Err(ParseError::duplicate_declaration_no_loc(format!(
2380
+ "Role '{}' already declared in namespace '{}'",
2381
+ name, namespace
2382
+ )));
2383
+ }
2384
+
2385
+ let role = Role::new_with_namespace(name.clone(), namespace);
2386
+ let role_id = role.id().clone();
2387
+ graph
2388
+ .add_role(role)
2389
+ .map_err(|e| ParseError::GrammarError(format!("Failed to add role: {}", e)))?;
2390
+ role_map.insert(key, role_id);
2391
+ }
2392
+ AstNode::Entity {
2393
+ name,
2394
+ domain,
2395
+ version,
2396
+ annotations,
2397
+ } => {
2398
+ let namespace = domain.as_ref().unwrap_or(&default_namespace).clone();
2399
+ let key = (namespace.clone(), name.clone());
2400
+ if entity_map.contains_key(&key) {
2401
+ return Err(ParseError::duplicate_declaration_no_loc(format!(
2402
+ "Entity '{}' already declared in namespace '{}'",
2403
+ name, namespace
2404
+ )));
2405
+ }
2406
+
2407
+ let mut entity = Entity::new_with_namespace(name.clone(), namespace);
2408
+
2409
+ if let Some(v_str) = version {
2410
+ let sem_ver = SemanticVersion::parse(v_str).map_err(|e| {
2411
+ ParseError::GrammarError(format!(
2412
+ "Invalid entity version '{}': {}",
2413
+ v_str, e
2414
+ ))
2415
+ })?;
2416
+ entity = entity.with_version(sem_ver);
2417
+ }
2418
+
2419
+ if let Some(replaces_val) = annotations.get("replaces") {
2420
+ if let Some(replaces_str) = replaces_val.as_str() {
2421
+ entity = entity.with_replaces(replaces_str.to_string());
2422
+ }
2423
+ }
2424
+
2425
+ if let Some(changes_val) = annotations.get("changes") {
2426
+ if let Some(changes_arr) = changes_val.as_array() {
2427
+ let changes: Vec<String> = changes_arr
2428
+ .iter()
2429
+ .filter_map(|v| v.as_str().map(|s| s.to_string()))
2430
+ .collect();
2431
+ entity = entity.with_changes(changes);
2432
+ }
2433
+ }
2434
+
2435
+ let entity_id = entity.id().clone();
2436
+ graph.add_entity(entity).map_err(|e| {
2437
+ ParseError::GrammarError(format!("Failed to add entity: {}", e))
2438
+ })?;
2439
+ entity_map.insert(key, entity_id);
2440
+ }
2441
+ AstNode::Resource {
2442
+ name,
2443
+ unit_name,
2444
+ domain,
2445
+ ..
2446
+ } => {
2447
+ let namespace = domain.as_ref().unwrap_or(&default_namespace).clone();
2448
+ let key = (namespace.clone(), name.clone());
2449
+ if resource_map.contains_key(&key) {
2450
+ return Err(ParseError::duplicate_declaration_no_loc(format!(
2451
+ "Resource '{}' already declared in namespace '{}'",
2452
+ name, namespace
2453
+ )));
2454
+ }
2455
+
2456
+ let unit = unit_from_string(unit_name.as_deref().unwrap_or("units"));
2457
+ let resource = Resource::new_with_namespace(name.clone(), unit, namespace);
2458
+ let resource_id = resource.id().clone();
2459
+ graph.add_resource(resource).map_err(|e| {
2460
+ ParseError::GrammarError(format!("Failed to add resource: {}", e))
2461
+ })?;
2462
+ resource_map.insert(key, resource_id);
2463
+ }
2464
+ _ => {}
2465
+ }
2466
+ }
2467
+
2468
+ // Third pass: Add flows
2469
+ for node in &ast.declarations {
2470
+ let node = unwrap_export(node);
2471
+ if let AstNode::Flow {
2472
+ resource_name,
2473
+ from_entity,
2474
+ to_entity,
2475
+ quantity,
2476
+ ..
2477
+ } = node
2478
+ {
2479
+ let from_id = resolve_by_name(&entity_map, from_entity, &default_namespace)?
2480
+ .ok_or_else(|| ParseError::undefined_entity_no_loc(from_entity))?;
2481
+
2482
+ let to_id = resolve_by_name(&entity_map, to_entity, &default_namespace)?
2483
+ .ok_or_else(|| ParseError::undefined_entity_no_loc(to_entity))?;
2484
+
2485
+ let resource_id = resolve_by_name(&resource_map, resource_name, &default_namespace)?
2486
+ .ok_or_else(|| ParseError::undefined_resource_no_loc(resource_name))?;
2487
+
2488
+ let qty = quantity.unwrap_or(Decimal::ZERO);
2489
+ let flow = Flow::new_with_namespace(
2490
+ resource_id.clone(),
2491
+ from_id.clone(),
2492
+ to_id.clone(),
2493
+ qty,
2494
+ default_namespace.clone(),
2495
+ );
2496
+
2497
+ graph
2498
+ .add_flow(flow)
2499
+ .map_err(|e| ParseError::GrammarError(format!("Failed to add flow: {}", e)))?;
2500
+ }
2501
+ }
2502
+
2503
+ // Fourth pass: Add relations
2504
+ for node in &ast.declarations {
2505
+ let node = unwrap_export(node);
2506
+ if let AstNode::Relation {
2507
+ name,
2508
+ subject_role,
2509
+ predicate,
2510
+ object_role,
2511
+ via_flow,
2512
+ } = node
2513
+ {
2514
+ if relation_map.contains_key(name) {
2515
+ return Err(ParseError::duplicate_declaration_no_loc(format!(
2516
+ "Relation '{}' already declared",
2517
+ name
2518
+ )));
2519
+ }
2520
+
2521
+ let subject_id = resolve_by_name(&role_map, subject_role, &default_namespace)?
2522
+ .ok_or_else(|| {
2523
+ ParseError::GrammarError(format!("Undefined subject role '{}'", subject_role))
2524
+ })?;
2525
+
2526
+ let object_id = resolve_by_name(&role_map, object_role, &default_namespace)?
2527
+ .ok_or_else(|| {
2528
+ ParseError::GrammarError(format!("Undefined object role '{}'", object_role))
2529
+ })?;
2530
+
2531
+ let via_flow_id = if let Some(flow_name) = via_flow {
2532
+ Some(
2533
+ resolve_by_name(&resource_map, flow_name, &default_namespace)?
2534
+ .ok_or_else(|| ParseError::undefined_resource_no_loc(flow_name))?,
2535
+ )
2536
+ } else {
2537
+ None
2538
+ };
2539
+
2540
+ let relation = RelationType::new(
2541
+ name.clone(),
2542
+ default_namespace.clone(),
2543
+ subject_id.clone(),
2544
+ predicate.clone(),
2545
+ object_id.clone(),
2546
+ via_flow_id,
2547
+ );
2548
+
2549
+ let relation_id = relation.id().clone();
2550
+ graph.add_relation_type(relation).map_err(|e| {
2551
+ ParseError::GrammarError(format!("Failed to add relation '{}': {}", name, e))
2552
+ })?;
2553
+ relation_map.insert(name.clone(), relation_id);
2554
+ }
2555
+ }
2556
+
2557
+ // Instance pass: Add instances (after entities are created)
2558
+ for node in &ast.declarations {
2559
+ let node = unwrap_export(node);
2560
+ if let AstNode::Instance {
2561
+ name,
2562
+ entity_type,
2563
+ fields,
2564
+ } = node
2565
+ {
2566
+ let namespace = default_namespace.clone();
2567
+ let mut instance = crate::primitives::Instance::new_with_namespace(
2568
+ name.clone(),
2569
+ entity_type.clone(),
2570
+ namespace,
2571
+ );
2572
+
2573
+ // Evaluate and set fields
2574
+ for (field_name, field_expr) in fields {
2575
+ let value = expression_to_json(field_expr)?;
2576
+ instance.set_field(field_name.clone(), value);
2577
+ }
2578
+
2579
+ graph.add_entity_instance(instance).map_err(|e| {
2580
+ ParseError::GrammarError(format!("Failed to add entity instance '{}': {}", name, e))
2581
+ })?;
2582
+ }
2583
+ }
2584
+
2585
+ // Fifth pass: Add policies
2586
+ for node in &ast.declarations {
2587
+ let node = unwrap_export(node);
2588
+ if let AstNode::Policy {
2589
+ name,
2590
+ version,
2591
+ metadata,
2592
+ expression,
2593
+ } = node
2594
+ {
2595
+ let namespace = ast
2596
+ .metadata
2597
+ .namespace
2598
+ .as_ref()
2599
+ .cloned()
2600
+ .or_else(|| options.default_namespace.clone())
2601
+ .unwrap_or_else(|| "default".to_string());
2602
+
2603
+ let kind = metadata.kind.as_ref().map(|kind| match kind {
2604
+ PolicyKind::Constraint => CorePolicyKind::Constraint,
2605
+ PolicyKind::Derivation => CorePolicyKind::Derivation,
2606
+ PolicyKind::Obligation => CorePolicyKind::Obligation,
2607
+ });
2608
+
2609
+ let modality = metadata.modality.as_ref().map(|modality| match modality {
2610
+ PolicyModality::Obligation => CorePolicyModality::Obligation,
2611
+ PolicyModality::Prohibition => CorePolicyModality::Prohibition,
2612
+ PolicyModality::Permission => CorePolicyModality::Permission,
2613
+ });
2614
+
2615
+ let mut policy =
2616
+ Policy::new_with_namespace(name.clone(), namespace, expression.clone())
2617
+ .with_metadata(
2618
+ kind,
2619
+ modality,
2620
+ metadata.priority,
2621
+ metadata.rationale.clone(),
2622
+ metadata.tags.clone(),
2623
+ );
2624
+
2625
+ let version_to_apply = version
2626
+ .as_ref()
2627
+ .cloned()
2628
+ .or_else(|| ast.metadata.version.clone());
2629
+
2630
+ if let Some(version_str) = version_to_apply {
2631
+ let semantic_version = SemanticVersion::parse(&version_str).map_err(|err| {
2632
+ ParseError::GrammarError(format!(
2633
+ "Invalid policy version '{}': {}",
2634
+ version_str, err
2635
+ ))
2636
+ })?;
2637
+ policy = policy.with_version(semantic_version);
2638
+ }
2639
+
2640
+ graph.add_policy(policy).map_err(|e| {
2641
+ ParseError::GrammarError(format!("Failed to add policy '{}': {}", name, e))
2642
+ })?;
2643
+ }
2644
+ }
2645
+
2646
+ // Sixth pass: Add metrics
2647
+ for node in &ast.declarations {
2648
+ let node = unwrap_export(node);
2649
+ if let AstNode::Metric {
2650
+ name,
2651
+ expression,
2652
+ metadata,
2653
+ } = node
2654
+ {
2655
+ let namespace = ast
2656
+ .metadata
2657
+ .namespace
2658
+ .as_ref()
2659
+ .cloned()
2660
+ .or_else(|| options.default_namespace.clone())
2661
+ .unwrap_or_else(|| "default".to_string());
2662
+
2663
+ let mut metric =
2664
+ crate::primitives::Metric::new(name.clone(), namespace, expression.clone());
2665
+
2666
+ if let Some(duration) = metadata.refresh_interval {
2667
+ metric = metric.with_refresh_interval(duration);
2668
+ }
2669
+
2670
+ if let Some(unit) = &metadata.unit {
2671
+ metric = metric.with_unit(unit.clone());
2672
+ }
2673
+
2674
+ if let Some(threshold) = metadata.threshold {
2675
+ metric = metric.with_threshold(threshold);
2676
+ }
2677
+
2678
+ if let Some(severity) = metadata.severity.clone() {
2679
+ metric = metric.with_severity(severity);
2680
+ }
2681
+
2682
+ if let Some(target) = metadata.target {
2683
+ metric = metric.with_target(target);
2684
+ }
2685
+
2686
+ if let Some(duration) = metadata.window {
2687
+ metric = metric.with_window(duration);
2688
+ }
2689
+
2690
+ graph.add_metric(metric).map_err(|e| {
2691
+ ParseError::GrammarError(format!("Failed to add metric '{}': {}", name, e))
2692
+ })?;
2693
+ }
2694
+ }
2695
+
2696
+ // Seventh pass: Add mappings
2697
+ for node in &ast.declarations {
2698
+ let node = unwrap_export(node);
2699
+ if let AstNode::MappingDecl {
2700
+ name,
2701
+ target,
2702
+ rules,
2703
+ } = node
2704
+ {
2705
+ let namespace = ast
2706
+ .metadata
2707
+ .namespace
2708
+ .clone()
2709
+ .or_else(|| options.default_namespace.clone())
2710
+ .unwrap_or_else(|| "default".to_string());
2711
+ let mapping = crate::primitives::MappingContract::new(
2712
+ crate::ConceptId::from_concept(&namespace, name),
2713
+ name.clone(),
2714
+ namespace,
2715
+ target.clone(),
2716
+ rules.clone(),
2717
+ );
2718
+ graph
2719
+ .add_mapping(mapping)
2720
+ .map_err(|e| ParseError::GrammarError(format!("Failed to add mapping: {}", e)))?;
2721
+ }
2722
+ }
2723
+
2724
+ // Eighth pass: Add projections
2725
+ for node in &ast.declarations {
2726
+ let node = unwrap_export(node);
2727
+ if let AstNode::ProjectionDecl {
2728
+ name,
2729
+ target,
2730
+ overrides,
2731
+ } = node
2732
+ {
2733
+ let namespace = ast
2734
+ .metadata
2735
+ .namespace
2736
+ .clone()
2737
+ .or_else(|| options.default_namespace.clone())
2738
+ .unwrap_or_else(|| "default".to_string());
2739
+ let projection = crate::primitives::ProjectionContract::new(
2740
+ crate::ConceptId::from_concept(&namespace, name),
2741
+ name.clone(),
2742
+ namespace,
2743
+ target.clone(),
2744
+ overrides.clone(),
2745
+ );
2746
+ graph.add_projection(projection).map_err(|e| {
2747
+ ParseError::GrammarError(format!("Failed to add projection: {}", e))
2748
+ })?;
2749
+ }
2750
+ }
2751
+
2752
+ Ok(graph)
2753
+ }
2754
+
2755
+ /// Parse metric declaration
2756
+ fn parse_metric(pair: Pair<Rule>) -> ParseResult<AstNode> {
2757
+ let mut inner = pair.into_inner();
2758
+
2759
+ let name = parse_name(
2760
+ inner
2761
+ .next()
2762
+ .ok_or_else(|| ParseError::GrammarError("Expected metric name".to_string()))?,
2763
+ )?;
2764
+
2765
+ let mut metadata = MetricMetadata {
2766
+ refresh_interval: None,
2767
+ unit: None,
2768
+ threshold: None,
2769
+ severity: None,
2770
+ target: None,
2771
+ window: None,
2772
+ };
2773
+
2774
+ let mut expression_pair = None;
2775
+
2776
+ for part in inner {
2777
+ match part.as_rule() {
2778
+ Rule::metric_annotation => {
2779
+ let mut annotation_inner = part.into_inner();
2780
+ let key_pair = annotation_inner.next().ok_or_else(|| {
2781
+ ParseError::GrammarError("Expected annotation key".to_string())
2782
+ })?;
2783
+
2784
+ match key_pair.as_rule() {
2785
+ Rule::ma_refresh_interval => {
2786
+ let value_pair = annotation_inner.next().ok_or_else(|| {
2787
+ ParseError::GrammarError("Expected refresh interval value".to_string())
2788
+ })?;
2789
+ let value = parse_number_i64(value_pair)?;
2790
+ let unit_pair = annotation_inner.next().ok_or_else(|| {
2791
+ ParseError::GrammarError("Expected refresh interval unit".to_string())
2792
+ })?;
2793
+ let unit = parse_string_literal(unit_pair.clone())?;
2794
+ let duration = parse_duration_with_unit(value, &unit, unit_pair.as_span())?;
2795
+ metadata.refresh_interval = Some(duration);
2796
+ }
2797
+ Rule::ma_unit => {
2798
+ let unit_pair = annotation_inner
2799
+ .next()
2800
+ .ok_or_else(|| ParseError::GrammarError("Expected unit".to_string()))?;
2801
+ metadata.unit = Some(parse_string_literal(unit_pair)?);
2802
+ }
2803
+ Rule::ma_threshold => {
2804
+ let value_pair = annotation_inner.next().ok_or_else(|| {
2805
+ ParseError::GrammarError("Expected threshold value".to_string())
2806
+ })?;
2807
+ metadata.threshold = Some(parse_decimal(value_pair)?);
2808
+ }
2809
+ Rule::ma_severity => {
2810
+ let severity_pair = annotation_inner.next().ok_or_else(|| {
2811
+ ParseError::GrammarError("Expected severity".to_string())
2812
+ })?;
2813
+ let severity_str = parse_string_literal(severity_pair.clone())?;
2814
+ let severity =
2815
+ parse_severity_value(&severity_str, severity_pair.as_span())?;
2816
+ metadata.severity = Some(severity);
2817
+ }
2818
+ Rule::ma_target => {
2819
+ let value_pair = annotation_inner.next().ok_or_else(|| {
2820
+ ParseError::GrammarError("Expected target value".to_string())
2821
+ })?;
2822
+ metadata.target = Some(parse_decimal(value_pair)?);
2823
+ }
2824
+ Rule::ma_window => {
2825
+ let value_pair = annotation_inner.next().ok_or_else(|| {
2826
+ ParseError::GrammarError("Expected window value".to_string())
2827
+ })?;
2828
+ let value = parse_number_i64(value_pair)?;
2829
+ let unit_pair = annotation_inner.next().ok_or_else(|| {
2830
+ ParseError::GrammarError("Expected window unit".to_string())
2831
+ })?;
2832
+ let unit = parse_string_literal(unit_pair.clone())?;
2833
+ let duration = parse_duration_with_unit(value, &unit, unit_pair.as_span())?;
2834
+ metadata.window = Some(duration);
2835
+ }
2836
+ _ => {
2837
+ let (line, column) = key_pair.as_span().start_pos().line_col();
2838
+ return Err(ParseError::GrammarError(format!(
2839
+ "Unknown metric annotation '{}' at {}:{}",
2840
+ key_pair.as_str(),
2841
+ line,
2842
+ column
2843
+ )));
2844
+ }
2845
+ }
2846
+ }
2847
+ Rule::expression => {
2848
+ expression_pair = Some(part);
2849
+ }
2850
+ _ => {}
2851
+ }
2852
+ }
2853
+
2854
+ let expression = parse_expression(
2855
+ expression_pair
2856
+ .ok_or_else(|| ParseError::GrammarError("Expected metric expression".to_string()))?,
2857
+ )?;
2858
+
2859
+ Ok(AstNode::Metric {
2860
+ name,
2861
+ expression,
2862
+ metadata,
2863
+ })
2864
+ }
2865
+
2866
+ fn parse_duration_with_unit(value: i64, unit: &str, span: Span<'_>) -> ParseResult<Duration> {
2867
+ let normalized_unit = unit.to_ascii_lowercase();
2868
+ let multiplier = match normalized_unit.as_str() {
2869
+ "second" => Some(1),
2870
+ "seconds" | "s" => Some(1),
2871
+ "minute" => Some(60),
2872
+ "minutes" | "m" => Some(60),
2873
+ "hour" => Some(60 * 60),
2874
+ "hours" | "h" => Some(60 * 60),
2875
+ "day" => Some(60 * 60 * 24),
2876
+ "days" | "d" => Some(60 * 60 * 24),
2877
+ _ => None,
2878
+ }
2879
+ .ok_or_else(|| {
2880
+ let (line, column) = span.start_pos().line_col();
2881
+ ParseError::GrammarError(format!(
2882
+ "Invalid duration unit '{}' at {}:{} (allowed: second(s)/s, minute(s)/m, hour(s)/h, day(s)/d)",
2883
+ unit, line, column
2884
+ ))
2885
+ })?;
2886
+
2887
+ let total_seconds = value.checked_mul(multiplier).ok_or_else(|| {
2888
+ let (line, column) = span.start_pos().line_col();
2889
+ ParseError::GrammarError(format!(
2890
+ "Duration overflow for value {} {} at {}:{}",
2891
+ value, unit, line, column
2892
+ ))
2893
+ })?;
2894
+
2895
+ Ok(Duration::seconds(total_seconds))
2896
+ }
2897
+
2898
+ fn parse_severity_value(value: &str, span: Span<'_>) -> ParseResult<Severity> {
2899
+ let normalized = value.to_ascii_lowercase();
2900
+ match normalized.as_str() {
2901
+ "info" => Ok(Severity::Info),
2902
+ "warning" => Ok(Severity::Warning),
2903
+ "error" => Ok(Severity::Error),
2904
+ "critical" => Ok(Severity::Critical),
2905
+ _ => {
2906
+ let (line, column) = span.start_pos().line_col();
2907
+ Err(ParseError::GrammarError(format!(
2908
+ "Unknown severity '{}' at {}:{} (expected one of: info, warning, error, critical)",
2909
+ value, line, column
2910
+ )))
2911
+ }
2912
+ }
2913
+ }
2914
+
2915
+ fn parse_number_i64(pair: Pair<Rule>) -> ParseResult<i64> {
2916
+ let s = pair.as_str();
2917
+ s.parse::<i64>()
2918
+ .map_err(|_| ParseError::GrammarError(format!("Invalid integer: {}", s)))
2919
+ }