resolved = $name_or_resolved; } else { $this->name = $name_or_resolved; if(!$scope) throw new Exception("Scope is not set"); self::$unresolved[] = $this; } $this->scope = $scope; $this->origin = $origin ?? new mtgOrigin(); } function getName() { if($this->resolved) return ''.$this->resolved; else return $this->name; } static function checkAllResolved() { while(sizeof(self::$unresolved) > 0) { $ref = array_shift(self::$unresolved); $resolved = $ref->resolve(); if($resolved instanceof mtgMetaFunc && $resolved->getName()) throw new Exception("Invalid resolving of named function as type: " . $resolved->getName() . " at " . $ref->origin); } } function resolve() { if($this->resolved) return $this->resolved; $symb = $this->scope->findSymbol($this->name); if($symb) { $this->resolved = $symb; return $this->resolved; } else throw new Exception("{$this->origin} : Symbol '{$this->name}' not found"); } function __toString() { return $this->getName(); } } class mtgMetaInfoUnit { public ?mtgMetaParsedModule $module = null; public string $file; public mtgMetaUnit $object; /** * @param string|mtgMetaParsedModule $file_or_module */ function __construct($file_or_module, mtgMetaUnit $obj) { if($file_or_module instanceof mtgMetaParsedModule) { $this->module = $file_or_module; $this->file = $file_or_module->file; } else if(is_string($file_or_module)) { $this->module = null; $this->file = $file_or_module; } $this->object = $obj; } } 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->getMetaId()])) throw new Exception("Meta info unit '{$unit->object->getMetaId()}' already defined in file '{$this->units[$unit->object->getMetaId()]->file}'"); $this->units[$unit->object->getMetaId()] = $unit; } function delUnit(mtgMetaInfoUnit $unit) { unset($this->units[$unit->object->getMetaId()]); } function getUnits() : array { return $this->units; } function findUnit($id) : ?mtgMetaInfoUnit { if(isset($this->units[$id])) return $this->units[$id]; return null; } function getUnit($id) : mtgMetaInfoUnit { if(isset($this->units[$id])) return $this->units[$id]; throw new Exception("Unit '$id' not found"); } function validate() { foreach($this->units as $u) $u->object->validate($this); } } abstract class mtgMetaUnit { abstract function getMetaId(); protected $tokens = array(); protected ?mtgOrigin $origin; function getTokens() { return $this->tokens; } function setTokens(array $tokens) { $this->tokens = $tokens; } function setToken(string $name, $val) { $this->tokens[$name] = $val; } function delToken(string $name) { unset($this->tokens[$name]); } function getToken(string $name) { return $this->hasToken($name) ? $this->tokens[$name] : null; } function hasToken($name) { return array_key_exists($name, $this->tokens); } function setOrigin(mtgOrigin $origin) { $this->origin = $origin; } function getOrigin() : ?mtgOrigin { return $this->origin; } abstract function validate(mtgMetaInfo $meta); } class mtgOrigin { public $file; public $line; function __construct($file = '', $line = 0) { $this->file = $file; $this->line = $line; } function __toString() { return "{$this->file}@{$this->line}"; } } abstract class mtgUserType extends mtgMetaUnit implements mtgType { protected $name; function __construct($name) { $this->name = $name; } function getMetaId() { return $this->name; } function getName() { return $this->name; } function setName($name) { $this->name = $name; } function getClassId() { if($this->hasToken('class_id')) return $this->getToken('class_id'); //TODO: migrate to crc32 from crc28 return crc32($this->name) & 0xFFFFFFF; } function __toString() { return $this->name; } } class mtgMetaStruct extends mtgUserType { protected $fields = array(); protected $funcs = array(); protected $parent = null; protected $implements = array(); function __construct($name, array $fields = array(), mtgTypeRef $parent = null, array $tokens = array(), array $implements = array()) { parent::__construct($name); $this->setFields($fields); $this->parent = $parent; $this->tokens = $tokens; $this->implements = $implements; } function validate(mtgMetaInfo $meta) { $parent = $this->getParent(); while($parent != null) { foreach($this->fields as $name => $_) if($parent->hasField($name)) throw new Exception("{$this->origin} : Parent struct '{$parent->getName()}' already has field '{$name}'"); foreach($this->funcs as $name => $_) if($parent->hasFunc($name)) throw new Exception("{$this->origin} : Parent struct '{$parent->getName()}' already has func '{$name}'"); $parent = $parent->getParent(); } $s = new SplObjectStorage(); foreach($this->getImplements() as $imp) { if(!($imp instanceof mtgMetaInterface)) throw new Exception("{$this->origin} : Not an interface '{$imp}'"); if($s->contains($imp)) throw new Exception("{$this->origin} : Duplicate interface reference '{$imp->getName()}'"); $s->attach($imp); } foreach($this->funcs as $func) { $func->validate($meta); } } function getParent() { return $this->parent ? $this->parent->resolve() : null; } function getImplements() { if(!$this->implements) return array(); $imps = array(); foreach($this->implements as $imp) $imps[] = $imp->resolve(); return $imps; } function getFields() { return $this->fields; } function setFields(array $fields, $replace = false) { if($replace) $this->fields = array(); 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; } 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]; } 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]; } //TODO: doesn't belong here function hasTokenInParent($name) { return mtg_try_get_token_in_hierarchy($this, $name, $value); } } class mtgMetaInterface extends mtgUserType { protected $funcs = array(); protected $implements = null; function __construct($name, array $implements = array(), $tokens = array()) { parent::__construct($name); $this->implements = $implements; $this->tokens = $tokens; } function validate(mtgMetaInfo $meta) {} function getImplements() { if(!$this->implements) return array(); $imps = array(); foreach($this->implements as $imp) $imps[] = $imp->resolve(); return $imps; } function getFuncs() { return $this->funcs; } function addFunc(mtgMetaFunc $fn) { if($this->hasFunc($fn->getName())) throw new Exception("Interface '{$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]; } } class mtgMetaFunc extends mtgMetaUnit implements mtgType { private $name; private $args = array(); private $ret_type; function __construct($name) { $this->name = $name; } function validate(mtgMetaInfo $meta) {} function getMetaId() { return $this->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 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 validate(mtgMetaInfo $meta) {} function getMetaId() { 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 mtgMetaService extends mtgMetaUnit implements mtgScope { private $name; private $parent_scope; private $rpcs = array(); private $user_types = array(); function __construct($name, mtgScope $parent_scope, array $tokens = array()) { $this->name = $name; $this->parent_scope = $parent_scope; $this->tokens = $tokens; } function validate(mtgMetaInfo $meta) {} function getName() { return $this->name; } function getMetaId() { return $this->name; } function findSymbol($name) { if(isset($this->user_types[$name])) return $this->user_types[$name]; return $this->parent_scope->findSymbol($name); } function getRPCs() { return $this->rpcs; } function addRPC(mtgMetaRPC $rpc) { foreach($this->rpcs as $name => $_rpc) { if($name == $rpc->getName()) throw new Exception("Service '{$this->name}' already has RPC '{$rpc->getName()}'"); if($rpc->getNumericCode() == $_rpc->getNumericCode()) throw new Exception("Service '{$this->name}' already has RPC '{$_rpc->getName()}' with code '{$rpc->getNumericCode()}'"); } $this->rpcs[$rpc->getName()] = $rpc; } function hasRPC($name) { return isset($this->rpcs[$name]); } function getRPC($name) { if(!isset($this->rpcs[$name])) throw new Exception("No such RPC '$name'"); return $this->rpcs[$name]; } function getUserTypes() { return $this->user_types; } function addUserType(mtgUserType $utype) { if($this->hasRPC($utype->getName())) throw new Exception("Service '{$this->name}' already has type '{$utype->getName()}'"); $this->user_types[$utype->getName()] = $utype; } function hasUserType($name) { return isset($this->user_types[$name]); } function getUserType($name) { if(!isset($this->user_types[$name])) throw new Exception("No such user type '$name'"); return $this->user_types[$name]; } function getClassId() { if($this->hasToken('class_id')) return $this->getToken('class_id'); return crc32($this->name); } function __toString() { return $this->name; } } 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 getName() { $str = ''; foreach($this->getValues() as $val) $str .= $val . ';'; return $str; } function __toString() { return $this->getName(); } } class mtgArrType implements mtgType { private $value; function __construct(mtgTypeRef $value) { $this->value = $value; } function getName() { return $this->getValue() . '[]'; } function __toString() { return $this->getName(); } 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 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 validate(mtgMetaInfo $meta) {} 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; } function override(mtgMetaEnum $other) { $this->tokens = array_merge($this->tokens, $other->getTokens()); $this->values = array_merge($this->values, $other->getValues()); } function replace(mtgMetaEnum $other) { $this->tokens = $other->getTokens(); $this->values = $other->getValues(); } } function mtg_get_all_fields(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($parent), $fields); return $fields; } function mtg_find_field_owner(mtgMetaStruct $struct, $name) { $tmp = $struct; while($tmp) { $fields = $tmp->getFields(); if(isset($fields[$name])) return $tmp; $tmp = $tmp->getParent(); } return null; } function mtg_try_get_token_in_hierarchy(mtgMetaStruct $struct, $name, &$value) { $tmp = $struct; while($tmp) { if($tmp->hasToken($name)) { $value = $tmp->getToken($name); return true; } $tmp = $tmp->getParent(); } return false; }