VIATRA, apart from being a general purpose model transformation tool, is also well suited for code generation tasks. In fact, code generation can be thought of as a special case of model transformation, where the (final) output is some textual content.
Basically, textual output can emitted easily by using the print()
and println()
built-in rules of the VTCL. These rules accept, in the simple case, a single
Term consisting of
Variables,
Constants and
Expressions combining these with operators.
Some simple examples:
rule test() = seq { print("Hello world!"); // string constant print(3); // integer constant print(3+4); // simple term let V1 = "variable1" in print(V1); // variable let V2 = "variable2" in println("The value is: "+V2); // expression with constant and variable: concatenation }
In VIATRA, code generation usually means traversing a model and emitting textual output. So, in order to create a code generator, you have to think of two things:
The following is a very simple code generation example, which generates XML from a simplified class diagram model.
import metamodel.uml; machine generateXML() { pattern class(C) { Class(C); } pattern attribute(A) { Attribute(A); } pattern c_a(C,A) { Class(C); Attribute(A); Class.attr(CA,C,A); }
rule main() = seq { println("<?xml version=1.0" encoding="UTF-8"?> println("<classes>"); forall C with find class(C) do seq { println("\t<class name=\""+name(C)+"\">"); // println supports control characters like \t forall A with find c_a(C,A) do println("\t\t<attr name=\""+name(A)+"\"/>"); // escaping is important! println("\t</class>"); } println("</classes">); } }
With VIATRA, you can use multiple output buffers for code generation. This feature is useful is a number of scenarios, e.g. when you wish to separate debugging and/or tracing output (emitted while the code generation transformation is running) from the actual result of the generation. You may also frequently encounter cases where the output itself needs to be separated into multiple files.
To use multiple output buffers, you may supply an additional parameter (as the first parameter) to the print()
and println()
rules, which holds a special native value retrieved from an invocation of the
getBuffer() native function.
The getBuffer() function accepts a special URI as a parameter, which specifies either a file-based output buffer (with a workspace-relative path), or an in-memory buffer (with an identifier).
See this illustrative example below for usage details:
machine helloworld { rule main() = seq { let Buf0 = getBuffer("file://test/dir/file.ext") in // append let Buf1 = getBuffer("file://test/dir2/file.ext") in // new dir let Buf2 = getBuffer("file://test/dir2/file2.ext") in // new file let Buf3 = getBuffer("file://test3/dir/file.ext") in // new project let Buf4 = getBuffer("file://test4/dir/dir2/file.ext") in let Buf5 = getBuffer("file://test5/dir/dir2/dir3/file.ext") in let Buf6 = getBuffer("file://test6/file.ext") in let Buf7 = getBuffer("core://test7") in // in-memory buffer let Content = "Test content written to buffer output" in seq { println(Buf0, Content); println(Buf1, Content); println(Buf2, Content); println(Buf3, Content); println(Buf4, Content); println(Buf5, Content); println(Buf6, Content); println(Buf7, Content); } println("This goes to the default output!"); } }
Important notes: