xschem-parser 0.1.0

Xschem schematic and symbol parser
Documentation
use nom::character::complete::{alpha1, digit1};
use nom::error::ErrorKind;
use nom::sequence::preceded;
use nom::{Err, Parser};

use crate::parse::{
    arc_object, attributes, component_instance, key_value, line_object, polygon_object, property,
    rectangle_object, schematic_full, text_object, try_skip, version_object, wire_object,
};
use crate::token::{
    Arc, Component, Line, Polygon, Property, Rectangle, Rotation, Text, Version, Wire,
};

#[test]
fn parse_try_skip() {
    assert_eq!(
        try_skip::<&str, &str, (&str, ErrorKind), _>(digit1).parse("abc123"),
        Ok(("abc123", None))
    );
    assert_eq!(
        try_skip::<&str, &str, (&str, ErrorKind), _>(digit1).parse("123abc"),
        Ok(("abc", Some("123")))
    );
    assert_eq!(
        try_skip::<&str, &str, (&str, ErrorKind), _>(preceded(digit1, alpha1)).parse("123abc"),
        Ok(("", Some("abc")))
    );
    assert_eq!(
        try_skip::<&str, &str, (&str, ErrorKind), _>(preceded(digit1, alpha1)).parse("123   "),
        Ok(("   ", None))
    );
    assert_eq!(
        try_skip::<&str, (&str, &str), (&str, ErrorKind), _>(key_value).parse("abc="),
        Ok(("", None))
    );
    assert_eq!(
        try_skip::<&str, (&str, &str), (&str, ErrorKind), _>(key_value).parse("test key=val"),
        Ok((" key=val", None))
    );
}

