alex-rkelly 1.0.5
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.
- data/.gemtest +0 -0
- data/CHANGELOG.rdoc +21 -0
- data/Manifest.txt +199 -0
- data/README.rdoc +58 -0
- data/Rakefile +38 -0
- data/lib/parser.y +871 -0
- data/lib/rkelly/constants.rb +3 -0
- data/lib/rkelly/generated_parser.rb +3274 -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 +88 -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 +104 -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 +15 -0
- data/lib/rkelly/tokenizer.rb +152 -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_comma_node.rb +13 -0
- data/test/test_comments.rb +44 -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 +23 -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 +191 -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 +410 -0
@@ -0,0 +1,88 @@
|
|
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, :line, :filename
|
9
|
+
def initialize(value)
|
10
|
+
@value = value
|
11
|
+
@comments = []
|
12
|
+
@filename = @line = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
other.is_a?(self.class) && @value == other.value
|
17
|
+
end
|
18
|
+
alias :=~ :==
|
19
|
+
|
20
|
+
def ===(other)
|
21
|
+
other.is_a?(self.class) && @value === other.value
|
22
|
+
end
|
23
|
+
|
24
|
+
def pointcut(pattern)
|
25
|
+
case pattern
|
26
|
+
when String
|
27
|
+
ast = RKelly::Parser.new.parse(pattern)
|
28
|
+
# Only take the first statement
|
29
|
+
finder = ast.value.first.class.to_s =~ /StatementNode$/ ?
|
30
|
+
ast.value.first.value : ast.value.first
|
31
|
+
visitor = PointcutVisitor.new(finder)
|
32
|
+
else
|
33
|
+
visitor = PointcutVisitor.new(pattern)
|
34
|
+
end
|
35
|
+
|
36
|
+
visitor.accept(self)
|
37
|
+
visitor
|
38
|
+
end
|
39
|
+
alias :/ :pointcut
|
40
|
+
|
41
|
+
def to_sexp
|
42
|
+
SexpVisitor.new.accept(self)
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_ecma
|
46
|
+
ECMAVisitor.new.accept(self)
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_dots
|
50
|
+
visitor = DotVisitor.new
|
51
|
+
visitor.accept(self)
|
52
|
+
header = <<-END
|
53
|
+
digraph g {
|
54
|
+
graph [ rankdir = "TB" ];
|
55
|
+
node [
|
56
|
+
fontsize = "16"
|
57
|
+
shape = "ellipse"
|
58
|
+
];
|
59
|
+
edge [ ];
|
60
|
+
END
|
61
|
+
nodes = visitor.nodes.map { |x| x.to_s }.join("\n")
|
62
|
+
counter = 0
|
63
|
+
arrows = visitor.arrows.map { |x|
|
64
|
+
s = "#{x} [\nid = #{counter}\n];"
|
65
|
+
counter += 1
|
66
|
+
s
|
67
|
+
}.join("\n")
|
68
|
+
"#{header}\n#{nodes}\n#{arrows}\n}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def each(&block)
|
72
|
+
EnumerableVisitor.new(block).accept(self)
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_real_sexp
|
76
|
+
RealSexpVisitor.new.accept(self)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
%w[EmptyStatement Parenthetical ExpressionStatement True Delete Return TypeOf
|
81
|
+
SourceElements Number LogicalNot AssignExpr FunctionBody
|
82
|
+
ObjectLiteral UnaryMinus Throw This BitwiseNot Element String
|
83
|
+
Array CaseBlock Null Break Parameter Block False Void Regexp
|
84
|
+
Arguments Attr Continue ConstStatement UnaryPlus VarStatement].each do |node|
|
85
|
+
eval "class #{node}Node < Node; end"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
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,104 @@
|
|
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
|
+
if token = val.find { |v| v.is_a?(Token) }
|
17
|
+
r.line = token.line if r.respond_to?(:line)
|
18
|
+
r.filename = @filename if r.respond_to?(:filename)
|
19
|
+
end
|
20
|
+
r
|
21
|
+
end
|
22
|
+
eoawesomehack
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_accessor :logger
|
26
|
+
def initialize
|
27
|
+
@tokens = []
|
28
|
+
@logger = nil
|
29
|
+
@terminator = false
|
30
|
+
@prev_token = nil
|
31
|
+
@comments = []
|
32
|
+
end
|
33
|
+
|
34
|
+
# Parse +javascript+ and return an AST
|
35
|
+
def parse(javascript, filename = nil)
|
36
|
+
@tokens = TOKENIZER.raw_tokens(javascript)
|
37
|
+
@position = 0
|
38
|
+
@filename = filename
|
39
|
+
ast = do_parse
|
40
|
+
apply_comments(ast)
|
41
|
+
end
|
42
|
+
|
43
|
+
def yyabort
|
44
|
+
raise "something bad happened, please report a bug with sample JavaScript"
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def apply_comments(ast)
|
49
|
+
ast_hash = Hash.new { |h,k| h[k] = [] }
|
50
|
+
(ast || []).each { |n|
|
51
|
+
next unless n.line
|
52
|
+
ast_hash[n.line] << n
|
53
|
+
}
|
54
|
+
max = ast_hash.keys.sort.last
|
55
|
+
@comments.each do |comment|
|
56
|
+
node = nil
|
57
|
+
comment.line.upto(max) do |line|
|
58
|
+
if ast_hash.key?(line)
|
59
|
+
node = ast_hash[line].first
|
60
|
+
break
|
61
|
+
end
|
62
|
+
end
|
63
|
+
node.comments << comment if node
|
64
|
+
end if max
|
65
|
+
ast
|
66
|
+
end
|
67
|
+
|
68
|
+
def on_error(error_token_id, error_value, value_stack)
|
69
|
+
if logger
|
70
|
+
logger.error(token_to_str(error_token_id))
|
71
|
+
logger.error("error value: #{error_value}")
|
72
|
+
logger.error("error stack: #{value_stack}")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def next_token
|
77
|
+
@terminator = false
|
78
|
+
begin
|
79
|
+
return [false, false] if @position >= @tokens.length
|
80
|
+
n_token = @tokens[@position]
|
81
|
+
@position += 1
|
82
|
+
case @tokens[@position - 1].name
|
83
|
+
when :COMMENT
|
84
|
+
@comments << n_token
|
85
|
+
@terminator = true if n_token.value =~ /^\/\//
|
86
|
+
when :S
|
87
|
+
@terminator = true if n_token.value =~ /[\r\n]/
|
88
|
+
end
|
89
|
+
end while([:COMMENT, :S].include?(n_token.name))
|
90
|
+
|
91
|
+
if @terminator &&
|
92
|
+
((@prev_token && %w[continue break return throw].include?(@prev_token.value)) ||
|
93
|
+
(n_token && %w[++ --].include?(n_token.value)))
|
94
|
+
@position -= 1
|
95
|
+
return (@prev_token = RKelly::Token.new(';', ';')).to_racc_token
|
96
|
+
end
|
97
|
+
|
98
|
+
@prev_token = n_token
|
99
|
+
v = n_token.to_racc_token
|
100
|
+
v[1] = n_token
|
101
|
+
v
|
102
|
+
end
|
103
|
+
end
|
104
|
+
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,15 @@
|
|
1
|
+
module RKelly
|
2
|
+
class Token
|
3
|
+
attr_accessor :name, :value, :transformer, :line
|
4
|
+
def initialize(name, value, &transformer)
|
5
|
+
@name = name
|
6
|
+
@value = value
|
7
|
+
@transformer = transformer
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_racc_token
|
11
|
+
return transformer.call(name, value) if transformer
|
12
|
+
[name, value]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'rkelly/lexeme'
|
2
|
+
|
3
|
+
module RKelly
|
4
|
+
class Tokenizer
|
5
|
+
KEYWORDS = %w{
|
6
|
+
break case catch continue default delete do else finally for function
|
7
|
+
if in instanceof new return switch this throw try typeof var void while
|
8
|
+
with
|
9
|
+
|
10
|
+
const true false null debugger
|
11
|
+
}
|
12
|
+
|
13
|
+
RESERVED = %w{
|
14
|
+
abstract boolean byte char class double enum export extends
|
15
|
+
final float goto implements import int interface long native package
|
16
|
+
private protected public short static super synchronized throws
|
17
|
+
transient volatile
|
18
|
+
}
|
19
|
+
|
20
|
+
LITERALS = {
|
21
|
+
# Punctuators
|
22
|
+
'==' => :EQEQ,
|
23
|
+
'!=' => :NE,
|
24
|
+
'===' => :STREQ,
|
25
|
+
'!==' => :STRNEQ,
|
26
|
+
'<=' => :LE,
|
27
|
+
'>=' => :GE,
|
28
|
+
'||' => :OR,
|
29
|
+
'&&' => :AND,
|
30
|
+
'++' => :PLUSPLUS,
|
31
|
+
'--' => :MINUSMINUS,
|
32
|
+
'<<' => :LSHIFT,
|
33
|
+
'<<=' => :LSHIFTEQUAL,
|
34
|
+
'>>' => :RSHIFT,
|
35
|
+
'>>=' => :RSHIFTEQUAL,
|
36
|
+
'>>>' => :URSHIFT,
|
37
|
+
'>>>='=> :URSHIFTEQUAL,
|
38
|
+
'&=' => :ANDEQUAL,
|
39
|
+
'%=' => :MODEQUAL,
|
40
|
+
'^=' => :XOREQUAL,
|
41
|
+
'|=' => :OREQUAL,
|
42
|
+
'+=' => :PLUSEQUAL,
|
43
|
+
'-=' => :MINUSEQUAL,
|
44
|
+
'*=' => :MULTEQUAL,
|
45
|
+
'/=' => :DIVEQUAL,
|
46
|
+
}
|
47
|
+
|
48
|
+
# Some keywords can be followed by regular expressions (eg, return and throw).
|
49
|
+
# Others can be followed by division.
|
50
|
+
KEYWORDS_THAT_IMPLY_DIVISION = %w{
|
51
|
+
this true false null
|
52
|
+
}
|
53
|
+
|
54
|
+
KEYWORDS_THAT_IMPLY_REGEX = KEYWORDS - KEYWORDS_THAT_IMPLY_DIVISION
|
55
|
+
|
56
|
+
SINGLE_CHARS_THAT_IMPLY_DIVISION = [')', ']', '}']
|
57
|
+
|
58
|
+
def initialize(&block)
|
59
|
+
@lexemes = []
|
60
|
+
|
61
|
+
token(:COMMENT, /\A\/(?:\*(?:.)*?\*\/|\/[^\n]*)/m)
|
62
|
+
token(:STRING, /\A"(?:[^"\\]*(?:\\.[^"\\]*)*)"|\A'(?:[^'\\]*(?:\\.[^'\\]*)*)'/m)
|
63
|
+
|
64
|
+
# A regexp to match floating point literals (but not integer literals).
|
65
|
+
token(:NUMBER, /\A\d+\.\d*(?:[eE][-+]?\d+)?|\A\d+(?:\.\d*)?[eE][-+]?\d+|\A\.\d+(?:[eE][-+]?\d+)?/m) do |type, value|
|
66
|
+
value.gsub!(/\.(\D)/, '.0\1') if value =~ /\.\w/
|
67
|
+
value.gsub!(/\.$/, '.0') if value =~ /\.$/
|
68
|
+
value.gsub!(/^\./, '0.') if value =~ /^\./
|
69
|
+
[type, eval(value)]
|
70
|
+
end
|
71
|
+
token(:NUMBER, /\A0[xX][\da-fA-F]+|\A0[0-7]*|\A\d+/) do |type, value|
|
72
|
+
[type, eval(value)]
|
73
|
+
end
|
74
|
+
|
75
|
+
token(:LITERALS,
|
76
|
+
Regexp.new(LITERALS.keys.sort_by { |x|
|
77
|
+
x.length
|
78
|
+
}.reverse.map { |x| "\\A#{x.gsub(/([|+*^])/, '\\\\\1')}" }.join('|')
|
79
|
+
)) do |type, value|
|
80
|
+
[LITERALS[value], value]
|
81
|
+
end
|
82
|
+
|
83
|
+
token(:RAW_IDENT, /\A([_\$A-Za-z][_\$0-9A-Za-z]*)/) do |type,value|
|
84
|
+
if KEYWORDS.include?(value)
|
85
|
+
[value.upcase.to_sym, value]
|
86
|
+
elsif RESERVED.include?(value)
|
87
|
+
[:RESERVED, value]
|
88
|
+
else
|
89
|
+
[:IDENT, value]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
token(:REGEXP, /\A\/(?:[^\/\r\n\\]*(?:\\[^\r\n][^\/\r\n\\]*)*)\/[gi]*/)
|
94
|
+
token(:S, /\A[\s\r\n]*/m)
|
95
|
+
|
96
|
+
token(:SINGLE_CHAR, /\A./) do |type, value|
|
97
|
+
[value, value]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def tokenize(string)
|
102
|
+
raw_tokens(string).map { |x| x.to_racc_token }
|
103
|
+
end
|
104
|
+
|
105
|
+
def raw_tokens(string)
|
106
|
+
tokens = []
|
107
|
+
line_number = 1
|
108
|
+
accepting_regexp = true
|
109
|
+
while string.length > 0
|
110
|
+
longest_token = nil
|
111
|
+
|
112
|
+
@lexemes.each { |lexeme|
|
113
|
+
next if lexeme.name == :REGEXP && !accepting_regexp
|
114
|
+
|
115
|
+
match = lexeme.match(string)
|
116
|
+
next if match.nil?
|
117
|
+
longest_token = match if longest_token.nil?
|
118
|
+
next if longest_token.value.length >= match.value.length
|
119
|
+
longest_token = match
|
120
|
+
}
|
121
|
+
|
122
|
+
if longest_token.name != :S
|
123
|
+
accepting_regexp = followable_by_regex(longest_token)
|
124
|
+
end
|
125
|
+
|
126
|
+
longest_token.line = line_number
|
127
|
+
line_number += longest_token.value.scan(/\n/).length
|
128
|
+
string = string.slice(Range.new(longest_token.value.length, -1))
|
129
|
+
tokens << longest_token
|
130
|
+
end
|
131
|
+
tokens
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
def token(name, pattern = nil, &block)
|
136
|
+
@lexemes << Lexeme.new(name, pattern, &block)
|
137
|
+
end
|
138
|
+
|
139
|
+
def followable_by_regex(current_token)
|
140
|
+
case current_token.name
|
141
|
+
when :RAW_IDENT
|
142
|
+
KEYWORDS_THAT_IMPLY_REGEX.include?(current_token.value)
|
143
|
+
when :NUMBER
|
144
|
+
false
|
145
|
+
when :SINGLE_CHAR
|
146
|
+
!SINGLE_CHARS_THAT_IMPLY_DIVISION.include?(current_token.value)
|
147
|
+
else
|
148
|
+
true
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
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
|