Class: Taoism::Lexer

Inherits:
Object
  • Object
show all
Defined in:
lib/taoism/lexer.rb

Defined Under Namespace

Classes: ErrorHandler

Constant Summary collapse

KEYWORDS =
{
  'if'      => TokenType::IF,
  'else'    => TokenType::ELSE,
  'loop'    => TokenType::LOOP,
  'leave'   => TokenType::LEAVE,
  'switch'  => TokenType::SWITCH,
  'package' => TokenType::PACKAGE,
  'import'  => TokenType::IMPORT,
  'const'   => TokenType::CONST,
  'data'    => TokenType::DATA,
  'fun'     => TokenType::FUN,
  'let'     => TokenType::LET,
  'mut'     => TokenType::MUT,
  'try'     => TokenType::TRY,
  'return'  => TokenType::RETURN,
  'and'     => TokenType::AND,
  'or'      => TokenType::OR,
  'not'     => TokenType::NOT,
}.freeze
PUNCTUATION =
{
  '{' => TokenType::LBRACE,
  '}' => TokenType::RBRACE,
  '[' => TokenType::LSQUARE,
  ']' => TokenType::RSQUARE,
  '(' => TokenType::LPAREN,
  ')' => TokenType::RPAREN,
  ',' => TokenType::COMMA,
  ':' => TokenType::COLON,
  ';' => TokenType::SEMI,
  '.' => TokenType::DOT,
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source) ⇒ Lexer

Returns a new instance of Lexer.



38
39
40
41
42
43
44
# File 'lib/taoism/lexer.rb', line 38

def initialize(source)
  @source = source
  @offset = 0
  @line   = 1
  @col    = 0
  @errors = ErrorHandler.new
end

Instance Attribute Details

#errorsObject (readonly)

Returns the value of attribute errors.



36
37
38
# File 'lib/taoism/lexer.rb', line 36

def errors
  @errors
end

Instance Method Details

#advanceObject



215
216
217
218
219
220
# File 'lib/taoism/lexer.rb', line 215

def advance
  char = @source[@offset]
  @offset += 1
  @col += 1
  char
end

#alpha?(char) ⇒ Boolean

Returns:

  • (Boolean)


249
250
251
252
253
# File 'lib/taoism/lexer.rb', line 249

def alpha?(char)
  ('A' <= char && char <= 'Z') ||
  ('a' <= char && char <= 'z') ||
  char == '_'
end

#alpha_numeric?(char) ⇒ Boolean

Returns:

  • (Boolean)


245
246
247
# File 'lib/taoism/lexer.rb', line 245

def alpha_numeric?(char)
  alpha?(char) || digit?(char)
end

#at_end?Boolean

Returns:

  • (Boolean)


222
223
224
# File 'lib/taoism/lexer.rb', line 222

def at_end?
  @offset >= @source.length
end

#bool_token?(str) ⇒ Boolean

Returns:

  • (Boolean)


226
227
228
# File 'lib/taoism/lexer.rb', line 226

def bool_token?(str)
  str == 'True' || str == 'False'
end

#digit?(char) ⇒ Boolean

Returns:

  • (Boolean)


255
256
257
# File 'lib/taoism/lexer.rb', line 255

def digit?(char)
  '0' <= char && char <= '9'
end

#eof_tokenObject



184
185
186
# File 'lib/taoism/lexer.rb', line 184

def eof_token
  Token.new(TokenType::EOF, "", nil, @start, pos)
end

#identifier_tokenObject



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/taoism/lexer.rb', line 107

def identifier_token
  while alpha_numeric?(peek)
    advance
  end

  str = lexeme

  if bool_token?(str)
    make_token(TokenType::BOOL)
  elsif none_token?(str)
    make_token(TokenType::NONE)
  else
    if type = KEYWORDS[str]
      make_token(type)
    else
      make_token(TokenType::IDENTIFIER)
    end
  end
end

#lexemeObject



188
189
190
# File 'lib/taoism/lexer.rb', line 188

def lexeme
  @source[@start.offset...@offset]
end

#make_token(type, text = lexeme) ⇒ Object



180
181
182
# File 'lib/taoism/lexer.rb', line 180

def make_token(type, text = lexeme)
  Token.new(type, text, nil, @start, pos)
end

#match(expected) ⇒ Object



