-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtmtracery.py
147 lines (132 loc) · 6.63 KB
/
tmtracery.py
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
import argparse
import json
from typing import NamedTuple, Set, Dict, Tuple
from enum import Enum
from itertools import product
from collections import OrderedDict
# Forward, Back, Stay
directions = "><_"
# Tracery special characters, JSON special characters, CBDQ special characters, and *
reserved_characters = '[],.{}#"*\n \t'
class StateSymbol(NamedTuple):
"""
Next action of a Turing machine depends only on current state and symbol under tape head
"""
state: str
symbol: str
def code(self):
return '*{0}*{1}'.format(self.state, self.symbol)
class Action(NamedTuple):
"""
Depending on state and symbol, the action can be to transition to any state, write any symbol under the tape head, and optionally move in either direction.
"""
state: str
symbol: str
direction: str
def code(self, accept, reject):
if self.state == accept or self.state == reject:
run_next = ""
else:
run_next = "[run_next:#activate_next#]"
return "[state:{0}][tape_right:POP][tape_right:{1}][direction:{2}]{3}".format(self.state, self.symbol, self.direction, run_next)
TransitionFunction = Dict[StateSymbol, Action]
class TuringMachine(NamedTuple):
states: Set[str]
symbols: Set[str]
blank_symbol: str
start_state: str
accept_state: str
reject_state: str
delta: TransitionFunction
@classmethod
def from_json(cls, mj):
states = set(mj['states'])
assert len(states) == len(mj['states']), "State names not unique"
symbols = set(mj['symbols'])
assert len(symbols) == len(mj['symbols']), "Symbols not unique"
delta = {StateSymbol(*state_symbol): Action(*action) for (state_symbol, action) in mj['delta']}
assert len(delta) == len(mj['delta'])
return TuringMachine(states, symbols, mj['blank_symbol'], mj['start_state'], mj['accept_state'], mj['reject_state'], delta)
def validate(self):
for state in self.states:
assert self.string_is_valid(state), 'state name "{}" contains reserved character'.format(state)
for symbol in self.symbols:
assert self.symbol_is_valid(symbol), 'symbol "{}" is reserved'.format(symbol)
assert len(self.blank_symbol) == 1, 'blank symbol "{}" must be a single symbol'.format(self.blank_symbol)
assert self.blank_symbol in self.symbols, 'blank symbol "{}" not in symbols'.format(self.blank_symbol)
assert self.start_state in self.states, 'start state "{}" not in states'.format(self.start_state)
assert self.accept_state in self.states, 'accept state "{}" not in states'.format(self.accept_state)
assert self.reject_state in self.states, 'reject state "{}" not in states'.format(self.reject_state)
for (state_symbol, action) in self.delta.items():
assert state_symbol.state in self.states, 'transition starts in nonexistent state "{}"'.format(state_symbol.state)
assert state_symbol.symbol in self.symbols, 'transition requires nonexistent symbol "{}"'.format(state_symbol.symbol)
assert state_symbol.state != self.accept_state, 'transition starts in accepting state "{}"'.format(state_symbol.state)
assert state_symbol.state != self.reject_state, 'transition starts in rejecting state "{}"'.format(state_symbol.state)
assert action.state in self.states, 'transition goes to nonexistent state "{}"'.format(action.state)
assert action.symbol in self.symbols, 'transition writes nonexistent symbol "{}"'.format(action.symbol)
assert action.direction in directions, 'transition goes in invalid direction "{}"'.format(action.direction)
return True
@staticmethod
def string_is_valid(string):
return all(c not in string for c in reserved_characters)
@staticmethod
def symbol_is_valid(symbol):
return len(symbol) == 1 and symbol not in reserved_characters
def as_tracery(self):
raise NotImplemented
def validate_input(machine, input):
for symbol in input:
assert symbol in machine.symbols
def main():
parser = argparse.ArgumentParser(description='Compile Turing machines to Tracery grammars.')
parser.add_argument('machine', type=str,
help='the Turing machine to compile')
parser.add_argument('--input', metavar='i', type=str,
help='optional input tape file for the machine')
parser.add_argument('--output', metavar='o', type=str,
help='optional output filename')
parser.add_argument('--verbose', dest='verbose', action='store_true')
parser.set_defaults(verbose=False)
args = parser.parse_args()
with open(args.machine) as machine_file:
machine_json = json.load(machine_file)
if args.input is not None:
with open(args.input) as input_file:
input = input_file.read()
else:
input = ""
if args.output is not None:
out_filename = args.output
else:
out_filename = args.machine+'.tracery.json'
machine = TuringMachine.from_json(machine_json)
machine.validate()
validate_input(machine, input)
with open('tmtracery.json') as tracery_base_file:
machine_tracery = json.load(tracery_base_file, object_pairs_hook=OrderedDict)
machine_tracery['init_tape'] = ''.join('[tape_right:{}]'.format(symbol) for symbol in reversed(input))
machine_tracery['init_state'] = "[state:{}]".format(machine.start_state)
if args.verbose:
machine_tracery['run'] = "#state##tape_right# " + machine_tracery['run']
for k,v in machine_tracery.items():
if 'activate' not in k and not k.startswith('tape'):
machine_tracery[k] = "\n*{}*".format(k) + v
machine_tracery['blank'] = machine.blank_symbol
for symbol in machine.symbols:
machine_tracery["padder_left{}".format(symbol)] = ""
machine_tracery["padder_right{}".format(symbol)] = ""
machine_tracery["rewind{}".format(symbol)] = "#rewind_tape#"
machine_tracery["show_left{}".format(symbol)] = "#show_left#"
machine_tracery["show_right{}".format(symbol)] = "#show_right#"
for state in machine.states:
if state == machine.accept_state:
continue
if state == machine.reject_state:
continue
for symbol in machine.symbols:
state_symbol = StateSymbol(state, symbol)
machine_tracery[state_symbol.code()] = machine.delta[state_symbol].code(machine.accept_state, machine.reject_state)
with open(out_filename, 'w') as out_file:
json.dump(machine_tracery, out_file, indent='\t')
if __name__ == '__main__':
main()