xdr-codegen 0.5.1

XDR code generation
use super::*;
use handlebars::Handlebars;
use std::collections::HashMap;

static HEADER: &str = r#"
"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});

var _xdrJsSerialize = _interopRequireDefault(require("xdr-js-serialize"));
function _interopRequireDefault(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}

{{#each this as |ns| ~}}
// Namespace start {{ns.name}}
"#;

static TYPEDEFS_T: &str = r#"
// Start typedef section
{{#each ns.typedefs as |td|}}
exports.{{td.def.name}} = {{td.def.name}};
function {{td.def.name}}() {
    return {{#typeconv td.def.name td.def.type_name td.def.array_size td.def.fixed_array}}{{/typeconv}};
}
{{/each~}}
// End typedef section
"#;

static STRUCTS_T: &str = r#"
// Start struct section
{{#each ns.structs as |st| ~}}
exports.{{st.name}} = {{st.name}};
function {{st.name}}() {
    return new _xdrJsSerialize.default.Struct(
        [{{#each st.props as |prop| ~}}"{{prop.name}}",{{/each ~}}],
        [{{#each st.props as |prop| ~}}{{#typeconv prop.name prop.type_name prop.array_size prop.fixed_array}}{{/typeconv}},{{/each ~}}]
    )
}
{{/each}}
// End struct section
"#;

static ENUM_T: &str = r#"
// Start enum section
{{#each ns.enums as |enum|}}
exports.{{enum.name}} = {{enum.name}};
function {{enum.name}}() {
    return new _xdrJsSerialize.default.Enum({
        {{#each enum.values as |val| ~}}
          {{val.index}}: "{{val.name}}",
        {{/each}}
    })
}
{{/each}}

// End enum section
"#;

static UNION_T: &str = r#"
// Start union section

{{#each ns.unions as |uni|}}
exports.{{uni.name}} = {{uni.name}};
function {{uni.name}}() {
    return new _xdrJsSerialize.default.Union(
        {{uni.switch.enum_type}}(),
        {
            {{#each uni.switch.cases as |case|~}}
                {{#if (not (isvoid case.ret_type.name))}}
                    "{{case.value}}": function() { return  {{#typeconv case.ret_type.name case.ret_type.type_name case.ret_type.array_size case.ret_type.fixed_array}}{{/typeconv}} },
                {{else}}
                    "{{case.value}}": function() { return new _xdrJsSerialize.default.Void() },
                {{/if}}
            {{/each}}
        }
    )
}
{{/each}}
// End union section
"#;

static FOOTER: &str = r#"
// End namespace {{ns.name}}
{{/each~}}
"#;

#[derive(Debug, Default)]
pub struct CommonJsGenerator {}

fn build_file_template() -> String {
    format!("{}{}{}{}{}{}", HEADER, TYPEDEFS_T, STRUCTS_T, ENUM_T, UNION_T, FOOTER)
}

fn is_array_type(def_type: &str) -> bool {
    match def_type {
        "Str" => true,
        "opaque" => true,
        _ => false,
    }
}

fn is_built_in(def_type: &str) -> bool {
    match def_type {
        "Void" => true,
        "Bool" => true,
        "Int" => true,
        "Hyper" => true,
        "UInt" => true,
        "UHyper" => true,
        "Float" => true,
        "Double" => true,
        "quadruple" => true,
        _ => false,
    }
}

fn is_built_in_single(def_type: &str) -> bool {
    !is_array_type(def_type) && is_built_in(def_type)
}

impl CodeGenerator for CommonJsGenerator {
    fn code(&self, namespaces: Vec<Namespace>) -> Result<String, &'static str> {
        let mut type_map = HashMap::new();
        type_map.insert("boolean", "Bool");
        type_map.insert("int", "Int");
        type_map.insert("unsigned int", "UInt");
        type_map.insert("unsigned hyper", "UHyper");
        type_map.insert("hyper", "Hyper");
        type_map.insert("string", "Str");
        type_map.insert("float", "Float");
        type_map.insert("double", "Double");
        type_map.insert("void", "Void");
        let processed = apply_type_map(namespaces, &type_map)?;
        let mut reg = Handlebars::new();
        let file_t = build_file_template();
        handlebars_helper!(typeconv: |name: str, typ: str, size: i64, fixed: bool| match (name, typ, size, fixed) {
            (_, "opaque", _, false) => format!("new _xdrJsSerialize.default.VarOpaque({})", size),
            (_, "opaque", _, true) => format!("new _xdrJsSerialize.default.FixedOpaque({})", size),
            (_, typ, size, false) if is_built_in_single(typ) && size > 0 => format!("new _xdrJsSerialize.default.VarArray({}, () => new _xdrJsSerialize.default.{}())", size, typ),
            (_, typ, size, false) if !is_array_type(typ) && size > 0 => format!("new _xdrJsSerialize.default.VarArray({}, {})", size, typ),
            (_, typ, size, _) if is_built_in_single(typ) && size == 0 => format!("new _xdrJsSerialize.default.{}()", typ),
            (_, typ, size, _) if is_built_in_single(typ) && size > 0 => format!("new _xdrJsSerialize.default.FixedArray({}, () => new _xdrJsSerialize.default.{}())", size, typ),
            (_, typ, size, _) if !is_array_type(typ) && size == 0 => format!("{}()", typ),
            (_, typ, size, _) if !is_array_type(typ) && size > 0 => format!("new _xdrJsSerialize.default.FixedArray({}, {})", size, typ),
            (_, typ, size, _) if !is_array_type(typ) && size > 0 => format!("new _xdrJsSerialize.default.FixedArray({}, {})", size, typ),
            _ => format!("new _xdrJsSerialize.default.{}('', {})", typ, size)
        });
        handlebars_helper!(isvoid: |x: str| x == "");
        reg.register_helper("isvoid", Box::new(isvoid));
        reg.register_helper("typeconv", Box::new(typeconv));
        let result = reg.render_template(file_t.into_boxed_str().as_ref(), &processed).unwrap();

        return Ok(result);
    }
}