Qwerty Reverse Polish Notation

From Esolang

Jump to: navigation, search

Qwerty Reverse Polish Notation is a Reverse Polish notation calculator that is designed to be generally Turing-complete. In Qwerty Reverse Polish Notation (QRPN), almost all opcodes (except literals, references, labels and variables) are as small as 1 keystroke. The opcodes are separated by space (" ").

Contents

[edit] Quick Reference

  +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
  | ~ not  | ! pop  | @ getn | # putn | $ swap | % mod  | ^ pow  | & and  | * mul  | ( dec  | ) inc  | _ neg  | + add  |
  | ` xor  | 1 ---- | 2 ---- | 3 ---- | 4 ---- | 5 ---- | 6 ---- | 7 ---- | 8 ---- | 9 ---- | 0 ---- | - sub  | = eq   |
  +--------+--------+--------+--------+--------+--------+--------+--------+-----------------+--------+--------+--------+
  | Q      | W      | E 10^x | R root | T atan | Y      | U      | I      | O      | P      | { shl  | } shr  | | or   |
  | q      | w      | e exp  | r sqrt | t tan  | y      | u      | i      | o      | p      | [ flr  | ] ceil | \ roun |
  +--------+--------+--------+--------+--------+--------+--------+--------+-----------------+--------+--------+--------+
  | A      | S asin | D      | F      | G      | H      | J      | K      | L log  | : dup  | "      |########|########|
  | a abs  | s sin  | d      | f      | g      | h      | j      | k      | l ln   | ;      | '      |########|########|
  +--------+--------+--------+--------+--------+--------+--------+--------+-----------------+--------+--------+--------+
  | Z      | X      | C acos | V      | B      | N      | M max  | < lt   | > gt   | ? rand |########|########|########|
  | z      | x exit | c cos  | v      | b bool | n      | m min  | , getc | . putc | / div  |########|########|########|
  +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+

[edit] Turing-Complete?

QRPN is stack-based, however, arrays/lists/tapes can be achieved by using variable-variables. For example, the following code creates a list of numbers from 1 to 5

5 l> ( : : =$ <l

[edit] Examples

Ask user to input 2 numbers, and print its sum.

 @ @ + #

Digital root calculator

 1 @ 1 - + 9 %

Locals-based factorial

 @ =value               // value = input_number();
 1 =result              // result = 1;
 $value =i              // i = value;
 >loop                  // loop:
  $result $i * =result  //   result *= i;
  $i ( =i $i            //   i --;
 <loop                  // if ($i) { goto loop; }
 $result                // return result;

without comments:

 @ =value 1 =result $value =i >loop $result $i * =result $i ( =i $i <loop $result

[edit] Implementation

This is the implementation in Perl, for more details, scroll down and see the POD documentation.

package Math::QwertyRPN;
require Exporter;

use vars qw($VERSION @ISA @EXPORT_OK);
use Math::Trig qw(asin acos tan atan);
use Math::BigFloat qw(:constant);
use POSIX qw(ceil floor log10);
use strict;

@ISA = qw(Exporter);
@EXPORT_OK = qw(eval_rpn shell);
$VERSION = '0';

sub eval_rpn($) {
  # init variables
  our $code = shift @_;
  our @tokens = (split /(?:\s|\n|\r)+/m, $code);
  our %labels = { };
  our $i = 0;

  # process code
  $code =~ s'//.*?$mg;
  $code =~ s'/\*([^*]|\*[^\/])*\*/mg;
  # index GOTO labels
  $i ++, (/>(\w+)/ and $labels{$1} = $i) foreach @tokens;

  # runtime variables
  our @stack = ();
  our %variables = { };
  our $pc = 0;
  $_ = 1;

  sub op(&) {
    @stack < 2 and die 'Not enough operands.';
    my $sub = shift;
    my ($r, $l) = (pop @stack, pop @stack);
    push @stack, $sub->($l, $r);
  }
  sub unary(&) {
    @stack < 1 and die 'Stack is empty.';
    my $sub = shift;
    $stack[-1] = $sub->($stack[-1]);
  }
  sub round($) {
    my $n = shift;
    return int $n + 0.5 * ($n <=> 0);
  }
  sub bool($) {
    shift and return 1;
    return 0;
  }

  # main loop
  while ($_) {
    $_ = $tokens[$pc++];
    # numbers
    /^[+-]?(\d+(\.\d+)?)$/
                and push @stack, $1 + 0;
    # goto statements
    /^<(\w+)$/  and do { return if @stack < 1;
                         pop @stack and $pc = $labels{$1}; };
    # get variable
    /^\$(\w+)$/ and push @stack, $variables{$1};
    # get variable-variables
    /^\$\$$/    and do { die 'Not enough operands for variable-variable.' if @stack < 1;
                         push @stack, $variables{pop @stack}; };
    # assign variables
    /^\=(\w+)$/ and do { die 'Not enough operands for variable assignment.' if @stack < 1;
                         $variables{$1} = pop @stack; };
    # assign variable-variables
    /^\=\$$/    and do { die 'Not enough operands for variable-variable assignment.' if @stack < 2;
                         my $l = pop @stack;
                         my $r = pop @stack;
                         $variables{$l} = $r; };
    # first row: 12345
    /^\~$/      and unary { ~(shift @_) };
    /^\!$/      and pop @stack;
    /^\@$/      and push @stack, <STDIN> + 0;
    /^\#$/      and print 0 + pop @stack;
    /^\$$/      and do { $a = pop @stack;
                         $b = pop @stack;
                         push @stack, $a;
                         push @stack, $b; };
    /^\%$/      and op { (shift @_) % (shift @_) };
    /^\^$/      and op { (shift @_) ** (shift @_) };
    /^\&$/      and op { (shift @_) & (shift @_) };
    /^\*$/      and op { (shift @_) * (shift @_) };
    /^\($/      and unary { (shift @_) - 1 };
    /^\)$/      and unary { (shift @_) + 1 };
    /^_$/       and unary { -(shift @_) };
    /^\+$/      and op { (shift @_) + (shift @_) };
    /^\`$/      and op { (shift @_) ^ (shift @_) };
    /^\-$/      and op { (shift @_) - (shift @_) };
    /^\=$/      and op { (shift @_) == (shift @_) };

    # second row: qwerty
    /^E$/       and unary { 10 ** shift @_ };
    /^R$/       and op { (shift @_) ** (1 / (shift @_)) };
    /^T$/       and unary { atan shift @_ };
    /^\{$/      and op { (shift @_) << (shift @_) };
    /^\}$/      and op { (shift @_) >> (shift @_) };
    /^\|$/      and op { (shift @_) | (shift @_) };

    /^e$/       and unary { exp shift @_ };
    /^r$/       and unary { sqrt shift @_ };
    /^t$/       and unary { tan shift @_ };
    /^\[$/      and unary { floor shift @_ };
    /^\]$/      and unary { ceil shift @_ };
    /^\\$/      and unary { round shift @_ };

    # third row: asdfgh
    /^S$/       and unary { asin shift @_ };
    /^L$/       and unary { log10 shift @_ };
    /^\:$/      and push @stack, $stack[$#stack];
    /^a$/       and unary { abs shift @_ };
    /^s$/       and unary { sin shift @_ };
    /^l$/       and unary { log shift @_ };

    # fourth row: zxcvbnm
    /^C$/       and unary { acos shift @_ };
    /^M$/       and op { my ($l, $r) = @_;
                         return ($l > $r) ? $l : $r; };
    /^\<$/      and op { bool (shift @_) < (shift @_) };
    /^\>$/      and op { bool (shift @_) > (shift @_) };
    /^\?$/      and push @stack, rand;

    /^x$/       and return @stack;
    /^c$/       and unary { cos shift @_ };
    /^b$/       and unary { bool shift @_ };
    /^m$/       and op { my ($l, $r) = @_;
                         return ($l < $r) ? $l : $r; };
    /^\,$/      and push @stack, getc STDIN;
    /^\.$/      and print chr (0 + pop @stack);
    /^\/$/      and op { (shift @_) / (shift @_) };
  }
  return @stack;
}

sub shell() {
  print '>';
  while (<>) {
    eval { print join ',', eval_rpn $_; };
    print "\n";
    print "ERROR: $@\n" if $@;
    print ">";
  }
}

1;



=head1 NAME

QWERTY Reverse Polish Notation Calculator

=head1 SYNOPSIS

 use Math::QwertyRPN qw(eval_rpn);
 print eval_rpn "12 54 + 12 *"; # 792

A Reverse Polish notation calculator that has most opcodes of stack-based
assembly languages. It is generally Turing-complete (needs to be tested).

=head1 DESCRIPTION

The eval_rpn function evaluates an expression. The first argument is a string
containing the expression. The function returns the stack after the evaluation.
When an error occurred, C<die> will be called. However, you can trap errors
by using C<eval>, C<do> or other try-catch modules.

An RPN expression contain a sequence of operations, seperated by whitespaces
or line breaks, to know more about Reverse Polish notation, visit its
article on Wikipedia: L<http://en.wikipedia.org/wiki/Reverse_Polish_notation>.

C/C++ style line/block comments are supported:

 // Line comment
 /* block comment */

The operators that do not exist in other RPN calculators include GOTO labels,
I/O and comparsion operators. To create a GOTO label, write C<< >label_name >>,
where C<label_name> is the name of the label.

The GOTO statement is like C<< >label_name >>. However, GOTO is conditional,
it is only made if the value on the top of stack is not 0 (zero). Also,
GOTO has a side effect of popping the stack.

C<< 12 3 <label >> will end up with 12 (instead of 12 3) on the stack.

Variables can be created and read by using the C<=> and C<$> operator.
To get a variable, write C<$name>, where C<name> is the
name of the variable. The value of that variable will be pushed
to the top of the stack.
To set a variable, write C<=name>. The stack will be popped.

For example, C<< 50 =value >> will set variable C<value> to 50. The stack
will end up empty because the 50 was popped to assign the variable.

You can read-write variables in the based on the value on the top of the stack
by using variable-variables. To read a variable-variable, use C<$$>.
The expression C<50 $$> pushes the value of variable C<50> to the stack.
The top of the stack will be popped
and it becomes the name of the variable-variable.

To write a variable-variable, write C<=$>. The top of the stack will be popped
and it becomes the name of the variable-variable.

The following expression outputs 10:

 10 // stack:10
 5  // stack:10 5
 =$ //            variables:$5=10
 5  // stack:5    variables:$5=10
 $$ // stack:10   variables:$5=10

Arrays can be made by using variable-variables.

=head2 ASSEMBLER to RPN

A typical stack-based assembly code looks like this:

 push 180  ; stack is 180
 push 250  ; stack is 180 250
 add       ; stack is 430
 push 50   ; stack is 430 50
 jeq LABEL ; jump if equals

It will be translated to RPN like this:

 180 250 + 50 = <LABEL

The PUSH opcode will be converted to numbers, and ADD will be converted
to C<+> as expected. However, there is no JEQ, JLT, JGT and other
compare-and-goto opcodes in RPN, so you must compare manually and goto.
C<50 = > means to compare the top of the stack with 50, and pushes 0 or 1
based on the result of the comparsion.

=head2 QUICK REFERENCE

Here is a list of all operators by order of QWERTY keyboard.
I don't have time to document all of them, so I used assembler opcode names.

 +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
 | ~ not  | ! pop  | @ getn | # putn | $ swap | % mod  | ^ pow  | & and  | * mul  | ( dec  | ) inc  | _ neg  | + add  |
 | ` xor  | 1 ---- | 2 ---- | 3 ---- | 4 ---- | 5 ---- | 6 ---- | 7 ---- | 8 ---- | 9 ---- | 0 ---- | - sub  | = eq   |
 +--------+--------+--------+--------+--------+--------+--------+--------+-----------------+--------+--------+--------+
 | Q      | W      | E 10^x | R root | T atan | Y      | U      | I      | O      | P      | { shl  | } shr  | | or   |
 | q      | w      | e exp  | r sqrt | t tan  | y      | u      | i      | o      | p      | [ flr  | ] ceil | \ roun |
 +--------+--------+--------+--------+--------+--------+--------+--------+-----------------+--------+--------+--------+
 | A      | S asin | D      | F      | G      | H      | J      | K      | L log  | : dup  | "      |########|########|
 | a abs  | s sin  | d      | f      | g      | h      | j      | k      | l ln   | ;      | '      |########|########|
 +--------+--------+--------+--------+--------+--------+--------+--------+-----------------+--------+--------+--------+
 | Z      | X      | C acos | V      | B      | N      | M max  | < lt   | > gt   | ? rand |########|########|########|
 | z      | x exit | c cos  | v      | b bool | n      | m min  | , getc | . putc | / div  |########|########|########|
 +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+

