#!/bin/env python
# -*- Mode: Python -*-
# GObject-Introspection - a framework for introspecting GObject libraries
# Copyright (C) 2008  Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#

# The idea here is that we want to compare offset information two ways:
#
#  1) As generated by the compiler
#  2) As found in the typelib
#
# So we find all the structures in the input file (offsets.h), parse out
# fields within the structure and generate code that outputs the field
# offsets using G_STRUCT_OFFSET() to one file and the field offsets using
# the typelib to the another file. We can then diff the two files to see
# if they are the same

import re
import sys

if len(sys.argv) != 2:
    print >>sys.stderr, "Usage: gen-gitestoffsets INPUT > OUTPUT"
    sys.exit(1)

# Helper function that we use to generate source. It does substitions
# from a dictionary, removes white space at the ends and removes a
# leading '|' from each line
STRIP_AROUND_RE = re.compile("^[ \t]*\n?(.*?)[ \t]*$", re.DOTALL)
STRIP_LEADER_RE = re.compile("^\|", re.MULTILINE)
def output(args, format):
    format = STRIP_AROUND_RE.sub(r"\1", format)
    format = STRIP_LEADER_RE.sub(r"", format)
    sys.stdout.write(format % args)

#
# Some regular expressions we use when parsing
#
TOKEN = "(?:[A-Za-z_][A-Za-z_0-9]+)"

def compile_re(expr):
    expr = expr.replace("TOKEN", TOKEN)
    return re.compile(expr, re.VERBOSE)

COMMENT_RE = compile_re("/\*([^*]+|\*[^/])*\*/")
STRUCT_DEF_RE = compile_re("struct\s+_(TOKEN)\s*{([^}]*)}")

# This certainly can't handle all type declarations, but it only
# needs to handle the ones we use in the test cases
FIELD_RE = compile_re(r"^(?:const\s+)?TOKEN(?:[\s*]+)(TOKEN)\s*(?:\[([0-9]*)\])?\s*;$")


input_f = open(sys.argv[1])
input = input_f.read()
input_f.close()

# Strip comments
input = COMMENT_RE.sub("", input)

symbols = []
symbol_fields = {}

for m in STRUCT_DEF_RE.finditer(input):
    symbol = m.group(1)

    fields = []

    body = m.group(2)
    for line in re.split("\n|\r\n", body):
        line = line.strip()
        if line == "":
            continue
        n = FIELD_RE.match(line)
        if not n:
            print sys.stderr, "Can't parse structure line '%s'" % line
            sys.exit(1)
        fields.append(n.group(1))

    symbols.append(symbol)
    symbol_fields[symbol] = fields

# Sort for convenience
symbols.sort()

output({}, r'''
| /* GENERATED FILE. DO NOT EDIT. See gen-gitestoffsets */
|
|#include <errno.h>
|#include <stdio.h>
|#include <string.h>
|#include <glib.h>
|#include <girepository.h>
|#include "offsets.h"
|
|static GIRepository *repository;
|static const char *namespace = "Offsets";
|static const char *version = "1.0";
|
|static void
|print_field_offset(FILE         *outfile,
|                   GIStructInfo *struct_info,
|                   const gchar  *name)
|{
|   gint i;
|   gint n_fields = g_struct_info_get_n_fields (struct_info);
|   for (i = 0; i < n_fields; i++)
|     {
|       GIFieldInfo *field_info = g_struct_info_get_field (struct_info, i);
|       const char *field_name = g_base_info_get_name ((GIBaseInfo *)field_info);
|       if (strcmp (field_name, name) == 0)
|         {
|           fprintf (outfile, "%%s %%d\n", name, g_field_info_get_offset (field_info));
|           g_base_info_unref ((GIBaseInfo *)field_info);
|           return;
|         }
|
|       g_base_info_unref ((GIBaseInfo *)field_info);
|     }
|
|   g_error("Can't find field '%%s.%%s' in introspection information",
|           g_base_info_get_name ((GIBaseInfo *)struct_info), name);
|}
|
''')

for symbol in symbols:
    fields = symbol_fields[symbol]

    output({'symbol' : symbol}, r'''
|typedef struct {
|   char dummy;
|   %(symbol)s struct_;
|} Align%(symbol)s;
|
|static void
|compiled_%(symbol)s (FILE *outfile)
|{
|  fprintf (outfile, "%(symbol)s: size=%%" G_GSIZE_FORMAT ", alignment=%%ld\n",
|           sizeof(%(symbol)s),
|           G_STRUCT_OFFSET(Align%(symbol)s, struct_));
|
           ''')

    for field in fields:
        output({ 'field' : field, 'symbol' : symbol }, r'''
|  fprintf (outfile, "%%s %%ld\n", "%(field)s", G_STRUCT_OFFSET(%(symbol)s, %(field)s));
               ''')

    output({}, r'''
|
|  fprintf (outfile, "\n");
|}
|
           ''')

    if not symbol.startswith("Offsets"):
        print >> sys.stderr, "Symbol '%s' doesn't start with Offsets" % symbol
    bare_symbol = symbol[len("Offsets"):]


    output({'symbol' : symbol, 'bare_symbol' : bare_symbol}, r'''
|static void
|introspected_%(symbol)s (FILE *outfile)
|{
|  GIStructInfo *struct_info = (GIStructInfo *)g_irepository_find_by_name(repository, namespace,
|                                                                         "%(bare_symbol)s");
|  if (!struct_info)
|     g_error ("Can't find GIStructInfo for '%(symbol)s'");
|
|  fprintf (outfile, "%(symbol)s: size=%%" G_GSIZE_FORMAT ", alignment=%%" G_GSIZE_FORMAT "\n",
|           g_struct_info_get_size (struct_info),
|           g_struct_info_get_alignment (struct_info));
|
           ''')
    for field in fields:
        output({'field' : field}, '''
|  print_field_offset(outfile, struct_info, "%(field)s");
               ''')

    output({}, r'''
|
|  fprintf (outfile, "\n");
|
|  g_base_info_unref ((GIBaseInfo *)struct_info);
|}
           ''')

output({}, r'''
|
|int main(int argc, char **argv)
|{
|  GError *error = NULL;
|  FILE *outfile;
|
|  if (argc != 3)
|    g_error ("Usage: gitestoffsets COMPILED_OFFSETS_FILE INTROSPECTED_OFFSETS_FILE");
|
|  g_type_init ();
|
|  repository = g_irepository_get_default ();
|  if (!g_irepository_require (repository, namespace, version, 0, &error))
|     g_error ("Failed to load %%s-%%s.typelib: %%s", namespace, version, error->message);
|
|  outfile = fopen (argv[1], "w");
|  if (!outfile)
|    g_error ("Cannot open '%%s': %%s'", argv[1], g_strerror(errno));
|
''')

for symbol in symbols:
    output({'symbol' : symbol}, '''
|  compiled_%(symbol)s (outfile);
   ''')

output({}, '''
|
|  fclose (outfile);
|
|  outfile = fopen (argv[2], "w");
|  if (!outfile)
|    g_error ("Cannot open '%%s': %%s'", argv[1], g_strerror(errno));
|
''')


for symbol in symbols:
    output({'symbol' : symbol}, '''
|  introspected_%(symbol)s (outfile);
''')

output({}, r'''
|
|  fclose (outfile);
|
|  return 0;
}
''')
