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.
@@ -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
@@ -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