Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/adrian-samoticha/zmija
Simple universal code generation.
https://github.com/adrian-samoticha/zmija
code-generation python python3
Last synced: 8 days ago
JSON representation
Simple universal code generation.
- Host: GitHub
- URL: https://github.com/adrian-samoticha/zmija
- Owner: Adrian-Samoticha
- License: mit
- Created: 2021-07-04T16:12:04.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2021-10-21T16:18:34.000Z (about 3 years ago)
- Last Synced: 2024-10-19T19:39:05.808Z (3 months ago)
- Topics: code-generation, python, python3
- Language: Python
- Homepage:
- Size: 69.3 KB
- Stars: 3
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
![Zmija](logo/logo.svg "Zmija")
# Żmija
**Żmija** is a simple universal code generation tool. It is intended to be used as a means to generate code that is both efficient and easily maintainable.It is intended to be used in embedded systems with limited resources, however it can be used anywhere else as well.
# Usage
**Żmija** lets you define sections in your code where code is generated automatically in accordance to a provided Python script. Such a section typically looks like this:```Python
/* ~ZMIJA.GENERATOR:
def declare(variables):
pass
def init(variables):
pass
def generate(variables):
return ""
*/// ~ZMIJA.GENERATED_CODE:// ~ZMIJA.END
```The section is defined inside a multi-line comment as to not affect the compilation of the code it is located in. **Żmija** supports any languge, including those that have non C-style comment styles (hence it is universal).
This is what the same section might look like inside a Lua script, for example:
```Python
--[[ ~ZMIJA.GENERATOR:
def declare(variables):
pass
def init(variables):
pass
def generate(variables):
return ""
]]-- ~ZMIJA.GENERATED_CODE:-- ~ZMIJA.END
```Each section consists of a `declare`-function, an `init`-function and a `generate`-function. Each function is provided with the `variables` argument, which is a dictionary that is intended to be used for the storage of variables.
The `declare`-function is executed first. It is meant for variable declaration and should only reference its own variables.
The `init`-function is meant to initialize variables, including those of other sections. It is executed only after the `declare`-function has been executed for all sections in the project.
The `generate`-function returns the generated code for the section it is located in. It is executed only after the `declare` and `init`-functions of all sections have been executed.
*Note:* Empty functions can safely be removed.
Run `python3 ./src/zmija.py /path/to/your/project/directory/` to perform the code generation. The generated code will be placed between the `~ZMIJA.GENERATED_CODE:` and the `~ZMIJA.END` lines.
# Help output
```
Zmija. Simple universal code generation.Usage:
zmija.py path
zmija.py path -d | --delete
zmija.py path -c | --check-only
zmija.py path --config-path="path/to/config"
zmija.py -h | --help
Options:
-h --help Show this screen.
-d --delete Delete all generated code.
-c --check-only Check Python code for syntax and runtime errors without writing the
changes to file.
-u --unsafe Skip the test pass. May cause data loss if the Python code raises
exceptions, but offers better performance. Use with caution.
--config-path Provides a path to a configuration file.
Config:
file_filter(file_path) A function intended to filter file paths. Any file path
for which this function returns False is ignored.
```
# Config
You can define a file path filter function inside a config file, such that certain files are ignored by **Żmija**.Here's what an example config file may look like:
```python
def file_filter(file_path):
return file_path.endswith('.cpp') or file_path.endswith('.h')
```The file path of the config file needs to be supplied using the `--config-file` argument, like so:
`python3 ./src/zmija.py /path/to/your/project/directory/ --config-file="/path/to/your/config/file"`
# Example
Say you have two modules, a ButtonController and a LedController. You would like to implement the observer pattern to allow the ButtonController to communicate with the LedController without depending on it.The following C++ code implements this. It is a simple example where pressing the button toggles the LED.
```C++
#include
#include
#include// This would typically go into .h files:
struct ButtonController {
private:
// Callbacks are functions that will be called
// when the button is pressed. Notice how the
// vector is constructed at runtime and held in
// RAM.
std::vector> callbacks;public:
void on_button_pressed();
void register_callback(std::function cb);
};struct LedController {
public:
void toggle_led();LedController();
};// This would typically go into .cpp files:
ButtonController *button_controller;
LedController *led_controller;// This function is meant to be automatically
// called whenever a button is pressed.
void ButtonController::on_button_pressed() {
// call all registered callbacks
for (auto &cb : callbacks) cb();
}// This function is meant to be called by other
// modules that would like to react to button
// presses.
void ButtonController::register_callback(std::function cb) {
callbacks.push_back(cb);
}void LedController::toggle_led() {
printf("LED toggled.\n");
}LedController::LedController() {
// Registering a new callback consumes precious RAM.
button_controller->register_callback([this]() {
toggle_led();
});
}int main() {
button_controller = new ButtonController();
led_controller = new LedController();button_controller->on_button_pressed();
return 0;
}
```Calling the `main()` function will print `LED toggled.` to the console, as intended.
However, the ButtonController's `callbacks` vector is built during runtime and held in RAM. This causes an unnecessary overhead regarding both memory usage and execution speed.
Since the registered callbacks do not change after they have been registered, it may be beneficial to register them during compile time instead.
The following C++ code attempts to achieve this by using **Żmija** to generate the callbacks during compile time:
```C++
#include// This would typically go into .h files:
struct ButtonController {
public:
void on_button_pressed();
};struct LedController {
public:
void toggle_led();LedController();
};// This would typically go into .cpp files:
ButtonController *button_controller;
LedController *led_controller;// This function is meant to be automatically
// called whenever a button is pressed.
void ButtonController::on_button_pressed() {
/* ~ZMIJA.GENERATOR:
def declare(variables):
# Declare a new list called "on_button_pressed".
# This list will contain calls to callback functions
# in string form.
variables["on_button_pressed"] = []
def init(variables):
# Nothing to do. This function can safely be removed.
pass
def generate(variables):
# Return a string containing all callback calls,
# separated by a newline character.
return "\n".join(variables["on_button_pressed"])
*/// ~ZMIJA.GENERATED_CODE:
// ~ZMIJA.END
}void LedController::toggle_led() {
printf("LED toggled.\n");
}LedController::LedController() {
/* ~ZMIJA.GENERATOR:
def declare(variables):
# Nothing to do. This function can safely be removed.
pass
def init(variables):
# Add a callback call in string form.
# This string will be added to the ButtonController's
# generated code.
variables["on_button_pressed"].append("led_controller->toggle_led();")
def generate(variables):
# Nothing to do. This function can safely be removed.
return ''
*/// ~ZMIJA.GENERATED_CODE:
// ~ZMIJA.END
}int main() {
button_controller = new ButtonController();
led_controller = new LedController();button_controller->on_button_pressed();
return 0;
}
```
Let's run **Żmija**:
```console
python3 ./src/zmija.py /path/to/your/project/directory/
```
This is what our newly generated .cpp file looks like now:
```C++
#include// This would typically go into .h files:
struct ButtonController {
public:
void on_button_pressed();
};struct LedController {
public:
void toggle_led();LedController();
};// This would typically go into .cpp files:
ButtonController *button_controller;
LedController *led_controller;// This function is meant to be automatically
// called whenever a button is pressed.
void ButtonController::on_button_pressed() {
/* ~ZMIJA.GENERATOR:
def declare(variables):
# Declare a new list called "on_button_pressed".
# This list will contain calls to callback functions
# in string form.
variables["on_button_pressed"] = []
def init(variables):
# Nothing to do. This function can safely be removed.
pass
def generate(variables):
# Return a string containing all callback calls,
# separated by a newline character.
return "\n".join(variables["on_button_pressed"])
*/// ~ZMIJA.GENERATED_CODE:
led_controller->toggle_led();
// ~ZMIJA.END
}void LedController::toggle_led() {
printf("LED toggled.\n");
}LedController::LedController() {
/* ~ZMIJA.GENERATOR:
def declare(variables):
# Nothing to do. This function can safely be removed.
pass
def init(variables):
# Add a callback call in string form.
# This string will be added to the ButtonController's
# generated code.
variables["on_button_pressed"].append("led_controller->toggle_led();")
def generate(variables):
# Nothing to do. This function can safely be removed.
return ''
*/// ~ZMIJA.GENERATED_CODE:
// ~ZMIJA.END
}int main() {
button_controller = new ButtonController();
led_controller = new LedController();button_controller->on_button_pressed();
return 0;
}
```As you can see, **Żmija** has generated the `led_controller->toggle_led();`-line, just as intended.