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