#!/usr/bin/env python3

# Copyright © 2020-2021 Red Hat
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

# This license also covers the generated code created by the app

import argparse
import sys
import os
from pyparsing import *

typename_prefix = ""
funcname_prefix = ""
internal_validation = False

LBRACK, RBRACK, LBRACE, RBRACE, COLON, SEMI = map(Suppress, "[]{}:;")

ident = Word(alphas + "_", alphanums + "_").setName("identifier")

named_types = {}

basic_types = {
    "boolean": ("b", True, 1, "gboolean", "", '%s'),
    "byte": ("y", True, 1, "guint8", "byte ", '0x%02x'),
    "int16": ("n", True, 2, "gint16", "int16 ", '%"G_GINT16_FORMAT"'),
    "uint16": ("q", True, 2, "guint16", "uint16 ", '%"G_GUINT16_FORMAT"'),
    "int32": ("i", True, 4, "gint32", "", '%"G_GINT32_FORMAT"'),
    "uint32": ("u", True, 4, "guint32", "uint32 ", '%"G_GUINT32_FORMAT"'),
    "int64": ("x", True, 8, "gint64", "int64 ", '%"G_GINT64_FORMAT"'),
    "uint64": ("t", True, 8, "guint64", "uint64 ", '%"G_GUINT64_FORMAT"'),
    "handle": ("h", True, 4, "guint32", "handle ", '%"G_GINT32_FORMAT"'),
    "double": ("d", True, 8, "double", "", None), # double formating is special
    "string": ("s", False, 1, "const char *", "", None), # String formating is special
    "objectpath": ("o", False, 1, "const char *", "objectpath ", "\\'%s\\'"),
    "signature": ("g", False, 1, "const char *", "signature ", "\\'%s\\'"),
}


def snake_case_to_CamelCase(name):
    res = ""
    for run in name.split("_"):
        res = res + run[0].upper() + run[1:]
    return res

def CamelCase_to_snake_case(name):
    res = ""
    for i, c in enumerate(name):
        if c.isupper():
            if i > 0:
                res = res + "_"
            res = res + c.lower()
        else:
            res = res + c
    return res

def remove_prefix(text, prefix):
    return text[text.startswith(prefix) and len(prefix):]

output_c_file = None
def writeC(code, continued = False):
    if output_file:
        output_file.write(code)
        if not continued:
            output_file.write('\n')
    else:
        print(code, end='' if continued else '\n')

output_h_file = None
def writeH(code, continued = False):
    if output_h_file:
        output_h_file.write(code)
        if not continued:
            output_h_file.write('\n')
    else:
        print(code, end='' if continued else '\n')

def genC(code, extra_vars = None):
    vars = {
        'Prefix': typename_prefix,
        'prefix_': funcname_prefix,
        'PREFIX_': funcname_prefix.upper()
    }
    if extra_vars:
        vars = {**vars, **extra_vars}
    res = code.format_map(vars)
    if res[:1] == '\n':
        res = res[1:]
    return res
def escapeC(s):
    return s.replace('{', '{{').replace('}', '}}')

def C(code, extra_vars = None, continued = False):
    writeC(genC(code, extra_vars), continued)

def H(code, extra_vars = None, continued = False):
    writeH(genC(code, extra_vars), continued)

