Skip to content

Commit f4b4065

Browse files
committed
autotranslate/scratch: phase-2 REPL compile gate (#951)
Extends the gate with the REPL-transcript check BR deferred to phase 2. For each rewritten, non-\ifswedish REPL/REPLnonum/REPLsmall transcript: split on `scala>` prompts (prompt-stripped input + indented continuations; output lines — result echoes `val x: T = …`, `resN`, error `|`/`-- Error`, blanks — dropped), concatenate the inputs into one `object` body and compile the Swedish vs rendered-English programs under the phase-1 regression rule (regression = SV compiles, EN doesn't; skip if both fail). Line classification is identical for the SV and EN transcripts (parallel structure), so a mis-split can only SKIP — never cause a false regression. Strips a leading [numbers=none] optional arg; clamp-aware + changed-only + per-file overrides as phase 1. `--list` now also prints the phase-2 OK/skip breakdown. Tested: an injected Tomat->Tomato duplicate-class transcript is caught (exit 1), a self-contained Gurka->Cucumber transcript compiles (non-vacuous), a clamped copy is skipped. Clean run: phase 2 = 12 ok, 85 skipped (many are output-only blocks with no scala> input), 0 regressions.
1 parent 96538e1 commit f4b4065

1 file changed

Lines changed: 62 additions & 3 deletions

File tree

autotranslate/scratch/verify-mirror-examples.scala

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
// to Swedish, and a NEW sound added to a .tex but not to codeStr is caught.
2222
//
2323
// Plus a PHASE-1 INLINE .tex COMPILE GATE (#951): the same regression rule applied to the display Scala-code
24-
// envs (Code/CodeSmall/lstlisting) the mirror rewrites in .tex — skipping \ifswedish-clamped code and deferring
25-
// REPL transcripts to phase 2. See that section below.
24+
// envs (Code/CodeSmall/lstlisting) the mirror rewrites in .tex — skipping \ifswedish-clamped code. And a
25+
// PHASE-2 REPL GATE: REPL* transcripts split on `scala>` prompts, inputs concatenated into an object and
26+
// compiled under the same regression rule (SV/EN classification is symmetric, so a mis-split only skips).
27+
// See those sections below.
2628
//
2729
// scala-cli run autotranslate/scratch/verify-mirror-examples.scala -- <introprog-root>
2830
// (exit 0 = clean, exit 1 = at least one regression or an untranslated ratified code-string)
@@ -123,4 +125,61 @@
123125
println(s"--- phase-1 SKIPPED (${inSkip.size}) — rewritten inline envs that compile in NEITHER language (not standalone) ---")
124126
inSkip.foreach(s => println(s" skip $s"))
125127

126-
if regressions.nonEmpty || leaks.nonEmpty || inRegressions.nonEmpty then sys.exit(1)
128+
// ---- PHASE-2 INLINE REPL COMPILE GATE (#951) ----
129+
// REPL transcripts: split on `scala>` prompts (prompt-stripped input + its indented continuation lines; OUTPUT
130+
// lines — result echoes `val x: T = …`, `resN`, error `|`/`-- Error`, blanks — are dropped), concatenate a
131+
// transcript's inputs into one `object` body and compile SV vs EN with the phase-1 regression rule. Line
132+
// classification is IDENTICAL for the SV and EN transcripts (they have parallel structure), so a mis-split can
133+
// only SKIP a transcript — never cause a false regression. Many transcripts reference defs from a neighbouring
134+
// Code env and so compile in neither language (self-skip). Clamp-aware + changed-only + per-file overrides.
135+
val replEnvs = Set("REPL", "REPLnonum", "REPLsmall")
136+
val promptLineRe = raw"^[ \t]*scala>[ ]?(.*)".r
137+
val replOutRe = raw"^[ \t]*(val res\d|res\d+:|[|]|\d+ *[|]|-- (Error|Warning)|<console>|\^).*".r
138+
def replProgram(bodyStr: String): String =
139+
val prog = collection.mutable.ArrayBuffer[String]()
140+
var inInput = false
141+
for line <- bodyStr.split("\n", -1) do
142+
promptLineRe.findFirstMatchIn(line) match
143+
case Some(pm) => prog += pm.group(1); inInput = true // `scala> …` input line (prompt stripped)
144+
case None =>
145+
val t = line.trim
146+
val isOutput = t.isEmpty || replOutRe.findFirstMatchIn(line).isDefined ||
147+
t.matches("(val|var)\\s+\\w+:.*=.*") // result-echo `val x: T = …`
148+
if inInput && !isOutput then prog += line // continuation of a multi-line input
149+
else inInput = false
150+
prog.mkString("\n")
151+
def wrapObj(prog: String): String =
152+
"object ReplWrap:\n" + prog.split("\n", -1).map(" " + _).mkString("\n")
153+
var rpChecked = 0; var rpSkipped = 0
154+
val rpRegressions = collection.mutable.ArrayBuffer[String]()
155+
val rpOk = collection.mutable.ArrayBuffer[String]() // file:line of each OK transcript (for --list)
156+
val rpSkip = collection.mutable.ArrayBuffer[String]() // file:line of each skipped transcript (for --list)
157+
for f <- texFiles do
158+
val tex = os.read(f)
159+
val rel = f.relativeTo(root).toString
160+
val extraId = CodeGlossary.overridesFor(rel)
161+
if !CodeGlossary.isOptedOut(rel) then
162+
val clampRanges = Latex.ifswedishRanges(tex)
163+
def clamped(pos: Int): Boolean = clampRanges.exists((a, b) => pos >= a && pos < b)
164+
for m <- envRe.findAllMatchIn(tex) if replEnvs(m.group(1)) && !clamped(m.start) do
165+
val body = stripLstOpt(m.group(2)) // strip a leading [numbers=none] optional arg
166+
val en = CodeGlossary.renderCodeIds(body, extraId)
167+
if en != body then
168+
val loc = s"$rel:${lineOf(tex, m.start)}"
169+
val enProg = replProgram(en)
170+
if enProg.trim.isEmpty then { rpSkipped += 1; rpSkip += s"$loc (no scala> input)" }
171+
else if compiles(wrapObj(enProg), "ReplWrap.scala") then { rpChecked += 1; rpOk += loc }
172+
else if compiles(wrapObj(replProgram(body)), "ReplWrap.scala") then
173+
rpRegressions += loc
174+
println(s" REPL REGRESSION: $loc — transcript compiles in Swedish but NOT after rename")
175+
else { rpSkipped += 1; rpSkip += loc }
176+
println(s"\n=== inline .tex REPL compile gate (phase 2): $rpChecked ok, $rpSkipped skipped (not standalone), ${rpRegressions.size} REGRESSIONS ===")
177+
if rpRegressions.nonEmpty then println("FAIL — REPL translation broke compilation in: " + rpRegressions.mkString(", "))
178+
else println("PASS — every rewritten, self-contained REPL transcript still compiles.")
179+
if listMode then
180+
println(s"\n--- phase-2 OK (${rpOk.size}) — rewritten REPL transcripts that compile after rename ---")
181+
rpOk.foreach(s => println(s" ok $s"))
182+
println(s"--- phase-2 SKIPPED (${rpSkip.size}) — REPL transcripts that compile in NEITHER language / no input ---")
183+
rpSkip.foreach(s => println(s" skip $s"))
184+
185+
if regressions.nonEmpty || leaks.nonEmpty || inRegressions.nonEmpty || rpRegressions.nonEmpty then sys.exit(1)

0 commit comments

Comments
 (0)