http://www.perlmonks.org?node_id=203239

$Variable_B has asked for the wisdom of the Perl Monks concerning the following question:

Hello Wise and Helpful Monks, I have a question for you that troubles me...

Ok, so I got bored and I thought I would make a calculator as one of my first programs. I figured out how to make it do anything, but you had to change the script everytime you had a different problem or operator. Then, as time went on I learned more as we all do. I learned the concept of "<STDIN>" so that I wouldn't have to change the script for new variables because the user would input them. I ended up with this and this is far as I have gotten in my problem.
#!/usr/bin/perl $a="<STDIN>"; $b="<STDIN>"; print "$a+$b\n";

I have a problem though. As you can see I need to change the script if I want any other operation such as: subtraction, powers, multiplication, division, etc...
I haved asked in the CB and searched my books and the web but in vain, because I cannot find a way to make the user input the operation. I thought that this idea might look like....

$operator="<STDIN>"

but I was obviously and expectedly wrong.
Any ideas on how I can get this little operation input problem fixed?


-Variable_B

Replies are listed 'Best First'.
Re: Inputing an Operator is it possible?
by dbp (Pilgrim) on Oct 07, 2002 at 03:19 UTC
    Well, you can do this, but it is a sort of silly thing to do. A better solution would be to put the time in to actually evaluate the user's input and act on that. If you really want to build a calculator, I'd suggest using reverse polish notation on your first go (see dc(1)) because it is easier to parse (less ambiguous) than standard notation. Now for the silly way (which is worth seeing since evaluating strings as code is sometimes useful):
    #!/usr/bin/perl -w use strict; my $eq = <STDIN>; print eval $eq, "\n";
    This will do what you want, but it is very DANGEROUS because any arbitrary code can be embedded in the expression (although this isn't exactly a production project anyway). If you wanted to go to the trouble to catch non-calc-related code, you might as well code up a real calculator. If not, call perl -e and do a calulation on the command line.
      Thanks! That's awesome. I don't need all those little variables and perl eval()s for me! Great! -Variable_B
        Glad you liked it. As we all know, Laziness is a wonderful virtue. But I figured I should provide you with something more constructive. Here are the basics of that reverse Polish calculator I was rambling about. It supports +, -, *, /. Print the stack with 'p' and quit with 'q'. You could probably make this a bit more concise if you wanted to. In fact, that might make for some pretty good GOLF. Anyway, Enjoy.
        #!/usr/bin/perl -w use strict; my @stack = (); my %ops = ( '+' => \&sum, '-' => \&difference, '*' => \&product, '/' => \&quotient, 'p' => sub { print $_, "\n" foreach @stack; }, 'q' => sub { exit 0; } ); while(<>) { chomp; if (exists $ops{$_}) { $ops{$_}->(); # The badass regex is out of the Perl Cookbook, page 44 # Makes sure non-ops are valid numbers } elsif ($_ =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/) { push @stack, $_; } else { warn "Bad input: $_\n"; } } sub pop_stack { my $rhs = pop @stack; my $lhs = pop @stack; if ((! defined $lhs) || (! defined $rhs)) { warn "Not enough values on stack!\n"; push @stack, $rhs if defined $rhs; return undef; } return $lhs, $rhs; } sub sum { my ($lhs, $rhs) = pop_stack; return unless defined $lhs; my $res = $lhs + $rhs; print "$res \n"; push @stack, $res; } sub difference { my ($lhs, $rhs) = pop_stack; return unless defined $lhs; my $res = $lhs - $rhs; print "$res \n"; push @stack, $res; } sub product { my ($lhs, $rhs) = pop_stack; return unless defined $lhs; my $res = $lhs * $rhs; print "$res \n"; push @stack, $res; } sub quotient { my ($lhs, $rhs) = pop_stack; return unless defined $lhs; my $res = $lhs / $rhs; print "$res \n"; push @stack, $res; }

        You have to be cautious of using eval(). It's quite possible for some joker to come along and type "rm / -rf" and run your program. That said, it's also the easiest way to get the code to work. Some others have suggested some very good checks to take care of malicious use of eval().



        If you make something idiot-proof, eventually someone will make a better idiot.
        I am that better idiot.
        A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Inputing an Operator is it possible?
by krusty (Hermit) on Oct 07, 2002 at 03:12 UTC
    Placing your angle operator inside of double quotes treats it as a literal "less than" and "greater than" symbol...

    Instead try:
    chomp($operator = <STDIN>);
    with the extra chomp there to get rid of your record input separator, (newline by default) so that you have control over where your newlines go when you print.

    Also if you want to add $a and $b instead of printing them out literally, try
    print $a + $b, "\n";


    Cheers,
    Kristina
    A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Inputing an Operator is it possible?
by peschkaj (Pilgrim) on Oct 07, 2002 at 03:23 UTC

    First off, you can't have your <STDIN> in double-quotes. That will, quite literally, assign the characters

    <STDIN>
    to all of your variables.

    What you can do is use a conditional statement to check for the operator that the user puts in. Something generic like the following will work. Of course, you'll have to work out the mojo behind each condition.

    #!/usr/bin/perl -w print "First Number: "; $a = <>; # <> is short for <STDIN> print "Second Number: "; $b = <>; print "Operator: "; $operator = <>; if ( $operator == "+" ) { # Do some addition } elsif ( $operator == "-" ) { # Do some subtraction } elsif ( $operator == "/" ) { # Division } elsif { $operator == "*" ) { # Multiply } else { # Catch everything else print "DON'T YOU KNOW MATH?!?!\nMONKIES CAN DO MATH!!!\n"; }

    And there you have it. CS 101, Assignment 1.



    If you make something idiot-proof, eventually someone will make a better idiot.
    I am that better idiot.
Re: Inputing an Operator is it possible?
by blahblahblah (Priest) on Oct 07, 2002 at 03:26 UTC
    What I would do for something like this is have a limited number of operators, then scan for each and do the right thing. For example,
    $operator = <STDIN>; chomp $operator; if ($operator eq '+') { $result = $a + $b; } elsif ($operator eq '-') { $result = $a - $b; } elsif ($operator eq '*') { $result = $a * $b; } else { die "unknown operator \"$operator\"\n"; }
    I'm more comfortable with code like that because it leaves less room for unexpected errors from bad input.
    But you can do what you're looking for with something like this, using eval:
    $first=<STDIN>; $second=<STDIN>; $operator=<STDIN>; chomp $first; chomp $second; chomp $operator; $expression = "$first $operator $second"; print "$expression:\n"; $result = eval $expression; if ($@) # ($@ is set by eval if something went wrong) { print "oh no!: $@\n"; } else { print "$result\n"; }

    By the way, you shouldn't get into the habit of using $a and $b as variable names, since those two variables have special meaning for perl's sort function.

Re: Inputing an Operator is it possible?
by robartes (Priest) on Oct 07, 2002 at 08:44 UTC
    Hi,

    another way you could implement this is using a hash of subroutine references, like so:

    use strict; my $operator=<STDIN>; my $op_map={}; my $one=10; my $two=5; chomp $operator; define_op_map(); if ( defined ($op_map->{$operator}) ) { &{$op_map->{$operator}}; } else { print "This op is undefined. Executing it might end the universe as +we know it.\n"; } sub define_op_map { $op_map->{'+'}=sub{print $one + $two . "\n"}; $op_map->{'-'}=sub{print $one - $two . "\n"}; $op_map->{'*'}=sub{print $one * $two . "\n"}; $op_map->{'/'}=sub{print $one / $two . "\n"}; }
    This has the advantage that you can define arbitrary operators, such as:
    $op_map->{'camel'}=sub{print "$one is a $two with humps.\n"};
    A more complicated way to do the above is to define the subroutine references in an array and then fill the op_map hash by looping over this array. That way extending the functionality of the thing is cleaner.

    CU
    Robartes-

Re: Inputing an Operator is it possible?
by fruiture (Curate) on Oct 07, 2002 at 11:12 UTC

    A Calculator might be a bad task as "one of your first programs", because parsing mathematical expressions is an advanced challenge, but you can do it in steps, getting better and better. First idea is getting operand,operator,operand as single unput:

    chomp(my $left = <STDIN>); chomp(my $op = <STDIN>); chomp(my $right = <STDIN>); print $op eq '+' ? $left + $right : $op eq '-' ? $left - $right : $op eq '*' ? $left * $right : $op eq '/' ? $left / ($right||1) : "dunno operator '$op'", , "\n";

    Next step is to read the whole thing in a one string:

    chomp( my $expr = <STDIN> ); # and to parse it with simple split my ($left,$op,$right) = split /\s+/,$expr # next step is code of above

    Sooner or later you'll want to replace the ?:?: .. chain with a hash:

    my %operations = ( '+' => sub { (shift) + (shift) }, '-' => sub { (shift) - (shift) }, '*' => sub { (shift) * (shift) }, '/' => sub { (shift) / (shift||1) }, ); my ($left,$op,$right) = ...; #wherever you get them from print exists $operations{$op} ? $operations{$op}->($left,$right) : "dunno operator '$op'", "\n";

    Next you want to verify the input using a regexp:

    chomp(my $expr = <STDIN>); $expr =~ /(\d+)\s+([+-/*])\s+(\d+)/ or die "invalid input\n"; my ($left,$op,$right) = ($1,$2,$3); # ...

    Then you'll find advanced regexps for numbers (see perlfaq4), Math::Expr, and finally Parse::RecDescent .

    --
    http://fruiture.de