acf
This commit is contained in:
		| @ -0,0 +1,358 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  *  @author          Tassos.gr <info@tassos.gr> | ||||
|  *  @link            https://www.tassos.gr | ||||
|  *  @copyright       Copyright © 2024 Tassos All Rights Reserved | ||||
|  *  @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later | ||||
| */ | ||||
|  | ||||
| namespace NRFramework\Parser; | ||||
|  | ||||
| defined('_JEXEC') or die; | ||||
|  | ||||
| use NRFramework\Parser\Parser; | ||||
| use NRFramework\Parser\ConditionLexer; | ||||
| /** | ||||
|  *  ConditionParser | ||||
|  *  LL(1) recursive-decent parser | ||||
|  *  Uses NRFramework\Parser\ConditionLexer as input source | ||||
|  *  | ||||
|  *  Grammar: | ||||
|  *  --------  | ||||
|  *  expr        : condition (logic_op condition)* (option)* | ||||
|  *  condition   : {negate_op} alias (parameter)* | (alias|l_func) ({negate_op}? operator (values)? (parameter)* | ||||
|  *  alias       : {ident} | ||||
|  *  values      : value ({comma} value)* | ||||
|  *  value       : {quotedval} | ({literal} | {ident})+ | ||||
|  *  func        : {ident} {l_paren} values {r_paren} | ||||
|  *  l_func      : func | ||||
|  *  r_func      : func   | ||||
|  *  parameter   : {param} ({equals} value)? | ||||
|  *  option      : {ident} ({equals} value)? | ||||
|  *  logic_op    : {and} | {or} | ||||
|  *  operator    : {equals} | {starts_with} | {ends_with} | {empty} | {contains} | {contains_any} | {contains_all}| {contains_only} | {lt} | {lte} | {gt} | {gte} | ||||
|  */ | ||||
| class ConditionParser extends Parser | ||||
| { | ||||
|     /** | ||||
|      *  Constructor | ||||
|      * | ||||
|      *  @param ConditionLexer $input | ||||
|      */ | ||||
|     public function __construct(ConditionLexer $input) | ||||
|     { | ||||
|         parent::__construct($input, 2); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      *  value : {quotedval} | ({literal} | {ident})+ | ||||
|      *  | ||||
|      *  @return string | ||||
|      *  @throws Exception | ||||
|      */ | ||||
|     public function value() | ||||
|     { | ||||
|         if ($this->lookahead[0]->type === 'quotedvalue') | ||||
|         { | ||||
|             $text = $this->lookahead[0]->text; | ||||
|             $this->match('quotedvalue'); | ||||
|             return $text; | ||||
|         } | ||||
|         else if ($this->lookahead[0]->type !== 'ident' && $this->lookahead[0]->type !== 'literal') | ||||
|         { | ||||
|             throw new \Exception("Syntax error in ConditionParser::value(); expecting 'ident' or 'literal'; found {$this->lookahead[0]}"); | ||||
|         } | ||||
|  | ||||
|         $text = $this->lookahead[0]->text; | ||||
|         $this->consume(); | ||||
|  | ||||
|         while ($this->lookahead[0]->type === 'ident' || $this->lookahead[0]->type === 'literal') | ||||
|         { | ||||
|             $text .= ' ' . $this->lookahead[0]->text; | ||||
|             $this->consume(); | ||||
|         } | ||||
|  | ||||
|         return $text; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      *  values : value ({comma} value)* | ||||
|      *  | ||||
|      *  @return array | ||||
|      */ | ||||
|     public function values() | ||||
|     { | ||||
|         $vals = []; | ||||
|         $vals[] = $this->value(); | ||||
|  | ||||
|         while ($this->lookahead[0]->type === 'comma') | ||||
|         { | ||||
|             $this->consume(); | ||||
|             $vals[] = $this->value(); | ||||
|         } | ||||
|         return $vals; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * func : {ident} {l_paren} values {r_paren} | ||||
|      *   | ||||
|      */ | ||||
|     public function func() | ||||
|     { | ||||
|         $func_name = $this->lookahead[0]->text; | ||||
|         $this->match('ident'); | ||||
|         $this->match('l_paren'); | ||||
|          | ||||
|         if ($this->lookahead[0]->type === 'quotedvalue' || | ||||
|             $this->lookahead[0]->type === 'ident' || | ||||
|             $this->lookahead[0]->type === 'literal') | ||||
|         { | ||||
|             $func_args = $this->values(); | ||||
|         } | ||||
|  | ||||
|         $this->match('r_paren'); | ||||
|  | ||||
|         return ['func_name' => $func_name, 'func_args' => $func_args ?? []]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      *  parameter : {param} ({equals} value)? | ||||
|      *  | ||||
|      *  @return string | ||||
|      */ | ||||
|     public function param() | ||||
|     { | ||||
|         $param = $this->lookahead[0]->text; | ||||
|         $value = true; | ||||
|         $this->match('param'); | ||||
|  | ||||
|         // If this is the 'context' parameter make sure that it appears as the last token | ||||
|         // if ($param === 'context') | ||||
|         // { | ||||
|         //     $this->consume(); // consume the 'equals' operator | ||||
|         //     $value = $this->value(); // expect a value | ||||
|         //     if ($this->lookahead[0]->type !== 'EOF') | ||||
|         //     { | ||||
|         //         throw new \Exception("Syntax error in ConditionParser::param(); the 'context' parameter can only appear as the last token"); | ||||
|         //     } | ||||
|         // }  | ||||
|         // else | ||||
|         if ($this->isOperator($this->lookahead[0]->type)) | ||||
|         { | ||||
|             if ($this->lookahead[0]->type === 'equals') | ||||
|             { | ||||
|                 $this->consume(); // consume the 'equals' operator | ||||
|                 $value = $this->value(); // expect a value | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // only the 'equals' operator is supported for the 'param' rule. | ||||
|                 throw new \Exception("Syntax error in ConditionParser::param(); expecting 'equals', found {$this->lookahead[0]}"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return ['param' => $param, 'value' => $value]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      *  alias : {ident} | ||||
|      *  | ||||
|      *  @return string | ||||
|      */ | ||||
|     public function alias() | ||||
|     { | ||||
|         $sel = $this->lookahead[0]->text; | ||||
|         $this->match('ident'); | ||||
|         return $sel; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      *  condition : {negate_op} alias (parameter)* | alias ({negate_op}? operator values)? (parameter)* | ||||
|      *  | ||||
|      *  @return object | ||||
|      */ | ||||
|     public function condition() | ||||
|     { | ||||
|         $result = []; | ||||
|         $operator = ''; | ||||
|         $params = []; | ||||
|         $negate_op = false; | ||||
|  | ||||
|         if ($this->lookahead[0]->type === 'negate_op') | ||||
|         { | ||||
|             $this->match('negate_op'); | ||||
|             $operator = 'empty'; | ||||
|             $result['alias'] = $this->alias(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             if($this->lookahead[0]->type === 'ident' && $this->lookahead[1]->type === 'l_paren') | ||||
|             { | ||||
|                 $l_func = $this->func(); | ||||
|                 $result['l_func_name'] = $l_func['func_name']; | ||||
|                 $result['l_func_args'] = $l_func['func_args']; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 $result['alias'] = $this->alias(); | ||||
|             } | ||||
|  | ||||
|             if ($this->lookahead[0]->type === 'negate_op') | ||||
|             { | ||||
|                 $this->match('negate_op'); | ||||
|                 $negate_op = true; | ||||
|                 // expect an operator after '!' | ||||
|                 if (!$this->isOperator($this->lookahead[0]->type)) | ||||
|                 { | ||||
|                     throw new Exceptions\SyntaxErrorException("Expecting an 'operator' after '!', found {$this->lookahead[0]}"); | ||||
|                 } | ||||
|             } | ||||
|             if ($this->isOperator($this->lookahead[0]->type)) | ||||
|             { | ||||
|                 $operator = $this->operator(); | ||||
|                 if($this->lookahead[0]->type === 'ident' && $this->lookahead[1]->type === 'l_paren') | ||||
|                 { | ||||
|                     $r_func = $this->func(); | ||||
|                     $result['r_func_name'] = $r_func['func_name']; | ||||
|                     $result['r_func_args'] = $r_func['func_args']; | ||||
|                 } | ||||
|                 else if ( | ||||
|                     $this->lookahead[0]->type === 'quotedvalue' || | ||||
|                     $this->lookahead[0]->type === 'ident' || | ||||
|                     $this->lookahead[0]->type === 'literal' | ||||
|                 ) | ||||
|                 { | ||||
|                      | ||||
|  | ||||
|                     $values = $this->values(); | ||||
|                     if (count($values) === 1) | ||||
|                     { | ||||
|                         $values = $values[0]; | ||||
|                     } | ||||
|                     $result['values'] = $values; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         while ($this->lookahead[0]->type === 'param') | ||||
|         { | ||||
|             $params[] = $this->param(); | ||||
|         } | ||||
|  | ||||
|         if (!$operator) { | ||||
|             $operator = 'empty'; | ||||
|             $negate_op = true; | ||||
|         } | ||||
|  | ||||
|         // | ||||
|         $_params = []; | ||||
|         foreach($params as $p) | ||||
|         { | ||||
|             $_params[$p['param']] = $p['value']; | ||||
|         } | ||||
|          | ||||
|         $result['operator'] = $operator; | ||||
|         $result['negate_op'] = $negate_op; | ||||
|         $result['params'] = $_params; | ||||
|         return $result; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      *  operator : {equals} | {starts_with} | {ends_with} | {empty} | {contains} | {contains_any} | {contains_all}| {contains_only} | {lt} | {lte} | {gt} | {gte} | ||||
|      *  | ||||
|      *  @return string | ||||
|      *  @throws Exception | ||||
|      */ | ||||
|     public function operator() | ||||
|     { | ||||
|         if (!$this->isOperator($this->lookahead[0]->type)) | ||||
|         { | ||||
|             throw new Exceptions\SyntaxErrorException("Expecting an 'operator', found " . $this->lookahead[0]); | ||||
|         } | ||||
|  | ||||
|         $op = $this->lookahead[0]->type; | ||||
|         $this->consume(); | ||||
|         return $op; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      *  expr  : condition ({logic_op} condition)* (option)* | ||||
|      *  | ||||
|      *  @return array The condition expression results | ||||
|      */ | ||||
|     public function expr() | ||||
|     { | ||||
|         $logic_op = 'and'; | ||||
|         $res = [ | ||||
|             'conditions'    => [$this->condition()], | ||||
|             'logic_op'      => 'and', | ||||
|             'context'       => null, | ||||
|             'global_params' => [] | ||||
|         ]; | ||||
|  | ||||
|         if ($this->lookahead[0]->type === 'or') | ||||
|         { | ||||
|             $logic_op = 'or'; | ||||
|         } | ||||
|  | ||||
|         while ($this->lookahead[0]->type !== 'EOF') | ||||
|         { | ||||
|             $this->match($logic_op); | ||||
|             $res['conditions'][] = $this->condition(); | ||||
|         } | ||||
|  | ||||
|         $res['logic_op'] = $logic_op; | ||||
|  | ||||
|         // check the last parsed condition for global parameters | ||||
|         $globalParams = [ | ||||
|             'debug', | ||||
|             'dateformat', | ||||
|             'context', | ||||
|             'nopreparecontent', | ||||
|             'excludebots' | ||||
|         ]; | ||||
|  | ||||
|         $last_params = $res['conditions'][count($res['conditions'])-1]['params']; | ||||
|         foreach(array_keys($last_params) as $param_key) | ||||
|         { | ||||
|             if (in_array(strtolower($param_key), $globalParams)) | ||||
|             { | ||||
|                 $res['global_params'][strtolower($param_key)] = $last_params[$param_key]; | ||||
|                 unset($res['conditions'][count($res['conditions'])-1]['params'][$param_key]); | ||||
|             } | ||||
|         } | ||||
|         // foreach ($last_params as $idx => $param) | ||||
|         // { | ||||
|         //     if (in_array($param['param'], $globalParams)) | ||||
|         //     { | ||||
|         //         $res['global_params'][$param['param']] = $param['value']; | ||||
|         //         unset($res['conditions'][count($res['conditions'])-1]['params'][$idx]); | ||||
|         //     } | ||||
|         // } | ||||
|         return $res; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Helper method that checks if the given Token is an operator. | ||||
|      */ | ||||
|     protected function isOperator($token_type) | ||||
|     { | ||||
|  | ||||
|         return in_array($token_type, [ | ||||
|             'equals', | ||||
|             'starts_with', | ||||
|             'ends_with', | ||||
|             'contains', | ||||
|             'contains_any', | ||||
|             'contains_all', | ||||
|             'contains_only', | ||||
|             'lt', | ||||
|             'lte', | ||||
|             'gt', | ||||
|             'gte', | ||||
|             'empty' | ||||
|         ]);     | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user