1b4ee4795Smrg#! /usr/bin/perl
2b4ee4795Smrg#
35efbdfc3Smrg# Copyright (c) 2009, Oracle and/or its affiliates.
4b4ee4795Smrg#
5b4ee4795Smrg# Permission is hereby granted, free of charge, to any person obtaining a
6b4ee4795Smrg# copy of this software and associated documentation files (the "Software"),
7b4ee4795Smrg# to deal in the Software without restriction, including without limitation
8b4ee4795Smrg# the rights to use, copy, modify, merge, publish, distribute, sublicense,
9b4ee4795Smrg# and/or sell copies of the Software, and to permit persons to whom the
10b4ee4795Smrg# Software is furnished to do so, subject to the following conditions:
11b4ee4795Smrg#
12b4ee4795Smrg# The above copyright notice and this permission notice (including the next
13b4ee4795Smrg# paragraph) shall be included in all copies or substantial portions of the
14b4ee4795Smrg# Software.
15b4ee4795Smrg#
16b4ee4795Smrg# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17b4ee4795Smrg# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18b4ee4795Smrg# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19b4ee4795Smrg# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20b4ee4795Smrg# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21b4ee4795Smrg# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22b4ee4795Smrg# DEALINGS IN THE SOFTWARE.
23b4ee4795Smrg#
24b4ee4795Smrg
25b4ee4795Smrg#
26b4ee4795Smrg# Check a compose file for duplicate/conflicting entries and other common errors
27b4ee4795Smrg#
28b4ee4795Smrg
29b4ee4795Smrg# Compose file grammar is defined in modules/im/ximcp/imLcPrs.c
30b4ee4795Smrg
31b4ee4795Smrguse strict;
32b4ee4795Smrguse warnings;
33b4ee4795Smrg
34b4ee4795Smrgmy $error_count = 0;
35b4ee4795Smrg
36b4ee4795Smrgif (scalar(@ARGV) == 0) {
37e9fcaa8aSmrg  if ( -f 'Compose' ) {
38e9fcaa8aSmrg    push @ARGV, 'Compose';
39e9fcaa8aSmrg  } else {
40e9fcaa8aSmrg    push @ARGV, glob '*/Compose';
41e9fcaa8aSmrg  }
42b4ee4795Smrg}
43b4ee4795Smrg
44b4ee4795Smrgforeach my $cf (@ARGV) {
45e9fcaa8aSmrg  # print "Checking $cf\n";
46b4ee4795Smrg  $error_count += check_compose_file($cf);
47b4ee4795Smrg}
48b4ee4795Smrg
49b4ee4795Smrgexit($error_count);
50b4ee4795Smrg
51b4ee4795Smrgsub check_compose_file {
52b4ee4795Smrg  my ($filename) = @_;
53b4ee4795Smrg  my $errors = 0;
54b4ee4795Smrg
55b4ee4795Smrg  my %compose_table = ();
56b4ee4795Smrg  my $line = 0;
57b4ee4795Smrg  my $pre_file = ($filename =~ m{\.pre$}) ? 1 : 0;
58b4ee4795Smrg  my $in_c_comment = 0;
59b4ee4795Smrg
60b4ee4795Smrg  open my $COMPOSE, '<', $filename or die "Could not open $filename: $!";
61b4ee4795Smrg
62b4ee4795Smrg COMPOSE_LINE:
63b4ee4795Smrg  while (my $cl = <$COMPOSE>) {
64b4ee4795Smrg    $line++;
65b4ee4795Smrg    chomp($cl);
66b4ee4795Smrg    my $original_line = $cl;
67b4ee4795Smrg
68b4ee4795Smrg    # Special handling for changes cpp makes to .pre files
69b4ee4795Smrg    if ($pre_file == 1) {
70b4ee4795Smrg      if ($in_c_comment) {		# Look for end of multi-line C comment
71b4ee4795Smrg	if ($cl =~ m{\*/(.*)$}) {
72b4ee4795Smrg	  $cl = $1;
73b4ee4795Smrg	  $in_c_comment = 0;
74b4ee4795Smrg	} else {
75b4ee4795Smrg	  next;
76b4ee4795Smrg	}
77b4ee4795Smrg      }
78b4ee4795Smrg      $cl =~ s{/\*.\**/}{};		# Remove single line C comments
79b4ee4795Smrg      if ($cl =~ m{^(.*)/\*}) {		# Start of a multi-line C comment
80b4ee4795Smrg	$cl = $1;
81b4ee4795Smrg	$in_c_comment = 1;
82b4ee4795Smrg      }
83b4ee4795Smrg      next if $cl =~ m{^\s*XCOMM};	# Skip pre-processing comments
84b4ee4795Smrg    }
85b4ee4795Smrg
86b4ee4795Smrg    $cl =~ s{#.*$}{};			# Remove comments
87b4ee4795Smrg    next if $cl =~ m{^\s*$};		# Skip blank (or comment-only) lines
88b4ee4795Smrg    chomp($cl);
89b4ee4795Smrg
90b4ee4795Smrg    if ($cl =~ m{^(STATE\s+|END_STATE)}) { # Sun extension to compose file syntax
91b4ee4795Smrg      %compose_table = ();
92b4ee4795Smrg    }
93b4ee4795Smrg    elsif ($cl =~ m{^([^:]+)\s*:\s*(.+)$}) {
94b4ee4795Smrg      my ($seq, $action) = ($1, $2);
95b4ee4795Smrg      $seq =~ s{\s+$}{};
96b4ee4795Smrg
97b4ee4795Smrg      my @keys = grep { $_ !~ m/^\s*$/ } split /[\s\<\>]+/, $seq;
98b4ee4795Smrg
99b4ee4795Smrg      my $final_key = pop @keys;
100b4ee4795Smrg      my $keytable = \%compose_table;
101b4ee4795Smrg
102b4ee4795Smrg      foreach my $k (@keys) {
103b4ee4795Smrg	if ($k =~ m{^U([[:xdigit:]]+)$}) {
104b4ee4795Smrg	  $k = 'U' . lc($1);
105b4ee4795Smrg	}
106b4ee4795Smrg	if (exists $keytable->{$k}) {
107b4ee4795Smrg	  $keytable = $keytable->{$k};
108b4ee4795Smrg	  if (ref($keytable) ne 'HASH') {
109b4ee4795Smrg	    print
110b4ee4795Smrg	      "Clash with existing sequence in $filename on line $line: $seq\n";
111b4ee4795Smrg	    print_sequences([$line, $original_line]);
112b4ee4795Smrg	    print_sequences($keytable);
113b4ee4795Smrg	    $errors++;
114b4ee4795Smrg	    next COMPOSE_LINE;
115b4ee4795Smrg	  }
116b4ee4795Smrg	} else {
117b4ee4795Smrg	  my $new_keytable = {};
118b4ee4795Smrg	  $keytable->{$k} = $new_keytable;
119b4ee4795Smrg	  $keytable = $new_keytable;
120b4ee4795Smrg	}
121b4ee4795Smrg      }
122b4ee4795Smrg
123b4ee4795Smrg      if (exists $keytable->{$final_key}) {
124b4ee4795Smrg	print "Clash with existing sequence in $filename on line $line: $seq\n";
125b4ee4795Smrg	print_sequences([$line, $original_line]);
126b4ee4795Smrg	print_sequences($keytable->{$final_key});
127b4ee4795Smrg	$errors++;
128b4ee4795Smrg      } else {
129b4ee4795Smrg	$keytable->{$final_key} = [$line, $original_line];
130b4ee4795Smrg      }
131b4ee4795Smrg    } elsif ($cl =~ m{^(STATE_TYPE:|\@StartDeadKeyMap|\@EndDeadKeyMap)}) {
132b4ee4795Smrg      # ignore
133b4ee4795Smrg    } elsif ($cl =~ m{^include "(.*)"}) {
134b4ee4795Smrg      my $incpath = $1;
135b4ee4795Smrg      if (($pre_file == 1) && ($incpath !~ m{^X11_LOCALEDATADIR/})) {
136b4ee4795Smrg	print "Include path starts with $incpath instead of X11_LOCALEDATADIR\n",
137b4ee4795Smrg	 " -- may not find include files when installed in alternate paths\n\n";
138b4ee4795Smrg      }
139b4ee4795Smrg    } else {
140b4ee4795Smrg      print 'Unrecognized pattern in ', $filename, ' on line #', $line, ":\n  ",
141b4ee4795Smrg	$cl, "\n";
142b4ee4795Smrg    }
143b4ee4795Smrg  }
144b4ee4795Smrg  close $COMPOSE;
145b4ee4795Smrg
146b4ee4795Smrg  return $errors;
147b4ee4795Smrg}
148b4ee4795Smrg
149b4ee4795Smrgsub print_sequences {
150b4ee4795Smrg  my ($entry_ref) = @_;
151b4ee4795Smrg
152b4ee4795Smrg  if (ref($entry_ref) eq 'HASH') {
153b4ee4795Smrg    foreach my $h (values %{$entry_ref}) {
154b4ee4795Smrg      print_sequences($h);
155b4ee4795Smrg    }
156b4ee4795Smrg  } else {
157b4ee4795Smrg    my ($line, $seq) = @{$entry_ref};
158b4ee4795Smrg
159b4ee4795Smrg    print "  line #", $line, ": ", $seq, "\n";
160b4ee4795Smrg  }
161b4ee4795Smrg}
162