1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
//! A lot of languages, Python included, have a concept of a class, which combines the definition of a data type with
//! an interface. In dynamic languages like Python, the class itself is a memory object, that can be permutated at runtime,
//! however, this is probably usually a bad idea. Classes can contain:
//! 1. Methods (special functions)
//! 2. properties (attributes of the data element)
//! 3. Base classes (for inheritace)
//! 4. static data
//! 5. Additional classes
//!
//! There is one construct in Rust that can emcompass all of these things: a module. So, we use modules to model classes
//! following these rules:
//! 1. The module is given the name of the class. Contrary to other Rust modules, this is typically SnakeCase.
//! 2. The main data type defined by the class is a struct inside the module, and called Data.
//! 3. The Data struct can take two forms:
//! a. If the properties of the class can be fully inferred, Data will be a simple struct and the attributes will be defined as fields of the struct.
//! b. If the properties of the class cannot be fully inferred, such as if the class is accessed as a dictionary, Data will be a HashMap<String, _>,
//! and the values will be accessed through it.
//! 4. Static data will be declared with lazy_static inside the module.
//! 5. Additional classes will be nested inside the module, and therefore they appear as modules inside a module.
//! 6. Each class also contains a trait, named Cls, which is used in inheritance.
//! 7. Each method of the class in Python will be translated to have a prototype in Cls. If it is possible to implement the method as a default method,
//! it will be, otherwise (if the method refers to attributes of the class), a prototype will be added to Cls, and the implementation will be done inside
//! an impl Cls for Data block.
//! 8. Cls will implement Clone, Default.
use pyo3::{FromPyObject};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use crate::{
Statement, StatementType, Name, ExprType,
CodeGen, PythonOptions, CodeGenContext,
SymbolTableScopes, SymbolTableNode,
};
use log::debug;
use serde::{Serialize, Deserialize};
#[derive(Clone, Debug, Default, FromPyObject, Serialize, Deserialize, PartialEq)]
pub struct ClassDef {
pub name: String,
pub bases: Vec<Name>,
pub keywords: Vec<String>,
pub body: Vec<Statement>,
}
impl CodeGen for ClassDef {
type Context = CodeGenContext;
type Options = PythonOptions;
type SymbolTable = SymbolTableScopes;
fn find_symbols(self, symbols: Self::SymbolTable) -> Self::SymbolTable {
let mut symbols = symbols;
symbols.insert(self.name.clone(), SymbolTableNode::ClassDef(self.clone()));
symbols
}
fn to_rust(self, _ctx: Self::Context, options: Self::Options, symbols: Self::SymbolTable) -> Result<TokenStream, Box<dyn std::error::Error>> {
let mut streams = TokenStream::new();
let class_name = format_ident!("{}", self.name);
// The Python convention is that functions that begin with a single underscore,
// it's private. Otherwise, it's public. We formalize that by default.
let visibility = if self.name.starts_with("_") && !self.name.starts_with("__") {
format_ident!("")
} else if self.name.starts_with("__") && self.name.ends_with("__") {
format_ident!("pub(crate)")
} else {
format_ident!("pub")
};
// bases will be empty if there are no base classes, which prevents any base traits
// being added, and also prevents the : from being emitted.
let mut bases = TokenStream::new();
if self.bases.len() > 0 {
bases.extend(quote!(:));
let base_name = format_ident!("{}", self.bases[0].id);
bases.extend(quote!(#base_name::Cls));
for base in &self.bases[1..] {
bases.extend(quote!(+));
let base_name = format_ident!("{}", base.id);
bases.extend(quote!(#base_name));
}
}
for s in self.body.clone() {
streams.extend(s.clone().to_rust(CodeGenContext::Class, options.clone(), symbols.clone()).expect(format!("Failed to parse statement {:?}", s).as_str()));
}
let _docstring = if let Some(d) = self.get_docstring() {
format!("/// {}", d)
} else { "".to_string() };
/*
let class = quote!{
#visibility trait #class_name #bases {
#streams
}
};*/
let class = quote!{
#visibility mod #class_name {
use super::*;
#visibility trait Cls #bases {
#streams
}
#[derive(Clone, Default)]
#visibility struct Data {
}
impl Cls for Data {}
}
};
debug!("class: {}", class);
Ok(class)
}
fn get_docstring(&self) -> Option<String> {
let expr = self.body[0].clone();
match expr.statement {
StatementType::Expr(e) => {
match e.value {
ExprType::Constant(c) => Some(c.to_string()),
_ => None,
}
},
_ => None,
}
}
}