drgdsl 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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