@@ -33,7 +33,7 @@ The skeleton of the assignment is provided by us in three files:
...
@@ -33,7 +33,7 @@ The skeleton of the assignment is provided by us in three files:
Now let's look into the code in a little more detail.
Now let's look into the code in a little more detail.
In ''Main.scala'', take a look at 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 is contains the different stages of the compiler (more on it in later assignments). 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]], and then passes the result to the Interpreter.
In ''Main.scala'', take a look at 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 (more on it in later assignments). 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](https://gitlab.epfl.ch/lara/cs320/-/blob/main/labs/amy_specification.md), and then passes the result to the Interpreter.
The implementation of the frontend is given to you in compiled form, because you will need to write your own version in the next assignments. **Note**: You are only allowed to use this binary code to link against your interpreter.
The implementation of the frontend is given to you in compiled form, because you will need to write your own version in the next assignments. **Note**: You are only allowed to use this binary code to link against your interpreter.
So what is this AST we've mentioned? For the computer to "understand" the meaning of a program, it first has to transform it from source (text) form to a more convenient form, which we call an abstract syntax tree. The AST abstracts away uninteresting things of the program (e.g. parentheses, whitespace, operator precedence...) and keeps the essential structure of the program.
So what is this AST we've mentioned? For the computer to "understand" the meaning of a program, it first has to transform it from source (text) form to a more convenient form, which we call an abstract syntax tree. The AST abstracts away uninteresting things of the program (e.g. parentheses, whitespace, operator precedence...) and keeps the essential structure of the program.
...
@@ -52,9 +52,9 @@ Note: This is not exactly the code we will use in later assignments, but it's go
...
@@ -52,9 +52,9 @@ Note: This is not exactly the code we will use in later assignments, but it's go
## The Interpreter class
## The Interpreter class
Now let's delve into ''Interpreter.scala''. This file currently only contains a partial implementation, and it is your task for 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.
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'', ''Booleans'', ''Unit'', ''String'' and ADT values). ''Value'' has convenience methods to cast to ''Int'', ''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'' 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.
''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.
...
@@ -72,13 +72,13 @@ A few final notes:
...
@@ -72,13 +72,13 @@ A few final notes:
## Implementation skeleton
## Implementation skeleton
If you have followed [[Labs Setup]] for Lab 01, you should have a working project with a stub implementation, containing the following files:
If you have followed [Labs Setup](https://gitlab.epfl.ch/lara/cs320/-/blob/main/labs/labs_setup.md) for Lab 01, you should have a working project with a stub implementation, containing the following files:
* ''src/amyc/interpreter/Interpreter.scala'' contains a partially implemented interpreter
* ''src/amyc/interpreter/Interpreter.scala'' contains a partially implemented interpreter
* ''src/amyc/Main.scala'' contains the ''main'' method which runs the interpreter on the input files
* ''src/amyc/Main.scala'' contains the ''main'' method which runs the interpreter on the input files
* The ''library'' directory contains library definitions you can call from your programs.
* The ''library'' directory contains library definitions you can call from your 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''.
* 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''.
* ''lib/amy-frontend_2.12-1.7.jar'' contains a the //frontend// of the compiler as a library, allowing you directly work with type-checked ASTs of input programs.
* ''lib/amy-frontend_2.12-1.7.jar'' contains the //frontend// of the compiler as a library, allowing you directly work with type-checked ASTs of input programs.
You will have to complete the interpreter by implementing the missing methods (marked with the placeholder ''???'').
You will have to complete the interpreter by implementing the missing methods (marked with the placeholder ''???'').
...
@@ -97,13 +97,13 @@ and the expected output under
...
@@ -97,13 +97,13 @@ and the expected output under
Then, you have to add the name of the new test in ''InterpreterTests'', similarly to the examples given.
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''.
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.scala" containing ''object Hello { Std.printString("Hello world") }'' and ''/test/resources/interpreter/outputs/Hello.txt'' containing ''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.scala'' and ''Hello.scala'' as inputs of the test. When you now run ''test'' from sbt, you should see the additional test case (called ''testHello'').
For example, to add a test that expects only "Hello world" to be printed, you can add "/test/resources/interpreter/passing/Hello.scala" containing ''object Hello Std.printString("Hello world") end Hello'' and ''/test/resources/interpreter/outputs/Hello.txt'' containing ''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.scala'' and ''Hello.scala'' as inputs of the test. When you now run ''test'' from sbt, you should see the additional test case (called ''testHello'').