By Stepan Mitkin
Download the source code for the tutorial: plugin-demo.zip
This tutorial explains how to generate source code in your favourite programming language from diagrams using DRAKON Editor.
Some basic knowledge of Tcl and SQL is needed to follow the tutorial.
Code generation in DRAKON Editor is done using plugins. In order to add a language to the list of supported languages, create a plugin for that language. A plugin is just a Tcl source code file in the generators folder.
The source code file must contain certain items in order to work as a plugin:
It is advisable to use a unique namespace inside the plugin. DRAKON Editor is a relatively large project and its global procedure space is already pretty crowded.
Let us develop a plugin for the D language. D has a syntax similar to that of C#, Java and C++.
This is a minimal "hello world" plugin:
# Register the gen_d::generate procedure as the code generator for language "D".
gen::add_generator "D" gen_d::generate
namespace eval gen_d {
# The code generator procedure. It will be called by DRAKON Editor.
# Arguments:
# db - A handle to the database of the original file. Read only!
# gdb - A handle to the temporary database. Read-write.
# filename - The .drn filename.
proc generate { db gdb filename } {
puts "hello world"
}
}
In order to actually make the plugin, create a tcl file in the generators folder and copy the above source code into it. Then restart DRAKON Editor.
DRAKON Editor data files are SQLite databases. SQLite is also used throughout DRAKON Editor as an in-memory database.
The db argument of the generate procedure is a handle to the database in the original .drn file. Do not change data in this database during code generation! Code generation should be a read-only operation.
The schema of the .drn files can be found in scripts/schema.sql. The format is documented here. If you are not sure how a particular element of the diagram is stored in the database, there is an easy way to find that out. Just select the element on the canvas and copy it to the clipboard. Then paste into a text editor. For example, an "action" icon with text "Hello world!" would look like that:
DRAKON 1.22 items {{15 action {Hello world!} {} {} 1 170 290 60 20 0 0}}
One element is one row in the items table.
The gdb argument is a handle to a temporary work database. It is an in-memory database. It is created before each code generation and destroyed afterwards. It is okay to write to the gdb database. The schema of the gdb database can be found in scripts/graph.tcl. (See the p.create_db procedure.)
Let us assume we have a test .drn file with DRAKON-D diagrams.
First, we need to set the language for the file. In DRAKON Editor, go to File / File properties... Set the language to "D" and press "Ok". We can now generate source code either from DRAKON Editor or using the command-line tool drakon_gen.tcl.
Using DRAKON Editor: go to DRAKON / Generate code.
Using drakon_gen.tcl:
tclsh drakon_gen.tcl -in <path to your .drn file>
Quite a lot of ping-pong happens between DRAKON Editor and the plugin during code generation.
The process is initiated on the DRAKON Editor side. The code inside Editor checks the diagrams against the rules of the DRAKON language and does some initial pre-processing.
Then, the control is passed to the plugin. The plugin is supposed to build a dictionary of callbacks. This dictionary is similar to a Java or C# interface. The plugin must "implement" it. The callbacks are small snippets of code that the generated code will be built of.
After that, the plugin does its own custom part of pre-processing on the diagrams. Having done that, the plugin returns control to the Editor together with the callbacks.
The Editor then actually generates the code from the diagrams using the callbacks.
There are two algorithms for code generation:
In this tutorial, we will concentrate on doing without goto.
If the generation has completed successfully, the generated code is handed over to the plugin.
The plugin then writes the code to the output file or files.
The callbacks are small methods that return snippets of code. Some of the callbacks are actually snippets, not even code.
The callbacks correspond to the syntax of the programming language that the plugin generates code in. It is the callbacks that comprise the bulk of the plugin code.
Here is a procedure that assembles the callback dictionary. It is a way to construct an interface implementation.
proc make_callbacks { } {
set callbacks {}
gen::put_callback callbacks assign gen_d::assign
gen::put_callback callbacks compare gen_d::compare
gen::put_callback callbacks compare2 gen_d::compare
gen::put_callback callbacks while_start gen_d::while_start
gen::put_callback callbacks if_start gen_d::if_start
gen::put_callback callbacks elseif_start gen_d::elseif_start
gen::put_callback callbacks if_end gen_d::if_end
gen::put_callback callbacks else_start gen_d::else_start
gen::put_callback callbacks pass gen_d::pass
gen::put_callback callbacks return_none gen_d::return_none
gen::put_callback callbacks block_close gen_d::block_close
gen::put_callback callbacks comment gen_d::comment
gen::put_callback callbacks bad_case gen_d::bad_case
gen::put_callback callbacks for_declare gen_d::foreach_declare
gen::put_callback callbacks for_init gen_d::foreach_init
gen::put_callback callbacks for_check gen_d::foreach_check
gen::put_callback callbacks for_current gen_d::foreach_current
gen::put_callback callbacks for_incr gen_d::foreach_incr
gen::put_callback callbacks and gen_d::and
gen::put_callback callbacks or gen_d::or
gen::put_callback callbacks not gen_d::not
gen::put_callback callbacks break "break;"
gen::put_callback callbacks declare gen_d::declare
gen::put_callback callbacks shelf gen_d::shelf
gen::put_callback callbacks body gen_d::generate_body
gen::put_callback callbacks signature gen_d::extract_signature
return $callbacks
}
Here are examples of callbacks. All callbacks required for the D language can be found in the source code for the tutorial: plugin-demo.zip.
# A simple variable assignment.
proc assign { variable value } {
return "$variable = $value;"
}
# A comparison of two values.
proc compare { variable constant } {
return "$variable == $constant"
}
# The beginning of an eternal __while__ loop.
proc while_start { } {
return "while (true) \{"
}
# The left part of the if condition
proc if_start { } {
return "if \("
}
This is a plugin that actually does something more useful than printing the infamous "hello world".
# The code generator procedure. It will be called by DRAKON Editor.
# Arguments:
# db - A handle to the database of the original file. Read only!
# gdb - A handle to the temporary database. Read-write.
# filename - The .drn filename.
proc generate { db gdb filename } {
global errorInfo
# Construct the callbacks dictionary
set callbacks [ make_callbacks ]
# Get the list of diagrams
set diagrams [ $gdb eval {
select diagram_id from diagrams } ]
# Select only DRAKON diagrams and pre-process them.
foreach diagram_id $diagrams {
if { [ mwc::is_drakon $diagram_id ] } {
set append_semicolon 1
gen::fix_graph_for_diagram $gdb $callbacks $append_semicolon $diagram_id
}
}
# Do the code generation
set nogoto 1
set functions [ gen::generate_functions \
$db $gdb $callbacks $nogoto ]
# Abort if any errors happened so far.
if { [ graph::errors_occured ] } { return }
set hfile [ replace_extension $filename "d" ]
# Open the output file and write the code.
set f [ open $hfile w ]
catch {
print_to_file $f $functions
} error_message
set savedInfo $errorInfo
# Close the file regardless of exceptions.
catch { close $f }
if { $error_message != "" } {
puts $errorInfo
# Rethrow the exception
error $error_message savedInfo
}
}
This plugin does the following:
Now we have a plugin that can produce some meaningful D code. However, some more features are needed to make the plugin useful.
Although we have written a beautiful code generator, sometimes it will not be enough. We must be able to put some arbitrary D code into the output file.
A way to do that is by utilising the file description. The file description is a block of free text that belongs to the .drn file.
It is possible to mark certain sections of the description. The code generation plugin will read the description, extract those sections and write them directly to the output file.
DRAKON Editor already has the code to extract the sections.
lassign [ gen::scan_file_description $db { "header" "footer" } ] header footer
In the above code snippet, the file description is searched for sections named "header" and "footer". Here is an example of sections in the file description:
This is just a description.
The sections follow below:
=== header ===
// This part goes to the beginning of the file.
import std.stdio;
class Foo {
private const int N;
=== footer ===
// This part goes to the end of the file.
} // End of class Foo.
void main()
{
auto f = new Foo(20);
f.run();
}
The header here is used to import std.stdio and to begin a class. The footer closes the class and adds some more code afterwards.
This way, sections are a way to do object-oriented programming.
In order to make better use of the features of the D language, we need to be able to decorate functions with keywords like pure, override etc.
This can be achieved in the extract_signature callback.
extract_signature will produce the following signature from this picture.
static pure int[] fibonacci(int n)
The list of keywords is expected to be in the first line of the "Formal parameters" icon.
Constructors and destructors need special treatment. We could give a diagram a special name to tell the plugin that it is a constructor. For example, ctr could mean "constructor" and dtr will stand for "destructor".
The code generation plugin could do more than just printing code for DRAKON diagrams. The DRAKON language is also suitable for state machine diagrams. Look into the source code of the C# and Erlang plugins to see how it's done.
Another cool feature that a plugin could do is generating code from ERIL diagrams. ERIL is visual language based on entity-relationship and class diagrams. Like with DRAKON diagrams, DRAKON Editor does some pre-processing for ERIL diagrams. But the task of outputting the language-specific code lies on the plugin. For examples of ERIL generators, see the plugins for C# and Tcl.
8 February 2014.
Contact: drakon.editor@gmail.com