def generate_header(filename):
    H("""
#ifndef __{PREFIX_}__{FILENAME}__H__
#define __{PREFIX_}__{FILENAME}__H__
/* generated code for {filename} */
#include <string.h>
#include <glib.h>

/********** Basic types *****************/

typedef struct {{
 gconstpointer base;
 gsize size;
}} {Prefix}Ref;
""", {
    "filename": filename,
    "FILENAME": filename.upper().replace(".", "_").replace(" ", "_").replace("-", "_"),
} )

    C("""
/* Make sure generated code works with older glib versions without g_memdup2 */
static inline gpointer
_{prefix_}memdup2(gconstpointer mem, gsize byte_size)
{{
#if GLIB_CHECK_VERSION(2, 68, 0)
    return g_memdup2(mem, byte_size);
#else
    gpointer new_mem;

    if (mem && byte_size != 0) {{
        new_mem = g_malloc(byte_size);
        memcpy(new_mem, mem, byte_size);
    }} else {{
        new_mem = NULL;
    }}

    return new_mem;
#endif
}}

#define {PREFIX_}REF_READ_FRAME_OFFSET(_v, _index) {prefix_}ref_read_unaligned_le ((guchar*)((_v).base) + (_v).size - (offset_size * ((_index) + 1)), offset_size)
#define {PREFIX_}REF_ALIGN(_offset, _align_to) ((_offset + _align_to - 1) & ~(gsize)(_align_to - 1))

/* Note: clz is undefinded for 0, so never call this size == 0 */
G_GNUC_CONST static inline guint
{prefix_}ref_get_offset_size (gsize size)
{{
#if defined(__GNUC__) && (__GNUC__ >= 4) && defined(__OPTIMIZE__) && defined(__LP64__)
  /* Instead of using a lookup table we use nibbles in a lookup word */
  guint32 v = (guint32)0x88884421;
  return (v >> (((__builtin_clzl(size) ^ 63) / 8) * 4)) & 0xf;
#else
  if (size > G_MAXUINT16)
    {{
      if (size > G_MAXUINT32)
        return 8;
      else
        return 4;
    }}
  else
    {{
      if (size > G_MAXUINT8)
         return 2;
      else
         return 1;
    }}
#endif
}}

G_GNUC_PURE static inline guint64
{prefix_}ref_read_unaligned_le (guchar *bytes, guint   size)
{{
  union
  {{
    guchar bytes[8];
    guint64 integer;
  }} tmpvalue;

  tmpvalue.integer = 0;
  /* we unroll the size checks here so that memcpy gets constant args */
  if (size >= 4)
    {{
      if (size == 8)
        memcpy (&tmpvalue.bytes, bytes, 8);
      else
        memcpy (&tmpvalue.bytes, bytes, 4);
    }}
  else
    {{
      if (size == 2)
        memcpy (&tmpvalue.bytes, bytes, 2);
      else
        memcpy (&tmpvalue.bytes, bytes, 1);
    }}

  return GUINT64_FROM_LE (tmpvalue.integer);
}}

static inline void
__{prefix_}gstring_append_double (GString *string, double d)
{{
  gchar buffer[100];
  gint i;

  g_ascii_dtostr (buffer, sizeof buffer, d);
  for (i = 0; buffer[i]; i++)
    if (buffer[i] == '.' || buffer[i] == 'e' ||
        buffer[i] == 'n' || buffer[i] == 'N')
      break;

  /* if there is no '.' or 'e' in the float then add one */
  if (buffer[i] == '\\0')
    {{
      buffer[i++] = '.';
      buffer[i++] = '0';
      buffer[i++] = '\\0';
    }}
   g_string_append (string, buffer);
}}

static inline void
__{prefix_}gstring_append_string (GString *string, const char *str)
{{
  gunichar quote = strchr (str, '\\'') ? '"' : '\\'';

  g_string_append_c (string, quote);
  while (*str)
    {{
      gunichar c = g_utf8_get_char (str);

      if (c == quote || c == '\\\\')
        g_string_append_c (string, '\\\\');

      if (g_unichar_isprint (c))
        g_string_append_unichar (string, c);
      else
        {{
          g_string_append_c (string, '\\\\');
          if (c < 0x10000)
            switch (c)
              {{
              case '\\a':
                g_string_append_c (string, 'a');
                break;

              case '\\b':
                g_string_append_c (string, 'b');
                break;

              case '\\f':
                g_string_append_c (string, 'f');
                break;

              case '\\n':
                g_string_append_c (string, 'n');
                break;

              case '\\r':
                g_string_append_c (string, 'r');
                break;

              case '\\t':
                g_string_append_c (string, 't');
                break;

              case '\\v':
                g_string_append_c (string, 'v');
                break;

              default:
                g_string_append_printf (string, "u%04x", c);
                break;
              }}
           else
             g_string_append_printf (string, "U%08x", c);
        }}

      str = g_utf8_next_char (str);
    }}

  g_string_append_c (string, quote);
}}
""")

    C("""
/************** {Prefix}VariantRef *******************/
""")

    H("""
typedef struct {{
 gconstpointer base;
 gsize size;
}} {Prefix}VariantRef;
""")

    C("""
static inline {Prefix}Ref
{prefix_}variant_get_child ({Prefix}VariantRef v, const GVariantType **out_type)
{{
  if (v.size)
    {{
      guchar *base = (guchar *)v.base;
      gsize size = v.size - 1;

      /* find '\\0' character */
      while (size > 0 && base[size] != 0)
        size--;

      /* ensure we didn't just hit the start of the string */
      if (base[size] == 0)
       {{
          const char *type_string = (char *) base + size + 1;
          const char *limit = (char *)base + v.size;
          const char *end;

          if (g_variant_type_string_scan (type_string, limit, &end) && end == limit)
            {{
              if (out_type)
                *out_type = (const GVariantType *)type_string;
              return ({Prefix}Ref) {{ v.base, size }};
            }}
       }}
    }}
  if (out_type)
    *out_type = G_VARIANT_TYPE_UNIT;
  return  ({Prefix}Ref) {{ "\\0", 1 }};
}}

static inline const GVariantType *
{prefix_}variant_get_type ({Prefix}VariantRef v)
{{
  if (v.size)
    {{
      guchar *base = (guchar *)v.base;
      gsize size = v.size - 1;

      /* find '\\0' character */
      while (size > 0 && base[size] != 0)
        size--;

      /* ensure we didn't just hit the start of the string */
      if (base[size] == 0)
       {{
          const char *type_string = (char *) base + size + 1;
          const char *limit = (char *)base + v.size;
          const char *end;

          if (g_variant_type_string_scan (type_string, limit, &end) && end == limit)
             return (const GVariantType *)type_string;
       }}
    }}
  return  G_VARIANT_TYPE_UNIT;
}}

static inline gboolean
{prefix_}variant_is_type ({Prefix}VariantRef v, const GVariantType *type)
{{
   return g_variant_type_equal ({prefix_}variant_get_type (v), type);
}}

static inline {Prefix}VariantRef
{prefix_}variant_from_gvariant (GVariant *v)
{{
  g_assert (g_variant_type_equal (g_variant_get_type (v), G_VARIANT_TYPE_VARIANT));
  return ({Prefix}VariantRef) {{ g_variant_get_data (v), g_variant_get_size (v) }};
}}

static inline {Prefix}VariantRef
{prefix_}variant_from_bytes (GBytes *b)
{{
  return ({Prefix}VariantRef) {{ g_bytes_get_data (b, NULL), g_bytes_get_size (b) }};
}}

static inline {Prefix}VariantRef
{prefix_}variant_from_data (gconstpointer data, gsize size)
{{
  return ({Prefix}VariantRef) {{ data, size }};
}}

static inline GVariant *
{prefix_}variant_dup_to_gvariant ({Prefix}VariantRef v)
{{
  guint8 *duped = _{prefix_}memdup2 (v.base, v.size);
  return g_variant_new_from_data (G_VARIANT_TYPE_VARIANT, duped, v.size, TRUE, g_free, duped);
}}

static inline GVariant *
{prefix_}variant_to_gvariant ({Prefix}VariantRef v,
                              GDestroyNotify      notify,
                              gpointer            user_data)
{{
  return g_variant_new_from_data (G_VARIANT_TYPE_VARIANT, v.base, v.size, TRUE, notify, user_data);
}}

static inline GVariant *
{prefix_}variant_to_owned_gvariant ({Prefix}VariantRef v,
                                     GVariant *base)
{{
  return {prefix_}variant_to_gvariant (v, (GDestroyNotify)g_variant_unref, g_variant_ref (base));
}}

static inline GVariant *
{prefix_}variant_peek_as_variant ({Prefix}VariantRef v)
{{
  return g_variant_new_from_data (G_VARIANT_TYPE_VARIANT, v.base, v.size, TRUE, NULL, NULL);
}}

static inline {Prefix}VariantRef
{prefix_}variant_from_variant ({Prefix}VariantRef v)
{{
  const GVariantType  *type;
  {Prefix}Ref child = {prefix_}variant_get_child (v, &type);
  g_assert (g_variant_type_equal(type, G_VARIANT_TYPE_VARIANT));
  return {prefix_}variant_from_data (child.base, child.size);
}}

static inline GVariant *
{prefix_}variant_dup_child_to_gvariant ({Prefix}VariantRef v)
{{
  const GVariantType  *type;
  {Prefix}Ref child = {prefix_}variant_get_child (v, &type);
  guint8 *duped = _{prefix_}memdup2 (child.base, child.size);
  return g_variant_new_from_data (type, duped, child.size, TRUE, g_free, duped);
}}

static inline GVariant *
{prefix_}variant_peek_child_as_variant ({Prefix}VariantRef v)
{{
  const GVariantType  *type;
  {Prefix}Ref child = {prefix_}variant_get_child (v, &type);
  return g_variant_new_from_data (type, child.base, child.size, TRUE, NULL, NULL);
}}

static inline GString *
{prefix_}variant_format ({Prefix}VariantRef v, GString *s, gboolean type_annotate)
{{
#ifdef {PREFIX_}DEEP_VARIANT_FORMAT
  GVariant *gv = {prefix_}variant_peek_as_variant (v);
  return g_variant_print_string (gv, s, TRUE);
#else
  const GVariantType  *type = {prefix_}variant_get_type (v);
  g_string_append_printf (s, "<@%.*s>", (int)g_variant_type_get_string_length (type), (const char *)type);
  return s;
#endif
}}

static inline char *
{prefix_}variant_print ({Prefix}VariantRef v, gboolean type_annotate)
{{
  GString *s = g_string_new ("");
  {prefix_}variant_format (v, s, type_annotate);
  return g_string_free (s, FALSE);
}}""", {'filename': filename})

    for kind in basic_types.keys():
        basic_type = basic_types[kind]
        ctype = basic_type[3]
        if kind == "boolean":
            read_ctype = "guint8"
        else:
            read_ctype = ctype
        fixed = basic_type[1]
        C("""
static inline {ctype}
{prefix_}variant_get_{kind} ({Prefix}VariantRef v)
{{""", {"kind": kind, 'ctype': ctype})
        if fixed:
            C("  return ({ctype})*(({read_ctype} *)v.base);", {'ctype': ctype, 'read_ctype': read_ctype})
        else:
            C("  return ({ctype})v.base;", {'ctype': ctype, 'read_ctype': read_ctype})
        C("}}")

    if internal_validation:
        C("""
static inline void
{prefix_}validate_length (const GVariantType *type, gconstpointer base, gsize size, gsize length)
{{
  GVariant *v = g_variant_ref_sink (g_variant_new_from_data (type, base, size, FALSE, NULL, NULL));
  g_assert (length == g_variant_n_children (v));
  g_variant_unref (v);
}}

static inline void
{prefix_}validate_child (const GVariantType *type, gconstpointer base, gsize size, gsize index, gconstpointer child_base, gsize child_size)
{{
  GVariant *v = g_variant_ref_sink (g_variant_new_from_data (type, base, size, FALSE, NULL, NULL));
  GVariant *child = g_variant_get_child_value (v, index);
  g_assert (child != NULL);
  g_assert (child_size == g_variant_get_size (child));
  gconstpointer child_data = g_variant_get_data (child);
  g_assert (child_base == child_data || (child_data == NULL && child_size == 0));
  g_variant_unref (child);
  g_variant_unref (v);
}}""")

def generate_footer(filename):
    H("#endif")

def align_down(value, alignment):
    return value & ~(alignment - 1)

def align_up(value, alignment):
    return align_down(value + alignment - 1, alignment)