Here is the list of other operations.

=over 5

=item 1

I<NUMBER>: Pushes a number to the stack.

=item 2

C<< < >>: Jumps to a label if the value on the top of the stack is not 0 (zero).
Example C<< <loop >>

=item 3

C<< > >>: Creates a label. Example: C<< >loop >>

=item 4

C<$>I<NAME>: Gets a variable. C<$var1> will push the value of C<var1>
on the top of the stack.

=item 5

C<=>I<NAME>: Sets a variable. C<12 =var1> will set C<var1> to 12, because
the item on the top of the stack is 12. Then 12 on the top of the stack will
be popped.

=item 6

C<$>I<NAME>: Gets a variable-variable.

=item 7: Sets a variable-variable.

=back

=head1 EXAMPLES

Ask user to input 2 numbers, and print its sum.

 @ @ + #

Digital root calculator

 1 @ 1 - + 9 %

Locals-based factorial

 @ =value               // value = input_number();
 1 =result              // result = 1;
 $value =i              // i = value;
 >loop                  // loop:
  $result $i * =result  //   result *= i;
  $i ( =i $i            //   i --;
 <loop                  // if ($i) { goto loop; }
 $result                // return result;

without comments:

 @ =value 1 =result $value =i >loop $result $i * =result $i ( =i $i <loop $result

=head1 TODO

 Stack-based factorial example
 Support of subroutines
 More detailed documentation

=cut
Personal tools