#! /usr/bin/perl -w
#-------------------------------------------------------------------------
#
# Gen_fmgrtab.pl
#    Perl script that generates fmgroids.h, fmgrprotos.h, and fmgrtab.c
#    from pg_proc.h
#
# Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
# Portions Copyright (c) 1994, Regents of the University of California
#
#
# IDENTIFICATION
#    src/backend/utils/Gen_fmgrtab.pl
#
#-------------------------------------------------------------------------

use Catalog;

use strict;
use warnings;

# Collect arguments
my $infile;    # pg_proc.h
my $output_path = '';
my @include_path;

while (@ARGV)
{
	my $arg = shift @ARGV;
	if ($arg !~ /^-/)
	{
		$infile = $arg;
	}
	elsif ($arg =~ /^-o/)
	{
		$output_path = length($arg) > 2 ? substr($arg, 2) : shift @ARGV;
	}
	elsif ($arg =~ /^-I/)
	{
		push @include_path, length($arg) > 2 ? substr($arg, 2) : shift @ARGV;
	}
	else
	{
		usage();
	}
}

# Make sure output_path ends in a slash.
if ($output_path ne '' && substr($output_path, -1) ne '/')
{
	$output_path .= '/';
}

# Sanity check arguments.
die "No input files.\n"                                     if !$infile;
die "No include path; you must specify -I at least once.\n" if !@include_path;

my $FirstBootstrapObjectId =
	Catalog::FindDefinedSymbol('access/transam.h', \@include_path, 'FirstBootstrapObjectId');
my $INTERNALlanguageId =
	Catalog::FindDefinedSymbol('catalog/pg_language.h', \@include_path, 'INTERNALlanguageId');

# Read all the data from the include/catalog files.
my $catalogs = Catalog::Catalogs($infile);

# Collect the raw data from pg_proc.h.
my @fmgr = ();
my @attnames;
foreach my $column (@{ $catalogs->{pg_proc}->{columns} })
{
	push @attnames, $column->{name};
}

my $data = $catalogs->{pg_proc}->{data};
foreach my $row (@$data)
{

	# Split line into tokens without interpreting their meaning.
	my %bki_values;
	@bki_values{@attnames} = Catalog::SplitDataLine($row->{bki_values});

	# Select out just the rows for internal-language procedures.
	next if $bki_values{prolang} ne $INTERNALlanguageId;

	push @fmgr,
	  { oid    => $row->{oid},
		strict => $bki_values{proisstrict},
		retset => $bki_values{proretset},
		nargs  => $bki_values{pronargs},
		prosrc => $bki_values{prosrc}, };
}

# Emit headers for both files
my $tmpext     = ".tmp$$";
my $oidsfile   = $output_path . 'fmgroids.h';
my $protosfile = $output_path . 'fmgrprotos.h';
my $tabfile    = $output_path . 'fmgrtab.c';

open my $ofh, '>', $oidsfile . $tmpext
  or die "Could not open $oidsfile$tmpext: $!";
open my $pfh, '>', $protosfile . $tmpext
  or die "Could not open $protosfile$tmpext: $!";
open my $tfh, '>', $tabfile . $tmpext
  or die "Could not open $tabfile$tmpext: $!";

print $ofh
qq|/*-------------------------------------------------------------------------
 *
 * fmgroids.h
 *    Macros that define the OIDs of built-in functions.
 *
 * These macros can be used to avoid a catalog lookup when a specific
 * fmgr-callable function needs to be referenced.
 *
 * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * NOTES
 *	******************************
 *	*** DO NOT EDIT THIS FILE! ***
 *	******************************
 *
 *	It has been GENERATED by src/backend/utils/Gen_fmgrtab.pl
 *
 *-------------------------------------------------------------------------
 */
#ifndef FMGROIDS_H
#define FMGROIDS_H

/*
 *	Constant macros for the OIDs of entries in pg_proc.
 *
 *	NOTE: macros are named after the prosrc value, ie the actual C name
 *	of the implementing function, not the proname which may be overloaded.
 *	For example, we want to be able to assign different macro names to both
 *	char_text() and name_text() even though these both appear with proname
 *	'text'.  If the same C function appears in more than one pg_proc entry,
 *	its equivalent macro will be defined with the lowest OID among those
 *	entries.
 */
