src/Pure/System/options.scala
author wenzelm
Sat, 27 Jul 2013 16:59:25 +0200
changeset 53872 842b5e7dcac8
parent 53202 78f2475aa126
child 53874 7b396ef36af6
permissions -rw-r--r--
support isabelle options -g;
     1 /*  Title:      Pure/System/options.scala
     2     Author:     Makarius
     3 
     4 System options with external string representation.
     5 */
     6 
     7 package isabelle
     8 
     9 
    10 import java.util.Calendar
    11 
    12 
    13 object Options
    14 {
    15   type Spec = (String, Option[String])
    16 
    17   val empty: Options = new Options()
    18 
    19 
    20   /* representation */
    21 
    22   sealed abstract class Type
    23   {
    24     def print: String = Library.lowercase(toString)
    25   }
    26   case object Bool extends Type
    27   case object Int extends Type
    28   case object Real extends Type
    29   case object String extends Type
    30   case object Unknown extends Type
    31 
    32   case class Opt(public: Boolean, name: String, typ: Type, value: String, default_value: String,
    33     description: String, section: String)
    34   {
    35     private def print(default: Boolean): String =
    36     {
    37       val x = if (default) default_value else value
    38       "option " + name + " : " + typ.print + " = " +
    39         (if (typ == Options.String) quote(x) else x) +
    40         (if (description == "") "" else "\n  -- " + quote(description))
    41     }
    42 
    43     def print: String = print(false)
    44     def print_default: String = print(true)
    45 
    46     def title(strip: String): String =
    47     {
    48       val words = space_explode('_', name)
    49       val words1 =
    50         words match {
    51           case word :: rest if word == strip => rest
    52           case _ => words
    53         }
    54       words1.map(Library.capitalize).mkString(" ")
    55     }
    56 
    57     def unknown: Boolean = typ == Unknown
    58   }
    59 
    60 
    61   /* parsing */
    62 
    63   private val SECTION = "section"
    64   private val PUBLIC = "public"
    65   private val OPTION = "option"
    66   private val OPTIONS = Path.explode("etc/options")
    67   private val PREFS_DIR = Path.explode("$ISABELLE_HOME_USER/etc")
    68   private val PREFS = PREFS_DIR + Path.basic("preferences")
    69   private val PREFS_BACKUP = PREFS_DIR + Path.basic("preferences~")
    70 
    71   lazy val options_syntax =
    72     Outer_Syntax.init() + ":" + "=" + "--" +
    73       (SECTION, Keyword.THY_HEADING2) + (PUBLIC, Keyword.THY_DECL) + (OPTION, Keyword.THY_DECL)
    74 
    75   lazy val prefs_syntax = Outer_Syntax.init() + "="
    76 
    77   object Parser extends Parse.Parser
    78   {
    79     val option_name = atom("option name", _.is_xname)
    80     val option_type = atom("option type", _.is_ident)
    81     val option_value =
    82       opt(token("-", tok => tok.is_sym_ident && tok.content == "-")) ~ atom("nat", _.is_nat) ^^
    83         { case s ~ n => if (s.isDefined) "-" + n else n } |
    84       atom("option value", tok => tok.is_name || tok.is_float)
    85 
    86     val option_entry: Parser[Options => Options] =
    87     {
    88       command(SECTION) ~! text ^^
    89         { case _ ~ a => (options: Options) => options.set_section(a) } |
    90       opt(command(PUBLIC)) ~ command(OPTION) ~! (option_name ~ keyword(":") ~ option_type ~
    91       keyword("=") ~ option_value ~ (keyword("--") ~! text ^^ { case _ ~ x => x } | success(""))) ^^
    92         { case a ~ _ ~ (b ~ _ ~ c ~ _ ~ d ~ e) =>
    93             (options: Options) => options.declare(a.isDefined, b, c, d, e) }
    94     }
    95 
    96     val prefs_entry: Parser[Options => Options] =
    97     {
    98       option_name ~ (keyword("=") ~! option_value) ^^
    99       { case a ~ (_ ~ b) => (options: Options) => options.add_permissive(a, b) }
   100     }
   101 
   102     def parse_file(syntax: Outer_Syntax, parser: Parser[Options => Options],
   103       options: Options, file: Path): Options =
   104     {
   105       val toks = syntax.scan(File.read(file))
   106       val ops =
   107         parse_all(rep(parser), Token.reader(toks, file.implode)) match {
   108           case Success(result, _) => result
   109           case bad => error(bad.toString)
   110         }
   111       try { (options.set_section("") /: ops) { case (opts, op) => op(opts) } }
   112       catch { case ERROR(msg) => error(msg + Position.here(file.position)) }
   113     }
   114   }
   115 
   116   def init_defaults(): Options =
   117   {
   118     var options = empty
   119     for {
   120       dir <- Isabelle_System.components()
   121       file = dir + OPTIONS if file.is_file
   122     } { options = Parser.parse_file(options_syntax, Parser.option_entry, options, file) }
   123     options
   124   }
   125 
   126   def init(): Options = init_defaults().load_prefs()
   127 
   128 
   129   /* encode */
   130 
   131   val encode: XML.Encode.T[Options] = (options => options.encode)
   132 
   133 
   134   /* command line entry point */
   135 
   136   def main(args: Array[String])
   137   {
   138     Command_Line.tool {
   139       args.toList match {
   140         case get_option :: export_file :: more_options =>
   141           val options = (Options.init() /: more_options)(_ + _)
   142 
   143           if (get_option != "")
   144             java.lang.System.out.println(options.check_name(get_option).value)
   145           else if (export_file != "")
   146             File.write(Path.explode(export_file), YXML.string_of_body(options.encode))
   147           else java.lang.System.out.println(options.print)
   148 
   149           0
   150         case _ => error("Bad arguments:\n" + cat_lines(args))
   151       }
   152     }
   153   }
   154 }
   155 
   156 
   157 final class Options private(
   158   val options: Map[String, Options.Opt] = Map.empty,
   159   val section: String = "")
   160 {
   161   override def toString: String = options.iterator.mkString("Options (", ",", ")")
   162 
   163   def print: String = cat_lines(options.toList.sortBy(_._1).map(p => p._2.print))
   164 
   165   def description(name: String): String = check_name(name).description
   166 
   167 
   168   /* check */
   169 
   170   def check_name(name: String): Options.Opt =
   171     options.get(name) match {
   172       case Some(opt) if !opt.unknown => opt
   173       case _ => error("Unknown option " + quote(name))
   174     }
   175 
   176   private def check_type(name: String, typ: Options.Type): Options.Opt =
   177   {
   178     val opt = check_name(name)
   179     if (opt.typ == typ) opt
   180     else error("Ill-typed option " + quote(name) + " : " + opt.typ.print + " vs. " + typ.print)
   181   }
   182 
   183 
   184   /* basic operations */
   185 
   186   private def put[A](name: String, typ: Options.Type, value: String): Options =
   187   {
   188     val opt = check_type(name, typ)
   189     new Options(options + (name -> opt.copy(value = value)), section)
   190   }
   191 
   192   private def get[A](name: String, typ: Options.Type, parse: String => Option[A]): A =
   193   {
   194     val opt = check_type(name, typ)
   195     parse(opt.value) match {
   196       case Some(x) => x
   197       case None =>
   198         error("Malformed value for option " + quote(name) +
   199           " : " + typ.print + " =\n" + quote(opt.value))
   200     }
   201   }
   202 
   203 
   204   /* internal lookup and update */
   205 
   206   class Bool_Access
   207   {
   208     def apply(name: String): Boolean = get(name, Options.Bool, Properties.Value.Boolean.unapply)
   209     def update(name: String, x: Boolean): Options =
   210       put(name, Options.Bool, Properties.Value.Boolean(x))
   211   }
   212   val bool = new Bool_Access
   213 
   214   class Int_Access
   215   {
   216     def apply(name: String): Int = get(name, Options.Int, Properties.Value.Int.unapply)
   217     def update(name: String, x: Int): Options =
   218       put(name, Options.Int, Properties.Value.Int(x))
   219   }
   220   val int = new Int_Access
   221 
   222   class Real_Access
   223   {
   224     def apply(name: String): Double = get(name, Options.Real, Properties.Value.Double.unapply)
   225     def update(name: String, x: Double): Options =
   226       put(name, Options.Real, Properties.Value.Double(x))
   227   }
   228   val real = new Real_Access
   229 
   230   class String_Access
   231   {
   232     def apply(name: String): String = get(name, Options.String, s => Some(s))
   233     def update(name: String, x: String): Options = put(name, Options.String, x)
   234   }
   235   val string = new String_Access
   236 
   237   class Seconds_Access
   238   {
   239     def apply(name: String): Time = Time.seconds(real(name))
   240   }
   241   val seconds = new Seconds_Access
   242 
   243 
   244   /* external updates */
   245 
   246   private def check_value(name: String): Options =
   247   {
   248     val opt = check_name(name)
   249     opt.typ match {
   250       case Options.Bool => bool(name); this
   251       case Options.Int => int(name); this
   252       case Options.Real => real(name); this
   253       case Options.String => string(name); this
   254       case Options.Unknown => this
   255     }
   256   }
   257 
   258   def declare(public: Boolean, name: String, typ_name: String, value: String, description: String)
   259     : Options =
   260   {
   261     options.get(name) match {
   262       case Some(_) => error("Duplicate declaration of option " + quote(name))
   263       case None =>
   264         val typ =
   265           typ_name match {
   266             case "bool" => Options.Bool
   267             case "int" => Options.Int
   268             case "real" => Options.Real
   269             case "string" => Options.String
   270             case _ => error("Unknown type for option " + quote(name) + " : " + quote(typ_name))
   271           }
   272         val opt = Options.Opt(public, name, typ, value, value, description, section)
   273         (new Options(options + (name -> opt), section)).check_value(name)
   274     }
   275   }
   276 
   277   def add_permissive(name: String, value: String): Options =
   278   {
   279     if (options.isDefinedAt(name)) this + (name, value)
   280     else
   281       new Options(
   282         options +
   283           (name -> Options.Opt(false, name, Options.Unknown, value, value, "", "")), section)
   284   }
   285 
   286   def + (name: String, value: String): Options =
   287   {
   288     val opt = check_name(name)
   289     (new Options(options + (name -> opt.copy(value = value)), section)).check_value(name)
   290   }
   291 
   292   def + (name: String, opt_value: Option[String]): Options =
   293   {
   294     val opt = check_name(name)
   295     opt_value match {
   296       case Some(value) => this + (name, value)
   297       case None if opt.typ == Options.Bool => this + (name, "true")
   298       case None => error("Missing value for option " + quote(name) + " : " + opt.typ.print)
   299     }
   300   }
   301 
   302   def + (str: String): Options =
   303   {
   304     str.indexOf('=') match {
   305       case -1 => this + (str, None)
   306       case i => this + (str.substring(0, i), str.substring(i + 1))
   307     }
   308   }
   309 
   310   def ++ (specs: List[Options.Spec]): Options =
   311     (this /: specs)({ case (x, (y, z)) => x + (y, z) })
   312 
   313 
   314   /* sections */
   315 
   316   def set_section(new_section: String): Options =
   317     new Options(options, new_section)
   318 
   319   def sections: List[(String, List[Options.Opt])] =
   320     options.groupBy(_._2.section).toList.map({ case (a, opts) => (a, opts.toList.map(_._2)) })
   321 
   322 
   323   /* encode */
   324 
   325   def encode: XML.Body =
   326   {
   327     val opts =
   328       for ((name, opt) <- options.toList; if !opt.unknown)
   329         yield (name, opt.typ.print, opt.value)
   330 
   331     import XML.Encode.{string => str, _}
   332     list(triple(str, str, str))(opts)
   333   }
   334 
   335 
   336   /* user preferences */
   337 
   338   def load_prefs(): Options =
   339     if (Options.PREFS.is_file)
   340       Options.Parser.parse_file(
   341         Options.prefs_syntax, Options.Parser.prefs_entry, this, Options.PREFS)
   342     else this
   343 
   344   def save_prefs()
   345   {
   346     val defaults = Options.init_defaults()
   347     val changed =
   348       (for {
   349         (name, opt2) <- options.iterator
   350         opt1 = defaults.options.get(name)
   351         if (opt1.isEmpty || opt1.get.value != opt2.value)
   352       } yield (name, opt2.value, if (opt1.isEmpty) "  (* unknown *)" else "")).toList
   353 
   354     val prefs =
   355       changed.sortBy(_._1)
   356         .map({ case (x, y, z) => x + " = " + Outer_Syntax.quote_string(y) + z + "\n" }).mkString
   357 
   358     Isabelle_System.mkdirs(Options.PREFS_DIR)
   359     Options.PREFS.file renameTo Options.PREFS_BACKUP.file
   360     File.write(Options.PREFS,
   361       "(* generated by Isabelle " + Calendar.getInstance.getTime + " *)\n\n" + prefs)
   362   }
   363 }
   364 
   365 
   366 class Options_Variable
   367 {
   368   private var options = Options.empty
   369 
   370   def value: Options = synchronized { options }
   371   def update(new_options: Options): Unit = synchronized { options = new_options }
   372 
   373   def + (name: String, x: String): Unit = synchronized { options = options + (name, x) }
   374 
   375   class Bool_Access
   376   {
   377     def apply(name: String): Boolean = synchronized { options.bool(name) }
   378     def update(name: String, x: Boolean): Unit =
   379       synchronized { options = options.bool.update(name, x) }
   380   }
   381   val bool = new Bool_Access
   382 
   383   class Int_Access
   384   {
   385     def apply(name: String): Int = synchronized { options.int(name) }
   386     def update(name: String, x: Int): Unit =
   387       synchronized { options = options.int.update(name, x) }
   388   }
   389   val int = new Int_Access
   390 
   391   class Real_Access
   392   {
   393     def apply(name: String): Double = synchronized { options.real(name) }
   394     def update(name: String, x: Double): Unit =
   395       synchronized { options = options.real.update(name, x) }
   396   }
   397   val real = new Real_Access
   398 
   399   class String_Access
   400   {
   401     def apply(name: String): String = synchronized { options.string(name) }
   402     def update(name: String, x: String): Unit =
   403       synchronized { options = options.string.update(name, x) }
   404   }
   405   val string = new String_Access
   406 
   407   class Seconds_Access
   408   {
   409     def apply(name: String): Time = synchronized { options.seconds(name) }
   410   }
   411   val seconds = new Seconds_Access
   412 }
   413