input_tokens = $input_tokens; $this->max = count( $input_tokens ); } /** * Returns the updated query. * * @return string */ public function get_updated_query() { $query = ''; foreach ( $this->output_tokens as $token ) { $query .= $token->token; } return $query; } /** * Add a token to the output. * * @param WP_SQLite_Token $token Token object. */ public function add( $token ) { if ( $token ) { $this->output_tokens[] = $token; } } /** * Add multiple tokens to the output. * * @param WP_SQLite_Token[] $tokens Array of token objects. */ public function add_many( $tokens ) { $this->output_tokens = array_merge( $this->output_tokens, $tokens ); } /** * Replaces all tokens. * * @param WP_SQLite_Token[] $tokens Array of token objects. */ public function replace_all( $tokens ) { $this->output_tokens = $tokens; } /** * Peek at the next tokens and return one that matches the given criteria. * * @param array $query Optional. Search query. * [ * 'type' => string|null, // Token type. * 'flags' => int|null, // Token flags. * 'values' => string|null, // Token values. * ]. * * @return WP_SQLite_Token */ public function peek( $query = array() ) { $type = isset( $query['type'] ) ? $query['type'] : null; $flags = isset( $query['flags'] ) ? $query['flags'] : null; $values = isset( $query['value'] ) ? ( is_array( $query['value'] ) ? $query['value'] : array( $query['value'] ) ) : null; $i = $this->index; while ( ++$i < $this->max ) { if ( $this->input_tokens[ $i ]->matches( $type, $flags, $values ) ) { return $this->input_tokens[ $i ]; } } } /** * Move forward and return the next tokens that match the given criteria. * * @param int $nth The nth token to return. * * @return WP_SQLite_Token */ public function peek_nth( $nth ) { $found = 0; for ( $i = $this->index + 1;$i < $this->max;$i++ ) { $token = $this->input_tokens[ $i ]; if ( ! $token->is_semantically_void() ) { ++$found; } if ( $found === $nth ) { return $this->input_tokens[ $i ]; } } } /** * Consume all the tokens. * * @param array $query Search query. * * @return void */ public function consume_all( $query = array() ) { while ( $this->consume( $query ) ) { // Do nothing. } } /** * Consume the next tokens and return one that matches the given criteria. * * @param array $query Search query. * [ * 'type' => null, // Optional. Token type. * 'flags' => null, // Optional. Token flags. * 'values' => null, // Optional. Token values. * ]. * * @return WP_SQLite_Token|null */ public function consume( $query = array() ) { $tokens = $this->move_forward( $query ); $this->output_tokens = array_merge( $this->output_tokens, $tokens ); return $this->token; } /** * Drop the last consumed token and return it. * * @return WP_SQLite_Token|null */ public function drop_last() { return array_pop( $this->output_tokens ); } /** * Skip over the next tokens and return one that matches the given criteria. * * @param array $query Search query. * [ * 'type' => null, // Optional. Token type. * 'flags' => null, // Optional. Token flags. * 'values' => null, // Optional. Token values. * ]. * * @return WP_SQLite_Token|null */ public function skip( $query = array() ) { $this->skip_and_return_all( $query ); return $this->token; } /** * Skip over the next tokens until one matches the given criteria, * and return all the skipped tokens. * * @param array $query Search query. * [ * 'type' => null, // Optional. Token type. * 'flags' => null, // Optional. Token flags. * 'values' => null, // Optional. Token values. * ]. * * @return WP_SQLite_Token[] */ public function skip_and_return_all( $query = array() ) { $tokens = $this->move_forward( $query ); /* * When skipping over whitespaces, make sure to consume * at least one to avoid SQL syntax errors. */ foreach ( $tokens as $token ) { if ( $token->matches( WP_SQLite_Token::TYPE_WHITESPACE ) ) { $this->add( $token ); break; } } return $tokens; } /** * Returns the next tokens that match the given criteria. * * @param array $query Search query. * [ * 'type' => string|null, // Optional. Token type. * 'flags' => int|null, // Optional. Token flags. * 'values' => string|null, // Optional. Token values. * ]. * * @return array */ private function move_forward( $query = array() ) { $type = isset( $query['type'] ) ? $query['type'] : null; $flags = isset( $query['flags'] ) ? $query['flags'] : null; $values = isset( $query['value'] ) ? ( is_array( $query['value'] ) ? $query['value'] : array( $query['value'] ) ) : null; $depth = isset( $query['depth'] ) ? $query['depth'] : null; $buffered = array(); while ( true ) { if ( ++$this->index >= $this->max ) { $this->token = null; $this->call_stack = array(); break; } $this->token = $this->input_tokens[ $this->index ]; $this->update_call_stack(); $buffered[] = $this->token; if ( ( null === $depth || $this->depth === $depth ) && $this->token->matches( $type, $flags, $values ) ) { break; } } return $buffered; } /** * Returns the last call stack element. * * @return array|null */ public function last_call_stack_element() { return count( $this->call_stack ) ? $this->call_stack[ count( $this->call_stack ) - 1 ] : null; } /** * Updates the call stack. * * @return void */ private function update_call_stack() { if ( $this->token->flags & WP_SQLite_Token::FLAG_KEYWORD_FUNCTION ) { $this->last_function_call = $this->token->value; } if ( WP_SQLite_Token::TYPE_OPERATOR === $this->token->type ) { switch ( $this->token->value ) { case '(': if ( $this->last_function_call ) { array_push( $this->call_stack, array( 'function' => $this->last_function_call, 'depth' => $this->depth, ) ); $this->last_function_call = null; } ++$this->depth; break; case ')': --$this->depth; $call_parent = $this->last_call_stack_element(); if ( $call_parent && $call_parent['depth'] === $this->depth ) { array_pop( $this->call_stack ); } break; } } } }