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/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/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 ]
- @@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
-
#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 |
#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.
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
168 169 170 171 172 173 |
# File 'lib/ruby2js/converter.rb', line 168 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
397 398 399 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 |
# File 'lib/ruby2js/converter/send.rb', line 397 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.
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/ruby2js/converter.rb', line 177 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 |
#es2022 ⇒ Object
163 164 165 |
# File 'lib/ruby2js/converter.rb', line 163 def es2022 @eslevel >= 2022 end |
#group(ast) ⇒ Object
238 239 240 241 242 243 244 |
# File 'lib/ruby2js/converter.rb', line 238 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.
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
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
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
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/ruby2js/converter.rb', line 204 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
225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/ruby2js/converter.rb', line 225 def parse_all(*args) @options = (Hash === args.last) ? args.pop : {} sep = @options[:join].to_s state = @options[: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
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 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 |
# File 'lib/ruby2js/converter/send.rb', line 439 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
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 |
# File 'lib/ruby2js/converter.rb', line 246 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? @timestamps[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 |