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.
- package/.cargo/config.toml +6 -0
- package/.claude/settings.local.json +18 -0
- package/.coderabbit.yml +43 -0
- package/.codex/skills/release-management/SKILL.md +151 -0
- package/.codex/skills/release-management/agents/openai.yaml +4 -0
- package/.github/actions/decrypt-secrets/action.yml +121 -0
- package/.github/agents/Coder.agent.md +97 -0
- package/.github/agents/DeepResearch.agent.md +61 -0
- package/.github/chatmodes/tdd.vibepro.chatmode.md +1183 -0
- package/.github/copilot-instructions.md +13 -0
- package/.github/dependabot.yml +68 -0
- package/.github/workflows/README.md +165 -0
- package/.github/workflows/ci.yml +335 -0
- package/.github/workflows/dependabot-automerge.yml +114 -0
- package/.github/workflows/dependency-review.yml +27 -0
- package/.github/workflows/deploy.yml +87 -0
- package/.github/workflows/prepare-release.yml +168 -0
- package/.github/workflows/release-crates.yml +42 -0
- package/.github/workflows/release-npm.yml +137 -0
- package/.github/workflows/release-please.yml +29 -0
- package/.github/workflows/release-pypi.yml +96 -0
- package/.gitkeep +1 -0
- package/.release-please-manifest.json +5 -0
- package/.sea-registry.toml +10 -0
- package/.serena/project.yml +133 -0
- package/.sops.yaml +10 -0
- package/AGENTS.md +216 -0
- package/CHANGELOG.md +400 -0
- package/CLAUDE.md +62 -0
- package/CONTRIBUTING.md +323 -0
- package/Cargo.lock +3612 -0
- package/Cargo.toml +12 -0
- package/LICENSE +201 -0
- package/README.md +660 -0
- package/README_PYTHON.md +256 -0
- package/README_TYPESCRIPT.md +305 -0
- package/README_WASM.md +329 -0
- package/RELEASE_NOTES.md +41 -0
- package/bun.lock +378 -0
- package/bunfig.toml +11 -0
- package/check_output.txt +83 -0
- package/clippy_output.txt +80 -0
- package/commitlint.config.cjs +8 -0
- package/deny.toml +42 -0
- package/devbox.json +14 -0
- package/devbox.lock +76 -0
- package/docs/RELEASE_PROCESS.md +360 -0
- package/docs/diagnostics.md +161 -0
- package/docs/doc_guidelines.md +53 -0
- package/docs/explanations/README.md +21 -0
- package/docs/explanations/architecture-overview.md +109 -0
- package/docs/explanations/cross-language-binding-strategy.md +68 -0
- package/docs/explanations/graph-store-design.md +47 -0
- package/docs/explanations/performance-benchmarks.md +63 -0
- package/docs/explanations/policy-evaluation-logic.md +106 -0
- package/docs/explanations/semantic-modeling-concepts.md +109 -0
- package/docs/explanations/three-valued-logic.md +66 -0
- package/docs/explanations/versioning-strategy.md +45 -0
- package/docs/governance.md +168 -0
- package/docs/how-tos/README.md +46 -0
- package/docs/how-tos/ci-cd-validation.md +93 -0
- package/docs/how-tos/create-custom-units.md +125 -0
- package/docs/how-tos/define-policies.md +119 -0
- package/docs/how-tos/export-to-calm.md +110 -0
- package/docs/how-tos/export-to-protobuf.md +312 -0
- package/docs/how-tos/extend-grammar.md +133 -0
- package/docs/how-tos/generate-rdf-turtle.md +106 -0
- package/docs/how-tos/import-from-calm.md +114 -0
- package/docs/how-tos/import-from-sbvr.md +249 -0
- package/docs/how-tos/install-cli.md +126 -0
- package/docs/how-tos/parse-sea-files.md +132 -0
- package/docs/how-tos/policy-evaluation-modes.md +30 -0
- package/docs/how-tos/run-cross-language-tests.md +115 -0
- package/docs/how-tos/troubleshoot-napi-builds.md +55 -0
- package/docs/how-tos/use-modules-imports.md +285 -0
- package/docs/index.md +13 -0
- package/docs/plans/canonical-normalizer.md +121 -0
- package/docs/plans/cd_improvement.md +112 -0
- package/docs/plans/cli-ast.md +29 -0
- package/docs/plans/expression-bindings-and-normalizer-integration.md +174 -0
- package/docs/plans/protobuf_advanced_features_plan.md +597 -0
- package/docs/plans/protobuf_plan.yml +525 -0
- package/docs/plans/refactor_dsl_architecture.md +131 -0
- package/docs/plans/release-plan.md +163 -0
- package/docs/plans/sea_fmt_implementation_plan.md +516 -0
- package/docs/playbooks/README.md +18 -0
- package/docs/playbooks/adding-new-primitive.md +68 -0
- package/docs/playbooks/debugging-parser-failures.md +42 -0
- package/docs/playbooks/local-release-preparation.md +139 -0
- package/docs/playbooks/migrating-schema-versions.md +43 -0
- package/docs/playbooks/onboarding-contributors.md +64 -0
- package/docs/playbooks/releasing-beta.md +86 -0
- package/docs/playbooks/secret-management.md +64 -0
- package/docs/reference/README.md +199 -0
- package/docs/reference/ast-json-api.md +427 -0
- package/docs/reference/calm-mapping.md +519 -0
- package/docs/reference/cli-commands.md +588 -0
- package/docs/reference/configuration.md +202 -0
- package/docs/reference/error-codes.md +664 -0
- package/docs/reference/generated-artifacts-policy.md +53 -0
- package/docs/reference/grammar-spec.md +255 -0
- package/docs/reference/primitives-api.md +317 -0
- package/docs/reference/protobuf-api.md +426 -0
- package/docs/reference/python-api.md +485 -0
- package/docs/reference/registry.md +50 -0
- package/docs/reference/sea-dsl-ai-cheatsheet.yaml +913 -0
- package/docs/reference/security-model.md +74 -0
- package/docs/reference/typescript-api.md +508 -0
- package/docs/reference/wasm-api.md +420 -0
- package/docs/semantic-pack-review.md +144 -0
- package/docs/semantic-pack-signing.md +234 -0
- package/docs/semantic-packs.md +284 -0
- package/docs/specs/ADR-001-sea-dsl-semantic-source-of-truth.md +33 -0
- package/docs/specs/ADR-002-projection-first-class-construct.md +50 -0
- package/docs/specs/ADR-003-protobuf-projection-target.md +51 -0
- package/docs/specs/ADR-004-projection-compatibility-semantics.md +57 -0
- package/docs/specs/ADR-005-multi-language-support-strategy.md +112 -0
- package/docs/specs/ADR-006-error-handling-strategy.md +115 -0
- package/docs/specs/ADR-007-policy-evaluation-engine.md +95 -0
- package/docs/specs/ADR-008-knowledge-graph-integration.md +90 -0
- package/docs/specs/ADR-009-module-resolution-strategy.md +115 -0
- package/docs/specs/ADR-010-unit-system.md +106 -0
- package/docs/specs/PRD-001-sea-projection-framework.md +155 -0
- package/docs/specs/PRD-002-sea-cli-tooling.md +169 -0
- package/docs/specs/PRD-003-dsl-core-capabilities.md +275 -0
- package/docs/specs/README.md +62 -0
- package/docs/specs/SDS-001-protobuf-projection-engine.md +451 -0
- package/docs/specs/SDS-002-sea-core-architecture.md +268 -0
- package/docs/specs/SDS-003-parser-semantic-graph.md +377 -0
- package/docs/specs/SDS-004-policy-engine-design.md +362 -0
- package/docs/specs/SDS-005-knowledge-graph-module.md +364 -0
- package/docs/specs/SDS-006-calm-integration.md +367 -0
- package/docs/specs/SDS-007-sbvr-import.md +347 -0
- package/docs/templates/template_explanation.md +14 -0
- package/docs/templates/template_howto.md +21 -0
- package/docs/templates/template_playbook.md +21 -0
- package/docs/templates/template_reference.md +17 -0
- package/docs/templates/template_tutorial.md +24 -0
- package/docs/tutorials/README.md +12 -0
- package/docs/tutorials/first-sea-model.md +85 -0
- package/docs/tutorials/getting-started.md +98 -0
- package/docs/tutorials/python-binding-quickstart.md +107 -0
- package/docs/tutorials/typescript-binding-quickstart.md +91 -0
- package/docs/tutorials/wasm-in-browser.md +75 -0
- package/domainforge-core/CHANGELOG.md +138 -0
- package/domainforge-core/Cargo.toml +101 -0
- package/domainforge-core/MIGRATING.md +32 -0
- package/domainforge-core/README.md +197 -0
- package/domainforge-core/benchmark_results.txt +51 -0
- package/domainforge-core/build.rs +6 -0
- package/domainforge-core/deny.toml +31 -0
- package/domainforge-core/docs/specs/projections/sbvr_kg_mapping.md +43 -0
- package/domainforge-core/examples/basic.sea +7 -0
- package/domainforge-core/examples/cli/import_export_workflow.sh +38 -0
- package/domainforge-core/examples/cli/validate_example.sh +30 -0
- package/domainforge-core/examples/evolution_semantics.sea +31 -0
- package/domainforge-core/examples/parser_demo.rs +203 -0
- package/domainforge-core/grammar/sea.pest +408 -0
- package/domainforge-core/schemas/calm-v1.schema.json +170 -0
- package/domainforge-core/schemas/shacl/sea_shapes.ttl +19 -0
- package/domainforge-core/src/authority/compiler.rs +309 -0
- package/domainforge-core/src/authority/environment.rs +203 -0
- package/domainforge-core/src/authority/error.rs +164 -0
- package/domainforge-core/src/authority/fact_resolver.rs +224 -0
- package/domainforge-core/src/authority/mod.rs +25 -0
- package/domainforge-core/src/authority/pack.rs +133 -0
- package/domainforge-core/src/authority/policy.rs +224 -0
- package/domainforge-core/src/authority/resolver.rs +446 -0
- package/domainforge-core/src/authority/trace.rs +217 -0
- package/domainforge-core/src/authority/transform.rs +168 -0
- package/domainforge-core/src/authority/types.rs +617 -0
- package/domainforge-core/src/bin/domainforge.rs +25 -0
- package/domainforge-core/src/calm/export.rs +538 -0
- package/domainforge-core/src/calm/import.rs +1220 -0
- package/domainforge-core/src/calm/mod.rs +9 -0
- package/domainforge-core/src/calm/models.rs +108 -0
- package/domainforge-core/src/calm/sbvr_import.rs +9 -0
- package/domainforge-core/src/cli/authority.rs +149 -0
- package/domainforge-core/src/cli/format.rs +85 -0
- package/domainforge-core/src/cli/import.rs +133 -0
- package/domainforge-core/src/cli/mod.rs +64 -0
- package/domainforge-core/src/cli/normalize.rs +180 -0
- package/domainforge-core/src/cli/pack.rs +904 -0
- package/domainforge-core/src/cli/parse.rs +112 -0
- package/domainforge-core/src/cli/project.rs +294 -0
- package/domainforge-core/src/cli/registry.rs +41 -0
- package/domainforge-core/src/cli/test.rs +12 -0
- package/domainforge-core/src/cli/validate.rs +195 -0
- package/domainforge-core/src/cli/validate_kg.rs +80 -0
- package/domainforge-core/src/concept_id.rs +89 -0
- package/domainforge-core/src/error/diagnostics.rs +426 -0
- package/domainforge-core/src/error/fuzzy.rs +253 -0
- package/domainforge-core/src/error/mod.rs +13 -0
- package/domainforge-core/src/formatter/comments.rs +223 -0
- package/domainforge-core/src/formatter/config.rs +114 -0
- package/domainforge-core/src/formatter/mod.rs +22 -0
- package/domainforge-core/src/formatter/printer.rs +906 -0
- package/domainforge-core/src/graph/mod.rs +858 -0
- package/domainforge-core/src/graph/to_ast.rs +66 -0
- package/domainforge-core/src/kg.rs +1476 -0
- package/domainforge-core/src/kg_import.rs +251 -0
- package/domainforge-core/src/lib.rs +203 -0
- package/domainforge-core/src/module/mod.rs +1 -0
- package/domainforge-core/src/module/resolver.rs +260 -0
- package/domainforge-core/src/parser/ast.rs +2919 -0
- package/domainforge-core/src/parser/ast_convert.rs +494 -0
- package/domainforge-core/src/parser/ast_schema.rs +491 -0
- package/domainforge-core/src/parser/error.rs +291 -0
- package/domainforge-core/src/parser/lint.rs +39 -0
- package/domainforge-core/src/parser/mod.rs +193 -0
- package/domainforge-core/src/parser/printer.rs +702 -0
- package/domainforge-core/src/parser/profiles.rs +71 -0
- package/domainforge-core/src/parser/string_utils.rs +138 -0
- package/domainforge-core/src/patterns.rs +68 -0
- package/domainforge-core/src/policy/core.rs +1148 -0
- package/domainforge-core/src/policy/expression.rs +399 -0
- package/domainforge-core/src/policy/mod.rs +18 -0
- package/domainforge-core/src/policy/normalize.rs +1028 -0
- package/domainforge-core/src/policy/quantifier.rs +940 -0
- package/domainforge-core/src/policy/three_valued.rs +140 -0
- package/domainforge-core/src/policy/three_valued_microbench.rs +104 -0
- package/domainforge-core/src/policy/type_inference.rs +67 -0
- package/domainforge-core/src/policy/violation.rs +36 -0
- package/domainforge-core/src/primitives/concept_change.rs +61 -0
- package/domainforge-core/src/primitives/entity.rs +224 -0
- package/domainforge-core/src/primitives/flow.rs +111 -0
- package/domainforge-core/src/primitives/instance.rs +93 -0
- package/domainforge-core/src/primitives/mapping_contract.rs +50 -0
- package/domainforge-core/src/primitives/metric.rs +79 -0
- package/domainforge-core/src/primitives/mod.rs +25 -0
- package/domainforge-core/src/primitives/projection_contract.rs +50 -0
- package/domainforge-core/src/primitives/quantity.rs +56 -0
- package/domainforge-core/src/primitives/relation.rs +68 -0
- package/domainforge-core/src/primitives/resource.rs +237 -0
- package/domainforge-core/src/primitives/resource_instance.rs +88 -0
- package/domainforge-core/src/primitives/role.rs +49 -0
- package/domainforge-core/src/projection/buf.rs +404 -0
- package/domainforge-core/src/projection/contracts.rs +22 -0
- package/domainforge-core/src/projection/engine.rs +19 -0
- package/domainforge-core/src/projection/mod.rs +16 -0
- package/domainforge-core/src/projection/protobuf.rs +3331 -0
- package/domainforge-core/src/projection/registry.rs +43 -0
- package/domainforge-core/src/python/authority.rs +253 -0
- package/domainforge-core/src/python/error.rs +227 -0
- package/domainforge-core/src/python/formatter.rs +86 -0
- package/domainforge-core/src/python/graph.rs +366 -0
- package/domainforge-core/src/python/mod.rs +9 -0
- package/domainforge-core/src/python/policy.rs +651 -0
- package/domainforge-core/src/python/primitives.rs +796 -0
- package/domainforge-core/src/python/registry.rs +98 -0
- package/domainforge-core/src/python/semantic_pack.rs +619 -0
- package/domainforge-core/src/python/units.rs +96 -0
- package/domainforge-core/src/registry/mod.rs +432 -0
- package/domainforge-core/src/registry/tests.rs +210 -0
- package/domainforge-core/src/sbvr.rs +744 -0
- package/domainforge-core/src/semantic_pack/builder.rs +470 -0
- package/domainforge-core/src/semantic_pack/canonical_json.rs +184 -0
- package/domainforge-core/src/semantic_pack/diagnostics.rs +214 -0
- package/domainforge-core/src/semantic_pack/diff.rs +216 -0
- package/domainforge-core/src/semantic_pack/mod.rs +31 -0
- package/domainforge-core/src/semantic_pack/pack_set.rs +240 -0
- package/domainforge-core/src/semantic_pack/resolver.rs +437 -0
- package/domainforge-core/src/semantic_pack/review.rs +125 -0
- package/domainforge-core/src/semantic_pack/schema.rs +342 -0
- package/domainforge-core/src/semantic_pack/signing.rs +105 -0
- package/domainforge-core/src/semantic_pack/validator.rs +368 -0
- package/domainforge-core/src/semantic_version.rs +140 -0
- package/domainforge-core/src/test_utils.rs +12 -0
- package/domainforge-core/src/typescript/authority.rs +184 -0
- package/domainforge-core/src/typescript/error.rs +146 -0
- package/domainforge-core/src/typescript/formatter.rs +76 -0
- package/domainforge-core/src/typescript/graph.rs +391 -0
- package/domainforge-core/src/typescript/mod.rs +9 -0
- package/domainforge-core/src/typescript/policy.rs +564 -0
- package/domainforge-core/src/typescript/primitives.rs +784 -0
- package/domainforge-core/src/typescript/registry.rs +88 -0
- package/domainforge-core/src/typescript/semantic_pack.rs +470 -0
- package/domainforge-core/src/typescript/units.rs +76 -0
- package/domainforge-core/src/units/mod.rs +462 -0
- package/domainforge-core/src/uuid_module.rs +42 -0
- package/domainforge-core/src/validation_error.rs +818 -0
- package/domainforge-core/src/validation_result.rs +30 -0
- package/domainforge-core/src/wasm/authority.rs +192 -0
- package/domainforge-core/src/wasm/error.rs +145 -0
- package/domainforge-core/src/wasm/formatter.rs +69 -0
- package/domainforge-core/src/wasm/graph.rs +471 -0
- package/domainforge-core/src/wasm/mod.rs +16 -0
- package/domainforge-core/src/wasm/policy.rs +607 -0
- package/domainforge-core/src/wasm/primitives.rs +295 -0
- package/domainforge-core/src/wasm/semantic_pack.rs +471 -0
- package/domainforge-core/src/wasm/units.rs +62 -0
- package/domainforge-core/std/aws.sea +6 -0
- package/domainforge-core/std/core.sea +6 -0
- package/domainforge-core/std/http.sea +27 -0
- package/domainforge-core/tests/aggregation_enhanced_tests.rs +162 -0
- package/domainforge-core/tests/aggregation_eval_tests.rs +248 -0
- package/domainforge-core/tests/aggregation_integration_tests.rs +379 -0
- package/domainforge-core/tests/aggregation_parser_tests.rs +92 -0
- package/domainforge-core/tests/aggregation_tests.rs +102 -0
- package/domainforge-core/tests/authority_conformance_tests.rs +1173 -0
- package/domainforge-core/tests/calm_round_trip_tests.rs +283 -0
- package/domainforge-core/tests/calm_schema_validation_tests.rs +137 -0
- package/domainforge-core/tests/cast_operator_tests.rs +85 -0
- package/domainforge-core/tests/cli_binary_check.rs +37 -0
- package/domainforge-core/tests/cli_import_tests.rs +291 -0
- package/domainforge-core/tests/cli_path_traversal_tests.rs +124 -0
- package/domainforge-core/tests/cli_tests.rs +63 -0
- package/domainforge-core/tests/diagnostics_tests.rs +203 -0
- package/domainforge-core/tests/dimension_unit_tests.rs +80 -0
- package/domainforge-core/tests/entity_tests.rs +69 -0
- package/domainforge-core/tests/evolution_semantics_tests.rs +157 -0
- package/domainforge-core/tests/flow_tests.rs +78 -0
- package/domainforge-core/tests/flow_unit_validation_tests.rs +31 -0
- package/domainforge-core/tests/graph_integration_tests.rs +218 -0
- package/domainforge-core/tests/graph_tests.rs +626 -0
- package/domainforge-core/tests/import_parsing_tests.rs +23 -0
- package/domainforge-core/tests/instance_integration_tests.rs +98 -0
- package/domainforge-core/tests/instance_parsing_tests.rs +58 -0
- package/domainforge-core/tests/instance_tests.rs +61 -0
- package/domainforge-core/tests/kg_uri_encoding_tests.rs +53 -0
- package/domainforge-core/tests/lint_tests.rs +19 -0
- package/domainforge-core/tests/metric_tests.rs +143 -0
- package/domainforge-core/tests/module_resolution_tests.rs +100 -0
- package/domainforge-core/tests/namespace_registry_tests.rs +247 -0
- package/domainforge-core/tests/null_handling_tests.rs +26 -0
- package/domainforge-core/tests/parser_ast_v3.rs +53 -0
- package/domainforge-core/tests/parser_dimension_registry_tests.rs +20 -0
- package/domainforge-core/tests/parser_integration_tests.rs +294 -0
- package/domainforge-core/tests/parser_metadata_tests.rs +97 -0
- package/domainforge-core/tests/parser_resource_domain_only_graph_test.rs +21 -0
- package/domainforge-core/tests/parser_resource_limits_tests.rs +122 -0
- package/domainforge-core/tests/parser_tests.rs +512 -0
- package/domainforge-core/tests/pattern_semantics_tests.rs +87 -0
- package/domainforge-core/tests/phase_14_determinism_tests.rs +166 -0
- package/domainforge-core/tests/phase_15_validation_error_tests.rs +136 -0
- package/domainforge-core/tests/phase_16_unicode_tests.rs +248 -0
- package/domainforge-core/tests/phase_17_export_tests.rs +285 -0
- package/domainforge-core/tests/phase_17_round_trip_tests.rs +264 -0
- package/domainforge-core/tests/policy_tests.rs +635 -0
- package/domainforge-core/tests/primitives_integration_tests.rs +151 -0
- package/domainforge-core/tests/print_rdf_xml.rs +14 -0
- package/domainforge-core/tests/printer_tests.rs +204 -0
- package/domainforge-core/tests/profile_tests.rs +35 -0
- package/domainforge-core/tests/projection_contracts_tests.rs +154 -0
- package/domainforge-core/tests/protobuf_projection_tests.rs +199 -0
- package/domainforge-core/tests/quantity_tests.rs +41 -0
- package/domainforge-core/tests/rdf_xml_typed_literal_tests.rs +105 -0
- package/domainforge-core/tests/registry_schema_tests.rs +33 -0
- package/domainforge-core/tests/resource_tests.rs +50 -0
- package/domainforge-core/tests/resource_unit_tests.rs +24 -0
- package/domainforge-core/tests/roles_relations_tests.rs +61 -0
- package/domainforge-core/tests/round_trip_tests.rs +34 -0
- package/domainforge-core/tests/runtime_toggle_tests.rs +70 -0
- package/domainforge-core/tests/sbvr_fact_schema_tests.rs +60 -0
- package/domainforge-core/tests/sbvr_flow_facts_tests.rs +55 -0
- package/domainforge-core/tests/sbvr_parsing_tests.rs +53 -0
- package/domainforge-core/tests/semantic_pack_alias_resolution.rs +197 -0
- package/domainforge-core/tests/semantic_pack_build.rs +302 -0
- package/domainforge-core/tests/semantic_pack_consumer_smoke.rs +150 -0
- package/domainforge-core/tests/semantic_pack_pack_set.rs +160 -0
- package/domainforge-core/tests/semantic_pack_signing.rs +157 -0
- package/domainforge-core/tests/semantic_pack_three_valued.rs +250 -0
- package/domainforge-core/tests/semantic_pack_validate.rs +196 -0
- package/domainforge-core/tests/std_lib_tests.rs +37 -0
- package/domainforge-core/tests/temporal_evaluation_tests.rs +159 -0
- package/domainforge-core/tests/temporal_semantics_tests.rs +214 -0
- package/domainforge-core/tests/three_valued_quantifiers_tests.rs +164 -0
- package/domainforge-core/tests/turtle_entity_export_tests.rs +38 -0
- package/domainforge-core/tests/turtle_escaping_tests.rs +53 -0
- package/domainforge-core/tests/turtle_resource_export_tests.rs +34 -0
- package/domainforge-core/tests/type_inference_tests.rs +40 -0
- package/domainforge-core/tests/unicode_validation_tests.rs +169 -0
- package/domainforge-core/tests/unit_tests.rs +81 -0
- package/domainforge-core/tests/validate_tests.rs +38 -0
- package/domainforge-core/tests/validation_unit_mismatch_tests.rs +83 -0
- package/domainforge-core/tests/wasm_tests.rs +229 -0
- package/domainforge-python/CHANGELOG-python.md +12 -0
- package/domainforge-python/MIGRATING.md +24 -0
- package/domainforge-python/README.md +256 -0
- package/domainforge-python/domainforge/__init__.py +95 -0
- package/domainforge-python/domainforge/domainforge.pyi +519 -0
- package/domainforge-python/pyproject.toml +36 -0
- package/domainforge-typescript/CHANGELOG-typescript.md +12 -0
- package/domainforge-typescript/LICENSE +201 -0
- package/domainforge-typescript/MIGRATING.md +24 -0
- package/domainforge-typescript/README.md +305 -0
- package/domainforge-typescript/index.d.ts +452 -0
- package/domainforge-typescript/index.js +361 -0
- package/domainforge-typescript/package.json +60 -0
- package/example.js +61 -0
- package/examples/browser.html +366 -0
- package/examples/namespaces/finance/cashflow.sea +5 -0
- package/examples/namespaces/logistics/core.sea +7 -0
- package/examples/observability_metrics.sea +38 -0
- package/fixtures/semantic_packs/acme_procurement/domain/entities.sea +39 -0
- package/fixtures/semantic_packs/acme_procurement/domain/metrics.sea +11 -0
- package/fixtures/semantic_packs/acme_procurement/domain/relations.sea +7 -0
- package/fixtures/semantic_packs/acme_procurement/domain/resources.sea +9 -0
- package/fixtures/semantic_packs/acme_procurement/review/acme.procurement.semantic-review.jsonl +7 -0
- package/fixtures/semantic_packs/acme_procurement/tests/ambiguous_vendor_alias.sea +8 -0
- package/fixtures/semantic_packs/acme_procurement/tests/deprecated_vendor_alias.sea +8 -0
- package/fixtures/semantic_packs/acme_procurement/tests/invalid_relation.sea +3 -0
- package/fixtures/semantic_packs/acme_procurement/tests/proposed_concept.sea +8 -0
- package/fixtures/semantic_packs/acme_procurement/tests/rejected_concept.sea +8 -0
- package/fixtures/semantic_packs/acme_procurement/tests/unit_mismatch.sea +7 -0
- package/fixtures/semantic_packs/acme_procurement/tests/unknown_vendor_policy.sea +8 -0
- package/fixtures/semantic_packs/acme_procurement/tests/valid_purchase_policy.sea +8 -0
- package/index.d.ts +2 -0
- package/index.js +8 -0
- package/justfile +200 -0
- package/lefthook.yml +13 -0
- package/lib/validate_native_exports.d.ts +4 -0
- package/lib/validate_native_exports.js +12 -0
- package/package.json +22 -0
- package/pytest.ini +5 -0
- package/python/tests/test_registry.py +75 -0
- package/python/tests/test_units.py +18 -0
- package/release-please-config.json +49 -0
- package/requirements-dev.txt +3 -0
- package/requirements.txt +3 -0
- package/rust-toolchain.toml +3 -0
- package/schemas/ast-v1.schema.json +72 -0
- package/schemas/ast-v2.schema.json +1200 -0
- package/schemas/ast-v3.schema.json +1200 -0
- package/schemas/sea-registry.schema.json +45 -0
- package/scripts/build-python.sh +37 -0
- package/scripts/build-release.sh +279 -0
- package/scripts/build-typescript.sh +13 -0
- package/scripts/build-wasm.sh +113 -0
- package/scripts/bump-version.sh +245 -0
- package/scripts/check_unused_test_imports.py +85 -0
- package/scripts/ci_tasks.py +379 -0
- package/scripts/clear_debug_test.sh +10 -0
- package/scripts/create-github-release.sh +262 -0
- package/scripts/create-tag.sh +203 -0
- package/scripts/find_and_link_test_binary.sh +70 -0
- package/scripts/generate-changelog.sh +271 -0
- package/scripts/generate-release-notes.sh +205 -0
- package/scripts/lint_release_security.py +96 -0
- package/scripts/lint_release_workflows.py +82 -0
- package/scripts/lint_workflow_gates.py +113 -0
- package/scripts/optimized-wasm-build.sh +61 -0
- package/scripts/patch_napi_types.py +62 -0
- package/scripts/pre-release-check.sh +289 -0
- package/scripts/prepare_rust_debug.sh +52 -0
- package/scripts/release.sh +373 -0
- package/scripts/resolve_rust_binary.py +230 -0
- package/scripts/run_commitlint.sh +29 -0
- package/scripts/test-all.sh +77 -0
- package/scripts/update_launch_program.py +93 -0
- package/secrets/README.md +27 -0
- package/secrets/secrets.yaml +21 -0
- package/test_integration.py +67 -0
- package/tests/test_authority.py +328 -0
- package/tests/test_ci_tasks.py +143 -0
- package/tests/test_expression.py +256 -0
- package/tests/test_golden_payment_flow.py +42 -0
- package/tests/test_graph.py +127 -0
- package/tests/test_instance.py +136 -0
- package/tests/test_parser.py +82 -0
- package/tests/test_primitives.py +68 -0
- package/tests/test_role_relation_parity.py +56 -0
- package/tests/test_runtime_toggle.py +156 -0
- package/tests/test_semantic_pack.py +639 -0
- package/tests/test_three_valued_eval.py +159 -0
- package/tsconfig.json +30 -0
- package/typescript-tests/advanced.test.ts +165 -0
- package/typescript-tests/authority.test.ts +216 -0
- package/typescript-tests/expression.test.ts +228 -0
- package/typescript-tests/golden-payment-flow.test.ts +51 -0
- package/typescript-tests/graph.test.ts +142 -0
- package/typescript-tests/native-binding.test.ts +20 -0
- package/typescript-tests/primitives.test.ts +88 -0
- package/typescript-tests/registry.test.ts +122 -0
- package/typescript-tests/role_relation.test.ts +63 -0
- package/typescript-tests/runtime_toggle.test.ts +141 -0
- package/typescript-tests/semantic-pack.test.ts +556 -0
- package/typescript-tests/three_valued_eval.test.ts +135 -0
- package/typescript-tests/units.test.ts +36 -0
- package/vitest.config.ts +13 -0
- 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
|
+
}
|