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.