metagen/targets/go/go.inc.php

629 lines
20 KiB
PHP

<?php
require_once(dirname(__FILE__) . '/../../metagen.inc.php');
require_once(dirname(__FILE__) . '/go_tpl.inc.php');
class mtgGoCodegen extends mtgCodegen
{
function genUnit(mtgMetaInfoUnit $unit)
{
$obj = $unit->object;
if($obj instanceof mtgMetaRPC)
return $this->genRPC($obj);
if($obj instanceof mtgMetaEnum)
return $this->genEnum($obj);
else if($obj instanceof mtgMetaStruct)
return $this->genStruct($obj);
else
{
echo "WARN: skipping meta unit '{$obj->getId()}'\n";
return '';
}
}
function genEnum(mtgMetaEnum $struct)
{
$templater = new mtg_go_templater();
$repl = array();
$repl['%class%'] = $struct->getName();
$repl['%class_id%'] = $struct->getClassId();
$tpl = $templater->tpl_enum();
$this->fillEnumRepls($struct, $repl);
return mtg_fill_template($tpl, $repl);
}
function fillEnumRepls(mtgMetaEnum $enum, &$repl)
{
$repl['%values%'] = '';
$repl['%vnames_list%'] = '';
$repl['%values_list%'] = '';
$vnames = array();
$values = array();
$values_map = array();
$default_value = null;
$consts = array();
$enum_name = $enum->getName();
$map = array();
foreach($enum->getValues() as $vname => $value)
{
$map[$vname] = $value;
$vnames[] = "'$vname'";
$values[] = $value;
if(!$default_value)
$default_value = "$value; // $vname";
$consts[$value] = "{$enum->getName()}_{$vname} {$enum->getName()} = $value";
}
// must be sorted
sort($values, SORT_NUMERIC);
$repl['%vnames_list%'] = implode(',', $vnames);
$repl['%values_list%'] = implode(',', $values);
$repl['%values_map%'] = $this->genIntMap($map);
$repl['%default_enum_value%'] = $default_value;
$repl['%consts%'] = "\n " . implode("\n ", $consts) . "\n";
}
function preprocessField(mtgMetaStruct $struct, mtgMetaField $field) {}
function genRPC(mtgMetaRPC $rpc)
{
return $this->genRPCPacket($rpc->getReq()) .
$this->genRPCPacket($rpc->getRsp()) .
$this->genRPCReqResp($rpc);
}
function genRPCReqResp(mtgMetaRPC $rpc)
{
$templater = new mtg_go_templater();
$repl = array();
$repl['%code%'] = $rpc->getCode();
$repl['%rpc_name%'] = $rpc->getName();
$repl['%rpc_req_name%'] = $rpc->getReq()->getName();
$repl['%rpc_rsp_name%'] = $rpc->getRsp()->getName();
$tpl = $templater->tpl_rpc();
return mtg_fill_template($tpl, $repl);
}
function genRPCPacket(mtgMetaPacket $packet)
{
$templater = new mtg_go_templater();
$repl = array();
$repl['%type%'] = $packet->getName();
$repl['%code%'] = $packet->getNumericCode();
$repl['%class%'] = $packet->getName();
$repl['%class_id%'] = $packet->getClassId();
$repl['%parent_class%'] = '';
if(strpos($packet->getName(), '_RSP_') !== false)
$repl['%class_invert%'] = str_replace('_RSP_', '_REQ_', $packet->getName());
else
$repl['%class_invert%'] = str_replace('_REQ_', '_RSP_', $packet->getName());
$fields = $packet->getFields();
$tpl = $templater->tpl_packet();
$this->fillStructRepls($packet, $repl);
return mtg_fill_template($tpl, $repl);
}
function genStruct(mtgMetaStruct $struct)
{
$templater = new mtg_go_templater();
$repl = array();
$repl['%class%'] = $struct->getName();
$repl['%class_id%'] = $struct->getClassId();
$repl['%parent_class%'] = $struct->getParent() ? ''.$struct->getParent() : "";
$tpl = $templater->tpl_struct();
$this->fillStructRepls($struct, $repl);
$result = mtg_fill_template($tpl, $repl);
if($struct->hasToken('POD') && $struct->hasToken("id") && $struct->hasToken("table") && $struct->hasToken("owner"))
$result .= "\n" . str_replace(array_keys($repl), array_values($repl), $templater->tpl_collection_item());
return $result;
}
function fillStructRepls(mtgMetaStruct $struct, &$repl)
{
foreach($struct->getFields() as $field)
$this->preprocessField($struct, $field);
$repl['%includes%'] = '';
$repl['%fields%'] = '';
$repl['%fields_names%'] = '[]string{';
$repl['%fields_props%'] = 'map[string]map[string]string{';
$repl['%fields_types%'] = 'map[string]string{';
$repl['%fields_reset%'] = '';
$repl['%read_buffer%'] = '';
$repl['%write_buffer%'] = '';
$repl['%total_top_fields%'] = sizeof($struct->getFields());
$repl['%ext_methods%'] = "";
$repl['%analytics_methods%'] = "";
$repl['%class_props%'] = 'map[string]string{' . $this->genStrMap($struct->getTokens()) . '}';
if($struct->hasToken("bitfields"))
{
$repl['%read_buffer%'] .= "\n use_mask, mask, err := reader.TryReadMask()\n";
$repl['%read_buffer%'] .= "\n if err != nil {\n";
$repl['%read_buffer%'] .= "\n return err\n";
$repl['%read_buffer%'] .= "\n }\n";
$repl['%read_buffer%'] .= "\n self.fieldsMask = mask\n";
}
$parent = $struct->getParent();
$all_fields = mtg_get_all_fields($this->info, $struct);
foreach($all_fields as $field)
{
$name = $field->getName();
$repl['%fields_names%'] .= '"' . $name . '",';
$repl['%fields_props%'] .= '"' . $this->genFieldName($field) . '" : map[string]string{' . $this->genStrMap($field->getTokens()) . "},";
$field_type = $field->getType();
$repl['%fields_types%'] .= '"' . $name . '" : "' . $field_type . '",';
}
if($struct->hasToken('statist'))
{
$table_name = $struct->getToken('statist');
$repl['%analytics_methods%'] .= "func (self *".$struct->getName().") Table() string {\n";
$repl['%analytics_methods%'] .= "\t return \"$table_name\"\n";
$repl['%analytics_methods%'] .= "}\n";
$repl['%analytics_methods%'] .= "func (self *".$struct->getName().") Columns() []string {\n";
$repl['%analytics_methods%'] .= "\treturn []string{\n";
foreach($all_fields as $field)
{
if ($field->hasToken('statist_skip'))
continue;
$column_name = $field->hasToken('statist_alias') ? $field->getToken('statist_alias') : $field->getName();
$repl['%analytics_methods%'] .= "\t\t\"$column_name\",\n";
}
$repl['%analytics_methods%'] .= "\t}\n";
$repl['%analytics_methods%'] .= "}\n";
$repl['%analytics_methods%'] .= "func (self *".$struct->getName().") Values() []interface{} {\n";
$repl['%analytics_methods%'] .= "\treturn []interface{}{\n";
foreach($all_fields as $field)
{
if ($field->hasToken('statist_skip'))
continue;
$repl['%analytics_methods%'] .= "\t\tself." . ucfirst($field->getName()) . ",\n";
}
$repl['%analytics_methods%'] .= "\t}\n";
$repl['%analytics_methods%'] .= "}\n";
}
$repl['%read_buffer%'] .= "\n _cont_size, err := reader.GetContainerSize()";
$repl['%read_buffer%'] .= "\n if err != nil {";
$repl['%read_buffer%'] .= "\n return err";
$repl['%read_buffer%'] .= "\n }";
$optional = 0;
foreach($struct->getFields() as $field)
{
if($field->hasToken('optional'))
$optional++;
}
$initial_fields_amount = count($struct->getFields()) - $optional;
$repl['%read_buffer%'] .= "\n if _cont_size < $initial_fields_amount {";
$repl['%read_buffer%'] .= "\n _cont_size = $initial_fields_amount";
$repl['%read_buffer%'] .= "\n }";
if($struct->hasToken('POD'))
{
$fields = $all_fields;
}
else
{
if($parent = $struct->getParent())
{
$repl['%read_buffer%'] .= "\n if err := self." . $parent . ".ReadFields(reader); err != nil { return err }";
$repl['%write_buffer%'] .= "\n if err := self." . $parent . ".WriteFields(writer); err != nil { return err }";
}
$fields = $struct->getFields();
}
$field_index = -1;
foreach($fields as $field)
{
++$field_index;
$options = $this->readFieldOptions($field);
$repl['%fields%'] .= "\n " . $this->genFieldDecl($field);
$repl['%fields_reset%'] .= "\n " . $this->genFieldReset($field);
$repl['%read_buffer%'] .= "\n if _cont_size <= 0 {";
$repl['%read_buffer%'] .= "\n return nil";
$repl['%read_buffer%'] .= "\n }";
if($struct->hasToken("bitfields"))
$repl['%read_buffer%'] .= "\n if !use_mask {";
$repl['%read_buffer%'] .= "\n _cont_size--";
if($struct->hasToken("bitfields"))
$repl['%read_buffer%'] .= "\n }";
if($struct->hasToken("bitfields"))
$repl['%read_buffer%'] .= "\n if !use_mask || (use_mask && self.HasValue($field_index)) {";
$repl['%read_buffer%'] .= "\n " . $this->genBufRead($field->getName(), $options['fname'], $field->getType(), $options['buf'], $field->getTokens());
if($struct->hasToken("bitfields"))
$repl['%read_buffer%'] .= "\n }";
$repl['%write_buffer%'] .= "\n " . $this->genBufWrite($field, "writer", $field->hasToken("virtual"));
}
$repl['%fields%'] = trim($repl['%fields%'], "\n ");
$repl['%fields_names%'] .= '}';
$repl['%fields_props%'] .= '}';
$repl['%fields_types%'] .= '}';
$repl['%import_from_mysql%'] = '';
$repl["%export_to_arr%"] = "";
if($struct->hasToken('POD') && $struct->hasToken("table"))
{
$ind = 0;
$repl['%import_from_mysql%'] .= "\n row := data.(".$struct->getName().")";
foreach($all_fields as $field)
{
$name = $this->genFieldName($field);
$repl['%import_from_mysql%'] .= sprintf("\n self.%s = row.%s", $name, $name);
$repl['%export_to_arr%'] .= sprintf("\n data[%d] = self.%s", $ind, $name);
$ind++;
}
}
$repl["%table_name%"] = $struct->getToken("table");
$repl["%owner%"] = $struct->getToken("owner");
$repl["%id_field_name%"] = $struct->getToken("id");
$repl["%id_field%"] = ucfirst($struct->getToken("id"));
if($struct->hasToken("bitfields"))
{
$repl['%fields%'] .= "\nfieldsMask uint64\n";
$repl['%fields_reset%'] .= "\nself.fieldsMask = 0\n";
$repl['%ext_methods%'] .= "func(self *".$struct->getName().") HasValue(index uint64) bool {\n";
$repl['%ext_methods%'] .= " value := uint64(1 << index)\n";
$repl['%ext_methods%'] .= " return (self.fieldsMask & value) > 0\n";
$repl['%ext_methods%'] .= "}\n";
$repl['%ext_methods%'] .= "func(self *".$struct->getName().") SetFieldChanged(index uint64) {\n";
$repl['%ext_methods%'] .= " value := uint64(1 << index)\n";
$repl['%ext_methods%'] .= " self.fieldsMask |= value\n";
$repl['%ext_methods%'] .= "}\n";
$repl['%ext_methods%'] .= "func(self *".$struct->getName().") IsMaskFilled() bool {\n";
$repl['%ext_methods%'] .= " return self.fieldsMask > 0\n";
$repl['%ext_methods%'] .= "}\n";
$repl['%ext_methods%'] .= "func(self *".$struct->getName().") GetMask() uint64 {\n";
$repl['%ext_methods%'] .= " return self.fieldsMask\n";
$repl['%ext_methods%'] .= "}\n";
}
}
function readFieldOptions(mtgMetaField $field)
{
return array(
'fname' => 'self.'.ucfirst($field->getName()),
'buf' => "reader",
);
}
function genReadBuiltinField($name, $fname, mtgType $type, $buf, array $tokens)
{
return $this->genRead($tokens, $buf.'.Read'.$this->genBuiltinTypePrefix($type).'(&'.$fname.', "'.$name.'")');
}
function genBufRead($name, $fname, mtgType $type, $buf, array $tokens = array(), $is_ptr = false)
{
$str = '';
if($type instanceof mtgBuiltinType)
{
$str .= $this->genReadBuiltinField($name, $fname, $type, $buf, $tokens);
}
else if($type instanceof mtgMetaEnum)
{
$str .= $this->genRead($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 .= $this->genRead($tokens, "meta.ReadStruct($buf, ".($is_ptr?"":"&")."$fname, \"$name\")");
}
else if($type instanceof mtgArrType)
{
$is_virtual = array_key_exists("virtual", $tokens);
$native_type = $this->genNativeType($type->getValue(), $tokens);
$offset = "\n ";
$str .= "/*[]{$name}*/";
$str .= $offset . $this->genRead($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 . $this->genBufRead("", "tmp_{$name}", $type->getValue(), $buf, $tokens, $is_virtual);
$str .= $offset . "{$fname} = append({$fname}, ".($need_new?"&":"")."tmp_{$name})";
$offset = "\n ";
$str .= $offset . "}";
$str .= $offset . $this->genRead($tokens, "{$buf}.EndContainer()");
$str .= "\n";
}
else
throw new Exception("Unknown type '{$type}'");
return $str;
}
function genRead(array $tokens, $op)
{
return "if err := $op; err != nil { return " . (array_key_exists("optional", $tokens) ? "/*optional*/nil" : "err"). " }";
}
function genWrite($op)
{
return "if err := $op; err != nil { return err }";
}
function genBufWrite(mtgMetaField $field, $buf, $is_ptr = false)
{
return $this->genBufWriteEx($field->getName(), "self.".ucfirst($field->getName()), $field->getType(), $buf, $field->getTokens(), $is_ptr);
}
function genBufWriteEx($name, $fname, mtgType $type, $buf, array $tokens = array(), $is_ptr = false)
{
$str = '';
if($type instanceof mtgBuiltinType)
{
$str .= $this->genWrite("{$buf}.Write".$this->genBuiltinTypePrefix($type)."($fname, \"$name\")")."\n";
}
else if($type instanceof mtgMetaEnum)
{
$str .= $this->genWrite("{$buf}.WriteI32(int32($fname), \"$name\")");
}
else if($type instanceof mtgMetaStruct)
{
if(array_key_exists('virtual', $tokens))
$str .= $this->genWrite("meta.WriteStructGeneric($buf, ".($is_ptr?"":"&")."$fname, \"$name\")");
else
$str .= $this->genWrite("meta.WriteStruct($buf, ".($is_ptr?"":"&")."$fname, \"$name\")");
}
else if($type instanceof mtgArrType)
{
$is_virtual = array_key_exists("virtual", $tokens);
$str .= "{$buf}.BeginContainer(\"{$name}\")\n";
$str .= " for _, v := range({$fname}) {\n";
$str .= " ".$this->genBufWriteEx("", "v", $type->getValue(), $buf, $tokens, true)."\n";
$str .= " }\n";
$str .= " ".$this->genWrite("{$buf}.EndContainer()")."\n";
}
else
throw new Exception("Unknown type '$type'");
return $str;
}
function genFieldDecl(mtgMetaField $field)
{
return $this->genFieldName($field) . " " .
$this->genFieldNativeType($field) .
($field->getName()[0] != '_' ? " `json:\"{$field->getName()}\"`" : "")
;
}
function genFieldName(mtgMetaField $field)
{
return ucfirst($field->getName());
}
function genFieldReset(mtgMetaField $field)
{
$tokens = $field->getTokens();
$str = '';
$name = $this->genFieldName($field);
$type = $field->getType();
if($type instanceof mtgBuiltinType)
{
if($type->isNumeric())
{
//NOTE: numeric check is against str2num
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(".$this->genFieldNativeType($field).",0) \n}\n ";
$str .= "self.$name = self.{$name}[0:0]";
}
else if($type instanceof mtgMetaEnum)
{
if(array_key_exists('default', $tokens))
$str = "self.$name = ".$this->genFieldNativeType($field)."_".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($this->genFieldNativeType($field),'I')."() ";
else
$str .= "self.$name.Reset()";
}
else
throw new Exception("Unknown type '$type'");
return $str;
}
function genFieldNativeType(mtgMetaField $field)
{
return $this->genNativeType($field->getType(), $field->getTokens());
}
function genNativeType(mtgType $type, array $tokens = array())
{
if($type instanceof mtgArrType)
{
$vtype = $type->getValue();
$native = $this->genNativePlainType($vtype);
$str = "[]";
if(array_key_exists("virtual", $tokens))
$str .= "I";
else
$str .= $vtype instanceof mtgMetaStruct ? "*" : "";
$str .= $native;
return $str;
}
else
return $this->genNativePlainType($type, $tokens);
}
function genBuiltinTypePrefix(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 genNativePlainType(mtgType $type, array $tokens = array())
{
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 genStrMap(array $source)
{
$str = '';
foreach($source as $k => $v)
$str .= '"' . $k . '": "' . ($v ? str_replace('"', '\\"', $v) : "") . '",';
return $str;
}
function genIntMap(array $source)
{
$str = '';
foreach($source as $k => $v)
$str .= "\"$k\": {$v},";
return $str;
}
function offset($num = 1)
{
return str_repeat(" ", $num);
}
}