idl 0.1.1

Library used for the idl language.
Documentation
use super::*;
use crate::idl::{keywords::Keywords, parser::*};

// TODO Split lines with more than 80 characters.

// The only stuff that this formatter reorders are the the library name and imports,
// since they must appear first. Anything else, only the comments, spaces and indentantion are fixed.
pub fn format_document(parser: &Parser) -> Option<String> {
    let mut body = String::new();
    let mut library = String::new();
    let mut imports = String::new();

    let mut is_comment = false;

    for node in &parser.nodes {
        match node {
            ParserNode::Imports(value) => {
                imports = format!("{} {{", Keywords::Import);
                // Only imports can span an entire line without being separated by a new line.
                imports +=
                    split_if_longer_than(value.as_slice(), MAX_LENGTH - imports.len() - 1).as_str();
                imports += CLOSE_NEW_LINE;
            }
            ParserNode::Library(value) => {
                library = format!("{} ", Keywords::Library);
                library += value.as_str();
                library += NEW_LINE;
            }
            ParserNode::Comment(value) => {
                if is_comment {
                    body += "\n";
                }

                for comment in value {
                    let list_comment = format!("{}{}\n", COMMENT_START, comment.as_str());
                    body += list_comment.as_str();
                }
                is_comment = true;
            }
            nodw => {
                if is_comment {
                    body += "\n";
                    is_comment = false;
                }
                match nodw {
                    ParserNode::Const(value) => {
                        if !value.comment.is_empty() {
                            push_comment(&mut body, &value.comment);
                        }

                        let type_body = format!(
                            "{} {} = {};{}",
                            Keywords::Const,
                            value.ident,
                            value.value.to_owned(),
                            NEW_LINE
                        );
                        body += type_body.as_str();
                    }
                    ParserNode::Enum(value) => {
                        if !value.comment.is_empty() {
                            push_comment(&mut body, &value.comment);
                        }

                        let type_body =
                            format!("{} {}{}", Keywords::Enum, value.ident, OPEN_NEW_LINE);
                        body += type_body.as_str();

                        for enum_node in value.fields.iter() {
                            match enum_node {
                                EnumNode::Comment(comment) => {
                                    push_field_comment(&mut body, comment)
                                }
                                EnumNode::EnumField(field) => {
                                    let field_str = format!("{}{},\n", INDENT, field);
                                    body += field_str.as_str();
                                }
                                EnumNode::TypeEnumField(field) => {
                                    let field_str = format!("{}{},\n", INDENT, field);
                                    body += field_str.as_str();
                                }
                            }
                        }
                        body += CLOSE_NEW_LINE;
                    }
                    ParserNode::Interface(value) => {
                        if !value.comment.is_empty() {
                            push_comment(&mut body, &value.comment);
                        }

                        let type_body =
                            format!("{} {}{}", Keywords::Interface, value.ident, OPEN_NEW_LINE);
                        body += type_body.as_str();

                        for interface_node in value.fields.iter() {
                            match interface_node {
                                InterfaceNode::Comment(comment) => {
                                    push_field_comment(&mut body, comment)
                                }
                                InterfaceNode::InterfaceField(field) => {
                                    let field_str = split_interface_field(field);
                                    body += field_str.as_str();
                                }
                            }
                        }
                        body += CLOSE_NEW_LINE;
                    }
                    ParserNode::Struct(value) => {
                        if !value.comment.is_empty() {
                            push_comment(&mut body, &value.comment);
                        }

                        let type_body =
                            format!("{} {}{}", Keywords::Struct, value.ident, OPEN_NEW_LINE);
                        body += type_body.as_str();

                        for struct_node in value.fields.iter() {
                            match struct_node {
                                StructNode::Comment(comment) => {
                                    push_field_comment(&mut body, comment)
                                }
                                StructNode::StructField(field) => {
                                    let field_str = format!("{}{},\n", INDENT, field);
                                    body += field_str.as_str();
                                }
                            }
                        }
                        body += CLOSE_NEW_LINE;
                    }
                    _ => {}
                }
            }
        }
    }

    library += imports.as_str();
    library += body.as_str();

    Some(library.trim().to_string())
}

fn push_field_comment(body: &mut String, comments: &[String]) {
    for comment in comments {
        let comment_str = format!("{}{}{}\n", INDENT, COMMENT_START, comment.as_str());
        body.push_str(comment_str.as_str());
    }
}

fn push_comment(body: &mut String, comments: &[String]) {
    for comment in comments {
        let list_comment = format!("{}{}\n", COMMENT_START, comment.as_str());
        body.push_str(list_comment.as_str());
    }
}

// If it fits in a line, a list with space in both sides,
// otherwise in each line, separated by a comma.
fn split_if_longer_than(txt: &[String], max_length: usize) -> String {
    let mut result = String::from(" ");

    for value in txt.iter() {
        if result != " " {
            result += ", ";
        }
        result += value;
    }

    result += " ";

    if result.len() > max_length {
        result = String::from("\n");

        for value in txt.iter() {
            let r = format!("{}{},\n", INDENT, value);
            result += r.as_str();
        }
    }

    result
}

fn split_interface_field(interface_field: &InterfaceField) -> String {
    // TODO
    let split_tuple = |indent: usize, ty: &[TupleEntry]| {
        let mut result = String::new();

        if ty.is_empty() {
            return "()".to_owned();
        }

        for (index, t) in ty.into_iter().enumerate() {
            let st = format!(
                "{}{}: {}{}",
                if index == 0 {
                    "(".to_owned()
                } else {
                    " ".repeat(indent)
                },
                t.ident,
                t.ty,
                if index != ty.len() - 1 { ",\n" } else { ")" }
            );
            result += st.as_str()
        }

        result
    };

    let tryln = format!(
        "{}:{}{},",
        INDENT,
        interface_field.ident.to_owned(),
        interface_field.ty.to_string()
    );

    if tryln.len() > MAX_LENGTH {
        let indent_len = format!("{}:{}(", INDENT, interface_field.ident.to_owned()).len();
        let ty_field = match &*interface_field.ty {
            Type::Tuple(tuple) => split_tuple(indent_len, &tuple.fields),
            Type::Function(function) => {
                let tt = match &*function.args {
                    Type::Tuple(value) => value.clone(),
                    _ => panic!("Not a tuple"),
                };

                format!(
                    "{} -> {}",
                    split_tuple(indent_len, &*tt.fields),
                    function.ret_ty.to_string()
                )
            }
            ty => ty.to_string(),
        };

        format!(
            "{}:{}{},\n",
            INDENT,
            interface_field.ident.to_owned(),
            ty_field
        )
    } else {
        tryln + "\n"
    }
}