Compare commits

...

3 Commits

10 changed files with 375 additions and 290 deletions

View File

@@ -7,7 +7,8 @@ pub enum Types {
U8, U16, U32, U64,
Array(Box<Types>),
Optional(Box<Types>),
Named(String)
Named(String),
Generic(String, Vec<Types>)
}
#[derive(Debug, Clone, Default)]
@@ -25,7 +26,8 @@ pub struct FieldTy {
#[derive(Debug, Clone, Default)]
pub struct StructTy {
pub name: String,
pub fields: Vec<FieldTy>
pub fields: Vec<FieldTy>,
pub generic_names: Vec<String>
}
#[derive(Debug, Clone, Default)]

View File

@@ -18,7 +18,7 @@ pub const JSON_INNER_IMPLS: &[(&str, &str)] = &[
pub fn ty_to_str(ty: &crate::data::Types) -> String {
use crate::data::Types;
match &ty {
match ty {
Types::String => "std::string".into(),
Types::Bool => "bool".into(),
Types::F32 => "std::float_t".into(),
@@ -33,10 +33,36 @@ pub fn ty_to_str(ty: &crate::data::Types) -> String {
Types::U64 => "std::uint64_t".into(),
Types::Named(name) => name.into(),
Types::Optional(inner) => format!("std::optional<{}>", ty_to_str(inner)),
Types::Array(inner) => format!("std::vector<{}>", ty_to_str(inner))
Types::Array(inner) => format!("std::vector<{}>", ty_to_str(inner)),
Types::Generic(name, types) =>
format!("{}<{}>", name, types.iter().map(|ty| ty_to_str(ty)).join(", "))
}
}
pub fn get_struct_generics(s: &crate::data::StructTy) -> String {
if s.generic_names.is_empty() {
"".into()
} else {
format!("template<{}>\n", generics_brace_inner_typename(s))
}
}
pub fn generics_brace_inner_typename(s: &crate::data::StructTy) -> String {
s.generic_names.iter().map(|n| String::from("typename ") + n).join(", ")
}
pub fn generics_brace(s: &crate::data::StructTy) -> String {
if s.generic_names.is_empty() {
"".into()
} else {
format!("<{}>", generics_brace_inner(s))
}
}
pub fn generics_brace_inner(s: &crate::data::StructTy) -> String {
s.generic_names.iter().join(", ")
}
pub fn method_args(method: &crate::data::MethodTy) -> String {
method.args.iter()
.map(|arg| format!("{} &&{}", ty_to_str(&arg.ty), arg.name))
@@ -59,37 +85,6 @@ pub fn call_args(method: &crate::data::MethodTy) -> String {
.join(", ")
}
pub fn json_write(ty: &crate::data::FieldTy) -> String {
use crate::data::Types;
match &ty.ty {
Types::String => format!("__w.String({});", ty.name),
Types::Bool => format!("__w.Bool({});", ty.name),
Types::F32 | Types::F64 => format!("__w.Double({});", ty.name),
Types::I8 | Types::I16 | Types::I32 | Types::I64 => format!("__w.Int64({});", ty.name),
Types::U8 | Types::U16 | Types::U32 | Types::U64 => format!("__w.Uint64({});", ty.name),
Types::Named(_) => format!("{} >> __w;", ty.name),
Types::Optional(inner) => {
let inner = crate::data::FieldTy { name: format!("({}.value())", ty.name), ty: (**inner).clone() };
let inner = json_write(&inner);
format!(
"if ({}.has_value()) {{
{}
}} else __w.Null();", ty.name, inner)
},
Types::Array(inner) => {
let inner_var_name = format!("__{}__entry", ty.name);
let inner = crate::data::FieldTy { name: inner_var_name.clone(), ty: (**inner).clone() };
let inner = json_write(&inner);
format!(
"__w.StartArray();
for (const auto &{} : {}) {{
{}
}}
__w.EndArray();", inner_var_name, ty.name, inner)
}
}
}
pub fn streams_required(rpc: &RPC) -> Vec<String> {
let mut streams = std::collections::HashSet::new();
for s in &rpc.services {
@@ -103,10 +98,10 @@ pub fn streams_required(rpc: &RPC) -> Vec<String> {
}
pub fn gen(file_base_name: &std::path::PathBuf, rpc: &RPC) {
let header_name = file_base_name.with_extension("h");
let header_name = file_base_name.with_extension("hxx");
let h = std::fs::File::create(&header_name).unwrap();
let header_name = header_name.file_name().unwrap().to_str().unwrap();
let h = std::fs::File::create(file_base_name.with_extension("h")).unwrap();
let c = std::fs::File::create(file_base_name.with_extension("cpp")).unwrap();
crate::templates::cpp_server_h(h, rpc).unwrap();
crate::templates::cpp_server_cpp(c, header_name, rpc).unwrap();
let c = std::fs::File::create(file_base_name.with_extension("cxx")).unwrap();
crate::templates::cpp_server_hxx(h, rpc).unwrap();
crate::templates::cpp_server_cxx(c, header_name, rpc).unwrap();
}

View File

@@ -12,7 +12,17 @@ pub fn ty_to_str(ty: &crate::data::Types) -> String {
|Types::U8 | Types::U16 | Types::U32 | Types::U64 => "number".into(),
Types::Named(name) => name.into(),
Types::Optional(inner) => format!("({}|null)", ty_to_str(inner)),
Types::Array(inner) => format!("{}[]", ty_to_str(inner))
Types::Array(inner) => format!("{}[]", ty_to_str(inner)),
Types::Generic(name, types) =>
format!("{}<{}>", name, types.iter().map(|ty| ty_to_str(ty)).join(", "))
}
}
pub fn get_struct_generics(s: &crate::data::StructTy) -> String {
if s.generic_names.is_empty() {
"".into()
} else {
format!("<{}>", s.generic_names.join(", "))
}
}
@@ -24,10 +34,10 @@ pub fn method_args(method: &crate::data::MethodTy) -> String {
}
pub fn method_ret(method: &crate::data::MethodTy) -> String {
if method.ret_stream || method.ret.is_none() {
if method.ret_stream {
String::new()
} else {
format!(": Promise<{}>", ty_to_str(method.ret.as_ref().unwrap()))
format!(": Promise<{}>", method.ret.as_ref().map(ty_to_str).unwrap_or("void".into()))
}
}

View File

@@ -1,11 +1,12 @@
mod data;
mod generators;
mod templates;
mod parser;
use std::fmt::Write;
use std::fs::File;
use std::io::Read;
use clap::Parser;
use syn::spanned::Spanned;
#[derive(Debug, clap::Parser)]
struct Args {
@@ -28,143 +29,6 @@ struct GenArgs {
static SOURCE_FILE: once_cell::sync::OnceCell<String> = once_cell::sync::OnceCell::new();
static SOURCE: once_cell::sync::OnceCell<String> = once_cell::sync::OnceCell::new();
fn parse_enum(item: &syn::ItemEnum) -> data::EnumTy {
data::EnumTy {
name: item.ident.to_string(),
values: item.variants.iter().enumerate().map(|(i, v) | (v.ident.to_string(), i)).collect()
}
}
fn parse_type_string(ty: String) -> data::Types {
use data::Types::*;
match ty.as_str() {
"str" | "String" => String,
"bool" => Bool,
"f32" => F32,
"f64" => F64,
"i8" => I8,
"i16" => I16,
"i32" => I32,
"i64" => I64,
"u8" => U8,
"u16" => U16,
"u32" => U32,
"u64" => U64,
_ => Named(ty)
}
}
fn parse_type(item: &syn::Type) -> data::Types {
match item {
syn::Type::Path(path) => {
let segments = &path.path.segments;
if segments.len() != 1 {
emit_error(item.span(), "Path segments with len != 1");
}
let segment = &segments[0];
if !segment.arguments.is_empty() {
if segment.ident.to_string() != "Option" {
emit_error(item.span(), "Only Option are currently allowed to have arguments");
}
let args = match &segment.arguments {
syn::PathArguments::AngleBracketed(v) => v,
_ => emit_error(item.span(), "Angle bracketed arguments expected")
};
if args.args.len() != 1 {
emit_error(item.span(), "Expected 1 argument");
}
let ty = match &args.args[0] {
syn::GenericArgument::Type(v) => parse_type(v),
_ => emit_error(item.span(), "Type bracketed arguments expected")
};
data::Types::Optional(ty.into())
} else {
parse_type_string(segment.ident.to_string())
}
}
syn::Type::Slice(slice) => {
data::Types::Array(parse_type(&slice.elem).into())
}
_ => emit_error(item.span(), "Unsupported type")
}
}
fn parse_struct(item: &syn::ItemStruct) -> data::StructTy {
let name = item.ident.to_string();
let mut fields = vec![];
for field in &item.fields {
if field.ident.is_none() {
emit_error(field.span(), "Missing field name");
}
let name = field.ident.as_ref().unwrap().to_string();
let ty = parse_type(&field.ty);
fields.push(data::FieldTy { name, ty });
}
data::StructTy { name, fields }
}
fn try_parse_iterator(ty: &syn::Type) -> Option<data::Types> {
if let syn::Type::Path(ty) = ty {
let seg = ty.path.segments.last()?;
if seg.ident.to_string() == "Iterator" {
if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
if let Some(syn::GenericArgument::Type(ty)) = args.args.first() {
Some(parse_type(ty))
} else { None }
} else { None }
} else { None }
} else { None }
}
fn parse_method(item: &syn::Signature) -> data::MethodTy {
let mut method = data::MethodTy::default();
method.name = item.ident.to_string();
for arg in &item.inputs {
let arg = match arg {
syn::FnArg::Typed(v) => v,
_ => emit_error(arg.span(), "Unsupported argument")
};
let ty = parse_type(&arg.ty);
let name = match &*arg.pat {
syn::Pat::Ident(v) => v.ident.to_string(),
_ => emit_error(arg.span(), "Unsupported argument")
};
method.args.push(data::FieldTy { name, ty });
}
match &item.output {
syn::ReturnType::Default => {
method.ret = None;
method.ret_stream = false;
}
syn::ReturnType::Type(_, ty) => {
if let Some(ty) = try_parse_iterator(ty) {
method.ret_stream = true;
method.ret = Some(ty);
} else {
method.ret_stream = false;
method.ret = Some(parse_type(ty));
}
}
};
method
}
fn parse_service(item: &syn::ItemTrait) -> data::ServiceTy {
let name = item.ident.to_string();
let mut methods = vec![];
for item in &item.items {
let item = match item {
syn::TraitItem::Fn(v) => v,
_ => emit_error(item.span(), "Only functions are supported")
};
methods.push(parse_method(&item.sig));
}
data::ServiceTy { name, methods }
}
fn main() {
let args = Args::parse();
@@ -175,19 +39,19 @@ fn main() {
SOURCE_FILE.set(args.file.to_string_lossy().to_string()).unwrap();
SOURCE.set(content).unwrap();
let ast = syn::parse_file(SOURCE.get().unwrap()).unwrap();
let ast = match syn::parse_file(SOURCE.get().unwrap()) {
Ok(v) => v,
Err(e) => emit_error(e.into_iter()
.map(|e| {
let span = e.span();
let mut msg = String::new();
write!(msg, "{e}").unwrap();
(span, msg)
})
)
};
let mut rpc = data::RPC::default();
for item in &ast.items {
match item {
syn::Item::Enum(v) => rpc.enums.push(parse_enum(v)),
syn::Item::Struct(v) => rpc.structs.push(parse_struct(v)),
syn::Item::Trait(v) => rpc.services.push(parse_service(v)),
syn::Item::Use(_) => {}
_ => emit_error(item.span(), "Unsupported item")
}
}
let rpc = parser::parse_file(&ast);
for gen in &args.generators.clients {
gen.generate(&args.rpc_name, &rpc);
@@ -198,7 +62,7 @@ fn main() {
}
}
fn emit_error(span: proc_macro2::Span, msg: impl Into<String>) -> ! {
pub fn emit_error(errors: impl IntoIterator<Item=(proc_macro2::Span, impl Into<String>)>) -> ! {
use codespan_reporting::{
diagnostic::{Diagnostic, Label},
files::{SimpleFiles, Files},
@@ -214,14 +78,17 @@ fn emit_error(span: proc_macro2::Span, msg: impl Into<String>) -> ! {
let file = files.get(file_id).unwrap();
let start = span.start();
let start: usize = file.line_range((), start.line-1).unwrap().start + start.column;
let end = span.end();
let end: usize = file.line_range((), end.line-1).unwrap().start + end.column;
let errors = errors.into_iter().map(|(span, msg)| {
let start = span.start();
let start: usize = file.line_range((), start.line-1).unwrap().start + start.column;
let end = span.end();
let end: usize = file.line_range((), end.line-1).unwrap().start + end.column;
Label::primary(file_id, start..end).with_message(msg)
});
let diagnostic = Diagnostic::error()
.with_labels(vec![Label::primary(file_id, start..end).with_message(msg)]);
.with_labels(errors.collect());
term::emit(&mut writer.lock(), &config, &files, &diagnostic).expect("cannot write error");
std::process::abort();
std::process::exit(1);
}

178
src/parser.rs Normal file
View File

@@ -0,0 +1,178 @@
use syn::spanned::Spanned;
use super::{data, emit_error};
fn parse_enum(item: &syn::ItemEnum) -> data::EnumTy {
data::EnumTy {
name: item.ident.to_string(),
values: item.variants.iter().enumerate().map(|(i, v) | (v.ident.to_string(), i)).collect()
}
}
fn parse_type_string(ty: String) -> data::Types {
use data::Types::*;
match ty.as_str() {
"str" | "String" => String,
"bool" => Bool,
"f32" => F32,
"f64" => F64,
"i8" => I8,
"i16" => I16,
"i32" => I32,
"i64" => I64,
"u8" => U8,
"u16" => U16,
"u32" => U32,
"u64" => U64,
_ => Named(ty)
}
}
fn parse_type(item: &syn::Type) -> data::Types {
match item {
syn::Type::Path(path) => {
let segments = &path.path.segments;
if segments.len() != 1 {
emit_error(vec![(item.span(), "Path segments with len != 1")]);
}
let segment = &segments[0];
if !segment.arguments.is_empty() {
let args = match &segment.arguments {
syn::PathArguments::AngleBracketed(v) => v,
_ => emit_error(vec![(item.span(), "Angle bracketed arguments expected")])
};
let types = args.args.iter().map(|arg| match arg {
syn::GenericArgument::Type(v) => parse_type(v),
_ => emit_error(vec![(item.span(), "Only types are supported")])
}).collect::<Vec<_>>();
let name = segment.ident.to_string();
if name == "Option" {
if types.len() != 1 { emit_error(Some((segment.span(), "Option needs exactly one argument"))); }
data::Types::Optional(types[0].clone().into())
} else if name == "Vec" {
if types.len() != 1 { emit_error(Some((segment.span(), "Vec needs exactly one argument"))); }
data::Types::Array(types[0].clone().into())
} else {
data::Types::Generic(name, types)
}
} else {
parse_type_string(segment.ident.to_string())
}
}
syn::Type::Slice(slice) => {
data::Types::Array(parse_type(&slice.elem).into())
}
_ => emit_error(vec![(item.span(), "Unsupported type")])
}
}
fn parse_struct(item: &syn::ItemStruct) -> data::StructTy {
let name = item.ident.to_string();
if let Some(v) = &item.generics.where_clause {
emit_error(Some((v.span(), "Where clauses are not allowed")));
}
if item.generics.params.len() > 1 {
emit_error(Some((item.generics.params.span(), "Only one generic parameter is allowed for now")));
}
let generic_names = item.generics.params.iter().map(|g| {
match g {
syn::GenericParam::Const(_) |
syn::GenericParam::Lifetime(_) => emit_error(Some((g.span(), "Only generic types are allowed"))),
syn::GenericParam::Type(ty) => {
if !ty.bounds.is_empty() {
emit_error(Some((ty.span(), "Bounds are not allowed")));
}
if let Some(d) = &ty.default {
emit_error(Some((d.span(), "Defaults are not allowed")));
}
ty.ident.to_string()
}
}
}).collect();
let fields = item.fields.iter().map(|field| {
if field.ident.is_none() {
emit_error(vec![(field.span(), "Missing field name")]);
}
let name = field.ident.as_ref().unwrap().to_string();
let ty = parse_type(&field.ty);
data::FieldTy { name, ty }
}).collect();
data::StructTy { name, fields, generic_names }
}
fn try_parse_iterator(ty: &syn::Type) -> Option<data::Types> {
if let syn::Type::Path(ty) = ty {
let seg = ty.path.segments.last()?;
if seg.ident.to_string() == "Iterator" {
if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
if let Some(syn::GenericArgument::Type(ty)) = args.args.first() {
Some(parse_type(ty))
} else { None }
} else { None }
} else { None }
} else { None }
}
fn parse_method(item: &syn::Signature) -> data::MethodTy {
let mut method = data::MethodTy::default();
method.name = item.ident.to_string();
for arg in &item.inputs {
let arg = match arg {
syn::FnArg::Typed(v) => v,
_ => emit_error(vec![(arg.span(), "Unsupported argument")])
};
let ty = parse_type(&arg.ty);
let name = match &*arg.pat {
syn::Pat::Ident(v) => v.ident.to_string(),
_ => emit_error(vec![(arg.span(), "Unsupported argument")])
};
method.args.push(data::FieldTy { name, ty });
}
match &item.output {
syn::ReturnType::Default => {
method.ret = None;
method.ret_stream = false;
}
syn::ReturnType::Type(_, ty) => {
if let Some(ty) = try_parse_iterator(ty) {
method.ret_stream = true;
method.ret = Some(ty);
} else {
method.ret_stream = false;
method.ret = Some(parse_type(ty));
}
}
};
method
}
fn parse_service(item: &syn::ItemTrait) -> data::ServiceTy {
let name = item.ident.to_string();
let mut methods = vec![];
for item in &item.items {
let item = match item {
syn::TraitItem::Fn(v) => v,
_ => emit_error(vec![(item.span(), "Only functions are supported")])
};
methods.push(parse_method(&item.sig));
}
data::ServiceTy { name, methods }
}
pub fn parse_file(ast: &syn::File) -> data::RPC {
let mut rpc = data::RPC::default();
for item in &ast.items {
match item {
syn::Item::Enum(v) => rpc.enums.push(parse_enum(v)),
syn::Item::Struct(v) => rpc.structs.push(parse_struct(v)),
syn::Item::Trait(v) => rpc.services.push(parse_service(v)),
syn::Item::Use(_) => {}
_ => emit_error(vec![(item.span(), "Unsupported item")])
}
}
rpc
}

View File

@@ -1,6 +1,6 @@
@use crate::data::RPC;
@use crate::generators::cpp_s::*;
@use super::cpp_server_json_cpp;
@use super::cpp_server_json_cxx;
@(header_name: &str, rpc: &RPC)
#include "@header_name"
@@ -8,7 +8,9 @@
#include <corvusoft/restbed/resource.hpp>
#include <corvusoft/restbed/request.hpp>
@:cpp_server_json_cpp(rpc)
using namespace mrpc;
@:cpp_server_json_cxx(rpc)
template<typename T>
void send_msg(const std::shared_ptr<restbed::Session> &c, const T &v) @{
@@ -55,7 +57,7 @@ mrpc::MRPCStreamImpl::MRPCStreamImpl(const std::shared_ptr<restbed::Session> &co
void mrpc::MRPCStreamImpl::close() const noexcept @{ conn->close("data:null\n\n"); @}
bool mrpc::MRPCStreamImpl::is_open() const noexcept @{ return conn->is_open(); @}
@for s in streams_required(rpc) {template<> void MRPCStream<mrpc::@s>::send(const mrpc::@s &v) const noexcept @{ send_sse_msg(conn, v); @}
@for s in streams_required(rpc) {template<> void MRPCStream<@s>::send(const @s &v) const noexcept @{ send_sse_msg(conn, v); @}
}
mrpc::MRPCServer::MRPCServer(std::shared_ptr<restbed::Resource> &r) @{
@@ -75,7 +77,8 @@ void mrpc::MRPCServer::msg_handler(const std::shared_ptr<restbed::Session> __c,
if (__j.HasParseError())
throw std::exception@{@};
std::string __service, __method;
json_get(__j, "service", __service); json_get(__j, "method", __method);
__service << json_get(__j, "service");
__method << json_get(__j, "method");
auto __data_member = __j.FindMember("data");
if (__data_member == __j.MemberEnd() || !__data_member->value.IsObject())
throw std::exception@{@};
@@ -84,10 +87,11 @@ void mrpc::MRPCServer::msg_handler(const std::shared_ptr<restbed::Session> __c,
@for (mi, m) in s.methods.iter().enumerate() {@if mi > 0 { else }else{ }if (__method == "@m.name") @{
@if m.ret_stream {auto __stream = MRPCStream<@ty_to_str(m.ret.as_ref().unwrap())>@{__c@};
}
@for (name, ty) in m.args.iter().map(|a| (&a.name, ty_to_str(&a.ty))) { @ty @name; json_get<@ty>(__data, "@name", @name);
@for (name, ty) in m.args.iter().map(|a| (&a.name, ty_to_str(&a.ty))) { @ty @name; @name << json_get(__data, "@name");
}
@if m.ret_stream || m.ret.is_none() {@(s.name)_@(m.name)(@call_args(m));}
else {send_msg(__c, @(s.name)_@(m.name)(@call_args(m)));}
@if m.ret_stream {@(s.name)_@(m.name)(@call_args(m));}
else if m.ret.is_some() {send_msg(__c, @(s.name)_@(m.name)(@call_args(m)));}
else {@(s.name)_@(m.name)(@call_args(m)); send_msg(__c, nullptr);}
@}}
else @{ throw std::exception@{@}; @}
@}}

View File

@@ -31,10 +31,10 @@ enum struct @e.name : std::uint64_t @{
@e.values.iter().map(|(k,v)| format!("{k} = {v}")).join(",\n ")
@};
}
@for s in &rpc.structs {struct @s.name;
@for s in &rpc.structs {@get_struct_generics(s)struct @s.name;
}
@for s in &rpc.structs {
struct @s.name @{
@get_struct_generics(s)struct @s.name @{
@for f in &s.fields { @ty_to_str(&f.ty) @f.name;
}
MRPCJWriter& operator >>(MRPCJWriter&) const;
@@ -58,6 +58,7 @@ struct MRPCStream final : MRPCStreamImpl @{
@for s in streams_required(rpc) {template struct MRPCStream<@(s)>;
}}
struct MRPCServer @{
MRPCServer() = delete;
explicit MRPCServer(std::shared_ptr<restbed::Resource>&);
private:
@for s in &rpc.services {@for m in &s.methods { virtual @method_ret(m) @(s.name)_@(m.name)(@method_args(m)) = 0;

View File

@@ -1,73 +0,0 @@
@use crate::data::RPC;
@use crate::generators::cpp_s::*;
@(rpc: &RPC)
template<typename T>
void json_get(const rapidjson::Value &j, const char *key, T &v);
template<typename T>
void json_get_inner(const rapidjson::Value&, T &v) = delete;
@for (ty, jty) in JSON_INNER_IMPLS {
template<> inline void json_get_inner(const rapidjson::Value &member, @ty &v) @{
if (!member.Is@(jty)())
throw std::exception@{@};
v = member.Get@(jty)();
@}}
template<typename T>
inline void json_get_inner(const rapidjson::Value &member, std::optional<T> &v) @{
if (member.IsNull())
v = std::nullopt;
else @{
T t;
json_get_inner<T>(member, t);
v = std::move(t);
@}
@}
template<typename T>
inline void json_get_inner(const rapidjson::Value &member, std::vector<T> &v) @{
if (!member.IsArray())
throw std::exception@{@};
for (const auto &j : member.GetArray()) @{
T t;
json_get_inner<T>(j, t);
v.push_back(std::move(t));
@}
@}
@for s in &rpc.structs {
template<> inline void json_get_inner(const rapidjson::Value &__j, mrpc::@s.name &v) @{
using namespace mrpc;
@for f in &s.fields { json_get<@ty_to_str(&f.ty)>(__j, "@f.name", v.@f.name);
}@}
}
@for e in &rpc.enums {
template<> inline void json_get_inner(const rapidjson::Value &j, mrpc::@e.name &v) @{
json_get_inner<std::uint64_t>(j, (std::uint64_t&)v);
@}
mrpc::MRPCJWriter& operator >>(const mrpc::@e.name &v, mrpc::MRPCJWriter &w) @{
w.Uint64((std::uint64_t)v);
return w;
@}
}
template<typename T>
inline void json_get(const rapidjson::Value &j, const char *key, T &v) @{
auto member = j.FindMember(key);
if (member == j.MemberEnd())
throw std::exception@{@};
json_get_inner(member->value, v);
@}
namespace mrpc @{
@for s in &rpc.structs {
MRPCJWriter& @(s.name)::operator >>(MRPCJWriter &__w) const @{
__w.StartObject();
@for f in &s.fields { __w.Key("@f.name", @f.name.len());
@json_write(&f)
} __w.EndObject();
return __w;
@}
@(s.name)& @(s.name)::operator <<(const rapidjson::Value &__j) @{ json_get_inner<@(s.name)>(__j, *this); return *this; @}
}

View File

@@ -0,0 +1,99 @@
@use crate::data::RPC;
@use crate::generators::cpp_s::*;
@(rpc: &RPC)
@for (ty, jty) in JSON_INNER_IMPLS {
inline @(ty)& operator<<(@ty &v, const rapidjson::Value &j) @{
if (!j.Is@(jty)())
throw std::exception@{@};
v = j.Get@(jty)();
return v;
@}
inline mrpc::MRPCJWriter& operator>>(const @ty &v, mrpc::MRPCJWriter &w) @{
w.@(jty)(v);
return w;
@}}
@for e in &rpc.enums {
inline mrpc::@e.name& operator<<(mrpc::@e.name &v, const rapidjson::Value &j) @{
((std::uint64_t&)v) << j;
return v;
@}
mrpc::MRPCJWriter& operator>>(const mrpc::@e.name &v, mrpc::MRPCJWriter &w) @{
w.Uint64((std::uint64_t)v);
return w;
@}
}
inline mrpc::MRPCJWriter& operator>>(const std::nullptr_t &, mrpc::MRPCJWriter &w) @{
w.Null();
return w;
@}
template<typename T>
inline std::vector<T>& operator<<(std::vector<T> &v, const rapidjson::Value &j);
template<typename T>
inline std::optional<T>& operator<<(std::optional<T> &v, const rapidjson::Value &j) @{
if (j.IsNull())
v = std::nullopt;
else @{
T t;
t << j;
v = std::move(t);
@}
return v;
@}
template<typename T>
inline std::vector<T>& operator<<(std::vector<T> &v, const rapidjson::Value &j) @{
if (!j.IsArray())
throw std::exception@{@};
for (const auto &e : j.GetArray()) @{
T t;
t << e;
v.push_back(std::move(t));
@}
return v;
@}
template<typename T>
inline mrpc::MRPCJWriter& operator>>(const std::vector<T> &v, mrpc::MRPCJWriter &w);
template<typename T>
inline mrpc::MRPCJWriter& operator>>(const std::optional<T> &v, mrpc::MRPCJWriter &w) @{
if (v.has_value())
v.value() >> w;
else
w.Null();
return w;
@}
template<typename T>
inline mrpc::MRPCJWriter& operator>>(const std::vector<T> &v, mrpc::MRPCJWriter &w) @{
w.StartArray();
for (const auto &e : v)
e >> w;
w.EndArray();
return w;
@}
inline const rapidjson::Value& json_get(const rapidjson::Value &j, const char *key) @{
auto member = j.FindMember(key);
if (member == j.MemberEnd())
throw std::exception@{@};
return member->value;
@}
namespace mrpc @{
@for s in &rpc.structs {
@get_struct_generics(s)MRPCJWriter& @(s.name)@(generics_brace(s))::operator>>(MRPCJWriter &__w) const @{
__w.StartObject();
@for f in &s.fields { __w.Key("@f.name", @f.name.len());
@f.name >> __w;
} __w.EndObject();
return __w;
@}
@get_struct_generics(s)@(s.name)@(generics_brace(s))& @(s.name)@(generics_brace(s))::operator<<(const rapidjson::Value &__j) @{
using namespace mrpc;
@for f in &s.fields { @f.name << json_get(__j, "@f.name");
} return *this;
@}
}

View File

@@ -10,7 +10,7 @@ export enum @e.name @{
}@}
}
@for s in &rpc.structs {
export interface @s.name @{
export interface @s.name@get_struct_generics(s) @{
@for f in &s.fields { @f.name: @ty_to_str(&f.ty);
}@}
}
@@ -18,17 +18,17 @@ export interface @s.name @{
export class MRPCConnector @{
url: string;
private __create_msg(service: string, method: string, data: any) @{
return @{service, method, data@};
@}
public constructor(url: string) @{
this.url = url;
@}
@for s in &rpc.services { @for m in &s.methods {
public @(s.name)_@(m.name)(@method_args(m))@method_ret(m) @{
const __msg = @{
service: '@s.name',
method: '@m.name',
data: @{@m.args.iter().map(|a| &a.name).join(",")@}
@};
const __msg = this.__create_msg('@s.name', '@m.name', @{@m.args.iter().map(|a| &a.name).join(",")@});
@if m.ret.is_some() && !m.ret_stream {return fetch(this.url, @{
method: 'POST',
body: JSON.stringify(__msg)
@@ -36,8 +36,10 @@ export class MRPCConnector @{
else if m.ret_stream {fetchEventSource(this.url, @{
method: 'POST',
body: JSON.stringify(__msg),
onmessage: __e => __cbk(JSON.parse(__e.data))
@});} else {fetch(this.url, @{method: 'POST', body: JSON.stringify(__msg)@});}
onmessage: __e => __cbk(JSON.parse(__e.data)),
onerror: __e => @{throw __e;@},
onclose: () => __cbk(null)
@});} else {return fetch(this.url, @{method: 'POST', body: JSON.stringify(__msg)@}).then(__r => @{@});}
@}
}}
@}