add rkelly, a javascript parsing library. this version fixes several bugs in the abandoned gem, see https://github.com/tenderlove/rkelly/pull/6

git-svn-id: file:///home/svn/framework3/trunk@12815 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
James Lee 2011-06-02 06:31:34 +00:00
parent df56bff027
commit 7458abc8b3
62 changed files with 17652 additions and 0 deletions

3
lib/rkelly/constants.rb Normal file
View File

@ -0,0 +1,3 @@
module RKelly
VERSION = '1.0.3'
end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

14
lib/rkelly/js.rb Normal file
View File

@ -0,0 +1,14 @@
require 'rkelly/js/nan'
require 'rkelly/js/property'
require 'rkelly/js/base'
require 'rkelly/js/global_object'
require 'rkelly/js/object'
require 'rkelly/js/object_prototype'
require 'rkelly/js/function_prototype'
require 'rkelly/js/array'
require 'rkelly/js/boolean'
require 'rkelly/js/math'
require 'rkelly/js/number'
require 'rkelly/js/string'
require 'rkelly/js/scope'
require 'rkelly/js/function'

15
lib/rkelly/js/array.rb Normal file
View File

@ -0,0 +1,15 @@
module RKelly
module JS
class Array < Base
class << self
def create(*args)
self.new(*args)
end
end
def initialize(*args)
super()
end
end
end
end

91
lib/rkelly/js/base.rb Normal file
View File

@ -0,0 +1,91 @@
module RKelly
module JS
class Base
attr_reader :properties, :return, :value
def initialize
@properties = Hash.new { |h,k|
h[k] = Property.new(k, :undefined, self)
}
@return = nil
@returned = false
@value = self
self['Class'] = self.class.to_s.split('::').last
end
def [](name)
return self.properties[name] if has_property?(name)
if self.properties['prototype'].value != :undefined
self.properties['prototype'].value[name]
else
RKelly::Runtime::UNDEFINED
end
end
def []=(name, value)
return unless can_put?(name)
if has_property?(name)
self.properties[name].value = value
else
self.properties[name] = Property.new(name, value, self)
end
end
def can_put?(name)
if !has_property?(name)
return true if self.properties['prototype'].nil?
return true if self.properties['prototype'].value.nil?
return true if self.properties['prototype'].value == :undefined
return self.properties['prototype'].value.can_put?(name)
end
!self.properties[name].read_only?
end
def has_property?(name)
return true if self.properties.has_key?(name)
return false if self.properties['prototype'].nil?
return false if self.properties['prototype'].value.nil?
return false if self.properties['prototype'].value == :undefined
self.properties['prototype'].value.has_property?(name)
end
def delete(name)
return true unless has_property?(name)
return false if self.properties[name].dont_delete?
self.properties.delete(name)
true
end
def default_value(hint)
case hint
when 'Number'
value_of = self['valueOf']
if value_of.function || value_of.value.is_a?(RKelly::JS::Function)
return value_of
end
to_string = self['toString']
if to_string.function || to_string.value.is_a?(RKelly::JS::Function)
return to_string
end
end
end
def return=(value)
@returned = true
@return = value
end
def returned?; @returned; end
private
def unbound_method(name, object_id = nil, &block)
name = "#{name}_#{self.class.to_s.split('::').last}_#{object_id}"
unless RKelly::JS::Base.instance_methods.include?(name.to_sym)
RKelly::JS::Base.class_eval do
define_method(name, &block)
end
end
RKelly::JS::Base.instance_method(name)
end
end
end
end

21
lib/rkelly/js/boolean.rb Normal file
View File

@ -0,0 +1,21 @@
module RKelly
module JS
class Boolean < Base
class << self
def create(*args)
return false if args.length == 0
self.new(args.first)
end
end
def initialize(*args)
super()
value = args.first.nil? ? false : args.first
self['valueOf'] = value
self['valueOf'].function = lambda {
value
}
self['toString'] = args.first.to_s
end
end
end
end

39
lib/rkelly/js/function.rb Normal file
View File

@ -0,0 +1,39 @@
module RKelly
module JS
class Function < Base
class << self
def create(*args)
if args.length > 0
parser = RKelly::Parser.new
body = args.pop
tree = parser.parse("function x(#{args.join(',')}) { #{body} }")
func = tree.value.first
self.new(func.function_body, func.arguments)
else
self.new
end
end
end
attr_reader :body, :arguments
def initialize(body = nil, arguments = [])
super()
@body = body
@arguments = arguments
self['prototype'] = JS::FunctionPrototype.new(self)
self['toString'] = :undefined
self['length'] = arguments.length
end
def js_call(scope_chain, *params)
arguments.each_with_index { |name, i|
scope_chain[name.value] = params[i] || RKelly::Runtime::UNDEFINED
}
function_visitor = RKelly::Visitors::FunctionVisitor.new(scope_chain)
eval_visitor = RKelly::Visitors::EvaluationVisitor.new(scope_chain)
body.accept(function_visitor) if body
body.accept(eval_visitor) if body
end
end
end
end

View File

@ -0,0 +1,15 @@
module RKelly
module JS
# This is the object protytpe
# ECMA-262 15.2.4
class FunctionPrototype < ObjectPrototype
def initialize(function)
super()
self['Class'] = 'Object'
self['constructor'] = function
self['arguments'].value = nil
end
end
end
end

View File

@ -0,0 +1,52 @@
module RKelly
module JS
class GlobalObject < Base
def initialize
super
self['class'] = 'GlobalObject'
self['NaN'] = JS::NaN.new
self['NaN'].attributes << :dont_enum
self['NaN'].attributes << :dont_delete
self['Infinity'] = 1.0/0.0
self['Infinity'].attributes << :dont_enum
self['Infinity'].attributes << :dont_delete
self['undefined'] = :undefined
self['undefined'].attributes << :dont_enum
self['undefined'].attributes << :dont_delete
self['Array'] = JS::Array.new
self['Array'].function = lambda { |*args|
JS::Array.create(*args)
}
self['Object'] = JS::Object.new
self['Object'].function = lambda { |*args|
JS::Object.create(*args)
}
self['Math'] = JS::Math.new
self['Function'] = :undefined
self['Function'].function = lambda { |*args|
JS::Function.create(*args)
}
self['Number'] = JS::Number.new
self['Number'].function = lambda { |*args|
JS::Number.create(*args)
}
self['Boolean'] = JS::Boolean.new
self['Boolean'].function = lambda { |*args|
JS::Boolean.create(*args)
}
self['String'] = JS::String.new('')
self['String'].function = lambda { |*args|
JS::String.create(*args)
}
end
end
end
end

10
lib/rkelly/js/math.rb Normal file
View File

@ -0,0 +1,10 @@
module RKelly
module JS
class Math < Base
def initialize
super
self['PI'] = ::Math::PI
end
end
end
end

18
lib/rkelly/js/nan.rb Normal file
View File

@ -0,0 +1,18 @@
module RKelly
module JS
# Class to represent Not A Number
# In Ruby NaN != NaN, but in JS, NaN == NaN
class NaN < ::Numeric
def ==(other)
other.respond_to?(:nan?) && other.nan?
end
def nan?
true
end
def +(o); self; end
def -(o); self; end
end
end
end

22
lib/rkelly/js/number.rb Normal file
View File

@ -0,0 +1,22 @@
module RKelly
module JS
class Number < Base
class << self
def create(*args)
self.new(args.first || 0)
end
end
def initialize(value = 0)
super()
self['MAX_VALUE'] = 1.797693134862315e+308
self['MIN_VALUE'] = 1.0e-306
self['NaN'] = JS::NaN.new
self['POSITIVE_INFINITY'] = 1.0/0.0
self['NEGATIVE_INFINITY'] = -1.0/0.0
self['valueOf'] = lambda { value }
self['toString'] = value.to_s
end
end
end
end

30
lib/rkelly/js/object.rb Normal file
View File

@ -0,0 +1,30 @@
module RKelly
module JS
class Object < Base
attr_reader :value
class << self
def create(*args)
arg = args.first
return self.new if arg.nil? || arg == :undefined
case arg
when true, false
JS::Boolean.new(arg)
when Numeric
JS::Number.new(arg)
when ::String
JS::String.new(arg)
else
self.new(arg)
end
end
end
def initialize(*args)
super()
self['prototype'] = JS::ObjectPrototype.new
self['valueOf'] = lambda { args.first || self }
self['valueOf'].function = lambda { args.first || self }
end
end
end
end

View File

@ -0,0 +1,14 @@
module RKelly
module JS
# This is the object protytpe
# ECMA-262 15.2.4
class ObjectPrototype < Base
def initialize
super
self['toString'].function = unbound_method(:toString) do
"[object #{self['Class'].value}]"
end
end
end
end
end

20
lib/rkelly/js/property.rb Normal file
View File

