1<?php 2 3 4namespace ComboStrap; 5 6 7use Antlr\Antlr4\Runtime\ParserRuleContext; 8use Antlr\Antlr4\Runtime\Tree\ErrorNode; 9use Antlr\Antlr4\Runtime\Tree\ParseTreeListener; 10use Antlr\Antlr4\Runtime\Tree\ParseTreeWalker; 11use Antlr\Antlr4\Runtime\Tree\TerminalNode; 12use ComboStrap\PageSqlParser\PageSqlLexer; 13use ComboStrap\PageSqlParser\PageSqlParser; 14 15 16/** 17 * Class SqlTreeListener 18 * @package ComboStrap\LogicalSqlAntlr 19 * 20 * The listener that is called by {@link ParseTreeWalker::walk()} 21 * that performs a walk on the given parse tree starting at the root 22 * and going down recursively with depth-first search. 23 * 24 * The process is to check all token and to process them 25 * with context 26 */ 27final class PageSqlTreeListener implements ParseTreeListener 28{ 29 /** 30 * @var PageSqlLexer 31 */ 32 private $lexer; 33 /** 34 * @var PageSqlParser 35 */ 36 private $parser; 37 /** 38 * @var String 39 */ 40 private $physicalSql; 41 /** 42 * @var int 43 */ 44 private $ruleState; 45 46 private const STATE_VALUES = [ 47 PageSqlParser::RULE_columns, 48 PageSqlParser::RULE_tables, 49 PageSqlParser::RULE_predicates, 50 PageSqlParser::RULE_orderBys, 51 PageSqlParser::RULE_limit, 52 ]; 53 /** 54 * @var string[] 55 */ 56 private $parameters = []; 57 /** 58 * @var array 59 */ 60 private $columns = []; 61 /** 62 * @var string 63 */ 64 private $pageSqlString; 65 66 /** 67 * SqlTreeListener constructor. 68 * 69 * @param PageSqlLexer $lexer 70 * @param PageSqlParser $parser 71 * @param string $sql 72 */ 73 public function __construct(PageSqlLexer $lexer, PageSqlParser $parser, string $sql) 74 { 75 $this->lexer = $lexer; 76 $this->parser = $parser; 77 $this->pageSqlString = $sql; 78 } 79 80 81 /** 82 * Leaf node 83 * @param TerminalNode $node 84 */ 85 public function visitTerminal(TerminalNode $node): void 86 { 87 88 $type = $node->getSymbol()->getType(); 89 $text = $node->getText(); 90 switch ($type) { 91 case PageSqlParser::SELECT: 92 $this->physicalSql .= "select\n\t*\n"; 93 94 /** 95 * The from select is optional 96 * Check if it's there 97 */ 98 $parent = $node->getParent(); 99 for ($i = 0; $i < $parent->getChildCount(); $i++) { 100 $child = $parent->getChild($i); 101 if ($child instanceof ParserRuleContext) { 102 /** 103 * @var ParserRuleContext $child 104 */ 105 if ($child->getRuleIndex() === PageSqlParser::RULE_tables) { 106 return; 107 } 108 } 109 } 110 $this->physicalSql .= "from\n\tpages\n"; 111 break; 112 case PageSqlParser::SqlName: 113 switch ($this->ruleState) { 114 case PageSqlParser::RULE_predicates: 115 116 // variable name 117 $variableName = strtolower($text); 118 $this->physicalSql .= "\t{$variableName} "; 119 120 break; 121 case 122 PageSqlParser::RULE_orderBys: 123 $text = strtolower($text); 124 $this->physicalSql .= "\t{$text} "; 125 break; 126 case PageSqlParser::RULE_columns: 127 $this->columns[] = $text; 128 break; 129 } 130 break; 131 case PageSqlParser::EQUAL: 132 case PageSqlParser::LIKE: 133 case PageSqlParser::GLOB: 134 case PageSqlParser::LESS_THAN_OR_EQUAL: 135 case PageSqlParser::LESS_THAN: 136 case PageSqlParser::GREATER_THAN: 137 case PageSqlParser::GREATER_THAN_OR_EQUAL: 138 case PageSqlParser::NOT_EQUAL: 139 switch ($this->ruleState) { 140 case PageSqlParser::RULE_predicates: 141 $this->physicalSql .= "{$text} "; 142 } 143 break; 144 case PageSqlParser::StringLiteral: 145 switch ($this->ruleState) { 146 case PageSqlParser::RULE_predicates: 147 // Parameters 148 if ( 149 ($text[0] === "'" and $text[strlen($text) - 1] === "'") 150 || 151 ($text[0] === '"' and $text[strlen($text) - 1] === '"')) { 152 $quote = $text[0]; 153 $text = substr($text, 1, strlen($text) - 2); 154 $text = str_replace("$quote$quote", "$quote", $text); 155 } 156 $this->parameters[] = $text; 157 $this->physicalSql .= "?"; 158 break; 159 } 160 break; 161 case PageSqlParser:: AND: 162 case PageSqlParser:: OR: 163 if ($this->ruleState === PageSqlParser::RULE_predicates) { 164 $this->physicalSql .= " {$text}\n"; 165 } 166 return; 167 case PageSqlParser::LIMIT: 168 case PageSqlParser:: NOT: 169 $this->physicalSql .= "{$text} "; 170 return; 171 case PageSqlParser:: DESC: 172 case PageSqlParser:: LPAREN: 173 case PageSqlParser:: RPAREN: 174 case PageSqlParser:: ASC: 175 $this->physicalSql .= "{$text}"; 176 break; 177 case PageSqlParser:: COMMA: 178 switch ($this->ruleState) { 179 case PageSqlParser::RULE_columns: 180 return; 181 case PageSqlParser::RULE_orderBys: 182 $this->physicalSql .= "{$text}\n"; 183 return; 184 default: 185 $this->physicalSql .= "{$text}"; 186 return; 187 } 188 case PageSqlParser::ESCAPE: 189 $this->physicalSql .= " {$text} "; 190 return; 191 case PageSqlParser::Number: 192 switch ($this->ruleState) { 193 case PageSqlParser::RULE_limit: 194 $this->physicalSql .= "{$text}"; 195 return; 196 case PageSqlParser::RULE_predicates: 197 $this->parameters[] = $text; 198 $this->physicalSql .= "?"; 199 return; 200 default: 201 $this->physicalSql .= "{$text} "; 202 return; 203 } 204 default: 205 // We do nothing because the token may have been printed at a higher level such as order by 206 } 207 } 208 209 210 public 211 function visitErrorNode(ErrorNode $node): void 212 { 213 $charPosition = $node->getSymbol()->getStartIndex(); 214 $textMakingTheError = $node->getText(); // $this->lexer->getText(); 215 216 $position = "at position: $charPosition"; 217 if ($charPosition != 0) { 218 $position .= ", in `" . substr($this->pageSqlString, $charPosition, -1)."`"; 219 } 220 $message = "PageSql Parsing Error: The token `$textMakingTheError` was unexpected ($position)."; 221 throw new \RuntimeException($message); 222 223 } 224 225 226 /** 227 * 228 * Parent Node 229 * 230 * On each node, enterRule is called before recursively walking down into child nodes, 231 * then {@link PageSqlTreeListener::exitEveryRule()} is called after the recursive call to wind up. 232 * Parameters: 233 * @param ParserRuleContext $ctx 234 */ 235 public 236 function enterEveryRule(ParserRuleContext $ctx): void 237 { 238 239 $ruleIndex = $ctx->getRuleIndex(); 240 if (in_array($ruleIndex, self::STATE_VALUES)) { 241 $this->ruleState = $ruleIndex; 242 } 243 switch ($ruleIndex) { 244 case PageSqlParser::RULE_orderBys: 245 $this->physicalSql .= "order by\n"; 246 break; 247 case PageSqlParser::RULE_tables: 248 $this->physicalSql .= "from\n"; 249 break; 250 case PageSqlParser::RULE_predicates: 251 $this->physicalSql .= "where\n"; 252 break; 253 case PageSqlParser::RULE_functionNames: 254 // Print the function name 255 $this->physicalSql .= $ctx->getText(); 256 break; 257 case PageSqlParser::RULE_tableNames: 258 // Print the table name 259 $this->physicalSql .= "\t{$ctx->getText()}\n"; 260 break; 261 } 262 263 264 } 265 266 /** 267 * 268 * Parent Node 269 * 270 * On each node, {@link PageSqlTreeListener::enterEveryRule()} is called before recursively walking down into child nodes, 271 * then {@link PageSqlTreeListener::exitEveryRule()} is called after the recursive call to wind up. 272 * @param ParserRuleContext $ctx 273 */ 274 public 275 function exitEveryRule(ParserRuleContext $ctx): void 276 { 277 $ruleIndex = $ctx->getRuleIndex(); 278 switch ($ruleIndex) { 279 case PageSqlParser::RULE_predicates: 280 case PageSqlParser::RULE_orderBys: 281 $this->physicalSql .= "\n"; 282 break; 283 } 284 285 } 286 287 public 288 function getParameters(): array 289 { 290 return $this->parameters; 291 } 292 293 public 294 function getColumns(): array 295 { 296 return $this->columns; 297 } 298 299 /** 300 * For documentation 301 * @param ParserRuleContext $ctx 302 * @return string 303 */ 304 private 305 function getRuleName(ParserRuleContext $ctx): string 306 { 307 $ruleNames = $this->parser->getRuleNames(); 308 return $ruleNames[$ctx->getRuleIndex()]; 309 } 310 311 /** 312 * For documentation 313 * @param TerminalNode $node 314 * @return string|null 315 */ 316 private 317 function getTokenName(TerminalNode $node) 318 { 319 $token = $node->getSymbol(); 320 return $this->lexer->getVocabulary()->getSymbolicName($token->getType()); 321 } 322 323 public 324 function getPhysicalSql(): string 325 { 326 return $this->physicalSql; 327 } 328 329 330} 331