expressir 2.4.29-arm-linux-musl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (426) hide show
  1. checksums.yaml +7 -0
  2. data/.cargo/config.toml +3 -0
  3. data/.github/scripts/test_installed_gem.rb +34 -0
  4. data/.github/workflows/codeql.yml +35 -0
  5. data/.github/workflows/docs.yml +99 -0
  6. data/.github/workflows/links.yml +100 -0
  7. data/.github/workflows/native-gems.yml +201 -0
  8. data/.github/workflows/rake.yml +19 -0
  9. data/.github/workflows/release.yml +37 -0
  10. data/.github/workflows/rust-ext.yml +41 -0
  11. data/.github/workflows/stress.yml +36 -0
  12. data/.github/workflows/validate_schemas.yml +48 -0
  13. data/.github/workflows/verify_remarks.yml +33 -0
  14. data/.gitignore +55 -0
  15. data/.hound.yml +3 -0
  16. data/.rspec +2 -0
  17. data/.rubocop.yml +18 -0
  18. data/.rubocop_todo.yml +297 -0
  19. data/CHANGELOG.md +159 -0
  20. data/Gemfile +18 -0
  21. data/README.adoc +2783 -0
  22. data/Rakefile +41 -0
  23. data/TODO.bugs/01-stale-transformer-autoload.md +39 -0
  24. data/TODO.bugs/02-parser-class-instance-vars.md +36 -0
  25. data/TODO.bugs/03-builder-mutable-state.md +43 -0
  26. data/TODO.bugs/04-formatter-public-send-dispatch.md +53 -0
  27. data/TODO.bugs/05-anonymous-formatter-subclass.md +45 -0
  28. data/TODO.bugs/06-collection-registry-single-source.md +53 -0
  29. data/TODO.bugs/07-require-relative-cleanup.md +42 -0
  30. data/TODO.bugs/08-require-expressir-in-commands.md +34 -0
  31. data/TODO.bugs/09-parser-split.md +53 -0
  32. data/TODO.bugs/10-to-s-override.md +42 -0
  33. data/TODO.bugs/11-parser-class-variables.md +39 -0
  34. data/TODO.bugs/12-marker-modules-vs-registry.md +64 -0
  35. data/TODO.bugs/13-string-literal-scanner-limitation.md +52 -0
  36. data/TODO.bugs/14-model-formatting-leak.md +30 -0
  37. data/TODO.bugs/15-expression-children-macro.md +27 -0
  38. data/TODO.bugs/16-pretty-formatter-duplication.md +28 -0
  39. data/TODO.bugs/17-snake-case-cache-mutable-constant.md +28 -0
  40. data/TODO.bugs/18-const-get-private-constants.md +30 -0
  41. data/TODO.bugs/19-format-methods-public.md +22 -0
  42. data/TODO.bugs/20-coverage-nested-entities-dedup.md +20 -0
  43. data/TODO.bugs/21-operator-tokens-secondary-dispatch.md +21 -0
  44. data/TODO.bugs/22-builder-fast-path-wrappers.md +32 -0
  45. data/TODO.bugs/23-coverage-inverse-maps.md +21 -0
  46. data/TODO.bugs/24-streaming-builder-complexity.md +19 -0
  47. data/TODO.bugs/25-debug-puts-in-production.md +21 -0
  48. data/TODO.bugs/26-generic-entity-children-misplaced.md +21 -0
  49. data/TODO.bugs/27-package-build-god-method.md +19 -0
  50. data/TODO.bugs/28-package-god-class.md +30 -0
  51. data/TODO.bugs/29-validate-ascii-god-class.md +24 -0
  52. data/TODO.bugs/30-unicode-map-extraction.md +19 -0
  53. data/TODO.bugs/README.md +43 -0
  54. data/TODO.max-perf/01-restore-ci-green.md +36 -0
  55. data/TODO.max-perf/02-streaming-parse-path.md +31 -0
  56. data/TODO.max-perf/03-cli-parallel-opt-in.md +27 -0
  57. data/TODO.max-perf/04-benchmark-harness.md +28 -0
  58. data/TODO.max-perf/05-parallel-fidelity-specs.md +22 -0
  59. data/TODO.max-perf/06-builder-cpu-audit.md +41 -0
  60. data/TODO.max-perf/07-upstream-parsanol-roadmap.md +27 -0
  61. data/TODO.max-perf/08-builder-build-perf.md +45 -0
  62. data/TODO.max-perf/09-grammar-cold-start.md +25 -0
  63. data/TODO.max-perf/10-parser-facade-hygiene.md +23 -0
  64. data/TODO.max-perf/11-ci-green-closeout.md +25 -0
  65. data/TODO.max-perf/12-require-boot-profile.md +25 -0
  66. data/TODO.max-perf/13-key-conversion-specs.md +26 -0
  67. data/TODO.max-perf/14-builder-call-handler-audit.md +28 -0
  68. data/TODO.parity-ee/00-overview.md +76 -0
  69. data/TODO.parity-ee/01-annex-g-rule-extraction.md +41 -0
  70. data/TODO.parity-ee/02-eeng-algorithm-inventory.md +8 -0
  71. data/TODO.parity-ee/03-eeng-oracle-harness.md +33 -0
  72. data/TODO.parity-ee/04-shtolo-concatenate.md +44 -0
  73. data/TODO.parity-ee/05-shtolo-longform-flatten.md +53 -0
  74. data/TODO.parity-ee/06-interface-scheduling-parity.md +47 -0
  75. data/TODO.parity-ee/07-semantic-checks-port.md +35 -0
  76. data/TODO.parity-ee/08-pretty-roundtrip-gate.md +27 -0
  77. data/TODO.parity-ee/09-smrl-index-and-listing.md +22 -0
  78. data/TODO.parity-ee/10-interface-dot-graph.md +21 -0
  79. data/TODO.parity-ee/11-import-eeng-tests.md +79 -0
  80. data/TODO.parity-ee/12-full-check-catalog.md +67 -0
  81. data/TODO.parity-ee/13-population-p21.md +54 -0
  82. data/TODO.parity-ee/14-shtolo-completion.md +44 -0
  83. data/TODO.parity-ee/15-part28-xml.md +51 -0
  84. data/TODO.parity-ee/16-compare-patch.md +48 -0
  85. data/TODO.parity-ee/17-official-tests.md +42 -0
  86. data/TODO.parity-ee/18-mim-mapping.md +47 -0
  87. data/TODO.parity-ee/19-architecture.md +97 -0
  88. data/TODO.parity-ee/parity-matrix.md +108 -0
  89. data/TODO.refpath-v2/00-overview.md +64 -0
  90. data/TODO.suma-improvements/00-overview.md +23 -0
  91. data/TODO.suma-improvements/01-prebuilt-platform-gems.md +33 -0
  92. data/TODO.suma-improvements/02-suma-e2e.md +39 -0
  93. data/TODO.suma-improvements/03-item-graph.md +22 -0
  94. data/TODO.suma-improvements/04-compiled-set-default.md +16 -0
  95. data/TODO.suma-improvements/05-validation-in-docs.md +17 -0
  96. data/TODO.suma-improvements/06-memory-remeasure.md +36 -0
  97. data/TODO.suma-improvements/07-part28-xml.md +15 -0
  98. data/TODO.suma-improvements/08-windows-gnu-toolchain.md +15 -0
  99. data/TODO.suma-improvements/09-checker-tranche3.md +13 -0
  100. data/TODO.suma-improvements/10-expressdoc.md +8 -0
  101. data/benchmark/srl_benchmark.rb +458 -0
  102. data/benchmark/srl_native_benchmark.rb +146 -0
  103. data/benchmark/srl_ruby_benchmark.rb +132 -0
  104. data/bin/console +11 -0
  105. data/bin/rspec +30 -0
  106. data/bin/setup +8 -0
  107. data/docs/Gemfile +12 -0
  108. data/docs/_config.yml +141 -0
  109. data/docs/_guides/changes/changes-format.adoc +778 -0
  110. data/docs/_guides/changes/importing-eengine.adoc +898 -0
  111. data/docs/_guides/changes/index.adoc +396 -0
  112. data/docs/_guides/changes/programmatic-usage.adoc +1038 -0
  113. data/docs/_guides/changes/validating-changes.adoc +681 -0
  114. data/docs/_guides/cli/benchmark-performance.adoc +834 -0
  115. data/docs/_guides/cli/coverage-analysis.adoc +921 -0
  116. data/docs/_guides/cli/format-schemas.adoc +547 -0
  117. data/docs/_guides/cli/index.adoc +8 -0
  118. data/docs/_guides/cli/managing-changes.adoc +927 -0
  119. data/docs/_guides/cli/validate-ascii.adoc +645 -0
  120. data/docs/_guides/cli/validate-schemas.adoc +534 -0
  121. data/docs/_guides/formatter/formatter-architecture.adoc +401 -0
  122. data/docs/_guides/index.adoc +165 -0
  123. data/docs/_guides/ler/creating-packages.adoc +664 -0
  124. data/docs/_guides/ler/index.adoc +305 -0
  125. data/docs/_guides/ler/loading-packages.adoc +707 -0
  126. data/docs/_guides/ler/package-formats.adoc +748 -0
  127. data/docs/_guides/ler/querying-packages.adoc +826 -0
  128. data/docs/_guides/ler/step-packages.adoc +385 -0
  129. data/docs/_guides/ler/validating-packages.adoc +750 -0
  130. data/docs/_guides/liquid/basic-templates.adoc +813 -0
  131. data/docs/_guides/liquid/documentation-generation.adoc +1042 -0
  132. data/docs/_guides/liquid/drops-reference.adoc +829 -0
  133. data/docs/_guides/liquid/filters-and-tags.adoc +912 -0
  134. data/docs/_guides/liquid/index.adoc +468 -0
  135. data/docs/_guides/manifests/creating-manifests.adoc +483 -0
  136. data/docs/_guides/manifests/index.adoc +307 -0
  137. data/docs/_guides/manifests/resolving-manifests.adoc +557 -0
  138. data/docs/_guides/manifests/validating-manifests.adoc +713 -0
  139. data/docs/_guides/ruby-api/formatting-schemas.adoc +605 -0
  140. data/docs/_guides/ruby-api/index.adoc +257 -0
  141. data/docs/_guides/ruby-api/parsing-files.adoc +421 -0
  142. data/docs/_guides/ruby-api/search-engine.adoc +609 -0
  143. data/docs/_guides/ruby-api/working-with-repository.adoc +577 -0
  144. data/docs/_pages/data-model.adoc +665 -0
  145. data/docs/_pages/express-language.adoc +506 -0
  146. data/docs/_pages/getting-started.adoc +414 -0
  147. data/docs/_pages/index.adoc +116 -0
  148. data/docs/_pages/introduction.adoc +256 -0
  149. data/docs/_pages/ler-packages.adoc +837 -0
  150. data/docs/_pages/parsers.adoc +709 -0
  151. data/docs/_pages/schema-manifests.adoc +431 -0
  152. data/docs/_references/index.adoc +228 -0
  153. data/docs/_tutorials/creating-ler-package.adoc +735 -0
  154. data/docs/_tutorials/documentation-coverage.adoc +795 -0
  155. data/docs/_tutorials/formatting-schemas.adoc +89 -0
  156. data/docs/_tutorials/index.adoc +231 -0
  157. data/docs/_tutorials/liquid-templates.adoc +806 -0
  158. data/docs/_tutorials/parsing-your-first-schema.adoc +522 -0
  159. data/docs/_tutorials/querying-schemas.adoc +751 -0
  160. data/docs/_tutorials/working-with-multiple-schemas.adoc +676 -0
  161. data/docs/index.adoc +242 -0
  162. data/docs/lychee.toml +90 -0
  163. data/examples/demo_ler_usage.sh +86 -0
  164. data/examples/ler/README.md +111 -0
  165. data/examples/ler/simple_example.ler +0 -0
  166. data/examples/ler/simple_schema.exp +33 -0
  167. data/examples/ler_build.rb +75 -0
  168. data/examples/ler_cli.rb +79 -0
  169. data/examples/ler_demo_complete.rb +276 -0
  170. data/examples/ler_query.rb +91 -0
  171. data/examples/ler_query_examples.rb +305 -0
  172. data/examples/ler_stats.rb +81 -0
  173. data/examples/phase3_demo.rb +159 -0
  174. data/examples/query_demo_simple.rb +131 -0
  175. data/exe/expressir +11 -0
  176. data/exe/expressir-format +14 -0
  177. data/exe/expressir-format-test +82 -0
  178. data/expressir.gemspec +61 -0
  179. data/lib/expressir/3.2/expressir_core.so +0 -0
  180. data/lib/expressir/3.3/expressir_core.so +0 -0
  181. data/lib/expressir/3.4/expressir_core.so +0 -0
  182. data/lib/expressir/4.0/expressir_core.so +0 -0
  183. data/lib/expressir/benchmark.rb +310 -0
  184. data/lib/expressir/changes/item_change.rb +20 -0
  185. data/lib/expressir/changes/mapping_change.rb +16 -0
  186. data/lib/expressir/changes/schema_change.rb +85 -0
  187. data/lib/expressir/changes/version_change.rb +26 -0
  188. data/lib/expressir/changes.rb +9 -0
  189. data/lib/expressir/cli.rb +148 -0
  190. data/lib/expressir/commands/base.rb +25 -0
  191. data/lib/expressir/commands/benchmark.rb +59 -0
  192. data/lib/expressir/commands/benchmark_cache.rb +80 -0
  193. data/lib/expressir/commands/changes.rb +34 -0
  194. data/lib/expressir/commands/changes_import_eengine.rb +146 -0
  195. data/lib/expressir/commands/changes_validate.rb +63 -0
  196. data/lib/expressir/commands/check.rb +56 -0
  197. data/lib/expressir/commands/clean.rb +20 -0
  198. data/lib/expressir/commands/coverage.rb +458 -0
  199. data/lib/expressir/commands/expand.rb +26 -0
  200. data/lib/expressir/commands/file_violations.rb +70 -0
  201. data/lib/expressir/commands/fix.rb +33 -0
  202. data/lib/expressir/commands/flatten.rb +29 -0
  203. data/lib/expressir/commands/format.rb +43 -0
  204. data/lib/expressir/commands/manifest.rb +420 -0
  205. data/lib/expressir/commands/mapping_validate.rb +68 -0
  206. data/lib/expressir/commands/non_ascii_character.rb +49 -0
  207. data/lib/expressir/commands/non_ascii_violation_collection.rb +301 -0
  208. data/lib/expressir/commands/package.rb +1222 -0
  209. data/lib/expressir/commands/parity_inputs.rb +160 -0
  210. data/lib/expressir/commands/validate.rb +106 -0
  211. data/lib/expressir/commands/validate_ascii.rb +94 -0
  212. data/lib/expressir/commands/validate_load.rb +91 -0
  213. data/lib/expressir/commands/version.rb +9 -0
  214. data/lib/expressir/commands/xsd.rb +23 -0
  215. data/lib/expressir/commands.rb +30 -0
  216. data/lib/expressir/config.rb +60 -0
  217. data/lib/expressir/coverage.rb +510 -0
  218. data/lib/expressir/eengine/arm_compare_report.rb +23 -0
  219. data/lib/expressir/eengine/changes_section.rb +16 -0
  220. data/lib/expressir/eengine/compare_report.rb +69 -0
  221. data/lib/expressir/eengine/mim_compare_report.rb +23 -0
  222. data/lib/expressir/eengine/modified_object.rb +21 -0
  223. data/lib/expressir/eengine.rb +9 -0
  224. data/lib/expressir/errors.rb +113 -0
  225. data/lib/expressir/express/adoc_hyperlink_formatter.rb +30 -0
  226. data/lib/expressir/express/adoc_source_formatter.rb +12 -0
  227. data/lib/expressir/express/ast_key_converter.rb +114 -0
  228. data/lib/expressir/express/builder.rb +277 -0
  229. data/lib/expressir/express/builder_context.rb +22 -0
  230. data/lib/expressir/express/builder_registry.rb +397 -0
  231. data/lib/expressir/express/builders/attribute_decl_builder.rb +32 -0
  232. data/lib/expressir/express/builders/built_in_builder.rb +75 -0
  233. data/lib/expressir/express/builders/constant_builder.rb +108 -0
  234. data/lib/expressir/express/builders/declaration_builder.rb +20 -0
  235. data/lib/expressir/express/builders/derive_clause_builder.rb +14 -0
  236. data/lib/expressir/express/builders/derived_attr_builder.rb +28 -0
  237. data/lib/expressir/express/builders/domain_rule_builder.rb +21 -0
  238. data/lib/expressir/express/builders/entity_decl_builder.rb +116 -0
  239. data/lib/expressir/express/builders/explicit_attr_builder.rb +49 -0
  240. data/lib/expressir/express/builders/expression_builder.rb +416 -0
  241. data/lib/expressir/express/builders/function_decl_builder.rb +86 -0
  242. data/lib/expressir/express/builders/helpers.rb +148 -0
  243. data/lib/expressir/express/builders/interface_builder.rb +151 -0
  244. data/lib/expressir/express/builders/inverse_attr_builder.rb +43 -0
  245. data/lib/expressir/express/builders/inverse_attr_type_builder.rb +30 -0
  246. data/lib/expressir/express/builders/inverse_clause_builder.rb +14 -0
  247. data/lib/expressir/express/builders/literal_builder.rb +93 -0
  248. data/lib/expressir/express/builders/procedure_decl_builder.rb +82 -0
  249. data/lib/expressir/express/builders/qualifier_builder.rb +102 -0
  250. data/lib/expressir/express/builders/reference_builder.rb +18 -0
  251. data/lib/expressir/express/builders/rule_decl_builder.rb +97 -0
  252. data/lib/expressir/express/builders/schema_body_decl_builder.rb +18 -0
  253. data/lib/expressir/express/builders/schema_decl_builder.rb +56 -0
  254. data/lib/expressir/express/builders/schema_version_builder.rb +34 -0
  255. data/lib/expressir/express/builders/simple_id_builder.rb +17 -0
  256. data/lib/expressir/express/builders/statement_builder.rb +243 -0
  257. data/lib/expressir/express/builders/subtype_constraint_builder.rb +166 -0
  258. data/lib/expressir/express/builders/syntax_builder.rb +30 -0
  259. data/lib/expressir/express/builders/type_builder.rb +238 -0
  260. data/lib/expressir/express/builders/type_decl_builder.rb +26 -0
  261. data/lib/expressir/express/builders/unique_clause_builder.rb +20 -0
  262. data/lib/expressir/express/builders/unique_rule_builder.rb +41 -0
  263. data/lib/expressir/express/builders/where_clause_builder.rb +20 -0
  264. data/lib/expressir/express/builders.rb +55 -0
  265. data/lib/expressir/express/cache.rb +76 -0
  266. data/lib/expressir/express/checker.rb +812 -0
  267. data/lib/expressir/express/concatenator.rb +79 -0
  268. data/lib/expressir/express/core.rb +95 -0
  269. data/lib/expressir/express/error.rb +124 -0
  270. data/lib/expressir/express/formatter.rb +149 -0
  271. data/lib/expressir/express/formatters/data_types_formatter.rb +362 -0
  272. data/lib/expressir/express/formatters/declarations_formatter.rb +753 -0
  273. data/lib/expressir/express/formatters/expressions_formatter.rb +180 -0
  274. data/lib/expressir/express/formatters/literals_formatter.rb +55 -0
  275. data/lib/expressir/express/formatters/references_formatter.rb +53 -0
  276. data/lib/expressir/express/formatters/remark_formatter.rb +270 -0
  277. data/lib/expressir/express/formatters/remark_item_formatter.rb +28 -0
  278. data/lib/expressir/express/formatters/statements_formatter.rb +272 -0
  279. data/lib/expressir/express/formatters/supertype_expressions_formatter.rb +54 -0
  280. data/lib/expressir/express/formatters.rb +22 -0
  281. data/lib/expressir/express/grammar/parser.rb +712 -0
  282. data/lib/expressir/express/grammar.rb +11 -0
  283. data/lib/expressir/express/hyperlink_formatter.rb +35 -0
  284. data/lib/expressir/express/interface_dot.rb +105 -0
  285. data/lib/expressir/express/lazy_repository.rb +211 -0
  286. data/lib/expressir/express/line_map.rb +48 -0
  287. data/lib/expressir/express/listing.rb +174 -0
  288. data/lib/expressir/express/model_traversal.rb +42 -0
  289. data/lib/expressir/express/model_visitor.rb +25 -0
  290. data/lib/expressir/express/node_position_index.rb +342 -0
  291. data/lib/expressir/express/parallel_files.rb +229 -0
  292. data/lib/expressir/express/parser.rb +523 -0
  293. data/lib/expressir/express/pretty_formatter.rb +586 -0
  294. data/lib/expressir/express/pretty_gate.rb +61 -0
  295. data/lib/expressir/express/refs_overlay.rb +47 -0
  296. data/lib/expressir/express/remark_attacher.rb +1098 -0
  297. data/lib/expressir/express/remark_overlay.rb +86 -0
  298. data/lib/expressir/express/remark_scanner.rb +245 -0
  299. data/lib/expressir/express/resolve_references_model_visitor.rb +29 -0
  300. data/lib/expressir/express/schema_block_scanner.rb +137 -0
  301. data/lib/expressir/express/schema_head_formatter.rb +22 -0
  302. data/lib/expressir/express/schema_plain_source_formatter.rb +11 -0
  303. data/lib/expressir/express/schema_source_formatter.rb +15 -0
  304. data/lib/expressir/express/scope_resolver.rb +223 -0
  305. data/lib/expressir/express/self_schema_reference.rb +126 -0
  306. data/lib/expressir/express/shtolo.rb +679 -0
  307. data/lib/expressir/express/smrl_xml.rb +125 -0
  308. data/lib/expressir/express/source_formatter.rb +15 -0
  309. data/lib/expressir/express/streaming_builder.rb +435 -0
  310. data/lib/expressir/express/xsd.rb +259 -0
  311. data/lib/expressir/express.rb +53 -0
  312. data/lib/expressir/liquid.rb +1 -0
  313. data/lib/expressir/manifest/resolver.rb +210 -0
  314. data/lib/expressir/manifest/validator.rb +192 -0
  315. data/lib/expressir/manifest.rb +6 -0
  316. data/lib/expressir/mapping/refpath.rb +857 -0
  317. data/lib/expressir/mapping.rb +253 -0
  318. data/lib/expressir/model/cache.rb +18 -0
  319. data/lib/expressir/model/concerns.rb +42 -0
  320. data/lib/expressir/model/data_types/aggregate.rb +26 -0
  321. data/lib/expressir/model/data_types/array.rb +25 -0
  322. data/lib/expressir/model/data_types/bag.rb +21 -0
  323. data/lib/expressir/model/data_types/binary.rb +19 -0
  324. data/lib/expressir/model/data_types/boolean.rb +15 -0
  325. data/lib/expressir/model/data_types/enumeration.rb +21 -0
  326. data/lib/expressir/model/data_types/enumeration_item.rb +24 -0
  327. data/lib/expressir/model/data_types/generic.rb +24 -0
  328. data/lib/expressir/model/data_types/generic_entity.rb +24 -0
  329. data/lib/expressir/model/data_types/integer.rb +15 -0
  330. data/lib/expressir/model/data_types/list.rb +23 -0
  331. data/lib/expressir/model/data_types/logical.rb +15 -0
  332. data/lib/expressir/model/data_types/number.rb +15 -0
  333. data/lib/expressir/model/data_types/real.rb +17 -0
  334. data/lib/expressir/model/data_types/select.rb +23 -0
  335. data/lib/expressir/model/data_types/set.rb +21 -0
  336. data/lib/expressir/model/data_types/string.rb +19 -0
  337. data/lib/expressir/model/data_types.rb +25 -0
  338. data/lib/expressir/model/declarations/attribute.rb +38 -0
  339. data/lib/expressir/model/declarations/constant.rb +28 -0
  340. data/lib/expressir/model/declarations/derived_attribute.rb +25 -0
  341. data/lib/expressir/model/declarations/entity.rb +62 -0
  342. data/lib/expressir/model/declarations/function.rb +59 -0
  343. data/lib/expressir/model/declarations/informal_proposition_rule.rb +25 -0
  344. data/lib/expressir/model/declarations/interface.rb +37 -0
  345. data/lib/expressir/model/declarations/interface_item.rb +21 -0
  346. data/lib/expressir/model/declarations/interfaced_item.rb +33 -0
  347. data/lib/expressir/model/declarations/inverse_attribute.rb +25 -0
  348. data/lib/expressir/model/declarations/parameter.rb +28 -0
  349. data/lib/expressir/model/declarations/procedure.rb +57 -0
  350. data/lib/expressir/model/declarations/remark_item.rb +21 -0
  351. data/lib/expressir/model/declarations/rule.rb +66 -0
  352. data/lib/expressir/model/declarations/schema.rb +215 -0
  353. data/lib/expressir/model/declarations/schema_version.rb +19 -0
  354. data/lib/expressir/model/declarations/schema_version_item.rb +19 -0
  355. data/lib/expressir/model/declarations/subtype_constraint.rb +32 -0
  356. data/lib/expressir/model/declarations/type.rb +45 -0
  357. data/lib/expressir/model/declarations/unique_rule.rb +26 -0
  358. data/lib/expressir/model/declarations/variable.rb +28 -0
  359. data/lib/expressir/model/declarations/where_rule.rb +26 -0
  360. data/lib/expressir/model/declarations.rb +31 -0
  361. data/lib/expressir/model/dependency_resolver.rb +268 -0
  362. data/lib/expressir/model/exp_file.rb +42 -0
  363. data/lib/expressir/model/expressions/aggregate_initializer.rb +18 -0
  364. data/lib/expressir/model/expressions/aggregate_initializer_item.rb +20 -0
  365. data/lib/expressir/model/expressions/binary_expression.rb +59 -0
  366. data/lib/expressir/model/expressions/entity_constructor.rb +20 -0
  367. data/lib/expressir/model/expressions/function_call.rb +20 -0
  368. data/lib/expressir/model/expressions/interval.rb +29 -0
  369. data/lib/expressir/model/expressions/query_expression.rb +31 -0
  370. data/lib/expressir/model/expressions/unary_expression.rb +25 -0
  371. data/lib/expressir/model/expressions.rb +18 -0
  372. data/lib/expressir/model/identifier.rb +26 -0
  373. data/lib/expressir/model/indexes/entity_index.rb +103 -0
  374. data/lib/expressir/model/indexes/item_graph.rb +185 -0
  375. data/lib/expressir/model/indexes/reference_index.rb +148 -0
  376. data/lib/expressir/model/indexes/type_index.rb +149 -0
  377. data/lib/expressir/model/indexes.rb +12 -0
  378. data/lib/expressir/model/interface_validator.rb +384 -0
  379. data/lib/expressir/model/literals/binary.rb +17 -0
  380. data/lib/expressir/model/literals/integer.rb +17 -0
  381. data/lib/expressir/model/literals/logical.rb +21 -0
  382. data/lib/expressir/model/literals/real.rb +17 -0
  383. data/lib/expressir/model/literals/string.rb +20 -0
  384. data/lib/expressir/model/literals.rb +13 -0
  385. data/lib/expressir/model/model_element.rb +377 -0
  386. data/lib/expressir/model/references/attribute_reference.rb +19 -0
  387. data/lib/expressir/model/references/group_reference.rb +19 -0
  388. data/lib/expressir/model/references/index_reference.rb +23 -0
  389. data/lib/expressir/model/references/simple_reference.rb +21 -0
  390. data/lib/expressir/model/references.rb +12 -0
  391. data/lib/expressir/model/remark_format.rb +17 -0
  392. data/lib/expressir/model/remark_info.rb +92 -0
  393. data/lib/expressir/model/remark_placement.rb +42 -0
  394. data/lib/expressir/model/repository.rb +483 -0
  395. data/lib/expressir/model/repository_validator.rb +293 -0
  396. data/lib/expressir/model/search_engine.rb +551 -0
  397. data/lib/expressir/model/statements/alias.rb +31 -0
  398. data/lib/expressir/model/statements/assignment.rb +23 -0
  399. data/lib/expressir/model/statements/case.rb +42 -0
  400. data/lib/expressir/model/statements/case_action.rb +20 -0
  401. data/lib/expressir/model/statements/compound.rb +21 -0
  402. data/lib/expressir/model/statements/escape.rb +18 -0
  403. data/lib/expressir/model/statements/if.rb +26 -0
  404. data/lib/expressir/model/statements/null.rb +18 -0
  405. data/lib/expressir/model/statements/procedure_call.rb +22 -0
  406. data/lib/expressir/model/statements/repeat.rb +40 -0
  407. data/lib/expressir/model/statements/return.rb +20 -0
  408. data/lib/expressir/model/statements/skip.rb +18 -0
  409. data/lib/expressir/model/statements.rb +20 -0
  410. data/lib/expressir/model/supertype_expressions/binary_supertype_expression.rb +25 -0
  411. data/lib/expressir/model/supertype_expressions/oneof_supertype_expression.rb +17 -0
  412. data/lib/expressir/model/supertype_expressions.rb +12 -0
  413. data/lib/expressir/model.rb +39 -0
  414. data/lib/expressir/package/builder.rb +231 -0
  415. data/lib/expressir/package/metadata.rb +79 -0
  416. data/lib/expressir/package/reader.rb +149 -0
  417. data/lib/expressir/package.rb +8 -0
  418. data/lib/expressir/schema_manifest.rb +159 -0
  419. data/lib/expressir/schema_manifest_entry.rb +15 -0
  420. data/lib/expressir/version.rb +8 -0
  421. data/lib/expressir.rb +94 -0
  422. data/lib/tasks/verify_remarks.rake +16 -0
  423. data/rakelib/native.rake +24 -0
  424. data/rakelib/oracle.rake +52 -0
  425. data/rakelib/verify_remarks.rake +16 -0
  426. metadata +692 -0
