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/hide.rb,
lib/ruby2js/converter/ivar.rb,
lib/ruby2js/converter/next.rb,
lib/ruby2js/converter/redo.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/match.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/assign.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, :op_asgn, :and_asgn, :or_asgn, :taglit, :gvar, :csend, :call ]
- @@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.
-
#namespace ⇒ Object
Returns the value of attribute namespace.
-
#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
- #es2022 ⇒ 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
- #redoable(block) ⇒ 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 70 71 |
# 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 @autobind = true @eslevel = :es5 @strict = false @comparison = :equality @or = :logical @underscored_private = true @redoable = false 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.
135 136 137 |
# File 'lib/ruby2js/converter.rb', line 135 def comparison @comparison end |
#eslevel ⇒ Object
Returns the value of attribute eslevel.
135 136 137 |
# File 'lib/ruby2js/converter.rb', line 135 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.
135 136 137 |
# File 'lib/ruby2js/converter.rb', line 135 def module_type @module_type end |
#namespace ⇒ Object
Returns the value of attribute namespace.
37 38 39 |
# File 'lib/ruby2js/converter.rb', line 37 def namespace @namespace end |
#or ⇒ Object
Returns the value of attribute or.
135 136 137 |
# File 'lib/ruby2js/converter.rb', line 135 def or @or end |
#strict ⇒ Object
Returns the value of attribute strict.
135 136 137 |
# File 'lib/ruby2js/converter.rb', line 135 def strict @strict end |
#underscored_private ⇒ Object
Returns the value of attribute underscored_private.
135 136 137 |
# File 'lib/ruby2js/converter.rb', line 135 def underscored_private @underscored_private end |
Class Method Details
.handle(*types, &block) ⇒ Object
170 171 172 173 174 175 |
# File 'lib/ruby2js/converter.rb', line 170 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
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 450 451 452 453 454 455 456 457 458 459 460 461 462 463 |
# File 'lib/ruby2js/converter/send.rb', line 423 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
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 |
# File 'lib/ruby2js/converter/begin.rb', line 39 def combine_properties(body) (0...body.length-1).each do |i| next unless body[i] and body[i].type == :prop (i+1...body.length).each do |j| 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.
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/ruby2js/converter.rb', line 179 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
77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/ruby2js/converter.rb', line 77 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
137 138 139 |
# File 'lib/ruby2js/converter.rb', line 137 def es2015 @eslevel >= 2015 end |
#es2016 ⇒ Object
141 142 143 |
# File 'lib/ruby2js/converter.rb', line 141 def es2016 @eslevel >= 2016 end |
#es2017 ⇒ Object
145 146 147 |
# File 'lib/ruby2js/converter.rb', line 145 def es2017 @eslevel >= 2017 end |
#es2018 ⇒ Object
149 150 151 |
# File 'lib/ruby2js/converter.rb', line 149 def es2018 @eslevel >= 2018 end |
#es2019 ⇒ Object
153 154 155 |
# File 'lib/ruby2js/converter.rb', line 153 def es2019 @eslevel >= 2019 end |
#es2020 ⇒ Object
157 158 159 |
# File 'lib/ruby2js/converter.rb', line 157 def es2020 @eslevel >= 2020 end |
#es2021 ⇒ Object
161 162 163 |
# File 'lib/ruby2js/converter.rb', line 161 def es2021 @eslevel >= 2021 end |
#es2022 ⇒ Object
165 166 167 |
# File 'lib/ruby2js/converter.rb', line 165 def es2022 @eslevel >= 2022 end |
#group(ast) ⇒ Object
240 241 242 243 244 245 246 |
# File 'lib/ruby2js/converter.rb', line 240 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?
68 69 70 71 72 73 74 75 |
# File 'lib/ruby2js/converter/vasgn.rb', line 68 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.
119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/ruby2js/converter.rb', line 119 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
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 105 106 107 108 109 |
# File 'lib/ruby2js/converter/vasgn.rb', line 77 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] = parts[0].gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1_") parts[1] = parts[1].gsub(/(\d\d\d)(?=\d)/, "\\1_") if parts[1] parts.join('.') end |
#operator_index(op) ⇒ Object
89 90 91 |
# File 'lib/ruby2js/converter.rb', line 89 def operator_index op OPERATORS.index( OPERATORS.find{ |el| el.include? op } ) || -1 end |
#parse(ast, state = :expression) ⇒ Object
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/ruby2js/converter.rb', line 206 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
227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/ruby2js/converter.rb', line 227 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
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 |
# File 'lib/ruby2js/converter/send.rb', line 465 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 |
#redoable(block) ⇒ Object
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/ruby2js/converter.rb', line 248 def redoable(block) save_redoable = @redoable has_redo = proc do |node| node.children.any? do |child| next false unless child.is_a? Parser::AST::Node next true if child.type == :redo next false if i[for while while_post until until_post].include? child.type has_redo[child] end end @redoable = has_redo[@ast] if @redoable put es2015 ? 'let ' : 'var ' put "redo$#@sep" puts 'do {' put "redo$ = false#@sep" scope block put "#@nl} while(redo$)" else scope block end ensure @redoable = save_redoable 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 or OPERATORS.flatten.include? right.children[1] s(:and, left, right) elsif conditionally_equals(left, right.children.first) # a && a.b => a&.b right.updated(:csend, [left, *right.children[1..-1]]) 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[1..-1]])]) else s(:and, left, right) end end |
#s(type, *args) ⇒ Object
131 132 133 |
# File 'lib/ruby2js/converter.rb', line 131 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
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/ruby2js/converter.rb', line 95 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
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/ruby2js/converter.rb', line 276 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
73 74 75 |
# File 'lib/ruby2js/converter.rb', line 73 def width=(width) @width = width end |