drgdsl 1.0.0 → 1.1.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 +4 -4
- data/.rubocop.yml +133 -0
- data/Gemfile.lock +1 -1
- data/bin/html +56 -0
- data/exe/drgdsl +31 -8
- data/lib/drgdsl/ast.rb +7 -1
- data/lib/drgdsl/ast_builder.rb +6 -4
- data/lib/drgdsl/core_extensions.rb +49 -0
- data/lib/drgdsl/paren_cleaner.rb +58 -0
- data/lib/drgdsl/parser.rb +17 -13
- data/lib/drgdsl/pretty_printer.rb +208 -0
- data/lib/drgdsl/version.rb +1 -1
- data/lib/drgdsl/visitor.rb +86 -6
- data/lib/drgdsl.rb +56 -2
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9715079e711fd5f608888b4b6433221db58145b85d58562e5419170496201c1
|
4
|
+
data.tar.gz: 7c83b0126a22e86431e5d8ededa65b75cb982d8fdd8b1dd2db726308c94e5b25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8051b841182ad48e4e13f57bd06bfe999f694321c1abd3c88d16065a6149d0c80d357d6a6e75fd0d56f96356bc16fabaf85f321da6a7deb66d7510e8cb8695a9
|
7
|
+
data.tar.gz: af4454d07118b2966da5074332d7549fda5b9ffc3fafae56dd151f86d40342ae23be3d089967d68e0ddcf6d8c6a7c1d388fba19b6aa61718d72ad9d1dc9f4b29
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# https://github.com/bbatsov/rubocop/blob/master/config/default.yml
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
DisplayCopNames: true
|
5
|
+
DisplayStyleGuide: true
|
6
|
+
TargetRubyVersion: 2.5
|
7
|
+
Include:
|
8
|
+
- Rakefile
|
9
|
+
|
10
|
+
LineLength:
|
11
|
+
Max: 80
|
12
|
+
Exclude:
|
13
|
+
- test/**/**
|
14
|
+
- lib/drgdsl/parser.rb
|
15
|
+
- lib/drgdsl/ast_builer.rb
|
16
|
+
|
17
|
+
Layout/SpaceInsideStringInterpolation:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
Layout/EmptyLinesAroundBlockBody:
|
21
|
+
Enabled: false
|
22
|
+
|
23
|
+
Layout/EmptyLinesAroundClassBody:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
Layout/EmptyLinesAroundModuleBody:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
Layout/EmptyLineBetweenDefs:
|
30
|
+
NumberOfEmptyLines: [1, 2]
|
31
|
+
|
32
|
+
Layout/MultilineOperationIndentation:
|
33
|
+
EnforcedStyle: indented
|
34
|
+
|
35
|
+
Layout/AlignParameters:
|
36
|
+
EnforcedStyle: with_first_parameter
|
37
|
+
|
38
|
+
Layout/DotPosition:
|
39
|
+
Enabled: false
|
40
|
+
|
41
|
+
Style/PercentLiteralDelimiters:
|
42
|
+
Enabled: false
|
43
|
+
|
44
|
+
Style/StringLiterals:
|
45
|
+
Enabled: false
|
46
|
+
|
47
|
+
Style/StringLiteralsInInterpolation:
|
48
|
+
Enabled: false
|
49
|
+
|
50
|
+
Style/SingleLineBlockParams:
|
51
|
+
Enabled: false
|
52
|
+
|
53
|
+
Style/TrivialAccessors:
|
54
|
+
AllowPredicates: true
|
55
|
+
|
56
|
+
Style/Semicolon:
|
57
|
+
AllowAsExpressionSeparator: true
|
58
|
+
|
59
|
+
Style/PerlBackrefs:
|
60
|
+
Enabled: false
|
61
|
+
|
62
|
+
Style/SingleLineMethods:
|
63
|
+
Enabled: false
|
64
|
+
|
65
|
+
Style/Documentation:
|
66
|
+
Enabled: false
|
67
|
+
|
68
|
+
Style/RegexpLiteral:
|
69
|
+
Enabled: false
|
70
|
+
|
71
|
+
Style/CommandLiteral:
|
72
|
+
Enabled: false
|
73
|
+
|
74
|
+
Style/FormatString:
|
75
|
+
Enabled: false
|
76
|
+
|
77
|
+
Style/AsciiComments:
|
78
|
+
Enabled: false
|
79
|
+
|
80
|
+
Style/SymbolProc:
|
81
|
+
AutoCorrect: false
|
82
|
+
|
83
|
+
Style/EmptyMethod:
|
84
|
+
Enabled: false
|
85
|
+
|
86
|
+
Style/MultilineTernaryOperator:
|
87
|
+
Enabled: false
|
88
|
+
|
89
|
+
Style/DoubleNegation:
|
90
|
+
Enabled: false
|
91
|
+
|
92
|
+
Metrics/ClassLength:
|
93
|
+
Enabled: false
|
94
|
+
|
95
|
+
Metrics/ModuleLength:
|
96
|
+
Enabled: false
|
97
|
+
|
98
|
+
Metrics/MethodLength:
|
99
|
+
Enabled: false
|
100
|
+
|
101
|
+
Metrics/CyclomaticComplexity:
|
102
|
+
Enabled: false
|
103
|
+
|
104
|
+
Metrics/PerceivedComplexity:
|
105
|
+
Enabled: false
|
106
|
+
|
107
|
+
Metrics/AbcSize:
|
108
|
+
Enabled: false
|
109
|
+
|
110
|
+
Metrics/ParameterLists:
|
111
|
+
CountKeywordArgs: false
|
112
|
+
|
113
|
+
Metrics/BlockLength:
|
114
|
+
Enabled: false
|
115
|
+
|
116
|
+
Naming/HeredocDelimiterNaming:
|
117
|
+
Enabled: false
|
118
|
+
|
119
|
+
Naming/PredicateName:
|
120
|
+
Enabled: false
|
121
|
+
|
122
|
+
Naming/MethodName:
|
123
|
+
Enabled: false
|
124
|
+
|
125
|
+
Naming/UncommunicativeMethodParamName:
|
126
|
+
Enabled: false
|
127
|
+
|
128
|
+
Performance/RedundantBlockCall:
|
129
|
+
Enabled: false
|
130
|
+
|
131
|
+
Lint/UnusedMethodArgument:
|
132
|
+
Enabled: true
|
133
|
+
AutoCorrect: false
|
data/Gemfile.lock
CHANGED
data/bin/html
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Generates highlighted html snippets for a bunch of expressions for
|
4
|
+
# visualization purposes.
|
5
|
+
|
6
|
+
require "bundler/setup"
|
7
|
+
require "drgdsl"
|
8
|
+
|
9
|
+
logics = []
|
10
|
+
|
11
|
+
File.read('./test/expressions.txt', encoding: 'utf-8').split("\n\n").each_with_index do |exp, i|
|
12
|
+
html = DrgDSL.pretty_print(exp, output_format: :html)
|
13
|
+
logics << html
|
14
|
+
end
|
15
|
+
|
16
|
+
puts <<~HTML
|
17
|
+
<html>
|
18
|
+
|
19
|
+
<head>
|
20
|
+
<style>
|
21
|
+
.drgdsl-keyword {
|
22
|
+
color: blue;
|
23
|
+
}
|
24
|
+
|
25
|
+
.drgdsl-function {
|
26
|
+
color: green;
|
27
|
+
}
|
28
|
+
|
29
|
+
.drgdsl-op {
|
30
|
+
color: grey;
|
31
|
+
}
|
32
|
+
|
33
|
+
.drgdsl-table {
|
34
|
+
color: pink;
|
35
|
+
}
|
36
|
+
|
37
|
+
.drgdsl-variable {
|
38
|
+
color: black;
|
39
|
+
}
|
40
|
+
|
41
|
+
.drgdsl-constant {
|
42
|
+
color: purple;
|
43
|
+
}
|
44
|
+
|
45
|
+
.drgdsl-drg, .drgdsl-adrg {
|
46
|
+
color: black;
|
47
|
+
}
|
48
|
+
</style>
|
49
|
+
</head>
|
50
|
+
|
51
|
+
<body>
|
52
|
+
#{logics.join("\n")}
|
53
|
+
</body>
|
54
|
+
|
55
|
+
</html>
|
56
|
+
HTML
|
data/exe/drgdsl
CHANGED
@@ -9,6 +9,14 @@ option_parser = OptionParser.new do |opts|
|
|
9
9
|
$options[:compact] = true
|
10
10
|
end
|
11
11
|
|
12
|
+
opts.on('-o', '--output [OUTPUT]', %i[json html]) do |output|
|
13
|
+
$options[:output] = output
|
14
|
+
end
|
15
|
+
|
16
|
+
opts.on('--clean-parens', 'Remove redundant parentheses') do
|
17
|
+
$options[:clean_parens] = true
|
18
|
+
end
|
19
|
+
|
12
20
|
opts.on_tail('-h', '--help', 'Show this message') do
|
13
21
|
puts opts
|
14
22
|
exit
|
@@ -25,15 +33,30 @@ input = if (arg = ARGV.first).is_a? String
|
|
25
33
|
end
|
26
34
|
|
27
35
|
begin
|
28
|
-
|
36
|
+
case $options[:output]
|
37
|
+
when :json
|
38
|
+
json = DrgDSL.json(input)
|
39
|
+
|
40
|
+
if $options[:compact]
|
41
|
+
puts json
|
42
|
+
else
|
43
|
+
# Forgive me Matz for I have sinned
|
44
|
+
system %{echo '#{json}' | python -m json.tool}
|
45
|
+
end
|
46
|
+
when :html
|
47
|
+
puts DrgDSL.pretty_print(
|
48
|
+
input,
|
49
|
+
output_format: :html,
|
50
|
+
remove_redundant_parens: $options[:clean_parens]
|
51
|
+
)
|
52
|
+
else
|
53
|
+
puts DrgDSL.pretty_print(
|
54
|
+
input,
|
55
|
+
output_format: :bash,
|
56
|
+
remove_redundant_parens: $options[:clean_parens]
|
57
|
+
)
|
58
|
+
end
|
29
59
|
rescue => e
|
30
60
|
puts e.message
|
31
61
|
exit(1)
|
32
62
|
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
CHANGED
@@ -45,6 +45,12 @@ module DrgDSL
|
|
45
45
|
def to_hash
|
46
46
|
raise NotImplementedError
|
47
47
|
end
|
48
|
+
|
49
|
+
def pretty_print(**pretty_printer_options)
|
50
|
+
accept PrettyPrinter.new(**pretty_printer_options)
|
51
|
+
end
|
52
|
+
|
53
|
+
alias to_s pretty_print
|
48
54
|
end
|
49
55
|
|
50
56
|
# Represents a list of expressions joined with an "OR",
|
@@ -314,7 +320,7 @@ module DrgDSL
|
|
314
320
|
@left_condition = left_condition
|
315
321
|
@right_condition = right_condition
|
316
322
|
@comparison = comparison
|
317
|
-
@opd = opd
|
323
|
+
@opd = opd.to_s.strip.upcase
|
318
324
|
end
|
319
325
|
|
320
326
|
def to_hash
|
data/lib/drgdsl/ast_builder.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'pp'
|
2
4
|
|
3
5
|
module DrgDSL
|
@@ -13,13 +15,13 @@ module DrgDSL
|
|
13
15
|
|
14
16
|
def message
|
15
17
|
<<~EOM
|
16
|
-
|
18
|
+
Don't know how to build AST from this CST:
|
17
19
|
|
18
|
-
|
20
|
+
#{pretty cst}
|
19
21
|
|
20
|
-
|
22
|
+
Intermediate result:
|
21
23
|
|
22
|
-
|
24
|
+
#{pretty result}
|
23
25
|
EOM
|
24
26
|
end
|
25
27
|
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DrgDSL
|
4
|
+
# Add core extensions here as refinements. Only load them when necessary, so
|
5
|
+
# that we don't monkeypatch applications that load DrgDSL.
|
6
|
+
#
|
7
|
+
# @example Loading the core extensions to the current lexical scope
|
8
|
+
#
|
9
|
+
# module MyScope
|
10
|
+
# using DrgDSL::CoreExtensions
|
11
|
+
#
|
12
|
+
# # Extensions exist here
|
13
|
+
#
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # Extensions no longer exist here
|
17
|
+
#
|
18
|
+
module CoreExtensions
|
19
|
+
refine String do
|
20
|
+
def colorize(color_code)
|
21
|
+
"\e[#{color_code}m#{self}\e[0m"
|
22
|
+
end
|
23
|
+
|
24
|
+
def magenta
|
25
|
+
colorize 35
|
26
|
+
end
|
27
|
+
|
28
|
+
def cyan
|
29
|
+
colorize 36
|
30
|
+
end
|
31
|
+
|
32
|
+
def red
|
33
|
+
colorize 31
|
34
|
+
end
|
35
|
+
|
36
|
+
def yellow
|
37
|
+
colorize 33
|
38
|
+
end
|
39
|
+
|
40
|
+
def green
|
41
|
+
colorize 32
|
42
|
+
end
|
43
|
+
|
44
|
+
def blue
|
45
|
+
colorize 34
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DrgDSL
|
4
|
+
class ParenCleaner
|
5
|
+
include Visitor
|
6
|
+
|
7
|
+
# @param expression [String]
|
8
|
+
# @return [String] pretty printed expression without any redundant
|
9
|
+
# parentheses.
|
10
|
+
def self.remove_redundant_parens(expression)
|
11
|
+
ast = Parser.parse(expression)
|
12
|
+
reduced_ast = clean(ast)
|
13
|
+
reduced_ast.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param ast [Node]
|
17
|
+
# @return [Node] ast without redundant ParenExpressions
|
18
|
+
def self.clean(ast, remove_outer_parens: true)
|
19
|
+
# If the whole expression is wrapped in parentheses, we can start
|
20
|
+
# visiting the first child.
|
21
|
+
if ast.type == 'ParenExpression' && remove_outer_parens
|
22
|
+
ast = ast.expression
|
23
|
+
end
|
24
|
+
|
25
|
+
ast.accept(new)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def visit_Expression(n)
|
31
|
+
Ast::Expression.new(n.expressions.map { |e| visit(e) })
|
32
|
+
end
|
33
|
+
|
34
|
+
def visit_AndExpression(n)
|
35
|
+
Ast::AndExpression.new(n.expressions.map { |e| visit(e) })
|
36
|
+
end
|
37
|
+
|
38
|
+
def visit_ParenExpression(n)
|
39
|
+
types = %w(
|
40
|
+
ParenExpression
|
41
|
+
DrgLink
|
42
|
+
BasicExpression
|
43
|
+
AndExpression
|
44
|
+
NotExpression
|
45
|
+
FunctionCall
|
46
|
+
Empty
|
47
|
+
TableCondition
|
48
|
+
)
|
49
|
+
return visit(n.expression) if types.include?(n.expression.type)
|
50
|
+
|
51
|
+
if n.expression.type == 'Expression'
|
52
|
+
return Ast::ParenExpression.new(visit(n.expression))
|
53
|
+
end
|
54
|
+
|
55
|
+
n
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/drgdsl/parser.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DrgDSL
|
2
4
|
class ParserError < StandardError
|
3
5
|
attr_reader :parslet_error, :input
|
@@ -39,7 +41,9 @@ module DrgDSL
|
|
39
41
|
# https://kschiess.github.io/parslet/tricks.html
|
40
42
|
def stri(str)
|
41
43
|
key_chars = str.split(//)
|
42
|
-
key_chars
|
44
|
+
key_chars
|
45
|
+
.map { |c| match["#{c.upcase}#{c.downcase}".squeeze] }
|
46
|
+
.inject(:>>)
|
43
47
|
end
|
44
48
|
|
45
49
|
# Tokens
|
@@ -51,9 +55,9 @@ module DrgDSL
|
|
51
55
|
rule(:k_empty) { stri('empty') }
|
52
56
|
rule(:k_in) { stri('in') }
|
53
57
|
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') }
|
58
|
+
rule(:in_table) { stri('in') >> sp? >> stri('table') }
|
59
|
+
rule(:in_tables) { stri('in') >> sp? >> stri('tables') }
|
60
|
+
rule(:all_in_table) { stri('all') >> sp? >> stri('in') >> sp? >> stri('table') }
|
57
61
|
rule(:lpar) { str('(') >> sp? }
|
58
62
|
rule(:rpar) { sp? >> str(')') }
|
59
63
|
rule(:underscore) { str('_') }
|
@@ -65,11 +69,11 @@ module DrgDSL
|
|
65
69
|
rule(:number) { digit.repeat(1) }
|
66
70
|
|
67
71
|
rule(:comparison_operator) do
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
72
|
+
str(">=") |
|
73
|
+
str(">") |
|
74
|
+
str("<=") |
|
75
|
+
str("<") |
|
76
|
+
str("=")
|
73
77
|
end
|
74
78
|
|
75
79
|
# name
|
@@ -106,11 +110,11 @@ module DrgDSL
|
|
106
110
|
|
107
111
|
# srglrb
|
108
112
|
# ::= 'SRGLRB' number{0,1}
|
109
|
-
rule(:srglrb)
|
113
|
+
rule(:srglrb) { stri('SRGLRB') >> number.maybe }
|
110
114
|
|
111
115
|
# opd
|
112
116
|
# ::= 'OPD' number{0,1}
|
113
|
-
rule(:opd)
|
117
|
+
rule(:opd) { stri('OPD') >> number.maybe }
|
114
118
|
|
115
119
|
# By "double nesting" the variable, the AST builder (a parslet transformer)
|
116
120
|
# can use subtree(:variable) to retrieve a variable node, given there's a
|
@@ -243,12 +247,12 @@ module DrgDSL
|
|
243
247
|
end
|
244
248
|
|
245
249
|
rule(:sp) do
|
246
|
-
(match("[ \t\r\n]") | comment).repeat(1)
|
250
|
+
(match("[ \t\r\n\f]") | comment).repeat(1)
|
247
251
|
end
|
248
252
|
|
249
253
|
# Optional whitespace rule. Unfortunately, skipping whitespace can't be
|
250
254
|
# automated:
|
251
|
-
# https://github.com/kschiess/parslet/issues/
|
255
|
+
# https://github.com/kschiess/parslet/issues/49
|
252
256
|
rule(:sp?) { sp.repeat }
|
253
257
|
|
254
258
|
rule(:root) { expression }
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cgi'
|
4
|
+
|
5
|
+
module DrgDSL
|
6
|
+
class PrettyPrinter
|
7
|
+
using CoreExtensions
|
8
|
+
include Visitor
|
9
|
+
|
10
|
+
OUTPUT_FORMATS = %i[string html bash].freeze
|
11
|
+
OUTPUT_FORMATS.each do |format|
|
12
|
+
define_method "output_#{format}?" do
|
13
|
+
@output_format == format
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :indentation_level, :indentation_string, :output_format
|
18
|
+
|
19
|
+
# @param expression [String]
|
20
|
+
# @return [String]
|
21
|
+
def self.pretty_print(expression, **options)
|
22
|
+
ast = Parser.parse(expression)
|
23
|
+
ast = ParenCleaner.clean(ast) if options.delete(:remove_redundant_parens)
|
24
|
+
|
25
|
+
output = ast.accept(new(**options))
|
26
|
+
output_format = options[:output_format]
|
27
|
+
return "<pre>#{output}</pre>" if output_format == :html
|
28
|
+
output
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param output_format [Symbol]
|
32
|
+
# @param multiline [Boolean]
|
33
|
+
def initialize(output_format: :string,
|
34
|
+
multiline: true)
|
35
|
+
|
36
|
+
@indentation_level = 0
|
37
|
+
@indentation_string = ' '
|
38
|
+
|
39
|
+
unless OUTPUT_FORMATS.include?(output_format)
|
40
|
+
raise "Unknown output format: #{output_format.inspect}"
|
41
|
+
end
|
42
|
+
|
43
|
+
@output_format = output_format
|
44
|
+
@multiline = multiline
|
45
|
+
end
|
46
|
+
|
47
|
+
def multiline?
|
48
|
+
@multiline
|
49
|
+
end
|
50
|
+
|
51
|
+
def visit_nil
|
52
|
+
''
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def visit_Expression(n)
|
58
|
+
keyword = wrap('OR', type: :keyword)
|
59
|
+
|
60
|
+
separator = " #{keyword} "
|
61
|
+
separator = "\n#{f(keyword)} " if multiline?
|
62
|
+
|
63
|
+
n.expressions.map { |e| visit(e) }.join(separator)
|
64
|
+
end
|
65
|
+
|
66
|
+
def visit_AndExpression(n)
|
67
|
+
keyword = wrap('AND', type: :keyword)
|
68
|
+
|
69
|
+
separator = " #{keyword} "
|
70
|
+
separator = "\n#{f(keyword)} " if multiline?
|
71
|
+
|
72
|
+
n.expressions.map { |e| visit(e) }.join(separator)
|
73
|
+
end
|
74
|
+
|
75
|
+
def visit_ParenExpression(n)
|
76
|
+
return "(#{visit(n.expression)})" unless multiline?
|
77
|
+
|
78
|
+
str = +"(\n"
|
79
|
+
indented do
|
80
|
+
str << f(visit(n.expression))
|
81
|
+
end
|
82
|
+
str << "\n"
|
83
|
+
str << f(')')
|
84
|
+
end
|
85
|
+
|
86
|
+
def visit_NotExpression(n)
|
87
|
+
"#{wrap('NOT', type: :keyword)} (#{visit(n.expression)})"
|
88
|
+
end
|
89
|
+
|
90
|
+
def visit_FunctionCall(n)
|
91
|
+
wrap(n.name, type: :function)
|
92
|
+
end
|
93
|
+
|
94
|
+
def visit_DrgLink(n)
|
95
|
+
"#{visit(n.variable)} (#{wrap(n.name, type: n.variable.name.downcase)})"
|
96
|
+
end
|
97
|
+
|
98
|
+
def visit_BasicExpression(n)
|
99
|
+
"#{visit(n.variable)} #{visit(n.condition)}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def visit_Comparison(n)
|
103
|
+
"#{wrap(n.op, type: :op)} #{visit(n.value)} #{visit(n.table_condition)}".strip
|
104
|
+
end
|
105
|
+
|
106
|
+
def visit_UnaryCondition(n)
|
107
|
+
"#{wrap(n.op.upcase, type: :keyword)} #{visit(n.condition)}"
|
108
|
+
end
|
109
|
+
|
110
|
+
def visit_Empty(_)
|
111
|
+
wrap('EMPTY', type: :keyword)
|
112
|
+
end
|
113
|
+
|
114
|
+
def visit_TableCondition(n)
|
115
|
+
str = +"#{wrap(n.op.upcase, type: :keyword)} "
|
116
|
+
|
117
|
+
if multiline? && n.tables.count > 4
|
118
|
+
str << "(\n"
|
119
|
+
indented do
|
120
|
+
str << n.tables.map { |t| f(wrap(t.upcase, type: :table)) }.join(",\n")
|
121
|
+
end
|
122
|
+
str << "\n"
|
123
|
+
str << f(')')
|
124
|
+
else
|
125
|
+
str << "(#{n.tables.map { |t| wrap(t.upcase, type: :table) }.join(', ')})"
|
126
|
+
end
|
127
|
+
|
128
|
+
str
|
129
|
+
end
|
130
|
+
|
131
|
+
def visit_SrglrbTableCondition(n)
|
132
|
+
"#{visit(n.variable)} #{visit(n.condition)}"
|
133
|
+
end
|
134
|
+
|
135
|
+
def visit_DateExpression(n)
|
136
|
+
"#{wrap(n.opd, type: :variable)} IN (#{visit(n.left_variable)} #{visit(n.left_condition)}#{n.right_variable && " AND #{visit(n.right_variable)} #{visit(n.right_condition)}"}) #{visit(n.comparison)}".strip
|
137
|
+
end
|
138
|
+
|
139
|
+
def visit_Variable(n)
|
140
|
+
wrap(n.name.upcase, type: :variable)
|
141
|
+
end
|
142
|
+
|
143
|
+
def visit_Constant(n)
|
144
|
+
wrap(n.value, type: :constant)
|
145
|
+
end
|
146
|
+
|
147
|
+
def indented
|
148
|
+
indent!
|
149
|
+
yield
|
150
|
+
deindent!
|
151
|
+
end
|
152
|
+
|
153
|
+
def indent!
|
154
|
+
@indentation_level += 1
|
155
|
+
end
|
156
|
+
|
157
|
+
def deindent!
|
158
|
+
@indentation_level -= 1
|
159
|
+
end
|
160
|
+
|
161
|
+
def indentation
|
162
|
+
return '' unless multiline?
|
163
|
+
@indentation_string * @indentation_level
|
164
|
+
end
|
165
|
+
|
166
|
+
# @param str [String]
|
167
|
+
# @return [String] indented string
|
168
|
+
def f(str)
|
169
|
+
"#{indentation}#{str}"
|
170
|
+
end
|
171
|
+
|
172
|
+
def wrap(str, **options)
|
173
|
+
return str if output_string?
|
174
|
+
return span(str, type: options[:type]) if output_html?
|
175
|
+
return ansi_colored(str, type: options[:type]) if output_bash?
|
176
|
+
end
|
177
|
+
|
178
|
+
def span(str, type:)
|
179
|
+
class_name = "drgdsl-#{type}"
|
180
|
+
%{<span class="#{class_name}">#{escape_html(str)}</span>}
|
181
|
+
end
|
182
|
+
|
183
|
+
def escape_html(str)
|
184
|
+
CGI.escapeHTML(str)
|
185
|
+
end
|
186
|
+
|
187
|
+
def ansi_colored(str, type:)
|
188
|
+
case type.to_sym
|
189
|
+
when :keyword
|
190
|
+
str.blue
|
191
|
+
when :function
|
192
|
+
str.green
|
193
|
+
when :op
|
194
|
+
str.blue
|
195
|
+
when :table
|
196
|
+
str.magenta
|
197
|
+
when :variable
|
198
|
+
str.cyan
|
199
|
+
when :constant
|
200
|
+
str.red
|
201
|
+
when :drg, :adrg
|
202
|
+
str.yellow
|
203
|
+
else
|
204
|
+
str
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
data/lib/drgdsl/version.rb
CHANGED
data/lib/drgdsl/visitor.rb
CHANGED
@@ -1,17 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DrgDSL
|
4
|
+
# Mixin for Ast::Node visitors. Visits all child nodes and returns the
|
5
|
+
# visited node by default.
|
2
6
|
module Visitor
|
3
7
|
def visit(n)
|
8
|
+
return visit_nil if n.nil?
|
4
9
|
send("visit_#{n.type}", n)
|
5
10
|
end
|
6
11
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
12
|
+
# Override to define what should happen when one calls #visit with nil (can
|
13
|
+
# occur on optional AST branches).
|
14
|
+
#
|
15
|
+
# Does nothing by default. A string visitor might want to return an empty
|
16
|
+
# string here.
|
17
|
+
def visit_nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def visit_Expression(n)
|
21
|
+
n.expressions.map { |e| visit(e) }
|
22
|
+
n
|
23
|
+
end
|
24
|
+
|
25
|
+
def visit_AndExpression(n)
|
26
|
+
n.expressions.map { |e| visit(e) }
|
27
|
+
n
|
28
|
+
end
|
29
|
+
|
30
|
+
def visit_ParenExpression(n)
|
31
|
+
visit(n.expression)
|
32
|
+
n
|
33
|
+
end
|
34
|
+
|
35
|
+
def visit_NotExpression(n)
|
36
|
+
visit(n.expression)
|
37
|
+
n
|
38
|
+
end
|
39
|
+
|
40
|
+
def visit_FunctionCall(n)
|
41
|
+
n
|
42
|
+
end
|
43
|
+
|
44
|
+
def visit_DrgLink(n)
|
45
|
+
visit(n.variable)
|
46
|
+
n
|
47
|
+
end
|
48
|
+
|
49
|
+
def visit_BasicExpression(n)
|
50
|
+
visit(n.variable)
|
51
|
+
visit(n.condition)
|
52
|
+
n
|
53
|
+
end
|
54
|
+
|
55
|
+
def visit_Comparison(n)
|
56
|
+
visit(n.value)
|
57
|
+
visit(n.table_condition)
|
58
|
+
n
|
59
|
+
end
|
60
|
+
|
61
|
+
def visit_UnaryCondition(n)
|
62
|
+
visit(n.condition)
|
63
|
+
n
|
64
|
+
end
|
65
|
+
|
66
|
+
def visit_Empty(n)
|
67
|
+
n
|
68
|
+
end
|
69
|
+
|
70
|
+
def visit_TableCondition(n)
|
71
|
+
visit(n.comparison)
|
72
|
+
n
|
73
|
+
end
|
74
|
+
|
75
|
+
def visit_SrglrbTableCondition(n)
|
76
|
+
visit(n.condition)
|
77
|
+
n
|
78
|
+
end
|
79
|
+
|
80
|
+
def visit_DateExpression(n)
|
81
|
+
visit(n.left_variable)
|
82
|
+
visit(n.left_condition)
|
83
|
+
visit(n.right_variable)
|
84
|
+
visit(n.right_condition)
|
85
|
+
visit(n.comparison)
|
86
|
+
n
|
87
|
+
end
|
88
|
+
|
89
|
+
def visit_Variable(n)
|
90
|
+
n
|
11
91
|
end
|
12
92
|
|
13
|
-
|
14
|
-
|
93
|
+
def visit_Constant(n)
|
94
|
+
n
|
15
95
|
end
|
16
96
|
end
|
17
97
|
end
|
data/lib/drgdsl.rb
CHANGED
@@ -1,14 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'oj'
|
2
4
|
require 'parslet'
|
3
5
|
|
4
6
|
require_relative "./drgdsl/version"
|
7
|
+
require_relative "./drgdsl/core_extensions"
|
5
8
|
require_relative "./drgdsl/ast"
|
6
9
|
require_relative "./drgdsl/ast_builder"
|
7
10
|
require_relative "./drgdsl/parser"
|
8
11
|
require_relative "./drgdsl/visitor"
|
12
|
+
require_relative "./drgdsl/pretty_printer"
|
13
|
+
require_relative "./drgdsl/paren_cleaner"
|
9
14
|
|
10
15
|
module DrgDSL
|
11
|
-
|
12
16
|
# @param input [String]
|
13
17
|
# @return [Ast::Node]
|
14
18
|
def self.parse(input)
|
@@ -16,8 +20,58 @@ module DrgDSL
|
|
16
20
|
end
|
17
21
|
|
18
22
|
# @param input [String]
|
19
|
-
# @return [String]
|
23
|
+
# @return [String] JSON representation of the AST
|
20
24
|
def self.json(input)
|
21
25
|
Oj.dump parse(input).to_hash, mode: :compat
|
22
26
|
end
|
27
|
+
|
28
|
+
# @param input [String]
|
29
|
+
# @param remove_redundant_parens [Boolean] (false)
|
30
|
+
# @param multiline [Boolean] (true) pass false to print the expression
|
31
|
+
# without any newlines.
|
32
|
+
# @param output_format [Symbol] (:string) also supports :html and :bash.
|
33
|
+
#
|
34
|
+
# @return [String] pretty printed DrgDSL expression
|
35
|
+
#
|
36
|
+
# @example Pretty printing with various options
|
37
|
+
# input = "(pdx in table (a )) and (drg (f46) or (drg (g22) and los < 3))"
|
38
|
+
# DrgDSL.pretty_print(input)
|
39
|
+
# # =>
|
40
|
+
# # (
|
41
|
+
# # PDX IN TABLE (A)
|
42
|
+
# # )
|
43
|
+
# # AND (
|
44
|
+
# # DRG (F46)
|
45
|
+
# # OR (
|
46
|
+
# # DRG (G22)
|
47
|
+
# # AND LOS < 3
|
48
|
+
# # )
|
49
|
+
# # )
|
50
|
+
#
|
51
|
+
# DrgDSL.pretty_print(input, remove_redundant_parens: true)
|
52
|
+
# # =>
|
53
|
+
# # PDX IN TABLE (A)
|
54
|
+
# # AND (
|
55
|
+
# # DRG (F46)
|
56
|
+
# # OR DRG (G22)
|
57
|
+
# # AND LOS < 3
|
58
|
+
# # )
|
59
|
+
#
|
60
|
+
# DrgDSL.pretty_print(input, remove_redundant_parens: true, multiline: false)
|
61
|
+
# # => PDX IN TABLE (A) AND (DRG (F46) OR DRG (G22) AND LOS < 3)
|
62
|
+
#
|
63
|
+
# DrgDSL.pretty_print(input, remove_redundant_parens: true, output_format: html)
|
64
|
+
# # =>
|
65
|
+
# # <pre>
|
66
|
+
# # <span class="drgdsl-variable">PDX</span> <span class="drgdsl-keyword">IN TABLE</span> (<span class="drgdsl-table">A</span>)
|
67
|
+
# # <span class="drgdsl-keyword">AND</span> (
|
68
|
+
# # <span class="drgdsl-variable">DRG</span> (<span class="drgdsl-drg">F46</span>)
|
69
|
+
# # <span class="drgdsl-keyword">OR</span> <span class="drgdsl-variable">DRG</span> (<span class="drgdsl-drg">G22</span>)
|
70
|
+
# # <span class="drgdsl-keyword">AND</span> <span class="drgdsl-variable">LOS</span> <span class="drgdsl-op"><</span> <span class="drgdsl-constant">3</span>
|
71
|
+
# # )
|
72
|
+
# # </pre>
|
73
|
+
#
|
74
|
+
def self.pretty_print(input, **options)
|
75
|
+
PrettyPrinter.pretty_print(input, **options)
|
76
|
+
end
|
23
77
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: drgdsl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- SwissDRG AG
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-07-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parslet
|
@@ -131,6 +131,7 @@ extensions: []
|
|
131
131
|
extra_rdoc_files: []
|
132
132
|
files:
|
133
133
|
- ".gitignore"
|
134
|
+
- ".rubocop.yml"
|
134
135
|
- ".travis.yml"
|
135
136
|
- Gemfile
|
136
137
|
- Gemfile.lock
|
@@ -138,13 +139,17 @@ files:
|
|
138
139
|
- README.md
|
139
140
|
- Rakefile
|
140
141
|
- bin/console
|
142
|
+
- bin/html
|
141
143
|
- bin/setup
|
142
144
|
- drgdsl.gemspec
|
143
145
|
- exe/drgdsl
|
144
146
|
- lib/drgdsl.rb
|
145
147
|
- lib/drgdsl/ast.rb
|
146
148
|
- lib/drgdsl/ast_builder.rb
|
149
|
+
- lib/drgdsl/core_extensions.rb
|
150
|
+
- lib/drgdsl/paren_cleaner.rb
|
147
151
|
- lib/drgdsl/parser.rb
|
152
|
+
- lib/drgdsl/pretty_printer.rb
|
148
153
|
- lib/drgdsl/version.rb
|
149
154
|
- lib/drgdsl/visitor.rb
|
150
155
|
homepage: https://www.swissdrg.org
|