mirror of
				https://github.com/ton-blockchain/ton
				synced 2025-03-09 15:40:10 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			323 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			323 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?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 getFunctionReturnTypeDescription($return_type, $for_constructor);
 | |
| 
 | |
|     abstract protected function addClassDocumentation($class_name, $base_class_name, $description);
 | |
| 
 | |
|     abstract protected function addFieldDocumentation($class_name, $field_name, $type_name, $field_info, $may_be_null);
 | |
| 
 | |
|     abstract protected function addDefaultConstructorDocumentation($class_name, $class_description);
 | |
| 
 | |
|     abstract protected function addFullConstructorDocumentation($class_name, $class_description, $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->escapeDocumentation($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') && ($value[0] < '0' || $value[0] > '9')) {
 | |
|                         $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);
 | |
|                 $class_description = $info['description'];
 | |
|                 if ($is_function) {
 | |
|                     $class_description .= $this->getFunctionReturnTypeDescription($this->getTypeName($type), false);
 | |
|                 }
 | |
|                 $this->addClassDocumentation($class_name, $base_class_name, $class_description);
 | |
| 
 | |
|                 foreach ($known_fields as $name => $field_type) {
 | |
|                     $may_be_null = stripos($info[$name], 'may be null') !== false;
 | |
|                     $field_name = $this->getFieldName($name, $class_name);
 | |
|                     $field_type_name = $this->getTypeName($field_type);
 | |
|                     $this->addFieldDocumentation($class_name, $field_name, $field_type_name, $info[$name], $may_be_null);
 | |
|                 }
 | |
| 
 | |
|                 if ($is_function) {
 | |
|                     $default_constructor_prefix = 'Default constructor for a function, which ';
 | |
|                     $full_constructor_prefix = 'Creates a function, which ';
 | |
|                     $class_description = lcfirst($info['description']);
 | |
|                     $class_description .= $this->getFunctionReturnTypeDescription($this->getTypeName($type), true);
 | |
|                 } else {
 | |
|                     $default_constructor_prefix = '';
 | |
|                     $full_constructor_prefix = '';
 | |
|                 }
 | |
|                 $this->addDefaultConstructorDocumentation($class_name, $default_constructor_prefix.$class_description);
 | |
| 
 | |
|                 if ($known_fields) {
 | |
|                     $this->addFullConstructorDocumentation($class_name, $full_constructor_prefix.$class_description, $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));
 | |
|         }
 | |
|     }
 | |
| }
 |