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