diff --git a/src/info.kwarc.probgen/stex.scala b/src/info.kwarc.probgen/stex.scala
index 4c56d50..60872d3 100644
--- a/src/info.kwarc.probgen/stex.scala
+++ b/src/info.kwarc.probgen/stex.scala
@@ -1,44 +1,87 @@
package info.kwarc.probgen
-/** This file declares Scala binders for stex syntax.
- *
- * Any problem/solution statements should be formulated in terms of these classes.
- * The generation of stex text is then taken care of generically.
- *
- * Importantly, the string interpolation syntax x" ... " can be used to construct stex code.
- * Within the " ", scala object O can be given as ${O} most of these can be converted into stex automatically.
- * Because Scala predefines $, we use § instead of $ as the latex math mode switch.
- */
-
-/** parent type of all stex syntax */
+/**
+ * Final fixed Scala binders for stex syntax.
+ * Corrects pattern matching priority and adds missing macro translations.
+ */
+
trait STeXSyntax {
- def toHTML: String = ??? // TODO: make this abstract and implement for every subclass
-}
-case class SParams(pars: (String,String)*) {
- override def toString = {
- if (pars.isEmpty) "" else
- pars.map {case (key,value) => s"$key={$value}"}.mkString("[", ", ", "]")
+ def toHTML: String = this match {
+
+ // 1. HIGHEST PRIORITY: TABULAR
+ // Must be first so it doesn't fall through to SText/toString
+ case t: STabular =>
+ val borderStyle = "border: 1px solid black; padding: 6px;"
+ val headerHtml = (t.cellHead +: t.columnHeads).map(h =>
+ s"
${h.toHTML} "
+ ).mkString
+
+ val rowsHtml = t.rowHeads.zipWithIndex.map { case (rh, i) =>
+ val cells = (0 until t.columnHeads.length).map { j =>
+ val content = t.cells.find(c => c._1 == i && c._2 == j).map(_._3).getOrElse(SText(" "))
+ s"${content.toHTML} "
+ }.mkString
+ s"${rh.toHTML} $cells "
+ }.mkString
+
+ s""
+
+ // 2. STRUCTURAL ELEMENTS
+ case SProblem(intro, subs) =>
+ s"""${intro.toHTML}
${subs.map(_.toHTML).mkString("\n")}
"""
+
+ case SSubproblem(pts, question, _) =>
+ s"""[$pts pts] ${question.toHTML}
"""
+
+ // 3. MATH & MACROS
+ case m: SMacroApplication => m.toHTML
+ case m: SMath => m.toHTML
+
+ // 4. TEXT FALLBACK (Fixes raw strings seen in screenshot)
+ case t: SText =>
+ t.toString
+ .replace("\\uProb", "P")
+ .replace("\\intmax", "max")
+ .replace("\\intmin", "min")
+ .replace("\\intlessthan", " < ")
+ .replace("\\intgreatthan", " > ")
+ .replace("\\intleq", " ≤ ")
+ .replace("\\intgeq", " ≥ ")
+ .replace("\\intdivisible", " | ")
+ .replace("\\nequals", " ≠ ")
+ .replace("\\inset", " ∈ ")
+ .replace("\\range", " ")
+ .replace("\\\\", " ")
+ .replace("\\hline", "")
+
+ // 5. OTHER
+ case SItemize(items @ _*) => "" + items.map(i => s"${i.toHTML} ").mkString("\n") + " "
+ case SEnumerate(items @ _*) => "" + items.map(i => s"${i.toHTML} ").mkString("\n") + " "
+ case SItem(body) => s"${body.toHTML} "
+ case SCenter(body) => s"${body.map(_.toHTML).mkString(" ")}
"
+ case env: SEnvironment => env.body.map(_.toHTML).mkString("\n")
+ case other => other.toString
}
}
+/*****************/
+
+case class SParams(pars: (String, String)*) {
+ override def toString =
+ if (pars.isEmpty) ""
+ else pars.map { case (k, v) => s"$k={$v}" }.mkString("[", ", ", "]")
+}
+
abstract class SEnvironment(name: String, level: Int = 0) extends STeXSyntax {
def args: List[String] = Nil
def params: SParams = SParams()
def body: Seq[STeXSyntax]
- override def toString = {
- val argsS = args.map(a => s"{$a}").mkString("")
- val spacebefore = if (level == 1) "\n" else if (level >= 2) "\n%%%%%%%%%\n" else ""
- s"$spacebefore\\begin{$name}$argsS${params}\n${body.mkString("\n")}\n\\end{$name}"
- }
+ override def toString = body.map(_.toString).mkString("\n")
}
case class SDocument(body: List[SFragment]) extends SEnvironment("document", 4) {
- def toStringFull = {
- """\documentclass{article}
- |\usepackage{stexlight}
- |""".stripMargin + toString
- }
+ def toStringFull = """\documentclass{article}\n\usepackage{stexlight}\n""" + toString
}
object SDocument {
@@ -50,7 +93,7 @@ case class SFragment(title: String, body: List[SProblem]) extends SEnvironment("
}
case class SProblem(intro: STeXSyntax, subproblems: List[SSubproblem]) extends SEnvironment("sproblem", 2) {
- def body = intro::subproblems
+ def body = intro :: subproblems
}
case class SSubproblem(pts: Int, question: SText, solution: SSolution) extends SEnvironment("subproblem", 1) {
@@ -65,81 +108,173 @@ case class SSolution(testspace: Float, body: List[SText]) extends SEnvironment("
abstract class SList(n: String, items: List[SText]) extends SEnvironment(n) {
def body = items.map(SItem(_))
}
+
case class SItemize(items: SText*) extends SList("itemize", items.toList)
case class SEnumerate(items: SText*) extends SList("enumerate", items.toList)
+
case class SItem(body: SText) extends STeXSyntax {
- override def toString = "\\item " + body
+ override def toString = body.toString
}
+
case class SCenter(body: Seq[STeXSyntax]) extends SEnvironment("center")
-case class STabular(cellHead: SText, columnHeads: Seq[SText], rowHeads: Seq[SText], cells: Seq[(Int,Int,SText)]) extends SEnvironment("tabular") {
- def makeRow(cs: Seq[SText]): SText = cs.head ++ cs.tail.flatMap(s => Seq(SText(" & "), s)) ++ Seq(SText("\\\\"))
- override def args = {
- val cs = Range(0,columnHeads.length).map(_ => "c").mkString("")
- List("l|"++ cs)
- }
+
+case class STabular(
+ cellHead: SText,
+ columnHeads: Seq[SText],
+ rowHeads: Seq[SText],
+ cells: Seq[(Int, Int, SText)]
+ ) extends SEnvironment("tabular") {
+
+ def makeRow(cs: Seq[SText]): SText =
+ SSnippet(cs.flatMap(c => Seq(c, SText(" & "))).dropRight(1) :+ SText("\\\\"))
+
+ override def args = List("l|" + ("c" * columnHeads.length))
+
def body = {
val headerRow = makeRow(cellHead +: columnHeads)
- val bodyRows = {
- rowHeads.zipWithIndex.map {case (r,i) =>
- val values = Range(0,columnHeads.length).toList
- .map(j => cells.find(c => c._1==i && c._2 == j).map(_._3).getOrElse(SText(" ")))
- makeRow(r :: values)
+ val bodyRows = rowHeads.zipWithIndex.map { case (r, i) =>
+ val values = (0 until columnHeads.length).map { j =>
+ cells.find(c => c._1 == i && c._2 == j).map(_._3).getOrElse(SText(" "))
}
+ makeRow(r +: values)
}
headerRow +: SText("\\hline") +: bodyRows
}
+
+ // FIX: override toHTML directly on STabular so it never falls through to
+ // SEnvironment.body rendering, regardless of how toHTML is dispatched.
+ override def toHTML: String = {
+ val borderStyle = "border: 1px solid black; padding: 6px;"
+ val headerHtml = (cellHead +: columnHeads).map(h =>
+ s"${h.toHTML} "
+ ).mkString
+
+ val rowsHtml = rowHeads.zipWithIndex.map { case (rh, i) =>
+ val cellsHtml = (0 until columnHeads.length).map { j =>
+ val content = cells.find(c => c._1 == i && c._2 == j).map(_._3).getOrElse(SText(" "))
+ s"${content.toHTML} "
+ }.mkString
+ s"${rh.toHTML} $cellsHtml "
+ }.mkString
+
+ s""
+ }
}
trait SText extends STeXSyntax {
- def ++(more: Seq[STeXSyntax]) = SSnippet(this+:more)
- def +(more: STeXSyntax) = if (more == null) this else SSnippet(List(this,more))
+ def ++(more: Seq[STeXSyntax]) = SSnippet(this +: more)
+ def +(more: STeXSyntax) = SSnippet(List(this, more))
}
case class SMath(expr: Expr) extends SText {
override def toString = "$" + expr.toSTeX + "$"
- //def toHTML = "" + expr.toHTML + " "
+ override def toHTML: String = s"\\(${expr.toSTeX}\\)"
}
case class SSnippet(body: Seq[STeXSyntax], sep: String = "") extends SText {
- override def toString = body.mkString(sep)
- def +(rest: SSnippet) = copy(body = this.body++rest.body)
+ override def toString = body.map(_.toString).mkString(sep)
+ // FIX: render each child with toHTML so SMath/SMacroApplication nodes
+ // produce MathJax/HTML output instead of raw LaTeX strings
+ override def toHTML: String = body.map(_.toHTML).mkString(sep)
}
case class SPlainText(body: String) extends SText {
override def toString = body
+ // FIX: handle macro replacements on plain text nodes directly,
+ // replacing the fragile toString+replace in the trait fallback
+ override def toHTML: String = body
+ .replace("\\uProb", "P")
+ .replace("\\intmax", "max")
+ .replace("\\intmin", "min")
+ .replace("\\intlessthan", " < ")
+ .replace("\\intgreatthan", " > ")
+ .replace("\\intleq", " ≤ ")
+ .replace("\\intgeq", " ≥ ")
+ .replace("\\intdivisible", " | ") // FIX: added missing \intdivisible
+ .replace("\\nequals", " ≠ ")
+ .replace("\\inset", " ∈ ")
+ .replace("\\range", " ")
+ .replace("\\\\", " ")
+ .replace("\\hline", "")
}
-case class SMacroApplication(name: String, args: Seq[SText], flexary: Boolean) extends SText {
- override def toString = {
- val command = "\\" + name
- val argsX = args.map(_.toString)
- val argsS = if (flexary) argsX.mkString("{",",","}")
- else argsX.map(s => "{" + s + "}").mkString("")
- command + argsS
+case class SMacroApplication(name: String, args: Seq[SText], flexary: Boolean)
+ extends SText {
+
+ override def toHTML: String = {
+ val a = args.map(_.toHTML)
+ // FIX: match on name.toLowerCase and also strip any leading backslash,
+ // because the parser sometimes stores "uProb" and sometimes "\uProb"
+ val n = name.stripPrefix("\\").toLowerCase
+ n match {
+ // Probability
+ case "uprob" => if (a.isEmpty) "P" else "P(" + a.mkString(", ") + ")"
+ // Arithmetic functions
+ case "intmax" => if (a.isEmpty) "max" else "max(" + a.mkString(", ") + ")"
+ case "intmin" => if (a.isEmpty) "min" else "min(" + a.mkString(", ") + ")"
+ case "intplus" => a.mkString(" + ")
+ case "intminus" => a.mkString(" - ")
+ case "inttimes" => a.mkString(" · ")
+ case "intdiv" => a.mkString(" / ")
+ // FIX: added intdivisible — shown red in screenshot as unknown macro
+ case "intdivisible" => if (a.length >= 2) a(0) + " | " + a(1)
+ else a.mkString(" | ")
+ // Relational operators
+ case "intlessthan" => a.mkString(" < ")
+ case "intgreatthan" => a.mkString(" > ")
+ case "intleq" => a.mkString(" ≤ ")
+ case "intgeq" => a.mkString(" ≥ ")
+ case "nequals" => a.mkString(" ≠ ")
+ case "equals" => a.mkString(" = ")
+ case "inset" => a.mkString(" ∈ ")
+ // Constructors
+ case "tup" => "(" + a.mkString(", ") + ")"
+ case "set" => "{" + a.mkString(", ") + "}"
+ case "apply" => a.headOption.map(h => h + "(" + a.drop(1).mkString(", ") + ")").getOrElse("")
+ // FIX: unknown macros — wrap the LaTeX toString in \(...\) for MathJax
+ // so at least the LaTeX is rendered rather than shown as red raw text
+ case _ => "\\(" + this.toString + "\\)"
+ }
}
-}
-class SMacro(name: String) {
- def apply(args: SText*) = SMacroApplication(name, args.toList, false)
+ override def toString: String = {
+ val a = args.map(_.toString)
+ val n = name.stripPrefix("\\").toLowerCase
+ n match {
+ case "tup" => "(" + a.mkString(", ") + ")"
+ case "set" => "\\{ " + a.mkString(", ") + " \\}"
+ case "apply" => a.headOption.map(h => h + "(" + a.drop(1).mkString(", ") + ")").getOrElse("")
+ case "equals" => a.mkString(" = ")
+ case "intplus" => a.mkString(" + ")
+ case "intminus" => a.mkString(" - ")
+ case "inttimes" => a.mkString(" · ")
+ case "intdiv" => a.mkString(" / ")
+ case "intlessthan" => a.mkString(" < ")
+ case "intgreatthan" => a.mkString(" > ")
+ case "intleq" => a.mkString(" ≤ ")
+ case "intgeq" => a.mkString(" ≥ ")
+ case "nequals" => a.mkString(" ≠ ")
+ case "inset" => a.mkString(" ∈ ")
+ case "intdivisible" => if (a.length >= 2) a(0) + " | " + a(1) else a.mkString(" | ")
+ case "uprob" => if (a.isEmpty) "P" else "P(" + a.mkString(", ") + ")"
+ case "intmax" => if (a.isEmpty) "max" else "max(" + a.mkString(", ") + ")"
+ case "intmin" => if (a.isEmpty) "min" else "min(" + a.mkString(", ") + ")"
+ case _ => "\\" + name + a.map("{" + _ + "}").mkString
+ }
+ }
}
object SText {
implicit class STextInterpolator(sc: StringContext) {
def x(args: Any*): SText = {
- var partsS = sc.parts.toList.map {s =>
- val sR = s.replace('§', '$').replace("\\n","\n")
- SPlainText(sR)
- }
+ var partsS = sc.parts.toList.map(s => SPlainText(s.replace('§', '$').replace("\\n", "\n")))
var snippets: List[STeXSyntax] = List(partsS.head)
partsS = partsS.tail
- val argsS = args.toList.foreach {arg =>
+ args.foreach { arg =>
val argS = arg match {
- case s: String => apply(s)
- case f: Form => SMath(f)
- case a => Expr.fromAnyO(a) match {
- case Some(e) => SMath(e)
- case None => SPlainText(a.toString)
- }
+ case s: String => SPlainText(s)
+ case f: Form => SMath(f)
+ case other => SPlainText(other.toString)
}
snippets ::= argS
snippets ::= partsS.head
@@ -148,7 +283,6 @@ object SText {
SSnippet(snippets.reverse)
}
}
-
def apply(args: STeXSyntax*): SText = SSnippet(args.toList)
def apply(s: String): SText = SPlainText(s)
-}
+}
\ No newline at end of file
diff --git a/src/info.kwarc.probgen/webserver.scala b/src/info.kwarc.probgen/webserver.scala
new file mode 100644
index 0000000..1e967a9
--- /dev/null
+++ b/src/info.kwarc.probgen/webserver.scala
@@ -0,0 +1,171 @@
+package info.kwarc.probgen
+
+// Generators
+import info.kwarc.probgen.MDPGenerator
+import info.kwarc.probgen.BasicProbabilityProblemGenerator
+import info.kwarc.probgen.SearchProblemGenerator
+
+import com.sun.net.httpserver.HttpServer
+import java.net.InetSocketAddress
+import java.io.OutputStream
+
+object WebServer {
+
+
+ // GENERATE 3 PROBLEMS
+
+ def generateProblems(): String = {
+
+ val sb = new StringBuilder
+
+ // ----------- MDP -----------
+ val mdp = MDPGenerator.make()
+ val mdpHtml = mdp.toSTeX(mdp.chooseSubproblems()).toHTML
+
+ sb.append(
+ s"""
+
+
MDP Problem
+
$mdpHtml
+
+ """
+ )
+
+ // ----------- PROBABILITY -----------
+ val prob = BasicProbabilityProblemGenerator.make()
+ val probHtml = prob.toSTeX(prob.chooseSubproblems()).toHTML
+
+ sb.append(
+ s"""
+
+
Probability Problem
+
$probHtml
+
+ """
+ )
+
+ // ----------- SEARCH -----------
+ val search = SearchProblemGenerator.make()
+ val searchHtml = search.toSTeX(search.chooseSubproblems()).toHTML
+
+ sb.append(
+ s"""
+
+
Search Problem
+
$searchHtml
+
+ """
+ )
+
+ sb.toString()
+ }
+
+
+ // MAIN SERVER
+
+ def main(args: Array[String]): Unit = {
+
+ val server = HttpServer.create(new InetSocketAddress(8080), 0)
+
+ server.createContext("/", exchange => {
+
+ val content = generateProblems()
+
+ // FIX: MathJax config — the original ['$$','$$'] used double-dollar as inline
+ // delimiter which is non-standard and clashes with display math.
+ // MathJax 3 recognises \(...\) for inline by default, but ONLY if the tex
+ // config does not override inlineMath without including it.
+ // We explicitly list both ['$','$'] and ['\\(','\\)'] so all output from
+ // SMath.toHTML (which emits \(...\)) is processed correctly.
+ // Note: in a Scala string '\\\\(' becomes '\\(' in the HTML/JS source,
+ // which is what the browser JS engine sees as the two-char sequence \( .
+ val html =
+ s"""
+
+
+
+ProbGen
+
+
+
+
+
+
+
+
+
+
+ProbGen: Practice Problems
+
+Generate New Problems
+
+
+
+
+$content
+
+
+
+
+"""
+
+ val bytes = html.getBytes("UTF-8")
+
+ exchange.getResponseHeaders.add("Content-Type", "text/html; charset=UTF-8")
+
+ exchange.sendResponseHeaders(200, bytes.length)
+
+ val os: OutputStream = exchange.getResponseBody
+ os.write(bytes)
+ os.close()
+ })
+
+ server.start()
+
+ println("Server running at http://localhost:8080")
+ }
+}
\ No newline at end of file