├── lib ├── rkelly │ ├── constants.rb │ ├── syntax_error.rb │ ├── js │ │ ├── scope.rb │ │ ├── math.rb │ │ ├── array.rb │ │ ├── object_prototype.rb │ │ ├── function_prototype.rb │ │ ├── nan.rb │ │ ├── boolean.rb │ │ ├── string.rb │ │ ├── number.rb │ │ ├── property.rb │ │ ├── object.rb │ │ ├── function.rb │ │ ├── global_object.rb │ │ └── base.rb │ ├── nodes │ │ ├── prefix_node.rb │ │ ├── strict_equal_node.rb │ │ ├── function_decl_node.rb │ │ ├── not_strict_equal_node.rb │ │ ├── comma_node.rb │ │ ├── label_node.rb │ │ ├── conditional_node.rb │ │ ├── new_expr_node.rb │ │ ├── postfix_node.rb │ │ ├── case_clause_node.rb │ │ ├── dot_accessor_node.rb │ │ ├── bracket_accessor_node.rb │ │ ├── for_in_node.rb │ │ ├── if_node.rb │ │ ├── for_node.rb │ │ ├── function_expr_node.rb │ │ ├── property_node.rb │ │ ├── function_call_node.rb │ │ ├── var_decl_node.rb │ │ ├── try_node.rb │ │ ├── op_equal_node.rb │ │ ├── resolve_node.rb │ │ ├── binary_node.rb │ │ └── node.rb │ ├── visitors.rb │ ├── runtime │ │ ├── ruby_function.rb │ │ └── scope_chain.rb │ ├── visitors │ │ ├── enumerable_visitor.rb │ │ ├── real_sexp_visitor.rb │ │ ├── pointcut_visitor.rb │ │ ├── function_visitor.rb │ │ ├── visitor.rb │ │ ├── dot_visitor.rb │ │ ├── sexp_visitor.rb │ │ └── ecma_visitor.rb │ ├── lexeme.rb │ ├── js.rb │ ├── visitable.rb │ ├── token.rb │ ├── char_range.rb │ ├── char_pos.rb │ ├── nodes.rb │ ├── runtime.rb │ ├── parser.rb │ └── tokenizer.rb └── rkelly.rb ├── .gitignore ├── test ├── helper.rb ├── test_null_node.rb ├── test_true_node.rb ├── test_false_node.rb ├── test_number_node.rb ├── test_this_node.rb ├── test_throw_node.rb ├── test_arguments_node.rb ├── test_parameter_node.rb ├── test_regexp_node.rb ├── test_string_node.rb ├── test_empty_statement_node.rb ├── test_element_node.rb ├── test_void_node.rb ├── test_assign_expr_node.rb ├── test_delete_node.rb ├── test_logical_not_node.rb ├── test_prefix_node.rb ├── test_type_of_node.rb ├── test_unary_plus_node.rb ├── test_postfix_node.rb ├── test_unary_minus_node.rb ├── test_add_node.rb ├── test_bitwise_not_node.rb ├── test_function_body_node.rb ├── test_less_node.rb ├── test_property_node.rb ├── test_equal_node.rb ├── test_in_node.rb ├── global_object │ ├── test_15_1_1_3.rb │ ├── test_15_1_1_2.rb │ └── test_15_1_1_1.rb ├── test_bit_and_node.rb ├── test_bit_or_node.rb ├── test_divide_node.rb ├── test_bit_x_or_node.rb ├── test_greater_node.rb ├── test_logical_or_node.rb ├── test_modulus_node.rb ├── test_left_shift_node.rb ├── test_logical_and_node.rb ├── test_multiply_node.rb ├── test_not_equal_node.rb ├── test_subtract_node.rb ├── node_test_case.rb ├── test_right_shift_node.rb ├── test_with_node.rb ├── test_instance_of_node.rb ├── test_strict_equal_node.rb ├── test_less_or_equal_node.rb ├── test_source_elements.rb ├── test_array_node.rb ├── test_break_node.rb ├── test_greater_or_equal_node.rb ├── test_not_strict_equal_node.rb ├── test_unsigned_right_shift_node.rb ├── test_continue_node.rb ├── test_dot_accessor_node.rb ├── test_new_expr_node.rb ├── test_return_node.rb ├── test_object_literal_node.rb ├── test_op_equal_node.rb ├── test_runtime.rb ├── test_op_or_equal_node.rb ├── test_op_and_equal_node.rb ├── test_op_mod_equal_node.rb ├── test_op_x_or_equal_node.rb ├── test_function_call_node.rb ├── test_op_plus_equal_node.rb ├── test_op_divide_equal_node.rb ├── test_op_minus_equal_node.rb ├── test_op_l_shift_equal_node.rb ├── test_op_r_shift_equal_node.rb ├── test_op_multiply_equal_node.rb ├── test_op_u_r_shift_equal_node.rb ├── test_expression_statement_node.rb ├── expressions │ ├── test_11_4_2.rb │ ├── test_11_9_1.rb │ ├── test_11_6_1-1.rb │ ├── test_11_4_8.rb │ ├── test_11_4_3.rb │ ├── test_11_5_1.rb │ ├── test_11_4_9.rb │ ├── test_11_4_4.rb │ ├── test_11_4_5.rb │ ├── test_11_3_1.rb │ ├── test_11_3_2.rb │ ├── test_11_5_2.rb │ ├── test_11_5_3.rb │ └── test_11_4_6.rb ├── test_getter_property_node.rb ├── test_setter_property_node.rb ├── test_var_statement_node.rb ├── test_bracket_accessor_node.rb ├── execute_test_case.rb ├── test_const_statement_node.rb ├── test_label_node.rb ├── test_case_block_node.rb ├── test_do_while_node.rb ├── test_rkelly.rb ├── test_block_node.rb ├── test_while_node.rb ├── test_comma_node.rb ├── test_switch_node.rb ├── test_resolve_node.rb ├── test_case_clause_node.rb ├── ecma_script_test_case.rb ├── test_if_node.rb ├── test_function_decl_node.rb ├── test_function_expr_node.rb ├── object │ ├── test_15_2_1_2.rb │ ├── test_15_2_2_1.rb │ └── test_15_2_1_1.rb ├── test_for_in_node.rb ├── test_conditional_node.rb ├── statements │ └── test_12_5-1.rb ├── test_var_decl_node.rb ├── test_char_range.rb ├── test_function_visitor.rb ├── function │ └── test_15_3_1_1-1.rb ├── test_for_node.rb ├── execution_contexts │ └── test_10_1_3-1.rb ├── test_char_pos.rb ├── test_pointcut_visitor.rb ├── test_comments.rb ├── test_global_object.rb ├── test_scope_chain.rb ├── test_evaluation_visitor.rb ├── test_try_node.rb ├── test_line_number.rb ├── test_automatic_semicolon_insertion.rb ├── test_ecma_visitor.rb └── test_tokenizer.rb ├── CHANGELOG.rdoc ├── Rakefile ├── README.rdoc └── Manifest.txt /lib/rkelly/constants.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | VERSION = '0.0.7' 3 | end 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /lib/rkelly/generated_parser.rb 2 | .*.swp 3 | tags 4 | pkg 5 | -------------------------------------------------------------------------------- /lib/rkelly/syntax_error.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | class SyntaxError < ::SyntaxError 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /lib/rkelly/js/scope.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module JS 3 | class Scope < Base 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/prefix_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class PrefixNode < PostfixNode 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/strict_equal_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class StrictEqualNode < BinaryNode 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/function_decl_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class FunctionDeclNode < FunctionExprNode 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/not_strict_equal_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class NotStrictEqualNode < BinaryNode 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rkelly' 3 | require 'test/ecma_script_test_case' 4 | require 'test/execute_test_case' 5 | require 'test/node_test_case' 6 | -------------------------------------------------------------------------------- /lib/rkelly/visitors.rb: -------------------------------------------------------------------------------- 1 | require 'rkelly/visitors/visitor' 2 | Dir[File.join(File.dirname(__FILE__), "visitors/*_visitor.rb")].each do |file| 3 | require file[/rkelly\/visitors\/.*/] 4 | end 5 | -------------------------------------------------------------------------------- /lib/rkelly/js/math.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module JS 3 | class Math < Base 4 | def initialize 5 | super 6 | self['PI'] = ::Math::PI 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/test_null_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class NullNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = NullNode.new('null') 6 | assert_sexp [:nil], node 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_true_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class TrueNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = TrueNode.new('true') 6 | assert_sexp [:true], node 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_false_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class FalseNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = FalseNode.new('false') 6 | assert_sexp [:false], node 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_number_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class NumberNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = NumberNode.new(10) 6 | assert_sexp [:lit, 10], node 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_this_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class ThisNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = ThisNode.new('this') 6 | assert_sexp([:this], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_throw_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class ThrowNodeTest < NodeTestCase 4 | def test_to_sexp 5 | assert_sexp([:throw, [:lit, 10]], ThrowNode.new(NumberNode.new(10))) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/test_arguments_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class ArgumentsNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = ArgumentsNode.new([]) 6 | assert_sexp([:args, []], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_parameter_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class ParameterNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = ParameterNode.new('a') 6 | assert_sexp([:param, 'a'], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_regexp_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class RegexpNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = RegexpNode.new('/yay!/') 6 | assert_sexp [:lit, '/yay!/'], node 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_string_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class StringNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = StringNode.new('"asdf"') 6 | assert_sexp [:str, '"asdf"'], node 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_empty_statement_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class EmptyStatementNodeTest < NodeTestCase 4 | def test_to_to_sexp 5 | node = EmptyStatementNode.new(';') 6 | assert_sexp([:empty], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_element_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class ElementNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = ElementNode.new(NumberNode.new(10)) 6 | assert_sexp([:element, [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_void_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class VoidNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = VoidNode.new(ResolveNode.new('foo')) 6 | assert_sexp([:void, [:resolve, 'foo']], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_assign_expr_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class AssignExprNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = AssignExprNode.new(NumberNode.new(10)) 6 | assert_sexp [:assign, [:lit, 10]], node 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_delete_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class DeleteNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = DeleteNode.new(ResolveNode.new('foo')) 6 | assert_sexp([:delete, [:resolve, 'foo']], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_logical_not_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class LogicalNotNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = LogicalNotNode.new(NumberNode.new(10)) 6 | assert_sexp([:not, [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_prefix_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class PrefixNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = PrefixNode.new(NumberNode.new(10), '++') 6 | assert_sexp([:prefix, [:lit, 10], '++'], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_type_of_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class TypeOfNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = TypeOfNode.new(ResolveNode.new('foo')) 6 | assert_sexp([:typeof, [:resolve, 'foo']], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_unary_plus_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class UnaryPlusNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = UnaryPlusNode.new(NumberNode.new(10)) 6 | assert_sexp([:u_plus, [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/comma_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class CommaNode < Node 4 | attr_reader :left 5 | def initialize(left, right) 6 | super(right) 7 | @left = left 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/label_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class LabelNode < Node 4 | attr_reader :name 5 | def initialize(name, value) 6 | super(value) 7 | @name = name 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/test_postfix_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class PostfixNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = PostfixNode.new(NumberNode.new(10), '++') 6 | assert_sexp([:postfix, [:lit, 10], '++'], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_unary_minus_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class UnaryMinusNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = UnaryMinusNode.new(NumberNode.new(10)) 6 | assert_sexp([:u_minus, [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_add_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class AddNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = AddNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:add, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_bitwise_not_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class BitwiseNotNodeTest < NodeTestCase 4 | def test_failure 5 | node = BitwiseNotNode.new(NumberNode.new(10)) 6 | assert_sexp([:bitwise_not, [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_function_body_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class FunctionBodyNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = FunctionBodyNode.new(SourceElementsNode.new([])) 6 | assert_sexp([:func_body, []], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_less_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class LessNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = LessNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:less, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_property_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class PropertyNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = PropertyNode.new('foo', NumberNode.new(10)) 6 | assert_sexp([:property, :foo, [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/conditional_node.rb: -------------------------------------------------------------------------------- 1 | require 'rkelly/nodes/if_node' 2 | 3 | module RKelly 4 | module Nodes 5 | class ConditionalNode < IfNode 6 | def initialize(test, true_block, else_block) 7 | super 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/test_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class EqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = EqualNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:equal, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_in_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class InNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = InNode.new(NumberNode.new(5), ResolveNode.new('foo')) 6 | assert_sexp([:in, [:lit, 5], [:resolve, 'foo']], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/new_expr_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class NewExprNode < Node 4 | attr_reader :arguments 5 | def initialize(value, args) 6 | super(value) 7 | @arguments = args 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/global_object/test_15_1_1_3.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | # ECMA-262 4 | # Section 15.1.1.3 5 | class GlobalObject_15_1_1_3_Test < ECMAScriptTestCase 6 | def test_undefined 7 | js_assert_equal('void 0', 'undefined') 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/test_bit_and_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class BitAndNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = BitAndNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:bit_and, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_bit_or_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class BitOrNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = BitOrNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:bit_or, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_divide_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class DivideNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = DivideNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:divide, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/postfix_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class PostfixNode < Node 4 | attr_reader :operand 5 | def initialize(operand, operator) 6 | super(operator) 7 | @operand = operand 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/test_bit_x_or_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class BitXOrNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = BitXOrNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:bit_xor, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_greater_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class GreaterNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = GreaterNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:greater, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_logical_or_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class LogicalOrNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = LogicalOrNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:or, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_modulus_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class ModulusNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = ModulusNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:modulus, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/case_clause_node.rb: -------------------------------------------------------------------------------- 1 | require 'rkelly/nodes/binary_node' 2 | 3 | module RKelly 4 | module Nodes 5 | class CaseClauseNode < BinaryNode 6 | def initialize(left, src = SourceElementsNode.new([])) 7 | super 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/test_left_shift_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class LeftShiftNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = LeftShiftNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:lshift, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_logical_and_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class LogicalAndNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = LogicalAndNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:and, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_multiply_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class MultiplyNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = MultiplyNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:multiply, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_not_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class NotEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = NotEqualNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:not_equal, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_subtract_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class SubtractNodeTest < NodeTestCase 4 | def test_subtract 5 | node = SubtractNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:subtract, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/dot_accessor_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class DotAccessorNode < Node 4 | attr_reader :accessor 5 | def initialize(resolve, accessor) 6 | super(resolve) 7 | @accessor = accessor 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/rkelly/runtime/ruby_function.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | class Runtime 3 | class RubyFunction 4 | def initialize(&block) 5 | @code = block 6 | end 7 | 8 | def call(chain, *args) 9 | @code.call(*args) 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/node_test_case.rb: -------------------------------------------------------------------------------- 1 | class NodeTestCase < Test::Unit::TestCase 2 | include RKelly::Nodes 3 | 4 | if method_defined? :default_test 5 | undef :default_test 6 | end 7 | 8 | def assert_sexp(expected, actual) 9 | assert_equal(expected, actual.to_sexp) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/test_right_shift_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class RightShiftNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = RightShiftNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:rshift, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_with_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class WithNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = WithNode.new(ResolveNode.new('foo'), ResolveNode.new('bar')) 6 | assert_sexp([:with, [:resolve, 'foo'], [:resolve, 'bar']], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_instance_of_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class InstanceOfNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = InstanceOfNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:instance_of, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_strict_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class StrictEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = StrictEqualNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:strict_equal, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/bracket_accessor_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class BracketAccessorNode < Node 4 | attr_reader :accessor 5 | def initialize(resolve, accessor) 6 | super(resolve) 7 | @accessor = accessor 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/test_less_or_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class LessOrEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = LessOrEqualNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:less_or_equal, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_source_elements.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class SourceElementListTest < NodeTestCase 4 | def test_to_sexp 5 | num = NumberNode.new(10) 6 | node = SourceElementsNode.new([num, num]) 7 | assert_sexp [[:lit, 10], [:lit, 10]], node 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/rkelly/js/array.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module JS 3 | class Array < Base 4 | class << self 5 | def create(*args) 6 | self.new(*args) 7 | end 8 | end 9 | 10 | def initialize(*args) 11 | super() 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/for_in_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class ForInNode < Node 4 | attr_reader :left, :right 5 | def initialize(left, right, block) 6 | super(block) 7 | @left = left 8 | @right = right 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/test_array_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class ArrayNodeTest < NodeTestCase 4 | def test_to_sexp 5 | element = ElementNode.new(NumberNode.new(10)) 6 | node = ArrayNode.new([element]) 7 | assert_sexp([:array, [[:element, [:lit, 10]]]], node) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/test_break_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class BreakNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = BreakNode.new(nil) 6 | assert_sexp([:break], node) 7 | 8 | node = BreakNode.new('foo') 9 | assert_sexp([:break, 'foo'], node) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/test_greater_or_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class GreaterOrEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = GreaterOrEqualNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:greater_or_equal, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_not_strict_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class NotStrictEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = NotStrictEqualNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:not_strict_equal, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_unsigned_right_shift_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class UnsignedRightShiftNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = UnsignedRightShiftNode.new(NumberNode.new(5), NumberNode.new(10)) 6 | assert_sexp([:urshift, [:lit, 5], [:lit, 10]], node) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/if_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class IfNode < Node 4 | attr_reader :conditions, :else 5 | def initialize(conditions, value, else_stmt = nil) 6 | super(value) 7 | @conditions = conditions 8 | @else = else_stmt 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/test_continue_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class ContinueNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = ContinueNode.new(nil) 6 | assert_sexp([:continue], node) 7 | 8 | node = ContinueNode.new('foo') 9 | assert_sexp([:continue, 'foo'], node) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/test_dot_accessor_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class DotAccessorNodeTest < NodeTestCase 4 | def test_to_sexp 5 | resolve = ResolveNode.new('foo') 6 | node = DotAccessorNode.new(resolve, 'bar') 7 | assert_sexp([:dot_access, [:resolve, 'foo'], 'bar', ], node) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/test_new_expr_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class NewExprNodeTest < NodeTestCase 4 | def test_to_sexp 5 | resolve = ResolveNode.new('foo') 6 | node = NewExprNode.new(resolve, ArgumentsNode.new([])) 7 | assert_sexp([:new_expr, [:resolve, 'foo'], [:args, []]], node) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/test_return_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class ReturnNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = ReturnNode.new(nil) 6 | assert_sexp([:return], node) 7 | 8 | node = ReturnNode.new(NumberNode.new(10)) 9 | assert_sexp([:return, [:lit, 10]], node) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/rkelly.rb: -------------------------------------------------------------------------------- 1 | require 'rkelly/constants' 2 | require 'rkelly/visitable' 3 | require 'rkelly/visitors' 4 | require 'rkelly/parser' 5 | require 'rkelly/runtime' 6 | require 'rkelly/syntax_error' 7 | 8 | module RKelly 9 | class << self 10 | def parse *args 11 | RKelly::Parser.new.parse(*args) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/for_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class ForNode < Node 4 | attr_reader :init, :test, :counter 5 | def initialize(init, test, counter, body) 6 | super(body) 7 | @init = init 8 | @test = test 9 | @counter = counter 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/function_expr_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class FunctionExprNode < Node 4 | attr_reader :function_body, :arguments 5 | def initialize(name, body, args = []) 6 | super(name) 7 | @function_body = body 8 | @arguments = args 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/test_object_literal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class ObjectLiteralNodeTest < NodeTestCase 4 | def test_to_sexp 5 | property = PropertyNode.new('foo', NumberNode.new(10)) 6 | node = ObjectLiteralNode.new([property]) 7 | assert_sexp([:object, [[:property, :foo, [:lit, 10]]]], node) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/test_op_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class OpEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | resolve = ResolveNode.new('foo') 6 | number = NumberNode.new(10) 7 | node = OpEqualNode.new(resolve, number) 8 | assert_sexp([:op_equal, [:resolve, 'foo'], [:lit, 10]], node) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/test_runtime.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class RuntimeTest < Test::Unit::TestCase 4 | def setup 5 | @runtime = RKelly::Runtime.new 6 | end 7 | 8 | def test_call_function 9 | @runtime.execute("function foo(a) { return a + 2; }") 10 | assert_equal(12, @runtime.call_function("foo", 10)) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/property_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class PropertyNode < Node 4 | attr_reader :name 5 | def initialize(name, value) 6 | super(value) 7 | @name = name 8 | end 9 | end 10 | 11 | %w[Getter Setter].each {|node| eval "class #{node}PropertyNode < PropertyNode; end"} 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/test_op_or_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class OpOrEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | resolve = ResolveNode.new('foo') 6 | number = NumberNode.new(10) 7 | node = OpOrEqualNode.new(resolve, number) 8 | assert_sexp([:op_or_equal, [:resolve, 'foo'], [:lit, 10]], node) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/test_op_and_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class OpAndEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | resolve = ResolveNode.new('foo') 6 | number = NumberNode.new(10) 7 | node = OpAndEqualNode.new(resolve, number) 8 | assert_sexp([:op_and_equal, [:resolve, 'foo'], [:lit, 10]], node) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/test_op_mod_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class OpModEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | resolve = ResolveNode.new('foo') 6 | number = NumberNode.new(10) 7 | node = OpModEqualNode.new(resolve, number) 8 | assert_sexp([:op_mod_equal, [:resolve, 'foo'], [:lit, 10]], node) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/test_op_x_or_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class OpXOrEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | resolve = ResolveNode.new('foo') 6 | number = NumberNode.new(10) 7 | node = OpXOrEqualNode.new(resolve, number) 8 | assert_sexp([:op_xor_equal, [:resolve, 'foo'], [:lit, 10]], node) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/test_function_call_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class FunctionCallNodeTest < NodeTestCase 4 | def test_to_sexp 5 | resolve = ResolveNode.new('foo') 6 | args = ArgumentsNode.new([]) 7 | node = FunctionCallNode.new(resolve, args) 8 | assert_sexp([:function_call, [:resolve, 'foo'], [:args, []]], node) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/test_op_plus_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class OpPlusEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | resolve = ResolveNode.new('foo') 6 | number = NumberNode.new(10) 7 | node = OpPlusEqualNode.new(resolve, number) 8 | assert_sexp([:op_plus_equal, [:resolve, 'foo'], [:lit, 10]], node) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/test_op_divide_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class OpDivideEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | resolve = ResolveNode.new('foo') 6 | number = NumberNode.new(10) 7 | node = OpDivideEqualNode.new(resolve, number) 8 | assert_sexp([:op_divide_equal, [:resolve, 'foo'], [:lit, 10]], node) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/test_op_minus_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class OpMinusEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | resolve = ResolveNode.new('foo') 6 | number = NumberNode.new(10) 7 | node = OpMinusEqualNode.new(resolve, number) 8 | assert_sexp([:op_minus_equal, [:resolve, 'foo'], [:lit, 10]], node) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/test_op_l_shift_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class OpLShiftEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | resolve = ResolveNode.new('foo') 6 | number = NumberNode.new(10) 7 | node = OpLShiftEqualNode.new(resolve, number) 8 | assert_sexp([:op_lshift_equal, [:resolve, 'foo'], [:lit, 10]], node) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/test_op_r_shift_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class OpRShiftEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | resolve = ResolveNode.new('foo') 6 | number = NumberNode.new(10) 7 | node = OpRShiftEqualNode.new(resolve, number) 8 | assert_sexp([:op_rshift_equal, [:resolve, 'foo'], [:lit, 10]], node) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/rkelly/js/object_prototype.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module JS 3 | # This is the object protytpe 4 | # ECMA-262 15.2.4 5 | class ObjectPrototype < Base 6 | def initialize 7 | super 8 | self['toString'].function = unbound_method(:toString) do 9 | "[object #{self['Class'].value}]" 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/test_op_multiply_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class OpMultiplyEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | resolve = ResolveNode.new('foo') 6 | number = NumberNode.new(10) 7 | node = OpMultiplyEqualNode.new(resolve, number) 8 | assert_sexp([:op_multiply_equal, [:resolve, 'foo'], [:lit, 10]], node) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/test_op_u_r_shift_equal_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class OpURShiftEqualNodeTest < NodeTestCase 4 | def test_to_sexp 5 | resolve = ResolveNode.new('foo') 6 | number = NumberNode.new(10) 7 | node = OpURShiftEqualNode.new(resolve, number) 8 | assert_sexp([:op_urshift_equal, [:resolve, 'foo'], [:lit, 10]], node) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/rkelly/js/function_prototype.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module JS 3 | # This is the object protytpe 4 | # ECMA-262 15.2.4 5 | class FunctionPrototype < ObjectPrototype 6 | def initialize(function) 7 | super() 8 | self['Class'] = 'Object' 9 | self['constructor'] = function 10 | self['arguments'].value = nil 11 | end 12 | end 13 | end 14 | end 15 | 16 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/function_call_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class FunctionCallNode < Node 4 | attr_reader :arguments 5 | def initialize(value, arguments) 6 | super(value) 7 | @arguments = arguments 8 | end 9 | 10 | def ==(other) 11 | super && @arguments == other.arguments 12 | end 13 | alias :=~ :== 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/var_decl_node.rb: -------------------------------------------------------------------------------- 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 16 | -------------------------------------------------------------------------------- /test/test_expression_statement_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class ExpressionStatementNodeTest < NodeTestCase 4 | def test_to_sexp 5 | resolve = ResolveNode.new('foo') 6 | access = DotAccessorNode.new(resolve, 'bar') 7 | node = ExpressionStatementNode.new(access) 8 | assert_sexp([:expression, [:dot_access, [:resolve, 'foo'], 'bar', ]], node) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/expressions/test_11_4_2.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Expressions_11_4_2_Test < Test::Unit::TestCase 4 | def setup 5 | @runtime = RKelly::Runtime.new 6 | end 7 | 8 | def test_void_1 9 | scope_chain = @runtime.execute("var x = void(10);") 10 | assert scope_chain.has_property?('x') 11 | assert_equal :undefined, scope_chain['x'].value 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/rkelly/js/nan.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module JS 3 | # Class to represent Not A Number 4 | # In Ruby NaN != NaN, but in JS, NaN == NaN 5 | class NaN < ::Numeric 6 | def ==(other) 7 | other.respond_to?(:nan?) && other.nan? 8 | end 9 | 10 | def nan? 11 | true 12 | end 13 | 14 | def +(o); self; end 15 | def -(o); self; end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/try_node.rb: -------------------------------------------------------------------------------- 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 14 | -------------------------------------------------------------------------------- /test/test_getter_property_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class GetterPropertyNodeTest < NodeTestCase 4 | def test_to_sexp 5 | body = FunctionBodyNode.new(SourceElementsNode.new([])) 6 | function = FunctionExprNode.new(nil, body) 7 | node = GetterPropertyNode.new('foo', function) 8 | assert_sexp([:getter, :foo, [:func_expr, nil, [], [:func_body, []]]], node) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/test_setter_property_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class SetterPropertyNodeTest < NodeTestCase 4 | def test_to_sexp 5 | body = FunctionBodyNode.new(SourceElementsNode.new([])) 6 | function = FunctionExprNode.new(nil, body) 7 | node = SetterPropertyNode.new('foo', function) 8 | assert_sexp([:setter, :foo, [:func_expr, nil, [], [:func_body, []]]], node) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/rkelly/visitors/enumerable_visitor.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Visitors 3 | class EnumerableVisitor < Visitor 4 | def initialize(block) 5 | @block = block 6 | end 7 | 8 | ALL_NODES.each do |type| 9 | eval <<-RUBY 10 | def visit_#{type}Node(o) 11 | @block[o] 12 | super 13 | end 14 | RUBY 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/op_equal_node.rb: -------------------------------------------------------------------------------- 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 17 | -------------------------------------------------------------------------------- /test/test_var_statement_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class VarStatementNodeTest < NodeTestCase 4 | def test_to_sexp 5 | initializer = AssignExprNode.new(NumberNode.new(10)) 6 | decl = VarDeclNode.new('foo', initializer) 7 | stmt = VarStatementNode.new([decl]) 8 | 9 | assert_sexp( 10 | [:var, [[:var_decl, :foo, [:assign, [:lit, 10]]]]], 11 | stmt 12 | ) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/test_bracket_accessor_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class BracketAccessorNodeTest < NodeTestCase 4 | def test_sexp 5 | resolve = ResolveNode.new('foo') 6 | index = NumberNode.new(10) 7 | node = BracketAccessorNode.new(resolve, index) 8 | assert_sexp( 9 | [:bracket_access, 10 | [:resolve, 'foo'], 11 | [:lit, 10], 12 | ], 13 | node 14 | ) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/execute_test_case.rb: -------------------------------------------------------------------------------- 1 | class ExecuteTestCase < Test::Unit::TestCase 2 | include RKelly::Nodes 3 | 4 | if method_defined? :default_test 5 | undef :default_test 6 | end 7 | 8 | def assert_execute(expected, code) 9 | scope_chain = @runtime.execute(code) 10 | expected.each do |name, value| 11 | assert scope_chain.has_property?(name) 12 | assert_equal value, scope_chain[name].value 13 | end 14 | end 15 | end 16 | 17 | -------------------------------------------------------------------------------- /test/test_const_statement_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class ConstStatementNodeTest < NodeTestCase 4 | def test_to_sexp 5 | initializer = AssignExprNode.new(NumberNode.new(10)) 6 | decl = VarDeclNode.new('foo', initializer, true) 7 | stmt = ConstStatementNode.new([decl]) 8 | 9 | assert_sexp( 10 | [:const, [[:const_decl, :foo, [:assign, [:lit, 10]]]]], 11 | stmt 12 | ) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/test_label_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class LabelNodeTest < NodeTestCase 4 | def test_to_sexp 5 | initializer = AssignExprNode.new(NumberNode.new(10)) 6 | var = VarDeclNode.new('bar', initializer) 7 | node = LabelNode.new('foo', var) 8 | assert_sexp( 9 | [:label, :foo, 10 | [:var_decl, :bar, [:assign, [:lit, 10]]], 11 | ], node) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/rkelly/lexeme.rb: -------------------------------------------------------------------------------- 1 | require 'rkelly/token' 2 | 3 | module RKelly 4 | class Lexeme 5 | attr_reader :name, :pattern 6 | def initialize(name, pattern, &block) 7 | @name = name 8 | @pattern = pattern 9 | @block = block 10 | end 11 | 12 | def match(scanner) 13 | match = scanner.check(pattern) 14 | return Token.new(name, match.to_s, &@block) if match 15 | match 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/test_case_block_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class CaseBlockNodeTest < NodeTestCase 4 | def test_to_sexp 5 | clause = CaseClauseNode.new( ResolveNode.new('foo'), 6 | SourceElementsNode.new([ResolveNode.new('bar')])) 7 | node = CaseBlockNode.new([clause]) 8 | assert_sexp([:case_block, [[:case, [:resolve, 'foo'], [[:resolve, 'bar']]]]], 9 | node) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/rkelly/js.rb: -------------------------------------------------------------------------------- 1 | require 'rkelly/js/nan' 2 | require 'rkelly/js/property' 3 | require 'rkelly/js/base' 4 | require 'rkelly/js/global_object' 5 | require 'rkelly/js/object' 6 | require 'rkelly/js/object_prototype' 7 | require 'rkelly/js/function_prototype' 8 | require 'rkelly/js/array' 9 | require 'rkelly/js/boolean' 10 | require 'rkelly/js/math' 11 | require 'rkelly/js/number' 12 | require 'rkelly/js/string' 13 | require 'rkelly/js/scope' 14 | require 'rkelly/js/function' 15 | -------------------------------------------------------------------------------- /lib/rkelly/visitors/real_sexp_visitor.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Visitors 3 | class RealSexpVisitor < Visitor 4 | ALL_NODES.each do |type| 5 | eval <<-RUBY 6 | def visit_#{type}Node(o) 7 | sexp = s(:#{type.scan(/[A-Z][a-z]+/).join('_').downcase}, *super(o)) 8 | sexp.line = o.line if o.line 9 | sexp.file = o.filename 10 | sexp 11 | end 12 | RUBY 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/global_object/test_15_1_1_2.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | # ECMA-262 4 | # Section 15.1.1.2 5 | class GlobalObject_15_1_1_2_Test < ECMAScriptTestCase 6 | def test_global_nan 7 | js_assert_equal('Number.POSITIVE_INFINITY', 'Infinity') 8 | end 9 | 10 | def test_this_nan 11 | js_assert_equal('Number.POSITIVE_INFINITY', 'this.Infinity') 12 | end 13 | 14 | def test_typeof_nan 15 | js_assert_equal("'number'", 'typeof Infinity') 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/test_do_while_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class DoWhileNodeTest < NodeTestCase 4 | def test_to_sepx 5 | initializer = AssignExprNode.new(NumberNode.new(10)) 6 | decl = VarDeclNode.new('foo', initializer) 7 | stmt = VarStatementNode.new([decl]) 8 | node = DoWhileNode.new(stmt, TrueNode.new('true')) 9 | 10 | assert_sexp([:do_while, [:var, [[:var_decl, :foo, [:assign, [:lit, 10]]]]], 11 | [:true]], node) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/test_rkelly.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class RKellyTest < Test::Unit::TestCase 4 | def test_array_access 5 | assert_sexp( 6 | [ 7 | [:var, 8 | [[:var_decl, :a, 9 | [:assign, [:bracket_access, [:resolve, "foo"], [:lit, 10]]], 10 | ]] 11 | ] 12 | ], 13 | RKelly.parse('var a = foo[10];')) 14 | end 15 | 16 | def assert_sexp(expected, node) 17 | assert_equal(expected, node.to_sexp) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/rkelly/visitable.rb: -------------------------------------------------------------------------------- 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 17 | -------------------------------------------------------------------------------- /test/test_block_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class BlockNodeTest < NodeTestCase 4 | def test_to_sexp 5 | initializer = AssignExprNode.new(NumberNode.new(10)) 6 | var_foo = VarDeclNode.new('foo', initializer) 7 | 8 | node = BlockNode.new(SourceElementsNode.new([])) 9 | assert_sexp([:block, []], node) 10 | 11 | node = BlockNode.new(SourceElementsNode.new([var_foo])) 12 | assert_sexp([:block, [[:var_decl, :foo, [:assign, [:lit, 10]]]]], node) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/test_while_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class WhileNodeTest < NodeTestCase 4 | def test_to_sexp 5 | initializer = AssignExprNode.new(NumberNode.new(10)) 6 | decl = VarDeclNode.new('foo', initializer) 7 | stmt = VarStatementNode.new([decl]) 8 | node = WhileNode.new(TrueNode.new('true'), stmt) 9 | 10 | assert_sexp([:while, 11 | [:true], 12 | [:var, [[:var_decl, :foo, [:assign, [:lit, 10]]]]], 13 | ], node) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/test_comma_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class CommaNodeTest < NodeTestCase 4 | def test_to_sexp 5 | left = OpEqualNode.new(ResolveNode.new('foo'), NumberNode.new(10)) 6 | right = OpEqualNode.new(ResolveNode.new('bar'), NumberNode.new(11)) 7 | node = CommaNode.new(left, right) 8 | assert_sexp([:comma, 9 | [:op_equal, [:resolve, 'foo'], [:lit, 10]], 10 | [:op_equal, [:resolve, 'bar'], [:lit, 11]]], 11 | node) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/test_switch_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class SwitchNodeTest < NodeTestCase 4 | def test_to_sexp 5 | clause = CaseClauseNode.new( ResolveNode.new('foo'), 6 | SourceElementsNode.new([ResolveNode.new('bar')])) 7 | block = CaseBlockNode.new([clause]) 8 | node = SwitchNode.new(ResolveNode.new('o'), block) 9 | assert_sexp([:switch, [:resolve, 'o'],[:case_block, [[:case, [:resolve, 'foo'], [[:resolve, 'bar']]]]]], 10 | node) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/expressions/test_11_9_1.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Expressions_11_9_1_Test < ExecuteTestCase 4 | def setup 5 | @runtime = RKelly::Runtime.new 6 | end 7 | 8 | def test_void_equal 9 | assert_execute({ 'x' => true }, "var x = void(0) == void(0);") 10 | end 11 | 12 | def test_void_equal_decl 13 | assert_execute({ 'z' => true }, "var x = 10; var y = 10; var z = x == y;") 14 | end 15 | 16 | def test_null_eql 17 | assert_execute({ 'x' => true }, "var x = null == null;") 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/rkelly/js/boolean.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module JS 3 | class Boolean < Base 4 | class << self 5 | def create(*args) 6 | return false if args.length == 0 7 | self.new(args.first) 8 | end 9 | end 10 | def initialize(*args) 11 | super() 12 | value = args.first.nil? ? false : args.first 13 | self['valueOf'] = value 14 | self['valueOf'].function = lambda { 15 | value 16 | } 17 | self['toString'] = args.first.to_s 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/rkelly/js/string.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module JS 3 | class String < Base 4 | class << self 5 | def create(*args) 6 | self.new(args.first || '') 7 | end 8 | end 9 | 10 | def initialize(value) 11 | super() 12 | self['valueOf'] = value 13 | self['valueOf'].function = lambda { value } 14 | self['toString'] = value 15 | self['fromCharCode'] = unbound_method(:fromCharCode) { |*args| 16 | args.map { |x| x.chr }.join 17 | } 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/test_resolve_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class ResolveNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = ResolveNode.new('foo') 6 | assert_sexp [:resolve, 'foo'], node 7 | end 8 | 9 | def test_match 10 | node = ResolveNode.new('foo') 11 | node2 = ResolveNode.new('foo') 12 | assert(node =~ node2) 13 | 14 | assert(node !~ NumberNode.new(10)) 15 | end 16 | 17 | def test_is_a 18 | node = ResolveNode.new('foo') 19 | node3 = ResolveNode.new('String') 20 | assert(node3 =~ node) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/test_case_clause_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class CaseClauseNodeTest < NodeTestCase 4 | def test_to_sexp 5 | node = CaseClauseNode.new(ResolveNode.new('foo')) 6 | assert_sexp([:case, [:resolve, 'foo'], []], node) 7 | 8 | node = CaseClauseNode.new(nil) 9 | assert_sexp([:case, nil, []], node) 10 | 11 | node = CaseClauseNode.new( ResolveNode.new('foo'), 12 | SourceElementsNode.new([ResolveNode.new('bar')])) 13 | assert_sexp([:case, [:resolve, 'foo'], [[:resolve, 'bar']]], node) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/resolve_node.rb: -------------------------------------------------------------------------------- 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 20 | -------------------------------------------------------------------------------- /test/ecma_script_test_case.rb: -------------------------------------------------------------------------------- 1 | class ECMAScriptTestCase < Test::Unit::TestCase 2 | include RKelly::JS 3 | 4 | if method_defined? :default_test 5 | undef :default_test 6 | end 7 | 8 | def setup 9 | @runtime = RKelly::Runtime.new 10 | @runtime.define_function(:assert_equal) do |*args| 11 | assert_equal(*args) 12 | end 13 | @runtime.define_function(:assert_in_delta) do |*args| 14 | assert_in_delta(*args) 15 | end 16 | end 17 | 18 | def js_assert_equal(expected, actual) 19 | @runtime.execute("assert_equal(#{expected}, #{actual});") 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/rkelly/token.rb: -------------------------------------------------------------------------------- 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 25 | -------------------------------------------------------------------------------- /test/test_if_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class IfNodeTest < NodeTestCase 4 | def test_to_sexp 5 | initializer = AssignExprNode.new(NumberNode.new(20)) 6 | decl = VarDeclNode.new('foo', initializer) 7 | stmt = VarStatementNode.new([decl]) 8 | and_node = LogicalAndNode.new(NumberNode.new(5), NumberNode.new(10)) 9 | node = IfNode.new(and_node, stmt) 10 | 11 | assert_sexp([:if, 12 | [:and, [:lit, 5], [:lit, 10]], 13 | [:var, [[:var_decl, :foo, [:assign, [:lit, 20]]]]]], 14 | node) 15 | 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/binary_node.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Nodes 3 | class BinaryNode < Node 4 | attr_reader :left 5 | def initialize(left, right) 6 | super(right) 7 | @left = left 8 | end 9 | end 10 | 11 | %w[Subtract LessOrEqual GreaterOrEqual Add Multiply NotEqual 12 | DoWhile Switch LogicalAnd UnsignedRightShift Modulus While 13 | NotStrictEqual Less With In Greater BitOr StrictEqual LogicalOr 14 | BitXOr LeftShift Equal BitAnd InstanceOf Divide RightShift].each do |node| 15 | const_set "#{node}Node", Class.new(BinaryNode) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/expressions/test_11_6_1-1.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Expressions_11_6_1_1_Test < ECMAScriptTestCase 4 | def test_primitive_boolean 5 | js_assert_equal("1", "true + false") 6 | end 7 | 8 | def test_boolean_object 9 | js_assert_equal("1", "new Boolean(true) + new Boolean(false)") 10 | end 11 | 12 | def test_object_boolean_object 13 | js_assert_equal("1", "new Object(true) + new Object(false)") 14 | end 15 | 16 | def test_object_boolean_object_boolean 17 | js_assert_equal("1", "new Object(new Boolean(true)) + new Object(new Boolean(false))") 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/test_function_decl_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class FunctionDeclNodeTest < NodeTestCase 4 | def test_to_sexp 5 | body = FunctionBodyNode.new(SourceElementsNode.new([])) 6 | node = FunctionDeclNode.new(nil, body) 7 | assert_sexp([:func_decl, nil, [], [:func_body, []]], node) 8 | end 9 | 10 | def test_to_sexp_with_args 11 | body = FunctionBodyNode.new(SourceElementsNode.new([])) 12 | node = FunctionDeclNode.new(nil, body, [ParameterNode.new('a')]) 13 | assert_sexp([:func_decl, nil, [[:param, 'a']], [:func_body, []]], 14 | node) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/test_function_expr_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class FunctionExprNodeTest < NodeTestCase 4 | def test_to_sexp 5 | body = FunctionBodyNode.new(SourceElementsNode.new([])) 6 | node = FunctionExprNode.new(nil, body) 7 | assert_sexp([:func_expr, nil, [], [:func_body, []]], node) 8 | end 9 | 10 | def test_to_sexp_with_args 11 | body = FunctionBodyNode.new(SourceElementsNode.new([])) 12 | node = FunctionExprNode.new(nil, body, [ParameterNode.new('a')]) 13 | assert_sexp([:func_expr, nil, [[:param, 'a']], [:func_body, []]], 14 | node) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/rkelly/js/number.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module JS 3 | class Number < Base 4 | class << self 5 | def create(*args) 6 | self.new(args.first || 0) 7 | end 8 | end 9 | 10 | def initialize(value = 0) 11 | super() 12 | self['MAX_VALUE'] = 1.797693134862315e+308 13 | self['MIN_VALUE'] = 1.0e-306 14 | self['NaN'] = JS::NaN.new 15 | self['POSITIVE_INFINITY'] = 1.0/0.0 16 | self['NEGATIVE_INFINITY'] = -1.0/0.0 17 | self['valueOf'] = lambda { value } 18 | self['toString'] = value.to_s 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/rkelly/js/property.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module JS 3 | class Property 4 | attr_accessor :name, :value, :attributes, :function, :binder 5 | def initialize(name, value, binder = nil, function = nil, attributes = []) 6 | @name = name 7 | @value = value 8 | @binder = binder 9 | @function = function 10 | @attributes = attributes 11 | end 12 | 13 | [:read_only, :dont_enum, :dont_delete, :internal].each do |property| 14 | define_method(:"#{property}?") do 15 | self.attributes.include?(property) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/object/test_15_2_1_2.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Object_15_2_1_2_Test < ECMAScriptTestCase 4 | def test_object_value_of 5 | @runtime.execute(" 6 | var MYOB = Object(); 7 | assert_equal(MYOB, MYOB.valueOf()); 8 | ") 9 | end 10 | 11 | def test_object_type_of 12 | js_assert_equal("'object'", 'typeof Object()') 13 | end 14 | 15 | def test_object_to_string 16 | @runtime.execute(" 17 | var MYOB = Object(); 18 | assert_equal('[object Object]', MYOB.toString()); 19 | ") 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/test_for_in_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class ForInNodeTest < NodeTestCase 4 | def test_to_sexp 5 | initializer = AssignExprNode.new(NumberNode.new(10)) 6 | decl = VarDeclNode.new('foo', initializer) 7 | stmt = VarStatementNode.new([decl]) 8 | block = BlockNode.new(SourceElementsNode.new([stmt])) 9 | 10 | node = ForInNode.new(ResolveNode.new('foo'), ResolveNode.new('bar'), block) 11 | assert_sexp([:for_in, 12 | [:resolve, 'foo'], 13 | [:resolve, 'bar'], 14 | [:block, [[:var, [[:var_decl, :foo, [:assign, [:lit, 10]]]]]]] 15 | ], node) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/test_conditional_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class ConditionalNodeTest < NodeTestCase 4 | def test_to_sexp 5 | initializer = AssignExprNode.new(NumberNode.new(20)) 6 | decl = VarDeclNode.new('foo', initializer) 7 | stmt = VarStatementNode.new([decl]) 8 | and_node = LogicalAndNode.new(NumberNode.new(5), NumberNode.new(10)) 9 | node = ConditionalNode.new(and_node, stmt, stmt) 10 | assert_sexp([:conditional, 11 | [:and, [:lit, 5], [:lit, 10]], 12 | [:var, [[:var_decl, :foo, [:assign, [:lit, 20]]]]], 13 | [:var, [[:var_decl, :foo, [:assign, [:lit, 20]]]]] 14 | ], 15 | node) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/global_object/test_15_1_1_1.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | # ECMA-262 4 | # Section 15.1.1.1 5 | class GlobalObject_15_1_1_1_Test < ECMAScriptTestCase 6 | def setup 7 | super 8 | @object = GlobalObject.new 9 | end 10 | 11 | def test_nan 12 | assert @object.has_property?('NaN') 13 | assert @object['NaN'].dont_enum? 14 | assert @object['NaN'].dont_delete? 15 | assert @object['NaN'].value.nan? 16 | end 17 | 18 | def test_global_nan 19 | js_assert_equal('Number.NaN', 'NaN') 20 | end 21 | 22 | def test_this_nan 23 | js_assert_equal('Number.NaN', 'this.NaN') 24 | end 25 | 26 | def test_typeof_nan 27 | js_assert_equal("'number'", 'typeof NaN') 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/statements/test_12_5-1.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Statement_12_5_1_Test < ExecuteTestCase 4 | def setup 5 | @runtime = RKelly::Runtime.new 6 | end 7 | 8 | def test_if_true 9 | assert_execute({ 'x' => 'pass' }, 10 | "var x; if(true) x = 'pass'; else x = 'fail';") 11 | end 12 | 13 | def test_if_false 14 | assert_execute({ 'x' => 'pass' }, 15 | "var x; if(false) x = 'fail'; else x = 'pass';") 16 | end 17 | 18 | def test_if_zero 19 | assert_execute({ 'x' => 'pass' }, 20 | "var x; if(0) x = 'fail'; else x = 'pass';") 21 | end 22 | 23 | def test_if_one 24 | assert_execute({ 'x' => 'pass' }, 25 | "var x; if(1) x = 'pass'; else x = 'fail';") 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/test_var_decl_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class VarDeclNodeTest < NodeTestCase 4 | def test_to_sexp 5 | initializer = AssignExprNode.new(NumberNode.new(10)) 6 | node = VarDeclNode.new('foo', initializer) 7 | assert_sexp [:var_decl, :foo, [:assign, [:lit, 10]]], node 8 | 9 | node = VarDeclNode.new('foo', nil) 10 | assert_sexp [:var_decl, :foo, nil], node 11 | end 12 | 13 | def test_const_to_sexp 14 | initializer = AssignExprNode.new(NumberNode.new(10)) 15 | node = VarDeclNode.new('foo', initializer, true) 16 | assert_sexp [:const_decl, :foo, [:assign, [:lit, 10]]], node 17 | 18 | node = VarDeclNode.new('foo', nil, true) 19 | assert_sexp [:const_decl, :foo, nil], node 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/expressions/test_11_4_8.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Expressions_11_4_8_Test < ECMAScriptTestCase 4 | def test_positive_not 5 | 0.upto(34) do |number| 6 | js_assert_equal(~to_int_32(2 ** number), "~#{2 ** number}") 7 | end 8 | end 9 | 10 | def test_negative_not 11 | 0.upto(34) do |number| 12 | js_assert_equal(~to_int_32(-(2 ** number)), "~(#{-(2 ** number)})") 13 | end 14 | end 15 | 16 | def to_int_32(value) 17 | return value if value == 0 18 | if value.respond_to?(:nan?) && (value.nan? || value.infinite?) 19 | return 0 20 | end 21 | value = ((value < 0 ? -1 : 1) * value.abs.floor) % (2 ** 32) 22 | if value >= 2 ** 31 23 | value - (2 ** 32) 24 | else 25 | value 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/rkelly/char_range.rb: -------------------------------------------------------------------------------- 1 | require 'rkelly/char_pos' 2 | 3 | module RKelly 4 | # Represents a syntax element location in source code - where it 5 | # begins and where it ends. 6 | # 7 | # It's a value object - it can't be modified. 8 | class CharRange 9 | attr_reader :from, :to 10 | 11 | def initialize(from, to) 12 | @from = from 13 | @to = to 14 | end 15 | 16 | # Creates a new range that immediately follows this one and 17 | # contains the given string. 18 | def next(string) 19 | CharRange.new(@to.next(string.slice(0, 1)), @to.next(string)) 20 | end 21 | 22 | def to_s 23 | "<#{@from}...#{@to}>" 24 | end 25 | 26 | alias_method :inspect, :to_s 27 | 28 | # A re-usable empty range 29 | EMPTY = CharRange.new(CharPos::EMPTY, CharPos::EMPTY) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/rkelly/visitors/pointcut_visitor.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Visitors 3 | class PointcutVisitor < Visitor 4 | attr_reader :matches 5 | def initialize(pattern, matches = []) 6 | @pattern = pattern 7 | @matches = matches 8 | end 9 | 10 | def >(pattern) 11 | pattern = 12 | case pattern 13 | when Class 14 | pattern.new(Object) 15 | else 16 | pattern 17 | end 18 | self.class.new(nil, @matches.map do |m| 19 | m.pointcut(pattern).matches 20 | end.flatten) 21 | end 22 | 23 | ALL_NODES.each do |type| 24 | define_method(:"visit_#{type}Node") do |o| 25 | @matches << o if @pattern === o 26 | super(o) 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/rkelly/js/object.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module JS 3 | class Object < Base 4 | attr_reader :value 5 | class << self 6 | def create(*args) 7 | arg = args.first 8 | return self.new if arg.nil? || arg == :undefined 9 | case arg 10 | when true, false 11 | JS::Boolean.new(arg) 12 | when Numeric 13 | JS::Number.new(arg) 14 | when ::String 15 | JS::String.new(arg) 16 | else 17 | self.new(arg) 18 | end 19 | end 20 | end 21 | 22 | def initialize(*args) 23 | super() 24 | self['prototype'] = JS::ObjectPrototype.new 25 | self['valueOf'] = lambda { args.first || self } 26 | self['valueOf'].function = lambda { args.first || self } 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/test_char_range.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class CharRangeTest < NodeTestCase 4 | def test_advancing_empty_range 5 | a = RKelly::CharRange::EMPTY 6 | b = a.next("foo") 7 | 8 | assert_equal(1, b.from.line) 9 | assert_equal(1, b.from.char) 10 | assert_equal(0, b.from.index) 11 | 12 | assert_equal(1, b.to.line) 13 | assert_equal(3, b.to.char) 14 | assert_equal(2, b.to.index) 15 | end 16 | 17 | def test_advancing_with_multiline_string 18 | a = RKelly::CharRange.new(RKelly::CharPos.new(1,1,0), RKelly::CharPos.new(1,1,0)) 19 | b = a.next("foo\nblah") 20 | 21 | assert_equal(1, b.from.line) 22 | assert_equal(2, b.from.char) 23 | assert_equal(1, b.from.index) 24 | 25 | assert_equal(2, b.to.line) 26 | assert_equal(4, b.to.char) 27 | assert_equal(8, b.to.index) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /CHANGELOG.rdoc: -------------------------------------------------------------------------------- 1 | = RKelly-Remix CHANGELOG 2 | 3 | === 0.0.7 4 | 5 | * Allow reserved words reserved only in strict mode. 6 | 7 | === 0.0.6 8 | 9 | * Properly treat all unicode whitespace characters as whitespace. 10 | 11 | === 0.0.5 12 | 13 | * Update list of reserved words to match ECMAScript 5 14 | * Fix ecma visitor for function expressions with name 15 | 16 | === 0.0.4 17 | 18 | * Allow use of keywords in property names. 19 | 20 | === 0.0.3 21 | 22 | * Add RKelly::Parser#stopped_at method for error reporting. 23 | 24 | === 0.0.2 25 | 26 | * Handle multibyte characters without crashing. 27 | 28 | === 0.0.1 29 | 30 | * A fork of RKelly 31 | * 10x speedup of Tokenizer. 32 | * Full begin/end line/char info on each node. 33 | * List all comment nodes in root node instead of attempting to associate them with syntax nodes and failing at it. 34 | * Recognize /[/]/ as valid JS regex. 35 | -------------------------------------------------------------------------------- /test/test_function_visitor.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class FunctionVisitorTest < Test::Unit::TestCase 4 | def setup 5 | @parser = RKelly::Parser.new 6 | @scope = RKelly::Runtime::ScopeChain.new 7 | @visitor = RKelly::Visitors::FunctionVisitor.new(@scope) 8 | end 9 | 10 | def test_function 11 | tree = @parser.parse('function foo() { var x = 10; }') 12 | @visitor.accept(tree) 13 | assert @visitor.scope_chain['foo'] 14 | 15 | tree = @parser.parse('function foo() { var x = 10; function bar() {}; }') 16 | @visitor.accept(tree) 17 | assert @visitor.scope_chain['foo'] 18 | assert !@visitor.scope_chain.has_property?('bar') 19 | end 20 | 21 | def test_function_call 22 | tree = @parser.parse('var x = foo(); function foo() { return 10; }') 23 | @visitor.accept(tree) 24 | assert @visitor.scope_chain['foo'] 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/rkelly/char_pos.rb: -------------------------------------------------------------------------------- 1 | 2 | module RKelly 3 | # Represents a character position in source code. 4 | # 5 | # It's a value object - it can't be modified. 6 | class CharPos 7 | attr_reader :line, :char, :index 8 | 9 | def initialize(line, char, index) 10 | @line = line 11 | @char = char 12 | @index = index 13 | end 14 | 15 | # Creates a new character position that's a given string away from 16 | # this one. 17 | def next(string) 18 | if string.include?("\n") 19 | lines = string.split(/\n/, -1) 20 | CharPos.new(@line + lines.length - 1, lines.last.length, @index + string.length) 21 | else 22 | CharPos.new(@line, @char + string.length, @index + string.length) 23 | end 24 | end 25 | 26 | def to_s 27 | "{line:#{@line} char:#{@char} (#{@index})}" 28 | end 29 | 30 | alias_method :inspect, :to_s 31 | 32 | # A re-usable empty position 33 | EMPTY = CharPos.new(1,0,-1) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/function/test_15_3_1_1-1.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Functions_15_3_1_1_1_Test < ECMAScriptTestCase 4 | def setup 5 | super 6 | @runtime.execute(< "lib/parser.y" do |t| 19 | if ENV['DEBUG'] 20 | sh "racc -g -v -o #{t.name} #{t.prerequisites.first}" 21 | else 22 | sh "racc -o #{t.name} #{t.prerequisites.first}" 23 | end 24 | end 25 | 26 | task :parser => GENERATED_PARSER 27 | 28 | # make sure the parser's up-to-date when we test 29 | Rake::Task[:test].prerequisites << :parser 30 | Rake::Task[:check_manifest].prerequisites << :parser 31 | 32 | namespace :gem do 33 | task :spec do 34 | File.open("#{HOE.name}.gemspec", 'w') do |f| 35 | HOE.spec.version = "#{HOE.version}.#{Time.now.strftime("%Y%m%d%H%M%S")}" 36 | f.write(HOE.spec.to_ruby) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/rkelly/runtime/scope_chain.rb: -------------------------------------------------------------------------------- 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 58 | -------------------------------------------------------------------------------- /test/test_global_object.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class GlobalObjectTest < Test::Unit::TestCase 4 | include RKelly::JS 5 | 6 | def setup 7 | @object = GlobalObject.new 8 | end 9 | 10 | def test_initialize 11 | assert_equal :undefined, @object['prototype'].value 12 | assert_equal 'GlobalObject', @object['class'].value 13 | end 14 | 15 | def test_braces 16 | @object['foo'] = 'blah' 17 | assert @object.has_property?('foo') 18 | assert @object['foo'] 19 | assert_equal('blah', @object['foo'].value) 20 | end 21 | 22 | def test_undefined_brace 23 | #assert_equal :undefined, @object['foo'].value 24 | end 25 | 26 | def test_delete 27 | assert !@object.has_property?('foo') 28 | @object['foo'] = 'blah' 29 | assert @object.has_property?('foo') 30 | assert @object.delete('foo') 31 | assert !@object.has_property?('foo') 32 | end 33 | 34 | def test_can_put 35 | @object['foo'] = 'blah' 36 | @object['foo'].attributes << :read_only 37 | assert @object['foo'].read_only? 38 | end 39 | 40 | def test_prototype 41 | proto = GlobalObject.new 42 | proto['foo'] = 'bar' 43 | assert proto.has_property?('foo') 44 | 45 | assert !@object.has_property?('foo') 46 | @object['prototype'] = proto 47 | assert @object.has_property?('foo') 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/rkelly/js/function.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module JS 3 | class Function < Base 4 | class << self 5 | def create(*args) 6 | if args.length > 0 7 | parser = RKelly::Parser.new 8 | body = args.pop 9 | tree = parser.parse("function x(#{args.join(',')}) { #{body} }") 10 | func = tree.value.first 11 | self.new(func.function_body, func.arguments) 12 | else 13 | self.new 14 | end 15 | end 16 | end 17 | 18 | attr_reader :body, :arguments 19 | def initialize(body = nil, arguments = []) 20 | super() 21 | @body = body 22 | @arguments = arguments 23 | self['prototype'] = JS::FunctionPrototype.new(self) 24 | self['toString'] = :undefined 25 | self['length'] = arguments.length 26 | end 27 | 28 | def js_call(scope_chain, *params) 29 | arguments.each_with_index { |name, i| 30 | scope_chain[name.value] = params[i] || RKelly::Runtime::UNDEFINED 31 | } 32 | function_visitor = RKelly::Visitors::FunctionVisitor.new(scope_chain) 33 | eval_visitor = RKelly::Visitors::EvaluationVisitor.new(scope_chain) 34 | body.accept(function_visitor) if body 35 | body.accept(eval_visitor) if body 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/expressions/test_11_4_3.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Typeof_11_4_3_Test < ExecuteTestCase 4 | def setup 5 | @runtime = RKelly::Runtime.new 6 | end 7 | 8 | def test_typeof_null 9 | assert_execute({ 'x' => 'object' }, 10 | "var x = typeof(null);") 11 | end 12 | 13 | def test_typeof_void_0 14 | assert_execute({ 'x' => 'undefined' }, 15 | "var x = typeof(void(0));") 16 | end 17 | 18 | def test_typeof_true 19 | assert_execute({ 'x' => 'boolean' }, 20 | "var x = typeof(true);") 21 | end 22 | 23 | def test_typeof_false 24 | assert_execute({ 'x' => 'boolean' }, 25 | "var x = typeof(false);") 26 | end 27 | 28 | def test_typeof_nan 29 | assert_execute({ 'x' => 'number' }, 30 | "var x = typeof(NaN);") 31 | end 32 | 33 | def test_typeof_zero 34 | assert_execute({ 'x' => 'number' }, "var x = typeof(0);") 35 | end 36 | 37 | def test_typeof_one 38 | assert_execute({ 'x' => 'number' }, "var x = typeof(1);") 39 | end 40 | 41 | def test_typeof_minus_one 42 | assert_execute({ 'x' => 'number' }, "var x = typeof(-1);") 43 | end 44 | 45 | def test_typeof_plus_one 46 | assert_execute({ 'x' => 'number' }, "var x = typeof(+1);") 47 | end 48 | 49 | def test_typeof_zero_string 50 | assert_execute({ 'x' => 'string' }, "var x = typeof('0');") 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/test_scope_chain.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class TestScopeChain < Test::Unit::TestCase 4 | def setup 5 | @scope_chain = RKelly::Runtime::ScopeChain.new 6 | scope_1 = RKelly::JS::Scope.new 7 | scope_1.properties[:foo] = 1 8 | scope_2 = RKelly::JS::Scope.new 9 | scope_2.properties[:bar] = 10 10 | @scope_chain << scope_1 11 | @scope_chain << scope_2 12 | end 13 | 14 | def test_global_object_in_chain 15 | assert @scope_chain.has_property?('NaN') 16 | end 17 | 18 | def test_has_property 19 | assert @scope_chain.has_property?(:foo) 20 | assert @scope_chain.has_property?(:bar) 21 | assert !@scope_chain.has_property?(:baz) 22 | @scope_chain.pop 23 | assert @scope_chain.has_property?(:bar).nil? 24 | end 25 | 26 | def test_find_property 27 | assert_equal(10, @scope_chain[:bar]) 28 | assert_equal(1, @scope_chain[:foo]) 29 | @scope_chain.pop 30 | assert(!@scope_chain.has_property?(:bar)) 31 | end 32 | 33 | def test_add_property 34 | assert !@scope_chain.has_property?(:baz) 35 | @scope_chain[:baz] = 10 36 | assert @scope_chain.has_property?(:baz) 37 | @scope_chain.pop 38 | assert !@scope_chain.has_property?(:baz) 39 | end 40 | 41 | def test_chain_in_block 42 | assert !@scope_chain.has_property?(:baz) 43 | @scope_chain.new_scope { |chain| 44 | chain[:baz] = 10 45 | assert @scope_chain.has_property?(:baz) 46 | assert chain.has_property?(:baz) 47 | } 48 | assert @scope_chain.has_property?(:baz).nil? 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/test_evaluation_visitor.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class EvaluationVisitorTest < Test::Unit::TestCase 4 | def setup 5 | @parser = RKelly::Parser.new 6 | @scope = RKelly::Runtime::ScopeChain.new 7 | @visitor = RKelly::Visitors::EvaluationVisitor.new(@scope) 8 | end 9 | 10 | def assert_properties(actual, js_code) 11 | tree = @parser.parse(js_code) 12 | @visitor.accept(tree) 13 | actual.each do |property, value| 14 | assert @visitor.scope_chain.has_property?(property) 15 | assert_equal value, @visitor.scope_chain[property].value 16 | end 17 | end 18 | 19 | def test_variable 20 | assert_properties({ 21 | 'foo' => 10, 22 | }, 'var foo = 10;') 23 | end 24 | 25 | def test_add 26 | assert_properties({ 27 | 'foo' => 6, 28 | }, 'var foo = 1 + 5;') 29 | end 30 | 31 | def test_subtract 32 | assert_properties({ 33 | 'foo' => 3, 34 | }, 'var foo = 4 - 1;') 35 | end 36 | 37 | def test_multiply 38 | assert_properties({ 39 | 'foo' => 8, 40 | }, 'var foo = 4 * 2;') 41 | end 42 | 43 | def test_divide 44 | assert_properties({ 45 | 'foo' => 2, 46 | }, 'var foo = 4 / 2;') 47 | end 48 | 49 | def test_a_bunch 50 | assert_properties({ 51 | 'foo' => 2, 52 | }, 'var foo = 1 + 2 * 2 / 4;') 53 | end 54 | 55 | def test_add_resolve 56 | assert_properties({ 57 | 'foo' => 3, 58 | }, 'foo = 1 + 2;') 59 | end 60 | 61 | def test_plus_equal 62 | assert_properties({ 63 | 'foo' => 3, 64 | }, 'var foo = 1; foo += 2;') 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/rkelly/js/global_object.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module JS 3 | class GlobalObject < Base 4 | def initialize 5 | super 6 | self['class'] = 'GlobalObject' 7 | self['NaN'] = JS::NaN.new 8 | self['NaN'].attributes << :dont_enum 9 | self['NaN'].attributes << :dont_delete 10 | 11 | self['Infinity'] = 1.0/0.0 12 | self['Infinity'].attributes << :dont_enum 13 | self['Infinity'].attributes << :dont_delete 14 | 15 | self['undefined'] = :undefined 16 | self['undefined'].attributes << :dont_enum 17 | self['undefined'].attributes << :dont_delete 18 | 19 | self['Array'] = JS::Array.new 20 | self['Array'].function = lambda { |*args| 21 | JS::Array.create(*args) 22 | } 23 | 24 | self['Object'] = JS::Object.new 25 | self['Object'].function = lambda { |*args| 26 | JS::Object.create(*args) 27 | } 28 | 29 | self['Math'] = JS::Math.new 30 | 31 | self['Function'] = :undefined 32 | self['Function'].function = lambda { |*args| 33 | JS::Function.create(*args) 34 | } 35 | 36 | self['Number'] = JS::Number.new 37 | self['Number'].function = lambda { |*args| 38 | JS::Number.create(*args) 39 | } 40 | 41 | self['Boolean'] = JS::Boolean.new 42 | self['Boolean'].function = lambda { |*args| 43 | JS::Boolean.create(*args) 44 | } 45 | self['String'] = JS::String.new('') 46 | self['String'].function = lambda { |*args| 47 | JS::String.create(*args) 48 | } 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/object/test_15_2_2_1.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Object_15_2_2_1_Test < ECMAScriptTestCase 4 | def test_null_typeof 5 | js_assert_equal("'object'", "typeof new Object(null)") 6 | end 7 | 8 | def test_null_to_string 9 | js_assert_to_string('Object', 'null') 10 | end 11 | 12 | def test_void_0_typeof 13 | js_assert_equal("'object'", "typeof new Object(void 0)") 14 | end 15 | 16 | def test_void_0_to_string 17 | js_assert_to_string('Object', 'void 0') 18 | end 19 | 20 | @@tests = { 21 | 'minus_1' => ['-1', 'Number'], 22 | '1' => ['1', 'Number'], 23 | 'minus_0' => ['-0', 'Number'], 24 | '0' => ['0', 'Number'], 25 | 'nan' => ['Number.NaN', 'Number'], 26 | 'empty_string' => ['""', 'String'], 27 | 'string' => ['"string"', 'String'], 28 | 'true' => ['true', 'Boolean'], 29 | 'false' => ['false', 'Boolean'], 30 | 'boolean' => ['Boolean()', 'Boolean'], 31 | } 32 | 33 | @@tests.each do |name, info| 34 | define_method(:"test_#{name}_typeof") do 35 | js_assert_equal("'object'", "typeof new Object(#{info[0]})") 36 | end 37 | define_method(:"test_#{name}_valueof") do 38 | js_assert_equal(info[0], "(new Object(#{info[0]})).valueOf()") 39 | end 40 | define_method(:"test_#{name}_tostring") do 41 | js_assert_to_string(info[1], info[0]) 42 | end 43 | end 44 | 45 | def js_assert_to_string(expected_type, js_obj) 46 | @runtime.execute(" 47 | MYOB = new Object(#{js_obj}); 48 | MYOB.toString = Object.prototype.toString; 49 | assert_equal('[object #{expected_type}]', MYOB.toString()); 50 | ") 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/rkelly/visitors/function_visitor.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Visitors 3 | class FunctionVisitor < Visitor 4 | attr_reader :scope_chain 5 | def initialize(scope) 6 | super() 7 | @scope_chain = scope 8 | end 9 | 10 | def visit_SourceElementsNode(o) 11 | o.value.each { |x| x.accept(self) } 12 | end 13 | 14 | def visit_FunctionDeclNode(o) 15 | if o.value 16 | scope_chain[o.value].value = RKelly::JS::Function.new(o.function_body, o.arguments) 17 | end 18 | end 19 | 20 | %w{ 21 | AddNode ArgumentsNode ArrayNode AssignExprNode BitAndNode BitOrNode 22 | BitXOrNode BitwiseNotNode BlockNode BracketAccessorNode BreakNode 23 | CaseBlockNode CaseClauseNode CommaNode ConditionalNode 24 | ConstStatementNode ContinueNode DeleteNode DivideNode 25 | DoWhileNode DotAccessorNode ElementNode EmptyStatementNode EqualNode 26 | ExpressionStatementNode FalseNode ForInNode ForNode FunctionBodyNode 27 | FunctionExprNode GetterPropertyNode GreaterNode GreaterOrEqualNode 28 | IfNode InNode InstanceOfNode LabelNode LeftShiftNode LessNode 29 | LessOrEqualNode LogicalAndNode LogicalNotNode LogicalOrNode ModulusNode 30 | MultiplyNode NewExprNode NotEqualNode NotStrictEqualNode NullNode 31 | NumberNode ObjectLiteralNode OpAndEqualNode OpDivideEqualNode 32 | OpEqualNode OpLShiftEqualNode OpMinusEqualNode OpModEqualNode 33 | OpMultiplyEqualNode OpOrEqualNode OpPlusEqualNode OpRShiftEqualNode 34 | OpURShiftEqualNode OpXOrEqualNode ParameterNode PostfixNode PrefixNode 35 | PropertyNode RegexpNode ResolveNode ReturnNode RightShiftNode 36 | SetterPropertyNode StrictEqualNode StringNode 37 | SubtractNode SwitchNode ThisNode ThrowNode TrueNode TryNode TypeOfNode 38 | UnaryMinusNode UnaryPlusNode UnsignedRightShiftNode VarDeclNode 39 | VarStatementNode VoidNode WhileNode WithNode 40 | }.each do |type| 41 | define_method(:"visit_#{type}") do |o| 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /test/test_try_node.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class TryNodeTest < NodeTestCase 4 | def test_failure 5 | var_foo = VarStatementNode.new([ 6 | VarDeclNode.new('foo', AssignExprNode.new(NumberNode.new(10))) 7 | ]) 8 | 9 | var_bar = VarStatementNode.new([ 10 | VarDeclNode.new('bar', AssignExprNode.new(NumberNode.new(20))) 11 | ]) 12 | 13 | var_baz = VarStatementNode.new([ 14 | VarDeclNode.new('baz', AssignExprNode.new(NumberNode.new(69))) 15 | ]) 16 | 17 | try_block = BlockNode.new(SourceElementsNode.new([var_baz])) 18 | catch_block = BlockNode.new(SourceElementsNode.new([var_bar])) 19 | finally_block = BlockNode.new(SourceElementsNode.new([var_foo])) 20 | 21 | node = TryNode.new(try_block, nil, nil, finally_block) 22 | assert_sexp([ :try, 23 | [:block, 24 | [[:var, [[:var_decl, :baz, [:assign, [:lit, 69]]]]]] 25 | ], 26 | nil, 27 | nil, 28 | [:block, 29 | [[:var, [[:var_decl, :foo, [:assign, [:lit, 10]]]]]] 30 | ] 31 | ], node) 32 | 33 | node = TryNode.new(try_block, 'a', catch_block) 34 | assert_sexp([ :try, 35 | [:block, 36 | [[:var, [[:var_decl, :baz, [:assign, [:lit, 69]]]]]] 37 | ], 38 | 'a', 39 | [:block, 40 | [[:var, [[:var_decl, :bar, [:assign, [:lit, 20]]]]]] 41 | ], 42 | nil, 43 | ], node) 44 | 45 | node = TryNode.new(try_block, 'a', catch_block, finally_block) 46 | assert_sexp([ :try, 47 | [:block, 48 | [[:var, [[:var_decl, :baz, [:assign, [:lit, 69]]]]]] 49 | ], 50 | 'a', 51 | [:block, 52 | [[:var, [[:var_decl, :bar, [:assign, [:lit, 20]]]]]] 53 | ], 54 | [:block, 55 | [[:var, [[:var_decl, :foo, [:assign, [:lit, 10]]]]]] 56 | ] 57 | ], node) 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /test/expressions/test_11_5_1.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Expressions_11_5_1_Test < ECMAScriptTestCase 4 | def test_nan_times_nan 5 | js_assert_equal("Number.NaN", "Number.NaN * Number.NaN") 6 | end 7 | 8 | def test_nan_times_1 9 | js_assert_equal("Number.NaN", "Number.NaN * 1") 10 | end 11 | 12 | def test_nan_1_times 13 | js_assert_equal("Number.NaN", "1 * Number.NaN") 14 | end 15 | 16 | def test_positive_inf_times_0 17 | js_assert_equal("Number.NaN", "Number.POSITIVE_INFINITY * 0") 18 | end 19 | 20 | def test_negative_inf_times_0 21 | js_assert_equal("Number.NaN", "Number.NEGATIVE_INFINITY * 0") 22 | end 23 | 24 | def test_0_times_positive_inf 25 | js_assert_equal("Number.NaN", "0 * Number.POSITIVE_INFINITY") 26 | end 27 | 28 | def test_0_times_negative_inf 29 | js_assert_equal("Number.NaN", "0 * Number.NEGATIVE_INFINITY") 30 | end 31 | 32 | def test_0_times_0 33 | js_assert_equal("0", "0 * 0") 34 | end 35 | 36 | def test_infinity_multiplication 37 | js_assert_equal("Number.POSITIVE_INFINITY", "Number.NEGATIVE_INFINITY * Number.NEGATIVE_INFINITY") 38 | js_assert_equal("Number.NEGATIVE_INFINITY", "Number.POSITIVE_INFINITY * Number.NEGATIVE_INFINITY") 39 | js_assert_equal("Number.NEGATIVE_INFINITY", "Number.NEGATIVE_INFINITY * Number.POSITIVE_INFINITY") 40 | js_assert_equal("Number.POSITIVE_INFINITY", "Number.POSITIVE_INFINITY * Number.POSITIVE_INFINITY") 41 | js_assert_equal("Number.NEGATIVE_INFINITY", "Number.NEGATIVE_INFINITY * 1") 42 | js_assert_equal("Number.POSITIVE_INFINITY", "Number.NEGATIVE_INFINITY * -1") 43 | js_assert_equal("Number.NEGATIVE_INFINITY", "1 * Number.NEGATIVE_INFINITY") 44 | js_assert_equal("Number.POSITIVE_INFINITY", "-1 * Number.NEGATIVE_INFINITY") 45 | js_assert_equal("Number.POSITIVE_INFINITY", "Number.POSITIVE_INFINITY * 1") 46 | js_assert_equal("Number.NEGATIVE_INFINITY", "Number.POSITIVE_INFINITY * -1") 47 | js_assert_equal("Number.POSITIVE_INFINITY", "1 * Number.POSITIVE_INFINITY") 48 | js_assert_equal("Number.NEGATIVE_INFINITY", "-1 * Number.POSITIVE_INFINITY") 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /test/test_line_number.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class LineNumberTest < NodeTestCase 4 | def test_line_numbers 5 | parser = RKelly::Parser.new 6 | ast = parser.parse(<<-eojs) 7 | /** 8 | * This is an awesome test comment. 9 | */ 10 | function aaron() { 11 | var x = 10; 12 | return 1 + 1; 13 | } 14 | eojs 15 | func = ast.pointcut(FunctionDeclNode).matches.first 16 | assert func 17 | assert_equal(4, func.line) 18 | 19 | return_node = ast.pointcut(ReturnNode).matches.first 20 | assert return_node 21 | assert_equal(6, return_node.line) 22 | end 23 | 24 | def test_ranges 25 | parser = RKelly::Parser.new 26 | ast = parser.parse(<<-eojs) 27 | /** 28 | * This is an awesome test comment. 29 | */ 30 | function aaron() { 31 | var x = 10; 32 | return 1 + 1; 33 | } 34 | eojs 35 | func = ast.pointcut(FunctionDeclNode).matches.first 36 | assert func 37 | assert_equal("<{line:4 char:7 (68)}...{line:7 char:7 (135)}>", func.range.to_s) 38 | 39 | return_node = ast.pointcut(ReturnNode).matches.first 40 | assert return_node 41 | assert_equal("<{line:6 char:9 (115)}...{line:6 char:21 (127)}>", return_node.range.to_s) 42 | end 43 | 44 | def test_range_of_var_statement_with_semicolon 45 | parser = RKelly::Parser.new 46 | ast = parser.parse(<<-eojs) 47 | var x = { 48 | foo: 10, 49 | bar: "blah" 50 | }; 51 | eojs 52 | stmt = ast.pointcut(VarStatementNode).matches.first 53 | assert_equal("<{line:1 char:7 (6)}...{line:4 char:8 (60)}>", stmt.range.to_s) 54 | end 55 | 56 | def test_range_of_var_statement_without_semicolon 57 | parser = RKelly::Parser.new 58 | ast = parser.parse(<<-eojs) 59 | var x = { 60 | foo: 10, 61 | bar: "blah" 62 | } 63 | eojs 64 | stmt = ast.pointcut(VarStatementNode).matches.first 65 | assert_equal("<{line:1 char:7 (6)}...{line:4 char:7 (59)}>", stmt.range.to_s) 66 | end 67 | 68 | def test_range_of_empty_function_body 69 | parser = RKelly::Parser.new 70 | ast = parser.parse(<<-eojs) 71 | function f () { 72 | } 73 | eojs 74 | 75 | stmt = ast.pointcut(FunctionDeclNode).matches.first 76 | assert_equal("<{line:1 char:7 (6)}...{line:2 char:7 (28)}>", stmt.range.to_s) 77 | 78 | stmt = ast.pointcut(FunctionBodyNode).matches.first 79 | assert_equal("<{line:1 char:21 (20)}...{line:2 char:7 (28)}>", stmt.range.to_s) 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /test/expressions/test_11_4_9.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Expressions_11_4_9_Test < ECMAScriptTestCase 4 | def test_not_null 5 | js_assert_equal("true", "!(null)") 6 | end 7 | 8 | def test_undefined 9 | js_assert_equal("true", "!(void 0)") 10 | end 11 | 12 | def test_false 13 | js_assert_equal("true", "!(false)") 14 | end 15 | 16 | def test_true 17 | js_assert_equal("false", "!(true)") 18 | end 19 | 20 | def test_zero 21 | js_assert_equal("true", "!(0)") 22 | end 23 | 24 | def test_negative_zero 25 | js_assert_equal("true", "!(-0)") 26 | end 27 | 28 | def test_nan 29 | js_assert_equal("true", "!(NaN)") 30 | end 31 | 32 | def test_infinity 33 | js_assert_equal("false", "!(Infinity)") 34 | end 35 | 36 | def test_negative_infinity 37 | js_assert_equal("false", "!(-Infinity)") 38 | end 39 | 40 | def test_math_pi 41 | js_assert_equal("false", "!(Math.PI)") 42 | end 43 | 44 | def test_1 45 | js_assert_equal("false", "!(1)") 46 | end 47 | 48 | def test_negative_1 49 | js_assert_equal("false", "!(-1)") 50 | end 51 | 52 | def test_empty_string 53 | js_assert_equal("true", "!('')") 54 | end 55 | 56 | def test_non_empty_string 57 | js_assert_equal("false", "!('a string')") 58 | end 59 | 60 | def test_empty_string_object 61 | js_assert_equal("false", "!(new String(''))") 62 | end 63 | 64 | def test_non_empty_string_object 65 | js_assert_equal("false", "!(new String('string'))") 66 | end 67 | 68 | def test_string_object 69 | js_assert_equal("false", "!(new String())") 70 | end 71 | 72 | def test_boolean_object 73 | js_assert_equal("false", "!(new Boolean(true))") 74 | end 75 | 76 | def test_false_boolean_object 77 | js_assert_equal("false", "!(new Boolean(false))") 78 | end 79 | 80 | def test_array_object 81 | js_assert_equal("false", "!(new Array())") 82 | end 83 | 84 | def test_stuff_in_array_object 85 | js_assert_equal("false", "!(new Array(1,2,3))") 86 | end 87 | 88 | def test_number_object 89 | js_assert_equal("false", "!(new Number())") 90 | end 91 | 92 | def test_number_object_0 93 | js_assert_equal("false", "!(new Number(0))") 94 | end 95 | 96 | def test_number_object_nan 97 | js_assert_equal("false", "!(new Number(NaN))") 98 | end 99 | 100 | def test_number_object_infinite 101 | js_assert_equal("false", "!(new Number(Infinity))") 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /test/expressions/test_11_4_4.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Expressions_11_4_4_Test < ECMAScriptTestCase 4 | def test_uninitialized 5 | @runtime.execute(" 6 | var MYVAR; 7 | assert_equal(NaN, ++MYVAR); 8 | assert_equal(NaN, MYVAR); 9 | ") 10 | end 11 | 12 | @@tests = [ 13 | :undefined => ['(void 0)', 'NaN'], 14 | :null => ['null', '1'], 15 | :true => ['true', '2'], 16 | :false => ['false', '1'], 17 | :positive_infinity => ['Number.POSITIVE_INFINITY', 18 | 'Number.POSITIVE_INFINITY'], 19 | :negative_infinity => ['Number.NEGATIVE_INFINITY', 20 | 'Number.NEGATIVE_INFINITY'], 21 | :nan => ['Number.NaN', 'Number.NaN'], 22 | :zero => ['0', '1'], 23 | #:pos_float => ['0.2345', '1.2345'], 24 | #:neg_float => ['-0.2345', '0.7655'], 25 | :boolean_false => ['new Boolean(false)', '1'], 26 | :boolean_true => ['new Boolean(true)', '2'], 27 | :string_string => ["'string'", 'Number.NaN'], 28 | :number_s => ["'12345'", '12346'], 29 | :negative_s => ["'-12345'", '-12344'], 30 | :hex_s => ["'0Xf'", '16'], 31 | :num_0_s => ["'077'", '78'], 32 | :empty_s => ["''", '1'], 33 | :obj_string_string => ['new String("string")', 'Number.NaN'], 34 | :obj_string_num => ['new String("12345")', '12346'], 35 | :obj_negative => ['new String("-12345")', '-12344'], 36 | :obj_hex => ['new String("0Xf")', '16'], 37 | :obj_0_s => ['new String("077")', '78'], 38 | :obj_empty => ['new String("")', '1'], 39 | ] 40 | 41 | def test_positive_float 42 | @runtime.execute(" 43 | var MYVAR=0.2345; 44 | assert_in_delta(1.2345, ++MYVAR, 0.00001); 45 | assert_in_delta(1.2345, MYVAR, 0.00001); 46 | ") 47 | end 48 | 49 | def test_negative_float 50 | @runtime.execute(" 51 | var MYVAR=-0.2345; 52 | assert_in_delta(0.7655, ++MYVAR, 0.00001); 53 | assert_in_delta(0.7655, MYVAR, 0.00001); 54 | ") 55 | end 56 | 57 | @@tests.each do |testing| 58 | testing.each do |name, values| 59 | define_method(:"test_#{name}") do 60 | @runtime.execute(" 61 | var MYVAR=#{values[0]}; 62 | assert_equal(#{values[1]}, ++MYVAR); 63 | assert_equal(#{values[1]}, MYVAR); 64 | ") 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /test/expressions/test_11_4_5.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Expressions_11_4_5_Test < ECMAScriptTestCase 4 | def test_uninitialized 5 | @runtime.execute(" 6 | var MYVAR; 7 | assert_equal(NaN, --MYVAR); 8 | assert_equal(NaN, MYVAR); 9 | ") 10 | end 11 | 12 | @@tests = [ 13 | :undefined => ['(void 0)', 'NaN'], 14 | :null => ['null', '-1'], 15 | :true => ['true', '0'], 16 | :false => ['false', '-1'], 17 | :positive_infinity => ['Number.POSITIVE_INFINITY', 18 | 'Number.POSITIVE_INFINITY'], 19 | :negative_infinity => ['Number.NEGATIVE_INFINITY', 20 | 'Number.NEGATIVE_INFINITY'], 21 | :nan => ['Number.NaN', 'Number.NaN'], 22 | :zero => ['0', '-1'], 23 | #:pos_float => ['0.2345', '1.2345'], 24 | #:neg_float => ['-0.2345', '0.7655'], 25 | :boolean_false => ['new Boolean(false)', '-1'], 26 | :boolean_true => ['new Boolean(true)', '0'], 27 | :string_string => ["'string'", 'Number.NaN'], 28 | :number_s => ["'12345'", '12344'], 29 | :negative_s => ["'-12345'", '-12346'], 30 | :hex_s => ["'0Xf'", '14'], 31 | :num_0_s => ["'077'", '76'], 32 | :empty_s => ["''", '-1'], 33 | :obj_string_string => ['new String("string")', 'Number.NaN'], 34 | :obj_string_num => ['new String("12345")', '12344'], 35 | :obj_negative => ['new String("-12345")', '-12346'], 36 | :obj_hex => ['new String("0Xf")', '14'], 37 | :obj_0_s => ['new String("077")', '76'], 38 | :obj_empty => ['new String("")', '-1'], 39 | ] 40 | 41 | def test_positive_float 42 | @runtime.execute(" 43 | var MYVAR=0.2345; 44 | assert_in_delta(-0.7655, --MYVAR, 0.00001); 45 | assert_in_delta(-0.7655, MYVAR, 0.00001); 46 | ") 47 | end 48 | 49 | def test_negative_float 50 | @runtime.execute(" 51 | var MYVAR=-0.2345; 52 | assert_in_delta(-1.2345, --MYVAR, 0.00001); 53 | assert_in_delta(-1.2345, MYVAR, 0.00001); 54 | ") 55 | end 56 | 57 | @@tests.each do |testing| 58 | testing.each do |name, values| 59 | define_method(:"test_#{name}") do 60 | @runtime.execute(" 61 | var MYVAR=#{values[0]}; 62 | assert_equal(#{values[1]}, --MYVAR); 63 | assert_equal(#{values[1]}, MYVAR); 64 | ") 65 | end 66 | end 67 | end 68 | end 69 | 70 | -------------------------------------------------------------------------------- /test/expressions/test_11_3_1.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Expressions_11_3_1_Test < ECMAScriptTestCase 4 | def test_uninitialized 5 | @runtime.execute("var MYVAR; assert_equal(NaN, MYVAR++); assert_equal(NaN, MYVAR);") 6 | end 7 | 8 | @@tests = [ 9 | :undefined => [ '(void 0)', 'NaN', 'NaN'], 10 | :null => [ 'null', '0', '1'], 11 | :true => [ 'true', '1', '2'], 12 | :false => [ 'false', '0', '1'], 13 | :positive_infinity => [ 'Number.POSITIVE_INFINITY', 14 | 'Number.POSITIVE_INFINITY', 15 | 'Number.POSITIVE_INFINITY'], 16 | :negative_infinity => [ 'Number.NEGATIVE_INFINITY', 17 | 'Number.NEGATIVE_INFINITY', 18 | 'Number.NEGATIVE_INFINITY'], 19 | :nan => [ 'Number.NaN', 'Number.NaN', 'Number.NaN'], 20 | :zero => [ '0', '0', '1'], 21 | :boolean_false => ['new Boolean(false)', '0', '1'], 22 | :boolean_true => ['new Boolean(true)', '1', '2'], 23 | :string_string => ["'string'", 'Number.NaN', 'Number.NaN'], 24 | :number_s => ["'12345'", '12345', '12346'], 25 | :negative_s => ["'-12345'", '-12345', '-12344'], 26 | :hex_s => ["'0Xf'", '15', '16'], 27 | :num_0_s => ["'077'", '77', '78'], 28 | :empty_s => ["''", '0', '1'], 29 | :obj_string_string => ['new String("string")', 'Number.NaN', 'Number.NaN'], 30 | :obj_string_num => ['new String("12345")', '12345', '12346'], 31 | :obj_negative => ['new String("-12345")', '-12345', '-12344'], 32 | :obj_hex => ['new String("0Xf")', '15', '16'], 33 | :obj_0_s => ['new String("077")', '77', '78'], 34 | :obj_empty => ['new String("")', '0', '1'], 35 | ] 36 | 37 | def test_positive_float 38 | @runtime.execute(" 39 | var MYVAR=0.2345; 40 | assert_equal(0.2345, MYVAR++); 41 | assert_in_delta(1.2345, MYVAR, 0.00001); 42 | ") 43 | end 44 | 45 | def test_negative_float 46 | @runtime.execute(" 47 | var MYVAR=-0.2345; 48 | assert_equal(-0.2345, MYVAR++); 49 | assert_in_delta(0.7655, MYVAR, 0.00001); 50 | ") 51 | end 52 | 53 | @@tests.each do |testing| 54 | testing.each do |name, values| 55 | define_method(:"test_#{name}") do 56 | @runtime.execute(" 57 | var MYVAR=#{values[0]}; 58 | assert_equal(#{values[1]}, MYVAR++); 59 | assert_equal(#{values[2]}, MYVAR); 60 | ") 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/expressions/test_11_3_2.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Expressions_11_3_2_Test < ECMAScriptTestCase 4 | def test_uninitialized 5 | @runtime.execute("var MYVAR; assert_equal(NaN, MYVAR--); assert_equal(NaN, MYVAR);") 6 | end 7 | 8 | @@tests = [ 9 | :undefined => [ '(void 0)', 'NaN', 'NaN'], 10 | :null => [ 'null', '0', '-1'], 11 | :true => [ 'true', '1', '0'], 12 | :false => [ 'false', '0', '-1'], 13 | :positive_infinity => [ 'Number.POSITIVE_INFINITY', 14 | 'Number.POSITIVE_INFINITY', 15 | 'Number.POSITIVE_INFINITY'], 16 | :negative_infinity => [ 'Number.NEGATIVE_INFINITY', 17 | 'Number.NEGATIVE_INFINITY', 18 | 'Number.NEGATIVE_INFINITY'], 19 | :nan => [ 'Number.NaN', 'Number.NaN', 'Number.NaN'], 20 | :zero => [ '0', '0', '-1'], 21 | :boolean_false => ['new Boolean(false)', '0', '-1'], 22 | :boolean_true => ['new Boolean(true)', '1', '0'], 23 | :string_string => ["'string'", 'Number.NaN', 'Number.NaN'], 24 | :number_s => ["'12345'", '12345', '12344'], 25 | :negative_s => ["'-12345'", '-12345', '-12346'], 26 | :hex_s => ["'0Xf'", '15', '14'], 27 | :num_0_s => ["'077'", '77', '76'], 28 | :empty_s => ["''", '0', '-1'], 29 | :obj_string_string => ['new String("string")', 'Number.NaN', 'Number.NaN'], 30 | :obj_string_num => ['new String("12345")', '12345', '12344'], 31 | :obj_negative => ['new String("-12345")', '-12345', '-12346'], 32 | :obj_hex => ['new String("0Xf")', '15', '14'], 33 | :obj_0_s => ['new String("077")', '77', '76'], 34 | :obj_empty => ['new String("")', '0', '-1'], 35 | ] 36 | 37 | def test_positive_float 38 | @runtime.execute(" 39 | var MYVAR=0.2345; 40 | assert_equal(0.2345, MYVAR--); 41 | assert_in_delta(-0.7655, MYVAR, 0.00001); 42 | ") 43 | end 44 | 45 | def test_negative_float 46 | @runtime.execute(" 47 | var MYVAR=-0.2345; 48 | assert_equal(-0.2345, MYVAR--); 49 | assert_in_delta(-1.2345, MYVAR, 0.00001); 50 | ") 51 | end 52 | 53 | @@tests.each do |testing| 54 | testing.each do |name, values| 55 | define_method(:"test_#{name}") do 56 | @runtime.execute(" 57 | var MYVAR=#{values[0]}; 58 | assert_equal(#{values[1]}, MYVAR--); 59 | assert_equal(#{values[2]}, MYVAR); 60 | ") 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/rkelly/js/base.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module JS 3 | class Base 4 | attr_reader :properties, :return, :value 5 | def initialize 6 | @properties = Hash.new { |h,k| 7 | h[k] = Property.new(k, :undefined, self) 8 | } 9 | @return = nil 10 | @returned = false 11 | @value = self 12 | self['Class'] = self.class.to_s.split('::').last 13 | end 14 | 15 | def [](name) 16 | return self.properties[name] if has_property?(name) 17 | if self.properties['prototype'].value != :undefined 18 | self.properties['prototype'].value[name] 19 | else 20 | RKelly::Runtime::UNDEFINED 21 | end 22 | end 23 | 24 | def []=(name, value) 25 | return unless can_put?(name) 26 | if has_property?(name) 27 | self.properties[name].value = value 28 | else 29 | self.properties[name] = Property.new(name, value, self) 30 | end 31 | end 32 | 33 | def can_put?(name) 34 | if !has_property?(name) 35 | return true if self.properties['prototype'].nil? 36 | return true if self.properties['prototype'].value.nil? 37 | return true if self.properties['prototype'].value == :undefined 38 | return self.properties['prototype'].value.can_put?(name) 39 | end 40 | !self.properties[name].read_only? 41 | end 42 | 43 | def has_property?(name) 44 | return true if self.properties.has_key?(name) 45 | return false if self.properties['prototype'].nil? 46 | return false if self.properties['prototype'].value.nil? 47 | return false if self.properties['prototype'].value == :undefined 48 | self.properties['prototype'].value.has_property?(name) 49 | end 50 | 51 | def delete(name) 52 | return true unless has_property?(name) 53 | return false if self.properties[name].dont_delete? 54 | self.properties.delete(name) 55 | true 56 | end 57 | 58 | def default_value(hint) 59 | case hint 60 | when 'Number' 61 | value_of = self['valueOf'] 62 | if value_of.function || value_of.value.is_a?(RKelly::JS::Function) 63 | return value_of 64 | end 65 | to_string = self['toString'] 66 | if to_string.function || to_string.value.is_a?(RKelly::JS::Function) 67 | return to_string 68 | end 69 | end 70 | end 71 | 72 | def return=(value) 73 | @returned = true 74 | @return = value 75 | end 76 | 77 | def returned?; @returned; end 78 | 79 | private 80 | def unbound_method(name, object_id = nil, &block) 81 | name = "#{name}_#{self.class.to_s.split('::').last}_#{object_id}" 82 | unless RKelly::JS::Base.instance_methods.include?(name.to_sym) 83 | RKelly::JS::Base.class_eval do 84 | define_method(name, &block) 85 | end 86 | end 87 | RKelly::JS::Base.instance_method(name) 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /test/test_automatic_semicolon_insertion.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class AutomaticSemicolonInsertionTest < Test::Unit::TestCase 4 | def setup 5 | @parser = RKelly::Parser.new 6 | end 7 | 8 | def test_basic_statement 9 | assert_sexp( 10 | [ 11 | [:return, 12 | [:lit, 12]] 13 | ], 14 | @parser.parse('return 12')) 15 | end 16 | 17 | def test_multiline_expression 18 | assert_sexp( 19 | [ 20 | [:expression, 21 | [:add, 22 | [:lit, 1], 23 | [:lit, 1] 24 | ] 25 | ] 26 | ], 27 | @parser.parse("1 +\n1")) 28 | end 29 | 30 | def test_multiple_statements 31 | assert_sexp( 32 | [ 33 | [:var, 34 | [ 35 | [:var_decl, :foo, nil] 36 | ] 37 | ], 38 | [:var, 39 | [ 40 | [:var_decl, :bar, nil] 41 | ] 42 | ] 43 | ], 44 | @parser.parse("var foo\nvar bar")) 45 | end 46 | 47 | def test_bracketed_statement 48 | assert_sexp( 49 | [ 50 | [:block, 51 | [ 52 | [:var, 53 | [ 54 | [:var_decl, :foo, nil] 55 | ] 56 | ] 57 | ] 58 | ] 59 | ], 60 | @parser.parse("{var foo}")) 61 | end 62 | 63 | def test_insertion_before_plus_plus 64 | assert_sexp( 65 | [ 66 | [:expression, 67 | [:op_equal, 68 | [:resolve, "a"], 69 | [:resolve, "b"] 70 | ] 71 | ], 72 | [:expression, 73 | [:prefix, [:resolve, "c"], "++"] 74 | ] 75 | ], 76 | @parser.parse("a = b\n++c")) 77 | end 78 | 79 | def test_insertion_before_minus_minus 80 | assert_sexp( 81 | [ 82 | [:expression, 83 | [:op_equal, 84 | [:resolve, "a"], 85 | [:resolve, "b"] 86 | ] 87 | ], 88 | [:expression, 89 | [:prefix, [:resolve, "c"], "--"] 90 | ] 91 | ], 92 | @parser.parse("a = b\n--c")) 93 | end 94 | 95 | def test_insertion_after_continue 96 | assert_sexp( 97 | [ 98 | [:continue], 99 | [:expression, [:resolve, "foo"]] 100 | ], 101 | @parser.parse("continue\nfoo")) 102 | end 103 | 104 | def test_insertion_after_break 105 | assert_sexp( 106 | [ 107 | [:break], 108 | [:expression, [:resolve, "foo"]] 109 | ], 110 | @parser.parse("break\nfoo")) 111 | end 112 | 113 | def test_insertion_after_return 114 | assert_sexp( 115 | [ 116 | [:return], 117 | [:expression, [:resolve, "foo"]] 118 | ], 119 | @parser.parse("return\nfoo")) 120 | end 121 | 122 | def test_insertion_after_throw 123 | assert_nil @parser.parse("throw\nfoo") 124 | end 125 | 126 | def test_no_empty_statement_insertion 127 | assert_nil @parser.parse("if (a > b)\nelse c = d") 128 | end 129 | 130 | def test_no_for_insertion 131 | assert_nil @parser.parse("for (a;b\n){}") 132 | end 133 | 134 | def assert_sexp(expected, node) 135 | assert_equal(expected, node.to_sexp) 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /lib/rkelly/parser.rb: -------------------------------------------------------------------------------- 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 | # When parsing finishes without all tokens being parsed, returns 53 | # the token at which the parsing stopped. Returns nil when parser 54 | # reached to the very last token (but possibly still failed as it 55 | # expeced more tokens). 56 | # 57 | # Useful for pin-pointing the position of a syntax error. 58 | def stopped_at 59 | if @position < @tokens.length 60 | @tokens[@position-1] 61 | else 62 | nil 63 | end 64 | end 65 | 66 | private 67 | def on_error(error_token_id, error_value, value_stack) 68 | if logger 69 | logger.error(token_to_str(error_token_id)) 70 | logger.error("error value: #{error_value}") 71 | logger.error("error stack: #{value_stack}") 72 | end 73 | end 74 | 75 | def next_token 76 | @terminator = false 77 | begin 78 | return [false, false] if @position >= @tokens.length 79 | n_token = @tokens[@position] 80 | @position += 1 81 | case @tokens[@position - 1].name 82 | when :COMMENT 83 | @comments << n_token 84 | @terminator = true if n_token.value =~ /^\/\// 85 | when :S 86 | @terminator = true if n_token.value =~ /[\r\n]/ 87 | end 88 | end while([:COMMENT, :S].include?(n_token.name)) 89 | 90 | if @terminator && 91 | ((@prev_token && %w[continue break return throw].include?(@prev_token.value)) || 92 | (n_token && %w[++ --].include?(n_token.value))) 93 | @position -= 1 94 | return (@prev_token = RKelly::Token.new(';', ';')).to_racc_token 95 | end 96 | 97 | @prev_token = n_token 98 | v = n_token.to_racc_token 99 | v[1] = n_token 100 | v 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /test/expressions/test_11_5_2.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Expressions_11_5_2_Test < ECMAScriptTestCase 4 | def test_nan_divide 5 | js_assert_equal("Number.NaN", "Number.NaN / Number.NaN") 6 | js_assert_equal("Number.NaN", "Number.NaN / 1") 7 | js_assert_equal("Number.NaN", "1 / Number.NaN") 8 | js_assert_equal("Number.NaN", "Number.POSITIVE_INFINITY / Number.NaN") 9 | js_assert_equal("Number.NaN", "Number.NEGATIVE_INFINITY / Number.NaN") 10 | end 11 | 12 | 13 | def test_infinity_divided_by_infinity 14 | js_assert_equal("Number.NaN", "Number.NEGATIVE_INFINITY / Number.NEGATIVE_INFINITY") 15 | js_assert_equal("Number.NaN", "Number.POSITIVE_INFINITY / Number.NEGATIVE_INFINITY") 16 | js_assert_equal("Number.NaN", "Number.NEGATIVE_INFINITY / Number.POSITIVE_INFINITY") 17 | js_assert_equal("Number.NaN", "Number.POSITIVE_INFINITY / Number.POSITIVE_INFINITY") 18 | end 19 | 20 | def test_infinity_divided_by_0 21 | js_assert_equal("Number.POSITIVE_INFINITY", "Number.POSITIVE_INFINITY / 0") 22 | js_assert_equal("Number.NEGATIVE_INFINITY", "Number.NEGATIVE_INFINITY / 0") 23 | end 24 | 25 | def test_infinity_divided_by_1 26 | js_assert_equal("Number.NEGATIVE_INFINITY", "Number.NEGATIVE_INFINITY / 1") 27 | js_assert_equal("Number.POSITIVE_INFINITY", "Number.NEGATIVE_INFINITY / -1") 28 | js_assert_equal("Number.POSITIVE_INFINITY", "Number.POSITIVE_INFINITY / 1") 29 | js_assert_equal("Number.NEGATIVE_INFINITY", "Number.POSITIVE_INFINITY / -1") 30 | end 31 | 32 | def test_infinity_divided_by_max 33 | js_assert_equal("Number.NEGATIVE_INFINITY","Number.NEGATIVE_INFINITY / Number.MAX_VALUE") 34 | js_assert_equal("Number.POSITIVE_INFINITY","Number.NEGATIVE_INFINITY / -Number.MAX_VALUE") 35 | js_assert_equal("Number.POSITIVE_INFINITY","Number.POSITIVE_INFINITY / Number.MAX_VALUE") 36 | js_assert_equal("Number.NEGATIVE_INFINITY","Number.POSITIVE_INFINITY / -Number.MAX_VALUE") 37 | end 38 | 39 | def test_number_divided_by_infinity 40 | js_assert_equal("-0", "1 / Number.NEGATIVE_INFINITY") 41 | js_assert_equal("0", "1 / Number.POSITIVE_INFINITY") 42 | js_assert_equal("-0", "-1 / Number.POSITIVE_INFINITY") 43 | js_assert_equal("0", "-1 / Number.NEGATIVE_INFINITY") 44 | end 45 | 46 | def test_max_val_divided_by_infinity 47 | js_assert_equal("-0", "Number.MAX_VALUE / Number.NEGATIVE_INFINITY") 48 | js_assert_equal("0", "Number.MAX_VALUE / Number.POSITIVE_INFINITY") 49 | js_assert_equal("-0", "-Number.MAX_VALUE / Number.POSITIVE_INFINITY") 50 | js_assert_equal("0", "-Number.MAX_VALUE / Number.NEGATIVE_INFINITY") 51 | end 52 | 53 | def test_0_divide_by_0 54 | js_assert_equal("Number.NaN", "0 / -0") 55 | js_assert_equal("Number.NaN", "-0 / 0") 56 | js_assert_equal("Number.NaN", "-0 / -0") 57 | js_assert_equal("Number.NaN", "0 / 0") 58 | end 59 | 60 | def test_0_divide_by_number 61 | js_assert_equal("0", "0 / 1") 62 | js_assert_equal("-0", "0 / -1") 63 | js_assert_equal("-0", "-0 / 1") 64 | js_assert_equal("0", "-0 / -1") 65 | end 66 | 67 | def test_number_divide_by_0 68 | js_assert_equal("Number.POSITIVE_INFINITY", "1/0") 69 | js_assert_equal("Number.NEGATIVE_INFINITY", "1/-0") 70 | js_assert_equal("Number.NEGATIVE_INFINITY", "-1/0") 71 | js_assert_equal("Number.POSITIVE_INFINITY", "-1/-0") 72 | end 73 | 74 | def test_0_divide_by_infinity 75 | js_assert_equal("0", "0 / Number.POSITIVE_INFINITY") 76 | js_assert_equal("-0", "0 / Number.NEGATIVE_INFINITY") 77 | js_assert_equal("-0", "-0 / Number.POSITIVE_INFINITY") 78 | js_assert_equal("0", "-0 / Number.NEGATIVE_INFINITY") 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/rkelly/nodes/node.rb: -------------------------------------------------------------------------------- 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 | # Matches nodes with the given pattern (usually a class name of 31 | # the node) and returns an instance of PointcutVisitor on which 32 | # #matches can be invoked to get the list of AST nodes that 33 | # matched. 34 | # 35 | # ast.pointcut(RKelly::Nodes::IfNode).matches --> array of nodes 36 | # 37 | def pointcut(pattern) 38 | case pattern 39 | when String 40 | ast = RKelly::Parser.new.parse(pattern) 41 | # Only take the first statement 42 | finder = ast.value.first.class.to_s =~ /StatementNode$/ ? 43 | ast.value.first.value : ast.value.first 44 | visitor = PointcutVisitor.new(finder) 45 | else 46 | visitor = PointcutVisitor.new(pattern) 47 | end 48 | 49 | visitor.accept(self) 50 | visitor 51 | end 52 | alias :/ :pointcut 53 | 54 | # Generates an s-expression data structure like so: 55 | # 56 | # "var x = 10;" --> [:var, [[:var_decl, :x, [:assign, [:lit, 10]]]]]] 57 | # 58 | def to_sexp 59 | SexpVisitor.new.accept(self) 60 | end 61 | 62 | # Generates formatted and intented JavaScript source code. 63 | def to_ecma 64 | ECMAVisitor.new.accept(self) 65 | end 66 | 67 | # Generates a graph description in DOT language. This can be 68 | # fed into the dot program to generate a graph of the AST: 69 | # 70 | # $ dot -Tpng generated-graph.dot -o graph.png 71 | # 72 | def to_dots 73 | visitor = DotVisitor.new 74 | visitor.accept(self) 75 | header = <<-END 76 | digraph g { 77 | graph [ rankdir = "TB" ]; 78 | node [ 79 | fontsize = "16" 80 | shape = "ellipse" 81 | ]; 82 | edge [ ]; 83 | END 84 | nodes = visitor.nodes.map { |x| x.to_s }.join("\n") 85 | counter = 0 86 | arrows = visitor.arrows.map { |x| 87 | s = "#{x} [\nid = #{counter}\n];" 88 | counter += 1 89 | s 90 | }.join("\n") 91 | "#{header}\n#{nodes}\n#{arrows}\n}" 92 | end 93 | 94 | # Loops through all the syntax nodes. 95 | def each(&block) 96 | EnumerableVisitor.new(block).accept(self) 97 | end 98 | 99 | # This CRASHES! 100 | # It calls method #s which is nowhere to be found. 101 | def to_real_sexp 102 | RealSexpVisitor.new.accept(self) 103 | end 104 | end 105 | 106 | %w[EmptyStatement Parenthetical ExpressionStatement True Delete Return TypeOf 107 | SourceElements Number LogicalNot AssignExpr FunctionBody 108 | ObjectLiteral UnaryMinus Throw This BitwiseNot Element String 109 | Array CaseBlock Null Break Parameter Block False Void Regexp 110 | Arguments Attr Continue ConstStatement UnaryPlus VarStatement].each do |node| 111 | eval "class #{node}Node < Node; end" 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /test/expressions/test_11_5_3.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Expressions_11_5_3_Test < ECMAScriptTestCase 4 | def test_either_is_nan 5 | js_assert_equal("Number.NaN", "Number.NaN % Number.NaN") 6 | js_assert_equal("Number.NaN", "Number.NaN % 1") 7 | js_assert_equal("Number.NaN", "1 % Number.NaN") 8 | js_assert_equal("Number.NaN", "Number.POSITIVE_INFINITY % Number.NaN") 9 | js_assert_equal("Number.NaN", "Number.NEGATIVE_INFINITY % Number.NaN") 10 | end 11 | 12 | def test_infinity_mod_infinity 13 | js_assert_equal("Number.NaN", "Number.NEGATIVE_INFINITY % Number.NEGATIVE_INFINITY") 14 | js_assert_equal("Number.NaN", "Number.POSITIVE_INFINITY % Number.NEGATIVE_INFINITY") 15 | js_assert_equal("Number.NaN", "Number.NEGATIVE_INFINITY % Number.POSITIVE_INFINITY") 16 | js_assert_equal("Number.NaN", "Number.POSITIVE_INFINITY % Number.POSITIVE_INFINITY") 17 | end 18 | 19 | def test_infinity_mod_0 20 | js_assert_equal("Number.NaN", "Number.POSITIVE_INFINITY % 0") 21 | js_assert_equal("Number.NaN", "Number.NEGATIVE_INFINITY % 0") 22 | js_assert_equal("Number.NaN", "Number.POSITIVE_INFINITY % -0") 23 | js_assert_equal("Number.NaN", "Number.NEGATIVE_INFINITY % -0") 24 | end 25 | 26 | def test_infinity_mod_1 27 | js_assert_equal("Number.NaN", "Number.NEGATIVE_INFINITY % 1") 28 | js_assert_equal("Number.NaN", "Number.NEGATIVE_INFINITY % -1") 29 | js_assert_equal("Number.NaN", "Number.POSITIVE_INFINITY % 1") 30 | js_assert_equal("Number.NaN", "Number.POSITIVE_INFINITY % -1") 31 | end 32 | 33 | def test_infinity_mod_max_value 34 | js_assert_equal("Number.NaN", "Number.NEGATIVE_INFINITY % Number.MAX_VALUE") 35 | js_assert_equal("Number.NaN", "Number.NEGATIVE_INFINITY % -Number.MAX_VALUE") 36 | js_assert_equal("Number.NaN", "Number.POSITIVE_INFINITY % Number.MAX_VALUE") 37 | js_assert_equal("Number.NaN", "Number.POSITIVE_INFINITY % -Number.MAX_VALUE") 38 | end 39 | 40 | def test_0_mod_0 41 | js_assert_equal("Number.NaN", "0 % -0") 42 | js_assert_equal("Number.NaN", "-0 % 0") 43 | js_assert_equal("Number.NaN", "-0 % -0") 44 | js_assert_equal("Number.NaN", "0 % 0") 45 | end 46 | 47 | def test_1_mod_0 48 | js_assert_equal("Number.NaN", "1%0") 49 | js_assert_equal("Number.NaN", "1%-0") 50 | js_assert_equal("Number.NaN", "-1%0") 51 | js_assert_equal("Number.NaN", "-1%-0") 52 | end 53 | 54 | def test_max_value_mod_0 55 | js_assert_equal("Number.NaN", "Number.MAX_VALUE%0") 56 | js_assert_equal("Number.NaN", "Number.MAX_VALUE%-0") 57 | js_assert_equal("Number.NaN", "-Number.MAX_VALUE%0") 58 | js_assert_equal("Number.NaN", "-Number.MAX_VALUE%-0") 59 | end 60 | 61 | def test_number_mod_infinity 62 | js_assert_equal("1", "1 % Number.NEGATIVE_INFINITY") 63 | js_assert_equal("1", "1 % Number.POSITIVE_INFINITY") 64 | js_assert_equal("-1", "-1 % Number.POSITIVE_INFINITY") 65 | js_assert_equal("-1", "-1 % Number.NEGATIVE_INFINITY") 66 | end 67 | 68 | def test_max_val_mod_infinity 69 | js_assert_equal("Number.MAX_VALUE", "Number.MAX_VALUE % Number.NEGATIVE_INFINITY") 70 | js_assert_equal("Number.MAX_VALUE", "Number.MAX_VALUE % Number.POSITIVE_INFINITY") 71 | js_assert_equal("-Number.MAX_VALUE", "-Number.MAX_VALUE % Number.POSITIVE_INFINITY") 72 | js_assert_equal("-Number.MAX_VALUE", "-Number.MAX_VALUE % Number.NEGATIVE_INFINITY") 73 | end 74 | 75 | def test_0_mod_infinity 76 | js_assert_equal("0", "0 % Number.POSITIVE_INFINITY") 77 | js_assert_equal("0", "0 % Number.NEGATIVE_INFINITY") 78 | js_assert_equal("-0", "-0 % Number.POSITIVE_INFINITY") 79 | js_assert_equal("-0", "-0 % Number.NEGATIVE_INFINITY") 80 | end 81 | 82 | def test_0_mod_1 83 | js_assert_equal("0", "0 % 1") 84 | js_assert_equal("-0", "0 % -1") 85 | js_assert_equal("-0", "-0 % 1") 86 | js_assert_equal("0", "-0 % -1") 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /test/expressions/test_11_4_6.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Expressions_11_4_6_Test < ECMAScriptTestCase 4 | @@tests = { 5 | :empty_string => ["''", 0], 6 | :space_string => ["' '", 0], 7 | :tab_string => ["'\t'", 0], 8 | :newline_string => ["'\n'", 0], 9 | :cr_string => ["'\r'", 0], 10 | :f_string => ["'\f'", 0], 11 | :char_code_09 => ['String.fromCharCode(0x0009)', 0], 12 | :char_code_20 => ['String.fromCharCode(0x0020)', 0], 13 | :char_code_0C => ['String.fromCharCode(0x000C)', 0], 14 | :char_code_0B => ['String.fromCharCode(0x000B)', 0], 15 | :char_code_0A => ['String.fromCharCode(0x000A)', 0], 16 | :lh_space_add => ["' ' + 999", 999], 17 | :lh_newline_add => ["'\n' + 999", 999], 18 | :lh_cr_add => ["'\r' + 999", 999], 19 | :lh_tab_add => ["'\t' + 999", 999], 20 | :lh_tab_f => ["'\f' + 999", 999], 21 | :rh_space_add => ["999 + ' '", 999], 22 | :rh_newline_add => ["999 + '\n'", 999], 23 | :rh_cr_add => ["999 + '\r'", 999], 24 | :rh_tab_add => ["999 + '\t'", 999], 25 | :rh_tab_f => ["999 + '\f'", 999], 26 | :bs_space_add => ["' ' + 999 + ' '", 999], 27 | :bs_newline_add => ["'\n' + 999 + '\n'", 999], 28 | :bs_cr_add => ["'\r' + 999 + '\r'", 999], 29 | :bs_tab_add => ["'\t' + 999 + '\t'", 999], 30 | :bs_tab_f => ["'\f' + 999 + '\f'", 999], 31 | :cl_09 => ["String.fromCharCode(0x0009) + 99", 99], 32 | :cl_20 => ["String.fromCharCode(0x0020) + 99", 99], 33 | :cl_0C => ["String.fromCharCode(0x000C) + 99", 99], 34 | :cl_0B => ["String.fromCharCode(0x000B) + 99", 99], 35 | :cl_0A => ["String.fromCharCode(0x000A) + 99", 99], 36 | :cr_09 => ["99 + String.fromCharCode(0x0009)", 99], 37 | :cr_20 => ["99 + String.fromCharCode(0x0020)", 99], 38 | :cr_0C => ["99 + String.fromCharCode(0x000C)", 99], 39 | :cr_0B => ["99 + String.fromCharCode(0x000B)", 99], 40 | :cr_0A => ["99 + String.fromCharCode(0x000A)", 99], 41 | :bs_09 => ["String.fromCharCode(0x0009) + 99 + String.fromCharCode(0x0009)", 99], 42 | :bs_20 => ["String.fromCharCode(0x0020) + 99 + String.fromCharCode(0x0020)", 99], 43 | :bs_0C => ["String.fromCharCode(0x000C) + 99 + String.fromCharCode(0x000C)", 99], 44 | :bs_0B => ["String.fromCharCode(0x000B) + 99 + String.fromCharCode(0x000B)", 99], 45 | :bs_0A => ["String.fromCharCode(0x000A) + 99 + String.fromCharCode(0x000A)", 99], 46 | :infinity => ["'Infinity'", 'Number.POSITIVE_INFINITY'], 47 | :neg_infinity => ["'-Infinity'", 'Number.NEGATIVE_INFINITY'], 48 | :pos_infinity => ["'+Infinity'", 'Number.POSITIVE_INFINITY'], 49 | } 50 | @@sign_tests = [ 51 | [3.14159, 3.14159], 52 | ['3.', 3], 53 | ['3.e1', 30], 54 | ['3.e+1', 30], 55 | ['3.e-1', 0.30], 56 | ['.00001', 0.00001], 57 | ['.01e2', 1], 58 | ['.01e+2', 1], 59 | ['.01e-2', 0.0001], 60 | ['1234e5', 123400000], 61 | ['1234e+5', 123400000], 62 | ['1234e-5', 0.01234], 63 | ] 64 | 0.upto(9) { |x| @@sign_tests << [x, x] } 65 | 66 | # 0x0 needs special treatment as sprintf("%#x", 0) results in "0" 67 | @@sign_tests << ["0x0", 0] 68 | @@sign_tests << ["0X0", 0] 69 | 70 | 1.upto(15) { |x| 71 | @@sign_tests << [sprintf("%#x", x), x] 72 | @@sign_tests << [sprintf("%#x", x).gsub(/x/, 'X'), x] 73 | if sprintf("%#x", x) =~ /[a-f]/ 74 | @@sign_tests << [sprintf("%#x", x).gsub(/([a-f])/) { |m| m.upcase }, x] 75 | @@sign_tests << [sprintf("%#x", x).upcase, x] 76 | end 77 | } 78 | 79 | @@sign_tests.each { |actual, expected| 80 | define_method(:"test_num_#{actual.to_s.gsub(/[\.+]/, '_')}") do 81 | @runtime.execute(" 82 | assert_equal(#{expected}, +('#{actual}')); 83 | assert_equal(#{expected}, +('+#{actual}')); 84 | assert_equal(-#{expected}, +('-#{actual}')); 85 | ") 86 | end 87 | } 88 | 89 | @@tests.each do |name,(actual, expected)| 90 | define_method(:"test_#{name}") do 91 | @runtime.execute("assert_equal(#{expected}, +(#{actual}));") 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = RKelly Remix 2 | 3 | https://github.com/nene/rkelly-remix 4 | 5 | == DESCRIPTION 6 | 7 | RKelly Remix is a fork of the 8 | RKelly[https://github.com/tenderlove/rkelly] JavaScript parser. 9 | 10 | == Install 11 | 12 | gem install rkelly-remix 13 | 14 | Note that you can't have rkelly and rkelly-remix both installed at the 15 | same time. That would cause a conflict since they're both included 16 | with: 17 | 18 | require 'rkelly' 19 | 20 | == Example 21 | 22 | # Iterate over and modify a JavaScript AST. Then print the modified 23 | # AST as JavaScript. 24 | require 'rkelly' 25 | 26 | parser = RKelly::Parser.new 27 | ast = parser.parse( 28 | "for(var i = 0; i < 10; i++) { var x = 5 + 5; }" 29 | ) 30 | 31 | ast.each do |node| 32 | node.value = 'hello' if node.value == 'i' 33 | node.name = 'hello' if node.respond_to?(:name) && node.name == 'i' 34 | end 35 | puts ast.to_ecma # => awesome javascript 36 | 37 | == Why fork? 38 | 39 | Currently the original RKelly project is unmaintained. The latest 40 | release was in late 2012, and since then the author has not responded 41 | to bug reports or pull requests. 42 | 43 | I created this fork mainly to satisfy the needs of my 44 | JSDuck[https://github.com/senchalabs/jsduck] project, but you too 45 | should consider using it, as it fixes several problems in the original 46 | RKelly: 47 | 48 | === Much improved speed 49 | 50 | * 20 x faster in Ruby 1.8. 51 | * 2 x faster in Ruby 1.9 and 2.0. 52 | 53 | === Correct start/end position data for all syntax nodes 54 | 55 | Original RKelly only had a rudimentary support for getting the line 56 | number of an AST node, and even that was often wrong. 57 | 58 | In RKelly Remix each AST node has a range property, which contains 59 | granular data about the exact location of the node in source code: 60 | 61 | parser = RKelly::Parser.new 62 | ast = parser.parse(<<-eojs) 63 | function aaron() { 64 | var x = 10; 65 | return 1 + 1; 66 | } 67 | eojs 68 | 69 | node = ast.pointcut(ReturnNode).matches.first 70 | 71 | node.range.to_s # <{line:3 char:5 (41)}...{line:3 char:17 (53)}> 72 | 73 | node.range.from.line # 3 74 | node.range.from.char # 5 75 | node.range.from.index # 41 76 | 77 | node.range.to.line # 3 78 | node.range.to.char # 17 79 | node.range.to.index # 53 80 | 81 | === Sensible comments handling 82 | 83 | Original RKelly attempted to associate each comment with a related AST 84 | node, but failed to correctly do so, which is understandable, as there 85 | is no standard way to do such a mapping. RKelly Remix abandons this 86 | and just lists all commenst in the root node, leaving the user with 87 | the task of associating them with AST nodes if he desires so (the same 88 | approach is taken by another JS parser: Esprima[http://esprima.org] ). 89 | 90 | parser = RKelly::Parser.new 91 | ast = parser.parse(<<-eojs) 92 | /** 93 | * This is an awesome test comment. 94 | */ 95 | function aaron() { // This is a side comment 96 | var x = 10; 97 | return 1 + 1; // Equals two! 98 | } 99 | eojs 100 | 101 | # print out all the comments 102 | ast.comments.each do |c| 103 | puts c.value 104 | end 105 | 106 | === Improved parser 107 | 108 | * List of reserved words matches with ECMAScript 5.1. 109 | * Keywords are allowed in property names. 110 | * Multibyte characters are supported in Ruby >= 1.9. 111 | * Correct parsing of regexes like /[/]/ 112 | 113 | == License 114 | 115 | The MIT License 116 | 117 | Copyright (c) 2007, 2008, 2009 Aaron Patterson, John Barnette 118 | 119 | Permission is hereby granted, free of charge, to any person obtaining a copy 120 | of this software and associated documentation files (the "Software"), to deal 121 | in the Software without restriction, including without limitation the rights 122 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 123 | copies of the Software, and to permit persons to whom the Software is 124 | furnished to do so, subject to the following conditions: 125 | 126 | The above copyright notice and this permission notice shall be included in 127 | all copies or substantial portions of the Software. 128 | 129 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 130 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 131 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 132 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 133 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 134 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 135 | THE SOFTWARE 136 | -------------------------------------------------------------------------------- /lib/rkelly/visitors/visitor.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Visitors 3 | class Visitor 4 | TERMINAL_NODES = %w{ 5 | Break Continue EmptyStatement False Null Number Parameter Regexp Resolve 6 | String This True 7 | } 8 | SINGLE_VALUE_NODES = %w{ 9 | Parenthetical AssignExpr BitwiseNot Block Delete Element ExpressionStatement 10 | FunctionBody LogicalNot Return Throw TypeOf UnaryMinus UnaryPlus Void 11 | } 12 | BINARY_NODES = %w{ 13 | Add BitAnd BitOr BitXOr CaseClause Comma Divide DoWhile Equal Greater 14 | GreaterOrEqual In InstanceOf LeftShift Less LessOrEqual LogicalAnd 15 | LogicalOr Modulus Multiply NotEqual NotStrictEqual OpAndEqual 16 | OpDivideEqual OpEqual OpLShiftEqual OpMinusEqual OpModEqual 17 | OpMultiplyEqual OpOrEqual OpPlusEqual OpRShiftEqual OpURShiftEqual 18 | OpXOrEqual RightShift StrictEqual Subtract Switch UnsignedRightShift 19 | While With 20 | } 21 | ARRAY_VALUE_NODES = %w{ 22 | Arguments Array CaseBlock ConstStatement ObjectLiteral SourceElements 23 | VarStatement 24 | } 25 | NAME_VALUE_NODES = %w{ 26 | Label Property GetterProperty SetterProperty VarDecl 27 | } 28 | PREFIX_POSTFIX_NODES = %w{ Postfix Prefix } 29 | CONDITIONAL_NODES = %w{ If Conditional } 30 | FUNC_CALL_NODES = %w{ NewExpr FunctionCall } 31 | FUNC_DECL_NODES = %w{ FunctionExpr FunctionDecl } 32 | ALL_NODES = %w{ For ForIn Try BracketAccessor DotAccessor } + 33 | TERMINAL_NODES + SINGLE_VALUE_NODES + BINARY_NODES + ARRAY_VALUE_NODES + 34 | NAME_VALUE_NODES + PREFIX_POSTFIX_NODES + CONDITIONAL_NODES + 35 | FUNC_CALL_NODES + FUNC_DECL_NODES 36 | 37 | def accept(target) 38 | target.accept(self) 39 | end 40 | 41 | TERMINAL_NODES.each do |type| 42 | define_method(:"visit_#{type}Node") { |o| o.value } 43 | end 44 | 45 | BINARY_NODES.each do |type| 46 | define_method(:"visit_#{type}Node") do |o| 47 | [o.left && o.left.accept(self), o.value && o.value.accept(self)] 48 | end 49 | end 50 | 51 | ARRAY_VALUE_NODES.each do |type| 52 | define_method(:"visit_#{type}Node") do |o| 53 | o.value && o.value.map { |v| v ? v.accept(self) : nil } 54 | end 55 | end 56 | 57 | NAME_VALUE_NODES.each do |type| 58 | define_method(:"visit_#{type}Node") do |o| 59 | [o.name.to_s.to_sym, o.value ? o.value.accept(self) : nil] 60 | end 61 | end 62 | 63 | SINGLE_VALUE_NODES.each do |type| 64 | define_method(:"visit_#{type}Node") do |o| 65 | o.value.accept(self) if o.value 66 | end 67 | end 68 | 69 | PREFIX_POSTFIX_NODES.each do |type| 70 | define_method(:"visit_#{type}Node") do |o| 71 | o.operand.accept(self) 72 | end 73 | end 74 | 75 | CONDITIONAL_NODES.each do |type| 76 | define_method(:"visit_#{type}Node") do |o| 77 | [ o.conditions.accept(self), 78 | o.value.accept(self), 79 | o.else ? o.else.accept(self) : nil 80 | ] 81 | end 82 | end 83 | FUNC_CALL_NODES.each do |type| 84 | define_method(:"visit_#{type}Node") do |o| 85 | [o.value.accept(self), o.arguments.accept(self)] 86 | end 87 | end 88 | FUNC_DECL_NODES.each do |type| 89 | define_method(:"visit_#{type}Node") do |o| 90 | [ 91 | o.value ? o.value : nil, 92 | o.arguments.map { |x| x.accept(self) }, 93 | o.function_body.accept(self) 94 | ] 95 | end 96 | end 97 | 98 | def visit_ForNode(o) 99 | [ 100 | o.init ? o.init.accept(self) : nil, 101 | o.test ? o.test.accept(self) : nil, 102 | o.counter ? o.counter.accept(self) : nil, 103 | o.value.accept(self) 104 | ] 105 | end 106 | 107 | def visit_ForInNode(o) 108 | [ 109 | o.left.accept(self), 110 | o.right.accept(self), 111 | o.value.accept(self) 112 | ] 113 | end 114 | 115 | def visit_TryNode(o) 116 | [ 117 | o.value.accept(self), 118 | o.catch_var ? o.catch_var : nil, 119 | o.catch_block ? o.catch_block.accept(self) : nil, 120 | o.finally_block ? o.finally_block.accept(self) : nil 121 | ] 122 | end 123 | 124 | def visit_BracketAccessorNode(o) 125 | [ 126 | o.value.accept(self), 127 | o.accessor.accept(self) 128 | ] 129 | end 130 | 131 | def visit_DotAccessorNode(o) 132 | o.value.accept(self) 133 | end 134 | end 135 | end 136 | end 137 | -------------------------------------------------------------------------------- /test/test_ecma_visitor.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/helper" 2 | 3 | class ECMAVisitorTest < Test::Unit::TestCase 4 | def setup 5 | @parser = RKelly::Parser.new 6 | end 7 | 8 | def test_anonymous_function_expr 9 | assert_to_ecma('a = function() { };') 10 | end 11 | 12 | def test_named_function_expr 13 | assert_to_ecma('a = function b() { };') 14 | end 15 | 16 | def test_this_node 17 | assert_to_ecma('this.foo;') 18 | end 19 | 20 | def test_bitwise_not_node 21 | assert_to_ecma('~10;') 22 | end 23 | 24 | def test_delete_node 25 | assert_to_ecma('delete foo;') 26 | end 27 | 28 | def test_element_node 29 | assert_to_ecma('var foo = [1];') 30 | end 31 | 32 | def test_logical_not_node 33 | assert_to_ecma('!foo;') 34 | end 35 | 36 | def test_unary_minus_node 37 | assert_to_ecma('-0;') 38 | end 39 | 40 | def test_return_node 41 | assert_to_ecma('return foo;') 42 | assert_to_ecma('return;') 43 | end 44 | 45 | def test_throw_node 46 | assert_to_ecma('throw foo;') 47 | end 48 | 49 | def test_type_of_node 50 | assert_to_ecma('typeof foo;') 51 | end 52 | 53 | def test_unary_plus_node 54 | assert_to_ecma('+10;') 55 | end 56 | 57 | def test_void_node 58 | assert_to_ecma('void(0);') 59 | end 60 | 61 | [ 62 | [:add, '+'], 63 | [:and_equal, '&='], 64 | [:bit_and, '&'], 65 | [:bit_or, '|'], 66 | [:bit_xor, '^'], 67 | [:divide, '/'], 68 | [:divide_equal, '/='], 69 | [:equal_equal, '=='], 70 | [:greater, '>'], 71 | [:greater_or_equal, '>='], 72 | [:left_shift, '<<'], 73 | [:left_shift_equal, '<<='], 74 | [:less_or_equal, '<='], 75 | [:logical_and, '&&'], 76 | [:logical_or, '||'], 77 | [:minus_equal, '-='], 78 | [:mod, '%'], 79 | [:mod_equal, '%='], 80 | [:multiply, '*'], 81 | [:multiply_equal, '*='], 82 | [:not_equal, '!='], 83 | [:not_strict_equal, '!=='], 84 | [:or_equal, '|='], 85 | [:plus_equal, '+='], 86 | [:right_shift, '>>'], 87 | [:right_shift_equal, '>>='], 88 | [:strict_equal, '==='], 89 | [:subtract, '-'], 90 | [:ur_shift, '>>>'], 91 | [:uright_shift_equal, '>>>='], 92 | [:xor_equal, '^='], 93 | [:instanceof, 'instanceof'], 94 | ].each do |name, value| 95 | define_method(:"test_#{name}_node") do 96 | assert_to_ecma("10 #{value} 20;") 97 | end 98 | end 99 | 100 | def test_while_node 101 | assert_to_ecma("while(true) { foo(); }") 102 | end 103 | 104 | def test_switch_node 105 | assert_to_ecma("switch(a) { }") 106 | end 107 | 108 | def test_switch_case_node 109 | assert_to_ecma("switch(a) { 110 | case 1: 111 | foo(); 112 | break; 113 | }") 114 | end 115 | 116 | def test_switch_default_node 117 | assert_to_ecma("switch(a) { 118 | case 1: 119 | foo(); 120 | break; 121 | default: 122 | bar(); 123 | break; 124 | }") 125 | end 126 | 127 | def test_do_while_node 128 | assert_to_ecma("do { foo(); } while(true);") 129 | end 130 | 131 | def test_with_node 132 | assert_to_ecma("with(o) { foo(); }") 133 | end 134 | 135 | def test_const_statement_node 136 | assert_to_ecma("const foo;") 137 | end 138 | 139 | def test_label_node 140 | assert_to_ecma("foo: var foo;") 141 | end 142 | 143 | def test_object_literal 144 | assert_to_ecma("var foo = { };") 145 | end 146 | 147 | def test_property 148 | assert_to_ecma("var foo = { bar: 10 };") 149 | end 150 | 151 | def test_getter_node 152 | assert_to_ecma("var foo = { get a() { } };") 153 | end 154 | 155 | def test_setter_node 156 | assert_to_ecma("var foo = { set a(b) { } };") 157 | end 158 | 159 | def test_bracket_access_node 160 | assert_to_ecma("var foo = foo.bar[10];") 161 | end 162 | 163 | def test_new_expr_node 164 | assert_to_ecma("var foo = new Array();") 165 | assert_to_ecma("var foo = new Array(10);") 166 | assert_to_ecma("var foo = new Array(a, 10);") 167 | end 168 | 169 | def test_try_finally 170 | assert_to_ecma('try { var x = 10; } finally { var x = 20; }') 171 | end 172 | 173 | def test_try_catch 174 | assert_to_ecma('try { var x = 10; } catch(a) { var x = 20; }') 175 | end 176 | 177 | def test_comma_node 178 | assert_to_ecma('i = 10, j = 11;') 179 | end 180 | 181 | def test_in_node 182 | assert_to_ecma('var x = 0 in foo;') 183 | end 184 | 185 | def test_if_node 186 | assert_to_ecma('if(5 && 10) var foo = 20;') 187 | assert_to_ecma('if(5 && 10) { var foo = 20; }') 188 | assert_to_ecma('if(5 && 10) { var foo = 20; } else var foo = 10;') 189 | assert_to_ecma('if(5 && 10) { var foo = 20; } else { var foo = 10; }') 190 | end 191 | 192 | def test_conditional_node 193 | assert_to_ecma('var x = 5 < 10 ? 20 : 30;') 194 | end 195 | 196 | def test_for_in_node 197 | assert_to_ecma('for(foo in bar) { var x = 10; }') 198 | assert_to_ecma('for(var foo in bar) { var x = 10; }') 199 | end 200 | 201 | def test_for_node 202 | assert_to_ecma('for(var i = 0; i < 10; i++) { var x = 10; }') 203 | assert_to_ecma('var i = 0; for(; i < 10; i++) { var x = 10; }') 204 | assert_to_ecma('var i; for(i = 0; i < 6; ++i) { var x = 10; }') 205 | end 206 | 207 | def assert_to_ecma(expected, actual = nil) 208 | ecma = @parser.parse(actual || expected).to_ecma 209 | ecma = ecma.gsub(/\n/, ' ').gsub(/\s+/, ' ') 210 | expected = expected.gsub(/\n/, ' ').gsub(/\s+/, ' ') 211 | assert_equal(expected, ecma) 212 | end 213 | end 214 | -------------------------------------------------------------------------------- /Manifest.txt: -------------------------------------------------------------------------------- 1 | CHANGELOG.rdoc 2 | Manifest.txt 3 | README.rdoc 4 | Rakefile 5 | lib/parser.y 6 | lib/rkelly.rb 7 | lib/rkelly/char_pos.rb 8 | lib/rkelly/char_range.rb 9 | lib/rkelly/constants.rb 10 | lib/rkelly/generated_parser.rb 11 | lib/rkelly/js.rb 12 | lib/rkelly/js/array.rb 13 | lib/rkelly/js/base.rb 14 | lib/rkelly/js/boolean.rb 15 | lib/rkelly/js/function.rb 16 | lib/rkelly/js/function_prototype.rb 17 | lib/rkelly/js/global_object.rb 18 | lib/rkelly/js/math.rb 19 | lib/rkelly/js/nan.rb 20 | lib/rkelly/js/number.rb 21 | lib/rkelly/js/object.rb 22 | lib/rkelly/js/object_prototype.rb 23 | lib/rkelly/js/property.rb 24 | lib/rkelly/js/scope.rb 25 | lib/rkelly/js/string.rb 26 | lib/rkelly/lexeme.rb 27 | lib/rkelly/nodes.rb 28 | lib/rkelly/nodes/binary_node.rb 29 | lib/rkelly/nodes/bracket_accessor_node.rb 30 | lib/rkelly/nodes/case_clause_node.rb 31 | lib/rkelly/nodes/comma_node.rb 32 | lib/rkelly/nodes/conditional_node.rb 33 | lib/rkelly/nodes/dot_accessor_node.rb 34 | lib/rkelly/nodes/for_in_node.rb 35 | lib/rkelly/nodes/for_node.rb 36 | lib/rkelly/nodes/function_call_node.rb 37 | lib/rkelly/nodes/function_decl_node.rb 38 | lib/rkelly/nodes/function_expr_node.rb 39 | lib/rkelly/nodes/if_node.rb 40 | lib/rkelly/nodes/label_node.rb 41 | lib/rkelly/nodes/new_expr_node.rb 42 | lib/rkelly/nodes/node.rb 43 | lib/rkelly/nodes/not_strict_equal_node.rb 44 | lib/rkelly/nodes/op_equal_node.rb 45 | lib/rkelly/nodes/postfix_node.rb 46 | lib/rkelly/nodes/prefix_node.rb 47 | lib/rkelly/nodes/property_node.rb 48 | lib/rkelly/nodes/resolve_node.rb 49 | lib/rkelly/nodes/strict_equal_node.rb 50 | lib/rkelly/nodes/try_node.rb 51 | lib/rkelly/nodes/var_decl_node.rb 52 | lib/rkelly/parser.rb 53 | lib/rkelly/runtime.rb 54 | lib/rkelly/runtime/ruby_function.rb 55 | lib/rkelly/runtime/scope_chain.rb 56 | lib/rkelly/syntax_error.rb 57 | lib/rkelly/token.rb 58 | lib/rkelly/tokenizer.rb 59 | lib/rkelly/visitable.rb 60 | lib/rkelly/visitors.rb 61 | lib/rkelly/visitors/dot_visitor.rb 62 | lib/rkelly/visitors/ecma_visitor.rb 63 | lib/rkelly/visitors/enumerable_visitor.rb 64 | lib/rkelly/visitors/evaluation_visitor.rb 65 | lib/rkelly/visitors/function_visitor.rb 66 | lib/rkelly/visitors/pointcut_visitor.rb 67 | lib/rkelly/visitors/real_sexp_visitor.rb 68 | lib/rkelly/visitors/sexp_visitor.rb 69 | lib/rkelly/visitors/visitor.rb 70 | test/ecma_script_test_case.rb 71 | test/execute_test_case.rb 72 | test/execution_contexts/test_10_1_3-1.rb 73 | test/expressions/test_11_3_1.rb 74 | test/expressions/test_11_3_2.rb 75 | test/expressions/test_11_4_2.rb 76 | test/expressions/test_11_4_3.rb 77 | test/expressions/test_11_4_4.rb 78 | test/expressions/test_11_4_5.rb 79 | test/expressions/test_11_4_6.rb 80 | test/expressions/test_11_4_8.rb 81 | test/expressions/test_11_4_9.rb 82 | test/expressions/test_11_5_1.rb 83 | test/expressions/test_11_5_2.rb 84 | test/expressions/test_11_5_3.rb 85 | test/expressions/test_11_6_1-1.rb 86 | test/expressions/test_11_9_1.rb 87 | test/function/test_15_3_1_1-1.rb 88 | test/global_object/test_15_1_1_1.rb 89 | test/global_object/test_15_1_1_2.rb 90 | test/global_object/test_15_1_1_3.rb 91 | test/helper.rb 92 | test/node_test_case.rb 93 | test/object/test_15_2_1_1.rb 94 | test/object/test_15_2_1_2.rb 95 | test/object/test_15_2_2_1.rb 96 | test/statements/test_12_5-1.rb 97 | test/test_add_node.rb 98 | test/test_arguments_node.rb 99 | test/test_array_node.rb 100 | test/test_assign_expr_node.rb 101 | test/test_automatic_semicolon_insertion.rb 102 | test/test_bit_and_node.rb 103 | test/test_bit_or_node.rb 104 | test/test_bit_x_or_node.rb 105 | test/test_bitwise_not_node.rb 106 | test/test_block_node.rb 107 | test/test_bracket_accessor_node.rb 108 | test/test_break_node.rb 109 | test/test_case_block_node.rb 110 | test/test_case_clause_node.rb 111 | test/test_char_pos.rb 112 | test/test_char_range.rb 113 | test/test_comma_node.rb 114 | test/test_comments.rb 115 | test/test_conditional_node.rb 116 | test/test_const_statement_node.rb 117 | test/test_continue_node.rb 118 | test/test_delete_node.rb 119 | test/test_divide_node.rb 120 | test/test_do_while_node.rb 121 | test/test_dot_accessor_node.rb 122 | test/test_ecma_visitor.rb 123 | test/test_element_node.rb 124 | test/test_empty_statement_node.rb 125 | test/test_equal_node.rb 126 | test/test_evaluation_visitor.rb 127 | test/test_expression_statement_node.rb 128 | test/test_false_node.rb 129 | test/test_for_in_node.rb 130 | test/test_for_node.rb 131 | test/test_function_body_node.rb 132 | test/test_function_call_node.rb 133 | test/test_function_decl_node.rb 134 | test/test_function_expr_node.rb 135 | test/test_function_visitor.rb 136 | test/test_getter_property_node.rb 137 | test/test_global_object.rb 138 | test/test_greater_node.rb 139 | test/test_greater_or_equal_node.rb 140 | test/test_if_node.rb 141 | test/test_in_node.rb 142 | test/test_instance_of_node.rb 143 | test/test_label_node.rb 144 | test/test_left_shift_node.rb 145 | test/test_less_node.rb 146 | test/test_less_or_equal_node.rb 147 | test/test_line_number.rb 148 | test/test_logical_and_node.rb 149 | test/test_logical_not_node.rb 150 | test/test_logical_or_node.rb 151 | test/test_modulus_node.rb 152 | test/test_multiply_node.rb 153 | test/test_new_expr_node.rb 154 | test/test_not_equal_node.rb 155 | test/test_not_strict_equal_node.rb 156 | test/test_null_node.rb 157 | test/test_number_node.rb 158 | test/test_object_literal_node.rb 159 | test/test_op_and_equal_node.rb 160 | test/test_op_divide_equal_node.rb 161 | test/test_op_equal_node.rb 162 | test/test_op_l_shift_equal_node.rb 163 | test/test_op_minus_equal_node.rb 164 | test/test_op_mod_equal_node.rb 165 | test/test_op_multiply_equal_node.rb 166 | test/test_op_or_equal_node.rb 167 | test/test_op_plus_equal_node.rb 168 | test/test_op_r_shift_equal_node.rb 169 | test/test_op_u_r_shift_equal_node.rb 170 | test/test_op_x_or_equal_node.rb 171 | test/test_parameter_node.rb 172 | test/test_parser.rb 173 | test/test_pointcut_visitor.rb 174 | test/test_postfix_node.rb 175 | test/test_prefix_node.rb 176 | test/test_property_node.rb 177 | test/test_regexp_node.rb 178 | test/test_resolve_node.rb 179 | test/test_return_node.rb 180 | test/test_right_shift_node.rb 181 | test/test_rkelly.rb 182 | test/test_runtime.rb 183 | test/test_scope_chain.rb 184 | test/test_setter_property_node.rb 185 | test/test_source_elements.rb 186 | test/test_strict_equal_node.rb 187 | test/test_string_node.rb 188 | test/test_subtract_node.rb 189 | test/test_switch_node.rb 190 | test/test_this_node.rb 191 | test/test_throw_node.rb 192 | test/test_tokenizer.rb 193 | test/test_true_node.rb 194 | test/test_try_node.rb 195 | test/test_type_of_node.rb 196 | test/test_unary_minus_node.rb 197 | test/test_unary_plus_node.rb 198 | test/test_unsigned_right_shift_node.rb 199 | test/test_var_decl_node.rb 200 | test/test_var_statement_node.rb 201 | test/test_void_node.rb 202 | test/test_while_node.rb 203 | test/test_with_node.rb 204 | -------------------------------------------------------------------------------- /lib/rkelly/visitors/dot_visitor.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Visitors 3 | class DotVisitor < Visitor 4 | class Node < Struct.new(:node_id, :fields) 5 | ESCAPE = /([<>"\\])/ 6 | def to_s 7 | counter = 0 8 | label = fields.map { |f| 9 | s = " #{f.to_s.gsub(ESCAPE, '\\\\\1').gsub(/[\r\n]/,' ')}" 10 | counter += 1 11 | s 12 | }.join('|') 13 | "\"#{node_id}\" [\nlabel = \"#{label}\"\nshape = \"record\"\n];" 14 | end 15 | end 16 | 17 | class Arrow < Struct.new(:from, :to, :label) 18 | def to_s 19 | "\"#{from.node_id}\":f0 -> \"#{to.node_id}\":f0" 20 | end 21 | end 22 | 23 | attr_reader :nodes, :arrows 24 | def initialize 25 | @stack = [] 26 | @node_index = 0 27 | @nodes = [] 28 | @arrows = [] 29 | end 30 | 31 | ## Terminal nodes 32 | %w{ 33 | BreakNode ContinueNode EmptyStatementNode FalseNode 34 | NullNode NumberNode ParameterNode RegexpNode ResolveNode StringNode 35 | ThisNode TrueNode 36 | }.each do |type| 37 | define_method(:"visit_#{type}") do |o| 38 | node = Node.new(@node_index += 1, [type, o.value].compact) 39 | add_arrow_for(node) 40 | @nodes << node 41 | end 42 | end 43 | ## End Terminal nodes 44 | 45 | # Single value nodes 46 | %w{ 47 | AssignExprNode BitwiseNotNode BlockNode DeleteNode ElementNode 48 | ExpressionStatementNode FunctionBodyNode LogicalNotNode ReturnNode 49 | ThrowNode TypeOfNode UnaryMinusNode UnaryPlusNode VoidNode 50 | }.each do |type| 51 | define_method(:"visit_#{type}") do |o| 52 | node = Node.new(@node_index += 1, [type]) 53 | add_arrow_for(node) 54 | @nodes << node 55 | @stack.push(node) 56 | o.value && o.value.accept(self) 57 | @stack.pop 58 | end 59 | end 60 | # End Single value nodes 61 | 62 | # Binary nodes 63 | %w{ 64 | AddNode BitAndNode BitOrNode BitXOrNode CaseClauseNode CommaNode 65 | DivideNode DoWhileNode EqualNode GreaterNode GreaterOrEqualNode InNode 66 | InstanceOfNode LeftShiftNode LessNode LessOrEqualNode LogicalAndNode 67 | LogicalOrNode ModulusNode MultiplyNode NotEqualNode NotStrictEqualNode 68 | OpAndEqualNode OpDivideEqualNode OpEqualNode OpLShiftEqualNode 69 | OpMinusEqualNode OpModEqualNode OpMultiplyEqualNode OpOrEqualNode 70 | OpPlusEqualNode OpRShiftEqualNode OpURShiftEqualNode OpXOrEqualNode 71 | RightShiftNode StrictEqualNode SubtractNode SwitchNode 72 | UnsignedRightShiftNode WhileNode WithNode 73 | }.each do |type| 74 | define_method(:"visit_#{type}") do |o| 75 | node = Node.new(@node_index += 1, [type]) 76 | add_arrow_for(node) 77 | @nodes << node 78 | @stack.push(node) 79 | o.left && o.left.accept(self) 80 | o.value && o.value.accept(self) 81 | @stack.pop 82 | end 83 | end 84 | # End Binary nodes 85 | 86 | # Array Value Nodes 87 | %w{ 88 | ArgumentsNode ArrayNode CaseBlockNode ConstStatementNode 89 | ObjectLiteralNode SourceElementsNode VarStatementNode 90 | }.each do |type| 91 | define_method(:"visit_#{type}") do |o| 92 | node = Node.new(@node_index += 1, [type]) 93 | add_arrow_for(node) 94 | @nodes << node 95 | @stack.push(node) 96 | o.value && o.value.each { |v| v && v.accept(self) } 97 | @stack.pop 98 | end 99 | end 100 | # END Array Value Nodes 101 | 102 | # Name and Value Nodes 103 | %w{ 104 | LabelNode PropertyNode GetterPropertyNode SetterPropertyNode VarDeclNode 105 | }.each do |type| 106 | define_method(:"visit_#{type}") do |o| 107 | node = Node.new(@node_index += 1, [type, o.name || 'NULL']) 108 | add_arrow_for(node) 109 | @nodes << node 110 | @stack.push(node) 111 | o.value && o.value.accept(self) 112 | @stack.pop 113 | end 114 | end 115 | # END Name and Value Nodes 116 | 117 | %w{ PostfixNode PrefixNode }.each do |type| 118 | define_method(:"visit_#{type}") do |o| 119 | node = Node.new(@node_index += 1, [type, o.value]) 120 | add_arrow_for(node) 121 | @nodes << node 122 | @stack.push(node) 123 | o.operand && o.operand.accept(self) 124 | @stack.pop 125 | end 126 | end 127 | 128 | def visit_ForNode(o) 129 | node = Node.new(@node_index += 1, ['ForNode']) 130 | add_arrow_for(node) 131 | @nodes << node 132 | @stack.push(node) 133 | [:init, :test, :counter, :value].each do |method| 134 | o.send(method) && o.send(method).accept(self) 135 | end 136 | @stack.pop 137 | end 138 | 139 | %w{ IfNode ConditionalNode }.each do |type| 140 | define_method(:"visit_#{type}") do |o| 141 | node = Node.new(@node_index += 1, [type]) 142 | add_arrow_for(node) 143 | @nodes << node 144 | @stack.push(node) 145 | [:conditions, :value, :else].each do |method| 146 | o.send(method) && o.send(method).accept(self) 147 | end 148 | @stack.pop 149 | end 150 | end 151 | 152 | def visit_ForInNode(o) 153 | node = Node.new(@node_index += 1, ['ForInNode']) 154 | add_arrow_for(node) 155 | @nodes << node 156 | @stack.push(node) 157 | [:left, :right, :value].each do |method| 158 | o.send(method) && o.send(method).accept(self) 159 | end 160 | @stack.pop 161 | end 162 | 163 | def visit_TryNode(o) 164 | node = Node.new(@node_index += 1, ['TryNode', o.catch_var || 'NULL']) 165 | add_arrow_for(node) 166 | @nodes << node 167 | @stack.push(node) 168 | [:value, :catch_block, :finally_block].each do |method| 169 | o.send(method) && o.send(method).accept(self) 170 | end 171 | @stack.pop 172 | end 173 | 174 | def visit_BracketAccessorNode(o) 175 | node = Node.new(@node_index += 1, ['BracketAccessorNode']) 176 | add_arrow_for(node) 177 | @nodes << node 178 | @stack.push(node) 179 | [:value, :accessor].each do |method| 180 | o.send(method) && o.send(method).accept(self) 181 | end 182 | @stack.pop 183 | end 184 | 185 | %w{ NewExprNode FunctionCallNode }.each do |type| 186 | define_method(:"visit_#{type}") do |o| 187 | node = Node.new(@node_index += 1, [type]) 188 | add_arrow_for(node) 189 | @nodes << node 190 | @stack.push(node) 191 | [:value, :arguments].each do |method| 192 | o.send(method) && o.send(method).accept(self) 193 | end 194 | @stack.pop 195 | end 196 | end 197 | 198 | %w{ FunctionExprNode FunctionDeclNode }.each do |type| 199 | define_method(:"visit_#{type}") do |o| 200 | node = Node.new(@node_index += 1, [type, o.value || 'NULL']) 201 | add_arrow_for(node) 202 | @nodes << node 203 | @stack.push(node) 204 | o.arguments.each { |a| a && a.accept(self) } 205 | o.function_body && o.function_body.accept(self) 206 | @stack.pop 207 | end 208 | end 209 | 210 | def visit_DotAccessorNode(o) 211 | node = Node.new(@node_index += 1, ['DotAccessorNode', o.accessor]) 212 | add_arrow_for(node) 213 | @nodes << node 214 | @stack.push(node) 215 | [:value].each do |method| 216 | o.send(method) && o.send(method).accept(self) 217 | end 218 | @stack.pop 219 | end 220 | 221 | private 222 | def add_arrow_for(node, label = nil) 223 | @arrows << Arrow.new(@stack.last, node, label) if @stack.length > 0 224 | end 225 | 226 | end 227 | end 228 | end 229 | -------------------------------------------------------------------------------- /lib/rkelly/visitors/sexp_visitor.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Visitors 3 | class SexpVisitor < Visitor 4 | def visit_NumberNode(o) 5 | [:lit, o.value] 6 | end 7 | 8 | def visit_RegexpNode(o) 9 | [:lit, o.value] 10 | end 11 | 12 | def visit_AssignExprNode(o) 13 | [:assign, super] 14 | end 15 | 16 | def visit_VarDeclNode(o) 17 | [ o.constant? ? :const_decl : :var_decl ] + super(o) 18 | end 19 | 20 | def visit_VarStatementNode(o) 21 | [:var, super] 22 | end 23 | 24 | def visit_PostfixNode(o) 25 | [:postfix, super, o.value] 26 | end 27 | 28 | def visit_PrefixNode(o) 29 | [:prefix, super, o.value] 30 | end 31 | 32 | def visit_DeleteNode(o) 33 | [:delete, super] 34 | end 35 | 36 | def visit_VoidNode(o) 37 | [:void, super] 38 | end 39 | 40 | def visit_TypeOfNode(o) 41 | [:typeof, super] 42 | end 43 | 44 | def visit_UnaryPlusNode(o) 45 | [:u_plus, super] 46 | end 47 | 48 | def visit_UnaryMinusNode(o) 49 | [:u_minus, super] 50 | end 51 | 52 | def visit_BitwiseNotNode(o) 53 | [:bitwise_not, super] 54 | end 55 | 56 | def visit_LogicalNotNode(o) 57 | [:not, super] 58 | end 59 | 60 | def visit_ConstStatementNode(o) 61 | [:const, super] 62 | end 63 | 64 | def visit_MultiplyNode(o) 65 | [:multiply, *super] 66 | end 67 | 68 | def visit_DivideNode(o) 69 | [:divide, *super] 70 | end 71 | 72 | def visit_ModulusNode(o) 73 | [:modulus, *super] 74 | end 75 | 76 | def visit_AddNode(o) 77 | [:add, *super] 78 | end 79 | 80 | def visit_LeftShiftNode(o) 81 | [:lshift, *super] 82 | end 83 | 84 | def visit_RightShiftNode(o) 85 | [:rshift, *super] 86 | end 87 | 88 | def visit_UnsignedRightShiftNode(o) 89 | [:urshift, *super] 90 | end 91 | 92 | def visit_SubtractNode(o) 93 | [:subtract, *super] 94 | end 95 | 96 | def visit_LessNode(o) 97 | [:less, *super] 98 | end 99 | 100 | def visit_GreaterNode(o) 101 | [:greater, *super] 102 | end 103 | 104 | def visit_LessOrEqualNode(o) 105 | [:less_or_equal, *super] 106 | end 107 | 108 | def visit_GreaterOrEqualNode(o) 109 | [:greater_or_equal, *super] 110 | end 111 | 112 | def visit_InstanceOfNode(o) 113 | [:instance_of, *super] 114 | end 115 | 116 | def visit_EqualNode(o) 117 | [:equal, *super] 118 | end 119 | 120 | def visit_NotEqualNode(o) 121 | [:not_equal, *super] 122 | end 123 | 124 | def visit_StrictEqualNode(o) 125 | [:strict_equal, *super] 126 | end 127 | 128 | def visit_NotStrictEqualNode(o) 129 | [:not_strict_equal, *super] 130 | end 131 | 132 | def visit_BitAndNode(o) 133 | [:bit_and, *super] 134 | end 135 | 136 | def visit_BitOrNode(o) 137 | [:bit_or, *super] 138 | end 139 | 140 | def visit_BitXOrNode(o) 141 | [:bit_xor, *super] 142 | end 143 | 144 | def visit_LogicalAndNode(o) 145 | [:and, *super] 146 | end 147 | 148 | def visit_LogicalOrNode(o) 149 | [:or, *super] 150 | end 151 | 152 | def visit_InNode(o) 153 | [:in, *super] 154 | end 155 | 156 | def visit_DoWhileNode(o) 157 | [:do_while, *super] 158 | end 159 | 160 | def visit_WhileNode(o) 161 | [:while, *super] 162 | end 163 | 164 | def visit_WithNode(o) 165 | [:with, *super] 166 | end 167 | 168 | def visit_CaseClauseNode(o) 169 | [:case, *super] 170 | end 171 | 172 | def visit_CaseBlockNode(o) 173 | [:case_block, super] 174 | end 175 | 176 | def visit_SwitchNode(o) 177 | [:switch, *super] 178 | end 179 | 180 | def visit_ForNode(o) 181 | [ :for, *super] 182 | end 183 | 184 | def visit_BlockNode(o) 185 | [:block, super] 186 | end 187 | 188 | def visit_IfNode(o) 189 | [:if, *super].compact 190 | end 191 | 192 | def visit_ConditionalNode(o) 193 | [:conditional, *super] 194 | end 195 | 196 | def visit_ForInNode(o) 197 | [ :for_in, *super] 198 | end 199 | 200 | def visit_TryNode(o) 201 | [ :try, *super] 202 | end 203 | 204 | def visit_EmptyStatementNode(o) 205 | [:empty] 206 | end 207 | 208 | def visit_FunctionBodyNode(o) 209 | [:func_body, super] 210 | end 211 | 212 | def visit_ResolveNode(o) 213 | [:resolve, o.value] 214 | end 215 | 216 | def visit_BracketAccessorNode(o) 217 | [:bracket_access, *super] 218 | end 219 | 220 | def visit_NewExprNode(o) 221 | [:new_expr, *super] 222 | end 223 | 224 | def visit_ParameterNode(o) 225 | [:param, o.value] 226 | end 227 | 228 | def visit_BreakNode(o) 229 | [:break, o.value].compact 230 | end 231 | 232 | def visit_ContinueNode(o) 233 | [:continue, o.value].compact 234 | end 235 | 236 | def visit_LabelNode(o) 237 | [:label ] + super 238 | end 239 | 240 | def visit_ThrowNode(o) 241 | [:throw, super] 242 | end 243 | 244 | def visit_ObjectLiteralNode(o) 245 | [:object, super] 246 | end 247 | 248 | def visit_PropertyNode(o) 249 | [ :property ] + super 250 | end 251 | 252 | def visit_GetterPropertyNode(o) 253 | [ :getter ] + super 254 | end 255 | 256 | def visit_SetterPropertyNode(o) 257 | [ :setter ] + super 258 | end 259 | 260 | def visit_ElementNode(o) 261 | [:element, super ] 262 | end 263 | 264 | def visit_ExpressionStatementNode(o) 265 | [:expression, super ] 266 | end 267 | 268 | def visit_OpEqualNode(o) 269 | [:op_equal, *super ] 270 | end 271 | 272 | def visit_OpPlusEqualNode(o) 273 | [:op_plus_equal, *super ] 274 | end 275 | 276 | def visit_OpMinusEqualNode(o) 277 | [:op_minus_equal, *super ] 278 | end 279 | 280 | def visit_OpMultiplyEqualNode(o) 281 | [:op_multiply_equal, *super ] 282 | end 283 | 284 | def visit_OpDivideEqualNode(o) 285 | [:op_divide_equal, *super] 286 | end 287 | 288 | def visit_OpLShiftEqualNode(o) 289 | [:op_lshift_equal, *super ] 290 | end 291 | 292 | def visit_OpRShiftEqualNode(o) 293 | [:op_rshift_equal, *super ] 294 | end 295 | 296 | def visit_OpURShiftEqualNode(o) 297 | [:op_urshift_equal, *super ] 298 | end 299 | 300 | def visit_OpAndEqualNode(o) 301 | [:op_and_equal, *super ] 302 | end 303 | 304 | def visit_OpXOrEqualNode(o) 305 | [:op_xor_equal, *super ] 306 | end 307 | 308 | def visit_OpOrEqualNode(o) 309 | [:op_or_equal, *super ] 310 | end 311 | 312 | def visit_OpModEqualNode(o) 313 | [:op_mod_equal, *super] 314 | end 315 | 316 | def visit_CommaNode(o) 317 | [:comma, *super] 318 | end 319 | 320 | def visit_FunctionCallNode(o) 321 | [:function_call, *super] 322 | end 323 | 324 | def visit_ArrayNode(o) 325 | [:array, super] 326 | end 327 | 328 | def visit_ThisNode(o) 329 | [:this] 330 | end 331 | 332 | def visit_ReturnNode(o) 333 | o.value ? [:return, super] : [:return] 334 | end 335 | 336 | def visit_FunctionExprNode(o) 337 | [ :func_expr, *super] 338 | end 339 | 340 | def visit_FunctionDeclNode(o) 341 | [ :func_decl, *super] 342 | end 343 | 344 | def visit_ArgumentsNode(o) 345 | [:args, super] 346 | end 347 | 348 | def visit_DotAccessorNode(o) 349 | [:dot_access, 350 | super, 351 | o.accessor 352 | ] 353 | end 354 | 355 | def visit_NullNode(o) 356 | [:nil] 357 | end 358 | 359 | def visit_StringNode(o) 360 | [:str, o.value] 361 | end 362 | 363 | def visit_FalseNode(o) 364 | [:false] 365 | end 366 | 367 | def visit_TrueNode(o) 368 | [:true] 369 | end 370 | 371 | end 372 | end 373 | end 374 | -------------------------------------------------------------------------------- /test/object/test_15_2_1_1.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/../helper" 2 | 3 | class Object_15_2_1_1_Test < ECMAScriptTestCase 4 | def test_null_value_of 5 | @runtime.execute(" 6 | var NULL_OBJECT = Object(null); 7 | assert_equal(NULL_OBJECT, (NULL_OBJECT).valueOf()); 8 | ") 9 | end 10 | 11 | def test_null_type_of 12 | js_assert_equal("'object'", 'typeof (Object(null))') 13 | end 14 | 15 | def test_undefined_value_of 16 | @runtime.execute(" 17 | var UNDEF_OBJECT = Object(void 0); 18 | assert_equal(UNDEF_OBJECT, (UNDEF_OBJECT).valueOf()); 19 | ") 20 | end 21 | 22 | def test_undefined_type_of 23 | js_assert_equal("'object'", 'typeof (Object(void 0))') 24 | end 25 | 26 | def test_true_type_of 27 | js_assert_equal("'object'", 'typeof (Object(true))') 28 | end 29 | 30 | def test_true_value_of 31 | js_assert_equal("true", 'Object(true).valueOf()') 32 | end 33 | 34 | def test_true_to_string 35 | @runtime.execute(" 36 | var MYOB = Object(true); 37 | MYOB.toString = Object.prototype.toString; 38 | assert_equal('[object Boolean]', MYOB.toString()); 39 | ") 40 | end 41 | 42 | def test_false_value_of 43 | js_assert_equal("false", 'Object(false).valueOf()') 44 | end 45 | 46 | def test_false_type_of 47 | js_assert_equal("'object'", 'typeof (Object(false))') 48 | end 49 | 50 | def test_false_to_string 51 | @runtime.execute(" 52 | var MYOB = Object(false); 53 | MYOB.toString = Object.prototype.toString; 54 | assert_equal('[object Boolean]', MYOB.toString()); 55 | ") 56 | end 57 | 58 | def test_0_value_of 59 | js_assert_equal("0", 'Object(0).valueOf()') 60 | end 61 | 62 | def test_0_type_of 63 | js_assert_equal("'object'", 'typeof Object(0)') 64 | end 65 | 66 | def test_0_to_string 67 | @runtime.execute(" 68 | var MYOB = Object(0); 69 | MYOB.toString = Object.prototype.toString; 70 | assert_equal('[object Number]', MYOB.toString()); 71 | ") 72 | end 73 | 74 | # Diverts from the ECMA. In ECMA, -0 is -0, not 0 75 | def test_minus_0_value_of 76 | js_assert_equal("0", 'Object(-0).valueOf()') 77 | end 78 | 79 | def test_minus_0_type_of 80 | js_assert_equal("'object'", 'typeof Object(-0)') 81 | end 82 | 83 | def test_minus_0_to_string 84 | @runtime.execute(" 85 | var MYOB = Object(-0); 86 | MYOB.toString = Object.prototype.toString; 87 | assert_equal('[object Number]', MYOB.toString()); 88 | ") 89 | end 90 | ## END Diversion. ;-) 91 | 92 | def test_1_value_of 93 | js_assert_equal("1", 'Object(1).valueOf()') 94 | end 95 | 96 | def test_1_type_of 97 | js_assert_equal("'object'", 'typeof Object(1)') 98 | end 99 | 100 | def test_1_to_string 101 | @runtime.execute(" 102 | var MYOB = Object(1); 103 | MYOB.toString = Object.prototype.toString; 104 | assert_equal('[object Number]', MYOB.toString()); 105 | ") 106 | end 107 | 108 | def test_minus_1_value_of 109 | js_assert_equal("-1", 'Object(-1).valueOf()') 110 | end 111 | 112 | def test_minus_1_type_of 113 | js_assert_equal("'object'", 'typeof Object(-1)') 114 | end 115 | 116 | def test_minus_1_to_string 117 | @runtime.execute(" 118 | var MYOB = Object(-1); 119 | MYOB.toString = Object.prototype.toString; 120 | assert_equal('[object Number]', MYOB.toString()); 121 | ") 122 | end 123 | 124 | def test_number_max_value_of 125 | js_assert_equal("1.797693134862315e308", 'Object(Number.MAX_VALUE).valueOf()') 126 | end 127 | 128 | def test_number_max_type_of 129 | js_assert_equal("'object'", 'typeof Object(Number.MAX_VALUE)') 130 | end 131 | 132 | def test_number_max_to_string 133 | @runtime.execute(" 134 | var MYOB = Object(Number.MAX_VALUE); 135 | MYOB.toString = Object.prototype.toString; 136 | assert_equal('[object Number]', MYOB.toString()); 137 | ") 138 | end 139 | 140 | def test_number_min_value_of 141 | js_assert_equal("1.0e-306", 'Object(Number.MIN_VALUE).valueOf()') 142 | end 143 | 144 | def test_number_min_type_of 145 | js_assert_equal("'object'", 'typeof Object(Number.MIN_VALUE)') 146 | end 147 | 148 | def test_number_min_to_string 149 | @runtime.execute(" 150 | var MYOB = Object(Number.MIN_VALUE); 151 | MYOB.toString = Object.prototype.toString; 152 | assert_equal('[object Number]', MYOB.toString()); 153 | ") 154 | end 155 | 156 | def test_number_positive_infinity_value 157 | js_assert_equal( 158 | "Number.POSITIVE_INFINITY", 159 | 'Object(Number.POSITIVE_INFINITY).valueOf()' 160 | ) 161 | end 162 | 163 | def test_number_positive_infinity_type 164 | js_assert_equal("'object'", 'typeof Object(Number.POSITIVE_INFINITY)') 165 | end 166 | 167 | def test_number_positive_infinity_string 168 | @runtime.execute(" 169 | var MYOB = Object(Number.POSITIVE_INFINITY); 170 | MYOB.toString = Object.prototype.toString; 171 | assert_equal('[object Number]', MYOB.toString()); 172 | ") 173 | end 174 | 175 | def test_number_negative_infinity_value 176 | js_assert_equal( 177 | "Number.NEGATIVE_INFINITY", 178 | 'Object(Number.NEGATIVE_INFINITY).valueOf()' 179 | ) 180 | end 181 | 182 | def test_number_negative_infinity_type 183 | js_assert_equal("'object'", 'typeof Object(Number.NEGATIVE_INFINITY)') 184 | end 185 | 186 | def test_number_negative_infinity_string 187 | @runtime.execute(" 188 | var MYOB = Object(Number.NEGATIVE_INFINITY); 189 | MYOB.toString = Object.prototype.toString; 190 | assert_equal('[object Number]', MYOB.toString()); 191 | ") 192 | end 193 | 194 | def test_nan_value 195 | js_assert_equal("Number.NaN", 'Object(Number.NaN).valueOf()') 196 | end 197 | 198 | def test_number_nan_type 199 | js_assert_equal("'object'", 'typeof Object(Number.NaN)') 200 | end 201 | 202 | def test_number_nan_string 203 | @runtime.execute(" 204 | var MYOB = Object(Number.NaN); 205 | MYOB.toString = Object.prototype.toString; 206 | assert_equal('[object Number]', MYOB.toString()); 207 | ") 208 | end 209 | 210 | def test_empty_string_value 211 | js_assert_equal("''", 'Object("").valueOf()') 212 | end 213 | 214 | def test_empty_string_type 215 | js_assert_equal("'object'", 'typeof Object("")') 216 | end 217 | 218 | def test_empty_string_to_string 219 | @runtime.execute(" 220 | var MYOB = Object(''); 221 | MYOB.toString = Object.prototype.toString; 222 | assert_equal('[object String]', MYOB.toString()); 223 | ") 224 | end 225 | 226 | def test_string_value 227 | js_assert_equal("'a string'", 'Object("a string").valueOf()') 228 | end 229 | 230 | def test_string_type 231 | js_assert_equal("'object'", 'typeof Object("a string")') 232 | end 233 | 234 | def test_string_to_string 235 | @runtime.execute(" 236 | var MYOB = Object('a string'); 237 | MYOB.toString = Object.prototype.toString; 238 | assert_equal('[object String]', MYOB.toString()); 239 | ") 240 | end 241 | 242 | def test_escape_string_value 243 | js_assert_equal("'\r\t\b\n\v\f'", "Object('\r\t\b\n\v\f').valueOf()") 244 | end 245 | 246 | def test_escape_string_type 247 | js_assert_equal("'object'", "typeof Object('\r\t\b\n\v\f')") 248 | end 249 | 250 | def test_escape_string_to_string 251 | @runtime.execute(" 252 | var MYOB = Object('\r\t\b\n\v\f'); 253 | MYOB.toString = Object.prototype.toString; 254 | assert_equal('[object String]', MYOB.toString()); 255 | ") 256 | end 257 | end 258 | -------------------------------------------------------------------------------- /test/test_tokenizer.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require File.dirname(__FILE__) + "/helper" 3 | 4 | class TokenizerTest < Test::Unit::TestCase 5 | def setup 6 | @tokenizer = RKelly::Tokenizer.new 7 | end 8 | 9 | { 10 | :space => " ", 11 | :tab => "\t", 12 | :form_feed => "\f", 13 | :vertical_tab => "\v", 14 | :no_break_space => [0x00A0].pack("U"), 15 | :ogham_space_mark => [0x1680].pack("U"), 16 | :en_quad => [0x2000].pack("U"), 17 | :em_quad => [0x2001].pack("U"), 18 | :en_space => [0x2002].pack("U"), 19 | :em_space => [0x2003].pack("U"), 20 | :three_per_em_space => [0x2004].pack("U"), 21 | :four_per_em_space => [0x2005].pack("U"), 22 | :six_per_em_space => [0x2006].pack("U"), 23 | :figure_space => [0x2007].pack("U"), 24 | :punctuation_space => [0x2008].pack("U"), 25 | :thin_space => [0x2009].pack("U"), 26 | :hair_space => [0x200a].pack("U"), 27 | :narrow_no_break_space => [0x202f].pack("U"), 28 | :medium_mathematical_space => [0x205f].pack("U"), 29 | :ideographic_space => [0x3000].pack("U"), 30 | 31 | # Line terminators 32 | :newline => "\n", 33 | :carriage_return => "\r", 34 | :line_separator => [0x2028].pack("U"), 35 | :paragraph_separator => [0x2029].pack("U"), 36 | }.each do |name, char| 37 | define_method(:"test_whitespace_#{name}") do 38 | assert_equal([[:S, char]], @tokenizer.tokenize(char)) 39 | end 40 | end 41 | 42 | def assert_tokens(expected, actual) 43 | assert_equal(expected, actual.select { |x| x[0] != :S }) 44 | end 45 | 46 | def test_comments 47 | tokens = @tokenizer.tokenize("/** Fooo */") 48 | assert_tokens([[:COMMENT, '/** Fooo */']], tokens) 49 | end 50 | 51 | def test_string_single_quote 52 | tokens = @tokenizer.tokenize("foo = 'hello world';") 53 | assert_tokens([ 54 | [:IDENT, 'foo'], 55 | ['=', '='], 56 | [:STRING, "'hello world'"], 57 | [';', ';'], 58 | ], tokens) 59 | end 60 | 61 | def test_string_double_quote 62 | tokens = @tokenizer.tokenize('foo = "hello world";') 63 | assert_tokens([ 64 | [:IDENT, 'foo'], 65 | ['=', '='], 66 | [:STRING, '"hello world"'], 67 | [';', ';'], 68 | ], tokens) 69 | end 70 | 71 | def test_number_parse 72 | tokens = @tokenizer.tokenize('3.') 73 | assert_tokens([[:NUMBER, 3.0]], tokens) 74 | 75 | tokens = @tokenizer.tokenize('3.e1') 76 | assert_tokens([[:NUMBER, 30]], tokens) 77 | 78 | tokens = @tokenizer.tokenize('.001') 79 | assert_tokens([[:NUMBER, 0.001]], tokens) 80 | 81 | tokens = @tokenizer.tokenize('3.e-1') 82 | assert_tokens([[:NUMBER, 0.30]], tokens) 83 | end 84 | 85 | def test_identifier 86 | tokens = @tokenizer.tokenize("foo") 87 | assert_tokens([[:IDENT, 'foo']], tokens) 88 | end 89 | 90 | def test_ignore_identifier 91 | tokens = @tokenizer.tokenize("0foo") 92 | assert_tokens([[:NUMBER, 0], [:IDENT, 'foo']], tokens) 93 | end 94 | 95 | def test_increment 96 | tokens = @tokenizer.tokenize("foo += 1;") 97 | assert_tokens([ 98 | [:IDENT, 'foo'], 99 | [:PLUSEQUAL, '+='], 100 | [:NUMBER, 1], 101 | [';', ';'], 102 | ], tokens) 103 | end 104 | 105 | def test_regular_expression 106 | tokens = @tokenizer.tokenize("foo = /=asdf/;") 107 | assert_tokens([ 108 | [:IDENT, 'foo'], 109 | ['=', '='], 110 | [:REGEXP, '/=asdf/'], 111 | [';', ';'], 112 | ], tokens) 113 | end 114 | 115 | def test_regular_expression_invalid 116 | tokens = @tokenizer.tokenize("foo = (1 / 2) / 3") 117 | assert_tokens([[:IDENT, "foo"], 118 | ["=", "="], 119 | ["(", "("], 120 | [:NUMBER, 1], 121 | ["/", "/"], 122 | [:NUMBER, 2], 123 | [")", ")"], 124 | ["/", "/"], 125 | [:NUMBER, 3] 126 | ], tokens) 127 | end 128 | 129 | def test_regular_expression_escape 130 | tokens = @tokenizer.tokenize('foo = /\/asdf/gi;') 131 | assert_tokens([ 132 | [:IDENT, 'foo'], 133 | ['=', '='], 134 | [:REGEXP, '/\/asdf/gi'], 135 | [';', ';'], 136 | ], tokens) 137 | end 138 | 139 | def test_regular_expression_with_slash_inside_charset 140 | tokens = @tokenizer.tokenize('foo = /[/]/;') 141 | assert_tokens([ 142 | [:IDENT, 'foo'], 143 | ['=', '='], 144 | [:REGEXP, '/[/]/'], 145 | [';', ';'], 146 | ], tokens) 147 | end 148 | 149 | def test_regular_expression_is_not_found_if_prev_token_implies_division 150 | {:IDENT => 'foo', 151 | :TRUE => 'true', 152 | :NUMBER => 1, 153 | ')' => ')', 154 | ']' => ']', 155 | '}' => '}'}.each do |name, value| 156 | tokens = @tokenizer.tokenize("#{value}/2/3") 157 | assert_tokens([ 158 | [name, value], 159 | ["/", "/"], 160 | [:NUMBER, 2], 161 | ["/", "/"], 162 | [:NUMBER, 3], 163 | ], tokens) 164 | end 165 | end 166 | 167 | def test_regular_expression_is_found_if_prev_token_is_non_literal_keyword 168 | {:RETURN => 'return', 169 | :THROW => 'throw'}.each do |name, value| 170 | tokens = @tokenizer.tokenize("#{value}/2/") 171 | assert_tokens([ 172 | [name, value], 173 | [:REGEXP, "/2/"], 174 | ], tokens) 175 | end 176 | end 177 | 178 | def test_regular_expression_is_not_found_if_block_comment_with_re_modifier 179 | tokens = @tokenizer.tokenize("/**/i") 180 | assert_tokens([ 181 | [:COMMENT, "/**/"], 182 | [:IDENT, "i"] 183 | ], tokens) 184 | end 185 | 186 | def test_comment_assign 187 | tokens = @tokenizer.tokenize("foo = /**/;") 188 | assert_tokens([ 189 | [:IDENT, 'foo'], 190 | ['=', '='], 191 | [:COMMENT, '/**/'], 192 | [';', ';'], 193 | ], tokens) 194 | 195 | tokens = @tokenizer.tokenize("foo = //;") 196 | assert_tokens([ 197 | [:IDENT, 'foo'], 198 | ['=', '='], 199 | [:COMMENT, '//;'], 200 | ], tokens) 201 | end 202 | 203 | def test_unicode_string 204 | tokens = @tokenizer.tokenize("foo = 'öäüõ';") 205 | assert_tokens([ 206 | [:IDENT, 'foo'], 207 | ['=', '='], 208 | [:STRING, "'öäüõ'"], 209 | [';', ';'], 210 | ], tokens) 211 | end 212 | 213 | def test_unicode_regex 214 | tokens = @tokenizer.tokenize("foo = /öäüõ/;") 215 | assert_tokens([ 216 | [:IDENT, 'foo'], 217 | ['=', '='], 218 | [:REGEXP, "/öäüõ/"], 219 | [';', ';'], 220 | ], tokens) 221 | end 222 | 223 | %w{ 224 | break case catch continue default delete do else finally for function 225 | if in instanceof new return switch this throw try typeof var void while 226 | with 227 | 228 | const true false null debugger 229 | }.each do |kw| 230 | define_method(:"test_keyword_#{kw}") do 231 | tokens = @tokenizer.tokenize(kw) 232 | assert_equal 1, tokens.length 233 | assert_equal([[kw.upcase.to_sym, kw]], tokens) 234 | end 235 | end 236 | 237 | %w{ 238 | class enum extends super export import 239 | }.each do |rw| 240 | define_method(:"test_future_reserved_word_#{rw}_is_reserved") do 241 | tokens = @tokenizer.tokenize(rw) 242 | assert_equal 1, tokens.length 243 | assert_equal([[:RESERVED, rw]], tokens) 244 | end 245 | end 246 | 247 | %w{ 248 | implements let private public yield 249 | interface package protected static 250 | }.each do |rw| 251 | define_method(:"test_future_reserved_word_#{rw}_is_identifier") do 252 | tokens = @tokenizer.tokenize(rw) 253 | assert_equal 1, tokens.length 254 | assert_equal([[:IDENT, rw]], tokens) 255 | end 256 | end 257 | 258 | { 259 | '==' => :EQEQ, 260 | '!=' => :NE, 261 | '===' => :STREQ, 262 | '!==' => :STRNEQ, 263 | '<=' => :LE, 264 | '>=' => :GE, 265 | '||' => :OR, 266 | '&&' => :AND, 267 | '++' => :PLUSPLUS, 268 | '--' => :MINUSMINUS, 269 | '<<' => :LSHIFT, 270 | '>>' => :RSHIFT, 271 | '>>>' => :URSHIFT, 272 | '+=' => :PLUSEQUAL, 273 | '-=' => :MINUSEQUAL, 274 | '*=' => :MULTEQUAL, 275 | 'null' => :NULL, 276 | 'true' => :TRUE, 277 | 'false' => :FALSE, 278 | }.each do |punctuator, sym| 279 | define_method(:"test_punctuator_#{sym}") do 280 | tokens = @tokenizer.tokenize(punctuator) 281 | assert_equal 1, tokens.length 282 | assert_equal([[sym, punctuator]], tokens) 283 | end 284 | end 285 | end 286 | -------------------------------------------------------------------------------- /lib/rkelly/tokenizer.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require 'rkelly/lexeme' 3 | require 'rkelly/char_range' 4 | require 'strscan' 5 | 6 | module RKelly 7 | class Tokenizer 8 | KEYWORDS = Hash[%w{ 9 | break case catch continue default delete do else finally for function 10 | if in instanceof new return switch this throw try typeof var void while 11 | with 12 | 13 | const true false null debugger 14 | }.map {|kw| [kw, kw.upcase.to_sym] }] 15 | 16 | # These 6 are always reserved in ECMAScript 5.1 17 | # Some others are only reserved in strict mode, but RKelly doesn't 18 | # differenciate between strict and non-strict mode code. 19 | # http://www.ecma-international.org/ecma-262/5.1/#sec-7.6.1.2 20 | # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Reserved_Words 21 | RESERVED = Hash[%w{ 22 | class enum export extends import super 23 | }.map {|kw| [kw, true] }] 24 | 25 | LITERALS = { 26 | # Punctuators 27 | '==' => :EQEQ, 28 | '!=' => :NE, 29 | '===' => :STREQ, 30 | '!==' => :STRNEQ, 31 | '<=' => :LE, 32 | '>=' => :GE, 33 | '||' => :OR, 34 | '&&' => :AND, 35 | '++' => :PLUSPLUS, 36 | '--' => :MINUSMINUS, 37 | '<<' => :LSHIFT, 38 | '<<=' => :LSHIFTEQUAL, 39 | '>>' => :RSHIFT, 40 | '>>=' => :RSHIFTEQUAL, 41 | '>>>' => :URSHIFT, 42 | '>>>='=> :URSHIFTEQUAL, 43 | '&=' => :ANDEQUAL, 44 | '%=' => :MODEQUAL, 45 | '^=' => :XOREQUAL, 46 | '|=' => :OREQUAL, 47 | '+=' => :PLUSEQUAL, 48 | '-=' => :MINUSEQUAL, 49 | '*=' => :MULTEQUAL, 50 | '/=' => :DIVEQUAL, 51 | } 52 | 53 | # Some keywords can be followed by regular expressions (eg, return and throw). 54 | # Others can be followed by division. 55 | KEYWORDS_THAT_IMPLY_DIVISION = { 56 | 'this' => true, 57 | 'true' => true, 58 | 'false' => true, 59 | 'null' => true, 60 | } 61 | 62 | KEYWORDS_THAT_IMPLY_REGEX = KEYWORDS.reject {|k,v| KEYWORDS_THAT_IMPLY_DIVISION[k] } 63 | 64 | SINGLE_CHARS_THAT_IMPLY_DIVISION = { 65 | ')' => true, 66 | ']' => true, 67 | '}' => true, 68 | } 69 | 70 | # Determine the method to use to measure String length in bytes, 71 | # because StringScanner#pos can only be set in bytes. 72 | # 73 | # - In Ruby 1.8 String#length returns always the string length 74 | # in bytes. 75 | # 76 | # - In Ruby 1.9+ String#length returns string length in 77 | # characters and we need to use String#bytesize instead. 78 | # 79 | BYTESIZE_METHOD = "".respond_to?(:bytesize) ? :bytesize : :length 80 | 81 | # JavaScript whitespace can consist of any Unicode space separator 82 | # characters. 83 | # 84 | # - In Ruby 1.9+ we can just use the [[:space:]] character class 85 | # and match them all. 86 | # 87 | # - In Ruby 1.8 we need a regex that identifies the specific bytes 88 | # in UTF-8 text. 89 | # 90 | WHITESPACE_REGEX = "".respond_to?(:encoding) ? /[[:space:]]+/m : %r{ 91 | ( 92 | \xC2\xA0 | # no-break space 93 | \xE1\x9A\x80 | # ogham space mark 94 | \xE2\x80\x80 | # en quad 95 | \xE2\x80\x81 | # em quad 96 | \xE2\x80\x82 | # en space 97 | \xE2\x80\x83 | # em space 98 | \xE2\x80\x84 | # three-per-em space 99 | \xE2\x80\x85 | # four-pre-em süace 100 | \xE2\x80\x86 | # six-per-em space 101 | \xE2\x80\x87 | # figure space 102 | \xE2\x80\x88 | # punctuation space 103 | \xE2\x80\x89 | # thin space 104 | \xE2\x80\x8A | # hair space 105 | \xE2\x80\xA8 | # line separator 106 | \xE2\x80\xA9 | # paragraph separator 107 | \xE2\x80\xAF | # narrow no-break space 108 | \xE2\x81\x9F | # medium mathematical space 109 | \xE3\x80\x80 # ideographic space 110 | )+ 111 | }mx 112 | 113 | def initialize(&block) 114 | @lexemes = Hash.new {|hash, key| hash[key] = [] } 115 | 116 | token(:COMMENT, /\/(?:\*(?:.)*?\*\/|\/[^\n]*)/m, ['/']) 117 | token(:STRING, /"(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)'/m, ["'", '"']) 118 | 119 | # Matcher for basic ASCII whitespace. 120 | # (Unicode whitespace is handled separately in #match_lexeme) 121 | # 122 | # Can't use just "\s" in regex, because in Ruby 1.8 this 123 | # doesn't include the vertical tab "\v" character 124 | token(:S, /[ \t\r\n\f\v]*/m, [" ", "\t", "\r", "\n", "\f", "\v"]) 125 | 126 | # A regexp to match floating point literals (but not integer literals). 127 | digits = ('0'..'9').to_a 128 | token(:NUMBER, /\d+\.\d*(?:[eE][-+]?\d+)?|\d+(?:\.\d*)?[eE][-+]?\d+|\.\d+(?:[eE][-+]?\d+)?/m, digits+['.']) do |type, value| 129 | value.gsub!(/\.(\D)/, '.0\1') if value =~ /\.\w/ 130 | value.gsub!(/\.$/, '.0') if value =~ /\.$/ 131 | value.gsub!(/^\./, '0.') if value =~ /^\./ 132 | [type, eval(value)] 133 | end 134 | token(:NUMBER, /0[xX][\da-fA-F]+|0[oO][0-7]+|0[0-7]*|\d+/, digits) do |type, value| 135 | [type, eval(value)] 136 | end 137 | 138 | word_chars = ('a'..'z').to_a + ('A'..'Z').to_a + ['_', '$'] 139 | token(:RAW_IDENT, /([_\$A-Za-z][_\$0-9A-Za-z]*)/, word_chars) do |type,value| 140 | if KEYWORDS[value] 141 | [KEYWORDS[value], value] 142 | elsif RESERVED[value] 143 | [:RESERVED, value] 144 | else 145 | [:IDENT, value] 146 | end 147 | end 148 | 149 | # To distinguish regular expressions from comments, we require that 150 | # regular expressions start with a non * character (ie, not look like 151 | # /*foo*/). Note that we can't depend on the length of the match to 152 | # correctly distinguish, since `/**/i` is longer if matched as a regular 153 | # expression than as matched as a comment. 154 | # Incidentally, we're also not matching empty regular expressions 155 | # (eg, // and //g). Here we could depend on match length and priority to 156 | # determine that these are actually comments, but it turns out to be 157 | # easier to not match them in the first place. 158 | token(:REGEXP, %r{ 159 | / (?# beginning ) 160 | 161 | (?: 162 | [^\r\n\[/\\]+ (?# any char except \r \n [ / \ ) 163 | | 164 | \\ [^\r\n] (?# escape sequence ) 165 | | 166 | \[ (?:[^\]\\]|\\.)* \] (?# [...] can contain any char including / ) 167 | (?# only \ and ] have to be escaped here ) 168 | )+ 169 | 170 | /[gimuy]* (?# ending + modifiers ) 171 | }x, ['/']) 172 | 173 | literal_chars = LITERALS.keys.map {|k| k.slice(0,1) }.uniq 174 | literal_regex = Regexp.new(LITERALS.keys.sort_by { |x| 175 | x.length 176 | }.reverse.map { |x| "#{x.gsub(/([|+*^])/, '\\\\\1')}" }.join('|')) 177 | token(:LITERALS, literal_regex, literal_chars) do |type, value| 178 | [LITERALS[value], value] 179 | end 180 | 181 | symbols = ('!'..'/').to_a + (':'..'@').to_a + ('['..'^').to_a + ['`'] + ('{'..'~').to_a 182 | token(:SINGLE_CHAR, /./, symbols) do |type, value| 183 | [value, value] 184 | end 185 | end 186 | 187 | def tokenize(string) 188 | raw_tokens(string).map { |x| x.to_racc_token } 189 | end 190 | 191 | def raw_tokens(string) 192 | scanner = StringScanner.new(string) 193 | tokens = [] 194 | range = CharRange::EMPTY 195 | accepting_regexp = true 196 | while !scanner.eos? 197 | token = match_lexeme(scanner, accepting_regexp) 198 | 199 | if token.name != :S 200 | accepting_regexp = followable_by_regex(token) 201 | end 202 | 203 | scanner.pos += token.value.send(BYTESIZE_METHOD) 204 | token.range = range = range.next(token.value) 205 | tokens << token 206 | end 207 | tokens 208 | end 209 | 210 | private 211 | 212 | # Returns the token of the first matching lexeme 213 | def match_lexeme(scanner, accepting_regexp) 214 | @lexemes[scanner.peek(1)].each do |lexeme| 215 | next if lexeme.name == :REGEXP && !accepting_regexp 216 | 217 | token = lexeme.match(scanner) 218 | return token if token 219 | end 220 | 221 | # When some other character encountered, try to match it as 222 | # whitespace, as in JavaScript whitespace can contain any 223 | # Unicode whitespace character. 224 | if str = scanner.check(WHITESPACE_REGEX) 225 | return Token.new(:S, str) 226 | end 227 | end 228 | 229 | # Registers a lexeme and maps it to all the characters it can 230 | # begin with. So later when scanning the source we only need to 231 | # match those lexemes that can begin with the character we're at. 232 | def token(name, pattern, chars, &block) 233 | lexeme = Lexeme.new(name, pattern, &block) 234 | chars.each do |c| 235 | @lexemes[c] << lexeme 236 | end 237 | end 238 | 239 | def followable_by_regex(current_token) 240 | case current_token.name 241 | when :RAW_IDENT 242 | KEYWORDS_THAT_IMPLY_REGEX[current_token.value] 243 | when :NUMBER 244 | false 245 | when :SINGLE_CHAR 246 | !SINGLE_CHARS_THAT_IMPLY_DIVISION[current_token.value] 247 | else 248 | true 249 | end 250 | end 251 | end 252 | end 253 | -------------------------------------------------------------------------------- /lib/rkelly/visitors/ecma_visitor.rb: -------------------------------------------------------------------------------- 1 | module RKelly 2 | module Visitors 3 | class ECMAVisitor < Visitor 4 | def initialize 5 | @indent = 0 6 | end 7 | 8 | def visit_ParentheticalNode(o) 9 | "(#{o.value.accept(self)})" 10 | end 11 | 12 | def visit_SourceElementsNode(o) 13 | o.value.map { |x| "#{indent}#{x.accept(self)}" }.join("\n") 14 | end 15 | 16 | def visit_VarStatementNode(o) 17 | "var #{o.value.map { |x| x.accept(self) }.join(', ')};" 18 | end 19 | 20 | def visit_ConstStatementNode(o) 21 | "const #{o.value.map { |x| x.accept(self) }.join(', ')};" 22 | end 23 | 24 | def visit_VarDeclNode(o) 25 | "#{o.name}#{o.value ? o.value.accept(self) : nil}" 26 | end 27 | 28 | def visit_AssignExprNode(o) 29 | " = #{o.value.accept(self)}" 30 | end 31 | 32 | def visit_NumberNode(o) 33 | o.value.to_s 34 | end 35 | 36 | def visit_ForNode(o) 37 | init = o.init ? o.init.accept(self) : ';' 38 | init << ';' unless init.end_with? ';' # make sure it has a ; 39 | test = o.test ? o.test.accept(self) : '' 40 | counter = o.counter ? o.counter.accept(self) : '' 41 | "for(#{init} #{test}; #{counter}) #{o.value.accept(self)}" 42 | end 43 | 44 | def visit_LessNode(o) 45 | "#{o.left.accept(self)} < #{o.value.accept(self)}" 46 | end 47 | 48 | def visit_ResolveNode(o) 49 | o.value 50 | end 51 | 52 | def visit_PostfixNode(o) 53 | "#{o.operand.accept(self)}#{o.value}" 54 | end 55 | 56 | def visit_PrefixNode(o) 57 | "#{o.value}#{o.operand.accept(self)}" 58 | end 59 | 60 | def visit_BlockNode(o) 61 | @indent += 1 62 | "{\n#{o.value.accept(self)}\n#{@indent -=1; indent}}" 63 | end 64 | 65 | def visit_ExpressionStatementNode(o) 66 | "#{o.value.accept(self)};" 67 | end 68 | 69 | def visit_OpEqualNode(o) 70 | "#{o.left.accept(self)} = #{o.value.accept(self)}" 71 | end 72 | 73 | def visit_FunctionCallNode(o) 74 | "#{o.value.accept(self)}(#{o.arguments.accept(self)})" 75 | end 76 | 77 | def visit_ArgumentsNode(o) 78 | o.value.map { |x| x.accept(self) }.join(', ') 79 | end 80 | 81 | def visit_StringNode(o) 82 | o.value 83 | end 84 | 85 | def visit_NullNode(o) 86 | "null" 87 | end 88 | 89 | def visit_FunctionDeclNode(o) 90 | "#{indent}function #{o.value}" + function_params_and_body(o) 91 | end 92 | 93 | def visit_ParameterNode(o) 94 | o.value 95 | end 96 | 97 | def visit_FunctionBodyNode(o) 98 | @indent += 1 99 | "{\n#{o.value.accept(self)}\n#{@indent -=1; indent}}" 100 | end 101 | 102 | def visit_BreakNode(o) 103 | "break" + (o.value ? " #{o.value}" : '') + ';' 104 | end 105 | 106 | def visit_ContinueNode(o) 107 | "continue" + (o.value ? " #{o.value}" : '') + ';' 108 | end 109 | 110 | def visit_TrueNode(o) 111 | "true" 112 | end 113 | 114 | def visit_FalseNode(o) 115 | "false" 116 | end 117 | 118 | def visit_EmptyStatementNode(o) 119 | ';' 120 | end 121 | 122 | def visit_RegexpNode(o) 123 | o.value 124 | end 125 | 126 | def visit_DotAccessorNode(o) 127 | "#{o.value.accept(self)}.#{o.accessor}" 128 | end 129 | 130 | def visit_ThisNode(o) 131 | "this" 132 | end 133 | 134 | def visit_BitwiseNotNode(o) 135 | "~#{o.value.accept(self)}" 136 | end 137 | 138 | def visit_DeleteNode(o) 139 | "delete #{o.value.accept(self)}" 140 | end 141 | 142 | def visit_ArrayNode(o) 143 | "[#{o.value.map { |x| x ? x.accept(self) : '' }.join(', ')}]" 144 | end 145 | 146 | def visit_ElementNode(o) 147 | o.value.accept(self) 148 | end 149 | 150 | def visit_LogicalNotNode(o) 151 | "!#{o.value.accept(self)}" 152 | end 153 | 154 | def visit_UnaryMinusNode(o) 155 | "-#{o.value.accept(self)}" 156 | end 157 | 158 | def visit_UnaryPlusNode(o) 159 | "+#{o.value.accept(self)}" 160 | end 161 | 162 | def visit_ReturnNode(o) 163 | "return" + (o.value ? " #{o.value.accept(self)}" : '') + ';' 164 | end 165 | 166 | def visit_ThrowNode(o) 167 | "throw #{o.value.accept(self)};" 168 | end 169 | 170 | def visit_TypeOfNode(o) 171 | "typeof #{o.value.accept(self)}" 172 | end 173 | 174 | def visit_VoidNode(o) 175 | "void(#{o.value.accept(self)})" 176 | end 177 | 178 | [ 179 | [:Add, '+'], 180 | [:BitAnd, '&'], 181 | [:BitOr, '|'], 182 | [:BitXOr, '^'], 183 | [:Divide, '/'], 184 | [:Equal, '=='], 185 | [:Greater, '>'], 186 | [:GreaterOrEqual, '>='], 187 | [:In, 'in'], 188 | [:InstanceOf, 'instanceof'], 189 | [:LeftShift, '<<'], 190 | [:LessOrEqual, '<='], 191 | [:LogicalAnd, '&&'], 192 | [:LogicalOr, '||'], 193 | [:Modulus, '%'], 194 | [:Multiply, '*'], 195 | [:NotEqual, '!='], 196 | [:NotStrictEqual, '!=='], 197 | [:OpAndEqual, '&='], 198 | [:OpDivideEqual, '/='], 199 | [:OpLShiftEqual, '<<='], 200 | [:OpMinusEqual, '-='], 201 | [:OpModEqual, '%='], 202 | [:OpMultiplyEqual, '*='], 203 | [:OpOrEqual, '|='], 204 | [:OpPlusEqual, '+='], 205 | [:OpRShiftEqual, '>>='], 206 | [:OpURShiftEqual, '>>>='], 207 | [:OpXOrEqual, '^='], 208 | [:RightShift, '>>'], 209 | [:StrictEqual, '==='], 210 | [:Subtract, '-'], 211 | [:UnsignedRightShift, '>>>'], 212 | ].each do |name,op| 213 | define_method(:"visit_#{name}Node") do |o| 214 | "#{o.left.accept(self)} #{op} #{o.value.accept(self)}" 215 | end 216 | end 217 | 218 | def visit_WhileNode(o) 219 | "while(#{o.left.accept(self)}) #{o.value.accept(self)}" 220 | end 221 | 222 | def visit_SwitchNode(o) 223 | "switch(#{o.left.accept(self)}) #{o.value.accept(self)}" 224 | end 225 | 226 | def visit_CaseBlockNode(o) 227 | @indent += 1 228 | "{\n" + (o.value ? o.value.map { |x| x.accept(self) }.join('') : '') + 229 | "#{@indent -=1; indent}}" 230 | end 231 | 232 | def visit_CaseClauseNode(o) 233 | if o.left 234 | case_code = "#{indent}case #{o.left.accept(self)}:\n" 235 | else 236 | case_code = "#{indent}default:\n" 237 | end 238 | @indent += 1 239 | case_code += "#{o.value.accept(self)}\n" 240 | @indent -= 1 241 | case_code 242 | end 243 | 244 | def visit_DoWhileNode(o) 245 | "do #{o.left.accept(self)} while(#{o.value.accept(self)});" 246 | end 247 | 248 | def visit_WithNode(o) 249 | "with(#{o.left.accept(self)}) #{o.value.accept(self)}" 250 | end 251 | 252 | def visit_LabelNode(o) 253 | "#{o.name}: #{o.value.accept(self)}" 254 | end 255 | 256 | def visit_ObjectLiteralNode(o) 257 | @indent += 1 258 | lit = "{" + (o.value.length > 0 ? "\n" : ' ') + 259 | o.value.map { |x| "#{indent}#{x.accept(self)}" }.join(",\n") + 260 | (o.value.length > 0 ? "\n" : '') + '}' 261 | @indent -= 1 262 | lit 263 | end 264 | 265 | def visit_PropertyNode(o) 266 | "#{o.name}: #{o.value.accept(self)}" 267 | end 268 | 269 | def visit_GetterPropertyNode(o) 270 | "get #{o.name}" + function_params_and_body(o.value) 271 | end 272 | 273 | def visit_SetterPropertyNode(o) 274 | "set #{o.name}" + function_params_and_body(o.value) 275 | end 276 | 277 | def visit_FunctionExprNode(o) 278 | name = (o.value == 'function') ? '' : ' '+o.value 279 | "function" + name + function_params_and_body(o) 280 | end 281 | 282 | # Helper for all the various function nodes 283 | def function_params_and_body(o) 284 | "(#{o.arguments.map { |x| x.accept(self) }.join(', ')}) " + 285 | "#{o.function_body.accept(self)}" 286 | end 287 | 288 | def visit_CommaNode(o) 289 | "#{o.left.accept(self)}, #{o.value.accept(self)}" 290 | end 291 | 292 | def visit_IfNode(o) 293 | "if(#{o.conditions.accept(self)}) #{o.value.accept(self)}" + 294 | (o.else ? " else #{o.else.accept(self)}" : '') 295 | end 296 | 297 | def visit_ConditionalNode(o) 298 | "#{o.conditions.accept(self)} ? #{o.value.accept(self)} : " + 299 | "#{o.else.accept(self)}" 300 | end 301 | 302 | def visit_ForInNode(o) 303 | var = o.left.is_a?(RKelly::Nodes::VarDeclNode) ? 'var ' : '' 304 | "for(#{var}#{o.left.accept(self)} in #{o.right.accept(self)}) " + 305 | "#{o.value.accept(self)}" 306 | end 307 | 308 | def visit_TryNode(o) 309 | "try #{o.value.accept(self)}" + 310 | (o.catch_block ? " catch(#{o.catch_var}) #{o.catch_block.accept(self)}" : '') + 311 | (o.finally_block ? " finally #{o.finally_block.accept(self)}" : '') 312 | end 313 | 314 | def visit_BracketAccessorNode(o) 315 | "#{o.value.accept(self)}[#{o.accessor.accept(self)}]" 316 | end 317 | 318 | def visit_NewExprNode(o) 319 | "new #{o.value.accept(self)}(#{o.arguments.accept(self)})" 320 | end 321 | 322 | private 323 | def indent; ' ' * @indent * 2; end 324 | end 325 | end 326 | end 327 | --------------------------------------------------------------------------------