Current section

Files

Jump to
foil src foil_compiler.erl
Raw

src/foil_compiler.erl

-module(foil_compiler).
-include("foil.hrl").
-export([
load/2
]).
%% public
-spec load(namespace(), [{key(), value()}]) ->
ok.
load(Module, KVs) ->
Forms = forms(Module, KVs),
{ok, Module, Bin} = compile:forms(Forms, [debug_info]),
code:soft_purge(Module),
Filename = atom_to_list(Module) ++ ".erl",
{module, Module} = code:load_binary(Module, Filename, Bin),
ok.
%% private
forms(Module, KVs) ->
Mod = erl_syntax:attribute(erl_syntax:atom(module),
[erl_syntax:atom(Module)]),
ExportList = [
erl_syntax:arity_qualifier(erl_syntax:atom(lookup),
erl_syntax:integer(1)),
erl_syntax:arity_qualifier(erl_syntax:atom(all),
erl_syntax:integer(0))],
Export = erl_syntax:attribute(erl_syntax:atom(export),
[erl_syntax:list(ExportList)]),
Lookup = erl_syntax:function(erl_syntax:atom(lookup),
lookup_clauses(KVs)),
All = erl_syntax:function(erl_syntax:atom(all),
all(lists:sort(KVs))),
[erl_syntax:revert(X) || X <- [Mod, Export, Lookup, All]].
all(KVs) ->
Pairs = [erl_syntax:map_field_assoc(to_syntax(K), to_syntax(V))
|| {K, V} <- KVs],
Body = erl_syntax:tuple([erl_syntax:atom(ok), erl_syntax:map_expr(Pairs)]),
[erl_syntax:clause([], [Body])].
lookup_clause(Key, Value) ->
Var = to_syntax(Key),
Body = erl_syntax:tuple([erl_syntax:atom(ok),
to_syntax(Value)]),
erl_syntax:clause([Var], [], [Body]).
lookup_clause_anon() ->
Var = erl_syntax:variable("_"),
Body = erl_syntax:tuple([erl_syntax:atom(error),
erl_syntax:atom(key_not_found)]),
erl_syntax:clause([Var], [], [Body]).
lookup_clauses(KVs) ->
lookup_clauses(KVs, []).
lookup_clauses([], Acc) ->
lists:reverse(lists:flatten([lookup_clause_anon() | Acc]));
lookup_clauses([{Key, Value} | T], Acc) ->
lookup_clauses(T, [lookup_clause(Key, Value) | Acc]).
to_syntax(Atom) when is_atom(Atom) ->
erl_syntax:atom(Atom);
to_syntax(Binary) when is_binary(Binary) ->
String = erl_syntax:string(binary_to_list(Binary)),
erl_syntax:binary([erl_syntax:binary_field(String)]);
to_syntax(Float) when is_float(Float) ->
erl_syntax:float(Float);
to_syntax(Integer) when is_integer(Integer) ->
erl_syntax:integer(Integer);
to_syntax(List) when is_list(List) ->
erl_syntax:list([to_syntax(X) || X <- List]);
to_syntax(Tuple) when is_tuple(Tuple) ->
erl_syntax:tuple([to_syntax(X) || X <- tuple_to_list(Tuple)]);
to_syntax(Map) when is_map(Map) ->
erl_syntax:map_expr(
[erl_syntax:map_field_assoc(to_syntax(K), to_syntax(V))
|| {K, V} <- maps:to_list(Map)]);
%% Non-representable terms (references, pids, ports, funs, non-byte-aligned
%% bitstrings) have no abstract literal form. We smuggle them through an
%% integer syntax node — the BEAM module loader carries the term in the
%% compiled module's constant pool as-is, so lookup/1 returns it unchanged.
to_syntax(Ref) when is_reference(Ref) ->
smuggle(Ref);
to_syntax(Pid) when is_pid(Pid) ->
smuggle(Pid);
to_syntax(Port) when is_port(Port) ->
smuggle(Port);
to_syntax(Fun) when is_function(Fun) ->
smuggle(Fun);
to_syntax(Bitstring) when is_bitstring(Bitstring) ->
smuggle(Bitstring).
%% Goes through apply/3 so dialyzer doesn't enforce
%% erl_syntax:integer/1's `integer()` contract on the call site —
%% the spec violation is the whole point (see to_syntax/1 above).
smuggle(Term) ->
erlang:apply(erl_syntax, integer, [Term]).