/automata/pda/npda.py

https://github.com/caleb531/automata
Python | 119 lines | 69 code | 9 blank | 41 comment | 7 complexity | 5e6bdd62f0d0b82b273a27a9e84e945d MD5 | raw file
  1. #!/usr/bin/env python3
  2. """Classes and methods for working with nondeterministic pushdown automata."""
  3. import copy
  4. import automata.base.exceptions as exceptions
  5. import automata.pda.pda as pda
  6. from automata.pda.configuration import PDAConfiguration
  7. from automata.pda.stack import PDAStack
  8. class NPDA(pda.PDA):
  9. """A nondeterministic pushdown automaton."""
  10. def __init__(self, *, states, input_symbols, stack_symbols,
  11. transitions, initial_state,
  12. initial_stack_symbol, final_states, acceptance_mode='both'):
  13. """Initialize a complete NPDA."""
  14. self.states = states.copy()
  15. self.input_symbols = input_symbols.copy()
  16. self.stack_symbols = stack_symbols.copy()
  17. self.transitions = copy.deepcopy(transitions)
  18. self.initial_state = initial_state
  19. self.initial_stack_symbol = initial_stack_symbol
  20. self.final_states = final_states.copy()
  21. self.acceptance_mode = acceptance_mode
  22. self.validate()
  23. def _validate_transition_invalid_symbols(self, start_state, paths):
  24. """Raise an error if transition symbols are invalid."""
  25. for input_symbol, symbol_paths in paths.items():
  26. self._validate_transition_invalid_input_symbols(
  27. start_state, input_symbol)
  28. for stack_symbol in symbol_paths:
  29. self._validate_transition_invalid_stack_symbols(
  30. start_state, stack_symbol)
  31. def _get_transitions(self, state, input_symbol, stack_symbol):
  32. """Get the transition tuples for the given state and symbols."""
  33. transitions = set()
  34. if (state in self.transitions and
  35. input_symbol in self.transitions[state] and
  36. stack_symbol in self.transitions[state][input_symbol]):
  37. for (
  38. dest_state,
  39. new_stack_top
  40. ) in self.transitions[state][input_symbol][stack_symbol]:
  41. transitions.add((
  42. input_symbol,
  43. dest_state,
  44. new_stack_top
  45. ))
  46. return transitions
  47. def _get_next_configurations(self, old_config):
  48. """Advance to the next configurations."""
  49. transitions = set()
  50. if old_config.remaining_input:
  51. transitions.update(self._get_transitions(
  52. old_config.state,
  53. old_config.remaining_input[0],
  54. old_config.stack.top()
  55. ))
  56. transitions.update(self._get_transitions(
  57. old_config.state,
  58. '',
  59. old_config.stack.top()
  60. ))
  61. new_configs = set()
  62. for input_symbol, new_state, new_stack_top in transitions:
  63. remaining_input = old_config.remaining_input
  64. if input_symbol:
  65. remaining_input = remaining_input[1:]
  66. new_config = PDAConfiguration(
  67. new_state,
  68. remaining_input,
  69. self._replace_stack_top(old_config.stack, new_stack_top)
  70. )
  71. new_configs.add(new_config)
  72. return new_configs
  73. def read_input_stepwise(self, input_str):
  74. """
  75. Check if the given string is accepted by this NPDA.
  76. Yield the NPDA's current configurations at each step.
  77. """
  78. current_configurations = set()
  79. current_configurations.add(PDAConfiguration(
  80. self.initial_state,
  81. input_str,
  82. PDAStack([self.initial_stack_symbol])
  83. ))
  84. yield current_configurations
  85. while current_configurations:
  86. new_configurations = set()
  87. for config in current_configurations:
  88. if self._has_accepted(config):
  89. # One accepting configuration is enough.
  90. return
  91. if config.remaining_input:
  92. new_configurations.update(
  93. self._get_next_configurations(config)
  94. )
  95. elif self._has_lambda_transition(
  96. config.state,
  97. config.stack.top()
  98. ):
  99. new_configurations.update(
  100. self._get_next_configurations(config)
  101. )
  102. current_configurations = new_configurations
  103. yield current_configurations
  104. raise exceptions.RejectionException(
  105. 'the NPDA did not reach an accepting configuration'
  106. )