rkelly-turbo 0.1.0

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. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +38 -0
  3. data/Manifest.txt +203 -0
  4. data/README.rdoc +135 -0
  5. data/Rakefile +41 -0
  6. data/lib/parser.y +883 -0
  7. data/lib/rkelly.rb +14 -0
  8. data/lib/rkelly/char_pos.rb +39 -0
  9. data/lib/rkelly/char_range.rb +33 -0
  10. data/lib/rkelly/constants.rb +3 -0
  11. data/lib/rkelly/generated_parser.rb +3380 -0
  12. data/lib/rkelly/js.rb +14 -0
  13. data/lib/rkelly/js/array.rb +15 -0
  14. data/lib/rkelly/js/base.rb +91 -0
  15. data/lib/rkelly/js/boolean.rb +21 -0
  16. data/lib/rkelly/js/function.rb +39 -0
  17. data/lib/rkelly/js/function_prototype.rb +15 -0
  18. data/lib/rkelly/js/global_object.rb +52 -0
  19. data/lib/rkelly/js/math.rb +10 -0
  20. data/lib/rkelly/js/nan.rb +18 -0
  21. data/lib/rkelly/js/number.rb +22 -0
  22. data/lib/rkelly/js/object.rb +30 -0
  23. data/lib/rkelly/js/object_prototype.rb +14 -0
  24. data/lib/rkelly/js/property.rb +20 -0
  25. data/lib/rkelly/js/scope.rb +6 -0
  26. data/lib/rkelly/js/string.rb +21 -0
  27. data/lib/rkelly/lexeme.rb +18 -0
  28. data/lib/rkelly/nodes.rb +25 -0
  29. data/lib/rkelly/nodes/binary_node.rb +18 -0
  30. data/lib/rkelly/nodes/bracket_accessor_node.rb +11 -0
  31. data/lib/rkelly/nodes/case_clause_node.rb +11 -0
  32. data/lib/rkelly/nodes/comma_node.rb +11 -0
  33. data/lib/rkelly/nodes/conditional_node.rb +11 -0
  34. data/lib/rkelly/nodes/dot_accessor_node.rb +11 -0
  35. data/lib/rkelly/nodes/for_in_node.rb +12 -0
  36. data/lib/rkelly/nodes/for_node.rb +13 -0
  37. data/lib/rkelly/nodes/function_call_node.rb +16 -0
  38. data/lib/rkelly/nodes/function_decl_node.rb +6 -0
  39. data/lib/rkelly/nodes/function_expr_node.rb +12 -0
  40. data/lib/rkelly/nodes/if_node.rb +12 -0
  41. data/lib/rkelly/nodes/label_node.rb +11 -0
  42. data/lib/rkelly/nodes/new_expr_node.rb +11 -0
  43. data/lib/rkelly/nodes/node.rb +116 -0
  44. data/lib/rkelly/nodes/not_strict_equal_node.rb +6 -0
  45. data/lib/rkelly/nodes/op_equal_node.rb +16 -0
  46. data/lib/rkelly/nodes/postfix_node.rb +11 -0
  47. data/lib/rkelly/nodes/prefix_node.rb +6 -0
  48. data/lib/rkelly/nodes/property_node.rb +13 -0
  49. data/lib/rkelly/nodes/resolve_node.rb +19 -0
  50. data/lib/rkelly/nodes/strict_equal_node.rb +6 -0
  51. data/lib/rkelly/nodes/try_node.rb +13 -0
  52. data/lib/rkelly/nodes/var_decl_node.rb +15 -0
  53. data/lib/rkelly/parser.rb +106 -0
  54. data/lib/rkelly/runtime.rb +36 -0
  55. data/lib/rkelly/runtime/ruby_function.rb +13 -0
  56. data/lib/rkelly/runtime/scope_chain.rb +57 -0
  57. data/lib/rkelly/syntax_error.rb +4 -0
  58. data/lib/rkelly/token.rb +27 -0
  59. data/lib/rkelly/tokenizer.rb +255 -0
  60. data/lib/rkelly/visitable.rb +31 -0
  61. data/lib/rkelly/visitors.rb +4 -0
  62. data/lib/rkelly/visitors/dot_visitor.rb +228 -0
  63. data/lib/rkelly/visitors/ecma_visitor.rb +328 -0
  64. data/lib/rkelly/visitors/enumerable_visitor.rb +18 -0
  65. data/lib/rkelly/visitors/evaluation_visitor.rb +419 -0
  66. data/lib/rkelly/visitors/function_visitor.rb +46 -0
  67. data/lib/rkelly/visitors/pointcut_visitor.rb +31 -0
  68. data/lib/rkelly/visitors/real_sexp_visitor.rb +16 -0
  69. data/lib/rkelly/visitors/sexp_visitor.rb +373 -0
  70. data/lib/rkelly/visitors/visitor.rb +149 -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 +94 -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 +213 -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 +1434 -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 +285 -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 +293 -0
