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.
- checksums.yaml +7 -0
- data/CHANGELOG.rdoc +38 -0
- data/Manifest.txt +203 -0
- data/README.rdoc +135 -0
- data/Rakefile +41 -0
- data/lib/parser.y +883 -0
- data/lib/rkelly.rb +14 -0
- data/lib/rkelly/char_pos.rb +39 -0
- data/lib/rkelly/char_range.rb +33 -0
- data/lib/rkelly/constants.rb +3 -0
- data/lib/rkelly/generated_parser.rb +3380 -0
- data/lib/rkelly/js.rb +14 -0
- data/lib/rkelly/js/array.rb +15 -0
- data/lib/rkelly/js/base.rb +91 -0
- data/lib/rkelly/js/boolean.rb +21 -0
- data/lib/rkelly/js/function.rb +39 -0
- data/lib/rkelly/js/function_prototype.rb +15 -0
- data/lib/rkelly/js/global_object.rb +52 -0
- data/lib/rkelly/js/math.rb +10 -0
- data/lib/rkelly/js/nan.rb +18 -0
- data/lib/rkelly/js/number.rb +22 -0
- data/lib/rkelly/js/object.rb +30 -0
- data/lib/rkelly/js/object_prototype.rb +14 -0
- data/lib/rkelly/js/property.rb +20 -0
- data/lib/rkelly/js/scope.rb +6 -0
- data/lib/rkelly/js/string.rb +21 -0
- data/lib/rkelly/lexeme.rb +18 -0
- data/lib/rkelly/nodes.rb +25 -0
- data/lib/rkelly/nodes/binary_node.rb +18 -0
- data/lib/rkelly/nodes/bracket_accessor_node.rb +11 -0
- data/lib/rkelly/nodes/case_clause_node.rb +11 -0
- data/lib/rkelly/nodes/comma_node.rb +11 -0
- data/lib/rkelly/nodes/conditional_node.rb +11 -0
- data/lib/rkelly/nodes/dot_accessor_node.rb +11 -0
- data/lib/rkelly/nodes/for_in_node.rb +12 -0
- data/lib/rkelly/nodes/for_node.rb +13 -0
- data/lib/rkelly/nodes/function_call_node.rb +16 -0
- data/lib/rkelly/nodes/function_decl_node.rb +6 -0
- data/lib/rkelly/nodes/function_expr_node.rb +12 -0
- data/lib/rkelly/nodes/if_node.rb +12 -0
- data/lib/rkelly/nodes/label_node.rb +11 -0
- data/lib/rkelly/nodes/new_expr_node.rb +11 -0
- data/lib/rkelly/nodes/node.rb +116 -0
- data/lib/rkelly/nodes/not_strict_equal_node.rb +6 -0
- data/lib/rkelly/nodes/op_equal_node.rb +16 -0
- data/lib/rkelly/nodes/postfix_node.rb +11 -0
- data/lib/rkelly/nodes/prefix_node.rb +6 -0
- data/lib/rkelly/nodes/property_node.rb +13 -0
- data/lib/rkelly/nodes/resolve_node.rb +19 -0
- data/lib/rkelly/nodes/strict_equal_node.rb +6 -0
- data/lib/rkelly/nodes/try_node.rb +13 -0
- data/lib/rkelly/nodes/var_decl_node.rb +15 -0
- data/lib/rkelly/parser.rb +106 -0
- data/lib/rkelly/runtime.rb +36 -0
- data/lib/rkelly/runtime/ruby_function.rb +13 -0
- data/lib/rkelly/runtime/scope_chain.rb +57 -0
- data/lib/rkelly/syntax_error.rb +4 -0
- data/lib/rkelly/token.rb +27 -0
- data/lib/rkelly/tokenizer.rb +255 -0
- data/lib/rkelly/visitable.rb +31 -0
- data/lib/rkelly/visitors.rb +4 -0
- data/lib/rkelly/visitors/dot_visitor.rb +228 -0
- data/lib/rkelly/visitors/ecma_visitor.rb +328 -0
- data/lib/rkelly/visitors/enumerable_visitor.rb +18 -0
- data/lib/rkelly/visitors/evaluation_visitor.rb +419 -0
- data/lib/rkelly/visitors/function_visitor.rb +46 -0
- data/lib/rkelly/visitors/pointcut_visitor.rb +31 -0
- data/lib/rkelly/visitors/real_sexp_visitor.rb +16 -0
- data/lib/rkelly/visitors/sexp_visitor.rb +373 -0
- data/lib/rkelly/visitors/visitor.rb +149 -0
- data/test/ecma_script_test_case.rb +21 -0
- data/test/execute_test_case.rb +16 -0
- data/test/execution_contexts/test_10_1_3-1.rb +32 -0
- data/test/expressions/test_11_3_1.rb +64 -0
- data/test/expressions/test_11_3_2.rb +64 -0
- data/test/expressions/test_11_4_2.rb +13 -0
- data/test/expressions/test_11_4_3.rb +52 -0
- data/test/expressions/test_11_4_4.rb +68 -0
- data/test/expressions/test_11_4_5.rb +69 -0
- data/test/expressions/test_11_4_6.rb +94 -0
- data/test/expressions/test_11_4_8.rb +28 -0
- data/test/expressions/test_11_4_9.rb +103 -0
- data/test/expressions/test_11_5_1.rb +51 -0
- data/test/expressions/test_11_5_2.rb +80 -0
- data/test/expressions/test_11_5_3.rb +88 -0
- data/test/expressions/test_11_6_1-1.rb +19 -0
- data/test/expressions/test_11_9_1.rb +19 -0
- data/test/function/test_15_3_1_1-1.rb +34 -0
- data/test/global_object/test_15_1_1_1.rb +29 -0
- data/test/global_object/test_15_1_1_2.rb +17 -0
- data/test/global_object/test_15_1_1_3.rb +9 -0
- data/test/helper.rb +5 -0
- data/test/node_test_case.rb +11 -0
- data/test/object/test_15_2_1_1.rb +257 -0
- data/test/object/test_15_2_1_2.rb +21 -0
- data/test/object/test_15_2_2_1.rb +52 -0
- data/test/statements/test_12_5-1.rb +27 -0
- data/test/test_add_node.rb +8 -0
- data/test/test_arguments_node.rb +8 -0
- data/test/test_array_node.rb +9 -0
- data/test/test_assign_expr_node.rb +8 -0
- data/test/test_automatic_semicolon_insertion.rb +137 -0
- data/test/test_bit_and_node.rb +8 -0
- data/test/test_bit_or_node.rb +8 -0
- data/test/test_bit_x_or_node.rb +8 -0
- data/test/test_bitwise_not_node.rb +8 -0
- data/test/test_block_node.rb +14 -0
- data/test/test_bracket_accessor_node.rb +16 -0
- data/test/test_break_node.rb +11 -0
- data/test/test_case_block_node.rb +11 -0
- data/test/test_case_clause_node.rb +15 -0
- data/test/test_char_pos.rb +39 -0
- data/test/test_char_range.rb +29 -0
- data/test/test_comma_node.rb +13 -0
- data/test/test_comments.rb +45 -0
- data/test/test_conditional_node.rb +17 -0
- data/test/test_const_statement_node.rb +14 -0
- data/test/test_continue_node.rb +11 -0
- data/test/test_delete_node.rb +8 -0
- data/test/test_divide_node.rb +8 -0
- data/test/test_do_while_node.rb +13 -0
- data/test/test_dot_accessor_node.rb +9 -0
- data/test/test_ecma_visitor.rb +213 -0
- data/test/test_element_node.rb +8 -0
- data/test/test_empty_statement_node.rb +8 -0
- data/test/test_equal_node.rb +8 -0
- data/test/test_evaluation_visitor.rb +66 -0
- data/test/test_expression_statement_node.rb +10 -0
- data/test/test_false_node.rb +8 -0
- data/test/test_for_in_node.rb +17 -0
- data/test/test_for_node.rb +24 -0
- data/test/test_function_body_node.rb +8 -0
- data/test/test_function_call_node.rb +10 -0
- data/test/test_function_decl_node.rb +16 -0
- data/test/test_function_expr_node.rb +16 -0
- data/test/test_function_visitor.rb +26 -0
- data/test/test_getter_property_node.rb +10 -0
- data/test/test_global_object.rb +49 -0
- data/test/test_greater_node.rb +8 -0
- data/test/test_greater_or_equal_node.rb +8 -0
- data/test/test_if_node.rb +17 -0
- data/test/test_in_node.rb +8 -0
- data/test/test_instance_of_node.rb +8 -0
- data/test/test_label_node.rb +13 -0
- data/test/test_left_shift_node.rb +8 -0
- data/test/test_less_node.rb +8 -0
- data/test/test_less_or_equal_node.rb +8 -0
- data/test/test_line_number.rb +81 -0
- data/test/test_logical_and_node.rb +8 -0
- data/test/test_logical_not_node.rb +8 -0
- data/test/test_logical_or_node.rb +8 -0
- data/test/test_modulus_node.rb +8 -0
- data/test/test_multiply_node.rb +8 -0
- data/test/test_new_expr_node.rb +9 -0
- data/test/test_not_equal_node.rb +8 -0
- data/test/test_not_strict_equal_node.rb +8 -0
- data/test/test_null_node.rb +8 -0
- data/test/test_number_node.rb +8 -0
- data/test/test_object_literal_node.rb +9 -0
- data/test/test_op_and_equal_node.rb +10 -0
- data/test/test_op_divide_equal_node.rb +10 -0
- data/test/test_op_equal_node.rb +10 -0
- data/test/test_op_l_shift_equal_node.rb +10 -0
- data/test/test_op_minus_equal_node.rb +10 -0
- data/test/test_op_mod_equal_node.rb +10 -0
- data/test/test_op_multiply_equal_node.rb +10 -0
- data/test/test_op_or_equal_node.rb +10 -0
- data/test/test_op_plus_equal_node.rb +10 -0
- data/test/test_op_r_shift_equal_node.rb +10 -0
- data/test/test_op_u_r_shift_equal_node.rb +10 -0
- data/test/test_op_x_or_equal_node.rb +10 -0
- data/test/test_parameter_node.rb +8 -0
- data/test/test_parser.rb +1434 -0
- data/test/test_pointcut_visitor.rb +34 -0
- data/test/test_postfix_node.rb +8 -0
- data/test/test_prefix_node.rb +8 -0
- data/test/test_property_node.rb +8 -0
- data/test/test_regexp_node.rb +8 -0
- data/test/test_resolve_node.rb +22 -0
- data/test/test_return_node.rb +11 -0
- data/test/test_right_shift_node.rb +8 -0
- data/test/test_rkelly.rb +19 -0
- data/test/test_runtime.rb +12 -0
- data/test/test_scope_chain.rb +50 -0
- data/test/test_setter_property_node.rb +10 -0
- data/test/test_source_elements.rb +9 -0
- data/test/test_strict_equal_node.rb +8 -0
- data/test/test_string_node.rb +8 -0
- data/test/test_subtract_node.rb +8 -0
- data/test/test_switch_node.rb +12 -0
- data/test/test_this_node.rb +8 -0
- data/test/test_throw_node.rb +7 -0
- data/test/test_tokenizer.rb +285 -0
- data/test/test_true_node.rb +8 -0
- data/test/test_try_node.rb +59 -0
- data/test/test_type_of_node.rb +8 -0
- data/test/test_unary_minus_node.rb +8 -0
- data/test/test_unary_plus_node.rb +8 -0
- data/test/test_unsigned_right_shift_node.rb +8 -0
- data/test/test_var_decl_node.rb +21 -0
- data/test/test_var_statement_node.rb +14 -0
- data/test/test_void_node.rb +8 -0
- data/test/test_while_node.rb +15 -0
- data/test/test_with_node.rb +8 -0
- 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,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
|
data/lib/rkelly/token.rb
ADDED
@@ -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,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
|