1use crate::{
5 common,
6 indent::{IndentConfig, IndentedWriter},
7 CodeGeneratorConfig,
8};
9use heck::CamelCase;
10use include_dir::include_dir as include_directory;
11use serde_reflection::{ContainerFormat, Format, FormatHolder, Named, Registry, VariantFormat};
12use std::{
13 collections::{BTreeMap, HashMap},
14 io::{Result, Write},
15 path::PathBuf,
16};
17
18pub struct CodeGenerator<'a> {
21 config: &'a CodeGeneratorConfig,
23 external_qualified_names: HashMap<String, String>,
26 namespaces_to_import: Vec<String>,
28}
29
30struct TypeScriptEmitter<'a, T> {
32 out: IndentedWriter<T>,
34 generator: &'a CodeGenerator<'a>,
36}
37
38impl<'a> CodeGenerator<'a> {
39 pub fn new(config: &'a CodeGeneratorConfig) -> Self {
41 if config.c_style_enums {
42 panic!("TypeScript does not support generating c-style enums");
43 }
44 let mut external_qualified_names = HashMap::new();
45 for (namespace, names) in &config.external_definitions {
46 for name in names {
47 external_qualified_names.insert(
48 name.to_string(),
49 format!("{}.{}", namespace.to_camel_case(), name),
50 );
51 }
52 }
53 Self {
54 config,
55 external_qualified_names,
56 namespaces_to_import: config
57 .external_definitions
58 .keys()
59 .map(|k| k.to_string())
60 .collect::<Vec<_>>(),
61 }
62 }
63
64 pub fn output(&self, out: &mut dyn Write, registry: &Registry) -> Result<()> {
66 let mut emitter = TypeScriptEmitter {
67 out: IndentedWriter::new(out, IndentConfig::Space(2)),
68 generator: self,
69 };
70
71 emitter.output_preamble()?;
72
73 for (name, format) in registry {
74 emitter.output_container(name, format)?;
75 }
76
77 if self.config.serialization {
78 emitter.output_helpers(registry)?;
79 }
80
81 Ok(())
82 }
83}
84
85impl<'a, T> TypeScriptEmitter<'a, T>
86where
87 T: Write,
88{
89 fn output_preamble(&mut self) -> Result<()> {
90 writeln!(
91 self.out,
92 r#"
93import {{ Serializer, Deserializer }} from '../serde/mod.ts';
94import {{ BcsSerializer, BcsDeserializer }} from '../bcs/mod.ts';
95import {{ Optional, Seq, Tuple, ListTuple, unit, bool, int8, int16, int32, int64, int128, uint8, uint16, uint32, uint64, uint128, float32, float64, char, str, bytes }} from '../serde/mod.ts';
96"#,
97 )?;
98 for namespace in self.generator.namespaces_to_import.iter() {
99 writeln!(
100 self.out,
101 "import * as {} from '../{}/mod.ts';\n",
102 namespace.to_camel_case(),
103 namespace
104 )?;
105 }
106
107 Ok(())
108 }
109
110 fn quote_qualified_name(&self, name: &str) -> String {
111 self.generator
112 .external_qualified_names
113 .get(name)
114 .cloned()
115 .unwrap_or_else(|| name.to_string())
116 }
117
118 fn output_comment(&mut self, name: &str) -> std::io::Result<()> {
119 let path = vec![name.to_string()];
120 if let Some(doc) = self.generator.config.comments.get(&path) {
121 let text = textwrap::indent(doc, " * ").replace("\n\n", "\n *\n");
122 writeln!(self.out, "/**\n{} */", text)?;
123 }
124 Ok(())
125 }
126
127 fn quote_type(&self, format: &Format) -> String {
128 use Format::*;
129 match format {
130 TypeName(x) => self.quote_qualified_name(x),
131 Unit => "unit".into(),
132 Bool => "bool".into(),
133 I8 => "int8".into(),
134 I16 => "int16".into(),
135 I32 => "int32".into(),
136 I64 => "int64".into(),
137 I128 => "int128".into(),
138 U8 => "uint8".into(),
139 U16 => "uint16".into(),
140 U32 => "uint32".into(),
141 U64 => "uint64".into(),
142 U128 => "uint128".into(),
143 F32 => "float32".into(),
144 F64 => "float64".into(),
145 Char => "char".into(),
146 Str => "str".into(),
147 Bytes => "bytes".into(),
148
149 Option(format) => format!("Optional<{}>", self.quote_type(format)),
150 Seq(format) => format!("Seq<{}>", self.quote_type(format)),
151 Map { key, value } => {
152 format!("Map<{},{}>", self.quote_type(key), self.quote_type(value))
153 }
154 Tuple(formats) => format!("Tuple<[{}]>", self.quote_types(formats, ", ")),
155 TupleArray {
156 content,
157 size: _size,
158 } => format!("ListTuple<[{}]>", self.quote_type(content),),
159 Variable(_) => panic!("unexpected value"),
160 }
161 }
162
163 fn quote_types(&self, formats: &[Format], sep: &str) -> String {
164 formats
165 .iter()
166 .map(|f| self.quote_type(f))
167 .collect::<Vec<_>>()
168 .join(sep)
169 }
170
171 fn output_helpers(&mut self, registry: &Registry) -> Result<()> {
172 let mut subtypes = BTreeMap::new();
173 for format in registry.values() {
174 format
175 .visit(&mut |f| {
176 if Self::needs_helper(f) {
177 subtypes.insert(common::mangle_type(f), f.clone());
178 }
179 Ok(())
180 })
181 .unwrap();
182 }
183
184 writeln!(self.out, "export class Helpers {{")?;
185 self.out.indent();
186 for (mangled_name, subtype) in &subtypes {
187 self.output_serialization_helper(mangled_name, subtype)?;
188 self.output_deserialization_helper(mangled_name, subtype)?;
189 }
190 self.out.unindent();
191 writeln!(self.out, "}}")?;
192 writeln!(self.out)
193 }
194
195 fn needs_helper(format: &Format) -> bool {
196 use Format::*;
197 matches!(
198 format,
199 Option(_) | Seq(_) | Map { .. } | Tuple(_) | TupleArray { .. }
200 )
201 }
202
203 fn quote_serialize_value(&self, value: &str, format: &Format, use_this: bool) -> String {
204 use Format::*;
205 let this_str = if use_this { "this." } else { "" };
206
207 match format {
208 TypeName(_) => format!("{}{}.serialize(serializer);", this_str, value),
209 Unit => format!("serializer.serializeUnit({}{});", this_str, value),
210 Bool => format!("serializer.serializeBool({}{});", this_str, value),
211 I8 => format!("serializer.serializeI8({}{});", this_str, value),
212 I16 => format!("serializer.serializeI16({}{});", this_str, value),
213 I32 => format!("serializer.serializeI32({}{});", this_str, value),
214 I64 => format!("serializer.serializeI64({}{});", this_str, value),
215 I128 => format!("serializer.serializeI128({}{});", this_str, value),
216 U8 => format!("serializer.serializeU8({}{});", this_str, value),
217 U16 => format!("serializer.serializeU16({}{});", this_str, value),
218 U32 => format!("serializer.serializeU32({}{});", this_str, value),
219 U64 => format!("serializer.serializeU64({}{});", this_str, value),
220 U128 => format!("serializer.serializeU128({}{});", this_str, value),
221 F32 => format!("serializer.serializeF32({}{});", this_str, value),
222 F64 => format!("serializer.serializeF64({}{});", this_str, value),
223 Char => format!("serializer.serializeChar({}{});", this_str, value),
224 Str => format!("serializer.serializeStr({}{});", this_str, value),
225 Bytes => format!("serializer.serializeBytes({}{});", this_str, value),
226 _ => format!(
227 "Helpers.serialize{}({}{}, serializer);",
228 common::mangle_type(format).to_camel_case(),
229 this_str,
230 value
231 ),
232 }
233 }
234
235 fn quote_deserialize(&self, format: &Format) -> String {
236 use Format::*;
237 match format {
238 TypeName(name) => format!(
239 "{}.deserialize(deserializer)",
240 self.quote_qualified_name(name)
241 ),
242 Unit => "deserializer.deserializeUnit()".to_string(),
243 Bool => "deserializer.deserializeBool()".to_string(),
244 I8 => "deserializer.deserializeI8()".to_string(),
245 I16 => "deserializer.deserializeI16()".to_string(),
246 I32 => "deserializer.deserializeI32()".to_string(),
247 I64 => "deserializer.deserializeI64()".to_string(),
248 I128 => "deserializer.deserializeI128()".to_string(),
249 U8 => "deserializer.deserializeU8()".to_string(),
250 U16 => "deserializer.deserializeU16()".to_string(),
251 U32 => "deserializer.deserializeU32()".to_string(),
252 U64 => "deserializer.deserializeU64()".to_string(),
253 U128 => "deserializer.deserializeU128()".to_string(),
254 F32 => "deserializer.deserializeF32()".to_string(),
255 F64 => "deserializer.deserializeF64()".to_string(),
256 Char => "deserializer.deserializeChar()".to_string(),
257 Str => "deserializer.deserializeStr()".to_string(),
258 Bytes => "deserializer.deserializeBytes()".to_string(),
259 _ => format!(
260 "Helpers.deserialize{}(deserializer)",
261 common::mangle_type(format).to_camel_case(),
262 ),
263 }
264 }
265
266 fn output_serialization_helper(&mut self, name: &str, format0: &Format) -> Result<()> {
267 use Format::*;
268
269 write!(
270 self.out,
271 "static serialize{}(value: {}, serializer: Serializer): void {{",
272 name.to_camel_case(),
273 self.quote_type(format0)
274 )?;
275 self.out.indent();
276 match format0 {
277 Option(format) => {
278 write!(
279 self.out,
280 r#"
281if (value) {{
282 serializer.serializeOptionTag(true);
283 {}
284}} else {{
285 serializer.serializeOptionTag(false);
286}}
287"#,
288 self.quote_serialize_value("value", format, false)
289 )?;
290 }
291
292 Seq(format) => {
293 write!(
294 self.out,
295 r#"
296serializer.serializeLen(value.length);
297value.forEach((item: {}) => {{
298 {}
299}});
300"#,
301 self.quote_type(format),
302 self.quote_serialize_value("item", format, false)
303 )?;
304 }
305
306 Map { key, value } => {
307 write!(
308 self.out,
309 r#"
310serializer.serializeLen(value.size);
311const offsets: number[] = [];
312for (const [k, v] of value.entries()) {{
313 offsets.push(serializer.getBufferOffset());
314 {}
315 {}
316}}
317serializer.sortMapEntries(offsets);
318"#,
319 self.quote_serialize_value("k", key, false),
320 self.quote_serialize_value("v", value, false)
321 )?;
322 }
323
324 Tuple(formats) => {
325 writeln!(self.out)?;
326 for (index, format) in formats.iter().enumerate() {
327 let expr = format!("value[{}]", index);
328 writeln!(
329 self.out,
330 "{}",
331 self.quote_serialize_value(&expr, format, false)
332 )?;
333 }
334 }
335
336 TupleArray {
337 content,
338 size: _size,
339 } => {
340 write!(
341 self.out,
342 r#"
343value.forEach((item) =>{{
344 {}
345}});
346"#,
347 self.quote_serialize_value("item[0]", content, false)
348 )?;
349 }
350
351 _ => panic!("unexpected case"),
352 }
353 self.out.unindent();
354 writeln!(self.out, "}}\n")
355 }
356
357 fn output_deserialization_helper(&mut self, name: &str, format0: &Format) -> Result<()> {
358 use Format::*;
359
360 write!(
361 self.out,
362 "static deserialize{}(deserializer: Deserializer): {} {{",
363 name.to_camel_case(),
364 self.quote_type(format0),
365 )?;
366 self.out.indent();
367 match format0 {
368 Option(format) => {
369 write!(
370 self.out,
371 r#"
372const tag = deserializer.deserializeOptionTag();
373if (!tag) {{
374 return null;
375}} else {{
376 return {};
377}}
378"#,
379 self.quote_deserialize(format),
380 )?;
381 }
382
383 Seq(format) => {
384 write!(
385 self.out,
386 r#"
387const length = deserializer.deserializeLen();
388const list: {} = [];
389for (let i = 0; i < length; i++) {{
390 list.push({});
391}}
392return list;
393"#,
394 self.quote_type(format0),
395 self.quote_deserialize(format)
396 )?;
397 }
398
399 Map { key, value } => {
400 write!(
401 self.out,
402 r#"
403const length = deserializer.deserializeLen();
404const obj = new Map<{0}, {1}>();
405let previousKeyStart = 0;
406let previousKeyEnd = 0;
407for (let i = 0; i < length; i++) {{
408 const keyStart = deserializer.getBufferOffset();
409 const key = {2};
410 const keyEnd = deserializer.getBufferOffset();
411 if (i > 0) {{
412 deserializer.checkThatKeySlicesAreIncreasing(
413 [previousKeyStart, previousKeyEnd],
414 [keyStart, keyEnd]);
415 }}
416 previousKeyStart = keyStart;
417 previousKeyEnd = keyEnd;
418 const value = {3};
419 obj.set(key, value);
420}}
421return obj;
422"#,
423 self.quote_type(key),
424 self.quote_type(value),
425 self.quote_deserialize(key),
426 self.quote_deserialize(value),
427 )?;
428 }
429
430 Tuple(formats) => {
431 write!(
432 self.out,
433 r#"
434return [{}
435];
436"#,
437 formats
438 .iter()
439 .map(|f| format!("\n {}", self.quote_deserialize(f)))
440 .collect::<Vec<_>>()
441 .join(",")
442 )?;
443 }
444
445 TupleArray { content, size } => {
446 write!(
447 self.out,
448 r#"
449const list: {} = [];
450for (let i = 0; i < {}; i++) {{
451 list.push([{}]);
452}}
453return list;
454"#,
455 self.quote_type(format0),
456 size,
457 self.quote_deserialize(content)
458 )?;
459 }
460
461 _ => panic!("unexpected case"),
462 }
463 self.out.unindent();
464 writeln!(self.out, "}}\n")
465 }
466
467 fn output_variant(
468 &mut self,
469 base: &str,
470 index: u32,
471 name: &str,
472 variant: &VariantFormat,
473 ) -> Result<()> {
474 use VariantFormat::*;
475 let fields = match variant {
476 Unit => Vec::new(),
477 NewType(format) => vec![Named {
478 name: "value".to_string(),
479 value: format.as_ref().clone(),
480 }],
481 Tuple(formats) => formats
482 .iter()
483 .enumerate()
484 .map(|(i, f)| Named {
485 name: format!("field{}", i),
486 value: f.clone(),
487 })
488 .collect(),
489 Struct(fields) => fields.clone(),
490 Variable(_) => panic!("incorrect value"),
491 };
492 self.output_struct_or_variant_container(Some(base), Some(index), name, &fields)
493 }
494
495 fn output_variants(
496 &mut self,
497 base: &str,
498 variants: &BTreeMap<u32, Named<VariantFormat>>,
499 ) -> Result<()> {
500 for (index, variant) in variants {
501 self.output_variant(base, *index, &variant.name, &variant.value)?;
502 }
503 Ok(())
504 }
505
506 fn output_struct_or_variant_container(
507 &mut self,
508 variant_base: Option<&str>,
509 variant_index: Option<u32>,
510 name: &str,
511 fields: &[Named<Format>],
512 ) -> Result<()> {
513 let mut variant_base_name = String::new();
514
515 if let Some(base) = variant_base {
517 writeln!(self.out)?;
518 self.output_comment(name)?;
519 writeln!(
520 self.out,
521 "export class {0}Variant{1} extends {0} {{",
522 base, name
523 )?;
524 variant_base_name = format!("{0}Variant", base);
525 } else {
526 self.output_comment(name)?;
527 writeln!(self.out, "export class {} {{", name)?;
528 }
529 if !fields.is_empty() {
530 writeln!(self.out)?;
531 }
532 writeln!(
534 self.out,
535 "constructor ({}) {{",
536 fields
537 .iter()
538 .map(|f| { format!("public {}: {}", &f.name, self.quote_type(&f.value)) })
539 .collect::<Vec<_>>()
540 .join(", ")
541 )?;
542 if let Some(_base) = variant_base {
543 self.out.indent();
544 writeln!(self.out, "super();")?;
545 self.out.unindent();
546 }
547 writeln!(self.out, "}}\n")?;
548 if self.generator.config.serialization {
550 writeln!(
551 self.out,
552 "public serialize(serializer: Serializer): void {{",
553 )?;
554 self.out.indent();
555 if let Some(index) = variant_index {
556 writeln!(self.out, "serializer.serializeVariantIndex({});", index)?;
557 }
558 for field in fields {
559 writeln!(
560 self.out,
561 "{}",
562 self.quote_serialize_value(&field.name, &field.value, true)
563 )?;
564 }
565 self.out.unindent();
566 writeln!(self.out, "}}\n")?;
567 }
568 if self.generator.config.serialization {
570 if variant_index.is_none() {
571 writeln!(
572 self.out,
573 "static deserialize(deserializer: Deserializer): {} {{",
574 name,
575 )?;
576 } else {
577 writeln!(
578 self.out,
579 "static load(deserializer: Deserializer): {}{} {{",
580 variant_base_name, name,
581 )?;
582 }
583 self.out.indent();
584 for field in fields {
585 writeln!(
586 self.out,
587 "const {} = {};",
588 field.name,
589 self.quote_deserialize(&field.value)
590 )?;
591 }
592 writeln!(
593 self.out,
594 r#"return new {0}{1}({2});"#,
595 variant_base_name,
596 name,
597 fields
598 .iter()
599 .map(|f| f.name.to_string())
600 .collect::<Vec<_>>()
601 .join(",")
602 )?;
603 self.out.unindent();
604 writeln!(self.out, "}}\n")?;
605 }
606 writeln!(self.out, "}}")
607 }
608
609 fn output_enum_container(
610 &mut self,
611 name: &str,
612 variants: &BTreeMap<u32, Named<VariantFormat>>,
613 ) -> Result<()> {
614 self.output_comment(name)?;
615 writeln!(self.out, "export abstract class {} {{", name)?;
616 if self.generator.config.serialization {
617 writeln!(
618 self.out,
619 "abstract serialize(serializer: Serializer): void;\n"
620 )?;
621 write!(
622 self.out,
623 "static deserialize(deserializer: Deserializer): {} {{",
624 name
625 )?;
626 self.out.indent();
627 writeln!(
628 self.out,
629 r#"
630const index = deserializer.deserializeVariantIndex();
631switch (index) {{"#,
632 )?;
633 self.out.indent();
634 for (index, variant) in variants {
635 writeln!(
636 self.out,
637 "case {}: return {}Variant{}.load(deserializer);",
638 index, name, variant.name,
639 )?;
640 }
641 writeln!(
642 self.out,
643 "default: throw new Error(\"Unknown variant index for {}: \" + index);",
644 name,
645 )?;
646 self.out.unindent();
647 writeln!(self.out, "}}")?;
648 self.out.unindent();
649 writeln!(self.out, "}}")?;
650 }
651 writeln!(self.out, "}}\n")?;
652 self.output_variants(name, variants)?;
653 Ok(())
654 }
655
656 fn output_container(&mut self, name: &str, format: &ContainerFormat) -> Result<()> {
657 use ContainerFormat::*;
658 let fields = match format {
659 UnitStruct => Vec::new(),
660 NewTypeStruct(format) => vec![Named {
661 name: "value".to_string(),
662 value: format.as_ref().clone(),
663 }],
664 TupleStruct(formats) => formats
665 .iter()
666 .enumerate()
667 .map(|(i, f)| Named {
668 name: format!("field{}", i),
669 value: f.clone(),
670 })
671 .collect::<Vec<_>>(),
672 Struct(fields) => fields.clone(),
673 Enum(variants) => {
674 self.output_enum_container(name, variants)?;
675 return Ok(());
676 }
677 };
678 self.output_struct_or_variant_container(None, None, name, &fields)
679 }
680}
681
682pub struct Installer {
684 install_dir: PathBuf,
685}
686
687impl Installer {
688 pub fn new(install_dir: PathBuf) -> Self {
689 Installer { install_dir }
690 }
691
692 fn install_runtime(
693 &self,
694 source_dir: include_dir::Dir,
695 path: &str,
696 ) -> std::result::Result<(), Box<dyn std::error::Error>> {
697 let dir_path = self.install_dir.join(path);
698 std::fs::create_dir_all(&dir_path)?;
699 for entry in source_dir.files() {
700 let mut file = std::fs::File::create(dir_path.join(entry.path()))?;
701 file.write_all(entry.contents())?;
702 }
703 Ok(())
704 }
705}
706
707impl crate::SourceInstaller for Installer {
708 type Error = Box<dyn std::error::Error>;
709
710 fn install_module(
711 &self,
712 config: &CodeGeneratorConfig,
713 registry: &Registry,
714 ) -> std::result::Result<(), Self::Error> {
715 let dir_path = self.install_dir.join(&config.module_name);
716 std::fs::create_dir_all(&dir_path)?;
717 let source_path = dir_path.join("mod.ts");
718 let mut file = std::fs::File::create(source_path)?;
719
720 let generator = CodeGenerator::new(config);
721 generator.output(&mut file, registry)?;
722 Ok(())
723 }
724
725 fn install_serde_runtime(&self) -> std::result::Result<(), Self::Error> {
726 self.install_runtime(include_directory!("runtime/typescript/serde"), "serde")
727 }
728
729 fn install_bincode_runtime(&self) -> std::result::Result<(), Self::Error> {
730 self.install_runtime(include_directory!("runtime/typescript/bincode"), "bincode")
731 }
732
733 fn install_bcs_runtime(&self) -> std::result::Result<(), Self::Error> {
734 self.install_runtime(include_directory!("runtime/typescript/bcs"), "bcs")
735 }
736}