initial commit

master
billsun 1 year ago
commit 8e8c90ec38

14
.gitignore vendored

@ -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,5 @@
while (2 + 2) {
1
};
2

@ -0,0 +1 @@
if (2 > 4) 1 + 4 else 4

@ -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…
Cancel
Save