commit e10147eed30ef22171cfdb68d324236e9d036a73 Author: Pavel Shevaev Date: Mon May 16 14:20:20 2022 +0300 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/metagen.inc.php b/metagen.inc.php new file mode 100644 index 0000000..20633ff --- /dev/null +++ b/metagen.inc.php @@ -0,0 +1,2002 @@ + +* --long-param +* --long-param= +* --long-param +* +*/ +function mtg_parse_argv($params, $noopt = array()) +{ + $result = array(); + reset($params); + while (list($tmp, $p) = each($params)) + { + if($p[0] == '-') + { + $pname = substr($p, 1); + $value = true; + if($pname[0] == '-') + { + // long-opt (--) + $pname = substr($pname, 1); + if(strpos($p, '=') !== false) + { + // value specified inline (--=) + list($pname, $value) = explode('=', substr($p, 2), 2); + } + } + // check if next parameter is a descriptor or a value + $nextparm = current($params); + if(!in_array($pname, $noopt) && $value === true && $nextparm !== false && $nextparm[0] != '-') + list($tmp, $value) = each($params); + $result[$pname] = $value; + } + else + // param doesn't belong to any option + $result[] = $p; + } + return $result; +} + +function mtg_read_from_stdin($timeout_sec = 5) +{ + $read = array(STDIN); + $write = NULL; + $except = NULL; + $stdin = ''; + if(false === ($num_changed_streams = stream_select($read, $write, $except, $timeout_sec))) + throw new Exception("Unknown stream select error happened"); + elseif ($num_changed_streams > 0) + $stdin = stream_get_contents(STDIN); + else + throw new Exception("No data in stdin"); + return $stdin; +} + +function mtg_extract_files($string) +{ + return mtg_split_and_process($string, '~\s+~', 'realpath'); +} + +function mtg_split_and_process($string, $regex = '~\s+~', $proc = '') +{ + $items = preg_split($regex, trim($string)); + $result = array(); + foreach($items as $item) + { + $item = trim($item); + if(!$item) + continue; + + if($proc) + $item = $proc($item); + + $result[] = $item; + } + return $result; +} + +function mtg_argv2conf($argv) +{ + global $METAGEN_CONFIG; + + $parsed = mtg_parse_argv($argv); + foreach($parsed as $key => $value) + { + if(!is_string($key)) + continue; + + $METAGEN_CONFIG[preg_replace('~^-+~', '', $key)] = $value; + } +} + +function mtg_conf($key) +{ + global $METAGEN_CONFIG; + + if(!isset($METAGEN_CONFIG[$key])) + { + if(func_num_args() == 1) + throw new Exception("Config option '$key' is not set"); + else //default value + return func_get_arg(1); + } + + return $METAGEN_CONFIG[$key]; +} + +function mtg_conf_set($key, $val) +{ + global $METAGEN_CONFIG; + $METAGEN_CONFIG[$key] = $val; +} + +interface mtgType +{} + +class mtgTypeRef implements mtgType +{ + private static $all = array(); + + private $name; + private $file; + private $line; + private $meta; + private $resolved; + + function __construct($name_or_resolved, mtgMetaInfo $meta = null, $file = '', $line= 0) + { + if(is_object($name_or_resolved)) + { + if(!($name_or_resolved instanceof mtgType)) + throw new Exception("Bad type"); + $this->resolved = $name_or_resolved; + } + else + $this->name = $name_or_resolved; + + $this->meta = $meta; + $this->file = $file; + $this->line = $line; + + self::$all[] = $this; + } + + static function checkAllResolved() + { + foreach(self::$all as $ref) + $ref->resolve(); + } + + function resolve() + { + if($this->resolved) + return $this->resolved; + + $u = $this->meta->findUnit($this->name, false/*non strict*/); + if($u) + { + $this->resolved = $u->object; + return $this->resolved; + } + else + { + throw new Exception("{$this->file}@{$this->line} : Symbol '{$this->name}' not found"); + } + } +} + +class mtgMetaInfoUnit +{ + public $file; + public $object; + + function __construct($f, mtgMetaUnit $o) + { + $this->file = $f; + $this->object = $o; + } +} + +class mtgMetaInfo +{ + public static $BUILTIN_TYPES = array('int8', 'int16', 'int32', 'uint8', 'uint16', 'uint32', 'float', 'double', 'uint64', 'int64', 'bool', 'string', 'blob'); + + private $units = array(); + + function addUnit(mtgMetaInfoUnit $unit) + { + if(isset($this->units[$unit->object->getId()])) + throw new Exception("Meta info unit '{$unit->object->getId()}' already defined in file '{$this->units[$unit->object->getId()]->file}'"); + + $this->units[$unit->object->getId()] = $unit; + } + + function getUnits() + { + return $this->units; + } + + function findUnit($id, $strict = true) + { + if(isset($this->units[$id])) + return $this->units[$id]; + else if($strict) + throw new Exception("Unit '$id' not found"); + } +} + +abstract class mtgMetaUnit +{ + abstract function getId(); + + protected $tokens = array(); + + function getTokens() + { + return $this->tokens; + } + + function setTokens(array $tokens) + { + $this->tokens = $tokens; + } + + function setToken($name, $val) + { + $this->tokens[$name] = $val; + } + + function getToken($name) + { + return $this->hasToken($name) ? $this->tokens[$name] : null; + } + + function hasToken($name) + { + return array_key_exists($name, $this->tokens); + } +} + +class mtgUserType extends mtgMetaUnit implements mtgType +{ + protected $name; + + function __construct($name) + { + $this->name = $name; + } + + function getName() + { + return $this->name; + } + + function setName($name) + { + $this->name = $name; + } + + function getId() + { + return $this->name; + } + + function getClassId() + { + //NOTE: using crc28 actually, leaving some extra reserved space + return crc32($this->name) & 0xFFFFFFF; + } + + function __toString() + { + return $this->name; + } +} + +class mtgMetaStruct extends mtgUserType +{ + protected $fields = array(); + protected $funcs = array(); + protected $parent = null; + + function __construct($name, $fields = array(), mtgTypeRef $parent = null, $tokens = array()) + { + parent::__construct($name); + + $this->setFields($fields); + $this->parent = $parent; + $this->tokens = $tokens; + } + + function getParent() + { + return $this->parent ? $this->parent->resolve() : null; + } + + function getFields() + { + return $this->fields; + } + + function setFields(array $fields) + { + foreach($fields as $field) + $this->addField($field); + } + + function addField(mtgMetaField $field) + { + if($this->hasField($field->getName())) + throw new Exception("Struct '{$this->name}' already has field '{$field->getName()}'"); + + $this->fields[$field->getName()] = $field; + } + + //TODO: doesn't really belong here + function delField(mtgMetaField $field) + { + if(isset($this->fields[$field->getName()])) + unset($this->fields[$field->getName()]); + } + + function hasField($name) + { + return isset($this->fields[$name]); + } + + function getField($name) + { + if(!isset($this->fields[$name])) + throw new Exception("No such field '$name'"); + return $this->fields[$name]; + } + + //TODO: doesn't really belong here + function findFieldOwner($name, mtgMetaInfo $meta) + { + $tmp = $this; + while($tmp) + { + $fields = $tmp->getFields(); + if(isset($fields[$name])) + return $tmp; + $tmp = $tmp->getParent(); + } + return null; + } + + function getFuncs() + { + return $this->funcs; + } + + function addFunc(mtgMetaFunc $fn) + { + if($this->hasFunc($fn->getName())) + throw new Exception("Struct '{$this->name}' already has func '{$fn->getName()}'"); + + $this->funcs[$fn->getName()] = $fn; + } + + function hasFunc($name) + { + return isset($this->funcs[$name]); + } + + function getFunc($name) + { + if(!isset($this->funcs[$name])) + throw new Exception("No such funcs '$name'"); + return $this->funcs[$name]; + } + + function hasTokenInParent($name) + { + $parent = $this; + while($parent) + { + if($parent->hasToken($name)) + return true; + $parent = $parent->getParent(); + } + + return false; + } +} + +class mtgMetaFunc extends mtgMetaUnit implements mtgType +{ + private $name; + private $args = array(); + private $ret_type; + + function __construct($name) + { + $this->name = $name; + } + + function __toString() + { + $str = "func "; + + $str .= $this->name.'('; + foreach($this->getArgs() as $arg) + $str .= $arg->getType() . ','; + $str = rtrim($str, ','); + $str .= ')'; + if($ret_type = $this->getReturnType()) + $str .= ':'.$ret_type; + return $str; + } + + function setReturnType(mtgTypeRef $type) + { + $this->ret_type = $type; + } + + function getReturnType() + { + return $this->ret_type ? $this->ret_type->resolve() : null; + } + + function getName() + { + return $this->name; + } + + function getId() + { + return $this->name; + } + + function getArgs() + { + return $this->args; + } + + function setArgs(array $args) + { + foreach($args as $arg) + $this->addArg($arg); + } + + function addArg(mtgMetaField $arg) + { + if($this->hasArg($arg->getName())) + throw new Exception("Func '{$this->name}' already has arg '{$arg->getName()}'"); + + $this->args[$arg->getName()] = $arg; + } + + function hasArg($name) + { + return isset($this->args[$name]); + } + + function getArg($name) + { + if(!isset($this->args[$name])) + throw new Exception("No such arg '$name'"); + return $this->args[$name]; + } +} + +class mtgMetaRPC extends mtgMetaUnit +{ + private $name; + private $code; + private $in; + private $out; + + function __construct($name, $code, mtgMetaPacket $in, mtgMetaPacket $out, array $tokens = array()) + { + $this->name = $name; + $this->code = $code; + $this->in = $in; + $this->out = $out; + $this->tokens = $tokens; + } + + function getId() + { + return $this->code; + } + + function getName() + { + return $this->name; + } + + function getCode() + { + return $this->code; + } + + function getNumericCode() + { + return (int)$this->code; + } + + function getReq() + { + return $this->in; + } + + function getRsp() + { + return $this->out; + } +} + +class mtgBuiltinType implements mtgType +{ + private $name; + + function __construct($name) + { + if(!in_array($name, mtgMetaInfo::$BUILTIN_TYPES)) + throw new Exception("Not a built-in type '$name'"); + + $this->name = $name; + } + + function getName() + { + return $this->name; + } + + function __toString() + { + return $this->name; + } + + function isNumeric() + { + return !$this->isString() && !$this->isBool() && !$this->isBlob(); + } + + function isString() + { + return $this->name === 'string'; + } + + function isInt() + { + return strpos($this->name, 'int') === 0; + } + + function isUint() + { + return strpos($this->name, 'uint') === 0; + } + + function isUint8() + { + return $this->name === 'uint8'; + } + + function isUint16() + { + return $this->name === 'uint16'; + } + + function isUint32() + { + return $this->name === 'uint32'; + } + + function isUint64() + { + return $this->name === 'uint64'; + } + + function isInt64() + { + return $this->name === 'int64'; + } + + function isInt8() + { + return $this->name === 'int8'; + } + + function isInt16() + { + return $this->name === 'int16'; + } + + function isInt32() + { + return $this->name === 'int32'; + } + + function isFloat() + { + return $this->name === 'float'; + } + + function isDouble() + { + return $this->name === 'double'; + } + + function isBool() + { + return $this->name === 'bool'; + } + + function isBlob() + { + return $this->name === 'blob'; + } +} + +class mtgMultiType implements mtgType +{ + private $values = array(); + + function __construct(array $values = array()) + { + foreach($values as $v) + $this->addValue($v); + } + + function addValue(mtgTypeRef $val) + { + $this->values[] = $val; + } + + function getValues() + { + $vals = array(); + foreach($this->values as $val) + $vals[] = $val->resolve(); + + return $vals; + } + + function __toString() + { + $str = ''; + foreach($this->getValues() as $val) + $str .= $val . ';'; + return $str; + } +} + +class mtgArrType implements mtgType +{ + private $value; + + function __construct(mtgTypeRef $value) + { + $this->value = $value; + } + + function __toString() + { + return $this->getValue() . '[]'; + } + + function getValue() + { + return $this->value->resolve(); + } +} + +class mtgMetaField +{ + private $name; + private $type; + private $tokens = array(); + + function __construct($name, mtgTypeRef $type) + { + $this->name = $name; + $this->type = $type; + } + + function setName($name) + { + $this->name = $name; + } + + function getName() + { + return $this->name; + } + + function getType() + { + return $this->type->resolve(); + } + + function getTokens() + { + return $this->tokens; + } + + function setTokens(array $tokens) + { + $this->tokens = $tokens; + } + + function setToken($name, $val) + { + $this->tokens[$name] = $val; + } + + function hasToken($name) + { + return array_key_exists($name, $this->tokens); + } + + function getToken($name) + { + return $this->hasToken($name) ? $this->tokens[$name] : null; + } +} + +class mtgMetaInfoParser +{ + private $config = array(); + private $current_meta; + private $parsed_files = array(); + private $file_stack = array(); + private $file = ""; + private $source = ""; + private $cursor = 0; + private $line = 0; + private $token = ""; + private $attribute = ""; + private $idltypes = array(); + private $token_strs = array(); + + const T_EOF = 1001; + const T_StringConstant = 1002; + const T_IntegerConstant = 1003; + const T_FloatConstant = 1004; + const T_Enum = 1005; + const T_RPC = 1006; + const T_End = 1007; + const T_Identifier = 1008; + const T_Struct = 1009; + const T_Prop = 1010; + const T_Extends = 1011; + const T_Func = 1012; + const T_string = 1020; + const T_uint32 = 1021; + const T_int32 = 1022; + const T_uint16 = 1023; + const T_int16 = 1024; + const T_uint8 = 1025; + const T_int8 = 1026; + const T_float = 1027; + const T_uint64 = 1028; + const T_int64 = 1029; + const T_bool = 1030; + const T_blob = 1031; + + function __construct($config = array()) + { + $this->config = $config; + if(!isset($this->config['include_path'])) + $this->config['include_path'] = array('.'); + + $this->idltypes = array( + "string" => self::T_string, + "uint32" => self::T_uint32, + "int32" => self::T_int32, + "uint16" => self::T_uint16, + "int16" => self::T_int16, + "uint8" => self::T_uint8, + "int8" => self::T_int8, + "float" => self::T_float, + "double" => self::T_float, + "uint64" => self::T_uint64, + "int64" => self::T_int64, + "bool" => self::T_bool, + "blob" => self::T_blob, + ); + $this->token_strs = array_flip($this->idltypes); + $this->token_strs[self::T_EOF] = ''; + $this->token_strs[self::T_StringConstant] = ''; + $this->token_strs[self::T_IntegerConstant] = ''; + $this->token_strs[self::T_FloatConstant] = ''; + $this->token_strs[self::T_Enum] = ''; + $this->token_strs[self::T_RPC] = ''; + $this->token_strs[self::T_End] = ''; + $this->token_strs[self::T_Identifier] = ''; + $this->token_strs[self::T_Struct] = ''; + $this->token_strs[self::T_Prop] = '<@prop>'; + $this->token_strs[self::T_Extends] = ''; + $this->token_strs[self::T_Func] = ''; + } + + function parse(mtgMetaInfo $meta, $raw_file) + { + $this->current_meta = $meta; + + $file = realpath($raw_file); + if($file === false) + throw new Exception("No such file '$raw_file'"); + + $this->_parse($file); + + mtgTypeRef::checkAllResolved(); + } + + private function _parse($file) + { + if(isset($this->parsed_files[$file])) + return; + $this->parsed_files[$file] = true; + $this->file_stack[] = $file; + $source = file_get_contents($file); + $is_php = false; + + try + { + if($source === false) + throw new Exception("Could not read file '$file'"); + + //PHP include + if(strpos($source, 'config['include_path'], array($this, '_parse')); + } + } + catch(Exception $e) + { + throw new Exception(end($this->file_stack) . " : " . $e->getMessage()); + } + + array_pop($this->file_stack); + + if($is_php) + return; + + $this->file = $file; + $this->source = $source; + $this->cursor = 0; + $this->line = 1; + + try + { + $this->_next(); + while($this->token != self::T_EOF) + { + //echo "TOKEN : " . $this->token . " " . $this->attribute . " " . $this->line . "\n"; + + if($this->token == self::T_Enum) + $this->_parseEnum(); + else if($this->token == self::T_Struct) + $this->_parseStruct(); + else if($this->token == self::T_Func) + $this->_parseFreeFunc(); + else if($this->token == self::T_RPC) + $this->_parseRPC(); + else + $this->_error("unexpected token ('" . $this->_toStr($this->token) . "' " . $this->attribute . ")"); + } + } + catch(Exception $e) + { + throw new Exception("$file@{$this->line} : " . $e->getMessage() . " " . $e->getTraceAsString()); + } + } + + private function _parseType($can_be_multi = false) + { + $types = array(); + + while(true) + { + $type = null; + $type_name = $this->attribute; + if($this->token == self::T_Identifier) + { + $type = new mtgTypeRef($type_name, $this->current_meta, $this->file, $this->line); + $this->_next(); + } + else if($this->token == self::T_Func) + { + $func_type = $this->_parseFuncType(); + $type = new mtgTypeRef($func_type); + } + else + { + $type = new mtgTypeRef(new mtgBuiltinType($type_name)); + $this->_next(); + } + + if($this->token == ord('[')) + { + $this->_next(); + $this->_checkThenNext(']'); + $type = new mtgTypeRef(new mtgArrType($type)); + } + $types[] = $type; + + if(!$can_be_multi) + break; + + if($this->token != ord(',')) + break; + $this->_next(); + } + + if(sizeof($types) > 1) + return new mtgTypeRef(new mtgMultiType($types)); + else + return $types[0]; + } + + private function _parseFuncType() + { + $ftype = new mtgMetaFunc(''); + + $this->_next(); + + $this->_checkThenNext('('); + + $c = 0; + while(true) + { + if($this->token == ord(')')) + { + $this->_next(); + break; + } + else if($c > 0) + { + $this->_checkThenNext(','); + } + + $arg_type = $this->_parseType(); + $c++; + $arg = new mtgMetaField("_$c", $arg_type); + $ftype->addArg($arg); + } + + if($this->token == ord(':')) + { + $this->_next(); + $ret_type = $this->_parseType(true/*can be multi-type*/); + $ftype->setReturnType($ret_type); + } + return $ftype; + } + + static function resolveIncludes(&$text, array $include_paths, $callback) + { + $result = array(); + $lines = explode("\n", $text); + foreach($lines as $line) + { + if(preg_match('~^#include\s+(\S+)~', $line, $m)) + { + self::processInclude($m[1], $include_paths, $callback); + $result[] = ""; + } + else + $result[] = $line; + } + $text = implode("\n", $result); + } + + static function processInclude($include, array $include_paths, $callback) + { + $file = false; + foreach($include_paths as $include_path) + { + $file = realpath($include_path . "/" . $include); + if($file !== false) + break; + } + + if($file === false) + throw new Exception("#include {$include} can't be resolved(include path is '". implode(':', $include_paths) . "')"); + + call_user_func_array($callback, array($file)); + } + + private function _parseEnumOrValues() + { + $values = array(); + while(true) + { + if($this->token == self::T_Identifier) + { + $values[] = $this->attribute; + $this->_next(); + if($this->token != ord('|')) + break; + else + $this->_next(); + } + else + break; + } + + return $values; + } + + private function _parseEnum() + { + $this->_next(); + + $name = $this->_checkThenNext(self::T_Identifier); + + $enum = new mtgMetaEnum($name); + $this->current_meta->addUnit(new mtgMetaInfoUnit($this->file, $enum)); + + if($this->token == self::T_Prop) + { + $enum->setTokens($this->_parsePropTokens()); + } + + $or_values = array(); + while(true) + { + if($this->_nextIf(self::T_End)) + break; + $key = $this->_checkThenNext(self::T_Identifier); + $this->_checkThenNext('='); + if($this->token == self::T_Identifier) + { + $or_values[$key] = $this->_parseEnumOrValues(); + } + else + { + $value = $this->_checkThenNext(self::T_IntegerConstant); + $enum->addValue($key, $value); + } + } + $enum->addOrValues($or_values); + } + + private function _parseFields($next_doer) + { + $flds = array(); + + while(true) + { + if($next_doer()) + break; + + if($this->token == self::T_Identifier) + { + $name = $this->attribute; + $this->_next(); + $this->_checkThenNext(':'); + + if($this->token == self::T_Identifier || + $this->token == self::T_Func || + ($this->token >= self::T_string && $this->token <= self::T_blob)) + { + $type = $this->_parseType(); + + $fld = new mtgMetaField($name, $type); + + if($this->token == self::T_Prop) + { + $fld->setTokens($this->_parsePropTokens()); + } + + $flds[] = $fld; + } + else + $this->_error("type expected"); + } + else + $this->_error("unexpected fields token"); + } + + return $flds; + } + + private function _parseFuncs() + { + $end_token = self::T_End; + + $funcs = array(); + + while(true) + { + $fn = $this->_parseFunc(); + $funcs[] = $fn; + + if($this->token == $end_token) + { + $this->_next(); + break; + } + + $this->_next(); + } + + return $funcs; + } + + private function _parseFunc() + { + if($this->token != self::T_Identifier) + $this->_error("unexpected func token"); + + $name = $this->attribute; + $fn = new mtgMetaFunc($name); + + $this->_next(); + $this->_checkThenNext('('); + if($this->token == self::T_Prop) + $fn->setTokens($this->_parsePropTokens()); + $args = $this->_parseFields(function() + { return $this->_nextIf(')'); } + ); + $fn->setArgs($args); + + $ret_type = null; + if($this->token == ord(':')) + { + $this->_next(); + if($this->token == self::T_Identifier || + $this->token == self::T_Func || + ($this->token >= self::T_string && $this->token <= self::T_bool)) + { + $ret_type = $this->_parseType(true/*can be multi-type*/); + $fn->setReturnType($ret_type); + } + else + $this->_error("unexpected func type token"); + } + + return $fn; + } + + private function _parseFreeFunc() + { + $this->_next(); + $fn = $this->_parseFunc(); + $this->current_meta->addUnit(new mtgMetaInfoUnit($this->file, $fn)); + } + + private function _parseStruct() + { + $this->_next(); + $name = $this->_checkThenNext(self::T_Identifier); + + $parent = null; + if($this->token == self::T_Extends) + { + $this->_next(); + $parent_name = $this->_checkThenNext(self::T_Identifier); + $parent = new mtgTypeRef($parent_name, $this->current_meta, $this->file, $this->line); + } + + $s = new mtgMetaStruct($name, array(), $parent); + $this->current_meta->addUnit(new mtgMetaInfoUnit($this->file, $s)); + + if($this->token == self::T_Prop) + { + $s->setTokens($this->_parsePropTokens()); + } + + $seen_funcs = false; + $flds = $this->_parseFields( + function() use(&$seen_funcs) + { + if($this->_nextIf(self::T_End)) + return true; + if($this->_nextIf(self::T_Func)) + { + $seen_funcs = true; + return true; + } + } + ); + foreach($flds as $fld) + $s->addField($fld); + + if($seen_funcs) + { + $funcs = $this->_parseFuncs(); + foreach($funcs as $fn) + $s->addFunc($fn); + } + } + + private function _parseRPC() + { + $this->_next(); + $code = $this->_checkThenNext(self::T_IntegerConstant); + $name = $this->_checkThenNext(self::T_Identifier); + $this->_checkThenNext('('); + + $tokens = array(); + if($this->token == self::T_Prop) + $tokens = $this->_parsePropTokens(); + + $req_fields = $this->_parseFields(function() + { return $this->_nextIf(')'); } + ); + $rsp_fields = $this->_parseFields(function() + { return $this->_nextIf(self::T_End); } + ); + + $req = new mtgMetaPacket($code, "RPC_REQ_$name"); + $req->setFields($req_fields); + $rsp = new mtgMetaPacket($code, "RPC_RSP_$name"); + $rsp->setFields($rsp_fields); + + $rpc = new mtgMetaRPC("RPC_$name", $code, $req, $rsp, $tokens); + $this->current_meta->addUnit(new mtgMetaInfoUnit($this->file, $rpc)); + } + + private function _parsePropTokens() + { + $new_line = ord("\n"); + + $prop_tokens = array(); + + while(true) + { + if($this->token != self::T_Prop) + break; + + $name = ltrim($this->attribute, '@'); + $this->_next(); + + $value = null; + if($this->token == ord(':')) + { + while(true) + { + $this->_next(false/*don't skip new line*/); + if($this->token == $new_line || + $this->token == self::T_Prop) + { + //let's skip it + if($this->token == $new_line) + $this->_next(); + break; + } + + $tmp = $this->attribute; + if($this->token == self::T_StringConstant) + $tmp = "\"$tmp\""; + if($value === null) + $value = ''; + $value .= $tmp; + } + } + if($value && substr($value, 0, 1) == '{') + { + $json = json_decode($value); + if($json === null) + { + --$this->line; //hack for more precise reporting + $this->_error("bad json"); + } + } + $this->_validatePropToken($name, $value); + + $prop_tokens[$name] = $value; + } + return $prop_tokens; + } + + private function _validatePropToken($name, $value) + { + if(!isset($this->config['valid_tokens']) || !$this->config['valid_tokens']) + return; + + if(!in_array($name, $this->config['valid_tokens'])) + throw new Exception("Unknown token '$name'"); + } + + private function _symbol() + { + return substr($this->source, $this->cursor, 1); + } + + private function _next($skip_newlines = true) + { + $this->__next($skip_newlines); + //for debug + //var_dump("NEXT " . $this->token . " " . $this->attribute); + //debug_print_backtrace(0, 1); + } + + private function __next($skip_newlines = true) + { + while(true) + { + $c = $this->_symbol(); + //NOTE: dealing with PHP's types juggling + if($c === false || $c === '') + $c = -1; + + $this->token = ord($c); + ++$this->cursor; + $this->attribute = $c; + + switch($c) + { + case -1: $this->cursor--; $this->token = self::T_EOF; return; + case ' ': case "\r": case "\t": break; + case "\n": $this->line++; if($skip_newlines) break; else return; + case '{': case '}': case '(': case ')': case '[': case ']': case '|': return; + case ',': case ':': case ';': case '=': return; + case '.': + if(!ctype_digit($this->_symbol())) return; + $this->_error("floating point constant can't start with ."); + break; + case '"': + $this->attribute = ""; + while($this->_symbol() != '"') + { + if(ord($this->_symbol()) < ord(' ') && ord($this->_symbol()) >= 0) + $this->_error("illegal character in string constant"); + if($this->_symbol() == '\\') + { + $this->cursor++; + switch($this->_symbol()) + { + case 'n': $this->attribute .= "\n"; $this->cursor++; break; + case 't': $this->attribute .= "\t"; $this->cursor++; break; + case 'r': $this->attribute .= "\r"; $this->cursor++; break; + case '"': $this->attribute .= '"'; $this->cursor++; break; + case '\\': $this->attribute .= '\\'; $this->cursor++; break; + default: $this->_error("unknown escape code in string constant"); break; + } + } + else // printable chars + UTF-8 bytes + { + $this->attribute .= $this->_symbol(); + $this->cursor++; + } + } + $this->token = self::T_StringConstant; + $this->cursor++; + return; + + case '/': + if($this->_symbol() == '/') + { + $this->cursor++; + while($this->_symbol() !== false && $this->_symbol() != "\n") $this->cursor++; + break; + } + + case '#': + while($this->_symbol() !== false && $this->_symbol() != "\n") $this->cursor++; + break; + + case '@': + $start = $this->cursor - 1; + while(ctype_alnum($this->_symbol()) || $this->_symbol() == '_') + $this->cursor++; + $this->token = self::T_Prop; + $this->attribute = substr($this->source, $start, $this->cursor - $start); + return; + + //fall thru + default: + + if(ctype_alpha($c)) + { + //collect all chars of an identifier + $start = $this->cursor - 1; + while(ctype_alnum($this->_symbol()) || $this->_symbol() == '_') + $this->cursor++; + $this->attribute = substr($this->source, $start, $this->cursor - $start); + + if(isset($this->idltypes[$this->attribute])) + { + $this->token = $this->idltypes[$this->attribute]; + return; + } + + if($this->attribute == "true" || $this->attribute == "false") + { + $this->token = self::T_IntegerConstant; + return; + } + + //check for declaration keywords: + if($this->attribute == "struct") { $this->token = self::T_Struct; return; } + if($this->attribute == "enum") { $this->token = self::T_Enum; return; } + if($this->attribute == "RPC") { $this->token = self::T_RPC; return; } + if($this->attribute == "end") { $this->token = self::T_End; return; } + if($this->attribute == "extends") { $this->token = self::T_Extends; return; } + if($this->attribute == "func") { $this->token = self::T_Func; return; } + + //if not it's a user defined identifier + $this->token = self::T_Identifier; + return; + } + else if(ctype_digit($c) || $c == '-') + { + $start = $this->cursor - 1; + while(ctype_digit($this->_symbol())) $this->cursor++; + if($this->_symbol() == '.') + { + $this->cursor++; + while(ctype_digit($this->_symbol())) $this->cursor++; + // see if this float has a scientific notation suffix. Both JSON + // and C++ (through strtod() we use) have the same format: + if($this->_symbol() == 'e' || $this->_symbol() == 'E') + { + $this->cursor++; + if($this->_symbol() == '+' || $this->_symbol() == '-') $this->cursor++; + while(ctype_digit($this->_symbol())) $this->cursor++; + } + $this->token = self::T_FloatConstant; + } + else + $this->token = self::T_IntegerConstant; + $this->attribute = substr($this->source, $start, $this->cursor - $start); + return; + } + + $this->_error("illegal character '$c'"); + } + } + } + + private function _nextIf($t) + { + if(is_string($t)) + $t = ord($t); + $yes = $t === $this->token; + if($yes) + $this->_next(); + return $yes; + } + + private function _checkThenNext($t) + { + if(is_string($t)) + $t = ord($t); + if($t !== $this->token) + { + $this->_error("Expecting '" . $this->_toStr($t) . "' instead got '" . $this->_toStr($this->token) . "'"); + } + + $attr = $this->attribute; + $this->_next(); + return $attr; + } + + private function _toStr($t) + { + if($t < 1000) + return chr($t); + return $this->token_strs[$t]; + } + + private function _error($msg) + { + throw new Exception($msg . "(token: {$this->token}, attr: {$this->attribute}})"); + } +} + +function mtg_get_file_deps(mtgMetaInfo $info, mtgMetaInfoUnit $unit) +{ + $deps = array(); + $deps[] = $unit->file; + + if($unit->object instanceof mtgMetaStruct) + { + foreach($unit->object->getFields() as $field) + { + $type = $field->getType(); + if($type instanceof mtgArrType) + $type = $type->getValue(); + + if($type instanceof mtgUserType) + $deps[] = $info->findUnit($type->getName())->file; + } + + $parent = $unit->object->getParent(); + if($parent) + $deps = array_merge($deps, mtg_get_file_deps($info, $info->findUnit($parent->getName()))); + } + + return $deps; +} + +function mtg_get_all_fields(mtgMetaInfo $info, mtgMetaStruct $struct) +{ + $fields = $struct->getFields(); + $parent = $struct->getParent(); + if($parent) + //NOTE: order is important, parent fields must come first + $fields = array_merge(mtg_get_all_fields($info, $parent), $fields); + return $fields; +} + +function mtg_load_meta(mtgMetaInfo $meta, mtgMetaInfoParser $meta_parser, $dir_or_file) +{ + $files = array(); + if(is_dir($dir_or_file)) + $files = mtg_find_meta_files($dir_or_file); + else if(is_file($dir_or_file)) + $files[] = $dir_or_file; + else + throw new Exception("Bad meta source '$dir_or_file'"); + + foreach($files as $file) + $meta_parser->parse($meta, $file); +} + +function mtg_find_meta_files($dir) +{ + $items = scandir($dir); + if($items === false) + throw new Exception("Directory '$dir' is invalid"); + + $files = array(); + foreach($items as $item) + { + if($item[0] == '.') + continue; + + if(strpos($item, ".meta") !== (strlen($item)-5)) + continue; + + $file = $dir . '/' . $item; + if(is_file($file) && !is_dir($file)) + $files[] = $file; + } + return $files; +} + +function mtg_is_filtered($name, $filters) +{ + foreach($filters as $filter) + { + if(!$filter) + continue; + if(strpos($name, $filter) !== false) + return false; + } + return true; +} + +function mtg_is_win() +{ + return DIRECTORY_SEPARATOR == '\\'; +} + +function mtg_which($bin) +{ + if(mtg_is_win()) + $bin .= ".exe"; + $path = exec("which $bin", $out, $ret); + if($ret != 0) + throw new Exception("Exection error 'which $bin': " . implode($out)); + return $path; +} + +function mtg_ensure_write_to_file($file, $body) +{ + if(file_put_contents($file, $body) === false) + throw new Exception("Could not write file '$file'"); + echo "> $file\n"; +} + +function mtg_fill_template($tpl, $replaces) +{ + return str_replace(array_keys($replaces), array_values($replaces), $tpl); +} + +function mtg_write_template($tpl, $replaces, $file, $exists_check = false) +{ + if($exists_check && file_exists($file)) + { + echo "! $file\n"; + return; + } + $body = mtg_fill_template($tpl, $replaces); + mtg_ensure_write_to_file($file, $body); +} + +function mtg_write_template_once($tpl, $replaces, $file) +{ + mtg_write_template($tpl, $replaces, $file, true); +} + +function mtg_append_to_slot($slot, $replace, &$contents) +{ + $contents = str_replace($slot, $slot . $replace, $contents); +} + +function mtg_prepend_to_slot($slot, $replace, &$contents) +{ + $contents = str_replace($slot, $replace . $slot, $contents); +} + +function mtg_apply_script($script, $args) +{ + $class = current(explode('.', basename($script))); + include_once($script); + $obj = new $class(); + call_user_func_array(array($obj, 'apply'), $args); +} + +function mtg_paginate($total, $step) +{ + //pages are returned as an array where each element is in interval [N, Y) + $pages = array(); + + $steps = (int)($total/$step); + $rest = $total % $step; + + for($i=1;$i<=$steps;++$i) + $pages[] = array(($i-1)*$step, $i*$step); + + if($rest != 0) + $pages[] = array(($i-1)*$step, ($i-1)*$step + $rest); + + return $pages; +} + +function mtg_mkdir($dir) +{ + if(!is_dir($dir)) + mkdir($dir, 0777, true); +} + +function mtg_rm($path) +{ + if(is_file($path)) + { + unlink($path); + return; + } + + if(!is_dir($path)) + return; + + $path= rtrim($path, '/').'/'; + $handle = opendir($path); + if($handle === false) + throw new Exception("Could not open directory '$path' for reading"); + for(;false !== ($file = readdir($handle));) + { + if($file != "." and $file != ".." ) + { + $fullpath= $path.$file; + if( is_dir($fullpath) ) + { + mtg_rm($fullpath); + rmdir($fullpath); + } + else + unlink($fullpath); + } + } + closedir($handle); +} + +//returns a name of the temp file to use, temp file IS NOT created +function mtg_get_tmpfile($prefix = '__') +{ + $MAX_TRIES = 1000; + $tries = 0; + while($tries < $MAX_TRIES) + { + $tmp_file = sys_get_temp_dir() . '/' . $prefix . uniqid(); + if(!is_file($tmp_file)) + break; + ++$tries; + } + + if($tries == $MAX_TRIES) + throw new Exception("Could not make unique temp file name"); + + return $tmp_file; +} + +function mtg_normalize_path($file) +{ + if(mtg_is_win()) + { + //strtolower drive letters + if(strpos($file, ':') === 1) + $file = strtolower(substr($file, 0, 1)) . substr($file, 1); + } + + $file = str_replace('\\', '/', $file); + $file = str_replace('//', '/', $file); + return $file; +} + +class mtgGenTarget +{ + public $file; + public $deps = array(); + public $func; + public $args = array(); + public $content_check; + + function __construct($file, array $deps, array $callback, $content_check = true) + { + if(!$callback) + throw new Exception("Bad bind arg"); + + if(!is_callable($callback[0])) + throw new Exception("Callback arg 0, not callable:" . serialize($callback[0])); + + $this->file = $file; + $this->deps = $deps; + $this->func = array_shift($callback); + $this->args = array_merge(array($this->file, $this->deps), $callback); + $this->content_check = $content_check; + } + + function execute() + { + if(!mtg_need_to_regen($this->file, $this->deps)) + return false; + + $res = call_user_func_array($this->func, $this->args); + + $exists_check = false; + if(is_array($res)) + { + $exists_check = $res[0]; + $text = $res[1]; + } + else if(is_string($res)) + $text = $res; + else + throw new Exception("Bad result:" . serialize($res)); + + if($exists_check && file_exists($this->file)) + { + //too verbose + //echo "! {$this->file}\n"; + return false; + } + + if($this->content_check) + { + if(file_exists($this->file) && file_get_contents($this->file) === $text) + return false; + } + + mtg_ensure_write_to_file($this->file, $text); + return true; + } +} + +class mtgGenBundleTarget extends mtgGenTarget +{ + function execute() + { + $bundle_info = $this->file . '.bndl'; + $prev_amount = 0; + + if(is_file($bundle_info)) + $prev_amount = 1*file_get_contents($bundle_info); + + //if the amount of bundled files has changed we need to update bundle info + if($prev_amount != sizeof($this->deps)) + file_put_contents($bundle_info, sizeof($this->deps)); + + $this->deps[] = $bundle_info; + return parent::execute(); + } +} + +class mtgMetaPacket extends mtgMetaStruct +{ + private $code; + + function __construct($code, $name, array $tokens = array()) + { + $this->code = $code; + + parent::__construct($name, array(), null, $tokens); + } + + function getPrefix() + { + list($prefix,) = explode('_', $this->getName()); + return $prefix; + } + + function getFullName() + { + return $this->code . "_" . $this->getName(); + } + + function getCode() + { + return $this->code; + } + + function getNumericCode() + { + return (int)$this->code; + } +} + +class mtgMetaEnum extends mtgUserType +{ + private $values = array(); + + function addValue($value_name, $value) + { + if(isset($this->values[$value_name])) + throw new Exception("Enum '{$this->name}' already has value with name '{$value_name}'"); + + if(in_array($value, $this->values)) + throw new Exception("Enum '{$this->name}' already has value '{$value}'"); + + $this->values[$value_name] = $value; + } + + function calcOrValue($or_keys) + { + $res = 0; + foreach ($or_keys as $value_name) + { + if(!isset($this->values[$value_name])) + throw new Exception("Enum '{$this->name}' has no value '{$value_name}'"); + $res |= $this->values[$value_name]; + } + + return $res; + } + + function addOrValues($values) + { + foreach($values as $value_name => $or_keys) + $this->addValue($value_name, $this->calcOrValue($or_keys)); + } + + function getValues() + { + return $this->values; + } +} + +abstract class mtgGenerator +{ + abstract function makeTargets(mtgMetaInfo $info); +} + +abstract class mtgCodegen +{ + protected $info; + private $tmp_cmds = array(); + + abstract function genUnit(mtgMetaInfoUnit $unit); + + function setMetaInfo(mtgMetaInfo $info) + { + $this->info = $info; + } + + function nextId() + { + static $id = 0; + $id++; + return $id; + } + + function tempRenameField($field, $new_name) + { + $prev_name = $field->getName(); + $field->setName($new_name); + + $this->tmp_cmds[] = array(1, $field, $prev_name); + } + + function addTempField(mtgMetaStruct $struct, mtgMetaField $new_fld) + { + $struct->addField($new_fld); + + $this->tmp_cmds[] = array(2, $struct, $new_fld); + } + + function undoTemp() + { + foreach($this->tmp_cmds as $cmd) + { + if($cmd[0] == 1) + { + $cmd[1]->setName($cmd[2]); + } + else if($cmd[0] == 2) + { + $cmd[1]->delField($cmd[2]); + } + else + throw new Exception("Unknown temp cmd"); + } + } +} + +function mtg_new_file($file, array $deps, array $callback, $content_check = false) +{ + return new mtgGenTarget($file, $deps, $callback, $content_check); +} + +function mtg_new_bundle($file, array $deps, array $callback, $content_check = false) +{ + return new mtgGenBundleTarget($file, $deps, $callback, $content_check); +} + +function mtg_need_to_regen($file, array $deps) +{ + if(!is_file($file)) + return true; + $fmtime = filemtime($file); + foreach($deps as $dep) + { + if(is_file($dep) && (filemtime($dep) > $fmtime)) + return true; + } + return false; +} + +function mtg_parse_meta(array $meta_srcs) +{ + $meta_dirs = array(); + foreach($meta_srcs as $src) + { + if(is_dir($src)) + $meta_dirs[] = $src; + else if(is_file($src)) + $meta_dirs[] = dirname($src); + } + + $meta_parser = new mtgMetaInfoParser( + array( + 'include_path' => $meta_dirs, + 'valid_tokens' => mtg_conf('valid-tokens', null) + ) + ); + $meta = new mtgMetaInfo(); + foreach($meta_srcs as $src) + mtg_load_meta($meta, $meta_parser, $src); + return $meta; +} + +function mtg_run(mtgGenerator $gen, array $config) +{ + global $METAGEN_CONFIG; + + $config_copy = $METAGEN_CONFIG; + foreach($config as $k => $v) + $METAGEN_CONFIG[$k] = $v; + + $meta = mtg_conf('meta', null); + if(!($meta instanceof mtgMetaInfo)) + { + $meta_dirs = mtg_conf('meta-dir'); + $meta = mtg_parse_meta($meta_dirs); + } + $targets = $gen->makeTargets($meta); + + foreach($targets as $t) + $t->execute(); + + $METAGEN_CONFIG = $config_copy; +} + +function mtg_crc28($what) +{ + return crc32($what) & 0xFFFFFFF; +} + +function mtg_static_hash($whar) +{ + $hash = 0; + for($i = 0; $i < strlen($whar); ++$i) + { + $hash = ((65599 * $hash) & 0x0FFFFFFF) + ord($whar[$i]); + } + + return ($hash ^ ($hash >> 16)); +} + + diff --git a/targets/cs/cs.inc.php b/targets/cs/cs.inc.php new file mode 100644 index 0000000..ed57387 --- /dev/null +++ b/targets/cs/cs.inc.php @@ -0,0 +1,702 @@ +object; + if($obj instanceof mtgMetaEnum) + return $this->genEnum($obj); + else if($obj instanceof mtgMetaStruct) + return $this->genStruct($obj); + else if($obj instanceof mtgMetaRPC) + return $this->genRPC($obj); + else + { + echo "WARN: skipping meta unit '{$obj->getId()}'\n"; + return ''; + } + } + + function genEnum(mtgMetaEnum $struct) + { + $templater = new mtg_cs_templater(); + + $repl = array(); + $repl['%namespace%'] = $this->namespace; + $repl['%class%'] = $struct->getName(); + $repl['%class_id%'] = $struct->getClassId(); + + $tpl = $templater->tpl_enum(); + + $this->fillEnumRepls($struct, $repl); + + return mtg_fill_template($tpl, $repl); + } + + function fillEnumRepls(mtgMetaEnum $enum, &$repl) + { + $repl['%fields%'] = ''; + $repl['%attributes%'] = ''; + $fields =& $repl['%fields%']; + if($enum->hasToken('cs_attributes')) + { + $attrs = explode(",", $enum->getToken('cs_attributes')); + foreach($attrs as $attr) + $repl['%attributes%'] .= "[$attr] "; + } + foreach($enum->getValues() as $vname => $value) + $fields .= sprintf("\n %s = %s,", $vname, $value); + } + + function genRPC(mtgMetaRPC $rpc) + { + $templater = new mtg_cs_templater(); + + $repl = array(); + $repl['%namespace%'] = $this->namespace; + $repl['%class%'] = $rpc->getName(); + $repl['%code%'] = $rpc->getCode(); + $repl['%req_class%'] = $rpc->getReq()->getName(); + $repl['%rsp_class%'] = $rpc->getRsp()->getName(); + + $tpl = $templater->tpl_rpc(); + + return + mtg_fill_template($tpl, $repl) . + $this->genStruct($rpc->getReq()) . + $this->genStruct($rpc->getRsp()); + } + + function genStruct(mtgMetaStruct $struct) + { + $templater = new mtg_cs_templater(); + + $repl = array(); + $repl['%namespace%'] = $this->namespace; + $repl['%class%'] = $struct->getName(); + $repl['%class_id%'] = $struct->getClassId(); + + $tpl = $templater->tpl_struct(); + + $this->fillStructRepls($struct, $repl); + + return mtg_fill_template($tpl, $repl); + } + + function preprocessField(mtgMetaStruct $struct, mtgMetaField $field) + {} + + function countFields(mtgMetaStruct $struct) + { + $fields = $struct->getFields(); + $count = 0; + foreach($fields as $field) + { + if($field->hasToken('i18n')) + $count += 2; + else + $count += 1; + } + + return $count; + } + + function fillStructRepls(mtgMetaStruct $struct, &$repl) + { + foreach($struct->getFields() as $field) + $this->preprocessField($struct, $field); + + $all_fields = mtg_get_all_fields($this->info, $struct); + $all_fields_count = $this->countAllFields($struct); + + $repl['%includes%'] = ''; + $repl['%fields%'] = ''; + $repl['%fields_reset%'] = ''; + $repl['%attributes%'] = ''; + + $repl['%sync_buffer%'] = ''; + $repl['%fields_count%'] = $all_fields_count; + $repl["%ext_methods%"] = ""; + + $repl['%new_method%'] = ''; + $repl['%virt_method%'] = ' virtual '; + $repl['%commented_in_child_begin%'] = ''; + $repl['%commented_in_child_end%'] = ''; + $repl['%commented_in_pod_begin%'] = ''; + $repl['%commented_in_pod_end%'] = ''; + + $has_bitfields_token = $struct->hasToken("bitfields"); + + $bitctx_name = 'bitctx'; + + $repl['%bitfields_sync_context%'] = ""; + if($has_bitfields_token) + { + $repl['%bitfields_sync_context%'] .= $this->offset(2)."var $bitctx_name = new Meta.BitfieldsContext(ctx, fields_mask);\n"; + $repl['%bitfields_sync_context%'] .= $this->offset(2)."$bitctx_name.SyncMaskHeader();\n"; + } + + if($struct->hasToken('cs_attributes')) + { + $attrs = explode(",", $struct->getToken('cs_attributes')); + foreach($attrs as $attr) + $repl['%attributes%'] .= "[$attr] "; + } + + $parent = $struct->getParent(); + $is_pod = $struct->hasToken('POD'); + + if($parent && $has_bitfields_token) + throw new Exception("@bitfields struct can't have a parent: {$struct->getName()}"); + + if($is_pod) + { + $fields = $all_fields; + $repl['%type_name%'] = 'struct'; + $repl['%parent%'] = " : IMetaStruct"; + $repl['%virt_method%'] = ''; + $repl['%commented_in_pod_begin%'] = '/*'; + $repl['%commented_in_pod_end%'] = '*/'; + } + else + { + $repl['%type_name%'] = 'class'; + $repl['%parent%'] = " : " . ($parent ? $parent : " BaseMetaStruct"); + if($parent) + $repl['%sync_buffer%'] .= "\n base.syncFields(ctx);\n"; + $repl['%commented_in_child_begin%'] = '/*'; + $repl['%commented_in_child_end%'] = '*/'; + $repl['%virt_method%'] = " override "; + + if($parent) + { + $repl['%new_method%'] = " new "; + $repl['%fields_reset%'] = "base.reset();\n"; + } + + $fields = $struct->getFields(); + } + + $repl['%copy_fields%'] = ''; + $repl['%virt_clone_method%'] = ''; + + if($struct->hasToken("cloneable")) + { + $repl['%parent%'] .= ",IMetaCloneable"; + $repl['%commented_in_non_cloneable_begin%'] = ''; + $repl['%commented_in_non_cloneable_end%'] = ''; + + if($is_pod) + $repl['%virt_clone_method%'] = ''; + else if($parent && $parent->hasTokenInParent("cloneable")) + $repl['%virt_clone_method%'] = ' override '; + else + $repl['%virt_clone_method%'] = ' virtual '; + } + else + { + $repl['%commented_in_non_cloneable_begin%'] = '/*'; + $repl['%commented_in_non_cloneable_end%'] = '*/'; + } + + $field_index = 0; + foreach($fields as $field) + { + $repl['%fields%'] .= $this->offset() . $this->genFieldDecl($struct, $field)."\n"; + + $repl['%fields_reset%'] .= $this->offset() . $this->genFieldReset($field)."\n"; + + $sync_opts = $this->resolveSyncOpts($struct, $bitctx_name); + + $repl['%sync_buffer%'] .= $this->offset(2) . $this->genBufSyncRegular($field, "ctx", $sync_opts)."\n"; + + $field_index++; + } + + if($has_bitfields_token) + { + $repl['%fields%'] .= "\n".$this->offset()."public long fields_mask;\n"; + + $repl['%ext_methods%'] .= "\n".$this->genBitmaskHelpers($struct, $fields); + + $repl['%copy_fields%'] .= $this->offset() . "fields_mask = other.fields_mask;\n"; + $repl['%fields_reset%'] .= $this->offset() . "fields_mask = 0L;\n"; + + $repl['%dirty_fields_count%'] = "Meta.GetDirtyFieldsCount(fields_mask, fields_count: $all_fields_count) + Meta.MASK_HEADER_FIELDS_COUNT"; + } + else + $repl['%dirty_fields_count%'] = "$all_fields_count /* equal to getFieldsCount */ "; + + $repl['%fields%'] = trim($repl['%fields%'], "\n "); + + $this->undoTemp(); + } + + function resolveSyncOpts(mtgMetaStruct $struct, string $bitctx_name) + { + $opts = array(); + if($struct->hasToken("bitfields")) + $opts[] = "$bitctx_name.GetNextOpts()"; + + if(count($opts) == 0) + return "0"; + + return implode(" | ", $opts); + } + + function countAllFields(mtgMetaStruct $struct) + { + $count = count($struct->getFields()); //preprocessed fields list + + //raw fields lists + $curr = $struct->getParent(); + while($curr) + { + $count += $this->countFields($curr); + $curr = $curr->getParent(); + } + + return $count; + } + + function genNativePlainType(mtgType $type) + { + if($type instanceof mtgBuiltinType) + { + switch($type->getName()) + { + case "int8": + return "sbyte"; + case "uint8": + return "byte"; + case "int16": + return "short"; + case "uint16": + return "ushort"; + case "int32": + case "int": + return "int"; + case "uint32": + case "uint": + return "uint"; + case "float": + return "float"; + case "double": + return "double"; + case "uint64": + return "ulong"; + case "int64": + return "long"; + case "string": + return "string"; + case "bool": + return "bool"; + case "blob": + return "byte[]"; + } + throw new Exception("Unknown type '{$type}'"); + } + return $type->getName(); + } + + function genTypePrefix(mtgType $type) + { + switch($type->getName()) + { + case "float": + return "Float"; + case "double": + return "Double"; + case "uint64": + return "U64"; + case "int64": + return "I64"; + case "uint": + case "uint32": + return "U32"; + case "uint16": + return "U16"; + case "uint8": + return "U8"; + case "int": + case "int32": + return "I32"; + case "int16": + return "I16"; + case "int8": + return "I8"; + case "string": + return "String"; + case "bool": + return "Bool"; + case "blob": + return "Blob"; + default: + throw new Exception("Unknown type '{$type}'"); + } + } + + function genBufSyncRegular(mtgMetaField $field, $buf, $opts = "") + { + return $this->genBufSync($field->getName(), $field, $buf, $opts); + } + + function genBufSync(string $fname, mtgMetaField $field, $buf, $opts) + { + return $this->genBufSyncEx($fname, $field->getType(), $buf, $field->getTokens(), $opts); + } + + function genBufSyncEx($fname, mtgType $type, $buf, array $tokens = array(), $opts = "") + { + $optional = array_key_exists('optional', $tokens); + if($optional) + $opts .= " | MetaSyncFieldOpts.SKIP_OPTIONAL"; + + $str = ''; + if($type instanceof mtgBuiltinType) + { + $str .= "Meta.Sync({$buf}, ref {$fname}, {$opts});\n"; + } + else if($type instanceof mtgMetaStruct) + { + if(array_key_exists('virtual', $tokens)) + $str .= "{$fname} = ({$type->getName()})Meta.SyncGeneric({$buf}, {$fname}, {$opts});\n"; + else + $str .= "Meta.Sync({$buf}, ref {$fname}, {$opts});\n"; + } + else if($type instanceof mtgMetaEnum) + { + $str .= "int __tmp_{$fname} = (int)$fname;\n"; + $str .= "Meta.Sync({$buf}, ref __tmp_{$fname}, {$opts});\n"; + $str .= "if($buf.is_read) {$fname} = ({$type->getName()})__tmp_{$fname};\n"; + } + else if($type instanceof mtgArrType) + { + if(array_key_exists('virtual', $tokens)) + $str .= "Meta.SyncGeneric({$buf}, {$fname}, {$opts});\n"; + else + { + if($type->getValue() instanceof mtgMetaEnum) + { + $str .= "Meta.tmp_enums_list.Clear(); if(!{$buf}.is_read) { foreach(var _enum_tmp in {$fname}) Meta.tmp_enums_list.Add((int)_enum_tmp); }\n"; + $str .= "Meta.Sync({$buf}, Meta.tmp_enums_list, {$opts});\n"; + $str .= "if({$buf}.is_read) foreach(var _int_tmp in Meta.tmp_enums_list) { {$fname}.Add(({$type->getValue()->getName()}) _int_tmp); }\n"; + } + else + $str .= "Meta.Sync({$buf}, {$fname}, {$opts});\n"; + } + } + else + throw new Exception("Unknown type '$type'"); + + return $str; + } + + function genConstructArgs($type) + { + if($type == "string") + return '""'; + return "new {$type}()"; + } + + function isPOD(mtgMetaStruct $struct) + { + return $struct->hasToken('POD'); + } + + function genFieldDecl(mtgMetaStruct $struct, mtgMetaField $field) + { + $str = ""; + + if($field->hasToken('cs_attributes')) + { + $attrs = explode(",", $field->getToken('cs_attributes')); + foreach($attrs as $attr) + $str .= "[$attr] "; + } + + $str .= "public " . $this->genFieldNativeType($field) . " "; + + if(!$this->isPOD($struct)) + $str .= $this->genFieldDeclInit($field); + else + $str .= $this->genFieldName($field) . ";"; + + return $str; + } + + function offset($amount = 1) + { + return str_repeat(" ", $amount); + } + + function genFieldCopy(mtgMetaField $field) + { + $tokens = $field->getTokens(); + $name = $this->genFieldName($field); + $type = $field->getType(); + $str = ''; + + if($type instanceof mtgArrType) + { + $str .= "for(int i=0;other.{$name} != null && igetValue(); + if($vtype instanceof mtgMetaStruct) + { + if(array_key_exists('virtual', $tokens)) + $str .= $this->offset(3)."var tmp = ({$vtype->getName()})other.{$name}[i].clone();\n"; + else + $str .= $this->offset(3)."var tmp = new {$vtype->getName()}(); tmp.copyFrom(other.{$name}[i]);\n"; + $str .= $this->offset(3)."{$name}.Add(tmp);\n"; + } + else + $str .= $this->offset(3)."{$name}.Add(other.{$name}[i]);\n"; + + $str .= $this->offset(2)."}\n"; + } + else if($type instanceof mtgMetaStruct) + { + if(array_key_exists('virtual', $tokens)) + $str .= $this->offset(2)."{$name} = ({$type->getName()})other.{$name}.clone();\n"; + else + $str .= $this->offset(2)."{$name}.copyFrom(other.{$name});\n"; + } + else + $str .= $this->offset()."{$name} = other.{$name};\n"; + + return $str; + } + + function genFieldDeclInit(mtgMetaField $field) + { + $tokens = $field->getTokens(); + + $str = $this->genFieldName($field); + $type = $field->getType(); + if($type instanceof mtgBuiltinType) + { + if($type->isString()) + $str .= ' = ""'; + } + else + $str .= " = new ".$this->genFieldNativeType($field)."()"; + $str .= ";"; + return $str; + } + + function genFieldReset(mtgMetaField $field) + { + $tokens = $field->getTokens(); + $default = array_key_exists('default', $tokens) ? $tokens['default'] : null; + + return $this->genFieldResetEx($this->genFieldName($field), $field->getType(), $default); + } + + function genFieldResetEx($name, mtgType $type, $default = null) + { + $str = ''; + if($type instanceof mtgBuiltinType) + { + if($type->isNumeric()) + { + $str = $name; + //NOTE: numeric check is against str2num + if($default && is_numeric($default)) + { + if($type->isFloat()) + $default .= "f"; + $str .= " = $default;"; + } + else + $str .= " = 0;"; + } + else if($type->isString()) + { + $str = $name; + if($default) + { + $str .= " = \"".trim($default, '"')."\";"; + } + else + $str .= ' = "";'; + } + else if($type->isBool()) + { + $str = $name; + if($default) + { + $str .= " = ".trim($default, '"').";"; + } + else + $str .= ' = false;'; + } + else if($type->isBlob()) + { + $str = $name; + if($default) + { + $str .= " = ".trim($default, '"').";"; + } + else + $str .= ' = null;'; + } + else + throw new Exception("Unknown type '$type'"); + } + else if($type instanceof mtgArrType) + { + $str = "Meta.ClearList(ref $name);"; + + if($default) + { + $default_arr = is_array($default) ? $default : json_decode($default, true); + if(!is_array($default_arr)) + throw new Exception("Bad default value for array: '$default'"); + + if($default_arr) + { + $type_str = $this->genNativeType($type->getValue()); + $str .= "{ $type_str tmp__"; + if($this->isNullableType($type->getValue())) + $str .= " = null"; + $str .= ";"; + foreach($default_arr as $v) + { + $str .= $this->genFieldResetEx("tmp__", $type->getValue(), $v); + $str .= "$name.Add(tmp__);"; + } + $str .= "}"; + } + } + } + else if($type instanceof mtgMetaEnum) + { + if($default) + $str = "$name = ".$this->genNativeType($type).".".trim($default,'"')."; "; + else + $str = "$name = new ".$this->genNativeType($type)."(); "; + } + else if($type instanceof mtgMetaStruct) + { + $str = ""; + $is_pod = $type->hasToken('POD'); + if($is_pod) + $str .= "$name.reset(); "; + else + $str .= "Meta.Reset(ref $name); "; + + if($default) + { + $default = is_array($default) ? $default : json_decode($default, true); + if(!is_array($default)) + throw new Exception("Bad default value for struct: $default"); + + foreach($default as $k => $v) + { + $kf = $type->getField($k); + $str .= $this->genFieldResetEx("$name." . $this->genFieldName($kf), $kf->getType(), $v); + } + } + } + else + throw new Exception("Bad type '$type'"); + return $str; + } + + function isNullableType(mtgType $type) + { + return $type instanceof mtgArrType || + ($type instanceof mtgMetaStruct && !$type->hasToken('POD')); + } + + function genFieldName(mtgMetaField $field) + { + return $field->getName(); + } + + function genFieldNativeType(mtgMetaField $field) + { + return $this->genNativeType($field->getType(), $field->getTokens()); + } + + function genNativeType(mtgType $type, array $tokens = array()) + { + if($type instanceof mtgBuiltinType || $type instanceof mtgUserType) + return $this->genNativePlainType($type); + else if($type instanceof mtgArrType) + return "List<" . $this->genNativePlainType($type->getValue()) . ">"; + else + throw new Exception("Unknown type '{$type}'"); + } + + function genBitmaskHelpers(mtgMetaStruct $struct, array $fields) + { + //RESET FIELD MASK + $str = $this->offset()."public void ResetFieldMask()\n"; + $str .= $this->offset()."{\n"; + $str .= $this->offset(2)."fields_mask = 0L;\n"; + $str .= $this->offset()."}\n\n"; + + //SET PRIMARY FIELDS CHANGED + $primary_fields = array(); + if($struct->hasToken("id")) + $primary_fields[] = $struct->getToken("id"); + + if($struct->hasToken("owner")) + $primary_fields[] = $struct->getToken("owner"); + + if($struct->hasToken("pkey")) + { + foreach(explode(",", $struct->getToken("pkey")) as $name) + $primary_fields[] = $name; + } + + $str .= $this->offset()."public void SetPrimaryFieldsChanged()\n"; + $str .= $this->offset()."{\n"; + $field_index = 0; + foreach($fields as $field) + { + if(in_array($field->getName(), $primary_fields)) + $str .= $this->offset(2)."Meta.SetFieldDirty(ref fields_mask, $field_index);\n"; + + $field_index++; + } + $str .= $this->offset()."}\n\n"; + + //DIRTY FIELD MASK + $str .= $this->offset()."public void SetDirtyMask()\n"; + $str .= $this->offset()."{\n"; + $str .= $this->offset(2)."fields_mask = ~0L;\n"; + $str .= $this->offset()."}\n\n"; + + $str .= $this->offset()."public void SetDirtyMaskDeep()\n"; + $str .= $this->offset()."{\n"; + $str .= $this->offset(2)."SetDirtyMask();\n"; + foreach($fields as $field) + { + $type = $field->getType(); + + if($type instanceof mtgMetaStruct && $type->hasToken("bitfields")) + $str .= $this->offset(2)."{$field->getName()}.SetDirtyMaskDeep();\n"; + else if($type instanceof mtgArrType && $type->getValue() instanceof mtgMetaStruct && $type->getValue()->hasToken("bitfields")) + { + $str .= $this->offset(2)."for(int i=0;i<{$field->getName()}.Count;++i) {\n"; + $str .= $this->offset(2)."var __tmp = {$field->getName()}[i];\n"; + $str .= $this->offset(2)."__tmp.SetDirtyMaskDeep();\n"; + $str .= $this->offset(2)."{$field->getName()}[i] = __tmp;\n"; + $str .= $this->offset(2)."}\n"; + } + } + $str .= $this->offset()."}\n\n"; + + return $str; + } + +} diff --git a/targets/cs/cs_generator.inc.php b/targets/cs/cs_generator.inc.php new file mode 100644 index 0000000..20e5b25 --- /dev/null +++ b/targets/cs/cs_generator.inc.php @@ -0,0 +1,87 @@ +setMetaInfo($meta); + + $refl = new ReflectionClass($codegen); + $SHARED_DEPS = array( + dirname(__FILE__) . '/cs.inc.php', + dirname(__FILE__) . '/cs_tpl.inc.php', + __FILE__, + $refl->getFileName() + ); + + $units = array(); + $files = array(); + foreach($meta->getUnits() as $unit) + { + $units[] = $unit; + $files = array_merge($files, mtg_get_file_deps($meta, $unit)); + } + $files = array_unique($files); + + $bundle = mtg_conf("bundle", null); + if($bundle && $units) + { + $targets[] = mtg_new_bundle($bundle, + array_merge($SHARED_DEPS, $files), + array(array($this, 'genBundle'), $meta, $codegen, $units)); + } + + return $targets; + } + + function genBundle($OUT, array $DEPS, mtgMetaInfo $meta, mtgCsCodegen $codegen, array $units) + { + $units_src = ''; + $id2type = ''; + $create_struct_by_crc28 = ''; + $create_rpc_by_id = ''; + + foreach($units as $unit) + { + if($unit->object instanceof mtgMetaRPC) + { + $units_src .= $codegen->genUnit($unit); + $rpc = $unit->object; + $create_rpc_by_id .= "\n case {$rpc->getCode()}: { return new {$rpc->getName()}(); };"; + } + else if($unit->object instanceof mtgMetaEnum || $unit->object instanceof mtgMetaStruct) + { + $units_src .= $codegen->genUnit($unit) . "\n"; + + if($unit->object->hasToken('POD') || $unit->object instanceof mtgMetaEnum) + continue; + + $id2type .= "\n case {$unit->object->getClassId()}: { return typeof({$unit->object->getName()}); };"; + $create_struct_by_crc28 .= "\n case {$unit->object->getClassId()}: { return new {$unit->object->getName()}(); };"; + } + } + + $templater = new mtg_cs_templater(); + $tpl = $templater->tpl_bundle(); + return mtg_fill_template($tpl, array( + '%namespace%' => $codegen->namespace, + '%units_src%' => $units_src, + '%id2type%' => $id2type, + '%create_struct_by_crc28%' => $create_struct_by_crc28, + '%create_rpc_by_id%' => $create_rpc_by_id, + )); + } +} + +function gen_cs_struct($OUT, array $DEPS, mtgCsCodegen $codegen, mtgMetaInfoUnit $unit) +{ + return $codegen->genUnit($unit); +} diff --git a/targets/cs/cs_tpl.inc.php b/targets/cs/cs_tpl.inc.php new file mode 100644 index 0000000..fc408a6 --- /dev/null +++ b/targets/cs/cs_tpl.inc.php @@ -0,0 +1,216 @@ + factory; + public MetaSyncContextOpts opts; + + public bool CanReadField(MetaSyncFieldOpts opts) + { + return is_read && (opts & MetaSyncFieldOpts.SKIP_NOT_IN_MASK) != MetaSyncFieldOpts.SKIP_NOT_IN_MASK; + } + + public bool CanWriteField(MetaSyncFieldOpts opts) + { + return !is_read && (opts & MetaSyncFieldOpts.SKIP_NOT_IN_MASK) != MetaSyncFieldOpts.SKIP_NOT_IN_MASK; + } + + public bool IsMaskUsed() + { + return (opts & MetaSyncContextOpts.USE_MASK) > 0; + } + + public static MetaSyncContext NewForRead(Func factory, IDataReader reader, MetaSyncContextOpts opts = 0) + { + var ctx = new MetaSyncContext() { + is_read = true, + reader = reader, + writer = null, + factory = factory, + opts = opts + }; + return ctx; + } + + public static MetaSyncContext NewForWrite(IDataWriter writer, MetaSyncContextOpts opts = 0) + { + var ctx = new MetaSyncContext() { + is_read = false, + reader = null, + writer = writer, + opts = opts + }; + return ctx; + } +} + +public interface IMetaStruct +{ + uint CLASS_ID(); + + int getFieldsCount(); + int getWritableFieldsCount(); + + void syncFields(MetaSyncContext ctx); + + void reset(); +} + +public interface IMetaCloneable +{ + void copy(IMetaStruct source); + IMetaStruct clone(); +} + +public abstract class BaseMetaStruct : IMetaStruct +{ + public virtual uint CLASS_ID() { return 0; } + + public virtual int getFieldsCount() { return 0; } + public virtual int getWritableFieldsCount() { return 0; } + + public virtual void reset() {} + + public virtual void syncFields(MetaSyncContext ctx) {} +} + +public interface IRpcError +{ + bool isOk(); +} + +public interface IRpc +{ + int getCode(); + IMetaStruct getRequest(); + IMetaStruct getResponse(); + IRpcError getError(); + void setError(IRpcError err); +} + +public interface IDataReader +{ + MetaIoError ReadI8(ref sbyte v); + MetaIoError ReadU8(ref byte v); + MetaIoError ReadI16(ref short v); + MetaIoError ReadU16(ref ushort v); + MetaIoError ReadI32(ref int v); + MetaIoError ReadU32(ref uint v); + MetaIoError ReadU64(ref ulong v); + MetaIoError ReadI64(ref long v); + MetaIoError ReadFloat(ref float v); + MetaIoError ReadBool(ref bool v); + MetaIoError ReadDouble(ref double v); + MetaIoError ReadString(ref string v); + MetaIoError ReadRaw(ref byte[] v, ref int vlen); + MetaIoError ReadNil(); + MetaIoError BeginContainer(); + MetaIoError GetContainerSize(ref int v); + MetaIoError EndContainer(); +} + +public interface IDataWriter +{ + MetaIoError WriteI8(sbyte v); + MetaIoError WriteU8(byte v); + MetaIoError WriteI16(short v); + MetaIoError WriteU16(ushort v); + MetaIoError WriteI32(int v); + MetaIoError WriteU32(uint v); + MetaIoError WriteI64(long v); + MetaIoError WriteU64(ulong v); + MetaIoError WriteFloat(float v); + MetaIoError WriteBool(bool v); + MetaIoError WriteDouble(double v); + MetaIoError WriteString(string v); + MetaIoError WriteRaw(byte[] v); + MetaIoError BeginContainer(int len); + MetaIoError EndContainer(); + MetaIoError End(); + MetaIoError WriteNil(); +} + +public static class Meta +{ + public delegate void LogCb(string text); + public static LogCb LogError = DefaultLog; + public static LogCb LogWarn = DefaultLog; + public static LogCb LogDebug = DefaultLog; + + public struct BitfieldsContext + { + long fields_mask; + int curr_field_idx; + + MetaSyncContext ctx; + + readonly bool use_mask; + + public BitfieldsContext(MetaSyncContext ctx, long fields_mask) + { + this.ctx = ctx; + this.use_mask = ctx.IsMaskUsed(); + this.fields_mask = fields_mask; + this.curr_field_idx = -1; + } + + public void SyncMaskHeader() + { + if(!use_mask) + return; + + if(ctx.is_read) + { + ensure(ctx.reader.ReadNil()); + ensure(ctx.reader.ReadI64(ref fields_mask)); + } + else + { + ensure(ctx.writer.WriteNil()); + ensure(ctx.writer.WriteI64(fields_mask)); + } + } + + public MetaSyncFieldOpts GetCurrentOpts() + { + if(!use_mask || curr_field_idx < 0 || curr_field_idx > 63) + return 0; + + long curr_field_ptr = 1L << curr_field_idx; + if((curr_field_ptr & fields_mask) == 0) + return MetaSyncFieldOpts.SKIP_NOT_IN_MASK; + + return 0; + } + + public MetaSyncFieldOpts GetNextOpts() + { + Advance(); + return GetCurrentOpts(); + } + + public void Advance() + { + curr_field_idx++; +#if DEBUG + if(curr_field_idx > 64) + { + LogError("overflow in @bitfields struct. Does it have > 64 fields?"); + throw new MetaException(MetaIoError.GENERIC); + } +#endif + } + } + + public const int MASK_HEADER_FIELDS_COUNT = 2; + + public static List tmp_enums_list = new List(); + + static void DefaultLog(string s) + {} + + public delegate string L_TCb(string text); + public static L_TCb L_T; + + public delegate string L_FromListCb(IList list); + public static L_FromListCb L_FromList; + + public delegate string L_PFromListCb(IList list, double force_n = double.NaN); + public static L_PFromListCb L_Pluralize; + + public delegate bool L_PListCheckCb(IList list, string plural_mark); + public static L_PListCheckCb L_IsPlural; + + public static void ensure(MetaIoError err) + { + if(err != 0) + throw new MetaException(err); + } + + static bool canSkipOptionalRead(MetaIoError err, MetaSyncFieldOpts opts) + { + return err != 0 && (opts & MetaSyncFieldOpts.SKIP_OPTIONAL) == MetaSyncFieldOpts.SKIP_OPTIONAL; + } + + static MetaIoError checkRead(MetaIoError err, MetaSyncFieldOpts opts) + { + if(canSkipOptionalRead(err, opts)) + { + LogWarn("Skipping optional field"); + return MetaIoError.NONE; + } + else + return err; + } + + public static void Sync(MetaSyncContext ctx, ref byte[] v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + { + int len = 0; + ensure(checkRead(ctx.reader.ReadRaw(ref v, ref len), opts)); + } + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteRaw(v)); + } + + public static void Sync(MetaSyncContext ctx, ref string v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + ensure(checkRead(ctx.reader.ReadString(ref v), opts)); + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteString(v)); + } + + public static void Sync(MetaSyncContext ctx, ref ObscuredString v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + { + string tmp = string.Empty; + ensure(checkRead(ctx.reader.ReadString(ref tmp), opts)); + v = tmp; + } + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteString(v)); + } + + public static void Sync(MetaSyncContext ctx, ref long v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + ensure(checkRead(ctx.reader.ReadI64(ref v), opts)); + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteI64(v)); + } + + public static void Sync(MetaSyncContext ctx, ref ObscuredLong v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + { + long tmp = 0; + ensure(checkRead(ctx.reader.ReadI64(ref tmp), opts)); + v = tmp; + } + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteI64(v)); + } + + public static void Sync(MetaSyncContext ctx, ref int v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + ensure(checkRead(ctx.reader.ReadI32(ref v), opts)); + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteI32(v)); + } + + public static void Sync(MetaSyncContext ctx, ref ObscuredInt v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + { + int tmp = 0; + ensure(checkRead(ctx.reader.ReadI32(ref tmp), opts)); + v = tmp; + } + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteI32(v)); + } + + public static void Sync(MetaSyncContext ctx, ref short v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + ensure(checkRead(ctx.reader.ReadI16(ref v), opts)); + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteI16(v)); + } + + public static void Sync(MetaSyncContext ctx, ref ObscuredShort v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + { + short tmp = 0; + ensure(checkRead(ctx.reader.ReadI16(ref tmp), opts)); + v = tmp; + } + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteI16(v)); + } + + public static void Sync(MetaSyncContext ctx, ref sbyte v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + ensure(checkRead(ctx.reader.ReadI8(ref v), opts)); + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteI8(v)); + } + + public static void Sync(MetaSyncContext ctx, ref ObscuredSByte v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + { + sbyte tmp = 0; + ensure(checkRead(ctx.reader.ReadI8(ref tmp), opts)); + v = tmp; + } + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteI8(v)); + } + + public static void Sync(MetaSyncContext ctx, ref ulong v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + ensure(checkRead(ctx.reader.ReadU64(ref v), opts)); + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteU64(v)); + } + + public static void Sync(MetaSyncContext ctx, ref ObscuredULong v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + { + ulong tmp = 0; + ensure(checkRead(ctx.reader.ReadU64(ref tmp), opts)); + v = tmp; + } + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteU64(v)); + } + + public static void Sync(MetaSyncContext ctx, ref ushort v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + ensure(checkRead(ctx.reader.ReadU16(ref v), opts)); + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteU16(v)); + } + + public static void Sync(MetaSyncContext ctx, ref ObscuredUShort v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + { + ushort tmp = 0; + ensure(checkRead(ctx.reader.ReadU16(ref tmp), opts)); + v = tmp; + } + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteU16(v)); + } + + public static void Sync(MetaSyncContext ctx, ref uint v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + ensure(checkRead(ctx.reader.ReadU32(ref v), opts)); + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteU32(v)); + } + + public static void Sync(MetaSyncContext ctx, ref ObscuredUInt v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + { + uint tmp = 0; + ensure(checkRead(ctx.reader.ReadU32(ref tmp), opts)); + v = tmp; + } + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteU32(v)); + } + + public static void Sync(MetaSyncContext ctx, ref byte v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + ensure(checkRead(ctx.reader.ReadU8(ref v), opts)); + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteU8(v)); + } + + public static void Sync(MetaSyncContext ctx, ref ObscuredByte v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + { + byte tmp = 0; + ensure(checkRead(ctx.reader.ReadU8(ref tmp), opts)); + v = tmp; + } + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteU8(v)); + } + + public static void Sync(MetaSyncContext ctx, ref bool v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + ensure(checkRead(ctx.reader.ReadBool(ref v), opts)); + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteBool(v)); + } + + public static void Sync(MetaSyncContext ctx, ref ObscuredBool v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + { + bool tmp = false; + ensure(checkRead(ctx.reader.ReadBool(ref tmp), opts)); + v = tmp; + } + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteBool(v)); + } + + public static void Sync(MetaSyncContext ctx, ref float v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + ensure(checkRead(ctx.reader.ReadFloat(ref v), opts)); + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteFloat(v)); + } + + public static void Sync(MetaSyncContext ctx, ref ObscuredFloat v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + { + float tmp = 0f; + ensure(checkRead(ctx.reader.ReadFloat(ref tmp), opts)); + v = tmp; + } + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteFloat(v)); + } + + public static void Sync(MetaSyncContext ctx, ref double v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + ensure(checkRead(ctx.reader.ReadDouble(ref v), opts)); + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteDouble(v)); + } + + public static void Sync(MetaSyncContext ctx, ref ObscuredDouble v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + { + double tmp = 0; + ensure(checkRead(ctx.reader.ReadDouble(ref tmp), opts)); + v = tmp; + } + else if(ctx.CanWriteField(opts)) + ensure(ctx.writer.WriteDouble(v)); + } + + static int SyncBeginListGetSize(MetaSyncContext ctx, IList v, MetaSyncFieldOpts opts) + { + if(ctx.CanReadField(opts)) + { + var err = ctx.reader.BeginContainer(); + if(canSkipOptionalRead(err, opts)) + { + LogWarn("Skipping optional array field"); + return -1; + } + ensure(err); + + int size = 0; + ensure(ctx.reader.GetContainerSize(ref size)); + + return size; + } + else if(ctx.CanWriteField(opts)) + { + int size = v == null ? 0 : v.Count; + ensure(ctx.writer.BeginContainer(size)); + return size; + } + else + return -1; + } + + static int SyncBeginList(MetaSyncContext ctx, List v, ref MetaSyncFieldOpts opts) + { + int size = SyncBeginListGetSize(ctx, v, opts); + if(size == -1) + return size; + + if(ctx.is_read && v.Capacity < size) + v.Capacity = size; + + //NOTE: unsetting opts for array items + opts &= ~MetaSyncFieldOpts.SKIP_OPTIONAL; + opts &= ~MetaSyncFieldOpts.SKIP_NOT_IN_MASK; + return size; + } + + static void SyncEndList(MetaSyncContext ctx, IList v) + { + if(ctx.is_read) + ensure(ctx.reader.EndContainer()); + else + ensure(ctx.writer.EndContainer()); + } + + public static void Sync(MetaSyncContext ctx, List v, MetaSyncFieldOpts opts = 0) + { + int size = SyncBeginList(ctx, v, ref opts); + if(size == -1) + return; + for(int i = 0; i < size; ++i) + { + var tmp = ctx.is_read ? "" : v[i]; + Sync(ctx, ref tmp, opts); + if(ctx.is_read) + v.Add(tmp); + } + SyncEndList(ctx, v); + } + + public static void Sync(MetaSyncContext ctx, List v, MetaSyncFieldOpts opts = 0) + { + int size = SyncBeginList(ctx, v, ref opts); + if(size == -1) + return; + for(int i = 0; i < size; ++i) + { + var tmp = ctx.is_read ? default(long) : v[i]; + Sync(ctx, ref tmp, opts); + if(ctx.is_read) + v.Add(tmp); + } + SyncEndList(ctx, v); + } + + public static void Sync(MetaSyncContext ctx, List v, MetaSyncFieldOpts opts = 0) + { + int size = SyncBeginList(ctx, v, ref opts); + if(size == -1) + return; + for(int i = 0; i < size; ++i) + { + var tmp = ctx.is_read ? default(int) : v[i]; + Sync(ctx, ref tmp, opts); + if(ctx.is_read) + v.Add(tmp); + } + SyncEndList(ctx, v); + } + + public static void Sync(MetaSyncContext ctx, List v, MetaSyncFieldOpts opts = 0) + { + int size = SyncBeginList(ctx, v, ref opts); + if(size == -1) + return; + for(int i = 0; i < size; ++i) + { + var tmp = ctx.is_read ? default(short) : v[i]; + Sync(ctx, ref tmp, opts); + if(ctx.is_read) + v.Add(tmp); + } + SyncEndList(ctx, v); + } + + public static void Sync(MetaSyncContext ctx, List v, MetaSyncFieldOpts opts = 0) + { + int size = SyncBeginList(ctx, v, ref opts); + if(size == -1) + return; + for(int i = 0; i < size; ++i) + { + var tmp = ctx.is_read ? default(sbyte) : v[i]; + Sync(ctx, ref tmp, opts); + if(ctx.is_read) + v.Add(tmp); + } + SyncEndList(ctx, v); + } + + public static void Sync(MetaSyncContext ctx, List v, MetaSyncFieldOpts opts = 0) + { + int size = SyncBeginList(ctx, v, ref opts); + if(size == -1) + return; + for(int i = 0; i < size; ++i) + { + var tmp = ctx.is_read ? default(ulong) : v[i]; + Sync(ctx, ref tmp, opts); + if(ctx.is_read) + v.Add(tmp); + } + SyncEndList(ctx, v); + } + + public static void Sync(MetaSyncContext ctx, List v, MetaSyncFieldOpts opts = 0) + { + int size = SyncBeginList(ctx, v, ref opts); + if(size == -1) + return; + for(int i = 0; i < size; ++i) + { + var tmp = ctx.is_read ? default(uint) : v[i]; + Sync(ctx, ref tmp, opts); + if(ctx.is_read) + v.Add(tmp); + } + SyncEndList(ctx, v); + } + + public static void Sync(MetaSyncContext ctx, List v, MetaSyncFieldOpts opts = 0) + { + int size = SyncBeginList(ctx, v, ref opts); + if(size == -1) + return; + for(int i = 0; i < size; ++i) + { + var tmp = ctx.is_read ? default(ushort) : v[i]; + Sync(ctx, ref tmp, opts); + if(ctx.is_read) + v.Add(tmp); + } + SyncEndList(ctx, v); + } + + public static void Sync(MetaSyncContext ctx, List v, MetaSyncFieldOpts opts = 0) + { + int size = SyncBeginList(ctx, v, ref opts); + if(size == -1) + return; + for(int i = 0; i < size; ++i) + { + var tmp = ctx.is_read ? default(byte) : v[i]; + Sync(ctx, ref tmp, opts); + if(ctx.is_read) + v.Add(tmp); + } + SyncEndList(ctx, v); + } + + public static void Sync(MetaSyncContext ctx, List v, MetaSyncFieldOpts opts = 0) + { + int size = SyncBeginList(ctx, v, ref opts); + if(size == -1) + return; + for(int i = 0; i < size; ++i) + { + var tmp = ctx.is_read ? default(bool) : v[i]; + Sync(ctx, ref tmp, opts); + if(ctx.is_read) + v.Add(tmp); + } + SyncEndList(ctx, v); + } + + public static void Sync(MetaSyncContext ctx, List v, MetaSyncFieldOpts opts = 0) + { + int size = SyncBeginList(ctx, v, ref opts); + if(size == -1) + return; + for(int i = 0; i < size; ++i) + { + var tmp = ctx.is_read ? default(float) : v[i]; + Sync(ctx, ref tmp, opts); + if(ctx.is_read) + v.Add(tmp); + } + SyncEndList(ctx, v); + } + + public static void Sync(MetaSyncContext ctx, List v, MetaSyncFieldOpts opts = 0) + { + int size = SyncBeginList(ctx, v, ref opts); + if(size == -1) + return; + for(int i = 0; i < size; ++i) + { + var tmp = ctx.is_read ? default(double) : v[i]; + Sync(ctx, ref tmp, opts); + if(ctx.is_read) + v.Add(tmp); + } + SyncEndList(ctx, v); + } + + public static void Sync(MetaSyncContext ctx, List v, MetaSyncFieldOpts opts = 0) where T : IMetaStruct, new() + { + int size = SyncBeginList(ctx, v, ref opts); + if(size == -1) + return; + + if(ctx.is_read) + v.Clear(); + + for(int i = 0; i < size; ++i) + { + var tmp = ctx.is_read ? new T() : v[i]; + Sync(ctx, ref tmp, opts); + if(ctx.is_read) + v.Add(tmp); + } + SyncEndList(ctx, v); + } + + public static IMetaStruct SyncGeneric(MetaSyncContext ctx, IMetaStruct v, MetaSyncFieldOpts opts = 0) + { + if(ctx.CanReadField(opts)) + { + ensure(ctx.reader.BeginContainer()); + + uint clid = 0; + ensure(ctx.reader.ReadU32(ref clid)); + + //NOTE: when reading we ignore the passed argument + v = ctx.factory(clid); + if(v == null) + { + LogError("Could not create struct: " + clid); + ensure(MetaIoError.TYPE_DONT_MATCH); + } + + v.reset(); + v.syncFields(ctx); + ensure(ctx.reader.EndContainer()); + } + else if(ctx.CanWriteField(opts)) + { + ensure(ctx.writer.BeginContainer(v.getFieldsCount() + 1)); + ensure(ctx.writer.WriteU32(v.CLASS_ID())); + v.syncFields(ctx); + ensure(ctx.writer.EndContainer()); + + } + return v; + } + + public static void SyncGeneric(MetaSyncContext ctx, List v, MetaSyncFieldOpts opts = 0) where T : IMetaStruct, new() + { + int size = SyncBeginList(ctx, v, ref opts); + if(size == -1) + return; + for(int i = 0; i < size; ++i) + { + var tmp = SyncGeneric(ctx, ctx.is_read ? default(T) : v[i]); + if(ctx.is_read) + v.Add((T)tmp); + } + SyncEndList(ctx, v); + } + + public static void Sync(MetaSyncContext ctx, ref T v, MetaSyncFieldOpts opts = 0) where T : IMetaStruct + { + if(ctx.CanReadField(opts)) + { + var err = ctx.reader.BeginContainer(); + if(canSkipOptionalRead(err, opts)) + { + LogWarn("Skipping optional struct field"); + return; + } + + ensure(err); + v.reset(); + v.syncFields(ctx); + ensure(ctx.reader.EndContainer()); + } + else if(ctx.CanWriteField(opts)) + { + int array_size = getWriteFieldsCount(ctx, v); + ensure(ctx.writer.BeginContainer(array_size)); + v.syncFields(ctx); + ensure(ctx.writer.EndContainer()); + } + } + + public static MetaIoError SyncSafe(MetaSyncContext ctx, ref T v, MetaSyncFieldOpts opts = 0) where T : IMetaStruct + { + try + { + Sync(ctx, ref v, opts); + } + catch(MetaException e) + { + LogError(e.Message + " " + e.StackTrace); + return e.err; + } + catch(Exception e) + { + LogError(e.Message + " " + e.StackTrace); + return MetaIoError.GENERIC; + } + return MetaIoError.NONE; + } + + static int getWriteFieldsCount(MetaSyncContext ctx, IMetaStruct v) + { + if(ctx.IsMaskUsed()) + return v.getWritableFieldsCount(); + else + return v.getFieldsCount(); + } + + public static MetaSyncContext PrepareForClone(ref T src) where T : IMetaStruct + { + var stream = new MemoryStream(); + + var ctx = new MetaSyncContext() { + is_read = false, + reader = new MsgPackDataReader(stream), + writer = new MsgPackDataWriter(stream), + opts = 0 + }; + + Sync(ctx, ref src); + + ctx.is_read = true; + stream.Position = 0; + + return ctx; + } + + //Shared mask utils below + + public static int GetDirtyFieldsCount(long fields_mask, int fields_count) + { + int changed_fields = 0; + long ptr = 1L; + for(int i=0; i(ref List v) + { + if(v == null) + v = new List(); + v.Clear(); + } + + public static string I18NPick(string raw_val, List list_val, string plural_marker, double pluralize_for_n) + { + if(raw_val.Length != 0) + return Meta.L_T(raw_val); + + if(Meta.L_IsPlural(list_val, plural_marker)) + return Meta.L_Pluralize(list_val, pluralize_for_n); + + return Meta.L_FromList(list_val); + } + + public static void Reset(ref T v) where T : IMetaStruct, new() + { + if(v == null) + v = new T(); + v.reset(); + } +} + +public class MsgPackDataWriter : IDataWriter +{ + Stream stream; + MsgPackWriter io; + Stack space = new Stack(); + + public MsgPackDataWriter(Stream _stream) + { + reset(_stream); + } + + public void reset(Stream _stream) + { + stream = _stream; + io = new MsgPackWriter(stream); + space.Clear(); + space.Push(1); + } + + MetaIoError decSpace() + { + if(space.Count == 0) + return MetaIoError.NO_SPACE_LEFT_IN_ARRAY; + + int left = space.Pop(); + left--; + if(left < 0) + return MetaIoError.NO_SPACE_LEFT_IN_ARRAY; + + space.Push(left); + return 0; + } + + public MetaIoError BeginContainer(int size) + { + MetaIoError err = decSpace(); + if(err != 0) + return err; + + space.Push(size); + io.WriteArrayHeader(size); + return 0; + } + + public MetaIoError EndContainer() + { + if(space.Count <= 1) + return MetaIoError.ARRAY_STACK_IS_EMPTY; + + int left = space.Pop(); + if(left != 0) + return MetaIoError.HAS_LEFT_SPACE; + + return 0; + } + + public MetaIoError End() + { + if(space.Count != 1) + return MetaIoError.ARRAY_STACK_IS_EMPTY; + + return 0; + } + + public MetaIoError WriteI8(sbyte v) + { + io.Write(v); + return decSpace(); + } + + public MetaIoError WriteU8(byte v) + { + io.Write(v); + return decSpace(); + } + + public MetaIoError WriteI16(short v) + { + io.Write(v); + return decSpace(); + } + + public MetaIoError WriteU16(ushort v) + { + io.Write(v); + return decSpace(); + } + + public MetaIoError WriteI32(int v) + { + io.Write(v); + return decSpace(); + } + + public MetaIoError WriteU32(uint v) + { + io.Write(v); + return decSpace(); + } + + public MetaIoError WriteU64(ulong v) + { + io.Write(v); + return decSpace(); + } + + public MetaIoError WriteI64(long v) + { + io.Write(v); + return decSpace(); + } + + public MetaIoError WriteBool(bool v) + { + io.Write(v); + return decSpace(); + } + + public MetaIoError WriteFloat(float v) + { + io.Write(v); + return decSpace(); + } + + public MetaIoError WriteDouble(double v) + { + io.Write(v); + return decSpace(); + } + + public MetaIoError WriteString(string v) + { + if(v == null) + io.Write(""); + else + io.Write(v); + + return decSpace(); + } + + public MetaIoError WriteNil() + { + io.WriteNil(); + + return decSpace(); + } + + public MetaIoError WriteRaw(byte[] v) + { + io.Write(v); + + return decSpace(); + } +} + +public class MsgPackDataReader : IDataReader +{ + Stream stream; + MsgPackReader io; + + public struct ContainerPosition + { + public int max; + public int curr; + + public ContainerPosition(int length) + { + curr = 0; + max = length - 1; + } + } + + Stack stack = new Stack(); + + public MsgPackDataReader(Stream _stream) + { + reset(_stream); + } + + public void reset(Stream _stream) + { + stream = _stream; + stack.Clear(); + io = new MsgPackReader(stream); + } + + public void setPos(long pos) + { + stream.Position = pos; + stack.Clear(); + } + + int nextInt(ref MetaIoError err) + { + if(!ContainerPositionValid()) + { + err = MetaIoError.DATA_MISSING; + return 0; + } + + if(!io.Read()) + { + err = MetaIoError.FAIL_READ; + return 0; + } + + ContainerPositionMoveNext(); + + if(io.IsSigned()) + return io.ValueSigned; + else if(io.IsUnsigned()) + return (int)io.ValueUnsigned; + else + { + Meta.LogWarn("Got type: " + io.Type); + err = MetaIoError.TYPE_DONT_MATCH; + return 0; + } + } + + uint nextUint(ref MetaIoError err) + { + if(!ContainerPositionValid()) + { + err = MetaIoError.DATA_MISSING; + return 0; + } + + if(!io.Read()) + { + err = MetaIoError.FAIL_READ; + return 0; + } + + ContainerPositionMoveNext(); + + if(io.IsUnsigned()) + return io.ValueUnsigned; + else if(io.IsSigned()) + return (uint)io.ValueSigned; + else + { + Meta.LogWarn("Got type: " + io.Type); + err = MetaIoError.TYPE_DONT_MATCH; + return 0; + } + } + + bool nextBool(ref MetaIoError err) + { + if(!ContainerPositionValid()) + { + err = MetaIoError.DATA_MISSING; + return false; + } + + if(!io.Read()) + { + err = MetaIoError.FAIL_READ; + return false; + } + + ContainerPositionMoveNext(); + + if(io.IsBoolean()) + return (bool)io.ValueBoolean; + else if(io.IsUnsigned()) + return io.ValueUnsigned != 0; + else if(io.IsSigned()) + return (uint)io.ValueSigned != 0; + else + { + Meta.LogWarn("Got type: " + io.Type); + err = MetaIoError.TYPE_DONT_MATCH; + return false; + } + } + + ulong nextUint64(ref MetaIoError err) + { + if(!ContainerPositionValid()) + { + err = MetaIoError.DATA_MISSING; + return 0; + } + + if(!io.Read()) + { + err = MetaIoError.FAIL_READ; + return 0; + } + + ContainerPositionMoveNext(); + + if(io.IsUnsigned()) + return io.ValueUnsigned; + else if(io.IsSigned()) + return (ulong)io.ValueSigned; + else if(io.IsUnsigned64()) + return io.ValueUnsigned64; + else if(io.IsSigned64()) + return (ulong)io.ValueSigned64; + else + { + Meta.LogWarn("Got type: " + io.Type); + err = MetaIoError.TYPE_DONT_MATCH; + return 0; + } + } + + long nextInt64(ref MetaIoError err) + { + if(!ContainerPositionValid()) + { + err = MetaIoError.DATA_MISSING; + return 0; + } + + if(!io.Read()) + { + err = MetaIoError.FAIL_READ; + return 0; + } + + ContainerPositionMoveNext(); + + if(io.IsUnsigned()) + return (long)io.ValueUnsigned; + else if(io.IsSigned()) + return io.ValueSigned; + else if(io.IsUnsigned64()) + return (long)io.ValueUnsigned64; + else if(io.IsSigned64()) + return io.ValueSigned64; + else + { + Meta.LogWarn("Got type: " + io.Type); + err = MetaIoError.TYPE_DONT_MATCH; + return 0; + } + } + + public MetaIoError ReadI8(ref sbyte v) + { + MetaIoError err = 0; + v = (sbyte) nextInt(ref err); + return err; + } + + public MetaIoError ReadI16(ref short v) + { + MetaIoError err = 0; + v = (short) nextInt(ref err); + return err; + } + + public MetaIoError ReadI32(ref int v) + { + MetaIoError err = 0; + v = nextInt(ref err); + return err; + } + + public MetaIoError ReadU8(ref byte v) + { + MetaIoError err = 0; + v = (byte) nextUint(ref err); + return err; + } + + public MetaIoError ReadU16(ref ushort v) + { + MetaIoError err = 0; + v = (ushort) nextUint(ref err); + return err; + } + + public MetaIoError ReadU32(ref uint v) + { + MetaIoError err = 0; + v = nextUint(ref err); + return err; + } + + public MetaIoError ReadU64(ref ulong v) + { + MetaIoError err = 0; + v = nextUint64(ref err); + return err; + } + + public MetaIoError ReadI64(ref long v) + { + MetaIoError err = 0; + v = nextInt64(ref err); + return err; + } + + public MetaIoError ReadBool(ref bool v) + { + MetaIoError err = 0; + v = nextBool(ref err); + return err; + } + + public MetaIoError ReadFloat(ref float v) + { + if(!ContainerPositionValid()) + return MetaIoError.DATA_MISSING; + + if(!io.Read()) + { + return MetaIoError.FAIL_READ; + } + + if(io.IsUnsigned()) + { + v = io.ValueUnsigned; + } + else if(io.IsSigned()) + { + v = io.ValueSigned; + } + else + { + switch(io.Type) + { + case TypePrefixes.Float: + v = io.ValueFloat; + break; + case TypePrefixes.Double: + var tmp = io.ValueDouble; + //TODO: + //if(tmp > float.MaxValue || tmp < float.MinValue) + //{ + // Meta.LogWarn("Double -> Float bad conversion: " + tmp); + // return MetaIoError.TYPE_DONT_MATCH; + //} + v = (float) tmp; + break; + default: + Meta.LogWarn("Got type: " + io.Type); + return MetaIoError.TYPE_DONT_MATCH; + } + } + ContainerPositionMoveNext(); + return 0; + } + + public MetaIoError ReadDouble(ref double v) + { + if(!ContainerPositionValid()) + return MetaIoError.DATA_MISSING; + + if(!io.Read()) + { + return MetaIoError.FAIL_READ; + } + + if(io.IsUnsigned()) + { + v = io.ValueUnsigned; + } + else if(io.IsSigned()) + { + v = io.ValueSigned; + } + else + { + switch(io.Type) + { + case TypePrefixes.Float: + v = (double) io.ValueFloat; + break; + case TypePrefixes.Double: + v = io.ValueDouble; + break; + case TypePrefixes.UInt64: + v = (double) io.ValueUnsigned64; + break; + case TypePrefixes.Int64: + v = (double) io.ValueSigned64; + break; + default: + Meta.LogWarn("Got type: " + io.Type); + return MetaIoError.TYPE_DONT_MATCH; + } + } + ContainerPositionMoveNext(); + return 0; + } + + public MetaIoError ReadRaw(ref byte[] v, ref int vlen) + { + if(!ContainerPositionValid()) + return MetaIoError.DATA_MISSING; + + if(!io.Read()) + return MetaIoError.FAIL_READ; + + if(!io.IsRaw()) + { + Meta.LogWarn("Got type: " + io.Type); + return MetaIoError.TYPE_DONT_MATCH; + } + + vlen = (int)io.Length; + if(v == null) + v = new byte[vlen]; + else if(v.Length < vlen) + Array.Resize(ref v, vlen); + io.ReadValueRaw(v, 0, vlen); + ContainerPositionMoveNext(); + return 0; + } + + public MetaIoError ReadNil() + { + if(!ContainerPositionValid()) + return MetaIoError.DATA_MISSING; + + if(!io.Read()) + return MetaIoError.FAIL_READ; + + if(io.Type != TypePrefixes.Nil) + { + Meta.LogWarn("Got type: " + io.Type); + return MetaIoError.TYPE_DONT_MATCH; + } + + ContainerPositionMoveNext(); + return 0; + } + + public MetaIoError ReadString(ref string v) + { + if(!ContainerPositionValid()) + return MetaIoError.DATA_MISSING; + + if(!io.Read()) + return MetaIoError.FAIL_READ; + + if(!io.IsRaw()) + { + Meta.LogWarn("Got type: " + io.Type); + return MetaIoError.TYPE_DONT_MATCH; + } + + //TODO: use shared buffer for strings loading + byte[] strval = new byte[io.Length]; + io.ReadValueRaw(strval, 0, strval.Length); + v = System.Text.Encoding.UTF8.GetString(strval); + ContainerPositionMoveNext(); + return 0; + } + + public MetaIoError BeginContainer() + { + if(!ContainerPositionValid()) + return MetaIoError.DATA_MISSING; + + if(!io.Read()) + return MetaIoError.FAIL_READ; + + if(!io.IsArray()) + { + Meta.LogWarn("Got type: " + io.Type); + return MetaIoError.TYPE_DONT_MATCH; + } + + ContainerPosition ap = new ContainerPosition((int)io.Length); + stack.Push(ap); + return 0; + } + + public MetaIoError GetContainerSize(ref int v) + { + if(!io.IsArray()) + { + Meta.LogWarn("Got type: " + io.Type); + return MetaIoError.TYPE_DONT_MATCH; + } + + v = (int) io.Length; + return 0; + } + + void SkipField() + { + if(!io.Read()) + return; + + if(!io.IsArray() && !io.IsMap()) + { + //NOTE: if value is a raw string we need to read all of its data + if(io.IsRaw()) + io.ReadRawString(); + return; + } + + uint array_len = io.Length; + for(uint i=0; i -1) + { + SkipField(); + ContainerPositionMoveNext(); + } + } + + public MetaIoError EndContainer() + { + SkipTrailingFields(); + stack.Pop(); + ContainerPositionMoveNext(); + return 0; + } + + int ContainerEntriesLeft() + { + if(stack.Count == 0) + return 0; + ContainerPosition ap = stack.Peek(); + return ap.max - ap.curr; + } + + bool ContainerPositionValid() + { + return ContainerEntriesLeft() >= 0; + } + + void ContainerPositionMoveNext() + { + if(stack.Count > 0) + { + ContainerPosition ap = stack.Pop(); + ap.curr++; + stack.Push(ap); + } + } +} + +} //namespace metagen diff --git a/targets/go/go.inc.php b/targets/go/go.inc.php new file mode 100644 index 0000000..1e546a6 --- /dev/null +++ b/targets/go/go.inc.php @@ -0,0 +1,628 @@ +object; + if($obj instanceof mtgMetaRPC) + return $this->genRPC($obj); + if($obj instanceof mtgMetaEnum) + return $this->genEnum($obj); + else if($obj instanceof mtgMetaStruct) + return $this->genStruct($obj); + else + { + echo "WARN: skipping meta unit '{$obj->getId()}'\n"; + return ''; + } + } + + function genEnum(mtgMetaEnum $struct) + { + $templater = new mtg_go_templater(); + + $repl = array(); + $repl['%class%'] = $struct->getName(); + $repl['%class_id%'] = $struct->getClassId(); + + $tpl = $templater->tpl_enum(); + + $this->fillEnumRepls($struct, $repl); + + return mtg_fill_template($tpl, $repl); + } + + function fillEnumRepls(mtgMetaEnum $enum, &$repl) + { + $repl['%values%'] = ''; + $repl['%vnames_list%'] = ''; + $repl['%values_list%'] = ''; + + $vnames = array(); + $values = array(); + $values_map = array(); + $default_value = null; + $consts = array(); + + $enum_name = $enum->getName(); + $map = array(); + foreach($enum->getValues() as $vname => $value) + { + $map[$vname] = $value; + $vnames[] = "'$vname'"; + $values[] = $value; + if(!$default_value) + $default_value = "$value; // $vname"; + $consts[$value] = "{$enum->getName()}_{$vname} {$enum->getName()} = $value"; + } + + // must be sorted + sort($values, SORT_NUMERIC); + + $repl['%vnames_list%'] = implode(',', $vnames); + $repl['%values_list%'] = implode(',', $values); + $repl['%values_map%'] = $this->genIntMap($map); + $repl['%default_enum_value%'] = $default_value; + $repl['%consts%'] = "\n " . implode("\n ", $consts) . "\n"; + } + + function preprocessField(mtgMetaStruct $struct, mtgMetaField $field) {} + + function genRPC(mtgMetaRPC $rpc) + { + return $this->genRPCPacket($rpc->getReq()) . + $this->genRPCPacket($rpc->getRsp()) . + $this->genRPCReqResp($rpc); + } + + function genRPCReqResp(mtgMetaRPC $rpc) + { + $templater = new mtg_go_templater(); + + $repl = array(); + $repl['%code%'] = $rpc->getCode(); + $repl['%rpc_name%'] = $rpc->getName(); + $repl['%rpc_req_name%'] = $rpc->getReq()->getName(); + $repl['%rpc_rsp_name%'] = $rpc->getRsp()->getName(); + + $tpl = $templater->tpl_rpc(); + return mtg_fill_template($tpl, $repl); + } + + function genRPCPacket(mtgMetaPacket $packet) + { + $templater = new mtg_go_templater(); + + $repl = array(); + $repl['%type%'] = $packet->getName(); + $repl['%code%'] = $packet->getNumericCode(); + $repl['%class%'] = $packet->getName(); + $repl['%class_id%'] = $packet->getClassId(); + $repl['%parent_class%'] = ''; + + if(strpos($packet->getName(), '_RSP_') !== false) + $repl['%class_invert%'] = str_replace('_RSP_', '_REQ_', $packet->getName()); + else + $repl['%class_invert%'] = str_replace('_REQ_', '_RSP_', $packet->getName()); + + $fields = $packet->getFields(); + + $tpl = $templater->tpl_packet(); + + $this->fillStructRepls($packet, $repl); + + return mtg_fill_template($tpl, $repl); + } + + function genStruct(mtgMetaStruct $struct) + { + $templater = new mtg_go_templater(); + + $repl = array(); + $repl['%class%'] = $struct->getName(); + $repl['%class_id%'] = $struct->getClassId(); + $repl['%parent_class%'] = $struct->getParent() ? ''.$struct->getParent() : ""; + + $tpl = $templater->tpl_struct(); + + $this->fillStructRepls($struct, $repl); + + $result = mtg_fill_template($tpl, $repl); + + if($struct->hasToken('POD') && $struct->hasToken("id") && $struct->hasToken("table") && $struct->hasToken("owner")) + $result .= "\n" . str_replace(array_keys($repl), array_values($repl), $templater->tpl_collection_item()); + return $result; + } + + function fillStructRepls(mtgMetaStruct $struct, &$repl) + { + foreach($struct->getFields() as $field) + $this->preprocessField($struct, $field); + + $repl['%includes%'] = ''; + $repl['%fields%'] = ''; + $repl['%fields_names%'] = '[]string{'; + $repl['%fields_props%'] = 'map[string]map[string]string{'; + $repl['%fields_types%'] = 'map[string]string{'; + $repl['%fields_reset%'] = ''; + $repl['%read_buffer%'] = ''; + $repl['%write_buffer%'] = ''; + $repl['%total_top_fields%'] = sizeof($struct->getFields()); + $repl['%ext_methods%'] = ""; + $repl['%analytics_methods%'] = ""; + + $repl['%class_props%'] = 'map[string]string{' . $this->genStrMap($struct->getTokens()) . '}'; + + if($struct->hasToken("bitfields")) + { + $repl['%read_buffer%'] .= "\n use_mask, mask, err := reader.TryReadMask()\n"; + $repl['%read_buffer%'] .= "\n if err != nil {\n"; + $repl['%read_buffer%'] .= "\n return err\n"; + $repl['%read_buffer%'] .= "\n }\n"; + $repl['%read_buffer%'] .= "\n self.fieldsMask = mask\n"; + } + + $parent = $struct->getParent(); + $all_fields = mtg_get_all_fields($this->info, $struct); + + foreach($all_fields as $field) + { + $name = $field->getName(); + $repl['%fields_names%'] .= '"' . $name . '",'; + $repl['%fields_props%'] .= '"' . $this->genFieldName($field) . '" : map[string]string{' . $this->genStrMap($field->getTokens()) . "},"; + $field_type = $field->getType(); + $repl['%fields_types%'] .= '"' . $name . '" : "' . $field_type . '",'; + } + if($struct->hasToken('statist')) + { + $table_name = $struct->getToken('statist'); + + $repl['%analytics_methods%'] .= "func (self *".$struct->getName().") Table() string {\n"; + $repl['%analytics_methods%'] .= "\t return \"$table_name\"\n"; + $repl['%analytics_methods%'] .= "}\n"; + + $repl['%analytics_methods%'] .= "func (self *".$struct->getName().") Columns() []string {\n"; + $repl['%analytics_methods%'] .= "\treturn []string{\n"; + foreach($all_fields as $field) + { + if ($field->hasToken('statist_skip')) + continue; + + $column_name = $field->hasToken('statist_alias') ? $field->getToken('statist_alias') : $field->getName(); + + $repl['%analytics_methods%'] .= "\t\t\"$column_name\",\n"; + } + $repl['%analytics_methods%'] .= "\t}\n"; + $repl['%analytics_methods%'] .= "}\n"; + + $repl['%analytics_methods%'] .= "func (self *".$struct->getName().") Values() []interface{} {\n"; + $repl['%analytics_methods%'] .= "\treturn []interface{}{\n"; + foreach($all_fields as $field) + { + if ($field->hasToken('statist_skip')) + continue; + + $repl['%analytics_methods%'] .= "\t\tself." . ucfirst($field->getName()) . ",\n"; + } + $repl['%analytics_methods%'] .= "\t}\n"; + $repl['%analytics_methods%'] .= "}\n"; + } + + $repl['%read_buffer%'] .= "\n _cont_size, err := reader.GetContainerSize()"; + $repl['%read_buffer%'] .= "\n if err != nil {"; + $repl['%read_buffer%'] .= "\n return err"; + $repl['%read_buffer%'] .= "\n }"; + + $optional = 0; + foreach($struct->getFields() as $field) + { + if($field->hasToken('optional')) + $optional++; + } + $initial_fields_amount = count($struct->getFields()) - $optional; + $repl['%read_buffer%'] .= "\n if _cont_size < $initial_fields_amount {"; + $repl['%read_buffer%'] .= "\n _cont_size = $initial_fields_amount"; + $repl['%read_buffer%'] .= "\n }"; + + if($struct->hasToken('POD')) + { + $fields = $all_fields; + } + else + { + if($parent = $struct->getParent()) + { + $repl['%read_buffer%'] .= "\n if err := self." . $parent . ".ReadFields(reader); err != nil { return err }"; + $repl['%write_buffer%'] .= "\n if err := self." . $parent . ".WriteFields(writer); err != nil { return err }"; + } + $fields = $struct->getFields(); + } + + $field_index = -1; + foreach($fields as $field) + { + ++$field_index; + $options = $this->readFieldOptions($field); + + $repl['%fields%'] .= "\n " . $this->genFieldDecl($field); + $repl['%fields_reset%'] .= "\n " . $this->genFieldReset($field); + $repl['%read_buffer%'] .= "\n if _cont_size <= 0 {"; + $repl['%read_buffer%'] .= "\n return nil"; + $repl['%read_buffer%'] .= "\n }"; + if($struct->hasToken("bitfields")) + $repl['%read_buffer%'] .= "\n if !use_mask {"; + $repl['%read_buffer%'] .= "\n _cont_size--"; + if($struct->hasToken("bitfields")) + $repl['%read_buffer%'] .= "\n }"; + if($struct->hasToken("bitfields")) + $repl['%read_buffer%'] .= "\n if !use_mask || (use_mask && self.HasValue($field_index)) {"; + $repl['%read_buffer%'] .= "\n " . $this->genBufRead($field->getName(), $options['fname'], $field->getType(), $options['buf'], $field->getTokens()); + if($struct->hasToken("bitfields")) + $repl['%read_buffer%'] .= "\n }"; + $repl['%write_buffer%'] .= "\n " . $this->genBufWrite($field, "writer", $field->hasToken("virtual")); + } + + $repl['%fields%'] = trim($repl['%fields%'], "\n "); + + $repl['%fields_names%'] .= '}'; + $repl['%fields_props%'] .= '}'; + $repl['%fields_types%'] .= '}'; + + $repl['%import_from_mysql%'] = ''; + $repl["%export_to_arr%"] = ""; + if($struct->hasToken('POD') && $struct->hasToken("table")) + { + $ind = 0; + + $repl['%import_from_mysql%'] .= "\n row := data.(".$struct->getName().")"; + foreach($all_fields as $field) + { + $name = $this->genFieldName($field); + $repl['%import_from_mysql%'] .= sprintf("\n self.%s = row.%s", $name, $name); + $repl['%export_to_arr%'] .= sprintf("\n data[%d] = self.%s", $ind, $name); + $ind++; + } + } + + $repl["%table_name%"] = $struct->getToken("table"); + $repl["%owner%"] = $struct->getToken("owner"); + $repl["%id_field_name%"] = $struct->getToken("id"); + $repl["%id_field%"] = ucfirst($struct->getToken("id")); + + if($struct->hasToken("bitfields")) + { + $repl['%fields%'] .= "\nfieldsMask uint64\n"; + + $repl['%fields_reset%'] .= "\nself.fieldsMask = 0\n"; + + $repl['%ext_methods%'] .= "func(self *".$struct->getName().") HasValue(index uint64) bool {\n"; + $repl['%ext_methods%'] .= " value := uint64(1 << index)\n"; + $repl['%ext_methods%'] .= " return (self.fieldsMask & value) > 0\n"; + $repl['%ext_methods%'] .= "}\n"; + + $repl['%ext_methods%'] .= "func(self *".$struct->getName().") SetFieldChanged(index uint64) {\n"; + $repl['%ext_methods%'] .= " value := uint64(1 << index)\n"; + $repl['%ext_methods%'] .= " self.fieldsMask |= value\n"; + $repl['%ext_methods%'] .= "}\n"; + + $repl['%ext_methods%'] .= "func(self *".$struct->getName().") IsMaskFilled() bool {\n"; + $repl['%ext_methods%'] .= " return self.fieldsMask > 0\n"; + $repl['%ext_methods%'] .= "}\n"; + + $repl['%ext_methods%'] .= "func(self *".$struct->getName().") GetMask() uint64 {\n"; + $repl['%ext_methods%'] .= " return self.fieldsMask\n"; + $repl['%ext_methods%'] .= "}\n"; + } + } + + function readFieldOptions(mtgMetaField $field) + { + return array( + 'fname' => 'self.'.ucfirst($field->getName()), + 'buf' => "reader", + ); + } + + function genReadBuiltinField($name, $fname, mtgType $type, $buf, array $tokens) + { + return $this->genRead($tokens, $buf.'.Read'.$this->genBuiltinTypePrefix($type).'(&'.$fname.', "'.$name.'")'); + } + + function genBufRead($name, $fname, mtgType $type, $buf, array $tokens = array(), $is_ptr = false) + { + $str = ''; + + if($type instanceof mtgBuiltinType) + { + $str .= $this->genReadBuiltinField($name, $fname, $type, $buf, $tokens); + } + else if($type instanceof mtgMetaEnum) + { + $str .= $this->genRead($tokens, "{$buf}.ReadI32((*int32)(&{$fname}), \"$name\")"); + $str .= "\n if !{$fname}.IsValid() { return errors.Errorf(\"Bad enum value %d for $name\", $fname) }"; + } + else if($type instanceof mtgMetaStruct) + { + if(array_key_exists('virtual', $tokens)) + $str .= "if v, err := meta.ReadStructGeneric($buf, CreateById, \"{$name}\"); err != nil { return err } else { {$fname} = v.(I{$type}) }"; + else + $str .= $this->genRead($tokens, "meta.ReadStruct($buf, ".($is_ptr?"":"&")."$fname, \"$name\")"); + } + else if($type instanceof mtgArrType) + { + $is_virtual = array_key_exists("virtual", $tokens); + $native_type = $this->genNativeType($type->getValue(), $tokens); + $offset = "\n "; + $str .= "/*[]{$name}*/"; + $str .= $offset . $this->genRead($tokens, "{$buf}.BeginContainer(\"$name\")"); + //we don't want to propagate 'optionalness' below + unset($tokens['optional']); + $str .= $offset . "_{$name}_size, err := {$buf}.GetContainerSize()"; + $str .= $offset . "if err != nil { return err }"; + + $need_new = !$is_virtual && $type->getValue() instanceof mtgMetaStruct; + + $str .= $offset . "for ; _{$name}_size > 0; _{$name}_size-- {"; + $offset = "\n "; + $str .= $offset . "var tmp_{$name} {$native_type}"; + + $str .= $offset . $this->genBufRead("", "tmp_{$name}", $type->getValue(), $buf, $tokens, $is_virtual); + $str .= $offset . "{$fname} = append({$fname}, ".($need_new?"&":"")."tmp_{$name})"; + + $offset = "\n "; + $str .= $offset . "}"; + $str .= $offset . $this->genRead($tokens, "{$buf}.EndContainer()"); + $str .= "\n"; + } + else + throw new Exception("Unknown type '{$type}'"); + + return $str; + } + + function genRead(array $tokens, $op) + { + return "if err := $op; err != nil { return " . (array_key_exists("optional", $tokens) ? "/*optional*/nil" : "err"). " }"; + } + + function genWrite($op) + { + return "if err := $op; err != nil { return err }"; + } + + function genBufWrite(mtgMetaField $field, $buf, $is_ptr = false) + { + return $this->genBufWriteEx($field->getName(), "self.".ucfirst($field->getName()), $field->getType(), $buf, $field->getTokens(), $is_ptr); + } + + function genBufWriteEx($name, $fname, mtgType $type, $buf, array $tokens = array(), $is_ptr = false) + { + $str = ''; + + if($type instanceof mtgBuiltinType) + { + $str .= $this->genWrite("{$buf}.Write".$this->genBuiltinTypePrefix($type)."($fname, \"$name\")")."\n"; + } + else if($type instanceof mtgMetaEnum) + { + $str .= $this->genWrite("{$buf}.WriteI32(int32($fname), \"$name\")"); + } + else if($type instanceof mtgMetaStruct) + { + if(array_key_exists('virtual', $tokens)) + $str .= $this->genWrite("meta.WriteStructGeneric($buf, ".($is_ptr?"":"&")."$fname, \"$name\")"); + else + $str .= $this->genWrite("meta.WriteStruct($buf, ".($is_ptr?"":"&")."$fname, \"$name\")"); + } + else if($type instanceof mtgArrType) + { + $is_virtual = array_key_exists("virtual", $tokens); + + $str .= "{$buf}.BeginContainer(\"{$name}\")\n"; + $str .= " for _, v := range({$fname}) {\n"; + $str .= " ".$this->genBufWriteEx("", "v", $type->getValue(), $buf, $tokens, true)."\n"; + $str .= " }\n"; + $str .= " ".$this->genWrite("{$buf}.EndContainer()")."\n"; + } + else + throw new Exception("Unknown type '$type'"); + + return $str; + } + + function genFieldDecl(mtgMetaField $field) + { + return $this->genFieldName($field) . " " . + $this->genFieldNativeType($field) . + ($field->getName()[0] != '_' ? " `json:\"{$field->getName()}\"`" : "") + ; + } + + function genFieldName(mtgMetaField $field) + { + return ucfirst($field->getName()); + } + + function genFieldReset(mtgMetaField $field) + { + $tokens = $field->getTokens(); + + $str = ''; + $name = $this->genFieldName($field); + + $type = $field->getType(); + + if($type instanceof mtgBuiltinType) + { + if($type->isNumeric()) + { + //NOTE: numeric check is against str2num + if(array_key_exists('default', $tokens) && is_numeric($tokens['default'])) + $str .= "self.$name = ".$tokens['default']; + else + $str .= " self.$name = 0"; + } + else if($type->isString()) + { + if(array_key_exists('default', $tokens)) + $str .= "self.$name = ".$tokens['default']; + else + $str .= "self.$name = \"\""; + } + else if($type->isBool()) + { + if(array_key_exists('default', $tokens)) + $str .= "self.$name = ".$tokens['default']; + else + $str .= "self.$name = false"; + } + else if($type->isBlob()) + { + if(array_key_exists('default', $tokens)) + $str .= "self.$name = ".$tokens['default']; + else + $str .= "self.$name = nil"; + } + else + throw new Exception("Unknown type '$type'"); + } + else if($type instanceof mtgArrType) + { + $str = "if self.$name == nil { \nself.$name = make(".$this->genFieldNativeType($field).",0) \n}\n "; + $str .= "self.$name = self.{$name}[0:0]"; + } + else if($type instanceof mtgMetaEnum) + { + if(array_key_exists('default', $tokens)) + $str = "self.$name = ".$this->genFieldNativeType($field)."_".trim($tokens['default'], '"'); + else + $str = "self.$name = 0"; + } + else if($type instanceof mtgMetaStruct) + { + $is_virtual = array_key_exists("virtual", $tokens); + if($is_virtual) + $str .= "self.$name = New".ltrim($this->genFieldNativeType($field),'I')."() "; + else + $str .= "self.$name.Reset()"; + } + else + throw new Exception("Unknown type '$type'"); + return $str; + } + + function genFieldNativeType(mtgMetaField $field) + { + return $this->genNativeType($field->getType(), $field->getTokens()); + } + + function genNativeType(mtgType $type, array $tokens = array()) + { + if($type instanceof mtgArrType) + { + $vtype = $type->getValue(); + + $native = $this->genNativePlainType($vtype); + + $str = "[]"; + if(array_key_exists("virtual", $tokens)) + $str .= "I"; + else + $str .= $vtype instanceof mtgMetaStruct ? "*" : ""; + + $str .= $native; + + return $str; + } + else + return $this->genNativePlainType($type, $tokens); + } + + function genBuiltinTypePrefix(mtgBuiltinType $type) + { + switch($type->getName()) + { + case "string": + return "String"; + case "bool": + return "Bool"; + case "blob": + return "Blob"; + case "float": + return "Float"; + case "double": + return "Double"; + case "uint64": + return "U64"; + case "int64": + return "I64"; + case "uint": + case "uint32": + return "U32"; + case "uint16": + return "U16"; + case "uint8": + return "U8"; + case "int": + case "int32": + return "I32"; + case "int16": + return "I16"; + case "int8": + return "I8"; + default: + throw new Exception("Unknown type '{$type}'"); + } + } + + function genNativePlainType(mtgType $type, array $tokens = array()) + { + if($type instanceof mtgBuiltinType) + { + if($type->isFloat()) + return "float32"; + else if($type->isDouble()) + return "float64"; + else if($type->isBlob()) + return "[]byte"; + else + return $type->getName(); + } + else if($type instanceof mtgMetaEnum) + return $type->getName(); + else if($type instanceof mtgMetaStruct) + { + if(array_key_exists("virtual", $tokens)) + return "I{$type->getName()}"; + else + return $type->getName(); + } + else + throw new Exception("Unknown type '$type'"); + } + + function genStrMap(array $source) + { + $str = ''; + foreach($source as $k => $v) + $str .= '"' . $k . '": "' . ($v ? str_replace('"', '\\"', $v) : "") . '",'; + return $str; + } + + function genIntMap(array $source) + { + $str = ''; + foreach($source as $k => $v) + $str .= "\"$k\": {$v},"; + return $str; + } + + function offset($num = 1) + { + return str_repeat(" ", $num); + } +} diff --git a/targets/go/go_generator.inc.php b/targets/go/go_generator.inc.php new file mode 100644 index 0000000..bf7ca6b --- /dev/null +++ b/targets/go/go_generator.inc.php @@ -0,0 +1,93 @@ +setMetaInfo($meta); + + $refl = new ReflectionClass($codegen); + $SHARED_DEPS = array( + dirname(__FILE__) . '/go.inc.php', + dirname(__FILE__) . '/go_tpl.inc.php', + __FILE__, + $refl->getFileName() + ); + + mtg_mkdir(mtg_conf('out-dir')); + + $units = $meta->getUnits(); + $files = array(); + foreach($units as $unit) + $files = array_merge($files, mtg_get_file_deps($meta, $unit)); + $files = array_unique($files); + + $bundle = mtg_conf("bundle", null); + if($bundle && $units) + { + $targets[] = mtg_new_bundle($bundle, + array_merge($SHARED_DEPS, $files), + array('gen_go_bundle', $meta, $codegen, $units)); + } + + return $targets; + } +} + +function gen_go_struct($OUT, array $DEPS, mtgCodegen $codegen, mtgMetaInfoUnit $unit) +{ + return $codegen->genUnit($unit); +} + +function gen_go_bundle($OUT, array $DEPS, mtgMetaInfo $meta, mtgGoCodegen $codegen, array $units) +{ + $bundle = ''; + $units_src = ''; + $create_struct_by_crc28 = ''; + $create_struct_by_name = ''; + $create_rpc_by_code = ''; + + foreach($units as $unit) + { + if($unit->object instanceof mtgMetaEnum || + $unit->object instanceof mtgMetaStruct || + $unit->object instanceof mtgMetaRPC + ) + $units_src .= $codegen->genUnit($unit); + } + + foreach($units as $unit) + { + if($unit->object instanceof mtgMetaRPC) + { + $rpc = $unit->object; + $create_rpc_by_code .= "\n case {$rpc->getCode()}: { return New{$rpc->getName()}(), nil; }"; + continue; + } + + if($unit->object->hasToken('POD') || + $unit->object instanceof mtgMetaFunc || + $unit->object instanceof mtgMetaEnum + ) + continue; + + $create_struct_by_crc28 .= "\n case {$unit->object->getClassId()}: { return New{$unit->object->getName()}(), nil }"; + $create_struct_by_name .= "\n case \"{$unit->object->getName()}\": { return New{$unit->object->getName()}(), nil }"; + } + + $templater = new mtg_go_templater(); + $tpl = $templater->tpl_bundle(); + return mtg_fill_template($tpl, + array('%bundle%' => $bundle, + '%units_src%' => $units_src, + '%create_struct_by_name%' => $create_struct_by_name, + '%create_struct_by_crc28%' => $create_struct_by_crc28, + '%create_rpc_by_code%' => $create_rpc_by_code, + )); +} diff --git a/targets/go/go_tpl.inc.php b/targets/go/go_tpl.inc.php new file mode 100644 index 0000000..c84443f --- /dev/null +++ b/targets/go/go_tpl.inc.php @@ -0,0 +1,387 @@ +filter_aliases = $filter_aliases; + } + + function genUnit(mtgMetaInfoUnit $unit) + { + $obj = $unit->object; + if($obj instanceof mtgMetaRPC) + return $this->genRPC($obj); + if($obj instanceof mtgMetaEnum) + return $this->genEnum($obj); + else if($obj instanceof mtgMetaStruct) + return $this->genStruct($obj); + else + { + echo "WARN: skipping meta unit '{$obj->getId()}'\n"; + return ''; + } + } + + function genEnum(mtgMetaEnum $struct) + { + $templater = new mtg_php_templater(); + + $repl = array(); + $repl['%class%'] = $struct->getName(); + $repl['%class_id%'] = $struct->getClassId(); + + $tpl = $templater->tpl_enum(); + + $this->fillEnumRepls($struct, $repl); + + return mtg_fill_template($tpl, $repl); + } + + function fillEnumRepls(mtgMetaEnum $enum, &$repl) + { + $repl['%values%'] = ''; + $repl['%vnames_list%'] = ''; + $repl['%values_list%'] = ''; + + $vnames = array(); + $values = array(); + $txt_values = array(); + $values_map = array(); + $names_map = array(); + $default_value = null; + + $enum_name = $enum->getName(); + foreach($enum->getValues() as $vname => $value) + { + $txt_values[] = "const $vname = $value;"; + $vnames[] = "'$vname'"; + $values[] = $value; + $values_map[] = "'$vname' => $value"; + $names_map[] = "$value => '$vname'"; + if(!$default_value) + $default_value = "$value; // $vname"; + } + + $repl['%values%'] = implode("\n ", $txt_values); + $repl['%vnames_list%'] = implode(',', $vnames); + $repl['%values_list%'] = implode(',', $values); + $repl['%values_map%'] = implode(',', $values_map); + $repl['%names_map%'] = implode(',', $names_map); + $repl['%default_enum_value%'] = $default_value; + } + + function genRPC(mtgMetaRpc $rpc) + { + $req = $this->genRPCPacket($rpc->getReq()); + $rsp = $this->genRPCPacket($rpc->getRsp()); + return $req . str_replace('getName(); + $repl['%code%'] = $packet->getNumericCode(); + $repl['%class%'] = $packet->getName(); + $repl['%class_id%'] = $packet->getClassId(); + $repl['%parent_class%'] = 'extends gmeStrictPropsObject'; + + $tpl = $templater->tpl_packet(); + + $this->fillStructRepls($packet, $repl); + + return mtg_fill_template($tpl, $repl); + } + + function genStruct(mtgMetaStruct $struct) + { + $templater = new mtg_php_templater(); + + $repl = array(); + $repl['%class%'] = $struct->getName(); + $repl['%class_id%'] = $struct->getClassId(); + $repl['%parent_class%'] = $struct->getParent() ? "extends " . $struct->getParent() : "extends gmeStrictPropsObject"; + + $tpl = $templater->tpl_struct(); + + $this->fillStructRepls($struct, $repl); + + return mtg_fill_template($tpl, $repl); + } + + function preprocessField(mtgMetaStruct $struct, mtgMetaField $field) + {} + + function fillStructRepls(mtgMetaStruct $struct, &$repl) + { + foreach($struct->getFields() as $field) + $this->preprocessField($struct, $field); + + $parent = $struct->getParent(); + + $repl['%includes%'] = ''; + $repl['%fields%'] = ''; + $repl['%fields_names%'] = ($parent ? 'array_merge(parent::CLASS_FIELDS(), ' : '') . 'array('; + $repl['%fields_props%'] = ($parent ? 'array_merge(parent::CLASS_FIELDS_PROPS(), ' : '') . 'array('; + $repl['%fields_types%'] = ($parent ? 'array_merge(parent::CLASS_FIELDS_TYPES(), ' : '') . 'array('; + $repl['%fields_init%'] = ''; + $repl['%fill_fields%'] = ''; + $repl['%fill_buffer%'] = ''; + $repl['%total_top_fields%'] = sizeof($struct->getFields()); + $repl["%ext_methods%"] = ""; + + $repl['%class_props%'] = str_replace("\n", "", var_export($struct->getTokens(), true)); + + $deps = array(); + if($parent) + $deps[] = $parent.''; + foreach($struct->getFields() as $field) + { + $type = $field->getType(); + if($type instanceof mtgArrType) + $type = $type->getValue(); + + if($type instanceof mtgUserType) + $deps[] = $type->getName(); + } + $deps = array_unique($deps); + + foreach($deps as $dep) + $repl['%includes%'] .= "require_once(dirname(__FILE__) . '/$dep.class.php');\n"; + + if($parent) + { + $repl['%fields_init%'] .= "parent::__construct();\n"; + $repl['%fill_fields%'] .= "\$IDX = parent::import(\$message, \$assoc, false);\n"; + $repl['%fill_buffer%'] .= "\$__last_var = 'parent';\$__last_val='<>';parent::fill(\$message, \$assoc, false);\n"; + } + + $indent = " "; + $local_indent = " "; + $fill_func_indent = " "; + + foreach($struct->getFields() as $field) + { + $repl['%fields_names%'] .= "'" . $field->getName() . "',"; + $repl['%fields_props%'] .= "'" . $field->getName() . "' => " . str_replace("\n", "", var_export($field->getTokens(), true)) . ","; + $field_type = $field->getType(); + $repl['%fields_types%'] .= "'" . $field->getName() . "' => '" . $field_type . "',"; + + $repl['%fields_init%'] .= $this->genFieldInit($field, ' $this->') . "\n"; + $repl['%fill_fields%'] .= $local_indent.$this->genFieldFill($field, '$message', '$this->', $indent) . $indent."++\$IDX;\n"; + $repl['%fill_buffer%'] .= $this->genBufFill($field, '$message', '$this->', $fill_func_indent) . "\n"; + $repl['%fields%'] .= $this->genFieldDecl($field) . "\n"; + } + + $repl['%fields_names%'] .= ')' . ($parent ? ')' : ''); + $repl['%fields_props%'] .= ')' . ($parent ? ')' : ''); + $repl['%fields_types%'] .= ')' . ($parent ? ')' : ''); + + $this->undoTemp(); + } + + function genFieldInitValue(mtgMetaField $field) + { + $tokens = $field->getTokens(); + $name = $field->getName(); + $type = $field->getType(); + + if($type instanceof mtgMetaStruct) + { + return "new {$type}()"; + } + else if($type instanceof mtgMetaEnum) + { + if(array_key_exists('default', $tokens)) + $default = $this->genDefault($tokens['default']); + else + $default = 'DEFAULT_VALUE'; + + $default = str_replace('"', '', $default); + return "{$type}::$default"; + } + else if($type instanceof mtgArrType) + { + return "array()"; + } + else if($type instanceof mtgBuiltinType) + { + if(array_key_exists('default', $tokens)) + { + $raw_default_value = $this->genDefault($tokens['default']); + $default_value = $this->genApplyFieldFilters($name, $tokens, $raw_default_value, false); + return "mtg_php_val_{$type}($default_value)"; + } + if ($type->isString()) + return "''"; + return '0'; + } + else + throw new Exception("Unable generate field initial value for unknown type '{$type}'"); + } + + function genFieldInit(mtgMetaField $field, $prefix = "") + { + $value = $this->genFieldInitValue($field); + + return $prefix . $field->getName() . " = $value;"; + } + + function genFieldFill(mtgMetaField $field, $buf, $prefix, $indent = "") + { + return $this->genFieldFillEx($field->getName(), $field->getType(), $buf, $prefix, $field->getTokens(), false, "", $indent); + } + + function genFieldFillEx($name, mtgType $type, $buf, $prefix = '', $tokens = array(), $as_is = false, $postfix = '', $indent = "") + { + $str = ''; + $tmp_val = '$tmp_val__'; + $pname = $prefix.$name; + + $cond_indent = $indent." "; + + $default_value_arg = array_key_exists('default', $tokens) ? $this->genDefault($tokens['default']) : 'null'; + if($type instanceof mtgMetaStruct) + { + $default = array_key_exists('default', $tokens) ? $tokens['default'] : null; + if($default) + { + $default = json_decode($default, true); + if(!is_array($default)) + throw new Exception("Bad default struct: " . $tokens['default']); + $default = str_replace("\n", "", var_export($default, true)); + $default_value_arg = "\$assoc ? $default : array_values($default)"; + } + else + $default_value_arg = "null"; + } + + $str .= "\n"; + + if($as_is) + $tmp_val = $buf; + else + $str .= $indent."{$tmp_val} = mtg_php_array_extract_val({$buf}, \$assoc, '{$name}', {$default_value_arg});\n"; + + if($type instanceof mtgBuiltinType) + { + $str .= $cond_indent."{$tmp_val} = " . $this->genApplyFieldFilters($name, $tokens, "{$tmp_val}"). ";\n"; + $str .= $cond_indent."{$pname} = mtg_php_val_{$type}({$tmp_val});\n"; + } + else if($type instanceof mtgMetaStruct) + { + if(array_key_exists('virtual', $tokens)) + { + $str .= $cond_indent."{$tmp_val} = " . $this->genApplyFieldFilters($name, $tokens, "{$tmp_val}"). ";\n"; + $str .= $cond_indent."\$tmp_sub_arr__ = mtg_php_val_arr({$tmp_val});\n"; + $str .= $cond_indent."\$vclass__ = AutogenBundle::getClassName(mtg_php_val_uint32(mtg_php_array_extract_val(\$tmp_sub_arr__, \$assoc, 'vclass__')));\n"; + $str .= $cond_indent."{$pname} = new \$vclass__(\$tmp_sub_arr__, \$assoc);\n"; + } + else + { + $str .= $cond_indent."{$tmp_val} = " . $this->genApplyFieldFilters($name, $tokens, "{$tmp_val}"). ";\n"; + $str .= $cond_indent."\$tmp_sub_arr__ = mtg_php_val_arr({$tmp_val});\n"; + $str .= $cond_indent."{$pname} = new {$type}(\$tmp_sub_arr__, \$assoc);\n"; + } + } + else if($type instanceof mtgArrType) + { + //TODO: maybe filters should be applied to the whole array as well? + $str .= $cond_indent."\$tmp_arr__ = mtg_php_val_arr({$tmp_val});\n"; + $str .= $cond_indent."foreach(\$tmp_arr__ as \$tmp_arr_item__)\n"; + $str .= $cond_indent."{\n"; + //NOTE: удаляем дефолтное значение уровня поля, а не его значений + unset($tokens['default']); + $str .= $this->genFieldFillEx('$tmp__', $type->getValue(), "\$tmp_arr_item__", '', $tokens, true, "{$pname}[] = \$tmp__;", $cond_indent." ") . "\n"; + $str .= $cond_indent."}\n"; + } + else if($type instanceof mtgMetaEnum) + { + $str .= $cond_indent."{$tmp_val} = " . $this->genApplyFieldFilters($name, $tokens, "{$tmp_val}"). ";\n"; + $check_enum_validity = array_key_exists('is_enum_mask', $tokens) ? 'false' : 'true'; + $str .= $cond_indent."{$pname} = mtg_php_val_enum('$type', {$tmp_val}, {$check_enum_validity});\n"; + } + else + throw new Exception("Unknown type '{$type}'"); + + if($postfix) + $str .= $cond_indent.$postfix."\n"; + + return $str; + } + + function genApplyFieldFilters($field_name, array $tokens, $val, $add_assoc_check = true) + { + $str = $val; + foreach($tokens as $token => $args_json) + { + if(isset($this->filter_aliases[$token])) + $token = $this->filter_aliases[$token]; + + if(strpos($token, 'flt_') === false) + continue; + + $filter_func = 'mtg_' . $token; + + if(function_exists($filter_func)) + { + $args = array(); + if($args_json) + { + $args = json_decode($args_json, true); + if(!is_array($args)) + throw new Exception("Bad filter args: $args_json"); + } + if ($add_assoc_check) + return "\$assoc ? $filter_func($val, '$field_name', \$this, " . str_replace("\n", "", var_export($args, true)) . ") : $val"; + else + return "$filter_func($val, '$field_name', \$this, " . str_replace("\n", "", var_export($args, true)) . ")"; + } + else + throw new Exception("No such filter '$filter_func'"); + } + + return $str; + } + + function genBufFill(mtgMetaField $field, $buf, $prefix, $indent = "") + { + return $this->genBufFillEx($field->getName(), $field->getType(), $buf, $prefix, $field->getTokens(), $indent); + } + + function genBufFillEx($name, mtgType $type, $buf, $prefix = '', $tokens = array(), $indent = "") + { + $pname = $prefix.$name; + + $str = ''; + $str .= "\$__last_var = '$name';"; + $str .= "\$__last_val = $pname;"; + + if($type instanceof mtgBuiltinType) + { + if($type->isNumeric()) + $str .= $indent."mtg_php_array_set_value({$buf}, \$assoc, '$name', 1*{$pname});"; + else if($type->isString()) + $str .= $indent."mtg_php_array_set_value({$buf}, \$assoc, '$name', ''.{$pname});"; + else if($type->isBool()) + $str .= $indent."mtg_php_array_set_value({$buf}, \$assoc, '$name', (bool){$pname});"; + else if($type->isBlob()) + $str .= $indent."mtg_php_array_set_value({$buf}, \$assoc, '$name', {$pname});"; + else + throw new Exception("Unknown type '$type'"); + } + else if($type instanceof mtgMetaStruct) + { + if(array_key_exists('virtual', $tokens)) + $str .= $indent."mtg_php_array_set_value({$buf}, \$assoc, '$name', is_array({$pname}) ? {$pname} : {$pname}->export(\$assoc, true/*virtual*/));"; + else + $str .= $indent."mtg_php_array_set_value({$buf}, \$assoc, '$name', is_array({$pname}) ? {$pname} : {$pname}->export(\$assoc));"; + } + else if($type instanceof mtgMetaEnum) + { + $str .= $indent."mtg_php_array_set_value({$buf}, \$assoc, '$name', 1*{$pname});"; + } + else if($type instanceof mtgArrType) + { + $str .= $indent."\$arr_tmp__ = array();\n"; + //NOTE: adding optimization, checking if the first item is array + $str .= $indent."if(!\$assoc && {$pname} && is_array(current({$pname})))\n"; + $str .= $indent."{\n"; + //$str .= " mtg_php_debug('BULK:' . get_class(\$this));\n"; + $str .= $indent." \$arr_tmp__ = {$pname};\n"; + $str .= $indent."}\n"; + $str .= $indent."else\n"; + $str .= $indent." foreach({$pname} as \$idx__ => \$arr_tmp_item__)\n"; + $str .= $indent." {\n"; + $str .= $this->genBufFillEx('$arr_tmp_item__', $type->getValue(), "\$arr_tmp__", "", $tokens, $indent." ") . "\n"; + $str .= $indent." if(\$assoc)\n"; + $str .= $indent." {\n"; + $str .= $indent." \$arr_tmp__[] = \$arr_tmp__['\$arr_tmp_item__'];\n"; + $str .= $indent." unset(\$arr_tmp__['\$arr_tmp_item__']);\n"; + $str .= $indent." }\n"; + $str .= $indent." }\n"; + $str .= $indent."mtg_php_array_set_value({$buf}, \$assoc, '$name', \$arr_tmp__);\n"; + } + else + throw new Exception("Unknown type '{$type}'"); + + return $str; + } + + function genDefault($default) + { + if($default == "[]") + return 'array()'; + if(strlen($default) > 2 && strpos($default, '[') === 0 && strpos($default, ']') === strlen($default)-1) + { + $str = trim($default, ']['); + if(strpos($str, "{") !== false ) + $str = var_export(json_decode(trim($default, ']['), true), true); + return 'array(' . $str . ')'; + } + return str_replace('$', '\$', $default); + } + + function genFieldDecl(mtgMetaField $field) + { + $type = $field->getType(); + + $type_comment = "/** @var " . $type ." */\n"; + if($type instanceof mtgBuiltinType && $type->isString()) + return $type_comment."public \$" . $field->getName() . " = '';"; + else if($type instanceof mtgMetaStruct) + return $type_comment."public \$" . $field->getName() . " = null;"; + else if($type instanceof mtgArrType) + return $type_comment."public \$" . $field->getName() . " = array();"; + else + return $type_comment."public \$" . $field->getName() . ";"; + } +} + +//////////////////////////////////////////////////////////////////////// + +function mtg_php_array_extract_val(&$arr, $assoc, $name, $default = null) +{ + if(!is_array($arr)) + throw new Exception("$name: Not an array"); + + if(!$assoc) + { + if(sizeof($arr) == 0) + { + if($default !== null) + return $default; + + throw new Exception("$name: No next array item"); + } + return array_shift($arr); + } + + if(!isset($arr[$name])) + { + if($default !== null) + return $default; + + throw new Exception("$name: No array item"); + } + + $val = $arr[$name]; + unset($arr[$name]); + return $val; +} + +function mtg_php_array_set_value(&$arr, $assoc, $name, $value) +{ + if($assoc) + $arr[$name] = $value; + else + $arr[] = $value; +} + +function mtg_php_val_string($val) +{ + //special case for empty strings + if(is_bool($val) && $val === false) + return ''; + if(!is_string($val)) + throw new Exception("Bad item, not a string(" . serialize($val) . ")"); + return $val; +} + +function mtg_php_val_bool($val) +{ + if(!is_bool($val)) + throw new Exception("Bad item, not a bool(" . serialize($val) . ")"); + return $val; +} + +function mtg_php_val_float($val) +{ + if(!is_numeric($val)) + throw new Exception("Bad item, not a number(" . serialize($val) . ")"); + $val = 1*$val; + return $val; +} + +function mtg_php_val_double($val) +{ + if(!is_numeric($val)) + throw new Exception("Bad item, not a number(" . serialize($val) . ")"); + return 1*$val; +} + +function mtg_php_val_uint64($val) +{ + if(!is_numeric($val)) + throw new Exception("Bad item, not a number(" . serialize($val) . ")"); + $val = 1*$val; + if(is_float($val)) + throw new Exception("Value not in range: $val"); + return $val; +} + +function mtg_php_val_int64($val) +{ + if(!is_numeric($val)) + throw new Exception("Bad item, not a number(" . serialize($val) . ")"); + $val = 1*$val; + if(is_float($val)) + throw new Exception("Value not in range: $val"); + return $val; +} + +function mtg_php_val_uint32($val) +{ + if(!is_numeric($val)) + throw new Exception("Bad item, not a number(" . serialize($val) . ")"); + $val = 1*$val; + if(($val < 0 && $val < -2147483648) || ($val > 0 && $val > 0xFFFFFFFF) || is_float($val)) + throw new Exception("Value not in range: $val"); + return $val; +} + +function mtg_php_val_int32($val) +{ + if(!is_numeric($val)) + throw new Exception("Bad item, not a number(" . serialize($val) . ")"); + $val = 1*$val; + if($val > 2147483647 || $val < -2147483648 || is_float($val)) + throw new Exception("Value not in range: $val"); + return $val; +} + +function mtg_php_val_uint16($val) +{ + if(!is_numeric($val)) + throw new Exception("Bad item, not a number(" . serialize($val) . ")"); + $val = 1*$val; + if($val > 0xFFFF || $val < 0 || is_float($val)) + throw new Exception("Value not in range: $val"); + return $val; +} + +function mtg_php_val_int16($val) +{ + if(!is_numeric($val)) + throw new Exception("Bad item, not a number(" . serialize($val) . ")"); + $val = 1*$val; + if($val > 32767 || $val < -32768 || is_float($val)) + throw new Exception("Value not in range: $val"); + return $val; +} + +function mtg_php_val_uint8($val) +{ + if(!is_numeric($val)) + throw new Exception("Bad item, not a number(" . serialize($val) . ")"); + $val = 1*$val; + if($val > 0xFF || $val < 0 || is_float($val)) + throw new Exception("Value not in range: $val"); + return $val; +} + +function mtg_php_val_int8($val) +{ + if(!is_numeric($val)) + throw new Exception("Bad item, not a number(" . serialize($val) . ")"); + $val = 1*$val; + if($val > 127 || $val < -128 || is_float($val)) + throw new Exception("Value not in range: $val"); + return $val; +} + +function mtg_php_val_arr($val) +{ + if(!is_array($val)) + throw new Exception("Bad item, not an array(" . serialize($val) . ")"); + return $val; +} + +function mtg_php_val_enum($enum, $val, $check_enum_validity = true) +{ + if(is_string($val)) + return call_user_func_array(array($enum, "getValueByName"), array($val)); + + if(!is_numeric($val)) + throw new Exception("Bad enum value, not a numeric or string(" . serialize($val) . ")"); + + if($check_enum_validity) + call_user_func_array(array($enum, "checkValidity"), array($val)); + return $val; +} + diff --git a/targets/php/php_generator.inc.php b/targets/php/php_generator.inc.php new file mode 100644 index 0000000..c9cf69f --- /dev/null +++ b/targets/php/php_generator.inc.php @@ -0,0 +1,95 @@ +setMetaInfo($meta); + + $refl = new ReflectionClass($codegen); + $SHARED_DEPS = array( + dirname(__FILE__) . '/php.inc.php', + dirname(__FILE__) . '/php_tpl.inc.php', + __FILE__, + $refl->getFileName() + ); + + mtg_mkdir(mtg_conf('out-dir')); + + $units = array(); + $rpcs = array(); + $files = array(); + foreach($meta->getUnits() as $unit) + { + if($unit->object instanceof mtgMetaRPC) + { + $rpcs[] = $unit->object; + $files = array_merge($files, mtg_get_file_deps($meta, $unit)); + } + else if($unit->object instanceof mtgMetaEnum || $unit->object instanceof mtgMetaStruct) + { + $files = array_merge($files, mtg_get_file_deps($meta, $unit)); + $units[] = $unit; + } + } + $files = array_unique($files); + + $bundle = mtg_conf("bundle", null); + if($bundle && ($units || $rpcs)) + $targets[] = mtg_new_bundle($bundle, array_merge($SHARED_DEPS, $files), array('gen_php_bundle', $codegen, $units, $rpcs, mtg_conf("inc-dir"))); + + return $targets; + } +} + +function gen_php_struct($OUT, array $DEPS, mtgCodegen $codegen, mtgMetaInfoUnit $unit) +{ + return $codegen->genUnit($unit); +} + +function gen_php_bundle($OUT, array $DEPS, mtgPHPCodegen $codegen, array $units, array $rpcs, $inc_dir) +{ + $bundle = ''; + $class_map = ''; + $packet_map = ''; + $units_src = ''; + + $map = array(); + foreach($units as $unit) + { + $class_id = $unit->object->getClassId(); + $class_name = $unit->object->getName(); + if(isset($map[$class_id])) + throw new Exception("Duplicating class id '$class_id'($class_name vs {$map[$class_id]})"); + $map[$class_id] = $class_name; + + $class_map .= "case " . 1*$class_id . ": return \"$class_name\";\n"; + + if($unit->object instanceof mtgMetaEnum || $unit->object instanceof mtgMetaStruct) + $units_src .= $codegen->genUnit($unit); + } + + foreach($rpcs as $rpc) + { + $code = $rpc->getCode(); + $name = $rpc->getReq()->getName(); + + $packet_map .= "case " . 1*$code . ": return new $name(\$data);\n"; + + $units_src .= $codegen->genRPC($rpc); + } + + $templater = new mtg_php_templater(); + $tpl = $templater->tpl_packet_bundle(); + return mtg_fill_template($tpl, array('%bundle%' => $bundle, + '%units_src%' => $units_src, + '%class_map%' => $class_map, + '%packet_map%' => $packet_map)); +} + diff --git a/targets/php/php_tpl.inc.php b/targets/php/php_tpl.inc.php new file mode 100644 index 0000000..9af4645 --- /dev/null +++ b/targets/php/php_tpl.inc.php @@ -0,0 +1,336 @@ +import(\$message, \$assoc); + } + + function getClassId() + { + return self::CLASS_ID; + } + + %ext_methods% + + function import(&\$message, \$assoc = false, \$root = true) + { + \$IDX = 0; + try + { + if(!is_array(\$message)) + throw new Exception("Bad message: \$message"); + + try + { + %fill_fields% + } + catch(Exception \$e) + { + \$FIELDS = self::CLASS_FIELDS(); + throw new Exception("Error while filling field '{\$FIELDS[\$IDX]}': " . \$e->getMessage()); + } + + if(\$root && \$assoc && sizeof(\$message) > 0) + throw new Exception("Junk fields: " . implode(',', array_keys(\$message))); + } + catch(Exception \$e) + { + throw new Exception("Error while filling fields of '%class%':" . \$e->getMessage()); + } + return \$IDX; + } + + function export(\$assoc = false, \$virtual = false) + { + \$message = array(); + \$this->fill(\$message, \$assoc, \$virtual); + return \$message; + } + + function fill(&\$message, \$assoc = false, \$virtual = false) + { + if(\$virtual) + mtg_php_array_set_value(\$message, \$assoc, 'vclass__', \$this->getClassId()); + + try + { + \$__last_var = null; + \$__last_val = null; + %fill_buffer% + } + catch(Exception \$e) + { + throw new Exception("Error while dumping fields of '%class%'->\$__last_var: ". PHP_EOL . serialize(\$__last_val) . PHP_EOL."\t" . \$e->getMessage()); + } + } +} + +EOD; + return $TPL; + } + + function tpl_enum() + { + $TPL = <<import(\$message, \$assoc); + } + + function getCode() + { + return %code%; + } + + function getType() + { + return "%type%"; + } + + function import(&\$message, \$assoc = false, \$root = true) + { + try + { + if(!\$message || !is_array(\$message)) + throw new Exception("Bad message"); + + \$IDX = 0; + try + { + %fill_fields% + } + catch(Exception \$e) + { + \$FIELDS = self::CLASS_FIELDS(); + throw new Exception("Error while filling field '{\$FIELDS[\$IDX]}': " . \$e->getMessage()); + } + + if(\$root && \$assoc && sizeof(\$message) > 0) + throw new Exception("Junk fields: " . implode(',', array_keys(\$message))); + } + catch(Exception \$e) + { + throw new Exception("Error while filling fields of '%class%':" . \$e->getMessage()); + } + } + + function export(\$assoc = false) + { + try + { + \$message = array(); + + %fill_buffer% + + return \$message; + } + catch(Exception \$e) + { + throw new Exception("Error while dumping fields of '%class%':" . \$e->getMessage()); + } + } +} + +EOD; + return $TPL; + } + + function tpl_packet_bundle() + { + $TPL = <<findUnit("DataPlayer"); +assert($u->object instanceof mtgMetaStruct); +assert($u->object->getName() == "DataPlayer"); +assert($u->object->getTokens() == array('POD' => null, 'table' => 'player', 'pkey' => 'id')); +assert(sizeof($u->object->getFields()) == 4); +assert($u->object->getFields()["id"]->getType() == "uint32"); +assert($u->object->getFields()["version"]->getType() == "string"); +assert($u->object->getFields()["version"]->getTokens() == array('strmax' => 16)); +assert($u->object->getFields()["gold"]->getType() == "uint8"); +assert($u->object->getFields()["gold"]->getTokens() == array('default' => 0)); +assert($u->object->getFields()["registered"]->getType() == "bool"); +assert($u->object->getFields()["registered"]->getTokens() == array('default' => 'true')); +assert(sizeof($u->object->getFuncs()) == 1); +assert($u->object->getFuncs()["Equals"]->getTokens() == array('bhl_ret_type' => "bool,bool")); +assert(sizeof($u->object->getFuncs()["Equals"]->getArgs()) == 2); +assert($u->object->getFuncs()["Equals"]->getArgs()["o"]->getType() == "DataPlayer"); +assert($u->object->getFuncs()["Equals"]->getArgs()["v"]->getType() == "uint32"); +assert($u->object->getFuncs()["Equals"]->getReturnType() instanceof mtgMultiType); +assert($u->object->getFuncs()["Equals"]->getReturnType()->getValues()[0] == "bool"); +assert($u->object->getFuncs()["Equals"]->getReturnType()->getValues()[1] == "bool"); + +$u = $meta->findUnit("GetPlayer"); +assert($u->object instanceof mtgMetaFunc); +assert($u->object->getName() == "GetPlayer"); +assert($u->object->getReturnType() == "DataPlayer"); +assert(sizeof($u->object->getArgs()) == 1); +assert($u->object->getArgs()["id"]->getName() == "id"); +assert($u->object->getArgs()["id"]->getType() == "uint32"); + +$u = $meta->findUnit("Apply"); +assert($u->object instanceof mtgMetaFunc); +assert($u->object->getName() == "Apply"); +assert($u->object->getReturnType() === null); +assert($u->object->getArgs()["fn"]->getName() == "fn"); +assert($u->object->getArgs()["fn"]->getType() == "func (float,DataPlayer):uint32"); + +$u = $meta->findUnit("ProtoBase"); +assert($u->object instanceof mtgMetaStruct); +assert(sizeof($u->object->getFields()) == 2); +assert($u->object->getFields()["id"]->getType() == "uint32"); +assert($u->object->getFields()["title"]->getType() == "string"); +assert($u->object->getFields()["title"]->getTokens() == array('strmax' => 16, 'default' => "\"`'Hello\"")); + +$u = $meta->findUnit("ProtoBuilding"); +assert($u->object instanceof mtgMetaStruct); +assert($u->object->getParent() == "ProtoBase"); + +$u = $meta->findUnit("ProtoTags"); +assert($u->object instanceof mtgMetaStruct); +assert(sizeof($u->object->getFields()) == 2); +assert($u->object->getFields()["children"]->getType() instanceof mtgArrType); +assert($u->object->getFields()["children"]->getType() == "ProtoBase[]"); +assert($u->object->getFields()["tags"]->getType() == "string[]"); + +//NOTE: RPCs are identified by ids +$u = $meta->findUnit("10"); +assert($u->object instanceof mtgMetaRPC); +assert($u->object->getName() == "RPC_GET_ALL_PROTO"); +assert($u->object->getNumericCode() == 10); +assert(sizeof($u->object->getReq()->getFields()) == 1); +assert($u->object->getReq()->getFields()["ticket"]->getType() == "string"); +assert(sizeof($u->object->getRsp()->getFields()) == 1); +assert($u->object->getRsp()->getFields()["list"]->getType() == "ProtoBase[]"); + +$u = $meta->findUnit("EnumStock"); +assert($u->object instanceof mtgMetaEnum); +assert($u->object->getValues() == array('GOLD' => 108683766, 'XP' => 90110385)); + +$u = $meta->findUnit("ConfStock"); +assert($u->object instanceof mtgMetaStruct); +assert(sizeof($u->object->getFields()) == 1); +assert($u->object->getFields()["id"]->getType() == "EnumStock"); + +mtg_run(new mtgCsGenerator(), array( + "meta" => $meta, + "out-dir" => __DIR__ . "/autogen", + "bundle" => __DIR__ . "/autogen/bundle.cs" + )); + +mtg_run(new mtgGoGenerator(), array( + "meta" => $meta, + "out-dir" => __DIR__ . "/autogen", + "bundle" => __DIR__ . "/autogen/bundle.go" + )); + +mtg_run(new mtgPHPGenerator(), array( + "meta" => $meta, + "out-dir" => __DIR__ . "/autogen", + "inc-dir" => __DIR__ . "/autogen", + "bundle" => __DIR__ . "/autogen/bundle.inc.php" + )); + +//TODO: было бы хорошо реализовать +//require_once(__DIR__ . '/../../gme/src/utils.inc.php'); +//require_once(__DIR__ . '/autogen/bundle.inc.php'); +// +//$man = new ProtoMan(); +//assert(count($man->right_hands) == 1); +//assert($man->right_hands[0]->skill == 100); +//assert($man->right_hands[0]->fingers == 1); +//assert($man->right_hands[0]->fingers2 == 77);