1pub mod backend;
7pub mod delta;
8pub mod emit;
9pub mod enum_gen;
10pub mod flags;
11pub mod message;
12pub mod newtype;
13pub mod types;
14pub mod union_gen;
15
16pub use backend::TypeScriptBackend;
17
18use vexil_lang::ir::{CompiledSchema, TypeDef};
19
20#[derive(Debug, Clone, PartialEq, thiserror::Error)]
21pub enum CodegenError {
22 #[error("unresolved type {type_id:?} referenced by {referenced_by}")]
23 UnresolvedType {
24 type_id: vexil_lang::ir::TypeId,
25 referenced_by: String,
26 },
27}
28
29pub fn generate(compiled: &CompiledSchema) -> Result<String, CodegenError> {
31 generate_with_imports(compiled, None)
32}
33
34pub(crate) fn generate_with_imports(
38 compiled: &CompiledSchema,
39 import_paths: Option<&std::collections::HashMap<String, String>>,
40) -> Result<String, CodegenError> {
41 let mut w = emit::CodeWriter::new();
42
43 w.line("// Code generated by vexilc. DO NOT EDIT.");
45 let ns = compiled.namespace.join(".");
46 w.line(&format!("// Source: {ns}"));
47 w.blank();
48
49 w.line("import { BitReader, BitWriter } from '@vexil-lang/runtime';");
51
52 if let Some(paths) = import_paths {
54 let mut imports: Vec<(&String, &String)> = paths.iter().collect();
55 imports.sort_by_key(|(name, _)| (*name).clone());
56 for (type_name, module_path) in imports {
57 w.line(&format!(
58 "import {{ {type_name}, encode{type_name}, decode{type_name} }} from '{module_path}';",
59 ));
60 }
61 }
62 w.blank();
63
64 let hash = vexil_lang::canonical::schema_hash(compiled);
66 let hash_str = hash
67 .iter()
68 .map(|b| format!("0x{b:02x}"))
69 .collect::<Vec<_>>()
70 .join(", ");
71 w.line(&format!(
72 "export const SCHEMA_HASH = new Uint8Array([{hash_str}]);"
73 ));
74
75 if let Some(ref version) = compiled.annotations.version {
77 w.line(&format!("export const SCHEMA_VERSION = '{version}';"));
78 }
79
80 for &type_id in &compiled.declarations {
82 let typedef =
83 compiled
84 .registry
85 .get(type_id)
86 .ok_or_else(|| CodegenError::UnresolvedType {
87 type_id,
88 referenced_by: "declarations".to_string(),
89 })?;
90
91 w.blank();
92 let type_name = type_name_of(typedef);
93 w.line(&format!("// \u{2500}\u{2500} {type_name} \u{2500}\u{2500}"));
94
95 match typedef {
96 TypeDef::Message(msg) => {
97 message::emit_message(&mut w, msg, &compiled.registry);
98 delta::emit_delta(&mut w, msg, &compiled.registry);
99 }
100 TypeDef::Enum(en) => {
101 enum_gen::emit_enum(&mut w, en, &compiled.registry);
102 }
103 TypeDef::Flags(fl) => {
104 flags::emit_flags(&mut w, fl, &compiled.registry);
105 }
106 TypeDef::Union(un) => {
107 union_gen::emit_union(&mut w, un, &compiled.registry);
108 }
109 TypeDef::Newtype(nt) => {
110 newtype::emit_newtype(&mut w, nt, &compiled.registry);
111 }
112 TypeDef::Config(cfg) => {
113 message::emit_config(&mut w, cfg, &compiled.registry);
114 }
115 _ => {} }
117 }
118
119 Ok(w.finish())
120}
121
122pub(crate) fn type_name_of(typedef: &TypeDef) -> &str {
124 match typedef {
125 TypeDef::Message(m) => m.name.as_str(),
126 TypeDef::Enum(e) => e.name.as_str(),
127 TypeDef::Flags(f) => f.name.as_str(),
128 TypeDef::Union(u) => u.name.as_str(),
129 TypeDef::Newtype(n) => n.name.as_str(),
130 TypeDef::Config(c) => c.name.as_str(),
131 _ => "Unknown",
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn generate_produces_output() {
141 let result = vexil_lang::compile("namespace test.gen\nmessage Foo { x @0 : u32 }");
142 let compiled = result.compiled.unwrap();
143 let code = generate(&compiled).unwrap();
144 assert!(code.contains("export interface Foo"));
145 assert!(code.contains("export function encodeFoo"));
146 assert!(code.contains("export function decodeFoo"));
147 assert!(code.contains("import { BitReader, BitWriter } from '@vexil-lang/runtime'"));
148 }
149
150 #[test]
151 fn generate_with_no_imports_matches_generate() {
152 let result = vexil_lang::compile("namespace test.gen\nmessage Foo { x @0 : u32 }");
153 let compiled = result.compiled.unwrap();
154 let without = generate(&compiled).unwrap();
155 let with = generate_with_imports(&compiled, None).unwrap();
156 assert_eq!(without, with);
157 }
158
159 #[test]
160 fn ts_backend_generate_matches_free_function() {
161 use vexil_lang::codegen::CodegenBackend;
162 let result = vexil_lang::compile("namespace test.backend\nmessage Foo { x @0 : u32 }");
163 let compiled = result.compiled.unwrap();
164 let free_fn = generate(&compiled).unwrap();
165 let backend = crate::backend::TypeScriptBackend;
166 let trait_fn = backend.generate(&compiled).unwrap();
167 assert_eq!(free_fn, trait_fn);
168 }
169}