-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Resolves #6 implement asm translator with documentation #7
Changes from all commits
6d323b0
18bb62b
48fe030
4d4d984
f694ab7
cb30fa4
20b4098
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# Язык программирования Asm | ||
|
||
Синтаксис в расширенной БНФ. | ||
|
||
- `[ ... ]` -- вхождение 0 или 1 раз | ||
- `{ ... }` -- повторение 0 или несколько раз | ||
- `{ ... }-` -- повторение 1 или несколько раз | ||
|
||
``` ebnf | ||
program ::= { line } | ||
|
||
line ::= label [ comment ] "\n" | ||
| instr [ comment ] "\n" | ||
| [ comment ] "\n" | ||
|
||
label ::= label_name ":" | ||
|
||
instr ::= op0 | ||
| op1 label_name | ||
|
||
op0 ::= "increment" | ||
| "decrement" | ||
| "right" | ||
| "left" | ||
| "print" | ||
| "input" | ||
|
||
op1 ::= "jmp" | ||
| "jz" | ||
|
||
integer ::= [ "-" ] { <any of "0-9"> }- | ||
|
||
label_name ::= <any of "a-z A-Z _"> { <any of "a-z A-Z 0-9 _"> } | ||
|
||
comment ::= ";" <any symbols except "\n"> | ||
``` | ||
|
||
Поддерживаются однострочные комментарии, начинающиеся с `;`. | ||
|
||
Операции: | ||
|
||
- `increment` -- увеличить значение в текущей ячейке на 1 | ||
- `decrement` -- уменьшить значение в текущей ячейке на 1 | ||
- `right` -- перейти к следующей ячейке | ||
- `left` -- перейти к предыдущей ячейке | ||
- `print` -- напечатать значение из текущей ячейки (символ) | ||
- `input` -- ввести извне значение и сохранить в текущей ячейке (символ) | ||
- `jmp addr` -- безусловный переход по заданному адресу или метке | ||
- `jz addr` -- условный переход по заданному адресу или метке, если значение текущей ячейки ноль | ||
|
||
Перед операцией можно определить метку: | ||
|
||
``` asm | ||
label: increment | ||
``` | ||
|
||
И в другом месте (неважно, до или после определения) сослаться на эту метку: | ||
|
||
``` asm | ||
jmp label ; --> `jmp 123`, где 123 - номер инструкции после объявления метки | ||
``` | ||
|
||
Транслятор поставит на место использования метки адрес той инструкции, перед которой она определена. | ||
|
||
В программе не может быть дублирующихся меток, определенных в разных местах с одним именем. | ||
|
||
Пример кода на Asm см. в файле [./examples/cat.asm](./examples/cat.asm). | ||
|
||
## Транслятор | ||
|
||
Интерфейс командной строки: `translator_asm.py <input_file> <target_file>` | ||
|
||
Реализован в модуле [translator_asm](./translator_asm.py) | ||
|
||
Ассемблерные мнемоники один в один транслируются в машинные команды. | ||
|
||
Метки из программы исчезают, а на место обращений к ним подставляются конкретные адреса. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two paragraphs? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
; Данный пример генерирует идентичный машинный код, что и программа на brainfuck: | ||
; | ||
; ,[.,] | ||
; | ||
; Каждый символ brainfuck соответствует одной инструкции на Asm. | ||
input ; , | ||
loop: | ||
jz break ; [ | ||
print ; . | ||
input ; , | ||
jmp loop ; ] | ||
break: | ||
halt ; конец файла |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
lang: asm | ||
in_source: |- | ||
; Данный пример генерирует идентичный машинный код, что и программа на brainfuck: | ||
; | ||
; ,[.,] | ||
; | ||
; Каждый символ brainfuck соответствует одной инструкции на Asm. | ||
input ; , | ||
loop: | ||
jz break ; [ | ||
print ; . | ||
input ; , | ||
jmp loop ; ] | ||
break: | ||
halt ; конец файла | ||
in_stdin: |- | ||
foo | ||
out_code: |- | ||
[{"index": 0, "opcode": "input", "term": [6, 0, "input"]}, | ||
{"index": 1, "opcode": "jz", "arg": 5, "term": [8, 0, "jz break"]}, | ||
{"index": 2, "opcode": "print", "term": [9, 0, "print"]}, | ||
{"index": 3, "opcode": "input", "term": [10, 0, "input"]}, | ||
{"index": 4, "opcode": "jmp", "arg": 1, "term": [11, 0, "jmp loop"]}, | ||
{"index": 5, "opcode": "halt", "term": [13, 0, "halt"]}] | ||
out_stdout: | | ||
source LoC: 13 code instr: 6 | ||
============================================================ | ||
foo | ||
instr_counter: 11 ticks: 21 | ||
out_log: | | ||
DEBUG machine:simulation TICK: 0 PC: 0 ADDR: 0 MEM_OUT: 0 ACC: 0 input ('input'@6:0) | ||
DEBUG machine:signal_wr input: 'f' | ||
DEBUG machine:simulation TICK: 2 PC: 1 ADDR: 0 MEM_OUT: 102 ACC: 0 jz 5 ('jz break'@8:0) | ||
DEBUG machine:simulation TICK: 4 PC: 2 ADDR: 0 MEM_OUT: 102 ACC: 102 print ('print'@9:0) | ||
DEBUG machine:signal_output output: '' << 'f' | ||
DEBUG machine:simulation TICK: 6 PC: 3 ADDR: 0 MEM_OUT: 102 ACC: 102 input ('input'@10:0) | ||
DEBUG machine:signal_wr input: 'o' | ||
DEBUG machine:simulation TICK: 8 PC: 4 ADDR: 0 MEM_OUT: 111 ACC: 102 jmp 1 ('jmp loop'@11:0) | ||
DEBUG machine:simulation TICK: 9 PC: 1 ADDR: 0 MEM_OUT: 111 ACC: 102 jz 5 ('jz break'@8:0) | ||
DEBUG machine:simulation TICK: 11 PC: 2 ADDR: 0 MEM_OUT: 111 ACC: 111 print ('print'@9:0) | ||
DEBUG machine:signal_output output: 'f' << 'o' | ||
DEBUG machine:simulation TICK: 13 PC: 3 ADDR: 0 MEM_OUT: 111 ACC: 111 input ('input'@10:0) | ||
DEBUG machine:signal_wr input: 'o' | ||
DEBUG machine:simulation TICK: 15 PC: 4 ADDR: 0 MEM_OUT: 111 ACC: 111 jmp 1 ('jmp loop'@11:0) | ||
DEBUG machine:simulation TICK: 16 PC: 1 ADDR: 0 MEM_OUT: 111 ACC: 111 jz 5 ('jz break'@8:0) | ||
DEBUG machine:simulation TICK: 18 PC: 2 ADDR: 0 MEM_OUT: 111 ACC: 111 print ('print'@9:0) | ||
DEBUG machine:signal_output output: 'fo' << 'o' | ||
DEBUG machine:simulation TICK: 20 PC: 3 ADDR: 0 MEM_OUT: 111 ACC: 111 input ('input'@10:0) | ||
WARNING machine:simulation Input buffer is empty! | ||
INFO machine:simulation output_buffer: 'foo' |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -22,6 +22,7 @@ | |||||||||||||||||||
import machine | ||||||||||||||||||||
import pytest | ||||||||||||||||||||
import translator | ||||||||||||||||||||
import translator_asm | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
@pytest.mark.golden_test("golden/*.yml") | ||||||||||||||||||||
|
@@ -48,6 +49,7 @@ def test_translator_and_machine(golden, caplog): | |||||||||||||||||||
|
||||||||||||||||||||
- `in_source` -- исходный код | ||||||||||||||||||||
- `in_stdin` -- данные на ввод процессора для симуляции | ||||||||||||||||||||
- `lang` -- язык: "bf" (по умолчанию) или "asm" | ||||||||||||||||||||
|
||||||||||||||||||||
Выход: | ||||||||||||||||||||
|
||||||||||||||||||||
|
@@ -60,8 +62,12 @@ def test_translator_and_machine(golden, caplog): | |||||||||||||||||||
|
||||||||||||||||||||
# Создаём временную папку для тестирования приложения. | ||||||||||||||||||||
with tempfile.TemporaryDirectory() as tmpdirname: | ||||||||||||||||||||
# Определяем язык golden теста. По умолчанию предполагается язык Brainfuck | ||||||||||||||||||||
lang = golden.get("lang", "bf") | ||||||||||||||||||||
assert lang == "bf" or lang == "asm" | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can be a little bit more simple. Just pass: If both or none -- just fail the test. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like this code will be a little bigger, if I get you right
And in this case we also need to add some logic about referencing the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just an idea
Comment on lines
+66
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||
|
||||||||||||||||||||
# Готовим имена файлов для входных и выходных данных. | ||||||||||||||||||||
source = os.path.join(tmpdirname, "source.bf") | ||||||||||||||||||||
source = os.path.join(tmpdirname, "source." + lang) | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
No body see this files... |
||||||||||||||||||||
input_stream = os.path.join(tmpdirname, "input.txt") | ||||||||||||||||||||
target = os.path.join(tmpdirname, "target.o") | ||||||||||||||||||||
|
||||||||||||||||||||
|
@@ -74,7 +80,10 @@ def test_translator_and_machine(golden, caplog): | |||||||||||||||||||
# Запускаем транслятор и собираем весь стандартный вывод в переменную | ||||||||||||||||||||
# stdout | ||||||||||||||||||||
with contextlib.redirect_stdout(io.StringIO()) as stdout: | ||||||||||||||||||||
translator.main(source, target) | ||||||||||||||||||||
if lang == "bf": | ||||||||||||||||||||
translator.main(source, target) | ||||||||||||||||||||
elif lang == "asm": | ||||||||||||||||||||
translator_asm.main(source, target) | ||||||||||||||||||||
Comment on lines
+83
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||
print("============================================================") | ||||||||||||||||||||
machine.main(target, input_stream) | ||||||||||||||||||||
|
||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
#!/usr/bin/python3 | ||
"""Транслятор Asm в машинный код. | ||
""" | ||
|
||
import sys | ||
|
||
from isa import Opcode, Term, write_code | ||
|
||
|
||
def get_meaningful_token(line): | ||
"""Извлекаем из строки содержательный токен (метка или инструкция), удаляем | ||
комментарии и пробелы в начале/конце строки. | ||
""" | ||
return line.split(";", 1)[0].strip() | ||
|
||
|
||
def translate_stage_1(text): | ||
"""Первый проход транслятора. Преобразование текста программы в список | ||
инструкций и определение адресов меток. | ||
|
||
Особенность: транслятор ожидает, что в строке может быть либо 1 метка, | ||
либо 1 инструкция. Поэтому: `col` заполняется всегда 0, так как не несёт | ||
смысловой нагрузки. | ||
""" | ||
code = [] | ||
labels = {} | ||
|
||
for line_num, raw_line in enumerate(text.splitlines(), 1): | ||
token = get_meaningful_token(raw_line) | ||
if token == "": | ||
continue | ||
|
||
pc = len(code) | ||
|
||
if token.endswith(":"): # токен содержит метку | ||
label = token.strip(":") | ||
assert label not in labels, "Redefinition of label: {}".format(label) | ||
labels[label] = pc | ||
elif " " in token: # токен содержит инструкцию с операндом (отделены пробелом) | ||
sub_tokens = token.split(" ") | ||
assert len(sub_tokens) == 2, "Invalid instruction: {}".format(token) | ||
mnemonic, arg = sub_tokens | ||
opcode = Opcode(mnemonic) | ||
assert opcode == Opcode.JZ or opcode == Opcode.JMP, "Only `jz` and `jnz` instructions take an argument" | ||
code.append({"index": pc, "opcode": opcode, "arg": arg, "term": Term(line_num, 0, token)}) | ||
else: # токен содержит инструкцию без операндов | ||
opcode = Opcode(token) | ||
code.append({"index": pc, "opcode": opcode, "term": Term(line_num, 0, token)}) | ||
|
||
return labels, code | ||
|
||
|
||
def translate_stage_2(labels, code): | ||
"""Второй проход транслятора. В уже определённые инструкции подставляются | ||
адреса меток.""" | ||
for instruction in code: | ||
if "arg" in instruction: | ||
label = instruction["arg"] | ||
assert label in labels, "Label not defined: " + label | ||
instruction["arg"] = labels[label] | ||
return code | ||
|
||
|
||
def translate(text): | ||
"""Трансляция текста программы на Asm в машинный код. | ||
|
||
Выполняется в два прохода: | ||
|
||
1. Разбор текста на метки и инструкции. | ||
|
||
2. Подстановка адресов меток в операнды инструкции. | ||
""" | ||
labels, code = translate_stage_1(text) | ||
code = translate_stage_2(labels, code) | ||
|
||
# ruff: noqa: RET504 | ||
return code | ||
|
||
|
||
def main(source, target): | ||
"""Функция запуска транслятора. Параметры -- исходный и целевой файлы.""" | ||
with open(source, encoding="utf-8") as f: | ||
source = f.read() | ||
|
||
code = translate(source) | ||
|
||
write_code(target, code) | ||
print("source LoC:", len(source.split("\n")), "code instr:", len(code)) | ||
|
||
|
||
if __name__ == "__main__": | ||
assert len(sys.argv) == 3, "Wrong arguments: translator_asm.py <input_file> <target_file>" | ||
_, source, target = sys.argv | ||
main(source, target) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need golden tests for the translator.