def add_named_type(name, type):
    assert not name in named_types
    type.set_typename(name, True)
    named_types[name] = type

def get_named_type(name):
    name = typename_prefix + name
    if not name in named_types:
        print ("Unknown type %s" % remove_prefix(name, typename_prefix))
        sys.exit(1)
    return named_types[name]

class TypeDef:
    def __init__(self, name, type):
        name = typename_prefix + name
        self.name = name
        self.type = type

        add_named_type(name, type)

    def generate(self, generated):
        def do_generate (type, generated):
            for c in type.get_children():
                do_generate (c, generated)
            if type.typename != None and type.typename not in generated:
                generated[type.typename] = True
                type.generate()

        do_generate(self.type, generated)


class Type:
    def __init__(self):
        self.typename = None

    def typestring(self):
        assert False

    def set_typename(self, name, override = False):
        if self.typename == None or override:
            self.typename = name
            self.propagate_typename(name)

    def propagate_typename(self, typename):
        pass

    def is_basic(self):
        return False

    def is_fixed(self):
        return False

    def get_fixed_size(self):
         return None

    def alignment(self):
        return 1

    def get_children(self):
        return []

    def add_expansion_vars(self, vars):
        pass

    def genC(self, code, extra_vars = None):
        vars = {
            'TypeName': self.typename,
            'typestring': self.typestring(),
            'ctype': self.get_ctype(),
            'alignment': self.alignment(),
            'fixed_size': self.get_fixed_size()
        }
        if self.typename:
            vars['TypeNameRef'] = self.typename + 'Ref'
            snake = CamelCase_to_snake_case(self.typename)
            vars['type_name_'] = snake + '_'
            vars['TYPE_NAME_'] = snake.upper() + '_'
        if self.is_fixed():
            vars['fixed_ctype'] = self.get_fixed_ctype()

        if extra_vars:
            vars = {**vars, **extra_vars}
        self.add_expansion_vars(vars)
        return genC(code, vars)

    def C(self, code, extra_vars = None, continued=False):
        writeC(self.genC(code, extra_vars), continued)

    def H(self, code, extra_vars = None, continued=False):
        writeH(self.genC(code, extra_vars), continued)

    def generate_types(self):
        self.C('''

/************** {TypeName} *******************/''')

        self.H('''
#define {TYPE_NAME_}TYPESTRING "{typestring}"
#define {TYPE_NAME_}TYPEFORMAT ((const GVariantType *) {TYPE_NAME_}TYPESTRING)

typedef struct {{
 gconstpointer base;
 gsize size;
}} {TypeNameRef};
''')

    def generate_standard_functions(self):
        C=self.C
        C('''

static inline {TypeNameRef}
{type_name_}from_gvariant (GVariant *v)
{{
  g_assert (g_variant_type_equal (g_variant_get_type (v), {TYPE_NAME_}TYPESTRING));
  return ({TypeNameRef}) {{ g_variant_get_data (v), g_variant_get_size (v) }};
}}

static inline {TypeNameRef}
{type_name_}from_bytes (GBytes *b)
{{''')
        if self.is_fixed():
            C('''
  g_assert (g_bytes_get_size (b) == {fixed_size});
''')
        C('''
  return ({TypeNameRef}) {{ g_bytes_get_data (b, NULL), g_bytes_get_size (b) }};
}}

static inline {TypeNameRef}
{type_name_}from_data (gconstpointer data, gsize size)
{{''')
        if self.is_fixed():
            C('''
  g_assert (size == {fixed_size});
''')
        C('''
  return ({TypeNameRef}) {{ data, size }};
}}

static inline GVariant *
{type_name_}dup_to_gvariant ({TypeNameRef} v)
{{
  guint8 *duped = _{prefix_}memdup2 (v.base, v.size);
  return g_variant_new_from_data ({TYPE_NAME_}TYPEFORMAT, duped, v.size, TRUE, g_free, duped);
}}

static inline GVariant *
{type_name_}to_gvariant ({TypeNameRef} v,
                             GDestroyNotify      notify,
                             gpointer            user_data)
{{
  return g_variant_new_from_data ({TYPE_NAME_}TYPEFORMAT, v.base, v.size, TRUE, notify, user_data);
}}

static inline GVariant *
{type_name_}to_owned_gvariant ({TypeNameRef} v, GVariant *base)
{{
  return {type_name_}to_gvariant (v, (GDestroyNotify)g_variant_unref, g_variant_ref (base));
}}

static inline GVariant *
{type_name_}peek_as_gvariant ({TypeNameRef} v)
{{
  return g_variant_new_from_data ({TYPE_NAME_}TYPEFORMAT, v.base, v.size, TRUE, NULL, NULL);
}}

static inline {TypeNameRef}
{type_name_}from_variant ({Prefix}VariantRef v)
{{
  const GVariantType  *type;
  {Prefix}Ref child = {prefix_}variant_get_child (v, &type);
  g_assert (g_variant_type_equal(type, {TYPE_NAME_}TYPESTRING));
  return {type_name_}from_data (child.base, child.size);
}}
''')

    def generate_print(self):
        self.C('''

static inline char *
{type_name_}print ({TypeNameRef} v, gboolean type_annotate)
{{
  GString *s = g_string_new ("");
  {type_name_}format (v, s, type_annotate);
  return g_string_free (s, FALSE);
}}''')

    def get_ctype(self):
         return self.typename + "Ref"

    def get_fixed_ctype(self):
         return self.typename

    def can_printf_format(self):
         return False

    def can_compare(self):
         return False

    def generate_append_value(self, value, with_type_annotate):
        return self.genC("{type_name_}format ({value}, s, {type_annotate});", {'value': value, 'type_annotate': with_type_annotate})

class BasicType(Type):
    def __init__(self, kind):
        super().__init__()
        assert kind in basic_types
        self.kind = kind
    def __repr__(self):
         return "BasicType(%s)" % self.kind
    def typestring(self):
         return basic_types[self.kind][0]
    def set_typename(self, name):
        pass # No names for basic types
    def is_basic(self):
        return True
    def is_fixed(self):
         return basic_types[self.kind][1]
    def get_fixed_size(self):
         return basic_types[self.kind][2]
    def alignment(self):
         return basic_types[self.kind][2]
    def get_ctype(self):
         return basic_types[self.kind][3]
    def get_read_ctype(self):
        if self.kind == "boolean":
            return "guint8"
        return self.get_ctype()
    def get_fixed_ctype(self):
         return self.get_read_ctype()
    def get_type_annotation(self):
        return basic_types[self.kind][4]
    def get_format_string(self):
        return basic_types[self.kind][5]
    def convert_value_for_format(self, value):
        if self.kind == "boolean":
            value = '(%s) ? "true" : "false"' % value
        return value
    def can_printf_format(self):
         return self.get_format_string() != None
    def add_expansion_vars(self, vars):
        vars['readctype'] = self.get_read_ctype()
    def generate_append_value(self, value, with_type_annotate):
        # Special case some basic types
        genC = self.genC
        if self.kind == "string":
            return genC('__{prefix_}gstring_append_string (s, {value});', {'value': value})
        elif self.kind == "double":
            return genC ('__{prefix_}gstring_append_double (s, {value});', {'value': value})
        else:
            value = self.convert_value_for_format(value)
            if with_type_annotate != "FALSE" and self.get_type_annotation() != "":
                return genC('g_string_append_printf (s, "%s{format}", {type_annotate} ? "{annotate}" : "", {value});',
                  {
                      'format': self.get_format_string(),
                      'type_annotate': with_type_annotate,
                      'annotate': self.get_type_annotation(),
                      'value': value
                  })
            else:
                return genC('g_string_append_printf (s, "{format}", {value});',
                  {
                      'format': self.get_format_string(),
                      'value': value
                  }
                )
    def can_compare(self):
        return True
    def compare_code(self, val1, val2):
        if self.is_fixed():
            if self.kind == "uint64" or self.kind == "int64":
                return "((%s) < (%s) ? -1 : (((%s) == (%s) ? 0 : 1))" % (val1, val2, val1, val2)
            else:
                return "((%s) - (%s))" % (val1, val2)
        else: # String type
            return "strcmp(%s, %s)" % (val1, val2)
    def equal_code(self, val1, val2):
        if self.is_fixed():
            return "%s == %s" % (val1, val2)
        else: # String type
            return "strcmp(%s, %s) == 0" % (val1, val2)
    def canonicalize_code(self, val):
        if self.kind == "boolean":
            return "!!%s" % val
        return val

class ArrayType(Type):
    def __init__(self, element_type):
        super().__init__()
        self.element_type = element_type

        if element_type.is_basic():
            self.typename = typename_prefix + "Arrayof" + self.element_type.kind
        elif element_type.typename:
            self.typename = typename_prefix + "Arrayof" + remove_prefix(element_type.typename, typename_prefix)

    def __repr__(self):
         return "ArrayType<%s>(%s)" % (self.typename, repr(self.element_type))
    def typestring(self):
         return "a" + self.element_type.typestring()
    def propagate_typename(self, name):
        self.element_type.set_typename (name + "Element")
    def alignment(self):
        return self.element_type.alignment()
    def get_children(self):
        return [self.element_type]
    def add_expansion_vars(self, vars):
        vars['element_ctype'] = self.element_type.get_ctype()
        vars['ElementTypename'] = self.element_type.typename
        if self.element_type.typename:
            vars['ElementTypenameRef'] = self.element_type.typename + "Ref"
        vars['element_fixed_size'] = self.element_type.get_fixed_size()
        if self.element_type.is_fixed():
            vars['element_fixed_ctype'] = self.element_type.get_fixed_ctype()
        vars['element_alignment'] = self.element_type.alignment()
        if self.element_type.is_basic():
            vars['element_read_ctype'] = self.element_type.get_read_ctype()

    def generate(self):
        super().generate_types()
        super().generate_standard_functions()
        C = self.C
        C("static inline gsize")
        C("{type_name_}get_length ({TypeNameRef} v)")
        C("{{")
        if self.element_type.is_fixed():
            C("  gsize length = v.size / {element_fixed_size};")
        else:
            C("  if (v.size == 0)");
            C("    return 0;");
            C("  guint offset_size = {prefix_}ref_get_offset_size (v.size);");
            C("  gsize last_end = {PREFIX_}REF_READ_FRAME_OFFSET(v, 0);");
            C("  gsize offsets_array_size;")
            C("  if (last_end > v.size)")
            C("    return 0;")
            C("  offsets_array_size = v.size - last_end;")
            C("  if (offsets_array_size % offset_size != 0)")
            C("    return 0;")
            C("  gsize length  = offsets_array_size / offset_size;")
        if internal_validation:
            C("  {prefix_}validate_length ({TYPE_NAME_}TYPEFORMAT, v.base, v.size, length);")
        C("  return length;")
        C("}}")
        C("")
        C("static inline {element_ctype}")
        C("{type_name_}get_at ({TypeNameRef} v, gsize index)")
        C("{{")
        if self.element_type.is_fixed():
            if self.element_type.is_basic():
                C("  return ({element_ctype})G_STRUCT_MEMBER({element_read_ctype}, v.base, index * {element_fixed_size});")
            else:
                C("  return ({ElementTypenameRef}) {{ G_STRUCT_MEMBER_P(v.base, index * {element_fixed_size}), {element_fixed_size}}};")
        else:
            # non-fixed size
            C("  guint offset_size = {prefix_}ref_get_offset_size (v.size);")
            C("  gsize last_end = {PREFIX_}REF_READ_FRAME_OFFSET(v, 0);");
            C("  gsize len = (v.size - last_end) / offset_size;")
            # Here we assume last_end is verified in get_length(), its not safe anyway to call get_at() without checking the length first
            C("  gsize start = (index > 0) ? {PREFIX_}REF_ALIGN({PREFIX_}REF_READ_FRAME_OFFSET(v, len - index), {element_alignment}) : 0;")
            C("  G_GNUC_UNUSED gsize end = {PREFIX_}REF_READ_FRAME_OFFSET(v, len - index - 1);")
            C("  g_assert (start <= end);")
            C("  g_assert (end <= last_end);")

            if internal_validation:
                C("  {prefix_}validate_child ({TYPE_NAME_}TYPEFORMAT, v.base, v.size, index, ((const char *)v.base) + start, end - start);");

            if self.element_type.is_basic(): # non-fixed basic == Stringlike
                C("  const char *base = (const char *)v.base;")
                C("  g_assert (base[end-1] == 0);")
                C("  return base + start;")
            else:
                C("  return ({ElementTypenameRef}) {{ ((const char *)v.base) + start, end - start }};")
        C("}}")

        if self.element_type.is_fixed():
            C("""

static inline const {element_fixed_ctype} *
{type_name_}peek ({TypeNameRef} v)
{{
  return (const {element_fixed_ctype} *)v.base;
}}""");
        elif self.element_type.is_basic(): # Array of string type
            C("""

static inline {element_ctype}*
{type_name_}to_strv ({TypeNameRef} v, gsize *length_out)
{{
  gsize length = {type_name_}get_length (v);
  gsize i;
  const char **resv = g_new (const char *, length + 1);

  for (i = 0; i < length; i++)
    resv[i] = {type_name_}get_at (v, i);
  resv[i] = NULL;

  if (length_out)
    *length_out = length;

  return resv;
}}""");

        C("""

static inline GString *
{type_name_}format ({TypeNameRef} v, GString *s, gboolean type_annotate)
{{
  gsize len = {type_name_}get_length (v);
  gsize i;
  if (len == 0 && type_annotate)
    g_string_append_printf (s, \"@%s \", {TYPE_NAME_}TYPESTRING);
  g_string_append_c (s, '[');
  for (i = 0; i < len; i++)
    {{
      if (i != 0)
        g_string_append (s, \", \");
      {append_element_code}
    }}
  g_string_append_c (s, ']');
  return s;
}}""",  {
    'append_element_code': escapeC(self.element_type.generate_append_value(self.genC("{type_name_}get_at (v, i)"), "((i == 0) ? type_annotate : FALSE)"))
})

        self.generate_print()

