diff options
Diffstat (limited to 'lib/irc/parser/line.ex')
-rw-r--r-- | lib/irc/parser/line.ex | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/lib/irc/parser/line.ex b/lib/irc/parser/line.ex new file mode 100644 index 0000000..3ba2016 --- /dev/null +++ b/lib/irc/parser/line.ex @@ -0,0 +1,143 @@ +defmodule Irc.Parser.Line do + @moduledoc """ + IRC line parser + """ + + defstruct tags: %{}, source: nil, command: nil, args: [] + + @doc "Parse a server-to-client line." + def parse(line) do + {tags, rest} = parse_tags(line) + {source, rest} = parse_source(rest) + {command, rest} = parse_cmd(rest) + args = parse_args(rest) + %__MODULE__{tags: tags, source: source, command: command, args: args} + end + + # ARGS + + def parse_args(input), do: parse_args(input, []) + + def parse_args(input, acc) do + case parse_arg(input, []) do + {:continue, new, rest} -> parse_args(rest, [new | acc]) + {:finished, new} -> + list = new ++ acc + format_args(list) + end + end + + # Final argument is the only argument. + def parse_arg([?: | rest], acc) when length(acc) == 0 do + final = parse_final_arg(rest, []) + {:finished, [final]} + end + + def parse_arg([0x20, ?: | rest], acc) do + final = parse_final_arg(rest, []) + {:finished, [final, acc]} + end + + def parse_arg([0x20 | rest], acc) do + {:continue, acc, rest} + end + + def parse_arg([char | rest], acc) do + parse_arg(rest, [char | acc]) + end + + def parse_arg([], acc) do + {:finished, [acc]} + end + + def parse_final_arg([char | rest], acc) do + parse_final_arg(rest, [char | acc]) + end + + def parse_final_arg([], acc) do + acc + end + + def format_args(list) when is_list(list) do + list + |> Enum.map(fn(arg) -> String.trim(acc_to_str(arg)) end) + |> Enum.reverse() + end + + # COMMAND + + def parse_cmd(input), do: parse_cmd(input, []) + + def parse_cmd([0x20 | rest], acc) do + {acc_to_str(acc), rest} + end + + def parse_cmd([char | rest], acc) do + parse_cmd(rest, [char | acc]) + end + + # SOURCE + + def parse_source([?: | input]), do: parse_source(input, []) + def parse_source(input), do: {nil, input} + + def parse_source([0x20 | rest], acc) do + {acc_to_str(acc), rest} + end + def parse_source([char | rest], acc), do: parse_source(rest, [char | acc]) + + # TAGS + + def parse_tags([?@ | input]), do: parse_tags(input, []) + def parse_tags(input), do: {%{}, input} + + def parse_tags(input, acc) do + case parse_tag(input) do + {:continue, new, rest} -> parse_tags([new | acc], rest) + {:finished, new, rest} -> {format_tags([new | acc]), rest} + end + end + + def parse_tag(input) do + parse_tag({[], nil}, input) + end + + def parse_tag({acc, nil}, [?= | rest]) do + parse_tag({acc, []}, rest) + end + def parse_tag({acc, nil}, [?; | rest]) do + {:continue, {acc, true}, rest} + end + def parse_tag({acc, nil}, [char | rest]) do + parse_tag({[char | acc], nil}, rest) + end + + def parse_tag({key, acc}, [?; | rest]) do + {:continue, {key, acc}, rest} + end + + def parse_tag({key, acc}, [0x20 | rest]) do + {:finished, {key, acc}, rest} + end + def parse_tag({key, acc}, [char | rest]) do + parse_tag({key, [char | acc]}, rest) + end + + def format_tags(list) do + list + |> Enum.map(fn({k,v}) -> + {acc_to_str(k), acc_to_str(v)} + end) + |> Enum.into(Map.new) + end + + def acc_to_str([]), do: nil + def acc_to_str(list) when is_list(list) do + list + |> Enum.reverse() + |> to_string() + end + def acc_to_str(other), do: other + +end + |