true, 'autoescape' => false, 'strict_variables' => true, ]); $twig->addExtension(new \Twig\Extension\DebugExtension()); add_twig_tests($twig); add_twig_filters($twig); add_twig_functions($twig); return $twig; } function supported_tokens() { return [ 'table', 'table_pkey', 'table_json_kv', 'db_skip', 'data_root', 'bitfields', 'diffable', 'diff_removed', 'alias', 'default', 'virtual', 'statist', 'statist_skip', 'statist_alias', ]; } function add_twig_tests(\Twig\Environment $twig) { $twig->addTest(new TwigTest( 'metaenum', fn(\mtgMetaInfoUnit $u): bool => $u->object instanceof \mtgMetaEnum, )); $twig->addTest(new TwigTest( 'metastruct', fn(\mtgMetaInfoUnit $u): bool => $u->object instanceof \mtgMetaStruct, )); $twig->addTest(new TwigTest( 'metarpc', fn(\mtgMetaInfoUnit $u): bool => $u->object instanceof \mtgMetaRPC, )); $twig->addTest(new TwigTest( 'enum', fn(\mtgType $t): bool => $t instanceof \mtgMetaEnum, )); $twig->addTest(new TwigTest( 'struct', fn(\mtgType $t): bool => $t instanceof \mtgMetaStruct, )); $twig->addTest(new TwigTest( 'array', fn(\mtgType $t): bool => $t instanceof \mtgArrType, )); $twig->addTest(new TwigTest( 'builtin', fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType, )); $twig->addTest(new TwigTest( 'numeric', fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType && $t->isNumeric(), )); $twig->addTest(new TwigTest( 'int', fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType && $t->isInt(), )); $twig->addTest(new TwigTest( 'uint', fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType && $t->isUint(), )); $twig->addTest(new TwigTest( 'bool', fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType && $t->isBool(), )); $twig->addTest(new TwigTest( 'float', fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType && $t->isFloat(), )); $twig->addTest(new TwigTest( 'double', fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType && $t->isDouble(), )); $twig->addTest(new TwigTest( 'string', fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType && $t->isString(), )); $twig->addTest(new TwigTest( 'blob', fn(\mtgType $t): bool => $t instanceof \mtgBuiltinType && $t->isBlob(), )); $twig->addTest(new TwigTest( 'instanceof', fn($obj, $class): bool => (new \ReflectionClass($class))->isInstance($obj), )); } function add_twig_filters(\Twig\Environment $twig) { $twig->addFilter(new TwigFilter( 'go_type', 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); } )); $twig->addFilter(new TwigFilter( 'alias', function(\mtgMetaField $field): string { if ($field->hasToken('alias')) { return $field->getToken('alias'); } return $field->getName(); } )); $twig->addFilter(new TwigFilter( 'fname', fn(\mtgMetaField $field): string => snake2camel($field->getName()) )); $twig->addFilter(new TwigFilter( 'varname', fn(\mtgMetaField $field): string => lcfirst(snake2camel($field->getName())) )); $twig->addFilter(new TwigFilter( 'builtin_type_suffix', fn (\mtgBuiltinType $type): string => builtin_type_method_suffix($type), )); $twig->addFilter(new TwigFilter( 'default_val', fn(\mtgMetaField $field) => default_value($field) )); $twig->addFilter(new TwigFilter( 'rpc_invert', fn(string $name): string => rpc_invert($name), )); } 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', 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', 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', 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', function(\mtgMetaStruct $struct): array { $pkey = explode(',', $struct->getToken('table_pkey')); return array_filter( $struct->getFields(), fn(\mtgMetaField $field): bool => !in_array($field->getName(), $pkey), ); } )); $twig->addFunction(new TwigFunction( 'meta_field', function (string $str) use ($twig): \mtgMetaField { $meta = $twig->getGlobals()['meta']; 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, '_')); } function go_type(\mtgType $type, array $tokens = []): string { if($type instanceof \mtgMetaEnum) return $type->getName(); else if($type instanceof \mtgMetaStruct) return $type->getName(); else if($type instanceof \mtgBuiltinType) { if($type->isFloat()) return 'float32'; else if($type->isDouble()) return 'float64'; else if($type->isBlob()) return '[]byte'; else return $type->getName(); } else if($type instanceof \mtgArrType) { $native = go_type($type->getValue()); return "[]$native"; } else throw new Exception("Unknown type '$type'"); } function builtin_type_method_suffix(\mtgBuiltinType $type): string { if($type->isBlob()) return 'Bytes'; return ucfirst(go_type($type)); } function default_value(\mtgMetaField $field) { $type = $field->getType(); $tokens = $field->getTokens(); $default = $field->getToken('default'); if($type instanceof \mtgBuiltinType) { if($type->isNumeric()) return $default ?? 0; else if($type->isString()) return $default ?? '""'; else if($type->isBool()) return $default ?? 'false'; else if($type->isBlob()) { if($default == 'null') $default = 'nil'; return $default ?? 'nil'; } else throw new Exception("Unknown type '$type'"); } else if($type instanceof \mtgMetaEnum) return $default ? go_type($type, $tokens)."_".trim($tokens['default'], '"') : 0; else throw new Exception("Unknown type '$type'"); } function parse_meta_field(string $str, \mtgMetaInfo $meta): \mtgMetaField { list($name, $type) = explode('|', $str); return new \mtgMetaField($name, new \mtgTypeRef(new \mtgBuiltinType($type))); } 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}'"); } } function field_reset($name, \mtgType $type, array $tokens) { $str = ''; 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'"); } else if($type instanceof \mtgArrType) { $str = "if self.$name == nil { \nself.$name = make(".go_type($type, $tokens).",0) \n}\n "; $str .= "self.$name = self.{$name}[0:0]"; } else if($type instanceof \mtgMetaEnum) { if(array_key_exists('default', $tokens)) $str = "self.$name = ".go_type($type, $tokens)."_".trim($tokens['default'], '"'); else $str = "self.$name = 0"; } else if($type instanceof \mtgMetaStruct) { $is_virtual = array_key_exists("virtual", $tokens); if($is_virtual) $str .= "self.$name = New".ltrim(go_type($type, $tokens),'I')."() "; else $str .= "self.$name.Reset()"; } else throw new Exception("Unknown type '$type'"); return $str; } function buf2var($name, $fname, \mtgType $type, $buf, array $tokens = array(), $is_ptr = false) { $str = ''; if($type instanceof \mtgBuiltinType) { $str .= _read_op($tokens, $buf.'.Read'.builtin_type_prefix($type).'(&'.$fname.', "'.$name.'")'); } 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) }"; } 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\")"); } else if($type instanceof \mtgArrType) { $is_virtual = array_key_exists("virtual", $tokens); $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 }"; $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 throw new Exception("Unknown type '{$type}'"); return $str; } function var2buf($name, $fname, \mtgType $type, $buf, array $tokens = array(), $is_ptr = false) { $str = ''; if($type instanceof \mtgBuiltinType) { $str .= _write_op("{$buf}.Write".builtin_type_prefix($type)."($fname, \"$name\")")."\n"; } else if($type instanceof \mtgMetaEnum) { $str .= _write_op("{$buf}.WriteI32(int32($fname), \"$name\")"); } 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\")"); } 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 throw new Exception("Unknown type '$type'"); return $str; } function _write_op($op) { return "if err := $op; err != nil { return err }"; } function _read_op(array $tokens, $op) { return "if err := $op; err != nil { return err }"; } function rpc_invert($name) { if(strpos($name, '_RSP_') !== false) return str_replace('_RSP_', '_REQ_', $name); else return str_replace('_REQ_', '_RSP_', $name); }