File size: 11,900 Bytes
a114d59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
"""
PDA Simulator - Flask Backend
Theory of Computation Visual Tool
"""

from flask import Flask, render_template, request, jsonify
import json
import re

app = Flask(__name__)

# ─────────────────────────────────────────────
#  Sample PDA definitions
# ─────────────────────────────────────────────
SAMPLE_PDAS = {
    "anbn": {
        "name": "aⁿbⁿ Language",
        "description": "Accepts strings of the form aⁿbⁿ where n β‰₯ 1",
        "states": ["q0", "q1", "q2"],
        "input_alphabet": ["a", "b"],
        "stack_alphabet": ["Z", "A"],
        "start_state": "q0",
        "initial_stack": "Z",
        "final_states": ["q2"],
        "acceptance": "final_state",
        "transitions": [
            "q0, a, Z -> q0, AZ",
            "q0, a, A -> q0, AA",
            "q0, b, A -> q1, Ξ΅",
            "q1, b, A -> q1, Ξ΅",
            "q1, Ξ΅, Z -> q2, Z"
        ],
        "test_strings": ["ab", "aabb", "aaabbb", "aab", "abb"]
    },
    "balanced_parens": {
        "name": "Balanced Parentheses",
        "description": "Accepts strings with balanced ( and ) brackets",
        "states": ["q0", "q1"],
        "input_alphabet": ["(", ")"],
        "stack_alphabet": ["Z", "P"],
        "start_state": "q0",
        "initial_stack": "Z",
        "final_states": ["q1"],
        "acceptance": "final_state",
        "transitions": [
            "q0, (, Z -> q0, PZ",
            "q0, (, P -> q0, PP",
            "q0, ), P -> q0, Ξ΅",
            "q0, Ξ΅, Z -> q1, Z"
        ],
        "test_strings": ["()", "(())", "((()))", ")(", "(()", "(())()"]
    },
    "wcwr": {
        "name": "wcwα΄Ώ Palindrome",
        "description": "Accepts strings of the form wcwᴿ where w∈{a,b}*",
        "states": ["q0", "q1", "q2"],
        "input_alphabet": ["a", "b", "c"],
        "stack_alphabet": ["Z", "A", "B"],
        "start_state": "q0",
        "initial_stack": "Z",
        "final_states": ["q2"],
        "acceptance": "final_state",
        "transitions": [
            "q0, a, Z -> q0, AZ",
            "q0, a, A -> q0, AA",
            "q0, a, B -> q0, AB",
            "q0, b, Z -> q0, BZ",
            "q0, b, A -> q0, BA",
            "q0, b, B -> q0, BB",
            "q0, c, Z -> q1, Z",
            "q0, c, A -> q1, A",
            "q0, c, B -> q1, B",
            "q1, a, A -> q1, Ξ΅",
            "q1, b, B -> q1, Ξ΅",
            "q1, Ξ΅, Z -> q2, Z"
        ],
        "test_strings": ["acΠ°", "abcba", "aabcbaa", "abc", "abcab"]
    }
}

# ─────────────────────────────────────────────
#  PDA Simulator Core Logic
# ─────────────────────────────────────────────

def parse_transitions(transition_lines):
    """
    Parse transition lines like:
      q0, a, Z -> q0, AZ
      q0, Ξ΅, Z -> q1, Z
    Returns dict: (state, input_sym, stack_top) -> list of (next_state, push_str)
    """
    transitions = {}
    errors = []
    for i, line in enumerate(transition_lines):
        line = line.strip()
        if not line:
            continue
        # Support -> or β†’
        line = line.replace('β†’', '->')
        if '->' not in line:
            errors.append(f"Line {i+1}: Missing '->' in '{line}'")
            continue
        left, right = line.split('->', 1)
        left_parts = [p.strip() for p in left.split(',')]
        right_parts = [p.strip() for p in right.split(',')]
        if len(left_parts) != 3 or len(right_parts) != 2:
            errors.append(f"Line {i+1}: Invalid format in '{line}'")
            continue
        cur_state, inp_sym, stack_top = left_parts
        next_state, push_val = right_parts
        # Normalize epsilon
        inp_sym = '' if inp_sym in ('Ξ΅', 'eps', 'epsilon', '') else inp_sym
        push_val = '' if push_val in ('Ξ΅', 'eps', 'epsilon') else push_val
        key = (cur_state, inp_sym, stack_top)
        if key not in transitions:
            transitions[key] = []
        transitions[key].append((next_state, push_val))
    return transitions, errors


