lkml 0.1.0 → 0.2.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/.github/workflows/ci.yml +28 -0
- data/.gitignore +57 -0
- data/.rubocop.yml +25 -0
- data/.ruby-version +1 -0
- data/CONTRIBUTING.md +56 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +76 -0
- data/LICENSE.md +1 -2
- data/README.md +81 -7
- data/Rakefile +12 -0
- data/bin/lkml +6 -0
- data/lib/lkml/cli.rb +44 -0
- data/lib/lkml/keys.rb +64 -114
- data/lib/lkml/lexer.rb +45 -68
- data/lib/lkml/parser.rb +296 -191
- data/lib/lkml/simple.rb +244 -238
- data/lib/lkml/tokens.rb +44 -129
- data/lib/lkml/tree.rb +362 -236
- data/lib/lkml/utils.rb +32 -0
- data/lib/lkml/version.rb +1 -1
- data/lib/lkml/visitors.rb +69 -64
- data/lib/lkml.rb +49 -14
- data/lkml.gemspec +35 -0
- data/script/benchmark.rb +36 -0
- data/script/download_lookml.rb +70 -0
- metadata +88 -13
data/lib/lkml/lexer.rb
CHANGED
|
@@ -1,106 +1,86 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
4
|
-
require_relative
|
|
3
|
+
require_relative "keys"
|
|
4
|
+
require_relative "tokens"
|
|
5
5
|
|
|
6
|
-
# Splits a LookML string into a sequence of tokens.
|
|
7
6
|
module Lkml
|
|
8
7
|
class Lexer
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
CHARACTER_TO_TOKEN = {
|
|
12
|
-
"\0" => Tokens::StreamEndToken,
|
|
13
|
-
'{' => Tokens::BlockStartToken,
|
|
14
|
-
'}' => Tokens::BlockEndToken,
|
|
15
|
-
'[' => Tokens::ListStartToken,
|
|
16
|
-
']' => Tokens::ListEndToken,
|
|
17
|
-
',' => Tokens::CommaToken,
|
|
18
|
-
':' => Tokens::ValueToken,
|
|
19
|
-
';' => Tokens::ExpressionBlockEndToken
|
|
20
|
-
}.freeze
|
|
8
|
+
attr_accessor :text, :index, :tokens, :line_number
|
|
21
9
|
|
|
22
10
|
def initialize(text)
|
|
23
|
-
|
|
24
|
-
@text = "#{text}\u0000"
|
|
11
|
+
@text = "#{text}\0"
|
|
25
12
|
@index = 0
|
|
26
13
|
@tokens = []
|
|
27
14
|
@line_number = 1
|
|
28
15
|
end
|
|
29
16
|
|
|
30
17
|
def peek
|
|
31
|
-
# Returns the character at the current index of the text being lexed.
|
|
32
18
|
@text[@index]
|
|
33
19
|
end
|
|
34
20
|
|
|
35
21
|
def peek_multiple(length)
|
|
36
|
-
|
|
37
|
-
@text[@index, length]
|
|
22
|
+
@text[@index, length] || ""
|
|
38
23
|
end
|
|
39
24
|
|
|
40
25
|
def advance(length = 1)
|
|
41
|
-
# Moves the index forward by n characters.
|
|
42
26
|
@index += length
|
|
43
27
|
nil
|
|
44
28
|
end
|
|
45
29
|
|
|
46
30
|
def consume
|
|
47
|
-
# Returns the current index character and advances the index 1 character.
|
|
48
31
|
advance
|
|
49
32
|
@text[@index - 1]
|
|
50
33
|
end
|
|
51
34
|
|
|
52
|
-
def scan
|
|
53
|
-
# Tokenizes LookML into a sequence of tokens.
|
|
35
|
+
def scan
|
|
54
36
|
@tokens << Tokens::StreamStartToken.new(@line_number)
|
|
55
37
|
loop do
|
|
56
38
|
ch = peek
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@tokens << CHARACTER_TO_TOKEN[ch].new(@line_number)
|
|
39
|
+
if ch == "\0"
|
|
40
|
+
@tokens << Keys::CHARACTER_TO_TOKEN[ch].new(@line_number)
|
|
60
41
|
break
|
|
61
|
-
|
|
42
|
+
elsif "\n\t ".include?(ch)
|
|
62
43
|
@tokens << scan_whitespace
|
|
63
|
-
|
|
44
|
+
elsif ch == "#"
|
|
64
45
|
advance
|
|
65
46
|
@tokens << scan_comment
|
|
66
|
-
|
|
67
|
-
if peek_multiple(2) ==
|
|
47
|
+
elsif ch == ";"
|
|
48
|
+
if peek_multiple(2) == ";;"
|
|
68
49
|
advance(2)
|
|
69
|
-
|
|
50
|
+
else
|
|
51
|
+
advance
|
|
70
52
|
end
|
|
71
|
-
|
|
53
|
+
@tokens << Keys::CHARACTER_TO_TOKEN[ch].new(@line_number)
|
|
54
|
+
elsif ch == '"'
|
|
72
55
|
advance
|
|
73
56
|
@tokens << scan_quoted_literal
|
|
74
|
-
|
|
57
|
+
elsif Keys::CHARACTER_TO_TOKEN.key?(ch)
|
|
58
|
+
advance
|
|
59
|
+
@tokens << Keys::CHARACTER_TO_TOKEN[ch].new(@line_number)
|
|
60
|
+
elsif expression_block?(peek_multiple(25))
|
|
61
|
+
@tokens << scan_literal
|
|
75
62
|
advance
|
|
76
|
-
@tokens <<
|
|
63
|
+
@tokens << Tokens::ValueToken.new(@line_number)
|
|
64
|
+
@tokens << scan_expression_block
|
|
77
65
|
else
|
|
78
|
-
|
|
79
|
-
# TODO: Handle edges here with whitespace and comments
|
|
80
|
-
@tokens << scan_literal
|
|
81
|
-
advance
|
|
82
|
-
@tokens << Tokens::ValueToken.new(@line_number)
|
|
83
|
-
@tokens << scan_expression_block
|
|
84
|
-
else
|
|
85
|
-
# TODO: This should actually check for valid literals first
|
|
86
|
-
# and throw an error if it doesn't match
|
|
87
|
-
@tokens << scan_literal
|
|
88
|
-
end
|
|
66
|
+
@tokens << scan_literal
|
|
89
67
|
end
|
|
90
68
|
end
|
|
91
|
-
@tokens
|
|
69
|
+
@tokens.freeze
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.expression_block?(string)
|
|
73
|
+
Keys::EXPR_BLOCK_KEYS.any? { |key| string.start_with?("#{key}:") }
|
|
92
74
|
end
|
|
93
75
|
|
|
94
|
-
def
|
|
95
|
-
|
|
96
|
-
EXPR_BLOCK_KEYS.any? { |key| string.start_with?("#{key}:") }
|
|
76
|
+
def expression_block?(string)
|
|
77
|
+
self.class.expression_block?(string)
|
|
97
78
|
end
|
|
98
79
|
|
|
99
80
|
def scan_whitespace
|
|
100
|
-
|
|
101
|
-
chars = ''
|
|
81
|
+
chars = ""
|
|
102
82
|
next_char = peek
|
|
103
|
-
while ["\n", "\t",
|
|
83
|
+
while ["\n", "\t", " "].include?(next_char)
|
|
104
84
|
if next_char == "\n"
|
|
105
85
|
while next_char == "\n"
|
|
106
86
|
chars += consume
|
|
@@ -117,16 +97,14 @@ module Lkml
|
|
|
117
97
|
end
|
|
118
98
|
|
|
119
99
|
def scan_comment
|
|
120
|
-
|
|
121
|
-
chars = '#'
|
|
100
|
+
chars = "#"
|
|
122
101
|
chars += consume until ["\0", "\n"].include?(peek)
|
|
123
102
|
Tokens::CommentToken.new(chars, @line_number)
|
|
124
103
|
end
|
|
125
104
|
|
|
126
105
|
def scan_expression_block
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
while peek_multiple(2) != ';;'
|
|
106
|
+
chars = ""
|
|
107
|
+
until peek_multiple(2) == ";;"
|
|
130
108
|
@line_number += 1 if peek == "\n"
|
|
131
109
|
chars += consume
|
|
132
110
|
end
|
|
@@ -134,22 +112,21 @@ module Lkml
|
|
|
134
112
|
end
|
|
135
113
|
|
|
136
114
|
def scan_literal
|
|
137
|
-
|
|
138
|
-
chars
|
|
139
|
-
chars += consume until ["\0", ' ', "\n", "\t", ':', '}', '{', ',', ']'].include?(peek)
|
|
115
|
+
chars = ""
|
|
116
|
+
chars += consume until ["\0", " ", "\n", "\t", ":", "}", "{", ",", "]"].include?(peek)
|
|
140
117
|
Tokens::LiteralToken.new(chars, @line_number)
|
|
141
118
|
end
|
|
142
119
|
|
|
143
120
|
def scan_quoted_literal
|
|
144
|
-
|
|
145
|
-
chars = ''
|
|
121
|
+
chars = ""
|
|
146
122
|
loop do
|
|
147
123
|
ch = peek
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
124
|
+
case ch
|
|
125
|
+
when '"'
|
|
126
|
+
break
|
|
127
|
+
when "\\"
|
|
128
|
+
chars += consume
|
|
129
|
+
when "\n"
|
|
153
130
|
@line_number += 1
|
|
154
131
|
end
|
|
155
132
|
chars += consume
|