forthic 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +5 -0
- data/Guardfile +42 -0
- data/README.md +37 -0
- data/Rakefile +14 -0
- data/lib/forthic/code_location.rb +20 -0
- data/lib/forthic/forthic_error.rb +51 -0
- data/lib/forthic/forthic_module.rb +145 -0
- data/lib/forthic/global_module.rb +2341 -0
- data/lib/forthic/interpreter.rb +328 -0
- data/lib/forthic/positioned_string.rb +19 -0
- data/lib/forthic/token.rb +38 -0
- data/lib/forthic/tokenizer.rb +305 -0
- data/lib/forthic/variable.rb +34 -0
- data/lib/forthic/version.rb +5 -0
- data/lib/forthic/words/definition_word.rb +40 -0
- data/lib/forthic/words/end_array_word.rb +28 -0
- data/lib/forthic/words/end_module_word.rb +16 -0
- data/lib/forthic/words/imported_word.rb +27 -0
- data/lib/forthic/words/map_word.rb +169 -0
- data/lib/forthic/words/module_memo_bang_at_word.rb +22 -0
- data/lib/forthic/words/module_memo_bang_word.rb +21 -0
- data/lib/forthic/words/module_memo_word.rb +35 -0
- data/lib/forthic/words/module_word.rb +21 -0
- data/lib/forthic/words/push_value_word.rb +21 -0
- data/lib/forthic/words/start_module_word.rb +31 -0
- data/lib/forthic/words/word.rb +30 -0
- data/lib/forthic.rb +25 -0
- data/sig/forthic.rbs +4 -0
- metadata +72 -0
@@ -0,0 +1,328 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'forthic_error'
|
4
|
+
require_relative 'tokenizer'
|
5
|
+
require_relative 'token'
|
6
|
+
require_relative 'code_location'
|
7
|
+
require_relative 'positioned_string'
|
8
|
+
require_relative 'words/word'
|
9
|
+
require_relative 'words/push_value_word'
|
10
|
+
require_relative 'words/start_module_word'
|
11
|
+
require_relative 'words/end_module_word'
|
12
|
+
require_relative 'words/end_array_word'
|
13
|
+
require_relative 'words/definition_word'
|
14
|
+
require_relative 'global_module'
|
15
|
+
|
16
|
+
module Forthic
|
17
|
+
class Interpreter
|
18
|
+
attr_accessor :stack, :global_module, :app_module, :module_stack, :registered_modules,
|
19
|
+
:is_compiling, :should_stop, :is_memo_definition, :cur_definition,
|
20
|
+
:screens, :default_module_flags, :module_flags, :string_location,
|
21
|
+
:word_counts, :is_profiling, :start_profile_time, :timestamps
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@stack = []
|
25
|
+
@registered_modules = {}
|
26
|
+
@is_compiling = false
|
27
|
+
@should_stop = false
|
28
|
+
@is_memo_definition = false
|
29
|
+
@cur_definition = nil
|
30
|
+
@screens = {}
|
31
|
+
@default_module_flags = {}
|
32
|
+
@module_flags = {}
|
33
|
+
@string_location = nil
|
34
|
+
|
35
|
+
@global_module = GlobalModule.new(self)
|
36
|
+
@app_module = ForthicModule.new("", self)
|
37
|
+
@module_stack = [@app_module]
|
38
|
+
|
39
|
+
@word_counts = {}
|
40
|
+
@is_profiling = false
|
41
|
+
@start_profile_time = nil
|
42
|
+
@timestamps = []
|
43
|
+
end
|
44
|
+
|
45
|
+
def halt
|
46
|
+
@should_stop = true
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [ForthicModule]
|
50
|
+
def get_app_module
|
51
|
+
@app_module
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [CodeLocation, nil]
|
55
|
+
def get_string_location
|
56
|
+
@string_location
|
57
|
+
end
|
58
|
+
|
59
|
+
# @param [String] module_id
|
60
|
+
# @param [Hash] flags
|
61
|
+
def set_flags(module_id, flags)
|
62
|
+
@default_module_flags[module_id] = flags
|
63
|
+
@module_flags[module_id] = flags
|
64
|
+
end
|
65
|
+
|
66
|
+
# @param [String] module_id
|
67
|
+
# @return [Hash]
|
68
|
+
def get_flags(module_id)
|
69
|
+
module_flags = @module_flags[module_id] || {}
|
70
|
+
result = module_flags.dup
|
71
|
+
@module_flags[module_id] = @default_module_flags[module_id].dup
|
72
|
+
result
|
73
|
+
end
|
74
|
+
|
75
|
+
# @param [String] module_id
|
76
|
+
# @param [Hash] flags
|
77
|
+
def modify_flags(module_id, flags)
|
78
|
+
module_flags = @module_flags[module_id] || {}
|
79
|
+
@module_flags[module_id] = module_flags.merge(flags)
|
80
|
+
end
|
81
|
+
|
82
|
+
def reset
|
83
|
+
@stack = []
|
84
|
+
@app_module.variables = {}
|
85
|
+
@module_stack = [@app_module]
|
86
|
+
@is_compiling = false
|
87
|
+
@is_memo_definition = false
|
88
|
+
@cur_definition = nil
|
89
|
+
@string_location = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
# @param [String] screen_name
|
93
|
+
# @return [String]
|
94
|
+
def get_screen_forthic(screen_name)
|
95
|
+
screen = @screens[screen_name]
|
96
|
+
raise ForthicError.new("interpreter-199", "Unable to find screen \"#{screen_name}\"", "Hmmm...something went wrong. Please file a ticket if this continues to happen") unless screen
|
97
|
+
screen
|
98
|
+
end
|
99
|
+
|
100
|
+
# @param [String] string
|
101
|
+
# @param [CodeLocation, nil] reference_location
|
102
|
+
# @return [Boolean]
|
103
|
+
def run(string, reference_location = nil)
|
104
|
+
tokenizer = Tokenizer.new(string, reference_location)
|
105
|
+
run_with_tokenizer(tokenizer)
|
106
|
+
end
|
107
|
+
|
108
|
+
# @param [Tokenizer] tokenizer
|
109
|
+
# @return [Boolean]
|
110
|
+
def run_with_tokenizer(tokenizer)
|
111
|
+
token = nil
|
112
|
+
loop do
|
113
|
+
token = tokenizer.next_token
|
114
|
+
handle_token(token)
|
115
|
+
break if token.type == TokenType::EOS || @should_stop
|
116
|
+
next if [TokenType::START_DEF, TokenType::END_DEF, TokenType::COMMENT].include?(token.type) || @is_compiling
|
117
|
+
end
|
118
|
+
true
|
119
|
+
# rescue => e
|
120
|
+
# error = ForthicError.new("interpreter-213", "Ran into an error executing this '#{token.string}'", "If there is an unknown error in the stack details, please file a ticket so we can resolve it.", token.location)
|
121
|
+
# error.set_caught_error(e)
|
122
|
+
# raise error
|
123
|
+
end
|
124
|
+
|
125
|
+
# @return [ForthicModule]
|
126
|
+
def cur_module
|
127
|
+
@module_stack.last
|
128
|
+
end
|
129
|
+
|
130
|
+
# @param [String] name
|
131
|
+
# @return [ForthicModule]
|
132
|
+
def find_module(name)
|
133
|
+
result = @registered_modules[name]
|
134
|
+
raise ForthicError.new("interpreter-236", "Couldn't find '#{name}' module", "This is most likely a typo in your Forthic code. Please check to see if '#{name}' is properly spelled and that you have permission to access it") unless result
|
135
|
+
result
|
136
|
+
end
|
137
|
+
|
138
|
+
# @param [Object] val
|
139
|
+
def stack_push(val)
|
140
|
+
@stack.push(val)
|
141
|
+
end
|
142
|
+
|
143
|
+
# @return [Object]
|
144
|
+
def stack_pop
|
145
|
+
raise ForthicError.new("interpreter-251", "Stack underflow", "This happens when we expect something to be on the stack, but it's empty. This is caused by a logical error in the Forthic and can be resolved through debugging.") if @stack.empty?
|
146
|
+
result = @stack.pop
|
147
|
+
@string_location = result.is_a?(PositionedString) ? result.location : nil
|
148
|
+
result.is_a?(PositionedString) ? result.value_of : result
|
149
|
+
end
|
150
|
+
|
151
|
+
# @param [ForthicModule] mod
|
152
|
+
def module_stack_push(mod)
|
153
|
+
@module_stack.push(mod)
|
154
|
+
end
|
155
|
+
|
156
|
+
def module_stack_pop
|
157
|
+
@module_stack.pop
|
158
|
+
end
|
159
|
+
|
160
|
+
# @param [ForthicModule] mod
|
161
|
+
def register_module(mod)
|
162
|
+
@registered_modules[mod.name] = mod
|
163
|
+
end
|
164
|
+
|
165
|
+
# @param [ForthicModule] mod
|
166
|
+
def run_module_code(mod)
|
167
|
+
module_stack_push(mod)
|
168
|
+
run(mod.forthic_code)
|
169
|
+
module_stack_pop
|
170
|
+
rescue => e
|
171
|
+
error = ForthicError.new("interpreter-278", "Something went wrong when running the module #{mod.name}", "TODO: File a ticket")
|
172
|
+
error.set_caught_error(e)
|
173
|
+
raise error
|
174
|
+
end
|
175
|
+
|
176
|
+
# @param [String] name
|
177
|
+
# @return [Word, nil]
|
178
|
+
def find_word(name)
|
179
|
+
result = nil
|
180
|
+
@module_stack.reverse_each do |m|
|
181
|
+
result = m.find_word(name)
|
182
|
+
break if result
|
183
|
+
end
|
184
|
+
result ||= @global_module.find_word(name)
|
185
|
+
result
|
186
|
+
end
|
187
|
+
|
188
|
+
def start_profiling
|
189
|
+
@is_profiling = true
|
190
|
+
@timestamps = []
|
191
|
+
@start_profile_time = Time.now
|
192
|
+
add_timestamp("START")
|
193
|
+
@word_counts = {}
|
194
|
+
end
|
195
|
+
|
196
|
+
# @param [Word] word
|
197
|
+
def count_word(word)
|
198
|
+
return unless @is_profiling
|
199
|
+
@word_counts[word.name] ||= 0
|
200
|
+
@word_counts[word.name] += 1
|
201
|
+
end
|
202
|
+
|
203
|
+
def stop_profiling
|
204
|
+
add_timestamp("END")
|
205
|
+
@is_profiling = false
|
206
|
+
end
|
207
|
+
|
208
|
+
# @param [String] label
|
209
|
+
def add_timestamp(label)
|
210
|
+
return unless @is_profiling
|
211
|
+
timestamp = { label: label, time_ms: (Time.now - @start_profile_time) * 1000 }
|
212
|
+
@timestamps.push(timestamp)
|
213
|
+
end
|
214
|
+
|
215
|
+
# @return [Array<Hash>]
|
216
|
+
def word_histogram
|
217
|
+
@word_counts.map { |name, count| { word: name, count: count } }.sort_by { |item| -item[:count] }
|
218
|
+
end
|
219
|
+
|
220
|
+
# @return [Array<Hash>]
|
221
|
+
def profile_timestamps
|
222
|
+
@timestamps
|
223
|
+
end
|
224
|
+
|
225
|
+
# @param [Token] token
|
226
|
+
def handle_token(token)
|
227
|
+
case token.type
|
228
|
+
when TokenType::STRING then handle_string_token(token)
|
229
|
+
when TokenType::COMMENT then handle_comment_token(token)
|
230
|
+
when TokenType::START_ARRAY then handle_start_array_token(token)
|
231
|
+
when TokenType::END_ARRAY then handle_end_array_token(token)
|
232
|
+
when TokenType::START_MODULE then handle_start_module_token(token)
|
233
|
+
when TokenType::END_MODULE then handle_end_module_token(token)
|
234
|
+
when TokenType::START_DEF then handle_start_definition_token(token)
|
235
|
+
when TokenType::START_MEMO then handle_start_memo_token(token)
|
236
|
+
when TokenType::END_DEF then handle_end_definition_token(token)
|
237
|
+
when TokenType::WORD then handle_word_token(token)
|
238
|
+
when TokenType::EOS then return
|
239
|
+
else
|
240
|
+
raise ForthicError.new("interpreter-362", "Hmmm...the interpreter doesn't know what to make of '#{token.string}'", "This is most likely caused by a typo in the Forthic code and can be resolved by debugging.", token.location)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# @param [Token] token
|
245
|
+
def handle_string_token(token)
|
246
|
+
value = PositionedString.new(token.string, token.location)
|
247
|
+
handle_word(PushValueWord.new("<string>", value))
|
248
|
+
end
|
249
|
+
|
250
|
+
# @param [Token] token
|
251
|
+
def handle_start_module_token(token)
|
252
|
+
word = StartModuleWord.new(token.string)
|
253
|
+
@cur_definition.add_word(word) if @is_compiling
|
254
|
+
count_word(word)
|
255
|
+
word.execute(self)
|
256
|
+
end
|
257
|
+
|
258
|
+
# @param [Token] _token
|
259
|
+
def handle_end_module_token(_token)
|
260
|
+
word = EndModuleWord.new
|
261
|
+
@cur_definition.add_word(word) if @is_compiling
|
262
|
+
count_word(word)
|
263
|
+
word.execute(self)
|
264
|
+
end
|
265
|
+
|
266
|
+
# @param [Token] token
|
267
|
+
def handle_start_array_token(token)
|
268
|
+
handle_word(PushValueWord.new("<start_array_token>", token))
|
269
|
+
end
|
270
|
+
|
271
|
+
# @param [Token] _token
|
272
|
+
def handle_end_array_token(_token)
|
273
|
+
handle_word(EndArrayWord.new)
|
274
|
+
end
|
275
|
+
|
276
|
+
# @param [Token] _token
|
277
|
+
def handle_comment_token(_token)
|
278
|
+
# Handle comment token (no-op)
|
279
|
+
end
|
280
|
+
|
281
|
+
# @param [Token] token
|
282
|
+
def handle_start_definition_token(token)
|
283
|
+
raise ForthicError.new("interpreter-407", "A definition was started while an existing definition was not ended", "This is probably caused by a missing semicolon. To resolve, ensure that all word definitions end with semicolons.", token.location) if @is_compiling
|
284
|
+
@cur_definition = DefinitionWord.new(token.string)
|
285
|
+
@is_compiling = true
|
286
|
+
@is_memo_definition = false
|
287
|
+
end
|
288
|
+
|
289
|
+
# @param [Token] token
|
290
|
+
def handle_start_memo_token(token)
|
291
|
+
raise ForthicError.new("interpreter-420", "A memo definition was started while an existing definition was not ended", "This is probably caused by a missing semicolon. To resolve, ensure that all word definitions end with semicolons.", token.location) if @is_compiling
|
292
|
+
@cur_definition = DefinitionWord.new(token.string)
|
293
|
+
@is_compiling = true
|
294
|
+
@is_memo_definition = true
|
295
|
+
end
|
296
|
+
|
297
|
+
# @param [Token] token
|
298
|
+
def handle_end_definition_token(token)
|
299
|
+
raise ForthicError.new("interpreter-433", "A definition was ended when one hadn't been started yet", "This is probably caused by an extra semicolon. To resolve, ensure that there are no spurious semicolons in the Forthic code.", token.location) unless @is_compiling
|
300
|
+
raise ForthicError.new("interpreter-440", "Cannot finish definition because there is no current definition", "Please file a ticket", token.location) unless @cur_definition
|
301
|
+
if @is_memo_definition
|
302
|
+
cur_module.add_memo_words(@cur_definition)
|
303
|
+
else
|
304
|
+
cur_module.add_word(@cur_definition)
|
305
|
+
end
|
306
|
+
@is_compiling = false
|
307
|
+
end
|
308
|
+
|
309
|
+
# @param [Token] token
|
310
|
+
def handle_word_token(token)
|
311
|
+
word = find_word(token.string)
|
312
|
+
raise ForthicError.new("interpreter-458", "Could not find word: #{token.string}", "Check to see if you have a typo in your word or the definition of that word", token.location) unless word
|
313
|
+
handle_word(word, token.location)
|
314
|
+
end
|
315
|
+
|
316
|
+
# @param [Word] word
|
317
|
+
# @param [CodeLocation, nil] location
|
318
|
+
def handle_word(word, location = nil)
|
319
|
+
if @is_compiling
|
320
|
+
word.set_location(location)
|
321
|
+
@cur_definition.add_word(word)
|
322
|
+
else
|
323
|
+
count_word(word)
|
324
|
+
word.execute(self)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Forthic
|
4
|
+
class PositionedString
|
5
|
+
attr_accessor :string, :location
|
6
|
+
|
7
|
+
# @string [String] the string value
|
8
|
+
# @location [CodeLocation] the location of the string in the code
|
9
|
+
def initialize(string, location)
|
10
|
+
@string = string
|
11
|
+
@location = location
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [String]
|
15
|
+
def value_of
|
16
|
+
@string
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Forthic
|
4
|
+
module TokenType
|
5
|
+
STRING = 1
|
6
|
+
COMMENT = 2
|
7
|
+
START_ARRAY = 3
|
8
|
+
END_ARRAY = 4
|
9
|
+
START_MODULE = 5
|
10
|
+
END_MODULE = 6
|
11
|
+
START_DEF = 7
|
12
|
+
END_DEF = 8
|
13
|
+
START_MEMO = 9
|
14
|
+
WORD = 10
|
15
|
+
EOS = 11
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
module Forthic
|
21
|
+
class Token
|
22
|
+
attr_reader :type, :value, :location
|
23
|
+
|
24
|
+
# @param [TokenType] type
|
25
|
+
# @param [String] value
|
26
|
+
# @param [CodeLocation] location
|
27
|
+
def initialize(type, value, location)
|
28
|
+
@type = type
|
29
|
+
@value = value
|
30
|
+
@location = location
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [String]
|
34
|
+
def string
|
35
|
+
@value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,305 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Forthic
|
4
|
+
class Tokenizer
|
5
|
+
attr_accessor :reference_location, :line, :column, :input_string, :input_pos,
|
6
|
+
:whitespace, :quote_chars, :token_start_pos, :token_end_pos,
|
7
|
+
:token_line, :token_column, :token_string
|
8
|
+
|
9
|
+
# @param [String] string
|
10
|
+
# @param [CodeLocation, nil] reference_location
|
11
|
+
def initialize(string, reference_location = nil)
|
12
|
+
reference_location ||= CodeLocation.new(screen_name: "<ad-hoc>")
|
13
|
+
@reference_location = reference_location
|
14
|
+
@line = reference_location.line
|
15
|
+
@column = reference_location.column
|
16
|
+
@input_string = unescape_string(string)
|
17
|
+
@input_pos = 0
|
18
|
+
@whitespace = [" ", "\t", "\n", "\r", "(", ")", ","]
|
19
|
+
@quote_chars = ['"', "'"]
|
20
|
+
|
21
|
+
# Token info
|
22
|
+
@token_start_pos = 0
|
23
|
+
@token_end_pos = 0
|
24
|
+
@token_line = 0
|
25
|
+
@token_column = 0
|
26
|
+
@token_string = ""
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Token]
|
30
|
+
def next_token
|
31
|
+
clear_token_string
|
32
|
+
transition_from_START
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [String] string
|
36
|
+
# @return [String]
|
37
|
+
def unescape_string(string)
|
38
|
+
string
|
39
|
+
end
|
40
|
+
|
41
|
+
def clear_token_string
|
42
|
+
@token_string = ""
|
43
|
+
end
|
44
|
+
|
45
|
+
def note_start_token
|
46
|
+
@token_start_pos = @input_pos + @reference_location.start_pos
|
47
|
+
@token_line = @line
|
48
|
+
@token_column = @column
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param [String] char
|
52
|
+
# @return [Boolean]
|
53
|
+
def is_whitespace(char)
|
54
|
+
@whitespace.include?(char)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param [String] char
|
58
|
+
# @return [Boolean]
|
59
|
+
def is_quote(char)
|
60
|
+
@quote_chars.include?(char)
|
61
|
+
end
|
62
|
+
|
63
|
+
# @param [Integer] index
|
64
|
+
# @param [String] char
|
65
|
+
# @return [Boolean]
|
66
|
+
def is_triple_quote(index, char)
|
67
|
+
return false unless is_quote(char)
|
68
|
+
return false if index + 2 >= @input_string.length
|
69
|
+
@input_string[index + 1] == char && @input_string[index + 2] == char
|
70
|
+
end
|
71
|
+
|
72
|
+
# @param [Integer] index
|
73
|
+
# @return [Boolean]
|
74
|
+
def is_start_memo(index)
|
75
|
+
return false if index + 1 >= @input_string.length
|
76
|
+
@input_string[index] == "@" && @input_string[index + 1] == ":"
|
77
|
+
end
|
78
|
+
|
79
|
+
# @param [Integer] num_chars
|
80
|
+
def advance_position(num_chars)
|
81
|
+
if num_chars >= 0
|
82
|
+
num_chars.times do
|
83
|
+
if @input_string[@input_pos] == "\n"
|
84
|
+
@line += 1
|
85
|
+
@column = 1
|
86
|
+
else
|
87
|
+
@column += 1
|
88
|
+
end
|
89
|
+
@input_pos += 1
|
90
|
+
end
|
91
|
+
else
|
92
|
+
(-num_chars).times do
|
93
|
+
@input_pos -= 1
|
94
|
+
raise Forthic::Error, "Invalid position" if @input_pos < 0 || @column < 0
|
95
|
+
if @input_string[@input_pos] == "\n"
|
96
|
+
@line -= 1
|
97
|
+
@column = 1
|
98
|
+
else
|
99
|
+
@column -= 1
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# @return [CodeLocation]
|
106
|
+
def get_token_location
|
107
|
+
CodeLocation.new(
|
108
|
+
screen_name: @reference_location.screen_name,
|
109
|
+
line: @token_line,
|
110
|
+
column: @token_column,
|
111
|
+
start_pos: @token_start_pos,
|
112
|
+
end_pos: @token_start_pos + @token_string.length
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
# @return [Token]
|
117
|
+
def transition_from_START
|
118
|
+
while @input_pos < @input_string.length
|
119
|
+
char = @input_string[@input_pos]
|
120
|
+
note_start_token
|
121
|
+
advance_position(1)
|
122
|
+
|
123
|
+
next if is_whitespace(char)
|
124
|
+
case char
|
125
|
+
when "#"
|
126
|
+
return transition_from_COMMENT
|
127
|
+
when ":"
|
128
|
+
return transition_from_START_DEFINITION
|
129
|
+
when ";"
|
130
|
+
@token_string = char
|
131
|
+
return Token.new(TokenType::END_DEF, char, get_token_location)
|
132
|
+
when "["
|
133
|
+
@token_string = char
|
134
|
+
return Token.new(TokenType::START_ARRAY, char, get_token_location)
|
135
|
+
when "]"
|
136
|
+
@token_string = char
|
137
|
+
return Token.new(TokenType::END_ARRAY, char, get_token_location)
|
138
|
+
when "{"
|
139
|
+
return transition_from_GATHER_MODULE
|
140
|
+
when "}"
|
141
|
+
@token_string = char
|
142
|
+
return Token.new(TokenType::END_MODULE, char, get_token_location)
|
143
|
+
else
|
144
|
+
if is_start_memo(@input_pos - 1)
|
145
|
+
advance_position(1)
|
146
|
+
return transition_from_START_MEMO
|
147
|
+
elsif is_triple_quote(@input_pos - 1, char)
|
148
|
+
advance_position(2)
|
149
|
+
return transition_from_GATHER_TRIPLE_QUOTE_STRING(char)
|
150
|
+
elsif is_quote(char)
|
151
|
+
return transition_from_GATHER_STRING(char)
|
152
|
+
else
|
153
|
+
advance_position(-1)
|
154
|
+
return transition_from_GATHER_WORD
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
Token.new(TokenType::EOS, "", get_token_location)
|
159
|
+
end
|
160
|
+
|
161
|
+
# @return [Token]
|
162
|
+
def transition_from_COMMENT
|
163
|
+
note_start_token
|
164
|
+
while @input_pos < @input_string.length
|
165
|
+
char = @input_string[@input_pos]
|
166
|
+
@token_string += char
|
167
|
+
advance_position(1)
|
168
|
+
break if char == "\n"
|
169
|
+
end
|
170
|
+
Token.new(TokenType::COMMENT, @token_string, get_token_location)
|
171
|
+
end
|
172
|
+
|
173
|
+
# @return [Token]
|
174
|
+
def transition_from_START_DEFINITION
|
175
|
+
while @input_pos < @input_string.length
|
176
|
+
char = @input_string[@input_pos]
|
177
|
+
advance_position(1)
|
178
|
+
next if is_whitespace(char)
|
179
|
+
if is_quote(char)
|
180
|
+
raise Forthic::Error, "Definition names can't have quotes in them"
|
181
|
+
else
|
182
|
+
advance_position(-1)
|
183
|
+
return transition_from_GATHER_DEFINITION_NAME
|
184
|
+
end
|
185
|
+
end
|
186
|
+
raise Forthic::Error, "Got EOS in START_DEFINITION"
|
187
|
+
end
|
188
|
+
|
189
|
+
# @return [Token]
|
190
|
+
def transition_from_START_MEMO
|
191
|
+
while @input_pos < @input_string.length
|
192
|
+
char = @input_string[@input_pos]
|
193
|
+
advance_position(1)
|
194
|
+
next if is_whitespace(char)
|
195
|
+
if is_quote(char)
|
196
|
+
raise Forthic::Error, "Definitions shouldn't have quotes in them"
|
197
|
+
else
|
198
|
+
advance_position(-1)
|
199
|
+
return transition_from_GATHER_MEMO_NAME
|
200
|
+
end
|
201
|
+
end
|
202
|
+
raise Forthic::Error, "Got EOS in START_MEMO"
|
203
|
+
end
|
204
|
+
|
205
|
+
def gather_definition_name
|
206
|
+
while @input_pos < @input_string.length
|
207
|
+
char = @input_string[@input_pos]
|
208
|
+
advance_position(1)
|
209
|
+
break if is_whitespace(char)
|
210
|
+
if is_quote(char)
|
211
|
+
raise Forthic::Error, "Definition names can't have quotes in them"
|
212
|
+
elsif ["[", "]", "{", "}"].include?(char)
|
213
|
+
raise Forthic::Error, "Definitions can't have '#{char}' in them"
|
214
|
+
else
|
215
|
+
@token_string += char
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# @return [Token]
|
221
|
+
def transition_from_GATHER_DEFINITION_NAME
|
222
|
+
note_start_token
|
223
|
+
gather_definition_name
|
224
|
+
Token.new(TokenType::START_DEF, @token_string, get_token_location)
|
225
|
+
end
|
226
|
+
|
227
|
+
# @return [Token]
|
228
|
+
def transition_from_GATHER_MEMO_NAME
|
229
|
+
note_start_token
|
230
|
+
gather_definition_name
|
231
|
+
Token.new(TokenType::START_MEMO, @token_string, get_token_location)
|
232
|
+
end
|
233
|
+
|
234
|
+
# @return [Token]
|
235
|
+
def transition_from_GATHER_MODULE
|
236
|
+
note_start_token
|
237
|
+
while @input_pos < @input_string.length
|
238
|
+
char = @input_string[@input_pos]
|
239
|
+
advance_position(1)
|
240
|
+
break if is_whitespace(char)
|
241
|
+
if char == "}"
|
242
|
+
advance_position(-1)
|
243
|
+
break
|
244
|
+
else
|
245
|
+
@token_string += char
|
246
|
+
end
|
247
|
+
end
|
248
|
+
Token.new(TokenType::START_MODULE, @token_string, get_token_location)
|
249
|
+
end
|
250
|
+
|
251
|
+
# @param [String] delim
|
252
|
+
# @return [Token]
|
253
|
+
def transition_from_GATHER_TRIPLE_QUOTE_STRING(delim)
|
254
|
+
note_start_token
|
255
|
+
string_delimiter = delim
|
256
|
+
|
257
|
+
while @input_pos < @input_string.length
|
258
|
+
char = @input_string[@input_pos]
|
259
|
+
if char == string_delimiter && is_triple_quote(@input_pos, char)
|
260
|
+
advance_position(3)
|
261
|
+
return Token.new(TokenType::STRING, @token_string, get_token_location)
|
262
|
+
else
|
263
|
+
advance_position(1)
|
264
|
+
@token_string += char
|
265
|
+
end
|
266
|
+
end
|
267
|
+
raise Forthic::Error, "Unterminated string: #{delim * 3}#{@token_string}"
|
268
|
+
end
|
269
|
+
|
270
|
+
# @param [String] delim
|
271
|
+
# @return [Token]
|
272
|
+
def transition_from_GATHER_STRING(delim)
|
273
|
+
note_start_token
|
274
|
+
string_delimiter = delim
|
275
|
+
|
276
|
+
while @input_pos < @input_string.length
|
277
|
+
char = @input_string[@input_pos]
|
278
|
+
advance_position(1)
|
279
|
+
if char == string_delimiter
|
280
|
+
return Token.new(TokenType::STRING, @token_string, get_token_location)
|
281
|
+
else
|
282
|
+
@token_string += char
|
283
|
+
end
|
284
|
+
end
|
285
|
+
raise Forthic::Error, "Unterminated string: #{delim}#{@token_string}"
|
286
|
+
end
|
287
|
+
|
288
|
+
# @return [Token]
|
289
|
+
def transition_from_GATHER_WORD
|
290
|
+
note_start_token
|
291
|
+
while @input_pos < @input_string.length
|
292
|
+
char = @input_string[@input_pos]
|
293
|
+
advance_position(1)
|
294
|
+
break if is_whitespace(char)
|
295
|
+
if [";", "[", "]", "{", "}", "#"].include?(char)
|
296
|
+
advance_position(-1)
|
297
|
+
break
|
298
|
+
else
|
299
|
+
@token_string += char
|
300
|
+
end
|
301
|
+
end
|
302
|
+
Token.new(TokenType::WORD, @token_string, get_token_location)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|