opal 0.3.26 → 0.3.27

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/.gitignore +1 -6
  2. data/.travis.yml +1 -4
  3. data/Gemfile +1 -1
  4. data/README.md +3 -8
  5. data/Rakefile +26 -3
  6. data/core/array.rb +33 -91
  7. data/core/boolean.rb +1 -4
  8. data/core/class.rb +25 -71
  9. data/core/error.rb +5 -3
  10. data/core/hash.rb +3 -2
  11. data/core/kernel.rb +40 -1
  12. data/core/load_order +1 -1
  13. data/core/nil_class.rb +1 -3
  14. data/core/runtime.js +4 -44
  15. data/core/template.rb +20 -0
  16. data/core/{opal-spec → test_runner}/runner.js +0 -6
  17. data/lib/opal.rb +1 -12
  18. data/lib/opal/builder.rb +3 -10
  19. data/lib/opal/lexer.rb +1095 -1110
  20. data/lib/opal/parser.rb +229 -26
  21. data/lib/opal/rake_task.rb +3 -3
  22. data/lib/opal/version.rb +1 -1
  23. data/opal.gemspec +2 -2
  24. data/spec/core/array/assoc_spec.rb +2 -3
  25. data/spec/core/array/comparison_spec.rb +16 -0
  26. data/spec/core/array/constructor_spec.rb +0 -9
  27. data/spec/core/array/drop_spec.rb +21 -0
  28. data/spec/core/array/dup_spec.rb +15 -0
  29. data/spec/core/array/{eql_spec.rb → equal_value_spec.rb} +0 -0
  30. data/spec/core/array/index_spec.rb +26 -0
  31. data/spec/core/array/inspect_spec.rb +13 -0
  32. data/spec/core/array/intersection_spec.rb +22 -0
  33. data/spec/core/array/join_spec.rb +9 -0
  34. data/spec/core/array/keep_if_spec.rb +7 -0
  35. data/spec/core/array/minus_spec.rb +19 -9
  36. data/spec/core/array/multiply_spec.rb +13 -0
  37. data/spec/core/array/new_spec.rb +40 -0
  38. data/spec/core/array/rindex_spec.rb +21 -0
  39. data/spec/core/array/select_spec.rb +13 -0
  40. data/spec/core/array/shift_spec.rb +51 -0
  41. data/spec/core/array/slice_spec.rb +37 -0
  42. data/spec/core/array/take_spec.rb +21 -0
  43. data/spec/core/array/take_while_spec.rb +13 -0
  44. data/spec/core/array/to_a_spec.rb +7 -0
  45. data/spec/core/array/unshift_spec.rb +29 -0
  46. data/spec/core/hash/constructor_spec.rb +13 -0
  47. data/spec/core/hash/default_proc_spec.rb +20 -0
  48. data/spec/core/hash/default_spec.rb +8 -0
  49. data/spec/core/hash/delete_spec.rb +11 -0
  50. data/spec/core/hash/dup_spec.rb +10 -0
  51. data/spec/core/hash/reject_spec.rb +18 -0
  52. data/spec/core/hash/to_a_spec.rb +13 -0
  53. data/spec/core/kernel/Array_spec.rb +10 -0
  54. data/spec/core/kernel/class_spec.rb +6 -0
  55. data/spec/core/kernel/equal_spec.rb +12 -0
  56. data/spec/core/kernel/extend_spec.rb +21 -0
  57. data/spec/core/kernel/instance_eval_spec.rb +28 -0
  58. data/spec/core/kernel/instance_variable_get_spec.rb +14 -0
  59. data/spec/core/kernel/instance_variable_set_spec.rb +10 -0
  60. data/spec/core/kernel/match_spec.rb +5 -0
  61. data/spec/core/module/alias_method_spec.rb +10 -0
  62. data/spec/core/module/ancestors_spec.rb +11 -0
  63. data/spec/core/module/append_features_spec.rb +14 -0
  64. data/spec/core/proc/call_spec.rb +21 -0
  65. data/spec/core/proc/proc_tricks_spec.rb +1 -1
  66. data/spec/language/alias_spec.rb +1 -1
  67. data/spec/opal/class/instance_methods_spec.rb +13 -0
  68. data/spec/opal/kernel/attribute_spec.rb +57 -0
  69. data/spec/spec_helper.rb +1 -1
  70. data/spec/test_case.html +13 -0
  71. metadata +88 -12
  72. data/core/erb.rb +0 -32
  73. data/lib/opal/erb_parser.rb +0 -20
  74. data/spec/opal/erb/erb_spec.rb +0 -23
