diff --git a/README.md b/README.md index 153a75f..6f10340 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,18 @@ turinglab ### Example ```bat -turinglab 0to1to0.txt 101 out.docx +turinglab 0to1to0.txt .\out -t 101 1E -e E -f +``` +or +```bat +turinglab 0to1to0.txt .\out --tests 101 1E --empty-character E --force ``` -Content of ```out.docx```: +Content of ```out\report.docx```: + +![image](https://user-images.githubusercontent.com/36362599/133917663-f725d137-e33e-4440-8640-15262120c5bb.png) + +Content of ```out\graph.svg```: + +![image](https://user-images.githubusercontent.com/36362599/133909074-e1928d40-263f-4c80-94ef-4ae495662419.png) -![image](https://user-images.githubusercontent.com/36362599/133625379-0624f6ab-b43b-40de-83e7-6495ee03c45b.png) diff --git a/turinglab/__main__.py b/turinglab/__main__.py index fb96ee2..a916508 100644 --- a/turinglab/__main__.py +++ b/turinglab/__main__.py @@ -1,15 +1,19 @@ +import os import sys from argparse import ArgumentParser +from turinglab.image import get_image from turinglab.input import from_file from turinglab.emulator import Emulator from turinglab.output import to_docx + def get_parser() -> ArgumentParser: parser = ArgumentParser() - parser.add_argument("input_file", type=str, help="Path to file with program") - parser.add_argument("input_string", type=str, help="Input symbols") - parser.add_argument("output_file", type=str, help="Output file") - + 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') return parser def main(): @@ -17,18 +21,32 @@ def main(): args = parser.parse_args() - program = from_file(args.input_file) + + if args.force == False and os.path.exists(args.output_dir): + print('Directory already exists!') + return -1 - tm = Emulator(program, args.input_string) + os.makedirs(args.output_dir, exist_ok=True) - data = [tm.info()] + test_data = [] - while not tm.stopped: - tm.step() - data.append(tm.info()) + for test in args.tests: + test = test.replace(args.empty_character, 'λ') + + tm = Emulator(program, test) + + data = [tm.info()] + + while not tm.stopped: + tm.step() + data.append(tm.info()) + + test_data.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) - to_docx(args.output_file, data) if __name__ == '__main__': main() \ No newline at end of file diff --git a/turinglab/image.py b/turinglab/image.py new file mode 100644 index 0000000..7ab4341 --- /dev/null +++ b/turinglab/image.py @@ -0,0 +1,60 @@ +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='z>', shape='circle')) + + for i in range(rows): + g.add_node(pydot.Node(f'g{i}', label=f'{i}>', 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) + \ No newline at end of file diff --git a/turinglab/input.py b/turinglab/input.py index 755201d..fa35ffc 100644 --- a/turinglab/input.py +++ b/turinglab/input.py @@ -59,16 +59,19 @@ def from_file(filename: str): if symbol == " ": symbol = "λ" - program[symbol] = [] + program[symbol] = dict() + + for i, action in enumerate(actions): + if not action: + continue - for action in actions: new_symbol, direction, new_state = list(action) if new_symbol == "_": new_symbol = 'λ' new_state = int(new_state) - 1 - program[symbol].append([new_symbol, direction, new_state]) + program[symbol][i] = ([new_symbol, direction, new_state]) f.close() diff --git a/turinglab/output.py b/turinglab/output.py index 1380a6b..5157408 100644 --- a/turinglab/output.py +++ b/turinglab/output.py @@ -1,45 +1,127 @@ +from turinglab.image import get_image from docx import Document from docx.oxml import OxmlElement from docx.oxml.ns import qn -from docx.shared import Pt +from docx.shared import Pt, Inches from docx.enum.text import WD_LINE_SPACING +from docx.enum.text import WD_ALIGN_PARAGRAPH -def to_docx(filename, data): +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() + style_paragraph(p) + 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 to_docx(filename, program, data): document = Document() - section = document.sections[0] - - sectPr = section._sectPr - cols = sectPr.xpath('./w:cols')[0] - cols.set(qn('w:num'),'2') style = document.styles['Normal'] - font = style.font font.name = 'Times New Roman' font.size = Pt(14) - for i in range(len(data)): - p = document.add_paragraph('K') + rows = 0 + cols = len(program.keys()) - p.paragraph_format.space_after = Pt(0) - p.paragraph_format.space_before = Pt(0) - p.paragraph_format.line_spacing_rule = WD_LINE_SPACING.DOUBLE + for x in program.keys(): + if len(list(program[x].keys())) == 0: + continue + rows = max(list(program[x].keys())[-1] + 1, rows) - index_text = p.add_run(str(i)) - index_text.font.subscript = True + program_table = [None] * rows - p.add_run(': ') + for i in range(rows): + program_table[i] = [None] * cols - head, state, tape = data[i] - offset = list(tape.keys())[0] - head -= offset - tape_str = ''.join(tape.values()) + symbols = [None] * cols - p.add_run(tape_str[:head].lstrip('λ') + 'q') + 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 j in range(cols): + action = program_table[i][j] + if action is None: continue - index_text = p.add_run(str('z' if state == -1 else state)) - index_text.font.subscript = True + p = create_paragraph(document) + add_state(p, i) - p.add_run(tape_str[head] + tape_str[head + 1:].rstrip('λ')) + symbol, direction, state = action + + p.add_run(symbols[j] + ' → ') + add_state(p, state) + p.add_run(symbol + ('R' if direction == '>' else 'L')) + + + p = create_paragraph(document) + + table = document.add_table(rows + 1, cols + 1) + table.style = 'Table Grid' + table.autofit = True + table.allow_autofit = True + + for i in range(0, rows): + p = table.rows[i + 1].cells[0].paragraphs[0] + p.alignment = WD_ALIGN_PARAGRAPH.CENTER + style_paragraph(p) + add_state(p, i) + + for i, x in enumerate(program.keys()): + p = table.rows[0].cells[i + 1].paragraphs[0] + p.alignment = WD_ALIGN_PARAGRAPH.CENTER + style_paragraph(p) + p.add_run(x) + + for i in range(rows): + for j in range(cols): + action = program_table[i][j] + 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') + + p = create_paragraph(document) + + for i, test in enumerate(data): + p = create_paragraph(document) + p.add_run(f'Test {i + 1}:') + + for j, data in enumerate(test): + p = create_paragraph(document) + p.add_run('K') + + index_text = p.add_run(str(j)) + index_text.font.subscript = True + + p.add_run(': ') + + head, state, tape = data + offset = list(tape.keys())[0] + head -= offset + tape_str = ''.join(tape.values()) + + p.add_run(tape_str[:head].lstrip('λ') + 'q') + + index_text = p.add_run(str('z' if state == -1 else state)) + index_text.font.subscript = True + + p.add_run(tape_str[head] + tape_str[head + 1:].rstrip('λ')) document.save(filename) \ No newline at end of file