class DictType(Type):
    def __init__(self, attributes, key_type, value_type):
        super().__init__()
        self.attributes = list(attributes)
        self.key_type = key_type
        self.value_type = value_type

        self._fixed_element = value_type.is_fixed() and key_type.is_fixed();
        if self._fixed_element:
            fixed_pos = key_type.get_fixed_size()
            fixed_pos = align_up(fixed_pos, value_type.alignment()) + value_type.get_fixed_size()
            self._fixed_element_size = align_up(fixed_pos, self.alignment())
        else:
            self._fixed_element_size = None

    def __repr__(self):
         return "DictType<%s>(%s, %s)" % (self.typename, repr(self.key_type), repr(self.value_type))
    def typestring(self):
         return "a{%s%s}" % (self.key_type.typestring(), self.value_type.typestring())
    def propagate_typename(self, name):
        self.value_type.set_typename (name + "Value")
    def alignment(self):
        return max(self.value_type.alignment(), self.key_type.alignment())
    def element_is_fixed(self):
        return self._fixed_element
    def element_fixed_size(self):
        return self._fixed_element_size
    def get_children(self):
        return [self.key_type, self.value_type]
    def add_expansion_vars(self, vars):
        vars['element_fixed_size'] = self.element_fixed_size()
        vars['element_typeformat'] = '((const GVariantType *) "' + self.typestring()[1:] + '")'
        vars['value_ctype'] = self.value_type.get_ctype()
        vars['value_typename'] = self.value_type.typename
        vars['value_fixed_size'] = self.value_type.get_fixed_size()
        vars['value_alignment'] = self.value_type.alignment()
        if self.value_type.is_basic():
            vars['value_read_ctype'] = self.value_type.get_read_ctype()
        vars['key_fixed_size'] = self.key_type.get_fixed_size()
        vars['key_ctype'] = self.key_type.get_ctype()
        if self.key_type.is_basic():
            vars['key_read_ctype'] = self.key_type.get_read_ctype()

    def generate(self):
        C=self.C
        H=self.H
        super().generate_types()
        H('''
typedef struct {{
 gconstpointer base;
 gsize size;
}} {TypeName}EntryRef;
''')

        if self.element_is_fixed():
            H("typedef struct {{")
            pad_index = 1;
            pos = 0
            for k, t in [("key", self.key_type), ("value", self.value_type)]:
                old_pos = pos
                pos = align_up(pos, t.alignment())
                if pos > old_pos:
                    H("  guchar _padding{pad_index}[{pad_count}];", {'pad_index': pad_index , 'pad_count': pos - old_pos})
                    pad_index += 1
                H("  {field_type} {fieldname};", {'field_type': t.get_fixed_ctype(), 'fieldname': k})
                pos += t.get_fixed_size()
            old_pos = pos
            pos = align_up(pos, self.alignment())
            if pos > old_pos:
                H("  guchar _padding{pad_index}[{pad_count}];", {'pad_index': pad_index , 'pad_count': pos - old_pos})
                pad_index += 1
            H("}} {TypeName}Entry;")
            H("")

        super().generate_standard_functions()
        C('')

        C("static inline gsize")
        C("{type_name_}get_length ({TypeNameRef} v)")
        C("{{")
        if self.element_is_fixed():
            C("  gsize length = v.size / {element_fixed_size};")
        else:
            C("  if (v.size == 0)");
            C("    return 0;");
            C("  guint offset_size = {prefix_}ref_get_offset_size (v.size);");
            C("  gsize last_end = {PREFIX_}REF_READ_FRAME_OFFSET(v, 0);");
            C("  gsize offsets_array_size;")
            C("  if (last_end > v.size)")
            C("    return 0;")
            C("  offsets_array_size = v.size - last_end;")
            C("  if (offsets_array_size % offset_size != 0)")
            C("    return 0;")
            C("  gsize length = offsets_array_size / offset_size;")
        if internal_validation:
            C("  {prefix_}validate_length ({TYPE_NAME_}TYPEFORMAT, v.base, v.size, length);")
        C("  return length;")
        C("}}")

        C('')

        C("static inline {TypeName}EntryRef")
        C("{type_name_}get_at ({TypeNameRef} v, gsize index)")
        C("{{")
        C("  {TypeName}EntryRef res;");
        if self.element_is_fixed():
            C("  res = ({TypeName}EntryRef) {{ G_STRUCT_MEMBER_P(v.base, index * {element_fixed_size}), {element_fixed_size} }};")
        else:
            # non-fixed size
            C("  guint offset_size = {prefix_}ref_get_offset_size (v.size);")
            C("  gsize last_end = {PREFIX_}REF_READ_FRAME_OFFSET(v, 0);");
            C("  gsize len = (v.size - last_end) / offset_size;")
            # Here we assume last_end is verified in get_length(), its not safe anyway to call get_at() without checking the length first
            C("  gsize start = (index > 0) ? {PREFIX_}REF_ALIGN({PREFIX_}REF_READ_FRAME_OFFSET(v, len - index), {alignment}) : 0;")
            C("  gsize end = {PREFIX_}REF_READ_FRAME_OFFSET(v, len - index - 1);");
            C("  g_assert (start <= end);")
            C("  g_assert (end <= last_end);")
            C("  res = ({TypeName}EntryRef) {{ ((const char *)v.base) + start, end - start }};")
        if internal_validation:
            C("  {prefix_}validate_child ({TYPE_NAME_}TYPEFORMAT, v.base, v.size, index, res.base, res.size);")
        C("  return res;")
        C("}}")

        C('')

        if self.element_is_fixed():
            C("""
static inline const {TypeName}Entry *
{type_name_}peek ({TypeNameRef} v) {{
  return (const {TypeName}Entry *)v.base;
}}
""");


        C("static inline {key_ctype}")
        C("{type_name_}entry_get_key ({TypeName}EntryRef v)")
        C("{{")
        # Keys are always basic
        if self.key_type.is_fixed():
            if not self.element_is_fixed(): # No need to verify size if the entire element is fixed
                C("  g_assert (v.size >= {key_fixed_size});")
            if internal_validation:
                C("  {prefix_}validate_child ({element_typeformat}, v.base, v.size, 0, v.base, {key_fixed_size});")
            C("  return ({key_ctype})*(({key_read_ctype} *)v.base);")
        else: # string-style
            C("  guint offset_size = {prefix_}ref_get_offset_size (v.size);")
            C("  G_GNUC_UNUSED gsize end = {PREFIX_}REF_READ_FRAME_OFFSET(v, 0);");
            C("  const char *base = (const char *)v.base;")
            C("  g_assert (end < v.size);")
            C("  g_assert (base[end-1] == 0);")
            if internal_validation:
                C("  {prefix_}validate_child ({element_typeformat}, v.base, v.size, 0, v.base, end);")
            C("  return base;")
        C("}}")

        C('')

        C("static inline {value_ctype}")
        C("{type_name_}entry_get_value ({TypeName}EntryRef v)")
        C("{{")
        if not self.key_type.is_fixed():
            C("  guint offset_size = {prefix_}ref_get_offset_size (v.size);")
            C("  gsize end = {PREFIX_}REF_READ_FRAME_OFFSET(v, 0);");
            C("  gsize offset = {PREFIX_}REF_ALIGN(end, {value_alignment});")
            if self.value_type.is_fixed():
                C("  g_assert (offset == v.size - offset_size - {value_fixed_size});");
            else:
                C("  g_assert (offset <= v.size);")
            offset = "offset"
            end = "(v.size - offset_size)"
        else:
            # Fixed key, so known offset
            offset = align_up(self.key_type.get_fixed_size(), self.value_type.alignment())
            end = "v.size"
            if not self.value_type.is_fixed():
                C("  g_assert (v.size >= {offset});", {'offset': offset})

        if internal_validation:
            C("  {prefix_}validate_child ({element_typeformat}, v.base, v.size, 1, (char *)v.base + {offset},  {end} - {offset});", {'offset': offset, 'end': end })

        if self.value_type.is_basic():
            if self.value_type.is_fixed():
                C("  return ({value_ctype})*(({value_read_ctype} *)((char *)v.base + {offset}));", {'offset': offset})
            else: # string-style
                C("  g_assert (((char *)v.base)[{end} - 1] == 0);", {'end': end })
                C("  return ({value_ctype})v.base + {offset};", {'offset': offset})
        else:
            C("  return ({value_typename}Ref) {{ (char *)v.base + {offset}, {end} - {offset} }};", {'offset': offset, 'end': end })

        C("}}")

        C('')

        C("""
static inline gboolean
{type_name_}lookup ({TypeNameRef} v, {key_ctype} key, gsize *index_out, {value_ctype} *out)
{{
  {key_ctype} canonical_key = {canonicalize};""", { 'canonicalize': self.key_type.canonicalize_code("key") })
        if self.element_is_fixed():
            if "sorted" in self.attributes and self.key_type.can_compare(): # Sorted, fixed
                C("""
  gsize len = v.size / {element_fixed_size};
  gsize start = 0;
  gsize end = len;

  while (start < end)
    {{
      gsize mid = (end + start) / 2;
      {TypeName}EntryRef e = {{ ((const char *)v.base) + mid * {element_fixed_size}, {element_fixed_size} }};
      {key_ctype} e_key = {type_name_}entry_get_key (e);
      gint32 cmp = {compare};
      if (cmp == 0)
        {{
           if (index_out)
             *index_out = mid;
           if (out)
             *out = {type_name_}entry_get_value (e);
           return TRUE;
        }}
      if (cmp < 0)
        end = mid; /* canonical_key < e_key */
      else
        start = mid + 1; /* canonical_key > e_key */
    }}
    return FALSE;
}}""", {'compare': self.key_type.compare_code("canonical_key", "e_key")})
            else: # Unsorted, fixed size
                C("""
  const guchar *p = v.base;
  const guchar *end = p + v.size;
  gsize i = 0;

  while (p < end)
    {{
        {TypeName}EntryRef e = {{ p, {element_fixed_size} }};
        {key_ctype} e_key = {type_name_}entry_get_key (e);
        if ({equal})
          {{
             if (index_out)
               *index_out = i;
             if (out)
               *out = {type_name_}entry_get_value (e);
             return TRUE;
          }}
         i++;
         p += {element_fixed_size};
    }}
    return FALSE;
}}""", {
    'equal': self.key_type.equal_code("canonical_key", "e_key"),
    'canonicalize': self.key_type.canonicalize_code("key")
})
        else:
            C("""
  if (v.size == 0)
    return FALSE;
  guint offset_size = {prefix_}ref_get_offset_size (v.size);
  gsize last_end = {PREFIX_}REF_READ_FRAME_OFFSET(v, 0);
  if (last_end > v.size)
    return FALSE;
  gsize offsets_array_size = v.size - last_end;
  if (offsets_array_size % offset_size != 0)
    return FALSE;
  gsize len = offsets_array_size / offset_size;""")
            if "sorted" in self.attributes and self.key_type.can_compare(): # Sorted, non-fixed size
                C("""
  gsize start = 0;
  gsize end = len;

  while (start < end)
    {{
      gsize mid = (end + start) / 2;
      gsize mid_end = {PREFIX_}REF_READ_FRAME_OFFSET(v, len - mid - 1);
      gsize mid_start = mid == 0 ? 0 : {PREFIX_}REF_ALIGN({PREFIX_}REF_READ_FRAME_OFFSET(v, len - mid), {alignment});
      g_assert (mid_start <= mid_end);
      g_assert (mid_end <= last_end);
      {TypeName}EntryRef e = {{ ((const char *)v.base) + mid_start, mid_end - mid_start }};
      {key_ctype} e_key = {type_name_}entry_get_key (e);
      gint32 cmp = {compare};
      if (cmp == 0)
        {{
           if (index_out)
             *index_out = mid;
           if (out)
             *out = {type_name_}entry_get_value (e);
           return TRUE;
        }}
      if (cmp < 0)
        end = mid; /* canonical_key < e_key */
      else
        start = mid + 1; /* canonical_key > e_key */
    }}
    return FALSE;
}}""", { 'compare': self.key_type.compare_code("canonical_key", "e_key")})
            else: # Unsorted, non-fixed size
                C("""
  gsize start = 0;
  gsize i;

  for (i = 0; i < len; i++)
    {{
      gsize end = {PREFIX_}REF_READ_FRAME_OFFSET(v, len - i - 1);
      {TypeName}EntryRef e = {{ ((const guchar *)v.base) + start, end - start }};
      g_assert (start <= end);
      g_assert (end <= last_end);
      {key_ctype} e_key = {type_name_}entry_get_key (e);
      if ({equal})
        {{
           if (index_out)
             *index_out = i;
           if (out)
             *out = {type_name_}entry_get_value (e);
           return TRUE;
        }}
      start = {PREFIX_}REF_ALIGN(end, {alignment});
    }}
    return FALSE;
}}""", { 'equal': self.key_type.equal_code("canonical_key", "e_key") })

            C('')

            if isinstance(self.value_type, VariantType):
                for kind in basic_types.keys():
                    basic_type = basic_types[kind]
                    ctype = basic_type[3]
                    typechar = basic_type[0]
                    C("""
static inline {ctype}
{type_name_}lookup_{kind} ({TypeNameRef} v, {key_ctype} key, {ctype} default_value)
{{
   {Prefix}VariantRef value_v;

  if ({type_name_}lookup (v, key, NULL, &value_v) &&
      *(const char *){prefix_}variant_get_type (value_v) == '{typechar}')
    return {prefix_}variant_get_{kind} (value_v);
  return default_value;
}}
""", {
    'ctype': ctype,
    'typechar': typechar,
    "kind": kind
})


        C(
"""static inline GString *
{type_name_}format ({TypeNameRef} v, GString *s, gboolean type_annotate)
{{
  gsize len = {type_name_}get_length (v);
  gsize i;

  if (len == 0 && type_annotate)
    g_string_append_printf (s, \"@%s \", {TYPE_NAME_}TYPESTRING);

  g_string_append_c (s, '{{');
  for (i = 0; i < len; i++)
    {{
      {TypeName}EntryRef entry = {type_name_}get_at (v, i);
      if (i != 0)
        g_string_append (s, \", \");
      {append_key_code}
      g_string_append (s, ": ");
      {append_value_code}
    }}
  g_string_append_c (s, '}}');
  return s;
}}""",{
    'append_key_code': escapeC(self.key_type.generate_append_value(self.genC("{type_name_}entry_get_key (entry)"), "type_annotate")),
    'append_value_code': escapeC(self.value_type.generate_append_value(self.genC("{type_name_}entry_get_value (entry)"), "type_annotate")),
})

        self.generate_print()

