######################################################################
# SPDX-License-Identifier: EPICS
# EPICS BASE is distributed subject to a Software License Agreement
# found in file LICENSE that is included with this distribution.
######################################################################

# Common utility functions used by the DBD components

package DBD::Base;

use strict;
use warnings;

use Carp;
require Exporter;

our @ISA = qw(Exporter);

our @EXPORT = qw(&pushContext &popContext &dieContext &warnContext &is_reserved
    &escapeCcomment &escapeCstring $RXident $RXname $RXuint $RXint $RXhex $RXoct
    $RXuintx $RXintx $RXnum $RXdqs $RXstr);


our $RXident = qr/ [a-zA-Z] [a-zA-Z0-9_]* /x;
our $RXnchr =  qr/ [a-zA-Z0-9_\-:.\[\]<>;] /x;
our $RXname =  qr/ $RXnchr+ (?: [{}] $RXnchr+ )* /x;
our $RXhex =   qr/ (?: 0 [xX] [0-9A-Fa-f]+ ) /x;
our $RXoct =   qr/ 0 [0-7]* /x;
our $RXuint =  qr/ [0-9]+ /x;
our $RXint =   qr/ -? $RXuint /x;
our $RXuintx = qr/ ( $RXhex | $RXoct | $RXuint ) /x;
our $RXintx =  qr/ ( $RXhex | $RXoct | $RXint ) /x;
our $RXnum =   qr/ -? (?: [0-9]+ | [0-9]* \. [0-9]+ ) (?: [eE] [-+]? [0-9]+ )? /x;
our $RXdqs =   qr/ " (?> \\. | [^"\\] )* " /x;
our $RXstr =   qr/ ( $RXname | $RXnum | $RXdqs ) /x;

our @context;


sub pushContext {
    my ($ctxt) = @_;
    unshift @context, $ctxt;
}

sub popContext {
    my ($ctxt) = @_;
    my $pop = shift @context;
    ($ctxt ne $pop) and
        dieContext("Leaving context \"$ctxt\", found \"$pop\" instead.",
            "\tBraces must be closed in the same file they open in.");
}

sub dieContext {
    my $msg = join "\n\t", @_;
    die "$msg\nContext: ", join(' in ', @context), "\n";
}

sub warnContext {
    my $msg = join "\n\t", @_;
    print STDERR "$msg\nContext: ", join(' in ', @context), "\n";
}


# Reserved words from C++ and the DB/DBD file parser
my %reserved = map { $_ => undef } qw(and and_eq asm auto bitand bitor bool
    break case catch char class compl const const_cast continue default delete
    do double dynamic_cast else enum explicit export extern false float for
    friend goto if inline int long mutable namespace new not not_eq operator or
    or_eq private protected public register reinterpret_cast return short signed
    sizeof static static_cast struct switch template this throw true try typedef
    typeid typename union unsigned using virtual void volatile wchar_t while xor
    xor_eq addpath alias breaktable choice device driver field function grecord
    include info menu path record recordtype registrar variable);
sub is_reserved {
    my $id = shift;
    return exists $reserved{$id};
}

sub identifier {
    my ($this, $id, $what) = @_;
    confess "DBD::Base::identifier: $what undefined!"
        unless defined $id;
    $id =~ m/^$RXident$/ or dieContext("Illegal $what '$id'",
        "Identifiers are used in C code so must start with a letter, followed",
        "by letters, digits and/or underscore characters only.");
    dieContext("Illegal $what '$id'",
        "Identifier is a C++ reserved word.")
        if is_reserved($id);
    return $id;
}


# Output filtering

sub escapeCcomment {
    ($_) = @_;
    s/\*\//**/g;
    return $_;
}

sub escapeCstring {
    ($_) = @_;
    # FIXME: How to do this?
    return $_;
}


# Base methods for the DBD component objects

sub new {
    my $class = shift;
    my $this = {};
    bless $this, $class;
    return $this->init(@_);
}

sub init {
    my ($this, $name, $what) = @_;
    $this->{NAME} = $this->identifier($name, "$what name");
    $this->{WHAT} = $what;
    return $this;
}

sub name {
    return shift->{NAME};
}

sub what {
    return shift->{WHAT};
}

sub add_comment {
    my $this = shift;
    confess "add_comment() not supported by $this->{WHAT} ($this)\n",
        "Context: ", join(' in ', @context), "\n";
}

sub add_pod {
    my $this = shift;
    warnContext "Warning: Pod text inside $this->{WHAT} will be ignored";
}

sub equals {
    my ($a, $b) = @_;
    return $a->{NAME} eq $b->{NAME}
        && $a->{WHAT} eq $b->{WHAT};
}

1;
