功能豐富的Perl:編寫說英語的Perl程序

設計程序的用戶界面可能很困難而且耗時。Teodor Zlatanov 討論了如何使用 Parse::RecDescent 模塊來用簡單的英語創建用戶界面文法。他還展示了向程序添加功能或從程序除去功能時,更改文法是如何的方便。另外還與標准的 CLI 解析器和 GUI 進行了比較,討論了這種方法的優缺點。

隨功能一起發展的很棒的用戶界面

因爲用戶界面是程序最初的入口,所以它必須能夠用于多種目的。必須向用戶提供對程序所有功能的合適訪問。在向程序添加更多功能時(這幾乎是必然發生的情況),它必須是可擴展的。必須具備靈活性,可以接受常用命令的縮寫和快捷方式。它不應該有層疊的菜單或瀑布式單詞,這樣會讓用戶感到迷惑。無可否認,以上所有這些要求對程序員來說都是複雜的約束,對此沒有一種很好的解決方案能把它們全包括。許多軟件産品開發人員到最後再解決用戶界面問題,把它作爲一種事後來考慮的問題。另外一些開發人員則首先主要考慮用戶界面,讓功能僅僅成爲界面設計選擇的結果。這些都不是理想的方法。用戶界面(UI)應該隨著程序功能的發展而發展,兩者就象一枚硬幣的正反面。

這裏我們將面向解析的方法用于用戶界面。雖然這種方法適合于 GUI 界面,但本文不討論 GUI 設計。我們將專門討論基于文本的 UI。首先,將簡要介紹標准的文本 UI 設計選擇,使您能熟悉該環境。然後,將展示 Parse::RecDescent 解決方案,事實證明它是靈活、直觀和易于編寫的!

注:爲了運行我們所討論的某些程序,將需要 Parse::RecDescent CPAN 模塊。

用傳統的 Unix 方式創建的簡單用戶界面

Unix 用戶非常熟悉基于文本的 UI 模型。設想有一個 Perl 程序,讓我們先看一下這個模型用于該程序的簡單實現。標准的 Getopt::Std 模塊簡化了命令行參數的解析。這個程序僅僅爲了說明 Getopt::Std 模塊(沒有實際用途)。請參閱本文後面的參考資料。

使用 Getopt::Std 的命令行開關

#!/usr/bin/perl -w

use strict;# always use strict, it's a good habit

use Getopt::Std;# see "perldoc Getopt::Std"

my %options;

getopts('f:hl', \%options);# read the options with getopts

# uncomment the following two lines to see what the options hash contains

#use Data::Dumper;

#print Dumper \%options;

$options{h} && usage();# the -h switch

# use the -f switch, if it's given, or use a default configuration filename

my $config_file = $options{f} || 'first.conf';

print "Configuration file is $config_file\n";

# check for the -l switch

if ($options{l})

{

system('/bin/ls -l');

}

else

{

system('/bin/ls');

}

# print out the help and exit

sub usage

{

print

first.pl [-l] [-h] [-f FILENAME]

Lists the files in the current directory, using either /bin/ls or

/bin/ls -l.

The -f switch selects a different configuration file.

The -h switch prints this help.

EOHIPPUS

exit;

}

簡單的事件循環

當命令行參數不夠用時,下一步是編寫一個事件循環。在這種方案中,仍然可接受命令行參數,並且有時只使用命令行參數就足夠了。然而,事件循環支持用戶在不輸入任何參數的情形下調用程序,以及看到提示符。在此提示符下,通常可使用 help 命令,這將打印出更詳細的幫助。有時,這個 help 甚至可以是一個單獨的輸入提示符,有一個完整的軟件子系統來專門負責它。

帶有命令行開關的事件循環

#!/usr/bin/perl -w

use strict; # always use strict, it's a good habit

use Getopt::Std; # see "perldoc Getopt::Std"

my %options;

getopts('f:hl', \%options); # read the options with getopts

# uncomment the following two lines to see what the options hash contains

#use Data::Dumper;

#print Dumper \%options;

$options{h} && usage(1); # the -h switch, with exit option

# use the -f switch, if it's given, or use a default configuration filename

my $config_file = $options{f} || 'first.conf';

print "Configuration file is $config_file\n";

# check for the -l switch

if ($options{l})

{

system('/bin/ls -l');

}

else

{

my $input; # a variable to hold user input

do

{

print "Type 'help' for help, or 'quit' to quit\n- ";

$input = ;

print "You entered $input\n"; # let the user know what we got

# note that 'listlong' matches /list/, so listlong has to come first

# also, the i switch is used so upper/lower case makes no difference

if ($input =~ /listlong/i)

{

system('/bin/ls -l');

}

elsif ($input =~ /list/i)

{

system('/bin/ls');

}

elsif ($input =~ /help/i)

{

usage();

}

elsif ($input =~ /quit/i)

{

exit;

}

}

while (1); # only 'quit' or ^C can exit the loop

}

exit; # implicit exit here anyway

# print out the help and exit

sub usage

