aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/edit_transform.erl
blob: 52bd29460bb971df7ab0a3b95a133c6126171974 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
%%%----------------------------------------------------------------------
%%% File    : edit_transform.erl
%%% Author  : Luke Gorrie <luke@bluetail.com>
%%% Purpose : Parse transform module for editor command modules
%%% Created : 25 Oct 2000 by Luke Gorrie <luke@bluetail.com>
%%%----------------------------------------------------------------------

% -command({cmd1, [{name1, prompt1}], doc1}).
% -command({cmd2, []}).

-module(edit_transform).
-author('luke@bluetail.com').

-export([parse_transform/2]).

%% Collect "command" attributes to generate a command_info function. These
%% like (interactive ...) declarations in ELisp. See edit_lib for examples.
%%
%% command grammar:
%% -command({Fun, [Arg]}) | -command({Fun, [Arg], DocString})
%% Arg = {TypeAtom, PromptString}
%%
%% command_info() returns [{Fun, [Arg], DocString}]
%%
parse_transform(Form, Opts) ->
    {Head, Rest} = split(Form),
    {Body, EOF} = splitlast(Rest),
    Line = element(2, hd(Body)),
    {Cmds, NewBody} = separate(Body),
    ListSrc = lists:flatten(io_lib:format("~p", [Cmds])),
    Export = scan_and_parse("-export([command_info/0]).", Line),
    Fun = scan_and_parse("command_info() -> " ++ ListSrc ++ ".", Line),
    NewForm = Head ++ [Export] ++ NewBody ++ [Fun, EOF],
    NewForm.

%% => {Commands, FilteredBody}
%%    Commands = {Name, Params, DocString}
separate(Body) ->
    separate(Body, [], []).

separate([{attribute, _Line, command, {Name, Params, Doc}}|T], Acc1, Acc2) ->
    separate(T, [{Name, Params, Doc}|Acc1], Acc2);
separate([{attribute, _Line, command, {Name, Params}}|T], Acc1, Acc2) ->
    separate(T, [{Name, Params, ""}|Acc1], Acc2);
separate([H|T], Acc1, Acc2) ->
    separate(T, Acc1, [H|Acc2]);
separate([], Acc1, Acc2) ->
    {lists:reverse(Acc1), lists:reverse(Acc2)}.

scan_and_parse(Source, Line) ->
    {ok, FunTokens, _} = erl_scan:string(Source, Line),
    {ok, FunParse} = erl_parse:parse_form(FunTokens),
    FunParse.

%% Split into {Head, Body}. Head is everything upto and including the
%% 'module' attribute, Body is the rest.
split(Form) ->
    split(Form, []).
split([X = {attribute, Line, module, Module}|T], Acc) ->
    {lists:reverse([X|Acc]), T};
split([H|T], Acc) ->
    split(T, [H|Acc]).

splitlast(L) ->
    splitlast(L, []).
splitlast([H], Acc)   -> {lists:reverse(Acc), H};
splitlast([H|T], Acc) -> splitlast(T, [H|Acc]).