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