verilization_lang_typescript/
lib.rs

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, &current_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				// Only use a qualifier if not a value of the current type.
149				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						// Only use a qualifier if not a value of the current type.
249						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				// Only use a qualifier if not a value of the current type.
286				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(&param)?;
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}