mix-language 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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