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 +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
|