%% ``The contents of this file are subject to the Erlang Public License,
%% Version 1.0, (the "License"); you may not use this file except in
%% compliance with the License. You may obtain a copy of the License at
%% http://www.erlang.org/EPL1_0.txt
%% 
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%% 
%% The Original Code is Erlang-4.7.3, December, 1998.
%% 
%% The Initial Developer of the Original Code is Ericsson Telecom
%% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson
%% Telecom AB. All Rights Reserved.
%% 
%% Contributor(s): ______________________________________.''
%%
%% Copyright (C) 1996 Ericsson Telecommunications AB
%% File    : erl_scan.erl
%% Author  : Robert Virding
%% Purpose : Erlang token scanning functions of io library.
%% Modified: Magnus Fr|berg 
%%           Added Position to all tokens

%% previously io_lib_scan.erl

-module(erl_scan).
-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
-vsn('$Revision: /main/release/free/1').

-copyright('Copyright (c) 1996 Ericsson Telecommunications AB').

-author('rv@cslab.ericsson.se').

-export([string/1,string/2,tokens/3,format_error/1,reserved_word/1]).

%% tokens(Continuation, CharList, StartPos) ->
%%	{done, {ok, [Tok], EndPos}, Rest} |
%%	{done, {error,{ErrorPos,erl_scan,What}, EndPos}, Rest} |
%%	{more, Continuation'}
%%  This is the main function into the re-entrant scanner. It calls the
%%  re-entrant pre-scanner until this says done, then calls scan/1 on
%%  the result

tokens([], Chars, Pos) ->			%First call
    tokens({[],[],{Pos,Pos}}, Chars, Pos);
tokens({Chars,SoFar,{SPos,CPos}}, MoreChars, _) ->
    In = append(Chars, MoreChars),
    R = pre_scan(In, SoFar, CPos),
    %%erlang:display({In,R}),
    case R of
	{chars,TChars,Rest,EndPos} ->
	    case scan(TChars, SPos) of
		{ok,Toks} -> {done,{ok,Toks,EndPos},Rest};
		{error,E} -> {done,{error,E,EndPos},Rest}
	    end;
	{more,Rest,SoFar1,CPos1} ->
	    {more,{Rest,SoFar1,{SPos,CPos1}}};
	Other ->				%An error has occurred
	    {done,Other,[]}
    end.

%% string([Char]) ->
%% string([Char], Pos) ->
%%    {ok, [Tok], Pos'} |
%%    {error,{Pos,erl_scan,What}, Pos'}

string(Cs) -> string(Cs, 1).

%% string(CharList, StartPosition)

string(Cs, StartPos) ->
    case scan(Cs, StartPos) of
	{ok,Toks} -> {ok,Toks,StartPos};
	{error,E} -> {error,E,StartPos}
    end.

%% format_error(Error)
%%  Return a string describing the error.

format_error({string,Quote,Head}) ->
    ["unterminated string starting with " ++ io_lib:write_string(Head,Quote)];
format_error({illegal,Type}) -> io_lib:fwrite("illegal ~w", [Type]);
format_error(char) -> "unterminated character";
format_error(scan) -> "premature end";
format_error({base,Base}) -> io_lib:fwrite("illegal base '~w'", [Base]);
format_error(float) -> "bad float";

format_error(Other) -> io_lib:write(Other).

%% Re-entrant pre-scanner.
%%
%% If the input list of characters is insufficient to build a term the
%% scanner returns a request for more characters and a continuation to be
%% used when trying to build a term with more characters. To indicate
%% end-of-file the input character list should be replaced with 'eof'
%% as an empty list has meaning.
%% Pos holds the current line number.
%%
%% The call into it is:
%%
%%	Result = pre_scan(Characters, ScannedChars, Pos)
%%
%% where Result is:
%%	{chars,TermCharList,LeftOverList,NewPos}
%%	{more,StartCharList,OutputCharList,NewPos}
%%	{eof,NewPos}
%%	{error,{ErrorPos,erl_scan,Description},NewPos}
%%
%% Comments are stripped and replaced with 1 blank.
%%
%% Internal details:
%%
%% The output character list is built in reverse order to save appending
%% and then reversed when all the characters have been collected.
%%
%% The start of each top-level "character sequence" is passed as an argument
%% saving rebuilding input sequences when formaing a continuation. Sometimes,
%% however, when an input sequence can be long (% comments, quoted strings)
%% a special re-enter token is added to the continuation. The token is usually
%% the start characters as an atom, e.g. '%', '\''.

%% pre_scan(Characters, CharStack)
%%  Main pre-scan function. It has been split into 2 functions because of
%%  efficiency, with a good indexing compiler it would be unnecessary.

pre_scan([C|Cs], SoFar, Pos) ->
    pre_scan(C, Cs, SoFar, Pos);
pre_scan([], SoFar, Pos) ->
    {more,[],SoFar, Pos};
pre_scan(eof, SoFar, Pos) ->
    pre_end(SoFar, Pos).

%% pre_scan(+Char, +RestChars, +CharStack, Pos)

pre_scan($$, Cs0, SoFar0, Pos) ->
    case pre_scan_char(Cs0, [$$|SoFar0]) of
	{Cs,SoFar} ->
	    pre_scan(Cs, SoFar, Pos);
	more ->
	    {more,[$$|Cs0],SoFar0, Pos};
	error ->
	    pre_scan_error(char, Pos)
    end;
pre_scan($', Cs, SoFar, Pos) ->
    pre_scan_string(Cs, $', '\'', [$'|SoFar], Pos);
pre_scan('\'', Cs, SoFar, Pos) ->			%Re-entering atom
    pre_scan_string(Cs, $', '\'', SoFar, Pos);
pre_scan($", Cs, SoFar, Pos) ->
    pre_scan_string(Cs, $", '"', [$"|SoFar], Pos);
pre_scan('"', Cs, SoFar, Pos) ->			%Re-entering string
    pre_scan_string(Cs, $", '"', SoFar, Pos);
pre_scan($%, Cs, SoFar, Pos) ->
    pre_scan_comment(Cs, SoFar, Pos);
pre_scan('%', Cs, SoFar, Pos) ->
    pre_scan_comment(Cs, SoFar, Pos);
pre_scan($., Cs, SoFar, Pos) ->
    pre_scan_dot(Cs, SoFar, Pos);
pre_scan($\n, Cs, SoFar, Pos) ->
    pre_scan(Cs, [$\n|SoFar], Pos+1);
pre_scan(C, Cs, SoFar, Pos) ->
    pre_scan(Cs, [C|SoFar], Pos).

pre_scan_string([Quote|Cs], Quote, Reent, SoFar, Pos) ->
    pre_scan(Cs, [Quote|SoFar], Pos);
pre_scan_string([$\n|Cs], Quote, Reent, SoFar, Pos) ->
    pre_scan_string(Cs, Quote, Reent, [$\n|SoFar], Pos+1);
pre_scan_string([C|Cs0], Quote, Reent, SoFar0, Pos) ->
    case pre_scan_char([C|Cs0], SoFar0) of
	{Cs,SoFar} ->
	    pre_scan_string(Cs, Quote, Reent, SoFar, Pos);
	more ->
	    {more,[Reent,C|Cs0],SoFar0,Pos};
	error ->
	    S = reverse(string:substr(SoFar0, 1, string:chr(SoFar0, Quote)-1)),
	    pre_scan_error({string,Quote,string:substr(S, 1, 16)}, Pos)
    end;
pre_scan_string([], Quote, Reent, SoFar, Pos) ->
    {more,[Reent],SoFar,Pos};
pre_scan_string(eof, Quote, _, SoFar, Pos) ->
    S = reverse(string:substr(SoFar, 1, string:chr(SoFar, Quote)-1)),
    pre_scan_error({string,Quote,string:substr(S, 1, 16)}, Pos).

pre_scan_char([$\\|Cs0], SoFar) ->
    case Cs0 of
	[$^,C3|Cs] ->
	    {Cs,[C3,$^,$\\|SoFar]};
	[$^] ->
	    more;
	[$^|eof] ->
	    error;
	[C2|Cs] ->
	    {Cs,[C2,$\\|SoFar]};
	[] ->
	    more;
	eof ->
	    error
    end;
pre_scan_char([C|Cs], SoFar) ->
    {Cs,[C|SoFar]};
pre_scan_char([], _) ->
    more;
pre_scan_char(eof, _) ->
    error.

pre_scan_comment([$\n|Cs], SoFar, Pos) ->
    pre_scan(Cs, [$\n|SoFar], Pos+1);		%Ignore NL for now, but add it
pre_scan_comment([_|Cs], SoFar, Pos) ->
    pre_scan_comment(Cs, SoFar, Pos);
pre_scan_comment([], SoFar, Pos) ->
    {more,['%'],SoFar,Pos};
pre_scan_comment(eof, Sofar, Pos) ->
    pre_scan(eof, [$\n|Sofar], Pos+1).

%% pre_scan_dot([Char], SoFar, Pos)

pre_scan_dot([$\n|Cs], SoFar, Pos) ->
    Chars = reverse(SoFar, [$.,$\n]),
    {chars,Chars,Cs,Pos+1};
pre_scan_dot([$%|Cs], SoFar, Pos) ->		%Special case .%
    Chars = reverse(SoFar, [$.,$ ]),		% and don't consume it!
    {chars,Chars,[$%|Cs],Pos};
pre_scan_dot([C|Cs], SoFar, Pos) when C >= 0, C =< $  ->
    Chars = reverse(SoFar, [$.,C]),
    {chars,Chars,Cs,Pos};
pre_scan_dot([C|Cs], SoFar, Pos) ->
    pre_scan([C|Cs], [$.|SoFar], Pos);
pre_scan_dot([], SoFar, Pos) ->
    {more,[$.],SoFar,Pos};
pre_scan_dot(eof, SoFar, Pos) ->
    Chars = reverse(SoFar, [$.,$ ]),
    {chars,Chars,eof,Pos}.

pre_scan_error(In, Pos) ->
    {error,{Pos,erl_scan,In}, Pos}.

%% pre_end(OutChars, Pos)
%%  Have hit end-of-file before . <blank>, check if just "blanks" or if
%%  this is a premature end.

pre_end([], Pos) ->
    {eof,Pos};
pre_end([C|Cs], Pos) when C >= 0, C =< $  ->
    pre_end(Cs, Pos);
pre_end(_, Pos) ->
    pre_scan_error(scan, Pos).

%% scan(CharList, StartPos)
%%  This takes a list of characters and tries to tokenise them.
%%
%%  The token list is built in reverse order (in a stack) to save appending
%%  and then reversed when all the tokens have been collected. Most tokens
%%  are built in the same way.
%%
%%  Returns:
%%	{ok,[Tok]}
%%	{error,{ErrorPos,erl_scan,What}}

scan(Cs, Pos) ->
    scan1(Cs, [], Pos).

%% scan1(Characters, TokenStack, Position)
%%  Scan a list of characters into tokens.

scan1([$\n|Cs], Toks, Pos) ->            	        %Newline
    scan1(Cs, Toks, Pos+1);
scan1([C|Cs], Toks, Pos) when C >= 0, C =< $  -> 	%Skip blanks
    scan1(Cs, Toks, Pos);
scan1([C|Cs], Toks, Pos) when C >= $a, C =< $z ->	%Atoms
    scan_atom(C, Cs, Toks, Pos);
scan1([C|Cs], Toks, Pos) when C >= $0, C =< $9 ->	%Numbers
    scan_number(C, Cs, Toks, Pos);
scan1([C|Cs], Toks, Pos) when C >= $A, C =< $Z ->	%Variables
    scan_variable(C, Cs, Toks, Pos);
scan1([$_|Cs], Toks, Pos) ->				%_ variables
    scan_variable($_, Cs, Toks, Pos);
scan1([$$|Cs], Toks, Pos) ->				%Character constant
    case scan_char_const(Cs, Toks, Pos) of
	{ok, Result} ->
	    {ok, Result};
	{error, truncated_char} ->
	    scan_error(char, Pos)
    end;
scan1([$'|Cs0], Toks, Pos) ->				%Quoted atom
%%%    {S,Cs1,Pos1} = scan_string(Cs0, $', Pos),
    case scan_string(Cs0, $', Pos) of
	{S,Cs1,Pos1} ->
	    case catch list_to_atom(S) of
		A when atom(A) ->
		    scan1(Cs1, [{atom,Pos,A}|Toks], Pos1);
		Error -> scan_error({illegal,atom}, Pos)
	    end;
	{error, premature_end} ->
	    scan_error({string,$',Cs0}, Pos);
	{error, truncated_char} ->
	    scan_error(char, Pos)
    end;
scan1([$"|Cs0], Toks, Pos) ->				%String
%%%    {S,Cs1,Pos1} = scan_string(Cs0, $", Pos),
    case scan_string(Cs0, $", Pos) of
	{S,Cs1,Pos1} ->
	    scan1(Cs1, [{string,Pos,S}|Toks], Pos1);
	{error, premature_end} ->
	    scan_error({string,$",Cs0}, Pos);
	{error, truncated_char} ->
	    scan_error(char, Pos)
    end;
%% Punctuation characters and operators, first recognise multiples.
scan1([$>,$=|Cs], Toks, Pos) ->
    scan1(Cs, [{'>=',Pos}|Toks], Pos);
scan1([$<,$-|Cs], Toks, Pos) ->
    scan1(Cs, [{'<-',Pos}|Toks], Pos);
scan1([$<,$=|Cs], Toks, Pos) ->
    scan1(Cs, [{'<=',Pos}|Toks], Pos);
scan1([$-,$>|Cs], Toks, Pos) ->
    scan1(Cs, [{'->',Pos}|Toks], Pos);
scan1([$-,$-|Cs], Toks, Pos) ->
    scan1(Cs, [{'--',Pos}|Toks], Pos);
scan1([$+,$+|Cs], Toks, Pos) ->
    scan1(Cs, [{'++',Pos}|Toks], Pos);
scan1([$=,$<|Cs], Toks, Pos) ->
    scan1(Cs, [{'=<',Pos}|Toks], Pos);
scan1([$=,$=|Cs], Toks, Pos) ->
    scan1(Cs, [{'==',Pos}|Toks], Pos);
scan1([$=,$:,$=|Cs], Toks, Pos) ->
    scan1(Cs, [{'=:=',Pos}|Toks], Pos);
scan1([$=,$/,$=|Cs], Toks, Pos) ->
    scan1(Cs, [{'=/=',Pos}|Toks], Pos);
scan1([$/,$=|Cs], Toks, Pos) ->
    scan1(Cs, [{'/=',Pos}|Toks], Pos);
scan1([$|,$||Cs], Toks, Pos) ->
    scan1(Cs, [{'||',Pos}|Toks], Pos);
scan1([$:,$-|Cs], Toks, Pos) ->
    scan1(Cs, [{':-',Pos}|Toks], Pos);
scan1([$.|Cs], Toks, Pos) ->
    scan_dot(Cs, Toks, Pos);
scan1([C|Cs], Toks, Pos) ->				%Punctuation character
    P = list_to_atom([C]),
    scan1(Cs, [{P,Pos}|Toks], Pos);
scan1([], Toks0, Pos) ->
    Toks = reverse(Toks0),
    {ok,Toks}.

%% Scan = scan_atom(FirstChar, CharList, Tokens, Pos)

scan_atom(C, Cs, Toks, Pos) ->
    scan_word(atom, C, Cs, Toks, Pos).

scan_variable(C, Cs, Toks, Pos) ->
    scan_word(var, C, Cs, Toks, Pos).

scan_word(Type, C, Cs0, Toks, Pos) ->
    {Wcs,Cs} = scan_name(Cs0, []),
    case {Type,catch list_to_atom([C|reverse(Wcs)])} of
	{var,W} when atom(W) ->
	    scan1(Cs, [{Type,Pos,W}|Toks], Pos);
	{Type,W} when atom(W) ->
	    case reserved_word(W) of
		true -> scan1(Cs, [{W,Pos}|Toks], Pos);
		false -> scan1(Cs, [{Type,Pos,W}|Toks], Pos)
	    end;
	Error -> scan_error({illegal,atom}, Pos)
    end.

%% scan_name(Cs) -> lists:splitwith(fun (C) -> name_char(C) end, Cs).

scan_name([C|Cs], Ncs) ->
    case name_char(C) of
	true ->
	    scan_name(Cs, [C|Ncs]);
	false ->
	    {Ncs,[C|Cs]}			%Must rebuild here, sigh!
    end;
scan_name([], Ncs) ->
    {Ncs,[]}.

name_char(C) when C >= $a, C =< $z -> true;
name_char(C) when C >= $A, C =< $Z -> true;
name_char(C) when C >= $0, C =< $9 -> true;
name_char($_) -> true;
name_char($@) -> true;
name_char(_) -> false.

%% {StringChars,RestChars, NewPos} = scan_string(CharList, QuoteChar, Pos)

scan_string(Cs, Quote, Pos) ->
    scan_string(Cs, [], Quote, Pos).

scan_string([Quote|Cs], Scs, Quote, Pos) ->
    {reverse(Scs),Cs,Pos};
scan_string([], Scs, Quote, Pos) ->
    {error, premature_end};
scan_string(Cs0, Scs, Quote, Pos) ->
    case scan_char(Cs0, Pos) of
	{C,Cs,Pos1} ->
	    scan_string(Cs, [C|Scs], Quote, Pos1); %Only build the string here
	Error ->
	    Error
    end.

scan_char_const(Cs0, Toks, Pos) ->
    case scan_char(Cs0, Pos) of
	{C,Cs,Pos1} ->
	    scan1(Cs, [{integer,Pos,C}|Toks], Pos1);
	Error ->
	    Error
    end.

%% {Character,RestChars,NewPos} = scan_char(Chars, Pos)
%%  Read a single character from a string or character constant. The
%%  pre-scan phase has checked for errors here.

scan_char([$\\|Cs], Pos) ->
    scan_escape(Cs, Pos);
scan_char([$\n|Cs], Pos) ->                  %Newline
    {$\n,Cs,Pos+1};
scan_char([C|Cs], Pos) ->
    {C,Cs,Pos};
scan_char([], Pos) ->
    {error, truncated_char}.

scan_escape([O1,O2,O3|Cs], Pos) when            %\<1-3> octal digits
    O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 ->
    Val = (O1*8 + O2)*8 + O3 - 73*$0,
    {Val,Cs,Pos};
scan_escape([O1,O2|Cs], Pos) when
    O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7 ->
    Val = (O1*8 + O2) - 9*$0,
    {Val,Cs,Pos};
scan_escape([O1|Cs], Pos) when
    O1 >= $0, O1 =< $7 ->
    {O1 - $0,Cs,Pos};
scan_escape([$^,C|Cs], Pos) ->			%\^X -> CTL-X
    Val = C band 31,
    {Val,Cs,Pos};
scan_escape([$\n,C1|Cs],Pos) ->
    {C1,Cs,Pos+1};
scan_escape([C,C1|Cs],Pos) when C >= 0, C =< $  ->
    {C1,Cs,Pos};
scan_escape([C0|Cs],Pos) ->
    C = escape_char(C0),
    {C,Cs,Pos};
scan_escape([],Pos) ->
    {error, truncated_char}.

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($d) -> $\d;				%\d = DEL
escape_char($s) -> $ ;				%\s = space
escape_char(C) -> C.

%% scan_number(Char, CharList, TokenStack, Pos)
%%  We can handle simple radix notation:
%%    <digit>#<digits>		- the digits read in that base
%%    <digits>			- the digits in base 10
%%    <digits>.<digits>
%%    <digits>.<digits>E+-<digits>
%%
%%  Except for explicitly based integers we build a list of all the
%%  characters and then use list_to_integer/1 or list_to_float/1 to
%%  generate the value.

%%  SPos == Start position
%%  CPos == Current position

scan_number(C, Cs0, Toks, Pos) ->
    {Ncs,Cs,Pos1} = scan_integer(Cs0, [C], Pos),
    scan_after_int(Cs, Ncs, Toks, Pos, Pos1).

scan_integer([C|Cs], Stack, Pos) when C >= $0, C =< $9 ->
    scan_integer(Cs, [C|Stack], Pos);
scan_integer(Cs, Stack, Pos) ->
    {Stack,Cs,Pos}.

scan_after_int([$.,C|Cs0], Ncs0, Toks, SPos, CPos) when C >= $0, C =< $9 ->
    {Ncs,Cs,CPos1} = scan_integer(Cs0, [C,$.|Ncs0], CPos),
    scan_after_fraction(Cs, Ncs, Toks, SPos, CPos1);	
scan_after_int([$#|Cs], Ncs, Toks, SPos, CPos) ->
    case list_to_integer(reverse(Ncs)) of
	Base when Base >= 2, Base =< 16 ->
	    scan_based_int(Cs, 0, Base, Toks, SPos, CPos);
	Base ->
	    scan_error({base,Base}, CPos)
    end;
scan_after_int(Cs, Ncs, Toks, SPos, CPos) ->
    N = list_to_integer(reverse(Ncs)),
    scan1(Cs, [{integer,SPos,N}|Toks], CPos).

scan_based_int([C|Cs], SoFar, Base, Toks, SPos, CPos) when
    C >= $0, C =< $9, C < Base + $0 ->
    Next = SoFar * Base + (C - $0),
    scan_based_int(Cs, Next, Base, Toks, SPos, CPos);
scan_based_int([C|Cs], SoFar, Base, Toks, SPos, CPos) when
    C >= $a, C =< $f, C < Base + $a - 10 ->
    Next = SoFar * Base + (C - $a + 10),
    scan_based_int(Cs, Next, Base, Toks, SPos, CPos);
scan_based_int([C|Cs], SoFar, Base, Toks, SPos, CPos) when
    C >= $A, C =< $F, C < Base + $A - 10 ->
    Next = SoFar * Base + (C - $A + 10),
    scan_based_int(Cs, Next, Base, Toks, SPos, CPos);
scan_based_int(Cs, SoFar, _, Toks, SPos, CPos) ->
    scan1(Cs, [{integer,SPos,SoFar}|Toks], CPos).

scan_after_fraction([$E|Cs], Ncs, Toks, SPos, CPos) ->
    scan_exponent(Cs, [$E|Ncs], Toks, SPos, CPos);
scan_after_fraction([$e|Cs], Ncs, Toks, SPos, CPos) ->
    scan_exponent(Cs, [$E|Ncs], Toks, SPos, CPos);
scan_after_fraction(Cs, Ncs, Toks, SPos, CPos) ->
    case catch list_to_float(reverse(Ncs)) of
	N when float(N) ->
	    scan1(Cs, [{float,SPos,N}|Toks], CPos);
	Error -> scan_error({illegal,float}, SPos)
    end.

%% scan_exponent(CharList, NumberCharStack, TokenStack, StartPos, CurPos)
%%  Generate an error here if E{+|-} not followed by any digits.

scan_exponent([$+|Cs], Ncs, Toks, SPos, CPos) ->
    scan_exponent1(Cs, [$+|Ncs], Toks, SPos, CPos);
scan_exponent([$-|Cs], Ncs, Toks, SPos, CPos) ->
    scan_exponent1(Cs, [$-|Ncs], Toks, SPos, CPos);
scan_exponent(Cs, Ncs, Toks, SPos, CPos) ->
    scan_exponent1(Cs, Ncs, Toks, SPos, CPos).

scan_exponent1([C|Cs0], Ncs0, Toks, SPos, CPos) when C >= $0, C =< $9 ->
    {Ncs,Cs,CPos1} = scan_integer(Cs0, [C|Ncs0], CPos),
    case catch list_to_float(reverse(Ncs)) of
	N when float(N) ->
	    scan1(Cs, [{float,SPos,N}|Toks], CPos1);
	Error -> scan_error({illegal,float}, SPos)
    end;
scan_exponent1(_, _, _, _, CPos) ->
    scan_error(float, CPos).

%% scan_dot(CharList, Tokens, Position)
%%  Scan a dot to see if is a terminating token.

scan_dot([$\n|Cs], Toks, Pos) ->
    scan1(Cs, [{dot,Pos}|Toks], Pos+1);
scan_dot([C|Cs], Toks, Pos) when C =< $  ->
    scan1(Cs, [{dot,Pos}|Toks], Pos);
scan_dot(Cs, Toks, Pos) ->
    scan1(Cs, [{'.',Pos}|Toks], Pos).

scan_error(In, Pos) ->
    {error,{Pos,erl_scan,In}}.

%% reserved_word(Atom) -> Bool
%%   return 'true' if Atom is an Erlang reserved word, else 'false'.

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.

%%
%% Utilities
%%

append([H|T], Tail) ->
    [H|append(T, Tail)];
append([], Tail) ->
    Tail.

reverse(List) ->
    reverse(List, []).

reverse([], Stack) ->
    Stack;
reverse([H|T], Stack) ->
    reverse(T, [H|Stack]).

head([H|T], N) when N > 0 ->
    [H|head(T, N-1)];
head(L, N) -> [].
