|
TuringSim
C++ framework to simulate abstract computing models
|
Simulating Turing machines, or finite state automata is easy when they have explicit transitions, and not many of them. But if they use transition patterns, it may quickly become awful. TuringSim is there for you! It is a very configurable framework with a lot of predefined machine types, that makes running computation models easy and efficient.
Check out the repo.
The fundamental class is TuringSim::Machine::Machine. It is the most abstract class representing a machine. The most important template parameter is the first one: TransitionType. It must be a derived class of TuringSim::Transition::Transition. A machine must be able to return initial states, transitions matching a given configuration and deciding when a configuration is halting. Moreover, depending whether the machine is accepting or not, it may also be able to decide whether a configuration is accepting.
Class TuringSim::Transition::Transition is the core of machine executions. A transition provides two functions: one that checks if the transition matches, and one to apply it. Transitions are parameterized by two main types: the type of the states of the machine, and the type of the storage.
A machine is not able to run by it self. To do so, we need to use a TuringSim::Runner::Runner. Runners may implement various execution strategy.
Machines may be deterministic or not. Non-deterministic machines must be able to return the set of initial states, matching transitions, and check if a configuration is terminating. Deterministic machines must also be able to return the single initial state, and the only matching transition. Deterministic machines are thus special case of non-deterministic machines.
Machines have 3 ways of terminating:
Machines can be used for computation (and thus, being non accepting) or decision. There are two kinds of accepting machines: regularly accepting machines (or simply accepting machines) and alternating machines. The execution of a non-accepting machine ends when it reaches a halting state. Execution of accepting or alternating machines ends when acceptance can be decided.
Accepting machines accepts their input if at least one execution leads to an accepting configuration. Each execution ends as soon as it encounters a halting configuration or an accepting configuration.
Alternating machines allows nodes to be quantified universally, and not only existentially like accepting machines. Each execution ends in one of these cases:
Transitions are returned by machines. They provide two functions:
match that test if the transition matches a configurationapply that apply the transition on a given configuration, modify the memory state in-place, and return the new state.When matching is a hard job (like term unification), usually, apply shall do something similar. To avoid performing twice the same computation, match return an apply helper that is provided to apply. The apply helper type is a template parameter.
Runner are in charge of running machines. They are derived classes of TuringSim::Runner::MachineRunner. There are generic runners, for deterministic and non-deterministic machines. They can run a given number of execution steps (step), or a run until the machine terminates (if non-accepting) or decides acceptation (if accepting or alternating) (exec). The latter is quite risky: termination is up to the developer since it is not decidable in general.
For accepting or alternating machines, generic runners also provide a function accept that runs the machine like exec but also returns the decision.
Runners can be implemented more efficiently for particular machines. In general, for non-deterministic machine, the memory can be arbitrarily modified, and must be copied between to execution traces. But, e.g. for finite state machines, the input word is constant, only the position may change (and only with epsilon transitions). For this reason, a runner for non-deterministic machines can be more efficient than the general case.
Machine execution can be logged by a TuringSim::Listener::Listener.
There are no formal requirements on memory structures. It is convenient to be able to observe and modify them externally.
We provide:
States pattern must provide a match function to match against an actuel state.
We provide two kind of states/patterns:
Unification is required when matching this second kind of states.
Symbol patterns can be static or dependant on a context for Turing 1936-style transition.
Transitions are provided for each kind of provided machines. Each derived class shall specialized only one aspect at once.