Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changed default stack order of args to functions and swapped the behaviour of :r #9

Open
wants to merge 1 commit into
base: store-command
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# 2.0.0 December 3, 2020

Changed default behaviour of the `:r` modifier following discussion with
@nfraprado at https://github.com/terrycojones/rpnpy/issues/6.

*Note that this is a backwards-incompatible change!*

# 1.0.31 December 2, 2020

Added special `store` command to save stack values into a variable,
Expand Down
114 changes: 60 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,43 +122,21 @@ the iterable, then call `map` (`reduce`, `filter`, etc).
Like this:

```sh
$ rpn.py 'str:! [6,7,8] map:i'
$ rpn.py [6,7,8] 'str:! map:i'
['6', '7', '8']
```

### Notes
1. Here the `:!` modifier causes the `str` function to be pushed onto the
stack instead of being run, and the `:i` modifier causes the result of
`map` to be iterated before being added to the stack.
1. When you run a function (like `map` or `apply`) that needs a callable
(or a function like `join` that needs a string) and you don't specify a
count (using `:3` for example), `rpn.py` will search the stack for a
suitable item and use the first one it finds. It doesn't really have a
choice in this case because it doesn't know how many arguments the
function (once it is found) will be applied to. This should usually
work just fine. You can always use an explicit count (like `:3`) if not.
Note that this situation does not apply if you use the `:r` modifier
(see below) because in that case the callable (or string, in the case of
`join`) will be expected to be on the top of the stack (and its
signature can then be examined to know how many arguments to pass it).
Here the `:!` modifier causes the `str` function to be pushed onto the
stack instead of being run, and the `:i` modifier causes the result of
`map` to be iterated before being added to the stack.

You might find it more natural to use `map` and friends the other way
around. I.e., first push the iterable, then push the function to be
applied, and then call `map`. In that case, you can use the `:r` modifier
to tell the calculator to reverse the order of the arguments passed to a
function. In the following, we push in the other order and then use
`map:ir` (the `i` is just to iterate the `map` result to produce a list).

```sh
$ rpn.py '[6,7,8] str:! map:ir'
['6', '7', '8']
```

Continuing on the map theme, you could instead simply reverse part of the
stack before running a function:
Continuing on the map theme, if you for some reason had the elements on the
stack in the wrong order, you could reverse part of the stack before
running a function:

```sh
$ rpn.py '[6,7,8] str:! reverse map:i'
$ rpn.py 'str:! [6,7,8] reverse map:i'
['6', '7', '8']
```

