esc¶
esc (pronounced /esk/) is an Extensible Stack-based Calculator designed for efficiency and customizability. What does this mean?
esc is stack-based, operating using a Reverse Polish Notation (RPN)-like syntax. Rather than typing
2 + 2
and pressing an equals key, you enter the two numbers2
and2
onto the stack, then choose+
to add them. This can be slightly awkward at first, but it means no parentheses are necessary, and for most people it becomes faster and more elegant than the standard algebraic method with a small amount of practice. In addition, it is considerably easier to customize and program.esc is extensible. If you frequently need to multiply two numbers together, add five, and then divide the result by pi, you can add a function to the calculator to do this specific operation using a couple of lines of Python code. The extension features are simple enough to be accessible even to people who do not know Python or have little to no programming experience.
esc operations are arbitrary Python code, so if you want to get fancy, they can get arbitrarily complicated. You can even call APIs to perform calculations or get data!
esc is fast, simple, and terminal-based. All you need is a working terminal (at least 80×24) and your keyboard.
User Manual¶
The esc user manual describes how you install and use esc to perform calculations. If you’re looking for information on hacking esc to add custom functionality, you want the Developer Manual.
Installation¶
The easiest way to install esc is through pip:
pip install esc-calc
This will install a package esc
and an executable esc
script,
so you should be able to run esc like:
python -m esc
Or just:
esc
(This latter option may not work depending on your system configuration.)
You can also install esc from source. Find it on GitHub.
Terminology and Notation¶
Throughout the rest of this documentation, we will rely on some simple terminology and notation:
In esc’s interface, the stack window shows numbers from top to bottom. To avoid wasting large amounts of space in the documentation, we often describe the stack in terms of a Python list, with the leftmost element showing at the top, so a stack containing the numbers 1, 2, 3, and 4 from top to bottom would be described as
[1, 2, 3, 4]
.Certain elements on the stack need to be discussed frequently and have shorthand names in the documentation, interface, and code:
- bos (Bottom of Stack) –
the item listed at the bottom of the Stack window,
4
in the example above - sos (Second on Stack) –
the item listed second from the bottom of the Stack window,
3
in the example above - tos (Top of Stack)
– the item listed at the top of the Stack window,
1
in the example above
- bos (Bottom of Stack) –
the item listed at the bottom of the Stack window,
Concepts¶
When you start esc, you will see a status bar and four windows: Stack, History, Commands, and Registers. These windows match up neatly with the important concepts in esc.

A typical esc window at startup.
Stack¶
The stack contains a series of numbers you are currently working with. Almost all commands follow the pattern of:
- read numbers from the stack;
- do something with them;
- return other numbers to the stack.
As long as no other operation is in progress,
your cursor will be positioned in the Stack window.
To enter a new number (this is called pushing it onto the stack),
simply start typing the number.
The indicator at the left of the status bar will change to [i]
to indicate that you’re inserting a number,
and the numbers will appear in the stack window as you type.
When you’re done typing the number, press Enter or Space;
your cursor will then move to the next line of the stack.
If you make a typo, correct it with Backspace.
(If you’ve already pressed Enter,
an undo will let you edit the number again.)

