%%%
%%% Authors:
%%%   Leif Kornstaedt <kornstae@ps.uni-sb.de>
%%%   Andreas Sundstroem <andreas@sics.se>
%%%
%%% Copyright:
%%%   Leif Kornstaedt, 1998
%%%   Andreas Sundstroem, 1998
%%%
%%% Last change:
%%%   $Date: 1999/11/26 19:51:43 $Author: 
%%%   $Revision: 
%%%
%%% This file is part of Mozart, an implementation of Oz 3:
%%%    http://www.mozart-oz.org
%%%
%%% See the file "LICENSE" or
%%%    http://www.mozart-oz.org/LICENSE.html
%%% for information on usage and redistribution
%%% of this file, and for a DISCLAIMER OF ALL
%%% WARRANTIES.
%%%

OzScanner
GS = GumpScanner.'class'

local
   Windows = ({Property.get platform}.os == win32)
   
   fun {GetUserHome _} "" end
   
   fun {FileExists FileName}
      try F in
	 F = {New Open.file init(name: FileName flags: [read])}
	 {F close()}
	 true
      catch _ then false
      end
   end
   
   %--------------------------------------------------------------------
   % Scanner Auxiliary Functions
   %--------------------------------------------------------------------
   
   %--------------------------------------------------------------------
   % File Name Handling
   
   local
      PathSeparator = if Windows then &; else &: end
      
      fun {IsAbsoluteFileName S}
	 case S of &/|_ then true
	 elseof _|&:|_ then Windows   % good old DOS filename like E:...
	 else false
	 end
      end

      fun {CheckAccess FileName}
	 if {FileExists FileName} then FileName
	 else FileName2 = {Append FileName ".oz"} in
	    if {FileExists FileName2} then FileName2
	    else ""
	    end
	 end
      end

      fun {GetDirName FileName}
	 {Reverse {List.dropWhile {Reverse FileName} fun {$ C} C \= &/ end}}
      end

      fun {SearchPath Path FileName}
	 case Path of _|_ then P1 Pr in
	    {List.takeDropWhile Path fun {$ C} C \= PathSeparator end ?P1 ?Pr}
	    case {CheckAccess {Append P1 &/|FileName}} of Res=_|_ then Res
	    elsecase Pr of !PathSeparator|Prr then {SearchPath Prr FileName}
	    [] nil then ""
	    end
	 [] nil then {CheckAccess &.|&/|FileName}
	 end
      end
   in
      fun {ExpandFileName FileName CurrentFileName}
	 if {IsAbsoluteFileName FileName} then {CheckAccess FileName}
	 else
	    case FileName of &~|Sr then FileRest UserHome in   % expand `~'
	       case Sr of &/|Srr then
		  FileRest = Srr
		  UserHome = {OS.getEnv "HOME"}
	       else User in
		  FileRest = {List.takeDropWhile Sr
			      fun {$ C} C \= &/ end ?User}
		  UserHome = {GetUserHome User}
	       end
	       case UserHome of "" then ""
	       [] _|_ then {CheckAccess {Append UserHome &/|FileRest}}
	       end
	    elsecase {CheckAccess
		      {Append {GetDirName CurrentFileName} FileName}}
	    of S2=_|_ then S2
	    [] "" then {SearchPath {OS.getEnv "OZPATH"} FileName}
	    end
	 end
      end
   end
   
   %--------------------------------------------------------------------
   % String Handling

   fun {ButLast Xs}
      case Xs of X|Xr then
	 case Xr of _|_ then X|{ButLast Xr}
	 else nil
	 end
      else nil
      end
   end

   fun {Strip C S}
      case S of !C|Sr then {ButLast Sr} else S end
   end

   Hex = hex(&0: 0x0 &1: 0x1 &2: 0x2 &3: 0x3 &4: 0x4
	     &5: 0x5 &6: 0x6 &7: 0x7 &8: 0x8 &9: 0x9
	     &A: 0xA &B: 0xB &C: 0xC &D: 0xD &E: 0xE &F: 0xF
	     &a: 0xA &b: 0xB &c: 0xC &d: 0xD &e: 0xE &f: 0xF)

   %--------------------------------------------------------------------
   % Scanner and Parser Auxiliary Classes
   %--------------------------------------------------------------------

   class ConditionalClass from BaseObject
      attr Conds AllTrue 
      meth init()
	 Conds <- nil
	 AllTrue <- true
      end
      meth pushCondition(Flag)
	 Conds <- Flag|@Conds
	 AllTrue <- if Flag then @AllTrue else false end
      end
      meth popCondition()
	 Conds <- @Conds.2
	 if @AllTrue then skip
	 else AllTrue <- {All @Conds fun {$ C} C end}
	 end
      end
      meth negateCondition() C|Cs = @Conds in
	 Conds <- {Not C}|Cs
	 if C then AllTrue <- false
	 else AllTrue <- {All @Conds fun {$ C} C end}
	 end
      end
      meth testCondition($)
	 @AllTrue
      end
      meth currentlyConditional($)
	 @Conds \= nil
      end
   end

   class MacroClass from BaseObject
      attr Dict
      meth init(Dict0)
	 Dict <- Dict0
      end
      meth 'define'(V)
	 {Dictionary.put @Dict V true}
      end
      meth undefine(V)
	 {Dictionary.remove @Dict V}
      end
      meth isDefined(V $)
	 {Dictionary.member @Dict V}
      end
   end
in
   %--------------------------------------------------------------------
   % The Scanner
   %--------------------------------------------------------------------

%   \gumpscannerprefix zz

   scanner OzScanner from GS
      attr
	 ErrorFlag ErrorCoord
	 Conditionals Macros
	 ShowInsertSwitch
	 BufferStack
	 filename line col
	 savedFilename savedLine savedCol
	 % coordinations for parse errors, the last tokens coordinates
	 peFilename peLine peCol
	 CommentLastMode CommentCoord CommentDepth
	 NewLineNumber LineErrorFlag
	 Reporter GumpSyntax
	 parserExpect scannerPrefix

      meth init(gump:UseGumpSyntax showInsert:ShowInsert reporter:Report
		macros:Dict)
	 GS, init()
	 ErrorFlag <- false
	 BufferStack <- nil
	 Macros <- {New MacroClass init(Dict)}
	 Conditionals <- {New ConditionalClass init()}
	 
	 ShowInsertSwitch <- ShowInsert
	 GumpSyntax <- UseGumpSyntax
	 filename <- ''
	 col <- 0
	 line <- 1
	 Reporter <- Report
	 parserExpect <- 0
	 scannerPrefix <- ''

	 _ = DIRECTIVEWITHARGS \= DIRECTIVE_FATHER
      end

      meth getParserExpect($)
	 @parserExpect
      end
      meth getScannerPrefix($)
	 @scannerPrefix
      end

      meth gumpSyntax(X)
	 case X
	 of on then GumpSyntax <- true 
	 [] off then GumpSyntax <- false
	 end
      end
      
      meth reportError(C K M) 
	 {@Reporter error(coord:C kind:K msg:M)}
      end

      meth setParseErrorCoords()
	 peFilename <- @filename
	 peLine <- @line
	 peCol <- @col
      end


      % sets parse error coordinates and updates col
      meth updateCol(C)
	 if C \= none then 
	    peFilename <- @filename
	    peLine <- @line
	    peCol <- @col
	    col <- @col + C
	 end
      end

      %put token, sets parse error coordinates and updates col
      meth pt1(T ColDiff)
	 if {@Conditionals testCondition($)} then
	    OzScanner, updateCol(ColDiff)
	    GS, putToken1(T)
	 else skip
	 end
      end
      meth pt(T V ColDiff)
	 if {@Conditionals testCondition($)} then
	    OzScanner, updateCol(ColDiff)
	    GS, putToken(T V)
	 else skip
	 end
      end

      meth scanFile(FN) Stripped FileName AtomFileName in
	 Stripped = {Strip &' FN}
	 FileName = {ExpandFileName Stripped {Atom.toString @filename}}
	 if @ShowInsertSwitch then
	    {System.showInfo '%%%     inserting file "'#FileName#'"'}
	 else skip
	 end

	 AtomFileName = {String.toAtom FileName}
	 filename <- AtomFileName
	 GS, scanFile(AtomFileName)
      end
      
      %--------------------------------------------------------------------
      % Handle Coordinate Information and Multiple Buffers

      meth PushBuffer(FileName)
	 try
	    GS, scanFile(FileName)
	    BufferStack <- (@filename#@line#@col#@Conditionals)|@BufferStack
	    filename <- {String.toAtom FileName}
	    line <- 1
	    col <- 0
	    Conditionals <- {New ConditionalClass init()}
	 catch gump(fileNotFound _) then C in
	    OzScanner, getCoordinates(?C)
	    {self reportError(C 'macro directive error'
			      'could not open file to insert')}
	 end
      end
      meth PopBuffer()
	 if {@Conditionals currentlyConditional($)} then C in
	    OzScanner, getCoordinates(?C)
	    {self reportError(C 'macro directive error'
			      'unterminated \\ifdef of \\ifndef')}
	 else skip
	 end
	 ErrorFlag <- false
	 GS, closeBuffer()
	 case @BufferStack of (F#L#C#Cond)|BufferStackRest then
	    BufferStack <- BufferStackRest
	    OzScanner, setParseErrorCoords()
	    filename <- F
	    line <- L
	    col <- C
	    Conditionals <- Cond
	    OzScanner, setMode(DIRECTIVE)
	 [] nil then
	    GS, putToken1('EOF')
	 end
      end

      %-----------------------------------------------------------------

      meth getCoordinates($)
	 pos(@filename @line @col)
      end
      meth SaveCoordinates()
	 % This has to be called each time a token of one of the following
	 % classes is produced:
	 %   'ATOM' 'ATOM()' 'VARIABLE' 'VARIABLE()' 'STRING' 'INT' 'FLOAT'
	 %   'Compare' 'FdCompare' 'FdIn' 'Add' 'FdMul' 'OtherMul'
	 savedFilename <- @filename
	 savedLine <- @line
	 savedCol <- @col
      end
      meth getSavedCoordinates($)
	 pos(@savedFilename @savedLine @savedCol)
      end
      meth UpdateCoordinates(S)
	 case S of C|Sr then
	    case C of &\n then
	       line <- @line + 1
	       col <- 0
	    else
	       col <- @col + 1
	    end
	    OzScanner, UpdateCoordinates(Sr)
	 [] nil then skip
	 end
      end
      meth parseErrorCoordinates($)
	 pos(@peFilename @peLine @peCol)
      end

      meth ConvertPseudoChars(S Col $)
	 case S of &\\|C1|Cr then
	    case C1
	    of &a then &\a|(OzScanner, ConvertPseudoChars(Cr Col + 2 $))
	    [] &b then &\b|(OzScanner, ConvertPseudoChars(Cr Col + 2 $))
	    [] &f then &\f|(OzScanner, ConvertPseudoChars(Cr Col + 2 $))
	    [] &n then &\n|(OzScanner, ConvertPseudoChars(Cr Col + 2 $))
	    [] &r then &\r|(OzScanner, ConvertPseudoChars(Cr Col + 2 $))
	    [] &t then &\t|(OzScanner, ConvertPseudoChars(Cr Col + 2 $))
	    [] &v then &\v|(OzScanner, ConvertPseudoChars(Cr Col + 2 $))
	    [] &\\ then &\\|(OzScanner, ConvertPseudoChars(Cr Col + 2 $))
	    [] &` then &`|(OzScanner, ConvertPseudoChars(Cr Col + 2 $))
	    [] &" then &"|(OzScanner, ConvertPseudoChars(Cr Col + 2 $))
	    [] &' then &'|(OzScanner, ConvertPseudoChars(Cr Col + 2 $))
	    [] && then &&|(OzScanner, ConvertPseudoChars(Cr Col + 2 $))
	    else
	       if C1 == &x orelse C1 == &X then C2|C3|Crr = Cr C in
		  C = Hex.C2 * 16 + Hex.C3
		  if C == 0 then
		     if {@Conditionals testCondition($)} then
			{self reportError(pos(@filename @line Col)
					  'lexical error'
				   'character in hexadecimal notation == 0')}
		     else skip
		     end
		     (OzScanner, ConvertPseudoChars(Crr Col + 4 $))
		  else
		     C|(OzScanner, ConvertPseudoChars(Crr Col + 4 $))
		  end
	       else C2|C3|Crr = Cr C in   % must be an octal constant
		  C = Hex.C1 * 64 + Hex.C2 * 8 + Hex.C3
		  if C == 0 then
		     if {@Conditionals testCondition($)} then
			{self reportError(pos(@filename @line Col)
					  'lexical error'
				       'character in octal notation == 0')}
		     else skip
		     end
		     (OzScanner, ConvertPseudoChars(Crr Col + 4 $))
		  elseif C >= 256 then
		     if {@Conditionals testCondition($)} then
			{self reportError(pos(@filename @line Col)
					  'lexical error'
				      'character in octal notation >= 256')}
		     else skip
		     end
		     (C - 256)|(OzScanner, ConvertPseudoChars(Crr Col + 4 $))
		  else
		     C|(OzScanner, ConvertPseudoChars(Crr Col + 4 $))
		  end
	       end
	    end
	 elseof C|Cr then
	    C|(OzScanner, ConvertPseudoChars(Cr Col + 1 $))
	 else ""
	 end
      end

      %-----------------------------------------------------------------
      % Comments
      %-----------------------------------------------------------------

      lex <"%".*\n> line <- @line + 1 col <- 0 end

      lex <"/*">
	 CommentDepth <- 1
	 CommentCoord <- OzScanner, getCoordinates($)
	 CommentLastMode <- GS, currentMode($)
	 col <- @col + 2
	 OzScanner, setMode(COMMENT)
      end
      mode COMMENT
	 lex <"/*">
	    CommentDepth <- @CommentDepth + 1
	    col <- @col + 2
	 end
	 lex <"*/">
	    CommentDepth <- @CommentDepth - 1
	    if @CommentDepth == 0 then
	       OzScanner, setMode(@CommentLastMode)
	    else skip
	    end
	    col <- @col + 2
	 end
	 lex <[^*/\n]+> col <- @col + GS, getLength($) end
	 lex <\n> line <- @line + 1 col <- 0 end
	 lex <[*/]> col <- @col + 1 end
	 lex <<EOF>>
	    if {@Conditionals testCondition($)} then
	       {self reportError(@CommentCoord 'lexical error'
				 'unterminated comment')}
	    else skip
	    end
	    OzScanner, PopBuffer()
	 end
      end

      %-----------------------------------------------------------------
      % General definitions

      lex blank = <[ \r\t]> end
      lex filename = <[A-Za-z0-9/_~.-]+|\'[ -~]+\'> end

      lex space = <[?\t\n\v\f\r ]> end
      lex spacenonl = <[?\t\v\f\r ]> end
      lex lower = <[a-z\337-\366\370-\377]> end
      lex upper = <[A-Z\300-\326\330-\336]> end
      lex digit = <[0-9]> end
      lex nonzerodigit = <[1-9]> end
      lex alphaNum = <{lower}|{upper}|{digit}|_> end
      lex char = <[^\\\x00]> end
      lex variableChar = <[^`\\\x00]> end
      lex atomChar = <[^'\\\x00]> end
      lex stringChar = <[^\"\\\x00]> end
      lex escape = <[abfnrtv\\'\"`&]> end
      lex bin = <[0-1]> end
      lex oct = <[0-7]> end
      lex hex = <[0-9a-fA-F]> end
      lex pseudoChar = <\\({oct}{3}|[xX]{hex}{2}|{escape})> end
      lex anyChar = <{char}|{pseudoChar}> end

      lex atom = <{lower}{alphaNum}*> end
      lex atomQuoted = <"'"({atomChar}|{pseudoChar})*"'"> end
      lex int = <~?(0{oct}*|0[Xx]{hex}+|0[Bb]{bin}+|{nonzerodigit}{digit}*)> end

      %-----------------------------------------------------------------
      % Compiler Directives
      %-----------------------------------------------------------------
      
      lex <\\switch>
	 OzScanner, pt1('switch' GS, getLength($))
	 OzScanner, setMode(SWITCH)
      end
      mode SWITCH from DIRECTIVE
	 lex <"+">
	    OzScanner, pt1('+' 1)
	 end
	 lex <"-">
	    OzScanner, pt1('-' 1)
	 end
	 lex <{alphaNum}+>
	    OzScanner, pt('SWITCHNAME' GS, getAtom($) GS, getLength($))
	 end
      end

      lex <\\showSwitches>
	 OzScanner, pt1('showSwitches' GS, getLength($))
	 OzScanner, setMode(DIRECTIVE)
      end
      lex <\\pushSwitches>
	 OzScanner, pt1('pushSwitches' GS, getLength($))
	 OzScanner, setMode(DIRECTIVE)
      end
      lex <\\popSwitches>
	 OzScanner, pt1('popSwitches' GS, getLength($))
	 OzScanner, setMode(DIRECTIVE)
      end
      lex <\\localSwitches>
	 OzScanner, pt1('localSwitches' GS, getLength($))
	 OzScanner, setMode(DIRECTIVE)
      end	 
      
      lex <\\insert>
	 ErrorCoord <- OzScanner, getCoordinates($)
	 col <- @col + GS, getLength($)
	 OzScanner, setMode(INSERT)
      end
      mode INSERT from DIRECTIVEWITHARGS
	 lex <{filename}>
	    col <- @col + GS, getLength($)
	    if {@Conditionals testCondition($)} then Stripped FileName in
	       Stripped = {Strip &' GS, getString($)}
	       FileName = {ExpandFileName Stripped {Atom.toString @filename}}
	       case FileName of "" then C in
		  OzScanner, getCoordinates(?C)
		  {self reportError(C 'macro directive error'
				    'could not open file to insert')}
		  OzScanner, setMode(DIRECTIVE)
	       else
		  if @ShowInsertSwitch then
		     {System.showInfo '%%%     inserting file "'#FileName#'"'}
		  else skip
		  end
		  OzScanner, PushBuffer(FileName)
		  OzScanner, setMode(INITIAL)
	       end
	    else
	       OzScanner, setMode(DIRECTIVE)
	    end
	 end
      end

      lex <\\define>
	 ErrorCoord <- OzScanner, getCoordinates($)
	 col <- @col + GS, getLength($)
	 OzScanner, setMode(DEFINE)
      end
      mode DEFINE from DIRECTIVEWITHARGS
	 lex <{variable}>
	    col <- @col + GS, getLength($)
	    if {@Conditionals testCondition($)} then
	       {@Macros 'define'(GS, getAtom($))}
	    else skip
	    end
	    OzScanner, setMode(DIRECTIVE)
	 end
      end

      lex <\\undef>
	 ErrorCoord <- OzScanner, getCoordinates($)
	 col <- @col + GS, getLength($)
	 OzScanner, setMode(UNDEF)
      end
      mode UNDEF from DIRECTIVEWITHARGS
	 lex <{variable}>
	    col <- @col + GS, getLength($)
	    if {@Conditionals testCondition($)} then
	       {@Macros undefine(GS, getAtom($))}
	    else skip
	    end
	    OzScanner, setMode(DIRECTIVE)
	 end
      end

      lex <\\ifdef>
	 ErrorCoord <- OzScanner, getCoordinates($)
	 col <- @col + GS, getLength($)
	 OzScanner, setMode(IFDEF)
      end
      mode IFDEF from DIRECTIVEWITHARGS
	 lex <{variable}>
	    col <- @col + GS, getLength($)
	    {@Conditionals
	     pushCondition({@Macros isDefined(GS, getAtom($) $)})}
	    OzScanner, setMode(DIRECTIVE)
	 end
      end

      lex <\\ifndef>
	 ErrorCoord <- OzScanner, getCoordinates($)
	 col <- @col + GS, getLength($)
	 OzScanner, setMode(IFNDEF)
      end
      mode IFNDEF from DIRECTIVEWITHARGS
	 lex <{variable}>
	    col <- @col + GS, getLength($)
	    {@Conditionals
	     pushCondition({Not {@Macros isDefined(GS, getAtom($) $)}})}
	    OzScanner, setMode(DIRECTIVE)
	 end
      end

      lex <\\else>
	 col <- @col + GS, getLength($)
	 OzScanner, setMode(DIRECTIVE)
	 if {@Conditionals currentlyConditional($)} then
	    {@Conditionals negateCondition()}
	 else C in
	    OzScanner, getCoordinates(?C)
	    {self reportError(C 'macro directive error'
			      '\\else without previous corresponding '#
			      '\\ifdef or \\ifndef')}
	 end
      end

      lex <\\endif>
	 col <- @col + GS, getLength($)
	 OzScanner, setMode(DIRECTIVE)
	 if {@Conditionals currentlyConditional($)} then
	    {@Conditionals popCondition()}
	 else C in
	    OzScanner, getCoordinates(?C)
	    {self reportError(C 'macro directive error'
			      '\\endif without previous corresponding '#
			      '\\ifdef or \\ifndef')}
	 end
      end

      lex <\\line>
	 col <- @col + GS, getLength($)
	 if {@Conditionals testCondition($)} then
	    NewLineNumber <- ~1   % meaning: expecting line number first
	    LineErrorFlag <- false
	    ErrorFlag <- true
	    OzScanner, setMode(LINE)
	 else skip
	 end
      end
      mode LINE from DIRECTIVEWITHARGS
	 lex <[0-9]+>
	    col <- @col + GS, getLength($)
	    if @NewLineNumber == ~1 then
	       NewLineNumber <- {String.toInt GS, getString($)}
	    else
	       LineErrorFlag <- true
	       ErrorCoord <- OzScanner, getCoordinates($)
	    end
	 end
	 lex <{filename}>
	    col <- @col + GS, getLength($)
	    if @NewLineNumber == ~1 then
	       ErrorFlag <- true
	       ErrorCoord <- OzScanner, getCoordinates($)
	    else Stripped FileName in
	       line <- @NewLineNumber
	       Stripped = {Strip &' GS, getString($)}
	       FileName = {ExpandFileName Stripped {Atom.toString @filename}}
	       case FileName of "" then
		  filename <- {String.toAtom Stripped}
	       else
		  filename <- {String.toAtom FileName}
	       end
	       ErrorFlag <- @LineErrorFlag
	    end
	    OzScanner, setMode(DIRECTIVE)
	 end
      end

      %-----------------------------------------------------------------
      % Gump Directives

      lex <\\gumpscannerprefix>
	 col <- @col + GS, getLength($)
	 OzScanner, setMode(SCANNERPREFIX)
      end
      mode SCANNERPREFIX from DIRECTIVEWITHARGS
	 lex <{atom}>
	    OzScanner, SaveCoordinates()
	    col <- @col + GS, getLength($)
	    scannerPrefix <- {String.toAtom GS, getString($)}
	    OzScanner, setMode(DIRECTIVE)
	 end
	 lex <{atomQuoted}> S1 S2 in
	    OzScanner, SaveCoordinates()
	    GS, getString(?S1)
	    OzScanner, ConvertPseudoChars({Strip &' S1} @col ?S2)
	    OzScanner, UpdateCoordinates(S1)
	    scannerPrefix <- {String.toAtom S2}
	    OzScanner, setMode(DIRECTIVE)
	 end
      end

      lex<\\gumpparserexpect>
	 col <- @col + GS, getLength($)
	 OzScanner, setMode(PARSEREXPECT)
      end
      mode PARSEREXPECT from DIRECTIVEWITHARGS
	 lex <{int}>
	    OzScanner, SaveCoordinates()
	    col <- @col + GS, getLength($)
	    parserExpect <- {String.toInt GS, getString($)}
	    OzScanner, setMode(DIRECTIVE)
	 end
      end
	       
      mode DIRECTIVE from DIRECTIVE_FATHER
	 lex <<EOF>>
	    OzScanner, PopBuffer()
	 end
      end
      mode DIRECTIVEWITHARGS from DIRECTIVE_FATHER
	 lex <<EOF>>
	    {self reportError(OzScanner, getCoordinates($) 'directive error'
			      'unterminated directive')}
	    OzScanner, PopBuffer()
	 end
      end
      mode DIRECTIVE_FATHER
	 % common layout rules valid for all directives
	 lex <"%".*\n> 
	    line <- @line + 1 col <- 0
	    if @ErrorFlag then
	       {self reportError(@ErrorCoord 'directive error'
				 'illegal directive syntax')}
	       ErrorFlag <- false
	    else skip
	    end
	    OzScanner, setMode(INITIAL)
	 end

      lex <"/*">
	    CommentDepth <- 1
	    CommentCoord <- OzScanner, getCoordinates($)
	    CommentLastMode <- GS, currentMode($)
	    col <- @col + 2
	    OzScanner, setMode(COMMENT)
	 end

	 lex <{blank}+> col <- @col + GS, getLength($) end
	 lex <.>
	    OzScanner, UpdateCoordinates(GS, getString($))
	    if @ErrorFlag then skip
	    else
	       ErrorFlag <- true
	       ErrorCoord <- OzScanner, getCoordinates($)
	    end
	 end
	 lex <\n>
	    line <- @line + 1 col <- 0
	    if @ErrorFlag then
	       {self reportError(@ErrorCoord 'directive error'
				 'illegal directive syntax')}
	       ErrorFlag <- false
	    else skip
	    end
	    OzScanner, setMode(INITIAL)
	 end
      end
      
      %--------------------------------------------------------------------
      % Gump Extensions
      %--------------------------------------------------------------------

      lex regexChar = <"["([^\]\\]|\\.)+"]"|\"[^"]+\"|\\.|[^<>"\[\]\\\n]> end

      lex <"=>"> OzScanner, pt1('=>' 2) end
      lex <"//"> OzScanner, pt1('//' 2) end

      lex <lex>
	 if @GumpSyntax then
	    OzScanner, pt1('lex' 3)
	    OzScanner, setMode(LEX)
	 else
	    OzScanner, SaveCoordinates()
	    OzScanner, pt('ATOM' 'lex' 3)
	 end
      end
      lex <mode>
	 if @GumpSyntax then
	    OzScanner, pt1('mode' 4)
	 else
	    OzScanner, SaveCoordinates()
	    OzScanner, pt('ATOM' 'mode' 4)
	 end
      end
      lex <parser>
	 if @GumpSyntax then C in
	    OzScanner, getCoordinates(?C)
	    OzScanner, pt('parser' C 6)
	 else
	    OzScanner, SaveCoordinates()
	    OzScanner, pt('ATOM' 'parser' 6)
	 end
      end
      lex <prod>
	 if @GumpSyntax then
	    OzScanner, pt1('prod' 4)
	 else
	    OzScanner, SaveCoordinates()
	    OzScanner, pt('ATOM' 'prod' 4)
	 end
      end
      lex <scanner>
	 if @GumpSyntax then C in
	    OzScanner, getCoordinates(?C)
	    OzScanner, pt('scanner' C 7)
	 else
	    OzScanner, SaveCoordinates()
	    OzScanner, pt('ATOM' 'scanner' 7)
	 end
      end
      lex <syn>
	 if @GumpSyntax then
	    OzScanner, pt1('syn' 3)
	 else
	    OzScanner, SaveCoordinates()
	    OzScanner, pt('ATOM' 'syn' 3)
	 end
      end
      lex <token>
	 if @GumpSyntax then
	    OzScanner, pt1('token' 5)
	 else
	    OzScanner, SaveCoordinates()
	    OzScanner, pt('ATOM' 'token' 5)
	 end
      end

      lex <lex/\(>
	 if @GumpSyntax then
	    OzScanner, pt1('lex' 3)
	    OzScanner, setMode(LEX)
	 else
	    OzScanner, SaveCoordinates()
	    OzScanner, pt('ATOM()' 'lex' 3)
	 end
      end
      lex <mode/\(>
	 if @GumpSyntax then
	    OzScanner, pt1('mode' 4)
	 else
	    OzScanner, SaveCoordinates()
	    OzScanner, pt('ATOM()' 'mode' 4)
	 end
      end
      lex <parser/\(>
	 if @GumpSyntax then C in
	    OzScanner, getCoordinates(?C)
	    OzScanner, pt('parser' C 6)
	 else
	    OzScanner, SaveCoordinates()
	    OzScanner, pt('ATOM()' 'parser' 6)
	 end
      end
      lex <prod/\(>
	 if @GumpSyntax then
	    OzScanner, pt1('prod' 4)
	 else
	    OzScanner, SaveCoordinates()
	    OzScanner, pt('ATOM()' 'prod' 4)
	 end
      end
      lex <scanner/\(>
	 if @GumpSyntax then C in
	    OzScanner, getCoordinates(?C)
	    OzScanner, pt('scanner' C 7)
	 else
	    OzScanner, SaveCoordinates()
	    OzScanner, pt('ATOM()' 'scanner' 7)
	 end
      end
      lex <syn/\(>
	 if @GumpSyntax then
	    OzScanner, pt1('syn' 3)
	 else
	    OzScanner, SaveCoordinates()
	    OzScanner, pt('ATOM()' 'syn' 3)
	 end
      end
      lex <token/\(>
	 if @GumpSyntax then
	    OzScanner, pt1('token' 5)
	 else
	    OzScanner, SaveCoordinates()
	    OzScanner, pt('ATOM()' 'token' 5)
	 end
      end

      mode LEX from INITIAL
	 lex <"<<EOF>>">
	    OzScanner, pt('REGEX' GS, getString($) 7)
	    OzScanner, setMode(INITIAL)
	 end
	 lex <"<"{regexChar}+">"> S in  
	    GS, getString(?S)
	    OzScanner, setParseErrorCoords()
	    OzScanner, UpdateCoordinates(S)
	    OzScanner, pt('REGEX' {ButLast S.2} none)
	    OzScanner, setMode(INITIAL)
	 end
      end

      %-----------------------------------------------------------------
      % The Oz Notation
      %-----------------------------------------------------------------

      %-----------------------------------------------------------------
      % Keywords

      lex <else>        OzScanner, pt1('else' 4)     end
      lex <elseof>      OzScanner, pt1('elseof' 6)   end
      lex <finally>     OzScanner, pt1('finally' 7)  end
      lex <in>          OzScanner, pt1('in' 2)       end
      lex <of>          OzScanner, pt1('of' 2)       end
      lex <then>        OzScanner, pt1('then' 4)     end

      lex <else/\(>     OzScanner, pt1('else' 4)     end
      lex <elseof/\(>   OzScanner, pt1('elseof' 6)   end
      lex <finally/\(>  OzScanner, pt1('finally' 7)  end
      lex <in/\(>       OzScanner, pt1('in' 2)       end
      lex <of/\(>       OzScanner, pt1('of' 2)       end
      lex <then/\(>     OzScanner, pt1('then' 4)     end

      lex <andthen>     C in OzScanner, getCoordinates(?C) OzScanner, pt('andthen' C 7)  end
      lex <at>          C in OzScanner, getCoordinates(?C) OzScanner, pt('at' C 2)       end
      lex <attr>        C in OzScanner, getCoordinates(?C) OzScanner, pt('attr' C 4)     end
      lex <case>        C in OzScanner, getCoordinates(?C) OzScanner, pt('case' C 4)     end
      lex <catch>       C in OzScanner, getCoordinates(?C) OzScanner, pt('catch' C 5)    end
      lex <choice>      C in OzScanner, getCoordinates(?C) OzScanner, pt('choice' C 6)   end
      lex <class>       C in OzScanner, getCoordinates(?C) OzScanner, pt('class' C 5)    end
      lex <cond>        C in OzScanner, getCoordinates(?C) OzScanner, pt('cond' C 4)     end
      lex <declare>     C in OzScanner, getCoordinates(?C) OzScanner, pt('declare' C 7)  end
      lex <define>      C in OzScanner, getCoordinates(?C) OzScanner, pt('define' C 6)   end
      lex <dis>         C in OzScanner, getCoordinates(?C) OzScanner, pt('dis' C 3)      end
      lex <elsecase>    C in OzScanner, getCoordinates(?C) OzScanner, pt('elsecase' C 8) end
      lex <elseif>      C in OzScanner, getCoordinates(?C) OzScanner, pt('elseif' C 6)   end
      lex <end>         C in OzScanner, getCoordinates(?C) OzScanner, pt('end' C 3)      end
      lex <export>      C in OzScanner, getCoordinates(?C) OzScanner, pt('export' C 6)   end
      lex <fail>        C in OzScanner, getCoordinates(?C) OzScanner, pt('fail' C 4)     end
      lex <false>       C in OzScanner, getCoordinates(?C) OzScanner, pt('false' C 5)    end
      lex <feat>        C in OzScanner, getCoordinates(?C) OzScanner, pt('feat' C 4)     end
      lex <from>        C in OzScanner, getCoordinates(?C) OzScanner, pt('from' C 4)     end
      lex <fun>         C in OzScanner, getCoordinates(?C) OzScanner, pt('fun' C 3)      end
      lex <functor>     C in OzScanner, getCoordinates(?C) OzScanner, pt('functor' C 7)  end
      lex <if>          C in OzScanner, getCoordinates(?C) OzScanner, pt('if' C 2)       end
      lex <import>      C in OzScanner, getCoordinates(?C) OzScanner, pt('import' C 6)   end
      lex <local>       C in OzScanner, getCoordinates(?C) OzScanner, pt('local' C 5)    end
      lex <lock>        C in OzScanner, getCoordinates(?C) OzScanner, pt('lock' C 4)     end
      lex <meth>        C in OzScanner, getCoordinates(?C) OzScanner, pt('meth' C 4)     end
      lex <not>         C in OzScanner, getCoordinates(?C) OzScanner, pt('not' C 3)      end
      lex <or>          C in OzScanner, getCoordinates(?C) OzScanner, pt('or' C 2)       end
      lex <orelse>      C in OzScanner, getCoordinates(?C) OzScanner, pt('orelse' C 6)   end
      lex <prepare>     C in OzScanner, getCoordinates(?C) OzScanner, pt('prepare' C 7)  end
      lex <proc>        C in OzScanner, getCoordinates(?C) OzScanner, pt('proc' C 4)     end
      lex <prop>        C in OzScanner, getCoordinates(?C) OzScanner, pt('prop' C 4)     end
      lex <require>     C in OzScanner, getCoordinates(?C) OzScanner, pt('require' C 7)  end
      lex <raise>       C in OzScanner, getCoordinates(?C) OzScanner, pt('raise' C 5)    end
      lex <self>        C in OzScanner, getCoordinates(?C) OzScanner, pt('self' C 4)     end
      lex <skip>        C in OzScanner, getCoordinates(?C) OzScanner, pt('skip' C 4)     end
      lex <thread>      C in OzScanner, getCoordinates(?C) OzScanner, pt('thread' C 6)   end
      lex <true>        C in OzScanner, getCoordinates(?C) OzScanner, pt('true' C 4)     end
      lex <try>         C in OzScanner, getCoordinates(?C) OzScanner, pt('try' C 3)      end
      lex <unit>        C in OzScanner, getCoordinates(?C) OzScanner, pt('unit' C 4)     end

      lex <andthen/\(>  C in OzScanner, getCoordinates(?C) OzScanner, pt('andthen' C 7)  end
      lex <at/\(>       C in OzScanner, getCoordinates(?C) OzScanner, pt('at' C 2)       end
      lex <attr/\(>     C in OzScanner, getCoordinates(?C) OzScanner, pt('attr' C 4)     end
      lex <catch/\(>    C in OzScanner, getCoordinates(?C) OzScanner, pt('catch' C 5)    end
      lex <case/\(>     C in OzScanner, getCoordinates(?C) OzScanner, pt('case' C 4)     end
      lex <choice/\(>   C in OzScanner, getCoordinates(?C) OzScanner, pt('choice' C 6)   end
      lex <class/\(>    C in OzScanner, getCoordinates(?C) OzScanner, pt('class' C 5)    end
      lex <cond/\(>     C in OzScanner, getCoordinates(?C) OzScanner, pt('cond' C 4)     end
      lex <declare/\(>  C in OzScanner, getCoordinates(?C) OzScanner, pt('declare' C 7)  end
      lex <define/\(>   C in OzScanner, getCoordinates(?C) OzScanner, pt('define' C 6)   end
      lex <dis/\(>      C in OzScanner, getCoordinates(?C) OzScanner, pt('dis' C 3)      end
      lex <elsecase/\(> C in OzScanner, getCoordinates(?C) OzScanner, pt('elsecase' C 8) end
      lex <elseif/\(>   C in OzScanner, getCoordinates(?C) OzScanner, pt('elseif' C 6)   end
      lex <end/\(>      C in OzScanner, getCoordinates(?C) OzScanner, pt('end' C 3)      end
      lex <export/\(>   C in OzScanner, getCoordinates(?C) OzScanner, pt('export' C 6)   end
      lex <fail/\(>     C in OzScanner, getCoordinates(?C) OzScanner, pt('fail' C 4)     end
      lex <false/\(>    C in OzScanner, getCoordinates(?C) OzScanner, pt('false()' C 5)  end
      lex <feat/\(>     C in OzScanner, getCoordinates(?C) OzScanner, pt('feat' C 4)     end
      lex <from/\(>     C in OzScanner, getCoordinates(?C) OzScanner, pt('from' C 4)     end
      lex <fun/\(>      C in OzScanner, getCoordinates(?C) OzScanner, pt('fun' C 3)      end
      lex <functor/\(>  C in OzScanner, getCoordinates(?C) OzScanner, pt('functor' C 7)  end
      lex <if/\(>       C in OzScanner, getCoordinates(?C) OzScanner, pt('if' C 2)       end
      lex <import/\(>   C in OzScanner, getCoordinates(?C) OzScanner, pt('import' C 6)   end
      lex <local/\(>    C in OzScanner, getCoordinates(?C) OzScanner, pt('local' C 5)    end
      lex <lock/\(>     C in OzScanner, getCoordinates(?C) OzScanner, pt('lock' C 4)     end
      lex <meth/\(>     C in OzScanner, getCoordinates(?C) OzScanner, pt('meth' C 4)     end
      lex <not/\(>      C in OzScanner, getCoordinates(?C) OzScanner, pt('not' C 3)      end
      lex <or/\(>       C in OzScanner, getCoordinates(?C) OzScanner, pt('or' C 2)       end
      lex <orelse/\(>   C in OzScanner, getCoordinates(?C) OzScanner, pt('orelse' C 6)   end
      lex <prepare/\(>  C in OzScanner, getCoordinates(?C) OzScanner, pt('prepare' C 7)  end
      lex <proc/\(>     C in OzScanner, getCoordinates(?C) OzScanner, pt('proc' C 4)     end
      lex <prop/\(>     C in OzScanner, getCoordinates(?C) OzScanner, pt('prop' C 4)     end
      lex <require/\(>  C in OzScanner, getCoordinates(?C) OzScanner, pt('require' C 7)  end
      lex <raise/\(>    C in OzScanner, getCoordinates(?C) OzScanner, pt('raise' C 5)    end
      lex <self/\(>     C in OzScanner, getCoordinates(?C) OzScanner, pt('self' C 4)     end
      lex <skip/\(>     C in OzScanner, getCoordinates(?C) OzScanner, pt('skip' C 4)     end
      lex <thread/\(>   C in OzScanner, getCoordinates(?C) OzScanner, pt('thread' C 6)   end
      lex <true/\(>     C in OzScanner, getCoordinates(?C) OzScanner, pt('true()' C 4)   end
      lex <try/\(>      C in OzScanner, getCoordinates(?C) OzScanner, pt('try' C 3)      end
      lex <unit/\(>     C in OzScanner, getCoordinates(?C) OzScanner, pt('unit()' C 4)   end

      lex add = <"+"|"-"> end
      lex <{add}>
	 OzScanner, SaveCoordinates()
	 OzScanner, pt('Add' GS, getAtom($) 1)
      end

      lex fdmul = <"*"|"/"> end
      lex <{fdmul}>
	 OzScanner, SaveCoordinates()
	 OzScanner, pt('FdMul' GS, getAtom($) 1)
      end

      lex othermul = <div|mod> end
      lex <{othermul}>
	 OzScanner, SaveCoordinates()
	 OzScanner, pt('OtherMul' GS, getAtom($) 3)
      end
      lex <{othermul}/\(>
	 OzScanner, SaveCoordinates()
	 OzScanner, pt('OtherMul' GS, getAtom($) 3)
      end

      lex compare = <"<"|">"|"=<"|">="|"\\="> end
      lex <"=="|{compare}>
	 OzScanner, SaveCoordinates()
	 OzScanner, pt('Compare' GS, getAtom($) GS, getLength($))
      end

      lex fdin = <"::"|":::"> end
      lex <{fdin}>
	 OzScanner, SaveCoordinates()
	 OzScanner, pt('FdIn' GS, getAtom($) GS, getLength($))
      end

      lex <(=|{compare}):>
	 OzScanner, SaveCoordinates()
	 OzScanner, pt('FdCompare' GS, getAtom($) GS, getLength($))
      end

      lex <"..."> OzScanner, pt1('...' 3) end
      lex <"[]">  OzScanner, pt1('[]' 2)  end
      lex <"!!">  OzScanner, pt1('!!' 2)  end

      lex <"<-"> C in
	 OzScanner, getCoordinates(?C)
	 OzScanner, pt('<-' C 2)
      end
      lex <"<="> C in
	 OzScanner, getCoordinates(?C)
	 OzScanner, pt('<=' C 2)
      end

      lex <"{"|"["|"|"|"#"|"="|"."|"^"|"@"|"$"|"!"|"~"|"_"|","> C in
	 OzScanner, getCoordinates(?C)
	 OzScanner, pt(GS, getAtom($) C 1)
      end
      lex <"}"|"("|")"|"]"|":"> C in
	 OzScanner, getCoordinates(?C)
	 OzScanner, pt(GS, getAtom($) C 1)
      end

      %-----------------------------------------------------------------
      % Variables, atoms, labels, strings, numbers

      lex variable = <{upper}{alphaNum}*> end
      lex variableQuoted = <`({variableChar}|{pseudoChar})*`> end
      lex <{variable}>
	 OzScanner, SaveCoordinates()
	 OzScanner, pt('VARIABLE' GS, getAtom($) GS, getLength($))
      end
      lex <{variableQuoted}> S1 S2 in 
	 OzScanner, SaveCoordinates()
	 GS, getString(?S1)
	 OzScanner, ConvertPseudoChars(S1 @col ?S2)
	 OzScanner, setParseErrorCoords()
	 OzScanner, UpdateCoordinates(S1)
	 OzScanner, pt('VARIABLE' {String.toAtom S2} none)
      end
      lex <{variable}/\(>
	 OzScanner, SaveCoordinates()
	 col <- @col + GS, getLength($)
	 OzScanner, pt('VARIABLE()' GS, getAtom($) GS, getLength($))
      end
      lex <{variableQuoted}/\(> S1 S2 in
	 OzScanner, SaveCoordinates()
	 GS, getString(?S1)
	 OzScanner, ConvertPseudoChars(S1 @col ?S2)
	 OzScanner, setParseErrorCoords()
	 OzScanner, UpdateCoordinates(S1)
	 OzScanner, pt('VARIABLE()' {String.toAtom S2} GS, getLength($))
      end

      lex <{atom}>
	 OzScanner, SaveCoordinates()
	 OzScanner, pt('ATOM' GS, getAtom($) GS, getLength($))
      end
      lex <{atomQuoted}> S1 S2 in 
	 OzScanner, SaveCoordinates()
	 GS, getString(?S1)
	 OzScanner, ConvertPseudoChars({Strip &' S1} @col ?S2)
	 OzScanner, setParseErrorCoords()
	 OzScanner, UpdateCoordinates(S1)
	 OzScanner, pt('ATOM' {String.toAtom S2} none)
      end
      lex <{atom}/\(>
	 OzScanner, SaveCoordinates()
	 OzScanner, pt('ATOM()' GS, getAtom($) GS, getLength($))
      end
      lex <{atomQuoted}/\(> S1 S2 in
	 OzScanner, SaveCoordinates()
	 GS, getString(?S1)
	 OzScanner, ConvertPseudoChars({Strip &' S1} @col ?S2)
	 OzScanner, setParseErrorCoords()
	 OzScanner, UpdateCoordinates(S1)
	 OzScanner, pt('ATOM()' {String.toAtom S2} none)
      end

      lex string = <\"({stringChar}|{pseudoChar})*\"> end
      lex <{string}> S1 S2 in 
	 OzScanner, SaveCoordinates()
	 GS, getString(?S1)
	 OzScanner, ConvertPseudoChars({Strip &" S1} @col ?S2)
	 OzScanner, setParseErrorCoords()
	 OzScanner, UpdateCoordinates(S1)
	 OzScanner, pt('STRING' S2 none)
      end

      lex <{int}>
	 OzScanner, SaveCoordinates()
	 OzScanner, pt('INT' {String.toInt GS, getString($)}
			     GS, getLength($))
      end
      lex <"."{space}*{digit}+> S C in
	 OzScanner, getCoordinates(C)
	 OzScanner, SaveCoordinates()
	 S = GS, getString($)
	 % This special case rule ensures that "X.1.1" is tokenized as
	 %   "X" "." "1" "." "1"
	 % and not as
	 %   "X" "." "1.1".
	 % Caveat: comments are not allowed between `.' and number.
	 OzScanner, pt('.' C 1)
	 OzScanner, pt('INT' {String.toInt
				    {List.dropWhile S
				     fun {$ C}
					{Not {Char.isDigit C}}
				     end}}
			     GS, getLength($) -1)
      end
      lex <{digit}+/\.\.\.>
	 OzScanner, SaveCoordinates()
	 % This special case rule ensures that "1..." is tokenized as
	 %   "1" "..."
	 % and not as
	 %   "1." "." "."
	 OzScanner, pt('INT' {String.toInt GS, getString($)}
			     GS, getLength($))
      end

      lex float = <~?{digit}+"."{digit}*([eE]~?{digit}+)?> end
      lex <{float}> F in
	 OzScanner, SaveCoordinates()
	 F = {String.toFloat GS, getString($)}
	 OzScanner, pt('FLOAT' F GS, getLength($))
      end

      lex <"&"{anyChar}> S1 S2 in
	 OzScanner, SaveCoordinates()
	 S1 = GS, getString($)
	 OzScanner, ConvertPseudoChars(S1.2 @col ?S2)
	 OzScanner, pt('INT' S2.1  GS, getLength($))
      end

      lex <{spacenonl}+> col <- @col + GS, getLength($) end
      lex <\n+> line <- @line + GS, getLength($) col <- 0 end

      lex <\'[^']+\'>
	 if {@Conditionals testCondition($)} then
	    {self reportError(OzScanner, getCoordinates($) 'lexical error'
			      'illegal quoted atom syntax')}
	 else skip
	 end
	 OzScanner, UpdateCoordinates(GS, getString($))
	 OzScanner, setParseErrorCoords()
	 OzScanner, pt1('error' none)
      end
      lex <`[^`]*`>
	 if {@Conditionals testCondition($)} then
	    {self reportError(OzScanner, getCoordinates($) 'lexical error'
			      'illegal backquote variable syntax')}
	 else skip
	 end
	 OzScanner, UpdateCoordinates(GS, getString($))
	 OzScanner, setParseErrorCoords()
	 OzScanner, pt1('error' none)
      end
      lex <\"[^"]+\">
	 if {@Conditionals testCondition($)} then
	    {self reportError(OzScanner, getCoordinates($) 'lexical error'
			      'illegal string syntax')}
	 else skip
	 end
	 OzScanner, UpdateCoordinates(GS, getString($))
	 OzScanner, setParseErrorCoords()
	 OzScanner, pt1('error' none)
      end

      lex <.>
	 if {@Conditionals testCondition($)} then
	    {self reportError(OzScanner, getCoordinates($) 'lexical error'
			      'illegal (pseudo-)character')}
	 else skip
	 end
	 OzScanner, UpdateCoordinates(GS , getString($))
      end

      lex <<EOF>>
	 OzScanner, PopBuffer()
      end
   end
end
