#!/usr/bin/perl
use strict;
use warnings;

use feature ':5.10';
use String::ShellQuote;


# input can come on the commandline, or pipe in on STDIN
my @defs; # field definitions. Each element is "type name"
if(@ARGV)
{
    @defs = @ARGV;
}
else
{
    # each line is a definition.
    # cut off trailing whitespace
    # ignore comments and any blank lines
    @defs = grep {!/^\s*#/ && /\S/ } map {chomp; $_;} <>;
}

if(!@defs)
{
    say STDERR "Field definitions must come on the commandline or on STDIN";
    exit 1;
}


my $legend = "#";

my $set_field_value_defs = '';
for my $field(@defs)
{
    my ($set_field_value, $name) = gen_field($field);
    $set_field_value_defs .= $set_field_value;
    $legend .= " $name";
}

my $argstring = shell_quote(@defs);
my $Nfields   = @defs;


say <<EOF;
// Generated by
//     $0 $argstring

#pragma once

#include <inttypes.h>

#define VNLOG_N_FIELDS         $Nfields
#include <vnlog/vnlog.h>

EOF

print $set_field_value_defs;

say <<EOF;
#define vnlog_emit_legend_ctx(ctx)  _vnlog_emit_legend(ctx,  \"$legend\\n\", VNLOG_N_FIELDS)
#define vnlog_emit_legend()         _vnlog_emit_legend(NULL, \"$legend\\n\", VNLOG_N_FIELDS)

#define vnlog_clear_fields_ctx(ctx, do_free_binary)  _vnlog_clear_fields_ctx(ctx, VNLOG_N_FIELDS, do_free_binary)
EOF



sub gen_field
{
    my ($field) = @_;

    state $idx = 0;

    my ($type, $name, $options) =
      $field =~ /\s*
                 (.+?) # A maximal string. May have space in the middle
                 \s+   # Some space
                 (\S+) # A maximal string with no spaces
                 (?:
                     \s*
                     \(\s* (.*?) \s* \) # some expression in () ...
                 )?                     # ... possibly missing
                /x;
    if ( !defined $type || !defined $name )
    {
        die "Couldn't parse field spec '$field'";
    }
    if( defined $options  )
    {
        die "No options are yet supported";
    }
    if( $name =~ /[^a-zA-Z0-9_]/ )
    {
        die "Name can only have [a-zA-Z0-9_], but got '$name'";
    }

    $type =~ s/ +/ /g;   # replace all consecutive ' ' with a single space
    $type =~ s/ \*/\*/g; # ' *' -> '*'. so 'const char *' -> 'const char*'

    my @ret;
    if( $type eq 'void*' )
    {
        # binary type
        my $set_field_value = <<EOF;
#define vnlog_set_field_value_ctx__$name(ctx, ptr, len) _vnlog_set_field_value_binary(ctx,  "$name", $idx, ptr, len)
#define vnlog_set_field_value__$name(ptr, len)          _vnlog_set_field_value_binary(NULL, "$name", $idx, ptr, len)
EOF

        @ret = ($set_field_value, $name);
    }
    else
    {
        my %typenames =
          (
           'int'          => "",
           'int8_t'       => "",
           'int16_t'      => "",
           'int32_t'      => "",
           'int64_t'      => "",
           'unsigned int' => "unsignedint",
           'unsigned'     => "",
           'uint8_t'      => "",
           'uint16_t'     => "",
           'uint32_t'     => "",
           'uint64_t'     => "",
           'char'         => "",
           'float'        => "",
           'double'       => "",
           'const char*'  => "ccharp",
           'char*'        => "charp",
           'void*'        => ""
          );

        my $typename = $typenames{$type};
        if( !defined $typename )
        {
            die "Unknown type '$type'. I only know about " . join(' ', keys %typenames);
        }

        $typename = $type if $typename eq "";
        my $arg = "($type)(x)";

        my $set_field_value = <<EOF;
#define vnlog_set_field_value_ctx__$name(ctx, x) _vnlog_set_field_value_${typename}(ctx,  "$name", $idx, $arg)
#define vnlog_set_field_value__$name(x)          _vnlog_set_field_value_${typename}(NULL, "$name", $idx, $arg)
EOF

        @ret = ($set_field_value, $name);
    }

    $idx++;
    return @ret;
}

__END__

=head1 NAME

vnl-gen-header - create definition for vnlog output from C

=head1 SYNOPSIS

 $ vnl-gen-header 'int w' 'uint8_t x' 'char* y' 'double z' > vnlog_fields_generated.h

=head1 DESCRIPTION

We provide a simple C library to produce vnlog output. The fields this
library outputs must be known at compile time, and are specified in a header
created by this tool. Please see the vnlog documentation for instructions on
how to use the library

=head1 ARGUMENTS

This tool needs to be given a list of field definitions. First we look at the
commandline, and if the definitions are not available there, we look on STDIN.
Each definition is a string C<type name> (one def per argument on the
commandline or per line on STDIN). If reading from STDIN, we ignore blank lines,
and treat any line starting with C<#> as a comment.

Each def represents a single output field. Each such field spec in a C-style
variable declaration with a type followed by a name. Note that these field specs
contain whitespace, so each one must be quoted before being passed to the shell.

The types can be basic scalars, possibly with set widths (C<char>, C<double>,
C<int>, C<uint32_t>, C<unsigned int>, ...), a NULL-terminated string (C<char*>)
or a generic chunk of binary data (C<void*>).

The names must consist entirely of letters, numbers or C<_>, like variables in
C.

=head1 REPOSITORY

https://github.com/dkogan/vnlog/

=head1 AUTHOR

Dima Kogan C<< <dima@secretsauce.net> >>

=head1 LICENSE AND COPYRIGHT

Copyright 2016 California Institute of Technology.

This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free
Software Foundation; either version 2.1 of the License, or (at your option) any
later version.

=cut