@@ -0,0 +1,857 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Expressir
4
+ module Mapping
5
+ # Formalization, parser, and validator for MIM reference paths
6
+ # (expressir#88): the "Reference path" sections of module
7
+ # mapping.yaml files, written in the EXPRESS mapping language
8
+ # (ELF 5006:2025). Until now the notation was prose for humans
9
+ # only; this makes it machine-readable and checks it against real
10
+ # schemas.
11
+ #
12
+ # Grammar (ELF 5006 §5; corpus-verified over all module
13
+ # mapping.yaml refpaths):
14
+ # path := step+
15
+ # step := [link] node [index] [constraint]
16
+ # | constraint
17
+ # | MARKER
18
+ # link := "->" | "<-" | "<=" | "=>" | "*>"
19
+ # | "<*" | "=" NAME (relation to the previous node)
20
+ # node := NAME ["." ATTRIBUTE]
21
+ # | LINK (<<express:SCHEMA.ITEM,ITEM>>,
22
+ # #460: a link may stand
23
+ # wherever a declaration
24
+ # is referenced)
25
+ # index := "[" ("i" | DIGITS) "]"
26
+ # constraint := ["!"] "{" NAME [NAME "."] "=" STRING "}"
27
+ # MARKER := "{" | "}" | "[" | "]" | "(" | ")" | ":" | "|" | "!"
28
+ # | "," | "/" | "*" | "<" | ">" (grouping/annotation;
29
+ # never breaks a
30
+ # pending link)
31
+ #
32
+ # Validation (ELF 5006 §8): a running path context tracks the
33
+ # entity a path is "at"; each step must connect to it, attribute
34
+ # navigations must be type-compatible with their target, and
35
+ # constraints must reference an entity in the path context with an
36
+ # existing, constrainable attribute whose type accepts the literal.
37
+ module RefPath
38
+ # Wire-name mapping is declared here so a parsed path can itself
39
+ # be serialized with lutaml-model.
40
+ class Step < Lutaml::Model::Serializable
41
+ attribute :operator, :string
42
+ attribute :name, :string
43
+ attribute :attribute, :string
44
+ attribute :index, :string
45
+ attribute :literal, :string
46
+ attribute :link, :string
47
+ attribute :negated, :boolean
48
+ # For constraint steps: the entity owning the constrained
49
+ # attribute when it differs from the constrained entity.
50
+ attribute :owner, :string
51
+
52
+ key_value do
53
+ map "operator", to: :operator
54
+ map "name", to: :name
55
+ map "attribute", to: :attribute
56
+ map "index", to: :index
57
+ map "literal", to: :literal
58
+ map "link", to: :link
59
+ map "negated", to: :negated
60
+ map "owner", to: :owner
61
+ end
62
+ end
63
+
64
+ class Path < Lutaml::Model::Serializable
65
+ attribute :steps, Step, collection: true
66
+ attribute :parse_errors, :string, collection: true
67
+
68
+ key_value do
69
+ map "steps", to: :steps
70
+ map "parse_errors", to: :parse_errors
71
+ end
72
+ end
73
+
74
+ # ELF 5006 §8 "Validation output": one structured verdict per
75
+ # path, serializable to YAML/JSON for the toolchain.
76
+ class ReportIssue < Lutaml::Model::Serializable
77
+ attribute :step, :integer
78
+ attribute :message, :string
79
+
80
+ key_value do
81
+ map "step", to: :step
82
+ map "message", to: :message
83
+ end
84
+ end
85
+
86
+ class Report < Lutaml::Model::Serializable
87
+ attribute :status, :string
88
+ attribute :start, :string
89
+ attribute :ending, :string
90
+ attribute :steps, :integer
91
+ attribute :entities, :string, collection: true
92
+ attribute :issues, ReportIssue, collection: true
93
+
94
+ key_value do
95
+ map "status", to: :status
96
+ map "start", to: :start
97
+ map "ending", to: :ending
98
+ map "steps", to: :steps
99
+ map "entities", to: :entities
100
+ map "issues", to: :issues
101
+ end
102
+ end
103
+
104
+ EXPRESS_LINK_PATH = /\A<<express:([^,>]+)(?:,[^>]+)?>>\z/
105
+ private_constant :EXPRESS_LINK_PATH
106
+
107
+ TOKEN = /<<express:[^,>]+(?:,[^>]+)?>>|->|<-|<=|=>|\*>|<\*|!?\{|[{}\[\]()=:|!,*\/<>]|'(?:''|[^'])*'|\w+(?:\.\w+)?/
108
+ MARKERS = ["{", "}", "[", "]", "(", ")", ":", "|", "!", ",", "/",
109
+ "*", "<", ">", "MAPPING_OF", "!{"].freeze
110
+ LINKS = ["->", "<-", "<=", "=>", "*>", "<*"].freeze
111
+ DECLARATION_COLLECTIONS = %i[types entities].freeze
112
+ private_constant :TOKEN, :MARKERS, :LINKS, :DECLARATION_COLLECTIONS
113
+
114
+ Issue = Struct.new(:step, :message, keyword_init: true)
115
+
116
+ module_function
117
+
118
+ # @return [Report] the structured VALID/INVALID verdict for
119
+ # +content+ (ELF 5006 §8 Validation output), carrying the
120
+ # traversed entities and every collected issue.
121
+ def report(content, repository, start: nil)
122
+ path = parse(content)
123
+ issues = validate(path, repository, start: start)
124
+ entities = path.steps.filter_map(&:name).uniq
125
+ Report.new(
126
+ status: (issues.empty? && path.parse_errors.empty? ? "VALID" : "INVALID"),
127
+ start: start || entities.first,
128
+ ending: entities.last,
129
+ steps: path.steps.size,
130
+ entities: entities,
131
+ issues: issues.map { |i| ReportIssue.new(step: i.step, message: i.message) },
132
+ )
133
+ end
134
+
135
+ # @param content [String] the mapping.yaml refpath content block
136
+ # @return [Path]
137
+ def parse(content)
138
+ tokens = content.to_s.scan(TOKEN)
139
+ steps = []
140
+ errors = []
141
+ pending_link = nil
142
+ pending_type_assign = false
143
+ false
144
+ i = 0
145
+ while i < tokens.length
146
+ token = tokens[i]
147
+ case token
148
+ when "!{"
149
+ true
150
+ step, consumed = parse_constraint(tokens[i..], negated: true)
151
+ steps << step
152
+ i += consumed
153
+ pending_link = nil
154
+ pending_type_assign = false
155
+ next
156
+ when "{"
157
+ step, consumed = parse_constraint(tokens[i..], negated: false)
158
+ steps << step
159
+ i += consumed
160
+ pending_link = nil
161
+ pending_type_assign = false
162
+ next
163
+ when *LINKS
164
+ pending_link = token
165
+ when "="
166
+ literal = tokens[i + 1]
167
+ if literal&.start_with?("'")
168
+ attach(steps, errors, :literal, literal)
169
+ i += 1
170
+ elsif literal.nil?
171
+ errors << "constraint '=' with nothing after it"
172
+ else
173
+ # `x = Type` type assignment: the following node (however
174
+ # many markers it sits behind, e.g. /MAPPING_OF(X)/)
175
+ # carries the "=" link.
176
+ pending_type_assign = true
177
+ end
178
+ when "["
179
+ if index_token?(tokens[i + 1])
180
+ attach(steps, errors, :index, tokens[i + 1])
181
+ i += 2 # the index token and the closing "]"
182
+ else
183
+ steps << Step.new(operator: "[")
184
+ end
185
+ when *MARKERS
186
+ steps << Step.new(operator: token)
187
+ when /\A<<express:/
188
+ steps << Step.new(operator: pending_link, link: token)
189
+ pending_link = nil
190
+ pending_type_assign = false
191
+ else
192
+ name, _, attribute = token.partition(".")
193
+ operator = pending_link || ("=" if pending_type_assign)
194
+ steps << Step.new(operator: operator, name: name,
195
+ attribute: attribute.empty? ? nil : attribute)
196
+ pending_link = nil
197
+ pending_type_assign = false
198
+ end
199
+ i += 1
200
+ end
201
+ Path.new(steps: steps, parse_errors: errors)
202
+ end
203
+
204
+ # A constraint block from +tokens+ (which start at "{" or "!{"):
205
+ # "{entity" or "{entity attr = literal}". Returns
206
+ # [Step, tokens_consumed].
207
+ def parse_constraint(tokens, negated:)
208
+ inner = []
209
+ consumed = 1
210
+ tokens[1..].each do |token|
211
+ consumed += 1
212
+ break if token == "}"
213
+
214
+ inner << token
215
+ end
216
+ eq = inner.index("=")
217
+ value = eq && inner[eq + 1]
218
+ literal = value || inner.find { |t| t.start_with?("'") }
219
+ names = inner.select { |t| t.match?(/\A\w+(\.\w+)?\z/) && !t.start_with?("'") }
220
+ qualified = names.find { |t| t.include?(".") }
221
+ entity = if qualified && names.size == 1
222
+ # {entity.attr = value} with the entity named only inside
223
+ # the qualified reference
224
+ qualified.split(".", 2)[0]
225
+ else
226
+ names.find { |t| !t.include?(".") }
227
+ end
228
+ owner, attribute = qualified ? qualified.split(".", 2) : [nil, nil]
229
+ owner = nil if entity && owner&.safe_downcase == entity.safe_downcase
230
+ step = Step.new(operator: "{", name: entity,
231
+ attribute: attribute, literal: literal,
232
+ negated: negated, owner: owner)
233
+ [step, consumed]
234
+ end
235
+
236
+ def index_token?(token)
237
+ token == "i" || token&.match?(/\A\d+\z/)
238
+ end
239
+
240
+ # Attach an index or constraint literal to the most recent node
241
+ # step that does not carry one yet.
242
+ def attach(steps, errors, field, value)
243
+ step = steps.reverse.each.find { |s| s.name && !s.public_send(field) }
244
+ if step
245
+ step.public_send("#{field}=", value)
246
+ else
247
+ case field
248
+ when :index then errors << "aggregate index [#{value}] has no node"
249
+ when :literal then errors << "constraint #{value} has no node"
250
+ end
251
+ end
252
+ end
253
+
254
+ # Validate a parsed path against a repository. Issues reference
255
+ # the step index they attach to. +start+ names the ARM entity the
256
+ # path is expected to begin at (the ae entity); when given and
257
+ # the path's first entity differs, a warning-level issue is
258
+ # recorded (ELF 5006 §8: the rule "may be relaxed for attribute
259
+ # mappings").
260
+ # @return [Array<Issue>]
261
+ def validate(path, repository, start: nil)
262
+ by_name = name_index(repository)
263
+ issues = []
264
+ ctx = nil
265
+ seen = {}
266
+ last_qualified = nil
267
+ path.steps.each_with_index do |step, pos|
268
+ # markers never break the running link, mirroring parse
269
+ next if marker_step?(step)
270
+
271
+ if step.link
272
+ issues.concat(check_link_step(step, pos, repository))
273
+ prev = preceding_name(steps_before(path, pos))
274
+ if step.name
275
+ check_exists(issues, pos, step.name, by_name)
276
+ ctx = step.name&.safe_downcase
277
+ elsif prev
278
+ ctx = prev&.safe_downcase
279
+ end
280
+ last_qualified = nil
281
+ next
282
+ end
283
+
284
+ case step.operator
285
+ when "(", ")"
286
+ # alternative sections: each starts a fresh path context
287
+ ctx = nil
288
+ last_qualified = nil
289
+ when "{"
290
+ issues.concat(check_constraint(step, pos, ctx, seen, by_name))
291
+ when "<=", "=>"
292
+ issues.concat(relation_issues(step, pos, ctx, by_name))
293
+ ctx = step.name&.safe_downcase
294
+ when "->"
295
+ issues.concat(forward_issues(step, pos, ctx, last_qualified, by_name))
296
+ ctx = step.name&.safe_downcase
297
+ last_qualified = nil
298
+ when "<-"
299
+ issues.concat(inverse_issues(step, pos, ctx, by_name))
300
+ ctx = step.name&.safe_downcase
301
+ last_qualified = step.attribute ? step : nil
302
+ when "*>", "<*"
303
+ issues.concat(select_extension_issues(step, pos, ctx, by_name))
304
+ ctx = step.name&.safe_downcase
305
+ else
306
+ issues.concat(check_step(step, pos, ctx, last_qualified, by_name))
307
+ if step.name
308
+ seen[step.name.safe_downcase] = true
309
+ last_qualified = step.attribute ? step : nil
310
+ ctx = step.name.safe_downcase unless ctx == step.name.safe_downcase && step.attribute
311
+ end
312
+ end
313
+ end
314
+ if start && path.steps.any?(&:name)
315
+ first = path.steps.find { |s| s.name && !s.link }
316
+ if first && first.name&.safe_downcase != start.safe_downcase
317
+ issues << Issue.new(step: 0,
318
+ message: "path starts at '#{first.name}' but " \
319
+ "the mapped entity is '#{start}'")
320
+ end
321
+ end
322
+ issues
323
+ end
324
+
325
+ def marker_step?(step)
326
+ step.name.nil? && step.attribute.nil? && step.link.nil? &&
327
+ step.operator != "{" && step.operator != "(" && step.operator != ")"
328
+ end
329
+
330
+ def steps_before(path, pos)
331
+ path.steps[0...pos].reverse_each
332
+ end
333
+
334
+ def preceding_name(steps)
335
+ steps.find { |s| s.name && !s.link }&.name
336
+ end
337
+
338
+ def check_exists(issues, pos, name, by_name)
339
+ return if name.nil? || by_name.key?(name.safe_downcase)
340
+
341
+ issues << Issue.new(step: pos,
342
+ message: "unknown type '#{name}'")
343
+ end
344
+
345
+ # ELF 5006 §8 constraint validation: the constrained entity must
346
+ # exist and appear in the path context; the constrained attribute
347
+ # must exist on it and be constrainable (not DERIVED/INVERSE);
348
+ # the literal must type-check against the attribute's type.
349
+ def check_constraint(step, pos, ctx, seen, by_name)
350
+ issues = []
351
+ entity = step.name
352
+ check_exists(issues, pos, entity, by_name)
353
+ return issues if entity.nil?
354
+
355
+ key = entity.safe_downcase
356
+ in_context = (ctx && ctx == key) || seen[key]
357
+ unless in_context
358
+ issues << Issue.new(step: pos,
359
+ message: "constraint entity '#{entity}' is not " \
360
+ "in the path context")
361
+ end
362
+
363
+ owner_key = (step.owner || entity).safe_downcase
364
+ decl = by_name[owner_key]&.last
365
+ return issues unless decl.is_a?(Model::Declarations::Entity)
366
+
367
+ if step.attribute
368
+ attribute = find_attribute(decl, step.attribute, by_name)
369
+ unless attribute
370
+ issues << Issue.new(step: pos,
371
+ message: "entity '#{entity}' has no attribute " \
372
+ "'#{step.attribute}'")
373
+ return issues
374
+ end
375
+
376
+ if attribute.respond_to?(:kind) &&
377
+ [Model::Declarations::Attribute::DERIVED, Model::Declarations::Attribute::INVERSE].include?(attribute.kind)
378
+ issues << Issue.new(step: pos,
379
+ message: "attribute '#{entity}." \
380
+ "#{step.attribute}' is " \
381
+ "#{attribute.kind} and cannot be " \
382
+ "constrained")
383
+ end
384
+
385
+ type = attribute.respond_to?(:type) ? attribute.type : nil
386
+ issue = value_type_issue(type, step.literal, entity,
387
+ step.attribute, by_name)
388
+ issues << Issue.new(step: pos, message: issue) if issue
389
+ elsif step.literal
390
+ issues << Issue.new(step: pos,
391
+ message: "constraint literal '#{step.literal}' " \
392
+ "has no attribute")
393
+ end
394
+ issues
395
+ end
396
+
397
+ # ELF 5006 §8 value-type compatibility table.
398
+ def value_type_issue(type, literal, entity, attribute, by_name)
399
+ return nil if literal.nil?
400
+
401
+ kind = value_kind(literal)
402
+ resolved = resolve_type_name(type, by_name)
403
+ case resolved
404
+ when :string
405
+ return nil if kind == :string
406
+
407
+ "value #{literal} is not a single-quoted string " \
408
+ "(#{entity}.#{attribute} is a STRING)"
409
+ when :integer
410
+ return nil if kind == :integer
411
+
412
+ "value #{literal} is not an unquoted integer " \
413
+ "(#{entity}.#{attribute} is an INTEGER)"
414
+ when :real, :number
415
+ return nil if %i[integer real].include?(kind)
416
+
417
+ "value #{literal} is not an unquoted number " \
418
+ "(#{entity}.#{attribute} is a #{resolved.to_s.upcase})"
419
+ when :boolean
420
+ return nil if kind == :boolean
421
+
422
+ "value #{literal} is not TRUE or FALSE " \
423
+ "(#{entity}.#{attribute} is a BOOLEAN)"
424
+ when :logical
425
+ return nil if %i[boolean logical].include?(kind)
426
+
427
+ "value #{literal} is not TRUE, FALSE or UNKNOWN " \
428
+ "(#{entity}.#{attribute} is a LOGICAL)"
429
+ when :enumeration
430
+ return nil if kind == :enumeration && enumeration_item?(type, literal, by_name)
431
+
432
+ "value #{literal} is not an item of the enumeration type of " \
433
+ "#{entity}.#{attribute}"
434
+ when :select
435
+ nil # any value shape may be legitimate against a SELECT arm
436
+ end
437
+ end
438
+
439
+ def value_kind(literal)
440
+ case literal
441
+ when /\A'/ then :string
442
+ when /\A(TRUE|FALSE)\z/ then :boolean
443
+ when /\AUNKNOWN\z/ then :logical
444
+ when /\A-?\d+\z/ then :integer
445
+ when /\A-?\d+\.\d+\z/ then :real
446
+ else :enumeration
447
+ end
448
+ end
449
+
450
+ # Resolve a parsed type node to a value-domain tag for the
451
+ # §8 table, or nil when the type cannot be determined.
452
+ def resolve_type_name(type, by_name)
453
+ case type
454
+ when Model::DataTypes::Boolean then :boolean
455
+ when Model::DataTypes::Logical then :logical
456
+ when Model::DataTypes::String then :string
457
+ when Model::DataTypes::Integer then :integer
458
+ when Model::DataTypes::Real then :real
459
+ when Model::DataTypes::Number then :number
460
+ when Model::DataTypes::Enumeration then :enumeration
461
+ when Model::DataTypes::Select then :select
462
+ when Model::ModelElement
463
+ return resolve_type_name(type.base_type, by_name) if type.respond_to?(:base_type) && type.base_type
464
+
465
+ id = if type.respond_to?(:base_path) && type.base_path
466
+ type.base_path.split(".").last
467
+ elsif type.respond_to?(:id)
468
+ type.id
469
+ end
470
+ return nil unless id
471
+
472
+ decl = by_name[id.safe_downcase]&.last
473
+ return nil unless decl.is_a?(Model::Declarations::Type)
474
+
475
+ resolve_type_name(decl.underlying_type, by_name)
476
+ end
477
+ end
478
+
479
+ def enumeration_item?(type, literal, by_name)
480
+ item = literal.delete_prefix("'").delete_suffix("'").safe_downcase
481
+ case type
482
+ when Model::DataTypes::Enumeration
483
+ type.items.to_a.any? { |i| i.id&.safe_downcase == item }
484
+ when Model::ModelElement
485
+ return enumeration_item?(type.base_type, literal, by_name) if type.respond_to?(:base_type) && type.base_type
486
+
487
+ id = if type.respond_to?(:base_path) && type.base_path
488
+ type.base_path.split(".").last
489
+ elsif type.respond_to?(:id)
490
+ type.id
491
+ end
492
+ decl = id && by_name[id.safe_downcase]&.last
493
+ decl.is_a?(Model::Declarations::Type) &&
494
+ enumeration_item?(decl.underlying_type, literal, by_name)
495
+ end
496
+ end
497
+
498
+ # ELF 5006 §8 navigation validation with a running context: the
499
+ # attribute must exist on the context entity, and its type must
500
+ # be able to reference the step's target entity.
501
+ def forward_issues(step, pos, ctx, last_qualified, by_name)
502
+ issues = []
503
+ unless ctx
504
+ issues << Issue.new(step: pos,
505
+ message: "'->' with no preceding entity in the " \
506
+ "path context")
507
+ return issues
508
+ end
509
+
510
+ owner = by_name[ctx]&.last
511
+ return issues unless owner.is_a?(Model::Declarations::Entity)
512
+
513
+ attr_name = last_qualified&.attribute || step.attribute
514
+ if attr_name.nil?
515
+ issues << Issue.new(step: pos,
516
+ message: "'->' requires an attribute-qualified " \
517
+ "node before it")
518
+ return issues
519
+ end
520
+
521
+ attribute = find_attribute(owner, attr_name, by_name)
522
+ unless attribute
523
+ issues << Issue.new(step: pos,
524
+ message: "entity '#{owner.id}' has no attribute " \
525
+ "'#{attr_name}'")
526
+ return issues
527
+ end
528
+
529
+ issues.concat(target_compat_issues(attribute, step, pos, ctx, by_name))
530
+ issues
531
+ end
532
+
533
+ def inverse_issues(step, pos, ctx, by_name)
534
+ issues = []
535
+ entity = step.name
536
+ check_exists(issues, pos, entity, by_name)
537
+ return issues if entity.nil?
538
+
539
+ decl = by_name[entity.safe_downcase]&.last
540
+ return issues unless decl.is_a?(Model::Declarations::Entity)
541
+
542
+ attr_name = step.attribute
543
+ if attr_name.nil?
544
+ issues << Issue.new(step: pos,
545
+ message: "'<-' requires an attribute-qualified " \
546
+ "node after it")
547
+ return issues
548
+ end
549
+
550
+ attribute = find_attribute(decl, attr_name, by_name)
551
+ unless attribute
552
+ issues << Issue.new(step: pos,
553
+ message: "entity '#{entity}' has no attribute " \
554
+ "'#{attr_name}'")
555
+ return issues
556
+ end
557
+
558
+ if ctx
559
+ issues.concat(target_compat_issues(attribute, step, pos, ctx, by_name,
560
+ inverse: true))
561
+ end
562
+ issues
563
+ end
564
+
565
+ # ELF 5006 §8: the attribute type must reference the target
566
+ # entity, a supertype of it, or a SELECT whose options reach it.
567
+ # `ctx` is the target of the navigation either way: for `->` the
568
+ # step names it; for `<-` the path context carries it.
569
+ def target_compat_issues(attribute, step, pos, ctx, by_name, inverse: false)
570
+ type = attribute.respond_to?(:type) ? attribute.type : nil
571
+ target = inverse ? ctx : step.name
572
+ return [] if type.nil? || target.nil?
573
+ return [] if references_entity?(type, target, by_name, {}.compare_by_identity)
574
+
575
+ owner = attribute.parent.respond_to?(:id) ? attribute.parent.id : nil
576
+ [Issue.new(step: pos,
577
+ message: "attribute '#{"#{owner}." if owner}" \
578
+ "#{attribute.id}' cannot reference '#{target}'")]
579
+ end
580
+
581
+ def references_entity?(type, target, by_name, seen)
582
+ return false if type.nil? || seen[type]
583
+
584
+ seen[type] = true
585
+ if type.is_a?(Model::References::SimpleReference)
586
+ id = type.respond_to?(:base_path) && type.base_path ? type.base_path.to_s.split(".").last : type.id
587
+ return false if id.nil?
588
+
589
+ key = id.safe_downcase
590
+ return true if key == target
591
+
592
+ decl = by_name[key]&.last
593
+ return select_reaches?(decl, target, by_name, seen) if decl.is_a?(Model::Declarations::Type)
594
+
595
+ return entity_reaches?(decl, target, by_name, seen)
596
+ end
597
+ if type.respond_to?(:base_type) && type.base_type
598
+ return references_entity?(type.base_type, target, by_name, seen)
599
+ end
600
+ if type.is_a?(Model::DataTypes::Select)
601
+ return type.items.to_a.any? do |item|
602
+ id = item.respond_to?(:id) ? item.id&.safe_downcase : nil
603
+ next false if id.nil?
604
+ next true if id == target
605
+
606
+ decl = by_name[id]&.last
607
+ decl ? entity_reaches?(decl, target, by_name, seen) : false
608
+ end
609
+ end
610
+
611
+ false
612
+ end
613
+
614
+ def select_reaches?(type_decl, target, by_name, seen)
615
+ return false unless type_decl.is_a?(Model::Declarations::Type)
616
+
617
+ underlying = type_decl.underlying_type
618
+ references_entity?(underlying, target, by_name, seen)
619
+ end
620
+
621
+ # target is a subtype of the referenced entity decl (or equal).
622
+ def entity_reaches?(decl, target, by_name, _seen)
623
+ return false unless decl.is_a?(Model::Declarations::Entity)
624
+
625
+ key = decl.id&.safe_downcase
626
+ return true if key == target
627
+
628
+ supertype_ids(decl, by_name).any? { |s| s&.safe_downcase == target }
629
+ end
630
+
631
+ def select_extension_issues(step, pos, ctx, by_name)
632
+ issues = []
633
+ check_exists(issues, pos, step.name, by_name)
634
+ return issues if step.name.nil? || ctx.nil?
635
+
636
+ base = by_name[ctx]&.last
637
+ ext = by_name[step.name.safe_downcase]&.last
638
+ return issues unless base.is_a?(Model::Declarations::Type) &&
639
+ ext.is_a?(Model::Declarations::Type)
640
+
641
+ return issues if select_extends?(ext, base, by_name, {}.compare_by_identity)
642
+
643
+ issues << Issue.new(step: pos,
644
+ message: "SELECT '#{step.name}' does not extend '#{ctx}'")
645
+ issues
646
+ end
647
+
648
+ # select_b <* select_a / select_a *> select_b: select_b extends
649
+ # select_a when select_b's BASED ON chain reaches select_a.
650
+ def select_extends?(ext, base, by_name, seen)
651
+ return false if seen[ext]
652
+
653
+ seen[ext] = true
654
+ underlying = ext.underlying_type
655
+ return false unless underlying.is_a?(Model::DataTypes::Select)
656
+
657
+ based = underlying.based_on
658
+ raw = based && (based.respond_to?(:base_path) ? based.base_path : based.id)
659
+ based_id = raw && raw.to_s.split(".").last
660
+ return true if based_id&.safe_downcase == base.id&.safe_downcase
661
+
662
+ based_decl = based_id && by_name[based_id.safe_downcase]&.last
663
+ based_decl ? select_extends?(based_decl, base, by_name, seen) : false
664
+ end
665
+
666
+ def relation_issues(step, pos, ctx, by_name)
667
+ target = by_name[step.name&.safe_downcase]&.last
668
+ source = ctx && by_name[ctx]&.last
669
+ return [] unless source.is_a?(Model::Declarations::Entity) &&
670
+ target.is_a?(Model::Declarations::Entity)
671
+
672
+ if step.operator == "<="
673
+ return [] if supertype_ids(source, by_name)
674
+ .any? { |id| id&.safe_downcase == step.name&.safe_downcase }
675
+
676
+ [Issue.new(step: pos,
677
+ message: "'#{ctx}' is not a subtype of '#{step.name}'")]
678
+ else
679
+ return [] if supertype_ids(target, by_name)
680
+ .any? { |id| id&.safe_downcase == ctx }
681
+
682
+ [Issue.new(step: pos,
683
+ message: "'#{step.name}' is not a subtype of '#{ctx}'")]
684
+ end
685
+ end
686
+
687
+ def check_step(step, pos, ctx, _last_qualified, by_name)
688
+ found = by_name[step.name&.safe_downcase]
689
+ return [Issue.new(step: pos, message: "unknown type '#{step.name}'")] unless found
690
+
691
+ issues = attribute_issues(step, pos, found[1], by_name)
692
+ issues.concat(bare_continuity_issue(step, pos, ctx, found, by_name))
693
+ issues
694
+ end
695
+
696
+ # ELF 5006 §8 navigation continuity: a bare node naming a
697
+ # different entity than the context must still be related to it
698
+ # (either one a supertype of the other).
699
+ def bare_continuity_issue(step, pos, ctx, found, by_name)
700
+ return [] unless ctx
701
+ return [] if ctx == step.name&.safe_downcase
702
+
703
+ decl = found[1]
704
+ return [] unless decl.is_a?(Model::Declarations::Entity)
705
+
706
+ down = ->(id) { id&.safe_downcase }
707
+ if supertype_ids(decl, by_name).any? { |id| down.(id) == ctx }
708
+ return []
709
+ end
710
+
711
+ ctx_decl = by_name[ctx]&.last
712
+ if ctx_decl.is_a?(Model::Declarations::Entity) &&
713
+ supertype_ids(ctx_decl, by_name).any? { |id| down.(id) == decl.id&.safe_downcase }
714
+ return []
715
+ end
716
+
717
+ [Issue.new(step: pos,
718
+ message: "'#{step.name}' breaks navigation continuity " \
719
+ "(context is '#{ctx}')")]
720
+ end
721
+
722
+ EXPRESS_BUILTINS = %w[NUMBER INTEGER REAL STRING BOOLEAN LOGICAL
723
+ BINARY].freeze
724
+ private_constant :EXPRESS_BUILTINS
725
+
726
+ def name_index(repository)
727
+ index = EXPRESS_BUILTINS.to_h { |b| [b.downcase, [nil, nil]] }
728
+ repository.schemas.flat_map do |schema|
729
+ DECLARATION_COLLECTIONS.flat_map do |coll|
730
+ Array(schema.public_send(coll))
731
+ .select { |d| d.respond_to?(:id) && d.id }
732
+ .map { |d| [d.id.safe_downcase, [schema, d]] }
733
+ end
734
+ end.each { |pair| index[pair[0]] = pair[1] }
735
+ index
736
+ end
737
+
738
+ # Validate links in aa.assertion_to (#460) and ae/sc entries.
739
+ def check_link_step(step, pos, repository)
740
+ parts = step.link.match(EXPRESS_LINK_PATH)
741
+ return [Issue.new(step: pos, message: "malformed link '#{step.link}'")] unless parts
742
+
743
+ schema_id, item_id = parts[1].split(".", 2).compact
744
+ schema = repository.schemas
745
+ .find { |s| s.id&.safe_downcase == schema_id&.safe_downcase }
746
+ unless schema
747
+ return [Issue.new(step: pos,
748
+ message: "unknown schema '#{schema_id}' in #{step.link}")]
749
+ end
750
+
751
+ return [] if item_id.nil?
752
+ return [] if Expressir::Mapping.item_declared?(schema, item_id)
753
+
754
+ [Issue.new(step: pos,
755
+ message: "schema '#{schema_id}' does not declare " \
756
+ "'#{item_id}' (#{step.link})")]
757
+ end
758
+
759
+ def attribute_issues(step, pos, decl, by_name)
760
+ return [] unless step.attribute
761
+
762
+ unless decl.is_a?(Model::Declarations::Entity)
763
+ return [Issue.new(step: pos,
764
+ message: "'#{step.name}' is not an entity, " \
765
+ "cannot carry '#{step.name}.#{step.attribute}'")]
766
+ end
767
+
768
+ attribute = find_attribute(decl, step.attribute, by_name)
769
+ unless attribute
770
+ return [Issue.new(step: pos,
771
+ message: "entity '#{step.name}' has no attribute " \
772
+ "'#{step.attribute}'")]
773
+ end
774
+
775
+ aggregate_issue(step, pos, attribute)
776
+ end
777
+
778
+ # Direct or inherited attribute: inheritance follows the
779
+ # subtype_of chain, resolved through the repository-wide index.
780
+ def find_attribute(entity, attribute_id, by_name)
781
+ key = attribute_id.safe_downcase
782
+ supertype_chain(entity, by_name).lazy.flat_map do |candidate|
783
+ Array(candidate.attributes)
784
+ end.find { |a| a.id&.safe_downcase == key }
785
+ end
786
+
787
+ def supertype_chain(entity, by_name)
788
+ seen = {}.compare_by_identity
789
+ queue = [entity]
790
+ result = []
791
+ until queue.empty?
792
+ current = queue.shift
793
+ next if seen[current]
794
+
795
+ seen[current] = true
796
+ result << current
797
+ current.subtype_of.to_a.each do |ref|
798
+ sup = by_name[ref.respond_to?(:id) ? ref.id&.safe_downcase : nil]&.last
799
+ queue << sup if sup.is_a?(Model::Declarations::Entity)
800
+ end
801
+ end
802
+ result
803
+ end
804
+
805
+ def aggregate_issue(step, pos, attribute)
806
+ return [] unless step.index
807
+ return [] if attribute.type.respond_to?(:bound1)
808
+
809
+ [Issue.new(step: pos,
810
+ message: "'#{step.name}.#{step.attribute}' is not an " \
811
+ "aggregate but carries [#{step.index}]")]
812
+ end
813
+
814
+ # ELF 5006 §8 subtype/supertype relations, resolved through the
815
+ # repository-wide index so inherited chains (including
816
+ # cross-schema USE FROM) walk correctly.
817
+ def supertype_ids(entity, by_name)
818
+ seen = {}.compare_by_identity
819
+ queue = [entity]
820
+ names = []
821
+ until queue.empty?
822
+ current = queue.shift
823
+ next if seen[current]
824
+
825
+ seen[current] = true
826
+ direct = current.subtype_of.to_a.filter_map do |ref|
827
+ ref.respond_to?(:id) ? ref.id : nil
828
+ end
829
+ leaves = expression_leaves(current.supertype_expression)
830
+ .filter_map { |leaf| leaf.respond_to?(:id) ? leaf.id : nil }
831
+ names.concat(direct)
832
+ names.concat(leaves)
833
+ direct.concat(leaves).each do |id|
834
+ sup = by_name[id&.safe_downcase]&.last
835
+ queue << sup if sup.is_a?(Model::Declarations::Entity)
836
+ end
837
+ end
838
+ names.uniq
839
+ end
840
+
841
+ # Leaf reference nodes of a supertype expression tree.
842
+ def expression_leaves(expr)
843
+ return [] unless expr
844
+
845
+ if expr.respond_to?(:operands)
846
+ Array(expr.operands).flat_map { |o| expression_leaves(o) }
847
+ elsif expr.respond_to?(:operand1)
848
+ expression_leaves(expr.operand1) + expression_leaves(expr.operand2)
849
+ elsif expr.respond_to?(:id) && expr.id
850
+ [expr]
851
+ else
852
+ []
853
+ end
854
+ end
855
+ end
856
+ end
857
+ end