196
197
198
199
200
# File 'lib/taoism/lexer.rb', line 196

def match(expected)
  return false if peek != expected
  advance
  true
end

#next_tokenObject



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/taoism/lexer.rb', line 46

def next_token
  loop do
    return eof_token if at_end?
    @start = pos

    char = advance
    next if whitespace?(char)

    if char == '/' && peek == '/'
      skip_line_comment
      next
    end

    return scan_token(char)
  end
end

#none_token?(str) ⇒ Boolean

Returns:

  • (Boolean)


230
231
232
# File 'lib/taoism/lexer.rb', line 230

def none_token?(str)
  str == 'None'
end

#number_tokenObject



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/taoism/lexer.rb', line 161

def number_token
  type = TokenType::INT

  while digit?(peek) || peek == '_'
    advance
  end

  if peek == '.' && digit?(peek_next)
    type = TokenType::FLOAT
    advance

    while digit?(peek) || peek == '_'
      advance
    end
  end

  make_token(type)
end

#peekObject



202
203
204
205
# File 'lib/taoism/lexer.rb', line 202

def peek
  return "\0" if at_end?
  @source[@offset]
end

#peek_nextObject



207
208
209
210
211
212
213
# File 'lib/taoism/lexer.rb', line 207

def peek_next
  if @offset.next < @source.length
    @source[@offset + 1]
  else
    "\0"
  end
end

#posObject



192
193
194
# File 'lib/taoism/lexer.rb', line 192

def pos
  Token::Position.new(@offset, @line, @col)
end

#scan_token(char) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/taoism/lexer.rb', line 70

def scan_token(char)
  type = PUNCTUATION[char]
  return make_token(type) if type

  case char
  when '+' then make_token(TokenType::PLUS)
  when '-'
  match('>') ? make_token(TokenType::ARROW) : make_token(TokenType::MINUS)
  when '*' then make_token(TokenType::STAR)
  when '/' then make_token(TokenType::SLASH)
  when '%' then make_token(TokenType::MODULO)
  when '>'
    match('=') ? make_token(TokenType::GREATEREQ) : make_token(TokenType::GREATER)
  when '<'
    match('=') ? make_token(TokenType::LESSEQ) : make_token(TokenType::LESS)
  when '='
    if match('=')
      make_token(TokenType::EQUALEQ)
    elsif match('>')
      make_token(TokenType::EQARROW)
    else
      make_token(TokenType::EQUAL)
    end
  when '!'
    match('=') ? make_token(TokenType::BANGEQ) : make_token(TokenType::BANG)
  when '&'
    match('&') ? make_token(TokenType::AND) : make_token(TokenType::ILLEGAL)
  when '|'
    match('|') ? make_token(TokenType::OR) : make_token(TokenType::ILLEGAL)
  when '?' then make_token(TokenType::ILLEGAL)
  when '0'..'9' then number_token
  when '"' then string_token
  else
    alpha?(char) ? identifier_token : make_token(TokenType::ILLEGAL)
  end
end

#skip_line_commentObject



63
64
65
66
67
68
# File 'lib/taoism/lexer.rb', line 63

def skip_line_comment
  advance
  until at_end? || peek == "\n"
    advance
  end
end

#string_tokenObject



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/taoism/lexer.rb', line 127

def string_token
  loop do
    if at_end?
      @errors.add(:unterminated_string, "unterminated string", pos)
      return make_token(TokenType::ILLEGAL, "")
    end

    case peek
    when '"'
      advance
      return make_token(TokenType::STRING)
    when "\n"
      @errors.add(:unterminated_string, "unterminated string", pos)
      return make_token(TokenType::ILLEGAL, "")
    when '\\'
      advance
      if peek == '('
        advance
        while alpha_numeric?(peek)
          advance
        end

        @errors.add(:bad_interpolation, "expected ')'", pos) unless peek == ')'
        advance if peek == ')'

      else
        advance unless at_end?
      end
    else
      advance
    end
  end
end

#whitespace?(char) ⇒ Boolean

Returns:

  • (Boolean)


234
235
236
237
238
239
240
241
242
243
# File 'lib/taoism/lexer.rb', line 234

def whitespace?(char)
  case char
  when ' ', "\t", "\r" then true
  when "\n"
    @line += 1
    @col = 0
    true
  else false
  end
end