. * */ namespace Vvveb\System\Sqlp; class Lexer { protected $regex; protected $offsetToToken; protected $macroMap; public function __construct($tokenMap, $macroMap) { $this->regex = '/(' . implode(')|(', array_keys($tokenMap)) . ')/As'; $this->offsetToToken = array_values($tokenMap); $this->macroMap = $macroMap; } public function lex($string) { $tokens = []; $offset = 0; $sql = ''; while (isset($string[$offset])) { if (! preg_match($this->regex, $string, $matches, 0, $offset)) { throw new \Exception(sprintf('Unexpected character "%s" at offset %d', $string[$offset], $offset)); } // find the first non-empty element (but skipping $matches[0]) using a quick for loop for ($i = 1; '' === $matches[$i]; ++$i); $token = $this->offsetToToken[$i - 1]; //gather all sql tokens into one continuous string if ($token == 'T_SQL') { $sql .= $matches[0]; } else { if ($sql) { $tokens[] = [$sql, 'T_SQL', $offset]; $sql = ''; } $tokens[] = [$matches[0], $token, $offset]; } $offset += strlen($matches[0]); } //add remaining sql if any left if ($sql) { $tokens[] = [$sql, 'T_SQL', $offset]; } return $tokens; } // a recursive function to build the ast structure function tree(&$structure, $k=0) { $output = []; $count = count($structure); for ($i= $k; $i < $count; $i++) { list($element, $type, $offset) = $structure[$i]; $node = $structure[$i]; if ($type == 'T_IF_START') { $ret = $this->tree($structure, $i + 1); $node['children'] = $ret[0]; $i = $ret[1]; } else { if ($type == 'T_IF_END' || $type == 'T_EACH_END') { $output[] = $node; return [$output, $i]; } else { if ($type == 'T_SQL') { } } } $output[] = $node; } return $output; } function parseError($structure, $node_idx) { $node = $structure[$node_idx]; throw( new \Exception( sprintf('sqlP parse error, expecting close tag for `%s` at offset %d at `%s`', $node[0] , $node[2], $this->treeToSql($structure)) )); } // a recursive function to build the ast structure function treeMacro(&$structure, $k=0, $level = 0, $startTag = false) { $output = []; $count = count($structure); for ($i = $k; $i < $count; $i++) { list($element, $type, $offset) = $structure[$i]; $node = $structure[$i]; if ($type == 'T_EACH') { $ret = $this->treeMacro($structure, $i + 1, $level + 1, $type); if (is_array($ret) && count($ret) == 2) { $node['children'] = $ret[0]; $i = $ret[1]; } else { $this->parseError($structure, $i); } } else { if ($type == 'T_ELSE') { $ret = $this->treeMacro($structure, $i + 1, $level + 1, $type); if (is_array($ret) && count($ret) == 2) { $node['children'] = $ret[0]; $i = $ret[1]; } else { $this->parseError($structure, $i); } } } if ($type == 'T_IF_START') { $ret = $this->treeMacro($structure, $i + 1, $level + 1, $type); if (is_array($ret) && count($ret) == 2) { $node['children'] = $ret[0]; $i = $ret[1]; } else { $this->parseError($structure, $i); } } else { if ($level > 0 && ( ($type == 'T_ELSE' && $startTag == 'T_IF_START') || ($type == 'T_IF_END' && $startTag == 'T_ELSE') || ($type == 'T_IF_END' && $startTag == 'T_IF_START') || ($type == 'T_EACH_END' && $startTag == 'T_EACH') )) { $output[] = $node; return [$output, $i]; } else { if ($type == 'T_SQL') { } } } $output[] = $node; } return $output; } function treeToSql(&$tree) { $sql = ''; foreach ($tree as $token) { $sql .= $token[0]; if (isset($token['children'])) { $sql .= $this->treeToSql($token['children']); } } return $sql; } function parseMacro($statement, $regex, $template) { $macro = $template; if (preg_match_all($regex, $statement, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { $macro = $template; //replace macro template variables %$variable $macro = preg_replace_callback( '@\$%(\w+)@', function ($varMatch) use ($match) { return preg_replace_callback( '/:([\w\.]+)/ms', function ($matches) { return '$' . \Vvveb\dotToArrayKey('params.' . $matches[1]); //return '$params[\'' . $matches[1] . '\']'; }, $match[$varMatch[1]]); }, $macro); //@result //replace result variables $macro = preg_replace_callback( '/@result\.([\w\.]+)/', function ($matches) { return \Vvveb\dotToArrayKey('$results.' . $matches[1]); }, $macro); //replace macro template placeholders %placeholder $macro = preg_replace_callback( '@\%(\w+)@', function ($varMatch) use ($match) { return $match[$varMatch[1]]; }, $macro); $statement = str_replace($match[0], $macro, $statement); } } return $statement; } function treeToPhp(&$tree, &$macroMap) { $sql = ''; foreach ($tree as $token) { $name = $token[1]; $code = $token[0]; if ($name == 'T_SQL') { $sql .= $code; } elseif (isset($macroMap[$name])) { $macro = $macroMap[$name]; if (is_array($macro)) { $regex = $macro[0]; $string = $macro[1]; $sql .= $this->parseMacro($code, $regex, $string); } else { $sql .= $macro; } } if (isset($token['children'])) { $sql .= $this->treeToPhp($token['children'], $macroMap); } } return $sql; } }