Class: Opal::Compiler
- Inherits:
-
Object
- Object
- Opal::Compiler
- Defined in:
- opal/lib/opal/compiler.rb
Overview
Constant Summary
- INDENT =
Generated code gets indented with two spaces on each scope
' '
- COMPARE =
All compare method nodes - used to optimize performance of math comparisons
%w[< > <= >=]
Instance Attribute Summary collapse
-
#case_stmt ⇒ Object
readonly
Current case_stmt.
-
#comments ⇒ Object
readonly
Comments from the source code.
-
#eof_content ⇒ Object
readonly
Any content in END special construct.
-
#fragments ⇒ Array
readonly
All [Opal::Fragment] used to produce result.
-
#result ⇒ String
readonly
The compiled ruby code.
-
#scope ⇒ Object
Current scope.
Class Method Summary collapse
-
.compiler_option(name, default_value, options = {}) ⇒ Object
defines a compiler option, also creating method of form 'name?'.
Instance Method Summary collapse
-
#arity_check? ⇒ Boolean
adds an arity check to every method definition.
-
#compile ⇒ String
Compile some ruby code to a string.
-
#dynamic_require_severity ⇒ Object
how to handle dynamic requires (:error, :warning, :ignore).
-
#enable_source_location? ⇒ Object
Adds source_location for every method definition.
-
#error(msg, line = nil) ⇒ Object
This is called when a parsing/processing error occurs.
-
#file ⇒ String
The filename to use for compiling this code.
- #fragment(str, scope, sexp = nil) ⇒ Object
- #freezing? ⇒ Boolean deprecated Deprecated.
- #handle_block_given_call(sexp) ⇒ Object
- #handlers ⇒ Object
-
#helper(name) ⇒ Object
Use the given helper.
-
#helpers ⇒ Set<Symbol>
Any helpers required by this file.
- #in_case ⇒ Object
- #in_ensure ⇒ Object
- #in_ensure? ⇒ Boolean
-
#in_while ⇒ Object
Used when we enter a while statement.
-
#in_while? ⇒ Boolean
Returns true if the parser is curently handling a while sexp, false otherwise.
-
#indent(&block) ⇒ Object
To keep code blocks nicely indented, this will yield a block after adding an extra layer of indent, and then returning the resulting code after reverting the indent.
-
#initialize(source, options = {}) ⇒ Compiler
constructor
A new instance of Compiler.
-
#inline_operators? ⇒ Object
are operators compiled inline.
-
#irb? ⇒ Object
compile top level local vars with support for irb style vars.
-
#method_calls ⇒ Object
Method calls made in this file.
-
#method_missing? ⇒ Boolean
adds method stubs for all used methods in file.
-
#operator_helpers ⇒ Object
Operator helpers.
- #parse ⇒ Object
-
#parse_comments? ⇒ Object
Adds comments for every method definition.
-
#parser_indent ⇒ Object
Instances of
Scope
can use this to determine the current scope indent. -
#process(sexp, level = :expr) ⇒ Object
Process the given sexp by creating a node instance, based on its type, and compiling it to fragments.
-
#requirable? ⇒ Object
Prepare the code for future requires.
-
#required_trees ⇒ Object
An array of trees required in this file (typically by calling #require_tree).
-
#requires ⇒ Object
An array of requires used in this file.
-
#returns(sexp) ⇒ Object
The last sexps in method bodies, for example, need to be returned in the compiled javascript.
-
#s(type, *children) ⇒ Object
Create a new sexp using the given parts.
-
#source_map(source_file = nil) ⇒ Opal::SourceMap
Returns a source map that can be used in the browser to map back to original ruby code.
- #tainting? ⇒ Object deprecated Deprecated.
-
#unique_temp(name) ⇒ Object
Used to generate a unique id name per file.
-
#warning(msg, line = nil) ⇒ Object
This is called when a parsing/processing warning occurs.
-
#with_temp(&block) ⇒ Object
Temporary varibales will be needed from time to time in the generated code, and this method will assign (or reuse) on while the block is yielding, and queue it back up once it is finished.
Constructor Details
#initialize(source, options = {}) ⇒ Compiler
Returns a new instance of Compiler
153 154 155 156 157 158 159 160 |
# File 'opal/lib/opal/compiler.rb', line 153 def initialize(source, = {}) @source = source @indent = '' @unique = 0 @options = @comments = Hash.new([]) @case_stmt = nil end |
Instance Attribute Details
#case_stmt ⇒ Object (readonly)
Current case_stmt
145 146 147 |
# File 'opal/lib/opal/compiler.rb', line 145 def case_stmt @case_stmt end |
#comments ⇒ Object (readonly)
Comments from the source code
151 152 153 |
# File 'opal/lib/opal/compiler.rb', line 151 def comments @comments end |
#eof_content ⇒ Object (readonly)
Any content in END special construct
148 149 150 |
# File 'opal/lib/opal/compiler.rb', line 148 def eof_content @eof_content end |
#fragments ⇒ Array (readonly)
Returns all [Opal::Fragment] used to produce result
139 140 141 |
# File 'opal/lib/opal/compiler.rb', line 139 def fragments @fragments end |
#result ⇒ String (readonly)
Returns The compiled ruby code
136 137 138 |
# File 'opal/lib/opal/compiler.rb', line 136 def result @result end |
#scope ⇒ Object
Current scope
142 143 144 |
# File 'opal/lib/opal/compiler.rb', line 142 def scope @scope end |
Class Method Details
.compiler_option(name, default_value, options = {}) ⇒ Object
defines a compiler option, also creating method of form 'name?'
54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'opal/lib/opal/compiler.rb', line 54 def self.compiler_option(name, default_value, = {}) mid = [:as] valid_values = [:valid_values] define_method(mid || name) do value = @options.fetch(name) { default_value } if valid_values and not(valid_values.include?(value)) raise ArgumentError, "invalid value #{value.inspect} for option #{name.inspect} "+ "(valid values: #{valid_values.inspect})" end value end end |
Instance Method Details
#arity_check? ⇒ Boolean
adds an arity check to every method definition
87 |
# File 'opal/lib/opal/compiler.rb', line 87 compiler_option :arity_check, false, :as => :arity_check? |
#compile ⇒ String
Compile some ruby code to a string.
165 166 167 168 169 170 171 |
# File 'opal/lib/opal/compiler.rb', line 165 def compile parse @fragments = process(@sexp).flatten @result = @fragments.map(&:code).join('') end |
#dynamic_require_severity ⇒ Object
how to handle dynamic requires (:error, :warning, :ignore)
111 |
# File 'opal/lib/opal/compiler.rb', line 111 compiler_option :dynamic_require_severity, :ignore, :valid_values => [:error, :warning, :ignore] |
#enable_source_location? ⇒ Object
Adds source_location for every method definition
128 |
# File 'opal/lib/opal/compiler.rb', line 128 compiler_option :enable_source_location, false, :as => :enable_source_location? |
#error(msg, line = nil) ⇒ Object
This is called when a parsing/processing error occurs. This method simply appends the filename and curent line number onto the message and raises it.
221 222 223 |
# File 'opal/lib/opal/compiler.rb', line 221 def error(msg, line = nil) raise SyntaxError, "#{msg} :#{file}:#{line}" end |
#file ⇒ String
The filename to use for compiling this code. Used for FILE directives as well as finding relative require()
73 |
# File 'opal/lib/opal/compiler.rb', line 73 compiler_option :file, '(file)' |
#fragment(str, scope, sexp = nil) ⇒ Object
246 247 248 |
# File 'opal/lib/opal/compiler.rb', line 246 def fragment(str, scope, sexp = nil) Fragment.new(str, scope, sexp) end |
#freezing? ⇒ Boolean
stubs out #freeze and #frozen?
95 |
# File 'opal/lib/opal/compiler.rb', line 95 compiler_option :freezing, true, :as => :freezing? |
#handle_block_given_call(sexp) ⇒ Object
475 476 477 478 479 480 481 482 483 484 |
# File 'opal/lib/opal/compiler.rb', line 475 def handle_block_given_call(sexp) @scope.uses_block! if @scope.block_name fragment("(#{@scope.block_name} !== nil)", scope, sexp) elsif scope = @scope.find_parent_def and scope.block_name fragment("(#{scope.block_name} !== nil)", scope, sexp) else fragment("false", scope, sexp) end end |
#handlers ⇒ Object
347 348 349 |
# File 'opal/lib/opal/compiler.rb', line 347 def handlers @handlers ||= Opal::Nodes::Base.handlers end |
#helper(name) ⇒ Object
Use the given helper
268 269 270 |
# File 'opal/lib/opal/compiler.rb', line 268 def helper(name) self.helpers << name end |
#helpers ⇒ Set<Symbol>
Any helpers required by this file. Used by Nodes::Top to reference runtime helpers that are needed. These are used to minify resulting javascript by keeping a reference to helpers used.
204 205 206 |
# File 'opal/lib/opal/compiler.rb', line 204 def helpers @helpers ||= Set.new([:breaker, :slice]) end |
#in_case ⇒ Object
321 322 323 324 325 326 327 |
# File 'opal/lib/opal/compiler.rb', line 321 def in_case return unless block_given? old = @case_stmt @case_stmt = {} yield @case_stmt = old end |
#in_ensure ⇒ Object
307 308 309 310 311 312 313 314 315 |
# File 'opal/lib/opal/compiler.rb', line 307 def in_ensure return unless block_given? @in_ensure = true result = yield @in_ensure = false result end |
#in_ensure? ⇒ Boolean
317 318 319 |
# File 'opal/lib/opal/compiler.rb', line 317 def in_ensure? !!@in_ensure end |
#in_while ⇒ Object
Used when we enter a while statement. This pushes onto the current scope's while stack so we know how to handle break, next etc.
299 300 301 302 303 304 305 |
# File 'opal/lib/opal/compiler.rb', line 299 def in_while return unless block_given? @while_loop = @scope.push_while result = indent { yield } @scope.pop_while result end |
#in_while? ⇒ Boolean
Returns true if the parser is curently handling a while sexp, false otherwise.
331 332 333 |
# File 'opal/lib/opal/compiler.rb', line 331 def in_while? @scope.in_while? end |
#indent(&block) ⇒ Object
To keep code blocks nicely indented, this will yield a block after adding an extra layer of indent, and then returning the resulting code after reverting the indent.
275 276 277 278 279 280 281 282 283 |
# File 'opal/lib/opal/compiler.rb', line 275 def indent(&block) indent = @indent @indent += INDENT @space = "\n#@indent" res = yield @indent = indent @space = "\n#@indent" res end |
#inline_operators? ⇒ Object
are operators compiled inline
121 |
# File 'opal/lib/opal/compiler.rb', line 121 compiler_option :inline_operators, true, :as => :inline_operators? |
#irb? ⇒ Object
compile top level local vars with support for irb style vars
106 |
# File 'opal/lib/opal/compiler.rb', line 106 compiler_option :irb, false, :as => :irb? |
#method_calls ⇒ Object
Method calls made in this file
214 215 216 |
# File 'opal/lib/opal/compiler.rb', line 214 def method_calls @method_calls ||= Set.new end |
#method_missing? ⇒ Boolean
adds method stubs for all used methods in file
80 |
# File 'opal/lib/opal/compiler.rb', line 80 compiler_option :method_missing, true, :as => :method_missing? |
#operator_helpers ⇒ Object
Operator helpers
209 210 211 |
# File 'opal/lib/opal/compiler.rb', line 209 def operator_helpers @operator_helpers ||= Set.new end |
#parse ⇒ Object
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'opal/lib/opal/compiler.rb', line 173 def parse @buffer = ::Opal::Source::Buffer.new(file, 1) @buffer.source = @source @parser = Opal::Parser.default_parser begin sexp, comments, tokens = @parser.tokenize(@buffer) rescue ::Parser::SyntaxError => error raise ::SyntaxError, error., error.backtrace end @sexp = s(:top, sexp || s(:nil)) @comments = ::Parser::Source::Comment.associate_locations(sexp, comments) @eof_content = EofContent.new(tokens, @source).eof end |
#parse_comments? ⇒ Object
Adds comments for every method definition
133 |
# File 'opal/lib/opal/compiler.rb', line 133 compiler_option :parse_comments, false, :as => :parse_comments? |
#parser_indent ⇒ Object
Instances of Scope
can use this to determine the current
scope indent. The indent is used to keep generated code easily
readable.
235 236 237 |
# File 'opal/lib/opal/compiler.rb', line 235 def parser_indent @indent end |
#process(sexp, level = :expr) ⇒ Object
Process the given sexp by creating a node instance, based on its type, and compiling it to fragments.
337 338 339 340 341 342 343 344 345 |
# File 'opal/lib/opal/compiler.rb', line 337 def process(sexp, level = :expr) return fragment('', scope) if sexp == nil if handler = handlers[sexp.type] return handler.new(sexp, level, self).compile_to_fragments else error "Unsupported sexp: #{sexp.type}" end end |
#requirable? ⇒ Object
Prepare the code for future requires
116 |
# File 'opal/lib/opal/compiler.rb', line 116 compiler_option :requirable, false, :as => :requirable? |
#required_trees ⇒ Object
An array of trees required in this file (typically by calling #require_tree)
358 359 360 |
# File 'opal/lib/opal/compiler.rb', line 358 def required_trees @required_trees ||= [] end |
#requires ⇒ Object
An array of requires used in this file
352 353 354 |
# File 'opal/lib/opal/compiler.rb', line 352 def requires @requires ||= [] end |
#returns(sexp) ⇒ Object
The last sexps in method bodies, for example, need to be returned
in the compiled javascript. Due to syntax differences between
javascript any ruby, some sexps need to be handled specially. For
example, if
statemented cannot be returned in javascript, so
instead the "truthy" and "falsy" parts of the if statement both
need to be returned instead.
Sexps that need to be returned are passed to this method, and the
alterned/new sexps are returned and should be used instead. Most
sexps can just be added into a s(:return) sexp
, so that is the
default action if no special case is required.
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 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 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 464 465 466 467 468 469 470 471 472 473 |
# File 'opal/lib/opal/compiler.rb', line 373 def returns(sexp) return returns s(:nil) unless sexp case sexp.type when :undef # undef :method_name always returns nil returns s(:begin, sexp, s(:nil)) when :break, :next, :redo sexp when :yield sexp.updated(:returnable_yield, nil) when :when *when_sexp, then_sexp = *sexp sexp.updated(nil, [*when_sexp, returns(then_sexp)] ) when :rescue body_sexp, *resbodies, else_sexp = *sexp resbodies = resbodies.map do |resbody| returns(resbody) end if else_sexp else_sexp = returns(else_sexp) end sexp.updated(nil, [ returns(body_sexp), *resbodies, else_sexp ]) when :resbody klass, lvar, body = *sexp sexp.updated(nil, [klass, lvar, returns(body)]) when :ensure rescue_sexp, ensure_body = *sexp sexp = sexp.updated(nil, [returns(rescue_sexp), ensure_body] ) s(:js_return, sexp) when :begin, :kwbegin # Wrapping last expression with s(:js_return, ...) *rest, last = *sexp sexp.updated( nil, [*rest, returns(last)] ) when :while, :until, :while_post, :until_post sexp when :return, :js_return, :returnable_yield sexp when :xstr if sexp.children.any? strs = sexp. children. select { |child| child.type == :str }. map { |child| child.children[0] } multiline = strs.any? { |str| str.end_with?(";\n") } first_child, *rest_children = *sexp if multiline # xstr starts with interpolation # then it must contain js_return inside sexp else if first_child.type == :str old_value = first_child.children[0] if old_value.include?('return') # 'return' is already there sexp else first_child = s(:js_return, first_child) sexp.updated(nil, [first_child, *rest_children]) end else s(:js_return, sexp) end end else returns s(:str, '') end when :if cond, true_body, false_body = *sexp sexp.updated( nil, [ cond, returns(true_body), returns(false_body) ] ) else s(:js_return, sexp).updated( nil, nil, location: sexp.loc ) end end |
#s(type, *children) ⇒ Object
Create a new sexp using the given parts. Even though this just returns an array, it must be used incase the internal structure of sexps does change.
242 243 244 |
# File 'opal/lib/opal/compiler.rb', line 242 def s(type, *children) ::Opal::AST::Node.new(type, children) end |
#source_map(source_file = nil) ⇒ Opal::SourceMap
Returns a source map that can be used in the browser to map back to original ruby code.
195 196 197 |
# File 'opal/lib/opal/compiler.rb', line 195 def source_map(source_file = nil) Opal::SourceMap.new(@fragments, source_file || self.file) end |
#tainting? ⇒ Object
stubs out #taint, #untaint and #tainted?
101 |
# File 'opal/lib/opal/compiler.rb', line 101 compiler_option :tainting, true, :as => :tainting? |
#unique_temp(name) ⇒ Object
Used to generate a unique id name per file. These are used mainly to name method bodies for methods that use blocks.
252 253 254 255 256 257 258 259 260 261 262 263 264 265 |
# File 'opal/lib/opal/compiler.rb', line 252 def unique_temp(name) name = name.to_s if name && !name.empty? name = "_#{name}" .gsub('?', '$q') .gsub('!', '$B') .gsub('=', '$eq') .gsub('<', '$lt') .gsub('>', '$gt') .gsub(/[^\w\$]/, '$') end unique = (@unique += 1) "TMP#{name}_#{unique}" end |
#warning(msg, line = nil) ⇒ Object
This is called when a parsing/processing warning occurs. This method simply appends the filename and curent line number onto the message and issues a warning.
228 229 230 |
# File 'opal/lib/opal/compiler.rb', line 228 def warning(msg, line = nil) warn "WARNING: #{msg} -- #{file}:#{line}" end |
#with_temp(&block) ⇒ Object
Temporary varibales will be needed from time to time in the generated code, and this method will assign (or reuse) on while the block is yielding, and queue it back up once it is finished. Variables are queued once finished with to save the numbers of variables needed at runtime.
290 291 292 293 294 295 |
# File 'opal/lib/opal/compiler.rb', line 290 def with_temp(&block) tmp = @scope.new_temp res = yield tmp @scope.queue_temp tmp res end |