mproto-codegen 0.0.7

library to parse and generate code for mproto schemas
Documentation
use genco::prelude::*;

use crate::{
    ast::{Enum, EnumVariant},
    codegen::{
        enum_base_len, enum_variant_base_len,
        js::{
            common::{js_named_fields_decode, js_named_fields_encode, js_named_fields_scratch_len},
            encoder_common::EncoderCommon,
            js_type_tokens,
        },
        CodegenCx, MprotoJs,
    },
};

pub fn js_enum(cx: &CodegenCx, name: &str, type_params: &[String], e: &Enum) -> js::Tokens {
    let encode_cursor = &js::import("@modrpc-org/mproto", "EncodeCursor");
    let decode_cursor = &js::import("@modrpc-org/mproto", "DecodeCursor");
    let encode_interface = &js::import("@modrpc-org/mproto", "Encoder");
    let decode_interface = &js::import("@modrpc-org/mproto", "Decoder");

    let EncoderCommon {
        ref type_param_list,
        encoder_fields,
        encoder_constructor,
        encoder_instance,
        ..
    } = EncoderCommon::new(name, type_params);

    let ref full_type_name: js::Tokens = quote! { $(name)$(type_param_list) };

    let mut variants_scratch_len_tokens = js::Tokens::new();
    for (variant_name, variant) in &e.variants {
        variants_scratch_len_tokens = quote! {
            $variants_scratch_len_tokens
            if (value instanceof $(name).$(variant_name)) {
                $({
                    match variant {
                        EnumVariant::Empty => {
                            quote! {
                                return 0;
                            }
                        }
                        EnumVariant::NamedFields { fields } => {
                            quote! {
                                return $(js_named_fields_scratch_len(cx, fields))0;
                            }
                        }
                    }
                })
            }
        };
    }

    let mut variants_encode_tokens = js::Tokens::new();
    for (i, (variant_name, variant)) in e.variants.iter().enumerate() {
        let variant_base_len = enum_variant_base_len::<MprotoJs>(cx, variant).as_tokens();
        variants_encode_tokens = quote! {
            $variants_encode_tokens
            if (value instanceof $(name).$(variant_name)) {
                $({
                    match variant {
                        EnumVariant::Empty => {
                            quote! {
                                cursor.buffer.setUint8(cursor.base(1), $i);
                                cursor.base(this.baseLength() - 1);
                            }
                        }
                        EnumVariant::NamedFields { fields } => {
                            quote! {
                                cursor.buffer.setUint8(cursor.base(1), $i);
                                cursor.base(this.baseLength() - 1 - $variant_base_len);
                                $(js_named_fields_encode(cx, fields))
                            }
                        }
                    }
                })
            }
        };
    }

    let mut variants_decode_tokens: js::Tokens = quote! {
        let variant = cursor.buffer.getUint8(cursor.base(1));
    };
    for (i, (variant_name, variant)) in e.variants.iter().enumerate() {
        let variant_base_len = enum_variant_base_len::<MprotoJs>(cx, variant).as_tokens();
        variants_decode_tokens = quote! {
            $variants_decode_tokens
            if (variant == $i) {
                $({
                    match variant {
                        EnumVariant::Empty => {
                            quote! {
                                cursor.base(this.baseLength() - 1);
                                return new $name.$variant_name();
                            }
                        }
                        EnumVariant::NamedFields { fields } => {
                            let decode_fields = js_named_fields_decode(cx, fields);

                            let mut constructor_fields = js::Tokens::new();
                            quote_in! { constructor_fields => $(&fields[0].name) };
                            for field in fields[1..].iter() {
                                quote_in! { constructor_fields => , $(&field.name) };
                            }

                            quote! {
                                $decode_fields
                                cursor.base(this.baseLength() - 1 - $variant_base_len);
                                return new $name.$variant_name($constructor_fields);
                            }
                        }
                    }
                })
            }
        };
    }

    let tokens: js::Tokens = quote! {
        export namespace $name {
            $({
                let mut variant_tokens = quote! { };

                for (variant_name, variant) in &e.variants {
                    let mut variant_fields = quote! { };
                    let mut variant_constr_params = quote! { };
                    let mut variant_constr_set_fields = quote! { };

                    match variant {
                        EnumVariant::Empty => { }
                        EnumVariant::NamedFields { fields } => {
                            for field in fields {
                                variant_fields = quote! {
                                    $variant_fields
                                    public $(&field.name): $(js_type_tokens(cx, &field.ty));
                                };
                                variant_constr_params = quote! {
                                    $variant_constr_params
                                    $(&field.name): $(js_type_tokens(cx, &field.ty)),
                                };
                                variant_constr_set_fields = quote! {
                                    $variant_constr_set_fields
                                    this.$(&field.name) = $(&field.name);
                                };
                            }
                        }
                    }

                    variant_tokens = quote! {
                        $variant_tokens
                        export class $(variant_name)$(type_param_list) {
                            $variant_fields

                            constructor(
                                $variant_constr_params
                            ) {
                                $variant_constr_set_fields
                            }

                            toString(): string { return $("`")$variant_name ${JSON.stringify(this)}$("`"); }
                        }
                    };
                }

                variant_tokens
            });
        }

        export type $full_type_name = $({
            let mut variant_tokens = quote! {
                $name.$(&e.variants[0].0)$(type_param_list)
            };

            for (variant_name, _) in &e.variants[1..] {
                variant_tokens = quote! {
                    $variant_tokens
                    | $name.$(variant_name)$(type_param_list)
                };
            }

            variant_tokens
        });

        export class $(name)Encoder$(type_param_list) implements $encode_interface<$full_type_name>, $decode_interface<$full_type_name> {
            $encoder_fields

            $encoder_constructor

            baseLength = () => $(enum_base_len::<MprotoJs>(cx, e).as_tokens());

            scratchLength(value: $full_type_name): number {
                $variants_scratch_len_tokens
            }

            encode(cursor: $encode_cursor, value: $full_type_name) {
                $variants_encode_tokens
            }

            decode(cursor: $decode_cursor): $full_type_name {
                $variants_decode_tokens
            }
        }

        $encoder_instance
    };

    tokens
}