diff --git a/Makefile b/Makefile index 933ce28..538d561 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ ERLWARE_COMMONS_PLT=$(CURDIR)/.erlware_commons_plt .PHONY: all compile doc clean test dialyzer typer shell distclean pdf get-deps escript -all: compile test dialyzer +all: compile test doc dialyzer get-deps: $(REBAR) get-deps diff --git a/src/ec_compile.erl b/src/ec_compile.erl new file mode 100644 index 0000000..482bdb3 --- /dev/null +++ b/src/ec_compile.erl @@ -0,0 +1,107 @@ +%%%------------------------------------------------------------------- +%%% @author Eric Merritt <> +%%% @copyright (C) 2011, Erlware, LLC. +%%% @doc +%%% These are various utility functions to help with compiling and +%%% decompiling erlang source. They are mostly useful to the +%%% language/parse transform implementor. +%%% @end +%%%------------------------------------------------------------------- +-module(ec_compile). + +-export([beam_to_erl_source/2, + erl_source_to_core_ast/1, + erl_source_to_erl_ast/1, + erl_source_to_asm/1, + erl_string_to_core_ast/1, + erl_string_to_erl_ast/1, + erl_string_to_asm/1]). + +%%%=================================================================== +%%% API +%%%=================================================================== + +%% @doc decompile a beam file that has been compiled with +debug_info +%% into a erlang source file +%% +%% @param BeamFName the name of the beamfile +%% @param ErlFName the name of the erlang file where the generated +%% source file will be output. This should *not* be the same as the +%% source file that created the beamfile unless you want to overwrite +%% it. +-spec beam_to_erl_source(string(), string()) -> ok | term(). +beam_to_erl_source(BeamFName, ErlFName) -> + case beam_lib:chunks(BeamFName, [abstract_code]) of + {ok, {_, [{abstract_code, {raw_abstract_v1,Forms}}]}} -> + Src = + erl_prettypr:format(erl_syntax:form_list(tl(Forms))), + {ok, Fd} = file:open(ErlFName, [write]), + io:fwrite(Fd, "~s~n", [Src]), + file:close(Fd); + Error -> + Error + end. + +%% @doc compile an erlang source file into a Core Erlang AST +%% +%% @param Path - The path to the erlang source file +-spec erl_source_to_core_ast(file:filename()) -> CoreAst::term(). +erl_source_to_core_ast(Path) -> + {ok, Contents} = file:read_file(Path), + erl_string_to_core_ast(binary_to_list(Contents)). + +%% @doc compile an erlang source file into an Erlang AST +%% +%% @param Path - The path to the erlang source file +-spec erl_source_to_erl_ast(file:filename()) -> ErlangAst::term(). +erl_source_to_erl_ast(Path) -> + {ok, Contents} = file:read_file(Path), + erl_string_to_erl_ast(binary_to_list(Contents)). + +%% @doc compile an erlang source file into erlang terms that represent +%% the relevant ASM +%% +%% @param Path - The path to the erlang source file +-spec erl_source_to_asm(file:filename()) -> ErlangAsm::term(). +erl_source_to_asm(Path) -> + {ok, Contents} = file:read_file(Path), + erl_string_to_asm(binary_to_list(Contents)). + +%% @doc compile a string representing an erlang expression into an +%% Erlang AST +%% +%% @param StringExpr - The path to the erlang source file +-spec erl_string_to_erl_ast(string()) -> ErlangAst::term(). +erl_string_to_erl_ast(StringExpr) -> + Forms0 = + lists:foldl(fun(<<>>, Acc) -> + Acc; + (<<"\n\n">>, Acc) -> + Acc; + (El, Acc) -> + {ok, Tokens, _} = + erl_scan:string(binary_to_list(El) + ++ "."), + [Tokens | Acc] + end, [], re:split(StringExpr, "\\.\n")), + %% No need to reverse. This will rereverse for us + lists:foldl(fun(Form, Forms) -> + {ok, ErlAST} = erl_parse:parse_form(Form), + [ErlAST | Forms] + end, [], Forms0). + +%% @doc compile a string representing an erlang expression into a +%% Core Erlang AST +%% +%% @param StringExpr - The path to the erlang source file +-spec erl_string_to_core_ast(string()) -> CoreAst::term(). +erl_string_to_core_ast(StringExpr) -> + compile:forms(erl_string_to_erl_ast(StringExpr), [to_core]). + +%% @doc compile a string representing an erlang expression into a term +%% that represents the ASM +%% +%% @param StringExpr - The path to the erlang source file +-spec erl_string_to_asm(string()) -> ErlangAsm::term(). +erl_string_to_asm(StringExpr) -> + compile:forms(erl_string_to_erl_ast(StringExpr), ['S']). diff --git a/src/ec_semver.erl b/src/ec_semver.erl index 4dc83e9..f8d4835 100644 --- a/src/ec_semver.erl +++ b/src/ec_semver.erl @@ -8,6 +8,7 @@ -module(ec_semver). -export([parse/1, + format/1, eql/2, gt/2, gte/2, @@ -54,6 +55,24 @@ parse(Version) when erlang:is_binary(Version) -> parse(Version) -> Version. +-spec format(semver()) -> iolist(). +format({Maj, {AlphaPart, BuildPart}}) + when erlang:is_integer(Maj) -> + [erlang:integer_to_list(Maj), + format_vsn_rest(<<"-">>, AlphaPart), + format_vsn_rest(<<"+">>, BuildPart)]; +format({{Maj, Min}, {AlphaPart, BuildPart}}) -> + [erlang:integer_to_list(Maj), ".", + erlang:integer_to_list(Min), + format_vsn_rest(<<"-">>, AlphaPart), + format_vsn_rest(<<"+">>, BuildPart)]; +format({{Maj, Min, Patch}, {AlphaPart, BuildPart}}) -> + [erlang:integer_to_list(Maj), ".", + erlang:integer_to_list(Min), ".", + erlang:integer_to_list(Patch), + format_vsn_rest(<<"-">>, AlphaPart), + format_vsn_rest(<<"+">>, BuildPart)]. + %% @doc test for quality between semver versions -spec eql(any_version(), any_version()) -> boolean(). eql(VsnA, VsnB) -> @@ -141,8 +160,8 @@ between(Vsn1, Vsn2, VsnMatch) -> %% revisions and "~> 2.6.5" is pessimistic about future minor %% revisions. %% -%% "~> 2.6" matches cookbooks >= 2.6.0 AND < 3.0.0 -%% "~> 2.6.5" matches cookbooks >= 2.6.5 AND < 2.7.0 +%% "~> 2.6" matches cookbooks >= 2.6.0 AND < 3.0.0 +%% "~> 2.6.5" matches cookbooks >= 2.6.5 AND < 2.7.0 pes(VsnA, VsnB) -> internal_pes(parse(VsnA), parse(VsnB)). @@ -189,6 +208,19 @@ format_alpha_part([<<".">>, AlphaPart]) -> %%%=================================================================== %%% Internal Functions %%%=================================================================== +-spec to_list(integer() | binary() | string()) -> string(). +to_list(Detail) when erlang:is_integer(Detail) -> + erlang:integer_to_list(Detail); +to_list(Detail) -> + Detail. + +-spec format_vsn_rest(binary() | string(), [integer() | binary()]) -> iolist(). +format_vsn_rest(_TypeMark, []) -> + []; +format_vsn_rest(TypeMark, [Head | Rest]) -> + [TypeMark, Head | + [[".", to_list(Detail)] || Detail <- Rest]]. + %% @doc normalize the semver so they can be compared -spec normalize(semver()) -> semver(). normalize({Vsn, Rest}) @@ -212,9 +244,6 @@ internal_pes(VsnA, {{LM, LMI, LP}, _}) -> internal_pes(Vsn, LVsn) -> gte(Vsn, LVsn). - - - %%%=================================================================== %%% Test Functions %%%=================================================================== @@ -242,7 +271,6 @@ eql_test() -> ?assertMatch(true, not eql("1.0.0+build.1", "1.0.1+build.2")). - gt_test() -> ?assertMatch(true, gt("1.0.0-alpha.1", "1.0.0-alpha")), @@ -523,4 +551,19 @@ pes_test() -> ?assertMatch(true, not pes("2.7", "2.6.5")), ?assertMatch(true, not pes("2.5", "2.6.5")). +version_format_test() -> + ?assertEqual(["1", [], []], format({1, {[],[]}})), + ?assertEqual(["1", ".", "2", ".", "34", [], []], format({{1,2,34},{[],[]}})), + ?assertEqual(<<"1">>, erlang:iolist_to_binary(format({1, {[],[]}}))), + ?assertEqual(<<"1.2">>, erlang:iolist_to_binary(format({{1,2}, {[],[]}}))), + ?assertEqual(<<"1.2.2">>, erlang:iolist_to_binary(format({{1,2,2}, {[],[]}}))), + ?assertEqual(<<"1.99.2">>, erlang:iolist_to_binary(format({{1,99,2}, {[],[]}}))), + ?assertEqual(<<"1.99.2-alpha">>, erlang:iolist_to_binary(format({{1,99,2}, {["alpha"],[]}}))), + ?assertEqual(<<"1.99.2-alpha.1">>, erlang:iolist_to_binary(format({{1,99,2}, {["alpha",1], []}}))), + ?assertEqual(<<"1.99.2+build.1.a36">>, + erlang:iolist_to_binary(format({{1,99,2}, {[], ["build", 1, "a36"]}}))), + ?assertEqual(<<"1.99.2-alpha.1+build.1.a36">>, + erlang:iolist_to_binary(format({{1,99,2}, {["alpha", 1], ["build", 1, "a36"]}}))), + ?assertEqual(<<"1">>, erlang:iolist_to_binary(format({1, {[],[]}}))). + -endif.