@ -0,0 +1,20 @@
module RKelly
module JS
class Property
attr_accessor :name, :value, :attributes, :function, :binder
def initialize(name, value, binder = nil, function = nil, attributes = [])
@name = name
@value = value
@binder = binder
@function = function
@attributes = attributes
end
[:read_only, :dont_enum, :dont_delete, :internal].each do |property|
define_method(:"#{property}?") do
self.attributes.include?(property)
end
end
end
end
end

6
lib/rkelly/js/scope.rb Normal file
View File

@ -0,0 +1,6 @@
module RKelly
module JS
class Scope < Base
end
end
end

21
lib/rkelly/js/string.rb Normal file
View File

@ -0,0 +1,21 @@
module RKelly
module JS
class String < Base
class << self
def create(*args)
self.new(args.first || '')
end
end
def initialize(value)
super()
self['valueOf'] = value
self['valueOf'].function = lambda { value }
self['toString'] = value
self['fromCharCode'] = unbound_method(:fromCharCode) { |*args|
args.map { |x| x.chr }.join
}
end
end
end
end

18
lib/rkelly/lexeme.rb Normal file
View File

@ -0,0 +1,18 @@
require 'rkelly/token'
module RKelly
class Lexeme
attr_reader :name, :pattern
def initialize(name, pattern, &block)
@name = name
@pattern = pattern
@block = block
end
def match(string)
match = pattern.match(string)
return Token.new(name, match.to_s, &@block) if match
match
end
end
end

25
lib/rkelly/nodes.rb Normal file
View File

@ -0,0 +1,25 @@
require 'rkelly/nodes/node'
require 'rkelly/nodes/function_expr_node'
require 'rkelly/nodes/binary_node'
require 'rkelly/nodes/bracket_accessor_node'
require 'rkelly/nodes/case_clause_node'
require 'rkelly/nodes/comma_node'
require 'rkelly/nodes/conditional_node'
require 'rkelly/nodes/dot_accessor_node'
require 'rkelly/nodes/for_in_node'
require 'rkelly/nodes/for_node'
require 'rkelly/nodes/function_call_node'
require 'rkelly/nodes/function_decl_node'
require 'rkelly/nodes/function_expr_node'
require 'rkelly/nodes/if_node'
require 'rkelly/nodes/label_node'
require 'rkelly/nodes/new_expr_node'
require 'rkelly/nodes/not_strict_equal_node'
require 'rkelly/nodes/op_equal_node'
require 'rkelly/nodes/postfix_node'
require 'rkelly/nodes/prefix_node'
require 'rkelly/nodes/property_node'
require 'rkelly/nodes/resolve_node'
require 'rkelly/nodes/strict_equal_node'
require 'rkelly/nodes/try_node'
require 'rkelly/nodes/var_decl_node'

View File

@ -0,0 +1,18 @@
module RKelly
module Nodes
class BinaryNode < Node
attr_reader :left
def initialize(left, right)
super(right)
@left = left
end
end
%w[Subtract LessOrEqual GreaterOrEqual Add Multiply While NotEqual
DoWhile Switch LogicalAnd UnsignedRightShift Modulus While
NotStrictEqual Less With In Greater BitOr StrictEqual LogicalOr
BitXOr LeftShift Equal BitAnd InstanceOf Divide RightShift].each do |node|
eval "class #{node}Node < BinaryNode; end"
end
end
end

View File

@ -0,0 +1,11 @@
module RKelly
module Nodes
class BracketAccessorNode < Node
attr_reader :accessor
def initialize(resolve, accessor)
super(resolve)
@accessor = accessor
end
end
end
end

View File

@ -0,0 +1,11 @@
require 'rkelly/nodes/binary_node'
module RKelly
module Nodes
class CaseClauseNode < BinaryNode
def initialize(left, src = SourceElementsNode.new([]))
super
end
end
end
end

View File

@ -0,0 +1,11 @@
module RKelly
module Nodes
class CommaNode < Node
attr_reader :left
def initialize(left, right)
super(right)
@left = left
end
end
end
end

View File

@ -0,0 +1,11 @@
require 'rkelly/nodes/if_node'
module RKelly
module Nodes
class ConditionalNode < IfNode
def initialize(test, true_block, else_block)
super
end
end
end
end

View File

@ -0,0 +1,11 @@
module RKelly
module Nodes
class DotAccessorNode < Node
attr_accessor :accessor
def initialize(resolve, accessor)
super(resolve)
@accessor = accessor
end
end
end
end

View File

@ -0,0 +1,12 @@
module RKelly
module Nodes
class ForInNode < Node
attr_reader :left, :right
def initialize(left, right, block)
super(block)
@left = left
@right = right
end
end
end
end

View File

@ -0,0 +1,13 @@
module RKelly
module Nodes
class ForNode < Node
attr_reader :init, :test, :counter
def initialize(init, test, counter, body)
super(body)
@init = init
@test = test
@counter = counter
end
end
end
end

View File

@ -0,0 +1,16 @@
module RKelly
module Nodes
class FunctionCallNode < Node
attr_reader :arguments
def initialize(value, arguments)
super(value)
@arguments = arguments
end
def ==(other)
super && @arguments == other.arguments
end
alias :=~ :==
end
end
end

View File

@ -0,0 +1,6 @@
module RKelly
module Nodes
class FunctionDeclNode < FunctionExprNode
end
end
end

View File

@ -0,0 +1,12 @@
module RKelly
module Nodes
class FunctionExprNode < Node
attr_reader :function_body, :arguments
def initialize(name, body, args = [])
super(name)
@function_body = body
@arguments = args
end
end
end
end

View File

@ -0,0 +1,12 @@
module RKelly
module Nodes
class IfNode < Node
attr_reader :conditions, :else
def initialize(conditions, value, else_stmt = nil)
super(value)
@conditions = conditions
@else = else_stmt
end
end
end
end

View File

@ -0,0 +1,11 @@
module RKelly
module Nodes
class LabelNode < Node
attr_reader :name
def initialize(name, value)
super(value)
@name = name
end
end
end
end

View File

@ -0,0 +1,11 @@
module RKelly
module Nodes
class NewExprNode < Node
attr_reader :arguments
def initialize(value, args)
super(value)
@arguments = args
end
end
end
end

88
lib/rkelly/nodes/node.rb Normal file
View File

