aboutsummaryrefslogtreecommitdiffhomepage
path: root/mods/src/em_erlang.erl
diff options
context:
space:
mode:
Diffstat (limited to 'mods/src/em_erlang.erl')
-rw-r--r--mods/src/em_erlang.erl501
1 files changed, 501 insertions, 0 deletions
diff --git a/mods/src/em_erlang.erl b/mods/src/em_erlang.erl
new file mode 100644
index 0000000..dd13e9d
--- /dev/null
+++ b/mods/src/em_erlang.erl
@@ -0,0 +1,501 @@
+%%%----------------------------------------------------------------------
+%%% File : em_erlang.erl
+%%% Author : Luke Gorrie <luke@bluetail.com>
+%%% Purpose : Erlang "Major Mode"
+%%% Created : 10 Mar 2001 by Luke Gorrie <luke@bluetail.com>
+%%%----------------------------------------------------------------------
+
+-module(em_erlang).
+-author('luke@bluetail.com').
+
+-include_lib("ermacs/include/edit.hrl").
+-import(edit_lib, [buffer/1]).
+-export([mod_init/0, erlang_mode/1, reindent_cmd/1]).
+
+-define(keymap, erlang_mode_map).
+
+%% Called by the editor when this module is load/require'd
+%%
+%% NB: may be called several times due to explicit "load" calls.
+mod_init() ->
+ catch edit_keymap:delete(?keymap),
+ init_map(),
+ edit_keymap:global_set_key("C-x e", {?MODULE, erlang_mode, []}),
+ edit_var:add_to_list(auto_mode_alist,
+ {"\.erl$$", {em_erlang, erlang_mode}}),
+ ok.
+
+erlang_mode(State) ->
+ Mode = #mode{name="Erlang",
+ id=erlang,
+ keymaps=[?keymap]},
+ Buf = buffer(State),
+ edit_buf:set_mode(Buf, Mode),
+ State.
+
+init_map() ->
+ edit_keymap:new(?keymap),
+ edit_keymap:bind_each(?keymap, bindings()).
+
+bindings() ->
+ [{"C-i", {?MODULE, reindent_cmd, []}}
+ ].
+
+reindent_cmd(State) ->
+ case scan_fun(State) of
+ no_previous_line ->
+ State;
+ Scan ->
+ B = buffer(State),
+ Indent = max(0,
+ idt(Scan)), %% + indent_cur_line_adjust(B)),
+ reindent(B, edit_lib:beginning_of_line_pos(B), Indent)
+ end.
+
+indent_cur_line_adjust(B) ->
+ case cur_line_first_token(B) of
+ nomatch ->
+ 0;
+ {match, Tok} ->
+ adjust_value(Tok)
+ end.
+
+adjust_value('when') -> 2;
+adjust_value(')') -> -1;
+adjust_value('}') -> -1;
+adjust_value(']') -> -1;
+adjust_value('>>') -> -2;
+adjust_value(_) -> 0.
+
+strip_scan(X) -> lists:flatten(strip_scan1(X)).
+
+strip_scan1([]) -> [];
+strip_scan1([{lit,_}|T]) -> ["lit "|strip_scan1(T)];
+strip_scan1([{X,Y}|T]) -> [atom_to_list(X) ++ " "|strip_scan1(T)].
+
+reindent(Buf, Pos, Lvl) ->
+ Pred = fun(C) -> (C /= $ ) and (C /= $\t) end,
+ End = max(Pos, edit_lib:find_char_forward(Buf, Pred, Pos, 1)),
+ edit_buf:replace(Buf, lists:duplicate(Lvl, $ ), Pos, End).
+
+max(X, Y) when X > Y -> X;
+max(X, Y) -> Y.
+
+min(X, Y) when X < Y -> X;
+min(X, Y) -> Y.
+
+
+beginning_of_function(State) ->
+ B = buffer(State),
+ Pos = beginning_of_function_pos(B),
+ edit_buf:move_mark(B, point, Pos).
+
+beginning_of_function_pos(B) ->
+ Point = min(edit_buf:point_max(B) - 1,
+ max(1, edit_lib:beginning_of_line_pos(B) - 1)),
+ Cord = edit_buf:get_cord(B),
+ case cord_regexp:first_match("^[a-z'$-?]", Cord, Point, backward) of
+ nomatch ->
+ 1;
+ {match, Start, End} ->
+ Start
+ end.
+
+%%% ----------------------------------------------------------------------
+%%% Indentation
+%%% ----------------------------------------------------------------------
+
+%% Calculate the indentation level for the next line of a
+%% function. `Scan' is the result of tokenising from the start of the
+%% function.
+idt(Scan) ->
+ idt(Scan, [], [], 0).
+
+%% Stack item
+-record(si,
+ {id, % icr | '->' | open
+ tok, % token
+ col, % column number of `tok'
+ old_indent % indent level when `tok' was found
+ }).
+
+%% icr means "if/case/receive(/begin/fun)" - really anything that's
+%% matched by an 'end'
+
+%% Variable names: C = Column, O = OldToken, S = Stack, I = IndentLevel
+
+%% if case receive begin
+idt([{Icr, C} | T], O, S, I)
+ when Icr == 'if'; Icr == 'case'; Icr == 'receive'; Icr == 'begin'->
+ CI = calc_clause_indent({Icr, C}, T, C),
+ idt(T, Icr, push_icr(Icr, C, I, S), CI);
+
+%% fun
+idt([{'fun', C}, {'(', OpenC} | T], O, S, I) ->
+ Rest = [{'(', OpenC} | T],
+ CI = calc_clause_indent({'fun', C}, Rest, C),
+ idt(Rest, 'fun', push_icr('fun', C, I, S), CI);
+
+%% end
+idt([{'end', C} | T], O, S, I) ->
+ case unwind_icr(S) of
+ nomatch ->
+ idt(T, 'end', [], 0);
+ {SI, S2} ->
+ idt(T, 'end', S2, SI#si.old_indent)
+ end;
+
+%% ( [ { <<
+idt([{Open, C} | T], O, S, I)
+ when Open == '('; Open == '['; Open == '{'; Open == '<<' ->
+ Width = length(atom_to_list(Open)),
+ idt(T, Open, push_open(Open, C, I, S), C+Width);
+
+%% ) ] } >>
+idt([{Close, C} | T], O, S, I)
+ when Close == ')'; Close == ']'; Close == '}'; Close == '>>' ->
+ case unwind_close(matching(Close), S) of
+ nomatch ->
+ idt(T, Close, [], 0);
+ {SI, S2} ->
+ idt(T, Close, S2, SI#si.old_indent)
+ end;
+
+%% ->
+idt([{'->', C} | T], O, S, I) ->
+ NewIndent = case unwind_icr(S) of
+ nomatch ->
+ 4;
+ {ICR_SI, ICR_S2} ->
+ ICR_SI#si.col + 8
+ end,
+ case unwind_when(S) of
+ {SI, S2} ->
+ idt(T, '->', push_arrow(C, SI#si.old_indent, S2), NewIndent);
+ nomatch ->
+ idt(T, '->', push_arrow(C, I, S), NewIndent)
+ end;
+
+%% when
+idt([{'when', C}, {Next, NextC} | T], Old, S, I) when Next /= '->' ->
+ idt(T, 'when', push_when(C, I, S), NextC);
+
+
+%% ;
+idt([{';', C} | T], O, S = [SI = #si{id='when'}|_], I) ->
+ idt(T, ';', S, I);
+
+idt([{',', C} | T], O, S = [SI = #si{id='when'}|_], I) ->
+ idt(T, ';', S, I);
+
+idt([{';', C} | T], O, S, I) ->
+ case unwind_arrow(S) of
+ nomatch ->
+ idt(T, ';', [], 0);
+ {SI, S2} ->
+ idt(T, ';', S2, SI#si.old_indent)
+ end;
+
+idt([{'when', C} | T], '\n', S, I) ->
+ idt(T, 'when', S, I+2);
+
+%% Period: end of function
+idt([{'.', C} | T], O, S, I) ->
+ idt(T, start, [], 0);
+
+%% Skip blank lines
+idt([{'\n', C}, {'\n', _} | T], O, S, I) ->
+ idt([{'\n', C} | T], O, S, I);
+
+idt([{'\n', C} | T], O, S = [#si{id=open}|_], I) ->
+ idt(T, '\n', S, I);
+
+%% End of line. The preceeding token indicates whether we need to be unwinding
+idt([{'\n', C} | T], Old, S, I)
+%%when member(Old, ?ICR_TOKENS) ->
+ when Old == '.'; Old == ';'; Old == 'of'; Old == 'begin';
+ Old == 'receive'; Old == 'if'; Old == '->'; Old == ',';
+ Old == '|' ->
+ idt(T, '\n', S, I);
+
+idt([{'\n', C} | T], O, S = [#si{id=open, old_indent=OldIndent}|_], I) ->
+ idt(T, '\n', S, OldIndent);
+
+idt([{'\n', C} | T], O, S, I) ->
+ case unwind_icr(S) of
+ nomatch ->
+ idt(T, '\n', [], 0);
+ {SI, _S2} ->
+ idt(T, '\n', S, SI#si.col)
+ end;
+
+idt([{'||', C} | T], O, S, I) ->
+ idt(T, '||', S, C + 3);
+
+%% Quotes
+
+idt([{Quote, C} | T], O, S, I) when Quote == '\'';
+ Quote == '"' ->
+ match_quote(Quote, T, S, I);
+
+%% Boring stuff
+idt([{Tok,_}|T], O, S, I) ->
+ idt(T, Tok, S, I);
+
+idt([], O, S, I) ->
+ I.
+
+push_icr(Atom, Col, Indent, Stack) ->
+ [#si{id=icr, tok=Atom, col=Col, old_indent=Indent} | Stack].
+
+push_open(Atom, Col, Indent, Stack) ->
+ [#si{id=open, tok=Atom, col=Col, old_indent=Indent} | Stack].
+
+push_arrow(Col, Indent, Stack) ->
+ [#si{id=arrow, tok='->', col=Col, old_indent=Indent} | Stack].
+
+push_when(Col, Indent, Stack) ->
+ [#si{id='when', tok='when', col=Col, old_indent=Indent} | Stack].
+
+%% Unwind the stack to find a match for a closing bracket. For a
+%% syntactically correct function, the head of the stack should always
+%% match, but otherwise I just keep unwinding to be "forgiving" -
+%% i.e. just give the wrong answer instead of an error :-)
+
+unwind_close(Tok, [SI = #si{id=open, tok=Tok} | T]) ->
+ {SI, T};
+unwind_close(Tok, [H|T]) ->
+ unwind_close(Tok, T);
+unwind_close(Tok, []) ->
+ nomatch.
+
+unwind_icr([SI = #si{id=icr} | T]) ->
+ {SI, T};
+unwind_icr([H|T]) ->
+ unwind_icr(T);
+unwind_icr([]) ->
+ nomatch.
+
+%% Returns: {SI, ICR_SI, Stack}
+unwind_arrow([SI = #si{id=arrow} | T]) ->
+ {SI, T};
+unwind_arrow([H|T]) ->
+ unwind_arrow(T);
+unwind_arrow([]) ->
+ nomatch.
+
+unwind_when([SI = #si{id='when'}|T]) ->
+ {SI, T};
+unwind_when([H|T]) ->
+ unwind_when(T);
+unwind_when([]) ->
+ nomatch.
+
+match_quote(Quote, [{'\\', _}, _ | T], S, I) ->
+ %% Something is being escaped, hop over it
+ match_quote(Quote, T, S, I);
+match_quote(Quote, [Tok = {Quote, _} | T], S, I) ->
+ idt(T, Quote, S, I);
+match_quote(Quote, [Tok|T], S, I) ->
+ match_quote(Quote, T, S, I);
+match_quote(Quote, [], S, I) ->
+ %% Out of tokens while inside a quote
+ 0.
+
+calc_clause_indent({IRF, X}, [{Next, Y}|T], I)
+ when IRF == 'if'; IRF == 'receive'; IRF == 'fun' ->
+ if
+ Next == '\n' ->
+ I + 4;
+ true ->
+ Y
+ end;
+calc_clause_indent(_, _, I) ->
+ I + 4.
+
+matching(')') -> '(';
+matching(']') -> '[';
+matching('}') -> '{';
+matching('>>') -> '<<'.
+
+%%% ----------------------------------------------------------------------
+%%% Scanner
+%%% ----------------------------------------------------------------------
+
+% make_erlang_scanner() ->
+% em_scan:make_scanner(em_erlang_scan:yystate(),
+% {em_erlang_scan, yystate},
+% {em_erlang_scan, yyaction}).
+
+% scan_fun(State) ->
+% B = buffer(State),
+% RStart = beginning_of_function_pos(B),
+% REnd = min(edit_lib:beginning_of_line_pos(B),
+% edit_buf:point_max(B) - 1),
+% Region = if
+% REnd > RStart ->
+% edit_buf:get_region(B, RStart, REnd);
+% true ->
+% ""
+% end,
+% scan(cord:new(Region)).
+
+% scan(Cord) ->
+% Scanner = make_erlang_scanner(),
+% Walker = cord:walker(Cord),
+% case em_scan:edit_scan(Scanner, Walker) of
+% {error, Rsn} ->
+% {error, Rsn};
+% {ok, Toks} ->
+% %% This module expects {Class, Column}
+% [{Type, Col} || {Type, Col, Line} <- Toks]
+% end.
+
+%%%%%%% OLD (current) SCANNER
+
+%% Token = {Class, Column}
+
+-define(MAX_TOKEN_LENGTH, 64).
+
+first_token(Str) ->
+ case scan(Str) of
+ [] ->
+ nomatch;
+ [{Tok,_}|_] ->
+ {match, Tok}
+ end.
+
+%% Returns: nomatch | {match, Token}
+cur_line_first_token(Buf) ->
+ Max = edit_buf:point_max(Buf),
+ Start = edit_lib:beginning_of_line_pos(Buf),
+ End = min(Max - 1,
+ min(Start + ?MAX_TOKEN_LENGTH,
+ edit_lib:end_of_line_pos(Buf))),
+ if
+ End > Start ->
+ Str = edit_buf:get_region(Buf, Start, End),
+ first_token(Str);
+ true ->
+ nomatch
+ end.
+
+scan_fun(State) ->
+ B = buffer(State),
+ RStart = beginning_of_function_pos(B),
+ REnd = min(edit_lib:beginning_of_line_pos(B),
+ edit_buf:point_max(B) - 1),
+ Region = if
+ REnd > RStart ->
+ edit_buf:get_region(B, RStart, REnd);
+ true ->
+ ""
+ end,
+ scan(Region).
+
+strip(X) -> [element(1, E) || E <- X].
+
+scan(Str) ->
+ S = merge_literals(scan1(Str, true, 0)),
+ S.
+
+merge_literals([]) -> [];
+merge_literals([{lit,P},{lit,_}|T]) -> merge_literals([{lit,P}|T]);
+merge_literals([H|T]) -> [H|merge_literals(T)].
+
+scan1([], _, P) ->
+ [];
+scan1([$%|T], _, P) ->
+ skip_comments(T, P);
+scan1([$$,_|T], _, P) ->
+ [{lit,P}|scan1(T, true, P+2)];
+scan1([$.,X|T], _, P) ->
+ case lists:member(X, ws()) of
+ true ->
+ [{'.',P} | scan1([X|T], true, P)];
+ false ->
+ [{lit,P}|scan1([X|T], true, P)]
+ end;
+scan1(Str, Fresh, P) ->
+ case match(Str, Fresh) of
+ nomatch when hd(Str) /= $ , hd(Str) /= $\t ->
+ [{lit,P}|scan1(tl(Str), not symchar(hd(Str)), P+1)];
+ nomatch ->
+ scan1(tl(Str), not symchar(hd(Str)), P+1);
+ {match, "\n"} ->
+ [{'\n',P} |
+ scan1(tl(Str), true, 0)];
+ {match, M} ->
+ Len = length(M),
+ Rest = lists:nthtail(Len, Str),
+ [{list_to_atom(M), P} |
+ scan1(Rest, true, P+Len)]
+ end.
+
+match(Str, true) ->
+ case match_alone(Str) of
+ nomatch ->
+ match(Str, false);
+ X ->
+ X
+ end;
+match(Str, false) ->
+ match_anywhere(Str).
+
+match_alone(Str) ->
+ match_alone(Str, alone()).
+
+match_alone(Str, [Str|_]) ->
+ {match, Str};
+match_alone(Str, [X|T]) ->
+ case lists:prefix(X, Str) of
+ true ->
+ case not symchar(lists:nth(length(X)+1, Str)) of
+ true ->
+ {match, X};
+ false ->
+ match_alone(Str, T)
+ end;
+ false ->
+ match_alone(Str, T)
+ end;
+match_alone(Str, [_|T]) ->
+ match_alone(Str, T);
+match_alone(Str, []) ->
+ nomatch.
+
+match_anywhere(Str) ->
+ match_anywhere(Str, anywhere()).
+
+match_anywhere(Str, [X|T]) ->
+ case lists:prefix(X, Str) of
+ true -> {match, X};
+ false -> match_anywhere(Str, T)
+ end;
+match_anywhere(Str, []) ->
+ nomatch.
+
+skip_comments([$\n|T], P) ->
+ scan1(T, true, P);
+skip_comments([_|T], P) ->
+ skip_comments(T, 0);
+skip_comments([], P) ->
+ [].
+
+%% Tokens that can appear anywhere
+anywhere() ->
+ ["{","}","<<",">>","(",")","[","]",";",".","->","||","|",";",",","\n",
+ "\"", "'", "\\"].
+
+%% Tokens that must appear "alone"
+alone() ->
+ ["begin", "end", "case", "if", "receive", "fun", "of", "when"].
+
+ws() -> "\r\t\n ".
+
+symchar(C) when C >= $a, C =< $z -> true;
+symchar(C) when C >= $A, C =< $Z -> true;
+symchar(C) when C >= $0, C =< $9 -> true;
+symchar($_) -> true;
+symchar(_) -> false.
+