data/lib/opal/parser.rb CHANGED
@@ -3,12 +3,52 @@ require 'opal/grammar'
3
3
  require 'opal/scope'
4
4
 
5
5
  module Opal
6
-
6
+ # This class is used to generate the javascript code from the given
7
+ # ruby code. First, this class will use an instance of `Opal::Grammar`
8
+ # to lex and then build up a tree of sexp statements. Once done, the
9
+ # top level sexp is passed into `#top` which recursively generates
10
+ # javascript for each sexp inside, and the result is returned as a
11
+ # string.
12
+ #
13
+ # p = Opal::Parser.new
14
+ # p.parse "puts 42"
15
+ # # => "(function() { ... })()"
16
+ #
17
+ # ## Sexps
18
+ #
19
+ # A sexp, in opal, is an array where the first value is a symbol
20
+ # identifying the type of sexp. A simple ruby string "hello" would
21
+ # be represented by the sexp:
22
+ #
23
+ # s(:str, "hello")
24
+ #
25
+ # Once that sexp is encounterd by the parser, it is handled by
26
+ # `#process` which removes the sexp type from the array, and checks
27
+ # for a method "process_str", which is used to handle specific sexps.
28
+ # Once found, that method is called with the sexp and level as
29
+ # arguments, and the returned string is the javascript to be used in
30
+ # the resulting file.
31
+ #
32
+ # ## Levels
33
+ #
34
+ # A level inside the parser is just a symbol representing what type
35
+ # of destination the code to be generated is for. For example, the
36
+ # main two levels are `:stmt` and `:expr`. Most sexps generate the
37
+ # same code for every level, but an `if` statement for example
38
+ # will change when written as an expression. Javascript cannot have
39
+ # if statements as expressions, so that sexp would wrap its result
40
+ # inside an anonymous function so the if statement can compile as
41
+ # expected.
7
42
  class Parser
43
+ # Generated code gets indented with two spaces on each scope
8
44
  INDENT = ' '
9
45
 
46
+ # Expressions are handled at diffferent levels. Some sexps
47
+ # need to know the js expression they are generating into.
10
48
  LEVEL = [:stmt, :stmt_closure, :list, :expr, :recv]
11
49
 
50
+ # All compare method nodes - used to optimize performance of
51
+ # math comparisons
12
52
  COMPARE = %w[< > <= >=]
13
53
 
14
54
  # Reserved javascript keywords - we cannot create variables with the
@@ -20,16 +60,32 @@ module Opal
20
60
  const
21
61
  )
22
62
 
63
+ # Statements which should not have ';' added to them.
23
64
  STATEMENTS = [:xstr, :dxstr]
24
65
 
66
+ # The grammar (tree of sexps) representing this compiled code
67
+ # @return [Opal::Grammar]
25
68
  attr_reader :grammar
26
69
 
70
+ # Holds an array of paths which this file "requires".
71
+ # @return [Array<String>]
27
72
  attr_reader :requires
28
73
 
74
+ # This does the actual parsing. The ruby code given is first
75
+ # run through a `Grammar` instance which returns a sexp to
76
+ # process. This is then handled recursively, resulting in a
77
+ # string of javascript being returned.
78
+ #
79
+ # p = Opal::Parser.new
80
+ # p.parse "puts 'hey'"
81
+ # # => "(function() { .... })()"
82
+ #
83
+ # @param [String] source the ruby code to parse
84
+ # @param [String] file the filename representing this code
85
+ # @return [String] string of javascript code
29
86
  def parse(source, file = '(file)')
