rkelly-remix 0.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.
Files changed (205) hide show
  1. data/.gemtest +0 -0
  2. data/CHANGELOG.rdoc +9 -0
  3. data/Manifest.txt +203 -0
  4. data/README.rdoc +58 -0
  5. data/Rakefile +39 -0
  6. data/lib/parser.y +871 -0
  7. data/lib/rkelly/char_pos.rb +35 -0
  8. data/lib/rkelly/char_range.rb +31 -0
  9. data/lib/rkelly/constants.rb +3 -0
  10. data/lib/rkelly/generated_parser.rb +3217 -0
  11. data/lib/rkelly/js/array.rb +15 -0
  12. data/lib/rkelly/js/base.rb +91 -0
  13. data/lib/rkelly/js/boolean.rb +21 -0
  14. data/lib/rkelly/js/function.rb +39 -0
  15. data/lib/rkelly/js/function_prototype.rb +15 -0
  16. data/lib/rkelly/js/global_object.rb +52 -0
  17. data/lib/rkelly/js/math.rb +10 -0
  18. data/lib/rkelly/js/nan.rb +18 -0
  19. data/lib/rkelly/js/number.rb +22 -0
  20. data/lib/rkelly/js/object.rb +30 -0
  21. data/lib/rkelly/js/object_prototype.rb +14 -0
  22. data/lib/rkelly/js/property.rb +20 -0
  23. data/lib/rkelly/js/scope.rb +6 -0
  24. data/lib/rkelly/js/string.rb +21 -0
  25. data/lib/rkelly/js.rb +14 -0
  26. data/lib/rkelly/lexeme.rb +18 -0
  27. data/lib/rkelly/nodes/binary_node.rb +18 -0
  28. data/lib/rkelly/nodes/bracket_accessor_node.rb +11 -0
  29. data/lib/rkelly/nodes/case_clause_node.rb +11 -0
  30. data/lib/rkelly/nodes/comma_node.rb +11 -0
  31. data/lib/rkelly/nodes/conditional_node.rb +11 -0
  32. data/lib/rkelly/nodes/dot_accessor_node.rb +11 -0
  33. data/lib/rkelly/nodes/for_in_node.rb +12 -0
  34. data/lib/rkelly/nodes/for_node.rb +13 -0
  35. data/lib/rkelly/nodes/function_call_node.rb +16 -0
  36. data/lib/rkelly/nodes/function_decl_node.rb +6 -0
  37. data/lib/rkelly/nodes/function_expr_node.rb +12 -0
  38. data/lib/rkelly/nodes/if_node.rb +12 -0
  39. data/lib/rkelly/nodes/label_node.rb +11 -0
  40. data/lib/rkelly/nodes/new_expr_node.rb +11 -0
  41. data/lib/rkelly/nodes/node.rb +94 -0
  42. data/lib/rkelly/nodes/not_strict_equal_node.rb +6 -0
  43. data/lib/rkelly/nodes/op_equal_node.rb +16 -0
  44. data/lib/rkelly/nodes/postfix_node.rb +11 -0
  45. data/lib/rkelly/nodes/prefix_node.rb +6 -0
  46. data/lib/rkelly/nodes/property_node.rb +13 -0
  47. data/lib/rkelly/nodes/resolve_node.rb +19 -0
  48. data/lib/rkelly/nodes/strict_equal_node.rb +6 -0
  49. data/lib/rkelly/nodes/try_node.rb +13 -0
  50. data/lib/rkelly/nodes/var_decl_node.rb +15 -0
  51. data/lib/rkelly/nodes.rb +25 -0
  52. data/lib/rkelly/parser.rb +89 -0
  53. data/lib/rkelly/runtime/ruby_function.rb +13 -0
  54. data/lib/rkelly/runtime/scope_chain.rb +57 -0
  55. data/lib/rkelly/runtime.rb +36 -0
  56. data/lib/rkelly/syntax_error.rb +4 -0
  57. data/lib/rkelly/token.rb +24 -0
  58. data/lib/rkelly/tokenizer.rb +193 -0
  59. data/lib/rkelly/visitable.rb +16 -0
  60. data/lib/rkelly/visitors/dot_visitor.rb +228 -0
  61. data/lib/rkelly/visitors/ecma_visitor.rb +322 -0
  62. data/lib/rkelly/visitors/enumerable_visitor.rb +18 -0
  63. data/lib/rkelly/visitors/evaluation_visitor.rb +419 -0
  64. data/lib/rkelly/visitors/function_visitor.rb +46 -0
  65. data/lib/rkelly/visitors/pointcut_visitor.rb +31 -0
  66. data/lib/rkelly/visitors/real_sexp_visitor.rb +16 -0
  67. data/lib/rkelly/visitors/sexp_visitor.rb +373 -0
  68. data/lib/rkelly/visitors/visitor.rb +136 -0
  69. data/lib/rkelly/visitors.rb +4 -0
  70. data/lib/rkelly.rb +14 -0
  71. data/test/ecma_script_test_case.rb +21 -0
  72. data/test/execute_test_case.rb +16 -0
  73. data/test/execution_contexts/test_10_1_3-1.rb +32 -0
  74. data/test/expressions/test_11_3_1.rb +64 -0
  75. data/test/expressions/test_11_3_2.rb +64 -0
  76. data/test/expressions/test_11_4_2.rb +13 -0
  77. data/test/expressions/test_11_4_3.rb +52 -0
  78. data/test/expressions/test_11_4_4.rb +68 -0
  79. data/test/expressions/test_11_4_5.rb +69 -0
  80. data/test/expressions/test_11_4_6.rb +88 -0
  81. data/test/expressions/test_11_4_8.rb +28 -0
  82. data/test/expressions/test_11_4_9.rb +103 -0
  83. data/test/expressions/test_11_5_1.rb +51 -0
  84. data/test/expressions/test_11_5_2.rb +80 -0
  85. data/test/expressions/test_11_5_3.rb +88 -0
  86. data/test/expressions/test_11_6_1-1.rb +19 -0
  87. data/test/expressions/test_11_9_1.rb +19 -0
  88. data/test/function/test_15_3_1_1-1.rb +34 -0
  89. data/test/global_object/test_15_1_1_1.rb +29 -0
  90. data/test/global_object/test_15_1_1_2.rb +17 -0
  91. data/test/global_object/test_15_1_1_3.rb +9 -0
  92. data/test/helper.rb +5 -0
  93. data/test/node_test_case.rb +11 -0
  94. data/test/object/test_15_2_1_1.rb +257 -0
  95. data/test/object/test_15_2_1_2.rb +21 -0
  96. data/test/object/test_15_2_2_1.rb +52 -0
  97. data/test/statements/test_12_5-1.rb +27 -0
  98. data/test/test_add_node.rb +8 -0
  99. data/test/test_arguments_node.rb +8 -0
  100. data/test/test_array_node.rb +9 -0
  101. data/test/test_assign_expr_node.rb +8 -0
  102. data/test/test_automatic_semicolon_insertion.rb +137 -0
  103. data/test/test_bit_and_node.rb +8 -0
  104. data/test/test_bit_or_node.rb +8 -0
  105. data/test/test_bit_x_or_node.rb +8 -0
  106. data/test/test_bitwise_not_node.rb +8 -0
  107. data/test/test_block_node.rb +14 -0
  108. data/test/test_bracket_accessor_node.rb +16 -0
  109. data/test/test_break_node.rb +11 -0
  110. data/test/test_case_block_node.rb +11 -0
  111. data/test/test_case_clause_node.rb +15 -0
  112. data/test/test_char_pos.rb +39 -0
  113. data/test/test_char_range.rb +29 -0
  114. data/test/test_comma_node.rb +13 -0
  115. data/test/test_comments.rb +45 -0
  116. data/test/test_conditional_node.rb +17 -0
  117. data/test/test_const_statement_node.rb +14 -0
  118. data/test/test_continue_node.rb +11 -0
  119. data/test/test_delete_node.rb +8 -0
  120. data/test/test_divide_node.rb +8 -0
  121. data/test/test_do_while_node.rb +13 -0
  122. data/test/test_dot_accessor_node.rb +9 -0
  123. data/test/test_ecma_visitor.rb +210 -0
  124. data/test/test_element_node.rb +8 -0
  125. data/test/test_empty_statement_node.rb +8 -0
  126. data/test/test_equal_node.rb +8 -0
  127. data/test/test_evaluation_visitor.rb +66 -0
  128. data/test/test_expression_statement_node.rb +10 -0
  129. data/test/test_false_node.rb +8 -0
  130. data/test/test_for_in_node.rb +17 -0
  131. data/test/test_for_node.rb +24 -0
  132. data/test/test_function_body_node.rb +8 -0
  133. data/test/test_function_call_node.rb +10 -0
  134. data/test/test_function_decl_node.rb +16 -0
  135. data/test/test_function_expr_node.rb +16 -0
  136. data/test/test_function_visitor.rb +26 -0
  137. data/test/test_getter_property_node.rb +10 -0
  138. data/test/test_global_object.rb +49 -0
  139. data/test/test_greater_node.rb +8 -0
  140. data/test/test_greater_or_equal_node.rb +8 -0
  141. data/test/test_if_node.rb +17 -0
  142. data/test/test_in_node.rb +8 -0
  143. data/test/test_instance_of_node.rb +8 -0
  144. data/test/test_label_node.rb +13 -0
  145. data/test/test_left_shift_node.rb +8 -0
  146. data/test/test_less_node.rb +8 -0
  147. data/test/test_less_or_equal_node.rb +8 -0
  148. data/test/test_line_number.rb +81 -0
  149. data/test/test_logical_and_node.rb +8 -0
  150. data/test/test_logical_not_node.rb +8 -0
  151. data/test/test_logical_or_node.rb +8 -0
  152. data/test/test_modulus_node.rb +8 -0
  153. data/test/test_multiply_node.rb +8 -0
  154. data/test/test_new_expr_node.rb +9 -0
  155. data/test/test_not_equal_node.rb +8 -0
  156. data/test/test_not_strict_equal_node.rb +8 -0
  157. data/test/test_null_node.rb +8 -0
  158. data/test/test_number_node.rb +8 -0
  159. data/test/test_object_literal_node.rb +9 -0
  160. data/test/test_op_and_equal_node.rb +10 -0
  161. data/test/test_op_divide_equal_node.rb +10 -0
  162. data/test/test_op_equal_node.rb +10 -0
  163. data/test/test_op_l_shift_equal_node.rb +10 -0
  164. data/test/test_op_minus_equal_node.rb +10 -0
  165. data/test/test_op_mod_equal_node.rb +10 -0
  166. data/test/test_op_multiply_equal_node.rb +10 -0
  167. data/test/test_op_or_equal_node.rb +10 -0
  168. data/test/test_op_plus_equal_node.rb +10 -0
  169. data/test/test_op_r_shift_equal_node.rb +10 -0
  170. data/test/test_op_u_r_shift_equal_node.rb +10 -0
  171. data/test/test_op_x_or_equal_node.rb +10 -0
  172. data/test/test_parameter_node.rb +8 -0
  173. data/test/test_parser.rb +1361 -0
  174. data/test/test_pointcut_visitor.rb +34 -0
  175. data/test/test_postfix_node.rb +8 -0
  176. data/test/test_prefix_node.rb +8 -0
  177. data/test/test_property_node.rb +8 -0
  178. data/test/test_regexp_node.rb +8 -0
  179. data/test/test_resolve_node.rb +22 -0
  180. data/test/test_return_node.rb +11 -0
  181. data/test/test_right_shift_node.rb +8 -0
  182. data/test/test_rkelly.rb +19 -0
  183. data/test/test_runtime.rb +12 -0
  184. data/test/test_scope_chain.rb +50 -0
  185. data/test/test_setter_property_node.rb +10 -0
  186. data/test/test_source_elements.rb +9 -0
  187. data/test/test_strict_equal_node.rb +8 -0
  188. data/test/test_string_node.rb +8 -0
  189. data/test/test_subtract_node.rb +8 -0
  190. data/test/test_switch_node.rb +12 -0
  191. data/test/test_this_node.rb +8 -0
  192. data/test/test_throw_node.rb +7 -0
  193. data/test/test_tokenizer.rb +209 -0
  194. data/test/test_true_node.rb +8 -0
  195. data/test/test_try_node.rb +59 -0
  196. data/test/test_type_of_node.rb +8 -0
  197. data/test/test_unary_minus_node.rb +8 -0
  198. data/test/test_unary_plus_node.rb +8 -0
  199. data/test/test_unsigned_right_shift_node.rb +8 -0
  200. data/test/test_var_decl_node.rb +21 -0
  201. data/test/test_var_statement_node.rb +14 -0
  202. data/test/test_void_node.rb +8 -0
  203. data/test/test_while_node.rb +15 -0
  204. data/test/test_with_node.rb +8 -0
  205. metadata +432 -0
