(* scanner.sml
 *
 * Sample code for CMSC 32102 Project-1
 *)

structure Scanner : sig

    datatype token
      = Path of string
      | Pipe			(* '|' *)
      | Lt			(* '<' *)
      | Gt			(* '>' *)
      | Amp			(* '&' *)

  (* raise this exception when the string contains a bad character *)
    exception BadChar of char

  (* given a string, scan it and return the list of tokens that it represents *)
    val scanLine : string -> token list

  (* return the string representation of a token/list of tokens *)
    val tokToString : token -> string
    val toksToString : token list -> string

  end = struct

    structure SS = Substring

    datatype token
      = Path of string
      | Pipe			(* '|' *)
      | Lt			(* '<' *)
      | Gt			(* '>' *)
      | Amp			(* '&' *)

  (* raise this exception when the string contains a bad character *)
    exception BadChar of char

  (* given a string, scan it and return the list of tokens that it represents *)
    fun scanLine (s : string) = let
	(* return true if c is a valid path character *)
	  val isPathChr = Char.contains "\
                  \ABCDEFGHIJKLMNOPQRSTUVWXYZ\
                  \abcdefghijklmnopqrstuvwxyz\
                  \0123456789\
                  \.-+=@_/\
		\"
	(* utility function to skip space characters in a substring *)
	  fun skipWS ss = (case SS.getc ss
		 of SOME(c, ss') => if Char.isSpace c
		      then skipWS ss'
		      else ss
		  | NONE => ss
		(* end case *))
	(* scan the next token *)
	  fun scan (ss, toks) = let
		val ss = skipWS ss
		in
		  case SS.getc ss
		   of NONE => List.rev toks
		    | SOME(#"|", ss') => scan (ss', Pipe::toks)
		    | SOME(#"<", ss') => scan (ss', Lt::toks)
		    | SOME(#">", ss') => scan (ss', Gt::toks)
		    | SOME(#"&", ss') => scan (ss', Amp::toks)
		    | SOME(c, ss') => if isPathChr c
			then let
			  fun getPath len = SS.string(SS.slice(ss, 0, SOME len))
			  fun scanPath (i, ss) = (case SS.getc ss
				 of NONE => (getPath i, ss)
				  | SOME(c, ss') => if isPathChr c
				      then scanPath(i+1, ss')
				      else (getPath i, ss)
				(* end case *))
			  val (path, ss'') = scanPath(1, ss')
			  in
			    scan (ss'', Path path::toks)
			  end
		       else raise BadChar c
		  (* end case *)
		end
	  in
	    scan (SS.full s, [])
	  end

  (* return the string representation of a token/list of tokens *)
    fun tokToString (Path s) = s
      | tokToString Pipe = "|"
      | tokToString Lt = "<"
      | tokToString Gt = ">"
      | tokToString Amp = "&"

    fun toksToString l = let
	  fun tos [] = []
	    | tos [tok] = [tokToString tok]
	    | tos (tok::r) = tokToString tok :: " " :: tos r
	  in
	    String.concat(tos l)
	  end


  end
