From fcbd8851101db9f5e4c9d4c681f28166d239e394 Mon Sep 17 00:00:00 2001 From: mostafacontrols Date: Sat, 2 May 2026 13:15:54 +0200 Subject: [PATCH 1/2] Added webserver and fixed stex.scala issues --- src/info.kwarc.probgen/stex.scala | 258 ++++++++++++++++++------- src/info.kwarc.probgen/webserver.scala | 162 ++++++++++++++++ 2 files changed, 349 insertions(+), 71 deletions(-) create mode 100644 src/info.kwarc.probgen/webserver.scala diff --git a/src/info.kwarc.probgen/stex.scala b/src/info.kwarc.probgen/stex.scala index 4c56d50..a1c7be1 100644 --- a/src/info.kwarc.probgen/stex.scala +++ b/src/info.kwarc.probgen/stex.scala @@ -1,154 +1,270 @@ 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 */ +/** + * 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 + */ 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 { + + // ---------------- PROBLEM ---------------- + case SProblem(intro, subs) => + s""" +
+

${intro.toHTML}

+ ${subs.map(_.toHTML).mkString("\n")} +
+ """ + + // ---------------- SUBPROBLEM ---------------- + case SSubproblem(pts, question, _) => + s""" +
+ [$pts pts]
+ ${question.toHTML} +
+ """ + + // ---------------- SOLUTION ---------------- + case SSolution(_, _) => + "" + + // ---------------- ITEMIZE ---------------- + case SItemize(items @ _*) => + "" + + // ---------------- ENUMERATE ---------------- + case SEnumerate(items @ _*) => + "
    " + items.map(i => s"
  1. ${i.toHTML}
  2. ").mkString("\n") + "
" + + // ---------------- ITEM ---------------- + case SItem(body) => + s"
  • ${body.toHTML}
  • " + + // ---------------- CENTER ---------------- + case SCenter(body) => + "
    " + + body.map(_.toHTML).mkString("
    ") + + "
    " + + // ---------------- TABULAR ---------------- + case t: STabular => + s"
    ${t.toString}
    " + + // ---------------- ENVIRONMENT SAFETY ---------------- + case env: SEnvironment => + env.body.map(_.toHTML).mkString("\n") + + // ---------------- MATH ---------------- + case t: SText => + t.toString + + 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("[", ", ", "]") +} + +/** + * Base environment + */ 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}" - } + + // IMPORTANT: keep structure internally only + override def toString = body.map(_.toString).mkString("\n") } case class SDocument(body: List[SFragment]) extends SEnvironment("document", 4) { - def toStringFull = { + def toStringFull = """\documentclass{article} |\usepackage{stexlight} |""".stripMargin + toString - } } object SDocument { - def apply(t: String, p: SProblem): SDocument = SDocument(List(SFragment(t, List(p)))) + def apply(t: String, p: SProblem): SDocument = + SDocument(List(SFragment(t, List(p)))) } -case class SFragment(title: String, body: List[SProblem]) extends SEnvironment("sfragment", 3) { +case class SFragment(title: String, body: List[SProblem]) + extends SEnvironment("sfragment", 3) { + override def args = List(title) } -case class SProblem(intro: STeXSyntax, subproblems: List[SSubproblem]) extends SEnvironment("sproblem", 2) { - def body = intro::subproblems +case class SProblem(intro: STeXSyntax, subproblems: List[SSubproblem]) + extends SEnvironment("sproblem", 2) { + + def body = intro :: subproblems } -case class SSubproblem(pts: Int, question: SText, solution: SSolution) extends SEnvironment("subproblem", 1) { +case class SSubproblem(pts: Int, question: SText, solution: SSolution) + extends SEnvironment("subproblem", 1) { + override def params = SParams("pts" -> pts.toString) def body = List(question, solution) } -case class SSolution(testspace: Float, body: List[SText]) extends SEnvironment("solution") { - override def params = SParams("testspace" -> (testspace.toString + "cm")) +case class SSolution(testspace: Float, body: List[SText]) + extends SEnvironment("solution") { + + override def params = + SParams("testspace" -> (testspace.toString + "cm")) } +/** + * Lists + */ 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) + +/** + * Item (FIXED: no LaTeX leakage) + */ 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) - } + +/** + * Tabular + */ +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 } } +/** + * TEXT BASE + */ 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 + "" } 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) } case class SPlainText(body: String) extends SText { override def toString = body } -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 - } -} +/** + * MACROS (SAFE RENDERING) + */ +case class SMacroApplication(name: String, args: Seq[SText], flexary: Boolean) + extends SText { + + override def toString: String = { + + val a = args.map(_.toString) + + name match { -class SMacro(name: String) { - def apply(args: SText*) = SMacroApplication(name, args.toList, false) + 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 "intlethan" | "intlessthan" => + a.mkString(" < ") + case "intgreaterthan" => + a.mkString(" > ") + + case _ => + "\\" + name + a.map("{" + _ + "}").mkString + } + } } +/** + * INTERPOLATION + */ 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 partsS = partsS.tail } + 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..20e2d4b --- /dev/null +++ b/src/info.kwarc.probgen/webserver.scala @@ -0,0 +1,162 @@ +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() + + val html = + s""" + + + +ProbGen + + + + + + + + + + +

    ProbGen: Practice 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 From 3026c3576cdc851a5e681ea6348499fd9c4afe87 Mon Sep 17 00:00:00 2001 From: mostafacontrols Date: Sun, 10 May 2026 13:59:06 +0200 Subject: [PATCH 2/2] Fixed all HTML issues like tables, maths, etc. --- src/info.kwarc.probgen/stex.scala | 290 +++++++++++++------------ src/info.kwarc.probgen/webserver.scala | 19 +- 2 files changed, 168 insertions(+), 141 deletions(-) diff --git a/src/info.kwarc.probgen/stex.scala b/src/info.kwarc.probgen/stex.scala index a1c7be1..60872d3 100644 --- a/src/info.kwarc.probgen/stex.scala +++ b/src/info.kwarc.probgen/stex.scala @@ -1,77 +1,67 @@ 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. + * Final fixed Scala binders for stex syntax. + * Corrects pattern matching priority and adds missing macro translations. */ -/** - * parent type of all stex syntax - */ trait STeXSyntax { def toHTML: String = this match { - // ---------------- PROBLEM ---------------- + // 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"$headerHtml$rowsHtml
    " + + // 2. STRUCTURAL ELEMENTS case SProblem(intro, subs) => - s""" -
    -

    ${intro.toHTML}

    - ${subs.map(_.toHTML).mkString("\n")} -
    - """ - - // ---------------- SUBPROBLEM ---------------- + s"""

    ${intro.toHTML}

    ${subs.map(_.toHTML).mkString("\n")}
    """ + case SSubproblem(pts, question, _) => - s""" -
    - [$pts pts]
    - ${question.toHTML} -
    - """ - - // ---------------- SOLUTION ---------------- - case SSolution(_, _) => - "" - - // ---------------- ITEMIZE ---------------- - case SItemize(items @ _*) => - "" - - // ---------------- ENUMERATE ---------------- - case SEnumerate(items @ _*) => - "
      " + items.map(i => s"
    1. ${i.toHTML}
    2. ").mkString("\n") + "
    " - - // ---------------- ITEM ---------------- - case SItem(body) => - s"
  • ${body.toHTML}
  • " - - // ---------------- CENTER ---------------- - case SCenter(body) => - "
    " + - body.map(_.toHTML).mkString("
    ") + - "
    " - - // ---------------- TABULAR ---------------- - case t: STabular => - s"
    ${t.toString}
    " + s"""
    [$pts pts]
    ${question.toHTML}
    """ - // ---------------- ENVIRONMENT SAFETY ---------------- - case env: SEnvironment => - env.body.map(_.toHTML).mkString("\n") + // 3. MATH & MACROS + case m: SMacroApplication => m.toHTML + case m: SMath => m.toHTML - // ---------------- MATH ---------------- + // 4. TEXT FALLBACK (Fixes raw strings seen in screenshot) case t: SText => t.toString - - case other => - other.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 @ _*) => "" + case SEnumerate(items @ _*) => "
      " + items.map(i => s"
    1. ${i.toHTML}
    2. ").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 } } @@ -83,59 +73,38 @@ case class SParams(pars: (String, String)*) { else pars.map { case (k, v) => s"$k={$v}" }.mkString("[", ", ", "]") } -/** - * Base environment - */ abstract class SEnvironment(name: String, level: Int = 0) extends STeXSyntax { def args: List[String] = Nil def params: SParams = SParams() def body: Seq[STeXSyntax] - - // IMPORTANT: keep structure internally only 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 { - def apply(t: String, p: SProblem): SDocument = - SDocument(List(SFragment(t, List(p)))) + def apply(t: String, p: SProblem): SDocument = SDocument(List(SFragment(t, List(p)))) } -case class SFragment(title: String, body: List[SProblem]) - extends SEnvironment("sfragment", 3) { - +case class SFragment(title: String, body: List[SProblem]) extends SEnvironment("sfragment", 3) { override def args = List(title) } -case class SProblem(intro: STeXSyntax, subproblems: List[SSubproblem]) - extends SEnvironment("sproblem", 2) { - +case class SProblem(intro: STeXSyntax, subproblems: List[SSubproblem]) extends SEnvironment("sproblem", 2) { def body = intro :: subproblems } -case class SSubproblem(pts: Int, question: SText, solution: SSolution) - extends SEnvironment("subproblem", 1) { - +case class SSubproblem(pts: Int, question: SText, solution: SSolution) extends SEnvironment("subproblem", 1) { override def params = SParams("pts" -> pts.toString) def body = List(question, solution) } -case class SSolution(testspace: Float, body: List[SText]) - extends SEnvironment("solution") { - - override def params = - SParams("testspace" -> (testspace.toString + "cm")) +case class SSolution(testspace: Float, body: List[SText]) extends SEnvironment("solution") { + override def params = SParams("testspace" -> (testspace.toString + "cm")) } -/** - * Lists - */ abstract class SList(n: String, items: List[SText]) extends SEnvironment(n) { def body = items.map(SItem(_)) } @@ -143,18 +112,12 @@ abstract class SList(n: String, items: List[SText]) extends SEnvironment(n) { case class SItemize(items: SText*) extends SList("itemize", items.toList) case class SEnumerate(items: SText*) extends SList("enumerate", items.toList) -/** - * Item (FIXED: no LaTeX leakage) - */ case class SItem(body: SText) extends STeXSyntax { override def toString = body.toString } case class SCenter(body: Seq[STeXSyntax]) extends SEnvironment("center") -/** - * Tabular - */ case class STabular( cellHead: SText, columnHeads: Seq[SText], @@ -165,26 +128,39 @@ case class STabular( def makeRow(cs: Seq[SText]): SText = SSnippet(cs.flatMap(c => Seq(c, SText(" & "))).dropRight(1) :+ SText("\\\\")) - override def args = - List("l|" + ("c" * columnHeads.length)) + 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 = (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"$headerHtml$rowsHtml
    " + } } -/** - * TEXT BASE - */ trait SText extends STeXSyntax { def ++(more: Seq[STeXSyntax]) = SSnippet(this +: more) def +(more: STeXSyntax) = SSnippet(List(this, more)) @@ -192,79 +168,121 @@ trait SText extends STeXSyntax { case class SMath(expr: Expr) extends SText { override def toString = "$" + expr.toSTeX + "$" + override def toHTML: String = s"\\(${expr.toSTeX}\\)" } case class SSnippet(body: Seq[STeXSyntax], sep: String = "") extends SText { 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", "") } -/** - * MACROS (SAFE RENDERING) - */ case class SMacroApplication(name: String, args: Seq[SText], flexary: Boolean) extends SText { - override def toString: String = { + 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 + "\\)" + } + } + override def toString: String = { val a = args.map(_.toString) - - name 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 "intlethan" | "intlessthan" => - a.mkString(" < ") - case "intgreaterthan" => - a.mkString(" > ") - - case _ => - "\\" + name + a.map("{" + _ + "}").mkString + 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 } } } -/** - * INTERPOLATION - */ object SText { - implicit class STextInterpolator(sc: StringContext) { - def x(args: Any*): SText = { - - var partsS = sc.parts.toList.map(s => - SPlainText(s.replace('§', '$').replace("\\n", "\n")) - ) - + var partsS = sc.parts.toList.map(s => SPlainText(s.replace('§', '$').replace("\\n", "\n"))) var snippets: List[STeXSyntax] = List(partsS.head) partsS = partsS.tail - args.foreach { arg => val argS = arg match { case s: String => SPlainText(s) case f: Form => SMath(f) case other => SPlainText(other.toString) } - snippets ::= argS snippets ::= partsS.head partsS = partsS.tail } - 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 index 20e2d4b..1e967a9 100644 --- a/src/info.kwarc.probgen/webserver.scala +++ b/src/info.kwarc.probgen/webserver.scala @@ -11,9 +11,9 @@ import java.io.OutputStream object WebServer { - + // GENERATE 3 PROBLEMS - + def generateProblems(): String = { val sb = new StringBuilder @@ -60,9 +60,9 @@ object WebServer { sb.toString() } - + // MAIN SERVER - + def main(args: Array[String]): Unit = { val server = HttpServer.create(new InetSocketAddress(8080), 0) @@ -71,6 +71,14 @@ object WebServer { 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""" @@ -81,7 +89,7 @@ object WebServer { @@ -148,6 +156,7 @@ $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