diff options
author | lukeg <lukeg> | 2003-02-21 19:01:14 +0000 |
---|---|---|
committer | lukeg <lukeg> | 2003-02-21 19:01:14 +0000 |
commit | e7d48fe500f6ed676ee1b212ebd61408bced1c5b (patch) | |
tree | 11a756c7bb4906f3e186c1cb8331cb7ed27bc69c | |
download | ermacs-fork-e7d48fe500f6ed676ee1b212ebd61408bced1c5b.tar.gz |
*** empty log message ***
53 files changed, 8223 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..1985f9c --- /dev/null +++ b/ChangeLog @@ -0,0 +1,103 @@ +2002-02-03 Luke Gorrie <luke@bluetail.com> + + * src/edit_help.erl: Added describe_key and find_source. + + Using M-h for help prefix. find_source is fun - it will let you + enter a key and then take you to the code that runs when that key + is pressed. Use it when a key doesn't do its job properly :-) + +2001-04-09 Luke Gorrie <luke@bluetail.com> + + * RELEASE: 0.6 + + * src/edit_lib.erl: Added search (not isearch yet) functions. + +2001-04-07 Luke Gorrie <luke@bluetail.com> + + * Completion: added filename completion. + + * Goal_Column: Made goal column tab-friendly. + +2001-03-26 Luke Gorrie <luke@bluetail.com> + + * src/edit_eval.erl: Group leader implemented, so that io:format's + get written into the buffer. + +2001-03-22 Luke Gorrie <luke@bluetail.com> + + * src/edit_util.erl: Changed bit-twiddly interpretation of Control + after talking with Per. + + * src/edit_lib.erl: Added 'unix_command', i.e. "M-!" + +2001-03-20 Luke Gorrie <luke@bluetail.com> + + * src/edit_globalmap.erl: Bindings for arrow keys. + +2001-03-19 Luke Gorrie <luke@bluetail.com> + + * src/edit_erlang.erl: Rewrite of indentation code. Emacs and + Ermacs both agree on how edit_erlang.erl should be + indented. Great! + + * Flushing_exits: Main edit process now flushes the exit messages + from its dispatcher processes. Growing the message queue made the + editor slowly bog down. + +2001-03-18 Luke Gorrie <luke@bluetail.com> + + * edit_display.erl: Performance hacking. + +2001-03-14 Luke Gorrie <luke@bluetail.com> + + * GTK: Now possible to use gterm as the terminal emulator - very + preliminary. + + * In_Progress: Regexp searches over cords, erlang syntax + indentation. Both unfinished. + +2001-03-09 Luke Gorrie <luke@bluetail.com> + + * M-x: Added "M-x" which takes Mod:Func, e.g. "M-x + edit_file:find_file" + + * ~/.ermacs: Now doing a file:eval/1 on ~/.ermacs during + initialization. + +2001-03-07 Luke Gorrie <luke@bluetail.com> + + * Cords: Changed balancing algorithm to do this + single-or-double-rotate as seen in a paper. Also added + new_from_file(Filename) as a slightly optimised way of reading + cords from files. + + Killring now uses cords instead of lists. Turning huge cords into + lists on the kill ring turns out to be the cause of my previous + problems with editing big files. + + Meg-or-so files seem just fine now. + + * Borrowed_buffers: Buffers can now be "borrowed" by custom + processes and used asynchronously. Only editor-dispatched + processes are supposed to do locking, as it's assumed that they + implicitly have everything that's not borrowed. + + edit_lib:buffer(State) now blocks waiting for the buffer to be + returned if someone has borrowed it. + + See edit_util:spawn_with/x for spawning asynchronous jobs with + borrowed buffers. + + * Aborts: New semantics for C-g: If the editor is dispatching a + command, that command is aborted. Otherwise, C-g is treated as a + normal key. + + The new default binding in edit_globalmap for C-g will abort any + process that is 'borrowing' the current buffer. This is in line + with emacs panic semantics: hold down C-g and everything that's + freezing up your buffer will be brutally killed :-) + + * edit_eval.erl: erlang-interaction-mode now evaluates expressions + asynchronously, by "borrowing" the buffer. + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9b85f2c --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +SUBDIRS = src mods/src + +include ../../support/subdir.mk + +conf: # nothing + @@ -0,0 +1,13 @@ + +Ermacs: an erlang clone of emacs. +================================= + +To run: + +1. make +2. bin/ermacs (add that dir to your path, or copy the script somewhere) + +Try opening doc/TOUR - it's an approximation of the emacs tutorial. + +-- luke@bluetail.com + @@ -0,0 +1,40 @@ +Needed for 0.5 (i.e. "semi-public" release): + ++ hard-abort - different process handling so you can abort anything with C-g ++ good erlang shell +- get slang's default colours under control + +Still needed for respectable 1.0: + +- buffer modified/unmodified flag +- handle sigwinch ++ completions +- window config save/restore for popups (e.g. after completion) +- stabilize API - get rid of export_all's +- some sort of "panic" procedure to ensure that crashes don't lose data +- robustify: commands making bad calls to buffers shouldn't crash the editor! ++ M-x ++ be group_leader in Erlang Interaction mode, in some sensible way. +- integrate with normal erlang shell + +For future versions: + +- colours / syntax highlighting ++ erlang programming mode +- documentation browser - wiki-mode instead of texinfo? +- isearch + +Things that it's really bothering me off not to have: + +- fill-paragraph ++ search +- isearch ++ M-x ++ C-x 1 ++ auto-mode-alist ++ tabs: count them properly for column-wise stuff (goal column) ++ find-file opens relative to current buffer ++ completions ++ contextual history in minibuffer +- parenthesis matching +- line numbers diff --git a/bin/.cvsignore b/bin/.cvsignore new file mode 100644 index 0000000..26357ca --- /dev/null +++ b/bin/.cvsignore @@ -0,0 +1 @@ +ermacs diff --git a/doc/DESIGN b/doc/DESIGN new file mode 100644 index 0000000..d33c28c --- /dev/null +++ b/doc/DESIGN @@ -0,0 +1,36 @@ +Notes on how this bastard works +=============================== + +Overview: + +The editor is based on Emacs: same sort of buffers, similar commands, etc. +It's written in Erlang and uses the S-Lang library for terminal control. + +Essential facts: + +- Text is stored as a tree of binaries +- Buffers are small servers and are destructively updated (.. for now?) +- Commands are executed serially, but buffers can be "loaned" to other + processes to work on them asynchronously. + +Hacking: + +To see how commands are implemented, look at the edit_lib module, it contains +all the most basic commands. A rundown of the most important modules: + +* edit: where it all begins, and main editor loop. +* edit_lib: basic command implementations +* edit_eval: erlang shell mode +* cord: text data structure +* edit_buf: buffer server +* edit_globalmap: standard keymap definitions - look there to see what exists. +* edit_display: screen redraw +* edit_extended: "interactive" command runner (shows how to use minibuffer) +* edit_var: emacs-style variables (optionally persistent) + +Programming style: + +I've gone with do-it-yourself servers and message sending in a lot of places. +I'd like to keep it that way, at least for now. It feels good to use +selective receives and whatnot :-) + diff --git a/doc/TOUR b/doc/TOUR new file mode 100644 index 0000000..4f61132 --- /dev/null +++ b/doc/TOUR @@ -0,0 +1,115 @@ +Interactive tour of the editor! + +Motion +------ + +Like emacs. + +Erlang Evaluation +----------------- + +In emacs M-: lets you enter a lisp expression in the minibuffer. In ermacs +it lets you enter an erlang expression. + +Try it, e.g: + + M-: os:type(). RET + +There's also an erlang-interaction-mode. You can enter it by pressing +"C-x i", and get back to Fundamental mode with "C-x f". + +Erlang interaction mode is an erlang shell, and its prompt looks like this: + +>> foo. + +When you press return in Erlang Interaction mode, it evaluates the expression +between the point and the prompt (or start of buffer) and shows the result. + +Try it: now press "C-x i". Now press return at the end of the following line: +>> (fun(X) -> X + 5 end)(10). + +And now press "C-x f" to get back into fundamental mode. + +In erlang-interaction-mode, your history is recorded. You can use: + + M-p: previous input + M-n: next input + M-r: regexp search backwards through input history + +If you want, you can make this history persistent so that it's stored in a +dets file in your home directory. To do that, go into erlang interaction mode +and hit enter at the end of the following line. + +>> edit_var:permanent(erlang_interaction_history, true). + +Asynchronous I/O works too: + +>> spawn(fun() -> receive after 1000 -> io:format("bar~n") end end). + +Erlang Indentation +------------------ + +The editor can indent erlang code. To enter erlang-mode, press "C-x e". Then +you can hit tab to indent these lines: + +one() -> +ok. + +two(X) when X > 0, +X < 10 -> +X. + +three(X) -> +[Y*2 || Y <- make_list(), +Y < 10]. + +four() -> +if X == 1 -> +if Y == 2 -> +ok; +Z == 3 -> +ok +end +end. + +five([]) -> +0; +five(List) -> +Sum = lists:foldl(fun(X, Sum) -> +X + Sum +end, +0, +List), +Sum / length(List). + +six(X) -> +if X /= 1, +X /= 2 -> +X; +true -> +3 +end, +six. + +Windows +------- + +You can split windows. + +Type "C-x 2". Now type "C-x 0". + +Notice that if you scroll one window, the other scrolls too. This is a bug :-) + +Multiple buffers +---------------- + +You can use "C-x C-f" to open a new file, and then "C-x b" back here +as you would in emacs (but without the completions). + +Other stuff +----------- + +To see what else there is, look in the keymap definition: + + C-x C-f ../src/edit_globalmap.erl RET + diff --git a/doc/TROUBLESHOOTING b/doc/TROUBLESHOOTING new file mode 100644 index 0000000..27997ac --- /dev/null +++ b/doc/TROUBLESHOOTING @@ -0,0 +1,11 @@ +Random notes on trouble shooting: + +- io:formats go to /tmp/edit.out, which is re-created from scratch each time. + +- Sometimes colours go strange, particularly (for me) when running in a + shell on a remote machine. When this happens to me in xterm, I retry in + rxvt and it tends to work. However, on *my* computer the colours are ok :-) + + Presumably some extra initialisation of the SLang library is needed. + + diff --git a/include/edit.hrl b/include/edit.hrl new file mode 100644 index 0000000..2d7bcd5 --- /dev/null +++ b/include/edit.hrl @@ -0,0 +1,48 @@ +%% -*- comment-column: 33 -*- + +-ifndef(_EDIT_HRL). +-define(_EDIT_HRL, true). + +-define(debug(F, A), + io:format("[~s:~p] " ++ F, [?MODULE, ?LINE | A]) + ). + +%% To use the GTK terminal, use the following definition. +%% Requires that you have erlgtk and gterm in your path. +%%-define(EDIT_TERMINAL, edit_terminal_gterm). +-define(EDIT_TERMINAL, edit_terminal). + +-record(state, + {curwin, % current window + windows=[], % other windows + buffers=[], % list of buffers, most recently used at head + lastcmd, % {M, F, A} of the last command executed + %% for edit_extended + pending_cmd, + pending_win + }). + +%% Window: an area of the display used for viewing a particular +%% buffer, just like an emacs window. +-record(window, + {buffer, % buffer being viewed + y, % screen row that this window starts at + width, % width in characters + height, % height in characters + start_mark, % mark on start of display (unique to window) + goal_column=0, + active=true, + id, % ref() - unique id + minibuffer=false, + %% fields just for the minibuffer + status_text, + prefix="" + }). + +-record(mode, {name, % string + id, % atom + keymaps}). + +-define(EOL_CHAR, $$). % Character to indicate the line is chopped + +-endif. diff --git a/mods/src/Makefile b/mods/src/Makefile new file mode 100644 index 0000000..8314a10 --- /dev/null +++ b/mods/src/Makefile @@ -0,0 +1,12 @@ +include ../../../../support/include.mk + +# Ermacs includes +ERLC_FLAGS += -I ../../.. -pa ../../ebin + +all: $(ERL_OBJECTS) + +$(ERL_OBJECTS): ../../include/edit.hrl + +clean: + -rm -f $(ERL_OBJECTS) + 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. + diff --git a/mods/src/em_erlang_scan.erl b/mods/src/em_erlang_scan.erl new file mode 100644 index 0000000..46ddba3 --- /dev/null +++ b/mods/src/em_erlang_scan.erl @@ -0,0 +1,780 @@ +%% THIS IS A PRE-RELEASE OF LEEX - RELEASED ONLY BECAUSE MANY PEOPLE +%% WANTED IT - THE OFFICIAL RELEASE WILL PROVIDE A DIFFERENT INCOMPATIBLE +%% AND BETTER INTERFACE - BE WARNED +%% PLEASE REPORT ALL BUGS TO THE AUTHOR. + +-module('em_erlang_scan'). + +-export([string/1,string/2,token/2,token/3,tokens/2,tokens/3]). +-export([format_error/1]). + +%% luke +-export([yystate/0, yystate/6, yyaction/4]). + +%% User code. This is placed here to allow extra attributes. + +-author('rv@cslab.ericsson.se'). +-copyright('Copyright (c) 1996 Ericsson Telecommunications AB'). + +-export([reserved_word/1]). + +reserved_word('after') -> true; +reserved_word('begin') -> true; +reserved_word('case') -> true; +reserved_word('catch') -> true; +reserved_word('end') -> true; +reserved_word('fun') -> true; +reserved_word('if') -> true; +reserved_word('let') -> true; +reserved_word('of') -> true; +reserved_word('query') -> true; +reserved_word('receive') -> true; +reserved_word('when') -> true; +reserved_word('bnot') -> true; +reserved_word('not') -> true; +reserved_word('div') -> true; +reserved_word('rem') -> true; +reserved_word('band') -> true; +reserved_word('and') -> true; +reserved_word('bor') -> true; +reserved_word('bxor') -> true; +reserved_word('bsl') -> true; +reserved_word('bsr') -> true; +reserved_word('or') -> true; +reserved_word('xor') -> true; +reserved_word(_) -> false. + +base(L, Cs) -> + H = string:chr(Cs, $#), + case list_to_integer(string:substr(Cs, 1, H-1)) of + B when B > 16 -> {error,"illegal base"}; + B -> + case base(string:substr(Cs, H+1), B, 0) of + error -> {error,"illegal based number"}; + N -> {token,{integer,L,N}} + end + end. + +base([C|Cs], Base, SoFar) when C >= $0, C =< $9, C < Base + $0 -> + Next = SoFar * Base + (C - $0), + base(Cs, Base, Next); +base([C|Cs], Base, SoFar) when C >= $a, C =< $f, C < Base + $a - 10 -> + Next = SoFar * Base + (C - $a + 10), + base(Cs, Base, Next); +base([C|Cs], Base, SoFar) when C >= $A, C =< $F, C < Base + $A - 10 -> + Next = SoFar * Base + (C - $A + 10), + base(Cs, Base, Next); +base([C|Cs], Base, SoFar) -> error; +base([], Base, N) -> N. + +cc_convert([$$,$\\|Cs]) -> + hd(string_escape(Cs)); +cc_convert([$$,C]) -> C. + +string_gen([$\\|Cs]) -> + string_escape(Cs); +string_gen([C|Cs]) -> + [C|string_gen(Cs)]; +string_gen([]) -> []. + +string_escape([O1,O2,O3|S]) when + O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 -> + [(O1*8 + O2)*8 + O3 - 73*$0|string_gen(S)]; +string_escape([$^,C|Cs]) -> + [C band 31|string_gen(Cs)]; +string_escape([C|Cs]) when C >= 0, C =< $ -> + string_gen(Cs); +string_escape([C|Cs]) -> + [escape_char(C)|string_gen(Cs)]. + +escape_char($n) -> $\n; %\n = LF +escape_char($r) -> $\r; %\r = CR +escape_char($t) -> $\t; %\t = TAB +escape_char($v) -> $\v; %\v = VT +escape_char($b) -> $\b; %\b = BS +escape_char($f) -> $\f; %\f = FF +escape_char($e) -> $\e; %\e = ESC +escape_char($s) -> $ ; %\s = SPC +escape_char($d) -> $\d; %\d = DEL +escape_char(C) -> C. + + +format_error({illegal,S}) -> ["illegal characters ",io_lib:write_string(S)]; +format_error({user,S}) -> S. + +string(String) -> string(String, 1). + +string(String, Line) -> string(String, Line, String, []). + +%% string(InChars, Line, TokenChars, Tokens) -> +%% {ok,Tokens,Line} | {error,ErrorInfo,Line}. + +string([], L, [], Ts) -> %No partial tokens! + {ok,yyrev(Ts),L}; +string(Ics0, L0, Tcs, Ts) -> + case yystate(yystate(), Ics0, L0, 0, reject, 0) of + {A,Alen,Ics1,L1} -> %Accepting end state + string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L1), Ts); + {A,Alen,Ics1,L1,S1} -> %After an accepting state + string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L1), Ts); + {reject,Alen,Tlen,Ics1,L1,S1} -> + {error,{L1,?MODULE,{illegal,yypre(Tcs, Tlen+1)}},L1}; + {A,Alen,Tlen,Ics1,L1,S1} -> + string_cont(yysuf(Tcs, Alen), L1, yyaction(A, Alen, Tcs, L1), Ts) + end. + +%% string_cont(RestChars, Line, Token, Tokens) +%% Test for and remove the end token wrapper. + +string_cont(Rest, Line, {token,T}, Ts) -> + string(Rest, Line, Rest, [T|Ts]); +string_cont(Rest, Line, {end_token,T}, Ts) -> + string(Rest, Line, Rest, [T|Ts]); +string_cont(Rest, Line, skip_token, Ts) -> + string(Rest, Line, Rest, Ts); +string_cont(Rest, Line, {error,S}, Ts) -> + {error,{Line,?MODULE,{user,S}},Line}. + +%% token(Continuation, Chars, Line) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. +%% Must be careful when re-entering to append the latest characters to the +%% after characters in an accept. + +token(Cont, Chars) -> token(Cont, Chars, 1). + +token([], Chars, Line) -> + token(Chars, Line, yystate(), Chars, 0, reject, 0); +token({Line,State,Tcs,Tlen,Action,Alen}, Chars, _) -> + token(Chars, Line, State, Tcs ++ Chars, Tlen, Action, Alen). + +%% token(InChars, Line, State, TokenChars, TokenLen, Accept) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +token(Ics0, L0, S0, Tcs, Tlen0, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + {A1,Alen1,Ics1,L1} -> %Accepting end state + token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1)); + {A1,Alen1,[],L1,S1} -> %After an accepting state + {more,{L1,S1,Tcs,Alen1,A1,Alen1}}; + {A1,Alen1,Ics1,L1,S1} -> + token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1)); + {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state + {more,{L1,S1,Tcs,Tlen1,A1,Alen1}}; + {reject,Alen1,Tlen1,eof,L1,S1} -> + {done,{eof,L1},[]}; + {reject,Alen1,Tlen1,Ics1,L1,S1} -> + {done,{error,{L1,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}},L1},Ics1}; + {A1,Alen1,Tlen1,Ics1,L1,S1} -> + token_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1)) + end. + +%% tokens_cont(RestChars, Line, Token) +%% Test if we have detected the end token, if so return done else continue. + +token_cont(Rest, Line, {token,T}) -> + {done,{ok,T,Line},Rest}; +token_cont(Rest, Line, {end_token,T}) -> + {done,{ok,T,Line},Rest}; +token_cont(Rest, Line, skip_token) -> + token(Rest, Line, yystate(), Rest, 0, reject, 0); +token_cont(Rest, Line, {error,S}) -> + {done,{error,{Line,?MODULE,{user,S}},Line},Rest}. + +%% tokens(Continuation, Chars, Line) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. +%% Must be careful when re-entering to append the latest characters to the +%% after characters in an accept. + +tokens(Cont, Chars) -> tokens(Cont, Chars, 1). + +tokens([], Chars, Line) -> + tokens(Chars, Line, yystate(), Chars, 0, [], reject, 0); +tokens({tokens,Line,State,Tcs,Tlen,Ts,Action,Alen}, Chars, _) -> + tokens(Chars, Line, State, Tcs ++ Chars, Tlen, Ts, Action, Alen); +tokens({skip_tokens,Line,State,Tcs,Tlen,Error,Action,Alen}, Chars, _) -> + skip_tokens(Chars, Line, State, Tcs ++ Chars, Tlen, Error, Action, Alen). + +%% tokens(InChars, Line, State, TokenChars, TokenLen, Tokens, Accept) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +tokens(Ics0, L0, S0, Tcs, Tlen0, Ts, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + {A1,Alen1,Ics1,L1} -> %Accepting end state + tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Ts); + {A1,Alen1,[],L1,S1} -> %After an accepting state + {more,{tokens,L1,S1,Tcs,Alen1,Ts,A1,Alen1}}; + {A1,Alen1,Ics1,L1,S1} -> + tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Ts); + {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state + {more,{tokens,L1,S1,Tcs,Tlen1,Ts,A1,Alen1}}; + {reject,Alen1,Tlen1,eof,L1,S1} -> + {done,if Ts == [] -> {eof,L1}; + true -> {ok,yyrev(Ts),L1} end,[]}; + {reject,Alen1,Tlen1,Ics1,L1,S1} -> + skip_tokens(yysuf(Tcs, Tlen1+1), L1, + {L1,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}}); + {A1,Alen1,Tlen1,Ics1,L1,S1} -> + tokens_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1), Ts) + end. + +%% tokens_cont(RestChars, Line, Token, Tokens) +%% Test if we have detected the end token, if so return done else continue. + +tokens_cont(Rest, Line, {token,T}, Ts) -> + tokens(Rest, Line, yystate(), Rest, 0, [T|Ts], reject, 0); +tokens_cont(Rest, Line, {end_token,T}, Ts) -> + {done,{ok,yyrev(Ts, [T]),Line},Rest}; +tokens_cont(Rest, Line, skip_token, Ts) -> + tokens(Rest, Line, yystate(), Rest, 0, Ts, reject, 0); +tokens_cont(Rest, Line, {error,S}, Ts) -> + skip_tokens(Rest, Line, {Line,?MODULE,{user,S}}). + +%% token_skip(InChars, Line, Error) -> {done,ReturnVal,RestChars}. +%% Skip tokens until an end token, junk everything and return the error. + +%%skip_tokens(Ics, Line, Error) -> {done,{error,Error,Line},Ics}. + +skip_tokens(Ics, Line, Error) -> + skip_tokens(Ics, Line, yystate(), Ics, 0, Error, reject, 0). + +%% skip_tokens(InChars, Line, State, TokenChars, TokenLen, Tokens, Accept) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +skip_tokens(Ics0, L0, S0, Tcs, Tlen0, Error, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + {A1,Alen1,Ics1,L1} -> %Accepting end state + skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Error); + {A1,Alen1,[],L1,S1} -> %After an accepting state + {more,{skip_tokens,L1,S1,Tcs,Alen1,Error,A1,Alen1}}; + {A1,Alen1,Ics1,L1,S1} -> + skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Error); + {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state + {more,{skip_tokens,L1,S1,Tcs,Tlen1,Error,A1,Alen1}}; + {reject,Alen1,Tlen1,eof,L1,S1} -> + {done,{error,Error,L1},[]}; + {reject,Alen1,Tlen1,Ics1,L1,S1} -> + skip_tokens(yysuf(Tcs, Tlen1), L1, Error); + {A1,Alen1,Tlen1,Ics1,L1,S1} -> + skip_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1), Error) + end. + +%% skip_cont(RestChars, Line, Token, Error) +%% Test if we have detected the end token, if so return done else continue. + +skip_cont(Rest, Line, {token,T}, Error) -> + skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0); +skip_cont(Rest, Line, {end_token,T}, Error) -> + {done,{error,Error,Line},Rest}; +skip_cont(Rest, Line, {error,S}, Error) -> + skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0); +skip_cont(Rest, Line, skip_token, Error) -> + skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0). + +yyrev(L) -> yyrev(L, []). + +yyrev([H|T], Acc) -> yyrev(T, [H|Acc]); +yyrev([], Acc) -> Acc. + +yypre([H|T], N) when N > 0 -> [H|yypre(T, N-1)]; +yypre(L, N) -> []. + +yysuf([H|T], N) when N > 0 -> yysuf(T, N-1); +yysuf(L, 0) -> L. + +yysplit(L, N) -> yysplit(L, N, []). +yysplit([H|T], N, Acc) when N > 0 -> yysplit(T, N-1, [H|Acc]); +yysplit(L, 0, Acc) -> {lists:reverse(Acc), L}. + +%% yystate() -> InitialState. +%% yystate(State, InChars, Line, Token, ) -> +%% {Action, AcceptLength, RestChars, Line} | Accepting end state +%% {Action, AcceptLength, RestChars, Line, State} | Accepting state +%% {Action, AcceptLength, TokLength, RestChars, Line, State} | +%% {reject, AcceptLength, TokLength, RestChars, Line, State}. +%% Generated state transition functions. + +yystate() -> 49. + +yystate(52, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(52, Ics, Line, Tlen+1, 21, Tlen); +yystate(52, [C|Ics], Line, Tlen, Action, Alen) when C >= $\v, C =< $ÿ -> + yystate(52, Ics, Line, Tlen+1, 21, Tlen); +yystate(52, Ics, Line, Tlen, Action, Alen) -> + {21,Tlen,Ics,Line,52}; +yystate(51, Ics, Line, Tlen, Action, Alen) -> + {19,Tlen,Ics,Line}; +yystate(50, Ics, Line, Tlen, Action, Alen) -> + {20,Tlen,Ics,Line}; +yystate(49, [$\n|Ics], Line, Tlen, Action, Alen) -> + yystate(45, Ics, Line+1, Tlen+1, Action, Alen); +yystate(49, [$!|Ics], Line, Tlen, Action, Alen) -> + yystate(51, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$"|Ics], Line, Tlen, Action, Alen) -> + yystate(41, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$#|Ics], Line, Tlen, Action, Alen) -> + yystate(51, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$$|Ics], Line, Tlen, Action, Alen) -> + yystate(21, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$%|Ics], Line, Tlen, Action, Alen) -> + yystate(2, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$'|Ics], Line, Tlen, Action, Alen) -> + yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$+|Ics], Line, Tlen, Action, Alen) -> + yystate(26, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$,|Ics], Line, Tlen, Action, Alen) -> + yystate(51, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$-|Ics], Line, Tlen, Action, Alen) -> + yystate(34, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$.|Ics], Line, Tlen, Action, Alen) -> + yystate(46, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$/|Ics], Line, Tlen, Action, Alen) -> + yystate(48, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$:|Ics], Line, Tlen, Action, Alen) -> + yystate(8, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$;|Ics], Line, Tlen, Action, Alen) -> + yystate(51, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$<|Ics], Line, Tlen, Action, Alen) -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$=|Ics], Line, Tlen, Action, Alen) -> + yystate(7, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$>|Ics], Line, Tlen, Action, Alen) -> + yystate(35, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$?|Ics], Line, Tlen, Action, Alen) -> + yystate(51, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$[|Ics], Line, Tlen, Action, Alen) -> + yystate(51, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$]|Ics], Line, Tlen, Action, Alen) -> + yystate(51, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [$_|Ics], Line, Tlen, Action, Alen) -> + yystate(43, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(45, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [C|Ics], Line, Tlen, Action, Alen) when C >= $\v, C =< $\s -> + yystate(45, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [C|Ics], Line, Tlen, Action, Alen) when C >= $(, C =< $* -> + yystate(51, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [C|Ics], Line, Tlen, Action, Alen) when C >= $0, C =< $9 -> + yystate(40, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [C|Ics], Line, Tlen, Action, Alen) when C >= $A, C =< $Z -> + yystate(43, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [C|Ics], Line, Tlen, Action, Alen) when C >= $a, C =< $z -> + yystate(47, Ics, Line, Tlen+1, Action, Alen); +yystate(49, [C|Ics], Line, Tlen, Action, Alen) when C >= ${, C =< $} -> + yystate(51, Ics, Line, Tlen+1, Action, Alen); +yystate(49, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,49}; +yystate(48, [$=|Ics], Line, Tlen, Action, Alen) -> + yystate(44, Ics, Line, Tlen+1, 19, Tlen); +yystate(48, Ics, Line, Tlen, Action, Alen) -> + {19,Tlen,Ics,Line,48}; +yystate(47, [$_|Ics], Line, Tlen, Action, Alen) -> + yystate(47, Ics, Line, Tlen+1, 3, Tlen); +yystate(47, [C|Ics], Line, Tlen, Action, Alen) when C >= $0, C =< $9 -> + yystate(47, Ics, Line, Tlen+1, 3, Tlen); +yystate(47, [C|Ics], Line, Tlen, Action, Alen) when C >= $@, C =< $Z -> + yystate(47, Ics, Line, Tlen+1, 3, Tlen); +yystate(47, [C|Ics], Line, Tlen, Action, Alen) when C >= $a, C =< $z -> + yystate(47, Ics, Line, Tlen+1, 3, Tlen); +yystate(47, Ics, Line, Tlen, Action, Alen) -> + {3,Tlen,Ics,Line,47}; +yystate(46, [$\n|Ics], Line, Tlen, Action, Alen) -> + yystate(50, Ics, Line+1, Tlen+1, 19, Tlen); +yystate(46, [$%|Ics], Line, Tlen, Action, Alen) -> + yystate(52, Ics, Line, Tlen+1, 19, Tlen); +yystate(46, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(50, Ics, Line, Tlen+1, 19, Tlen); +yystate(46, [C|Ics], Line, Tlen, Action, Alen) when C >= $\v, C =< $\s -> + yystate(50, Ics, Line, Tlen+1, 19, Tlen); +yystate(46, Ics, Line, Tlen, Action, Alen) -> + {19,Tlen,Ics,Line,46}; +yystate(45, [$\n|Ics], Line, Tlen, Action, Alen) -> + yystate(45, Ics, Line+1, Tlen+1, 22, Tlen); +yystate(45, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(45, Ics, Line, Tlen+1, 22, Tlen); +yystate(45, [C|Ics], Line, Tlen, Action, Alen) when C >= $\v, C =< $\s -> + yystate(45, Ics, Line, Tlen+1, 22, Tlen); +yystate(45, Ics, Line, Tlen, Action, Alen) -> + {22,Tlen,Ics,Line,45}; +yystate(44, Ics, Line, Tlen, Action, Alen) -> + {13,Tlen,Ics,Line}; +yystate(43, [$_|Ics], Line, Tlen, Action, Alen) -> + yystate(43, Ics, Line, Tlen+1, 4, Tlen); +yystate(43, [C|Ics], Line, Tlen, Action, Alen) when C >= $0, C =< $9 -> + yystate(43, Ics, Line, Tlen+1, 4, Tlen); +yystate(43, [C|Ics], Line, Tlen, Action, Alen) when C >= $@, C =< $Z -> + yystate(43, Ics, Line, Tlen+1, 4, Tlen); +yystate(43, [C|Ics], Line, Tlen, Action, Alen) when C >= $a, C =< $z -> + yystate(43, Ics, Line, Tlen+1, 4, Tlen); +yystate(43, Ics, Line, Tlen, Action, Alen) -> + {4,Tlen,Ics,Line,43}; +yystate(42, Ics, Line, Tlen, Action, Alen) -> + {8,Tlen,Ics,Line}; +yystate(41, [$\n|Ics], Line, Tlen, Action, Alen) -> + yystate(41, Ics, Line+1, Tlen+1, Action, Alen); +yystate(41, [$"|Ics], Line, Tlen, Action, Alen) -> + yystate(37, Ics, Line, Tlen+1, Action, Alen); +yystate(41, [$\\|Ics], Line, Tlen, Action, Alen) -> + yystate(33, Ics, Line, Tlen+1, Action, Alen); +yystate(41, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(41, Ics, Line, Tlen+1, Action, Alen); +yystate(41, [C|Ics], Line, Tlen, Action, Alen) when C >= $\v, C =< $! -> + yystate(41, Ics, Line, Tlen+1, Action, Alen); +yystate(41, [C|Ics], Line, Tlen, Action, Alen) when C >= $#, C =< $[ -> + yystate(41, Ics, Line, Tlen+1, Action, Alen); +yystate(41, [C|Ics], Line, Tlen, Action, Alen) when C >= $], C =< $ÿ -> + yystate(41, Ics, Line, Tlen+1, Action, Alen); +yystate(41, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,41}; +yystate(40, [$#|Ics], Line, Tlen, Action, Alen) -> + yystate(36, Ics, Line, Tlen+1, 2, Tlen); +yystate(40, [$.|Ics], Line, Tlen, Action, Alen) -> + yystate(28, Ics, Line, Tlen+1, 2, Tlen); +yystate(40, [C|Ics], Line, Tlen, Action, Alen) when C >= $0, C =< $9 -> + yystate(40, Ics, Line, Tlen+1, 2, Tlen); +yystate(40, Ics, Line, Tlen, Action, Alen) -> + {2,Tlen,Ics,Line,40}; +yystate(39, Ics, Line, Tlen, Action, Alen) -> + {14,Tlen,Ics,Line}; +yystate(38, Ics, Line, Tlen, Action, Alen) -> + {18,Tlen,Ics,Line}; +yystate(37, Ics, Line, Tlen, Action, Alen) -> + {5,Tlen,Ics,Line}; +yystate(36, [C|Ics], Line, Tlen, Action, Alen) when C >= $0, C =< $9 -> + yystate(32, Ics, Line, Tlen+1, Action, Alen); +yystate(36, [C|Ics], Line, Tlen, Action, Alen) when C >= $A, C =< $F -> + yystate(32, Ics, Line, Tlen+1, Action, Alen); +yystate(36, [C|Ics], Line, Tlen, Action, Alen) when C >= $a, C =< $f -> + yystate(32, Ics, Line, Tlen+1, Action, Alen); +yystate(36, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,36}; +yystate(35, [$=|Ics], Line, Tlen, Action, Alen) -> + yystate(39, Ics, Line, Tlen+1, 19, Tlen); +yystate(35, Ics, Line, Tlen, Action, Alen) -> + {19,Tlen,Ics,Line,35}; +yystate(34, [$-|Ics], Line, Tlen, Action, Alen) -> + yystate(38, Ics, Line, Tlen+1, 19, Tlen); +yystate(34, [$>|Ics], Line, Tlen, Action, Alen) -> + yystate(42, Ics, Line, Tlen+1, 19, Tlen); +yystate(34, Ics, Line, Tlen, Action, Alen) -> + {19,Tlen,Ics,Line,34}; +yystate(33, [$\n|Ics], Line, Tlen, Action, Alen) -> + yystate(41, Ics, Line+1, Tlen+1, Action, Alen); +yystate(33, [$"|Ics], Line, Tlen, Action, Alen) -> + yystate(29, Ics, Line, Tlen+1, Action, Alen); +yystate(33, [$\\|Ics], Line, Tlen, Action, Alen) -> + yystate(33, Ics, Line, Tlen+1, Action, Alen); +yystate(33, [$]|Ics], Line, Tlen, Action, Alen) -> + yystate(41, Ics, Line, Tlen+1, Action, Alen); +yystate(33, [$^|Ics], Line, Tlen, Action, Alen) -> + yystate(25, Ics, Line, Tlen+1, Action, Alen); +yystate(33, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(41, Ics, Line, Tlen+1, Action, Alen); +yystate(33, [C|Ics], Line, Tlen, Action, Alen) when C >= $\v, C =< $! -> + yystate(41, Ics, Line, Tlen+1, Action, Alen); +yystate(33, [C|Ics], Line, Tlen, Action, Alen) when C >= $#, C =< $[ -> + yystate(41, Ics, Line, Tlen+1, Action, Alen); +yystate(33, [C|Ics], Line, Tlen, Action, Alen) when C >= $_, C =< $ÿ -> + yystate(41, Ics, Line, Tlen+1, Action, Alen); +yystate(33, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,33}; +yystate(32, [C|Ics], Line, Tlen, Action, Alen) when C >= $0, C =< $9 -> + yystate(32, Ics, Line, Tlen+1, 1, Tlen); +yystate(32, [C|Ics], Line, Tlen, Action, Alen) when C >= $A, C =< $F -> + yystate(32, Ics, Line, Tlen+1, 1, Tlen); +yystate(32, [C|Ics], Line, Tlen, Action, Alen) when C >= $a, C =< $f -> + yystate(32, Ics, Line, Tlen+1, 1, Tlen); +yystate(32, Ics, Line, Tlen, Action, Alen) -> + {1,Tlen,Ics,Line,32}; +yystate(31, Ics, Line, Tlen, Action, Alen) -> + {11,Tlen,Ics,Line}; +yystate(30, Ics, Line, Tlen, Action, Alen) -> + {17,Tlen,Ics,Line}; +yystate(29, [$\n|Ics], Line, Tlen, Action, Alen) -> + yystate(41, Ics, Line+1, Tlen+1, 5, Tlen); +yystate(29, [$"|Ics], Line, Tlen, Action, Alen) -> + yystate(37, Ics, Line, Tlen+1, 5, Tlen); +yystate(29, [$\\|Ics], Line, Tlen, Action, Alen) -> + yystate(33, Ics, Line, Tlen+1, 5, Tlen); +yystate(29, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(41, Ics, Line, Tlen+1, 5, Tlen); +yystate(29, [C|Ics], Line, Tlen, Action, Alen) when C >= $\v, C =< $! -> + yystate(41, Ics, Line, Tlen+1, 5, Tlen); +yystate(29, [C|Ics], Line, Tlen, Action, Alen) when C >= $#, C =< $[ -> + yystate(41, Ics, Line, Tlen+1, 5, Tlen); +yystate(29, [C|Ics], Line, Tlen, Action, Alen) when C >= $], C =< $ÿ -> + yystate(41, Ics, Line, Tlen+1, 5, Tlen); +yystate(29, Ics, Line, Tlen, Action, Alen) -> + {5,Tlen,Ics,Line,29}; +yystate(28, [C|Ics], Line, Tlen, Action, Alen) when C >= $0, C =< $9 -> + yystate(24, Ics, Line, Tlen+1, Action, Alen); +yystate(28, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,28}; +yystate(27, Ics, Line, Tlen, Action, Alen) -> + {15,Tlen,Ics,Line}; +yystate(26, [$+|Ics], Line, Tlen, Action, Alen) -> + yystate(30, Ics, Line, Tlen+1, 19, Tlen); +yystate(26, Ics, Line, Tlen, Action, Alen) -> + {19,Tlen,Ics,Line,26}; +yystate(25, [$\n|Ics], Line, Tlen, Action, Alen) -> + yystate(41, Ics, Line+1, Tlen+1, Action, Alen); +yystate(25, [$"|Ics], Line, Tlen, Action, Alen) -> + yystate(29, Ics, Line, Tlen+1, Action, Alen); +yystate(25, [$\\|Ics], Line, Tlen, Action, Alen) -> + yystate(33, Ics, Line, Tlen+1, Action, Alen); +yystate(25, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(41, Ics, Line, Tlen+1, Action, Alen); +yystate(25, [C|Ics], Line, Tlen, Action, Alen) when C >= $\v, C =< $! -> + yystate(41, Ics, Line, Tlen+1, Action, Alen); +yystate(25, [C|Ics], Line, Tlen, Action, Alen) when C >= $#, C =< $[ -> + yystate(41, Ics, Line, Tlen+1, Action, Alen); +yystate(25, [C|Ics], Line, Tlen, Action, Alen) when C >= $], C =< $ÿ -> + yystate(41, Ics, Line, Tlen+1, Action, Alen); +yystate(25, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,25}; +yystate(24, [$E|Ics], Line, Tlen, Action, Alen) -> + yystate(12, Ics, Line, Tlen+1, 0, Tlen); +yystate(24, [$e|Ics], Line, Tlen, Action, Alen) -> + yystate(12, Ics, Line, Tlen+1, 0, Tlen); +yystate(24, [C|Ics], Line, Tlen, Action, Alen) when C >= $0, C =< $9 -> + yystate(24, Ics, Line, Tlen+1, 0, Tlen); +yystate(24, Ics, Line, Tlen, Action, Alen) -> + {0,Tlen,Ics,Line,24}; +yystate(23, Ics, Line, Tlen, Action, Alen) -> + {12,Tlen,Ics,Line}; +yystate(22, [$\n|Ics], Line, Tlen, Action, Alen) -> + yystate(6, Ics, Line+1, Tlen+1, Action, Alen); +yystate(22, [$'|Ics], Line, Tlen, Action, Alen) -> + yystate(18, Ics, Line, Tlen+1, Action, Alen); +yystate(22, [$\\|Ics], Line, Tlen, Action, Alen) -> + yystate(14, Ics, Line, Tlen+1, Action, Alen); +yystate(22, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(22, [C|Ics], Line, Tlen, Action, Alen) when C >= $\v, C =< $& -> + yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(22, [C|Ics], Line, Tlen, Action, Alen) when C >= $(, C =< $[ -> + yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(22, [C|Ics], Line, Tlen, Action, Alen) when C >= $], C =< $ÿ -> + yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(22, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,22}; +yystate(21, [$\\|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, Action, Alen); +yystate(21, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(5, Ics, Line, Tlen+1, Action, Alen); +yystate(21, [C|Ics], Line, Tlen, Action, Alen) when C >= $\v, C =< $[ -> + yystate(5, Ics, Line, Tlen+1, Action, Alen); +yystate(21, [C|Ics], Line, Tlen, Action, Alen) when C >= $], C =< $ÿ -> + yystate(5, Ics, Line, Tlen+1, Action, Alen); +yystate(21, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,21}; +yystate(20, [C|Ics], Line, Tlen, Action, Alen) when C >= $0, C =< $9 -> + yystate(16, Ics, Line, Tlen+1, Action, Alen); +yystate(20, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,20}; +yystate(19, [$=|Ics], Line, Tlen, Action, Alen) -> + yystate(23, Ics, Line, Tlen+1, Action, Alen); +yystate(19, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,19}; +yystate(18, [$\n|Ics], Line, Tlen, Action, Alen) -> + yystate(6, Ics, Line+1, Tlen+1, 6, Tlen); +yystate(18, [$'|Ics], Line, Tlen, Action, Alen) -> + yystate(10, Ics, Line, Tlen+1, 6, Tlen); +yystate(18, [$\\|Ics], Line, Tlen, Action, Alen) -> + yystate(14, Ics, Line, Tlen+1, 6, Tlen); +yystate(18, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(6, Ics, Line, Tlen+1, 6, Tlen); +yystate(18, [C|Ics], Line, Tlen, Action, Alen) when C >= $\v, C =< $& -> + yystate(6, Ics, Line, Tlen+1, 6, Tlen); +yystate(18, [C|Ics], Line, Tlen, Action, Alen) when C >= $(, C =< $[ -> + yystate(6, Ics, Line, Tlen+1, 6, Tlen); +yystate(18, [C|Ics], Line, Tlen, Action, Alen) when C >= $], C =< $ÿ -> + yystate(6, Ics, Line, Tlen+1, 6, Tlen); +yystate(18, Ics, Line, Tlen, Action, Alen) -> + {6,Tlen,Ics,Line,18}; +yystate(17, [$^|Ics], Line, Tlen, Action, Alen) -> + yystate(1, Ics, Line, Tlen+1, 7, Tlen); +yystate(17, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(5, Ics, Line, Tlen+1, 7, Tlen); +yystate(17, [C|Ics], Line, Tlen, Action, Alen) when C >= $\v, C =< $/ -> + yystate(5, Ics, Line, Tlen+1, 7, Tlen); +yystate(17, [C|Ics], Line, Tlen, Action, Alen) when C >= $0, C =< $7 -> + yystate(13, Ics, Line, Tlen+1, 7, Tlen); +yystate(17, [C|Ics], Line, Tlen, Action, Alen) when C >= $8, C =< $] -> + yystate(5, Ics, Line, Tlen+1, 7, Tlen); +yystate(17, [C|Ics], Line, Tlen, Action, Alen) when C >= $_, C =< $ÿ -> + yystate(5, Ics, Line, Tlen+1, 7, Tlen); +yystate(17, Ics, Line, Tlen, Action, Alen) -> + {7,Tlen,Ics,Line,17}; +yystate(16, [C|Ics], Line, Tlen, Action, Alen) when C >= $0, C =< $9 -> + yystate(16, Ics, Line, Tlen+1, 0, Tlen); +yystate(16, Ics, Line, Tlen, Action, Alen) -> + {0,Tlen,Ics,Line,16}; +yystate(15, Ics, Line, Tlen, Action, Alen) -> + {10,Tlen,Ics,Line}; +yystate(14, [$\n|Ics], Line, Tlen, Action, Alen) -> + yystate(6, Ics, Line+1, Tlen+1, Action, Alen); +yystate(14, [$'|Ics], Line, Tlen, Action, Alen) -> + yystate(18, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [$\\|Ics], Line, Tlen, Action, Alen) -> + yystate(14, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [$]|Ics], Line, Tlen, Action, Alen) -> + yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [$^|Ics], Line, Tlen, Action, Alen) -> + yystate(22, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= $\v, C =< $& -> + yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= $(, C =< $[ -> + yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= $_, C =< $ÿ -> + yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(14, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,14}; +yystate(13, [C|Ics], Line, Tlen, Action, Alen) when C >= $0, C =< $7 -> + yystate(9, Ics, Line, Tlen+1, 7, Tlen); +yystate(13, Ics, Line, Tlen, Action, Alen) -> + {7,Tlen,Ics,Line,13}; +yystate(12, [$+|Ics], Line, Tlen, Action, Alen) -> + yystate(20, Ics, Line, Tlen+1, Action, Alen); +yystate(12, [$-|Ics], Line, Tlen, Action, Alen) -> + yystate(20, Ics, Line, Tlen+1, Action, Alen); +yystate(12, [C|Ics], Line, Tlen, Action, Alen) when C >= $0, C =< $9 -> + yystate(16, Ics, Line, Tlen+1, Action, Alen); +yystate(12, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,12}; +yystate(11, [$=|Ics], Line, Tlen, Action, Alen) -> + yystate(15, Ics, Line, Tlen+1, Action, Alen); +yystate(11, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,11}; +yystate(10, Ics, Line, Tlen, Action, Alen) -> + {6,Tlen,Ics,Line}; +yystate(9, [C|Ics], Line, Tlen, Action, Alen) when C >= $0, C =< $7 -> + yystate(5, Ics, Line, Tlen+1, Action, Alen); +yystate(9, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,9}; +yystate(8, [$-|Ics], Line, Tlen, Action, Alen) -> + yystate(4, Ics, Line, Tlen+1, 19, Tlen); +yystate(8, Ics, Line, Tlen, Action, Alen) -> + {19,Tlen,Ics,Line,8}; +yystate(7, [$/|Ics], Line, Tlen, Action, Alen) -> + yystate(11, Ics, Line, Tlen+1, 19, Tlen); +yystate(7, [$:|Ics], Line, Tlen, Action, Alen) -> + yystate(19, Ics, Line, Tlen+1, 19, Tlen); +yystate(7, [$<|Ics], Line, Tlen, Action, Alen) -> + yystate(27, Ics, Line, Tlen+1, 19, Tlen); +yystate(7, [$=|Ics], Line, Tlen, Action, Alen) -> + yystate(31, Ics, Line, Tlen+1, 19, Tlen); +yystate(7, Ics, Line, Tlen, Action, Alen) -> + {19,Tlen,Ics,Line,7}; +yystate(6, [$\n|Ics], Line, Tlen, Action, Alen) -> + yystate(6, Ics, Line+1, Tlen+1, Action, Alen); +yystate(6, [$'|Ics], Line, Tlen, Action, Alen) -> + yystate(10, Ics, Line, Tlen+1, Action, Alen); +yystate(6, [$\\|Ics], Line, Tlen, Action, Alen) -> + yystate(14, Ics, Line, Tlen+1, Action, Alen); +yystate(6, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(6, [C|Ics], Line, Tlen, Action, Alen) when C >= $\v, C =< $& -> + yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(6, [C|Ics], Line, Tlen, Action, Alen) when C >= $(, C =< $[ -> + yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(6, [C|Ics], Line, Tlen, Action, Alen) when C >= $], C =< $ÿ -> + yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(6, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,6}; +yystate(5, Ics, Line, Tlen, Action, Alen) -> + {7,Tlen,Ics,Line}; +yystate(4, Ics, Line, Tlen, Action, Alen) -> + {9,Tlen,Ics,Line}; +yystate(3, Ics, Line, Tlen, Action, Alen) -> + {16,Tlen,Ics,Line}; +yystate(2, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(2, Ics, Line, Tlen+1, 23, Tlen); +yystate(2, [C|Ics], Line, Tlen, Action, Alen) when C >= $\v, C =< $ÿ -> + yystate(2, Ics, Line, Tlen+1, 23, Tlen); +yystate(2, Ics, Line, Tlen, Action, Alen) -> + {23,Tlen,Ics,Line,2}; +yystate(1, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(5, Ics, Line, Tlen+1, 7, Tlen); +yystate(1, [C|Ics], Line, Tlen, Action, Alen) when C >= $\v, C =< $ÿ -> + yystate(5, Ics, Line, Tlen+1, 7, Tlen); +yystate(1, Ics, Line, Tlen, Action, Alen) -> + {7,Tlen,Ics,Line,1}; +yystate(0, [$=|Ics], Line, Tlen, Action, Alen) -> + yystate(3, Ics, Line, Tlen+1, 19, Tlen); +yystate(0, Ics, Line, Tlen, Action, Alen) -> + {19,Tlen,Ics,Line,0}; +yystate(S, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,S}. + + +%% yyaction(Action, TokenLength, TokenChars, Line) -> +%% {token,Token} | {end_token, Token} | skip_token | {error,String}. +%% Generated action function. + +yyaction(0, YYlen, YYtcs, YYline) -> + YYtext = yypre(YYtcs, YYlen), + {token,{float,YYline,list_to_float(YYtext)}}; +yyaction(1, YYlen, YYtcs, YYline) -> + YYtext = yypre(YYtcs, YYlen), + base(YYline,YYtext); +yyaction(2, YYlen, YYtcs, YYline) -> + YYtext = yypre(YYtcs, YYlen), + {token,{integer,YYline,list_to_integer(YYtext)}}; +yyaction(3, YYlen, YYtcs, YYline) -> + YYtext = yypre(YYtcs, YYlen), + Atom = list_to_atom(YYtext), + {token,case reserved_word(Atom) of + true -> + {Atom,YYline}; + false -> + {atom,YYline,Atom} + end}; +yyaction(4, YYlen, YYtcs, YYline) -> + YYtext = yypre(YYtcs, YYlen), + {token,{var,YYline,list_to_atom(YYtext)}}; +yyaction(5, YYlen, YYtcs, YYline) -> + YYtext = yypre(YYtcs, YYlen), + S = lists:sublist(YYtext,2,length(YYtext) - 2), + {token,{string,YYline,string_gen(S)}}; +yyaction(6, YYlen, YYtcs, YYline) -> + YYtext = yypre(YYtcs, YYlen), + S = lists:sublist(YYtext,2,length(YYtext) - 2), + {token,{atom,YYline,list_to_atom(string_gen(S))}}; +yyaction(7, YYlen, YYtcs, YYline) -> + YYtext = yypre(YYtcs, YYlen), + {token,{integer,YYline,cc_convert(YYtext)}}; +yyaction(8, YYlen, YYtcs, YYline) -> + {token,{'->',YYline}}; +yyaction(9, YYlen, YYtcs, YYline) -> + {token,{':-',YYline}}; +yyaction(10, YYlen, YYtcs, YYline) -> + {token,{'=/=',YYline}}; +yyaction(11, YYlen, YYtcs, YYline) -> + {token,{'==',YYline}}; +yyaction(12, YYlen, YYtcs, YYline) -> + {token,{'=:=',YYline}}; +yyaction(13, YYlen, YYtcs, YYline) -> + {token,{'/=',YYline}}; +yyaction(14, YYlen, YYtcs, YYline) -> + {token,{'>=',YYline}}; +yyaction(15, YYlen, YYtcs, YYline) -> + {token,{'=<',YYline}}; +yyaction(16, YYlen, YYtcs, YYline) -> + {token,{'<=',YYline}}; +yyaction(17, YYlen, YYtcs, YYline) -> + {token,{'++',YYline}}; +yyaction(18, YYlen, YYtcs, YYline) -> + {token,{'--',YYline}}; +yyaction(19, YYlen, YYtcs, YYline) -> + YYtext = yypre(YYtcs, YYlen), + {token,{list_to_atom(YYtext),YYline}}; +yyaction(20, YYlen, YYtcs, YYline) -> + {end_token,{dot,YYline}}; +yyaction(21, YYlen, YYtcs, YYline) -> + {end_token,{dot,YYline}}; +yyaction(22, YYlen, YYtcs, YYline) -> skip_token; +yyaction(23, YYlen, YYtcs, YYline) -> + skip_token; +yyaction(_, _, _, _) -> error. diff --git a/mods/src/em_erlang_scan.xrl b/mods/src/em_erlang_scan.xrl new file mode 100644 index 0000000..572cb29 --- /dev/null +++ b/mods/src/em_erlang_scan.xrl @@ -0,0 +1,135 @@ +%% Token Definitions for Erlang. + +Definitions. +O = [0-7] +D = [0-9] +H = [0-9a-fA-F] +A = [a-z_A-Z@0-9] +WS = [\000-\s] + +Rules. +{D}+\.{D}+((E|e)(\+|\-)?{D}+)? : + {token,{float,YYline,list_to_float(YYtext)}}. +{D}+#{H}+ : base(YYline, YYtext). +{D}+ : {token,{integer,YYline,list_to_integer(YYtext)}}. +[a-z]{A}* : Atom = list_to_atom(YYtext), + {token,case reserved_word(Atom) of + true -> {Atom,YYline}; + false -> {atom,YYline,Atom} + end}. +[_A-Z]{A}* : {token,{var,YYline,list_to_atom(YYtext)}}. +"(\\\^.|\\.|[^"])*" : + %% Strip quotes. + S = lists:sublist(YYtext, 2, length(YYtext) - 2), + {token,{string,YYline,string_gen(S)}}. +'(\\\^.|\\.|[^'])*' : + %% Strip quotes. + S = lists:sublist(YYtext, 2, length(YYtext) - 2), + {token,{atom,YYline,list_to_atom(string_gen(S))}}. +\$(\\{O}{O}{O}|\\\^.|\\.|.) : + {token,{integer,YYline,cc_convert(YYtext)}}. +-> : {token,{'->',YYline}}. +:- : {token,{':-',YYline}}. +=/= : {token,{'=/=',YYline}}. +== : {token,{'==',YYline}}. +=:= : {token,{'=:=',YYline}}. +/= : {token,{'/=',YYline}}. +>= : {token,{'>=',YYline}}. +=< : {token,{'=<',YYline}}. +<= : {token,{'<=',YYline}}. +\+\+ : {token,{'++',YYline}}. +-- : {token,{'--',YYline}}. +[!?/;:,.*+#()[\]|<>={}-] : + {token,{list_to_atom(YYtext),YYline}}. +\.{WS} : {end_token,{dot,YYline}}. +\.%.* : {end_token,{dot,YYline}}. %Must special case this +{WS}+ : . %No token returned, eqivalent +\%.* : skip_token. % to 'skip_token' + +Erlang code. + +-author('rv@cslab.ericsson.se'). +-copyright('Copyright (c) 1996 Ericsson Telecommunications AB'). + +-export([reserved_word/1]). + +reserved_word('after') -> true; +reserved_word('begin') -> true; +reserved_word('case') -> true; +reserved_word('catch') -> true; +reserved_word('end') -> true; +reserved_word('fun') -> true; +reserved_word('if') -> true; +reserved_word('let') -> true; +reserved_word('of') -> true; +reserved_word('query') -> true; +reserved_word('receive') -> true; +reserved_word('when') -> true; +reserved_word('bnot') -> true; +reserved_word('not') -> true; +reserved_word('div') -> true; +reserved_word('rem') -> true; +reserved_word('band') -> true; +reserved_word('and') -> true; +reserved_word('bor') -> true; +reserved_word('bxor') -> true; +reserved_word('bsl') -> true; +reserved_word('bsr') -> true; +reserved_word('or') -> true; +reserved_word('xor') -> true; +reserved_word(_) -> false. + +base(L, Cs) -> + H = string:chr(Cs, $#), + case list_to_integer(string:substr(Cs, 1, H-1)) of + B when B > 16 -> {error,"illegal base"}; + B -> + case base(string:substr(Cs, H+1), B, 0) of + error -> {error,"illegal based number"}; + N -> {token,{integer,L,N}} + end + end. + +base([C|Cs], Base, SoFar) when C >= $0, C =< $9, C < Base + $0 -> + Next = SoFar * Base + (C - $0), + base(Cs, Base, Next); +base([C|Cs], Base, SoFar) when C >= $a, C =< $f, C < Base + $a - 10 -> + Next = SoFar * Base + (C - $a + 10), + base(Cs, Base, Next); +base([C|Cs], Base, SoFar) when C >= $A, C =< $F, C < Base + $A - 10 -> + Next = SoFar * Base + (C - $A + 10), + base(Cs, Base, Next); +base([C|Cs], Base, SoFar) -> error; +base([], Base, N) -> N. + +cc_convert([$$,$\\|Cs]) -> + hd(string_escape(Cs)); +cc_convert([$$,C]) -> C. + +string_gen([$\\|Cs]) -> + string_escape(Cs); +string_gen([C|Cs]) -> + [C|string_gen(Cs)]; +string_gen([]) -> []. + +string_escape([O1,O2,O3|S]) when + O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 -> + [(O1*8 + O2)*8 + O3 - 73*$0|string_gen(S)]; +string_escape([$^,C|Cs]) -> + [C band 31|string_gen(Cs)]; +string_escape([C|Cs]) when C >= 0, C =< $ -> + string_gen(Cs); +string_escape([C|Cs]) -> + [escape_char(C)|string_gen(Cs)]. + +escape_char($n) -> $\n; %\n = LF +escape_char($r) -> $\r; %\r = CR +escape_char($t) -> $\t; %\t = TAB +escape_char($v) -> $\v; %\v = VT +escape_char($b) -> $\b; %\b = BS +escape_char($f) -> $\f; %\f = FF +escape_char($e) -> $\e; %\e = ESC +escape_char($s) -> $ ; %\s = SPC +escape_char($d) -> $\d; %\d = DEL +escape_char(C) -> C. + diff --git a/mods/src/em_scan.erl b/mods/src/em_scan.erl new file mode 100644 index 0000000..5467423 --- /dev/null +++ b/mods/src/em_scan.erl @@ -0,0 +1,381 @@ +%%%---------------------------------------------------------------------- +%%% File : em_scan.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Scanner for leex-style DFAs. +%%% Created : 1 May 2001 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(em_scan). +-author('luke@bluetail.com'). + +-record(scanner, {initial_state, dfa_fn, action_fn}). + +%% Token record. The actual lexeme, if returned by the scanner, is +%% discarded - all that's taken from the user-written "actions" is the +%% token type. Instead we just keep the start/finish positions in a +%% cord. +%% +%% Not retaining line/column information. Hopefully that's convenient +%% to derive later. +-record(token, + { + %% type chosen by scanner. There is also a special type: + %% 'em_skipped', when the scanner returns 'skip_token' but + %% we record it anyway. + type, + %% start position in cord + start, + %% finish position in cord (point *after* the last character) + finish, + %% number of characters beyond the end of the lexeme that + %% the scanner state machine examined, recorded to track + %% dependency + lookahead, + %% for book keeping, saying whether the token is known to + %% need re-scanning + dirty=false + }). + +-compile(export_all). +%%-export([Function/Arity, ...]). + +%% Bogus line number to pass to leex/yecc, because I don't want to +%% track lines at this low level. It's not safe to use an atom, but +%% hopefully -42 looks deliberate enough to prompt a grep :-) + +-define(line, -42). %% Confusing number appearing where line # should be :-) + +make_scanner(State0, DFA, Action) -> + #scanner{initial_state=State0, + dfa_fn=DFA, + action_fn=Action}. + +make_scheme_scanner() -> make_leex_scanner(em_scheme_scan). +make_erlang_scanner() -> make_leex_scanner(em_erlang_scan). +make_test_scanner() -> make_leex_scanner(em_test_scan). + +make_leex_scanner(Mod) -> + make_scanner(Mod:yystate(), + {Mod, yystate}, + {Mod, yyaction}). + +scan_annotation(Scanner, _S0, Cord, RText, Start, End) -> + Walker = cord:walker(Cord), + case edit_scan(Scanner, Walker) of + {ok, Toks} -> + {ok, Toks}; + {error, Reason} -> + {ok, bad_scan} + end. + +test_test(Str) -> + case test_string(Str) of + {ok, Toks} -> + {ok, lists:map(fun(T) -> element(1, T) end, + Toks)}; + X -> + X + end. + +erlang_test(Str) -> + case erlang_string(Str) of + {ok, Toks} -> + {ok, lists:map(fun(T) -> element(1, T) end, + Toks)}; + X -> + X + end. + +scheme_test(Str) -> + case scheme_string(Str) of + {ok, Toks} -> + {ok, lists:map(fun(T) -> element(1, T) end, + Toks)}; + X -> + X + end. + +test_string(Str) -> + Cord = list_to_binary(Str), + Scan = make_test_scanner(), + edit_scan(Scan, cord:walker(Cord)). + +scheme_string(Str) -> + Cord = list_to_binary(Str), + Scan = make_scheme_scanner(), + edit_scan(Scan, cord:walker(Cord)). + +erlang_string(Str) -> + Cord = list_to_binary(Str), + Scan = make_erlang_scanner(), + edit_scan(Scan, cord:walker(Cord)). + +%% Returns: {ok, [#token]} | {error, Reason} +edit_scan(Scn, Walker) -> + edit_scan(Scn, Walker, 1, []). + +edit_scan(Scn, Walker, Pos) -> + edit_scan(Scn, Walker, 1, []). + +edit_scan(Scn, W0, Pos, Acc) -> + case token(Scn, W0) of + {done, eof} -> + {ok, lists:reverse(Acc)}; + {done, Result, W1} -> + Token = make_token(Pos, Result), + edit_scan(Scn, W1, Token#token.finish, [Token|Acc]); + {error, Reason} -> + {error, Reason} + end. + +make_skipped_token(Pos, Len, LookAhead) -> + #token{type=em_skipped, + start=Pos, + finish=Pos+Len, + lookahead=LookAhead}. + +% tokens(Scn, Walker) -> +% tokens(Scn, Walker, []). + +% tokens(Scn, W0, Acc) -> +% case token(Scn, W0) of +% {done, {ok, T, Acs}, W1} -> +% tokens(Scn, W1, [T|Acc]); +% {done, {skip_token, Acs}, W1} -> +% tokens(Scn, W1, Acc); +% {done, eof} -> +% {ok, lists:reverse(Acc)}; +% {error, Reason} -> +% {error, Reason} +% end. + +token(Scn, Walker) -> + State0 = Scn#scanner.initial_state, + token(Scn, Walker, State0, [], 0, reject, 0). + +token(Scn, W0, S0, Tcs0, Tlen0, A0, Alen0) -> + ActionF = Scn#scanner.action_fn, + DFA_F = Scn#scanner.dfa_fn, + {Ics, Tcs1, W1} = case cord:walker_next(W0) of + {done, WEOF} -> + {eof, Tcs0, WEOF}; + {Ch, Wnext} -> + {[Ch], [Ch|Tcs0], Wnext} + end, + case DFA_F(S0, Ics, ?line, Tlen0, A0, Alen0) of + {A1,Alen1,[],L1} -> % accepting end state + TcsFwd = lists:reverse(Tcs1), + token_cont(Scn,TcsFwd,W1,[],ActionF(A1,Alen1,TcsFwd,?line)); + {A1,Alen1,[],L1,S1} -> % after accepting state + token(Scn,W1,S1,Tcs1,Alen1,A1,Alen1); + {A1,Alen1,Ics1,L1,S1} -> % accepting state with leftover + % chars.. sounds like a time + % to act. + TcsFwd = lists:reverse(Tcs1), + Acs = yypre(TcsFwd, Alen1), + token_cont(Scn,Acs,W1,Ics1,ActionF(A1,Alen1,TcsFwd,?line)); + {A1,Alen1,Tlen1,[],L1,S1} -> % after a non-accepting state + token(Scn,W1,S1,Tcs1,Tlen1,A1,Alen1); + {reject,Alen1,Tlen1,eof,L1,S1} -> + {done,eof}; + {reject,Alen1,Tlen1,Ics1,L1,S1} -> + {done, + {error,{illegal,yypre(Tcs1, Tlen1+1)}}, + Ics1}; + {A1,Alen1,Tlen1,Ics1,L1,S1} -> + TcsFwd = lists:reverse(Tcs1), + {Acs, Rest} = yysplit(TcsFwd, Alen1), + token_cont(Scn,Acs,W1,Rest,ActionF(A1,Alen1,TcsFwd,?line)) + end. + +token_cont(Scn, Acs, W, Rest, {token, T}) -> + {done, {ok, T, Acs, length_of(Rest)}, pushback(W, Rest)}; +token_cont(Scn, Acs, W, Rest, {end_token, T}) -> + {done, {ok, T, Acs, length_of(Rest)}, pushback(W, Rest)}; +token_cont(Scn, Acs, W, Rest, SkipToken) -> + {done, {skip_token, Acs, length_of(Rest)}, pushback(W, Rest)}; +token_cont(Scn, Acs, W, Rest, {error, S}) -> + {done, {error, {user,S}}, pushback(W, Rest)}. + +adjust_pos(C, L, [$\n|T]) -> adjust_pos(0, L+1, T); +adjust_pos(C, L, [_|T]) -> adjust_pos(C+1, L, T); +adjust_pos(C, L, []) -> {C, L}. + +length_of(eof) -> 0; +length_of(List) -> length(List). + +pushback(W, eof) -> + W; +pushback(W, Chars) -> + lists:foldr(fun(C, Wn) -> cord:walker_push(C, Wn) end, + W, + Chars). + +yyrev([H|T], Acc) -> yyrev(T, [H|Acc]); +yyrev([], Acc) -> Acc. + +yypre([H|T], N) when N > 0 -> [H|yypre(T, N-1)]; +yypre(L, N) -> []. + +yysuf([H|T], N) when N > 0 -> yysuf(T, N-1); +yysuf(L, 0) -> L. + +yysplit(L, N) -> yysplit(L, N, []). +yysplit([H|T], N, Acc) when N > 0 -> yysplit(T, N-1, [H|Acc]); +yysplit(L, 0, Acc) -> {lists:reverse(Acc), L}. + +%%%%% +%% Experimentation with incremental parsing (working) + +str0() -> "-bar+ +foo-". +toks0() -> + {ok, Toks} = test_string(str0()), + Toks. + +%% Hand-hacked version of Toks0 with the space character deleted and +%% its token marked as dirty. When properly re-scanned, this should be +%% the same as toks1 (basis of the test case) +dirty_toks() -> [OK_A,OK_B,OK_C,Changed0|Following0] = toks0(), + Changed1 = Changed0#token{dirty=true}, + Following1 = lists:map(fun(T) -> + Start = T#token.start, + Finish = T#token.finish, + T#token{start=Start-1, + finish=Finish-1} + end, + Following0), + [OK_A,OK_B,OK_C,Changed1|Following1]. + +%% for: "bar++foo" +%% The space has been changed (now length 0) and is marked dirty. +str1() -> str0() -- " ". +toks1() -> + {ok, Toks} = test_string(str1()), + Toks. + +rescan_test() -> + Scn = make_test_scanner(), + Dirty = mark_dirty(dirty_toks()), + Cord = cord:new(str1()), + Result = rescan(Scn, Cord, Dirty), + {Result == toks1(), Result, Dirty}. + +%% What should happen with this simple algorithm and test case: +%% 1. We see that a token is dirty. +%% 2. We scan backwards for dependencies (by seeing what has a look-ahead +%% that reaches the dirty token, or another token that does, etc) +%% 3. From the earliest dependency, we start re-scanning everything +%% until we scan a token which leaves the state of the lexer the same as +%% it was previously (i.e. so the following token is known to be +%% unchanged) +%% +%% So, how is the state of the lexer defined? In leex it seems to me +%% that the internal scanner state is always the same at the start of +%% each token (barring anything magical done in actions - which for +%% now I ignore). So, I think it's safe to assume that a token will be +%% unchanged if both the chars in its lexeme and the ones reached by +%% its look-ahead are the same. For "tricky stuff" it may be necessary +%% to capture the process dictionary and include (some of) it in the +%% state. +%% +%% So I will terminate when I reach a token beyond the changed text +%% which is starting in the same place. +%% +%% NB: We shouldn't need to go *too* far backwards when marking +%% dependencies in languages I can think of, because of common +%% zero-lookahead tokens like: \n , ; ( ) ...etc and most other +%% tokens will just be 1-lookahead. + +re_lex(Toks0) -> + Toks1 = mark_dirty(Toks0). + +mark_dirty(Toks) -> + mark_dirty(Toks, []). + +mark_dirty([H|T], Acc) when H#token.dirty == true -> + mark_dirty1([H|T], H#token.start, Acc); +mark_dirty([H|T], Acc) -> + mark_dirty(T, [H|Acc]); +mark_dirty([], Acc) -> + lists:reverse(Acc). + +mark_dirty1(OK, DirtyPos, Toks0) -> + F = fun(Tok, DP) -> + case (Tok#token.finish-1) + Tok#token.lookahead of + P when P >= DP -> + {Tok#token{dirty=true}, + Tok#token.start}; + _ -> + {Tok, DP} + end + end, + {Toks1, _} = lists:mapfoldl(F, DirtyPos, Toks0), + lists:reverse(Toks1) ++ OK. + +%% Rescan dirty tokens. +rescan(Scn, Cord, []) -> + []; +rescan(Scn, Cord, [H|T]) when H#token.dirty == false -> + [H|rescan(Scn, Cord, T)]; +rescan(Scn, Cord, [H|T]) when H#token.dirty == true -> + Pos = H#token.start, + {_, Region} = cord:split(Cord, Pos-1), + Walker = cord:walker(Region), + rescan_dirty(Scn, Walker, Pos, [H|T]). + +%% rescan_dirty(Toks) +%% +%% The first token is dirty. Scan until we get back to a sane state +rescan_dirty(Scn, W0, Pos, [Tok|Toks]) -> + Start = Tok#token.start, + io:format("(Pos = ~p) Rescanning ~p~n", [Pos, Tok]), + case token(Scn, W0) of + {done, eof} -> + []; + {done, Result, W1} -> + Token = make_token(Pos, Result), + [Token|rescan_dirty_cont(Scn, W1, Token#token.finish, Toks)]; + {error, Reason} -> + %% FIXME: should make an error-token and carry on + {error, Reason} + end. + +rescan_dirty_cont(Scn, W, Pos, []) -> + []; +rescan_dirty_cont(Scn, W, Pos, Rest) -> + Next = hd(Rest), + if + %% This token no longer exists! + Next#token.finish =< Pos -> + io:format("(Pos = ~p) Discaring token: ~p~n", [Pos, Next]), + rescan_dirty_cont(Scn, W, Pos, tl(Rest)); + %% We need to carry on if the token is known to be dirty, or + %% if we aren't at the same place that it was scanned from + %% before + Next#token.dirty == true; + Next#token.start /= Pos -> + rescan_dirty(Scn, W, Pos, Rest); + true -> + Rest + end. + +make_token(Pos, {ok, T, Acs, LookAhead}) -> + Type = element(1, T), + Len = length(Acs), + #token{type=Type, + start=Pos, + finish=Pos+Len, + lookahead=LookAhead}; +make_token(Pos, {skip_token, Acs, LookAhead}) -> + Len = length(Acs), + #token{type=em_skipped, + start=Pos, + finish=Pos+Len, + lookahead=LookAhead}. + +make_error_token(Pos) -> + #token{type=em_error, + start=Pos, + finish=Pos+1, + lookahead=1}. + diff --git a/mods/src/em_scheme.erl b/mods/src/em_scheme.erl new file mode 100644 index 0000000..fd4bd14 --- /dev/null +++ b/mods/src/em_scheme.erl @@ -0,0 +1,184 @@ +%%%---------------------------------------------------------------------- +%%% File : em_scheme.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Scheme-mode +%%% Created : 30 Apr 2001 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(em_scheme). +-author('luke@bluetail.com'). + +-include_lib("ermacs/include/edit.hrl"). +-import(edit_lib, [buffer/1]). + +-compile(export_all). +%%-export([Function/Arity, ...]). + +-define(keymap, scheme_mode_map). + +-record(tok, {column, % Column number + line, % Line number + read_ahead, % # chars read ahead + token % Token returned from leex + }). + +mod_init() -> + catch edit_keymap:delete(?keymap), + init_map(), + edit_keymap:global_set_key("C-x s", {?MODULE, scheme_mode, []}), + edit_var:add_to_list(auto_mode_alist, + {"\.scheme$$", {?MODULE, scheme_mode}}), + ok. + +init_map() -> + edit_keymap:new(?keymap), + edit_keymap:bind_each(?keymap, bindings()). + +bindings() -> + [{"C-i", {?MODULE, reindent_cmd, []}} + ]. + +%% test buffer annotation +ann_trace(S0, Cord, Text, Start, End) -> + io:format("trace: ~p at (~p,~p)~n", [Text, Start, End]), + {ok, S0}. + +scheme_mode(State) -> + Mode = #mode{name="Scheme", + id=scheme, + keymaps=[?keymap]}, + Buf = buffer(State), + Scanner = em_scan:make_scheme_scanner(), + edit_buf:add_annotation(Buf, scan, {em_scan, scan_annotation, [Scanner]}, + no_scan), + %%edit_buf:add_annotation(Buf, scheme, {?MODULE, ann_trace, []}, []), + edit_buf:set_mode(Buf, Mode), + State. + +scan_buffer(State) -> + B = buffer(State), + C = edit_buf:get_cord(B), + Walker = cord:walker(C), + Scanner = make_scheme_scanner(), + case em_scan:edit_scan(Scanner, Walker) of + {error, Rsn} -> + edit_util:status_msg(State, "Error: ~s", + [em_scheme_scan:format_error(Rsn)]); + {ok, Toks} -> + edit_util:status_msg(State, "Scan: (~p) ~s", + [length(Toks), format_tokens(Toks)]) + end. + +make_scheme_scanner() -> + em_scan:make_scanner(em_scheme_scan:yystate(), + {em_scheme_scan, yystate}, + {em_scheme_scan, yyaction}). + +format_tokens([A,B|T]) -> + format_token(A) ++ ", " ++ format_tokens([B|T]); +format_tokens([A]) -> + format_token(A). + +format_token({T, C, L}) -> + io_lib:format("~p:~p:~p", [T, C, L]). + +scan(W) -> + em_scan:edit_scan(make_scheme_scanner(), W). + +%%%---------------------------------------------------------------------- +%%% Reindent + +reindent_cmd(State) -> + B = buffer(State), + Start = beginning_of_fun_pos(B), + End = edit_lib:beginning_of_line_pos(B), + Region = edit_buf:get_region_cord(B, Start, End), + Walker = cord:walker(Region), + {ok, Scan} = scan(Walker), + reindent(B, End, calc_indent(munge(Scan))). + +munge([A = {_,_,LineA}, B = {_,_,LineB} | T]) when LineA /= LineB -> + [strip(A), newline | munge([B|T])]; +munge([H|T]) -> + [strip(H) | munge(T)]; +munge([]) -> + [newline]. + +strip({Type, Col, Line}) -> {Type, Col}. + +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). + +beginning_of_fun_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("^\\(", Cord, Point, backward) of + nomatch -> + 1; + {match, Start, End} -> + Start + end. + +%% Calculate the indent level for the line *following* the tokens `Toks'. +calc_indent(Toks) -> + io:format("calc_indent: ~p~n", [Toks]), + calc_indent(Toks, 0, []). + +%% ( s : (lambda +%% push; |(|+2 +calc_indent([{'(', OC},{special,SC}|T], Lvl0, S0) -> + S1 = [Lvl0|S0], + calc_indent(T, OC+2, S1); +%% ( a:!\n b:!\n +%% push; |b| +calc_indent([{'(',OC}, {_,_}, {_,C}|T], Lvl0, S0) -> + S1 = [Lvl0|S0], + calc_indent(T, C, S1); +calc_indent([{'(',OC}|T], Lvl0, S0) -> + S1 = [Lvl0|S0], + calc_indent(T, OC+1, S1); +calc_indent([{')',CC}|T], Lvl0, S0) -> + [Lvl1|S1] = S0, + calc_indent(T, Lvl1, S1); +calc_indent([H|T], Lvl, S) -> + calc_indent(T, Lvl, S); +calc_indent([], Lvl, S) -> + Lvl. + +%% calc_indent(Tokens, CurrentLevel, State, Stack) +%% State = normal | special +%% Stack = [{State, Level}] + + +%% Opening + +% calc_indent([{'(',COpen}, {special, CSpecial}|T], Lvl0, S0, Stk0) -> +% %% opening a special form +% calc_indent(T, COpen + 2, special, [{S0, Lvl0}|Stk0]); +% calc_indent([{'(',COpen}|T], Lvl0, S0, Stk0) -> +% calc_indent(T, COpen+1, normal, [{S0, Lvl0}|Stk0]); +% calc_indent([{')', _}|T], Lvl0, S, [{SPrev,LvlPrev}|Stk]) -> +% calc_indent(T, LvlPrev, SPrev, Stk); +% calc_indent([{')', _}|T], Lvl0, S, []) -> +% %% Too many )'s - go back to 0 +% calc_indent(T, 0, normal, []); +% calc_indent([{Symbol, SC}, newline | T], Lvl0, normal, Stk0) +% when Symbol == atom; Symbol == special -> +% calc_indent(T, SC, normal0, Stk0); +% calc_indent([{atom, _}|T], Lvl0, S0, Stk0) -> +% calc_indent(T, Lvl0, S0, Stk0); +% calc_indent([{special, _}|T], Lvl0, S0, Stk0) -> +% calc_indent(T, Lvl0, S0, Stk0); +% calc_indent([newline|T], Lvl, S, Stk) -> +% calc_indent(T, Lvl, S, Stk); +% calc_indent([], Lvl, S, Stk) -> +% Lvl. + +max(X, Y) when X > Y -> X; +max(X, Y) -> Y. + +min(X, Y) when X < Y -> X; +min(X, Y) -> Y. diff --git a/mods/src/em_scheme_scan.erl b/mods/src/em_scheme_scan.erl new file mode 100644 index 0000000..8021791 --- /dev/null +++ b/mods/src/em_scheme_scan.erl @@ -0,0 +1,531 @@ +%% THIS IS A PRE-RELEASE OF LEEX - RELEASED ONLY BECAUSE MANY PEOPLE +%% WANTED IT - THE OFFICIAL RELEASE WILL PROVIDE A DIFFERENT INCOMPATIBLE +%% AND BETTER INTERFACE - BE WARNED +%% PLEASE REPORT ALL BUGS TO THE AUTHOR. + +-module('em_scheme_scan'). + +-export([string/1,string/2,token/2,token/3,tokens/2,tokens/3]). +-export([format_error/1]). + +%% luke +-export([yystate/0, yystate/6, yyaction/4]). + +%% User code. This is placed here to allow extra attributes. + +format_error({illegal,S}) -> ["illegal characters ",io_lib:write_string(S)]; +format_error({user,S}) -> S. + +string(String) -> string(String, 1). + +string(String, Line) -> string(String, Line, String, []). + +%% string(InChars, Line, TokenChars, Tokens) -> +%% {ok,Tokens,Line} | {error,ErrorInfo,Line}. + +string([], L, [], Ts) -> %No partial tokens! + {ok,yyrev(Ts),L}; +string(Ics0, L0, Tcs, Ts) -> + case yystate(yystate(), Ics0, L0, 0, reject, 0) of + {A,Alen,Ics1,L1} -> %Accepting end state + string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L1), Ts); + {A,Alen,Ics1,L1,S1} -> %After an accepting state + string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L1), Ts); + {reject,Alen,Tlen,Ics1,L1,S1} -> + {error,{L1,?MODULE,{illegal,yypre(Tcs, Tlen+1)}},L1}; + {A,Alen,Tlen,Ics1,L1,S1} -> + string_cont(yysuf(Tcs, Alen), L1, yyaction(A, Alen, Tcs, L1), Ts) + end. + +%% string_cont(RestChars, Line, Token, Tokens) +%% Test for and remove the end token wrapper. + +string_cont(Rest, Line, {token,T}, Ts) -> + string(Rest, Line, Rest, [T|Ts]); +string_cont(Rest, Line, {end_token,T}, Ts) -> + string(Rest, Line, Rest, [T|Ts]); +string_cont(Rest, Line, skip_token, Ts) -> + string(Rest, Line, Rest, Ts); +string_cont(Rest, Line, {error,S}, Ts) -> + {error,{Line,?MODULE,{user,S}},Line}. + +%% token(Continuation, Chars, Line) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. +%% Must be careful when re-entering to append the latest characters to the +%% after characters in an accept. + +token(Cont, Chars) -> token(Cont, Chars, 1). + +token([], Chars, Line) -> + token(Chars, Line, yystate(), Chars, 0, reject, 0); +token({Line,State,Tcs,Tlen,Action,Alen}, Chars, _) -> + token(Chars, Line, State, Tcs ++ Chars, Tlen, Action, Alen). + +%% token(InChars, Line, State, TokenChars, TokenLen, Accept) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +token(Ics0, L0, S0, Tcs, Tlen0, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + {A1,Alen1,Ics1,L1} -> %Accepting end state + token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1)); + {A1,Alen1,[],L1,S1} -> %After an accepting state + {more,{L1,S1,Tcs,Alen1,A1,Alen1}}; + {A1,Alen1,Ics1,L1,S1} -> + token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1)); + {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state + {more,{L1,S1,Tcs,Tlen1,A1,Alen1}}; + {reject,Alen1,Tlen1,eof,L1,S1} -> + {done,{eof,L1},[]}; + {reject,Alen1,Tlen1,Ics1,L1,S1} -> + {done,{error,{L1,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}},L1},Ics1}; + {A1,Alen1,Tlen1,Ics1,L1,S1} -> + token_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1)) + end. + +%% tokens_cont(RestChars, Line, Token) +%% Test if we have detected the end token, if so return done else continue. + +token_cont(Rest, Line, {token,T}) -> + {done,{ok,T,Line},Rest}; +token_cont(Rest, Line, {end_token,T}) -> + {done,{ok,T,Line},Rest}; +token_cont(Rest, Line, skip_token) -> + token(Rest, Line, yystate(), Rest, 0, reject, 0); +token_cont(Rest, Line, {error,S}) -> + {done,{error,{Line,?MODULE,{user,S}},Line},Rest}. + +%% tokens(Continuation, Chars, Line) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. +%% Must be careful when re-entering to append the latest characters to the +%% after characters in an accept. + +tokens(Cont, Chars) -> tokens(Cont, Chars, 1). + +tokens([], Chars, Line) -> + tokens(Chars, Line, yystate(), Chars, 0, [], reject, 0); +tokens({tokens,Line,State,Tcs,Tlen,Ts,Action,Alen}, Chars, _) -> + tokens(Chars, Line, State, Tcs ++ Chars, Tlen, Ts, Action, Alen); +tokens({skip_tokens,Line,State,Tcs,Tlen,Error,Action,Alen}, Chars, _) -> + skip_tokens(Chars, Line, State, Tcs ++ Chars, Tlen, Error, Action, Alen). + +%% tokens(InChars, Line, State, TokenChars, TokenLen, Tokens, Accept) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +tokens(Ics0, L0, S0, Tcs, Tlen0, Ts, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + {A1,Alen1,Ics1,L1} -> %Accepting end state + tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Ts); + {A1,Alen1,[],L1,S1} -> %After an accepting state + {more,{tokens,L1,S1,Tcs,Alen1,Ts,A1,Alen1}}; + {A1,Alen1,Ics1,L1,S1} -> + tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Ts); + {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state + {more,{tokens,L1,S1,Tcs,Tlen1,Ts,A1,Alen1}}; + {reject,Alen1,Tlen1,eof,L1,S1} -> + {done,if Ts == [] -> {eof,L1}; + true -> {ok,yyrev(Ts),L1} end,[]}; + {reject,Alen1,Tlen1,Ics1,L1,S1} -> + skip_tokens(yysuf(Tcs, Tlen1+1), L1, + {L1,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}}); + {A1,Alen1,Tlen1,Ics1,L1,S1} -> + tokens_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1), Ts) + end. + +%% tokens_cont(RestChars, Line, Token, Tokens) +%% Test if we have detected the end token, if so return done else continue. + +tokens_cont(Rest, Line, {token,T}, Ts) -> + tokens(Rest, Line, yystate(), Rest, 0, [T|Ts], reject, 0); +tokens_cont(Rest, Line, {end_token,T}, Ts) -> + {done,{ok,yyrev(Ts, [T]),Line},Rest}; +tokens_cont(Rest, Line, skip_token, Ts) -> + tokens(Rest, Line, yystate(), Rest, 0, Ts, reject, 0); +tokens_cont(Rest, Line, {error,S}, Ts) -> + skip_tokens(Rest, Line, {Line,?MODULE,{user,S}}). + +%% token_skip(InChars, Line, Error) -> {done,ReturnVal,RestChars}. +%% Skip tokens until an end token, junk everything and return the error. + +%%skip_tokens(Ics, Line, Error) -> {done,{error,Error,Line},Ics}. + +skip_tokens(Ics, Line, Error) -> + skip_tokens(Ics, Line, yystate(), Ics, 0, Error, reject, 0). + +%% skip_tokens(InChars, Line, State, TokenChars, TokenLen, Tokens, Accept) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +skip_tokens(Ics0, L0, S0, Tcs, Tlen0, Error, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + {A1,Alen1,Ics1,L1} -> %Accepting end state + skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Error); + {A1,Alen1,[],L1,S1} -> %After an accepting state + {more,{skip_tokens,L1,S1,Tcs,Alen1,Error,A1,Alen1}}; + {A1,Alen1,Ics1,L1,S1} -> + skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Error); + {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state + {more,{skip_tokens,L1,S1,Tcs,Tlen1,Error,A1,Alen1}}; + {reject,Alen1,Tlen1,eof,L1,S1} -> + {done,{error,Error,L1},[]}; + {reject,Alen1,Tlen1,Ics1,L1,S1} -> + skip_tokens(yysuf(Tcs, Tlen1), L1, Error); + {A1,Alen1,Tlen1,Ics1,L1,S1} -> + skip_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1), Error) + end. + +%% skip_cont(RestChars, Line, Token, Error) +%% Test if we have detected the end token, if so return done else continue. + +skip_cont(Rest, Line, {token,T}, Error) -> + skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0); +skip_cont(Rest, Line, {end_token,T}, Error) -> + {done,{error,Error,Line},Rest}; +skip_cont(Rest, Line, {error,S}, Error) -> + skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0); +skip_cont(Rest, Line, skip_token, Error) -> + skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0). + +yyrev(L) -> yyrev(L, []). + +yyrev([H|T], Acc) -> yyrev(T, [H|Acc]); +yyrev([], Acc) -> Acc. + +yypre([H|T], N) when N > 0 -> [H|yypre(T, N-1)]; +yypre(L, N) -> []. + +yysuf([H|T], N) when N > 0 -> yysuf(T, N-1); +yysuf(L, 0) -> L. + +yysplit(L, N) -> yysplit(L, N, []). +yysplit([H|T], N, Acc) when N > 0 -> yysplit(T, N-1, [H|Acc]); +yysplit(L, 0, Acc) -> {lists:reverse(Acc), L}. + +%% yystate() -> InitialState. +%% yystate(State, InChars, Line, Token, ) -> +%% {Action, AcceptLength, RestChars, Line} | Accepting end state +%% {Action, AcceptLength, RestChars, Line, State} | Accepting state +%% {Action, AcceptLength, TokLength, RestChars, Line, State} | +%% {reject, AcceptLength, TokLength, RestChars, Line, State}. +%% Generated state transition functions. + +yystate() -> 15. + +yystate(18, [$\v|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 3, Tlen); +yystate(18, [$\f|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 3, Tlen); +yystate(18, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(17, Ics, Line, Tlen+1, 3, Tlen); +yystate(18, [C|Ics], Line, Tlen, Action, Alen) when C >= $\016, C =< $\037 -> + yystate(17, Ics, Line, Tlen+1, 3, Tlen); +yystate(18, [C|Ics], Line, Tlen, Action, Alen) when C >= $!, C =< $' -> + yystate(17, Ics, Line, Tlen+1, 3, Tlen); +yystate(18, [C|Ics], Line, Tlen, Action, Alen) when C >= $*, C =< $ÿ -> + yystate(17, Ics, Line, Tlen+1, 3, Tlen); +yystate(18, Ics, Line, Tlen, Action, Alen) -> + {3,Tlen,Ics,Line,18}; +yystate(17, [$\v|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(17, [$\f|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(17, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(17, [C|Ics], Line, Tlen, Action, Alen) when C >= $\016, C =< $\037 -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(17, [C|Ics], Line, Tlen, Action, Alen) when C >= $!, C =< $' -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(17, [C|Ics], Line, Tlen, Action, Alen) when C >= $*, C =< $ÿ -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(17, Ics, Line, Tlen, Action, Alen) -> + {5,Tlen,Ics,Line,17}; +yystate(16, [$\v|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(16, [$\f|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(16, [$e|Ics], Line, Tlen, Action, Alen) -> + yystate(18, Ics, Line, Tlen+1, 5, Tlen); +yystate(16, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(16, [C|Ics], Line, Tlen, Action, Alen) when C >= $\016, C =< $\037 -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(16, [C|Ics], Line, Tlen, Action, Alen) when C >= $!, C =< $' -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(16, [C|Ics], Line, Tlen, Action, Alen) when C >= $*, C =< $d -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(16, [C|Ics], Line, Tlen, Action, Alen) when C >= $f, C =< $ÿ -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(16, Ics, Line, Tlen, Action, Alen) -> + {5,Tlen,Ics,Line,16}; +yystate(15, [$\n|Ics], Line, Tlen, Action, Alen) -> + yystate(11, Ics, Line+1, Tlen+1, Action, Alen); +yystate(15, [$\v|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, Action, Alen); +yystate(15, [$\f|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, Action, Alen); +yystate(15, [$\r|Ics], Line, Tlen, Action, Alen) -> + yystate(11, Ics, Line, Tlen+1, Action, Alen); +yystate(15, [$\s|Ics], Line, Tlen, Action, Alen) -> + yystate(11, Ics, Line, Tlen+1, Action, Alen); +yystate(15, [$(|Ics], Line, Tlen, Action, Alen) -> + yystate(7, Ics, Line, Tlen+1, Action, Alen); +yystate(15, [$)|Ics], Line, Tlen, Action, Alen) -> + yystate(3, Ics, Line, Tlen+1, Action, Alen); +yystate(15, [$d|Ics], Line, Tlen, Action, Alen) -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(15, [$l|Ics], Line, Tlen, Action, Alen) -> + yystate(14, Ics, Line, Tlen+1, Action, Alen); +yystate(15, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(17, Ics, Line, Tlen+1, Action, Alen); +yystate(15, [C|Ics], Line, Tlen, Action, Alen) when C >= $\016, C =< $\037 -> + yystate(17, Ics, Line, Tlen+1, Action, Alen); +yystate(15, [C|Ics], Line, Tlen, Action, Alen) when C >= $!, C =< $' -> + yystate(17, Ics, Line, Tlen+1, Action, Alen); +yystate(15, [C|Ics], Line, Tlen, Action, Alen) when C >= $*, C =< $c -> + yystate(17, Ics, Line, Tlen+1, Action, Alen); +yystate(15, [C|Ics], Line, Tlen, Action, Alen) when C >= $e, C =< $k -> + yystate(17, Ics, Line, Tlen+1, Action, Alen); +yystate(15, [C|Ics], Line, Tlen, Action, Alen) when C >= $m, C =< $ÿ -> + yystate(17, Ics, Line, Tlen+1, Action, Alen); +yystate(15, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,15}; +yystate(14, [$\v|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(14, [$\f|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(14, [$a|Ics], Line, Tlen, Action, Alen) -> + yystate(10, Ics, Line, Tlen+1, 5, Tlen); +yystate(14, [$e|Ics], Line, Tlen, Action, Alen) -> + yystate(9, Ics, Line, Tlen+1, 5, Tlen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= $\016, C =< $\037 -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= $!, C =< $' -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= $*, C =< $` -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= $b, C =< $d -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= $f, C =< $ÿ -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(14, Ics, Line, Tlen, Action, Alen) -> + {5,Tlen,Ics,Line,14}; +yystate(13, [$\v|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 4, Tlen); +yystate(13, [$\f|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 4, Tlen); +yystate(13, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(17, Ics, Line, Tlen+1, 4, Tlen); +yystate(13, [C|Ics], Line, Tlen, Action, Alen) when C >= $\016, C =< $\037 -> + yystate(17, Ics, Line, Tlen+1, 4, Tlen); +yystate(13, [C|Ics], Line, Tlen, Action, Alen) when C >= $!, C =< $' -> + yystate(17, Ics, Line, Tlen+1, 4, Tlen); +yystate(13, [C|Ics], Line, Tlen, Action, Alen) when C >= $*, C =< $ÿ -> + yystate(17, Ics, Line, Tlen+1, 4, Tlen); +yystate(13, Ics, Line, Tlen, Action, Alen) -> + {4,Tlen,Ics,Line,13}; +yystate(12, [$\v|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(12, [$\f|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(12, [$n|Ics], Line, Tlen, Action, Alen) -> + yystate(16, Ics, Line, Tlen+1, 5, Tlen); +yystate(12, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(12, [C|Ics], Line, Tlen, Action, Alen) when C >= $\016, C =< $\037 -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(12, [C|Ics], Line, Tlen, Action, Alen) when C >= $!, C =< $' -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(12, [C|Ics], Line, Tlen, Action, Alen) when C >= $*, C =< $m -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(12, [C|Ics], Line, Tlen, Action, Alen) when C >= $o, C =< $ÿ -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(12, Ics, Line, Tlen, Action, Alen) -> + {5,Tlen,Ics,Line,12}; +yystate(11, Ics, Line, Tlen, Action, Alen) -> + {6,Tlen,Ics,Line}; +yystate(10, [$\v|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(10, [$\f|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(10, [$m|Ics], Line, Tlen, Action, Alen) -> + yystate(6, Ics, Line, Tlen+1, 5, Tlen); +yystate(10, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(10, [C|Ics], Line, Tlen, Action, Alen) when C >= $\016, C =< $\037 -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(10, [C|Ics], Line, Tlen, Action, Alen) when C >= $!, C =< $' -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(10, [C|Ics], Line, Tlen, Action, Alen) when C >= $*, C =< $l -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(10, [C|Ics], Line, Tlen, Action, Alen) when C >= $n, C =< $ÿ -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(10, Ics, Line, Tlen, Action, Alen) -> + {5,Tlen,Ics,Line,10}; +yystate(9, [$\v|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(9, [$\f|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(9, [$t|Ics], Line, Tlen, Action, Alen) -> + yystate(13, Ics, Line, Tlen+1, 5, Tlen); +yystate(9, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(9, [C|Ics], Line, Tlen, Action, Alen) when C >= $\016, C =< $\037 -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(9, [C|Ics], Line, Tlen, Action, Alen) when C >= $!, C =< $' -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(9, [C|Ics], Line, Tlen, Action, Alen) when C >= $*, C =< $s -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(9, [C|Ics], Line, Tlen, Action, Alen) when C >= $u, C =< $ÿ -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(9, Ics, Line, Tlen, Action, Alen) -> + {5,Tlen,Ics,Line,9}; +yystate(8, [$\v|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(8, [$\f|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(8, [$i|Ics], Line, Tlen, Action, Alen) -> + yystate(12, Ics, Line, Tlen+1, 5, Tlen); +yystate(8, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(8, [C|Ics], Line, Tlen, Action, Alen) when C >= $\016, C =< $\037 -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(8, [C|Ics], Line, Tlen, Action, Alen) when C >= $!, C =< $' -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(8, [C|Ics], Line, Tlen, Action, Alen) when C >= $*, C =< $h -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(8, [C|Ics], Line, Tlen, Action, Alen) when C >= $j, C =< $ÿ -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(8, Ics, Line, Tlen, Action, Alen) -> + {5,Tlen,Ics,Line,8}; +yystate(7, Ics, Line, Tlen, Action, Alen) -> + {0,Tlen,Ics,Line}; +yystate(6, [$\v|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(6, [$\f|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(6, [$b|Ics], Line, Tlen, Action, Alen) -> + yystate(2, Ics, Line, Tlen+1, 5, Tlen); +yystate(6, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(6, [C|Ics], Line, Tlen, Action, Alen) when C >= $\016, C =< $\037 -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(6, [C|Ics], Line, Tlen, Action, Alen) when C >= $!, C =< $' -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(6, [C|Ics], Line, Tlen, Action, Alen) when C >= $*, C =< $a -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(6, [C|Ics], Line, Tlen, Action, Alen) when C >= $c, C =< $ÿ -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(6, Ics, Line, Tlen, Action, Alen) -> + {5,Tlen,Ics,Line,6}; +yystate(5, [$\v|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 2, Tlen); +yystate(5, [$\f|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 2, Tlen); +yystate(5, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(17, Ics, Line, Tlen+1, 2, Tlen); +yystate(5, [C|Ics], Line, Tlen, Action, Alen) when C >= $\016, C =< $\037 -> + yystate(17, Ics, Line, Tlen+1, 2, Tlen); +yystate(5, [C|Ics], Line, Tlen, Action, Alen) when C >= $!, C =< $' -> + yystate(17, Ics, Line, Tlen+1, 2, Tlen); +yystate(5, [C|Ics], Line, Tlen, Action, Alen) when C >= $*, C =< $ÿ -> + yystate(17, Ics, Line, Tlen+1, 2, Tlen); +yystate(5, Ics, Line, Tlen, Action, Alen) -> + {2,Tlen,Ics,Line,5}; +yystate(4, [$\v|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(4, [$\f|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(4, [$f|Ics], Line, Tlen, Action, Alen) -> + yystate(8, Ics, Line, Tlen+1, 5, Tlen); +yystate(4, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(4, [C|Ics], Line, Tlen, Action, Alen) when C >= $\016, C =< $\037 -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(4, [C|Ics], Line, Tlen, Action, Alen) when C >= $!, C =< $' -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(4, [C|Ics], Line, Tlen, Action, Alen) when C >= $*, C =< $e -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(4, [C|Ics], Line, Tlen, Action, Alen) when C >= $g, C =< $ÿ -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(4, Ics, Line, Tlen, Action, Alen) -> + {5,Tlen,Ics,Line,4}; +yystate(3, Ics, Line, Tlen, Action, Alen) -> + {1,Tlen,Ics,Line}; +yystate(2, [$\v|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(2, [$\f|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(2, [$d|Ics], Line, Tlen, Action, Alen) -> + yystate(1, Ics, Line, Tlen+1, 5, Tlen); +yystate(2, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(2, [C|Ics], Line, Tlen, Action, Alen) when C >= $\016, C =< $\037 -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(2, [C|Ics], Line, Tlen, Action, Alen) when C >= $!, C =< $' -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(2, [C|Ics], Line, Tlen, Action, Alen) when C >= $*, C =< $c -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(2, [C|Ics], Line, Tlen, Action, Alen) when C >= $e, C =< $ÿ -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(2, Ics, Line, Tlen, Action, Alen) -> + {5,Tlen,Ics,Line,2}; +yystate(1, [$\v|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(1, [$\f|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(1, [$a|Ics], Line, Tlen, Action, Alen) -> + yystate(5, Ics, Line, Tlen+1, 5, Tlen); +yystate(1, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(1, [C|Ics], Line, Tlen, Action, Alen) when C >= $\016, C =< $\037 -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(1, [C|Ics], Line, Tlen, Action, Alen) when C >= $!, C =< $' -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(1, [C|Ics], Line, Tlen, Action, Alen) when C >= $*, C =< $` -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(1, [C|Ics], Line, Tlen, Action, Alen) when C >= $b, C =< $ÿ -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(1, Ics, Line, Tlen, Action, Alen) -> + {5,Tlen,Ics,Line,1}; +yystate(0, [$\v|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(0, [$\f|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(0, [$e|Ics], Line, Tlen, Action, Alen) -> + yystate(4, Ics, Line, Tlen+1, 5, Tlen); +yystate(0, [C|Ics], Line, Tlen, Action, Alen) when C >= $\000, C =< $\t -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(0, [C|Ics], Line, Tlen, Action, Alen) when C >= $\016, C =< $\037 -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(0, [C|Ics], Line, Tlen, Action, Alen) when C >= $!, C =< $' -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(0, [C|Ics], Line, Tlen, Action, Alen) when C >= $*, C =< $d -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(0, [C|Ics], Line, Tlen, Action, Alen) when C >= $f, C =< $ÿ -> + yystate(17, Ics, Line, Tlen+1, 5, Tlen); +yystate(0, Ics, Line, Tlen, Action, Alen) -> + {5,Tlen,Ics,Line,0}; +yystate(S, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,S}. + + +%% yyaction(Action, TokenLength, TokenChars, Line) -> +%% {token,Token} | {end_token, Token} | skip_token | {error,String}. +%% Generated action function. + +yyaction(0, YYlen, YYtcs, YYline) -> + {token,{'(',YYline}}; +yyaction(1, YYlen, YYtcs, YYline) -> + {token,{')',YYline}}; +yyaction(2, YYlen, YYtcs, YYline) -> + {token,{special,YYline}}; +yyaction(3, YYlen, YYtcs, YYline) -> + {token,{special,YYline}}; +yyaction(4, YYlen, YYtcs, YYline) -> + {token,{special,YYline}}; +yyaction(5, YYlen, YYtcs, YYline) -> + YYtext = yypre(YYtcs, YYlen), + {token,{atom,YYline,YYtext}}; +yyaction(6, YYlen, YYtcs, YYline) -> + skip_token; +yyaction(_, _, _, _) -> error. diff --git a/mods/src/em_scheme_scan.xrl b/mods/src/em_scheme_scan.xrl new file mode 100644 index 0000000..632cf13 --- /dev/null +++ b/mods/src/em_scheme_scan.xrl @@ -0,0 +1,18 @@ +Definitions. + +AtomChar = [^\s\r\n\(\)] +WS = [\s\r\n] + +Rules. + +\( : {token, {'(', YYline}}. +\) : {token, {')', YYline}}. + +lambda : {token, {special, YYline}}. +define : {token, {special, YYline}}. +let : {token, {special, YYline}}. + +({AtomChar}{AtomChar}*) : {token, {atom, YYline, YYtext}}. +%% Ignore +{WS} : skip_token. + diff --git a/mods/src/em_stdlib.erl b/mods/src/em_stdlib.erl new file mode 100644 index 0000000..1bc4687 --- /dev/null +++ b/mods/src/em_stdlib.erl @@ -0,0 +1,21 @@ +%%%---------------------------------------------------------------------- +%%% File : em_stdlib.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Module for loading the standard library of editor commands. +%%% This module is loaded/initialised on editor startup. +%%% Created : 29 Apr 2001 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(em_stdlib). +-author('luke@bluetail.com'). + +-export([mod_init/0]). + +mod_init() -> + lists:foreach(fun(Mod) -> edit_mod:require(Mod) end, + mods()). + +%% List of modules to be initialised at start-up. +mods() -> + [em_erlang, em_scheme]. + diff --git a/mods/src/em_test_scan.erl b/mods/src/em_test_scan.erl new file mode 100644 index 0000000..4a39ca3 --- /dev/null +++ b/mods/src/em_test_scan.erl @@ -0,0 +1,255 @@ +%% THIS IS A PRE-RELEASE OF LEEX - RELEASED ONLY BECAUSE MANY PEOPLE +%% WANTED IT - THE OFFICIAL RELEASE WILL PROVIDE A DIFFERENT INCOMPATIBLE +%% AND BETTER INTERFACE - BE WARNED +%% PLEASE REPORT ALL BUGS TO THE AUTHOR. + +-module('em_test_scan'). + +-export([string/1,string/2,token/2,token/3,tokens/2,tokens/3]). +-export([format_error/1]). + +%% luke +-export([yystate/0, yystate/6, yyaction/4]). + +%% User code. This is placed here to allow extra attributes. + +format_error({illegal,S}) -> ["illegal characters ",io_lib:write_string(S)]; +format_error({user,S}) -> S. + +string(String) -> string(String, 1). + +string(String, Line) -> string(String, Line, String, []). + +%% string(InChars, Line, TokenChars, Tokens) -> +%% {ok,Tokens,Line} | {error,ErrorInfo,Line}. + +string([], L, [], Ts) -> %No partial tokens! + {ok,yyrev(Ts),L}; +string(Ics0, L0, Tcs, Ts) -> + case yystate(yystate(), Ics0, L0, 0, reject, 0) of + {A,Alen,Ics1,L1} -> %Accepting end state + string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L1), Ts); + {A,Alen,Ics1,L1,S1} -> %After an accepting state + string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L1), Ts); + {reject,Alen,Tlen,Ics1,L1,S1} -> + {error,{L1,?MODULE,{illegal,yypre(Tcs, Tlen+1)}},L1}; + {A,Alen,Tlen,Ics1,L1,S1} -> + string_cont(yysuf(Tcs, Alen), L1, yyaction(A, Alen, Tcs, L1), Ts) + end. + +%% string_cont(RestChars, Line, Token, Tokens) +%% Test for and remove the end token wrapper. + +string_cont(Rest, Line, {token,T}, Ts) -> + string(Rest, Line, Rest, [T|Ts]); +string_cont(Rest, Line, {end_token,T}, Ts) -> + string(Rest, Line, Rest, [T|Ts]); +string_cont(Rest, Line, skip_token, Ts) -> + string(Rest, Line, Rest, Ts); +string_cont(Rest, Line, {error,S}, Ts) -> + {error,{Line,?MODULE,{user,S}},Line}. + +%% token(Continuation, Chars, Line) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. +%% Must be careful when re-entering to append the latest characters to the +%% after characters in an accept. + +token(Cont, Chars) -> token(Cont, Chars, 1). + +token([], Chars, Line) -> + token(Chars, Line, yystate(), Chars, 0, reject, 0); +token({Line,State,Tcs,Tlen,Action,Alen}, Chars, _) -> + token(Chars, Line, State, Tcs ++ Chars, Tlen, Action, Alen). + +%% token(InChars, Line, State, TokenChars, TokenLen, Accept) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +token(Ics0, L0, S0, Tcs, Tlen0, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + {A1,Alen1,Ics1,L1} -> %Accepting end state + token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1)); + {A1,Alen1,[],L1,S1} -> %After an accepting state + {more,{L1,S1,Tcs,Alen1,A1,Alen1}}; + {A1,Alen1,Ics1,L1,S1} -> + token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1)); + {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state + {more,{L1,S1,Tcs,Tlen1,A1,Alen1}}; + {reject,Alen1,Tlen1,eof,L1,S1} -> + {done,{eof,L1},[]}; + {reject,Alen1,Tlen1,Ics1,L1,S1} -> + {done,{error,{L1,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}},L1},Ics1}; + {A1,Alen1,Tlen1,Ics1,L1,S1} -> + token_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1)) + end. + +%% tokens_cont(RestChars, Line, Token) +%% Test if we have detected the end token, if so return done else continue. + +token_cont(Rest, Line, {token,T}) -> + {done,{ok,T,Line},Rest}; +token_cont(Rest, Line, {end_token,T}) -> + {done,{ok,T,Line},Rest}; +token_cont(Rest, Line, skip_token) -> + token(Rest, Line, yystate(), Rest, 0, reject, 0); +token_cont(Rest, Line, {error,S}) -> + {done,{error,{Line,?MODULE,{user,S}},Line},Rest}. + +%% tokens(Continuation, Chars, Line) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. +%% Must be careful when re-entering to append the latest characters to the +%% after characters in an accept. + +tokens(Cont, Chars) -> tokens(Cont, Chars, 1). + +tokens([], Chars, Line) -> + tokens(Chars, Line, yystate(), Chars, 0, [], reject, 0); +tokens({tokens,Line,State,Tcs,Tlen,Ts,Action,Alen}, Chars, _) -> + tokens(Chars, Line, State, Tcs ++ Chars, Tlen, Ts, Action, Alen); +tokens({skip_tokens,Line,State,Tcs,Tlen,Error,Action,Alen}, Chars, _) -> + skip_tokens(Chars, Line, State, Tcs ++ Chars, Tlen, Error, Action, Alen). + +%% tokens(InChars, Line, State, TokenChars, TokenLen, Tokens, Accept) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +tokens(Ics0, L0, S0, Tcs, Tlen0, Ts, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + {A1,Alen1,Ics1,L1} -> %Accepting end state + tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Ts); + {A1,Alen1,[],L1,S1} -> %After an accepting state + {more,{tokens,L1,S1,Tcs,Alen1,Ts,A1,Alen1}}; + {A1,Alen1,Ics1,L1,S1} -> + tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Ts); + {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state + {more,{tokens,L1,S1,Tcs,Tlen1,Ts,A1,Alen1}}; + {reject,Alen1,Tlen1,eof,L1,S1} -> + {done,if Ts == [] -> {eof,L1}; + true -> {ok,yyrev(Ts),L1} end,[]}; + {reject,Alen1,Tlen1,Ics1,L1,S1} -> + skip_tokens(yysuf(Tcs, Tlen1+1), L1, + {L1,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}}); + {A1,Alen1,Tlen1,Ics1,L1,S1} -> + tokens_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1), Ts) + end. + +%% tokens_cont(RestChars, Line, Token, Tokens) +%% Test if we have detected the end token, if so return done else continue. + +tokens_cont(Rest, Line, {token,T}, Ts) -> + tokens(Rest, Line, yystate(), Rest, 0, [T|Ts], reject, 0); +tokens_cont(Rest, Line, {end_token,T}, Ts) -> + {done,{ok,yyrev(Ts, [T]),Line},Rest}; +tokens_cont(Rest, Line, skip_token, Ts) -> + tokens(Rest, Line, yystate(), Rest, 0, Ts, reject, 0); +tokens_cont(Rest, Line, {error,S}, Ts) -> + skip_tokens(Rest, Line, {Line,?MODULE,{user,S}}). + +%% token_skip(InChars, Line, Error) -> {done,ReturnVal,RestChars}. +%% Skip tokens until an end token, junk everything and return the error. + +%%skip_tokens(Ics, Line, Error) -> {done,{error,Error,Line},Ics}. + +skip_tokens(Ics, Line, Error) -> + skip_tokens(Ics, Line, yystate(), Ics, 0, Error, reject, 0). + +%% skip_tokens(InChars, Line, State, TokenChars, TokenLen, Tokens, Accept) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +skip_tokens(Ics0, L0, S0, Tcs, Tlen0, Error, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + {A1,Alen1,Ics1,L1} -> %Accepting end state + skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Error); + {A1,Alen1,[],L1,S1} -> %After an accepting state + {more,{skip_tokens,L1,S1,Tcs,Alen1,Error,A1,Alen1}}; + {A1,Alen1,Ics1,L1,S1} -> + skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Error); + {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state + {more,{skip_tokens,L1,S1,Tcs,Tlen1,Error,A1,Alen1}}; + {reject,Alen1,Tlen1,eof,L1,S1} -> + {done,{error,Error,L1},[]}; + {reject,Alen1,Tlen1,Ics1,L1,S1} -> + skip_tokens(yysuf(Tcs, Tlen1), L1, Error); + {A1,Alen1,Tlen1,Ics1,L1,S1} -> + skip_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1), Error) + end. + +%% skip_cont(RestChars, Line, Token, Error) +%% Test if we have detected the end token, if so return done else continue. + +skip_cont(Rest, Line, {token,T}, Error) -> + skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0); +skip_cont(Rest, Line, {end_token,T}, Error) -> + {done,{error,Error,Line},Rest}; +skip_cont(Rest, Line, {error,S}, Error) -> + skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0); +skip_cont(Rest, Line, skip_token, Error) -> + skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0). + +yyrev(L) -> yyrev(L, []). + +yyrev([H|T], Acc) -> yyrev(T, [H|Acc]); +yyrev([], Acc) -> Acc. + +yypre([H|T], N) when N > 0 -> [H|yypre(T, N-1)]; +yypre(L, N) -> []. + +yysuf([H|T], N) when N > 0 -> yysuf(T, N-1); +yysuf(L, 0) -> L. + +yysplit(L, N) -> yysplit(L, N, []). +yysplit([H|T], N, Acc) when N > 0 -> yysplit(T, N-1, [H|Acc]); +yysplit(L, 0, Acc) -> {lists:reverse(Acc), L}. + +%% yystate() -> InitialState. +%% yystate(State, InChars, Line, Token, ) -> +%% {Action, AcceptLength, RestChars, Line} | Accepting end state +%% {Action, AcceptLength, RestChars, Line, State} | Accepting state +%% {Action, AcceptLength, TokLength, RestChars, Line, State} | +%% {reject, AcceptLength, TokLength, RestChars, Line, State}. +%% Generated state transition functions. + +yystate() -> 2. + +yystate(5, [$+|Ics], Line, Tlen, Action, Alen) -> + yystate(3, Ics, Line, Tlen+1, 0, Tlen); +yystate(5, Ics, Line, Tlen, Action, Alen) -> + {0,Tlen,Ics,Line,5}; +yystate(4, [C|Ics], Line, Tlen, Action, Alen) when C >= $a, C =< $z -> + yystate(4, Ics, Line, Tlen+1, 4, Tlen); +yystate(4, Ics, Line, Tlen, Action, Alen) -> + {4,Tlen,Ics,Line,4}; +yystate(3, Ics, Line, Tlen, Action, Alen) -> + {1,Tlen,Ics,Line}; +yystate(2, [$\s|Ics], Line, Tlen, Action, Alen) -> + yystate(1, Ics, Line, Tlen+1, Action, Alen); +yystate(2, [$+|Ics], Line, Tlen, Action, Alen) -> + yystate(5, Ics, Line, Tlen+1, Action, Alen); +yystate(2, [$-|Ics], Line, Tlen, Action, Alen) -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(2, [C|Ics], Line, Tlen, Action, Alen) when C >= $a, C =< $z -> + yystate(4, Ics, Line, Tlen+1, Action, Alen); +yystate(2, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,2}; +yystate(1, Ics, Line, Tlen, Action, Alen) -> + {3,Tlen,Ics,Line}; +yystate(0, Ics, Line, Tlen, Action, Alen) -> + {2,Tlen,Ics,Line}; +yystate(S, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,S}. + + +%% yyaction(Action, TokenLength, TokenChars, Line) -> +%% {token,Token} | {end_token, Token} | skip_token | {error,String}. +%% Generated action function. + +yyaction(0, YYlen, YYtcs, YYline) -> + {token,{'+',YYline}}; +yyaction(1, YYlen, YYtcs, YYline) -> + {token,{'++',YYline}}; +yyaction(2, YYlen, YYtcs, YYline) -> + {token,{'-',YYline}}; +yyaction(3, YYlen, YYtcs, YYline) -> + skip_token; +yyaction(4, YYlen, YYtcs, YYline) -> + YYtext = yypre(YYtcs, YYlen), + {token,{atom,YYline,YYtext}}; +yyaction(_, _, _, _) -> error. diff --git a/mods/src/em_test_scan.xrl b/mods/src/em_test_scan.xrl new file mode 100644 index 0000000..05ac6bb --- /dev/null +++ b/mods/src/em_test_scan.xrl @@ -0,0 +1,12 @@ +Definitions. + +Ch = [a-z] + +Rules. + +\+ : {token, {'+', YYline}}. +\+\+ : {token, {'++', YYline}}. +- : {token, {'-', YYline}}. +\s : skip_token. +({Ch}{Ch}*) : {token, {atom, YYline, YYtext}}. + diff --git a/mods/src/leex.hrl b/mods/src/leex.hrl new file mode 100644 index 0000000..4845418 --- /dev/null +++ b/mods/src/leex.hrl @@ -0,0 +1,217 @@ +%% THIS IS A PRE-RELEASE OF LEEX - RELEASED ONLY BECAUSE MANY PEOPLE +%% WANTED IT - THE OFFICIAL RELEASE WILL PROVIDE A DIFFERENT INCOMPATIBLE +%% AND BETTER INTERFACE - BE WARNED +%% PLEASE REPORT ALL BUGS TO THE AUTHOR. + +##module + +-export([string/1,string/2,token/2,token/3,tokens/2,tokens/3]). +-export([format_error/1]). + +%% luke +-export([yystate/0, yystate/6, yyaction/4]). + +%% User code. This is placed here to allow extra attributes. +##code + +format_error({illegal,S}) -> ["illegal characters ",io_lib:write_string(S)]; +format_error({user,S}) -> S. + +string(String) -> string(String, 1). + +string(String, Line) -> string(String, Line, String, []). + +%% string(InChars, Line, TokenChars, Tokens) -> +%% {ok,Tokens,Line} | {error,ErrorInfo,Line}. + +string([], L, [], Ts) -> %No partial tokens! + {ok,yyrev(Ts),L}; +string(Ics0, L0, Tcs, Ts) -> + case yystate(yystate(), Ics0, L0, 0, reject, 0) of + {A,Alen,Ics1,L1} -> %Accepting end state + string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L1), Ts); + {A,Alen,Ics1,L1,S1} -> %After an accepting state + string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L1), Ts); + {reject,Alen,Tlen,Ics1,L1,S1} -> + {error,{L1,?MODULE,{illegal,yypre(Tcs, Tlen+1)}},L1}; + {A,Alen,Tlen,Ics1,L1,S1} -> + string_cont(yysuf(Tcs, Alen), L1, yyaction(A, Alen, Tcs, L1), Ts) + end. + +%% string_cont(RestChars, Line, Token, Tokens) +%% Test for and remove the end token wrapper. + +string_cont(Rest, Line, {token,T}, Ts) -> + string(Rest, Line, Rest, [T|Ts]); +string_cont(Rest, Line, {end_token,T}, Ts) -> + string(Rest, Line, Rest, [T|Ts]); +string_cont(Rest, Line, skip_token, Ts) -> + string(Rest, Line, Rest, Ts); +string_cont(Rest, Line, {error,S}, Ts) -> + {error,{Line,?MODULE,{user,S}},Line}. + +%% token(Continuation, Chars, Line) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. +%% Must be careful when re-entering to append the latest characters to the +%% after characters in an accept. + +token(Cont, Chars) -> token(Cont, Chars, 1). + +token([], Chars, Line) -> + token(Chars, Line, yystate(), Chars, 0, reject, 0); +token({Line,State,Tcs,Tlen,Action,Alen}, Chars, _) -> + token(Chars, Line, State, Tcs ++ Chars, Tlen, Action, Alen). + +%% token(InChars, Line, State, TokenChars, TokenLen, Accept) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +token(Ics0, L0, S0, Tcs, Tlen0, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + {A1,Alen1,Ics1,L1} -> %Accepting end state + token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1)); + {A1,Alen1,[],L1,S1} -> %After an accepting state + {more,{L1,S1,Tcs,Alen1,A1,Alen1}}; + {A1,Alen1,Ics1,L1,S1} -> + token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1)); + {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state + {more,{L1,S1,Tcs,Tlen1,A1,Alen1}}; + {reject,Alen1,Tlen1,eof,L1,S1} -> + {done,{eof,L1},[]}; + {reject,Alen1,Tlen1,Ics1,L1,S1} -> + {done,{error,{L1,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}},L1},Ics1}; + {A1,Alen1,Tlen1,Ics1,L1,S1} -> + token_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1)) + end. + +%% tokens_cont(RestChars, Line, Token) +%% Test if we have detected the end token, if so return done else continue. + +token_cont(Rest, Line, {token,T}) -> + {done,{ok,T,Line},Rest}; +token_cont(Rest, Line, {end_token,T}) -> + {done,{ok,T,Line},Rest}; +token_cont(Rest, Line, skip_token) -> + token(Rest, Line, yystate(), Rest, 0, reject, 0); +token_cont(Rest, Line, {error,S}) -> + {done,{error,{Line,?MODULE,{user,S}},Line},Rest}. + +%% tokens(Continuation, Chars, Line) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. +%% Must be careful when re-entering to append the latest characters to the +%% after characters in an accept. + +tokens(Cont, Chars) -> tokens(Cont, Chars, 1). + +tokens([], Chars, Line) -> + tokens(Chars, Line, yystate(), Chars, 0, [], reject, 0); +tokens({tokens,Line,State,Tcs,Tlen,Ts,Action,Alen}, Chars, _) -> + tokens(Chars, Line, State, Tcs ++ Chars, Tlen, Ts, Action, Alen); +tokens({skip_tokens,Line,State,Tcs,Tlen,Error,Action,Alen}, Chars, _) -> + skip_tokens(Chars, Line, State, Tcs ++ Chars, Tlen, Error, Action, Alen). + +%% tokens(InChars, Line, State, TokenChars, TokenLen, Tokens, Accept) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +tokens(Ics0, L0, S0, Tcs, Tlen0, Ts, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + {A1,Alen1,Ics1,L1} -> %Accepting end state + tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Ts); + {A1,Alen1,[],L1,S1} -> %After an accepting state + {more,{tokens,L1,S1,Tcs,Alen1,Ts,A1,Alen1}}; + {A1,Alen1,Ics1,L1,S1} -> + tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Ts); + {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state + {more,{tokens,L1,S1,Tcs,Tlen1,Ts,A1,Alen1}}; + {reject,Alen1,Tlen1,eof,L1,S1} -> + {done,if Ts == [] -> {eof,L1}; + true -> {ok,yyrev(Ts),L1} end,[]}; + {reject,Alen1,Tlen1,Ics1,L1,S1} -> + skip_tokens(yysuf(Tcs, Tlen1+1), L1, + {L1,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}}); + {A1,Alen1,Tlen1,Ics1,L1,S1} -> + tokens_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1), Ts) + end. + +%% tokens_cont(RestChars, Line, Token, Tokens) +%% Test if we have detected the end token, if so return done else continue. + +tokens_cont(Rest, Line, {token,T}, Ts) -> + tokens(Rest, Line, yystate(), Rest, 0, [T|Ts], reject, 0); +tokens_cont(Rest, Line, {end_token,T}, Ts) -> + {done,{ok,yyrev(Ts, [T]),Line},Rest}; +tokens_cont(Rest, Line, skip_token, Ts) -> + tokens(Rest, Line, yystate(), Rest, 0, Ts, reject, 0); +tokens_cont(Rest, Line, {error,S}, Ts) -> + skip_tokens(Rest, Line, {Line,?MODULE,{user,S}}). + +%% token_skip(InChars, Line, Error) -> {done,ReturnVal,RestChars}. +%% Skip tokens until an end token, junk everything and return the error. + +%%skip_tokens(Ics, Line, Error) -> {done,{error,Error,Line},Ics}. + +skip_tokens(Ics, Line, Error) -> + skip_tokens(Ics, Line, yystate(), Ics, 0, Error, reject, 0). + +%% skip_tokens(InChars, Line, State, TokenChars, TokenLen, Tokens, Accept) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +skip_tokens(Ics0, L0, S0, Tcs, Tlen0, Error, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + {A1,Alen1,Ics1,L1} -> %Accepting end state + skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Error); + {A1,Alen1,[],L1,S1} -> %After an accepting state + {more,{skip_tokens,L1,S1,Tcs,Alen1,Error,A1,Alen1}}; + {A1,Alen1,Ics1,L1,S1} -> + skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, L1), Error); + {A1,Alen1,Tlen1,[],L1,S1} -> %After a non-accepting state + {more,{skip_tokens,L1,S1,Tcs,Tlen1,Error,A1,Alen1}}; + {reject,Alen1,Tlen1,eof,L1,S1} -> + {done,{error,Error,L1},[]}; + {reject,Alen1,Tlen1,Ics1,L1,S1} -> + skip_tokens(yysuf(Tcs, Tlen1), L1, Error); + {A1,Alen1,Tlen1,Ics1,L1,S1} -> + skip_cont(yysuf(Tcs, Alen1), L1, yyaction(A1, Alen1, Tcs, L1), Error) + end. + +%% skip_cont(RestChars, Line, Token, Error) +%% Test if we have detected the end token, if so return done else continue. + +skip_cont(Rest, Line, {token,T}, Error) -> + skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0); +skip_cont(Rest, Line, {end_token,T}, Error) -> + {done,{error,Error,Line},Rest}; +skip_cont(Rest, Line, {error,S}, Error) -> + skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0); +skip_cont(Rest, Line, skip_token, Error) -> + skip_tokens(Rest, Line, yystate(), Rest, 0, Error, reject, 0). + +yyrev(L) -> yyrev(L, []). + +yyrev([H|T], Acc) -> yyrev(T, [H|Acc]); +yyrev([], Acc) -> Acc. + +yypre([H|T], N) when N > 0 -> [H|yypre(T, N-1)]; +yypre(L, N) -> []. + +yysuf([H|T], N) when N > 0 -> yysuf(T, N-1); +yysuf(L, 0) -> L. + +yysplit(L, N) -> yysplit(L, N, []). +yysplit([H|T], N, Acc) when N > 0 -> yysplit(T, N-1, [H|Acc]); +yysplit(L, 0, Acc) -> {lists:reverse(Acc), L}. + +%% yystate() -> InitialState. +%% yystate(State, InChars, Line, Token, ) -> +%% {Action, AcceptLength, RestChars, Line} | Accepting end state +%% {Action, AcceptLength, RestChars, Line, State} | Accepting state +%% {Action, AcceptLength, TokLength, RestChars, Line, State} | +%% {reject, AcceptLength, TokLength, RestChars, Line, State}. +%% Generated state transition functions. + +##dfa + +%% yyaction(Action, TokenLength, TokenChars, Line) -> +%% {token,Token} | {end_token, Token} | skip_token | {error,String}. +%% Generated action function. + +##actions diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..0e2630d --- /dev/null +++ b/src/Makefile @@ -0,0 +1,21 @@ +include ../../../support/include.mk + +ERLC_FLAGS += -I ../.. -pa ../ebin/ + +SCRIPT=../bin/ermacs +SED_EXPR = s\\%BASEDIR%\\`pwd`/..\\ + +all: $(ERL_OBJECTS) $(SCRIPT) + +# edit_transform has to be built first. This rule is actually +# circular, but that seems okay with GNU make. +$(ERL_OBJECTS): ../ebin/edit_transform.beam + +$(SCRIPT): ermacs.in + sed ${SED_EXPR} < $< > $@ + chmod +x $@ + +clean: + -rm -f $(ERL_OBJECTS) + -rm -f ../bin/ermacs + diff --git a/src/cord.erl b/src/cord.erl new file mode 100644 index 0000000..980248d --- /dev/null +++ b/src/cord.erl @@ -0,0 +1,504 @@ +%%%---------------------------------------------------------------------- +%%% File : cord.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Data structure for large strings of text +%%% Created : 21 Oct 2000 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- +%% +%% Cords - a scalable data structure for strings of text. +%% +%% Cords are binary trees with erlang binaries as leaves. The trees +%% are kept fairly balanced, and the sizes of the binary objects are +%% kept within acceptable bounds. By using binaries, it should only +%% cost about one byte per character. +%% +%% The main idea is to have a fast insert/replace operation which +%% doesn't change the binaries too much. By keeping the binaries on +%% the leaves fairly small, we generally get to move them about in one +%% piece while we're balancing things after large updates or pulling +%% out large regions. An added advantage of not disturbing the +%% binaries is that it's not too expensive to keep old copies of cords +%% around, since most of the binaries get shared on the heap. +%% +%% API: +%% new(): +%% Creates a new, empty cord. +%% new(ListOrBinary): +%% Creates a new cord from some input characters. +%% replace(Cord, NewText, Start, Length): +%% Replaces the `Length' long portion of `Cord' starting at `Start' +%% with `NewText' (a cord, binary, or iolist). +%% join(Left, Right) +%% Join two cords together into a new one. +%% to_binary(Cord): Convert a cord into a binary +%% to_list(Cord): Convert a cord into a list +%% walk(Cord, Pos, Direction, Fun): +%% "Walk" character by character along `Cord' in `Direction'. For each +%% character, we call Fun(Char) which returns either {result, R}, or +%% {more, NextFun}. If we run out of characters before the fun returns +%% a result, we call Fun(finish), which is required to return +%% {result, R}. + +-module(cord). +-author('luke@bluetail.com'). + +-compile(export_all). + +-export([new/0, new/1, cord_size/1, + replace/4, region/3, region_binary/3, region_list/3, + to_binary/1, to_list/1, to_iolist/1, + walk/4]). + +%% It seems that static values won't do for scaling to gigantic (tens +%% of megabytes) sizes. +-define(MIN_SIZE, 512). +-define(MAX_SIZE, 2048). + +%% A cord is either a #cord record or a binary. +-record(cord, {size, % Combined size + left, % cord() + right, % cord() + %% The dirty flag indicates that a cord (or one of its + %% children) has been changed since the last time the + %% cord was 'fixed' + dirty=false + }). + +-define(assert(X), + (case X of + true -> + true; + false -> + exit(lists:flatten(io_lib:format("Assertion failed at ~p:~p", + [?MODULE, ?LINE]))) + end)). + +new() -> <<>>. + +new(B) when binary(B) -> fix_cord(B); +new(L) when list(L) -> new(list_to_binary(L)). + +%% More efficient way to create a cord from a file. This +%% implementation is not terribly clever (read small chunks, balance +%% at the end), but at least it isn't a memory hog. +%% +%% Returns: {ok, cord()} | {error, Reason} +new_from_file(Filename) -> + case file:open(Filename, [raw, binary]) of + X = {error, Rsn} -> + X; + {ok, F} -> + read_chunks(F) + end. + +read_chunks(F) -> + read_chunks(F, new()). + +read_chunks(F, Acc) -> + case file:read(F, ?MAX_SIZE) of + eof -> + {ok, fix_cord(Acc)}; + {ok, Bin} -> + read_chunks(F, make_cord(Acc, Bin)); + X = {error, Reason} -> + X + end. + +%% Make a cord. +%% This is a "dirty" operation that doesn't rebalance the tree. +make_cord(Left, Right) -> + #cord{size=cord_size(Left) + cord_size(Right), + left=Left, + right=Right, + dirty=true}. + +cord_size(Cord) when binary(Cord) -> + size(Cord); +cord_size(Cord) when record(Cord, cord) -> + Cord#cord.size. + +max_depth(Cord) when binary(Cord) -> + 1; +max_depth(Cord) -> + 1 + max(max_depth(Cord#cord.left), + max_depth(Cord#cord.right)). + +nr_nodes(Cord) when binary(Cord) -> + 1; +nr_nodes(Cord) -> + 1 + nr_nodes(Cord#cord.left) + nr_nodes(Cord#cord.right). + +mean_leaf_size(Cord) -> + Sizes = leaf_sizes(Cord), + Sum = lists:foldr(fun(X, Acc) -> X + Acc end, + 0, + Sizes), + round(Sum / length(Sizes)). + +leaf_sizes(Cord) when binary(Cord) -> + [size(Cord)]; +leaf_sizes(Cord) -> + leaf_sizes(Cord#cord.left) ++ leaf_sizes(Cord#cord.right). + +max(X, Y) when X > Y -> X; +max(X, Y) -> Y. + +insert(Cord, New, Point) -> + replace(Cord, New, Point, 0). + +delete(Cord, Point, Length) -> + replace(Cord, [], Point, Length). + +%% replace/4: Replace a region of the cord. `New' is the text to +%% replace the region with, and can be either a list, binary, or cord. +replace(Cord, New, Start, Length) when list(New) -> + replace(Cord, list_to_binary(New), Start, Length); +replace(Cord, New, Start, Length) -> + %% Replace is done by copying the areas on the left and right of + %% the region, and joining them together with the new cord in the + %% middle. + {A, B} = split(Cord, Start-1), + {C, D} = split(B, Length), + fix_cord(make_cord(make_cord(A, New), D)). + +split(Cord, 0) when binary(Cord) -> + {<<>>, Cord}; +split(Cord, Pos) when binary(Cord) -> + ?assert(Pos =< cord_size(Cord)), + <<Left:Pos/binary, Right/binary>> = Cord, + {Left, Right}; + +split(Cord, Pos) when record(Cord, cord) -> + ?assert(Pos =< cord_size(Cord)), + LeftSz = cord_size(Cord#cord.left), + RightSz = cord_size(Cord#cord.right), + %%io:format("Split - left:~p right:~p~n", [LeftSz, RightSz]), + if LeftSz == Pos -> + {Cord#cord.left, Cord#cord.right}; + LeftSz > Pos -> + {SplitLeft, SplitRight} = split(Cord#cord.left, Pos), + {SplitLeft, make_cord(SplitRight, Cord#cord.right)}; + LeftSz < Pos -> + {SplitLeft, SplitRight} = split(Cord#cord.right, Pos-LeftSz), + {make_cord(Cord#cord.left, SplitLeft), SplitRight} + end. + +%% join two cords together and rebalance. +join(Left, Right) when binary(Left) -> + fix_cord(make_cord(Left, Right)). + +%% fix_cord/1 +%% +%% "Fix" a cord so that it's reasonably balanced, and it's leaves are +%% reasonable sizes. + +%% Leaf (binary) - break it up if it's too big +fix_cord(Bin) when binary(Bin) -> + if size(Bin) > ?MAX_SIZE -> + {Left, Right} = split(Bin, round(size(Bin) / 2)), + fix_cord(make_cord(Left, Right)); + true -> + Bin + end; +fix_cord(Cord) when Cord#cord.dirty == false -> + Cord; +%% Branch (cord) - merge its children if they're too small, balance it +%% if it's too unbalanced. +fix_cord(Cord) when record(Cord, cord) -> + Sz = cord_size(Cord), + Left = Cord#cord.left, + Right = Cord#cord.right, + LeftSz = cord_size(Left), + RightSz = cord_size(Right), + SzDiff = abs(LeftSz - RightSz), + if Sz < ?MIN_SIZE -> + %% Too small - make it into a binary + to_binary(Cord); + SzDiff > (Sz/3) -> + %% needs rebalancing + if LeftSz > RightSz -> + if binary(Left) -> + fix_cord(to_binary(Cord)); + true -> + balance_from_left(Cord) + end; + LeftSz =< RightSz -> + if binary(Right) -> + fix_cord(to_binary(Cord)); + true -> + balance_from_right(Cord) + end + end; + true -> + %% this cord is ok, fix the children + Cord#cord{left=fix_cord(Left), + right=fix_cord(Right), + dirty=false} + end. + +%% Balance by taking from the left side. +%% Left must be a #cord, right can be a binary. +balance_from_left(#cord{left=Left, right=Right}) when record(Left, cord) -> + LLSz = cord_size(Left#cord.left), + LRSz = cord_size(Left#cord.right), + if + LRSz < LLSz -> + %% single rotate + fix_cord(make_cord(Left#cord.left, + make_cord(Left#cord.right, + Right))); + record(Left#cord.right, cord) -> + %% double rotate + LeftRight = Left#cord.right, + fix_cord(make_cord(make_cord(Left#cord.left, + LeftRight#cord.left), + make_cord(LeftRight#cord.right, + Right))); + true -> + fix_cord(to_binary(make_cord(Left, Right))) + end. + +%% oh, pain, duplication. never have been good at taking redundancy +%% out of symmetric functions. -luke +balance_from_right(#cord{left=Left, right=Right}) when record(Right, cord) -> + RLSz = cord_size(Right#cord.left), + RRSz = cord_size(Right#cord.right), + if + RLSz < RRSz -> + %% single rotate + fix_cord(make_cord(make_cord(Left, + Right#cord.left), + Right#cord.right)); + record(Right#cord.left, cord) -> + %% double rotate + RightLeft = Right#cord.left, + fix_cord(make_cord(make_cord(Left, + RightLeft#cord.left), + make_cord(RightLeft#cord.right, + Right#cord.right))); + true -> + fix_cord(to_binary(make_cord(Left, Right))) + end. + +%% Return: cord() +region(Cord, Start, Length) -> + {A, B} = split(Cord, Start-1), + {C, D} = split(B, Length), + C. + +%% Return: binary() +region_binary(Cord, Start, Length) -> + to_binary(region(Cord, Start, Length)). + +%% Return: list() +region_list(Cord, Start, Length) -> + binary_to_list(region_binary(Cord, Start, Length)). + +to_binary(Cord) when binary(Cord) -> + Cord; +to_binary(Cord) -> + list_to_binary(to_binary1(Cord)). + +to_binary1(Cord) when binary(Cord) -> + Cord; +to_binary1(Cord) when record(Cord, cord) -> + [to_binary1(Cord#cord.left),to_binary1(Cord#cord.right)]. + +to_list(Cord) -> + binary_to_list(to_binary(Cord)). + +to_iolist(Cord) when binary(Cord) -> + [Cord]; +to_iolist(Cord) -> + [to_iolist(Cord#cord.left) | to_iolist(Cord#cord.right)]. + +%% Walk backwards along a cord, character by character. +%% F = fun(X) -> {more, F2} | {result, R} +%% X = char() | finish +walk(Cord, Pos, Direction, F) -> + %% Make this simple: extract the region we want to walk along. + Region = case Direction of + backward -> + {A, B} = split(Cord, Pos), + A; + forward -> + {A, B} = split(Cord, Pos-1), + B + end, + case walk1(Region, Direction, F) of + {result, R} -> + R; + {more, FNext} -> + {result, R} = FNext(finish), + R + end. + +walk1(<<>>, Direction, F) -> + {more, F}; +walk1(Bin, Direction, F) when binary(Bin) -> + {Chunk, Char} = case Direction of + backward -> + Sz = size(Bin) - 1, + <<Front:Sz/binary, Back>> = Bin, + {Front, Back}; + forward -> + <<Front, Back/binary>> = Bin, + {Back, Front} + end, + case F(Char) of + {more, F2} -> + walk1(Chunk, Direction, F2); + {result, R} -> + {result, R} + end; +walk1(Cord, Direction, F) when record(Cord, cord) -> + {First, Second} = case Direction of + backward -> + {Cord#cord.right, Cord#cord.left}; + forward -> + {Cord#cord.left, Cord#cord.right} + end, + case walk1(First, Direction, F) of + {more, F2} -> + walk1(Second, Direction, F2); + {result, R} -> + {result, R} + end. + +walker(Cord) -> + walker(Cord, forward). + +walker(Cord, Direction) -> + walker(Cord, Direction, <<>>). + +walker(Cord, Direction, More) when binary(Cord) -> + {Cord, Direction, More}; +walker(Cord, forward, More) -> + walker(Cord#cord.left, forward, make_cord(Cord#cord.right, More)); +walker(Cord, backward, More) -> + walker(Cord#cord.right, backward, make_cord(More, Cord#cord.left)). + +walker_at_end({walked_to_end, _}) -> + true; +walker_at_end({C,_,Rest}) -> + cord_size(C) + cord_size(Rest) == 0. + +walker_direction({walked_to_end, Direction}) -> Direction; +walker_direction({_, Direction, _}) -> Direction. + +walker_next({walked_to_end, Direction}) -> + {done, {walked_to_end, Direction}}; +walker_next({<<>>, Direction, More}) -> + case cord_size(More) of + 0 -> + {done, {walked_to_end, Direction}}; + _ -> + walker_next(walker(More, Direction)) + end; +walker_next({<<A, Chunk/binary>>, forward, More}) -> + {A, {Chunk, forward, More}}; +walker_next({Bin, backward, More}) -> + ChunkSz = size(Bin) - 1, + <<Chunk:ChunkSz/binary, A>> = Bin, + {A, {Chunk, backward, More}}. + +walker_push(done, Walker) -> + Walker; +walker_push(X, {walked_to_end, Dir}) -> + {<<X>>, Dir, <<>>}; +walker_push(X, {Bin, forward, More}) -> + {<<X>>, forward, make_cord(Bin, More)}; +walker_push(X, {Bin, backward, More}) -> + {<<X>>, backward, make_cord(Bin, More)}. + +walker_test() -> + Cord = make_cord(make_cord(<<1,2>>, <<3>>), + make_cord(<<4,5,6>>, <<7,8,9>>)), + W = walker(Cord, forward), + walker_test_loop(W). + +walker_test_loop(W) -> + case walker_next(W) of + {done, _} -> + []; + {Ch, W2} -> + [Ch|walker_test_loop(W2)] + end. + +test() -> + %% Test of binary cords + BinCord = <<1, 2, 3, 4, 5>> , + {BinCord, <<>>} = split(BinCord, size(BinCord)), + {<<>>, BinCord} = split(BinCord, 0), + {<<1, 2>>, <<3, 4, 5>>} = split(BinCord, 2), + %% Test of a simple cord + Cord1 = make_cord(<<1, 2>>, <<3, 4, 5>>), + {<<>>, <<1, 2, 3, 4, 5>>} = binsplit(Cord1, 0), + {<<1, 2>>, <<3, 4, 5>>} = binsplit(Cord1, 2), + {<<1, 2, 3>>, <<4, 5>>} = binsplit(Cord1, 3), + %% A less trivial cord + %% (spaces before commas are to workaround an erlang-mode indent problem) + A = <<1, 2>> , + B = <<3, 4>> , + C = <<5, 6>> , + D = <<7, 8>> , + E = <<9>> , + Cord2 = make_cord(make_cord(A, B), make_cord(C, make_cord(D, E))), + {<<>>, <<1, 2, 3, 4, 5, 6, 7, 8, 9>>} = binsplit(Cord2, 0), + {<<1, 2, 3, 4, 5>>, <<6, 7, 8, 9>>} = binsplit(Cord2, 5), + %% Joining + BinCord2 = to_binary(Cord2), + %% Why does = fail but == work? + true = <<BinCord/binary, BinCord2/binary>> == binjoin(BinCord, Cord2), + true = <<BinCord2/binary, BinCord/binary>> == binjoin(Cord2, Cord1), + true = <<BinCord2/binary, BinCord2/binary>> == binjoin(Cord2, Cord2), + %% Test "walking" + [1,2,3,4,5] = walk(Cord2, 5, backward, walk_test([])), + [9, 8, 7, 6, 5] = walk(Cord2, 5, forward, walk_test([])), + %% Test some operations + <<1, 2, 3, 4>> = delete(Cord2, 5, 5), + <<1, 2, 3, + 1, 2, 3, 4, 5, 6, 7, 8, 9, + 4, 5, 6, 7, 8, 9>> = cord:insert(Cord2, Cord2, 4), + ok. + +walk_test(Acc) -> + fun(finish) -> + {result, Acc}; + (X) -> + {more, walk_test([X|Acc])} + end. + +test2() -> + A = <<1, 2, 3, 4>> , + B = <<5, 6>> , + C = <<>> , + D = <<7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17>> , + join(A, join(B, join(C, D))). + +binsplit(Cord, Pos) -> + {Left, Right} = split(Cord, Pos), + {to_binary(Left), to_binary(Right)}. + +binjoin(Left, Right) -> + to_binary(make_cord(Left, Right)). + +benchmark(File) -> + {ok, Cord} = cord:new_from_file(File), + Sz = cord_size(Cord), + random:seed(), + Randoms = [random:uniform(Sz) || _ <- lists:seq(1, 100)], + timer:tc(?MODULE, split_with_each, [Cord, Randoms, 10]). + +split_with_each(Cord, L, N) -> + split_with_each(Cord, L, L, N). + +split_with_each(Cord, _, _, 0) -> + ok; +split_with_each(Cord, [H|T], L, N) -> + split(Cord, H), + split_with_each(Cord, T, L, N); +split_with_each(Cord, [], L, N) -> + split_with_each(Cord, L, L, N-1). + + diff --git a/src/cord_regexp.erl b/src/cord_regexp.erl new file mode 100644 index 0000000..1d6bb4d --- /dev/null +++ b/src/cord_regexp.erl @@ -0,0 +1,296 @@ +%%%---------------------------------------------------------------------- +%%% File : cord_regexp.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Regexp ops on cords +%%% Created : 10 Mar 2001 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(cord_regexp). +-author('luke@bluetail.com'). + +-compile(export_all). +%%-export([Function/Arity, ...]). + +first_match(RE, Cord) -> + first_match(RE, Cord, 1). + +first_match(RE, Cord, Pos) -> + first_match(RE, Cord, Pos, forward). + +%% Find the first match of RE in Cord starting from Pos. +first_match(RE, Cord, Pos, Dir) -> + case first_match_c(RE, Cord, Pos, Dir) of + {{match, Start, Len}, WalkerOut} -> + {match, Start, Len}; + X -> + X + end. + +first_match_c(RE, Cord) -> + first_match_c(RE, Cord, 1). + +first_match_c(RE, Cord, Pos) -> + first_match_c(RE, Cord, Pos, forward). + +first_match_c(RE, Cord, Pos, forward) -> + {_, Region} = cord:split(Cord, Pos-1), + first_match1(RE, cord:walker(Region, forward), Pos); + +first_match_c(RE, Cord, Pos, backward) -> + case regexp:parse(RE) of + {ok, REP} -> + {Region,_} = cord:split(Cord, Pos), + RREP = reverse(REP), + case first_match1(RREP, cord:walker(Region, backward), Pos) of + {{match, S, L}, C} -> + {{match, S, L}, C}; + X -> + X + end; + {error, Rsn} -> + {error, Rsn} + end. + +continue_match(RE, {W, N}) -> + case first_match1(RE, W, N) of + {{match, S, L}, C} -> + {{match, S, L}, {C, S+L}}; + X -> + X + end. + +first_match1(RE, W, Pos) when list(RE) -> + case regexp:parse(RE) of + {ok, REP} -> + first_match1(optimise(REP), W, Pos); + X = {error, Rsn} -> + X + end; +first_match1(RE, Walker, Pos) -> + case cord:walker_at_end(Walker) of + true -> + nomatch; + false -> + Start = case cord:walker_direction(Walker) of + forward -> 1; + backward -> Pos + end, + case apply_regexp(RE, Start, Walker) of + nomatch -> + %% it would be more efficient to use Walker to get + %% a new continuation to recurse with + {_, W1} = cord:walker_next(Walker), + first_match1(RE, W1, advance(Pos, W1)); + {match, EndPos, WalkerOut} -> + Len = case cord:walker_direction(Walker) of + forward -> EndPos; + backward -> Pos - EndPos - 1 + end, + {{match, Pos, Len}, WalkerOut} + end + end. + +apply_regexp(RE, Pos, W) -> + {Ch, W1} = cord:walker_next(W), + re_apply(RE, [], Ch, Pos, W1). + +%% re_apply(RE, More, ThisChar, Pos, InputCont) => {match, Len} | nomatch + +%% FIXME: Handling of bos (^) and eos ($) need thinking about when it +%% comes to backwards-searching. Right now, ^ will match the end when +%% going backwards. + +re_apply(epsilon, More, Ch, P, C) -> + re_apply_more(More, P, push(Ch, C)); +re_apply(eos, More, done, P, C) -> + case cord:walker_direction(C) of + forward -> + re_apply_more(More, P, C); + backward -> + nomatch + end; +re_apply(eos, More, $\n, P, C) -> + re_apply_more(More, P, push($\n, C)); % \n isn't consumed +re_apply(eos, More, done, P, C) -> + case cord:walker_direction(C) of + forward -> re_apply_more(More, P, C); + backward -> nomatch + end; +re_apply(bos, More, done, P, C) -> + case cord:walker_direction(C) of + forward -> nomatch; + backward -> re_apply_more(More, P, C) + end; +re_apply(eos, _, done, _, _) -> + true; +re_apply(eos, _, done, _, _) -> + true; +re_apply({'or', RE1, RE2}, More, Ch, P, C) -> + re_apply_or({apply, RE1, More, Ch, P, C}, + {apply, RE2, More, Ch, P, C}); +re_apply({concat, RE1, RE2}, More, Ch, P, C) -> + re_apply(RE1, [RE2|More], Ch, P, C); +re_apply({kclosure, CE}, More, Ch, P, C) -> + re_apply_or({apply_more, More, P, push(Ch, C)}, + {apply, CE, [{kclosure, CE}|More], Ch, P, C}); +re_apply({pclosure, CE}, More, Ch, P, C) -> + re_apply(CE, [{kclosure, CE}|More], Ch, P, C); +re_apply({optional, CE}, More, Ch, P, C) -> + re_apply_or({apply_more, More, P, push(Ch, C)}, + {apply, CE, More, Ch, P, C}); +re_apply(bos, More, Ch, 1, C) -> + case cord:walker_direction(C) of + forward -> re_apply_more(More, 1, push(Ch, C)); + backward -> nomatch + end; +re_apply(bos, More, $\n, P, C) -> + re_apply_more(More, 1, push($\n, C)); % \n isn't consumed +re_apply({char_class, Cc}, More, Ch, P, C) -> + case in_char_class(Ch, Cc) of + true -> re_apply_more(More, advance(P, C), C); + false -> nomatch + end; +re_apply({comp_class, Cc}, More, Ch, P, C) -> + case in_char_class(Ch, Cc) of + true -> nomatch; + false -> re_apply_more(More, advance(P, C), C) + end; +re_apply(Ch, More, Ch, P, C) when integer(Ch) -> + re_apply_more(More, advance(P, C), C); +re_apply(_, _, _, _, _) -> + nomatch. + +advance(Pos, Walker) -> + case cord:walker_direction(Walker) of + forward -> Pos + 1; + backward -> Pos - 1 + end. + +re_apply_more([H|T], P, C) -> + {Next, C1} = cord:walker_next(C), + re_apply(H, T, Next, P, C1); +re_apply_more([], P, C) -> + %% -1 isn't used in 'regexp' module, not sure what's different.. + {match, P-1, C}. + +re_apply_or(A, B) -> + case re_apply_or_operand(A) of + nomatch -> + re_apply_or_operand(B); + X -> + X + end. + +re_apply_or_operand({apply_more, More, P, C}) -> + re_apply_more(More, P, C); +re_apply_or_operand({apply, RE, More, Ch, P, C}) -> + re_apply(RE, More, Ch, P, C). + + +% re_apply_or({match,P1}, {match,P2}) when P1 >= P2 -> {match,P1}; +% re_apply_or({match,P1}, {match,P2}) -> {match,P2}; +% re_apply_or(nomatch, R2) -> R2; +% re_apply_or(R1, nomatch) -> R1. + +in_char_class(C, [{C1,C2}|Cc]) when C >= C1, C =< C2 -> true; +in_char_class(C, [C|Cc]) -> true; +in_char_class(C, [_|Cc]) -> in_char_class(C, Cc); +in_char_class(C, []) -> false. + +push(Ch, Cont) -> + cord:walker_push(Ch, Cont). + +%% reverse(RE) +%% Returns an equivalent regexp which takes its input in reverse order. +%% RE should come from regexp:parse/1 +reverse({'or', A, B}) -> + {'or', reverse(B), reverse(A)}; +reverse({concat, A, B}) -> + {concat, reverse(B), reverse(A)}; +reverse({optional, RE}) -> + {optional, reverse(RE)}; +reverse({kclosure, RE}) -> + {kclosure, reverse(RE)}; +reverse({pclosure, RE}) -> + {pclosure, reverse(RE)}; +reverse(Other) -> + Other. + +%% Just trivial stuff for starters. +%% +%% Rearrange concat +optimise({concat, {concat, A, B}, C}) -> + optimise({concat, A, {concat, B, C}}); +%% recurse +optimise({'or', A, B}) -> + {'or', optimise(A), optimise(B)}; +optimise({kclosure, A}) -> + {kclosure, optimise(A)}; +optimise({pclosure, A}) -> + {pclosure, optimise(A)}; +optimise({optional, A}) -> + {optional, optimise(A)}; +optimise(X) -> + X. + +test_exprs() -> + ["foo", + "a|b", + "(foo)|(bar)", + "a+"]. + +test_inputs() -> + ["foo", "a", "b", "c", "foo", "bar", "baz", "aaaaa"]. + +test() -> + case [{Inp, RE} || RE <- test_exprs(), Inp <- test_inputs(), + not same(Inp, RE)] of + [] -> + ok; + %% BadOnes is where the match result was different. BUT! + %% sometimes that is OK. At time of writing, the code seems + %% correct but has some BadOnes. + BadOnes -> + lists:foreach(fun complain/1, BadOnes) + end. + +same(Inp, REStr) -> + forwards(Inp, REStr) == backwards(Inp, REStr). + +forwards(Inp, RE) -> + regexp:match(Inp, RE). + +backwards(Inp, REStr) -> + {ok, RE} = regexp:parse(REStr), + BackwardsResult = regexp:match(lists:reverse(Inp), reverse(RE)). + +complain({Inp, REStr}) -> + {ok, RE} = regexp:parse(REStr), + ForwardsResult = regexp:match(Inp, RE), + BackwardsResult = regexp:match(lists:reverse(Inp), reverse(RE)), + io:format("match(~p, ~p):~n Fwds = ~p~n Bwds = ~p~n", + [Inp, REStr, ForwardsResult, BackwardsResult]). + +%% Compare speed of this module on a cord to the speed of the 'regexp' +%% module on a string. +bench(Regexp, Cord) -> + List = cord:to_list(Cord), + {CordSpeed,_} = timer:tc(?MODULE, first_match, [Regexp, Cord]), + {ListSpeed, _} = timer:tc(regexp, first_match, [List, Regexp]), + io:format("Cord takes ~p% time of List (~pms vs ~pms).~n", + [round(CordSpeed * 100 / ListSpeed), + round(CordSpeed/1000), + round(ListSpeed/1000)]). + +escape([H|T]) -> + case lists:member(H, specials()) of + true -> + [$\\,H|escape(T)]; + false -> + [H|escape(T)] + end; +escape([]) -> + []. + +specials() -> "()^$[]*+?.\\|". + diff --git a/src/edit.erl b/src/edit.erl new file mode 100644 index 0000000..a1e0cbf --- /dev/null +++ b/src/edit.erl @@ -0,0 +1,264 @@ +%%%---------------------------------------------------------------------- +%%% File : edit.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Main editor process. +%%% +%%% Grown out of Tobbe's 'edit' program, and slowly rewritten. +%%%---------------------------------------------------------------------- +-module(edit). +-author('luke@bluetail.com'). +-export([start/0]). + +-include_lib("ermacs/include/edit.hrl"). + +-compile(export_all). + +%% Command-line entry function. Starts the editor. +start() -> + init(). + +%% Another command-line entry function. Starts the editor and loads +%% some files. +start(Args) -> + %% Easy/hacky way - asynchronously ask that all the files be + %% loaded. When this process is initialised it'll see the + %% messages. + Filenames = lists:map(fun(X) -> atom_to_list(X) end, + Args), + lists:foreach(fun(Filename) -> + self() ! {invoke, {edit_file, find_file, [Filename]}} + end, + Filenames), + start(). + +%% Another command-line entry function. Starts the editor with some +%% modules loaded for debugging. +debug() -> + lists:foreach(fun(Mod) -> i:ii(Mod) end, debug_modules()), + i:im(), + sleep(1000), + proc_lib:start_link(?MODULE, start, []). + +debug_modules() -> + [edit_display, edit_lib, ?EDIT_TERMINAL, edit_keymap, edit_buf, + edit_extended, edit_file, cord, edit_eval, edit_util, edit_text]. + +%% ---------------------------------------------------------------------- +%% API program + +invoke_async(M, F, A) -> + edit ! {invoke, {M, F, A}}. + +invoke_async(Fn) -> + edit ! {invoke, Fn}. + +invoke_extended_async(M, F, A) -> + edit ! {invoke_extended, {M, F, A}}. + +get_key() -> + edit ! {want_key, self()}, + receive {key_input, Ch} -> Ch end. + +%% ---------------------------------------------------------------------- +%% Turn this process into a lean, mean, editing machine +init() -> + register(?MODULE, self()), + init_io_traps(), + process_flag(trap_exit, true), + %% Initialize editor + edit_keymap:start_link_server(), + edit_globalmap:init(), + ?EDIT_TERMINAL:setup(), + edit_input:start_link(self()), + init_vars(), + init_buffers(), + init_mods(), + State = init_windows(#state{}), + State1 = load_dot_ermacs(State), + State2 = redraw(State1), + io:format("INIT: ~p~n", [edit_mod:require(em_stdlib)]), + %%profile(State2), + loop(State2). + +init_io_traps() -> + {ok, Leader} = file_gl:start_link("/tmp/edit.out"), + group_leader(Leader, self()), + error_logger:tty(false). + +%% Setup initial buffers (scratch and minibuffer) +init_buffers() -> + edit_buf:new('*scratch*'), + edit_buf:new(minibuffer), + MBMode = #mode{name="Minibuffer", + id=minibuffer, + keymaps=[minibuffer_map]}, + edit_buf:set_mode(minibuffer, MBMode). + +%% Setup initial windows +init_windows(State) -> + Width = ?EDIT_TERMINAL:width(), + Height = ?EDIT_TERMINAL:height(), + ScratchWin = edit_window:make_window('*scratch*', + 0, + Width, + Height - 1), + MiniWin1 = edit_window:make_window(minibuffer, + Height - 1, + Width, + 1), + MiniWin2 = MiniWin1#window{active=false, + minibuffer=true}, + State#state{curwin=ScratchWin, + buffers=['*scratch*'], + windows=[MiniWin2]}. + +init_minibuffer(State) -> + edit_buf:new(minibuffer), + State. + +init_vars() -> + edit_var:start_link(), + edit_var:set(killring, []). + +init_mods() -> + edit_mod:init(), + %% special-case explicit initialisations for the core stuffs + edit_file:mod_init(), + ok. + +load_dot_ermacs(State) -> + Filename = filename:join(os:getenv("HOME"), ".ermacs"), + case file:read_file_info(Filename) of + {error, _} -> + edit_util:status_msg(State, "No ~~/.ermacs to read."); + {ok, _} -> + case catch file:eval(Filename) of + ok -> + State; + {error, Rsn} -> + edit_util:status_msg(State, "~/.ermacs failed: ~p", [Rsn]) + end + end. + +%% ------------- +%% The Main Loop + +loop(S) -> + State = S#state{curwin=edit_display:draw_window(S#state.curwin)}, + NewState = dispatch(State), + ?MODULE:loop(redraw(NewState)). + +redraw(State) -> + Wins = [edit_display:draw_window(W) || W <- State#state.windows], + lists:foreach(fun(W) -> edit_display:draw_window(W) end, + State#state.windows), + Cur = edit_display:draw_window(State#state.curwin), + ?EDIT_TERMINAL:refresh(), + State#state{curwin=Cur, + windows=Wins}. + +%% Dispatch a command, based on the next message we receive. +dispatch(State) -> + Buf = (State#state.curwin)#window.buffer, + Keymaps = (edit_buf:get_mode(Buf))#mode.keymaps ++ [global_map], + receive + {invoke, {M, F, A}} -> + dispatch_proc(State, fun() -> apply(M, F, [State | A]) end); + {invoke, Fun} when function(Fun) -> + dispatch_proc(State, fun() -> Fun(State) end); + {invoke_extended, {Mod, Func, Args}} -> + dispatch_extended(State, Mod, Func, Args); + {key_input, Ch} -> + case find_cmd(State, Keymaps, Ch) of + unbound -> + edit_util:status_msg(State, "Unbound key"); + {Mod, Func, Args} -> + dispatch_extended(State, Mod, Func, Args); + Other -> + edit_util:status_msg(State,"Bad binding: ~p~n",[Other]) + end; + {'EXIT', _Someone, _SomeReason} -> + dispatch(State); + Other -> + io:format("Unexpected message: ~p~n", [Other]) + end. + +dispatch_extended(State, Mod, Func, Args) -> + F = fun() -> edit_extended:extended_command(State, Mod, Func, Args) end, + dispatch_proc(State, F). + +%% ---------------------------------------------------------------------- +%% Dispatch a command in a new process. The process gets aborted if +%% the user presses C-g. +dispatch_proc(State, CommandFun) -> + Self = self(), + F = fun() -> + Result = CommandFun(), + Self ! {result, self(), Result} + end, + Pid = spawn_link(F), + dispatch_loop(State, Pid, false). + +dispatch_loop(State, Pid, WantKey) -> + receive + {result, Pid, Result} -> + Result; + {key_input, $\^G} -> + exit(Pid, user_abort), + edit_util:status_msg(State, "Abort"); + {key_input, Ch} when WantKey == true -> + Pid ! {key_input, Ch}, + dispatch_loop(State, Pid, false); + {want_key, Pid} when WantKey == false -> + dispatch_loop(State, Pid, true); + {'EXIT', Pid, Reason} -> + io:format("DE: ~p~n", [Reason]), + edit_util:status_msg(State,"Dispatch error: ~p~n",[Reason]) + end. + + +%% ---------------------------------------------------------------------- +%% Keymap lookup + +find_cmd(State, Keymaps) -> + Ch = get_char(), + find_cmd(State, Keymaps, Ch). + +find_cmd(State, [], Ch) -> + unbound; +find_cmd(State, [Keymap|Keymaps], Ch) -> + case edit_keymap:lookup(Keymap, Ch) of + {ok, {keymap, NewMap}} -> + find_cmd(State, [NewMap]); + {ok, Cmd} -> + Cmd; + unbound -> + find_cmd(State, Keymaps, Ch) + end. + +get_char() -> + receive {key_input, C} -> C end. + +sleep(T) -> receive after T -> true end. + + +%% ---------------------------------------------------------------------- +%% Profiling + +profile(State) -> + receive after 100 -> ok end, + Procs = [edit|State#state.buffers], + timer:start_link(), + spawn_link(fun() -> + analyse_loop(Procs) + end). + +analyse_loop(Procs) -> + eprof:start(), + profiling = eprof:profile(Procs), + receive after 15000 -> + eprof:total_analyse() + end, + analyse_loop(Procs). + + diff --git a/src/edit_bench.erl b/src/edit_bench.erl new file mode 100644 index 0000000..1c40925 --- /dev/null +++ b/src/edit_bench.erl @@ -0,0 +1,32 @@ +%%%------------------------------------------------------------------- +%%% File : edit_bench.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Random benchmarking +%%% +%%% Created : 29 Sep 2001 by Luke Gorrie <luke@bluetail.com> +%%%------------------------------------------------------------------- +-module(edit_bench). + +-compile(export_all). + +%% Testing the speed of sending (large) cords in messages + +cord_bench(Filename, N) -> + {ok, Cord} = cord:new_from_file(Filename), + Pid = spawn_link(?MODULE, cord_receiver, []), + timer:tc(?MODULE, cord_bench_loop, [Cord, Pid, N]). + +cord_bench_loop(Cord, Pid, 0) -> + exit(Pid, kill), + ok; +cord_bench_loop(Cord, Pid, N) when N > 0 -> + Pid ! {cord, self(), Cord}, + receive ack -> ok end, + cord_bench_loop(Cord, Pid, N-1). + +cord_receiver() -> + receive {cord, Who, Cord} -> Who ! ack end, + cord_receiver(). + + + diff --git a/src/edit_buf.erl b/src/edit_buf.erl new file mode 100644 index 0000000..7d89741 --- /dev/null +++ b/src/edit_buf.erl @@ -0,0 +1,320 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_buf.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Buffer process +%%% Created : 14 Sep 2000 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_buf). +-author('luke@bluetail.com'). + +-compile(export_all). +%%-export([Function/Arity, ...]). + +-record(state, {name, + filename, % optional + text, % text() + mode, + borrower=nobody % for a lock pid() + }). + +%% ---------------------------------------------------------------------- +%% API +%% ---------------------------------------------------------------------- + +new(Name) -> + case start_link(Name) of + {ok, B} -> + add_mark(Name, point, 1, forward), + add_mark(Name, mark, 1, backward), + {ok, Name}; + Err -> + Err + end. + +start_link(Name) when atom(Name) -> + case whereis(Name) of + undefined -> + Pid = proc_lib:spawn_link(?MODULE, init, [Name]), + register(Name, Pid), + {ok, Pid}; + Pid -> + {error, {already_started, Pid}} + end. + +kill(Buf) -> + call(Buf, kill). + +set_filename(Buf, Filename) -> + call(Buf, {set_filename, Filename}). + +get_filename(Buf) -> + call(Buf, get_filename). + +set_mode(Buf, Mode) -> + call(Buf, {set_mode, Mode}). + +get_mode(Buf) -> + call(Buf, get_mode). + +get_text(Buf) -> + call(Buf, get_text). + +set_text(Buf, Text) -> + call(Buf, {set_text, Text}). + +get_cord(Buf) -> + call(Buf, get_cord). + +get_size(Buf) -> + call(Buf, get_size). + +replace(Buf, New, Start, End) when Start > End -> + replace(Buf, New, End, Start); +replace(Buf, New, Start, End) -> + call(Buf, {replace, New, Start, End}). + +get_region(Buf, Start, End) -> + cord:to_list(get_region_cord(Buf, Start, End)). + +get_region_cord(Buf, Start, End) when Start > End -> + get_region_cord(Buf, End, Start); +get_region_cord(Buf, Start, End) -> + call(Buf, {get_region_cord, Start, End}). + +undo(Buf, Continuing) -> + call(Buf, {undo, Continuing}). + +delete(Buf, Start, End) -> + replace(Buf, "", Start, End). + +insert(Buf, New, Position) -> + replace(Buf, New, Position, Position). + +add_mark(Buf, Name, Pos, Direction) -> + call(Buf, {add_mark, Name, Pos, Direction}). + +move_mark(Buf, Name, Pos) -> + call(Buf, {move_mark, Name, Pos}). + +mark_pos(Buf, Name) -> + call(Buf, {mark_pos, Name}). + +point_max(Buf) -> + call(Buf, point_max). + +point_min(Buf) -> + 1. + +walk_backward(Buf, Fun, Pos) -> + call(Buf, {walk_backward, Fun, Pos}). + +walk_forward(Buf, Fun, Pos) -> + call(Buf, {walk_forward, Fun, Pos}). + +walk(Buf, Direction, Fun, Pos) -> + call(Buf, {walk, Direction, Fun, Pos}). + +wait_available(Buf) -> + call(Buf, wait_available). + +borrow(Buf) -> + call(Buf, {borrow, self()}). + +return(Buf) -> + call(Buf, {return, self()}). + +revoke(Buf) -> + call(Buf, revoke). + +regexp_search(Buf, RE, Pos, Direction) -> + call(Buf, {regexp_search, RE, Pos, Direction}). + +%% Asynchronous locking + +%% Returns: ref() +async_borrow(Buf) -> + Ref = make_ref(), + cast(Buf, {borrow, self()}). + +async_return(Buf) -> + cast(Buf, {return, self()}). + +%% Internals + +call(Buf, Msg) -> + Buf ! {call, self(), Msg}, + receive {reply, R} -> R end. + +cast(Buf, Msg) -> + Buf ! {cast, Msg}, + ok. + +init(Name) -> + {'EXIT', Reason} = (loop(#state{name=Name, + mode=edit_lib:fundamental_mode_rec(), + text=edit_text:new()})), + io:format("Buffer ~p crashed: ~p~n", [Name, Reason]), + exit(Reason). + +loop(State) -> + receive + %% get_text + {call, From, get_text} -> + From ! {reply, cord:to_list(edit_text:cord(State#state.text))}, + edit_buf:loop(State); + %% set_text + {call, From, {set_text, Text}} -> + Cord = edit_text:cord(State#state.text), + NewCmd = {replace, Text, 1, cord:cord_size(Cord) + 1}, + self() ! {call, From, NewCmd}, + edit_buf:loop(State); + %% get_cord + {call, From, get_cord} -> + From ! {reply, edit_text:cord(State#state.text)}, + edit_buf:loop(State); + %% get_size + {call, From, get_size} -> + From ! {reply, cord:cord_size(edit_text:cord(State#state.text))}, + edit_buf:loop(State); + %% set_filename + {call, From, {set_filename, Filename}} -> + NewState = State#state{filename=Filename}, + From ! {reply, ok}, + edit_buf:loop(NewState); + %% get_filename + {call, From, get_filename} -> + From ! {reply, State#state.filename}, + edit_buf:loop(State); + %% set_mode + {call, From, {set_mode, Mode}} -> + From ! {reply, ok}, + edit_buf:loop(State#state{mode=Mode}); + %% get_mode + {call, From, get_mode} -> + From ! {reply, State#state.mode}, + edit_buf:loop(State); + %% replace + {call, From, {replace, Text, Start, End}} -> + NewText = edit_text:replace(State#state.text,Text,Start,End-Start), + From ! {reply, ok}, + edit_buf:loop(State#state{text=NewText}); + +% NewText = cord:replace(State#state.text, Text, Start, End-Start), +% Marks1 = update_marks(State#state.marks, Text, Start, End), +% State2 = State#state{text=NewText, marks=Marks1}, +% Undo = [{State#state.text, State#state.marks}|State2#state.undo], +% State3 = State2#state{undo=Undo}, +% From ! {reply, ok}, +% edit_buf:loop(State3); + %% undo + {call, From, {undo, Continuing}} -> + NewText = edit_text:undo(State#state.text, Continuing), + From ! {reply, ok}, + edit_buf:loop(State#state{text=NewText}); + +% NewState = do_undo(State, Continuing), +% From ! {reply, ok}, +% edit_buf:loop(NewState); + %% get_region + {call, From, {get_region_cord, Start, End}} -> + Cord = edit_text:cord(State#state.text), + Text = cord:region(Cord, Start, End-Start), + From ! {reply, Text}, + edit_buf:loop(State); + %% add_mark + {call, From, {add_mark, Name, Pos, Direction}} -> + NewText = edit_text:add_mark(State#state.text,Name,Pos,Direction), + From ! {reply, ok}, + edit_buf:loop(State#state{text=NewText}); + %% move_mark + {call, From, {move_mark, Name, Pos}} -> + NewText = edit_text:move_mark(State#state.text, Name, Pos), + From ! {reply, ok}, + edit_buf:loop(State#state{text=NewText}); + %% mark_pos + {call, From, {mark_pos, Name}} -> + From ! {reply, edit_text:mark_pos(State#state.text, Name)}, + edit_buf:loop(State); + %% point_max + {call, From, point_max} -> + Cord = edit_text:cord(State#state.text), + From ! {reply, cord:cord_size(Cord) + 1}, + edit_buf:loop(State); + %% walk_backward + {call, From, {walk_backward, Fun, Pos}} -> + From ! {reply, edit_text:walk_backward(State#state.text,Fun,Pos)}, + edit_buf:loop(State); + %% walk_forward + {call, From, {walk_forward, Fun, Pos}} -> + From ! {reply, edit_text:walk_forward(State#state.text,Fun,Pos)}, + edit_buf:loop(State); + %% walk + {call, From, {walk, Direction, Fun, Pos}} -> + Result = case Direction of + forward -> + edit_text:walk_forward(State#state.text,Fun,Pos); + backward -> + edit_text:walk_backward(State#state.text,Fun,Pos) + end, + From ! {reply, Result}, + edit_buf:loop(State); + %% wait_available + {call, From, wait_available} when State#state.borrower == nobody -> + From ! {reply, true}, + edit_buf:loop(State); + %% borrow + {call, From, {borrow, Borrower}} when State#state.borrower == nobody -> + From ! {reply, true}, + io:format("Borrowing buffer for ~p at ~s~n", [Borrower, + os:cmd("date")]), + %% monitor the borrower so we know when to release + erlang:monitor(process, Borrower), + edit_buf:loop(State#state{borrower=Borrower}); + %% return + {call, From, {return, Who}} when Who == State#state.borrower -> + edit_buf:loop(State#state{borrower=nobody}); + {'DOWN', _, process, Who, _} when Who == State#state.borrower -> + edit_buf:loop(State#state{borrower=nobody}); + %% revoke + {call, From, revoke} -> + case State#state.borrower of + nobody -> + From ! {reply, false}, + true; + Who -> + From ! {reply, true}, + exit(Who, buffer_revoked) + end, + edit_buf:loop(State#state{borrower=nobody}); + %% regexp_search + {call, From, {regexp_search, RE, Pos, Direction}} -> + Cord = edit_text:cord(State#state.text), + From ! {reply, cord_regexp:first_match(RE, Cord, Pos, Direction)}, + edit_buf:loop(State); + %% kill + {call, From, kill} -> + From ! {reply, ok}; + %% --------------------------------------------------------------- + %% Casts + + %% {borrow, Who} --> {loan, Buffer} + {cast, {borrow, Who}} when State#state.borrower == nobody -> + Who ! {loan, State#state.name}, + erlang:monitor(process, Who), + loop(State#state{borrower=Who}); + %% {return, ByWho} --> void + {cast, {return, Who}} when Who == State#state.borrower -> + loop(State#state{borrower=nobody}) + end. + +%% Debug + +log(FmtString, Args) -> + log(io_lib:format(FmtString, Args)). + +log(String) -> + {ok, Fd} = file:open("edit_buf.log", [append, raw]), + file:write(Fd, String), + file:close(Fd). + diff --git a/src/edit_complete.erl b/src/edit_complete.erl new file mode 100644 index 0000000..b955574 --- /dev/null +++ b/src/edit_complete.erl @@ -0,0 +1,170 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_complete.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Minibuffer completion. +%%% Created : 26 Mar 2001 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_complete). +-author('luke@bluetail.com'). + +-include_lib("ermacs/include/edit.hrl"). +-include_lib("kernel/include/file.hrl"). + +-compile(export_all). +%%-export([Function/Arity, ...]). + +-import(edit_lib, [buffer/1]). + +-define(completions_buffer, '*Completions*'). + +%% Variables: +%% completion_state = initial | ambiguous | showing | nomatch +%% completion_type = file | boring_atom() +%% completion_prev_path = string() +completion_init(Type) -> + edit_var:set(completion_state, fresh), + edit_var:set(completion_prev_path, ""), + edit_var:set(completion_type, Type), + ok. + +complete(State) -> + complete(State, edit_var:lookup(completion_type)). + +complete(State, file) -> + MBuf = buffer(State), + Path = edit_buf:get_text(MBuf), + FSMState = case edit_var:lookup(completion_prev_path) of + Path -> + edit_var:lookup(completion_state); + _ -> + %% Different path, reset fsm + initial + end, + Result = filename_complete(Path), + {State1, NewFSMState, NewPath} = + do_complete(State, FSMState, Path, Result), + edit_buf:set_text(MBuf, NewPath), + edit_var:set(completion_state, NewFSMState), + edit_var:set(completion_prev_path, NewPath), + State1; +complete(State, _) -> + State. + +do_complete(EditState, FSMState, Path, Result) -> + {NextFSMState, Action} = complete_fsm(FSMState, Result), + {EditState1, NewPath} = action(EditState, Path, Action), + {EditState1, NextFSMState, NewPath}. + +%% complete_fsm(State, CompletionResult) => {NextState, Action} +%% +%% State = initial | ambiguous | showing | nomatch +%% CompletionResult = {unique, Path} | {completions, Path, List} | nomatch +%% Action = nop | scroll | {show, List} | {rewrite, Path} + +%% FIXME: Add transitions to allow for files being created/deleted +%% between inputs. + +complete_fsm(initial, {unique, New}) -> + {initial, {rewrite, New}}; +complete_fsm(initial, {completions, New, List}) -> + {ambiguous, {rewrite, New}}; +complete_fsm(initial, nomatch) -> + {nomatch, nop}; + +complete_fsm(ambiguous, {completions, New, List}) -> + {showing, {show, List}}; + +complete_fsm(showing, {completions, New, List}) -> + {showing, scroll}; + +complete_fsm(nomatch, nomatch) -> + {nomatch, nop}; + +%% catch all for unexpected cases +complete_fsm(_, _) -> + {nomatch, nop}. + +%% action(State, Path, Action) => {State', Path'} +action(State, Path, nop) -> + {State, Path}; +action(State, Path, scroll) -> + {edit_util:window_map(State, fun maybe_complete_scroll/1), Path}; +action(State, Path, {show, List}) -> + {NewState, Buf} = make_completion_buffer(State, List), + {edit_util:popup_buffer(NewState, Buf), Path}; +action(State, Path, {rewrite, NewPath}) -> + {State, NewPath}. + +make_completion_buffer(St, AbsStrings) -> + BaseStrings = lists:map(fun(X) -> filename:basename(X) end, + AbsStrings), + Strings = lists:sort(BaseStrings), + Name = ?completions_buffer, + edit_buf:new(Name), + edit_buf:set_text(Name, ""), + lists:foreach(fun(String) -> + P = edit_buf:mark_pos(Name, point), + edit_buf:insert(Name, String ++ "\n", P) + end, + Strings), + edit_buf:move_mark(Name, point, 1), + St1 = case lists:member(Name, St#state.buffers) of + true -> + St; + false -> + St#state{buffers=[Name|St#state.buffers]} + end, + {St1, Name}. + +maybe_complete_scroll(Win) when Win#window.buffer == ?completions_buffer -> + edit_lib:scroll_down_wrap(Win); +maybe_complete_scroll(Win) -> + Win. + +%% Returns: {unique, Complete} +%% | {completions, LongestPrefix, [string()]} +%% | nomatch +filename_complete(Path) -> + Dir = filename:dirname(Path), + case file:list_dir(Dir) of + {ok, BaseNames} -> + Names1 = lists:map(fun(X) -> filename:join(Dir, X) end, + BaseNames), + Names2 = lists:map(fun dirify/1, Names1), + string_complete(Path, Names2); + Err -> + nomatch + end. + +%% Append a "/" to directory names if they don't already have them. +dirify(Path) -> + case file:read_file_info(Path) of + {ok, Inf} when Inf#file_info.type == directory -> + case lists:last(Path) of + $/ -> + Path; + _ -> + Path ++ "/" + end; + _ -> + Path + end. + +string_complete(In, L) -> + case lists:filter(fun(X) -> lists:prefix(In, X) end, L) of + [C] -> + {unique, C}; + [] -> + nomatch; + Completions -> + {completions, longest_prefix(Completions), Completions} + end. + +longest_prefix([]) -> []; +longest_prefix([A, B | T]) -> longest_prefix([longest_prefix1(A, B) | T]); +longest_prefix([X]) -> X. + +longest_prefix1([X|T1], [X|T2]) -> [X|longest_prefix1(T1, T2)]; +longest_prefix1(_, _) -> []. + diff --git a/src/edit_display.erl b/src/edit_display.erl new file mode 100644 index 0000000..e732c62 --- /dev/null +++ b/src/edit_display.erl @@ -0,0 +1,154 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_display.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Editor display process: talks to curses +%%% Created : 16 Sep 2000 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_display). +-author('luke@bluetail.com'). + +-include_lib("ermacs/include/edit.hrl"). + +-compile(export_all). +%%-export([Function/Arity, ...]). + +draw_window(Window) when Window#window.minibuffer == true, + Window#window.status_text /= undefined -> + ?EDIT_TERMINAL:move_to(0, Window#window.y), + draw_line(Window#window.status_text), + Window#window{status_text=undefined}; +draw_window(Window) -> + try_update(Window). + +try_update(Window) -> + Buf = Window#window.buffer, + PointMax = edit_buf:point_max(Buf), + DStart = edit_buf:mark_pos(Buf, Window#window.start_mark), + Scan = edit_lib:beginning_of_line_pos(Buf, DStart), + Point = edit_buf:mark_pos(Buf, point), + Chars = (?EDIT_TERMINAL:width() * ?EDIT_TERMINAL:height()) * 4, %% FIXME + Text = edit_buf:get_region(Buf, Scan, min(PointMax, Scan + Chars)), + ?EDIT_TERMINAL:move_to(0, Window#window.y), + Rows = edit_window:text_lines(Window), + Prefix = Window#window.prefix, + PLen = length(Prefix), + PAcc = lists:reverse(Prefix), + case try_update_loop(Text,Rows,Scan,PLen,0,Point,undefined,PAcc) of + {X, Y} -> + %% draw mode line + draw_modeline(Window), + TrimX = edit_lib:min(X, Window#window.width - 1), + ?EDIT_TERMINAL:move_to(TrimX, Y + Window#window.y), + Window; + undefined -> + %% The point wasn't inside the area we drew, so we + %% recenter the display with the point in the middle and + %% then draw again. + try_update(recenter_window(Window)) + end. + +%% Returns the location of the point in a tuple {X, Y}, or undefined +%% if it wasn't in the area drawn. + +try_update_loop(Text, NRows, Scan, Col, Row, Point, PointXY, Acc) + when Scan == Point, + PointXY == undefined -> + try_update_loop(Text,NRows,Scan,Col,Row,Point,{Col, Row},Acc); +try_update_loop([$\n|T], NRows, Scan, Col, Row, Point, PointXY, Acc) -> + draw_line(lists:reverse(Acc)), + ?EDIT_TERMINAL:newline(), + NextRow = Row+1, + if NextRow == NRows -> + PointXY; + true -> + try_update_loop(T,NRows,Scan+1,0,Row+1,Point,PointXY, []) + end; +try_update_loop([$\t|T], NRows, Scan, Col, Row, Point, PointXY, Acc) -> + Size = 8 - (Col rem 8), + Tab = lists:duplicate(Size, $ ), + try_update_loop(T,NRows,Scan+1,Col+Size,Row,Point,PointXY,Tab++Acc); +try_update_loop([H|T], NRows, Scan, Col, Row, Point, PointXY, Acc) -> + try_update_loop(T,NRows,Scan+1,Col+1,Row,Point,PointXY,[H|Acc]); +try_update_loop([], NRows, Scan, Col, Row, Point, PointXY, Acc) -> + draw_line(lists:reverse(Acc)), + RemainingRows = NRows - Row, + %% draw empty lines until the end + dotimes(fun() -> draw_line([]), + ?EDIT_TERMINAL:newline() + end, + RemainingRows), + PointXY. + +draw_line(L) -> + Wth = ?EDIT_TERMINAL:width(), + Str = trunc_line(L, Wth), + ?EDIT_TERMINAL:put_string(L), + ?EDIT_TERMINAL:erase_to_eol(). + +trunc_line([H], 1) -> [H]; +trunc_line(_, 1) -> [$$]; +trunc_line([H|T], N) -> [H|trunc_line(T, N-1)]; +trunc_line([], _) -> []. + +draw_modeline(Window) when Window#window.minibuffer == true -> + ok; +draw_modeline(Window) -> + Buffer = Window#window.buffer, + Where = modeline_where(Window, Buffer), + Text = lists:flatten( + io_lib:format("--:?? ~s (~s) ~s", + [atom_to_list(Buffer), + (edit_buf:get_mode(Buffer))#mode.name, + Where])), + ?EDIT_TERMINAL:font_reverse(), + ?EDIT_TERMINAL:move_to(0, Window#window.y + + edit_window:physical_lines(Window) - 1), + draw_line(Text), + ?EDIT_TERMINAL:font_normal(). + +modeline_where(Window, Buffer) -> + case edit_buf:get_size(Buffer) of + 0 -> + "ALL"; + BSize -> + Start = edit_buf:mark_pos(Buffer, Window#window.start_mark), + Percentage = trunc(Start * 100 / BSize), + io_lib:format("~p%", [Percentage]) + end. + +%% Update the display_start of a window so that it presents the point +%% in the middle of the screen. +recenter_window(Window) -> + Buf = Window#window.buffer, + Height = edit_window:text_lines(Window), + Pos = backward_lines(Buf, trunc(Height / 2)), + edit_buf:move_mark(Buf, Window#window.start_mark, Pos), + Window. + +backward_lines(Buf, N) -> + StartPos = edit_lib:beginning_of_line_pos(Buf), + edit_buf:walk_backward(Buf, + fun(X) -> back_lines(X, N, StartPos) end, + StartPos). + +back_lines(finish, N, Pos) -> + {result, 1}; +back_lines($\n, N, Pos) -> + if + N == 1 -> + {result, Pos}; + true -> + {more, fun(New) -> back_lines(New, N-1, Pos-1) end} + end; +back_lines(_, N, Pos) -> + {more, fun(New) -> back_lines(New, N, Pos-1) end}. + +dotimes(Fun, 0) -> + true; +dotimes(Fun, N) when integer(N), N > 0 -> + Fun(), + dotimes(Fun, N-1). + +min(X, Y) when X < Y -> X; +min(X, Y) -> Y. diff --git a/src/edit_display.erl.slow b/src/edit_display.erl.slow new file mode 100644 index 0000000..54a1d01 --- /dev/null +++ b/src/edit_display.erl.slow @@ -0,0 +1,160 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_display.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Editor display process: talks to curses +%%% Created : 16 Sep 2000 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_display). +-author('luke@bluetail.com'). + +-include_lib("ermacs/include/edit.hrl"). + +-compile(export_all). +%%-export([Function/Arity, ...]). + +draw_window(Window) when Window#window.minibuffer == true, + Window#window.status_text /= undefined -> + ?EDIT_TERMINAL:move_to(0, Window#window.y), + draw_line(Window#window.status_text), + Window#window{status_text=undefined}; +draw_window(Window) -> + try_update(Window). + +try_update(Window) -> + Buf = Window#window.buffer, + PointMax = edit_buf:point_max(Buf), + DStart = edit_buf:mark_pos(Buf, Window#window.start_mark), + Scan = edit_lib:beginning_of_line_pos(Buf, DStart), + Point = edit_buf:mark_pos(Buf, point), + Cord = edit_buf:get_cord(Buf), + {_, CordInFront} = cord:split(Cord, Scan-1), + Walker = cord:walker(CordInFront, forward), + ?EDIT_TERMINAL:move_to(0, Window#window.y), + Rows = edit_window:text_lines(Window), + Prefix = Window#window.prefix, + PLen = length(Prefix), + PAcc = lists:reverse(Prefix), + case try_update_loop(Walker,Rows,Scan,PLen,0,Point,undefined,PAcc) of + {X, Y} -> + %% draw mode line + draw_modeline(Window), + TrimX = edit_lib:min(X, Window#window.width - 1), + ?EDIT_TERMINAL:move_to(TrimX, Y + Window#window.y), + Window; + undefined -> + %% The point wasn't inside the area we drew, so we + %% recenter the display with the point in the middle and + %% then draw again. + try_update(recenter_window(Window)) + end. + +%% Returns the location of the point in a tuple {X, Y}, or undefined +%% if it wasn't in the area drawn. + +try_update_loop(W0, NRows, Scan, Col, Row, Point, PointXY, Acc) + when Scan == Point, PointXY == undefined -> + try_update_loop(W0, NRows, Scan, Col, Row, Point, {Col,Row}, Acc); +try_update_loop(W0, NRows, Scan, Col, Row, Point, PointXY, Acc) -> + {Ch, W1} = cord:walker_next(W0), + case Ch of + done -> + draw_line(lists:reverse(Acc)), + RemainingRows = NRows - Row, + %% draw empty lines until the end + dotimes(fun() -> draw_line([]), + ?EDIT_TERMINAL:newline() + end, + RemainingRows), + PointXY; + $\n -> + draw_line(lists:reverse(Acc)), + ?EDIT_TERMINAL:newline(), + NextRow = Row+1, + if NextRow == NRows -> + PointXY; + true -> + try_update_loop(W1,NRows,Scan+1,0,Row+1,Point,PointXY,[]) + end; + $\t -> + Size = 8 - (Col rem 8), + Tab = lists:duplicate(Size, $ ), + Acc1 = Tab++Acc, + try_update_loop(W1,NRows,Scan+1,Col+Size,Row,Point,PointXY,Acc1); + Ch -> + Acc1 = [Ch|Acc], + try_update_loop(W1,NRows,Scan+1,Col+1,Row,Point,PointXY,Acc1) + end. + +draw_line(L) -> + Wth = ?EDIT_TERMINAL:width(), + Str = trunc_line(L, Wth), + ?EDIT_TERMINAL:put_string(L), + ?EDIT_TERMINAL:erase_to_eol(). + +trunc_line([H], 1) -> [H]; +trunc_line(_, 1) -> [$$]; +trunc_line([H|T], N) -> [H|trunc_line(T, N-1)]; +trunc_line([], _) -> []. + +draw_modeline(Window) when Window#window.minibuffer == true -> + ok; +draw_modeline(Window) -> + Buffer = Window#window.buffer, + Where = modeline_where(Window, Buffer), + Text = lists:flatten( + io_lib:format("--:?? ~s (~s) ~s", + [atom_to_list(Buffer), + (edit_buf:get_mode(Buffer))#mode.name, + Where])), + ?EDIT_TERMINAL:font_reverse(), + ?EDIT_TERMINAL:move_to(0, Window#window.y + + edit_window:physical_lines(Window) - 1), + draw_line(Text), + ?EDIT_TERMINAL:font_normal(). + +modeline_where(Window, Buffer) -> + case edit_buf:get_size(Buffer) of + 0 -> + "ALL"; + BSize -> + Start = edit_buf:mark_pos(Buffer, Window#window.start_mark), + Percentage = trunc(Start * 100 / BSize), + io_lib:format("~p%", [Percentage]) + end. + +%% Update the display_start of a window so that it presents the point +%% in the middle of the screen. +recenter_window(Window) -> + Buf = Window#window.buffer, + Height = edit_window:text_lines(Window), + Pos = backward_lines(Buf, trunc(Height / 2)), + edit_buf:move_mark(Buf, Window#window.start_mark, Pos), + Window. + +backward_lines(Buf, N) -> + StartPos = edit_lib:beginning_of_line_pos(Buf), + edit_buf:walk_backward(Buf, + fun(X) -> back_lines(X, N, StartPos) end, + StartPos). + +back_lines(finish, N, Pos) -> + {result, 1}; +back_lines($\n, N, Pos) -> + if + N == 1 -> + {result, Pos}; + true -> + {more, fun(New) -> back_lines(New, N-1, Pos-1) end} + end; +back_lines(_, N, Pos) -> + {more, fun(New) -> back_lines(New, N, Pos-1) end}. + +dotimes(Fun, 0) -> + true; +dotimes(Fun, N) when integer(N), N > 0 -> + Fun(), + dotimes(Fun, N-1). + +min(X, Y) when X < Y -> X; +min(X, Y) -> Y. diff --git a/src/edit_eval.erl b/src/edit_eval.erl new file mode 100644 index 0000000..24a102e --- /dev/null +++ b/src/edit_eval.erl @@ -0,0 +1,318 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_eval.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Erlang code evaluation +%%% Created : 21 Jan 2001 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_eval). +-author('luke@bluetail.com'). + +-include_lib("ermacs/include/edit.hrl"). + +-compile({parse_transform, edit_transform}). + +-import(edit_lib, [buffer/1]). + +-compile(export_all). +%%-export([Function/Arity, ...]). + +-define(history, erlang_interaction_history). + +eval_buffer(State) -> + B = buffer(State), + Text = edit_buf:get_text(B), + {ok, Scan, _} = erl_scan:string(Text), % safe ? + case erl_parse:parse_exprs(Scan) of + {ok, Parse} -> + case catch erl_eval:exprs(Parse, []) of + {value, V, _} -> + edit_util:status_msg(State, "~p", [V]); + {'EXIT', Reason} -> + edit_util:status_msg(State, "** exited: ~p **", [Reason]) + end; + {error, {_, erl_parse, Err}} -> + edit_util:status_msg(State, "~p", [Err]) + end. + +eval_string(State, String) -> + {ok, Scan, _} = erl_scan:string(String), % safe ? + eval_tokens(State, Scan, []). + +eval_tokens(Buf, Tokens, Bindings) -> + case erl_parse:parse_exprs(Tokens) of + {ok, Parse} -> + case erl_eval:exprs(Parse, Bindings, lf_handler(Buf)) of + {value, V, NewBs} -> + {ok, V, NewBs}; + Error -> + {error, Error} + end; + {error, {_, erl_parse, Err}} -> + {error, fmt("~s", [Err])} + end. + +lf_handler(Buf) -> + {eval, {?MODULE, local_func}, [Buf]}. + +fmt(Fmt, Args) -> lists:flatten(io_lib:format(Fmt, Args)). + +-command({eval_expression, [{erl_expr, "Eval:"}]}). + +eval_expression(State, Expr) -> + Str = case eval_string(State, Expr) of + {ok, Val, NewBS} -> + fmt("~p", [Val]); + {error, Rsn} -> + fmt("~p", [Rsn]) + end, + edit_util:popup_message(State, '*Eval*', Str). + +region(Buffer) -> + {find_start(Buffer), edit_lib:end_of_line_pos(Buffer)}. + +%% ---------------------------------------------------------------------- +%% Interactive evaluation major mode +%% ---------------------------------------------------------------------- + +-define(keymap, erlang_interaction_map). +-define(output_mark, erlang_output). +-define(PROMPT, ">> "). + +erlang_interaction_mode(State) -> + case edit_keymap:keymap_exists(?keymap) of + false -> + init_map(); + true -> + ok + end, + Mode = #mode{name="Erlang Interaction", + id=erlang_interaction, + keymaps=[?keymap]}, + Buf = buffer(State), + edit_buf:set_mode(Buf, Mode), + edit_buf:add_mark(Buf, ?output_mark, 1, forward), + ok. + +init_map() -> + edit_keymap:new(?keymap), + edit_keymap:bind_each(?keymap, bindings()). + +bindings() -> + [{"C-m", {?MODULE, interactive_eval, []}}, + {"C-a", {?MODULE, beginning_of_prompt, []}}] + ++ edit_history:bindings(?history, {?MODULE, region}). + +interactive_eval(State) -> + P = find_start(buffer(State)), + interactive_eval1(State, P, P). + +interactive_eval1(State, CodeStart, SearchStart) -> + Buf = buffer(State), + Max = edit_buf:point_max(Buf), + Pred = fun(C) -> C == $\n end, + CodeEnd = edit_lib:find_char_forward(Buf, Pred, SearchStart, Max), + Region = edit_buf:get_region(Buf, CodeStart, CodeEnd), + Bindings = edit_var:lookup(erlang_interaction_bindings, []), + case erl_scan:tokens([], Region ++ "\n", 1) of + {done, {ok, Tokens, _}, _} -> % ok - enough + %% Move point to the end + edit_buf:move_mark(Buf, point, CodeEnd), + %% eval + edit_util:spawn_with([Buf], + fun() -> + eval_async(Buf, Tokens, Bindings) + end), + edit_history:add(?history, Region); + {more, _} when CodeEnd == Max -> + edit_buf:insert(Buf, "\n", edit_buf:mark_pos(Buf, point)); + {more, _} -> + interactive_eval1(State, CodeStart, CodeEnd + 1) + end. + +eval_async(Buf, Tokens, Bindings) -> + ensure_serv_started(Buf), + %% update output marker + edit_buf:insert(Buf, "\n", edit_buf:mark_pos(Buf, point)), + InsertionPoint = edit_buf:mark_pos(Buf, point), + edit_buf:move_mark(Buf, ?output_mark, InsertionPoint), + %% eval + Str = case serv_eval(Buf, Tokens, Bindings) of + {ok, Value, NewBs} -> + %% FIXME: bindings + edit_var:set(erlang_interaction_bindings, NewBs), + fmt("=> ~p\n", [Value]); + {error, {'EXIT', Rsn}} -> + fmt("** exited: ~p **\n", [Rsn]); + {error, Rsn} -> + fmt("** ~s **\n", [Rsn]) + end, + edit_buf:insert(Buf, Str, edit_buf:mark_pos(Buf, point)), + PromptPos = edit_buf:mark_pos(Buf, point), + edit_buf:insert(Buf, ?PROMPT, PromptPos), + edit_buf:move_mark(Buf, ?output_mark, PromptPos), + redraw(), + ok. + +redraw() -> + edit:invoke_async(edit_lib, nop, []). + +%% Go to the beginning of the line or directly after the prompt, +%% whichever is closer. +beginning_of_prompt(State) -> + Buf = buffer(State), + edit_buf:move_mark(Buf, point, beginning_of_prompt_pos(Buf)). + +beginning_of_prompt_pos(Buf) -> + Point = edit_buf:mark_pos(Buf, point), + BOL = edit_lib:beginning_of_line_pos(Buf), + case find_start(Buf) of + Pos when Pos > BOL, Pos =< Point -> + Pos; + _ -> + BOL + end. + +find_start(Buf) -> + case edit_lib:find_string_backward(Buf, ?PROMPT) of + not_found -> + 1; + X -> + edit_lib:min(X + length(?PROMPT), edit_buf:point_max(Buf)) + end. + +%% local_func(Function, Args, Bindings, Shell) -> +%% {value,Val,Bs} +%% Evaluate local functions, including shell commands. + +local_func(F, As0, Bs0, Buf) -> + {As,Bs} = erl_eval:expr_list(As0, Bs0, {eval,{?MODULE,local_func},[Buf]}), + case erlang:function_exported(user_default, F, length(As)) of + true -> + {value,apply(user_default, F, As),Bs}; + false -> + {value,apply(shell_default, F, As),Bs} + end; +local_func(F, As0, Bs0, Buf) -> + {As,Bs} = erl_eval:expr_list(As0, Bs0, {eval,{?MODULE,local_func},[Buf]}), + {value,apply(F, As),Bs}. + + +%% ---------------------------------------------------------------------- +%% Evaluation server API + +%% ok | already_started +ensure_serv_started(Buffer) -> + case whereis(serv_name(Buffer)) of + undefined -> + Pid = spawn(?MODULE, eval_serv_init, [Buffer]), + register(serv_name(Buffer), Pid), + ok; + Pid -> + already_started + end. + +serv_eval(Buffer, Tokens, Bindings) -> + Serv = whereis(serv_name(Buffer)), + erlang:monitor(process, Serv), + Serv ! {call, self(), {eval, Tokens, Bindings}}, + receive + {reply, Result} -> + Result; + {'DOWN', _, process, Serv, Rsn} -> + {error, {'EXIT', Rsn}} + end. + +%% serv_name(foo) -> 'foo:eval_serv' +serv_name(Buffer) -> list_to_atom(atom_to_list(Buffer) ++ ":eval_serv"). + +%% ---------------------------------------------------------------------- +%% Eval server loop + +eval_serv_init(Buffer) -> + erlang:monitor(process, whereis(Buffer)), + GL = gl_spawn_link(Buffer), + group_leader(GL, self()), + eval_serv_loop(Buffer, GL). + +eval_serv_loop(Buffer, GL) -> + receive + {call, From, {eval, Tokens, Bindings}} -> + GL ! {got_shared_lock, self()}, + Result = eval_tokens(Buffer, Tokens, Bindings), + GL ! {releasing_shared_lock, self()}, + receive gl_ack -> ok end, + From ! {reply, Result}, + eval_serv_loop(Buffer, GL); + {'DOWN', _, _, _, _} -> + exit(buffer_died) + end. + +%% ---------------------------------------------------------------------- +%% Group leader process for putting output into a buffer. +%% +%% The evaluation server sends us: +%% {got_shared_lock, HolderPid} +%% followed by: +%% {releasing_shared_lock, HolderPid} +%% Between these messages, we informally share the lock on the buffer. + +gl_spawn_link(Buffer) -> + spawn_link(?MODULE, gl_serv_init, [Buffer]). + +gl_serv_init(Buffer) -> + gl_serv_without_lock(Buffer). + +%% State: Nothing known about Buffer's lock +gl_serv_without_lock(Buffer) -> + ?debug("STATE: no lock~n", []), + receive + Msg = {io_request, From, ReplyAs, Req} -> + gl_serv_work(Buffer, Msg); + {got_shared_lock, Holder} -> + gl_serv_with_lock(Buffer) + end. + +%% State: Buffer is locked by eval_server, so we can use it. +gl_serv_with_lock(Buffer) -> + receive + Msg = {io_request, From, ReplyAs, Req} -> + do_gl_request(Buffer, Msg), + gl_serv_with_lock(Buffer); + {releasing_shared_lock, Holder} -> + Holder ! gl_ack, + gl_serv_without_lock(Buffer) + end. + +%% Action: Acquire the lock on Buffer, perform a request, and release. +gl_serv_work(Buffer, Req) -> + edit_buf:async_borrow(Buffer), + receive + {loan, Buffer} -> + do_gl_request(Buffer, Req), + edit_buf:async_return(Buffer), + gl_serv_without_lock(Buffer); + got_shared_lock -> + do_gl_request(Buffer, Req), + gl_serv_with_lock(Buffer) + end. + +%% Perform an I/O request by writing the result into the buffer. +do_gl_request(Buffer, {io_request, From, ReplyAs, {put_chars, M, F, A}}) -> + case catch apply(M, F, A) of + {'EXIT', Reason} -> + exit(From, Reason); + IOList -> + do_gl_request(Buffer, + {io_request, From, ReplyAs, {put_chars, IOList}}) + end; +do_gl_request(Buffer, {io_request, From, ReplyAs, {put_chars, IOList}}) -> + From ! {io_reply, ReplyAs, ok}, + Bin = list_to_binary(IOList), + Pos = edit_buf:mark_pos(Buffer, ?output_mark), + edit_buf:insert(Buffer, Bin, Pos), + redraw(); +do_gl_request(Buffer, {io_request, From, ReplyAs, {get_until, _, _, _}}) -> + From ! {io_reply, ReplyAs, eof}. + + diff --git a/src/edit_extended.erl b/src/edit_extended.erl new file mode 100644 index 0000000..31920ca --- /dev/null +++ b/src/edit_extended.erl @@ -0,0 +1,149 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_extended.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : execute-extended-command (emacs lingo) +%%% Created : 14 Jan 2001 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_extended). +-author('luke@bluetail.com'). + +-include_lib("ermacs/include/edit.hrl"). + +-compile(export_all). +-compile({parse_transform, edit_transform}). + +extended_command(State, Mod, Func, Args) -> + {Params, _Doc} = find_cmd_info(Mod, Func), + NewState = execute(State, Mod, Func, Args, Params), + NewState#state{lastcmd={Mod, Func, Args}}. + +execute(State, Mod, Func, Args, []) -> + case catch apply(Mod, Func, [State | Args]) of + S when record(S, state) -> + S; + {'EXIT', Rsn} -> + io:format("** Crash: ~p~n", [Rsn]), + edit_util:popup_message(State, '*Error*', "** Crash: ~p", [Rsn]); + Other -> + State + end; +execute(State, Mod, Func, Args, Params) + when State#state.pending_cmd /= undefined -> + edit_util:status_msg(State, "Minibuffer busy!"); +execute(State, Mod, Func, Args, Params) -> + Cont = make_execute_continuation(Mod, Func, Args, Params), + {Type, Prompt} = hd(Params), + edit_var:set(extended_arg_type, Type), + edit_complete:completion_init(Type), + default_init(State, Type), + State1 = activate_continuation(State, Cont, Prompt ++ " "), + State2 = edit_util:select_window(State1, + fun(W) -> + W#window.minibuffer + end), + case State#state.pending_win of + undefined -> + State2#state{pending_win=(State#state.curwin)#window.id}; + _ -> + State2 + end. + +default_init(State, file) -> + edit_buf:set_text(minibuffer, edit_util:pwd(State)); +default_init(State, _) -> + ok. + +make_execute_continuation(Mod, Func, Args, Params) -> + fun(State, NewArg) -> + State1 = if length(Params) == 1 -> + %% last argument, we're done! + deactivate_continuation(State); + true -> + State + end, + execute(State1, Mod, Func, Args ++ [NewArg], tl(Params)) + end. + +activate_continuation(State, C, Prompt) -> + State1 = State#state{pending_cmd=C}, + Enabler = fun(Win) -> Win#window{active=true, prefix=Prompt} end, + edit_util:update_minibuffer_window(State1, Enabler). + +deactivate_continuation(State) -> + edit_buf:set_text(minibuffer, ""), + State1 = State#state{pending_cmd=undefined}, + Disabler = fun(Win) -> Win#window{active=false, prefix=""} + end, + %% disable minibuffer + State2 = edit_util:update_minibuffer_window(State1, Disabler), + %% reselect the window that the command is being done in + State3 = + edit_util:select_window(State2, + fun(W) -> + W#window.id == State2#state.pending_win + end), + State4 = State3#state{pending_win=undefined}. + + +find_cmd_info(Mod, Func) -> + case catch Mod:command_info() of + {'EXIT', _} -> + {[], ""}; + L when list(L) -> + case lists:keysearch(Func, 1, L) of + {value, {_, Params, Doc}} -> + {Params, Doc}; + _ -> + {[], ""} + end + end. + +%% Callbacks from key bindings + +take_argument(State) when State#state.pending_cmd /= undefined -> + Text = edit_buf:get_text(minibuffer), + edit_history:add(history_var_name(), Text), + Cont = State#state.pending_cmd, + Cont(State, Text). + +abort(State) -> + io:format("Aborted!~n"), + State1 = deactivate_continuation(State). + +%% M-x + + +-command({execute_extended_command, + [{mod_func, "M-x"}], + "Like M-x in emacs."}). + +execute_extended_command(State, MF) -> + case string:tokens(MF, " :") of + [ModStr, FunStr] -> + Mod = list_to_atom(ModStr), + Fun = list_to_atom(FunStr), + edit:invoke_extended_async(Mod, Fun, []), + State; + _ -> + edit_util:status_msg(State, "Bad string; must be \"Mod:Fun\"") + end. + +%% History + +history_move(State, Dir) -> + edit_history:move(State, history_var_name(), history_region_fun(), Dir). + +-command({history_search, [{history_regexp, "History regexp:"}]}). +%% FIXME: This function fails when it tries to activate the minibuffer to +%% search regexp, but the minibuffer is already active. +history_search(State, RE) -> + edit_history:search(State, history_var_name(), history_region_fun(), RE). + +history_var_name() -> + TypeString = atom_to_list(edit_var:lookup(extended_arg_type)), + list_to_atom("extended_history:" ++ TypeString). + +history_region_fun() -> + fun(Buf) -> {1, edit_buf:point_max(Buf)} end. + diff --git a/src/edit_file.erl b/src/edit_file.erl new file mode 100644 index 0000000..5c26de6 --- /dev/null +++ b/src/edit_file.erl @@ -0,0 +1,95 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_file.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : file-related editor commands +%%% Created : 14 Jan 2001 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_file). +-author('luke@bluetail.com'). + +-include_lib("ermacs/include/edit.hrl"). + +-compile(export_all). +-compile({parse_transform, edit_transform}). + +-import(edit_lib, [buffer/1]). + +-command({find_file, [{file, "Filename:"}], "Open a file in a buffer."}). + +mod_init() -> + edit_var:set(auto_mode_alist, []), + ok. + +find_file(State, "") -> + State; +find_file(State, Filename) -> + BufferName = list_to_atom(lists:last(string:tokens(Filename, "/"))), + NewState = find_file1(State, Filename, BufferName, 0), + auto_set_mode(NewState). + +%% Open Filename in the buffer Name. If Name exists and is visiting a +%% different file, then try Name<1>, etc. If we hit a buffer that's +%% already visiting this file, just attach to that. +find_file1(State, Filename, Name, N) -> + BufferName = make_buffer_name(Name, N), + case whereis(BufferName) of + undefined -> + {ok, Buf} = edit_buf:new(BufferName), + edit_buf:set_filename(Buf, filename:absname(Filename)), + State1 = edit_util:set_buffer(State, BufferName), + case cord:new_from_file(Filename) of + {ok, Cord} -> + edit_buf:set_text(Buf, Cord), + edit_buf:move_mark(Buf, point, 1), + State1; + {error, Reason} -> + edit_util:status_msg(State1, "(New file)") + end; + Pid -> + case edit_buf:get_filename(BufferName) of + Filename -> + edit_util:set_buffer(State, BufferName); + _ -> + find_file1(State, Filename, Name, N+1) + end + end. + +make_buffer_name(Atom, 0) -> + Atom; +make_buffer_name(Atom, N) -> + Name = io_lib:format("~s<~p>", [atom_to_list(Atom), N]), + list_to_atom(lists:flatten(Name)). + +save_file(State) -> + case edit_buf:get_filename(buffer(State)) of + undefined -> + edit_util:status_msg(State, "No file associated with buffer"); + Filename -> + Cord = edit_buf:get_cord(buffer(State)), + IO = cord:to_iolist(Cord), + ok = file:write_file(Filename, IO), + edit_util:status_msg(State, "Wrote ~s", [Filename]) + end. + +auto_set_mode(State) -> + AutoModeList = edit_var:lookup(auto_mode_alist), + Buf = buffer(State), + case edit_buf:get_filename(Buf) of + undefined -> + State; + Filename -> + auto_set_mode(State, Filename, AutoModeList) + end. + +auto_set_mode(State, Filename, []) -> + State; +auto_set_mode(State, Filename, [{RE, {Mod, Fun}}|T]) -> + case regexp:match(Filename, RE) of + nomatch -> + auto_set_mode(State, Filename, T); + {match, _, _} -> + Mod:Fun(State) + end. + + diff --git a/src/edit_genserv.erl b/src/edit_genserv.erl new file mode 100644 index 0000000..0ec5557 --- /dev/null +++ b/src/edit_genserv.erl @@ -0,0 +1,50 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_genserv.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Generic utility functions for servers. +%%% Created : 28 Apr 2001 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +%% This is a library for writing servers. The idea is to have a less +%% abstract gen_server, in which the protocols are exposed and the +%% servers still use 'receive' to get requests with pattern matching. + +-module(edit_genserv). +-author('luke@bluetail.com'). + +-export([start_link/4, call/2, call/3, reply/2, cast/2]). + +%% Start a named process, if it's not already running. +%% +%% Returns: {ok, Pid} | {error, Reason} +start_link(Name, M, F, A) -> + case whereis(Name) of + Pid when pid(Pid) -> + {error, {already_started, Pid}}; + undefined -> + Pid = spawn_link(M, F, A), + register(Name, Pid), + {ok, Pid} + end. + +%% Protocol for calls (from 'gen'): +%% -> {call, {SenderPid, Ref}, Req} +%% <- {Ref, Reply} + +%% Synchronous call. +call(Pid, Req) -> + call(Pid, Req, 10000). + +call(Pid, Req, Timeout) -> + {ok, Reply} = gen:call(Pid, call, Req, Timeout), + Reply. + +reply(To, Reply) -> + gen:reply(To, Reply). + +%% Protocol for casts: +%% -> {cast, Req} + +cast(Pid, Req) -> + Pid ! {cast, Req}. + diff --git a/src/edit_globalmap.erl b/src/edit_globalmap.erl new file mode 100644 index 0000000..8751f95 --- /dev/null +++ b/src/edit_globalmap.erl @@ -0,0 +1,126 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_globalmap.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Global keymap +%%% Created : 23 Sep 2000 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_globalmap). +-author('luke@bluetail.com'). + +-export([init/0]). + +%% Initialise global keymap +init() -> + Map = edit_keymap:new(global_map), + edit_keymap:bind_each(Map, bindings()), + edit_keymap:new(global_cx_map), + edit_keymap:bind_each(global_cx_map, cx_bindings()), + edit_keymap:new(minibuffer_map), + edit_keymap:bind_each(minibuffer_map, minibuffer_bindings()), + edit_keymap:new(term_escape_map), + edit_keymap:bind_each(term_escape_map, term_escape_bindings()), + edit_keymap:new(help_map), + edit_keymap:bind_each(help_map, help_bindings()), + edit_keymap:new(empty_keymap), + global_map. + +bindings() -> + [ + %% Motion + {"C-a", {edit_lib, beginning_of_line, []}}, + {"C-b", {edit_lib, backward_char, []}}, + {"C-e", {edit_lib, end_of_line, []}}, + {"C-f", {edit_lib, forward_char, []}}, + {"M-b", {edit_lib, backward_word, []}}, + {"M-f", {edit_lib, forward_word, []}}, + {"M-<", {edit_lib, start_of_buffer, []}}, + {"M->", {edit_lib, end_of_buffer, []}}, + {"C-n", {edit_lib, next_line, []}}, + {"C-p", {edit_lib, previous_line, []}}, + {"C-M-a", {edit_erlang, beginning_of_function, []}}, + {"C-v", {edit_lib, scroll_down, []}}, + {"M-v", {edit_lib, scroll_up, []}}, + {"C-l", {edit_lib, recenter, []}}, + %% Editing + {"C-h", {edit_lib, delete_char_backward, []}}, + {"C-?", {edit_lib, delete_char_backward, []}}, + {"C-d", {edit_lib, delete_char_forward, []}}, + {"M-d", {edit_lib, delete_word_forward, []}}, + {"C-k", {edit_lib, kill_line, []}}, + {"C-w", {edit_lib, kill_region, [true]}}, + {"M-w", {edit_lib, kill_region, [false]}}, + {"C-y", {edit_lib, yank, []}}, + {"C-o", {edit_lib, open_line, []}}, + {"C-_", {edit_lib, undo, []}}, + {"C-/", {edit_lib, undo, []}}, + {"C-u", {edit_lib, undo, []}}, + %% Searching + {"C-s", {edit_lib, search, [forward]}}, + {"C-r", {edit_lib, search, [backward]}}, + {"C-M-s", {edit_lib, regexp_search, [forward]}}, + {"C-M-r", {edit_lib, regexp_search, [backward]}}, + %% Misc + {"C-@", {edit_lib, set_mark, []}}, + {"M-!", {edit_lib, unix_command, []}}, + {"C-t", {edit_lib, printf, []}}, + {"M-:", {edit_eval, eval_expression, []}}, + {"M-x", {edit_extended, execute_extended_command, []}}, + {"C-g", {edit_lib, abort, []}}, + %% Keymaps + {"M-[", {keymap, term_escape_map}}, + {"C-x", {keymap, global_cx_map}}, + %% Help + {"M-h", {keymap, help_map}}, + %% self insert commands (\r becomes \n) + {$\r, {edit_lib, self_insert_command, [$\n]}} + | [{Ch, {edit_lib, self_insert_command, [Ch]}} || Ch <- self_inserts()] + ]. + +%% ESC [ <key> - for arrows etc +term_escape_bindings() -> + [{"A", {edit_lib, previous_line, []}}, + {"B", {edit_lib, next_line, []}}, + {"C", {edit_lib, forward_char, []}}, + {"D", {edit_lib, backward_char, []}}]. + +%% Bindings after C-x prefix +cx_bindings() -> + [{"C-x", {edit_lib, exchange_point_and_mark, []}}, + {"C-c", {edit_lib, quit, []}}, + {"C-f", {edit_file, find_file, []}}, + {"C-s", {edit_file, save_file, []}}, + {"C-m", {edit_eval, interactive_eval, []}}, + {"i", {edit_eval, erlang_interaction_mode, []}}, + {"f", {edit_lib, fundamental_mode, []}}, + {"r", {edit_eval, back_to_start, []}}, + {"b", {edit_lib, switch_to_buffer, []}}, + {"k", {edit_lib, kill_buffer, []}}, + {"h", {edit_lib, mark_whole_buffer, []}}, + {"o", {edit_lib, next_window, []}}, + {"0", {edit_lib, delete_window, []}}, + {"1", {edit_lib, delete_other_windows, []}}, + {"2", {edit_lib, split_window_vertically, []}}, +% {"e", {edit_erlang, erlang_mode, []}}, + {"d", {edit_lib, buffer_cord_info, []}} + ]. + +minibuffer_bindings() -> + [{"C-m", {edit_extended, take_argument, []}}, + {"C-g", {edit_extended, abort, []}}, + {"C-i", {edit_complete, complete, []}}, + %% generic history + {"M-p", {edit_extended, history_move, [up]}}, + {"M-n", {edit_extended, history_move, [down]}}, + {"M-r", {edit_extended, history_search, []}} + ]. + +help_bindings() -> + [{"k", {edit_help, describe_key, []}}, + {"s", {edit_help, find_source, []}}]. + +self_inserts() -> + %% HACK + "\n\t" ++ lists:seq(32, 126). + %%"\r\n\t" ++ (lists:seq(32, 126) -- [127]). + diff --git a/src/edit_help.erl b/src/edit_help.erl new file mode 100644 index 0000000..dc904cf --- /dev/null +++ b/src/edit_help.erl @@ -0,0 +1,97 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_help.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Help-related functions +%%% Created : 2 Feb 2002 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_help). + +-include_lib("ermacs/include/edit.hrl"). + +-import(edit_lib, [buffer/1]). + +-compile(export_all). +-compile({parse_transform, edit_transform}). + +describe_key(S) -> + edit_util:popup_message(S, '*Help*', description(lookup_key(S))). + +description({Mod, Fun, Args}) -> + case edit_extended:find_cmd_info(Mod, Fun) of + {[], ""} -> + io_lib:format("Command: ~s:~s", [Mod, Fun]); + {Params, Doc} -> + io_lib:format("Command: ~s:~s ~s\n~s", + [Mod, + Fun, + [io_lib:format("~p ", [Param]) || + {Param,_} <- Params], + Doc]) + end; +description(X) -> + lists:flatten(io_lib:format("~p", [X])). + +find_source(S) -> + case lookup_key(S) of + {Mod, Fun, Args} -> + find_source(S, Mod, Fun); + _ -> + edit_lib:status_msg(S, "Not bound to a function") + end. + +find_source(S0, Mod, Fun) -> + case guess_source_file(code:which(Mod)) of + {ok, Filename} -> + S1 = edit_file:find_file(S0, Filename), + Buf = buffer(S1), + edit_buf:move_mark(Buf, point, 1), + edit_lib:regexp_search(S1, forward, [$\n]++atom_to_list(Fun)), + edit_lib:beginning_of_line(S1) + end. + +guess_source_file(S0) -> + case regexp:sub(S0, "ebin", "src") of + {ok, S1, _} -> + case regexp:sub(S1, "beam", "erl") of + {ok, S2, _} -> + case file:read_file_info(S2) of + {ok, _} -> + {ok, S2}; + _ -> + error + end; + _ -> + error + end; + _ -> + error + end. + +%% ---------------------------------------------------------------------- +%% Keymap lookup - cut&paste from edit.erl + +lookup_key(State) -> + io:format("here.~n"), + Buf = buffer(State), + Keymaps = (edit_buf:get_mode(Buf))#mode.keymaps ++ [global_map], + lookup_key(State, Keymaps). + +lookup_key(State, Keymaps) -> + Ch = edit:get_key(), + io:format("Snarfed key: ~p ~p~n", [Ch, Keymaps]), + lookup_key(State, Keymaps, Ch). + +lookup_key(State, [], Ch) -> + unbound; +lookup_key(State, [Keymap|Keymaps], Ch) -> + case edit_keymap:lookup(Keymap, Ch) of + {ok, {keymap, NewMap}} -> + io:format("Retrying with ~p~n", [NewMap]), + lookup_key(State, [NewMap]); + {ok, Cmd} -> + Cmd; + unbound -> + lookup_key(State, Keymaps, Ch) + end. + diff --git a/src/edit_history.erl b/src/edit_history.erl new file mode 100644 index 0000000..750ac35 --- /dev/null +++ b/src/edit_history.erl @@ -0,0 +1,126 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_history.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Command history +%%% Created : 24 Mar 2001 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_history). +-author('luke@bluetail.com'). + +-include_lib("ermacs/include/edit.hrl"). +-compile({parse_transform, edit_transform}). +-import(edit_lib, [buffer/1]). + +-define(HISTORY_MAX_LENGTH, 500). + +%% API +-export([bindings/2, add/2]). + +%% Commands +-export([move/4, search/4]). + +%% BaseVar = atom() name of edit_var variable to put history +%% RegionFn = fun(Buffer) -> {Start, End} of history region +%% +%% Returns a set of (standard) keybindings. +bindings(BaseVar, RegionFn) -> + [{"M-p", {?MODULE, move, [BaseVar, RegionFn, up]}}, + {"M-n", {?MODULE, move, [BaseVar, RegionFn, down]}}, + {"M-r", {?MODULE, search, [BaseVar, RegionFn]}} + ]. + +add(BaseVar, "") -> + %% No blanks in history. + skipped; +add(BaseVar, Item) -> + Hist = edit_var:lookup(BaseVar, []), + if + hd(Hist) == Item -> + %% duplicate, leave history alone + ok; + true -> + edit_var:set(BaseVar, trim_history([Item|Hist])) + end. + +move(State, BaseVar, RegionFn, Direction) -> + IdxVar = index_var(BaseVar), + HVar = list_var(BaseVar), + History = ["" | edit_var:lookup(HVar, [])], + Idx = case continuing_p(State) of + true -> + Prev = edit_var:lookup(IdxVar, 1), + case Direction of + up -> + Prev + 1; + down -> + Prev - 1 + end; + false -> + 2 % 1 is a new empty string + end, + if + Idx >= 1, length(History) >= Idx -> + Buf = buffer(State), + New = lists:nth(Idx, History), + kill_old(Buf, RegionFn), + edit_buf:insert(Buf, New, edit_buf:mark_pos(Buf, point)), + edit_var:set(IdxVar, Idx), + State; + true -> + edit_util:status_msg(State, "No more history") + end. + +-command({search, [{string, "History regexp:"}]}). +search(State, BaseVar, RegionFn, Regexp) -> + History = edit_var:lookup(BaseVar, []), + case find_match(History, Regexp) of + {match, Cmd} -> + Buf = buffer(State), + kill_old(Buf, RegionFn), + edit_buf:insert(Buf, Cmd, edit_buf:mark_pos(Buf, point)), + State; + {error, Err} -> + edit_util:status_msg(State, "Error: " ++ regexp:format_error(Err)); + nomatch -> + edit_util:status_msg(State, "Not found") + end. + +continuing_p(State) -> + case State#state.lastcmd of + {?MODULE, move, _} -> + true; + {?MODULE, search, _} -> + true; + {_, history_move, _} -> + true; + {_, history_search, _} -> + true; + _ -> + false + end. + +kill_old(Buf, RegionFn) -> + {Start, End} = RegionFn(Buf), + edit_buf:delete(Buf, Start, End). + +trim_history(Hist) when length(Hist) > ?HISTORY_MAX_LENGTH -> + lists:sublist(Hist, ?HISTORY_MAX_LENGTH); +trim_history(Hist) -> + Hist. + +find_match([], Regexp) -> + nomatch; +find_match([H|T], Regexp) -> + case regexp:match(H, Regexp) of + nomatch -> + find_match(T, Regexp); + {match, _Start, _Length} -> + {match, H}; + {error, Err} -> + {error, Err} + end. + +list_var(Base) -> Base. +index_var(Base) -> list_to_atom(atom_to_list(Base) ++ "_idx"). + diff --git a/src/edit_input.erl b/src/edit_input.erl new file mode 100644 index 0000000..2f0990a --- /dev/null +++ b/src/edit_input.erl @@ -0,0 +1,34 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_input.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Keyboard input server +%%% Created : 22 Jan 2001 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_input). +-author('luke@bluetail.com'). + +-include_lib("ermacs/include/edit.hrl"). + +-export([start_link/1, loop/1]). + +%% Receiver will be sent {key_input, Char} each time a key is pressed. +start_link(Receiver) -> + Pid = spawn_link(edit_input, loop, [Receiver]), + register(?MODULE, Pid), + Pid. + +loop(Receiver) -> + Ch = case ?EDIT_TERMINAL:read() of + $\n -> + $\r; + 145 -> % C-M-q is reserved for panic + panic(); + X -> + X + end, + Receiver ! {key_input, Ch}, + loop(Receiver). + +panic() -> + halt(). diff --git a/src/edit_keymap.erl b/src/edit_keymap.erl new file mode 100644 index 0000000..9ed0ce6 --- /dev/null +++ b/src/edit_keymap.erl @@ -0,0 +1,109 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_keymap.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Keymap implemented as an ETS table +%%% Created : 23 Sep 2000 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_keymap). +-author('luke@bluetail.com'). + +-export([start_link_server/0, new/1, keymap_exists/1, + global_set_key/2, set_key/3, + bind/3, bind_each/2, delete/1, lookup/2, test/0]). + +-export([server/0]). + +start_link_server() -> + case whereis(?MODULE) of + Pid when pid(Pid) -> + {error, {already_started, Pid}}; + undefined -> + Pid = spawn_link(?MODULE, server, []), + register(?MODULE, Pid), + {ok, Pid} + end. + +server() -> + receive + {request, From, {create, Name}} -> + Reply = case keymap_exists(Name) of + true -> + {error, {already_exists, Name}}; + false -> + ets:new(Name, [named_table, set, public]), + Name + end, + From ! {reply, Reply}; + _ -> + true + end, + server(). + +new(Name) -> + ?MODULE ! {request, self(), {create, Name}}, + receive {reply, R} -> R end. + +keymap_exists(Name) -> + case ets:info(Name) of + undefined -> + false; + _ -> + true + end. + +global_set_key(KeySeq, Binding) -> + set_key(global_map, KeySeq, Binding). + +%% e.g. set_key(edit_globalmap, "C-x C-f", {edit_file, find_file, []}) +set_key(KeyMap, KeySeq, Binding) -> + set_key_traverse(KeyMap, string:tokens(KeySeq, " "), Binding). + +set_key_traverse(KeyMap, [Key], Binding) -> + bind(KeyMap, Key, Binding), + ok; +set_key_traverse(KeyMap, [MapKey | KeySeq], Binding) -> + case lookup(KeyMap, MapKey) of + {_, {keymap, SubMap}} -> + set_key_traverse(SubMap, KeySeq, Binding); + _ -> + error + end. + +bind(Keymap, Key, Value) -> + ets:insert(Keymap, {Key, Value}). + +%% Bind each item of a key-value list. +bind_each(Name, KVList) -> + lists:foreach(fun({Key, Value}) -> bind(Name, Key, Value) end, + KVList). + +lookup(Name, Key) -> + %% First lookup by key-code number + case ets:lookup(Name, Key) of + [{Key, Value}] -> + {ok, Value}; + [] -> + %% Next lookup by stringified name + String = edit_util:keyname(Key), + case ets:lookup(Name, String) of + [{String, Value}] -> + {ok, Value}; + [] -> + unbound + end + end. + +delete(Name) -> + ets:delete(Name). + +test() -> + Map = new(test), + unbound = lookup(Map, key), + bind(Map, key, value), + {ok, value} = lookup(Map, key), + bind(Map, key, newvalue), + {ok, newvalue} = lookup(Map, key), + delete(Map), + ok. + diff --git a/src/edit_lex.erl b/src/edit_lex.erl new file mode 100644 index 0000000..4d6e6ef --- /dev/null +++ b/src/edit_lex.erl @@ -0,0 +1,14 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_lex.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Lexer +%%% Created : 8 Apr 2001 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_lex). +-author('luke@bluetail.com'). + +%%-compile(export_all). +%%-export([Function/Arity, ...]). + + diff --git a/src/edit_lib.erl b/src/edit_lib.erl new file mode 100644 index 0000000..73372db --- /dev/null +++ b/src/edit_lib.erl @@ -0,0 +1,801 @@ +-module(edit_lib). +-author('tobbe@serc.rmit.edu.au'). +%%---------------------------------------------------------------------- +%% Created : 15 Jun 1998 by tobbe@serc.rmit.edu.au +%% Function: Core library routines for the Edit editor. +%%---------------------------------------------------------------------- +-vc('$Id$ '). + +-include_lib("ermacs/include/edit.hrl"). + +-compile(export_all). +-compile({parse_transform, edit_transform}). + +self_insert_command(S,Ch) when record(S,state) -> + insert_char(S,Ch). + +delete_char_backward(S) -> + B = buffer(S), + case edit_buf:mark_pos(B, point) of + 1 -> + S; + N -> + edit_buf:delete(B, N-1, N) + end. + +delete_char_forward(S) -> + B = buffer(S), + Max = edit_buf:point_max(B), + case edit_buf:mark_pos(B, point) of + N when N < Max -> + edit_buf:delete(B, N, N+1); + _ -> + true + end, + S. + +%% Note: delete_word, not kill_word. Should single words really go on +%% the kill ring, like they do in emacs? +delete_word_forward(S) -> + Buf = buffer(S), + Point = edit_buf:mark_pos(Buf, point), + P1 = find_char_forward(Buf, alphanumeric_P(), Point, Point), + P2 = find_char_forward(Buf, not_P(alphanumeric_P()), P1, P1), + edit_buf:delete(Buf, Point, P2). + +insert_char(S, Ch) -> + Buf = buffer(S), + edit_buf:insert(Buf, [Ch], edit_buf:mark_pos(Buf, point)). + +%% "open_line" is the emacs name for what C-o does +open_line(S) -> + Buf = buffer(S), + OrigPoint = edit_buf:mark_pos(Buf, point), + insert_char(S, $\n), + edit_buf:move_mark(Buf, point, OrigPoint). + +%% Abort - interpreted to abort any asynchronous command which is +%% borrowing the current buffer (or nothing if there isn't one). +abort(S) -> + case edit_buf:revoke((S#state.curwin)#window.buffer) of + false -> + S; + true -> + edit_util:status_msg(S, "Buffer revoked") + end. + +%% ----------------------------------- +%% Move cursor vertically down 1 line. + +next_line(S) when record(S,state) -> + Buf = buffer(S), + NewWindow = update_goal(S, Buf), + Goal = NewWindow#window.goal_column, + Max = edit_buf:point_max(Buf), + case end_of_line_pos(Buf) of + X when X == Max -> + ok; + X -> + F = fun(Ch) -> walk_goal(Ch, Goal, X+1) end, + NewP = edit_buf:walk_forward(Buf, F, X+1), + edit_buf:move_mark(Buf, point, NewP), + S#state{curwin=NewWindow} + end. + +previous_line(S) when record(S,state) -> + Buf = buffer(S), + NewWindow = update_goal(S, Buf), + Goal = NewWindow#window.goal_column, + case beginning_of_line_pos(Buf) of + 1 -> + move_to(Buf, 1); + X -> + From = beginning_of_line_pos(Buf, X-1), + F = fun(Ch) -> walk_goal(Ch, Goal, From) end, + NewP = edit_buf:walk_forward(Buf, + F, + beginning_of_line_pos(Buf, From)), + edit_buf:move_mark(Buf, point, NewP), + S#state{curwin=NewWindow} + end. + +update_goal(State, Buf) -> + Win = State#state.curwin, + case goal_preserving(State#state.lastcmd) of + true -> + Win; + false -> + Region = edit_buf:get_region(Buf, + point(Buf), + beginning_of_line_pos(Buf)), + Win#window{goal_column=goal_value(Region)} + end. + +goal_value(Region) -> + lists:foldl(fun($\t, Acc) -> Acc + 8; + (_, Acc) -> Acc + 1 + end, + 0, + Region). + +goal_preserving({?MODULE, next_line, []}) -> true; +goal_preserving({?MODULE, previous_line, []}) -> true; +goal_preserving(_) -> false. + +%% Go forward until we reach the goal column, or the end of file +walk_goal(C, Z, P) when Z =< 0 -> + {result, P}; +walk_goal(C, Remaining, P) when C == finish; + C == $\n -> + {result, P}; +walk_goal($\t, Remaining, P) when Remaining < 8 -> + {result, P}; +walk_goal($\t, Remaining, P) -> + {more, fun(C) -> walk_goal(C, Remaining-8, P+1) end}; +walk_goal(_, Remaining, P) -> + {more, fun(C) -> walk_goal(C, Remaining-1, P+1) end}. + +point(Buf) -> + edit_buf:mark_pos(Buf, point). + +%% ---------------------------- +%% Move point left 1 character. + +backward_char(S) when record(S,state) -> + Buf = buffer(S), + Pos = edit_buf:mark_pos(Buf, point) - 1, + case Pos < 1 of + true -> + S; + false -> + edit_buf:move_mark(Buf, point, Pos) + end. + +%% ----------------------------- +%% Move point right 1 character. +%% + +forward_char(S) when record(S,state) -> + Buf = buffer(S), + Pos = edit_buf:mark_pos(Buf, point) + 1, + Max = edit_buf:point_max(Buf), + case Pos > Max of + true -> + S; + false -> + edit_buf:move_mark(Buf, point, Pos) + end. + +%% ---------------------------------------- +%% Move point to beginning of current line. + +beginning_of_line(S) -> + Pos = beginning_of_line_pos(buffer(S)), + edit_buf:move_mark(buffer(S), point, Pos), + S. + +%% ugly function +backward_word(S) -> + Buf = buffer(S), + Point = edit_buf:mark_pos(Buf, point), + if Point == 1 -> + S; + true -> + X = find_char_backward(Buf, alphanumeric_P(), Point-1, 1), + if X == 1 -> + move_to(Buf, 1), + S; + true -> + NewP = 1 + find_char_backward(Buf, + not_P(alphanumeric_P()), + X, + 0), + move_to(Buf, NewP), + S + end + end. + +beginning_of_line_pos(Buf) -> + Pos = edit_buf:mark_pos(Buf, point), + beginning_of_line_pos(Buf, Pos). +beginning_of_line_pos(Buf, Pos) -> + %% We start before the point, incase we're on a newline + case find_char_backward(Buf, const_P($\n), Pos - 1) of + not_found -> 1; % point_min + N when integer(N) -> N + 1 % we want to be just after the newline + end. + +move_to_char_backward(Buf, Pred) -> + Pos = edit_buf:mark_pos(Buf, point), + edit_buf:move_mark(Buf, point, find_char_backward(Buf, Pred, Pos, 1)). + +%% Returns CharPos | not_found +find_char_backward(Buf, Pred) -> + find_char_backward(Buf, Pred, edit_buf:mark_pos(Buf, point)). +find_char_backward(Buf, Pred, Pos) -> + find_char_backward(Buf, Pred, Pos, not_found). +find_char_backward(Buf, Pred, Pos, Default) -> + case edit_buf:walk_backward(Buf, + fun(C) -> find_char_backward1(C,Pred,Pos) end, + Pos) of + not_found -> + Default; + P when integer(P) -> + P + end. + +%% This clause is a kludge; see edit_buf.erl for details +find_char_backward1(not_there, Pred, Pos) -> + {more, fun(New) -> find_char_backward1(New, Pred, Pos-1) end}; +find_char_backward1(finish, Pred, Pos) -> + {result, not_found}; +find_char_backward1(C, Pred, Pos) -> + %%edit_buf:log("Char: ~p~n", [C]), + case Pred(C) of + true -> + {result, Pos}; + false -> + {more, fun(New) -> find_char_backward1(New, Pred, Pos-1) end} + end. + +%% ---------------------------------- +%% Move point to end of current line. + +end_of_line(S) -> + move_to_char_forward(buffer(S), fun(C) -> C == $\n end), + S. + +forward_word(S) -> + move_to_char_forward(buffer(S), alphanumeric_P()), + move_to_char_forward(buffer(S), not_P(alphanumeric_P())), + S. + +end_of_line_pos(Buf) -> + Pos = edit_buf:mark_pos(Buf, point), + end_of_line_pos(Buf, Pos). +end_of_line_pos(Buf, Pos) -> + P = case find_char_forward(Buf, fun(C) -> C == $\n end, Pos) of + not_found -> + edit_buf:point_max(Buf); + N when integer(N) -> + N + end. + +move_to_char_forward(Buf, Pred) -> + Pos = edit_buf:mark_pos(Buf, point), + NewPos = find_char_forward(Buf, Pred, Pos, edit_buf:point_max(Buf)), + edit_buf:move_mark(Buf, point, NewPos). + +%% Returns CharPos | not_found +find_char_forward(Buf, Pred, Pos) -> + find_char_forward(Buf, Pred, Pos, not_found). +%% Returns CharPos | Default (if not found) +find_char_forward(Buf, Pred, Pos, Default) -> + case edit_buf:walk_forward(Buf, + fun(C) -> find_char_forward1(C, Pred, Pos) end, + Pos) of + not_found -> + Default; + P when integer(P) -> + P + end. + +find_char_forward1(finish, Pred, Pos) -> + {result, not_found}; +find_char_forward1(C, Pred, Pos) -> + case Pred(C) of + true -> + {result, Pos}; + false -> + {more, fun(New) -> find_char_forward1(New, Pred, Pos+1) end} + end. + +recenter(State) -> + Buf = buffer(State), + Win = State#state.curwin, + Lines = trunc(Win#window.height/2), + Point = edit_buf:mark_pos(Buf, point), + NewDStart = case find_nth(Buf, backward, Point, $\n, Lines) of + not_found -> + 1; + Pos -> + Pos + 1 + end, + edit_buf:move_mark(Buf, Win#window.start_mark, NewDStart), + ?EDIT_TERMINAL:invalidate(), + State. + +%% These scrolling functions are ugly + +scroll_up(State) -> + Win = State#state.curwin, + Height = edit_window:text_lines(Win), + Buf = buffer(State), + DStart = edit_buf:mark_pos(Buf, Win#window.start_mark), + NewDStart = + case find_nth(Buf, backward, DStart, $\n, Height - 1) of + not_found -> + 1; + X when integer(X) -> + X + 1 + end, + DEnd = case find_nth(Buf, forward, NewDStart, $\n, Height) of + not_found -> + edit_buf:point_max(Buf); + N -> + N + end, + Point = edit_buf:mark_pos(Buf, point), + if Point > DEnd -> + move_to(Buf, beginning_of_line_pos(Buf, DEnd)); + true -> + ok + end, + edit_buf:move_mark(Buf, Win#window.start_mark, NewDStart), + State. + +scroll_down(State) -> + Win = State#state.curwin, + Buf = buffer(State), + DStart = edit_buf:mark_pos(Buf, Win#window.start_mark), + PMax = edit_buf:point_max(Buf), + case find_nth(Buf, forward, DStart, $\n, edit_window:text_lines(Win)-2) of + X when integer(X), + X < PMax -> + %% We want to be just after the newline - i.e. start of next line + Pos = X + 1, + Point = edit_buf:mark_pos(Buf, point), + if Point < Pos -> + %% Scrolled the point off the screen, move the + %% point to the old display start (up near the + %% top). + edit_buf:move_mark(Buf, point, Pos); + true -> + ok + end, + edit_buf:move_mark(Buf, Win#window.start_mark, Pos), + State; + _ -> + State + end. + +scroll_down_wrap(Win) -> + Buf = Win#window.buffer, + DStart = edit_buf:mark_pos(Buf, Win#window.start_mark), + PMax = edit_buf:point_max(Buf), + Lines = edit_window:text_lines(Win), + Pos = case find_nth(Buf, forward, DStart, $\n, Lines) of + X when integer(X), + X < PMax -> + %% We want to be just after the newline - i.e. start + %% of next line + X + 1; + _ -> + 1 + end, + edit_buf:move_mark(Buf, Win#window.start_mark, Pos), + edit_buf:move_mark(Buf, point, Pos), + Win. + +start_of_buffer(State) -> + B = buffer(State), + edit_buf:move_mark(B, mark, edit_buf:mark_pos(B, point)), + edit_buf:move_mark(B, point, 1), + State. + +end_of_buffer(State) -> + B = buffer(State), + edit_buf:move_mark(B, mark, edit_buf:mark_pos(B, point)), + edit_buf:move_mark(B, point, edit_buf:point_max(B)), + State. + +mark_whole_buffer(State) -> + Buf = buffer(State), + edit_buf:move_mark(Buf, point, 1), + edit_buf:move_mark(Buf, mark, edit_buf:point_max(Buf)), + State. + +find_nth(Buf, Direction, Pos, Char, N) -> + case edit_buf:walk(Buf, Direction, find_nth_fun(Char, N), Pos) of + not_found -> + not_found; + Offset when Direction == forward -> + Pos + Offset; + Offset when Direction == backward -> + Pos - Offset + end. + +find_nth_fun(Char, N) -> find_nth_fun(Char, N, 0). +find_nth_fun(Char, N, Offset) -> find_nth_fun(Char, N, Offset, not_found). + +find_nth_fun(Char, N, Offset, Default) -> + fun(X) when X == Char -> + case N of + 1 -> + {result, Offset}; + _ -> + {more, find_nth_fun(Char, N-1, Offset+1, Default)} + end; + (finish) -> + {result, Default}; + (_) -> + {more, find_nth_fun(Char, N, Offset+1, Default)} + end. + +set_mark(State) -> + Buf = buffer(State), + Point = edit_buf:mark_pos(Buf, point), + edit_buf:move_mark(Buf, mark, Point), + State. + +exchange_point_and_mark(State) -> + Buf = buffer(State), + Point = edit_buf:mark_pos(Buf, point), + Mark = edit_buf:mark_pos(Buf, mark), + edit_buf:move_mark(Buf, point, Mark), + edit_buf:move_mark(Buf, mark, Point), + State. + +kill_region(State, KillFlag) -> + Buf = buffer(State), + Point = edit_buf:mark_pos(Buf, point), + Mark = edit_buf:mark_pos(Buf, mark), + Text = edit_buf:get_region_cord(Buf, Point, Mark), + State1 = case KillFlag of + true -> + edit_buf:delete(Buf, Point, Mark), + State; + false -> + edit_util:status_msg(State, "Copied") + end, + KR = edit_var:lookup(killring), + edit_var:set(killring, [Text|KR]). +%%State1#state{killring=[Text|State#state.killring]}. + +kill_line(State) -> + Buf = buffer(State), + P = edit_buf:mark_pos(Buf, point), + EOL = end_of_line_pos(Buf), + End = if P == EOL -> + min(P + 1, edit_buf:point_max(Buf)); + true -> + EOL + end, + Text = edit_buf:get_region_cord(Buf, P, End), + edit_buf:delete(Buf, P, End), + %%OldKillring = State#state.killring, + OldKillring = edit_var:lookup(killring), + NewKillring = case State#state.lastcmd of + %% Multiple kill_line's accumulate at the front + %% of the kill ring + {?MODULE, kill_line, _} -> + [cord:join(hd(OldKillring), Text) | + tl(OldKillring)]; + _ -> + [Text|OldKillring] + end, + edit_var:set(killring, NewKillring). + %%State#state{killring=NewKillring}. + +yank(State) -> + case edit_var:lookup(killring) of + [] -> + State; + [Text|_] -> + Buf = buffer(State), + Point = edit_buf:mark_pos(Buf, point), + edit_buf:insert(Buf, Text, Point) + end. + +undo(State) -> + Continuing = case State#state.lastcmd of + {?MODULE, undo, []} -> + true; + _ -> + false + end, + edit_buf:undo(buffer(State), Continuing). + +move_to(Buf, Pos) -> + edit_buf:move_mark(Buf, point, Pos). + +next_window(State) when State#state.windows == [] -> + State; +next_window(State) -> + next_window1(State). + +next_window1(State) -> + [Next | Rest] = State#state.windows, + NewState = State#state{curwin=Next, + windows = Rest ++ [State#state.curwin]}, + if (NewState#state.curwin)#window.active == false -> + next_window1(NewState); + true -> + NewState + end. + +split_window_vertically(State) -> + case edit_window:physical_lines(State#state.curwin) of + N when N < 4 -> % too small to split + State; + N -> + split_window_vertically1(State) + end. + +split_window_vertically1(State) -> + Orig = State#state.curwin, + OrigHeight = edit_window:physical_lines(Orig), + TopHeight = trunc(OrigHeight / 2), + BottomHeight = OrigHeight - TopHeight, + BottomY = Orig#window.y + TopHeight, + BottomW = edit_window:make_window(buffer(State), + BottomY, + Orig#window.width, + BottomHeight), + State#state{curwin=Orig#window{height=TopHeight}, + windows=[BottomW | State#state.windows]}. + +delete_other_windows(State) + when (State#state.curwin)#window.minibuffer == true -> + edit_util:status_msg(State, "Minibuffer can't expand to full frame"); +delete_other_windows(State) -> + Others = lists:filter(fun(W) -> not W#window.minibuffer end, + State#state.windows), + ExtraSpace = lists:foldl(fun(W, Sum) -> Sum + W#window.height end, + 0, + Others), + [Minibuffer] = State#state.windows -- Others, + OldWin = State#state.curwin, + NewWin = OldWin#window{y=0, height = OldWin#window.height + ExtraSpace}, + State#state{curwin=NewWin, + windows=[Minibuffer]}. + +delete_window(State) when (State#state.curwin)#window.minibuffer == true -> + State; +delete_window(State) when length(State#state.windows) =< 1 -> + State; +delete_window(State) -> + Cur = State#state.curwin, + Wins = State#state.windows, + [Prev | OthersReversed] = lists:reverse(Wins), + Others = lists:reverse(OthersReversed), + io:format("Prev.y = ~p~nCur.y = ~p~n", [Prev#window.y, Cur#window.y]), + if Prev#window.y > Cur#window.y -> + %% Topmost window is being deleted. In this special case, + %% its size is allocated to the next (rather than + %% previous, i.e. bottom) window. + Kin = hd(Wins), + Kin1 = Kin#window{height=Kin#window.height + Cur#window.height, + y=Cur#window.y}, + State#state{curwin=Kin1, + windows=tl(Wins)}; + true -> + Kin = Prev, + Kin1 = Kin#window{height=Kin#window.height + Cur#window.height}, + Others1 = Others ++ [Kin1], + case Others1 of + [Next = #window{minibuffer=false} | Rest] -> + State#state{curwin=Next, + windows=Rest}; + [MB = #window{minibuffer=true}, Next | Rest] -> + State#state{curwin=Next, + windows=(Rest ++ [MB])} + end + end. + +-command({quit, [], "Exit the editor process"}). +quit(State) -> + ?EDIT_TERMINAL:teardown(), + halt(). + +-command({printf, [{string, "String:"}], + "Print a string to standard output (the file edit.out)"}). + +printf(State, String) -> + io:format("*** PRINTF: ~s~n", [String]), + State. + +-command({switch_to_buffer, [{string, "Switch to buffer:"}]}). + +switch_to_buffer(State, "") -> + VisibleBuffers = [W#window.buffer || W <- edit_util:windows(State)], + case State#state.buffers -- VisibleBuffers of + [] -> + State; + [Next|_] -> + switch_to_buffer(State, atom_to_list(Next)) + end; +switch_to_buffer(State, Name) -> + Buffer = list_to_atom(Name), + case whereis(Buffer) of % FIXME: how do I know its a buffer? + undefined -> + edit_util:status_msg(State, "No such buffer: ~s", [Name]); + Pid -> + Buffers = State#state.buffers, + NewBuffers = [Buffer | (Buffers -- [Buffer])], + Win = edit_window:attach(State#state.curwin, Buffer), + State#state{curwin=Win, buffers=NewBuffers} + end. + +-command({kill_buffer, [{string, "Kill buffer:"}]}). + +kill_buffer(State, "") -> + kill_buffer(State, atom_to_list((State#state.curwin)#window.buffer)); +kill_buffer(State, Name) -> + BufferName = list_to_atom(Name), + case State#state.buffers of + [OnlyBuffer] -> + State; + Buffers -> + case whereis(BufferName) of + Pid when pid(Pid) -> + edit_buf:kill(BufferName), + NewBuffers = Buffers -- [BufferName], + F = fun(Win) -> + case Win#window.buffer of + BufferName -> + edit_window:attach(Win, + hd(NewBuffers)); + _ -> + Win + end + end, + State1 = edit_util:window_map(State, F), + io:format("Killing ~p~n", [BufferName]), + State1#state{buffers=NewBuffers}; + undefined -> + edit_util:status_msg(State, "No such buffer: ~s", [Name]) + end + end. + +min(X,Y) when X<Y -> X; +min(_,Y) -> Y. + +max(X,Y) when X>Y -> X; +max(_,Y) -> Y. + +%% Get the buffer from state - blocks if it's being borrowed by someone else. +buffer(State) -> + Buf = (State#state.curwin)#window.buffer, + edit_buf:wait_available(Buf), + Buf. + +find(Pred, []) -> + not_found; +find(Pred, [H|T]) -> + case Pred(H) of + true -> + H; + false -> + find(Pred, T) + end. + +%% FIXME: Expects the *end* of the string to be before the point +find_string_backward(Buf, String) -> + find_string_backward(Buf, String, edit_buf:mark_pos(Buf, point)). +find_string_backward(Buf, String, Point) -> + find_string_backward(Buf, String, Point, not_found). +find_string_backward(Buf, String, Point, Default) -> + find_string_backward1(Buf, String, Point, Default). + +find_string_backward1(Buf, String, Point, Default) -> + PointMax = edit_buf:point_max(Buf), + P = fun(C) -> C == hd(String) end, + case min(Point, PointMax - length(String)) of + Start when Start =< 0 -> + Default; + Start -> + case find_char_backward(Buf, P, Start) of + not_found -> + Default; + Pos -> + case edit_buf:get_region(Buf, Pos, Pos + length(String)) of + String -> + Pos; + _Other when Pos == 1 -> + Default; + _Other -> + find_string_backward1(Buf, String, Pos-1, Default) + end + end + end. + +%% Predicate functions on characters. +%% Functions ending in _P return predicate fun's. +whitespace_P() -> fun(C) -> lists:member(C, "\n\t ") end. +alphabet_P() -> fun(C) when C >= $A, C =< $Z -> true; + (C) when C >= $a, C =< $z -> true; + (_) -> false + end. +numeric_P() -> fun(C) -> (C >= $0) and (C =< $9) end. +not_whitespace_P() -> not_P(whitespace_P()). + +either_P(A, B) -> fun(C) -> A(C) or B(C) end. +const_P(Char) -> fun(C) -> C == Char end. +not_P(Pred) -> fun(C) -> not Pred(C) end. + +alphanumeric_P() -> either_P(alphabet_P(), numeric_P()). + +fundamental_mode(State) -> + Buf = buffer(State), + edit_buf:set_mode(Buf, fundamental_mode_rec()). + +fundamental_mode_rec() -> + #mode{name="Fundamental", + id=fundamental, + keymaps=[]}. + +-command({unix_command, [{shell_command, "Shell command:"}]}). + +unix_command(State, Cmd) -> + %% FIXME: This looks suspiciously system dependent. + %% Infact running the command as a port would be much nicer + Text = os:cmd("sh -c '(cd "++edit_util:pwd(State)++" ; "++Cmd++")'"), + edit_util:popup_message(State, '*Shell Command Output*', Text). + +lines(Text) -> + length(lists:filter(fun(C) -> C == $\n end, Text)). + +-command({search, [{search_string, "Search:"}]}). + +search(State, Dir, "") -> + Str = edit_var:lookup(prev_search_string, ""), + regexp_search1(State, Dir, cord_regexp:escape(Str)); +search(State, Dir, Str) -> + edit_var:set(prev_search_string, Str), + regexp_search1(State, Dir, cord_regexp:escape(Str)). + +-command({regexp_search, [{search_regexp, "Regexp:"}]}). + +regexp_search(State, Dir, "") -> + %% Blank search string - reuse previous + RE = edit_var:lookup(prev_search_regexp, ""), + regexp_search1(State, Dir, RE); +regexp_search(State, Dir, RE) -> + edit_var:set(prev_search_regexp, RE), + regexp_search1(State, Dir, RE). + +regexp_search1(State, Direction, RE) -> + B = buffer(State), + P = edit_buf:mark_pos(B, point), + case edit_buf:regexp_search(B, RE, clip(B, P), Direction) of + nomatch -> + edit_util:status_msg(State, "Not found: " ++ RE); + {match, Start, Len} -> + NewPos = case Direction of + forward -> + Start + Len; + backward -> + Start - (Len - 1) + end, + ?debug("Start = ~p Len = ~p~n", [Start, Len]), + mark_to_point(B), + edit_buf:move_mark(B, point, NewPos) + end. + +%% Returns: Point' < point_max +clip(Buf, Point) -> + max(1, min(Point, edit_buf:point_max(Buf) - 1)). + +mark_to_point(Buf) -> + edit_buf:move_mark(Buf, mark, edit_buf:mark_pos(Buf, point)). + +%% Null command. Invoked by async processes to get the editor to +%% redraw. +nop(State) -> + State. + +%% ---------------------------------------------------------------------- +%% Debug info + +buffer_cord_info(State) -> + B = buffer(State), + C = edit_buf:get_cord(B), + Fmt = "Byte size: ~p ; Max depth: ~p ; Mean leaf size: ~p ; # nodes: ~p", + edit_util:status_msg(State, + Fmt, + [cord:cord_size(C), + cord:max_depth(C), + cord:mean_leaf_size(C), + cord:nr_nodes(C)]). + diff --git a/src/edit_make.erl b/src/edit_make.erl new file mode 100644 index 0000000..20f6cc2 --- /dev/null +++ b/src/edit_make.erl @@ -0,0 +1,14 @@ +-module(edit_make). +-export([start/0]). + +start() -> + spawn(fun() -> doit() end). + +doit() -> + sleep(1), + ig:gen(edit_ig), + c:c(edit_ig), + halt(). + +sleep(Sec) -> + receive after Sec*1000 -> true end. diff --git a/src/edit_mod.erl b/src/edit_mod.erl new file mode 100644 index 0000000..ae5be54 --- /dev/null +++ b/src/edit_mod.erl @@ -0,0 +1,42 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_mod.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Module loader +%%% Created : 28 Apr 2001 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_mod). +-author('luke@bluetail.com'). + +-export([init/0, require/1, load/1]). + +init() -> + ets:new(?MODULE, [set, named_table, public]), + ok. + +require(Mod) -> + case ets:lookup(?MODULE, Mod) of + [] -> % not initialised + case load(Mod) of + ok -> + ok; + {error, Reason} -> + {error, Reason} + end; + [_] -> % already initialised + ok + end. + +load(Mod) -> + case catch Mod:mod_init() of + ok -> + ets:insert(?MODULE, {Mod}), + ok; + {'EXIT', {undef, _}} -> + {error, {missing, Mod, mod_init, 0}}; + {error, Reason} -> + {error, Reason}; + Unexpected -> + {error, {unexpected, Unexpected}} + end. + diff --git a/src/edit_prof.erl b/src/edit_prof.erl new file mode 100644 index 0000000..08aea4d --- /dev/null +++ b/src/edit_prof.erl @@ -0,0 +1,36 @@ +-module(edit_prof). +-compile(export_all). + +start(Filename) -> + Edit = spawn_link(fun() -> edit:start(Filename) end), + timer:start_link(), + eprof:start(), + profiling = eprof:profile([Edit, scratch]), + ok = eprof:analyse(), + analyse_loop(). + +analyse_loop() -> + receive after 5000 -> + eprof:total_analyse() + end, + analyse_loop(). + +leader() -> + {ok, ProfLog} = file:open("prof.log", [write]), + spawn_link(?MODULE, leader_proc, [ProfLog]). + +leader_proc(Fd) -> + receive + {io_request, From, ReplyAs, {put_chars, C}} -> + file:write(Fd, C), + From ! {io_reply, ReplyAs, ok}; + {io_request, From, ReplyAs, {put_chars, M, F, A}} -> + file:write(Fd, apply(M, F, A)), + From ! {io_reply, ReplyAs, ok}; + {io_request, From, ReplyAs, {get_until, _, _, _}} -> + From ! {io_reply, ReplyAs, eof}; + X -> + file:write(Fd, io_lib:format("Unexpected: ~p~n", [X])) + end, + leader_proc(Fd). + diff --git a/src/edit_terminal.erl b/src/edit_terminal.erl new file mode 100644 index 0000000..b18cafb --- /dev/null +++ b/src/edit_terminal.erl @@ -0,0 +1,77 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_terminal.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : ncurses terminal implementation +%%% Created : 16 Sep 2000 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_terminal). +-author('luke@bluetail.com'). + +-include_lib("slang/include/slang.hrl"). + +-define(ESC, 27). + +-compile(export_all). +%%-export([Function/Arity, ...]). + +setup() -> + slang:tt_get_terminfo(), + slang:kp_init(), + slang:init_tty(0, 1, 1), + slang:set_abort_signal(null), + slang:smg_init_smg (), + slang:smg_normal_video(), + slang:setvar(newline_behaviour, ?NEWLINE_MOVES), + refresh(), + ok. + +teardown() -> + slang:smg_reset_smg(), + slang:reset_tty(), + ok. + +newline() -> + put_char($\n). + +put_char(C) -> + slang:smg_write_char(C). + +put_string(S) -> + slang:smg_write_string(S). + +format(Fmt, Args) -> + slang:smg_printf(Fmt, Args). + +erase_to_eol() -> + slang:smg_erase_eol(). + +move_to(X, Y) -> + slang:smg_gotorc(Y, X). + +refresh() -> + slang:smg_refresh(). + +invalidate() -> + slang:smg_touch_screen(). + +width() -> + slang:getvar(screen_cols). + +height() -> + slang:getvar(screen_rows). + +read() -> + case slang:getkey() of + ?ESC -> + read() bor 2#10000000; + N -> + N + end. + +font_reverse() -> + slang:smg_reverse_video(). + +font_normal() -> + slang:smg_normal_video(). + diff --git a/src/edit_terminal_gterm.erl b/src/edit_terminal_gterm.erl new file mode 100644 index 0000000..0d6b678 --- /dev/null +++ b/src/edit_terminal_gterm.erl @@ -0,0 +1,42 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_terminal_gterm.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : edit_terminal implementation for gterm (Tony's GTK terminal +%%% emulator) +%%% Created : 14 Mar 2001 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_terminal_gterm). +-author('luke@bluetail.com'). + +-compile(export_all). +%%-export([Function/Arity, ...]). + +%% name of terminal process +-define(TERM, ?MODULE). + +setup() -> + Term = gterm:run(), + register(?TERM, Term), +% gterm_api:set_local_echo(Term, false), +% gterm_api:set_auto_scroll(Term, false), + Term. + +teardown() -> + gterm_api:quit(). + +newline() -> gterm_api:newline(?TERM). +put_char(C) -> gterm_api:put_char(?TERM, C). +put_string(S) -> gterm_api:put_string(?TERM, S). +format(Fmt, Args) -> gterm_api:format(?TERM, Fmt, Args). +erase_to_eol() -> gterm_api:erase_to_eol(?TERM). +move_to(X, Y) -> gterm_api:move_to(?TERM, X, Y). +refresh() -> gterm_api:refresh(?TERM). +invalidate() -> gterm_api:refresh(?TERM). +width() -> gterm_api:width(?TERM). +height() -> gterm_api:height(?TERM). +read() -> gterm_api:read(?TERM). +font_reverse() -> gterm_api:font_reverse(?TERM). +font_normal() -> gterm_api:font_normal(?TERM). + + diff --git a/src/edit_text.erl b/src/edit_text.erl new file mode 100644 index 0000000..3bcc7d9 --- /dev/null +++ b/src/edit_text.erl @@ -0,0 +1,148 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_text.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Text data structure with markers and undo +%%% Created : 2 Oct 2001 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_text). +-author('luke@bluetail.com'). + +-export([new/0, new/1, replace/4, add_mark/4, move_mark/3, mark_pos/2, + undo/2, cord/1, walk_backward/3, walk_forward/3]). + +-record(text, {cord, marks=[], undo=[], running_undo=[]}). + +-record(mark, {name, pos, direction}). + +%% ---------------------------------------------------------------------- +%% new + +new() -> + new(<<>>). + +new(BCS) when list(BCS) -> + new(cord:new(BCS)); +new(Cord) -> + #text{cord=Cord}. + +%% ---------------------------------------------------------------------- +%% replace (CBS means Cord, Binary, or String) + +replace(Text0 = #text{cord=Cord0,marks=Marks0,undo=Undo0}, CBS, Start, Len) -> + Cord1 = cord:replace(Cord0, CBS, Start, Len), + Marks1 = update_marks(Marks0, Start, Start+Len, cbs_length(CBS)), + Undo1 = [{Cord0, Marks0} | Undo0], + %% FIXME: maybe better to chain undo through states (i.e. just + %% know about previous state, which knows about previous state, + %% ...) - if that plays nicely with "running undo" + Text0#text{cord=Cord1, marks=Marks1, undo=Undo1, running_undo=[]}. + +cbs_length(L) when list(L) -> length(L); +cbs_length(B) when binary(B) -> size(B); +cbs_length(C) -> cord:cord_size(C). + +update_marks(Marks, Start, End, Len) -> + [update_mark(Mark,Start,End,Len) || Mark <- Marks]. + +%% Inside the replaced region (includes being on an inserted character) +update_mark(Mark, Start, End, Len) + when Mark#mark.pos >= Start, + Mark#mark.pos < End; + Mark#mark.pos == Start -> + case Mark#mark.direction of + forward -> + Mark#mark{pos=Start+Len}; + backward -> + Mark#mark{pos=Start} + end; +%% After the replaced region +update_mark(Mark, Start, End, Len) when Mark#mark.pos >= End -> + %% We move forward by the length-delta + Mark#mark{pos = Mark#mark.pos - (End - Start) + Len}; +%% Before the replaced region +update_mark(Mark, _Start, _End, _Len) -> + Mark. + +%% ---------------------------------------------------------------------- +%% add_mark + +add_mark(Text = #text{marks=Marks0}, Name, Pos, Direction) -> + Mark = #mark{name=Name, pos=Pos, direction=Direction}, + Text#text{marks=[Mark|Marks0]}. + +%% ---------------------------------------------------------------------- +%% move_mark + +move_mark(Text0, Name, Pos) -> + {Front, [M | Back]} = + lists:splitwith(fun(X) -> X#mark.name /= Name end, + Text0#text.marks), + NewMarks = Front ++ [M#mark{pos=Pos} | Back], + Text0#text{marks=NewMarks}. + +%% ---------------------------------------------------------------------- +%% mark_pos + +mark_pos(#text{marks=Marks}, Name) -> + {found, Mark} = find(fun(M) -> M#mark.name == Name end, Marks), + Mark#mark.pos. + +find(Pred, []) -> + not_found; +find(Pred, [H|T]) -> + case Pred(H) of + true -> + {found, H}; + false -> + find(Pred, T) + end. + +%% ---------------------------------------------------------------------- +%% walk_backward + +walk_backward(Text = #text{cord=Cord}, Fun, Pos) -> + Size = cord:cord_size(Cord), + if Pos == Size + 1 -> + case Fun(not_there) of + {more, FNext} -> + walk_backward(Text, FNext, Pos-1); + {result, R} -> + R + end; + true -> + cord:walk(Cord, Pos, backward, Fun) + end. + +%% ---------------------------------------------------------------------- +%% walk_forward + +walk_forward(#text{cord=Cord}, Fun, Pos) -> + cord:walk(Cord, Pos, forward, Fun). + +%% ---------------------------------------------------------------------- +%% undo + +undo(Text0 = #text{undo=Undo, running_undo=Running}, Continuing) -> + Text1 = case Continuing of + true -> + Text0; + false -> + Text0#text{running_undo=Undo} + end, + case Running of + %% Nothing to undo - recycle redo list + [] -> + Text1#text{running_undo=Undo}; + [{UndoCord, UndoMarks}|Undos] -> + Text1#text{cord=UndoCord, + marks=UndoMarks, + undo=[{Text0#text.cord, Text0#text.marks} | Undo], + running_undo=Undos} + end. + +%% ---------------------------------------------------------------------- +%% cord + +cord(#text{cord=Cord}) -> Cord. + diff --git a/src/edit_transform.erl b/src/edit_transform.erl new file mode 100644 index 0000000..52bd294 --- /dev/null +++ b/src/edit_transform.erl @@ -0,0 +1,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]). + + diff --git a/src/edit_util.erl b/src/edit_util.erl new file mode 100644 index 0000000..c539277 --- /dev/null +++ b/src/edit_util.erl @@ -0,0 +1,245 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_util.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Utility functions +%%% Created : 15 Oct 2000 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_util). +-author('luke@bluetail.com'). + +-include_lib("ermacs/include/edit.hrl"). + +-export([keyname/1, status_msg/2, status_msg/3, update_minibuffer_window/2, + select_window/2, update/3]). + +-compile(export_all). + +keyname(263) -> "C-h"; +keyname(127) -> "C-h"; +keyname(272) -> "C-h"; +keyname(C) -> + case ctrl_p(C) of + true -> + "C-" ++ keyname(tolower(unctrl(C))); + false -> + case meta_p(C) of + true -> + "M-" ++ keyname(tolower(unmeta(C))); + false -> + [C] + end + end. + +%% Eighth bit set means meta +meta_p(C) -> C band meta_bit() /= 0. +unmeta(C) -> C band (bnot meta_bit()). + +meta_bit() -> 2#10000000. + +ctrl_p(C) -> Base = unmeta(C), ((Base band 2#01000000) == 0) and (Base < 32). +unctrl(C) -> C + 64. + +tolower(C) when C >= $A, C =< $Z -> + C - $A + $a; +tolower(C) -> + C. + +%% Show a one-line message in the status line (where the minibuffer is) +status_msg(State, Fmt, Args) -> + status_msg(State, io_lib:format(Fmt, Args)). +status_msg(State, String) -> + Text = lists:flatten(String), + AllWindows = [State#state.curwin | State#state.windows], + NewWindows = update(fun(Win) -> Win#window{status_text=Text} end, + fun(Win) -> Win#window.minibuffer end, + AllWindows), + State#state{curwin=hd(NewWindows), + windows=tl(NewWindows)}. + +kill_newlines([]) -> + []; +kill_newlines([$\n|T]) -> + [$ | + kill_newlines(lists:dropwhile(fun(C) -> C == $ end, T))]; +kill_newlines([H|T]) -> + [H|kill_newlines(T)]. + +%% Apply Fun to minibuffer window and put the result in the state. +update_minibuffer_window(State, Fun) -> + AllWindows = [State#state.curwin | State#state.windows], + NewWindows = update(Fun, + fun(Win) -> Win#window.minibuffer end, + AllWindows), + State#state{curwin=hd(NewWindows), + windows=tl(NewWindows)}. + +%% Map over all windows in State with Fun. +%% Fun is fun(Win) -> Win' +window_map(State, Fun) -> + State#state{curwin=Fun(State#state.curwin), + windows=lists:map(Fun, State#state.windows)}. + +%% Return a list of all windows in State. +windows(State) -> + [State#state.curwin | State#state.windows]. + +%% Use F to update the first item of L which satisfies P. +%% e.g. +update(F, P, []) -> + []; +update(F, P, [H|T]) -> + case P(H) of + true -> + [F(H)|T]; + false -> + [H|update(F, P, T)] + end. + +%% Select the first window satisfying P, or crash if no match +select_window(State, P) -> + case P(State#state.curwin) of + true -> + State; + false -> + select_window1(State, P, State#state.windows, [State#state.curwin]) + end. + +select_window1(State, P, [], Acc) -> + exit(nomatch); +select_window1(State, P, [H|T], Acc) -> + case P(H) of + true -> + State#state{curwin=H, + windows=T ++ lists:reverse(Acc)}; + false -> + select_window1(State, P, T, [H|Acc]) + end. + +set_buffer(State, Buffer) -> + Win = edit_window:attach(State#state.curwin, Buffer), + State#state{curwin = Win, + buffers = [Buffer | (State#state.buffers -- [Buffer])]}. + +%% Display the message `String'. If it's only one line, it's shown in +%% the status line. Otherwise, a buffer called `Buf' is popped up to +%% show it. +popup_message(State, Buf, String) -> + popup_message(State, Buf, "~s", [String]). + +popup_message(State, Buf, Format, Args) -> + String = lists:flatten(io_lib:format(Format, Args)), + case monoline(String) of + {true, Line} -> + edit_util:status_msg(State, Line); + false -> + edit_buf:new(Buf), % might be already started, s'ok + edit_buf:set_text(Buf, String), + popup_buffer(State, Buf) + end. + +%% See if a string fits on one line. +%% Returns: {true, Line} | false +%% Where Line is the same as the input string, but with no newlines. +monoline(S) -> monoline(S, []). + +monoline([$\n], Acc) -> {true, lists:reverse(Acc)}; +monoline([], Acc) -> {true, lists:reverse(Acc)}; +monoline([$\n|T], Acc) -> false; +monoline([H|T], Acc) -> monoline(T, [H|Acc]). + +%% Make `Buffer' visible (but not selected) in a window - either by hijacking +%% an existing window or creating a new one. +popup_buffer(State, Buffer) -> + case lists:any(fun(W) -> W#window.buffer == Buffer end, windows(State)) of + true -> + %% already visible + State; + false -> + popup_buffer1(State, Buffer) + end. + +popup_buffer1(State = #state{windows=[MB]}, Buffer) + when MB#window.minibuffer == true -> + %% Clause: One normal window, which is curwin. Split it. + {A, B} = split_window(State#state.curwin), + popup_buffer1(State#state{curwin=A, + windows=[B,MB]}, + Buffer); +popup_buffer1(State = #state{windows=[Win]}, Buffer) + when (State#state.curwin)#window.minibuffer == true -> + %% Clause: One normal window, minibuffer is curwin. Split normal + %% window. + {A, B} = split_window(Win), + popup_buffer1(State#state{windows=[A,B]}, Buffer); +popup_buffer1(State, Buffer) when State#state.windows > 1 -> + %% OK! The first non-minibuffer is used. + State#state{windows=popup_buffer_in(State#state.windows, Buffer)}. + +popup_buffer_in([W|Ws], Buffer) when W#window.minibuffer == false -> + [edit_window:attach(W, Buffer) | Ws]; +popup_buffer_in([MB|Ws], Buffer) -> + [MB | popup_buffer_in(Ws, Buffer)]. + +split_window(W) -> + Height = W#window.height, + HeightA = trunc(Height / 2), + HeightB = Height - HeightA, + io:format("Height = ~p~nHeightA = ~p~nHeightB = ~p~n", + [Height, HeightA, HeightB]), + New = edit_window:make_window(W#window.buffer, + W#window.y + HeightA, + W#window.width, + HeightB), + {W#window{height=HeightA}, New}. + +%% spawn process with some borrowed buffers + +spawn_with(Buffer, M, F, A) -> + spawn_with(Buffer, {M, F, A}). + +spawn_with(Buffers, What) -> + Ref = make_ref(), + Pid = spawn_link(?MODULE, spawn_with_init, [self(), Ref, Buffers, What]), + receive {ready, Ref} -> ok end. + +spawn_with_init(Pid, Ref, Buffers, What) -> + lists:foreach(fun(Buf) -> edit_buf:borrow(Buf) end, + Buffers), + Pid ! {ready, Ref}, + spawn_with_apply(What), + %% we miss this redraw if the command crashes. oops. + edit:invoke_later(?MODULE, redraw, []). + +spawn_with_apply({M, F, A}) -> + apply(M, F, A); +spawn_with_apply(Fun) when function(Fun) -> + Fun(). + + +redraw(State) -> + %% Actually we needn't do anything, because the screen redraws + %% after each command. (What a pointless abstraction :-) + State. + +%% Get the current working directory for the state or buffer. +pwd(State) when record(State, state) -> + pwd((State#state.curwin)#window.buffer); +pwd(Buf) when atom(Buf) -> + case edit_buf:get_filename(Buf) of + undefined -> + case file:get_cwd() of + {ok, Path} -> + case lists:last(Path) of + $/ -> + Path; + _ -> + Path ++ "/" + end; + _ -> + undefined + end; + Filename -> + filename:dirname(Filename)++"/" + end. + diff --git a/src/edit_var.erl b/src/edit_var.erl new file mode 100644 index 0000000..0af74b4 --- /dev/null +++ b/src/edit_var.erl @@ -0,0 +1,171 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_var.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Variable management server - transient and persistent +%%% Created : 21 Jan 2001 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +%%% This module implements "setq"-like variables. But, this seems a bit +%%% distasteful because of concurrent updates and so on. Maybe there is +%%% better way to do variables in general (or just program-internal +%%% variables). + +-module(edit_var). +-author('luke@bluetail.com'). + +%%-compile(export_all). +%%-export([Function/Arity, ...]). + +-behaviour(gen_server). + +%% External exports +-export([start_link/0]). + +-export([lookup/1, lookup/2, set/2, permanent/2, add_to_list/2]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). + +-record(state, {ets, dets}). + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +start_link() -> + gen_server:start_link({local, edit_var}, edit_var, [], []). + +%% Return: Value | undefined +lookup(Name) -> + lookup(Name, undefined). + +lookup(Name, Default) -> + gen_server:call(?MODULE, {lookup, Name, Default}). + +set(Name, Value) -> + gen_server:call(?MODULE, {set, Name, Value}). + +%% permanent(Name, true | false) +permanent(Name, Flag) -> + gen_server:call(?MODULE, {permanent, Name, Flag}). + +add_to_list(Name, Value) -> + List = lookup(Name, []), + edit_var:set(Name, include(List, Value)). + +include([], Value) -> [Value]; +include([Value|T], Value) -> [Value|T]; +include([H|T], Value) -> [H|include(T, Value)]. + +%%%---------------------------------------------------------------------- +%%% Callback functions from gen_server +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%%---------------------------------------------------------------------- +init([]) -> + Filename = filename:join(os:getenv("HOME"), "edit_var.dets"), + Ets = ets:new(edit_mem_var, [set, public, named_table]), + {ok, Dets} = dets:open_file(edit_disk_var, + [{type, set}, + {file, Filename}]), + load_file(Dets, Ets), + State = #state{ets=Ets, + dets=Dets}, + {ok, State}. + +%%---------------------------------------------------------------------- +%% Func: handle_call/3 +%% Returns: {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | (terminate/2 is called) +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- +handle_call({lookup, Name, Default}, From, State) -> + {reply, do_lookup(State, Name, Default), State}; + +handle_call({set, Name, Value}, From, State) -> + ets:insert(State#state.ets, {Name, Value}), + case is_permanent(State, Name) of + true -> + dets:insert(State#state.dets, {Name, Value}); + false -> + ok + end, + {reply, ok, State}; + +handle_call({permanent, Name, Flag}, From, State) -> + case Flag of + false -> + dets:delete(State#state.dets, Name); + true -> + dets:insert(State#state.dets, {Name, do_lookup(State, Name)}) + end, + {reply, ok, State}. + +%%---------------------------------------------------------------------- +%% Func: handle_cast/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- +handle_cast(Msg, State) -> + {noreply, State}. + +%%---------------------------------------------------------------------- +%% Func: handle_info/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- +handle_info(Info, State) -> + {noreply, State}. + +%%---------------------------------------------------------------------- +%% Func: terminate/2 +%% Purpose: Shutdown the server +%% Returns: any (ignored by gen_server) +%%---------------------------------------------------------------------- +terminate(Reason, State) -> + ok. + +%%---------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Convert process state when code is changed +%% Returns: {ok, NewState} +%%---------------------------------------------------------------------- +code_change(OldVsn, State, Extra) -> + {ok, State}. + +%%%---------------------------------------------------------------------- +%%% Internal functions +%%%---------------------------------------------------------------------- + +load_file(Dets, Ets) -> + F = fun(X) -> ets:insert(Ets, X) end, + dets:traverse(Dets, F). + +do_lookup(State, Name) -> + do_lookup(State, Name, undefined). + +do_lookup(State, Name, Default) -> + case ets:lookup(State#state.ets, Name) of + [{_, Value}] -> + Value; + [] -> + Default + end. + +is_permanent(State, Name) -> + case dets:lookup(State#state.dets, Name) of + [] -> + false; + _ -> + true + end. diff --git a/src/edit_window.erl b/src/edit_window.erl new file mode 100644 index 0000000..a14c047 --- /dev/null +++ b/src/edit_window.erl @@ -0,0 +1,45 @@ +%%%---------------------------------------------------------------------- +%%% File : edit_window.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Window handling functions +%%% Created : 14 Oct 2000 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(edit_window). +-author('luke@bluetail.com'). + +-include_lib("ermacs/include/edit.hrl"). + +-compile(export_all). +%%-export([Function/Arity, ...]). + +%% NB: Height is the total height including modeline +make_window(Buffer, Y, Width, Height) -> + Id = make_ref(), + W = #window{start_mark={start, Id}, + y=Y, + width=Width, + height=Height, + id=Id}, + attach(W, Buffer). + +%% Number of lines for viewing text - excludes modeline +text_lines(W) when W#window.minibuffer == true -> + physical_lines(W); +text_lines(W) -> + physical_lines(W) - 1. + +physical_lines(W) -> + W#window.height. + +width(W) -> + W#window.width. + +%% "Attach" a window to a buffer. Puts a mark in the buffer so that +%% the window knows where it's up to. +attach(Window, Buffer) -> + attach(Window, Buffer, 1). +attach(Window, Buffer, Start) -> + edit_buf:add_mark(Buffer, Window#window.start_mark, 1, backward), + Window#window{buffer=Buffer}. + diff --git a/src/ermacs.in b/src/ermacs.in new file mode 100644 index 0000000..77fa8c8 --- /dev/null +++ b/src/ermacs.in @@ -0,0 +1,12 @@ +#!/bin/sh + +BASEDIR=%BASEDIR% + +SLANG_EBIN_DIR=${BASEDIR}/../slang/ebin + +erl -pa ${SLANG_EBIN_DIR} -pa ${BASEDIR}/ebin -pa ${BASEDIR}/mods/ebin \ + -noshell -s edit start $* + +# Can I get bash to run this in response to SIGINT? +stty sane + diff --git a/src/file_gl.erl b/src/file_gl.erl new file mode 100644 index 0000000..832df73 --- /dev/null +++ b/src/file_gl.erl @@ -0,0 +1,62 @@ +%%%---------------------------------------------------------------------- +%%% File : file_gl.erl +%%% Author : Luke Gorrie <luke@bluetail.com> +%%% Purpose : Group leader server for writing to a file +%%% Created : 22 Oct 2000 by Luke Gorrie <luke@bluetail.com> +%%%---------------------------------------------------------------------- + +-module(file_gl). +-author('luke@bluetail.com'). + +-behaviour(gen_server). + +%% External exports +-export([start_link/1]). + +%% gen_server callbacks +-export([init/1, handle_info/2, terminate/2, code_change/3, + handle_call/3, handle_cast/2]). + +-record(state, {fd}). + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +start_link(Filename) -> + gen_server:start_link({local, file_gl}, file_gl, Filename, []). + +%%%---------------------------------------------------------------------- +%%% Callback functions from gen_server +%%%---------------------------------------------------------------------- + +init(Filename) -> + {ok, Fd} = file:open(Filename, [write]), + {ok, #state{fd=Fd}}. + +handle_info({io_request, From, ReplyAs, {put_chars, C}}, State) -> + file:write(State#state.fd, C), + From ! {io_reply, ReplyAs, ok}, + {noreply, State}; +handle_info({io_request, From, ReplyAs, {put_chars, M, F, A}}, State) -> + file:write(State#state.fd, apply(M, F, A)), + From ! {io_reply, ReplyAs, ok}, + {noreply, State}; +handle_info({io_request, From, ReplyAs, {get_until, _, _, _}}, State) -> + From ! {io_reply, ReplyAs, eof}, + {noreply, State}; +handle_info(Info, State) -> + {noreply, State}. + +handle_call(_, _, _) -> + exit(no_such_callback). + +handle_cast(_, _) -> + exit(no_such_callback). + +terminate(Reason, State) -> + file:close(State#state.fd), + ok. + +code_change(OldVsn, State, Extra) -> + {ok, State}. + |