class MaybeType(Type):
    def __init__(self, element_type):
        super().__init__()
        self.element_type = element_type
        if element_type.is_basic():
            self.typename = typename_prefix + "Maybe" + self.element_type.kind
        elif element_type.typename:
            self.typename = typename_prefix + "Maybe" + remove_prefix(element_type.typename, typename_prefix)
    def __repr__(self):
         return "MaybeType<%s>(%s, %s)" % (self.typename, repr(self.element_type))
    def typestring(self):
         return "m" + self.element_type.typestring()
    def propagate_typename(self, name):
        self.element_type.set_typename (name + "Element")
    def alignment(self):
        return self.element_type.alignment()
    def get_children(self):
        return [self.element_type]
    def add_expansion_vars(self, vars):
        vars['element_ctype'] = self.element_type.get_ctype()
        vars['ElementTypename'] = self.element_type.typename
        if self.element_type.typename:
            vars['ElementTypenameRef'] = self.element_type.typename + "Ref"
        vars['element_fixed_size'] = self.element_type.get_fixed_size()
        vars['element_alignment'] = self.element_type.alignment()
        if self.element_type.is_basic():
            vars['element_read_ctype'] = self.element_type.get_read_ctype()

    def generate(self):
        super().generate_types()
        super().generate_standard_functions()

        C=self.C
        # has_value
        C(
"""static inline gboolean
{type_name_}has_value({TypeNameRef} v)
{{
  return v.size != 0;
}}""")

        # Getter
        C("static inline {element_ctype}")
        C("{type_name_}get_value ({TypeNameRef} v)")
        C("{{")
        if self.element_type.is_fixed():
            C("  g_assert (v.size == {element_fixed_size});")
        else:
            if self.element_type.is_basic(): # string type
                C("  g_assert (v.size >= 2);") # Must be at least extra zero byte plus a terminating zero
            else:
                C("  g_assert (v.size >= 1);") # Must be at least extra zero byte

        if self.element_type.is_basic():
            if self.element_type.is_fixed():
                C("  return ({element_ctype})*(({element_read_ctype} *)v.base);")
            else: # string
                C("  const char *base = (const char *)v.base;")
                C("  g_assert (base[v.size - 2] == 0);")
                C("  return base;")
        else:
            if self.element_type.is_fixed():
                # Fixed means use whole size
                size = "v.size"
            else:
                # Otherwise, ignore extra zero byte
                size = "(v.size - 1)"
            if internal_validation:
                C("  {prefix_}validate_child ({TYPE_NAME_}TYPEFORMAT, v.base, v.size, 0, ((const char *)v.base), {size});", { 'size': size })
            C("  return ({ElementTypenameRef}) {{ v.base, {size} }};", { 'size': size })
        C("}}")

        C("static inline GString *")
        C("{type_name_}format ({TypeNameRef} v, GString *s, gboolean type_annotate)")
        C("{{")
        C("  if (type_annotate)")
        C('    g_string_append_printf (s, "@%s ", {TYPE_NAME_}TYPESTRING);')
        C("  if (v.size != 0)")
        C("    {{")
        if isinstance(self.element_type, MaybeType):
            C('      g_string_append (s, "just ");')
        C('      {append_element_code}', {
            'append_element_code': escapeC(self.element_type.generate_append_value(self.genC("{type_name_}get_value (v)"), "FALSE"))
        })
        C("    }}")
        C("  else")
        C("    {{")
        C('      g_string_append (s, "nothing");')
        C("    }}")
        C("  return s;")
        C("}}")
        self.generate_print()

