1
1
use crate :: _aeon_parser:: { FnUpdateTemp , RegulationTemp } ;
2
- use crate :: { BooleanNetwork , Parameter , RegulatoryGraph } ;
2
+ use crate :: { BooleanNetwork , ModelAnnotation , Parameter , RegulatoryGraph } ;
3
3
use regex:: Regex ;
4
- use std:: collections:: HashSet ;
4
+ use std:: collections:: { HashMap , HashSet } ;
5
5
use std:: convert:: TryFrom ;
6
6
7
7
impl TryFrom < & str > for BooleanNetwork {
8
8
type Error = String ;
9
9
10
10
fn try_from ( value : & str ) -> Result < Self , Self :: Error > {
11
+ // This parsing should never fail, so it should be safe to do this...
12
+ let annotations = ModelAnnotation :: from_model_string ( value) ;
13
+
14
+ // If the model is requesting a declaration check, we should perform it. Otherwise,
15
+ // declarations are only informative.
16
+ let check_declarations = annotations
17
+ . get_child ( & [ "check_declarations" ] )
18
+ . and_then ( |it| it. value ( ) )
19
+ . map ( |it| it. as_str ( ) == "true" )
20
+ . unwrap_or ( false ) ;
21
+
22
+ // Read declared variables. Variable is declared either as a string name in the "variable"
23
+ // annotation, or by a corresponding child annotation.
24
+ let expected_variables = if let Some ( decl) = annotations. get_child ( & [ "variable" ] ) {
25
+ let mut data = decl. read_multiline_value ( ) ;
26
+ for ( child, _) in decl. children ( ) {
27
+ data. push ( child. clone ( ) ) ;
28
+ }
29
+ data
30
+ } else {
31
+ Vec :: new ( )
32
+ } ;
33
+
34
+ // Try to read parameter declarations from the model annotation data. A parameter is
35
+ // declared if it is present as a name inside "function" annotation, or if it is present
36
+ // as one of its children. If arity is not present, it is zero.
37
+ let expected_parameters = if let Some ( decl) = annotations. get_child ( & [ "function" ] ) {
38
+ let all_names = decl. read_multiline_value ( ) ;
39
+ let mut expected = HashMap :: new ( ) ;
40
+ for name in all_names {
41
+ let arity = decl
42
+ . get_value ( & [ name. as_str ( ) , "arity" ] )
43
+ . cloned ( )
44
+ . unwrap_or_else ( || "0" . to_string ( ) ) ;
45
+ expected. insert ( name, arity) ;
46
+ }
47
+ for ( child, data) in decl. children ( ) {
48
+ if !expected. contains_key ( child) {
49
+ let arity = data
50
+ . get_value ( & [ "arity" ] )
51
+ . cloned ( )
52
+ . unwrap_or_else ( || "0" . to_string ( ) ) ;
53
+ expected. insert ( child. clone ( ) , arity) ;
54
+ }
55
+ }
56
+ expected
57
+ } else {
58
+ HashMap :: new ( )
59
+ } ;
60
+
61
+ if ( !expected_variables. is_empty ( ) || !expected_parameters. is_empty ( ) )
62
+ && !check_declarations
63
+ {
64
+ eprintln ! ( "WARNING: Network contains variable or function declarations, but integrity checking is turned off." ) ;
65
+ }
66
+
11
67
// trim lines and remove comments
12
68
let lines = value. lines ( ) . filter_map ( |l| {
13
69
let line = l. trim ( ) ;
@@ -50,6 +106,25 @@ impl TryFrom<&str> for BooleanNetwork {
50
106
let mut variable_names: Vec < String > = variable_names. into_iter ( ) . collect ( ) ;
51
107
variable_names. sort ( ) ;
52
108
109
+ // If a variable is declared, but not present in the graph, we can still create it.
110
+ // But if it is present yet not declared, that's a problem.
111
+ if check_declarations {
112
+ for var in & variable_names {
113
+ if !expected_variables. contains ( var) {
114
+ return Err ( format ! (
115
+ "Variable `{}` used, but not declared in annotations." ,
116
+ var
117
+ ) ) ;
118
+ }
119
+ }
120
+ for var in & expected_variables {
121
+ if !variable_names. contains ( var) {
122
+ variable_names. push ( var. clone ( ) ) ;
123
+ }
124
+ }
125
+ variable_names. sort ( ) ;
126
+ }
127
+
53
128
let mut rg = RegulatoryGraph :: new ( variable_names) ;
54
129
55
130
for reg in regulations {
@@ -76,9 +151,35 @@ impl TryFrom<&str> for BooleanNetwork {
76
151
77
152
// Add the parameters (if there is a cardinality clash, here it will be thrown).
78
153
for parameter in & parameters {
154
+ if check_declarations {
155
+ if let Some ( expected_arity) = expected_parameters. get ( & parameter. name ) {
156
+ if & format ! ( "{}" , parameter. arity) != expected_arity {
157
+ return Err ( format ! (
158
+ "Parameter `{}` is declared with arity `{}`, but used with arity `{}`." ,
159
+ parameter. name, expected_arity, parameter. arity
160
+ ) ) ;
161
+ }
162
+ } else {
163
+ return Err ( format ! (
164
+ "Network has parameter `{}` that is not declared in annotations." ,
165
+ parameter. name
166
+ ) ) ;
167
+ }
168
+ }
79
169
bn. add_parameter ( & parameter. name , parameter. arity ) ?;
80
170
}
81
171
172
+ if check_declarations {
173
+ for param_name in expected_parameters. keys ( ) {
174
+ if bn. find_parameter ( param_name) . is_none ( ) {
175
+ return Err ( format ! (
176
+ "Parameter `{}` declared in annotations, but not found in the network." ,
177
+ param_name
178
+ ) ) ;
179
+ }
180
+ }
181
+ }
182
+
82
183
// Actually build and add the functions
83
184
for ( name, function) in update_functions {
84
185
bn. add_template_update_function ( & name, function) ?;
@@ -199,19 +300,122 @@ mod tests {
199
300
200
301
#[ test]
201
302
fn test_bn_from_and_to_string ( ) {
202
- let bn_string = "a -> b
303
+ // Without parameters:
304
+ let bn_string = format ! (
305
+ "#!check_declarations:true
306
+ #!variable:a
307
+ #!variable:b
308
+ #!variable:c
309
+ #!variable:d
310
+ #!version:lib_param_bn:{}
311
+
312
+ a -> b
313
+ a -?? a
314
+ b -|? c
315
+ c -? a
316
+ c -> d
317
+ $a: a & !c
318
+ $b: a
319
+ $c: !b
320
+ " ,
321
+ env!( "CARGO_PKG_VERSION" )
322
+ ) ;
323
+
324
+ assert_eq ! (
325
+ bn_string,
326
+ BooleanNetwork :: try_from( bn_string. as_str( ) )
327
+ . unwrap( )
328
+ . to_string( )
329
+ ) ;
330
+
331
+ // With parameters:
332
+ let bn_string = format ! (
333
+ "#!check_declarations:true
334
+ #!function:k:arity:0
335
+ #!function:p:arity:1
336
+ #!function:q:arity:2
337
+ #!variable:a
338
+ #!variable:b
339
+ #!variable:c
340
+ #!variable:d
341
+ #!version:lib_param_bn:{}
342
+
343
+ a -> b
203
344
a -?? a
204
345
b -|? c
205
346
c -? a
206
347
c -> d
207
348
$a: a & (p(c) => (c | c))
208
349
$b: p(a) <=> q(a, a)
209
350
$c: q(b, b) => !(b ^ k)
210
- " ;
351
+ " ,
352
+ env!( "CARGO_PKG_VERSION" )
353
+ ) ;
211
354
212
355
assert_eq ! (
213
356
bn_string,
214
- BooleanNetwork :: try_from( bn_string) . unwrap( ) . to_string( )
357
+ BooleanNetwork :: try_from( bn_string. as_str( ) )
358
+ . unwrap( )
359
+ . to_string( )
215
360
) ;
216
361
}
362
+
363
+ #[ test]
364
+ fn test_bn_with_parameter_declarations ( ) {
365
+ let bn_string = r"
366
+ #! check_declarations:true
367
+ #! function: f: arity: 2
368
+ #! variable: a
369
+ #! variable: b
370
+ #! variable: x
371
+
372
+ a -> x
373
+ b -> x
374
+ $x: f(a, b)
375
+ " ;
376
+ assert ! ( BooleanNetwork :: try_from( bn_string) . is_ok( ) ) ;
377
+
378
+ // Wrong arity
379
+ let bn_string = r"
380
+ #! check_declarations:true
381
+ #! function: f: arity: 1
382
+ #! variable: a
383
+ #! variable: b
384
+ #! variable: x
385
+
386
+ a -> x
387
+ b -> x
388
+ $x: f(a, b)
389
+ " ;
390
+ assert ! ( BooleanNetwork :: try_from( bn_string) . is_err( ) ) ;
391
+
392
+ // Unused declaration
393
+ let bn_string = r"
394
+ #! check_declarations:true
395
+ #! function: f: arity: 2
396
+ #! function: g: arity: 1
397
+ #! variable: a
398
+ #! variable: b
399
+ #! variable: x
400
+
401
+ a -> x
402
+ b -> x
403
+ $x: f(a, b)
404
+ " ;
405
+ assert ! ( BooleanNetwork :: try_from( bn_string) . is_err( ) ) ;
406
+
407
+ // Parameter not declared
408
+ let bn_string = r"
409
+ #! check_declarations:true
410
+ #! function: f: arity: 2
411
+ #! variable: a
412
+ #! variable: b
413
+ #! variable: x
414
+
415
+ a -> x
416
+ b -> x
417
+ $x: g(a, b)
418
+ " ;
419
+ assert ! ( BooleanNetwork :: try_from( bn_string) . is_err( ) ) ;
420
+ }
217
421
}
0 commit comments