├── .State ├── .gitignore ├── .travis.yml ├── Bin └── Assert.php ├── CHANGELOG.md ├── Context.php ├── Documentation ├── En │ └── Index.xyl └── Fr │ └── Index.xyl ├── DynamicCallable.php ├── Exception ├── Asserter.php ├── Exception.php └── Interpreter.php ├── Grammar.pp ├── Model ├── Bag │ ├── Bag.php │ ├── Context.php │ ├── RulerArray.php │ └── Scalar.php ├── Model.php └── Operator.php ├── README.md ├── Ruler.php ├── Test ├── Integration │ ├── Documentation.php │ └── Model │ │ └── Operator.php └── Unit │ ├── Context.php │ ├── DynamicCallable.php │ ├── Exception │ ├── Asserter.php │ ├── Exception.php │ └── Interpreter.php │ ├── Issue.php │ ├── Model │ ├── Bag │ │ ├── Bag.php │ │ ├── Context.php │ │ ├── RulerArray.php │ │ └── Scalar.php │ ├── Model.php │ └── Operator.php │ ├── Ruler.php │ └── Visitor │ ├── Asserter.php │ ├── Compiler.php │ ├── Disassembly.php │ └── Interpreter.php ├── Visitor ├── Asserter.php ├── Compiler.php ├── Disassembly.php └── Interpreter.php ├── bors.toml └── composer.json /.State: -------------------------------------------------------------------------------- 1 | finalized 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /composer.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | branches: 4 | only: 5 | - staging 6 | - trying 7 | - master 8 | 9 | matrix: 10 | include: 11 | - php: 5.5 12 | - php: 5.6 13 | - php: 7.0 14 | - php: 7.1 15 | env: 16 | - ENABLE_XDEBUG=true 17 | - php: 7.1 18 | env: 19 | - ENABLE_DEVTOOLS=true 20 | - php: nightly 21 | allow_failures: 22 | - php: nightly 23 | fast_finish: true 24 | 25 | os: 26 | - linux 27 | 28 | notifications: 29 | irc: "chat.freenode.net#hoaproject" 30 | 31 | sudo: false 32 | 33 | env: 34 | global: 35 | - secure: "AAAAB3NzaC1yc2EAAAADAQABAAAAgQDzZax7/VFMmTnePlw4PQmD7pVaJQDbLaMXfIIuV/h51m0g8dYfWiGytsblv+/tW37b3TKaGVLMP3vL9jGU73V4P64Ytafj0UV3UKSzHR4atrAPjsCjsFMzlIvKLCZf2FmADRspv/pAg1loQWRzXAiZ9pqCSTxx32x20uLAJmLucQ==" 36 | 37 | cache: 38 | directories: 39 | - vendor/ 40 | 41 | before_script: 42 | - export PATH="$PATH:$HOME/.composer/vendor/bin" 43 | - if [[ ! $ENABLE_XDEBUG ]]; then 44 | phpenv config-rm xdebug.ini || echo "ext-xdebug is not available, cannot remove it."; 45 | fi 46 | 47 | script: 48 | - composer install 49 | - vendor/bin/hoa test:run 50 | - if [[ $ENABLE_DEVTOOLS ]]; then 51 | composer global require friendsofphp/php-cs-fixer; 52 | vendor/bin/hoa devtools:cs --diff --dry-run .; 53 | fi 54 | -------------------------------------------------------------------------------- /Bin/Assert.php: -------------------------------------------------------------------------------- 1 | getOption($v)) { 75 | switch ($c) { 76 | case '__ambiguous': 77 | $context[$v['option']] = $v['value']; 78 | 79 | break; 80 | 81 | case 'h': 82 | case '?': 83 | return $this->usage(); 84 | } 85 | } 86 | 87 | $this->parser->listInputs($rule); 88 | 89 | if (empty($rule)) { 90 | return $this->usage(); 91 | } 92 | 93 | return (int) (!$ruler->assert($rule, $context)); 94 | } 95 | 96 | /** 97 | * The command usage. 98 | * 99 | * @return int 100 | */ 101 | public function usage() 102 | { 103 | echo 104 | 'Usage : ruler:assert rule', "\n", 105 | 'Options :', "\n", 106 | $this->makeUsageOptionsList([ 107 | 'help' => 'This help.' 108 | ]), "\n", 109 | 'Example : -x=2 -y=6 \'x in [1, 2, 4] and x < y\'.', "\n", 110 | 'See $? to see the result (0 for true, > 0 for false).', "\n"; 111 | 112 | return; 113 | } 114 | } 115 | 116 | __halt_compiler(); 117 | Assert rules. 118 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2.17.05.16 2 | 3 | * Asserter: Can visit array-like dimensions. (Arne Groskurth, 2017-05-16T09:42:29+02:00) 4 | 5 | # 2.17.04.26 6 | 7 | * Grammar: Logical operators are left-associative. (Ivan Enderlin, 2017-03-24T14:39:19+01:00) 8 | * CS: Fix copyright. (Ivan Enderlin, 2017-03-13T14:59:05+01:00) 9 | * Test: Support PHP 5.x syntax. (Ivan Enderlin, 2017-03-13T14:44:33+01:00) 10 | * CI: Set up Travis. (Ivan Enderlin, 2017-03-13T14:16:45+01:00) 11 | 12 | # 2.17.01.13 13 | 14 | * Quality: Happy new year! (Alexis von Glasow, 2017-01-09T21:37:11+01:00) 15 | * Test: Add the `Decorrelated` interface. (Ivan Enderlin, 2016-10-25T07:57:09+02:00) 16 | 17 | # 2.16.10.24 18 | 19 | * Documentation: New `README.md` file. (Ivan Enderlin, 2016-10-14T23:10:14+02:00) 20 | * Grammar: Chain dimensions on function. (Ivan Enderlin, 2016-10-14T08:37:12+02:00) 21 | * Documentation: Update `support` properties. (Ivan Enderlin, 2016-10-11T11:54:40+02:00) 22 | * Test: Update according to previous commit. (Ivan Enderlin, 2016-09-09T16:55:21+02:00) 23 | * Disassembly: Always add parenthesis around operators. (Ivan Enderlin, 2016-01-16T08:09:28+01:00) 24 | * Test: Add test cases for `…uler\Visitor\Asserter`. (Ivan Enderlin, 2016-09-09T08:04:49+02:00) 25 | 26 | # 2.16.09.07 27 | 28 | * Test: Write `…\Ruler\Visitor\Asserter` test suite. (Ivan Enderlin, 2016-09-07T15:06:46+02:00) 29 | * Asserter: Fix an exception message. (Ivan Enderlin, 2016-09-07T15:04:19+02:00) 30 | * Test: Write `…ler\Visitor\Interpreter` test suite. (Ivan Enderlin, 2016-09-06T08:54:00+02:00) 31 | * Quality: Rename an internal variable. (Ivan Enderlin, 2016-09-06T08:02:30+02:00) 32 | * Test: Parameterized cases are usually protected. (Ivan Enderlin, 2016-09-06T08:00:43+02:00) 33 | * Disassembly: Escape the escaping symbol. (Ivan Enderlin, 2016-09-05T17:23:26+02:00) 34 | * Test: Write `…ler\Visitor\Disassembly` test suite. (Ivan Enderlin, 2016-09-05T17:22:16+02:00) 35 | * Test: Write `…\Ruler\Visitor\Compiler` test suite. (Ivan Enderlin, 2016-09-05T17:15:41+02:00) 36 | * Test: Write `Hoa\Ruler\Ruler` test suite. (Ivan Enderlin, 2016-09-05T08:56:36+02:00) 37 | * Ruler: Rename a namespace alias. (Ivan Enderlin, 2016-09-05T08:19:19+02:00) 38 | * Ruler: Remove the `interprete` method. (Ivan Enderlin, 2016-09-05T08:14:21+02:00) 39 | * Test: Update name for `Issue`. (Ivan Enderlin, 2016-09-05T08:10:18+02:00) 40 | * Quality: Run `hoa devtools:cs`. (Ivan Enderlin, 2016-09-05T08:09:13+02:00) 41 | * Test: Write `…r\Exception\Interpreter` test suite. (Ivan Enderlin, 2016-09-05T08:08:20+02:00) 42 | * Test: Write `…uler\Exception\Asserter` test suite. (Ivan Enderlin, 2016-09-05T08:07:31+02:00) 43 | * Test: Write `…ler\Exception\Exception` test suite. (Ivan Enderlin, 2016-09-05T08:05:36+02:00) 44 | * Test: Format to standard vocabulary. (Ivan Enderlin, 2016-09-05T08:04:39+02:00) 45 | * Test: Rename `CUT` to `SUT`. (Ivan Enderlin, 2016-09-05T08:02:49+02:00) 46 | * Test: Move `Documentation` as integration suite. (Ivan Enderlin, 2016-09-05T08:01:25+02:00) 47 | * Test: Write `Hoa\Ruler\Model\Model` test suite. (Ivan Enderlin, 2016-09-02T17:40:35+02:00) 48 | * Visitor: If the model is empty, compile to `''`. (Ivan Enderlin, 2016-09-02T17:34:27+02:00) 49 | * Test: Ensure recursivity applies onto array items. (Ivan Enderlin, 2016-09-02T17:19:51+02:00) 50 | * Test: Write `Hoa\Ruler\Model\Operator` test suite. (Ivan Enderlin, 2016-09-02T17:09:31+02:00) 51 | * Model: Use the public `getName` method. (Ivan Enderlin, 2016-09-02T17:08:48+02:00) 52 | * Model: Move set auto laziness to `setName`. (Ivan Enderlin, 2016-09-02T17:08:02+02:00) 53 | * Test: Move `…erator` as unit to integration suite. (Ivan Enderlin, 2016-09-02T07:49:40+02:00) 54 | * Documentation: Fix API documentation. (Ivan Enderlin, 2016-09-02T07:47:03+02:00) 55 | * Test: Write `…Ruler\Model\Bag\Context` test suite. (Ivan Enderlin, 2016-09-02T07:46:09+02:00) 56 | * Quality: Remove an unnecessary namespace alias. (Ivan Enderlin, 2016-08-30T17:03:58+02:00) 57 | * Test: Write `…er\Model\Bag\RulerArray` test suite. (Ivan Enderlin, 2016-08-30T17:03:38+02:00) 58 | * Test: Write `…\Ruler\Model\Bag\Scalar` test suite. (Ivan Enderlin, 2016-08-29T16:29:37+02:00) 59 | * Documentation: Fix API documentation. (Ivan Enderlin, 2016-08-29T16:29:16+02:00) 60 | * Test: Write `Hoa\Ruler\Model\Bag` test suite. (Ivan Enderlin, 2016-08-29T15:51:28+02:00) 61 | * Test: Use the `::class` constant. (Ivan Enderlin, 2016-08-29T15:49:09+02:00) 62 | 63 | # 2.16.04.06 64 | 65 | * Asserter: Fix a wrong namespace access. (jroenf, 2016-04-06T09:09:43+02:00) 66 | 67 | # 2.16.03.15 68 | 69 | * Composer: `hoa/protocol` is explicitly required. (Ivan Enderlin, 2016-01-18T22:14:18+01:00) 70 | * Grammar: Update copyright. (Ivan Enderlin, 2016-01-17T14:22:07+01:00) 71 | 72 | # 2.16.01.15 73 | 74 | * Composer: Remove a useless dependency. (Ivan Enderlin, 2016-01-14T22:42:15+01:00) 75 | 76 | # 2.16.01.14 77 | 78 | * Composer: New stable libraries. (Ivan Enderlin, 2016-01-14T22:13:39+01:00) 79 | 80 | # 2.16.01.11 81 | 82 | * Quality: Drop PHP5.4. (Ivan Enderlin, 2016-01-11T09:15:26+01:00) 83 | * Quality: Run devtools:cs. (Ivan Enderlin, 2016-01-09T09:08:44+01:00) 84 | * Core: Remove `Hoa\Core`. (Ivan Enderlin, 2016-01-09T08:24:06+01:00) 85 | * Consistency: Use `Hoa\Consistency`. (Ivan Enderlin, 2015-12-08T21:50:12+01:00) 86 | * Exception: Use `Hoa\Exception`. (Ivan Enderlin, 2015-11-20T13:10:38+01:00) 87 | 88 | # 1.15.11.09 89 | 90 | * Fix CS. (Ivan Enderlin, 2015-09-23T11:31:44+02:00) 91 | 92 | # 1.15.09.22 93 | 94 | * Fix bad evaluation of `null` as an array key in the asserter. (Grummfy, 2015-09-22T15:47:23+02:00) 95 | 96 | # 1.15.09.08 97 | 98 | * Add a `.gitignore` file. (Stéphane HULARD, 2015-08-03T11:45:52+02:00) 99 | * Add the `matches` operator. (Kévin Gomez, 2015-07-18T19:44:24+02:00) 100 | 101 | # 1.15.07.28 102 | 103 | * Auto-box the expression to its bag representation. (Ivan Enderlin, 2015-07-13T11:37:00+02:00) 104 | * Fix a typo. (Ivan Enderlin, 2015-06-27T16:08:06+02:00) 105 | 106 | # 1.15.05.29 107 | 108 | * Move to PSR-1 and PSR-2. (Ivan Enderlin, 2015-04-20T10:21:13+02:00) 109 | 110 | # 1.15.04.13 111 | 112 | * Add the English documentation. (Ivan Enderlin, 2015-03-19T10:32:47+01:00) 113 | * Add the French documentation. (Ivan Enderlin, 2015-03-19T10:29:59+01:00) 114 | * Add the `CHANGELOG.md` file. (Ivan Enderlin, 2015-02-16T14:08:39+01:00) 115 | * Fix CS and API documentation. (Ivan Enderlin, 2015-02-06T10:37:23+01:00) 116 | * Add lazy operator support. (Alexis von Glasow, 2014-12-15T23:42:34+01:00) 117 | * Add tests for the dynamic callable. (Ivan Enderlin, 2015-02-05T17:13:12+01:00) 118 | 119 | # 1.15.02.05 120 | 121 | * Sandbox function calls in the context. (Ivan Enderlin, 2015-02-05T16:50:13+01:00) 122 | * Add tests for the context. (Ivan Enderlin, 2015-02-05T16:49:30+01:00) 123 | 124 | # 1.15.02.02 125 | 126 | * s/interprete/interpret/ (Ivan Enderlin, 2015-02-02T11:31:29+01:00) 127 | * `Ruler::interprete` is an alias to `Ruler::interpret` (simkimsia, 2015-01-16T22:22:18+08:00) 128 | * Improve type-hints in `Visitor\Asserter` (Alexis von Glasow, 2015-01-15T13:34:30+01:00) 129 | * Happy new year! (Ivan Enderlin, 2015-01-05T14:47:59+01:00) 130 | 131 | # 1.14.12.10 132 | 133 | * Move to PSR-4. (Ivan Enderlin, 2014-12-09T18:45:18+01:00) 134 | 135 | # 1.14.12.09 136 | 137 | * Fix a bug in the `Visitor\Compiler` when function has no argument (Catalin Criste, 2014-12-09T18:25:25+01:00) 138 | * Format namespace. (Ivan Enderlin, 2014-12-08T14:04:08+01:00) 139 | * Require `hoa/test`. (Alexis von Glasow, 2014-11-26T13:21:41+01:00) 140 | * `Hoa\Visitor` has been finalized. (Ivan Enderlin, 2014-11-15T22:28:07+01:00) 141 | 142 | # 1.14.11.10 143 | 144 | * Avoid collisions with user-defined operators… (Ivan Enderlin, 2014-11-10T15:43:04+01:00) 145 | 146 | # 1.14.11.09 147 | 148 | * Split the visitor into several methods (Stéphane PY, 2014-11-07T09:29:55+01:00) 149 | * Add tests for the documentation. (Ivan Enderlin, 2014-09-26T09:23:44+02:00) 150 | 151 | # 1.14.09.25 152 | 153 | * Fix Fatal error. (Stéphane PY, 2014-09-25T12:22:18+02:00) 154 | * Add `branch-alias` (Stéphane PY, 2014-09-23T16:06:06+02:00) 155 | * `Hoa\Core` was missing (Ivan Enderlin, 2014-09-23T15:58:55+02:00) 156 | 157 | # 1.14.09.23 158 | 159 | * First tag :-). (Ivan Enderlin, 2014-09-23T15:41:11+02:00) 160 | * Finalized! (Ivan Enderlin, 2014-09-23T15:37:04+02:00) 161 | * Remove `from`/`import` and update to PHP5.4. (Ivan Enderlin, 2014-09-23T15:32:36+02:00) 162 | * Declare array with […] and not (…). (Ivan Enderlin, 2014-09-23T14:58:18+02:00) 163 | 164 | # 0.14.09.17 165 | 166 | * Drop PHP5.3. (Ivan Enderlin, 2014-09-17T17:13:16+02:00) 167 | * Add the installation section. (Ivan Enderlin, 2014-09-17T17:13:05+02:00) 168 | 169 | (first snapshot) 170 | -------------------------------------------------------------------------------- /Context.php: -------------------------------------------------------------------------------- 1 | _data = $data; 73 | 74 | return; 75 | } 76 | 77 | /** 78 | * Set a data. 79 | * 80 | * @param string $id ID. 81 | * @param mixed $value Value. 82 | * @return void 83 | */ 84 | public function offsetSet($id, $value) 85 | { 86 | $this->_data[$id] = $value; 87 | 88 | return; 89 | } 90 | 91 | /** 92 | * Get a data. 93 | * 94 | * @param string $id ID. 95 | * @return mixed 96 | * @throws \Hoa\Ruler\Exception 97 | */ 98 | public function offsetGet($id) 99 | { 100 | if (false === array_key_exists($id, $this->_data)) { 101 | throw new Exception( 102 | 'Identifier %s does not exist in the context.', 103 | 0, 104 | $id 105 | ); 106 | } 107 | 108 | $value = $this->_data[$id]; 109 | 110 | if ($value instanceof DynamicCallable) { 111 | return $value($this); 112 | } 113 | 114 | if (true === is_callable($value)) { 115 | if (true === is_string($value) && 116 | false === in_array(strtolower($value), get_defined_functions()['user'])) { 117 | return $value; 118 | } 119 | 120 | $value = $this->_data[$id] = $value($this); 121 | } 122 | 123 | return $value; 124 | } 125 | 126 | /** 127 | * Check if a data exists. 128 | * 129 | * @return bool 130 | */ 131 | public function offsetExists($id) 132 | { 133 | return true === array_key_exists($id, $this->_data); 134 | } 135 | 136 | /** 137 | * Unset a data. 138 | * 139 | * @param string $id ID. 140 | * @return void 141 | */ 142 | public function offsetUnset($id) 143 | { 144 | unset($this->_data[$id]); 145 | 146 | return; 147 | } 148 | 149 | /** 150 | * Get a data as context property 151 | * 152 | * @param string $name 153 | * @return mixed 154 | * @throws \Hoa\Ruler\Exception 155 | */ 156 | public function __get($name) 157 | { 158 | return $this->offsetGet($name); 159 | } 160 | 161 | /** 162 | * Set a data as context property 163 | * 164 | * @param string $id ID. 165 | * @param mixed $value Value. 166 | * @return void 167 | */ 168 | public function __set($id, $value) 169 | { 170 | $this->offsetSet($id, $value); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /DynamicCallable.php: -------------------------------------------------------------------------------- 1 | logical_operation_primary() #operation )? 82 | 83 | operand: 84 | ::parenthesis_:: logical_operation_primary() ::_parenthesis:: 85 | | value() 86 | 87 | value: 88 | ::not:: logical_operation_primary() #not 89 | | | | | | | 90 | | array_declaration() 91 | | chain() 92 | 93 | chain: 94 | ( variable() | function_call() ) 95 | ( ( array_access() | object_access() ) #variable_access )* 96 | 97 | variable: 98 | 99 | 100 | #array_access: 101 | ::bracket_:: value() ::_bracket:: 102 | 103 | object_access: 104 | ::dot:: ( #attribute_access | function_call() #method_access ) 105 | 106 | #array_declaration: 107 | ::bracket_:: value() ( ::comma:: value() )* ::_bracket:: 108 | 109 | #function_call: 110 | ::parenthesis_:: 111 | ( logical_operation_primary() ( ::comma:: logical_operation_primary() )* )? 112 | ::_parenthesis:: 113 | -------------------------------------------------------------------------------- /Model/Bag/Bag.php: -------------------------------------------------------------------------------- 1 | visit($this, $handle, $eldnah); 66 | } 67 | } 68 | 69 | /** 70 | * Flex entity. 71 | */ 72 | Consistency::flexEntity('Hoa\Ruler\Model\Bag\Bag'); 73 | -------------------------------------------------------------------------------- /Model/Bag/Context.php: -------------------------------------------------------------------------------- 1 | _id = $id; 110 | 111 | return; 112 | } 113 | 114 | /** 115 | * Call an index (variable[indexA][indexB][indexC]). 116 | * 117 | * @param mixed $index Index (a bag, a scalar or an array). 118 | * @return \Hoa\Ruler\Model\Bag\Context 119 | */ 120 | public function index($index) 121 | { 122 | if (is_scalar($index) || null === $index) { 123 | $index = new Scalar($index); 124 | } elseif (is_array($index)) { 125 | $index = new RulerArray($index); 126 | } 127 | 128 | $this->_dimensions[] = [ 129 | static::ACCESS_TYPE => static::ARRAY_ACCESS, 130 | static::ACCESS_VALUE => $index 131 | ]; 132 | 133 | return $this; 134 | } 135 | 136 | /** 137 | * Call an attribute (variable.attrA.attrB). 138 | * 139 | * @param string $attribute Attribute name. 140 | * @return \Hoa\Ruler\Model\Bag\Context 141 | */ 142 | public function attribute($attribute) 143 | { 144 | $this->_dimensions[] = [ 145 | static::ACCESS_TYPE => static::ATTRIBUTE_ACCESS, 146 | static::ACCESS_VALUE => $attribute 147 | ]; 148 | 149 | return $this; 150 | } 151 | 152 | /** 153 | * Call a method (variable.foo().bar().baz()). 154 | * 155 | * @param \Hoa\Ruler\Model\Operator $method Method to call. 156 | * @return \Hoa\Ruler\Model\Bag\Context 157 | */ 158 | public function call(Ruler\Model\Operator $method) 159 | { 160 | $this->_dimensions[] = [ 161 | static::ACCESS_TYPE => static::METHOD_ACCESS, 162 | static::ACCESS_VALUE => $method 163 | ]; 164 | 165 | return $this; 166 | } 167 | 168 | /** 169 | * Get all dimensions. 170 | * 171 | * @return array 172 | */ 173 | public function getDimensions() 174 | { 175 | return $this->_dimensions; 176 | } 177 | 178 | /** 179 | * Get ID. 180 | * 181 | * @return string 182 | */ 183 | public function getId() 184 | { 185 | return $this->_id; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /Model/Bag/RulerArray.php: -------------------------------------------------------------------------------- 1 | _array = $data; 78 | 79 | return; 80 | } 81 | 82 | /** 83 | * Get array. 84 | * 85 | * @return array 86 | */ 87 | public function getArray() 88 | { 89 | return $this->_array; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Model/Bag/Scalar.php: -------------------------------------------------------------------------------- 1 | _value = $value; 66 | 67 | return; 68 | } 69 | 70 | /** 71 | * Get content of the bag. 72 | * 73 | * @return scalar 74 | */ 75 | public function getValue() 76 | { 77 | return $this->_value; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Model/Model.php: -------------------------------------------------------------------------------- 1 | $name = $value; 80 | } 81 | 82 | if (is_scalar($value)) { 83 | $value = new Bag\Scalar($value); 84 | } elseif (is_array($value)) { 85 | $value = new Bag\RulerArray($value); 86 | } 87 | 88 | $this->_root = $value; 89 | 90 | return; 91 | } 92 | 93 | /** 94 | * Get the expression. 95 | * 96 | * @return \Hoa\Ruler\Model\Operator 97 | */ 98 | public function getExpression() 99 | { 100 | return $this->_root; 101 | } 102 | 103 | /** 104 | * Declare a function. 105 | * 106 | * @param string $name Name. 107 | * @param mixed … 108 | * @return \Hoa\Ruler\Model\Operator 109 | */ 110 | public function func() 111 | { 112 | $arguments = func_get_args(); 113 | $name = array_shift($arguments); 114 | 115 | return $this->_operator($name, $arguments, true); 116 | } 117 | 118 | /** 119 | * Declare an operation. 120 | * 121 | * @param string $name Name. 122 | * @param array $arguments Arguments. 123 | * @return \Hoa\Ruler\Model\Operator 124 | */ 125 | public function operation($name, array $arguments) 126 | { 127 | return $this->_operator($name, $arguments, false); 128 | } 129 | 130 | /** 131 | * Create an operator object. 132 | * 133 | * @param string $name Name. 134 | * @param array $arguments Arguments. 135 | * @param bool $isFunction Whether it is a function or not. 136 | * @return \Hoa\Ruler\Model\Operator 137 | */ 138 | public function _operator($name, array $arguments, $isFunction) 139 | { 140 | return new Operator(mb_strtolower($name), $arguments, $isFunction); 141 | } 142 | 143 | /** 144 | * Declare an operation. 145 | * 146 | * @param string $name Name. 147 | * @param array $arguments Arguments. 148 | * @return \Hoa\Ruler\Model\Operator 149 | */ 150 | public function __call($name, array $arguments) 151 | { 152 | return $this->operation($name, $arguments); 153 | } 154 | 155 | /** 156 | * Declare a variable. 157 | * 158 | * @parma string $id ID. 159 | * @return \Hoa\Ruler\Model\Bag\Context 160 | */ 161 | public function variable($id) 162 | { 163 | return new Bag\Context($id); 164 | } 165 | 166 | /** 167 | * Accept a visitor. 168 | * 169 | * @param \Hoa\Visitor\Visit $visitor Visitor. 170 | * @param mixed &$handle Handle (reference). 171 | * @param mixed $eldnah Handle (no reference). 172 | * @return mixed 173 | */ 174 | public function accept( 175 | Visitor\Visit $visitor, 176 | &$handle = null, 177 | $eldnah = null 178 | ) { 179 | return $visitor->visit($this, $handle, $eldnah); 180 | } 181 | 182 | /** 183 | * Transform the object as a string. 184 | * 185 | * @return string 186 | */ 187 | public function __toString() 188 | { 189 | if (null === static::$_compiler) { 190 | static::$_compiler = new Ruler\Visitor\Compiler(); 191 | } 192 | 193 | return static::$_compiler->visit($this); 194 | } 195 | } 196 | 197 | /** 198 | * Flex entity. 199 | */ 200 | Consistency::flexEntity('Hoa\Ruler\Model\Model'); 201 | -------------------------------------------------------------------------------- /Model/Operator.php: -------------------------------------------------------------------------------- 1 | setName($name); 109 | $this->setArguments($arguments); 110 | $this->setFunction($isFunction); 111 | 112 | return; 113 | } 114 | 115 | /** 116 | * Set name. 117 | * 118 | * @param string $name Name. 119 | * @return string 120 | */ 121 | protected function setName($name) 122 | { 123 | $old = $this->_name; 124 | $this->_name = $name; 125 | 126 | $this->setLaziness('and' === $name || 'or' === $name); 127 | 128 | return $old; 129 | } 130 | 131 | /** 132 | * Get name. 133 | * 134 | * @return string 135 | */ 136 | public function getName() 137 | { 138 | return $this->_name; 139 | } 140 | 141 | /** 142 | * Set arguments. 143 | * 144 | * @param array $arguments Arguments. 145 | * @return array 146 | */ 147 | protected function setArguments(array $arguments) 148 | { 149 | foreach ($arguments as &$argument) { 150 | if (is_scalar($argument) || null === $argument) { 151 | $argument = new Bag\Scalar($argument); 152 | } elseif (is_array($argument)) { 153 | $argument = new Bag\RulerArray($argument); 154 | } 155 | } 156 | 157 | $old = $this->_arguments; 158 | $this->_arguments = $arguments; 159 | 160 | return $old; 161 | } 162 | 163 | /** 164 | * Get arguments. 165 | * 166 | * @return array 167 | */ 168 | public function getArguments() 169 | { 170 | return $this->_arguments; 171 | } 172 | 173 | /** 174 | * Set whether the operator is a function or not. 175 | * 176 | * @param bool $isFunction Is a function or not. 177 | * @return bool 178 | */ 179 | protected function setFunction($isFunction) 180 | { 181 | $old = $this->_function; 182 | $this->_function = $isFunction; 183 | 184 | return $old; 185 | } 186 | 187 | /** 188 | * Check if the operator is a function or not. 189 | * 190 | * @return bool 191 | */ 192 | public function isFunction() 193 | { 194 | return $this->_function; 195 | } 196 | 197 | /** 198 | * Set whether the operator is lazy or not. 199 | * 200 | * @param bool $isLazy Is a lazy operator or not. 201 | * @return bool 202 | */ 203 | protected function setLaziness($isLazy) 204 | { 205 | $old = $this->_laziness; 206 | $this->_laziness = $isLazy; 207 | 208 | return $old; 209 | } 210 | 211 | /** 212 | * Check if the operator is lazy or not. 213 | * 214 | * @return bool 215 | */ 216 | public function isLazy() 217 | { 218 | return $this->_laziness; 219 | } 220 | 221 | /** 222 | * Check whether we should break the lazy evaluation or not. 223 | * 224 | * @param mixed $value Value to check. 225 | * @return bool 226 | */ 227 | public function shouldBreakLazyEvaluation($value) 228 | { 229 | switch ($this->getName()) { 230 | case 'and': 231 | if (false === $value) { 232 | return self::LAZY_BREAK; 233 | } 234 | 235 | break; 236 | 237 | case 'or': 238 | if (true === $value) { 239 | return self::LAZY_BREAK; 240 | } 241 | 242 | break; 243 | } 244 | 245 | return self::LAZY_CONTINUE; 246 | } 247 | 248 | /** 249 | * Check if the operator is a token of the grammar or not. 250 | * 251 | * @param string $operator Operator. 252 | * @return bool 253 | */ 254 | public static function isToken($operator) 255 | { 256 | static $_tokens = ['not', 'and', 'or', 'xor']; 257 | 258 | return true === in_array($operator, $_tokens); 259 | } 260 | 261 | /** 262 | * Accept a visitor. 263 | * 264 | * @param \Hoa\Visitor\Visit $visitor Visitor. 265 | * @param mixed &$handle Handle (reference). 266 | * @param mixed $eldnah Handle (no reference). 267 | * @return mixed 268 | */ 269 | public function accept( 270 | Visitor\Visit $visitor, 271 | &$handle = null, 272 | $eldnah = null 273 | ) { 274 | return $visitor->visit($this, $handle, $eldnah); 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Hoa 3 |

