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