diff options
author | Jack Sangdahl <[email protected]> | 2024-12-12 21:03:45 -0700 |
---|---|---|
committer | Jack Sangdahl <[email protected]> | 2024-12-12 21:03:45 -0700 |
commit | 8bb81e391405554d02ec36574db2ca58300c89c4 (patch) | |
tree | 2e6e16ea68cefc63466b4cc3a56352843ec6a92c /lab5/src/main/scala/jsy/util | |
parent | ae554ab7d4d425fa821c1167e734db3425a881f6 (diff) | |
download | csci3155-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.scala | 33 | ||||
-rw-r--r-- | lab5/src/main/scala/jsy/util/JsyApplication.scala | 191 | ||||
-rw-r--r-- | lab5/src/main/scala/jsy/util/Visitor.scala | 32 |
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) +} |