Kiama is a Scala library for language processing including attribute grammars, term rewriting, abstract state machines and pretty printing.
This is a major release cross-published for both Scala 2.10 and 2.11. It’s been a while and there are lots of changes, so these notes are crazy long.
Since June 2015, Kiama has been based at Bitbucket instead of Google code. Please raise issues on the Bitbucket site.
To reflect the move from Google Code to Bitbucket, the group ID under which Kiama is published on Maven Central
is now org.bitbucket.inkytonik.kiama instead of com.googlecode.kiama and the top-level
package is now also org.bitbucket.inkytonik.kiama instead of org.kiama.
Attribution: The Attributable trait that provided generic tree access has been removed
and replaced with a new approach based on tree relations. See the “What happened to Attributable?” section below
for details.
Attribution and Rewriting: Deep cloning was previously implemented in
Attribution using Attributable. It is now provided by Rewriting and does
not rely on any attribution.
Attribution: Replaced Attribution object with a class so that it is now necessary to
instantiate it (or sub-class) rather than using a single default global Attribution instance.
Output: Removed pretty_any pretty-printing method, use pretty and
any individually instead.
Output: Removed PrettyPrintable class and related implicit conversions and methods
(plist and pseq) since they were hardly ever used and complicated the pretty-printer
interface.
Output: Removed product pretty-printer combinator. Use any instead.
Output: Renamed empty pretty-printer combinator to emptyDoc to avoid
clashes with other libraries, particularly ScalaTest.
Utility: Removed the global resetMemo operation for all memoised data since it doesn’t
play well with concurrent execution.
Utility: Removed the resetEnvironments operation from the Environments
trait. It is better to create different environments than reuse one.
Utility: As a result of the move to our own parsing library (see below), the old Kiama
ParserUtilities and PositionedParserUtilities traits have been removed. Their
functionality is now provided directly by the parsing library.
Utility: The new parsing library uses vectors to hold repetitive constructs instead of lists as in
the old parsing library. Other parts of Kiama have also standardised on using vectors instead of lists for this
purpose.
Utility: The definition of whitespace for the new parsers must also now match “no whitespace”
instead of assuming that there was at least some whitespace present as in previous versions. This new definition
allows two constructs to be separated by “no whitespace” provided the parser for the first construct doesn’t
consume any of the second construct. Previously it was necessary to have actual whitespace between them.
Utility: configuration of output emitters for Kiama-based programs is now done using the
command-line option --Koutput instead of via specialised configuration code. Error emitters
have been removed since they were not really needed and never actually output to standard error.
We now rely less on general Seq and Stack types where there is really no benefit.
Parsing: Kiama no longer uses the Scala parser combinator library. The new parsing
package contains a parsing combinator library with a very similar interface to the Scala library one. Ours has a
simpler implementation and interfaces more easily with the rest of Kiama on things like positions. Many features
that we didn’t need have been omitted (e.g., there is no distinction between RegexParsers and other
ones). We use the new parsing library for our examples, teaching and testing but wouldn’t recommend it for
production use. Instead, you may be interested in our sbt-rats
parser generator for Scala.
Output: Added support for parenthesised pretty-printing of n-ary expressions.
Output: Added arguments method which embodies how a parenthesised sequence of arguments
should be pretty-printed. Previously this was built-in to the seq combinator but now can be accessed
separately.
Output: Added a form of origin tracking that makes available a map between source objects and
pretty-printed text ranges as a result of pretty-printing. Rendering operations now return a Document
which combines the pretty-printed layout with the mapping information.
Utility: Added defineIfNew operation and pretty-printing to environments.
Utility: Some details of messaging have changed with a view to generalizing the implementation.
formats is now called formatMessages. sortmessages has been removed in
favour of just using .sorted on a message list with a provided Ordering. There are now
some helper methods dropPrefix and dropCurrentPath for reducing the lengths of names in
messages.
Utility: A new Source abstraction is used to encapsulate the source of text used by a
processor and to provide facilities used by messaging. It comes in file and string varieties.
Utility: Position handling is now provided by a PositionStore trait rather than a
single global position store. Positions now include a filename component as well as line and column.
Utility: Added support for testing REPLs.
Rewriting: In the core don’t duplicate singleton objects such as Nil but return the
same instance. The previous approach didn’t cause problems in rewriting since the generic traversals avoided this
kind of duplication, but it was a problem when writing other general operations including deep cloning.
Utility: When printing positions, a blank line is now no longer included before the pointer line.
Utility: The keywords helper method in the parser utilities now correctly deals with
keywords that occur at the end of the input.
The Bitbucket project page now shows build status from latest nightly SNAPSHOT build on CloudBees.
Many other minor adjustments, behind-the-scenes fixes and improvements to examples.
Moved to sbt 0.13.11, Scala 2.11.8 and 2.10.6, ScalaCheck 1.12.5, ScalaTest 2.2.5, jline 2.14.1, Guava 19, Scallop 1.0.0, Findbugs jsr305 3.0.1
The code base is also compatible with Scala 2.12.0-M3.
In Kiama 1.x you could use abstract syntax trees reflexively by accessing fields such as parent,
next or isRoot. These fields were provided by the Attributable trait that you
had to mix in to your AST classes. You also had to call initTree on the root of your AST to setup these
fields before you tried to use them.
Kiama 2.x changes this aspect of the library quite a bit, so we devote some space here to describing the migration path. The Kiama examples have all been updated so you can consult them for more detail.
In a nutshell, Attributable and initTree no longer exist. Instead, you can use a mechanism
called tree relations to achieve a similar goal. A key property of the new approach is that the relations
exist outside your AST so that no modification of the AST classes is necessary. Thus, you can now do full attribution
of structures that come from elsewhere for which mixing in Attributable would have been impossible.
Since Attributable no longer exists, the accompanying n->a notation for evaluating an
attribute a on a node n has also gone. Instead, just use the normal function call notation
a(n).
Attribution that doesn’t need to access the tree reflexively (i.e., doesn’t useparent,
isRoot, etc.) can be written as before.
If you want the equivalent of the parent, isRoot, etc fields of 1.x in 2.x you need to
create a Tree object for your AST. Suppose that you have a tree rooted at a node root that
you wish to attribute. You create the Tree for this structure as follows:
import org.bitbucket.inkytonik.kiama.relation.Tree
val tree = new Tree(root)
The Tree object traverses the structure to set up relations that give reflective access to the
structure. E.g., tree.parent is a relation that relates each node to its parent (if there is one).
The easiest way to use the relations such as tree.parent is via pattern matching. Suppose that you want
to write a depth attribute whose value is zero if the node is the root of the tree (i.e., has no parent)
and one more than its parent’s value otherwise. Assuming you have access to a relevant Tree via the
variable tree you can define the attribute as follows:
val depth =
attr {
case tree.parent(p) =>
depth(p) + 1
case _ =>
0
}
The first case will only succeed if the matched node has a parent and it will bind p to that parent node
for use in the recursive call. If the first case fails, then there is no parent and we use the default case for the
root of the tree.
Each relation also comes with a pair method that enables you to match on both parts of a relational
tuple at the same time. For example, the following case uses the tree.next relation to match if the
current node is an assignment statement node and the next one at the same level is a while loop statement node:
case tree.next.pair(_ : AsgnStmt, _ : WhileStmt) =>
Of course, patterns can be nested so very powerful matching up and down trees can be expressed using these relations.
The tree relation approach brings a disciplined approach to programs that combine attribution and rewriting. In Kiama 1.x you had to be very careful if you rewrote an AST and then wanted to perform attribution on it. Because the old and new ASTs could share nodes, it was not always clear what the appropriate value of an attribute should be. E.g., imagine that the depth of a node changes. The safest way out was to clear the attribute values before computing any in the new AST, which was a brute force solution and didn’t allow you to go back and access attributes on the old AST.
In Kiama 2.x you are forced to use a Tree value to access the AST structure (other than children). Hence
you can have a Tree for the old AST and another one for the new AST and can perform
well-defined attribution on either whenever you want. In a future version of Kiama we plan to support some form of
incremental computation of attributes where Tree relations overlap.
To make rewriting a little easier than before, it is no longer necessary to clone nodes when rewriting to avoid
creating sharing in the rewritten tree. Just create the sharing and move on. If later you decide to perform some
sharing-sensitive attribution on the AST that contains shared nodes, the Tree that you create will clone
nodes as necessary to avoid sharing in the relations.
For more information on how to use tree relations, please consult the Kiama 2.x examples. In particular, the semantic analysis modules of the compiler examples (e.g., MiniJava and Oberon-0) show how reusable collections of attribute definitions can be defined.