#!/usr/bin/perl -w
#
# Copyright 2025 Henri Verbeet
#
# 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.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

use strict;
use warnings;
use JSON;
use open ':utf8';
binmode STDOUT, ':utf8';

sub opcode_id($)
{
    sprintf "0x%04x", shift;
}

sub enumerant_id($)
{
    my ($value) = @_;

    sprintf "%#x", $value =~ /^0[xX]/ ? hex $value : $value;
}

sub fix_name($)
{
    shift =~ s/([A-Z]+)/_$1/rg;
}

sub operand_category_name(_)
{
    "SPIRV_PARSER_OPERAND_CATEGORY${\uc fix_name shift}";
}

sub operand_type_name(_)
{
    "SPIRV_PARSER_OPERAND_TYPE${\uc fix_name shift}";
}

sub grammar_version($)
{
    my ($grammar) = @_;

    "$grammar->{major_version}.$grammar->{minor_version}.$grammar->{revision}";
}

sub print_enumerant(_)
{
    my ($enumerant) = @_;
    my $indent = " " x 12;
    my $parameter_count = @{$enumerant->{parameters} // []};

    if (!$parameter_count)
    {
        print $indent . "{${\enumerant_id $enumerant->{value}}, \"$enumerant->{enumerant}\"},\n";
        return;
    }

    print $indent . "{\n";
    print $indent . "    ${\enumerant_id $enumerant->{value}}, \"$enumerant->{enumerant}\", $parameter_count,\n";
    print $indent . "    (enum spirv_parser_operand_type[])\n";
    print $indent . "    {\n";
    print $indent . "        ${\operand_type_name $_->{kind}},\n" foreach @{$enumerant->{parameters}};
    print $indent . "    }\n";
    print $indent . "},\n";
}

sub print_operand_type_info(_)
{
    my ($type) = @_;
    my $enumerant_count = @{$type->{enumerants} // []};

    print "    [${\operand_type_name $type->{kind}}] =\n";
    print "    {\n";
    print "        \"$type->{kind}\", ${\operand_category_name $type->{category}}"
            . ($enumerant_count ? ", $enumerant_count,\n" : "\n");
    if ($enumerant_count)
    {
        print "        (struct spirv_parser_enumerant[])\n";
        print "        {\n";
        print_enumerant foreach @{$type->{enumerants}};
        print "        }\n";
    }
    print "    },\n";
}

sub instruction_operand(_)
{
    my ($operand) = @_;

    "{${\operand_type_name $operand->{kind}}" . (defined $operand->{quantifier} ? ", '$operand->{quantifier}'}" : "}");
}

sub print_opcode_info(_)
{
    my ($instruction) = @_;
    my $operand_count = @{$instruction->{operands} // []};

    if (!$operand_count)
    {
        print "    {${\opcode_id $instruction->{opcode}}, \"$instruction->{opname}\"},\n";
        return;
    }

    print "    {\n";
    print "        ${\opcode_id $instruction->{opcode}}, \"$instruction->{opname}\", $operand_count,\n";
    print "        (struct spirv_parser_instruction_operand[])\n";
    print "        {\n";
    print "            ${\instruction_operand},\n" foreach @{$instruction->{operands}};
    print "        }\n";
    print "    },\n";
}

sub print_header($)
{
    my ($grammar) = @_;
    my @operand_types = sort {$a->{kind} cmp $b->{kind}} @{$grammar->{operand_kinds}};

    print "/* This file is automatically generated from version ${\grammar_version $grammar} of the\n";
    print " * machine-readable SPIR-V grammar.\n";
    print " *\n";
    print " * The original source is covered by the following license:\n";
    print " *\n";
    print map {" * $_" =~ s/ +$//r . "\n"} @{$grammar->{copyright}};
    print " */\n\n";

    print "enum spirv_parser_operand_category\n";
    print "{\n";
    print "    ${\operand_category_name},\n" foreach sort keys %{{map {$_->{category}, undef} @operand_types}};
    print "};\n\n";

    print "enum spirv_parser_operand_type\n";
    print "{\n";
    print "    ${\operand_type_name $_->{kind}},\n" foreach @operand_types;
    print "};\n\n";

    print "static const struct spirv_parser_operand_type_info\n";
    print "{\n";
    print "    const char *name;\n";
    print "    enum spirv_parser_operand_category category;\n";
    print "    size_t enumerant_count;\n";
    print "    const struct spirv_parser_enumerant\n";
    print "    {\n";
    print "        uint32_t value;\n";
    print "        const char *name;\n";
    print "        size_t parameter_count;\n";
    print "        enum spirv_parser_operand_type *parameters;\n";
    print "    } *enumerants;\n";
    print "}\n";
    print "spirv_parser_operand_type_info[] =\n";
    print "{\n";
    print_operand_type_info foreach @operand_types;
    print "};\n\n";

    print "static const struct spirv_parser_opcode_info\n";
    print "{\n";
    print "    uint16_t op;\n";
    print "    const char *name;\n";
    print "    size_t operand_count;\n";
    print "    const struct spirv_parser_instruction_operand\n";
    print "    {\n";
    print "        enum spirv_parser_operand_type type;\n";
    print "        char quantifier;\n";
    print "    } *operands;\n";
    print "}\n";
    print "spirv_parser_opcode_info[] =\n";
    print "{\n";
    print_opcode_info foreach sort {$a->{opcode} <=> $b->{opcode}} @{$grammar->{instructions}};
    print "};\n";
}

die "No input file specified.\n" unless @ARGV;
print_header do
{
    local $/;
    open my $fh, '<', $ARGV[0] or die $!;
    decode_json <$fh>;
};
