lkml 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/lkml/tree.rb ADDED
@@ -0,0 +1,319 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Node and token classes that make up the parse tree.
4
+
5
+ module Lkml
6
+ class SyntaxToken
7
+ attr_reader :value, :line_number, :prefix, :suffix
8
+
9
+ def initialize(value, line_number = nil, prefix: '', suffix: '')
10
+ @value = value
11
+ @line_number = line_number
12
+ @prefix = prefix
13
+ @suffix = suffix
14
+ end
15
+
16
+ def format_value
17
+ @value
18
+ end
19
+
20
+ def accept(visitor)
21
+ visitor.visit_token(self)
22
+ end
23
+
24
+ def ==(other)
25
+ if other.is_a?(self.class)
26
+ instance_variables.all? { |var| instance_variable_get(var) == other.instance_variable_get(var) }
27
+ elsif other.is_a?(String)
28
+ @value == other
29
+ else
30
+ false
31
+ end
32
+ end
33
+
34
+ def to_s
35
+ [@prefix, format_value, @suffix].join
36
+ end
37
+ end
38
+
39
+ class LeftCurlyBrace < SyntaxToken
40
+ def initialize(*, **)
41
+ super('{', *, **)
42
+ end
43
+ end
44
+
45
+ class RightCurlyBrace < SyntaxToken
46
+ def initialize(*, **)
47
+ super('}', *, **)
48
+ end
49
+ end
50
+
51
+ class Colon < SyntaxToken
52
+ def initialize(*, **)
53
+ super(':', *, **)
54
+ end
55
+ end
56
+
57
+ class LeftBracket < SyntaxToken
58
+ def initialize(*, **)
59
+ super('[', *, **)
60
+ end
61
+ end
62
+
63
+ class RightBracket < SyntaxToken
64
+ def initialize(*, **)
65
+ super(']', *, **)
66
+ end
67
+ end
68
+
69
+ class DoubleSemicolon < SyntaxToken
70
+ def initialize(*, **)
71
+ super(';;', *, **)
72
+ end
73
+ end
74
+
75
+ class Comma < SyntaxToken
76
+ def initialize(*, **)
77
+ super(',', *, **)
78
+ end
79
+ end
80
+
81
+ class QuotedSyntaxToken < SyntaxToken
82
+ def format_value
83
+ "\"#{@value.gsub('\"', '"').gsub('"', '\"')}\""
84
+ end
85
+ end
86
+
87
+ class ExpressionSyntaxToken < SyntaxToken
88
+ attr_reader :expr_suffix
89
+
90
+ def initialize(value, line_number = nil, prefix: ' ', expr_suffix: ' ', suffix: '')
91
+ super(value, line_number, prefix: prefix, suffix: suffix)
92
+ @expr_suffix = expr_suffix
93
+ end
94
+
95
+ def to_s
96
+ [@prefix, format_value, @expr_suffix, ';;', @suffix].join
97
+ end
98
+ end
99
+
100
+ class SyntaxNode
101
+ def children
102
+ raise NotImplementedError, 'Subclasses must implement the children method'
103
+ end
104
+
105
+ def line_number
106
+ raise NotImplementedError, 'Subclasses must implement the line_number method'
107
+ end
108
+
109
+ def accept(visitor)
110
+ raise NotImplementedError, 'Subclasses must implement the accept method'
111
+ end
112
+
113
+ def ==(other)
114
+ if other.is_a?(self.class)
115
+ instance_variables.all? { |var| instance_variable_get(var) == other.instance_variable_get(var) }
116
+ elsif other.is_a?(String)
117
+ @value == other
118
+ else
119
+ false
120
+ end
121
+ end
122
+ end
123
+
124
+ class PairNode < SyntaxNode
125
+ attr_reader :type, :value, :colon
126
+
127
+ def initialize(type, value, colon: Colon.new(suffix: ' '))
128
+ super()
129
+ @type = type
130
+ @value = value
131
+ @colon = colon
132
+ end
133
+
134
+ def to_s
135
+ [@type, @colon, @value].join
136
+ end
137
+
138
+ def children
139
+ nil
140
+ end
141
+
142
+ def line_number
143
+ @type.line_number
144
+ end
145
+
146
+ def accept(visitor)
147
+ visitor.visit_pair(self)
148
+ end
149
+ end
150
+
151
+ class ListNode < SyntaxNode
152
+ attr_reader :type, :items, :left_bracket, :right_bracket, :colon, :leading_comma, :trailing_comma
153
+
154
+ def initialize(type, items:, left_bracket:, right_bracket:, colon: Colon.new(suffix: ' '), leading_comma: nil, trailing_comma: nil) # rubocop:disable Metrics/ParameterLists,Layout/LineLength
155
+ super()
156
+ @type = type
157
+ @items = items
158
+ @left_bracket = left_bracket
159
+ @right_bracket = right_bracket
160
+ @colon = colon
161
+ @leading_comma = leading_comma
162
+ @trailing_comma = trailing_comma
163
+ end
164
+
165
+ def to_s
166
+ [
167
+ @type,
168
+ @colon,
169
+ @left_bracket,
170
+ @leading_comma && @items.any? ? @leading_comma : '',
171
+ @items.map(&:to_s).join(','),
172
+ @trailing_comma && @items.any? ? @trailing_comma : '',
173
+ @right_bracket
174
+ ].join
175
+ end
176
+
177
+ def children
178
+ if @items.any? && @items.first.is_a?(PairNode)
179
+ @items
180
+ else
181
+ []
182
+ end
183
+ end
184
+
185
+ def line_number
186
+ @type.line_number
187
+ end
188
+
189
+ def accept(visitor)
190
+ visitor.visit_list(self)
191
+ end
192
+ end
193
+
194
+ class ContainerNode < SyntaxNode
195
+ attr_reader :items, :top_level
196
+
197
+ def initialize(items, top_level: false)
198
+ super()
199
+ @items = items
200
+ @top_level = top_level
201
+ validate_keys
202
+ end
203
+
204
+ def validate_keys
205
+ counter = @items.each_with_object(Hash.new(0)) { |item, hash| hash[item.type.value] += 1 }
206
+ counter.each do |key, count|
207
+ if !@top_level && count > 1 && !PLURAL_KEYS.include?(key)
208
+ raise KeyError, "Key \"#{key}\" already exists in tree and would overwrite the existing value."
209
+ end
210
+ end
211
+ end
212
+
213
+ def children
214
+ @items
215
+ end
216
+
217
+ def line_number
218
+ @items.first&.line_number
219
+ end
220
+
221
+ def accept(visitor)
222
+ visitor.visit_container(self)
223
+ end
224
+
225
+ def to_s
226
+ @items.map(&:to_s).join
227
+ end
228
+ end
229
+
230
+ class BlockNode < SyntaxNode
231
+ attr_reader :type, :left_brace, :right_brace, :colon, :name, :container
232
+
233
+ def initialize(type, left_brace: LeftCurlyBrace.new(suffix: "\n"), right_brace: RightCurlyBrace.new(prefix: "\n"), colon: Colon.new(suffix: ' '), name: nil, container: ContainerNode.new([])) # rubocop:disable Metrics/ParameterLists,Layout/LineLength
234
+ super()
235
+ @type = type
236
+ @left_brace = left_brace
237
+ @right_brace = right_brace
238
+ @colon = colon
239
+ @name = name
240
+ @container = container
241
+ end
242
+
243
+ def to_s
244
+ [
245
+ @type,
246
+ @colon,
247
+ @name || '',
248
+ @left_brace,
249
+ @container || '',
250
+ @right_brace
251
+ ].join
252
+ end
253
+
254
+ def children
255
+ @container.children
256
+ end
257
+
258
+ def line_number
259
+ @type.line_number
260
+ end
261
+
262
+ def accept(visitor)
263
+ visitor.visit_block(self)
264
+ end
265
+ end
266
+
267
+ class DocumentNode < SyntaxNode
268
+ attr_reader :container, :prefix, :suffix
269
+
270
+ def initialize(container, prefix: '', suffix: '')
271
+ super()
272
+ @container = container
273
+ @prefix = prefix
274
+ @suffix = suffix
275
+ end
276
+
277
+ def children
278
+ [@container]
279
+ end
280
+
281
+ def line_number
282
+ 1
283
+ end
284
+
285
+ def accept(visitor)
286
+ visitor.visit(self)
287
+ end
288
+
289
+ def to_s
290
+ [@prefix, @container, @suffix].join
291
+ end
292
+ end
293
+
294
+ class Visitor
295
+ def visit(document)
296
+ raise NotImplementedError, 'Subclasses must implement the visit method'
297
+ end
298
+
299
+ def visit_container(node)
300
+ raise NotImplementedError, 'Subclasses must implement the visit_container method'
301
+ end
302
+
303
+ def visit_block(node)
304
+ raise NotImplementedError, 'Subclasses must implement the visit_block method'
305
+ end
306
+
307
+ def visit_list(node)
308
+ raise NotImplementedError, 'Subclasses must implement the visit_list method'
309
+ end
310
+
311
+ def visit_pair(node)
312
+ raise NotImplementedError, 'Subclasses must implement the visit_pair method'
313
+ end
314
+
315
+ def visit_token(token)
316
+ raise NotImplementedError, 'Subclasses must implement the visit_token method'
317
+ end
318
+ end
319
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lkml
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ require_relative 'tree'
6
+
7
+ module Lkml
8
+ class BasicVisitor < Visitor
9
+ def _visit(node)
10
+ if node.is_a?(SyntaxToken)
11
+ nil
12
+ elsif node.respond_to?(:children) && node.children
13
+ node.children.each { |child| child.accept(self) }
14
+ end
15
+ end
16
+
17
+ def visit(document)
18
+ _visit(document)
19
+ end
20
+
21
+ def visit_container(node)
22
+ _visit(node)
23
+ end
24
+
25
+ def visit_block(node)
26
+ _visit(node)
27
+ end
28
+
29
+ def visit_list(node)
30
+ _visit(node)
31
+ end
32
+
33
+ def visit_pair(node)
34
+ _visit(node)
35
+ end
36
+
37
+ def visit_token(token)
38
+ _visit(token)
39
+ end
40
+ end
41
+
42
+ class LookMlVisitor < BasicVisitor
43
+ def _visit(node)
44
+ node.to_s
45
+ end
46
+ end
47
+
48
+ class BasicTransformer < Visitor
49
+ def _visit_items(node)
50
+ if node.respond_to?(:children) && node.children
51
+ new_children = node.children.map { |child| child.accept(self) }
52
+ node.class.new(items: new_children)
53
+ else
54
+ node
55
+ end
56
+ end
57
+
58
+ def _visit_container(node)
59
+ if node.respond_to?(:container) && node.container
60
+ new_child = node.container.accept(self)
61
+ node.class.new(container: new_child)
62
+ else
63
+ node
64
+ end
65
+ end
66
+
67
+ def visit(node)
68
+ _visit_container(node)
69
+ end
70
+
71
+ def visit_container(node)
72
+ _visit_items(node)
73
+ end
74
+
75
+ def visit_list(node)
76
+ _visit_items(node)
77
+ end
78
+
79
+ def visit_block(node)
80
+ _visit_container(node)
81
+ end
82
+
83
+ def visit_pair(node)
84
+ node
85
+ end
86
+
87
+ def visit_token(token)
88
+ token
89
+ end
90
+ end
91
+ end
data/lib/lkml.rb ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lkml/keys'
4
+ require_relative 'lkml/lexer'
5
+ require_relative 'lkml/parser'
6
+ require_relative 'lkml/simple'
7
+ require_relative 'lkml/tokens'
8
+ require_relative 'lkml/tree'
9
+ require_relative 'lkml/visitors'
10
+
11
+ module Lkml
12
+ def self.parse(text)
13
+ lexer = Lexer.new(text)
14
+ tokens = lexer.scan
15
+ Parser.new(tokens).parse
16
+ end
17
+
18
+ def self.dump(obj, io = nil)
19
+ parser = DictParser.new
20
+ result = parser.parse(obj).to_s
21
+ return result unless io
22
+
23
+ io.write(result)
24
+ nil
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lkml
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sylvain Utard
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-01-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: ''
14
+ email: sylvain.utard@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - LICENSE.md
20
+ - README.md
21
+ - lib/lkml.rb
22
+ - lib/lkml/keys.rb
23
+ - lib/lkml/lexer.rb
24
+ - lib/lkml/parser.rb
25
+ - lib/lkml/simple.rb
26
+ - lib/lkml/tokens.rb
27
+ - lib/lkml/tree.rb
28
+ - lib/lkml/version.rb
29
+ - lib/lkml/visitors.rb
30
+ homepage: ''
31
+ licenses:
32
+ - MIT
33
+ metadata:
34
+ rubygems_mfa_required: 'true'
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '3.2'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubygems_version: 3.5.11
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: LookML Ruby Parser
54
+ test_files: []