mix-language 1.0.0

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.
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Copyright © 2011 Jesse Sielaff
3
+ */
4
+
5
+ var TEXT =
6
+ {
7
+ functions: {},
8
+ };
9
+
10
+ copy_functions(TEXT, OBJECT);
11
+
12
+ function new_text (fields, keys)
13
+ {
14
+ var node = document.createTextNode('');
15
+ var text =
16
+ {
17
+ object_type: TEXT_TYPE,
18
+ functions: {},
19
+ data: fields,
20
+ keys: keys,
21
+ unknown: Object_bad_key,
22
+ value: node,
23
+ parent: NULL
24
+ }
25
+
26
+ copy_functions(text, TEXT);
27
+
28
+ return text;
29
+ }
30
+
31
+ function Text_content (self, args)
32
+ {
33
+ var str = args[0];
34
+
35
+ if (xself(self, TEXT_TYPE, "#content") || xarg(str, STRING_TYPE, "Text#content", "string"))
36
+ {
37
+ return self;
38
+ }
39
+
40
+ var content = str.value;
41
+ var node = self.value;
42
+
43
+ node.nodeValue = content;
44
+
45
+ return self;
46
+ }
47
+ function Text_release (self, args)
48
+ {
49
+ if (xself(self, TEXT_TYPE, "#release"))
50
+ {
51
+ return self;
52
+ }
53
+
54
+ var parent = self.parent;
55
+
56
+ if (parent === NULL)
57
+ {
58
+ return self;
59
+ }
60
+
61
+ var node = self.value;
62
+ var parent_node = parent.value;
63
+ var index = parent.children.indexOf(self);
64
+
65
+ parent.children.splice(index, 1);
66
+ parent_node.removeChild(node);
67
+ self.parent = NULL;
68
+
69
+ return self;
70
+ }
71
+
72
+ add_function(TEXT, 'content', Text_content, ['string']);
73
+ add_function(TEXT, 'release', Text_release, []);
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Copyright © 2011 Jesse Sielaff
3
+ */
4
+
5
+ var TR =
6
+ {
7
+ functions: {},
8
+ tag_name: 'tr'
9
+ };
10
+
11
+ HTML_TYPES['Tr'] = TR;
@@ -0,0 +1,528 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+ require_relative './mix.tab.rb'
6
+
7
+ class MixCompiler
8
+ def initialize (filename)
9
+ @primary_filename = filename
10
+ end
11
+
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")
54
+ end
55
+
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}",'}')
64
+ end
65
+
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
94
+
95
+ 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
188
+ end
189
+
190
+ def next_token
191
+ @tokens.shift
192
+ end
193
+
194
+ def node (type, *values)
195
+ { type: type, values: values }
196
+ end
197
+
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
203
+ 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
+ end
233
+
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]
523
+ end
524
+
525
+ def unscan
526
+ @pointer -= 1
527
+ end
528
+ end