class Piggly::Compiler::TraceCompiler

Walks the parse tree, attaching Tag values and rewriting source code to ping them.

Public Class Methods

cache_sources() click to toggle source

Each of these files' mtimes are used to determine when another file is stale

# File lib/piggly/compiler/trace_compiler.rb, line 109
def cache_sources
  [Parser.grammar_path,
   Parser.parser_path,
   Parser.nodes_path]
end
new(config) click to toggle source
# File lib/piggly/compiler/trace_compiler.rb, line 10
def initialize(config)
  @config = config
end

Public Instance Methods

compile(procedure) click to toggle source
# File lib/piggly/compiler/trace_compiler.rb, line 21
      def compile(procedure)
        cache = CacheDir.new(cache_path(procedure.source_path(@config)))

        if stale?(procedure)
          begin
          $stdout.puts "Compiling #{procedure.name}"
          tree = Parser.parse(IO.read(procedure.source_path(@config)))
          tree = tree.force! if tree.respond_to?(:thunk?)

          tags = []
          code = traverse(tree, procedure.oid, tags)

          cache.replace(:tree => tree, :code => code, :tags => tags)
          rescue RuntimeError => e
            $stdout.puts <<-EXMSG
            ****
            Error compiling procedure #{procedure.name}
            Source: #{procedure.source_path(@config)}
            Exception Message:
            #{e.message}
            ****
            EXMSG
          end

        end

        cache
      end
stale?(procedure) click to toggle source

Is the cache_path is older than its source path or the other files?

# File lib/piggly/compiler/trace_compiler.rb, line 15
def stale?(procedure)
  Util::File.stale?(cache_path(procedure.source_path(@config)),
                    procedure.source_path(@config),
                    *self.class.cache_sources)
end

Protected Instance Methods

traverse(node, oid, tags) click to toggle source

Rewrites the parse tree to call instrumentation helpers, and destructively updates `tags` by appending the tags of instrumented nodes

@return [String]
# File lib/piggly/compiler/trace_compiler.rb, line 55
def traverse(node, oid, tags)
  if node.terminal? or node.expression?
    node.source_text
  else
    if node.respond_to?(:condStub) and node.respond_to?(:cond)
      # Preserve opening parenthesis and whitespace before injecting code. This way
      # IF(test) becomes IF(piggly_cond(TAG, test)) instead of IFpiggly_cond(TAG, (test))
      pre, cond = node.cond.expr.text_value.match(/\A(\(?[\t\n\r ]*)(.+)\z/m).captures
      node.cond.source_text = ""

      tags << node.cond.tag(oid)

      node.condStub.source_text  = "#{pre}public.piggly_cond($PIGGLY$#{node.cond.tag_id}$PIGGLY$, (#{cond}))"
      node.condStub.source_text << traverse(node.cond.tail, oid, tags) # preserve trailing whitespace
    end

    if node.respond_to?(:bodyStub)
      if node.respond_to?(:exitStub) and node.respond_to?(:cond)
        tags << node.body.tag(oid)
        tags << node.cond.tag(oid)

        # Hack to simulate a loop conditional statement in stmtForLoop and stmtLoop.
        # signal condition is true when body is executed, and false when exit stub is reached
        node.bodyStub.source_text  = "perform public.piggly_cond($PIGGLY$#{node.cond.tag_id}$PIGGLY$, true);#{node.indent(:bodySpace)}"
        node.bodyStub.source_text << "perform public.piggly_branch($PIGGLY$#{node.body.tag_id}$PIGGLY$);#{node.indent(:bodySpace)}"

        if node.respond_to?(:doneStub)
          # Signal the end of an iteration was reached
          node.doneStub.source_text  = "#{node.indent(:bodySpace)}perform public.piggly_signal($PIGGLY$#{node.cond.tag_id}$PIGGLY$, $PIGGLY$@$PIGGLY$);"
          node.doneStub.source_text << node.body.indent
        end

        # Signal the loop terminated
        node.exitStub.source_text  = "\n#{node.indent}perform public.piggly_cond($PIGGLY$#{node.cond.tag_id}$PIGGLY$, false);"
      elsif node.respond_to?(:body)
        # Unconditional branches (or blocks)
        #   BEGIN ... END;
        #   ... ELSE ... END;
        #   CONTINUE label;
        #   EXIT label;
        tags << node.body.tag(oid)
        node.bodyStub.source_text = "perform public.piggly_branch($PIGGLY$#{node.body.tag_id}$PIGGLY$);#{node.indent(:bodySpace)}"
      end
    end

    # Traverse children (in which we just injected code)
    node.elements.map{|e| traverse(e, oid, tags) }.join
  end
end