class VariantType(Type):
    def __init__(self):
        super().__init__()
        self.typename = typename_prefix + "Variant"
    def __repr__(self):
         return "VariantType()"
    def typestring(self):
         return "v"
    def set_typename(self, name):
        pass # No names for variant
    def alignment(self):
        return 8
    def generate(self):
        pass # These are hardcoded in the prefix so all types can use it

class Field:
    def __init__(self, name, attributes, type):
        self.name = name
        self.attributes = list(attributes)
        self.type = type
        self.last = False
        self.struct = None
        self.index = None

    def __repr__(self):
         return "Field(%s, %s)" % (self.name, self.type)

    def propagate_typename(self, struct_name):
        self.type.set_typename (struct_name + snake_case_to_CamelCase (self.name))

    def genC(self, code, extra_vars = None):
        vars = {
            'fieldname': self.name,
            'FIELDNAME': self.name.upper(),
            'StructName': self.struct.typename,
            'StructNameRef': self.struct.typename + "Ref",
            'STRUCT_NAME_': CamelCase_to_snake_case(self.struct.typename).upper() + '_',
            'struct_name_': CamelCase_to_snake_case(self.struct.typename) + '_',
            'fieldindex': self.fieldindex,
        }
        if extra_vars:
            vars = {**vars, **extra_vars}
        return self.type.genC(code, vars)

    def C(self, code, extra_vars = None, continued = False):
        writeC(self.genC(code, extra_vars), continued=continued)

    def H(self, code, extra_vars = None, continued = False):
        writeH(self.genC(code, extra_vars), continued=continued)

    def generate(self):
        # Getter
        C=self.C
        H=self.H
        genC=self.genC
        H("#define {STRUCT_NAME_}INDEXOF_{FIELDNAME} {fieldindex}")
        H("")
        C("static inline {ctype}")
        C("{struct_name_}get_{fieldname} ({StructName}Ref v)")
        C("{{")

        if not self.type.is_fixed() or self.table_i >= 0:
            C("  guint offset_size = {prefix_}ref_get_offset_size (v.size);");

        if self.table_i == -1:
            offset = "((%d) & (~(gsize)%d)) + %d" % (self.table_a + self.table_b, self.table_b, self.table_c)
        else:
            C("  gsize last_end = {PREFIX_}REF_READ_FRAME_OFFSET(v, {table_i});", {'table_i': self.table_i });
            offset = "((last_end + %d) & (~(gsize)%d)) + %d" % (self.table_a + self.table_b, self.table_b, self.table_c)
        C("  guint offset = {offset};", {'offset': offset});

        if self.type.is_basic():
            if self.type.is_fixed():
                if not self.struct.is_fixed():
                    C("  g_assert (offset + {fixed_size} <= v.size);");
                val = genC("({ctype})G_STRUCT_MEMBER({readctype}, v.base, offset)")
                if "bigendian" in self.attributes:
                    val = "%s_FROM_BE(%s)" % (self.type.get_ctype().upper(), val)
                if "littleendian" in self.attributes:
                    val = "%s_FROM_LE(%s)" % (self.type.get_ctype().upper(), val)
                C("  return {val};", {"val": val})
            else: # string
                C("  const char *base = (const char *)v.base;")
                C("  gsize start = offset;");
                if self.last:
                    C("  G_GNUC_UNUSED gsize end = v.size - offset_size * {framing_offset_size};", {'framing_offset_size': self.struct.framing_offset_size })
                else:
                    C("  G_GNUC_UNUSED gsize end = {PREFIX_}REF_READ_FRAME_OFFSET(v, %d);" % (self.table_i + 1));
                C("  g_assert (start <= end);");
                C("  g_assert (end <= v.size);");
                C("  g_assert (base[end-1] == 0);");
                if internal_validation:
                    C("  {prefix_}validate_child ({STRUCT_NAME_}TYPEFORMAT, v.base, v.size, {fieldindex}, (const char *)v.base + start, end - start);")
                C("  return &G_STRUCT_MEMBER(const char, v.base, start);")
        else:
            if self.type.is_fixed():
                if not self.struct.is_fixed():
                    C("  g_assert (offset + {fixed_size} <= v.size);");
                C("  return ({TypeNameRef}) {{ G_STRUCT_MEMBER_P(v.base, offset), {fixed_size} }};")
            else:
                C("  gsize start = offset;");
                if self.last:
                    C("  gsize end = v.size - offset_size * {framing_offset_size};", {'framing_offset_size': self.struct.framing_offset_size })
                else:
                    C("  gsize end = {PREFIX_}REF_READ_FRAME_OFFSET(v, %d);" % (self.table_i + 1));
                C("  g_assert (start <= end);");
                C("  g_assert (end <= v.size);");
                if internal_validation:
                    C("  {prefix_}validate_child ({STRUCT_NAME_}TYPEFORMAT, v.base, v.size, {fieldindex}, (const char *)v.base + start, end - start);")
                C("  return ({TypeNameRef}) {{ G_STRUCT_MEMBER_P(v.base, start), end - start }};")
        C("}}")
        C("")

        if self.type.is_fixed() and not self.type.is_basic():
            C(
"""static inline const {fixed_ctype} *
{struct_name_}peek_{fieldname} ({StructName}Ref v) {{
  return ({fixed_ctype} *){struct_name_}get_{fieldname} (v).base;
}}
""")
        elif isinstance(self.type, ArrayType) and self.type.element_type.is_fixed():
            C(
"""static inline const {element_fixed_ctype} *
{struct_name_}peek_{fieldname} ({StructName}Ref v, gsize *len) {{
  {ctype} a = {struct_name_}get_{fieldname} (v);
  if (len != NULL)
    *len = {type_name_}get_length (a);
  return (const {element_fixed_ctype} *)a.base;
}}
""", {
    'element_fixed_ctype': self.type.element_type.get_fixed_ctype(),
})
        elif isinstance(self.type, DictType) and self.type.element_is_fixed():
            C(
"""static inline const {element_fixed_ctype} *
{struct_name_}peek_{fieldname} ({StructName}Ref v, gsize *len) {{
  {ctype} a = {struct_name_}get_{fieldname} (v);
  if (len != NULL)
    *len = {type_name_}get_length (a);
  return (const {element_fixed_ctype} *)a.base;
}}
""", {
    'element_fixed_ctype': self.type.typename + "Entry",
})

    def generate_fixed(self):
        comments = []
        if "bigendian" in self.attributes:
            comments.append("big endian")
        if "littleendian" in self.attributes:
            comments.append("little endian")
        self.H("  {field_type} {fieldname};{comment}", {
            'field_type': self.type.get_fixed_ctype(),
            'comment': "" if len(comments) == 0 else "/* " +  ",".join(comments) + " */",
        })

