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