The Forth Wizard finds the shortest sequence of forth instructions needed for a given stack manipulation.
In addition to simple stack transforms it allows for partial specification of the final stack state, usage of the return stack, and caching. All common forth instructions are supported.
This was translated from the orignal forth wizard and heavily extended. It is written in C and provides a Python3 module. Tested only on Ubuntu.
Install from pypi with pip3 install forthwiz
,
or download and run sudo ./make.sh
to build and install. Requires package python3-dev to build.
Instantiate a forthwiz.Wizard
object and use the setup
method to specify
the machine state. This can include the initial and ending state of data stack,
initial state of return stack, and options for caching, supported ops, and return stack usage.
The stack states are specified with lists, the items in those lists may be any hashable type.
$ python3
>>> import forthwiz
>>> wiz = forthwiz.Wizard()
>>> wiz.setup( in_stack=['a', 'b'], out_stack=['b', 'a', 'a'] )
>>> solution = wiz.solve()
>>> solution.code
['swap', 'dup']
Wizard.solve
may be called multiple times to generate alternative solutions:
>>> wiz.setup( in_stack=['a', 'b'], out_stack=['b', 'a', 'a'] )
>>> solution = wiz.solve(); solution.code
['swap', 'dup']
>>> solution = wiz.solve(); solution.code
['over', 'rot']
>>> solution = wiz.solve(); solution.code
['swap', '2dup', 'nip']
The out_vars
parameter can be used to specify symbols that must remain
in the final machine state. When out_vars
is set
the out_stack
parameter only specifies the top values
that are expected on the stack. Symbols in out_vars
that are not in out_stack
may be included in the bottom portion of the data stack in any order. This may yield
shorter solutions when only the top ordering matters.
The final data stack value is saved as the Solution.stack
attribute.
>>> wiz.setup( in_stack=[1, 2, 3, 4, 5], out_stack=[1],
out_vars=[1, 2, 3, 5] )
>>> solution = wiz.solve()
>>> solution.code
['nip', '2swap', 'swap']
>>> solution.stack
[3, 5, 2, 1]
The use_rstack
parameter tells the solver that it is ok to leave values on the
return stack. When it is set values that appear in out_vars
that are not present
in out_stack
may be left on the return stack in addition to the bottom of
the data stack. This is disabled by default. The final state of the return stack
is saved as the Solution.rstack
attribute.
>>> wiz.setup( in_stack=[1, 2, 3, 4, 5], out_stack=[1],
out_vars=[1, 2, 3, 5], use_rstack=True )
>>> solution = wiz.solve()
>>> solution.code
['>r', 'drop', 'rot']
>>> solution.stack
[2, 3, 1]
>>> solution.rstack
[5]
in_rstack
can be used to specify the starting state of the return stack.
Use the Wizard.solutions
to find all the solutions of the minimal length.
>>> wiz.setup( in_stack=[1, 2], out_stack=[2, 1, 1] )
>>> solution = wiz.solutions()
>>> [s.code for s in solution]
[['swap', 'dup'], ['over', 'rot']]
Subsequent calls to Wizard.solutions
gives the collection of the next
longest solution length.
Wizard.solve_many
can be used to find all solutions under a given length.
The forth ops the solver will use to find the solution can be set with the ops
parameter. Specify the op N pick
with Npick
, currently supported for N=[2,5].
For example, duplicating top of stack without dup
:
>>> wiz.setup( in_stack=['A', 'B'], out_stack=['A', 'B', 'B'],
ops=['swap', 'r>', 'over', '>r', 'r@'] )
>>> solution = wiz.solve(); solution.code
>>> ['>r', 'r@', 'r>']
Predefined collections of ops supported by common forth interpreters can be set
using the target
parameter. Currently supported targets are gforth and amforth.
>>> wiz.setup( in_stack=['a', 'b','c','d'], out_stack=['a', 'b','c','d','a','b'],
target='gforth' )
>>> solution = wiz.solve(); solution.code
['2over']
>>> wiz.setup( in_stack=['a', 'b','c','d'], out_stack=['a', 'b','c','d','a','b'],
target='amforth' )
>>> solution = wiz.solve(); solution.code
['2>r', '2dup', '2r>', '2swap']
By default calls to solve
will cache the solution.
To disable caching set the optional setup
parameter use_cache
to False.
A different cache file is used for each solver version and collection of
ops used to find the solution, for example wizard_cache_1_2_7ffff.txt
.
Use of the pick
instruction may be disabled with the use_pick
option:
>>> wiz.setup( in_stack=[0, 1, 2], out_stack=[0, 2, 0, 1] )
>>> solution = wiz.solve(); solution
['2', 'pick', 'rot']
>>> wiz.setup( in_stack=[0, 1, 2], out_stack=[0, 2, 0, 1],
use_pick=False )
>>> solution = wiz.solve(); solution
['swap', '>r', 'over', 'r>']
forthwiz.solve_stacks
is a convenience function supporting only basic usage. It takes
two lists describing the input and output states of the data stack and a subset
of the options available to Wizard.setup
>>> import forthwiz
>>> forthwiz.solve_stacks( ['a', 'b'], ['b', 'a', 'a'] )
['swap', 'dup']