nur-sery/nur_src/php/SrcUpdater.php

318 lines
9.7 KiB
PHP
Raw Normal View History

2023-12-03 22:10:18 +04:00
<?php
namespace nur\php;
use nur\A;
use nur\b\io\TmpfileWriter;
use nur\b\ValueException;
use nur\base;
use nur\func;
use nur\log;
use nur\reader;
use ReflectionClass;
/**
* Class Updater: met à jour une fichier source
*/
class SrcUpdater {
protected $allowUndefined = false;
function setAllowUndefined(bool $allowUndefined): void {
$this->allowUndefined = $allowUndefined;
}
/**
* analyser le fichier $pf et trouver le namespace et la classe. retourner un
* tableau ["namespace" => $namespace, "class" => $class]
*/
function parseFile(string $pf): array {
$lines = reader::read_lines($pf);
$namespace = false;
$class_name = null;
$consts = [];
foreach ($lines as $line) {
if (preg_match('/^\s*namespace\s+(.+);/', $line, $ms)) {
$namespace = $ms[1];
} elseif (preg_match('/^\s*(?:abstract\s*)?class\s+(\w+)/', $line, $ms)) {
$class_name = $ms[1];
} elseif (preg_match('/^\s*const\s+(\w+)/', $line, $ms)) {
$consts[$ms[1]] = true;
}
}
$class = $class_name;
if ($namespace) $class = "$namespace\\$class";
return [
"namespace" => $namespace,
"class_name" => $class_name,
"class" => $class,
"defined_consts" => $consts,
"file" => $pf,
"lines" => $lines,
];
}
function _initParams(?array &$params, string $file) {
A::update_n($params, $this->parseFile($file));
if (base::z($params["class"])) throw new ValueException("class is required");
}
protected static function undefined_const_msg(string $name, string $class): string {
return "$name: is not defined in class $class";
}
protected static function undefined_const(string $name, string $class): ValueException {
return new ValueException(self::undefined_const_msg($name, $class));
}
function _updateConsts(array &$params, ?bool $allowUndefined=null) {
if ($allowUndefined === null) $allowUndefined = $this->allowUndefined;
$class = $params["class"];
$rc = new ReflectionClass($class);
$availableConsts = $rc->getConstants();
if (!A::has($availableConsts, "_AUTOGEN_CONSTS")) {
# il n'y a rien à mettre à jour
return;
}
# normaliser _AUTOGEN_CONSTS
# chaque élement séquentiel $value de $autogenConsts est remplacé par
# $value => [$class, "_AUTOGEN_$value"]
$autogenConsts = [];
$index = 0;
foreach ($availableConsts["_AUTOGEN_CONSTS"] as $key => $value) {
if ($key === $index) {
# clé séquentielle
$index++;
$autogenConsts[$value] = [$class, "_AUTOGEN_$value"];
} else {
$autogenConsts[$key] = $value;
}
}
# calculer la liste dynamique de constantes et leur valeur
if (A::has($autogenConsts, "")) {
# liste dynamique de constantes et leurs valeurs associées
$func = A::getdel($autogenConsts, "");
$args = [];
func::fix_static($func, $class);
func::fix_args($func, $args);
$dynamicConsts = A::with(func::call($func, ...$args));
foreach (array_keys($dynamicConsts) as $name) {
if (!A::has($availableConsts, $name) && !$allowUndefined) {
throw self::undefined_const($name, $class);
}
}
} else {
$dynamicConsts = [];
}
# liste de tuples [$value, $literal_value]
$literals = A::get($dynamicConsts, "_AUTOGEN_LITERALS");
if ($literals === null) $literals = A::get($availableConsts, "_AUTOGEN_LITERALS");
# lister les constantes à calculer
# une valeur mentionée dans _AUTOGEN_CONSTS prend le pas sur la même valeur
# retournée dans la liste dynamique de constantes
$definedConsts = $params["defined_consts"];
$consts = [];
foreach (array_keys($autogenConsts) as $name) {
if (A::has($availableConsts, $name)) {
$value = $availableConsts[$name];
A::del($dynamicConsts, $name);
} elseif ($allowUndefined) {
$value = null;
} else {
throw self::undefined_const($name, $class);
}
if (!A::has($definedConsts, $name)) {
log::warning(self::undefined_const_msg($name, $class));
}
$consts[$name] = $value;
}
# calculer les valeurs des constantes
foreach ($consts as $name => &$const) {
if (!A::has($autogenConsts, $name)) continue;
$func = $autogenConsts[$name];
$args = [];
func::fix_static($func, $class);
func::fix_args($func, $args);
$args[] = $const;
$const = func::call($func, ...$args);
}; unset($const);
# puis mettre à jour le fichier
$lines = [];
$rewriteConst = false;
$prefix = null;
foreach ($params["lines"] as $line) {
if (preg_match('/^(\s*)const\s+(\w+)/', $line, $ms)) {
$prefix = $ms[1];
$name = $ms[2];
if (A::has($consts, $name)) {
# cette constante doit être mise à jour
$rewriteConst = true;
} elseif (A::has($dynamicConsts, $name)) {
$consts[$name] = A::getdel($dynamicConsts, $name);
$rewriteConst = true;
}
} elseif (!$rewriteConst && $dynamicConsts &&
preg_match('/^(\s*)#+\s*--autogen-dynamic--/', $line, $ms)) {
# il faut écrire les constantes dynamiques restantes
$prefix = $ms[1];
foreach ($dynamicConsts as $name => $value) {
$generator = new SrcGenerator($prefix);
$generator->genConst($name, $value, null, $literals, false, "/*autogen*/");
$generator->mergeInto($lines);
}
$dynamicConsts = false;
$prefix = null;
}
if ($rewriteConst) {
if (preg_match('/;\s*$/', $line, $ms)) {
# écrire la constante mise à jour
$generator = new SrcGenerator($prefix);
$generator->genConst($name, $consts[$name], null, $literals, false, "/*autogen*/");
$generator->mergeInto($lines);
$rewriteConst = false;
$prefix = null;
}
} else {
$lines[] = $line;
}
}
if ($dynamicConsts) {
$count = count($dynamicConsts);
log::warning("$params[file]: missing #--autogen-dynamic-- section, $count dynamic const(s) skipped", log::NORMAL);
}
if ($rewriteConst) {
throw new ValueException("$params[file]: parse error");
}
$params["lines"] = $lines;
}
private static function call_autogens(?array $autogens, string $class) {
$dest = [];
if ($autogens !== null) {
foreach ($autogens as $func) {
$args = [];
func::fix_static($func, $class);
func::fix_args($func, $args);
$fcontext = func::_prepare($func);
func::_fill($fcontext, $args);
$minArgs = $fcontext[3];
$maxArgs = $fcontext[4];
if ($maxArgs > $minArgs) {
# ne rajouter la classe qu'en tant que dernier argument optionnel
$maxArgs--;
$minArgs = count($args); # prendre le nombre effectif
while ($minArgs < $maxArgs) {
$args[] = null;
$minArgs++;
}
$args[] = $class;
}
A::merge($dest, func::_call($fcontext, $args));
};
}
return $dest;
}
function _updatePropertiesAndMethods(array &$params) {
$class = $params["class"];
$rc = new ReflectionClass($class);
$defined_consts = $rc->getConstants();
$autogenProperties = A::get($defined_consts, "_AUTOGEN_PROPERTIES");
$autogenMethods = A::get($defined_consts, "_AUTOGEN_METHODS");
if ($autogenProperties === null && $autogenMethods === null) {
# il n'y a rien à mettre à jour
return;
}
$properties = self::call_autogens($autogenProperties, $class);
$methods = self::call_autogens($autogenMethods, $class);
$lines = [];
$done = false;
$in_comment = false;
$found_section = false;
foreach ($params["lines"] as $line) {
if ($done) {
$lines[] = $line;
continue;
}
if (!$in_comment) {
if (preg_match('/^\s*\/\*\*/', $line)) {
$in_comment = true;
}
$lines[] = $line;
continue;
}
# ici, $in_comment == true
if (preg_match('/^(\s*)\*\//', $line, $ms)) {
$prefix = "$ms[1]* ";
$lines[] = "$prefix--autogen-properties-and-methods--";
foreach ($properties as $property) {
$lines[] = "$prefix@property $property";
}
foreach ($methods as $method) {
$lines[] = "$prefix@method $method";
}
$lines[] = $line;
$in_comment = false;
$done = true;
continue;
}
if (!$found_section) {
if (preg_match('/^\s*\*?\s*--autogen-(?:properties-and-)?methods--\s*$/', $line)) {
$found_section = true;
} else {
$lines[] = $line;
continue;
}
}
# ici, $found_section == true
}
if ($in_comment) {
throw new ValueException("$params[file]: parse error: check class comment");
}
$params["lines"] = $lines;
}
function _writeFile(array $params) {
$file = $params["file"];
$outf = new TmpfileWriter(dirname($file));
$outf->writeLines($params["lines"]);
$outf->rename($file);
}
function updateConsts(string $file, ?bool $allowUndefined=null, ?array $params=null) {
$this->_initParams($params, $file);
require($file);
$this->_updateConsts($params, $allowUndefined);
$this->_writeFile($params);
}
function updatePropertiesAndMethods(string $file, ?array $params=null) {
$this->_initParams($params, $file);
require($file);
$this->_updatePropertiesAndMethods($params);
$this->_writeFile($params);
}
function update(string $file, ?array $params=null) {
$this->_initParams($params, $file);
require($file);
$this->_updateConsts($params);
$this->_updatePropertiesAndMethods($params);
$this->_writeFile($params);
}
}