_core.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. # -*- test-case-name: automat._test.test_core -*-
  2. """
  3. A core state-machine abstraction.
  4. Perhaps something that could be replaced with or integrated into machinist.
  5. """
  6. from itertools import chain
  7. _NO_STATE = "<no state>"
  8. class NoTransition(Exception):
  9. """
  10. A finite state machine in C{state} has no transition for C{symbol}.
  11. @param state: the finite state machine's state at the time of the
  12. illegal transition.
  13. @param symbol: the input symbol for which no transition exists.
  14. """
  15. def __init__(self, state, symbol):
  16. self.state = state
  17. self.symbol = symbol
  18. super(Exception, self).__init__(
  19. "no transition for {} in {}".format(symbol, state)
  20. )
  21. class Automaton(object):
  22. """
  23. A declaration of a finite state machine.
  24. Note that this is not the machine itself; it is immutable.
  25. """
  26. def __init__(self):
  27. """
  28. Initialize the set of transitions and the initial state.
  29. """
  30. self._initialState = _NO_STATE
  31. self._transitions = set()
  32. @property
  33. def initialState(self):
  34. """
  35. Return this automaton's initial state.
  36. """
  37. return self._initialState
  38. @initialState.setter
  39. def initialState(self, state):
  40. """
  41. Set this automaton's initial state. Raises a ValueError if
  42. this automaton already has an initial state.
  43. """
  44. if self._initialState is not _NO_STATE:
  45. raise ValueError(
  46. "initial state already set to {}".format(self._initialState))
  47. self._initialState = state
  48. def addTransition(self, inState, inputSymbol, outState, outputSymbols):
  49. """
  50. Add the given transition to the outputSymbol. Raise ValueError if
  51. there is already a transition with the same inState and inputSymbol.
  52. """
  53. # keeping self._transitions in a flat list makes addTransition
  54. # O(n^2), but state machines don't tend to have hundreds of
  55. # transitions.
  56. for (anInState, anInputSymbol, anOutState, _) in self._transitions:
  57. if (anInState == inState and anInputSymbol == inputSymbol):
  58. raise ValueError(
  59. "already have transition from {} via {}".format(inState, inputSymbol))
  60. self._transitions.add(
  61. (inState, inputSymbol, outState, tuple(outputSymbols))
  62. )
  63. def allTransitions(self):
  64. """
  65. All transitions.
  66. """
  67. return frozenset(self._transitions)
  68. def inputAlphabet(self):
  69. """
  70. The full set of symbols acceptable to this automaton.
  71. """
  72. return {inputSymbol for (inState, inputSymbol, outState,
  73. outputSymbol) in self._transitions}
  74. def outputAlphabet(self):
  75. """
  76. The full set of symbols which can be produced by this automaton.
  77. """
  78. return set(
  79. chain.from_iterable(
  80. outputSymbols for
  81. (inState, inputSymbol, outState, outputSymbols)
  82. in self._transitions
  83. )
  84. )
  85. def states(self):
  86. """
  87. All valid states; "Q" in the mathematical description of a state
  88. machine.
  89. """
  90. return frozenset(
  91. chain.from_iterable(
  92. (inState, outState)
  93. for
  94. (inState, inputSymbol, outState, outputSymbol)
  95. in self._transitions
  96. )
  97. )
  98. def outputForInput(self, inState, inputSymbol):
  99. """
  100. A 2-tuple of (outState, outputSymbols) for inputSymbol.
  101. """
  102. for (anInState, anInputSymbol,
  103. outState, outputSymbols) in self._transitions:
  104. if (inState, inputSymbol) == (anInState, anInputSymbol):
  105. return (outState, list(outputSymbols))
  106. raise NoTransition(state=inState, symbol=inputSymbol)
  107. class Transitioner(object):
  108. """
  109. The combination of a current state and an L{Automaton}.
  110. """
  111. def __init__(self, automaton, initialState):
  112. self._automaton = automaton
  113. self._state = initialState
  114. self._tracer = None
  115. def setTrace(self, tracer):
  116. self._tracer = tracer
  117. def transition(self, inputSymbol):
  118. """
  119. Transition between states, returning any outputs.
  120. """
  121. outState, outputSymbols = self._automaton.outputForInput(self._state,
  122. inputSymbol)
  123. outTracer = None
  124. if self._tracer:
  125. outTracer = self._tracer(self._state._name(),
  126. inputSymbol._name(),
  127. outState._name())
  128. self._state = outState
  129. return (outputSymbols, outTracer)