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



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

def initialize
  @line = 1
  @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

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



60
61
62
63
64
65
66
67
68
69
# File 'opal/lib/opal/compiler.rb', line 60

def compile(source, options = {})
  @source = source
  @options.update options

  @sexp = s(:top, Parser.new.parse(@source, self.file) || s(:nil))

  @fragments = process(@sexp).flatten

  @result = @fragments.map(&:code).join('')
end

#error(msg) ⇒ 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)


88
89
90
# File 'opal/lib/opal/compiler.rb', line 88

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

#fragment(str, sexp = nil) ⇒ Object



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

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

#handle_block_given_call(sexp) ⇒ Object



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

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



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

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

#helper(name) ⇒ Object

Use the given helper



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

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

#helpersObject

Any helpers required by this file



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

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

#in_caseObject



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

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.



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

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)


176
177
178
# File 'opal/lib/opal/compiler.rb', line 176

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.



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

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



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

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.



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

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.



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

def process(sexp, level = :expr)
  if handler = handlers[sexp.type]
    @line = sexp.line
    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



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

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.



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

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.line = sexp.line
    }
  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.



109
110
111
112
113
# File 'opal/lib/opal/compiler.rb', line 109

def s(*parts)
  sexp = Sexp.new(parts)
  sexp.line = @line
  sexp
end

#source_map(source_file = nil) ⇒ Object



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

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.



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

def unique_temp
  "TMP_#{@unique += 1}"
end

#warning(msg) ⇒ 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.



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

def warning(msg)
  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.



148
149
150
151
152
153
# File 'opal/lib/opal/compiler.rb', line 148

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