array("type" => "operator"), "-" => array("type" => "operator"), "*" => array("type" => "operator"), "/" => array("type" => "operator"), //operators that's need to be translate by a function for the kernel "^" => array("type" => "function", "function" => "pow"), "<" => array("type" => "function", "function" => "lower_than"), ">" => array("type" => "function", "function" => "greater_than"), "&" => array("type" => "function", "function" => "And"), "|" => array("type" => "function", "function" => "Or") ); private $constantsArray; private $aliasesArray; private $functionsArray; private $templatedParamMgr = NULL; private $impexParamMgr = NULL; private $isTest = FALSE; /* * @brief Constructor */ function __construct($isTest = FALSE) { $this->isTest = $isTest; if (!$isTest) $this->templatedParamMgr = new IHMParamTemplateClass(); } /* * @brief test function */ public function test() { //tests $tests = array( "dst>3" => array ("expression" => "(greater_than(\$dst,3))", "params" => array(array("paramid" => "dst"))), "@const_1 array("expression" => "(lower_than(1.0E-10,\$dst))", "params" => array(array("paramid" =>"dst"))), "dst^#alias_2" => array("expression" => "(pow(\$dst,\$imf[1]))", "params" => array(array("paramid" => "dst"),array("paramid" => "imf"))), "dst^2+@const_2" => array("expression" => "(pow(\$dst,2))+-2.8", "params" => array(array("paramid" => "dst"))), "atan(imf(2)/imf(1))+speed/100.0" => array("expression" => "(atan((\$imf[2]/\$imf[1])))+(\$speed/100.0)", "params" => array(array("paramid" => "imf"), array("paramid" => "speed"))), "shiftT_(clust1_hia_pad,60)" => array("expression" => "(#timeShift(\$clust1_hia_pad;60))", "params" => array(array("paramid" => "clust1_hia_pad"))), "smooth_(density,1200)" => array("expression" => "(#boxcar(\$density;1200))", "params" => array(array("paramid" => "density"))), "shiftT_(density,-600)" => array("expression" => "(#timeShift(\$density;-600))", "params" => array(array("paramid" => "density"))), "deriv(density)" => array("expression" => "(#deriv(\$density))", "params" => array(array("paramid" => "density"))), "abs(dst)" =>array("expression" => "(abs(\$dst))", "params" => array(array("paramid" => "dst"))), "density*speed+(density^2-1.0)/speed" => array("expression" => "(\$density*\$speed)+((pow(\$density,2))-1.0)/\$speed", "params" => array(array("paramid" => "density"),array("paramid" => "speed"))), "(-7 array("expression" => "And((lower_than(-7,\$dst)),(lower_than(\$dst,-3)))", "params" => array(array("paramid" => "dst"))), "cass_caps_elssec4(range[0,10])" => array("expression" => "\$sum_into_table_range_cass_caps_elssec4_0_0_10", "params" => array(array("paramid" => "sum_into_table_range", "fullparamid" => "sum_into_table_range_cass_caps_elssec4_0_0_10", "template_args" => array("paramid"=>"cass_caps_elssec4","relateddim"=>0,"min"=>"0","max"=>"10")))), "abs(dst)>0" => array("expression" => "greater_than((abs(\$dst)),0)", "params" => array(array("paramid" => "dst"))), ); //init constants, aliases and functions for test $this->constantsArray = array( "@const_1" => 1e-10, "@const_2" => -2.8 ); $this->aliasesArray = array( "#alias_1" => "dst", "#alias_2" => "imf(1)" ); $this->functionsArray = array( "func_old" => array("kernel_name" => "func_new", "nb_args" => 0), "atan" => array("kernel_name" => "atan", "nb_args" => 0), "shiftT_" => array("kernel_name" => "#timeShift", "nb_args" => 1), "smooth_" => array("kernel_name" => "#boxcar", "nb_args" => 1), "deriv" => array("kernel_name" => "#deriv", "nb_args" => 0), "abs" => array("kernel_name" => "abs", "nb_args" => 0), ); //add operator to change in function foreach (self::$operators as $operator) { if ($operator["type"] == "function") $this->functionsArray[$operator["function"]] = array("kernel_name" => $operator["function"], "nb_args" => 0); } // foreach ($tests as $key => $value) { echo "==> Test : ".$key.PHP_EOL; $res = $this->parse($key); echo "Result : ".$res["expression"].PHP_EOL; echo json_encode($value).PHP_EOL.json_encode($res).PHP_EOL; if (json_encode($value) == json_encode($res)) echo "OK !".PHP_EOL; else echo "ERROR !".PHP_EOL; } } /* * @brief main method to parse a IHM expression */ public function parse($expression) { //echo "Source expression : ".$expression.PHP_EOL; //clean expression and replace constants and aliases by associated value $this->clean($expression); $this->replaceConstants($expression); $this->replaceAliases($expression); $this->clean($expression); //explode expression $elements = $this->explodeExpression($expression); //group elements in tree $tree = $this->buildTreeElements($elements); //translate $params_full = array(); $translated = $this->translate($tree,$params_full); //keep only params id $params = array(); foreach ($params_full as $param_full) { if (array_key_exists("template_args", $param_full)) { $params[] = array( "paramid" => $param_full['id'], "fullparamid" => $param_full['fullparamid'], "template_args" => $param_full['template_args'] ); } else if (!$this->isTest && (($templated_param_info = $this->templatedParamMgr->parseTemplatedParam($param_full["id"])) !== FALSE)) { $params[] = $templated_param_info; } else if ($this->isImpexParam($param_full["id"])) { if (!$this->impexParamMgr) { $this->impexParamMgr = new IHMImpexParamClass(); } $templated_param_info = $this->impexParamMgr->parseImpexParam($param_full["id"]); $params[] = $templated_param_info; } else $params[] = array("paramid" => $param_full["id"]); } return array("expression" => $translated, "params" => $params); } private function isImpexParam($paramID) { if (!$this->isTest) return preg_match("#^".IHMImpexParamClass::$paramPrefix."#",$paramID); return FALSE; } /* * @brief clean expression */ private function clean(&$expression) { //remove all " " $expression = str_replace(" ", "", $expression); //replace [ and { by ( $expression = str_replace(array("[","{"), "(", $expression); //replace ] and } by ) $expression = str_replace(array("]","}"), ")", $expression); } /* * @brief detect if some constants are defined in the expression */ private function isConstantDetected($expression) { return preg_match('/'.self::$constantTag.'/', $expression); } /* * @brief replace constants by real values in expression */ private function replaceConstants(&$expression) { if (!$this->isConstantDetected($expression)) return; if (!isset($this->constantsArray)) { //load constants array $dom = new DomDocument("1.0"); $constantsXMLFile = IHMConfigClass::getConstantsFilePath(); if (!$dom->load($constantsXMLFile)) throw new Exception('Cannot load constants file'); $this->constantsArray = array(); $constants_ = $dom->getElementsByTagName(self::$constantNode); for ($i = 1; $i < $constants_->length; $i++) $this->constantsArray[self::$constantTag.$constants_->item($i)->getAttribute(self::$constantNameAtt)] = $constants_->item($i)->nodeValue; } //replace $expression = strtr($expression, $this->constantsArray); //be sure that all constants are replaced if ($this->isConstantDetected($expression)) throw new Exception('Cannot replace some constants : '.$expression); } /* * @brief detect if at least one aliases is defined in the expression */ private function isAliasDetected($expression) { return preg_match('/'.self::$aliasTag.'/', $expression); } /* * @brief replace aliases by real parameter id in expression */ private function replaceAliases(&$expression) { if (!$this->isAliasDetected($expression)) return; if (!isset($this->aliasesArray)) { //load aliases array $dom = new DomDocument("1.0"); $aliasesXMLFile = IHMConfigClass::getUserAliasesFilePath(); if (!$dom->load($aliasesXMLFile)) throw new Exception('Cannot load aliases file'); $this->aliasesArray = array(); $aliases_ = $dom->getElementsByTagName(self::$aliasNode); for ($i = 1; $i < $aliases_->length; $i++) $this->aliasesArray[self::$alias_tag.$aliases_->item($i)->getAttribute(self::$aliasNameAtt)] = $aliases_->item($i)->nodeValue; } //replace $expression = strtr($expression, $this->aliasesArray); //be sure that all aliases are replaced if ($this->isAliasDetected($expression)) throw new Exception('Cannot replace some aliases : '.$expression); } /* * @brief load list of available functions */ private function loadFunctions() { if (isset($this->functionsArray)) return; $dom = new DomDocument("1.0"); $functionsXMLFile = IHMConfigClass::getFunctionsFilePath(); if (!$dom->load($functionsXMLFile)) throw new Exception('Cannot load functions file'); $this->functionsArray = array(); $functions_ = $dom->getElementsByTagName(self::$functionsNode); for ($i = 0; $i < $functions_->length; $i++) { $tempArr = explode('(', $functions_->item($i)->getAttribute(self::$functionsNameAtt)); $nbArgs = $functions_->item($i)->getAttribute(self::$functionsArgsAtt); $kernelFunctions_ = $functions_->item($i)->getElementsByTagName(self::$functionsNewKernelNode); if ($kernelFunctions_->length == 0) $kernelFunction = ""; else $kernelFunction = $kernelFunctions_->item(0)->nodeValue; $this->functionsArray[$tempArr[0]] = array( "kernel_name" => $kernelFunction, "nb_args" => intval($nbArgs), "isOperator" => false ); } //add operator to change in function foreach (self::$operators as $operator) { if ($operator["type"] == "function") $this->functionsArray[$operator["function"]] = array( "kernel_name" => $operator["function"], "nb_args" => 0, "isOperator" => true ); } } /* * @brief detect if the element is a function */ private function isFunction($element) { $this->loadFunctions(); return array_key_exists($element,$this->functionsArray); } /* * @brief detect if the element is "range" */ private function isRange($element) { return ($element == "range"); } /* * @brief detect if the element is an operator */ private function isOperator($c) { foreach (self::$operators as $key => $value) if ($key == $c) return true; } /* * @brief get the function associated to an operator */ private function getOperatorAssociatedFunction($c) { foreach (self::$operators as $key => $value) if ($key == $c) if ($value["type"] == "function") return $value["function"]; return ""; } /* * @brief detect if the operator needed to be replace by a function */ private function isOperatorToFunction($c) { return ($this->getOperatorAssociatedFunction($c) != ""); } /* * @brief detect if the element is a separator */ private function isSeparator($c) { return ($this->isOperator($c) || $c == "(" || $c == ")" || $c == ","); } /* * @brief detect a decimal value */ private function isDecimal($element) { return preg_match("/^[+\-]?(?:0|[1-9]\d*)(?:\.?\d*)?(?:[eE][+\-]?\d+)?/",$element); //return preg_match("/[+\-]?(?:0|[1-9]\d*)(?:\.?\d*)?(?:[eE][+\-]?\d+)/",$element); } /* * @brief detect an integer value */ private function isInteger($element) { return preg_match("/^\d+$/",$element); } /* * @brief explode expression for parsing */ private function explodeExpression($expression) { $crt = ""; $elements = array(); for ($i = 0; $i < strlen($expression); ++$i) { $c = $expression[$i]; if ($this->isSeparator($c)) { if ((($c == "+") || ($c == "-")) && preg_match('/^\d*\.?\d*[eE]$/',$crt)) { //scientific decimal element => false positive separator $crt .= $c; continue; } //push in elements list if ($crt != "") $elements[] = $crt; $elements[] = $c; $crt = ""; continue; } $crt .= $c; } if ($crt != "") $elements[] = $crt; //regroup parameters and components before to add brackets for operators priority $els = array(); $i = 0; for ($i; $i < count($elements)-3; ++$i) { if (($elements[$i+1] == "(") && $this->isParameter($elements[$i]) && $this->isInteger($elements[$i+2]) && ($elements[$i+3] == ")")) { $els[] = ($elements[$i].$elements[$i+1].$elements[$i+2].$elements[$i+3]); $i = $i+3; continue; } $els[] = $elements[$i]; } for ($j = $i; $j < count($elements); ++$j) $els[] = $elements[$j]; $elements = $els; //regroup negative number $els = array(); $i = 0; for ($i; $i < count($elements)-1; ++$i) { if (($elements[$i] == "-") && ( $this->isDecimal($elements[$i+1]) || $this->isInteger($elements[$i+1]))) { if (($i == 0) || (!$this->isDecimal($elements[$i-1]) && !$this->isInteger($elements[$i-1]) && !$this->isParameter($elements[$i-1]))) { $els[] = ($elements[$i].$elements[$i+1]); $i = $i+1; continue; } } $els[] = $elements[$i]; } for ($j = $i; $j < count($elements); ++$j) $els[] = $elements[$j]; $elements = $els; //add brackets for operators priority $els = array(); $i = 0; for ($i; $i < count($elements)-2; ++$i) { if ($elements[$i+1] == "*" || $elements[$i+1] == "/" || $elements[$i+1] == "^" || $elements[$i+1] == "<" || $elements[$i+1] == ">") { if ($elements[$i] != ")") { $els[] = "("; $els[] = $elements[$i]; $els[] = $elements[$i+1]; $els[] = $elements[$i+2]; $els[] = ")"; $i += 2; } else $els[] = $elements[$i]; } else $els[] = $elements[$i]; } for ($j = $i; $j < count($elements); ++$j) $els[] = $elements[$j]; //split parameters and components $elements = $els; $els = array(); $i = 0; for ($i; $i < count($elements); ++$i) { $tmpArray = explode('(',$elements[$i]); if ((count($tmpArray) == 2) && ($tmpArray[0] != "") && ($tmpArray[1] != "")) { $tmpArray2 = explode(')',$tmpArray[1]); if ((count($tmpArray2) == 2) && ($tmpArray2[0] != "") && ($tmpArray2[1] == "")) { $els[] = "("; $els[] = $tmpArray[0]; $els[] = "("; $els[] = $tmpArray2[0]; $els[] = ")"; $els[] = ")"; continue; } } $els[] = $elements[$i]; } return $els; } /* * @brief build a tree of the expression */ private function buildTreeElements($elements) { $expression_group = array(); $crt_group = &$expression_group; $opened_groups = array($expression_group); for($i = 0; $i < count($elements); ++$i) { if ($elements[$i] == "(") { array_push($opened_groups,array()); continue; } if ($elements[$i] == ")") { $group_to_close = $opened_groups[count($opened_groups)-1]; array_pop($opened_groups); if (count($opened_groups) <= 0) throw new Exception('Expression error - Brackets definition'); array_push($opened_groups[count($opened_groups)-1],$group_to_close); continue; } if (count($opened_groups) <= 0) throw new Exception('Expression error - Brackets definition'); array_push($opened_groups[count($opened_groups)-1],$elements[$i]); } if (count($opened_groups) != 1) throw new Exception('Expression error - Brackets definition'); $expression_group = $opened_groups[0]; return $expression_group; } /* * @brief detect if the element is a parameter */ private function isParameter($element) { return (!is_array($element) && !$this->isDecimal($element) && !$this->isOperator($element) && !$this->isFunction($element) && !$this->isRange($element) && ($element != ",") && ($element != ";") && ($element != "(")); } /* * @brief detect if the element is a component for a parameter */ private function isParameterComponent($tree, $i) { $paramId = ""; if ($i <= 0) return false; if (!$this->isParameter($tree[$i-1])) return false; return (is_array($tree[$i]) && (count($tree[$i] == 1)) && $this->isInteger($tree[$i][0])); } /* * @brief detect if the element is a range for a parameter */ private function isParameterRange($tree, $i) { $paramId = ""; if ($i <= 0) return false; if (!$this->isParameter($tree[$i-1])) return false; return (is_array($tree[$i]) && (count($tree[$i] == 1)) && $this->isRange($tree[$i][0])); } /* * @brief add a parameter */ private function addParameterIdInParameterArray($paramId, &$params) { foreach ($params as $param) if ($param["id"] == $paramId) return; $params[] = array("id" => $paramId, "indexes" => array(), "calib_infos" => array()); } /* * @brief add a parameter component */ private function addParameterComponentInParameterArray($paramId, $component, &$params) { foreach ($params as &$param) if ($param["id"] == $paramId) { array_push($param["indexes"],$component); return; } } /* * @brief add a parameter range */ private function addParameterRangeInParameterArray($paramId, $range, &$params) { $sum_parameter_id = ""; if ((count($range) == 2) && (count($range[1]) == 3) && ($this->isInteger($range[1][0])) && ($this->isInteger($range[1][2])) & ($range[1][1] == ",")) { $sum_parameter_id = "sum_into_table_range_".$paramId."_0_".$range[1][0]."_".$range[1][2]; $params[] = array("id" => "sum_into_table_range", "fullparamid" => $sum_parameter_id, "indexes" => array(), "calib_infos" => array(), "template_args" => array( "paramid" => $paramId, "relateddim" => 0, "min" => $range[1][0], "max" => $range[1][2] ) ); } return $sum_parameter_id; } /* * @brief process used to treat parameter components */ private function regroupParameterAndComponent($tree,&$params,&$result) { for ($i = 0; $i < count($tree); ++$i) { if (is_array($tree[$i]) && (count($tree[$i]) > 0)) { $res = array(); $this->regroupParameterAndComponent($tree[$i],$params,$res); $result[] = $res; } else if ($this->isParameter($tree[$i])) { $param = $tree[$i]; if ($i < count($tree) - 1) { if ($this->isParameterRange($tree,$i+1)) { $sum_parameter_id = $this->addParameterRangeInParameterArray($param, $tree[$i+1], $params); $param = $sum_parameter_id; ++$i; } else { $this->addParameterIdInParameterArray($param,$params); if ($this->isParameterComponent($tree,$i+1)) { $this->addParameterComponentInParameterArray($param, $tree[$i+1][0], $params); $param .= ("[".$tree[$i+1][0]."]"); ++$i; } } } else $this->addParameterIdInParameterArray($param,$params); $result[] = $param; } else { $result[] = $tree[$i]; } } } /* * @brief replace all operators that's needed to be replace by a function */ private function fixOperatorToFunction($tree,&$result) { $i = 0; for ($i; $i < count($tree)-1; ++$i) { if ($this->isOperatorToFunction($tree[$i+1])) { if ($i >= count($tree)-2) throw new Exception('Expression error - Error in operator definition'); if (is_array($tree[$i])) { $left = array(); $this->fixOperatorToFunction($tree[$i],$left); } else $left = $tree[$i]; $function = $this->getOperatorAssociatedFunction($tree[$i+1]); if (is_array($tree[$i+2])) { $right = array(); $this->fixOperatorToFunction($tree[$i+2],$right); } else $right = $tree[$i+2]; $result[] = $function; $result[] = array($left,",",$right); $i += 2; } else { if (is_array($tree[$i])) { $res = array(); $this->fixOperatorToFunction($tree[$i],$res); $result[] = $res; } else $result[] = $tree[$i]; } } for ($j = $i; $j < count($tree); ++$j) { if (is_array($tree[$j])) { $res = array(); $this->fixOperatorToFunction($tree[$j],$res); $result[] = $res; } else $result[] = $tree[$j]; } } private function fixGroupFunction($tree,&$result) { $result = array(); for ($i = 0; $i < count($tree); ++$i) { if (is_array($tree[$i])) { $res = array(); $this->fixGroupFunction($tree[$i],$res); $result[] = $res; } else if ($this->isFunction($tree[$i])) { if ($i == count($tree) - 1) { throw new Exception('Expression error - Error in function call'); } $res = array(); $this->fixGroupFunction($tree[$i+1],$res); $result[] = array($tree[$i],$res); ++$i; } else $result[] = $tree[$i]; } } /* * @brief process used to replace "," by ";" for function */ private function fixComaFunction($tree,$isFunctionGroup,&$result) { for ($i = 0; $i < count($tree); ++$i) { if (is_array($tree[$i]) && (count($tree[$i]) > 0)) { $groupFunc = false; if (($i > 0) && (!is_array($tree[$i-1]))) { $groupFunc = $this->isFunction($tree[$i-1]); } $res = array(); $this->fixComaFunction($tree[$i],$groupFunc,$res); $result[] = $res; } else { if ($isFunctionGroup && ($tree[$i] == ',')) $result[] = ';'; else $result[] = $tree[$i]; } } } /* * @brief write the result expression from tree elements */ private function writeTranslate($tree,$params) { $translated = ""; for ($i = 0; $i < count($tree); ++$i) { //echo "=> ".$tree[$i].PHP_EOL; if (is_array($tree[$i])) { //echo "ARRAY".PHP_EOL; if ((count($tree[$i]) > 1) || (count($tree[$i]) == 1) && ($i>0) && (!is_array($tree[$i-1]) && $this->isFunction($tree[$i-1]))) $translated .= ("(".$this->writeTranslate($tree[$i],$params).")"); else $translated .= ($this->writeTranslate($tree[$i],$params)); } else if ($this->isDecimal($tree[$i])) { //echo "DECIMAL ".$tree[$i].PHP_EOL; $translated .= $tree[$i]; } else if ($this->isOperator($tree[$i])) { //echo "OPERATOR".PHP_EOL; $translated .= $tree[$i]; } else if ($tree[$i] == ",") { //echo "COMA".PHP_EOL; $translated .= ","; } else if ($tree[$i] == ";") { // echo "DOT COMA".PHP_EOL; $translated .= ";"; } else if ($this->isFunction($tree[$i])) { //echo "FUNCTION".PHP_EOL; $kernelFunction = $this->functionsArray[$tree[$i]]["kernel_name"]; if ($kernelFunction == "") throw new Exception('Expression error - Function '.$tree[$i]." not implemented"); $firstArgForFunc = ($this->functionsArray[$tree[$i]]["nb_args"] != 0); $translated .= $kernelFunction; } else { $founded = false; foreach ($params as $param) { //echo "TEST ".$param["id"].PHP_EOL; if (array_key_exists("template_args", $param)) { $founded = true; $translated .= "\$".$param["fullparamid"]; break; } else if ($param["id"] == $tree[$i]) { $founded = true; $translated .= ("\$".$tree[$i]); break; } foreach ($param["indexes"] as $index) { $temp = ""; $temp .= ($param["id"]."[".$index."]"); //echo "TEST 2 ".$temp.PHP_EOL; if ($temp == $tree[$i]) { $founded = true; $translated .= ("\$".$tree[$i]); break; } } if ($founded) break; } if (!$founded) { throw new Exception('Expression error - Unknown element '.$tree[$i]); } } } return $translated; } /* * @brief sequence used to translate a tree elements */ private function translate($tree,&$params) { $res_1 = array(); $this->regroupParameterAndComponent($tree,$params,$res_1); $res_2 = array(); $this->fixGroupFunction($res_1,$res_2); $res_3 = array(); $this->fixComaFunction($res_2,false,$res_3); $res_4 = array(); $this->fixOperatorToFunction($res_3,$res_4); return $this->writeTranslate($res_4,$params); } } /*$parser = new IHMExpressionParserClass(TRUE); try { $parser->test(); } catch (Exception $e) { echo 'Exception detected : '.$e->getMessage().PHP_EOL; }*/ ?>