defmodule FT do
def boot(), do: Tests.compilePrivFolder
# Erlang AST definitions
def cons([]), do: {:nil,1}
def cons([x|t]), do: {:cons,1,x,cons(t)}
def mod(name), do: {:attribute,1,:module,name}
def compile_all(), do: {:attribute,1,:compile,:export_all}
def string(val), do: {:string,1,val}
def integer(val), do: {:integer,1,val}
def binary(val), do: {:bin,1,[{:bin_element,1,string(val),:default,[:utf8]}]}
def var(val), do: {:var,1,latom(val)}
def atom(val) when is_atom(val), do: {:atom,1,val}
def atom(val) when is_list(val), do: {:atom,1,latom(val)}
def fun(mod,name,arity), do: {:fun,1,{:function,mod,name,integer(arity)}}
# Imported module records
def record(recname,fields) do
recfields = :lists.map(fn {name,defx,type} ->
default = case defx do
[] -> cons([])
x when is_integer(x) -> integer(x)
x when is_list(x) -> string(x)
x when is_binary(x) -> binary(blist(x))
x -> x
end
{:typed_record_field,{:record_field,1,atom(name),default},type}
end, fields)
{:attribute,1,:record,{recname,recfields}}
end
# routeProc{} onstructor invocation generation
def routeProcInvoke(folder,users,folderType,callback) do
{:record,1,:routeProc,
:lists.map(fn {name,val} -> {:record_field,1,atom(name),val} end,
:lists.flatten([ RouteProc.folderField(folder),
RouteProc.folderTypeField(folderType),
RouteProc.usersField(users),
RouteProc.callbackField(callback) ]))}
end
# Route clause generator
def routeClause(src,dst,commands) do
cmds = :lists.map fn {folder,users,folderType,callback} ->
routeProcInvoke(folder,users,folderType,callback) end, commands
{:clause,1,[{:tuple,1,[atom(:request),string(src),string(dst)]},var('FT')],[],[cons(cmds)]}
end
# Route function generator
def routeFun(name,clauses) do
routeClauses = :lists.map fn {s,d,cmds} -> routeClause(s,d,cmds) end, clauses
{:function,1,name,2,routeClauses}
end
# Load and parse file
def loadFile(file), do: :ft.console ['snd','load',[priv_dir(),priv_prefix(),file]]
# Parse file and substutute imports in its declarations
def load(file) when is_list(file), do: loadFileAndUnrollImports file
def load(file) when is_binary(file), do: loadFileAndUnrollImports blist(file)
def loadFileAndUnrollImports(file,dict \\ %{}) when is_list(file) do
{:module,name,spec,decls} = loadFile(file)
{:module,name,spec,unrollImports(decls, :maps.put(name,true,dict))}
end
# Substitute imports in file declarations
def unrollImports(decls,d) do
{acc,_} = :lists.foldl(
fn {:import,import}, {acc,dict} ->
case :maps.get(latom(blist(import)),dict,[]) do
true -> {acc,dict}
_ -> {_,_,_,dec} = loadFileAndUnrollImports(blist(import),dict)
{acc++dec,:maps.put(latom(blist(import)),true,dict)} end
x, {acc,dict} -> {acc++[x],dict} end, {[],d}, decls)
:lists.flatten(acc)
end
# Helpers
def latom(x), do: :erlang.list_to_atom x
def alist(x), do: :erlang.atom_to_list x
def blist(x), do: :erlang.binary_to_list x
def priv_prefix(), do: :application.get_env(:ft,:priv_prefix,'/erp.uno/')
def priv_dir(), do: :application.get_env(:ft,:priv_dir,:code.priv_dir(:ft))
# Compile Erlang AST forms to disk and reload
def default(["[]"]), do: []
def default([str]) when is_binary(str), do: binary(blist(str))
def default(_), do: []
def compileRecord(name,fields) do
record(latom(blist(name)),
:lists.map(fn {:field, name, defx, _type} ->
{latom(blist(name)),default(defx),{:type,1,:term,[]}}
end, fields))
end
def compileEvent(_name, _decls) do
[]
end
def compileNotice(_name, _decls) do
[]
end
def compileForm(_name, _decls) do
[]
end
def compileRoute(name,calls) do
routeFun(latom(blist(name)), :lists.map(fn {:call,[y]} ->
x = blist(y)
[l,r] = case :string.tokens(x,':') do [a1,a2] -> [a1,a2] ; [a1] -> [a1,[]] end
[s,d] = :string.tokens(:string.trim(l,:both,'()'),',')
clauses = :string.tokens(r,';')
# :io.format 'debug route: ~p~n', [{s,d,clauses}]
{Stages.dis(s),Stages.dis(d),
:lists.flatten(:lists.map(fn x ->
case :string.tokens(x,',') do
[] -> []
[_folder] -> []
[folder,users] -> {Folders.dis(folder),Fields.dis(users),[],[]}
[folder,users,callback] -> {Folders.dis(folder),Fields.dis(users),[],{'Elixir.CRM.KEP',callback}}
[folder,users,callback,folderType] -> {Folders.dis(folder),Fields.dis(users),folderType,{'Elixir.CRM.KEP',callback}}
end
end, clauses))}
end, calls))
end
def compileFile(file), do: compileFormalTalkFile(file)
def compileFormalTalkFile(file) do
{:module,name,spec,decls} = file
[ mod(latom(alist(spec) ++ '.' ++ blist(name))), compile_all() ] ++
:lists.flatten(:lists.map(fn
{:record,{:name,name}, [], fields} -> compileRecord(name,fields)
{:route,{:name,name}, [], calls} -> compileRoute(name,calls)
{:event,{:name,name}, [], decls} -> compileEvent(name,decls)
{:notice,{:name,name}, [], decls} -> compileNotice(name,decls)
{:form,{:name,name}, [], decls} -> compileForm(name,decls)
_ -> [] end, decls))
end
def compileForms(ast, out \\ 'priv/out/'), do: compileErlangForms(ast, out)
def compileErlangForms(ast, out) do
:filelib.ensure_dir out
:code.add_pathz out
case :compile.forms ast, [:debug_info,:return] do
{:ok,name,beam,[{_,_warn}]} ->
# :io.format 'warnings: ~p~n', [warn]
:file.write_file out ++ alist(name) ++ '.beam', beam
:code.purge name
:code.load_file name
{name,beam}
x ->
:io.format 'errors: ~p~n', [x]
{[],[]}
end
end
end