#[test]
fn parse_key_value() {
    assert_eq!(
        key_value::<&str, (&str, ErrorKind)>("a1A_9Z=!#$zcv_`~^)"),
        Ok(("", ("a1A_9Z", "!#$zcv_`~^)")))
    );
    assert_eq!(
        key_value::<&str, (&str, ErrorKind)>(r#"key="""#),
        Ok(("", ("key", "")))
    );
    assert_eq!(
        key_value::<&str, (&str, ErrorKind)>("=val"),
        Err(Err::Error(("=val", ErrorKind::TakeWhile1)))
    );
    assert_eq!(
        key_value::<&str, (&str, ErrorKind)>(r"key=\{val\}"),
        Ok(("", ("key", r"\{val\}")))
    );
    assert_eq!(
        key_value::<&str, (&str, ErrorKind)>(r#"key="\{val\}""#),
        Ok(("", ("key", r"\{val\}")))
    );
    assert_eq!(
        key_value::<&str, (&str, ErrorKind)>(r#"key="\\"val\\"""#),
        Ok(("", ("key", r#"\\"val\\""#)))
    );
    assert_eq!(
        key_value::<&str, (&str, ErrorKind)>(r#"key="\\val""#),
        Ok(("", ("key", r"\\val")))
    );
}

#[test]
fn parse_attributes() {
    assert_eq!(
        attributes::<&str, (&str, ErrorKind)>("key=val"),
        Ok(("", [("key", "val")].into()))
    );
    assert_eq!(
        attributes::<&str, (&str, ErrorKind)>("key=val k=v"),
        Ok(("", [("key", "val"), ("k", "v")].into()))
    );
    assert_eq!(
        attributes::<&str, (&str, ErrorKind)>("nokey k=v test"),
        Ok(("", [("k", "v")].into()))
    );
}

#[test]
fn parse_property() {
    assert_eq!(
        property::<&str, (&str, ErrorKind)>("{}"),
        Ok(("", Property::default()))
    );
    assert_eq!(
        property::<&str, (&str, ErrorKind)>("{a b c}"),
        Ok((
            "",
            Property {
                prop: "a b c",
                ..Default::default()
            }
        ))
    );
    assert_eq!(
        property::<&str, (&str, ErrorKind)>(r"{\\\}}"),
        Ok((
            "",
            Property {
                prop: r"\\\}",
                ..Default::default()
            }
        ))
    );
    assert_eq!(
        property::<&str, (&str, ErrorKind)>("{\t\n \\{\\}}"),
        Ok((
            "",
            Property {
                prop: "\t\n \\{\\}",
                ..Default::default()
            }
        ))
    );
}

#[test]
fn parse_version_object() {
    assert_eq!(
        version_object::<&str, (&str, ErrorKind)>("v {xschem version=3.4.0 file_version=1.0}"),
        Ok((
            "",
            Version(Property {
                prop: "xschem version=3.4.0 file_version=1.0",
                attrs: [("version", "3.4.0"), ("file_version", "1.0")].into()
            })
        ))
    );
    assert_eq!(
        version_object::<&str, (&str, ErrorKind)>(
            "v {xschem version=3.4.5 file_version=1.2\n* copyright info}"
        ),
        Ok((
            "",
            Version(Property {
                prop: "xschem version=3.4.5 file_version=1.2\n* copyright info",
                attrs: [("version", "3.4.5"), ("file_version", "1.2")].into()
            })
        ))
    );
}

#[test]
fn parse_text_object() {
    assert_eq!(
        text_object::<&str, (&str, ErrorKind)>(
            "T {3 of 4 NANDS of a 74ls00} 500 -580 0 0 0.4 0.4 {font=Monospace layer=4}",
        ),
        Ok((
            "",
            Text {
                text: "3 of 4 NANDS of a 74ls00",
                position: (500.0, -580.0).try_into().unwrap(),
                rotation: Rotation::Zero,
                flip: false.into(),
                size: (0.4, 0.4).try_into().unwrap(),
                property: Property {
                    prop: "font=Monospace layer=4",
                    attrs: [("font", "Monospace"), ("layer", "4")].into()
                },
            }
        )),
    );
    assert_eq!(
        text_object::<&str, (&str, ErrorKind)>("T {1\n2\n\n3} 1.1 4.04 3 1 1.0 2.0 {}",),
        Ok((
            "",
            Text {
                text: "1\n2\n\n3",
                position: (1.1, 4.04).try_into().unwrap(),
                rotation: Rotation::Three,
                flip: true.into(),
                size: (1.0, 2.0).try_into().unwrap(),
                property: Property::default(),
            }
        )),
    );
}

#[test]
fn parse_wire_object() {
    assert_eq!(
        wire_object::<&str, (&str, ErrorKind)>("N 890 -130 890 -110 {lab=ANALOG_GND}",),
        Ok((
            "",
            Wire {
                start: (890.0, -130.0).try_into().unwrap(),
                end: (890.0, -110.0).try_into().unwrap(),
                property: Property {
                    prop: "lab=ANALOG_GND",
                    attrs: [("lab", "ANALOG_GND")].into()
                },
            }
        )),
    );
}

#[test]
fn parse_line_object() {
    assert_eq!(
        line_object::<&str, (&str, ErrorKind)>("L 4 10 0 20 0 {}",),
        Ok((
            "",
            Line {
                layer: 4,
                start: (10.0, 0.0).try_into().unwrap(),
                end: (20.0, 0.0).try_into().unwrap(),
                property: Property::default(),
            }
        )),
    );
}

#[test]
fn parse_rectangle_object() {
    assert_eq!(
        rectangle_object::<&str, (&str, ErrorKind)>(
            "B 5 -62.5 -2.5 -57.5 2.5 {name=IN dir=in pinnumber=1}",
        ),
        Ok((
            "",
            Rectangle {
                layer: 5,
                start: (-62.5, -2.5).try_into().unwrap(),
                end: (-57.5, 2.5).try_into().unwrap(),
                property: Property {
                    prop: "name=IN dir=in pinnumber=1",
                    attrs: [("name", "IN"), ("dir", "in"), ("pinnumber", "1")].into()
                },
            }
        )),
    );
}

#[test]
fn parse_polygon_object() {
    assert_eq!(
        polygon_object::<&str, (&str, ErrorKind)>(
            "P 3 5 2450 -210 2460 -170 2500 -170 2510 -210 2450 -210 {}",
        ),
        Ok((
            "",
            Polygon {
                layer: 3,
                points: vec![
                    (2450.0, -210.0),
                    (2460.0, -170.0),
                    (2500.0, -170.0),
                    (2510.0, -210.0),
                    (2450.0, -210.0),
                ]
                .try_into()
                .unwrap(),
                property: Property::default(),
            }
        )),
    );
    assert_eq!(
        polygon_object::<&str, (&str, ErrorKind)>("P 3 2 0 0 {}",),
        Err(Err::Failure(("{}", ErrorKind::Char))),
    );
    assert_eq!(
        polygon_object::<&str, (&str, ErrorKind)>("P 3 2 0 0 1 {}",),
        Err(Err::Failure(("{}", ErrorKind::Char))),
    );
}

#[test]
fn parse_arc_object() {
    assert_eq!(
        arc_object::<&str, (&str, ErrorKind)>("A 3 450 -210 120 45 225 {}",),
        Ok((
            "",
            Arc {
                layer: 3,
                center: (450.0, -210.0).try_into().unwrap(),
                radius: 120.0.try_into().unwrap(),
                start_angle: 45.0.try_into().unwrap(),
                sweep_angle: 225.0.try_into().unwrap(),
                property: Property::default(),
            }
        )),
    );
}

#[test]
fn parse_component_instance() {
    assert_eq!(
        component_instance::<&str, (&str, ErrorKind)>("C {capa.sym} 890 -160 0 0 {name=C4}",),
        Ok((
            "",
            Component {
                reference: "capa.sym",
                position: (890.0, -160.0).try_into().unwrap(),
                rotation: Rotation::Zero,
                flip: false.into(),
                property: Property {
                    prop: "name=C4",
                    attrs: [("name", "C4")].into()
                },
                embedding: None,
            }
        )),
    );
}

#[test]
fn parse_7805_sym() {
    let input = include_str!("../../../../assets/7805.sym");
    let result = schematic_full::<&str, (&str, ErrorKind)>(input);
    assert!(result.is_ok(), "parse error: {result:?}");
}

#[test]
fn parse_embedding_sch() {
    let input = include_str!("../../../../assets/embedding.sch");
    let result = schematic_full::<&str, (&str, ErrorKind)>(input);
    assert!(result.is_ok(), "parse error: {result:?}");
}

#[test]
fn parse_pcb_test1_sch() {
    let input = include_str!("../../../../assets/pcb_test1.sch");
    let result = schematic_full::<&str, (&str, ErrorKind)>(input);
    assert!(result.is_ok(), "parse error: {result:?}");
}

#[test]
fn parse_pmos_sym() {
    let input = include_str!("../../../../assets/pmos.sym");
    let result = schematic_full::<&str, (&str, ErrorKind)>(input);
    assert!(result.is_ok(), "parse error: {result:?}");
}