Entering several numbers onto the stack.
You can enter as many numbers as you like this way, at least until the stack fills up. (In esc, the stack size is based on the number of lines allocated for the stack on the screen, which is ordinarily 12. 12 entries should be more than enough for all but the craziest calculations; many RPN-based HP calculators only have four, and that is normally enough.)
Enter negative signs with _ (underscore)
and scientific notation with e
(e.g., 3.66e11
is 3.66 × 10^12 or 366,000,000,000).
Tip
You need only press Enter or Space
between consecutively typed numbers.
If you type a number and then wish to
perform an operation,
you can simply press the key for the operator.
For instance, typing 2 2+
is equivalent to typing 2 2 +
.
Commands¶
Arithmetic operations¶
Eventually you’ll probably get tired of typing in numbers and want to actually perform some operation on the contents of the stack. In the Commands window, you will see a list of operations that you can perform. At the very top are listed the typical arithmetic operations. To perform one of these operations, simply press the associated key. For instance, to perform addition, you press +.
When you perform an operation,
it removes a certain number of entries from the bottom of the stack
(this is called popping them off the stack).
It then uses those values to calculate the result
and pushes the result back onto the bottom of the stack.
For example, if your stack is [1, 2, 3]
and you press the + key to add two numbers,
the 1
is untouched,
while the 2
and 3
are removed from the stack
and the answer, 5
, is pushed on,
so that the stack now has two entries, 1 and 5.
Note
Most operations pop one or two items from the stack and push one answer. However, this is not a requirement; an operator could pop four values and push two back.
You can see how many and what values an operation pops and pushes in its help page.
Stack operations¶
In addition to the arithmetic operations, some stack operations are provided. These don’t calculate anything but allow you to manipulate the contents of the stack.
-
esc.functions.
duplicate
(bos)[source]¶ Duplicate bos into a new stack entry. Useful if you want to hang onto the value for another calculation later.
Other commands¶
In addition to the arithmetic and stack manipulation commands described above, esc defines several special commands.
builtin_stubs.py - stub classes for built-in commands
-
class
esc.builtin_stubs.
StoreRegister
[source]¶ Copy the bottommost value on the stack into a register. Registers store values under a single-letter name until you need them again.
See Registers for more information on registers.
-
class
esc.builtin_stubs.
RetrieveRegister
[source]¶ Copy the value of a register you’ve previously stored to the bottom of the stack. Registers store values under a single-letter name until you need them again.
See Registers for more information on registers.
-
class
esc.builtin_stubs.
DeleteRegister
[source]¶ Remove an existing register from your registers list and destroy its value.
See Registers for more information on registers.
-
class
esc.builtin_stubs.
Undo
[source]¶ Undo the last change made to your stack. Registers are unaffected. (This is a feature, not a bug: a common esc workflow is to reach an answer, then realize you need to go back and do something else with those same numbers. Registers allow you to hold onto your answer while you do so.)
See History for more information on calculation history.
Custom commands¶
In addition to all the commands described above, you may see some other commands in your list at times. These are added by esc plugins.
Getting help on commands¶
esc has a built-in help system you can use to discover what a command does, including the exact effect it would have on your current stack if you ran it. Simply press F1, then the key associated with the command. If you choose a menu, you’ll get a description of the menu, but you can also choose an item from the menu to get specific help on that item.
History¶
esc maintains a complete history of all the numbers you push onto the stack and operations you perform. The operations you execute and brief descriptions of their results are displayed in the History window in the middle of the screen.
If you perform an operation and then want to back up,
simply choose Undo
.
To undo an undo, use Redo
.
Calculation history you can step through is so useful it’s amazing how few calculators offer it.
Registers¶
In addition to placing numbers on the stack, sometimes you might want to keep track of numbers in a slightly more permanent way. In this case, you can store the number to a register.
- To
store to a register
, press >, then type an upper- or lowercase letter to name the register. The bottom item on the stack is copied into the Registers window. - To
retrieve the value of a register
, press <, then type the letter corresponding to the register whose value you want to retrieve. The value is copied into a new item at the bottom of a stack. - To
delete a register
, press X, then type the letter of the register you want to delete. It is removed from the Registers window and its value is lost.
Note
Registers do not participate in the undo/redo history. This is a feature, not a bug: a common esc workflow is to perform some calculation, then realize you needed those numbers again for something. You can store your answer to a register, then undo as needed to get those numbers back.
Exercises¶
To get the hang of using esc and Reverse Polish Notation, here are a few simple exercises you can work through. Click the footnote link to show the answer.
Questions¶
Calculate the answers to the following algebraic expressions:
- \(2 + 5\) [1]
- \(17 / 5\) [2]
- \(\frac{30}{3 \cdot 5}\) [3]
- \(60(2 \cdot (17 + 8))(5 \cdot 3)\) [4]
- \(\frac{12 + 6}{18 \mod 3}\) [5]
- \(\frac{1.52 \times 10^3}{12^{-2}}\) [6]
Enter the numbers listed in the left column into the stack, then manipulate it using arithmetic or stack operations or registers to match the right column. You cannot enter any new numbers by typing them.
Answer | Initial stack | Final stack |
---|---|---|
[7] | 5
3
|
3
5
|
[8] | 1
2
3
|
(empty) |
[9] | 1
2
3
|
3
1
2
|
[10] | 1
2
8
|
11
16
8
|
Answers¶
There are an infinite number of possible entry sequences that would work for every question. The sequence or sequences shown here are just some sensible choices; obviously, the most important thing is that you have the right answer.
In many cases, you’ll have a choice between entering the numbers in strict order as they come in the expression or working from the inner parentheses out, or some combination thereof. This is largely a matter of taste, although in extremely large calculations you could run out of stack space if you type the numbers in order. This is not a serious threat in esc since the stack holds at least 12 numbers, but is more of a concern on hardware RPN calculators, which may hold as few as 3.
Note
Keystroke sequences are rendered with spaces between operations for readability here. Many of these spaces are not necessary when typing into esc or will even give the error “No number to finish adding”.
[1] | 7 (2 5 + ) |
[2] | 3.4
(17 5 / ) |
[3] | 2
(30 3 5 * /
or 3 5 * 30 x / ) |
[4] | 244800
(60 2 17 8 + * * 5 3 * *
or 17 8 + 2 * 60 * 5 3 * * ) |
[5] | Trick question, this was a division by zero error!
(12 6 + 18 3 % / ) |
[6] | 218880
(1.52e3 12 _2 ^ * ) |
[7] | x |
[8] | c |
[9] | r r ,
or more playfully x y p x ^V , where ^V means to paste into esc
using whatever method your terminal emulator requires! |
[10] | >x + + <x d + <x |
Plugins¶
esc bundles only a small number of built-in operations so you can decide what operations will be useful to you and avoid staring all day at operations you never use. Any remaining operations you need can be added via plugins.
Plugin location¶
Plugins are Python modules (.py
files)
stored in the plugins
subdirectory of your esc configuration directory.
By default your esc configuration directory is ~/.esc
,
where ~
represents your home or user directory.
If this directory doesn’t exist but $XDG_CONFIG_HOME/esc
(or ~/.config/esc
if that environment variable is unset)
does, that is used instead.
(See here
for why you might want to use the XDG folders specification.)
Installing plugins¶
To install a plugin,
you simply copy its .py
file into your plugins directory
(see Plugin location).
You can create the .esc
and/or plugins
directories
if they don’t exist.
The next time you start esc, the plugins will be loaded.
Warning
esc plugins can execute arbitrary Python code on your computer, which could include malicious code, so you should not install esc plugins from sources that you do not trust.
Tip
Plugins are loaded
and their operations placed in the Commands window
in alphabetical order by their filename.
To control the order, you can prefix their filenames with numbers,
e.g., 01_trig.py
, 02_log.py
.
Finding plugins¶
You can write your own plugins (see the Developer Manual) or get plugins from someone else. Some plugins providing common features like trig and log functions are available for download in the esc repository under esc-plugins.
Developer Manual¶
The esc developer manual documents the API and internals of esc and describes how you add custom functionality and change the behavior of esc. If you’re looking for information on installing or running esc or performing calculations, you want the User Manual.
If you haven’t built an esc plugin before, start with the Operations section, which walks you through creating a plugin that implements a simple operation.
Operations¶
The most common thing to do in a plugin is to add a new operation. In the walkthrough presented on this page, we’ll implement a new operation on the main menu. Our operation will calculate a proportion, like:
We’ll pass the parameters in order,
so when the stack reads [1, 2, 3]
,
we will obtain 6.
Creating a plugin¶
Create a new file in your esc plugins directory and paste in the following template:
"""
{name}.py - {description}
Copyright (c) 2019 {your name}.
{additional details}
"""
from esc.commands import Operation, main_menu
Change the sections in brackets to appropriate values for your plugins. Of course, the docstring is just a suggestion. Reload esc and ensure no errors occur. If you do get an exception, you probably made a syntax error; fix the file as necessary to resolve it.
Writing a function¶
Operations are written as Python functions
decorated with @Operation
.
We’ll start with the function and then look at the decorator.
How should we write this function? Let’s generalize the example of a proportion calculation above, where d is the number we’re solving for:
We’ll go ahead and use these letters for our variable names; they’ll serve as well as anything else since this is a very general operation that could be used for just about anything. Translating the algebraic notation above into Python:
def proportion(a, b, c):
return b * c / a
We can specify any number of parameters we want here and name them anything we want. When the user runs our operation, esc will check the function’s parameter list to see how many parameters it has, slice that many items off the bottom of the stack, and bind them to the parameters in order. If there aren’t enough items available, the user will get an error message telling them so. When we’re done, we can return a single value or a tuple of values, and those values will replace the parameters that we received on the stack.
This is an oversimplification,
as there are additional options that can change much of this behavior;
we’ll get to those in the discussion
of the @Operation
decorator.
Note
esc uses the Decimal library to implement decimal arithmetic similar to that of many handheld calculators. All function arguments are thus Decimal objects. Most operations on Decimals yield other Decimals, so you probably will not even notice if you’re doing normal arithmetic on your arguments. If you ever get confused, check out the linked library documentation.
Return values from functions may be Decimal objects
or any type that can be converted to a Decimal
(string, integer, or float).
Beware of returning floats except for numbers that are already irrational,
as all the precision will be kept
when converting back to the internal Decimal representation,
even the rounding error inherent in binary floating-point values,
which may result in silly values like 1.000000000083
appearing on the stack.
If your function uses non-integer literals anywhere,
it’s a good idea to head this issue off
by using the Decimal constructor to create them,
like from decimal import Decimal; x = Decimal("2.54")
.
Creating an @Operation¶
If you save the file and start esc, you won’t get any errors,
but you won’t have any new operations either.
In order to get an operation to show up,
we need to add the @Operation
decorator described earlier.
That will make our code look like this:
@Operation(key='P', menu=main_menu, push=1,
description="proportion from abc",
log_as="{0}:{1} :: {2}:[{3}]")
def proportion(a, b, c):
"""
Quickly calculate a proportion.
If the bottom three items on the stack are A, B, and C,
then calculate D where A : B = C : D.
"""
return b * c / a
Most of this is probably fairly self-explanatory, but a couple of points are worth noting.
log_as
is a format string whose positional placeholders will be replaced with a chain of the arguments to the function and the return values from the function. The formatted version will be used in the history window and help system.- The function’s docstring is used as the description in the help system.
@Operation
can get more complicated,
so without further ado here are the dirty details:
-
esc.commands.
Operation
(key, menu, push, description=None, retain=False, log_as=None, simulate=True)[source]¶ Decorator to register a function on a menu and make it available for use as an esc operation.
Parameters: - key – The key on the keyboard to press to trigger this operation on the menu.
- menu – The
Menu
to place this operation on. The simplest choice ismain_menu
, which you can import from :mod:esc.commands
. - push – The number of items the decorated function
will return to the stack on success.
0
means nothing is ever returned;-1
means a variable number of things are returned. - description – A very brief description of the operation this function implements,
to be displayed next to it on the menu.
If this is
None
(the default), the operation is “anonymous” and will be displayed at the top of the menu with just its key. - retain – If
True
, the items bound to this function’s arguments will remain on the stack on successful execution. The default isFalse
(meaning the function’s return value replaces whatever was there before – the usual behavior of an RPN calculator). - log_as –
A specification describing what appears in the History window after executing this function. It may be
None
(the default),UNOP
orBINOP
, a .format() string, or a callable.- If it is
None
, the description is used. - If it is the module constant
esc.commands.UNOP
oresc.commands.BINOP
, the log string is a default suitable for many unary or binary operations: forUNOP
it isdescription argument = return
and forBINOP
it isargument key argument = return
.Note
If the function being decorated does not take one or two arguments, respectively, using
UNOP
orBINOP
will raise aProgrammingError
. - If it is a format string, positional placeholders are replaced
with the parameters to the function in sequence,
then the return values.
Thus, a function with two arguments
bos
andsos
returning a tuple of two values replaces{0}
withbos
,{1}
withsos
, and{2}
and{3}
with the two return values. - If it is a callable, the parameters will be examined and bound
by name to the following (none of these parameters are required,
but arguments other than these will raise a
ProgrammingError
).args: a list of the arguments the function requested retval: a list of values the function returned registry: the current Registry
instanceThe function should return an appropriate string.
- If it is
- simulate – If
True
(the default), function execution will be simulated when the user looks at the help page for the function, so they can see what would happen to the stack if they actually chose the function. You should disable this option if your function is extremely slow or has side effects (e.g., changing the system clipboard, editing registers).
In addition to placing the function on the menu, the function is wrapped with the following magic.
Function parameters are bound according to the following rules:
- Most parameters are bound
to a slice of values at the bottom of the stack, by position.
If the function has one parameter,
it receives bos;
if the function has two parameters,
the first receives sos and the second bos;
and so on.
The parameters can have any names (see exceptions below).
Using
bos
andsos
is conventional for general operations, but if the operation is implementing some kind of formula, it may be more useful to name the parameters for their meaning in the formula. - By default, passed parameters are of type Decimal.
If the parameter name ends with
_str
, it instead receives a string representation (this is exactly what shows up in the calculator window, so it’s helpful when doing something display-oriented like copying to the clipboard). If the parameter name ends with_stackitem
, it receives the completeStackItem
, containing both of those representations and a few other things besides. - A varargs parameter, like
*args
, receives the entire contents of the stack as a tuple. This is invalid with any other parameters exceptregistry
. The_str
and_stackitem
suffixes still work. Again, it can have any name;*stack
is conventional for esc operations. - The special parameter name
registry
receives aRegistry
instance containing the current state of all registers. Using this parameter is generally discouraged; see Registry for details. - The special parameter name
testing
receives a boolean describing whether the current execution is a test (seeesc.functest.TestCase()
). This can be useful if your function has side effects that you don’t want to execute during testing, but you’d still like to test the rest of the function.
- Most parameters are bound
to a slice of values at the bottom of the stack, by position.
If the function has one parameter,
it receives bos;
if the function has two parameters,
the first receives sos and the second bos;
and so on.
The parameters can have any names (see exceptions below).
Using
The function has a callable attached to it as an attribute, called
ensure
, which can be used to test the function at startup to ensure the function never stops calculating the correct answers due to updates or other issues:def add(sos, bos): return sos + bos add.ensure(before=[1, 2, 3], after=[1, 5])
See
TestCase
for further information on this testing feature.
Writing tests¶
You probably don’t want a calculator that returns the wrong results,
so it’s important to test your custom function!
You could simply load esc and try it out,
and that’s a good idea regardless,
but esc also offers built-in tests.
These tests run automatically every time esc starts up;
if they ever fail, esc will raise
a ProgrammingError
and refuse to load.
This way, even if a new version of esc
makes breaking changes you don’t know about
or you accidentally modify and break your function,
you can be confident that esc won’t return incorrect results
(at least to the extent of your test coverage).
We can define automatic tests using the ensure
attribute
which the @Operation
decorator
adds to our function.
Let’s define a test that tests
the example we discussed at the start of this page:
proportion.ensure(before=[1, 2, 3], after=[6])
Let’s test an error condition too.
What happens if calculating our proportion requires a divide by zero?
Without special-casing that in our function,
we would hope it informs the user that she can’t divide by zero,
which esc does by raising a ZeroDivisionError
which is caught by the interface.
proportion.ensure(before=[0, 2, 3], raises=ZeroDivisionError)
And it’s that easy.
If you don’t get a ProgrammingError
after restarting esc, your tests pass.
Here’s the full scoop on defining tests:
-
class
esc.functest.
TestCase
(before, after=None, raises=None, close=False)[source]¶ Test case defined with the
.ensure()
attribute of functions decorated with@Operation
.Parameters: - before – Required. A list of Decimals or values that can be coerced to Decimals. These values will be pushed onto a test stack that the operation will consume values from.
- after – Optional (either this or raises is required). A list of Decimals or values that can be coerced to Decimals. After the function is executed and changes the stack, the stack is compared to this list, and the test passes if they are identical.
- raises – Optional (either this or after is required).
An exception type you expect the function to raise.
This checks both the top-level exception type
and any nested exceptions
(since esc wraps many types of exceptions in
FunctionExecutionError
s). The test passes if executing the function (including all the machinery inside esc surrounding your actual function’s execution, like pulling values off the stack) raises an exception of this type. - close – If True, instead of doing an exact Decimal comparison,
math.isclose()
is used to perform a floating-point comparison. This is useful if dealing with irrational numbers or other sources of rounding error which may make it difficult to define the exact result in your test.
Test cases are executed every time esc starts (this is not a performance issue in practice unless you have a lot of plugins). If a test ever fails, a
ProgrammingError
is raised. Preventing the whole program from starting may sound extreme, but wrong calculations are pretty bad news!Test cases are not inherently associated with an operation due to scope issues: Since the
EscOperation
itself is not returned to the functions file, only a decorated function, the function author can’t access theEscOperation
. Instead, they are associated with the function itself, and when the test() method is called on anEscOperation
, it retrieves the test cases from the function and passes itself into the execute() method of each test case.
Putting it all together¶
Launch esc again. If you’ve made any mistakes, esc will hopefully catch them for you here and describe why you have an error or your test failed. Type in three values that can be used to calculate a proportion, choose the function from the menu, and you should be set!
Handling errors¶
esc handles many kinds of errors that could occur in your functions for you:
- If there aren’t enough items on the stack to bind to all your arguments, your function won’t even be called and the user will be told there aren’t enough items on the stack.
- If your function raises a
ValueError
, the user will be informed a domain error has occurred (many math functions raise this exception in this case). - If your function raises a
ZeroDivisionError
, the user will be informed he cannot divide by zero. - If your function raises a Decimal
InvalidOperation
, the user will be informed the result is undefined. (The Decimal library in esc is configured so thatInfinity
is a valid result which occurs when extremely large numbers are put together such that the available precision is exceeded, but any result that would returnNaN
like0 / 0
raisesInvalidOperation
.)
However, at times this will not be sufficient.
One of the most common cases occurs
when you need to work with the entire stack.
In this case, you need to check yourself to see if there are sufficient items,
as esc doesn’t know how many you need.
To do so, simply check the length of your *args
tuple
and raise an InsufficientItemsError
if it’s too short:
def my_operation(*stack):
if len(stack) < 2:
raise InsufficientItemsError(number_required=2)
# do stuff
-
class
esc.oops.
InsufficientItemsError
(number_required, msg=None)[source]¶ Raised directly by operations functions that request the entire stack to indicate not enough items are on the stack to finish their work, or by the menu logic when an operation requests more parameters than items on the stack.
Functions may use the simplified form of the exception, providing an int describing the number of items that should have been on the stack for the
number_required
constructor parameter. esc will then reraise the exception with a more useful message; a fallback message is provided in case this doesn’t happen for some reason.
Another case arises when your function encounters some arbitrary condition
that prevents it from continuing.
As a silly example, perhaps the result of your formula is undefined
if the sum of its input values is 6.
Raising a FunctionExecutionError
with a message argument will cause the function’s execution to stop
and the message to be printed to the status bar.
(The stack will remain unchanged.)
As noted below, the message should be concise so it fits in the status bar
– it will be truncated if it doesn’t fit.
def my_operation(sos, bos):
if sos + bos == 6:
raise FunctionExecutionError("I don't like the number 6.")
# do stuff
-
class
esc.oops.
FunctionExecutionError
[source]¶ A broad exception type that occurs when the code within an operation function couldn’t be executed successfully for some reason. Examples include:
- a number is in the middle of being entered and isn’t a valid number
- a function performed an undefined operation like dividing by zero
- there are too many or too few items on the stack
- a function directly raises this error due to invalid input or inability to complete its task for some other reason
FunctionExecutionError
s normally result in the__str__
of the exception being printed to the status bar, so exception messages should be concise.A
FunctionExecutionError
may be raised directly, or one of its subclasses may be used.
Warning
If you do something complicated in your function
that could result in an exception other than the types listed above,
be aware that if you let an exception of another type
bubble up from your function, esc will crash and show the traceback.
This is generally reasonable behavior if you don’t expect the error,
since it makes it easy to spot and fix the problem,
but if the error is an expected possibility you’ll probably want to catch it
and give the user a helpful error message describing the problem
by raising a FunctionExecutionError
.
Constants¶
Adding new constants is easy as pi
with the Constant
constructor:
-
esc.commands.
Constant
(value, key, description, menu)[source]¶ Register a new constant. Constants are just exceedingly boring operations that pop no values and push a constant value, so this is merely syntactic sugar.
Parameters: - value – The value of the constant, as a Decimal or a value that can be converted to one.
- key – The key to press to select the constant from the menu.
- description – A brief description to show next to the key.
- menu – A
Menu
to place this function on.
The one trick here is that if you want to add your constant to the constants menu, you may not have access to the constants menu. In this case, use the trick for adding to existing menus:
from esc.commands import Constant, main_menu
constants_menu = main_menu.child('i')
Constant(299792458, 'c', 'speed of light (m/s)', constants_menu)
Since the constants menu is built in to esc,
i
will always be the constants menu
and there is no need to perform the other checks
described in the linked section.
Modes¶
Modes are global state that can be used to allow esc operations to work in different ways. For instance, the trig operations in the trig plugin distributed with esc use a degrees/radians mode. Modes tend to be confusing to both users and programmers, so it’s best to avoid them when possible, but sometimes it’s difficult to do without them (do you really want to create and select a whole separate set of trig operations depending on whether you’re calculating in degrees or radians?).
Working with modes¶
Modes are created using the Mode
and ModeChange
constructors:
-
esc.commands.
Mode
(name, default_value, allowable_values=None)[source]¶ Register a new mode.
Parameters: - name – The name of the mode. This is used to refer to it in code.
If a mode with this name already exists,
a
ProgrammingError
will be raised. - default_value – The value the mode starts at.
- allowable_values – An optional sequence of possible values for the mode.
If defined, if code ever tries to set a different value,
a
ProgrammingError
will be raised.
- name – The name of the mode. This is used to refer to it in code.
If a mode with this name already exists,
a
-
esc.commands.
ModeChange
(key, description, menu, mode_name, to_value)[source]¶ Create a new mode change operation the user can select from a menu. Syntactic sugar for registering an operation.
Parameters: - key – The key to press to select the constant from the menu.
- description – A brief description to show next to the key.
- menu – A
Menu
to place this operation on. - mode_name – The name of the mode, registered with
Mode()
, to set. - to_value – The value the mode will be set to when this operation is selected.
Aside from defining ModeChange
operations,
you can view and edit the values of modes
using the modes
module:
modes.py - Manage calculator state/modes
-
class
esc.modes.
Mode
(name, value, allowable_values)[source]¶ esc modes implement basic calculator state like a degrees/radians switch. In esc, they are created and used by menus with families of related operations, where they can also be displayed. They have a name, a current value, and optionally a set of allowable values; if something ever causes the value to be set to a non-allowable value, a
ProgrammingError
will be raised, hopefully identifying the issue before it leads to wrong results.Modes are usually created by the
esc.commands.Mode()
factory function, not by calling this constructor directly.
-
esc.modes.
get
(name)[source]¶ Retrieve the value of a mode with a given name. Return None if no mode by that name has been registered.
-
esc.modes.
register
(name, default_value, allowable_values=None)[source]¶ Create a new mode. If the mode already exists, a
ProgrammingError
is raised.Modes should be registered by the
esc.commands.Mode()
factory function, not by calling this function directly.
-
esc.modes.
set
(name, val)[source]¶ Set a mode to a new value. If the mode doesn’t exist, a
KeyError
will be raised. If the value is invalid for the mode, aProgrammingError
will be raised.
You’ll probably want to display the value of your mode somewhere
– as confusing as modes can be already,
they’re even worse when they’re invisible!
Typically, this is done by supplying a mode_display
callable
to the Menu
that contains the associated operations.
This callable takes no arguments
and calls modes.get
to determine the display value:
def my_mode_display_1():
# Assumes the values of the mode are strings.
# If they're something else, you need to map them to strings here.
return modes.get('my_mode')
Example¶
Here’s a complete silly example:
from esc.commands import Operation, Mode, ModeChange, Menu, main_menu, BINOP
from esc import modes
def opposite_mode():
return "[opposite day]" if modes.get('opposite_day') else ""
bool_menu = Menu('b', 'boolean functions', parent=main_menu, doc="blah",
mode_display=opposite_mode)
Mode('opposite_day', False, (True, False))
ModeChange("o", "enable opposite day", menu=bool_menu,
mode_name='opposite_day', to_value=True)
ModeChange("O", "disable opposite day", menu=bool_menu,
mode_name='opposite_day', to_value=False)
@Operation('a', menu=bool_menu, push=1,
description="AND sos and bos",
log_as=BINOP)
def and_(x, y):
result = x and y
return int((not result) if modes.get('opposite_day') else result)
# ...insert other boolean functions here
Debugging¶
If you do something wrong in a plugin,
a ProgrammingError
will be raised:
-
class
esc.oops.
ProgrammingError
[source]¶ Indicates an error caused by incorrectly written or defined esc plugins. This includes modes, menus, operations, and so on. It does not include runtime errors within operation functions themselves; these are
FunctionExecutionError
s.
The traceback will usually contain enough information to identify the problem.
If you have difficulty with the operation function itself
(rather than interacting with the esc framework),
you may wish to pull the function out into a test script
or use the logging
standard library module,
as it can be quite challenging to debug a function
within the context of a curses app
without a separate connected debugger
(the terminal is taken over, precluding the use of print()
statements
or a direct drop into pdb
).
One quick-and-dirty hack is to raise an Exception
with a value you want to check as the message;
this will only let you check one value per run,
but it can still be useful.
Class Reference¶
This page describes some miscellaneous classes you may encounter while developing esc plugins and want to know more about. This is not an exhaustive reference to all of esc’s classes; for details on classes that plugin developers do not come into contact with, you’ll want to dive into the source.
EscCommands¶
Anything that shows up on the Commands menu
has an associated instance of EscCommand
.
EscCommand
is subclassed as follows:

