|
#!/usr/bin/env rbx |
|
require 'set' |
|
|
|
module Rubinius |
|
module AST |
|
class Node |
|
# Public: Works like #visit, but it doesn't visit the children just yet; |
|
# instead, lets the visitor decide when and how to do it. |
|
# |
|
# visitor - The visitor object. It must respond to methods named after the |
|
# node names. |
|
# |
|
# Returns nothing. |
|
def lazy_visit(visitor, parent=nil, indent=false) |
|
args = [self.node_name, self, parent] |
|
args.push true if indent |
|
|
|
visitor.__send__ *args |
|
end |
|
end |
|
end |
|
end |
|
|
|
module RubyScript |
|
VERSION = '0.0.1' |
|
|
|
class Visitor |
|
def initialize |
|
@locals = Set.new |
|
@output = [] |
|
@indentation = 1 |
|
end |
|
|
|
def emit(code) |
|
@output.push code |
|
end |
|
|
|
def finalize |
|
@output.unshift header |
|
emit footer |
|
|
|
@output.join |
|
end |
|
|
|
def header |
|
"(function() {\n" << |
|
local_declarations |
|
end |
|
|
|
def local_declarations |
|
if @locals.any? |
|
current_indentation << |
|
"// Declare local variables\n" << |
|
current_indentation << |
|
"var #{@locals.to_a.join(', ')};\n\n" |
|
else |
|
"" |
|
end |
|
end |
|
|
|
def footer |
|
"\n})();" |
|
end |
|
|
|
def current_indentation |
|
' ' * @indentation |
|
end |
|
|
|
def local_variable_assignment(node, parent) |
|
emit "%s = " % node.name |
|
node.value.lazy_visit self, node |
|
@locals << node.name |
|
end |
|
|
|
def local_variable_access(node, parent) |
|
emit node.name |
|
end |
|
|
|
def instance_variable_assignment(node, parent) |
|
emit "this.%s = " % node.name |
|
node.value.lazy_visit self, node |
|
end |
|
|
|
def instance_variable_access(node, parent) |
|
emit "this.%s" % node.name[1..-1] |
|
end |
|
|
|
def fixnum_literal(node, parent) |
|
emit node.value.to_s |
|
end |
|
|
|
def float_literal(node, parent) |
|
emit node.value.to_s |
|
end |
|
|
|
def string_literal(node, parent) |
|
emit '"' << node.string.to_s << '"' |
|
end |
|
|
|
def symbol_literal(node, parent) |
|
emit '"' << node.value.to_s << '"' |
|
end |
|
|
|
def true_literal(node, parent) |
|
emit 'true' |
|
end |
|
|
|
def false_literal(node, parent) |
|
emit 'false' |
|
end |
|
|
|
def nil_literal(node, parent) |
|
emit 'undefined' |
|
end |
|
|
|
def array_literal(node, parent) |
|
body = node.body |
|
|
|
emit '[' |
|
body.each_with_index do |node, index| |
|
node.lazy_visit self, node |
|
emit ', ' unless body.length == index + 1 # last element |
|
end |
|
emit ']' |
|
end |
|
|
|
def hash_literal(node, parent) |
|
body = node.array.each_slice(2) |
|
|
|
emit "{\n" |
|
@indentation += 1 |
|
body.each_with_index do |slice, index| |
|
key, value = slice |
|
|
|
emit current_indentation |
|
|
|
emit key.value |
|
emit ": " |
|
value.lazy_visit self, node |
|
|
|
emit ",\n" unless body.to_a.length == index + 1 # last element |
|
end |
|
@indentation -= 1 |
|
emit "\n" |
|
emit current_indentation |
|
emit '}' |
|
end |
|
|
|
def regex_literal(node, parent) |
|
emit '/' |
|
emit node.source |
|
emit '/' |
|
end |
|
|
|
def send(node, parent) |
|
unless node.receiver.is_a?(Rubinius::AST::Self) |
|
node.receiver.lazy_visit self, node |
|
emit '.' |
|
end |
|
unless node.name == :lambda |
|
emit node.name |
|
end |
|
|
|
if node.block |
|
emit ' ' unless node.name == :lambda |
|
node.block.lazy_visit self, node if node.block |
|
end |
|
end |
|
|
|
def send_with_arguments(node, parent) |
|
return if process_binary_operator(node, parent) # 1 * 2, a / 3, true && false |
|
|
|
unless node.receiver.is_a?(Rubinius::AST::Self) |
|
node.receiver.lazy_visit self, node |
|
emit '.' |
|
end |
|
|
|
emit node.name |
|
emit '(' |
|
node.arguments.lazy_visit self, node |
|
emit ')' |
|
if node.block |
|
emit ' ' |
|
node.block.lazy_visit self, node if node.block |
|
end |
|
end |
|
|
|
def actual_arguments(node, parent) |
|
body = node.array |
|
body.each_with_index do |argument, index| |
|
argument.lazy_visit self, parent |
|
emit ', ' unless body.length == index + 1 # last element |
|
end |
|
end |
|
|
|
def formal_arguments19(node, parent) |
|
names = node.names |
|
|
|
names.each_with_index do |argument, index| |
|
emit argument.to_s |
|
emit ', ' unless names.length == index + 1 # last element |
|
end |
|
end |
|
|
|
def iter(node, parent) |
|
emit 'function(' |
|
|
|
if node.arguments && node.arguments.arity != -1 |
|
node.arguments.lazy_visit self, parent |
|
end |
|
|
|
emit ") {\n" |
|
|
|
@indentation += 1 |
|
|
|
if node.body.is_a?(Rubinius::AST::Block) |
|
node.body.lazy_visit self, parent, true |
|
else |
|
emit current_indentation |
|
emit 'return ' |
|
node.body.lazy_visit self, parent |
|
emit ';' |
|
end |
|
|
|
emit "\n" |
|
@indentation -=1 |
|
emit current_indentation |
|
emit '}' |
|
end |
|
|
|
def iter19(node, parent) |
|
iter(node, parent) |
|
end |
|
|
|
def block(node, parent, indent=false) |
|
body = node.array |
|
body.each_with_index do |expression, index| |
|
last = body.length == index + 1 |
|
|
|
emit current_indentation if indent |
|
emit 'return ' if last |
|
|
|
expression.lazy_visit self, parent |
|
emit ";" |
|
emit "\n" unless last |
|
end |
|
end |
|
|
|
def and(node, parent) |
|
node.left.lazy_visit self, node |
|
emit ' && ' |
|
node.right.lazy_visit self, node |
|
end |
|
|
|
def or(node, parent) |
|
node.left.lazy_visit self, node |
|
emit ' || ' |
|
node.right.lazy_visit self, node |
|
end |
|
|
|
def op_assign_and(node, parent) |
|
node.left.lazy_visit self, node |
|
emit ' && ' |
|
node.right.lazy_visit self, node |
|
end |
|
|
|
def op_assign_or(node, parent) |
|
node.left.lazy_visit self, node |
|
emit ' || ' |
|
node.right.lazy_visit self, node |
|
end |
|
|
|
def constant_access(node, parent) |
|
emit node.name |
|
end |
|
|
|
def if(node, parent) |
|
body, else_body = node.body, node.else |
|
keyword = 'if (' |
|
|
|
_unless = false |
|
|
|
if node.body.is_a?(Rubinius::AST::NilLiteral) && !node.else.is_a?(Rubinius::AST::NilLiteral) |
|
body, else_body = else_body, body |
|
_unless = true |
|
end |
|
|
|
emit keyword |
|
emit '!(' if _unless |
|
node.condition.lazy_visit self, node |
|
emit ')' if _unless |
|
emit ') {' |
|
emit "\n" |
|
|
|
@indentation += 1 |
|
|
|
if body.is_a?(Rubinius::AST::Block) |
|
body.lazy_visit self, parent, true |
|
else |
|
emit current_indentation |
|
emit 'return ' |
|
body.lazy_visit self, parent |
|
emit ';' |
|
end |
|
|
|
emit "\n" |
|
|
|
if else_body.is_a?(Rubinius::AST::NilLiteral) |
|
@indentation -=1 |
|
emit current_indentation |
|
emit '}' |
|
return |
|
else |
|
@indentation -=1 |
|
emit current_indentation |
|
emit "}" |
|
end |
|
|
|
emit " else {\n" |
|
|
|
@indentation +=1 |
|
|
|
if else_body.is_a?(Rubinius::AST::Block) |
|
else_body.lazy_visit self, parent, true |
|
else |
|
emit current_indentation |
|
else_body.lazy_visit self, parent |
|
end |
|
|
|
emit "\n" |
|
@indentation -=1 |
|
emit current_indentation |
|
emit '}' |
|
end |
|
|
|
def while(node, parent, reverse=false) |
|
emit 'while (' |
|
emit '!(' if reverse |
|
node.condition.lazy_visit self, node |
|
emit ')' if reverse |
|
emit ') {' |
|
emit "\n" |
|
|
|
@indentation += 1 |
|
|
|
if node.body.is_a?(Rubinius::AST::Block) |
|
node.body.lazy_visit self, parent, true |
|
else |
|
emit current_indentation |
|
emit 'return ' |
|
node.body.lazy_visit self, parent |
|
emit ';' |
|
end |
|
|
|
emit "\n" |
|
@indentation -=1 |
|
emit current_indentation |
|
emit '}' |
|
end |
|
|
|
def until(node, parent) |
|
__send__ :while, node, parent, true |
|
end |
|
|
|
def return(node, parent) |
|
emit 'return ' |
|
node.value.lazy_visit self, parent |
|
end |
|
|
|
def method_missing(m, *args, &block) |
|
raise "RubyScript doesn't support #{m}." |
|
end |
|
|
|
private |
|
|
|
def process_binary_operator(node, parent) |
|
operators = %w(+ - * / & | <<).map(&:to_sym) |
|
return false unless operators.include?(node.name) |
|
return false if node.arguments.array.length != 1 |
|
|
|
operand = node.arguments.array[0] |
|
|
|
unless node.receiver.is_a?(Rubinius::AST::Self) |
|
node.receiver.lazy_visit self, node |
|
end |
|
|
|
emit ' ' << node.name.to_s << ' ' |
|
operand.lazy_visit self, node |
|
end |
|
end |
|
end |
|
|
|
unless ARGV.first |
|
puts "RubyScript #{RubyScript::VERSION}\n================" |
|
puts "\tUsage: rubyscript my_file.rb" |
|
exit(1) |
|
end |
|
|
|
AST = Rubinius::Melbourne19.parse_file(ARGV[0]) |
|
visitor = RubyScript::Visitor.new |
|
AST.lazy_visit(visitor, AST, true) |
|
puts visitor.finalize |