0

我已经使用 perl 有一段时间了。我想知道如何在 perl 中运行以下操作:

subtract(40)(20)

要得到结果:

20

我想我得看看 Perl 的自定义解析技术。这就是我现在正在查看的内容:

开发::声明

开发::CallParser

http://www.perl.com/pub/2012/10/an-overview-of-lexing-and-parsing.html

现在,我不确定要寻找什么或做什么。任何有关如何解决此问题的帮助,阅读内容将不胜感激。请清楚。谢谢你。

4

5 回答 5

3

我建议尝试Parse::Keyword。Parse::Keyword 非常适合解析自定义语法,因为它可以让您回调 Perl 解析器的各个部分,例如parse_listexpr, parse_block,parse_fullstmt等(参见perlapi)。

它有一个缺点,如果你使用它们来解析关闭变量的表达式,这些会被处理得很糟糕,但这可以用PadWalker 解决

Parse::Keyword(包括 PadWalker 诡计)是Kavorka使用的;这会做一些非常复杂的事情!p5-mop-redux 的早期版本也使用了它。

无论如何,这是一个如何解析你的奇怪函数的演示......

use v5.14;
use strict;
use warnings;

# This is the package where we define the functions...
BEGIN {
  package Math::Weird;

  # Set up parsing for the functions
  use Parse::Keyword {
    add      => \&_parser,
    subtract => \&_parser,
    multiply => \&_parser,
    divide   => \&_parser,
  };

  # This package is an exporter of course
  use parent 'Exporter::Tiny';
  our @EXPORT = qw( add subtract multiply divide );

  # We'll need these things from PadWalker
  use PadWalker qw( closed_over set_closed_over peek_my );

  sub add {
    my @numbers = _grab_args(@_);
    my $sum = 0;
    $sum += $_ for @numbers;
    return $sum;
  }

  sub subtract {
    my @numbers = _grab_args(@_);
    my $diff = shift @numbers;
    $diff -= $_ for @numbers;
    return $diff;
  }

  sub multiply {
    my @numbers = _grab_args(@_);
    my $product = 1;
    $product *= $_ for @numbers;
    return $product;
  }

  sub divide {
    my @numbers = _grab_args(@_);
    my $quotient = shift @numbers;
    $quotient /= $_ for @numbers;
    return $quotient;
  }

  sub _parser {
    lex_read_space;

    my @args;
    while (lex_peek eq '(')
    {
      # read "("
      lex_read(1);
      lex_read_space;

      # read a term within the parentheses
      push @args, parse_termexpr;
      lex_read_space;

      # read ")"
      lex_peek eq ')' or die;
      lex_read(1);
      lex_read_space;
    }

    return sub { @args };
  }

  # In an ideal world _grab_args would be implemented like
  # this:
  #
  #    sub _grab_args { map scalar(&$_), @_ }
  #
  # But because of issues with Parse::Keyword, we need
  # something slightly more complex...
  #
  sub _grab_args {
    my $caller_vars = peek_my(2);
    map {
      my $code = $_;
      my $closed_over = closed_over($code);
      $closed_over->{$_} = $caller_vars->{$_} for keys %$closed_over;
      set_closed_over($code, $closed_over);
      scalar $code->();
    } @_;
  }

  # We've defined a package inline. Mark it as loaded, so
  # that we can `use` it below.
  $INC{'Math/Weird.pm'}  = __FILE__;
};

use Math::Weird qw( add subtract multiply );

say add(2)(3);          # says 5
say subtract(40)(20);   # says 20

say multiply( add(2)(3) )( subtract(40)(20) );   # says 100
于 2013-12-18T22:22:26.777 回答
2

如果您可以忍受添加印记和箭头,那么您可以使用咖喱 subtract

my $subtract = sub {
  my($x) = @_;

  sub { my($y) = @_; $x - $y };
};

像这样称呼它

my $result = $subtract->(40)(20);

如果箭是可以接受的但不是印记,重铸subtract

sub subtract {
  my($x) = @_;

  sub { my($y) = @_; $x - $y };
};

在这种情况下调用看​​起来像

my $result = subtract(40)->(20);
于 2013-12-18T21:05:20.100 回答
1

请不要在您的程序上添加损坏的语法扩展来解决已解决的问题。你想要的是闭包,以及一种有时称为柯里化的技术。

柯里化是将一个接受多个参数的函数转换为一个每次调用一个参数的函数。例如,考虑

sub subtract {
  my ($x, $y) = @_;
  return $x - $y;
}

现在我们可以创建一个已经提供第一个参数的子程序:

sub subtract1 { subtract(40, @_) }

现在调用subtract1(20)的计算结果为20

我们可以改用匿名子例程,这使得它更加灵活:

my $subtract = sub { subtract(40, @_) };
$subtract->(20);

我们不需要那个变量:

sub { subtract(40, @_) }->(20); # equivalent to subtract(40, 20)

我们可以直接写成subtract这样:

sub subtract_curried {
  my $x = shift;
  # don't return the result, but a subroutine that calculates the result
  return sub {
    my $y = shift;
    return $x - $y;
  };
}

现在:subtract_curried(40)->(20)– 注意中间的箭头,因为我们正在处理代码引用(匿名子例程的另一个名称,或闭包)。

这种编写函数的风格在HaskellOCaml等函数式语言中更为常见,其语法更漂亮。它允许非常灵活的功能组合。如果您对 Perl 中的这种编程感兴趣,您可能想阅读Higher-Order Perl

于 2013-12-18T21:08:56.350 回答
0

@Heartache:请忘记这个挑战,因为它对解析器和用户没有意义。

您可以考虑使用fn[x][y]orfn{x}{y}哪些是有效的语法变体 - 即您可以堆叠但不是列表,或者[]看起来不错的 or 也是有效且有意义的语法变体。但不知道应该在哪个上下文中使用第二个列表。{}fn(x,y)fn(x)->(y)fn(x)(y)

常见的fn(x)(y)解释是fn(x); (y) => (y). 它在评估第一次调用后返回第二个列表。

于 2013-12-18T22:40:32.920 回答
-1

您可以创建源代码过滤器

package BracketFilter;

use Filter::Util::Call;

sub import {
    filter_add(sub {
        my $status;
        s/\)\(/, /g if ($status = filter_read()) > 0;
        return $status ;
    });
}

1;

并使用它:

#!/usr/bin/perl

use BracketFilter;

subtract(40)(20);

sub subtract {
    return $_[0] - $_[1];
}
于 2013-12-18T22:35:38.360 回答