mix-language 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/ruby/compiler.rb CHANGED
@@ -1,528 +1,65 @@
1
1
  # encoding: UTF-8
2
2
  #
3
- # Copyright © 2011 Jesse Sielaff
3
+ # Copyright © 2011-2012 Jesse Sielaff
4
4
  #
5
+ require_relative './precompiler.rb'
6
+ require_relative './tokenizer.rb'
5
7
  require_relative './mix.tab.rb'
8
+ require_relative './parser.rb'
9
+ require_relative './translator.rb'
6
10
 
7
11
  class MixCompiler
8
- def initialize (filename)
9
- @primary_filename = filename
12
+ def initialize
13
+ @precompiler = MixPrecompiler.new
14
+ @tokenizer = MixTokenizer.new
15
+ @parser = MixParser.new
16
+ @translator = MixTranslator.new
10
17
  end
11
18
 
12
- NODE_TYPES = {}
13
- %w:
14
- access_brackets access_colon
15
- access_hash and app array
16
- break call false function if
17
- mix mixin newline not null
18
- number object op or return
19
- self set statement string
20
- switch true variable while
21
- :
22
- .each_with_index {|t,i| NODE_TYPES[t.to_sym] = i + 1 }
23
-
24
- OPERATORS =
25
- {
26
- '+' => :plus, '-' => :minus,
27
- '*' => :star, '/' => :slash, '%' => :percent,
28
- '>' => :gt, '>>' => :gtgt, '>=' => :gteq,
29
- '<' => :lt, '<<' => :ltlt, '<=' => :lteq,
30
- '==' => :eq, '!=' => :neq
31
- }
32
-
33
- def append_statement (outermost_statement, final_statement)
34
- current = outermost_statement
35
-
36
- current = current[:values][1] while current[:values][1]
37
- current[:values][1] = final_statement
38
-
39
- outermost_statement
40
- end
41
-
42
- def build_script
43
- script = File.open(@primary_filename) {|f| "#{f.read}\n" }
44
- files = {'mix' => [], 'js' => %w[base/env.js base/eval.js base/object.js base/array.js base/false.js base/function.js base/null.js base/number.js base/string.js base/true.js]}
45
-
46
- @unordered_mix_filenames = [@primary_filename]
47
-
48
- scan_includes(script, files)
49
- files['mix'].push [@primary_filename, script]
50
-
51
- @js_filenames = files['js']
52
- @filenames = files['mix'].map {|name,*| name }
53
- @script = files['mix'].map {|*,script| script }.join("\034")
19
+ def basename (filename)
20
+ filename.match(/(.*?)(\.mix$|$)/)[1]
54
21
  end
55
22
 