EscCommand subclasses¶
-
class
esc.commands.
EscCommand
(key, description)[source]¶ Base class for some esc functionality or operation the user can activate.
When the user activates this item,
execute()
is called. Execution takes any action associated with the item, throwing an exception if something didn’t work right. It then returns the menu that the interface should return to. A return value of None returns to the main menu.-
children
= None¶ Mapping from keys to
EscCommand
s on the current menu, if this is a menu. Add to this usingregister_child()
, not directly.
-
description
= None¶ How this item is described on its parent menu.
-
execute
(access_key, ss, registry)[source]¶ Execute this EscCommand. For operations or builtins, this involves the class doing its own work; for menus, this returns the child defined by access_key.
Returns: An instance of EscCommand
representing the menu the UI should now return to, orNone
to indicate the main menu.
-
help_title
¶ The title this command should show in the help system. This is the access key and description if a description is defined; otherwise it is just the access key.
-
key
= None¶ The key used to activate this item on its parent menu.
-
parent
= None¶ An
EscCommand
(hopefully a menu) this item is contained in.
-
register_child
(child)[source]¶ Register a new child
EscCommand
of this menu (either a menu or an operation). This operation doesn’t make sense forEscOperation
instances; the caller should avoid doing this.
-
signature_info
¶ An iterable of strings to display under the “Signature” section in help.
-
simulated_result
(ss, registry)[source]¶ Execute this command against the given stack state and registry, but instead of actually changing the state, return a string describing the result.
May return
None
if theEscCommand
does not change the stack state (e.g., a menu).
-
test
()[source]¶ Execute any self-tests associated with this
EscCommand
. If a test fails, raise aProgrammingError
.
-
-
class
esc.commands.
EscMenu
(key, description, doc, mode_display=None)[source]¶ Bases:
esc.commands.EscCommand
A type of EscCommand that serves as a container for other menus and operations. Executing it activates a child item.
-
anonymous_children
¶ Iterable of children without a description.
-
child
(access_key)[source]¶ Return the child defined by access_key. Raises
NotInMenuError
if it doesn’t exist.
-
execute
(access_key, ss, registry)[source]¶ Look up the child described by access_key and execute it. If said child is a menu, return it (so the user can choose an item from that menu). Otherwise, execute the child immediately.
Parameters: - access_key – A menu access key indicating which child to execute.
- ss – The current stack state, passed through to a child operation.
- registry – The current registry, passed through to a child operation.
Returns: The
EscMenu
to display next, orNone
to return to the main menu. This will be a child menu, if one was selected, or None if an operation runs.Raises: FunctionExecutionError
or a subclass, if a child operation was selected but does not complete successfully.If the user chose the special quit command, return to the previous menu, or raise
SystemExit
if this is the main menu.
This is the main menu if it has no parent.
-
mode_display
= None¶ An optional callable whose return value will be shown under the menu title.
-
named_children
¶ Iterable of children with a description.
-
signature_info
¶ Constant string that describes the menu as a menu.
-
-
class
esc.commands.
EscOperation
(key, func, pop, push, description, menu, retain=False, log_as=None, simulate=True)[source]¶ Bases:
esc.commands.EscCommand
-
execute
(access_key, ss, registry)[source]¶ Execute the esc operation wrapped by this instance on the given stack state and registry.
Parameters: - access_key – Not used by this subclass.
- ss – The current stack state, passed through to a child operation.
- registry – The current registry, passed through to a child operation.
Returns: A constant
None
, indicating that we go back to the main menu.Raises: FunctionExecutionError
or a subclass, if the operation cannot be completed successfully.
-
function
= None¶ The function, decorated with
@Operation
, that defines the logic of this operation.
-
log_as
= None¶ A description of how to log this function’s execution (see the docs for
@Operation
for details on allowable values).
-
pop
= None¶ The number of items the function gets from the bottom of the stack.
-1
indicates the entire stack is popped.
-
push
= None¶ The number of items the function returns to the stack.
-1
indicates a variable number of items will be returned.
-
retain
= None¶ If true, items pulled from the stack before execution won’t be removed.
-
signature_info
¶ A description of the function’s signature as a tuple of strings (one per line to display in the help system), based on the
pop
andpush
values.
-
simulate_allowed
= None¶ Whether this function should be run when a simulation is requested for help purposes. Turn off if the function is slow or has side effects.
-
simulated_result
(ss, registry)[source]¶ Execute the operation on the provided StackState, but don’t actually change the state – instead, provide a description of what would happen.
-
test
()[source]¶ If the function on this
EscOperation
has associatedTestCase
s defined in its tests attribute, execute those tests.
-
-
class
esc.commands.
EscBuiltin
[source]¶ Bases:
esc.commands.EscCommand
Mock class for built-in commands. Built-in
EscCommand
s do not actually get run and do anything – they are special-cased because they need access to internals normal commands cannot access. However, it’s still useful to have classes for them as stand-ins for things like retrieving help.Unlike the other
EscCommand
s, eachEscBuiltin
has its own subclass rather than its own instance, as they each need special behaviors. The subclasses are defined in thebuiltin_stubs
module.Subclasses should override the docstring and the
simulated_result()
method.Subclasses should also define
key
anddescription
as class variables. They’ll be shadowed by instance variables once we instantiate the class, but the values will be the same. That sounds dumb, but it makes sense for all other classes in the hierarchy and doesn’t hurt us here. We don’t want to define them in the__init__
of each subclass because then we have to instantiate every class to match on them by key (see the reflective search inesc.helpme
).-
signature_info
¶ Constant string that describes the built-in as a built-in.
-
Loading EscCommands¶
EscCommand
s are loaded
by the function_loader
:
function_loader.py - load esc functions from builtins and plugins onto menus
-
esc.function_loader.
load_all
()[source]¶ Load built-in and user functions files. This will execute the constructors in the functions files, which will (if these files are written correctly) ultimately register the functions onto main_menu. This method needs to be called only once at application startup.
The function loader imports the built-in functions file
and any function files in your user config directory.
Importing a function file causes
calls to the constructors in EscCommand
to be run (Operation()
, Menu()
, Constant()
,
Mode()
, and ModeChange()
),
and these constructors in turn create EscCommand
instances
which are added to the children
attribute
of the main menu or a submenu of the main menu.
StackItems¶
StackItem
s are used to represent each number entered onto the stack.
Typically, you can request a Decimal or string representation of the stack item
in your operation functions
(see the documentation on parameter binding
in Operation
for details).
If you need both in one function, you may want to work with the full object:
-
class
esc.stack.
StackItem
(firstchar=None, decval=None)[source]¶ An item placed on esc’s stack. At its root, this is a number, but it gets more complicated than that!
For one, we need a numeric value for calculations as well as a string value to display on the screen. The method
finish_entry()
updates the numeric representation from the string representation. We could dynamically compute the string representation with reasonable performance, but see the next paragraph for why this isn’t helpful.For another, a stack item may be incomplete (
is_entered
attribute =False
). That’s because the user doesn’t enter a number all at once – it will typically consist of multiple keystrokes. If so, there won’t be a decimal representation until we callfinish_entry()
. The StackState is in charge of calling this method if needed before trying to do any calculations with the number.Note
By the time you receive a
StackItem
in a plugin function,is_entered
should always beFalse
, so many of the following methods will not be applicable.-
add_character
(nextchar)[source]¶ Add a character to the running string of the number being entered on the stack. Calling
add_character()
is illegal and will raise anAssertionError
if the number has already been entered completely.Returns: True
if successful,False
if the stack width (esc.consts.STACKWIDTH
) has been exceeded.
-
backspace
(num_chars=1)[source]¶ Remove the last character(s) from the string being entered. Calling
backspace()
is illegal and will raise anAssertionError
if the number has already been entered completely.
-
decimal
= None¶ Decimal representation. This is
None
ifis_entered
isFalse
.
-
finish_entry
()[source]¶ Signal that the user is done entering a string and it should be converted to a Decimal value. If successful, return
True
; if the entered string does not form a valid number, returnFalse
. This should be called only by theenter_number
method ofStackState
.
-
is_entered
= None¶ Whether the number has been fully entered. If not entered, many methods will not work as we don’t have a Decimal representation yet.
-
string
= None¶ String representation.
-
Registry¶
In general, you should prefer working solely with the stack when writing operations, rather than accessing registers; operations that use registers are harder to write and harder for users to understand, and any changes made to registers can’t be undone. (Working through the undo/redo history will still deliver the correct values to the stack, since undoing and redoing restores past states rather than doing the calculations again, but history entries might not match the current values of registers anymore, and changes your operation makes to registers won’t be undone/redone.)
However, sometimes it may be useful to use registers as parameters
or even as outputs in special-purpose custom operations.
You should do this only if you fully understand the consequences
as described in the previous paragraph!
In this case, you can provide a parameter called registry
,
and you will receive the following object:
-
class
esc.registers.
Registry
[source]¶ The Registry stores the values of esc registers. It’s basically a fancy dictionary with some validation and a display-sorted
items()
method.-
__setitem__
(key, value)[source]¶ Set the value of a register.
Raises: InvalidNameError
if the key (register name) isn’t valid.
-
Warning
If you set a register in your operation,
be sure to turn the simulate <esc.commands.Operation.simulate>
option off in your Operation
decorator,
or users may end up inadvertently setting registers
when viewing the help for your function.
Exceptions¶
The most important exceptions are described (and indexed) in the context where you need to deal with them, but here is the complete hierarchy for reference.

