aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorlukeg <lukeg>2003-02-21 19:01:14 +0000
committerlukeg <lukeg>2003-02-21 19:01:14 +0000
commite7d48fe500f6ed676ee1b212ebd61408bced1c5b (patch)
tree11a756c7bb4906f3e186c1cb8331cb7ed27bc69c
downloadermacs-fork-e7d48fe500f6ed676ee1b212ebd61408bced1c5b.tar.gz
*** empty log message ***
-rw-r--r--ChangeLog103
-rw-r--r--Makefile6
-rw-r--r--README13
-rw-r--r--TODO40
-rw-r--r--bin/.cvsignore1
-rw-r--r--doc/DESIGN36
-rw-r--r--doc/TOUR115
-rw-r--r--doc/TROUBLESHOOTING11
-rw-r--r--include/edit.hrl48
-rw-r--r--mods/src/Makefile12
-rw-r--r--mods/src/em_erlang.erl501
-rw-r--r--mods/src/em_erlang_scan.erl780
-rw-r--r--mods/src/em_erlang_scan.xrl135
-rw-r--r--mods/src/em_scan.erl381
-rw-r--r--mods/src/em_scheme.erl184
-rw-r--r--mods/src/em_scheme_scan.erl531
-rw-r--r--mods/src/em_scheme_scan.xrl18
-rw-r--r--mods/src/em_stdlib.erl21
-rw-r--r--mods/src/em_test_scan.erl255
-rw-r--r--mods/src/em_test_scan.xrl12
-rw-r--r--mods/src/leex.hrl217
-rw-r--r--src/Makefile21
-rw-r--r--src/cord.erl504
-rw-r--r--src/cord_regexp.erl296
-rw-r--r--src/edit.erl264
-rw-r--r--src/edit_bench.erl32
-rw-r--r--src/edit_buf.erl320
-rw-r--r--src/edit_complete.erl170
-rw-r--r--src/edit_display.erl154
-rw-r--r--src/edit_display.erl.slow160
-rw-r--r--src/edit_eval.erl318
-rw-r--r--src/edit_extended.erl149
-rw-r--r--src/edit_file.erl95
-rw-r--r--src/edit_genserv.erl50
-rw-r--r--src/edit_globalmap.erl126
-rw-r--r--src/edit_help.erl97
-rw-r--r--src/edit_history.erl126
-rw-r--r--src/edit_input.erl34
-rw-r--r--src/edit_keymap.erl109
-rw-r--r--src/edit_lex.erl14
-rw-r--r--src/edit_lib.erl801
-rw-r--r--src/edit_make.erl14
-rw-r--r--src/edit_mod.erl42
-rw-r--r--src/edit_prof.erl36
-rw-r--r--src/edit_terminal.erl77
-rw-r--r--src/edit_terminal_gterm.erl42
-rw-r--r--src/edit_text.erl148
-rw-r--r--src/edit_transform.erl69
-rw-r--r--src/edit_util.erl245
-rw-r--r--src/edit_var.erl171
-rw-r--r--src/edit_window.erl45
-rw-r--r--src/ermacs.in12
-rw-r--r--src/file_gl.erl62
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
+
diff --git a/README b/README
new file mode 100644
index 0000000..8ac758f
--- /dev/null
+++ b/README
@@ -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
+
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..0861469
--- /dev/null
+++ b/TODO
@@ -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}.
+