src/Pure/Isar/outer_syntax.scala
author wenzelm
Wed Nov 05 15:32:11 2014 +0100 (2014-11-05)
changeset 58899 0a793c580685
parent 58868 c5e1cce7ace3
child 58900 1435cc20b022
permissions -rw-r--r--
clarified minor/major lexicon (like ML version);
     1 /*  Title:      Pure/Isar/outer_syntax.scala
     2     Author:     Makarius
     3 
     4 Isabelle/Isar outer syntax.
     5 */
     6 
     7 package isabelle
     8 
     9 
    10 import scala.util.parsing.input.{Reader, CharSequenceReader}
    11 import scala.collection.mutable
    12 import scala.annotation.tailrec
    13 
    14 
    15 object Outer_Syntax
    16 {
    17   /* syntax */
    18 
    19   val empty: Outer_Syntax = new Outer_Syntax()
    20 
    21   def init(): Outer_Syntax = new Outer_Syntax(completion = Completion.init())
    22 
    23 
    24   /* string literals */
    25 
    26   def quote_string(str: String): String =
    27   {
    28     val result = new StringBuilder(str.length + 10)
    29     result += '"'
    30     for (s <- Symbol.iterator(str)) {
    31       if (s.length == 1) {
    32         val c = s(0)
    33         if (c < 32 && c != YXML.X && c != YXML.Y || c == '\\' || c == '"') {
    34           result += '\\'
    35           if (c < 10) result += '0'
    36           if (c < 100) result += '0'
    37           result ++= (c.asInstanceOf[Int].toString)
    38         }
    39         else result += c
    40       }
    41       else result ++= s
    42     }
    43     result += '"'
    44     result.toString
    45   }
    46 
    47 
    48   /* line-oriented structure */
    49 
    50   object Line_Structure
    51   {
    52     val init = Line_Structure()
    53   }
    54 
    55   sealed case class Line_Structure(
    56     improper: Boolean = true,
    57     command: Boolean = false,
    58     depth: Int = 0,
    59     span_depth: Int = 0,
    60     after_span_depth: Int = 0)
    61 
    62 
    63   /* overall document structure */
    64 
    65   sealed abstract class Document { def length: Int }
    66   case class Document_Block(name: String, text: String, body: List[Document]) extends Document
    67   {
    68     val length: Int = (0 /: body)(_ + _.length)
    69   }
    70   case class Document_Atom(command: Command) extends Document
    71   {
    72     def length: Int = command.length
    73   }
    74 }
    75 
    76 final class Outer_Syntax private(
    77   keywords: Map[String, (String, List[String])] = Map.empty,
    78   minor: Scan.Lexicon = Scan.Lexicon.empty,
    79   major: Scan.Lexicon = Scan.Lexicon.empty,
    80   val completion: Completion = Completion.empty,
    81   val language_context: Completion.Language_Context = Completion.Language_Context.outer,
    82   val has_tokens: Boolean = true) extends Prover.Syntax
    83 {
    84   /** syntax content **/
    85 
    86   override def toString: String =
    87     (for ((name, (kind, files)) <- keywords) yield {
    88       if (kind == Keyword.MINOR) quote(name)
    89       else
    90         quote(name) + " :: " + quote(kind) +
    91         (if (files.isEmpty) "" else " (" + commas_quote(files) + ")")
    92     }).toList.sorted.mkString("keywords\n  ", " and\n  ", "")
    93 
    94 
    95   /* keyword kind */
    96 
    97   def keyword_kind_files(name: String): Option[(String, List[String])] = keywords.get(name)
    98   def keyword_kind(name: String): Option[String] = keyword_kind_files(name).map(_._1)
    99 
   100   def is_command(name: String): Boolean =
   101     keyword_kind(name) match {
   102       case Some(kind) => kind != Keyword.MINOR
   103       case None => false
   104     }
   105 
   106   def command_kind(token: Token, pred: String => Boolean): Boolean =
   107     token.is_command && is_command(token.source) &&
   108       pred(keyword_kind(token.source).get)
   109 
   110 
   111   /* load commands */
   112 
   113   def load_command(name: String): Option[List[String]] =
   114     keywords.get(name) match {
   115       case Some((Keyword.THY_LOAD, exts)) => Some(exts)
   116       case _ => None
   117     }
   118 
   119   val load_commands: List[(String, List[String])] =
   120     (for ((name, (Keyword.THY_LOAD, files)) <- keywords.iterator) yield (name, files)).toList
   121 
   122   def load_commands_in(text: String): Boolean =
   123     load_commands.exists({ case (cmd, _) => text.containsSlice(cmd) })
   124 
   125 
   126   /* add keywords */
   127 
   128   def + (name: String, kind: (String, List[String]), replace: Option[String]): Outer_Syntax =
   129   {
   130     val keywords1 = keywords + (name -> kind)
   131     val (minor1, major1) =
   132       if (kind._1 == Keyword.MINOR) (minor + name, major) else (minor, major + name)
   133     val completion1 =
   134       if (replace == Some("")) completion
   135       else completion + (name, replace getOrElse name)
   136     new Outer_Syntax(keywords1, minor1, major1, completion1, language_context, true)
   137   }
   138 
   139   def + (name: String, kind: (String, List[String])): Outer_Syntax =
   140     this + (name, kind, Some(name))
   141   def + (name: String, kind: String): Outer_Syntax =
   142     this + (name, (kind, Nil), Some(name))
   143   def + (name: String, replace: Option[String]): Outer_Syntax =
   144     this + (name, (Keyword.MINOR, Nil), replace)
   145   def + (name: String): Outer_Syntax = this + (name, None)
   146 
   147   def add_keywords(keywords: Thy_Header.Keywords): Outer_Syntax =
   148     (this /: keywords) {
   149       case (syntax, (name, Some((kind, _)), replace)) =>
   150         syntax +
   151           (Symbol.decode(name), kind, replace) +
   152           (Symbol.encode(name), kind, replace)
   153       case (syntax, (name, None, replace)) =>
   154         syntax +
   155           (Symbol.decode(name), replace) +
   156           (Symbol.encode(name), replace)
   157     }
   158 
   159 
   160   /* language context */
   161 
   162   def set_language_context(context: Completion.Language_Context): Outer_Syntax =
   163     new Outer_Syntax(keywords, minor, major, completion, context, has_tokens)
   164 
   165   def no_tokens: Outer_Syntax =
   166   {
   167     require(keywords.isEmpty && minor.isEmpty && major.isEmpty)
   168     new Outer_Syntax(
   169       completion = completion,
   170       language_context = language_context,
   171       has_tokens = false)
   172   }
   173 
   174 
   175 
   176   /** parsing **/
   177 
   178   /* line-oriented structure */
   179 
   180   def line_structure(tokens: List[Token], struct: Outer_Syntax.Line_Structure)
   181     : Outer_Syntax.Line_Structure =
   182   {
   183     val improper1 = tokens.forall(_.is_improper)
   184     val command1 = tokens.exists(_.is_command)
   185 
   186     val depth1 =
   187       if (tokens.exists(tok => command_kind(tok, Keyword.theory))) 0
   188       else if (command1) struct.after_span_depth
   189       else struct.span_depth
   190 
   191     val (span_depth1, after_span_depth1) =
   192       ((struct.span_depth, struct.after_span_depth) /: tokens) {
   193         case ((x, y), tok) =>
   194           if (tok.is_command) {
   195             if (command_kind(tok, Keyword.theory_goal)) (2, 1)
   196             else if (command_kind(tok, Keyword.theory)) (1, 0)
   197             else if (command_kind(tok, Keyword.proof_goal) || tok.is_begin_block) (y + 2, y + 1)
   198             else if (command_kind(tok, Keyword.qed) || tok.is_end_block) (y + 1, y - 1)
   199             else if (command_kind(tok, Keyword.qed_global)) (1, 0)
   200             else (x, y)
   201           }
   202           else (x, y)
   203       }
   204 
   205     Outer_Syntax.Line_Structure(improper1, command1, depth1, span_depth1, after_span_depth1)
   206   }
   207 
   208 
   209   /* token language */
   210 
   211   def scan(input: CharSequence): List[Token] =
   212   {
   213     val in: Reader[Char] = new CharSequenceReader(input)
   214     Token.Parsers.parseAll(Token.Parsers.rep(Token.Parsers.token(minor, major)), in) match {
   215       case Token.Parsers.Success(tokens, _) => tokens
   216       case _ => error("Unexpected failure of tokenizing input:\n" + input.toString)
   217     }
   218   }
   219 
   220   def scan_line(input: CharSequence, context: Scan.Line_Context): (List[Token], Scan.Line_Context) =
   221   {
   222     var in: Reader[Char] = new CharSequenceReader(input)
   223     val toks = new mutable.ListBuffer[Token]
   224     var ctxt = context
   225     while (!in.atEnd) {
   226       Token.Parsers.parse(Token.Parsers.token_line(minor, major, ctxt), in) match {
   227         case Token.Parsers.Success((x, c), rest) => { toks += x; ctxt = c; in = rest }
   228         case Token.Parsers.NoSuccess(_, rest) =>
   229           error("Unexpected failure of tokenizing input:\n" + rest.source.toString)
   230       }
   231     }
   232     (toks.toList, ctxt)
   233   }
   234 
   235 
   236   /* command spans */
   237 
   238   def parse_spans(toks: List[Token]): List[Command_Span.Span] =
   239   {
   240     val result = new mutable.ListBuffer[Command_Span.Span]
   241     val content = new mutable.ListBuffer[Token]
   242     val improper = new mutable.ListBuffer[Token]
   243 
   244     def ship(span: List[Token])
   245     {
   246       val kind =
   247         if (!span.isEmpty && span.head.is_command && !span.exists(_.is_error)) {
   248           val name = span.head.source
   249           val pos = Position.Range(Text.Range(0, Symbol.iterator(name).length) + 1)
   250           Command_Span.Command_Span(name, pos)
   251         }
   252         else if (span.forall(_.is_improper)) Command_Span.Ignored_Span
   253         else Command_Span.Malformed_Span
   254       result += Command_Span.Span(kind, span)
   255     }
   256 
   257     def flush()
   258     {
   259       if (!content.isEmpty) { ship(content.toList); content.clear }
   260       if (!improper.isEmpty) { ship(improper.toList); improper.clear }
   261     }
   262 
   263     for (tok <- toks) {
   264       if (tok.is_command) { flush(); content += tok }
   265       else if (tok.is_improper) improper += tok
   266       else { content ++= improper; improper.clear; content += tok }
   267     }
   268     flush()
   269 
   270     result.toList
   271   }
   272 
   273   def parse_spans(input: CharSequence): List[Command_Span.Span] =
   274     parse_spans(scan(input))
   275 
   276 
   277   /* overall document structure */
   278 
   279   def heading_level(command: Command): Option[Int] =
   280   {
   281     command.name match {
   282       case "chapter" => Some(0)
   283       case "section" | "header" => Some(1)
   284       case "subsection" => Some(2)
   285       case "subsubsection" => Some(3)
   286       case _ =>
   287         keyword_kind(command.name) match {
   288           case Some(kind) if Keyword.theory(kind) => Some(4)
   289           case _ => None
   290         }
   291     }
   292   }
   293 
   294   def parse_document(node_name: Document.Node.Name, text: CharSequence):
   295     List[Outer_Syntax.Document] =
   296   {
   297     /* stack operations */
   298 
   299     def buffer(): mutable.ListBuffer[Outer_Syntax.Document] =
   300       new mutable.ListBuffer[Outer_Syntax.Document]
   301 
   302     var stack: List[(Int, Command, mutable.ListBuffer[Outer_Syntax.Document])] =
   303       List((0, Command.empty, buffer()))
   304 
   305     @tailrec def close(level: Int => Boolean)
   306     {
   307       stack match {
   308         case (lev, command, body) :: (_, _, body2) :: rest if level(lev) =>
   309           body2 += Outer_Syntax.Document_Block(command.name, command.source, body.toList)
   310           stack = stack.tail
   311           close(level)
   312         case _ =>
   313       }
   314     }
   315 
   316     def result(): List[Outer_Syntax.Document] =
   317     {
   318       close(_ => true)
   319       stack.head._3.toList
   320     }
   321 
   322     def add(command: Command)
   323     {
   324       heading_level(command) match {
   325         case Some(i) =>
   326           close(_ > i)
   327           stack = (i + 1, command, buffer()) :: stack
   328         case None =>
   329       }
   330       stack.head._3 += Outer_Syntax.Document_Atom(command)
   331     }
   332 
   333 
   334     /* result structure */
   335 
   336     val spans = parse_spans(text)
   337     spans.foreach(span => add(Command(Document_ID.none, node_name, Nil, span)))
   338     result()
   339   }
   340 }