Initial commit
This commit is contained in:
396
pma/vendor/phpmyadmin/sql-parser/src/Tools/ContextGenerator.php
vendored
Normal file
396
pma/vendor/phpmyadmin/sql-parser/src/Tools/ContextGenerator.php
vendored
Normal file
@ -0,0 +1,396 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\SqlParser\Tools;
|
||||
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function array_slice;
|
||||
use function basename;
|
||||
use function count;
|
||||
use function dirname;
|
||||
use function file;
|
||||
use function file_put_contents;
|
||||
use function implode;
|
||||
use function ksort;
|
||||
use function preg_match;
|
||||
use function round;
|
||||
use function scandir;
|
||||
use function sort;
|
||||
use function sprintf;
|
||||
use function str_repeat;
|
||||
use function str_replace;
|
||||
use function str_split;
|
||||
use function strlen;
|
||||
use function strstr;
|
||||
use function strtoupper;
|
||||
use function substr;
|
||||
use function trim;
|
||||
use const FILE_IGNORE_NEW_LINES;
|
||||
use const FILE_SKIP_EMPTY_LINES;
|
||||
use const SORT_STRING;
|
||||
|
||||
/**
|
||||
* Used for context generation.
|
||||
*/
|
||||
class ContextGenerator
|
||||
{
|
||||
/**
|
||||
* Labels and flags that may be used when defining keywords.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $LABELS_FLAGS = [
|
||||
'(R)' => 2, // reserved
|
||||
'(D)' => 8, // data type
|
||||
'(K)' => 16, // keyword
|
||||
'(F)' => 32, // function name
|
||||
];
|
||||
|
||||
/**
|
||||
* Documentation links for each context.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $LINKS = [
|
||||
'MySql50000' => 'https://dev.mysql.com/doc/refman/5.0/en/keywords.html',
|
||||
'MySql50100' => 'https://dev.mysql.com/doc/refman/5.1/en/keywords.html',
|
||||
'MySql50500' => 'https://dev.mysql.com/doc/refman/5.5/en/keywords.html',
|
||||
'MySql50600' => 'https://dev.mysql.com/doc/refman/5.6/en/keywords.html',
|
||||
'MySql50700' => 'https://dev.mysql.com/doc/refman/5.7/en/keywords.html',
|
||||
'MySql80000' => 'https://dev.mysql.com/doc/refman/8.0/en/keywords.html',
|
||||
'MariaDb100000' => 'https://mariadb.com/kb/en/the-mariadb-library/reserved-words/',
|
||||
'MariaDb100100' => 'https://mariadb.com/kb/en/the-mariadb-library/reserved-words/',
|
||||
'MariaDb100200' => 'https://mariadb.com/kb/en/the-mariadb-library/reserved-words/',
|
||||
'MariaDb100300' => 'https://mariadb.com/kb/en/the-mariadb-library/reserved-words/',
|
||||
];
|
||||
|
||||
/**
|
||||
* The template of a context.
|
||||
*
|
||||
* Parameters:
|
||||
* 1 - name
|
||||
* 2 - class
|
||||
* 3 - link
|
||||
* 4 - keywords array
|
||||
*/
|
||||
public const TEMPLATE = <<<'PHP'
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\SqlParser\Contexts;
|
||||
|
||||
use PhpMyAdmin\SqlParser\Context;
|
||||
use PhpMyAdmin\SqlParser\Token;
|
||||
|
||||
/**
|
||||
* Context for %1$s.
|
||||
*
|
||||
* This class was auto-generated from tools/contexts/*.txt.
|
||||
* Use tools/run_generators.sh for update.
|
||||
*
|
||||
* @see %3$s
|
||||
*/
|
||||
class %2$s extends Context
|
||||
{
|
||||
/**
|
||||
* List of keywords.
|
||||
*
|
||||
* The value associated to each keyword represents its flags.
|
||||
*
|
||||
* @see Token::FLAG_KEYWORD_RESERVED Token::FLAG_KEYWORD_COMPOSED
|
||||
* Token::FLAG_KEYWORD_DATA_TYPE Token::FLAG_KEYWORD_KEY
|
||||
* Token::FLAG_KEYWORD_FUNCTION
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $KEYWORDS = [
|
||||
%4$s ];
|
||||
}
|
||||
|
||||
PHP;
|
||||
|
||||
/**
|
||||
* Sorts an array of words.
|
||||
*
|
||||
* @param array $arr
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function sortWords(array &$arr)
|
||||
{
|
||||
ksort($arr);
|
||||
foreach ($arr as &$wordsByLen) {
|
||||
ksort($wordsByLen);
|
||||
foreach ($wordsByLen as &$words) {
|
||||
sort($words, SORT_STRING);
|
||||
}
|
||||
}
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a list of words and sorts it by type, length and keyword.
|
||||
*
|
||||
* @param string[] $files
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function readWords(array $files)
|
||||
{
|
||||
$words = [];
|
||||
foreach ($files as $file) {
|
||||
$words = array_merge($words, file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES));
|
||||
}
|
||||
|
||||
$types = [];
|
||||
|
||||
for ($i = 0, $count = count($words); $i !== $count; ++$i) {
|
||||
$type = 1;
|
||||
$value = trim($words[$i]);
|
||||
|
||||
// Reserved, data types, keys, functions, etc. keywords.
|
||||
foreach (static::$LABELS_FLAGS as $label => $flags) {
|
||||
if (strstr($value, $label) !== false) {
|
||||
$type |= $flags;
|
||||
$value = trim(str_replace($label, '', $value));
|
||||
}
|
||||
}
|
||||
|
||||
// Composed keyword.
|
||||
if (strstr($value, ' ') !== false) {
|
||||
$type |= 2; // Reserved keyword.
|
||||
$type |= 4; // Composed keyword.
|
||||
}
|
||||
|
||||
$len = strlen($words[$i]);
|
||||
if ($len === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = strtoupper($value);
|
||||
if (! isset($types[$value])) {
|
||||
$types[$value] = $type;
|
||||
} else {
|
||||
$types[$value] |= $type;
|
||||
}
|
||||
}
|
||||
|
||||
$ret = [];
|
||||
foreach ($types as $word => $type) {
|
||||
$len = strlen($word);
|
||||
if (! isset($ret[$type])) {
|
||||
$ret[$type] = [];
|
||||
}
|
||||
|
||||
if (! isset($ret[$type][$len])) {
|
||||
$ret[$type][$len] = [];
|
||||
}
|
||||
|
||||
$ret[$type][$len][] = $word;
|
||||
}
|
||||
|
||||
return static::sortWords($ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints an array of a words in PHP format.
|
||||
*
|
||||
* @param array $words the list of words to be formatted
|
||||
* @param int $spaces the number of spaces that starts every line
|
||||
* @param int $line the length of a line
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function printWords($words, $spaces = 8, $line = 140)
|
||||
{
|
||||
$typesCount = count($words);
|
||||
$ret = '';
|
||||
$j = 0;
|
||||
|
||||
foreach ($words as $type => $wordsByType) {
|
||||
foreach ($wordsByType as $len => $wordsByLen) {
|
||||
$count = round(($line - $spaces) / ($len + 9)); // strlen("'' => 1, ") = 9
|
||||
$i = 0;
|
||||
|
||||
foreach ($wordsByLen as $word) {
|
||||
if ($i === 0) {
|
||||
$ret .= str_repeat(' ', $spaces);
|
||||
}
|
||||
|
||||
$ret .= sprintf('\'%s\' => %s, ', $word, $type);
|
||||
if (++$i === $count || ++$i > $count) {
|
||||
$ret .= "\n";
|
||||
$i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($i !== 0) {
|
||||
$ret .= "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (++$j < $typesCount) {
|
||||
$ret .= "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Trim trailing spaces and return.
|
||||
return str_replace(" \n", "\n", $ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a context's class.
|
||||
*
|
||||
* @param array $options the options that are used in generating this context
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function generate($options)
|
||||
{
|
||||
if (isset($options['keywords'])) {
|
||||
$options['keywords'] = static::printWords($options['keywords']);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
self::TEMPLATE,
|
||||
$options['name'],
|
||||
$options['class'],
|
||||
$options['link'],
|
||||
$options['keywords']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats context name.
|
||||
*
|
||||
* @param string $name name to format
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function formatName($name)
|
||||
{
|
||||
/* Split name and version */
|
||||
$parts = [];
|
||||
if (preg_match('/([^[0-9]*)([0-9]*)/', $name, $parts) === false) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
/* Format name */
|
||||
$base = $parts[1];
|
||||
switch ($base) {
|
||||
case 'MySql':
|
||||
$base = 'MySQL';
|
||||
break;
|
||||
case 'MariaDb':
|
||||
$base = 'MariaDB';
|
||||
break;
|
||||
}
|
||||
|
||||
/* Parse version to array */
|
||||
$ver_str = $parts[2];
|
||||
if (strlen($ver_str) % 2 === 1) {
|
||||
$ver_str = '0' . $ver_str;
|
||||
}
|
||||
|
||||
$version = array_map('intval', str_split($ver_str, 2));
|
||||
/* Remove trailing zero */
|
||||
if ($version[count($version) - 1] === 0) {
|
||||
$version = array_slice($version, 0, count($version) - 1);
|
||||
}
|
||||
|
||||
/* Create name */
|
||||
return $base . ' ' . implode('.', $version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a test.
|
||||
*
|
||||
* Reads the input file, generates the data and writes it back.
|
||||
*
|
||||
* @param string $input the input file
|
||||
* @param string $output the output directory
|
||||
*/
|
||||
public static function build($input, $output)
|
||||
{
|
||||
/**
|
||||
* The directory that contains the input file.
|
||||
*
|
||||
* Used to include common files.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
$directory = dirname($input) . '/';
|
||||
|
||||
/**
|
||||
* The name of the file that contains the context.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
$file = basename($input);
|
||||
|
||||
/**
|
||||
* The name of the context.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
$name = substr($file, 0, -4);
|
||||
|
||||
/**
|
||||
* The name of the class that defines this context.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
$class = 'Context' . $name;
|
||||
|
||||
/**
|
||||
* The formatted name of this context.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
$formattedName = static::formatName($name);
|
||||
|
||||
file_put_contents(
|
||||
$output . '/' . $class . '.php',
|
||||
static::generate(
|
||||
[
|
||||
'name' => $formattedName,
|
||||
'class' => $class,
|
||||
'link' => static::$LINKS[$name],
|
||||
'keywords' => static::readWords(
|
||||
[
|
||||
$directory . '_common.txt',
|
||||
$directory . '_functions' . $file,
|
||||
$directory . $file,
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates recursively all tests preserving the directory structure.
|
||||
*
|
||||
* @param string $input the input directory
|
||||
* @param string $output the output directory
|
||||
*/
|
||||
public static function buildAll($input, $output)
|
||||
{
|
||||
$files = scandir($input);
|
||||
|
||||
foreach ($files as $file) {
|
||||
// Skipping current and parent directories.
|
||||
if (($file[0] === '.') || ($file[0] === '_')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Building the context.
|
||||
sprintf("Building context for %s...\n", $file);
|
||||
static::build($input . '/' . $file, $output);
|
||||
}
|
||||
}
|
||||
}
|
219
pma/vendor/phpmyadmin/sql-parser/src/Tools/TestGenerator.php
vendored
Normal file
219
pma/vendor/phpmyadmin/sql-parser/src/Tools/TestGenerator.php
vendored
Normal file
@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\SqlParser\Tools;
|
||||
|
||||
use Exception;
|
||||
use PhpMyAdmin\SqlParser\Context;
|
||||
use PhpMyAdmin\SqlParser\Lexer;
|
||||
use PhpMyAdmin\SqlParser\Parser;
|
||||
use function file_exists;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function in_array;
|
||||
use function is_dir;
|
||||
use function mkdir;
|
||||
use function print_r;
|
||||
use function scandir;
|
||||
use function serialize;
|
||||
use function sprintf;
|
||||
use function strpos;
|
||||
use function substr;
|
||||
|
||||
/**
|
||||
* Used for test generation.
|
||||
*/
|
||||
class TestGenerator
|
||||
{
|
||||
/**
|
||||
* Generates a test's data.
|
||||
*
|
||||
* @param string $query the query to be analyzed
|
||||
* @param string $type test's type (may be `lexer` or `parser`)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function generate($query, $type = 'parser')
|
||||
{
|
||||
/**
|
||||
* Lexer used for tokenizing the query.
|
||||
*
|
||||
* @var Lexer
|
||||
*/
|
||||
$lexer = new Lexer($query);
|
||||
|
||||
/**
|
||||
* Parsed used for analyzing the query.
|
||||
* A new instance of parser is generated only if the test requires.
|
||||
*
|
||||
* @var Parser
|
||||
*/
|
||||
$parser = $type === 'parser' ? new Parser($lexer->list) : null;
|
||||
|
||||
/**
|
||||
* Lexer's errors.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
$lexerErrors = [];
|
||||
|
||||
/**
|
||||
* Parser's errors.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
$parserErrors = [];
|
||||
|
||||
// Both the lexer and the parser construct exception for errors.
|
||||
// Usually, exceptions contain a full stack trace and other details that
|
||||
// are not required.
|
||||
// The code below extracts only the relevant information.
|
||||
|
||||
// Extracting lexer's errors.
|
||||
if (! empty($lexer->errors)) {
|
||||
foreach ($lexer->errors as $err) {
|
||||
$lexerErrors[] = [
|
||||
$err->getMessage(),
|
||||
$err->ch,
|
||||
$err->pos,
|
||||
$err->getCode(),
|
||||
];
|
||||
}
|
||||
|
||||
$lexer->errors = [];
|
||||
}
|
||||
|
||||
// Extracting parser's errors.
|
||||
if (! empty($parser->errors)) {
|
||||
foreach ($parser->errors as $err) {
|
||||
$parserErrors[] = [
|
||||
$err->getMessage(),
|
||||
$err->token,
|
||||
$err->getCode(),
|
||||
];
|
||||
}
|
||||
|
||||
$parser->errors = [];
|
||||
}
|
||||
|
||||
return [
|
||||
'query' => $query,
|
||||
'lexer' => $lexer,
|
||||
'parser' => $parser,
|
||||
'errors' => [
|
||||
'lexer' => $lexerErrors,
|
||||
'parser' => $parserErrors,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a test.
|
||||
*
|
||||
* Reads the input file, generates the data and writes it back.
|
||||
*
|
||||
* @param string $type the type of this test
|
||||
* @param string $input the input file
|
||||
* @param string $output the output file
|
||||
* @param string $debug the debug file
|
||||
* @param bool $ansi activate quotes ANSI mode
|
||||
*/
|
||||
public static function build($type, $input, $output, $debug = null, $ansi = false)
|
||||
{
|
||||
// Support query types: `lexer` / `parser`.
|
||||
if (! in_array($type, ['lexer', 'parser'])) {
|
||||
throw new Exception('Unknown test type (expected `lexer` or `parser`).');
|
||||
}
|
||||
|
||||
/**
|
||||
* The query that is used to generate the test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
$query = file_get_contents($input);
|
||||
|
||||
// There is no point in generating a test without a query.
|
||||
if (empty($query)) {
|
||||
throw new Exception('No input query specified.');
|
||||
}
|
||||
|
||||
if ($ansi === true) {
|
||||
// set ANSI_QUOTES for ansi tests
|
||||
Context::setMode('ANSI_QUOTES');
|
||||
}
|
||||
|
||||
$test = static::generate($query, $type);
|
||||
|
||||
// unset mode, reset to default every time, to be sure
|
||||
Context::setMode();
|
||||
|
||||
// Writing test's data.
|
||||
file_put_contents($output, serialize($test));
|
||||
|
||||
// Dumping test's data in human readable format too (if required).
|
||||
if (! empty($debug)) {
|
||||
file_put_contents($debug, print_r($test, true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates recursively all tests preserving the directory structure.
|
||||
*
|
||||
* @param string $input the input directory
|
||||
* @param string $output the output directory
|
||||
* @param mixed|null $debug
|
||||
*/
|
||||
public static function buildAll($input, $output, $debug = null)
|
||||
{
|
||||
$files = scandir($input);
|
||||
|
||||
foreach ($files as $file) {
|
||||
// Skipping current and parent directories.
|
||||
if (($file === '.') || ($file === '..')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Appending the filename to directories.
|
||||
$inputFile = $input . '/' . $file;
|
||||
$outputFile = $output . '/' . $file;
|
||||
$debugFile = $debug !== null ? $debug . '/' . $file : null;
|
||||
|
||||
if (is_dir($inputFile)) {
|
||||
// Creating required directories to maintain the structure.
|
||||
// Ignoring errors if the folder structure exists already.
|
||||
if (! is_dir($outputFile)) {
|
||||
mkdir($outputFile);
|
||||
}
|
||||
|
||||
if (($debug !== null) && (! is_dir($debugFile))) {
|
||||
mkdir($debugFile);
|
||||
}
|
||||
|
||||
// Generating tests recursively.
|
||||
static::buildAll($inputFile, $outputFile, $debugFile);
|
||||
} elseif (substr($inputFile, -3) === '.in') {
|
||||
// Generating file names by replacing `.in` with `.out` and
|
||||
// `.debug`.
|
||||
$outputFile = substr($outputFile, 0, -3) . '.out';
|
||||
if ($debug !== null) {
|
||||
$debugFile = substr($debugFile, 0, -3) . '.debug';
|
||||
}
|
||||
|
||||
// Building the test.
|
||||
if (! file_exists($outputFile)) {
|
||||
sprintf("Building test for %s...\n", $inputFile);
|
||||
static::build(
|
||||
strpos($inputFile, 'lex') !== false ? 'lexer' : 'parser',
|
||||
$inputFile,
|
||||
$outputFile,
|
||||
$debugFile,
|
||||
strpos($inputFile, 'ansi') !== false
|
||||
);
|
||||
} else {
|
||||
sprintf("Test for %s already built!\n", $inputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user