def simulate_pda(states, start_state, final_states, initial_stack,
                 transitions, input_string, acceptance_mode, max_steps=500):
    """
    BFS/DFS simulation of PDA. Returns list of step configurations.
    Each config: {step, state, remaining_input, stack, transition_applied, status}
    """
    # Each item: (state, remaining_input, stack_list, history_of_steps)
    initial_config = (start_state, list(input_string), [initial_stack], [])
    queue = [initial_config]
    visited = set()
    accepting_path = None
    all_paths_tried = []

    step_counter = 0

    while queue and step_counter < max_steps:
        step_counter += 1
        state, remaining, stack, history = queue.pop(0)

        # Avoid infinite loops via visited states
        visit_key = (state, tuple(remaining), tuple(stack))
        if visit_key in visited:
            continue
        visited.add(visit_key)

        inp_sym = remaining[0] if remaining else ''
        stack_top = stack[-1] if stack else ''

        current_step = {
            "step": len(history) + 1,
            "state": state,
            "remaining_input": ''.join(remaining) if remaining else 'Ξ΅',
            "stack": list(stack),
            "transition_applied": None,
            "status": "running"
        }

        # Check acceptance
        if acceptance_mode == "final_state":
            if not remaining and state in final_states:
                accepting_path = history + [current_step]
                accepting_path[-1]["status"] = "accepted"
                break
        elif acceptance_mode == "empty_stack":
            if not remaining and not stack:
                accepting_path = history + [current_step]
                accepting_path[-1]["status"] = "accepted"
                break

        # Try epsilon transitions first, then input transitions
        moved = False
        for try_inp in (['', inp_sym] if inp_sym else ['']):
            key = (state, try_inp, stack_top)
            if key in transitions:
                for next_state, push_val in transitions[key]:
                    new_remaining = remaining[1:] if try_inp else remaining
                    new_stack = stack[:-1]  # pop stack top
                    # Push new symbols (push_val reversed onto stack)
                    if push_val:
                        for ch in reversed(push_val):
                            new_stack.append(ch)
                    transition_label = f"({state}, {'Ξ΅' if not try_inp else try_inp}, {stack_top}) β†’ ({next_state}, {'Ξ΅' if not push_val else push_val})"
                    step_info = dict(current_step)
                    step_info["transition_applied"] = transition_label
                    new_history = history + [step_info]
                    queue.append((next_state, new_remaining, new_stack, new_history))
                    moved = True

        if not moved:
            # Dead configuration
            dead_step = dict(current_step)
            dead_step["status"] = "dead"
            all_paths_tried.append(history + [dead_step])

    if accepting_path:
        return {"accepted": True, "steps": accepting_path}
    else:
        # Return the longest dead path for informative output
        if all_paths_tried:
            longest = max(all_paths_tried, key=len)
        else:
            longest = [{"step": 1, "state": start_state,
                        "remaining_input": input_string or 'Ξ΅',
                        "stack": [initial_stack],
                        "transition_applied": None, "status": "dead"}]
        if longest:
            longest[-1]["status"] = "rejected"
        return {"accepted": False, "steps": longest}


# ─────────────────────────────────────────────
#  Flask Routes
# ─────────────────────────────────────────────

@app.route('/')
def index():
    return render_template('index.html')


@app.route('/api/samples', methods=['GET'])
def get_samples():
    """Return list of sample PDA names and keys."""
    result = {}
    for key, val in SAMPLE_PDAS.items():
        result[key] = {k: v for k, v in val.items()}
    return jsonify(result)


@app.route('/api/simulate', methods=['POST'])
def simulate():
    """
    Simulate PDA.
    Expects JSON body with PDA definition and input string.
    """
    data = request.get_json()
    if not data:
        return jsonify({"error": "No JSON body provided"}), 400

    # Extract fields
    states_raw = data.get('states', '')
    input_alpha_raw = data.get('input_alphabet', '')
    stack_alpha_raw = data.get('stack_alphabet', '')
    start_state = data.get('start_state', '').strip()
    initial_stack = data.get('initial_stack', '').strip()
    final_states_raw = data.get('final_states', '')
    transitions_raw = data.get('transitions', [])
    input_string = data.get('input_string', '')
    acceptance_mode = data.get('acceptance', 'final_state')

    # Parse comma-separated fields
    states = [s.strip() for s in states_raw.split(',') if s.strip()]
    final_states = [s.strip() for s in final_states_raw.split(',') if s.strip()]

    # Validate
    errors = []
    if not states:
        errors.append("States cannot be empty.")
    if not start_state:
        errors.append("Start state cannot be empty.")
    if start_state and start_state not in states:
        errors.append(f"Start state '{start_state}' not in states list.")
    if not initial_stack:
        errors.append("Initial stack symbol cannot be empty.")
    for fs in final_states:
        if fs not in states:
            errors.append(f"Final state '{fs}' not in states list.")

    # Parse transitions
    if isinstance(transitions_raw, list):
        transition_lines = transitions_raw
    else:
        transition_lines = transitions_raw.split('\n')

    transitions, parse_errors = parse_transitions(transition_lines)
    errors.extend(parse_errors)

    if errors:
        return jsonify({"error": " | ".join(errors)}), 400

    # Run simulation
    result = simulate_pda(
        states=states,
        start_state=start_state,
        final_states=final_states,
        initial_stack=initial_stack,
        transitions=transitions,
        input_string=input_string,
        acceptance_mode=acceptance_mode
    )

    # Build transitions list for diagram
    diagram_transitions = []
    for (cur_state, inp_sym, stack_top), targets in transitions.items():
        for next_state, push_val in targets:
            diagram_transitions.append({
                "from": cur_state,
                "to": next_state,
                "label": f"{'Ξ΅' if not inp_sym else inp_sym}, {stack_top}/{'Ξ΅' if not push_val else push_val}"
            })

    result["diagram"] = {
        "states": states,
        "start_state": start_state,
        "final_states": final_states,
        "transitions": diagram_transitions
    }
    result["total_steps"] = len(result["steps"])

    return jsonify(result)


@app.route('/api/validate', methods=['POST'])
def validate():
    """Quick validation endpoint."""
    data = request.get_json()
    transitions_raw = data.get('transitions', [])
    if isinstance(transitions_raw, list):
        lines = transitions_raw
    else:
        lines = transitions_raw.split('\n')
    _, errors = parse_transitions(lines)
    return jsonify({"valid": len(errors) == 0, "errors": errors})


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=7860, debug=True)