{

my $exit = shift @_ || 0; # don't exit unless explicitly told so

print

first.pl [-l] [-h] [-f FILENAME]

The -l switch lists the files in the current directory, using /bin/ls -l.

The -f switch selects a different configuration file.

The -h

switch prints this help.

Without the -l or -h arguments, will show

a command prompt.

Commands you can use at the prompt:

list:

list the files in the current directory

listlong:

list the files in the current directory in long format

help:

print out this help

quit:

quit the program

EOHIPPUS

exit if $exit;

}

這裏,通常會有三種選擇:

由于可能會有多個開關組合,所以程序的 UI 會複雜到不可忍受的程度。

UI 將發展爲 GUI。

從頭重新開發 UI,使其至少帶有一些解析功能。

第一種選擇太可怕,難以去想象。這裏不討論第二種選擇,但它的確在向後兼容性和靈活性方面提出了有趣的挑戰。第三種選擇是本文下面要討論的內容。

Parse::RecDescent 的快速教程

Parse::RecDescent 是一個用于解析文本的模塊。通過幾個簡單構造就可以用它完成幾乎所有的解析任務。更高級的文法構造可能會讓人望而生畏,不過在大多數用途中不需要這麽高級的文法。

Parse::RecDescent 是一個面向對象的模塊。它圍繞著文法創建解析器對象。文法(grammar)是一組以文本形式表示的規則。下面這個示例是一條匹配單詞的規則:

word 規則

word: /\w+/

這條規則一次或多次匹配字符(\w)。跟在冒號後的部分稱爲産品(production)。一條規則必須包含至少一個産品。一個産品可以包含其它規則或直接要匹配的內容。下面這個示例是一條規則,它可以匹配一個單詞、另一條規則(non-word)或一個錯誤(如果其它兩個都失敗):

另一些産品

token: word | non-word |

word: /\w+/

non-word: /\W+/

每個産品也可以包含一個用花括號括起的操作:

産品中的操作

print: /print/i { print_function(); }

如果操作是産品中的最後一項,則操作的返回碼決定産品是否成功。該操作是一種空産品,它將總是會匹配,除非它返回 0。

可以用 (s) 修飾符指定多個標記(token):

一個産品中帶一個或多個標記

word: letter(s)

letter: /\w/

也可以將 (?)(0 或 1)和 (s?)(0 到 N)修飾符作爲可選關鍵字來使用。

可以通過 $item[position] 或 $item{name} 機制來訪問産品中的任何內容。請注意,在第二種情形中,兩個單詞的名稱是相同的,所以必須使用位置查找。在第三種情形中,單詞數組以數組引用的形式存儲在 $item{word} 中。如果在産品中使用可選項,則數組定位方案將肯定無法很好地工作。一般來說,無論如何都應避免使用這種方案,因爲通過名稱查找的方式總是更方便更簡單:

使用 %item 和 @item 變量

print: /print/i word { print_function($item{word}); }

print2: /print2/i word word { print_function($item[1], $item[2]); }

print3: /print3/i w

 
功能豐富的Perl:輕松調試Perl的技巧
  錯誤所帶來的麻煩  軟件開發人員通常都低估了軟件測試的重要性。這一現象的根本原因很簡單:處理錯誤很困難!因爲錯誤往往暴露了代碼的根本缺陷,所以有時候開發人員甚至會爲了幾個錯誤而從頭開始重新編寫項目...查看完整版>>功能豐富的Perl:輕松調試Perl的技巧
 
功能豐富的Perl:Perl自動化系統管理
  UNIX 系統管理總是一個棘手的問題,運用正確的工具會使這個問題變得容易。在這一部分中,Teodor 提出了關于使用 Perl 來簡化和牢固系統管理的想法。在這種環境中,系統配置引擎 cfengine 是一個極其重要的工具。...查看完整版>>功能豐富的Perl:Perl自動化系統管理
 
功能豐富的Perl:JustAnotherPerlHacker
  JAPH:“Just another Perl hacker”  據我們所知,JAPH 格式是在二十世紀九十年代由 Randal Schwartz 推廣的(好幾處信息來源都同意這個說法)。今天,JAPH 到處可見,它們是由該流派的那些不知疲倦的藝術家們...查看完整版>>功能豐富的Perl:JustAnotherPerlHacker
 
功能豐富的Perl:Perl用于實現遺傳算法
  如果您的機器上已經安裝了 Perl 5.005 或者更高的版本,您可以運行一下文章中的例子。您的系統最好應該是安裝了最近的(2000 年或者更遲些)主流的 UNIX(Linux,Solaris,BSD),但其它種類的操作系統可能也可以...查看完整版>>功能豐富的Perl:Perl用于實現遺傳算法
 
功能豐富的Perl:用Perl讀寫Excel文件
  Spreadsheet::WriteExcel 和 Spreadsheet::ParseExcel  在 2000 年,Takanori Kawai 和 John McNamara 編寫出了 Spreadsheet::WriteExcel 和 Spreadsheet::ParseExcel 模塊並將它們張貼在 CPAN 上,這兩個模塊...查看完整版>>功能豐富的Perl:用Perl讀寫Excel文件