706 lines
18 KiB
PHP
706 lines
18 KiB
PHP
<?php
|
|
namespace metagen_cs;
|
|
use Exception;
|
|
|
|
function codegen(?string $cache_dir, \mtgMetaInfo $meta, array $options = []) : string
|
|
{
|
|
$twig = get_twig();
|
|
|
|
if(!empty($cache_dir))
|
|
$twig->setCache($cache_dir);
|
|
|
|
$twig->addGlobal('meta', $meta);
|
|
|
|
$options['meta'] = $meta;
|
|
if(!isset($options['namespace']))
|
|
$options['namespace'] = 'BitGames.Autogen';
|
|
|
|
return $twig->render('codegen_bundle.twig', $options);
|
|
}
|
|
|
|
function get_twig(array $inc_path = []) : \Twig\Environment
|
|
{
|
|
array_unshift($inc_path, __DIR__ . "/../tpl/");
|
|
$loader = new \Twig\Loader\FilesystemLoader($inc_path);
|
|
|
|
$twig = new \Twig\Environment($loader, [
|
|
'debug' => true,
|
|
'autoescape' => false,
|
|
'strict_variables' => true]
|
|
);
|
|
$twig->addExtension(new \Twig\Extension\DebugExtension());
|
|
|
|
_add_twig_support($twig);
|
|
|
|
return $twig;
|
|
}
|
|
|
|
function supported_tokens() : array
|
|
{
|
|
return [
|
|
'POD',
|
|
'default',
|
|
'alias',
|
|
'virtual',
|
|
'bitfields',
|
|
'cloneable',
|
|
'obscured',
|
|
'diffable',
|
|
//TODO: this should be in a different package as a plugin
|
|
'table_pkey',
|
|
'cs_embed',
|
|
'cs_implements',
|
|
'cs_attributes',
|
|
'cs_accessor_interface',
|
|
'cs_propget_interface',
|
|
'cs_propset_interface',
|
|
'cs_propgetset_interface',
|
|
//TODO:
|
|
//'i18n'
|
|
];
|
|
}
|
|
|
|
function _add_twig_support(\Twig\Environment $twig)
|
|
{
|
|
$twig->addTest(new \Twig\TwigTest('instanceof',
|
|
function($obj, $class)
|
|
{
|
|
return (new \ReflectionClass($class))->isInstance($obj);
|
|
}
|
|
));
|
|
$twig->addFunction(new \Twig\TwigFunction('Error',
|
|
function($e)
|
|
{
|
|
throw new Exception($e);
|
|
}
|
|
));
|
|
$twig->addFunction(new \Twig\TwigFunction('has_token',
|
|
function($o, $token)
|
|
{
|
|
return (($o instanceof \mtgMetaUnit) || ($o instanceof \mtgMetaField)) && $o->hasToken($token);
|
|
}
|
|
));
|
|
$twig->addFunction(new \Twig\TwigFunction('has_token_in_parent',
|
|
function($o, $token)
|
|
{
|
|
return $o->hasTokenInParent($token);
|
|
}
|
|
));
|
|
$twig->addFunction(new \Twig\TwigFunction('token',
|
|
function($o, $name)
|
|
{
|
|
return $o->getToken($name);
|
|
}
|
|
));
|
|
$twig->addFunction(new \Twig\TwigFunction('token_or',
|
|
function($o, $name, $v)
|
|
{
|
|
if($o->hasToken($name))
|
|
return $o->getToken($name);
|
|
else
|
|
return $v;
|
|
}
|
|
));
|
|
$twig->addFunction(new \Twig\TwigFunction('field_index',
|
|
function(\mtgMetaStruct $s, string $f): int
|
|
{
|
|
$idx = get_field_index($s, $f);
|
|
if($idx == -1)
|
|
{
|
|
throw new Exception("field `$f` not found in `{$s->getName()}`");
|
|
}
|
|
|
|
return $idx;
|
|
}
|
|
));
|
|
$twig->addFunction(new \Twig\TwigFunction('find_struct',
|
|
function(string $name) use ($twig): \mtgMetaStruct
|
|
{
|
|
$meta = $twig->getGlobals()['meta'];
|
|
$unit = $meta->findUnit($name);
|
|
return $unit->object;
|
|
}
|
|
));
|
|
$twig->addFilter(new \Twig\TwigFilter('cs_type',
|
|
function($type)
|
|
{
|
|
return cs_type($type);
|
|
}
|
|
));
|
|
$twig->addFilter(new \Twig\TwigFilter('cs_type_prefix',
|
|
function($type)
|
|
{
|
|
return cs_type_prefix($type);
|
|
}
|
|
));
|
|
$twig->addFilter(new \Twig\TwigFilter('obscure_type',
|
|
function($type_str, $fld)
|
|
{
|
|
return obscure_type($type_str, $fld);
|
|
}
|
|
));
|
|
$twig->addFunction(new \Twig\TwigFunction('var_reset',
|
|
function($name, $type, $default)
|
|
{
|
|
return var_reset($name, $type, $default);
|
|
}
|
|
));
|
|
$twig->addFunction(new \Twig\TwigFunction('var_sync',
|
|
function($name, $type, $buf, $tokens, $opts)
|
|
{
|
|
return var_sync($name, $type, $buf, $tokens, $opts);
|
|
}
|
|
));
|
|
$twig->addFunction(new \Twig\TwigFunction('get_sync_opts',
|
|
function($o, $name)
|
|
{
|
|
return get_sync_opts($o, $name);
|
|
}
|
|
));
|
|
$twig->addFunction(new \Twig\TwigFunction('fields_count',
|
|
function($o)
|
|
{
|
|
return fields_count($o);
|
|
}
|
|
));
|
|
$twig->addFunction(new \Twig\TwigFunction('is_primary_field',
|
|
function($o, $f)
|
|
{
|
|
return is_primary_field($o, $f);
|
|
}
|
|
));
|
|
$twig->addFunction(new \Twig\TwigFunction('get_diff_related_units',
|
|
function($o)
|
|
{
|
|
return get_diff_all_related_structs($o);
|
|
}
|
|
));
|
|
$twig->addFunction(new \Twig\TwigFunction('get_all_declarable_accessor_interfaces',
|
|
function($o)
|
|
{
|
|
return get_all_declarable_accessor_interfaces($o);
|
|
}
|
|
));
|
|
$twig->addFunction(new \Twig\TwigFunction('get_accessor_interfaces',
|
|
function($o)
|
|
{
|
|
return get_accessor_interfaces($o);
|
|
}
|
|
));
|
|
}
|
|
|
|
function cs_type(\mtgType $type)
|
|
{
|
|
if($type instanceof \mtgBuiltinType || $type instanceof \mtgUserType)
|
|
return cs_simple_type($type);
|
|
else if($type instanceof \mtgArrType)
|
|
return "List<" . cs_simple_type($type->getValue()) . ">";
|
|
else
|
|
throw new Exception("Unknown type '{$type->getName()}'");
|
|
}
|
|
|
|
function cs_simple_type(\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}'");
|
|
}
|
|
if($type instanceof \mtgUserType && $type->hasToken("bhl_native_class"))
|
|
return $type->getToken("bhl_native_class");
|
|
return $type->getName();
|
|
}
|
|
|
|
function cs_type_prefix(\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->getName()}'");
|
|
}
|
|
}
|
|
|
|
function obscure_type($type_str, $fld)
|
|
{
|
|
if($fld->hasToken('obscured'))
|
|
{
|
|
if($fld->getType() instanceof \mtgBuiltinType && $fld->getType()->isUint32())
|
|
return "CodeStage.AntiCheat.ObscuredTypes.ObscuredUInt";
|
|
else if($fld->getType() instanceof \mtgBuiltinType && $fld->getType()->isUint64())
|
|
return "CodeStage.AntiCheat.ObscuredTypes.ObscuredULong";
|
|
else if($fld->getType() instanceof \mtgBuiltinType && $fld->getType()->isInt64())
|
|
return "CodeStage.AntiCheat.ObscuredTypes.ObscuredLong";
|
|
else if($fld->getType() instanceof \mtgBuiltinType && $fld->getType()->isFloat())
|
|
return "CodeStage.AntiCheat.ObscuredTypes.ObscuredFloat";
|
|
else
|
|
throw new Exception("Not supported obscured type: " . $fld->getType());
|
|
}
|
|
|
|
return $type_str;
|
|
}
|
|
|
|
function var_reset($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 = "if($name == null) $name = new(); else $name.Clear();";
|
|
|
|
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 = cs_type($type->getValue());
|
|
$str .= "{ $type_str tmp__";
|
|
if(is_nullable_type($type->getValue()))
|
|
$str .= " = null";
|
|
$str .= ";";
|
|
foreach($default_arr as $v)
|
|
{
|
|
$str .= var_reset("tmp__", $type->getValue(), $v);
|
|
$str .= "$name.Add(tmp__);";
|
|
}
|
|
$str .= "}";
|
|
}
|
|
}
|
|
}
|
|
else if($type instanceof \mtgMetaEnum)
|
|
{
|
|
if($default)
|
|
$str = "$name = ".cs_type($type).".".trim($default,'"')."; ";
|
|
else
|
|
$str = "$name = new ".cs_type($type)."(); ";
|
|
}
|
|
else if($type instanceof \mtgMetaStruct)
|
|
{
|
|
$str = "";
|
|
if(!is_null_str($default))
|
|
{
|
|
$is_pod = $type->hasToken('POD');
|
|
if($is_pod)
|
|
$str .= "$name.Reset(); ";
|
|
else
|
|
$str .= "if($name == null) $name = new(); else $name.Reset(); ";
|
|
}
|
|
|
|
if($default)
|
|
{
|
|
$default = is_array($default) ? $default : json_decode($default, true);
|
|
if(is_array($default))
|
|
{
|
|
foreach($default as $k => $v)
|
|
{
|
|
$kf = $type->getField($k);
|
|
$str .= var_reset("$name." . $kf->getName(), $kf->getType(), $v);
|
|
}
|
|
}
|
|
else if($default === null)
|
|
$str .= "$name = null; ";
|
|
else
|
|
throw new Exception("Bad default value for struct: " . var_export($default, true));
|
|
}
|
|
}
|
|
else
|
|
throw new Exception("Bad type '{$type->getName()}'");
|
|
return $str;
|
|
}
|
|
|
|
function is_null_str($default)
|
|
{
|
|
return is_string($default) && json_decode($default, true) === null;
|
|
}
|
|
|
|
function var_sync($fname, \mtgType $type, $buf, array $tokens, $opts)
|
|
{
|
|
$key_name = array_key_exists('alias', $tokens) ? $tokens['alias'] : $fname;
|
|
|
|
$str = '';
|
|
if($type instanceof \mtgBuiltinType)
|
|
{
|
|
$str .= "MetaIO.Sync({$buf}, ref {$fname}, \"{$key_name}\", {$opts});\n";
|
|
}
|
|
else if($type instanceof \mtgMetaStruct)
|
|
{
|
|
if(array_key_exists('virtual', $tokens))
|
|
$str .= "{$fname} = MetaIO.SyncGeneric({$buf}, {$fname}, \"{$key_name}\", {$opts});\n";
|
|
else
|
|
$str .= "MetaIO.Sync({$buf}, ref {$fname}, \"{$key_name}\", {$opts});\n";
|
|
}
|
|
else if($type instanceof \mtgMetaEnum)
|
|
{
|
|
$str .= "int __tmp_{$fname} = (int)$fname;\n";
|
|
$str .= "MetaIO.Sync({$buf}, ref __tmp_{$fname}, \"{$key_name}\", {$opts});\n";
|
|
$str .= "if($buf.is_read) {$fname} = (".cs_type($type).")__tmp_{$fname};\n";
|
|
}
|
|
else if($type instanceof \mtgArrType)
|
|
{
|
|
if(array_key_exists('virtual', $tokens))
|
|
$str .= "MetaIO.SyncGeneric({$buf}, {$fname}, \"{$key_name}\", {$opts});\n";
|
|
else
|
|
{
|
|
if($type->getValue() instanceof \mtgMetaEnum)
|
|
{
|
|
$str .= "{\n";
|
|
$str .= "var tmp_enums_list = new List<int>(); if(!{$buf}.is_read) { foreach(var _enum_tmp in {$fname}) tmp_enums_list.Add((int)_enum_tmp); }\n";
|
|
$str .= "MetaIO.Sync({$buf}, tmp_enums_list, \"{$key_name}\", {$opts});\n";
|
|
$str .= "if({$buf}.is_read) { {$fname}.Clear(); foreach(var _int_tmp in tmp_enums_list) {$fname}.Add(({$type->getValue()->getName()}) _int_tmp); }\n";
|
|
$str .= "}\n";
|
|
}
|
|
else
|
|
$str .= "MetaIO.Sync({$buf}, {$fname}, \"{$key_name}\", {$opts});\n";
|
|
}
|
|
}
|
|
else
|
|
throw new Exception("Unknown type '{$type->getName()}'");
|
|
|
|
return $str;
|
|
}
|
|
|
|
function get_sync_opts(\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 fields_count(\mtgMetaStruct $struct)
|
|
{
|
|
//NOTE: why not using fields_count(..) here as well?
|
|
// (becase they include i18n fields already?)
|
|
$count = count($struct->getFields());
|
|
$curr = $struct->getParent();
|
|
while($curr)
|
|
{
|
|
$count += fields_count_self($curr);
|
|
$curr = $curr->getParent();
|
|
}
|
|
return $count;
|
|
}
|
|
|
|
function fields_count_self(\mtgMetaStruct $struct)
|
|
{
|
|
$fields = $struct->getFields();
|
|
$count = 0;
|
|
foreach($fields as $field)
|
|
{
|
|
//TODO: do we still need this
|
|
if($field->hasToken('i18n'))
|
|
$count += 2;
|
|
else
|
|
$count += 1;
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
function is_primary_field(\mtgMetaStruct $struct, $fld)
|
|
{
|
|
$primary_fields = array();
|
|
if($struct->hasToken("table_pkey"))
|
|
$primary_fields = explode(",", $struct->getToken("table_pkey"));
|
|
|
|
return in_array($fld->getName(), $primary_fields);
|
|
}
|
|
|
|
function is_nullable_type(\mtgType $type)
|
|
{
|
|
return $type instanceof \mtgArrType ||
|
|
($type instanceof \mtgMetaStruct && !$type->hasToken('POD'));
|
|
}
|
|
|
|
function get_diff_related_units(\mtgMetaStruct $struct)
|
|
{
|
|
$result = array();
|
|
foreach($struct->getFields() as $field)
|
|
{
|
|
if($field->hasToken('nodiff'))
|
|
continue;
|
|
|
|
if($field->getType() instanceof \mtgMetaStruct)
|
|
$result[] = $field->getType();
|
|
else if($field->getType() instanceof \mtgArrType)
|
|
$result[] = $field->getType()->getValue();
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
function get_diff_all_related_structs(\mtgMetaStruct $struct): array
|
|
{
|
|
$result = array_reduce($struct->getFields(), function(array $structs, \mtgMetaField $field) {
|
|
if($field->hasToken('nodiff'))
|
|
{
|
|
return $structs;
|
|
}
|
|
|
|
$type = $field->getType();
|
|
if($type instanceof \mtgArrType)
|
|
{
|
|
$type = $type->getValue();
|
|
}
|
|
|
|
if($type instanceof \mtgMetaStruct)
|
|
{
|
|
$structs[] = $type;
|
|
}
|
|
|
|
return $structs;
|
|
}, []);
|
|
|
|
foreach($result as $s)
|
|
{
|
|
$result = array_merge(get_diff_all_related_structs($s), $result);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
class AccessorInterfaceField
|
|
{
|
|
public $field;
|
|
public $is_propget;
|
|
public $is_propset;
|
|
public $is_accessor;
|
|
|
|
function getUID()
|
|
{
|
|
return $this->field->getName();
|
|
}
|
|
|
|
function getCamelFieldName()
|
|
{
|
|
$name_parts = explode("_", $this->field->getName());
|
|
$uc_parts = array();
|
|
foreach($name_parts as $part)
|
|
$uc_parts[] = ucfirst($part);
|
|
return implode($uc_parts);
|
|
}
|
|
}
|
|
|
|
class AccessorInterface
|
|
{
|
|
public $cs_interface_name;
|
|
public $fields = array();
|
|
|
|
function getUID()
|
|
{
|
|
return $this->getInterfaceName();
|
|
}
|
|
|
|
function getInterfaceName()
|
|
{
|
|
return $this->cs_interface_name;
|
|
}
|
|
}
|
|
|
|
//NOTE: by default, accessor interface declarations will be generated in the autogen bundle.
|
|
//If you want your metagen struct to implement some existing C# inteface
|
|
//use quotes and prefix the fully-qualified interface name with "!", e.g. like this:
|
|
//
|
|
// ticket : string @cs_accessor_interface:"!BitGames.ServerIntegration.IRpcWithAuth"
|
|
//
|
|
function get_accessor_interfaces(\mtgMetaStruct $struct)
|
|
{
|
|
return get_accessor_interfaces_ex($struct, /* declarable_only = */ false);
|
|
}
|
|
|
|
function get_accessor_interfaces_declarable(\mtgMetaStruct $struct)
|
|
{
|
|
return get_accessor_interfaces_ex($struct, /* declarable_only = */ true);
|
|
}
|
|
|
|
function get_accessor_interfaces_ex(\mtgMetaStruct $struct, $declarable_only)
|
|
{
|
|
$ifs = array();
|
|
$external_mark = '"!';
|
|
foreach($struct->getFields() as $field)
|
|
{
|
|
$is_accessor = $field->hasToken('cs_accessor_interface');
|
|
$is_propget = $field->hasToken('cs_propget_interface');
|
|
$is_propset = $field->hasToken('cs_propset_interface');
|
|
$is_propgetset = $field->hasToken('cs_propgetset_interface');
|
|
if($is_accessor || $is_propget || $is_propset || $is_propgetset)
|
|
{
|
|
$ai = new AccessorInterface();
|
|
|
|
if($is_accessor)
|
|
$ai->cs_interface_name = $field->getToken('cs_accessor_interface');
|
|
elseif($is_propget)
|
|
$ai->cs_interface_name = $field->getToken('cs_propget_interface');
|
|
elseif($is_propset)
|
|
$ai->cs_interface_name = $field->getToken('cs_propset_interface');
|
|
elseif($is_propgetset)
|
|
$ai->cs_interface_name = $field->getToken('cs_propgetset_interface');
|
|
|
|
$is_external = strpos($ai->cs_interface_name, $external_mark) === 0;
|
|
if($is_external)
|
|
{
|
|
$ai->cs_interface_name = trim($ai->cs_interface_name, $external_mark);
|
|
if($declarable_only)
|
|
continue;
|
|
}
|
|
|
|
if(array_key_exists($ai->getUID(), $ifs))
|
|
$ai = $ifs[$ai->getUID()];
|
|
|
|
$aif = new AccessorInterfaceField();
|
|
$aif->field = $field;
|
|
|
|
if(array_key_exists($aif->getUID(), $ai->fields))
|
|
$aif = $ai->fields[$aif->getUID()];
|
|
|
|
$aif->is_propget |= $is_propgetset || $is_propget;
|
|
$aif->is_propset |= $is_propgetset || $is_propset;
|
|
$aif->is_accessor |= $is_accessor;
|
|
|
|
$ai->fields[$aif->getUID()] = $aif;
|
|
$ifs[$ai->getUID()] = $ai;
|
|
}
|
|
}
|
|
return $ifs;
|
|
}
|
|
|
|
function get_all_declarable_accessor_interfaces(\mtgMetaInfo $info)
|
|
{
|
|
$all = array();
|
|
foreach($info->getUnits() as $unit)
|
|
{
|
|
if($unit->object instanceof \mtgMetaStruct)
|
|
$all = array_merge($all, get_accessor_interfaces_declarable($unit->object));
|
|
else if($unit->object instanceof \mtgMetaRPC)
|
|
{
|
|
$all = array_merge($all, get_accessor_interfaces_declarable($unit->object->getReq()));
|
|
$all = array_merge($all, get_accessor_interfaces_declarable($unit->object->getRsp()));
|
|
}
|
|
}
|
|
return $all;
|
|
}
|
|
|
|
function get_field_index(\mtgMetaStruct $struct, string $field_name): int
|
|
{
|
|
$idx = -1;
|
|
foreach($struct->getFields() as $field)
|
|
{
|
|
$idx++;
|
|
if($field->getName() == $field_name)
|
|
{
|
|
return $idx;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|