1

I'm just starting in Perl and I'm quite enjoying it. I'm writing some basic functions, but what I really want to be able to do is to use those functions intelligently using console commands. For example, say I have a function adding two numbers. I'd want to be able to type in console "add 2, 4" and read the first word, then pass the two numbers as parameters in an "add" function. Essentially, I'm asking for help in creating some basic scripting using Perl ^^'.

I have some vague ideas about how I might do this in VB, but Perl, I have no idea where I'd start, or what functions would be useful to me. Is there something like VB.net's "Split" function where you can break down the contents of a scalar into an array? Is there a simple way to analyse one word at a time in a scalar, or iterate through a scalar until you hit a separator, for example?

I hope you can help, any suggestions are appreciated! Bear in mind, I'm no expert, I started Perl all of a few weeks ago, and I've only been doing VB.net half a year.

Thank you!

Edit: If you're not sure what to suggest and you know any simple/intuitive resources that might be of help, that would also be appreciated.

4
  • 5
    I heartily recommend the "Learning Perl" book (it's one of the O'Reilly series). I used it to learn the language a decade or more ago, and found it very helpful. Also, the Perl programming documentation should be bookmarked; it's your friend. Commented Mar 12, 2013 at 18:16
  • Thank you for the quick reply! I don't suppose you know if Perl is a language that changes quickly? That's my fear about buying books for programming languages, I worry that the language will age quickly. Commented Mar 12, 2013 at 18:30
  • 2
    Perl has been around since 1987, and won't be going away (at least within our lifetimes). Between 90 and 95% of what you'll use day in and day out, the essential core elements, won't change. Modules come and go, but the core stuff is pretty solid. It's a great language; I predict you'll have lots of fun learning it. Commented Mar 12, 2013 at 20:08
  • 1
    While the old standard books are good, I recommend the Modern Perl Book (its free online and pdf!). A curated list of tutorials exists at perl-tutorial.org Commented Mar 12, 2013 at 21:46

3 Answers 3

3

Its rather easy to make a script which dispatches to a command by name. Here is a simple example:

#!/usr/bin/env perl

use strict;
use warnings;

# take the command name off the @ARGV stack
my $command_name = shift;

# get a reference to the subroutine by name
my $command = __PACKAGE__->can($command_name) || die "Unknown command: $command_name\n";

# execute the command, using the rest of @ARGV as arguments
# and print the return with a trailing newline
print $command->(@ARGV);
print "\n";

sub add {
  my ($x, $y) = @_;
  return $x + $y;
}

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

This script (say its named myscript.pl) can be called like

$ ./myscript.pl add 2 3

or

$ ./myscript.pl subtract 2 3

Once you have played with that for a while, you might want to take it further and use a framework for this kind of thing. There are several available, like App::Cmd or you can take the logic shown above and modularize as you see fit.

Sign up to request clarification or add additional context in comments.

2 Comments

Sorry, I don't understand a few parts of the code... what is __PACKAGE__->can($command_name) etc. doing? And what does the -> operator mean? Also, when you print $command->(@ARGV). Yeah, sorry, new to Perl ^^'
__PACKAGE__ is the current namespace, can is a universal method which asks if a class (or package) has a certain method or function and if so it returns a reference to it. -> is the arrow operator which is a generic dereference operator or method invocation operator. Since $command is a reference to a function, calling it like I do executes the function with those arguments. You might want to read The Modern Perl Book to get started.
1

You want to parse command line arguments. A space serves as the delimiter, so just do a ./add.pl 2 3 Something like this:

$num1=$ARGV[0];
$num2=$ARGV[1];

print $num1 + $num2;

will print 5

4 Comments

Dude, that's awesome! It's not what I was looking for but it may be even more useful. Thank you very much, and also thank you for the quick reply!
$num = shift @ARGV works pretty well too. It takes the next argument and puts it in $num. Don't mix the two though, shift also moves $ARGV[1] into $ARGV[0]. I personally usually use while(length($arg = shift @ARGV) > 0) in my command line parsing functions -- $arg is the next argument, and checking the length lets arguments of 0 not stop the process -- but there are many ways...
That sounds interesting, Bob. So it moves everything in an array along by one to the left and transfers the zeroth element into the scalar variable? Is that right?
That's correct. Again, always check the documentation (shift).
1

Here is a short implementation of a simple scripting language.

Each statement is exactly one line long, and has the following structure:

Statement = [<Var> =] <Command> [<Arg> ...]
# This is a regular grammar, so we don't need a complicated parser.

Tokens are seperated by whitespace. A command may take any number of arguments. These can either be the contents of variables $var, a string "foo", or a number (int or float).

As these are Perl scalars, there is no visible difference between strings and numbers.

Here is the preamble of the script:

#!/usr/bin/perl
use strict;
use warnings;
use 5.010;

strict and warnings are essential when learning Perl, else too much weird stuff would be possible. The use 5.010 is a minimum version, it also defines the say builtin (like a print but appends a newline).

Now we declare two global variables: The %env hash (table or dict) associates variable names with their values. %functions holds our builtin functions. The values are anonymous functions.

my %env;

my %functions = (
  add => sub { $_[0] + $_[1] },
  mul => sub { $_[0] * $_[1] },
  say => sub { say $_[0] },
  bye => sub { exit 0 },
);

Now comes our read-eval-loop (we don't print by default). The readline operator <> will read from the file specified as the first command line argument, or from STDIN if no filename is provided.

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;
}

That was fairly trivial. Now comes some parsing code. Perl “binds” regexes to strings with the =~ operator. Regexes may look like /foo/ or m/foo/. The /x flags allows us to include whitespace in our regex that doesn't match actual whitespace. The /g flag matches globally. This also enables the \G assertion. This is where the last successful match ended. The /c flag is important for this m//gc style parsing to consume one match at a time, and to prevent the position of the regex engine in out string to being reset.

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 arrays can be handled like linked list: shift removes and returns the first element (pop does the same to the last element). push adds an element to the end, unshift to the beginning.

Out little programming language can execute simple programs like:

#!my_little_language
$a = mul 2 20
$b = add 0 2
$answer = add $a $b
say $answer
bye

If (1) our perl script is saved in my_little_language, set to be executable, and is in the system PATH, and (2) the above file in our little language saved as meaning_of_life.mll, and also set to be executable, then

$ ./meaning_of_life

should be able to run it.

Output is obviously 42. Note that our language doesn't yet have string manipulation or simple assignment to variables. Also, it would be nice to be able to call functions with the return value of other functions directly. This requires some sort of parens, or precedence mechanism. Also, the language requires better error reporting for batch processing (which it already supports).

1 Comment

Oh wow, that's complicated! I understood most of the code before you said it was fairly trivial - I think the regexes were where I got lost. I know what a regex is but I'm not very good at using them. But really, thanks a bunch!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.