for review: enhancements to mtags script

David Matthew Overton dmo at cs.mu.OZ.AU
Mon Jul 6 12:19:08 AEST 1998


Hi,

I've made some changes to the mtags script to support typeclasses and
some enhancements in the latest versions of Vim and Elvis.  If anyone
else thinks they're useful, I'll commit them.

This is the first time I've used Perl, so if anyone knows of ways that
things could be done better, I'd appreciate comments.

David
-- 
David Overton
MEngSc Student                       Email: dmo at cs.mu.oz.au     
Department of Computer Science       Web: http://www.cs.mu.oz.au/~dmo
The University of Melbourne          Phone: +61 3 9344 9159

Estimated hours taken: 4 (mostly working out how to do what I wanted in Perl)

Enhance the mtags script to produce tags for typeclasses and to
support some new features of Vim and Elvis.

scripts/mtags:
        Add support for producing tags for typeclass, instance and
	method declarations.

	Add new options `--elvis' and `--ext' which work as follows:

	`mtags --elvis':
		Works the same as `mtags --vim'.  This produces tags
		that will work in versions of Vim before 5.0 and
		versions of Elvis before 2.1.

	`mtags --ext':
		Produce tags in the extended format used by Vim 5.0+.
		Duplicate tags are not removed from the tags file.
		Extra attributes are added to each tag to say whether
		it is in the implementation or interface of the source
		file and to describe the kind of tag.  Tag kinds used
		are:
		`pred':	for predicate declarations
		`func': for function declarations
		`type': for type definitions
		`cons': for type constructors
		`inst': for inst definitions
		`mode': for mode definitions
		`tc':	for typeclass declarations
		`tci':	for typeclass instance declarations
		`tcm':	for typeclass methods
		`tcim':	for typeclass instance methods

		(Vim assumes that the `kind' attribute has at most 4
		characters.)

		Current limitation: assumes each method declaration
		start on a new line.

	`mtags --ext --elvis':
		Similar to `mtags --ext', but produces a slightly
		different format that works with Elvis 2.1+.
Index: mtags
===================================================================
RCS file: /home/staff/zs/imp/mercury/scripts/mtags,v
retrieving revision 1.16
diff -u -r1.16 mtags
--- 1.16	1997/08/27 14:23:23
+++ mtags	1998/07/06 01:40:09
@@ -23,6 +23,8 @@
 $warnings = 0;
 $emacs = 0;
 $vim = 0;
+$ext = 0;
+$elvis = 0;
 
 OPTION:
 while ($#ARGV >= 0 && $ARGV[0] =~ /^-/) {
@@ -33,12 +35,25 @@
 	}
 	if ($ARGV[0] eq "--vim") {
 		$vim = 1;
+		$elvis = 0;
 		shift(ARGV);
 		next OPTION;
 	}
+	if ($ARGV[0] eq "--ext") {
+		$ext = 1;
+		shift(ARGV);
+		next OPTION;
+	}
+	if ($ARGV[0] eq "--elvis") {
+		$elvis = 1;
+		$vim = 0;
+		shift(ARGV);
+		next OPTION;
+	}
 }
 
-die "Usage: mtags [--vim] [-e] [--emacs] file1.m ...\n" if $#ARGV < 0;
+die "Usage: mtags [--vim | --elvis] [--ext] [-e] [--emacs] file1.m ...\n" 
+	if $#ARGV < 0;
 
 #---------------------------------------------------------------------------#
 
@@ -56,7 +71,11 @@
 	    $name =~ s/ .*//;
 	}
 