@@ -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,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,4 @@
1
+ module RKelly
2
+ class SyntaxError < ::SyntaxError
3
+ end
4
+ end
@@ -0,0 +1,27 @@
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 if transformer
8
+ end
9
+
10
+ # For backwards compatibility
11
+ def line
12
+ @range.from.line
13
+ end
14
+
15
+ def to_racc_token
16
+ if @transformer
17
+ @name, @value = @transformer.call(@name, @value)
18
+ @transformer = nil
19
+ end
20
+ [@name, @value]
21
+ end
22
+
23
+ def to_s
24
+ return "#{self.name}: #{self.value}"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,255 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rkelly/lexeme'
4
+ require 'rkelly/char_range'
5
+ require 'strscan'
6
+
7
+ module RKelly
8
+ class Tokenizer
9
+ KEYWORDS = Hash[%w{
10
+ break case catch continue default delete do else finally for function
11
+ if in instanceof new return switch this throw try typeof var void while
12
+ with
13
+
14
+ const true false null debugger
15
+ }.map {|kw| [kw, kw.upcase.to_sym] }]
16
+
17
+ # These 6 are always reserved in ECMAScript 5.1
18
+ # Some others are only reserved in strict mode, but RKelly doesn't
19
+ # differenciate between strict and non-strict mode code.
20
+ # http://www.ecma-international.org/ecma-262/5.1/#sec-7.6.1.2
21
+ # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Reserved_Words
22
+ RESERVED = Hash[%w{
23
+ class enum export extends import super
24
+ }.map {|kw| [kw, true] }]
25
+
26
+ LITERALS = {
27
+ # Punctuators
28
+ '==' => :EQEQ,
29
+ '!=' => :NE,
30
+ '===' => :STREQ,
31
+ '!==' => :STRNEQ,
32
+ '<=' => :LE,
33
+ '>=' => :GE,
34
+ '||' => :OR,
35
+ '&&' => :AND,
36
+ '++' => :PLUSPLUS,
37
+ '--' => :MINUSMINUS,
38
+ '<<' => :LSHIFT,
39
+ '<<=' => :LSHIFTEQUAL,
40
+ '>>' => :RSHIFT,
41
+ '>>=' => :RSHIFTEQUAL,
42
+ '>>>' => :URSHIFT,
43
+ '>>>='=> :URSHIFTEQUAL,
44
+ '&=' => :ANDEQUAL,
45
+ '%=' => :MODEQUAL,
46
+ '^=' => :XOREQUAL,
47
+ '|=' => :OREQUAL,
48
+ '+=' => :PLUSEQUAL,
49
+ '-=' => :MINUSEQUAL,
50
+ '*=' => :MULTEQUAL,
51
+ '/=' => :DIVEQUAL,
52
+ }
53
+
54
+ # Some keywords can be followed by regular expressions (eg, return and throw).
55
+ # Others can be followed by division.
56
+ KEYWORDS_THAT_IMPLY_DIVISION = {
57
+ 'this' => true,
58
+ 'true' => true,
59
+ 'false' => true,
60
+ 'null' => true,
61
+ }
62
+
63
+ KEYWORDS_THAT_IMPLY_REGEX = KEYWORDS.reject {|k,v| KEYWORDS_THAT_IMPLY_DIVISION[k] }
64
+
65
+ SINGLE_CHARS_THAT_IMPLY_DIVISION = {
66
+ ')' => true,
67
+ ']' => true,
68
+ '}' => true,
69
+ }
70
+
71
+ # Determine the method to use to measure String length in bytes,
72
+ # because StringScanner#pos can only be set in bytes.
73
+ #
74
+ # - In Ruby 1.8 String#length returns always the string length
75
+ # in bytes.
76
+ #
77
+ # - In Ruby 1.9+ String#length returns string length in
78
+ # characters and we need to use String#bytesize instead.
79
+ #
80
+ BYTESIZE_METHOD = "".respond_to?(:bytesize) ? :bytesize : :length
81
+
82
+ # JavaScript whitespace can consist of any Unicode space separator
83
+ # characters.
84
+ #
85
+ # - In Ruby 1.9+ we can just use the [[:space:]] character class
86
+ # and match them all.
87
+ #
88
+ # - In Ruby 1.8 we need a regex that identifies the specific bytes
89
+ # in UTF-8 text.
90
+ #
91
+ WHITESPACE_REGEX = "".respond_to?(:encoding) ? /[[:space:]]+/m : %r{
92
+ (
93
+ \xC2\xA0 | # no-break space
94
+ \xE1\x9A\x80 | # ogham space mark
95
+ \xE2\x80\x80 | # en quad
96
+ \xE2\x80\x81 | # em quad
97
+ \xE2\x80\x82 | # en space
98
+ \xE2\x80\x83 | # em space
99
+ \xE2\x80\x84 | # three-per-em space
100
+ \xE2\x80\x85 | # four-pre-em süace
101
+ \xE2\x80\x86 | # six-per-em space
102
+ \xE2\x80\x87 | # figure space
103
+ \xE2\x80\x88 | # punctuation space
104
+ \xE2\x80\x89 | # thin space
105
+ \xE2\x80\x8A | # hair space
106
+ \xE2\x80\xA8 | # line separator
107
+ \xE2\x80\xA9 | # paragraph separator
108
+ \xE2\x80\xAF | # narrow no-break space
109
+ \xE2\x81\x9F | # medium mathematical space
110
+ \xE3\x80\x80 # ideographic space
111
+ )+
112
+ }mx
113
+
114
+ WORD_CHARS = (('a'..'z').to_a + ('A'..'Z').to_a + ['_', '$']).freeze
115
+ DIGITS = ('0'..'9').to_a.freeze
116
+ def initialize(&block)
117
+ @lexemes = Hash.new {|hash, key| hash[key] = [] }
118
+
119
+ token(:COMMENT, /\/(?:\*(?:.)*?\*\/|\/[^\n]*)/m, ['/'])
120
+ token(:STRING, /"(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)'/m, ["'", '"'])
121
+
122
+ # Matcher for basic ASCII whitespace.
123
+ # (Unicode whitespace is handled separately in #match_lexeme)
124
+ #
125
+ # Can't use just "\s" in regex, because in Ruby 1.8 this
126
+ # doesn't include the vertical tab "\v" character
127
+ token(:S, /[ \t\r\n\f\v]*/m, [" ", "\t", "\r", "\n", "\f", "\v"])
128
+
129
+ # A regexp to match floating point literals (but not integer literals).
130
+
131
+ token(:NUMBER, /\d+\.\d*(?:[eE][-+]?\d+)?|\d+(?:\.\d*)?[eE][-+]?\d+|\.\d+(?:[eE][-+]?\d+)?/m, DIGITS+['.']) do |type, value|
132
+ value.gsub!(/\.(\D)/, '.0\1') if value =~ /\.\w/
133
+ #value.gsub!(/\.$/, '.0') if value.end_with? '.'
134
+ #value.gsub!(/^\./, '0.') if value.start_with? '.'
135
+ [type, value.to_f]
136
+ end
137
+ token(:NUMBER, /0[xX][\da-fA-F]+|0[oO][0-7]+|0[0-7]*|\d+/, DIGITS) do |type, value|
138
+ [type, value.to_i(0)]
139
+ end
140
+
141
+ token(:RAW_IDENT, /([_\$A-Za-z][_\$0-9A-Za-z]*)/, WORD_CHARS) do |type,value|
142
+ if KEYWORDS[value]
143
+ [KEYWORDS[value], value]
144
+ elsif RESERVED[value]
145
+ [:RESERVED, value]
146
+ else
147
+ [:IDENT, value]
148
+ end
149
+ end
150
+
151
+ # To distinguish regular expressions from comments, we require that
152
+ # regular expressions start with a non * character (ie, not look like
153
+ # /*foo*/). Note that we can't depend on the length of the match to
154
+ # correctly distinguish, since `/**/i` is longer if matched as a regular
155
+ # expression than as matched as a comment.
156
+ # Incidentally, we're also not matching empty regular expressions
157
+ # (eg, // and //g). Here we could depend on match length and priority to
158
+ # determine that these are actually comments, but it turns out to be
159
+ # easier to not match them in the first place.
160
+ token(:REGEXP, %r{
161
+ / (?# beginning )
162
+
163
+ (?:
164
+ [^\r\n\[/\\]+ (?# any char except \r \n [ / \ )
165
+ |
166
+ \\ [^\r\n] (?# escape sequence )
167
+ |
168
+ \[ (?:[^\]\\]|\\.)* \] (?# [...] can contain any char including / )
169
+ (?# only \ and ] have to be escaped here )
170
+ )+
171
+
172
+ /[gimuy]* (?# ending + modifiers )
173
+ }x, ['/'])
174
+
175
+ literal_chars = LITERALS.keys.map {|k| k.slice(0,1) }.uniq
176
+ literal_regex = Regexp.new(LITERALS.keys.sort_by { |x|
177
+ x.length
178
+ }.reverse.map { |x| "#{x.gsub(/([|+*^])/, '\\\\\1')}" }.join('|'))
179
+ token(:LITERALS, literal_regex, literal_chars) do |type, value|
180
+ [LITERALS[value], value]
181
+ end
182
+
183
+ symbols = ('!'..'/').to_a + (':'..'@').to_a + ('['..'^').to_a + ['`'] + ('{'..'~').to_a
184
+ token(:SINGLE_CHAR, /./, symbols) do |type, value|
185
+ [value, value]
186
+ end
187
+ end
188
+
189
+ def tokenize(string)
190
+ raw_tokens(string).map { |x| x.to_racc_token }
191
+ end
192
+
193
+ def raw_tokens(string)
194
+ scanner = StringScanner.new(string)
195
+ tokens = []
196
+ range = CharRange::EMPTY
197
+ accepting_regexp = true
198
+ while !scanner.eos?
199
+ token = match_lexeme(scanner, accepting_regexp)
200
+
201
+ if token.name != :S
202
+ accepting_regexp = followable_by_regex(token)
203
+ end
204
+
205
+ scanner.pos += token.value.send(BYTESIZE_METHOD)
206
+ token.range = range = range.next(token.value)
207
+ tokens << token
208
+ end
209
+ tokens
210
+ end
211
+
212
+ private
213
+
214
+ # Returns the token of the first matching lexeme
215
+ def match_lexeme(scanner, accepting_regexp)
216
+ lexemes = @lexemes[scanner.peek(1)].reverse
217
+ while lexeme = lexemes.pop
218
+ next if lexeme.name == :REGEXP && !accepting_regexp
219
+
220
+ token = lexeme.match(scanner)
221
+ return token if token
222
+ end
223
+
224
+ # When some other character encountered, try to match it as
225
+ # whitespace, as in JavaScript whitespace can contain any
226
+ # Unicode whitespace character.
227
+ if str = scanner.check(WHITESPACE_REGEX)
228
+ return Token.new(:S, str)
229
+ end
230
+ end
231
+
232
+ # Registers a lexeme and maps it to all the characters it can
233
+ # begin with. So later when scanning the source we only need to
234
+ # match those lexemes that can begin with the character we're at.
235
+ def token(name, pattern, chars, &block)
236
+ lexeme = Lexeme.new(name, pattern, &block)
237
+ chars.each do |c|
238
+ @lexemes[c] << lexeme
239
+ end
240
+ end
241
+
242
+ def followable_by_regex(current_token)
243
+ case current_token.name
244
+ when :SINGLE_CHAR
245
+ !SINGLE_CHARS_THAT_IMPLY_DIVISION[current_token.value]
246
+ when :RAW_IDENT
247
+ KEYWORDS_THAT_IMPLY_REGEX[current_token.value]
248
+ when :NUMBER
249
+ false
250
+ else
251
+ true
252
+ end
253
+ end
254
+ end
255
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RKelly
4
+ module Visitable
5
+ # This is a big optimization impairing readability of this code. I'm sorry for
6
+ # that, but this path is called heavily. See git history.
7
+ module ClassMethods
8
+ def visitor_method_names_by_ancestors
9
+ @visitor_method_names_by_ancestors ||= self.ancestors.reject do |i|
10
+ %w[Kernel Object BasicObject Enumerable
11
+ RKelly::Visitors RKelly::Visitable PP::ObjectMixin].include? i.name.to_s
12
+ end.map do |ancestor|
13
+ :"visit_#{ancestor.name.split('::').last}"
14
+ end
15
+ end
16
+ end
17
+ def self.included(klass)
18
+ klass.extend(ClassMethods)
19
+ end
20
+ # End of the big optimization
21
+
22
+ # Based off the visitor pattern from RubyGarden
23
+ def accept(visitor, &block)
24
+ klass, meth = self.class.visitor_method_names_by_ancestors.find do |meth|
25
+ return visitor.send(meth, self, &block) if visitor.respond_to?(meth)
26
+ end
27
+
28
+ raise "No visitor for '#{self.class}'"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,4 @@
1
+ require 'rkelly/visitors/visitor'
2
+ Dir[File.join(File.dirname(__FILE__), "visitors/*_visitor.rb")].each do |file|
3
+ require file[/rkelly\/visitors\/.*/]
4
+ end
@@ -0,0 +1,228 @@
1
+ module RKelly
2
+ module Visitors
3
+ class DotVisitor < Visitor
4
+ class Node < Struct.new(:node_id, :fields)
5
+ ESCAPE = /([<>"\\])/
6
+ def to_s
7
+ counter = 0
8
+ label = fields.map { |f|
9
+ s = "<f#{counter}> #{f.to_s.gsub(ESCAPE, '\\\\\1').gsub(/[\r\n]/,' ')}"
10
+ counter += 1
11
+ s
12
+ }.join('|')
13
+ "\"#{node_id}\" [\nlabel = \"#{label}\"\nshape = \"record\"\n];"
14
+ end
15
+ end
16
+
17
+ class Arrow < Struct.new(:from, :to, :label)
18
+ def to_s
19
+ "\"#{from.node_id}\":f0 -> \"#{to.node_id}\":f0"
20
+ end
21
+ end
22
+
23
+ attr_reader :nodes, :arrows
24
+ def initialize
25
+ @stack = []
26
+ @node_index = 0
27
+ @nodes = []
28
+ @arrows = []
29
+ end
30
+
31
+ ## Terminal nodes
32
+ %w{
33
+ BreakNode ContinueNode EmptyStatementNode FalseNode
34
+ NullNode NumberNode ParameterNode RegexpNode ResolveNode StringNode
35
+ ThisNode TrueNode
36
+ }.each do |type|
37
+ define_method(:"visit_#{type}") do |o|
38
+ node = Node.new(@node_index += 1, [type, o.value].compact)
39
+ add_arrow_for(node)
40
+ @nodes << node
41
+ end
42
+ end
43
+ ## End Terminal nodes
44
+
45
+ # Single value nodes
46
+ %w{
47
+ AssignExprNode BitwiseNotNode BlockNode DeleteNode ElementNode
48
+ ExpressionStatementNode FunctionBodyNode LogicalNotNode ReturnNode
49
+ ThrowNode TypeOfNode UnaryMinusNode UnaryPlusNode VoidNode
50
+ }.each do |type|
51
+ define_method(:"visit_#{type}") do |o|
52
+ node = Node.new(@node_index += 1, [type])
53
+ add_arrow_for(node)
54
+ @nodes << node
55
+ @stack.push(node)
56
+ o.value && o.value.accept(self)
57
+ @stack.pop
58
+ end
59
+ end
60
+ # End Single value nodes
61
+
62
+ # Binary nodes
63
+ %w{
64
+ AddNode BitAndNode BitOrNode BitXOrNode CaseClauseNode CommaNode
65
+ DivideNode DoWhileNode EqualNode GreaterNode GreaterOrEqualNode InNode
66
+ InstanceOfNode LeftShiftNode LessNode LessOrEqualNode LogicalAndNode
67
+ LogicalOrNode ModulusNode MultiplyNode NotEqualNode NotStrictEqualNode
68
+ OpAndEqualNode OpDivideEqualNode OpEqualNode OpLShiftEqualNode
69
+ OpMinusEqualNode OpModEqualNode OpMultiplyEqualNode OpOrEqualNode
70
+ OpPlusEqualNode OpRShiftEqualNode OpURShiftEqualNode OpXOrEqualNode
71
+ RightShiftNode StrictEqualNode SubtractNode SwitchNode
72
+ UnsignedRightShiftNode WhileNode WithNode
73
+ }.each do |type|
74
+ define_method(:"visit_#{type}") do |o|
75
+ node = Node.new(@node_index += 1, [type])
76
+ add_arrow_for(node)
77
+ @nodes << node
78
+ @stack.push(node)
79
+ o.left && o.left.accept(self)
80
+ o.value && o.value.accept(self)
81
+ @stack.pop
82
+ end
83
+ end
84
+ # End Binary nodes
85
+
86
+ # Array Value Nodes
87
+ %w{
88
+ ArgumentsNode ArrayNode CaseBlockNode ConstStatementNode
89
+ ObjectLiteralNode SourceElementsNode VarStatementNode
90
+ }.each do |type|
91
+ define_method(:"visit_#{type}") do |o|
92
+ node = Node.new(@node_index += 1, [type])
93
+ add_arrow_for(node)
94
+ @nodes << node
95
+ @stack.push(node)
96
+ o.value && o.value.each { |v| v && v.accept(self) }
97
+ @stack.pop
98
+ end
99
+ end
100
+ # END Array Value Nodes
101
+
102
+ # Name and Value Nodes
103
+ %w{
104
+ LabelNode PropertyNode GetterPropertyNode SetterPropertyNode VarDeclNode
105
+ }.each do |type|
106
+ define_method(:"visit_#{type}") do |o|
107
+ node = Node.new(@node_index += 1, [type, o.name || 'NULL'])
108
+ add_arrow_for(node)
109
+ @nodes << node
110
+ @stack.push(node)
111
+ o.value && o.value.accept(self)
112
+ @stack.pop
113
+ end
114
+ end
115
+ # END Name and Value Nodes
116
+
117
+ %w{ PostfixNode PrefixNode }.each do |type|
118
+ define_method(:"visit_#{type}") do |o|
119
+ node = Node.new(@node_index += 1, [type, o.value])
120
+ add_arrow_for(node)
121
+ @nodes << node
122
+ @stack.push(node)
123
+ o.operand && o.operand.accept(self)
124
+ @stack.pop
125
+ end
126
+ end
127
+
128
+ def visit_ForNode(o)
129
+ node = Node.new(@node_index += 1, ['ForNode'])
130
+ add_arrow_for(node)
131
+ @nodes << node
132
+ @stack.push(node)
133
+ [:init, :test, :counter, :value].each do |method|
134
+ o.send(method) && o.send(method).accept(self)
135
+ end
136
+ @stack.pop
137
+ end
138
+
139
+ %w{ IfNode ConditionalNode }.each do |type|
140
+ define_method(:"visit_#{type}") do |o|
141
+ node = Node.new(@node_index += 1, [type])
142
+ add_arrow_for(node)
143
+ @nodes << node
144
+ @stack.push(node)
145
+ [:conditions, :value, :else].each do |method|
146
+ o.send(method) && o.send(method).accept(self)
147
+ end
148
+ @stack.pop
149
+ end
150
+ end
151
+
152
+ def visit_ForInNode(o)
153
+ node = Node.new(@node_index += 1, ['ForInNode'])
154
+ add_arrow_for(node)
155
+ @nodes << node
156
+ @stack.push(node)
157
+ [:left, :right, :value].each do |method|
158
+ o.send(method) && o.send(method).accept(self)
159
+ end
160
+ @stack.pop
161
+ end
162
+
163
+ def visit_TryNode(o)
164
+ node = Node.new(@node_index += 1, ['TryNode', o.catch_var || 'NULL'])
165
+ add_arrow_for(node)
166
+ @nodes << node
167
+ @stack.push(node)
168
+ [:value, :catch_block, :finally_block].each do |method|
169
+ o.send(method) && o.send(method).accept(self)
170
+ end
171
+ @stack.pop
172
+ end
173
+
174
+ def visit_BracketAccessorNode(o)
175
+ node = Node.new(@node_index += 1, ['BracketAccessorNode'])
176
+ add_arrow_for(node)
177
+ @nodes << node
178
+ @stack.push(node)
179
+ [:value, :accessor].each do |method|
180
+ o.send(method) && o.send(method).accept(self)
181
+ end
182
+ @stack.pop
183
+ end
184
+
185
+ %w{ NewExprNode FunctionCallNode }.each do |type|
186
+ define_method(:"visit_#{type}") do |o|
187
+ node = Node.new(@node_index += 1, [type])
188
+ add_arrow_for(node)
189
+ @nodes << node
190
+ @stack.push(node)
191
+ [:value, :arguments].each do |method|
192
+ o.send(method) && o.send(method).accept(self)
193
+ end
194
+ @stack.pop
195
+ end
196
+ end
197
+
198
+ %w{ FunctionExprNode FunctionDeclNode }.each do |type|
199
+ define_method(:"visit_#{type}") do |o|
200
+ node = Node.new(@node_index += 1, [type, o.value || 'NULL'])
201
+ add_arrow_for(node)
202
+ @nodes << node
203
+ @stack.push(node)
204
+ o.arguments.each { |a| a && a.accept(self) }
205
+ o.function_body && o.function_body.accept(self)
206
+ @stack.pop
207
+ end
208
+ end
209
+
210
+ def visit_DotAccessorNode(o)
211
+ node = Node.new(@node_index += 1, ['DotAccessorNode', o.accessor])
212
+ add_arrow_for(node)
213
+ @nodes << node
214
+ @stack.push(node)
215
+ [:value].each do |method|
216
+ o.send(method) && o.send(method).accept(self)
217
+ end
218
+ @stack.pop
219
+ end
220
+
221
+ private
222
+ def add_arrow_for(node, label = nil)
223
+ @arrows << Arrow.new(@stack.last, node, label) if @stack.length > 0
224
+ end
225
+
226
+ end
227
+ end
228
+ end