rkelly-remix 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/CHANGELOG.rdoc +9 -0
- data/Manifest.txt +203 -0
- data/README.rdoc +58 -0
- data/Rakefile +39 -0
- data/lib/parser.y +871 -0
- data/lib/rkelly/char_pos.rb +35 -0
- data/lib/rkelly/char_range.rb +31 -0
- data/lib/rkelly/constants.rb +3 -0
- data/lib/rkelly/generated_parser.rb +3217 -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/js.rb +14 -0
- data/lib/rkelly/lexeme.rb +18 -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 +94 -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/nodes.rb +25 -0
- data/lib/rkelly/parser.rb +89 -0
- data/lib/rkelly/runtime/ruby_function.rb +13 -0
- data/lib/rkelly/runtime/scope_chain.rb +57 -0
- data/lib/rkelly/runtime.rb +36 -0
- data/lib/rkelly/syntax_error.rb +4 -0
- data/lib/rkelly/token.rb +24 -0
- data/lib/rkelly/tokenizer.rb +193 -0
- data/lib/rkelly/visitable.rb +16 -0
- data/lib/rkelly/visitors/dot_visitor.rb +228 -0
- data/lib/rkelly/visitors/ecma_visitor.rb +322 -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 +136 -0
- data/lib/rkelly/visitors.rb +4 -0
- data/lib/rkelly.rb +14 -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 +88 -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 +210 -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 +1361 -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 +209 -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 +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,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,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,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
|
data/lib/rkelly/nodes.rb
ADDED
@@ -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,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
|
data/lib/rkelly/token.rb
ADDED
@@ -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
|