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