Current section
Files
Jump to
Current section
Files
lib/eyeon/macro/system.ex
defmodule Eyeon.Macro.System do
@moduledoc false
alias Eyeon.Macro.{Helpers, Template, Timestamp}
@doc false
def dispatch("none", args, _name_token) do
case args do
[] -> {:splice, []}
_ -> {:error, "none: does not accept any arguments"}
end
end
def dispatch("values", args, _name_token) do
{:splice, args}
end
def dispatch("make_string", args, name_token) do
case Helpers.concat_string_args(args) do
{:error, _} = err -> err
result -> {:short_string, Helpers.extract_line(name_token), String.to_charlist(result)}
end
end
def dispatch("meta", _args, _name_token) do
{:splice, []}
end
def dispatch("make_symbol", args, name_token) do
case Helpers.concat_string_args(args) do
{:error, _} = err ->
err
result ->
escaped = result |> String.replace("\\", "\\\\") |> String.replace("'", "\\'")
{:quoted_symbol, Helpers.extract_line(name_token), String.to_charlist("'#{escaped}'")}
end
end
def dispatch("make_decimal", args, name_token) do
case args do
[coeff_node, exp_node] ->
coeff_node = Helpers.strip_annotations(coeff_node)
exp_node = Helpers.strip_annotations(exp_node)
with {:ok, coeff} <- Helpers.extract_integer(coeff_node),
{:ok, exp} <- Helpers.extract_integer(exp_node) do
decimal_str = "#{coeff}d#{exp}"
{:decimal, Helpers.extract_line(name_token), String.to_charlist(decimal_str)}
else
:error -> {:error, "make_decimal: arguments must be integers"}
end
_ ->
{:error, "make_decimal: requires exactly 2 arguments"}
end
end
def dispatch("sum", args, name_token) do
case args do
[a_node, b_node] ->
a_node = Helpers.strip_annotations(a_node)
b_node = Helpers.strip_annotations(b_node)
with {:ok, a} <- Helpers.extract_integer(a_node),
{:ok, b} <- Helpers.extract_integer(b_node) do
Helpers.make_int_node(a + b, Helpers.extract_line(name_token))
else
:error -> {:error, "sum: arguments must be integers"}
end
_ ->
{:error, "sum: requires exactly 2 arguments"}
end
end
def dispatch("delta", args, _name_token) do
case args do
[] ->
{:splice, []}
_ ->
result =
Enum.reduce_while(args, {:ok, 0, []}, fn arg, {:ok, running, acc} ->
arg = Helpers.strip_annotations(arg)
case Helpers.extract_integer(arg) do
{:ok, n} ->
new_sum = running + n
{:cont, {:ok, new_sum, acc ++ [Helpers.make_int_node(new_sum)]}}
:error ->
{:halt, {:error, "delta: arguments must be integers"}}
end
end)
case result do
{:error, _} = err -> err
{:ok, _running, nodes} -> {:splice, nodes}
end
end
end
def dispatch("repeat", args, _name_token) do
case args do
[] ->
{:error, "repeat: requires at least 1 argument (count)"}
[count_node | value_nodes] ->
count_node = Helpers.strip_annotations(count_node)
case Helpers.extract_integer(count_node) do
{:ok, n} when n >= 0 ->
repeated = List.duplicate(value_nodes, n) |> List.flatten()
{:splice, repeated}
{:ok, _n} ->
{:error, "repeat: count must be non-negative"}
:error ->
{:error, "repeat: first argument must be an integer"}
end
end
end
def dispatch("flatten", args, _name_token) do
case Helpers.flatten_sequence_args(args) do
{:error, _} = err -> err
elements -> {:splice, elements}
end
end
def dispatch("make_list", args, _name_token) do
case Helpers.flatten_sequence_args(args) do
{:error, _} = err -> err
elements -> {:list, elements}
end
end
def dispatch("make_sexp", args, _name_token) do
case Helpers.flatten_sequence_args(args) do
{:error, _} = err -> err
[] -> {:sexp}
elements -> {:sexp, Enum.map(elements, &[&1])}
end
end
def dispatch("make_field", args, _name_token) do
case args do
[name_node, value_node] ->
name_node = Helpers.strip_annotations(name_node)
case Helpers.extract_text(name_node) do
{:error, _} = err ->
err
text ->
field_name =
{:quoted_symbol, Helpers.extract_line_from(name_node),
String.to_charlist("'#{text}'")}
{:struct, [{field_name, value_node}]}
end
_ ->
{:error, "make_field: requires exactly 2 arguments"}
end
end
def dispatch("make_struct", args, _name_token) do
result =
Enum.reduce_while(args, [], fn arg, acc ->
arg = Helpers.strip_annotations(arg)
case Helpers.extract_struct_fields(arg) do
{:ok, fields} -> {:cont, acc ++ fields}
:error -> {:halt, {:error, "make_struct: arguments must be structs"}}
end
end)
case result do
{:error, _} = err -> err
fields -> {:struct, fields}
end
end
def dispatch("annotate", args, _name_token) do
case args do
[] ->
{:error, "annotate: requires at least 1 argument (value)"}
_ ->
# Last arg is the value, everything before is the annotation stream
{ann_nodes, [value_node]} = Enum.split(args, length(args) - 1)
case Helpers.collect_annotations(ann_nodes) do
{:error, _} = err ->
err
[] ->
value_node
annotations ->
Enum.reduce(Enum.reverse(annotations), value_node, fn ann, inner ->
{:annotated, ann, inner}
end)
end
end
end
def dispatch("default", args, _name_token) do
case args do
[] ->
{:splice, []}
[single] ->
single
[first | _rest] ->
first
_ ->
{:splice, []}
end
end
def dispatch("make_timestamp", args, name_token) do
Timestamp.dispatch(args, name_token)
end
def dispatch("parse_ion", args, _name_token) do
case args do
[arg] ->
arg = Helpers.strip_annotations(arg)
case Helpers.extract_parse_ion_text(arg) do
{:error, _} = err ->
err
{:ok, <<0xE0, 0x01, _minor::8, 0xEA, _rest::binary>>} ->
{:error, "parse_ion: binary Ion blobs not yet supported at AST level"}
{:ok, text} ->
case Eyeon.TextParser.parse(text) do
{:ok, values} when is_list(values) ->
{:splice, values}
{:ok, value} ->
{:splice, [value]}
{:error, _} = err ->
err
end
end
[] ->
{:error, "parse_ion: requires exactly 1 argument"}
_ ->
{:error, "parse_ion: requires exactly 1 argument"}
end
end
def dispatch("set_symbols", args, _name_token) do
case extract_symbol_args(args) do
{:error, _} = err -> err
symbols -> {:directive, :set_symbols, symbols}
end
end
def dispatch("add_symbols", args, _name_token) do
case extract_symbol_args(args) do
{:error, _} = err -> err
symbols -> {:directive, :add_symbols, symbols}
end
end
def dispatch("set_macros", args, _name_token) do
case Template.parse_macro_defs(args) do
{:error, _} = err -> err
macro_defs -> {:directive, :set_macros, macro_defs}
end
end
def dispatch("add_macros", args, _name_token) do
case Template.parse_macro_defs(args) do
{:error, _} = err -> err
macro_defs -> {:directive, :add_macros, macro_defs}
end
end
def dispatch(name, _args, _name_token) do
{:error, "unknown macro: #{name}"}
end
# Extract symbol text from args for set_symbols/add_symbols
defp extract_symbol_args(args) do
Enum.reduce_while(args, [], fn arg, acc ->
case arg do
{:null, _} ->
{:halt, {:error, "set_symbols/add_symbols: null not allowed"}}
{:typed_null, _, _} ->
{:halt, {:error, "set_symbols/add_symbols: null not allowed"}}
{:annotated, _, _} ->
{:halt, {:error, "set_symbols/add_symbols: annotated arguments not allowed"}}
{:symbol_id, _, chars} ->
# $N symbols with unknown text are not allowed
id =
chars
|> List.to_string()
|> String.trim_leading("$")
|> String.to_integer()
if id == 0 do
{:halt, {:error, "set_symbols/add_symbols: $0 not allowed"}}
else
{:halt, {:error, "set_symbols/add_symbols: unknown symbol ID $#{id}"}}
end
_ ->
case Helpers.extract_text(arg) do
{:error, _} = err -> {:halt, err}
text -> {:cont, acc ++ [text]}
end
end
end)
end
end