module Klam::CompilationStages::EmitRuby

Emit Ruby

This is the final stage in the compilation pipeline. It is responsible for converting the compiler's internal representation to a string containing Ruby code that is suitable for execution via instance_eval in the context of a Klam::Environment object.

By the time compilation reaches this stage, all of the heavy lifting should be complete. Emitting Ruby is simply a matter of transliterating the simplified s-expression into Ruby.

Constants

PRIMITIVE_TEMPLATES

Public Instance Methods

emit_ruby(sexp) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 37
def emit_ruby(sexp)
  case sexp
  when Symbol
    emit_symbol(sexp)
  when String
    emit_string(sexp)
  when Klam::Constant, Klam::Variable, Numeric, true, false
    sexp.to_s
  when Array
    if sexp.empty?
      Klam::Primitives::Lists::EMPTY_LIST.inspect
    else
      emit_compound_form(sexp)
    end
  end
end

Private Instance Methods

emit_application(form) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 99
def emit_application(form)
  rator = form[0]
  rands = form[1..-1]
  rator_rb = emit_ruby(rator)
  rands_rb = rands.map { |rand| emit_ruby(rand) }

  if rator.kind_of?(Symbol)
    # Application of a function defined in the environment. At this
    # point partial application and currying have been taken care of,
    # so a simple send suffices.
    render_string('__send__($1)', [rator_rb] + rands_rb)
  else
    render_string('__apply($1)', [rator_rb] + rands_rb)
  end
end
emit_compound_form(form) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 56
def emit_compound_form(form)
  # Handle special forms and fall back to normal function application
  case form[0]
  when :defun
    emit_defun(form)
  when :if
    emit_if(form)
  when :lambda
    emit_lambda(form)
  when :let
    emit_let(form)
  when :"trap-error"
    emit_trap_error(form)
  when :do
    emit_do(form)
  when :"[DEFUN-CLOSURE]"
    emit_defun_closure(form)
  when :"[FIX-VARS]"
    emit_fix_vars(form)
  when :"[LOOP]"
    emit_loop(form)
  when :"[RECUR]"
    emit_recur(form)
  else
    if full_primitive_form?(form)
      emit_primitive(form)
    else
      emit_application(form)
    end
  end
end
emit_defun(form) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 115
      def emit_defun(form)
        _, name, params, body = form
        name_rb = emit_ruby(name)
        params_rb = params.map { |param| emit_ruby(param) }
        body_rb = emit_ruby(body)

        # Some valid Kl function names (e.g. foo-bar) are not valid when used
        # with Ruby's def syntax. They will work with define_method, but the
        # resulting methods are slower than if they had been defined via def.
        # To maximize performance, methods are defined with def and then
        # renamed to their intended name afterwards.
        mangled_name = '__klam_fn_' + name.to_s.gsub(/[^a-zA-Z0-9]/, '_')
        render_string(<<-EOT, name_rb, params_rb, body_rb, mangled_name, params.size)
          def $4($2)
            $3
          end
          @eigenclass.rename_method(:$4, $1)
          @arities[$1] = $5
          @curried_methods.delete($1)
          $1
        EOT
      end
emit_defun_closure(form) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 138
      def emit_defun_closure(form)
        _, name, params, body = form
        name_rb = emit_ruby(name)
        params_rb = params.map { |param| emit_ruby(param) }
        body_rb = emit_ruby(body)

        # Some valid Kl function names (e.g. foo-bar) are not valid when used
        # with Ruby's def syntax. They will work with define_method, but the
        # resulting methods are slower than if they had been defined via def.
        # To maximize performance, methods are defined with def and then
        # renamed to their intended name afterwards.
        mangled_name = ('__klam_fn_' + name.to_s.gsub(/[^a-zA-Z0-9]/, '_')).intern
        mangled_name_rb = emit_ruby(mangled_name)
        render_string(<<-EOT, name_rb, params_rb, body_rb, mangled_name_rb, params.size)
          @eigenclass.def_method($4, ::Kernel.lambda { |$2| $3 })
          @eigenclass.rename_method($4, $1)
          @arities[$1] = $5
          @curried_methods.delete($1)
          $1
        EOT
      end
emit_do(form) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 160
def emit_do(form)
  rands = form[1,2]
  rands_rb = rands.map { |rand| emit_ruby(rand) }
  '(' + rands_rb.join(';') + ')'
end
emit_fix_vars(form) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 166
def emit_fix_vars(form)
  _, params, expr = form
  params_rb = params.map { |param| emit_ruby(param) }
  expr_rb = emit_ruby(expr)

  render_string('(::Kernel.lambda { |$1| $2 }).call($1)',
                params_rb, expr_rb)
end
emit_if(form) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 175
def emit_if(form)
  args = form[1..3].map { |sexp| emit_ruby(sexp) }
  render_string('($1 ? $2 : $3)', *args)
end
emit_lambda(form) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 180
def emit_lambda(form)
  _, params, body = form
  params_rb = params.map { |param| emit_ruby(param) }
  body_rb = emit_ruby(body)

  render_string('(::Kernel.lambda { |$1| $2 })', params_rb, body_rb)
end
emit_let(form) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 188
def emit_let(form)
  _, var, value_expr, body_expr = form

  var_rb = emit_ruby(var)
  value_expr_rb = emit_ruby(value_expr)
  body_expr_rb = emit_ruby(body_expr)

  render_string('($1 = $2; $3)', var_rb, value_expr_rb, body_expr_rb)
end
emit_loop(form) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 198
      def emit_loop(form)
        _, loop_var, expr = form
        val_var = fresh_variable

        expr_rb = emit_ruby(expr)
        loop_var_rb = emit_ruby(loop_var)
        val_var_rb = emit_ruby(val_var)

        render_string(<<-EOT, loop_var_rb, val_var_rb, expr_rb)
          $1 = false
          $2 = nil
          begin
            $1 = false
            $2 = $3
          end while $1
          $2
        EOT
      end
emit_primitive(form) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 93
def emit_primitive(form)
  _, template = PRIMITIVE_TEMPLATES[form[0]]
  rands_rb = form[1..-1].map { |rand| emit_ruby(rand) }
  render_string(template, *rands_rb)
end
emit_recur(form) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 217
def emit_recur(form)
  _, params, new_value_exprs, loop_var = form
  loop_var_rb = emit_ruby(loop_var)

  if params.size > 0
    params_rb = params.map { |param| emit_ruby(param) }
    new_value_exprs_rb = new_value_exprs.map { |expr| emit_ruby(expr) }

    render_string('(($1 = $2); $3 = true)', params_rb,
                  new_value_exprs_rb, loop_var_rb)
  else
    render_string('$1 = true', loop_var_rb)
  end
end
emit_string(str) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 232
def emit_string(str)
  str.inspect
end
emit_symbol(sym) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 236
def emit_symbol(sym)
  ':"' + sym.to_s + '"'
end
emit_trap_error(form) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 240
def emit_trap_error(form)
  _, expr, handler = form
  err_var = fresh_variable

  expr_rb = emit_ruby(expr)
  apply_handler_rb = emit_application([handler, err_var])
  err_var_rb = emit_ruby(err_var)

  render_string('(begin; $2; rescue => $1; $3; end)', err_var_rb,
                expr_rb, apply_handler_rb)
end
full_primitive_form?(form) click to toggle source
# File lib/klam/compilation_stages/emit_ruby.rb, line 88
def full_primitive_form?(form)
  num_args, template = PRIMITIVE_TEMPLATES[form[0]]
  num_args && (num_args == form.size - 1)
end