/**
  * @param mixed $expr
  * @return mixed
  * @throws \Exception
  */
 protected function evl($expr)
 {
     if (is_string($expr)) {
         return $expr;
     }
     if (is_array($expr)) {
         return $expr;
     }
     $className = get_class($expr);
     switch ($className) {
         case "PhpParser\\Node\\Scalar\\DNumber":
         case "PhpParser\\Node\\Scalar\\LNumber":
         case "PhpParser\\Node\\Scalar\\String_":
             return $this->debug($expr, $expr->value);
             // Arithmetic Operators
         // Arithmetic Operators
         case "PhpParser\\Node\\Expr\\UnaryMinus":
             return $this->debug($expr, -$this->evl($expr->expr));
         case "PhpParser\\Node\\Expr\\BinaryOp\\Plus":
             return $this->debug($expr, $this->evl($expr->left) + $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\Minus":
             return $this->debug($expr, $this->evl($expr->left) - $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\Mul":
             return $this->debug($expr, $this->evl($expr->left) * $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\Div":
             return $this->debug($expr, $this->evl($expr->left) / $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\Mod":
             return $this->debug($expr, $this->evl($expr->left) % $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\Pow":
             // Operator ** Introduced in PHP 5.6
             return $this->debug($expr, pow($this->evl($expr->left), $this->evl($expr->right)));
             // Bitwise Operators
         // Bitwise Operators
         case "PhpParser\\Node\\Expr\\BinaryOp\\BitwiseAnd":
             return $this->debug($expr, $this->evl($expr->left) & $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\BitwiseOr":
             return $this->debug($expr, $this->evl($expr->left) | $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\BitwiseXor":
             return $this->debug($expr, $this->evl($expr->left) ^ $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BitwiseNot":
             return $this->debug($expr, ~$this->evl($expr->expr));
         case "PhpParser\\Node\\Expr\\BinaryOp\\ShiftLeft":
             return $this->debug($expr, $this->evl($expr->left) << $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\ShiftRight":
             return $this->debug($expr, $this->evl($expr->left) >> $this->evl($expr->right));
             // Comparison Operators
         // Comparison Operators
         case "PhpParser\\Node\\Expr\\BinaryOp\\Equal":
             return $this->debug($expr, $this->evl($expr->left) == $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\Identical":
             return $this->debug($expr, $this->evl($expr->left) === $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\NotEqual":
             return $this->debug($expr, $this->evl($expr->left) != $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\NotIdentical":
             return $this->debug($expr, $this->evl($expr->left) !== $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\Smaller":
             return $this->debug($expr, $this->evl($expr->left) < $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\Greater":
             return $this->debug($expr, $this->evl($expr->left) > $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\SmallerOrEqual":
             return $this->debug($expr, $this->evl($expr->left) <= $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\GreaterOrEqual":
             return $this->debug($expr, $this->evl($expr->left) >= $this->evl($expr->right));
             // Logical Operators
         // Logical Operators
         case "PhpParser\\Node\\Expr\\BinaryOp\\LogicalAnd":
             return $this->debug($expr, $this->evl($expr->left) and $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\LogicalOr":
             return $this->debug($expr, $this->evl($expr->left) or $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\LogicalXor":
             return $this->debug($expr, $this->evl($expr->left) xor $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BooleanNot":
             return $this->debug($expr, !$this->evl($expr->expr));
         case "PhpParser\\Node\\Expr\\BinaryOp\\BooleanAnd":
             return $this->debug($expr, $this->evl($expr->left) && $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\BooleanOr":
             return $this->debug($expr, $this->evl($expr->left) || $this->evl($expr->right));
             // Casting
         // Casting
         case "PhpParser\\Node\\Expr\\Cast\\String_":
             return $this->debug($expr, (string) $this->evl($expr->expr));
         case "PhpParser\\Node\\Expr\\Cast\\Int_":
             return $this->debug($expr, (int) $this->evl($expr->expr));
         case "PhpParser\\Node\\Expr\\Cast\\Bool_":
             return $this->debug($expr, (bool) $this->evl($expr->expr));
         case "PhpParser\\Node\\Expr\\Cast\\Double":
             return $this->debug($expr, (double) $this->evl($expr->expr));
         case "PhpParser\\Node\\Expr\\Cast\\Object_":
             return $this->debug($expr, (object) $this->evl($expr->expr));
         case "PhpParser\\Node\\Expr\\Cast\\Array_":
             return $this->debug($expr, (array) $this->evl($expr->expr));
             // String Operators
         // String Operators
         case "PhpParser\\Node\\Expr\\BinaryOp\\Concat":
             return $this->debug($expr, $this->evl($expr->left) . $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\BinaryOp\\Coalesce":
             // Operator ?? Introduced in PHP 7
             try {
                 $left = $this->evl($expr->left);
             } catch (\OutOfBoundsException $e) {
                 $left = null;
             }
             return $this->debug($expr, null !== $left ? $left : $this->evl($expr->right));
         case "PhpParser\\Node\\Expr\\Ternary":
             return $this->debug($expr, $this->evl($expr->cond) ? $this->evl($expr->if) : $this->evl($expr->else));
         case "PhpParser\\Node\\Expr\\Isset_":
             try {
                 $result = $this->evl($expr->vars[0]);
             } catch (\OutOfBoundsException $e) {
                 $result = null;
             }
             return $this->debug($expr, $result !== null);
         case "PhpParser\\Node\\Expr\\MethodCall":
             return $this->evaluateMethodCall($expr);
         case "PhpParser\\Node\\Expr\\ArrayDimFetch":
             $propertyName = $this->evl($expr->dim);
             $variable = $this->evl($expr->var);
             if ($variable instanceof \PhpParser\Node\Expr\ArrayItem) {
                 $variable = $this->evl($variable->value);
             }
             if ($variable instanceof \PhpParser\Node\Expr\Array_) {
                 $variable = $this->evl($variable);
             }
             // var_export($variable);
             if (!is_array($variable)) {
                 $variableName = isset($expr->var->name) ? $expr->var->name : '';
                 return $this->error("Unsupported ArrayDimFetch expression" . " - Variable \${$variableName} is not an array", $expr);
             } elseif (is_array($variable) && isset($variable[$propertyName])) {
                 return $this->debug($expr, $variable[$propertyName]);
             } elseif (is_array($variable) && !isset($variable[$propertyName])) {
                 $this->debug($expr, null);
                 throw new \OutOfBoundsException("Undefined index: {$propertyName}", $this::UNDEFINED_INDEX);
             }
             return $this->error("Unsupported ArrayDimFetch expression", $expr);
         case "PhpParser\\Node\\Expr\\StaticPropertyFetch":
             $className = $this->evl($expr->class);
             //if ($className !== 'R') {
             if (true) {
                 // StaticPropertyFetch is forbidden
                 return $this->error("Unsupported StaticPropertyFetch expression", $expr);
             }
             // echo "var: R . {$propertyName}; \n\n" ;
             $propertyName = $this->evl($expr->name);
             $result = $this->registry->getGlobal($propertyName);
             // var_export($this->registry->getKeys());
             // echo "= " . var_export(is_object($result) ? get_class($result) : $result, true) . "; \n\n" ;
             return $this->debug($expr, $result);
         case "PhpParser\\Node\\Expr\\PropertyFetch":
             return $this->evaluatePropertyFetch($expr);
         case "PhpParser\\Node\\Expr\\Variable":
             return $this->debug($expr, $this->registry->get($expr->name));
         case "PhpParser\\Node\\Expr\\Array_":
             $items = [];
             foreach ($expr->items as $item) {
                 $value = $this->evl($item->value);
                 if (isset($item->key)) {
                     $items[$this->evl($item->key)] = $value;
                 } else {
                     $items[] = $value;
                 }
             }
             return $this->debug($expr, $items);
         case "PhpParser\\Node\\Expr\\ConstFetch":
             return $this->debug($expr, constant($expr->name->parts[0]));
         case "PhpParser\\Node\\Expr\\FuncCall":
             return $this->evaluateFuncCall($expr);
         case "PhpParser\\Node\\Expr\\Closure":
             return $this->debug($expr, $this->closure($expr));
         case "PhpParser\\Node\\Stmt\\Return_":
             return $this->debug($expr, $this->evl($expr->expr));
         case "PhpParser\\Node\\Stmt\\Global_":
             foreach ($expr->vars as $var) {
                 $variableName = $var->name;
                 $value = $this->registry->getGlobal($variableName);
                 $this->registry->register($variableName, $value, true);
             }
             return $this->debug($expr, null);
         case "PhpParser\\Node\\Expr\\Assign":
             if (!isset($expr->var->name) || !isset($expr->expr) || !$expr->var instanceof \PhpParser\Node\Expr\Variable) {
                 return $this->error("Unsupported Assign expression", $expr);
             }
             $variableName = $expr->var->name;
             $value = $this->evl($expr->expr);
             $this->registry->register($variableName, $value, true);
             return $this->debug($expr, $value);
         case "PhpParser\\Node\\Stmt\\If_":
             $cond = $this->evl($expr->cond);
             if ($cond) {
                 return $this->debug($expr, $this->evaluateStmts($expr->stmts), $wrap = false);
             }
             if (isset($expr->elseifs)) {
                 foreach ($expr->elseifs as $elseif) {
                     $cond = $this->evl($elseif->cond);
                     if ($cond) {
                         return $this->debug($expr, $this->evaluateStmts($elseif->stmts), $wrap = false);
                     }
                 }
             }
             if (isset($expr->else)) {
                 return $this->debug($expr, $this->evaluateStmts($expr->else->stmts), $wrap = false);
             }
             return $this->debug($expr, null);
         case "PhpParser\\Node\\Stmt\\Foreach_":
             $exp = $this->evl($expr->expr);
             $valueVar = $this->evl($expr->valueVar->name);
             $keyVar = $expr->keyVar ? $this->evl($expr->keyVar->name) : null;
             if (!is_array($exp)) {
                 return $this->error("Unsupported Foreach_ expression - Undefined variable", $expr);
             }
             foreach ($exp as $key => $value) {
                 $this->registry->register($valueVar, $this->wrap($value), true);
                 if ($keyVar) {
                     $this->registry->register($keyVar, $this->wrap($key), true);
                 }
                 $result = $this->evaluateStmts($expr->stmts);
                 if ($result instanceof \PhpParser\Node\Stmt\Return_) {
                     return $this->debug($expr, $result);
                 }
             }
             return $this->debug($expr, null);
             /*
              * case "PhpParser\\Node\\Stmt\\Echo_":
              * foreach ($expr->exprs as $expr) {
              * echo $this->evl($expr);
              * return null;
              * }
              * return $this->debug($expr, $expr->parts[0]);
              */
         /*
          * case "PhpParser\\Node\\Stmt\\Echo_":
          * foreach ($expr->exprs as $expr) {
          * echo $this->evl($expr);
          * return null;
          * }
          * return $this->debug($expr, $expr->parts[0]);
          */
         case "PhpParser\\Node\\Name":
             if (!isset($expr->parts) || count($expr->parts) != 1) {
                 return $this->error("Unsupported Name expression", $expr);
             }
             return $this->debug($expr, $expr->parts[0]);
         default:
             return $this->error("Unsupported expression {$className}", $expr);
     }
 }