aboutsummaryrefslogtreecommitdiff
path: root/lab5/src/main/scala/jsy/util
diff options
context:
space:
mode:
authorJack Sangdahl <[email protected]>2024-12-12 21:03:45 -0700
committerJack Sangdahl <[email protected]>2024-12-12 21:03:45 -0700
commit8bb81e391405554d02ec36574db2ca58300c89c4 (patch)
tree2e6e16ea68cefc63466b4cc3a56352843ec6a92c /lab5/src/main/scala/jsy/util
parentae554ab7d4d425fa821c1167e734db3425a881f6 (diff)
downloadcsci3155-8bb81e391405554d02ec36574db2ca58300c89c4.tar.gz
csci3155-8bb81e391405554d02ec36574db2ca58300c89c4.zip
Unit 5
Signed-off-by: Jack Sangdahl <[email protected]>
Diffstat (limited to 'lab5/src/main/scala/jsy/util')
-rw-r--r--lab5/src/main/scala/jsy/util/DoWith.scala33
-rw-r--r--lab5/src/main/scala/jsy/util/JsyApplication.scala191
-rw-r--r--lab5/src/main/scala/jsy/util/Visitor.scala32
3 files changed, 256 insertions, 0 deletions
diff --git a/lab5/src/main/scala/jsy/util/DoWith.scala b/lab5/src/main/scala/jsy/util/DoWith.scala
new file mode 100644
index 0000000..08d2e3a
--- /dev/null
+++ b/lab5/src/main/scala/jsy/util/DoWith.scala
@@ -0,0 +1,33 @@
+package jsy.util
+
+/*
+ * DoWith is a data structure that holds a function that returns a result of
+ * type R with a input-output state of type W.
+ *
+ * Aside: This is also known as the State monad.
+ */
+sealed class DoWith[W,+R] private (doer: W => (W,R)) {
+ def apply(w: W) = doer(w)
+
+ def map[B](f: R => B): DoWith[W,B] = new DoWith[W,B]({
+ (w: W) => {
+ val (wp, r) = doer(w)
+ (wp, f(r))
+ }
+ })
+
+ def flatMap[B](f: R => DoWith[W,B]): DoWith[W,B] = new DoWith[W,B]({
+ (w: W) => {
+ val (wp, r) = doer(w)
+ f(r)(wp) // same as f(a).apply(s)
+ }
+ })
+}
+
+object DoWith {
+ def doget[W]: DoWith[W, W] = new DoWith[W, W]({ w => (w, w) })
+ def doput[W](w: W): DoWith[W, Unit] = new DoWith[W, Unit]({ _ => (w, ()) })
+ def doreturn[W, R](r: R): DoWith[W, R] = new DoWith[W, R]({ w => (w, r) })
+ def domodify[W](f: W => W): DoWith[W, Unit] = new DoWith[W, Unit]({ w => (f(w), ()) })
+}
+
diff --git a/lab5/src/main/scala/jsy/util/JsyApplication.scala b/lab5/src/main/scala/jsy/util/JsyApplication.scala
new file mode 100644
index 0000000..46b658d
--- /dev/null
+++ b/lab5/src/main/scala/jsy/util/JsyApplication.scala
@@ -0,0 +1,191 @@
+package jsy.util
+
+import java.io.{ByteArrayOutputStream, File}
+import java.nio.file.Files
+
+trait JsyApplication {
+ var debug = false /* set to false to disable debugging output */
+ var keepGoing = true /* set to true to keep going after exceptions */
+ var maxSteps: Option[Int] = None /* set to a number to bound the number of execution steps */
+
+ /*** Implement this method to process a file. ***/
+ def processFile(file: File): Unit
+
+ var anonOption = ("input",
+ { case filename :: Nil => new File(filename) }: PartialFunction[List[String], File],
+ "A file containing a JavaScripty program or a directory with .jsy files.")
+
+ var flagOptions = List(
+ ("debug", options.SetBool(b => debug = b, Some(b => debug == b)), "debugging"),
+ ("keep-going", options.SetBool(b => keepGoing = b, Some(b => keepGoing == b)), "keep going after exceptions"),
+ ("bound-steps", options.SetInt(
+ { i => maxSteps = i },
+ Some({
+ case true => maxSteps map { i => ": %d".format(i) }
+ case false => if (maxSteps == None) Some("") else None
+ })),
+ "bound for maximum number of execution steps before aborting")
+ )
+
+ def handle[T](default: T)(e: => T): T =
+ if (!keepGoing) e
+ else try e catch {
+ case exn: Throwable => println(exn.toString); default
+ }
+
+ def testJsy(file: File)(k: (File, File, => (Boolean, String)) => Unit): Unit = {
+ val jsyext = """[.]jsy$""".r
+ val ans: File = new File(jsyext.replaceAllIn(file.getPath, ".ans"))
+ k(file, ans, {
+ if (!ans.exists()) {
+ (false, s"Expected output file ${ans} does not exist.")
+ }
+ else {
+ val outstream = new ByteArrayOutputStream()
+ Console.withOut(outstream)(handle(()){ processFile(file)})
+
+ val encoding = java.nio.charset.StandardCharsets.UTF_8
+ val ansstring = new String(Files.readAllBytes(ans.toPath), encoding)
+
+ outstream.flush()
+ val outstring = new String(outstream.toString(encoding.toString))
+ outstream.close()
+
+ (ansstring == outstring, s"Computed output does not match expected output.\nComputed:\n${outstring}\nExpected:\n${ansstring}")
+ }
+ })
+ }
+
+ def isJsy(file: File): Boolean = {
+ val jsyext = """[.]jsy$""".r
+ jsyext findFirstIn file.getName match {
+ case Some(_) => true
+ case None => false
+ }
+ }
+
+ def doFile(doit: File => Unit, file: File) = {
+ def loop(file: File): Unit = {
+ if (file.isFile) {
+ doit(file)
+ }
+ else if (file.isDirectory) {
+ file.listFiles filter { f => f.isDirectory || isJsy(f) } foreach loop
+ }
+ else {
+ throw new IllegalArgumentException("File %s does not exist.".format(file))
+ }
+ }
+ loop(file)
+ }
+
+ def test(fileordir: File)(k: (File, File, => (Boolean, String)) => Unit): Unit = {
+ doFile({ f => testJsy(f)(k) }, fileordir)
+ }
+
+ def main(args: Array[String]): Unit = {
+ val opts = new options.Options("jsy", flagOptions, anonOption)
+ val file: File = opts.process(args)
+ doFile(processFile, file)
+ }
+}
+
+object options {
+ sealed abstract class Spec
+ case class SetBool(setter: Boolean => Unit, default: Option[Boolean => Boolean]) extends Spec
+ case class SetInt(setter: Option[Int] => Unit, default: Option[Boolean => Option[String]]) extends Spec
+
+ class Options[T](program: String, specs: List[(String, Spec, String)], anon: (String, PartialFunction[List[String], T], String)) {
+ val opts: Map[String, List[String] => Option[List[String]]] =
+ specs.foldLeft[Map[String, List[String] => Option[List[String]]]](Map.empty)((acc, spec) => spec match {
+ case (name, SetBool(setter, _), _) =>
+ acc +
+ (("--" + name) -> { (t: List[String]) => setter(true); Some(t) }) +
+ (("--no-" + name) -> { (t: List[String]) => setter(false); Some(t) })
+ case (name, SetInt(setter, _), _) =>
+ acc +
+ (("--" + name) -> { (t: List[String]) => t match {
+ case arg :: t =>
+ val argp: Int = try arg.toInt catch { case _: Throwable => usageErr() }
+ setter(Some(argp)); Some(t)
+ case _ => usageErr()
+ } }) +
+ (("--no-" + name) -> { (t: List[String]) => setter(None); Some(t) })
+ })
+
+ val (anonName, anonDo, anonDesc) = anon
+
+ val nameWidth: Int = opts.foldLeft(anonName.length){ case (acc, (n, _)) => acc max (n.length + 5) }
+
+ def padRight(s: String, w: Int): String = {
+ val nspaces = (w - s.length) max 0
+ s + (" " * nspaces)
+ }
+
+ def optline(name: String, text: String): String = {
+ "%-2s".format("") + padRight(name, nameWidth) + " %s%n".format(text)
+ }
+
+ val descriptions: String = {
+ (specs foldRight "")((spec, acc) => spec match {
+ case (name, SetBool(_, default), desc) => {
+ def defaultStr(b: Boolean): String =
+ default map (f => if (f(b)) " (default)" else "") getOrElse("")
+ optline("--" + name, "turn on %s".format(desc) + defaultStr(true)) +
+ optline("--no-" + name, "turn off %s".format(desc) + defaultStr(false)) +
+ acc
+ }
+ case (name, SetInt(_, default), desc) => {
+ def defaultStr(b: Boolean): String = {
+ val opt =
+ for {
+ f <- default
+ s <- f(b)
+ } yield " (default%s)".format(s)
+ opt.getOrElse("")
+ }
+ optline("--" + name + " <int>", "set %s".format(desc) + defaultStr(true)) +
+ optline("--no-" + name, "unset %s".format(desc) + defaultStr(false)) +
+ acc
+ }
+ })
+ }
+
+ val header =
+ """
+Usage: %s [options] %s
+
+""".format(program, anonName) +
+optline(anonName, anonDesc) + """
+Options:
+"""
+
+ private var currentArgs: List[String] = List()
+
+ def usageErr(): Nothing = {
+ val unprocessed = currentArgs match {
+ case Nil => ""
+ case _ => """
+Unprocessed Arguments:
+ """ + currentArgs.reduceRight({ _ + " " + _ }) + "%n".format()
+ }
+ print(header + descriptions + unprocessed)
+ sys.exit(1)
+ }
+
+ def process(args: Array[String]): T = {
+ def loop(l: List[String]): List[String] = {
+ currentArgs = l
+ l match {
+ case Nil => Nil
+ case h :: t => opts.get(h) match {
+ case None => l
+ case Some(doit) => { val tp = doit(t).getOrElse(t); loop(tp) }
+ }
+ }
+ }
+ val err: PartialFunction[List[String], T] = { case _ => usageErr() }
+ (anonDo orElse err)(loop(args.toList))
+ }
+ }
+} \ No newline at end of file
diff --git a/lab5/src/main/scala/jsy/util/Visitor.scala b/lab5/src/main/scala/jsy/util/Visitor.scala
new file mode 100644
index 0000000..a74ec70
--- /dev/null
+++ b/lab5/src/main/scala/jsy/util/Visitor.scala
@@ -0,0 +1,32 @@
+package jsy.util
+
+object Visitor {
+ def apply[E,R](visitant: (E => R) => PartialFunction[E,R]) = new Visitor(visitant)
+
+ def visit[E,R](visitant: (E => R) => PartialFunction[E,R])(e: E): R = {
+ def rec(e: E): R = visitant(rec)(e)
+ rec(e)
+ }
+}
+
+/** A visitor of type Visitor[E,R] is an abstraction of a recursive function
+ * on E of type E => R where E is an algebraic data type that might be extended.
+ *
+ * @param visitant
+ * @tparam E
+ * @tparam R
+ */
+class Visitor[E,R] private (val visitant: (E => R) => PartialFunction[E,R]) {
+ private lazy val rec: E => R = Visitor.visit(visitant)
+ def apply(e: E) = rec(e)
+
+ def orElse(that: Visitor[E,R]): Visitor[E,R] = orElse(that.visitant)
+ def orElse(that: (E => R) => PartialFunction[E,R]): Visitor[E,R] = new Visitor(
+ (v: E => R) => visitant(v) orElse that(v)
+ )
+
+ def extendWith(that: Visitor[E,R]): Visitor[E,R] = that orElse this
+ def extendWith(that: (E => R) => PartialFunction[E,R]) = new Visitor(that).orElse(this)
+
+ def flatMap[B](op: (E => R) => Visitor[E,B]): Visitor[E,B] = op(rec)
+}