drgdsl 1.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c17114e6f5723fac59d6acf342e6038be2a74a6b1d31cc300cc83e8c77a1ae2b
4
+ data.tar.gz: 820f8aea9f44b5e911f6112bef704cb3b8a198ac551038819364840e1cefbe4b
5
+ SHA512:
6
+ metadata.gz: 26aba3e5db76bf2e10899f1c007fb3463736ab3d3bc05f7883b5250992ad6fb8b755319aab3446c61f269a4688e317b46b219f46f190ea5360f4a59ce36e3bd8
7
+ data.tar.gz: 473c59b7476fc637493742ae50814d76a9cb63f58f45921b60fa3a51880c11b4e564312556eadf2c8f0134d49ba966d0b71c69f90cda4d6c074c5a66eded7853
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.0
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in drgdsl.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,42 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ drgdsl (1.0.0)
5
+ oj (~> 3.5.0)
6
+ parslet (~> 1.8.2)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ byebug (10.0.2)
12
+ coderay (1.1.2)
13
+ method_source (0.9.0)
14
+ minitest (5.11.3)
15
+ oj (3.5.1)
16
+ parslet (1.8.2)
17
+ pry (0.11.3)
18
+ coderay (~> 1.1.0)
19
+ method_source (~> 0.9.0)
20
+ pry-byebug (3.6.0)
21
+ byebug (~> 10.0)
22
+ pry (~> 0.10)
23
+ pry-doc (0.13.4)
24
+ pry (~> 0.11)
25
+ yard (~> 0.9.11)
26
+ rake (12.3.1)
27
+ yard (0.9.12)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ bundler
34
+ drgdsl!
35
+ minitest
36
+ pry
37
+ pry-byebug
38
+ pry-doc
39
+ rake
40
+
41
+ BUNDLED WITH
42
+ 1.16.1
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Rathesan Iyadurai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # DrgDSL
2
+
3
+ ## Installation
4
+
5
+ Add this line to your application's Gemfile:
6
+
7
+ ```ruby
8
+ gem 'drgdsl', git: 'git@192.168.145.133:gems/drgdsl.git'
9
+ ```
10
+
11
+ And then execute:
12
+
13
+ ```
14
+ $ bundle
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ From Ruby
20
+
21
+ ```ruby
22
+ # Use .parse to get an AST Object
23
+ DrgDSL.parse("PDX IN TABLES( A0801PXA, A0802PXA, A0803PXA, A0804PXA )")
24
+ # => #<DrgDSL::Ast::BasicExpression:0x00007f9ad8e29558 @condition=#<DrgDSL::Ast::TableCondition:0x00007f9ad8e2ae58 @comparison=nil, @op="in tables", @tables=["A0801PXA", "A0802PXA", "A0803PXA", "A0804PXA"]>, @variable=#<DrgDSL::Ast::Variable:0x00007f9ad8e30010 @name="PDX">>
25
+
26
+ # Use .json to get a JSON representation of the AST
27
+ DrgDSL.json("PDX IN TABLES( A0801PXA, A0802PXA, A0803PXA, A0804PXA )")
28
+ # => "{\"basic\":{\"var\":{\"name\":\"PDX\"},\"condition\":{\"table_condition\":{\"op\":\"in tables\",\"tables\":[\"A0801PXA\",\"A0802PXA\",\"A0803PXA\",\"A0804PXA\"],\"comparison\":null}}}}"
29
+ ```
30
+
31
+ From the CLI
32
+
33
+ ```
34
+ $ drgdsl "PDX IN TABLES( A0801PXA, A0802PXA, A0803PXA, A0804PXA )"
35
+
36
+ {
37
+ "basic": {
38
+ "condition": {
39
+ "table_condition": {
40
+ "comparison": null,
41
+ "op": "in tables",
42
+ "tables": [
43
+ "A0801PXA",
44
+ "A0802PXA",
45
+ "A0803PXA",
46
+ "A0804PXA"
47
+ ]
48
+ }
49
+ },
50
+ "var": {
51
+ "name": "PDX"
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### Traversing the AST
58
+
59
+ DrgDSL comes with a mixin that provides AST visit hooks. See `DrgDSL::Visitor`.
60
+
61
+ ## Development
62
+
63
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
64
+
65
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
66
+
67
+ ## License
68
+
69
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "drgdsl"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "pry"
14
+ Pry.start(DrgDSL)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/drgdsl.gemspec ADDED
@@ -0,0 +1,41 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "drgdsl/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "drgdsl"
8
+ spec.version = DrgDSL::VERSION
9
+ spec.authors = ["SwissDRG AG"]
10
+ spec.email = ["rathesan.iyadurai@swissdrg.org"]
11
+
12
+ spec.summary = %q{DrgDSL}
13
+ spec.homepage = "https://www.swissdrg.org"
14
+ spec.license = "MIT"
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ # if spec.respond_to?(:metadata)
19
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
20
+ # else
21
+ # raise "RubyGems 2.0 or newer is required to protect against " \
22
+ # "public gem pushes."
23
+ # end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
+ f.match(%r{^(test|spec|features)/})
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_dependency 'parslet', '~> 1.8.2'
33
+ spec.add_dependency 'oj', '~> 3.5.0'
34
+
35
+ spec.add_development_dependency "bundler"
36
+ spec.add_development_dependency "rake"
37
+ spec.add_development_dependency "minitest"
38
+ spec.add_development_dependency "pry"
39
+ spec.add_development_dependency "pry-byebug"
40
+ spec.add_development_dependency "pry-doc"
41
+ end
data/exe/drgdsl ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require_relative '../lib/drgdsl'
5
+
6
+ $options = {}
7
+ option_parser = OptionParser.new do |opts|
8
+ opts.on('--compact') do
9
+ $options[:compact] = true
10
+ end
11
+
12
+ opts.on_tail('-h', '--help', 'Show this message') do
13
+ puts opts
14
+ exit
15
+ end
16
+ end
17
+
18
+ HELP = option_parser.help
19
+ option_parser.parse!
20
+
21
+ input = if (arg = ARGV.first).is_a? String
22
+ arg
23
+ else
24
+ ARGF.read
25
+ end
26
+
27
+ begin
28
+ json = DrgDSL.json(input)
29
+ rescue => e
30
+ puts e.message
31
+ exit(1)
32
+ end
33
+
34
+ if $options[:compact]
35
+ puts json
36
+ else
37
+ # Forgive me Matz for I have sinned
38
+ system %{echo '#{json}' | python -m json.tool}
39
+ end
data/lib/drgdsl/ast.rb ADDED
@@ -0,0 +1,368 @@
1
+ module DrgDSL
2
+
3
+ # Nodes in DRG logic AST.
4
+ #
5
+ # @see AstBuilder
6
+ module Ast
7
+ module Node
8
+ class << self
9
+ def node_classes
10
+ @node_classes ||= []
11
+ end
12
+
13
+ def included(node_class)
14
+ node_classes << node_class
15
+ end
16
+
17
+ def type(node_class)
18
+ node_class.to_s[/\w+$/]
19
+ end
20
+ end
21
+
22
+ def accept(visitor)
23
+ visitor.visit(self)
24
+ end
25
+
26
+ # Class name of the concrete node without the module name. Do not
27
+ # override. Used by Visitor to determine which method to call.
28
+ def type
29
+ Node.type(self.class)
30
+ end
31
+
32
+ # @return [Boolean] whether this node represents a MDC equality check,
33
+ # e.g. "MDC = '01'"
34
+ def mdc_equality?
35
+ false
36
+ end
37
+
38
+ # @return [Boolean] whether this node represents a SEP equality check,
39
+ # e.g. "SEP = '07'"
40
+ def sep_equality?
41
+ false
42
+ end
43
+
44
+ # @return [Hash] Hash representation of the node
45
+ def to_hash
46
+ raise NotImplementedError
47
+ end
48
+ end
49
+
50
+ # Represents a list of expressions joined with an "OR",
51
+ # e.g., "exp1 or exp2 or exp3 ..."
52
+ class Expression
53
+ include Node
54
+
55
+ attr_reader :expressions
56
+
57
+ def initialize(expressions)
58
+ @expressions = expressions
59
+ end
60
+
61
+ # @return [Boolean] whether this expression only contains SEP equality
62
+ # checks.
63
+ def only_sep_equality_nodes?
64
+ expressions.all?(&:sep_equality?)
65
+ end
66
+
67
+ # @return [Boolean] whether this expression only contains MDC equality
68
+ # checks.
69
+ def only_mdc_equality_nodes?
70
+ expressions.all?(&:mdc_equality?)
71
+ end
72
+
73
+ def to_hash
74
+ { or: [expressions.map(&:to_hash)] }
75
+ end
76
+ end
77
+
78
+ # Represents a list of expressions joined with an "AND",
79
+ # e.g., "exp1 and exp2 and exp3 ..."
80
+ class AndExpression
81
+ include Node
82
+
83
+ attr_reader :expressions
84
+
85
+ def initialize(expressions)
86
+ @expressions = expressions
87
+ end
88
+
89
+ def to_hash
90
+ { and: [ expressions.map(&:to_hash) ] }
91
+ end
92
+ end
93
+
94
+ # One expression surrounded by parentheses, e.g., "(exp)"
95
+ class ParenExpression
96
+ include Node
97
+
98
+ attr_reader :expression
99
+
100
+ def initialize(expression)
101
+ @expression = expression
102
+ end
103
+
104
+ def to_hash
105
+ { paren: expression.to_hash }
106
+ end
107
+ end
108
+
109
+ # A NotExpression is a logic text of the form
110
+ # "not (expression)"
111
+ class NotExpression
112
+ include Node
113
+
114
+ attr_reader :expression
115
+
116
+ def initialize(expression)
117
+ @expression = expression
118
+ end
119
+
120
+ def to_hash
121
+ { not: expression.to_hash }
122
+ end
123
+ end
124
+
125
+ # "Komplizierende_Prozeduren", "Dialyse", ...
126
+ class FunctionCall
127
+ include Node
128
+
129
+ attr_reader :name
130
+
131
+ def initialize(name)
132
+ @name = name.to_s.strip
133
+ end
134
+
135
+ def to_hash
136
+ { function_call: name }
137
+ end
138
+ end
139
+
140
+ # A drg link originates from the grammar rule basic_or_drg_link
141
+ # This rule is of the form "variable (basic_expression | drg_link)"
142
+ # therefore a DrgLink has a variable and a name
143
+ class DrgLink
144
+ include Node
145
+
146
+ attr_reader :variable, :name
147
+
148
+ # @param name [String] either a drg or adrg name
149
+ def initialize(name:, variable:)
150
+ @name = name.to_s.strip.upcase
151
+ @variable = variable
152
+ end
153
+
154
+ def to_hash
155
+ { drg_link: { var: variable.to_hash, name: name } }
156
+ end
157
+ end
158
+
159
+ # A BasicExpression consists of a variable and a condition
160
+ # (because every variant of condition has a variable)
161
+ class BasicExpression
162
+ include Node
163
+
164
+ attr_reader :variable, :condition
165
+
166
+ # @param variable [Variable]
167
+ # @param condition [Comparison|UnaryCondition|TableCondition|Empty]
168
+ def initialize(variable:, condition:)
169
+ @variable = variable
170
+ @condition = condition
171
+ end
172
+
173
+ def mdc_equality?
174
+ variable.name == "MDC" && condition.op == "="
175
+ end
176
+
177
+ def sep_equality?
178
+ variable.name == "SEP" && condition.op == "="
179
+ end
180
+
181
+ def to_hash
182
+ { basic: { var: variable.to_hash, condition: condition.to_hash } }
183
+ end
184
+ end
185
+
186
+ # A comparison is either from a table_condition, a condition or a date_expression
187
+ class Comparison
188
+ include Node
189
+
190
+ attr_reader :op, :value, :table_condition
191
+
192
+ def initialize(op:, value:, table_condition: nil)
193
+ @op = op.to_s.strip
194
+ @value = value
195
+ @table_condition = table_condition
196
+ end
197
+
198
+ def to_hash
199
+ {
200
+ comparison: {
201
+ op: op,
202
+ value: value.to_hash,
203
+ table_condition: table_condition&.to_hash
204
+ }
205
+ }
206
+ end
207
+ end
208
+
209
+ # Has a certain operator (i.e. not / different) and a condition
210
+ #
211
+ # "not < 3"
212
+ class UnaryCondition
213
+ include Node
214
+
215
+ attr_reader :op, :condition
216
+
217
+ # @param op [String]
218
+ def initialize(op:, condition:)
219
+ @op = op.to_s.downcase
220
+ @condition = condition
221
+ end
222
+
223
+ def to_hash
224
+ {
225
+ unary_condition: {
226
+ op: op,
227
+ condition: condition.to_hash
228
+ }
229
+ }
230
+ end
231
+ end
232
+
233
+ class Empty
234
+ include Node
235
+
236
+ def to_hash
237
+ { empty: nil }
238
+ end
239
+ end
240
+
241
+ # There are 3 variants of table conditions (in_table, in_tables, all_in_table)
242
+ # A table condition can have an optional comparison.
243
+ # Tables refers to the table names (the internal names)
244
+ class TableCondition
245
+ IN_TABLE = 'in table'.freeze
246
+ IN_TABLES = 'in tables'.freeze
247
+ ALL_IN_TABLE = 'all in table'.freeze
248
+
249
+ include Node
250
+
251
+ attr_reader :op, :tables, :comparison
252
+
253
+ # @param op [String] IN_TABLE, IN_TABLES or ALL_IN_TABLE
254
+ # @param tables [Array<String>] one or more table names
255
+ def initialize(op:, tables:, comparison: nil)
256
+ @op = op
257
+ @tables = Array(tables).map(&:to_s)
258
+ @comparison = comparison
259
+ end
260
+
261
+ def to_hash
262
+ {
263
+ table_condition: {
264
+ op: op,
265
+ tables: tables,
266
+ comparison: comparison&.to_hash
267
+ }
268
+ }
269
+ end
270
+ end
271
+
272
+ # INFO: srg (prozedur) l (links) r (rechts) b(beidseitig)
273
+ class SrglrbTableCondition
274
+ include Node
275
+
276
+ attr_reader :variable, :condition
277
+
278
+ def initialize(variable:, condition:)
279
+ @variable = variable
280
+ @condition = condition
281
+ end
282
+
283
+ def to_hash
284
+ {
285
+ srglrb_table_condition: {
286
+ var: variable.to_hash,
287
+ condition: condition.to_hash
288
+ }
289
+ }
290
+ end
291
+ end
292
+
293
+ class DateExpression
294
+ include Node
295
+
296
+ attr_reader :left_variable,
297
+ :right_variable,
298
+ :left_condition,
299
+ :right_condition,
300
+ :comparison,
301
+ :opd
302
+
303
+ # @param opd [String] E.g. "opd4" => "mind. 4 erfüllen" query
304
+ def initialize(left_variable:,
305
+ right_variable: nil,
306
+ left_condition:,
307
+ right_condition: nil,
308
+ comparison:,
309
+ opd:
310
+ )
311
+
312
+ @left_variable = left_variable
313
+ @right_variable = right_variable
314
+ @left_condition = left_condition
315
+ @right_condition = right_condition
316
+ @comparison = comparison
317
+ @opd = opd
318
+ end
319
+
320
+ def to_hash
321
+ {
322
+ opd: {
323
+ var: opd,
324
+ left_var: left_variable.to_hash,
325
+ left_condition: left_condition.to_hash,
326
+ right_var: right_variable&.to_hash,
327
+ right_condition: right_condition&.to_hash,
328
+ comparison: comparison.to_hash
329
+ }
330
+ }
331
+ end
332
+ end
333
+
334
+ # "PDX", "AGEYEARS", ...
335
+ class Variable
336
+ include Node
337
+
338
+ attr_reader :name
339
+
340
+ def initialize(name)
341
+ @name = name.to_s.strip
342
+ end
343
+
344
+ def to_hash
345
+ { name: name }
346
+ end
347
+ end
348
+
349
+ # "3", "'MDC'", ...
350
+ class Constant
351
+ include Node
352
+
353
+ attr_reader :value
354
+
355
+ def initialize(value)
356
+ @value = value.to_s.strip.tr("'", '')
357
+ end
358
+
359
+ def to_s
360
+ value.to_s
361
+ end
362
+
363
+ def to_hash
364
+ { constant: { value: value } }
365
+ end
366
+ end
367
+ end
368
+ end