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); } }