diff --git a/info/labs/.gitignore b/info/labs/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..f7f3d6dcb84881f0697bfea38991b16fdfff981b --- /dev/null +++ b/info/labs/.gitignore @@ -0,0 +1,31 @@ +# sbt compilation output +target/ +project/target +# Compiler output and nodejs modules +compiler/**/*.html +compiler/**/*.wat +compiler/**/*.wasm +compiler/**/*.js +compiler/node_modules +compiler/package-lock.json +# Latex output +*.out +*.aux +*.log +*.nav +*.snm +*.toc +*.vrb +# Vim +*.swp +# IntelliJ +.idea +# VSCode +.vscode +# tester +tester/repos +# Metals & Bloop +.bsp/ +.bloop/ +.metals/ +metals.sbt diff --git a/info/labs/lab01/amyi.sh b/info/labs/lab01/amyi.sh new file mode 100755 index 0000000000000000000000000000000000000000..f1cc66f38ee42f0f43f657a9a712802243cfe42b --- /dev/null +++ b/info/labs/lab01/amyi.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# Script similar in structure to amytc.sh, see explanations there. +if [ $# -eq 0 ]; then + echo "Usage: amyi Prog1.amy Prog2.amy ... ProgN.amy" + exit 1 +fi +echo Intepreting: $* +AMYJAR=target/scala-3.5.2/amyc-assembly-1.7.jar +if test -r "${AMYJAR}"; then + echo "Reusing existing jar: ${AMYJAR}" +else + sbt assembly +fi +java -jar ${AMYJAR} --interpret library/Std.amy library/Option.amy library/List.amy $* diff --git a/info/labs/lab01/amytc.sh b/info/labs/lab01/amytc.sh new file mode 100755 index 0000000000000000000000000000000000000000..2e2650879e24dd42901627cb0e6cf74f87786b32 --- /dev/null +++ b/info/labs/lab01/amytc.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# The above line tells the operating system to use the +# bash shell interpreter to execute the commands in this file. +# the hash sign, '#' means that characters following it are comments +if [ $# -eq 0 ]; then # script is called with 0 parameters + # output short usage instructions on the command line + echo "Usage: amytc.sh Prog1.amy Prog2.amy ... ProgN.amy" + echo "Example invocation:" + echo "./amytc.sh examples/Arithmetic.amy" + + echo "Example output:" + echo " Type checking: examples/Arithmetic.amy" + echo " Reusing existing jar: target/scala-3.5.2/amyc-assembly-1.7.jar" + echo " Type checking successful!" + # Now, terminate the script with an error exit code 1: + exit 1 +fi +# print progress message. $* denotes all arguments +echo Type checking: $* +# jar file is a zip file containing .class files of our interpreter/compiler +# sbt generates the file in this particular place. +AMYJAR=target/scala-3.5.2/amyc-assembly-1.7.jar +# You can copy this file into test.zip and run unzip test.zip to see inside +if test -r "${AMYJAR}"; then + # jar file exists, so we just reuse it + echo "Reusing existing jar: ${AMYJAR}" + # Note that we do not check if scala sources changed! + # Hence, our jar file can be old +else + # If there is no jar file, we invoke `sbt assembly` to create it + sbt assembly +fi +# We should have the jar file now, so we invoke it +# java starts the Java Virtual Machine. +# Here, it will unpack the jar file and find META-INF/MANIFEST.MF file +# which specifies the main class of the jar file (entry point). +# java will execute `public static void main` method of that class. +java -jar ${AMYJAR} --type-check library/Std.amy library/Option.amy library/List.amy $* +# Here, we ask amy to only type check the give files. +# We always provide standard library files as well as +# the explicitly files explicit given to the script (denoted $*) diff --git a/info/labs/lab01/build.sbt b/info/labs/lab01/build.sbt new file mode 100644 index 0000000000000000000000000000000000000000..48a2d6bbe5aea6212fafc355584140e5f758b651 --- /dev/null +++ b/info/labs/lab01/build.sbt @@ -0,0 +1,16 @@ +version := "1.7" +organization := "ch.epfl.lara" +scalaVersion := "3.5.2" +assembly / test := {} +name := "amyc" + +Compile / scalaSource := baseDirectory.value / "src" +scalacOptions ++= Seq("-feature") + +Test / scalaSource := baseDirectory.value / "test" / "scala" +Test / parallelExecution := false +libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" +libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.4" % "test" +testOptions += Tests.Argument(TestFrameworks.JUnit, "-v") + + diff --git a/info/labs/lab01/examples/Arithmetic.amy b/info/labs/lab01/examples/Arithmetic.amy new file mode 100644 index 0000000000000000000000000000000000000000..bc47b3dd27d257e4e4b2d70d654aa7cd894d5082 --- /dev/null +++ b/info/labs/lab01/examples/Arithmetic.amy @@ -0,0 +1,34 @@ +object Arithmetic + def pow(b: Int(32), e: Int(32)): Int(32) = { + if (e == 0) { 1 } + else { + if (e % 2 == 0) { + val rec: Int(32) = pow(b, e/2); + rec * rec + } else { + b * pow(b, e - 1) + } + } + } + + def gcd(a: Int(32), b: Int(32)): Int(32) = { + 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)); // 1 + Std.printInt(gcd(16, 46)); // 2 + Std.printInt(gcd(222, 888)) // 222 +end Arithmetic diff --git a/info/labs/lab01/examples/Factorial.amy b/info/labs/lab01/examples/Factorial.amy new file mode 100644 index 0000000000000000000000000000000000000000..d31b150a6f62dd31e7b3063a7def606d26c602e9 --- /dev/null +++ b/info/labs/lab01/examples/Factorial.amy @@ -0,0 +1,12 @@ +object Factorial + def fact(i: Int(32)): Int(32) = { + if (i < 2) { 1 } + else { + val rec: Int(32) = fact(i-1); + i * rec + } + } + + Std.printString("5! = " ++ Std.intToString(fact(5))); + Std.printString("10! = " ++ Std.intToString(fact(10))) +end Factorial diff --git a/info/labs/lab01/examples/Hanoi.amy b/info/labs/lab01/examples/Hanoi.amy new file mode 100644 index 0000000000000000000000000000000000000000..911f7739f78487d4db3ffe7df283be04146e980a --- /dev/null +++ b/info/labs/lab01/examples/Hanoi.amy @@ -0,0 +1,16 @@ +object Hanoi + + def solve(n : Int(32)) : Int(32) = { + 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))) +end Hanoi \ No newline at end of file diff --git a/info/labs/lab01/examples/Hello.amy b/info/labs/lab01/examples/Hello.amy new file mode 100644 index 0000000000000000000000000000000000000000..5cc4b6ea8c91294dfa6d00bc7a63be9881c1d6dd --- /dev/null +++ b/info/labs/lab01/examples/Hello.amy @@ -0,0 +1,3 @@ +object Hello + Std.printString("Hello " ++ "world!") +end Hello diff --git a/info/labs/lab01/examples/HelloInt.amy b/info/labs/lab01/examples/HelloInt.amy new file mode 100644 index 0000000000000000000000000000000000000000..79cab1aa6f078a8de78b9bb576f2ab559a2451e2 --- /dev/null +++ b/info/labs/lab01/examples/HelloInt.amy @@ -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(32) = Std.readInt(); + Std.printString(Std.intToString(age) ++ " years old then.") +end HelloInt diff --git a/info/labs/lab01/examples/Printing.amy b/info/labs/lab01/examples/Printing.amy new file mode 100644 index 0000000000000000000000000000000000000000..bc3bac0381c6752e9729c7354a5f0eca2d27ae69 --- /dev/null +++ b/info/labs/lab01/examples/Printing.amy @@ -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("" ++ "") +end Printing diff --git a/info/labs/lab01/examples/TestLists.amy b/info/labs/lab01/examples/TestLists.amy new file mode 100644 index 0000000000000000000000000000000000000000..a01698881a0b68e1cba5a92d59e01676339e0096 --- /dev/null +++ b/info/labs/lab01/examples/TestLists.amy @@ -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))) +end TestLists diff --git a/info/labs/lab01/lab01-README.md b/info/labs/lab01/lab01-README.md new file mode 100644 index 0000000000000000000000000000000000000000..c4f1f7c0535adf21f0bd0dfa61db3db65c2bae2f --- /dev/null +++ b/info/labs/lab01/lab01-README.md @@ -0,0 +1,157 @@ +# Amy Lab 01: Interpreter + +Below you will find the instructions for the first lab assignment in which you will get to know and implement an interpreter for the Amy language. + +## Logistics + +As a reminder, the labs are done in groups of 2-3, please register on Moodle if not already done. + +We advice you to create a git repository to track your work and collaborate. + +The labs are graded through Moodle assignments, similarly to Software Construction (CS-214) that you might have taken. You will have to submit your `.scala` files on Moodle and you will receive automatically a grade and feedback. You submit as many times as you want, only the last submission will be taken into account. The tests are the same as the ones you will receive for each lab, we do not use any hidden tests. + +For this first lab, you can download the initial project scaffold from this folder. + +Future labs will be distributed in a similar way and will files to add to this initial scaffold. + +## Part 1: Your first Amy programs + +Write two example Amy programs each make sure that they typecheck (see [Type check examples](#type-check-examples)). Put them under `/examples`. Please be creative when writing your programs: they should be nontrivial and not reproduce the functionality of the examples in the `/library` and `/examples` directories of the repository. Of course you are welcome to browse these directories for inspiration. + +Remember that you will use these programs in the remaining of the semester to test your compiler, so don't make them too trivial! Try to test many features of the language. + +If you have questions about how a feature of Amy works, you can always look at the [Amy Specification](../amy-specification/AmySpec.md). It's a good idea to keep a local copy of this document handy -- it will be your reference for whenever you are asked to implement an aspect of the Amy language throughout this semester. + +### Type check examples + +You can use the provided Frontend to type check your programs. To do so, run the provided bash script: + +```bash.sh +./amytc.sh examples/your_program.amy +``` + +This will run the compiler frontend up to type checking and report either `Type checking successful!` or an error message. If you get an error message, you should fix the error before moving on to the next step. + +Please examine the bash scipt amytc.sh and its comments in your editor to understand how it works. Do not modify it. + +#### Troubleshooting + +- Your project must compile before you call the `amytc.sh` script. +- If you get unexpected errors or behaviour, try to delete the `target/scala-3.5.2/amyc-assembly-1.7.jar` and retry. + +## Part 2: An Interpreter for Amy + +The main task of the first lab is to write an interpreter for Amy. + +### Interpreters + +The way to execute programs you have mostly seen so far is compilation to some kind of low-level code (bytecode for a virtual machine such as Java's; native binary code in case of languages such as C). An alternative way to execute programs is interpretation. According to Wikipedia, "an interpreter is a computer program that directly executes, i.e. performs, instructions written in a programming or scripting language, without previously compiling them into a machine language program". In other words, your interpreter is supposed to directly look at the code and *interpret* its meaning. For example, when encountering a call to the 'printString' function, your interpreter should print its argument on the standard output. This is the way Python is executing your code. + +### The general structure of the Interpreter + +The skeleton of the assignment is provided by us as an `sbt` project. See the [Implementation skeleton](#implementation-skeleton) section for more details. + +You will modify the `Interpreter.scala` file. + +In `Main.scala` you find the main method which is the entry point to your program. After processing the command line arguments of the interpreter, the main method creates a Pipeline, which contains the different stages of the compiler which you will implement in the future labs. The Pipeline will first call the Amy frontend, which will parse the source program into an abstract syntax tree (AST) and check it for correctness according to the [Amy Specification](../amy-specification/AmySpec.md), and then passes the result to the Interpreter. + +The AST abstracts away uninteresting things of the program (e.g. parentheses, whitespace, operator precedence...) and keeps the essential structure of the program. It describes the structure of programs recursively. For example, here you have the description of a module in Amy: + +`Module ::= **object** Id Definition* Expr? **end** Id` + +and in the implementation we find a class: + +`case class ModuleDef(name: Identifier, defs: List[ClassOrFunDef], optExpr: Option[Expr]) extends Definition` + +A comparison of the implementation of ASTs in Java (as shown in the book) and Scala is instructive. + +You can find the source code of the AST in the [TreeModule.scala](./src/amyc/ast/TreeModule.scala). + +### The Interpreter class + +Now let's delve into `Interpreter.scala`. This file currently only contains a partial implementation, and it is your task to complete it! The entrypoint into the interpreter is `interpret`, which takes an expression as input and executes its meaning. The main loop at the end of the class will just take the modules in order and interpret their expression, if present. + +`interpret` returns a `Value`, which is a type that represents a value that an Amy expression can produce. Value is inherited by classes which represent the different types of values present in Amy (`Int(32)`, `Booleans`, `Unit`, `String` and ADT values). `Value` has convenience methods to cast to `Int(32)`, `Boolean` and `String` (`as*`). Remember we can always call these methods safely when we know the types of an expression (e.g. the operands of an addition), since we know that the program type-checks. + +`interpret` takes an additional implicit parameter as an argument, which is a mapping from variables to values (in the interpreted language). In Scala, when an implicit parameter is expected, the compiler will look in the scope for some binding of the correct type and pass it automatically. This way we do not have to pass the same mapping over and over to all recursive calls to `interpret`. Be aware, however, that there are some cases when you need to change the `locals` parameter! Think carefully about when you have to do so. + +A few final notes: + +- You can print program output straight to the console. +- You can assume the input programs are valid. This is guaranteed by the Amy frontend. +- To find constructors and functions in the program, you have to search in the `SymbolTable` passed along with the program. To do so, use the three helper methods provided in the interpreter: + - `isConstrutor` will return whether the `Identifier` argument is a type constructor in the program + - `findFunctionOwner` will return the module which contains the given `Identifier`, which has to be a function in the program. E.g. if you give it the `printInt` function of the `Std` module, you will get the string `"Std"`. + - `findFunction` will return the function definition given a pair of Strings representing the module containing the function, and the function name. The return value is of type `FunDef` (see [the AST definitions](./src/amyc/ast/TreeModule.scala)). +- When comparing Strings by reference, compare the two `StringValue`s directly and not the underlying Strings. The reason is that the JVM may return true when comparing Strings by equality when it is not expected (it has to do with JVM constant pools). +- Some functions contained in the `Std` module are built-in in the language, i.e. they are hard-coded in the interpreter because they cannot be implemented in Amy otherwise. An example of a built-in function is `printString`. When you implement the interpreter for function calls, you should first check if the function is built-in, and if so, use the implementation provided in the `builtIns` map in the interpreter. +- When a program fails (e.g. due to a call to `error` or a match fail), you should call the dedicated method in the Context: `ctx.reporter.fatal`. + +### Implementation skeleton + +You can get the project scaffold from [this folder](.). + +- `src/amyc/interpreter/Interpreter.scala` contains the partially implemented interpreter +- `src/amyc/Main.scala` contains the `main` method which runs the interpreter on the input files +- The `library` directory contains library definitions you can use in your Amy programs. +- The `examples` directory contains some example programs on which you can try your implementation. Remember that most of them also use library files from `/library`. This should also contain the programs you wrote in Part 1. +- `lib/amy-frontend-1.7.jar` contains the frontend of the compiler as a library, allowing you directly work with type-checked ASTs of input programs. You need this to be able to extract the AST from your source code to interpret it, as you did not implement this part of the compiler yet. This is also what allowed you to type check programs in part 1. **Note**: You are only allowed to use this binary code to link against your interpreter. + +You will have to complete the interpreter by implementing the missing methods (marked with the placeholder `???`). + +### Testing + +When you are done, use sbt to try some of your programs from Part 1: + +```bash + $ sbt + > run library/Std.amy examples/Hello.amy + Hello world! +``` + +You can also run your interpreter with the `amyi.sh` script in a similar way as you did with the type checker: + +```bash + $ ./amyi.sh examples/Hello.amy + Hello world! +``` + +**Note**: if you use this method, you have to delete `target/scala-3.5.2/amyc-assembly-1.7.jar` before running the script when you modified your interpreter. Otherwise, the script will reuse the previously compiled version of the interpreter and your new modifications would not be taken into account. Therefore this method is more recommended for testing multiple amy programs, rather than testing your interpreter while you are developing it. + +There is also testing infrastructure under `/test`. To add your own tests, you have to add your testcases under `/test/resources/interpreter/passing` +and the expected output under `/test/resources/interpreter/outputs`. +Then, you have to add the name of the new test in `InterpreterTests`, similarly to the examples given. +To allow a test to also use the standard library (e.g., `Std.printString`), you can copy `Std.scala` from `library/Std.scala` to `/test/resources/interpreter/passing`. + +For example, to add a test that expects only "Hello world" to be printed, you can add `/test/resources/interpreter/passing/Hello.amy` containing: + +```scala +object Hello + Std.printString("Hello world") +end Hello +``` + +and `/test/resources/interpreter/outputs/Hello.txt` containing + +```text +Hello world + +``` + +(with a newline in the end!). + +You will also have to add a line to `/test/scala/amyc/test/InterpreterTests.scala`: `@Test def testHello = shouldOutput(List("Std", "Hello"), "Hello")`. This will pass both files `Std.amy` and `Hello.amy` as inputs of the test. When you now run `test` from sbt, you should see the additional test case (called `testHello`). + +The tests provided originally in `test/` are the ones used to grade your work on Moodle. Please note that the grade returned by the grader on Moodle is what you will get for the lab. Therefore you should submit regularly on Moodle to validate your progress. Also, if tests pass locally but not on the grader, the grader is the one that counts so submit your work regularly and check the feedback in case of discrepancies. + +### Deliverables + +You should submit the following files on Moodle: + +- `Interpreter.scala` with your implementation of the interpreter + +Deadline: **07.03.2025 23:59:59** + +#### Related documentation + +- End of Chapter 1 in the Tiger Book presents a similar problem for another mini-language. A comparison of the implementation of ASTs in Java (as shown in the book) and Scala is instructive. diff --git a/info/labs/lab01/lib/amyc-frontend-1.7.jar b/info/labs/lab01/lib/amyc-frontend-1.7.jar new file mode 100644 index 0000000000000000000000000000000000000000..3dcb9f90883affc70ce2063e390ae5b8415acbd9 Binary files /dev/null and b/info/labs/lab01/lib/amyc-frontend-1.7.jar differ diff --git a/info/labs/lab01/lib/scallion-assembly-0.6.1.jar b/info/labs/lab01/lib/scallion-assembly-0.6.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..074c47c01983b0096ab078cac3fceafc97814db4 Binary files /dev/null and b/info/labs/lab01/lib/scallion-assembly-0.6.1.jar differ diff --git a/info/labs/lab01/library/List.amy b/info/labs/lab01/library/List.amy new file mode 100644 index 0000000000000000000000000000000000000000..60fc3bc110a4df310733fd87d42ee24e4f99ee98 --- /dev/null +++ b/info/labs/lab01/library/List.amy @@ -0,0 +1,144 @@ +object L + abstract class List + case class Nil() extends List + case class Cons(h: Int(32), t: List) extends List + + def isEmpty(l : List): Boolean = { l match { + case Nil() => true + case _ => false + }} + + def length(l: List): Int(32) = { l match { + case Nil() => 0 + case Cons(_, t) => 1 + length(t) + }} + + def head(l: List): Int(32) = { + 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(32)): Int(32) = { + l match { + case Nil() => -1 + case Cons(h, t) => + if (h == i) { 0 } + else { + val rec: Int(32) = indexOf(t, i); + if (0 <= rec) { rec + 1 } + else { -1 } + } + } + } + + def range(from: Int(32), to: Int(32)): List = { + if (to < from) { Nil() } + else { + Cons(from, range(from + 1, to)) + } + } + + def sum(l: List): Int(32) = { 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(32)): 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 xs => + split(xs) 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(32)): List = { + if (n <= 0) { Nil() } + else { + l match { + case Nil() => Nil() + case Cons(h, t) => + Cons(h, take(t, n-1)) + } + } + } + +end L diff --git a/info/labs/lab01/library/Option.amy b/info/labs/lab01/library/Option.amy new file mode 100644 index 0000000000000000000000000000000000000000..dabec722fbf00083b815768e62e6bbf9f7096b23 --- /dev/null +++ b/info/labs/lab01/library/Option.amy @@ -0,0 +1,40 @@ +object O + abstract class Option + case class None() extends Option + case class Some(v: Int(32)) extends Option + + def isdefined(o: Option): Boolean = { + o match { + case None() => false + case _ => true + } + } + + def get(o: Option): Int(32) = { + o match { + case Some(i) => i + case None() => error("get(None)") + } + } + + def getOrElse(o: Option, i: Int(32)): Int(32) = { + 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() + } + } +end O diff --git a/info/labs/lab01/library/Std.amy b/info/labs/lab01/library/Std.amy new file mode 100644 index 0000000000000000000000000000000000000000..511bb6eb1f7584652516f2fc3daf84da5cc8d987 --- /dev/null +++ b/info/labs/lab01/library/Std.amy @@ -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(32)): 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(32) = { + error("") // Stub implementation + } + + def intToString(i: Int(32)): String = { + if (i < 0) { + "-" ++ intToString(-i) + } else { + val rem: Int(32) = i % 10; + val div: Int(32) = i / 10; + if (div == 0) { digitToString(rem) } + else { intToString(div) ++ digitToString(rem) } + } + } + def digitToString(i: Int(32)): String = { + error("") // Stub implementation + } + def booleanToString(b: Boolean): String = { + if (b) { "true" } else { "false" } + } +end Std diff --git a/info/labs/lab01/project/build.properties b/info/labs/lab01/project/build.properties new file mode 100644 index 0000000000000000000000000000000000000000..73df629ac1a71e9f7a1c2a1b576bfa037a6142bd --- /dev/null +++ b/info/labs/lab01/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.10.7 diff --git a/info/labs/lab01/project/plugins.sbt b/info/labs/lab01/project/plugins.sbt new file mode 100644 index 0000000000000000000000000000000000000000..04934558068c370e38064654370e09e029476366 --- /dev/null +++ b/info/labs/lab01/project/plugins.sbt @@ -0,0 +1,3 @@ +addSbtPlugin("com.lightbend.sbt" % "sbt-proguard" % "0.3.0") + +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.2.0") \ No newline at end of file diff --git a/info/labs/lab01/src/amyc/Main.scala b/info/labs/lab01/src/amyc/Main.scala new file mode 100644 index 0000000000000000000000000000000000000000..fed5f66b6121d7898db6fb8a8cd87b1ea35c9100 --- /dev/null +++ b/info/labs/lab01/src/amyc/Main.scala @@ -0,0 +1,49 @@ +package amyc + +import utils._ +import interpreter.Interpreter + +import java.io.File + +object Main { + private def parseArgs(args: Array[String]): Context = { + var ctx = Context(new Reporter, Nil) + args foreach { + case "--interpret" => ctx = ctx.copy(interpret = true) + case "--help" => ctx = ctx.copy(help = true) + case "--type-check" => ctx = ctx.copy(typeCheck = true) + case file => ctx = ctx.copy(files = ctx.files :+ file) + } + ctx + } + + def main(args: Array[String]): Unit = { + val ctx = parseArgs(args) + val pipelineInterpret = Frontend.pipeline.andThen(Interpreter) + + if ctx.help then + println("Usage: amyc [ --interpret | --type-check ] file1.amy file2.amy ...") + sys.exit(0) + val files = ctx.files.map(new File(_)) + + try { + if (files.isEmpty) { + ctx.reporter.fatal("No input files") + } + files.find(!_.exists()).foreach { f => + ctx.reporter.fatal(s"File not found: ${f.getName}") + } + if ctx.interpret then + pipelineInterpret.run(ctx)(files) + else if ctx.typeCheck then + Frontend.pipeline.run(ctx)(files) + println("Type checking successful!") + else + ctx.reporter.fatal("No action specified") + ctx.reporter.terminateIfErrors() + } catch { + case AmycFatalError(_) => + sys.exit(1) + } + } +} \ No newline at end of file diff --git a/info/labs/lab01/src/amyc/ast/TreeModule.scala b/info/labs/lab01/src/amyc/ast/TreeModule.scala new file mode 100644 index 0000000000000000000000000000000000000000..b92bf4b647cfffd1ad87241299355f373816cbfe --- /dev/null +++ b/info/labs/lab01/src/amyc/ast/TreeModule.scala @@ -0,0 +1,142 @@ +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 { self => + /* 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: self.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/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 +} + diff --git a/info/labs/lab01/src/amyc/interpreter/Interpreter.scala b/info/labs/lab01/src/amyc/interpreter/Interpreter.scala new file mode 100644 index 0000000000000000000000000000000000000000..8b7dd1eb19b251d6b80086d0f1c98cbf7e06eca4 --- /dev/null +++ b/info/labs/lab01/src/amyc/interpreter/Interpreter.scala @@ -0,0 +1,171 @@ +package amyc +package interpreter + +import utils._ +import ast.SymbolicTreeModule._ +import ast.Identifier +import analyzer.SymbolTable + +// An interpreter for Amy programs, implemented in Scala +object Interpreter extends Pipeline[(Program, SymbolTable), Unit] { + + // A class that represents a value computed by interpreting an expression + abstract class Value { + def asInt: Int = this.asInstanceOf[IntValue].i + def asBoolean: Boolean = this.asInstanceOf[BooleanValue].b + def asString: String = this.asInstanceOf[StringValue].s + + override def toString: String = this match { + case IntValue(i) => i.toString + case BooleanValue(b) => b.toString + case StringValue(s) => s + case UnitValue => "()" + case CaseClassValue(constructor, args) => + constructor.name + "(" + args.map(_.toString).mkString(", ") + ")" + } + } + case class IntValue(i: Int) extends Value + case class BooleanValue(b: Boolean) extends Value + case class StringValue(s: String) extends Value + case object UnitValue extends Value + case class CaseClassValue(constructor: Identifier, args: List[Value]) extends Value + + def run(ctx: Context)(v: (Program, SymbolTable)): Unit = { + val (program, table) = v + + // These built-in functions do not have an Amy implementation in the program, + // instead their implementation is encoded in this map + val builtIns: Map[(String, String), (List[Value]) => Value] = Map( + ("Std", "printInt") -> { args => println(args.head.asInt); UnitValue }, + ("Std", "printString") -> { args => println(args.head.asString); UnitValue }, + ("Std", "readString") -> { args => StringValue(scala.io.StdIn.readLine()) }, + ("Std", "readInt") -> { args => + val input = scala.io.StdIn.readLine() + try { + IntValue(input.toInt) + } catch { + case ne: NumberFormatException => + ctx.reporter.fatal(s"""Could not parse "$input" to Int""") + } + }, + ("Std", "intToString") -> { args => StringValue(args.head.asInt.toString) }, + ("Std", "digitToString") -> { args => StringValue(args.head.asInt.toString) } + ) + + // Utility functions to interface with the symbol table. + def isConstructor(name: Identifier) = table.getConstructor(name).isDefined + def findFunctionOwner(functionName: Identifier) = table.getFunction(functionName).get.owner.name + def findFunction(owner: String, name: String) = { + program.modules.find(_.name.name == owner).get.defs.collectFirst { + case fd@FunDef(fn, _, _, _) if fn.name == name => fd + }.get + } + + // Interprets a function, using evaluations for local variables contained in 'locals' + + // TODO: Complete all missing cases. Look at the given ones for guidance. + def interpret(expr: Expr)(implicit locals: Map[Identifier, Value]): Value = { + expr match { + case Variable(name) => + ??? + case IntLiteral(i) => + ??? + case BooleanLiteral(b) => + ??? + case StringLiteral(s) => + ??? + case UnitLiteral() => + ??? + case Plus(lhs, rhs) => + IntValue(interpret(lhs).asInt + interpret(rhs).asInt) + case Minus(lhs, rhs) => + ??? + case Times(lhs, rhs) => + ??? + case Div(lhs, rhs) => + ??? + case Mod(lhs, rhs) => + ??? + case LessThan(lhs, rhs) => + ??? + case LessEquals(lhs, rhs) => + ??? + case And(lhs, rhs) => + ??? + case Or(lhs, rhs) => + ??? + case Equals(lhs, rhs) => + ??? // Hint: Take care to implement Amy equality semantics + case Concat(lhs, rhs) => + ??? + case Not(e) => + ??? + case Neg(e) => + ??? + case Call(qname, args) => + ??? + // Hint: Check if it is a call to a constructor first, + // then if it is a built-in function (otherwise it is a normal function). + // Use the helper methods provided above to retrieve information from the symbol table. + // Think how locals should be modified. + case Sequence(e1, e2) => + ??? + case Let(df, value, body) => + ??? + case Ite(cond, thenn, elze) => + ??? + case Match(scrut, cases) => + ??? + // Hint: We give you a skeleton to implement pattern matching + // and the main body of the implementation + val evS = interpret(scrut) + + // None = pattern does not match + // Returns a list of pairs id -> value, + // where id has been bound to value within the pattern. + // Returns None when the pattern fails to match. + // Note: Only works on well typed patterns (which have been ensured by the type checker). + def matchesPattern(v: Value, pat: Pattern): Option[List[(Identifier, Value)]] = { + ((v, pat): @unchecked) match { + case (_, WildcardPattern()) => + ??? + case (_, IdPattern(name)) => + ??? + case (IntValue(i1), LiteralPattern(IntLiteral(i2))) => + ??? + case (BooleanValue(b1), LiteralPattern(BooleanLiteral(b2))) => + ??? + case (StringValue(_), LiteralPattern(StringLiteral(_))) => + ??? + case (UnitValue, LiteralPattern(UnitLiteral())) => + ??? + case (CaseClassValue(con1, realArgs), CaseClassPattern(con2, formalArgs)) => + ??? + } + } + // Main "loop" of the implementation: Go through every case, + // check if the pattern matches, and if so return the evaluation of the case expression + cases.to(LazyList).map(matchCase => + val MatchCase(pat, rhs) = matchCase + (rhs, matchesPattern(evS, pat)) + ).find(_._2.isDefined) match { + case Some((rhs, Some(moreLocals))) => + interpret(rhs)(locals ++ moreLocals) + case _ => + // No case matched + ctx.reporter.fatal(s"Match error: ${evS.toString}@${scrut.position}") + } + + case Error(msg) => + ??? + } + } + + for { + m <- program.modules + e <- m.optExpr + } { + interpret(e)(Map()) + } + } +} diff --git a/info/labs/lab01/test/resources/execution/failing/BasicError.grading.amy b/info/labs/lab01/test/resources/execution/failing/BasicError.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..7e894b03f85d4e12598b088f6559c411aa986aa9 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/failing/BasicError.grading.amy @@ -0,0 +1,8 @@ +object BasicError + + def throwError(msg: String): Unit = { + error(msg) + } + + throwError("basic error test") +end BasicError diff --git a/info/labs/lab01/test/resources/execution/failing/Division.grading.amy b/info/labs/lab01/test/resources/execution/failing/Division.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..a87940e7572b822c189b8481b44d1d188b5fda8b --- /dev/null +++ b/info/labs/lab01/test/resources/execution/failing/Division.grading.amy @@ -0,0 +1,3 @@ +object Division + 1/0 +end Division diff --git a/info/labs/lab01/test/resources/execution/failing/Locals.grading.amy b/info/labs/lab01/test/resources/execution/failing/Locals.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..f7524adb7cae914b7b7025cd4f7ce1ccd14ea42a --- /dev/null +++ b/info/labs/lab01/test/resources/execution/failing/Locals.grading.amy @@ -0,0 +1,9 @@ +object Locals + def foo(i: Int(32)): Int(32) = { + val i: Int(32) = 0; + if (i == 0) { error("") } + else { 0 } + } + + foo(1) +end Locals diff --git a/info/labs/lab01/test/resources/execution/failing/MatchError1.grading.amy b/info/labs/lab01/test/resources/execution/failing/MatchError1.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..fe9ec299882412c6ba04d5ca0036ff5907e9e0a4 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/failing/MatchError1.grading.amy @@ -0,0 +1,7 @@ +object MatchError + abstract class Foo + case class Bar() extends Foo + case class Baz() extends Foo + + Bar() match { case Baz() => () } +end MatchError diff --git a/info/labs/lab01/test/resources/execution/failing/MatchError2.grading.amy b/info/labs/lab01/test/resources/execution/failing/MatchError2.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..01b173207fb7bed0e13f799b517dea87fde4a0e0 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/failing/MatchError2.grading.amy @@ -0,0 +1,7 @@ +object MatchError + abstract class Foo + case class Bar() extends Foo + case class Baz(i: Int(32)) extends Foo + + Baz(1) match { case Baz(2) => () } +end MatchError diff --git a/info/labs/lab01/test/resources/execution/failing/MatchError3.grading.amy b/info/labs/lab01/test/resources/execution/failing/MatchError3.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..bc671b41b4cae786e70c0897737733071a073a59 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/failing/MatchError3.grading.amy @@ -0,0 +1,7 @@ +object MatchError + abstract class Foo + case class Bar() extends Foo + case class Baz(f: Foo) extends Foo + + Baz(Bar()) match { case Baz(Baz(_)) => () } +end MatchError diff --git a/info/labs/lab01/test/resources/execution/failing/MatchError4.grading.amy b/info/labs/lab01/test/resources/execution/failing/MatchError4.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..0f7a09fb380ec6ff2cc5ed9ee0f73ba0f2370ea3 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/failing/MatchError4.grading.amy @@ -0,0 +1,6 @@ +object MatchError + abstract class Foo + case class Bar(s: String) extends Foo + + Bar("foo") match { case Bar("foo") => () } +end MatchError diff --git a/info/labs/lab01/test/resources/execution/failing/MinimalError.amy b/info/labs/lab01/test/resources/execution/failing/MinimalError.amy new file mode 100644 index 0000000000000000000000000000000000000000..2879444e4db30901ee488230faf9be9c3616a329 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/failing/MinimalError.amy @@ -0,0 +1,3 @@ +object MinimalError + error("") +end MinimalError diff --git a/info/labs/lab01/test/resources/execution/failing/ShortCircuit.grading.amy b/info/labs/lab01/test/resources/execution/failing/ShortCircuit.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..b5a3bb6c2cbe47c9fa15fd0d06c0f4648776e916 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/failing/ShortCircuit.grading.amy @@ -0,0 +1,3 @@ +object ShortCircuit + false || error("") +end ShortCircuit diff --git a/info/labs/lab01/test/resources/execution/outputs/Arithmetic.grading.txt b/info/labs/lab01/test/resources/execution/outputs/Arithmetic.grading.txt new file mode 100644 index 0000000000000000000000000000000000000000..2db01671773d36a9f0d24ad1a61f27ebd5081a57 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/outputs/Arithmetic.grading.txt @@ -0,0 +1,8 @@ +0 +1 +1024 +27 +10 +1 +2 +222 diff --git a/info/labs/lab01/test/resources/execution/outputs/BasicArithmetic.grading.txt b/info/labs/lab01/test/resources/execution/outputs/BasicArithmetic.grading.txt new file mode 100644 index 0000000000000000000000000000000000000000..222625d9305dc3e720f6061c576560aa6e758136 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/outputs/BasicArithmetic.grading.txt @@ -0,0 +1,13 @@ +3 +6 +4 +8 +2 +1 +0 +0 +-1 +-1 +1 +-1 +test finished \ No newline at end of file diff --git a/info/labs/lab01/test/resources/execution/outputs/BasicBranching.grading.txt b/info/labs/lab01/test/resources/execution/outputs/BasicBranching.grading.txt new file mode 100644 index 0000000000000000000000000000000000000000..0c5da7797264a3fa701f5259b4f2f4c372f4212b --- /dev/null +++ b/info/labs/lab01/test/resources/execution/outputs/BasicBranching.grading.txt @@ -0,0 +1,5 @@ +correct +not correct +not correct +correct +test finished \ No newline at end of file diff --git a/info/labs/lab01/test/resources/execution/outputs/BasicConditions.grading.txt b/info/labs/lab01/test/resources/execution/outputs/BasicConditions.grading.txt new file mode 100644 index 0000000000000000000000000000000000000000..281082f3ceacc334de5d676f65481d75c1af9646 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/outputs/BasicConditions.grading.txt @@ -0,0 +1 @@ +test finished \ No newline at end of file diff --git a/info/labs/lab01/test/resources/execution/outputs/BasicPatternMatching.grading.txt b/info/labs/lab01/test/resources/execution/outputs/BasicPatternMatching.grading.txt new file mode 100644 index 0000000000000000000000000000000000000000..c614373e1af587648a64c9551644609babd12c19 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/outputs/BasicPatternMatching.grading.txt @@ -0,0 +1,9 @@ +42 +42 +2 +3 +prim +prim +not prim +not prim +test finished \ No newline at end of file diff --git a/info/labs/lab01/test/resources/execution/outputs/EmptyObject.txt b/info/labs/lab01/test/resources/execution/outputs/EmptyObject.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/info/labs/lab01/test/resources/execution/outputs/Equality.grading.txt b/info/labs/lab01/test/resources/execution/outputs/Equality.grading.txt new file mode 100644 index 0000000000000000000000000000000000000000..7505cdd12a2cc0c8a5945afb40005fc1583a67b2 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/outputs/Equality.grading.txt @@ -0,0 +1,12 @@ +true +false +true +false +true +false +false +true +false +false +true +true diff --git a/info/labs/lab01/test/resources/execution/outputs/Factorial.grading.txt b/info/labs/lab01/test/resources/execution/outputs/Factorial.grading.txt new file mode 100644 index 0000000000000000000000000000000000000000..02a20d609d67e7ccded33de3e1f88ec2ad3110d1 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/outputs/Factorial.grading.txt @@ -0,0 +1,2 @@ +5! = 120 +10! = 3628800 diff --git a/info/labs/lab01/test/resources/execution/outputs/FunCallEnv.grading.txt b/info/labs/lab01/test/resources/execution/outputs/FunCallEnv.grading.txt new file mode 100644 index 0000000000000000000000000000000000000000..c3e370b75a2b596bb5792690ada560b400e0210d --- /dev/null +++ b/info/labs/lab01/test/resources/execution/outputs/FunCallEnv.grading.txt @@ -0,0 +1,7 @@ +3 +8 +2 +4 +1 +2 + diff --git a/info/labs/lab01/test/resources/execution/outputs/IntInputToOutput.grading.txt b/info/labs/lab01/test/resources/execution/outputs/IntInputToOutput.grading.txt new file mode 100644 index 0000000000000000000000000000000000000000..f70d7bba4ae1f07682e0358bd7a2068094fc023b --- /dev/null +++ b/info/labs/lab01/test/resources/execution/outputs/IntInputToOutput.grading.txt @@ -0,0 +1 @@ +42 \ No newline at end of file diff --git a/info/labs/lab01/test/resources/execution/outputs/Match4.grading.txt b/info/labs/lab01/test/resources/execution/outputs/Match4.grading.txt new file mode 100644 index 0000000000000000000000000000000000000000..12cd139c8c6be5da944d7ecbd25cfef436e473e1 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/outputs/Match4.grading.txt @@ -0,0 +1,2 @@ +This side effect should only happen once! +yes diff --git a/info/labs/lab01/test/resources/execution/outputs/MixStdAndNonStd.grading.txt b/info/labs/lab01/test/resources/execution/outputs/MixStdAndNonStd.grading.txt new file mode 100644 index 0000000000000000000000000000000000000000..ea91e50883cad8e2cc0981b3c46897e9665b2bbe --- /dev/null +++ b/info/labs/lab01/test/resources/execution/outputs/MixStdAndNonStd.grading.txt @@ -0,0 +1,2 @@ +42 +42 \ No newline at end of file diff --git a/info/labs/lab01/test/resources/execution/outputs/SideEffect.grading.txt b/info/labs/lab01/test/resources/execution/outputs/SideEffect.grading.txt new file mode 100644 index 0000000000000000000000000000000000000000..da1b439e66b24743c17584005e421bec464bfd4a --- /dev/null +++ b/info/labs/lab01/test/resources/execution/outputs/SideEffect.grading.txt @@ -0,0 +1,20 @@ +1 +2 +1 +2 +1 +2 +1 +2 +1 +2 +1 +2 +1 +2 +Left +Right +SLeft +SRight +CLeft +CRight \ No newline at end of file diff --git a/info/labs/lab01/test/resources/execution/outputs/StringInputToOutput.grading.txt b/info/labs/lab01/test/resources/execution/outputs/StringInputToOutput.grading.txt new file mode 100644 index 0000000000000000000000000000000000000000..49759043451ceb5342f3e5c5f665c943c3cd89c2 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/outputs/StringInputToOutput.grading.txt @@ -0,0 +1,2 @@ +Hello World! +Hello World again! \ No newline at end of file diff --git a/info/labs/lab01/test/resources/execution/outputs/TestLists.grading.txt b/info/labs/lab01/test/resources/execution/outputs/TestLists.grading.txt new file mode 100644 index 0000000000000000000000000000000000000000..7d4d10a4d0349f900eb0c7bd1fe47272f99f7a98 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/outputs/TestLists.grading.txt @@ -0,0 +1,2 @@ +9 +List(-5, -1, 0, 5, 10) diff --git a/info/labs/lab01/test/resources/execution/passing/Arithmetic.grading.amy b/info/labs/lab01/test/resources/execution/passing/Arithmetic.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..130e57c2eb64b5c3293ad8a8df667244f21c0f56 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/Arithmetic.grading.amy @@ -0,0 +1,34 @@ +object Arithmetic + def pow(b: Int(32), e: Int(32)): Int(32) = { + if (e == 0) { 1 } + else { + if (e % 2 == 0) { + val rec: Int(32) = pow(b, e/2); + rec * rec + } else { + b * pow(b, e - 1) + } + } + } + + def gcd(a: Int(32), b: Int(32)): Int(32) = { + 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)); // 1 + Std.printInt(gcd(16, 46)); // 2 + Std.printInt(gcd(222, 888)) // 222 +end Arithmetic \ No newline at end of file diff --git a/info/labs/lab01/test/resources/execution/passing/BasicArithmetic.grading.amy b/info/labs/lab01/test/resources/execution/passing/BasicArithmetic.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..00729fe928848327696004c6301926468a031b5b --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/BasicArithmetic.grading.amy @@ -0,0 +1,42 @@ +object BasicArithmetic + + def plus(a: Int(32), b: Int(32)): Int(32) = { + a + b + } + + def minus(a: Int(32), b: Int(32)): Int(32) = { + a - b + } + + def mul(a: Int(32), b: Int(32)): Int(32) = { + a * b + } + + def mod(a: Int(32), b: Int(32)): Int(32) = { + a % b + } + + def div(a: Int(32), b: Int(32)): Int(32) = { + a / b + } + + val a: Int(32) = 1; + val b: Int(32) = 2; + + Std.printString(Std.intToString(plus(a, b))); + Std.printString(Std.intToString(mul(plus(a, b), b))); + Std.printString(Std.intToString(minus(mul(plus(a, b), b), b))); + Std.printString(Std.intToString(mul(minus(mul(plus(a, b), b), b), b))); + + Std.printString(Std.intToString(div(4, 2))); + Std.printString(Std.intToString(div(3, 2))); + Std.printString(Std.intToString(div(1, 2))); + Std.printString(Std.intToString(div(-1, 2))); + Std.printString(Std.intToString(div(-2, 2))); + Std.printString(Std.intToString(div(-3, 2))); + + Std.printString(Std.intToString(mod(5, 2))); + Std.printString(Std.intToString(mod(-5, 2))); + + Std.printString("test finished") +end BasicArithmetic diff --git a/info/labs/lab01/test/resources/execution/passing/BasicBranching.grading.amy b/info/labs/lab01/test/resources/execution/passing/BasicBranching.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..1549699e8fa76002fafd1e3c8f9b9e195a329e54 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/BasicBranching.grading.amy @@ -0,0 +1,82 @@ +object BasicBranching + + def less(a: Int(32), b: Int(32)): Boolean = { + a < b + } + + def lessOrEqual(a: Int(32), b: Int(32)): Boolean = { + a <= b + } + + def test1(): Unit = { + val a: Int(32) = 1; + val b: Int(32) = a + 1; + if(less(a, b)) { + Std.printString("correct") + } else { + Std.printString("not correct") + } + } + + def test2(): Unit = { + val a: Int(32) = 1; + val b: Int(32) = a + 1; + if(less(a, 1)) { + Std.printString("correct") + } else { + Std.printString("not correct") + } + } + + def test3(): Unit = { + val a: Int(32) = 1; + val b: Int(32) = a + 1; + if(less(b, a)) { + Std.printString("correct") + } else { + Std.printString("not correct") + } + } + + def test4(): Unit = { + val a: Int(32) = 1; + val b: Int(32) = a + 1; + if(lessOrEqual(a, a)) { + Std.printString("correct") + } else { + Std.printString("not correct") + } + } + + def test(): Unit = { + val a: Int(32) = 1; + val b: Int(32) = a + 1; + if(less(a, b)) { + Std.printString("correct") + } else { + Std.printString("not correct") + }; + + if(less(a, 1)) { + Std.printString("correct") + } else { + Std.printString("not correct") + }; + + if(less(b, a)) { + Std.printString("correct") + } else { + Std.printString("not correct") + }; + + if(lessOrEqual(a, a)) { + Std.printString("correct") + } else { + Std.printString("not correct") + } + } + + test(); + + Std.printString("test finished") +end BasicBranching diff --git a/info/labs/lab01/test/resources/execution/passing/BasicConditions.grading.amy b/info/labs/lab01/test/resources/execution/passing/BasicConditions.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..4aa8d317f5b2e2be14baaaac469848c4f519b0c8 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/BasicConditions.grading.amy @@ -0,0 +1,12 @@ +object BasicConditions + + def neverTrigger(): Boolean = { + Std.printString("should not happen"); + true + } + + val c1: Boolean = true || neverTrigger(); + val c2: Boolean = false && neverTrigger(); + + Std.printString("test finished") +end BasicConditions \ No newline at end of file diff --git a/info/labs/lab01/test/resources/execution/passing/BasicPatternMatching.grading.amy b/info/labs/lab01/test/resources/execution/passing/BasicPatternMatching.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..bb43e69d2b5db3be240b316175d12cdae4704e98 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/BasicPatternMatching.grading.amy @@ -0,0 +1,51 @@ +object BasicPatternMatching + abstract class Letter + case class A() extends Letter + case class APrim(i: Int(32)) extends Letter + case class B() extends Letter + case class BPrim(i: Int(32)) extends Letter + + def isRegularLetter(l: Letter): Boolean = { + l match { + case A() => true + case B() => true + case APrim(_) => false + case BPrim(_) => false + } + } + + def getPrimNumber(l: Letter): Int(32) = { + !isRegularLetter(l) match { + case false => 42 + case true => l match { + case APrim(i) => i + case BPrim(i) => i + } + } + } + + def isPrimLetter(l: Letter): String = { + getPrimNumber(l) match { + case 42 => "not prim" + case _ => "prim" + } + } + + + val a: Letter = A(); + val ap: Letter = APrim(2); + val b: Letter = B(); + val bp: Letter = BPrim(3); + + Std.printString(Std.intToString(getPrimNumber(a))); + Std.printString(Std.intToString(getPrimNumber(b))); + Std.printString(Std.intToString(getPrimNumber(ap))); + Std.printString(Std.intToString(getPrimNumber(bp))); + + Std.printString(isPrimLetter(ap)); + Std.printString(isPrimLetter(bp)); + Std.printString(isPrimLetter(a)); + Std.printString(isPrimLetter(b)); + + Std.printString("test finished") +end BasicPatternMatching \ No newline at end of file diff --git a/info/labs/lab01/test/resources/execution/passing/EmptyObject.amy b/info/labs/lab01/test/resources/execution/passing/EmptyObject.amy new file mode 100644 index 0000000000000000000000000000000000000000..0db1c211448d42213a76d40774a50d6e78c6b447 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/EmptyObject.amy @@ -0,0 +1,3 @@ +object EmptyObject + +end EmptyObject \ No newline at end of file diff --git a/info/labs/lab01/test/resources/execution/passing/Equality.grading.amy b/info/labs/lab01/test/resources/execution/passing/Equality.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..2463cc6905490293819e3a4016518944570534ee --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/Equality.grading.amy @@ -0,0 +1,18 @@ +object Equality + abstract class Foo + case class Foo1() extends Foo + case class Foo2() extends Foo + + Std.printString(Std.booleanToString(0 == 0)); + Std.printString(Std.booleanToString(0 == 1)); + Std.printString(Std.booleanToString(true == true)); + Std.printString(Std.booleanToString(true == false)); + Std.printString(Std.booleanToString(() == ())); + Std.printString(Std.booleanToString("hello" == "hello")); + Std.printString(Std.booleanToString("hello" == "hel" ++ "lo")); + Std.printString(Std.booleanToString(val h: String = "hello"; h == h)); + Std.printString(Std.booleanToString(Foo1() == Foo1())); + Std.printString(Std.booleanToString(Foo1() == Foo2())); + Std.printString(Std.booleanToString(val f: Foo = Foo1(); f == f)); + Std.printString(Std.booleanToString(val f: Foo = Foo1(); val g: Foo = f; f == g)) +end Equality diff --git a/info/labs/lab01/test/resources/execution/passing/Factorial.grading.amy b/info/labs/lab01/test/resources/execution/passing/Factorial.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..de991f6638dff1f29ad0e7de8d788e3f68735d7a --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/Factorial.grading.amy @@ -0,0 +1,9 @@ +object Factorial + def fact(i: Int(32)): Int(32) = { + if (i < 2) { 1 } + else { i * fact(i-1) } + } + + Std.printString("5! = " ++ Std.intToString(fact(5))); + Std.printString("10! = " ++ Std.intToString(fact(10))) +end Factorial diff --git a/info/labs/lab01/test/resources/execution/passing/FunCallEnv.grading.amy b/info/labs/lab01/test/resources/execution/passing/FunCallEnv.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..46b9df8ae51ccb1ec5bfebd900c5c0930617cd86 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/FunCallEnv.grading.amy @@ -0,0 +1,12 @@ +object FunCallEnv + def printRec(i: Int(32), j: Int(32)): Unit = { + if (i <= 0) { () } else { + Std.printInt(i); + Std.printInt(j); + printRec(i - 1, j / 2) + } + } + + printRec(3, 8) + +end FunCallEnv diff --git a/info/labs/lab01/test/resources/execution/passing/IntInputToOutput.grading.amy b/info/labs/lab01/test/resources/execution/passing/IntInputToOutput.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..6347c501aa7c8c1729d3752a243325853cda9a47 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/IntInputToOutput.grading.amy @@ -0,0 +1,4 @@ +object IntInputToOutput + val intInput: Int(32) = Std.readInt(); + Std.printString(Std.intToString(intInput)) +end IntInputToOutput \ No newline at end of file diff --git a/info/labs/lab01/test/resources/execution/passing/List.amy b/info/labs/lab01/test/resources/execution/passing/List.amy new file mode 100644 index 0000000000000000000000000000000000000000..59ccc31c0664ab0fed3c801ff871e57a292e0ccd --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/List.amy @@ -0,0 +1,144 @@ +object L + abstract class List + case class Nil() extends List + case class Cons(h: Int(32), t: List) extends List + + def isEmpty(l : List): Boolean = { l match { + case Nil() => true + case _ => false + }} + + def length(l: List): Int(32) = { l match { + case Nil() => 0 + case Cons(_, t) => 1 + length(t) + }} + + def head(l: List): Int(32) = { + 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(32)): Int(32) = { + l match { + case Nil() => -1 + case Cons(h, t) => + if (h == i) { 0 } + else { + val rec: Int(32) = indexOf(t, i); + if (0 <= rec) { rec + 1 } + else { -1 } + } + } + } + + def range(from: Int(32), to: Int(32)): List = { + if (to < from) { Nil() } + else { + Cons(from, range(from + 1, to)) + } + } + + def sum(l: List): Int(32) = { 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(l1, l2)) + } + } + + def contains(l: List, elem: Int(32)): 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(32)): List = { + if (n <= 0) { Nil() } + else { + l match { + case Nil() => Nil() + case Cons(h, t) => + Cons(h, take(t, n-1)) + } + } + } + +end L diff --git a/info/labs/lab01/test/resources/execution/passing/Locals.grading.amy b/info/labs/lab01/test/resources/execution/passing/Locals.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..87d94619ec297dc51cd1fe7323c0afa77ca3b3e1 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/Locals.grading.amy @@ -0,0 +1,9 @@ +object Locals + def foo(i: Int(32)): Int(32) = { + val i: Int(32) = 0; + if (i == 1) { error("") } + else { 0 } + } + + foo(1) +end Locals diff --git a/info/labs/lab01/test/resources/execution/passing/Match1.grading.amy b/info/labs/lab01/test/resources/execution/passing/Match1.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..42b2324a96ceb8ec24d480314d60a15d2b9f7362 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/Match1.grading.amy @@ -0,0 +1,10 @@ +object MatchError + abstract class Foo + case class Bar(i: Int(32)) extends Foo + case class Baz(f1: Foo, f2: Foo) extends Foo + + Baz(Baz(Baz(Bar(1), Bar(2)), Bar(3)), Bar(4)) match { + case Baz(Baz(Baz(Bar(1), Bar(2)), Bar(3)), Bar(4)) => + () + } +end MatchError diff --git a/info/labs/lab01/test/resources/execution/passing/Match2.grading.amy b/info/labs/lab01/test/resources/execution/passing/Match2.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..ab57c8f0edf0729e455976c9763a7b185db3ef51 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/Match2.grading.amy @@ -0,0 +1,6 @@ +object MatchError + abstract class Foo + case class Bar(i: Int(32), b: Boolean, u: Unit) extends Foo + + Bar(10, true, ()) match { case Bar(10, true, ()) => () } +end MatchError diff --git a/info/labs/lab01/test/resources/execution/passing/Match3.grading.amy b/info/labs/lab01/test/resources/execution/passing/Match3.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..52e3351cd9f65158fa64a2adf8ccdb6fc538f276 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/Match3.grading.amy @@ -0,0 +1,9 @@ +object MatchError + abstract class Foo + case class Bar(i: Int(32)) extends Foo + case class Baz(f1: Foo, f2: Foo) extends Foo + + Baz(Baz(Bar(1), Bar(2)), Baz(Bar(3), Bar(4))) match { + case Baz(Baz(_, Bar(_)), _) => () + } +end MatchError diff --git a/info/labs/lab01/test/resources/execution/passing/Match4.grading.amy b/info/labs/lab01/test/resources/execution/passing/Match4.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..7160aa07a2b8b6a7eb910bb8d48cc07156f21716 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/Match4.grading.amy @@ -0,0 +1,12 @@ +object Match4 + def sideEffect(): Int(32) = { + Std.printString("This side effect should only happen once!"); + 10 + } + + sideEffect() match { + case 1 => Std.printString("no"); false + case 2 => Std.printString("no"); false + case 10 => Std.printString("yes"); true + } +end Match4 diff --git a/info/labs/lab01/test/resources/execution/passing/MixStdAndNonStd.grading.amy b/info/labs/lab01/test/resources/execution/passing/MixStdAndNonStd.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..75f6cc5d13fb75ad3ab7527316c40c7012b70aec --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/MixStdAndNonStd.grading.amy @@ -0,0 +1,9 @@ +object MixStdAndNonStd + def printString(str: String): Unit = { + Std.printString(str); + Std.printString(str) + } + + val intInput: Int(32) = Std.readInt(); + printString(Std.intToString(intInput)) +end MixStdAndNonStd \ No newline at end of file diff --git a/info/labs/lab01/test/resources/execution/passing/Option.amy b/info/labs/lab01/test/resources/execution/passing/Option.amy new file mode 100644 index 0000000000000000000000000000000000000000..640a733353c506f74db663a638b6a9fa56dd3341 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/Option.amy @@ -0,0 +1,40 @@ +object O + abstract class Option + case class None() extends Option + case class Some(v: Int(32)) extends Option + + def isDefined(o: Option): Boolean = { + o match { + case None() => false + case _ => true + } + } + + def get(o: Option): Int(32) = { + o match { + case Some(i) => i + case None() => error("get(None)") + } + } + + def getOrElse(o: Option, i: Int(32)): Int(32) = { + 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() + } + } +end O diff --git a/info/labs/lab01/test/resources/execution/passing/ShortCircuit.grading.amy b/info/labs/lab01/test/resources/execution/passing/ShortCircuit.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..a1fd42ba658915f07e691973d0411f8d9d0bab1e --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/ShortCircuit.grading.amy @@ -0,0 +1,4 @@ +object ShortCircuit + true || error(""); + false && error("") +end ShortCircuit diff --git a/info/labs/lab01/test/resources/execution/passing/SideEffect.grading.amy b/info/labs/lab01/test/resources/execution/passing/SideEffect.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..c1bc5df75faa4c11f03719a923dce7bf298b668c --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/SideEffect.grading.amy @@ -0,0 +1,57 @@ +object SideEffect + + def firstI(): Int(32) = { + Std.printInt(1); + 1 + } + + def secondI(): Int(32) = { + Std.printInt(2); + 2 + } + + def stringLeft(): String = { + Std.printString("SLeft"); + "a" + } + + def stringRight(): String = { + Std.printString("SRight"); + "a" + } + + def caseLeft(): L.List = { + Std.printString("CLeft"); + L.Nil() + } + + def caseRight(): L.List = { + Std.printString("CRight"); + L.Nil() + } + + // Make sure that operands to binary operations are interpreted only once + // and in the right order + firstI() + secondI(); + firstI() - secondI(); + firstI() * secondI(); + firstI() / secondI(); + firstI() % secondI(); + + firstI() < secondI(); + firstI() <= secondI(); + + // Make sure that the rhs of comparisons to Unit (which always succeed) + // are interpreted. + Std.printString("Left") == Std.printString("Right"); + + // Make sure that string comparisons evaluate their arguments once + // and in the correct order + stringLeft() == stringRight(); + + // Make sure that case class comparisons evaluate their arguments once + // and in the correct order + caseLeft() == caseRight(); + () + +end SideEffect \ No newline at end of file diff --git a/info/labs/lab01/test/resources/execution/passing/Std.amy b/info/labs/lab01/test/resources/execution/passing/Std.amy new file mode 100644 index 0000000000000000000000000000000000000000..0fa08b7f752db65ab2a54b0f590a415a06c90010 --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/Std.amy @@ -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(32)): 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(32) = { + error("") // Stub implementation + } + + def intToString(i: Int(32)): String = { + if (i < 0) { + "-" ++ intToString(-i) + } else { + val rem: Int(32) = i % 10; + val div: Int(32) = i / 10; + if (div == 0) { digitToString(rem) } + else { intToString(div) ++ digitToString(rem) } + } + } + def digitToString(i: Int(32)): String = { + error("") // Stub implementation + } + def booleanToString(b: Boolean): String = { + if (b) { "true" } else { "false" } + } +end Std diff --git a/info/labs/lab01/test/resources/execution/passing/StringInputToOutput.grading.amy b/info/labs/lab01/test/resources/execution/passing/StringInputToOutput.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..05ef541e977e33e88886f3d8182a392c4d5a203a --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/StringInputToOutput.grading.amy @@ -0,0 +1,7 @@ +object StringInputToOutput + val stringInput: String = Std.readString(); + val stringInput2: String = Std.readString(); + + Std.printString(stringInput ++ "!"); + Std.printString(stringInput2 ++ "!") +end StringInputToOutput \ No newline at end of file diff --git a/info/labs/lab01/test/resources/execution/passing/TestLists.grading.amy b/info/labs/lab01/test/resources/execution/passing/TestLists.grading.amy new file mode 100644 index 0000000000000000000000000000000000000000..b9cae33f73e3385bad054c7bba7c83117defcb2e --- /dev/null +++ b/info/labs/lab01/test/resources/execution/passing/TestLists.grading.amy @@ -0,0 +1,5 @@ +object TestLists + val l: L.List = L.Cons(5, L.Cons(-5, L.Cons(-1, L.Cons(0, L.Cons(10, L.Nil()))))); + Std.printInt(L.sum(l)); + Std.printString(L.toString(L.mergeSort(l))) +end TestLists diff --git a/info/labs/lab01/test/scala/amyc/test/CompilerTest.scala b/info/labs/lab01/test/scala/amyc/test/CompilerTest.scala new file mode 100644 index 0000000000000000000000000000000000000000..1024cfb7ef93b16acb940b9c150b461b19c537ab --- /dev/null +++ b/info/labs/lab01/test/scala/amyc/test/CompilerTest.scala @@ -0,0 +1,92 @@ +package amyc.test + +import amyc.utils._ +import java.io.File + +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.linesIterator.filterNot(rejectLine).mkString("\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 + } + + } + + +} diff --git a/info/labs/lab01/test/scala/amyc/test/ExecutionTests.scala b/info/labs/lab01/test/scala/amyc/test/ExecutionTests.scala new file mode 100644 index 0000000000000000000000000000000000000000..023f8377c06f42286325541d7b9b07a92f140e94 --- /dev/null +++ b/info/labs/lab01/test/scala/amyc/test/ExecutionTests.scala @@ -0,0 +1,64 @@ +package amyc.test + +import org.junit.Test + +abstract class ExecutionTests extends TestSuite { + + val baseDir = "execution" + + val outputExt = "txt" + + @Test def testEmptyObject = shouldOutput("EmptyObject") + + @Test def testStringInputToOutput = shouldOutput(List("Std", "StringInputToOutput"), "StringInputToOutput", "Hello World\nHello World again") + + @Test def testIntInputToOutput = shouldOutput(List("Std", "IntInputToOutput"), "IntInputToOutput", "42") + + @Test def testMixStdAndNonStd = shouldOutput(List("Std", "MixStdAndNonStd"), "MixStdAndNonStd", "42") + + @Test def testBasicArithmetic = shouldOutput(List("Std", "BasicArithmetic"), "BasicArithmetic", "") + + @Test def testDivisionByZero = shouldFail("Division") + + @Test def testBasicPatternMatching = shouldOutput(List("Std", "BasicPatternMatching"), "BasicPatternMatching", "") + + @Test def testBasicBranching = shouldOutput(List("Std", "BasicBranching"), "BasicBranching", "") + + @Test def testBasicConditions = shouldOutput(List("Std", "BasicConditions"), "BasicConditions", "") + + @Test def testBasicError = shouldFail("BasicError") + + @Test def testEquality = shouldOutput(List("Std", "Equality"), "Equality") + + @Test def testFactorial = shouldOutput(List("Std", "Factorial"), "Factorial", "") + + @Test def testArithmetic = shouldOutput(List("Std", "Arithmetic"), "Arithmetic", "") + + @Test def testLists = shouldOutput(List("Std", "Option", "List", "TestLists"), "TestLists", "") + + @Test def testMatchError1 = shouldFail("MatchError1") + + @Test def testMatchError2 = shouldFail("MatchError2") + + @Test def testMatchError3 = shouldFail("MatchError3") + + @Test def testMatchError4 = shouldFail("MatchError4") + + @Test def testMatch1 = shouldPass("Match1") + + @Test def testMatch2 = shouldPass("Match2") + + @Test def testMatch3 = shouldPass("Match3") + + @Test def testMatch4 = shouldOutput(List("Std", "Match4"), "Match4") + + @Test def testShortCircuit1 = shouldPass("ShortCircuit") + + @Test def testShortCircuit2 = shouldFail("ShortCircuit") + + @Test def testLocals1 = shouldPass("Locals") + + @Test def testLocals2 = shouldFail("Locals") + + @Test def testFunCallEnv = shouldOutput(List("FunCallEnv", "Std"), "FunCallEnv") +} diff --git a/info/labs/lab01/test/scala/amyc/test/InterpreterTests.scala b/info/labs/lab01/test/scala/amyc/test/InterpreterTests.scala new file mode 100644 index 0000000000000000000000000000000000000000..7ae72223bccd1bd6cca8f33836c55a61b09108d0 --- /dev/null +++ b/info/labs/lab01/test/scala/amyc/test/InterpreterTests.scala @@ -0,0 +1,11 @@ +package amyc +package test + +import parsing._ +import analyzer._ +import interpreter.Interpreter +import amyc.utils.Frontend + +class InterpreterTests extends ExecutionTests { + val pipeline = Frontend.pipeline.andThen(Interpreter) +} diff --git a/info/labs/lab01/test/scala/amyc/test/TestSuite.scala b/info/labs/lab01/test/scala/amyc/test/TestSuite.scala new file mode 100644 index 0000000000000000000000000000000000000000..3ce9ebf63be767da33d155a3b191db745a8b0109 --- /dev/null +++ b/info/labs/lab01/test/scala/amyc/test/TestSuite.scala @@ -0,0 +1,78 @@ +package amyc.test + +import amyc.utils.Pipeline +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption + +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 tmpDir = Files.createTempDirectory("amyc"); + + val outputExt: String + + def getResourcePath(relativePath: String, otherPath: Option[String] = None): String = + val firstPath = Path.of(effectiveBaseDir, relativePath) + + val (stream, path) = + if Files.exists(firstPath) then + (Files.newInputStream(firstPath), relativePath) + else + otherPath match + case Some(p) => + val secondPath = Path.of(effectiveBaseDir, p) + (Files.newInputStream(secondPath), p) + case None => + assert(false, s"can not read $effectiveBaseDir/$relativePath") + (null, "") + + val targetPath = tmpDir.resolve(path) + Files.createDirectories(targetPath.getParent()) + Files.copy(stream, targetPath, StandardCopyOption.REPLACE_EXISTING) + targetPath.toAbsolutePath().toString() + + def shouldOutput(inputFiles: List[String], outputFile: String, input: String = ""): Unit = { + compareOutputs( + pipeline, + inputFiles map (f => getResourcePath(s"$passing/$f.amy", Some(s"$passing/$f.grading.amy"))), + getResourcePath(s"$outputs/$outputFile.$outputExt", Some(s"$outputs/$outputFile.grading.$outputExt")), + input + ) + } + + def shouldOutput(inputFile: String): Unit = { + shouldOutput(List(inputFile), inputFile) + } + + def shouldFail(inputFiles: List[String], input: String = ""): Unit = { + demandFailure( + pipeline, + inputFiles map (f => getResourcePath(s"$failing/$f.amy", Some(s"$failing/$f.grading.amy"))), + input + ) + } + + def shouldFail(inputFile: String): Unit = { + shouldFail(List(inputFile)) + } + + def shouldPass(inputFiles: List[String], input: String = ""): Unit = { + demandPass(pipeline, inputFiles map (f => getResourcePath(s"$passing/$f.amy", Some(s"$passing/$f.grading.amy"))), input) + } + + def shouldPass(inputFile: String): Unit = { + shouldPass(List(inputFile)) + } + +} diff --git a/info/labs/lab01/test/scala/amyc/test/TestUtils.scala b/info/labs/lab01/test/scala/amyc/test/TestUtils.scala new file mode 100644 index 0000000000000000000000000000000000000000..6fe74a037e1b04c7fb8ec0a3dffdc920a3db5f42 --- /dev/null +++ b/info/labs/lab01/test/scala/amyc/test/TestUtils.scala @@ -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() + } +}