You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by jm...@apache.org on 2006/08/13 00:40:12 UTC

svn commit: r431104 - in /spamassassin/branches/jm_re2c_hacks/rule2xs: ./ re2xs

Author: jm
Date: Sat Aug 12 15:40:11 2006
New Revision: 431104

URL: http://svn.apache.org/viewvc?rev=431104&view=rev
Log:
import of Matt Sergeant's 're2xs' script, which uses re2c(1) to compile regexps into a fully-compiled, binary lexer XS module.  This has the potential to provide *insanely* fast rules -- which is nice.  Copied from http://www.sergeant.org/re2xs, via http://use.perl.org/~Matts/journal/30597

Added:
    spamassassin/branches/jm_re2c_hacks/rule2xs/
    spamassassin/branches/jm_re2c_hacks/rule2xs/re2xs   (with props)

Added: spamassassin/branches/jm_re2c_hacks/rule2xs/re2xs
URL: http://svn.apache.org/viewvc/spamassassin/branches/jm_re2c_hacks/rule2xs/re2xs?rev=431104&view=auto
==============================================================================
--- spamassassin/branches/jm_re2c_hacks/rule2xs/re2xs (added)
+++ spamassassin/branches/jm_re2c_hacks/rule2xs/re2xs Sat Aug 12 15:40:11 2006
@@ -0,0 +1,388 @@
+#!/usr/bin/perl -w
+
+use ExtUtils::MakeMaker qw(prompt);
+use File::Copy;
+use Getopt::Long;
+
+use constant MAX_RULES => 60;
+
+our $NAME = "My::FastRE";
+our $FORCE;
+
+sub usage {
+	print "Usage:
+$0 [OPTIONS] file
+
+Options:
+	-n, --name N    : Name of the module
+	-h, --help      : This page
+	-f, --force     : run even if module dir exists
+\n";
+	exit();
+}
+
+GetOptions(
+	"name|n=s" => \$NAME,
+	"help|h"   => \&usage,
+	"force|f"  => \$FORCE,
+	);
+
+usage() unless @ARGV;
+our $FILE = shift @ARGV;
+-e $FILE || die "No such file: $FILE";
+
+if ($NAME eq "My::FastRE") {
+	$NAME = prompt("Module name?", $NAME);
+}
+
+our $PATH = $NAME;
+$PATH =~ s/::/-/g;
+our $PMFILE = $NAME;
+$PMFILE =~ s/.*:://;
+$PMFILE .= ".pm";
+our $XSFILE = $PMFILE;
+$XSFILE =~ s/\.pm$/.xs/;
+
+open(my $fh, "sort $FILE |") || die "open($FILE): $!";
+mkdir $PATH or (!$FORCE and die "mkdir($PATH): $!");
+chdir $PATH;
+
+my $numscans = 0;
+my (@dot_star, @dot_plus);
+
+while (!eof($fh)) {
+	$numscans++;
+	
+	open(my $re, ">scanner${numscans}.re") || die "open(>scanner{$numscans}.re): $!";
+	
+	print $re <<EOT;
+#define NULL            ((char*) 0)
+#define YYCTYPE         char
+#define YYCURSOR        p
+#define YYLIMIT         p
+#define YYMARKER        q
+#define YYFILL(n)
+EOT
+
+	print $re <<EOT;
+char *scan${numscans}(char *p){
+char *q;
+/*!re2c
+EOT
+
+	my $line = 0;
+	while (<$fh>) {
+		next if /^#/;
+		my ($regexp, $reason) = /^(.*):(.*)$/;
+		if ($regexp =~ /^\.\*/) {
+			push @dot_star, "$regexp:$reason";
+			next;
+		}
+		elsif ($regexp =~ /^\.\+/) {
+			push @dot_plus, "$regexp:$reason";
+			next;
+		}
+		print $re "\t", fixup_re($regexp), "               {return \"$reason\";}\n";
+		last if $line++ == MAX_RULES;
+	}
+	
+	print $re <<EOT;
+	[\\000-\\377]				{ return NULL; }
+*/
+}
+EOT
+
+	#last if $numscans == 2;
+}
+
+while (@dot_star) {
+	$numscans++;
+
+	open(my $re, ">scanner${numscans}.re") || die "open(>scanner{$numscans}.re): $!";
+	
+	print $re <<EOT;
+#define NULL            ((char*) 0)
+#define YYCTYPE         char
+#define YYCURSOR        p
+#define YYLIMIT         p
+#define YYMARKER        q
+#define YYFILL(n)
+EOT
+
+	print $re <<EOT;
+char *scan${numscans}(char *p){
+char *q;
+start:
+/*!re2c
+EOT
+
+	my $line = 0;
+	while ($_ = shift @dot_star) {
+		my ($regexp, $reason) = /^(.*):(.*)$/;
+		$regexp =~ s/^\.\*//;
+		print $re "\t", fixup_re($regexp), "               {return \"$reason\";}\n";
+		last if $line++ == MAX_RULES;
+	}
+	
+	print $re <<EOT;
+	[\\001-\\377]		{ goto start; }
+	[\\000]				{return NULL; }
+*/
+}
+EOT
+}
+
+while (@dot_plus) {
+	$numscans++;
+
+	open(my $re, ">scanner${numscans}.re") || die "open(>scanner{$numscans}.re): $!";
+	
+	print $re <<EOT;
+#define NULL            ((char*) 0)
+#define YYCTYPE         char
+#define YYCURSOR        p
+#define YYLIMIT         p
+#define YYMARKER        q
+#define YYFILL(n)
+EOT
+
+	print $re <<EOT;
+char *scan${numscans}(char *p){
+char *q;
+p++;
+start:
+/*!re2c
+EOT
+	
+	my $line = 0;
+	while ($_ = shift @dot_plus) {
+		my ($regexp, $reason) = /^(.*):(.*)$/;
+		$regexp =~ s/^\.\+//;
+		print $re "\t", fixup_re($regexp), "               {return \"$reason\";}\n";
+		last if $line++ == MAX_RULES;
+	}
+	
+	print $re <<EOT;
+	[\\001-\\377]		{ goto start; }
+	[\\000]				{return NULL; }
+*/
+
+}
+EOT
+}
+
+open(my $re, ">scanner.c") || die "open(>scanner.c): $!";
+	
+for (1..$numscans) {
+	print $re "char *scan$_(char *);\n";
+}
+
+print $re <<EOT;
+#define NULL            ((char*) 0)
+char *scan(char *p){
+int i;
+char *match;
+EOT
+
+for (1..$numscans) {
+	print $re "if (match = scan$_(p)) return match;\n";
+}
+
+print $re <<EOT;
+return NULL;
+}
+EOT
+
+close($re);
+
+print "Running re2c...\n";
+for (1..$numscans) {
+	print "$_ / $numscans ";
+	system("time re2c -i -b -o scanner$_.c scanner$_.re");
+}
+
+open(FILE, ">Makefile.PL") || die "write Makefile.PL: $!";
+print FILE <<"EOT";
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+    'NAME' => '$NAME',
+    'VERSION_FROM' => '$PMFILE',
+    'OBJECT' => '\$(O_FILES)',
+    'OPTIMIZE' => '-O2',
+    'ABSTRACT_FROM' => '$PMFILE',
+    'AUTHOR' => 'A. N. Author <au...@example.com>',
+);
+EOT
+
+open(FILE, ">MANIFEST.SKIP") || die "write MANIFEST.SKIP: $!";
+print FILE <<'EOT';
+CVS/.*
+\.bak$
+\.sw[a-z]$
+\.tar$
+\.tgz$
+\.tar\.gz$
+\.o$
+\.xsi$
+\.bs$
+output/.*
+^.#
+^mess/
+^sqlite/
+^output/
+^tmp/
+^blib/
+^Makefile$
+^Makefile\.[a-z]+$
+^pm_to_blib$
+~$
+EOT
+
+open(FILE, ">scanner.h") || die "write scanner.h: $!";
+print FILE <<EOT;
+/* scanner.h */
+
+char *scan(char *);
+
+EOT
+
+open(FILE, ">$XSFILE") || die "write $XSFILE: $!";
+print FILE <<"EOT";
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+#include "scanner.h"
+
+MODULE = $NAME  PACKAGE = $NAME
+
+PROTOTYPES: DISABLE
+
+char *
+scan(char *p);
+
+EOT
+
+open(FILE, ">$PMFILE") || die "write $PMFILE: $!";
+print FILE <<"EOT";
+
+package $NAME;
+
+use strict;
+use vars qw(\$VERSION \@ISA \@EXPORT_OK);
+
+use DynaLoader ();
+use Exporter ();
+
+BEGIN {
+\$VERSION = '1.0';
+\@ISA = qw(Exporter DynaLoader);
+\@EXPORT_OK = qw(scan);
+
+bootstrap $NAME \$VERSION;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+$NAME - Efficient string matching for regexps found in $FILE
+
+=head1 SYNOPSIS
+
+  use $NAME;
+  
+  ...
+  my \$match = ${NAME}::scan(\$string);
+
+=head1 DESCRIPTION
+
+This module is created by re2xs which uses re2c to create an XS library
+capable of scanning through a bunch of regular expressions as defined
+in F<$FILE>.
+
+That's all for now. Enjoy.
+
+=cut
+EOT
+
+sub fixup_re {
+	my $re = shift;
+	
+	# print "FIXUP: $re\n";
+	
+	my $output = "";
+	my $TOK = qr([\{\(\[\|\\\)\.\+\*]);
+	
+	my $STATE;
+	while ($re =~ /\G(.*?)($TOK)/gc) {
+		my $pre = $1;
+		my $tok = $2;
+		
+		if (length($pre)) {
+			$output .= "\"$pre\"";
+		}
+		if ($tok eq "(") {
+			# Grouping
+			if ($re =~ /\G(.*?)\)/gc) {
+				$output .= "( " . fixup_re($1) . " )";
+			}
+			else {
+				die "re: $re doesn't have group closing bracket";
+			}
+		}
+		elsif ($tok eq "|") {
+			$output .= " | ";
+		}
+		elsif ($tok eq "[") {
+			# chars
+			if ($re =~ /\G(.*?)\]/gc) {
+				$output .= "[$1]";
+			}
+			else {
+				die "re: $re doesn't have character class closing bracket";
+			}
+		}
+		elsif ($tok eq '{') {
+			if ($re =~ /\G(.*?)\}/gc) {
+				$output .= "{$1}";
+			}
+			else {
+				die "re: $re doesn't have quantifier closing bracket";
+			}
+		}
+		elsif ($tok eq '.') {
+			$output .= '.';
+		}
+		elsif ($tok eq '*') {
+			$output .= '* ';
+		}
+		elsif ($tok eq '+') {
+			$output .= '+ ';
+		}
+		elsif ($tok eq '\\') {
+			$re =~ /\G(.)/gc or die "\\ at end of string!";
+			my $esc = $1;
+			if ($esc !~ /^[\.\-\+\*_]$/) {
+				die "Unsupported escape: \\$esc";
+			}
+			$output .= "\"$esc\"";
+		}
+		else {
+			print "PRE: $pre\nTOK: $tok\n";
+		}
+	}
+	
+	if (!defined(pos($re))) {
+		# no matches
+		$output .= "\"$re\"";
+	}
+	elsif (pos($re) <= length($re)) {
+		$output .= fixup_re(substr($re, pos($re)));
+	}
+	
+	$output =~ s/""//g; # strip empty strings, or turn "abc""def" -> "abcdef"
+	# print "OUTPUT: $output\n";
+	return $output;
+}
\ No newline at end of file

Propchange: spamassassin/branches/jm_re2c_hacks/rule2xs/re2xs
------------------------------------------------------------------------------
    svn:executable = *