Class: Opal::Compiler

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

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

#initializeCompiler

Returns a new instance of Compiler



55
56
57
58
59
# File 'opal/lib/opal/compiler.rb', line 55

def initialize
  @indent = ''
  @unique = 0
  @options = {}
end

Instance Attribute Details

#case_stmtObject (readonly)

Current case_stmt



50
51
52
# File 'opal/lib/opal/compiler.rb', line 50

def case_stmt
  @case_stmt
end

#eof_contentObject (readonly)

Any content in END special construct



53
54
55
# File 'opal/lib/opal/compiler.rb', line 53

def eof_content
  @eof_content
end

#fragmentsObject (readonly)

Returns the value of attribute fragments



44
45
46
# File 'opal/lib/opal/compiler.rb', line 44

def fragments
  @fragments
end

#resultObject (readonly)

Returns the value of attribute result



44
45
46
# File 'opal/lib/opal/compiler.rb', line 44

def result
  @result
end

#scopeObject

Current scope



47
48
49
# File 'opal/lib/opal/compiler.rb', line 47

def scope
  @scope
end

Class Method Details

.compiler_option(name, default_value, mid = nil) ⇒ Object

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



20
21
22
23
24
# File 'opal/lib/opal/compiler.rb', line 20

def self.compiler_option(name, default_value, mid = nil)
  define_method(mid || name) do
    @options.fetch(name) { default_value }
  end
end

Instance Method Details

#compile(source, options = {}) ⇒ Object

Compile some ruby code to a string.



62
63
64
65
66
67
68
69
70
71
72
73
# File 'opal/lib/opal/compiler.rb', line 62

def compile(source, options = {})
  @source = source
  @options.update options
  @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('')
end

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


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

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

#fragment(str, sexp = nil) ⇒ Object



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

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

#handle_block_given_call(sexp) ⇒ Object



277
278
279
280
281
282
283
284
285
286
# File 'opal/lib/opal/compiler.rb', line 277

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



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

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

#helper(name) ⇒ Object

Use the given helper



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

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

#helpersObject

Any helpers required by this file



80
81
82
# File 'opal/lib/opal/compiler.rb', line 80

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

#in_caseObject



168
169
170
171
172
173
174
# File 'opal/lib/opal/compiler.rb', line 168

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.



159
160
161
162
163
164
165
166
# File 'opal/lib/opal/compiler.rb', line 159

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)


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

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.



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

def indent(&block)
  indent = @indent
  @indent += INDENT
  @space = "\n#@indent"
  res = yield
  @indent = indent
  @space = "\n#@indent"
  res
end

#method_callsObject

Method calls made in this file



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

def method_calls
  @method_calls ||= 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.



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

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.



184
185
186
187
188
189
190
# File 'opal/lib/opal/compiler.rb', line 184

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

#requiresObject

An array of requires used in this file



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

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.



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
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
275
# File 'opal/lib/opal/compiler.rb', line 212

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.



113
114
115
# File 'opal/lib/opal/compiler.rb', line 113

def s(*parts)
  Sexp.new(parts)
end

#source_map(source_file = nil) ⇒ Object



75
76
77
# File 'opal/lib/opal/compiler.rb', line 75

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.



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

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.



99
100
101
# File 'opal/lib/opal/compiler.rb', line 99

def warning(msg, line = nil)
  warn "#{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.



150
151
152
153
154
155
# File 'opal/lib/opal/compiler.rb', line 150

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