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/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] }