Skip to content

Commit fea824a

Browse files
dvdvgtjiribenes
andauthored
Fix: Make case studies runnable on the website (#1009)
Currently, the case studies are not runnable on the website or resort to the trick of having an empty REPL where you have to input `main()`. In this PR, I try to properly handle `effekt:repl` in literate Effekt files such that it has the same (observable) behaviour as on the website. Since expressions can not be top-level, I wrap them in a function `def replN() = <REPL-CONTENT>` and later append a main function where all repl cells are called: ``` def repl1() = 1 + 2 def repl2() = println("hello") def main() = { inspect(repl1()) inspect(repl2()) () } ``` If there's already a user-defined `main` function, then an error occurs. Though, I argue that the user should not define a `main` function in a literate Effekt file, as this kind of defeats its purpose. Furthermore, I also tackle the issue of importing non-stdlib modules by just merging all files in the dependency chain into the same file and adding appropriate redirects for the old links. Changes: - You may now either have one or more `effekt:repl` code fences or one manually defined `main` function - A repl cell is translated such that it is wrapped in a function: ``` def replN = { stmt } ``` corresponds to ```` ```effekt:repl stmt ``` ```` where `def replN = {` is inserted for the opening fence and `}` for the closing fence to preserve positional information. - You may now have other languages in a literate effekt file other than Effekt, e.g. ```` ```scala ```` gives you syntax highlighting but is ignored by the effekt compiler - Similarly, `effekt:ignore` code fences are also ignored by the compiler - Previously, `[[()]] = #f` in chez. Now, `[[()]] = #<void>`, reasone being that `inspect(inspect(())) = inspect(())` should hold but with `[[()]] = #f` it is not since `inspect` returns `#<void>`. - `anf`, `lexer`, `pretty` and `parser` are concatenated into a single file to solve import issues. --------- Co-authored-by: Jiří Beneš <[email protected]>
1 parent 2aedbc6 commit fea824a

30 files changed

+2111
-441
lines changed

effekt/jvm/src/test/scala/effekt/ChezSchemeTests.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,20 @@ import scala.language.implicitConversions
99

1010
abstract class ChezSchemeTests extends EffektTests {
1111

12-
override def positives: List[File] = List(
12+
override def positives: Set[File] = Set(
1313
examplesDir / "pos",
1414
examplesDir / "casestudies",
1515
examplesDir / "chez",
1616
examplesDir / "benchmarks"
1717
)
1818

1919
// Test files which are to be ignored (since features are missing or known bugs exist)
20-
override def ignored: List[File] = List(
20+
override def ignored: Set[File] = super.ignored ++ Set(
2121

2222
examplesDir / "llvm",
2323

24+
examplesDir / "casestudies" / "smc.effekt.md",
25+
2426
// bidirectional effects are not yet supported in our Chez backend
2527
examplesDir / "pos" / "maps.effekt",
2628
examplesDir / "pos" / "bidirectional",
@@ -49,6 +51,7 @@ abstract class ChezSchemeTests extends EffektTests {
4951
// in the CallCC variant, we cannot have toplevel vals at the moment (their bindings need to be wrapped in `(run (thunk ...))`
5052
// see comment on commit 61492d9
5153
examplesDir / "casestudies" / "anf.effekt.md",
54+
examplesDir / "casestudies" / "frontend.effekt.md",
5255

5356
// we do not need to run the negative tests for the other backends
5457
examplesDir / "neg",

effekt/jvm/src/test/scala/effekt/EffektTests.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,17 @@ trait EffektTests extends munit.FunSuite {
2929
def examplesDir = new File("examples")
3030

3131
// Test files which are to be ignored (since features are missing or known bugs exist)
32-
def ignored: List[File] = List()
32+
def ignored: Set[File] = Set(
33+
examplesDir / "casestudies" / "smc.effekt.md"
34+
)
3335

3436
// Folders to discover and run tests in
35-
def positives: List[File] = List()
37+
def positives: Set[File] = Set()
3638

37-
def negatives: List[File] = List()
39+
def negatives: Set[File] = Set()
3840

3941
// Test files that should be run with optimizations disabled
40-
def withoutOptimizations: List[File] = List()
42+
def withoutOptimizations: Set[File] = Set()
4143

4244
def runTestFor(input: File, expected: String): Unit =
4345
test(input.getPath + s" (${backendName})") {

effekt/jvm/src/test/scala/effekt/JavaScriptTests.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@ class JavaScriptTests extends EffektTests {
1414

1515
def backendName = "js"
1616

17-
override def positives: List[File] = List(
17+
override def positives: Set[File] = Set(
1818
examplesDir / "pos",
1919
examplesDir / "casestudies",
2020
examplesDir / "benchmarks",
2121
)
2222

23-
override def negatives: List[File] = List(
23+
override def negatives: Set[File] = Set(
2424
examplesDir / "neg"
2525
)
2626

27-
override lazy val withoutOptimizations: List[File] = List(
27+
override lazy val withoutOptimizations: Set[File] = Set(
2828
// contifying under reset
2929
examplesDir / "pos" / "issue842.effekt",
3030
examplesDir / "pos" / "issue861.effekt",
@@ -34,9 +34,11 @@ class JavaScriptTests extends EffektTests {
3434
examplesDir / "pos" / "probabilistic.effekt",
3535
)
3636

37-
override def ignored: List[File] = List(
37+
override def ignored: Set[File] = super.ignored ++ Set(
3838
// unsafe cont
39-
examplesDir / "pos" / "propagators.effekt"
39+
examplesDir / "pos" / "propagators.effekt",
40+
41+
examplesDir / "casestudies" / "smc.effekt.md"
4042
)
4143
}
4244

effekt/jvm/src/test/scala/effekt/LLVMTests.scala

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class LLVMTests extends EffektTests {
1313
override def valgrind = sys.env.get("EFFEKT_VALGRIND").nonEmpty
1414
override def debug = sys.env.get("EFFEKT_DEBUG").nonEmpty
1515

16-
override lazy val positives: List[File] = List(
16+
override lazy val positives: Set[File] = Set(
1717
examplesDir / "llvm",
1818
examplesDir / "pos",
1919
examplesDir / "benchmarks",
@@ -23,7 +23,7 @@ class LLVMTests extends EffektTests {
2323
* Documentation of currently failing tests because of missing features
2424
* and their reason
2525
*/
26-
lazy val missingFeatures: List[File] = List(
26+
lazy val missingFeatures: Set[File] = Set(
2727
// Regex
2828
examplesDir / "pos" / "simpleparser.effekt",
2929

@@ -33,6 +33,9 @@ class LLVMTests extends EffektTests {
3333
// unsafe cont
3434
examplesDir / "pos" / "propagators.effekt",
3535

36+
// no generic inspect
37+
examplesDir / "casestudies",
38+
3639
// Only JS (tests should be moved to a JS folder)
3740
examplesDir / "pos" / "genericcompare.effekt",
3841
examplesDir / "pos" / "multiline_extern_definition.effekt",
@@ -53,7 +56,7 @@ class LLVMTests extends EffektTests {
5356
examplesDir / "pos" / "issue733.effekt",
5457
)
5558

56-
override lazy val withoutOptimizations: List[File] = List(
59+
override lazy val withoutOptimizations: Set[File] = Set(
5760
// contifying under reset
5861
examplesDir / "pos" / "issue842.effekt",
5962
examplesDir / "pos" / "issue861.effekt",
@@ -74,14 +77,14 @@ class LLVMTests extends EffektTests {
7477
examplesDir / "pos" / "bidirectional" / "effectfulobject.effekt",
7578
)
7679

77-
override lazy val ignored: List[File] = missingFeatures ++ noValgrind(examplesDir)
80+
override lazy val ignored: Set[File] = missingFeatures ++ noValgrind(examplesDir) ++ super.ignored
7881
}
7982

8083
/**
8184
* Documentation of tests that succeed in running, but fail valgrind
8285
* and their reason
8386
*/
84-
def noValgrind(examplesDir: File): List[File] = List(
87+
def noValgrind(examplesDir: File): Set[File] = Set(
8588
examplesDir / "llvm" / "prompt-duplication.effekt",
8689
)
8790

@@ -91,5 +94,5 @@ class LLVMNoValgrindTests extends EffektTests {
9194
override def valgrind = false
9295
override def debug = false
9396

94-
override lazy val positives: List[File] = noValgrind(examplesDir)
97+
override lazy val positives: Set[File] = noValgrind(examplesDir)
9598
}

effekt/jvm/src/test/scala/effekt/StdlibTests.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@ import sbt.io.syntax.*
77
import scala.language.implicitConversions
88

99
abstract class StdlibTests extends EffektTests {
10-
override def positives: List[File] = List(
10+
override def positives: Set[File] = Set(
1111
examplesDir / "stdlib",
1212
)
1313

14-
override def ignored: List[File] = List()
14+
override def ignored: Set[File] = Set()
1515

16-
override def withoutOptimizations: List[File] = List()
16+
override def withoutOptimizations: Set[File] = Set()
1717
}
1818

1919
class StdlibJavaScriptTests extends StdlibTests {
2020
def backendName: String = "js"
2121

22-
override def withoutOptimizations: List[File] = List(
22+
override def withoutOptimizations: Set[File] = Set(
2323
examplesDir / "stdlib" / "acme.effekt",
2424
examplesDir / "stdlib" / "json.effekt",
2525
examplesDir / "stdlib" / "exception" / "combinators.effekt",
@@ -33,11 +33,11 @@ class StdlibJavaScriptTests extends StdlibTests {
3333
examplesDir / "stdlib" / "char" / "ascii_iswhitespace.effekt",
3434
)
3535

36-
override def ignored: List[File] = List()
36+
override def ignored: Set[File] = Set()
3737
}
3838

3939
abstract class StdlibChezTests extends StdlibTests {
40-
override def ignored: List[File] = List(
40+
override def ignored: Set[File] = Set(
4141
// Not implemented yet
4242
examplesDir / "stdlib" / "io",
4343
examplesDir / "stdlib" / "stream" / "characters.effekt",
@@ -56,11 +56,11 @@ class StdlibLLVMTests extends StdlibTests {
5656
override def valgrind = sys.env.get("EFFEKT_VALGRIND").nonEmpty
5757
override def debug = sys.env.get("EFFEKT_DEBUG").nonEmpty
5858

59-
override def withoutOptimizations: List[File] = List(
59+
override def withoutOptimizations: Set[File] = Set(
6060
examplesDir / "stdlib" / "acme.effekt",
6161
)
6262

63-
override def ignored: List[File] = List(
63+
override def ignored: Set[File] = Set(
6464
// String comparison using `<`, `<=`, `>`, `>=` is not implemented yet on LLVM
6565
examplesDir / "stdlib" / "string" / "compare.effekt",
6666

effekt/jvm/src/test/scala/effekt/core/VMTests.scala

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -647,8 +647,8 @@ class VMTests extends munit.FunSuite {
647647
dynamicDispatches = 57,
648648
patternMatches = 31,
649649
branches = 40,
650-
pushedFrames = 23,
651-
poppedFrames = 20,
650+
pushedFrames = 24,
651+
poppedFrames = 21,
652652
allocations = 15,
653653
closures = 36,
654654
variableReads = 7,
@@ -663,8 +663,8 @@ class VMTests extends munit.FunSuite {
663663
dynamicDispatches = 7,
664664
patternMatches = 80,
665665
branches = 41,
666-
pushedFrames = 56,
667-
poppedFrames = 57,
666+
pushedFrames = 57,
667+
poppedFrames = 58,
668668
allocations = 58,
669669
closures = 7,
670670
variableReads = 29,
@@ -679,8 +679,8 @@ class VMTests extends munit.FunSuite {
679679
dynamicDispatches = 18,
680680
patternMatches = 205,
681681
branches = 405,
682-
pushedFrames = 194,
683-
poppedFrames = 194,
682+
pushedFrames = 195,
683+
poppedFrames = 195,
684684
allocations = 109,
685685
closures = 27,
686686
variableReads = 164,
@@ -723,19 +723,19 @@ class VMTests extends munit.FunSuite {
723723
)),
724724

725725
examplesDir / "casestudies" / "inference.effekt.md" -> Some(Summary(
726-
staticDispatches = 1457454,
727-
dynamicDispatches = 3201452,
728-
patternMatches = 1474254,
729-
branches = 303298,
730-
pushedFrames = 2914407,
731-
poppedFrames = 2049112,
732-
allocations = 4625971,
733-
closures = 865541,
734-
variableReads = 2908620,
735-
variableWrites = 1453663,
736-
resets = 288559,
737-
shifts = 297723,
738-
resumes = 9275
726+
staticDispatches = 1482058,
727+
dynamicDispatches = 3224955,
728+
patternMatches = 1497935,
729+
branches = 304502,
730+
pushedFrames = 2943014,
731+
poppedFrames = 2077323,
732+
allocations = 4654126,
733+
closures = 867068,
734+
variableReads = 2955965,
735+
variableWrites = 1480868,
736+
resets = 288904,
737+
shifts = 298107,
738+
resumes = 9263
739739
)),
740740

741741
examplesDir / "pos" / "raytracer.effekt" -> Some(Summary(

effekt/shared/src/main/scala/effekt/generator/chez/Transformer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ trait Transformer {
210210
}
211211

212212
def toChez(expr: Expr): chez.Expr = expr match {
213-
case Literal((), _) => chez.RawValue("#f")
213+
case Literal((), _) => chez.RawValue("(void)")
214214
215215
case Literal(s: String, _) => escape(s)
216216
case Literal(b: Boolean, _) => if (b) chez.RawValue("#t") else chez.RawValue("#f")

effekt/shared/src/main/scala/effekt/util/Source.scala

Lines changed: 87 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package effekt
22
package util
33

4+
import scala.collection.mutable.ListBuffer
5+
46
import effekt.source.ModuleDecl
57

68
import kiama.util.Source
@@ -10,28 +12,97 @@ import kiama.util.Source
1012
*/
1113
case class MarkdownSource(source: Source) extends Source {
1214

13-
val fenceLine = """^```\s*(effekt)?\s*((\s|:)[^`]*)?$""".r
14-
val otherFenceLine = """^```[^`\n]*$""".r
15+
val fenceLine = """^```\s*(\w+)?\s*((?::[^:\n]*)*)$""".r
16+
val emptyFence = """^```\s*$""".r
17+
18+
enum FenceType {
19+
case Repl
20+
case Ignore
21+
case Code
22+
case Other
23+
}
1524

1625
def name = source.name
1726

1827
lazy val content = {
19-
var inCode = false
20-
var inOtherFence = false
28+
import FenceType.*
29+
var code = ListBuffer.empty[String]
30+
var replCounter = 0
31+
val lines = ListBuffer.empty[String]
32+
var fenceType: Option[FenceType] = None
2133
// we map non-code lines to empty lines to preserve line numbers
22-
val lines = source.content.linesIterator.map {
23-
case line if fenceLine.matches(line) && !inOtherFence =>
24-
inCode = !inCode
25-
""
26-
case line if otherFenceLine.matches(line) =>
27-
inOtherFence = !inOtherFence
28-
""
29-
case line if inCode =>
30-
line
31-
case _ =>
32-
""
34+
source.content.linesIterator.foreach {
35+
case fenceLine(lang, ots) =>
36+
val opts = if (ots != null) { ots.tail.split(":").toList } else { Nil }
37+
if (opts.contains("ignore")) {
38+
fenceType = Some(Ignore)
39+
lines += ""
40+
} else if (opts.contains("repl")) {
41+
fenceType = Some(Repl)
42+
// opening fences are replaced by a wrapper function
43+
lines += s"def repl${replCounter}() = {"
44+
replCounter += 1
45+
} else {
46+
fenceType match {
47+
// opening fences
48+
case None =>
49+
// ```effekt
50+
if (lang != null && lang == "effekt") {
51+
fenceType = Some(Code)
52+
// ```scala
53+
} else if (lang != null) {
54+
fenceType = Some(Other)
55+
// ```
56+
} else {
57+
fenceType = Some(Code)
58+
}
59+
lines += ""
60+
// closing fences
61+
case Some(fence) =>
62+
fence match {
63+
// closing fence of ```effekt:repl
64+
case Repl =>
65+
// take care when wrapping to preserve line numbers
66+
lines ++= code
67+
// remember to close wrapper function
68+
lines += "}"
69+
// closing fence of ```effekt or ```
70+
case Code =>
71+
lines ++= code
72+
lines += ""
73+
// closing fence of ```effekt:ignore
74+
case Ignore =>
75+
lines += ""
76+
// closing fence of ```scala or other languages
77+
// same semantic as effekt:ignore
78+
case Other =>
79+
lines += ""
80+
}
81+
fenceType = None
82+
code = ListBuffer.empty
83+
}
84+
}
85+
// collect code if in code fence
86+
case line if fenceType.isDefined && line != "" =>
87+
code += line
88+
// add empty lines to preserve positional information outside of code fences
89+
case line =>
90+
lines += ""
91+
}
92+
val prog = lines.mkString("\n")
93+
val repls = 0.until(replCounter).map { i => s"inspect(repl${i}())"}
94+
// We either allow to have one manually defined main function XOR one or more REPLs
95+
val main = if (replCounter > 0) {
96+
s"""
97+
|def main() = {
98+
|${repls.mkString("\n") }
99+
|()
100+
|}
101+
""".stripMargin
102+
} else {
103+
""
33104
}
34-
lines.mkString("\n")
105+
prog ++ main
35106
}
36107
}
37108

0 commit comments

Comments
 (0)