-
class
esc.oops.
FunctionExecutionError
[source] A broad exception type that occurs when the code within an operation function couldn’t be executed successfully for some reason. Examples include:
- a number is in the middle of being entered and isn’t a valid number
- a function performed an undefined operation like dividing by zero
- there are too many or too few items on the stack
- a function directly raises this error due to invalid input or inability to complete its task for some other reason
FunctionExecutionError
s normally result in the__str__
of the exception being printed to the status bar, so exception messages should be concise.A
FunctionExecutionError
may be raised directly, or one of its subclasses may be used.
-
class
esc.oops.
InsufficientItemsError
(number_required, msg=None)[source] Raised directly by operations functions that request the entire stack to indicate not enough items are on the stack to finish their work, or by the menu logic when an operation requests more parameters than items on the stack.
Functions may use the simplified form of the exception, providing an int describing the number of items that should have been on the stack for the
number_required
constructor parameter. esc will then reraise the exception with a more useful message; a fallback message is provided in case this doesn’t happen for some reason.
-
class
esc.oops.
ProgrammingError
[source] Indicates an error caused by incorrectly written or defined esc plugins. This includes modes, menus, operations, and so on. It does not include runtime errors within operation functions themselves; these are
FunctionExecutionError
s.
-
class
esc.oops.
FunctionProgrammingError
(operation, problem)[source]¶ A more specific type of
ProgrammingError
that occurs when a user’s@Operation
decorator or function parameters are invalid or function tests fail.The distinction is mostly for convenience within esc’s codebase rather than because client code needs to tell the difference from other
ProgrammingError
s; this class wraps some handy logic for generating a standardized message.