commit
acf15f81fe
@ -0,0 +1,12 @@
|
||||
scalaVersion := "2.12.10"
|
||||
|
||||
scalacOptions in ThisBuild ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||
|
||||
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.1" % "test"
|
||||
|
||||
excludeFilter in unmanagedSources := HiddenFileFilter || "*sample*"
|
||||
|
||||
logBuffered in Test := false
|
||||
|
||||
parallelExecution in Test := false
|
||||
|
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
find . -name target -type d -prune -exec rm -rf {} \;
|
@ -0,0 +1 @@
|
||||
1 + 3 -- -1
|
@ -0,0 +1 @@
|
||||
val x = y; z
|
@ -0,0 +1 @@
|
||||
val x = 0; x = 1
|
@ -0,0 +1 @@
|
||||
1 + 3 4
|
@ -0,0 +1 @@
|
||||
if (0 == 0) 2
|
@ -0,0 +1 @@
|
||||
val x == 4; x
|
@ -0,0 +1,5 @@
|
||||
while (2 + 2) {
|
||||
1
|
||||
};
|
||||
2
|
||||
|
@ -0,0 +1,2 @@
|
||||
var x = 0
|
||||
x + 1
|
@ -0,0 +1,2 @@
|
||||
// Test
|
||||
&
|
@ -0,0 +1 @@
|
||||
1* -4 + 4/2
|
@ -0,0 +1 @@
|
||||
if (2 > 4) 1 + 4 else 4
|
@ -0,0 +1 @@
|
||||
val x = 0; x
|
@ -0,0 +1,7 @@
|
||||
var x = 2;
|
||||
var y = 0;
|
||||
while ({ if (y < 3) true else y < 4}) {
|
||||
x = x * x;
|
||||
y = y + 1
|
||||
};
|
||||
x
|
@ -0,0 +1 @@
|
||||
var x = 0; x = (x = x + 2) - 1
|
@ -0,0 +1,12 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// assembly function that we are compiling
|
||||
int entry_point(char* heap);
|
||||
|
||||
int main() {
|
||||
char* heap = malloc(10000 * 8);
|
||||
printf("Exit Code: %d\n", entry_point(heap));
|
||||
free(heap);
|
||||
return 0;
|
||||
}
|
@ -0,0 +1 @@
|
||||
sbt.version=1.2.8
|
@ -0,0 +1,318 @@
|
||||
package project3
|
||||
|
||||
abstract class X86Compiler extends BugReporter with Codegen {
|
||||
import Language._
|
||||
|
||||
/*
|
||||
* Abstract class used to store the location
|
||||
*/
|
||||
abstract class Loc {
|
||||
def +(y: Int): Loc
|
||||
}
|
||||
|
||||
/*
|
||||
* Register location, the physical location
|
||||
* can be addressed with the register #sp
|
||||
*/
|
||||
case class Reg(sp: Int) extends Loc {
|
||||
def +(y: Int) = Reg(sp+y)
|
||||
}
|
||||
|
||||
/*
|
||||
* Function location, the physical location
|
||||
* can be addressed directly with the name
|
||||
*/
|
||||
case class Func(name: String) extends Loc {
|
||||
def +(y: Int) = BUG("This Loc should not be used as a stack location.")
|
||||
}
|
||||
|
||||
// Function to extra physical address from Loc
|
||||
// CHANGE: instead of using regs(...) directly
|
||||
// we now use the function loc.
|
||||
def loc(l: Loc): String = l match {
|
||||
case Reg(sp) => avRegs(sp)
|
||||
case Func(name) => name
|
||||
}
|
||||
|
||||
def loc(sp: Int): String = avRegs(sp)
|
||||
|
||||
// List of available register.
|
||||
// DO NOT CHANGE THE REGISTERS!!
|
||||
val avRegs = Seq("%rdi", "%rsi", "%rdx", "%rcx", "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15")
|
||||
|
||||
/****************************************************************************/
|
||||
|
||||
def onMac = System.getProperty("os.name").toLowerCase contains "mac"
|
||||
val entry_point = "entry_point"
|
||||
def funcName(name: String) = (if (onMac) "_" else "") + name
|
||||
|
||||
/**
|
||||
* Env of the compiler. Keep track of the location
|
||||
* in memory of each variable defined.
|
||||
*/
|
||||
val primitives = Map(
|
||||
"putchar" -> Func("putchar"),
|
||||
"getchar" -> Func("getchar"))
|
||||
|
||||
private class Env {
|
||||
def undef(name: String) = BUG(s"Undefined identifier $name (should have been found during the semantic analysis)")
|
||||
def apply(name: String): Loc = undef(name)
|
||||
}
|
||||
|
||||
private case class LocationEnv(
|
||||
vars: Map[String, Loc] = primitives,
|
||||
outer: Env = new Env) extends Env {
|
||||
|
||||
/*
|
||||
* Return a copy of the current state plus a
|
||||
* variable 'name' at the location 'loc'
|
||||
*/
|
||||
def withVal(name: String, loc: Loc): LocationEnv = {
|
||||
copy(vars = vars + (name -> loc))
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a copy of the current state plus all
|
||||
* variables in 'list'
|
||||
*/
|
||||
def withVals(list: List[(String,Loc)]): LocationEnv = {
|
||||
copy(vars = vars ++ list.toMap)
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the location of the variable 'name'
|
||||
*/
|
||||
override def apply(name: String): Loc = vars.get(name) match {
|
||||
case Some(loc) => loc
|
||||
case _ => outer(name)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate code that computes the unary operator
|
||||
* 'op' on the value at memory location 'sp' and that
|
||||
* stores the result at 'sp'.
|
||||
*/
|
||||
def transUn(op: String)(sp: Loc) = op match {
|
||||
case "+" => () // nothing to do!
|
||||
case "-" => emitln(s"negq ${loc(sp)}")
|
||||
case _ => BUG(s"Unary operator $op undefined")
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate code that computes the binary operator
|
||||
* 'op' on the values at memory location 'sp' and
|
||||
* 'sp1' and that stores the result at 'sp'.
|
||||
*
|
||||
* TODO: implement missing operators.
|
||||
* Here are the valid operators:
|
||||
* +, -, *, /, ==, !=, <=, <, >=, >, block-get
|
||||
*/
|
||||
def transBin(op: String)(sp: Loc, sp1: Loc) = op match {
|
||||
case "+" => emitln(s"addq ${loc(sp1)}, ${loc(sp)}")
|
||||
case "-" => emitln(s"subq ${loc(sp1)}, ${loc(sp)}")
|
||||
case "*" => emitln(s"imul ${loc(sp1)}, ${loc(sp)}")
|
||||
case "/" =>
|
||||
emitln(s"movq ${loc(sp)}, %rax")
|
||||
emitln(s"pushq %rdx") // save $rdx for the division
|
||||
emitln(s"movq ${loc(sp1)}, %rbx") // in case sp1 == %rdx
|
||||
emitln(s"cqto")
|
||||
emitln(s"idiv %rbx")
|
||||
emitln(s"popq %rdx") // put back
|
||||
emitln(s"movq %rax, ${loc(sp)}")
|
||||
case "==" =>
|
||||
emitln(s"cmp ${loc(sp1)}, ${loc(sp)}")
|
||||
emitln(s"sete %al")
|
||||
emitln(s"movzbq %al, ${loc(sp)}")
|
||||
case _ => BUG(s"Binary operator $op undefined")
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate code that computes the ternary operator
|
||||
* 'op' on the values at memory location 'sp', 'sp1 and'
|
||||
* 'sp2' and that stores the result at 'sp'.
|
||||
*
|
||||
* TODO: implement the missing operator
|
||||
* Valid operators: block-set
|
||||
*/
|
||||
def transTer(op: String)(sp: Loc, sp1: Loc, sp2: Loc) = op match {
|
||||
case _ => BUG(s"ternary operator $op undefined")
|
||||
}
|
||||
|
||||
def transPrim(op: String)(idxs: List[Loc]) = idxs match {
|
||||
case List(sp, sp1, sp2) => transTer(op)(sp, sp1, sp2)
|
||||
case List(sp, sp1) => transBin(op)(sp, sp1)
|
||||
case List(sp) => transUn(op)(sp)
|
||||
case _ => BUG(s"no prim with ${idxs.length} arguments")
|
||||
}
|
||||
|
||||
type Label = String
|
||||
|
||||
var nLabel = 0
|
||||
def freshLabel(pref: String) = { nLabel += 1; s"$pref$nLabel" }
|
||||
|
||||
/*
|
||||
* Generate code that compute the result of the
|
||||
* computation represented by the AST 'exp'.
|
||||
*/
|
||||
val global = (primitives.keySet + entry_point) map(funcName(_))
|
||||
def emitCode(exp: Exp): Unit = {
|
||||
emitln(".text", 0)
|
||||
emitln(s".global ${global mkString ", "}\n", 0)
|
||||
|
||||
// Generate code for our AST
|
||||
trans(exp, Reg(0))(LocationEnv())
|
||||
|
||||
emitln("#################### DATA #######################", 0)
|
||||
emitln("\n.data\nheap:\t.quad 0",0)
|
||||
emitln("#################################################", 0)
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate code that jump to the label 'label'
|
||||
* if the location 'sp' contains the value 'true'
|
||||
*/
|
||||
def transJumpIfTrue(sp: Loc)(label: Label) = {
|
||||
???
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate code that compute the result og the
|
||||
* computation represented by the AST 'exp'. The
|
||||
* value will be placed at memory location 'sp'
|
||||
*
|
||||
* TODO: Fill in each TODO with the appropriate code.
|
||||
*
|
||||
* The ??? can be filled for extra credit.
|
||||
*/
|
||||
def trans(exp: Exp, sp: Loc)(env: LocationEnv): Unit = exp match {
|
||||
case Lit(x: Int) =>
|
||||
emitln(s"movq $$$x, ${loc(sp)}")
|
||||
case Lit(b: Boolean) => () // TODO
|
||||
case Lit(x: Unit) => () // TODO
|
||||
case Prim(op, args) =>
|
||||
val idxs = List.tabulate(args.length)(i => sp + i)
|
||||
(args zip idxs) foreach { case (arg, idx) => trans(arg, idx)(env) }
|
||||
transPrim(op)(idxs)
|
||||
case Let(x, tp, rhs, body) =>
|
||||
trans(rhs, sp)(env)
|
||||
if (tp == UnitType) { // simple optimization for Daniel
|
||||
trans(body, sp)(env)
|
||||
} else {
|
||||
trans(body, sp + 1)(env.withVal(x, sp))
|
||||
emitln(s"movq ${loc(sp + 1)}, ${loc(sp)}")
|
||||
}
|
||||
case Ref(x) =>
|
||||
env(x) match {
|
||||
case Reg(sp1) => emitln(s"movq ${loc(sp1)}, ${loc(sp)}")
|
||||
case Func(name) => ??? // Extra credit
|
||||
}
|
||||
case If(cond, tBranch, eBranch) =>
|
||||
val lab = freshLabel("if")
|
||||
trans(cond, sp)(env)
|
||||
transJumpIfTrue(sp)(s"${lab}_then")
|
||||
trans(eBranch, sp)(env)
|
||||
emitln(s"jmp ${lab}_end")
|
||||
emitln(s"${lab}_then:", 0)
|
||||
trans(tBranch, sp)(env)
|
||||
emitln(s"${lab}_end:", 0)
|
||||
case VarDec(x, tp, rhs, body) =>
|
||||
trans(rhs, sp)(env)
|
||||
trans(body, sp + 1)(env.withVal(x, sp))
|
||||
emitln(s"movq ${loc(sp + 1)}, ${loc(sp)}")
|
||||
case VarAssign(x, rhs) =>
|
||||
trans(rhs, sp)(env)
|
||||
emitln(s"movq ${loc(sp)}, ${loc(env(x))}")
|
||||
case While(cond, lBody, body) =>
|
||||
val lab = freshLabel("loop")
|
||||
emitln(s"jmp ${lab}_cond")
|
||||
emitln(s"${lab}_body:", 0)
|
||||
trans(lBody, sp)(env)
|
||||
emitln(s"${lab}_cond:", 0)
|
||||
trans(cond, sp)(env)
|
||||
transJumpIfTrue(sp)(s"${lab}_body")
|
||||
trans(body, sp)(env)
|
||||
case LetRec(funs, body) =>
|
||||
emitln("################# FUNCTIONS #####################", 0)
|
||||
// We do not save the location of the function into register because we can use their
|
||||
// name as a label.
|
||||
val funsLoc = funs map { case FunDef(name, _, _, _) => (name, Func(name)) }
|
||||
|
||||
// TODO complete the code
|
||||
|
||||
emitln("#################################################\n\n", 0)
|
||||
emitln("###################### MAIN #####################", 0)
|
||||
//////////// DO NOT CHANGE////////////////
|
||||
emitln(s"${funcName(entry_point)}:", 0)
|
||||
emitln("pushq %rbp\t# save stack frame for calling convention")
|
||||
emitln("movq %rsp, %rbp")
|
||||
emitln("movq %rdi, heap(%rip)")
|
||||
|
||||
emitln("pushq %rbx")
|
||||
emitln("pushq %r12")
|
||||
emitln("pushq %r13")
|
||||
emitln("pushq %r14")
|
||||
emitln("pushq %r15")
|
||||
//////////////////////////////////////////
|
||||
|
||||
// emit the main function (body of LetRec) here
|
||||
// TODO you may need to change that code.
|
||||
trans(body, Reg(0))(LocationEnv())
|
||||
emitln(s"movq ${loc(0)}, %rax")
|
||||
|
||||
//////////// DO NOT CHANGE////////////////
|
||||
emitln("popq %r15")
|
||||
emitln("popq %r14")
|
||||
emitln("popq %r13")
|
||||
emitln("popq %r12")
|
||||
emitln("popq %rbx")
|
||||
emitln("movq %rbp, %rsp\t# reset frame")
|
||||
emitln("popq %rbp")
|
||||
emitln("ret")
|
||||
emitln("#################################################\n\n", 0)
|
||||
//////////////////////////////////////////
|
||||
|
||||
case FunDef(fname, args, _, fbody) =>
|
||||
//////////// DO NOT CHANGE////////////////
|
||||
emitln(s"${funcName(fname)}:", 0)
|
||||
emitln("pushq %rbp\t# save stack frame for calling convention")
|
||||
emitln("movq %rsp, %rbp")
|
||||
//////////////////////////////////////////
|
||||
|
||||
// TODO
|
||||
|
||||
//////////// DO NOT CHANGE////////////////
|
||||
emitln("movq %rbp, %rsp\t# reset frame")
|
||||
emitln("popq %rbp")
|
||||
emitln("ret\n")
|
||||
//////////////////////////////////////////
|
||||
case App(fun, args) =>
|
||||
// Advice: you may want to start to work on functions with only one argument
|
||||
// i.e. change args to List(arg). Once it is working you can generalize your
|
||||
// code and work on multiple arguments.
|
||||
// Evaluate the arguments
|
||||
// TODO
|
||||
|
||||
// Compute the physical location of the function to be called
|
||||
val fLoc: String = fun match {
|
||||
case Ref(fname) =>
|
||||
env(fname) match {
|
||||
case Reg(sp) => ??? // Extra credit
|
||||
case Func(name) => "" // TODO
|
||||
}
|
||||
case _ => ??? // Extra credit
|
||||
}
|
||||
|
||||
// Implement the calling conventions after that point
|
||||
// and generate the function call
|
||||
// TODO
|
||||
()
|
||||
case ArrayDec(size, _) =>
|
||||
// This node needs to allocate an area of eval(size) * 8 bytes in the heap
|
||||
// the assembly variable "heap" contains a pointer to the first valid byte
|
||||
// in the heap. Make sure to update its value accordingly.
|
||||
// TODO
|
||||
()
|
||||
case _ => BUG(s"don't know how to implement $exp")
|
||||
}
|
||||
}
|
@ -0,0 +1,415 @@
|
||||
package project3
|
||||
|
||||
abstract class Interpreter {
|
||||
type MyVal
|
||||
def run(ast: Language.Exp): MyVal
|
||||
}
|
||||
|
||||
/**
|
||||
* This interpreter specifies the semantics of our
|
||||
* programming language.
|
||||
*
|
||||
* The evaluation of each node returns a value.
|
||||
*/
|
||||
class ValueInterpreter extends Interpreter with BugReporter {
|
||||
import Language._
|
||||
|
||||
/*
|
||||
* Values for primitives operators. Already stored in the environment.
|
||||
*/
|
||||
val primitives = Map[String, BoxedVal](
|
||||
"putchar" -> BoxedVal(Primitive("putchar")),
|
||||
"getchar" -> BoxedVal(Primitive("getchar"))
|
||||
)
|
||||
|
||||
/*
|
||||
* Definition of the values of our language
|
||||
*
|
||||
* We can return constant Int, Boolean, Unit or Array.
|
||||
* We can also return Function. Primitive is a special
|
||||
* kind of function.
|
||||
*/
|
||||
abstract class Val
|
||||
case class Cst(x: Any) extends Val {
|
||||
override def toString = if (x != null) x.toString else "null"
|
||||
}
|
||||
case class Func(args: List[String], fbody: Exp, var env: ValueEnv) extends Val {
|
||||
// Add all the value into the existing environment
|
||||
def withVals(list: List[(String,Val)]) = env = env.withVals(list)
|
||||
override def toString = s"(${args mkString ","}) => $fbody"
|
||||
}
|
||||
case class Primitive(name: String) extends Val
|
||||
|
||||
type MyVal = Val
|
||||
|
||||
/**
|
||||
* Env of the interpreter. Keeps track of the value
|
||||
* of each variable defined.
|
||||
*/
|
||||
class Env {
|
||||
def undef(name: String) =
|
||||
BUG(s"Undefined identifier $name (should have been found during the semantic analysis)")
|
||||
|
||||
def updateVar(name: String, v: Val): Val = undef(name)
|
||||
def apply(name: String): Val = undef(name)
|
||||
}
|
||||
|
||||
case class BoxedVal(var v: Val)
|
||||
case class ValueEnv(
|
||||
vars: Map[String, BoxedVal] = primitives,
|
||||
outer: Env = new Env) extends Env {
|
||||
|
||||
/*
|
||||
* Return a copy of the current state plus an immutable
|
||||
* variable 'name' of value 'v'
|
||||
*/
|
||||
def withVal(name: String, v: Val): ValueEnv = {
|
||||
copy(vars = vars + (name -> BoxedVal(v)))
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a copy of the current state plus all the immutables
|
||||
* variable in list.
|
||||
*/
|
||||
def withVals(list: List[(String,Val)]): ValueEnv = {
|
||||
copy(vars = vars ++ (list.map {case (n, v) => n -> BoxedVal(v) }))
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the variable 'name' in this scope or in the
|
||||
* outer scope.
|
||||
* Return the new value of the variable
|
||||
*/
|
||||
override def updateVar(name: String, v: Val): Val = {
|
||||
if (vars.contains(name))
|
||||
vars(name).v = v
|
||||
else
|
||||
outer.updateVar(name, v)
|
||||
v
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the value of the variable 'name'
|
||||
*/
|
||||
override def apply(name: String): Val = {
|
||||
if (vars.contains(name))
|
||||
vars(name).v
|
||||
else
|
||||
outer(name)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute and return the result of the unary
|
||||
* operation 'op' on the value 'v'
|
||||
*/
|
||||
def evalUn(op: String)(v: Val) = (op, v) match {
|
||||
case ("-", Cst(v: Int)) => Cst(-v)
|
||||
case ("+", Cst(_: Int)) => v
|
||||
case _ => BUG(s"unary operator $op undefined")
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute and return the result of the binary
|
||||
* operation 'op' on the value 'v' and 'w'
|
||||
* Note: v op w
|
||||
*/
|
||||
def evalBin(op: String)(v: Val, w: Val) = (op, v, w) match {
|
||||
case ("-", Cst(v: Int), Cst(w: Int)) => Cst(v-w)
|
||||
case ("+", Cst(v: Int), Cst(w: Int)) => Cst(v+w)
|
||||
case ("*", Cst(v: Int), Cst(w: Int)) => Cst(v*w)
|
||||
case ("/", Cst(v: Int), Cst(w: Int)) => Cst(v/w)
|
||||
case ("==", Cst(v: Int), Cst(w: Int)) => Cst(v == w)
|
||||
case ("!=", Cst(v: Int), Cst(w: Int)) => Cst(v != w)
|
||||
case ("<=", Cst(v: Int), Cst(w: Int)) => Cst(v <= w)
|
||||
case (">=", Cst(v: Int), Cst(w: Int)) => Cst(v >= w)
|
||||
case ("<" , Cst(v: Int), Cst(w: Int)) => Cst(v < w)
|
||||
case (">" , Cst(v: Int), Cst(w: Int)) => Cst(v > w)
|
||||
case ("block-get", Cst(arr: Array[Any]), Cst(i: Int)) =>
|
||||
if (arr(i) == null)
|
||||
BUG(s"uninitialized memory")
|
||||
Cst(arr(i))
|
||||
case _ => BUG(s"binary operator $op undefined")
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute and return the result of the ternary
|
||||
* operations 'op' on the value 'v', 'w' and 'z'
|
||||
*/
|
||||
def evalTer(op: String)(v: Val, w: Val, z: Val) = (op, v, w, z) match {
|
||||
case ("block-set", Cst(arr: Array[Any]), Cst(i: Int), Cst(x)) => Cst(arr(i) = x)
|
||||
case _ => BUG(s"ternary operator $op undefined")
|
||||
}
|
||||
|
||||
def evalPrim(op: String)(eargs: List[Val]) = eargs match {
|
||||
case List(v, w, z) => evalTer(op)(v, w, z)
|
||||
case List(v, w) => evalBin(op)(v, w)
|
||||
case List(v) => evalUn(op)(v)
|
||||
case _ => BUG(s"no prim with ${eargs.length} arguments")
|
||||
}
|
||||
|
||||
/*
|
||||
* Evaluate the AST starting with an empty Env
|
||||
*/
|
||||
def run(exp: Exp) = eval(exp)(ValueEnv())
|
||||
|
||||
/*
|
||||
* Evaluate the AST within the environment 'env'
|
||||
*/
|
||||
def eval(exp: Exp)(env: ValueEnv): Val = exp match {
|
||||
case Lit(x) => Cst(x)
|
||||
case Prim(op, args) =>
|
||||
val eargs = args map { arg => eval(arg)(env) }
|
||||
evalPrim(op)(eargs)
|
||||
case Let(x, tp, a, b) =>
|
||||
eval(b)(env.withVal(x, eval(a)(env)))
|
||||
case Ref(x) =>
|
||||
env(x)
|
||||
case If(cond, tBranch, eBranch) =>
|
||||
val Cst(v: Boolean) = eval(cond)(env)
|
||||
if (v)
|
||||
eval(tBranch)(env)
|
||||
else
|
||||
eval(eBranch)(env)
|
||||
case VarDec(x, tp, rhs, body) =>
|
||||
eval(body)(env.withVal(x, eval(rhs)(env)))
|
||||
case VarAssign(x, rhs) =>
|
||||
env.updateVar(x, eval(rhs)(env))
|
||||
case While(cond, lBody, body) =>
|
||||
while (eval(cond)(env) == Cst(true)) {
|
||||
eval(lBody)(env)
|
||||
}
|
||||
eval(body)(env)
|
||||
case FunDef(_, args, _, fbody) =>
|
||||
Func(args map { arg => arg.name }, fbody, env)
|
||||
case LetRec(funs, body) =>
|
||||
// Evaluate all functions
|
||||
val funcs = funs map { case fun@FunDef(name, _, _, _) => (name, eval(fun)(env)) }
|
||||
|
||||
// Add all functions to the functions environment (recursion)
|
||||
funcs foreach { case (_, func@Func(_, _, _)) => func.withVals(funcs) }
|
||||
|
||||
eval(body)(env.withVals(funcs))
|
||||
case App(fun, args) =>
|
||||
// Evaluate the arguments
|
||||
val eargs = args map { arg => eval(arg)(env) }
|
||||
|
||||
// Evaluate the function to be called.
|
||||
eval(fun)(env) match {
|
||||
case Func(fargs, fbody, fenv) =>
|
||||
eval(fbody)(fenv.withVals(fargs zip eargs))
|
||||
case Primitive("getchar") => Cst(Console.in.read)
|
||||
case Primitive("putchar") =>
|
||||
val List(Cst(c: Int)) = eargs
|
||||
Console.out.write(c)
|
||||
Console.out.flush
|
||||
Cst(())
|
||||
}
|
||||
case ArrayDec(size, _) =>
|
||||
val Cst(s: Int) = eval(size)(env)
|
||||
Cst(new Array[Any](s))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Defintion of the value produces by the StackInterpreter
|
||||
*/
|
||||
object StackVal extends BugReporter {
|
||||
import Language._
|
||||
|
||||
type Loc = Int
|
||||
|
||||
/*
|
||||
* Location of primitives operators. Already stored in the environment.
|
||||
*/
|
||||
val primitives = Map[String, Loc](
|
||||
"putchar" -> 0,
|
||||
"getchar" -> 1
|
||||
)
|
||||
|
||||
/**
|
||||
* Env of the interpreter. Keep track of the location
|
||||
* in memory of each variable defined.
|
||||
*/
|
||||
class Env {
|
||||
def apply(name: String): Loc =
|
||||
BUG(s"Undefined identifier $name (should have been found during the semantic analysis)")
|
||||
}
|
||||
|
||||
case class LocationEnv(
|
||||
vars: Map[String, Loc] = primitives,
|
||||
outer: Env = new Env) extends Env {
|
||||
|
||||
/*
|
||||
* Return a copy of the current state plus a
|
||||
* variable 'name' at the location 'loc'
|
||||
*/
|
||||
def withVal(name: String, loc: Loc): LocationEnv = {
|
||||
copy(vars = vars + (name -> loc))
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a copy of the current state plus a
|
||||
* variable 'name' at the location 'loc'
|
||||
*/
|
||||
def withVals(list: List[(String,Loc)]): LocationEnv = {
|
||||
copy(vars = vars ++ list.toMap)
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the location of the variable 'name'
|
||||
*/
|
||||
override def apply(name: String) = vars.get(name) match {
|
||||
case Some(loc) => loc
|
||||
case None => outer(name)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Val
|
||||
|
||||
// Constant values: Int, Boolean, Array
|
||||
case class Cst(x: Any) extends Val {
|
||||
override def toString = if (x != null) x.toString else "null"
|
||||
}
|
||||
|
||||
// Function values
|
||||
case class Func(args: List[String], fbody: Exp, var env: LocationEnv) extends Val {
|
||||
def withVals(list: List[(String,Int)]) = env = env.withVals(list)
|
||||
override def toString = s"(${args mkString ","}) => $fbody"
|
||||
}
|
||||
|
||||
// Primitives
|
||||
case class Primitive(name: String) extends Val
|
||||
}
|
||||
|
||||
/**
|
||||
* This interpreter is a stack-based interpreter as we have seen
|
||||
* during the lecture.
|
||||
*
|
||||
* Rather than returning the value of a node, it stores it in memory,
|
||||
* following a well-establish convention.
|
||||
*
|
||||
* This interpreter works in a similar manner as a processor.
|
||||
*/
|
||||
class StackInterpreter extends Interpreter with BugReporter {
|
||||
import Language._
|
||||
import StackVal._
|
||||
|
||||
type MyVal = Val
|
||||
|
||||
|
||||
// Memory and flag used by the interpreter
|
||||
val memory = new Array[Val](1000)
|
||||
memory(0) = Primitive("putchar")
|
||||
memory(1) = Primitive("getchar")
|
||||
var flag: Boolean = true
|
||||
|
||||
/*
|
||||
* Compute the result of the operator 'op' on the
|
||||
* value stored at 'sp' and store it at 'sp'
|
||||
*/
|
||||
def evalUn(op: String)(sp: Loc) = (op, memory(sp)) match {
|
||||
case ("+", _) => ()
|
||||
case ("-", Cst(x: Int)) => memory(sp) = Cst(-x)
|
||||
case _ => BUG(s"Unary operator $op undefined")
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute the result of the operator 'op' on the
|
||||
* value stored at 'sp' and 'sp1', and store it at 'sp'
|
||||
*
|
||||
* TODO: implement the missing case
|
||||
*/
|
||||
def evalBin(op: String)(sp: Loc, sp1: Loc) = (op, memory(sp), memory(sp1)) match {
|
||||
case ("+", Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x + y)
|
||||
case ("-", Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x - y)
|
||||
case ("*", Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x * y)
|
||||
case ("/", Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x / y)
|
||||
case ("==", Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x == y)
|
||||
case ("!=", Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x != y)
|
||||
case ("<=", Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x <= y)
|
||||
case (">=", Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x >= y)
|
||||
case ("<" , Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x < y)
|
||||
case (">" , Cst(x: Int), Cst(y: Int)) => memory(sp) = Cst(x > y)
|
||||
case ("block-get", Cst(arr: Array[Any]), Cst(i: Int)) => ???
|
||||
case _ => BUG(s"Binary operator $op undefined")
|
||||
}
|
||||
|
||||
def evalTer(op: String)(sp: Loc, sp1: Loc, sp2: Loc) = (op, memory(sp), memory(sp1), memory(sp2)) match {
|
||||
case ("block-set", Cst(arr: Array[Any]), Cst(i: Int), Cst(x)) => ???
|
||||
case _ => BUG(s"ternary operator $op undefined")
|
||||
}
|
||||
|
||||
def evalPrim(op: String)(idxs: List[Int]) = idxs match {
|
||||
case List(sp, sp1, sp2) => evalTer(op)(sp, sp1, sp2)
|
||||
case List(sp, sp1) => evalBin(op)(sp, sp1)
|
||||
case List(sp) => evalUn(op)(sp)
|
||||
case _ => BUG(s"no prim with ${idxs.length} arguments")
|
||||
}
|
||||
|
||||
/*
|
||||
* Evaluate the value of the AST 'exp' within
|
||||
* an empty environment and return the value.
|
||||
*/
|
||||
def run(exp: Exp): Val = {
|
||||
// Start at 2, putchar and getchar are store at 0 and 1!!
|
||||
eval(exp, 2)(LocationEnv())
|
||||
memory(2)
|
||||
}
|
||||
|
||||
/*
|
||||
* Evaluate the value of the AST 'exp' within
|
||||
* the environment 'env 'and store the result
|
||||
* at 'sp'.
|
||||
*
|
||||
* NOTE: Cond stores its result in the 'flag'
|
||||
* variable.
|
||||
*
|
||||
* TODO: Remove all ???s and implement the
|
||||
* appropriate code. The result must be the
|
||||
* same than the evaluator defined above.
|
||||
*/
|
||||
def eval(exp: Exp, sp: Loc)(env: LocationEnv): Unit = exp match {
|
||||
case Lit(x: Unit) => ()
|
||||
case Lit(x) =>
|
||||
memory(sp) = Cst(x)
|
||||
case Prim(op, args) =>
|
||||
val idxs = List.tabulate(args.length)(i => sp + i)
|
||||
(args zip idxs) foreach { case (arg, idx) => eval(arg, idx)(env) }
|
||||
evalPrim(op)(idxs)
|
||||
case Let(x, tp, a, b) =>
|
||||
eval(a, sp)(env)
|
||||
eval(b, sp + 1)(env.withVal(x, sp))
|
||||
memory(sp) = memory(sp + 1)
|
||||
case Ref(x) =>
|
||||
memory(sp) = memory(env(x))
|
||||
case If(cond, tBranch, eBranch) =>
|
||||
eval(cond, sp)(env)
|
||||
val Cst(flag: Boolean) = memory(sp)
|
||||
if (flag)
|
||||
eval(tBranch, sp)(env)
|
||||
else
|
||||
eval(eBranch, sp)(env)
|
||||
case VarDec(x, tp, rhs, body) =>
|
||||
eval(rhs, sp)(env)
|
||||
eval(body, sp + 1)(env.withVal(x, sp))
|
||||
memory(sp) = memory(sp + 1)
|
||||
case VarAssign(x, rhs) =>
|
||||
eval(rhs, sp)(env)
|
||||
memory(env(x)) = memory(sp)
|
||||
case While(cond, lbody, body) =>
|
||||
eval(cond, sp)(env)
|
||||
while (memory(sp) == Cst(true)) {
|
||||
eval(lbody, sp)(env)
|
||||
eval(cond, sp)(env)
|
||||
}
|
||||
eval(body, sp)(env)
|
||||
case FunDef(_, args, _, fbody) => ???
|
||||
case LetRec(funs, body) =>
|
||||
// TODO modify that code
|
||||
eval(body, sp)(env)
|
||||
case App(fun, args) => ???
|
||||
case ArrayDec(size, _) => ???
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package project3
|
||||
|
||||
import java.io._
|
||||
import scala.io._
|
||||
|
||||
trait CodeGenerator {
|
||||
// Define the PrintWriter used to emit
|
||||
// the code.
|
||||
val out = new ByteArrayOutputStream
|
||||
val pOut = new PrintWriter(out, true)
|
||||
def stream = pOut
|
||||
|
||||
def emitCode(ast: Language.Exp): Unit
|
||||
|
||||
// Emit the code and return the String
|
||||
// representation of the code
|
||||
def code(ast: Language.Exp) = {
|
||||
emitCode(ast)
|
||||
out.toString.stripLineEnd
|
||||
}
|
||||
}
|
||||
|
||||
object Runner {
|
||||
import Language._
|
||||
|
||||
def printUsage: Unit = {
|
||||
println("""Usage: run PROG [OPTION]
|
||||
or: run FILE [OPTION]
|
||||
OPTION: intStack""")
|
||||
}
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
if (args.size == 0) {
|
||||
printUsage
|
||||
return
|
||||
}
|
||||
|
||||
val src = if (new File(args(0)).exists) {
|
||||
val source = Source.fromFile(args(0))
|
||||
try source.getLines mkString "\n" finally source.close()
|
||||
} else {
|
||||
args(0)
|
||||
}
|
||||
|
||||
println("============ SRC CODE ============")
|
||||
println(src)
|
||||
println("==================================\n")
|
||||
|
||||
val reader = new BaseReader(src, '\u0000')
|
||||
val scanner = new Scanner(reader)
|
||||
|
||||
// Parser to test!
|
||||
// TODO: Change this as you finish parsers
|
||||
val parser = new BaseParser(scanner)
|
||||
val ast = try {
|
||||
parser.parseCode
|
||||
} catch {
|
||||
case e: AbortException => return
|
||||
case e: Throwable => throw e
|
||||
}
|
||||
|
||||
println("============= AST ================")
|
||||
println(ast)
|
||||
println("==================================\n")
|
||||
|
||||
val analyzer = new SemanticAnalyzer(parser)
|
||||
println("======= Semantic Analyzer ========")
|
||||
val (nast, numWarning, numError) = analyzer.run(ast)
|
||||
if (numError > 0) {
|
||||
println("==================================\n")
|
||||
return
|
||||
}
|
||||
println("=========== Typed AST ============")
|
||||
print(nast)
|
||||
println(s": ${nast.tp}")
|
||||
println("==================================\n")
|
||||
|
||||
|
||||
|
||||
val interpreter = if (args.contains("intStack"))
|
||||
new StackInterpreter
|
||||
else
|
||||
new ValueInterpreter
|
||||
println("========== Interpreter ===========")
|
||||
println(s"Exit Code: ${interpreter.run(nast)}")
|
||||
println("==================================\n")
|
||||
|
||||
// Generator to test
|
||||
val generator = new X86Compiler with CodeGenerator
|
||||
val code = generator.code(nast)
|
||||
|
||||
val runner = new ASMRunner(code)
|
||||
println("============ OUTPUT ==============")
|
||||
println(runner.code)
|
||||
println("==================================\n")
|
||||
|
||||
if (runner.assemble != 0) {
|
||||
println("Compilation error!")
|
||||
} else {
|
||||
println("============ RESULT ==============")
|
||||
println(s"Exit Code: ${runner.run}")
|
||||
println("==================================")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,885 @@
|
||||
package project3
|
||||
|
||||
// Class used to carry position information within the source code
|
||||
case class Position(gapLine: Int, gapCol: Int, startLine: Int, startCol: Int, endLine: Int, endCol: Int) {
|
||||
override def toString = "pos"
|
||||
}
|
||||
class Positioned {
|
||||
var pos: Position = _
|
||||
def withPos(p: Position) = {
|
||||
pos = p
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
object Tokens {
|
||||
|
||||
abstract class Token {
|
||||
var pos: Position = _
|
||||
}
|
||||
case object EOF extends Token
|
||||
|
||||
// CHANGED: As we added new types, instead of having a Token called Number,
|
||||
// we have a Token called Literal for all constant values.
|
||||
case class Literal(x: Any) extends Token
|
||||
case class Ident(x: String) extends Token
|
||||
case class Keyword(x: String) extends Token
|
||||
case class Delim(x: Char) extends Token
|
||||
}
|
||||
|
||||
|
||||
// Scanner
|
||||
class Scanner(in: Reader[Char]) extends Reader[Tokens.Token] with Reporter {
|
||||
import Tokens._
|
||||
|
||||
// Position handling
|
||||
def pos = in.pos
|
||||
def input = in.input
|
||||
|
||||
// Current line in the file
|
||||
var line = 0
|
||||
|
||||
// lineStarts(i) contains the offset of the i th line within the file
|
||||
val lineStarts = scala.collection.mutable.ArrayBuffer(0)
|
||||
|
||||
// Current column in the file
|
||||
def column = pos - lineStarts(line)
|
||||
|
||||
// Extract the i th line of code.
|
||||
def getLine(i: Int) = {
|
||||
val start = lineStarts(i)
|
||||
val end = input.indexOf('\n', start)
|
||||
|
||||
if (end < 0)
|
||||
input.substring(start)
|
||||
else
|
||||
input.substring(start, end)
|
||||
}
|
||||
|
||||
// Information for the current Position
|
||||
var gapLine = 0;
|
||||
var gapCol = 0;
|
||||
var startLine = 0;
|
||||
var startCol = 0;
|
||||
var endLine = 0;
|
||||
var endCol = 0;
|
||||
|
||||
override def abort(msg: String) = {
|
||||
abort(msg, showSource(getCurrentPos()))
|
||||
}
|
||||
|
||||
/*
|
||||
* Show the line of code and highlight the token at position p
|
||||
*/
|
||||
def showSource(p: Position) = {
|
||||
val width = if (p.endLine == p.startLine) (p.endCol - p.startCol) else 0
|
||||
|
||||
val header = s"${p.startLine + 1}:${p.startCol + 1}: "
|
||||
val line1 = getLine(p.startLine)
|
||||
val line2 = " "*(p.startCol+header.length) + "^"*(width max 1)
|
||||
header + line1 + '\n' + line2
|
||||
}
|
||||
|
||||
def isAlpha(c: Char) =
|
||||
('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')
|
||||
|
||||
def isDigit(c: Char) = '0' <= c && c <= '9'
|
||||
|
||||
def isAlphaNum(c: Char) = isAlpha(c) || isDigit(c)
|
||||
|
||||
def isCommentStart(c1: Char, c2: Char) = c1 == '/' && c2 == '/'
|
||||
|
||||
val isWhiteSpace = Set(' ','\t','\n','\r')
|
||||
|
||||
// Boolean operators start with one of the following characters
|
||||
val isBOperator = Set('<', '>', '!', '=')
|
||||
|
||||
// Operators start with one of the following characters
|
||||
val isOperator = Set('+','-','*','/') ++ isBOperator
|
||||
|
||||
// List of delimiters
|
||||
// TODO: Update this as delimiters are added to our language
|
||||
val isDelim = Set('(',')','=',';','{','}',':')
|
||||
|
||||
// List of keywords
|
||||
// TODO: Update this as keywords are added to our language
|
||||
val isKeyword = Set("if", "else", "val", "var", "while")
|
||||
|
||||
val isBoolean = Set("true", "false")
|
||||
|
||||
/*
|
||||
* Extract a name from the stream
|
||||
*
|
||||
* TODO: Handle Boolean literals
|
||||
*/
|
||||
def getName() = {
|
||||
val buf = new StringBuilder
|
||||
while (in.hasNext(isAlphaNum)) {
|
||||
buf += in.next()
|
||||
}
|
||||
val s = buf.toString
|
||||
if (isKeyword(s)) Keyword(s)
|
||||
else if (isBoolean(s)) Boolean(s)
|
||||
else Ident(s)
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract an operator from the stream
|
||||
*/
|
||||
def getOperator() = {
|
||||
val buf = new StringBuilder
|
||||
do {
|
||||
buf += in.next()
|
||||
} while (in.hasNext(isOperator))
|
||||
val s = buf.toString
|
||||
// "=" is a delimiter, "=>" is a keyword, "==","=+", etc are operators
|
||||
if (s == "=") Delim('=')
|
||||
else if (isKeyword(s)) Keyword(s)
|
||||
else Ident(s)
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract a number from the stream and return it.
|
||||
* Raise an error if there is overflow.
|
||||
*
|
||||
* NOTE: An integer can be between 0 and (2 to the power 31) minus 1
|
||||
*/
|
||||
val MAX_NUM = s"${(1 << 31) - 1}"
|
||||
def getNum() = {
|
||||
val num = new StringBuilder
|
||||
while (in.hasNext(isDigit)) {
|
||||
num += in.next()
|
||||
}
|
||||
|
||||
val sNum = num.toString
|
||||
if (sNum.length < MAX_NUM.length || sNum <= MAX_NUM)
|
||||
Literal(sNum.toInt)
|
||||
else
|
||||
abort(s"integer overflow")
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract a raw token from the stream.
|
||||
* i.e. without position information.
|
||||
*/
|
||||
def getRawToken(): Token = {
|
||||
if (in.hasNext(isAlpha)) {
|
||||
getName()
|
||||
} else if (in.hasNext(isOperator)) {
|
||||
getOperator()
|
||||
} else if (in.hasNext(isDigit)) {
|
||||
getNum()
|
||||
} else if (in.hasNext(isDelim)) {
|
||||
Delim(in.next())
|
||||
} else if (!in.hasNext) {
|
||||
EOF
|
||||
} else {
|
||||
abort(s"unexpected character")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Skip whitespace and comments. Stop at the next token.
|
||||
*/
|
||||
def skipWhiteSpace() = {
|
||||
while (in.hasNext(isWhiteSpace) || in.hasNext2(isCommentStart)) {
|
||||
|
||||
// If it is a comment, consume the full line
|
||||
if (in.peek == '/') {
|
||||
in.next()
|
||||
while (in.peek != '\n') in.next()
|
||||
|
||||
}
|
||||
|
||||
// Update file statistics if new line
|
||||
if (in.peek == '\n') {
|
||||
lineStarts += pos + 1
|
||||
line += 1
|
||||
}
|
||||
in.next()
|
||||
}
|
||||
}
|
||||
|
||||
def getCurrentPos() = {
|
||||
endLine = line; endCol = column
|
||||
Position(gapLine,gapCol,startLine,startCol,endLine,endCol)
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract a token and set position information
|
||||
*/
|
||||
def getToken(): Token = {
|
||||
gapLine = line; gapCol = column
|
||||
skipWhiteSpace()
|
||||
startLine = line; startCol = column
|
||||
val tok = getRawToken()
|
||||
tok.pos = getCurrentPos()
|
||||
|
||||
tok
|
||||
}
|
||||
|
||||
var peek = getToken()
|
||||
var peek1 = getToken()
|
||||
def hasNext: Boolean = peek != EOF
|
||||
def hasNext(f: Token => Boolean) = f(peek)
|
||||
def hasNext2(f: (Token, Token) => Boolean) = f(peek, peek1)
|
||||
def next() = {
|
||||
val res = peek
|
||||
peek = peek1
|
||||
peek1 = getToken()
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
class Parser(in: Scanner) extends Reporter {
|
||||
import Tokens._
|
||||
|
||||
/*
|
||||
* Overloaded methods that show the source code
|
||||
* and highlight the current token when reporting
|
||||
* an error.
|
||||
*/
|
||||
override def expected(msg: String) = {
|
||||
expected(msg, in.showSource(in.peek.pos))
|
||||
}
|
||||
|
||||
override def abort(msg: String) = {
|
||||
abort(msg, in.showSource(in.peek.pos))
|
||||
}
|
||||
|
||||
def error(msg: String, pos: Position): Unit =
|
||||
error(msg, in.showSource(pos))
|
||||
|
||||
def warn(msg: String, pos: Position): Unit =
|
||||
warn(msg, in.showSource(pos))
|
||||
|
||||
def accept(c: Char) = {
|
||||
if (in.hasNext(_ == Delim(c))) in.next()
|
||||
else expected(s"'$c'")
|
||||
}
|
||||
|
||||
def accept(s: String) = {
|
||||
if (in.hasNext(_ == Keyword(s))) in.next()
|
||||
else expected(s"'$s'")
|
||||
}
|
||||
|
||||
/*
|
||||
* Auxilaries functions
|
||||
* Test and extract data
|
||||
*/
|
||||
def isName(x: Token) = x match {
|
||||
case Ident(x) => true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def getName(): (String, Position) = {
|
||||
if (!in.hasNext(isName)) expected("Name")
|
||||
val pos = in.peek.pos
|
||||
val Ident(x) = in.next()
|
||||
(x, pos)
|
||||
}
|
||||
|
||||
// CHANGED: It was only Number previsously
|
||||
def isLiteral(x: Token) = x match {
|
||||
case Literal(x) => true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def getLiteral(): (Any, Position) = {
|
||||
if (!in.hasNext(isLiteral)) expected("Literal")
|
||||
val pos = in.peek.pos
|
||||
val Literal(x) = in.next()
|
||||
(x, pos)
|
||||
}
|
||||
|
||||
def getOperator(): (String, Position) = {
|
||||
if (!in.hasNext(isName)) expected("Operator")
|
||||
val pos = in.peek.pos
|
||||
val Ident(x) = in.next()
|
||||
(x, pos)
|
||||
}
|
||||
|
||||
/*
|
||||
* Test if the following token is an infix
|
||||
* operator with highest precedence
|
||||
*/
|
||||
def isInfixOp(min: Int)(x: Token) = isOperator(x) && (x match {
|
||||
case Ident(x) => prec(x) >= min
|
||||
case _ => false
|
||||
})
|
||||
|
||||
/*
|
||||
* Test if the following token is an operator.
|
||||
*/
|
||||
def isOperator(x: Token) = x match {
|
||||
case Ident(x) => in.isOperator(x.charAt(0))
|
||||
case _ => false
|
||||
}
|
||||
|
||||
/*
|
||||
* Define precedence of operator.
|
||||
* Negative precedence means that the operator can
|
||||
* not be used as an infix operator within a simple expression.
|
||||
*
|
||||
* CHANGED: boolean operators have precedence of 0
|
||||
*/
|
||||
def prec(a: String) = a match { // higher bind tighter
|
||||
case "+" | "-" => 1
|
||||
case "*" | "/" => 2
|
||||
case _ if in.isBOperator(a.charAt(0)) => 0
|
||||
case _ => 0
|
||||
}
|
||||
|
||||
def assoc(a: String) = a match {
|
||||
case "+" | "-" | "*" | "/" => 1
|
||||
case _ => 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Definition of our target language.
|
||||
*
|
||||
* The different nodes of the AST also keep Position information
|
||||
* for error handling during the semantic analysis.
|
||||
*
|
||||
* TODO: Every time you add an AST node, you must also track the position
|
||||
*/
|
||||
object Language {
|
||||
abstract class Exp {
|
||||
var pos: Position = _
|
||||
var tp: Type = UnknownType
|
||||
|
||||
def withPos(p: Position) = {
|
||||
pos = p
|
||||
this
|
||||
}
|
||||
|
||||
def withType(pt: Type) = {
|
||||
tp = pt
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Type
|
||||
case object UnknownType extends Type
|
||||
case class BaseType(v: String) extends Type {
|
||||
override def toString = v
|
||||
}
|
||||
case class FunType(args: List[(String, Type)], rtp: Type) extends Type {
|
||||
override def toString = s"(${args mkString ","}) => $rtp"
|
||||
}
|
||||
case class ArrayType(tp: Type) extends Type
|
||||
|
||||
val IntType = BaseType("Int")
|
||||
val UnitType = BaseType("Unit")
|
||||
val BooleanType = BaseType("Boolean")
|
||||
|
||||
|
||||
// Arithmetic
|
||||
case class Lit(x: Any) extends Exp
|
||||
// CHANGED: instead of creating a node for different operator arity,
|
||||
// we use a single node with a list of arguments.
|
||||
case class Prim(op: String, args: List[Exp]) extends Exp
|
||||
|
||||
// Immutable variables
|
||||
case class Let(x: String, xtp: Type, a: Exp, b: Exp) extends Exp
|
||||
case class Ref(x: String) extends Exp
|
||||
|
||||
// Branches
|
||||
case class If(cond: Exp, tBranch: Exp, eBranch: Exp) extends Exp
|
||||
|
||||
// Mutable variables
|
||||
case class VarDec(x: String, xtp: Type, rhs: Exp, body: Exp) extends Exp
|
||||
case class VarAssign(x: String, rhs: Exp) extends Exp
|
||||
|
||||
// While loops
|
||||
case class While(cond: Exp, lbody: Exp, body: Exp) extends Exp
|
||||
|
||||
// Functions
|
||||
case class LetRec(funs: List[Exp], body: Exp) extends Exp
|
||||
case class Arg(name: String, tp: Type, pos: Position)
|
||||
case class FunDef(name: String, args: List[Arg], rtp: Type, fbody: Exp) extends Exp
|
||||
case class App(f: Exp, args: List[Exp]) extends Exp
|
||||
|
||||
// Arrays
|
||||
case class ArrayDec(size: Exp, etp: Type) extends Exp
|
||||
}
|
||||
|
||||
/*
|
||||
* The BaseParser class implements all of the functionality implemented in project 2,
|
||||
* with the addition of type information.
|
||||
*
|
||||
* To avoid repeating your effort from project 2, we have implemented all of the
|
||||
* parsing for you, excluding the parsing of types. As such...
|
||||
*
|
||||
* TODO: Implement the two functions that parse types.
|
||||
*
|
||||
* <type> ::= <ident>
|
||||
* <op> ::= ['*' | '/' | '+' | '-' | '<' | '>' | '=' | '!']+
|
||||
* <bool> ::= 'true' | 'false'
|
||||
* <atom> ::= <number> | <bool> | '()'
|
||||
* | '('<simp>')'
|
||||
* | <ident>
|
||||
* | '{'<exp>'}'
|
||||
* <uatom> ::= [<op>]<atom>
|
||||
* <simp> ::= <uatom>[<op><uatom>]*
|
||||
* | 'if' '('<simp>')' <simp> 'else' <simp>
|
||||
* | <ident> '=' <simp>
|
||||
* <exp> ::= <simp>
|
||||
* | 'val' <ident>[:<type>] '=' <simp>';' <exp>
|
||||
* | 'var' <ident>[:<type>] '=' <simp>';' <exp>
|
||||
* | 'while' '('<simp>')'<simp>';' <exp>
|
||||
*/
|
||||
class BaseParser(in: Scanner) extends Parser(in) {
|
||||
import Language._
|
||||
import Tokens._
|
||||
|
||||
/******************* Types **********************/
|
||||
|
||||
/*
|
||||
* This function extracts the type information from
|
||||
* the source code. Raise an error if there is no
|
||||
* type information.
|
||||
*
|
||||
* This function will only be used to read in a type
|
||||
* (i.e. you should not read in a delimiter)
|
||||
*
|
||||
* TODO: Implement this function
|
||||
*/
|
||||
def parseType: Type = in.peek match {
|
||||
case _ => expected("type")
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This function is parsing a type which can be omitted.
|
||||
* If the type information is not in the source code,
|
||||
* it returns UnknownType
|
||||
*
|
||||
* TODO: Implement this function
|
||||
*/
|
||||
def parseOptionalType: Type = in.peek match {
|
||||
case _ => UnknownType
|
||||
}
|
||||
|
||||
/******************* Code **********************/
|
||||
|
||||
/*
|
||||
* Parse the full code,
|
||||
* verify that there are no unused tokens,
|
||||
* and raise an error if there are.
|
||||
*/
|
||||
def parseCode = {
|
||||
val res = parseExpression
|
||||
if (in.hasNext)
|
||||
expected(s"EOF")
|
||||
LetRec(Nil, res)
|
||||
}
|
||||
|
||||
def parseAtom: Exp = (in.peek, in.peek1) match {
|
||||
case (Literal(x), _) =>
|
||||
val (_, pos) = getLiteral
|
||||
Lit(x).withPos(pos)
|
||||
case (Delim('('), Delim(')')) =>
|
||||
val pos = in.next().pos
|
||||
in.next
|
||||
Lit(()).withPos(pos)
|
||||
case (Delim('('), _) =>
|
||||
in.next()
|
||||
val res = parseSimpleExpression
|
||||
accept(')')
|
||||
res
|
||||
case (Ident(x), _) =>
|
||||
val (_, pos) = getName
|
||||
Ref(x).withPos(pos)
|
||||
case (Delim('{'), _) =>
|
||||
accept('{')
|
||||
val res = parseExpression
|
||||
accept('}')
|
||||
res
|
||||
case _ => abort(s"Illegal start of simple expression")
|
||||
}
|
||||
|
||||
def parseUAtom: Exp = if (in.hasNext(isOperator)) {
|
||||
val (op, pos) = getOperator
|
||||
Prim(op, List(parseAtom)).withPos(pos)
|
||||
} else {
|
||||
parseAtom
|
||||
}
|
||||
|
||||
def parseSimpleExpression(min: Int): Exp = {
|
||||
var res = parseUAtom
|
||||
while (in.hasNext(isInfixOp(min))) {
|
||||
val (op, pos) = getOperator
|
||||
val nMin = prec(op) + assoc(op)
|
||||
val rhs = parseSimpleExpression(nMin)
|
||||
res = Prim(op, List(res, rhs)).withPos(pos)
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
def parseSimpleExpression: Exp = (in.peek, in.peek1) match {
|
||||
case (Ident(x), Delim('=')) =>
|
||||
val (_, pos) = getName
|
||||
accept('=')
|
||||
val rhs = parseSimpleExpression
|
||||
VarAssign(x, rhs).withPos(pos)
|
||||
case (Keyword("if"), _) =>
|
||||
val pos = accept("if").pos
|
||||
accept('(')
|
||||
val cond = parseSimpleExpression
|
||||
accept(')')
|
||||
val tBranch = parseSimpleExpression
|
||||
accept("else")
|
||||
val eBranch = parseSimpleExpression
|
||||
If(cond, tBranch, eBranch).withPos(pos)
|
||||
case _ => parseSimpleExpression(0)
|
||||
}
|
||||
|
||||
def parseExpression: Exp = in.peek match {
|
||||
case Keyword("val") =>
|
||||
accept("val")
|
||||
val (name, pos) = getName
|
||||
val tp = parseOptionalType
|
||||
accept('=')
|
||||
val rhs = parseSimpleExpression
|
||||
accept(';')
|
||||
val body = parseExpression
|
||||
Let(name, tp, rhs, body).withPos(pos)
|
||||
case Keyword("var") =>
|
||||
accept("var")
|
||||
val (name, pos) = getName
|
||||
val tp = parseOptionalType
|
||||
accept('=')
|
||||
val rhs = parseSimpleExpression
|
||||
accept(';')
|
||||
val body = parseExpression
|
||||
VarDec(name, tp, rhs, body).withPos(pos)
|
||||
case Keyword("while") =>
|
||||
val pos = accept("while").pos
|
||||
accept('(')
|
||||
val cond = parseSimpleExpression
|
||||
accept(')')
|
||||
val lBody = parseSimpleExpression
|
||||
accept(';')
|
||||
val body = parseExpression
|
||||
While(cond, lBody, body).withPos(pos)
|
||||
case _ => parseSimpleExpression
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We want to make our syntax easier for the programmer to use.
|
||||
*
|
||||
* For example, instead of writing:
|
||||
*
|
||||
* var x = 0;
|
||||
* var y = 3;
|
||||
* let dummy = x = x + 1;
|
||||
* y = y + 1
|
||||
*
|
||||
* We will write
|
||||
*
|
||||
* var x = 0;
|
||||
* var y = 3;
|
||||
* x = x + 1;
|
||||
* y = y + 1
|
||||
*
|
||||
* However the AST generated will be the same. The parser will have to create a dummy
|
||||
* variable and insert a let binding.
|
||||
*
|
||||
* We also have some syntactic sugar for the if statement. If the else branch doesn't exist,
|
||||
* then the unit literal will be used for that branch.
|
||||
*
|
||||
* TODO complete the two functions to handle syntactic sugar.
|
||||
*
|
||||
* <type> ::= <ident>
|
||||
* <op> ::= ['*' | '/' | '+' | '-' | '<' | '>' | '=' | '!']+
|
||||
* <bool> ::= 'true' | 'false'
|
||||
* <atom> ::= <number> | <bool> | '()'
|
||||
* | '('<simp>')'
|
||||
* | <ident>
|
||||
* | '{'<exp>'}'
|
||||
* <uatom> ::= [<op>]<atom>
|
||||
* <simp> ::= <uatom>[<op><uatom>]*
|
||||
* | 'if' '('<simp>')' <simp> ['else' <simp>]
|
||||
* | <ident> '=' <simp>
|
||||
* <exp> ::= <simp>[;<exp>]
|
||||
* | 'val' <ident>[:<type>] '=' <simp>';' <exp>
|
||||
* | 'var' <ident>[:<type>] '=' <simp>';' <exp>
|
||||
* | 'while' '('<simp>')'<simp>';' <exp>
|
||||
*/
|
||||
class SyntacticSugarParser(in: Scanner) extends BaseParser(in) {
|
||||
import Language._
|
||||
import Tokens._
|
||||
|
||||
// Can be overriden for ; inference
|
||||
def isNewLine(x: Token) = x match {
|
||||
case Delim(';') => true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
var next = 0
|
||||
def freshName(suf: String = "x") = {
|
||||
next += 1
|
||||
suf + "$" + next
|
||||
}
|
||||
|
||||
override def parseSimpleExpression = in.peek match {
|
||||
case _ => super.parseSimpleExpression
|
||||
}
|
||||
|
||||
override def parseExpression = {
|
||||
// NOTE: parse expression terminates when it parse a simples expression.
|
||||
// syntax sugar allows to have an other expression after it.
|
||||
val res = super.parseExpression
|
||||
res
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* The next parser is going to add the necessary mechanic to parse functions.
|
||||
*
|
||||
* With function come function declaration, function definition and function type.
|
||||
*
|
||||
* Here are some example of valid syntax:
|
||||
*
|
||||
* def f(x: Int, k: Int => Int): Int = h(x);
|
||||
*
|
||||
* h(1)(2, 4);
|
||||
*
|
||||
* val g: (Int => Int) => Int = 3; g
|
||||
*
|
||||
* You need to write the function to parse these expression. The job has been splitted
|
||||
* in multiple small auxilary functions. Also don't forget that we already have some
|
||||
* function doing part of the job in the super class.
|
||||
*
|
||||
* We also defined the concept of program. All function must be defined first and then
|
||||
* the following expression is considered the main.
|
||||
*
|
||||
* Here is the formalized grammar. Most of it is already handle by the based parser. you
|
||||
* only need to handle the new constructs.
|
||||
*
|
||||
* <type> ::= <ident>
|
||||
* | <type> '=>' <type>
|
||||
* | '('[<type>[','<type>]*]')' '=>' <type>
|
||||
* <op> ::= ['*' | '/' | '+' | '-' | '<' | '>' | '=' | '!']+
|
||||
* <bool> ::= 'true' | 'false'
|
||||
* <atom> ::= <number> | <bool> | '()'
|
||||
* | '('<simp>')'
|
||||
* | <ident>
|
||||
* <tight> ::= <atom>['('[<simp>[','<simp>]*]')']*
|
||||
* | '{'<exp>'}'
|
||||
* <utight> ::= [<op>]<tight>
|
||||
* <simp> ::= <utight>[<op><utight>]*
|
||||
* | 'if' '('<simp>')' <simp> ['else' <simp>]
|
||||
* | <ident> '=' <simp>
|
||||
* <exp> ::= <simp>[;<exp>]
|
||||
* | 'val' <ident> [':'<type>] '=' <simp>';' <exp>
|
||||
* | 'var' <ident> [':'<type>] '=' <simp>';' <exp>
|
||||
* | 'while' '('<simp>')'<simp>';' <exp>
|
||||
* <arg> ::= <ident>':'<type>
|
||||
* <prog> ::= ['def'<ident>'('[<arg>[','<arg>]*]')'[':' <type>] '=' <simp>';']*<exp>
|
||||
*/
|
||||
class FunctionParser(in: Scanner) extends SyntacticSugarParser(in) {
|
||||
import Language._
|
||||
import Tokens._
|
||||
|
||||
/*
|
||||
* This function is an auxilary function that is parsing a list of elements of type T which are
|
||||
* separated by 'sep'.
|
||||
*
|
||||
* 'sep' must be a valid delimiter.
|
||||
*
|
||||
* 12, 14, 11, 23, 10, 234
|
||||
*
|
||||
* parseList[Exp](parseAtom, ',', tok => tok match {
|
||||
* case Literal(x: Int) => x < 20;
|
||||
* case _ => false
|
||||
* })
|
||||
*
|
||||
* will return the list List(Lit(12), Lit(14), lit(11)) and the next token will be Delim(',')
|
||||
*
|
||||
* You don't have to use this function but it may be useful.
|
||||
*/
|
||||
def parseList[T](parseElem: => T, sep: Char, cond: Token => Boolean, first: Boolean = true): List[T] = {
|
||||
if (first && cond(in.peek) || (!first && in.peek == Delim(sep) && cond(in.peek1))) {
|
||||
if (!first) {
|
||||
accept(sep)
|
||||
}
|
||||
parseElem :: parseList(parseElem, sep, cond, false)
|
||||
} else {
|
||||
Nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This function parse types.
|
||||
*
|
||||
* TODO
|
||||
*/
|
||||
override def parseType = in.peek match {
|
||||
case _ => super.parseType
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the program and verify that there nothing left
|
||||
* to be parsed.
|
||||
*/
|
||||
override def parseCode = {
|
||||
val prog = parseProgram
|
||||
if (in.hasNext)
|
||||
expected(s"EOF")
|
||||
prog
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse one argument (<arg>)
|
||||
*
|
||||
* TODO: complete the function
|
||||
*/
|
||||
def parseArg: Arg = {
|
||||
???
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse one function.
|
||||
* We assume that the first token is Keyword("def")
|
||||
*
|
||||
* TODO: complete the function
|
||||
*/
|
||||
def parseFunction: Exp = {
|
||||
???
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a program. I.e a list of function following
|
||||
* by an expression.
|
||||
*
|
||||
* If there is no functions defined, this function
|
||||
* still return a LetRec with an empty function list.
|
||||
*
|
||||
* TODO: complete the function
|
||||
*/
|
||||
def parseProgram = in.peek match {
|
||||
case _ => LetRec(Nil, parseExpression)
|
||||
}
|
||||
|
||||
/*
|
||||
* this function is called uatom to avoid reimplementing
|
||||
* the previous functions. However it is parsing the <utight>
|
||||
* grammar.
|
||||
*/
|
||||
override def parseUAtom = if (in.hasNext(isOperator)) {
|
||||
val (op, pos) = getOperator
|
||||
Prim(op, List(parseTight)).withPos(pos)
|
||||
} else {
|
||||
parseTight
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse <tight> grammar. i.e. function applications.
|
||||
*
|
||||
* Remember function application is left associative
|
||||
* and they all have the same precedence.
|
||||
*
|
||||
* a(i)(k, j) is parsed to
|
||||
*
|
||||
* App(App(Ref("a"), List(Ref("i"))), List(Ref("k"), Ref("j")))
|
||||
*/
|
||||
def parseTight = in.peek match {
|
||||
case Delim('{') =>
|
||||
val pos = in.next().pos
|
||||
val res = parseExpression
|
||||
accept('}')
|
||||
res
|
||||
case _ =>
|
||||
var res = parseAtom
|
||||
// TODO: complete
|
||||
res
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* We are now going to add heap storage. This kind of storage is persistant
|
||||
* between function calls.
|
||||
*
|
||||
* We are going to use the scala syntax of: new Array[Int](4). However
|
||||
* we are not going to implement object. The array behavior will be closer
|
||||
* to a C array.
|
||||
*
|
||||
* In order to access an element the element in the array we use the syntax:
|
||||
*
|
||||
* val arr = new Array[Int](4);
|
||||
* val x = arr(0);
|
||||
*
|
||||
* And for the update:
|
||||
*
|
||||
* arr(0) = 3;
|
||||
*
|
||||
* The acces is going to be parse as a function application but this is fine.
|
||||
* For the value update, the parser need to generate a primitive: block-set
|
||||
* which take three paramter. 1 the arr, 2 the idx and 3 the value to update.
|
||||
*
|
||||
* arr(0) = 3;
|
||||
*
|
||||
* will be parsed to
|
||||
* Prim("block-set", List(Ref("arr"), Lit(0), Lit(3)))
|
||||
*
|
||||
* One idea to parse it it to follow the following process:
|
||||
*
|
||||
* parse a tight, if it returns a function application with only one argument
|
||||
* and the following token is an '=' then you are in the array update situation.
|
||||
*
|
||||
* TODO: Complete the methods
|
||||
*
|
||||
* <type> ::= <ident>
|
||||
* | <type> '=>' <type>
|
||||
* | '('[<type>[','<type>]*]')' '=>' <type>
|
||||
* | 'Array' '[' <type> ']'
|
||||
* <op> ::= ['*' | '/' | '+' | '-' | '<' | '>' | '=' | '!']+
|
||||
* <bool> ::= 'true' | 'false'
|
||||
* <atom> ::= <number> | <bool> | '()'
|
||||
* | '('<simp>')'
|
||||
* | <ident>
|
||||
* <tight> ::= <atom>['('[<simp>[','<simp>]*]')']*['('<simp>')' '=' <simp>]
|
||||
* | '{'<exp>'}'
|
||||
* <utight> ::= [<op>]<tight>
|
||||
* <simp> ::= <utight>[<op><utight>]*
|
||||
* | 'if' '('<simp>')' <simp> ['else' <simp>]
|
||||
* | <ident> '=' <simp>
|
||||
* | 'new' 'Array' '['<type> ']' '('<simpl>')' // type not optional '[' is the delimiter.
|
||||
* <exp> ::= <simp>[;<exp>]
|
||||
* | 'val' <ident> [':'<type>] '=' <simp>';' <exp>
|
||||
* | 'var' <ident> [':'<type>] '=' <simp>';' <exp>
|
||||
* | 'while' '('<simp>')'<simp>';' <exp>
|
||||
* <arg> ::= <ident>':'<type>
|
||||
* <prog> ::= ['def'<ident>'('[<arg>[','<arg>]*]')'[':' <type>] '=' <simp>';']*<exp>
|
||||
*/
|
||||
class ArrayParser(in: Scanner) extends FunctionParser(in) {
|
||||
import Language._
|
||||
import Tokens._
|
||||
|
||||
override def parseType = in.peek match {
|
||||
case Ident("Array") => ???
|
||||
case _ => super.parseType
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse array update
|
||||
*
|
||||
* TODO
|
||||
*/
|
||||
override def parseTight = ???
|
||||
|
||||
/*
|
||||
* Parse array declaration
|
||||
*
|
||||
* TODO
|
||||
*/
|
||||
override def parseSimpleExpression = ???
|
||||
}
|
@ -0,0 +1,386 @@
|
||||
package project3
|
||||
|
||||
class SemanticAnalyzer(parser: Parser) extends Reporter with BugReporter {
|
||||
import Language._
|
||||
|
||||
/*
|
||||
* Primitive functions that do not need to be defined or declared.
|
||||
*/
|
||||
val primitives = Map[String,(Boolean,Type)](
|
||||
"getchar" -> (false, FunType(List(), IntType)),
|
||||
"putchar" -> (false, FunType(List(("", IntType)), UnitType))
|
||||
)
|
||||
|
||||
/*
|
||||
* Define an empty state for the Semantic Analyzer.
|
||||
*
|
||||
* NOTE:
|
||||
* val env = new Env
|
||||
*
|
||||
* env("hello") is equivalent to env.apply("hello")
|
||||
*/
|
||||
class Env {
|
||||
def apply(name: String): Option[Type] = None
|
||||
def isVar(name: String) = false
|
||||
}
|
||||
|
||||
/*
|
||||
* Env that keeps track of variables defined.
|
||||
* The map stores true if the variable is mutable,
|
||||
* false otherwise and its type.
|
||||
*/
|
||||
case class TypeEnv(
|
||||
vars: Map[String,(Boolean, Type)] = primitives,
|
||||
outer: Env = new Env) extends Env {
|
||||
|
||||
/*
|
||||
* Return true if the variable is already defined
|
||||
* in this scope
|
||||
*/
|
||||
def isDefined(name: String) = vars.contains(name)
|
||||
|
||||
/*
|
||||
* Make a copy of this object and add a mutable variable 'name'
|
||||
*/
|
||||
def withVar(name: String, tp: Type): TypeEnv = {
|
||||
copy(vars = vars + (name -> (true, tp)))
|
||||
}
|
||||
|
||||
/*
|
||||
* Make a copy of this object and add an immutable variable 'name'
|
||||
*/
|
||||
def withVal(name: String, tp: Type): TypeEnv = {
|
||||
copy(vars = vars + (name -> (false, tp)))
|
||||
}
|
||||
|
||||
/*
|
||||
* Make a copy of this object and add in the list of immutable variables.
|
||||
*/
|
||||
def withVals(list: List[(String,Type)]): TypeEnv = {
|
||||
copy(vars = vars ++ (list map { t => (t._1, (false, t._2)) }).toMap)
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if 'name' is a mutable variable defined in this scope
|
||||
* or in the outer scope.
|
||||
*/
|
||||
override def isVar(name: String) = vars.get(name) match {
|
||||
case None => outer.isVar(name)
|
||||
case Some((mut, _)) => mut
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the Type if the variable 'name' is an option.
|
||||
* i.e. Some(tp) if the variable exists or None if it doesn't
|
||||
*/
|
||||
override def apply(name: String): Option[Type] = vars.get(name) match {
|
||||
case Some((_, tp)) => Some(tp)
|
||||
case None => outer(name)
|
||||
}
|
||||
}
|
||||
|
||||
// Error reporting
|
||||
var numError = 0
|
||||
def error(msg: String, pos: Position): Unit = {
|
||||
numError += 1
|
||||
parser.error(msg, pos)
|
||||
}
|
||||
|
||||
// Warning reporting
|
||||
var numWarning = 0
|
||||
def warn(msg: String, pos: Position): Unit = {
|
||||
numWarning += 1
|
||||
parser.warn(msg, pos)
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a fresh name if a new variable needs to be defined
|
||||
*/
|
||||
var next = 0
|
||||
def freshName(pref: String = "x") = {
|
||||
next += 1
|
||||
s"${pref}_$next"
|
||||
}
|
||||
|
||||
/*
|
||||
* Auxiliary functions. May be useful.
|
||||
*/
|
||||
def getName(arg: Any): String = arg match {
|
||||
case Arg(name, _, _) => name
|
||||
case FunDef(name, _, _, _) => name
|
||||
case _ => BUG(s"Don't know how to extract name from $arg")
|
||||
}
|
||||
|
||||
def getPos(arg: Any): Position = arg match {
|
||||
case Arg(_, _, pos) => pos
|
||||
case fd@FunDef(_, _, _, _) => fd.pos
|
||||
case _ => BUG(s"Don't know how to extract position from $arg")
|
||||
}
|
||||
|
||||
def checkDuplicateNames(args: List[Any]): Boolean = args match {
|
||||
case h::t =>
|
||||
val name = getName(h)
|
||||
val (dup, other) = t partition { arg => name == getName(arg) }
|
||||
dup foreach { arg =>
|
||||
error(s"$name is already defined", getPos(arg))
|
||||
}
|
||||
checkDuplicateNames(other) || dup.length > 0
|
||||
case Nil => false
|
||||
}
|
||||
|
||||
def funType(args: List[Arg], rtp: Type): FunType = {
|
||||
FunType(args map { arg => (arg.name, arg.tp) }, rtp)
|
||||
}
|
||||
|
||||
def listArgType(size: Int, tp: Type) = List.fill(size)(("", tp))
|
||||
|
||||
/**
|
||||
* Run the Semantic Analyzer on the given AST.
|
||||
*
|
||||
* Print out the number of warnings and errors found, if any.
|
||||
* Return the AST with types resolved and the number of warnings
|
||||
* and errors.
|
||||
*
|
||||
* NOTE: we want our main program to return an Int!
|
||||
*/
|
||||
def run(exp: Exp) = {
|
||||
numError = 0
|
||||
val nexp = typeCheck(exp, IntType)(TypeEnv())
|
||||
if (numWarning > 0)
|
||||
System.err.println(s"""$numWarning warning${if (numWarning != 1) "s" else ""} found""")
|
||||
if (numError > 0)
|
||||
System.err.println(s"""$numError error${if (numError != 1) "s" else ""} found""")
|
||||
|
||||
(nexp, numWarning, numError)
|
||||
}
|
||||
|
||||
// List of valid infix operators
|
||||
val isBOperator = Set("==","!=","<=",">=","<",">")
|
||||
val isIntOperator = Set("+","-","*","/")
|
||||
|
||||
/*
|
||||
* Returns the type of the binary operator 'op'. See case "+" for an example
|
||||
* TODO: implement the remaining binary operators for typeBinOperator
|
||||
*/
|
||||
def typeBinOperator(op: String)(pos: Position) = op match {
|
||||
case "+" => FunType(List(("", IntType), ("", IntType)), IntType)
|
||||
case _ =>
|
||||
error("undefined binary operator", pos)
|
||||
UnknownType
|
||||
}
|
||||
|
||||
// List of valid unary operators
|
||||
val isIntUnOperator = Set("+","-")
|
||||
|
||||
/*
|
||||
* Returns the type of the unary operator 'op'
|
||||
* TODO: implement typeUnOperator
|
||||
*/
|
||||
def typeUnOperator(op: String)(pos: Position) = op match {
|
||||
case _ =>
|
||||
error(s"undefined unary operator", pos)
|
||||
UnknownType
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the type of the ternary operator 'op'
|
||||
* TODO: implement typeTerOperator
|
||||
* operators: block-set
|
||||
*/
|
||||
def typeTerOperator(op: String)(pos: Position) = op match {
|
||||
case _ =>
|
||||
error(s"undefined ternary operator", pos)
|
||||
UnknownType
|
||||
}
|
||||
/*
|
||||
* Return the type of the operator 'op' with arity 'arity'
|
||||
*/
|
||||
def typeOperator(op: String, arity: Int)(pos: Position): Type = arity match {
|
||||
case 3 => typeTerOperator(op)(pos)
|
||||
case 2 => typeBinOperator(op)(pos)
|
||||
case 1 => typeUnOperator(op)(pos)
|
||||
case _ =>
|
||||
error(s"undefined operator", pos)
|
||||
UnknownType
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if 'tp' conforms to 'pt' and return the more precise type.
|
||||
* The result needs to be well formed.
|
||||
*
|
||||
* TODO: implement the case of function type.
|
||||
*/
|
||||
def typeConforms(tp: Type, pt: Type)(env: TypeEnv, pos: Position): Type = (tp, pt) match {
|
||||
case (_, _) if tp == pt => typeWellFormed(tp)(env, pos)
|
||||
case (_, UnknownType) => typeWellFormed(tp)(env, pos) // tp <: Any
|
||||
case (UnknownType, _) => typeWellFormed(pt)(env, pos) // for function arguments
|
||||
case (FunType(args1, rtp1), FunType(args2, rtp2)) if args1.length == args2.length =>
|
||||
??? // TODO: Function type conformity
|
||||
case (ArrayType(tp), ArrayType(pt)) => ArrayType(typeConforms(tp, pt)(env, pos))
|
||||
case _ => error(s"type mismatch;\nfound : $tp\nexpected: $pt", pos); pt
|
||||
}
|
||||
|
||||
/*
|
||||
* Auxiliary function used to check function type argument conformity.
|
||||
*
|
||||
* The function is verifying that 'tp' elements number n conforms
|
||||
* to 'pt' element number n. It returns the list of precise types
|
||||
* returned by each invocation to typeConforms
|
||||
*/
|
||||
def typeConform(tp: List[(String, Type)], pt: List[(String,Type)])(env: TypeEnv, pos: Position): List[(String, Type)] = {
|
||||
if (tp.length != pt.length) BUG("length of list does not match")
|
||||
|
||||
(tp zip pt) map { case ((arg1, tp1), (arg2, tp2)) =>
|
||||
(if (tp1 != UnknownType) arg1
|
||||
else arg2, typeConforms(tp1, tp2)(env, pos))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify that the type 'tp' is well formed. i.e there is no
|
||||
* UnknownType.
|
||||
*/
|
||||
def typeWellFormed(tp: Type)(env: TypeEnv, pos: Position)(implicit forFunction: Boolean=false): Type = tp match {
|
||||
case FunType(args, rte) =>
|
||||
FunType(args map { case (n, tp) =>
|
||||
(n, typeWellFormed(tp)(env, pos))
|
||||
}, typeWellFormed(rte)(env, pos)(true))
|
||||
case ArrayType(tp) => ArrayType(typeWellFormed(tp)(env, pos))
|
||||
case UnknownType =>
|
||||
if (forFunction) error("malformed type: function return types must be explicit if function is used recursively or in other functions' bodies", pos)
|
||||
else error("malformed type", pos)
|
||||
UnknownType
|
||||
case _ => tp
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* typeCheck takes an expression and an expected type (which may be UnknownType).
|
||||
* This is done via calling the typeInfer and typeConforms
|
||||
* functions (details below), and finally returning the original
|
||||
* expression with all typing information resolved.
|
||||
*
|
||||
* typeInfer uses the inference rules seen during the lectures
|
||||
* to discover the type of an expression. As a reminder, the rules we saw can be
|
||||
* found in lectures 5 and 6.
|
||||
*
|
||||
* TODO: Remove the ??? and add the correct implementation.
|
||||
* The code must follow the inference rules seen during the lectures.
|
||||
*
|
||||
* The errors/warnings check that you had to implement for project 2
|
||||
* should be already implemented. However, there are new variables
|
||||
* introduced that need to be check for duplicate (function name,
|
||||
* variables names). We defined the rules for function semantic in
|
||||
* lecture 5.
|
||||
*/
|
||||
def typeCheck(exp: Exp, pt: Type)(env: TypeEnv): Exp = {
|
||||
val nexp = typeInfer(exp, pt)(env)
|
||||
val rnexpType = typeConforms(nexp.tp, pt)(env, exp.pos)
|
||||
nexp.withType(rnexpType)
|
||||
}
|
||||
|
||||
def typeInfer(exp: Exp, pt: Type)(env: TypeEnv): Exp = exp match {
|
||||
case Lit(_: Int) => exp.withType(IntType)
|
||||
case Lit(_: Boolean) => ???
|
||||
case Lit(_: Unit) => ???
|
||||
case Prim("block-set", args) => ???
|
||||
case Prim(op, args) =>
|
||||
typeOperator(op, args.length)(exp.pos) match {
|
||||
case FunType(atps, rtp) => ???
|
||||
case UnknownType => exp.withType(UnknownType)
|
||||
case _ => BUG("operator's type needs to be FunType")
|
||||
}
|
||||
case Let(x, tp, rhs, body) =>
|
||||
if (env.isDefined(x))
|
||||
warn("reuse of variable name", exp.pos)
|
||||
val nrhs = typeCheck(rhs, tp)(env)
|
||||
val nbody = typeCheck(body, pt)(env.withVal(x, nrhs.tp))
|
||||
Let(x, nrhs.tp, nrhs, nbody).withType(nbody.tp)
|
||||
case Ref(x) =>
|
||||
env(x) match {
|
||||
case Some(tp) => ??? // Remember to check that the type taken from the environment is welformed
|
||||
case _ =>
|
||||
error("undefined identifier", exp.pos)
|
||||
???
|
||||
}
|
||||
case If(cond, tBranch, eBranch) =>
|
||||
// Hint: type check the else branch before the then branch.
|
||||
???
|
||||
case VarDec(x, tp, rhs, body) =>
|
||||
if (env.isDefined(x))
|
||||
warn("reuse of variable name", exp.pos)
|
||||
???
|
||||
case VarAssign(x, rhs) =>
|
||||
val xtp = if (!env.isDefined(x)) {
|
||||
error("undefined identifier", exp.pos)
|
||||
UnknownType
|
||||
} else {
|
||||
if (!env.isVar(x))
|
||||
error("reassignment to val", exp.pos)
|
||||
env(x).get
|
||||
}
|
||||
|
||||
???
|
||||
|
||||
/* Because of syntactic sugar, a variable assignment
|
||||
* statement can be accepted as an expression
|
||||
* of type Unit. In this case, we will modify
|
||||
* the AST and store the assignment value into
|
||||
* a "dummy" variable and return the Unit Literal.
|
||||
*
|
||||
* For example,
|
||||
*
|
||||
* If(..., VarAssign("x", Lit(1)), Lit(()))
|
||||
*
|
||||
* requires the two branches of the If to be of the same
|
||||
* type, in this case, Unit. Therefore the "then" branch
|
||||
* will need to be modified to have the correct type.
|
||||
* Without changing the semantics!
|
||||
*/
|
||||
pt match {
|
||||
case UnitType => ???
|
||||
case _ => ???
|
||||
}
|
||||
case While(cond, lbody, body) => ???
|
||||
case FunDef(fname, args, rtp, fbody) => ???
|
||||
case LetRec(funs, body) =>
|
||||
// TODO modify to handle general case
|
||||
val nbody = typeCheck(body, pt)(env)
|
||||
LetRec(Nil, nbody).withType(nbody.tp)
|
||||
case App(fun, args) =>
|
||||
// TODO Check fun type
|
||||
val nFun: Exp = ???
|
||||
|
||||
// Handling some errors
|
||||
val ftp = nFun.tp match {
|
||||
case ftp@FunType(fargs, _) if fargs.length == args.length =>
|
||||
ftp
|
||||
case ftp@FunType(fargs, rtp) if fargs.length < args.length =>
|
||||
error(s"too many arguments for method: ($fargs)$rtp", exp.pos)
|
||||
FunType(fargs ++ List.fill(args.length - fargs.length)(("", UnknownType)), rtp)
|
||||
case ftp@FunType(fargs, rtp) =>
|
||||
error(s"not enough arguments for method: ($fargs)$rtp", exp.pos)
|
||||
ftp
|
||||
case ArrayType(tp) =>
|
||||
FunType(List(("", IntType)), tp)
|
||||
case tp =>
|
||||
error(s"$tp does not take paramters", exp.pos)
|
||||
FunType(List.fill(args.length)(("", UnknownType)), pt)
|
||||
}
|
||||
|
||||
// TODO: Check arguments type
|
||||
val nargs: List[Exp] = ???
|
||||
|
||||
// Transform some function applications into primitives on arrays.
|
||||
nFun.tp match {
|
||||
case ArrayType(tp) =>
|
||||
Prim("block-get", List(nFun, nargs.head)).withType(tp)
|
||||
case _ => App(nFun, nargs).withType(ftp.rtp)
|
||||
}
|
||||
case ArrayDec(size: Exp, etp: Type) =>
|
||||
// TODO: Check array declaration
|
||||
// Note that etp is the type of elements
|
||||
???
|
||||
case _ => BUG(s"malformed expresstion $exp")
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package project3
|
||||
|
||||
import java.io._
|
||||
import scala.sys.process._
|
||||
|
||||
class AbortException extends Exception("aborted")
|
||||
|
||||
// Error reporting
|
||||
trait Reporter {
|
||||
// report a warning
|
||||
def warn(s: String): Unit = System.err.println(s"Warning: $s.")
|
||||
def warn(s: String, msg: String): Unit = System.err.println(s"Warning: $s.\n" + msg)
|
||||
// report an error
|
||||
def error(s: String): Unit = System.err.println(s"Error: $s.")
|
||||
def error(s: String, msg: String): Unit = System.err.println(s"Error: $s.\n" + msg)
|
||||
// report error and halt
|
||||
def abort(s: String): Nothing = { error(s); throw new AbortException()}
|
||||
def abort(s: String, msg: String): Nothing = { error(s, msg); throw new AbortException()}
|
||||
|
||||
def expected(s: String): Nothing = abort(s"$s expected")
|
||||
def expected(s: String, msg: String): Nothing =
|
||||
abort(s"$s expected", msg)
|
||||
}
|
||||
|
||||
trait BugReporter {
|
||||
def BUG(msg: String) = throw new Exception(s"BUG: $msg")
|
||||
}
|
||||
|
||||
// Utilities to emit code
|
||||
trait Codegen {
|
||||
|
||||
def stream: PrintWriter
|
||||
|
||||
// output
|
||||
def emit(s: String): Unit = stream.print('\t' + s)
|
||||
def emitln(s: String, nTab: Int = 1): Unit = stream.println("\t" * nTab + s)
|
||||
}
|
||||
|
||||
abstract class Reader[T] {
|
||||
def pos: Int
|
||||
def input: String
|
||||
def peek: T
|
||||
def peek1: T // second look-ahead character used for comments '//'
|
||||
def hasNext: Boolean
|
||||
def hasNext(f: T => Boolean): Boolean
|
||||
def hasNext2(f: (T,T) => Boolean): Boolean
|
||||
def next(): T
|
||||
}
|
||||
|
||||
class BaseReader(str: String, eof: Char) extends Reader[Char] {
|
||||
var pos = 0
|
||||
def input = str
|
||||
val in = str.iterator
|
||||
var peek = if (in.hasNext) in.next() else eof
|
||||
var peek1 = if (in.hasNext) in.next() else eof
|
||||
def hasNext: Boolean = peek != eof
|
||||
def hasNext(f: Char => Boolean) = f(peek)
|
||||
def hasNext2(f: (Char,Char) => Boolean) = f(peek,peek1)
|
||||
def next() = {
|
||||
val x = peek; peek = peek1;
|
||||
peek1 = if (in.hasNext) in.next() else eof
|
||||
pos += 1
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
// ASM bootstrapping
|
||||
class ASMRunner(snipet: String) {
|
||||
|
||||
def code = snipet
|
||||
|
||||
def assemble = {
|
||||
val file = new File("gen/gen.s")
|
||||
val writer = new PrintWriter(file)
|
||||
|
||||
writer.println(snipet)
|
||||
writer.flush
|
||||
writer.close
|
||||
|
||||
Seq("gcc", "-no-pie", "gen/bootstrap.c", "gen/gen.s", "-o", "gen/out").!.toInt
|
||||
}
|
||||
|
||||
def run = {
|
||||
val stdout = "gen/out".!!
|
||||
// output format: Exit Code: <res>\n
|
||||
stdout.split(" ").last.trim.toInt
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package project3
|
||||
|
||||
import org.scalatest._
|
||||
import java.io.{ByteArrayOutputStream, PrintWriter}
|
||||
|
||||
// Define the stream method
|
||||
trait TestOutput {
|
||||
import Language._
|
||||
|
||||
val out = new ByteArrayOutputStream
|
||||
val pOut = new PrintWriter(out, true)
|
||||
def stream = pOut
|
||||
def emitCode(ast: Exp): Unit
|
||||
|
||||
def code(ast: Exp) = {
|
||||
emitCode(ast)
|
||||
out.toString.stripLineEnd
|
||||
}
|
||||
}
|
||||
|
||||
class CompilerTest extends TimedSuite {
|
||||
import Language._
|
||||
|
||||
def runner(src: String) = new ASMRunner(src)
|
||||
|
||||
def testCompiler(ast: Exp, res: Int) = {
|
||||
val interpreter = new X86Compiler with TestOutput
|
||||
|
||||
val code = interpreter.code(ast)
|
||||
val asm = runner(code)
|
||||
|
||||
assert(asm.assemble == 0, "Code generated couldn't be assembled")
|
||||
assert(asm.run == res, "Invalid result")
|
||||
}
|
||||
|
||||
test("arithm") {
|
||||
testCompiler(LetRec(Nil, Lit(-21)), -21)
|
||||
testCompiler(LetRec(Nil, Prim("-", List(Lit(10), Lit(2)))), 8)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package project3
|
||||
|
||||
import org.scalatest._
|
||||
|
||||
class InterpretTest extends TimedSuite {
|
||||
import Language._
|
||||
import StackVal._
|
||||
|
||||
def testInterpreter(ast: Exp, res: Any) = {
|
||||
val interpreter = new StackInterpreter
|
||||
|
||||
assert(res == interpreter.run(ast), "Interpreter does not return the correct value")
|
||||
}
|
||||
|
||||
test("arithm") {
|
||||
testInterpreter(Lit(-21), Cst(-21))
|
||||
testInterpreter(Prim("-", List(Lit(10), Lit(2))), Cst(8))
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package project3
|
||||
|
||||
import java.io._
|
||||
import org.scalatest._
|
||||
|
||||
class ParserTest extends TimedSuite {
|
||||
import Language._
|
||||
|
||||
def scanner(src: String) = new Scanner(new BaseReader(src, '\u0000'))
|
||||
|
||||
def testBaseParser(op: String, res: Exp) = {
|
||||
val gen = new BaseParser(scanner(op))
|
||||
val ast = gen.parseCode
|
||||
|
||||
assert(ast == LetRec(Nil, res), "Invalid result")
|
||||
}
|
||||
|
||||
test("SingleDigit") {
|
||||
testBaseParser("1", Lit(1))
|
||||
}
|
||||
|
||||
test("GenericPrecedence") {
|
||||
testBaseParser("2-4*3", Prim("-", List(Lit(2), Prim("*", List(Lit(4), Lit(3))))))
|
||||
}
|
||||
|
||||
test("ParseType") {
|
||||
testBaseParser("val x: Int = 1; 2", Let("x", IntType, Lit(1), Lit(2)))
|
||||
}
|
||||
|
||||
test("ParseOptionalType") {
|
||||
testBaseParser("val x = 1; 2", Let("x", UnknownType, Lit(1), Lit(2)))
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package project3
|
||||
|
||||
import org.scalatest._
|
||||
|
||||
class SemanticAnalyzerTest extends TimedSuite {
|
||||
import Language._
|
||||
|
||||
def astTypeEquals(ast: Exp, tsa: Exp): Boolean = ast == tsa && ast.tp == tsa.tp && { (ast, tsa) match {
|
||||
case (Prim(_, args), Prim(_, sgra))=>
|
||||
(args zip sgra) forall { case (arg, gra) => astTypeEquals(arg, gra) }
|
||||
case (Let(_, _, a, b), Let(_, _, c, d)) =>
|
||||
astTypeEquals(a, c) && astTypeEquals(b, d)
|
||||
case (If(cond, tBranch, eBranch), If(cond1, tBranch1, eBranch1)) =>
|
||||
astTypeEquals(cond, cond1) && astTypeEquals(tBranch, tBranch1) && astTypeEquals(eBranch, eBranch1)
|
||||
case (VarDec(_, _, a, b), VarDec(_, _, c, d)) =>
|
||||
astTypeEquals(a, c) && astTypeEquals(b, d)
|
||||
case (VarAssign(_, rhs), VarAssign(_, shr)) =>
|
||||
astTypeEquals(rhs, shr)
|
||||
case (While(cond, tBranch, eBranch), While(cond1, tBranch1, eBranch1)) =>
|
||||
astTypeEquals(cond, cond1) && astTypeEquals(tBranch, tBranch1) && astTypeEquals(eBranch, eBranch1)
|
||||
case (FunDef(_, _, _, fbody), FunDef(_, _, _, fbody1)) =>
|
||||
astTypeEquals(fbody, fbody1)
|
||||
case (LetRec(funs, body), LetRec(funs1, body1)) =>
|
||||
((funs zip funs1) forall { case (arg, gra) => astTypeEquals(arg, gra) }) && astTypeEquals(body, body1)
|
||||
case (App(fun, args), App(fun1, args1)) =>
|
||||
((args zip args1) forall { case (arg, gra) => astTypeEquals(arg, gra) }) && astTypeEquals(fun, fun1)
|
||||
case (ArrayDec(size, _), ArrayDec(size1, _)) =>
|
||||
astTypeEquals(size, size1)
|
||||
case _ => true
|
||||
}}
|
||||
|
||||
def testSemanticAnalyzer(ast: Exp, tsa: Exp, nWarning: Int, nError: Int) = {
|
||||
val fakeParser = new Parser(null) {
|
||||
override def error(msg: String, pos: Position) = {}
|
||||
override def warn(msg: String, pos: Position) = {}
|
||||
}
|
||||
|
||||
val analyzer = new SemanticAnalyzer(fakeParser)
|
||||
|
||||
val (tast, w, e) = analyzer.run(ast)
|
||||
assert(w == nWarning, "Incorrect number of Warnings")
|
||||
assert(e == nError, "Incorrect number of Errors")
|
||||
assert(astTypeEquals(tast, tsa), "AST does not have correct type")
|
||||
}
|
||||
|
||||
test("NoErrorNoWarning") {
|
||||
testSemanticAnalyzer(Lit(1), Lit(1).withType(IntType), 0, 0)
|
||||
testSemanticAnalyzer(Prim("+", List(Lit(1), Lit(2))), Prim("+", List(Lit(1).withType(IntType), Lit(2).withType(IntType))).withType(IntType), 0, 0)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package project3
|
||||
import org.scalatest._
|
||||
import org.scalatest.concurrent.{TimeLimitedTests, Signaler, ThreadSignaler}
|
||||
import org.scalatest.time.{Span, Millis}
|
||||
|
||||
class TimedSuite extends FunSuite with TimeLimitedTests {
|
||||
|
||||
val timeLimit = Span(1000, Millis)
|
||||
override val defaultTestSignaler: Signaler = ThreadSignaler
|
||||
}
|
Loading…
Reference in new issue