diff options
Diffstat (limited to 'protocols/Telegram/tdlib/td/td/generate/TlDocumentationGenerator.php')
-rw-r--r-- | protocols/Telegram/tdlib/td/td/generate/TlDocumentationGenerator.php | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/protocols/Telegram/tdlib/td/td/generate/TlDocumentationGenerator.php b/protocols/Telegram/tdlib/td/td/generate/TlDocumentationGenerator.php new file mode 100644 index 0000000000..11a281ce38 --- /dev/null +++ b/protocols/Telegram/tdlib/td/td/generate/TlDocumentationGenerator.php @@ -0,0 +1,306 @@ +<?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; + // $this->printError($code); + } + + 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) { + if (ctype_upper($str[$pos + 1])) { + return substr($str, 0, -1).'.)'; + } + } else { + $this->printError("Unmatched bracket"); + } + } + return $str.'.'; + } + + abstract protected function escapeDocumentation($doc); + + abstract protected function getFieldName($name, $class_name); + + 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); + + abstract protected function addClassDocumentation($class_name, $base_class_name, $description, $return_type); + + abstract protected function addFieldDocumentation($class_name, $field_name, $type_name, $field_info, $may_be_null); + + abstract protected function addDefaultConstructorDocumentation($class_name); + + abstract protected function addFullConstructorDocumentation($class_name, $known_fields, $info); + + public function generate($tl_scheme_file, $source_file) + { + $lines = array_filter(array_map('trim', file($tl_scheme_file))); + $description = ''; + $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; + } + if ($line[2] === '@' || $line[2] === '-') { + $description .= trim(substr($line, 2 + intval($line[2] === '-'))).' '; + } 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'); + } + + $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; + + $value = $this->addDot($value); + + $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); + if (empty($current_class) === ($type_lower !== $class_name_lower)) { + $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; + } + $this->printError("Have no info about field `$field_name`"); + } + + foreach ($info as $name => $value) { + if (!$value) { + $this->printError("info[$name] for $class_name is empty"); + } elseif ($value[0] < 'A' || $value[0] > 'Z') { + $this->printError("info[$name] for $class_name doesn't begins with capital letter"); + } + } + + foreach (array_diff_key($info, $known_fields) as $field_name => $field_info) { + if ($field_name !== 'description') { + $this->printError("Have info about unexisted field `$field_name`"); + } + } + + if (!$info['description']) { + $this->printError("Have no description for class `$class_name`"); + } + + foreach ($info as &$v) { + $v = $this->escapeDocumentation($this->addDot($v)); + } + + $base_class_name = $current_class ?: $this->getBaseClassName($is_function); + $this->addClassDocumentation($class_name, $base_class_name, $info['description'], $is_function ? $this->getTypeName($type) : ''); + + foreach ($known_fields as $name => $type) { + $may_be_null = stripos($info[$name], 'may be null') !== false; + $this->addFieldDocumentation($class_name, $this->getFieldName($name, $class_name), $this->getTypeName($type), $info[$name], $may_be_null); + } + + $this->addDefaultConstructorDocumentation($class_name); + + if ($known_fields) { + $this->addFullConstructorDocumentation($class_name, $known_fields, $info); + } + + $description = ''; + } + } + + $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]; + // unset($this->documentation[$fixed_line]); + } elseif (isset($this->documentation[$current_class.$fixed_line])) { + $doc = $this->documentation[$current_class.$fixed_line]; + // unset($this->documentation[$current_class.$fixed_line]); + } 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); + } + + if (count($this->documentation)) { + // $this->printError('Have unused docs '.print_r(array_keys($this->documentation), true)); + } + } +} |