@ -0,0 +1,88 @@
module RKelly
module Nodes
class Node
include RKelly::Visitable
include RKelly::Visitors
include Enumerable
attr_accessor :value, :comments, :line, :filename
def initialize(value)
@value = value
@comments = []
@filename = @line = nil
end
def ==(other)
other.is_a?(self.class) && @value == other.value
end
alias :=~ :==
def ===(other)
other.is_a?(self.class) && @value === other.value
end
def pointcut(pattern)
case pattern
when String
ast = RKelly::Parser.new.parse(pattern)
# Only take the first statement
finder = ast.value.first.class.to_s =~ /StatementNode$/ ?
ast.value.first.value : ast.value.first
visitor = PointcutVisitor.new(finder)
else
visitor = PointcutVisitor.new(pattern)
end
visitor.accept(self)
visitor
end
alias :/ :pointcut
def to_sexp
SexpVisitor.new.accept(self)
end
def to_ecma
ECMAVisitor.new.accept(self)
end
def to_dots
visitor = DotVisitor.new
visitor.accept(self)
header = <<-END
digraph g {
graph [ rankdir = "TB" ];
node [
fontsize = "16"
shape = "ellipse"
];
edge [ ];
END
nodes = visitor.nodes.map { |x| x.to_s }.join("\n")
counter = 0
arrows = visitor.arrows.map { |x|
s = "#{x} [\nid = #{counter}\n];"
counter += 1
s
}.join("\n")
"#{header}\n#{nodes}\n#{arrows}\n}"
end
def each(&block)
EnumerableVisitor.new(block).accept(self)
end
def to_real_sexp
RealSexpVisitor.new.accept(self)
end
end
%w[EmptyStatement Parenthetical ExpressionStatement True Delete Return TypeOf
SourceElements Number LogicalNot AssignExpr FunctionBody
ObjectLiteral UnaryMinus Throw This BitwiseNot Element String
Array CaseBlock Null Break Parameter Block False Void Regexp
Arguments Attr Continue ConstStatement UnaryPlus VarStatement].each do |node|
eval "class #{node}Node < Node; end"
end
end
end

View File

@ -0,0 +1,6 @@
module RKelly
module Nodes
class NotStrictEqualNode < BinaryNode
end
end
end

View File

@ -0,0 +1,16 @@
module RKelly
module Nodes
class OpEqualNode < Node
attr_reader :left
def initialize(left, right)
super(right)
@left = left
end
end
%w[Multiply Divide LShift Minus Plus Mod XOr RShift And URShift Or].each do |node|
eval "class Op#{node}EqualNode < OpEqualNode; end"
end
end
end

View File

@ -0,0 +1,11 @@
module RKelly
module Nodes
class PostfixNode < Node
attr_reader :operand
def initialize(operand, operator)
super(operator)
@operand = operand
end
end
end
end

View File

@ -0,0 +1,6 @@
module RKelly
module Nodes
class PrefixNode < PostfixNode
end
end
end

View File

@ -0,0 +1,13 @@
module RKelly
module Nodes
class PropertyNode < Node
attr_reader :name
def initialize(name, value)
super(value)
@name = name
end
end
%w[Getter Setter].each {|node| eval "class #{node}PropertyNode < PropertyNode; end"}
end
end

View File

@ -0,0 +1,19 @@
module RKelly
module Nodes
class ResolveNode < Node
def ==(other)
return true if super
if @value =~ /^[A-Z]/
place = [Object, Module, RKelly::Nodes].find { |x|
x.const_defined?(@value.to_sym)
}
return false unless place
klass = place.const_get(@value.to_sym)
return true if klass && other.is_a?(klass) || other.value.is_a?(klass)
end
false
end
alias :=~ :==
end
end
end

View File

@ -0,0 +1,6 @@
module RKelly
module Nodes
class StrictEqualNode < BinaryNode
end
end
end

View File

@ -0,0 +1,13 @@
module RKelly
module Nodes
class TryNode < Node
attr_reader :catch_var, :catch_block, :finally_block
def initialize(value, catch_var, catch_block, finally_block = nil)
super(value)
@catch_var = catch_var
@catch_block = catch_block
@finally_block = finally_block
end
end
end
end

View File

@ -0,0 +1,15 @@
module RKelly
module Nodes
class VarDeclNode < Node
attr_accessor :name, :type
def initialize(name, value, constant = false)
super(value)
@name = name
@constant = constant
end
def constant?; @constant; end
def variable?; !@constant; end
end
end
end

104
lib/rkelly/parser.rb Normal file
View File

@ -0,0 +1,104 @@
require 'rkelly/tokenizer'
require 'rkelly/generated_parser'
module RKelly
class Parser < RKelly::GeneratedParser
TOKENIZER = Tokenizer.new
RKelly::GeneratedParser.instance_methods.each do |im|
next unless im.to_s =~ /^_reduce_\d+$/
eval(<<-eoawesomehack)
def #{im}(val, _values, result)
r = super(val.map { |v|
v.is_a?(Token) ? v.to_racc_token[1] : v
}, _values, result)
if token = val.find { |v| v.is_a?(Token) }
r.line = token.line if r.respond_to?(:line)
r.filename = @filename if r.respond_to?(:filename)
end
r
end
eoawesomehack
end
attr_accessor :logger
def initialize
@tokens = []
@logger = nil
@terminator = false
@prev_token = nil
@comments = []
end
# Parse +javascript+ and return an AST
def parse(javascript, filename = nil)
@tokens = TOKENIZER.raw_tokens(javascript)
@position = 0
@filename = filename
ast = do_parse
apply_comments(ast)
end
def yyabort
raise "something bad happened, please report a bug with sample JavaScript"
end
private
def apply_comments(ast)
ast_hash = Hash.new { |h,k| h[k] = [] }
(ast || []).each { |n|
next unless n.line
ast_hash[n.line] << n
}
max = ast_hash.keys.sort.last
@comments.each do |comment|
node = nil
comment.line.upto(max) do |line|
if ast_hash.key?(line)
node = ast_hash[line].first
break
end
end
node.comments << comment if node
end if max
ast
end
def on_error(error_token_id, error_value, value_stack)
if logger
logger.error(token_to_str(error_token_id))
logger.error("error value: #{error_value}")
logger.error("error stack: #{value_stack}")
end
end
def next_token
@terminator = false
begin
return [false, false] if @position >= @tokens.length
n_token = @tokens[@position]
@position += 1
case @tokens[@position - 1].name
when :COMMENT
@comments << n_token
@terminator = true if n_token.value =~ /^\/\//
when :S
@terminator = true if n_token.value =~ /[\r\n]/
end
end while([:COMMENT, :S].include?(n_token.name))
if @terminator &&
((@prev_token && %w[continue break return throw].include?(@prev_token.value)) ||
(n_token && %w[++ --].include?(n_token.value)))
@position -= 1
return (@prev_token = RKelly::Token.new(';', ';')).to_racc_token
end
@prev_token = n_token
v = n_token.to_racc_token
v[1] = n_token
v
end
end
end

36
lib/rkelly/runtime.rb Normal file
View File

@ -0,0 +1,36 @@
require 'rkelly/js'
require 'rkelly/runtime/scope_chain'
module RKelly
class Runtime
UNDEFINED = RKelly::JS::Property.new(:undefined, :undefined)
def initialize
@parser = Parser.new
@scope = ScopeChain.new
end
# Execute +js+
def execute(js)
function_visitor = Visitors::FunctionVisitor.new(@scope)
eval_visitor = Visitors::EvaluationVisitor.new(@scope)
tree = @parser.parse(js)
function_visitor.accept(tree)
eval_visitor.accept(tree)
@scope
end
def call_function(function_name, *args)
function = @scope[function_name].value
@scope.new_scope { |chain|
function.js_call(chain, *(args.map { |x|
RKelly::JS::Property.new(:param, x)
}))
}.value
end
def define_function(function, &block)
@scope[function.to_s].function = block
end
end
end

View File

@ -0,0 +1,13 @@
module RKelly
class Runtime
class RubyFunction
def initialize(&block)
@code = block
end
def call(chain, *args)
@code.call(*args)
end
end
end
end

View File

@ -0,0 +1,57 @@
module RKelly
class Runtime
class ScopeChain
include RKelly::JS
def initialize(scope = Scope.new)
@chain = [GlobalObject.new]
end
def <<(scope)
@chain << scope
end
def has_property?(name)
scope = @chain.reverse.find { |x|
x.has_property?(name)
}
scope ? scope[name] : nil
end
def [](name)
property = has_property?(name)
return property if property
@chain.last.properties[name]
end
def []=(name, value)
@chain.last.properties[name] = value
end
def pop
@chain.pop
end
def this
@chain.last
end
def new_scope(&block)
@chain << Scope.new
result = yield(self)
@chain.pop
result
end
def return=(value)
@chain.last.return = value
end
def return; @chain.last.return; end
def returned?
@chain.last.returned?
end
end
end
end

View File

@ -0,0 +1,4 @@
module RKelly
class SyntaxError < ::SyntaxError
end
end

15
lib/rkelly/token.rb Normal file
View File

@ -0,0 +1,15 @@
module RKelly
class Token
attr_accessor :name, :value, :transformer, :line
def initialize(name, value, &transformer)
@name = name
@value = value
@transformer = transformer
end
def to_racc_token
return transformer.call(name, value) if transformer
[name, value]
end
end
end

136
lib/rkelly/tokenizer.rb Normal file
View File

@ -0,0 +1,136 @@
require 'rkelly/lexeme'
module RKelly
class Tokenizer
KEYWORDS = %w{
break case catch continue default delete do else finally for function
if in instanceof new return switch this throw try typeof var void while
with
const true false null debugger
}
RESERVED = %w{
abstract boolean byte char class double enum export extends
final float goto implements import int interface long native package
private protected public short static super synchronized throws
transient volatile
}
LITERALS = {
# Punctuators
'==' => :EQEQ,
'!=' => :NE,
'===' => :STREQ,
'!==' => :STRNEQ,
'<=' => :LE,
'>=' => :GE,
'||' => :OR,
'&&' => :AND,
'++' => :PLUSPLUS,
'--' => :MINUSMINUS,
'<<' => :LSHIFT,
'<<=' => :LSHIFTEQUAL,
'>>' => :RSHIFT,
'>>=' => :RSHIFTEQUAL,
'>>>' => :URSHIFT,
'>>>='=> :URSHIFTEQUAL,
'&=' => :ANDEQUAL,
'%=' => :MODEQUAL,
'^=' => :XOREQUAL,
'|=' => :OREQUAL,
'+=' => :PLUSEQUAL,
'-=' => :MINUSEQUAL,
'*=' => :MULTEQUAL,
'/=' => :DIVEQUAL,
}
TOKENS_THAT_IMPLY_DIVISION = [:IDENT, :NUMBER, ')', ']', '}']
def initialize(&block)
@lexemes = []
token(:COMMENT, /\A\/(?:\*(?:.)*?\*\/|\/[^\n]*)/m)
token(:STRING, /\A"(?:[^"\\]*(?:\\.[^"\\]*)*)"|\A'(?:[^'\\]*(?:\\.[^'\\]*)*)'/m)
# A regexp to match floating point literals (but not integer literals).
token(:NUMBER, /\A\d+\.\d*(?:[eE][-+]?\d+)?|\A\d+(?:\.\d*)?[eE][-+]?\d+|\A\.\d+(?:[eE][-+]?\d+)?/m) do |type, value|
value.gsub!(/\.(\D)/, '.0\1') if value =~ /\.\w/
value.gsub!(/\.$/, '.0') if value =~ /\.$/
value.gsub!(/^\./, '0.') if value =~ /^\./
[type, eval(value)]
end
token(:NUMBER, /\A0[xX][\da-fA-F]+|\A0[0-7]*|\A\d+/) do |type, value|
[type, eval(value)]
end
token(:LITERALS,
Regexp.new(LITERALS.keys.sort_by { |x|
x.length
}.reverse.map { |x| "\\A#{x.gsub(/([|+*^])/, '\\\\\1')}" }.join('|')
)) do |type, value|
[LITERALS[value], value]
end
token(:IDENT, /\A([_\$A-Za-z][_\$0-9A-Za-z]*)/) do |type,value|
if KEYWORDS.include?(value)
[value.upcase.to_sym, value]
elsif RESERVED.include?(value)
[:RESERVED, value]
else
[type, value]
end
end
token(:REGEXP, /\A\/(?:[^\/\r\n\\]*(?:\\[^\r\n][^\/\r\n\\]*)*)\/[gi]*/)
token(:S, /\A[\s\r\n]*/m)
token(:SINGLE_CHAR, /\A./) do |type, value|
[value, value]
end
end
def tokenize(string)
raw_tokens(string).map { |x| x.to_racc_token }
end
def raw_tokens(string)
tokens = []
line_number = 1
accepting_regexp = true
while string.length > 0
longest_token = nil
@lexemes.each { |lexeme|
next if lexeme.name == :REGEXP && !accepting_regexp
match = lexeme.match(string)
next if match.nil?
longest_token = match if longest_token.nil?
next if longest_token.value.length >= match.value.length
longest_token = match
}
accepting_regexp = followable_by_regex(longest_token)
longest_token.line = line_number
line_number += longest_token.value.scan(/\n/).length
string = string.slice(Range.new(longest_token.value.length, -1))
tokens << longest_token
end
tokens
end
private
def token(name, pattern = nil, &block)
@lexemes << Lexeme.new(name, pattern, &block)
end
def followable_by_regex(current_token)
name = current_token.name
name = current_token.value if name == :SINGLE_CHAR
#the tokens that imply division vs. start of regex form a disjoint set
!TOKENS_THAT_IMPLY_DIVISION.include?(name)
end
end
end

16
lib/rkelly/visitable.rb Normal file
View File

@ -0,0 +1,16 @@
module RKelly
module Visitable
# Based off the visitor pattern from RubyGarden
def accept(visitor, &block)
klass = self.class.ancestors.find { |ancestor|
visitor.respond_to?("visit_#{ancestor.name.split(/::/)[-1]}")
}
if klass
visitor.send(:"visit_#{klass.name.split(/::/)[-1]}", self, &block)
else
raise "No visitor for '#{self.class}'"
end
end
end
end

4
lib/rkelly/visitors.rb Normal file
View File

@ -0,0 +1,4 @@
require 'rkelly/visitors/visitor'
Dir[File.join(File.dirname(__FILE__), "visitors/*_visitor.rb")].each do |file|
require file[/rkelly\/visitors\/.*/]
end

View File

@ -0,0 +1,228 @@
module RKelly
module Visitors
class DotVisitor < Visitor
class Node < Struct.new(:node_id, :fields)
ESCAPE = /([<>"\\])/
def to_s
counter = 0
label = fields.map { |f|
s = "<f#{counter}> #{f.to_s.gsub(ESCAPE, '\\\\\1').gsub(/[\r\n]/,' ')}"
counter += 1
s
}.join('|')
"\"#{node_id}\" [\nlabel = \"#{label}\"\nshape = \"record\"\n];"
end
end
class Arrow < Struct.new(:from, :to, :label)
def to_s
"\"#{from.node_id}\":f0 -> \"#{to.node_id}\":f0"
end
end
attr_reader :nodes, :arrows
def initialize
@stack = []
@node_index = 0
@nodes = []
@arrows = []
end
## Terminal nodes
%w{
BreakNode ContinueNode EmptyStatementNode FalseNode
NullNode NumberNode ParameterNode RegexpNode ResolveNode StringNode
ThisNode TrueNode
}.each do |type|
define_method(:"visit_#{type}") do |o|
node = Node.new(@node_index += 1, [type, o.value].compact)
add_arrow_for(node)
@nodes << node
end
end
## End Terminal nodes
# Single value nodes
%w{
AssignExprNode BitwiseNotNode BlockNode DeleteNode ElementNode
ExpressionStatementNode FunctionBodyNode LogicalNotNode ReturnNode
ThrowNode TypeOfNode UnaryMinusNode UnaryPlusNode VoidNode
}.each do |type|
define_method(:"visit_#{type}") do |o|
node = Node.new(@node_index += 1, [type])
add_arrow_for(node)
@nodes << node
@stack.push(node)
o.value && o.value.accept(self)
@stack.pop
end
end
# End Single value nodes
# Binary nodes
%w{
AddNode BitAndNode BitOrNode BitXOrNode CaseClauseNode CommaNode
DivideNode DoWhileNode EqualNode GreaterNode GreaterOrEqualNode InNode
InstanceOfNode LeftShiftNode LessNode LessOrEqualNode LogicalAndNode
LogicalOrNode ModulusNode MultiplyNode NotEqualNode NotStrictEqualNode
OpAndEqualNode OpDivideEqualNode OpEqualNode OpLShiftEqualNode
OpMinusEqualNode OpModEqualNode OpMultiplyEqualNode OpOrEqualNode
OpPlusEqualNode OpRShiftEqualNode OpURShiftEqualNode OpXOrEqualNode
RightShiftNode StrictEqualNode SubtractNode SwitchNode
UnsignedRightShiftNode WhileNode WithNode
}.each do |type|
define_method(:"visit_#{type}") do |o|
node = Node.new(@node_index += 1, [type])
add_arrow_for(node)
@nodes << node
@stack.push(node)
o.left && o.left.accept(self)
o.value && o.value.accept(self)
@stack.pop
end
end
# End Binary nodes
# Array Value Nodes
%w{
ArgumentsNode ArrayNode CaseBlockNode ConstStatementNode
ObjectLiteralNode SourceElementsNode VarStatementNode
}.each do |type|
define_method(:"visit_#{type}") do |o|
node = Node.new(@node_index += 1, [type])
add_arrow_for(node)
@nodes << node
@stack.push(node)
o.value && o.value.each { |v| v && v.accept(self) }
@stack.pop
end
end
# END Array Value Nodes
# Name and Value Nodes
%w{
LabelNode PropertyNode GetterPropertyNode SetterPropertyNode VarDeclNode
}.each do |type|
define_method(:"visit_#{type}") do |o|
node = Node.new(@node_index += 1, [type, o.name || 'NULL'])
add_arrow_for(node)
@nodes << node
@stack.push(node)
o.value && o.value.accept(self)
@stack.pop
end
end
# END Name and Value Nodes
%w{ PostfixNode PrefixNode }.each do |type|
define_method(:"visit_#{type}") do |o|
node = Node.new(@node_index += 1, [type, o.value])
add_arrow_for(node)
@nodes << node
@stack.push(node)
o.operand && o.operand.accept(self)
@stack.pop
end
end
def visit_ForNode(o)
node = Node.new(@node_index += 1, ['ForNode'])
add_arrow_for(node)
@nodes << node
@stack.push(node)
[:init, :test, :counter, :value].each do |method|
o.send(method) && o.send(method).accept(self)
end
@stack.pop
end
%w{ IfNode ConditionalNode }.each do |type|
define_method(:"visit_#{type}") do |o|
node = Node.new(@node_index += 1, [type])
add_arrow_for(node)
@nodes << node
@stack.push(node)
[:conditions, :value, :else].each do |method|
o.send(method) && o.send(method).accept(self)
end
@stack.pop
end
end
def visit_ForInNode(o)
node = Node.new(@node_index += 1, ['ForInNode'])
add_arrow_for(node)
@nodes << node
@stack.push(node)
[:left, :right, :value].each do |method|
o.send(method) && o.send(method).accept(self)
end
@stack.pop
end
def visit_TryNode(o)
node = Node.new(@node_index += 1, ['TryNode', o.catch_var || 'NULL'])
add_arrow_for(node)
@nodes << node
@stack.push(node)
[:value, :catch_block, :finally_block].each do |method|
o.send(method) && o.send(method).accept(self)
end
@stack.pop
end
def visit_BracketAccessorNode(o)
node = Node.new(@node_index += 1, ['BracketAccessorNode'])
add_arrow_for(node)
@nodes << node
@stack.push(node)
[:value, :accessor].each do |method|
o.send(method) && o.send(method).accept(self)
end
@stack.pop
end
%w{ NewExprNode FunctionCallNode }.each do |type|
define_method(:"visit_#{type}") do |o|
node = Node.new(@node_index += 1, [type])
add_arrow_for(node)
@nodes << node
@stack.push(node)
[:value, :arguments].each do |method|
o.send(method) && o.send(method).accept(self)
end
@stack.pop
end
end
%w{ FunctionExprNode FunctionDeclNode }.each do |type|
define_method(:"visit_#{type}") do |o|
node = Node.new(@node_index += 1, [type, o.value || 'NULL'])
add_arrow_for(node)
@nodes << node
@stack.push(node)
o.arguments.each { |a| a && a.accept(self) }
o.function_body && o.function_body.accept(self)
@stack.pop
end
end
def visit_DotAccessorNode(o)
node = Node.new(@node_index += 1, ['DotAccessorNode', o.accessor])
add_arrow_for(node)
@nodes << node
@stack.push(node)
[:value].each do |method|
o.send(method) && o.send(method).accept(self)
end
@stack.pop
end
private
def add_arrow_for(node, label = nil)
@arrows << Arrow.new(@stack.last, node, label) if @stack.length > 0
end
end
end
end

View File

@ -0,0 +1,327 @@
module RKelly
module Visitors
class ECMAVisitor < Visitor
def initialize
@indent = 0
end
def visit_ParentheticalNode(o)
"(#{o.value.accept(self)})"
end
def visit_SourceElementsNode(o)
o.value.map { |x| "#{indent}#{x.accept(self)}" }.join("\n")
end
def visit_VarStatementNode(o)
"var #{o.value.map { |x| x.accept(self) }.join(', ')};"
end
def visit_ConstStatementNode(o)
"const #{o.value.map { |x| x.accept(self) }.join(', ')};"
end
def visit_VarDeclNode(o)
"#{o.name}#{o.value ? o.value.accept(self) : nil}"
end
def visit_AssignExprNode(o)
" = #{o.value.accept(self)}"
end
def visit_NumberNode(o)
o.value.to_s
end
def visit_ForNode(o)
init = o.init ? o.init.accept(self) : ';'
test = o.test ? o.test.accept(self) : ''
counter = o.counter ? o.counter.accept(self) : ''
# VarStatementNodes will have a ; appended automatically, but other
# expressions won't, so make sure it's there
if init[-1,1] != ';'
init += ';'
end
"for(#{init} #{test}; #{counter}) #{o.value.accept(self)}"
end
def visit_LessNode(o)
"#{o.left.accept(self)} < #{o.value.accept(self)}"
end
def visit_ResolveNode(o)
o.value
end
def visit_PostfixNode(o)
"#{o.operand.accept(self)}#{o.value}"
end
def visit_PrefixNode(o)
"#{o.value}#{o.operand.accept(self)}"
end
def visit_BlockNode(o)
@indent += 1
"{\n#{o.value.accept(self)}\n#{@indent -=1; indent}}"
end
def visit_ExpressionStatementNode(o)
"#{o.value.accept(self)};"
end
def visit_OpEqualNode(o)
"#{o.left.accept(self)} = #{o.value.accept(self)}"
end
def visit_FunctionCallNode(o)
"#{o.value.accept(self)}(#{o.arguments.accept(self)})"
end
def visit_ArgumentsNode(o)
o.value.map { |x| x.accept(self) }.join(', ')
end
def visit_StringNode(o)
o.value
end
def visit_NullNode(o)
"null"
end
def visit_FunctionDeclNode(o)
"#{indent}function #{o.value}(" +
"#{o.arguments.map { |x| x.accept(self) }.join(', ')})" +
"#{o.function_body.accept(self)}"
end
def visit_ParameterNode(o)
o.value
end
def visit_FunctionBodyNode(o)
@indent += 1
"{\n#{o.value.accept(self)}\n#{@indent -=1; indent}}"
end
def visit_BreakNode(o)
"break" + (o.value ? " #{o.value}" : '') + ';'
end
def visit_ContinueNode(o)
"continue" + (o.value ? " #{o.value}" : '') + ';'
end
def visit_TrueNode(o)
"true"
end
def visit_FalseNode(o)
"false"
end
def visit_EmptyStatementNode(o)
';'
end
def visit_RegexpNode(o)
o.value
end
def visit_DotAccessorNode(o)
"#{o.value.accept(self)}.#{o.accessor}"
end
def visit_ThisNode(o)
"this"
end
def visit_BitwiseNotNode(o)
"~#{o.value.accept(self)}"
end
def visit_DeleteNode(o)
"delete #{o.value.accept(self)}"
end
def visit_ArrayNode(o)
"[#{o.value.map { |x| x ? x.accept(self) : '' }.join(', ')}]"
end
def visit_ElementNode(o)
o.value.accept(self)
end
def visit_LogicalNotNode(o)
"!#{o.value.accept(self)}"
end
def visit_UnaryMinusNode(o)
"-#{o.value.accept(self)}"
end
def visit_UnaryPlusNode(o)
"+#{o.value.accept(self)}"
end
def visit_ReturnNode(o)
"return" + (o.value ? " #{o.value.accept(self)}" : '') + ';'
end
def visit_ThrowNode(o)
"throw #{o.value.accept(self)};"
end
def visit_TypeOfNode(o)
"typeof #{o.value.accept(self)}"
end
def visit_VoidNode(o)
"void(#{o.value.accept(self)})"
end
[
[:Add, '+'],
[:BitAnd, '&'],
[:BitOr, '|'],
[:BitXOr, '^'],
[:Divide, '/'],
[:Equal, '=='],
[:Greater, '>'],
[:Greater, '>'],
[:GreaterOrEqual, '>='],
[:GreaterOrEqual, '>='],
[:In, 'in'],
[:InstanceOf, 'instanceof'],
[:LeftShift, '<<'],
[:LessOrEqual, '<='],
[:LogicalAnd, '&&'],
[:LogicalOr, '||'],
[:Modulus, '%'],
[:Multiply, '*'],
[:NotEqual, '!='],
[:NotStrictEqual, '!=='],
[:OpAndEqual, '&='],
[:OpDivideEqual, '/='],
[:OpLShiftEqual, '<<='],
[:OpMinusEqual, '-='],
[:OpModEqual, '%='],
[:OpMultiplyEqual, '*='],
[:OpOrEqual, '|='],
[:OpPlusEqual, '+='],
[:OpRShiftEqual, '>>='],
[:OpURShiftEqual, '>>>='],
[:OpXOrEqual, '^='],
[:RightShift, '>>'],
[:StrictEqual, '==='],
[:Subtract, '-'],
[:UnsignedRightShift, '>>>'],
].each do |name,op|
define_method(:"visit_#{name}Node") do |o|
"#{o.left.accept(self)} #{op} #{o.value.accept(self)}"
end
end
def visit_WhileNode(o)
"while(#{o.left.accept(self)}) #{o.value.accept(self)}"
end
def visit_SwitchNode(o)
"switch(#{o.left.accept(self)}) #{o.value.accept(self)}"
end
def visit_CaseBlockNode(o)
@indent += 1
"{\n" + (o.value ? o.value.map { |x| x.accept(self) }.join('') : '') +
"#{@indent -=1; indent}}"
end
def visit_CaseClauseNode(o)
if o.left
case_code = "#{indent}case #{o.left.accept(self)}:\n"
else
case_code = "#{indent}default:\n"
end
@indent += 1
case_code += "#{o.value.accept(self)}\n"
@indent -= 1
case_code
end
def visit_DoWhileNode(o)
"do #{o.left.accept(self)} while(#{o.value.accept(self)});"
end
def visit_WithNode(o)
"with(#{o.left.accept(self)}) #{o.value.accept(self)}"
end
def visit_LabelNode(o)
"#{o.name}: #{o.value.accept(self)}"
end
def visit_ObjectLiteralNode(o)
@indent += 1
lit = "{" + (o.value.length > 0 ? "\n" : ' ') +
o.value.map { |x| "#{indent}#{x.accept(self)}" }.join(",\n") +
(o.value.length > 0 ? "\n" : '') + '}'
@indent -= 1
lit
end
def visit_PropertyNode(o)
"#{o.name}: #{o.value.accept(self)}"
end
def visit_GetterPropertyNode(o)
"get #{o.name}#{o.value.accept(self)}"
end
def visit_SetterPropertyNode(o)
"set #{o.name}#{o.value.accept(self)}"
end
def visit_FunctionExprNode(o)
"#{o.value}(#{o.arguments.map { |x| x.accept(self) }.join(', ')}) " +
"#{o.function_body.accept(self)}"
end
def visit_CommaNode(o)
"#{o.left.accept(self)}, #{o.value.accept(self)}"
end
def visit_IfNode(o)
"if(#{o.conditions.accept(self)}) #{o.value.accept(self)}" +
(o.else ? " else #{o.else.accept(self)}" : '')
end
def visit_ConditionalNode(o)
"#{o.conditions.accept(self)} ? #{o.value.accept(self)} : " +
"#{o.else.accept(self)}"
end
def visit_ForInNode(o)
"for(#{o.left.accept(self)} in #{o.right.accept(self)}) " +
"#{o.value.accept(self)}"
end
def visit_TryNode(o)
"try #{o.value.accept(self)}" +
(o.catch_block ? " catch(#{o.catch_var}) #{o.catch_block.accept(self)}" : '') +
(o.finally_block ? " finally #{o.finally_block.accept(self)}" : '')
end
def visit_BracketAccessorNode(o)
"#{o.value.accept(self)}[#{o.accessor.accept(self)}]"
end
def visit_NewExprNode(o)
"new #{o.value.accept(self)}(#{o.arguments.accept(self)})"
end
private
def indent; ' ' * @indent * 2; end
end
end
end

View File

@ -0,0 +1,18 @@
module RKelly
module Visitors
class EnumerableVisitor < Visitor
def initialize(block)
@block = block
end
ALL_NODES.each do |type|
eval <<-RUBY
def visit_#{type}Node(o)
@block[o]
super
end
RUBY
end
end
end
end

View File

@ -0,0 +1,419 @@
module RKelly
module Visitors
class EvaluationVisitor < Visitor
attr_reader :scope_chain
def initialize(scope)
super()
@scope_chain = scope
@operand = []
end
def visit_SourceElementsNode(o)
o.value.each { |x|
next if scope_chain.returned?
x.accept(self)
}
end
def visit_FunctionDeclNode(o)
end
def visit_VarStatementNode(o)
o.value.each { |x| x.accept(self) }
end
def visit_VarDeclNode(o)
@operand << o.name
o.value.accept(self) if o.value
@operand.pop
end
def visit_IfNode(o)
truthiness = o.conditions.accept(self)
if truthiness.value && truthiness.value != 0
o.value.accept(self)
else
o.else && o.else.accept(self)
end
end
def visit_ResolveNode(o)
scope_chain[o.value]
end
def visit_ThisNode(o)
scope_chain.this
end
def visit_ExpressionStatementNode(o)
o.value.accept(self)
end
def visit_AddNode(o)
left = to_primitive(o.left.accept(self), 'Number')
right = to_primitive(o.value.accept(self), 'Number')
if left.value.is_a?(::String) || right.value.is_a?(::String)
RKelly::JS::Property.new(:add,
"#{left.value}#{right.value}"
)
else
additive_operator(:+, left, right)
end
end
def visit_SubtractNode(o)
RKelly::JS::Property.new(:subtract,
o.left.accept(self).value - o.value.accept(self).value
)
end
def visit_MultiplyNode(o)
left = to_number(o.left.accept(self)).value
right = to_number(o.value.accept(self)).value
return_val =
if [left, right].any? { |x| x.respond_to?(:nan?) && x.nan? }
RKelly::JS::NaN.new
else
[left, right].any? { |x|
x.respond_to?(:intinite?) && x.infinite?
} && [left, right].any? { |x| x == 0
} ? RKelly::JS::NaN.new : left * right
end
RKelly::JS::Property.new(:multiple, return_val)
end
def visit_DivideNode(o)
left = to_number(o.left.accept(self)).value
right = to_number(o.value.accept(self)).value
return_val =
if [left, right].any? { |x|
x.respond_to?(:nan?) && x.nan? ||
x.respond_to?(:intinite?) && x.infinite?
}
RKelly::JS::NaN.new
elsif [left, right].all? { |x| x == 0 }
RKelly::JS::NaN.new
elsif right == 0
left * (right.eql?(0) ? (1.0/0.0) : (-1.0/0.0))
else
left / right
end
RKelly::JS::Property.new(:divide, return_val)
end
def visit_ModulusNode(o)
left = to_number(o.left.accept(self)).value
right = to_number(o.value.accept(self)).value
return_val =
if [left, right].any? { |x| x.respond_to?(:nan?) && x.nan? }
RKelly::JS::NaN.new
elsif [left, right].all? { |x| x.respond_to?(:infinite?) && x.infinite? }
RKelly::JS::NaN.new
elsif right == 0
RKelly::JS::NaN.new
elsif left.respond_to?(:infinite?) && left.infinite?
RKelly::JS::NaN.new
elsif right.respond_to?(:infinite?) && right.infinite?
left
else
left % right
end
RKelly::JS::Property.new(:divide, return_val)
end
def visit_OpEqualNode(o)
left = o.left.accept(self)
right = o.value.accept(self)
left.value = right.value
left.function = right.function
left
end
def visit_OpPlusEqualNode(o)
o.left.accept(self).value += o.value.accept(self).value
end
def visit_AssignExprNode(o)
scope_chain[@operand.last] = o.value.accept(self)
end
def visit_NumberNode(o)
RKelly::JS::Property.new(o.value, o.value)
end
def visit_VoidNode(o)
o.value.accept(self)
RKelly::JS::Property.new(:undefined, :undefined)
end
def visit_NullNode(o)
RKelly::JS::Property.new(nil, nil)
end
def visit_TrueNode(o)
RKelly::JS::Property.new(true, true)
end
def visit_FalseNode(o)
RKelly::JS::Property.new(false, false)
end
def visit_StringNode(o)
RKelly::JS::Property.new(:string,
o.value.gsub(/\A['"]/, '').gsub(/['"]$/, '')
)
end
def visit_FunctionCallNode(o)
left = o.value.accept(self)
arguments = o.arguments.accept(self)
call_function(left, arguments)
end
def visit_NewExprNode(o)
visit_FunctionCallNode(o)
end
def visit_DotAccessorNode(o)
left = o.value.accept(self)
right = left.value[o.accessor]
right.binder = left.value
right
end
def visit_EqualNode(o)
left = o.left.accept(self)
right = o.value.accept(self)
RKelly::JS::Property.new(:equal_node, left.value == right.value)
end
def visit_BlockNode(o)
o.value.accept(self)
end
def visit_FunctionBodyNode(o)
o.value.accept(self)
scope_chain.return
end
def visit_ReturnNode(o)
scope_chain.return = o.value.accept(self)
end
def visit_BitwiseNotNode(o)
orig = o.value.accept(self)
number = to_int_32(orig)
RKelly::JS::Property.new(nil, ~number.value)
end
def visit_PostfixNode(o)
orig = o.operand.accept(self)
number = to_number(orig)
case o.value
when '++'
orig.value = number.value + 1
when '--'
orig.value = number.value - 1
end
number
end
def visit_PrefixNode(o)
orig = o.operand.accept(self)
number = to_number(orig)
case o.value
when '++'
orig.value = number.value + 1
when '--'
orig.value = number.value - 1
end
orig
end
def visit_LogicalNotNode(o)
bool = to_boolean(o.value.accept(self))
bool.value = !bool.value
bool
end
def visit_ArgumentsNode(o)
o.value.map { |x| x.accept(self) }
end
def visit_TypeOfNode(o)
val = o.value.accept(self)
return RKelly::JS::Property.new(:string, 'object') if val.value.nil?
case val.value
when String
RKelly::JS::Property.new(:string, 'string')
when Numeric
RKelly::JS::Property.new(:string, 'number')
when true
RKelly::JS::Property.new(:string, 'boolean')
when false
RKelly::JS::Property.new(:string, 'boolean')
when :undefined
RKelly::JS::Property.new(:string, 'undefined')
else
RKelly::JS::Property.new(:object, 'object')
end
end
def visit_UnaryPlusNode(o)
orig = o.value.accept(self)
to_number(orig)
end
def visit_UnaryMinusNode(o)
orig = o.value.accept(self)
v = to_number(orig)
v.value = v.value == 0 ? -0.0 : 0 - v.value
v
end
%w{
ArrayNode BitAndNode BitOrNode
BitXOrNode BracketAccessorNode BreakNode
CaseBlockNode CaseClauseNode CommaNode ConditionalNode
ConstStatementNode ContinueNode DeleteNode
DoWhileNode ElementNode EmptyStatementNode
ForInNode ForNode
FunctionExprNode GetterPropertyNode GreaterNode GreaterOrEqualNode
InNode InstanceOfNode LabelNode LeftShiftNode LessNode
LessOrEqualNode LogicalAndNode LogicalOrNode
NotEqualNode NotStrictEqualNode
ObjectLiteralNode OpAndEqualNode OpDivideEqualNode
OpLShiftEqualNode OpMinusEqualNode OpModEqualNode
OpMultiplyEqualNode OpOrEqualNode OpRShiftEqualNode
OpURShiftEqualNode OpXOrEqualNode ParameterNode
PropertyNode RegexpNode RightShiftNode
SetterPropertyNode StrictEqualNode
SwitchNode ThrowNode TryNode
UnsignedRightShiftNode
WhileNode WithNode
}.each do |type|
define_method(:"visit_#{type}") do |o|
raise "#{type} not defined"
end
end
private
def to_number(object)
return RKelly::JS::Property.new('0', 0) unless object.value
return_val =
case object.value
when :undefined
RKelly::JS::NaN.new
when false
0
when true
1
when Numeric
object.value
when ::String
s = object.value.gsub(/(\A[\s\xB\xA0]*|[\s\xB\xA0]*\Z)/, '')
if s.length == 0
0
else
case s
when /^([+-])?Infinity/
$1 == '-' ? -1.0/0.0 : 1.0/0.0
when /\A[-+]?\d+\.\d*(?:[eE][-+]?\d+)?$|\A[-+]?\d+(?:\.\d*)?[eE][-+]?\d+$|\A[-+]?\.\d+(?:[eE][-+]?\d+)?$/, /\A[-+]?0[xX][\da-fA-F]+$|\A[+-]?0[0-7]*$|\A[+-]?\d+$/
s.gsub!(/\.(\D)/, '.0\1') if s =~ /\.\w/
s.gsub!(/\.$/, '.0') if s =~ /\.$/
s.gsub!(/^\./, '0.') if s =~ /^\./
s.gsub!(/^([+-])\./, '\10.') if s =~ /^[+-]\./
s = s.gsub(/^[0]*/, '') if /^0[1-9]+$/.match(s)
eval(s)
else
RKelly::JS::NaN.new
end
end
when RKelly::JS::Base
return to_number(to_primitive(object, 'Number'))
end
RKelly::JS::Property.new(nil, return_val)
end
def to_boolean(object)
return RKelly::JS::Property.new(false, false) unless object.value
value = object.value
boolean =
case value
when :undefined
false
when true
true
when Numeric
value == 0 || value.respond_to?(:nan?) && value.nan? ? false : true
when ::String
value.length == 0 ? false : true
when RKelly::JS::Base
true
else
raise
end
RKelly::JS::Property.new(boolean, boolean)
end
def to_int_32(object)
number = to_number(object)
value = number.value
return number if value == 0
if value.respond_to?(:nan?) && (value.nan? || value.infinite?)
RKelly::JS::Property.new(nil, 0)
end
value = ((value < 0 ? -1 : 1) * value.abs.floor) % (2 ** 32)
if value >= 2 ** 31
RKelly::JS::Property.new(nil, value - (2 ** 32))
else
RKelly::JS::Property.new(nil, value)
end
end
def to_primitive(object, preferred_type = nil)
return object unless object.value
case object.value
when false, true, :undefined, ::String, Numeric
RKelly::JS::Property.new(nil, object.value)
when RKelly::JS::Base
call_function(object.value.default_value(preferred_type))
end
end
def additive_operator(operator, left, right)
left, right = to_number(left).value, to_number(right).value
left = left.respond_to?(:nan?) && left.nan? ? 0.0/0.0 : left
right = right.respond_to?(:nan?) && right.nan? ? 0.0/0.0 : right
result = left.send(operator, right)
result = result.respond_to?(:nan?) && result.nan? ? JS::NaN.new : result
RKelly::JS::Property.new(operator, result)
end
def call_function(property, arguments = [])
function = property.function || property.value
case function
when RKelly::JS::Function
scope_chain.new_scope { |chain|
function.js_call(chain, *arguments)
}
when UnboundMethod
RKelly::JS::Property.new(:ruby,
function.bind(property.binder).call(*(arguments.map { |x| x.value}))
)
else
RKelly::JS::Property.new(:ruby,
function.call(*(arguments.map { |x| x.value }))
)
end
end
end
end
end

View File

@ -0,0 +1,46 @@
module RKelly
module Visitors
class FunctionVisitor < Visitor
attr_reader :scope_chain
def initialize(scope)
super()
@scope_chain = scope
end
def visit_SourceElementsNode(o)
o.value.each { |x| x.accept(self) }
end
def visit_FunctionDeclNode(o)
if o.value
scope_chain[o.value].value = RKelly::JS::Function.new(o.function_body, o.arguments)
end
end
%w{
AddNode ArgumentsNode ArrayNode AssignExprNode BitAndNode BitOrNode
BitXOrNode BitwiseNotNode BlockNode BracketAccessorNode BreakNode
CaseBlockNode CaseClauseNode CommaNode ConditionalNode
ConstStatementNode ContinueNode DeleteNode DivideNode
DoWhileNode DotAccessorNode ElementNode EmptyStatementNode EqualNode
ExpressionStatementNode FalseNode ForInNode ForNode FunctionBodyNode
FunctionExprNode GetterPropertyNode GreaterNode GreaterOrEqualNode
IfNode InNode InstanceOfNode LabelNode LeftShiftNode LessNode
LessOrEqualNode LogicalAndNode LogicalNotNode LogicalOrNode ModulusNode
MultiplyNode NewExprNode NotEqualNode NotStrictEqualNode NullNode
NumberNode ObjectLiteralNode OpAndEqualNode OpDivideEqualNode
OpEqualNode OpLShiftEqualNode OpMinusEqualNode OpModEqualNode
OpMultiplyEqualNode OpOrEqualNode OpPlusEqualNode OpRShiftEqualNode
OpURShiftEqualNode OpXOrEqualNode ParameterNode PostfixNode PrefixNode
PropertyNode RegexpNode ResolveNode ReturnNode RightShiftNode
SetterPropertyNode StrictEqualNode StringNode
SubtractNode SwitchNode ThisNode ThrowNode TrueNode TryNode TypeOfNode
UnaryMinusNode UnaryPlusNode UnsignedRightShiftNode VarDeclNode
VarStatementNode VoidNode WhileNode WithNode
}.each do |type|
define_method(:"visit_#{type}") do |o|
end
end
end
end
end

View File

@ -0,0 +1,31 @@
module RKelly
module Visitors
class PointcutVisitor < Visitor
attr_reader :matches
def initialize(pattern, matches = [])
@pattern = pattern
@matches = matches
end
def >(pattern)
pattern =
case pattern
when Class
pattern.new(Object)
else
pattern
end
self.class.new(nil, @matches.map do |m|
m.pointcut(pattern).matches
end.flatten)
end
ALL_NODES.each do |type|
define_method(:"visit_#{type}Node") do |o|
@matches << o if @pattern === o
super(o)
end
end
end
end
end

View File

@ -0,0 +1,16 @@
module RKelly
module Visitors
class RealSexpVisitor < Visitor
ALL_NODES.each do |type|
eval <<-RUBY
def visit_#{type}Node(o)
sexp = s(:#{type.scan(/[A-Z][a-z]+/).join('_').downcase}, *super(o))
sexp.line = o.line if o.line
sexp.file = o.filename
sexp
end
RUBY
end
end
end
end

View File

@ -0,0 +1,373 @@
module RKelly
module Visitors
class SexpVisitor < Visitor
def visit_NumberNode(o)
[:lit, o.value]
end
def visit_RegexpNode(o)
[:lit, o.value]
end
def visit_AssignExprNode(o)
[:assign, super]
end
def visit_VarDeclNode(o)
[ o.constant? ? :const_decl : :var_decl ] + super(o)
end
def visit_VarStatementNode(o)
[:var, super]
end
def visit_PostfixNode(o)
[:postfix, super, o.value]
end
def visit_PrefixNode(o)
[:prefix, super, o.value]
end
def visit_DeleteNode(o)
[:delete, super]
end
def visit_VoidNode(o)
[:void, super]
end
def visit_TypeOfNode(o)
[:typeof, super]
end
def visit_UnaryPlusNode(o)
[:u_plus, super]
end
def visit_UnaryMinusNode(o)
[:u_minus, super]
end
def visit_BitwiseNotNode(o)
[:bitwise_not, super]
end
def visit_LogicalNotNode(o)
[:not, super]
end
def visit_ConstStatementNode(o)
[:const, super]
end
def visit_MultiplyNode(o)
[:multiply, *super]
end
def visit_DivideNode(o)
[:divide, *super]
end
def visit_ModulusNode(o)
[:modulus, *super]
end
def visit_AddNode(o)
[:add, *super]
end
def visit_LeftShiftNode(o)
[:lshift, *super]
end
def visit_RightShiftNode(o)
[:rshift, *super]
end
def visit_UnsignedRightShiftNode(o)
[:urshift, *super]
end
def visit_SubtractNode(o)
[:subtract, *super]
end
def visit_LessNode(o)
[:less, *super]
end
def visit_GreaterNode(o)
[:greater, *super]
end
def visit_LessOrEqualNode(o)
[:less_or_equal, *super]
end
def visit_GreaterOrEqualNode(o)
[:greater_or_equal, *super]
end
def visit_InstanceOfNode(o)
[:instance_of, *super]
end
def visit_EqualNode(o)
[:equal, *super]
end
def visit_NotEqualNode(o)
[:not_equal, *super]
end
def visit_StrictEqualNode(o)
[:strict_equal, *super]
end
def visit_NotStrictEqualNode(o)
[:not_strict_equal, *super]
end
def visit_BitAndNode(o)
[:bit_and, *super]
end
def visit_BitOrNode(o)
[:bit_or, *super]
end
def visit_BitXOrNode(o)
[:bit_xor, *super]
end
def visit_LogicalAndNode(o)
[:and, *super]
end
def visit_LogicalOrNode(o)
[:or, *super]
end
def visit_InNode(o)
[:in, *super]
end
def visit_DoWhileNode(o)
[:do_while, *super]
end
def visit_WhileNode(o)
[:while, *super]
end
def visit_WithNode(o)
[:with, *super]
end
def visit_CaseClauseNode(o)
[:case, *super]
end
def visit_CaseBlockNode(o)
[:case_block, super]
end
def visit_SwitchNode(o)
[:switch, *super]
end
def visit_ForNode(o)
[ :for, *super]
end
def visit_BlockNode(o)
[:block, super]
end
def visit_IfNode(o)
[:if, *super].compact
end
def visit_ConditionalNode(o)
[:conditional, *super]
end
def visit_ForInNode(o)
[ :for_in, *super]
end
def visit_TryNode(o)
[ :try, *super]
end
def visit_EmptyStatementNode(o)
[:empty]
end
def visit_FunctionBodyNode(o)
[:func_body, super]
end
def visit_ResolveNode(o)
[:resolve, o.value]
end
def visit_BracketAccessorNode(o)
[:bracket_access, *super]
end
def visit_NewExprNode(o)
[:new_expr, *super]
end
def visit_ParameterNode(o)
[:param, o.value]
end
def visit_BreakNode(o)
[:break, o.value].compact
end
def visit_ContinueNode(o)
[:continue, o.value].compact
end
def visit_LabelNode(o)
[:label ] + super
end
def visit_ThrowNode(o)
[:throw, super]
end
def visit_ObjectLiteralNode(o)
[:object, super]
end
def visit_PropertyNode(o)
[ :property ] + super
end
def visit_GetterPropertyNode(o)
[ :getter ] + super
end
def visit_SetterPropertyNode(o)
[ :setter ] + super
end
def visit_ElementNode(o)
[:element, super ]
end
def visit_ExpressionStatementNode(o)
[:expression, super ]
end
def visit_OpEqualNode(o)
[:op_equal, *super ]
end
def visit_OpPlusEqualNode(o)
[:op_plus_equal, *super ]
end
def visit_OpMinusEqualNode(o)
[:op_minus_equal, *super ]
end
def visit_OpMultiplyEqualNode(o)
[:op_multiply_equal, *super ]
end
def visit_OpDivideEqualNode(o)
[:op_divide_equal, *super]
end
def visit_OpLShiftEqualNode(o)
[:op_lshift_equal, *super ]
end
def visit_OpRShiftEqualNode(o)
[:op_rshift_equal, *super ]
end
def visit_OpURShiftEqualNode(o)
[:op_urshift_equal, *super ]
end
def visit_OpAndEqualNode(o)
[:op_and_equal, *super ]
end
def visit_OpXOrEqualNode(o)
[:op_xor_equal, *super ]
end
def visit_OpOrEqualNode(o)
[:op_or_equal, *super ]
end
def visit_OpModEqualNode(o)
[:op_mod_equal, *super]
end
def visit_CommaNode(o)
[:comma, *super]
end
def visit_FunctionCallNode(o)
[:function_call, *super]
end
def visit_ArrayNode(o)
[:array, super]
end
def visit_ThisNode(o)
[:this]
end
def visit_ReturnNode(o)
o.value ? [:return, super] : [:return]
end
def visit_FunctionExprNode(o)
[ :func_expr, *super]
end
def visit_FunctionDeclNode(o)
[ :func_decl, *super]
end
def visit_ArgumentsNode(o)
[:args, super]
end
def visit_DotAccessorNode(o)
[:dot_access,
super,
o.accessor
]
end
def visit_NullNode(o)
[:nil]
end
def visit_StringNode(o)
[:str, o.value]
end
def visit_FalseNode(o)
[:false]
end
def visit_TrueNode(o)
[:true]
end
end
end
end

View File

@ -0,0 +1,136 @@
module RKelly
module Visitors
class Visitor
TERMINAL_NODES = %w{
Break Continue EmptyStatement False Null Number Parameter Regexp Resolve
String This True
}
SINGLE_VALUE_NODES = %w{
Parenthetical AssignExpr BitwiseNot Block Delete Element ExpressionStatement
FunctionBody LogicalNot Return Throw TypeOf UnaryMinus UnaryPlus Void
}
BINARY_NODES = %w{
Add BitAnd BitOr BitXOr CaseClause Comma Divide DoWhile Equal Greater
GreaterOrEqual In InstanceOf LeftShift Less LessOrEqual LogicalAnd
LogicalOr Modulus Multiply NotEqual NotStrictEqual OpAndEqual
OpDivideEqual OpEqual OpLShiftEqual OpMinusEqual OpModEqual
OpMultiplyEqual OpOrEqual OpPlusEqual OpRShiftEqual OpURShiftEqual
OpXOrEqual RightShift StrictEqual Subtract Switch UnsignedRightShift
While With
}
ARRAY_VALUE_NODES = %w{
Arguments Array CaseBlock ConstStatement ObjectLiteral SourceElements
VarStatement
}
NAME_VALUE_NODES = %w{
Label Property GetterProperty SetterProperty VarDecl
}
PREFIX_POSTFIX_NODES = %w{ Postfix Prefix }
CONDITIONAL_NODES = %w{ If Conditional }
FUNC_CALL_NODES = %w{ NewExpr FunctionCall }
FUNC_DECL_NODES = %w{ FunctionExpr FunctionDecl }
ALL_NODES = %w{ For ForIn Try BracketAccessor DotAccessor } +
TERMINAL_NODES + SINGLE_VALUE_NODES + BINARY_NODES + ARRAY_VALUE_NODES +
NAME_VALUE_NODES + PREFIX_POSTFIX_NODES + CONDITIONAL_NODES +
FUNC_CALL_NODES + FUNC_DECL_NODES
def accept(target)
target.accept(self)
end
TERMINAL_NODES.each do |type|
define_method(:"visit_#{type}Node") { |o| o.value }
end
BINARY_NODES.each do |type|
define_method(:"visit_#{type}Node") do |o|
[o.left && o.left.accept(self), o.value && o.value.accept(self)]
end
end
ARRAY_VALUE_NODES.each do |type|
define_method(:"visit_#{type}Node") do |o|
o.value && o.value.map { |v| v ? v.accept(self) : nil }
end
end
NAME_VALUE_NODES.each do |type|
define_method(:"visit_#{type}Node") do |o|
[o.name.to_s.to_sym, o.value ? o.value.accept(self) : nil]
end
end
SINGLE_VALUE_NODES.each do |type|
define_method(:"visit_#{type}Node") do |o|
o.value.accept(self) if o.value
end
end
PREFIX_POSTFIX_NODES.each do |type|
define_method(:"visit_#{type}Node") do |o|
o.operand.accept(self)
end
end
CONDITIONAL_NODES.each do |type|
define_method(:"visit_#{type}Node") do |o|
[ o.conditions.accept(self),
o.value.accept(self),
o.else ? o.else.accept(self) : nil
]
end
end
FUNC_CALL_NODES.each do |type|
define_method(:"visit_#{type}Node") do |o|
[o.value.accept(self), o.arguments.accept(self)]
end
end
FUNC_DECL_NODES.each do |type|
define_method(:"visit_#{type}Node") do |o|
[
o.value ? o.value : nil,
o.arguments.map { |x| x.accept(self) },
o.function_body.accept(self)
]
end
end
def visit_ForNode(o)
[
o.init ? o.init.accept(self) : nil,
o.test ? o.test.accept(self) : nil,
o.counter ? o.counter.accept(self) : nil,
o.value.accept(self)
]
end
def visit_ForInNode(o)
[
o.left.accept(self),
o.right.accept(self),
o.value.accept(self)
]
end
def visit_TryNode(o)
[
o.value.accept(self),
o.catch_var ? o.catch_var : nil,
o.catch_block ? o.catch_block.accept(self) : nil,
o.finally_block ? o.finally_block.accept(self) : nil
]
end
def visit_BracketAccessorNode(o)
[
o.value.accept(self),
o.accessor.accept(self)
]
end
def visit_DotAccessorNode(o)
o.value.accept(self)
end
end
end
end