SDEditGen is a preprocessor tool for Quick Sequence Diagram Editor, better known as sdedit. QSDE / sdedit is an excellent open source text to UML tool, but its syntax is not so nice: it is often unintutitive, cryptic, and hard to remember. Also, like syntax of many sequence diagram tools, sdedit’s syntax is unstructured. It is easy to create and read a Hello world (Bob → Alice) example, but it is much harder to write and read complex real-world diagrams. SDEditGen aims to provide a structured, nicer, easier and more intuitive syntax for sdedit.
QSDE / sdedit: good parts
Nice desktop GUI application
Quick Sequence Diagram Editor is a GUI application, with a tree view containing open diagrams on the left, a text editor on the bottom right and a real-time diagram preview pane on the top right:
Plenty of features
QSDE / sddit offers some features which are not very often found in sequence diagram tools, like real time diagram server and multithreading support, as well as not so exotic but nice features like automatic redrawing.
Links to other diagrams
In my view this is the most important feature for real-world complex diagrams! For toy examples it is not needed, of course.
The diagram to the right includes the links to diagrams Create_Matadata.sd and Create_leaf.sd. When the user clicks on a diagram link the application will replace the current diagram with the clicked one, and also display the newly opened diagram name in the tree view.
Why is this feature so important? The main reason why we need UML diagrams at all is software complexity. Therefore the diagrams worth to be drawn are also complex, and this means they tend to become big pretty soon. In my view a single diagram should not become too big. Perhaps a meaningful limit for a single diagram is that it horizontally fits a screen width in the full-screen mode and that vertically it is not much bigger than what fits in the full screen. If we want to follow this tip the only solution is to break a potentially big diagram into smaller interlinked diagrams.
There are many text to UML tools but it is questionable whether any open source or inexpensive tool besides sdedit includes a similar functionality.
What’s wrong with sdedit
Unstructured syntax
An important use case for sequence diagrams is when we need to understand an unfamiliar, complex and undocumented source code.
The source code written in modern programming languages is structured: individual declarations and instructions reside in classes and methods, they are not just floating around.
On the other hand, the code in sdedit and many other text to UML tools is unstructured: these tools don’t provide the means to reflect the structure present in the source code.
The basic syntax for sending messages in sdedit is following:
<caller>:<callee>.message
These instructions are by far the most common ones, so our program is mostly just a sequence of such instructions.
Do repeat yourself
Object-oriented programming languages don’t force us to specify the current object when we call methods of other objects. Unlike that, when we are writing sdedit code we must specify the current object each time. For example:
|
This affects readability badly.
Cryptic syntax
Let’s illustrate the problematic syntax on just one example.
In the following line it looks that the source object bfs assigns the variable adjList, but actually this specifies that the target object’s method getAdjacentNodes returns adjList, which is something quite different:
bfs:adjList=node.getAdjacentNodes()
SDEditGen: structured syntax for sdedit
Object creation & method calls
The most important operations supported by SDEditGen are object creation and method calling.
QSDE / sdedit models both operations as message passing:
sourceObject:targetObject.new(arguments)
sourceObject:targetObject.method(arguments)
The SDEditGen’s basic idea is to avoid specifying the source object in every object creation and method call: the source object should be determined implicitly. The SDEditGen’s syntax for these operations is simply:
new targetObject(arguments)
call targetObject.method(arguments)
How can we achieve the source object to be implicitly known? There are several ways, but let’s show the simplest way first.
Object as context
Let’s say the object test creates the object adapter and we don’t care in which particular method this is happening. We can specify it like this:
|
The object statement implicitly provides the context (the source object) for the enclosed operations. |
If the object test calls a method on the created object, we also don’t have to specify the source object because it is known from the context:
|
Method call as context
So far so good, but how can we specify the context if adapter’s init method creates another object?
We can do it easily because adapter’s init method call itself can serve as a context:
|
The method call statement implicitly provides the context (the source object) for the enclosed operations. |
Object creation as context
What if the manager’s constructor creates yet another object?
You guess it: the manager’s creation instruction can also implicitly determine the source object for instructions (object creations and method calls) performed in the constructor:
|
The object creation statement implicitly provides the context (the source object) for the enclosed operations. |
Object declarations
Finally we are ready to show the complete example:
|
Return clauses
Adding returned objects or values to diagrams can significantly improve readability. SDEditGen’s syntax supports return clauses in object creation and method call statements:
new targetObject(arguments) return expression
call targetObject.method(arguments) return expression
In my view this syntax is considerably easier to read and understand than the original sdedit syntax. The following diagram includes several return clauses:
object bfs {
new queue
call someNode.setLevel(0)
call queue.insert(someNode)
loop "while queue != ()" {
call queue.remove() return node (1)
call node.getLevel() return level
call node.getAdjacentNodes() return adjList
loop "0 <= i < #adjList" {
call adjList.get(i) return adj
call adj.getLevel() return nodeLevel
alt "nodeLevel IS NOT defined" { (2)
call adj.setLevel(`level+1`)
call queue.insert(adj)
section "else"
}
}
}
call queue.destroy()
}
1 | An example of a method returning an object |
2 | The alt construct corresponds to [c:alt] construct in sdedit |
Diagram links
The following diagram contains several diagramLink statements:
objects {
test: ProtocolAdapterTest | existing
adapter: ProtocolAdapter
configurator: ProtocolAdapterConfigurator
confHelp: ConfigurationHelper
manager: Manager
protocolBuilder: ProtocolBuilder
protocol: Protocol
}
object test {
new adapter;
call adapter.init(configuration) {
new configurator(this, configuration) {
new confHelp(this);
}
new manager(configurator) {
new protocolBuilder;
diagramLink "Create_Metadata.sd" 1; (1)
call protocolBuilder.add(Metadata);
diagramLink "Create_Leaf.sd" 2;
call protocolBuilder.add(Leaf);
loop { (2)
call protocolBuilder.add(Hierarchy);
}
call protocolBuilder.add(Top);
call protocolBuilder.add(Timestamp);
call protocolBuilder.build {
new protocol(this) {
call protocol "Copy items from builder"; (3)
}
}
}
}
}
1 | The diagramLink statement creates a link to another sdedit diagram |
2 | The loop construct corresponds to [c:loop] construct in sdedit. |
3 | A method name doesn’t have to be an identifier, it can be descriptive. The dot character between object name and method name is optional. A statement can end with a semicolon, but it is optional as well. |
Syntax diagrams
The SDEditGen syntax is described in the form of railroad diagrams generated by RRD for ANTLR4 tool. The following excerpt illustrates how they look:
Running
SDEditGen currently doesn’t include a graphical user interface. There are two ways of converting the source SDEDitGen code to sdedit format: an HTTP server and a command-line interface.
HTTP server
An HTTP server which converts from SDEDitGen to sdedit format allows us to use GUI of an HTTP client application like Insomnia.
The server can be started from command line using sbt runMain
command:
sbt "runMain sdeditgen.Server"
Alternatively, you can first start sbt and then execute runMain:
$ sbt
> runMain sdeditgen.Server
The command starts the server on the port 8080. To convert from SDEditGen source code to sdedit format just issue an HTTP POST request to the following endpoint:
localhost:8080/generate
With Insomnia it looks like this:
Command-line interface
An SDEditGen source file can be converted into sdedit form by the following command:
sbt "runMain sdeditgen.CLI <input file path> <output folder path>"
Both paths can be relative or absolute. If <output folder path> is a relative path it is interpreted as a subfolder of the input folder. If <output folder path> is a dot character the input and output folders are the same.
The input file’s extension must be sdgen.
Let’s see several examples:
sbt "runMain sdeditgen.CLI /users/jsmith/source/demo.sdgen target"
sbt "runMain sdeditgen.CLI /users/jsmith/source/demo.sdgen /users/jsmith/source/target"
sbt "runMain sdeditgen.CLI source/demo.sdgen target"
sbt "runMain sdeditgen.CLI demo.sdgen ."
Of course, an alternative way is to start sbt and execute runMain in sbt shell:
$ sbt
> runMain sdeditgen.CLI <input file path> <output folder path>