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 switch ($this->ruleState) { 132 case PageSqlParser::RULE_predicates: 133 $this->physicalSql .= "{$text} "; 134 } 135 break; 136 case PageSqlParser::StringLiteral: 137 switch ($this->ruleState) { 138 case PageSqlParser::RULE_predicates: 139 // Parameters 140 if ( 141 ($text[0] === "'" and $text[strlen($text) - 1] === "'") 142 || 143 ($text[0] === '"' and $text[strlen($text) - 1] === '"')) { 144 $quote = $text[0]; 145 $text = substr($text, 1, strlen($text) - 2); 146 $text = str_replace("$quote$quote", "$quote", $text); 147 } 148 $this->parameters[] = $text; 149 $this->physicalSql .= "?"; 150 break; 151 } 152 break; 153 case PageSqlParser:: AND: 154 case PageSqlParser:: OR: 155 if ($this->ruleState === PageSqlParser::RULE_predicates) { 156 $this->physicalSql .= " {$text}\n"; 157 } 158 return; 159 case PageSqlParser:: NOT: 160 $this->physicalSql .= "{$text} "; 161 return; 162 case PageSqlParser:: DESC: 163 case PageSqlParser:: LPAREN: 164 case PageSqlParser:: RPAREN: 165 case PageSqlParser:: ASC: 166 $this->physicalSql .= "{$text}"; 167 break; 168 case PageSqlParser:: COMMA: 169 switch ($this->ruleState) { 170 case PageSqlParser::RULE_columns: 171 return; 172 case PageSqlParser::RULE_orderBys: 173 $this->physicalSql .= "{$text}\n"; 174 return; 175 default: 176 $this->physicalSql .= "{$text}"; 177 return; 178 } 179 case PageSqlParser::LIMIT: 180 $this->physicalSql .= "{$text} "; 181 return; 182 case PageSqlParser::ESCAPE: 183 $this->physicalSql .= " {$text} "; 184 return; 185 case PageSqlParser::Number: 186 switch ($this->ruleState) { 187 case PageSqlParser::RULE_limit: 188 $this->physicalSql .= "{$text}"; 189 return; 190 case PageSqlParser::RULE_predicates: 191 $this->parameters[] = $text; 192 $this->physicalSql .= "?"; 193 return; 194 default: 195 $this->physicalSql .= "{$text} "; 196 return; 197 } 198 default: 199 // We do nothing because the token may have been printed at a higher level such as order by 200 } 201 } 202 203 204 public 205 function visitErrorNode(ErrorNode $node): void 206 { 207 } 208 209 210 /** 211 * 212 * Parent Node 213 * 214 * On each node, enterRule is called before recursively walking down into child nodes, 215 * then {@link PageSqlTreeListener::exitEveryRule()} is called after the recursive call to wind up. 216 * Parameters: 217 * @param ParserRuleContext $ctx 218 */ 219 public 220 function enterEveryRule(ParserRuleContext $ctx): void 221 { 222 223 $ruleIndex = $ctx->getRuleIndex(); 224 if (in_array($ruleIndex, self::STATE_VALUES)) { 225 $this->ruleState = $ruleIndex; 226 } 227 switch ($ruleIndex) { 228 case PageSqlParser::RULE_orderBys: 229 $this->physicalSql .= "order by\n"; 230 break; 231 case PageSqlParser::RULE_tables: 232 $this->physicalSql .= "from\n"; 233 break; 234 case PageSqlParser::RULE_predicates: 235 $this->physicalSql .= "where\n"; 236 break; 237 case PageSqlParser::RULE_functionNames: 238 // Print the function name 239 $this->physicalSql .= $ctx->getText(); 240 break; 241 case PageSqlParser::RULE_tableNames: 242 // Print the table name 243 $this->physicalSql .= "\t{$ctx->getText()}\n"; 244 break; 245 } 246 247 248 } 249 250 /** 251 * 252 * Parent Node 253 * 254 * On each node, {@link PageSqlTreeListener::enterEveryRule()} is called before recursively walking down into child nodes, 255 * then {@link PageSqlTreeListener::exitEveryRule()} is called after the recursive call to wind up. 256 * @param ParserRuleContext $ctx 257 */ 258 public 259 function exitEveryRule(ParserRuleContext $ctx): void 260 { 261 $ruleIndex = $ctx->getRuleIndex(); 262 switch ($ruleIndex) { 263 case PageSqlParser::RULE_predicates: 264 case PageSqlParser::RULE_orderBys: 265 $this->physicalSql .= "\n"; 266 break; 267 } 268 269 } 270 271 public 272 function getParameters(): array 273 { 274 return $this->parameters; 275 } 276 277 public 278 function getColumns(): array 279 { 280 return $this->columns; 281 } 282 283 /** 284 * For documentation 285 * @param ParserRuleContext $ctx 286 * @return string 287 */ 288 private 289 function getRuleName(ParserRuleContext $ctx): string 290 { 291 $ruleNames = $this->parser->getRuleNames(); 292 return $ruleNames[$ctx->getRuleIndex()]; 293 } 294 295 /** 296 * For documentation 297 * @param TerminalNode $node 298 * @return string|null 299 */ 300 private 301 function getTokenName(TerminalNode $node) 302 { 303 $token = $node->getSymbol(); 304 return $this->lexer->getVocabulary()->getSymbolicName($token->getType()); 305 } 306 307 public 308 function getPhysicalSql(): string 309 { 310 return $this->physicalSql; 311 } 312 313 314 315} 316