Adding rust support
This commit is contained in:
@ -2,24 +2,37 @@ use std::{collections::HashMap, path::PathBuf};
|
|||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
use crate::ir::{EnumDefinition, ServiceDefinition, TypeDefinition};
|
use crate::{
|
||||||
|
ir::{EnumDefinition, ServiceDefinition, TypeDefinition},
|
||||||
|
IR,
|
||||||
|
};
|
||||||
|
|
||||||
pub trait Compile {
|
pub trait Compile {
|
||||||
|
fn new(options: &HashMap<String, String>) -> Result<Self>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
|
||||||
fn name(&self) -> String;
|
fn name(&self) -> String;
|
||||||
|
|
||||||
fn start(&mut self, ctx: &mut CompileContext, options: HashMap<String, String>) -> Result<()>;
|
fn start(&mut self, ctx: &mut CompileContext) -> Result<()>;
|
||||||
|
|
||||||
fn generate_type(&mut self, ctx: &mut CompileContext, definition: TypeDefinition)
|
fn generate_type(
|
||||||
-> Result<()>;
|
&mut self,
|
||||||
fn generate_enum(&mut self, ctx: &mut CompileContext, definition: EnumDefinition)
|
ctx: &mut CompileContext,
|
||||||
-> Result<()>;
|
definition: &TypeDefinition,
|
||||||
|
) -> Result<()>;
|
||||||
|
fn generate_enum(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut CompileContext,
|
||||||
|
definition: &EnumDefinition,
|
||||||
|
) -> Result<()>;
|
||||||
fn generate_service(
|
fn generate_service(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &mut CompileContext,
|
ctx: &mut CompileContext,
|
||||||
definition: ServiceDefinition,
|
definition: &ServiceDefinition,
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
|
|
||||||
fn finalize(&mut self, ctx: &mut CompileContext) -> Result<()>;
|
fn finalize(&mut self, ctx: &mut CompileContext, ir: &IR) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CompileContext {
|
pub struct CompileContext {
|
||||||
@ -42,34 +55,57 @@ impl CompileContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct FileGenerator {
|
pub struct FileGenerator {
|
||||||
content: String,
|
content: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileGenerator {
|
impl FileGenerator {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
FileGenerator {
|
FileGenerator {
|
||||||
content: String::new(),
|
content: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn a<T: ToString>(&mut self, indent: u32, content: T) {
|
pub fn a<T: ToString>(&mut self, indent: usize, content: T) {
|
||||||
for _ in 0..indent {
|
let line = " ".repeat(indent) + &content.to_string();
|
||||||
self.content.push_str(" ");
|
self.content.push(line);
|
||||||
}
|
}
|
||||||
self.content.push_str(&content.to_string());
|
|
||||||
self.content.push_str("\n");
|
pub fn a0<T: ToString>(&mut self, content: T) {
|
||||||
|
self.a(0, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn a1<T: ToString>(&mut self, content: T) {
|
||||||
|
self.a(1, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn a2<T: ToString>(&mut self, content: T) {
|
||||||
|
self.a(2, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn a3<T: ToString>(&mut self, content: T) {
|
||||||
|
self.a(3, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn a4<T: ToString>(&mut self, content: T) {
|
||||||
|
self.a(4, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn a5<T: ToString>(&mut self, content: T) {
|
||||||
|
self.a(5, content);
|
||||||
|
}
|
||||||
|
pub fn a6<T: ToString>(&mut self, content: T) {
|
||||||
|
self.a(6, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_line(&mut self, line: &str) {
|
pub fn add_line(&mut self, line: &str) {
|
||||||
self.content.push_str(line);
|
self.content.push(line.to_string());
|
||||||
self.content.push_str("\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_content(&mut self) -> String {
|
pub fn get_content(&self) -> String {
|
||||||
self.content.clone()
|
self.content.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_content(self) -> String {
|
pub fn into_content(self) -> String {
|
||||||
self.content
|
self.get_content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ use crate::parser::{
|
|||||||
EnumStatement, Node, ParserPosition, RootNode, ServiceStatement, TypeStatement,
|
EnumStatement, Node, ParserPosition, RootNode, ServiceStatement, TypeStatement,
|
||||||
};
|
};
|
||||||
|
|
||||||
static BUILT_INS: [&str; 5] = ["int", "float", "string", "boolean", "bytes"];
|
static BUILT_INS: [&str; 6] = ["int", "float", "string", "boolean", "bytes", "void"];
|
||||||
|
|
||||||
pub trait Definition {
|
pub trait Definition {
|
||||||
fn get_position(&self) -> ParserPosition;
|
fn get_position(&self) -> ParserPosition;
|
||||||
@ -37,17 +37,53 @@ pub enum Type {
|
|||||||
String,
|
String,
|
||||||
Bool,
|
Bool,
|
||||||
Bytes,
|
Bytes,
|
||||||
|
Void,
|
||||||
Custom(String),
|
Custom(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToString for Type {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Type::Int => "int".to_string(),
|
||||||
|
Type::Float => "float".to_string(),
|
||||||
|
Type::String => "string".to_string(),
|
||||||
|
Type::Bool => "bool".to_string(),
|
||||||
|
Type::Bytes => "bytes".to_string(),
|
||||||
|
Type::Void => "void".to_string(),
|
||||||
|
Type::Custom(name) => name.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Hash for Type {
|
impl Hash for Type {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
match self {
|
self.to_string().hash(state);
|
||||||
Type::Int => "int".hash(state),
|
}
|
||||||
Type::Float => "float".hash(state),
|
}
|
||||||
Type::String => "string".hash(state),
|
|
||||||
Type::Bool => "bool".hash(state),
|
impl From<&String> for Type {
|
||||||
Type::Bytes => "bytes".hash(state),
|
fn from(value: &String) -> Self {
|
||||||
Type::Custom(name) => name.hash(state),
|
Self::from(value.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Type {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Self::from(value.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Type {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
match s {
|
||||||
|
"int" => Type::Int,
|
||||||
|
"float" => Type::Float,
|
||||||
|
"string" => Type::String,
|
||||||
|
"bool" => Type::Bool,
|
||||||
|
"boolean" => Type::Bool,
|
||||||
|
"bytes" => Type::Bytes,
|
||||||
|
"void" => Type::Void,
|
||||||
|
_ => Type::Custom(s.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,17 +173,6 @@ pub struct MethodDecorators {
|
|||||||
pub return_description: Option<String>,
|
pub return_description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn typename_to_type(name: &str) -> Type {
|
|
||||||
match name {
|
|
||||||
"int" => Type::Int,
|
|
||||||
"float" => Type::Float,
|
|
||||||
"string" => Type::String,
|
|
||||||
"boolean" => Type::Bool,
|
|
||||||
"bool" => Type::Bool,
|
|
||||||
_ => Type::Custom(name.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_type(stmt: &TypeStatement) -> Result<TypeDefinition> {
|
fn build_type(stmt: &TypeStatement) -> Result<TypeDefinition> {
|
||||||
let mut typedef = TypeDefinition {
|
let mut typedef = TypeDefinition {
|
||||||
position: stmt.position.clone(),
|
position: stmt.position.clone(),
|
||||||
@ -157,7 +182,7 @@ fn build_type(stmt: &TypeStatement) -> Result<TypeDefinition> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for field in &stmt.fields {
|
for field in &stmt.fields {
|
||||||
let typ = typename_to_type(&field.fieldtype);
|
let typ = Type::from(&field.fieldtype);
|
||||||
typedef.depends.insert(typ.clone());
|
typedef.depends.insert(typ.clone());
|
||||||
|
|
||||||
if let Some(maptype) = &field.map {
|
if let Some(maptype) = &field.map {
|
||||||
@ -171,7 +196,7 @@ fn build_type(stmt: &TypeStatement) -> Result<TypeDefinition> {
|
|||||||
typ: typ.clone(),
|
typ: typ.clone(),
|
||||||
array: field.array,
|
array: field.array,
|
||||||
optional: field.optional,
|
optional: field.optional,
|
||||||
map: field.map.as_ref().map(|s| typename_to_type(s)),
|
map: field.map.as_ref().map(|s| Type::from(s)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,8 +247,8 @@ fn build_service(stmt: &ServiceStatement) -> Result<ServiceDefinition> {
|
|||||||
name: method.name.clone(),
|
name: method.name.clone(),
|
||||||
inputs: Vec::new(),
|
inputs: Vec::new(),
|
||||||
output: method.return_type.as_ref().map(|rt| {
|
output: method.return_type.as_ref().map(|rt| {
|
||||||
let typ = typename_to_type(&rt.fieldtype);
|
let typ = Type::from(&rt.fieldtype);
|
||||||
if typ != Type::Custom("void".to_string()) {
|
if typ != Type::Void {
|
||||||
servdef.depends.insert(typ.clone());
|
servdef.depends.insert(typ.clone());
|
||||||
}
|
}
|
||||||
MethodOutput {
|
MethodOutput {
|
||||||
@ -240,7 +265,7 @@ fn build_service(stmt: &ServiceStatement) -> Result<ServiceDefinition> {
|
|||||||
|
|
||||||
let mut optional_starts = false;
|
let mut optional_starts = false;
|
||||||
for inp in &method.inputs {
|
for inp in &method.inputs {
|
||||||
let typ = typename_to_type(&inp.fieldtype);
|
let typ = Type::from(&inp.fieldtype);
|
||||||
servdef.depends.insert(typ.clone());
|
servdef.depends.insert(typ.clone());
|
||||||
|
|
||||||
if optional_starts && !inp.optional {
|
if optional_starts && !inp.optional {
|
||||||
|
@ -13,6 +13,11 @@ pub use tokenizer::{tokenize, Token, TokenError, TokenPosition, TokenType};
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use crate::{
|
||||||
|
compile::{Compile, CompileContext},
|
||||||
|
targets::{self, rust::RustCompiler},
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[ctor::ctor]
|
#[ctor::ctor]
|
||||||
fn init() {
|
fn init() {
|
||||||
@ -27,4 +32,12 @@ mod test {
|
|||||||
|
|
||||||
println!("{:?}", ir);
|
println!("{:?}", ir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn generate_rust() {
|
||||||
|
let mut fp = crate::process::FileProcessor::new();
|
||||||
|
let ir = fp.start_compile("./test.jrpc").unwrap();
|
||||||
|
|
||||||
|
targets::compile::<RustCompiler>(ir, "./output/rust").unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1,28 @@
|
|||||||
mod rust;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
compile::{Compile, CompileContext},
|
||||||
|
IR,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod rust;
|
||||||
|
|
||||||
|
pub fn compile<T: Compile>(ir: IR, output: &str) -> Result<()> {
|
||||||
|
let mut ctx = CompileContext::new(output);
|
||||||
|
let mut compiler = T::new(&ir.options)?;
|
||||||
|
compiler.start(&mut ctx)?;
|
||||||
|
|
||||||
|
for step in ir.steps.iter() {
|
||||||
|
match step {
|
||||||
|
crate::ir::Step::Type(definition) => compiler.generate_type(&mut ctx, &definition)?,
|
||||||
|
crate::ir::Step::Enum(definition) => compiler.generate_enum(&mut ctx, &definition)?,
|
||||||
|
crate::ir::Step::Service(definition) => {
|
||||||
|
compiler.generate_service(&mut ctx, &definition)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compiler.finalize(&mut ctx, &ir)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -3,25 +3,40 @@ use log::warn;
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use crate::compile::{Compile, CompileContext, FileGenerator};
|
use crate::compile::{Compile, CompileContext, FileGenerator};
|
||||||
use crate::ir::{EnumDefinition, ServiceDefinition, Type, TypeDefinition};
|
use crate::ir::{EnumDefinition, ServiceDefinition, Step, Type, TypeDefinition};
|
||||||
use crate::shared::Keywords;
|
use crate::shared::Keywords;
|
||||||
|
use crate::IR;
|
||||||
|
|
||||||
pub struct RustCompiler {
|
pub struct RustCompiler {
|
||||||
crate_name: String,
|
crate_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static RUST_KEYWORDS: [&'static str; 6] = ["type", "return", "static", "pub", "enum", "self"];
|
||||||
|
|
||||||
impl RustCompiler {
|
impl RustCompiler {
|
||||||
fn type_to_rust(typ: Type) -> String {
|
fn type_to_rust(typ: &Type) -> String {
|
||||||
match typ {
|
match typ {
|
||||||
Type::String => "String".to_string(),
|
Type::String => "String".to_string(),
|
||||||
Type::Int => "i64".to_string(),
|
Type::Int => "i64".to_string(),
|
||||||
Type::Float => "f64".to_string(),
|
Type::Float => "f64".to_string(),
|
||||||
Type::Bool => "bool".to_string(),
|
Type::Bool => "bool".to_string(),
|
||||||
Type::Bytes => "Vec<u8>".to_string(),
|
Type::Bytes => "Vec<u8>".to_string(),
|
||||||
Type::Custom(name) => name,
|
Type::Void => "()".to_string(),
|
||||||
|
Type::Custom(name) => name.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn type_to_rust_ext(typ: &Type, optional: bool, array: bool) -> String {
|
||||||
|
let mut result = Self::type_to_rust(typ);
|
||||||
|
if optional {
|
||||||
|
result = format!("Option<{}>", result);
|
||||||
|
}
|
||||||
|
if array {
|
||||||
|
result = format!("Vec<{}>", result);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
fn add_dependencies(
|
fn add_dependencies(
|
||||||
&mut self,
|
&mut self,
|
||||||
file: &mut FileGenerator,
|
file: &mut FileGenerator,
|
||||||
@ -30,18 +45,18 @@ impl RustCompiler {
|
|||||||
for dep in depends {
|
for dep in depends {
|
||||||
match dep {
|
match dep {
|
||||||
Type::Custom(name) => {
|
Type::Custom(name) => {
|
||||||
file.a(0, &format!("use crate::{};", name));
|
file.a0(&format!("use crate::{};", name));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file.a(0, "");
|
file.a0("");
|
||||||
file.a(0, "");
|
file.a0("");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fix_keyword_name(name: &str) -> String {
|
fn fix_keyword_name(name: &str) -> String {
|
||||||
if Keywords::is_keyword(name) {
|
if RUST_KEYWORDS.contains(&name) {
|
||||||
format!("{}_", name)
|
format!("{}_", name)
|
||||||
} else {
|
} else {
|
||||||
name.to_string()
|
name.to_string()
|
||||||
@ -69,23 +84,333 @@ impl RustCompiler {
|
|||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_service_lib(ctx: &CompileContext, ir: &IR) -> Result<()> {
|
||||||
|
let mut f = FileGenerator::new();
|
||||||
|
let mut fc = FileGenerator::new();
|
||||||
|
let mut fs = FileGenerator::new();
|
||||||
|
|
||||||
|
f.a0("pub mod base_lib;");
|
||||||
|
f.a0("pub use base_lib::{JRPCServer, JRPCClient, Result};");
|
||||||
|
|
||||||
|
for step in ir.steps.iter() {
|
||||||
|
match step {
|
||||||
|
Step::Type(def) => {
|
||||||
|
f.a0(format!("mod {};", Self::to_snake(&def.name)));
|
||||||
|
f.a(
|
||||||
|
0,
|
||||||
|
format!("pub use {}::{};", Self::to_snake(&def.name), def.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Step::Enum(def) => {
|
||||||
|
f.a0(format!("mod {};", Self::to_snake(&def.name)));
|
||||||
|
f.a(
|
||||||
|
0,
|
||||||
|
format!("pub use {}::{};", Self::to_snake(&def.name), def.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Step::Service(def) => {
|
||||||
|
fs.a0(format!("mod {};", Self::to_snake(&def.name)));
|
||||||
|
fs.a0(format!(
|
||||||
|
"pub use {}::{{ {}, {}Handler }};",
|
||||||
|
Self::to_snake(&def.name),
|
||||||
|
def.name,
|
||||||
|
def.name
|
||||||
|
));
|
||||||
|
|
||||||
|
fc.a0(format!("mod {};", Self::to_snake(&def.name)));
|
||||||
|
fc.a0(format!(
|
||||||
|
"pub use {}::{};",
|
||||||
|
Self::to_snake(&def.name),
|
||||||
|
def.name,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f.a0("pub mod server;");
|
||||||
|
f.a0("pub mod client;");
|
||||||
|
|
||||||
|
ctx.write_file("src/lib.rs", f.get_content())?;
|
||||||
|
ctx.write_file("src/server/mod.rs", fs.get_content())?;
|
||||||
|
ctx.write_file("src/client/mod.rs", fc.get_content())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_service_server(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut CompileContext,
|
||||||
|
definition: &ServiceDefinition,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut f = FileGenerator::new();
|
||||||
|
|
||||||
|
self.add_dependencies(&mut f, &definition.depends)?;
|
||||||
|
|
||||||
|
f.a0("use crate::base_lib::{JRPCServerService, JRPCRequest, Result};");
|
||||||
|
f.a0("use serde_json::Value;");
|
||||||
|
f.a0("use std::sync::Arc;");
|
||||||
|
f.a0("use async_trait::async_trait;");
|
||||||
|
|
||||||
|
f.a0("#[async_trait]");
|
||||||
|
f.a0(format!("pub trait {} {{", definition.name));
|
||||||
|
for method in definition.methods.iter() {
|
||||||
|
let params = method
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.map(|arg| {
|
||||||
|
format!(
|
||||||
|
"{}: {}",
|
||||||
|
Self::fix_keyword_name(&arg.name),
|
||||||
|
Self::type_to_rust_ext(&arg.typ, arg.optional, arg.array)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
let ret = method.output.as_ref().map_or_else(
|
||||||
|
|| "()".to_owned(),
|
||||||
|
|r| Self::type_to_rust_ext(&r.typ, false, r.array),
|
||||||
|
);
|
||||||
|
|
||||||
|
f.a1("#[allow(non_snake_case)]");
|
||||||
|
f.a(
|
||||||
|
1,
|
||||||
|
format!(
|
||||||
|
"async fn {}(&self, {}) -> Result<{}>;",
|
||||||
|
Self::fix_keyword_name(&method.name),
|
||||||
|
params,
|
||||||
|
ret
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
f.a0("}");
|
||||||
|
|
||||||
|
f.a0("");
|
||||||
|
f.a0(format!("pub struct {}Handler {{", definition.name));
|
||||||
|
f.a1(format!(
|
||||||
|
"implementation: Box<dyn {} + Sync + Send + 'static>,",
|
||||||
|
definition.name
|
||||||
|
));
|
||||||
|
f.a0("}");
|
||||||
|
f.a0("");
|
||||||
|
|
||||||
|
f.a0(format!("impl {}Handler {{", definition.name));
|
||||||
|
f.a1(format!(
|
||||||
|
"pub fn new(implementation: Box<dyn {} + Sync + Send + 'static>) -> Arc<Self> {{",
|
||||||
|
definition.name,
|
||||||
|
));
|
||||||
|
f.a2("Arc::from(Self { implementation })");
|
||||||
|
f.a1("}");
|
||||||
|
f.a0("}");
|
||||||
|
|
||||||
|
f.a0("");
|
||||||
|
|
||||||
|
f.a0("#[async_trait]");
|
||||||
|
f.a0(format!(
|
||||||
|
"impl JRPCServerService for {}Handler {{",
|
||||||
|
definition.name
|
||||||
|
));
|
||||||
|
f.a1(format!(
|
||||||
|
"fn get_id(&self) -> String {{ \"{}\".to_owned() }} ",
|
||||||
|
definition.name
|
||||||
|
));
|
||||||
|
|
||||||
|
f.a1("");
|
||||||
|
|
||||||
|
f.a1("#[allow(non_snake_case)]");
|
||||||
|
f.a1(
|
||||||
|
"async fn handle(&self, msg: &JRPCRequest, function: &str) -> Result<(bool, Value)> {",
|
||||||
|
);
|
||||||
|
f.a2("match function {");
|
||||||
|
|
||||||
|
// TODO: Implement optional arguments!
|
||||||
|
|
||||||
|
for method in &definition.methods {
|
||||||
|
f.a3(format!(
|
||||||
|
"\"{}\" => {{",
|
||||||
|
Self::fix_keyword_name(&method.name)
|
||||||
|
));
|
||||||
|
if method.inputs.len() < 1 {
|
||||||
|
f.a5(format!(
|
||||||
|
"let res = self.implementation.{}().await?;",
|
||||||
|
method.name
|
||||||
|
));
|
||||||
|
f.a5("Ok((true, serde_json::to_value(res)?))");
|
||||||
|
} else {
|
||||||
|
f.a4("if msg.params.is_array() {");
|
||||||
|
if method.inputs.len() > 0 {
|
||||||
|
f.a5(
|
||||||
|
"let arr = msg.params.as_array().unwrap(); //TODO: Check if this can fail.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
f.a5(format!("let res = self.implementation.{}(", method.name));
|
||||||
|
for (i, arg) in method.inputs.iter().enumerate() {
|
||||||
|
f.a6(format!("serde_json::from_value(arr[{}].clone())", i));
|
||||||
|
f.a(
|
||||||
|
7,
|
||||||
|
format!(
|
||||||
|
".map_err(|_| \"Parameter for field '{}' should be of type '{}'!\")?{}",
|
||||||
|
arg.name,
|
||||||
|
arg.typ.to_string(),
|
||||||
|
if i < method.inputs.len() - 1 { "," } else { "" }
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
f.a5(").await?;");
|
||||||
|
|
||||||
|
if let Some(_output) = &method.output {
|
||||||
|
f.a5("Ok((true, serde_json::to_value(res)?))");
|
||||||
|
} else {
|
||||||
|
f.a5("_ = res;");
|
||||||
|
f.a5("Ok((true, Value::Null))");
|
||||||
|
}
|
||||||
|
|
||||||
|
f.a4("} else if msg.params.is_object() {");
|
||||||
|
f.a5("let obj = msg.params.as_object().unwrap(); //TODO: Check if this can fail.");
|
||||||
|
f.a5(format!("let res = self.implementation.{}(", method.name));
|
||||||
|
for (i, arg) in method.inputs.iter().enumerate() {
|
||||||
|
f.a6(format!(
|
||||||
|
"serde_json::from_value(obj.get(\"{}\").ok_or(\"Parameter of field '{}' missing!\")?.clone())",
|
||||||
|
arg.name,
|
||||||
|
arg.name
|
||||||
|
));
|
||||||
|
f.a(
|
||||||
|
7,
|
||||||
|
format!(
|
||||||
|
".map_err(|_| \"Parameter for field {} should be of type '{}'!\")?{}",
|
||||||
|
arg.name,
|
||||||
|
arg.typ.to_string(),
|
||||||
|
if i < method.inputs.len() - 1 { "," } else { "" }
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
f.a5(").await?;");
|
||||||
|
if let Some(_output) = &method.output {
|
||||||
|
f.a5("Ok((true, serde_json::to_value(res)?))");
|
||||||
|
} else {
|
||||||
|
f.a5("Ok((false, Value::Null))");
|
||||||
|
}
|
||||||
|
f.a4("} else {");
|
||||||
|
f.a5("Err(Box::from(\"Invalid parameters??\".to_owned()))");
|
||||||
|
f.a4("}");
|
||||||
|
}
|
||||||
|
f.a3("}");
|
||||||
|
}
|
||||||
|
f.a3("_ => { Err(Box::from(format!(\"Invalid function {}\", function).to_owned())) },");
|
||||||
|
|
||||||
|
f.a2("}");
|
||||||
|
f.a1("}");
|
||||||
|
f.a0("}");
|
||||||
|
|
||||||
|
ctx.write_file(
|
||||||
|
&format!("src/server/{}.rs", Self::to_snake(&definition.name)),
|
||||||
|
f.into_content(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_service_client(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut CompileContext,
|
||||||
|
definition: &ServiceDefinition,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut f = FileGenerator::new();
|
||||||
|
|
||||||
|
self.add_dependencies(&mut f, &definition.depends)?;
|
||||||
|
|
||||||
|
f.a0("use crate::base_lib::{JRPCClient, JRPCRequest, Result};");
|
||||||
|
f.a0("use serde_json::json;");
|
||||||
|
f.a0("");
|
||||||
|
f.a0(format!("pub struct {} {{", definition.name));
|
||||||
|
f.a1("client: JRPCClient,");
|
||||||
|
f.a0("}");
|
||||||
|
f.a0("");
|
||||||
|
|
||||||
|
f.a0(format!("impl {} {{", definition.name));
|
||||||
|
f.a1("pub fn new(client: JRPCClient) -> Self {");
|
||||||
|
f.a2(format!("Self {{ client }}"));
|
||||||
|
f.a1("}");
|
||||||
|
|
||||||
|
f.a0("");
|
||||||
|
|
||||||
|
for method in &definition.methods {
|
||||||
|
let params = method
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.map(|arg| {
|
||||||
|
format!(
|
||||||
|
"{}: {}",
|
||||||
|
Self::fix_keyword_name(&arg.name),
|
||||||
|
Self::type_to_rust_ext(&arg.typ, arg.optional, arg.array)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
let ret = method.output.as_ref().map_or("()".to_string(), |output| {
|
||||||
|
Self::type_to_rust_ext(&output.typ, false, output.array)
|
||||||
|
});
|
||||||
|
|
||||||
|
f.a1("#[allow(non_snake_case)]");
|
||||||
|
f.a1(format!(
|
||||||
|
"pub async fn {}(&self, {}) -> Result<{}> {{",
|
||||||
|
method.name, params, ret
|
||||||
|
));
|
||||||
|
|
||||||
|
f.a2("let l_req = JRPCRequest {");
|
||||||
|
f.a3("jsonrpc: \"2.0\".to_owned(),");
|
||||||
|
f.a3("id: None, // 'id' will be set by the send_request function");
|
||||||
|
f.a3(format!(
|
||||||
|
"method: \"{}.{}\".to_owned(),",
|
||||||
|
definition.name, method.name
|
||||||
|
));
|
||||||
|
f.a3(format!(
|
||||||
|
"params: json!([{}])",
|
||||||
|
method
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.map(|e| Self::fix_keyword_name(&e.name))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ")
|
||||||
|
));
|
||||||
|
f.a2("};");
|
||||||
|
|
||||||
|
if let Some(output) = &method.output {
|
||||||
|
f.a2("let l_res = self.client.send_request(l_req).await;");
|
||||||
|
f.a2("match l_res {");
|
||||||
|
f.a3("Err(e) => Err(e),");
|
||||||
|
if output.typ == Type::Void {
|
||||||
|
f.a3("Ok(_) => Ok(())");
|
||||||
|
} else {
|
||||||
|
f.a3("Ok(o) => serde_json::from_value(o).map_err(|e| Box::from(e))");
|
||||||
|
}
|
||||||
|
f.a2("}");
|
||||||
|
} else {
|
||||||
|
f.a2("self.client.send_notification(l_req).await;");
|
||||||
|
f.a2("Ok(())");
|
||||||
|
}
|
||||||
|
f.a1("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
f.a0("}");
|
||||||
|
|
||||||
|
ctx.write_file(
|
||||||
|
&format!("src/client/{}.rs", Self::to_snake(&definition.name)),
|
||||||
|
f.into_content(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Compile for RustCompiler {
|
impl Compile for RustCompiler {
|
||||||
fn name(&self) -> String {
|
fn new(options: &HashMap<String, String>) -> anyhow::Result<Self> {
|
||||||
"rust".to_string()
|
let crate_name = if let Some(crate_name) = options.get("rust_crate") {
|
||||||
}
|
crate_name.to_string()
|
||||||
|
|
||||||
fn start(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut CompileContext,
|
|
||||||
options: HashMap<String, String>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
if let Some(crate_name) = options.get("crate") {
|
|
||||||
self.crate_name = crate_name.to_string();
|
|
||||||
} else {
|
} else {
|
||||||
anyhow::bail!("crate option is required for rust compiler");
|
anyhow::bail!("crate option is required for rust compiler");
|
||||||
}
|
};
|
||||||
|
|
||||||
if let Some(allow_bytes) = options.get("allow_bytes") {
|
if let Some(allow_bytes) = options.get("allow_bytes") {
|
||||||
if allow_bytes == "true" {
|
if allow_bytes == "true" {
|
||||||
@ -93,6 +418,14 @@ impl Compile for RustCompiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(RustCompiler { crate_name })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"rust".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(&mut self, ctx: &mut CompileContext) -> anyhow::Result<()> {
|
||||||
ctx.write_file(
|
ctx.write_file(
|
||||||
"Cargo.toml",
|
"Cargo.toml",
|
||||||
include_str!("../../templates/Rust/Cargo.toml")
|
include_str!("../../templates/Rust/Cargo.toml")
|
||||||
@ -105,28 +438,28 @@ impl Compile for RustCompiler {
|
|||||||
include_str!("../../templates/Rust/src/lib.rs").to_owned(),
|
include_str!("../../templates/Rust/src/lib.rs").to_owned(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
todo!()
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_type(
|
fn generate_type(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &mut CompileContext,
|
ctx: &mut CompileContext,
|
||||||
definition: TypeDefinition,
|
definition: &TypeDefinition,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let mut f = FileGenerator::new();
|
let mut f = FileGenerator::new();
|
||||||
|
|
||||||
if definition.fields.iter().any(|e| e.map.is_some()) {
|
if definition.fields.iter().any(|e| e.map.is_some()) {
|
||||||
f.a(0, "use std::collections::hash_map::HashMap;")
|
f.a0("use std::collections::hash_map::HashMap;")
|
||||||
}
|
}
|
||||||
f.a(0, "use serde::{Deserialize, Serialize};");
|
f.a0("use serde::{Deserialize, Serialize};");
|
||||||
|
|
||||||
self.add_dependencies(&mut f, &definition.depends)?;
|
self.add_dependencies(&mut f, &definition.depends)?;
|
||||||
|
|
||||||
f.a(0, "#[derive(Clone, Debug, Serialize, Deserialize)]");
|
f.a0("#[derive(Clone, Debug, Serialize, Deserialize)]");
|
||||||
f.a(0, format!("pub struct {} {{", definition.name));
|
f.a0(format!("pub struct {} {{", definition.name));
|
||||||
for field in definition.fields {
|
for field in definition.fields.iter() {
|
||||||
f.a(1, "#[allow(non_snake_case)]");
|
f.a(1, "#[allow(non_snake_case)]");
|
||||||
let mut func = format!("pub {}: ", Self::fix_keyword_name(&field.name));
|
let func = format!("pub {}:", Self::fix_keyword_name(&field.name));
|
||||||
|
|
||||||
if Keywords::is_keyword(&field.name) {
|
if Keywords::is_keyword(&field.name) {
|
||||||
warn!(
|
warn!(
|
||||||
@ -134,7 +467,7 @@ impl Compile for RustCompiler {
|
|||||||
field.name, field.name
|
field.name, field.name
|
||||||
);
|
);
|
||||||
|
|
||||||
f.a(1, "#[serde(rename = \"type\")]");
|
f.a(1, format!("#[serde(rename = \"{}\")]", field.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut opts = String::new();
|
let mut opts = String::new();
|
||||||
@ -152,19 +485,19 @@ impl Compile for RustCompiler {
|
|||||||
"{} {}Vec<{}>{},",
|
"{} {}Vec<{}>{},",
|
||||||
func,
|
func,
|
||||||
opts,
|
opts,
|
||||||
Self::type_to_rust(field.typ),
|
Self::type_to_rust(&field.typ),
|
||||||
opte
|
opte
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if let Some(map) = field.map {
|
} else if let Some(map) = &field.map {
|
||||||
f.a(
|
f.a(
|
||||||
1,
|
1,
|
||||||
format!(
|
format!(
|
||||||
"{} {}HashMap<{},{}>{},",
|
"{} {}HashMap<{}, {}>{},",
|
||||||
func,
|
func,
|
||||||
opts,
|
opts,
|
||||||
Self::type_to_rust(map),
|
Self::type_to_rust(map),
|
||||||
Self::type_to_rust(field.typ),
|
Self::type_to_rust(&field.typ),
|
||||||
opte
|
opte
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -175,14 +508,14 @@ impl Compile for RustCompiler {
|
|||||||
"{} {}{}{},",
|
"{} {}{}{},",
|
||||||
func,
|
func,
|
||||||
opts,
|
opts,
|
||||||
Self::type_to_rust(field.typ),
|
Self::type_to_rust(&field.typ),
|
||||||
opte
|
opte
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.a(0, "}");
|
f.a0("}");
|
||||||
|
|
||||||
ctx.write_file(
|
ctx.write_file(
|
||||||
&format!("src/{}.rs", Self::to_snake(&definition.name)),
|
&format!("src/{}.rs", Self::to_snake(&definition.name)),
|
||||||
@ -195,20 +528,41 @@ impl Compile for RustCompiler {
|
|||||||
fn generate_enum(
|
fn generate_enum(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &mut CompileContext,
|
ctx: &mut CompileContext,
|
||||||
definition: EnumDefinition,
|
definition: &EnumDefinition,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
todo!()
|
let mut f = FileGenerator::new();
|
||||||
|
f.a0("use int_enum::IntEnum;");
|
||||||
|
// f.a0("use serde::{Deserialize, Serialize};");
|
||||||
|
f.a0("");
|
||||||
|
f.a0("");
|
||||||
|
f.a0("#[repr(i64)]");
|
||||||
|
f.a0("#[derive(Clone, Copy, Debug, Eq, PartialEq, IntEnum)]");
|
||||||
|
f.a0(format!("pub enum {} {{", definition.name));
|
||||||
|
for val in definition.values.iter() {
|
||||||
|
f.a(1, format!("{} = {},", val.name, val.value));
|
||||||
|
}
|
||||||
|
f.a0("}");
|
||||||
|
|
||||||
|
ctx.write_file(
|
||||||
|
&format!("src/{}.rs", Self::to_snake(&definition.name)),
|
||||||
|
f.into_content(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_service(
|
fn generate_service(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &mut CompileContext,
|
ctx: &mut CompileContext,
|
||||||
definition: ServiceDefinition,
|
definition: &ServiceDefinition,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
todo!()
|
self.generate_service_client(ctx, definition)?;
|
||||||
|
self.generate_service_server(ctx, definition)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(&mut self, ctx: &mut CompileContext) -> anyhow::Result<()> {
|
fn finalize(&mut self, ctx: &mut CompileContext, ir: &IR) -> anyhow::Result<()> {
|
||||||
todo!()
|
Self::generate_service_lib(ctx, ir)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
int-enum = { version ="0.5.0", features = ["serde", "convert"] }
|
int-enum = { version = "0.5.0", features = ["serde", "convert"] }
|
||||||
serde = { version = "1.0.147", features = ["derive"] }
|
serde = { version = "1.0.147", features = ["derive"] }
|
||||||
serde_json = "1.0.88"
|
serde_json = "1.0.88"
|
||||||
nanoid = "0.4.0"
|
nanoid = "0.4.0"
|
||||||
|
Reference in New Issue
Block a user