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, array $tokens = []): string => go_type($t, $tokens), )); $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( 'tname', fn(\mtgMetaField $field): string => $field->getType() instanceof \mtgArrType ? $field->getType()->getValue()->getName() : $field->getType()->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( '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; } )); } 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); } )); } 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) { if(array_key_exists('virtual', $tokens)) { return 'I'.$type->getName(); } 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(), $tokens); return "[]$native"; } else throw new Exception("Unknown type '{$type->getName()}'"); } 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))); }