4 | 5 | --- 6 | 7 |

8 | Build status 9 | Code coverage 10 | Packagist 11 | License 12 |

13 |

14 | Hoa is a modular, extensible and 15 | structured set of PHP libraries.
16 | Moreover, Hoa aims at being a bridge between industrial and research worlds. 17 |

18 | 19 | # Hoa\Ruler 20 | 21 | [![Help on IRC](https://img.shields.io/badge/help-%23hoaproject-ff0066.svg)](https://webchat.freenode.net/?channels=#hoaproject) 22 | [![Help on Gitter](https://img.shields.io/badge/help-gitter-ff0066.svg)](https://gitter.im/hoaproject/central) 23 | [![Documentation](https://img.shields.io/badge/documentation-hack_book-ff0066.svg)](https://central.hoa-project.net/Documentation/Library/Ruler) 24 | [![Board](https://img.shields.io/badge/organisation-board-ff0066.svg)](https://waffle.io/hoaproject/ruler) 25 | 26 | This library allows to manipulate a rule engine. Rules can be written 27 | by using a dedicated language, very close to SQL. Therefore, they can 28 | be written by a user and saved in a database. 29 | 30 | Such rules are useful, for example, for commercial solutions that need 31 | to manipulate promotion or special offer rules written by a user. To 32 | quote [Wikipedia](https://en.wikipedia.org/wiki/Business_rules_engine): 33 | 34 | > A business rules engine is a software system that executes one or more 35 | > business rules in a runtime production environment. The rules might come from 36 | > legal regulation (“An employee can be fired for any reason or no reason but 37 | > not for an illegal reason”), company policy (“All customers that spend more 38 | > than $100 at one time will receive a 10% discount”), or other sources. A 39 | > business rule system enables these company policies and other operational 40 | > decisions to be defined, tested, executed and maintained separately from 41 | > application code. 42 | 43 | [Learn more](https://central.hoa-project.net/Documentation/Library/Ruler). 44 | 45 | ## Installation 46 | 47 | With [Composer](https://getcomposer.org/), to include this library into 48 | your dependencies, you need to 49 | require [`hoa/ruler`](https://packagist.org/packages/hoa/ruler): 50 | 51 | ```sh 52 | $ composer require hoa/ruler '~2.0' 53 | ``` 54 | 55 | For more installation procedures, please read [the Source 56 | page](https://hoa-project.net/Source.html). 57 | 58 | ## Testing 59 | 60 | Before running the test suites, the development dependencies must be installed: 61 | 62 | ```sh 63 | $ composer install 64 | ``` 65 | 66 | Then, to run all the test suites: 67 | 68 | ```sh 69 | $ vendor/bin/hoa test:run 70 | ``` 71 | 72 | For more information, please read the [contributor 73 | guide](https://hoa-project.net/Literature/Contributor/Guide.html). 74 | 75 | ## Quick usage 76 | 77 | As a quick overview, we propose to see a very simple example that manipulates a 78 | simple rule with a simple context. After, we will add a new operator in the 79 | rule. And finally, we will see how to save a rule in a database. 80 | 81 | ### Three steps 82 | 83 | So first, we create a context with two variables: `group` and `points`, and we 84 | then assert a rule. A context holds values to concretize a rule. A value can 85 | also be the result of a callable. Thus: 86 | 87 | ```php 88 | $ruler = new Hoa\Ruler\Ruler(); 89 | 90 | // 1. Write a rule. 91 | $rule = 'group in ["customer", "guest"] and points > 30'; 92 | 93 | // 2. Create a context. 94 | $context = new Hoa\Ruler\Context(); 95 | $context['group'] = 'customer'; 96 | $context['points'] = function () { 97 | return 42; 98 | }; 99 | 100 | // 3. Assert! 101 | var_dump( 102 | $ruler->assert($rule, $context) 103 | ); 104 | 105 | /** 106 | * Will output: 107 | * bool(true) 108 | */ 109 | ``` 110 | 111 | In the next example, we have a `User` object and a context that is populated 112 | dynamically (when the `user` variable is concretized, two new variables, `group` 113 | and `points` are created). Moreover, we will create a new operator/function 114 | called `logged`. There is no difference between an operator and a function 115 | except that an operator has two operands (so arguments). 116 | 117 | ### Adding operators and functions 118 | 119 | For now, we have the following operators/functions by default: `and`, `or`, 120 | `xor`, `not`, `=` (`is` as an alias), `!=`, `>`, `>=`, `<`, `<=`, `in` and 121 | `sum`. We can add our own by different way. The simplest and volatile one is 122 | given in the following example. Thus: 123 | 124 | ```php 125 | // The User object. 126 | class User 127 | { 128 | const DISCONNECTED = 0; 129 | const CONNECTED = 1; 130 | 131 | public $group = 'customer'; 132 | public $points = 42; 133 | protected $_status = 1; 134 | 135 | public function getStatus() 136 | { 137 | return $this->_status; 138 | } 139 | } 140 | 141 | $ruler = new Hoa\Ruler\Ruler(); 142 | 143 | // New rule. 144 | $rule = 'logged(user) and group in ["customer", "guest"] and points > 30'; 145 | 146 | // New context. 147 | $context = new Hoa\Ruler\Context(); 148 | $context['user'] = function () use ($context) { 149 | $user = new User(); 150 | $context['group'] = $user->group; 151 | $context['points'] = $user->points; 152 | 153 | return $user; 154 | }; 155 | 156 | // We add the logged() operator. 157 | $ruler->getDefaultAsserter()->setOperator('logged', function (User $user) { 158 | return $user::CONNECTED === $user->getStatus(); 159 | }); 160 | 161 | // Finally, we assert the rule. 162 | var_dump( 163 | $ruler->assert($rule, $context) 164 | ); 165 | 166 | /** 167 | * Will output: 168 | * bool(true) 169 | */ 170 | ``` 171 | 172 | Also, if a variable in the context is an array, we can access to its values from 173 | a rule with the same syntax as PHP. For example, if the `a` variable is an 174 | array, we can write `a[0]` to access to the value associated to the `0` key. It 175 | works as an hashmap (PHP array implementation), so we can have strings & co. as 176 | keys. In the same way, if a variable is an object, we can call a method on it. 177 | For example, if the `a` variable is an array where the value associated to the 178 | first key is an object with a `foo` method, we can write: `a[0].foo(b)` where 179 | `b` is another variable in the context. Also, we can access to the public 180 | attributes of an object. Obviously, we can mixe array and object accesses. 181 | Please, take a look at the grammar (`hoa://Library/Ruler/Grammar.pp`) to see all 182 | the possible constructions. 183 | 184 | ### Saving a rule 185 | 186 | Now, we have two options to save the rule, for example, in a database. Either we 187 | save the rule as a string directly, or we will save the serialization of the 188 | rule which will avoid further interpretations. In the next example, we see how 189 | to serialize and unserialize a rule by using the `Hoa\Ruler\Ruler::interpret` 190 | static method: 191 | 192 | ```php 193 | $database->save( 194 | serialize( 195 | Hoa\Ruler\Ruler::interpret( 196 | 'logged(user) and group in ["customer", "guest"] and points > 30' 197 | ) 198 | ) 199 | ); 200 | ``` 201 | 202 | And for next executions: 203 | 204 | ```php 205 | $rule = unserialize($database->read()); 206 | var_dump( 207 | $ruler->assert($rule, $context) 208 | ); 209 | ``` 210 | 211 | When a rule is interpreted, its object model is created. We serialize and 212 | unserialize this model. To see the PHP code needed to create such a model, we 213 | can print the model itself (as an example). Thus: 214 | 215 | ```php 216 | echo Hoa\Ruler\Ruler::interpret( 217 | 'logged(user) and group in ["customer", "guest"] and points > 30' 218 | ); 219 | 220 | /** 221 | * Will output: 222 | * $model = new \Hoa\Ruler\Model(); 223 | * $model->expression = 224 | * $model->and( 225 | * $model->func( 226 | * 'logged', 227 | * $model->variable('user') 228 | * ), 229 | * $model->and( 230 | * $model->in( 231 | * $model->variable('group'), 232 | * [ 233 | * 'customer', 234 | * 'guest' 235 | * ] 236 | * ), 237 | * $model->{'>'}( 238 | * $model->variable('points'), 239 | * 30 240 | * ) 241 | * ) 242 | * ); 243 | */ 244 | ``` 245 | 246 | Have fun! 247 | 248 | ## Documentation 249 | 250 | The 251 | [hack book of `Hoa\Ruler`](https://central.hoa-project.net/Documentation/Library/Ruler) contains 252 | detailed information about how to use this library and how it works. 253 | 254 | To generate the documentation locally, execute the following commands: 255 | 256 | ```sh 257 | $ composer require --dev hoa/devtools 258 | $ vendor/bin/hoa devtools:documentation --open 259 | ``` 260 | 261 | More documentation can be found on the project's website: 262 | [hoa-project.net](https://hoa-project.net/). 263 | 264 | ## Getting help 265 | 266 | There are mainly two ways to get help: 267 | 268 | * On the [`#hoaproject`](https://webchat.freenode.net/?channels=#hoaproject) 269 | IRC channel, 270 | * On the forum at [users.hoa-project.net](https://users.hoa-project.net). 271 | 272 | ## Contribution 273 | 274 | Do you want to contribute? Thanks! A detailed [contributor 275 | guide](https://hoa-project.net/Literature/Contributor/Guide.html) explains 276 | everything you need to know. 277 | 278 | ## License 279 | 280 | Hoa is under the New BSD License (BSD-3-Clause). Please, see 281 | [`LICENSE`](https://hoa-project.net/LICENSE) for details. 282 | 283 | ## Related projects 284 | 285 | The following projects are using this library: 286 | 287 | * [RulerZ](https://github.com/K-Phoen/rulerz), Powerful implementation of the 288 | Specification pattern in PHP, 289 | * [ownCloud](https://owncloud.org/), A safe home for all your data, 290 | * [PhpMetrics](http://www.phpmetrics.org/), Static analysis tool for PHP, 291 | * [`hiqdev/php-billing`](https://github.com/hiqdev/php-billing), a billing library in PHP 292 | * [`atoum/ruler-extension`](https://github.com/atoum/ruler-extension), This 293 | extension allows to filter test results in [atoum](http://atoum.org/). 294 | -------------------------------------------------------------------------------- /Ruler.php: -------------------------------------------------------------------------------- 1 | getAsserter($context)->visit($rule); 103 | } 104 | 105 | /** 106 | * Short interpreter. 107 | * 108 | * @param string $rule Rule. 109 | * @return \Hoa\Ruler\Model 110 | * @throws \Hoa\Ruler\Exception 111 | */ 112 | public static function interpret($rule) 113 | { 114 | return static::getInterpreter()->visit( 115 | static::getCompiler()->parse($rule) 116 | ); 117 | } 118 | 119 | /** 120 | * Get interpreter. 121 | * 122 | * @return \Hoa\Ruler\Visitor\Interpreter 123 | */ 124 | public static function getInterpreter() 125 | { 126 | if (null === static::$_interpreter) { 127 | static::$_interpreter = new Visitor\Interpreter(); 128 | } 129 | 130 | return static::$_interpreter; 131 | } 132 | 133 | /** 134 | * Set current asserter. 135 | * 136 | * @param \Hoa\Visitor\Visit $visitor Visitor. 137 | * @return \Hoa\Visitor\Visit 138 | */ 139 | public function setAsserter(HoaVisitor\Visit $visitor) 140 | { 141 | $old = $this->_asserter; 142 | $this->_asserter = $visitor; 143 | 144 | return $old; 145 | } 146 | 147 | /** 148 | * Get asserter. 149 | * 150 | * @param \Hoa\Ruler\Context $context Context. 151 | * @return \Hoa\Visitor\Visit 152 | */ 153 | public function getAsserter(Context $context = null) 154 | { 155 | if (null === $asserter = $this->_asserter) { 156 | return static::getDefaultAsserter($context); 157 | } 158 | 159 | if (null !== $context) { 160 | $asserter->setContext($context); 161 | } 162 | 163 | return $asserter; 164 | } 165 | 166 | /** 167 | * Get default asserter. 168 | * 169 | * @param \Hoa\Ruler\Context $context Context. 170 | * @return \Hoa\Ruler\Visitor\Asserter 171 | */ 172 | public static function getDefaultAsserter(Context $context = null) 173 | { 174 | if (null === static::$_defaultAsserter) { 175 | static::$_defaultAsserter = new Visitor\Asserter($context); 176 | } 177 | 178 | if (null !== $context) { 179 | static::$_defaultAsserter->setContext($context); 180 | } 181 | 182 | return static::$_defaultAsserter; 183 | } 184 | 185 | /** 186 | * Get compiler. 187 | * 188 | * @return \Hoa\Compiler\Llk\Parser 189 | */ 190 | public static function getCompiler() 191 | { 192 | if (null === static::$_compiler) { 193 | static::$_compiler = Compiler\Llk::load( 194 | new File\Read('hoa://Library/Ruler/Grammar.pp') 195 | ); 196 | } 197 | 198 | return static::$_compiler; 199 | } 200 | } 201 | 202 | /** 203 | * Flex entity. 204 | */ 205 | Consistency::flexEntity('Hoa\Ruler\Ruler'); 206 | -------------------------------------------------------------------------------- /Test/Integration/Documentation.php: -------------------------------------------------------------------------------- 1 | given( 56 | $ruler = new LUT(), 57 | $rule = 'group in ["customer", "guest"] and points > 30' 58 | ); 59 | 60 | $this->next_case_classical($ruler, $rule); 61 | } 62 | 63 | public function next_case_classical($ruler, $rule) 64 | { 65 | $this 66 | ->given( 67 | $context = new LUT\Context(), 68 | $context['group'] = $this->sample( 69 | $this->realdom->regex('/customer|guest/') 70 | ), 71 | $context['points'] = function () { 72 | return 42; 73 | } 74 | ) 75 | ->when($result = $ruler->assert($rule, $context)) 76 | ->then 77 | ->boolean($result) 78 | ->isTrue() 79 | 80 | ->given($context['points'] = 29) 81 | ->when($result = $ruler->assert($rule, $context)) 82 | ->then 83 | ->boolean($result) 84 | ->isFalse(); 85 | } 86 | 87 | public function case_new_operators() 88 | { 89 | $this 90 | ->given( 91 | $user = new \Mock\StdClass(), 92 | $user->group = 'customer', 93 | $user->points = 42, 94 | $user->status = true, 95 | $ruler = new LUT(), 96 | $rule = 'logged(user) and group in ["customer", "guest"] and points > 30', 97 | $context = new LUT\Context(), 98 | $context['user'] = function () use ($user, $context) { 99 | $context['group'] = $user->group; 100 | $context['points'] = $user->points; 101 | 102 | return $user; 103 | } 104 | ) 105 | ->when( 106 | $ruler->getDefaultAsserter()->setOperator('logged', function ($user) { 107 | return $user->status; 108 | }), 109 | $result = $ruler->assert($rule, $context) 110 | ) 111 | ->then 112 | ->boolean($result) 113 | ->isTrue() 114 | 115 | ->given($user->status = false) 116 | ->when($result = $ruler->assert($rule, $context)) 117 | ->then 118 | ->boolean($result) 119 | ->isFalse(); 120 | } 121 | 122 | public function case_interpret() 123 | { 124 | $this 125 | ->given( 126 | $model = LUT::interpret('group in ["customer", "guest"] and points > 30') 127 | ) 128 | ->when($ledom = unserialize(serialize($model))) 129 | ->then 130 | ->object($model) 131 | ->isEqualTo($ledom); 132 | 133 | $this->next_case_classical(new LUT(), $model); 134 | } 135 | 136 | public function case_compile() 137 | { 138 | $expectedResult = <<<'RESULT' 139 | $model = new \Hoa\Ruler\Model(); 140 | $model->expression = 141 | $model->and( 142 | $model->func( 143 | 'logged', 144 | $model->variable('user') 145 | ), 146 | $model->and( 147 | $model->in( 148 | $model->variable('group'), 149 | [ 150 | 'customer', 151 | 'guest' 152 | ] 153 | ), 154 | $model->{'>'}( 155 | $model->variable('points'), 156 | 30 157 | ) 158 | ) 159 | ); 160 | RESULT; 161 | 162 | $this 163 | ->when($result = LUT::interpret( 164 | 'logged(user) and group in ["customer", "guest"] and points > 30' 165 | ) . '') 166 | ->then 167 | ->string($result) 168 | ->isEqualTo($expectedResult); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /Test/Integration/Model/Operator.php: -------------------------------------------------------------------------------- 1 | given( 56 | $ruler = new LUT(), 57 | $fExecuted = false, 58 | $gExecuted = false, 59 | $asserter = $ruler->getDefaultAsserter(), 60 | $asserter->setOperator( 61 | 'f', 62 | function ($a = false) use (&$fExecuted) { 63 | $fExecuted = true; 64 | 65 | return $a; 66 | } 67 | ), 68 | $asserter->setOperator( 69 | 'g', 70 | function ($b = false) use (&$gExecuted) { 71 | $gExecuted = true; 72 | 73 | return $b; 74 | } 75 | ), 76 | $rule = 'f(false) and g(true)' 77 | ) 78 | ->when($result = $ruler->assert($rule, new LUT\Context())) 79 | ->then 80 | ->boolean($result) 81 | ->isFalse() 82 | ->boolean($fExecuted) 83 | ->isTrue() 84 | ->boolean($gExecuted) 85 | ->isFalse() 86 | 87 | ->given( 88 | $fExecuted = false, 89 | $gExecuted = false, 90 | $rule = 'f(true) and g(true)' 91 | ) 92 | ->when($result = $ruler->assert($rule, new LUT\Context())) 93 | ->then 94 | ->boolean($result) 95 | ->isTrue() 96 | ->boolean($fExecuted) 97 | ->isTrue() 98 | ->boolean($gExecuted) 99 | ->isTrue() 100 | 101 | ->given( 102 | $fExecuted = false, 103 | $gExecuted = false, 104 | $rule = 'f(true) and g(false)' 105 | ) 106 | ->when($result = $ruler->assert($rule, new LUT\Context())) 107 | ->then 108 | ->boolean($result) 109 | ->isFalse() 110 | ->boolean($fExecuted) 111 | ->isTrue() 112 | ->boolean($gExecuted) 113 | ->isTrue() 114 | 115 | ->given( 116 | $fExecuted = false, 117 | $gExecuted = false, 118 | $rule = 'f(false) and g(false)' 119 | ) 120 | ->when($result = $ruler->assert($rule, new LUT\Context())) 121 | ->then 122 | ->boolean($result) 123 | ->isFalse() 124 | ->boolean($fExecuted) 125 | ->isTrue() 126 | ->boolean($gExecuted) 127 | ->isFalse(); 128 | } 129 | 130 | public function case_lazy_or() 131 | { 132 | $this 133 | ->given( 134 | $ruler = new LUT(), 135 | $fExecuted = false, 136 | $gExecuted = false, 137 | $asserter = $ruler->getDefaultAsserter(), 138 | $asserter->setOperator( 139 | 'f', 140 | function ($a) use (&$fExecuted) { 141 | $fExecuted = true; 142 | 143 | return $a; 144 | } 145 | ), 146 | $asserter->setOperator( 147 | 'g', 148 | function ($b) use (&$gExecuted) { 149 | $gExecuted = true; 150 | 151 | return $b; 152 | } 153 | ), 154 | $rule = 'f(false) or g(true)' 155 | ) 156 | ->when($result = $ruler->assert($rule, new LUT\Context())) 157 | ->then 158 | ->boolean($result) 159 | ->isTrue() 160 | ->boolean($fExecuted) 161 | ->isTrue() 162 | ->boolean($gExecuted) 163 | ->isTrue() 164 | 165 | ->given( 166 | $fExecuted = false, 167 | $gExecuted = false, 168 | $rule = 'f(true) or g(true)' 169 | ) 170 | ->when($result = $ruler->assert($rule, new LUT\Context())) 171 | ->then 172 | ->boolean($result) 173 | ->isTrue() 174 | ->boolean($fExecuted) 175 | ->isTrue() 176 | ->boolean($gExecuted) 177 | ->isFalse() 178 | 179 | ->given( 180 | $fExecuted = false, 181 | $gExecuted = false, 182 | $rule = 'f(true) or g(false)' 183 | ) 184 | ->when($result = $ruler->assert($rule, new LUT\Context())) 185 | ->then 186 | ->boolean($result) 187 | ->isTrue() 188 | ->boolean($fExecuted) 189 | ->isTrue() 190 | ->boolean($gExecuted) 191 | ->isFalse() 192 | 193 | ->given( 194 | $fExecuted = false, 195 | $gExecuted = false, 196 | $rule = 'f(false) or g(false)' 197 | ) 198 | ->when($result = $ruler->assert($rule, new LUT\Context())) 199 | ->then 200 | ->boolean($result) 201 | ->isFalse() 202 | ->boolean($fExecuted) 203 | ->isTrue() 204 | ->boolean($gExecuted) 205 | ->isTrue(); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /Test/Unit/Context.php: -------------------------------------------------------------------------------- 1 | when($context = new CUT()) 57 | ->then 58 | ->object($context) 59 | ->isInstanceOf('ArrayAccess'); 60 | } 61 | 62 | public function case_exists_set_get() 63 | { 64 | $this 65 | ->given( 66 | $key = 'foo', 67 | $value = 'bar', 68 | $context = new CUT() 69 | ) 70 | ->then 71 | ->boolean(isset($context[$key])) 72 | ->isFalse() 73 | 74 | ->when($context[$key] = $value) 75 | ->then 76 | ->boolean(isset($context[$key])) 77 | ->isTrue() 78 | ->string($context[$key]) 79 | ->isEqualTo($value); 80 | } 81 | 82 | public function case_exception_when_getting_unspecified_key() 83 | { 84 | $this 85 | ->given($context = new CUT()) 86 | ->exception(function () use ($context) { 87 | $context['foo']; 88 | }) 89 | ->isInstanceOf('Hoa\Ruler\Exception'); 90 | } 91 | 92 | public function case_unset() 93 | { 94 | $this 95 | ->given( 96 | $key = 'foo', 97 | $value = 'bar', 98 | $context = new CUT() 99 | ) 100 | ->then 101 | ->boolean(isset($context[$key])) 102 | ->isFalse() 103 | 104 | ->when($context[$key] = $value) 105 | ->then 106 | ->boolean(isset($context[$key])) 107 | ->isTrue() 108 | 109 | ->when(function () use ($context, $key) { 110 | unset($context[$key]); 111 | }) 112 | ->then 113 | ->boolean(isset($context[$key])) 114 | ->isFalse(); 115 | } 116 | 117 | public function case_callable_closure() 118 | { 119 | $this 120 | ->given( 121 | $context = new CUT(), 122 | $context['foo'] = function () { 123 | return fakeCallable(); 124 | } 125 | ) 126 | ->when($result = $context['foo']) 127 | ->then 128 | ->boolean($result) 129 | ->isTrue(); 130 | } 131 | 132 | public function case_callable_user_function() 133 | { 134 | $this 135 | ->given( 136 | $context = new CUT(), 137 | $context['foo'] = __NAMESPACE__ . '\fakeCallable' 138 | ) 139 | ->when($result = $context['foo']) 140 | ->then 141 | ->boolean($result) 142 | ->isTrue(); 143 | } 144 | 145 | public function case_callable_internal_function() 146 | { 147 | $this 148 | ->given( 149 | $context = new CUT(), 150 | $context['foo'] = 'var_dump' 151 | ) 152 | ->when($result = $context['foo']) 153 | ->then 154 | ->string($result) 155 | ->isEqualTo('var_dump'); 156 | } 157 | 158 | public function case_callable_method() 159 | { 160 | $this 161 | ->given( 162 | $context = new CUT(), 163 | $context['foo'] = [$this, 'fakeCallable'] 164 | ) 165 | ->when($result = $context['foo']) 166 | ->then 167 | ->boolean($result) 168 | ->isTrue(); 169 | } 170 | 171 | public function case_callable_xcallable() 172 | { 173 | $this 174 | ->given( 175 | $context = new CUT(), 176 | $context['foo'] = xcallable($this, 'fakeCallable') 177 | ) 178 | ->when($result = $context['foo']) 179 | ->then 180 | ->boolean($result) 181 | ->isTrue(); 182 | } 183 | 184 | public function case_callable_cache() 185 | { 186 | $this 187 | ->given( 188 | $context = new CUT(), 189 | $context['foo'] = function () { 190 | static $i = 0; 191 | 192 | return $i++; 193 | } 194 | ) 195 | ->when($result = $context['foo']) 196 | ->then 197 | ->integer($result) 198 | ->isEqualTo(0) 199 | 200 | ->when($result = $context['foo']) 201 | ->then 202 | ->integer($result) 203 | ->isEqualTo(0); 204 | } 205 | 206 | public function case_callable_no_cache() 207 | { 208 | $this 209 | ->given( 210 | $context = new CUT(), 211 | $context['foo'] = new LUT\DynamicCallable(function () { 212 | static $i = 0; 213 | 214 | return $i++; 215 | }) 216 | ) 217 | ->when($result = $context['foo']) 218 | ->then 219 | ->integer($result) 220 | ->isEqualTo(0) 221 | 222 | ->when($result = $context['foo']) 223 | ->then 224 | ->integer($result) 225 | ->isEqualTo(1); 226 | } 227 | 228 | public function case_callable_argument() 229 | { 230 | $this 231 | ->given( 232 | $self = $this, 233 | $context = new CUT(), 234 | $context['foo'] = function () use ($self, $context) { 235 | $arguments = func_get_args(); 236 | 237 | $self 238 | ->integer(count($arguments)) 239 | ->isEqualTo(1) 240 | ->object($arguments[0]) 241 | ->isIdenticalTo($context); 242 | } 243 | ) 244 | ->when($result = $context['foo']); 245 | } 246 | 247 | public function case_access_as_properties() 248 | { 249 | $this 250 | ->given( 251 | $self = $this, 252 | $context = new CUT(), 253 | $context['foo'] = 42 254 | ) 255 | ->when($result = $context->foo) 256 | ->then 257 | ->integer($result) 258 | ->isEqualTo(42) 259 | ->when($context->bar = 24) 260 | ->then 261 | ->integer($context['bar']) 262 | ->isEqualTo(24); 263 | } 264 | 265 | public function fakeCallable() 266 | { 267 | return fakeCallable(); 268 | } 269 | } 270 | 271 | function fakeCallable() 272 | { 273 | return true; 274 | } 275 | -------------------------------------------------------------------------------- /Test/Unit/DynamicCallable.php: -------------------------------------------------------------------------------- 1 | when($result = new SUT(function () {})) 57 | ->then 58 | ->object($result) 59 | ->isInstanceOf(Consistency\Xcallable::class); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Test/Unit/Exception/Asserter.php: -------------------------------------------------------------------------------- 1 | when($result = new SUT('foo')) 57 | ->then 58 | ->object($result) 59 | ->isInstanceOf(LUT\Exception::class); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Test/Unit/Exception/Exception.php: -------------------------------------------------------------------------------- 1 | when($result = new SUT('foo')) 57 | ->then 58 | ->object($result) 59 | ->isInstanceOf(HoaException::class); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Test/Unit/Exception/Interpreter.php: -------------------------------------------------------------------------------- 1 | when($result = new SUT('foo')) 57 | ->then 58 | ->object($result) 59 | ->isInstanceOf(LUT\Exception::class); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Test/Unit/Issue.php: -------------------------------------------------------------------------------- 1 | given( 56 | $ruler = new LUT(), 57 | $rule = 'variable', 58 | $context = new LUT\Context(), 59 | $context['variable'] = 'file' 60 | ) 61 | ->when(function () use ($ruler, $rule, $context) { 62 | $ruler->assert($rule, $context); 63 | }) 64 | ->error() 65 | ->notExists(); 66 | } 67 | 68 | public function case_github_70() 69 | { 70 | $this 71 | ->given( 72 | $ruler = new LUT(), 73 | $rule = 'variable["foo"] is null', 74 | $context = new LUT\Context(), 75 | $context['variable'] = ['foo' => null] 76 | ) 77 | ->when($result = $ruler->assert($rule, $context)) 78 | ->then 79 | ->boolean($result) 80 | ->isTrue(); 81 | } 82 | 83 | public function case_github_100_1() 84 | { 85 | $this 86 | ->given( 87 | $ruler = new LUT(), 88 | $rule = '(false and true) or true' 89 | ) 90 | ->when($result = $ruler->assert($rule)) 91 | ->then 92 | ->boolean($result) 93 | ->isTrue(); 94 | } 95 | 96 | public function case_github_100_2() 97 | { 98 | $this 99 | ->given( 100 | $ruler = new LUT(), 101 | $rule = 'false and true or true' 102 | ) 103 | ->when($result = $ruler->assert($rule)) 104 | ->then 105 | ->boolean($result) 106 | ->isTrue(); 107 | } 108 | 109 | public function case_github_100_3() 110 | { 111 | $this 112 | ->given( 113 | $ruler = new LUT(), 114 | $rule = 'true or true and false' 115 | ) 116 | ->when($result = $ruler->assert($rule)) 117 | ->then 118 | ->boolean($result) 119 | ->isTrue(); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Test/Unit/Model/Bag/Bag.php: -------------------------------------------------------------------------------- 1 | when($result = new SUT()) 57 | ->then 58 | ->object($result) 59 | ->isInstanceOf(Visitor\Element::class); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Test/Unit/Model/Bag/Context.php: -------------------------------------------------------------------------------- 1 | when($result = new SUT('foobar')) 57 | ->then 58 | ->object($result) 59 | ->isInstanceOf(LUT\Model\Bag::class); 60 | } 61 | 62 | public function case_constructor() 63 | { 64 | $this 65 | ->given($id = 'foobar') 66 | ->when($result = new SUT($id)) 67 | ->then 68 | ->string($result->getId()) 69 | ->isEqualTo($id) 70 | ->array($result->getDimensions()) 71 | ->isEmpty(); 72 | } 73 | 74 | public function case_scalar_index_from_root() 75 | { 76 | return $this->_case_index_from_root( 77 | 'baz', 78 | new LUT\Model\Bag\Scalar('baz') 79 | ); 80 | } 81 | 82 | public function case_array_index_from_root() 83 | { 84 | return $this->_case_index_from_root( 85 | ['baz'], 86 | new LUT\Model\Bag\RulerArray(['baz']) 87 | ); 88 | } 89 | 90 | public function case_bag_index_from_root() 91 | { 92 | return $this->_case_index_from_root( 93 | new LUT\Model\Bag\Scalar('baz'), 94 | new LUT\Model\Bag\Scalar('baz') 95 | ); 96 | } 97 | 98 | protected function _case_index_from_root($index, $expectedIndex) 99 | { 100 | $this 101 | ->given( 102 | $id = 'foobar', 103 | $context = new SUT($id) 104 | ) 105 | ->when($result = $context->index($index)) 106 | ->then 107 | ->object($result) 108 | ->isIdenticalTo($context) 109 | ->array($result->getDimensions()) 110 | ->isEqualTo([ 111 | [ 112 | SUT::ACCESS_TYPE => SUT::ARRAY_ACCESS, 113 | SUT::ACCESS_VALUE => $expectedIndex 114 | ] 115 | ]); 116 | } 117 | 118 | public function case_scalar_index() 119 | { 120 | return $this->_case_index( 121 | 'baz', 122 | new LUT\Model\Bag\Scalar('baz') 123 | ); 124 | } 125 | 126 | public function case_array_index() 127 | { 128 | return $this->_case_index( 129 | ['baz'], 130 | new LUT\Model\Bag\RulerArray(['baz']) 131 | ); 132 | } 133 | 134 | public function case_bag_index() 135 | { 136 | return $this->_case_index( 137 | new LUT\Model\Bag\Scalar('baz'), 138 | new LUT\Model\Bag\Scalar('baz') 139 | ); 140 | } 141 | 142 | protected function _case_index($index, $expectedIndex) 143 | { 144 | $this 145 | ->given( 146 | $id = 'foobar', 147 | $context = new SUT($id), 148 | $context->index(new LUT\Model\Bag\Scalar('qux')) 149 | ) 150 | ->when($result = $context->index($index)) 151 | ->then 152 | ->object($result) 153 | ->isIdenticalTo($context) 154 | ->array($result->getDimensions()) 155 | ->isEqualTo([ 156 | [ 157 | SUT::ACCESS_TYPE => SUT::ARRAY_ACCESS, 158 | SUT::ACCESS_VALUE => new LUT\Model\Bag\Scalar('qux') 159 | ], 160 | [ 161 | SUT::ACCESS_TYPE => SUT::ARRAY_ACCESS, 162 | SUT::ACCESS_VALUE => $expectedIndex 163 | ] 164 | ]); 165 | } 166 | 167 | public function case_attribute() 168 | { 169 | $this 170 | ->given( 171 | $id = 'foobar', 172 | $attribute = 'bazqux', 173 | $context = new SUT($id) 174 | ) 175 | ->when($result = $context->attribute($attribute)) 176 | ->then 177 | ->object($result) 178 | ->isIdenticalTo($context) 179 | ->array($result->getDimensions()) 180 | ->isEqualTo([ 181 | [ 182 | SUT::ACCESS_TYPE => SUT::ATTRIBUTE_ACCESS, 183 | SUT::ACCESS_VALUE => $attribute 184 | ] 185 | ]); 186 | } 187 | 188 | public function case_call() 189 | { 190 | $this 191 | ->given( 192 | $id = 'foobar', 193 | $method = new LUT\Model\Operator('f'), 194 | $context = new SUT($id) 195 | ) 196 | ->when($result = $context->call($method)) 197 | ->then 198 | ->object($result) 199 | ->isIdenticalTo($context) 200 | ->array($result->getDimensions()) 201 | ->isEqualTo([ 202 | [ 203 | SUT::ACCESS_TYPE => SUT::METHOD_ACCESS, 204 | SUT::ACCESS_VALUE => $method 205 | ] 206 | ]); 207 | } 208 | 209 | public function case_multiple_dimension_types() 210 | { 211 | $this 212 | ->given( 213 | $id = 'foobar', 214 | $index = 'baz', 215 | $attribute = 'qux', 216 | $method = new LUT\Model\Operator('f'), 217 | $context = new SUT($id), 218 | $context->attribute($attribute), 219 | $context->index($index), 220 | $context->call($method) 221 | ) 222 | ->when($result = $context->getDimensions()) 223 | ->then 224 | ->array($result) 225 | ->isEqualTo([ 226 | [ 227 | SUT::ACCESS_TYPE => SUT::ATTRIBUTE_ACCESS, 228 | SUT::ACCESS_VALUE => $attribute 229 | ], 230 | [ 231 | SUT::ACCESS_TYPE => SUT::ARRAY_ACCESS, 232 | SUT::ACCESS_VALUE => new LUT\Model\Bag\Scalar($index) 233 | ], 234 | [ 235 | SUT::ACCESS_TYPE => SUT::METHOD_ACCESS, 236 | SUT::ACCESS_VALUE => $method 237 | ] 238 | ]); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /Test/Unit/Model/Bag/RulerArray.php: -------------------------------------------------------------------------------- 1 | when($result = new SUT(['foobar'])) 57 | ->then 58 | ->object($result) 59 | ->isInstanceOf(LUT\Model\Bag::class); 60 | } 61 | 62 | public function case_constructor() 63 | { 64 | $this 65 | ->given($data = ['foo', ['bar'], new LUT\Model\Bag\Scalar('baz')]) 66 | ->when($result = new SUT($data)) 67 | ->then 68 | ->let($array = $result->getArray()) 69 | ->array($array) 70 | ->hasSize(count($data)) 71 | ->isEqualTo([ 72 | new LUT\Model\Bag\Scalar('foo'), 73 | new SUT([new LUT\Model\Bag\Scalar('bar')]), 74 | new LUT\Model\Bag\Scalar('baz') 75 | ]); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Test/Unit/Model/Bag/Scalar.php: -------------------------------------------------------------------------------- 1 | when($result = new SUT('foobar')) 57 | ->then 58 | ->object($result) 59 | ->isInstanceOf(LUT\Model\Bag::class); 60 | } 61 | 62 | public function case_constructor() 63 | { 64 | $this 65 | ->given($scalar = 'foobar') 66 | ->when($result = new SUT($scalar)) 67 | ->then 68 | ->string($result->getValue()) 69 | ->isEqualTo($scalar); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Test/Unit/Model/Model.php: -------------------------------------------------------------------------------- 1 | when($result = new SUT()) 58 | ->then 59 | ->object($result) 60 | ->isInstanceOf(Visitor\Element::class); 61 | } 62 | 63 | public function case_set_default() 64 | { 65 | $this 66 | ->given($model = new SUT()) 67 | ->when($result = $model->foo = 'bar') 68 | ->then 69 | ->string($result) 70 | ->isEqualTo('bar') 71 | ->string($model->foo) 72 | ->isEqualTo('bar'); 73 | } 74 | 75 | public function case_set_expression() 76 | { 77 | $this 78 | ->given($model = new SUT()) 79 | ->when($result = $model->expression = 'foo') 80 | ->then 81 | ->string($result) 82 | ->isEqualTo('foo') 83 | ->boolean(isset($model->expression)) 84 | ->isFalse(); 85 | } 86 | 87 | public function case_get_expression_is_a_scalar() 88 | { 89 | $this 90 | ->given( 91 | $model = new SUT(), 92 | $model->expression = 'foo' 93 | ) 94 | ->when($result = $model->getExpression()) 95 | ->then 96 | ->object($result) 97 | ->isEqualTo(new LUT\Model\Bag\Scalar('foo')); 98 | } 99 | 100 | public function case_get_expression_is_an_array() 101 | { 102 | $this 103 | ->given( 104 | $model = new SUT(), 105 | $model->expression = ['foo'] 106 | ) 107 | ->when($result = $model->getExpression()) 108 | ->then 109 | ->object($result) 110 | ->isEqualTo(new LUT\Model\Bag\RulerArray(['foo'])); 111 | } 112 | 113 | public function case_get_expression() 114 | { 115 | $this 116 | ->given( 117 | $model = new SUT(), 118 | $model->expression = new LUT\Model\Operator('f') 119 | ) 120 | ->when($result = $model->getExpression()) 121 | ->then 122 | ->object($result) 123 | ->isEqualTo(new LUT\Model\Operator('f')); 124 | } 125 | 126 | public function case_func() 127 | { 128 | $this 129 | ->given($model = new SUT()) 130 | ->when($result = $model->func('f', 'x', 42)) 131 | ->then 132 | ->object($result) 133 | ->isInstanceOf(LUT\Model\Operator::class) 134 | ->string($result->getName()) 135 | ->isEqualTo('f') 136 | ->array($result->getArguments()) 137 | ->isEqualTo([ 138 | new LUT\Model\Bag\Scalar('x'), 139 | new LUT\Model\Bag\Scalar(42) 140 | ]) 141 | ->boolean($result->isFunction()) 142 | ->isTrue() 143 | ->boolean($result->isLazy()) 144 | ->isFalse(); 145 | } 146 | 147 | public function case_operation() 148 | { 149 | $this 150 | ->given($model = new SUT()) 151 | ->when($result = $model->operation('f', ['x', 42])) 152 | ->then 153 | ->object($result) 154 | ->isInstanceOf(LUT\Model\Operator::class) 155 | ->string($result->getName()) 156 | ->isEqualTo('f') 157 | ->array($result->getArguments()) 158 | ->isEqualTo([ 159 | new LUT\Model\Bag\Scalar('x'), 160 | new LUT\Model\Bag\Scalar(42) 161 | ]) 162 | ->boolean($result->isFunction()) 163 | ->isFalse() 164 | ->boolean($result->isLazy()) 165 | ->isFalse(); 166 | } 167 | 168 | public function case_operator() 169 | { 170 | $this 171 | ->given($model = new SUT()) 172 | ->when($result = $model->_operator('f', ['x', 42], true)) 173 | ->then 174 | ->object($result) 175 | ->isInstanceOf(LUT\Model\Operator::class) 176 | ->string($result->getName()) 177 | ->isEqualTo('f') 178 | ->array($result->getArguments()) 179 | ->isEqualTo([ 180 | new LUT\Model\Bag\Scalar('x'), 181 | new LUT\Model\Bag\Scalar(42) 182 | ]) 183 | ->boolean($result->isFunction()) 184 | ->isTrue() 185 | ->boolean($result->isLazy()) 186 | ->isFalse(); 187 | } 188 | 189 | public function case_call() 190 | { 191 | $this 192 | ->given($model = new SUT()) 193 | ->when($result = $model->f('x', 42)) 194 | ->then 195 | ->object($result) 196 | ->isInstanceOf(LUT\Model\Operator::class) 197 | ->string($result->getName()) 198 | ->isEqualTo('f') 199 | ->array($result->getArguments()) 200 | ->isEqualTo([ 201 | new LUT\Model\Bag\Scalar('x'), 202 | new LUT\Model\Bag\Scalar(42) 203 | ]) 204 | ->boolean($result->isFunction()) 205 | ->isFalse() 206 | ->boolean($result->isLazy()) 207 | ->isFalse(); 208 | } 209 | 210 | public function case_variable() 211 | { 212 | $this 213 | ->given($model = new SUT()) 214 | ->when($result = $model->variable('x')) 215 | ->then 216 | ->object($result) 217 | ->isEqualTo(new LUT\Model\Bag\Context('x')); 218 | } 219 | 220 | public function case_to_string() 221 | { 222 | $this 223 | ->given( 224 | $model = new SUT(), 225 | $model->expression = $model->f('x', 42) 226 | ) 227 | ->when($result = $model->__toString()) 228 | ->then 229 | ->string($result) 230 | ->isEqualTo( 231 | '$model = new \Hoa\Ruler\Model();' . "\n" . 232 | '$model->expression =' . "\n" . 233 | ' $model->f(' . "\n" . 234 | ' \'x\',' . "\n" . 235 | ' 42' . "\n" . 236 | ' );' 237 | ); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /Test/Unit/Model/Operator.php: -------------------------------------------------------------------------------- 1 | given($name = 'foo') 58 | ->when($result = new SUT($name)) 59 | ->then 60 | ->object($result) 61 | ->isInstanceOf(Visitor\Element::class); 62 | } 63 | 64 | public function case_is_a_context() 65 | { 66 | $this 67 | ->given($name = 'foo') 68 | ->when($result = new SUT($name)) 69 | ->then 70 | ->object($result) 71 | ->isInstanceOf(LUT\Model\Bag\Context::class); 72 | } 73 | 74 | public function case_constructor() 75 | { 76 | $this 77 | ->given($name = 'foo') 78 | ->when($result = new SUT($name)) 79 | ->then 80 | ->string($result->getName()) 81 | ->isEqualTo($name) 82 | ->array($result->getArguments()) 83 | ->isEmpty() 84 | ->boolean($result->isFunction()) 85 | ->isTrue() 86 | ->boolean($result->isLazy()) 87 | ->isFalse(); 88 | } 89 | 90 | public function case_constructor_with_arguments() 91 | { 92 | $this 93 | ->given( 94 | $name = 'foo', 95 | $arguments = [new LUT\Model\Bag\Scalar(42)] 96 | ) 97 | ->when($result = new SUT($name, $arguments)) 98 | ->then 99 | ->string($result->getName()) 100 | ->isEqualTo($name) 101 | ->array($result->getArguments()) 102 | ->isEqualTo($arguments) 103 | ->boolean($result->isFunction()) 104 | ->isTrue() 105 | ->boolean($result->isLazy()) 106 | ->isFalse(); 107 | } 108 | 109 | public function case_constructor_with_arguments_and_function_flag() 110 | { 111 | $this 112 | ->given( 113 | $name = 'foo', 114 | $arguments = [new LUT\Model\Bag\Scalar(42)], 115 | $isFunction = false 116 | ) 117 | ->when($result = new SUT($name, $arguments, $isFunction)) 118 | ->then 119 | ->string($result->getName()) 120 | ->isEqualTo($name) 121 | ->array($result->getArguments()) 122 | ->isEqualTo($arguments) 123 | ->boolean($result->isFunction()) 124 | ->isFalse() 125 | ->boolean($result->isLazy()) 126 | ->isFalse(); 127 | } 128 | 129 | public function case_set_name() 130 | { 131 | $this 132 | ->given( 133 | $oldName = 'foo', 134 | $name = 'bar', 135 | $operator = new SUT('foo') 136 | ) 137 | ->when($result = $this->invoke($operator)->setName($name)) 138 | ->then 139 | ->string($result) 140 | ->isEqualTo($oldName) 141 | ->boolean($operator->isLazy()) 142 | ->isFalse(); 143 | } 144 | 145 | public function case_set_name_with_the_and_operator_for_auto_laziness() 146 | { 147 | return $this->_case_set_name_with_auto_laziness('and'); 148 | } 149 | 150 | public function case_set_name_with_the_or_operator_for_auto_laziness() 151 | { 152 | return $this->_case_set_name_with_auto_laziness('or'); 153 | } 154 | 155 | protected function _case_set_name_with_auto_laziness($name) 156 | { 157 | $this 158 | ->given($operator = new SUT('foo')) 159 | ->when($result = $this->invoke($operator)->setName($name)) 160 | ->then 161 | ->string($result) 162 | ->isEqualTo('foo') 163 | ->boolean($operator->isLazy()) 164 | ->isTrue(); 165 | } 166 | 167 | public function case_get_name() 168 | { 169 | $this 170 | ->given( 171 | $name = 'bar', 172 | $operator = new SUT('foo'), 173 | $this->invoke($operator)->setName($name) 174 | ) 175 | ->when($result = $operator->getName()) 176 | ->then 177 | ->string($result) 178 | ->isEqualTo($name); 179 | } 180 | 181 | public function case_set_arguments() 182 | { 183 | $this 184 | ->given( 185 | $operator = new SUT('foo'), 186 | $arguments = ['foo', [42], new LUT\Model\Bag\Scalar('baz')] 187 | ) 188 | ->when($result = $this->invoke($operator)->setArguments($arguments)) 189 | ->then 190 | ->array($result) 191 | ->isEmpty(); 192 | } 193 | 194 | public function case_set_arguments_not_additive() 195 | { 196 | $this 197 | ->given( 198 | $operator = new SUT('foo'), 199 | $argumentsA = [new LUT\Model\Bag\Scalar('foo')], 200 | $argumentsB = [new LUT\Model\Bag\Scalar('bar')], 201 | $this->invoke($operator)->setArguments($argumentsA) 202 | ) 203 | ->when($result = $this->invoke($operator)->setArguments($argumentsB)) 204 | ->then 205 | ->array($result) 206 | ->isEqualTo($argumentsA) 207 | ->array($operator->getArguments()) 208 | ->isEqualTo($argumentsB); 209 | } 210 | 211 | public function case_get_arguments() 212 | { 213 | $this 214 | ->given( 215 | $operator = new SUT('foo'), 216 | $arguments = ['foo', [42], new LUT\Model\Bag\Scalar('baz')], 217 | $this->invoke($operator)->setArguments($arguments) 218 | ) 219 | ->when($result = $operator->getArguments()) 220 | ->then 221 | ->array($result) 222 | ->isEqualTo([ 223 | new LUT\Model\Bag\Scalar('foo'), 224 | new LUT\Model\Bag\RulerArray([42]), 225 | new LUT\Model\Bag\Scalar('baz') 226 | ]); 227 | } 228 | 229 | public function case_set_function() 230 | { 231 | $this 232 | ->given($operator = new SUT('foo')) 233 | ->when($result = $this->invoke($operator)->setFunction(false)) 234 | ->then 235 | ->boolean($result) 236 | ->isTrue(); 237 | } 238 | 239 | public function case_is_function() 240 | { 241 | $this 242 | ->given( 243 | $operator = new SUT('foo'), 244 | $this->invoke($operator)->setFunction(true) 245 | ) 246 | ->when($result = $operator->isFunction()) 247 | ->then 248 | ->boolean($result) 249 | ->isTrue(); 250 | } 251 | 252 | public function case_is_not_function() 253 | { 254 | $this 255 | ->given( 256 | $operator = new SUT('foo'), 257 | $this->invoke($operator)->setFunction(false) 258 | ) 259 | ->when($result = $operator->isFunction()) 260 | ->then 261 | ->boolean($result) 262 | ->isFalse(); 263 | } 264 | 265 | public function case_set_laziness() 266 | { 267 | $this 268 | ->given($operator = new SUT('foo')) 269 | ->when($result = $this->invoke($operator)->setLaziness(false)) 270 | ->then 271 | ->boolean($result) 272 | ->isFalse(); 273 | } 274 | 275 | public function case_is_lazy() 276 | { 277 | $this 278 | ->given( 279 | $operator = new SUT('foo'), 280 | $this->invoke($operator)->setLaziness(true) 281 | ) 282 | ->when($result = $operator->isLazy()) 283 | ->then 284 | ->boolean($result) 285 | ->isTrue(); 286 | } 287 | 288 | public function case_is_not_lazy() 289 | { 290 | $this 291 | ->given( 292 | $operator = new SUT('foo'), 293 | $this->invoke($operator)->setLaziness(false) 294 | ) 295 | ->when($result = $operator->isLazy()) 296 | ->then 297 | ->boolean($result) 298 | ->isFalse(); 299 | } 300 | 301 | public function case_should_break_lazy_evaluation_with_and_operator() 302 | { 303 | return $this->_case_should_break_lazy_evaluation_with_x_operator( 304 | 'and', 305 | false, 306 | SUT::LAZY_BREAK 307 | ); 308 | } 309 | 310 | public function case_should_not_break_lazy_evaluation_with_and_operator() 311 | { 312 | return $this->_case_should_break_lazy_evaluation_with_x_operator( 313 | 'and', 314 | true, 315 | SUT::LAZY_CONTINUE 316 | ); 317 | } 318 | 319 | public function case_should_break_lazy_evaluation_with_or_operator() 320 | { 321 | return $this->_case_should_break_lazy_evaluation_with_x_operator( 322 | 'or', 323 | true, 324 | SUT::LAZY_BREAK 325 | ); 326 | } 327 | 328 | public function case_should_not_break_lazy_evaluation_with_or_operator() 329 | { 330 | return $this->_case_should_break_lazy_evaluation_with_x_operator( 331 | 'or', 332 | false, 333 | SUT::LAZY_CONTINUE 334 | ); 335 | } 336 | 337 | public function case_should_not_break_lazy_evaluation_with_any_operator() 338 | { 339 | return $this->_case_should_break_lazy_evaluation_with_x_operator( 340 | 'foo', 341 | 42, 342 | SUT::LAZY_CONTINUE 343 | ); 344 | } 345 | 346 | protected function _case_should_break_lazy_evaluation_with_x_operator($name, $value, $expect) 347 | { 348 | $this 349 | ->given($operator = new SUT($name)) 350 | ->when($result = $operator->shouldBreakLazyEvaluation($value)) 351 | ->then 352 | ->boolean($result) 353 | ->isEqualTo($expect); 354 | } 355 | 356 | public function case_is_token() 357 | { 358 | $this 359 | ->when(function () { 360 | foreach (['not', 'and', 'or', 'xor'] as $token) { 361 | $this 362 | ->when($result = SUT::isToken($token)) 363 | ->then 364 | ->boolean($result) 365 | ->isTrue(); 366 | } 367 | }); 368 | } 369 | 370 | public function case_is_not_token() 371 | { 372 | $this 373 | ->when($result = SUT::isToken('foo')) 374 | ->then 375 | ->boolean($result) 376 | ->isFalse(); 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /Test/Unit/Ruler.php: -------------------------------------------------------------------------------- 1 | given( 58 | $rule = '7 < 42', 59 | $ruler = new SUT() 60 | ) 61 | ->when($result = $ruler->assert($rule)) 62 | ->then 63 | ->boolean($result) 64 | ->isTrue(); 65 | } 66 | 67 | public function case_assert_with_a_context() 68 | { 69 | $this 70 | ->given( 71 | $rule = 'x < 42', 72 | $ruler = new SUT(), 73 | $context = new LUT\Context(), 74 | $context['x'] = 7 75 | ) 76 | ->when($result = $ruler->assert($rule, $context)) 77 | ->then 78 | ->boolean($result) 79 | ->isTrue(); 80 | } 81 | 82 | public function case_assert_with_rule_as_a_model() 83 | { 84 | $this 85 | ->given( 86 | $rule = SUT::interpret('x < 42'), 87 | $ruler = new SUT(), 88 | $context = new LUT\Context(), 89 | $context['x'] = 7 90 | ) 91 | ->when($result = $ruler->assert($rule, $context)) 92 | ->then 93 | ->boolean($result) 94 | ->isTrue(); 95 | } 96 | 97 | public function case_interpret() 98 | { 99 | $this 100 | ->when($result = SUT::interpret('x < 42')) 101 | ->then 102 | ->object($result) 103 | ->isInstanceOf(LUT\Model::class); 104 | } 105 | 106 | public function case_get_interpreter() 107 | { 108 | $this 109 | ->when($result = SUT::getInterpreter()) 110 | ->then 111 | ->object($result) 112 | ->isInstanceOf(LUT\Visitor\Interpreter::class); 113 | } 114 | 115 | public function case_set_asserter() 116 | { 117 | $this 118 | ->given( 119 | $ruler = new SUT(), 120 | $asserter = new LUT\Visitor\Asserter() 121 | ) 122 | ->when($result = $ruler->setAsserter($asserter)) 123 | ->then 124 | ->variable($result) 125 | ->isNull(); 126 | } 127 | 128 | public function case_get_asserter() 129 | { 130 | $this 131 | ->given( 132 | $asserter = new LUT\Visitor\Asserter(), 133 | $ruler = new SUT(), 134 | $context = new LUT\Context(), 135 | $ruler->setAsserter($asserter), 136 | $asserter->setContext($context) 137 | ) 138 | ->when($result = $ruler->getAsserter()) 139 | ->then 140 | ->object($result) 141 | ->isIdenticalTo($asserter) 142 | ->object($result->getContext()) 143 | ->isIdenticalTo($context); 144 | } 145 | 146 | public function case_get_asserter_with_a_specific_context() 147 | { 148 | $this 149 | ->given( 150 | $asserter = new LUT\Visitor\Asserter(), 151 | $contextA = new LUT\Context(), 152 | $contextB = new LUT\Context(), 153 | $ruler = new SUT(), 154 | $ruler->setAsserter($asserter), 155 | $asserter->setContext($contextA) 156 | ) 157 | ->when($result = $ruler->getAsserter($contextB)) 158 | ->then 159 | ->object($result) 160 | ->isIdenticalTo($asserter) 161 | ->object($result->getContext()) 162 | ->isIdenticalTo($contextB); 163 | } 164 | 165 | public function case_get_asserter_the_default_one() 166 | { 167 | $this 168 | ->given($ruler = new SUT()) 169 | ->when($result = $ruler->getAsserter()) 170 | ->then 171 | ->object($result) 172 | ->isInstanceOf(LUT\Visitor\Asserter::class) 173 | ->variable($result->getContext()) 174 | ->isNull() 175 | ->object($ruler->getAsserter()) 176 | ->isIdenticalTo($result); 177 | } 178 | 179 | public function case_get_asserter_the_default_one_with_a_specific_context() 180 | { 181 | $this 182 | ->given( 183 | $ruler = new SUT(), 184 | $context = new LUT\Context() 185 | ) 186 | ->when($result = $ruler->getAsserter($context)) 187 | ->then 188 | ->object($result) 189 | ->isInstanceOf(LUT\Visitor\Asserter::class) 190 | ->object($result->getContext()) 191 | ->isIdenticalTo($context) 192 | ->object($ruler->getAsserter($context)) 193 | ->isIdenticalTo($result); 194 | } 195 | 196 | public function case_get_default_asserter() 197 | { 198 | $this 199 | ->when($result = SUT::getDefaultAsserter()) 200 | ->then 201 | ->object($result) 202 | ->isInstanceOf(LUT\Visitor\Asserter::class) 203 | ->variable($result->getContext()) 204 | ->isNull() 205 | ->object(SUT::getDefaultAsserter()) 206 | ->isIdenticalTo($result); 207 | } 208 | 209 | public function case_get_default_asserter_with_a_specific_context() 210 | { 211 | $this 212 | ->given($context = new LUT\Context()) 213 | ->when($result = SUT::getDefaultAsserter($context)) 214 | ->then 215 | ->object($result) 216 | ->isInstanceOf(LUT\Visitor\Asserter::class) 217 | ->object($result->getContext()) 218 | ->isIdenticalTo($context) 219 | ->object(SUT::getDefaultAsserter($context)) 220 | ->isIdenticalTo($result); 221 | } 222 | 223 | public function case_get_compiler() 224 | { 225 | $this 226 | ->when($result = SUT::getCompiler()) 227 | ->then 228 | ->object($result) 229 | ->isInstanceOf(Compiler\Llk\Parser::class) 230 | ->object(SUT::getCompiler()) 231 | ->isIdenticalTo($result); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /Test/Unit/Visitor/Compiler.php: -------------------------------------------------------------------------------- 1 | when($result = new SUT()) 58 | ->then 59 | ->object($result) 60 | ->isInstanceOf(Visitor\Visit::class); 61 | } 62 | 63 | public function case_model() 64 | { 65 | return $this->_case( 66 | 'true', 67 | '$model = new \Hoa\Ruler\Model();' . "\n" . 68 | '$model->expression =' . "\n" . 69 | ' true;' 70 | ); 71 | } 72 | 73 | public function case_operator() 74 | { 75 | return $this->_case( 76 | '7 < 42', 77 | '$model = new \Hoa\Ruler\Model();' . "\n" . 78 | '$model->expression =' . "\n" . 79 | ' $model->{\'<\'}(' . "\n" . 80 | ' 7,' . "\n" . 81 | ' 42' . "\n" . 82 | ' );' 83 | ); 84 | } 85 | 86 | public function case_operator_is_an_identifier() 87 | { 88 | return $this->_case( 89 | 'true and false', 90 | '$model = new \Hoa\Ruler\Model();' . "\n" . 91 | '$model->expression =' . "\n" . 92 | ' $model->and(' . "\n" . 93 | ' true,' . "\n" . 94 | ' false' . "\n" . 95 | ' );' 96 | ); 97 | } 98 | 99 | public function case_function() 100 | { 101 | return $this->_case( 102 | 'f(7, 42)', 103 | '$model = new \Hoa\Ruler\Model();' . "\n" . 104 | '$model->expression =' . "\n" . 105 | ' $model->func(' . "\n" . 106 | ' \'f\',' . "\n" . 107 | ' 7,' . "\n" . 108 | ' 42' . "\n" . 109 | ' );' 110 | ); 111 | } 112 | 113 | public function case_function_of_arity_1() 114 | { 115 | return $this->_case( 116 | 'f(7)', 117 | '$model = new \Hoa\Ruler\Model();' . "\n" . 118 | '$model->expression =' . "\n" . 119 | ' $model->func(' . "\n" . 120 | ' \'f\',' . "\n" . 121 | ' 7' . "\n" . 122 | ' );' 123 | ); 124 | } 125 | 126 | public function case_function_with_array_dimension() 127 | { 128 | return $this->_case( 129 | 'x(7)[42]', 130 | '$model = new \Hoa\Ruler\Model();' . "\n" . 131 | '$model->expression =' . "\n" . 132 | ' $model->func(' . "\n" . 133 | ' \'x\',' . "\n" . 134 | ' 7' . "\n" . 135 | ' )' . "\n" . 136 | ' ->index(' . "\n" . 137 | ' 42' . "\n" . 138 | ' );' 139 | ); 140 | } 141 | 142 | public function case_function_with_attribute_dimension() 143 | { 144 | return $this->_case( 145 | 'x(7).y', 146 | '$model = new \Hoa\Ruler\Model();' . "\n" . 147 | '$model->expression =' . "\n" . 148 | ' $model->func(' . "\n" . 149 | ' \'x\',' . "\n" . 150 | ' 7' . "\n" . 151 | ' )' . "\n" . 152 | ' ->attribute(\'y\');' 153 | ); 154 | } 155 | 156 | public function case_function_with_call_dimension() 157 | { 158 | return $this->_case( 159 | 'x(7).y(42)', 160 | '$model = new \Hoa\Ruler\Model();' . "\n" . 161 | '$model->expression =' . "\n" . 162 | ' $model->func(' . "\n" . 163 | ' \'x\',' . "\n" . 164 | ' 7' . "\n" . 165 | ' )' . "\n" . 166 | ' ->call(' . "\n" . 167 | ' $model->func(' . "\n" . 168 | ' \'y\',' . "\n" . 169 | ' 42' . "\n" . 170 | ' )' . "\n" . 171 | ' );' 172 | ); 173 | } 174 | 175 | public function case_function_with_many_dimensions() 176 | { 177 | return $this->_case( 178 | 'x(7).y(42).z[153]', 179 | '$model = new \Hoa\Ruler\Model();' . "\n" . 180 | '$model->expression =' . "\n" . 181 | ' $model->func(' . "\n" . 182 | ' \'x\',' . "\n" . 183 | ' 7' . "\n" . 184 | ' )' . "\n" . 185 | ' ->call(' . "\n" . 186 | ' $model->func(' . "\n" . 187 | ' \'y\',' . "\n" . 188 | ' 42' . "\n" . 189 | ' )' . "\n" . 190 | ' )' . "\n" . 191 | ' ->attribute(\'z\')' . "\n" . 192 | ' ->index(' . "\n" . 193 | ' 153' . "\n" . 194 | ' );' 195 | ); 196 | } 197 | 198 | public function case_scalar_true() 199 | { 200 | return $this->_case( 201 | 'true', 202 | '$model = new \Hoa\Ruler\Model();' . "\n" . 203 | '$model->expression =' . "\n" . 204 | ' true;' 205 | ); 206 | } 207 | 208 | public function case_scalar_false() 209 | { 210 | return $this->_case( 211 | 'false', 212 | '$model = new \Hoa\Ruler\Model();' . "\n" . 213 | '$model->expression =' . "\n" . 214 | ' false;' 215 | ); 216 | } 217 | 218 | public function case_scalar_null() 219 | { 220 | return $this->_case( 221 | 'null and true', 222 | '$model = new \Hoa\Ruler\Model();' . "\n" . 223 | '$model->expression =' . "\n" . 224 | ' $model->and(' . "\n" . 225 | ' null,' . "\n" . 226 | ' true' . "\n" . 227 | ' );' 228 | ); 229 | } 230 | 231 | public function case_scalar_numeric() 232 | { 233 | return $this->_case( 234 | '7', 235 | '$model = new \Hoa\Ruler\Model();' . "\n" . 236 | '$model->expression =' . "\n" . 237 | ' 7;' 238 | ); 239 | } 240 | 241 | public function case_scalar_string() 242 | { 243 | return $this->_case( 244 | "'Hello, World!'", 245 | '$model = new \Hoa\Ruler\Model();' . "\n" . 246 | '$model->expression =' . "\n" . 247 | ' \'Hello, World!\';' 248 | ); 249 | } 250 | 251 | public function case_scalar_escaped_string() 252 | { 253 | return $this->_case( 254 | "'He\llo, \'World\'!'", 255 | '$model = new \Hoa\Ruler\Model();' . "\n" . 256 | '$model->expression =' . "\n" . 257 | ' \'He\llo, \\\'World\\\'!\';' 258 | ); 259 | } 260 | 261 | public function case_array() 262 | { 263 | return $this->_case( 264 | '[7, true, \'foo\']', 265 | '$model = new \Hoa\Ruler\Model();' . "\n" . 266 | '$model->expression =' . "\n" . 267 | ' [' . "\n" . 268 | ' 7,' . "\n" . 269 | ' true,' . "\n" . 270 | ' \'foo\'' . "\n" . 271 | ' ];' 272 | ); 273 | } 274 | 275 | public function case_context() 276 | { 277 | return $this->_case( 278 | 'x', 279 | '$model = new \Hoa\Ruler\Model();' . "\n" . 280 | '$model->expression =' . "\n" . 281 | ' $model->variable(\'x\');' 282 | ); 283 | } 284 | 285 | public function case_context_with_array_dimension() 286 | { 287 | return $this->_case( 288 | 'x[7]', 289 | '$model = new \Hoa\Ruler\Model();' . "\n" . 290 | '$model->expression =' . "\n" . 291 | ' $model->variable(\'x\')' . "\n" . 292 | ' ->index(' . "\n" . 293 | ' 7' . "\n" . 294 | ' );' 295 | ); 296 | } 297 | 298 | public function case_context_with_array_dimensions() 299 | { 300 | return $this->_case( 301 | 'x[7][42]', 302 | '$model = new \Hoa\Ruler\Model();' . "\n" . 303 | '$model->expression =' . "\n" . 304 | ' $model->variable(\'x\')' . "\n" . 305 | ' ->index(' . "\n" . 306 | ' 7' . "\n" . 307 | ' )' . "\n" . 308 | ' ->index(' . "\n" . 309 | ' 42' . "\n" . 310 | ' );' 311 | ); 312 | } 313 | 314 | public function case_context_with_attribute_dimension() 315 | { 316 | return $this->_case( 317 | 'x.y', 318 | '$model = new \Hoa\Ruler\Model();' . "\n" . 319 | '$model->expression =' . "\n" . 320 | ' $model->variable(\'x\')' . "\n" . 321 | ' ->attribute(\'y\');' 322 | ); 323 | } 324 | 325 | public function case_context_with_attribute_dimensions() 326 | { 327 | return $this->_case( 328 | 'x.y.z', 329 | '$model = new \Hoa\Ruler\Model();' . "\n" . 330 | '$model->expression =' . "\n" . 331 | ' $model->variable(\'x\')' . "\n" . 332 | ' ->attribute(\'y\')' . "\n" . 333 | ' ->attribute(\'z\');' 334 | ); 335 | } 336 | 337 | public function case_context_with_call_dimension() 338 | { 339 | return $this->_case( 340 | 'x.y(7)', 341 | '$model = new \Hoa\Ruler\Model();' . "\n" . 342 | '$model->expression =' . "\n" . 343 | ' $model->variable(\'x\')' . "\n" . 344 | ' ->call(' . "\n" . 345 | ' $model->func(' . "\n" . 346 | ' \'y\',' . "\n" . 347 | ' 7' . "\n" . 348 | ' )' . "\n" . 349 | ' );' 350 | ); 351 | } 352 | 353 | public function case_context_with_call_dimensions() 354 | { 355 | return $this->_case( 356 | 'x.y(7).z(42)', 357 | '$model = new \Hoa\Ruler\Model();' . "\n" . 358 | '$model->expression =' . "\n" . 359 | ' $model->variable(\'x\')' . "\n" . 360 | ' ->call(' . "\n" . 361 | ' $model->func(' . "\n" . 362 | ' \'y\',' . "\n" . 363 | ' 7' . "\n" . 364 | ' )' . "\n" . 365 | ' )' . "\n" . 366 | ' ->call(' . "\n" . 367 | ' $model->func(' . "\n" . 368 | ' \'z\',' . "\n" . 369 | ' 42' . "\n" . 370 | ' )' . "\n" . 371 | ' );' 372 | ); 373 | } 374 | 375 | public function case_context_with_many_dimensions() 376 | { 377 | return $this->_case( 378 | 'a.b(7).c[42].d.e(153).f', 379 | '$model = new \Hoa\Ruler\Model();' . "\n" . 380 | '$model->expression =' . "\n" . 381 | ' $model->variable(\'a\')' . "\n" . 382 | ' ->call(' . "\n" . 383 | ' $model->func(' . "\n" . 384 | ' \'b\',' . "\n" . 385 | ' 7' . "\n" . 386 | ' )' . "\n" . 387 | ' )' . "\n" . 388 | ' ->attribute(\'c\')' . "\n" . 389 | ' ->index(' . "\n" . 390 | ' 42' . "\n" . 391 | ' )' . "\n" . 392 | ' ->attribute(\'d\')' . "\n" . 393 | ' ->call(' . "\n" . 394 | ' $model->func(' . "\n" . 395 | ' \'e\',' . "\n" . 396 | ' 153' . "\n" . 397 | ' )' . "\n" . 398 | ' )' . "\n" . 399 | ' ->attribute(\'f\');' 400 | ); 401 | } 402 | 403 | protected function _case($rule, $compiled) 404 | { 405 | $this 406 | ->given($compiler = new SUT()) 407 | ->when($result = $compiler->visit(LUT::interpret($rule))) 408 | ->then 409 | ->string($result) 410 | ->isEqualTo($compiled); 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /Test/Unit/Visitor/Disassembly.php: -------------------------------------------------------------------------------- 1 | when($result = new SUT()) 58 | ->then 59 | ->object($result) 60 | ->isInstanceOf(Visitor\Visit::class); 61 | } 62 | 63 | public function case_model() 64 | { 65 | return $this->_case( 66 | 'true', 67 | 'true' 68 | ); 69 | } 70 | 71 | public function case_operator() 72 | { 73 | return $this->_case( 74 | '7 < 42', 75 | '(7 < 42)' 76 | ); 77 | } 78 | 79 | public function case_operator_is_an_identifier() 80 | { 81 | return $this->_case( 82 | 'true and false', 83 | '(true and false)' 84 | ); 85 | } 86 | 87 | public function case_function() 88 | { 89 | return $this->_case( 90 | 'f(7, 42)', 91 | 'f(7, 42)' 92 | ); 93 | } 94 | 95 | public function case_function_of_arity_1() 96 | { 97 | return $this->_case( 98 | 'f(7)', 99 | 'f(7)' 100 | ); 101 | } 102 | 103 | public function case_function_with_array_dimensions() 104 | { 105 | return $this->_case( 106 | 'x(7)[42]', 107 | 'x(7)[42]' 108 | ); 109 | } 110 | 111 | public function case_function_with_attribute_dimensions() 112 | { 113 | return $this->_case( 114 | 'x(7).y', 115 | 'x(7).y' 116 | ); 117 | } 118 | 119 | public function case_function_with_call_dimensions() 120 | { 121 | return $this->_case( 122 | 'x(7).y(42)', 123 | 'x(7).y(42)' 124 | ); 125 | } 126 | 127 | public function case_function_with_many_dimensions() 128 | { 129 | return $this->_case( 130 | 'x(7).y(42).z[153]', 131 | 'x(7).y(42).z[153]' 132 | ); 133 | } 134 | 135 | public function case_scalar_true() 136 | { 137 | return $this->_case( 138 | 'true', 139 | 'true' 140 | ); 141 | } 142 | 143 | public function case_scalar_false() 144 | { 145 | return $this->_case( 146 | 'false', 147 | 'false' 148 | ); 149 | } 150 | 151 | public function case_scalar_null() 152 | { 153 | return $this->_case( 154 | 'null and true', 155 | '(null and true)' 156 | ); 157 | } 158 | 159 | public function case_scalar_numeric() 160 | { 161 | return $this->_case( 162 | '7', 163 | '7' 164 | ); 165 | } 166 | 167 | public function case_scalar_string() 168 | { 169 | return $this->_case( 170 | "'Hello, World!'", 171 | "'Hello, World!'" 172 | ); 173 | } 174 | 175 | public function case_scalar_escaped_string() 176 | { 177 | return $this->_case( 178 | "'He\llo, \'World\'!'", 179 | "'He\llo, \'World\'!'" 180 | ); 181 | } 182 | 183 | public function case_array() 184 | { 185 | return $this->_case( 186 | '[7, true, \'foo\']', 187 | '[7, true, \'foo\']' 188 | ); 189 | } 190 | 191 | public function case_context() 192 | { 193 | return $this->_case( 194 | 'x', 195 | 'x' 196 | ); 197 | } 198 | 199 | public function case_context_with_array_dimension() 200 | { 201 | return $this->_case( 202 | 'x[7]', 203 | 'x[7]' 204 | ); 205 | } 206 | 207 | public function case_context_with_attribute_dimension() 208 | { 209 | return $this->_case( 210 | 'x.y', 211 | 'x.y' 212 | ); 213 | } 214 | 215 | public function case_context_with_call_dimension() 216 | { 217 | return $this->_case( 218 | 'x.y(7)', 219 | 'x.y(7)' 220 | ); 221 | } 222 | 223 | public function case_context_with_many_dimensions() 224 | { 225 | return $this->_case( 226 | 'x.y(7).z[42]', 227 | 'x.y(7).z[42]' 228 | ); 229 | } 230 | 231 | protected function _case($rule, $disassembled) 232 | { 233 | $this 234 | ->given($compiler = new SUT()) 235 | ->when($result = $compiler->visit(LUT::interpret($rule))) 236 | ->then 237 | ->string($result) 238 | ->isEqualTo($disassembled); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /Test/Unit/Visitor/Interpreter.php: -------------------------------------------------------------------------------- 1 | when($result = new SUT()) 58 | ->then 59 | ->object($result) 60 | ->isInstanceOf(Visitor\Visit::class); 61 | } 62 | 63 | public function case_model() 64 | { 65 | return $this->_case( 66 | 'true', 67 | function () { 68 | $model = new LUT\Model(); 69 | $model->expression = true; 70 | 71 | return $model; 72 | } 73 | ); 74 | } 75 | 76 | public function case_operator() 77 | { 78 | return $this->_case( 79 | '7 < 42', 80 | function () { 81 | $model = new LUT\Model(); 82 | $model->expression = 83 | $model->{'<'}( 84 | 7, 85 | 42 86 | ); 87 | 88 | return $model; 89 | } 90 | ); 91 | } 92 | 93 | public function case_operator_is_an_identifier() 94 | { 95 | return $this->_case( 96 | 'true and false', 97 | function () { 98 | $model = new LUT\Model(); 99 | $model->expression = 100 | $model->and( 101 | true, 102 | false 103 | ); 104 | 105 | return $model; 106 | } 107 | ); 108 | } 109 | 110 | public function case_operator_and() 111 | { 112 | return $this->_case( 113 | 'true and false', 114 | function () { 115 | $model = new LUT\Model(); 116 | $model->expression = 117 | $model->and( 118 | true, 119 | false 120 | ); 121 | 122 | return $model; 123 | } 124 | ); 125 | } 126 | 127 | public function case_operator_or() 128 | { 129 | return $this->_case( 130 | 'true or false', 131 | function () { 132 | $model = new LUT\Model(); 133 | $model->expression = 134 | $model->or( 135 | true, 136 | false 137 | ); 138 | 139 | return $model; 140 | } 141 | ); 142 | } 143 | 144 | public function case_operator_xor() 145 | { 146 | return $this->_case( 147 | 'true xor false', 148 | function () { 149 | $model = new LUT\Model(); 150 | $model->expression = 151 | $model->xor( 152 | true, 153 | false 154 | ); 155 | 156 | return $model; 157 | } 158 | ); 159 | } 160 | 161 | public function case_operator_not() 162 | { 163 | return $this->_case( 164 | 'not true', 165 | function () { 166 | $model = new LUT\Model(); 167 | $model->expression = 168 | $model->not( 169 | true 170 | ); 171 | 172 | return $model; 173 | } 174 | ); 175 | } 176 | 177 | public function case_function() 178 | { 179 | return $this->_case( 180 | 'f(7, 42)', 181 | function () { 182 | $model = new LUT\Model(); 183 | $model->expression = 184 | $model->func( 185 | 'f', 186 | 7, 187 | 42 188 | ); 189 | 190 | return $model; 191 | } 192 | ); 193 | } 194 | 195 | public function case_function_of_arity_1() 196 | { 197 | return $this->_case( 198 | 'f(7)', 199 | function () { 200 | $model = new LUT\Model(); 201 | $model->expression = 202 | $model->func( 203 | 'f', 204 | 7 205 | ); 206 | 207 | return $model; 208 | } 209 | ); 210 | } 211 | 212 | public function case_function_with_array_dimension() 213 | { 214 | return $this->_case( 215 | 'x(7)[42]', 216 | function () { 217 | $model = new LUT\Model(); 218 | $model->expression = 219 | $model->func( 220 | 'x', 221 | 7 222 | ) 223 | ->index( 224 | 42 225 | ); 226 | 227 | return $model; 228 | } 229 | ); 230 | } 231 | 232 | public function case_function_with_attribute_dimension() 233 | { 234 | return $this->_case( 235 | 'x(7).y', 236 | function () { 237 | $model = new LUT\Model(); 238 | $model->expression = 239 | $model->func( 240 | 'x', 241 | 7 242 | ) 243 | ->attribute('y'); 244 | 245 | return $model; 246 | } 247 | ); 248 | } 249 | 250 | public function case_function_with_call_dimension() 251 | { 252 | return $this->_case( 253 | 'x(7).y(42)', 254 | function () { 255 | $model = new LUT\Model(); 256 | $model->expression = 257 | $model->func( 258 | 'x', 259 | 7 260 | ) 261 | ->call( 262 | $model->func( 263 | 'y', 264 | 42 265 | ) 266 | ); 267 | 268 | return $model; 269 | } 270 | ); 271 | } 272 | 273 | public function case_function_with_many_dimensions() 274 | { 275 | return $this->_case( 276 | 'x(7).y(42).z[153]', 277 | function () { 278 | $model = new LUT\Model(); 279 | $model->expression = 280 | $model->func( 281 | 'x', 282 | 7 283 | ) 284 | ->call( 285 | $model->func( 286 | 'y', 287 | 42 288 | ) 289 | ) 290 | ->attribute('z') 291 | ->index( 292 | 153 293 | ); 294 | 295 | return $model; 296 | } 297 | ); 298 | } 299 | 300 | public function case_scalar_true() 301 | { 302 | return $this->_case( 303 | 'true', 304 | function () { 305 | $model = new LUT\Model(); 306 | $model->expression = 307 | true; 308 | 309 | return $model; 310 | } 311 | ); 312 | } 313 | 314 | public function case_scalar_false() 315 | { 316 | return $this->_case( 317 | 'false', 318 | function () { 319 | $model = new LUT\Model(); 320 | $model->expression = 321 | false; 322 | 323 | return $model; 324 | } 325 | ); 326 | } 327 | 328 | public function case_scalar_null() 329 | { 330 | return $this->_case( 331 | 'null and true', 332 | function () { 333 | $model = new LUT\Model(); 334 | $model->expression = 335 | $model->and( 336 | null, 337 | true 338 | ); 339 | 340 | return $model; 341 | } 342 | ); 343 | } 344 | 345 | public function case_scalar_float() 346 | { 347 | return $this->_case( 348 | '4.2', 349 | function () { 350 | $model = new LUT\Model(); 351 | $model->expression = 352 | 4.2; 353 | 354 | return $model; 355 | } 356 | ); 357 | } 358 | 359 | public function case_scalar_integer() 360 | { 361 | return $this->_case( 362 | '7', 363 | function () { 364 | $model = new LUT\Model(); 365 | $model->expression = 366 | 7; 367 | 368 | return $model; 369 | } 370 | ); 371 | } 372 | 373 | public function case_scalar_string() 374 | { 375 | return $this->_case( 376 | "'Hello, World!'", 377 | function () { 378 | $model = new LUT\Model(); 379 | $model->expression = 380 | 'Hello, World!'; 381 | 382 | return $model; 383 | } 384 | ); 385 | } 386 | 387 | public function case_scalar_escaped_string() 388 | { 389 | return $this->_case( 390 | "'He\llo, \'World\'!'", 391 | function () { 392 | $model = new LUT\Model(); 393 | $model->expression = 394 | 'He\llo, \'World\'!'; 395 | 396 | return $model; 397 | } 398 | ); 399 | } 400 | 401 | public function case_array() 402 | { 403 | return $this->_case( 404 | '[7, true, \'foo\']', 405 | function () { 406 | $model = new LUT\Model(); 407 | $model->expression = 408 | [ 409 | 7, 410 | true, 411 | 'foo' 412 | ]; 413 | 414 | return $model; 415 | } 416 | ); 417 | } 418 | 419 | public function case_context() 420 | { 421 | return $this->_case( 422 | 'x', 423 | function () { 424 | $model = new LUT\Model(); 425 | $model->expression = 426 | $model->variable('x'); 427 | 428 | return $model; 429 | } 430 | ); 431 | } 432 | 433 | public function case_context_with_array_dimension() 434 | { 435 | return $this->_case( 436 | 'x[7]', 437 | function () { 438 | $model = new LUT\Model(); 439 | $model->expression = 440 | $model->variable('x') 441 | ->index( 442 | 7 443 | ); 444 | 445 | return $model; 446 | } 447 | ); 448 | } 449 | 450 | public function case_context_with_array_dimensions() 451 | { 452 | return $this->_case( 453 | 'x[7][42]', 454 | function () { 455 | $model = new LUT\Model(); 456 | $model->expression = 457 | $model->variable('x') 458 | ->index( 459 | 7 460 | ) 461 | ->index( 462 | 42 463 | ); 464 | 465 | return $model; 466 | } 467 | ); 468 | } 469 | 470 | public function case_context_with_attribute_dimension() 471 | { 472 | return $this->_case( 473 | 'x.y', 474 | function () { 475 | $model = new LUT\Model(); 476 | $model->expression = 477 | $model->variable('x') 478 | ->attribute('y'); 479 | 480 | return $model; 481 | } 482 | ); 483 | } 484 | 485 | public function case_context_with_attribute_dimensions() 486 | { 487 | return $this->_case( 488 | 'x.y.z', 489 | function () { 490 | $model = new LUT\Model(); 491 | $model->expression = 492 | $model->variable('x') 493 | ->attribute('y') 494 | ->attribute('z'); 495 | 496 | return $model; 497 | } 498 | ); 499 | } 500 | 501 | public function case_context_with_many_dimensions() 502 | { 503 | return $this->_case( 504 | 'a.b(7).c[42].d.e(153).f', 505 | function () { 506 | $model = new LUT\Model(); 507 | $model->expression = 508 | $model->variable('a') 509 | ->call( 510 | $model->func( 511 | 'b', 512 | 7 513 | ) 514 | ) 515 | ->attribute('c') 516 | ->index( 517 | 42 518 | ) 519 | ->attribute('d') 520 | ->call( 521 | $model->func( 522 | 'e', 523 | 153 524 | ) 525 | ) 526 | ->attribute('f'); 527 | 528 | return $model; 529 | } 530 | ); 531 | } 532 | 533 | protected function _case($rule, \Closure $expected) 534 | { 535 | $this 536 | ->given( 537 | $interpreter = new SUT(), 538 | $ast = LUT::getCompiler()->parse($rule) 539 | ) 540 | ->when($result = $interpreter->visit($ast)) 541 | ->then 542 | ->object($result) 543 | ->isEqualTo($expected()); 544 | } 545 | } 546 | -------------------------------------------------------------------------------- /Visitor/Compiler.php: -------------------------------------------------------------------------------- 1 | _indentation); 74 | 75 | if ($element instanceof Ruler\Model) { 76 | $expression = $element->getExpression(); 77 | 78 | if (null === $expression) { 79 | $out = ''; 80 | } else { 81 | $this->_indentation = 1; 82 | 83 | $out = 84 | '$model = new \Hoa\Ruler\Model();' . "\n" . 85 | '$model->expression =' . "\n" . 86 | $expression->accept($this, $handle, $eldnah) . 87 | ';'; 88 | } 89 | } elseif ($element instanceof Ruler\Model\Operator) { 90 | $out = $_ . '$model->'; 91 | $name = $element->getName(); 92 | $_handle = []; 93 | 94 | if (false === $element->isFunction()) { 95 | if (true === Consistency::isIdentifier($name)) { 96 | $out .= $name; 97 | } else { 98 | $out .= '{\'' . $name . '\'}'; 99 | } 100 | 101 | $out .= '(' . "\n"; 102 | } else { 103 | $out .= 'func(' . "\n" . $_ . ' '; 104 | $_handle[] = '\'' . $name . '\''; 105 | } 106 | 107 | ++$this->_indentation; 108 | 109 | foreach ($element->getArguments() as $argument) { 110 | $_handle[] = $argument->accept($this, $handle, $eldnah); 111 | } 112 | 113 | $out .= 114 | implode(',' . "\n", $_handle) . "\n" . $_ . ')' . 115 | $this->visitContext($element, $handle, $eldnah, $_); 116 | 117 | --$this->_indentation; 118 | } elseif ($element instanceof Ruler\Model\Bag\Scalar) { 119 | $value = $element->getValue(); 120 | $out = $_; 121 | 122 | if (true === $value) { 123 | $out .= 'true'; 124 | } elseif (false === $value) { 125 | $out .= 'false'; 126 | } elseif (null === $value) { 127 | $out .= 'null'; 128 | } elseif (is_numeric($value)) { 129 | $out .= (string) $value; 130 | } else { 131 | $out .= '\'' . str_replace(['\'', '\\\\'], ['\\\'', '\\'], $value) . '\''; 132 | } 133 | } elseif ($element instanceof Ruler\Model\Bag\RulerArray) { 134 | $values = []; 135 | ++$this->_indentation; 136 | 137 | foreach ($element->getArray() as $value) { 138 | $values[] = $value->accept($this, $handle, $eldnah); 139 | } 140 | 141 | --$this->_indentation; 142 | $out = 143 | $_ . '[' . "\n" . 144 | implode(',' . "\n", $values) . "\n" . 145 | $_ . ']'; 146 | } elseif ($element instanceof Ruler\Model\Bag\Context) { 147 | ++$this->_indentation; 148 | 149 | $out = 150 | $_ . '$model->variable(\'' . $element->getId() . '\')' . 151 | $this->visitContext($element, $handle, $eldnah, $_); 152 | 153 | --$this->_indentation; 154 | } 155 | 156 | return $out; 157 | } 158 | 159 | /** 160 | * Visit a context. 161 | * 162 | * @param \Hoa\Ruler\Model\Bag\Context $context Context. 163 | * @param mixed &$handle Handle (reference). 164 | * @param mixed $eldnah Handle (not reference). 165 | * @param string $_ Indentation. 166 | * @return mixed 167 | */ 168 | protected function visitContext(Ruler\Model\Bag\Context $context, &$handle, $eldnah, $_) 169 | { 170 | $out = null; 171 | 172 | foreach ($context->getDimensions() as $dimension) { 173 | ++$this->_indentation; 174 | 175 | $value = $dimension[Ruler\Model\Bag\Context::ACCESS_VALUE]; 176 | $out .= "\n" . $_ . ' ->'; 177 | 178 | switch ($dimension[Ruler\Model\Bag\Context::ACCESS_TYPE]) { 179 | case Ruler\Model\Bag\Context::ARRAY_ACCESS: 180 | $out .= 181 | 'index(' . "\n" . 182 | $value->accept($this, $handle, $eldnah) . "\n" . 183 | $_ . ' )'; 184 | 185 | break; 186 | 187 | case Ruler\Model\Bag\Context::ATTRIBUTE_ACCESS: 188 | $out .= 'attribute(\'' . $value . '\')'; 189 | 190 | break; 191 | 192 | case Ruler\Model\Bag\Context::METHOD_ACCESS: 193 | $out .= 194 | 'call(' . "\n" . 195 | $value->accept($this, $handle, $eldnah) . "\n" . 196 | $_ . ' )'; 197 | 198 | break; 199 | } 200 | 201 | --$this->_indentation; 202 | } 203 | 204 | return $out; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /Visitor/Disassembly.php: -------------------------------------------------------------------------------- 1 | getExpression()->accept($this, $handle, $eldnah); 66 | } elseif ($element instanceof Ruler\Model\Operator) { 67 | $name = $element->getName(); 68 | $arguments = []; 69 | 70 | foreach ($element->getArguments() as $argument) { 71 | $arguments[] = $argument->accept($this, $handle, $eldnah); 72 | } 73 | 74 | if (true === $element->isFunction()) { 75 | $out .= $name . '(' . implode(', ', $arguments) . ')'; 76 | } else { 77 | if (!isset($arguments[1])) { 78 | $_out = $name . ' ' . $arguments[0]; 79 | } else { 80 | $_out = '(' . $arguments[0] . ' ' . $name . ' ' . $arguments[1] . ')'; 81 | } 82 | 83 | $out .= $_out; 84 | } 85 | 86 | $out .= $this->visitContext($element, $handle, $eldnah); 87 | } elseif ($element instanceof Ruler\Model\Bag\Scalar) { 88 | $value = $element->getValue(); 89 | 90 | if (true === $value) { 91 | $out .= 'true'; 92 | } elseif (false === $value) { 93 | $out .= 'false'; 94 | } elseif (null === $value) { 95 | $out .= 'null'; 96 | } elseif (is_numeric($value)) { 97 | $out .= (string) $value; 98 | } else { 99 | $out .= '\'' . str_replace(['\\', '\''], ['\\', '\\\''], $value) . '\''; 100 | } 101 | } elseif ($element instanceof Ruler\Model\Bag\RulerArray) { 102 | $values = []; 103 | 104 | foreach ($element->getArray() as $value) { 105 | $values[] = $value->accept($this, $handle, $eldnah); 106 | } 107 | 108 | $out .= '[' . implode(', ', $values) . ']'; 109 | } elseif ($element instanceof Ruler\Model\Bag\Context) { 110 | $out .= $element->getId() . $this->visitContext($element, $handle, $eldnah); 111 | } 112 | 113 | return $out; 114 | } 115 | 116 | /** 117 | * Visit a context. 118 | * 119 | * @param \Hoa\Ruler\Model\Bag\Context $context Context. 120 | * @param mixed &$handle Handle (reference). 121 | * @param mixed $eldnah Handle (not reference). 122 | * @return mixed 123 | */ 124 | protected function visitContext(Ruler\Model\Bag\Context $context, &$handle, $eldnah) 125 | { 126 | $out = null; 127 | 128 | foreach ($context->getDimensions() as $dimension) { 129 | $value = $dimension[Ruler\Model\Bag\Context::ACCESS_VALUE]; 130 | 131 | switch ($dimension[Ruler\Model\Bag\Context::ACCESS_TYPE]) { 132 | case Ruler\Model\Bag\Context::ARRAY_ACCESS: 133 | $out .= 134 | '[' . 135 | $value->accept($this, $handle, $eldnah) . 136 | ']'; 137 | 138 | break; 139 | 140 | case Ruler\Model\Bag\Context::ATTRIBUTE_ACCESS: 141 | $out .= '.' . $value; 142 | 143 | break; 144 | 145 | case Ruler\Model\Bag\Context::METHOD_ACCESS: 146 | $out .= '.' . $value->accept($this, $handle, $eldnah); 147 | 148 | break; 149 | } 150 | } 151 | 152 | return $out; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Visitor/Interpreter.php: -------------------------------------------------------------------------------- 1 | getId(); 80 | $variable = false !== $eldnah; 81 | 82 | switch ($id) { 83 | case '#expression': 84 | $this->_root = new Ruler\Model(); 85 | $this->_root->expression = $element->getChild(0)->accept( 86 | $this, 87 | $handle, 88 | $eldnah 89 | ); 90 | 91 | return $this->_root; 92 | 93 | case '#operation': 94 | $children = $element->getChildren(); 95 | $left = $children[0]->accept($this, $handle, $eldnah); 96 | $right = $children[2]->accept($this, $handle, $eldnah); 97 | $name = $children[1]->accept($this, $handle, false); 98 | 99 | return $this->_root->_operator( 100 | $name, 101 | [$left, $right], 102 | false 103 | ); 104 | 105 | case '#variable_access': 106 | $children = $element->getChildren(); 107 | $name = $children[0]->accept($this, $handle, $eldnah); 108 | array_shift($children); 109 | 110 | foreach ($children as $child) { 111 | $_child = $child->accept($this, $handle, $eldnah); 112 | 113 | switch ($child->getId()) { 114 | case '#array_access': 115 | $name->index($_child); 116 | 117 | break; 118 | 119 | case '#attribute_access': 120 | $name->attribute($_child); 121 | 122 | break; 123 | 124 | case '#method_access': 125 | $name->call($_child); 126 | 127 | break; 128 | } 129 | } 130 | 131 | return $name; 132 | 133 | case '#array_access': 134 | return $element->getChild(0)->accept($this, $handle, $eldnah); 135 | 136 | case '#attribute_access': 137 | return $element->getChild(0)->accept($this, $handle, false); 138 | 139 | case '#method_access': 140 | return $element->getChild(0)->accept($this, $handle, $eldnah); 141 | 142 | case '#array_declaration': 143 | $out = []; 144 | 145 | foreach ($element->getChildren() as $child) { 146 | $out[] = $child->accept($this, $handle, $eldnah); 147 | } 148 | 149 | return $out; 150 | 151 | case '#function_call': 152 | $children = $element->getChildren(); 153 | $name = $children[0]->accept($this, $handle, false); 154 | array_shift($children); 155 | 156 | $arguments = []; 157 | 158 | foreach ($children as $child) { 159 | $arguments[] = $child->accept($this, $handle, $eldnah); 160 | } 161 | 162 | return $this->_root->_operator( 163 | $name, 164 | $arguments, 165 | true 166 | ); 167 | 168 | case '#and': 169 | case '#or': 170 | case '#xor': 171 | $name = substr($id, 1); 172 | $children = $element->getChildren(); 173 | $left = $children[0]->accept($this, $handle, $eldnah); 174 | $right = $children[1]->accept($this, $handle, $eldnah); 175 | 176 | return $this->_root->operation( 177 | $name, 178 | [$left, $right] 179 | ); 180 | 181 | case '#not': 182 | return $this->_root->operation( 183 | 'not', 184 | [$element->getChild(0)->accept($this, $handle, $eldnah)] 185 | ); 186 | 187 | case 'token': 188 | $token = $element->getValueToken(); 189 | $value = $element->getValueValue(); 190 | 191 | switch ($token) { 192 | case 'identifier': 193 | return 194 | true === $variable 195 | ? $this->_root->variable($value) 196 | : $value; 197 | 198 | case 'true': 199 | return true; 200 | 201 | case 'false': 202 | return false; 203 | 204 | case 'null': 205 | return null; 206 | 207 | case 'float': 208 | return floatval($value); 209 | 210 | case 'integer': 211 | return intval($value); 212 | 213 | case 'string': 214 | return str_replace( 215 | '\\' . $value[0], 216 | $value[0], 217 | substr($value, 1, -1) 218 | ); 219 | 220 | default: 221 | throw new Ruler\Exception\Interpreter( 222 | 'Token %s is unknown.', 223 | 0, 224 | $token 225 | ); 226 | } 227 | 228 | break; 229 | 230 | default: 231 | throw new Ruler\Exception\Interpreter( 232 | 'Element %s is unknown.', 233 | 1, 234 | $id 235 | ); 236 | } 237 | 238 | return; 239 | } 240 | 241 | /** 242 | * Get root. 243 | * 244 | * @return \Hoa\Ruler\Model 245 | */ 246 | public function getRoot() 247 | { 248 | return $this->_root; 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /bors.toml: -------------------------------------------------------------------------------- 1 | status = [ 2 | "continuous-integration/travis-ci/push" 3 | ] 4 | timeout_sec = 1800 5 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "hoa/ruler", 3 | "description": "The Hoa\\Ruler library.", 4 | "type" : "library", 5 | "keywords" : ["library", "ruler"], 6 | "homepage" : "https://hoa-project.net/", 7 | "license" : "BSD-3-Clause", 8 | "authors" : [ 9 | { 10 | "name" : "Ivan Enderlin", 11 | "email": "ivan.enderlin@hoa-project.net" 12 | }, 13 | { 14 | "name" : "Hoa community", 15 | "homepage": "https://hoa-project.net/" 16 | } 17 | ], 18 | "support": { 19 | "email" : "support@hoa-project.net", 20 | "irc" : "irc://chat.freenode.net/hoaproject", 21 | "forum" : "https://users.hoa-project.net/", 22 | "docs" : "https://central.hoa-project.net/Documentation/Library/Ruler", 23 | "source": "https://central.hoa-project.net/Resource/Library/Ruler" 24 | }, 25 | "require": { 26 | "php" : ">=5.5.0", 27 | "hoa/compiler" : "~3.0", 28 | "hoa/consistency": "~1.0", 29 | "hoa/exception" : "~1.0", 30 | "hoa/file" : "~1.0", 31 | "hoa/protocol" : "~1.0", 32 | "hoa/visitor" : "~2.0" 33 | }, 34 | "require-dev": { 35 | "hoa/test": "~2.0" 36 | }, 37 | "autoload": { 38 | "psr-4": { 39 | "Hoa\\Ruler\\": "." 40 | } 41 | }, 42 | "extra": { 43 | "branch-alias": { 44 | "dev-master": "2.x-dev" 45 | } 46 | } 47 | } 48 | --------------------------------------------------------------------------------