Expand Down Expand Up @@ -530,7 +508,8 @@ There are two kinds of commands: special and normal.
* `store`: Store the value on the top of the stack into a variable (whose name has
previously been pushed onto the stack). If given a numeric argument, that number
of items from the stack will be stored into the variable as a list.
* `swap`: Swap the top two stack elements.
* `swap`: Swap the top two stack elements (this is the same as calling
`reverse` with no arguments.
* `undo`: Undo the last stack-changing operation and variable settings.
* `variables`: Show all known variables and their values.

Expand Down Expand Up @@ -597,6 +576,27 @@ attrgetter Function(attrgetter (calls operator.attrgetter with 1 arg))
# 300+ lines deleted
```

## Variables

You can set variables and push them (or their values) onto the stack:

```sh
$ rpn.py --noSplit
--> a = 4
--> a
--> f
[4]
--> a:!
--> f
[4, Variable(a, current value: 4)]
--> a = 10
--> f
[4, Variable(a, current value: 10)]
--> 20
--> +:p
30
```

## Modifiers

Modifiers for a command are introduced with a colon, `:`. The modifiers are
Expand Down Expand Up @@ -632,50 +632,56 @@ The full list of modifiers is:
* `P`: Toggle automatic printing of all command results.
* `r`: When applied to a special command, reverses how the function (for
`map`, `apply`, `reduce`) or a string (for `join`) is looked for on the
stack. Normally the function or string argument to one of those special
functions has to be pushed onto the stack first. If `:r` is used, the
function or string can be given last (i.e., can be on the top of the
stack). In other contexts, causes all arguments given to a function to be
reversed (i.e., to use a stack order opposite to the normal).
stack. If `:r` is used, the function or string argument to one of those special
functions has to be pushed onto the stack first. In other contexts, causes
all arguments given to a function to be reversed (i.e., to use a stack order
opposite to the normal):

```sh
$ rpn.py '+:! 5 4 apply'
$ rpn.py '5 4 +:! apply'
9
$ rpn.py '5 4 +:! apply:r'
$ rpn.py '+:! 5 4 apply:r'
9
$ rpn.py '5 4 -'
1
$ rpn.py '5 4 -:r'
-1
```
See <a href="#reversing">below</a> for more detail.
* `s`: Turn on line splitting on whitespace. Note that this will only go into
effect from the _next_ command on.

If a count is given, it is either interpreted as a number of times to push
something onto the stack or the number of arguments to act on, depending on
context (um, sorry about that - should be clearer).

## Variables
<a id="reversing"></a>
### Reversing argument ordering

You can set variables and push them (or their values) onto the stack:
You might find it more natural to use `map` and friends the other way
around. I.e., first push the function, then push the iterable to be acted
on, and then call `map`. In that case, you can use the `:r` modifier to
tell the calculator to reverse the order of the arguments passed to a
function. In the following, we push in the other order and then use
`map:ir` (the `i` is just to iterate the `map` result to produce a list).

```sh
$ rpn.py --noSplit
--> a = 4
--> a
--> f
[4]
--> a:!
--> f
[4, Variable(a, current value: 4)]
--> a = 10
--> f
[4, Variable(a, current value: 10)]
--> 20
--> +:p
30
$ rpn.py 'str:! [6,7,8] map:ir'
['6', '7', '8']
```

Note that if you use the `:r` modifier to when running a function (like
`map` or `apply`) that needs a callable (or a function like `join` that
needs a string) and you don't specify a count (using `:3` for example),
`rpn.py` will search the stack for a suitable item and use the first one it
finds. It doesn't really have a choice in this case because it doesn't know
how many arguments the function (once it is found) will be applied to.
This should usually work just fine. You can always use an explicit count
(like `:3`) if not.

The argument ordering just described was the default in `rpn.py` prior to
version `2.0.0`.

## Undo

The effect of commands on the stack and variables can be undone with the
Expand Down
2 changes: 1 addition & 1 deletion rpnpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# will not be found by the version() function in ../setup.py
#
# Remember to update ../CHANGELOG.md describing what's new in each version.
__version__ = '1.0.31'
__version__ = '2.0.0'

# Keep Python linters quiet.
_ = Calculator
Expand Down
40 changes: 20 additions & 20 deletions rpnpy/calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,26 +661,6 @@ def _findWithArgs(self, command, description, predicate, defaultArgCount,
(command, stackLen, '' if stackLen == 1 else 's'))

if modifiers.reverse:
item = self.stack[-1]

if not predicate(item):
raise StackError('Top stack item (%r) is not %s' %
(item, description))

if count is None:
count = (stackLen - 1 if modifiers.all else
defaultArgCount(item))

nargsAvail = stackLen - 1
if nargsAvail < count:
raise StackError(
'Cannot run %r with %d argument%s '
'(stack has only %d item%s available)' %
(command, count, '' if count == 1 else 's',
nargsAvail, '' if nargsAvail == 1 else 's'))

args = self.stack[-(count + 1):-1]
else:
if count is None:
if modifiers.all:
item = self.stack[0]
Expand Down Expand Up @@ -708,6 +688,26 @@ def _findWithArgs(self, command, description, predicate, defaultArgCount,
item, description))

args = self.stack[-count:]
else:
item = self.stack[-1]

if not predicate(item):
raise StackError('Top stack item (%r) is not %s' %
(item, description))

if count is None:
count = (stackLen - 1 if modifiers.all else
defaultArgCount(item))

nargsAvail = stackLen - 1
if nargsAvail < count:
raise StackError(
'Cannot run %r with %d argument%s '
'(stack has only %d item%s available)' %
(command, count, '' if count == 1 else 's',
nargsAvail, '' if nargsAvail == 1 else 's'))

args = self.stack[-(count + 1):-1]

return item, self.convertStackArgs(args)

Expand Down
2 changes: 1 addition & 1 deletion rpnpy/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ def map_(calc, modifiers, count):


def addSpecialFunctions(calc):
"""Add functions defined above
"""Add functions defined above.

@param calc: A C{Calculator} instance.
"""
Expand Down
Loading