recorder.ex (3037B)
1 defmodule NimbleParsec.Recorder do 2 @moduledoc false 3 4 @name __MODULE__ 5 6 @doc """ 7 Starts the recorder server. 8 """ 9 def start_link(_opts) do 10 Agent.start_link(fn -> %{} end, name: @name) 11 end 12 13 @doc """ 14 Stops the recorder server. 15 """ 16 def stop() do 17 Agent.stop(@name) 18 end 19 20 @doc """ 21 Records the given call and potentially debugs it. 22 """ 23 def record(module, parser_kind, combinator_kind, name, combinators, inline, opts) do 24 inline? = Keyword.get(opts, :inline, false) 25 26 if Keyword.get(opts, :debug, false) do 27 IO.puts(format_defs(combinator_kind, combinators, inline, inline?)) 28 end 29 30 if Process.whereis(@name) do 31 Agent.update(@name, fn state -> 32 update_in( 33 state[module], 34 &[{parser_kind, combinator_kind, name, combinators, inline, inline?} | &1 || []] 35 ) 36 end) 37 end 38 39 :ok 40 end 41 42 defp format_parser_kind(nil, _name) do 43 [] 44 end 45 46 defp format_parser_kind(:def, name) do 47 {doc, spec, def} = NimbleParsec.Compiler.entry_point(name) 48 49 """ 50 @doc "\"" 51 #{doc} 52 "\"" 53 @spec #{Macro.to_string(spec)} 54 #{format_def(:def, def)} 55 """ 56 end 57 58 defp format_parser_kind(:defp, name) do 59 {_doc, spec, def} = NimbleParsec.Compiler.entry_point(name) 60 61 """ 62 @spec #{Macro.to_string(spec)} 63 #{format_def(:defp, def)} 64 """ 65 end 66 67 defp format_defs(kind, defs, inline, inline?) do 68 functions = Enum.map(defs, &format_def(kind, &1)) 69 inline = if inline?, do: "@compile {:inline, #{inspect(inline)}}\n\n", else: "" 70 [inline | functions] 71 end 72 73 defp format_def(kind, {name, args, guards, body}) do 74 signature = Macro.to_string(quote(do: unquote(name)(unquote_splicing(args)))) 75 76 if guards == true do 77 """ 78 #{kind} #{signature} do 79 #{Macro.to_string(body)} 80 end 81 82 """ 83 else 84 """ 85 #{kind} #{signature} when #{Macro.to_string(guards)} do 86 #{Macro.to_string(body)} 87 end 88 89 """ 90 end 91 end 92 93 @doc """ 94 Replays recorded parsers on the given content. 95 """ 96 def replay(contents, id) when is_binary(contents) do 97 code = inject_recorded(contents, id, Agent.get(@name, & &1)) 98 [Code.format_string!(code) | "\n"] 99 end 100 101 defp inject_recorded(contents, id, recorded) do 102 Enum.reduce(recorded, contents, fn {module, entries}, acc -> 103 marker = "# parsec:#{inspect(module)}" 104 105 case String.split(acc, marker) do 106 [pre, _middle, pos] -> 107 replacement = Enum.map(entries, &format_recorded/1) 108 IO.iodata_to_binary([pre, replacement, pos]) 109 110 [_, _] -> 111 raise ArgumentError, "expected 2 markers #{inspect(marker)} on #{inspect(id)}, got 1" 112 113 _ -> 114 raise ArgumentError, "could not find marker #{inspect(marker)} on #{inspect(id)}" 115 end 116 end) 117 end 118 119 defp format_recorded({parser_kind, combinator_kind, name, combinators, inline, inline?}) do 120 [ 121 format_parser_kind(parser_kind, name) 122 | format_defs(combinator_kind, combinators, inline, inline?) 123 ] 124 end 125 end