#!/usr/bin/env perl # # optlib2c - a tool translating ctags option file to C # # Copyright (C) 2016 Masatake YAMATO # Copyright (C) 2016 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, see . # use strict; use warnings; sub show_help { print< FILE.c EOF } ######################################################################## # # PARSE # ######################################################################## my $options = [ [ qr/^--options=(.*)/, sub { parse_optlib ($1, $_[0]); } ], [ qr/^--langdef=(.*)/, sub { die "LANG is alrady defined as $_[0]->{'langdef'}: $1" if (defined $_[0]->{'langdef'}); $_[0]->{'langdef'} = $1; } ], [ qr/^--languages=-(.*)/, sub { die "Don't use --languages=- optoin before defining a language" if (! defined $_[0]->{'langdef'}); die "Only language specified with --langdef can be disabled: $1" unless ($_[0]->{'langdef'} eq $1); $_[0]->{'disabled'} = 1 } ], [ qr/^--language=(.*)/, sub { die "--languages can be used only for disabling a language defined with --langdef: $1"; } ], [ qr/^--map-([^=]*)=\+(.*)/, sub { die "Don't use --map-=+ optoin before defining a language" if (! defined $_[0]->{'langdef'}); die "Adding a map is allowed only to the language specified with --langdef: $1" unless ($_[0]->{'langdef'} eq $1); my $spec = $2; if ($spec =~ /\((.*)\)/) { push @{$_[0]->{'patterns'}}, $1; } elsif ($spec =~ /\.(.*)/) { push @{$_[0]->{'extensions'}}, $1; } else { die "Unexpected notation is used in the argument for --map-$1= option"; } } ], [ qr/^--map-([^=]*)=[^+].*/, sub { die "map manipulation other than the appending(--map-=+...) is not supported"; } ], [ qr /^--alias-([^=]*)=\+(.*)/, sub { die "Don't use --alias-=+ optoin before defining a language" if (! defined $_[0]->{'langdef'}); die "Adding an alias is allowed only to the language specified with --langdef: $1" unless ($_[0]->{'langdef'} eq $1); push @{$_[0]->{'aliases'}}, $2; } ], [ qr/^--alias-([^=]*)=[^+].*/, sub { die "alias manipulation other than the appending(--alias-=+...) is not supported"; } ], [ qr/^--regex-([^=]*)=(.*)/, sub { die "Don't use --regex-= optoin before defining a language" if (! defined $_[0]->{'langdef'}); die "Adding a regex is allowed only to the language specified with --langdef: $1" unless ($_[0]->{'langdef'} eq $1); parse_regex ($2, $_[0]); } ], [ qr/^--kinds-([^=]*)=-(.*)/, sub { die "Don't use --kind-= optoin before defining a language" if (! defined $_[0]->{'langdef'}); parse_kinds ($2, $_[0]); } ], [ qr/^--kinds-([^=]*)=(.*)/, sub { die "--kinds-= can be used only for disabling a kind: $1"; } ], [ qr/^--xcmd-([^=]*)=([^{]*)({.*)?/, sub { die "Don't use --xcmd-= optoin before defining a language" if (! defined $_[0]->{'langdef'}); die "Adding a xcmd is allowed only to the language specified with --langdef: $1" unless ($_[0]->{'langdef'} eq $1); push @{$_[0]->{'xcmds'}}, { xcmd => $2, flags => $3 }; } ], [ qr/^--langmap=.*/, sub { die "Use --map- option instead of --langmap"; } ], [ qr/^-.*/, sub { die "Unhandled option: $&"; } ], [ qr/.*/, sub { die "Unhandled argument: $&"; } ], ]; sub parse_line { my ($line, $opts) = @_; my $r = 0; for (@{$options}) { my ($pat, $action) = @{$_}; if ($line =~ $pat) { $action -> ($opts); $r = 1; last; } } $r; } sub gather_chars { my $input = shift; my $output = ""; my $escape = 0; my $c; while (defined ($c = shift @{$input})) { if ($c eq '/') { if ($escape) { $output = $output . $c; $escape = 0; } else { last; } } elsif ($c eq '\\') { $output = $output . $c . $c; $escape = $escape? 0: 1; } elsif ($c eq '"') { $output = $output . '\\' . $c; } else { $output = $output . $c; $escape = 0; } } return ($output, (defined $c)? $c: ""); } sub parse_regex { my ($regspec, $opts) = @_; my @chars = split //, $regspec; if (! ($chars[0] eq '/')) { die "--regex-= option is not started from /: $regspec"; } shift @chars; my $last_char; my $regex; ($regex, $last_char) = gather_chars (\@chars); if (! ($last_char eq '/')) { die "The pattern in --regex-= option is not ended with /: $regspec"; } my $name; ($name, $last_char) = gather_chars (\@chars); if (! ($last_char eq '/')) { die "Wrong kind/flags syntax: $regspec"; } my $tmp; ($tmp, $last_char) = gather_chars (\@chars); my $kind = ""; my $flags; if ( (! @chars) && (! ($last_char eq '/'))) { $flags = $tmp; } else { $kind = $tmp; } if ($last_char eq '/') { ($flags, $last_char) = gather_chars (\@chars); } push @{$opts->{'regexs'}}, { 'regex' => $regex, 'name' => $name, 'kind' => $kind, 'flags' => $flags, }; } sub parse_kinds { my ($kinds, $opts) = @_; for (split //, $kinds) { push @{$opts->{'disabledKinds'}}, $_; } } sub parse_optlib { my ($optlib, $opts) = @_; open(my $optlibfh, "<", $optlib) or die "cannot open the optlib file: \"$optlib\""; while (<$optlibfh>) { chomp; if ( /^#.*/ ) { next; } else { s/^\s*//; next if ( /^\s*$/ ); if (! parse_line ($_, $opts)) { return undef; } } } return $opts; } sub emit_header { my ($optlib) = @_; print <{'xcmds'}} || @{$opts->{'disabledKinds'}}) ? "": " CTAGS_ATTR_UNUSED"; print <{'Clangdef'}Parser (const langType language$may_unused) { EOF for (@{$opts->{'xcmds'}}) { my ($p, $f) = ($_ -> {'xcmd'}, $_ -> {'flags'}); $f = defined ($f) ? $f: ""; print <{'disabledKinds'}}) { print <{$key}}) { print <{'regexs'}}); print <{'Clangdef'}TagRegexTable [] = { EOF for (@{$opts->{'regexs'}}) { my $flags = $_-> {'flags'}? '"' . $_-> {'flags'} . '"': "NULL"; print <{'regex'}", "$_->{'name'}", "$_->{'kind'}", $flags}, EOF } print <{"disabled"} ? "false": "true"; my $method; $method = "METHOD_NOT_CRAFTED"; if (@{$opts->{"regexs"}}) { $method = "${method}|METHOD_REGEX"; } if (@{$opts->{"xcmds"}}) { $method = "${method}|METHOD_XCMD"; } print <enabled = ${enabled}; def->extensions = extensions; def->patterns = patterns; def->aliases = aliases; def->method = ${method}; EOF if (@{$opts->{'regexs'}}) { print <tagRegexTable = $opts->{'Clangdef'}TagRegexTable; def->tagRegexCount = ARRAY_SIZE($opts->{'Clangdef'}TagRegexTable); EOF } print <initialize = initialize$opts->{'Clangdef'}Parser; EOF } sub emit { my ($optlib, $opts) = @_; emit_header $optlib; emit_initializer $opts; print <{'Clangdef'}Parser (void) { EOF emit_extensions $opts; emit_aliases $opts; emit_patterns $opts; emit_regexs $opts; print "\n"; print <{'langdef'}"); EOF emit_fields_initialization $opts; print <{'xcmd'} = `echo $xcmd->{'xcmd'} | sed -e '${trxcmd}'`; chomp $xcmd->{'xcmd'}; } sub rearrange { my ($trxcmd, $opts) = @_; my $langdef = $opts -> {'langdef'}; my $c = substr ($langdef, 0, 1); $c =~ tr/a-z/A-Z/; $opts -> {'Clangdef'} = $c . substr ($langdef, 1); if ($trxcmd) { for (@{$opts->{'xcmds'}}) { transform_xcmd $_, $trxcmd; } } } ######################################################################## # # MAIN # ######################################################################## sub main { my $trxcmd; my $optlib; my %opts = (langdef => undef, Clangdef => undef, disabled => 0, patterns => [], extensions => [], aliases => [], disabledKinds => [], regexs => [# { regex => "", name => "", kind => "", flags => "" }, ], xcmds => [# { xcmd => "", flags => "" }, ], ); for (@_) { if ( ($_ eq '-h') || ($_ eq '--help') ) { show_help; exit 0; } elsif ( s/^--transform-xcmd=// ) { $trxcmd=$_; } elsif ( /^-.*/ ) { die "unrecongnized option: $_"; } else { if ($optlib) { die "Too man input files: @_"; } $optlib=$_; } } if (! $optlib) { die "No optlib file name is given"; } if (! parse_optlib ($optlib, \%opts)) { exit 1; } rearrange ($trxcmd, \%opts); if (! emit ($optlib, \%opts) ) { exit 1; } 0; } main @ARGV; # optlib2c ends here.