5

我想知道如何运行这样的操作

$T = 25 C;
@specs = (273.15 K, 23 bar, 2.0 mol/s);

并让他们编译。我对他们的结果是什么或如何实施并不挑剔。我的目标是让带有传统后缀单元注释的物理量表达式编译为这些单元的 perl 表达式。

我认为我需要使用自定义解析技术,但我更喜欢使用任何现有的功能或解析模块,而不是将正则表达式过滤器应用于我的原始源。

Parse::Keyword似乎很有希望,但我看不出它是否可以解析后缀操作,并且它声称已被弃用。

编辑:如果可能,我想避免使用源过滤器,因为我不想为 Perl 的语法极端情况编写正则表达式(例如“25 (J/K)”)。

Perl 在这里产生的错误告诉我们:

perl -E "25 C"
Bareword found where operator expected at -e line 1, near "25 C"
(Missing operator before C?)

似乎我需要挂钩 Perl 在数字文字之后检测运算符的位置。

Devel::Declare可以添加后缀运算符吗?如果是这样,怎么做?

4

3 回答 3

3

你可以滥用超载来获得接近你想要的东西。

#!/usr/bin/perl

use strict;
use warnings;
use 5.010;

use Data::Dumper;
use MyUnits;

my $T = '25 C';

say Dumper $T;

my @specs = ('273.15 K', '23 bar', '2.0 mol/s');

say Dumper \@specs;

正如您将看到的,您将返回具有“值”和“类型”属性的对象。

MyUnits.pm 看起来像这样:

package MyUnits;

use strict;
use warnings;

use overload
  '""' => \&to_string;

my %_const_handlers = (
  q => \&string_parser,
);

sub string_parser {
  my $c = eval { __PACKAGE__->new($_[0]) };
  return $_[1] if $@;
  return $c;
}

sub import {
  overload::constant %_const_handlers;
}

sub new {
  my $class = shift;

  # ->new(type => ..., value => ...)
  if (@_ == 4) {
    return bless { @_ }, $class;
  }
  # ->new({ type => ..., value => ...)
  if (@_ == 1 and ref $_[0] eq 'HASH') {
    return bless $_[0], $class;
  }
  # -> new('999xxx')
  if (@_ == 1 and ! ref $_[0]) {
    my ($val, $type) = $_[0] =~ /(\d+\.?\d*)\s*(.+)/;
    return bless({
      value => $val, type => $type,
    });
  }
}

sub to_string {
  return "$_[0]->{value}$_[0]->{type}";
}

1;

你会想要添加更多的方法来让它做一些有用的事情。

在大多数情况下,重载并不比源过滤器少多少。它几乎肯定会让你的程序慢得多。

于 2016-08-03T08:12:36.930 回答
2

如果您愿意使用中介功能,如果您足够用力地眯眼,您可以获得看起来像您想要的东西。我不可能用 Perl 编写 Haskell ;-)

package My::Units;

use strict;
use warnings;

use Importer 'Math::Units::PhysicalValue', 'PV';

our @EXPORT = qw();
our @EXPORT_OK = qw( with_units );

sub with_units(\$@) {
    my (undef, $value, $units) = @_;
    ${ $_[0] } = PV "$value $units";
    return;
}

__PACKAGE__;
__END__

从脚本中使用它:

#!/usr/bin/env perl

use feature 'say';
use strict;
use warnings;

use lib '.';
use Importer 'My::Units', 'with_units';

with_units my $x => 25 => 'C';
with_units my $y => 20 => 'F';
with_units my $z =>  0 => 'C';

say $x + $y;
say $y + $z;

输出:

C:\...\t> perl t.pl
97楼
-6.67 摄氏度

现在,Math::Units::PhysicalValue无条件地使用Math::BigFloat,所以算术应该很慢,但很准确。如果你真的需要这种东西,你可能想研究一下Math::UnitsMath::Units::PhysicalValue的清理部分,并从这些部分中更快地创建一些东西。

于 2016-08-03T17:14:03.697 回答
1

源过滤器是出了名的脆弱,但它可能是获得你想要的东西的最简单的方法,而不需要深入研究 perl 的不足之处。可能是这样的:

package U;
use strict;
use warnings;
use Filter::Simple;

my @UNITS = qw( degC degK bar mol s );

FILTER {
    my $unit_re = '(?:' . join('|', @UNITS) . ')';
    s#(\d+(?:\.\d\+)?)\s?((?:${unit_re}[*/])*$unit_re)\b#Units->new({value => $1, unit => '$2'})#g;
};

package Units;
use Class::Accessor 'antlers';
has value => ( is => "ro", isa => "Num" );
has unit => ( is => "ro", isa => "Str" );

1;

我对它很敏感,并改变了“C”,因为看起来你不是指库仑。use utf8不过,您可能会全力以赴°C;)

测试:

perl -I. -MU -e'my $val = 23 degK/s; printf "Value: %g, Unit: %s\n", $val->value, $val->unit'
Value: 23, Unit: degK/s

当然,简单的正则表达式还有很多不足之处,比如括号等你可能需要的东西Text::Balanced,而且这个Units类实际上可能更想解析那个单位字符串并重载一些运算符,这样你就可以用单位进行计算.

于 2016-08-03T09:55:23.740 回答