|;

print $pfh
qq|/*-------------------------------------------------------------------------
 *
 * fmgrprotos.h
 *    Prototypes for built-in functions.
 *
 * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * NOTES
 *	******************************
 *	*** DO NOT EDIT THIS FILE! ***
 *	******************************
 *
 *	It has been GENERATED by src/backend/utils/Gen_fmgrtab.pl
 *
 *-------------------------------------------------------------------------
 */

#ifndef FMGRPROTOS_H
#define FMGRPROTOS_H

#include "fmgr.h"

|;

print $tfh
qq|/*-------------------------------------------------------------------------
 *
 * fmgrtab.c
 *    The function manager's table of internal functions.
 *
 * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * NOTES
 *
 *	******************************
 *	*** DO NOT EDIT THIS FILE! ***
 *	******************************
 *
 *	It has been GENERATED by src/backend/utils/Gen_fmgrtab.pl
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/transam.h"
#include "utils/fmgrtab.h"
#include "utils/fmgrprotos.h"

|;

# Emit #define's and extern's -- only one per prosrc value
my %seenit;
foreach my $s (sort { $a->{oid} <=> $b->{oid} } @fmgr)
{
	next if $seenit{ $s->{prosrc} };
	$seenit{ $s->{prosrc} } = 1;
	print $ofh "#define F_" . uc $s->{prosrc} . " $s->{oid}\n";
	print $pfh "extern Datum $s->{prosrc}(PG_FUNCTION_ARGS);\n";
}

# Create the fmgr_builtins table, collect data for fmgr_builtin_oid_index
print $tfh "\nconst FmgrBuiltin fmgr_builtins[] = {\n";
my %bmap;
$bmap{'t'} = 'true';
$bmap{'f'} = 'false';
my @fmgr_builtin_oid_index;
my $fmgr_count = 0;
foreach my $s (sort { $a->{oid} <=> $b->{oid} } @fmgr)
{
	print $tfh
"  { $s->{oid}, \"$s->{prosrc}\", $s->{nargs}, $bmap{$s->{strict}}, $bmap{$s->{retset}}, $s->{prosrc} }";

	$fmgr_builtin_oid_index[$s->{oid}] = $fmgr_count++;

	if ($fmgr_count <= $#fmgr)
	{
		print $tfh ",\n";
	}
	else
	{
		print $tfh "\n";
	}
}
print $tfh "};\n";

print $tfh qq|
const int fmgr_nbuiltins = (sizeof(fmgr_builtins) / sizeof(FmgrBuiltin));
|;


# Create fmgr_builtins_oid_index table.
#
# Note that the array has to be filled up to FirstBootstrapObjectId,
# as we can't rely on zero initialization as 0 is a valid mapping.
print $tfh qq|
const uint16 fmgr_builtin_oid_index[FirstBootstrapObjectId] = {
|;

for (my $i = 0; $i < $FirstBootstrapObjectId; $i++)
{
	my $oid = $fmgr_builtin_oid_index[$i];

	# fmgr_builtin_oid_index is sparse, map nonexistant functions to
	# InvalidOidBuiltinMapping
	if (not defined $oid)
	{
		$oid = 'InvalidOidBuiltinMapping';
	}

	if ($i + 1 == $FirstBootstrapObjectId)
	{
		print $tfh "  $oid\n";
	}
	else
	{
		print $tfh "  $oid,\n";
	}
}
print $tfh "};\n";


# And add the file footers.
print $ofh "\n#endif /* FMGROIDS_H */\n";
print $pfh "\n#endif /* FMGRPROTOS_H */\n";

close($ofh);
close($pfh);
close($tfh);

# Finally, rename the completed files into place.
Catalog::RenameTempFile($oidsfile,   $tmpext);
Catalog::RenameTempFile($protosfile, $tmpext);
Catalog::RenameTempFile($tabfile,    $tmpext);

sub usage
{
	die <<EOM;
Usage: perl -I [directory of Catalog.pm] Gen_fmgrtab.pl [path to pg_proc.h]

Gen_fmgrtab.pl generates fmgroids.h, fmgrprotos.h, and fmgrtab.c from
pg_proc.h

Report bugs to <pgsql-bugs\@postgresql.org>.
EOM
}

exit 0;
