21 declare(strict_types=1);
50 private $identifierStack = [];
52 private $parsingQuery =
false;
54 private $docLines = [];
56 private $variables = [];
61 private $knownDialect = null;
63 private $results = [];
70 $this->fileName = $fileName;
81 while(($line =
fgets($this->fh)) !==
false){
82 $this->readLine($this->lineNo + 1, $line);
84 if(!empty($this->identifierStack)){
85 $this->error(
"Unexpected end of file, " .
count($this->identifierStack) .
" groups not closed");
96 return $this->results;
99 private function readLine(
int $lineNo,
string $line) : void{
100 $this->lineNo = $lineNo;
107 if($this->tryCommand($line)){
111 if(empty($this->identifierStack)){
112 $this->error(
"Unexpected query text; start a query with { first");
114 $this->buffer[] = $line;
115 $this->parsingQuery =
true;
118 private function tryCommand(
string $line) : bool{
119 if(
strpos($line,
"-- #") !== 0){
134 $argOffsets[] = $offset;
139 $this->dialectCommand($args);
142 $this->startCommand($args);
148 $this->docCommand($args, $line, $argOffsets);
151 $this->varCommand($args, $line, $argOffsets);
158 private function dialectCommand(array $args) : void{
160 if($this->knownDialect !== null){
161 $this->error(
"Dialect declared more than once");
164 if(!isset($args[0])){
165 $this->error(
"Missing operand: DIALECT");
168 $this->knownDialect = $args[0];
171 private function startCommand(array $args) : void{
172 if($this->knownDialect === null){
173 $this->error(
"Dialect declaration must be the very first line");
176 if($this->parsingQuery){
177 $this->error(
"Unexpected {, close previous query first");
180 if(!isset($args[0])){
181 $this->error(
"Missing operand: IDENTIFIER_NAME");
184 $this->identifierStack[] = $args[0];
187 private function endCommand() : void{
188 if(
count($this->identifierStack) === 0){
189 $this->error(
"No matching { for }");
192 if($this->parsingQuery){
193 if(
count($this->buffer) === 0){
194 $this->error(
"Documentation/Variables are declared but no query is provided");
197 $query =
implode(
"\n", $this->buffer);
198 $doc =
implode(
"\n", $this->docLines);
199 assert($this->knownDialect !== null);
201 $this->docLines = [];
202 $this->variables = [];
204 $this->parsingQuery =
false;
206 if(isset($this->results[$stmt->getName()])){
207 $this->error(
"Duplicate query name ({$stmt->getName()})");
209 $this->results[$stmt->getName()] = $stmt;
215 private function varCommand(array $args,
string $line, array $argOffsets) : void{
216 if(empty($this->identifierStack)){
217 $this->error(
"Unexpected variable declaration; start a query with { first");
220 if(!isset($args[1])){
221 $this->error(
"Missing operand: VAR_TYPE");
225 $var =
new GenericVariable($args[0], $args[1], isset($args[2]) ?
substr($line, $argOffsets[2] + 1) : null);
227 throw $this->error($e->getMessage());
229 if(isset($this->variables[$var->getName()])){
230 $this->error(
"Duplicate variable definition of :{$var->getName()}");
232 $this->variables[$var->getName()] = $var;
233 $this->parsingQuery =
true;
236 private function docCommand(array $args,
string $line, array $argOffsets) : void{
237 if(empty($this->identifierStack)){
238 $this->error(
"Unexpected documentation; start a query with { first");
242 $this->parsingQuery =
true;
250 private function error(
string $problem) : GenericStatementFileParseException{
251 throw new GenericStatementFileParseException($problem, $this->lineNo, $this->fileName);
__construct(?string $fileName, $fh)
static forDialect(string $dialect, string $name, string $query, string $doc, array $variables, ?string $file, int $lineNo)