以下は、単純なスクリプト言語の短い実装です。
各ステートメントの長さは正確に 1 行で、次のような構造になっています。
Statement = [<Var> =] <Command> [<Arg> ...]
# This is a regular grammar, so we don't need a complicated parser.
トークンは空白で区切られます。コマンドは、任意の数の引数を取ることができます。これらは、変数の内容$var
、文字列"foo"
、または数値 (int または float) のいずれかです。
これらは Perl のスカラーであるため、文字列と数値の間に目に見える違いはありません。
スクリプトのプリアンブルは次のとおりです。
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
strict
warnings
これらは Perl を学習する際に不可欠です。はuse 5.010
最小バージョンであり、say
ビルトインも定義します (a と同様ですprint
が、改行を追加します)。
ここで、2 つのグローバル変数を宣言します。%env
ハッシュ (テーブルまたは辞書) は、変数名をその値に関連付けます。%functions
組み込み関数を保持します。値は無名関数です。
my %env;
my %functions = (
add => sub { $_[0] + $_[1] },
mul => sub { $_[0] * $_[1] },
say => sub { say $_[0] },
bye => sub { exit 0 },
);
次に、read-eval-loop を実行します (デフォルトでは出力しません)。readline 演算子<>
は、最初のコマンド ライン引数として指定されたファイルから読み取るか、ファイル名が指定されていない場合は STDIN から読み取ります。
while (<>) {
next if /^\s*\#/; # jump comment lines
# parse the line. We get a destination $var, a $command, and any number of @args
my ($var, $command, @args) = parse($_);
# Execute the anonymous sub specified by $command with the @args
my $value = $functions{ $command }->(@args);
# Store the return value if a destination $var was specified
$env{ $var } = $value if defined $var;
}
それはかなり些細なことでした。次に、解析コードをいくつか示します。Perl は、正規表現を演算子で文字列に「バインド」します=~
。/foo/
正規表現はまたはのようになりますm/foo/
。フラグを使用すると、/x
正規表現に実際の空白と一致しない空白を含めることができます。フラグは/g
グローバルに一致します。これにより、\G
アサーションも有効になります。これは、最後に成功したマッチが終了した場所です。/c
フラグは、このスタイルの解析で一度に 1 つの一致を消費し、出力文字列内の正規表現エンジンの位置がリセットされるのを防ぐために重要ですm//gc
。
sub parse {
my ($line) = @_; # get the $line, which is a argument
my ($var, $command, @args); # declare variables to be filled
# Test if this statement has a variable declaration
if ($line =~ m/\G\s* \$(\w+) \s*=\s* /xgc) {
$var = $1; # assign first capture if successful
}
# Parse the function of this statement.
if ($line =~ m/\G\s* (\w+) \s*/xgc) {
$command = $1;
# Test if the specified function exists in our %functions
if (not exists $functions{$command}) {
die "The command $command is not known\n";
}
} else {
die "Command required\n"; # Throw fatal exception on parse error.
}
# As long as our matches haven't consumed the whole string...
while (pos($line) < length($line)) {
# Try to match variables
if ($line =~ m/\G \$(\w+) \s*/xgc) {
die "The variable $1 does not exist\n" if not exists $env{$1};
push @args, $env{$1};
}
# Try to match strings
elsif ($line =~ m/\G "([^"]+)" \s*/xgc) {
push @args, $1;
}
# Try to match ints or floats
elsif ($line =~ m/\G (\d+ (?:\.\d+)? ) \s*/xgc) {
push @args, 0+$1;
}
# Throw error if nothing matched
else {
die "Didn't understand that line\n";
}
}
# return our -- now filled -- vars.
return $var, $command, @args;
}
Perl 配列は連結リストのように扱うことができます:shift
最初の要素を削除して返します (pop
最後の要素に対しても同じことを行います)。要素を末尾、先頭push
に追加します。unshift
ほとんどのプログラミング言語は、次のような単純なプログラムを実行できます。
#!my_little_language
$a = mul 2 20
$b = add 0 2
$answer = add $a $b
say $answer
bye
(1) Perl スクリプトが に保存されmy_little_language
、実行可能に設定され、システム PATH にあり、(2) 上記の小さな言語のファイルが として保存されmeaning_of_life.mll
、実行可能に設定されている場合、
$ ./meaning_of_life
実行できるはずです。
出力は明らかに42
. 私たちの言語にはまだ文字列操作や変数への単純な代入がないことに注意してください。また、他の関数の戻り値で直接関数を呼び出せるといいですね。これには、ある種のかっこまたは優先メカニズムが必要です。また、この言語では、バッチ処理 (既にサポートされています) のエラー報告を改善する必要があります。