Class: Ruby2JS::Converter
- Inherits:
-
Serializer
- Object
- Serializer
- Ruby2JS::Converter
- Defined in:
- lib/ruby2js/converter.rb,
lib/ruby2js/converter/if.rb,
lib/ruby2js/converter/in.rb,
lib/ruby2js/converter/arg.rb,
lib/ruby2js/converter/def.rb,
lib/ruby2js/converter/for.rb,
lib/ruby2js/converter/nil.rb,
lib/ruby2js/converter/sym.rb,
lib/ruby2js/converter/var.rb,
lib/ruby2js/converter/args.rb,
lib/ruby2js/converter/case.rb,
lib/ruby2js/converter/cvar.rb,
lib/ruby2js/converter/defs.rb,
lib/ruby2js/converter/dstr.rb,
lib/ruby2js/converter/hash.rb,
lib/ruby2js/converter/ivar.rb,
lib/ruby2js/converter/next.rb,
lib/ruby2js/converter/self.rb,
lib/ruby2js/converter/send.rb,
lib/ruby2js/converter/xstr.rb,
lib/ruby2js/converter/array.rb,
lib/ruby2js/converter/begin.rb,
lib/ruby2js/converter/block.rb,
lib/ruby2js/converter/break.rb,
lib/ruby2js/converter/casgn.rb,
lib/ruby2js/converter/class.rb,
lib/ruby2js/converter/const.rb,
lib/ruby2js/converter/masgn.rb,
lib/ruby2js/converter/super.rb,
lib/ruby2js/converter/undef.rb,
lib/ruby2js/converter/until.rb,
lib/ruby2js/converter/vasgn.rb,
lib/ruby2js/converter/while.rb,
lib/ruby2js/converter/xnode.rb,
lib/ruby2js/converter/yield.rb,
lib/ruby2js/converter/class2.rb,
lib/ruby2js/converter/cvasgn.rb,
lib/ruby2js/converter/import.rb,
lib/ruby2js/converter/ivasgn.rb,
lib/ruby2js/converter/module.rb,
lib/ruby2js/converter/nthref.rb,
lib/ruby2js/converter/opasgn.rb,
lib/ruby2js/converter/regexp.rb,
lib/ruby2js/converter/return.rb,
lib/ruby2js/converter/taglit.rb,
lib/ruby2js/converter/boolean.rb,
lib/ruby2js/converter/defined.rb,
lib/ruby2js/converter/kwbegin.rb,
lib/ruby2js/converter/literal.rb,
lib/ruby2js/converter/logical.rb,
lib/ruby2js/converter/fileline.rb,
lib/ruby2js/converter/blockpass.rb,
lib/ruby2js/converter/prototype.rb,
lib/ruby2js/converter/untilpost.rb,
lib/ruby2js/converter/whilepost.rb
Constant Summary collapse
- LOGICAL =
:and, :not, :or
- OPERATORS =
[:[], :[]=], [:not, :!], [:**], [:*, :/, :%], [:+, :-], [:>>, :<<], [:&], [:^, :|], [:<=, :<, :>, :>=], [:==, :!=, :===, :"!=="], [:and, :or]
- INVERT_OP =
{ :< => :>=, :<= => :>, :== => :!=, :!= => :==, :> => :<=, :>= => :<, :=== => :'!==' }
- GROUP_OPERATORS =
[:begin, :dstr, :dsym, :and, :or, :casgn, :if]
- VASGN =
[:cvasgn, :ivasgn, :gvasgn, :lvasgn]
- EXPRESSIONS =
[ :array, :float, :hash, :int, :lvar, :nil, :send, :attr, :str, :sym, :dstr, :dsym, :cvar, :ivar, :zsuper, :super, :or, :and, :block, :const, :true, :false, :xnode, :taglit, :self ]
- @@handlers =
[]
Constants inherited from Serializer
Instance Attribute Summary collapse
-
#ast ⇒ Object
Returns the value of attribute ast.
-
#binding ⇒ Object
Returns the value of attribute binding.
-
#comparison ⇒ Object
Returns the value of attribute comparison.
-
#eslevel ⇒ Object
Returns the value of attribute eslevel.
-
#ivars ⇒ Object
Returns the value of attribute ivars.
-
#module_type ⇒ Object
Returns the value of attribute module_type.
-
#or ⇒ Object
Returns the value of attribute or.
-
#strict ⇒ Object
Returns the value of attribute strict.
-
#underscored_private ⇒ Object
Returns the value of attribute underscored_private.
Attributes inherited from Serializer
Class Method Summary collapse
Instance Method Summary collapse
-
#collapse_strings(node) ⇒ Object
do string concatenation when possible.
- #combine_properties(body) ⇒ Object
-
#comments(ast) ⇒ Object
extract comments that either precede or are included in the node.
-
#conditionally_equals(left, right) ⇒ Object
determine if two trees are identical, modulo conditionalilties in other words a.b == a&.b.
- #convert ⇒ Object
- #es2015 ⇒ Object
- #es2016 ⇒ Object
- #es2017 ⇒ Object
- #es2018 ⇒ Object
- #es2019 ⇒ Object
- #es2020 ⇒ Object
- #es2021 ⇒ Object
- #group(ast) ⇒ Object
-
#hoist?(outer, inner, name) ⇒ Boolean
is ‘name’ referenced outside of inner scope?.
-
#initialize(ast, comments, vars = {}) ⇒ Converter
constructor
A new instance of Converter.
-
#jscope(ast, args = nil) ⇒ Object
handle the oddity where javascript considers there to be a scope (e.g. the body of an if statement), whereas Ruby does not.
- #multi_assign_declarations ⇒ Object
- #number_format(number) ⇒ Object
- #operator_index(op) ⇒ Object
- #parse(ast, state = :expression) ⇒ Object
- #parse_all(*args) ⇒ Object
- #range_to_array(node) ⇒ Object
-
#rewrite(left, right) ⇒ Object
rewrite a && a.b to a&.b.
- #s(type, *args) ⇒ Object
-
#scope(ast, args = nil) ⇒ Object
define a new scope; primarily determines what variables are visible and deals with hoisting of declarations.
- #timestamp(file) ⇒ Object
- #transform_defs(target, method, args, body) ⇒ Object
- #width=(width) ⇒ Object
Methods inherited from Serializer
#+, #capture, #compact, #enable_vertical_whitespace, #insert, #mtime, #output_location, #put, #put!, #puts, #reindent, #respace, #sourcemap, #sput, #to_s, #to_str, #uptodate?, #vlq, #wrap
Constructor Details
#initialize(ast, comments, vars = {}) ⇒ Converter
Returns a new instance of Converter.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/ruby2js/converter.rb', line 39 def initialize( ast, comments, vars = {} ) super() @ast, @comments, @vars = ast, comments, vars.dup @varstack = [] @scope = ast @inner = nil @rbstack = [] @next_token = :return @handlers = {} @@handlers.each do |name| @handlers[name] = method("on_#{name}") end @state = nil @block_this = nil @block_depth = nil @prop = nil @instance_method = nil @prototype = nil @class_parent = nil @class_name = nil @jsx = false @eslevel = :es5 @strict = false @comparison = :equality @or = :logical @underscored_private = true end |
Instance Attribute Details
#ast ⇒ Object
Returns the value of attribute ast.
16 17 18 |
# File 'lib/ruby2js/converter.rb', line 16 def ast @ast end |
#binding ⇒ Object
Returns the value of attribute binding.
37 38 39 |
# File 'lib/ruby2js/converter.rb', line 37 def binding @binding end |
#comparison ⇒ Object
Returns the value of attribute comparison.
133 134 135 |
# File 'lib/ruby2js/converter.rb', line 133 def comparison @comparison end |
#eslevel ⇒ Object
Returns the value of attribute eslevel.
133 134 135 |
# File 'lib/ruby2js/converter.rb', line 133 def eslevel @eslevel end |
#ivars ⇒ Object
Returns the value of attribute ivars.
37 38 39 |
# File 'lib/ruby2js/converter.rb', line 37 def ivars @ivars end |
#module_type ⇒ Object
Returns the value of attribute module_type.
133 134 135 |
# File 'lib/ruby2js/converter.rb', line 133 def module_type @module_type end |
#or ⇒ Object
Returns the value of attribute or.
133 134 135 |
# File 'lib/ruby2js/converter.rb', line 133 def or @or end |
#strict ⇒ Object
Returns the value of attribute strict.
133 134 135 |
# File 'lib/ruby2js/converter.rb', line 133 def strict @strict end |
#underscored_private ⇒ Object
Returns the value of attribute underscored_private.
133 134 135 |
# File 'lib/ruby2js/converter.rb', line 133 def underscored_private @underscored_private end |
Class Method Details
.handle(*types, &block) ⇒ Object
164 165 166 167 168 169 |
# File 'lib/ruby2js/converter.rb', line 164 def self.handle(*types, &block) types.each do |type| define_method("on_#{type}", block) @@handlers << type end end |
Instance Method Details
#collapse_strings(node) ⇒ Object
do string concatenation when possible
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 |
# File 'lib/ruby2js/converter/send.rb', line 358 def collapse_strings(node) left = node.children[0] return node unless left right = node.children[2] # recursively evaluate left hand side if \ left.type == :send and left.children.length == 3 and left.children[1] == :+ then left = collapse_strings(left) end # recursively evaluate right hand side if \ right.type == :send and right.children.length == 3 and right.children[1] == :+ then right = collapse_strings(right) end # if left and right are both strings, perform concatenation if [:dstr, :str].include? left.type and [:dstr, :str].include? right.type if left.type == :str and right.type == :str return left.updated nil, [left.children.first + right.children.first] else left = s(:dstr, left) if left.type == :str right = s(:dstr, right) if right.type == :str return left.updated(nil, left.children + right.children) end end # if left and right are unchanged, return original node; otherwise # return node modified to include new left and/or right hand sides. if left == node.children[0] and right == node.children[2] return node else return node.updated(nil, [left, :+, right]) end end |
#combine_properties(body) ⇒ Object
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/ruby2js/converter/begin.rb', line 34 def combine_properties(body) for i in 0...body.length-1 next unless body[i] and body[i].type == :prop for j in i+1...body.length break unless body[j] and body[j].type == :prop if body[i].children[0] == body[j].children[0] # relocate property comment to first method [body[i], body[j]].each do |node| unless @comments[node].empty? node.children[1].values.first.each do |key, value| if [:get, :set].include? key and Parser::AST::Node === value @comments[value] = @comments[node] break end end end end # merge properties merge = Hash[(body[i].children[1].to_a+body[j].children[1].to_a). group_by {|name, value| name.to_s}.map {|name, values| [name, values.map(&:last).reduce(:merge)]}] body[j] = s(:prop, body[j].children[0], merge) body[i] = nil break end end end end |
#comments(ast) ⇒ Object
extract comments that either precede or are included in the node. remove from the list this node may appear later in the tree.
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/ruby2js/converter.rb', line 173 def comments(ast) if ast.loc and ast.loc.respond_to? :expression expression = ast.loc.expression list = @comments[ast].select do |comment| expression.source_buffer == comment.loc.expression.source_buffer and comment.loc.expression.begin_pos < expression.end_pos end else list = @comments[ast] end @comments[ast] -= list list.map do |comment| if comment.text.start_with? '=begin' if comment.text.include? '*/' comment.text.sub(/\A=begin/, '').sub(/^=end\Z/, '').gsub(/^/, '//') else comment.text.sub(/\A=begin/, '/*').sub(/^=end\Z/, '*/') end else comment.text.sub(/^#/, '//') + "\n" end end end |
#conditionally_equals(left, right) ⇒ Object
determine if two trees are identical, modulo conditionalilties in other words a.b == a&.b
89 90 91 92 93 94 95 96 97 98 |
# File 'lib/ruby2js/converter/logical.rb', line 89 def conditionally_equals(left, right) if left == right true elsif !left.respond_to?(:type) or !left or !right or left.type != :csend or right.type != :send false else conditionally_equals(left.children.first, right.children.first) && conditionally_equals(left.children.last, right.children.last) end end |
#convert ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/ruby2js/converter.rb', line 75 def convert scope @ast if @strict if @sep == '; ' @lines.first.unshift "\"use strict\"#@sep" else @lines.unshift Line.new('"use strict";') end end end |
#es2015 ⇒ Object
135 136 137 |
# File 'lib/ruby2js/converter.rb', line 135 def es2015 @eslevel >= 2015 end |
#es2016 ⇒ Object
139 140 141 |
# File 'lib/ruby2js/converter.rb', line 139 def es2016 @eslevel >= 2016 end |
#es2017 ⇒ Object
143 144 145 |
# File 'lib/ruby2js/converter.rb', line 143 def es2017 @eslevel >= 2017 end |
#es2018 ⇒ Object
147 148 149 |
# File 'lib/ruby2js/converter.rb', line 147 def es2018 @eslevel >= 2018 end |
#es2019 ⇒ Object
151 152 153 |
# File 'lib/ruby2js/converter.rb', line 151 def es2019 @eslevel >= 2019 end |
#es2020 ⇒ Object
155 156 157 |
# File 'lib/ruby2js/converter.rb', line 155 def es2020 @eslevel >= 2020 end |
#es2021 ⇒ Object
159 160 161 |
# File 'lib/ruby2js/converter.rb', line 159 def es2021 @eslevel >= 2021 end |
#group(ast) ⇒ Object
234 235 236 237 238 239 240 |
# File 'lib/ruby2js/converter.rb', line 234 def group( ast ) if [:dstr, :dsym].include? ast.type and es2015 parse ast else put '('; parse ast; put ')' end end |
#hoist?(outer, inner, name) ⇒ Boolean
is ‘name’ referenced outside of inner scope?
63 64 65 66 67 68 69 70 |
# File 'lib/ruby2js/converter/vasgn.rb', line 63 def hoist?(outer, inner, name) outer.children.each do |var| next if var == inner return true if var == name and [:lvar, :gvar].include? outer.type return true if Parser::AST::Node === var and hoist?(var, inner, name) end return false end |
#jscope(ast, args = nil) ⇒ Object
handle the oddity where javascript considers there to be a scope (e.g. the body of an if statement), whereas Ruby does not.
117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/ruby2js/converter.rb', line 117 def jscope( ast, args=nil ) @varstack.push @vars @vars = args if args @vars = Hash[@vars.map {|key, value| [key, true]}] parse( ast, :statement ) ensure pending = @vars.select {|key, value| value == :pending} @vars = @varstack.pop @vars.merge! pending end |
#multi_assign_declarations ⇒ Object
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/ruby2js/converter/vasgn.rb', line 72 def multi_assign_declarations undecls = [] child = @ast loop do if [:send, :casgn].include? child.type subchild = child.children[2] else subchild = child.children[1] end if subchild.type == :send break unless subchild.children[1] =~ /=$/ else break unless [:send, :cvasgn, :ivasgn, :gvasgn, :lvasgn]. include? subchild.type end child = subchild if child.type == :lvasgn and not @vars.include?(child.children[0]) undecls << child.children[0] end end unless undecls.empty? if es2015 put "let " else put "var " end put "#{undecls.map(&:to_s).join(', ')}#@sep" end end |
#number_format(number) ⇒ Object
20 21 22 23 24 25 26 |
# File 'lib/ruby2js/converter/literal.rb', line 20 def number_format(number) return number.to_s unless es2021 parts = number.to_s.split('.') parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1_") parts[1].gsub!(/(\d\d\d)(?=\d)/, "\\1_") if parts[1] parts.join('.') end |
#operator_index(op) ⇒ Object
87 88 89 |
# File 'lib/ruby2js/converter.rb', line 87 def operator_index op OPERATORS.index( OPERATORS.find{ |el| el.include? op } ) || -1 end |
#parse(ast, state = :expression) ⇒ Object
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/ruby2js/converter.rb', line 200 def parse(ast, state=:expression) oldstate, @state = @state, state oldast, @ast = @ast, ast return unless ast handler = @handlers[ast.type] unless handler raise Error.new("unknown AST type #{ ast.type }", ast) end if state == :statement and not @comments[ast].empty? comments(ast).each {|comment| puts comment.chomp} end handler.call(*ast.children) ensure @ast = oldast @state = oldstate end |
#parse_all(*args) ⇒ Object
221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/ruby2js/converter.rb', line 221 def parse_all(*args) = (Hash === args.last) ? args.pop : {} sep = [:join].to_s state = [:state] || :expression index = 0 args.each do |arg| put sep unless index == 0 parse arg, state index += 1 unless arg == s(:begin) end end |
#range_to_array(node) ⇒ Object
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 |
# File 'lib/ruby2js/converter/send.rb', line 400 def range_to_array(node) start, finish = node.children if start.type == :int and start.children.first == 0 # Ranges which start from 0 can be achieved with more simpler code if finish.type == :int # output cleaner code if we know the value already length = finish.children.first + (node.type == :irange ? 1 : 0) else # If this is variable we need to fix indexing by 1 in js length = "#{finish.children.last}" + (node.type == :irange ? "+1" : "") end if es2015 return put "[...Array(#{length}).keys()]" else return put "Array.apply(null, {length: #{length}}).map(Function.call, Number)" end else # Use .compact because the first argument is nil with variables # This way the first value is always set start_value = start.children.compact.first finish_value = finish.children.compact.first if start.type == :int and finish.type == :int length = finish_value - start_value + (node.type == :irange ? 1 : 0) else length = "(#{finish_value}-#{start_value}" + (node.type == :irange ? "+1" : "") + ")" end # Avoid of using same variables in the map as used in the irange or elsewhere in this code # Ruby2js only allows dollar sign in beginning of variable so i$ is safe if @vars.include? :idx or start_value == :idx or finish_value == :idx index_var = 'i$' else index_var = 'idx' end if es2015 # Use _ because it's normal convention in JS for variable which is not used at all if @vars.include? :_ or start_value == :_ or finish_value == :_ blank = '_$' else blank = '_' end return put "Array.from({length: #{length}}, (#{blank}, #{index_var}) => #{index_var}+#{start_value})" else return put "Array.apply(null, {length: #{length}}).map(Function.call, Number).map(function (#{index_var}) { return #{index_var}+#{start_value} })" end end end |
#rewrite(left, right) ⇒ Object
rewrite a && a.b to a&.b
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/ruby2js/converter/logical.rb', line 67 def rewrite(left, right) if left && left.type == :and left = rewrite(*left.children) end if right.type != :send s(:and, left, right) elsif conditionally_equals(left, right.children.first) # a && a.b => a&.b right.updated(:csend, [left, right.children.last]) elsif conditionally_equals(left.children.last, right.children.first) # a && b && b.c => a && b&.c left.updated(:and, [left.children.first, left.children.last.updated(:csend, [left.children.last, right.children.last])]) else s(:and, left, right) end end |
#s(type, *args) ⇒ Object
129 130 131 |
# File 'lib/ruby2js/converter.rb', line 129 def s(type, *args) Parser::AST::Node.new(type, args) end |
#scope(ast, args = nil) ⇒ Object
define a new scope; primarily determines what variables are visible and deals with hoisting of declarations
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/ruby2js/converter.rb', line 93 def scope( ast, args=nil ) scope, @scope = @scope, ast inner, @inner = @inner, nil mark = output_location @varstack.push @vars @vars = args if args @vars = Hash[@vars.map {|key, value| [key, true]}] parse( ast, :statement ) # retroactively add a declaration for 'pending' variables vars = @vars.select {|key, value| value == :pending}.keys unless vars.empty? insert mark, "#{es2015 ? 'let' : 'var'} #{vars.join(', ')}#{@sep}" vars.each {|var| @vars[var] = true} end ensure @vars = @varstack.pop @scope = scope @inner = inner end |
#timestamp(file) ⇒ Object
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/ruby2js/converter.rb', line 242 def (file) super return unless file walk = proc do |ast| if ast.loc and ast.loc.expression filename = ast.loc.expression.source_buffer.name if filename and not filename.empty? [filename] ||= File.mtime(filename) rescue nil end end ast.children.each do |child| walk[child] if child.is_a? Parser::AST::Node end end walk[@ast] if @ast end |
#transform_defs(target, method, args, body) ⇒ Object
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/ruby2js/converter/defs.rb', line 21 def transform_defs(target, method, args, body) if not @ast.is_method? or @ast.type == :defp node = s(:prop, target, method.to_s => {enumerable: s(:true), configurable: s(:true), get: s(:block, s(:send, nil, :proc), args, s(:autoreturn, body))}) elsif method =~ /=$/ node = s(:prop, target, method.to_s.sub('=', '') => {enumerable: s(:true), configurable: s(:true), set: s(:block, s(:send, nil, :proc), args, body)}) else node = s(:send, target, "#{method}=", s(:def, nil, args, body)) end @comments[node] = @comments[@ast] if @comments[@ast] node end |
#width=(width) ⇒ Object
71 72 73 |
# File 'lib/ruby2js/converter.rb', line 71 def width=(width) @width = width end |