Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl custom syntax for suffixes or custom postfix operators

I'd like to know how I can run operations like this

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

and get them to compile. I'm not picky about what their result is, or how it's implemented. My goal is to let expressions for physical quantities with conventional postfix unit annotations compile to perl expressions for those units.

I think I need to use custom parsing techniques, but I'd prefer to use any existing functionality or parsing modules over just applying regex filters to my raw source.

Parse::Keyword seemed promising, but I can't see whether it can parse postfix operations, and it claims to be deprecated.

Edit: I'd like to avoid source filters if possible because I'd rather not write regexes for Perl's syntactical corner cases (e.g. "25 (J/K)").

The error Perl produces here is telling:

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

It seems like I need to hook into where Perl detects operators after numeric literals.

Could Devel::Declare add postfix operators? If so, how?

like image 269
alexchandel Avatar asked Aug 02 '16 16:08

alexchandel


1 Answers

You can abuse overloading to get something close to what you want.

#!/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;

As you'll see, you get objects back with "value" and "type" attributes.

MyUnits.pm looks like this:

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;

You'd want to add more methods to enable it to do something useful.

In most cases, overloading isn't much less of a party trick than source filters are. It will almost certainly make your program far slower.

like image 124
Dave Cross Avatar answered Oct 19 '22 05:10

Dave Cross