1use vexil_lang::ast::{PrimitiveType, SemanticType};
2use vexil_lang::ir::{
3 ConfigDef, Encoding, FieldEncoding, MessageDef, ResolvedType, TombstoneDef, TypeDef,
4 TypeRegistry,
5};
6
7use crate::emit::CodeWriter;
8use crate::types::ts_type;
9
10pub fn is_byte_aligned(ty: &ResolvedType, registry: &TypeRegistry) -> bool {
16 match ty {
17 ResolvedType::Primitive(PrimitiveType::Bool) => false,
18 ResolvedType::SubByte(_) => false,
19 ResolvedType::Named(id) => {
20 if let Some(TypeDef::Enum(e)) = registry.get(*id) {
21 e.wire_bits >= 8
22 } else {
23 true
24 }
25 }
26 ResolvedType::Optional(inner) => is_byte_aligned(inner, registry),
27 _ => true,
28 }
29}
30
31fn primitive_bits(p: &PrimitiveType) -> u8 {
36 match p {
37 PrimitiveType::I8 | PrimitiveType::U8 => 8,
38 PrimitiveType::I16 | PrimitiveType::U16 => 16,
39 PrimitiveType::I32 | PrimitiveType::U32 | PrimitiveType::F32 => 32,
40 PrimitiveType::I64 | PrimitiveType::U64 | PrimitiveType::F64 => 64,
41 _ => 0,
42 }
43}
44
45pub fn emit_write(
54 w: &mut CodeWriter,
55 access: &str,
56 ty: &ResolvedType,
57 enc: &FieldEncoding,
58 registry: &TypeRegistry,
59 writer: &str,
60) {
61 match &enc.encoding {
63 Encoding::Varint => {
64 let is_64 = matches!(
65 ty,
66 ResolvedType::Primitive(PrimitiveType::U64 | PrimitiveType::I64)
67 );
68 if is_64 {
69 w.line(&format!("{writer}.writeLeb12864({access});"));
70 } else {
71 w.line(&format!("{writer}.writeLeb128({access});"));
72 }
73 return;
74 }
75 Encoding::ZigZag => {
76 let type_bits = match ty {
77 ResolvedType::Primitive(p) => primitive_bits(p),
78 _ => 64,
79 };
80 if type_bits == 64 {
81 w.line(&format!("{writer}.writeZigZag64({access});"));
82 } else {
83 w.line(&format!("{writer}.writeZigZag({access}, {type_bits});",));
84 }
85 return;
86 }
87 Encoding::Delta(inner) => {
88 let base_enc = FieldEncoding {
89 encoding: *inner.clone(),
90 limit: enc.limit,
91 };
92 emit_write(w, access, ty, &base_enc, registry, writer);
93 return;
94 }
95 Encoding::Default => {} _ => {} }
98
99 emit_write_type(w, access, ty, registry, writer);
100}
101
102fn emit_write_type(
103 w: &mut CodeWriter,
104 access: &str,
105 ty: &ResolvedType,
106 registry: &TypeRegistry,
107 writer: &str,
108) {
109 match ty {
110 ResolvedType::Primitive(p) => match p {
111 PrimitiveType::Bool => w.line(&format!("{writer}.writeBool({access});")),
112 PrimitiveType::U8 => w.line(&format!("{writer}.writeU8({access});")),
113 PrimitiveType::U16 => w.line(&format!("{writer}.writeU16({access});")),
114 PrimitiveType::U32 => w.line(&format!("{writer}.writeU32({access});")),
115 PrimitiveType::U64 => w.line(&format!("{writer}.writeU64({access});")),
116 PrimitiveType::I8 => w.line(&format!("{writer}.writeI8({access});")),
117 PrimitiveType::I16 => w.line(&format!("{writer}.writeI16({access});")),
118 PrimitiveType::I32 => w.line(&format!("{writer}.writeI32({access});")),
119 PrimitiveType::I64 => w.line(&format!("{writer}.writeI64({access});")),
120 PrimitiveType::F32 => w.line(&format!("{writer}.writeF32({access});")),
121 PrimitiveType::F64 => w.line(&format!("{writer}.writeF64({access});")),
122 PrimitiveType::Void => {} },
124 ResolvedType::SubByte(s) => {
125 let bits = s.bits;
126 w.line(&format!("{writer}.writeBits({access}, {bits});"));
127 }
128 ResolvedType::Semantic(s) => match s {
129 SemanticType::String => w.line(&format!("{writer}.writeString({access});")),
130 SemanticType::Bytes => w.line(&format!("{writer}.writeBytes({access});")),
131 SemanticType::Rgb => {
132 w.line(&format!("{writer}.writeU8({access}[0]);"));
133 w.line(&format!("{writer}.writeU8({access}[1]);"));
134 w.line(&format!("{writer}.writeU8({access}[2]);"));
135 }
136 SemanticType::Uuid => w.line(&format!("{writer}.writeRawBytes({access});")),
137 SemanticType::Timestamp => w.line(&format!("{writer}.writeI64({access});")),
138 SemanticType::Hash => w.line(&format!("{writer}.writeRawBytes({access});")),
139 },
140 ResolvedType::Named(id) => {
141 let type_name = match registry.get(*id) {
142 Some(def) => match def {
143 TypeDef::Message(m) => m.name.to_string(),
144 TypeDef::Enum(e) => e.name.to_string(),
145 TypeDef::Flags(f) => f.name.to_string(),
146 TypeDef::Union(u) => u.name.to_string(),
147 TypeDef::Newtype(n) => n.name.to_string(),
148 _ => "Unknown".to_string(),
149 },
150 None => "Unknown".to_string(),
151 };
152 w.line(&format!("{writer}.enterNested();"));
153 w.line(&format!("encode{type_name}({access}, {writer});"));
154 w.line(&format!("{writer}.leaveNested();"));
155 }
156 ResolvedType::Optional(inner) => {
157 w.line(&format!("{writer}.writeBool({access} !== null);"));
159 if is_byte_aligned(inner, registry) {
160 w.line(&format!("{writer}.flushToByteBoundary();"));
161 }
162 w.open_block(&format!("if ({access} !== null)"));
163 emit_write_type(w, access, inner, registry, writer);
164 w.close_block();
165 }
166 ResolvedType::Array(inner) => {
167 w.line(&format!("{writer}.writeLeb128({access}.length);"));
168 w.open_block(&format!("for (const item of {access})"));
169 emit_write_type(w, "item", inner, registry, writer);
170 w.close_block();
171 }
172 ResolvedType::Map(k, v) => {
173 w.line(&format!("{writer}.writeLeb128({access}.size);"));
174 w.open_block(&format!("for (const [mapK, mapV] of {access})"));
175 emit_write_type(w, "mapK", k, registry, writer);
176 emit_write_type(w, "mapV", v, registry, writer);
177 w.close_block();
178 }
179 ResolvedType::Result(ok, err) => {
180 w.open_block(&format!("if ('ok' in {access})"));
181 w.line(&format!("{writer}.writeBool(true);"));
182 emit_write_type(w, &format!("{access}.ok"), ok, registry, writer);
183 w.dedent();
184 w.line("} else {");
185 w.indent();
186 w.line(&format!("{writer}.writeBool(false);"));
187 emit_write_type(w, &format!("{access}.err"), err, registry, writer);
188 w.close_block();
189 }
190 _ => {} }
192}
193
194pub fn emit_read(
202 w: &mut CodeWriter,
203 var_name: &str,
204 ty: &ResolvedType,
205 enc: &FieldEncoding,
206 registry: &TypeRegistry,
207 reader: &str,
208) {
209 match &enc.encoding {
210 Encoding::Varint => {
211 let is_64 = matches!(
212 ty,
213 ResolvedType::Primitive(PrimitiveType::U64 | PrimitiveType::I64)
214 );
215 if is_64 {
216 w.line(&format!("const {var_name} = {reader}.readLeb12864();"));
217 } else {
218 w.line(&format!("const {var_name} = {reader}.readLeb128();"));
219 }
220 return;
221 }
222 Encoding::ZigZag => {
223 let type_bits = match ty {
224 ResolvedType::Primitive(p) => primitive_bits(p),
225 _ => 64,
226 };
227 if type_bits == 64 {
228 w.line(&format!("const {var_name} = {reader}.readZigZag64();",));
229 } else {
230 w.line(&format!(
231 "const {var_name} = {reader}.readZigZag({type_bits});",
232 ));
233 }
234 return;
235 }
236 Encoding::Delta(inner) => {
237 let base_enc = FieldEncoding {
238 encoding: *inner.clone(),
239 limit: enc.limit,
240 };
241 emit_read(w, var_name, ty, &base_enc, registry, reader);
242 return;
243 }
244 Encoding::Default => {}
245 _ => {} }
247
248 emit_read_type(w, var_name, ty, registry, reader);
249}
250
251fn emit_read_type(
252 w: &mut CodeWriter,
253 var_name: &str,
254 ty: &ResolvedType,
255 registry: &TypeRegistry,
256 reader: &str,
257) {
258 match ty {
259 ResolvedType::Primitive(p) => match p {
260 PrimitiveType::Bool => {
261 w.line(&format!("const {var_name} = {reader}.readBool();"));
262 }
263 PrimitiveType::U8 => {
264 w.line(&format!("const {var_name} = {reader}.readU8();"));
265 }
266 PrimitiveType::U16 => {
267 w.line(&format!("const {var_name} = {reader}.readU16();"));
268 }
269 PrimitiveType::U32 => {
270 w.line(&format!("const {var_name} = {reader}.readU32();"));
271 }
272 PrimitiveType::U64 => {
273 w.line(&format!("const {var_name} = {reader}.readU64();"));
274 }
275 PrimitiveType::I8 => {
276 w.line(&format!("const {var_name} = {reader}.readI8();"));
277 }
278 PrimitiveType::I16 => {
279 w.line(&format!("const {var_name} = {reader}.readI16();"));
280 }
281 PrimitiveType::I32 => {
282 w.line(&format!("const {var_name} = {reader}.readI32();"));
283 }
284 PrimitiveType::I64 => {
285 w.line(&format!("const {var_name} = {reader}.readI64();"));
286 }
287 PrimitiveType::F32 => {
288 w.line(&format!("const {var_name} = {reader}.readF32();"));
289 }
290 PrimitiveType::F64 => {
291 w.line(&format!("const {var_name} = {reader}.readF64();"));
292 }
293 PrimitiveType::Void => {
294 w.line(&format!("const {var_name} = undefined;"));
295 }
296 },
297 ResolvedType::SubByte(s) => {
298 let bits = s.bits;
299 if s.signed {
300 let shift = 8 - bits;
302 w.line(&format!(
303 "const {var_name} = ({reader}.readBits({bits}) << {shift}) >> {shift};",
304 ));
305 } else {
306 w.line(&format!("const {var_name} = {reader}.readBits({bits});",));
307 }
308 }
309 ResolvedType::Semantic(s) => match s {
310 SemanticType::String => {
311 w.line(&format!("const {var_name} = {reader}.readString();"));
312 }
313 SemanticType::Bytes => {
314 w.line(&format!("const {var_name} = {reader}.readBytes();"));
315 }
316 SemanticType::Rgb => {
317 w.line(&format!("const {var_name}_0 = {reader}.readU8();"));
318 w.line(&format!("const {var_name}_1 = {reader}.readU8();"));
319 w.line(&format!("const {var_name}_2 = {reader}.readU8();"));
320 w.line(&format!(
321 "const {var_name}: [number, number, number] = [{var_name}_0, {var_name}_1, {var_name}_2];"
322 ));
323 }
324 SemanticType::Uuid => {
325 w.line(&format!("const {var_name} = {reader}.readRawBytes(16);"));
326 }
327 SemanticType::Timestamp => {
328 w.line(&format!("const {var_name} = {reader}.readI64();"));
329 }
330 SemanticType::Hash => {
331 w.line(&format!("const {var_name} = {reader}.readRawBytes(32);"));
332 }
333 },
334 ResolvedType::Named(id) => {
335 let type_name = match registry.get(*id) {
336 Some(def) => match def {
337 TypeDef::Message(m) => m.name.to_string(),
338 TypeDef::Enum(e) => e.name.to_string(),
339 TypeDef::Flags(f) => f.name.to_string(),
340 TypeDef::Union(u) => u.name.to_string(),
341 TypeDef::Newtype(n) => n.name.to_string(),
342 _ => "Unknown".to_string(),
343 },
344 None => "Unknown".to_string(),
345 };
346 w.line(&format!("{reader}.enterNested();"));
347 w.line(&format!("const {var_name} = decode{type_name}({reader});"));
348 w.line(&format!("{reader}.leaveNested();"));
349 }
350 ResolvedType::Optional(inner) => {
351 w.line(&format!("const {var_name}_present = {reader}.readBool();"));
352 if is_byte_aligned(inner, registry) {
353 w.line(&format!("{reader}.flushToByteBoundary();"));
354 }
355 let inner_ts = ts_type(inner, registry);
356 w.line(&format!("let {var_name}: {inner_ts} | null;",));
357 w.open_block(&format!("if ({var_name}_present)"));
358 emit_read_type(w, &format!("{var_name}_inner"), inner, registry, reader);
359 w.line(&format!("{var_name} = {var_name}_inner;"));
360 w.dedent();
361 w.line("} else {");
362 w.indent();
363 w.line(&format!("{var_name} = null;"));
364 w.close_block();
365 }
366 ResolvedType::Array(inner) => {
367 w.line(&format!("const {var_name}_len = {reader}.readLeb128();"));
368 let inner_ts = ts_type(inner, registry);
369 w.line(&format!("const {var_name}: {inner_ts}[] = [];"));
370 w.open_block(&format!("for (let i = 0; i < {var_name}_len; i++)"));
371 emit_read_type(w, &format!("{var_name}_item"), inner, registry, reader);
372 w.line(&format!("{var_name}.push({var_name}_item);"));
373 w.close_block();
374 }
375 ResolvedType::Map(k, v) => {
376 w.line(&format!("const {var_name}_len = {reader}.readLeb128();"));
377 let k_ts = ts_type(k, registry);
378 let v_ts = ts_type(v, registry);
379 w.line(&format!("const {var_name} = new Map<{k_ts}, {v_ts}>();"));
380 w.open_block(&format!("for (let i = 0; i < {var_name}_len; i++)"));
381 emit_read_type(w, &format!("{var_name}_k"), k, registry, reader);
382 emit_read_type(w, &format!("{var_name}_v"), v, registry, reader);
383 w.line(&format!("{var_name}.set({var_name}_k, {var_name}_v);"));
384 w.close_block();
385 }
386 ResolvedType::Result(ok, err) => {
387 let ok_ts = ts_type(ok, registry);
388 let err_ts = ts_type(err, registry);
389 w.line(&format!("const {var_name}_isOk = {reader}.readBool();"));
390 w.line(&format!(
391 "let {var_name}: {{ ok: {ok_ts} }} | {{ err: {err_ts} }};"
392 ));
393 w.open_block(&format!("if ({var_name}_isOk)"));
394 emit_read_type(w, &format!("{var_name}_ok"), ok, registry, reader);
395 w.line(&format!("{var_name} = {{ ok: {var_name}_ok }};"));
396 w.dedent();
397 w.line("} else {");
398 w.indent();
399 emit_read_type(w, &format!("{var_name}_err"), err, registry, reader);
400 w.line(&format!("{var_name} = {{ err: {var_name}_err }};"));
401 w.close_block();
402 }
403 _ => {} }
405}
406
407fn emit_tombstone_read(
413 w: &mut CodeWriter,
414 ty: &ResolvedType,
415 registry: &TypeRegistry,
416 reader: &str,
417 idx: usize,
418) {
419 match ty {
420 ResolvedType::Primitive(p) => match p {
421 PrimitiveType::Bool => w.line(&format!("{reader}.readBool();")),
422 PrimitiveType::U8 => w.line(&format!("{reader}.readU8();")),
423 PrimitiveType::U16 => w.line(&format!("{reader}.readU16();")),
424 PrimitiveType::U32 => w.line(&format!("{reader}.readU32();")),
425 PrimitiveType::U64 => w.line(&format!("{reader}.readU64();")),
426 PrimitiveType::I8 => w.line(&format!("{reader}.readI8();")),
427 PrimitiveType::I16 => w.line(&format!("{reader}.readI16();")),
428 PrimitiveType::I32 => w.line(&format!("{reader}.readI32();")),
429 PrimitiveType::I64 => w.line(&format!("{reader}.readI64();")),
430 PrimitiveType::F32 => w.line(&format!("{reader}.readF32();")),
431 PrimitiveType::F64 => w.line(&format!("{reader}.readF64();")),
432 PrimitiveType::Void => {} },
434 ResolvedType::SubByte(s) => {
435 w.line(&format!("{reader}.readBits({});", s.bits));
436 }
437 ResolvedType::Semantic(s) => match s {
438 SemanticType::String => w.line(&format!("{reader}.readString();")),
439 SemanticType::Bytes => w.line(&format!("{reader}.readBytes();")),
440 SemanticType::Rgb => {
441 w.line(&format!("{reader}.readU8();"));
442 w.line(&format!("{reader}.readU8();"));
443 w.line(&format!("{reader}.readU8();"));
444 }
445 SemanticType::Uuid => w.line(&format!("{reader}.readRawBytes(16);")),
446 SemanticType::Timestamp => w.line(&format!("{reader}.readI64();")),
447 SemanticType::Hash => w.line(&format!("{reader}.readRawBytes(32);")),
448 },
449 ResolvedType::Named(id) => {
450 let type_name = match registry.get(*id) {
451 Some(def) => match def {
452 TypeDef::Message(m) => m.name.to_string(),
453 TypeDef::Enum(e) => e.name.to_string(),
454 TypeDef::Flags(f) => f.name.to_string(),
455 TypeDef::Union(u) => u.name.to_string(),
456 TypeDef::Newtype(n) => n.name.to_string(),
457 _ => "Unknown".to_string(),
458 },
459 None => "Unknown".to_string(),
460 };
461 w.line(&format!("{reader}.enterNested();"));
462 w.line(&format!("decode{type_name}({reader});"));
463 w.line(&format!("{reader}.leaveNested();"));
464 }
465 ResolvedType::Optional(inner) => {
466 let var = format!("_tombstone_{idx}_present");
467 w.line(&format!("const {var} = {reader}.readBool();"));
468 if is_byte_aligned(inner, registry) {
469 w.line(&format!("{reader}.flushToByteBoundary();"));
470 }
471 w.open_block(&format!("if ({var})"));
472 emit_tombstone_read(w, inner, registry, reader, idx);
473 w.close_block();
474 }
475 ResolvedType::Array(inner) => {
476 let len_var = format!("_tombstone_{idx}_len");
477 w.line(&format!("const {len_var} = {reader}.readLeb128();"));
478 w.open_block(&format!("for (let i = 0; i < {len_var}; i++)"));
479 emit_tombstone_read(w, inner, registry, reader, idx);
480 w.close_block();
481 }
482 ResolvedType::Map(k, v) => {
483 let len_var = format!("_tombstone_{idx}_len");
484 w.line(&format!("const {len_var} = {reader}.readLeb128();"));
485 w.open_block(&format!("for (let i = 0; i < {len_var}; i++)"));
486 emit_tombstone_read(w, k, registry, reader, idx);
487 emit_tombstone_read(w, v, registry, reader, idx);
488 w.close_block();
489 }
490 ResolvedType::Result(ok, err) => {
491 let var = format!("_tombstone_{idx}_isOk");
492 w.line(&format!("const {var} = {reader}.readBool();"));
493 w.open_block(&format!("if ({var})"));
494 emit_tombstone_read(w, ok, registry, reader, idx);
495 w.dedent();
496 w.line("} else {");
497 w.indent();
498 emit_tombstone_read(w, err, registry, reader, idx);
499 w.close_block();
500 }
501 _ => {} }
503}
504
505pub fn emit_message(w: &mut CodeWriter, msg: &MessageDef, registry: &TypeRegistry) {
511 let name = msg.name.as_str();
512
513 w.open_block(&format!("export interface {name}"));
515 for field in &msg.fields {
516 let field_ts = ts_type(&field.resolved_type, registry);
517 w.line(&format!("{}: {};", field.name, field_ts));
518 }
519 w.line("_unknown: Uint8Array;");
520 w.close_block();
521 w.blank();
522
523 w.open_block(&format!(
525 "export function encode{name}(v: {name}, w: BitWriter): void"
526 ));
527 for field in &msg.fields {
528 let access = format!("v.{}", field.name);
529 emit_write(
530 w,
531 &access,
532 &field.resolved_type,
533 &field.encoding,
534 registry,
535 "w",
536 );
537 }
538 w.line("w.flushToByteBoundary();");
539 w.open_block("if (v._unknown.length > 0)");
540 w.line("w.writeRawBytes(v._unknown);");
541 w.close_block();
542 w.close_block();
543 w.blank();
544
545 w.open_block(&format!(
547 "export function decode{name}(r: BitReader): {name}"
548 ));
549
550 enum DecodeAction<'a> {
553 Field(&'a vexil_lang::ir::FieldDef),
554 Tombstone(&'a TombstoneDef),
555 }
556 let mut actions: Vec<(u32, DecodeAction<'_>)> = Vec::new();
557 for field in &msg.fields {
558 actions.push((field.ordinal, DecodeAction::Field(field)));
559 }
560 for tombstone in &msg.tombstones {
561 if tombstone.original_type.is_some() {
562 actions.push((tombstone.ordinal, DecodeAction::Tombstone(tombstone)));
563 }
564 }
565 actions.sort_by_key(|(ord, _)| *ord);
566
567 for (idx, (_ord, action)) in actions.iter().enumerate() {
568 match action {
569 DecodeAction::Field(field) => {
570 emit_read(
571 w,
572 field.name.as_str(),
573 &field.resolved_type,
574 &field.encoding,
575 registry,
576 "r",
577 );
578 }
579 DecodeAction::Tombstone(tombstone) => {
580 if let Some(ref ty) = tombstone.original_type {
581 w.line(&format!(
582 "// discard @removed ordinal {}",
583 tombstone.ordinal
584 ));
585 emit_tombstone_read(w, ty, registry, "r", idx);
586 }
587 }
588 }
589 }
590 w.line("r.flushToByteBoundary();");
591 w.line("const _unknown = r.readRemaining();");
592 let field_names: Vec<&str> = msg.fields.iter().map(|f| f.name.as_str()).collect();
593 let mut all_names = field_names;
594 all_names.push("_unknown");
595 w.line(&format!("return {{ {} }};", all_names.join(", ")));
596 w.close_block();
597 w.blank();
598}
599
600pub fn emit_config(w: &mut CodeWriter, cfg: &ConfigDef, registry: &TypeRegistry) {
606 let name = cfg.name.as_str();
607
608 w.open_block(&format!("export interface {name}"));
609 for field in &cfg.fields {
610 let field_ts = ts_type(&field.resolved_type, registry);
611 w.line(&format!("{}: {};", field.name, field_ts));
612 }
613 w.close_block();
614 w.blank();
615}