src/Pure/Isar/outer_syntax.scala
author wenzelm
Fri Nov 07 23:35:13 2014 +0100 (2014-11-07)
changeset 58938 0c45680b7d9d
parent 58907 0ee3563803c9
child 59073 dcecfcc56dce
permissions -rw-r--r--
tuned outline;
     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   val keywords: Keyword.Keywords = Keyword.Keywords.empty,
    78   val completion: Completion = Completion.empty,
    79   val language_context: Completion.Language_Context = Completion.Language_Context.outer,
    80   val has_tokens: Boolean = true) extends Prover.Syntax
    81 {
    82   /** syntax content **/
    83 
    84   override def toString: String = keywords.toString
    85 
    86 
    87   /* add keywords */
    88 
    89   def + (name: String): Outer_Syntax = this + (name, None, None)
    90   def + (name: String, kind: String): Outer_Syntax = this + (name, Some((kind, Nil)), None)
    91   def + (name: String, opt_kind: Option[(String, List[String])], replace: Option[String])
    92     : Outer_Syntax =
    93   {
    94     val keywords1 =
    95       opt_kind match {
    96         case None => keywords + name
    97         case Some(kind) => keywords + (name, kind)
    98       }
    99     val completion1 =
   100       if (replace == Some("")) completion
   101       else completion + (name, replace getOrElse name)
   102     new Outer_Syntax(keywords1, completion1, language_context, true)
   103   }
   104 
   105   def add_keywords(keywords: Thy_Header.Keywords): Outer_Syntax =
   106     (this /: keywords) {
   107       case (syntax, (name, opt_spec, replace)) =>
   108         val opt_kind = opt_spec.map(_._1)
   109         syntax +
   110           (Symbol.decode(name), opt_kind, replace) +
   111           (Symbol.encode(name), opt_kind, replace)
   112     }
   113 
   114 
   115   /* load commands */
   116 
   117   def load_command(name: String): Option[List[String]] = keywords.load_command(name)
   118   def load_commands_in(text: String): Boolean = keywords.load_commands_in(text)
   119 
   120 
   121   /* language context */
   122 
   123   def set_language_context(context: Completion.Language_Context): Outer_Syntax =
   124     new Outer_Syntax(keywords, completion, context, has_tokens)
   125 
   126   def no_tokens: Outer_Syntax =
   127   {
   128     require(keywords.is_empty)
   129     new Outer_Syntax(
   130       completion = completion,
   131       language_context = language_context,
   132       has_tokens = false)
   133   }
   134 
   135 
   136 
   137   /** parsing **/
   138 
   139   /* line-oriented structure */
   140 
   141   def line_structure(tokens: List[Token], struct: Outer_Syntax.Line_Structure)
   142     : Outer_Syntax.Line_Structure =
   143   {
   144     val improper1 = tokens.forall(_.is_improper)
   145     val command1 = tokens.exists(_.is_command)
   146 
   147     val depth1 =
   148       if (tokens.exists(tok => keywords.is_command_kind(tok, Keyword.theory))) 0
   149       else if (command1) struct.after_span_depth
   150       else struct.span_depth
   151 
   152     val (span_depth1, after_span_depth1) =
   153       ((struct.span_depth, struct.after_span_depth) /: tokens) {
   154         case ((x, y), tok) =>
   155           if (tok.is_command) {
   156             if (keywords.is_command_kind(tok, Keyword.theory_goal))
   157               (2, 1)
   158             else if (keywords.is_command_kind(tok, Keyword.theory))
   159               (1, 0)
   160             else if (keywords.is_command_kind(tok, Keyword.proof_goal) || tok.is_begin_block)
   161               (y + 2, y + 1)
   162             else if (keywords.is_command_kind(tok, Keyword.qed) || tok.is_end_block)
   163               (y + 1, y - 1)
   164             else if (keywords.is_command_kind(tok, Keyword.qed_global))
   165               (1, 0)
   166             else (x, y)
   167           }
   168           else (x, y)
   169       }
   170 
   171     Outer_Syntax.Line_Structure(improper1, command1, depth1, span_depth1, after_span_depth1)
   172   }
   173 
   174 
   175   /* token language */
   176 
   177   def scan(input: CharSequence): List[Token] =
   178   {
   179     val in: Reader[Char] = new CharSequenceReader(input)
   180     Token.Parsers.parseAll(Token.Parsers.rep(Token.Parsers.token(keywords)), in) match {
   181       case Token.Parsers.Success(tokens, _) => tokens
   182       case _ => error("Unexpected failure of tokenizing input:\n" + input.toString)
   183     }
   184   }
   185 
   186   def scan_line(input: CharSequence, context: Scan.Line_Context): (List[Token], Scan.Line_Context) =
   187   {
   188     var in: Reader[Char] = new CharSequenceReader(input)
   189     val toks = new mutable.ListBuffer[Token]
   190     var ctxt = context
   191     while (!in.atEnd) {
   192       Token.Parsers.parse(Token.Parsers.token_line(keywords, ctxt), in) match {
   193         case Token.Parsers.Success((x, c), rest) => { toks += x; ctxt = c; in = rest }
   194         case Token.Parsers.NoSuccess(_, rest) =>
   195           error("Unexpected failure of tokenizing input:\n" + rest.source.toString)
   196       }
   197     }
   198     (toks.toList, ctxt)
   199   }
   200 
   201 
   202   /* command spans */
   203 
   204   def parse_spans(toks: List[Token]): List[Command_Span.Span] =
   205   {
   206     val result = new mutable.ListBuffer[Command_Span.Span]
   207     val content = new mutable.ListBuffer[Token]
   208     val improper = new mutable.ListBuffer[Token]
   209 
   210     def ship(span: List[Token])
   211     {
   212       val kind =
   213         if (!span.isEmpty && span.head.is_command && !span.exists(_.is_error)) {
   214           val name = span.head.source
   215           val pos = Position.Range(Text.Range(0, Symbol.iterator(name).length) + 1)
   216           Command_Span.Command_Span(name, pos)
   217         }
   218         else if (span.forall(_.is_improper)) Command_Span.Ignored_Span
   219         else Command_Span.Malformed_Span
   220       result += Command_Span.Span(kind, span)
   221     }
   222 
   223     def flush()
   224     {
   225       if (!content.isEmpty) { ship(content.toList); content.clear }
   226       if (!improper.isEmpty) { ship(improper.toList); improper.clear }
   227     }
   228 
   229     for (tok <- toks) {
   230       if (tok.is_command) { flush(); content += tok }
   231       else if (tok.is_improper) improper += tok
   232       else { content ++= improper; improper.clear; content += tok }
   233     }
   234     flush()
   235 
   236     result.toList
   237   }
   238 
   239   def parse_spans(input: CharSequence): List[Command_Span.Span] =
   240     parse_spans(scan(input))
   241 
   242 
   243   /* overall document structure */
   244 
   245   def heading_level(command: Command): Option[Int] =
   246   {
   247     command.name match {
   248       case "chapter" => Some(0)
   249       case "section" | "header" => Some(1)
   250       case "subsection" => Some(2)
   251       case "subsubsection" => Some(3)
   252       case _ =>
   253         keywords.command_kind(command.name) match {
   254           case Some(kind) if Keyword.theory(kind) && kind != Keyword.THY_END => Some(4)
   255           case _ => None
   256         }
   257     }
   258   }
   259 
   260   def parse_document(node_name: Document.Node.Name, text: CharSequence):
   261     List[Outer_Syntax.Document] =
   262   {
   263     /* stack operations */
   264 
   265     def buffer(): mutable.ListBuffer[Outer_Syntax.Document] =
   266       new mutable.ListBuffer[Outer_Syntax.Document]
   267 
   268     var stack: List[(Int, Command, mutable.ListBuffer[Outer_Syntax.Document])] =
   269       List((0, Command.empty, buffer()))
   270 
   271     @tailrec def close(level: Int => Boolean)
   272     {
   273       stack match {
   274         case (lev, command, body) :: (_, _, body2) :: rest if level(lev) =>
   275           body2 += Outer_Syntax.Document_Block(command.name, command.source, body.toList)
   276           stack = stack.tail
   277           close(level)
   278         case _ =>
   279       }
   280     }
   281 
   282     def result(): List[Outer_Syntax.Document] =
   283     {
   284       close(_ => true)
   285       stack.head._3.toList
   286     }
   287 
   288     def add(command: Command)
   289     {
   290       heading_level(command) match {
   291         case Some(i) =>
   292           close(_ > i)
   293           stack = (i + 1, command, buffer()) :: stack
   294         case None =>
   295       }
   296       stack.head._3 += Outer_Syntax.Document_Atom(command)
   297     }
   298 
   299 
   300     /* result structure */
   301 
   302     val spans = parse_spans(text)
   303     spans.foreach(span => add(Command(Document_ID.none, node_name, Nil, span)))
   304     result()
   305   }
   306 }