Current section

Files

Jump to
efene src efene.erl
Raw

src/efene.erl

%% Copyright 2015 Mariano Guerra
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-module(efene).
-export([main/0, main/1, compile/2, compile/3, to_code/1, to_code/2,
to_raw_lex/1, to_lex/1, to_ast/1, to_erl/1, to_erl_ast/1, to_mod/1,
pprint/1, print_errors/2]).
-export([str_to_ast/1]).
read_file(Path) ->
case file:read_file(Path) of
{ok, Content} -> {ok, unicode:characters_to_list(Content, utf8)};
Other -> Other
end.
with_file_content(Path, Fn) ->
case read_file(Path) of
{ok, Content} -> Fn(Content);
Other -> Other
end.
to_raw_lex(Path) -> with_file_content(Path, fun str_to_raw_lex/1).
to_lex(Path) -> with_file_content(Path, fun str_to_lex/1).
to_ast(Path) -> with_file_content(Path, fun str_to_ast/1).
to_erl_ast(Path) -> with_file_content(Path, fun (Str) ->
Module = get_module_name(Path),
str_to_erl_ast(Str, Module)
end).
to_mod(Path) ->
case to_erl_ast(Path) of
{ok, {Ast, State}} ->
ModAtomName = get_module_name(Path),
ToMod = fun () ->
ExportAttr = fn_util:get_export_attr_ast(State),
FnAttrs = fn_util:get_fn_attribute(State),
ModAttr = {attribute, 1, module, ModAtomName},
FileAttr = {attribute, 1, file, {Path, 1}},
FilteredFnAttrs = remove_emptish_fn_attrs(FnAttrs, []),
case ExportAttr of
{attribute, _ExportLine, export, []} ->
{ok, [FileAttr, ModAttr|(FilteredFnAttrs ++ Ast)]};
_ ->
{ok, [FileAttr, ModAttr, ExportAttr|(FilteredFnAttrs ++ Ast)]}
end
end,
format_errors_or(ModAtomName, State, ToMod);
Other -> Other
end.
pprint(Path) ->
case to_ast(Path) of
{ok, Ast} -> io:format("~s", [fn_pp:print(Ast)]);
Other -> io:format("Error: ~p", [Other])
end.
to_erl(Path) ->
case to_mod(Path) of
{ok, [_|Mod]} -> erl_prettypr:format(erl_syntax:form_list(Mod));
Other -> Other
end.
to_code(Path) ->
to_code(Path, []).
to_code(Path, Opts) ->
case to_mod(Path) of
{ok, Ast} ->
case compile:forms(Ast, [return, strong_validation|Opts]) of
{error, _Errors, _Warnings}=Error -> Error;
error -> {error, [{error, compile_forms_error}], []};
{ok, _ModuleName0, ValidationInfo} ->
Warnings0 = case ValidationInfo of
[] -> [];
[{_FileName0, W0}] -> W0
end,
case compile:forms(Ast, Opts) of
{ok, ModuleName, Code} -> {ok, ModuleName, Code, Warnings0};
{ok, ModuleName, Code, Warnings} ->
{ok, ModuleName, Code, Warnings0 ++ Warnings};
error -> {error, [{error, compile_forms_error}], []};
{error, _Errors, _Warnings}=Error -> Error
end
end;
Other -> Other
end.
compile(Path, DestPath) ->
compile(Path, DestPath, []).
compile(Path, DestPath, Opts) ->
case to_code(Path, Opts) of
{ok, ModuleName, Code, Warnings} ->
BeamPath = filename:join(DestPath, get_module_beam_name(Path)),
case bin_to_file(Code, BeamPath) of
error -> {error, [{file_write_error, BeamPath}], []};
{error, enoent} -> {error, [{file_write_error, BeamPath}], []};
ok -> {ok, [{warnings, Warnings}, {module_name, ModuleName}]}
end;
Other -> Other
end.
from_erl(Path) -> epp:parse_file(Path, [], []).
str_to_raw_lex(String) -> fn_lexer:string(String).
str_to_lex(String) ->
case fn_lexer:string(String) of
{ok, Tokens, Endline} ->
CleanTokens = clean_tokens(Tokens),
{ok, CleanTokens, Endline};
{eof, Endline} -> {error, {Endline, fn_lexer, {eof, Endline}}};
{error, Error} -> {error, Error};
{error, Error, _} -> {error, Error}
end.
str_to_ast(Str) ->
case str_to_lex(Str) of
{ok, Tokens, _NewLine} -> fn_parser:parse(Tokens);
Other -> Other
end.
% to force escriptize to load the default extension modules
force_extensions_load() ->
fn_ext_expr_atom_b:handle(nil, nil, nil),
fn_ext_expr_var__:handle(nil, nil, nil),
fn_ext_val_atom_atom:handle(nil, nil, nil),
fn_ext_val_atom_b:handle(nil, nil, nil),
fn_ext_val_atom_c:handle(nil, nil, nil),
fn_ext_val_atom_i:handle(nil, nil, nil),
fn_ext_val_atom_m:handle(nil, nil, nil),
fn_ext_val_atom_r:handle(nil, nil, nil),
fn_ext_val_var__:handle(nil, nil, nil).
str_to_erl_ast(String, Module) ->
force_extensions_load(),
case str_to_ast(String) of
{ok, Ast} -> {ok, fn_to_erl:to_erl(Ast, Module)};
Other -> Other
end.
% cli utilities
print({ok, Data}) ->
print(Data);
print({error, _}=Error) ->
Reason = fn_error:normalize(Error),
io:format("error:~s~n", [Reason]);
print(Data) ->
try io:format("~s~n", [Data]) catch
_:_ -> io:format("~p~n", [Data])
end.
% command line interface
main() ->
main([]).
main([]) ->
io:format("Usage:~n"),
io:format("\tefene rawlex <file>: print raw lex tokens~n"),
io:format("\tefene lex <file>: print lex tokens after normalization~n"),
io:format("\tefene ast <file>: print efene abstract syntax tree~n"),
io:format("\tefene erlast <file>: print erlang abstract syntax tree (absform)~n"),
io:format("\tefene mod <file>: print erlang absform with module attributes~n"),
io:format("\tefene erl <file>: print erlang code for <file>~n"),
io:format("\tefene erl2ast <file.erl>: print erlang absform <file.erl>~n"),
io:format("\tefene beam <file> [<outdir>]: compile <file> to beam bytecode to <outdir>~n"),
io:format("\tefene pprint <file>: pretty print code from <file> (experimental)~n"),
ok;
main(["rawlex", File]) ->
print(to_raw_lex(File));
main(["lex", File]) ->
print(to_lex(File));
main(["ast", File]) ->
print(to_ast(File));
main(["mod", File]) ->
print(to_mod(File));
main(["erlast", File]) ->
print(to_erl_ast(File));
main(["erl", File]) ->
print(to_erl(File));
main(["erl2ast", File]) ->
print(from_erl(File));
main(["beam", File]) ->
main(["beam", File, "."]);
main(["shell"]) ->
user_drv:start(["tty_sl -c -e", {fn_repl, start, []}]);
main(["beam", File, OutputDir]) ->
case compile(File, OutputDir, [debug_info]) of
{ok, CompileInfo} ->
print_errors(proplists:get_value(warnings, CompileInfo, []), "warnings");
{error, Errors, Warnings} ->
print_errors(Errors, "errors"),
print_errors(Warnings, "warnings"),
ok;
Other ->
print(Other)
end;
main(["pprint", File]) ->
pprint(File);
main(Opts) ->
io:format("Invalid arguments: \"~p\"~n", [Opts]),
main([]).
% private
clean_tokens(Tokens) -> clean_tokens(Tokens, []).
clean_tokens([], Accum) -> lists:reverse(Accum);
% remove newline after colon, comma and semicolon
clean_tokens([{colon, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{semicolon, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{sep, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
% remove newline after logical ops
clean_tokens([{bool_or, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{bool_orr, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{bool_xor, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{bool_and, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{bool_andd, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
% remove newline after ops
clean_tokens([{add_op, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{mul_op, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{comp_op, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{concat_op, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{send_op, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{bin_shift, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{bin_and, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{bin_or, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{assign, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{coloneq, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
% remove newline after open [ ( and {
clean_tokens([{open, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{open_list, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{open_map, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
% remove newline after arrows
clean_tokens([{arrow, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{arrowend, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{larrow, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{larrowend, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
% remove newline after try, catch, after, receive, begin, when
clean_tokens([{'try', _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{'catch', _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{'after', _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{'receive', _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{'begin', _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{'when', _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{'fn', _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{'fn', _}=H1, {var, _, _}=H2, {nl, _, _}|T], Accum) -> clean_tokens([H1, H2|T], Accum);
clean_tokens([{'fn', _}=H1, {atom, _, _}=H2, {nl, _, _}|T], Accum) -> clean_tokens([H1, H2|T], Accum);
clean_tokens([{'def', _}=H1, {atom, _, _}=H2, {nl, _, _}|T], Accum) -> clean_tokens([H1, H2|T], Accum);
% remove newline before end and @
clean_tokens([{nl, _, _}, {'end', _, _}=H|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{nl, _, _}, {'case', _}=H|T], Accum) -> clean_tokens([H|T], Accum);
% remove newline before closing ] ) and }
clean_tokens([{nl, _, _}, {close, _, _}=H|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{nl, _, _}, {close_list, _, _}=H|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{nl, _, _}, {close_map, _, _}=H|T], Accum) -> clean_tokens([H|T], Accum);
% remove newline before arrows
clean_tokens([{nl, _, _}, {arrow, _, _}=H|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{nl, _, _}, {arrowend, _, _}=H|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{nl, _, _}, {larrow, _, _}=H|T], Accum) -> clean_tokens([H|T], Accum);
clean_tokens([{nl, _, _}, {larrowend, _, _}=H|T], Accum) -> clean_tokens([H|T], Accum);
% remove duplicated endlines
clean_tokens([{nl, _, _}, {nl, _, _}=H|T], Accum) -> clean_tokens([H|T], Accum);
% remove last endline
clean_tokens([{nl, _, _}], Accum) -> clean_tokens([], Accum);
clean_tokens([H|T], Accum) -> clean_tokens(T, [H|Accum]).
get_module_str_name(Path) ->
BaseName = filename:basename(Path),
filename:rootname(BaseName).
get_module_name(Path) ->
list_to_atom(get_module_str_name(Path)).
get_module_beam_name(Path) ->
ModuleNameStr = get_module_str_name(Path),
string:concat(ModuleNameStr, ".beam").
bin_to_file(Bin, Path) ->
to_file(Bin, Path, [binary, write]).
to_file(Data, Path, Mode) ->
case file:open(Path, Mode) of
{ok, Device} ->
file:write(Device, Data),
file:close(Device),
ok;
Error -> Error
end.
format_errors_or(_Module, #{errors:=[]}, Fn) -> Fn();
format_errors_or(Module, #{errors:=Errors}, _Fn) ->
ErrorsFirstToLast = lists:reverse(Errors),
{error, {efene, Module, fn_error:to_string(Module, ErrorsFirstToLast)}}.
% remove fn attributes that only contain the public or no attributes
remove_emptish_fn_attrs([], Accum) ->
lists:reverse(Accum);
remove_emptish_fn_attrs([{attribute, _ALine, fn_attrs, {_FName, _FArity, []}}|T], Accum) ->
remove_emptish_fn_attrs(T, Accum);
remove_emptish_fn_attrs([{attribute, _ALine, fn_attrs, {_FName, _FArity, [{[public], {[], []}}]}}|T], Accum) ->
remove_emptish_fn_attrs(T, Accum);
remove_emptish_fn_attrs([H|T], Accum) ->
remove_emptish_fn_attrs(T, [H|Accum]).
print_errors([], _Prefix) -> ok;
print_errors(Errors, Prefix) ->
lists:foreach(fun (Error) ->
Reason = fn_error:normalize(Error),
io:format("~s:~n~s~n", [Prefix, Reason])
end, Errors).