Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • shchen/cs320
  • raveendr/cs320
  • mwojnaro/cs320
3 results
Show changes
Showing
with 829 additions and 40 deletions
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
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
# Lab 02: Lexer ([Slides](slides/lab02.pdf))
# Lab 02: Lexer
This assignment is the first stage of the Amy compiler.
## Code Scaffold
## Code Skeleton
In this lab you will start your own compiler from scratch, meaning that you will no longer rely on the compiler frontend which was previously provided to you as a jar file. In this lab you will build the lexical analysis phase (`lexer`).
In this lab you will start your own compiler from scratch, meaning that you will no longer rely on the compiler frontend which was previously provided to you as a jar file. In this lab you will build the lexical analysis phase (`lexer`). Since practically none of the compiler's code will be shared with the previous lab, the new branch (lab02) contains a fresh skeleton.
As we are now starting to work on your own compiler, we will start with a fresh scaffold. We suggest to keep the interpreter alongside, and we will tell you at what point you can add back your interpreter in your project.
For now, you should work in this new project that will become your full compiler. Following labs will be delivered as files to add to this project.
The structure of your project `src` directory should be as follows:
```text
lib
└── scallion-assembly-0.6.1.jar
library
├── ...
└── ...
examples
├── ...
└── ...
Compared to the previous lab, the structure of your src directory will be as follows:
```
amyc
├── Main.scala (updated)
├── lib
│ ├── amy-frontend_2.12-1.7.jar (removed)
│ └── silex_2.12-0.5.jar (new)
├── Main.scala
├── parsing (new)
├── parsing
│ ├── Lexer.scala
│ └── Tokens.scala
└── utils (new)
└── utils
├── AmycFatalError.scala
├── Context.scala
├── Document.scala
......@@ -30,36 +41,49 @@ amyc
├── Reporter.scala
└── UniqueCounter.scala
test
├── scala
│ └── amyc
│ └── test
│ ├── CompilerTest.scala
│ ├── LexerTests.scala
│ ├── TestSuite.scala
│ └── TestUtils.scala
└── resources
└── lexer
└── ...
```
This lab will focus on the following two files:
* `src/amyc/parsing/Tokens.scala`: list of tokens and token kinds.
* `src/amyc/parsing/Lexer.scala`: skeleton for the `Lexer` phase.
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. If you haven't looked at the [Labs Setup](https://gitlab.epfl.ch/lara/cs320/-/blob/main/labs/labs_setup.md) page yet, please do so before starting out with the assignment.
Below you will find the instructions for the first lab assignment in which you will get to know and implement an lexer for the Amy language.
## A Lexer for Amy
The role of a lexer is to read the input text and convert it to a list of tokens. Tokens are the smallest useful units in a source file: a name referring to a variable, a bracket, a keyword etc. The role of the lexer is to group together those useful units (e.g. return the keyword else as a unit, as opposed to individual characters e, l, s, e) and to abstract away all useless information (i.e. whitespace, comments).
The role of a lexer is to read the input text as a string and convert it to a list of tokens. Tokens are the smallest useful units in a source file: a name referring to a variable, a bracket, a keyword etc. The role of the lexer is to group together those useful units (e.g. return the keyword else as a unit, as opposed to individual characters e, l, s, e) and to abstract away all useless information (i.e. whitespace, comments).
## Code structure
You can find the `lexer` in the `Lexer.scala` file. It is based on Scallion and Silex, a pair of Scala libraries which simplify the implementation of parsing pipelines. Silex allows you to transform an input character stream (such as the contents of an Amy source file) into a sequence of Tokens. We are going to take a closer look at Scallion in the next lab, where our goal will be to build Amy's parser. You can find more information on Scallion and Silex [here](https://github.com/epfl-lara/scallion), but we also included a short reference of Silex's API in `Lexer.scala`.
You can find the `lexer` in the `Lexer.scala` file. It is based on Scallion and Silex, a pair of Scala libraries which simplify the implementation of parsing pipelines. Silex allows you to transform an input character stream (such as the contents of an Amy source file) into a sequence of Tokens. We are going to take a closer look at Scallion in the next lab, where our goal will be to build Amy's parser. You can find more information on Scallion [here](https://github.com/epfl-lara/scallion) and Silex [here](https://github.com/epfl-lara/silex), but we also included a short reference of Silex's API in `Lexer.scala`.
The Lexer has the following components:
The `Lexer` has the following components:
* The public method is `run`. It just calls `lexer.spawn`(`source`) for every input file and concatenates the results.
* `lexer` is the Silex-based definition of tokenization rules. Each rule corresponds to a regular expression matching a prefix of the remaining program input. Silex will compose all of these rules into one finite state machine and apply the maximum-munch rule you've seen in class.
* Whenever a rule is found to match a (maximal) prefix of the remaining input, Scallion will call the transformation function provided using the |> operator in the rule. This function is given the matched input characters (cs) along with positional information (range) and should then produce an instance of Token. You can find its definition in `Tokens.scala`, which includes a list of all the different kinds of tokens that your Amy compiler should process. For instance, KeywordToken(`if`) represents an occurence of the reserved word if in a program.
For more details on how to write new rules, read the short introduction to Silex's API at the top of `Lexer.scala` or consider the examples on the Scallion website. You can also refer to [Silex's Scaladoc page](https://epfl-lara.github.io/silex/silex/index.html).
* `lexer` is the Silex-based definition of tokenization rules. Each rule corresponds to a regular expression matching a prefix of the remaining program input. Silex will compose all of these rules into one finite state machine and apply the maximum-munch or longest match rule you've seen in class.
* Whenever a rule is found to match a (maximal) prefix of the remaining input, Scallion will call the transformation function provided using the `|>` operator in the rule. This function is given the matched input characters (`cs`) along with positional information (`range`) and should then produce an instance of `Token`. You can find its definition in `Tokens.scala`, which includes a list of all the different kinds of tokens that your Amy compiler should process. For instance, KeywordToken(`if`) represents an occurrence of the reserved word `if` in a program.
For more details on how to write new rules, read the short introduction to Silex's API at the top of `Lexer.scala` or consider the examples on the Scallion website.
Your task is to complete the rules in `Lexer.scala` and implement the filtering of irrelevant tokens.
## Notes
Here are some details you should pay attention to:
* Make sure you recognize keywords as their own token kind. if, for instance, should be lexed as a token KeywordToken(“if”), not as an identifier with the content `if`.
* Make sure you recognize keywords as their own token kind. `if`, for instance, should be lexed as a token `KeywordToken(“if”)`, not as an identifier with the content `if`.
* Make sure you correctly register the position of all tokens. Note the range parameter of the transformer functions. Once you have created a token, use `setPos`(`range._1`) to associate it with its position in the program source.
* In general, it is good to output as many errors as possible (this will be helpful to whomever uses your compiler, including yourself). Your lexer should therefore not give up after the first error, but rather skip the erroneous token, emit an error message, and then continue lexing. Scallion takes care of this for you for the most part. However, there are certain inputs that you might explicitly want to map to `ErrorToken`, such as unclosed multi-line comments.
* The Lexer does not immediately read and return all tokens, it returns an `Iterator`[`Token`] that will be used by future phases to read tokens on demand.
......@@ -68,28 +92,13 @@ Comments and whitespace should not produce tokens. (The most convenient way of d
* Make sure to correctly implement the Amy lexing rules for literals and identifiers.
## Example Output
For reference, here is a possible output for the example under `examples/Hello.scala`. You can always get reference output for the lexer from the reference compiler by typing
```
java -jar amyc-assembly-1.7.jar --printTokens <files>
```
```
KeywordToken(object)(1:1)
IdentifierToken(Hello)(1:8)
DelimiterToken({)(1:14)
IdentifierToken(Std)(2:3)
DelimiterToken(.)(2:6)
IdentifierToken(printString)(2:7)
DelimiterToken(()(2:18)
StringLitToken(Good morning!)(2:19)
DelimiterToken())(2:34)
DelimiterToken(})(3:1)
EOFToken()(4:1)
```
For reference, you can look at resources in the test folder to see example outputs.
## Deliverables
You are given **2 weeks** for this assignment.
Deadline: **Wed, Oct. 20, 23.00pm**.
Deadline: **Deadline: **14.03.2025 23:59:59****.
As for the previous lab, you should submit your work on the corresponding Moodle assignment. You should submit the following file:
Submission: one team member submits a zip file submission-groupNumber.zip to the [moodle submission page](https://moodle.epfl.ch/mod/assign/view.php?id=1170952).
* `Lexer.scala`: your implementation of the lexer.
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
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
/** 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
sbt.version=1.10.7
addSbtPlugin("com.lightbend.sbt" % "sbt-proguard" % "0.3.0")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.2.0")
\ No newline at end of file
package amyc
import utils._
import java.io.File
import amyc.parsing._
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 "--printTokens" => ctx = ctx.copy(printTokens = true)
case file => ctx = ctx.copy(files = ctx.files :+ file)
}
ctx
}
def main(args: Array[String]): Unit = {
val ctx = parseArgs(args)
val pipeline = AmyLexer.andThen(DisplayTokens)
if ctx.help then
println("Usage: amyc [ --interpret | --type-check | --printTokens ] 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 || ctx.typeCheck then
ctx.reporter.fatal("Unsupported actions for now")
else if ctx.printTokens then
pipeline.run(ctx)(files)
ctx.reporter.terminateIfErrors()
else
ctx.reporter.fatal("No action specified")
ctx.reporter.terminateIfErrors()
} catch {
case AmycFatalError(_) =>
sys.exit(1)
}
}
}
\ No newline at end of file
package amyc
package parsing
import amyc.utils._
import java.io.File
import silex._
import amyc.utils.Position
// The lexer for Amy.
object AmyLexer extends Pipeline[List[File], Iterator[Token]]
with Lexers {
/** Tiny Silex reference:
* ==============================
* Silex'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").
*/
// Type of characters consumed.
type Character = Char
// Type of positions.
type Position = SourcePosition
// Type of tokens produced.
type Token = parsing.Token
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("_") | word("end")
|> { (cs, range) => KeywordToken(cs.mkString).setPos(range._1) },
// Primitive type names,
// TODO
// Boolean literals,
// TODO
// Operators,
// NOTE: You can use `oneof("abc")` as a shortcut for `word("a") | word("b") | word("c")`
// TODO
// Identifiers,
// TODO
// Integer literal,
// NOTE: Make sure to handle invalid (e.g. overflowing) integer values safely by
// emitting an ErrorToken instead.
// TODO
// String literal,
// TODO
// Delimiters,
// TODO
// Whitespace,
// TODO
// Single line comment,
word("//") ~ many(elem(_ != '\n'))
|> { cs => CommentToken(cs.mkString("")) },
// Multiline comments,
// NOTE: Amy does not support nested multi-line comments (e.g. `/* foo /* bar */ */`).
// Make sure that unclosed multi-line comments result in an ErrorToken.
// TODO
) onError {
// We also emit ErrorTokens for Silex-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: amyc.utils.Context)(files: List[File]): Iterator[Token] = {
var it = Seq[Token]().iterator
for (file <- files) {
val source = Source.fromFile(file.toString, SourcePositioner(file))
it ++= lexer.spawn(source).filter {
token =>
// TODO: Remove all whitespace and comment tokens
???
}.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(_))
}
}
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)
case object IdentifierKind extends TokenKind("<Identifier>")
case object PrimTypeKind extends TokenKind("<Primitive Type>")
case object LiteralKind extends TokenKind("<Literal>")
final case class DelimiterKind(value: String) extends TokenKind(value)
final case class OperatorKind(value: String) extends TokenKind(value)
case object EOFKind extends TokenKind("<EOF>")
case object NoKind extends TokenKind("<???>")
}
object TokenKind {
import Tokens._
import TokenKinds._
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
}
}
\ No newline at end of file
package amyc.utils
case class AmycFatalError(msg: String) extends Exception(msg)
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,
typeCheck: Boolean = false,
help: Boolean = false
)
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)
}
\ No newline at end of file
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")) then Linux
else if (osName.contains("win")) then Windows
else if (osName.contains("mac")) then Mac
else Linux
} getOrElse Linux
}
}
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
}
package amyc.utils
import java.io.File
import silex._
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)
}
}
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): Unit = {
Console.err.println(msg)
}
private def report(prefix: String, msg: Any, pos: Position): Unit = {
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
}
}
}
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)
}
}