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
//! 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::codegen::{CodeGen, PythonOptions, CodeGenContext};
use crate::tree::{Statement, StatementType, Name, ExprType};
use crate::symbols::{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,
}
}
}