Class: Opal::Compiler

Inherits:
Object
  • Object
show all
Defined in:
opal/lib/opal/compiler.rb

Overview

Compiler is the main class used to compile ruby to javascript code. This class uses Parser to gather the sexp syntax tree for the ruby code, and then uses Node to step through the sexp to generate valid javascript.

Examples:

Opal::Compiler.new("ruby code").compile
# => "javascript code"

Accessing result

compiler = Opal::Compiler.new("ruby_code")
compiler.compile
compiler.result # => "javascript code"

Source Maps

compiler = Opal::Compiler.new("")
compiler.compile
compiler.source_map # => #<SourceMap:>

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

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source, options = {}) ⇒ Compiler

Returns a new instance of Compiler



136
137
138
139
140
141
# File 'opal/lib/opal/compiler.rb', line 136

def initialize(source, options = {})
  @source = source
  @indent = ''
  @unique = 0
  @options = options
end

Instance Attribute Details

#case_stmtObject (readonly)

Current case_stmt



131
132
133
# File 'opal/lib/opal/compiler.rb', line 131

def case_stmt
  @case_stmt
end

#eof_contentObject (readonly)

Any content in END special construct



134
135
136
# File 'opal/lib/opal/compiler.rb', line 134

def eof_content
  @eof_content
end

#fragmentsArray (readonly)

Returns all [Opal::Fragment] used to produce result

Returns:

  • (Array)

    all [Opal::Fragment] used to produce result



125
126
127
# File 'opal/lib/opal/compiler.rb', line 125

def fragments
  @fragments
end

#resultString (readonly)

Returns The compiled ruby code

Returns:

  • (String)

    The compiled ruby code



122
123
124
# File 'opal/lib/opal/compiler.rb', line 122

def result
  @result
end

#scopeObject

Current scope



128
129
130
# File 'opal/lib/opal/compiler.rb', line 128

def scope
  @scope
end

Class Method Details

.compiler_option(name, default_value, options = {}) ⇒ Object

defines a compiler option, also creating method of form 'name?'



52
53
54
55
56
57
58
59
60
61
62
63
# File 'opal/lib/opal/compiler.rb', line 52

