import sys
def generate_actions(strings, machines):
res = set()
for (sidx, offset) in machines:
string = strings[sidx]
if offset < len(string):
res.add(string[offset])
for s in strings:
res.add(s[0])
# FIXME: need a cleverer way to inject a non-matching char
res.add('?')
res = list(res)
res.sort()
return res
def format_machine(s, offset):
res = ""
for i, ch in enumerate(s):
if i == offset:
res += "|"
res += ch
if offset == len(s):
res += "|"
return res
def format_machines(strings, machines):
return ', '.join([format_machine(strings[sidx], offset) for (sidx, offset) in machines])
def format_state(strings, state):
machines, emit, text = state
return "(" + ", ".join([format_machine(strings[sidx], offset) for sidx, offset in machines]) + \
f" {' EMIT' if emit else ''} '{text}')"
def format_states(strings, states):
return ", ".join([format_state(strings, state) for state in states])
def format_active_states(strings, active_states):
return ", ".join(["{} ({}) [{}] '{}'".format(format_state(strings, (state, emit, text)), ix, spans, text) for (ix, state, spans, emit, text) in active_states])
def unify_spans(spans):
res = []
for span in spans:
off, ext = span
x1, x2 = off, off + ext
print(f"Inserting span {x1},{x2}")
new_res = []
for off, ext in res:
y1, y2 = off, off + ext
print(f"Merging with span {y1},{y2}")
if x1 <= y1:
# new span is leftmost
if x2 < y1:
# new span ends before old span starts
print(f"{x1},{x2} is disjoint from {y1},{y2}")
new_res.append((y1, y2- y1))
else:
x2 = max(x2, y2)
print(f"Unified spans into {x1}, {x2}")
else:
# new span is rightmost
if y2 < x1:
# old span ends before new span starts
print(f"{x1},{x2} is disjoint from {y1},{y2}")
new_res.append((y1, y2 - y1))
else:
x1 = y1
x2 = max(x2, y2)
print(f"Unified spans into {x1}, {x2}")
print(f"Inserting span {x1}, {x2}")
new_res.append((x1, x2 - x1))
res = new_res
return res
def mask(inp, spans):
last = 0
res = ""
for x1, x2 in spans:
res += inp[last:x1]
res += "MASKED"
last = x2
res += inp[last:]
return res
def generate_states(*strings, mask_pattern="MASK"):
states = [((), False, '')]
active_states = [(0, (), (), False, "")]
links = set()
changes = True
while changes:
changes = False
new_states = []
print(f"Beginning loop, active_states\n{format_active_states(strings, active_states)}\nall states\n{format_states(strings, states)}")
for (old_ix, machines, spans, emit, text) in active_states:
print(f"Processing state {format_state(strings, (machines, emit, text))}")
actions = generate_actions(strings, machines)
print(f"Actions available {actions}")
for action in actions:
new_machines = []
print(f"Processing action {action}")
ended = []
for (sidx, offset) in machines:
print(f"Processing machine {format_machine(strings[sidx], offset)}")
if offset < len(strings[sidx]):
if action == strings[sidx][offset]:
print(f"Machine advances")
new_machines.append((sidx, offset+1))
if offset+1 == len(strings[sidx]):
print(f"Saw terminator for {sidx}")
ended.append(sidx)
for sidx in range(len(strings)):
if strings[sidx][0] == action:
print(f"New machine for {sidx}")
new_machines.append((sidx, 1))
new_extent = max([0] + [offset for sidx, offset in new_machines])
prev_extent = max([0] + [offset for sidx, offset in machines])
delta = new_extent - prev_extent - 1
# origin is at LHS of new_extent
print(f"Prev extent {prev_extent} New extent {new_extent} Delta {delta}")
new_spans = [ (offset + delta, extent) for offset, extent in spans ]
for sidx in ended:
new_spans.append((new_extent - len(strings[sidx]), len(strings[sidx])))
if emit:
new_spans.append(((new_extent - len(text) - 1), 1))
print(f"Have new spans {new_spans}")
new_unified_spans = unify_spans(new_spans)
print(f"Unified spans to give {new_unified_spans}")
emitted = []
new_filtered_spans = []
last_offset = 0
for (offset, extent) in new_unified_spans:
if offset + extent <= 0:
emitted.append((offset, extent))
else:
new_filtered_spans.append((offset, extent))
last_offset = min(offset, last_offset)
print(f"Last offset: {last_offset}")
print(f"Emitting spans {emitted}")
print(f"Kept spans {new_filtered_spans}")
new_text = text + action
text_origin = new_extent - len(new_text)
limit = min(last_offset - text_origin, -text_origin)
print(f"Had full text '{new_text}' clipped to '{new_text[limit:]}' by limit {limit}")
emitted_text = ""
if limit > 0:
if emit:
emit = False
emitted_text += mask(new_text[:limit], emitted)
print(f"Masked text is {emitted_text}")
new_text = new_text[limit:]
new_filtered_spans.sort()
print(f"Have text origin {text_origin}, nfs {new_filtered_spans[0] if len(new_filtered_spans) > 0 else 'nothing'}")
if len(new_filtered_spans) > 0 and new_filtered_spans[0][0] == text_origin:
(start, extent) = new_filtered_spans[0]
print(f"Setting EMIT flag for ({start}, {extent})")
emit = True
new_text = new_text[extent-1:]
print(f"Clipped text to {new_text}")
new_filtered_spans.pop(0)
new_machines.sort()
state = (tuple(new_machines), emit, new_text)
print(f"New state is {format_state(strings, state)}")
try:
ix = states.index(state)
except ValueError:
print("Adding to full state list, changes = true")
ix = len(states)
states.append(state)
new_states.append((ix, tuple(new_machines), new_filtered_spans, emit, new_text))
changes = True
links.add((old_ix, ix, action, emitted_text))
print()
active_states = new_states
print("Done")
print()
print(strings)
print("Complete states")
for state in states:
print(format_state(strings, state))
print()
print("Links")
for (f, t, a, e) in links:
print("{} -> {} via {} emitting {}".format(
format_state(strings, states[f]),
format_state(strings, states[t]),
a,
e)
)
return states, links
# def parse_with_states(inp, states, links):
# state = 0
# spans = []
# for i, ch in enumerate(inp):
# for (f, t, a, m) in links:
# if f != state:
# continue
# if ch == a:
# if m is not None:
# spans.append((i+1-m, i+1))
# state = t
# break
# else:
# state = 0
# return spans
if __name__ == "__main__":
s, l = generate_states("abcd", "1ab", "aa") # "cde", "bce", ,
# for case in [ "1abcdef", "1a", "qqcdeblah" ]:
# spans = parse_with_states(case, s, l)
# print(f"{case} yield spans {spans}")
# spans = unify_spans(spans)
# print(f"{case} yield unified spans {spans}")
# print(f"Have masked {mask(case, spans)}")