Generating Code with QFace - Part 1
QFace provides a set of IDL parsers which can help you convert easy-to-read interface definitions into compiling code!
It was written by a Qt/QML developer so it provides many useful features that are Qt-compatible. It was also recently ingested into the Qt Infotainment package
IDL - Interface Description Language
An IDL helps you describe code in a language independent way.
We can refer to the "meta-language" that QFace provides as QFace
Let's Describe!
Let's think about describing a Human Resources service that manages Employees.
We can call this service – the EmployeeService
.
Let's define an object, or struct, called Employee
that encapsulates one individual that works for this company. This struct can define the employee's name, age, and role. An employee's Role
can be a list of positions within the company, i.e. Engineers, Managers, or Executives.
This EmployeeService
might have a list of employees, or employeeList
, which can be a list of type Employee
.
This EmployeeService
may also have two operations, one to hire and one to fire an employee. Let's call these methods hireEmployee(..) and fireEmployee(..); respectively. Both methods take a parameter of type Employee
.
We can also create a signal that can be emitted whenever a new employee is hired, employeeHired(..)
. We can pass more context into this signal, e.g. the Employee
that was hired.
Let's write this scenario as a QFace file:
module qml.guide.example 1.0
interface EmployeeService {
int employeeCount
list<Employee> employeeList
void hireEmployee(Employee employee)
void fireEmployee(Employee employee)
signal employeeHired(Employee employee)
}
struct Employee {
string firstName
string lastName
int age
Role role
}
enum Role {
Unknown,
SoftwareEngineer,
HardwareEngineer,
QualityEngineer,
ProjectManager,
Manager,
Executive
}
An important thing to remember at this stage is that the file above does follow QFace language rules, so every keyword (module, interface, struct, enum) is provided by QFace. The collection of the possible keywords that QFace provides will be referred to as the grammar of QFace.
Please read about the grammar that QFace supports here.
Getting started with the QFace generators
QFace generators are written in python3
.
Start by using pip (python package manager) to install qface and its dependencies:
python3 -m pip install qface
Once qface is done installing, check the version:
$ python3 -m pip freeze | grep qface
qface==2.0.0
I have noticed some API changes between 1.X and 2.0, so please keep that in mind if the version of qface you are using is newer than this tutorial
Let's code!
Save the above EmployeeService
and friends example to a file called qml.guide.example.qface
. Notice how we try to keep the file name the same as the module name. This simplifies searching for right module when you have hundreds of QFace documents.
qml.guide.example.qface should look like this:
module qml.guide.example 1.0
interface EmployeeService {
int employeeCount
list<Employee> employeeList
void hireEmployee(Employee employee)
void fireEmployee(Employee employee)
signal employeeHired(Employee employee)
}
struct Employee {
string firstName
string lastName
int age
Role role
}
enum Role {
Unknown,
SoftwareEngineer,
HardwareEngineer,
QualityEngineer,
ProjectManager,
Manager,
Executive
}
Big Picture
This quasi-architecture-flow diagram is straight from the QFace manual. As you can see, we need to take our QFace Document (Interface Definition) which describes an EmployeeService
, and feed that into Your Generator which uses the QFace (Generator Library) to finally produce Your Code.
Your Generator
Let's continue our escapade by writing the Your Generator part of the diagram above. We need to write a python3 script that uses the qface package we just downloaded from pip.
Open a new file and name it generator.py
:
#!/usr/bin/env python3
from qface.generator import FileSystem, Generator
print("My QFace Generator goes here!")
Run it and make sure you have no errors. This confirms if pip installed the qface package correctly:
python3 generator.py
You can also set the script to executable so you can forever omit the python3
prefix to your command (offtopic: works because of the shebang):
chmod u+x generator.py
./generator.py
Now that we know qface is installed correctly, let's continue writing our generator. The contents of this generator are inspired by the QFace manual:
#!/usr/bin/env python3
from qface.generator import FileSystem, Generator
def generate(input, output):
# parse the interface files
system = FileSystem.parse(input)
# ...
# call the generation function
generate('qml.guide.example.qface', 'generated_code_output')
What's going on here?
Let's start from the last line:
generate('qml.guide.example.qface', 'generated_code_output')
We are sending in our QFace document from above, and a folder name of where our final code output will be placed.
Inside the generate
method, we are using qface.FileSystem
to parse the QFace document and produce a variable called system
which is of type System. This variable (shown in the diagram as ctx
) is a python object that holds the grammar of our QFace document.
By reading the API documentation of System, we can see it has a property called modules
.
We can use python's pprint
package to print out the modules
property to see what's inside:
#!/usr/bin/env python3
from qface.generator import FileSystem, Generator
from pprint import pprint
def generate(input, output):
# parse the interface files
system = FileSystem.parse(input)
pprint(system.modules)
# call the generation function
generate('qml.guide.example.qface', 'generated_code_output')
You should see something like this:
qface uses language profile: EProfile.FULL
odict_values([<<class 'qface.idl.domain.Module'> name=qml.guide.example>])
Notice our module name is displayed, qml.guide.examples.
It is now easy to see the type hierarchy present inside the System
type:
- System
- -> Module(s)
- ->-> Interface(s)
- ->-> Struct(s)
- ->-> Enum(s)
All of these types are stored as lists of dictionaries (maps) so we can easily iterate through them and print out the grammar of our QFace document:
#!/usr/bin/env python3
from qface.generator import FileSystem, Generator
from pprint import pprint
def generate(input, output):
# parse the interface files
system = FileSystem.parse(input)
for module in system.modules:
print("module.name = " + module.name)
for interface in module.interfaces:
print("interface.name = " + interface.name)
for struct in module.structs:
print("struct.name = " + struct.name)
for enum in module.enums:
print("enum.name = " + enum.name)
# call the generation function
generate('qml.guide.example.qface', 'generated_code_output')
Run this and you will see familiar names:
$ ./generator.py
qface uses language profile: EProfile.FULL
module.name = qml.guide.example
interface.name = EmployeeService
struct.name = Employee
enum.name = Role
Success! Our generator has reached it's first milestone. Sit back and reflect on what just happened.
We took a seemingly unknown IDL and turned it into grammatically significant types: modules (as a collection of interfaces, structs, and enums).
Just imagine the potential of evolving a simple generator like this to something grander!
Stay tuned for the next post in this series!
Subscribe to QML Guide
Get the latest posts delivered right to your inbox