Test-Driven Language Development with Spoofax
Spoofax
This short primer shows how to use tests as a basis for language development with Spoofax. As an example project we create a small 'calculator' language that shows many of the basics of language definitions.
The source for our example project is collected in
before.zip?. The complete project, with all functionality implemented to make the test cases succeed is collected in
after.zip?.
A full description of the testing language can be found in the paper
Integrated Language Definition Testing. Enabling Test-Driven Language Development. Presentation slides also show Spoofax testing in action:
http://slidesha.re/tYaHQT
A Calculator Language
Our example language supports arithmetic expressions, variables, and assignments. An example:
a = 3
a * 4
Based on this simple language we can write test cases. Doing test-driven development, these can even drive the development of the language, but in this document we focus on the tests.
Tests can be written using a
.spt
file. Create one in a Spoofax project, and press control-space to get a basic test definition. It will have tests of this form:
test description [[
a = 3
a * 4
]] 0 errors
As the example shows, each test can quote a program using
[[
,
[[[
or
[[[[
brackets. It can also specify a
description for the test, and a condition. This test specifies that
0
semantic
errors
are expected.
Syntax
Example of tests of the syntax:
test Add [[
1 + 2
]] parse succeeds
test Abstract syntax (1) [[
1
]]
parse to Int("1")
test Abstract syntax (2) [[
1 * 2
]]
parse to Mul(Int("1"), _)
test Parentheses [[
(1 + 2)
]]
These tests specify parse success, compare the abstract syntax of the test input against a pattern, or simply specify that the test should succeed.
Tests can also use concrete syntax to test operator precedence and associativity:
test Multiply and add (1) [[
1 + 2 * 3
]] parse to Add(_, Mul(_, _))
test Multiply and add (2) [[
1 + 2 * 3
]] parse to [[
1 + (2 * 3)
]]
test Add and multiply [[
1 * 2 + 3
]] parse to [[
(1 * 2) + 3
]]
test Add and add [[
1 + 2 + 3
]] parse to [[
(1 + 2) + 3
]]
Evaluation of expressions
The following tests will
run
a transformation named
calc
to test evaluation of expressions:
test Constant [[
1
]] run calc to "1"
test Add [[
1 + 1
]] run calc to "2"
test Subexpression [[
1 + [[2 + 3]]
]] run calc to "5"
Note that the last test in this series uses the
[[
...
]] brackets to make a
selection in the test. The
calc
transformation is evaluated for this selection.
Variables
The following tests exercise the definition of variables:
test Variable [[
x
]] parse
test Variable [[
longname
]] parse
test Assignment [[
x = 4
]] parse
test Multiple statements [[
x = 1
y = 2
]] parse to Statements([Assign(_, _), Assign(_, _)])
Stm* -> Start {cons("Statements")}
ID "=" Exp -> Stm {cons("Assign")}
Exp -> Stm
ID -> Exp {cons("Var")}
test Evaluate multiple statements [[
1
2
]] run calc to "2"
calc:
Statements(s*) -> last
where
s'* := <map(calc)> s*;
last := <last> s'*
test Eval constant [[
PI
]] run calc to "3.14"
calc:
Var("PI") -> "3.14"
test Eval multiple variables [[
x = 2
y = x * 2 + x
y
]] run calc to "6"
Editor features
The following test cases test the editor facilities of the language:
test Variable unassigned [[
y
]] 1 error /unassigned/
This test succeeds if the input has
1 error
and it matches the regular expression
/unassigned/
(as variable
y
is unassigned).
test Multiple assignments to same variable [[
y = 1
y = 2
y
]] /multiple/
This test succeeds if there are one or more (error) messages that match
/multiple/
(as there are
multiple definitions of
y
).
test Reference resolving (1) [[
x = 4
[[x]]
]] resolve
test Reference resolving (2) [[
[[x]] = 4
[[x]]
]] resolve #2 to #1
test Content completion [[
avariable = 1
[[a]]
]] complete to "avariable"
These test cases test reference resolving and content completion. They use the
[[
...
]] selection mechanic to select parts of the program. The first test case specifies that reference resolving should work for the selection. The second specifies that the second selection should resolve to the first selection. The last test case specifies that the selection should provide a content completion option
avariable
.
Execution
The
Calculang
project generates Java code. The following test case tests the output that is returned when the program is compiled and executed:
test 42 [[
42
]] build generate-result to 42