From 155d7af19139ee959d3a770d3116a01134404c87 Mon Sep 17 00:00:00 2001 From: Pavel Shevaev Date: Tue, 6 Dec 2022 13:36:09 +0300 Subject: [PATCH] Gradually implementing PHP codegen --- src/codegen.inc.php | 30 +++---- src/data_utils.inc.php | 186 ++++++++++++++++++++++++++++++++++++++++ tpl/codegen_bundle.twig | 17 +--- tpl/macro.twig | 146 +++++++++++++++++++++++++++++-- 4 files changed, 343 insertions(+), 36 deletions(-) create mode 100644 src/data_utils.inc.php diff --git a/src/codegen.inc.php b/src/codegen.inc.php index ccfb63c..eb5efa5 100644 --- a/src/codegen.inc.php +++ b/src/codegen.inc.php @@ -54,30 +54,30 @@ function _add_twig_support(\Twig\Environment $twig) $twig->addFilter(new \Twig\TwigFilter('default_value', function($default) { - return _default_value($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); + 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); + 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); + return value2data($name, $type, $buf, $prefix, $tokens); } )); } -function _default_value($default) +function default_value($default) { if($default == "[]") return 'array()'; @@ -92,7 +92,7 @@ function _default_value($default) return str_replace('$', '\$', $default); } -function _apply_value_filters($field_name, array $tokens, $val, $add_assoc_check = true) +function apply_value_filters($field_name, array $tokens, $val, $add_assoc_check = true) { $str = $val; foreach($tokens as $token => $args_json) @@ -123,7 +123,7 @@ function _apply_value_filters($field_name, array $tokens, $val, $add_assoc_check } //TODO: move it to template -function _data2value($name, \mtgType $type, $buf, $prefix = '', $tokens = array(), $as_is = false, $postfix = '', $indent = '') +function data2value($name, \mtgType $type, $buf, $prefix = '', $tokens = array(), $as_is = false, $postfix = '', $indent = '') { $str = ''; $tmp_val = '$tmp_val__'; @@ -131,7 +131,7 @@ function _data2value($name, \mtgType $type, $buf, $prefix = '', $tokens = array( $cond_indent = $indent." "; - $default_value_arg = array_key_exists('default', $tokens) ? _default_value($tokens['default']) : 'null'; + $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; @@ -156,21 +156,21 @@ function _data2value($name, \mtgType $type, $buf, $prefix = '', $tokens = array( if($type instanceof \mtgBuiltinType) { - $str .= $cond_indent."{$tmp_val} = " . _apply_value_filters($name, $tokens, "{$tmp_val}"). ";\n"; + $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_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_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"; } @@ -183,12 +183,12 @@ function _data2value($name, \mtgType $type, $buf, $prefix = '', $tokens = array( $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 .= 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"; + $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"; } @@ -204,7 +204,7 @@ function _data2value($name, \mtgType $type, $buf, $prefix = '', $tokens = array( } //TODO: move it to template -function _value2data($name, \mtgType $type, $buf, $prefix = '', $tokens = array(), $indent = '') +function value2data($name, \mtgType $type, $buf, $prefix = '', $tokens = array(), $indent = '') { $pname = $prefix.$name; @@ -248,7 +248,7 @@ function _value2data($name, \mtgType $type, $buf, $prefix = '', $tokens = array( $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 .= 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"; diff --git a/src/data_utils.inc.php b/src/data_utils.inc.php new file mode 100644 index 0000000..f76891b --- /dev/null +++ b/src/data_utils.inc.php @@ -0,0 +1,186 @@ + 0 && $val > 0xFFFFFFFF)) + throw new Exception("Value not in range: $val"); + return $val; +} + +function val_int32($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("Incompatible type, expected int32, float given: $val"); + if($val > 2147483647 || $val < -2147483648) + throw new Exception("Value not in range: $val"); + return $val; +} + +function val_uint16($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("Incompatible type, expected uint16, float given: $val"); + if($val > 0xFFFF || $val < 0) + throw new Exception("Value not in range: $val"); + return $val; +} + +function val_int16($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("Incompatible type, expected int16, float given: $val"); + if($val > 32767 || $val < -32768) + throw new Exception("Value not in range: $val"); + return $val; +} + +function val_uint8($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("Incompatible type, expected uint8, float given: $val"); + if($val > 0xFF || $val < 0) + throw new Exception("Value not in range: $val"); + return $val; +} + +function val_int8($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("Incompatible type, expected int8, float given: $val"); + if($val > 127 || $val < -128) + throw new Exception("Value not in range: $val"); + return $val; +} + +function val_arr($val) +{ + if(!is_array($val)) + throw new Exception("Bad item, not an array(" . serialize($val) . ")"); + return $val; +} + +function 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/tpl/codegen_bundle.twig b/tpl/codegen_bundle.twig index 3a06cdd..d704ced 100644 --- a/tpl/codegen_bundle.twig +++ b/tpl/codegen_bundle.twig @@ -1,10 +1,11 @@ getClassId()); + \metagen_php\array_set_value($data, $assoc, 'vclass__', $this->getClassId()); try { $__last_var = null; @@ -115,7 +114,7 @@ class {{o.name}} {{o.parent ? 'extends ' ~ o.parent.name}} } catch(Exception $e) { - throw new Exception("[%class%]\n->$__last_var\n" . serialize($__last_val) . "\n\t" . $e->getMessage()); + throw new Exception("[{{o.name}}]\n->$__last_var\n" . serialize($__last_val) . "\n\t" . $e->getMessage()); } } } @@ -195,7 +194,7 @@ array_merge(parent::CLASS_FIELDS_PROPS(), new {{f.type}}() {%- elseif f.type is instanceof('\\mtgMetaEnum') ~%} {%- if has_token(f, 'default') -%} - {{f.type}}::{{token(f, 'default')|default_value}} + {{f.type}}::{{token(f, 'default')|replace({'"' : ''})|default_value}} {%- else -%} {{f.type}}::DEFAULT_VALUE {%- endif -%} @@ -203,7 +202,7 @@ array_merge(parent::CLASS_FIELDS_PROPS(), [] {%- 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)}}) + \metagen_php\val_{{f.type}}({{apply_value_filters(f.name, f.tokens, token(f, 'default')|default_value, false)}}) {%- elseif f.type.isstring -%} '' {%- else -%} @@ -247,3 +246,134 @@ array_merge(parent::CLASS_FIELDS_PROPS(), {{value2data(f.name, f.type, '$data', '$this->', f.tokens)}} {%- endfor -%} {% endmacro %} + +{% macro decl_enum(o) %} + +class {{o.name}} +{ + const CLASS_ID = {{o.classid}}; + + {{ _self.enum_values(o) }} + + const DEFAULT_VALUE = {{o.values | first}}; + + function getClassId() + { + return self::CLASS_ID; + } + + static function isValueValid($value) + { + $values_list = self::getValuesList(); + return in_array($value, self::$values_list_); + } + + static private $values_map_; + + static function getValueByName($name) + { + if(!self::$values_map_) + { + self::$values_map_ = array( + {{ _self.enum_values_map(o) }} + ); + } + + if(!isset(self::$values_map_[$name])) + throw new Exception("Value with name '$name' isn't defined in enum {{o.name}}. Accepted: " . implode(',', self::getNamesList())); + + return self::$values_map_[$name]; + } + + static private $names_map_; + + static function getNameByValue($value) + { + if(!self::$names_map_) + { + self::$names_map_ = array( + {{ _self.enum_names_map(o) }} + ); + } + + if(!isset(self::$names_map_[$value])) + throw new Exception("Value $value isn't defined in enum {{o.name}}. Accepted: " . implode(',', self::getValuesList())); + + return self::$names_map_[$value]; + } + + // throws exception if $value is not valid numeric enum value + static function checkValidity($value) + { + if(!is_numeric($value)) + throw new Exception("Numeric expected but got $value"); + + if(!self::isValueValid($value)) + throw new Exception("Numeric value '$value' isn't value from enum {{o.name}}. Accepted numerics are " . implode(',', self::getValuesList()) . " but better to use one of names instead: " . implode(',', self::getNamesList())); + } + + static private $values_list_; + + static function getValuesList() + { + if(!self::$values_list_) + { + self::$values_list_ = array( + {{ _self.enum_values_list(o) }} + ); + } + return self::$values_list_; + } + + static private $names_list_; + + static function getNamesList() + { + if(!self::$names_list_) + { + self::$names_list_ = array( + {{ _self.enum_names_list(o) }} + ); + } + return self::$names_list_; + } +} +{% endmacro %} + +{% macro enum_values(o) %} +{%- for n,v in o.values ~%} + const {{n}} = {{v}}; +{%- endfor ~%} +{% endmacro %} + +{% macro enum_values_map(o) %} +{%- for n,v in o.values ~%} + '{{n}}' => {{v}}, +{%- endfor ~%} +{% endmacro %} + +{% macro enum_names_map(o) %} +{%- for n,v in o.values ~%} + {{v}} => '{{n}}', +{%- endfor ~%} +{% endmacro %} + +{% macro enum_values_list(o) %} +{%- for v in o.values ~%} + {{v}}, +{%- endfor ~%} +{% endmacro %} + +{% macro enum_names_list(o) %} +{%- for n,v in o.values ~%} + '{{n}}', +{%- endfor ~%} +{% endmacro %} + +{% macro class_map(meta) %} + +{%- for u in meta.getunits ~%} + case {{u.object.classid}} : return "{{u.object.name}}"; +{%- endfor ~%} + +{% endmacro %}