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