1<?php 2 3/** 4 * Hoa 5 * 6 * 7 * @license 8 * 9 * New BSD License 10 * 11 * Copyright © 2007-2017, Hoa community. All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions are met: 15 * * Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * * Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * * Neither the name of the Hoa nor the names of its contributors may be 21 * used to endorse or promote products derived from this software without 22 * specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE 28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 * POSSIBILITY OF SUCH DAMAGE. 35 */ 36 37namespace Hoa\Compiler\Test\Unit\Llk; 38 39use Hoa\Compiler as LUT; 40use Hoa\Compiler\Llk\Llk as SUT; 41use Hoa\File; 42use Hoa\Test; 43 44/** 45 * Class \Hoa\Compiler\Test\Unit\Llk\Llk. 46 * 47 * Test suite of the LL(k) helper class. 48 * 49 * @copyright Copyright © 2007-2017 Hoa community 50 * @license New BSD License 51 */ 52class Llk extends Test\Unit\Suite 53{ 54 public function case_load_empty() 55 { 56 $this 57 ->given($stream = new File\ReadWrite('hoa://Test/Vfs/Empty.pp?type=file')) 58 ->exception(function () use ($stream) { 59 SUT::load($stream); 60 }) 61 ->isInstanceOf(LUT\Exception::class) 62 ->hasMessage( 63 'The grammar is empty: Nothing to read on the stream ' . 64 'hoa://Test/Vfs/Empty.pp?type=file.' 65 ); 66 } 67 68 public function case_load() 69 { 70 $this 71 ->given( 72 $stream = new File\ReadWrite('hoa://Test/Vfs/Grammar.pp?type=file'), 73 $stream->writeAll( 74 '%pragma hello world' . "\n" . 75 '%token foobar bazqux' . "\n" . 76 'ruleA:' . "\n" . 77 ' <foobar>' 78 ), 79 80 $_ruleA = new LUT\Llk\Rule\Token('ruleA', 'foobar', null, -1, true), 81 $_ruleA->setPPRepresentation(' <foobar>') 82 ) 83 ->when($result = SUT::load($stream)) 84 ->then 85 ->object($result) 86 ->isInstanceOf(LUT\Llk\Parser::class) 87 ->array($result->getPragmas()) 88 ->isEqualTo([ 89 'hello' => 'world' 90 ]) 91 ->array($result->getTokens()) 92 ->isEqualTo([ 93 'default' => [ 94 'foobar' => 'bazqux' 95 ] 96 ]) 97 ->array($result->getRules()) 98 ->isEqualTo([ 99 'ruleA' => $_ruleA 100 ]); 101 } 102 103 public function case_save() 104 { 105 $this 106 ->given( 107 $stream = new File\Read('hoa://Library/Compiler/Llk/Llk.pp'), 108 $parser = SUT::load($stream) 109 ) 110 ->when($result = SUT::save($parser, 'Foobar')) 111 ->then 112 ->string($result) 113 ->isEqualTo(<<<'OUTPUT' 114class Foobar extends \Hoa\Compiler\Llk\Parser 115{ 116 public function __construct() 117 { 118 parent::__construct( 119 [ 120 'default' => [ 121 'skip' => '\s', 122 'or' => '\|', 123 'zero_or_one' => '\?', 124 'one_or_more' => '\+', 125 'zero_or_more' => '\*', 126 'n_to_m' => '\{[0-9]+,[0-9]+\}', 127 'zero_to_m' => '\{,[0-9]+\}', 128 'n_or_more' => '\{[0-9]+,\}', 129 'exactly_n' => '\{[0-9]+\}', 130 'token' => '[a-zA-Z_][a-zA-Z0-9_]*', 131 'skipped' => '::', 132 'kept_' => '<', 133 '_kept' => '>', 134 'named' => '\(\)', 135 'node' => '#[a-zA-Z_][a-zA-Z0-9_]*(:[mM])?', 136 'capturing_' => '\(', 137 '_capturing' => '\)', 138 'unification_' => '\[', 139 'unification' => '[0-9]+', 140 '_unification' => '\]', 141 ], 142 ], 143 [ 144 0 => new \Hoa\Compiler\Llk\Rule\Concatenation(0, ['choice'], null), 145 'rule' => new \Hoa\Compiler\Llk\Rule\Concatenation('rule', [0], '#rule'), 146 2 => new \Hoa\Compiler\Llk\Rule\Token(2, 'or', null, -1, false), 147 3 => new \Hoa\Compiler\Llk\Rule\Concatenation(3, [2, 'concatenation'], '#choice'), 148 4 => new \Hoa\Compiler\Llk\Rule\Repetition(4, 0, -1, 3, null), 149 'choice' => new \Hoa\Compiler\Llk\Rule\Concatenation('choice', ['concatenation', 4], null), 150 6 => new \Hoa\Compiler\Llk\Rule\Concatenation(6, ['repetition'], '#concatenation'), 151 7 => new \Hoa\Compiler\Llk\Rule\Repetition(7, 0, -1, 6, null), 152 'concatenation' => new \Hoa\Compiler\Llk\Rule\Concatenation('concatenation', ['repetition', 7], null), 153 9 => new \Hoa\Compiler\Llk\Rule\Concatenation(9, ['quantifier'], '#repetition'), 154 10 => new \Hoa\Compiler\Llk\Rule\Repetition(10, 0, 1, 9, null), 155 11 => new \Hoa\Compiler\Llk\Rule\Token(11, 'node', null, -1, true), 156 12 => new \Hoa\Compiler\Llk\Rule\Repetition(12, 0, 1, 11, null), 157 'repetition' => new \Hoa\Compiler\Llk\Rule\Concatenation('repetition', ['simple', 10, 12], null), 158 14 => new \Hoa\Compiler\Llk\Rule\Token(14, 'capturing_', null, -1, false), 159 15 => new \Hoa\Compiler\Llk\Rule\Token(15, '_capturing', null, -1, false), 160 16 => new \Hoa\Compiler\Llk\Rule\Concatenation(16, [14, 'choice', 15], null), 161 17 => new \Hoa\Compiler\Llk\Rule\Token(17, 'skipped', null, -1, false), 162 18 => new \Hoa\Compiler\Llk\Rule\Token(18, 'token', null, -1, true), 163 19 => new \Hoa\Compiler\Llk\Rule\Token(19, 'unification_', null, -1, false), 164 20 => new \Hoa\Compiler\Llk\Rule\Token(20, 'unification', null, -1, true), 165 21 => new \Hoa\Compiler\Llk\Rule\Token(21, '_unification', null, -1, false), 166 22 => new \Hoa\Compiler\Llk\Rule\Concatenation(22, [19, 20, 21], null), 167 23 => new \Hoa\Compiler\Llk\Rule\Repetition(23, 0, 1, 22, null), 168 24 => new \Hoa\Compiler\Llk\Rule\Token(24, 'skipped', null, -1, false), 169 25 => new \Hoa\Compiler\Llk\Rule\Concatenation(25, [17, 18, 23, 24], '#skipped'), 170 26 => new \Hoa\Compiler\Llk\Rule\Token(26, 'kept_', null, -1, false), 171 27 => new \Hoa\Compiler\Llk\Rule\Token(27, 'token', null, -1, true), 172 28 => new \Hoa\Compiler\Llk\Rule\Token(28, 'unification_', null, -1, false), 173 29 => new \Hoa\Compiler\Llk\Rule\Token(29, 'unification', null, -1, true), 174 30 => new \Hoa\Compiler\Llk\Rule\Token(30, '_unification', null, -1, false), 175 31 => new \Hoa\Compiler\Llk\Rule\Concatenation(31, [28, 29, 30], null), 176 32 => new \Hoa\Compiler\Llk\Rule\Repetition(32, 0, 1, 31, null), 177 33 => new \Hoa\Compiler\Llk\Rule\Token(33, '_kept', null, -1, false), 178 34 => new \Hoa\Compiler\Llk\Rule\Concatenation(34, [26, 27, 32, 33], '#kept'), 179 35 => new \Hoa\Compiler\Llk\Rule\Token(35, 'token', null, -1, true), 180 36 => new \Hoa\Compiler\Llk\Rule\Token(36, 'named', null, -1, false), 181 37 => new \Hoa\Compiler\Llk\Rule\Concatenation(37, [35, 36], null), 182 'simple' => new \Hoa\Compiler\Llk\Rule\Choice('simple', [16, 25, 34, 37], null), 183 39 => new \Hoa\Compiler\Llk\Rule\Token(39, 'zero_or_one', null, -1, true), 184 40 => new \Hoa\Compiler\Llk\Rule\Token(40, 'one_or_more', null, -1, true), 185 41 => new \Hoa\Compiler\Llk\Rule\Token(41, 'zero_or_more', null, -1, true), 186 42 => new \Hoa\Compiler\Llk\Rule\Token(42, 'n_to_m', null, -1, true), 187 43 => new \Hoa\Compiler\Llk\Rule\Token(43, 'n_or_more', null, -1, true), 188 44 => new \Hoa\Compiler\Llk\Rule\Token(44, 'exactly_n', null, -1, true), 189 'quantifier' => new \Hoa\Compiler\Llk\Rule\Choice('quantifier', [39, 40, 41, 42, 43, 44], null), 190 ], 191 [ 192 ] 193 ); 194 195 $this->getRule('rule')->setDefaultId('#rule'); 196 $this->getRule('rule')->setPPRepresentation(' choice()'); 197 $this->getRule('choice')->setPPRepresentation(' concatenation() ( ::or:: concatenation() #choice )*'); 198 $this->getRule('concatenation')->setPPRepresentation(' repetition() ( repetition() #concatenation )*'); 199 $this->getRule('repetition')->setPPRepresentation(' simple() ( quantifier() #repetition )? <node>?'); 200 $this->getRule('simple')->setPPRepresentation(' ::capturing_:: choice() ::_capturing:: | ::skipped:: <token> ( ::unification_:: <unification> ::_unification:: )? ::skipped:: #skipped | ::kept_:: <token> ( ::unification_:: <unification> ::_unification:: )? ::_kept:: #kept | <token> ::named::'); 201 $this->getRule('quantifier')->setPPRepresentation(' <zero_or_one> | <one_or_more> | <zero_or_more> | <n_to_m> | <n_or_more> | <exactly_n>'); 202 } 203} 204 205OUTPUT 206); 207 } 208 209 public function case_parse_tokens() 210 { 211 $this 212 ->given( 213 $pp = 214 '%token foobar1 bazqux1' . "\n" . 215 '%token sourceNS1:foobar2 bazqux2' . "\n" . 216 '%token sourceNS2:foobar3 bazqux3 -> destinationNS' . "\n" . 217 '%token foobar4 barqux4 -> destinationNS' 218 ) 219 ->when($result = SUT::parsePP($pp, $tokens, $rules, $pragmas, 'streamFoo')) 220 ->then 221 ->variable($result) 222 ->isNull() 223 ->array($tokens) 224 ->isEqualTo([ 225 'default' => [ 226 'foobar1' => 'bazqux1', 227 'foobar4:destinationNS' => 'barqux4' 228 ], 229 'sourceNS1' => [ 230 'foobar2' => 'bazqux2' 231 ], 232 'sourceNS2' => [ 233 'foobar3:destinationNS' => 'bazqux3' 234 ] 235 ]) 236 ->array($rules) 237 ->isEmpty() 238 ->array($pragmas) 239 ->isEmpty(); 240 } 241 242 public function case_parse_skip_tokens() 243 { 244 $this 245 ->given( 246 $pp = 247 '%skip foobar1 bazqux1' . "\n" . 248 '%skip foobar2 bazqux2' . "\n" . 249 '%skip foobar3 bazqux3' . "\n" . 250 '%skip sourceNS1:foobar4 bazqux4' . "\n" . 251 '%skip sourceNS1:foobar5 bazqux5' . "\n" . 252 '%skip sourceNS2:foobar6 bazqux6' . "\n" 253 ) 254 ->when($result = SUT::parsePP($pp, $tokens, $rules, $pragmas, 'streamFoo')) 255 ->then 256 ->variable($result) 257 ->isNull() 258 ->array($tokens) 259 ->isEqualTo([ 260 'default' => [ 261 'skip' => '(?:(?:bazqux1|bazqux2)|bazqux3)', 262 ], 263 'sourceNS1' => [ 264 'skip' => '(?:bazqux4|bazqux5)' 265 ], 266 'sourceNS2' => [ 267 'skip' => 'bazqux6' 268 ] 269 ]) 270 ->array($rules) 271 ->isEmpty() 272 ->array($pragmas) 273 ->isEmpty(); 274 } 275 276 public function case_parse_pragmas() 277 { 278 $this 279 ->given( 280 $pp = 281 '%pragma truly true' . "\n" . 282 '%pragma falsy false' . "\n" . 283 '%pragma numby 42' . "\n" . 284 '%pragma foobar hello' . "\n" . 285 '%pragma bazqux "world!" ' . "\n" 286 ) 287 ->when($result = SUT::parsePP($pp, $tokens, $rules, $pragmas, 'streamFoo')) 288 ->then 289 ->variable($result) 290 ->isNull() 291 ->array($tokens) 292 ->isEqualTo(['default' => []]) 293 ->array($rules) 294 ->isEmpty() 295 ->array($pragmas) 296 ->isIdenticalTo([ 297 'truly' => true, 298 'falsy' => false, 299 'numby' => 42, 300 'foobar' => 'hello', 301 'bazqux' => '"world!"' 302 ]); 303 } 304 305 public function case_unrecognized_instructions() 306 { 307 $this 308 ->given( 309 $pp = 310 '// shift line' . "\n" . 311 '%foobar baz qux' . "\n" 312 ) 313 ->exception(function () use ($pp) { 314 SUT::parsePP($pp, $tokens, $rules, $pragmas, 'streamFoo'); 315 }) 316 ->isInstanceOf(LUT\Exception::class) 317 ->hasMessage( 318 'Unrecognized instructions:' . "\n" . 319 ' %foobar baz qux' . "\n" . 320 'in file streamFoo at line 2.' 321 ); 322 } 323 324 public function case_parse_rules() 325 { 326 $this 327 ->given( 328 $pp = 329 'ruleA:' . "\n" . 330 ' single space' . "\n" . 331 ' single space' . "\n" . 332 'ruleB:' . "\n" . 333 ' many spaces' . "\n" . 334 "\t" . 'single tab' . "\n" . 335 'ruleC:' . "\n" . 336 "\t\t" . 'many tabs' . "\n" 337 ) 338 ->when($result = SUT::parsePP($pp, $tokens, $rules, $pragmas, 'streamFoo')) 339 ->then 340 ->variable($result) 341 ->isNull() 342 ->array($tokens) 343 ->isEqualTo(['default' => []]) 344 ->array($rules) 345 ->isEqualTo([ 346 'ruleA' => ' single space single space', 347 'ruleB' => ' many spaces single tab', 348 'ruleC' => ' many tabs' 349 ]) 350 ->array($pragmas) 351 ->isEmpty(); 352 } 353 354 public function case_parse_skip_comments() 355 { 356 $this 357 ->given( 358 $pp = 359 '// Hello,' . "\n" . 360 '// World!' 361 ) 362 ->when($result = SUT::parsePP($pp, $tokens, $rules, $pragmas, 'streamFoo')) 363 ->then 364 ->variable($result) 365 ->isNull() 366 ->array($tokens) 367 ->isEqualTo(['default' => []]) 368 ->array($rules) 369 ->isEmpty() 370 ->array($pragmas) 371 ->isEmpty(); 372 } 373} 374