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

Make variables command only show user-defined variables #10

Open
wants to merge 2 commits into
base: master
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
117 changes: 62 additions & 55 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,9 +508,11 @@ 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.
* `variables`: Show all user-defined variables and their values.
* `variables_all`: Show **all** known variables and their values.

### Normal

Expand Down Expand Up @@ -597,6 +577,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 +633,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
54 changes: 32 additions & 22 deletions rpnpy/calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def __init__(self, autoPrint=False, splitLines=True, separator=None,
self._functions = {}
self._special = {}
self._variables = {}
self._userVariables = []

self.addSpecialCases()
addSpecialFunctions(self)
Expand Down Expand Up @@ -546,6 +547,7 @@ def _tryEvalExec(self, command, modifiers, count):
value = EngNumber(command)
except decimal.InvalidOperation:
try:
variables_before = set(self._variables.keys())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need the .keys() here. The dict is being iterated, so you get the keys as a result.

Also, could you use camelCase? I know it's a tiny/silly issue, but I'd prefer to keep the code consistent in that respect. Sorry!

exec(command, globals(), self._variables)
except BaseException as e:
err = str(e)
Expand All @@ -561,6 +563,13 @@ def _tryEvalExec(self, command, modifiers, count):
'whitespace in a command line?')
raise CalculatorError(*errors)
else:
# If we have new variables, add them to the user variables
# list
variables_after = set(self._variables.keys())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for .keys().

new_variables = variables_after.difference(variables_before)
for var in new_variables:
self._userVariables.append(var)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are the user variables a list and not a set? Does that mean that a variable could end up in self._userVariables twice? If you really want it to be a list you can replace these two lines with self._userVariables.extend(new_variables).


self.debug('exec(%r) worked.' % command)
return True, self.NO_VALUE
else:
Expand Down Expand Up @@ -661,26 +670,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 +697,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 Expand Up @@ -738,11 +747,12 @@ def defaultArgCount(x):
return self._findWithArgs(command, 'a string', predicate,
defaultArgCount, modifiers, count)

def setVariable(self, variable, value):
def setUserVariable(self, variable, value):
"""
Set the value of a variable.
Set the value of a user-defined variable.

@param variable: The C{str} variable name.
@param value: The value to give the variable.
"""
self._variables[variable] = value
self._userVariables.append(variable)
24 changes: 19 additions & 5 deletions rpnpy/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def stack(calc, modifiers, count):
stack.names = ('stack', 's', 'f')


def variables(calc, modifiers, count):
def variables_all(calc, modifiers, count):
"""Show all variables.

@param calc: A C{Calculator} instance.
Expand All @@ -67,8 +67,21 @@ def variables(calc, modifiers, count):
return calc.NO_VALUE


variables.names = ('variables',)
variables_all.names = ('variables_all',)

def variables_user(calc, modifiers, count):
"""Show user-defined variables.

@param calc: A C{Calculator} instance.
@param modifiers: A C{Modifiers} instance.
@param count: An C{int} count of the number of arguments to pass.
"""
for name, value in sorted(calc._variables.items()):
if name in calc._userVariables:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like self._userVariables should be a set.

calc.report('%s: %r' % (name, value))
return calc.NO_VALUE

variables_user.names = ('variables',)

def clear(calc, modifiers, count):
"""
Expand Down Expand Up @@ -318,7 +331,7 @@ def store(calc, modifiers, count):
else:
value = args

calc.setVariable(variable, value)
calc.setUserVariable(variable, value)
calc._finalize(None, nPop=len(args) + 1, modifiers=modifiers, noValue=True)
return calc.NO_VALUE

Expand Down Expand Up @@ -369,12 +382,13 @@ def map_(calc, modifiers, count):
store,
swap,
undo,
variables,
variables_all,
variables_user,
)


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

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