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