[/] [trunk/] [src/] [urpal.sml] - Rev 65

(* $Id: urpal.sml 65 2008-08-25 22:47:20Z tbourke $
 *
 * Copyright (c) 2008 Timothy Bourke (University of NSW and NICTA)
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the "BSD License" which is distributed with the
 * software in the file LICENSE.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the BSD
 * License for more details.
 *)

structure Urpal :
sig
  val main : string * string list -> OS.Process.status
  val run: string -> OS.Process.status
end
=
let structure UP  = UppaalParse
          and UXML = UppaalXML
          and TIO  = TextIO
          and CMD  = Commands
in
  struct
  exception FailedCommand
        and NoInputFile

  fun abort ss = let in
      TIO.output (TIO.stdErr, String.concat (Settings.progName::":"::ss));
      TIO.output (TIO.stdErr, "\n");
      raise FailedCommand
    end

  fun readNta filename = let
      val uri = Uri.String2Uri filename
    in case UXML.parse uri
       of NONE      => abort [filename,
                          " is not a valid XML file or the dtd is invalid."]
        | SOME xnta => UP.parse (xnta, filename)
    end

  fun writeNta (filename, nta) = let
      fun makeStream (s, c) = (TIO.getOutstream s, c)
      val (strm, close) = makeStream (case filename of
                             NONE   => (TIO.stdOut, fn _=>())
                           | SOME n => (TIO.openOut n, TIO.StreamIO.closeOut))
    in ParsedNta.output strm nta; close strm end
    handle IO.Io {cause,...} => (Util.warn ["failed to write nta (",
                                            General.exnMessage cause, ")"])
                                before (raise FailedCommand)

  fun tryConfigFiles () = let
      fun tryDir (NONE, _, _) = NONE
        | tryDir (SOME d, subs, f) = let
              val dir=foldl (fn(f,d)=>OS.Path.joinDirFile{dir=d,file=f}) d subs
            in SOME (OS.Path.joinDirFile {dir=dir, file=f}) end

      fun loadFile NONE = ()
        | loadFile (SOME path) = let
          val ()= Util.debugOutline (fn()=>["seeking config file ",path,"..."])

          val inso = SOME (TextIO.getInstream (TextIO.openIn path))
                     before Util.debugOutline (fn()=>["found"])
                     handle IO.Io {cause, ...} => NONE before
                       Util.debugOutline (fn()=>["not found (",
                                                 General.exnMessage cause,")"])
        in
          ignore (Option.map
                    (SettingsRW.loadConfigFile TextIO.StreamIO.inputLine) inso)
        end

    in
      app loadFile [tryDir (Settings.prefix (), ["etc"], "urpalrc"),
                    tryDir (OS.Process.getEnv "HOME", [], ".urpalrc"),
                    SOME "urpalrc"]
    end

  fun testFlip (NONE, _) = abort ["no input file specified"]
    | testFlip (SOME inf,NONE) = TestTransFlip.runTest {input=inf,
                                                        output=TextIO.stdOut}
    | testFlip (SOME inf,SOME outf) = (let
           val ostrm = TextIO.openOut outf
         in
           TestTransFlip.runTest {input=inf, output=ostrm};
           TextIO.closeOut ostrm;
           OS.Process.success
         end
         handle IO.Io {cause, ...} => (Util.warn
           ["cannot write to '", outf, "': ",
            General.exnMessage cause]; OS.Process.failure))

  fun main (name, args) = let
      val _ = Util.debugIndent (Settings.Detailed,fn()=>["try config files..."])
      val _ = tryConfigFiles ()
      val _ = Util.debugOutdent (Settings.Detailed, fn()=>["done."])
      val (cmds, {inputfile, outputfile}) = CMD.processCommands args
      val _ = Settings.validate ()
      val freshEnv = SOME (CmdEnv.fromNta ParsedNta.emptyNta)

      fun doCmd (a as CMD.ScriptFile _, NONE)       = doCmd (a, freshEnv)
        | doCmd (CMD.ScriptFile filename, SOME env) = let
              val ins = TextIO.openIn filename
            in
              CmdLang.parse TextIO.StreamIO.inputLine
                            (env, TextIO.getInstream ins)
            end

        | doCmd (a as CMD.ScriptText _, NONE)       = doCmd (a, freshEnv)
        | doCmd (CMD.ScriptText script, SOME env)   =
              CmdLang.parse (fn""=>NONE|s=>SOME(s,"")) (env, script)

        | doCmd (a as CMD.ScriptTerminal, NONE)     = doCmd (a, freshEnv)
        | doCmd (CMD.ScriptTerminal, SOME env)      = let
              fun getLine ins = (TextIO.print "> ";
                                 TextIO.StreamIO.inputLine ins)
            in
              CmdLang.parse getLine (env, TextIO.getInstream TextIO.stdIn)
            end

        | doCmd (CMD.ConfigFile p, envo) = (let
              val ins = TextIO.getInstream (TextIO.openIn p)
            in SettingsRW.loadConfigFile TextIO.StreamIO.inputLine ins;
               TextIO.StreamIO.closeIn ins;
               envo
            end
            handle IO.Io {cause, ...} => (Util.warn ["reading ",p," failed (",
                                            General.exnMessage cause, ")"];
                                          raise FailedCommand))

        | doCmd (CMD.ConfigText t, envo) = envo before
                SettingsRW.loadConfigFile (fn""=>NONE|s=>SOME(s,"")) t

        | doCmd (CMD.ShowConfig, envo)   = envo before
                                           Settings.saveConfigFile TextIO.stdOut
        | doCmd (CMD.TestFlip, _) = (Util.warn
                      ["The test flip command must be used by itself."];
                      raise FailedCommand)

    in
      case cmds of
        [CMD.TestFlip] => testFlip (inputfile, outputfile)

      | _ => let
               val ntao      = Option.mapPartial readNta inputfile
               val inputEnvo = Option.map CmdEnv.fromNta ntao
               val outputfile= if isSome outputfile then outputfile
                                                    else inputfile
             in
               case foldl doCmd inputEnvo cmds of
                 NONE     => OS.Process.success
               | SOME env => (writeNta (outputfile, CmdEnv.toNta env);
                                        OS.Process.success)
             end
    end
    handle FailedCommand => OS.Process.failure
         | CmdLang.Failure => OS.Process.failure
         | e => Util.abort ["uncaught exception:", General.exnMessage e]
    
    fun run args = main (Settings.progName, String.tokens Char.isSpace args)

  end
end