src/Tools/Find_Facts/thy_blocks.scala
changeset 81764 fcba3250fb2a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Tools/Find_Facts/thy_blocks.scala	Sat Jan 11 21:31:13 2025 +0100
@@ -0,0 +1,141 @@
+/*  Author:     Fabian Huch, TU Muenchen
+
+Block structure for Isabelle theories, read from build database.
+*/
+
+package isabelle
+
+
+import scala.annotation.tailrec
+
+
+object Thy_Blocks {
+  /** spans **/
+
+  val keyword_elements =
+    Markup.Elements(Markup.KEYWORD, Markup.KEYWORD1, Markup.KEYWORD2, Markup.KEYWORD3)
+
+  object Span {
+    def read_build(snapshot: Document.Snapshot): List[Span] = {
+      def is_begin(markup: Text.Markup): Boolean = markup.info match {
+        case XML.Elem(Markup(_, Markup.Kind(Markup.KEYWORD)), Nil) =>
+          XML.content(snapshot.xml_markup(markup.range)) == "begin"
+        case _ => false
+      }
+
+      def has_begin(range: Text.Range): Boolean =
+        snapshot
+          .select(range, keyword_elements, _ => markup => Some(is_begin(markup)))
+          .exists(_.info)
+
+      snapshot.select(Text.Range.full, Markup.Elements(Markup.COMMAND_SPAN), _ =>
+        {
+          case Text.Info(range, XML.Elem(Markup.Command_Span(arg), _)) =>
+            Some(Span(range, arg.name, arg.kind, has_begin(range)))
+          case _ => None
+        }).map(_.info)
+    }
+  }
+
+  case class Span(
+    override val range: Text.Range,
+    override val command: String,
+    kind: String,
+    has_begin: Boolean
+  ) extends Block {
+    def spans: List[Span] = List(this)
+
+    def is_of_kind(kinds: Set[String]): Boolean = kinds.contains(kind)
+  }
+
+
+  /** block structure **/
+
+  sealed trait Block {
+    def spans: List[Span]
+
+    def command: String = spans.head.command
+    def range: Text.Range = Text.Range(spans.head.range.start, spans.last.range.stop)
+  }
+
+  case class Single(span: Span) extends Block { def spans = List(span) }
+  case class Thy(inner: List[Block]) extends Block { def spans = inner.flatMap(_.spans) }
+  case class Prf(inner: List[Block]) extends Block { def spans = inner.flatMap(_.spans) }
+  case class Decl(inner: List[Block]) extends Block { def spans = inner.flatMap(_.spans) }
+
+
+  /** parser **/
+
+  object Parser {
+    object Blocks {
+      def empty: Blocks = new Blocks(Nil, Nil)
+    }
+
+    case class Blocks(private val stack: List[Block], private val out: List[Block]) {
+      def peek: Option[Block] = stack.headOption
+      def push(block: Block): Blocks = copy(stack = block :: stack)
+      def add(block: Block): Blocks =
+        stack match {
+          case Nil => copy(out = block :: out)
+          case head :: rest =>
+            val head1 =
+              head match {
+                case Thy(inner) => Thy(inner :+ block)
+                case Prf(inner) => Prf(inner :+ block)
+                case Decl(inner) => Decl(inner :+ block)
+                case _ => error("Cannot add to " + head)
+              }
+            copy(stack = head1 :: rest)
+        }
+
+      def pop: Blocks =
+        stack match {
+          case Nil => error("Nothing to pop")
+          case head :: rest => copy(stack = rest).add(head)
+        }
+
+      def pop_prfs: Blocks = {
+        val blocks1 = pop
+        blocks1.stack match {
+          case Prf(_) :: _ => blocks1.pop_prfs
+          case _ => blocks1
+        }
+      }
+
+      def output: List[Block] = if (stack.nonEmpty) error("Nonempty stack") else out.reverse
+    }
+
+    def parse(spans: List[Span]): List[Block] = {
+      import Keyword.*
+
+      def parse1(blocks: Blocks, span: Span): Blocks =
+        blocks.peek match {
+          case _ if span.is_of_kind(document) => blocks.add(span)
+          case None if span.is_of_kind(theory_begin) => blocks.push(Thy(List(span)))
+          case Some(_) if span.is_of_kind(diag) => blocks.add(span)
+          case Some(Thy(_)) if span.is_of_kind(theory_goal) => blocks.push(Prf(List(span)))
+          case Some(Thy(_)) if span.is_of_kind(theory_block) =>
+            (if (span.has_begin) blocks.push else blocks.add)(Decl(List(span)))
+          case Some(Thy(_)) if span.is_of_kind(theory_end) => blocks.add(span).pop
+          case Some(Thy(_)) if span.is_of_kind(theory_body) => blocks.add(span)
+          case Some(Prf(_)) if span.is_of_kind(proof_open) => blocks.push(Prf(List(span)))
+          case Some(Prf(_)) if span.is_of_kind(proof_close) => blocks.add(span).pop
+          case Some(Prf(_)) if span.is_of_kind(qed_global) => blocks.add(span).pop_prfs
+          case Some(Prf(_)) if span.is_of_kind(proof_body) => blocks.add(span)
+          case Some(Decl(_)) if span.is_of_kind(proof_open) => blocks.push(Prf(List(span)))
+          case Some(Decl(_)) if span.is_of_kind(proof_body) => blocks.add(span)
+          case Some(Decl(_)) if span.is_of_kind(theory_goal) => blocks.push(Prf(List(span)))
+          case Some(Decl(_)) if span.is_of_kind(theory_block) =>
+            (if (span.has_begin) blocks.push else blocks.add)(Decl(List(span)))
+          case Some(Decl(_)) if span.is_of_kind(theory_end) => blocks.add(span).pop
+          case Some(Decl(_)) if span.is_of_kind(theory_body) => blocks.add(span)
+          case e => error("Unexpected span " + span + " at " + e)
+        }
+
+      spans.foldLeft(Blocks.empty)(parse1).output
+    }
+  }
+
+  def read_blocks(snapshot: Document.Snapshot): List[Block] =
+    Parser.parse(Span.read_build(snapshot))
+}