First commit

This commit is contained in:
Pavel Shevaev 2022-12-05 18:21:32 +03:00
commit da3f1f78e7
5 changed files with 561 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
tags

15
composer.json Normal file
View File

@ -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"
]
}
}

265
src/codegen.inc.php Normal file
View File

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

31
tpl/codegen_bundle.twig Normal file
View File

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

249
tpl/macro.twig Normal file
View File

@ -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 %}