rbelly 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +7 -0
- data/Manifest.txt +199 -0
- data/README.rdoc +36 -0
- data/Rakefile +38 -0
- data/lib/parser.y +871 -0
- data/lib/rbelly/constants.rb +3 -0
- data/lib/rbelly/generated_parser.rb +3274 -0
- data/lib/rbelly/js/array.rb +15 -0
- data/lib/rbelly/js/base.rb +91 -0
- data/lib/rbelly/js/boolean.rb +21 -0
- data/lib/rbelly/js/function.rb +39 -0
- data/lib/rbelly/js/function_prototype.rb +15 -0
- data/lib/rbelly/js/global_object.rb +52 -0
- data/lib/rbelly/js/math.rb +10 -0
- data/lib/rbelly/js/nan.rb +18 -0
- data/lib/rbelly/js/number.rb +22 -0
- data/lib/rbelly/js/object.rb +30 -0
- data/lib/rbelly/js/object_prototype.rb +14 -0
- data/lib/rbelly/js/property.rb +20 -0
- data/lib/rbelly/js/scope.rb +6 -0
- data/lib/rbelly/js/string.rb +21 -0
- data/lib/rbelly/js.rb +14 -0
- data/lib/rbelly/lexeme.rb +18 -0
- data/lib/rbelly/nodes/binary_node.rb +18 -0
- data/lib/rbelly/nodes/bracket_accessor_node.rb +11 -0
- data/lib/rbelly/nodes/case_clause_node.rb +11 -0
- data/lib/rbelly/nodes/comma_node.rb +11 -0
- data/lib/rbelly/nodes/conditional_node.rb +11 -0
- data/lib/rbelly/nodes/dot_accessor_node.rb +11 -0
- data/lib/rbelly/nodes/for_in_node.rb +12 -0
- data/lib/rbelly/nodes/for_node.rb +13 -0
- data/lib/rbelly/nodes/function_call_node.rb +16 -0
- data/lib/rbelly/nodes/function_decl_node.rb +6 -0
- data/lib/rbelly/nodes/function_expr_node.rb +12 -0
- data/lib/rbelly/nodes/if_node.rb +12 -0
- data/lib/rbelly/nodes/label_node.rb +11 -0
- data/lib/rbelly/nodes/new_expr_node.rb +11 -0
- data/lib/rbelly/nodes/node.rb +88 -0
- data/lib/rbelly/nodes/not_strict_equal_node.rb +6 -0
- data/lib/rbelly/nodes/op_equal_node.rb +16 -0
- data/lib/rbelly/nodes/postfix_node.rb +11 -0
- data/lib/rbelly/nodes/prefix_node.rb +6 -0
- data/lib/rbelly/nodes/property_node.rb +13 -0
- data/lib/rbelly/nodes/resolve_node.rb +19 -0
- data/lib/rbelly/nodes/strict_equal_node.rb +6 -0
- data/lib/rbelly/nodes/try_node.rb +13 -0
- data/lib/rbelly/nodes/var_decl_node.rb +15 -0
- data/lib/rbelly/nodes.rb +25 -0
- data/lib/rbelly/parser.rb +104 -0
- data/lib/rbelly/runtime/ruby_function.rb +13 -0
- data/lib/rbelly/runtime/scope_chain.rb +57 -0
- data/lib/rbelly/runtime.rb +36 -0
- data/lib/rbelly/syntax_error.rb +4 -0
- data/lib/rbelly/token.rb +19 -0
- data/lib/rbelly/tokenizer.rb +161 -0
- data/lib/rbelly/visitable.rb +16 -0
- data/lib/rbelly/visitors/dot_visitor.rb +228 -0
- data/lib/rbelly/visitors/ecma_visitor.rb +322 -0
- data/lib/rbelly/visitors/enumerable_visitor.rb +18 -0
- data/lib/rbelly/visitors/evaluation_visitor.rb +419 -0
- data/lib/rbelly/visitors/function_visitor.rb +46 -0
- data/lib/rbelly/visitors/pointcut_visitor.rb +31 -0
- data/lib/rbelly/visitors/real_sexp_visitor.rb +16 -0
- data/lib/rbelly/visitors/sexp_visitor.rb +373 -0
- data/lib/rbelly/visitors/visitor.rb +136 -0
- data/lib/rbelly/visitors.rb +4 -0
- data/lib/rbelly.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_rbelly.rb +19 -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_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 +199 -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 +409 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
module RBelly
|
2
|
+
module Nodes
|
3
|
+
class Node
|
4
|
+
include RBelly::Visitable
|
5
|
+
include RBelly::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 = RBelly::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 RBelly
|
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 RBelly
|
2
|
+
module Nodes
|
3
|
+
class ResolveNode < Node
|
4
|
+
def ==(other)
|
5
|
+
return true if super
|
6
|
+
if @value =~ /^[A-Z]/
|
7
|
+
place = [Object, Module, RBelly::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 RBelly
|
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 RBelly
|
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/rbelly/nodes.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rbelly/nodes/node'
|
2
|
+
require 'rbelly/nodes/function_expr_node'
|
3
|
+
require 'rbelly/nodes/binary_node'
|
4
|
+
require 'rbelly/nodes/bracket_accessor_node'
|
5
|
+
require 'rbelly/nodes/case_clause_node'
|
6
|
+
require 'rbelly/nodes/comma_node'
|
7
|
+
require 'rbelly/nodes/conditional_node'
|
8
|
+
require 'rbelly/nodes/dot_accessor_node'
|
9
|
+
require 'rbelly/nodes/for_in_node'
|
10
|
+
require 'rbelly/nodes/for_node'
|
11
|
+
require 'rbelly/nodes/function_call_node'
|
12
|
+
require 'rbelly/nodes/function_decl_node'
|
13
|
+
require 'rbelly/nodes/function_expr_node'
|
14
|
+
require 'rbelly/nodes/if_node'
|
15
|
+
require 'rbelly/nodes/label_node'
|
16
|
+
require 'rbelly/nodes/new_expr_node'
|
17
|
+
require 'rbelly/nodes/not_strict_equal_node'
|
18
|
+
require 'rbelly/nodes/op_equal_node'
|
19
|
+
require 'rbelly/nodes/postfix_node'
|
20
|
+
require 'rbelly/nodes/prefix_node'
|
21
|
+
require 'rbelly/nodes/property_node'
|
22
|
+
require 'rbelly/nodes/resolve_node'
|
23
|
+
require 'rbelly/nodes/strict_equal_node'
|
24
|
+
require 'rbelly/nodes/try_node'
|
25
|
+
require 'rbelly/nodes/var_decl_node'
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'rbelly/tokenizer'
|
2
|
+
require 'rbelly/generated_parser'
|
3
|
+
|
4
|
+
|
5
|
+
module RBelly
|
6
|
+
class Parser < RBelly::GeneratedParser
|
7
|
+
TOKENIZER = Tokenizer.new
|
8
|
+
|
9
|
+
RBelly::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 = RBelly::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 RBelly
|
2
|
+
class Runtime
|
3
|
+
class ScopeChain
|
4
|
+
include RBelly::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 'rbelly/js'
|
2
|
+
require 'rbelly/runtime/scope_chain'
|
3
|
+
|
4
|
+
module RBelly
|
5
|
+
class Runtime
|
6
|
+
UNDEFINED = RBelly::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
|
+
RBelly::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/rbelly/token.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module RBelly
|
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
|
+
|
15
|
+
def to_s
|
16
|
+
return "#{self.name}: #{self.value}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'rbelly/lexeme'
|
2
|
+
|
3
|
+
module RBelly
|
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
|
+
# To distinguish regular expressions from comments, we require that
|
94
|
+
# regular expressions start with a non * character (ie, not look like
|
95
|
+
# /*foo*/). Note that we can't depend on the length of the match to
|
96
|
+
# correctly distinguish, since `/**/i` is longer if matched as a regular
|
97
|
+
# expression than as matched as a comment.
|
98
|
+
# Incidentally, we're also not matching empty regular expressions
|
99
|
+
# (eg, // and //g). Here we could depend on match length and priority to
|
100
|
+
# determine that these are actually comments, but it turns out to be
|
101
|
+
# easier to not match them in the first place.
|
102
|
+
token(:REGEXP, /\A\/(?:[^\/\r\n\\*]|\\[^\r\n])[^\/\r\n\\]*(?:\\[^\r\n][^\/\r\n\\]*)*\/[gim]*/)
|
103
|
+
token(:S, /\A[\s\r\n]*/m)
|
104
|
+
|
105
|
+
token(:SINGLE_CHAR, /\A./) do |type, value|
|
106
|
+
[value, value]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def tokenize(string)
|
111
|
+
raw_tokens(string).map { |x| x.to_racc_token }
|
112
|
+
end
|
113
|
+
|
114
|
+
def raw_tokens(string)
|
115
|
+
tokens = []
|
116
|
+
line_number = 1
|
117
|
+
accepting_regexp = true
|
118
|
+
while string.length > 0
|
119
|
+
longest_token = nil
|
120
|
+
|
121
|
+
@lexemes.each { |lexeme|
|
122
|
+
next if lexeme.name == :REGEXP && !accepting_regexp
|
123
|
+
|
124
|
+
match = lexeme.match(string)
|
125
|
+
next if match.nil?
|
126
|
+
longest_token = match if longest_token.nil?
|
127
|
+
next if longest_token.value.length >= match.value.length
|
128
|
+
longest_token = match
|
129
|
+
}
|
130
|
+
|
131
|
+
if longest_token.name != :S
|
132
|
+
accepting_regexp = followable_by_regex(longest_token)
|
133
|
+
end
|
134
|
+
|
135
|
+
longest_token.line = line_number
|
136
|
+
line_number += longest_token.value.scan(/\n/).length
|
137
|
+
string = string.slice(Range.new(longest_token.value.length, -1))
|
138
|
+
tokens << longest_token
|
139
|
+
end
|
140
|
+
tokens
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
def token(name, pattern = nil, &block)
|
145
|
+
@lexemes << Lexeme.new(name, pattern, &block)
|
146
|
+
end
|
147
|
+
|
148
|
+
def followable_by_regex(current_token)
|
149
|
+
case current_token.name
|
150
|
+
when :RAW_IDENT
|
151
|
+
KEYWORDS_THAT_IMPLY_REGEX.include?(current_token.value)
|
152
|
+
when :NUMBER
|
153
|
+
false
|
154
|
+
when :SINGLE_CHAR
|
155
|
+
!SINGLE_CHARS_THAT_IMPLY_DIVISION.include?(current_token.value)
|
156
|
+
else
|
157
|
+
true
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module RBelly
|
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
|