fhirpath-rb 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +25 -0
  4. data/CLAUDE.md +59 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +43 -0
  7. data/Rakefile +12 -0
  8. data/ext/fhir_path_parser/extconf.rb +40 -0
  9. data/lib/fhirpath/engine/evaluators/expressions.rb +59 -0
  10. data/lib/fhirpath/engine/evaluators/literals.rb +70 -0
  11. data/lib/fhirpath/engine/evaluators/member_invocation.rb +109 -0
  12. data/lib/fhirpath/engine/evaluators.rb +130 -0
  13. data/lib/fhirpath/engine/invocations/aggregate.rb +48 -0
  14. data/lib/fhirpath/engine/invocations/collections.rb +32 -0
  15. data/lib/fhirpath/engine/invocations/combining.rb +30 -0
  16. data/lib/fhirpath/engine/invocations/datetime.rb +34 -0
  17. data/lib/fhirpath/engine/invocations/equality.rb +95 -0
  18. data/lib/fhirpath/engine/invocations/equivalence.rb +138 -0
  19. data/lib/fhirpath/engine/invocations/existence.rb +117 -0
  20. data/lib/fhirpath/engine/invocations/filtering.rb +107 -0
  21. data/lib/fhirpath/engine/invocations/inequality.rb +92 -0
  22. data/lib/fhirpath/engine/invocations/logic.rb +74 -0
  23. data/lib/fhirpath/engine/invocations/math.rb +109 -0
  24. data/lib/fhirpath/engine/invocations/math_functions.rb +121 -0
  25. data/lib/fhirpath/engine/invocations/misc.rb +144 -0
  26. data/lib/fhirpath/engine/invocations/misc_converts_to.rb +91 -0
  27. data/lib/fhirpath/engine/invocations/navigation.rb +102 -0
  28. data/lib/fhirpath/engine/invocations/registry_strings_and_math.rb +48 -0
  29. data/lib/fhirpath/engine/invocations/strings.rb +139 -0
  30. data/lib/fhirpath/engine/invocations/strings_encoding.rb +68 -0
  31. data/lib/fhirpath/engine/invocations/subsetting.rb +15 -0
  32. data/lib/fhirpath/engine/invocations/types.rb +36 -0
  33. data/lib/fhirpath/engine/invocations.rb +118 -0
  34. data/lib/fhirpath/engine/nodes/fp_date_time.rb +140 -0
  35. data/lib/fhirpath/engine/nodes/fp_date_time_arithmetic.rb +112 -0
  36. data/lib/fhirpath/engine/nodes/fp_quantity.rb +142 -0
  37. data/lib/fhirpath/engine/nodes/fp_quantity_deep_equal.rb +67 -0
  38. data/lib/fhirpath/engine/nodes/fp_time.rb +139 -0
  39. data/lib/fhirpath/engine/nodes/fp_time_arithmetic.rb +111 -0
  40. data/lib/fhirpath/engine/nodes/resource_node.rb +66 -0
  41. data/lib/fhirpath/engine/nodes/type_info.rb +92 -0
  42. data/lib/fhirpath/engine/param_resolution.rb +74 -0
  43. data/lib/fhirpath/engine/util.rb +117 -0
  44. data/lib/fhirpath/engine.rb +100 -0
  45. data/lib/fhirpath/models/dstu2/choiceTypePaths.json +1041 -0
  46. data/lib/fhirpath/models/dstu2/path2Type.json +4728 -0
  47. data/lib/fhirpath/models/dstu2/pathsDefinedElsewhere.json +21 -0
  48. data/lib/fhirpath/models/dstu2/type2Parent.json +141 -0
  49. data/lib/fhirpath/models/r4/choiceTypePaths.json +1365 -0
  50. data/lib/fhirpath/models/r4/path2Type.json +7707 -0
  51. data/lib/fhirpath/models/r4/pathsDefinedElsewhere.json +57 -0
  52. data/lib/fhirpath/models/r4/type2Parent.json +211 -0
  53. data/lib/fhirpath/models/r5/choiceTypePaths.json +1883 -0
  54. data/lib/fhirpath/models/r5/path2Type.json +9653 -0
  55. data/lib/fhirpath/models/r5/pathsDefinedElsewhere.json +80 -0
  56. data/lib/fhirpath/models/r5/type2Parent.json +234 -0
  57. data/lib/fhirpath/models/stu3/choiceTypePaths.json +991 -0
  58. data/lib/fhirpath/models/stu3/path2Type.json +5729 -0
  59. data/lib/fhirpath/models/stu3/pathsDefinedElsewhere.json +37 -0
  60. data/lib/fhirpath/models/stu3/type2Parent.json +173 -0
  61. data/lib/fhirpath/models.rb +25 -0
  62. data/lib/fhirpath/parser/FHIRPath.g4 +172 -0
  63. data/lib/fhirpath/parser/ast_builder.rb +51 -0
  64. data/lib/fhirpath/parser.rb +21 -0
  65. data/lib/fhirpath/version.rb +5 -0
  66. data/lib/fhirpath.rb +117 -0
  67. data/rakelib/parser.rake +146 -0
  68. data/sig/fhirpath.rbs +14 -0
  69. metadata +168 -0
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Builds the native FHIRPath parser extension (see lib/fhirpath/parser/FHIRPath.g4) using the
4
+ # antlr4-native gem. Generated/vendored sources are gitignored; run `rake parser:setup compile`
5
+ # once (and again after editing the grammar) before running specs.
6
+ namespace :parser do
7
+ root_dir = File.expand_path("..", __dir__)
8
+ ext_dir = File.join(root_dir, "ext/fhir_path_parser")
9
+ grammar_file = File.join(root_dir, "lib/fhirpath/parser/FHIRPath.g4")
10
+ runtime_dir = File.join(ext_dir, "antlr4-upstream")
11
+ interop_file = File.join(ext_dir, "fhir_path_parser.cpp")
12
+
13
+ desc "Clone the ANTLR4 C++ runtime version matching antlr4-native's bundled ANTLR jar"
14
+ task :vendor_runtime do
15
+ require "antlr4-native"
16
+
17
+ if Dir.exist?(runtime_dir)
18
+ puts "#{runtime_dir} already present, skipping clone"
19
+ else
20
+ version = Antlr4Native::Generator::ANTLR_VERSION
21
+ sh "git clone --depth 1 --branch #{version} " \
22
+ "https://github.com/antlr/antlr4 #{runtime_dir}"
23
+ end
24
+ end
25
+
26
+ desc "Generate the ANTLR4 parser sources from FHIRPath.g4"
27
+ task generate: :vendor_runtime do
28
+ require "antlr4-native"
29
+
30
+ generator = Antlr4Native::Generator.new(
31
+ grammar_files: [grammar_file],
32
+ output_dir: File.join(root_dir, "ext"),
33
+ parser_root_method: "expression"
34
+ )
35
+ generator.generate
36
+
37
+ # generator.generate overwrites the interop file every run, so the patches below have to
38
+ # be re-applied here rather than committed to the generated file.
39
+ source = File.read(interop_file)
40
+
41
+ # ContextProxy::wrapParseTree (generated) dispatches on antlrcpp::is<T>, i.e. dynamic_cast,
42
+ # checking context types in the order they were first seen in the parser source. Since a
43
+ # labeled alternative's context (e.g. AdditiveExpressionContext) is a C++ subclass of its
44
+ # rule's base context (ExpressionContext), and the base class is emitted first, dynamic_cast
45
+ # to the *base* type succeeds first and every labeled node gets wrapped as the generic base
46
+ # proxy — the specific label is lost. RTTI on the raw ParseTree pointer gives us the real
47
+ # most-derived type name regardless of which proxy class wrapped it, since getText()/
48
+ # getChildren() live on the common ContextProxy base either way.
49
+ source = source.sub(
50
+ "#include <iostream>",
51
+ "#include <iostream>\n#include <cxxabi.h>\n#include <cstdlib>"
52
+ )
53
+
54
+ source = source.sub(
55
+ " bool doubleEquals(Object other) {",
56
+ "#{<<~CPP.chomp}\n\n bool doubleEquals(Object other) {"
57
+ std::string getTypeName() {
58
+ if (orig == nullptr) return "";
59
+
60
+ int status = 0;
61
+ char *demangled = abi::__cxa_demangle(typeid(*orig).name(), nullptr, nullptr, &status);
62
+ std::string result = (status == 0 && demangled != nullptr)
63
+ ? std::string(demangled)
64
+ : std::string(typeid(*orig).name());
65
+
66
+ if (demangled != nullptr) {
67
+ free(demangled);
68
+ }
69
+
70
+ return result;
71
+ }
72
+ CPP
73
+ )
74
+
75
+ source = source.sub(
76
+ '.define_method("==", &ContextProxy::doubleEquals);',
77
+ ".define_method(\"==\", &ContextProxy::doubleEquals)\n" \
78
+ ' .define_method("type_name", &ContextProxy::getTypeName);'
79
+ )
80
+
81
+ error_listener_class = <<~CPP
82
+ class CountingErrorListener : public BaseErrorListener {
83
+ public:
84
+ size_t *counter = nullptr;
85
+
86
+ void syntaxError(Recognizer *recognizer, Token *offendingSymbol, size_t line,
87
+ size_t charPositionInLine, const std::string &msg,
88
+ std::exception_ptr e) override {
89
+ if (counter != nullptr) {
90
+ (*counter)++;
91
+ }
92
+ }
93
+ };
94
+
95
+ CPP
96
+ source = source.sub("class ParserProxy {", "#{error_listener_class}class ParserProxy {")
97
+
98
+ source = source.sub(
99
+ "public:\n static ParserProxy* parse(string code) {",
100
+ "public:\n size_t syntaxErrorCount() { return errorCount; }\n\n" \
101
+ " static ParserProxy* parse(string code) {"
102
+ )
103
+
104
+ source = source.sub(
105
+ /(#{Regexp.escape("FHIRPathParser* parser;")})/,
106
+ "\\1\n CountingErrorListener errorListener;\n size_t errorCount = 0;"
107
+ )
108
+
109
+ source = source.sub(
110
+ "parser -> parser = new FHIRPathParser(parser -> tokens);",
111
+ <<~CPP.chomp
112
+ parser -> parser = new FHIRPathParser(parser -> tokens);
113
+
114
+ parser -> errorListener.counter = &parser -> errorCount;
115
+ parser -> lexer -> removeErrorListeners();
116
+ parser -> lexer -> addErrorListener(&parser -> errorListener);
117
+ parser -> parser -> removeErrorListeners();
118
+ parser -> parser -> addErrorListener(&parser -> errorListener);
119
+ CPP
120
+ )
121
+
122
+ source = source.sub(
123
+ '.define_method("visit", &ParserProxy::visit);',
124
+ ".define_method(\"visit\", &ParserProxy::visit)\n" \
125
+ ' .define_method("syntax_error_count", &ParserProxy::syntaxErrorCount);'
126
+ )
127
+
128
+ File.write(interop_file, source)
129
+ end
130
+
131
+ desc "Vendor the ANTLR4 C++ runtime and generate the parser sources"
132
+ task setup: :generate
133
+
134
+ desc "Compile the native FHIRPath parser extension"
135
+ task :compile do
136
+ require "etc"
137
+
138
+ Dir.chdir(ext_dir) do
139
+ ruby "extconf.rb"
140
+ sh "make -j #{Etc.nprocessors}"
141
+ end
142
+ end
143
+ end
144
+
145
+ desc "Compile native extensions (see rake parser:setup to (re)generate their sources first)"
146
+ task compile: "parser:compile"
data/sig/fhirpath.rbs ADDED
@@ -0,0 +1,14 @@
1
+ module Fhirpath
2
+ VERSION: String
3
+
4
+ class Error < StandardError
5
+ end
6
+
7
+ def self.evaluate: (FHIR::Model | Array[FHIR::Model] resource, untyped path, ?untyped context, ?Module? model, ?untyped options) -> Array[untyped]
8
+
9
+ def self.compile: [SRC < FHIR::Model, DST] (untyped path, ?Module? model, ?untyped options) -> ^(SRC resource, ?untyped context) -> Array[DST]
10
+
11
+ def self.compile_as_array: [SRC < FHIR::Model, DST] (String expression, Class input_type, Class output_type, ?Module? model) -> ^(SRC resource, ?untyped context) -> Array[DST]
12
+
13
+ def self.compile_as_first: [SRC < FHIR::Model, DST] (String expression, Class input_type, Class output_type, ?Module? model) -> ^(SRC resource, ?untyped context) -> DST?
14
+ end
metadata ADDED
@@ -0,0 +1,168 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fhirpath-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - beda.software
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: bigdecimal
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: fhir_models
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '5.1'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '5.1'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rice
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '4.0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '4.0'
68
+ description: A Ruby port of fhirpath-py (https://github.com/beda-software/fhirpath-py),
69
+ implementing the FHIRPath expression language (https://hl7.org/fhirpath/).
70
+ email:
71
+ - ilya@beda.software
72
+ executables: []
73
+ extensions:
74
+ - ext/fhir_path_parser/extconf.rb
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".rspec"
78
+ - ".rubocop.yml"
79
+ - CLAUDE.md
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - ext/fhir_path_parser/extconf.rb
84
+ - lib/fhirpath.rb
85
+ - lib/fhirpath/engine.rb
86
+ - lib/fhirpath/engine/evaluators.rb
87
+ - lib/fhirpath/engine/evaluators/expressions.rb
88
+ - lib/fhirpath/engine/evaluators/literals.rb
89
+ - lib/fhirpath/engine/evaluators/member_invocation.rb
90
+ - lib/fhirpath/engine/invocations.rb
91
+ - lib/fhirpath/engine/invocations/aggregate.rb
92
+ - lib/fhirpath/engine/invocations/collections.rb
93
+ - lib/fhirpath/engine/invocations/combining.rb
94
+ - lib/fhirpath/engine/invocations/datetime.rb
95
+ - lib/fhirpath/engine/invocations/equality.rb
96
+ - lib/fhirpath/engine/invocations/equivalence.rb
97
+ - lib/fhirpath/engine/invocations/existence.rb
98
+ - lib/fhirpath/engine/invocations/filtering.rb
99
+ - lib/fhirpath/engine/invocations/inequality.rb
100
+ - lib/fhirpath/engine/invocations/logic.rb
101
+ - lib/fhirpath/engine/invocations/math.rb
102
+ - lib/fhirpath/engine/invocations/math_functions.rb
103
+ - lib/fhirpath/engine/invocations/misc.rb
104
+ - lib/fhirpath/engine/invocations/misc_converts_to.rb
105
+ - lib/fhirpath/engine/invocations/navigation.rb
106
+ - lib/fhirpath/engine/invocations/registry_strings_and_math.rb
107
+ - lib/fhirpath/engine/invocations/strings.rb
108
+ - lib/fhirpath/engine/invocations/strings_encoding.rb
109
+ - lib/fhirpath/engine/invocations/subsetting.rb
110
+ - lib/fhirpath/engine/invocations/types.rb
111
+ - lib/fhirpath/engine/nodes/fp_date_time.rb
112
+ - lib/fhirpath/engine/nodes/fp_date_time_arithmetic.rb
113
+ - lib/fhirpath/engine/nodes/fp_quantity.rb
114
+ - lib/fhirpath/engine/nodes/fp_quantity_deep_equal.rb
115
+ - lib/fhirpath/engine/nodes/fp_time.rb
116
+ - lib/fhirpath/engine/nodes/fp_time_arithmetic.rb
117
+ - lib/fhirpath/engine/nodes/resource_node.rb
118
+ - lib/fhirpath/engine/nodes/type_info.rb
119
+ - lib/fhirpath/engine/param_resolution.rb
120
+ - lib/fhirpath/engine/util.rb
121
+ - lib/fhirpath/models.rb
122
+ - lib/fhirpath/models/dstu2/choiceTypePaths.json
123
+ - lib/fhirpath/models/dstu2/path2Type.json
124
+ - lib/fhirpath/models/dstu2/pathsDefinedElsewhere.json
125
+ - lib/fhirpath/models/dstu2/type2Parent.json
126
+ - lib/fhirpath/models/r4/choiceTypePaths.json
127
+ - lib/fhirpath/models/r4/path2Type.json
128
+ - lib/fhirpath/models/r4/pathsDefinedElsewhere.json
129
+ - lib/fhirpath/models/r4/type2Parent.json
130
+ - lib/fhirpath/models/r5/choiceTypePaths.json
131
+ - lib/fhirpath/models/r5/path2Type.json
132
+ - lib/fhirpath/models/r5/pathsDefinedElsewhere.json
133
+ - lib/fhirpath/models/r5/type2Parent.json
134
+ - lib/fhirpath/models/stu3/choiceTypePaths.json
135
+ - lib/fhirpath/models/stu3/path2Type.json
136
+ - lib/fhirpath/models/stu3/pathsDefinedElsewhere.json
137
+ - lib/fhirpath/models/stu3/type2Parent.json
138
+ - lib/fhirpath/parser.rb
139
+ - lib/fhirpath/parser/FHIRPath.g4
140
+ - lib/fhirpath/parser/ast_builder.rb
141
+ - lib/fhirpath/version.rb
142
+ - rakelib/parser.rake
143
+ - sig/fhirpath.rbs
144
+ homepage: https://github.com/beda-software/fhirpath-ruby
145
+ licenses:
146
+ - MIT
147
+ metadata:
148
+ homepage_uri: https://github.com/beda-software/fhirpath-ruby
149
+ source_code_uri: https://github.com/beda-software/fhirpath-ruby
150
+ changelog_uri: https://github.com/beda-software/fhirpath-ruby/blob/main/CHANGELOG.md
151
+ rdoc_options: []
152
+ require_paths:
153
+ - lib
154
+ required_ruby_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: 3.0.0
159
+ required_rubygems_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ requirements: []
165
+ rubygems_version: 3.6.9
166
+ specification_version: 4
167
+ summary: FHIRPath implementation in Ruby
168
+ test_files: []