In this article, I’ll show you the codebase on which we will build a DSL. In order to build a DSL, it is imperative to know some Kotlin features that are applied intensively. These features are:

In addition, for a more idiomatic DSL, other Kotlin features will also be applied. These features are:

If you’re not familiar with any of them, I suggest you to click on it and take a look before moving forward.

Project description

The code that I will show you in this article consists of a console application that prints out geometric shapes. Specifically, there will be 4 of them: Square, Triangle, Rhombus and Composed Shape.

All shapes will be able to be merged with Union and Intersection operations, resulting in a composed shape. These operations are analogous to the Set operations in mathematics. Union operation will result in a shape that covers all the space occupied by both shapes. Intersection operation will result in a shape that covers only the space that both shapes have in common.

Union and Intersection operations

The idea is add geometric shapes of different sizes to a Panel and print them all top-down, line by line, arranged side by side, simulating a physical printer that prints on paper.

For example, the code below…

Initial code

…prints out to the console the following:

Output

Our goal is to build a DSL on top of this code so creating a Panel and its Shapes becomes clearer and more idiomatic:

Final code

Codebase

I will present the initial code and although it is quite short and simple, I suggest you to keep reading to ensure you haven’t missed any details.

🔗 You can find the initial code on my GitHub account:

Make sure to download the project and checkout to the master branch where you will find the codebase which won’t be modified, except for the main method since our intention is to change its syntax for a more idiomatic one using our DSL.

The dsl branch contains the final code which is the result of what we are going to do during these next articles. The ultimate goal is that you learn how to build a DSL on top of existing code, especially on code that you can’t modify, either because it is code that is part of a module that you don’t have access to or permission to modify, or because it is packaged in a library. At the same time, you will discover what many libraries that we include in our projects have under the hood.

Project Package Organization

If you open the project and display its directory tree structure under the ‘Project’ view option, you will see the following:

Shapes-DSL: Package structure

Under the console_shapes package you will find 2 packages: container and shapes. The container package contains only the Panel class which will behave as a container for all the geometric shapes. The shapes package contains classes that represent geometric shapes able to be added to the Panel.

‘Shapes’ package

Our project has 4 geometric shapes of which 3 are simple shapes and 1 is a composed shape. Square, Triangle and Rhombus shapes correspond to the simple ones. ComposedShape corresponds to the composed one since it can be built only by merging 2 shapes, whether they are both simple, both composed or one simple and one composed. Additionally, we have the Space shape that represents an “empty” space within the Panel.

  • Shape.kt

The abstract class Shape is the base class that determines a common behavior for all geometric shapes.

  • WHITE_SPACE: establishes the character that will take place on every empty space of the shape.
  • grid: corresponds to a matrix of Char — array of CharArray — which represents the shape itself. Its dimension will be determined by the number of lines established during every concrete class instantiation, as well as its characters.
  • size: defines a variable with custom accessor without backing field that returns grid size.

🔗 If you’re starting with Kotlin and you don’t know how custom accessors and backing fields work, take a look at the section Getters and setters of Kotlin’s official documentation at: https://kotlinlang.org/docs/properties.html#getters-and-setters

  • Square.kt

Square class extends from Shape class and represents a Square shape. During its instantiation, it receives the following parameters:

lines: determines the grid size.
char: determines the grid character.

Both, grid width and height, will be the same size.

  • Triangle.kt

Triangle class extends from Shape class and represents a Triangle shape. During its instantiation, it receives the following parameters:

lines: determines the grid size.
char: determines the grid character.

grid width will double its height.

  • Rhombus.kt

Rhombus class extends from Shape class and represents a Rhombus shape. During its instantiation, it receives the following parameters:

lines: determines the grid size.
char: determines the grid character.

grid width and height will be approximately the same size.

💬 The variable width can be calculated directly based on the lines parameter, however, I decided to calculate it based on the center variable. Sometimes I sacrifice performance for code clarity, but only when the loss of performance seems harmless to me. Expressing its width in function of its center makes things clearer than expressing its width in function of its height.

  • ComposedShape.kt

ComposedShape class is a special type of Shape since it is determined by merging 2 other Shapes.

The enum class Operation defines all the operations that can be applied on the shapes involved in the merger. These operations are UNION and INTERSECTION.

grid width and height depend on the grids involved in the merging operation.

  • Space.kt

Space class defines an object by applying the Singleton design pattern. It extends from Shape class so it can be added to the Panel. This object represents an “empty” space and it will be used as a Shape separator. Since this object’s composition doesn’t vary, it can be reused which makes it a good candidate for a Singleton instead of instantiating a new object every time a separator is required.

WIDTH constant determines its CharArray line length.

grid will return an empty CharArray.

line defines a lambda that always returns a CharArray of length WIDTH filled with WHITE_SPACE characters.

‘Container’ package

There is only one class whose role is to contain a collection of geometric shapes to print them to the console.

  • Panel.kt

shapes property defines a list that contains all the Shapes added to the Panel.

addShape function adds a Shape to the list.

print function prints out to the console all the Shapes from the list, one line at a time.

Undoubtedly, Shapes-DSL project could implement more geometric shapes and could even be improved. For example, we can prevent the Space object to be used to create a ComposedShape. However, the goal of this series of articles is to create a DSL on top of existing code, assuming that we can’t modify it. In the next article, we’re going to start coding our DSL.

💬 If you enjoyed this article, you can show your appreciation by buying me a coffee at the link below. Thanks for reading and for your support.

--

--

Glenn Sandoval
Kotlin and Kotlin for Android

I’m a software developer who loves learning and making new things all the time. I especially like mobile technology.