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.pycodestyleEnabled": true,
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
}

View File

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

View File

@ -1,35 +1,39 @@
import os
import sys
from argparse import ArgumentParser
from turinglab.image import get_image
# from turinglab.image import get_image
from turinglab.input import from_file
from turinglab.emulator import Emulator
from turinglab.output import to_docx
from turinglab.output import output
def get_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('-t','--tests', nargs='+', type=str, help='List of input strings for tests')
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')
parser.add_argument("-t", '--tests', nargs='+', type=str,
help='List of input strings for tests')
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
def main():
parser = get_parser()
args = parser.parse_args()
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!')
return -1
os.makedirs(args.output_dir, exist_ok=True)
test_data = []
tests = []
for test in args.tests:
test = test.replace(args.empty_character, 'λ')
@ -42,10 +46,12 @@ def main():
tm.step()
data.append(tm.info())
test_data.append(data)
tests.append(data)
to_docx(os.path.join(args.output_dir, 'report.docx'), program, test_data)
get_image(os.path.join(args.output_dir, 'graph'), program)
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)
if __name__ == '__main__':

View File

@ -1,54 +1,135 @@
from collections import defaultdict
from sortedcontainers import SortedDict
from copy import copy, deepcopy
from enum import IntEnum
DIRECTIONS = {
'>': 1,
'<': -1,
'.': 0
}
class Movemement(IntEnum):
R = 1
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():
'''
"""
A class used to emulate an Turing machine
'''
"""
def __init__(self, instructions, input_string: str = '', blank_symbol: chr = 'λ'):
self.head = 0
self.current_state = 0
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
self.instructions = instructions
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.state = 0
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):
if self.stopped == True:
if self.stopped is True:
raise RuntimeError('Turing machine is stopped!')
symbol = self.tape[self.head]
try:
symbol, direction, state = self.instructions[symbol][self.current_state]
self.tape[self.head] = symbol
self.current_state = state
self.head += DIRECTIONS[direction]
action = self.program.get(self.state, self.tape[self.head])
self.tape[self.head] = action.symbol
self.state = action.state
self.head += int(action.movement)
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
except:
except Exception as err:
self.stopped = True
print('Error', err)
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,77 +1,37 @@
import csv
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):
f = open(filename, "r")
program = dict()
program = Program(blank_symbol="_")
# program = dict()
for line in f:
symbol, *actions = line.replace("\n", "").split('\t')
if symbol == " ":
symbol = "λ"
program[symbol] = dict()
symbol = "_"
for i, action in enumerate(actions):
if not action:
continue
new_symbol, direction, new_state = re.split('([<|>|.])', action)
if new_symbol == "_":
new_symbol = 'λ'
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()

View File

@ -1,70 +1,83 @@
from turinglab.image import get_image
import pydot
import requests
import shutil
from os import path
from docx import Document
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Pt, Inches
from docx.shared import Pt
from docx.enum.text import WD_LINE_SPACING
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):
p = document.add_paragraph()
def output(
directory,
program: Program,
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 create_paragraph(document, text=""):
p = document.add_paragraph(text)
style_paragraph(p)
return p
def style_paragraph(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 to_docx(filename, program, data):
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 'λ'
document = Document()
style = document.styles['Normal']
font = style.font
font.name = 'Times New Roman'
font.size = Pt(14)
#
# Command system
#
create_paragraph(document, "Система команд:")
rows = 0
cols = len(program.keys())
symbols = program.get_symbols()
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
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):
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
for j in range(cols):
program_table[i][j] = program.get(i, symbols[j])
for i in range(rows):
for j in range(cols):
action = program_table[i][j]
if action is None: continue
if action is None:
continue
p = create_paragraph(document)
add_state(p, i)
symbol, direction, state = action
movement = Movemement(action.movement).name
p.add_run(symbols[j] + '')
add_state(p, state)
p.add_run(symbol + ('R' if direction == '>' else 'L'))
p.add_run(get_symbol(symbols[j]) + '')
add_state(p, action.state)
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.style = 'Table Grid'
@ -77,31 +90,34 @@ def to_docx(filename, program, data):
style_paragraph(p)
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.alignment = WD_ALIGN_PARAGRAPH.CENTER
style_paragraph(p)
p.add_run(x)
p.add_run(get_symbol(x))
for i in range(rows):
for j in range(cols):
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.alignment = WD_ALIGN_PARAGRAPH.CENTER
style_paragraph(p)
symbol, direction, state = action
add_state(p, state)
p.add_run(symbol)
p.add_run('R' if direction == '>' else 'L')
movement = Movemement(action.movement).name
add_state(p, action.state)
p.add_run(get_symbol(action.symbol) + movement)
#
# Tests
#
p = create_paragraph(document)
for i, test in enumerate(data):
for i, test in enumerate(tests):
p = create_paragraph(document)
p.add_run(f'Test {i + 1}:')
p.add_run(f'Тест {i + 1}')
for j, data in enumerate(test):
p = create_paragraph(document)
@ -113,15 +129,76 @@ def to_docx(filename, program, data):
p.add_run(': ')
head, state, tape = data
offset = list(tape.keys())[0]
head -= offset
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))
index_text.font.subscript = True
p.add_run(left_str)
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)