[フレーム]

Class: Opal::Compiler

Inherits:
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

119
120
121
122
123
124
# File 'opal/lib/opal/compiler.rb', line 119
def initialize(source, options = {})
 @source = source
 @indent = ''
 @unique = 0
 @options = options
end

Instance Attribute Details

#case_stmtObject (readonly)

Current case_stmt

114
115
116
# File 'opal/lib/opal/compiler.rb', line 114
def case_stmt
 @case_stmt
end

#eof_contentObject (readonly)

Any content in END special construct

117
118
119
# File 'opal/lib/opal/compiler.rb', line 117
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

108
109
110
# File 'opal/lib/opal/compiler.rb', line 108
def fragments
 @fragments
end

#resultString (readonly)

Returns The compiled ruby code

Returns:

  • (String)

    The compiled ruby code

105
106
107
# File 'opal/lib/opal/compiler.rb', line 105
def result
 @result
end

#scopeObject

Current scope

111
112
113
# File 'opal/lib/opal/compiler.rb', line 111
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
# 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 }
 raise ArgumentError if valid_values and not(valid_values.include?(value))
 value
 end
end

Instance Method Details

#arity_check?Boolean

adds an arity check to every method definition

Returns:

  • (Boolean)
82
# File 'opal/lib/opal/compiler.rb', line 82
compiler_option :arity_check, false, :as => :arity_check?

#compileString

Compile some ruby code to a string.

Returns:

  • (String)

    javascript code

129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'opal/lib/opal/compiler.rb', line 129
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
end

#dynamic_require_severityObject

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

92
# File 'opal/lib/opal/compiler.rb', line 92
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)
174
175
176
# File 'opal/lib/opal/compiler.rb', line 174
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)
68
# File 'opal/lib/opal/compiler.rb', line 68
compiler_option :file, '(file)'

#fragment(str, sexp = nil) ⇒ Object

199
200
201
# File 'opal/lib/opal/compiler.rb', line 199
def fragment(str, sexp = nil)
 Fragment .new (str, sexp)
end

#handle_block_given_call(sexp) ⇒ Object

367
368
369
370
371
372
373
374
375
376
# File 'opal/lib/opal/compiler.rb', line 367
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

276
277
278
# File 'opal/lib/opal/compiler.rb', line 276
def handlers
 @handlers ||= Opal ::Nodes ::Base .handlers 
end

#helper(name) ⇒ Object

Use the given helper

210
211
212
# File 'opal/lib/opal/compiler.rb', line 210
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>)
157
158
159
# File 'opal/lib/opal/compiler.rb', line 157
def helpers
 @helpers ||= Set.new([:breaker, :slice])
end

#in_caseObject

250
251
252
253
254
255
256
# File 'opal/lib/opal/compiler.rb', line 250
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.

241
242
243
244
245
246
247
248
# File 'opal/lib/opal/compiler.rb', line 241
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)
260
261
262
# File 'opal/lib/opal/compiler.rb', line 260
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.

217
218
219
220
221
222
223
224
225
# File 'opal/lib/opal/compiler.rb', line 217
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

102
# File 'opal/lib/opal/compiler.rb', line 102
compiler_option :inline_operators, false, :as => :inline_operators?

#irb?Object

compile top level local vars with support for irb style vars

87
# File 'opal/lib/opal/compiler.rb', line 87
compiler_option :irb, false, :as => :irb?

#method_callsObject

Method calls made in this file

167
168
169
# File 'opal/lib/opal/compiler.rb', line 167
def method_calls
 @method_calls ||= Set.new
end

#method_missing?Boolean

adds method stubs for all used methods in file

Returns:

  • (Boolean)
75
# File 'opal/lib/opal/compiler.rb', line 75
compiler_option :method_missing, true, :as => :method_missing?

#operator_helpersObject

Operator helpers

162
163
164
# File 'opal/lib/opal/compiler.rb', line 162
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.

188
189
190
# File 'opal/lib/opal/compiler.rb', line 188
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.

266
267
268
269
270
271
272
273
274
# File 'opal/lib/opal/compiler.rb', line 266
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

97
# File 'opal/lib/opal/compiler.rb', line 97
compiler_option :requirable, false, :as => :requirable?

#required_treesObject

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

287
288
289
# File 'opal/lib/opal/compiler.rb', line 287
def required_trees
 @required_trees ||= []
end

#requiresObject

An array of requires used in this file

281
282
283
# File 'opal/lib/opal/compiler.rb', line 281
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.

302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
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
# File 'opal/lib/opal/compiler.rb', line 302
def returns(sexp)
 return returns s(:nil) unless sexp
 case sexp.type
 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.

195
196
197
# File 'opal/lib/opal/compiler.rb', line 195
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:

148
149
150
# File 'opal/lib/opal/compiler.rb', line 148
def source_map(source_file = nil)
 Opal ::SourceMap .new (@fragments, source_file || self.file)
end

#unique_tempObject

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

205
206
207
# File 'opal/lib/opal/compiler.rb', line 205
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.

181
182
183
# File 'opal/lib/opal/compiler.rb', line 181
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.

232
233
234
235
236
237
# File 'opal/lib/opal/compiler.rb', line 232
def with_temp(&block)
 tmp = @scope.new_temp
 res = yield tmp
 @scope.queue_temp tmp
 res
end

AltStyle によって変換されたページ (->オリジナル) /