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.
data/lib/lkml/lexer.rb CHANGED
@@ -1,106 +1,86 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'keys'
4
- require_relative 'tokens'
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
- attr_reader :text, :index, :tokens, :line_number
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
- # Initializes the Lexer with a LookML string and sets the index.
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
- # Returns the next n characters from the current index in the text being lexed.
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 # rubocop:disable Metrics/CyclomaticComplexity
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
- case ch
58
- when "\0"
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
- when "\n", "\t", ' '
42
+ elsif "\n\t ".include?(ch)
62
43
  @tokens << scan_whitespace
63
- when '#'
44
+ elsif ch == "#"
64
45
  advance
65
46
  @tokens << scan_comment
66
- when ';'
67
- if peek_multiple(2) == ';;'
47
+ elsif ch == ";"
48
+ if peek_multiple(2) == ";;"
68
49
  advance(2)
69
- @tokens << CHARACTER_TO_TOKEN[ch].new(@line_number)
50
+ else
51
+ advance
70
52
  end
71
- when '"'
53
+ @tokens << Keys::CHARACTER_TO_TOKEN[ch].new(@line_number)
54
+ elsif ch == '"'
72
55
  advance
73
56
  @tokens << scan_quoted_literal
74
- when *CHARACTER_TO_TOKEN.keys
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 << CHARACTER_TO_TOKEN[ch].new(@line_number)
63
+ @tokens << Tokens::ValueToken.new(@line_number)
64
+ @tokens << scan_expression_block
77
65
  else
78
- if self.class.check_for_expression_block(peek_multiple(25))
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 self.check_for_expression_block(string)
95
- # Returns true if the input string is an expression block.
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
- # Returns a token from one or more whitespace characters.
101
- chars = ''
81
+ chars = ""
102
82
  next_char = peek
103
- while ["\n", "\t", ' '].include?(next_char)
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
- # Returns a token from a comment.
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
- # Returns a token from an expression block string.
128
- chars = ''
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
- # Returns a token from a literal string.
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
- # Returns a token from a quoted literal string.
145
- chars = ''
121
+ chars = ""
146
122
  loop do
147
123
  ch = peek
148
- break if ch == '"'
149
-
150
- if ch == '\\'
151
- chars += consume # Extra consume to skip the escaped character
152
- elsif ch == "\n"
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