-	if (!$emacs && $seen{$name}) {
+	$match_line = $_;
+	$match_line =~ s|\\|\\\\|g;   # replace `\' with `\\'
+	$match_line =~ s|/|\\/|g;     # replace `/' with `\/'
+
+	if (!$emacs && !$ext && $seen{$name}) {
 	    if ($warnings &&
 		$file ne $prev_file{$name} &&
 		$. != $prev_line{$name})
@@ -71,12 +90,38 @@
 	    if ($emacs) {
 		printf out "%s\177%s\001%d,%d\n",
 		    $_, $name, $., $.;
-	    } elsif ($vim) {
-		printf out "%s\t%s\t/^%s\$/\n",
-		    $name, $file, $_, $name;
+	    } elsif ($ext) {
+		# In ``ext'' mode, print the extra attributes used by
+		# vim 5.0+ and elvis 2.1+.
+		if ($context =~ /implementation/) {
+			$static = "\tfile:";
+		} else {
+			$static = "";
+		}
+		if ($elvis) {
+		    # Elvis (as of 2.1i) seems to require `[' to be escaped
+		    # in tag patterns, even though they are supposed to use
+		    # `nomagic' mode.
+		    $match_line =~ s/\[/\\\[/g;
+
+		    # Elvis is still not quite as smart as vim 5.0+.
+		    # It only allows a single pattern rather than an arbitrary
+		    # ex command.
+		    printf out "%s\t%s\t/^%s\$/;\"\t%s%s\n",
+			$name, $file, $match_line, $kind, $static;
+		} else {
+		    # Works with vim or vi (vi ignores anything after the `;"').
+		    printf out "%s\t%s\t/^%s\$/;-;/%s/;\"\t%s%s\n",
+			$name, $file, $match_line, $name, $kind, $static;
+		}
+	    } elsif ($vim || $elvis) {
+	    	# Works with any version of vim, elvis or vi.
+	    	printf out "%s\t%s\t/^%s\$/\n",
+		    $name, $file, $match_line;
 	    } else {
+		# Works with vi or vim 5.0+
 		printf out "%s\t%s\t/^%s\$/;-;/%s/\n",
-		    $name, $file, $_, $name;
+		    $name, $file, $match_line, $name;
 	    }
 	    $seen{$name} = 1;
 	    $prev_file{$name} = $file;
@@ -88,10 +133,19 @@
 
 if ($emacs) {
 	open(out, "> TAGS") || die "mtags: error opening TAGS: $!\n";
+} elsif ($ext) {
+	# Vim 5.0+ and elvis 2.1+ allow multiple matches for a tag, so don't
+	# remove duplicate tags.
+	# Vim and elvis expect the tags file to be sorted so they can do
+	# binary search.
+	open(out, "| sort > tags") ||
+		die "mtags: error opening pipe: $!\n";
 } else {
+	# Remove duplicate tags for vi.
 	open(out, "| sort -u +0 -1 > tags") ||
 		die "mtags: error opening pipe: $!\n";
 }
+$context = "implementation";
 while ($#ARGV >= 0)
 {
     $file = shift(ARGV);
@@ -115,21 +169,36 @@
 	($cmd, $decl, @rest) = split;
 	$body = join(' ', @rest);
 
-	# skip lines which are not pred, func, type, inst, or mode
-	# declarations.
+	# Is this an "interface" or "implementation" declaration?
+	# If so, change context.
+	if ($decl =~ "\binterface\b" || $decl =~ "\bimplementation\b") {
+		$context = $decl;
+	}
+
+	# Skip lines which are not pred, func, type, inst, mode,
+	# typeclass or instance declarations.
+	# Also skip instance declarations if we're producing a normal vi
+	# tags file since vi doesn't allow duplicate tags and the
+	# typeclass tags are probably more important than the instance
+	# tags.
 	next unless (
 	    $decl eq "pred" ||
 	    $decl eq "func" ||
 	    $decl eq "type" ||
 	    $decl eq "inst" ||
-	    ($decl eq "mode" && $body =~ /::/)
+	    ($decl eq "mode" && $body =~ /::/) ||
+	    $decl eq "typeclass" ||
+	    ($decl eq "instance" && ($emacs || $ext))
 	);
 
 	# skip declarations which are not definitions
 	next unless (
-	    # pred and func declarations are always definitions
+	    # pred, func, typeclass and instance declarations are always
+	    # definitions
 	    $decl eq "pred" ||
 	    $decl eq "func" ||
+	    $decl eq "typeclass" ||
+	    $decl eq "instance"  ||
 
 	    # if it doesn't end in a `.' (i.e if it doesn't fit on one line),
 	    # then it's probably a definition
@@ -143,53 +212,114 @@
 	);
 
 	$name = $body;
+	$kind = $decl;
+	# Shorten $kind for typeclass and instance so they display better in
+	# vim which assumes the kind attribute has at most 4 chars.
+	if ($kind eq "typeclass") {$kind = "tc";}
+	if ($kind eq "instance") {$kind = "tci";}
 	do output_name();
 	
-	# for everything except type declarations, we're done
-	next unless ($decl eq "type");
+	# for everything except type, typeclass and instance declarations,
+	# we're done
+	next unless ($decl eq "type" || $decl eq "typeclass" || 
+			$decl eq "instance");
+
+	if ($decl eq "type") {
+	    # make sure we're at the line with the `--->'
+	    if ($body !~ /--->/) {
+		    next if $_ =~ /\.[ \t]*$/ || $_ =~ /\.[ \t]*%.*$/;
+		    $_ = <srcfile>;
+		    chop;
+		    $body = $_;
+	    }
+	    next unless ($body =~ /--->/);
+
+	    # replace everything up to the `--->' with `;'
+	    $body =~ s/.*--->/;/;
 
-	# make sure we're at the line with the `--->'
-	if ($body !~ /--->/) {
-		next if $_ =~ /\.[ \t]*$/ || $_ =~ /\.[ \t]*%.*$/;
+	    for(;;) {
+		# if the body starts with `;', we assume it must be the
+		# start of a constructor definition
+		if ($body =~ /^[ \t]*;/) {
+
+		    # delete the leading `;'
+		    $body =~ s/[^;]*;[ \t]*//;
+
+		    if ($body =~ /^[ \t]*$/) {
+			$_ = <srcfile>;
+			chop;
+			$body = $_;
+		    }
+
+		    $name = $body;
+		    $name =~ s/[;.%].*//;
+		    $kind  = "cons";
+		    do output_name();
+
+		    # if there are more constructor definitions on the
+		    # same line, process the next one
+		    if ($body =~ /;/) {
+			    $body =~ s/[^;]*;/;/;
+			    next;
+		    }
+		}
+		    
+		last if $_ =~ /\.[ \t]*$/ || $_ =~ /\.[ \t]*%.*$/;
 		$_ = <srcfile>;
 		chop;
 		$body = $_;
-	}
-	next unless ($body =~ /--->/);
+	    }
+	} elsif ($decl eq "typeclass") {
 
-	# replace everything up to the `--->' with `;'
-	$body =~ s/.*--->/;/;
+	    for(;;) {
 
-	for(;;) {
-	    # if the body starts with `;', we assume it must be the start of a
-	    # constructor definition
-	    if ($body =~ /^[ \t]*;/) {
+		# Assume each method declaration starts on a new line.
+		if ($body =~ /^.*\b(pred|func)[ \t]*/) {
+		    $body =~ s/.*\b(pred|func)[ \t]*//;
+
+		    if ($body =~ /^[ \t]*$/) {
+		    	$_ = <srcfile>;
+		    	chop;
+		    	$body = $_;
+		    }
+
+		    $name = $body;
+		    $name =~ s/[(,%].*//;
+		    $kind = "tcm";	# tcm == type class method
+		    do output_name();
+		}
 
-		# delete the leading `;'
-		$body =~ s/[^;]*;[ \t]*//;
+		last if $_ =~ /\]/;
 
-		if ($body =~ /^[ \t]*$/) {
-		    $_ = <srcfile>;
-		    chop;
-		    $body = $_;
-		}
+		$_ = <srcfile>;
+		chop;
+		$body = $_;
+	    }
+	} else { # instance declaration
+	    for(;;) {
 
-		$name = $body;
-		$name =~ s/[;.%].*//;
-		do output_name();
-
-		# if there are more constructor definitions on the same line,
-		# process the next one
-	        if ($body =~ /;/) {
-			$body =~ s/[^;]*;/;/;
-			next;
+		# Assume each method declaration starts on a new line.
+		if ($body =~ /^.*\b(pred\(|func\()/) {
+		    $body =~ s/.*\b(pred\(|func\()//;
+
+		    if ($body =~ /^[ \t]*$/) {
+		    	$_ = <srcfile>;
+		    	chop;
+		    	$body = $_;
+		    }
+
+		    $name = $body;
+		    $name =~ s/[\/)].*//;
+		    $kind = "tcim";	# tcim == type class instance method
+		    do output_name();
 		}
+
+		last if $_ =~ /\]/;
+
+		$_ = <srcfile>;
+		chop;
+		$body = $_;
 	    }
-		
-	    last if $_ =~ /\.[ \t]*$/ || $_ =~ /\.[ \t]*%.*$/;
-	    $_ = <srcfile>;
-	    chop;
-	    $body = $_;
 	}
     }
     close(srcfile) || die "mtags: error closing `$file': $!\n";



More information about the developers mailing list