rkelly-remix 0.0.1
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 +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
|