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\Integration\Llk\Rule; 38 39use Hoa\Compiler as LUT; 40use Hoa\Compiler\Llk\Rule; 41use Hoa\Compiler\Llk\Rule\Analyzer as SUT; 42use Hoa\Test; 43 44/** 45 * Class \Hoa\Compiler\Test\Integration\Llk\Rule\Analyzer. 46 * 47 * Test suite of the rule analyzer. 48 * 49 * @copyright Copyright © 2007-2017 Hoa community 50 * @license New BSD License 51 */ 52class Analyzer extends Test\Integration\Suite 53{ 54 public function case_simple_kept_token() 55 { 56 return $this->_case_simple_token(true); 57 } 58 59 public function case_simple_skipped_token() 60 { 61 return $this->_case_simple_token(false); 62 } 63 64 protected function _case_simple_token($kept) 65 { 66 $this 67 ->given( 68 $tokens = ['default' => ['foo' => 'bar']], 69 $ruleA = $kept ? '<foo>' : '::foo::', 70 $analyzer = new SUT($tokens), 71 72 $_ruleA = new Rule\Token('ruleA', 'foo', null, -1, $kept), 73 $_ruleA->setPPRepresentation($ruleA) 74 ) 75 ->when($result = $analyzer->analyzeRules(['ruleA' => $ruleA])) 76 ->then 77 ->array($result) 78 ->isEqualTo([ 79 'ruleA' => $_ruleA 80 ]); 81 } 82 83 public function case_simple_named() 84 { 85 $this 86 ->given( 87 $tokens = ['default' => ['foo' => 'bar']], 88 $rules = [ 89 'ruleA' => 'ruleB()', 90 'ruleB' => '<foo>' 91 ], 92 $analyzer = new SUT($tokens), 93 94 $_ruleA = new Rule\Concatenation('ruleA', ['ruleB']), 95 $_ruleA->setPPRepresentation($rules['ruleA']), 96 $_ruleB = new Rule\Token('ruleB', 'foo', null, -1, true), 97 $_ruleB->setPPRepresentation($rules['ruleB']) 98 ) 99 ->when($result = $analyzer->analyzeRules($rules)) 100 ->then 101 ->array($result) 102 ->isEqualTo([ 103 'ruleA' => $_ruleA, 104 'ruleB' => $_ruleB 105 ]); 106 } 107 108 public function case_simple_kept_unified_token() 109 { 110 return $this->_case_simple_unified_token(true); 111 } 112 113 public function case_simple_skipped_unified_token() 114 { 115 return $this->_case_simple_unified_token(false); 116 } 117 118 protected function _case_simple_unified_token($kept) 119 { 120 $this 121 ->given( 122 $tokens = ['default' => ['foo' => 'bar']], 123 $ruleA = $kept ? '<foo[42]>' : '::foo[42]::', 124 $analyzer = new SUT($tokens), 125 126 $_ruleA = new Rule\Token('ruleA', 'foo', null, 42, $kept), 127 $_ruleA->setPPRepresentation($ruleA) 128 ) 129 ->when($result = $analyzer->analyzeRules(['ruleA' => $ruleA])) 130 ->then 131 ->array($result) 132 ->isEqualTo([ 133 'ruleA' => $_ruleA 134 ]); 135 } 136 137 public function case_repetition_zero_or_one() 138 { 139 return $this->_case_repetition('?', 0, 1); 140 } 141 142 public function case_repetition_one_or_more() 143 { 144 return $this->_case_repetition('+', 1, -1); 145 } 146 147 public function case_repetition_zero_or_more() 148 { 149 return $this->_case_repetition('*', 0, -1); 150 } 151 152 public function case_repetition_n_to_m() 153 { 154 return $this->_case_repetition('{7,42}', 7, 42); 155 } 156 157 public function case_repetition_n_or_more() 158 { 159 return $this->_case_repetition('{7,}', 7, -1); 160 } 161 162 public function case_repetition_exactly_n() 163 { 164 return $this->_case_repetition('{7}', 7, 7); 165 } 166 167 protected function _case_repetition($quantifier, $min, $max) 168 { 169 $this 170 ->given( 171 $tokens = ['default' => ['foo' => 'bar']], 172 $ruleA = '<foo>' . $quantifier, 173 $analyzer = new SUT($tokens), 174 175 $_ruleA = new Rule\Repetition('ruleA', $min, $max, 0, null), 176 $_ruleA->setPPRepresentation($ruleA), 177 $_rule0 = new Rule\Token(0, 'foo', null, -1, true) 178 ) 179 ->when($result = $analyzer->analyzeRules(['ruleA' => $ruleA])) 180 ->then 181 ->array($result) 182 ->isEqualTo([ 183 '0' => $_rule0, 184 'ruleA' => $_ruleA 185 ]); 186 } 187 188 public function case_concatenation() 189 { 190 $this 191 ->given( 192 $tokens = ['default' => ['foo' => 'bar', 'baz' => 'qux']], 193 $ruleA = '<foo> <baz>', 194 $analyzer = new SUT($tokens), 195 196 $_ruleA = new Rule\Concatenation('ruleA', [0, 1], null), 197 $_ruleA->setPPRepresentation($ruleA), 198 $_rule0 = new Rule\Token(0, 'foo', null, -1, true), 199 $_rule1 = new Rule\Token(1, 'baz', null, -1, true) 200 ) 201 ->when($result = $analyzer->analyzeRules(['ruleA' => $ruleA])) 202 ->then 203 ->array($result) 204 ->isEqualTo([ 205 0 => $_rule0, 206 1 => $_rule1, 207 'ruleA' => $_ruleA 208 ]); 209 } 210 211 public function case_choice() 212 { 213 $this 214 ->given( 215 $tokens = ['default' => ['foo' => 'bar', 'baz' => 'qux']], 216 $ruleA = '<foo> | <baz>', 217 $analyzer = new SUT($tokens), 218 219 $_ruleA = new Rule\Choice('ruleA', [0, 1], null), 220 $_ruleA->setPPRepresentation($ruleA), 221 $_rule0 = new Rule\Token(0, 'foo', null, -1, true), 222 $_rule1 = new Rule\Token(1, 'baz', null, -1, true) 223 ) 224 ->when($result = $analyzer->analyzeRules(['ruleA' => $ruleA])) 225 ->then 226 ->array($result) 227 ->isEqualTo([ 228 0 => $_rule0, 229 1 => $_rule1, 230 'ruleA' => $_ruleA 231 ]); 232 } 233 234 public function case_rule() 235 { 236 $this 237 ->given( 238 $tokens = ['default' => ['foo' => 'bar', 'baz' => 'qux']], 239 $ruleA = '<foo> | <baz>', 240 $analyzer = new SUT($tokens), 241 242 $_ruleA = new Rule\Choice('ruleA', [0, 1], null), 243 $_ruleA->setPPRepresentation($ruleA), 244 $_rule0 = new Rule\Token(0, 'foo', null, -1, true), 245 $_rule1 = new Rule\Token(1, 'baz', null, -1, true) 246 ) 247 ->when($result = $analyzer->analyzeRules(['ruleA' => $ruleA])) 248 ->then 249 ->array($result) 250 ->isEqualTo([ 251 0 => $_rule0, 252 1 => $_rule1, 253 'ruleA' => $_ruleA 254 ]); 255 } 256 257 public function case_full() 258 { 259 $this 260 ->given( 261 $tokens = ['default' => ['foo' => 'bar', 'baz' => 'qux']], 262 $ruleA = '<foo>+ | ruleB() ::foo::', 263 $ruleB = '<baz> ruleA()', 264 $analyzer = new SUT($tokens), 265 266 $_rule0 = new Rule\Token(0, 'foo', null, -1, true), 267 $_rule1 = new Rule\Repetition('1', 1, -1, '0', null), 268 $_rule2 = new Rule\Token(2, 'foo', null, -1, false), 269 $_rule3 = new Rule\Concatenation('3', ['ruleB', 2]), 270 $_ruleA = new Rule\Choice('ruleA', [1, 3], null), 271 $_ruleA->setPPRepresentation($ruleA), 272 $_rule5 = new Rule\Token(5, 'baz', null, -1, true), 273 $_ruleB = new Rule\Concatenation('ruleB', [5, 'ruleA']), 274 $_ruleB->setPPRepresentation($ruleB) 275 ) 276 ->when($result = $analyzer->analyzeRules(['ruleA' => $ruleA, 'ruleB' => $ruleB])) 277 ->then 278 ->array($result) 279 ->isEqualTo([ 280 0 => $_rule0, 281 1 => $_rule1, 282 2 => $_rule2, 283 3 => $_rule3, 284 'ruleA' => $_ruleA, 285 5 => $_rule5, 286 'ruleB' => $_ruleB 287 ]); 288 } 289 290 public function case_no_rule() 291 { 292 $this 293 ->given($analyzer = new SUT([])) 294 ->exception(function () use ($analyzer) { 295 $analyzer->analyzeRules([]); 296 }) 297 ->isInstanceOf(LUT\Exception::class) 298 ->hasMessage('No rules specified!'); 299 } 300 301 public function case_error_while_parsing_rule() 302 { 303 $this 304 ->given( 305 $tokens = ['default' => ['foo' => 'bar']], 306 $ruleA = '<foo>{2,1}', 307 $analyzer = new SUT($tokens) 308 ) 309 ->exception(function () use ($analyzer, $ruleA) { 310 $analyzer->analyzeRules(['ruleA' => $ruleA]); 311 }) 312 ->isInstanceOf(LUT\Exception::class) 313 ->hasMessage( 314 'Upper bound 1 must be greater or equal to lower bound ' . 315 '2 in rule ruleA.' 316 ); 317 } 318 319 public function case_kept_token_does_not_exist() 320 { 321 $this 322 ->given( 323 $tokens = [], 324 $ruleA = '<foo>', 325 $analyzer = new SUT($tokens) 326 ) 327 ->exception(function () use ($analyzer, $ruleA) { 328 $analyzer->analyzeRules(['ruleA' => $ruleA]); 329 }) 330 ->isInstanceOf(LUT\Exception::class) 331 ->hasMessage('Token <foo> does not exist in rule ruleA.'); 332 } 333 334 public function case_skipped_token_does_not_exist() 335 { 336 $this 337 ->given( 338 $tokens = [], 339 $ruleA = '::foo::', 340 $analyzer = new SUT($tokens) 341 ) 342 ->exception(function () use ($analyzer, $ruleA) { 343 $analyzer->analyzeRules(['ruleA' => $ruleA]); 344 }) 345 ->isInstanceOf(LUT\Exception::class) 346 ->hasMessage('Token ::foo:: does not exist in rule ruleA.'); 347 } 348 349 public function case_rule_does_not_exist() 350 { 351 $this 352 ->given( 353 $tokens = [], 354 $ruleA = 'ruleB()', 355 $analyzer = new SUT($tokens) 356 ) 357 ->exception(function () use ($analyzer, $ruleA) { 358 $analyzer->analyzeRules(['ruleA' => $ruleA]); 359 }) 360 ->isInstanceOf(LUT\Exception::class) 361 ->hasMessage( 362 'Cannot call rule ruleB() in rule ruleA because it does not exist.' 363 ); 364 } 365} 366