Finish implementation of typescript generator.
The implementation is still untested and will have issues
This commit is contained in:
@ -137,9 +137,9 @@ impl FileGenerator {
|
|||||||
self.a(6, content);
|
self.a(6, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_line(&mut self, line: &str) {
|
// pub fn add_line(&mut self, line: &str) {
|
||||||
self.content.push(line.to_string());
|
// self.content.push(line.to_string());
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn get_content(&self) -> String {
|
pub fn get_content(&self) -> String {
|
||||||
self.content.join("\n")
|
self.content.join("\n")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
error::Error,
|
error::Error,
|
||||||
fmt::Display,
|
fmt::{Debug, Display},
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -13,8 +13,9 @@ use crate::parser::{
|
|||||||
|
|
||||||
static BUILT_INS: [&str; 6] = ["int", "float", "string", "boolean", "bytes", "void"];
|
static BUILT_INS: [&str; 6] = ["int", "float", "string", "boolean", "bytes", "void"];
|
||||||
|
|
||||||
pub trait Definition {
|
pub trait Definition: Debug {
|
||||||
fn get_position(&self) -> ParserPosition;
|
fn get_position(&self) -> ParserPosition;
|
||||||
|
fn get_name(&self) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -186,6 +187,9 @@ impl Definition for TypeDefinition {
|
|||||||
fn get_position(&self) -> ParserPosition {
|
fn get_position(&self) -> ParserPosition {
|
||||||
self.position.clone()
|
self.position.clone()
|
||||||
}
|
}
|
||||||
|
fn get_name(&self) -> String {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -205,6 +209,9 @@ impl Definition for EnumDefinition {
|
|||||||
fn get_position(&self) -> ParserPosition {
|
fn get_position(&self) -> ParserPosition {
|
||||||
self.position.clone()
|
self.position.clone()
|
||||||
}
|
}
|
||||||
|
fn get_name(&self) -> String {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -225,6 +232,9 @@ impl Definition for ServiceDefinition {
|
|||||||
fn get_position(&self) -> ParserPosition {
|
fn get_position(&self) -> ParserPosition {
|
||||||
self.position.clone()
|
self.position.clone()
|
||||||
}
|
}
|
||||||
|
fn get_name(&self) -> String {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -10,31 +10,3 @@ pub use ir::IR;
|
|||||||
pub use parser::{Parser, RootNode};
|
pub use parser::{Parser, RootNode};
|
||||||
pub use process::FileProcessor;
|
pub use process::FileProcessor;
|
||||||
pub use tokenizer::{tokenize, Token, TokenError, TokenPosition, TokenType};
|
pub use tokenizer::{tokenize, Token, TokenError, TokenPosition, TokenType};
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::targets::{self, rust::RustCompiler};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[ctor::ctor]
|
|
||||||
fn init() {
|
|
||||||
env_logger::init();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn parse_jrpc() {
|
|
||||||
let mut fp = crate::process::FileProcessor::new();
|
|
||||||
// let ir = fp.start_compile("./test.jrpc").unwrap();
|
|
||||||
let ir = fp.start_compile("http://127.0.0.1:7878/test.jrpc").unwrap();
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -667,8 +667,8 @@ pub struct ParserError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ParserError {
|
impl ParserError {
|
||||||
fn new(msg: &str, token: &Token) -> ParserError {
|
fn new(msg: &str, token: &Token) -> Self {
|
||||||
ParserError {
|
Self {
|
||||||
message: format!("{}: {}", msg, token.1),
|
message: format!("{}: {}", msg, token.1),
|
||||||
token: token.clone(),
|
token: token.clone(),
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,47 @@
|
|||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
compile::{Compile, CompileContext},
|
compile::{Compile, CompileContext},
|
||||||
|
ir::Definition,
|
||||||
IR,
|
IR,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod rust;
|
pub mod rust;
|
||||||
pub mod typescript;
|
pub mod typescript;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CompilerError<D: Definition> {
|
||||||
|
pub message: String,
|
||||||
|
pub definition: D,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Definition> CompilerError<D> {
|
||||||
|
fn new(msg: &str, definition: D) -> Self {
|
||||||
|
Self {
|
||||||
|
message: format!("{}: {}", msg, definition.get_name()),
|
||||||
|
definition,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Definition> Error for CompilerError<D> {}
|
||||||
|
|
||||||
|
impl<D: Definition> Display for CompilerError<D> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"CompilerError: {} at {:?}",
|
||||||
|
self.message,
|
||||||
|
self.definition.get_position()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
let mut compiler = T::new(&ir.options)?;
|
let mut compiler = T::new(&ir.options)?;
|
||||||
@ -15,10 +49,29 @@ pub fn compile<T: Compile>(ir: IR, output: &str) -> Result<()> {
|
|||||||
|
|
||||||
for step in ir.steps.iter() {
|
for step in ir.steps.iter() {
|
||||||
match step {
|
match step {
|
||||||
crate::ir::Step::Type(definition) => compiler.generate_type(&mut ctx, &definition)?,
|
crate::ir::Step::Type(definition) => {
|
||||||
crate::ir::Step::Enum(definition) => compiler.generate_enum(&mut ctx, &definition)?,
|
match compiler.generate_type(&mut ctx, &definition) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(err) => {
|
||||||
|
return Err(CompilerError::new(&err.to_string(), definition.clone()).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crate::ir::Step::Enum(definition) => {
|
||||||
|
match compiler.generate_enum(&mut ctx, &definition) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(err) => {
|
||||||
|
return Err(CompilerError::new(&err.to_string(), definition.clone()).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
crate::ir::Step::Service(definition) => {
|
crate::ir::Step::Service(definition) => {
|
||||||
compiler.generate_service(&mut ctx, &definition)?
|
match compiler.generate_service(&mut ctx, &definition) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(err) => {
|
||||||
|
return Err(CompilerError::new(&err.to_string(), definition.clone()).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,7 @@ 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::{
|
use crate::ir::{BaseType, EnumDefinition, ServiceDefinition, Step, Type, TypeDefinition};
|
||||||
BaseType, EnumDefinition, ServiceDefinition, Step, Type, TypeDefinition, TypeModifier,
|
|
||||||
};
|
|
||||||
use crate::shared::Keywords;
|
use crate::shared::Keywords;
|
||||||
use crate::IR;
|
use crate::IR;
|
||||||
|
|
||||||
|
@ -1,78 +1,105 @@
|
|||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
use log::{info, warn};
|
use log::info;
|
||||||
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::{BaseType, EnumDefinition, ServiceDefinition, Step, Type, TypeDefinition};
|
use crate::ir::{BaseType, EnumDefinition, ServiceDefinition, Step, Type, TypeDefinition};
|
||||||
use crate::shared::Keywords;
|
|
||||||
use crate::IR;
|
use crate::IR;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum Flavour {
|
// pub enum Flavour {
|
||||||
ESM,
|
// ESM,
|
||||||
Node,
|
// Node,
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub trait Flavour {
|
||||||
|
fn ext() -> &'static str;
|
||||||
|
fn name() -> &'static str;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TypeScriptCompiler {
|
pub struct Node;
|
||||||
flavour: Flavour,
|
impl Flavour for Node {
|
||||||
|
fn ext() -> &'static str {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
fn name() -> &'static str {
|
||||||
|
"ts-node"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static TS_KEYWORDS: [&'static str; 52] = [
|
pub struct ESM;
|
||||||
"abstract",
|
impl Flavour for ESM {
|
||||||
"arguments",
|
fn ext() -> &'static str {
|
||||||
"await",
|
".js"
|
||||||
"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",
|
|
||||||
"static",
|
|
||||||
];
|
|
||||||
|
|
||||||
impl TypeScriptCompiler {
|
fn name() -> &'static str {
|
||||||
|
"ts-esm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TypeScriptCompiler<F: Flavour> {
|
||||||
|
// flavour: Flavour,
|
||||||
|
flavour: std::marker::PhantomData<F>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// static TS_KEYWORDS: [&'static str; 52] = [
|
||||||
|
// "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",
|
||||||
|
// "static",
|
||||||
|
// ];
|
||||||
|
|
||||||
|
impl<F: Flavour> TypeScriptCompiler<F> {
|
||||||
fn type_to_typescript(typ: &BaseType) -> String {
|
fn type_to_typescript(typ: &BaseType) -> String {
|
||||||
match typ {
|
match typ {
|
||||||
BaseType::String => "string".to_string(),
|
BaseType::String => "string".to_string(),
|
||||||
@ -109,13 +136,9 @@ impl TypeScriptCompiler {
|
|||||||
file: &mut FileGenerator,
|
file: &mut FileGenerator,
|
||||||
depends: &HashSet<BaseType>,
|
depends: &HashSet<BaseType>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let esm = if self.flavour == Flavour::ESM {
|
let esm = F::ext();
|
||||||
".js"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
file.a0(format!(
|
file.a0(format!(
|
||||||
"import {{ VerificationError, apply_int, apply_float, apply_string, apply_boolean, apply_void }} from \"./ts_base{esm}\""));
|
"import {{ VerificationError, apply_int, apply_float, apply_string, apply_boolean, apply_void, apply_array, apply_required, apply_optional, apply_map }} from \"./ts_base{esm}\""));
|
||||||
for dep in depends {
|
for dep in depends {
|
||||||
match dep {
|
match dep {
|
||||||
BaseType::Custom(name) => {
|
BaseType::Custom(name) => {
|
||||||
@ -132,17 +155,76 @@ impl TypeScriptCompiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fix_keyword_name(name: &str) -> String {
|
fn fix_keyword_name(name: &str) -> String {
|
||||||
if TS_KEYWORDS.contains(&name) {
|
// if TS_KEYWORDS.contains(&name) {
|
||||||
format!("{}_", name)
|
// format!("{}_", name)
|
||||||
} else {
|
// } else {
|
||||||
|
// name.to_string()
|
||||||
|
// }
|
||||||
|
// TODO: Check if this is something that can be implemented. There is the issue of JSON generation...
|
||||||
name.to_string()
|
name.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_type_apply(typ: &Type, value: &str) -> String {
|
||||||
|
let apply_field_value = format!("apply_{}", typ.0.to_string());
|
||||||
|
match &typ.1 {
|
||||||
|
crate::ir::TypeModifier::None => format!("{}({})", apply_field_value, value),
|
||||||
|
crate::ir::TypeModifier::Optional => format!("apply_optional({}, {})", value, apply_field_value),
|
||||||
|
crate::ir::TypeModifier::Array => format!("apply_array({}, {})", value, apply_field_value),
|
||||||
|
crate::ir::TypeModifier::OptionalArray => format!("apply_optional({}, data => apply_array(data, {}))", value, apply_field_value),
|
||||||
|
crate::ir::TypeModifier::Map(map) => format!("apply_map({}, apply_{}, {})", value, map.to_string(), apply_field_value),
|
||||||
|
crate::ir::TypeModifier::OptionalMap(map) => format!("apply_optional({}, data => apply_map(data, apply_{}, {}))", value, map.to_string(), apply_field_value),
|
||||||
|
crate::ir::TypeModifier::MapArray(map) => format!("apply_map({}, apply_{}, data => apply_array(data, {}))", value, map.to_string(), apply_field_value),
|
||||||
|
crate::ir::TypeModifier::OptionalMapArray(map) => format!("apply_optional({}, data => apply_map(data, apply_{}, (data) => apply_array(data, {})))", value, map.to_string(), apply_field_value),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_service_lib(ctx: &CompileContext, ir: &IR) -> Result<()> {
|
fn generate_service_lib(&mut self, ctx: &CompileContext, ir: &IR) -> Result<()> {
|
||||||
|
let esm = F::ext();
|
||||||
|
|
||||||
let mut f = FileGenerator::new();
|
let mut f = FileGenerator::new();
|
||||||
|
|
||||||
let mut fc = FileGenerator::new();
|
let mut fc = FileGenerator::new();
|
||||||
|
fc.a0(format!("export * from \"./ts_service_client{}\";", esm));
|
||||||
|
fc.a0("");
|
||||||
|
|
||||||
let mut fs = FileGenerator::new();
|
let mut fs = FileGenerator::new();
|
||||||
|
fs.a0(format!("export * from \"./ts_service_server{}\";", esm));
|
||||||
|
fs.a0("");
|
||||||
|
|
||||||
|
for step in &ir.steps {
|
||||||
|
match step {
|
||||||
|
Step::Enum(def) => {
|
||||||
|
f.a0(format!(
|
||||||
|
"import {}, {{ apply_{} }} from \"./{}{}\";",
|
||||||
|
def.name, def.name, def.name, esm
|
||||||
|
));
|
||||||
|
f.a0(format!("export {{ {}, apply_{} }};", def.name, def.name));
|
||||||
|
f.a0("");
|
||||||
|
}
|
||||||
|
Step::Type(def) => {
|
||||||
|
f.a0(format!(
|
||||||
|
"import {}, {{ apply_{} }} from \"./{}{}\";",
|
||||||
|
def.name, def.name, def.name, esm
|
||||||
|
));
|
||||||
|
f.a0(format!("export {{ {}, apply_{} }};", def.name, def.name));
|
||||||
|
f.a0("");
|
||||||
|
}
|
||||||
|
Step::Service(def) => {
|
||||||
|
fc.a0(format!(
|
||||||
|
"export {{ {} }} from \"./{}_client{}\"",
|
||||||
|
def.name, def.name, esm
|
||||||
|
));
|
||||||
|
fs.a0(format!(
|
||||||
|
"export {{ {} }} from \"./{}_server{}\"",
|
||||||
|
def.name, def.name, esm
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.write_file("index.ts", &f.into_content())?;
|
||||||
|
ctx.write_file("index_client.ts", &fc.into_content())?;
|
||||||
|
ctx.write_file("index_server.ts", &fs.into_content())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -152,11 +234,7 @@ impl TypeScriptCompiler {
|
|||||||
ctx: &mut CompileContext,
|
ctx: &mut CompileContext,
|
||||||
definition: &ServiceDefinition,
|
definition: &ServiceDefinition,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let esm = if self.flavour == Flavour::ESM {
|
let esm = F::ext();
|
||||||
".js"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
|
|
||||||
ctx.write_file("ts_service_server.ts", &format!("
|
ctx.write_file("ts_service_server.ts", &format!("
|
||||||
import {{ type RequestObject, type ResponseObject, ErrorCodes, Logging }} from \"./ts_service_base{esm}\";
|
import {{ type RequestObject, type ResponseObject, ErrorCodes, Logging }} from \"./ts_service_base{esm}\";
|
||||||
@ -190,7 +268,13 @@ import {{ VerificationError }} from \"./ts_base{esm}\";
|
|||||||
let mut params = func
|
let mut params = func
|
||||||
.inputs
|
.inputs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| format!("{}: {}", p.name, Self::type_to_typescript_ext(&p.typ)))
|
.map(|p| {
|
||||||
|
format!(
|
||||||
|
"{}: {}",
|
||||||
|
Self::fix_keyword_name(&p.name),
|
||||||
|
Self::type_to_typescript_ext(&p.typ)
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
params.push("ctx: T".to_string());
|
params.push("ctx: T".to_string());
|
||||||
let params = params.join(", ");
|
let params = params.join(", ");
|
||||||
@ -207,6 +291,13 @@ import {{ VerificationError }} from \"./ts_base{esm}\";
|
|||||||
func.name,
|
func.name,
|
||||||
Self::type_to_typescript_ext(&output.typ)
|
Self::type_to_typescript_ext(&output.typ)
|
||||||
));
|
));
|
||||||
|
} else {
|
||||||
|
f.a1(format!("public abstract {}({}): void;", func.name, params));
|
||||||
|
f.a1(format!(
|
||||||
|
"_{}(params: any[] | any, ctx: T): void {{",
|
||||||
|
func.name,
|
||||||
|
));
|
||||||
|
}
|
||||||
let params_str_arr = func
|
let params_str_arr = func
|
||||||
.inputs
|
.inputs
|
||||||
.iter()
|
.iter()
|
||||||
@ -214,54 +305,34 @@ import {{ VerificationError }} from \"./ts_base{esm}\";
|
|||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(", ");
|
.join(", ");
|
||||||
f.a2(format!(
|
f.a2(format!(
|
||||||
"let p = this._into_parameters([{}], params)",
|
"let p = this._into_parameters([{}], params);",
|
||||||
params_str_arr
|
params_str_arr
|
||||||
));
|
));
|
||||||
|
|
||||||
for (index, input) in func.inputs.iter().enumerate() {
|
for (index, input) in func.inputs.iter().enumerate() {
|
||||||
f.a2(format!(
|
f.a2(format!(
|
||||||
"if(p[{}] !== null && p[{}] !== undefined) {{",
|
"p[{}] = {};",
|
||||||
index, index
|
index,
|
||||||
|
Self::get_type_apply(&input.typ, &format!("p[{}]", index))
|
||||||
));
|
));
|
||||||
if input.typ.array {
|
|
||||||
f.a2(format!("for (const elm of p[{}]) {{", index));
|
|
||||||
f.a3(format!("apply_{}(elm);", input.name));
|
|
||||||
f.a2(format!("}}"));
|
|
||||||
} else if let Some(_map) = &input.typ.map {
|
|
||||||
// TODO: Implement map type handling
|
|
||||||
panic!("Map in arguments is not allowed!");
|
|
||||||
} else {
|
|
||||||
f.a3(format!(
|
|
||||||
"apply_{}(p[{}]);",
|
|
||||||
input.typ.base.to_string(),
|
|
||||||
index
|
|
||||||
));
|
|
||||||
}
|
|
||||||
f.a2("}");
|
|
||||||
if !input.typ.optional {
|
|
||||||
f.a2(format!(
|
|
||||||
"else throw new Error(`Missing required parameter ${}`);",
|
|
||||||
input.name
|
|
||||||
));
|
|
||||||
}
|
|
||||||
f.a0("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
f.a2("p.push(ctx);");
|
f.a2("p.push(ctx);");
|
||||||
|
|
||||||
let ret_apply = String::new();
|
let ret_value = if let Some(output) = &func.output {
|
||||||
if output.typ.
|
let ret_apply = Self::get_type_apply(&output.typ, "res");
|
||||||
|
format!(".then(res => {})", ret_apply)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
f.a2("//@ts-ignore This will cause a typescript error when strict checking, since p is not a tuple");
|
f.a2("//@ts-ignore This will cause a typescript error when strict checking, since p is not a tuple");
|
||||||
f.a2(format!(
|
f.a2(format!(
|
||||||
"return this.{}.call(this, ...p).then(res=>{});",
|
"return this.{}.call(this, ...p){};",
|
||||||
func.name, ret_apply
|
func.name, ret_value
|
||||||
));
|
));
|
||||||
|
|
||||||
f.a1("}");
|
f.a1("}");
|
||||||
} else {
|
|
||||||
f.a1(format!("public abstract {}({}): void;", func.name, params));
|
|
||||||
}
|
|
||||||
|
|
||||||
f.a0("");
|
f.a0("");
|
||||||
}
|
}
|
||||||
@ -279,11 +350,7 @@ import {{ VerificationError }} from \"./ts_base{esm}\";
|
|||||||
ctx: &mut CompileContext,
|
ctx: &mut CompileContext,
|
||||||
definition: &ServiceDefinition,
|
definition: &ServiceDefinition,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let esm = if self.flavour == Flavour::ESM {
|
let esm = F::ext();
|
||||||
".js"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
|
|
||||||
ctx.write_file(
|
ctx.write_file(
|
||||||
"ts_service_base.ts",
|
"ts_service_base.ts",
|
||||||
@ -364,7 +431,7 @@ import {{ VerificationError }} from \"./ts_base{esm}\";
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Compile for TypeScriptCompiler {
|
impl<F: Flavour> Compile for TypeScriptCompiler<F> {
|
||||||
fn new(options: &HashMap<String, String>) -> Result<Self> {
|
fn new(options: &HashMap<String, String>) -> Result<Self> {
|
||||||
let flavour = options
|
let flavour = options
|
||||||
.get("flavour")
|
.get("flavour")
|
||||||
@ -372,15 +439,12 @@ impl Compile for TypeScriptCompiler {
|
|||||||
.unwrap_or_else(|| "node".to_string());
|
.unwrap_or_else(|| "node".to_string());
|
||||||
info!("TypeScript target initialized with flavour: {}", flavour);
|
info!("TypeScript target initialized with flavour: {}", flavour);
|
||||||
Ok(TypeScriptCompiler {
|
Ok(TypeScriptCompiler {
|
||||||
flavour: Flavour::ESM,
|
flavour: std::marker::PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
match self.flavour {
|
F::name().to_string()
|
||||||
Flavour::ESM => "ts-esm".to_string(),
|
|
||||||
Flavour::Node => "ts-node".to_string(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(&mut self, ctx: &mut CompileContext) -> anyhow::Result<()> {
|
fn start(&mut self, ctx: &mut CompileContext) -> anyhow::Result<()> {
|
||||||
@ -418,8 +482,12 @@ impl Compile for TypeScriptCompiler {
|
|||||||
));
|
));
|
||||||
f.a2("if(init) {");
|
f.a2("if(init) {");
|
||||||
for field in definition.fields.iter() {
|
for field in definition.fields.iter() {
|
||||||
f.a3(format!("if(init.{})", field.name));
|
f.a3(format!("if(init.{})", Self::fix_keyword_name(&field.name)));
|
||||||
f.a4(format!("this.{} = init[\"{}\"]", field.name, field.name));
|
f.a4(format!(
|
||||||
|
"this.{} = init[\"{}\"]",
|
||||||
|
Self::fix_keyword_name(&field.name),
|
||||||
|
Self::fix_keyword_name(&field.name)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
f.a2("}");
|
f.a2("}");
|
||||||
f.a1("}");
|
f.a1("}");
|
||||||
@ -443,68 +511,12 @@ impl Compile for TypeScriptCompiler {
|
|||||||
f.a1(format!("let res = new {}() as any;", definition.name));
|
f.a1(format!("let res = new {}() as any;", definition.name));
|
||||||
|
|
||||||
for field in definition.fields.iter() {
|
for field in definition.fields.iter() {
|
||||||
if field.typ.is_optional() {
|
let value_name = format!("data.{}", Self::fix_keyword_name(&field.name));
|
||||||
f.a1(format!(
|
f.a1(format!(
|
||||||
"if (data.{} !== null && data.{} !== undefined ) {{",
|
"res.{} = {};",
|
||||||
field.name, field.name
|
Self::fix_keyword_name(&field.name),
|
||||||
|
Self::get_type_apply(&field.typ, &value_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.typ.is_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.base),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if field.typ.is_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.base),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if field.typ.array {
|
|
||||||
|
|
||||||
} else if let Some(_map) = &field.typ.map {
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
|
||||||
f.a2(format!(
|
|
||||||
"res.{} = apply_{}(data.{})",
|
|
||||||
field.name,
|
|
||||||
Self::type_to_typescript(&field.typ.base),
|
|
||||||
field.name
|
|
||||||
));
|
|
||||||
}
|
|
||||||
f.a1("}");
|
|
||||||
}
|
}
|
||||||
f.a1("return res;");
|
f.a1("return res;");
|
||||||
|
|
||||||
@ -560,7 +572,7 @@ impl Compile for TypeScriptCompiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(&mut self, ctx: &mut CompileContext, ir: &IR) -> anyhow::Result<()> {
|
fn finalize(&mut self, ctx: &mut CompileContext, ir: &IR) -> anyhow::Result<()> {
|
||||||
Self::generate_service_lib(ctx, ir)?;
|
self.generate_service_lib(ctx, ir)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ export class VerificationError extends Error {
|
|||||||
constructor(
|
constructor(
|
||||||
public readonly type?: string,
|
public readonly type?: string,
|
||||||
public readonly field?: string,
|
public readonly field?: string,
|
||||||
public readonly value?: any
|
public readonly value?: any,
|
||||||
) {
|
) {
|
||||||
super(form_verficiation_error_message(type, field));
|
super(form_verficiation_error_message(type, field));
|
||||||
}
|
}
|
||||||
@ -28,8 +28,7 @@ export function apply_int(data: any) {
|
|||||||
|
|
||||||
export function apply_float(data: any) {
|
export function apply_float(data: any) {
|
||||||
data = Number(data);
|
data = Number(data);
|
||||||
if (Number.isNaN(data))
|
if (Number.isNaN(data)) throw new VerificationError("float", undefined, data);
|
||||||
throw new VerificationError("float", undefined, data);
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,4 +40,48 @@ export function apply_boolean(data: any) {
|
|||||||
return Boolean(data);
|
return Boolean(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function apply_void(data: any) { }
|
export function apply_map(
|
||||||
|
data: any,
|
||||||
|
apply_key: (data: any) => any,
|
||||||
|
apply_value: (data: any) => any,
|
||||||
|
) {
|
||||||
|
if (typeof data !== "object")
|
||||||
|
throw new VerificationError("map", undefined, data);
|
||||||
|
|
||||||
|
let res = {};
|
||||||
|
for (const key in data) {
|
||||||
|
let key_ = apply_key(key);
|
||||||
|
let value_ = apply_value(data[key]);
|
||||||
|
res[key_] = value_;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function apply_array(data: any, apply_value: (data: any) => any) {
|
||||||
|
if (!Array.isArray(data))
|
||||||
|
throw new VerificationError("array", undefined, data);
|
||||||
|
return data.map((item) => apply_value(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function apply_required(
|
||||||
|
data: any,
|
||||||
|
apply_value: (data: any) => any,
|
||||||
|
): any {
|
||||||
|
if (typeof data === "undefined" || data === null) {
|
||||||
|
throw new VerificationError("required", undefined, data);
|
||||||
|
}
|
||||||
|
return apply_value(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function apply_optional(
|
||||||
|
data: any,
|
||||||
|
apply_value: (data: any) => any,
|
||||||
|
): any {
|
||||||
|
if (typeof data === "undefined" || data === null) {
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
return apply_value(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function apply_void(data: any) {}
|
||||||
|
20
src/main.rs
20
src/main.rs
@ -1,7 +1,10 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use libjrpc::{
|
use libjrpc::{
|
||||||
targets::{rust::RustCompiler, typescript::TypeScriptCompiler},
|
targets::{
|
||||||
|
rust::RustCompiler,
|
||||||
|
typescript::{Node, TypeScriptCompiler},
|
||||||
|
},
|
||||||
FileProcessor,
|
FileProcessor,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -55,17 +58,28 @@ pub fn main() -> Result<()> {
|
|||||||
|
|
||||||
match output_target {
|
match output_target {
|
||||||
"rust" => libjrpc::targets::compile::<RustCompiler>(ir, output_dir)?,
|
"rust" => libjrpc::targets::compile::<RustCompiler>(ir, output_dir)?,
|
||||||
"ts-node" => libjrpc::targets::compile::<TypeScriptCompiler>(ir, output_dir)?,
|
"ts-node" => {
|
||||||
|
libjrpc::targets::compile::<TypeScriptCompiler<Node>>(ir, output_dir)?
|
||||||
|
}
|
||||||
|
"ts-esm" => {
|
||||||
|
libjrpc::targets::compile::<TypeScriptCompiler<Node>>(ir, output_dir)?
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
println!("Unsupported target: {}", output_target);
|
println!("Unsupported target: {}", output_target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(_def) = definition {
|
||||||
|
panic!("Definition output is not yet implemented!");
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: Implement definition output!
|
//TODO: Implement definition output!
|
||||||
}
|
}
|
||||||
Commands::Targets => {
|
Commands::Targets => {
|
||||||
panic!("Not yet implemented!")
|
println!("rust");
|
||||||
|
println!("ts-node");
|
||||||
|
println!("ts-esm");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user