这是一个简单脚本语言的简短实现。
每条语句只有一行长,并具有以下结构:
Statement = [<Var> =] <Command> [<Arg> ...]
# This is a regular grammar, so we don't need a complicated parser.
令牌由空格分隔。一个命令可以接受任意数量的参数。这些可以是 variables $var
、 string"foo"
或数字(int 或 float)的内容。
由于这些是 Perl 标量,因此字符串和数字之间没有明显的区别。
这是脚本的序言:
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
strict
并且warnings
在学习 Perl 时必不可少,否则可能会出现太多奇怪的东西。这use 5.010
是一个最低版本,它还定义了say
内置函数(像 aprint
但附加了一个换行符)。
现在我们声明两个全局变量:%env
哈希(表或字典)将变量名与其值相关联。%functions
保存我们的内置函数。这些值是匿名函数。
my %env;
my %functions = (
add => sub { $_[0] + $_[1] },
mul => sub { $_[0] * $_[1] },
say => sub { say $_[0] },
bye => sub { exit 0 },
);
现在是我们的读取评估循环(默认情况下我们不打印)。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
标志对于这种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
到开头。
Out little 编程语言可以执行简单的程序,例如:
#!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
。请注意,我们的语言还没有字符串操作或对变量的简单赋值。此外,如果能够直接调用具有其他函数返回值的函数,那就太好了。这需要某种括号或优先机制。此外,该语言需要更好的批处理错误报告(它已经支持)。