First commit
This commit is contained in:
commit
da3f1f78e7
|
@ -0,0 +1 @@
|
|||
tags
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "bit/metagen_php",
|
||||
"version" : "v0.0.9",
|
||||
"description": "PHP codgen support from meta descriptions",
|
||||
"homepage": "https://git.bit5.ru/bit/metagen_php",
|
||||
"require": {
|
||||
"php": ">=7.4",
|
||||
"twig/twig" : "v3.4.3"
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/codegen.inc.php"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
<?php
|
||||
namespace metagen_php;
|
||||
use Exception;
|
||||
|
||||
function get_twig(array $inc_path = [])
|
||||
{
|
||||
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 _add_twig_support(\Twig\Environment $twig)
|
||||
{
|
||||
$twig->addTest(new \Twig\TwigTest('instanceof',
|
||||
function($obj, $class)
|
||||
{
|
||||
return (new \ReflectionClass($class))->isInstance($obj);
|
||||
}
|
||||
));
|
||||
$twig->addFilter(new \Twig\TwigFilter('var_export',
|
||||
function($v)
|
||||
{
|
||||
return str_replace("\n", "", var_export($v, true));
|
||||
}
|
||||
));
|
||||
$twig->addFunction(new \Twig\TwigFunction('Error',
|
||||
function($e)
|
||||
{
|
||||
throw new Exception($e);
|
||||
}
|
||||
));
|
||||
$twig->addFunction(new \Twig\TwigFunction('has_token',
|
||||
function($o, $token)
|
||||
{
|
||||
return $o->hasToken($token);
|
||||
}
|
||||
));
|
||||
$twig->addFunction(new \Twig\TwigFunction('token',
|
||||
function($o, $name)
|
||||
{
|
||||
return $o->getToken($name);
|
||||
}
|
||||
));
|
||||
$twig->addFilter(new \Twig\TwigFilter('default_value',
|
||||
function($default)
|
||||
{
|
||||
return _default_value($default);
|
||||
}
|
||||
));
|
||||
$twig->addFunction(new \Twig\TwigFunction('apply_value_filters',
|
||||
function ($field_name, array $tokens, $val, $add_assoc_check = true)
|
||||
{
|
||||
return _apply_value_filters($field_name, $tokens, $val, $add_assoc_check);
|
||||
}
|
||||
));
|
||||
$twig->addFunction(new \Twig\TwigFunction('data2value',
|
||||
function($name, \mtgType $type, $buf, $prefix = '', $tokens = array(), $as_is = false, $postfix = '')
|
||||
{
|
||||
return _data2value($name, $type, $buf, $prefix, $tokens, $as_is, $postfix);
|
||||
}
|
||||
));
|
||||
$twig->addFunction(new \Twig\TwigFunction('value2data',
|
||||
function($name, \mtgType $type, $buf, $prefix = '', $tokens = array())
|
||||
{
|
||||
return _value2data($name, $type, $buf, $prefix, $tokens);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
function _default_value($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 _apply_value_filters($field_name, array $tokens, $val, $add_assoc_check = true)
|
||||
{
|
||||
$str = $val;
|
||||
foreach($tokens as $token => $args_json)
|
||||
{
|
||||
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)
|
||||
$str = "(\$assoc ? $filter_func($str, '$field_name', \$this, " . str_replace("\n", "", var_export($args, true)) . ") : $str)";
|
||||
else
|
||||
$str = "$filter_func($str, '$field_name', \$this, " . str_replace("\n", "", var_export($args, true)) . ")";
|
||||
}
|
||||
else
|
||||
throw new Exception("No such filter '$filter_func'");
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
//TODO: move it to template
|
||||
function _data2value($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) ? _default_value($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} = " . _apply_value_filters($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} = " . _apply_value_filters($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} = " . _apply_value_filters($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: removing default for field
|
||||
unset($tokens['default']);
|
||||
$str .= _data2value('$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} = " . _apply_value_filters($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";
|
||||
|
||||
//replacing some garbage
|
||||
$str = str_replace('$tmp_val__ = $tmp_val__;', '', $str);
|
||||
return $str;
|
||||
}
|
||||
|
||||
//TODO: move it to template
|
||||
function _value2data($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 .= _value2data('$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;
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
//THIS FILE IS GENERATED AUTOMATICALLY, DO NOT TOUCH IT!
|
||||
|
||||
{%- import "macro.twig" as macro -%}
|
||||
|
||||
//%bundle%
|
||||
|
||||
{{ macro.decl_units(meta) }}
|
||||
|
||||
class AutogenBundle
|
||||
{
|
||||
static function getClassName($id)
|
||||
{
|
||||
switch($id)
|
||||
{
|
||||
//%class_map%
|
||||
|
||||
default : throw new Exception("Can't find class for id: $id");
|
||||
}
|
||||
}
|
||||
|
||||
static function createRPC($code, $data)
|
||||
{
|
||||
switch($code)
|
||||
{
|
||||
//%packet_map%
|
||||
|
||||
default : throw new Exception("Can't find RPC for code: $code");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
|
||||
{% macro decl_units(meta) %}
|
||||
|
||||
{%- for u in meta.getunits ~%}
|
||||
{%- if u.object is instanceof('\\mtgMetaStruct') -%}
|
||||
{{ _self.decl_struct(u.object) }}
|
||||
{%- endif ~%}
|
||||
{%- endfor ~%}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro decl_struct(o, extra = '') %}
|
||||
|
||||
class {{o.name}} {{o.parent ? 'extends ' ~ o.parent.name}}
|
||||
{
|
||||
const CLASS_ID = {{o.classid}};
|
||||
{{ _self.struct_fields(o) }}
|
||||
|
||||
static function CLASS_PROPS()
|
||||
{
|
||||
static $props = {{o.tokens|var_export}};
|
||||
return $props;
|
||||
}
|
||||
|
||||
static function CLASS_FIELDS()
|
||||
{
|
||||
static $flds = null;
|
||||
if($flds === null)
|
||||
$flds = {{_self.fields_names(o)}};
|
||||
return $flds;
|
||||
}
|
||||
|
||||
static function CLASS_FIELDS_TYPES()
|
||||
{
|
||||
static $flds = null;
|
||||
if($flds === null)
|
||||
$flds = {{_self.fields_types(o)}};
|
||||
return $flds;
|
||||
}
|
||||
|
||||
static function CLASS_FIELDS_PROPS()
|
||||
{
|
||||
static $flds = null;
|
||||
if($flds === null)
|
||||
$flds = {{_self.fields_props(o)}};
|
||||
return $flds;
|
||||
}
|
||||
|
||||
{{extra}}
|
||||
|
||||
function __construct(&$data = null, $assoc = false)
|
||||
{
|
||||
{{_self.fields_init(o)}}
|
||||
|
||||
if(!is_null($data))
|
||||
$this->import($data, $assoc);
|
||||
}
|
||||
|
||||
function getClassId()
|
||||
{
|
||||
return self::CLASS_ID;
|
||||
}
|
||||
|
||||
function import(&$data, $assoc = false, $root = true)
|
||||
{
|
||||
$IDX = -1;
|
||||
|
||||
try
|
||||
{
|
||||
if(!is_array($data))
|
||||
throw new Exception("Bad data: $data");
|
||||
try
|
||||
{
|
||||
$IDX = 0;
|
||||
{{_self.import_fields(o)}}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
if($IDX > -1)
|
||||
{
|
||||
$FIELDS = self::CLASS_FIELDS();
|
||||
throw new Exception($e->getMessage() . " < {$FIELDS[$IDX]}");
|
||||
}
|
||||
else
|
||||
throw $e;
|
||||
}
|
||||
if($root && $assoc && sizeof($data) > 0)
|
||||
throw new Exception("Junk fields: " . implode(',', array_keys($data)));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new Exception($e->getMessage() . " < ({{o.name}})");
|
||||
}
|
||||
return $IDX;
|
||||
}
|
||||
|
||||
function export($assoc = false, $virtual = false)
|
||||
{
|
||||
$data = array();
|
||||
$this->fill($data, $assoc, $virtual);
|
||||
return $data;
|
||||
}
|
||||
|
||||
function fill(&$data, $assoc = false, $virtual = false)
|
||||
{
|
||||
if($virtual)
|
||||
mtg_php_array_set_value($data, $assoc, 'vclass__', $this->getClassId());
|
||||
try
|
||||
{
|
||||
$__last_var = null;
|
||||
$__last_val = null;
|
||||
|
||||
{{_self.export_fields(o)}}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new Exception("[%class%]\n->$__last_var\n" . serialize($__last_val) . "\n\t" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro struct_fields(o) %}
|
||||
{%- for f in o.fields ~%}
|
||||
{{ _self.decl_struct_field(o, f) }}
|
||||
{%- endfor -%}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro decl_struct_field(o, f) ~%}
|
||||
/** @var {{f.type}}*/
|
||||
{%- if f.type is instanceof('\\mtgBuiltinType') and f.type.isstring ~%}
|
||||
public ${{f.name}} = '';
|
||||
{%- elseif f.type is instanceof('\\mtgArrType') ~%}
|
||||
public ${{f.name}} = [];
|
||||
{%- else ~%}
|
||||
public ${{f.name}};
|
||||
{%- endif -%}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro fields_names(o) %}
|
||||
{%- if o.parent -%}
|
||||
array_merge(parent::CLASS_FIELDS(),
|
||||
{%- endif -%}
|
||||
|
||||
[
|
||||
{%- for f in o.fields -%}
|
||||
'{{f.name}}' {{not loop.last ? ','}}
|
||||
{%- endfor -%}
|
||||
]
|
||||
|
||||
{%- if o.parent -%}
|
||||
)
|
||||
{%- endif -%}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro fields_types(o) %}
|
||||
{%- if o.parent -%}
|
||||
array_merge(parent::CLASS_FIELDS_TYPES(),
|
||||
{%- endif -%}
|
||||
|
||||
[
|
||||
{%- for f in o.fields -%}
|
||||
'{{f.name}}' => '{{f.type}}' {{not loop.last ? ','}}
|
||||
{%- endfor -%}
|
||||
]
|
||||
|
||||
{%- if o.parent -%}
|
||||
)
|
||||
{%- endif -%}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro fields_props(o) %}
|
||||
{%- if o.parent -%}
|
||||
array_merge(parent::CLASS_FIELDS_PROPS(),
|
||||
{%- endif -%}
|
||||
|
||||
[
|
||||
{%- for f in o.fields -%}
|
||||
'{{f.name}}' => {{f.tokens|var_export}} {{not loop.last ? ','}}
|
||||
{%- endfor -%}
|
||||
]
|
||||
|
||||
{%- if o.parent -%}
|
||||
)
|
||||
{%- endif -%}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro field_init_value(o, f) %}
|
||||
{%- if f.type is instanceof('\\mtgMetaStruct') ~%}
|
||||
new {{f.type}}()
|
||||
{%- elseif f.type is instanceof('\\mtgMetaEnum') ~%}
|
||||
{%- if has_token(f, 'default') -%}
|
||||
{{f.type}}::{{token(f, 'default')|default_value}}
|
||||
{%- else -%}
|
||||
{{f.type}}::DEFAULT_VALUE
|
||||
{%- endif -%}
|
||||
{%- elseif f.type is instanceof('\\mtgArrType') ~%}
|
||||
[]
|
||||
{%- elseif f.type is instanceof('\\mtgBuiltinType') ~%}
|
||||
{%- if has_token(f, 'default') -%}
|
||||
mtg_php_val_{{f.type}}({{apply_value_filters(f.name, f.tokens, token(f, 'default')|default_value, false)}})
|
||||
{%- elseif f.type.isstring -%}
|
||||
''
|
||||
{%- else -%}
|
||||
0
|
||||
{%- endif -%}
|
||||
{%- else -%}
|
||||
{{Error("Uknown type: " ~ f.type)}}
|
||||
{%- endif -%}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro field_init(o, f) %}
|
||||
$this->{{f.name}} = {{_self.field_init_value(o, f)}};
|
||||
{% endmacro %}
|
||||
|
||||
{% macro fields_init(o) %}
|
||||
{%- if o.parent -%}
|
||||
parent::__construct();
|
||||
{%- endif -%}
|
||||
{%- for f in o.fields ~%}
|
||||
{{_self.field_init(o, f)}}
|
||||
{%- endfor -%}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro import_fields(o) %}
|
||||
{%- if o.parent -%}
|
||||
$IDX = parent::import($data, $assoc, false);
|
||||
{%- endif -%}
|
||||
{%- for f in o.fields ~%}
|
||||
{{data2value(f.name, f.type, '$data', '$this->', f.tokens)}}
|
||||
++$IDX;
|
||||
{%- endfor -%}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro export_fields(o) %}
|
||||
{%- if o.parent -%}
|
||||
$__last_var = 'parent';
|
||||
$__last_val = '<<skipped>>';
|
||||
parent::fill($data, $assoc, false);
|
||||
{%- endif -%}
|
||||
{%- for f in o.fields ~%}
|
||||
{{value2data(f.name, f.type, '$data', '$this->', f.tokens)}}
|
||||
{%- endfor -%}
|
||||
{% endmacro %}
|
Loading…
Reference in New Issue