def self.compiler_option(name, default_value, options = {})
  mid          = options[:as]
  valid_values = options[: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

Returns:

  • (Boolean)


85
# File 'opal/lib/opal/compiler.rb', line 85

compiler_option :arity_check, false, :as => :arity_check?

#compileString

Compile some ruby code to a string.

Returns:

  • (String)

    javascript code



146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'opal/lib/opal/compiler.rb', line 146

def compile
  @parser = Parser.new

  @sexp = s(:top, @parser.parse(@source, self.file) || s(:nil))
  @eof_content = @parser.lexer.eof_content

  @fragments = process(@sexp).flatten

  @result = @fragments.map(&:code).join('')
rescue => error
  message = "An error occurred while compiling: #{self.file}\n#{error.message}"
  raise error.class, message, error.backtrace
end

#dynamic_require_severityObject

how to handle dynamic requires (:error, :warning, :ignore)



107
# File 'opal/lib/opal/compiler.rb', line 107

compiler_option :dynamic_require_severity, :error, :valid_values => [:error, :warning, :ignore]

#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.

Raises:

  • (SyntaxError)


191
192
193
# File 'opal/lib/opal/compiler.rb', line 191

def error(msg, line = nil)
  raise SyntaxError, "#{msg} :#{file}:#{line}"
end

#fileString

The filename to use for compiling this code. Used for FILE directives as well as finding relative require()

Returns:

  • (String)


71
# File 'opal/lib/opal/compiler.rb', line 71

compiler_option :file, '(file)'

#fragment(str, sexp = nil) ⇒ Object



216
217
218
# File 'opal/lib/opal/compiler.rb', line 216

def fragment(str, sexp = nil)
  Fragment.new(str, sexp)
end

#freezing?Boolean

stubs out #freeze and #frozen?

Returns:

  • (Boolean)


92
# File 'opal/lib/opal/compiler.rb', line 92

compiler_option :freezing, true, :as => :freezing?

#handle_block_given_call(sexp) ⇒ Object



389
390
391
392
393
394
395
396
397
398
# File 'opal/lib/opal/compiler.rb', line 389

def handle_block_given_call(sexp)
  @scope.uses_block!
  if @scope.block_name
    fragment("(#{@scope.block_name} !== nil)", sexp)
  elsif scope = @scope.find_parent_def and scope.block_name
    fragment("(#{scope.block_name} !== nil)", sexp)
  else
    fragment("false", sexp)
  end
end

#handlersObject



293
294
295
# File 'opal/lib/opal/compiler.rb', line 293

def handlers
  @handlers ||= Opal::Nodes::Base.handlers
end

#helper(name) ⇒ Object

Use the given helper



227
228
229
# File 'opal/lib/opal/compiler.rb', line 227

def helper(name)
  self.helpers << name
end

#helpersSet<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.

Returns:

  • (Set<Symbol>)


174
175
176
# File 'opal/lib/opal/compiler.rb', line 174

def helpers
  @helpers ||= Set.new([:breaker, :slice])
end

#in_caseObject



267
268
269
270
271
272
273
# File 'opal/lib/opal/compiler.rb', line 267

def in_case
  return unless block_given?
  old = @case_stmt
  @case_stmt = {}
  yield
  @case_stmt = old
end

#in_whileObject

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.



258
259
260
261
262
263
264
265
# File 'opal/lib/opal/compiler.rb', line 258

def in_while
  return unless block_given?
  @while_loop = @scope.push_while
  result = yield
  @scope.pop_while

  result
end

#in_while?Boolean

Returns true if the parser is curently handling a while sexp, false otherwise.

Returns:

  • (Boolean)


277
278
279
# File 'opal/lib/opal/compiler.rb', line 277

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.



234
235
236
237
238
239
240
241
242
# File 'opal/lib/opal/compiler.rb', line 234

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



117
# File 'opal/lib/opal/compiler.rb', line 117

compiler_option :inline_operators, true, :as => :inline_operators?

#irb?Object

compile top level local vars with support for irb style vars



102
# File 'opal/lib/opal/compiler.rb', line 102

compiler_option :irb, false, :as => :irb?

#method_callsObject

Method calls made in this file



184
185
186
# File 'opal/lib/opal/compiler.rb', line 184

def method_calls
  @method_calls ||= Set.new
end

#method_missing?Boolean

adds method stubs for all used methods in file

Returns:

  • (Boolean)


78
# File 'opal/lib/opal/compiler.rb', line 78

compiler_option :method_missing, true, :as => :method_missing?

#operator_helpersObject

Operator helpers



179
180
181
# File 'opal/lib/opal/compiler.rb', line 179

def operator_helpers
  @operator_helpers ||= Set.new
end

#parser_indentObject

Instances of Scope can use this to determine the current scope indent. The indent is used to keep generated code easily readable.



205
206
207
# File 'opal/lib/opal/compiler.rb', line 205

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.



283
284
285
286
287
288
289
290
291
# File 'opal/lib/opal/compiler.rb', line 283

def process(sexp, level = :expr)
  return fragment('') if sexp == nil

  if handler = handlers[sexp.type]
    return handler.new(sexp, level, self).compile_to_fragments
  else
    raise "Unsupported sexp: #{sexp.type}"
  end
end

#requirable?Object

Prepare the code for future requires



112
# File 'opal/lib/opal/compiler.rb', line 112

compiler_option :requirable, false, :as => :requirable?

#required_treesObject

An array of trees required in this file (typically by calling #require_tree)



304
305
306
# File 'opal/lib/opal/compiler.rb', line 304

def required_trees
  @required_trees ||= []
end

#requiresObject

An array of requires used in this file



298
299
300
# File 'opal/lib/opal/compiler.rb', line 298

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.



319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
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
# File 'opal/lib/opal/compiler.rb', line 319

def returns(sexp)
  return returns s(:nil) unless sexp

  case sexp.type
  # Undefs go from 1 ruby undef a,b,c to multiple JS Opal.udef() calls, so need to treat them as individual statements
  # and put the return on the last one 
  when :undef
    last = sexp.pop
    sexp << returns(last)
  when :break, :next, :redo
    sexp
  when :yield
    sexp[0] = :returnable_yield
    sexp
  when :scope
    sexp[1] = returns sexp[1]
    sexp
  when :block
    if sexp.length > 1
      sexp[-1] = returns sexp[-1]
    else
      sexp << returns(s(:nil))
    end
    sexp
  when :when
    sexp[2] = returns(sexp[2])
    sexp
  when :rescue
    sexp[1] = returns sexp[1]

    if sexp[2] and sexp[2][0] == :resbody
      if sexp[2][2]
        sexp[2][2] = returns sexp[2][2]
      else
        sexp[2][2] = returns s(:nil)
      end
    end
    sexp
  when :ensure
    sexp[1] = returns sexp[1]
    sexp
  when :begin
    sexp[1] = returns sexp[1]
    sexp
  when :rescue_mod
    sexp[1] = returns sexp[1]
    sexp[2] = returns sexp[2]
    sexp
  when :while
    # sexp[2] = returns(sexp[2])
    sexp
  when :return, :js_return
    sexp
  when :xstr
    sexp[1] = "return #{sexp[1]};" unless /return|;/ =~ sexp[1]
    sexp
  when :dxstr
    sexp[1] = "return #{sexp[1]}" unless /return|;|\n/ =~ sexp[1]
    sexp
  when :if
    sexp[2] = returns(sexp[2] || s(:nil))
    sexp[3] = returns(sexp[3] || s(:nil))
    sexp
  else
    s(:js_return, sexp).tap { |s|
      s.source = sexp.source
    }
  end
end

#s(*parts) ⇒ 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.



212
213
214
# File 'opal/lib/opal/compiler.rb', line 212

def s(*parts)
  Sexp.new(parts)
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.

Parameters:

  • source_file (String) (defaults to: nil)

    optional source_file to reference ruby source

Returns:



165
166
167
# File 'opal/lib/opal/compiler.rb', line 165

def source_map(source_file = nil)
  Opal::SourceMap.new(@fragments, source_file || self.file)
end

#tainting?Object

stubs out #taint, #untaint and #tainted?



97
# File 'opal/lib/opal/compiler.rb', line 97

compiler_option :tainting, true, :as => :tainting?

#unique_tempObject

Used to generate a unique id name per file. These are used mainly to name method bodies for methods that use blocks.



222
223
224
# File 'opal/lib/opal/compiler.rb', line 222

def unique_temp
  "TMP_#{@unique += 1}"
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.



198
199
200
# File 'opal/lib/opal/compiler.rb', line 198

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.



249
250
251
252
253
254
# File 'opal/lib/opal/compiler.rb', line 249

def with_temp(&block)
  tmp = @scope.new_temp
  res = yield tmp
  @scope.queue_temp tmp
  res
end