commit
8e8c90ec38
@ -0,0 +1,14 @@
|
||||
*
|
||||
!src
|
||||
!src/**
|
||||
!gen
|
||||
gen/*
|
||||
!gen/bootstrap.c
|
||||
!project
|
||||
project/*
|
||||
!project/build.properties
|
||||
!*.scala
|
||||
!.gitignore
|
||||
!cleanall.sh
|
||||
!build.sbt
|
||||
!examples
|
@ -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 (y < 3) {
|
||||
val dummy = x = x * x;
|
||||
y = y + 1
|
||||
};
|
||||
x
|
@ -0,0 +1 @@
|
||||
var x = 0; x = (x = x + 2) - 1
|
@ -0,0 +1,9 @@
|
||||
#include <stdio.h>
|
||||
|
||||
// assembly function that we are compiling
|
||||
int entry_point();
|
||||
|
||||
int main() {
|
||||
printf("Result: %d\n", entry_point());
|
||||
return 0;
|
||||
}
|
@ -0,0 +1 @@
|
||||
sbt.version=1.3.5
|
@ -0,0 +1,277 @@
|
||||
package project2
|
||||
|
||||
import scala.collection.mutable.HashMap
|
||||
|
||||
/*
|
||||
* This compiler generates Scala-like code that computes the
|
||||
* result of a given AST.
|
||||
*/
|
||||
abstract class StackCompiler extends BugReporter with Codegen {
|
||||
import Language._
|
||||
|
||||
type Loc = Int
|
||||
|
||||
// Pretty printing
|
||||
var nTab = 0
|
||||
def emitln(msg: String): Unit = emitln(msg, nTab)
|
||||
|
||||
/**
|
||||
* Env of the compiler. Keeps track of the location
|
||||
* in memory of each variable defined.
|
||||
*/
|
||||
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)
|
||||
}
|
||||
|
||||
case class LocationEnv(
|
||||
vars: Map[String, Loc] = Map.empty,
|
||||
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 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 "-" => emitln(s"memory($sp) = -memory($sp)")
|
||||
case "+" => ()
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate code that computes the binary operator
|
||||
* 'op' on the values at memory location 'sp' and
|
||||
* 'sp1' and that stores the result at 'sp'.
|
||||
*/
|
||||
def transBin(op: String)(sp: Loc, sp1: Loc) = op match {
|
||||
case "-" => emitln(s"memory($sp) -= memory($sp1)")
|
||||
case "+" => emitln(s"memory($sp) += memory($sp1)")
|
||||
case "*" => emitln(s"memory($sp) *= memory($sp1)")
|
||||
case "/" => emitln(s"memory($sp) /= memory($sp1)")
|
||||
case "%" => emitln(s"memory($sp) %= memory($sp1)")
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate code that computes the binary operator
|
||||
* 'op' on the values at memory location 'sp' and
|
||||
* 'sp1' and that stores the result in 'flag'.
|
||||
*/
|
||||
def transCond(op: String)(sp: Loc, sp1: Loc) = op match {
|
||||
case "==" => emitln(s"flag = (memory($sp) == memory($sp1))")
|
||||
case "!=" => emitln(s"flag = (memory($sp) != memory($sp1))")
|
||||
case "<=" => emitln(s"flag = (memory($sp) <= memory($sp1))")
|
||||
case ">=" => emitln(s"flag = (memory($sp) >= memory($sp1))")
|
||||
case "<" => emitln(s"flag = (memory($sp) < memory($sp1))")
|
||||
case ">" => emitln(s"flag = (memory($sp) > memory($sp1))")
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate code that computesS the result of the
|
||||
* computation represented by the AST 'exp'.
|
||||
*/
|
||||
def emitCode(exp: Exp): Unit = {
|
||||
emitln("type Val = Int")
|
||||
emitln("val memory = new Array[Val](1000)")
|
||||
emitln("var flag = true")
|
||||
trans(exp, 0)(LocationEnv())
|
||||
emitln(s"memory(0)")
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate code that computes the result of the
|
||||
* computation represented by the AST 'exp'. The
|
||||
* value will be placed at memory location 'sp'
|
||||
*/
|
||||
def trans(exp: Exp, sp: Loc)(env: LocationEnv): Unit = exp match {
|
||||
case Lit(x) =>
|
||||
emitln(s"memory($sp) = $x")
|
||||
case Unary(op, v) =>
|
||||
trans(v, sp)(env)
|
||||
transUn(op)(sp)
|
||||
case Prim(op, lop, rop) =>
|
||||
trans(lop, sp)(env)
|
||||
trans(rop, sp + 1)(env)
|
||||
transBin(op)(sp, sp + 1)
|
||||
case Let(x, a, b) =>
|
||||
trans(a, sp)(env)
|
||||
trans(b, sp + 1)(env.withVal(x, sp))
|
||||
emitln(s"memory($sp) = memory(${sp + 1})")
|
||||
case Ref(x) =>
|
||||
emitln(s"memory($sp) = memory(${env(x)})")
|
||||
case Cond(op, l, r) =>
|
||||
trans(l, sp)(env)
|
||||
trans(r, sp+1)(env)
|
||||
transCond(op)(sp, sp + 1)
|
||||
case If(cond, tBranch, eBranch) =>
|
||||
trans(cond, sp)(env) // Set flag value
|
||||
emitln(s"if (flag) {")
|
||||
nTab += 1
|
||||
trans(tBranch, sp)(env)
|
||||
nTab -= 1
|
||||
emitln("} else {")
|
||||
nTab += 1
|
||||
trans(eBranch, sp)(env)
|
||||
nTab -= 1
|
||||
emitln("}")
|
||||
case VarDec(x, rhs, body) =>
|
||||
trans(rhs, sp)(env)
|
||||
trans(body, sp + 1)(env.withVal(x, sp))
|
||||
emitln(s"memory($sp) = memory(${sp + 1})")
|
||||
case VarAssign(x, rhs) =>
|
||||
trans(rhs, sp)(env)
|
||||
emitln(s"memory(${env(x)}) = memory($sp)")
|
||||
case While(cond, lBody, body) =>
|
||||
trans(cond, sp)(env)
|
||||
emitln(s"while (flag) {")
|
||||
nTab += 1
|
||||
trans(lBody, sp)(env)
|
||||
trans(cond, sp)(env)
|
||||
nTab -= 1
|
||||
emitln(s"}")
|
||||
trans(body, sp)(env)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract class X86Compiler extends BugReporter with Codegen {
|
||||
import Language._
|
||||
|
||||
type Loc = Int
|
||||
type PhyLoc = String
|
||||
|
||||
/**
|
||||
* Env of the compiler. Keep track of the location
|
||||
* in memory of each variable defined.
|
||||
*/
|
||||
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] = Map.empty,
|
||||
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 the location of the variable 'name'
|
||||
*/
|
||||
override def apply(name: String): Loc = vars.get(name) match {
|
||||
case Some(loc) => loc
|
||||
case _ => outer(name)
|
||||
}
|
||||
}
|
||||
|
||||
// List of available register.
|
||||
val regs = Seq("%rbx", "%rcx", "%rdi", "%rsi", "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15")
|
||||
|
||||
/*
|
||||
* Generate code that computes the unary operator
|
||||
* 'op' on the value at memory location 'sp' and that
|
||||
* stores the result at 'sp'.
|
||||
*
|
||||
* TODO: Fill in transUn with the appropriate code.
|
||||
*/
|
||||
def transUn(op: String)(sp: Loc) = op match {
|
||||
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'.
|
||||
*/
|
||||
def transBin(op: String)(sp: Loc, sp1: Loc) = op match {
|
||||
case "+" => emitln(s"addq ${regs(sp1)}, ${regs(sp)}")
|
||||
case "-" => emitln(s"subq ${regs(sp1)}, ${regs(sp)}")
|
||||
case "*" => emitln(s"imulq ${regs(sp1)}, ${regs(sp)}")
|
||||
case "/" =>
|
||||
emitln(s"movq ${regs(sp)}, %rax")
|
||||
emitln(s"cqto")
|
||||
emitln(s"idiv ${regs(sp1)}")
|
||||
emitln(s"movq %rax, ${regs(sp)}")
|
||||
case _ => BUG(s"Binary operator $op undefined")
|
||||
}
|
||||
|
||||
type Label = String
|
||||
|
||||
var nLabel = 0
|
||||
def freshLabel(pref: String) = { nLabel += 1; s"$pref$nLabel" }
|
||||
|
||||
/*
|
||||
* Generate code that jumps to the label 'label'
|
||||
* if the CPU flags are verifying the operator
|
||||
* 'op'
|
||||
* NOTE: The flags will need to be set before.
|
||||
*/
|
||||
def transJumpIf(op: String)(label: Label) = {
|
||||
op match {
|
||||
case "==" => emitln(s"je $label")
|
||||
case "!=" => emitln(s"jne $label")
|
||||
case "<=" => emitln(s"jle $label")
|
||||
case ">=" => emitln(s"jge $label")
|
||||
case "<" => emitln(s"jl $label")
|
||||
case ">" => emitln(s"jg $label")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate code that compute the result of the
|
||||
* computation represented by the AST 'exp'.
|
||||
*/
|
||||
def emitCode(exp: Exp): Unit = {
|
||||
trans(exp, 0)(LocationEnv())
|
||||
emitln(s"movq ${regs(0)}, %rax")
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 () with the appropriate code.
|
||||
*/
|
||||
def trans(exp: Exp, sp: Loc)(env: LocationEnv): Unit = exp match {
|
||||
case Lit(x) =>
|
||||
emitln(s"movq $$$x, ${regs(sp)}")
|
||||
case Unary(op, v) => ()
|
||||
case Prim(op, lop, rop) => ()
|
||||
case Let(x, a, b) => ()
|
||||
case Ref(x) => ()
|
||||
case Cond(op, l, r) => ()
|
||||
case If(cond, tBranch, eBranch) => ()
|
||||
case VarDec(x, rhs, body) => ()
|
||||
case VarAssign(x, rhs) => ()
|
||||
case While(cond, lBody, body) =>
|
||||
val lab = freshLabel("loop")
|
||||
emitln(s"jmp ${lab}_cond")
|
||||
emitln(s"${lab}_body:", 0)
|
||||
() // TODO: continue ...
|
||||
}
|
||||
}
|
@ -0,0 +1,268 @@
|
||||
package project2
|
||||
|
||||
import scala.collection.mutable.HashMap
|
||||
|
||||
abstract class Interpreter {
|
||||
type Val
|
||||
def run(ast: Language.Exp): Val
|
||||
}
|
||||
|
||||
/**
|
||||
* This interpreter specifies the semantics of our
|
||||
* programming lanaguage.
|
||||
*
|
||||
* The evaluation of each node returns a value.
|
||||
*/
|
||||
class ValueInterpreter extends Interpreter with BugReporter {
|
||||
import Language._
|
||||
|
||||
type Val = Int
|
||||
|
||||
/**
|
||||
* 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 x: Val)
|
||||
case class ValueEnv(
|
||||
vars: Map[String, BoxedVal] = Map.empty,
|
||||
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)))
|
||||
}
|
||||
|
||||
/*
|
||||
* 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).x = 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).x
|
||||
else
|
||||
outer(name)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute and return the result of the unary
|
||||
* operation 'op' on the value 'v'
|
||||
*/
|
||||
def evalUn(op: String)(v: Val) = op match {
|
||||
case "-" => -v
|
||||
case "+" => v
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 match {
|
||||
case "-" => v-w
|
||||
case "+" => v+w
|
||||
case "*" => v*w
|
||||
case "/" => v/w
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute and return the result of the condition
|
||||
* operation 'op' on the value 'v' and 'w'
|
||||
* Note: v op w
|
||||
*/
|
||||
def evalCond(op: String)(v: Val, w: Val) = op match {
|
||||
case "==" => v == w
|
||||
case "!=" => v != w
|
||||
case "<=" => v <= w
|
||||
case ">=" => v >= w
|
||||
case "<" => v < w
|
||||
case ">" => v > w
|
||||
}
|
||||
|
||||
/*
|
||||
* 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) => x
|
||||
case Unary(op, v) =>
|
||||
evalUn(op)(eval(v)(env))
|
||||
case Prim(op, lop, rop) =>
|
||||
evalBin(op)(eval(lop)(env), eval(rop)(env))
|
||||
case Let(x, a, b) =>
|
||||
eval(b)(env.withVal(x, eval(a)(env)))
|
||||
case Ref(x) =>
|
||||
env(x)
|
||||
case If(Cond(op, l, r), tBranch, eBranch) =>
|
||||
if (evalCond(op)(eval(l)(env), eval(r)(env)))
|
||||
eval(tBranch)(env)
|
||||
else
|
||||
eval(eBranch)(env)
|
||||
case VarDec(x, rhs, body) =>
|
||||
eval(body)(env.withVal(x, eval(rhs)(env)))
|
||||
case VarAssign(x, rhs) =>
|
||||
env.updateVar(x, eval(rhs)(env))
|
||||
case While(Cond(op, l, r), lBody, body) =>
|
||||
while (evalCond(op)(eval(l)(env), eval(r)(env))) {
|
||||
eval(lBody)(env)
|
||||
}
|
||||
eval(body)(env)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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._
|
||||
|
||||
type Val = Int
|
||||
type Loc = Int
|
||||
|
||||
/**
|
||||
* 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] = Map.empty,
|
||||
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 the location of the variable 'name'
|
||||
*/
|
||||
override def apply(name: String) = vars.get(name) match {
|
||||
case Some(loc) => loc
|
||||
case None => outer(name)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute the result of the operator 'op' on the
|
||||
* value stored at 'sp' and store it at 'sp'
|
||||
*
|
||||
* TODO: Implement the appropriate code as defined in the handout.
|
||||
*/
|
||||
def evalUn(op: String)(sp: Loc) = op match {
|
||||
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'
|
||||
*
|
||||
* NOTE: sp op sp1
|
||||
*/
|
||||
def evalBin(op: String)(sp: Loc, sp1: Loc) = op match {
|
||||
case "+" => memory(sp) += memory(sp1)
|
||||
case "-" => memory(sp) -= memory(sp1)
|
||||
case "*" => memory(sp) *= memory(sp1)
|
||||
case "/" => memory(sp) /= memory(sp1)
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute the result of the operator 'op' on the
|
||||
* value stored at 'sp' and 'sp1', and store it in the
|
||||
* variable 'flag'.
|
||||
*
|
||||
* NOTE: sp op sp1
|
||||
*
|
||||
* TODO: Implement the appropriate code as defined in the handout.
|
||||
*/
|
||||
def evalCond(op: String)(sp: Loc, sp1: Loc) = {
|
||||
flag = op match {
|
||||
case "==" => memory(sp) == memory(sp1)
|
||||
case _ => BUG(s"Binary operator $op undefined")
|
||||
}
|
||||
}
|
||||
|
||||
// Memory and flag used by the interpreter
|
||||
val memory = new Array[Val](1000)
|
||||
var flag: Boolean = true
|
||||
|
||||
/*
|
||||
* Evaluate the value of the AST 'exp' within
|
||||
* an empty environment and return the value.
|
||||
*/
|
||||
def run(exp: Exp): Val = {
|
||||
eval(exp, 0)(LocationEnv())
|
||||
memory(0)
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 as defined in the handout.
|
||||
*/
|
||||
def eval(exp: Exp, sp: Loc)(env: LocationEnv): Unit = exp match {
|
||||
case Lit(x) =>
|
||||
memory(sp) = x
|
||||
case Unary(op, v) => ???
|
||||
case Prim(op, lop, rop) => ???
|
||||
case Let(x, a, b) => ???
|
||||
case Ref(x) => ???
|
||||
case Cond(op, l, r) => ???
|
||||
case If(cond, tBranch, eBranch) => ???
|
||||
case VarDec(x, rhs, body) => ???
|
||||
case VarAssign(x, rhs) => ???
|
||||
case While(cond, lbody, body) =>
|
||||
eval(cond, sp)(env)
|
||||
while (flag) {
|
||||
eval(lbody, sp)(env)
|
||||
eval(cond, sp)(env)
|
||||
}
|
||||
eval(body, sp)(env)
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package project2
|
||||
|
||||
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: compX86, intStack, intValue (default)""")
|
||||
}
|
||||
|
||||
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 ArithParser(scanner)
|
||||
val ast = parser.parseCode
|
||||
|
||||
println("============= AST ================")
|
||||
println(s"\t$ast")
|
||||
println("==================================\n")
|
||||
|
||||
val analyzer = new SemanticAnalyzer(parser)
|
||||
println("======= Semantic Analyzer ========")
|
||||
val (numWarning, numError) = analyzer.run(ast)
|
||||
println("==================================\n")
|
||||
if (numError > 0)
|
||||
return
|
||||
|
||||
val interpreter = if (args.contains("intStack"))
|
||||
new StackInterpreter
|
||||
else
|
||||
new ValueInterpreter
|
||||
println("========== Interpreter ===========")
|
||||
println(s"Result ${interpreter.run(ast)}")
|
||||
println("==================================\n")
|
||||
|
||||
// Generator to test
|
||||
if (args.contains("compX86")) {
|
||||
val generator = new X86Compiler with CodeGenerator
|
||||
val code = generator.code(ast)
|
||||
|
||||
val data = Map[Char,Int]()
|
||||
val runner = new ASMRunner(code, data)
|
||||
|
||||
println("============ OUTPUT ==============")
|
||||
println(runner.code)
|
||||
println("==================================\n")
|
||||
|
||||
if (runner.assemble != 0) {
|
||||
println("Compilation error!")
|
||||
} else {
|
||||
println("============ RESULT ==============")
|
||||
println(s"Result ${runner.run}")
|
||||
println("==================================")
|
||||
}
|
||||
} else {
|
||||
val generator = new StackCompiler with CodeGenerator
|
||||
val code = generator.code(ast)
|
||||
|
||||
println("============ OUTPUT ==============")
|
||||
println(code)
|
||||
println("==================================")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,601 @@
|
||||
package project2
|
||||
|
||||
// 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)
|
||||
|
||||
object Tokens {
|
||||
|
||||
abstract class Token {
|
||||
var pos: Position = _
|
||||
}
|
||||
case object EOF extends Token
|
||||
case class Number(x: Int) 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[String]("val", "var")
|
||||
|
||||
/*
|
||||
* Extract a name from the stream
|
||||
*/
|
||||
def getName() = {
|
||||
val buf = new StringBuilder
|
||||
while (in.hasNext(isAlphaNum)) {
|
||||
buf += in.next()
|
||||
}
|
||||
val s = buf.toString
|
||||
if (isKeyword(s)) Keyword(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
|
||||
* TODO: implement the method
|
||||
*/
|
||||
def getNum() = {
|
||||
val buf = new java.lang.StringBuilder
|
||||
while (in.hasNext(isNum)) {
|
||||
buf += in.next
|
||||
}
|
||||
Number(buf.toString().toInt)
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 white space 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)
|
||||
}
|
||||
|
||||
def isNum(x: Token) = x match {
|
||||
case Number(x) => true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def getNum(): (Int, Position) = {
|
||||
if (!in.hasNext(isNum)) expected("Number")
|
||||
val pos = in.peek.pos
|
||||
val Number(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) = 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.
|
||||
*/
|
||||
def prec(a: String) = a match { // higher bind tighter
|
||||
case "+" | "-" => 1
|
||||
case "*" | "/" => 2
|
||||
case _ if in.isBOperator(a.charAt(0)) => -1
|
||||
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 = _
|
||||
def withPos(p: Position) = {
|
||||
pos = p
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Arithmetic
|
||||
case class Lit(x: Int) extends Exp
|
||||
case class Unary(op: String, v: Exp) extends Exp
|
||||
case class Prim(op: String, lop: Exp, rop: Exp) extends Exp
|
||||
|
||||
// Immutable variables
|
||||
case class Let(x: String, a: Exp, b: Exp) extends Exp
|
||||
case class Ref(x: String) extends Exp
|
||||
|
||||
// Branches
|
||||
case class Cond(op: String, lop: Exp, rop: Exp) extends Exp
|
||||
case class If(cond: Cond, tBranch: Exp, eBranch: Exp) extends Exp
|
||||
|
||||
// Mutable variables
|
||||
case class VarDec(x: String, rhs: Exp, body: Exp) extends Exp
|
||||
case class VarAssign(x: String, rhs: Exp) extends Exp
|
||||
|
||||
// While loops
|
||||
case class While(cond: Cond, lBody: Exp, body: Exp) extends Exp
|
||||
}
|
||||
|
||||
/*
|
||||
* In the previous project, we highlighted some of the difficulties of operator precedence. We proposed a solution
|
||||
* that works well, but requires some duplication of code. What happens if we add another operator such as '&'?
|
||||
* What is the correct parsing for 3 & 1 + 3 * 8 & 2?
|
||||
*
|
||||
* 1) (3 & 1) + (3 * (8 & 2)) & has higher precedence than + and *
|
||||
* 2) (3 & 1) + ((3 * 8) & 2) & has higher precedence than + but lower than *
|
||||
* 3) (3 & (1 + (3 * 8))) & 2 & has lower precedence than + and * and is left associative
|
||||
* 4) 3 & ((1 + (3 * 8)) & 2) & has lower precedence than + and * and is right associative
|
||||
*
|
||||
* In any case, it seems that we would add a new function to handle this operator. And then we must think
|
||||
* about what will happen with '==',
|
||||
* '~', '|' etc.!
|
||||
*
|
||||
* We are therefore going to implement the algorithm we have seen in class and parse the following grammar. The
|
||||
* operator precedence is given through the prec function defined in Parser, and the associativity is given
|
||||
* through the assoc function.
|
||||
*
|
||||
* <op> ::= ['*' | '/' | '+' | '-']+
|
||||
* <atom> ::= <number>
|
||||
* | '('<exp>')'
|
||||
* <uatom> ::= [<op>]<atom>
|
||||
* <exp> ::= <uatom>[<op><uatom>]*
|
||||
*
|
||||
* We have expanded our grammar to allow many more operators. For example "+++++++++" would be a valid syntax.
|
||||
* We don't really need it for now, but it will be useful later. It also allows us to handle generic
|
||||
* operators.
|
||||
*
|
||||
* We also introduce unary operators. These are always attached to the subsequent atom. There is no precedence
|
||||
* associated with them.
|
||||
*
|
||||
* Our grammar may look a little bit nondeterministic now. For example, how do we parse the following code
|
||||
* (Lit omitted)?
|
||||
*
|
||||
* 1*-4 => Prim("*-", 1, 4)
|
||||
* => Prim("*", 1, Unary("-", 4))
|
||||
*
|
||||
* We are going to enforce the "longest match rule." When extracting the first operator in 1*-4, the longest
|
||||
* match that satisfies the grammar is "*-". However, 1* -4 will be parsed as Prim("*", 1, Unary("-", 4)).
|
||||
* The longest match rule has already been implemented for you in the Scanner class.
|
||||
*
|
||||
* The parser only enforces syntax: even if "*-" is not a valid operation, it is valid syntax. The semantic
|
||||
* analysis will catch the error.
|
||||
*
|
||||
* TODO: complete the implementation of the parseExpression method using the algorithm we have seen in class.
|
||||
*/
|
||||
class ArithParser(in: Scanner) extends Parser(in) {
|
||||
import Language._
|
||||
import Tokens._
|
||||
|
||||
def parseCode = {
|
||||
val res = parseExpression
|
||||
if (in.hasNext)
|
||||
expected(s"EOF")
|
||||
res
|
||||
}
|
||||
|
||||
def parseAtom: Exp = in.peek match {
|
||||
case Delim('(') =>
|
||||
in.next()
|
||||
val res = parseExpression
|
||||
accept(')')
|
||||
res
|
||||
case Number(x) =>
|
||||
val (lit, pos) = getNum
|
||||
Lit(lit).withPos(pos)
|
||||
case _ => expected(s"Atom")
|
||||
}
|
||||
|
||||
def parseUAtom: Exp = if (in.hasNext(isOperator)) {
|
||||
val (op, pos) = getOperator
|
||||
Unary(op, parseAtom).withPos(pos)
|
||||
} else {
|
||||
parseAtom
|
||||
}
|
||||
|
||||
def parseExpression: Exp = parseExpression(0)
|
||||
def parseExpression(min: Int): Exp = {
|
||||
var res = parseUAtom
|
||||
// TODO: complete
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Now we can introduce immutable variables. An atom can now be
|
||||
* a reference to a variable exactly like a number.
|
||||
*
|
||||
* We do not introduce the variable declaration at the
|
||||
* same level as the expressions we have used thus far.
|
||||
* Instead, we downgrade the arithmetic expression to a simple expression,
|
||||
* as shown in the BNF below.
|
||||
*
|
||||
* For now we can say that it is a design choice.
|
||||
*
|
||||
* TODO: implement the case Ident in parseAtom
|
||||
*
|
||||
* <op> ::= ['*' | '/' | '+' | '-']+
|
||||
* <atom> ::= <number>
|
||||
* | '('<simp>')'
|
||||
* | <ident>
|
||||
* <uatom> ::= [<op>]<atom>
|
||||
* <simp> ::= <uatom>[<op><uatom>]*
|
||||
* <exp> ::= <simp>
|
||||
* | 'val' <ident> '=' <simp>';' <exp>
|
||||
*/
|
||||
class LetParser(in: Scanner) extends ArithParser(in) {
|
||||
import Language._
|
||||
import Tokens._
|
||||
|
||||
override def parseAtom: Exp = in.peek match {
|
||||
case Delim('(') =>
|
||||
in.next()
|
||||
val res = parseSimpleExpression
|
||||
accept(')')
|
||||
res
|
||||
case Number(x) =>
|
||||
val (_, pos) = getNum
|
||||
Lit(x).withPos(pos)
|
||||
case Ident(x) =>
|
||||
val (_, pos) = getName
|
||||
Ref(x).withPos(pos)
|
||||
// TODO: remove ??? and add the implementation for this case
|
||||
case _ => abort(s"Illegal start of simple expression")
|
||||
}
|
||||
|
||||
def parseSimpleExpression = super.parseExpression
|
||||
|
||||
override def parseExpression = in.peek match {
|
||||
case Keyword("val") =>
|
||||
in.next()
|
||||
val (name, pos) = getName
|
||||
accept('=')
|
||||
val rhs = parseSimpleExpression
|
||||
accept(';')
|
||||
val body = parseExpression
|
||||
Let(name, rhs, body).withPos(pos)
|
||||
case _ => parseSimpleExpression
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We can now add if-else statements to our language. Because
|
||||
* we don't yet have Boolean variables, we instead define a list
|
||||
* of boolean operators which can be used. Similarly, we define
|
||||
* an if-else statement as described in the BNF below:
|
||||
*
|
||||
* <op> ::= ['*' | '/' | '+' | '-' ]+
|
||||
* <bop> ::= ('<' | '>' | '=' | '!')[<op>]
|
||||
* <atom> ::= <number>
|
||||
* | '('<simp>')'
|
||||
* | <ident>
|
||||
* | '{'<exp>'}'
|
||||
* <uatom> ::= [<op>]<atom>
|
||||
* <cond> ::= <simp><bop><simp>
|
||||
* <simp> ::= <uatom>[<op><uatom>]*
|
||||
* | 'if' '('<cond>')' <simp> 'else' <simp>
|
||||
* <exp> ::= <simp>
|
||||
* | 'val' <ident> '=' <simp>';' <exp>
|
||||
*/
|
||||
class BranchParser(in: Scanner) extends LetParser(in) {
|
||||
import Language._
|
||||
import Tokens._
|
||||
|
||||
override def parseAtom: Exp = in.peek match {
|
||||
case Delim('{') =>
|
||||
val pos = in.next().pos
|
||||
val res = parseExpression
|
||||
accept('}')
|
||||
res
|
||||
case _ => super.parseAtom
|
||||
}
|
||||
|
||||
def parseCondition: Cond = {
|
||||
val l = parseSimpleExpression
|
||||
if (in.hasNext(isOperator)) {
|
||||
val (op, pos) = getOperator
|
||||
val r = parseSimpleExpression
|
||||
Cond(op, l, r).withPos(pos).asInstanceOf[Cond]
|
||||
} else {
|
||||
expected(s"operator")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove ??? and complete the implementation.
|
||||
override def parseSimpleExpression = ???
|
||||
}
|
||||
|
||||
/*
|
||||
* We can now introduce mutable variables using the 'var' keyword.
|
||||
* When parsing statements involving mutable variables, we must
|
||||
* look for declarations as well as assignments, as described
|
||||
* here:
|
||||
*
|
||||
* <op> ::= ['*' | '/' | '+' | '-' ]+
|
||||
* <bop> ::= ('<' | '>' | '=' | '!')[<op>]
|
||||
* <atom> ::= <number>
|
||||
* | '('<simp>')'
|
||||
* | <ident>
|
||||
* | '{'<exp>'}'
|
||||
* <uatom> ::= [<uop>]<atom>
|
||||
* <cond> ::= <simp> <bop> <simp>
|
||||
* <simp> ::= <uatom>[<op><uatom>]*
|
||||
* | 'if' '('<cond>')' <simp> 'else' <simp>
|
||||
* | <ident> '=' <simp>
|
||||
* <exp> ::= <simp>
|
||||
* | 'val' <ident> '=' <simp>';' <exp>
|
||||
* | 'var' <ident> '=' <simp>';' <exp>
|
||||
*/
|
||||
class VariableParser(in: Scanner) extends BranchParser(in) {
|
||||
import Language._
|
||||
import Tokens._
|
||||
|
||||
// TODO: remove ??? and complete the implementation.
|
||||
override def parseExpression = in.peek match {
|
||||
case _ => ???
|
||||
}
|
||||
|
||||
// TODO: remove ??? and complete the implementation.
|
||||
override def parseSimpleExpression = (in.peek, in.peek1) match {
|
||||
case _ => ???
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Finally, we must parse while loops. These function
|
||||
* similar to if-else statements: they consist of a
|
||||
* condition and a loop body.
|
||||
*
|
||||
* <op> ::= ['*' | '/' | '+' | '-' ]+
|
||||
* <bop> ::= ('<' | '>' | '=' | '!')[<op>]
|
||||
* <atom> ::= <number>
|
||||
* | '('<simp>')'
|
||||
* | <ident>
|
||||
* | '{'<exp>'}'
|
||||
* <uatom> ::= [<op>]<atom>
|
||||
* <cond> ::= <simp> <bop> <simp>
|
||||
* <simp> ::= <uatom>[<op><uatom>]*
|
||||
* | 'if' '('<cond>')' <simp> 'else' <simp>
|
||||
* | <ident> '=' <simp>
|
||||
* <exp> ::= <simp>
|
||||
* | 'val' <ident> '=' <simp>';' <exp>
|
||||
* | 'var' <ident> '=' <simp>';' <exp>
|
||||
* | 'while' '('<cond>')'<simp>';' <exp>
|
||||
*/
|
||||
class LoopParser(in: Scanner) extends VariableParser(in) {
|
||||
import Language._
|
||||
import Tokens._
|
||||
|
||||
// TODO: remove ??? and complete the implementation.
|
||||
override def parseExpression = ???
|
||||
}
|
@ -0,0 +1,157 @@
|
||||
package project2
|
||||
|
||||
class SemanticAnalyzer(parser: Parser) extends Reporter {
|
||||
import Language._
|
||||
|
||||
/*
|
||||
* Define an empty state for the Semantic Analyser.
|
||||
*
|
||||
* NOTE:
|
||||
* val env = new Env
|
||||
*
|
||||
* env("hello") is equivalent to env.apply("hello")
|
||||
*/
|
||||
class Env {
|
||||
def apply(name: String) = false
|
||||
def isVar(name: String) = false
|
||||
}
|
||||
|
||||
/*
|
||||
* Env that keeps track of variables defined.
|
||||
* The map stores true if the variable is mutable,
|
||||
* false otherwise.
|
||||
*/
|
||||
case class VarEnv(
|
||||
vars: Map[String,Boolean] = Map.empty,
|
||||
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): VarEnv = {
|
||||
copy(vars = vars + (name -> true))
|
||||
}
|
||||
|
||||
/*
|
||||
* Make a copy of this object and add an immutable variable 'name'
|
||||
*/
|
||||
def withVal(name: String): VarEnv = {
|
||||
copy(vars = vars + (name -> false))
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 true if 'name' is a variable defined in this scope
|
||||
* or in the outer scope.
|
||||
*/
|
||||
override def apply(name: String): Boolean = isDefined(name) || 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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the Semantic Analyzer on the given AST.
|
||||
* Print out the number of warnings and errors
|
||||
* found, if any.
|
||||
* Return the number of warnings and errors
|
||||
*/
|
||||
def run(exp: Exp) = {
|
||||
numError = 0
|
||||
numWarning = 0
|
||||
analyze(exp)(VarEnv())
|
||||
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""")
|
||||
else
|
||||
System.out.println("Correct semantic")
|
||||
|
||||
(numWarning, numError)
|
||||
}
|
||||
|
||||
// List of valid infix operators
|
||||
val isOperator = Set("+","-","*","/")
|
||||
|
||||
// List of valid unary operators
|
||||
val isUnOperator = Set("+","-")
|
||||
|
||||
// List of valid boolean operators
|
||||
val isBOperator = Set("==", "!=", "<=", ">=", "<", ">")
|
||||
|
||||
/*
|
||||
* Analyze 'exp' with the environment 'env'
|
||||
*
|
||||
* TODO: Remove the () and add the correct implementation.
|
||||
* The code must follow the rules listed in the handout.
|
||||
* Use the error and warning methods provided.
|
||||
*/
|
||||
def analyze(exp: Exp)(env: VarEnv): Unit = exp match {
|
||||
case Lit(x) =>
|
||||
() // Correct there is nothing to check here.
|
||||
case Unary(op, v) =>
|
||||
if (!isUnOperator(op))
|
||||
error("undefined unary operator", exp.pos)
|
||||
analyze(v)(env)
|
||||
case Prim(op, lop, rop) => // Done
|
||||
if(!(isOperator(op) || isBOperator(op)))
|
||||
error("undefined primitive operator")
|
||||
analyze(lop)
|
||||
analyze(rop)
|
||||
case Let(x, a, b) =>
|
||||
// Done: check variable reuse
|
||||
if(!(env.vars.contains(x) && env.vars[x]))
|
||||
error(s"cannot assign variable $x")
|
||||
analyze(a)(env)
|
||||
analyze(b)(env.withVal(x))
|
||||
case Ref(x) =>
|
||||
if (!env.vars.contains(x))
|
||||
error(s"symbol $x not found")
|
||||
()// Done
|
||||
case Cond(op, l, r) =>
|
||||
// if (!isBOperator(op))
|
||||
// error(s"$op is not a boolean operator")
|
||||
analyze(op)
|
||||
analyze(l)
|
||||
analyze(r)
|
||||
() // Done
|
||||
case If(cond, tBranch, eBranch) =>
|
||||
analyze(cond)(env)
|
||||
analyze(tBranch)(env)
|
||||
analyze(eBranch)(env)
|
||||
case VarDec(x, rhs, body) =>
|
||||
() // TODO
|
||||
case VarAssign(x, rhs) => //diff w/ LET??
|
||||
() // TODO
|
||||
case While(cond, lBody, body) =>
|
||||
() // TODO
|
||||
case _ => abort(s"unknown AST node $exp")
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
package project2
|
||||
|
||||
import java.io._
|
||||
import scala.sys.process._
|
||||
|
||||
// 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 Exception()}
|
||||
def abort(s: String, msg: String): Nothing = { error(s, msg); throw new Exception()}
|
||||
|
||||
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, gData: Map[Char,Int]) {
|
||||
|
||||
val data = if (gData.size > 0) {
|
||||
".data\n" + (gData map {
|
||||
case (k,v) => s"$k:\t.quad $v"
|
||||
} mkString "\n")
|
||||
} else ""
|
||||
|
||||
val template =
|
||||
s"""|.text
|
||||
|#if(__APPLE__)
|
||||
|\t.global _entry_point
|
||||
|
|
||||
|_entry_point:
|
||||
|#else
|
||||
|\t.global entry_point
|
||||
|
|
||||
|entry_point:
|
||||
|#endif
|
||||
|\tpush %rbp\t# save stack frame for C convention
|
||||
|\tmov %rsp, %rbp
|
||||
|
|
||||
|\tpushq %rbx
|
||||
|\tpushq %r12
|
||||
|\tpushq %r13
|
||||
|\tpushq %r14
|
||||
|\tpushq %r15
|
||||
|
|
||||
|\t# beginning generated code
|
||||
|${snipet}
|
||||
|\t# end generated code
|
||||
|\t# %rax contains the result
|
||||
|
|
||||
|\tpopq %r15
|
||||
|\tpopq %r14
|
||||
|\tpopq %r13
|
||||
|\tpopq %r12
|
||||
|\tpopq %rbx
|
||||
|\tmov %rbp, %rsp\t# reset frame
|
||||
|\tpop %rbp
|
||||
|\tret
|
||||
|
|
||||
|$data
|
||||
|""".stripMargin
|
||||
|
||||
def code = template
|
||||
|
||||
def assemble = {
|
||||
val file = new File("gen/gen.s")
|
||||
val writer = new PrintWriter(file)
|
||||
|
||||
writer.println(template)
|
||||
writer.flush
|
||||
writer.close
|
||||
|
||||
Seq("gcc","gen/bootstrap.c","gen/gen.s","-o","gen/out").!.toInt
|
||||
}
|
||||
|
||||
def run = {
|
||||
val stdout = "gen/out".!!
|
||||
// output format: Result: <res>\n
|
||||
stdout.split(" ").last.trim.toInt
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package project2
|
||||
|
||||
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, gData: Map[Char,Int] = Map()) = new ASMRunner(src, gData)
|
||||
|
||||
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(Lit(-21), -21)
|
||||
testCompiler(Prim("-", Lit(10), Lit(2)), 8)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package project2
|
||||
|
||||
import org.scalatest._
|
||||
|
||||
class InterpretTest extends TimedSuite {
|
||||
import Language._
|
||||
|
||||
def testInterpreter(ast: Exp, res: Int) = {
|
||||
val interpreter = new StackInterpreter
|
||||
|
||||
assert(res == interpreter.run(ast), "Interpreter does not return the correct value")
|
||||
}
|
||||
|
||||
test("arithm") {
|
||||
testInterpreter(Lit(-21), -21)
|
||||
testInterpreter(Prim("-", Lit(10), Lit(2)), 8)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
package project2
|
||||
|
||||
import java.io._
|
||||
import org.scalatest._
|
||||
|
||||
class ParserTest extends TimedSuite {
|
||||
|
||||
import Language._
|
||||
|
||||
def scanner(src: String) = new Scanner(new BaseReader(src, '\u0000'))
|
||||
|
||||
def testGenericPrecedence(op: String, res: Exp) = {
|
||||
val gen = new ArithParser(scanner(op))
|
||||
val ast = gen.parseCode
|
||||
|
||||
assert(ast == res, "Invalid result")
|
||||
}
|
||||
|
||||
def testLetParser(op: String, res: Exp) = {
|
||||
val gen = new LetParser(scanner(op))
|
||||
val ast = gen.parseCode
|
||||
|
||||
assert(ast == res, "Invalid result")
|
||||
}
|
||||
|
||||
def testBranchParser(op: String, res: Exp) = {
|
||||
val gen = new BranchParser(scanner(op))
|
||||
val ast = gen.parseCode
|
||||
|
||||
assert(ast == res, "Invalid result")
|
||||
}
|
||||
def testVariableParser(op: String, res: Exp) = {
|
||||
val gen = new VariableParser(scanner(op))
|
||||
val ast = gen.parseCode
|
||||
|
||||
assert(ast == res, "Invalid result")
|
||||
}
|
||||
def testLoopParser(op: String, res: Exp) = {
|
||||
val gen = new LoopParser(scanner(op))
|
||||
val ast = gen.parseCode
|
||||
|
||||
assert(ast == res, "Invalid result")
|
||||
}
|
||||
|
||||
/*test("SingleDigit") {
|
||||
testGenericPrecedence("1", Lit(1))
|
||||
}
|
||||
|
||||
test("GenericPrecedence") {
|
||||
testGenericPrecedence("2-4*3", Prim("-", Lit(2), Prim("*", Lit(4), Lit(3))))
|
||||
}*/
|
||||
test("1") {
|
||||
testGenericPrecedence(s"${(1 << 31) - 1}", Lit({
|
||||
(1 << 31) - 1
|
||||
}))
|
||||
}
|
||||
test("2") {
|
||||
testGenericPrecedence(s"${(1 << 31) - 2}", Lit({
|
||||
(1 << 31) - 2
|
||||
}))
|
||||
}
|
||||
/*test("3") {
|
||||
val thrown = intercept[Exception] {
|
||||
testGenericPrecedence(s"${(1 << 31)}", Lit({(1 << 31) - 1}))
|
||||
}
|
||||
assert(thrown != null)
|
||||
}*/
|
||||
test("3") {
|
||||
assertThrows[Exception] { // Result type: Assertion
|
||||
testGenericPrecedence(s"${(1 << 31)}", Lit({
|
||||
(1 << 31) - 1
|
||||
}))
|
||||
}
|
||||
}
|
||||
test("4") {
|
||||
testGenericPrecedence("12+5*30", Prim("+", Lit(12), Prim("*", Lit(5), Lit(30))))
|
||||
}
|
||||
test("5") {
|
||||
testGenericPrecedence("12+5*30", Prim("+", Lit(12), Prim("*", Lit(5), Lit(30))))
|
||||
}
|
||||
test("6") {
|
||||
testGenericPrecedence("12-5/30", Prim("-", Lit(12), Prim("/", Lit(5), Lit(30))))
|
||||
}
|
||||
test("7") {
|
||||
testGenericPrecedence("2*9*5*3-18/6/3", Prim("-",
|
||||
Prim("*", Prim("*", Prim("*", Lit(2), Lit(9)), Lit(5)), Lit(3)),
|
||||
Prim("/", Prim("/", Lit(18), Lit(6)), Lit(3))))
|
||||
}
|
||||
/*test("rt") {
|
||||
testGenericPrecedence("18/6/3",Prim("/",Prim("/",Lit(18),Lit(6)),Lit(3)))
|
||||
}
|
||||
test("go"){
|
||||
testGenericPrecedence("2*9*5*3",Prim("*",Prim("*",Prim("*",Lit(2),Lit(9)),Lit(5)), Lit(3)))
|
||||
}*/
|
||||
test("8") {
|
||||
0
|
||||
}
|
||||
test("9") {
|
||||
0
|
||||
}
|
||||
test("10") {
|
||||
0
|
||||
}
|
||||
test("11") {
|
||||
0
|
||||
}
|
||||
test("12") {
|
||||
0
|
||||
}
|
||||
test("13") {
|
||||
0
|
||||
}
|
||||
test("14") {
|
||||
testLetParser("val x=3;x", Let("x", Lit(3), Ref("x")))
|
||||
}
|
||||
test("15") {
|
||||
testLetParser("val x=3;val y=1;x-y", Let("x", Lit(3), Let("y", Lit(1), Prim("-", Ref("x"), Ref("y")))))
|
||||
testLetParser("val x=100;val y=1;x*5-y", Let("x", Lit(100),
|
||||
Let("y", Lit(1),
|
||||
Prim("-", Prim("*", Ref("x"), Lit(5)), Ref("y")))))
|
||||
}
|
||||
|
||||
|
||||
//branch parser
|
||||
test("16") {
|
||||
testBranchParser("if(3>5){1}else{2}", If(Cond(">", Lit(3), Lit(5)), Lit(1), Lit(2)))
|
||||
}
|
||||
test("17") {
|
||||
testBranchParser("if(3>5){if(9<0){1}else{2}}else{4}", If(Cond(">", Lit(3), Lit(5)),
|
||||
If(Cond("<", Lit(9), Lit(0)), Lit(1), Lit(2)),
|
||||
Lit(4)))
|
||||
}
|
||||
test("18") {
|
||||
assertThrows[Exception] {
|
||||
testBranchParser("if(1+2) 3 else 5", Lit(2))
|
||||
}
|
||||
}
|
||||
test("19") {
|
||||
assertThrows[Exception] {
|
||||
testBranchParser("if(3>5){1}", If(Cond(">", Lit(3), Lit(5)), Lit(1), Lit(4)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//variable parser
|
||||
|
||||
test("20"){
|
||||
testVariableParser("var a = 4; a",VarDec("a",Lit(4),Ref("a")))
|
||||
}
|
||||
test("21"){
|
||||
testVariableParser("var a = 4; a=7",VarDec("a",Lit(4),VarAssign("a",Lit(7))))
|
||||
}
|
||||
test("22"){
|
||||
testVariableParser("var a = 4; var b=1; a=b=7",VarDec("a",Lit(4),
|
||||
VarDec("b",Lit(1),
|
||||
VarAssign("a",VarAssign("b",Lit(7))))))
|
||||
}
|
||||
test("23"){
|
||||
assertThrows[Exception] {
|
||||
testVariableParser("var a = 4 a",VarDec("a",Lit(4),Ref("a")))
|
||||
}
|
||||
}
|
||||
|
||||
//Loop Parser
|
||||
test("24"){
|
||||
testLoopParser("while(5>4)0;1",While(Cond(">",Lit(5),Lit(4)),Lit(0),Lit(1)))
|
||||
}
|
||||
test("25"){
|
||||
testLoopParser("while(5>4){while(3<6)9;0};1",While(Cond(">",Lit(5),Lit(4)),
|
||||
While(Cond("<",Lit(3),Lit(6)),Lit(9),Lit(0)),Lit(1)))
|
||||
}
|
||||
test("26"){
|
||||
assertThrows[Exception] {
|
||||
testLoopParser("while(5>4)0;",While(Cond(">",Lit(5),Lit(4)),Lit(0),Lit(1)))
|
||||
}
|
||||
}
|
||||
test("27"){
|
||||
testLoopParser("var x=5;while(x>0)x=x-1;x",VarDec("x",Lit(5),
|
||||
While(Cond(">",Ref("x"),Lit(0)),VarAssign("x",Prim("-",Ref("x"),Lit(1))),Ref("x"))))
|
||||
}
|
||||
test("28"){
|
||||
testLoopParser("var x=10;var b=1;while(x>0){if(b==5)b else b=b+7};b",VarDec("x",Lit(10),
|
||||
VarDec("b",Lit(1),
|
||||
While(Cond(">",Ref("x"),Lit(0)),
|
||||
If(Cond("==",Ref("b"),Lit(5)),Ref("b"),VarAssign("b",Prim("+",Ref("b"),Lit(7)))),Ref("b")))))
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package project2
|
||||
|
||||
import org.scalatest._
|
||||
|
||||
class SemanticAnalyzerTest extends TimedSuite {
|
||||
import Language._
|
||||
|
||||
def testSemanticAnalyzer(ast: 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 (w, e) = analyzer.run(ast)
|
||||
assert(w == nWarning, "Incorrect number of Warnings")
|
||||
assert(e == nError, "Incorrect number of Errors")
|
||||
}
|
||||
|
||||
test("29") {
|
||||
testSemanticAnalyzer(Lit(1), 0, 0)
|
||||
testSemanticAnalyzer(Prim("+", Lit(1), Lit(2)), 0, 0)
|
||||
}
|
||||
test("30"){
|
||||
testSemanticAnalyzer(Unary("*",Lit(1)),0,1)
|
||||
}
|
||||
test("31"){
|
||||
testSemanticAnalyzer(Prim("%",Lit(1),Lit(5)),0,1)
|
||||
}
|
||||
test("32"){
|
||||
testSemanticAnalyzer(VarDec("x",Lit(1),Let("x",Lit(5),Lit(4))), 1 ,0)
|
||||
}
|
||||
/*test("3"){
|
||||
testSemanticAnalyzer(Prim("%",Lit(1),Lit(5)),0,1)
|
||||
}
|
||||
test("3134"){
|
||||
testSemanticAnalyzer(Prim("%",Lit(1),Lit(5)),0,1)
|
||||
}*/
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package project2
|
||||
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