2012-01-22 10 views
11

Estaba flojo y escribí un módulo Haskell (usando el excelente IDE de EclipseFP) sin dar membretes de tipo a mis funciones de nivel superior.Agregue automáticamente firmas de tipo a funciones de nivel superior

EclipseFP usa HLint para etiquetar automáticamente cada función ofensiva, y puedo arreglar cada una con 4 clics del mouse. Eficaz, pero tedioso.

¿Hay algún programa de utilidad que escanee un archivo .hs y emita una versión modificada que agregue firmas de tipos a cada función de nivel superior?

Ejemplo:

./addTypeSignatures Foo.hs 

leerían un archivo Foo.hs:

foo x = foo + a 

y emiten

foo :: Num a => a -> a 
foo x = x + 1 

puntos de bonificación si la herramienta corrige automáticamente Foo.hs en su lugar y guarda una copia de seguridad Foo.bak.hs

+5

El comando 'hs-lint' en Emacs se aplicarán automáticamente sugerencias si 'hs-lint-replace-without-ask' está establecido en' t'. No estoy seguro de cómo restringirlo solo a escribir firmas, pero seguramente debe haber una manera. Y solo estoy publicando esto como un comentario porque no es una solución de EclipseFP. –

Respuesta

5

Hay modo haskell para emacs que tiene un atajo para insertar la firma de tipo de una función: C-u, C-c, C-t. No es automático, tienes que hacerlo para cada función. Pero si solo tiene un módulo, probablemente le llevará unos minutos hacerlo.

+0

Esto es cierto, pero apenas es mejor que la solución EclipseFP + hlint. –

0

Este script perl realiza un trabajo de hack en él, haciendo algunas suposiciones sobre la estructura del archivo fuente. (Tales como: .hs archivo (no .lhs), las firmas están en la línea inmediatamente anterior a las definiciones, las definiciones son de color en el margen izquierdo, etc)

Se trata de manejar (saltar) los comentarios, las definiciones de estilo ecuación (con lados izquierdos repetidos) y tipos que generan salida de varias líneas en ghci.

Sin duda, muchos casos válidos interesantes no se manejan correctamente. El script no está cerca de respetar la sintaxis real de Haskell.

Es increíblemente lento, ya que inicia una sesión ghci para cada función que necesita una firma. Hace un archivo de copia de seguridad File.hs.bak, imprime las funciones que encuentra en stderr, así como las firmas para las funciones que faltan firmas, y escribe el código fuente actualizado en File.hs. Utiliza un archivo intermedio File.hs.new y tiene algunas comprobaciones de seguridad para evitar sobrescribir su contenido con basura.

UTILÍCELO BAJO SU PROPIO RIESGO.

Este script puede formatear su disco duro, grabar su casa, inseguroPerformIO, y tener otros efectos secundarios impuros. De hecho, probablemente lo hará.

Me siento tan sucio.

Probado en Mac OS X 10.6 Snow Leopard con un par de mis propios archivos de origen .hs.

#!/usr/bin/env perl 

use warnings; 
use strict; 

my $sig=0; 
my $file; 

my %funcs_seen =(); 
my %keywords =(); 
for my $kw qw(type newtype data class) { $keywords{$kw} = 1;} 