class StructType(Type):
    def __init__(self, fields):
        super().__init__()
        self.fields = list(fields)

        if len(self.fields) > 0:
            self.fields[len(self.fields) - 1].last = True

        for i, f in enumerate(self.fields):
            f.struct = self
            f.fieldindex = i

        framing_offset_size = 0
        fixed = True
        fixed_pos = 0
        for f in fields:
            if f.type.is_fixed():
                fixed_pos = align_up(fixed_pos, f.type.alignment()) + f.type.get_fixed_size()
            else:
                fixed = False
                if not f.last:
                    framing_offset_size = framing_offset_size + 1

        self.framing_offset_size = framing_offset_size
        self._fixed = fixed
        self._fixed_size = None;
        if fixed:
            if fixed_pos == 0: # Special case unit struct
                self._fixed_size = 1;
            else:
                # Round up to alignment
                self._fixed_size = align_up(fixed_pos, self.alignment())

        def tuple_align(offset, alignment):
            return offset + ((-offset) & alignment)

        # This is code equivalend to tuple_generate_table() in gvariantinfo.c, see its docs
        i = -1
        a = 0
        b = 0
        c = 0
        for f in fields:
            d = f.type.alignment() - 1;
            e = f.type.get_fixed_size() if f.type.is_fixed() else 0

            # align to 'd'
            if d <= b: # rule 1
                c = tuple_align(c, d)
            else: # rule 2
                a = a + tuple_align(c, b)
                b = d
                c = 0

            # the start of the item is at this point (ie: right after we
            # have aligned for it).  store this information in the table.
            f.table_i = i
            f.table_a = a
            f.table_b = b
            f.table_c = c

            # "move past" the item by adding in its size.
            if e == 0:
                # variable size:
                #
                # we'll have an offset stored to mark the end of this item, so
                # just bump the offset index to give us a new starting point
                # and reset all the counters.
                i = i + 1
                a = b = c = 0
            else:
                # fixed size
                c = c + e # rule 3

    def __repr__(self):
        return "StructType<%s>(%s)" % (self.typename, ",".join(map(repr, self.fields)))

    def typestring(self):
        res = ['(']
        for f in self.fields:
            res.append(f.type.typestring())
        res.append(')')
        return "".join(res)

    def get_children(self):
        children = []
        for f in self.fields:
            children.append(f.type)
        return children

    def propagate_typename(self, name):
        for f in self.fields:
            f.propagate_typename(name)

    def alignment(self):
        alignment = 1;
        for f in self.fields:
            alignment = max(alignment, f.type.alignment())
        return alignment

    def is_fixed(self):
        return self._fixed;
    def get_fixed_size(self):
        return self._fixed_size

    def generate(self):
        C=self.C
        H=self.H
        super().generate_types()

        if self.is_fixed():
            H("typedef struct {{")
            pos = 0
            pad_index = 1;
            for f in self.fields:
                old_pos = pos
                pos = align_up(pos, f.type.alignment())
                if pos > old_pos:
                    H("  guchar _padding{pad_index}[{pad_count}];", {'pad_index': pad_index , 'pad_count': pos - old_pos})
                    pad_index += 1
                f.generate_fixed()
                pos += f.type.get_fixed_size()
            old_pos = pos
            pos = align_up(pos, self.alignment())
            if pos > old_pos:
                H("  guchar _padding{pad_index}[{pad_count}];", {'pad_index': pad_index , 'pad_count': pos - old_pos})
                pad_index += 1
            H("}} {TypeName};")


        super().generate_standard_functions()

        if self.is_fixed():
            C(
"""static inline const {fixed_ctype} *
{type_name_}peek ({TypeNameRef} v) {{
  return (const {fixed_ctype} *)v.base;
}}
""");

        for f in self.fields:
            f.generate()

        C("static inline GString *")
        C("{type_name_}format ({TypeNameRef} v, GString *s, gboolean type_annotate)")
        C("{{")

        # Create runs of things we can combine into single printf
        field_runs = []
        current_run = None
        for f in self.fields:
            if current_run and f.type.can_printf_format() == current_run[0].type.can_printf_format():
                current_run.append(f)
            else:
                current_run = [f]
                field_runs.append(current_run)

        for i, run in enumerate(field_runs):
            if run[0].type.can_printf_format():
                # A run of printf fields
                C('  g_string_append_printf (s, "%s' % ("(" if i == 0 else ""), continued=True)
                for f in run:
                    if f.type.get_type_annotation() != "":
                        C('%s', continued=True)
                    C('%s' % (f.type.get_format_string()), continued=True)
                    if not f.last:
                        C(', ', continued=True)
                    elif len(self.fields) == 1:
                        C(',)', continued=True)
                    else:
                        C(')', continued=True)
                C('",')
                for j, f in enumerate(run):
                    if f.type.get_type_annotation() != "":
                        C('                   type_annotate ? "%s" : "",' % (f.type.get_type_annotation()))
                    value = f.type.convert_value_for_format(f.genC("{struct_name_}get_{fieldname} (v)"))
                    C('                   %s%s' % (value, "," if j != len(run) - 1 else ");"))
            else:
                # A run of container fields
                if i == 0:
                    C('  g_string_append (s, "(");')
                for f in run:
                    C('  {append_field_code}', {'append_field_code': escapeC(f.type.generate_append_value(f.genC("{struct_name_}get_{fieldname} (v)"), "type_annotate"))})
                    if not f.last:
                        C('  g_string_append (s, ", ");')
                    elif len(self.fields) == 1:
                        C('  g_string_append (s, ",)");')
                    else:
                        C('  g_string_append (s, ")");')
        C("  return s;")
        C("}}")
        self.generate_print()

typeSpec = Forward()

basicType = oneOf(list(basic_types.keys())).setParseAction(lambda toks: BasicType(toks[0]))

variantType = Keyword("variant").setParseAction(lambda toks: VariantType())

arrayType = (LBRACK + RBRACK + typeSpec).setParseAction(lambda toks: ArrayType(toks[0]))

indexAttribute = oneOf("sorted")

dictType = (LBRACK + Group(ZeroOrMore(indexAttribute)) + basicType + RBRACK + typeSpec).setParseAction(lambda toks: DictType(toks[0], toks[1], toks[2]))

maybeType = (Suppress("?") + typeSpec).setParseAction(lambda toks: MaybeType(toks[0]))

fieldAttribute = oneOf("bigendian littleendian")

field = (ident + COLON + Group(ZeroOrMore(fieldAttribute)) + typeSpec + SEMI).setParseAction(lambda toks: Field(toks[0], toks[1], toks[2]))

structType = (LBRACE + ZeroOrMore(field) + RBRACE).setParseAction(lambda toks: StructType(toks))

namedType = ident.copy().setParseAction(lambda toks: get_named_type(str(toks[0])))

def handleNameableType(toks):
    type = toks[-1]
    if len(toks) == 2:
        name = toks[0]
        add_named_type(typename_prefix + name, type)
    return type

nameableType = (Optional(Combine(Suppress("'") + ident)) + (arrayType ^ maybeType ^ dictType ^ structType)).setParseAction(handleNameableType)

typeSpec <<= basicType  ^ variantType ^ namedType ^ nameableType

typeDef = (Suppress(Keyword("type")) + ident + typeSpec + SEMI).setParseAction(lambda toks: TypeDef(toks[0], toks[1]))

typeDefs = ZeroOrMore(typeDef).ignore(cppStyleComment)

def generate(typedefs, filename):
    generate_header(filename)
    generated = {}
    for td in typedefs:
        td.generate(generated)
    generate_footer(filename)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Generate variant accessors.')
    parser.add_argument('--prefix', help='prefix')
    parser.add_argument('--outfile', help='output filename')
    parser.add_argument('--outfile-header', help='output filename')
    parser.add_argument('--internal-validation', help='enable internal validation', action='store_true')
    parser.add_argument('file')
    args = parser.parse_args()
    if args.prefix:
        typename_prefix = snake_case_to_CamelCase(args.prefix)
        funcname_prefix = args.prefix + "_"
    if args.outfile:
        output_file = open(args.outfile, "w")
        output_h_file = output_file
    if args.outfile_header:
        output_h_file = open(args.outfile_header, "w")

    internal_validation = args.internal_validation

    with open(args.file, "r") as f:
        testdata = f.read()
        try:
            typedefs = typeDefs.parseString(testdata, parseAll=True)
            generate(typedefs, os.path.basename(args.file))
        except ParseException as pe:
            print("Parse error:", pe)
            sys.exit(1)