56
- def precompile (js_file_string)
57
- NODE_TYPES.each {|k,v| js_file_string.gsub!("case #{k}:","case #{v}:") }
58
-
59
- %w:ARRAY ELEMENT EVENT FALSE FUNCTION NULL NUMBER OBJECT STRING TEXT TRUE:.each_with_index do |t,i|
60
- js_file_string.gsub!("#{t}_TYPE","#{i}")
61
- end
62
-
63
- js_file_string.gsub!(/\s*\/\*.*?\*\/\s*/m,"\n").gsub!(/\s*\n\s*/,?\n).gsub!(/\s*([{;:,=])\s*/,'\1').gsub!("\n}",'}')
23
+ def combine_strings (js_string, ast_string, symbol_string)
24
+ js_string
25
+ .sub(%%'...Replace with AST nodes...'%, ast_string)
26
+ .sub(%%'...Replace with symbol table...'%, symbol_string)
64
27
  end
65
28
 
66
- def compile
67
- build_script
68
- node = parse
69
- node_string = js(node)
70
- symbol_string = @symbols.keys.map {|s| "'#{s}'" }.join ?,
71
-
72
- js_file_string = @js_filenames.map do |filename|
73
- File.read(File.expand_path('../../javascript/' + filename, __FILE__))
74
- end.join ?\n
75
-
76
- precompile(js_file_string)
77
-
78
- <<-JAVASCRIPT
79
- mix_result_should_be_undefined = function ()
80
- {
81
-
82
- var HTML_TYPES = {};
83
- var SYMBOLS = [#{symbol_string.force_encoding('US-ASCII')}];
84
- #{js_file_string}
85
-
86
- window.onload = function ()
87
- {
88
- startup();
89
- evaluate(#{node_string});
90
- }
91
-
92
- }();
93
- JAVASCRIPT
29
+ def compile (filename)
30
+ mix_files, js_string = precompile_js(filename)
31
+ mix_tokens = tokenize_mix_files(mix_files)
32
+ mix_node = parse_token_stream(mix_tokens)
33
+ ast_string, symbol_string = translate_to_js(mix_node)
34
+ js_output_string = combine_strings(js_string, ast_string, symbol_string)
35
+ write_js_file(filename, js_output_string)
94
36
 
95
37
  rescue SyntaxError => e
96
- "window.onload = function () { if (console && console.log) console.log(\"SyntaxError: #{e.message}\"); };"
97
- end
98
-
99
- def getch
100
- @script[@pointer += 1]
101
- end
102
-
103
- def js (node)
104
- return "0" unless node
105
-
106
- t = node[:type]
107
- a, b, c = node[:values]
108
- args = [NODE_TYPES[t]]
109
-
110
- case (t = node[:type])
111
- when :access_brackets, :and, :or, :set, :statement, :while
112
- args << js(a) << js(b)
113
-
114
- when :access_colon, :access_hash, :mix
115
- args << js(a) << @symbols[b.to_sym]
116
-
117
- when :app, :false, :null, :self, :true
118
- nil
119
-
120
- when :array
121
- items = a.map {|x| js(x) }.join ?,
122
- args << "[#{items}]"
123
-
124
- when :break, :not, :return
125
- args << js(a)
126
-
127
- when :call
128
- function = js(a)
129
- arguments = b.map {|x| js(x) }.join ?,
130
-
131
- args << function << "[#{arguments}]"
132
-
133
- when :function
134
- parameters = a[0].map {|x| @symbols[x.to_sym] }.join ?,
135
- variables = a[1].map {|x| @symbols[x.to_sym] }.join ?,
136
- body = js(b)
137
-
138
- args << "[[#{parameters}],[#{variables}]]" << body
139
-
140
- when :if
141
- args << js(a) << js(b) << js(c)
142
-
143
- when :mixin
144
- name = @symbols[a.to_sym]
145
- init_statement = js(b[0])
146
- definitions = b[1].map {|s| js(s) }.join ?,
147
-
148
- args << name << init_statement << "[#{definitions}]"
149
-
150
- when :newline
151
- file, line = @symbols[a[0].to_sym], a[1]
152
- statement = js(b)
153
-
154
- args << file << line << statement
155
-
156
- when :number
157
- args << a
158
-
159
- when :object
160
- entries = a.map {|kv| "[#{@symbols[kv[0].to_sym]},#{js(kv[1])}]" }.join ?,
161
- mixins = b.map {|x| @symbols[x.to_sym] }.join ?,
162
-
163
- args << "[#{entries}]" << "[#{mixins}]"
164
-
165
- when :op
166
- args << @symbols[OPERATORS[a]] << js(b) << js(c)
167
-
168
- when :string, :variable
169
- args << @symbols[a.to_sym]
170
-
171
- when :switch
172
- condition = js(a)
173
- cases = b.map {|x| "[#{js(x[0])},#{js(x[1])}]" }.join ?,
174
- else_case = js(c)
175
-
176
- args << condition << cases << else_case
177
-
178
- else
179
- raise "BUG: node type :#{t} not implemented in MixCompiler#js"
180
- end
181
-
182
- return "[#{args.join ?,}]"
183
- end
184
-
185
- def newline (statement_node)
186
- statement_node[:values][0] = node(:newline, @location, statement_node[:values][0])
187
- statement_node
38
+ write_js_file("window.onload = function () { if (console && console.log) console.log(\"SyntaxError: #{e.message}\"); };")
188
39
  end
189
40
 
190
- def next_token
191
- @tokens.shift
41
+ def parse_token_stream (mix_tokens)
42
+ @parser.parse(mix_tokens)
192
43
  end
193
44
 
194
- def node (type, *values)
195
- { type: type, values: values }
45
+ def precompile_js (filename)
46
+ mix_filename = basename(filename) + '.mix'
47
+ @precompiler.precompile(mix_filename)
196
48
  end
197
49
 
198
- def on_error (token, value, stack)
199
- if value
200
- value, file, line = value[0].inspect, *value[1]
201
- else
202
- value, file, line = 'EOF', @primary_filename, @line
50
+ def tokenize_mix_files (mix_files)
51
+ mix_files.each_with_object([]) do |(filename, script), tokens|
52
+ @tokenizer.tokenize(filename, script, tokens)
203
53
  end
204
-
205
- raise SyntaxError, "Unexpected token #{token_to_str(token)} (#{value}): #{file}:#{line}".gsub(?",'\"')
206
- end
207
-
208
- def parameter (name)
209
- @variables[:parameters] << name
210
- @variables[:local] << name
211
- end
212
-
213
- def parse
214
- tokenize
215
- symbol_index = -1
216
- @symbols = Hash.new {|h,k| h[k] = (symbol_index += 1) }
217
- @variables = { parameters: [], local: [], embedded: [], prev: nil }
218
-
219
- return do_parse
220
- end
221
-
222
- def pop_variables
223
- vars = [@variables[:parameters], @variables[:embedded]]
224
- @variables = @variables[:prev]
225
-
226
- return vars
227
- end
228
-
229
- def push_variables
230
- embedded = @variables[:local] + @variables[:embedded]
231
- @variables = { parameters: [], local: [], embedded: embedded, prev: @variables }
232
54
  end
233
55
 
234
- def variable (name)
235
- unless (@variables[:local] + @variables[:embedded]).include?(name)
236
- @variables[:local] << name
237
- end
238
-
239
- node :variable, name
240
- end
241
-
242
- def scan_includes (script, files)
243
- require 'strscan'
244
-
245
- ss = StringScanner.new(script)
246
- js_filenames = []
247
-
248
- while ss.scan(/~~\s+(.*\.(js|mix))\s*\n/)
249
- name = ss[1]
250
- type = ss[2]
251
-
252
- if type == 'mix'
253
- unless @unordered_mix_filenames.include?(name)
254
- script = File.open(name) {|f| "#{f.read}\n" }
255
- @unordered_mix_filenames << name
256
- scan_includes(script, files)
257
- files['mix'].push [name,script]
258
- end
259
- else
260
- js_filenames << name
261
- end
262
- end
263
-
264
- files['js'] |= js_filenames
265
- end
266
-
267
- def token (token, value = token)
268
- @tokens << [token, [value, [@filename, @line]]]
269
- end
270
-
271
- def tokenize
272
- @filename = @filenames[file_index = 0]
273
- @line = 1
274
- @pointer = -1
275
- @indent_stack = [0]
276
- @state = :newline
277
- @tokens = []
278
-
279
- empty_lines = 0
280
-
281
- while c = getch
282
- case c
283
- when ?\034
284
- @filename = @filenames[file_index += 1]
285
- @line = 1
286
-
287
- when ?~
288
- if getch == ?~
289
- :skip until getch =~ /\n/
290
- unscan
291
- next
292
- end
293
-
294
- unscan
295
- :skip while getch =~ /\s/
296
- unscan
297
-
298
- when ' ', ?\t
299
- @state = :space if @state == :reference
300
-
301
- when ?\n
302
- if @state == :newline
303
- empty_lines += 1
304
- next
305
- end
306
-
307
- current_indent = 0
308
- previous_indent = @indent_stack.last
309
-
310
- while (c = getch) == ' '
311
- current_indent += 1
312
- end
313
-
314
- unscan
315
-
316
- if c == ?\n
317
- empty_lines += 1
318
- next
319
- end
320
-
321
- @state = :newline
322
- token :NEWLINE
323
-
324
- @line += (empty_lines + 1)
325
- empty_lines = 0
326
-
327
- if current_indent > previous_indent
328
- @indent_stack << current_indent
329
- token :INDENT
330
- elsif current_indent < previous_indent
331
- unless @indent_stack.include?(current_indent)
332
- raise(SyntaxError, "Indent error: #{@filename}:#{@line}")
333
- end
334
-
335
- until @indent_stack.last == current_indent
336
- @indent_stack.pop
337
- token :OUTDENT
338
- end
339
- end
340
-
341
- when ?!, ?=
342
- @state = :begin
343
-
344
- if getch == ?=
345
- token c + '='
346
- else
347
- unscan
348
- token c
349
- end
350
-
351
- when ?+, ?*, ?/, ?%
352
- @state = :begin
353
-
354
- if getch == ?=
355
- token '·=', c
356
- else
357
- unscan
358
- token c
359
- end
360
-
361
- when ?-
362
- if getch == ?=
363
- token '·=', c
364
- elsif c =~ /[0-9]/
365
- token '-1'
366
- unscan
367
- else
368
- unscan
369
-
370
- if @state == :begin || @state == :newline
371
- token '-·'
372
- else
373
- token '-'
374
- end
375
- end
376
-
377
- @state = :begin
378
-
379
- when ??, ?., ?;, ?,, ?(, ?{
380
- @state = :begin
381
- token c
382
-
383
- when ?'
384
- @state = :reference
385
-
386
- s = ""
387
-
388
- until (c = getch) == ?'
389
- s << c
390
- end
391
-
392
- token '\''
393
- token :STRING, s
394
- token '\''
395
-
396
- when ?>, ?<
397
- @state = :begin
398
-
399
- if getch == c
400
- if getch == ?=
401
- token '·=', c * 2
402
- else
403
- unscan
404
- token c * 2
405
- end
406
- else
407
- unscan
408
-
409
- if getch == ?=
410
- token c + '='
411
- else
412
- unscan
413
- token c
414
- end
415
- end
416
-
417
- when ?&, ?|
418
- @state = :begin
419
-
420
- if getch == c
421
- if getch == ?=
422
- token c + c + '='
423
- else
424
- unscan
425
- token c + c
426
- end
427
- else
428
- unscan
429
- token c
430
- end
431
-
432
- when /[0-9]/
433
- @state = :reference
434
- value = c
435
-
436
- while (c = getch) =~ /[0-9]/
437
- value << c
438
- end
439
-
440
- unscan
441
-
442
- token :NUMBER, value.to_i
443
-
444
- when ?], ?}, ?)
445
- @state = :reference
446
- token c
447
-
448
- when ?:, ?[, ?#
449
- if @state == :reference
450
- token '·' + c
451
- else
452
- token c
453
- end
454
-
455
- @state = :begin
456
-
457
- when /[A-Z]/
458
- @state = :begin
459
- value = c
460
-
461
- while (c = getch) =~ /[_a-zA-Z0-9]/
462
- value << c
463
- end
464
-
465
- unscan
466
-
467
- token :MIXIN, value.to_sym
468
-
469
- when /[_a-z]/
470
- @state = :reference
471
- value = c
472
-
473
- while (c = getch) =~ /[_a-zA-Z0-9]/
474
- value << c
475
- end
476
-
477
- if c == ?:
478
- if getch =~ /[a-z]/
479
- unscan
480
- else
481
- @state = :begin
482
- token :KEY, value.to_sym
483
- unscan
484
- next
485
- end
486
-
487
- unscan
488
- elsif c == ?? || c == ?!
489
- value << c
490
- else
491
- unscan
492
- end
493
-
494
- if %w:
495
- app break case
496
- elsif else false if
497
- null return self
498
- switch true unless
499
- until while
500
- :
501
- .include?(value)
502
- token value.upcase.to_sym
503
- else
504
- token :IDENTIFIER, value.to_sym
505
- end
506
-
507
- case c = getch
508
- when ?:, ?#
509
- token '·' + c
510
- when ?[
511
- token '·['
512
- @state = :begin
513
- else
514
- unscan
515
- end
516
-
517
- else
518
- raise SyntaxError, "Invalid token `#{c}': #{@filename}:#{@line}"
519
- end
520
- end
521
-
522
- @tokens << [false, false]
56
+ def translate_to_js (node)
57
+ @translator.translate(node)
523
58
  end
524
59
 
525
- def unscan
526
- @pointer -= 1
60
+ def write_js_file (filename, js_output_string)
61
+ js_filename = basename(filename) + '.js'
62
+ File.open(js_filename,'w') {|f| f.write(js_output_string) }
63
+ puts "Writing to #{js_filename}"
527
64
  end
528
65
  end
data/lib/ruby/mix.grammar CHANGED
@@ -1,14 +1,14 @@
1
1
  # encoding: UTF-8
2
2
  #
3
- # Copyright © 2011 Jesse Sielaff
3
+ # Copyright © 2011-2012 Jesse Sielaff
4
4
  #
5
5
 
6
- class MixCompiler
6
+ class MixParser
7
7
  options no_result_var
8
8
 
9
9
  prechigh
10
10
  right '!'
11
- right '-·' '-1'
11
+ right '-·' NEGATIVE
12
12
  left '*' '/' '%'
13
13
  left '+' '-'
14
14
  left '<<' '>>'
@@ -43,7 +43,7 @@ class MixCompiler
43
43
  | function_statement
44
44
 
45
45
  mixin_statement
46
- : MIXIN ':' newline INDENT mixin_body OUTDENT { node :mixin, val[0][0], val[4] }
46
+ : MIXIN ':' newline INDENT mixin_body OUTDENT { node :mixin, val[0][0], val[4][0], val[4][1] }
47
47
 
48
48
  mixin_body
49
49
  : statements { [val[0], []] }
@@ -56,10 +56,10 @@ class MixCompiler
56
56
 
57
57
  definition
58
58
  : IDENTIFIER function_block { node :set, node(:access_hash, node(:self), val[0][0]), val[1] }
59
- | IDENTIFIER inline_function newline { node :newline, @location, node(:set, node(:access_hash, node(:self), val[0][0]), val[1]) }
59
+ | IDENTIFIER inline_function newline { node :newline, *@location, node(:set, node(:access_hash, node(:self), val[0][0]), val[1]) }
60
60
 
61
61
  function_block
62
- : parameter_list indent_block { node :function, pop_variables, val[1] }
62
+ : parameter_list indent_block { node :function, *pop_variables, val[1] }
63
63
 
64
64
  parameter_list
65
65
  : '#' { push_variables; nil }
@@ -92,8 +92,8 @@ class MixCompiler
92
92
  | cases case { val[0] << val[1] }
93
93
 
94
94
  case
95
- : CASE exp indent_block { [node(:newline, @location, val[1]), val[2]] }
96
- | CASE exp ':' inline_statements newline { [node(:newline, @location, val[1]), val[3]] }
95
+ : CASE exp indent_block { [node(:newline, *@location, val[1]), val[2]] }
96
+ | CASE exp ':' inline_statements newline { [node(:newline, *@location, val[1]), val[3]] }
97
97
 
98
98
  inline_statements
99
99
  : inline_statement { node :statement, val[0], nil }
@@ -112,8 +112,8 @@ class MixCompiler
112
112
  | inline_statement UNTIL exp { node :while, node(:not, val[2]), val[0] }
113
113
 
114
114
  mix_statement
115
- : '+' MIXIN object { node :mix, val[2], val[1][0] }
116
- | '+' MIXIN { node :mix, node(:self), val[1][0] }
115
+ : '+' MIXIN object { node :mix, val[1][0], val[2] }
116
+ | '+' MIXIN { node :mix, val[1][0], node(:self) }
117
117
 
118
118
  keyword_statement
119
119
  : BREAK exp { node :break, val[1] }
@@ -184,10 +184,10 @@ class MixCompiler
184
184
 
185
185
  literal_number
186
186
  : NUMBER { node :number, val[0][0] }
187
- | '-1' NUMBER { node :number, -val[1][0] }
187
+ | NEGATIVE NUMBER { node :number, -val[1][0] }
188
188
 
189
189
  literal_string
190
- : '\'' STRING '\'' { node :string, val[1][0] }
190
+ : STRING { node :string, val[0][0] }
191
191
 
192
192
  literal_constant
193
193
  : APP { node :app }
@@ -223,7 +223,7 @@ class MixCompiler
223
223
  : '(' inline_statements ')' { val[1] }
224
224
 
225
225
  inline_function
226
- : parameter_list curly_brace_block { node :function, pop_variables, val[1] }
226
+ : parameter_list curly_brace_block { node :function, *pop_variables, val[1] }
227
227
 
228
228
  curly_brace_block
229
229
  : '{' inline_statements '}' { val[1] }