aboutsummaryrefslogtreecommitdiff
path: root/lab2/src/main/scala/jsy
diff options
context:
space:
mode:
Diffstat (limited to 'lab2/src/main/scala/jsy')
-rw-r--r--lab2/src/main/scala/jsy/lab2/Lab2.scala150
-rw-r--r--lab2/src/main/scala/jsy/lab2/Lab2.worksheet.js21
-rw-r--r--lab2/src/main/scala/jsy/lab2/Lab2.worksheet.sc34
-rw-r--r--lab2/src/main/scala/jsy/lab2/Parser.scala255
-rw-r--r--lab2/src/main/scala/jsy/lab2/ast.scala92
-rw-r--r--lab2/src/main/scala/jsy/util/JsyApplication.scala191
6 files changed, 743 insertions, 0 deletions
diff --git a/lab2/src/main/scala/jsy/lab2/Lab2.scala b/lab2/src/main/scala/jsy/lab2/Lab2.scala
new file mode 100644
index 0000000..5788d74
--- /dev/null
+++ b/lab2/src/main/scala/jsy/lab2/Lab2.scala
@@ -0,0 +1,150 @@
+package jsy.lab2
+
+object Lab2 extends jsy.util.JsyApplication {
+ import ast._
+ /*
+ * CSCI 3155: Lab 2
+ * Jack Sangdahl
+ */
+
+ def toNumber(v: Expr): Double = {
+ require(isValue(v))
+ (v: @unchecked) match {
+ case N(n) => n
+ case B(b) => if (b) 1.0 else 0.0
+ case S(s) => try { s.toDouble} catch { case _: NumberFormatException => Double.NaN }
+ case Undefined => Double.NaN
+ }
+ }
+
+ def toBoolean(v: Expr): Boolean = {
+ require(isValue(v))
+ (v: @unchecked) match {
+ case B(b) => b
+ case N(n) => n != 0 && !n.isNaN
+ case S(s) => s.nonEmpty
+ case Undefined => false
+ }
+ }
+
+ def toStr(v: Expr): String = {
+ require(isValue(v))
+ (v: @unchecked) match {
+ case S(s) => s
+ case N(n) => n.toString
+ case B(b) => b.toString
+ case Undefined => "undefined"
+ }
+ }
+
+ /* We represent a variable environment as a map from a string of the
+ * variable name to the value to which it is bound.
+ *
+ * You may use the following provided helper functions to manipulate
+ * environments, which are just thin wrappers around the Map type
+ * in the Scala standard library. You can use the Scala standard
+ * library directly, but these are the only interfaces that you
+ * need.
+ */
+ type Env = Map[String, Expr]
+ val empty: Env = Map()
+ def lookup(env: Env, x: String): Expr = env(x)
+ def extend(env: Env, x: String, v: Expr): Env = {
+ require(isValue(v))
+ env + (x -> v)
+ }
+
+ def eval(env: Env, e: Expr): Expr = {
+ e match {
+ // literals
+ case N(n) => N(n)
+ case B(b) => B(b)
+ case S(s) => S(s)
+ case Undefined => Undefined
+
+ // unary ops
+ case Unary(Neg, e1) => N(-toNumber(eval(env, e1)))
+ case Unary(Not, e1) => B(!toBoolean(eval(env, e1)))
+
+ // binary ops
+ case Binary(Plus, e1, e2) => N(toNumber(eval(env, e1)) + toNumber(eval(env, e2)))
+ case Binary(Minus, e1, e2) => N(toNumber(eval(env, e1)) - toNumber(eval(env, e2)))
+ case Binary(Times, e1, e2) => N(toNumber(eval(env, e1)) * toNumber(eval(env, e2)))
+ case Binary(Div, e1, e2) => N(toNumber(eval(env, e1)) / toNumber(eval(env, e2)))
+
+ case Binary(And, e1, e2) => eval(env, e1) match {
+ case B(b1) if !b1 => B(false)
+ case N(n1) if n1 == 0 => N(0)
+ case Undefined => eval(env, e1)
+ case _ => eval(env, e2)
+ }
+ case Binary(Or, e1, e2) => eval(env, e1) match {
+ case B(b1) if b1 => B(true)
+ case N(n1) if n1 != 0 => N(n1)
+ case Undefined => eval(env, e2)
+ case _ => eval(env, e2)
+ }
+
+ case Binary(Eq, e1, e2) => (eval(env, e1), eval(env, e2)) match {
+ case (N(n1), N(n2)) => B(n1 == n2)
+ case (B(b1), B(b2)) => B(b1 == b2)
+ case (S(s1), S(s2)) => B(s1 == s2)
+ case (Undefined, Undefined) => B(true)
+ case (Undefined, _) => B(false)
+ case (_, Undefined) => B(false)
+ case _ => B(false)
+ }
+ case Binary(Ne, e1, e2) => B(toNumber(eval(env, e1)) != toNumber(eval(env, e2)))
+
+ case Binary(Lt, e1, e2) => B(toNumber(eval(env, e1)) < toNumber(eval(env, e2)))
+ case Binary(Le, e1, e2) => B(toNumber(eval(env, e1)) <= toNumber(eval(env, e2)))
+ case Binary(Gt, e1, e2) => B(toNumber(eval(env, e1)) > toNumber(eval(env, e2)))
+ case Binary(Ge, e1, e2) => B(toNumber(eval(env, e1)) >= toNumber(eval(env, e2)))
+
+ case Binary(Seq, e1, e2) =>
+ eval(env, e1)
+ eval(env, e2)
+
+ case If(e1, e2, e3) =>
+ if (toBoolean(eval(env, e1))) eval(env, e2) else eval(env, e3)
+
+ case ConstDecl(x, e1, e2) =>
+ val v1 = eval(env, e1)
+ eval(env + (x -> v1), e2)
+
+ case Print(e1) => println(pretty(eval(env, e1))); Undefined
+ case Var(x) => env.getOrElse(x, throw new RuntimeException(s"Variable $x not found"))
+ }
+ }
+
+ /* Interface to run your interpreter starting from an empty environment. This is convenient for unit testing. */
+ def eval(e: Expr): Expr = {
+ require(closed(e))
+ eval(empty, e)
+ }
+
+ /* Interface to run your interpreter from a string. This is convenient for unit testing. */
+ def eval(s: String): Expr = eval(Parser.parse(s))
+
+ // You may set this debug flag to true to get the debugging output below, but you need to turn it off to pass test cases.
+ // debug = true
+
+ /* Interface to run your interpreter on an input file. This is for file-based integration tests (see Lab2JsyTests in Lab2Spec.scala). */
+ def processFile(file: java.io.File): Unit = {
+
+ if (debug) { println("Parsing ...") }
+
+ val expr = Parser.parseFile(file)
+
+ if (debug) {
+ println("\nExpression AST:\n " + expr)
+ println("------------------------------------------------------------")
+ }
+
+ if (debug) { println("Evaluating ...") }
+
+ val v = eval(expr)
+
+ println(pretty(v))
+ }
+}
diff --git a/lab2/src/main/scala/jsy/lab2/Lab2.worksheet.js b/lab2/src/main/scala/jsy/lab2/Lab2.worksheet.js
new file mode 100644
index 0000000..5a1cb50
--- /dev/null
+++ b/lab2/src/main/scala/jsy/lab2/Lab2.worksheet.js
@@ -0,0 +1,21 @@
+/*
+ * CSCI 3155: Lab 2 JavaScript Worksheet
+ *
+ * This worksheet is a place to experiment with JavaScript expressions.
+ */
+
+const five = 5;
+
+five + "five";
+
+console.log(five), console.log("five");
+
+undefined - true;
+
+10 * false;
+
+10 * undefined;
+
+console.log(undefined ? "then" : "else");
+
+console.log("" ? "then" : "else"); \ No newline at end of file
diff --git a/lab2/src/main/scala/jsy/lab2/Lab2.worksheet.sc b/lab2/src/main/scala/jsy/lab2/Lab2.worksheet.sc
new file mode 100644
index 0000000..3b401ee
--- /dev/null
+++ b/lab2/src/main/scala/jsy/lab2/Lab2.worksheet.sc
@@ -0,0 +1,34 @@
+/*
+ * CSCI 3155: Lab 2 Worksheet
+ *
+ * This worksheet demonstrates how you could experiment
+ * interactively with your implementations in Lab2.scala.
+ */
+
+// Imports the parse and parseFile functions
+import jsy.lab2.Parser.{parse, parseFile}
+
+// Imports the ast nodes
+import jsy.lab2.ast._
+
+// Imports all of the functions from your implementation
+import jsy.lab2.Lab2._
+
+// Call the JavaScripty parser (from the provided library) on a string
+val negFourAST = parse("-4")
+
+// Construct an AST directly
+val negFiveAST: Expr = Unary(Neg, N(5.0))
+
+// Evaluate that JavaScripty expression.
+//eval(negFourAST)
+
+// For convenience, we also have an eval function that takes a string,
+// which calls the parser and then delegates to your eval function.
+//eval("undefined + 1")
+
+// Parse the JavaScripty expression in your worksheet
+val worksheetJsy = parseFile("src/main/scala/jsy/lab2/Lab2.worksheet.js")
+
+// Eval the JavaScripty expression in your worksheet
+//eval(worksheetJsy) \ No newline at end of file
diff --git a/lab2/src/main/scala/jsy/lab2/Parser.scala b/lab2/src/main/scala/jsy/lab2/Parser.scala
new file mode 100644
index 0000000..b4cf478
--- /dev/null
+++ b/lab2/src/main/scala/jsy/lab2/Parser.scala
@@ -0,0 +1,255 @@
+/**
+ *
+ */
+package jsy.lab2
+
+import jsy.lab2.ast._
+import scala.util.parsing.combinator._
+import scala.util.parsing.input.{StreamReader}
+import java.io.{InputStreamReader,FileInputStream}
+import java.io.InputStream
+import java.io.File
+import scala.util.parsing.input.Position
+import scala.util.parsing.input.NoPosition
+
+trait JSTokens extends token.StdTokens {
+ case class FloatLiteral(chars: String) extends Token {
+ override def toString = chars
+ }
+}
+
+class Lexer extends lexical.StdLexical with JSTokens {
+ override def token: this.Parser[Token] =
+ decimal ~ opt(exponent) ^^ {
+ case dec ~ exp => FloatLiteral(List(Some(dec), exp).flatten.mkString)
+ } |
+ super.token
+
+ def decimal: this.Parser[String] =
+ rep1(digit) ~ opt('.' ~ rep(digit)) ^^ {
+ case ws ~ fs =>
+ List(Some(ws), fs map { mkList }).flatten.flatten.mkString
+ }
+
+ def exponent: this.Parser[String] =
+ (accept('e') | accept('E')) ~ opt(accept('+') | accept('-')) ~ rep1(digit) ^^ {
+ case exp ~ sign ~ digits =>
+ List(Some(List(exp)), sign map { List(_) }, Some(digits)).flatten.flatten.mkString
+ }
+}
+
+trait TokenParser extends syntactical.StdTokenParsers {
+ type Tokens = JSTokens
+ val lexical = new Lexer
+
+ import lexical.FloatLiteral
+
+ def floatLit: this.Parser[String] =
+ elem("float", _.isInstanceOf[FloatLiteral]) ^^ (_.chars)
+}
+
+object Parser extends TokenParser {
+ /* Lexer Set Up */
+ lexical.reserved ++= List("jsy", "undefined", "true", "false", "print", "console", "log", "const", "function", "return", "number", "bool", "string", "Undefined", "null", "Null", "interface", "var", "name", "ref", "RegExp")
+ lexical.delimiters ++= List("-", "!", ";", ",", "+", "*", "/", "=", "===", "!==", "<", "<=", ">", ">=", "&&", "||", "(", ")", ".", "{", "}", "?", ":", "=>")
+
+ /* Helpers */
+ def seqExpr(e1: Expr, e2: Expr): Expr = Binary(Seq, e1, e2)
+
+ /* EBNF
+ *
+ * prog ::= {stmt}
+ * stmt ::= block | decl | ; | expr
+ * block ::= '{' prog '}'
+ * decl ::= const x = expr
+ * expr ::= seq
+ * seq ::= cond{,cond}
+ * cond ::= binary [? cond : cond]
+ * binary ::= unary{bop(_)unary}
+ * unary ::= uop unary | term
+ * term ::= x | n | b | undefined | jsy.print(expr) | (expr)
+ */
+
+ sealed abstract class PStmt
+ case class ExprPStmt(e: Expr) extends PStmt
+ case class DeclPStmt(d: Expr => Expr) extends PStmt
+ case object EmpPStmt extends PStmt
+
+ def prog: this.Parser[Expr] =
+ stmts ^^ (s => s(None))
+
+ def stmts: this.Parser[Option[Expr] => Expr] =
+ rep(stmt) ^^ { (stmts: List[PStmt]) => (body: Option[Expr]) =>
+ (stmts foldRight body){
+ case (EmpPStmt, eopt) => eopt
+ case (ExprPStmt(e), None) => Some(e)
+ case (ExprPStmt(e1), Some(e2)) => Some(seqExpr(e1, e2))
+ case (DeclPStmt(d), None) => Some(d(Undefined))
+ case (DeclPStmt(d), Some(e)) => Some(d(e))
+ } match {
+ case None => Undefined
+ case Some(e) => e
+ }
+ }
+
+ def stmt: this.Parser[PStmt] =
+ block ^^ ExprPStmt |
+ decl ^^ DeclPStmt |
+ expr ^^ ExprPStmt |
+ empty_stmt
+
+ def empty_stmt: this.Parser[PStmt] =
+ ";" ^^ (_ => EmpPStmt)
+
+ def block: this.Parser[Expr] =
+ "{" ~> prog <~ "}"
+
+ def decl: this.Parser[Expr => Expr] =
+ ("const" ~> ident) ~ withpos("=" ~> expr) ^^ {
+ case x ~ ((pos,e1)) => ((e2: Expr) => ConstDecl(x, e1, e2) setPos pos)
+ }
+
+ def expr: this.Parser[Expr] =
+ seq
+
+ def seq: this.Parser[Expr] =
+ noseq ~ withposrep("," ~> noseq) ^^ {
+ case e0 ~ es =>
+ (es foldRight (None: Option[(Position,Expr)])){
+ case ((posi,ei), None) => Some(posi,ei)
+ case ((posi,ei), Some((pos,e))) => Some(posi, seqExpr(ei,e) setPos pos)
+ } match {
+ case None => e0
+ case Some((pos,e)) => seqExpr(e0, e) setPos pos
+ }
+ }
+
+ def noseq: this.Parser[Expr] =
+ cond
+
+ def cond: this.Parser[Expr] =
+ binary(0) ~ opt(withpos(("?" ~> noseq) ~ (":" ~> noseq))) ^^ {
+ case e1 ~ None => e1
+ case e1 ~ Some((pos, e2 ~ e3)) => If(e1, e2, e3) setPos pos
+ }
+
+ val binaryOperators: Vector[List[(String, (Expr, Expr) => Expr)]] = {
+ def createBinaryFunction(op: Bop): (Expr, Expr) => Expr =
+ { Binary(op, _, _) }
+ Vector() ++ List(
+ List("||" -> createBinaryFunction(Or)),
+ List("&&" -> createBinaryFunction(And)),
+ List("===" -> createBinaryFunction(Eq),
+ "!==" -> createBinaryFunction(Ne)),
+ List("<" -> createBinaryFunction(Lt),
+ "<=" -> createBinaryFunction(Le),
+ ">" -> createBinaryFunction(Gt),
+ ">=" -> createBinaryFunction(Ge)),
+ List("+" -> createBinaryFunction(Plus),
+ "-" -> createBinaryFunction(Minus)),
+ List("*" -> createBinaryFunction(Times),
+ "/" -> createBinaryFunction(Div))
+ )
+ }
+
+ def binary(level: Int): this.Parser[Expr] =
+ if (level >= binaryOperators.length)
+ unary
+ else
+ binary(level + 1) * bop(level)
+
+ def bop(level: Int): this.Parser[(Expr, Expr) => Expr] = {
+ def doBop(opf: (String, (Expr, Expr) => Expr)): this.Parser[(Expr, Expr) => Expr] = {
+ val (op, f) = opf
+ withpos(op) ^^ { case (pos, _) => ((e1, e2) => f(e1, e2) setPos pos) }
+ }
+ val bopf0 :: bopfrest = binaryOperators(level)
+ bopfrest.foldLeft(doBop(bopf0))((acc, bopf) => acc | doBop(bopf))
+ }
+
+ def unary: this.Parser[Expr] =
+ positioned(uop ~ unary ^^ { case op ~ e => op(e) }) |
+ term
+
+ def uop: this.Parser[Expr => Expr] =
+ "-" ^^ (_ => (e: Expr) => Unary(Neg, e)) |
+ "!" ^^ (_ => (e: Expr) => Unary(Not, e))
+
+ def term: this.Parser[Expr] =
+ positioned(
+ ident ^^ (s => Var(s)) |
+ floatLit ^^ (s => N(s.toDouble)) |
+ stringLit ^^ (s => S(s)) |
+ "true" ^^ (_ => B(true)) |
+ "false" ^^ (_ => B(false)) |
+ "undefined" ^^ (_ => Undefined) |
+ ("jsy" ~ "." ~ "print") ~> "(" ~> expr <~ ")" ^^ (e => Print(e)) |
+ ("console" ~ "." ~ "log") ~> "(" ~> expr <~ ")" ^^ (e => Print(e))
+ ) |
+ "(" ~> expr <~ ")" |
+ "{" ~> "{" ~> prog <~ "}" <~ "}" |
+ failure("atomic expression expected")
+
+ def withpos[T](q: => this.Parser[T]): this.Parser[(Position, T)] = this.Parser { in =>
+ q(in) match {
+ case Success(t, in1) => Success((in.pos,t), in1)
+ case ns: NoSuccess => ns
+ }
+ }
+
+ def withposrep[T](q: => this.Parser[T]): this.Parser[List[(Position,T)]] =
+ rep(withpos(q))
+
+ def withposrep1[T](q: => this.Parser[T]): this.Parser[List[(Position,T)]] =
+ rep1(withpos(q))
+
+ private var parseSource: String = "<source>"
+
+ def formatErrorMessage(pos: Position, kind: String, msg: String): String =
+ if (pos != NoPosition)
+ "%s\n%s:%s:%s: %s\n%s".format(kind, parseSource, pos.line, pos.column, msg, pos.longString)
+ else
+ "%s\n%s: %s".format(kind, parseSource, msg)
+
+ class SyntaxError(msg: String, next: Input) extends Exception {
+ override def toString = formatErrorMessage(next.pos, "SyntaxError", msg)
+ }
+
+ def parseTokens(tokens: lexical.Scanner): Expr = {
+ phrase(prog)(tokens) match {
+ case Success(e, _) => e
+ case NoSuccess(msg, next) => throw new SyntaxError(msg, next)
+ case Error(msg,next) => throw new SyntaxError(msg, next)
+ case Failure(msg,next) => throw new SyntaxError(msg, next)
+ }
+ }
+
+ /*** External Interface ***/
+
+ def formatErrorMessage(e: Expr, kind: String, msg: String): String =
+ formatErrorMessage(e.pos, kind, msg)
+
+ def parse(s: String): Expr = {
+ parseTokens(new lexical.Scanner(s))
+ }
+
+ def parse(in: InputStream): Expr = {
+ val reader = StreamReader(new InputStreamReader(in))
+ parseTokens(new lexical.Scanner(reader))
+ }
+
+ def parseFile(filename: String): Expr = {
+ parseSource = filename
+ parse(new FileInputStream(filename))
+ }
+
+ def parseFile(file: File): Expr = {
+ parseSource = file.getName
+ parse(new FileInputStream(file))
+ }
+
+ implicit class StringExpr(s: String) {
+ val e = parse(s)
+ def a: Expr = e
+ }
+}
diff --git a/lab2/src/main/scala/jsy/lab2/ast.scala b/lab2/src/main/scala/jsy/lab2/ast.scala
new file mode 100644
index 0000000..7c8fb5e
--- /dev/null
+++ b/lab2/src/main/scala/jsy/lab2/ast.scala
@@ -0,0 +1,92 @@
+package jsy.lab2
+
+import scala.util.parsing.input.Positional
+
+/**
+ * @author Bor-Yuh Evan Chang
+ */
+object ast {
+ sealed trait Expr extends Positional // e ::=
+
+ /* Literals */
+ case class N(n: Double) extends Expr // e ::= n
+ case class B(b: Boolean) extends Expr // e ::= b
+ case class S(str: String) extends Expr // e ::= str
+ case object Undefined extends Expr // e ::= undefined
+
+ /* Unary and Binary Expressions */
+ case class Unary(uop: Uop, e1: Expr) extends Expr // e ::= uop e1
+ case class Binary(bop: Bop, e1: Expr, e2: Expr) extends Expr // e ::= e1 bop e2
+
+ sealed trait Uop // uop ::=
+ sealed trait Bop // bop ::=
+
+ /* Numbers */
+ case object Neg extends Uop // uop ::= -
+
+ case object Plus extends Bop // bop ::= +
+ case object Minus extends Bop // bop ::= -
+ case object Times extends Bop // bop ::= *
+ case object Div extends Bop // bop ::= /
+
+ /* Booleans */
+ case object Not extends Uop // uop ::= !
+ case object And extends Bop // bop ::= &&
+ case object Or extends Bop // bop ::= ||
+
+ case class If(e1: Expr, e2: Expr, e3: Expr) extends Expr // e ::= e1 ? e2 : e3
+
+ case object Eq extends Bop // bop ::= ===
+ case object Ne extends Bop // bop ::= !==
+ case object Lt extends Bop // bop ::= <
+ case object Le extends Bop // bop ::= <=
+ case object Gt extends Bop // bop ::= >
+ case object Ge extends Bop // bop ::= >=
+
+ /* Side-Effects */
+ case class Print(e1: Expr) extends Expr // e ::= console.log(e1)
+ case object Seq extends Bop // bop ::= ,
+
+ /* Variables */
+ case class Var(x: String) extends Expr // e ::= x
+ case class ConstDecl(x: String, e1: Expr, e2: Expr) extends Expr // e ::= const x = e1; e2
+
+ /* Define values. */
+ def isValue(e: Expr): Boolean = e match {
+ case N(_) | B(_) | S(_) | Undefined => true
+ case _ => false
+ }
+
+ /*
+ * Pretty-print values.
+ *
+ * We do not override the toString method so that the abstract syntax can be printed
+ * as is.
+ */
+ def pretty(v: Expr): String = {
+ require(isValue(v))
+ (v: @unchecked) match {
+ case N(n) => prettyNumber(n)
+ case B(b) => b.toString
+ case S(str) => str
+ case Undefined => "undefined"
+ }
+ }
+
+ def prettyNumber(n: Double): String =
+ if (n.isWhole) "%.0f" format n else n.toString
+
+ /* Get the free variables of e. */
+ def freeVars(e: Expr): Set[String] = e match {
+ case Var(x) => Set(x)
+ case ConstDecl(x, e1, e2) => freeVars(e1) | (freeVars(e2) - x)
+ case N(_) | B(_) | Undefined | S(_) => Set.empty
+ case Unary(_, e1) => freeVars(e1)
+ case Binary(_, e1, e2) => freeVars(e1) | freeVars(e2)
+ case If(e1, e2, e3) => freeVars(e1) | freeVars(e2) | freeVars(e3)
+ case Print(e1) => freeVars(e1)
+ }
+
+ /* Check closed expressions. */
+ def closed(e: Expr): Boolean = freeVars(e).isEmpty
+}
diff --git a/lab2/src/main/scala/jsy/util/JsyApplication.scala b/lab2/src/main/scala/jsy/util/JsyApplication.scala
new file mode 100644
index 0000000..46b658d
--- /dev/null
+++ b/lab2/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