Viewing file: ContextGenerator.php (10.16 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?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); } } }
|