make big changes

This commit is contained in:
Maxim Slipenko 2021-09-21 00:04:50 +03:00
parent 7720a2a621
commit 3c4bcfdb72
7 changed files with 300 additions and 231 deletions

View File

@ -1,8 +1,10 @@
{ {
"python.linting.enabled": true, "python.linting.enabled": true,
"python.linting.pycodestyleEnabled": true,
"python.testing.pytestArgs": [ "python.testing.pytestArgs": [
"tests" "tests"
], ],
"python.testing.unittestEnabled": false, "python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true, "python.testing.pytestEnabled": true,
} }

View File

@ -6,12 +6,15 @@ setup(
setup_requires=['setuptools_scm'], setup_requires=['setuptools_scm'],
author='Maxim Slipenko', author='Maxim Slipenko',
packages=['turinglab'], packages=['turinglab'],
install_requires = [ install_requires=[
'python-docx' 'python-docx',
'pydot',
'requests',
'sortedcontainers'
], ],
entry_points = { entry_points={
'console_scripts': [ 'console_scripts': [
'turinglab = turinglab.__main__:main' 'turinglab = turinglab.__main__:main'
] ]
} }
) )

View File

@ -1,35 +1,39 @@
import os import os
import sys import sys
from argparse import ArgumentParser from argparse import ArgumentParser
from turinglab.image import get_image # from turinglab.image import get_image
from turinglab.input import from_file from turinglab.input import from_file
from turinglab.emulator import Emulator from turinglab.emulator import Emulator
from turinglab.output import to_docx from turinglab.output import output
def get_parser() -> ArgumentParser: def get_parser() -> ArgumentParser:
parser = ArgumentParser() parser = ArgumentParser()
parser.add_argument("input_file", type=str, help="Path to file with program",) parser.add_argument("input_file", type=str,
help="Path to file with program",)
parser.add_argument("output_dir", type=str, help="Output dir") parser.add_argument("output_dir", type=str, help="Output dir")
parser.add_argument('-t','--tests', nargs='+', type=str, help='List of input strings for tests') parser.add_argument("-t", '--tests', nargs='+', type=str,
parser.add_argument('-e','--empty-character', type=str, help='Empty character') help='List of input strings for tests')
parser.add_argument('-f','--force', default=False, action='store_true', help='Force rewrite output dir') parser.add_argument('-e', '--empty-character',
type=str, help='Empty character')
parser.add_argument('-f', '--force', default=False,
action='store_true', help='Force rewrite output dir')
return parser return parser
def main(): def main():
parser = get_parser() parser = get_parser()
args = parser.parse_args() args = parser.parse_args()
program = from_file(args.input_file) program = from_file(args.input_file)
if args.force == False and os.path.exists(args.output_dir): if args.force is False and os.path.exists(args.output_dir):
print('Directory already exists!') print('Directory already exists!')
return -1 return -1
os.makedirs(args.output_dir, exist_ok=True) os.makedirs(args.output_dir, exist_ok=True)
test_data = [] tests = []
for test in args.tests: for test in args.tests:
test = test.replace(args.empty_character, 'λ') test = test.replace(args.empty_character, 'λ')
@ -42,11 +46,13 @@ def main():
tm.step() tm.step()
data.append(tm.info()) data.append(tm.info())
test_data.append(data) tests.append(data)
output(args.output_dir, program, tests)
# to_docx(os.path.join(args.output_dir, 'report.docx'), program, test_data)
# get_image(os.path.join(args.output_dir, 'graph'), program)
to_docx(os.path.join(args.output_dir, 'report.docx'), program, test_data)
get_image(os.path.join(args.output_dir, 'graph'), program)
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -1,54 +1,135 @@
from collections import defaultdict from collections import defaultdict
from sortedcontainers import SortedDict
from copy import copy, deepcopy
from enum import IntEnum
DIRECTIONS = {
'>': 1, class Movemement(IntEnum):
'<': -1, R = 1
'.': 0 L = -1
} E = 0
class Action:
def __init__(
self,
state,
symbol: chr,
movement: Movemement
) -> None:
self.state = state
self.symbol = symbol
self.movement = movement
class Program:
data = dict()
state_count = 0
symbol_count = 0
symbol_dict = dict()
blank_symbol = 'λ'
def __init__(self, blank_symbol: chr = 'λ') -> None:
self.blank_symbol = blank_symbol
def is_blank(self, symbol) -> bool:
return symbol == self.blank_symbol
def set(self, state, symbol, action) -> None:
if state not in self.data:
self.data[state] = dict()
self.state_count += 1
self.symbol_dict[symbol] = None
self.data[state][symbol] = action
def get(self, state, symbol) -> Action or None:
if symbol not in self.data[state]:
return None
return self.data[state][symbol]
def get_symbols(self):
return list(self.symbol_dict.keys())
class Emulator(): class Emulator():
''' """
A class used to emulate an Turing machine A class used to emulate an Turing machine
''' """
def __init__(self, instructions, input_string: str = '', blank_symbol: chr = 'λ'): class Tape:
l_index = 0
r_index = 0
def __init__(self, input_string: str, blank_symbol: str) -> None:
self.tape = SortedDict(dict(enumerate(input_string)))
self.blank_symbol = blank_symbol
r_index = len(input_string)
pass
def __deepcopy__(self, memo):
id_self = id(self)
_copy = memo.get(id_self)
if _copy is None:
_copy = type(self)('', self.blank_symbol)
_copy.tape = deepcopy(self.tape)
_copy.blank_symbol = self.blank_symbol
_copy.l_index = self.l_index
_copy.r_index = self.r_index
memo[id_self] = _copy
return _copy
def __setitem__(self, key, value):
self.tape[key] = value
self.l_index = min(self.l_index, key)
self.r_index = max(self.r_index, key)
def __getitem__(self, key):
if key not in self.tape:
return self.blank_symbol
return self.tape[key]
def values(self):
return self.tape.values()
def __init__(self, program: Program, input_string: str = ''):
self.head = 0 self.head = 0
self.current_state = 0 self.state = 0
self.blank_symbol = blank_symbol
self.instructions = instructions
self.stopped = False self.stopped = False
self.tape = defaultdict(lambda: self.blank_symbol, dict(enumerate(input_string))) self.program = program
self.tape = Emulator.Tape(input_string, program.blank_symbol)
def step(self): def step(self):
if self.stopped == True: if self.stopped is True:
raise RuntimeError('Turing machine is stopped!') raise RuntimeError('Turing machine is stopped!')
symbol = self.tape[self.head]
try: try:
symbol, direction, state = self.instructions[symbol][self.current_state] action = self.program.get(self.state, self.tape[self.head])
self.tape[self.head] = symbol
self.current_state = state self.tape[self.head] = action.symbol
self.head += DIRECTIONS[direction] self.state = action.state
self.head += int(action.movement)
self.tape[self.head] = self.tape[self.head] self.tape[self.head] = self.tape[self.head]
if self.head < 0:
self.tape = defaultdict(lambda: self.blank_symbol, (sorted(self.tape.items())))
if self.current_state == -1: if self.state == -1:
self.stopped = True self.stopped = True
except: except Exception as err:
self.stopped = True self.stopped = True
print('Error', err)
def info(self): def info(self):
return [self.head, self.current_state, defaultdict(lambda: self.blank_symbol, self.tape)] return [
self.head,
self.state,
deepcopy(self.tape),
]

View File

@ -1,60 +0,0 @@
import pydot
import requests
import shutil
def get_image(filename, program):
g = pydot.Dot('my_graph', nodesep=0.5)
g.set_node_defaults(
width='1.5',
fontsize='18',
shape='circle'
)
rows = 0
cols = len(program.keys())
for x in program.keys():
if len(list(program[x].keys())) == 0:
continue
rows = max(list(program[x].keys())[-1] + 1, rows)
program_table = [None] * rows
for i in range(rows):
program_table[i] = [None] * cols
symbols = [None] * cols
for j, (symbol, value) in enumerate(program.items()):
symbols[j] = symbol
for i, (state, action) in enumerate(value.items()):
program_table[state][j] = action
g.add_node(pydot.Node(f'gz', label='<g<SUB>z</SUB>>', shape='circle'))
for i in range(rows):
g.add_node(pydot.Node(f'g{i}', label=f'<g<SUB>{i}</SUB>>', shape='circle'))
for j in range(cols):
action = program_table[i][j]
if action is None: continue
symbol, direction, state = action
label = symbols[j] + '/' + symbol + '/' + ('R' if direction == '>' else 'L')
src = f'g{i}'
dst = f'g{"z" if state == -1 else state}'
if src == dst:
src = src + ':ne'
dst = dst + ':se'
g.add_edge(pydot.Edge(src, dst, label = label))
g.write_raw(f'{filename}.dot', encoding='utf-8')
try:
g.write_svg(f'{filename}.svg', encoding='utf-8')
except Exception:
output_raw_dot = g.to_string()
img_data = requests.get("https://quickchart.io/graphviz?graph=" + output_raw_dot).content
with open(f'{filename}_quickchart.svg', 'wb') as handler:
handler.write(img_data)

View File

@ -1,78 +1,38 @@
import csv
import re import re
from turinglab.emulator import Program, Movemement, Action
def from_tur(filename):
f = open(filename, "rb")
raw = []
byte = f.read(1)
while byte:
raw.append(byte)
byte = f.read(1)
indx = [0]
empty = [b'\x00', b'\x00', b'\x00']
for i in range(len(raw) - 2):
if raw[i:i+3] == empty:
indx.append(i)
indx.append(i+3)
data = []
for i in range(1, len(indx)):
packet = raw[indx[i - 1]:indx[i]]
if packet != empty:
data.append(packet)
task = b''.join(data[1]).decode('cp1251')
description = b''.join(data[4]).decode('cp1251')
solution = b''.join(data[3][:-2]).decode('cp1251')
solution = list(csv.reader(solution.split('\r\n'), delimiter='\t'))
header = solution[:1][0][1:]
solution_dict = [dict() for x in range(len(header))]
for x in solution[1:]:
symbol = x[0] if x[0] != ' ' else 'λ'
for (i, action) in enumerate(x[1:]):
action = list(action)
if len(action) == 0:
continue
if action[0] == '_':
action[0] = 'λ'
action[2] = int(action[2]) - 1
solution_dict[i][symbol] = action
return solution_dict, task, description
def from_file(filename: str): def from_file(filename: str):
f = open(filename, "r") f = open(filename, "r")
program = dict() program = Program(blank_symbol="_")
# program = dict()
for line in f: for line in f:
symbol, *actions = line.replace("\n", "").split('\t') symbol, *actions = line.replace("\n", "").split('\t')
if symbol == " ": if symbol == " ":
symbol = "λ" symbol = "_"
program[symbol] = dict()
for i, action in enumerate(actions): for i, action in enumerate(actions):
if not action: if not action:
continue continue
new_symbol, direction, new_state = re.split('([<|>|.])', action) new_symbol, direction, new_state = re.split('([<|>|.])', action)
if new_symbol == "_":
new_symbol = 'λ'
new_state = int(new_state) - 1 new_state = int(new_state) - 1
program[symbol][i] = ([new_symbol, direction, new_state]) movement = {
'>': Movemement.R,
'.': Movemement.E,
'<': Movemement.L
}[direction]
program.set(i, symbol, Action(new_state, new_symbol, movement))
# program[symbol][i] = ([new_symbol, direction, new_state])
f.close() f.close()
return program return program

View File

@ -1,107 +1,123 @@
from turinglab.image import get_image import pydot
import requests
import shutil
from os import path
from docx import Document from docx import Document
from docx.oxml import OxmlElement from docx.shared import Pt
from docx.oxml.ns import qn
from docx.shared import Pt, Inches
from docx.enum.text import WD_LINE_SPACING from docx.enum.text import WD_LINE_SPACING
from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.enum.text import WD_ALIGN_PARAGRAPH
from turinglab.emulator import Movemement, Program
def add_state(p, state):
p.add_run('q')
index_text = p.add_run(str('z' if state == -1 else state))
index_text.font.subscript = True
def create_paragraph(document): def output(
p = document.add_paragraph() directory,
style_paragraph(p) program: Program,
return p tests,
docx_name="report",
graph_name="graph"
):
"""Create output of a program and tests. It's includes a docx file with
a 'command system', 'functional table' and tests.
"""
def style_paragraph(p): def create_paragraph(document, text=""):
p.paragraph_format.space_after = Pt(0) p = document.add_paragraph(text)
p.paragraph_format.space_before = Pt(0) style_paragraph(p)
p.paragraph_format.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE return p
def style_paragraph(p):
p.paragraph_format.space_after = Pt(0)
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
def state_index(state):
return str('z' if state == -1 else state)
def add_state(p, state):
p.add_run('q')
index_text = p.add_run(state_index(state))
index_text.font.subscript = True
def get_symbol(x):
return x if program.is_blank(x) is False else 'λ'
def to_docx(filename, program, data):
document = Document() document = Document()
style = document.styles['Normal']
font = style.font
font.name = 'Times New Roman'
font.size = Pt(14)
rows = 0 #
cols = len(program.keys()) # Command system
#
create_paragraph(document, "Система команд:")
for x in program.keys(): symbols = program.get_symbols()
if len(list(program[x].keys())) == 0:
continue
rows = max(list(program[x].keys())[-1] + 1, rows)
program_table = [None] * rows cols = len(symbols)
rows = program.state_count
program_table = [[[None] for n in range(cols)] for m in range(rows)]
for i in range(rows): for i in range(rows):
program_table[i] = [None] * cols for j in range(cols):
program_table[i][j] = program.get(i, symbols[j])
symbols = [None] * cols
for j, (symbol, value) in enumerate(program.items()):
symbols[j] = symbol
for i, (state, action) in enumerate(value.items()):
program_table[state][j] = action
for i in range(rows): for i in range(rows):
for j in range(cols): for j in range(cols):
action = program_table[i][j] action = program_table[i][j]
if action is None: continue if action is None:
continue
p = create_paragraph(document) p = create_paragraph(document)
add_state(p, i) add_state(p, i)
symbol, direction, state = action movement = Movemement(action.movement).name
p.add_run(symbols[j] + '') p.add_run(get_symbol(symbols[j]) + '')
add_state(p, state) add_state(p, action.state)
p.add_run(symbol + ('R' if direction == '>' else 'L')) p.add_run(get_symbol(action.symbol) + movement)
#
p = create_paragraph(document) # Functional table
#
create_paragraph(document)
create_paragraph(document, "Функциональная таблица:")
table = document.add_table(rows + 1, cols + 1) table = document.add_table(rows + 1, cols + 1)
table.style = 'Table Grid' table.style = 'Table Grid'
table.autofit = True table.autofit = True
table.allow_autofit = True table.allow_autofit = True
for i in range(0, rows): for i in range(0, rows):
p = table.rows[i + 1].cells[0].paragraphs[0] p = table.rows[i + 1].cells[0].paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.alignment = WD_ALIGN_PARAGRAPH.CENTER
style_paragraph(p) style_paragraph(p)
add_state(p, i) add_state(p, i)
for i, x in enumerate(program.keys()): for i, x in enumerate(symbols):
p = table.rows[0].cells[i + 1].paragraphs[0] p = table.rows[0].cells[i + 1].paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.alignment = WD_ALIGN_PARAGRAPH.CENTER
style_paragraph(p) style_paragraph(p)
p.add_run(x) p.add_run(get_symbol(x))
for i in range(rows): for i in range(rows):
for j in range(cols): for j in range(cols):
action = program_table[i][j] action = program_table[i][j]
if action is None: continue if action is None:
continue
p = table.rows[i + 1].cells[j + 1].paragraphs[0] p = table.rows[i + 1].cells[j + 1].paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.alignment = WD_ALIGN_PARAGRAPH.CENTER
style_paragraph(p) style_paragraph(p)
symbol, direction, state = action movement = Movemement(action.movement).name
add_state(p, state) add_state(p, action.state)
p.add_run(symbol) p.add_run(get_symbol(action.symbol) + movement)
p.add_run('R' if direction == '>' else 'L')
#
# Tests
#
p = create_paragraph(document) p = create_paragraph(document)
for i, test in enumerate(data): for i, test in enumerate(tests):
p = create_paragraph(document) p = create_paragraph(document)
p.add_run(f'Test {i + 1}:') p.add_run(f'Тест {i + 1}')
for j, data in enumerate(test): for j, data in enumerate(test):
p = create_paragraph(document) p = create_paragraph(document)
@ -113,15 +129,76 @@ def to_docx(filename, program, data):
p.add_run(': ') p.add_run(': ')
head, state, tape = data head, state, tape = data
offset = list(tape.keys())[0]
head -= offset
tape_str = ''.join(tape.values()) tape_str = ''.join(tape.values())
head -= tape.l_index
p.add_run(tape_str[:head].lstrip('λ') + 'q') left_str = tape_str[:head].lstrip(program.blank_symbol)
left_str = left_str.replace(program.blank_symbol, 'λ')
right_str = tape_str[head + 1:].rstrip(program.blank_symbol)
right_str = right_str.replace(program.blank_symbol, 'λ')
index_text = p.add_run(str('z' if state == -1 else state)) p.add_run(left_str)
index_text.font.subscript = True add_state(p, state)
p.add_run(get_symbol(tape_str[head]))
p.add_run(right_str)
p.add_run(tape_str[head] + tape_str[head + 1:].rstrip('λ')) document.save(path.join(directory, docx_name + ".docx"))
document.save(filename) #
# Image
#
g = pydot.Dot("mygraph", nodesep=0.75)
g.set_node_defaults(
width='1.5',
fontsize='40',
shape='circle'
)
g.set_edge_defaults(
fontsize='20'
)
end_node = pydot.Node(f'gz', label='<g<SUB>z</SUB>>', shape='circle')
g.add_node(end_node)
for i in range(rows):
node = pydot.Node(f'g{i}', label=f'<g<SUB>{i}</SUB>>', shape='circle')
g.add_node(node)
for j in range(cols):
action = program_table[i][j]
if action is None:
continue
movement = Movemement(action.movement).name
label = get_symbol(symbols[j]) +\
'/' + get_symbol(action.symbol) +\
'/' + movement
src = f'g{i}'
dst = f'g{state_index(action.state)}'
if src == dst:
src = src + ':ne'
dst = dst + ':se'
g.add_edge(pydot.Edge(src, dst, label=label))
dot_path = path.join(directory, f'{graph_name}.dot')
g.write_raw(dot_path, encoding='utf-8')
try:
svg_path = path.join(directory, f'{graph_name}.svg')
g.write_svg(svg_path, encoding='utf-8')
except Exception:
output_raw_dot = g.to_string()
img_data = requests.get(
"https://quickchart.io/graphviz?graph=" + output_raw_dot
).content
svg_path = path.join(directory, f'{graph_name}_quickchart.svg')
with open(svg_path, 'wb') as handler:
handler.write(img_data)