@@ -0,0 +1,94 @@
1
+ module RKelly
2
+ module Nodes
3
+ class Node
4
+ include RKelly::Visitable
5
+ include RKelly::Visitors
6
+ include Enumerable
7
+
8
+ attr_accessor :value, :comments, :range, :filename
9
+ def initialize(value)
10
+ @value = value
11
+ @comments = []
12
+ @range = CharRange::EMPTY
13
+ @filename = nil
14
+ end
15
+
16
+ # For backwards compatibility
17
+ def line
18
+ @range.from.line
19
+ end
20
+
21
+ def ==(other)
22
+ other.is_a?(self.class) && @value == other.value
23
+ end
24
+ alias :=~ :==
25
+
26
+ def ===(other)
27
+ other.is_a?(self.class) && @value === other.value
28
+ end
29
+
30
+ def pointcut(pattern)
31
+ case pattern
32
+ when String
33
+ ast = RKelly::Parser.new.parse(pattern)
34
+ # Only take the first statement
35
+ finder = ast.value.first.class.to_s =~ /StatementNode$/ ?
36
+ ast.value.first.value : ast.value.first
37
+ visitor = PointcutVisitor.new(finder)
38
+ else
39
+ visitor = PointcutVisitor.new(pattern)
40
+ end
41
+
42
+ visitor.accept(self)
43
+ visitor
44
+ end
45
+ alias :/ :pointcut
46
+
47
+ def to_sexp
48
+ SexpVisitor.new.accept(self)
49
+ end
50
+
51
+ def to_ecma
52
+ ECMAVisitor.new.accept(self)
53
+ end
54
+
55
+ def to_dots
56
+ visitor = DotVisitor.new
57
+ visitor.accept(self)
58
+ header = <<-END
59
+ digraph g {
60
+ graph [ rankdir = "TB" ];
61
+ node [
62
+ fontsize = "16"
63
+ shape = "ellipse"
64
+ ];
65
+ edge [ ];
66
+ END
67
+ nodes = visitor.nodes.map { |x| x.to_s }.join("\n")
68
+ counter = 0
69
+ arrows = visitor.arrows.map { |x|
70
+ s = "#{x} [\nid = #{counter}\n];"
71
+ counter += 1
72
+ s
73
+ }.join("\n")
74
+ "#{header}\n#{nodes}\n#{arrows}\n}"
75
+ end
76
+
77
+ def each(&block)
78
+ EnumerableVisitor.new(block).accept(self)
79
+ end
80
+
81
+ def to_real_sexp
82
+ RealSexpVisitor.new.accept(self)
83
+ end
84
+ end
85
+
86
+ %w[EmptyStatement Parenthetical ExpressionStatement True Delete Return TypeOf
87
+ SourceElements Number LogicalNot AssignExpr FunctionBody
88
+ ObjectLiteral UnaryMinus Throw This BitwiseNot Element String
89
+ Array CaseBlock Null Break Parameter Block False Void Regexp
90
+ Arguments Attr Continue ConstStatement UnaryPlus VarStatement].each do |node|
91
+ eval "class #{node}Node < Node; end"
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,6 @@
1
+ module RKelly
2
+ module Nodes
3
+ class NotStrictEqualNode < BinaryNode
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,16 @@
1
+ module RKelly
2
+ module Nodes
3
+ class OpEqualNode < Node
4
+ attr_reader :left
5
+ def initialize(left, right)
6
+ super(right)
7
+ @left = left
8
+ end
9
+ end
10
+
11
+ %w[Multiply Divide LShift Minus Plus Mod XOr RShift And URShift Or].each do |node|
12
+ eval "class Op#{node}EqualNode < OpEqualNode; end"
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module RKelly
2
+ module Nodes
3
+ class PostfixNode < Node
4
+ attr_reader :operand
5
+ def initialize(operand, operator)
6
+ super(operator)
7
+ @operand = operand
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module RKelly
2
+ module Nodes
3
+ class PrefixNode < PostfixNode
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ module RKelly
2
+ module Nodes
3
+ class PropertyNode < Node
4
+ attr_reader :name
5
+ def initialize(name, value)
6
+ super(value)
7
+ @name = name
8
+ end
9
+ end
10
+
11
+ %w[Getter Setter].each {|node| eval "class #{node}PropertyNode < PropertyNode; end"}
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module RKelly
2
+ module Nodes
3
+ class ResolveNode < Node
4
+ def ==(other)
5
+ return true if super
6
+ if @value =~ /^[A-Z]/
7
+ place = [Object, Module, RKelly::Nodes].find { |x|
8
+ x.const_defined?(@value.to_sym)
9
+ }
10
+ return false unless place
11
+ klass = place.const_get(@value.to_sym)
12
+ return true if klass && other.is_a?(klass) || other.value.is_a?(klass)
13
+ end
14
+ false
15
+ end
16
+ alias :=~ :==
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ module RKelly
2
+ module Nodes
3
+ class StrictEqualNode < BinaryNode
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ module RKelly
2
+ module Nodes
3
+ class TryNode < Node
4
+ attr_reader :catch_var, :catch_block, :finally_block
5
+ def initialize(value, catch_var, catch_block, finally_block = nil)
6
+ super(value)
7
+ @catch_var = catch_var
8
+ @catch_block = catch_block
9
+ @finally_block = finally_block
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module RKelly
2
+ module Nodes
3
+ class VarDeclNode < Node
4
+ attr_accessor :name, :type
5
+ def initialize(name, value, constant = false)
6
+ super(value)
7
+ @name = name
8
+ @constant = constant
9
+ end
10
+
11
+ def constant?; @constant; end
12
+ def variable?; !@constant; end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ require 'rkelly/nodes/node'
2
+ require 'rkelly/nodes/function_expr_node'
3
+ require 'rkelly/nodes/binary_node'
4
+ require 'rkelly/nodes/bracket_accessor_node'
5
+ require 'rkelly/nodes/case_clause_node'
6
+ require 'rkelly/nodes/comma_node'
7
+ require 'rkelly/nodes/conditional_node'
8
+ require 'rkelly/nodes/dot_accessor_node'
9
+ require 'rkelly/nodes/for_in_node'
10
+ require 'rkelly/nodes/for_node'
11
+ require 'rkelly/nodes/function_call_node'
12
+ require 'rkelly/nodes/function_decl_node'
13
+ require 'rkelly/nodes/function_expr_node'
14
+ require 'rkelly/nodes/if_node'
15
+ require 'rkelly/nodes/label_node'
16
+ require 'rkelly/nodes/new_expr_node'
17
+ require 'rkelly/nodes/not_strict_equal_node'
18
+ require 'rkelly/nodes/op_equal_node'
19
+ require 'rkelly/nodes/postfix_node'
20
+ require 'rkelly/nodes/prefix_node'
21
+ require 'rkelly/nodes/property_node'
22
+ require 'rkelly/nodes/resolve_node'
23
+ require 'rkelly/nodes/strict_equal_node'
24
+ require 'rkelly/nodes/try_node'
25
+ require 'rkelly/nodes/var_decl_node'
@@ -0,0 +1,89 @@
1
+ require 'rkelly/tokenizer'
2
+ require 'rkelly/generated_parser'
3
+
4
+
5
+ module RKelly
6
+ class Parser < RKelly::GeneratedParser
7
+ TOKENIZER = Tokenizer.new
8
+
9
+ RKelly::GeneratedParser.instance_methods.each do |im|
10
+ next unless im.to_s =~ /^_reduce_\d+$/
11
+ eval(<<-eoawesomehack)
12
+ def #{im}(val, _values, result)
13
+ r = super(val.map { |v|
14
+ v.is_a?(Token) ? v.to_racc_token[1] : v
15
+ }, _values, result)
16
+
17
+ suitable_values = val.flatten.find_all {|v| v.is_a?(Node) || v.is_a?(Token) }
18
+ first = suitable_values.first
19
+ last = suitable_values.last
20
+ if first
21
+ r.range = CharRange.new(first.range.from, last.range.to) if r.respond_to?(:range)
22
+ r.filename = @filename if r.respond_to?(:filename)
23
+ end
24
+ r
25
+ end
26
+ eoawesomehack
27
+ end
28
+
29
+ attr_accessor :logger
30
+ def initialize
31
+ @tokens = []
32
+ @logger = nil
33
+ @terminator = false
34
+ @prev_token = nil
35
+ @comments = []
36
+ end
37
+
38
+ # Parse +javascript+ and return an AST
39
+ def parse(javascript, filename = nil)
40
+ @tokens = TOKENIZER.raw_tokens(javascript)
41
+ @position = 0
42
+ @filename = filename
43
+ ast = do_parse
44
+ ast.comments = @comments if ast
45
+ ast
46
+ end
47
+
48
+ def yyabort
49
+ raise "something bad happened, please report a bug with sample JavaScript"
50
+ end
51
+
52
+ private
53
+ def on_error(error_token_id, error_value, value_stack)
54
+ if logger
55
+ logger.error(token_to_str(error_token_id))
56
+ logger.error("error value: #{error_value}")
57
+ logger.error("error stack: #{value_stack}")
58
+ end
59
+ end
60
+
61
+ def next_token
62
+ @terminator = false
63
+ begin
64
+ return [false, false] if @position >= @tokens.length
65
+ n_token = @tokens[@position]
66
+ @position += 1
67
+ case @tokens[@position - 1].name
68
+ when :COMMENT
69
+ @comments << n_token
70
+ @terminator = true if n_token.value =~ /^\/\//
71
+ when :S
72
+ @terminator = true if n_token.value =~ /[\r\n]/
73
+ end
74
+ end while([:COMMENT, :S].include?(n_token.name))
75
+
76
+ if @terminator &&
77
+ ((@prev_token && %w[continue break return throw].include?(@prev_token.value)) ||
78
+ (n_token && %w[++ --].include?(n_token.value)))
79
+ @position -= 1
80
+ return (@prev_token = RKelly::Token.new(';', ';')).to_racc_token
81
+ end
82
+
83
+ @prev_token = n_token
84
+ v = n_token.to_racc_token
85
+ v[1] = n_token
86
+ v
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,13 @@
1
+ module RKelly
2
+ class Runtime
3
+ class RubyFunction
4
+ def initialize(&block)
5
+ @code = block
6
+ end
7
+
8
+ def call(chain, *args)
9
+ @code.call(*args)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,57 @@
1
+ module RKelly
2
+ class Runtime
3
+ class ScopeChain
4
+ include RKelly::JS
5
+
6
+ def initialize(scope = Scope.new)
7
+ @chain = [GlobalObject.new]
8
+ end
9
+
10
+ def <<(scope)
11
+ @chain << scope
12
+ end
13
+
14
+ def has_property?(name)
15
+ scope = @chain.reverse.find { |x|
16
+ x.has_property?(name)
17
+ }
18
+ scope ? scope[name] : nil
19
+ end
20
+
21
+ def [](name)
22
+ property = has_property?(name)
23
+ return property if property
24
+ @chain.last.properties[name]
25
+ end
26
+
27
+ def []=(name, value)
28
+ @chain.last.properties[name] = value
29
+ end
30
+
31
+ def pop
32
+ @chain.pop
33
+ end
34
+
35
+ def this
36
+ @chain.last
37
+ end
38
+
39
+ def new_scope(&block)
40
+ @chain << Scope.new
41
+ result = yield(self)
42
+ @chain.pop
43
+ result
44
+ end
45
+
46
+ def return=(value)
47
+ @chain.last.return = value
48
+ end
49
+
50
+ def return; @chain.last.return; end
51
+
52
+ def returned?
53
+ @chain.last.returned?
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,36 @@
1
+ require 'rkelly/js'
2
+ require 'rkelly/runtime/scope_chain'
3
+
4
+ module RKelly
5
+ class Runtime
6
+ UNDEFINED = RKelly::JS::Property.new(:undefined, :undefined)
7
+
8
+ def initialize
9
+ @parser = Parser.new
10
+ @scope = ScopeChain.new
11
+ end
12
+
13
+ # Execute +js+
14
+ def execute(js)
15
+ function_visitor = Visitors::FunctionVisitor.new(@scope)
16
+ eval_visitor = Visitors::EvaluationVisitor.new(@scope)
17
+ tree = @parser.parse(js)
18
+ function_visitor.accept(tree)
19
+ eval_visitor.accept(tree)
20
+ @scope
21
+ end
22
+
23
+ def call_function(function_name, *args)
24
+ function = @scope[function_name].value
25
+ @scope.new_scope { |chain|
26
+ function.js_call(chain, *(args.map { |x|
27
+ RKelly::JS::Property.new(:param, x)
28
+ }))
29
+ }.value
30
+ end
31
+
32
+ def define_function(function, &block)
33
+ @scope[function.to_s].function = block
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,4 @@
1
+ module RKelly
2
+ class SyntaxError < ::SyntaxError
3
+ end
4
+ end
@@ -0,0 +1,24 @@
1
+ module RKelly
2
+ class Token
3
+ attr_accessor :name, :value, :transformer, :range
4
+ def initialize(name, value, &transformer)
5
+ @name = name
6
+ @value = value
7
+ @transformer = transformer
8
+ end
9
+
10
+ # For backwards compatibility
11
+ def line
12
+ @range.from.line
13
+ end
14
+
15
+ def to_racc_token
16
+ return transformer.call(name, value) if transformer
17
+ [name, value]
18
+ end
19
+
20
+ def to_s
21
+ return "#{self.name}: #{self.value}"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,193 @@
1
+ require 'rkelly/lexeme'
2
+ require 'rkelly/char_range'
3
+ require 'strscan'
4
+
5
+ module RKelly
6
+ class Tokenizer
7
+ KEYWORDS = Hash[%w{
8
+ break case catch continue default delete do else finally for function
9
+ if in instanceof new return switch this throw try typeof var void while
10
+ with
11
+
12
+ const true false null debugger
13
+ }.map {|kw| [kw, kw.upcase.to_sym] }]
14
+
15
+ RESERVED = Hash[%w{
16
+ abstract boolean byte char class double enum export extends
17
+ final float goto implements import int interface long native package
18
+ private protected public short static super synchronized throws
19
+ transient volatile
20
+ }.map {|kw| [kw, true] }]
21
+
22
+ LITERALS = {
23
+ # Punctuators
24
+ '==' => :EQEQ,
25
+ '!=' => :NE,
26
+ '===' => :STREQ,
27
+ '!==' => :STRNEQ,
28
+ '<=' => :LE,
29
+ '>=' => :GE,
30
+ '||' => :OR,
31
+ '&&' => :AND,
32
+ '++' => :PLUSPLUS,
33
+ '--' => :MINUSMINUS,
34
+ '<<' => :LSHIFT,
35
+ '<<=' => :LSHIFTEQUAL,
36
+ '>>' => :RSHIFT,
37
+ '>>=' => :RSHIFTEQUAL,
38
+ '>>>' => :URSHIFT,
39
+ '>>>='=> :URSHIFTEQUAL,
40
+ '&=' => :ANDEQUAL,
41
+ '%=' => :MODEQUAL,
42
+ '^=' => :XOREQUAL,
43
+ '|=' => :OREQUAL,
44
+ '+=' => :PLUSEQUAL,
45
+ '-=' => :MINUSEQUAL,
46
+ '*=' => :MULTEQUAL,
47
+ '/=' => :DIVEQUAL,
48
+ }
49
+
50
+ # Some keywords can be followed by regular expressions (eg, return and throw).
51
+ # Others can be followed by division.
52
+ KEYWORDS_THAT_IMPLY_DIVISION = {
53
+ 'this' => true,
54
+ 'true' => true,
55
+ 'false' => true,
56
+ 'null' => true,
57
+ }
58
+
59
+ KEYWORDS_THAT_IMPLY_REGEX = KEYWORDS.reject {|k,v| KEYWORDS_THAT_IMPLY_DIVISION[k] }
60
+
61
+ SINGLE_CHARS_THAT_IMPLY_DIVISION = {
62
+ ')' => true,
63
+ ']' => true,
64
+ '}' => true,
65
+ }
66
+
67
+ def initialize(&block)
68
+ @lexemes = Hash.new {|hash, key| hash[key] = [] }
69
+
70
+ token(:COMMENT, /\/(?:\*(?:.)*?\*\/|\/[^\n]*)/m, ['/'])
71
+ token(:STRING, /"(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)'/m, ["'", '"'])
72
+ token(:S, /[\s\r\n]*/m, [" ", "\t", "\r", "\n", "\f"])
73
+
74
+ # A regexp to match floating point literals (but not integer literals).
75
+ digits = ('0'..'9').to_a
76
+ token(:NUMBER, /\d+\.\d*(?:[eE][-+]?\d+)?|\d+(?:\.\d*)?[eE][-+]?\d+|\.\d+(?:[eE][-+]?\d+)?/m, digits+['.']) do |type, value|
77
+ value.gsub!(/\.(\D)/, '.0\1') if value =~ /\.\w/
78
+ value.gsub!(/\.$/, '.0') if value =~ /\.$/
79
+ value.gsub!(/^\./, '0.') if value =~ /^\./
80
+ [type, eval(value)]
81
+ end
82
+ token(:NUMBER, /0[xX][\da-fA-F]+|0[0-7]*|\d+/, digits) do |type, value|
83
+ [type, eval(value)]
84
+ end
85
+
86
+ word_chars = ('a'..'z').to_a + ('A'..'Z').to_a + ['_', '$']
87
+ token(:RAW_IDENT, /([_\$A-Za-z][_\$0-9A-Za-z]*)/, word_chars) do |type,value|
88
+ if KEYWORDS[value]
89
+ [KEYWORDS[value], value]
90
+ elsif RESERVED[value]
91
+ [:RESERVED, value]
92
+ else
93
+ [:IDENT, value]
94
+ end
95
+ end
96
+
97
+ # To distinguish regular expressions from comments, we require that
98
+ # regular expressions start with a non * character (ie, not look like
99
+ # /*foo*/). Note that we can't depend on the length of the match to
100
+ # correctly distinguish, since `/**/i` is longer if matched as a regular
101
+ # expression than as matched as a comment.
102
+ # Incidentally, we're also not matching empty regular expressions
103
+ # (eg, // and //g). Here we could depend on match length and priority to
104
+ # determine that these are actually comments, but it turns out to be
105
+ # easier to not match them in the first place.
106
+ token(:REGEXP, %r{
107
+ / (?# beginning )
108
+
109
+ (?:
110
+ [^\r\n\[/\\]+ (?# any char except \r \n [ / \ )
111
+ |
112
+ \\ [^\r\n] (?# escape sequence )
113
+ |
114
+ \[ (?:[^\]\\]|\\.)* \] (?# [...] can contain any char including / )
115
+ (?# only \ and ] have to be escaped here )
116
+ )+
117
+
118
+ /[gim]* (?# ending + modifiers )
119
+ }x, ['/'])
120
+
121
+ literal_chars = LITERALS.keys.map {|k| k.slice(0,1) }.uniq
122
+ literal_regex = Regexp.new(LITERALS.keys.sort_by { |x|
123
+ x.length
124
+ }.reverse.map { |x| "#{x.gsub(/([|+*^])/, '\\\\\1')}" }.join('|'))
125
+ token(:LITERALS, literal_regex, literal_chars) do |type, value|
126
+ [LITERALS[value], value]
127
+ end
128
+
129
+ symbols = ('!'..'/').to_a + (':'..'@').to_a + ('['..'^').to_a + ['`'] + ('{'..'~').to_a
130
+ token(:SINGLE_CHAR, /./, symbols) do |type, value|
131
+ [value, value]
132
+ end
133
+ end
134
+
135
+ def tokenize(string)
136
+ raw_tokens(string).map { |x| x.to_racc_token }
137
+ end
138
+
139
+ def raw_tokens(string)
140
+ scanner = StringScanner.new(string)
141
+ tokens = []
142
+ range = CharRange::EMPTY
143
+ accepting_regexp = true
144
+ while !scanner.eos?
145
+ token = match_lexeme(scanner, accepting_regexp)
146
+
147
+ if token.name != :S
148
+ accepting_regexp = followable_by_regex(token)
149
+ end
150
+
151
+ scanner.pos += token.value.length
152
+ token.range = range = range.next(token.value)
153
+ tokens << token
154
+ end
155
+ tokens
156
+ end
157
+
158
+ private
159
+
160
+ # Returns the token of the first matching lexeme
161
+ def match_lexeme(scanner, accepting_regexp)
162
+ @lexemes[scanner.peek(1)].each do |lexeme|
163
+ next if lexeme.name == :REGEXP && !accepting_regexp
164
+
165
+ token = lexeme.match(scanner)
166
+ return token if token
167
+ end
168
+ end
169
+
170
+ # Registers a lexeme and maps it to all the characters it can
171
+ # begin with. So later when scanning the source we only need to
172
+ # match those lexemes that can begin with the character we're at.
173
+ def token(name, pattern, chars, &block)
174
+ lexeme = Lexeme.new(name, pattern, &block)
175
+ chars.each do |c|
176
+ @lexemes[c] << lexeme
177
+ end
178
+ end
179
+
180
+ def followable_by_regex(current_token)
181
+ case current_token.name
182
+ when :RAW_IDENT
183
+ KEYWORDS_THAT_IMPLY_REGEX[current_token.value]
184
+ when :NUMBER
185
+ false
186
+ when :SINGLE_CHAR
187
+ !SINGLE_CHARS_THAT_IMPLY_DIVISION[current_token.value]
188
+ else
189
+ true
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,16 @@
1
+ module RKelly
2
+ module Visitable
3
+ # Based off the visitor pattern from RubyGarden
4
+ def accept(visitor, &block)
5
+ klass = self.class.ancestors.find { |ancestor|
6
+ visitor.respond_to?("visit_#{ancestor.name.split(/::/)[-1]}")
7
+ }
8
+
9
+ if klass
10
+ visitor.send(:"visit_#{klass.name.split(/::/)[-1]}", self, &block)
11
+ else
12
+ raise "No visitor for '#{self.class}'"
13
+ end
14
+ end
15
+ end
16
+ end