421 lines
12 KiB
Ruby
421 lines
12 KiB
Ruby
# -*- coding: binary -*-
|
|
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
|