Working on implementing the typescript target
This commit is contained in:
@ -45,7 +45,29 @@ impl CompileContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_file(&self, filename: &str, content: String) -> Result<()> {
|
pub fn to_snake(name: &str) -> String {
|
||||||
|
let mut result = String::new();
|
||||||
|
let mut last_upper = false;
|
||||||
|
for c in name.chars() {
|
||||||
|
if c.is_uppercase() {
|
||||||
|
if last_upper {
|
||||||
|
result.push(c.to_ascii_lowercase());
|
||||||
|
} else {
|
||||||
|
if !result.is_empty() {
|
||||||
|
result.push('_');
|
||||||
|
}
|
||||||
|
result.push(c.to_ascii_lowercase());
|
||||||
|
}
|
||||||
|
last_upper = true;
|
||||||
|
} else {
|
||||||
|
result.push(c);
|
||||||
|
last_upper = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_file(&self, filename: &str, content: &str) -> Result<()> {
|
||||||
let res_path = self.output_folder.clone().join(filename);
|
let res_path = self.output_folder.clone().join(filename);
|
||||||
let res_dir = res_path.parent().context("Path has no parent!")?;
|
let res_dir = res_path.parent().context("Path has no parent!")?;
|
||||||
std::fs::create_dir_all(res_dir)?;
|
std::fs::create_dir_all(res_dir)?;
|
||||||
|
@ -6,6 +6,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub mod rust;
|
pub mod rust;
|
||||||
|
pub mod typescript;
|
||||||
|
|
||||||
pub fn compile<T: Compile>(ir: IR, output: &str) -> Result<()> {
|
pub fn compile<T: Compile>(ir: IR, output: &str) -> Result<()> {
|
||||||
let mut ctx = CompileContext::new(output);
|
let mut ctx = CompileContext::new(output);
|
||||||
|
@ -63,28 +63,6 @@ impl RustCompiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_snake(name: &str) -> String {
|
|
||||||
let mut result = String::new();
|
|
||||||
let mut last_upper = false;
|
|
||||||
for c in name.chars() {
|
|
||||||
if c.is_uppercase() {
|
|
||||||
if last_upper {
|
|
||||||
result.push(c.to_ascii_lowercase());
|
|
||||||
} else {
|
|
||||||
if !result.is_empty() {
|
|
||||||
result.push('_');
|
|
||||||
}
|
|
||||||
result.push(c.to_ascii_lowercase());
|
|
||||||
}
|
|
||||||
last_upper = true;
|
|
||||||
} else {
|
|
||||||
result.push(c);
|
|
||||||
last_upper = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_service_lib(ctx: &CompileContext, ir: &IR) -> Result<()> {
|
fn generate_service_lib(ctx: &CompileContext, ir: &IR) -> Result<()> {
|
||||||
let mut f = FileGenerator::new();
|
let mut f = FileGenerator::new();
|
||||||
let mut fc = FileGenerator::new();
|
let mut fc = FileGenerator::new();
|
||||||
@ -96,32 +74,40 @@ impl RustCompiler {
|
|||||||
for step in ir.steps.iter() {
|
for step in ir.steps.iter() {
|
||||||
match step {
|
match step {
|
||||||
Step::Type(def) => {
|
Step::Type(def) => {
|
||||||
f.a0(format!("mod {};", Self::to_snake(&def.name)));
|
f.a0(format!("mod {};", CompileContext::to_snake(&def.name)));
|
||||||
f.a(
|
f.a(
|
||||||
0,
|
0,
|
||||||
format!("pub use {}::{};", Self::to_snake(&def.name), def.name),
|
format!(
|
||||||
|
"pub use {}::{};",
|
||||||
|
CompileContext::to_snake(&def.name),
|
||||||
|
def.name
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Step::Enum(def) => {
|
Step::Enum(def) => {
|
||||||
f.a0(format!("mod {};", Self::to_snake(&def.name)));
|
f.a0(format!("mod {};", CompileContext::to_snake(&def.name)));
|
||||||
f.a(
|
f.a(
|
||||||
0,
|
0,
|
||||||
format!("pub use {}::{};", Self::to_snake(&def.name), def.name),
|
format!(
|
||||||
|
"pub use {}::{};",
|
||||||
|
CompileContext::to_snake(&def.name),
|
||||||
|
def.name
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Step::Service(def) => {
|
Step::Service(def) => {
|
||||||
fs.a0(format!("mod {};", Self::to_snake(&def.name)));
|
fs.a0(format!("mod {};", CompileContext::to_snake(&def.name)));
|
||||||
fs.a0(format!(
|
fs.a0(format!(
|
||||||
"pub use {}::{{ {}, {}Handler }};",
|
"pub use {}::{{ {}, {}Handler }};",
|
||||||
Self::to_snake(&def.name),
|
CompileContext::to_snake(&def.name),
|
||||||
def.name,
|
def.name,
|
||||||
def.name
|
def.name
|
||||||
));
|
));
|
||||||
|
|
||||||
fc.a0(format!("mod {};", Self::to_snake(&def.name)));
|
fc.a0(format!("mod {};", CompileContext::to_snake(&def.name)));
|
||||||
fc.a0(format!(
|
fc.a0(format!(
|
||||||
"pub use {}::{};",
|
"pub use {}::{};",
|
||||||
Self::to_snake(&def.name),
|
CompileContext::to_snake(&def.name),
|
||||||
def.name,
|
def.name,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -131,9 +117,9 @@ impl RustCompiler {
|
|||||||
f.a0("pub mod server;");
|
f.a0("pub mod server;");
|
||||||
f.a0("pub mod client;");
|
f.a0("pub mod client;");
|
||||||
|
|
||||||
ctx.write_file("src/lib.rs", f.get_content())?;
|
ctx.write_file("src/lib.rs", &f.get_content())?;
|
||||||
ctx.write_file("src/server/mod.rs", fs.get_content())?;
|
ctx.write_file("src/server/mod.rs", &fs.get_content())?;
|
||||||
ctx.write_file("src/client/mod.rs", fc.get_content())?;
|
ctx.write_file("src/client/mod.rs", &fc.get_content())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -304,8 +290,11 @@ impl RustCompiler {
|
|||||||
f.a0("}");
|
f.a0("}");
|
||||||
|
|
||||||
ctx.write_file(
|
ctx.write_file(
|
||||||
&format!("src/server/{}.rs", Self::to_snake(&definition.name)),
|
&format!(
|
||||||
f.into_content(),
|
"src/server/{}.rs",
|
||||||
|
CompileContext::to_snake(&definition.name)
|
||||||
|
),
|
||||||
|
&f.into_content(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -396,8 +385,11 @@ impl RustCompiler {
|
|||||||
f.a0("}");
|
f.a0("}");
|
||||||
|
|
||||||
ctx.write_file(
|
ctx.write_file(
|
||||||
&format!("src/client/{}.rs", Self::to_snake(&definition.name)),
|
&format!(
|
||||||
f.into_content(),
|
"src/client/{}.rs",
|
||||||
|
CompileContext::to_snake(&definition.name)
|
||||||
|
),
|
||||||
|
&f.into_content(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -428,14 +420,14 @@ impl Compile for RustCompiler {
|
|||||||
fn start(&mut self, ctx: &mut CompileContext) -> anyhow::Result<()> {
|
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")
|
||||||
.to_owned()
|
.to_owned()
|
||||||
.replace("__name__", &self.crate_name),
|
.replace("__name__", &self.crate_name),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
ctx.write_file(
|
ctx.write_file(
|
||||||
"src/base_lib.rs",
|
"src/base_lib.rs",
|
||||||
include_str!("../../templates/Rust/src/lib.rs").to_owned(),
|
&include_str!("../../templates/Rust/src/lib.rs").to_owned(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -518,8 +510,8 @@ impl Compile for RustCompiler {
|
|||||||
f.a0("}");
|
f.a0("}");
|
||||||
|
|
||||||
ctx.write_file(
|
ctx.write_file(
|
||||||
&format!("src/{}.rs", Self::to_snake(&definition.name)),
|
&format!("src/{}.rs", CompileContext::to_snake(&definition.name)),
|
||||||
f.into_content(),
|
&f.into_content(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -544,8 +536,8 @@ impl Compile for RustCompiler {
|
|||||||
f.a0("}");
|
f.a0("}");
|
||||||
|
|
||||||
ctx.write_file(
|
ctx.write_file(
|
||||||
&format!("src/{}.rs", Self::to_snake(&definition.name)),
|
&format!("src/{}.rs", CompileContext::to_snake(&definition.name)),
|
||||||
f.into_content(),
|
&f.into_content(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
398
libjrpc/src/targets/typescript.rs
Normal file
398
libjrpc/src/targets/typescript.rs
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use log::{info, warn};
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use crate::compile::{Compile, CompileContext, FileGenerator};
|
||||||
|
use crate::ir::{EnumDefinition, ServiceDefinition, Step, Type, TypeDefinition};
|
||||||
|
use crate::shared::Keywords;
|
||||||
|
use crate::IR;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum Flavour {
|
||||||
|
ESM,
|
||||||
|
Node,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TypeScriptCompiler {
|
||||||
|
flavour: Flavour,
|
||||||
|
}
|
||||||
|
|
||||||
|
static TS_KEYWORDS: [&'static str; 51] = [
|
||||||
|
"abstract",
|
||||||
|
"arguments",
|
||||||
|
"await",
|
||||||
|
"boolean",
|
||||||
|
"break",
|
||||||
|
"byte",
|
||||||
|
"case",
|
||||||
|
"catch",
|
||||||
|
"class",
|
||||||
|
"const",
|
||||||
|
"continue",
|
||||||
|
"debugger",
|
||||||
|
"default",
|
||||||
|
"delete",
|
||||||
|
"do",
|
||||||
|
"else",
|
||||||
|
"enum",
|
||||||
|
"export",
|
||||||
|
"extends",
|
||||||
|
"false",
|
||||||
|
"final",
|
||||||
|
"finally",
|
||||||
|
"for",
|
||||||
|
"function",
|
||||||
|
"goto",
|
||||||
|
"if",
|
||||||
|
"implements",
|
||||||
|
"import",
|
||||||
|
"in",
|
||||||
|
"instanceof",
|
||||||
|
"interface",
|
||||||
|
"let",
|
||||||
|
"new",
|
||||||
|
"null",
|
||||||
|
"package",
|
||||||
|
"private",
|
||||||
|
"protected",
|
||||||
|
"public",
|
||||||
|
"return",
|
||||||
|
"super",
|
||||||
|
"switch",
|
||||||
|
"this",
|
||||||
|
"throw",
|
||||||
|
"true",
|
||||||
|
"try",
|
||||||
|
"typeof",
|
||||||
|
"var",
|
||||||
|
"void",
|
||||||
|
"while",
|
||||||
|
"with",
|
||||||
|
"yield",
|
||||||
|
];
|
||||||
|
|
||||||
|
impl TypeScriptCompiler {
|
||||||
|
fn type_to_typescript(typ: &Type) -> String {
|
||||||
|
match typ {
|
||||||
|
Type::String => "string".to_string(),
|
||||||
|
Type::Int => "number".to_string(),
|
||||||
|
Type::Float => "number".to_string(),
|
||||||
|
Type::Bool => "boolean".to_string(),
|
||||||
|
Type::Bytes => "Uint8Array".to_string(),
|
||||||
|
Type::Void => "void".to_string(),
|
||||||
|
Type::Custom(name) => name.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_to_typescript_ext(
|
||||||
|
typ: &Type,
|
||||||
|
optional: bool,
|
||||||
|
array: bool,
|
||||||
|
map: &Option<Type>,
|
||||||
|
) -> String {
|
||||||
|
let mut result = Self::type_to_typescript(typ);
|
||||||
|
if optional {
|
||||||
|
result = format!("({} | undefined)", result);
|
||||||
|
}
|
||||||
|
if array {
|
||||||
|
result = format!("({})[]", result);
|
||||||
|
}
|
||||||
|
if let Some(map) = map {
|
||||||
|
result = format!(
|
||||||
|
"{{ [key: {} ]: {} }}",
|
||||||
|
Self::type_to_typescript(map),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_dependencies(
|
||||||
|
&mut self,
|
||||||
|
file: &mut FileGenerator,
|
||||||
|
depends: &HashSet<Type>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let esm = if self.flavour == Flavour::ESM {
|
||||||
|
".js"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
file.a0(format!(
|
||||||
|
"import {{ VerificationError, apply_int, apply_float, apply_string, apply_boolean, apply_void }} from \"./ts_base{esm}\""));
|
||||||
|
for dep in depends {
|
||||||
|
match dep {
|
||||||
|
Type::Custom(name) => {
|
||||||
|
file.a0(&format!(
|
||||||
|
"import {name}, {{ apply_{name} }} from \"./{name}{esm}\";"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.a0("");
|
||||||
|
file.a0("");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fix_keyword_name(name: &str) -> String {
|
||||||
|
if TS_KEYWORDS.contains(&name) {
|
||||||
|
format!("{}_", name)
|
||||||
|
} else {
|
||||||
|
name.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_service_lib(ctx: &CompileContext, ir: &IR) -> Result<()> {
|
||||||
|
let mut f = FileGenerator::new();
|
||||||
|
let mut fc = FileGenerator::new();
|
||||||
|
let mut fs = FileGenerator::new();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_service_server(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut CompileContext,
|
||||||
|
definition: &ServiceDefinition,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut f = FileGenerator::new();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_service_client(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut CompileContext,
|
||||||
|
definition: &ServiceDefinition,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let esm = if self.flavour == Flavour::ESM {
|
||||||
|
".js"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.write_file("ts_service_client.ts", &format!("
|
||||||
|
import {{ type RequestObject, type ResponseObject, ErrorCodes, Logging }} from \"./service_base{esm}\";
|
||||||
|
import {{ VerificationError }} from \"./ts_base{esm}\";
|
||||||
|
|
||||||
|
{}
|
||||||
|
", include_str!("../../templates/TypeScript/ts_service_client.ts")))?;
|
||||||
|
|
||||||
|
let mut f = FileGenerator::new();
|
||||||
|
self.add_dependencies(&mut f, &definition.depends)?;
|
||||||
|
|
||||||
|
f.a0(format!(
|
||||||
|
"import {{ Service, ServiceProvider, getRandomID }} from \"./service_client{esm}\""
|
||||||
|
));
|
||||||
|
|
||||||
|
f.a0("export type {");
|
||||||
|
for dep in &definition.depends {
|
||||||
|
f.a1(format!("{},", Self::type_to_typescript(&dep)));
|
||||||
|
}
|
||||||
|
f.a0("}");
|
||||||
|
|
||||||
|
f.a0(format!(
|
||||||
|
"export class {} extends Service {{",
|
||||||
|
definition.name
|
||||||
|
));
|
||||||
|
f.a1("constructor(provider: ServiceProvider) {");
|
||||||
|
f.a2(format!("super(provider, \"{}\");", definition.name));
|
||||||
|
f.a1("}");
|
||||||
|
|
||||||
|
//TODO: Change the way methods are implemented in a way, that the jsonrpc, etc. fields are exportedf into the ServiceProvider class. This should make the actual function body a lot easier!
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Compile for TypeScriptCompiler {
|
||||||
|
fn new(options: &HashMap<String, String>) -> Result<Self> {
|
||||||
|
let flavour = options
|
||||||
|
.get("flavour")
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| "node".to_string());
|
||||||
|
info!("TypeScript target initialized with flavour: {}", flavour);
|
||||||
|
Ok(TypeScriptCompiler {
|
||||||
|
flavour: Flavour::ESM,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
match self.flavour {
|
||||||
|
Flavour::ESM => "ts-esm".to_string(),
|
||||||
|
Flavour::Node => "ts-node".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(&mut self, ctx: &mut CompileContext) -> anyhow::Result<()> {
|
||||||
|
ctx.write_file(
|
||||||
|
"ts_base.ts",
|
||||||
|
include_str!("../../templates/TypeScript/ts_base.ts"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_type(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut CompileContext,
|
||||||
|
definition: &TypeDefinition,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut f = FileGenerator::new();
|
||||||
|
|
||||||
|
self.add_dependencies(&mut f, &definition.depends)?;
|
||||||
|
|
||||||
|
f.a0(format!("export default class {} {{", definition.name));
|
||||||
|
for field in definition.fields.iter() {
|
||||||
|
let typ =
|
||||||
|
Self::type_to_typescript_ext(&field.typ, field.optional, field.array, &field.map);
|
||||||
|
|
||||||
|
f.a1(format!(
|
||||||
|
"public {}: {};",
|
||||||
|
Self::fix_keyword_name(&field.name),
|
||||||
|
typ
|
||||||
|
));
|
||||||
|
}
|
||||||
|
f.a0("");
|
||||||
|
f.a1(format!(
|
||||||
|
"constructor(init?: Partial<{}>) {{",
|
||||||
|
definition.name
|
||||||
|
));
|
||||||
|
f.a2("if(init) {");
|
||||||
|
for field in definition.fields.iter() {
|
||||||
|
f.a3(format!("if(init.{})", field.name));
|
||||||
|
f.a4(format!("this.{} = init[\"{}\"]", field.name, field.name));
|
||||||
|
}
|
||||||
|
f.a2("}");
|
||||||
|
f.a1("}");
|
||||||
|
|
||||||
|
f.a0("");
|
||||||
|
f.a1(format!("static apply(data: {}) {{", definition.name));
|
||||||
|
f.a2(format!("apply_{}(data);", definition.name));
|
||||||
|
f.a1("}");
|
||||||
|
|
||||||
|
f.a0("}");
|
||||||
|
|
||||||
|
f.a0(format!(
|
||||||
|
"export function apply_{}(data: {}) {{",
|
||||||
|
definition.name, definition.name
|
||||||
|
));
|
||||||
|
|
||||||
|
f.a1(format!(
|
||||||
|
"if(typeof data !== \"object\") throw new VerificationError(\"{}\", undefined, data);",
|
||||||
|
definition.name
|
||||||
|
));
|
||||||
|
f.a1(format!("let res = new {}() as any;", definition.name));
|
||||||
|
|
||||||
|
for field in definition.fields.iter() {
|
||||||
|
if field.optional {
|
||||||
|
f.a1(format!(
|
||||||
|
"if (data.{} !== null && data.{} !== undefined ) {{",
|
||||||
|
field.name, field.name
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
f.a1(format!(
|
||||||
|
"if (data.{} === null || data.{} === undefined ) throw new VerificationError(\"{}\", \"{}\", data.{});",
|
||||||
|
field.name, field.name,
|
||||||
|
definition.name,
|
||||||
|
field.name,
|
||||||
|
field.name
|
||||||
|
));
|
||||||
|
f.a1("else {");
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.array {
|
||||||
|
f.a2(format!(
|
||||||
|
"if (!Array.isArray(data.{})) throw new VerificationError(\"array\", \"{}\", data.{});",
|
||||||
|
field.name,
|
||||||
|
definition.name,
|
||||||
|
field.name
|
||||||
|
));
|
||||||
|
f.a2(format!(
|
||||||
|
"res.{} = data.{}.map(elm => apply_{}(elm));",
|
||||||
|
field.name,
|
||||||
|
field.name,
|
||||||
|
Self::type_to_typescript(&field.typ),
|
||||||
|
));
|
||||||
|
} else if let Some(map) = &field.map {
|
||||||
|
f.a2(format!(
|
||||||
|
"if (typeof data.{} != \"object\") throw new VerificationError(\"map\", \"{}\", data.{}); ",
|
||||||
|
field.name,
|
||||||
|
definition.name,
|
||||||
|
field.name
|
||||||
|
));
|
||||||
|
f.a2(format!("res.{} = {{}}", field.name));
|
||||||
|
f.a2(format!(
|
||||||
|
"Object.entries(data.{}).forEach(([key, val]) => res.{}[key] = apply_{}(val));",
|
||||||
|
field.name,
|
||||||
|
field.name,
|
||||||
|
Self::type_to_typescript(&field.typ),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
f.a2(format!(
|
||||||
|
"res.{} = apply_{}(data.{})",
|
||||||
|
field.name,
|
||||||
|
Self::type_to_typescript(&field.typ),
|
||||||
|
field.name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
f.a1("}");
|
||||||
|
}
|
||||||
|
f.a1("return res;");
|
||||||
|
|
||||||
|
f.a0("}");
|
||||||
|
|
||||||
|
ctx.write_file(&format!("{}.ts", definition.name), &f.into_content())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_enum(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut CompileContext,
|
||||||
|
definition: &EnumDefinition,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut f = FileGenerator::new();
|
||||||
|
|
||||||
|
self.add_dependencies(&mut f, &HashSet::new())?;
|
||||||
|
|
||||||
|
f.a0(format!("enum {} {{", definition.name));
|
||||||
|
for value in &definition.values {
|
||||||
|
f.a1(format!("{}={},", value.name, value.value));
|
||||||
|
}
|
||||||
|
f.a0("}");
|
||||||
|
f.a0("");
|
||||||
|
f.a0(format!("export default {};", definition.name));
|
||||||
|
f.a0("");
|
||||||
|
f.a0(format!(
|
||||||
|
"export function apply_{}(data: {}): {} {{",
|
||||||
|
definition.name, definition.name, definition.name
|
||||||
|
));
|
||||||
|
f.a1("data = Number(data);");
|
||||||
|
f.a1(format!(
|
||||||
|
"if ({}[data] == undefined) throw new VerificationError(\"{}\", undefined, data);",
|
||||||
|
definition.name, definition.name
|
||||||
|
));
|
||||||
|
f.a1("return data;");
|
||||||
|
f.a0("}");
|
||||||
|
|
||||||
|
ctx.write_file(&format!("{}.ts", definition.name), &f.into_content())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_service(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut CompileContext,
|
||||||
|
definition: &ServiceDefinition,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
self.generate_service_client(ctx, definition)?;
|
||||||
|
self.generate_service_server(ctx, definition)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finalize(&mut self, ctx: &mut CompileContext, ir: &IR) -> anyhow::Result<()> {
|
||||||
|
Self::generate_service_lib(ctx, ir)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
14
src/main.rs
14
src/main.rs
@ -1,6 +1,9 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use libjrpc::{targets::rust::RustCompiler, FileProcessor};
|
use libjrpc::{
|
||||||
|
targets::{rust::RustCompiler, typescript::TypeScriptCompiler},
|
||||||
|
FileProcessor,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
@ -44,17 +47,20 @@ pub fn main() -> Result<()> {
|
|||||||
let ir = fp.start_compile(&input)?;
|
let ir = fp.start_compile(&input)?;
|
||||||
|
|
||||||
let output_split = output.split(':').collect::<Vec<&str>>();
|
let output_split = output.split(':').collect::<Vec<&str>>();
|
||||||
|
if output_split.len() != 2 {
|
||||||
|
println!("The output must follow the structure: <target:path>");
|
||||||
|
} else {
|
||||||
let output_target = output_split[0];
|
let output_target = output_split[0];
|
||||||
let output_dir = output_split[1];
|
let output_dir = output_split[1];
|
||||||
|
|
||||||
match output_target {
|
match output_target {
|
||||||
"rust" => {
|
"rust" => libjrpc::targets::compile::<RustCompiler>(ir, output_dir)?,
|
||||||
libjrpc::targets::compile::<RustCompiler>(ir, output_dir)?;
|
"ts-node" => libjrpc::targets::compile::<TypeScriptCompiler>(ir, output_dir)?,
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
println!("Unsupported target: {}", output_target);
|
println!("Unsupported target: {}", output_target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: Implement definition output!
|
//TODO: Implement definition output!
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user