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