Current section
Files
Jump to
Current section
Files
lib/linker.ex
defmodule Xpeg.Linker do
@moduledoc false
defp link_rule(program, rules, name) do
if rules[name] == nil do
raise("XPeg: referencing undefined rule '#{name}'")
end
instructions = rules[name]
program = %{
program
| symtab: Map.put(program.symtab, name, Enum.count(program.instructions)),
instructions: program.instructions ++ instructions ++ [{:return}]
}
Enum.reduce(instructions, program, fn inst, program ->
case inst do
{:call, callname} ->
if !Map.has_key?(program.symtab, callname) do
link_rule(program, rules, callname)
else
program
end
_ ->
program
end
end)
end
def link_grammar(grammar, options) do
program = %{
instructions: [],
symtab: %{},
refs: %{},
}
program = link_rule(program, grammar.rules, grammar.start)
insts = program.instructions
|> Enum.with_index(fn inst, ip -> {ip, inst} end)
|> resolve_addresses(program)
|> peephole()
program = %{program |
refs: count_refs(insts),
instructions: insts ++ [{:fail, {:fail}}]}
dump(program, options)
program
end
def count_refs(insts) do
Enum.reduce(insts, %{}, fn {ip, inst}, acc ->
case inst do
{:choice, ip_back, ip_commit, _} ->
acc = Map.put(acc, ip_back, true)
acc = Map.put(acc, ip_commit, true)
{op, ip_dest} when op in [:call] ->
acc = Map.put(acc, ip, true) # Callers can not be inlined because their 'ip' is wrong
acc = Map.put(acc, ip+1, true) # Instruction after a call can not be inlined because they are return dest
acc = Map.put(acc, ip_dest, true)
{op, ip_dest} when op in [:jump] ->
acc = Map.put(acc, ip_dest, true)
i -> acc
end
end)
|> Map.put(0, true)
|> Map.put(:fail, true)
end
def peephole(insts) do
case insts do
# tail call optimization: call + return = jump
[{ip1, {:call, ip}}, {ip2, {:return}} | rest] ->
[{ip1, {:jump, ip}}, {ip2,{:nop}} | peephole(rest)]
# squash choice/commit pairs that ended up back-to-back because of head fail optimization
[{ip1, {:choice, _ip_back, ip_commit, _}}, {ip2, {:commit}} | rest] ->
[{ip1, {:jump, ip_commit}}, {ip2, {:nop}} | peephole(rest)]
[a | rest] -> [a | peephole(rest)]
e -> e
end
end
defp resolve_addresses(insts, program) do
Enum.map(insts, fn {ip, inst} ->
inst = case inst do
{op, name} when op in [:call, :jump] ->
{op, program.symtab[name]}
{:choice, off_back, off_commit, c} ->
{:choice, off_back+ip, off_commit+ip, c}
inst -> inst
end
{ip, inst}
end)
end
def dump(program, options) do
revtab = Enum.reduce(program.symtab, %{}, fn {k, v}, map -> Map.put(map, v, k) end)
if options[:dump_ir] do
Enum.reduce(program.instructions, [], fn {ip, inst}, lines ->
lines = if Map.has_key?(revtab, ip) do
[ "#{revtab[ip]}:" | lines]
else
lines
end
inlined = if program.refs[ip] do " " else "*" end
[ " #{ip} #{inlined} #{Xpeg.dump_inst(inst)}" | lines]
end)
|> Enum.reverse()
|> Enum.join("\n")
|> IO.puts
end
end
end
# set ft=elixir