30
87
  @grammar = Grammar.new
31
88
  @requires = []
32
- @comments = false
33
89
  @file = file
34
90
  @line = 1
35
91
  @indent = ''
@@ -43,24 +99,54 @@ module Opal
43
99
  top @grammar.parse(source, file)
44
100
  end
45
101
 
46
- def warn(msg)
47
- puts "#{msg} :#{@file}:#{@line}"
48
- end
49
-
102
+ # This is called when a parsing/processing error occurs. This
103
+ # method simply appends the filename and curent line number onto
104
+ # the message and raises it.
105
+ #
106
+ # parser.error "bad variable name"
107
+ # # => raise "bad variable name :foo.rb:26"
108
+ #
109
+ # @param [String] msg error message to raise
50
110
  def error(msg)
51
111
  raise "#{msg} :#{@file}:#{@line}"
52
112
  end
53
113
 
114
+ # Instances of `Scope` can use this to determine the current
115
+ # scope indent. The indent is used to keep generated code easily
116
+ # readable.
117
+ #
118
+ # @return [String]
54
119
  def parser_indent
55
120
  @indent
56
121
  end
57
122
 
123
+ # Create a new sexp using the given parts. Even though this just
124
+ # returns an array, it must be used incase the internal structure
125
+ # of sexps does change.
126
+ #
127
+ # s(:str, "hello there")
128
+ # # => [:str, "hello there"]
129
+ #
130
+ # @result [Array]
58
131
  def s(*parts)
59
132
  sexp = Sexp.new(parts)
60
133
  sexp.line = @line
61
134
  sexp
62
135
  end
63
136
 
137
+ # Converts a ruby method name into its javascript equivalent for
138
+ # a method/function call. All ruby method names get prefixed with
139
+ # a '$', and if the name is a valid javascript identifier, it will
140
+ # have a '.' prefix (for dot-calling), otherwise it will be
141
+ # wrapped in brackets to use reference notation calling.
142
+ #
143
+ # mid_to_jsid('foo') # => ".$foo"
144
+ # mid_to_jsid('class') # => ".$class"
145
+ # mid_to_jsid('==') # => "['$==']"
146
+ # mid_to_jsid('name=') # => "['$name=']"
147
+ #
148
+ # @param [String] mid ruby method id
149
+ # @return [String]
64
150
  def mid_to_jsid(mid)
