finitio/fio/
sub.rs

1use super::any::parse_any;
2use super::builtin::parse_builtin;
3use super::r#ref::parse_ref;
4use super::r#struct::parse_struct;
5use super::relation::parse_relation;
6use super::seq::parse_seq;
7use super::set::parse_set;
8use super::tuple::parse_tuple;
9use crate::fio::r#type::parse_subtypeable;
10
11use super::{Type};
12use crate::common::FilePosition;
13use crate::fio::common::Span;
14use nom::branch::alt;
15use nom::character::complete::alphanumeric1;
16use nom::combinator::{peek};
17use nom::sequence::{preceded, separated_pair, terminated};
18use nom::{bytes::complete::tag, combinator::map, IResult};
19use serde::{Serialize, Deserialize};
20use super::common::{ws, take_parenth_content};
21
22
23#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
24pub struct Constraint {
25    pub param: String,
26    pub expr: String,
27    pub position: FilePosition,
28}
29
30#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
31pub struct SubType {
32    pub base: Box<Type>,
33    pub constraints: Vec<Constraint>,
34    pub position: FilePosition,
35}
36
37pub fn parse_anonymous_constraint(input: Span) -> IResult<Span, Constraint> {
38    let constraint = take_parenth_content('(', ')');
39
40    let (rest, parsed) = match constraint(input) {
41        Ok(c) => c,
42        Err(err) => {
43            return Err(err)
44        }
45    };
46
47    let mut param = preceded(ws, terminated(alphanumeric1, preceded(ws, tag("|"))));
48    match param(parsed) {
49        Ok((expr, param)) => {
50            let c = Constraint {
51                param: param.to_string(),
52                expr: expr.to_string(),
53                position: parsed.into(),
54            };
55            Ok((rest, c))
56        }
57        Err(err) => return Err(err)
58    }
59}
60
61pub fn check_looks_like_sub(input: Span) -> IResult<Span, bool> {
62    // First we ensure that it looks like a subtype
63    let types = alt((
64        map(parse_builtin, |_| {}),
65        map(parse_ref, |_| {}),
66        map(parse_seq, |_| {}),
67        map(parse_set, |_| {}),
68        map(parse_struct, |_| {}),
69        map(parse_tuple, |_| {}),
70        map(parse_relation, |_| {}),
71        map(parse_any, |_| {}),
72    ));
73    let check = separated_pair(types, ws, tag("("));
74    match peek(check)(input) {
75        Ok(_s) => Ok((input, true)),
76        Err(err) => Err(err),
77    }
78}
79
80pub fn parse_sub(input: Span) -> IResult<Span, SubType> {
81    // First we ensure that it looks like a subtype
82    match peek(check_looks_like_sub)(input) {
83        Err(err) => Err(err),
84        Ok(_) => {
85            map(
86                separated_pair(parse_subtypeable, ws, parse_anonymous_constraint),
87                |(ftype, constraint)| SubType {
88                    base: Box::new(ftype),
89                    constraints: vec![constraint],
90                    position: input.into(),
91                },
92            )(input)
93        }
94    }
95}
96
97#[cfg(test)]
98use super::{BuiltinType, SeqType};
99#[cfg(test)]
100use crate::fio::common::assert_parse;
101
102#[test]
103fn test_parse_anonymous_constraint() {
104    assert_parse(
105        parse_anonymous_constraint(Span::new("(s | some anonymous constraint)")),
106        Constraint {
107            param: "s".to_string(),
108            expr: " some anonymous constraint".to_string(),
109            position: FilePosition { line: 1, column: 2 },
110        },
111    );
112}
113
114#[test]
115fn test_check_looks_like_sub() {
116    let output = check_looks_like_sub(Span::new("Number(s | foo bar baz)"));
117    let output = output.unwrap();
118    assert_eq!(output.0.fragment(), &"Number(s | foo bar baz)");
119    assert_eq!(output.1, true);
120}
121
122#[test]
123fn test_parse_sub_type_builtin() {
124    assert_parse(
125        parse_sub(Span::new(".Number(s | some anonymous constraint)")),
126        SubType {
127            position: FilePosition { line: 1, column: 1 },
128            base: Box::new(Type::BuiltinType(BuiltinType {
129                name: "Number".to_string(),
130                position: FilePosition { line: 1, column: 1 },
131            })),
132            constraints: vec![Constraint {
133                param: "s".to_string(),
134                expr: " some anonymous constraint".to_string(),
135                position: FilePosition { line: 1, column: 9 },
136            }],
137        },
138    );
139}
140
141#[test]
142fn test_parse_sub_type_seq() {
143    assert_parse(
144        parse_sub(Span::new("[.Number](s | some anonymous constraint)")),
145        SubType {
146            position: FilePosition { line: 1, column: 1 },
147            base: Box::new(Type::SeqType(SeqType {
148                elm_type: Box::new(Type::BuiltinType(BuiltinType {
149                    name: "Number".to_string(),
150                    position: FilePosition { line: 1, column: 2 },
151                })),
152                position: FilePosition { line: 1, column: 1 },
153            })),
154            constraints: vec![Constraint {
155                param: "s".to_string(),
156                expr: " some anonymous constraint".to_string(),
157                position: FilePosition {
158                    line: 1,
159                    column: 11,
160                },
161            }],
162        },
163    );
164}
165
166#[test]
167fn test_parse_sub_type_spacing() {
168    assert_parse(
169        parse_sub(Span::new(
170            "[ .Number ] (   s  |   \nsome anonymous constraint)",
171        )),
172        SubType {
173            position: FilePosition { line: 1, column: 1 },
174            base: Box::new(Type::SeqType(SeqType {
175                elm_type: Box::new(Type::BuiltinType(BuiltinType {
176                    name: "Number".to_string(),
177                    position: FilePosition { line: 1, column: 3 },
178                })),
179                position: FilePosition { line: 1, column: 1 },
180            })),
181            constraints: vec![Constraint {
182                param: "s".to_string(),
183                expr: "   \nsome anonymous constraint".to_string(),
184                position: FilePosition {
185                    line: 1,
186                    column: 14,
187                },
188            }],
189        },
190    );
191}
192
193
194#[test]
195fn test_parse_sub_type_functions_in_expressions() {
196    assert_parse(
197        parse_sub(Span::new(
198            ".String(s | len(s) > 8)",
199        )),
200        SubType {
201            position: FilePosition { line: 1, column: 1 },
202            base: Box::new(Type::BuiltinType(BuiltinType {
203                name: "String".to_string(),
204                position: FilePosition { line: 1, column: 1 },
205            })),
206            constraints: vec![Constraint {
207                param: "s".to_string(),
208                expr: " len(s) > 8".to_string(),
209                position: FilePosition { line: 1, column: 9 },
210            }],
211        },
212    );
213}