fips_md/parser/
parser.rs

1//! Parser for the FIPS language
2
3use super::*;
4use crate::runtime::BUILTIN_CONST_NDIM;
5use std::num::IntErrorKind;
6
7// Helper function
8fn make_binop(lhs: Expression, rhs: Expression, op: BinaryOperator) -> Expression {
9    Expression::BinaryOperation(BinaryOperation {
10        lhs: Box::new(lhs),
11        rhs: Box::new(rhs),
12        op
13    })
14}
15
16peg::parser!{
17    pub grammar fips_parser() for str {
18
19        /// Parse a unit (i.e. a source file)
20        pub rule unit() -> Unit
21            = _ members:unit_member() ** _ _ {
22                let mut unit = Unit::new();
23                for member in members {
24                    match member {
25                        UnitMember::GlobalStateMember(global_state_member) => unit.global_state_members.push(global_state_member),
26                        UnitMember::Particle(particle) => unit.particles.push(particle),
27                        UnitMember::Interaction(interaction) => unit.interactions.push(interaction),
28                        UnitMember::Simulation(simulation) => unit.simulations.push(simulation),
29                        UnitMember::ExternFunctionDecl(extern_functiondecl) => unit.extern_functiondecls.push(extern_functiondecl),
30                    }
31                }
32                unit
33            }
34
35        pub(crate) rule unit_member() -> UnitMember
36            = global_state_member:global_state_member() { UnitMember::GlobalStateMember(global_state_member) } 
37            / particle:particle() { UnitMember::Particle(particle) }
38            / interaction:interaction() { UnitMember::Interaction(interaction) }
39            / simulation:simulation() { UnitMember::Simulation(simulation) }
40            / extern_funcdecl:extern_functiondecl() { UnitMember::ExternFunctionDecl(extern_funcdecl) }
41
42        /// Global state member
43        rule global_state_member() -> GlobalStateMember
44            = "global" __ name:identifier_not_ignored() _ ":" _ mutable:("mut" __)? _ typ:typ() ";"
45            {
46                let mutable = mutable.is_some();
47                GlobalStateMember { name, mutable, typ}
48            }
49
50        /// Parse an `extern` function declaration
51        pub rule extern_functiondecl() -> ExternFunctionDecl
52            = "extern" __ "fn" __ name:identifier_not_ignored() _ 
53                "(" _ parameter_types:typ() ** (_ "," _) _ ")" _ 
54                "->" _ return_type:typ() 
55            {
56                ExternFunctionDecl { name, parameter_types, return_type }
57            }
58
59        /// Parse a `particle` primary block
60        pub rule particle() -> Particle
61            = "particle" __ name:identifier_not_ignored() _ 
62                "{" _ members:(
63                    _ "}" { Vec::new() } // Default to empty vector
64                    / (m:particle_member() ** (_ "," _) _ "}" {m})
65                )
66            {
67                Particle { name, members }
68            }
69
70        rule particle_member() -> ParticleMember 
71            = name:identifier_not_ignored() _ ":" _ mutable:("mut" __)? _ typ_pos:typ_or_position() { 
72                ParticleMember { name, mutable: !mutable.is_none(), typ: typ_pos.0, is_position: typ_pos.1 }
73            }
74
75        /// Parse an `interaction` primary block
76        pub rule interaction() -> Interaction
77            = "interaction" __ name:identifier_not_ignored() _
78                "(" _ name_a:identifier() _ ":" _ type_a:identifier_not_ignored() _
79                "," _ name_b:identifier() _ ":" _ type_b:identifier_not_ignored() _ ")" _
80                "for" distance_vec:( __ "|" _ d:identifier_not_ignored() _ "|" _ "=" {d})? __ distance:identifier() _ "<" _ cutoff:float_ctc() _
81                "{" _ common_block:("common" _ "{" _ b:statement() ** (_ ";" _) _ ";" _ "}" {b})? _ // TODO: Clean this up            
82                    quantities:(
83                    _ "}" { Vec::new() } // Default to empty vector
84                    / (q:interaction_quantity() ** ( _ ) _ "}" {q})
85                )
86            {
87                Interaction {
88                    name,
89                    name_a, type_a,
90                    name_b, type_b,
91                    distance, distance_vec, cutoff,
92                    common_block, quantities
93                }
94            }
95
96        rule interaction_quantity() -> InteractionQuantity
97            = "quantity" _ name:identifier_not_ignored() _ 
98                "-" _ "[" _ reduction_method:reduction_method()  _ "]" _ "->" _
99                "(" _ target_a:identifier_not_ignored() _ "," _ symmetry:interaction_symmetry()? _ target_b:identifier_not_ignored() _ ")" _
100                "{" _ expression:expression_block() _ "}"
101            {
102                let symmetry = symmetry.unwrap_or(InteractionSymmetry::Symmetric);
103                InteractionQuantity {
104                    name,
105                    reduction_method,
106                    target_a,
107                    target_b,
108                    symmetry,
109                    expression
110                }
111            }
112
113        rule reduction_method() -> ReductionMethod
114            = "sum" { ReductionMethod::Sum }
115
116        rule interaction_symmetry() -> InteractionSymmetry
117            = "-" { InteractionSymmetry::Antisymmetric }
118            / "!" { InteractionSymmetry::Asymmetric }
119
120        /// Parse a simulation block
121        pub rule simulation() -> Simulation
122            = "simulation" _ name:identifier_not_ignored() _ "{" _
123                default_particle:("default" _ "particle" _ dp:identifier_not_ignored() _ ";" {dp})? _
124                blocks:simulation_block() ** _ _
125                "}"
126            { 
127                Simulation {
128                    name,default_particle,blocks
129                }
130            }
131
132        rule simulation_block() -> SimulationBlock
133            = "once" __ step:nat_ctc() _ "{" _ subblocks:simulation_sub_block()* _ "}" {
134                SimulationBlock::Once(OnceBlock {
135                    step, subblocks
136                })
137            }
138            / "step" __ step_range:step_range()? _ "{" _ subblocks:simulation_sub_block() ** ( _ ) _ "}" {
139                SimulationBlock::Step( StepBlock {
140                    step_range: step_range.unwrap_or_default(), subblocks
141                })
142            }
143        
144        rule simulation_sub_block() -> SimulationSubBlock
145            = "particle" __ particle:identifier_not_ignored() _ statements:statement_block()
146            {
147                SimulationSubBlock {
148                    statements, particle: Some(particle)
149                }
150            }
151            / &statement() _ statements:statement() ** ( _ ";" _ ) _ ";" {
152                SimulationSubBlock {
153                    statements, particle: None
154                }
155            }
156
157        pub(crate) rule step_range() -> StepRange
158            = start:nat_ctc()? _ ".." _ end:nat_ctc()? _ step:("," _ s:nat_ctc() {s})? {
159                let mut range: StepRange = Default::default();
160                range.start = start.unwrap_or(range.start);
161                range.end   = end.unwrap_or(range.end);
162                range.step  = step.unwrap_or(range.step);
163                range
164            }
165            / step:nat_ctc() { StepRange {
166                step, ..Default::default()
167            }}
168
169        // == Expressions and statements ==
170
171        /// Parse a block expression
172        rule expression_block() -> Expression
173            = statements:(s:statement() ** ( _ ";" _ ) _ ";" {s})? _ expr:expression() {
174                let statements = statements.unwrap_or(vec![]);
175                Expression::Block(BlockExpression {
176                    statements, expression: Box::new(expr)
177                })
178            }
179
180        /// Parse a non-block expression
181        pub rule expression() -> Expression = precedence!{
182            lhs:(@) _ "+" _ rhs:@ { make_binop(lhs,rhs,BinaryOperator::Add) }
183            lhs:(@) _ "-" _ rhs:@ { make_binop(lhs,rhs,BinaryOperator::Sub) }
184            --
185            lhs:(@) _ "*" _ rhs:@ { make_binop(lhs,rhs,BinaryOperator::Mul) }
186            lhs:(@) _ "/" _ rhs:@ { make_binop(lhs,rhs,BinaryOperator::Div) }
187            --
188            fn_name:identifier_not_ignored() _ "(" _ parameters:expression() ** (_ "," _) _ ")"  {
189                Expression::FunctionCall(FunctionCall {
190                    fn_name,
191                    parameters
192                })
193            }
194            namespace:identifier_not_ignored() _ "." _ name:identifier_not_ignored() {
195                Expression::Atom(Atom::NamespaceVariable{namespace, name})
196            }
197            // Currently only one-dimensional arrays are supported and
198            // all arrays must be bound to an identifier first (TODO)
199            array:identifier_not_ignored() _ "[" _ index:nat_ctc() _ "]" {
200                Expression::Indexing(AtIndex {
201                    array, index
202                })
203            }
204            "[" _ elements:expression() ** (_ "," _) _ "]" {
205                Expression::AdHocArray(AdHocArrayExpression{ elements })
206            }
207            atom:atom() {
208                Expression::Atom(atom)
209            }
210            "(" _ expr:expression() _ ")" { expr }
211        }
212
213        /// Parse an atomic expression
214        rule atom() -> Atom 
215            = x:numeric_literal() { Atom::Literal(x) }
216            / name:identifier_not_ignored() { Atom::Variable(name) }
217
218        rule statement_block() -> Vec<Statement>
219            = "{" _ statements:statement() ** ( _ ";" _ ) _ ";" _ "}" {
220                statements
221            }
222            / "{" _ "}" { vec![] }
223
224        /// Parse a statement
225        pub rule statement() -> Statement
226            = "let" __ name:identifier_not_ignored() _ ":" _ typ:typ() _ "=" _ initial:expression() {
227                Statement::Let(LetStatement {
228                    name, initial, typ
229                })
230            }
231            / "update" __ interaction:identifier_not_ignored()
232                quantity:(_ "." _ q:identifier_not_ignored() {q})?
233            {
234                Statement::Update(UpdateStatement {
235                    interaction, quantity
236                })
237            }
238            / "call" __ name:identifier_not_ignored() {
239                Statement::Call(CallStatement {
240                    name
241                })
242            }
243            / assignee:identifier_not_ignored() _ index:("[" _ idx:nat_ctc() _ "]" {idx})? _ "=" _ value:expression() {
244                Statement::Assign(AssignStatement {
245                    assignee, value, index
246                })
247            }
248
249        // == General syntax elements ==
250
251        /// Any type specification
252        /// (This is a tuple to denote the position flag)
253        rule typ_or_position() -> (FipsType, bool)
254            = "position" {
255                (FipsType::Array {
256                    typ: Box::new(FipsType::Double),
257                    length: CompileTimeConstant::Identifier(BUILTIN_CONST_NDIM.into())
258                }, true)
259            }
260            / typ:typ() { (typ, false) }
261
262        rule typ() -> FipsType
263            = "f64" { FipsType::Double }
264            / "i64" { FipsType::Int64 }
265            / "[" _ typ:typ() _ ";" _ length:nat_ctc() _ "]" { 
266                FipsType::Array {
267                    typ: Box::new(typ),
268                    length
269                }
270            }
271
272        /// Natural number (including 0)
273        rule nat() -> usize
274            = s:$("0" / (['1'..='9']['0'..='9']*))
275            { ?
276                s.parse::<usize>().or_else(|err| { 
277                    match err.kind() {
278                        IntErrorKind::Empty => unreachable!(),
279                        IntErrorKind::InvalidDigit => unreachable!(),
280                        IntErrorKind::PosOverflow => Err("Cannot parse integer number (positive overflow)"),
281                        IntErrorKind::NegOverflow => unreachable!(),
282                        IntErrorKind::Zero => unreachable!(),
283                        _ => Err("Cannot parse integer number (unknown reason)")
284                    }
285                } )
286            }
287
288        /// Floating point or integer literal (always tries to parse as integer first)
289        rule numeric_literal() -> Literal 
290            = ['n'|'N'] ['a'|'A'] ['n'|'N'] { Literal::Double(f64::NAN) }
291            / "+"? ['i'|'I'] ['n'|'N'] ['f'|'F'] { Literal::Double(f64::INFINITY) }
292            / "-" ['i'|'I'] ['n'|'N'] ['f'|'F'] { Literal::Double(f64::NEG_INFINITY) }
293            / s:$(['+'|'-']? ['0'..='9']+ ("." ['0'..='9']*)? ((['e'|'E'] ['+'|'-']? ['0'..='9']+)?)?)
294            { ?
295                // First try to parse as integer
296                match s.parse::<i64>() {
297                    Ok(n) => Ok(Literal::Int64(n)),
298                    Err(e) => {
299                        match e.kind() {
300                            // Integer literals should not silently overflow to floats
301                            IntErrorKind::PosOverflow => Err("Positive integer overflow"),
302                            IntErrorKind::NegOverflow => Err("Negative integer overflow"),
303                            // If overflow was not the problem, try to parse as float
304                            _ => {
305                                s.parse::<f64>()
306                                    .or_else(|err| { Err("Cannot parse float number") } )
307                                    .map(|x| Literal::Double(x))
308                            },
309                        }
310                    },
311                }
312            }
313
314        /// Floating point compile_time_constant
315        rule float_ctc() -> CompileTimeConstant<f64>
316            = x:numeric_literal() {
317                // Unify type of literal to float
318                let x = match x {
319                    Literal::Double(x) => x,
320                    Literal::Int64(n) => n as f64,
321                };
322                CompileTimeConstant::Literal(x)
323            }
324            / name:identifier_not_ignored() { CompileTimeConstant::Identifier(name) }
325
326        /// Natural compile time constant
327        rule nat_ctc() ->  CompileTimeConstant<usize>
328            = n:nat() { CompileTimeConstant::Literal(n) } 
329            / name:identifier_not_ignored() { CompileTimeConstant::Identifier(name) }
330
331        rule identifier() -> Identifier
332            = !keyword() s:$(['a'..='z'|'A'..='Z']['a'..='z'|'A'..='Z'|'0'..='9'|'_']*) {
333                match s {
334                    "_" => Identifier::Ignored,
335                    _ => Identifier::Named(String::from(s))
336                }
337            }
338
339        rule identifier_not_ignored() -> String
340            = ident:identifier() { ?
341                match ident {
342                    Identifier::Ignored => Err("Cannot use _ here"),
343                    Identifier::Named(name) => Ok(name)
344                }
345            }
346
347        rule keyword() -> ()
348            = "particle" / "once" / "step" / "call" / "let" / "update"
349
350        rule _() -> ()
351            = quiet!{(
352                [' '|'\n'|'\t'|'\r'] _) // 0 or more whitespaces
353                / ("//" (!['\n'][_])* ['\n'] _) // Comment to EOL
354                / ""}
355
356        rule __() -> ()
357            = quiet!{[' '|'\n'|'\t'|'\r'] _}
358    }
359}
360
361#[cfg(test)]
362mod test {
363    use super::*;
364
365    #[test]
366    fn global_state() {
367        // Invalid cases
368        assert!(fips_parser::unit_member("global foo: i64").is_err(),
369            "Missing semicolon in global state member declaration not caught");
370        assert!(fips_parser::unit_member("global foo;").is_err(),
371            "Missing type specification in global state member declaration not caught");
372        assert!(fips_parser::unit_member("global : i64;").is_err(),
373            "Missing member name in global state member declaration not caught");
374        assert!(fips_parser::unit_member("global foo : i64 = 42;").is_err(),
375            "Invalid assignment in global state member declaration not caught");
376        // Valid cases
377        assert_eq!(fips_parser::unit_member("global foo: f64;")
378            .expect("Cannot parse immutable global state member"),
379            UnitMember::GlobalStateMember(GlobalStateMember {
380                name: "foo".into(),
381                mutable: false,
382                typ: FipsType::Double
383            })
384        );
385        assert_eq!(fips_parser::unit_member("global foo: mut f64;")
386        .expect("Cannot parse mutable global state member"),
387        UnitMember::GlobalStateMember(GlobalStateMember {
388            name: "foo".into(),
389            mutable: true,
390            typ: FipsType::Double
391        })
392    );
393    }
394
395    #[test]
396    fn statements_let() {
397        assert!(fips_parser::statement("let foo = 42").is_err(),
398            "Let statement without type was not caught");
399        assert!(fips_parser::statement("let : f64 = 42").is_err(),
400            "Let statement without name was not caught");
401        assert!(fips_parser::statement("let _: f64 = 42").is_err(),
402            "Let statement with omitted name was not caught");
403        assert!(fips_parser::statement("let _: f64").is_err(),
404            "Let statement without initial value not caught");
405        let parser_result = fips_parser::statement("let foo: f64 = 42")
406            .expect("Cannot parse valid let statement");
407        assert_eq!(parser_result, Statement::Let(LetStatement{
408            name: "foo".into(),
409            typ: FipsType::Double,
410            initial: Expression::Atom(
411                Atom::Literal(
412                    Literal::Int64(42)
413                )
414            ) 
415        }));
416    }
417
418    #[test]
419    fn statements_assign() {
420        assert!(fips_parser::statement(" = 1337").is_err(),
421            "Assign statement without assignee not caught");
422        assert!(fips_parser::statement("foo[] = 1337").is_err(),
423            "Indexed assign statement with empty index not caught");
424        assert!(fips_parser::statement("foo[1.2] = 1337").is_err(),
425            "Indexed assign statement with invalid index not caught");
426        assert!(fips_parser::statement("foo = ").is_err(),
427            "Assign statement without value not caught");
428        assert!(fips_parser::statement("foo[123] = ").is_err(),
429            "Indexed assign statement without value not caught");
430        let parser_result = fips_parser::statement("foo = 1337")
431            .expect("Cannot parse valid assign statement");
432        assert_eq!(parser_result, Statement::Assign(AssignStatement { 
433            assignee: "foo".into(),
434            value: Expression::Atom(Atom::Literal(Literal::Int64(1337))),
435            index: None
436        }));
437        let parser_result = fips_parser::statement("foo[123] = 1337")
438            .expect("Cannot parse valid assign statement");
439        assert_eq!(parser_result, Statement::Assign(AssignStatement { 
440            assignee: "foo".into(),
441            value: Expression::Atom(Atom::Literal(Literal::Int64(1337))),
442            index: Some(CompileTimeConstant::Literal(123))
443        }));
444    }
445
446    #[test]
447    fn statements_update() {
448        assert!(fips_parser::statement("update").is_err(),
449            "Update statement without interaction name not caught");
450        let parser_result = fips_parser::statement("update myinteraction")
451            .expect("Cannot parse valid update statement without quantity");
452        assert_eq!(parser_result, Statement::Update(UpdateStatement {
453            interaction: "myinteraction".into(),
454            quantity: None
455        }));
456        let parser_result = fips_parser::statement("update myinteraction.myquantity")
457            .expect("Cannot parse valid update statement with quantity");
458        assert_eq!(parser_result, Statement::Update(UpdateStatement {
459            interaction: "myinteraction".into(),
460            quantity: Some("myquantity".into())
461        }));
462
463    }
464
465    #[test]
466    fn statements_call() {
467        assert!(fips_parser::statement("call").is_err(),
468            "Call statement without callback name not caught");
469        let parser_result = fips_parser::statement("call mycallback")
470            .expect("Cannot parse valid call statement");
471        assert_eq!(parser_result, Statement::Call(CallStatement {
472            name: "mycallback".into()
473        }));
474
475    }
476
477    #[test]
478    fn expression_atom() {
479        // Test invalid literals
480        assert!(fips_parser::expression("41foobar").is_err(),
481            "Invalid identifier atom not caught");
482        assert!(fips_parser::expression("41.2.3").is_err(),
483            "Invalid numeric literal not caught");
484        assert!(fips_parser::expression("9223372036854775808").is_err(),
485            "Overflowing integer literal not caught");
486        // Test integer literals
487        let integer_test_set: Vec<(_, i64)> = vec![
488            ("0", 0),
489            ("+0", 0),
490            ("-0", 0),
491            ("123", 123),
492            ("-123", -123),
493            ("(123)", 123),
494            ("(-123)", -123),
495            ("9223372036854775807", 9223372036854775807),
496            ("-9223372036854775808", -9223372036854775808)
497        ];
498        for (s,n) in integer_test_set {
499            assert_eq!(fips_parser::expression(s)
500                .expect(&format!("Cannot parse integer literal {} as expression", s)),
501                Expression::Atom(Atom::Literal(Literal::Int64(n)))
502            );
503        }
504        // Test floating point literals
505        let float_test_set = vec![
506            ("0.0", 0.0),
507            ("+0.0", 0.0),
508            ("-0.0", -0.0),
509            ("123.456", 123.456),
510            ("-123.456", -123.456),
511            ("123456e-3", 123456e-3),
512            ("123456.789e-3", 123456.789e-3),
513            ("inf", f64::INFINITY),
514            ("+inf", f64::INFINITY),
515            ("-inf", f64::NEG_INFINITY),
516            ("nan", f64::NAN),
517        ];
518        for (s,x) in float_test_set {
519            let parser_result = fips_parser::expression(s)
520                .expect(&format!("Cannot parse float literal {} as expression", s));
521            if x.is_nan() {
522                match parser_result {
523                    Expression::Atom(Atom::Literal(Literal::Double(y))) => {
524                        assert!(y.is_nan(), "Expected NaN but got {}", y);
525                    },
526                    _ => panic!("Expected Atom(Literal(Double(NaN))) but got {:?}", parser_result)
527                }
528            }
529            else {
530                assert_eq!(parser_result,
531                    Expression::Atom(Atom::Literal(Literal::Double(x)))
532                );
533            }
534        }
535        // Test identifiers
536        assert_eq!(fips_parser::expression("foobar")
537            .expect(&format!("Cannot parse identifier as expression")),
538            Expression::Atom(Atom::Variable("foobar".into()))
539        );
540        assert_eq!(fips_parser::expression("foo.bar")
541            .expect(&format!("Cannot parse identifier as expression")),
542            Expression::Atom(Atom::NamespaceVariable { namespace: "foo".into(), name: "bar".into() })
543        );
544    }
545
546    #[test]
547    fn expression_arrays() {
548        // Helper functions
549        let make_int_atom = |i| {
550            Expression::Atom(Atom::Literal(Literal::Int64(i)))
551        };
552        let make_ident_atom = |name: &str| {
553            Expression::Atom(Atom::Variable(name.into()))
554        };
555        // Test invalid ad-hoc arrays
556        assert!(fips_parser::expression("[1,2,3").is_err(),
557            "Unbalanced array brackets not caught");
558        assert!(fips_parser::expression("[,1,2,3]").is_err(),
559            "Missing array element at the start not caught");
560        assert!(fips_parser::expression("[1,2,,3]").is_err(),
561            "Missing array element inbetween not caught");
562        assert!(fips_parser::expression("[1,2,3,]").is_err(),
563            "Missing array element at the end not caught");
564        // Test valid ad-hoc arrays
565        assert_eq!(fips_parser::expression("[]")
566            .expect(&format!("Cannot parse empty array")),
567            Expression::AdHocArray(AdHocArrayExpression { elements: vec![] })
568        );
569        assert_eq!(fips_parser::expression("[1]")
570            .expect(&format!("Cannot parse one-element array")),
571            Expression::AdHocArray(AdHocArrayExpression {
572                elements: vec![make_int_atom(1)] 
573            })
574        );
575        assert_eq!(fips_parser::expression("[1,2,a,b]")
576        .expect(&format!("Cannot parse multi-element array")),
577        Expression::AdHocArray(AdHocArrayExpression {
578            elements: vec![
579                make_int_atom(1), make_int_atom(2),
580                make_ident_atom("a"), make_ident_atom("b")
581            ]})
582        );
583    }
584
585    #[test]
586    fn expression_indexing() {
587        // Test invalid array index access
588        assert!(fips_parser::expression("myarr[]").is_err(),
589            "Empty array index not caught");
590        assert!(fips_parser::expression("myarr[-1]").is_err(),
591            "Negative array index not caught");
592        // Test valid indexing expressions
593        assert_eq!(fips_parser::expression("foo[0]")
594            .expect(&format!("Cannot parse valid array indexing")),
595            Expression::Indexing(AtIndex {
596                array: "foo".into(),
597                index: CompileTimeConstant::Literal(0)
598            })
599        );
600    }
601
602    #[test]
603    fn expression_identifier_namespace() {
604        // Test invalid identifier namespacing
605        assert!(fips_parser::expression("myparticle.").is_err(),
606            "Missing identifier not caught");
607        assert!(fips_parser::expression(".myident").is_err(),
608            "Missing namespace not caught");
609        assert!(fips_parser::expression(".").is_err(),
610            "Missing identifier and namespace not caught");
611        // Test valid namespacing
612        assert_eq!(fips_parser::expression("myparticle.myident")
613            .expect(&format!("Cannot parse empty array")),
614            Expression::Atom(Atom::NamespaceVariable {
615                namespace: "myparticle".into(),
616                name: "myident".into()
617            })
618        );
619    }
620
621    #[test]
622    fn expression_function_call() {
623        // Test invalid function calls
624        assert!(fips_parser::expression("foo(").is_err(),
625            "Missing closing parenthesis not caught");
626        assert!(fips_parser::expression("foo(bar,").is_err(),
627            "Missing closing parenthesis with parameters not caught");
628        assert!(fips_parser::expression("foo(bar,)").is_err(),
629            "Missing function argument not caught");
630        assert!(fips_parser::expression("foo)").is_err(),
631            "Missing opening parenthesis not caught");
632        // Test valid function call
633        assert_eq!(fips_parser::expression("foo(bar)")
634            .expect(&format!("Cannot parse empty array")),
635            Expression::FunctionCall(FunctionCall {
636                fn_name: "foo".into(),
637                parameters: vec![
638                    Expression::Atom(Atom::Variable("bar".into()))
639                ]
640            })
641        );
642    }
643
644    #[ignore]
645    #[test]
646    fn euler() {
647        let input = r#"// Simple Euler integrator for point-like particles
648
649// Format for a particle:
650particle PointLike {
651    // Position is special and cannot be redefined, but aliased (array specifier is not allowed,
652    // as position is always NDIM dimensional)
653    x : mut position,
654    v : mut [f64; NDIM], // NDIM is dimensionality of problem
655    F : mut [f64; NDIM], // Quantities bound to interaction calculations later also need to be declared
656    mass: f64            // Constants have to be defined in Rust code (can be either per-particle or
657                         // per-type)
658}
659
660// Particles can also inherit members
661particle Orientable extends PointLike {
662    phi : mut [f64; ROTDIM], // ROTDIM is user defined (1 for 2D, 3 for 3D)
663    omega : mut [f64; ROTDIM],
664    torque : mut [f64; ROTDIM],
665    inertia : f64
666}
667
668// Format for an interaction block:
669// The "dyn" marks an interaction between the first type and all types extending the second
670// (similar to the meaning of dyn in Rust)
671// Here we also bind names to the two interacting particles so we can access their members
672// (i.e. the carge for E/M interactions)
673interaction myinteraction (p1: PointLike, p2: dyn PointLike) for r < CUTOFF {
674    // Sole argument is the distance (commonly used and already calculated in neighbour checking)
675    // (p1 and p2 can be used too, obviously)
676    // Syntax: quantity <name> -[<reduction method>]-> <member in first>, <member in second> { <body> }
677    quantity myforce -[sum]-> (F, F) {
678        // Rust like expression syntax
679        4.0*EPSILON*((SIGMA/r))
680    }
681}
682// Interactions are always mutual (this is enforced in syntax to avoid accidental violations of e.g.
683// Newton's third law)!
684
685// Types of value reduction:
686// * sum: (Vector) sum all contributions together
687
688// Simulation block
689simulation MySim {
690    // Indicate that PointLike is the default particle for step blocks
691    default particle PointLike;
692    // Once blocks are executed once in the given timestep
693    once 0 {
694
695    }
696    // Step blocks contain a single time step
697    // Multiple step blocks can be defined in one simulation, so they are interleaved as necessary
698    // Step blocks are executed every x timesteps (default is 1, i.e. every timestep):
699    // step x {...}
700    // If multiple step blocks are to be executed in the same timestep, they are executed in the order of definition
701    step { 
702        // This block can be omitted, since PointLike is the default particle type
703        particle PointLike {
704            update myinteraction; // This causes all quantities of myinteraction to be updated across all processors
705            // All members of the particle are accessible without extra scoping
706            // Members bound in interactions are technically write-accessible, but this should issue a warning
707            // since their value will be overwritten by any update command
708            v = v + F / m * DT; // Euler step for velocity (DT is the timestep constant)
709            x = x + v * DT;
710        }
711    }
712}"#;
713        
714        fips_parser::unit(input).expect("Cannot parse input");
715    }
716}