2012-01-22 28 views
11

Tôi đã lười biếng và đã viết mô-đun Haskell (sử dụng IDE EclipseFP tuyệt vời) mà không đưa ra chữ ký kiểu cho các hàm cấp cao nhất của tôi.Tự động thêm chữ ký loại vào chức năng cấp cao nhất

EclipseFP sử dụng HLint để tự động gắn nhãn mọi chức năng vi phạm và tôi có thể sửa từng chức năng bằng 4 lần nhấp chuột. Hiệu quả, nhưng tẻ nhạt.

Có chương trình tiện ích nào sẽ quét tệp .hs và phát ra phiên bản đã sửa đổi để thêm chữ ký loại cho từng chức năng cấp cao nhất không?

Ví dụ:

./addTypeSignatures Foo.hs 

sẽ đọc một tập tin Foo.hs:

foo x = foo + a 

và phát ra

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

điểm thưởng nếu công cụ tự động chỉnh sửa Foo.hs tại chỗ và lưu bản sao lưu Foo.bak.hs

+5

Lệnh 'lệnh hs-lint' trong Emacs sẽ tự động áp dụng các gợi ý nếu 'hs-lint-replace-without-ask' được đặt thành' t'. Tôi không chắc chắn làm thế nào để hạn chế nó để chỉ cần gõ chữ ký, nhưng chắc chắn phải có một cách. Và tôi chỉ đăng nhận xét này vì nhận xét vì nó không phải là giải pháp EclipseFP. –

Trả lời

5

Có chế độ haskell cho các emacs có phím tắt để chèn chữ ký loại của hàm: C-u, C-c, C-t. Nó không phải là tự động, bạn phải làm điều đó cho mỗi chức năng. Nhưng nếu bạn chỉ có một mô-đun, nó có thể sẽ đưa bạn một vài phút để đi qua nó.

+0

Điều này đúng, nhưng nó chỉ tốt hơn so với giải pháp EclipseFP + hlint. –

0

Tập lệnh perl này thực hiện công việc hack tại đó, đưa ra một số giả định về cấu trúc tệp nguồn. (Chẳng hạn như: .hs tệp (không phải .lhs), chữ ký nằm trên dòng ngay trước định nghĩa, định nghĩa tuôn ra ở lề trái, vv)

Nó cố xử lý (bỏ qua) nhận xét, định nghĩa kiểu phương trình (với lặp lại bên trái) và các loại tạo ra đầu ra nhiều dòng trong ghci.

Không nghi ngờ gì, nhiều trường hợp hợp lệ thú vị không được xử lý đúng cách. Kịch bản không gần với việc tôn trọng cú pháp thực tế của Haskell.

Nó cực kỳ chậm, vì nó khởi chạy phiên ghci cho mỗi chức năng cần chữ ký. Nó tạo một tệp sao lưu File.hs.bak, in các hàm mà nó tìm thấy thành tiêu chuẩn, cũng như chữ ký cho các hàm thiếu chữ ký và viết mã nguồn được nâng cấp lên File.hs. Nó sử dụng một tệp trung gian File.hs.new và có một vài kiểm tra an toàn để tránh ghi đè nội dung của bạn bằng rác.

SỬ DỤNG RỦI RO RIÊNG CỦA BẠN.

Tập lệnh này có thể định dạng ổ cứng của bạn, đốt cháy ngôi nhà của bạn, không an toànPerformIO và có các tác dụng phụ không tinh khiết khác. Trong thực tế, nó có thể sẽ.

Tôi cảm thấy rất bẩn.

Được thử nghiệm trên Mac OS X 10.6 Snow Leopard với một vài tệp nguồn của riêng tôi .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

Bạn sẽ có thể làm cho nó nhanh hơn rất nhiều bằng cách chỉ tải tệp vào GHCi một lần và sử dụng ': browse'. – ehird

+0

@ehird: Cảm ơn con trỏ. Tôi đã đăng một câu trả lời khác sử dụng ": duyệt", nhưng có các vấn đề khác :-( – misterbee

1

Đây là biến thể của tập lệnh trên, sử dụng ": duyệt" thay vì ": type", theo nhận xét của ehird.

Một vấn đề lớn với giải pháp này là ": duyệt" hiển thị tên loại đủ điều kiện, trong khi ": type" sử dụng tên loại được nhập (viết tắt). Điều này, nếu mô-đun của bạn sử dụng các loại được nhập không đủ điều kiện (một trường hợp phổ biến), đầu ra của tập lệnh này sẽ không biên dịch.

Việc rút ngắn đó có thể khắc phục được (sử dụng một số phân tích cú pháp nhập khẩu), nhưng lỗ thỏ này đang trở nên sâu.

#!/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"; 
    } 
} 
Các vấn đề liên quan