mix-language 1.0.0 → 1.0.1
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/bin/mix-compile +3 -14
- data/lib/javascript/base/array.js +1 -1
- data/lib/javascript/base/env.js +2 -1
- data/lib/javascript/base/eval.js +6 -7
- data/lib/javascript/base/false.js +1 -1
- data/lib/javascript/base/function.js +1 -1
- data/lib/javascript/base/null.js +1 -1
- data/lib/javascript/base/number.js +1 -1
- data/lib/javascript/base/object.js +1 -1
- data/lib/javascript/base/string.js +1 -1
- data/lib/javascript/base/true.js +1 -1
- data/lib/javascript/dom/div.js +1 -1
- data/lib/javascript/dom/drag.js +1 -1
- data/lib/javascript/dom/element.js +1 -1
- data/lib/javascript/dom/event.js +1 -1
- data/lib/javascript/dom/mouse.js +1 -1
- data/lib/javascript/dom/table.js +1 -1
- data/lib/javascript/dom/td.js +1 -1
- data/lib/javascript/dom/text.js +1 -1
- data/lib/javascript/dom/tr.js +1 -1
- data/lib/ruby/compiler.rb +38 -501
- data/lib/ruby/mix.grammar +13 -13
- data/lib/ruby/mix.tab.rb +453 -464
- data/lib/ruby/new_tokenizer.rb +290 -0
- data/lib/ruby/parser.rb +71 -0
- data/lib/ruby/precompiler.rb +106 -0
- data/lib/ruby/tokenizer.rb +268 -0
- data/lib/ruby/translator.rb +179 -0
- metadata +7 -2
@@ -0,0 +1,290 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright © 2011-2012 Jesse Sielaff
|
4
|
+
#
|
5
|
+
|
6
|
+
class MixTokenizer
|
7
|
+
def access?
|
8
|
+
@state == :access
|
9
|
+
end
|
10
|
+
|
11
|
+
def access!
|
12
|
+
@state = :access
|
13
|
+
end
|
14
|
+
|
15
|
+
def access_token
|
16
|
+
add_token '·' + @token
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_token (token, value = token)
|
20
|
+
@token_stream << [token, [value, [@filename, @line]]]
|
21
|
+
end
|
22
|
+
|
23
|
+
def adjust_indent
|
24
|
+
current_indent = @token.length
|
25
|
+
previous_indent = @indent_stack.last
|
26
|
+
|
27
|
+
if current_indent > previous_indent
|
28
|
+
indent_token
|
29
|
+
elsif current_indent < previous_indent
|
30
|
+
unless @indent_stack.include?(current_indent)
|
31
|
+
raise SyntaxError, "Indent error: #{@filename}:#{@line}"
|
32
|
+
end
|
33
|
+
|
34
|
+
outdent_token until @indent_stack.last == current_indent
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def comparison_token
|
39
|
+
add_token @token + '='
|
40
|
+
end
|
41
|
+
|
42
|
+
def double_token?
|
43
|
+
lookahead?(@token) do
|
44
|
+
@token *= 2
|
45
|
+
yield
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def getch
|
50
|
+
@script[@pointer += 1]
|
51
|
+
end
|
52
|
+
|
53
|
+
def handle_token
|
54
|
+
case @token
|
55
|
+
when ?~
|
56
|
+
lookahead?(?~) { skip_to_end_of_line } || skip_whitespace
|
57
|
+
|
58
|
+
when ' ', ?\t
|
59
|
+
no_access!
|
60
|
+
|
61
|
+
when ?\n
|
62
|
+
return if line_empty?
|
63
|
+
|
64
|
+
@token = ''
|
65
|
+
scan_token(/ /)
|
66
|
+
return if lookahead?(?\n) { @empty_lines += 1; unscan }
|
67
|
+
|
68
|
+
newline_token
|
69
|
+
adjust_indent
|
70
|
+
|
71
|
+
when ??, ?., ?;, ?,, ?(, ?{
|
72
|
+
no_access!
|
73
|
+
plain_token
|
74
|
+
|
75
|
+
when ?], ?}, ?)
|
76
|
+
access!
|
77
|
+
plain_token
|
78
|
+
|
79
|
+
when ?[, ?:, ?#
|
80
|
+
(access?) ? access_token : plain_token
|
81
|
+
no_access!
|
82
|
+
|
83
|
+
when ?!, ?=
|
84
|
+
no_access!
|
85
|
+
lookahead?(?=) { comparison_token } || plain_token
|
86
|
+
|
87
|
+
when ?+, ?*, ?/, ?%
|
88
|
+
no_access!
|
89
|
+
lookahead?(?=) { operator_assignment_token } || plain_token
|
90
|
+
|
91
|
+
when ?-
|
92
|
+
return if lookahead?(?=) { operator_assignment_token }
|
93
|
+
return if lookahead?(/[0-9]/) { negative_token; unscan }
|
94
|
+
|
95
|
+
(unary_minus_possible?) ? unary_minus_token : plain_token
|
96
|
+
no_access!
|
97
|
+
|
98
|
+
when ?>, ?<
|
99
|
+
no_access!
|
100
|
+
return if double_token? { lookahead?(?=) { operator_assignment_token } || plain_token }
|
101
|
+
lookahead?(?=) { comparison_token } || plain_token
|
102
|
+
|
103
|
+
when ?&, ?|
|
104
|
+
no_access!
|
105
|
+
return if double_token? { lookahead?(?=) { operator_assignment_token } || plain_token }
|
106
|
+
plain_token
|
107
|
+
|
108
|
+
when /[0-9]/
|
109
|
+
access!
|
110
|
+
scan_token(/\./)
|
111
|
+
scan_token(/[0-9]/)
|
112
|
+
number_token
|
113
|
+
|
114
|
+
when /[A-Z]/
|
115
|
+
no_access!
|
116
|
+
scan_token(/[_a-zA-Z0-9]/)
|
117
|
+
mixin_token
|
118
|
+
|
119
|
+
when ?'
|
120
|
+
access!
|
121
|
+
@token = ""
|
122
|
+
scan_token(/[^']/)
|
123
|
+
getch
|
124
|
+
|
125
|
+
lookahead?(?:) do
|
126
|
+
return if lookahead?(/[^a-z]/) do
|
127
|
+
no_access!
|
128
|
+
key_token
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
string_token
|
133
|
+
|
134
|
+
when /[_a-z]/
|
135
|
+
access!
|
136
|
+
scan_token(/[_a-zA-Z0-9]/)
|
137
|
+
scan_token(/[?!]/)
|
138
|
+
|
139
|
+
lookahead?(?:) do
|
140
|
+
return if lookahead?(/[^a-z]/) do
|
141
|
+
no_access!
|
142
|
+
key_token
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
(keyword?) ? keyword_token : identifier_token
|
147
|
+
|
148
|
+
case @token = getch
|
149
|
+
when ?:, ?#
|
150
|
+
access_token
|
151
|
+
when ?[
|
152
|
+
access_token
|
153
|
+
no_access!
|
154
|
+
else
|
155
|
+
unscan
|
156
|
+
end
|
157
|
+
|
158
|
+
else
|
159
|
+
raise SyntaxError, "Invalid token `#{@token}': #{@filename}:#{@line}"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def identifier_token
|
164
|
+
add_token :IDENTIFIER, @token.to_sym
|
165
|
+
end
|
166
|
+
|
167
|
+
def indent_token
|
168
|
+
@indent_stack << @token.length
|
169
|
+
add_token :INDENT
|
170
|
+
end
|
171
|
+
|
172
|
+
def key_token
|
173
|
+
add_token :KEY, @token.to_sym
|
174
|
+
end
|
175
|
+
|
176
|
+
def keyword?
|
177
|
+
%w|app break case
|
178
|
+
elsif else false
|
179
|
+
if null return
|
180
|
+
self switch true
|
181
|
+
unless until while|.include? @token
|
182
|
+
end
|
183
|
+
|
184
|
+
def keyword_token
|
185
|
+
add_token @token.upcase.to_sym
|
186
|
+
end
|
187
|
+
|
188
|
+
def line_empty?
|
189
|
+
if @state == :newline
|
190
|
+
@empty_lines += 1
|
191
|
+
return true
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def mixin_token
|
196
|
+
add_token :MIXIN, @token.to_sym
|
197
|
+
end
|
198
|
+
|
199
|
+
def negative_token
|
200
|
+
add_token :NEGATIVE
|
201
|
+
end
|
202
|
+
|
203
|
+
def newline_token
|
204
|
+
add_token :NEWLINE
|
205
|
+
|
206
|
+
@line += (@empty_lines + 1)
|
207
|
+
@empty_lines = 0
|
208
|
+
@state = :newline
|
209
|
+
end
|
210
|
+
|
211
|
+
def lookahead? (pattern)
|
212
|
+
if getch =~ Regexp.new(pattern)
|
213
|
+
yield
|
214
|
+
true
|
215
|
+
else
|
216
|
+
unscan
|
217
|
+
false
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def no_access!
|
222
|
+
@state = :no_access
|
223
|
+
end
|
224
|
+
|
225
|
+
def number_token
|
226
|
+
add_token :NUMBER, @token.to_i
|
227
|
+
end
|
228
|
+
|
229
|
+
def operator_assignment_token
|
230
|
+
add_token '·=', @token
|
231
|
+
end
|
232
|
+
|
233
|
+
def outdent_token
|
234
|
+
@indent_stack.pop
|
235
|
+
add_token :OUTDENT
|
236
|
+
end
|
237
|
+
|
238
|
+
def plain_token
|
239
|
+
add_token @token
|
240
|
+
end
|
241
|
+
|
242
|
+
def scan_token (pattern)
|
243
|
+
while (c = getch) =~ pattern
|
244
|
+
@token << c
|
245
|
+
end
|
246
|
+
|
247
|
+
unscan
|
248
|
+
end
|
249
|
+
|
250
|
+
def skip_to_end_of_line
|
251
|
+
:skip until getch =~ /\n/
|
252
|
+
unscan
|
253
|
+
end
|
254
|
+
|
255
|
+
def skip_whitespace
|
256
|
+
:skip while getch =~ /\s/
|
257
|
+
unscan
|
258
|
+
end
|
259
|
+
|
260
|
+
def string_token
|
261
|
+
add_token :STRING, @token
|
262
|
+
end
|
263
|
+
|
264
|
+
def tokenize (filename, script, token_stream)
|
265
|
+
@filename = filename
|
266
|
+
@script = script
|
267
|
+
@token_stream = token_stream
|
268
|
+
@line = 1
|
269
|
+
@pointer = -1
|
270
|
+
@empty_lines = 0
|
271
|
+
@indent_stack = [0]
|
272
|
+
@state = :newline
|
273
|
+
|
274
|
+
handle_token while @token = getch
|
275
|
+
|
276
|
+
puts @token_stream.inspect
|
277
|
+
end
|
278
|
+
|
279
|
+
def unary_minus_possible?
|
280
|
+
@state == :begin || @state == :newline
|
281
|
+
end
|
282
|
+
|
283
|
+
def unary_minus_token
|
284
|
+
add_token '-·'
|
285
|
+
end
|
286
|
+
|
287
|
+
def unscan
|
288
|
+
@pointer -= 1
|
289
|
+
end
|
290
|
+
end
|
data/lib/ruby/parser.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright © 2011-2012 Jesse Sielaff
|
4
|
+
#
|
5
|
+
|
6
|
+
class MixParser
|
7
|
+
def append_statement (outermost_statement, final_statement)
|
8
|
+
current = outermost_statement
|
9
|
+
|
10
|
+
current = current[:values][1] while current[:values][1]
|
11
|
+
current[:values][1] = final_statement
|
12
|
+
|
13
|
+
outermost_statement
|
14
|
+
end
|
15
|
+
|
16
|
+
def newline (statement_node)
|
17
|
+
statement_node[:values][0] = node(:newline, *@location, statement_node[:values][0])
|
18
|
+
statement_node
|
19
|
+
end
|
20
|
+
|
21
|
+
def next_token
|
22
|
+
@token_stream.shift || [false, false]
|
23
|
+
end
|
24
|
+
|
25
|
+
def node (type, *values)
|
26
|
+
{ type: type, values: values }
|
27
|
+
end
|
28
|
+
|
29
|
+
def on_error (token, value, stack)
|
30
|
+
if value
|
31
|
+
value, file, line = value[0].inspect, *value[1]
|
32
|
+
else
|
33
|
+
value, file, line = 'EOF', *@location
|
34
|
+
end
|
35
|
+
|
36
|
+
raise SyntaxError, "Unexpected token #{token_to_str(token)} (#{value}): #{file}:#{line}".gsub(?",'\"')
|
37
|
+
end
|
38
|
+
|
39
|
+
def parameter (name)
|
40
|
+
@variables[:parameters] << name
|
41
|
+
@variables[:local] << name
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse (tokens)
|
45
|
+
@location = ['',0]
|
46
|
+
@token_stream = tokens
|
47
|
+
@variables = { parameters: [], local: [], embedded: [], prev: nil }
|
48
|
+
|
49
|
+
return do_parse
|
50
|
+
end
|
51
|
+
|
52
|
+
def pop_variables
|
53
|
+
vars = [@variables[:parameters], @variables[:embedded]]
|
54
|
+
@variables = @variables[:prev]
|
55
|
+
|
56
|
+
return vars
|
57
|
+
end
|
58
|
+
|
59
|
+
def push_variables
|
60
|
+
embedded = @variables[:local] + @variables[:embedded]
|
61
|
+
@variables = { parameters: [], local: [], embedded: embedded, prev: @variables }
|
62
|
+
end
|
63
|
+
|
64
|
+
def variable (name)
|
65
|
+
unless (@variables[:local] + @variables[:embedded]).include?(name)
|
66
|
+
@variables[:local] << name
|
67
|
+
end
|
68
|
+
|
69
|
+
node :variable, name
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright © 2011-2012 Jesse Sielaff
|
4
|
+
#
|
5
|
+
require 'strscan'
|
6
|
+
|
7
|
+
class MixPrecompiler
|
8
|
+
JS_BASE_FILES = %w{
|
9
|
+
env eval object array
|
10
|
+
false function null
|
11
|
+
number string true }
|
12
|
+
.each_with_object({}) do |x,h|
|
13
|
+
filename = File.expand_path("../../javascript/base/#{x}.js", __FILE__)
|
14
|
+
h[filename] = File.read(filename)
|
15
|
+
end
|
16
|
+
|
17
|
+
JS_OBJECT_TYPES = %w=
|
18
|
+
ARRAY ELEMENT EVENT
|
19
|
+
FALSE FUNCTION NULL
|
20
|
+
NUMBER OBJECT STRING
|
21
|
+
TEXT TRUE =
|
22
|
+
|
23
|
+
def build_js_string
|
24
|
+
@js_string = @js_files.map {|*,script| script }.join ?\n
|
25
|
+
|
26
|
+
replace_type_variables
|
27
|
+
replace_case_markers
|
28
|
+
minify
|
29
|
+
|
30
|
+
@js_string = <<-JAVASCRIPT
|
31
|
+
mix_result_should_be_undefined=function(){var SYMBOLS='...Replace with symbol table...';#{@js_string};
|
32
|
+
window.onload=function(){startup();evaluate('...Replace with AST nodes...');}}();
|
33
|
+
JAVASCRIPT
|
34
|
+
end
|
35
|
+
|
36
|
+
def collect_scripts
|
37
|
+
@mix_files = {}
|
38
|
+
@js_files = JS_BASE_FILES.dup
|
39
|
+
|
40
|
+
scan_mix_file(@primary_filename)
|
41
|
+
end
|
42
|
+
|
43
|
+
def minify
|
44
|
+
@js_string.gsub!(/\s*\/\*.*?\*\/\s*/m, ?\n)
|
45
|
+
@js_string.gsub!(/\s*\n\s*/, ?\n)
|
46
|
+
@js_string.gsub!(/\s*([{;:,=])\s*/, '\1')
|
47
|
+
@js_string.gsub!("\n}", ?})
|
48
|
+
end
|
49
|
+
|
50
|
+
def precompile (filename)
|
51
|
+
@primary_filename = filename
|
52
|
+
collect_scripts
|
53
|
+
build_js_string
|
54
|
+
|
55
|
+
return @mix_files, @js_string
|
56
|
+
end
|
57
|
+
|
58
|
+
def replace_case_markers
|
59
|
+
MixTranslator::NODE_TYPES.each do |k,v|
|
60
|
+
@js_string.gsub!("case #{k}:","case #{v}:")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def replace_type_variables
|
65
|
+
JS_OBJECT_TYPES.each_with_index do |t,i|
|
66
|
+
@js_string.gsub!("#{t}_TYPE", i.to_s)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def scan_includes (script)
|
71
|
+
ss = StringScanner.new(script)
|
72
|
+
js_filenames = []
|
73
|
+
|
74
|
+
while ss.scan(/~~\s*(.*)\.(js|mix)\s*\n/)
|
75
|
+
basename = ss[1]
|
76
|
+
type = ss[2]
|
77
|
+
filename = "#{basename}.#{type}"
|
78
|
+
|
79
|
+
if type == 'mix'
|
80
|
+
unless [@primary_filename, *@mix_files.keys].include?(filename)
|
81
|
+
scan_mix_file(filename)
|
82
|
+
end
|
83
|
+
else
|
84
|
+
js_filenames << filename
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
js_filenames.each do |name|
|
89
|
+
filename = File.expand_path("../../javascript/#{name}", __FILE__)
|
90
|
+
next if @js_files.has_key?(filename)
|
91
|
+
@js_files[filename] = File.read(filename)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def scan_mix_file (filename)
|
96
|
+
unless File.exists? filename
|
97
|
+
puts "no such file #{filename}"
|
98
|
+
exit
|
99
|
+
end
|
100
|
+
|
101
|
+
script = File.read(filename) + ?\n
|
102
|
+
scan_includes(script)
|
103
|
+
|
104
|
+
@mix_files[filename] = script
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright © 2011-2012 Jesse Sielaff
|
4
|
+
#
|
5
|
+
|
6
|
+
class MixTokenizer
|
7
|
+
def getch
|
8
|
+
@script[@pointer += 1]
|
9
|
+
end
|
10
|
+
|
11
|
+
def token (token, value = token)
|
12
|
+
@tokens << [token, [value, [@filename, @line]]]
|
13
|
+
end
|
14
|
+
|
15
|
+
def tokenize (filename, script, tokens)
|
16
|
+
@filename = filename
|
17
|
+
@script = script
|
18
|
+
@tokens = tokens
|
19
|
+
|
20
|
+
@line = 1
|
21
|
+
@pointer = -1
|
22
|
+
@indent_stack = [0]
|
23
|
+
@state = :newline
|
24
|
+
|
25
|
+
empty_lines = 0
|
26
|
+
|
27
|
+
while c = getch
|
28
|
+
case c
|
29
|
+
when ?~
|
30
|
+
if getch == ?~
|
31
|
+
:skip until getch =~ /\n/
|
32
|
+
unscan
|
33
|
+
next
|
34
|
+
end
|
35
|
+
|
36
|
+
unscan
|
37
|
+
:skip while getch =~ /\s/
|
38
|
+
unscan
|
39
|
+
|
40
|
+
when ' ', ?\t
|
41
|
+
@state = :space if @state == :reference
|
42
|
+
|
43
|
+
when ?\n
|
44
|
+
if @state == :newline
|
45
|
+
empty_lines += 1
|
46
|
+
next
|
47
|
+
end
|
48
|
+
|
49
|
+
current_indent = 0
|
50
|
+
previous_indent = @indent_stack.last
|
51
|
+
|
52
|
+
while (c = getch) == ' '
|
53
|
+
current_indent += 1
|
54
|
+
end
|
55
|
+
|
56
|
+
unscan
|
57
|
+
|
58
|
+
if c == ?\n
|
59
|
+
empty_lines += 1
|
60
|
+
next
|
61
|
+
end
|
62
|
+
|
63
|
+
@state = :newline
|
64
|
+
token :NEWLINE
|
65
|
+
|
66
|
+
@line += (empty_lines + 1)
|
67
|
+
empty_lines = 0
|
68
|
+
|
69
|
+
if current_indent > previous_indent
|
70
|
+
@indent_stack << current_indent
|
71
|
+
token :INDENT
|
72
|
+
elsif current_indent < previous_indent
|
73
|
+
unless @indent_stack.include?(current_indent)
|
74
|
+
raise(SyntaxError, "Indent error: #{@filename}:#{@line}")
|
75
|
+
end
|
76
|
+
|
77
|
+
until @indent_stack.last == current_indent
|
78
|
+
@indent_stack.pop
|
79
|
+
token :OUTDENT
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
when ?!, ?=
|
84
|
+
@state = :begin
|
85
|
+
|
86
|
+
if getch == ?=
|
87
|
+
token c + '='
|
88
|
+
else
|
89
|
+
unscan
|
90
|
+
token c
|
91
|
+
end
|
92
|
+
|
93
|
+
when ?+, ?*, ?/, ?%
|
94
|
+
@state = :begin
|
95
|
+
|
96
|
+
if getch == ?=
|
97
|
+
token '·=', c
|
98
|
+
else
|
99
|
+
unscan
|
100
|
+
token c
|
101
|
+
end
|
102
|
+
|
103
|
+
when ?-
|
104
|
+
if getch == ?=
|
105
|
+
token '·=', c
|
106
|
+
elsif c =~ /[0-9]/
|
107
|
+
token :NEGATIVE
|
108
|
+
unscan
|
109
|
+
else
|
110
|
+
unscan
|
111
|
+
|
112
|
+
if @state == :begin || @state == :newline
|
113
|
+
token '-·'
|
114
|
+
else
|
115
|
+
token '-'
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
@state = :begin
|
120
|
+
|
121
|
+
when ??, ?., ?;, ?,, ?(, ?{
|
122
|
+
@state = :begin
|
123
|
+
token c
|
124
|
+
|
125
|
+
when ?'
|
126
|
+
@state = :reference
|
127
|
+
|
128
|
+
s = ""
|
129
|
+
|
130
|
+
until (c = getch) == ?'
|
131
|
+
s << c
|
132
|
+
end
|
133
|
+
|
134
|
+
token :STRING, s
|
135
|
+
|
136
|
+
when ?>, ?<
|
137
|
+
@state = :begin
|
138
|
+
|
139
|
+
if getch == c
|
140
|
+
if getch == ?=
|
141
|
+
token '·=', c * 2
|
142
|
+
else
|
143
|
+
unscan
|
144
|
+
token c * 2
|
145
|
+
end
|
146
|
+
else
|
147
|
+
unscan
|
148
|
+
|
149
|
+
if getch == ?=
|
150
|
+
token c + '='
|
151
|
+
else
|
152
|
+
unscan
|
153
|
+
token c
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
when ?&, ?|
|
158
|
+
@state = :begin
|
159
|
+
|
160
|
+
if getch == c
|
161
|
+
if getch == ?=
|
162
|
+
token c + c + '='
|
163
|
+
else
|
164
|
+
unscan
|
165
|
+
token c + c
|
166
|
+
end
|
167
|
+
else
|
168
|
+
unscan
|
169
|
+
token c
|
170
|
+
end
|
171
|
+
|
172
|
+
when /[0-9]/
|
173
|
+
@state = :reference
|
174
|
+
value = c
|
175
|
+
|
176
|
+
while (c = getch) =~ /[0-9]/
|
177
|
+
value << c
|
178
|
+
end
|
179
|
+
|
180
|
+
unscan
|
181
|
+
|
182
|
+
token :NUMBER, value.to_i
|
183
|
+
|
184
|
+
when ?], ?}, ?)
|
185
|
+
@state = :reference
|
186
|
+
token c
|
187
|
+
|
188
|
+
when ?:, ?[, ?#
|
189
|
+
if @state == :reference
|
190
|
+
token '·' + c
|
191
|
+
else
|
192
|
+
token c
|
193
|
+
end
|
194
|
+
|
195
|
+
@state = :begin
|
196
|
+
|
197
|
+
when /[A-Z]/
|
198
|
+
@state = :begin
|
199
|
+
value = c
|
200
|
+
|
201
|
+
while (c = getch) =~ /[_a-zA-Z0-9]/
|
202
|
+
value << c
|
203
|
+
end
|
204
|
+
|
205
|
+
unscan
|
206
|
+
|
207
|
+
token :MIXIN, value.to_sym
|
208
|
+
|
209
|
+
when /[_a-z]/
|
210
|
+
@state = :reference
|
211
|
+
value = c
|
212
|
+
|
213
|
+
while (c = getch) =~ /[_a-zA-Z0-9]/
|
214
|
+
value << c
|
215
|
+
end
|
216
|
+
|
217
|
+
if c == ?:
|
218
|
+
if getch =~ /[a-z]/
|
219
|
+
unscan
|
220
|
+
else
|
221
|
+
@state = :begin
|
222
|
+
token :KEY, value.to_sym
|
223
|
+
unscan
|
224
|
+
next
|
225
|
+
end
|
226
|
+
|
227
|
+
unscan
|
228
|
+
elsif c == ?? || c == ?!
|
229
|
+
value << c
|
230
|
+
else
|
231
|
+
unscan
|
232
|
+
end
|
233
|
+
|
234
|
+
if %w:
|
235
|
+
app break case
|
236
|
+
elsif else false if
|
237
|
+
null return self
|
238
|
+
switch true unless
|
239
|
+
until while
|
240
|
+
:
|
241
|
+
.include?(value)
|
242
|
+
token value.upcase.to_sym
|
243
|
+
else
|
244
|
+
token :IDENTIFIER, value.to_sym
|
245
|
+
end
|
246
|
+
|
247
|
+
case c = getch
|
248
|
+
when ?:, ?#
|
249
|
+
token '·' + c
|
250
|
+
when ?[
|
251
|
+
token '·['
|
252
|
+
@state = :begin
|
253
|
+
else
|
254
|
+
unscan
|
255
|
+
end
|
256
|
+
|
257
|
+
else
|
258
|
+
raise SyntaxError, "Invalid token `#{c}': #{@filename}:#{@line}"
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
@tokens
|
263
|
+
end
|
264
|
+
|
265
|
+
def unscan
|
266
|
+
@pointer -= 1
|
267
|
+
end
|
268
|
+
end
|