Disabled external gits
This commit is contained in:
Submodule cs320-clp deleted from bc6ea60401
7
cs320-clp/.gitignore
vendored
Normal file
7
cs320-clp/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
bin
|
||||
bin/*
|
||||
wasmout
|
||||
wasmout/*
|
||||
/.idea/
|
||||
/target/
|
||||
/project/target/
|
BIN
cs320-clp/amyc-frontend-1.6.jar
Normal file
BIN
cs320-clp/amyc-frontend-1.6.jar
Normal file
Binary file not shown.
23
cs320-clp/build.sbt
Normal file
23
cs320-clp/build.sbt
Normal file
@@ -0,0 +1,23 @@
|
||||
lazy val amyc = (project in file("."))
|
||||
.disablePlugins(plugins.JUnitXmlReportPlugin)
|
||||
.enablePlugins(StudentPlugin)
|
||||
.settings(
|
||||
name := "amyc",
|
||||
|
||||
version := "1.5",
|
||||
organization := "ch.epfl.lara",
|
||||
scalaVersion := "2.12.3",
|
||||
|
||||
scalaSource in Compile := baseDirectory.value / "src",
|
||||
scalacOptions ++= Seq("-feature"),
|
||||
scalacOptions += "-Xmixin-force-forwarders:false", //Fixes bug https://github.com/eclipse/lsp4j/issues/127
|
||||
|
||||
scalaSource in Test := baseDirectory.value / "test" / "scala",
|
||||
parallelExecution in Test := false,
|
||||
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test",
|
||||
libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.1",
|
||||
|
||||
resolvers += "lsp4j-repository" at "https://download.eclipse.org/lsp4j/maven",
|
||||
libraryDependencies += "org.eclipse.lsp4j" % "org.eclipse.lsp4j" % "0.8.1",
|
||||
testOptions += Tests.Argument(TestFrameworks.JUnit, "-v")
|
||||
)
|
36
cs320-clp/examples/Arithmetic.amy
Normal file
36
cs320-clp/examples/Arithmetic.amy
Normal file
@@ -0,0 +1,36 @@
|
||||
object Arithmetic {
|
||||
def pow(b: Int, e: Int): Int = {
|
||||
if (e == 0) { 1 }
|
||||
else {
|
||||
if (e % 2 == 0) {
|
||||
val rec: Int = pow(b, e/2);
|
||||
rec * rec
|
||||
} else {
|
||||
b * pow(b, e - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def gcd(a: Int, b: Int): Int = {
|
||||
if (a == 0 || b == 0) {
|
||||
a + b
|
||||
} else {
|
||||
if (a < b) {
|
||||
gcd(a, b % a)
|
||||
} else {
|
||||
gcd(a % b, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Std.printInt("");
|
||||
Std.printInt(pow(0, 10));
|
||||
Std.printInt(pow(1, 5));
|
||||
Std.printInt(pow(2, 10));
|
||||
Std.printInt(pow(3, 3));
|
||||
Std.printInt(gcd(0, 10));
|
||||
Std.printInt(gcd(17, 99)); // 1
|
||||
Std.printInt(gcd(16, 46)); // 2
|
||||
|
||||
Std.printInt(gcd(222, 888)) // 222
|
||||
}
|
34
cs320-clp/examples/Arithmetic.scala
Normal file
34
cs320-clp/examples/Arithmetic.scala
Normal file
@@ -0,0 +1,34 @@
|
||||
object Arithmetic {
|
||||
def pow(b: Int, e: Int): Int = {
|
||||
if (e == 0) { 1 }
|
||||
else {
|
||||
if (e % 2 == 0) {
|
||||
val rec: Int = pow(b, e/2);
|
||||
rec * rec
|
||||
} else {
|
||||
b * pow(b, e - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def gcd(a: Int, b: Int): Int = {
|
||||
if (a == 0 || b == 0) {
|
||||
a + b
|
||||
} else {
|
||||
if (a < b) {
|
||||
gcd(a, b % a)
|
||||
} else {
|
||||
gcd(a % b, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Std.printInt(pow(0, 10));
|
||||
Std.printInt(pow(1, 5));
|
||||
Std.printInt(pow(2, 10));
|
||||
Std.printInt(pow(3, 3));
|
||||
Std.printInt(gcd(0, 10));
|
||||
Std.printInt(gcd(17, 99)); //
|
||||
Std.printInt(gcd(16, 46)); // 2
|
||||
Std.printInt(gcd(222, 888)) // 222
|
||||
}
|
12
cs320-clp/examples/Factorial.scala
Normal file
12
cs320-clp/examples/Factorial.scala
Normal file
@@ -0,0 +1,12 @@
|
||||
object Factorial {
|
||||
def fact(i: Int): Int = {
|
||||
if (i < 2) { 1 }
|
||||
else {
|
||||
val rec: Int = fact(i-1);
|
||||
i * rec
|
||||
}
|
||||
}
|
||||
|
||||
Std.printString("5! = " ++ Std.intToString(fact(5)));
|
||||
Std.printString("10! = " ++ Std.intToString(fact(10)))
|
||||
}
|
16
cs320-clp/examples/Hanoi.scala
Normal file
16
cs320-clp/examples/Hanoi.scala
Normal file
@@ -0,0 +1,16 @@
|
||||
object Hanoi {
|
||||
|
||||
def solve(n : Int) : Int = {
|
||||
if (n < 1) {
|
||||
error("can't solve Hanoi for less than 1 plate")
|
||||
} else {
|
||||
if (n == 1) {
|
||||
1
|
||||
} else {
|
||||
2 * solve(n - 1) + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Std.printString("Hanoi for 4 plates: " ++ Std.intToString(solve(4)))
|
||||
}
|
3
cs320-clp/examples/Hello.scala
Normal file
3
cs320-clp/examples/Hello.scala
Normal file
@@ -0,0 +1,3 @@
|
||||
object Hello {
|
||||
Std.printString("Hello " ++ "world!")
|
||||
}
|
7
cs320-clp/examples/HelloInt.scala
Normal file
7
cs320-clp/examples/HelloInt.scala
Normal file
@@ -0,0 +1,7 @@
|
||||
object HelloInt {
|
||||
Std.printString("What is your name?");
|
||||
val name: String = Std.readString();
|
||||
Std.printString("Hello " ++ name ++ "! And how old are you?");
|
||||
val age: Int = Std.readInt();
|
||||
Std.printString(Std.intToString(age) ++ " years old then.");
|
||||
}
|
12
cs320-clp/examples/Printing.scala
Normal file
12
cs320-clp/examples/Printing.scala
Normal file
@@ -0,0 +1,12 @@
|
||||
object Printing {
|
||||
Std.printInt(0); Std.printInt(-222); Std.printInt(42);
|
||||
Std.printBoolean(true); Std.printBoolean(false);
|
||||
Std.printString(Std.digitToString(0));
|
||||
Std.printString(Std.digitToString(5));
|
||||
Std.printString(Std.digitToString(9));
|
||||
Std.printString(Std.intToString(0));
|
||||
Std.printString(Std.intToString(-111));
|
||||
Std.printString(Std.intToString(22));
|
||||
Std.printString("Hello " ++ "world!");
|
||||
Std.printString("" ++ "")
|
||||
}
|
6
cs320-clp/examples/TestLists.scala
Normal file
6
cs320-clp/examples/TestLists.scala
Normal file
@@ -0,0 +1,6 @@
|
||||
object TestLists {
|
||||
val l: L.List = L.Cons(5, L.Cons(-5, L.Cons(-1, L.Cons(0, L.Cons(10, L.Nil())))));
|
||||
Std.printString(L.toString(L.concat(L.Cons(1, L.Cons(2, L.Nil())), L.Cons(3, L.Nil()))));
|
||||
Std.printInt(L.sum(l));
|
||||
Std.printString(L.toString(L.mergeSort(l)))
|
||||
}
|
36
cs320-clp/extension-examples/Arithmetic.amy
Normal file
36
cs320-clp/extension-examples/Arithmetic.amy
Normal file
@@ -0,0 +1,36 @@
|
||||
object Arithmetic {
|
||||
def pow(b: Int, e: Int): Int = {
|
||||
if (e == 0) { 1 }
|
||||
else {
|
||||
if (e % 2 == 0) {
|
||||
val rec: Int = pow(b, e/2);
|
||||
rec * rec
|
||||
} else {
|
||||
b * pow(b, e - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def gcd(a: Int, b: Int): Int = {
|
||||
if (a == 0 || b == 0) {
|
||||
a + b
|
||||
} else {
|
||||
if (a < b) {
|
||||
gcd(a, b % a)
|
||||
} else {
|
||||
gcd(a % b, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Std.printInt("");
|
||||
Std.printInt(pow(0, 10));
|
||||
Std.printInt(pow(1, 5));
|
||||
Std.printInt(pow(2, 10));
|
||||
Std.printInt(pow(3, 3));
|
||||
Std.printInt(gcd(0, 10));
|
||||
Std.printInt(gcd(17, 99)); // 1
|
||||
Std.printInt(gcd(16, 46)); // 2
|
||||
|
||||
Std.printInt(gcd(222, 888)) // 222
|
||||
}
|
38
cs320-clp/extension-examples/howto-ST
Normal file
38
cs320-clp/extension-examples/howto-ST
Normal file
@@ -0,0 +1,38 @@
|
||||
How to sublimetext:
|
||||
|
||||
1) install LSP package
|
||||
2) Add this to user settings :
|
||||
|
||||
"clients":
|
||||
{
|
||||
"amycd":
|
||||
{
|
||||
"command":
|
||||
[
|
||||
"sbt",
|
||||
"run"
|
||||
],
|
||||
"enabled": true,
|
||||
"languages":
|
||||
[
|
||||
{
|
||||
"languageId": "amyc",
|
||||
"scopes":
|
||||
[
|
||||
"source.amy",
|
||||
"source.scala"
|
||||
],
|
||||
"syntaxes":
|
||||
[
|
||||
"Packages/Scala/Scala.sublime-syntax"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
Note: With the previous command/setting, you need to edit code in tha same project as the amyc language server (since it runs naivly and doesnt exist in the PATH or as an alias)
|
||||
|
||||
3) Open a .amy file
|
||||
4) Set Syntax to scala
|
||||
5) Start LSP server
|
BIN
cs320-clp/lib/grammarcomparison_2.12-0.1.jar
Normal file
BIN
cs320-clp/lib/grammarcomparison_2.12-0.1.jar
Normal file
Binary file not shown.
BIN
cs320-clp/lib/scallion_2.12-0.3.jar
Normal file
BIN
cs320-clp/lib/scallion_2.12-0.3.jar
Normal file
Binary file not shown.
144
cs320-clp/library/List.scala
Normal file
144
cs320-clp/library/List.scala
Normal file
@@ -0,0 +1,144 @@
|
||||
object L {
|
||||
abstract class List
|
||||
case class Nil() extends List
|
||||
case class Cons(h: Int, t: List) extends List
|
||||
|
||||
def isEmpty(l : List): Boolean = { l match {
|
||||
case Nil() => true
|
||||
case _ => false
|
||||
}}
|
||||
|
||||
def length(l: List): Int = { l match {
|
||||
case Nil() => 0
|
||||
case Cons(_, t) => 1 + length(t)
|
||||
}}
|
||||
|
||||
def head(l: List): Int = {
|
||||
l match {
|
||||
case Cons(h, _) => h
|
||||
case Nil() => error("head(Nil)")
|
||||
}
|
||||
}
|
||||
|
||||
def headOption(l: List): O.Option = {
|
||||
l match {
|
||||
case Cons(h, _) => O.Some(h)
|
||||
case Nil() => O.None()
|
||||
}
|
||||
}
|
||||
|
||||
def reverse(l: List): List = {
|
||||
reverseAcc(l, Nil())
|
||||
}
|
||||
|
||||
def reverseAcc(l: List, acc: List): List = {
|
||||
l match {
|
||||
case Nil() => acc
|
||||
case Cons(h, t) => reverseAcc(t, Cons(h, acc))
|
||||
}
|
||||
}
|
||||
|
||||
def indexOf(l: List, i: Int): Int = {
|
||||
l match {
|
||||
case Nil() => -1
|
||||
case Cons(h, t) =>
|
||||
if (h == i) { 0 }
|
||||
else {
|
||||
val rec: Int = indexOf(t, i);
|
||||
if (0 <= rec) { rec + 1 }
|
||||
else { -1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def range(from: Int, to: Int): List = {
|
||||
if (to < from) { Nil() }
|
||||
else {
|
||||
Cons(from, range(from + 1, to))
|
||||
}
|
||||
}
|
||||
|
||||
def sum(l: List): Int = { l match {
|
||||
case Nil() => 0
|
||||
case Cons(h, t) => h + sum(t)
|
||||
}}
|
||||
|
||||
def concat(l1: List, l2: List): List = {
|
||||
l1 match {
|
||||
case Nil() => l2
|
||||
case Cons(h, t) => Cons(h, concat(t, l2))
|
||||
}
|
||||
}
|
||||
|
||||
def contains(l: List, elem: Int): Boolean = { l match {
|
||||
case Nil() =>
|
||||
false
|
||||
case Cons(h, t) =>
|
||||
h == elem || contains(t, elem)
|
||||
}}
|
||||
|
||||
abstract class LPair
|
||||
case class LP(l1: List, l2: List) extends LPair
|
||||
|
||||
def merge(l1: List, l2: List): List = {
|
||||
l1 match {
|
||||
case Nil() => l2
|
||||
case Cons(h1, t1) =>
|
||||
l2 match {
|
||||
case Nil() => l1
|
||||
case Cons(h2, t2) =>
|
||||
if (h1 <= h2) {
|
||||
Cons(h1, merge(t1, l2))
|
||||
} else {
|
||||
Cons(h2, merge(l1, t2))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def split(l: List): LPair = {
|
||||
l match {
|
||||
case Cons(h1, Cons(h2, t)) =>
|
||||
val rec: LPair = split(t);
|
||||
rec match {
|
||||
case LP(rec1, rec2) =>
|
||||
LP(Cons(h1, rec1), Cons(h2, rec2))
|
||||
}
|
||||
case _ =>
|
||||
LP(l, Nil())
|
||||
}
|
||||
}
|
||||
def mergeSort(l: List): List = {
|
||||
l match {
|
||||
case Nil() => l
|
||||
case Cons(h, Nil()) => l
|
||||
case l =>
|
||||
split(l) match {
|
||||
case LP(l1, l2) =>
|
||||
merge(mergeSort(l1), mergeSort(l2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def toString(l: List): String = { l match {
|
||||
case Nil() => "List()"
|
||||
case more => "List(" ++ toString1(more) ++ ")"
|
||||
}}
|
||||
|
||||
def toString1(l : List): String = { l match {
|
||||
case Cons(h, Nil()) => Std.intToString(h)
|
||||
case Cons(h, t) => Std.intToString(h) ++ ", " ++ toString1(t)
|
||||
}}
|
||||
|
||||
def take(l: List, n: Int): List = {
|
||||
if (n <= 0) { Nil() }
|
||||
else {
|
||||
l match {
|
||||
case Nil() => Nil()
|
||||
case Cons(h, t) =>
|
||||
Cons(h, take(t, n-1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
40
cs320-clp/library/Option.scala
Normal file
40
cs320-clp/library/Option.scala
Normal file
@@ -0,0 +1,40 @@
|
||||
object O {
|
||||
abstract class Option
|
||||
case class None() extends Option
|
||||
case class Some(v: Int) extends Option
|
||||
|
||||
def isDefined(o: Option): Boolean = {
|
||||
o match {
|
||||
case None() => false
|
||||
case _ => true
|
||||
}
|
||||
}
|
||||
|
||||
def get(o: Option): Int = {
|
||||
o match {
|
||||
case Some(i) => i
|
||||
case None() => error("get(None)")
|
||||
}
|
||||
}
|
||||
|
||||
def getOrElse(o: Option, i: Int): Int = {
|
||||
o match {
|
||||
case None() => i
|
||||
case Some(oo) => oo
|
||||
}
|
||||
}
|
||||
|
||||
def orElse(o1: Option, o2: Option): Option = {
|
||||
o1 match {
|
||||
case Some(_) => o1
|
||||
case None() => o2
|
||||
}
|
||||
}
|
||||
|
||||
def toList(o: Option): L.List = {
|
||||
o match {
|
||||
case Some(i) => L.Cons(i, L.Nil())
|
||||
case None() => L.Nil()
|
||||
}
|
||||
}
|
||||
}
|
40
cs320-clp/library/Std.scala
Normal file
40
cs320-clp/library/Std.scala
Normal file
@@ -0,0 +1,40 @@
|
||||
/** This module contains basic functionality for Amy,
|
||||
* including stub implementations for some built-in functions
|
||||
* (implemented in WASM or JavaScript)
|
||||
*/
|
||||
object Std {
|
||||
def printInt(i: Int): Unit = {
|
||||
error("") // Stub implementation
|
||||
}
|
||||
def printString(s: String): Unit = {
|
||||
error("") // Stub implementation
|
||||
}
|
||||
def printBoolean(b: Boolean): Unit = {
|
||||
printString(booleanToString(b))
|
||||
}
|
||||
|
||||
def readString(): String = {
|
||||
error("") // Stub implementation
|
||||
}
|
||||
|
||||
def readInt(): Int = {
|
||||
error("") // Stub implementation
|
||||
}
|
||||
|
||||
def intToString(i: Int): String = {
|
||||
if (i < 0) {
|
||||
"-" ++ intToString(-i)
|
||||
} else {
|
||||
val rem: Int = i % 10;
|
||||
val div: Int = i / 10;
|
||||
if (div == 0) { digitToString(rem) }
|
||||
else { intToString(div) ++ digitToString(rem) }
|
||||
}
|
||||
}
|
||||
def digitToString(i: Int): String = {
|
||||
error("") // Stub implementation
|
||||
}
|
||||
def booleanToString(b: Boolean): String = {
|
||||
if (b) { "true" } else { "false" }
|
||||
}
|
||||
}
|
98
cs320-clp/project/StudentPlugin.scala
Normal file
98
cs320-clp/project/StudentPlugin.scala
Normal file
@@ -0,0 +1,98 @@
|
||||
/** Adapted from student-build/project/StudentPlugin.scala in the moocs repo */
|
||||
package ch.epfl.lara
|
||||
|
||||
import java.io.{File, FileInputStream, IOException}
|
||||
|
||||
import sbt.Keys._
|
||||
import sbt._
|
||||
|
||||
/**
|
||||
* Provides tasks for submitting the assignment
|
||||
*/
|
||||
object StudentPlugin extends AutoPlugin {
|
||||
|
||||
override lazy val projectSettings = Seq(
|
||||
packageSubmissionSetting
|
||||
) ++ packageSubmissionZipSettings
|
||||
|
||||
/** **********************************************************
|
||||
* SUBMITTING A SOLUTION TO COURSERA
|
||||
*/
|
||||
|
||||
val packageSourcesOnly = TaskKey[File]("packageSourcesOnly", "Package the sources of the project")
|
||||
|
||||
val packageSubmissionZip = TaskKey[File]("packageSubmissionZip")
|
||||
|
||||
val packageSubmissionZipSettings = Seq(
|
||||
packageSubmissionZip := {
|
||||
val submission = crossTarget.value / "submission.zip"
|
||||
val sources = (packageSourcesOnly in Compile).value
|
||||
val binaries = (packageBin in Compile).value
|
||||
IO.zip(Seq(sources -> "sources.zip", binaries -> "binaries.jar"), submission)
|
||||
submission
|
||||
},
|
||||
// Exclude resources from binaries
|
||||
mappings in (Compile, packageBin) := {
|
||||
val relativePaths =
|
||||
(unmanagedResources in Compile).value.flatMap(Path.relativeTo((unmanagedResourceDirectories in Compile).value)(_))
|
||||
(mappings in (Compile, packageBin)).value.filterNot { case (_, path) => relativePaths.contains(path) }
|
||||
},
|
||||
artifactClassifier in packageSourcesOnly := Some("sources")
|
||||
) ++ inConfig(Compile)(Defaults.packageTaskSettings(packageSourcesOnly, Defaults.sourceMappings))
|
||||
|
||||
val maxSubmitFileSize = {
|
||||
val mb = 1024 * 1024
|
||||
10 * mb
|
||||
}
|
||||
|
||||
/** Check that the jar exists, isn't empty, isn't crazy big, and can be read
|
||||
*/
|
||||
def checkJar(jar: File, s: TaskStreams): Unit = {
|
||||
val errPrefix = "Error submitting assignment jar: "
|
||||
val fileLength = jar.length()
|
||||
if (!jar.exists()) {
|
||||
s.log.error(errPrefix + "jar archive does not exist\n" + jar.getAbsolutePath)
|
||||
failSubmit()
|
||||
} else if (fileLength == 0L) {
|
||||
s.log.error(errPrefix + "jar archive is empty\n" + jar.getAbsolutePath)
|
||||
failSubmit()
|
||||
} else if (fileLength > maxSubmitFileSize) {
|
||||
s.log.error(errPrefix + "jar archive is too big. Allowed size: " +
|
||||
maxSubmitFileSize + " bytes, found " + fileLength + " bytes.\n" +
|
||||
jar.getAbsolutePath)
|
||||
failSubmit()
|
||||
} else {
|
||||
val bytes = new Array[Byte](fileLength.toInt)
|
||||
val sizeRead = try {
|
||||
val is = new FileInputStream(jar)
|
||||
val read = is.read(bytes)
|
||||
is.close()
|
||||
read
|
||||
} catch {
|
||||
case ex: IOException =>
|
||||
s.log.error(errPrefix + "failed to read sources jar archive\n" + ex.toString)
|
||||
failSubmit()
|
||||
}
|
||||
if (sizeRead != bytes.length) {
|
||||
s.log.error(errPrefix + "failed to read the sources jar archive, size read: " + sizeRead)
|
||||
failSubmit()
|
||||
} else () // used to be: `encodeBase64(bytes)`
|
||||
}
|
||||
}
|
||||
|
||||
/** Task to package solution to a given file path */
|
||||
val packageSubmission = inputKey[Unit]("package solution as an archive file")
|
||||
lazy val packageSubmissionSetting = packageSubmission := {
|
||||
val s: TaskStreams = streams.value // for logging
|
||||
val jar = (packageSubmissionZip in Compile).value
|
||||
|
||||
checkJar(jar, s)
|
||||
|
||||
val path = baseDirectory.value / "submission.zip"
|
||||
IO.copyFile(jar, path)
|
||||
}
|
||||
|
||||
def failSubmit(): Nothing = {
|
||||
sys.error("Packaging failed")
|
||||
}
|
||||
}
|
1
cs320-clp/project/build.properties
Normal file
1
cs320-clp/project/build.properties
Normal file
@@ -0,0 +1 @@
|
||||
sbt.version=1.3.5
|
BIN
cs320-clp/report/report.pdf
Normal file
BIN
cs320-clp/report/report.pdf
Normal file
Binary file not shown.
BIN
cs320-clp/src/.DS_Store
vendored
Normal file
BIN
cs320-clp/src/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
cs320-clp/src/amyc/.DS_Store
vendored
Normal file
BIN
cs320-clp/src/amyc/.DS_Store
vendored
Normal file
Binary file not shown.
55
cs320-clp/src/amyc/Main.scala
Normal file
55
cs320-clp/src/amyc/Main.scala
Normal file
@@ -0,0 +1,55 @@
|
||||
package amyc
|
||||
|
||||
import java.io.File
|
||||
|
||||
import amyc.analyzer._
|
||||
import amyc.ast._
|
||||
import amyc.languageserver.LSLauncher
|
||||
import amyc.parsing._
|
||||
import amyc.utils._
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
object StdioLauncher {
|
||||
|
||||
}
|
||||
|
||||
|
||||
object Main extends MainHelpers {
|
||||
private def parseArgs(args: Array[String]): Context = {
|
||||
Context(new Reporter, args.toList)
|
||||
}
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
|
||||
println("Starting LS")
|
||||
val lsl = new LSLauncher();
|
||||
lsl.main(args)
|
||||
}
|
||||
}
|
||||
|
||||
trait MainHelpers {
|
||||
import NominalTreeModule.{Program => NP}
|
||||
import SymbolicTreeModule.{Program => SP}
|
||||
|
||||
def treePrinterS(title: String): Pipeline[(SP, SymbolTable), Unit] = {
|
||||
new Pipeline[(SP, SymbolTable), Unit] {
|
||||
def run(ctx: Context)(v: (SP, SymbolTable)) = {
|
||||
println(title)
|
||||
println(SymbolicPrinter(v._1)(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def treePrinterN(title: String): Pipeline[NP, Unit] = {
|
||||
new Pipeline[NP, Unit] {
|
||||
def run(ctx: Context)(v: NP) = {
|
||||
println(title)
|
||||
println(NominalPrinter(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
283
cs320-clp/src/amyc/analyzer/NameAnalyzer.scala
Normal file
283
cs320-clp/src/amyc/analyzer/NameAnalyzer.scala
Normal file
@@ -0,0 +1,283 @@
|
||||
package amyc
|
||||
package analyzer
|
||||
|
||||
import amyc.ast.{Identifier, NominalTreeModule => N, SymbolicTreeModule => S}
|
||||
import amyc.utils._
|
||||
|
||||
// Name analyzer for Amy
|
||||
// Takes a nominal program (names are plain strings, qualified names are string pairs)
|
||||
// and returns a symbolic program, where all names have been resolved to unique Identifiers.
|
||||
// Rejects programs that violate the Amy naming rules.
|
||||
// Also populates and returns the symbol table.
|
||||
object NameAnalyzer extends Pipeline[N.Program, (S.Program, SymbolTable)] {
|
||||
def run(ctx: Context)(p: N.Program): (S.Program, SymbolTable) = {
|
||||
import ctx.reporter._
|
||||
|
||||
// Step 0: Initialize symbol table
|
||||
val table = new SymbolTable
|
||||
|
||||
// Step 1: Add modules to table
|
||||
val modNames = p.modules.groupBy(_.name)
|
||||
modNames.foreach { case (name, modules) =>
|
||||
if (modules.size > 1) {
|
||||
fatal(s"Two modules named $name in program", modules.head.position)
|
||||
}
|
||||
}
|
||||
|
||||
modNames.keys.toList foreach table.addModule
|
||||
|
||||
|
||||
// Helper method: will transform a nominal type 'tt' to a symbolic type,
|
||||
// given that we are within module 'inModule'.
|
||||
def transformType(tt: N.TypeTree, inModule: String): S.Type = {
|
||||
tt.tpe match {
|
||||
case N.IntType => S.IntType
|
||||
case N.BooleanType => S.BooleanType
|
||||
case N.StringType => S.StringType
|
||||
case N.UnitType => S.UnitType
|
||||
case N.ClassType(qn@N.QualifiedName(module, name)) =>
|
||||
table.getType(module getOrElse inModule, name) match {
|
||||
case Some(symbol) =>
|
||||
S.ClassType(symbol)
|
||||
case None =>
|
||||
fatal(s"Could not find type $qn", tt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Check name uniqueness of definitions in each module
|
||||
p.modules.foreach{
|
||||
mod => mod.defs.groupBy(_.name).foreach {
|
||||
case (name, ld) =>
|
||||
if (ld.size > 1) fatal(s"Multiple definitions of module: $name in ${mod.name}")
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Discover types and add them to symbol table
|
||||
p.modules.foreach{
|
||||
mod => mod.defs.foreach {
|
||||
case N.AbstractClassDef(name) =>
|
||||
table.addType(
|
||||
mod.name,
|
||||
name
|
||||
)
|
||||
case _ => //Ignore if not abstract-class
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Discover type constructors, add them to table
|
||||
p.modules.foreach{
|
||||
mod => mod.defs.foreach {
|
||||
case N.CaseClassDef(name, fields, parent) =>
|
||||
table.addConstructor(
|
||||
mod.name,
|
||||
name,
|
||||
fields.map(tt => transformType(tt, mod.name)),
|
||||
table.getType(mod.name, parent).getOrElse(fatal(s"Non-existant: ${mod.name}"))
|
||||
)
|
||||
case _ => //Ignore if not case-class
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Discover functions signatures, add them to table
|
||||
p.modules.foreach {
|
||||
mod => mod.defs.foreach{
|
||||
case N.FunDef(name, params, rt, _) =>
|
||||
table.addFunction(
|
||||
mod.name,
|
||||
name,
|
||||
params.map(p => p.tt).map(tt => transformType(tt, mod.name)),
|
||||
transformType(rt, mod.name)
|
||||
)
|
||||
case _ => //Ignore if not fun (note: last assignment was fun)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Step 6: We now know all definitions in the program.
|
||||
// Reconstruct modules and analyse function bodies/ expressions
|
||||
|
||||
// This part is split into three transfrom functions,
|
||||
// for definitions, FunDefs, and expressions.
|
||||
// Keep in mind that we transform constructs of the NominalTreeModule 'N' to respective constructs of the SymbolicTreeModule 'S'.
|
||||
// transformFunDef is given as an example, as well as some code for the other ones
|
||||
|
||||
def transformDef(df: N.ClassOrFunDef, module: String): S.ClassOrFunDef = { df match {
|
||||
case N.AbstractClassDef(name) =>
|
||||
table.getType(module, name) match {
|
||||
case Some(n)=> S.AbstractClassDef(n)
|
||||
case None => fatal(s"Could not find class name")
|
||||
}
|
||||
case N.CaseClassDef(name, _, _) =>
|
||||
table.getConstructor(module, name) match {
|
||||
case Some(c)=>
|
||||
S.CaseClassDef(c._1,c._2.argTypes.map(arg => S.TypeTree(arg)),c._2.parent)
|
||||
case None => fatal(s"Could not find class name")
|
||||
}
|
||||
case fd: N.FunDef =>
|
||||
transformFunDef(fd, module)
|
||||
}}.setPos(df)
|
||||
|
||||
def transformFunDef(fd: N.FunDef, module: String): S.FunDef = {
|
||||
val N.FunDef(name, params, retType, body) = fd
|
||||
val Some((sym, sig)) = table.getFunction(module, name)
|
||||
|
||||
params.groupBy(_.name).foreach { case (name, ps) =>
|
||||
if (ps.size > 1) {
|
||||
fatal(s"Two parameters named $name in function ${fd.name}", fd)
|
||||
}
|
||||
}
|
||||
|
||||
val paramNames = params.map(_.name)
|
||||
|
||||
val newParams = params zip sig.argTypes map { case (pd@N.ParamDef(name, tt), tpe) =>
|
||||
val s = Identifier.fresh(name)
|
||||
S.ParamDef(s, S.TypeTree(tpe).setPos(tt)).setPos(pd)
|
||||
}
|
||||
|
||||
val paramsMap = paramNames.zip(newParams.map(_.name)).toMap
|
||||
|
||||
S.FunDef(
|
||||
sym,
|
||||
newParams,
|
||||
S.TypeTree(sig.retType).setPos(retType),
|
||||
transformExpr(body)(module, (paramsMap, Map()))
|
||||
).setPos(fd)
|
||||
}
|
||||
|
||||
// This function takes as implicit a pair of two maps:
|
||||
// The first is a map from names of parameters to their unique identifiers,
|
||||
// the second is similar for local variables.
|
||||
// Make sure to update them correctly if needed given the scoping rules of Amy
|
||||
def transformExpr(expr: N.Expr)
|
||||
(implicit module: String, names: (Map[String, Identifier], Map[String, Identifier])): S.Expr = {
|
||||
val (params, locals) = names
|
||||
val res : S.Expr = expr match {
|
||||
|
||||
|
||||
// L1
|
||||
case N.Let(df, value, body) =>
|
||||
if (locals.contains(df.name))
|
||||
fatal(s"Redefinition of variable ${df.name}", df.position)
|
||||
val sn = Identifier.fresh(df.name)
|
||||
S.Let(S.ParamDef(sn, S.TypeTree(transformType(df.tt, module))), transformExpr(value), transformExpr(body)(module, (params, locals + (df.name -> sn))))
|
||||
|
||||
case N.Sequence(s1, s2) => S.Sequence(transformExpr(s1),transformExpr(s2))
|
||||
|
||||
|
||||
// L2
|
||||
case N.Match(scrut, cases) =>
|
||||
// Returns a transformed pattern along with all bindings
|
||||
// from strings to unique identifiers for names bound in the pattern.
|
||||
// Also, calls 'fatal' if a new name violates the Amy naming rules.
|
||||
def transformPattern(pat: N.Pattern): (S.Pattern, List[(String, Identifier)]) = {
|
||||
pat match{
|
||||
case N.LiteralPattern(l) => l match{
|
||||
case N.BooleanLiteral(x) => (S.LiteralPattern(S.BooleanLiteral(x)), List())
|
||||
case N.IntLiteral(x) => (S.LiteralPattern(S.IntLiteral(x)), List())
|
||||
case N.StringLiteral(x) => (S.LiteralPattern(S.StringLiteral(x)), List())
|
||||
case N.UnitLiteral() => (S.LiteralPattern(S.UnitLiteral()), List())
|
||||
}
|
||||
case N.IdPattern(name) =>
|
||||
if(locals.contains(name)){
|
||||
fatal(s"Duplicate variable $name")
|
||||
}
|
||||
if(name == s"Nil"){
|
||||
warning("Maybe you meant to write `Nil()` ?")
|
||||
}
|
||||
val id = Identifier.fresh(name)
|
||||
(S.IdPattern(id), List((name,id)))
|
||||
case N.WildcardPattern() => (S.WildcardPattern(), List())
|
||||
case N.CaseClassPattern(p_name,args) =>
|
||||
val owner = p_name.module.getOrElse(module)
|
||||
val name = p_name.name
|
||||
val constructor = table.getConstructor(owner,name).getOrElse(fatal(s"Constructor not found for Pattern $owner.$name"))
|
||||
if(constructor._2.argTypes.size != args.size)
|
||||
fatal(s"Invalid Arg Count for $owner.$name: had ${args.size}, expected ${constructor._2.argTypes.size}")
|
||||
|
||||
val n_args = args.map(arg => transformPattern(arg))
|
||||
|
||||
val pn_args = n_args.map(_._1)
|
||||
val ln_args = n_args.flatMap(_._2)
|
||||
|
||||
ln_args.groupBy(_._1).foreach(el => {
|
||||
if(el._2.size > 1)
|
||||
fatal(s"Duplicate local variable ${el._1}")
|
||||
if(locals.contains(el._1))
|
||||
warning(s"Shadowing an existing variable ${el._1}")
|
||||
})
|
||||
|
||||
(S.CaseClassPattern(constructor._1, pn_args),ln_args)
|
||||
}
|
||||
}
|
||||
|
||||
def transformCase(cse: N.MatchCase) : S.MatchCase = {
|
||||
val N.MatchCase(pat, rhs) = cse
|
||||
val (trans_pat, local_locals) = transformPattern(pat)
|
||||
S.MatchCase(trans_pat,transformExpr(rhs)(module, (params,locals ++ local_locals)))
|
||||
}
|
||||
|
||||
S.Match(transformExpr(scrut), cases.map(transformCase))
|
||||
|
||||
case N.Ite(cond, thenn, elze) => S.Ite(transformExpr(cond), transformExpr(thenn), transformExpr(elze))
|
||||
|
||||
|
||||
// L3
|
||||
case N.Plus(lhs,rhs) => S.Plus(transformExpr(lhs),transformExpr(rhs))
|
||||
case N.Minus(lhs,rhs) => S.Minus(transformExpr(lhs),transformExpr(rhs))
|
||||
case N.Times(lhs,rhs) => S.Times(transformExpr(lhs),transformExpr(rhs))
|
||||
case N.Div(lhs,rhs) => S.Div(transformExpr(lhs),transformExpr(rhs))
|
||||
case N.Mod(lhs,rhs) => S.Mod(transformExpr(lhs),transformExpr(rhs))
|
||||
case N.LessThan(lhs,rhs) => S.LessThan(transformExpr(lhs),transformExpr(rhs))
|
||||
case N.LessEquals(lhs,rhs) => S.LessEquals(transformExpr(lhs),transformExpr(rhs))
|
||||
case N.And(lhs,rhs) => S.And(transformExpr(lhs),transformExpr(rhs))
|
||||
case N.Or(lhs,rhs) => S.Or(transformExpr(lhs),transformExpr(rhs))
|
||||
case N.Equals(lhs,rhs) => S.Equals(transformExpr(lhs),transformExpr(rhs))
|
||||
case N.Concat(lhs,rhs) => S.Concat(transformExpr(lhs),transformExpr(rhs))
|
||||
|
||||
|
||||
// L4
|
||||
case N.Not(e) => S.Not(transformExpr(e))
|
||||
case N.Neg(e) => S.Neg(transformExpr(e))
|
||||
|
||||
|
||||
// L5
|
||||
case N.IntLiteral(v) => S.IntLiteral(v)
|
||||
case N.BooleanLiteral(v) => S.BooleanLiteral(v)
|
||||
case N.StringLiteral(v) => S.StringLiteral(v)
|
||||
case N.UnitLiteral() => S.UnitLiteral()
|
||||
|
||||
case N.Error(err) => S.Error(transformExpr(err))
|
||||
|
||||
case N.Variable(n) => S.Variable(locals.getOrElse(n, params.getOrElse(n, fatal(s"No variable $n", expr))))
|
||||
|
||||
case N.Call(qname, args) =>
|
||||
val owner = qname.module.getOrElse(module)
|
||||
val name = qname.name
|
||||
|
||||
val (sn, cs) = table.getConstructor(owner, name).getOrElse(table.getFunction(owner,name).getOrElse(fatal(s"No function found for $owner.$name")))
|
||||
if (args.size != cs.argTypes.size)
|
||||
fatal(s"Invalid Arg count for $owner.$name")
|
||||
val fun_args = args.map(arg => transformExpr(arg))
|
||||
S.Call(sn, fun_args)
|
||||
|
||||
//case _ =>// TODO: Implement the rest of the cases
|
||||
}
|
||||
res.setPos(expr)
|
||||
}
|
||||
|
||||
// Putting it all together to construct the final program for step 6.
|
||||
val newProgram = S.Program(
|
||||
p.modules map { case mod@N.ModuleDef(name, defs, optExpr) =>
|
||||
S.ModuleDef(
|
||||
table.getModule(name).get,
|
||||
defs map (transformDef(_, name)),
|
||||
optExpr map (transformExpr(_)(name, (Map(), Map())))
|
||||
).setPos(mod)
|
||||
}
|
||||
).setPos(p)
|
||||
|
||||
(newProgram, table)
|
||||
|
||||
}
|
||||
}
|
85
cs320-clp/src/amyc/analyzer/SymbolTable.scala
Normal file
85
cs320-clp/src/amyc/analyzer/SymbolTable.scala
Normal file
@@ -0,0 +1,85 @@
|
||||
package amyc.analyzer
|
||||
|
||||
import amyc.ast.Identifier
|
||||
import amyc.ast.SymbolicTreeModule._
|
||||
import amyc.utils.UniqueCounter
|
||||
|
||||
import scala.collection.mutable.HashMap
|
||||
|
||||
trait Signature[RT <: Type]{
|
||||
val argTypes: List[Type]
|
||||
val retType: RT
|
||||
}
|
||||
// The signature of a function in the symbol table
|
||||
case class FunSig(argTypes: List[Type], retType: Type, owner: Identifier) extends Signature[Type]
|
||||
// The signature of a constructor in the symbol table
|
||||
case class ConstrSig(argTypes: List[Type], parent: Identifier, index: Int) extends Signature[ClassType] {
|
||||
val retType = ClassType(parent)
|
||||
}
|
||||
|
||||
// A class that represents a dictionary of symbols for an Amy program
|
||||
class SymbolTable {
|
||||
private val defsByName = HashMap[(String, String), Identifier]()
|
||||
private val modules = HashMap[String, Identifier]()
|
||||
|
||||
private val types = HashMap[Identifier, Identifier]()
|
||||
private val functions = HashMap[Identifier, FunSig]()
|
||||
private val constructors = HashMap[Identifier, ConstrSig]()
|
||||
|
||||
private val typesToConstructors = HashMap[Identifier, List[Identifier]]()
|
||||
|
||||
private val constrIndexes = new UniqueCounter[Identifier]
|
||||
|
||||
def addModule(name: String) = {
|
||||
val s = Identifier.fresh(name)
|
||||
modules += name -> s
|
||||
s
|
||||
}
|
||||
def getModule(name: String) = modules.get(name)
|
||||
|
||||
def addType(owner: String, name: String) = {
|
||||
val s = Identifier.fresh(name)
|
||||
defsByName += (owner, name) -> s
|
||||
types += (s -> modules.getOrElse(owner, sys.error(s"Module $name not found!")))
|
||||
s
|
||||
}
|
||||
def getType(owner: String, name: String) =
|
||||
defsByName.get(owner,name) filter types.contains
|
||||
def getType(symbol: Identifier) = types.get(symbol)
|
||||
|
||||
def addConstructor(owner: String, name: String, argTypes: List[Type], parent: Identifier) = {
|
||||
val s = Identifier.fresh(name)
|
||||
defsByName += (owner, name) -> s
|
||||
constructors += s -> ConstrSig(
|
||||
argTypes,
|
||||
parent,
|
||||
constrIndexes.next(parent)
|
||||
)
|
||||
typesToConstructors += parent -> (typesToConstructors.getOrElse(parent, Nil) :+ s)
|
||||
s
|
||||
}
|
||||
def getConstructor(owner: String, name: String): Option[(Identifier, ConstrSig)] = {
|
||||
for {
|
||||
sym <- defsByName.get(owner, name)
|
||||
sig <- constructors.get(sym)
|
||||
} yield (sym, sig)
|
||||
}
|
||||
def getConstructor(symbol: Identifier) = constructors.get(symbol)
|
||||
|
||||
def getConstructorsForType(t: Identifier) = typesToConstructors.get(t)
|
||||
|
||||
def addFunction(owner: String, name: String, argTypes: List[Type], retType: Type) = {
|
||||
val s = Identifier.fresh(name)
|
||||
defsByName += (owner, name) -> s
|
||||
functions += s -> FunSig(argTypes, retType, getModule(owner).getOrElse(sys.error(s"Module $owner not found!")))
|
||||
s
|
||||
}
|
||||
def getFunction(owner: String, name: String): Option[(Identifier, FunSig)] = {
|
||||
for {
|
||||
sym <- defsByName.get(owner, name)
|
||||
sig <- functions.get(sym)
|
||||
} yield (sym, sig)
|
||||
}
|
||||
def getFunction(symbol: Identifier) = functions.get(symbol)
|
||||
|
||||
}
|
175
cs320-clp/src/amyc/analyzer/TypeChecker.scala
Normal file
175
cs320-clp/src/amyc/analyzer/TypeChecker.scala
Normal file
@@ -0,0 +1,175 @@
|
||||
package amyc
|
||||
package analyzer
|
||||
|
||||
import amyc.ast.Identifier
|
||||
import amyc.ast.SymbolicTreeModule._
|
||||
import amyc.utils._
|
||||
|
||||
// The type checker for Amy
|
||||
// Takes a symbolic program and rejects it if it does not follow the Amy typing rules.
|
||||
object TypeChecker extends Pipeline[(Program, SymbolTable), (Program, SymbolTable)] {
|
||||
|
||||
def run(ctx: Context)(v: (Program, SymbolTable)): (Program, SymbolTable) = {
|
||||
import ctx.reporter._
|
||||
|
||||
val (program, table) = v
|
||||
|
||||
case class Constraint(found: Type, expected: Type, pos: Position)
|
||||
|
||||
// Represents a type variable.
|
||||
// It extends Type, but it is meant only for internal type checker use,
|
||||
// since no Amy value can have such type.
|
||||
case class TypeVariable private (id: Int) extends Type
|
||||
object TypeVariable {
|
||||
private val c = new UniqueCounter[Unit]
|
||||
def fresh(): TypeVariable = TypeVariable(c.next(()))
|
||||
}
|
||||
|
||||
// Generates typing constraints for an expression `e` with a given expected type.
|
||||
// The environment `env` contains all currently available bindings (you will have to
|
||||
// extend these, e.g., to account for local variables).
|
||||
// Returns a list of constraints among types. These will later be solved via unification.
|
||||
def genConstraints(e: Expr, expected: Type)(implicit env: Map[Identifier, Type]): List[Constraint] = {
|
||||
|
||||
// This helper returns a list of a single constraint recording the type
|
||||
// that we found (or generated) for the current expression `e`
|
||||
def topLevelConstraint(found: Type): List[Constraint] =
|
||||
List(Constraint(found, expected, e.position))
|
||||
|
||||
e match {
|
||||
case IntLiteral(_) => topLevelConstraint(IntType)
|
||||
case BooleanLiteral(_) => topLevelConstraint(BooleanType)
|
||||
case StringLiteral(_) => topLevelConstraint(StringType)
|
||||
case UnitLiteral() => topLevelConstraint(UnitType)
|
||||
case Variable(name) => topLevelConstraint(env.getOrElse(name,UnitType))
|
||||
|
||||
case Plus(lhs, rhs) => topLevelConstraint(IntType) ++ genConstraints(lhs,IntType) ++ genConstraints(rhs,IntType)
|
||||
case Minus(lhs, rhs) => topLevelConstraint(IntType) ++ genConstraints(lhs,IntType) ++ genConstraints(rhs,IntType)
|
||||
case Times(lhs, rhs) => topLevelConstraint(IntType) ++ genConstraints(lhs,IntType) ++ genConstraints(rhs,IntType)
|
||||
case Div(lhs, rhs) => topLevelConstraint(IntType) ++ genConstraints(lhs,IntType) ++ genConstraints(rhs,IntType)
|
||||
case Mod(lhs, rhs) => topLevelConstraint(IntType) ++ genConstraints(lhs,IntType) ++ genConstraints(rhs,IntType)
|
||||
|
||||
case Neg(e) => topLevelConstraint(IntType) ++ genConstraints(e,IntType)
|
||||
case Not(e) => topLevelConstraint(BooleanType) ++ genConstraints(e,BooleanType)
|
||||
|
||||
case LessThan(lhs, rhs) => topLevelConstraint(BooleanType) ++ genConstraints(lhs,IntType) ++ genConstraints(rhs,IntType)
|
||||
case LessEquals(lhs, rhs) => topLevelConstraint(BooleanType) ++ genConstraints(lhs,IntType) ++ genConstraints(rhs,IntType)
|
||||
case Equals(lhs, rhs) =>
|
||||
val ltype = TypeVariable.fresh()
|
||||
topLevelConstraint(BooleanType) ++ genConstraints(lhs,ltype) ++ genConstraints(rhs,ltype)
|
||||
|
||||
case And(lhs, rhs) => topLevelConstraint(BooleanType) ++ genConstraints(lhs,BooleanType) ++ genConstraints(rhs,BooleanType)
|
||||
case Or(lhs, rhs) => topLevelConstraint(BooleanType) ++ genConstraints(lhs,BooleanType) ++ genConstraints(rhs,BooleanType)
|
||||
|
||||
case Concat(lhs, rhs) => topLevelConstraint(StringType) ++ genConstraints(lhs,StringType) ++ genConstraints(rhs,StringType)
|
||||
|
||||
case Error(msg) => topLevelConstraint(expected) ++ genConstraints(msg,StringType)
|
||||
|
||||
case Ite(cond, thenn, elze) =>
|
||||
topLevelConstraint(expected) ++ genConstraints(cond,BooleanType) ++ genConstraints(thenn,expected) ++ genConstraints(elze,expected)
|
||||
|
||||
case Sequence(e1, e2) =>
|
||||
topLevelConstraint(expected) ++ genConstraints(e1,TypeVariable.fresh()) ++ genConstraints(e2,expected)
|
||||
|
||||
case Let(df, value, body) =>
|
||||
topLevelConstraint(expected) ++ genConstraints(value,df.tt.tpe) ++ genConstraints(body, expected)(env + (df.name -> df.tt.tpe))
|
||||
|
||||
case Call(qname, args) =>
|
||||
val (sign, constr) = (table.getFunction(qname),table.getConstructor(qname)) match{
|
||||
case (Some(s), None) => (s, Constraint(s.retType, expected, e.position))
|
||||
case (None, Some(s)) => (s, Constraint(ClassType(s.parent), expected, e.position))
|
||||
case _ => throw new MatchError("Invalid Case")
|
||||
}
|
||||
(constr :: (args.zip(sign.argTypes)).flatMap(p => genConstraints(p._1, p._2)))
|
||||
|
||||
case Match(scrut, cases) =>
|
||||
// Returns additional constraints from within the pattern with all bindings
|
||||
// from identifiers to types for names bound in the pattern.
|
||||
// (This is analogous to `transformPattern` in NameAnalyzer.)
|
||||
def handlePattern(pat: Pattern, scrutExpected: Type):
|
||||
(List[Constraint], Map[Identifier, Type]) = pat match {
|
||||
case CaseClassPattern(constr, args) =>
|
||||
val constr_sig = table.getConstructor(constr).get
|
||||
val in_pat = args.zip(constr_sig.argTypes).map(p => handlePattern(p._1, p._2))
|
||||
(Constraint(ClassType(constr_sig.parent), scrutExpected, pat.position) ::
|
||||
in_pat.flatMap(_._1), in_pat.flatMap(_._2.toList).toMap)
|
||||
case IdPattern(name) => (List(), Map(name->scrutExpected))
|
||||
case LiteralPattern(lit) => (genConstraints(lit,scrutExpected), Map())
|
||||
case WildcardPattern() => (List(), Map())
|
||||
}
|
||||
|
||||
def handleCase(cse: MatchCase, scrutExpected: Type): List[Constraint] = {
|
||||
val (patConstraints, moreEnv) = handlePattern(cse.pat, scrutExpected)
|
||||
patConstraints ++ genConstraints(cse.expr, expected)(env ++ moreEnv)
|
||||
}
|
||||
|
||||
val st = TypeVariable.fresh()
|
||||
genConstraints(scrut, st) ++ cases.flatMap(cse => handleCase(cse, st))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Given a list of constraints `constraints`, replace every occurence of type variable
|
||||
// with id `from` by type `to`.
|
||||
def subst_*(constraints: List[Constraint], from: Int, to: Type): List[Constraint] = {
|
||||
// Do a single substitution.
|
||||
def subst(tpe: Type, from: Int, to: Type): Type = {
|
||||
tpe match {
|
||||
case TypeVariable(`from`) => to
|
||||
case other => other
|
||||
}
|
||||
}
|
||||
|
||||
constraints map { case Constraint(found, expected, pos) =>
|
||||
Constraint(subst(found, from, to), subst(expected, from, to), pos)
|
||||
}
|
||||
}
|
||||
|
||||
// Solve the given set of typing constraints and
|
||||
// call `typeError` if they are not satisfiable.
|
||||
// We consider a set of constraints to be satisfiable exactly if they unify.
|
||||
def solveConstraints(constraints: List[Constraint]): Unit = {
|
||||
constraints match {
|
||||
case Nil => ()
|
||||
case Constraint(found, expected, pos) :: more =>
|
||||
// HINT: You can use the `subst_*` helper above to replace a type variable
|
||||
// by another type in your current set of constraints.
|
||||
(found, expected) match {
|
||||
case (TypeVariable(id1), tpe@TypeVariable(id2)) =>
|
||||
if (id1 == id2) {
|
||||
solveConstraints(more)
|
||||
} else {
|
||||
solveConstraints(subst_*(constraints, id1, tpe))
|
||||
}
|
||||
case (tpe, TypeVariable(id)) =>
|
||||
solveConstraints(subst_*(constraints, id, tpe))
|
||||
case (tpe1, tpe2) =>
|
||||
if(tpe1 == tpe2){
|
||||
solveConstraints(more)
|
||||
}else{
|
||||
error(s"Error in TypeChecking, Types Not matching, found: ${found.toString}, expected ${expected.toString}.",pos)
|
||||
solveConstraints(more)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Putting it all together to type-check each module's functions and main expression.
|
||||
program.modules.foreach { mod =>
|
||||
// Put function parameters to the symbol table, then typecheck them against the return type
|
||||
mod.defs.collect { case FunDef(_, params, retType, body) =>
|
||||
val env = params.map{ case ParamDef(name, tt) => name -> tt.tpe }.toMap
|
||||
solveConstraints(genConstraints(body, retType.tpe)(env))
|
||||
}
|
||||
|
||||
// Type-check expression if present. We allow the result to be of an arbitrary type by
|
||||
// passing a fresh (and therefore unconstrained) type variable as the expected type.
|
||||
val tv = TypeVariable.fresh()
|
||||
mod.optExpr.foreach(e => solveConstraints(genConstraints(e, tv)(Map())))
|
||||
}
|
||||
|
||||
v
|
||||
|
||||
}
|
||||
}
|
19
cs320-clp/src/amyc/ast/Identifier.scala
Normal file
19
cs320-clp/src/amyc/ast/Identifier.scala
Normal file
@@ -0,0 +1,19 @@
|
||||
package amyc.ast
|
||||
|
||||
object Identifier {
|
||||
private val counter = new amyc.utils.UniqueCounter[String]
|
||||
|
||||
def fresh(name: String): Identifier = new Identifier(name)
|
||||
}
|
||||
|
||||
// Denotes a unique identifier in an Amy program
|
||||
// Notice that we rely on reference equality to compare Identifiers.
|
||||
// The numeric id will be generated lazily,
|
||||
// so the Identifiers are numbered in order when we print the program.
|
||||
final class Identifier private(val name: String) {
|
||||
private lazy val id = Identifier.counter.next(name)
|
||||
|
||||
def fullName = s"${name}_$id"
|
||||
|
||||
override def toString: String = name
|
||||
}
|
201
cs320-clp/src/amyc/ast/Printer.scala
Normal file
201
cs320-clp/src/amyc/ast/Printer.scala
Normal file
@@ -0,0 +1,201 @@
|
||||
package amyc.ast
|
||||
|
||||
import amyc.utils._
|
||||
|
||||
import scala.language.implicitConversions
|
||||
|
||||
// A printer for Amy trees
|
||||
trait Printer {
|
||||
|
||||
val treeModule: TreeModule
|
||||
import treeModule._
|
||||
|
||||
implicit def printName(name: Name)(implicit printUniqueIds: Boolean): Document
|
||||
implicit def printQName(name: QualifiedName)(implicit printUniqueIds: Boolean): Document
|
||||
|
||||
protected implicit def stringToDoc(s: String): Raw = Raw(s)
|
||||
|
||||
def apply(t: Tree)(implicit printUniqueIDs: Boolean = false): String = {
|
||||
|
||||
def binOp(e1: Expr, op: String, e2: Expr) = "(" <:> rec(e1) <:> " " + op + " " <:> rec(e2) <:> ")"
|
||||
|
||||
def rec(t: Tree, parens: Boolean = true): Document = t match {
|
||||
/* Definitions */
|
||||
case Program(modules) =>
|
||||
Stacked(modules map (rec(_)), emptyLines = true)
|
||||
|
||||
case ModuleDef(name, defs, optExpr) =>
|
||||
Stacked(
|
||||
"object " <:> name <:> " {",
|
||||
"",
|
||||
Indented(Stacked(defs ++ optExpr.toList map (rec(_, false)), emptyLines = true)),
|
||||
"}",
|
||||
""
|
||||
)
|
||||
|
||||
case AbstractClassDef(name) =>
|
||||
"abstract class " <:> printName(name)
|
||||
|
||||
case CaseClassDef(name, fields, parent) =>
|
||||
def printField(f: TypeTree) = "v: " <:> rec(f)
|
||||
"case class " <:> name <:> "(" <:> Lined(fields map printField, ", ") <:> ") extends " <:> parent
|
||||
|
||||
case FunDef(name, params, retType, body) =>
|
||||
Stacked(
|
||||
"def " <:> name <:> "(" <:> Lined(params map (rec(_)), ", ") <:> "): " <:> rec(retType) <:> " = {",
|
||||
Indented(rec(body, false)),
|
||||
"}"
|
||||
)
|
||||
|
||||
case ParamDef(name, tpe) =>
|
||||
name <:> ": " <:> rec(tpe)
|
||||
|
||||
/* Expressions */
|
||||
case Variable(name) =>
|
||||
name
|
||||
case IntLiteral(value) =>
|
||||
value.toString
|
||||
case BooleanLiteral(value) =>
|
||||
value.toString
|
||||
case StringLiteral(value) =>
|
||||
'"' + value + '"'
|
||||
case UnitLiteral() =>
|
||||
"()"
|
||||
case Plus(lhs, rhs) =>
|
||||
binOp(lhs, "+", rhs)
|
||||
case Minus(lhs, rhs) =>
|
||||
binOp(lhs, "-", rhs)
|
||||
case Times(lhs, rhs) =>
|
||||
binOp(lhs, "*", rhs)
|
||||
case Div(lhs, rhs) =>
|
||||
binOp(lhs, "/", rhs)
|
||||
case Mod(lhs, rhs) =>
|
||||
binOp(lhs, "%", rhs)
|
||||
case LessThan(lhs, rhs) =>
|
||||
binOp(lhs, "<", rhs)
|
||||
case LessEquals(lhs, rhs) =>
|
||||
binOp(lhs, "<=", rhs)
|
||||
case And(lhs, rhs) =>
|
||||
binOp(lhs, "&&", rhs)
|
||||
case Or(lhs, rhs) =>
|
||||
binOp(lhs, "||", rhs)
|
||||
case Equals(lhs, rhs) =>
|
||||
binOp(lhs, "==", rhs)
|
||||
case Concat(lhs, rhs) =>
|
||||
binOp(lhs, "++", rhs)
|
||||
case Not(e) =>
|
||||
"!(" <:> rec(e) <:> ")"
|
||||
case Neg(e) =>
|
||||
"-(" <:> rec(e) <:> ")"
|
||||
case Call(name, args) =>
|
||||
name <:> "(" <:> Lined(args map (rec(_)), ", ") <:> ")"
|
||||
case Sequence(lhs, rhs) =>
|
||||
val main = Stacked(
|
||||
rec(lhs, false) <:> ";",
|
||||
rec(rhs, false),
|
||||
)
|
||||
if (parens) {
|
||||
Stacked(
|
||||
"(",
|
||||
Indented(main),
|
||||
")"
|
||||
)
|
||||
} else {
|
||||
main
|
||||
}
|
||||
case Let(df, value, body) =>
|
||||
val main = Stacked(
|
||||
"val " <:> rec(df) <:> " =",
|
||||
Indented(rec(value)) <:> ";",
|
||||
rec(body, false) // For demonstration purposes, the scope or df is indented
|
||||
)
|
||||
if (parens) {
|
||||
Stacked(
|
||||
"(",
|
||||
Indented(main),
|
||||
")"
|
||||
)
|
||||
} else {
|
||||
main
|
||||
}
|
||||
case Ite(cond, thenn, elze) =>
|
||||
Stacked(
|
||||
"(if(" <:> rec(cond) <:> ") {",
|
||||
Indented(rec(thenn)),
|
||||
"} else {",
|
||||
Indented(rec(elze)),
|
||||
"})"
|
||||
)
|
||||
case Match(scrut, cases) =>
|
||||
Stacked(
|
||||
rec(scrut) <:> " match {",
|
||||
Indented(Stacked(cases map (rec(_)))),
|
||||
"}"
|
||||
)
|
||||
case Error(msg) =>
|
||||
"error(" <:> rec(msg) <:> ")"
|
||||
|
||||
/* cases and patterns */
|
||||
case MatchCase(pat, expr) =>
|
||||
Stacked(
|
||||
"case " <:> rec(pat) <:> " =>",
|
||||
Indented(rec(expr))
|
||||
)
|
||||
case WildcardPattern() =>
|
||||
"_"
|
||||
case IdPattern(name) =>
|
||||
name
|
||||
case LiteralPattern(lit) =>
|
||||
rec(lit)
|
||||
case CaseClassPattern(name, args) =>
|
||||
name <:> "(" <:> Lined(args map (rec(_)), ", ") <:> ")"
|
||||
|
||||
/* Types */
|
||||
case TypeTree(tp) =>
|
||||
tp match {
|
||||
case IntType => "Int"
|
||||
case BooleanType => "Boolean"
|
||||
case StringType => "String"
|
||||
case UnitType => "Unit"
|
||||
case ClassType(name) => name
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
rec(t).print
|
||||
}
|
||||
}
|
||||
|
||||
object NominalPrinter extends Printer {
|
||||
val treeModule = NominalTreeModule
|
||||
import NominalTreeModule._
|
||||
|
||||
implicit def printName(name: Name)(implicit printUniqueIds: Boolean): Document = Raw(name)
|
||||
|
||||
implicit def printQName(name: QualifiedName)(implicit printUniqueIds: Boolean): Document = {
|
||||
Raw(name match {
|
||||
case QualifiedName(Some(module), name) =>
|
||||
s"$module.$name"
|
||||
case QualifiedName(None, name) =>
|
||||
name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
object SymbolicPrinter extends SymbolicPrinter
|
||||
trait SymbolicPrinter extends Printer {
|
||||
val treeModule = SymbolicTreeModule
|
||||
import SymbolicTreeModule._
|
||||
|
||||
implicit def printName(name: Name)(implicit printUniqueIds: Boolean): Document = {
|
||||
if (printUniqueIds) {
|
||||
name.fullName
|
||||
} else {
|
||||
name.name
|
||||
}
|
||||
}
|
||||
|
||||
@inline implicit def printQName(name: QualifiedName)(implicit printUniqueIds: Boolean): Document = {
|
||||
printName(name)
|
||||
}
|
||||
}
|
141
cs320-clp/src/amyc/ast/TreeModule.scala
Normal file
141
cs320-clp/src/amyc/ast/TreeModule.scala
Normal file
@@ -0,0 +1,141 @@
|
||||
package amyc.ast
|
||||
|
||||
import amyc.utils.Positioned
|
||||
|
||||
/* A polymorphic module containing definitions of Amy trees.
|
||||
*
|
||||
* This trait represents either nominal trees (where names have not been resolved)
|
||||
* or symbolic trees (where names/qualified names) have been resolved to unique identifiers.
|
||||
* This is done by having two type fields within the module,
|
||||
* which will be instantiated differently by the two different modules.
|
||||
*
|
||||
*/
|
||||
|
||||
trait TreeModule {
|
||||
/* Represents the type for the name for this tree module.
|
||||
* (It will be either a plain string, or a unique symbol)
|
||||
*/
|
||||
type Name
|
||||
|
||||
// Represents a name within an module
|
||||
type QualifiedName
|
||||
|
||||
// A printer that knows how to print trees in this module.
|
||||
// The modules will instantiate it as appropriate
|
||||
val printer: Printer{ val treeModule: TreeModule.this.type }
|
||||
|
||||
// Common ancestor for all trees
|
||||
trait Tree extends Positioned {
|
||||
override def toString: String = printer(this)
|
||||
}
|
||||
|
||||
// Expressions
|
||||
trait Expr extends Tree
|
||||
|
||||
// Variables
|
||||
case class Variable(name: Name) extends Expr
|
||||
|
||||
// Literals
|
||||
trait Literal[+T] extends Expr { val value: T }
|
||||
case class IntLiteral(value: Int) extends Literal[Int]
|
||||
case class BooleanLiteral(value: Boolean) extends Literal[Boolean]
|
||||
case class StringLiteral(value: String) extends Literal[String]
|
||||
case class UnitLiteral() extends Literal[Unit] { val value: Unit = () }
|
||||
|
||||
// Binary operators
|
||||
case class Plus(lhs: Expr, rhs: Expr) extends Expr
|
||||
case class Minus(lhs: Expr, rhs: Expr) extends Expr
|
||||
case class Times(lhs: Expr, rhs: Expr) extends Expr
|
||||
case class Div(lhs: Expr, rhs: Expr) extends Expr
|
||||
case class Mod(lhs: Expr, rhs: Expr) extends Expr
|
||||
case class LessThan(lhs: Expr, rhs: Expr) extends Expr
|
||||
case class LessEquals(lhs: Expr, rhs: Expr) extends Expr
|
||||
case class And(lhs: Expr, rhs: Expr) extends Expr
|
||||
case class Or(lhs: Expr, rhs: Expr) extends Expr
|
||||
case class Equals(lhs: Expr, rhs: Expr) extends Expr
|
||||
case class Concat(lhs: Expr, rhs: Expr) extends Expr
|
||||
|
||||
// Unary operators
|
||||
case class Not(e: Expr) extends Expr
|
||||
case class Neg(e: Expr) extends Expr
|
||||
|
||||
// Function/ type constructor call
|
||||
case class Call(qname: QualifiedName, args: List[Expr]) extends Expr
|
||||
// The ; operator
|
||||
case class Sequence(e1: Expr, e2: Expr) extends Expr
|
||||
// Local variable definition
|
||||
case class Let(df: ParamDef, value: Expr, body: Expr) extends Expr
|
||||
// If-then-else
|
||||
case class Ite(cond: Expr, thenn: Expr, elze: Expr) extends Expr
|
||||
// Pattern matching
|
||||
case class Match(scrut: Expr, cases: List[MatchCase]) extends Expr {
|
||||
require(cases.nonEmpty)
|
||||
}
|
||||
// Represents a computational error; prints its message, then exits
|
||||
case class Error(msg: Expr) extends Expr
|
||||
|
||||
// Cases and patterns for Match expressions
|
||||
case class MatchCase(pat: Pattern, expr: Expr) extends Tree
|
||||
|
||||
abstract class Pattern extends Tree
|
||||
case class WildcardPattern() extends Pattern // _
|
||||
case class IdPattern(name: Name) extends Pattern // x
|
||||
case class LiteralPattern[+T](lit: Literal[T]) extends Pattern // 42, true
|
||||
case class CaseClassPattern(constr: QualifiedName, args: List[Pattern]) extends Pattern // C(arg1, arg2)
|
||||
|
||||
// Definitions
|
||||
trait Definition extends Tree { val name: Name }
|
||||
case class ModuleDef(name: Name, defs: List[ClassOrFunDef], optExpr: Option[Expr]) extends Definition
|
||||
trait ClassOrFunDef extends Definition
|
||||
case class FunDef(name: Name, params: List[ParamDef], retType: TypeTree, body: Expr) extends ClassOrFunDef {
|
||||
def paramNames = params.map(_.name)
|
||||
}
|
||||
case class AbstractClassDef(name: Name) extends ClassOrFunDef
|
||||
case class CaseClassDef(name: Name, fields: List[TypeTree], parent: Name) extends ClassOrFunDef
|
||||
case class ParamDef(name: Name, tt: TypeTree) extends Definition
|
||||
|
||||
// Types
|
||||
trait Type
|
||||
case object IntType extends Type {
|
||||
override def toString: String = "Int"
|
||||
}
|
||||
case object BooleanType extends Type {
|
||||
override def toString: String = "Boolean"
|
||||
}
|
||||
case object StringType extends Type {
|
||||
override def toString: String = "String"
|
||||
}
|
||||
case object UnitType extends Type {
|
||||
override def toString: String = "Unit"
|
||||
}
|
||||
case class ClassType(qname: QualifiedName) extends Type {
|
||||
override def toString: String = printer.printQName(qname)(false).print
|
||||
}
|
||||
|
||||
// A wrapper for types that is also a Tree (i.e. has a position)
|
||||
case class TypeTree(tpe: Type) extends Tree
|
||||
|
||||
// All is wrapped in a program
|
||||
case class Program(modules: List[ModuleDef]) extends Tree
|
||||
}
|
||||
|
||||
/* A module containing trees where the names have not been resolved.
|
||||
* Instantiates Name to String and QualifiedName to a pair of Strings
|
||||
* representing (module, name) (where module is optional)
|
||||
*/
|
||||
object NominalTreeModule extends TreeModule {
|
||||
type Name = String
|
||||
case class QualifiedName(module: Option[String], name: String) {
|
||||
override def toString: String = printer.printQName(this)(false).print
|
||||
}
|
||||
val printer = NominalPrinter
|
||||
}
|
||||
|
||||
/* A module containing trees where the names have been resolved to unique identifiers.
|
||||
* Both Name and ModuleName are instantiated to Identifier.
|
||||
*/
|
||||
object SymbolicTreeModule extends TreeModule {
|
||||
type Name = Identifier
|
||||
type QualifiedName = Identifier
|
||||
val printer = SymbolicPrinter
|
||||
}
|
152
cs320-clp/src/amyc/codegen/CodeGen.scala
Normal file
152
cs320-clp/src/amyc/codegen/CodeGen.scala
Normal file
@@ -0,0 +1,152 @@
|
||||
package amyc
|
||||
package codegen
|
||||
|
||||
import amyc.analyzer._
|
||||
import amyc.ast.Identifier
|
||||
import amyc.ast.SymbolicTreeModule.{And => AmyAnd, Call => AmyCall, Div => AmyDiv, Or => AmyOr, _}
|
||||
import amyc.codegen.Utils._
|
||||
import amyc.utils.{Context, Pipeline}
|
||||
import amyc.wasm.Instructions._
|
||||
import amyc.wasm._
|
||||
|
||||
// Generates WebAssembly code for an Amy program
|
||||
object CodeGen extends Pipeline[(Program, SymbolTable), Module] {
|
||||
def run(ctx: Context)(v: (Program, SymbolTable)): Module = {
|
||||
val (program, table) = v
|
||||
|
||||
// Generate code for an Amy module
|
||||
def cgModule(moduleDef: ModuleDef): List[Function] = {
|
||||
val ModuleDef(name, defs, optExpr) = moduleDef
|
||||
// Generate code for all functions
|
||||
defs.collect { case fd: FunDef if !builtInFunctions(fullName(name, fd.name)) =>
|
||||
cgFunction(fd, name, false)
|
||||
} ++
|
||||
// Generate code for the "main" function, which contains the module expression
|
||||
optExpr.toList.map { expr =>
|
||||
val mainFd = FunDef(Identifier.fresh("main"), Nil, TypeTree(IntType), expr)
|
||||
cgFunction(mainFd, name, true)
|
||||
}
|
||||
}
|
||||
|
||||
// Generate code for a function in module 'owner'
|
||||
def cgFunction(fd: FunDef, owner: Identifier, isMain: Boolean): Function = {
|
||||
// Note: We create the wasm function name from a combination of
|
||||
// module and function name, since we put everything in the same wasm module.
|
||||
val name = fullName(owner, fd.name)
|
||||
Function(name, fd.params.size, isMain){ lh =>
|
||||
val locals = fd.paramNames.zipWithIndex.toMap
|
||||
val body = cgExpr(fd.body)(locals, lh)
|
||||
if (isMain) {
|
||||
body <:> Drop // Main functions do not return a value,
|
||||
// so we need to drop the value generated by their body
|
||||
} else {
|
||||
body
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate code for an expression expr.
|
||||
// Additional arguments are a mapping from identifiers (parameters and variables) to
|
||||
// their index in the wasm local variables, and a LocalsHandler which will generate
|
||||
// fresh local slots as required.
|
||||
def cgExpr(expr: Expr)(implicit locals: Map[Identifier, Int], lh: LocalsHandler): Code = expr match {
|
||||
|
||||
case Let(df, value, body) =>
|
||||
val address = lh.getFreshLocal()
|
||||
cgExpr(value) <:> SetLocal(address) <:> cgExpr(body)(locals + (df.name -> address), lh)
|
||||
|
||||
case Variable(name) => GetLocal(locals(name))
|
||||
case Concat(lhs, rhs) => cgExpr(lhs) <:> cgExpr(rhs) <:> Call(concatImpl.name)
|
||||
case Sequence(expr1, expr2) => cgExpr(expr1) <:> Drop <:> cgExpr(expr2)
|
||||
case Ite(condition, thenBlock, elseBlock) => cgExpr(condition) <:> If_i32 <:> cgExpr(thenBlock) <:> Else <:> cgExpr(elseBlock) <:> End
|
||||
|
||||
case Plus(lhs, rhs) => cgExpr(lhs) <:> cgExpr(rhs) <:> Add
|
||||
case Minus(lhs, rhs) => cgExpr(lhs) <:> cgExpr(rhs) <:> Sub
|
||||
case Times(lhs, rhs) => cgExpr(lhs) <:> cgExpr(rhs) <:> Mul
|
||||
case AmyDiv(lhs, rhs) => cgExpr(lhs) <:> cgExpr(rhs) <:> Div
|
||||
case Mod(lhs, rhs) => cgExpr(lhs) <:> cgExpr(rhs) <:> Rem
|
||||
case Equals(lhs, rhs) => cgExpr(lhs) <:> cgExpr(rhs) <:> Eq
|
||||
case LessEquals(lhs, rhs) => cgExpr(lhs) <:> cgExpr(rhs) <:> Le_s
|
||||
case LessThan(lhs, rhs) => cgExpr(lhs) <:> cgExpr(rhs) <:> Lt_s
|
||||
case AmyAnd(lhs, rhs) => cgExpr(lhs) <:> If_i32 <:> cgExpr(rhs) <:> Else <:> Const(0) <:> End
|
||||
case AmyOr(lhs, rhs) => cgExpr(lhs) <:> If_i32 <:> Const(1) <:> Else <:> cgExpr(rhs) <:> End
|
||||
|
||||
case Neg(expr1) => Const(0) <:> cgExpr(expr1) <:> Sub
|
||||
case Not(expr1) => cgExpr(expr1) <:> Eqz
|
||||
|
||||
case IntLiteral(lit) => Const(lit)
|
||||
case StringLiteral(lit) => mkString(lit)
|
||||
case BooleanLiteral(lit) => if (lit) Const(1) else Const(0)
|
||||
case UnitLiteral() => Const(0)
|
||||
|
||||
case AmyCall(qname, args) => table.getConstructor(qname) match {
|
||||
case Some(sig) => {
|
||||
val newLocal = lh.getFreshLocal()
|
||||
|
||||
GetGlobal(memoryBoundary) <:> SetLocal(newLocal) <:>
|
||||
GetGlobal(memoryBoundary) <:> adtField(args.size) <:> SetGlobal(memoryBoundary) <:>
|
||||
GetLocal(newLocal) <:> Const(sig.index) <:> Store <:>
|
||||
args.indices.map(i => GetLocal(newLocal) <:> adtField(i) <:> cgExpr(args(i)) <:> Store).toList <:>
|
||||
GetLocal(newLocal)
|
||||
}
|
||||
case None => args.map(cgExpr) <:> Call(fullName(table.getFunction(qname).get.owner, qname))
|
||||
}
|
||||
|
||||
|
||||
case Match(scrut, cases) =>
|
||||
|
||||
def matchAndBind(pat: Pattern): (Code, Map[Identifier, Int]) = pat match {
|
||||
case WildcardPattern() => (Drop <:> Const(1), Map.empty)
|
||||
|
||||
case IdPattern(name) =>
|
||||
val idLocal = lh.getFreshLocal()
|
||||
(SetLocal(idLocal) <:> Const(1), Map.empty + (name -> idLocal))
|
||||
|
||||
case LiteralPattern(lit) => (cgExpr(lit) <:> Eq, Map.empty)
|
||||
|
||||
case CaseClassPattern(constr, args) => {
|
||||
|
||||
|
||||
val caseLocal = lh.getFreshLocal()
|
||||
val argsLocalnCode = args.zipWithIndex.map(v => {
|
||||
val mab = matchAndBind(v._1)
|
||||
(GetLocal(caseLocal) <:> adtField(v._2) <:> Load <:> mab._1, mab._2)
|
||||
})
|
||||
|
||||
val argsCode: Code = {
|
||||
if (args.isEmpty) Const(1)
|
||||
else if (args.lengthCompare(1) == 0) argsLocalnCode.map( _._1)
|
||||
else argsLocalnCode.map( _._1) <:> args.tail.map(_ => And)
|
||||
}
|
||||
|
||||
val idx = table.getConstructor(constr).get.index
|
||||
|
||||
(SetLocal(caseLocal) <:> GetLocal(caseLocal) <:> Load <:> Const(idx) <:> Eq <:>
|
||||
If_i32 <:> argsCode <:> Else <:> Const(0) <:> End,
|
||||
argsLocalnCode.map(_._2).foldLeft(Map.empty[Identifier, Int])(_ ++ _))
|
||||
|
||||
}
|
||||
case _ => (Unreachable, Map.empty)
|
||||
}
|
||||
|
||||
val newLocal = lh.getFreshLocal()
|
||||
val caseCodes = cases.map(cse => {
|
||||
val mnb = matchAndBind(cse.pat)
|
||||
GetLocal(newLocal) <:> mnb._1 <:> If_i32 <:> cgExpr(cse.expr)(locals ++ mnb._2, lh) <:> Else
|
||||
})
|
||||
|
||||
(cgExpr(scrut) <:> SetLocal(newLocal) <:> caseCodes <:> mkString("Match error!") <:> Call("Std_printString") <:> Unreachable <:> cases.map(_ => End))
|
||||
|
||||
case Error(msg) => cgExpr(StringLiteral("Error: ")) <:> cgExpr(msg) <:> Call(concatImpl.name) <:> Call("Std_printString") <:> Unreachable
|
||||
}
|
||||
|
||||
|
||||
Module(
|
||||
program.modules.last.name.name,
|
||||
defaultImports,
|
||||
globalsNo,
|
||||
wasmFunctions ++ (program.modules flatMap cgModule)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
56
cs320-clp/src/amyc/codegen/CodePrinter.scala
Normal file
56
cs320-clp/src/amyc/codegen/CodePrinter.scala
Normal file
@@ -0,0 +1,56 @@
|
||||
package amyc.codegen
|
||||
|
||||
import java.io._
|
||||
|
||||
import amyc.utils.{Context, Env, Pipeline}
|
||||
import amyc.wasm.Module
|
||||
|
||||
import scala.sys.process._
|
||||
|
||||
// Prints all 4 different files from a wasm Module
|
||||
object CodePrinter extends Pipeline[Module, Unit]{
|
||||
def run(ctx: Context)(m: Module) = {
|
||||
val outDirName = "wasmout"
|
||||
|
||||
def withExt(ext: String) = s"$outDirName/${m.name}.$ext"
|
||||
|
||||
val (local, inPath) = {
|
||||
import Env._
|
||||
os match {
|
||||
case Linux => ("./bin/wat2wasm", "wat2wasm")
|
||||
case Windows => ("./bin/wat2wasm.exe", "wat2wasm.exe")
|
||||
case Mac => ("./bin/mac/wat2wasm", "wat2wasm")
|
||||
}
|
||||
}
|
||||
|
||||
val w2wOptions = s"${withExt("wat")} -o ${withExt("wasm")}"
|
||||
|
||||
val outDir = new File(outDirName)
|
||||
if (!outDir.exists()) {
|
||||
outDir.mkdir()
|
||||
}
|
||||
|
||||
m.writeWasmText(withExt("wat"))
|
||||
|
||||
try {
|
||||
try {
|
||||
s"$local $w2wOptions".!!
|
||||
} catch {
|
||||
case _: IOException =>
|
||||
s"$inPath $w2wOptions".!!
|
||||
}
|
||||
} catch {
|
||||
case _: IOException =>
|
||||
ctx.reporter.fatal(
|
||||
"wat2wasm utility was not found under ./bin or in system path, " +
|
||||
"or did not have permission to execute"
|
||||
)
|
||||
case _: RuntimeException =>
|
||||
ctx.reporter.fatal(s"wat2wasm failed to translate WebAssembly text file ${withExt("wat")} to binary")
|
||||
}
|
||||
|
||||
m.writeHtmlWrapper(withExt("html"), withExt("wasm"))
|
||||
m.writeNodejsWrapper(withExt("js"), withExt("wasm"))
|
||||
|
||||
}
|
||||
}
|
158
cs320-clp/src/amyc/codegen/Utils.scala
Normal file
158
cs320-clp/src/amyc/codegen/Utils.scala
Normal file
@@ -0,0 +1,158 @@
|
||||
package amyc
|
||||
package codegen
|
||||
|
||||
import amyc.ast.Identifier
|
||||
import amyc.wasm.Function
|
||||
import amyc.wasm.Instructions._
|
||||
|
||||
// Utilities for CodeGen
|
||||
object Utils {
|
||||
|
||||
// The index of the global variable that represents the free memory boundary
|
||||
val memoryBoundary: Int = 0
|
||||
// # of global variables
|
||||
val globalsNo = 1
|
||||
|
||||
// The default imports we will pass to a wasm Module
|
||||
val defaultImports: List[String] = List(
|
||||
"\"system\" \"printInt\" (func $Std_printInt (param i32) (result i32))",
|
||||
"\"system\" \"printString\" (func $Std_printString (param i32) (result i32))",
|
||||
"\"system\" \"readString0\" (func $js_readString0 (param i32) (result i32))",
|
||||
"\"system\" \"readInt\" (func $Std_readInt (result i32))",
|
||||
"\"system\" \"mem\" (memory 100)"
|
||||
)
|
||||
|
||||
// We don't generate code for these functions in CodeGen (they are hard-coded here or in js wrapper)
|
||||
val builtInFunctions: Set[String] = Set(
|
||||
"Std_printInt",
|
||||
"Std_printString",
|
||||
"Std_digitToString",
|
||||
"Std_readInt",
|
||||
"Std_readString"
|
||||
)
|
||||
|
||||
/** Utilities */
|
||||
// A globally unique name for definitions
|
||||
def fullName(owner: Identifier, df: Identifier): String = owner.name + "_" + df.name
|
||||
|
||||
// Given a pointer to an ADT on the top of the stack,
|
||||
// will point at its field in index (and consume the ADT).
|
||||
// 'index' MUST be 0-based.
|
||||
def adtField(index: Int): Code = {
|
||||
Const(4* (index + 1)) <:> Add
|
||||
}
|
||||
|
||||
// Increment a local variable
|
||||
def incr(local: Int): Code = {
|
||||
GetLocal(local) <:> Const(1) <:> Add <:> SetLocal(local)
|
||||
}
|
||||
|
||||
// A fresh label name
|
||||
def getFreshLabel(name: String = "label") = {
|
||||
Identifier.fresh(name).fullName
|
||||
}
|
||||
|
||||
// Creates a known string constant s in memory
|
||||
def mkString(s: String): Code = {
|
||||
val size = s.length
|
||||
val padding = 4 - size % 4
|
||||
|
||||
val completeS = s + 0.toChar.toString * padding
|
||||
|
||||
val setChars = for ((c, ind) <- completeS.zipWithIndex.toList) yield {
|
||||
GetGlobal(memoryBoundary) <:> Const(ind) <:> Add <:>
|
||||
Const(c.toInt) <:> Store8
|
||||
}
|
||||
|
||||
val setMemory =
|
||||
GetGlobal(memoryBoundary) <:> GetGlobal(memoryBoundary) <:> Const(size + padding) <:> Add <:>
|
||||
SetGlobal(memoryBoundary)
|
||||
|
||||
setChars <:> setMemory
|
||||
}
|
||||
|
||||
// Built-in implementation of concatenation
|
||||
val concatImpl: Function = {
|
||||
Function("String_concat", 2, false) { lh =>
|
||||
val ptrS = lh.getFreshLocal()
|
||||
val ptrD = lh.getFreshLocal()
|
||||
val label = getFreshLabel()
|
||||
|
||||
def mkLoop: Code = {
|
||||
val label = getFreshLabel()
|
||||
Loop(label) <:>
|
||||
// Load current character
|
||||
GetLocal(ptrS) <:> Load8_u <:>
|
||||
// If != 0
|
||||
If_void <:>
|
||||
// Copy to destination
|
||||
GetLocal(ptrD) <:>
|
||||
GetLocal(ptrS) <:> Load8_u <:>
|
||||
Store8 <:>
|
||||
// Increment pointers
|
||||
incr(ptrD) <:> incr(ptrS) <:>
|
||||
// Jump to loop
|
||||
Br(label) <:>
|
||||
Else <:>
|
||||
End <:>
|
||||
End
|
||||
}
|
||||
|
||||
// Instantiate ptrD to previous memory, ptrS to first string
|
||||
GetGlobal(memoryBoundary) <:>
|
||||
SetLocal(ptrD) <:>
|
||||
GetLocal(0) <:>
|
||||
SetLocal(ptrS) <:>
|
||||
// Copy first string
|
||||
mkLoop <:>
|
||||
// Set ptrS to second string
|
||||
GetLocal(1) <:>
|
||||
SetLocal(ptrS) <:>
|
||||
// Copy second string
|
||||
mkLoop <:>
|
||||
//
|
||||
// Pad with zeros until multiple of 4
|
||||
//
|
||||
Loop(label) <:>
|
||||
// Write 0
|
||||
GetLocal(ptrD) <:> Const(0) <:> Store8 <:>
|
||||
// Check if multiple of 4
|
||||
GetLocal(ptrD) <:> Const(4) <:> Rem <:>
|
||||
// If not
|
||||
If_void <:>
|
||||
// Increment pointer and go back
|
||||
incr(ptrD) <:>
|
||||
Br(label) <:>
|
||||
Else <:>
|
||||
End <:>
|
||||
End <:>
|
||||
//
|
||||
// Put string pointer to stack, set new memory boundary and return
|
||||
GetGlobal(memoryBoundary) <:> GetLocal(ptrD) <:> Const(1) <:> Add <:> SetGlobal(memoryBoundary)
|
||||
}
|
||||
}
|
||||
|
||||
val digitToStringImpl: Function = {
|
||||
Function("Std_digitToString", 1, false) { lh =>
|
||||
// We know we have to create a string of total size 4 (digit code + padding), so we do it all together
|
||||
// We do not need to shift the digit due to little endian structure!
|
||||
GetGlobal(memoryBoundary) <:> GetLocal(0) <:> Const('0'.toInt) <:> Add <:> Store <:>
|
||||
// Load memory boundary to stack, then move it by 4
|
||||
GetGlobal(memoryBoundary) <:>
|
||||
GetGlobal(memoryBoundary) <:> Const(4) <:> Add <:> SetGlobal(memoryBoundary)
|
||||
}
|
||||
}
|
||||
|
||||
val readStringImpl: Function = {
|
||||
Function("Std_readString", 0, false) { lh =>
|
||||
// We need to use the weird interface of javascript read string:
|
||||
// we pass the old memory boundary and get the new one.
|
||||
// In the end we have to return the old, where the fresh string lies.
|
||||
GetGlobal(memoryBoundary) <:> GetGlobal(memoryBoundary) <:> Call("js_readString0") <:>
|
||||
SetGlobal(memoryBoundary)
|
||||
}
|
||||
}
|
||||
|
||||
val wasmFunctions = List(concatImpl, digitToStringImpl, readStringImpl)
|
||||
|
||||
}
|
61
cs320-clp/src/amyc/languageserver/AmyServer.scala
Normal file
61
cs320-clp/src/amyc/languageserver/AmyServer.scala
Normal file
@@ -0,0 +1,61 @@
|
||||
package amyc.languageserver
|
||||
|
||||
import java.util
|
||||
|
||||
import org.eclipse.lsp4j.{CompletionOptions, InitializeParams, InitializeResult, MessageParams, MessageType, ServerCapabilities, TextDocumentSyncKind}
|
||||
import org.eclipse.lsp4j.services.{LanguageClient, LanguageClientAware, LanguageServer, TextDocumentService, WorkspaceService}
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class AmyServer extends LanguageServer with LanguageClientAware {
|
||||
|
||||
private val textDocumentService: TextDocumentService = new AmyTextDocumentService()
|
||||
private val workspaceService: WorkspaceService = new AmyWorkspaceService()
|
||||
private var client : LanguageClient = _
|
||||
private var errorCode = 1
|
||||
|
||||
override def initialize(initializeParams: InitializeParams): CompletableFuture[InitializeResult] = {
|
||||
|
||||
sendLogMessageNotification(MessageType.Info, "Initializing the server...")
|
||||
val initializeResult = new InitializeResult
|
||||
val capabilities = new ServerCapabilities
|
||||
|
||||
capabilities.setTextDocumentSync(TextDocumentSyncKind.Full)
|
||||
capabilities.setCompletionProvider(new CompletionOptions(true, util.Arrays.asList(".","?","&", "\"", "=")));
|
||||
capabilities.setHoverProvider(false)
|
||||
capabilities.setDefinitionProvider(true)
|
||||
|
||||
initializeResult.setCapabilities(capabilities)
|
||||
CompletableFuture.completedFuture(initializeResult)
|
||||
}
|
||||
|
||||
override def shutdown: CompletableFuture[AnyRef] = {
|
||||
errorCode = 0
|
||||
CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
override def exit(): Unit = {
|
||||
System.exit(0)
|
||||
}
|
||||
|
||||
override def getTextDocumentService: TextDocumentService = {
|
||||
this.textDocumentService
|
||||
}
|
||||
|
||||
override def getWorkspaceService: WorkspaceService = {
|
||||
this.workspaceService
|
||||
}
|
||||
|
||||
override def connect(client: LanguageClient): Unit = {
|
||||
this.client = client
|
||||
sendLogMessageNotification(MessageType.Info, "Connected to Language Server...")
|
||||
|
||||
}
|
||||
|
||||
def sendLogMessageNotification(tpe: MessageType, msg: String): Unit = {
|
||||
client.logMessage(new MessageParams(tpe, msg))
|
||||
}
|
||||
|
||||
def sendShowMessageNotification(tpe: MessageType, msg: String): Unit = {
|
||||
client.showMessage(new MessageParams(tpe, msg))
|
||||
}
|
||||
}
|
172
cs320-clp/src/amyc/languageserver/AmyTextDocumentService.scala
Normal file
172
cs320-clp/src/amyc/languageserver/AmyTextDocumentService.scala
Normal file
@@ -0,0 +1,172 @@
|
||||
package amyc.languageserver
|
||||
|
||||
import java.io.File
|
||||
|
||||
import org.eclipse.lsp4j._
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either
|
||||
import org.eclipse.lsp4j.TextDocumentItem
|
||||
import org.eclipse.lsp4j.services.TextDocumentService
|
||||
import org.eclipse.lsp4j.Hover
|
||||
import java.util
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
import amyc.analyzer.{NameAnalyzer, TypeChecker}
|
||||
import amyc.ast.SymbolicTreeModule
|
||||
import amyc.parsing.{Lexer, Parser}
|
||||
import amyc.utils.{Context, Reporter, SourcePosition}
|
||||
|
||||
|
||||
class AmyTextDocumentService extends TextDocumentService {
|
||||
/*
|
||||
|
||||
*/
|
||||
|
||||
private val openedDocuments : util.Map[String, TextDocumentItem] = new util.HashMap[String, TextDocumentItem]()
|
||||
|
||||
|
||||
private def parseFile(file: Array[String]): Context = {
|
||||
Context(new Reporter, file.toList)
|
||||
}
|
||||
|
||||
override def completion(params: CompletionParams): CompletableFuture[Either[util.List[CompletionItem], CompletionList]] = {
|
||||
// Provide completion item.
|
||||
|
||||
def AmyCompletionCompute(item: TextDocumentItem, pos: SourcePosition): util.List[CompletionItem] = {
|
||||
|
||||
val completionItems = new util.ArrayList[CompletionItem]
|
||||
|
||||
val completionItemPrintString = new CompletionItem
|
||||
completionItemPrintString.setInsertText("Std.printString(\"\")")
|
||||
completionItemPrintString.setLabel("Std.printString()")
|
||||
completionItemPrintString.setKind(CompletionItemKind.Snippet)
|
||||
completionItemPrintString.setDetail("Template to Print a String")
|
||||
completionItems.add(completionItemPrintString)
|
||||
|
||||
val completionItemPrintInt = new CompletionItem
|
||||
completionItemPrintInt.setInsertText("Std.printInt(\"\")")
|
||||
completionItemPrintInt.setLabel("Std.printInt()")
|
||||
completionItemPrintInt.setKind(CompletionItemKind.Snippet)
|
||||
completionItemPrintInt.setDetail("Template to Print an Integer")
|
||||
completionItems.add(completionItemPrintInt)
|
||||
|
||||
val pipeline = Lexer andThen Parser andThen NameAnalyzer //andThen TypeChecker
|
||||
val ctx = parseFile(Array("./library/List.scala","./library/Option.scala", "./library/Std.scala",item.getUri))
|
||||
|
||||
val files = ctx.files.map(new File(_))
|
||||
pipeline.run(ctx)(files)._1.modules.foreach(_.defs.foreach {
|
||||
case SymbolicTreeModule.AbstractClassDef(name) =>
|
||||
case SymbolicTreeModule.CaseClassDef(name, fields, parent) =>
|
||||
|
||||
case SymbolicTreeModule.FunDef(name, params, retType, body) =>
|
||||
|
||||
val completionItemPrintModule = new CompletionItem
|
||||
if(params.nonEmpty) {
|
||||
completionItemPrintModule.setInsertText(name.name + "(" +params.map(_.name.name).reduceLeft((s,p) => s + ", "+ p) + ")")
|
||||
}
|
||||
else {
|
||||
completionItemPrintModule.setInsertText(name.name + "()")
|
||||
}
|
||||
completionItemPrintModule.setLabel(name.name)
|
||||
completionItemPrintModule.setKind(CompletionItemKind.Snippet)
|
||||
completionItemPrintModule.setDetail("Filled out call to " +name.name)
|
||||
completionItems.add(completionItemPrintModule)
|
||||
})
|
||||
|
||||
completionItems
|
||||
}
|
||||
|
||||
CompletableFuture.supplyAsync(() => {
|
||||
val textDocumentItem = openedDocuments.get(params.getTextDocument.getUri)
|
||||
Either.forLeft(AmyCompletionCompute(textDocumentItem,
|
||||
SourcePosition(null,
|
||||
params.getPosition.getLine,
|
||||
params.getPosition.getCharacter)))
|
||||
})
|
||||
}
|
||||
|
||||
override def resolveCompletionItem(item: CompletionItem): CompletableFuture[CompletionItem] = {
|
||||
//Not Implemented
|
||||
CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
override def hover(params: TextDocumentPositionParams): CompletableFuture[Hover] = {
|
||||
//Not Implemented
|
||||
CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
override def signatureHelp(params: TextDocumentPositionParams): CompletableFuture[SignatureHelp] = {
|
||||
//Not Implemented
|
||||
CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
override def definition(params: TextDocumentPositionParams): CompletableFuture[Either[util.List[_ <:Location],util.List[_<:LocationLink]]] = {
|
||||
//Not Implemented
|
||||
CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
override def references(params: ReferenceParams): CompletableFuture[util.List[_ <:Location]] = {
|
||||
//Not Implemented
|
||||
CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
override def documentHighlight(params: TextDocumentPositionParams): CompletableFuture[util.List[_ <:DocumentHighlight]] = {
|
||||
//Not Implemented
|
||||
CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
override def documentSymbol(params: DocumentSymbolParams): CompletableFuture[util.List[Either[SymbolInformation,DocumentSymbol]]] = {
|
||||
//Not Implemented
|
||||
CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
override def codeAction(params: CodeActionParams): CompletableFuture[util.List[Either[Command, CodeAction]]] = {
|
||||
//Not Implemented
|
||||
CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
override def codeLens(params: CodeLensParams): CompletableFuture[util.List[_ <:CodeLens]] = {
|
||||
//Not Implemented
|
||||
CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
override def resolveCodeLens(codeLens: CodeLens): CompletableFuture[CodeLens] = {
|
||||
//Not Implemented
|
||||
CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
override def formatting(params: DocumentFormattingParams): CompletableFuture[util.List[_ <:TextEdit]] = {
|
||||
//Not Implemented
|
||||
CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
override def rangeFormatting(documentRangeFormattingParams: DocumentRangeFormattingParams): CompletableFuture[util.List[_ <:TextEdit]] = {
|
||||
//Not Implemented
|
||||
CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
override def onTypeFormatting(documentOnTypeFormattingParams: DocumentOnTypeFormattingParams): CompletableFuture[util.List[_ <:TextEdit]] = {
|
||||
//Not Implemented
|
||||
CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
override def rename(renameParams: RenameParams): CompletableFuture[WorkspaceEdit] = {
|
||||
//Not Implemented
|
||||
CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
override def didOpen(didOpenTextDocumentParams: DidOpenTextDocumentParams): Unit = {
|
||||
//Not Implemented
|
||||
}
|
||||
|
||||
override def didChange(didChangeTextDocumentParams: DidChangeTextDocumentParams): Unit = {
|
||||
//Not Implemented
|
||||
}
|
||||
|
||||
override def didClose(didCloseTextDocumentParams: DidCloseTextDocumentParams): Unit = {
|
||||
//Not Implemented
|
||||
}
|
||||
|
||||
override def didSave(didSaveTextDocumentParams: DidSaveTextDocumentParams): Unit = {
|
||||
//Not Implemented
|
||||
}
|
||||
}
|
25
cs320-clp/src/amyc/languageserver/AmyWorkspaceService.scala
Normal file
25
cs320-clp/src/amyc/languageserver/AmyWorkspaceService.scala
Normal file
@@ -0,0 +1,25 @@
|
||||
package amyc.languageserver
|
||||
|
||||
import java.util
|
||||
|
||||
import org.eclipse.lsp4j.{DidChangeConfigurationParams, DidChangeWatchedFilesParams, FileEvent, SymbolInformation, WorkspaceSymbolParams}
|
||||
import org.eclipse.lsp4j.services.WorkspaceService
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
|
||||
class AmyWorkspaceService extends WorkspaceService {
|
||||
override def symbol(workspaceSymbolParams: WorkspaceSymbolParams): CompletableFuture[util.List[_ <:SymbolInformation]] = {
|
||||
//Not Implemented
|
||||
CompletableFuture.completedFuture(new util.ArrayList[SymbolInformation]())
|
||||
}
|
||||
|
||||
override def didChangeConfiguration(params: DidChangeConfigurationParams): Unit = {
|
||||
//Not Implemented
|
||||
val settings: Any = params.getSettings
|
||||
}
|
||||
|
||||
override def didChangeWatchedFiles(params: DidChangeWatchedFilesParams): Unit = {
|
||||
//Not Implemented
|
||||
val settings : util.List[FileEvent] = params.getChanges
|
||||
}
|
||||
}
|
24
cs320-clp/src/amyc/languageserver/LSLauncher.scala
Normal file
24
cs320-clp/src/amyc/languageserver/LSLauncher.scala
Normal file
@@ -0,0 +1,24 @@
|
||||
package amyc.languageserver
|
||||
|
||||
import java.net.Socket
|
||||
import java.util.logging.{Level, LogManager, Logger}
|
||||
|
||||
import org.eclipse.lsp4j.launch.LSPLauncher
|
||||
|
||||
|
||||
class LSLauncher {
|
||||
def main(args: Array[String]): Unit = {
|
||||
LogManager.getLogManager.reset()
|
||||
val globalLogger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME)
|
||||
globalLogger.setLevel(Level.OFF)
|
||||
startLS()
|
||||
}
|
||||
|
||||
private def startLS(): Unit = {
|
||||
val ls = new AmyServer()
|
||||
val launcher = LSPLauncher.createServerLauncher(ls, System.in, System.out)
|
||||
ls.connect(launcher.getRemoteProxy)
|
||||
|
||||
launcher.startListening
|
||||
}
|
||||
}
|
158
cs320-clp/src/amyc/parsing/Lexer.scala
Normal file
158
cs320-clp/src/amyc/parsing/Lexer.scala
Normal file
@@ -0,0 +1,158 @@
|
||||
package amyc
|
||||
package parsing
|
||||
|
||||
import java.io.File
|
||||
|
||||
import amyc.utils._
|
||||
import scallion.input._
|
||||
import scallion.lexical._
|
||||
|
||||
// The lexer for Amy.
|
||||
object Lexer extends Pipeline[List[File], Iterator[Token]]
|
||||
with Lexers[Token, Char, SourcePosition] {
|
||||
|
||||
/** Tiny Scallion-lexer reference:
|
||||
* ==============================
|
||||
* Scallion's lexer essentially allows you to define a list of regular expressions
|
||||
* in their order of priority. To tokenize a given input stream of characters, each
|
||||
* individual regular expression is applied in turn. If a given expression matches, it
|
||||
* is used to produce a token of maximal length. Whenever a regular expression does not
|
||||
* match, the expression of next-highest priority is tried.
|
||||
* The result is a stream of tokens.
|
||||
*
|
||||
* Regular expressions `r` can be built using the following operators:
|
||||
* - `word("abc")` matches the sequence "abc" exactly
|
||||
* - `r1 | r2` matches either expression `r1` or expression `r2`
|
||||
* - `r1 ~ r2` matches `r1` followed by `r2`
|
||||
* - `oneof("xy")` matches either "x" or "y"
|
||||
* (i.e., it is a shorthand of `word` and `|` for single characters)
|
||||
* - `elem(c)` matches character `c`
|
||||
* - `elem(f)` matches any character for which the boolean predicate `f` holds
|
||||
* - `opt(r)` matches `r` or nothing at all
|
||||
* - `many(r)` matches any number of repetitions of `r` (including none at all)
|
||||
* - `many1(r)` matches any non-zero number of repetitions of `r`
|
||||
*
|
||||
* To define the token that should be output for a given expression, one can use
|
||||
* the `|>` combinator with an expression on the left-hand side and a function
|
||||
* producing the token on the right. The function is given the sequence of matched
|
||||
* characters and the source-position range as arguments.
|
||||
*
|
||||
* For instance,
|
||||
*
|
||||
* `elem(_.isDigit) ~ word("kg") |> {
|
||||
* (cs, range) => WeightLiteralToken(cs.mkString).setPos(range._1)) }`
|
||||
*
|
||||
* will match a single digit followed by the characters "kg" and turn them into a
|
||||
* "WeightLiteralToken" whose value will be the full string matched (e.g. "1kg").
|
||||
*/
|
||||
|
||||
|
||||
import Tokens._
|
||||
|
||||
val lexer = Lexer(
|
||||
// Keywords
|
||||
word("abstract") | word("case") | word("class") |
|
||||
word("def") | word("else") | word("extends") |
|
||||
word("if") | word("match") | word("object") |
|
||||
word("val") | word("error") | word("_")
|
||||
|> { (cs, range) => KeywordToken(cs.mkString).setPos(range._1) },
|
||||
|
||||
//Type names
|
||||
word("Int") | word("String") | word("Boolean") | word("Unit")
|
||||
|> { (cs,range) => PrimTypeToken(cs.mkString).setPos(range._1) } ,
|
||||
|
||||
//Boolean literals
|
||||
word("true")
|
||||
|> { (cs,range)=> BoolLitToken(true).setPos(range._1) } ,
|
||||
word("false")
|
||||
|> { (cs,range)=> BoolLitToken(false).setPos(range._1) } ,
|
||||
|
||||
// Operators
|
||||
oneOf("+-*/%!") |
|
||||
word("||") | word("&&") |
|
||||
word("<=") | word("<") |
|
||||
word("==") | word("++")
|
||||
|> { (cs,range )=> OperatorToken(cs.mkString).setPos(range._1) } ,
|
||||
|
||||
//Identifiers
|
||||
elem(_.isLetter) ~ many(elem(c => { c.isLetter || c.isDigit || c == '_' }))
|
||||
|> { (cs,range) => IdentifierToken(cs.mkString).setPos(range._1) } ,
|
||||
|
||||
|
||||
//Integer literals
|
||||
many(elem(_.isDigit))
|
||||
|> { (cs,range)=> {
|
||||
try{
|
||||
IntLitToken(cs.mkString.toInt).setPos(range._1)
|
||||
}catch{
|
||||
case e: Exception => ErrorToken("Integer Error : "+e.toString+" at "+range._1).setPos(range._1)
|
||||
}
|
||||
}
|
||||
},
|
||||
// NOTE: Make sure to handle invalid (e.g. overflowing) integer values safely by
|
||||
// emitting an ErrorToken instead.
|
||||
|
||||
// String literals
|
||||
elem('\"') ~ many(elem((c)=> {c != '"' && c != '\n' && c!= '\r'} )) ~ elem('\"')
|
||||
|> { (cs,range) => StringLitToken(cs.mkString.slice(1,cs.length -1 )).setPos(range._1) },
|
||||
|
||||
// NOTE: Not certain I should make an errorToken in case of unclosed strings...
|
||||
// elem('"') ~ many(elem((c)=> {c != '"' && c != '\n' || c!= '\r'} ))
|
||||
// |> {(cs,range) => ErrorToken("Unclosed String Error at "+range._1).setPos(range._1) },
|
||||
|
||||
|
||||
// Delimiters and whitespace
|
||||
word("=>") | oneOf(".,:;(){}[]=")
|
||||
|> { (cs,range) => DelimiterToken(cs.mkString).setPos(range._1) },
|
||||
|
||||
elem(c => { c.isWhitespace })
|
||||
|> { (cs,range) => SpaceToken().setPos(range._1) },
|
||||
|
||||
// Single line comments
|
||||
word("//") ~ many(elem(_ != '\n'))
|
||||
|> { cs => CommentToken(cs.mkString("")) },
|
||||
|
||||
// Multiline comments
|
||||
word("/*") ~ many(elem(_ != '*') | elem('*')~elem(_ != '/') ) ~ word("*/")
|
||||
|> { cs => CommentToken(cs.mkString("")) },
|
||||
// NOTE: Amy does not support nested multi-line comments (e.g. `/* foo /* bar */ */`).
|
||||
// Make sure that unclosed multi-line comments result in an ErrorToken.
|
||||
word("/*")
|
||||
|> { (cs,range) => ErrorToken("Unclosed Comment Error at "+range._1).setPos(range._1) },
|
||||
|
||||
) onError {
|
||||
// We also emit ErrorTokens for Scallion-handled errors.
|
||||
(cs, range) => ErrorToken(cs.mkString).setPos(range._1)
|
||||
} onEnd {
|
||||
// Once all the input has been consumed, we emit one EOFToken.
|
||||
pos => EOFToken().setPos(pos)
|
||||
}
|
||||
|
||||
override def run(ctx: Context)(files: List[File]): Iterator[Token] = {
|
||||
var it = Seq[Token]().toIterator
|
||||
|
||||
for (file <- files) {
|
||||
val source = Source.fromFile(file, SourcePositioner(file))
|
||||
it ++= lexer.spawn(source).filter {
|
||||
// Remove all whitespace and comment tokens
|
||||
(token) => token match{
|
||||
case CommentToken(_) => false
|
||||
case SpaceToken() => false
|
||||
case _ => true
|
||||
}
|
||||
|
||||
}.map {
|
||||
case token@ErrorToken(error) => ctx.reporter.fatal("Unknown token at " + token.position + ": " + error)
|
||||
case token => token
|
||||
}
|
||||
}
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
/** Extracts all tokens from input and displays them */
|
||||
object DisplayTokens extends Pipeline[Iterator[Token], Unit] {
|
||||
override def run(ctx: Context)(tokens: Iterator[Token]): Unit = {
|
||||
tokens.foreach(println(_))
|
||||
}
|
||||
}
|
274
cs320-clp/src/amyc/parsing/Parser.scala
Normal file
274
cs320-clp/src/amyc/parsing/Parser.scala
Normal file
@@ -0,0 +1,274 @@
|
||||
package amyc
|
||||
package parsing
|
||||
|
||||
import amyc.ast.NominalTreeModule._
|
||||
import amyc.parsing.TokenKinds._
|
||||
import amyc.parsing.Tokens._
|
||||
import amyc.utils._
|
||||
import scallion.syntactic._
|
||||
|
||||
import scala.language.implicitConversions
|
||||
|
||||
// The parser for Amy
|
||||
object Parser extends Pipeline[Iterator[Token], Program]
|
||||
with Syntaxes[Token, TokenKind] with Debug[Token, TokenKind]
|
||||
with Operators {
|
||||
import Implicits._
|
||||
|
||||
override def getKind(token: Token): TokenKind = TokenKind.of(token)
|
||||
|
||||
val eof: Syntax[Token] = elem(EOFKind)
|
||||
def op(string: String): Syntax[Token] = elem(OperatorKind(string))
|
||||
def kw(string: String): Syntax[Token] = elem(KeywordKind(string))
|
||||
|
||||
implicit def delimiter(string: String): Syntax[Token] = elem(DelimiterKind(string))
|
||||
|
||||
// An entire program (the starting rule for any Amy file).
|
||||
lazy val program: Syntax[Program] = many1(many1(module) ~<~ eof).map(ms => Program(ms.flatten.toList).setPos(ms.head.head))
|
||||
|
||||
// A module (i.e., a collection of definitions and an initializer expression)
|
||||
lazy val module: Syntax[ModuleDef] = (kw("object") ~ identifier ~ "{" ~ many(definition) ~ opt(expr) ~ "}").map {
|
||||
case obj ~ id ~ _ ~ defs ~ body ~ _ => ModuleDef(id, defs.toList, body).setPos(obj)
|
||||
}
|
||||
|
||||
// An identifier.
|
||||
val identifier: Syntax[String] = accept(IdentifierKind) {
|
||||
case IdentifierToken(name) => name
|
||||
}
|
||||
|
||||
// An identifier along with its position.
|
||||
val identifierPos: Syntax[(String, Position)] = accept(IdentifierKind) {
|
||||
case id@IdentifierToken(name) => (name, id.position)
|
||||
}
|
||||
|
||||
// A definition within a module.
|
||||
lazy val definition: Syntax[ClassOrFunDef] = fun_def | abstract_class_def | case_class_def
|
||||
|
||||
lazy val abstract_class_def : Syntax[ClassOrFunDef] = {
|
||||
(kw("abstract") ~ kw("class") ~ identifierPos).map{
|
||||
case v ~ _ ~ id => AbstractClassDef(id._1).setPos(v)
|
||||
}
|
||||
}
|
||||
|
||||
lazy val case_class_def : Syntax[ClassOrFunDef] = {
|
||||
(kw("case") ~ kw("class") ~ identifier ~ "(" ~ parameters ~ ")" ~ kw("extends") ~ identifier).map{
|
||||
case v ~ _ ~ id ~ _ ~ params ~ _ ~ _ ~ idp => CaseClassDef(id, params.map(_.tt), idp).setPos(v)
|
||||
}
|
||||
}
|
||||
|
||||
lazy val fun_def : Syntax[ClassOrFunDef]= {
|
||||
(kw("def") ~ identifier ~ "(" ~ parameters ~ ")" ~ ":" ~ typeTree ~ "=" ~ "{" ~ expr ~ "}").map {
|
||||
case v ~ id ~ _ ~ params ~ _ ~ _ ~ ret ~ _ ~ _ ~ body ~ _ => FunDef(id, params, ret, body).setPos(v)
|
||||
}
|
||||
}
|
||||
|
||||
// A list of parameter definitions.
|
||||
lazy val parameters: Syntax[List[ParamDef]] = repsep(parameter, ",").map(_.toList)
|
||||
|
||||
// A parameter definition, i.e., an identifier along with the expected type.
|
||||
lazy val parameter: Syntax[ParamDef] = {
|
||||
(identifierPos ~ ":" ~ typeTree).map {
|
||||
case id ~ _ ~ tt => ParamDef(id._1, tt).setPos(id._2)
|
||||
}
|
||||
}
|
||||
|
||||
// A type expression.
|
||||
lazy val typeTree: Syntax[TypeTree] = primitiveType | identifierType
|
||||
|
||||
// A built-in type (such as `Int`).
|
||||
val primitiveType: Syntax[TypeTree] = accept(PrimTypeKind) {
|
||||
case tk@PrimTypeToken(name) => TypeTree(name match {
|
||||
case "Unit" => UnitType
|
||||
case "Boolean" => BooleanType
|
||||
case "Int" => IntType
|
||||
case "String" => StringType
|
||||
case _ => throw new java.lang.Error("Unexpected primitive type name: " + name)
|
||||
}).setPos(tk)
|
||||
}
|
||||
|
||||
// A user-defined type (such as `List`).
|
||||
lazy val identifierType: Syntax[TypeTree] = {
|
||||
(identifierPos ~ opt("." ~ identifierPos)).map{
|
||||
case id1 ~ Some(_ ~ id2) => TypeTree(ClassType(QualifiedName(Some(id1._1),id2._1))).setPos(id1._2)
|
||||
case id1 ~ None => TypeTree(ClassType(QualifiedName(None, id1._1))).setPos(id1._2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// An expression.
|
||||
// HINT: You can use `operators` to take care of associativity and precedence
|
||||
lazy val expr: Syntax[Expr] = recursive {
|
||||
expr_l1
|
||||
}
|
||||
|
||||
lazy val expr_l1: Syntax[Expr] = expr_ass | expr_seq
|
||||
lazy val expr_l2: Syntax[Expr] = expr_match | expr_if
|
||||
lazy val expr_l3: Syntax[Expr] = expr_op
|
||||
lazy val expr_l4: Syntax[Expr] = expr_op_un
|
||||
lazy val expr_l5: Syntax[Expr] = expr_err | expr_para | simpleExpr
|
||||
|
||||
|
||||
lazy val expr_ass : Syntax[Expr] = {
|
||||
(kw("val") ~ parameter ~ "=" ~ expr_l2 ~ ";" ~ expr).map {
|
||||
case v ~ param ~ _ ~ expr1 ~ _ ~ expr2 => Let(param, expr1, expr2).setPos(v)
|
||||
}
|
||||
}
|
||||
|
||||
lazy val expr_seq : Syntax[Expr] = {
|
||||
((expr_l2) ~ opt(";" ~ expr)).map{
|
||||
case expr1 ~ Some(_ ~ expr2) => Sequence(expr1,expr2).setPos(expr1)
|
||||
case expr1 ~ None => expr1.setPos(expr1)
|
||||
}
|
||||
}
|
||||
|
||||
lazy val expr_if : Syntax[Expr] = {
|
||||
(kw("if") ~ "(" ~ expr ~ ")" ~ "{" ~ expr ~ "}" ~ kw("else") ~ "{" ~ expr ~ "}").map{
|
||||
case v ~ _ ~ expr_iif ~ _ ~ _ ~ expr_then ~ _ ~ _ ~ _ ~ expr_else ~ _ => Ite(expr_iif, expr_then, expr_else).setPos(v)
|
||||
}
|
||||
}
|
||||
|
||||
lazy val expr_match : Syntax[Expr] = {
|
||||
(expr_l3 ~ opt(kw("match") ~ "{" ~ many1(expr_case) ~ "}")).map{
|
||||
case expr1 ~ Some(_ ~ _ ~ expr_c ~_) => Match(expr1, expr_c.toList).setPos(expr1)
|
||||
case expr1 ~ None => expr1.setPos(expr1)
|
||||
}
|
||||
}
|
||||
lazy val expr_case : Syntax[MatchCase] = {
|
||||
(kw("case") ~ pattern ~ "=>" ~ expr).map{
|
||||
case v ~ pat ~ _ ~ expr1 => MatchCase(pat,expr1).setPos(v)
|
||||
}
|
||||
}
|
||||
|
||||
lazy val expr_op : Syntax[Expr] = operation
|
||||
lazy val expr_op_un : Syntax[Expr] = unary_operation
|
||||
|
||||
lazy val expr_err : Syntax[Expr] = {
|
||||
(kw("error") ~ expr_para).map{
|
||||
case v ~ err => Error(err).setPos(v)
|
||||
}
|
||||
}
|
||||
|
||||
lazy val expr_para : Syntax[Expr] = {
|
||||
("(" ~ opt(expr) ~ ")").map {
|
||||
case v ~ Some(expr1) ~ _ => expr1.setPos(v)
|
||||
case _ ~ None ~ _ => UnitLiteral()
|
||||
}
|
||||
}
|
||||
|
||||
lazy val operation : Syntax[Expr] = operators(expr_l4)(
|
||||
op("*") | op("/") | op("%") is LeftAssociative,
|
||||
op("+") | op("-") | op("++") is LeftAssociative,
|
||||
op("<") | op("<=") is LeftAssociative,
|
||||
op("==") is LeftAssociative,
|
||||
op("&&") is LeftAssociative,
|
||||
op("||") is LeftAssociative
|
||||
) {
|
||||
case (lhs, OperatorToken("+"), rhs) => Plus(lhs, rhs).setPos(lhs)
|
||||
case (lhs, OperatorToken("-"), rhs) => Minus(lhs, rhs).setPos(lhs)
|
||||
case (lhs, OperatorToken("*"), rhs) => Times(lhs, rhs).setPos(lhs)
|
||||
case (lhs, OperatorToken("/"), rhs) => Div(lhs, rhs).setPos(lhs)
|
||||
case (lhs, OperatorToken("%"), rhs) => Mod(lhs, rhs).setPos(lhs)
|
||||
case (lhs, OperatorToken("<"), rhs) => LessThan(lhs, rhs).setPos(lhs)
|
||||
case (lhs, OperatorToken("<="), rhs) => LessEquals(lhs, rhs).setPos(lhs)
|
||||
case (lhs, OperatorToken("&&"), rhs) => And(lhs, rhs).setPos(lhs)
|
||||
case (lhs, OperatorToken("||"), rhs) => Or(lhs, rhs).setPos(lhs)
|
||||
case (lhs, OperatorToken("=="), rhs) => Equals(lhs, rhs).setPos(lhs)
|
||||
case (lhs, OperatorToken("++"), rhs) => Concat(lhs, rhs).setPos(lhs)
|
||||
case _ => throw new java.lang.Error("Unexpected operation")
|
||||
}
|
||||
|
||||
|
||||
lazy val unary_operation : Syntax[Expr] = {
|
||||
(opt( op("-") | op("!")) ~ expr_l5).map {
|
||||
case Some(OperatorToken("-")) ~ expr1 => Neg(expr1)
|
||||
case Some(OperatorToken("!")) ~ expr1 => Not(expr1)
|
||||
case None ~ expr1 => expr1
|
||||
case _ => throw new java.lang.Error("Unexpected unary operator")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// A literal expression.
|
||||
lazy val literal: Syntax[Literal[_]] = {
|
||||
accept(LiteralKind){
|
||||
case tk@BoolLitToken(b) => BooleanLiteral(b).setPos(tk)
|
||||
case tk@IntLitToken(i) => IntLiteral(i).setPos(tk)
|
||||
case tk@StringLitToken(s) => StringLiteral(s).setPos(tk)
|
||||
}
|
||||
}
|
||||
|
||||
// A pattern as part of a mach case.
|
||||
lazy val pattern: Syntax[Pattern] = recursive {
|
||||
literalPattern | wildPattern | ioc_pat | unitPattern
|
||||
}
|
||||
|
||||
lazy val ioc_pat : Syntax[Pattern] = {
|
||||
(identifierPos ~ opt("." ~ identifierPos) ~ opt("(" ~ patterns ~")")).map{
|
||||
case id1 ~ Some(_ ~ id2) ~ Some(_ ~ pats ~ _) => CaseClassPattern(QualifiedName(Some(id1._1), id2._1), pats)
|
||||
case id1 ~ None ~ Some(_ ~ pats ~ _) => CaseClassPattern(QualifiedName(None, id1._1), pats)
|
||||
case id ~ None ~ None => IdPattern(id._1).setPos(id._2)
|
||||
case _ => throw new java.lang.Error("Unexpected pattern")
|
||||
}
|
||||
}
|
||||
|
||||
lazy val patterns: Syntax[List[Pattern]] = repsep(pattern,",").map(_.toList)
|
||||
|
||||
lazy val literalPattern: Syntax[Pattern] = {
|
||||
literal.map{
|
||||
v => LiteralPattern(v).setPos(v)
|
||||
}
|
||||
}
|
||||
|
||||
lazy val wildPattern: Syntax[Pattern] = {
|
||||
kw("_").map{
|
||||
v => WildcardPattern().setPos(v);
|
||||
}
|
||||
}
|
||||
|
||||
lazy val unitPattern: Syntax[Pattern] = {
|
||||
("(" ~ ")").map(_ => LiteralPattern(UnitLiteral()) )
|
||||
}
|
||||
|
||||
|
||||
// HINT: It is useful to have a restricted set of expressions that don't include any more operators on the outer level.
|
||||
lazy val simpleExpr: Syntax[Expr] = literal.up[Expr] | variableOrCall
|
||||
|
||||
|
||||
lazy val variableOrCall: Syntax[Expr] = recursive{
|
||||
(identifierPos ~ opt(opt("." ~ identifierPos) ~"(" ~ funArgs ~ ")")).map{
|
||||
case id ~ None => Variable(id._1).setPos(id._2)
|
||||
case id1 ~ Some(Some(_ ~ id2) ~ _ ~ params ~ _) => Call(QualifiedName(Some(id1._1), id2._1),params).setPos(id1._2)
|
||||
case id1 ~ Some(None ~ _ ~ params ~ _) => Call(QualifiedName(None, id1._1),params).setPos(id1._2)
|
||||
case _ => throw new java.lang.Error("Unexpected variable def")
|
||||
}
|
||||
}
|
||||
|
||||
lazy val funArgs: Syntax[List[Expr]] = repsep(expr, ",").map(_.toList)
|
||||
|
||||
|
||||
// Ensures the grammar is in LL(1), otherwise prints some counterexamples
|
||||
lazy val checkLL1: Boolean = {
|
||||
if (program.isLL1) {
|
||||
true
|
||||
} else {
|
||||
debug(program,true)
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override def run(ctx: Context)(tokens: Iterator[Token]): Program = {
|
||||
import ctx.reporter._
|
||||
if (!checkLL1) {
|
||||
ctx.reporter.fatal("Program grammar is not LL1!")
|
||||
}
|
||||
|
||||
program(tokens) match {
|
||||
case Parsed(result, rest) => result
|
||||
case UnexpectedEnd(rest) => fatal("Unexpected end of input.")
|
||||
case UnexpectedToken(token, rest) => fatal("Unexpected token: " + token + ", possible kinds: " + rest.first.map(_.toString).mkString(", "))
|
||||
}
|
||||
}
|
||||
}
|
58
cs320-clp/src/amyc/parsing/Tokens.scala
Normal file
58
cs320-clp/src/amyc/parsing/Tokens.scala
Normal file
@@ -0,0 +1,58 @@
|
||||
package amyc
|
||||
package parsing
|
||||
|
||||
import amyc.utils.Positioned
|
||||
|
||||
sealed trait Token extends Positioned with Product {
|
||||
override def toString = {
|
||||
productPrefix + productIterator.mkString("(", ",", ")") + "(" + position.withoutFile + ")"
|
||||
}
|
||||
}
|
||||
|
||||
object Tokens {
|
||||
final case class KeywordToken(value: String) extends Token // e.g. keyword "if"
|
||||
final case class IdentifierToken(name: String) extends Token // e.g. variable name "x"
|
||||
final case class PrimTypeToken(value: String) extends Token // e.g. primitive type "Int"
|
||||
final case class IntLitToken(value: Int) extends Token // e.g. integer literal "123"
|
||||
final case class StringLitToken(value: String) extends Token
|
||||
final case class BoolLitToken(value: Boolean) extends Token
|
||||
final case class DelimiterToken(value: String) extends Token // .,:;(){}[]= and =>
|
||||
final case class OperatorToken(name: String) extends Token // e.g. "+"
|
||||
final case class CommentToken(text: String) extends Token // e.g. "// this is a comment"
|
||||
final case class SpaceToken() extends Token // e.g. "\n "
|
||||
final case class ErrorToken(content: String) extends Token
|
||||
final case class EOFToken() extends Token // special token at the end of file
|
||||
}
|
||||
|
||||
sealed abstract class TokenKind(representation: String) {
|
||||
override def toString: String = representation
|
||||
}
|
||||
|
||||
object TokenKinds {
|
||||
final case class KeywordKind(value: String) extends TokenKind(value)
|
||||
final case object IdentifierKind extends TokenKind("<Identifier>")
|
||||
final case object PrimTypeKind extends TokenKind("<Primitive Type>")
|
||||
final case object LiteralKind extends TokenKind("<Literal>")
|
||||
final case class DelimiterKind(value: String) extends TokenKind(value)
|
||||
final case class OperatorKind(value: String) extends TokenKind(value)
|
||||
final case object EOFKind extends TokenKind("<EOF>")
|
||||
final case object NoKind extends TokenKind("<???>")
|
||||
}
|
||||
|
||||
object TokenKind {
|
||||
import TokenKinds._
|
||||
import Tokens._
|
||||
|
||||
def of(token: Token): TokenKind = token match {
|
||||
case KeywordToken(value) => KeywordKind(value)
|
||||
case IdentifierToken(_) => IdentifierKind
|
||||
case PrimTypeToken(_) => PrimTypeKind
|
||||
case BoolLitToken(_) => LiteralKind
|
||||
case IntLitToken(_) => LiteralKind
|
||||
case StringLitToken(_) => LiteralKind
|
||||
case DelimiterToken(value) => DelimiterKind(value)
|
||||
case OperatorToken(value) => OperatorKind(value)
|
||||
case EOFToken() => EOFKind
|
||||
case _ => NoKind
|
||||
}
|
||||
}
|
3
cs320-clp/src/amyc/utils/AmycFatalError.scala
Normal file
3
cs320-clp/src/amyc/utils/AmycFatalError.scala
Normal file
@@ -0,0 +1,3 @@
|
||||
package amyc.utils
|
||||
|
||||
case class AmycFatalError(msg: String) extends Exception(msg)
|
12
cs320-clp/src/amyc/utils/Context.scala
Normal file
12
cs320-clp/src/amyc/utils/Context.scala
Normal file
@@ -0,0 +1,12 @@
|
||||
package amyc.utils
|
||||
|
||||
// Contains a reporter and configuration for the compiler
|
||||
case class Context(
|
||||
reporter: Reporter,
|
||||
files: List[String],
|
||||
printTokens: Boolean = false,
|
||||
printTrees: Boolean = false,
|
||||
printNames: Boolean = false,
|
||||
interpret: Boolean = false,
|
||||
help: Boolean = false
|
||||
)
|
49
cs320-clp/src/amyc/utils/Document.scala
Normal file
49
cs320-clp/src/amyc/utils/Document.scala
Normal file
@@ -0,0 +1,49 @@
|
||||
package amyc.utils
|
||||
|
||||
// A structured document to be printed with nice indentation
|
||||
abstract class Document {
|
||||
|
||||
def <:>(other: Document) = Lined(List(this, other))
|
||||
|
||||
def print: String = {
|
||||
val sb = new StringBuffer()
|
||||
|
||||
def rec(d: Document)(implicit ind: Int, first: Boolean): Unit = d match {
|
||||
case Raw(s) =>
|
||||
if (first && s.nonEmpty) sb append (" " * ind)
|
||||
sb append s
|
||||
case Indented(doc) =>
|
||||
rec(doc)(ind + 1, first)
|
||||
case Unindented(doc) =>
|
||||
assume(ind > 0)
|
||||
rec(doc)(ind - 1, first)
|
||||
case Lined(Nil, _) => // skip
|
||||
case Lined(docs, sep) =>
|
||||
rec(docs.head)
|
||||
docs.tail foreach { doc =>
|
||||
rec(sep)(ind, false)
|
||||
rec(doc)(ind, false)
|
||||
}
|
||||
case Stacked(Nil, _) => // skip
|
||||
case Stacked(docs, emptyLines) =>
|
||||
rec(docs.head)
|
||||
docs.tail foreach { doc =>
|
||||
sb append "\n"
|
||||
if (emptyLines) sb append "\n"
|
||||
rec(doc)(ind, true)
|
||||
}
|
||||
}
|
||||
|
||||
rec(this)(0, true)
|
||||
sb.toString
|
||||
}
|
||||
}
|
||||
case class Indented(content: Document) extends Document
|
||||
case class Unindented(content: Document) extends Document
|
||||
case class Stacked(docs: List[Document], emptyLines: Boolean = false) extends Document
|
||||
case class Lined(docs: List[Document], separator: Document = Raw("")) extends Document
|
||||
case class Raw(s: String) extends Document
|
||||
|
||||
object Stacked {
|
||||
def apply(docs: Document*): Stacked = Stacked(docs.toList)
|
||||
}
|
19
cs320-clp/src/amyc/utils/Env.scala
Normal file
19
cs320-clp/src/amyc/utils/Env.scala
Normal file
@@ -0,0 +1,19 @@
|
||||
package amyc.utils
|
||||
|
||||
object Env {
|
||||
trait OS
|
||||
object Linux extends OS
|
||||
object Windows extends OS
|
||||
object Mac extends OS
|
||||
|
||||
lazy val os = {
|
||||
// If all fails returns Linux
|
||||
val optOsName = Option(System.getProperty("os.name"))
|
||||
optOsName.map(_.toLowerCase()).map { osName =>
|
||||
if (osName contains "linux") Linux
|
||||
else if (osName contains "win") Windows
|
||||
else if (osName contains "mac") Mac
|
||||
else Linux
|
||||
} getOrElse Linux
|
||||
}
|
||||
}
|
21
cs320-clp/src/amyc/utils/Pipeline.scala
Normal file
21
cs320-clp/src/amyc/utils/Pipeline.scala
Normal file
@@ -0,0 +1,21 @@
|
||||
package amyc.utils
|
||||
|
||||
// A sequence of operations to be run by the compiler,
|
||||
// with interruption at every stage if there is an error
|
||||
abstract class Pipeline[-F, +T] {
|
||||
self =>
|
||||
|
||||
def andThen[G](thenn: Pipeline[T, G]): Pipeline[F, G] = new Pipeline[F,G] {
|
||||
def run(ctx : Context)(v : F) : G = {
|
||||
val first = self.run(ctx)(v)
|
||||
ctx.reporter.terminateIfErrors()
|
||||
thenn.run(ctx)(first)
|
||||
}
|
||||
}
|
||||
|
||||
def run(ctx: Context)(v: F): T
|
||||
}
|
||||
|
||||
case class Noop[T]() extends Pipeline[T, T] {
|
||||
def run(ctx: Context)(v: T) = v
|
||||
}
|
81
cs320-clp/src/amyc/utils/Position.scala
Normal file
81
cs320-clp/src/amyc/utils/Position.scala
Normal file
@@ -0,0 +1,81 @@
|
||||
package amyc.utils
|
||||
|
||||
import java.io.File
|
||||
|
||||
import scallion.input.Positioner
|
||||
|
||||
object Position {
|
||||
/** Number of bits used to encode the line number */
|
||||
private final val LINE_BITS = 20
|
||||
/** Number of bits used to encode the column number */
|
||||
private final val COLUMN_BITS = 31 - LINE_BITS // no negatives => 31
|
||||
/** Mask to decode the line number */
|
||||
private final val LINE_MASK = (1 << LINE_BITS) - 1
|
||||
/** Mask to decode the column number */
|
||||
private final val COLUMN_MASK = (1 << COLUMN_BITS) - 1
|
||||
|
||||
private def lineOf(pos: Int): Int = (pos >> COLUMN_BITS) & LINE_MASK
|
||||
private def columnOf(pos: Int): Int = pos & COLUMN_MASK
|
||||
|
||||
def fromFile(f: File, i: Int) = {
|
||||
SourcePosition(f, lineOf(i), columnOf(i))
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Position {
|
||||
val file: File
|
||||
val line: Int
|
||||
val col: Int
|
||||
|
||||
def isDefined: Boolean
|
||||
def withoutFile: String
|
||||
}
|
||||
|
||||
case class SourcePosition(file: File, line: Int, col: Int) extends Position {
|
||||
override def toString: String = s"${file.getPath}:$line:$col"
|
||||
def withoutFile = s"$line:$col"
|
||||
val isDefined = true
|
||||
}
|
||||
|
||||
case object NoPosition extends Position {
|
||||
val file = null
|
||||
val line = 0
|
||||
val col = 0
|
||||
|
||||
override def toString: String = "?:?"
|
||||
def withoutFile = toString
|
||||
val isDefined = false
|
||||
}
|
||||
|
||||
// A trait for entities which have a position in a file
|
||||
trait Positioned {
|
||||
|
||||
protected var pos_ : Position = NoPosition
|
||||
|
||||
def hasPosition = pos_ != NoPosition
|
||||
|
||||
def position = pos_
|
||||
|
||||
def setPos(pos: Position): this.type = {
|
||||
pos_ = pos
|
||||
this
|
||||
}
|
||||
|
||||
def setPos(other: Positioned): this.type = {
|
||||
setPos(other.position)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case class SourcePositioner(file: File) extends Positioner[Char, SourcePosition] {
|
||||
override val start: SourcePosition = SourcePosition(file, 1, 1)
|
||||
|
||||
override def increment(position: SourcePosition, character: Char): SourcePosition =
|
||||
if (character == '\n') {
|
||||
position.copy(line = position.line + 1, col = 1)
|
||||
}
|
||||
else {
|
||||
position.copy(col = position.col + 1)
|
||||
}
|
||||
}
|
||||
|
88
cs320-clp/src/amyc/utils/Reporter.scala
Normal file
88
cs320-clp/src/amyc/utils/Reporter.scala
Normal file
@@ -0,0 +1,88 @@
|
||||
package amyc.utils
|
||||
|
||||
import java.io.File
|
||||
|
||||
import scala.io.Source
|
||||
|
||||
// Reports errors and warnings during compilation
|
||||
class Reporter {
|
||||
|
||||
/** Issues some information from the compiler */
|
||||
def info(msg: Any, pos: Position = NoPosition): Unit = {
|
||||
report("[ Info ]", msg, pos)
|
||||
}
|
||||
|
||||
/** Issues a warning from the compiler */
|
||||
def warning(msg: Any, pos: Position = NoPosition): Unit = {
|
||||
report("[Warning]", msg, pos)
|
||||
}
|
||||
|
||||
private var hasErrors = false
|
||||
|
||||
/** Issues a recoverable error message */
|
||||
def error(msg: Any, pos: Position = NoPosition): Unit = {
|
||||
hasErrors = true
|
||||
report("[ Error ]", msg, pos)
|
||||
}
|
||||
|
||||
/** Used for an unrecoverable error: Issues a message, then exits the compiler */
|
||||
def fatal(msg: Any, pos: Position = NoPosition): Nothing = {
|
||||
report("[ Fatal ]", msg, pos)
|
||||
// Despite printing the message, we store it in the error for testing
|
||||
val errMsg = s"$pos: $msg"
|
||||
throw AmycFatalError(errMsg)
|
||||
}
|
||||
|
||||
// Versions for Positioned
|
||||
def info(msg: Any, pos: Positioned): Unit = info(msg, pos.position)
|
||||
def warning(msg: Any, pos: Positioned): Unit = warning(msg, pos.position)
|
||||
def error(msg: Any, pos: Positioned): Unit = error(msg, pos.position)
|
||||
def fatal(msg: Any, pos: Positioned): Nothing = fatal(msg, pos.position)
|
||||
|
||||
|
||||
/** Terminates the compiler if any errors have been detected. */
|
||||
def terminateIfErrors() = {
|
||||
if (hasErrors) {
|
||||
fatal("There were errors.")
|
||||
}
|
||||
}
|
||||
|
||||
private def err(msg: String) {
|
||||
Console.err.println(msg)
|
||||
}
|
||||
|
||||
private def report(prefix: String, msg: Any, pos: Position) {
|
||||
if (pos.isDefined) {
|
||||
err(s"$prefix $pos: $msg")
|
||||
|
||||
val lines = getLines(pos.file)
|
||||
|
||||
if (pos.line > 0 && pos.line-1 < lines.size) {
|
||||
err(s"$prefix ${lines(pos.line-1)}")
|
||||
err(prefix + " " + " "*(pos.col - 1)+"^")
|
||||
} else {
|
||||
err(s"$prefix <line unavailable in source file>")
|
||||
}
|
||||
} else {
|
||||
err(s"$prefix $msg")
|
||||
}
|
||||
}
|
||||
|
||||
private var filesToLines = Map[File, IndexedSeq[String]]()
|
||||
|
||||
private def getLines(f: File): IndexedSeq[String] = {
|
||||
filesToLines.get(f) match {
|
||||
case Some(lines) =>
|
||||
lines
|
||||
|
||||
case None =>
|
||||
val source = Source.fromFile(f).withPositioning(true)
|
||||
val lines = source.getLines().toIndexedSeq
|
||||
source.close()
|
||||
|
||||
filesToLines += f -> lines
|
||||
|
||||
lines
|
||||
}
|
||||
}
|
||||
}
|
14
cs320-clp/src/amyc/utils/UniqueCounter.scala
Normal file
14
cs320-clp/src/amyc/utils/UniqueCounter.scala
Normal file
@@ -0,0 +1,14 @@
|
||||
package amyc.utils
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
// Generates unique counters for each element of a type K
|
||||
class UniqueCounter[K] {
|
||||
private val elemIds = mutable.Map[K, Int]().withDefaultValue(-1)
|
||||
|
||||
def next(key: K): Int = synchronized {
|
||||
elemIds(key) += 1
|
||||
elemIds(key)
|
||||
}
|
||||
|
||||
}
|
26
cs320-clp/src/amyc/wasm/Function.scala
Normal file
26
cs320-clp/src/amyc/wasm/Function.scala
Normal file
@@ -0,0 +1,26 @@
|
||||
package amyc.wasm
|
||||
import amyc.wasm.Instructions.Code
|
||||
|
||||
// If isMain = false, represents a function which returns an i32 and will not be exported to js
|
||||
// If isMain = true , represents a function which does not return a value, and will be exported to js
|
||||
class Function private (val name: String, val args: Int, val isMain: Boolean, val locals: Int, val code: Code) {
|
||||
override def toString: String = ModulePrinter(this)
|
||||
}
|
||||
|
||||
class LocalsHandler(args: Int) {
|
||||
private var locals_ = 0
|
||||
def getFreshLocal(): Int = {
|
||||
locals_ += 1
|
||||
args + locals_ - 1
|
||||
}
|
||||
private[wasm] def locals = locals_
|
||||
}
|
||||
|
||||
object Function {
|
||||
def apply(name: String, args: Int, isMain: Boolean)(codeGen: LocalsHandler => Code) = {
|
||||
val lh = new LocalsHandler(args)
|
||||
// Make code first, as it may increment the locals in lh
|
||||
val code = codeGen(lh)
|
||||
new Function(name, args, isMain, lh.locals, code)
|
||||
}
|
||||
}
|
68
cs320-clp/src/amyc/wasm/Instructions.scala
Normal file
68
cs320-clp/src/amyc/wasm/Instructions.scala
Normal file
@@ -0,0 +1,68 @@
|
||||
package amyc
|
||||
package wasm
|
||||
|
||||
import scala.language.implicitConversions
|
||||
|
||||
// A subset of instructions defined by the WASM standard
|
||||
object Instructions {
|
||||
abstract class Instruction
|
||||
|
||||
// Load an int32 constant to the stack
|
||||
case class Const(value: Int) extends Instruction
|
||||
|
||||
// Numeric/logical instructions (all take i32 operands)
|
||||
case object Add extends Instruction
|
||||
case object Sub extends Instruction
|
||||
case object Mul extends Instruction
|
||||
case object Div extends Instruction
|
||||
case object Rem extends Instruction
|
||||
case object And extends Instruction
|
||||
case object Or extends Instruction
|
||||
case object Eqz extends Instruction // Return 1 if operand is 0, 0 otherwise
|
||||
case object Lt_s extends Instruction // Signed less-than
|
||||
case object Le_s extends Instruction // Signed less-equals
|
||||
case object Eq extends Instruction
|
||||
case object Drop extends Instruction // Drops the top value of the stack
|
||||
|
||||
// Control instructions
|
||||
case object If_void extends Instruction // Marks the beginning of an if-block (with implicit 'then').
|
||||
case object If_i32 extends Instruction // Marks the beginning of an if-block (with implicit 'then'). Must leave an i32 on the stack
|
||||
case object Else extends Instruction // Marks the end of the implicit 'then' of an if-block
|
||||
case object End extends Instruction // Marks the end of an if-then-else or block
|
||||
case class Loop(label: String) extends Instruction // A block of instructions with a label at the beginning
|
||||
case class Block(label: String) extends Instruction // A block of instructions with a label at the end
|
||||
case class Br(label: String) extends Instruction // Jump to "label", which MUST be the label of an enclosing structure
|
||||
case class Call(name: String) extends Instruction
|
||||
case object Return extends Instruction
|
||||
case object Unreachable extends Instruction // Always fails the program
|
||||
|
||||
// Locals (parameters, local variables)
|
||||
case class GetLocal(index: Int) extends Instruction
|
||||
case class SetLocal(index: Int) extends Instruction
|
||||
|
||||
// Global variables
|
||||
case class GetGlobal(index: Int) extends Instruction
|
||||
case class SetGlobal(index: Int) extends Instruction
|
||||
|
||||
// Memory
|
||||
// Stores an i32 to memory. Expects memory address, then stored value as operands
|
||||
case object Store extends Instruction
|
||||
// Loads an i32 to memory. Expects memory address as operand
|
||||
case object Load extends Instruction
|
||||
// Stores a single byte to memory (the least significant byte of the operand)
|
||||
// Operands expected are like Store
|
||||
case object Store8 extends Instruction
|
||||
// Load a byte from memory, then zero-extend it to fill an i32
|
||||
case object Load8_u extends Instruction
|
||||
|
||||
// Represents a sequence of instructions
|
||||
case class Code(instructions: List[Instruction]) {
|
||||
def <:>(i: Instruction) = Code(instructions :+ i)
|
||||
def <:>(other: Code) = Code(this.instructions ++ other.instructions)
|
||||
}
|
||||
|
||||
// Useful implicit conversions to construct Code objects
|
||||
implicit def i2c(i: Instruction): Code = Code(List(i))
|
||||
implicit def is2c(is: List[Instruction]): Code = Code(is)
|
||||
implicit def cs2c(cs: List[Code]): Code = Code(cs flatMap (_.instructions))
|
||||
}
|
201
cs320-clp/src/amyc/wasm/Module.scala
Normal file
201
cs320-clp/src/amyc/wasm/Module.scala
Normal file
@@ -0,0 +1,201 @@
|
||||
package amyc
|
||||
package wasm
|
||||
|
||||
// A WebAssembly module
|
||||
case class Module(name: String, imports: List[String], globals: Int, functions: List[Function]) {
|
||||
|
||||
import java.io.{File, FileWriter}
|
||||
|
||||
def writeWasmText(fileName: String) = {
|
||||
val fw = new FileWriter(new File(fileName))
|
||||
fw.write(ModulePrinter(this))
|
||||
fw.flush()
|
||||
}
|
||||
|
||||
def writeHtmlWrapper(fileName: String, moduleFile: String) = {
|
||||
val wrapperString =
|
||||
s"""|<!doctype html>
|
||||
|
|
||||
|<html>
|
||||
|
|
||||
| <head>
|
||||
| <meta charset="utf-8">
|
||||
| <title>$name</title>
|
||||
| </head>
|
||||
|
|
||||
| <body>
|
||||
| <p id="htmlText"></p>
|
||||
| <script>
|
||||
|
|
||||
| // This library function fetches the wasm module at 'url', instantiates it with
|
||||
| // the given 'importObject', and returns the instantiated object instance
|
||||
| // Taken from https://github.com/WebAssembly/spec
|
||||
| function fetchAndInstantiate(url, importObject) {
|
||||
| return fetch(url).then(response =>
|
||||
| response.arrayBuffer()
|
||||
| ).then(bytes =>
|
||||
| WebAssembly.instantiate(bytes, importObject)
|
||||
| ).then(results =>
|
||||
| results.instance
|
||||
| );
|
||||
| }
|
||||
|
|
||||
| function writeHtml(line) {
|
||||
| document.getElementById("htmlText").innerHTML += line + "<br>"
|
||||
| }
|
||||
|
|
||||
| var memory = new WebAssembly.Memory({initial:100});
|
||||
| var importObject = {
|
||||
| system: {
|
||||
| printInt: function(arg) {
|
||||
| writeHtml(arg);
|
||||
| 0;
|
||||
| },
|
||||
| printString: function(arg) {
|
||||
| var bufView = new Uint8Array(memory.buffer);
|
||||
| var i = arg;
|
||||
| var result = "";
|
||||
| while(bufView[i] != 0) {
|
||||
| result += String.fromCharCode(bufView[i]);
|
||||
| i = i + 1
|
||||
| }
|
||||
| writeHtml(result);
|
||||
| 0;
|
||||
| },
|
||||
| mem: memory
|
||||
| }
|
||||
| };
|
||||
|
|
||||
| fetchAndInstantiate('$moduleFile', importObject).then(function(instance) {
|
||||
|""".stripMargin ++
|
||||
functions.filter(_.isMain).map { f =>
|
||||
s" instance.exports.${f.name}();\n"
|
||||
}.mkString ++
|
||||
"""| });
|
||||
| </script>
|
||||
| </body>
|
||||
|
|
||||
|</html>
|
||||
|
|
||||
""".stripMargin
|
||||
val fw = new FileWriter(new File(fileName))
|
||||
fw.write(wrapperString)
|
||||
fw.flush()
|
||||
}
|
||||
|
||||
def writeNodejsWrapper(fileName: String, moduleFile: String): Unit = {
|
||||
val wrapperString =
|
||||
s"""function safe_require(module_name) {
|
||||
| try {
|
||||
| return require(module_name);
|
||||
| } catch (e) {
|
||||
| console.log('Error: nodejs module ' + module_name +
|
||||
| ' must be installed (you might want to try: npm install ' + module_name + ')');
|
||||
| process.exit(1);
|
||||
| }
|
||||
|}
|
||||
|
|
||||
|// `Wasm` does **not** understand node buffers, but thankfully a node buffer
|
||||
|// is easy to convert to a native Uint8Array.
|
||||
|function toUint8Array(buf) {
|
||||
| var u = new Uint8Array(buf.length);
|
||||
| for (var i = 0; i < buf.length; ++i) {
|
||||
| u[i] = buf[i];
|
||||
| }
|
||||
| return u;
|
||||
|}
|
||||
|// Loads a WebAssembly dynamic library, returns a promise.
|
||||
|// imports is an optional imports object
|
||||
|function loadWebAssembly(filename, imports) {
|
||||
| // Fetch the file and compile it
|
||||
| const buffer = toUint8Array(require('fs').readFileSync(filename))
|
||||
| return WebAssembly.compile(buffer).then(module => {
|
||||
| return new WebAssembly.Instance(module, imports)
|
||||
| })
|
||||
|}
|
||||
|
|
||||
|var rl = require('readline').createInterface({
|
||||
| input: process.stdin,
|
||||
| output: process.stdout
|
||||
|});
|
||||
|
|
||||
|function waitInput() {
|
||||
| input = "";
|
||||
| done = false;
|
||||
| rl.resume();
|
||||
| rl.on('line', function(answer) {
|
||||
| rl.pause();
|
||||
| input = answer;
|
||||
| done = true
|
||||
| });
|
||||
| safe_require('deasync').loopWhile(function(){return !done;});
|
||||
| return input;
|
||||
|}
|
||||
|
|
||||
|var memory = new WebAssembly.Memory({initial:100});
|
||||
|var importObject = {
|
||||
| system: {
|
||||
| mem: memory,
|
||||
|
|
||||
| printInt: function(arg) {
|
||||
| console.log(arg);
|
||||
| 0;
|
||||
| },
|
||||
|
|
||||
| printString: function(arg) {
|
||||
| var bufView = new Uint8Array(memory.buffer);
|
||||
| var i = arg;
|
||||
| var result = "";
|
||||
| while(bufView[i] != 0) {
|
||||
| result += String.fromCharCode(bufView[i]);
|
||||
| i = i + 1
|
||||
| }
|
||||
| console.log(result);
|
||||
| 0;
|
||||
| },
|
||||
|
|
||||
| readInt: function() {
|
||||
| var res = parseInt(waitInput());
|
||||
| if (isNaN(res)) {
|
||||
| console.log("Error: Could not parse int");
|
||||
| process.exit(1)
|
||||
| } else {
|
||||
| return res
|
||||
| }
|
||||
| },
|
||||
|
|
||||
| // This function has a weird signature due to restrictions of the current WASM format:
|
||||
| // It takes as argument the position that it needs to store the string to,
|
||||
| // and returns the first position after the new string.
|
||||
| readString0: function(memB) {
|
||||
| var s = waitInput();
|
||||
| var size = s.length;
|
||||
| var padding = 4 - size % 4;
|
||||
| var fullString = s + "\0".repeat(padding);
|
||||
| var newMemB = memB + size + padding;
|
||||
| var bufView8 = new Uint8Array(memory.buffer);
|
||||
| for (var i = 0; i < fullString.length; i++) {
|
||||
| bufView8[memB + i] = fullString.charCodeAt(i);
|
||||
| }
|
||||
| return newMemB;
|
||||
| }
|
||||
|
|
||||
| }
|
||||
|};
|
||||
|
|
||||
|loadWebAssembly('$moduleFile', importObject).then(function(instance) {
|
||||
|""".stripMargin ++
|
||||
functions.filter(_.isMain).map { f =>
|
||||
s" instance.exports.${f.name}();\n"
|
||||
}.mkString ++
|
||||
""" rl.close();
|
||||
|}).catch( function(error) {
|
||||
| rl.close();
|
||||
| process.exit(1)
|
||||
|})
|
||||
|""".stripMargin
|
||||
val fw = new FileWriter(new File(fileName))
|
||||
fw.write(wrapperString)
|
||||
fw.flush()
|
||||
}
|
||||
}
|
107
cs320-clp/src/amyc/wasm/ModulePrinter.scala
Normal file
107
cs320-clp/src/amyc/wasm/ModulePrinter.scala
Normal file
@@ -0,0 +1,107 @@
|
||||
package amyc.wasm
|
||||
|
||||
import amyc.utils._
|
||||
import amyc.wasm.Instructions._
|
||||
|
||||
import scala.language.implicitConversions
|
||||
|
||||
// Printer for Wasm modules
|
||||
object ModulePrinter {
|
||||
private implicit def s2d(s: String) = Raw(s)
|
||||
|
||||
private def mkMod(mod: Module): Document = Stacked(
|
||||
"(module ",
|
||||
Indented(Stacked(mod.imports map mkImport)),
|
||||
Indented("(global (mut i32) i32.const 0) " * mod.globals),
|
||||
Indented(Stacked(mod.functions map mkFun)),
|
||||
")"
|
||||
)
|
||||
|
||||
private def mkImport(s: String): Document =
|
||||
Lined(List("(import ", s, ")"))
|
||||
|
||||
private def mkFun(fh: Function): Document = {
|
||||
val name = fh.name
|
||||
val isMain = fh.isMain
|
||||
val exportDoc: Document = if (isMain) s"""(export "$name" (func $$$name))""" else ""
|
||||
val paramsDoc: Document = if (fh.args == 0) "" else {
|
||||
Lined(List(
|
||||
"(param ",
|
||||
Lined(List.fill(fh.args)(Raw("i32")), " "),
|
||||
") "
|
||||
))
|
||||
}
|
||||
val resultDoc: Document = if (isMain) "" else "(result i32) "
|
||||
val localsDoc: Document =
|
||||
if (fh.locals > 0)
|
||||
"(local " <:> Lined(List.fill(fh.locals)(Raw("i32")), " ") <:> ")"
|
||||
else
|
||||
""
|
||||
|
||||
Stacked(
|
||||
exportDoc,
|
||||
Lined(List(s"(func $$${fh.name} ", paramsDoc, resultDoc, localsDoc)),
|
||||
Indented(Stacked(mkCode(fh.code))),
|
||||
")"
|
||||
)
|
||||
}
|
||||
|
||||
private def mkCode(code: Code): List[Document] = code.instructions match {
|
||||
case Nil => Nil
|
||||
case h :: t => h match {
|
||||
case Else =>
|
||||
Unindented(mkInstr(h)) ::
|
||||
mkCode(t)
|
||||
case End =>
|
||||
Unindented(mkInstr(h)) ::
|
||||
(mkCode(t) map Unindented)
|
||||
case If_void | If_i32 | Block(_) | Loop(_) =>
|
||||
mkInstr(h) ::
|
||||
(mkCode(t) map Indented)
|
||||
case _ =>
|
||||
mkInstr(h) ::
|
||||
mkCode(t)
|
||||
}
|
||||
}
|
||||
|
||||
private def mkInstr(instr: Instruction): Document = {
|
||||
instr match {
|
||||
case Const(value) => s"i32.const $value"
|
||||
case Add => "i32.add"
|
||||
case Sub => "i32.sub"
|
||||
case Mul => "i32.mul"
|
||||
case Div => "i32.div_s"
|
||||
case Rem => "i32.rem_s"
|
||||
case And => "i32.and"
|
||||
case Or => "i32.or"
|
||||
case Eqz => "i32.eqz"
|
||||
case Lt_s => "i32.lt_s"
|
||||
case Le_s => "i32.le_s"
|
||||
case Eq => "i32.eq"
|
||||
case Drop => "drop"
|
||||
case If_void => "if"
|
||||
case If_i32 => "if (result i32)"
|
||||
case Else => "else"
|
||||
case Block(label) => s"block $$$label"
|
||||
case Loop(label) => s"loop $$$label"
|
||||
case Br(label)=> s"br $$$label"
|
||||
case Return => "ret"
|
||||
case End => "end"
|
||||
case Call(name) => s"call $$$name"
|
||||
case Unreachable => "unreachable"
|
||||
case GetLocal(index) => s"get_local $index"
|
||||
case SetLocal(index) => s"set_local $index"
|
||||
case GetGlobal(index) => s"get_global $index"
|
||||
case SetGlobal(index) => s"set_global $index"
|
||||
case Store => "i32.store"
|
||||
case Load => "i32.load"
|
||||
case Store8 => "i32.store8"
|
||||
case Load8_u => "i32.load8_u"
|
||||
}
|
||||
}
|
||||
|
||||
def apply(mod: Module) = mkMod(mod).print
|
||||
def apply(fh: Function) = mkFun(fh).print
|
||||
def apply(instr: Instruction) = mkInstr(instr).print
|
||||
|
||||
}
|
BIN
cs320-clp/test/.DS_Store
vendored
Normal file
BIN
cs320-clp/test/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
object MinimalError {
|
||||
error("")
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
object EmptyObject {
|
||||
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
/* This comment is closed twice,
|
||||
which should not be interpreted as a single closed comment
|
||||
|
||||
*/*/
|
1
cs320-clp/test/resources/lexer/failing/SingleAmp.scala
Normal file
1
cs320-clp/test/resources/lexer/failing/SingleAmp.scala
Normal file
@@ -0,0 +1 @@
|
||||
&
|
@@ -0,0 +1 @@
|
||||
/***
|
@@ -0,0 +1,3 @@
|
||||
OperatorToken(*)(4:3)
|
||||
OperatorToken(/)(4:4)
|
||||
EOFToken()(4:5)
|
18
cs320-clp/test/resources/lexer/outputs/Keywords.txt
Normal file
18
cs320-clp/test/resources/lexer/outputs/Keywords.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
KeywordToken(abstract)(1:1)
|
||||
PrimTypeToken(Boolean)(1:10)
|
||||
KeywordToken(case)(2:1)
|
||||
KeywordToken(class)(2:6)
|
||||
KeywordToken(def)(2:12)
|
||||
KeywordToken(else)(2:16)
|
||||
KeywordToken(error)(2:21)
|
||||
KeywordToken(extends)(2:27)
|
||||
BoolLitToken(false)(2:35)
|
||||
KeywordToken(if)(2:41)
|
||||
PrimTypeToken(Int)(2:44)
|
||||
KeywordToken(match)(2:48)
|
||||
KeywordToken(object)(2:54)
|
||||
PrimTypeToken(String)(2:61)
|
||||
BoolLitToken(true)(3:1)
|
||||
PrimTypeToken(Unit)(3:6)
|
||||
KeywordToken(val)(3:11)
|
||||
EOFToken()(4:1)
|
3
cs320-clp/test/resources/lexer/outputs/Whitespace.txt
Normal file
3
cs320-clp/test/resources/lexer/outputs/Whitespace.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
IntLitToken(1)(1:2)
|
||||
IntLitToken(2)(2:5)
|
||||
EOFToken()(3:1)
|
@@ -0,0 +1,4 @@
|
||||
/* This comment is closed twice,
|
||||
which should be an error:
|
||||
|
||||
*/*/
|
3
cs320-clp/test/resources/lexer/passing/Keywords.scala
Normal file
3
cs320-clp/test/resources/lexer/passing/Keywords.scala
Normal file
@@ -0,0 +1,3 @@
|
||||
abstract Boolean
|
||||
case class def else error extends false if Int match object String
|
||||
true Unit val
|
2
cs320-clp/test/resources/lexer/passing/Whitespace.scala
Normal file
2
cs320-clp/test/resources/lexer/passing/Whitespace.scala
Normal file
@@ -0,0 +1,2 @@
|
||||
1 // Tab indented
|
||||
2 // Space indented
|
@@ -0,0 +1,3 @@
|
||||
object Args {
|
||||
def foo(i: Int): Int = { foo(1, 2) }
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
object ParamAndLocal_0 {
|
||||
def foo_0(i_0: Int): Int = {
|
||||
val i_1: Int =
|
||||
(i_0 + 1);
|
||||
i_1
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,6 @@
|
||||
object ParamAndLocal {
|
||||
def foo(i: Int): Int = {
|
||||
val i: Int = i + 1;
|
||||
i
|
||||
}
|
||||
}
|
2
cs320-clp/test/resources/parser/outputs/Empty.scala
Normal file
2
cs320-clp/test/resources/parser/outputs/Empty.scala
Normal file
@@ -0,0 +1,2 @@
|
||||
object Empty {
|
||||
}
|
11
cs320-clp/test/resources/parser/outputs/IfCondition.scala
Normal file
11
cs320-clp/test/resources/parser/outputs/IfCondition.scala
Normal file
@@ -0,0 +1,11 @@
|
||||
object IfCondition {
|
||||
(if((
|
||||
val x: Boolean =
|
||||
true;
|
||||
x
|
||||
)) {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
})
|
||||
}
|
7
cs320-clp/test/resources/parser/outputs/Literals.scala
Normal file
7
cs320-clp/test/resources/parser/outputs/Literals.scala
Normal file
@@ -0,0 +1,7 @@
|
||||
object Literals {
|
||||
1;
|
||||
();
|
||||
();
|
||||
"Hello";
|
||||
true
|
||||
}
|
12
cs320-clp/test/resources/parser/outputs/MatchScrutinee.scala
Normal file
12
cs320-clp/test/resources/parser/outputs/MatchScrutinee.scala
Normal file
@@ -0,0 +1,12 @@
|
||||
object IfMatch {
|
||||
(if(true) {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
}) match {
|
||||
case 1 =>
|
||||
true
|
||||
case 2 =>
|
||||
false
|
||||
}
|
||||
}
|
2
cs320-clp/test/resources/parser/passing/Empty.scala
Normal file
2
cs320-clp/test/resources/parser/passing/Empty.scala
Normal file
@@ -0,0 +1,2 @@
|
||||
object Empty {
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
object IfCondition {
|
||||
if (val x: Boolean = true; x) { 1 } else { 2 }
|
||||
}
|
3
cs320-clp/test/resources/parser/passing/Literals.scala
Normal file
3
cs320-clp/test/resources/parser/passing/Literals.scala
Normal file
@@ -0,0 +1,3 @@
|
||||
object Literals {
|
||||
1; (); ( ); "Hello"; true
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
object IfMatch {
|
||||
if (true) { 1 } else { 2 } match {
|
||||
case 1 => true
|
||||
case 2 => false
|
||||
}
|
||||
}
|
3
cs320-clp/test/resources/typer/failing/ArithError1.scala
Normal file
3
cs320-clp/test/resources/typer/failing/ArithError1.scala
Normal file
@@ -0,0 +1,3 @@
|
||||
object ArithError1 {
|
||||
1 + true
|
||||
}
|
3
cs320-clp/test/resources/typer/passing/Arithmetic.scala
Normal file
3
cs320-clp/test/resources/typer/passing/Arithmetic.scala
Normal file
@@ -0,0 +1,3 @@
|
||||
object Arithmetic {
|
||||
1 + 2 * 3 / 4 % -5
|
||||
}
|
38
cs320-clp/test/scala/amyc/test/CodegenTests.scala
Normal file
38
cs320-clp/test/scala/amyc/test/CodegenTests.scala
Normal file
@@ -0,0 +1,38 @@
|
||||
package amyc.test
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
import amyc.analyzer.{NameAnalyzer, TypeChecker}
|
||||
import amyc.codegen._
|
||||
import amyc.parsing._
|
||||
import amyc.utils._
|
||||
import amyc.wasm.Module
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.sys.process._
|
||||
|
||||
class CodegenTests extends ExecutionTests {
|
||||
|
||||
object CodePrinterExecutor extends Pipeline[Module, Unit] {
|
||||
def run(ctx: Context)(m: Module) = {
|
||||
CodePrinter.run(ctx)(m)
|
||||
val fileName = s"${m.name}.js"
|
||||
|
||||
// Consume all standard input!
|
||||
val input = Console.in.lines.iterator().asScala.toList.mkString("\n")
|
||||
val inputS = new ByteArrayInputStream(input.getBytes("UTF-8"))
|
||||
|
||||
val exitCode = s"nodejs wasmout/$fileName" #< inputS ! ProcessLogger(Console.out.println, Console.err.println)
|
||||
if (exitCode != 0)
|
||||
throw AmycFatalError("Nonzero code returned from nodejs")
|
||||
}
|
||||
}
|
||||
|
||||
val pipeline =
|
||||
Lexer andThen
|
||||
Parser andThen
|
||||
NameAnalyzer andThen
|
||||
TypeChecker andThen
|
||||
CodeGen andThen
|
||||
CodePrinterExecutor
|
||||
}
|
93
cs320-clp/test/scala/amyc/test/CompilerTest.scala
Normal file
93
cs320-clp/test/scala/amyc/test/CompilerTest.scala
Normal file
@@ -0,0 +1,93 @@
|
||||
package amyc.test
|
||||
|
||||
import java.io.File
|
||||
import java.util.stream.Collectors
|
||||
|
||||
import amyc.utils._
|
||||
import org.junit.Assert.fail
|
||||
|
||||
abstract class CompilerTest extends TestUtils {
|
||||
private def runPipeline(pipeline: Pipeline[List[File], Unit], fileNames: List[String]) = {
|
||||
val ctx = Context(new Reporter, fileNames)
|
||||
val files = ctx.files.map(new File(_))
|
||||
pipeline.run(ctx)(files)
|
||||
ctx.reporter.terminateIfErrors()
|
||||
}
|
||||
|
||||
private def runPipelineRedirected(
|
||||
pipeline: Pipeline[List[File], Unit],
|
||||
compiledFiles: List[String],
|
||||
input: String
|
||||
): String = {
|
||||
testWithRedirectedIO(runPipeline(pipeline, compiledFiles), input)
|
||||
}
|
||||
|
||||
private def assertEqual(output: String, expected: String) = {
|
||||
val rejectLine = (s: String) =>
|
||||
s.isEmpty ||
|
||||
s.startsWith("[ Info ]") ||
|
||||
s.startsWith("[Warning]") ||
|
||||
s.startsWith("[ Error ]") ||
|
||||
s.startsWith("[ Fatal ]")
|
||||
def filtered(s: String) = s.lines.filter(rejectLine(_)==false).collect(Collectors.joining("\n"));
|
||||
val filteredOutput = filtered(output)
|
||||
val filteredExpected = filtered(expected)
|
||||
if (filteredOutput != filteredExpected) {
|
||||
val sb = new StringBuffer()
|
||||
sb.append("\nOutput is different:\n")
|
||||
sb.append("\nOutput: \n")
|
||||
sb.append(filteredOutput)
|
||||
sb.append("\n\nExpected output: \n")
|
||||
sb.append(filteredExpected)
|
||||
sb.append("\n")
|
||||
fail(sb.toString)
|
||||
}
|
||||
}
|
||||
|
||||
protected def compareOutputs(
|
||||
pipeline: Pipeline[List[File], Unit],
|
||||
compiledFiles: List[String],
|
||||
expectedFile: String,
|
||||
input: String = ""
|
||||
) = {
|
||||
try {
|
||||
val output = runPipelineRedirected(pipeline, compiledFiles, input)
|
||||
val expected = scala.io.Source.fromFile(new File(expectedFile)).mkString
|
||||
assertEqual(output, expected)
|
||||
} catch {
|
||||
// We only want to catch AmyFatalError gracefully, the rest can propagate
|
||||
case AmycFatalError(msg) =>
|
||||
fail(s"\n $msg\n")
|
||||
}
|
||||
}
|
||||
|
||||
protected def demandPass(
|
||||
pipeline: Pipeline[List[File], Unit],
|
||||
compiledFiles: List[String],
|
||||
input: String = ""
|
||||
) = {
|
||||
try {
|
||||
runPipelineRedirected(pipeline, compiledFiles, input)
|
||||
} catch {
|
||||
case AmycFatalError(msg) =>
|
||||
fail(s"\n $msg\n")
|
||||
}
|
||||
}
|
||||
|
||||
protected def demandFailure(
|
||||
pipeline: Pipeline[List[File], Unit],
|
||||
compiledFiles: List[String],
|
||||
input: String = ""
|
||||
) = {
|
||||
try {
|
||||
runPipelineRedirected(pipeline, compiledFiles, input)
|
||||
fail("Test should fail but it passed!")
|
||||
} catch {
|
||||
case AmycFatalError(_) =>
|
||||
// Ok, this is what we wanted. Other exceptions should propagate though
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
15
cs320-clp/test/scala/amyc/test/ExecutionTests.scala
Normal file
15
cs320-clp/test/scala/amyc/test/ExecutionTests.scala
Normal file
@@ -0,0 +1,15 @@
|
||||
package amyc.test
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
abstract class ExecutionTests extends TestSuite {
|
||||
|
||||
val baseDir = "interpreter"
|
||||
|
||||
val outputExt = "txt"
|
||||
|
||||
@Test def testEmptyObject = shouldOutput("EmptyObject")
|
||||
|
||||
@Test def testMinimalError = shouldFail("MinimalError")
|
||||
|
||||
}
|
17
cs320-clp/test/scala/amyc/test/LexerTests.scala
Normal file
17
cs320-clp/test/scala/amyc/test/LexerTests.scala
Normal file
@@ -0,0 +1,17 @@
|
||||
package amyc.test
|
||||
|
||||
import amyc.parsing._
|
||||
import org.junit.Test
|
||||
|
||||
class LexerTests extends TestSuite {
|
||||
val pipeline = Lexer andThen DisplayTokens
|
||||
|
||||
val baseDir = "lexer"
|
||||
|
||||
val outputExt = "txt"
|
||||
|
||||
@Test def testKeywords = shouldOutput("Keywords")
|
||||
|
||||
@Test def testSingleAmp = shouldFail("SingleAmp")
|
||||
|
||||
}
|
44
cs320-clp/test/scala/amyc/test/NameAnalyzerTests.scala
Normal file
44
cs320-clp/test/scala/amyc/test/NameAnalyzerTests.scala
Normal file
@@ -0,0 +1,44 @@
|
||||
package amyc.test
|
||||
|
||||
import amyc.analyzer.{NameAnalyzer, SymbolTable}
|
||||
import amyc.ast.SymbolicTreeModule.Program
|
||||
import amyc.ast.{Identifier, SymbolicPrinter}
|
||||
import amyc.parsing._
|
||||
import amyc.utils._
|
||||
import org.junit.Test
|
||||
|
||||
import scala.language.implicitConversions
|
||||
|
||||
class NameAnalyzerTests extends TestSuite {
|
||||
// A little hackery to overcome that Identifier names do not refresh over pipeline runs...
|
||||
private class TestUniquePrinter extends SymbolicPrinter {
|
||||
private val counter = new UniqueCounter[String]
|
||||
private val map = scala.collection.mutable.Map[Identifier, Int]()
|
||||
override implicit def printName(name: Identifier)(implicit printUniqueIds: Boolean): Document = {
|
||||
if (printUniqueIds) {
|
||||
val id = map.getOrElseUpdate(name, counter.next(name.name))
|
||||
s"${name.name}_$id"
|
||||
} else {
|
||||
name.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val treePrinterS: Pipeline[(Program, SymbolTable), Unit] = {
|
||||
new Pipeline[(Program, SymbolTable), Unit] {
|
||||
def run(ctx: Context)(v: (Program, SymbolTable)) = {
|
||||
println((new TestUniquePrinter)(v._1)(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val pipeline = Lexer andThen Parser andThen NameAnalyzer andThen treePrinterS
|
||||
|
||||
val baseDir = "nameAnalyzer"
|
||||
|
||||
val outputExt = "scala"
|
||||
|
||||
@Test def testParamAndLocal = shouldOutput("ParamAndLocal")
|
||||
|
||||
@Test def testArgumentNumberFunction = shouldFail("ArgumentNumberFunction")
|
||||
}
|
18
cs320-clp/test/scala/amyc/test/ParserTests.scala
Normal file
18
cs320-clp/test/scala/amyc/test/ParserTests.scala
Normal file
@@ -0,0 +1,18 @@
|
||||
package amyc.test
|
||||
|
||||
import amyc.parsing._
|
||||
import org.junit.Test
|
||||
|
||||
class ParserTests extends TestSuite with amyc.MainHelpers {
|
||||
val pipeline = Lexer andThen Parser andThen treePrinterN("")
|
||||
|
||||
val baseDir = "parser"
|
||||
|
||||
val outputExt = "scala"
|
||||
|
||||
@Test def testEmpty = shouldOutput("Empty")
|
||||
@Test def testLiterals = shouldOutput("Literals")
|
||||
|
||||
@Test def testEmptyFile = shouldFail("EmptyFile")
|
||||
}
|
||||
|
54
cs320-clp/test/scala/amyc/test/TestSuite.scala
Normal file
54
cs320-clp/test/scala/amyc/test/TestSuite.scala
Normal file
@@ -0,0 +1,54 @@
|
||||
package amyc.test
|
||||
|
||||
import java.io.File
|
||||
|
||||
import amyc.utils.Pipeline
|
||||
|
||||
abstract class TestSuite extends CompilerTest {
|
||||
val pipeline: Pipeline[List[File], Unit]
|
||||
|
||||
val baseDir: String
|
||||
lazy val effectiveBaseDir: String =
|
||||
// getClass.getResource(s"/$baseDir").getPath
|
||||
s"test/resources/$baseDir"
|
||||
|
||||
val passing = "passing"
|
||||
val failing = "failing"
|
||||
val outputs = "outputs"
|
||||
|
||||
val outputExt: String
|
||||
|
||||
def shouldOutput(inputFiles: List[String], outputFile: String, input: String = ""): Unit = {
|
||||
compareOutputs(
|
||||
pipeline,
|
||||
inputFiles map (f => s"$effectiveBaseDir/$passing/$f.scala"),
|
||||
s"$effectiveBaseDir/$outputs/$outputFile.$outputExt",
|
||||
input
|
||||
)
|
||||
}
|
||||
|
||||
def shouldOutput(inputFile: String): Unit = {
|
||||
shouldOutput(List(inputFile), inputFile)
|
||||
}
|
||||
|
||||
def shouldFail(inputFiles: List[String], input: String = ""): Unit = {
|
||||
demandFailure(
|
||||
pipeline,
|
||||
inputFiles map (f => s"$effectiveBaseDir/$failing/$f.scala"),
|
||||
input
|
||||
)
|
||||
}
|
||||
|
||||
def shouldFail(inputFile: String): Unit = {
|
||||
shouldFail(List(inputFile))
|
||||
}
|
||||
|
||||
def shouldPass(inputFiles: List[String], input: String = ""): Unit = {
|
||||
demandPass(pipeline, inputFiles map (f => s"$effectiveBaseDir/$passing/$f.scala"), input)
|
||||
}
|
||||
|
||||
def shouldPass(inputFile: String): Unit = {
|
||||
shouldPass(List(inputFile))
|
||||
}
|
||||
|
||||
}
|
24
cs320-clp/test/scala/amyc/test/TestUtils.scala
Normal file
24
cs320-clp/test/scala/amyc/test/TestUtils.scala
Normal file
@@ -0,0 +1,24 @@
|
||||
package amyc.test
|
||||
|
||||
import java.io._
|
||||
|
||||
/** Some utilities for running tests */
|
||||
trait TestUtils {
|
||||
/** Run test,
|
||||
* with input also redirected from a String,
|
||||
* and output is redirected to a local StringBuilder.
|
||||
*/
|
||||
def testWithRedirectedIO[T](test: => T, input: String): String = {
|
||||
import scala.Console._
|
||||
val inputS = new StringReader(input)
|
||||
val outputS = new ByteArrayOutputStream()
|
||||
withOut(outputS) {
|
||||
withErr(outputS) {
|
||||
withIn(inputS) {
|
||||
test
|
||||
}
|
||||
}
|
||||
}
|
||||
outputS.toString()
|
||||
}
|
||||
}
|
25
cs320-clp/test/scala/amyc/test/TyperTests.scala
Normal file
25
cs320-clp/test/scala/amyc/test/TyperTests.scala
Normal file
@@ -0,0 +1,25 @@
|
||||
package amyc.test
|
||||
|
||||
import amyc.analyzer.{NameAnalyzer, TypeChecker}
|
||||
import amyc.parsing._
|
||||
import amyc.utils._
|
||||
import org.junit.Test
|
||||
|
||||
class TyperTests extends TestSuite {
|
||||
// We need a unit pipeline
|
||||
private def unit[A]: Pipeline[A, Unit] = {
|
||||
new Pipeline[A, Unit] {
|
||||
def run(ctx: Context)(v: A) = ()
|
||||
}
|
||||
}
|
||||
|
||||
val pipeline = Lexer andThen Parser andThen NameAnalyzer andThen TypeChecker andThen unit
|
||||
|
||||
val baseDir = "typer"
|
||||
|
||||
val outputExt = "" // No output files for typechecking
|
||||
|
||||
@Test def testArithError1 = shouldFail("ArithError1")
|
||||
|
||||
@Test def testArithmetic = shouldPass("Arithmetic")
|
||||
}
|
Reference in New Issue
Block a user