I built a REPL for a language in Erlang. I run it from the shell, like so:
repl:main(Args).
It reads using io_getchars
until it encounters a ;
, then parses and lexes it:
getchar(eof) -> throw(eof);
getchar([C]) -> C.
getchar() -> getchar(io:get_chars("", 1)).
read_term() ->
case getchar() of
$; -> ";";
C -> [C|read_term()]
end.
That's all well and good, but the interaction with the repl looks like this:
willow% erl -name MyName
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:4:4] [async-threads:10] [kernel-poll:false]
Eshell V7.3 (abort with ^G)
(MyName)1> repl:main(Args).
Master is <0.39.0> in init_workers
1> map(fn(x)=x+1, '[1 2 3 4 5]);
ap(fn(x)=x+1, '[1 2 3 4 5]);
p(fn(x)=x+1, '[1 2 3 4 5]);
(fn(x)=x+1, '[1 2 3 4 5]);
fn(x)=x+1, '[1 2 3 4 5]);
n(x)=x+1, '[1 2 3 4 5]);
(x)=x+1, '[1 2 3 4 5]);
x)=x+1, '[1 2 3 4 5]);
)=x+1, '[1 2 3 4 5]);
=x+1, '[1 2 3 4 5]);
x+1, '[1 2 3 4 5]);
+1, '[1 2 3 4 5]);
1, '[1 2 3 4 5]);
, '[1 2 3 4 5]);
'[1 2 3 4 5]);
'[1 2 3 4 5]);
[1 2 3 4 5]);
1 2 3 4 5]);
2 3 4 5]);
2 3 4 5]);
3 4 5]);
3 4 5]);
4 5]);
4 5]);
5]);
5]);
]);
);
;
Delegated packet 0
Delegated packet 1
Delegated packet 2
Delegated packet 3
Delegated packet 4
(2 3 4 5 6)
2>
I also tried:
read_term(Acc) ->
case io:request(standard_io, {get_until, '', scanner, token, [1]}) of
{ok, EndToken={';;', _}, _} -> Acc ++ [EndToken];
{ok, Token, _} -> read_term(Acc ++ [Token]);
{error, token} -> {error, scanning_error};
{eof, _} -> Acc
end.
And it unfortunately has the same effect.
I'd rather not see every possible right-moving slice of the string I entered. What is causing this and how can I stop it?
EDIT: If I run it as erl -noshell -eval 'repl:main()'
from the shell instead, this printing does not happen. Why?
You will have to use io:get_line
instead, and iterate over the received string. Then you have to decide what to do if the received string does not contain any ";"
for example:
1> Eval = fun(X) -> io:format("evaluation of ~p~n",[X]) end.
#Fun<erl_eval.6.50752066>
2> F = fun() ->
R = io:get_line("Enter a string : "),
L = string:tokens(R,";"),
[Eval(X) || X <- L]
end.
#Fun<erl_eval.20.50752066>
3> F().
Enter a string : map(fn(x)=x+1, '[1 2 3 4 5]);map(fn(x)=x+5, '[1 2 3 4 5]);uncomplete
evaluation of " map(fn(x)=x+1, '[1 2 3 4 5])"
evaluation of "map(fn(x)=x+5, '[1 2 3 4 5])"
evaluation of "uncomplete\n"
[ok,ok,ok]
4> F().
Enter a string : map(fn(x)=x+1, '[1 2 3 4 5]);
evaluation of " map(fn(x)=x+1, '[1 2 3 4 5])"
evaluation of "\n"
[ok,ok]
5>
[edit]
io:get_chars/2
is taking the number of chars given as parameter from the shell input, 1 in your case, and let the rest. The shell can't know that you will invoke again the io:get_chars/2
function, so it prints again the rest of the input string, and it does the same again and again at each call of the read_term/0
function, it is why you see this weird behavior.
io:get_line/2
takes the whole input from the shell and return it in a string (in erlang it is a list of integer), it is why you have a clean behavior in the shell window, then it is up to you to choose your favorite algorithm to analyze this string. I have chosen string:tokens/2
because I try to use library functions when they exists, I think it is easier to read, and does not need documentation :o)
If you don't want to take care of possible incorrect input, you can replace the F/0 function by
2> F = fun() ->
2> Rep = io:get_line("Enter a string : "),
2> [L,"\n"] = string:tokens(Rep,";"),
2> L
2> end.
and it will do the same thing that your read_term/0 function (except it removes the last $;, but it checks its presence):
3> F().
Enter a string : correct input;
"correct input"
4> F().
Enter a string : missing semi colon
** exception error: no match of right hand side value ["missing semi colon\n"]
5> F().
Enter a string : correct input; but extracharacters after semi colon
** exception error: no match of right hand side value ["correct input"," but extracharacters after semi colon\n"]