1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4#
5# Copyright 2017, Data61
6# Commonwealth Scientific and Industrial Research Organisation (CSIRO)
7# ABN 41 687 119 230.
8#
9# This software may be distributed and modified according to the terms of
10# the BSD 2-Clause license. Note that NO WARRANTY is provided.
11# See "LICENSE_BSD2.txt" for details.
12#
13# @TAG(DATA61_BSD)
14#
15
16from __future__ import absolute_import, division, print_function, \
17    unicode_literals
18
19import os, six, sys, unittest
20
21ME = os.path.abspath(__file__)
22
23# Make CAmkES importable
24sys.path.append(os.path.join(os.path.dirname(ME), '../../..'))
25
26from camkes.ast import Component, Configuration, Include, LiftedAST, \
27    Procedure, Setting
28from camkes.internal.tests.utils import CAmkESTest, cpp_available, \
29    plyplus_introspectible
30from camkes.parser.stage0 import CPP, Reader
31from camkes.parser.stage1 import Parse1
32from camkes.parser.stage2 import Parse2
33from camkes.parser.stage3 import DONT_LIFT, LIFT, Parse3
34from camkes.parser import ParseError
35
36class TestStage3(CAmkESTest):
37    def setUp(self):
38        super(TestStage3, self).setUp()
39        r = Reader()
40        s1 = Parse1(r)
41        s2 = Parse2(s1)
42        self.parser = Parse3(s2, debug=True)
43
44        r = CPP()
45        s1 = Parse1(r)
46        s2 = Parse2(s1)
47        self.cpp_parser = Parse3(s2, debug=True)
48
49    def test_empty_string(self):
50        content, read = self.parser.parse_string('')
51
52        self.assertIsInstance(content, LiftedAST)
53        self.assertEqual(content.children, [])
54        self.assertLen(read, 0)
55
56    def test_basic_entity(self):
57        content, read = self.parser.parse_string('component foo {}')
58
59        self.assertIsInstance(content, LiftedAST)
60        self.assertLen(content.items, 1)
61        comp = content.items[0]
62
63        self.assertIsInstance(comp, Component)
64        self.assertEqual(comp.name, 'foo')
65
66    def test_malformed(self):
67        with self.assertRaises(ParseError):
68            self.parser.parse_string('hello world')
69
70    def test_numerics_basic(self):
71        content, read = self.parser.parse_string(
72            'configuration { hello.world = 1 + 4 - 2; }')
73
74        self.assertIsInstance(content, LiftedAST)
75        self.assertLen(content.items, 1)
76        conf = content.items[0]
77
78        self.assertIsInstance(conf, Configuration)
79        self.assertLen(conf.settings, 1)
80        setting = conf.settings[0]
81
82        self.assertIsInstance(setting, Setting)
83        self.assertEqual(setting.instance, 'hello')
84        self.assertEqual(setting.attribute, 'world')
85        self.assertEqual(setting.value, 3)
86
87    def test_numerics_precedence(self):
88        content, read = self.parser.parse_string(
89            'configuration { hello.world = 1 + 4 * 2; }')
90
91        self.assertIsInstance(content, LiftedAST)
92        self.assertLen(content.items, 1)
93        conf = content.items[0]
94
95        self.assertIsInstance(conf, Configuration)
96        self.assertLen(conf.settings, 1)
97        setting = conf.settings[0]
98
99        self.assertIsInstance(setting, Setting)
100        self.assertEqual(setting.instance, 'hello')
101        self.assertEqual(setting.attribute, 'world')
102        # If the following fails with `10`, you've screwed up operator
103        # precedence in the grammar.
104        self.assertEqual(setting.value, 9,
105            'operator precedence incorrect when constant folding')
106
107    def test_numerics_bracketing(self):
108        content, read = self.parser.parse_string(
109            'configuration { hello.world = (1 + 4) * 2; }')
110
111        self.assertIsInstance(content, LiftedAST)
112        self.assertLen(content.items, 1)
113        conf = content.items[0]
114
115        self.assertIsInstance(conf, Configuration)
116        self.assertLen(conf.settings, 1)
117        setting = conf.settings[0]
118
119        self.assertIsInstance(setting, Setting)
120        self.assertEqual(setting.instance, 'hello')
121        self.assertEqual(setting.attribute, 'world')
122        self.assertEqual(setting.value, 10)
123
124    def test_numerics_bracketing2(self):
125        content, read = self.parser.parse_string(
126            'configuration { hello.world = 2 * (1 + 4); }')
127
128        self.assertIsInstance(content, LiftedAST)
129        self.assertLen(content.items, 1)
130        conf = content.items[0]
131
132        self.assertIsInstance(conf, Configuration)
133        self.assertLen(conf.settings, 1)
134        setting = conf.settings[0]
135
136        self.assertIsInstance(setting, Setting)
137        self.assertEqual(setting.instance, 'hello')
138        self.assertEqual(setting.attribute, 'world')
139        self.assertEqual(setting.value, 10)
140
141    def test_numerics_illegal_op(self):
142        with self.assertRaises(ParseError):
143            self.parser.parse_string(
144                'configuration { hello.world = 2.0 << 1; }')
145
146    def test_unicode(self):
147        content, read = self.parser.parse_string('component fo�� {}')
148
149        self.assertIsInstance(content, LiftedAST)
150        self.assertLen(content.items, 1)
151        comp = content.items[0]
152
153        self.assertIsInstance(comp, Component)
154        self.assertEqual(comp.name, 'fo��')
155        self.assertLen(read, 0)
156
157    def test_from_file(self):
158        tmp = self.mkstemp()
159        with open(tmp, 'wt') as f:
160            f.write('component foo {}')
161
162        content, read = self.parser.parse_file(tmp)
163
164        self.assertIn(tmp, read)
165        self.assertIsInstance(content, LiftedAST)
166        self.assertLen(content.items, 1)
167        comp = content.items[0]
168
169        self.assertIsInstance(comp, Component)
170        self.assertEqual(comp.name, 'foo')
171        self.assertEqual(comp.filename, tmp)
172
173    @unittest.skipIf(not cpp_available(), 'CPP not found')
174    def test_with_cpp(self):
175        parent = self.mkstemp()
176        child = self.mkstemp()
177
178        with open(parent, 'wt') as f:
179            f.write('component foo\n#include "%s"' % child)
180        with open(child, 'wt') as f:
181            f.write('{}')
182
183        content, read = self.cpp_parser.parse_file(parent)
184
185        self.assertIn(parent, read)
186        self.assertIn(child, read)
187        self.assertIsInstance(content, LiftedAST)
188        self.assertLen(content.items, 1)
189        comp = content.items[0]
190
191        self.assertIsInstance(comp, Component)
192        self.assertEqual(comp.name, 'foo')
193        self.assertEqual(comp.filename, parent)
194
195    def test_lineno_basic(self):
196        content, _ = self.parser.parse_string('component foo {}')
197
198        self.assertIsInstance(content, LiftedAST)
199        self.assertLen(content.items, 1)
200        comp = content.items[0]
201
202        self.assertIsNone(comp.filename)
203        self.assertEqual(comp.lineno, 1)
204
205    def test_lineno_basic2(self):
206        content, _ = self.parser.parse_string('\ncomponent foo {}')
207
208        self.assertIsInstance(content, LiftedAST)
209        self.assertLen(content.items, 1)
210        comp = content.items[0]
211
212        self.assertIsNone(comp.filename)
213        self.assertEqual(comp.lineno, 2)
214
215    def test_lineno_basic3(self):
216        content, _ = self.parser.parse_string('component\nfoo {}')
217
218        self.assertIsInstance(content, LiftedAST)
219        self.assertLen(content.items, 1)
220        comp = content.items[0]
221
222        self.assertIsNone(comp.filename)
223        self.assertIn(comp.lineno, (1, 2))
224
225    def test_filename_in_malformed_error(self):
226        tmp = self.mkstemp()
227        with open(tmp, 'wt') as f:
228            f.write('\nhello world')
229
230        with six.assertRaisesRegex(self, ParseError, '%s:2:.*' % tmp):
231            self.parser.parse_file(tmp)
232
233    @unittest.skipIf(not cpp_available(), 'CPP not found')
234    def test_lineno_basic_cpp(self):
235        content, _ = self.cpp_parser.parse_string('component foo {}')
236
237        self.assertIsInstance(content, LiftedAST)
238        self.assertLen(content.items, 1)
239        comp = content.items[0]
240
241        self.assertEqual(comp.filename, '<stdin>')
242        self.assertEqual(comp.lineno, 1)
243
244    @unittest.skipIf(not cpp_available(), 'CPP not found')
245    def test_lineno_basic2_cpp(self):
246        content, _ = self.cpp_parser.parse_string('\ncomponent foo {}')
247
248        self.assertIsInstance(content, LiftedAST)
249        self.assertLen(content.items, 1)
250        comp = content.items[0]
251
252        self.assertEqual(comp.filename, '<stdin>')
253        self.assertEqual(comp.lineno, 2)
254
255    @unittest.skipIf(not cpp_available(), 'CPP not found')
256    def test_lineno_basic3_cpp(self):
257        content, _ = self.cpp_parser.parse_string('component\nfoo {}')
258
259        self.assertIsInstance(content, LiftedAST)
260        self.assertLen(content.items, 1)
261        comp = content.items[0]
262
263        self.assertEqual(comp.filename, '<stdin>')
264        self.assertIn(comp.lineno, (1, 2))
265
266    @unittest.skipIf(not cpp_available(), 'CPP not found')
267    def test_lineno_basic4_cpp(self):
268        tmp = self.mkstemp()
269        with open(tmp, 'wt') as f:
270            f.write('component foo {}')
271
272        content, _ = self.cpp_parser.parse_file(tmp)
273
274        self.assertIsInstance(content, LiftedAST)
275        self.assertLen(content.items, 1)
276        comp = content.items[0]
277
278        self.assertEqual(comp.filename, tmp)
279        self.assertEqual(comp.lineno, 1)
280
281    @unittest.skipIf(not cpp_available(), 'CPP not found')
282    def test_lineno_with_cpp_include(self):
283        parent = self.mkstemp()
284        child = self.mkstemp()
285
286        with open(parent, 'wt') as f:
287            f.write('\n#include "%s"' % child)
288        with open(child, 'wt') as f:
289            f.write('\ncomponent foo {}')
290
291        content, _ = self.cpp_parser.parse_file(parent)
292
293        self.assertIsInstance(content, LiftedAST)
294        self.assertLen(content.items, 1)
295        comp = content.items[0]
296
297        self.assertEqual(comp.filename, child)
298        self.assertEqual(comp.lineno, 2)
299
300    def test_simple_spec_complete(self):
301        content, _ = self.parser.parse_string('''
302            procedure Hello {
303                void hello(void);
304            }
305
306            component Foo {
307                provides Hello h;
308            }
309
310            component Bar {
311                control;
312                uses Hello w;
313            }
314
315            assembly {
316                composition {
317                    component Foo f;
318                    component Bar b;
319                    connection Conn conn(from Foo.h, to Bar.w);
320                }
321            }
322            ''')
323
324    @unittest.skipIf(not plyplus_introspectible(), 'plyplus internals not as '
325        'expected')
326    def test_for_missing_lifters(self):
327        '''
328        Check that stage 3 looks comprehensive.
329
330        The stage 3 parser is designed to translate an augmented AST into a
331        lifted AST. In order to do this, it needs to have specific translation
332        code for each element that may appear in plyplus' emitted AST. The
333        connection between [the CAmkES grammar](../camkes.g) and the stage 3
334        lifting code is necessarily informal. That is, there is no built-in
335        checking mechanism that the stage 3 parser is able to handle everything
336        that could appear in the AST.
337
338        What we do in this test is use plyplus' own internal parser to examine
339        the CAmkES grammar and, for each parsing rule that could generate an
340        entity in the augmented AST, we check that there is corresponding code
341        in the stage 3 parser to handle lifting this entity.
342        '''
343
344        # Parse the CAmkES grammar using plyplus' internals.
345        from plyplus.grammar_parser import parse
346        grammar = os.path.join(os.path.dirname(ME), '../camkes.g')
347        with open(grammar, 'rt') as f:
348            camkes_grammar = parse(f.read())
349
350        # Every type of augmented AST entity should be either lifted by a
351        # a function in the dispatch table `LIFT` or returned unmodified due to
352        # an entry in `DONT_LIFT`.
353        lifters = list(DONT_LIFT) + list(LIFT)
354
355        for ruledef in camkes_grammar.select('ruledef'):
356
357            name = ruledef.tail[0]
358
359            if name == 'start':
360                # Ignore the grammar root.
361                continue
362
363            if name.startswith('@'):
364                # This rule is not intended to appear in the AST.
365                continue
366
367            if name == 'import':
368                # Import statements are handled in stage 2 and never expected
369                # to make it to stage 3.
370                continue
371
372            self.assertIn(name, lifters, '%s, that could appear in the AST, '
373                'does not appear to be handled by any of the lifters in stage '
374                '3' % name)
375
376    def test_signed_int(self):
377        '''
378        Signed int parsing was broken in an early iteration of the parser. Test
379        that we haven't re-introduced this issue.
380        '''
381        content, _ = self.parser.parse_string('''
382            procedure P {
383                void f(in signed int x);
384                signed int g(void);
385                signed int h(in signed int x);
386            }
387            ''')
388
389        self.assertLen(content.children, 1)
390        P = content.children[0]
391        self.assertIsInstance(P, Procedure)
392
393        self.assertLen(P.methods, 3)
394        f, g, h = P.methods
395
396        self.assertIsNone(f.return_type)
397        self.assertLen(f.parameters, 1)
398        self.assertEqual(f.parameters[0].type, 'int')
399
400        self.assertEqual(g.return_type, 'int')
401        self.assertLen(g.parameters, 0)
402
403        self.assertEqual(h.return_type, 'int')
404        self.assertLen(h.parameters, 1)
405        self.assertEqual(h.parameters[0].type, 'int')
406
407    def test_char(self):
408        content, _ = self.parser.parse_string('''
409            procedure P {
410                void f(in char x);
411                char g(void);
412                char h(in char x);
413            }
414            ''')
415
416        self.assertLen(content.children, 1)
417        P = content.children[0]
418        self.assertIsInstance(P, Procedure)
419
420        self.assertLen(P.methods, 3)
421        f, g, h = P.methods
422
423        self.assertIsNone(f.return_type)
424        self.assertLen(f.parameters, 1)
425        self.assertEqual(f.parameters[0].type, 'char')
426
427        self.assertEqual(g.return_type, 'char')
428        self.assertLen(g.parameters, 0)
429
430        self.assertEqual(h.return_type, 'char')
431        self.assertLen(h.parameters, 1)
432        self.assertEqual(h.parameters[0].type, 'char')
433
434    def test_signed_char(self):
435        content, _ = self.parser.parse_string('''
436            procedure P {
437                void f(in signed char x);
438                signed char g(void);
439                signed char h(in signed char x);
440            }
441            ''')
442
443        self.assertLen(content.children, 1)
444        P = content.children[0]
445        self.assertIsInstance(P, Procedure)
446
447        self.assertLen(P.methods, 3)
448        f, g, h = P.methods
449
450        self.assertIsNone(f.return_type)
451        self.assertLen(f.parameters, 1)
452        self.assertEqual(f.parameters[0].type, 'signed char')
453
454        self.assertEqual(g.return_type, 'signed char')
455        self.assertLen(g.parameters, 0)
456
457        self.assertEqual(h.return_type, 'signed char')
458        self.assertLen(h.parameters, 1)
459        self.assertEqual(h.parameters[0].type, 'signed char')
460
461    def test_unsigned_char(self):
462        content, _ = self.parser.parse_string('''
463            procedure P {
464                void f(in unsigned char x);
465                unsigned char g(void);
466                unsigned char h(in unsigned char x);
467            }
468            ''')
469
470        self.assertLen(content.children, 1)
471        P = content.children[0]
472        self.assertIsInstance(P, Procedure)
473
474        self.assertLen(P.methods, 3)
475        f, g, h = P.methods
476
477        self.assertIsNone(f.return_type)
478        self.assertLen(f.parameters, 1)
479        self.assertEqual(f.parameters[0].type, 'unsigned char')
480
481        self.assertEqual(g.return_type, 'unsigned char')
482        self.assertLen(g.parameters, 0)
483
484        self.assertEqual(h.return_type, 'unsigned char')
485        self.assertLen(h.parameters, 1)
486        self.assertEqual(h.parameters[0].type, 'unsigned char')
487
488    def test_line_number_preservation(self):
489        content, _ = self.parser.parse_string('''
490            /* multiline comment
491             * that may affect line numbering
492             * if we've messed up
493             */
494            procedure P {}
495            ''')
496
497        self.assertLen(content.children, 1)
498        P = content.children[0]
499        self.assertEqual(P.lineno, 6)
500
501    def test_illegal_import(self):
502        '''
503        There was a bug in an early implementation of the parser that caused
504        back-to-front export statements to trigger an assertion failure. This
505        test validates that these correctly trigger a parser error.
506        '''
507        spec_bad = '''
508            component {
509                composition {
510                    export a -> b.c;
511                }
512            }
513        '''
514
515        with six.assertRaisesRegex(self, ParseError, 'illegal source in export '
516                'statement'):
517            self.parser.parse_string(spec_bad)
518
519        spec_good = '''
520            component {
521                composition {
522                    export a.b -> c;
523                }
524            }
525        '''
526        self.parser.parse_string(spec_good)
527
528    def test_division_by_zero(self):
529        '''
530        Test that dividing by zero in a spec is correctly caught.
531        '''
532        with six.assertRaisesRegex(self, ParseError, 'division.*by zero'):
533            self.parser.parse_string('configuration { foo.bar = 1 / 0; }')
534
535    def test_modulo_by_zero(self):
536        '''
537        Test that modulo by zero in a spec is correctly caught.
538        '''
539        with six.assertRaisesRegex(self, ParseError, 'modulo.*by zero'):
540            self.parser.parse_string('configuration { foo.bar = 1 % 0; }')
541
542    def test_dataport_variant_syntax(self):
543        '''
544        Test that the syntax for variant dataports is accepted by the parser.
545        '''
546        spec = '''component Foo {
547                dataport Buf(4000) foo;
548            }'''
549        self.parser.parse_string(spec)
550
551    def test_dataport_variant_arithmetic(self):
552        '''
553        Test we can do arithmetic inside dataport variant specs.
554        '''
555        spec = '''component Foo {
556                dataport Buf (123 * 45/5) foo;
557            }'''
558        self.parser.parse_string(spec)
559
560    def test_flooring_division(self):
561        '''
562        Test that constant folding of division of two integers correctly
563        produces an integer.
564        '''
565        content, _ = self.parser.parse_string(
566            'configuration { foo.bar = 3 / 2; }')
567
568        self.assertLen(content.children, 1)
569        conf = content.children[0]
570        self.assertIsInstance(conf, Configuration)
571
572        self.assertLen(conf.settings, 1)
573        s = conf.settings[0]
574        self.assertIsInstance(s, Setting)
575
576        self.assertEqual(s.instance, 'foo')
577        self.assertEqual(s.attribute, 'bar')
578        self.assertEqual(s.value, 1)
579
580    def test_floating_point_division1(self):
581        '''
582        Test that constant folding of a division by a float produces the
583        correct float result.
584        '''
585        content, _ = self.parser.parse_string(
586            'configuration { foo.bar = 3 / 2.0; }')
587
588        self.assertLen(content.children, 1)
589        conf = content.children[0]
590        self.assertIsInstance(conf, Configuration)
591
592        self.assertLen(conf.settings, 1)
593        s = conf.settings[0]
594        self.assertIsInstance(s, Setting)
595
596        self.assertEqual(s.instance, 'foo')
597        self.assertEqual(s.attribute, 'bar')
598        self.assertEqual(s.value, 1.5)
599
600    def test_floating_point_division2(self):
601        '''
602        Test that constant folding of a division of a float produces the
603        correct float result.
604        '''
605        content, _ = self.parser.parse_string(
606            'configuration { foo.bar = 3.0 / 2; }')
607
608        self.assertLen(content.children, 1)
609        conf = content.children[0]
610        self.assertIsInstance(conf, Configuration)
611
612        self.assertLen(conf.settings, 1)
613        s = conf.settings[0]
614        self.assertIsInstance(s, Setting)
615
616        self.assertEqual(s.instance, 'foo')
617        self.assertEqual(s.attribute, 'bar')
618        self.assertEqual(s.value, 1.5)
619
620    def test_floating_point_division3(self):
621        '''
622        Test that constant folding of a division involving two floats produces
623        the correct float result.
624        '''
625        content, _ = self.parser.parse_string(
626            'configuration { foo.bar = 3.0 / 2.0; }')
627
628        self.assertLen(content.children, 1)
629        conf = content.children[0]
630        self.assertIsInstance(conf, Configuration)
631
632        self.assertLen(conf.settings, 1)
633        s = conf.settings[0]
634        self.assertIsInstance(s, Setting)
635
636        self.assertEqual(s.instance, 'foo')
637        self.assertEqual(s.attribute, 'bar')
638        self.assertEqual(s.value, 1.5)
639
640    def test_ternary_conditional_basic(self):
641        '''
642        Test that the ternary conditional works as expected.
643        '''
644        content, _ = self.parser.parse_string(
645            'configuration { foo.bar = 1 ? 2 : 3; }')
646
647        self.assertLen(content.children, 1)
648        conf = content.children[0]
649        self.assertIsInstance(conf, Configuration)
650
651        self.assertLen(conf.settings, 1)
652        s = conf.settings[0]
653        self.assertIsInstance(s, Setting)
654
655        self.assertEqual(s.instance, 'foo')
656        self.assertEqual(s.attribute, 'bar')
657        self.assertEqual(s.value, 2)
658
659        content, _ = self.parser.parse_string(
660            'configuration { foo.bar = 0 ? 2 : 3; }')
661
662        self.assertLen(content.children, 1)
663        conf = content.children[0]
664        self.assertIsInstance(conf, Configuration)
665
666        self.assertLen(conf.settings, 1)
667        s = conf.settings[0]
668        self.assertIsInstance(s, Setting)
669
670        self.assertEqual(s.instance, 'foo')
671        self.assertEqual(s.attribute, 'bar')
672        self.assertEqual(s.value, 3)
673
674    def test_ternary_conditional_extended(self):
675        '''
676        Test the ternary conditional with some more complicated expressions
677        inside it.
678        '''
679        content, _ = self.parser.parse_string(
680            'configuration { foo.bar = (1 || 0) ? 2 : 3; }')
681
682        self.assertLen(content.children, 1)
683        conf = content.children[0]
684        self.assertIsInstance(conf, Configuration)
685
686        self.assertLen(conf.settings, 1)
687        s = conf.settings[0]
688        self.assertIsInstance(s, Setting)
689
690        self.assertEqual(s.instance, 'foo')
691        self.assertEqual(s.attribute, 'bar')
692        self.assertEqual(s.value, 2)
693
694        # Look ma, no brackets.
695        content, _ = self.parser.parse_string(
696            'configuration { foo.bar = 1 ? 1 || 0 : 3; }')
697
698        self.assertLen(content.children, 1)
699        conf = content.children[0]
700        self.assertIsInstance(conf, Configuration)
701
702        self.assertLen(conf.settings, 1)
703        s = conf.settings[0]
704        self.assertIsInstance(s, Setting)
705
706        self.assertEqual(s.instance, 'foo')
707        self.assertEqual(s.attribute, 'bar')
708        self.assertEqual(s.value, 1)
709
710        content, _ = self.parser.parse_string(
711            'configuration { foo.bar = 1 ? 2 : (1 || 0); }')
712
713        self.assertLen(content.children, 1)
714        conf = content.children[0]
715        self.assertIsInstance(conf, Configuration)
716
717        self.assertLen(conf.settings, 1)
718        s = conf.settings[0]
719        self.assertIsInstance(s, Setting)
720
721        self.assertEqual(s.instance, 'foo')
722        self.assertEqual(s.attribute, 'bar')
723        self.assertEqual(s.value, 2)
724
725    def test_ternary_conditional_nested(self):
726        '''
727        Test nested ternary conditionals.
728        '''
729        content, _ = self.parser.parse_string(
730            'configuration { foo.bar = (1 ? 2 : 3) ? 4 : 5; }')
731
732        self.assertLen(content.children, 1)
733        conf = content.children[0]
734        self.assertIsInstance(conf, Configuration)
735
736        self.assertLen(conf.settings, 1)
737        s = conf.settings[0]
738        self.assertIsInstance(s, Setting)
739
740        self.assertEqual(s.instance, 'foo')
741        self.assertEqual(s.attribute, 'bar')
742        self.assertEqual(s.value, 4)
743
744        content, _ = self.parser.parse_string(
745            'configuration { foo.bar = 1 ? 2 ? 3 : 4 : 5; }')
746
747        self.assertLen(content.children, 1)
748        conf = content.children[0]
749        self.assertIsInstance(conf, Configuration)
750
751        self.assertLen(conf.settings, 1)
752        s = conf.settings[0]
753        self.assertIsInstance(s, Setting)
754
755        self.assertEqual(s.instance, 'foo')
756        self.assertEqual(s.attribute, 'bar')
757        self.assertEqual(s.value, 3)
758
759        content, _ = self.parser.parse_string(
760            'configuration { foo.bar = 1 ? 2 : (3 ? 4 : 5); }')
761
762        self.assertLen(content.children, 1)
763        conf = content.children[0]
764        self.assertIsInstance(conf, Configuration)
765
766        self.assertLen(conf.settings, 1)
767        s = conf.settings[0]
768        self.assertIsInstance(s, Setting)
769
770        self.assertEqual(s.instance, 'foo')
771        self.assertEqual(s.attribute, 'bar')
772        self.assertEqual(s.value, 2)
773
774    def test_boolean_literals_plain(self):
775        '''
776        Test bare boolean literals.
777        '''
778        content, _ = self.parser.parse_string(
779            'configuration { foo.bar = true; }')
780
781        self.assertLen(content.children, 1)
782        conf = content.children[0]
783        self.assertIsInstance(conf, Configuration)
784
785        self.assertLen(conf.settings, 1)
786        s = conf.settings[0]
787        self.assertIsInstance(s, Setting)
788
789        self.assertEqual(s.instance, 'foo')
790        self.assertEqual(s.attribute, 'bar')
791        self.assertEqual(s.value, 1)
792
793        content, _ = self.parser.parse_string(
794            'configuration { foo.bar = True; }')
795
796        self.assertLen(content.children, 1)
797        conf = content.children[0]
798        self.assertIsInstance(conf, Configuration)
799
800        self.assertLen(conf.settings, 1)
801        s = conf.settings[0]
802        self.assertIsInstance(s, Setting)
803
804        self.assertEqual(s.instance, 'foo')
805        self.assertEqual(s.attribute, 'bar')
806        self.assertEqual(s.value, 1)
807
808        content, _ = self.parser.parse_string(
809            'configuration { foo.bar = false; }')
810
811        self.assertLen(content.children, 1)
812        conf = content.children[0]
813        self.assertIsInstance(conf, Configuration)
814
815        self.assertLen(conf.settings, 1)
816        s = conf.settings[0]
817        self.assertIsInstance(s, Setting)
818
819        self.assertEqual(s.instance, 'foo')
820        self.assertEqual(s.attribute, 'bar')
821        self.assertEqual(s.value, 0)
822
823        content, _ = self.parser.parse_string(
824            'configuration { foo.bar = False; }')
825
826        self.assertLen(content.children, 1)
827        conf = content.children[0]
828        self.assertIsInstance(conf, Configuration)
829
830        self.assertLen(conf.settings, 1)
831        s = conf.settings[0]
832        self.assertIsInstance(s, Setting)
833
834        self.assertEqual(s.instance, 'foo')
835        self.assertEqual(s.attribute, 'bar')
836        self.assertEqual(s.value, 0)
837
838    def test_boolean_literals_expression(self):
839        '''
840        Test boolean literals within an expression.
841        '''
842        content, _ = self.parser.parse_string(
843            'configuration { foo.bar = true || false; }')
844
845        self.assertLen(content.children, 1)
846        conf = content.children[0]
847        self.assertIsInstance(conf, Configuration)
848
849        self.assertLen(conf.settings, 1)
850        s = conf.settings[0]
851        self.assertIsInstance(s, Setting)
852
853        self.assertEqual(s.instance, 'foo')
854        self.assertEqual(s.attribute, 'bar')
855        self.assertEqual(s.value, 1)
856
857        content, _ = self.parser.parse_string(
858            'configuration { foo.bar = true && false; }')
859
860        self.assertLen(content.children, 1)
861        conf = content.children[0]
862        self.assertIsInstance(conf, Configuration)
863
864        self.assertLen(conf.settings, 1)
865        s = conf.settings[0]
866        self.assertIsInstance(s, Setting)
867
868        self.assertEqual(s.instance, 'foo')
869        self.assertEqual(s.attribute, 'bar')
870        self.assertEqual(s.value, 0)
871
872        content, _ = self.parser.parse_string(
873            'configuration { foo.bar = !(true || false); }')
874
875        self.assertLen(content.children, 1)
876        conf = content.children[0]
877        self.assertIsInstance(conf, Configuration)
878
879        self.assertLen(conf.settings, 1)
880        s = conf.settings[0]
881        self.assertIsInstance(s, Setting)
882
883        self.assertEqual(s.instance, 'foo')
884        self.assertEqual(s.attribute, 'bar')
885        self.assertEqual(s.value, 0)
886
887    def test_boolean_literals_coercion(self):
888        '''
889        Test that boolean literals coerce to ints.
890        '''
891        content, _ = self.parser.parse_string(
892            'configuration { foo.bar = (true + true) * (true + true) - false; }')
893
894        self.assertLen(content.children, 1)
895        conf = content.children[0]
896        self.assertIsInstance(conf, Configuration)
897
898        self.assertLen(conf.settings, 1)
899        s = conf.settings[0]
900        self.assertIsInstance(s, Setting)
901
902        self.assertEqual(s.instance, 'foo')
903        self.assertEqual(s.attribute, 'bar')
904        self.assertEqual(s.value, 4)
905
906        content, _ = self.parser.parse_string(
907            'configuration { foo.bar = (true + 2) ** 3; }')
908
909        self.assertLen(content.children, 1)
910        conf = content.children[0]
911        self.assertIsInstance(conf, Configuration)
912
913        self.assertLen(conf.settings, 1)
914        s = conf.settings[0]
915        self.assertIsInstance(s, Setting)
916
917        self.assertEqual(s.instance, 'foo')
918        self.assertEqual(s.attribute, 'bar')
919        self.assertEqual(s.value, 27)
920
921    def test_negative_left_shift(self):
922        '''
923        Test constant folding of a left shift by a negative number. This should
924        trigger an exception that CAmkES converts to a `ParseError`, but
925        previously it did not. See CAMKES-440 for more information.
926        '''
927        with self.assertRaises(ParseError):
928            self.parser.parse_string('configuration { foo.bar = 1 << -1; }')
929
930    def test_negative_right_shift(self):
931        '''
932        Test constant folding of a right shift by a negative number. As above,
933        this error should be converted.
934        '''
935        with self.assertRaises(ParseError):
936            self.parser.parse_string('configuration { foo.bar = 1 >> -1; }')
937
938    def test_overflow(self):
939        '''
940        Test constant folding of a calculation that deliberately induces an
941        `OverflowError`.
942        '''
943        with self.assertRaises(ParseError):
944            self.parser.parse_string(
945                'configuration { foo.bar = 1 << 2 ** 64; }')
946
947    def test_basic_default_parameter_direction(self):
948        '''
949        A feature that was added late to CAmkES was the ability to omit the
950        direction of a method parameter and have it assumed to be 'in'. This
951        tests that such syntax is supported.
952        '''
953        content, _ = self.parser.parse_string('procedure P {\n'
954                                              '  void foo(int x);\n'
955                                              '}')
956
957        self.assertLen(content.children, 1)
958        P = content.children[0]
959        self.assertIsInstance(P, Procedure)
960
961        self.assertLen(P.methods, 1)
962        foo = P.methods[0]
963
964        self.assertLen(foo.parameters, 1)
965        x = foo.parameters[0]
966
967        self.assertEqual(x.direction, 'in')
968
969    def test_complex_default_parameter_direction(self):
970        '''
971        Test a more complex example of default parameter directions.
972        '''
973        content, _ = self.parser.parse_string(
974            'procedure P {\n'
975            '  void foo(int x, inout unsigned int y, unsigned int z);\n'
976            '  void bar(out int x, struct foo y, MyType_t z);\n'
977            '}')
978
979        self.assertLen(content.children, 1)
980        P = content.children[0]
981        self.assertIsInstance(P, Procedure)
982
983        self.assertLen(P.methods, 2)
984        foo, bar = P.methods
985
986        self.assertLen(foo.parameters, 3)
987        x, y, z = foo.parameters
988
989        self.assertEqual(x.direction, 'in')
990        self.assertEqual(x.type, 'int')
991
992        self.assertEqual(y.direction, 'inout')
993        self.assertEqual(y.type, 'unsigned int')
994
995        self.assertEqual(z.direction, 'in')
996        self.assertEqual(z.type, 'unsigned int')
997
998        self.assertLen(bar.parameters, 3)
999        x, y, z = bar.parameters
1000
1001        self.assertEqual(x.direction, 'out')
1002        self.assertEqual(x.type, 'int')
1003
1004        self.assertEqual(y.direction, 'in')
1005        self.assertEqual(y.type, 'struct foo')
1006
1007        self.assertEqual(z.direction, 'in')
1008        self.assertEqual(z.type, 'MyType_t')
1009
1010    def test_default_attributes(self):
1011        '''
1012        Test that we can set default values for attributes.
1013        '''
1014        content, _ = self.parser.parse_string(
1015            'component Foo {\n'
1016            '  attribute string x;\n'
1017            '  attribute string y = "hello world";\n'
1018            '  attribute int z = 42;\n'
1019            '}')
1020
1021        self.assertLen(content.children, 1)
1022        Foo = content.children[0]
1023        self.assertIsInstance(Foo, Component)
1024
1025        self.assertLen(Foo.attributes, 3)
1026        x, y, z = Foo.attributes
1027
1028        self.assertIsNone(x.default)
1029
1030        self.assertEqual(y.default, 'hello world')
1031
1032        self.assertEqual(z.default, 42)
1033
1034    def test_string_concat(self):
1035        '''
1036        Test that C-style string concatenation works.
1037        '''
1038
1039        content, _ = self.parser.parse_string(
1040            'configuration {\n'
1041            '  foo.bar = "hello" "world";\n'
1042            '}')
1043
1044        self.assertLen(content.children, 1)
1045        conf = content.children[0]
1046        self.assertIsInstance(conf, Configuration)
1047
1048        self.assertLen(conf.settings, 1)
1049        foobar = conf.settings[0]
1050        self.assertIsInstance(foobar, Setting)
1051
1052        self.assertEqual(foobar.instance, 'foo')
1053        self.assertEqual(foobar.attribute, 'bar')
1054
1055        self.assertEqual(foobar.value, 'helloworld')
1056
1057    def test_string_concat_line_split(self):
1058        '''
1059        Test that C-style string concatenation works across line breaks.
1060        '''
1061
1062        content, _ = self.parser.parse_string(
1063            'configuration {\n'
1064            '  foo.bar = "hello" \n'
1065            '"world";\n'
1066            '}')
1067
1068        self.assertLen(content.children, 1)
1069        conf = content.children[0]
1070        self.assertIsInstance(conf, Configuration)
1071
1072        self.assertLen(conf.settings, 1)
1073        foobar = conf.settings[0]
1074        self.assertIsInstance(foobar, Setting)
1075
1076        self.assertEqual(foobar.instance, 'foo')
1077        self.assertEqual(foobar.attribute, 'bar')
1078
1079        self.assertEqual(foobar.value, 'helloworld')
1080
1081    def test_loose_semicolons(self):
1082        '''
1083        Test that we can cope with empty statements.
1084        '''
1085        content, _ = self.parser.parse_string(';;;')
1086        self.assertLen(content.children, 0)
1087
1088    def test_c_include(self):
1089        '''
1090        Test we can parse a relative C include.
1091        '''
1092        content, _ = self.parser.parse_string(
1093            'procedure P { include "hello.h"; }')
1094
1095        self.assertLen(content.children, 1)
1096        P = content.children[0]
1097        self.assertIsInstance(P, Procedure)
1098
1099        self.assertLen(P.children, 1)
1100        include = P.children[0]
1101        self.assertIsInstance(include, Include)
1102
1103        self.assertTrue(include.relative)
1104        self.assertEqual(include.source, 'hello.h')
1105
1106    def test_c_include2(self):
1107        '''
1108        Test we can parse a relative C include that relies on multi strings.
1109        '''
1110        content, _ = self.parser.parse_string(
1111            'procedure P { include "hello" "world"; }')
1112
1113        self.assertLen(content.children, 1)
1114        P = content.children[0]
1115        self.assertIsInstance(P, Procedure)
1116
1117        self.assertLen(P.children, 1)
1118        include = P.children[0]
1119        self.assertIsInstance(include, Include)
1120
1121        self.assertTrue(include.relative)
1122        self.assertEqual(include.source, 'helloworld')
1123
1124if __name__ == '__main__':
1125    unittest.main()
1126