diff --git a/rust/compiler/src/adlrt/custom/sys/types/maybe.rs b/rust/compiler/src/adlrt/custom/sys/types/maybe.rs index 9cfe6a7c..252b228d 100644 --- a/rust/compiler/src/adlrt/custom/sys/types/maybe.rs +++ b/rust/compiler/src/adlrt/custom/sys/types/maybe.rs @@ -6,7 +6,7 @@ use std::result; // with ADL compatible serialization #[derive(Clone,Debug,Eq,Hash,PartialEq)] -pub struct Maybe (Option); +pub struct Maybe (pub Option); impl Maybe { pub fn nothing() -> Maybe { diff --git a/rust/compiler/src/cli/tsgen.rs b/rust/compiler/src/cli/tsgen.rs index f45d2b7a..f0941361 100644 --- a/rust/compiler/src/cli/tsgen.rs +++ b/rust/compiler/src/cli/tsgen.rs @@ -1,17 +1,21 @@ use super::TsOpts; -use std::collections::HashMap; +use std::cmp::Ordering; +use std::collections::{HashMap}; use anyhow::anyhow; -use genco::prelude::js::Import; +use genco::prelude::js::Import as JsImport; use genco::tokens::{Item, ItemStr}; use crate::adlgen::sys::adlast2::{ - Decl, DeclType, Module, NewType, ScopedDecl, Struct, TypeDef, TypeExpr, TypeRef, Union, + Annotations, Decl, DeclType, Field, Ident, Module, NewType, PrimitiveType, ScopedName, Struct, + TypeDef, TypeExpr, TypeRef, Union, }; +use crate::adlrt::custom::sys::types::maybe::Maybe; +use crate::parser::docstring_scoped_name; use crate::processing::loader::loader_from_search_paths; use crate::processing::resolver::Resolver; -use genco::fmt; +use genco::fmt::{self, Indentation}; use genco::prelude::*; pub fn tsgen(opts: &TsOpts) -> anyhow::Result<()> { @@ -26,8 +30,24 @@ pub fn tsgen(opts: &TsOpts) -> anyhow::Result<()> { } for mn in &opts.modules { if let Some(m) = resolver.get_module(mn) { - gen_module(m); - // println!("// gen {}", m.name); + // TODO sys.annotations::SerializedName needs to be embedded + let mut tokens = js::Tokens::new(); + let mut mgen = TsGenVisitor { + adlr: js::import("./runtime/adl", "ADL").into_wildcard(), + _map: HashMap::new(), + t: &mut tokens, + }; + mgen.gen_module(m)?; + let stdout = std::io::stdout(); + let mut w = fmt::IoWriter::new(stdout.lock()); + let fmt = fmt::Config::from_lang::(); + let fmt = fmt::Config::with_indentation(fmt, Indentation::Space(2)); + + let config = js::Config::default(); + // let config = js::Config{ + // ..Default::default() + // }; + tokens.format_file(&mut w.as_formatter(&fmt), &config)?; } } // let modules: Vec<&Module1> = resolver @@ -39,160 +59,611 @@ pub fn tsgen(opts: &TsOpts) -> anyhow::Result<()> { Ok(()) } -struct Imports { - adlr: Import, - map: HashMap, -} - -fn gen_module(m: &Module>) -> anyhow::Result<()> { - let mut tokens = js::Tokens::new(); - let imports = Imports { - adlr: js::import("./runtime/adl", "ADL").into_wildcard(), - map: HashMap::new(), - }; - // let adlr = &js::import("./runtime/adl", "ADL").into_wildcard(); - - // println!("// gen {}", m.name); - - quote_in! { tokens => - $("/* @generated from adl module") $(m.name.clone()) $("*/") - $['\n'] - }; - - for decl in m.decls.iter() { - // let scopedDecl = ScopedDecl::new(m.name.clone(), decl); - // let scopedDecl = ScopedDecl { - // module_name: m.name.clone(), - // decl: *decl, - // }; - let r = match &decl.r#type { - DeclType::Struct(d) => Ok(()), - DeclType::Union(d) => gen_union(&mut tokens, &imports, m.name.clone(), decl, d), - DeclType::Newtype(d) => Ok(()), - DeclType::Type(d) => Ok(()), - }; - if let Err(_) = r { - return r; +struct TsScopedDeclGenVisitor<'a> { + module_name: &'a String, + t: &'a mut Tokens, +} + +impl TsScopedDeclGenVisitor<'_> { + fn lit(&mut self, s: &'static str) { + self.t.append(Item::Literal(ItemStr::Static(s))); + } +} + +impl TsScopedDeclGenVisitor<'_> { + fn visit_annotations(&mut self, d: &Annotations) { + let mut keys: Vec<&ScopedName> = d.0.keys().collect(); + keys.sort_by(|a, b| { + if &a.module_name == &b.module_name { + if a.name == b.name { + Ordering::Equal + } else if a.name > b.name { + Ordering::Greater + } else { + Ordering::Less + } + } else if a.module_name > b.module_name { + Ordering::Greater + } else { + Ordering::Less + } + }); + quote_in! { self.t => "annotations":$("[") }; + keys.iter().fold(false, |rest, key| { + if **key == crate::parser::docstring_scoped_name() { + return rest; + } + if rest { + self.lit(","); + } + self.lit("{"); + let jv = &serde_json::to_string(d.0.get(key).unwrap()).unwrap(); + quote_in! { self.t => "value":$jv,} + quote_in! { self.t => "key": } + self.lit("{"); + quote_in! { self.t => "moduleName":$DQ$(&key.module_name)$DQ,"name":$DQ$(&key.name)$DQ } + self.lit("}"); + self.lit("}"); + return true; + }); + quote_in! { self.t => $("]") }; + } + + fn visit_decl(&mut self, d: &Decl>) { + self.lit("{"); + self.visit_annotations(&d.annotations); + self.lit(","); + self.visit_decl_type(&d.r#type); + self.lit(","); + self.visit_decl_name(&d.name); + self.lit(","); + quote_in! { self.t => "version":{"kind":"nothing"}}; + self.lit("}"); + } + fn visit_decl_name(&mut self, n: &String) { + quote_in! { self.t => "name":$("\"")$n$("\"")}; + } + fn visit_decl_type(&mut self, r#type: &DeclType>) { + self.lit("\"type_\":{"); + match r#type { + DeclType::Struct(dt) => self.visit_struct(dt), + DeclType::Union(dt) => self.visit_union(dt), + DeclType::Newtype(dt) => self.visit_newtype(dt), + DeclType::Type(dt) => self.visit_typealias(dt), } + self.lit("}"); + } + fn visit_struct(&mut self, dt: &Struct>) { + quote_in! { self.t => "kind":"struct_","value":$("{") } + self.visit_type_params(&dt.type_params); + self.lit(","); + self.lit("\"fields\":["); + dt.fields.iter().fold(false, |rest, f| { + if rest { + self.lit(","); + } + self.visit_field(f); + return true; + }); + self.lit("]"); + self.lit("}"); + } + fn visit_union(&mut self, dt: &Union>) { + quote_in! { self.t => "kind":"union_","value":$("{") } + self.visit_type_params(&dt.type_params); + self.lit(","); + self.lit("\"fields\":["); + dt.fields.iter().fold(false, |rest, f| { + if rest { + self.lit(","); + } + self.visit_field(f); + return true; + }); + self.lit("]"); + self.lit("}"); + } + fn visit_newtype(&mut self, _dt: &NewType>) { + // TODO + println!("newtype: "); + } + fn visit_typealias(&mut self, _dt: &TypeDef>) { + // TODO + println!("type: "); + } + fn visit_type_params(&mut self, tps: &Vec) { + quote_in! { self.t => "typeParams":[$(for tp in tps join (,) => $DQ$tp$DQ)]} + } + fn visit_field(&mut self, f: &Field>) { + self.lit("{"); + self.visit_annotations(&f.annotations); + self.lit(","); + quote_in! { self.t => "serializedName":$("\"")$(&f.serialized_name)$("\""), } + self.visit_default(&f.default); + self.lit(","); + quote_in! { self.t => "name":$("\"")$(&f.name)$("\"")}; + self.lit(","); + quote_in! { self.t => "typeExpr":} + self.visit_type_expr(&f.type_expr); + self.lit("}"); + } + fn visit_default(&mut self, f: &Maybe) { + quote_in! { self.t => "default":$("{")}; + match f { + Maybe(None) => { + quote_in! { self.t => "kind":"nothing"} + } + Maybe(Some(v)) => { + let jv = &serde_json::to_string(&v).unwrap(); + quote_in! { self.t => "kind":"just","value":$(jv)}; + } + } + quote_in! { self.t => $("}")}; + } + fn visit_type_expr(&mut self, te: &TypeExpr) { + quote_in! { self.t => $("{")} + self.visit_type_ref(&te.type_ref); + quote_in! { self.t => ,"parameters":$("[")} + te.parameters.iter().fold(false, |rest, p| { + if rest { + self.lit(","); + } + self.visit_type_expr(p); + return true; + }); + self.lit("]"); + self.lit("}"); + } + fn visit_type_ref(&mut self, te: &TypeRef) { + quote_in! { self.t => "typeRef":$("{")} + match te { + TypeRef::ScopedName(n) => { + quote_in! { self.t => "kind":"reference","value":{"moduleName":$("\"")$(&n.module_name)$("\""),"name":$("\"")$(&n.name)$("\"")}}; + } + TypeRef::LocalName(n) => { + quote_in! { self.t => "kind":"reference","value":{"moduleName":$("\"")$(self.module_name)$("\""),"name":$("\"")$(n)$("\"")}}; + } + TypeRef::Primitive(n) => { + let p = crate::processing::primitives::str_from_prim(n.clone()); + // let p = &serde_json::to_string(n).unwrap(); + quote_in! { self.t => "kind":"primitive","value":$DQ$p$DQ}; + } + TypeRef::TypeParam(n) => { + quote_in! { self.t => "kind":"typeParam","value":$("\"")$n$("\"")}; + } + } + self.lit("}"); + } +} + +struct TsGenVisitor<'a> { + t: &'a mut Tokens, + adlr: JsImport, + _map: HashMap, +} + +impl TsGenVisitor<'_> { + fn lit(&mut self, s: &'static str) { + self.t.append(Item::Literal(ItemStr::Static(s))); } +} + +struct RttiPayload<'a> { + mname: String, + type_params: &'a Vec, +} - tokens.append(Item::Literal(ItemStr::Static( - "export const _AST_MAP: { [key: string]: ADL.ScopedDecl } = {\n", - ))); - for decl in m.decls.iter() { - quote_in! { tokens => - $[' ']$[' ']$("\"")$(m.name.clone()).$(&decl.name)$("\"") : $(&decl.name)_AST,$['\r'] +impl TsGenVisitor<'_> { + fn gen_doc_comment(&mut self, annotations: &Annotations) { + if let Some(ds) = annotations.0.get(&docstring_scoped_name()) { + self.lit("/**\n"); + for c in ds.as_array().unwrap().iter() { + if let Ok(x) = serde_json::to_string(&c.clone()) { + // TODO should this be trimmed? or should the output be "*$y" ie no space + let y = x[1..x.len() - 1].trim(); + quote_in! {self.t => $[' ']* $(y)$['\r']}; + } + } + self.lit(" */\n"); } } - tokens.append(Item::Literal(ItemStr::Static("}"))); - let stdout = std::io::stdout(); - let mut w = fmt::IoWriter::new(stdout.lock()); + fn gen_rtti( + &mut self, + decl: &Decl>, + payload: &RttiPayload, + ) -> anyhow::Result<()> { + // Generation AST holder + let name = &decl.name; + // let name_up = title(name); + let mname = &payload.mname; + quote_in! { self.t => + $['\n'] + const $(name)_AST : $(&self.adlr).ScopedDecl = + {"moduleName":$("\"")$(mname.clone())$("\""),"decl":$(ref tok => { + let mut sdg = TsScopedDeclGenVisitor{module_name: &mname.clone(), t: tok}; + sdg.visit_decl(decl); + })}; - let fmt = fmt::Config::from_lang::(); - let config = js::Config::default(); + export const sn$(cap_or__(name)): $(&self.adlr).ScopedName = {moduleName:$("\"")$mname$("\""), name:$("\"")$name$("\"")}; - tokens.format_file(&mut w.as_formatter(&fmt), &config)?; - Ok(()) + export function texpr$(cap_or__(name))<$(for tp in payload.type_params join (, ) => $tp)>($(ref t => texpr_args(t, &payload.type_params))): ADL.ATypeExpr<$(name)<$(for tp in payload.type_params join (, ) => $tp)>> { + return {value:{typeRef:{kind:"reference",value:sn$(cap_or__(name))},parameters:[$(ref t => texpr_params(t, &payload.type_params))]}}; + } + $['\n'] + } + Ok(()) + } + + fn gen_module(&mut self, m: &Module>) -> anyhow::Result<()> { + quote_in! { self.t => + $("/* @generated from adl module") $(m.name.clone()) $("*/") + $['\n'] + }; + let mname = &m.name; + for decl in m.decls.iter() { + self.gen_doc_comment(&decl.annotations); + let r = match &decl.r#type { + DeclType::Struct(d) => self.gen_struct( + d, + DeclPayload { + decl: decl, + mname: &mname.clone(), + }, + ), + DeclType::Union(d) => self.gen_union( + d, + DeclPayload { + decl: decl, + mname: &mname.clone(), + }, + ), + DeclType::Newtype(d) => self.gen_newtype(d), + DeclType::Type(d) => self.gen_type(d), + }; + if let Err(_) = r { + return r; + } + } + self.lit("export const _AST_MAP: { [key: string]: ADL.ScopedDecl } = {\n"); + m.decls.iter().fold(false, |rest, decl| { + if rest { + self.lit(",\n") + } + self.lit(" "); + quote_in! { self.t => + $("\"")$(m.name.clone()).$(&decl.name)$("\"") : $(&decl.name)_AST + }; + true + }); + self.lit("\n};"); + Ok(()) + } } -fn gen_struct( - tokens: &mut Tokens, - imports: &Imports, - name: &String, - m: &Struct>, -) -> anyhow::Result<()> { - quote_in! { *tokens => - $("// struct") - // export type $name; +const OC: &str = "{"; +const CC: &str = "}"; +const DQ: &str = "\""; +const SP: &str = " "; + +// fn lit(t: &mut Tokens, s: &'static str) { +// t.append(Item::Literal(ItemStr::Static(s))); +// } - // const $(name)_AST : $(&imports.adlr).ScopedDecl - // export const sn$(name): $(&imports.adlr).ScopedName = {moduleName:"test5", name:"U1"}; - $['\n'] +fn gen_type_params__<'a>(mut t: &mut Tokens, used: &Vec, type_params: &'a Vec) { + if type_params.len() > 0 { + type_params.iter().fold(false, |rest, p| { + if rest { + quote_in! { t => ,$[' '] } + } + if !used.contains(p) { + quote_in! { t => _ } + } + quote_in! { t => $p } + true + }); } - Ok(()) } -fn rust_type(te: &TypeExpr) -> String { - return "number".to_string(); +fn texpr_args<'a>(mut t: &mut Tokens, type_params: &'a Vec) { + type_params.iter().fold(false, |rest, p| { + if rest { + quote_in! { t => ,$[' '] }; + } + quote_in! { t => texpr$p : ADL.ATypeExpr<$p> }; + true + }); } -fn gen_union( - tokens: &mut Tokens, - imports: &Imports, - mname: String, - decl: &Decl>, - m: &Union>, -) -> anyhow::Result<()> { - // let scopedDecl = ScopedDecl::new(mname.clone(), *decl); - // let scopedDecl = ScopedDecl { - // module_name: mname.clone(), - // decl: *(decl.clone()), - // }; - // TODO this is wireformat need TS format - // TODO sys.annotations::SerializedName needs to be embedded - let astDecl = serde_json::to_string(decl).unwrap(); - let name = &decl.name; - tokens.append(Item::Literal(ItemStr::Static("// union \n"))); - // let ast = serde_json::to_string(&scopedDecl).unwrap(); - let mut bNames = vec![]; - let mut opts = vec![]; - for b in m.fields.iter() { - let bname = b.name.clone(); - let bName = b.name.to_uppercase(); - bNames.push(bName.clone()); - let rtype = rust_type(&b.type_expr); - opts.push((bname.clone(), rtype.clone())); - quote_in! { *tokens => - export interface $(name)_$(bName) { - kind: $("'")$(bname)$("'"); - value: $(rtype); - }$['\r'] - } - } - quote_in! { *tokens => - $['\n'] - export type $name = $(for n in bNames join ( | ) => $(name)_$n); - - export interface $(name)Opts { - $(for opt in opts => $(opt.0): $(opt.1);$['\r']) - }$['\n'] - - export function make$(name)(kind: K, value: $(name)Opts[K]) { return {kind, value}; } - - const $(name)_AST : $(&imports.adlr).ScopedDecl = - { "moduleName": $("\"")$(mname.clone())$("\""), $("\"")decl$("\""): $astDecl} - - export const sn$(name): $(&imports.adlr).ScopedName = {moduleName:$("\"")$mname$("\""), name:$("\"")$name$("\"")}; - - export function texpr$(name)(): ADL.ATypeExpr<$(name)> { - return {value : {typeRef : {kind: "reference", value : sn$(name)}, parameters : []}}; - } - $['\n'] +fn texpr_params<'a>(mut t: &mut Tokens, type_params: &'a Vec) { + type_params.iter().fold(false, |rest, p| { + if rest { + quote_in! { t => , }; + } + quote_in! { t => texpr$(p).value }; + true + }); +} + +fn used_type_params(te_trs: &Vec>) -> Vec { + let fnames = &mut Vec::new(); + collect_used_type_params(&te_trs, fnames); + fnames.to_vec() +} +fn collect_used_type_params(te_trs: &Vec>, mut fnames: &mut Vec) { + te_trs.iter().for_each(|te| { + if let TypeRef::TypeParam(tp) = &te.type_ref { + fnames.push(tp.clone()); + } + collect_used_type_params(&te.parameters, &mut fnames); + }) +} + +impl TsGenVisitor<'_> { + fn gen_struct( + &mut self, + m: &Struct>, + payload: DeclPayload, + ) -> anyhow::Result<()> { + let (decl, name) = (payload.decl, &payload.decl.name); + // let name_up = &title(name); + self.gen_doc_comment(&decl.annotations); + // let fnames: &Vec = &m.fields.iter().map(|f| f.name.clone()).collect(); + let te_trs: Vec> = m.fields.iter().map(|f| f.type_expr.clone()).collect(); + let fnames = used_type_params(&te_trs); + quote_in! { self.t => + export interface $(name)<$(ref t => gen_type_params__(t, &fnames, &m.type_params))> $OC$['\r'] + } + let mut has_make = true; + for f in m.fields.iter() { + self.gen_doc_comment(&f.annotations); + let rt = rust_type(&f.type_expr).map_err(|s| anyhow!(s))?; + has_make = has_make && rt.0; + quote_in! { self.t => + $SP$SP$(&f.name): $(rt.1);$['\r'] + } + } + quote_in! { self.t => + $CC$['\r']$['\n'] + } + if has_make { + quote_in! { self.t => + export function make$(cap_or__(name))<$(ref t => gen_type_params__(t, &fnames, &m.type_params))>( + $(if m.fields.len() == 0 => _)input: { + $(ref tok => struct_field_make_input(tok, &m.fields)?) + } + ): $(name)<$(ref t => gen_type_params__(t, &fnames, &m.type_params))> { + return { + $(ref tok => struct_field_make_return(tok, &m.fields)) + }; + } + } + } + self.gen_rtti( + decl, + &RttiPayload { + mname: payload.mname.clone(), + type_params: &m.type_params, + }, + )?; + Ok(()) } - Ok(()) } -fn gen_newtype( - tokens: &mut Tokens, - imports: &Imports, - name: &String, - m: &NewType>, +fn struct_field_make_input( + t: &mut Tokens, + fs: &Vec>>, ) -> anyhow::Result<()> { - quote_in! { *tokens => - $("// newtype") + for f in fs { + let rt = rust_type(&f.type_expr).map_err(|s| anyhow!(s))?; + quote_in! { *t => + $(&f.name): $(rt.1),$['\r'] + } } Ok(()) } -fn gen_type( - tokens: &mut Tokens, - imports: &Imports, - name: &String, - m: &TypeDef>, -) -> anyhow::Result<()> { - quote_in! { *tokens => - $("// type") +fn struct_field_make_return(t: &mut Tokens, fs: &Vec>>) { + for f in fs { + quote_in! { *t => + $(&f.name): input.$(&f.name),$['\r'] + } + } +} + +// struct DeclPayload<'a>(&'a Decl>); +struct DeclPayload<'a> { + decl: &'a Decl>, + mname: &'a String, +} + +impl TsGenVisitor<'_> { + fn gen_union( + &mut self, + m: &Union>, + payload: DeclPayload, + ) -> anyhow::Result<()> { + let name = &payload.decl.name; + // self.lit("// union \n"); + let is_enum = m + .fields + .iter() + .find(|f| match &f.type_expr.type_ref { + TypeRef::Primitive(p) => match p { + PrimitiveType::Void => false, + _ => true, + }, + _ => true, + }) + .is_none(); + if !is_enum { + // let bnames_up = m.fields.iter().map(|b| title(&b.name)); + let mut opts = vec![]; + for b in m.fields.iter() { + self.gen_doc_comment(&b.annotations); + let bname = b.name.clone(); + // let bname_up = title(&b.name); + let rtype = rust_type(&b.type_expr).map_err(|s| anyhow!(s))?; + // &vec![rtype.1.clone()] + let used = used_type_params(&vec![b.type_expr.clone()]); + + opts.push((bname.clone(), rtype.clone().1)); + quote_in! { self.t => + export interface $(name)_$(bname.clone())<$(ref t => gen_type_params__(t, &used, &m.type_params))> { + kind: $("'")$(bname.clone())$("'"); + value: $(rtype.1.clone()); + }$['\r'] + } + } + let te_trs: Vec> = m.fields.iter().map(|f| f.type_expr.clone()).collect(); + let tp_names = used_type_params(&te_trs); + quote_in! { self.t => + $['\n'] + export type $name<$(ref t => gen_type_params__(t, &tp_names, &m.type_params))> = $(for n in m.fields.iter().map(|b| &b.name) join ( | ) => $(name)_$n<$(ref t => gen_type_params__(t, &tp_names, &m.type_params))>); + + export interface $(name)Opts<$(ref t => gen_type_params__(t, &tp_names, &m.type_params))> { + $(for opt in opts => $(opt.0): $(opt.1);$['\r']) + }$['\n'] + + export function make$(name)<$(ref t => gen_type_params__(t, &tp_names, &m.type_params))$(if m.type_params.len() > 0 => ,$[' '] )K extends keyof $(name)Opts<$(ref t => gen_type_params__(t, &tp_names, &m.type_params))>>(kind: K, value: $(name)Opts<$(ref t => gen_type_params__(t, &tp_names, &m.type_params))>[K]) { return {kind, value}; }$['\n'] + } + } else { + let b_names: Vec<&String> = m.fields.iter().map(|f| &f.name).collect(); + let b_len = b_names.len(); + let b1 = if b_len > 0 { b_names[0] } else { "" }; + quote_in! { self.t => + $['\n'] + export type $name = $(for n in b_names join ( | ) => $("'")$(n)$("'")); + $['\r'] + } + // TODO not sure what this is for -- duplicating existing ts + if b_len == 1 { + quote_in! { self.t => export const values$name : $name[] = [$("'")$(b1)$("'")];$['\r'] } + } + } + self.gen_rtti( + payload.decl, + &RttiPayload { + mname: payload.mname.clone(), + type_params: &m.type_params, + }, + )?; + Ok(()) + } + + fn gen_newtype(&mut self, _m: &NewType>) -> anyhow::Result<()> { + quote_in! { self.t => + $("// newtype") + } + Ok(()) + } + + fn gen_type(&mut self, _m: &TypeDef>) -> anyhow::Result<()> { + quote_in! { self.t => + $("// type") + } + Ok(()) + } +} + +pub fn cap_or__(input: &String) -> String { + let mut c = input.chars(); + match c.next() { + None => String::new(), + Some(first) => { + if first.is_uppercase() { + return input.clone(); + } else { + return "_".to_string() + input; + } + }, + } +} + +pub fn to_title(input: &String) -> String { + let mut c = input.chars(); + match c.next() { + None => String::new(), + Some(first) => first.to_uppercase().to_string() + &String::from(&input[1..]), + } +} + +/// returns (has_make_function,ts type) +fn rust_type(te: &TypeExpr) -> Result<(bool, String), String> { + match &te.type_ref { + TypeRef::ScopedName(_n) => todo!(), + TypeRef::LocalName(n) => tstype_from_local_name(n, &te.parameters), + TypeRef::Primitive(n) => tstype_from_prim(n, &te.parameters), + TypeRef::TypeParam(n) => Ok((true, n.clone())), + } +} + +fn tstype_from_local_name( + local_name: &String, + params: &Vec>, +) -> Result<(bool, String), String> { + if params.len() > 0 { + let mut tperr = vec![]; + let tps: Vec = params + .iter() + .filter_map(|p| { + let r = rust_type(p); + match r { + Ok((_, t)) => Some(t), + Err(e) => { + tperr.push(e); + return None; + } + } + }) + .collect(); + if tperr.len() != 0 { + let msg = tperr.join("\n\t"); + return Err(format!("Error constructing type param: {}", msg)); + } + let tpstr = format!("{}<{}>", local_name.clone(), tps.join(",")); + return Ok((true, tpstr)); + } + Ok((true, local_name.clone())) +} + +fn tstype_from_prim( + prim: &PrimitiveType, + params: &Vec>, +) -> Result<(bool, String), String> { + match prim { + PrimitiveType::Void => Ok((true, "null".to_string())), + PrimitiveType::Bool => Ok((true, "boolean".to_string())), + PrimitiveType::Int8 => Ok((true, "number".to_string())), + PrimitiveType::Int16 => Ok((true, "number".to_string())), + PrimitiveType::Int32 => Ok((true, "number".to_string())), + PrimitiveType::Int64 => Ok((true, "number".to_string())), + PrimitiveType::Word8 => Ok((true, "number".to_string())), + PrimitiveType::Word16 => Ok((true, "number".to_string())), + PrimitiveType::Word32 => Ok((true, "number".to_string())), + PrimitiveType::Word64 => Ok((true, "number".to_string())), + PrimitiveType::Float => Ok((true, "number".to_string())), + PrimitiveType::Double => Ok((true, "number".to_string())), + PrimitiveType::Json => Ok((true, "{}|null".to_string())), + PrimitiveType::ByteVector => Ok((true, "Uint8Array".to_string())), + PrimitiveType::String => Ok((true, "string".to_string())), + _ => { + if params.len() != 1 { + return Err(format!( "Primitive parameterized type require 1 and only one param. Type {:?} provided with {}", prim, params.len() )); + } + let param_type = rust_type(¶ms[0])?; + match prim { + PrimitiveType::Vector => { + return Ok((param_type.0, format!("{}[]", param_type.1))); + } + PrimitiveType::StringMap => Ok(( + param_type.0, + format!("{}[key: string]: {}{}", "{", param_type.1, "}"), + )), + PrimitiveType::Nullable => Ok((param_type.0, format!("({}|null)", param_type.1))), + PrimitiveType::TypeToken => Ok((false, format!("ADL.ATypeExpr<{}>", param_type.1))), + _ => Err(format!("unknown primitive {:?}", prim)), + } + } } - Ok(()) } diff --git a/rust/compiler/src/parser/mod.rs b/rust/compiler/src/parser/mod.rs index 54e49f2a..58bc6482 100644 --- a/rust/compiler/src/parser/mod.rs +++ b/rust/compiler/src/parser/mod.rs @@ -1,9 +1,8 @@ -use crate::adlgen::sys::adlast2 as adlast; +use crate::adlgen::sys::adlast2::{self as adlast}; use crate::adlgen::sys::adlast2::Spanned; use std::iter::repeat; use std::collections::HashMap; - use crate::adlrt::custom::sys::types::map::Map; use crate::adlrt::custom::sys::types::maybe::Maybe; @@ -169,6 +168,7 @@ pub fn raw_module(i: Input) -> Res { pub fn raw_module0(i: Input) -> Res { let (i, annotations) = many0(prefix_annotation)(i)?; + let ma = merge_annotations(annotations).map_err(|emsg| custom_error(i, emsg))?; let (i, _) = ws(tag("module"))(i)?; let (i, name) = ws(module_name)(i)?; let (i, (imports, decls_or_annotations)) = delimited( @@ -195,8 +195,7 @@ pub fn raw_module0(i: Input) -> Res { } } } - - let module = adlast::Module::new(name, imports, decls, merge_annotations(annotations)); + let module = adlast::Module::new(name, imports, decls, ma); Ok((i, (module, explicit_annotations))) } @@ -230,12 +229,12 @@ pub fn decl_or_annotation(i: Input) -> Res { pub fn decl(i: Input) -> Res> { let (i, annotations) = many0(prefix_annotation)(i)?; + let ma = merge_annotations(annotations).map_err(|emsg| custom_error(i, emsg))?; let (i, (name, dtype)) = decl_type(i)?; - let decl = adlast::Decl { name: name.to_owned(), r#type: dtype, - annotations: merge_annotations(annotations), + annotations: ma, version: Maybe::nothing(), }; Ok((i, decl)) @@ -259,8 +258,7 @@ pub fn prefix_annotation_(i: Input) -> Res, -) -> adlast::Annotations { - // Create a map out of the annotations, but join any doc strings as separate lines +) -> Result { let mut hm = HashMap::new(); let mut ds = Vec::new(); @@ -268,16 +266,20 @@ pub fn merge_annotations( if k == docstring_scoped_name() { ds.push(v.as_str().unwrap().to_owned()); } else { - hm.insert(k, v); + if let Some(_) = hm.insert(k.clone(), v) { + return Err(format!( + "Error duplicate annotation '{}.{}'", + &k.module_name, &k.name + )); + } } } if !ds.is_empty() { - hm.insert( - docstring_scoped_name(), - serde_json::Value::from(ds.join("\n")), - ); - } - Map(hm) + // ADL Doc string is (in ADL) `type Doc = Vector` not `type Doc = String` + hm.insert(docstring_scoped_name(), serde_json::Value::from(ds)); + }; + + Ok(Map(hm)) } pub fn docstring_scoped_name() -> adlast::ScopedName { @@ -384,6 +386,7 @@ pub fn field(i: Input) -> Res> { pub fn field0(i: Input) -> Res> { let (i, annotations) = many0(prefix_annotation)(i)?; + let ma = merge_annotations(annotations).map_err(|emsg| custom_error(i, emsg))?; let (i, texpr) = ws(type_expr)(i)?; let (i, name) = ws(ident0)(i)?; let (i, default) = opt(preceded(wtag("="), json))(i)?; @@ -392,7 +395,7 @@ pub fn field0(i: Input) -> Res> { serialized_name: name.to_owned(), type_expr: texpr, default: maybe_from_option(default), - annotations: merge_annotations(annotations), + annotations: ma, }; Ok((i, field)) } diff --git a/rust/compiler/src/processing/annotations.rs b/rust/compiler/src/processing/annotations.rs index 5e31ef4f..688ce9ff 100644 --- a/rust/compiler/src/processing/annotations.rs +++ b/rust/compiler/src/processing/annotations.rs @@ -1,14 +1,12 @@ use std::fmt; use super::Module0; -use crate::adlgen::sys::adlast2 as adlast; +use crate::adlgen::sys::adlast2::{self as adlast}; use crate::parser::{ExplicitAnnotationRef, RawModule}; /// Attach explicit annotations to the appropriate nodes in the AST. On failure, returns /// the nodes that could not be attached. -pub fn apply_explicit_annotations( - raw_module: RawModule, -) -> Result { +pub fn apply_explicit_annotations(raw_module: RawModule) -> Result { let (mut module0, explicit_annotations) = raw_module; let mut unresolved = Vec::new(); @@ -16,7 +14,16 @@ pub fn apply_explicit_annotations( let aref = find_annotations_ref(&ea.refr, &mut module0); match aref { Some(aref) => { - aref.0.insert(ea.scoped_name, ea.value); + if let Some(_) = aref.0.get(&ea.scoped_name) { + return Err(AnnotationError::Override(format!( + "explicit annotations can't override prefix annotation. Target '{}.{}'", + module0.name, + // ea.scoped_name.module_name, + ea.scoped_name.name + ))); + } else { + aref.0.insert(ea.scoped_name, ea.value); + } } None => { unresolved.push(ea.refr); @@ -27,7 +34,9 @@ pub fn apply_explicit_annotations( if unresolved.is_empty() { Ok(module0) } else { - Err(UnresolvedExplicitAnnotations { unresolved }) + Err(AnnotationError::Unresolved(UnresolvedExplicitAnnotations { + unresolved, + })) } } @@ -63,6 +72,30 @@ fn find_annotations_ref<'a>( } } +#[derive(Debug)] +pub enum AnnotationError { + Unresolved(UnresolvedExplicitAnnotations), + Override(String), +} + +impl std::error::Error for AnnotationError {} + +impl fmt::Display for AnnotationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AnnotationError::Unresolved(urefs) => { + urefs.fmt(f)?; + // Ok(()) + } + AnnotationError::Override(o) => { + write!(f, "Forbidden override: {}", o)?; + // Ok(()) + } + } + Ok(()) + } +} + #[derive(Debug)] pub struct UnresolvedExplicitAnnotations { pub unresolved: Vec, @@ -159,7 +192,10 @@ module X { let err = super::apply_explicit_annotations(rm).unwrap_err(); // All of which should have failed - assert_eq!(err.unresolved.len(), 3); + match err { + super::AnnotationError::Unresolved(err) => assert_eq!(err.unresolved.len(), 3), + _ => assert!(false), + } } const BAD_ADL: &str = " diff --git a/rust/compiler/src/processing/primitives.rs b/rust/compiler/src/processing/primitives.rs index 146c413b..e92c10f7 100644 --- a/rust/compiler/src/processing/primitives.rs +++ b/rust/compiler/src/processing/primitives.rs @@ -15,7 +15,7 @@ pub fn str_from_prim(prim: PrimitiveType) -> &'static str { PrimitiveType::Float => "Float", PrimitiveType::Double => "Double", PrimitiveType::Json => "Json", - PrimitiveType::ByteVector => "ByteVector", + PrimitiveType::ByteVector => "Bytes", PrimitiveType::String => "String", PrimitiveType::Vector => "Vector", PrimitiveType::StringMap => "StringMap", @@ -39,7 +39,7 @@ pub fn prim_from_str(s: &str) -> Option { "Float" => Some(PrimitiveType::Float), "Double" => Some(PrimitiveType::Double), "Json" => Some(PrimitiveType::Json), - "ByteVector" => Some(PrimitiveType::ByteVector), + "Bytes" => Some(PrimitiveType::ByteVector), "String" => Some(PrimitiveType::String), "Vector" => Some(PrimitiveType::Vector), "StringMap" => Some(PrimitiveType::StringMap), diff --git a/rust/compiler/src/processing/resolver.rs b/rust/compiler/src/processing/resolver.rs index 6244100d..8755c82b 100644 --- a/rust/compiler/src/processing/resolver.rs +++ b/rust/compiler/src/processing/resolver.rs @@ -1,8 +1,8 @@ use anyhow::anyhow; -use std::collections::{HashSet}; use std::collections::HashMap; +use std::collections::HashSet; -use crate::adlgen::sys::adlast2 as adlast; +use crate::adlgen::sys::adlast2::{self as adlast, ScopedName}; use crate::adlrt::custom::sys::types::map::Map; use super::loader::AdlLoader; @@ -266,6 +266,7 @@ pub fn resolve_field( Ok(field1) } +/// This function checks that each annotation refers to an actual declaration pub fn resolve_annotations( ctx: &ResolveCtx, annotations0: &adlast::Annotations, @@ -275,13 +276,17 @@ pub fn resolve_annotations( .iter() .map(|(sn0, jv)| { let tr1 = ctx.resolve_type_ref(sn0)?; - if let TypeRef::ScopedName(sn1) = tr1 { - Ok((sn1, jv.clone())) - } else { - Err(anyhow!( - "no decl {} found for explicit annotation", - sn0.name - )) + match tr1 { + TypeRef::ScopedName(sn1) => Ok((sn1, jv.clone())), + TypeRef::LocalName(ln1) => Ok(( + ScopedName { + module_name: ctx.module0.name.clone(), + name: ln1, + }, + jv.clone(), + )), + TypeRef::Primitive(_) => Err(anyhow!("primitives can't be annotations")), + TypeRef::TypeParam(_) => Err(anyhow!("typeparams can't be annotations")), } }) .collect::>>()?; @@ -327,7 +332,7 @@ impl<'a> ResolveCtx<'a> { if let Some(scoped_name) = self.expanded_imports.get(name) { return Ok(TypeRef::ScopedName(scoped_name.clone())); } - Err(anyhow!("type {} not found", name)) + Err(anyhow!("Local type {} not found", name)) } else { match self.resolver.get_rmodule(&scoped_name0.module_name) { None => return Err(anyhow!("module {} not found", scoped_name0.module_name)), diff --git a/rust/runtime/custom/sys/types/maybe.rs b/rust/runtime/custom/sys/types/maybe.rs index 9cfe6a7c..252b228d 100644 --- a/rust/runtime/custom/sys/types/maybe.rs +++ b/rust/runtime/custom/sys/types/maybe.rs @@ -6,7 +6,7 @@ use std::result; // with ADL compatible serialization #[derive(Clone,Debug,Eq,Hash,PartialEq)] -pub struct Maybe (Option); +pub struct Maybe (pub Option); impl Maybe { pub fn nothing() -> Maybe {