true, 'autoescape' => false, 'strict_variables' => true, ]); $twig->addExtension(new \Twig\Extension\DebugExtension()); addTwigTests($twig); addTwigFilters($twig); addTwigFunctions($twig); return $twig; } function supported_tokens() { return [ 'table', 'table_pkey', 'table_json_kv', 'root', 'bitfields', 'diffable', 'diff_removed', 'alias', 'default', 'optional', 'virtual', 'statist', 'statist_skip', 'statist_alias', ]; } function addTwigTests(\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 addTwigFilters(\Twig\Environment $twig) { $twig->addFilter(new TwigFilter( 'go_type', fn(mtgType $t): string => goType($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 => builtinTypeMethodSuffix($type), )); $twig->addFilter(new TwigFilter( 'default_val', fn(mtgMetaField $field) => defaultValue($field) )); $twig->addFilter(new TwigFilter( 'rpc_invert', fn(string $name): string => rpc_invert($name), )); } function addTwigFunctions(\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( 'count_optional', function($os) { $opts = 0; foreach($os as $o) if($o->hasToken('optional')) ++$opts; return $opts; } )); $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 parseMetaField($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 goType(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 = goType($type->getValue()); return "[]$native"; } else { throw new Exception("Unknown type '$type'"); } } function builtinTypeMethodSuffix(mtgBuiltinType $type): string { if ($type->isBlob()) { return 'Bytes'; } return ucfirst(goType($type)); } function defaultValue(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()) { return $default ?? 'nil'; } else { throw new Exception("Unknown type '$type'"); } } else if($type instanceof mtgMetaEnum) { return $default ? goType($type, $tokens)."_".trim($tokens['default'], '"') : 0; } else { throw new Exception("Unknown type '$type'"); } } function parseMetaField(string $str, mtgMetaInfo $meta): mtgMetaField { list($name, $type) = explode('|', $str); return new mtgMetaField($name, new mtgTypeRef(new mtgBuiltinType($type), $meta)); } function go_type(\mtgType $type, array $tokens = array()) { if($type instanceof \mtgArrType) { $vtype = $type->getValue(); $native = go_type($vtype); $str = "[]"; if(array_key_exists("virtual", $tokens)) $str .= "I"; else $str .= $vtype instanceof \mtgMetaStruct ? "*" : ""; $str .= $native; return $str; } 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 \mtgMetaEnum) return $type->getName(); else if($type instanceof \mtgMetaStruct) { if(array_key_exists("virtual", $tokens)) return "I{$type->getName()}"; else return $type->getName(); } else throw new Exception("Unknown type '$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(".goType($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 = ".goType($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(goType($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 = goType($type->getValue(), $tokens); $offset = "\n "; $str .= "/*[]{$name}*/"; $str .= $offset . _read_op($tokens, "{$buf}.BeginContainer(\"$name\")"); //we don't want to propagate 'optionalness' below unset($tokens['optional']); $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 " . (array_key_exists("optional", $tokens) ? "/*optional*/nil" : "err"). " }"; } function rpc_invert($name) { if(strpos($name, '_RSP_') !== false) return str_replace('_RSP_', '_REQ_', $name); else return str_replace('_REQ_', '_RSP_', $name); }