2018-12-31 20:04:05 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
abstract class TlDocumentationGenerator
|
|
|
|
{
|
|
|
|
private $current_line = '';
|
|
|
|
private $documentation = array();
|
|
|
|
private $line_replacement = array();
|
|
|
|
|
|
|
|
final protected function printError($error)
|
|
|
|
{
|
|
|
|
fwrite(STDERR, "$error near line \"".rtrim($this->current_line)."\"\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
final protected function addDocumentation($code, $doc) {
|
|
|
|
if (isset($this->documentation[$code])) {
|
|
|
|
$this->printError("Duplicate documentation for \"$code\"");
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->documentation[$code] = $doc;
|
2018-03-16 22:26:27 +01:00
|
|
|
// $this->printError($code);
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
final protected function addLineReplacement($line, $new_line) {
|
|
|
|
if (isset($this->line_replacement[$line])) {
|
|
|
|
$this->printError("Duplicate line replacement for \"$line\"");
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->line_replacement[$line] = $new_line;
|
|
|
|
}
|
|
|
|
|
|
|
|
final protected function addDot($str) {
|
|
|
|
if (!$str) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
$len = strlen($str);
|
|
|
|
if ($str[$len - 1] === '.') {
|
|
|
|
return $str;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($str[$len - 1] === ')') {
|
|
|
|
// trying to place dot inside the brackets
|
|
|
|
$bracket_count = 1;
|
|
|
|
for ($pos = $len - 2; $pos >= 0; $pos--) {
|
|
|
|
if ($str[$pos] === ')') {
|
|
|
|
$bracket_count++;
|
|
|
|
}
|
|
|
|
if ($str[$pos] === '(') {
|
|
|
|
$bracket_count--;
|
|
|
|
if ($bracket_count === 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($bracket_count === 0) {
|
2020-11-11 14:38:48 +01:00
|
|
|
if (ord('A') <= ord($str[$pos + 1]) && ord($str[$pos + 1]) <= ord('Z')) {
|
2018-12-31 20:04:05 +01:00
|
|
|
return substr($str, 0, -1).'.)';
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$this->printError("Unmatched bracket");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $str.'.';
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract protected function escapeDocumentation($doc);
|
|
|
|
|
2018-03-16 22:26:27 +01:00
|
|
|
abstract protected function getFieldName($name, $class_name);
|
2018-12-31 20:04:05 +01:00
|
|
|
|
|
|
|
abstract protected function getClassName($name);
|
|
|
|
|
|
|
|
abstract protected function getTypeName($type);
|
|
|
|
|
|
|
|
abstract protected function getBaseClassName($is_function);
|
|
|
|
|
|
|
|
abstract protected function needRemoveLine($line);
|
|
|
|
|
|
|
|
abstract protected function needSkipLine($line);
|
|
|
|
|
|
|
|
abstract protected function isHeaderLine($line);
|
|
|
|
|
|
|
|
abstract protected function extractClassName($line);
|
|
|
|
|
|
|
|
abstract protected function fixLine($line);
|
|
|
|
|
|
|
|
abstract protected function addGlobalDocumentation();
|
|
|
|
|
|
|
|
abstract protected function addAbstractClassDocumentation($class_name, $value);
|
|
|
|
|
2019-03-25 00:07:31 +01:00
|
|
|
abstract protected function getFunctionReturnTypeDescription($return_type, $for_constructor);
|
2019-03-24 23:18:25 +01:00
|
|
|
|
2022-03-14 14:29:17 +01:00
|
|
|
abstract protected function addClassDocumentation($class_name, $base_class_name, $return_type, $description);
|
2018-12-31 20:04:05 +01:00
|
|
|
|
|
|
|
abstract protected function addFieldDocumentation($class_name, $field_name, $type_name, $field_info, $may_be_null);
|
|
|
|
|
2019-03-25 00:07:31 +01:00
|
|
|
abstract protected function addDefaultConstructorDocumentation($class_name, $class_description);
|
2018-12-31 20:04:05 +01:00
|
|
|
|
2019-03-25 00:07:31 +01:00
|
|
|
abstract protected function addFullConstructorDocumentation($class_name, $class_description, $known_fields, $info);
|
2018-12-31 20:04:05 +01:00
|
|
|
|
|
|
|
public function generate($tl_scheme_file, $source_file)
|
|
|
|
{
|
|
|
|
$lines = array_filter(array_map('trim', file($tl_scheme_file)));
|
|
|
|
$description = '';
|
2022-12-14 14:30:37 +01:00
|
|
|
$description_line_count = 0;
|
2018-12-31 20:04:05 +01:00
|
|
|
$current_class = '';
|
|
|
|
$is_function = false;
|
|
|
|
$need_class_description = false;
|
|
|
|
|
|
|
|
$this->addGlobalDocumentation();
|
|
|
|
|
|
|
|
foreach ($lines as $line) {
|
|
|
|
$this->current_line = $line;
|
|
|
|
if ($line === '---types---') {
|
|
|
|
$is_function = false;
|
|
|
|
} elseif ($line === '---functions---') {
|
|
|
|
$is_function = true;
|
|
|
|
$current_class = '';
|
|
|
|
$need_class_description = false;
|
|
|
|
} elseif ($line[0] === '/') {
|
|
|
|
if ($line[1] !== '/') {
|
|
|
|
$this->printError('Wrong comment');
|
|
|
|
continue;
|
|
|
|
}
|
2022-12-14 14:30:37 +01:00
|
|
|
if ($line[2] === '@') {
|
|
|
|
if (substr($line, 2, 7) !== '@class ') {
|
|
|
|
$description_line_count++;
|
|
|
|
}
|
|
|
|
$description .= trim(substr($line, 2)).' ';
|
|
|
|
} elseif ($line[2] === '-') {
|
2022-12-14 15:35:31 +01:00
|
|
|
if (strpos($line, '@') !== false) {
|
|
|
|
$description_line_count += 100;
|
|
|
|
}
|
2022-12-14 14:30:37 +01:00
|
|
|
$description .= trim(substr($line, 3)).' ';
|
2018-12-31 20:04:05 +01:00
|
|
|
} else {
|
|
|
|
$this->printError('Unexpected comment');
|
|
|
|
}
|
|
|
|
} elseif (strpos($line, '? =') || strpos($line, ' = Vector t;') || $line === 'boolFalse = Bool;' ||
|
|
|
|
$line === 'boolTrue = Bool;' || $line === 'bytes = Bytes;' || $line === 'int32 = Int32;' ||
|
|
|
|
$line === 'int53 = Int53;'|| $line === 'int64 = Int64;') {
|
|
|
|
// skip built-in types
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
$description = trim($description);
|
|
|
|
if ($description[0] !== '@') {
|
|
|
|
$this->printError('Wrong description begin');
|
|
|
|
}
|
|
|
|
|
2020-10-01 18:39:58 +02:00
|
|
|
if (preg_match('/[^ ]@/', $description)) {
|
|
|
|
$this->printError("Wrong documentation '@' usage: $description");
|
|
|
|
}
|
2018-12-31 20:04:05 +01:00
|
|
|
$docs = explode('@', $description);
|
|
|
|
array_shift($docs);
|
|
|
|
$info = array();
|
|
|
|
|
|
|
|
foreach ($docs as $doc) {
|
|
|
|
list($key, $value) = explode(' ', $doc, 2);
|
|
|
|
$value = trim($value);
|
|
|
|
|
|
|
|
if ($need_class_description) {
|
|
|
|
if ($key === 'description') {
|
|
|
|
$need_class_description = false;
|
|
|
|
|
2019-02-20 02:23:02 +01:00
|
|
|
$value = $this->escapeDocumentation($this->addDot($value));
|
2018-12-31 20:04:05 +01:00
|
|
|
|
|
|
|
$this->addAbstractClassDocumentation($current_class, $value);
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
$this->printError('Expected abstract class description');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($key === 'class') {
|
|
|
|
$current_class = $this->getClassName($value);
|
|
|
|
$need_class_description = true;
|
|
|
|
|
|
|
|
if ($is_function) {
|
|
|
|
$this->printError('Unexpected class definition');
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (isset($info[$key])) {
|
|
|
|
$this->printError("Duplicate info about `$key`");
|
|
|
|
}
|
|
|
|
$info[$key] = trim($value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (substr_count($line, '=') !== 1) {
|
|
|
|
$this->printError("Wrong '=' count");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
list($fields, $type) = explode('=', $line);
|
|
|
|
$type = $this->getClassName($type);
|
|
|
|
$fields = explode(' ', trim($fields));
|
|
|
|
$class_name = $this->getClassName(array_shift($fields));
|
|
|
|
|
|
|
|
if ($type !== $current_class) {
|
|
|
|
$current_class = '';
|
|
|
|
$need_class_description = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$is_function) {
|
|
|
|
$type_lower = strtolower($type);
|
|
|
|
$class_name_lower = strtolower($class_name);
|
2018-03-16 22:26:27 +01:00
|
|
|
if (empty($current_class) === ($type_lower !== $class_name_lower)) {
|
2018-12-31 20:04:05 +01:00
|
|
|
$this->printError('Wrong constructor name');
|
|
|
|
}
|
|
|
|
if (strpos($class_name_lower, $type_lower) !== 0) {
|
|
|
|
// $this->printError('Wrong constructor name');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$known_fields = array();
|
|
|
|
foreach ($fields as $field) {
|
|
|
|
list ($field_name, $field_type) = explode(':', $field);
|
|
|
|
if (isset($info['param_'.$field_name])) {
|
|
|
|
$known_fields['param_'.$field_name] = $field_type;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (isset($info[$field_name])) {
|
|
|
|
$known_fields[$field_name] = $field_type;
|
|
|
|
continue;
|
|
|
|
}
|
2022-10-05 20:29:05 +02:00
|
|
|
$this->printError("Have no documentation for field `$field_name`");
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($info as $name => $value) {
|
|
|
|
if (!$value) {
|
2022-10-05 20:29:05 +02:00
|
|
|
$this->printError("Documentation for field $name of $class_name is empty");
|
2019-02-21 13:23:05 +01:00
|
|
|
} elseif (($value[0] < 'A' || $value[0] > 'Z') && ($value[0] < '0' || $value[0] > '9')) {
|
2022-10-05 20:29:05 +02:00
|
|
|
$this->printError("Documentation for field $name of $class_name doesn't begin with a capital letter");
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-07 19:55:03 +02:00
|
|
|
foreach ($info as &$v) {
|
|
|
|
$v = $this->escapeDocumentation($this->addDot($v));
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
|
2021-07-07 19:55:03 +02:00
|
|
|
$description = $info['description'];
|
|
|
|
unset($info['description']);
|
|
|
|
|
|
|
|
if (!$description) {
|
2018-12-31 20:04:05 +01:00
|
|
|
$this->printError("Have no description for class `$class_name`");
|
|
|
|
}
|
|
|
|
|
2021-07-07 19:55:03 +02:00
|
|
|
foreach (array_diff_key($info, $known_fields) as $field_name => $field_info) {
|
2022-10-05 20:29:05 +02:00
|
|
|
$this->printError("Have info about nonexistent field `$field_name`");
|
2021-07-07 19:55:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (array_keys($info) !== array_keys($known_fields)) {
|
|
|
|
$this->printError("Have wrong documentation for class `$class_name`");
|
2022-12-14 15:35:31 +01:00
|
|
|
} else if ($description_line_count === 1 ? count($known_fields) >= 4 : $description_line_count !== count($known_fields) + 1) {
|
2022-12-14 14:30:37 +01:00
|
|
|
$this->printError("Documentation for fields of class `$class_name` must be split to different lines");
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$base_class_name = $current_class ?: $this->getBaseClassName($is_function);
|
2021-07-07 19:55:03 +02:00
|
|
|
$class_description = $description;
|
2022-03-14 14:29:17 +01:00
|
|
|
$return_type = "";
|
2019-03-24 23:18:25 +01:00
|
|
|
if ($is_function) {
|
2022-03-14 14:29:17 +01:00
|
|
|
$return_type = $this->getTypeName($type);
|
|
|
|
$class_description .= $this->getFunctionReturnTypeDescription($return_type, false);
|
2019-03-24 23:18:25 +01:00
|
|
|
}
|
2022-03-14 14:29:17 +01:00
|
|
|
$this->addClassDocumentation($class_name, $base_class_name, $return_type, $class_description);
|
2018-12-31 20:04:05 +01:00
|
|
|
|
2019-04-10 23:59:57 +02:00
|
|
|
foreach ($known_fields as $name => $field_type) {
|
2019-04-11 00:01:48 +02:00
|
|
|
$may_be_null = stripos($info[$name], 'may be null') !== false;
|
2019-04-10 23:59:57 +02:00
|
|
|
$field_name = $this->getFieldName($name, $class_name);
|
2019-04-09 22:58:11 +02:00
|
|
|
$field_type_name = $this->getTypeName($field_type);
|
2019-04-10 23:59:57 +02:00
|
|
|
$this->addFieldDocumentation($class_name, $field_name, $field_type_name, $info[$name], $may_be_null);
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
|
2019-03-25 00:07:31 +01:00
|
|
|
if ($is_function) {
|
|
|
|
$default_constructor_prefix = 'Default constructor for a function, which ';
|
|
|
|
$full_constructor_prefix = 'Creates a function, which ';
|
2021-07-07 19:55:03 +02:00
|
|
|
$class_description = lcfirst($description);
|
2019-03-25 00:07:31 +01:00
|
|
|
$class_description .= $this->getFunctionReturnTypeDescription($this->getTypeName($type), true);
|
|
|
|
} else {
|
|
|
|
$default_constructor_prefix = '';
|
|
|
|
$full_constructor_prefix = '';
|
|
|
|
}
|
|
|
|
$this->addDefaultConstructorDocumentation($class_name, $default_constructor_prefix.$class_description);
|
2018-12-31 20:04:05 +01:00
|
|
|
|
|
|
|
if ($known_fields) {
|
2019-03-25 00:07:31 +01:00
|
|
|
$this->addFullConstructorDocumentation($class_name, $full_constructor_prefix.$class_description, $known_fields, $info);
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$description = '';
|
2022-12-14 14:30:37 +01:00
|
|
|
$description_line_count = 0;
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$lines = file($source_file);
|
|
|
|
$result = '';
|
|
|
|
$current_class = '';
|
|
|
|
$current_headers = '';
|
|
|
|
foreach ($lines as $line) {
|
|
|
|
$this->current_line = $line;
|
|
|
|
if ($this->needRemoveLine($line)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ($this->needSkipLine($line)) {
|
|
|
|
$result .= $current_headers.$line;
|
|
|
|
$current_headers = '';
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ($this->isHeaderLine($line)) {
|
|
|
|
$current_headers .= $line;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$current_class = $this->extractClassName($line) ?: $current_class;
|
|
|
|
|
|
|
|
$fixed_line = rtrim($this->fixLine($line));
|
|
|
|
|
|
|
|
$doc = '';
|
|
|
|
if (isset($this->documentation[$fixed_line])) {
|
|
|
|
$doc = $this->documentation[$fixed_line];
|
2018-03-16 22:26:27 +01:00
|
|
|
// unset($this->documentation[$fixed_line]);
|
2018-12-31 20:04:05 +01:00
|
|
|
} elseif (isset($this->documentation[$current_class.$fixed_line])) {
|
|
|
|
$doc = $this->documentation[$current_class.$fixed_line];
|
2018-03-16 22:26:27 +01:00
|
|
|
// unset($this->documentation[$current_class.$fixed_line]);
|
2018-12-31 20:04:05 +01:00
|
|
|
} else {
|
|
|
|
$this->printError('Have no docs for "'.$fixed_line.'"');
|
|
|
|
}
|
|
|
|
if ($doc) {
|
|
|
|
$result .= $doc."\n";
|
|
|
|
}
|
|
|
|
if (isset($this->line_replacement[$fixed_line])) {
|
|
|
|
$line = $this->line_replacement[$fixed_line];
|
|
|
|
} elseif (isset($this->line_replacement[$current_class.$fixed_line])) {
|
|
|
|
$line = $this->line_replacement[$current_class.$fixed_line];
|
|
|
|
}
|
|
|
|
$result .= $current_headers.$line;
|
|
|
|
$current_headers = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (file_get_contents($source_file) !== $result) {
|
|
|
|
file_put_contents($source_file, $result);
|
|
|
|
}
|
2018-03-16 22:26:27 +01:00
|
|
|
|
|
|
|
if (count($this->documentation)) {
|
|
|
|
// $this->printError('Have unused docs '.print_r(array_keys($this->documentation), true));
|
|
|
|
}
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
}
|