foreach $file (@ARGV) 
{ 
    if ($file =~ /\.lhs$/) 
    { 
    print STDERR "$file: .lhs is not supported. Skipping."; 
    next; 
    } 

    if ($file !~ /\.hs$/) 
    { 
    print STDERR "$file is not a .hs file. Skipping."; 
    next; 
    } 

    my $ghciPreTest = `echo 1 | ghci $file`; 
    if ($ghciPreTest !~ /Ok, modules loaded: /) 
    { 
    print STDERR $ghciPreTest; 
    print STDERR "$file is not valid Haskell source file. Skipping."; 
    next; 
    } 

    my $module = $file; 
    $module =~ s/\.hs$//; 

    my $backup = "$file.bak"; 
    my $new = "$module.New.hs"; 
    -e $backup and die "Backup $backup file exists. Refusing to overwrite. Quitting"; 
    open OLD, $file; 
    open NEW, ">$new"; 

    print STDERR "Functions in $file:\n"; 
    my $block_comment = 0; 
    while (<OLD>) 
    { 
    my $original_line = $_; 
    my $line = $_; 
    my $skip = 0; 
    $line =~ s/--.*//; 
    if ($line =~ /{-/) { $block_comment = 1;} # start block comment 
    $line =~ s/{-.*//; 
    if ($block_comment and $line =~ /-}/) { $block_comment=0; $skip=1} # end block comment 

    if ($line =~ /^ *$/) { $skip=1; } # comment/blank 
    if ($block_comment) { $skip = 1}; 
    if (!$skip) 
    { 
     if (/^(('|\w)+)(+(('|\w)+))* *=/) 
     { 
     my $object = $1; 
     if ((! $keywords{$object}) and !($funcs_seen{$object})) 
     { 
      $funcs_seen{$object} = 1; 
      print STDERR "$object\n"; 
      my $dec=`echo ":t $1" | ghci $file | grep -A100 "^[^>]*$module>" | grep -v "Leaving GHCi\." | sed -e "s/^[^>]*$module> //"`; 

      unless ($sig) 
      { 
      print NEW $dec; 
      print STDERR $dec; 
      } 
     } 
     } 

    $sig = /^(('|\w)+) *::/; 
    } 
    print NEW $original_line; 
    } 
    close OLD; 
    close NEW; 

    my $ghciPostTest = `echo 1 | ghci $new`; 
    if ($ghciPostTest !~ /Ok, modules loaded: /) 
    { 
    print $ghciPostTest; 
    print STDERR "$new is not valid Haskell source file. Will not replace original (but you might find it useful)"; 
    next; 
    } else { 
    rename ($file, $backup) or die "Could not make backup of $file -> $backup"; 
    rename ($new, $file) or die "Could not make new file $new"; 
    } 
} 
+0

Debería poder hacerlo mucho más rápido solo cargando el archivo en GHCi una vez y usando ': browse'. – ehird

+0

@ehird: gracias por el puntero. Publiqué otra respuesta que usa ": navegar", pero tiene otros problemas :-( – misterbee

1

Aquí es una variación de la secuencia de comandos, que los usos ": Examinar" en lugar de ": tipo", por el comentario de ehird.

Uno de los principales problemas con esta solución es que ": browse" muestra nombres de tipos totalmente calificados, mientras que ": type" usa los nombres de tipos importados (abreviados). Esto, si su módulo utiliza tipos importados no calificados (un caso común), el resultado de este script fallará la compilación.

Ese shortoming es reparable (utilizando algunos análisis de importación), pero este agujero de conejo se está haciendo más profundo.

#!/usr/bin/env perl 

use warnings; 
use strict; 

sub trim { 
    my $string = shift; 
    $string =~ s/^\s+|\s+$//g; 
    return $string; 
} 


my $sig=0; 
my $file; 

my %funcs_seen =(); 
my %keywords =(); 
for my $kw qw(type newtype data class) { $keywords{$kw} = 1;} 

foreach $file (@ARGV) 
{ 
    if ($file =~ /\.lhs$/) 
    { 
    print STDERR "$file: .lhs is not supported. Skipping.\n"; 
    next; 
    } 

    if ($file !~ /\.hs$/) 
    { 
    print STDERR "$file is not a .hs file. Skipping.\n"; 
    next; 
    } 

    my $module = $file; 
    $module =~ s/\.hs$//; 

    my $browseInfo = `echo :browse | ghci $file`; 
    if ($browseInfo =~ /Failed, modules loaded:/) 
    { 
    print STDERR "$browseInfo\n"; 
    print STDERR "$file is not valid Haskell source file. Skipping.\n"; 
    next; 
    } 

    my @browseLines = split("\n", $browseInfo); 
    my $browseLine; 
    my $func = undef; 
    my %dict =(); 
    for $browseLine (@browseLines) { 
    chomp $browseLine; 
    if ($browseLine =~ /::/) { 
    my ($data, $type) = split ("::", $browseLine); 
    $func = trim($data); 
    $dict{$func} = $type; 
    print STDERR "$func :: $type\n"; 
    } elsif ($func && $browseLine =~ /^ /) { # indent on continutation 
    $dict{$func} .= " " . trim($browseLine); 
    print STDERR "$func ... $browseLine\n"; 
    } else { 
    $func = undef; 
    } 
    } 



    my $backup = "$file.bak"; 
    my $new = "$module.New.hs"; 
    -e $backup and die "Backup $backup file exists. Refusing to overwrite. Quitting"; 
    open OLD, $file; 
    open NEW, ">$new"; 

    print STDERR "Functions in $file:\n"; 
    my $block_comment = 0; 
    while (<OLD>) 
    { 
    my $original_line = $_; 
    my $line = $_; 
    my $skip = 0; 
    $line =~ s/--.*//; 
    if ($line =~ /{-/) { $block_comment = 1;} # start block comment 
    $line =~ s/{-.*//; 
    if ($block_comment and $line =~ /-}/) { $block_comment=0; $skip=1} # end block comment 

    if ($line =~ /^ *$/) { $skip=1; } # comment/blank 
    if ($block_comment) { $skip = 1}; 
    if (!$skip) 
    { 
     if (/^(('|\w)+)(+(('|\w)+))* *=/) 
     { 
     my $object = $1; 
     if ((! $keywords{$object}) and !($funcs_seen{$object})) 
     { 
      $funcs_seen{$object} = 1; 
      print STDERR "$object\n"; 
      my $type = $dict{$1}; 

      unless ($sig) 
      { 
      if ($type) { 
       print NEW "$1 :: $type\n"; 
       print STDERR "$1 :: $type\n"; 
      } else { 
       print STDERR "no type for $1\n"; 
      } 
      } 
     } 
     } 

    $sig = /^(('|\w)+) *::/; 
    } 
    print NEW $original_line; 
    } 
    close OLD; 
    close NEW; 

    my $ghciPostTest = `echo 1 | ghci $new`; 
    if ($ghciPostTest !~ /Ok, modules loaded: /) 
    { 
    print $ghciPostTest; 
    print STDERR "$new is not valid Haskell source file. Will not replace original (but you might find it useful)"; 
    next; 
    } else { 
    rename ($file, $backup) or die "Could not make backup of $file -> $backup"; 
    rename ($new, $file) or die "Could not make new file $new"; 
    } 
} 
Cuestiones relacionadas