finitio 0.1.0

Finitio data language
Documentation
use nom::{
    character::complete::char,
    combinator::{map, opt},
    error::context,
    multi::separated_list0,
    sequence::{delimited, pair, preceded, separated_pair, terminated},
    IResult,
};
use serde::{Serialize, Deserialize};

use crate::common::FilePosition;
use crate::fio::common::{parse_identifier, ws, Span};
use crate::fio::r#type::{parse_type, Type};

#[cfg(test)]
use crate::fio::common::assert_parse;
#[cfg(test)]

use super::RefType;

#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct Heading {
    pub attributes: Vec<Attribute>,
    pub position: FilePosition,
}

#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct Attribute {
    pub name: String,
    pub att_type: Type,
    pub optional: bool,
    pub position: FilePosition,
}

fn parse_separator(input: Span) -> IResult<Span, char> {
    preceded(ws, char(','))(input)
}

fn parse_attribute(input: Span) -> IResult<Span, Attribute> {
    map(
        separated_pair(
            parse_identifier,
            preceded(ws, char(':')),
            pair(opt(terminated(char('?'), ws)), parse_type),
        ),
        |(name, (optional, att_type))| Attribute {
            name,
            att_type,
            optional: optional != None,
            position: input.into(),
        },
    )(input)
}

pub fn parse_heading(input: Span) -> IResult<Span, Heading> {
    map(parse_attributes, |attributes| Heading {
        attributes,
        position: input.into(),
    })(input)
}

fn parse_attributes(input: Span) -> IResult<Span, Vec<Attribute>> {
    context(
        "fields",
        delimited(
            char('{'),
            separated_list0(parse_separator, preceded(ws, parse_attribute)),
            preceded(ws, char('}')),
        ),
    )(input)
}

#[test]
fn test_parse_attribute() {
    assert_parse(
        parse_attribute(Span::new("name:String")),
        Attribute {
            name: "name".to_string(),
            position: FilePosition { line: 1, column: 1 },
            att_type: Type::RefType(RefType {
                name: "String".to_string(),
                position: FilePosition { line: 1, column: 6 },
            }),
            optional: false,
        },
    );
    assert_parse(
        parse_attribute(Span::new("name: String")),
        Attribute {
            name: "name".to_string(),
            position: FilePosition { line: 1, column: 1 },
            att_type: Type::RefType(RefType {
                name: "String".to_string(),
                position: FilePosition { line: 1, column: 7 },
            }),
            optional: false,
        },
    );
    assert_parse(
        parse_attribute(Span::new("name : String")),
        Attribute {
            name: "name".to_string(),
            position: FilePosition { line: 1, column: 1 },
            att_type: Type::RefType(RefType {
                name: "String".to_string(),
                position: FilePosition { line: 1, column: 8 },
            }),
            optional: false,
        },
    );
}

#[test]
fn test_parse_attribute_optional() {
    assert_parse(
        parse_attribute(Span::new("name :? String")),
        Attribute {
            name: "name".to_string(),
            position: FilePosition { line: 1, column: 1 },
            att_type: Type::RefType(RefType {
                name: "String".to_string(),
                position: FilePosition { line: 1, column: 9 },
            }),
            optional: true,
        },
    );
}

#[test]
fn test_parse_attributes_0() {
    let contents = ["{}", "{ }"];
    for content in contents.iter() {
        assert_parse(parse_attributes(Span::new(content)), vec![])
    }
}

#[test]
fn test_parse_attributes_simple() {
    assert_parse(
        parse_attributes(Span::new("{name: String}")),
        vec![Attribute {
            name: "name".to_string(),
            att_type: Type::RefType(RefType {
                name: "String".to_string(),
                position: FilePosition { line: 1, column: 8 },
            }),
            optional: false,
            position: FilePosition { line: 1, column: 2 },
        }],
    );
}

#[test]
fn test_parse_attributes_duo() {
    assert_parse(
        parse_attributes(Span::new("{name: String, age: Number}")),
        vec![
            Attribute {
                name: "name".to_string(),
                att_type: Type::RefType(RefType {
                    name: "String".to_string(),
                    position: FilePosition { line: 1, column: 8 },
                }),
                optional: false,
                position: FilePosition { line: 1, column: 2 },
            },
            Attribute {
                name: "age".to_string(),
                att_type: Type::RefType(RefType {
                    name: "Number".to_string(),
                    position: FilePosition {
                        line: 1,
                        column: 21,
                    },
                }),
                optional: false,
                position: FilePosition {
                    line: 1,
                    column: 16,
                },
            },
        ],
    );
}

#[test]
fn test_parse_attributes_spacing() {
    let heading = "{
      name :  String,
      age  :? Number
    }";
    assert_parse(
        parse_attributes(Span::new(heading)),
        vec![
            Attribute {
                name: "name".to_string(),
                att_type: Type::RefType(RefType {
                    name: "String".to_string(),
                    position: FilePosition {
                        line: 2,
                        column: 15,
                    },
                }),
                optional: false,
                position: FilePosition { line: 2, column: 7 },
            },
            Attribute {
                name: "age".to_string(),
                att_type: Type::RefType(RefType {
                    name: "Number".to_string(),
                    position: FilePosition {
                        line: 3,
                        column: 15,
                    },
                }),
                optional: true,
                position: FilePosition { line: 3, column: 7 },
            },
        ],
    );
}