65
151
  if /\=|\+|\-|\*|\/|\!|\?|\<|\>|\&|\||\^|\%|\~|\[/ =~ mid.to_s
66
152
  "['$#{mid}']"
@@ -69,11 +155,21 @@ module Opal
69
155
  end
70
156
  end
71
157
 
72
- # guaranteed unique id per file..
158
+ # Used to generate a unique id name per file. These are used
159
+ # mainly to name method bodies for methods that use blocks.
160
+ #
161
+ # @return [String]
73
162
  def unique_temp
74
163
  "TMP_#{@unique += 1}"
75
164
  end
76
165
 
166
+ # Generate the code for the top level sexp, i.e. the root sexp
167
+ # for a file. This is used directly by `#parse`. It pushes a
168
+ # ":top" scope onto the stack and handles the passed in sexp.
169
+ # The result is a string of javascript representing the sexp.
170
+ #
171
+ # @param [Array] sexp the sexp to process
172
+ # @return [String]
77
173
  def top(sexp, options = {})
78
174
  code = nil
79
175
  vars = []
@@ -93,9 +189,29 @@ module Opal
93
189
  code = "#{INDENT}var #{vars.join ', '};\n" + INDENT + @scope.to_vars + "\n" + code
94
190
  end
95
191
 
96
- "function() {\n#{ code }\n}"
192
+ "(function() {\n#{ code }\n})();"
97
193
  end
98
194
 
195
+ # Every time the parser enters a new scope, this is called with
196
+ # the scope type as an argument. Valid types are `:top` for the
197
+ # top level/file scope; `:class`, `:module` and `:sclass` for the
198
+ # obvious ruby classes/modules; `:def` and `:iter` for methods
199
+ # and blocks respectively.
200
+ #
201
+ # This method just pushes a new instance of `Opal::Scope` onto the
202
+ # stack, sets the new scope as the `@scope` variable, and yields
203
+ # the given block. Once the block returns, the old scope is put
204
+ # back on top of the stack.
205
+ #
206
+ # in_scope(:class) do
207
+ # # generate class body in here
208
+ # body = "..."
209
+ # end
210
+ #
211
+ # # use body result..
212
+ #
213
+ # @param [Symbol] type the type of scope
214
+ # @return [nil]
99
215
  def in_scope(type)
100
216
  return unless block_given?
101
217
 
@@ -106,6 +222,15 @@ module Opal
106
222
  @scope = parent
107
223
  end
108
224
 
225
+ # To keep code blocks nicely indented, this will yield a block after
226
+ # adding an extra layer of indent, and then returning the resulting
227
+ # code after reverting the indent.
228
+ #
229
+ # indented_code = indent do
230
+ # "foo"
231
+ # end
232
+ #
233
+ # @result [String]
109
234
  def indent(&block)
110
235
  indent = @indent
111
236
  @indent += INDENT
@@ -116,6 +241,17 @@ module Opal
116
241
  res
117
242
  end
118
243
 
244
+ # Temporary varibales will be needed from time to time in the
245
+ # generated code, and this method will assign (or reuse) on
246
+ # while the block is yielding, and queue it back up once it is
247
+ # finished. Variables are queued once finished with to save the
248
+ # numbers of variables needed at runtime.
249
+ #
250
+ # with_temp do |tmp|
251
+ # "tmp = 'value';"
252
+ # end
253
+ #
254
+ # @return [String] generated code withing block
119
255
  def with_temp(&block)
120
256
  tmp = @scope.new_temp
121
257
  res = yield tmp
@@ -123,12 +259,6 @@ module Opal
123
259
  res
124
260
  end
125
261
 
126
- # Should be overriden in custom parsers to handle a require
127
- # statement.
128
- def handle_require(arglist)
129
- "/* require statement removed */"
130
- end
131
-
132
262
  # Used when we enter a while statement. This pushes onto the current
133
263
  # scope's while stack so we know how to handle break, next etc.
134
264
  #
@@ -146,10 +276,28 @@ module Opal
146
276
  result
147
277
  end
148
278
 
279
+ # Returns true if the parser is curently handling a while sexp,
280
+ # false otherwise.
281
+ #
282
+ # @return [Boolean]
149
283
  def in_while?
150
284
  @scope.in_while?
151
285
  end
152
286
 
287
+ # Processes a given sexp. This will send a method to the receiver
288
+ # of the format "process_<sexp_name>". Any sexp handler should
289
+ # return a string of content.
290
+ #
291
+ # For example, calling `process` with `s(:lit, 42)` will call the
292
+ # method `#process_lit`. If a method with that name cannot be
293
+ # found, then an error is raised.
294
+ #
295
+ # process(s(:lit, 42), :stmt)
296
+ # # => "42"
297
+ #
298
+ # @param [Array] sexp the sexp to process
299
+ # @param [Symbol] level the level to process (see `LEVEL`)
300
+ # @return [String]
153
301
  def process(sexp, level)
154
302
  type = sexp.shift
155
303
  meth = "process_#{type}"
@@ -160,6 +308,27 @@ module Opal
160
308
  __send__ meth, sexp, level
161
309
  end
162
310
 
311
+ # The last sexps in method bodies, for example, need to be returned
312
+ # in the compiled javascript. Due to syntax differences between
313
+ # javascript any ruby, some sexps need to be handled specially. For
314
+ # example, `if` statemented cannot be returned in javascript, so
315
+ # instead the "truthy" and "falsy" parts of the if statement both
316
+ # need to be returned instead.
317
+ #
318
+ # Sexps that need to be returned are passed to this method, and the
319
+ # alterned/new sexps are returned and should be used instead. Most
320
+ # sexps can just be added into a s(:return) sexp, so that is the
321
+ # default action if no special case is required.
322
+ #
323
+ # sexp = s(:str, "hey")
324
+ # parser.returns(sexp)
325
+ # # => s(:js_return, s(:str, "hey"))
326
+ #
327
+ # `s(:js_return)` is just a special sexp used to return the result
328
+ # of processing its arguments.
329
+ #
330
+ # @param [Array] sexp the sexp to alter
331
+ # @return [Array] altered sexp
163
332
  def returns(sexp)
164
333
  return returns s(:nil) unless sexp
165
334
 
@@ -206,10 +375,28 @@ module Opal
206
375
  end
207
376
  end
208
377
 
378
+ # Returns true if the given sexp is an expression. All expressions
379
+ # will get ';' appended to their result, except for the statement
380
+ # sexps. See `STATEMENTS` for a list of sexp names that are
381
+ # statements.
382
+ #
383
+ # @param [Array] sexp the sexp to check
384
+ # @return [Boolean]
209
385
  def expression?(sexp)
210
386
  !STATEMENTS.include?(sexp.first)
211
387
  end
212
388
 
389
+ # More than one expression in a row will be grouped by the grammar
390
+ # into a block sexp. A block sexp just holds any number of other
391
+ # sexps.
392
+ #
393
+ # s(:block, s(:str, "hey"), s(:lit, 42))
394
+ #
395
+ # A block can actually be empty. As opal requires real values to
396
+ # be returned (to appease javascript values), a nil sexp
397
+ # s(:nil) will be generated if the block is empty.
398
+ #
399
+ # @return [String]
213
400
  def process_block(sexp, level)
214
401
  result = []
215
402
  sexp << s(:nil) if sexp.empty?
@@ -231,6 +418,27 @@ module Opal
231
418
  result.join(@scope.class_scope? ? "\n\n#@indent" : "\n#@indent")
232
419
  end
233
420
 
421
+ # When a block sexp gets generated, any inline yields (i.e. yield
422
+ # statements that are not direct members of the block) need to be
423
+ # generated as a top level member. This is because if a yield
424
+ # is returned by a break statement, then the method must return.
425
+ #
426
+ # As inline expressions in javascript cannot return, the block
427
+ # must be rewritten.
428
+ #
429
+ # For example, a yield inside an array:
430
+ #
431
+ # [1, 2, 3, yield(4)]
432
+ #
433
+ # Must be rewitten into:
434
+ #
435
+ # tmp = yield 4
436
+ # [1, 2, 3, tmp]
437
+ #
438
+ # This rewriting happens on sexps directly.
439
+ #
440
+ # @param [Sexp] stmt sexps to (maybe) rewrite
441
+ # @return [Sexp]
234
442
  def find_inline_yield(stmt)
235
443
  found = nil
236
444
  case stmt.first
@@ -415,12 +623,12 @@ module Opal
415
623
  args ||= s(:masgn, s(:array))
416
624
  args = args.first == :lasgn ? s(:array, args) : args[1]
417
625
 
418
- if args.last[0] == :block_pass
626
+ if args.last.is_a?(Array) and args.last[0] == :block_pass
419
627
  block_arg = args.pop
420
628
  block_arg = block_arg[1][1].to_sym
421
629
  end
422
630
 
423
- if args.last[0] == :splat
631
+ if args.last.is_a?(Array) and args.last[0] == :splat
424
632
  splat = args.last[1][1]
425
633
  args.pop
426
634
  len = args.length
@@ -509,7 +717,7 @@ module Opal
509
717
  # @param [Symbol] meth :attr_{reader,writer,accessor}
510
718
  # @param [Array<Sexp>] attrs array of s(:lit) or s(:str)
511
719
  # @return [String] precompiled attr methods
512
- def attr_optimize(meth, attrs)
720
+ def handle_attr_optimize(meth, attrs)
513
721
  out = []
514
722
 
515
723
  attrs.each do |attr|
@@ -555,16 +763,12 @@ module Opal
555
763
 
556
764
  case meth
557
765
  when :attr_reader, :attr_writer, :attr_accessor
558
- attrs = arglist[1..-1]
559
- if @scope.class_scope? && attrs.all? { |a| [:lit, :str].include? a.first }
560
- return attr_optimize meth, attrs
561
- end
766
+ return handle_attr_optimize(meth, arglist[1..-1])
562
767
  when :block_given?
563
768
  return js_block_given(sexp, level)
564
769
  when :alias_native
565
770
  return handle_alias_native(sexp) if @scope.class_scope?
566
771
  when :require
567
- # return handle_require(arglist)
568
772
  path = arglist[1]
569
773
 
570
774
  if path and path[0] == :str
@@ -699,7 +903,7 @@ module Opal
699
903
  spacer = "\n#{@indent}#{INDENT}"
700
904
  cls = "function #{name}() {};"
701
905
  boot = "#{name} = __klass(__base, __super, #{name.inspect}, #{name});"
702
- comment = "#{spacer}// line #{ sexp.line }, #{ @file }, class #{ name }" if @comment
906
+ comment = "#{spacer}// line #{ sexp.line }, #{ @file }, class #{ name }"
703
907
 
704
908
  "(function(__base, __super){#{comment}#{spacer}#{cls}#{spacer}#{boot}\n#{code}\n#{@indent}})(#{base}, #{sup})"
705
909
  end
@@ -753,7 +957,7 @@ module Opal
753
957
  spacer = "\n#{@indent}#{INDENT}"
754
958
  cls = "function #{name}() {};"
755
959
  boot = "#{name} = __module(__base, #{name.inspect}, #{name});"
756
- comment = "#{spacer}// line #{ sexp.line }, #{ @file }, module #{ name }" if @comment
960
+ comment = "#{spacer}// line #{ sexp.line }, #{ @file }, module #{ name }"
757
961
 
758
962
  "(function(__base){#{comment}#{spacer}#{cls}#{spacer}#{boot}\n#{code}\n#{@indent}})(#{base})"
759
963
  end
@@ -808,7 +1012,7 @@ module Opal
808
1012
 
809
1013
  # block name &block
810
1014
  if args.last.to_s.start_with? '&'
811
- block_name = args.pop[1..-1].to_sym
1015
+ block_name = args.pop.to_s[1..-1].to_sym
812
1016
  end
813
1017
 
814
1018
  # splat args *splat
@@ -874,7 +1078,6 @@ module Opal
874
1078
  end
875
1079
 
876
1080
  comment += "\n#{@indent}"
877
- comment = nil unless @comments
878
1081
 
879
1082
  if recvr
880
1083
  if smethod
@@ -15,7 +15,7 @@ module Opal
15
15
  @dir = @project_dir
16
16
  @build_dir = 'build'
17
17
  @specs_dir = 'spec'
18
- @files = Dir['lib/**/*.{rb,js,erb}']
18
+ @files = Dir['lib/**/*.rb']
19
19
  @dependencies = []
20
20
 
21
21
  yield self if block_given?
@@ -67,9 +67,9 @@ module Opal
67
67
  @dependencies.each { |dep| build_gem dep }
68
68
  end
69
69
 
70
- desc "Run tests through phantomjs"
70
+ desc "Run specs in spec/index.html"
71
71
  task 'opal:test' do
72
- runner = File.join(Opal.core_dir, 'opal-spec', 'runner.js')
72
+ runner = File.join Opal.core_dir, 'test_runner', 'runner.js'
73
73
  sh "phantomjs #{runner} spec/index.html"
74
74
  end
75
75
 
data/lib/opal/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Opal
2
- VERSION = '0.3.26'
2
+ VERSION = '0.3.27'
3
3
  end