diff options
Diffstat (limited to 'lab5/src/main/scala')
-rw-r--r-- | lab5/src/main/scala/jsy/lab5/Lab5.scala | 410 | ||||
-rw-r--r-- | lab5/src/main/scala/jsy/lab5/Lab5.worksheet.sc | 47 | ||||
-rw-r--r-- | lab5/src/main/scala/jsy/lab5/Lab5.worksheet.ts | 12 | ||||
-rw-r--r-- | lab5/src/main/scala/jsy/lab5/Parser.scala | 359 | ||||
-rw-r--r-- | lab5/src/main/scala/jsy/lab5/ast.scala | 321 | ||||
-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 |
8 files changed, 1405 insertions, 0 deletions
diff --git a/lab5/src/main/scala/jsy/lab5/Lab5.scala b/lab5/src/main/scala/jsy/lab5/Lab5.scala new file mode 100644 index 0000000..ad62ef9 --- /dev/null +++ b/lab5/src/main/scala/jsy/lab5/Lab5.scala @@ -0,0 +1,410 @@ +package jsy.lab5 + +import scala.collection.immutable.SortedMap +import jsy.lab5.Parser.parse + +object Lab5 extends jsy.util.JsyApplication { + import jsy.lab5.ast._ + import jsy.util.DoWith + import jsy.util.DoWith._ + + /* + * CSCI 3155: Lab 5 + * Jack Sangdahl + */ + + /** * Type Inference ** + */ + def hasloctype(env: Map[String, MTyp], e: Expr): Option[Typ] = + try { Some(hastype(env, e)) } + catch { case _: StaticTypeError => None } + + def hasmodetype(env: Map[String, MTyp], e: Expr, d: Mode): Option[Typ] = + try { + val typ = hastype(env, e) + d match { + case MConst => Some(typ) + case MVar if typ != TUndefined => Some(typ) + case MRef if typ != TUndefined => Some(typ) + case _ => None + } + } catch { + case e: StaticTypeError => None + } + + def hastype(env: Map[String, MTyp], e: Expr): Typ = { + def err[T](tgot: Typ, e1: Expr): T = throw StaticTypeError(tgot, e1, e) + def errnotype[T](e1: Expr): T = err(TUndefined, e1) + + e match { + case Print(e1) => hastype(env, e1); TUndefined + case N(_) => TNumber + case B(_) => TBool + case Undefined => TUndefined + case S(_) => TString + case Var(x) => + env.get(x).map { case MTyp(_, typ) => typ }.getOrElse(errnotype(e)) + case Unary(Neg, e1) => + hastype(env, e1) match { + case TNumber => TNumber + case tgot => err(tgot, e1) + } + case Unary(Not, e1) => + hastype(env, e1) match { + case TBool => TBool + case tgot => err(tgot, e1) + } + case Binary(Plus, e1, e2) => + (hastype(env, e1), hastype(env, e2)) match { + case (TNumber, TNumber) => TNumber + case (TString, TString) => TString + case (tgot1, _) => err(tgot1, e1) + } + case Binary(Minus | Times | Div, e1, e2) => + (hastype(env, e1), hastype(env, e2)) match { + case (TNumber, TNumber) => TNumber + case (tgot1, _) => err(tgot1, e1) + } + case Binary(Eq | Ne, e1, e2) => + (hastype(env, e1), hastype(env, e2)) match { + case (t1, t2) if t1 == t2 => TBool + case (tgot1, _) => err(tgot1, e1) + } + case Binary(Lt | Le | Gt | Ge, e1, e2) => + (hastype(env, e1), hastype(env, e2)) match { + case (TNumber, TNumber) => TBool + case (tgot1, _) => err(tgot1, e1) + } + case Binary(And | Or, e1, e2) => + (hastype(env, e1), hastype(env, e2)) match { + case (TBool, TBool) => TBool + case (tgot, _) => err(tgot, e1) + } + case Binary(Seq, e1, e2) => + hastype(env, e1) + hastype(env, e2) + case If(e1, e2, e3) => + hastype(env, e1) match { + case (TBool) => + val t2 = hastype(env, e2) + val t3 = hastype(env, e3) + if (t2 == t3) t2 + else err(t2, e2) + case tgot => err(tgot, e1) + } + + case Obj(fields) => + TObj(fields.map { case (f, e1) => (f, hastype(env, e1)) }) + case GetField(e1, f) => + hastype(env, e1) match { + case TObj(fieldTypes) => + fieldTypes.getOrElse(f, err(TObj(fieldTypes), e1)) + case tgot => err(tgot, e1) + } + case Decl(m, x, e1, e2) => + val t1 = hastype(env, e1) + val newEnv = env + (x -> MTyp(m, t1)) + hastype(newEnv, e2) + + case Fun(xopt, params, tretopt, e1) => { + val env1 = (xopt, tretopt) match { + case (Some(x), Some(tret)) => + env + (x -> MTyp(MConst, TFun(params, tret))) + case (Some(x), None) => errnotype(e1) + case (None, Some(tret)) => env + case (None, None) => errnotype(e1) + } + val env2 = env1 ++ params.map { case (x, MTyp(m, t)) => + x -> MTyp(m, t) + } + val t1 = hastype(env2, e1) + tretopt match { + case Some(tret) if t1 == tret => TFun(params, tret) + case Some(tret) => err(t1, e1) + case None => TFun(params, t1) + } + } + + case Call(e1, args) => + hastype(env, e1) match { + case TFun(params, tret) if params.length == args.length => + (params zip args).foreach { case ((x, MTyp(_, paramType)), ex) => + val argType = hastype(env, ex) + if (paramType != argType) err(paramType, ex) + } + tret + case tgot => err(tgot, e1) + } + + case Assign(e1, e2) => + e1 match { + case Var(x) => + env.get(x) match { + case Some(MTyp(MConst, _)) => + errnotype(e1) + case Some(MTyp(MVar, t1)) => + val t2 = hastype(env, e2) + if (t1 == t2) t2 + else err(t1, e2) + case None => + errnotype(e1) + } + + case Unary(Deref, a @ A(_)) => + val t1 = hastype(env, e1) + val t2 = hastype(env, e2) + if (t1 == t2) t2 + else err(t1, e2) + + case GetField(e1Inner, f) => + hastype(env, e1Inner) match { + case TObj(fields) if fields.contains(f) => + val fieldType = fields(f) + val t2 = hastype(env, e2) + if (fieldType == t2) t2 + else err(fieldType, e2) + case tgot => + err(tgot, e1Inner) + } + + case _ => + errnotype(e1) + } + + /* Should not match: non-source expressions */ + case A(_) | Unary(Deref, _) => + throw new IllegalArgumentException( + "Gremlins: Encountered unexpected expression %s.".format(e) + ) + } + } + + /** * Small-Step Interpreter ** + */ + def substitute(with_e: Expr, x: String, e: Expr): Expr = { + def subst(e: Expr): Expr = e match { + case N(_) | B(_) | Undefined | S(_) => e + case Print(e1) => Print(subst(e1)) + case Unary(uop, e1) => Unary(uop, subst(e1)) + case Binary(bop, e1, e2) => Binary(bop, subst(e1), subst(e2)) + case If(e1, e2, e3) => If(subst(e1), subst(e2), subst(e3)) + case Var(y) => if (y == x) with_e else e + case Decl(d, y, e1, e2) => + if (y == x) Decl(d, y, subst(e1), e2) + else Decl(d, y, subst(e1), subst(e2)) + case Fun(xopt, params, tann, e1) => + if (xopt.contains(x) || params.exists(_._1 == x)) e + else Fun(xopt, params, tann, subst(e1)) + case Call(e1, args) => Call(subst(e1), args.map(subst)) + case Obj(fields) => Obj(fields.map { case (f, e1) => (f, subst(e1)) }) + case GetField(e1, f) => GetField(subst(e1), f) + case A(_) => e + case Assign(e1, e2) => Assign(subst(e1), subst(e2)) + } + subst(e) + } + + /* A small-step transition. */ + def step(e: Expr): DoWith[Mem, Expr] = { + require(!isValue(e), s"stepping on a value: ${e}") + e match { + case Print(v1) if isValue(v1) => + doget map { m => println(pretty(m, v1)); Undefined } + case Unary(Neg, v1) if isValue(v1) => + v1 match { + case N(n) => doreturn(N(-n)) + case _ => throw StuckError(e) + } + case Unary(Not, v1) if isValue(v1) => + v1 match { + case B(n) => doreturn(B(!n)) + case _ => throw StuckError(e) + } + + case Obj(fields) if (fields forall { case (_, vi) => isValue(vi) }) => + memalloc(e) + + case GetField(a @ A(_), f) => + doget map { m => + m.get(a) match { + case Some(Obj(fields)) => fields.getOrElse(f, throw StuckError(e)) + case _ => throw StuckError(e) + } + } + case Binary(Plus, v1, v2) if isValue(v1) && isValue(v2) => + (v1, v2) match { + case (N(n1), N(n2)) => doreturn(N(n1 + n2)) + case (S(s1), S(s2)) => doreturn(S(s1 + s2)) + case _ => throw StuckError(e) + } + + case Binary(Plus, e1, e2) if isValue(e1) => + step(e2) map { e2p => Binary(Plus, e1, e2p) } + + case Binary(Plus, e1, e2) => + step(e1) map { e1p => Binary(Plus, e1p, e2) } + + case Binary(Seq, v1, v2) if (isValue(v1)) => doreturn(v2) + case Binary(And, B(x), v2) => if (x) doreturn(v2) else doreturn(B(false)) + case Binary(Or, B(x), v2) => if (x) doreturn(B(true)) else doreturn(v2) + case Binary(Eq, v1, v2) if (isValue(v1) && isValue(v2)) => + doreturn(B(v1 == v2)) + case Binary(Ne, v1, v2) if (isValue(v1) && isValue(v2)) => + doreturn(B(v1 != v2)) + case Binary(Minus, N(x), N(y)) => doreturn(N(x - y)) + case Binary(Times, N(x), N(y)) => doreturn(N(x * y)) + case Binary(Div, N(x), N(y)) => doreturn(N(x / y)) + case If(B(true), v2, v3) => doreturn(v2) + case If(B(false), v2, v3) => doreturn(v3) + + case Decl(MConst, x, v1, e2) if isValue(v1) => + doreturn(substitute(v1, x, e2)) + case Decl(MVar, x, v1, e2) if isValue(v1) => + memalloc(v1) flatMap { a => + doreturn(substitute(Unary(Deref, a), x, e2)) + } + + case Decl(MRef, x, l1, e2) if isValue(l1) => + doreturn(substitute(l1, x, e2)) + + case Unary(Deref, a @ A(_)) => + doget[Mem] flatMap { m => doreturn(m(a)) } + + case Assign(Unary(Deref, a @ A(_)), v) if isValue(v) => + domodify[Mem] { m => + if (m.contains(a)) { m + (a -> v) } + else throw StuckError(e) + } flatMap { _ => doreturn(v) } + + case Assign(GetField(a @ A(_), f), v) if isValue(v) => + domodify[Mem] { m => + m.get(a) match { + case Some(Obj(fields)) => + val updatedFields = fields + (f -> v) + m + (a -> Obj(updatedFields)) + case _ => throw StuckError(e) + } + } flatMap { _ => doreturn(v) } + + case Call(v @ Fun(xopt, params, _, e), args) => { + val pazip = params zip args + val ep = pazip.foldRight(e) { + case (((x, MTyp(MConst, _)), arg), acc) => + substitute(arg, x, acc) + + case (((x, MTyp(MVar, _)), arg), acc) => + substitute(Unary(Deref, arg), x, acc) + + case (((x, MTyp(MRef, _)), arg), acc) => + substitute(arg, x, acc) + } + DoWith.doreturn(ep) + } + + case Print(e1) => step(e1) map { e1p => Print(e1p) } + case Unary(uop, e1) => step(e1) map { e1p => Unary(uop, e1p) } + case GetField(e1, f) => + step(e1) map { e1p => GetField(e1p, f) } + case Obj(fields) => + val (k, v) = fields.find { case (_, vi) => !isValue(vi) }.get + step(v) map { vp => Obj(fields + (k -> vp)) } + + case Decl(d, x, e1, e2) => + step(e1) map { e1p => Decl(d, x, e1p, e2) } + case Call(e1, args) => + step(e1) map { e1p => Call(e1p, args) } + + case Assign(e1, e2) if !isValue(e1) => + step(e1) map { e1p => Assign(e1p, e2) } + case Assign(e1, e2) if !isValue(e2) => + step(e2) map { e2p => Assign(e1, e2p) } + case Assign(e1, e2) => + step(e1) map { v1 => Assign(v1, e2) } + + /* Everything else is a stuck error. */ + case _ => throw StuckError(e) + } + } + + /** * External Interfaces ** + */ + /** Interface to run your small-step interpreter and print out the steps of + * evaluation if debugging. + */ + def iterateStep(e: Expr): Expr = { + require( + closed(e), + "input Expr to iterateStep is not closed: free variables: %s".format( + freeVars(e) + ) + ) + def loop(e: Expr, n: Int): DoWith[Mem, Expr] = + if (Some(n) == maxSteps) throw TerminationError(e, n) + else if (isValue(e)) doreturn(e) + else { + doget[Mem] flatMap { m => + println("## step %4d:%n## %s%n## %s".format(n, m, e)) + step(e) flatMap { ep => loop(ep, n + 1) } + } + } + val (m, v) = loop(e, 0)(memempty) + println("## result:%n## %s%n## %s".format(m, v)) + v + } + + // Convenience to pass in a jsy expression as a string. + def iterateStep(s: String): Expr = iterateStep(parse(s)) + + /** Interface to run your type checker. */ + def inferType(e: Expr): Typ = { + val t = hastype(Map.empty, e) + println(s"## type ${e} : ${pretty(t)}") + t + } + + // this.debug = true // comment this out or set to false if you don't want print debugging information + this.maxSteps = Some( + 1000 + ) // comment this out or set to None to not bound the number of steps. + this.keepGoing = + true // comment this out if you want to stop at first exception when processing a file + + // Interface for main + def processFile(file: java.io.File): Unit = { + if (debug) { + println("# ============================================================") + println("# File: " + file.getName) + println("# Parsing ...") + } + + val expr = + handle(None: Option[Expr]) { + Some { + jsy.lab5.Parser.parseFile(file) + } + } getOrElse { + return + } + + if (debug) { + println("# ------------------------------------------------------------") + println("# Type checking %s ...".format(expr)) + } + + val welltyped = handle(false) { + val t = inferType(expr) + true + } + if (!welltyped) return + + if (debug) { + println("# ------------------------------------------------------------") + println("# Stepping %s ...".format(expr)) + } + + handle(()) { + val v = iterateStep(expr) + println(pretty(v)) + } + } +} diff --git a/lab5/src/main/scala/jsy/lab5/Lab5.worksheet.sc b/lab5/src/main/scala/jsy/lab5/Lab5.worksheet.sc new file mode 100644 index 0000000..75689b5 --- /dev/null +++ b/lab5/src/main/scala/jsy/lab5/Lab5.worksheet.sc @@ -0,0 +1,47 @@ +/* + * CSCI 3155: Lab 5 Worksheet + * + * This worksheet demonstrates how you could experiment + * interactively with your implementations in Lab5.scala. + */ +import jsy.lab5._ + +// Imports the parse function from Parser +import Parser.{parse,parseFile} + +// Imports the ast nodes +import ast._ + +// Imports all of the functions from Lab5 (your implementations in Lab5.scala) +import Lab5._ + +// Parse code with assignments +parse("var x = 1; x = 2; console.log(x)") +parse("const x = {f: 1}; x.f = 2; console.log(x.f)") + +// Parse code with null +// parse("null") +// parse("<Null>null") +// parse("<{f: number}>null") + +// Parse functions +parse("(x: number) => x") +parse("function (x: var number) { x = 0; return x }") +parse("var y = 1; (function (x: ref number) { x = 0; return x })(y)") +// parse("((x: name number) => 0)(console.log(100))") + +// Aliasing example +val aliasingex = parse(""" + const x = { f: 1 } + const y = x + x.f = 2 + console.log(y.f) +""") +//iterateStep(aliasingex) // uncomment when you are ready to test your step function. + +// Parse the JavaScripty expression in your worksheet +val worksheetJsy = parseFile("src/main/scala/jsy/lab5/Lab5.worksheet.ts") + +// Interpret the JavaScripty expression in your worksheet +//inferType(worksheetJsy) +//iterateStep(worksheetJsy)
\ No newline at end of file diff --git a/lab5/src/main/scala/jsy/lab5/Lab5.worksheet.ts b/lab5/src/main/scala/jsy/lab5/Lab5.worksheet.ts new file mode 100644 index 0000000..3fb3023 --- /dev/null +++ b/lab5/src/main/scala/jsy/lab5/Lab5.worksheet.ts @@ -0,0 +1,12 @@ +/* + * CSCI 3155: Lab 5 TypeScript Worksheet + * + * This worksheet is a place to experiment with TypeScript expressions. + */ + +// New language features in lab 5 are mutable parameters, and call-by-reference, along with objects +// Functional programming-wise, we work with higher-order functions + +const two_int = (a:number, b:number, f:(a:number,b:number)=>number) => f(a,b); +two_int(1,2, (a:number,b:number)=>a*b) + diff --git a/lab5/src/main/scala/jsy/lab5/Parser.scala b/lab5/src/main/scala/jsy/lab5/Parser.scala new file mode 100644 index 0000000..5517b2b --- /dev/null +++ b/lab5/src/main/scala/jsy/lab5/Parser.scala @@ -0,0 +1,359 @@ +/** + * + */ +package jsy.lab5 + +import jsy.lab5.ast._ +import scala.util.parsing.combinator._ +import scala.util.parsing.combinator.lexical.StdLexical +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 +import scala.collection.immutable.SortedMap + +trait JSTokens extends token.StdTokens { + case class FloatLiteral(chars: String) extends Token { + override def toString = chars + } +} + +class Lexer extends 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 + def floatLit: this.Parser[String] = + elem("float", _.isInstanceOf[lexical.FloatLiteral]) ^^ (_.chars) +} + +trait Parser { self: TokenParser => + /* 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 ::= seoeq + * 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] = + (mode ~ ident) ~ withpos("=" ~> expr) ^^ { + case mode ~ x ~ ((pos,e1)) => ((e2: Expr) => Decl(mode, x, e1, e2) setPos pos) + } /*| + ("interface" ~> ident) ~ withpos(record(";", TObj, ty)) ^^ { + case tyvar ~ ((pos,ty)) => ((e: Expr) => InterfaceDecl(tyvar, ty, e) setPos pos) + }*/ + + def mode: this.Parser[Mode] = + "const" ^^ { _ => MConst } | + //"name" ^^ { _ => MName } | + "var" ^^ { _ => MVar } | + "ref" ^^ { _ => MRef } + + 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] = + assign + + def assign: this.Parser[Expr] = + cond ~ opt(withpos(("=" ~> assign))) ^^ { + case e1 ~ None => e1 + case e1 ~ Some((pos,e2)) => Assign(e1, e2) setPos pos + } + + 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) }) | + call + + def uop: this.Parser[Expr => Expr] = + "-" ^^ (_ => (e: Expr) => Unary(Neg, e)) | + "!" ^^ (_ => (e: Expr) => Unary(Not, e)) /*| + "<" ~> ty <~ ">" ^^ (t => (e: Expr) => Unary(Cast(t), e))*/ + + def call: this.Parser[Expr] = + term ~ rep(callop | derefop) ^^ { case e0 ~ callderefs => (callderefs.foldLeft(e0)){ case (acc, mk) => mk(acc) } } + + def callop: this.Parser[Expr => Expr] = + withpos("(" ~> repsep(noseq, ",") <~ ")") ^^ { case (pos, args) => (e0 => Call(e0, args) setPos pos) } + + def derefop: this.Parser[Expr => Expr] = + withpos("." ~> ident) ^^ { case (pos,f) => (e0 => GetField(e0, f) setPos pos) } + + 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) | + //"null" ^^ (_ => Null) | + ("console" ~ "." ~ "log") ~> "(" ~> expr <~ ")" ^^ (e => Print(e)) | + function | + record(",", Obj, noseq) + ) | + "(" ~> expr <~ ")" | + "{" ~> "{" ~> prog <~ "}" <~ "}" | + failure("atomic expression expected") + + def jsy: this.Parser[Expr] = + "jsy" ~> "." ~> withpos("print") ~ ("(" ~> expr <~ ")") ^^ { + case ((pos, _)) ~ e => Print(e) setPos pos + } + + def function: this.Parser[Expr] = + ("function" ~> opt(ident)) ~ ("(" ~> repsep(colonpair(modety), ",") <~ ")") ~ opt(":" ~> ty) ~ ("{" ~> stmts ~ ret <~ rep(empty_stmt) <~ "}") ^^ { + case f ~ params ~ retty ~ (stmts ~ ret) => + val body = stmts(Some(ret)) + Fun(f, params, retty, body) + } | + ("(" ~> repsep(colonpair(modety), ",") <~ ")") ~ (withpos("=>" ~> noseq)) ^^ { + case params ~ ((pos, body)) => Fun(None, params, None, body) setPos pos + } + + def ret: this.Parser[Expr] = + "return" ~> expr + + def record[A](sep: String, node: SortedMap[String, A] => A, q: => this.Parser[A]): this.Parser[A] = { + lazy val p = q + "{" ~> repsep(colonpair(p), sep) <~ (opt(",") ~ "}") ^^ { flist => + val fmap = flist.foldLeft(SortedMap.empty: SortedMap[String, A]){ + case (fmap, (f, e)) => fmap + (f -> e) + } + node(fmap) + } + } + + def colonpair[A](q: => this.Parser[A]): this.Parser[(String, A)] = { + lazy val p = q + ident ~ (":" ~> p) ^^ { case f ~ e => (f, e) } + } + + def ty: this.Parser[Typ] = + ident ^^ TVar | + "number" ^^ (_ => TNumber) | + "bool" ^^ (_ => TBool) | + "string" ^^ (_ => TString) | + "Undefined" ^^ (_ => TUndefined) | + //"Null" ^^ (_ => TNull) | + record(";", TObj, ty) | + tyfunction | + failure("type expected") + + def tyfunction: this.Parser[Typ] = + ("(" ~> repsep(colonpair(modety), ",") <~ ")") ~ ("=>" ~> ty) ^^ { + case params ~ t2 => TFun(params, t2) + } + + def modety: this.Parser[MTyp] = + mode ~ ty ^^ { case mode ~ ty => MTyp(mode, ty) } | + ty ^^ { case ty => MTyp(MConst, ty) } + + 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)) + + protected var parseSource: String = "<source>" + + def formatErrorMessage(pos: Position, kind: String, msg: String, longString: Boolean = false): String = + if (pos != NoPosition) + if (longString) + "%s\n%s:%s:%s: %s\n%s".format(kind, parseSource, pos.line, pos.column, msg, pos.longString) + else + "%s\n%s:%s:%s: %s".format(kind, parseSource, pos.line, pos.column, msg) + else + "%s\n%s: %s".format(kind, parseSource, msg) + + case class SyntaxError(msg: String, pos: Position) extends Exception { + override def toString = formatErrorMessage(pos, "SyntaxError", msg, true) + } +} + +object Parser extends Parser with TokenParser { + type Tokens = JSTokens + val lexical = new Lexer + + /* 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("-", "!", ";", ",", "+", "*", "/", "=", "===", "!==", "<", "<=", ">", ">=", "&&", "||", "(", ")", ".", "{", "}", "?", ":", "=>") + + def parseTokens(tokens: lexical.Scanner): Expr = { + phrase(prog)(tokens) match { + case Success(e, _) => e + case NoSuccess(msg, next) => throw SyntaxError(msg, next.pos) + case Error(msg,next) => throw SyntaxError(msg, next.pos) + case Failure(msg, next) => throw SyntaxError(msg, next.pos) + } + } + + def parseTypTokens(tokens: lexical.Scanner): Typ = { + phrase(ty)(tokens) match { + case Success(t, _) => t + case NoSuccess(msg, next) => throw SyntaxError(msg, next.pos) + case Error(msg,next) => throw SyntaxError(msg, next.pos) + case Failure(msg, next) => throw SyntaxError(msg, next.pos) + } + } + + /*** 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 parseTyp(s: String): Typ = { + parseTypTokens(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 + } + + implicit class StringTyp(s: String) { + val typ = parseTyp(s) + def t: Typ = typ + } +}
\ No newline at end of file diff --git a/lab5/src/main/scala/jsy/lab5/ast.scala b/lab5/src/main/scala/jsy/lab5/ast.scala new file mode 100644 index 0000000..c5ecb99 --- /dev/null +++ b/lab5/src/main/scala/jsy/lab5/ast.scala @@ -0,0 +1,321 @@ +package jsy.lab5 + +import scala.collection.immutable.SortedMap +import scala.util.parsing.input.Positional +import jsy.util.DoWith +import jsy.util.Visitor + +/** + * @author Bor-Yuh Evan Chang + */ +class ast { + abstract class Expr extends Positional + + /* Variables */ + case class Var(x: String) extends Expr + + /* Declarations */ + case class Decl(m: Mode, x: String, e1: Expr, e2: Expr) extends Expr + + /* Literals and Values*/ + case class N(n: Double) extends Expr + case class B(b: Boolean) extends Expr + case object Undefined extends Expr + case class S(s: String) extends Expr + + /* Unary and Binary Operators */ + case class Unary(uop: Uop, e1: Expr) extends Expr + case class Binary(bop: Bop, e1: Expr, e2: Expr) extends Expr + + sealed abstract class Uop + + case object Neg extends Uop /* -e1 */ + case object Not extends Uop /* !e1 */ + + sealed abstract class Bop + + case object Plus extends Bop /* e1 + e2 */ + case object Minus extends Bop /* e1 - e2 */ + case object Times extends Bop /* e1 * e2 */ + case object Div extends Bop /* e1 / e2 */ + case object Eq extends Bop /* e1 === e2 */ + case object Ne extends Bop /* e1 !=== e2 */ + case object Lt extends Bop /* e1 < e2 */ + case object Le extends Bop /* e1 <= e2 */ + case object Gt extends Bop /* e1 > e2 */ + case object Ge extends Bop /* e1 >= e2 */ + + case object And extends Bop /* e1 && e2 */ + case object Or extends Bop /* e1 || e2 */ + + case object Seq extends Bop /* , */ + + /* Intraprocedural Control */ + case class If(e1: Expr, e2: Expr, e3: Expr) extends Expr + + /* Functions */ + case class Fun(p: Option[String], params: List[(String,MTyp)], tann: Option[Typ], e1: Expr) extends Expr + case class Call(e1: Expr, args: List[Expr]) extends Expr + + /* I/O */ + case class Print(e1: Expr) extends Expr + + /* Objects */ + case class Obj(fields: SortedMap[String, Expr]) extends Expr + case class GetField(e1: Expr, f: String) extends Expr + + /* Addresses and Mutation */ + case class Assign(e1: Expr, e2: Expr) extends Expr + case class A private[ast] (addr: Int) extends Expr + case object Deref extends Uop /* *e1 */ + + /* Types */ + abstract class Typ + case object TNumber extends Typ + case object TBool extends Typ + case object TString extends Typ + case object TUndefined extends Typ + case class TFun(params: List[(String,MTyp)], tret: Typ) extends Typ { + override def equals(other: Any) = other.isInstanceOf[TFun] && { + other match { + case TFun(oparams, otret) if otret == tret && oparams.length == params.length => + (oparams zip params).forall { case ((_, omt), (_, mt)) => omt == mt } + case _ => false + } + } + } + case class TObj(tfields: SortedMap[String, Typ]) extends Typ + case class TVar(tvar: String) extends Typ + + /* Parameter Modes */ + sealed abstract class Mode + case object MConst extends Mode + case object MVar extends Mode + case object MRef extends Mode + + /* Parameter Types */ + case class MTyp(m: Mode, t: Typ) + + /* Extension: Call-By-Name */ + //case object MName extends Mode + + /* Extension: Casting and Null */ + //case class Cast(t: Typ) extends Uop + //case object Null extends Expr + //case object TNull extends Typ + + /* Extension: Interfaces and Recursive Types */ + //case class InterfaceDecl(tvar: String, t: Typ, e: Expr) extends Expr + //case class TInterface(tvar: String, t: Typ) extends Typ + + /* + * Memory + * + * Note that the memempty and memalloc functions would idiomatically be in + * the companion object to this class, but we do not have a companion object to + * avoid introducing this feature. + */ + class Mem private[ast] (map: Map[A, Expr], nextAddr: Int) { + def apply(key: A): Expr = map(key) + def get(key: A): Option[Expr] = map.get(key) + def +(kv: (A, Expr)): Mem = new Mem(map + kv, nextAddr) + def contains(key: A): Boolean = map.contains(key) + + private[ast] def alloc(v: Expr): (Mem, A) = { + val fresha = A(nextAddr) + (new Mem(map + (fresha -> v), nextAddr + 1), fresha) + } + + override def toString: String = map.toString + } + + def memempty: Mem = new Mem(Map.empty, 1) + def memalloc(k: Expr): DoWith[Mem, A] = DoWith.doget flatMap { m => + val (mp, a) = m.alloc(k) + DoWith.doput(mp) map { _ => a } + } + + /* Define values. */ + def isValue(e: Expr): Boolean = e match { + case N(_) | B(_) | Undefined | S(_) | Fun(_, _, _, _) | A(_) /*| Null*/ => true + case _ => false + } + + def isLValue(e: Expr): Boolean = e match { + case Unary(Deref, A(_)) | GetField(A(_), _) => true + case _ => false + } + + /* + * Pretty-print values. + * + * We do not override the toString method so that the abstract syntax can be printed + * as is. + */ + protected def prettyVal: Visitor[Expr,String] = Visitor(rec => { + case N(n) => n.toString + case B(b) => b.toString + case Undefined => "undefined" + case S(s) => s + case Fun(xopt, _, _, _) => + "[Function%s]".format(xopt match { case None => " (anonymous)" case Some(s) => ": " + s }) + case Obj(fields) => + val pretty_fields = + fields map { + case (f, S(s)) => f + ": '" + s + "'" + case (f, v) => f + ": " + rec(v) + } reduceRightOption { + (s, acc) => s + ",\n " + acc + } + "{ %s }".format(pretty_fields.getOrElse("")) + //case Null => "null" + case A(i) => "0x%x".format(i) + }) + + def pretty(v: Expr): String = prettyVal(v) + + protected def prettyValMem: Visitor[Expr, Mem => String] = prettyVal flatMap { pv => Visitor(rec => { + case a @ A(_) => { m => if (m contains a) rec(m(a))(m) else pv(a) } + case Obj(fields) => { m => + val pretty_fields = + fields map { + case (f, S(s)) => f + ": '" + s + "'" + case (f, v) => f + ": " + rec(v)(m) + } reduceRightOption { + (s, acc) => s + ",\n " + acc + } + "{ %s }".format(pretty_fields.getOrElse("")) + } + case v => { _ => pv(v) } + })} + + def pretty(m: Mem, v: Expr): String = { + require(isValue(v)) + prettyValMem(v)(m) + } + + /* + * Pretty-print types. + * + * We do not override the toString method so that the abstract syntax can be printed + * as is. + */ + protected def prettyTyp: Visitor[Typ,String] = Visitor(rec => { + case TNumber => "number" + case TBool => "bool" + case TString => "string" + case TUndefined => "Undefined" + case TFun(params, tret) => { + val pretty_params = + params map { case (x,mt) => "%s: %s".format(x, pretty(mt)) } reduceRightOption { + (s, acc) => s + ", " + acc + } + "(%s) => %s".format(pretty_params.getOrElse(""), rec(tret)) + } + case TObj(tfields) => + val pretty_fields: Option[String] = + tfields map { case (f,t) => "%s: %s".format(f, rec(t)) } reduceRightOption { + (s, acc) => s + "; " + acc + } + "{ %s }".format(pretty_fields.getOrElse("")) + //case TNull => "Null" + case TVar(tvar) => tvar + //case TInterface(tvar, t1) => "Interface %s %s".format(tvar, rec(t1)) + }) + + def pretty(t: Typ): String = prettyTyp(t) + + + def pretty(mty: MTyp): String = mty match { + case MTyp(MConst, ty) => s"${pretty(ty)}" + case MTyp(mode, ty) => s"${pretty(mode)} ${pretty(ty)}" + } + + def pretty(mode: Mode): String = mode match { + case MConst => "const" + //case MName => "name" + case MVar => "var" + case MRef => "ref" + } + + /* Get the free variables of e. */ + protected def freeVarsVar: Visitor[Expr,Set[Var]] = Visitor(fv => { + case vr@Var(x) => Set(vr) + case Decl(_, x, e1, e2) => fv(e1) | (fv(e2) - Var(x)) + case Fun(p, params, _, e1) => { + val boundvars = (params map { case (x, _) => Var(x) }) ++ (p map Var) + fv(e1) -- boundvars + } + case N(_) | B(_) | Undefined | S(_) /*| Null*/ | A(_) => Set.empty + case Unary(_, e1) => fv(e1) + case Binary(_, e1, e2) => fv(e1) | fv(e2) + case If(e1, e2, e3) => fv(e1) | fv(e2) | fv(e3) + case Call(e1, args) => fv(e1) | args.foldLeft(Set.empty: Set[Var]) { + ((acc: Set[Var], ei) => acc | fv(ei)) + } + case Print(e1) => fv(e1) + case Obj(fields) => fields.foldLeft(Set.empty: Set[Var])({ case (acc, (_, ei)) => acc | fv(ei) }) + case GetField(e1, _) => fv(e1) + case Assign(e1, e2) => fv(e1) | fv(e2) + //case InterfaceDecl(_, _, e1) => fv(e1) + }) + + def freeVars(e: Expr): Set[String] = freeVarsVar(e) map { case Var(x) => x } + + /* Check closed expressions. */ + def closed(e: Expr): Boolean = freeVarsVar(e).isEmpty + + def checkClosed(e: Expr): Unit = { + freeVarsVar(e).headOption.foreach { x => throw new UnboundVariableError(x) } + } + /* Dynamic Type Error. Extends Expr for simplicity. */ + case class DynamicTypeError(e: Expr) extends Expr { + override def toString = Parser.formatErrorMessage(e.pos, "DynamicTypeError", "in evaluating " + e) + } + + /* Null Dereference Error exception. Extends Expr for simplicity. */ + case class NullDereferenceError(e: Expr) extends Expr { + override def toString = Parser.formatErrorMessage(e.pos, "NullDereferenceError", "in evaluating " + e) + } + + /* + * Unbound Variable Error exception. Throw this exception to signal an unbound variable. + */ + case class UnboundVariableError(x: Var) extends Exception { + override def toString = + Parser.formatErrorMessage(x.pos, "UnboundVariableError", "unbound variable %s".format(x.x)) + } + + /* + * Static Type Error exception. Throw this exception to signal a static + * type error. + * + * throw StaticTypeError(tbad, esub, e) + * + */ + case class StaticTypeError(tbad: Typ, esub: Expr, e: Expr) extends Exception { + override def toString = + Parser.formatErrorMessage(esub.pos, "StaticTypeError", "invalid type %s for sub-expression %s in %s".format(pretty(tbad), esub, e)) + } + + /* + * Stuck Error exception. Throw this exception to signal getting + * stuck in evaluation. This exception should not get raised if + * evaluating a well-typed expression. + * + * throw StuckError(e) + * + */ + case class StuckError(e: Expr) extends Exception { + override def toString = Parser.formatErrorMessage(e.pos, "StuckError", "in evaluating " + e) + } + + /* + * Termination Error exception. Throw this exception to signal exceeding maximum number of allowed steps. + */ + case class TerminationError(e: Expr, n: Int) extends Exception { + override def toString = Parser.formatErrorMessage(e.pos, "TerminationError", s"exceeded ${n} steps in evaluating ${e}") + } +} + +object ast extends ast
\ No newline at end of file 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) +} |