drgdsl 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +42 -0
- data/LICENSE.txt +21 -0
- data/README.md +69 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/drgdsl.gemspec +41 -0
- data/exe/drgdsl +39 -0
- data/lib/drgdsl/ast.rb +368 -0
- data/lib/drgdsl/ast_builder.rb +269 -0
- data/lib/drgdsl/parser.rb +256 -0
- data/lib/drgdsl/version.rb +3 -0
- data/lib/drgdsl/visitor.rb +17 -0
- data/lib/drgdsl.rb +23 -0
- metadata +174 -0
@@ -0,0 +1,269 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
module DrgDSL
|
4
|
+
class UnknownCstError < StandardError
|
5
|
+
attr_reader :cst, :result
|
6
|
+
|
7
|
+
# @param cst [Hash] CST obtained by parser
|
8
|
+
# @param result [Object] whatever the AstBuilder was able to generate
|
9
|
+
def initialize(cst, result)
|
10
|
+
@cst = cst
|
11
|
+
@result = result
|
12
|
+
end
|
13
|
+
|
14
|
+
def message
|
15
|
+
<<~EOM
|
16
|
+
Don't know how to build AST from this CST:
|
17
|
+
|
18
|
+
#{pretty cst}
|
19
|
+
|
20
|
+
Intermediate result:
|
21
|
+
|
22
|
+
#{pretty result}
|
23
|
+
EOM
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# @return [String] nicely formatted string for enhanced readability.
|
29
|
+
def pretty(object)
|
30
|
+
PP.pp object, ''
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class AstBuilder < Parslet::Transform
|
35
|
+
|
36
|
+
# @param cst [Hash] CST-ish hash obtained from DrgParser.
|
37
|
+
# @return [Node] AST
|
38
|
+
# @raise [UnknownCstError] when CST could not be converted to a correct
|
39
|
+
# AST.
|
40
|
+
def self.build(cst)
|
41
|
+
ast = new.apply(cst)
|
42
|
+
raise UnknownCstError.new(cst, ast) if broken_ast?(ast)
|
43
|
+
ast
|
44
|
+
end
|
45
|
+
|
46
|
+
# Did we manage to build a correct AST?
|
47
|
+
#
|
48
|
+
# @return [Boolean]
|
49
|
+
def self.broken_ast?(ast)
|
50
|
+
!ast.is_a?(Ast::Node)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Parslet transformation rules that transform a nested CST-ish hash
|
54
|
+
# generated by DrgParser into a AST.
|
55
|
+
#
|
56
|
+
# Check out https://kschiess.github.io/parslet/transform.html for more
|
57
|
+
# details on transformations.
|
58
|
+
|
59
|
+
# Only one and-expression means there's no RHS of an "oder" expression, so
|
60
|
+
# we just go downwards.
|
61
|
+
rule(exp: subtree(:and_exp)) { and_exp }
|
62
|
+
|
63
|
+
# Two or more and-expressions will be joined with an "oder". So and has
|
64
|
+
# higher precedence.
|
65
|
+
rule(exp: sequence(:and_exp)) do
|
66
|
+
Ast::Expression.new(and_exp)
|
67
|
+
end
|
68
|
+
|
69
|
+
# In case of no RHS we simply go downwards.
|
70
|
+
rule(and_exp: subtree(:simple)) do
|
71
|
+
simple
|
72
|
+
end
|
73
|
+
|
74
|
+
rule(and_exp: sequence(:simple)) do
|
75
|
+
Ast::AndExpression.new(simple)
|
76
|
+
end
|
77
|
+
|
78
|
+
rule(simple: subtree(:simple)) { simple }
|
79
|
+
|
80
|
+
rule(paren_exp: subtree(:exp)) do
|
81
|
+
Ast::ParenExpression.new(exp)
|
82
|
+
end
|
83
|
+
|
84
|
+
# "NOT ( ... )" -> "nicht [...]"
|
85
|
+
rule(not_exp: subtree(:exp)) do
|
86
|
+
Ast::NotExpression.new(exp)
|
87
|
+
end
|
88
|
+
|
89
|
+
# "EMPTY()" -> "leer"
|
90
|
+
rule(empty: simple(:empty)) do
|
91
|
+
Ast::Empty.new
|
92
|
+
end
|
93
|
+
|
94
|
+
# "NOT IN TABLE ... -> "nicht in Tabelle ...""
|
95
|
+
rule(
|
96
|
+
unary_condition: {
|
97
|
+
keyword: simple(:keyword),
|
98
|
+
condition: subtree(:condition)
|
99
|
+
}
|
100
|
+
) do
|
101
|
+
Ast::UnaryCondition.new(op: keyword, condition: condition)
|
102
|
+
end
|
103
|
+
|
104
|
+
rule(
|
105
|
+
variable: subtree(:inner_variable)
|
106
|
+
) do
|
107
|
+
inner_variable
|
108
|
+
end
|
109
|
+
|
110
|
+
# "AGEYEARS" --> "Alter"
|
111
|
+
rule(inner_variable: subtree(:variable)) do
|
112
|
+
Ast::Variable.new(variable)
|
113
|
+
end
|
114
|
+
|
115
|
+
# "42" -> "42"
|
116
|
+
rule(constant: simple(:constant)) do
|
117
|
+
Ast::Constant.new(constant)
|
118
|
+
end
|
119
|
+
|
120
|
+
# "DRG (F89)" -> "Definition der DRG (F98)"
|
121
|
+
rule(
|
122
|
+
basic_or_drg_link: {
|
123
|
+
variable: subtree(:var),
|
124
|
+
drg_link: {
|
125
|
+
drg: simple(:drg)
|
126
|
+
}
|
127
|
+
}
|
128
|
+
) do
|
129
|
+
Ast::DrgLink.new(name: drg, variable: var)
|
130
|
+
end
|
131
|
+
|
132
|
+
# "PDX IN TABLE ..." -> "Hauptdiagnose in Tabelle ..."
|
133
|
+
rule(
|
134
|
+
basic_or_drg_link: {
|
135
|
+
variable: subtree(:var),
|
136
|
+
condition: subtree(:condition)
|
137
|
+
}
|
138
|
+
) do
|
139
|
+
Ast::BasicExpression.new(variable: var, condition: condition)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Go down to in-table, in-tables or all-in-tables condition.
|
143
|
+
rule(table_condition: subtree(:table_condition)) { table_condition }
|
144
|
+
|
145
|
+
# "in table (<reftable>)" -> "in Tabelle <external table name>"
|
146
|
+
rule(in_table: simple(:table)) do
|
147
|
+
Ast::TableCondition.new(
|
148
|
+
op: Ast::TableCondition::IN_TABLE,
|
149
|
+
tables: table
|
150
|
+
)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Multi-tables expression is deliberately mapped to one external table for
|
154
|
+
# enhanced readability.
|
155
|
+
#
|
156
|
+
# "in tables (<ref1>, <ref2>, ...)" -> "in Tabelle <external table name>"
|
157
|
+
rule(
|
158
|
+
in_tables: {
|
159
|
+
tables: sequence(:tables)
|
160
|
+
}
|
161
|
+
) do
|
162
|
+
Ast::TableCondition.new(
|
163
|
+
op: Ast::TableCondition::IN_TABLES,
|
164
|
+
tables: tables
|
165
|
+
)
|
166
|
+
end
|
167
|
+
|
168
|
+
rule(
|
169
|
+
comparison: subtree(:comparison)
|
170
|
+
) do
|
171
|
+
comparison
|
172
|
+
end
|
173
|
+
|
174
|
+
rule(
|
175
|
+
op: simple(:op),
|
176
|
+
value: subtree(:value),
|
177
|
+
table_condition: subtree(:table_condition)
|
178
|
+
) do
|
179
|
+
Ast::Comparison.new(
|
180
|
+
op: op,
|
181
|
+
value: value,
|
182
|
+
table_condition: table_condition
|
183
|
+
)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Handle dangling comparison after table expressions.
|
187
|
+
rule(
|
188
|
+
in_table: {
|
189
|
+
table: simple(:table),
|
190
|
+
comparison: subtree(:comparison)
|
191
|
+
}
|
192
|
+
) do
|
193
|
+
Ast::TableCondition.new(
|
194
|
+
op: Ast::TableCondition::IN_TABLE,
|
195
|
+
tables: table,
|
196
|
+
comparison: comparison
|
197
|
+
)
|
198
|
+
end
|
199
|
+
|
200
|
+
rule(
|
201
|
+
in_tables: {
|
202
|
+
tables: sequence(:tables),
|
203
|
+
comparison: subtree(:comparison)
|
204
|
+
}
|
205
|
+
) do
|
206
|
+
Ast::TableCondition.new(
|
207
|
+
op: Ast::TableCondition::IN_TABLES,
|
208
|
+
tables: tables,
|
209
|
+
comparison: comparison
|
210
|
+
)
|
211
|
+
end
|
212
|
+
|
213
|
+
rule(table: simple(:table)) { table.to_s.strip }
|
214
|
+
|
215
|
+
# "SRGLRB IN TABLE( CO3021ORS )" -> "Beidseitige Prozedur in Tabelle CO3021ORS"
|
216
|
+
rule(
|
217
|
+
srglrb_table_condition: {
|
218
|
+
variable: subtree(:var),
|
219
|
+
condition: subtree(:condition)
|
220
|
+
}
|
221
|
+
) do
|
222
|
+
Ast::SrglrbTableCondition.new(variable: var, condition: condition)
|
223
|
+
end
|
224
|
+
|
225
|
+
# "OPD2 in (SRG in table ...) > 0" -> "Mindestens zwei Behandlungen, die mindestens einen Tag auseinander liegen in [Prozedur in Tabelle ...]" ¯\_(ツ)_/¯
|
226
|
+
rule(
|
227
|
+
date_exp: {
|
228
|
+
opd: simple(:opd),
|
229
|
+
variable: subtree(:left_var),
|
230
|
+
left_table_condition: subtree(:left_table_condition),
|
231
|
+
and_table_condition: {
|
232
|
+
variable: subtree(:right_var),
|
233
|
+
right_table_condition: subtree(:right_table_condition)
|
234
|
+
},
|
235
|
+
comparison: subtree(:comparison)
|
236
|
+
}
|
237
|
+
) do
|
238
|
+
Ast::DateExpression.new(
|
239
|
+
left_variable: left_var,
|
240
|
+
right_variable: right_var,
|
241
|
+
left_condition: left_table_condition,
|
242
|
+
right_condition: right_table_condition,
|
243
|
+
comparison: comparison,
|
244
|
+
opd: opd
|
245
|
+
)
|
246
|
+
end
|
247
|
+
|
248
|
+
rule(
|
249
|
+
date_exp: {
|
250
|
+
opd: simple(:opd),
|
251
|
+
variable: subtree(:left_var),
|
252
|
+
left_table_condition: subtree(:left_table_condition),
|
253
|
+
comparison: subtree(:comparison)
|
254
|
+
}
|
255
|
+
) do
|
256
|
+
Ast::DateExpression.new(
|
257
|
+
left_variable: left_var,
|
258
|
+
left_condition: left_table_condition,
|
259
|
+
comparison: comparison,
|
260
|
+
opd: opd
|
261
|
+
)
|
262
|
+
end
|
263
|
+
|
264
|
+
# "Vierzeitige_bestimmte_OR" -> "Vierzeitige bestimmte OR-Prozeduren"
|
265
|
+
rule(function_call: simple(:fname)) do
|
266
|
+
Ast::FunctionCall.new(fname)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
module DrgDSL
|
2
|
+
class ParserError < StandardError
|
3
|
+
attr_reader :parslet_error, :input
|
4
|
+
|
5
|
+
def initialize(parslet_error:, input:)
|
6
|
+
@parslet_error = parslet_error
|
7
|
+
@input = input
|
8
|
+
end
|
9
|
+
|
10
|
+
def message
|
11
|
+
%{Failed to parse "#{input}"\nParslet::ParseFailed: #{parslet_error}}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Used to parse logic expressions in flowchart decision tree. Based on the
|
16
|
+
# Grouper's DRG parser[0], only difference being that this parser doesn't
|
17
|
+
# know about statements, since they do not occur in the decision nodes.
|
18
|
+
#
|
19
|
+
# To learn more about Parslet see the parser[1] and transform[2] docs.
|
20
|
+
#
|
21
|
+
# The full syntax of the logic is described in
|
22
|
+
# documents/Spec-Handbuch_v2.2.3.pdf in chapter 5.5.
|
23
|
+
#
|
24
|
+
# [0] https://github.com/swissdrg/grouper/blob/master/src/main/common/org/swissdrg/grouper/parser/DrgParser.jj
|
25
|
+
# [1] https://kschiess.github.io/parslet/parser.html
|
26
|
+
# [2] https://kschiess.github.io/parslet/transform.html
|
27
|
+
class Parser < Parslet::Parser
|
28
|
+
|
29
|
+
# @param expression [String]
|
30
|
+
# @return [Ast::Node]
|
31
|
+
def self.parse(expression)
|
32
|
+
AstBuilder.build new.parse(expression.to_s.strip)
|
33
|
+
rescue Parslet::ParseFailed => e
|
34
|
+
raise ParserError.new(parslet_error: e, input: expression)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Case-insensitive string matching
|
38
|
+
#
|
39
|
+
# https://kschiess.github.io/parslet/tricks.html
|
40
|
+
def stri(str)
|
41
|
+
key_chars = str.split(//)
|
42
|
+
key_chars.map { |c| match["#{c.upcase}#{c.downcase}"] }.inject(:>>)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Tokens
|
46
|
+
rule(:star) { str('*') >> sp? }
|
47
|
+
rule(:k_or) { stri('or') }
|
48
|
+
rule(:k_and) { stri('and') }
|
49
|
+
rule(:k_not) { stri('not') }
|
50
|
+
rule(:k_different) { stri('different') }
|
51
|
+
rule(:k_empty) { stri('empty') }
|
52
|
+
rule(:k_in) { stri('in') }
|
53
|
+
rule(:mdc) { stri('mdc') }
|
54
|
+
rule(:in_table) { stri('in table') }
|
55
|
+
rule(:in_tables) { stri('in tables') }
|
56
|
+
rule(:all_in_table) { stri('all in table') }
|
57
|
+
rule(:lpar) { str('(') >> sp? }
|
58
|
+
rule(:rpar) { sp? >> str(')') }
|
59
|
+
rule(:underscore) { str('_') }
|
60
|
+
rule(:comma) { str(',') }
|
61
|
+
rule(:quote) { str("'") }
|
62
|
+
rule(:assign) { str(':=') }
|
63
|
+
rule(:letter) { match('[a-zA-Z]') }
|
64
|
+
rule(:digit) { match('[0-9]') }
|
65
|
+
rule(:number) { digit.repeat(1) }
|
66
|
+
|
67
|
+
rule(:comparison_operator) do
|
68
|
+
stri(">=") |
|
69
|
+
stri(">") |
|
70
|
+
stri("<=") |
|
71
|
+
stri("<") |
|
72
|
+
stri("=")
|
73
|
+
end
|
74
|
+
|
75
|
+
# name
|
76
|
+
# ::= letter (letter | number | underscore)*
|
77
|
+
rule(:name) { letter >> (letter | number | underscore).repeat }
|
78
|
+
|
79
|
+
rule(:variable_name) do
|
80
|
+
stri("PDX") |
|
81
|
+
stri("SDX") >> number.maybe |
|
82
|
+
stri("DDX") >> number.maybe |
|
83
|
+
stri("SRG") >> number.maybe |
|
84
|
+
stri("SRGB") >> number.maybe |
|
85
|
+
stri("SRGR") >> number.maybe |
|
86
|
+
stri("SRGL") >> number.maybe |
|
87
|
+
stri("SRGN") >> number.maybe |
|
88
|
+
stri("SRGU") >> number.maybe |
|
89
|
+
stri("AGEYEARS") |
|
90
|
+
stri("AGEDAYS") |
|
91
|
+
stri("SEX") |
|
92
|
+
stri("ADM_WT") |
|
93
|
+
stri("HMV") |
|
94
|
+
stri("MDC") |
|
95
|
+
stri("DRG") |
|
96
|
+
stri("ADRG") |
|
97
|
+
stri("GST") |
|
98
|
+
stri("LOS") |
|
99
|
+
stri("PCCL") |
|
100
|
+
stri("SEP") |
|
101
|
+
stri("ENTRY") |
|
102
|
+
stri("ADM_MODE") |
|
103
|
+
stri("VOID") |
|
104
|
+
stri("GESTAGE")
|
105
|
+
end
|
106
|
+
|
107
|
+
# srglrb
|
108
|
+
# ::= 'SRGLRB' number{0,1}
|
109
|
+
rule(:srglrb) { stri('SRGLRB') >> number.maybe }
|
110
|
+
|
111
|
+
# opd
|
112
|
+
# ::= 'OPD' number{0,1}
|
113
|
+
rule(:opd) { stri('OPD') >> number.maybe }
|
114
|
+
|
115
|
+
# By "double nesting" the variable, the AST builder (a parslet transformer)
|
116
|
+
# can use subtree(:variable) to retrieve a variable node, given there's a
|
117
|
+
# rule for inner_variable that creates said node.
|
118
|
+
#
|
119
|
+
# variable
|
120
|
+
# ::= variable_name
|
121
|
+
rule(:variable) { (star.maybe >> variable_name).as(:inner_variable).as(:variable) }
|
122
|
+
|
123
|
+
# constant
|
124
|
+
# ::= quote{0,1} (number | name) name{0,1} quote{0,1}
|
125
|
+
rule(:constant) do
|
126
|
+
(quote.maybe >> ((number | name) >> name.maybe) >> quote.maybe).as(:constant)
|
127
|
+
end
|
128
|
+
|
129
|
+
# value
|
130
|
+
# ::= variable | constant
|
131
|
+
rule(:value) { (variable | constant).as(:value) }
|
132
|
+
|
133
|
+
# function_call
|
134
|
+
# ::= name
|
135
|
+
rule(:function_call) do
|
136
|
+
name.as(:function_call)
|
137
|
+
end
|
138
|
+
|
139
|
+
# empty
|
140
|
+
# ::= empty '()'
|
141
|
+
rule(:empty) do
|
142
|
+
k_empty >> sp? >> (lpar >> rpar).maybe
|
143
|
+
end
|
144
|
+
|
145
|
+
# table_condition
|
146
|
+
# ::= (in_table '(' name ')' comparison{0,1})
|
147
|
+
# | in_tables '(' name (comma name)* ')' comparison{0,1}
|
148
|
+
# | all_in_table '(' name ')'
|
149
|
+
rule(:table_condition) do
|
150
|
+
(in_table >> sp? >> lpar >> name.as(:table) >> rpar >> sp? >> comparison.as(:comparison).maybe).as(:in_table) |
|
151
|
+
(in_tables >> sp? >> lpar >> (name.as(:table) >> (sp? >> comma >> sp? >> name.as(:table)).repeat).as(:tables) >> rpar >> sp? >> comparison.as(:comparison).maybe).as(:in_tables) |
|
152
|
+
(all_in_table >> sp? >> lpar >> name.as(:table) >> rpar).as(:all_in_table)
|
153
|
+
end
|
154
|
+
|
155
|
+
# comparison
|
156
|
+
# ::= comparison_operator value table_condition{0,1}
|
157
|
+
rule(:comparison) do
|
158
|
+
comparison_operator.as(:op) >> sp? >>
|
159
|
+
value >> sp? >> table_condition.maybe.as(:table_condition)
|
160
|
+
end
|
161
|
+
|
162
|
+
# unary_condition
|
163
|
+
# ::= (not | different) condition
|
164
|
+
rule(:unary_condition) do
|
165
|
+
(k_not | k_different).as(:keyword) >> sp? >> condition.as(:condition)
|
166
|
+
end
|
167
|
+
|
168
|
+
# condition
|
169
|
+
# ::= comparison
|
170
|
+
# | unary_operator
|
171
|
+
# | empty
|
172
|
+
# | table_condition
|
173
|
+
rule(:condition) do
|
174
|
+
comparison.as(:comparison) | unary_condition.as(:unary_condition) | empty.as(:empty) | table_condition.as(:table_condition)
|
175
|
+
end
|
176
|
+
|
177
|
+
# expression
|
178
|
+
# ::= and_expression (or and_expression)*
|
179
|
+
rule(:expression) do
|
180
|
+
(sp? >> and_expression.as(:and_exp) >> (sp? >> k_or >> sp? >> and_expression.as(:and_exp)).repeat >> sp?).as(:exp)
|
181
|
+
end
|
182
|
+
|
183
|
+
# and_expression
|
184
|
+
# ::= simple_expression (and simple_expression)*
|
185
|
+
rule(:and_expression) do
|
186
|
+
simple_expression.as(:simple) >> (sp? >> k_and >> sp? >> simple_expression.as(:simple)).repeat
|
187
|
+
end
|
188
|
+
|
189
|
+
# simple_expression
|
190
|
+
# ::= '(' expression ')'
|
191
|
+
# | basic_or_drg_link
|
192
|
+
# | srglrb_table_condition
|
193
|
+
# | date_expression
|
194
|
+
# | not_expression
|
195
|
+
# | function_call
|
196
|
+
rule(:simple_expression) do
|
197
|
+
(lpar >> expression >> rpar).as(:paren_exp) |
|
198
|
+
basic_or_drg_link |
|
199
|
+
srglrb_table_condition |
|
200
|
+
date_expression |
|
201
|
+
not_expression |
|
202
|
+
function_call
|
203
|
+
end
|
204
|
+
|
205
|
+
# date_expression
|
206
|
+
# ::= opd in '(' variable table_condition (and variable table_ondition){0,1} ')' comparison
|
207
|
+
rule(:date_expression) do
|
208
|
+
(opd.as(:opd) >> sp? >> k_in >> sp? >> lpar >> variable >> sp? >> table_condition.as(:left_table_condition) >> (sp? >> k_and >> sp? >> variable >> sp? >> table_condition.as(:right_table_condition)).as(:and_table_condition).maybe >> rpar >> sp? >> comparison.as(:comparison)).as(:date_exp)
|
209
|
+
end
|
210
|
+
|
211
|
+
# srglrb_table_condition
|
212
|
+
# ::= star{0,1} srglrb condition
|
213
|
+
rule(:srglrb_table_condition) do
|
214
|
+
((star.maybe >> srglrb).as(:inner_variable).as(:variable) >> sp? >> condition.as(:condition)).as(:srglrb_table_condition)
|
215
|
+
end
|
216
|
+
|
217
|
+
# not_expression
|
218
|
+
# ::= not '(' expression ')'
|
219
|
+
rule(:not_expression) do
|
220
|
+
(k_not >> sp? >> lpar >> expression >> rpar).as(:not_exp)
|
221
|
+
end
|
222
|
+
|
223
|
+
# basic_or_drg_link
|
224
|
+
# ::= variable (basic_expression | drg_link)
|
225
|
+
rule(:basic_or_drg_link) do
|
226
|
+
(variable >> sp? >> (basic_expression | drg_link)).as(:basic_or_drg_link)
|
227
|
+
end
|
228
|
+
|
229
|
+
# basic_expression
|
230
|
+
# ::= condition
|
231
|
+
rule(:basic_expression) do
|
232
|
+
condition.as(:condition)
|
233
|
+
end
|
234
|
+
|
235
|
+
# drg_link
|
236
|
+
# ::= '(' name ')'
|
237
|
+
rule(:drg_link) do
|
238
|
+
(lpar >> name.as(:drg) >> rpar).as(:drg_link)
|
239
|
+
end
|
240
|
+
|
241
|
+
rule(:comment) do
|
242
|
+
str('/*') >> (str('*/').absent? >> any).repeat >> str('*/')
|
243
|
+
end
|
244
|
+
|
245
|
+
rule(:sp) do
|
246
|
+
(match("[ \t\r\n]") | comment).repeat(1)
|
247
|
+
end
|
248
|
+
|
249
|
+
# Optional whitespace rule. Unfortunately, skipping whitespace can't be
|
250
|
+
# automated:
|
251
|
+
# https://github.com/kschiess/parslet/issues/4://github.com/kschiess/parslet/issues/49
|
252
|
+
rule(:sp?) { sp.repeat }
|
253
|
+
|
254
|
+
rule(:root) { expression }
|
255
|
+
end
|
256
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module DrgDSL
|
2
|
+
module Visitor
|
3
|
+
def visit(n)
|
4
|
+
send("visit_#{n.type}", n)
|
5
|
+
end
|
6
|
+
|
7
|
+
Ast::Node.node_classes.each do |node_class|
|
8
|
+
define_method("visit_#{Ast::Node.type(node_class)}") do |n|
|
9
|
+
default_value
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Object] what the visit_<node_class> methods return by default.
|
14
|
+
def default_value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/drgdsl.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'oj'
|
2
|
+
require 'parslet'
|
3
|
+
|
4
|
+
require_relative "./drgdsl/version"
|
5
|
+
require_relative "./drgdsl/ast"
|
6
|
+
require_relative "./drgdsl/ast_builder"
|
7
|
+
require_relative "./drgdsl/parser"
|
8
|
+
require_relative "./drgdsl/visitor"
|
9
|
+
|
10
|
+
module DrgDSL
|
11
|
+
|
12
|
+
# @param input [String]
|
13
|
+
# @return [Ast::Node]
|
14
|
+
def self.parse(input)
|
15
|
+
Parser.parse(input)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param input [String]
|
19
|
+
# @return [String]
|
20
|
+
def self.json(input)
|
21
|
+
Oj.dump parse(input).to_hash, mode: :compat
|
22
|
+
end
|
23
|
+
end
|