1use verilization_compiler::{lang, model, util, for_sep};
2
3use model::Named;
4use lang::{GeneratorError, Language, OutputHandler};
5use std::ffi::OsString;
6use std::collections::{HashMap, HashSet};
7use std::io::Write;
8use std::path::{Path, PathBuf};
9use num_bigint::BigUint;
10use lang::generator::*;
11use util::{capitalize_identifier, uncapitalize_identifier};
12
13pub struct TSOptionsBuilder {
14 output_dir: Option<OsString>,
15 package_mapping: HashMap<model::PackageName, OsString>,
16 library_mapping: HashMap<model::PackageName, OsString>,
17}
18
19pub struct TSOptions {
20 pub output_dir: OsString,
21 pub package_mapping: HashMap<model::PackageName, OsString>,
22 pub library_mapping: HashMap<model::PackageName, OsString>,
23}
24
25
26fn open_ts_file<'output, Output: OutputHandler<'output>>(options: &TSOptions, output: &'output mut Output, name: &model::QualifiedName) -> Result<Output::FileHandle, GeneratorError> {
27 let pkg_dir = options.package_mapping.get(&name.package).ok_or_else(|| GeneratorError::UnmappedPackage(name.package.clone()))?;
28 let mut path = PathBuf::from(&options.output_dir);
29 path.push(pkg_dir);
30 path.push(name.name.clone() + ".ts");
31 Ok(output.create_file(path)?)
32}
33
34fn make_type_name(name: &str) -> String {
35 let mut name = String::from(name);
36 capitalize_identifier(&mut name);
37 name
38}
39
40fn make_field_name(field_name: &str) -> String {
41 let mut name = String::from(field_name);
42 uncapitalize_identifier(&mut name);
43 name
44}
45
46pub trait TSGenerator<'model> : Generator<'model> + GeneratorWithFile {
47 fn generator_element_name(&self) -> Option<&'model model::QualifiedName>;
48 fn options(&self) -> &TSOptions;
49 fn referenced_types(&self) -> model::ReferencedTypeIterator<'model>;
50 fn current_dir(&self) -> Result<PathBuf, GeneratorError>;
51
52 fn add_user_converter(&mut self, name: String);
53
54 fn write_import_name(&mut self, name: &model::QualifiedName) -> Result<(), GeneratorError> {
55 write!(self.file(), "sym_")?;
56
57 for part in &name.package.package {
58 write!(self.file(), "{}_", part)?;
59 }
60
61 write!(self.file(), "{}", &name.name)?;
62
63 Ok(())
64 }
65
66 fn write_import<P: AsRef<Path>>(&mut self, t: &model::QualifiedName, current_path: &P) -> Result<(), GeneratorError> {
67 let is_rel;
68
69 let mut import_path = if let Some(import_pkg_dir) = self.options().package_mapping.get(&t.package) {
70 let mut abs_import_path = PathBuf::from(&self.options().output_dir);
71 abs_import_path.push(import_pkg_dir);
72
73 is_rel = true;
74 pathdiff::diff_paths(abs_import_path, current_path).expect("Could not find relative path.")
75 }
76 else if let Some(import_lib) = self.options().library_mapping.get(&t.package) {
77 is_rel = false;
78 PathBuf::from(import_lib)
79 }
80 else {
81 return Err(GeneratorError::UnmappedPackage(t.package.clone()))
82 };
83
84 import_path.push(t.name.clone() + ".js");
85
86
87 write!(self.file(), "import * as ")?;
88 self.write_import_name(&t)?;
89 write!(self.file(), " from \"")?;
90 if is_rel {
91 write!(self.file(), "./")?;
92 }
93 writeln!(self.file(), "{}\";", import_path.to_str().unwrap())?;
94
95 Ok(())
96 }
97
98 fn write_imports(&mut self) -> Result<(), GeneratorError> {
99 let current_path = self.current_dir()?;
100
101
102 let mut referenced_types: Vec<_> = self.referenced_types().collect();
103 referenced_types.sort();
104
105 for t in referenced_types {
106 if self.generator_element_name() == Some(t) {
107 continue;
108 }
109
110 let t = match self.scope().lookup(t.clone()) {
111 model::ScopeLookup::TypeParameter(_) => continue,
112 model::ScopeLookup::NamedType(t) => t,
113 };
114
115 self.write_import(&t, ¤t_path)?;
116 }
117
118 Ok(())
119 }
120
121 fn write_type_args(&mut self, args: &Vec<LangType<'model>>) -> Result<(), GeneratorError> {
122 if !args.is_empty() {
123 write!(self.file(), "<")?;
124 for_sep!(arg, args, { write!(self.file(), ", ")?; }, {
125 self.write_type(&arg)?;
126 });
127 write!(self.file(), ">")?;
128 }
129
130 Ok(())
131 }
132
133 fn write_args(&mut self, args: &Vec<LangExpr<'model>>) -> Result<(), GeneratorError> {
134 if !args.is_empty() {
135 write!(self.file(), "(")?;
136 for_sep!(arg, args, { write!(self.file(), ", ")?; }, {
137 self.write_expr(&arg)?;
138 });
139 write!(self.file(), ")")?;
140 }
141
142 Ok(())
143 }
144
145 fn write_type(&mut self, t: &LangType<'model>) -> Result<(), GeneratorError> {
146 Ok(match t {
147 LangType::Versioned(_, name, version, args, _) => {
148 if self.generator_element_name() != Some(name) {
150 self.write_import_name(name)?;
151 write!(self.file(), ".")?;
152 }
153
154 write!(self.file(), "V{}", version)?;
155 self.write_type_args(&args)?;
156 },
157
158 LangType::Extern(name, args, _) => {
159 self.write_import_name(name)?;
160
161 write!(self.file(), ".{}", make_type_name(&name.name))?;
162 self.write_type_args(&args)?;
163 },
164
165 LangType::TypeParameter(name) => {
166 write!(self.file(), "{}", name)?;
167 },
168
169 LangType::Converter(from, to) => {
170 write!(self.file(), "Converter<")?;
171 self.write_type(&*from)?;
172 write!(self.file(), ", ")?;
173 self.write_type(&*to)?;
174 write!(self.file(), ">")?;
175 },
176
177 LangType::Codec(t) => {
178 write!(self.file(), "Codec<")?;
179 self.write_type(&*t)?;
180 write!(self.file(), ">")?;
181 },
182 })
183 }
184
185 fn write_operation_name(&mut self, op: &Operation) -> Result<(), GeneratorError> {
186 match op {
187 Operation::FromPreviousVersion(prev_ver) => write!(self.file(), "fromV{}", prev_ver)?,
188 Operation::FinalTypeConverter => write!(self.file(), "converter")?,
189 Operation::TypeCodec => write!(self.file(), "codec")?,
190 Operation::FromInteger => write!(self.file(), "fromInteger")?,
191 Operation::FromString => write!(self.file(), "fromString")?,
192 Operation::FromSequence => write!(self.file(), "fromSequence")?,
193 Operation::FromRecord(_) => write!(self.file(), "fromRecord")?,
194 Operation::FromCase(name) => write!(self.file(), "fromCase{}", make_type_name(name))?,
195 }
196
197 Ok(())
198 }
199
200 fn write_expr(&mut self, expr: &LangExpr<'model>) -> Result<(), GeneratorError> {
201 match expr {
202 LangExpr::Identifier(name) => write!(self.file(), "{}", name)?,
203 LangExpr::IntegerLiteral(n) => write!(self.file(), "{}n", n)?,
204 LangExpr::StringLiteral(s) => {
205 write!(self.file(), "\"")?;
206 for codepoint in s.chars() {
207 match codepoint {
208 '"' => write!(self.file(), "\\\"")?,
209 '\\' => write!(self.file(), "\\\\")?,
210 '\n' => write!(self.file(), "\\n")?,
211 '\r' => write!(self.file(), "\\r")?,
212 '\u{2028}' => write!(self.file(), "\\u2028")?,
213 '\u{2029}' => write!(self.file(), "\\u2029")?,
214 _ => write!(self.file(), "{}", codepoint)?,
215 }
216 }
217 write!(self.file(), "\"")?;
218 },
219
220 LangExpr::InvokeConverter { converter, value } => {
221 self.write_expr(&*converter)?;
222 write!(self.file(), ".convert(")?;
223 self.write_expr(&*value)?;
224 write!(self.file(), ")")?;
225 },
226 LangExpr::IdentityConverter(t) => {
227 write!(self.file(), "Converter.identity<")?;
228 self.write_type(t)?;
229 write!(self.file(), ">()")?;
230 },
231 LangExpr::ReadDiscriminator => write!(self.file(), "await natCodec.read(reader)")?,
232 LangExpr::WriteDiscriminator(value) => write!(self.file(), "await natCodec.write(writer, {}n)", value)?,
233 LangExpr::CodecRead { codec } => {
234 write!(self.file(), "await ")?;
235 self.write_expr(&*codec)?;
236 write!(self.file(), ".read(reader)")?;
237 },
238 LangExpr::CodecWrite { codec, value } => {
239 write!(self.file(), "await ")?;
240 self.write_expr(&*codec)?;
241 write!(self.file(), ".write(writer, ")?;
242 self.write_expr(value)?;
243 write!(self.file(), ")")?;
244 },
245 LangExpr::InvokeOperation(op, target, type_args, args) => {
246 match target {
247 OperationTarget::VersionedType(name, version) => {
248 if self.generator_element_name() != Some(name) {
250 self.write_import_name(name)?;
251 write!(self.file(), ".")?;
252 }
253
254 write!(self.file(), "V{}.", version)?;
255 },
256 OperationTarget::ExternType(name) => {
257 self.write_import_name(name)?;
258 write!(self.file(), ".")?;
259 },
260 }
261 self.write_operation_name(op)?;
262 self.write_type_args(type_args)?;
263
264 match op {
265 Operation::FromRecord(field_names) => {
266 write!(self.file(), "({{ ")?;
267 for (field_name, arg) in field_names.iter().zip(args.iter()) {
268 write!(self.file(), "{}: ", make_field_name(field_name))?;
269 self.write_expr(arg)?;
270 write!(self.file(), ", ")?;
271 }
272 write!(self.file(), "}})")?;
273 },
274 _ => self.write_args(args)?,
275 }
276 },
277 LangExpr::InvokeUserConverter { name: _, prev_ver, version, type_args, args } => {
278 let name = format!("v{}_to_v{}", prev_ver, version);
279 write!(self.file(), "{}", name)?;
280 self.add_user_converter(name);
281 self.write_type_args(type_args)?;
282 self.write_args(args)?;
283 },
284 LangExpr::ConstantValue(name, version) => {
285 if self.generator_element_name() != Some(name) {
287 self.write_import_name(name)?;
288 write!(self.file(), ".")?;
289 }
290
291 write!(self.file(), "{}", TypeScriptLanguage::constant_version_name(version))?;
292 },
293 LangExpr::CreateStruct(_, _, _, fields) => {
294 write!(self.file(), "{{ ")?;
295 for (field_name, value) in fields {
296 write!(self.file(), "{}: ", make_field_name(field_name))?;
297 self.write_expr(value)?;
298 write!(self.file(), ", ")?;
299 }
300 write!(self.file(), "}}")?;
301 },
302 LangExpr::CreateEnum(_, _, _, field_name, value) => {
303 write!(self.file(), "{{ tag: \"{}\", {}: ", field_name, make_field_name(field_name))?;
304 self.write_expr(value)?;
305 write!(self.file(), "}}")?;
306 },
307 LangExpr::StructField(_, _, field_name, value) => {
308 self.write_expr(value)?;
309 write!(self.file(), ".{}", make_field_name(field_name))?;
310 },
311 }
312
313 Ok(())
314 }
315}
316
317impl GeneratorNameMapping for TypeScriptLanguage {
318 fn convert_prev_type_param(param: &str) -> String {
319 format!("{}_1", param)
320 }
321
322 fn convert_current_type_param(param: &str) -> String {
323 format!("{}_2", param)
324 }
325
326 fn convert_conv_param_name(param: &str) -> String {
327 format!("{}_conv", param)
328 }
329
330 fn convert_prev_param_name() -> &'static str {
331 "prev"
332 }
333
334 fn codec_write_value_name() -> &'static str {
335 "value"
336 }
337
338 fn codec_codec_param_name(param: &str) -> String {
339 format!("{}_codec", param)
340 }
341
342 fn constant_version_name(version: &BigUint) -> String {
343 format!("v{}", version)
344 }
345}
346
347fn current_dir_of_name<'model, Gen: TSGenerator<'model>>(gen: &Gen, name: &model::QualifiedName) -> Result<PathBuf, GeneratorError> {
348 let current_pkg_dir = gen.options().package_mapping.get(&name.package)
349 .or_else(|| gen.options().library_mapping.get(&name.package))
350 .ok_or_else(|| GeneratorError::UnmappedPackage(name.package.clone()))?;
351 let mut current_path = PathBuf::from(&gen.options().output_dir);
352 current_path.push(current_pkg_dir);
353 Ok(current_path)
354}
355
356
357
358struct TSConstGenerator<'model, 'opt, 'output, Output: OutputHandler<'output>> {
359 file: Output::FileHandle,
360 model: &'model model::Verilization,
361 options: &'opt TSOptions,
362 constant: Named<'model, model::Constant>,
363 scope: model::Scope<'model>,
364}
365
366impl <'model, 'opt, 'output, Output: OutputHandler<'output>> Generator<'model> for TSConstGenerator<'model, 'opt, 'output, Output> {
367 type Lang = TypeScriptLanguage;
368
369 fn model(&self) -> &'model model::Verilization {
370 self.model
371 }
372
373 fn scope(&self) -> &model::Scope<'model> {
374 &self.scope
375 }
376}
377
378impl <'model, 'opt, 'output, Output: OutputHandler<'output>> GeneratorWithFile for TSConstGenerator<'model, 'opt, 'output, Output> {
379 type GeneratorFile = Output::FileHandle;
380 fn file(&mut self) -> &mut Self::GeneratorFile {
381 &mut self.file
382 }
383}
384
385impl <'model, 'opt, 'output, Output: OutputHandler<'output>> TSGenerator<'model> for TSConstGenerator<'model, 'opt, 'output, Output> {
386 fn generator_element_name(&self) -> Option<&'model model::QualifiedName> {
387 Some(self.constant.name())
388 }
389
390 fn options(&self) -> &TSOptions {
391 self.options
392 }
393
394 fn referenced_types(&self) -> model::ReferencedTypeIterator<'model> {
395 self.constant.referenced_types()
396 }
397
398 fn current_dir(&self) -> Result<PathBuf, GeneratorError> {
399 current_dir_of_name(self, self.constant.name())
400 }
401
402 fn add_user_converter(&mut self, _name: String) {}
403}
404
405impl <'model, 'opt, 'output, Output: OutputHandler<'output>> ConstGenerator<'model> for TSConstGenerator<'model, 'opt, 'output, Output> {
406 fn constant(&self) -> Named<'model, model::Constant> {
407 self.constant
408 }
409
410 fn write_header(&mut self) -> Result<(), GeneratorError> {
411 self.write_imports()
412 }
413
414 fn write_constant(&mut self, version_name: String, t: LangType<'model>, value: LangExpr<'model>) -> Result<(), GeneratorError> {
415 write!(self.file(), "export const {}: ", version_name)?;
416 self.write_type(&t)?;
417 write!(self.file, " = ")?;
418 self.write_expr(&value)?;
419 writeln!(self.file, ";")?;
420
421 Ok(())
422 }
423
424 fn write_footer(&mut self) -> Result<(), GeneratorError> {
425 Ok(())
426 }
427}
428
429
430
431impl <'model, 'opt, 'output, Output: OutputHandler<'output>> TSConstGenerator<'model, 'opt, 'output, Output> {
432
433 fn open(model: &'model model::Verilization, options: &'opt TSOptions, output: &'output mut Output, constant: Named<'model, model::Constant>) -> Result<Self, GeneratorError> {
434 let file = open_ts_file(options, output, constant.name())?;
435 Ok(TSConstGenerator {
436 file: file,
437 model: model,
438 options: options,
439 constant: constant,
440 scope: constant.scope(),
441 })
442 }
443
444}
445
446struct TSTypeGenerator<'model, 'opt, 'output, Output: OutputHandler<'output>> {
447 file: Output::FileHandle,
448 model: &'model model::Verilization,
449 options: &'opt TSOptions,
450 type_def: Named<'model, model::VersionedTypeDefinitionData>,
451 scope: model::Scope<'model>,
452 versions: HashSet<BigUint>,
453 imported_user_converters: HashSet<String>,
454 unimported_user_converters: Vec<String>,
455 indentation_level: u32,
456}
457
458impl <'model, 'opt, 'output, Output: OutputHandler<'output>> Generator<'model> for TSTypeGenerator<'model, 'opt, 'output, Output> {
459 type Lang = TypeScriptLanguage;
460
461 fn model(&self) -> &'model model::Verilization {
462 self.model
463 }
464
465 fn scope(&self) -> &model::Scope<'model> {
466 &self.scope
467 }
468}
469
470impl <'model, 'opt, 'output, Output: OutputHandler<'output>> GeneratorWithFile for TSTypeGenerator<'model, 'opt, 'output, Output> {
471 type GeneratorFile = Output::FileHandle;
472 fn file(&mut self) -> &mut Self::GeneratorFile {
473 &mut self.file
474 }
475}
476
477impl <'model, 'opt, 'output, Output: OutputHandler<'output>> Indentation for TSTypeGenerator<'model, 'opt, 'output, Output> {
478 fn indentation_size(&mut self) -> &mut u32 {
479 &mut self.indentation_level
480 }
481}
482
483impl <'model, 'opt, 'output, Output: OutputHandler<'output>> TSGenerator<'model> for TSTypeGenerator<'model, 'opt, 'output, Output> {
484 fn generator_element_name(&self) -> Option<&'model model::QualifiedName> {
485 Some(self.type_def.name())
486 }
487
488 fn options(&self) -> &TSOptions {
489 self.options
490 }
491
492 fn referenced_types(&self) -> model::ReferencedTypeIterator<'model> {
493 self.type_def.referenced_types()
494 }
495
496 fn current_dir(&self) -> Result<PathBuf, GeneratorError> {
497 current_dir_of_name(self, self.type_def.name())
498 }
499
500 fn add_user_converter(&mut self, name: String) {
501 self.unimported_user_converters.push(name);
502 }
503}
504
505impl <'model, 'opt, 'output, Output: OutputHandler<'output>> VersionedTypeGenerator<'model> for TSTypeGenerator<'model, 'opt, 'output, Output> {
506 fn type_def(&self) -> Named<'model, model::VersionedTypeDefinitionData> {
507 self.type_def
508 }
509
510 fn write_header(&mut self) -> Result<(), GeneratorError> {
511 writeln!(self.file, "import {{Codec, FormatWriter, FormatReader, Converter, natCodec}} from \"@verilization/runtime\";")?;
512 self.write_imports()?;
513
514 Ok(())
515 }
516
517 fn write_version_header(&mut self, t: LangType<'model>) -> Result<(), GeneratorError> {
518 let version;
519
520 match t.clone() {
521 LangType::Versioned(VersionedTypeKind::Struct, _, ver, _, fields) => {
522 version = ver;
523 write!(self.file, "export interface V{}", version)?;
524 self.write_type_params(self.type_def().type_params())?;
525 writeln!(self.file, " {{")?;
526 self.indent_increase();
527 for field in fields.build()? {
528 self.write_indent()?;
529 write!(self.file, "readonly {}: ", make_field_name(field.name))?;
530 self.write_type(&field.field_type)?;
531 writeln!(self.file, ";")?;
532 }
533 self.indent_decrease();
534 writeln!(self.file, "}}")?;
535 },
536 LangType::Versioned(VersionedTypeKind::Enum, _, ver, _, fields) => {
537 version = ver;
538 write!(self.file, "export type V{}", version)?;
539 self.write_type_params(self.type_def().type_params())?;
540 write!(self.file, " = ")?;
541 self.indent_increase();
542 let mut is_first = true;
543 for field in fields.build()? {
544 if !is_first {
545 writeln!(self.file)?;
546 self.write_indent()?;
547 write!(self.file, "| ")?;
548 }
549 else {
550 is_first = false;
551 }
552 write!(self.file, "{{ readonly tag: \"{}\", readonly {}: ", field.name, make_field_name(field.name))?;
553 self.write_type(&field.field_type)?;
554 write!(self.file, ", }}")?;
555 }
556 if is_first {
557 write!(self.file, "never")?;
558 }
559 self.indent_decrease();
560
561 writeln!(self.file, ";")?;
562 },
563
564 _ => return Err(GeneratorError::CouldNotGenerateType)
565 }
566
567 self.versions.insert(version.clone());
568
569 writeln!(self.file, "export namespace V{} {{", version)?;
570 self.indent_increase();
571
572 Ok(())
573 }
574
575 fn write_operation(&mut self, operation: OperationInfo<'model>) -> Result<(), GeneratorError> {
576 let is_func = !operation.type_params.is_empty() || !operation.params.is_empty();
577
578 self.write_indent()?;
579 write!(self.file, "export ")?;
580
581 if is_func {
582 write!(self.file, "function ")?;
583 }
584 else {
585 write!(self.file, "const ")?;
586 }
587
588 self.write_operation_name(&operation.operation)?;
589
590 self.write_type_params(&operation.type_params)?;
591 if is_func {
592 write!(self.file, "(")?;
593 for_sep!((param_name, param), operation.params, { write!(self.file, ", ")?; }, {
594 write!(self.file, "{}: ", param_name)?;
595 self.write_type(¶m)?;
596 });
597 write!(self.file, ")")?;
598 }
599
600 write!(self.file, ": ")?;
601 self.write_type(&operation.result)?;
602
603 if is_func {
604 writeln!(self.file, " {{")?;
605 self.indent_increase();
606 }
607 else {
608 write!(self.file, " = ")?;
609 }
610
611 self.write_expr_statement(&operation.implementation, !is_func)?;
612
613 if is_func {
614 self.indent_decrease();
615 self.write_indent()?;
616 writeln!(self.file, "}}")?;
617 }
618
619 Ok(())
620 }
621
622 fn write_version_footer(&mut self) -> Result<(), GeneratorError> {
623 self.indent_decrease();
624
625
626 writeln!(self.file, "}}")?;
627
628
629 for user_conv in self.unimported_user_converters.drain(..) {
630 if self.imported_user_converters.insert(user_conv.clone()) {
631 writeln!(self.file, "import {{{}}} from \"./{}.conv.js\";", user_conv, self.type_def.name().name)?;
632 }
633 }
634
635 Ok(())
636 }
637
638 fn write_footer(&mut self) -> Result<(), GeneratorError> {
639
640 Ok(())
641 }
642
643}
644
645
646
647impl <'model, 'opt, 'output, Output: OutputHandler<'output>> TSTypeGenerator<'model, 'opt, 'output, Output> {
648
649 fn open(model: &'model model::Verilization, options: &'opt TSOptions, output: &'output mut Output, type_def: Named<'model, model::VersionedTypeDefinitionData>) -> Result<Self, GeneratorError> {
650 let file = open_ts_file(options, output, type_def.name())?;
651 Ok(TSTypeGenerator {
652 file: file,
653 model: model,
654 options: options,
655 type_def: type_def,
656 scope: type_def.scope(),
657 versions: HashSet::new(),
658 imported_user_converters: HashSet::new(),
659 unimported_user_converters: Vec::new(),
660 indentation_level: 0,
661 })
662 }
663
664 fn write_expr_statement(&mut self, stmt: &LangExprStmt<'model>, is_expr: bool) -> Result<(), GeneratorError> {
665 if !is_expr {
666 self.write_indent()?;
667 write!(self.file, "return ")?;
668 }
669
670 match stmt {
671 LangExprStmt::Expr(expr) => {
672 self.write_expr(expr)?;
673 writeln!(self.file, ";")?;
674 },
675
676 LangExprStmt::CreateCodec { t, read, write } => {
677 writeln!(self.file, "{{")?;
678 self.indent_increase();
679
680
681 self.write_indent()?;
682 write!(self.file, "async read(reader: FormatReader): Promise<")?;
683 self.write_type(t)?;
684 writeln!(self.file, "> {{")?;
685 self.indent_increase();
686 self.write_statement(read)?;
687 self.indent_decrease();
688 self.write_indent()?;
689 writeln!(self.file, "}},")?;
690
691 self.write_indent()?;
692 write!(self.file, "async write(writer: FormatWriter, {}: ", TypeScriptLanguage::codec_write_value_name())?;
693 self.write_type(t)?;
694 writeln!(self.file, "): Promise<void> {{")?;
695 self.indent_increase();
696 self.write_statement(write)?;
697 self.indent_decrease();
698 self.write_indent()?;
699 writeln!(self.file, "}},")?;
700
701
702 self.indent_decrease();
703 self.write_indent()?;
704 writeln!(self.file, "}};")?;
705 }
706
707 LangExprStmt::CreateConverter { from_type, to_type, body } => {
708 writeln!(self.file, "{{")?;
709 self.indent_increase();
710
711
712 self.write_indent()?;
713 write!(self.file, "convert({}: ", TypeScriptLanguage::convert_prev_param_name())?;
714 self.write_type(from_type)?;
715 write!(self.file, "): ")?;
716 self.write_type(to_type)?;
717 writeln!(self.file, " {{")?;
718 self.indent_increase();
719 self.write_statement(body)?;
720 self.indent_decrease();
721 self.write_indent()?;
722 writeln!(self.file, "}},")?;
723
724 self.indent_decrease();
725 self.write_indent()?;
726 writeln!(self.file, "}};")?;
727 }
728 }
729
730 Ok(())
731 }
732
733 fn write_statement(&mut self, stmt: &LangStmt<'model>) -> Result<(), GeneratorError> {
734 match stmt {
735 LangStmt::Expr(exprs, result_expr) => {
736 for expr in exprs {
737 self.write_indent()?;
738 self.write_expr(expr)?;
739 writeln!(self.file, ";")?;
740 }
741
742 if let Some(result_expr) = result_expr {
743 self.write_indent()?;
744 write!(self.file, "return ")?;
745 self.write_expr(result_expr)?;
746 writeln!(self.file, ";")?;
747 }
748 },
749
750 LangStmt::MatchEnum { value, value_type: _, cases } => {
751 self.write_indent()?;
752 write!(self.file, "switch(")?;
753 self.write_expr(value)?;
754 writeln!(self.file, ".tag) {{")?;
755
756 self.indent_increase();
757
758 for MatchCase { binding_name, case_name, body } in cases {
759 self.write_indent()?;
760 writeln!(self.file, "case \"{}\":", case_name)?;
761 self.write_indent()?;
762 writeln!(self.file, "{{")?;
763
764 self.indent_increase();
765
766 self.write_indent()?;
767 write!(self.file, "const {} = ", binding_name)?;
768 self.write_expr(value)?;
769 writeln!(self.file, ".{};", make_field_name(case_name))?;
770
771 self.write_statement(body)?;
772 if !body.has_value() {
773 self.write_indent()?;
774 writeln!(self.file, "break;")?;
775 }
776 self.indent_decrease();
777
778 self.write_indent()?;
779 writeln!(self.file, "}}")?;
780 }
781
782 if stmt.has_value() {
783 self.write_indent()?;
784 write!(self.file, "default: return ")?;
785 self.write_expr(value)?;
786 writeln!(self.file, ";")?;
787 }
788
789 self.indent_decrease();
790
791 self.write_indent()?;
792 writeln!(self.file, "}}")?;
793 },
794
795 LangStmt::MatchDiscriminator { value, cases } => {
796 self.write_indent()?;
797 write!(self.file, "switch(")?;
798 self.write_expr(value)?;
799 writeln!(self.file, ") {{")?;
800
801 self.indent_increase();
802
803 for (n, body) in cases {
804 self.write_indent()?;
805 writeln!(self.file, "case {}n:", n)?;
806 self.write_indent()?;
807 writeln!(self.file, "{{")?;
808
809 self.indent_increase();
810
811 self.write_statement(body)?;
812 if !body.has_value() {
813 self.write_indent()?;
814 writeln!(self.file, "break;")?;
815 }
816 self.indent_decrease();
817
818 self.write_indent()?;
819 writeln!(self.file, "}}")?;
820 }
821
822 self.write_indent()?;
823 write!(self.file, "default: throw new Error(\"Unknown tag\");")?;
824
825 self.indent_decrease();
826
827 self.write_indent()?;
828 writeln!(self.file, "}}")?;
829 },
830 }
831
832 Ok(())
833 }
834
835 fn write_type_params(&mut self, params: &Vec<String>) -> Result<(), GeneratorError> {
836 if !params.is_empty() {
837 write!(self.file, "<")?;
838 for_sep!(param, params, { write!(self.file, ", ")?; }, {
839 write!(self.file, "{}", param)?;
840 });
841 write!(self.file, ">")?;
842 }
843
844 Ok(())
845 }
846}
847
848
849pub struct TypeScriptLanguage {}
850
851impl Language for TypeScriptLanguage {
852 type OptionsBuilder = TSOptionsBuilder;
853 type Options = TSOptions;
854
855 fn name() -> &'static str {
856 "typescript"
857 }
858
859 fn empty_options() -> TSOptionsBuilder {
860 TSOptionsBuilder {
861 output_dir: None,
862 package_mapping: HashMap::new(),
863 library_mapping: HashMap::new(),
864 }
865 }
866
867 fn add_option(builder: &mut TSOptionsBuilder, name: &str, value: OsString) -> Result<(), GeneratorError> {
868 if name == "out_dir" {
869 if builder.output_dir.is_some() {
870 return Err(GeneratorError::InvalidOptions(String::from("Output directory already specified")))
871 }
872
873 builder.output_dir = Some(value);
874 Ok(())
875 }
876 else if let Some(pkg) = name.strip_prefix("pkg:") {
877 let package = model::PackageName::from_str(pkg);
878
879 if builder.library_mapping.contains_key(&package) || builder.package_mapping.insert(package, value).is_some() {
880 return Err(GeneratorError::InvalidOptions(format!("Package already mapped: {}", pkg)))
881 }
882 Ok(())
883 }
884 else if let Some(pkg) = name.strip_prefix("lib:") {
885 let package = model::PackageName::from_str(pkg);
886
887 if builder.package_mapping.contains_key(&package) || builder.library_mapping.insert(package, value).is_some() {
888 return Err(GeneratorError::InvalidOptions(format!("Package already mapped: {}", pkg)))
889 }
890 Ok(())
891 }
892 else {
893 Err(GeneratorError::InvalidOptions(format!("Unknown option: {}", name)))
894 }
895 }
896
897 fn finalize_options(builder: Self::OptionsBuilder) -> Result<Self::Options, GeneratorError> {
898 Ok(TSOptions {
899 output_dir: builder.output_dir.ok_or_else(|| GeneratorError::InvalidOptions(String::from("Output directory not specified")))?,
900 package_mapping: builder.package_mapping,
901 library_mapping: builder.library_mapping,
902 })
903 }
904
905 fn generate<Output: for<'output> OutputHandler<'output>>(model: &model::Verilization, options: Self::Options, output: &mut Output) -> Result<(), GeneratorError> {
906 for constant in model.constants() {
907 if options.library_mapping.contains_key(&constant.name().package) {
908 continue;
909 }
910
911 let mut const_gen = TSConstGenerator::open(model, &options, output, constant)?;
912 const_gen.generate()?;
913 }
914
915 for t in model.types() {
916 if options.library_mapping.contains_key(&t.name().package) {
917 continue;
918 }
919
920 match t {
921 model::NamedTypeDefinition::StructType(t) | model::NamedTypeDefinition::EnumType(t) => {
922 let mut type_gen = TSTypeGenerator::open(model, &options, output, t)?;
923 type_gen.generate()?;
924 },
925 model::NamedTypeDefinition::ExternType(_) => (),
926 }
927 }
928
929 Ok(())
930 }
931
932}