Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a new example #16

Merged
merged 7 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc_src/demos/demo_accessors_generator.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Generate setters and getters for private fields
===============================================

Please find demo code in our `GitHub <https://github.com/JhnW/devana/tree/main/examples/accesor_generator>`_.

This demo program automatically generates setter and getter methods for private fields within C++ classes. The process specifically targets header files.
3 changes: 2 additions & 1 deletion doc_src/how_to_start.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,5 @@ Examples
:maxdepth: 1

demos/demo_include_map
demos/demo_meta_info_enum
demos/demo_meta_info_enum
demos/demo_accessors_generator
22 changes: 22 additions & 0 deletions examples/accessors_generator/input/car.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#ifndef CAR_H
#define CAR_H

#include <iostream>
#include <string>

class Car {
private:
std::string brand;
float speed;

// devana: ignore-attributes
bool working;
public:
[[maybe_unused]]
xXenvy marked this conversation as resolved.
Show resolved Hide resolved
void speedUp() {
std::cout << "Speeeeed!" << std::endl;
setSpeed(speed + 10.1); // The setSpeed method will be generated.
}
};

#endif //CAR_H
31 changes: 31 additions & 0 deletions examples/accessors_generator/input/human.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#ifndef HUMAN_H
#define HUMAN_H

#include <iostream>
#include <string>

class Human {
private:
// devana: ignore-attributes
std::string name;

std::string lastName;

// devana: custom-name=Age
int _age;

// devana: ignore-field
double height;

double weight;
public:
Human(const std::string &name, const std::string &lastName, int age, double height, double weight):
name(name), lastName(lastName), _age(age), height(height), weight(weight) {}

[[maybe_unused]]
void say() const {
std::cout << "Hello! My name is " << getName() << std::endl; // The getName method will be generated.
}
};

#endif //HUMAN_H
126 changes: 126 additions & 0 deletions examples/accessors_generator/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import os
from typing import Iterable, Tuple, Optional

from devana.syntax_abstraction.classinfo import MethodInfo, Comment, FunctionInfo, AccessSpecifier, ClassInfo, FieldInfo, ConstructorInfo
from devana.syntax_abstraction.organizers import ModuleFilter, SourceModule, SourceFile, SourceFileType, IncludeInfo
from devana.code_generation.printers.default.defaultprinter import create_default_printer, CodePrinter
from devana.syntax_abstraction.functioninfo import BasicType, FunctionModification
from devana.syntax_abstraction.attribute import Attribute, AttributeDeclaration
from devana.syntax_abstraction.typeexpression import TypeModification


def get_devana_comments(comment: Comment) -> Iterable[str]:
# Function to get all devana comments properly formatted.
for comment_source in comment.text:
if (formatted_comment := comment_source.lstrip()).startswith("devana:"):
yield formatted_comment


def get_field_name(field: FieldInfo) -> str:
# Function to retrieve the field name.
name: str = field.name.capitalize()

if field.associated_comment:
for text in get_devana_comments(field.associated_comment):
if text.startswith("devana: custom-name="):
name = text.split("=")[1]
break

return name


def create_field_methods(field: FieldInfo) -> Tuple[MethodInfo, MethodInfo]:
# Function to generate setter and getter for field.
def should_ignore_attributes(comment: Optional[Comment]) -> bool:
return "devana: ignore-attributes" in get_devana_comments(comment) if comment else False

field_name: str = get_field_name(field)
maybe_unused_attribute: Attribute = Attribute("maybe_unused")
nodiscard_attribute: Attribute = Attribute("nodiscard")

field_setter: MethodInfo = MethodInfo()
field_setter.body = f"{field.name} = new{field.name.capitalize()};"
field_setter.access_specifier = AccessSpecifier.PUBLIC
field_setter.name = f"set{field_name}"
field_setter.return_type = BasicType.VOID

setter_arg: FunctionInfo.Argument = FunctionInfo.Argument()
setter_arg.name = f"new{field.name.capitalize()}"
setter_arg.type.details = field.type
if field.type.name == "string":
setter_arg.type.modification |= TypeModification.CONST | TypeModification.REFERENCE

field_setter.arguments.append(setter_arg)

field_getter: MethodInfo = MethodInfo()
field_getter.body = f"return {field.name};"
field_getter.access_specifier = AccessSpecifier.PUBLIC
field_getter.name = f"get{field_name}"
field_getter.return_type = field.type
field_getter.modification |= FunctionModification.CONST

if not should_ignore_attributes(field.associated_comment):
field_getter.attributes.append(
AttributeDeclaration([nodiscard_attribute, maybe_unused_attribute])
)
field_setter.attributes.append(
AttributeDeclaration([maybe_unused_attribute])
)

return field_setter, field_getter


def create_class_methods(class_info: ClassInfo):
# Function to generate methods for all class fields.
def should_ignore_field(comment: Optional[Comment]) -> bool:
return "devana: ignore-field" in get_devana_comments(comment) if comment else False

for constructor in filter(lambda constr: isinstance(constr, ConstructorInfo), class_info.constructors):
if constructor.body == "{}":
# We need to define body (as empty in this case) to say devana: it is function definition.
constructor.body = ""

for private_field in filter(lambda field: isinstance(field, FieldInfo), class_info.private):
if should_ignore_field(private_field.associated_comment):
continue

class_info.content.extend(create_field_methods(field=private_field))


def load_files_content() -> Iterable[Tuple[str, Iterable[ClassInfo]]]:
xXenvy marked this conversation as resolved.
Show resolved Hide resolved
module_filter: ModuleFilter = ModuleFilter(allowed_filter=[".h"]) # Accept only header files.
source: SourceModule = SourceModule("HEADERS", "./input", module_filter=module_filter)
for file in source.files:
yield file.name, filter(lambda element: isinstance(element, ClassInfo), file.content)


def main():
if not os.path.exists("./output"):
os.makedirs(os.path.dirname("./output/"))

printer: CodePrinter = create_default_printer()

iostream_include = IncludeInfo()
iostream_include.value = "iostream"
iostream_include.is_standard = True # to write this as <iostream> instead of "iostream"

string_include = IncludeInfo()
string_include.value = "string"
string_include.is_standard = True # to write this as <string> instead of "string"

for file_name, file_content in load_files_content():
header_file: SourceFile = SourceFile()
header_file.type = SourceFileType.HEADER
header_file.header_guard = file_name.upper().replace(".", "_")
header_file.includes.extend([string_include, iostream_include])

for class_info in file_content:
create_class_methods(class_info)
header_file.content.append(class_info)

with open(f"./output/{file_name}", "w+", encoding="utf-8") as file:
file.write(printer.print(header_file))


if __name__ == "__main__":
main()
2 changes: 1 addition & 1 deletion examples/meta_enum/input/enums.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ enum class AnimalPetKind

enum class AnimalPetState
{
OK = 0x0
OK = 0x0,
HUNGRY = 0x2,
SICK = 0x4,
ANGRY = 0x6
Expand Down