defmodule Xpeg.Codegen do @moduledoc false defp emit_inst(ip, inst, options) do case inst do {:nop} -> quote do {ctx, s, si, unquote(ip + 1)} end {:any, n} -> quote do if Enum.count(s) >= unquote(n) do s = Enum.drop(s, unquote(n)) si = si + unquote(n) {ctx, s, si, unquote(ip + 1)} else {ctx, s, si, :fail} end end {:chr, c, off_fail} -> ip_fail = if off_fail == 0 do :fail else ip + off_fail end quote do case s do [unquote(c) | s] -> si = si + 1 {ctx, s, si, unquote(ip + 1)} _ -> {ctx, s, si, unquote(ip_fail)} end end {:set, cs, off_fail} -> ip_fail = if off_fail == 0 do :fail else ip + off_fail end quote do case s do [c | s] when c in unquote(cs) -> si = si + 1 {ctx, s, si, unquote(ip + 1)} _ -> {ctx, s, si, unquote(ip_fail)} end end {:span, cs} -> quote do {s, si} = Enum.reduce_while(s, {s,si}, fn _, {[c|s],si} when c in unquote(cs) -> {:cont, {s,si+1}} _, {s, si} -> {:halt, {s,si}} end) {ctx, s, si, unquote(ip + 1)} end {:return} -> quote do case Xpeg.state(ctx, :ret_stack) do [ip | ret_stack] -> ctx = Xpeg.state(ctx, ret_stack: ret_stack) {ctx, s, si, ip} [] -> {ctx, s, si, :ok} end end {:choice, ip_back, ip_commit, c} -> ssave = case c do nil -> quote do s end c -> quote do [unquote(c) | s] end # Restore consumed c for headfails end quote do frame = %{ ip_back: unquote(ip_back), ip_commit: unquote(ip_commit), ret_stack: Xpeg.state(ctx, :ret_stack), cap_stack: Xpeg.state(ctx, :cap_stack), s: unquote(ssave), si: si } back_stack = Xpeg.state(ctx, :back_stack) ctx = Xpeg.state(ctx, back_stack: [frame | back_stack]) {ctx, s, si, unquote(ip + 1)} end {:commit} -> quote do [frame | back_stack] = Xpeg.state(ctx, :back_stack) ctx = Xpeg.state(ctx, back_stack: back_stack) {ctx, s, si, frame.ip_commit} end {:call, addr} -> quote do ret_stack = Xpeg.state(ctx, :ret_stack) ctx = Xpeg.state(ctx, ret_stack: [ip+1 | ret_stack]) {ctx, s, si, unquote(addr)} end {:jump, addr} -> quote do {ctx, s, si, unquote(addr)} end {:capopen} -> quote do cap_stack = Xpeg.state(ctx, :cap_stack) ctx = Xpeg.state(ctx, cap_stack: [{:open, s, si} | cap_stack]) {ctx, s, si, unquote(ip + 1)} end {:capclose, type} -> quote do cap_stack = Xpeg.state(ctx, :cap_stack) ctx = Xpeg.state(ctx, cap_stack: [{:close, s, si, unquote(type)} | cap_stack]) {ctx, s, si, unquote(ip + 1)} end {:code, code} -> body = if options[:userdata] do quote do {captures, data} = func.(Xpeg.state(ctx, :captures), Xpeg.state(ctx, :userdata)) ctx = Xpeg.state(ctx, captures: captures, userdata: data) end else quote do captures = func.(Xpeg.state(ctx, :captures)) ctx = Xpeg.state(ctx, captures: captures) end end quote do ctx = Xpeg.collect_captures(ctx) func = unquote(code) unquote(body) {ctx, s, si, unquote(ip + 1)} end {:fail} -> quote do case Xpeg.state(ctx, :back_stack) do [frame | back_stack] -> ctx = Xpeg.state(ctx, back_stack: back_stack, ret_stack: frame.ret_stack, cap_stack: frame.cap_stack ) {ctx, frame.s, frame.si, frame.ip_back} [] -> {ctx, s, si, :error} end end end end def trace_inst(code, ip, inst, options) do if options[:trace] do trace = quote do: Xpeg.trace(unquote(ip), unquote(Xpeg.dump_inst(inst)), s) {:__block__, [], [trace, code]} else code end end def inline_one(ip, code, cases, refs, top) do if top and not Map.has_key?(refs, ip) do quote do :inlined end else Macro.postwalk(code, fn n -> case n do {:{}, [], [{:ctx, _, _}, _, _, ip_next]} -> if is_number(ip_next) and not Map.has_key?(refs, ip_next) do inline_one(ip_next, cases[ip_next], cases, refs, false) else n end n -> n end end) end end def inline(cases, refs) do Enum.map(cases, fn {ip, code} -> {ip, inline_one(ip, code, cases, refs, true)} end) |> Enum.filter(fn {ip, code} -> code != :inlined end) end def emit(program, options \\ []) do # Generate code for the IR cases = Enum.reduce(program.instructions, %{}, fn {ip, inst}, cases -> Map.put(cases, ip, emit_inst(ip, inst, options) |> trace_inst(ip, inst, options) ) end) |> inline(program.refs) # Generate case clauses clauses = Enum.map(cases, fn {ip, code} -> {:->, [], [[ip], code]} end) # Generate the main parser function f = quote do fn ctx, s, si, ip -> {ctx, s, si, ip} = case ip do unquote(clauses) end func = Xpeg.state(ctx, :func) case ip do :ok -> {:ok, ctx, si} :error -> {:error, ctx, si} _ -> func.(ctx, s, si, ip) end end end # Optionally dump generated code if options[:dump_code] do IO.puts(Macro.to_string(f)) end f end end # set ft=elixir