metagen_go/src/codegen.inc.php

571 lines
15 KiB
PHP
Raw Normal View History

2022-12-08 11:40:55 +03:00
<?php
namespace metagen_go;
2022-12-08 11:40:55 +03:00
use Exception;
use Twig\{TwigTest, TwigFilter, TwigFunction};
2022-12-08 11:40:55 +03:00
2023-08-31 10:48:55 +03:00
function get_twig(array $inc_path = []): \Twig\Environment
2022-12-08 11:40:55 +03:00
{
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,
]);
2022-12-08 11:40:55 +03:00
$twig->addExtension(new \Twig\Extension\DebugExtension());
2023-08-10 12:52:45 +03:00
add_twig_tests($twig);
add_twig_filters($twig);
add_twig_functions($twig);
2022-12-08 11:40:55 +03:00
return $twig;
}
function supported_tokens()
{
return [
'table',
'table_pkey',
'table_json_kv',
2023-07-31 17:48:54 +03:00
'db_skip',
'data_root',
'bitfields',
'diffable',
'diff_removed',
'alias',
2022-12-08 11:40:55 +03:00
'default',
'virtual',
'statist',
'statist_skip',
2022-12-08 16:51:08 +03:00
'statist_alias',
2022-12-08 11:40:55 +03:00
];
}
2023-08-10 12:52:45 +03:00
function add_twig_tests(\Twig\Environment $twig)
2022-12-08 11:40:55 +03:00
{
$twig->addTest(new TwigTest(
'metaenum',
2023-08-10 12:52:45 +03:00
fn(\mtgMetaInfoUnit $u): bool => $u->object instanceof \mtgMetaEnum,
2022-12-08 11:40:55 +03:00
));
$twig->addTest(new TwigTest(
'metastruct',
2023-08-10 12:52:45 +03:00
fn(\mtgMetaInfoUnit $u): bool => $u->object instanceof \mtgMetaStruct,
2022-12-08 11:40:55 +03:00
));
$twig->addTest(new TwigTest(
'metarpc',
2023-08-10 12:52:45 +03:00
fn(\mtgMetaInfoUnit $u): bool => $u->object instanceof \mtgMetaRPC,
2022-12-08 11:40:55 +03:00
));
$twig->addTest(new TwigTest(
'enum',
2023-08-10 12:52:45 +03:00
fn(\mtgType $t): bool => $t instanceof \mtgMetaEnum,
2022-12-08 11:40:55 +03:00
));
$twig->addTest(new TwigTest(
'struct',
2023-08-10 12:52:45 +03:00
fn(\mtgType $t): bool => $t instanceof \mtgMetaStruct,
));
$twig->addTest(new TwigTest(
'array',
2023-08-10 12:52:45 +03:00
fn(\mtgType $t): bool => $t instanceof \mtgArrType,
));
$twig->addTest(new TwigTest(
'builtin',
2023-08-10 12:52:45 +03:00
fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType,
));
$twig->addTest(new TwigTest(
'numeric',
2023-08-10 12:52:45 +03:00
fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType && $t->isNumeric(),
));
$twig->addTest(new TwigTest(
'int',
2023-08-10 12:52:45 +03:00
fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType && $t->isInt(),
));
$twig->addTest(new TwigTest(
'uint',
2023-08-10 12:52:45 +03:00
fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType && $t->isUint(),
));
$twig->addTest(new TwigTest(
'bool',
2023-08-10 12:52:45 +03:00
fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType && $t->isBool(),
));
$twig->addTest(new TwigTest(
'float',
2023-08-10 12:52:45 +03:00
fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType && $t->isFloat(),
));
$twig->addTest(new TwigTest(
'double',
2023-08-10 12:52:45 +03:00
fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType && $t->isDouble(),
));
$twig->addTest(new TwigTest(
'string',
2023-08-10 12:52:45 +03:00
fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType && $t->isString(),
));
$twig->addTest(new TwigTest(
'blob',
2023-08-10 12:52:45 +03:00
fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType && $t->isBlob(),
));
$twig->addTest(new TwigTest(
'instanceof',
fn($obj, $class): bool => (new \ReflectionClass($class))->isInstance($obj),
));
}
2023-08-10 12:52:45 +03:00
function add_twig_filters(\Twig\Environment $twig)
{
$twig->addFilter(new TwigFilter(
'go_type',
2023-08-10 12:52:45 +03:00
fn(\mtgType $t): string => go_type($t),
));
$twig->addFilter(new TwigFilter(
'ucfirst',
fn(string $str): string => ucfirst($str),
));
$twig->addFilter(new TwigFilter(
'lcfirst',
fn(string $str): string => lcfirst($str),
));
$twig->addFilter(new TwigFilter(
'camel',
fn(string $str): string => snake2camel($str),
));
$twig->addFilter(new TwigFilter(
'quote',
fn(string $str, string $char = '`'): string => "{$char}{$str}{$char}"
));
$twig->addFilter(new TwigFilter(
'sql_where',
function(array $fields): string {
$exprs = [];
foreach ($fields as $field) {
$fieldName = $field->getName();
$exprs[] = "`{$fieldName}` = ?";
}
return implode(' AND ', $exprs);
2022-12-08 11:40:55 +03:00
}
));
$twig->addFilter(new TwigFilter(
'alias',
2023-08-10 12:52:45 +03:00
function(\mtgMetaField $field): string {
if ($field->hasToken('alias')) {
return $field->getToken('alias');
}
return $field->getName();
2022-12-08 11:40:55 +03:00
}
));
$twig->addFilter(new TwigFilter(
'fname',
2023-08-10 12:52:45 +03:00
fn(\mtgMetaField $field): string => snake2camel($field->getName())
));
$twig->addFilter(new TwigFilter(
'varname',
2023-08-10 12:52:45 +03:00
fn(\mtgMetaField $field): string => lcfirst(snake2camel($field->getName()))
));
2023-08-31 10:48:55 +03:00
$twig->addFilter(new TwigFilter(
'tname',
fn(\mtgMetaField $field): string => $field->getType() instanceof \mtgArrType ? $field->getType()->getValue()->getName() : $field->getType()->getName()
));
$twig->addFilter(new TwigFilter(
'builtin_type_suffix',
2023-08-10 12:52:45 +03:00
fn (\mtgBuiltinType $type): string => builtin_type_method_suffix($type),
));
$twig->addFilter(new TwigFilter(
'default_val',
2023-08-10 12:52:45 +03:00
fn(\mtgMetaField $field) => default_value($field)
));
2023-08-31 10:48:55 +03:00
$twig->addFilter(new TwigFilter(
'fields',
function (\mtgMetaField $field): array {
$type = $field->getType();
if($type instanceof \mtgArrType)
{
$type = $type->getValue();
}
return $type->getFields();
}
));
$twig->addFilter(new TwigFilter(
'pk_fields',
function (\mtgMetaField $field): array {
$type = $field->getType();
if($type instanceof \mtgArrType)
{
$type = $type->getValue();
}
$pkey = explode(',', $type->getToken('table_pkey'));
$fields = [];
foreach ($pkey as $fieldName) {
$fields[] = $type->getField($fieldName);
}
return $fields;
}
));
$twig->addFilter(new TwigFilter(
'rpc_invert',
fn(string $name): string => rpc_invert($name),
));
}
2023-08-10 12:52:45 +03:00
function add_twig_functions(\Twig\Environment $twig)
{
$twig->addFunction(new TwigFunction(
'Error',
function(string $e) {
throw new Exception($e);
}
));
$twig->addFunction(new TwigFunction(
'has_token',
fn($o, string $token): bool => $o->hasToken($token),
));
$twig->addFunction(new TwigFunction(
'has_token_in_parent',
2023-08-10 12:52:45 +03:00
fn(\mtgMetaStruct $s, string $token): bool => $s->hasTokenInParent($token),
));
$twig->addFunction(new TwigFunction(
'token',
fn($o, string $name) => $o->getToken($name),
));
$twig->addFunction(new TwigFunction(
'token_or',
fn($o, string $name, $v) => $o->hasToken($name) ? $o->getToken($name) : $v,
));
$twig->addFunction(new TwigFunction(
'get_all_fields',
2023-08-10 12:52:45 +03:00
fn(\mtgMetaStruct $s): array => \mtg_get_all_fields($s),
));
$twig->addFunction(new TwigFunction(
'arr_fill',
fn($value, int $count): array => array_fill(0, $count, $value)
));
$twig->addFunction(new TwigFunction(
'table_pkey',
2023-08-10 12:52:45 +03:00
function(\mtgMetaStruct $struct): array {
$pkey = explode(',', $struct->getToken('table_pkey'));
$fields = [];
foreach ($pkey as $fieldName) {
$fields[] = $struct->getField($fieldName);
}
return $fields;
}
));
$twig->addFunction(new TwigFunction(
'table_fields',
2023-08-10 12:52:45 +03:00
function(\mtgMetaStruct $struct): array {
$pkey = explode(',', $struct->getToken('table_pkey'));
return array_filter(
$struct->getFields(),
2023-08-10 12:52:45 +03:00
fn(\mtgMetaField $field): bool => !in_array($field->getName(), $pkey),
);
}
));
$twig->addFunction(new TwigFunction(
'meta_field',
2023-08-10 12:52:45 +03:00
function (string $str) use ($twig): \mtgMetaField {
$meta = $twig->getGlobals()['meta'];
2023-08-10 12:52:45 +03:00
return parse_meta_field($str, $meta);
}
));
$twig->addFunction(new TwigFunction('field_reset',
function($name, $type, $tokens)
{
return field_reset($name, $type, $tokens);
}
));
$twig->addFunction(new TwigFunction('buf2var',
function($name, $fname, $type, $buf, $tokens, $is_ptr)
{
return buf2var($name, $fname, $type, $buf, $tokens, $is_ptr);
}
));
$twig->addFunction(new TwigFunction('var2buf',
function($name, $fname, $type, $buf, $tokens, $is_ptr)
{
return var2buf($name, $fname, $type, $buf, $tokens, $is_ptr);
}
));
}
function snake2camel(string $str): string
{
return str_replace('_', '', ucwords($str, '_'));
}
2023-08-10 12:52:45 +03:00
function go_type(\mtgType $type, array $tokens = []): string
{
2023-08-10 12:52:45 +03:00
if($type instanceof \mtgMetaEnum)
return $type->getName();
2023-08-10 12:52:45 +03:00
else if($type instanceof \mtgMetaStruct)
return $type->getName();
2023-08-10 12:52:45 +03:00
else if($type instanceof \mtgBuiltinType)
{
if($type->isFloat())
return 'float32';
2023-08-10 12:52:45 +03:00
else if($type->isDouble())
return 'float64';
2023-08-10 12:52:45 +03:00
else if($type->isBlob())
return '[]byte';
2023-08-10 12:52:45 +03:00
else
return $type->getName();
2023-08-10 12:52:45 +03:00
}
else if($type instanceof \mtgArrType)
{
$native = go_type($type->getValue());
return "[]$native";
2023-08-10 12:52:45 +03:00
}
else
2023-08-16 14:17:23 +03:00
throw new Exception("Unknown type '{$type->getName()}'");
}
2023-08-10 12:52:45 +03:00
function builtin_type_method_suffix(\mtgBuiltinType $type): string
{
2023-08-10 12:52:45 +03:00
if($type->isBlob())
return 'Bytes';
2023-08-10 12:52:45 +03:00
return ucfirst(go_type($type));
}
2023-08-10 12:52:45 +03:00
function default_value(\mtgMetaField $field)
{
$type = $field->getType();
$tokens = $field->getTokens();
$default = $field->getToken('default');
2023-08-10 12:52:45 +03:00
if($type instanceof \mtgBuiltinType)
{
if($type->isNumeric())
return $default ?? 0;
2023-08-10 12:52:45 +03:00
else if($type->isString())
return $default ?? '""';
2023-08-10 12:52:45 +03:00
else if($type->isBool())
return $default ?? 'false';
2023-08-10 12:52:45 +03:00
else if($type->isBlob())
{
if($default == 'null')
$default = 'nil';
2023-08-10 12:52:45 +03:00
return $default ?? 'nil';
2023-08-10 12:52:45 +03:00
}
else
throw new Exception("Unknown type '$type'");
2023-08-10 12:52:45 +03:00
}
else if($type instanceof \mtgMetaEnum)
return $default ? go_type($type, $tokens)."_".trim($tokens['default'], '"') : 0;
else
throw new Exception("Unknown type '$type'");
}
2023-08-10 12:52:45 +03:00
function parse_meta_field(string $str, \mtgMetaInfo $meta): \mtgMetaField
{
list($name, $type) = explode('|', $str);
2023-08-10 12:52:45 +03:00
return new \mtgMetaField($name, new \mtgTypeRef(new \mtgBuiltinType($type)));
}
2023-08-10 12:52:45 +03:00
function builtin_type_prefix(\mtgBuiltinType $type)
{
switch($type->getName())
{
case "string":
return "String";
case "bool":
return "Bool";
case "blob":
return "Blob";
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";
default:
throw new Exception("Unknown type '{$type}'");
}
}
2023-08-10 12:52:45 +03:00
function field_reset($name, \mtgType $type, array $tokens)
{
$str = '';
2023-08-10 12:52:45 +03:00
if($type instanceof \mtgBuiltinType)
{
if($type->isNumeric())
{
if(array_key_exists('default', $tokens) && is_numeric($tokens['default']))
$str .= "self.$name = ".$tokens['default'];
else
$str .= " self.$name = 0";
}
else if($type->isString())
{
if(array_key_exists('default', $tokens))
$str .= "self.$name = ".$tokens['default'];
else
$str .= "self.$name = \"\"";
}
else if($type->isBool())
{
if(array_key_exists('default', $tokens))
$str .= "self.$name = ".$tokens['default'];
else
$str .= "self.$name = false";
}
else if($type->isBlob())
{
if(array_key_exists('default', $tokens))
$str .= "self.$name = ".$tokens['default'];
else
$str .= "self.$name = nil";
}
else
throw new Exception("Unknown type '$type'");
}
2023-08-10 12:52:45 +03:00
else if($type instanceof \mtgArrType)
{
2023-08-10 12:52:45 +03:00
$str = "if self.$name == nil { \nself.$name = make(".go_type($type, $tokens).",0) \n}\n ";
$str .= "self.$name = self.{$name}[0:0]";
}
2023-08-10 12:52:45 +03:00
else if($type instanceof \mtgMetaEnum)
{
if(array_key_exists('default', $tokens))
2023-08-10 12:52:45 +03:00
$str = "self.$name = ".go_type($type, $tokens)."_".trim($tokens['default'], '"');
else
$str = "self.$name = 0";
}
2023-08-10 12:52:45 +03:00
else if($type instanceof \mtgMetaStruct)
{
$is_virtual = array_key_exists("virtual", $tokens);
if($is_virtual)
2023-08-10 12:52:45 +03:00
$str .= "self.$name = New".ltrim(go_type($type, $tokens),'I')."() ";
else
$str .= "self.$name.Reset()";
}
else
2023-08-16 14:17:23 +03:00
throw new Exception("Unknown type '{$type->getName()}'");
return $str;
}
2023-08-10 12:52:45 +03:00
function buf2var($name, $fname, \mtgType $type, $buf, array $tokens = array(), $is_ptr = false)
{
$str = '';
2023-08-10 12:52:45 +03:00
if($type instanceof \mtgBuiltinType)
{
$str .= _read_op($tokens, $buf.'.Read'.builtin_type_prefix($type).'(&'.$fname.', "'.$name.'")');
}
2023-08-10 12:52:45 +03:00
else if($type instanceof \mtgMetaEnum)
{
$str .= _read_op($tokens, "{$buf}.ReadI32((*int32)(&{$fname}), \"$name\")");
$str .= "\n if !{$fname}.IsValid() { return errors.Errorf(\"Bad enum value %d for $name\", $fname) }";
}
2023-08-10 12:52:45 +03:00
else if($type instanceof \mtgMetaStruct)
{
if(array_key_exists('virtual', $tokens))
$str .= "if v, err := meta.ReadStructGeneric($buf, CreateById, \"{$name}\"); err != nil { return err } else { {$fname} = v.(I{$type}) }";
else
$str .= _read_op($tokens, "meta.ReadStruct($buf, ".($is_ptr?"":"&")."$fname, \"$name\")");
}
2023-08-10 12:52:45 +03:00
else if($type instanceof \mtgArrType)
{
$is_virtual = array_key_exists("virtual", $tokens);
2023-08-10 12:52:45 +03:00
$native_type = go_type($type->getValue(), $tokens);
$offset = "\n ";
$str .= "/*[]{$name}*/";
$str .= $offset . _read_op($tokens, "{$buf}.BeginContainer(\"$name\")");
$str .= $offset . "_{$name}_size, err := {$buf}.GetContainerSize()";
$str .= $offset . "if err != nil { return err }";
2023-08-10 12:52:45 +03:00
$need_new = !$is_virtual && $type->getValue() instanceof \mtgMetaStruct;
$str .= $offset . "for ; _{$name}_size > 0; _{$name}_size-- {";
$offset = "\n ";
$str .= $offset . "var tmp_{$name} {$native_type}";
$str .= $offset . buf2var("", "tmp_{$name}", $type->getValue(), $buf, $tokens, $is_virtual);
$str .= $offset . "{$fname} = append({$fname}, tmp_{$name})";
$offset = "\n ";
$str .= $offset . "}";
$str .= $offset . _read_op($tokens, "{$buf}.EndContainer()");
$str .= "\n";
}
else
2023-08-16 14:17:23 +03:00
throw new Exception("Unknown type '{$type->getName()}'");
return $str;
}
2023-08-10 12:52:45 +03:00
function var2buf($name, $fname, \mtgType $type, $buf, array $tokens = array(), $is_ptr = false)
{
$str = '';
2023-08-10 12:52:45 +03:00
if($type instanceof \mtgBuiltinType)
{
$str .= _write_op("{$buf}.Write".builtin_type_prefix($type)."($fname, \"$name\")")."\n";
}
2023-08-10 12:52:45 +03:00
else if($type instanceof \mtgMetaEnum)
{
$str .= _write_op("{$buf}.WriteI32(int32($fname), \"$name\")");
}
2023-08-10 12:52:45 +03:00
else if($type instanceof \mtgMetaStruct)
{
if(array_key_exists('virtual', $tokens))
$str .= _write_op("meta.WriteStructGeneric($buf, ".($is_ptr?"":"&")."$fname, \"$name\")");
else
$str .= _write_op("meta.WriteStruct($buf, &$fname, \"$name\")");
}
2023-08-10 12:52:45 +03:00
else if($type instanceof \mtgArrType)
{
$str .= "{$buf}.BeginContainer(\"{$name}\")\n";
$str .= " for _, v := range({$fname}) {\n";
$str .= " ".var2buf("", "v", $type->getValue(), $buf, $tokens, true)."\n";
$str .= " }\n";
$str .= " "._write_op("{$buf}.EndContainer()")."\n";
}
else
2023-08-16 14:17:23 +03:00
throw new Exception("Unknown type '{$type->getName()}'");
return $str;
}
function _write_op($op)
{
return "if err := $op; err != nil { return err }";
}
function _read_op(array $tokens, $op)
{
2023-07-26 13:09:50 +03:00
return "if err := $op; err != nil { return err }";
}
2022-12-08 17:45:28 +03:00
function rpc_invert($name)
{
if(strpos($name, '_RSP_') !== false)
return str_replace('_RSP_', '_REQ_', $name);
else
return str_replace('_REQ_', '_RSP_', $name);
}