Пишем самый примитивный компилятор на Python

от автора

Всем привет, сегодня мы с вами напишем компилятор на Python. Мало чего он будет уметь, но для начала сойдёт.

Структура компилятора

Всего 4 этапа:

  • Лексер со своими токенами

  • Парсер со своим AST

  • Сам компилятор со своими байткодом

  • И напоследок виртуальная машина

Лексер токенизирует часть строки (к примеру a = 5)

Парсер делает АСД а поток токенов создает он сам

Компилятор превращает АСД в байт код (PUSH 5 STORE a HALT)

А виртуальная машина выполняет байткод.

Правила нашего компилятора

Вот набор правил для компилятора:

  • Цикл while, условие if-else. Никакого for

  • Переменные делаются так: a = 5 если объявление последнее в строке, иначе a = 5; …

  • Максимум выражения 2+2 но не как не 2+2==4 и они разделены пробелами (2 + 2)

  • RelOp (Relation operation, реляционные операция): <, >, !=, ==

  • BinOp (Binary operation, бинарная операция): +, -, *, /

  • В цикле while и условие if-else максимум присваивание и одну

  • Переменные с любым названием

  • Команда для игнорирования («pass») и для выхода во время выполнения («exit»)

  • Один тип данных: int

Да, никаких списков, функций, словарей и другого. Вот такая BNF получилась:

<program> ::= <statement> <statement> ::= <while> | <if> | <assign> <while> ::= "while " <paren_expr> " " <statement> <if> ::= "if " <paren_expr> " " <statement> | "if " <paren_expr> " " <statement> " else " <statement> <paren_expr> ::= "(" <exp> ")" <assign> ::= <indent> " = " <exp> "; " | <indent> " = " <exp> <indent> ::= [a-z] | [A-Z] <digit> ::= [0-9] <exit> = "exit" <pass> = "pass" <exp> ::= <term> | <bexp> | <aexp> <term> ::= <digit> | <indent> <bexp> ::= <term> " > " <term> | <term> " < " <term> | <term> " == " <term> | <term> " != " <term> <aexp> ::= <term> " + " <term> | <term> " - " <term> | <term> " * " <term> | <term> " / " <term>

(если что-то не так то скажите, я мало BNF понимаю) пример программы:

a = 5; if (a < 10) a = 10; else a = 5

Пишем лексер

Как я говорил ранее, лексер токенизирует лишь один токен но из-за парсера получается поток токенов. Вот код самого лексера:

#для регулярок import re  #лексер class Lexer:     def __init__(self):         pass      #распознать терм (в нашем случае это переменная или число)     def lexterm(self, term):         #регулярка для переменной         if re.match(r'[a-zA-Z][a-zA-Z0-9_]*', term):             return ('id', term)         #регулярка для чисел         elif re.match(r'[0-9]+', term):             return ('num', term)      #возращает два терма и операцию по середине (индекс 1)     def lex_expr(self, exp):         res = exp.split(' ')         if len(res) == 1:             return (self.lexterm(res[0]), res[0])         first = self.lexterm(res[0])         second = self.lexterm(res[2])         return [first, res[1], second]          def lex_token(self, string):         #здесь результат         self.sum = None                  d = string.split(' ')         #проверка         if d[0] == 'while':             d = string.split(')')             try:                 cond = d[0][d[0].index('while') + 6:] + ')'             except:                 raise SyntaxError('Invalid paren expression')             try:                 stmt = d[1][1:]             except:                 raise SyntaxError('Invalid "while" statement')             self.sum = ("while", cond, stmt)             return None         elif d[0] == 'if':             d = string.split(')')             try:                 cond = d[0][d[0].index('if') + 3:] + ')'             except:                 raise SyntaxError('Invalid paren expression')             try:                 stmt = d[1][1:]             except:                 raise SyntaxError('Invalid "if" statement')             self.sum = ("if", cond, stmt)             return None         #остальные операторы или необязательные стэйтменты         elif d[0] == 'pass':             self.sum = ("pass", None)             return None         #иначе         elif d[0] == 'else':             stmt = string.replace('else ', '')             if stmt == '':                 raise SyntaxError('Invalid "else" statement')             self.sum = ("else", stmt)             return None         elif d[0] == 'exit':             self.sum = ('exit', None)             return None         try:             #присвание             if d[1] == '=':                 name = d[0]                 valu = d[2:]                 pos = 0                 value = ''                 while pos < len(valu):                     if pos + 1 == len(valu):                         value += valu[pos]                     else:                         value += valu[pos] + ' '                     pos += 1                 self.sum = ("assign", name, value)                 return None         except:             pass         #иначе         raise SyntaxError('Invalid syntax: '+str(string))

Почему я поместил оператор присваивания в try-except? Потому-что если его нету то будет неправильный индекс в списке «d».

Извините за такие отвратительные названия, нормального не могу придумаю.

Как использовать:

#в PyShell >>> lexer = Lexer() >>> lexer.lex_token('a = 5') >>> token = lexer.sum >>> token ('assign', 'a', '5')

Написать лексер мне было проще всего, а сложнее всего было написать парсер и компилятор (логично)

Парсер

Как работает:

На вход подается однострочная программа (и возможно не только однострочная, я не пробывал) пример:

a = 5; while (a < 10) a = a + 1

Парсер получает эти токены:

('assign', 'a', '5') ('while', '(a < 10)', 'a = a + 1')

И преобразует их в это AST:

Node('ASSIGN', 'a',      op2=Node('INT', '5') ) Node('WHILE',       Node('<', Node('ID', 'a'), op2=Node('INT', '10')),       op2=Node('ASSIGN', 'a', op2=Node('+', Node('ID', 'a'), Node('INT', '1'))   ) )

AST очень запутанный но просто скопируйте эту часть кода и кайфуйте. AST — это предпоследняя часть компиляции.

Для начало код для класса узла AST:

class Node:   def __init__(self, name, op1, op2=None, op3=None):     self.name = name     self.op1 = op1     self.op2 = op2     self.op3 = op3    def __repr__(self):     return f'Node("{self.name}"\n {self.op1}\n {self.op2}\n {self.op3})'

Параметр op3 нужен для таких условий как else.

Давайте я покажу вам код парсера:

class Parser:     def __init__(self, lexer):         self.pos = 0         self.lexer = lexer      #терм     def term(self, value):         if value[0] == 'id':             return 'ID'         elif value[0] == 'num':             return 'INT'      #выражения     def parse_expr(self, token):         try:             left = token[0]             right = token[2]             op = token[1]              result = Node(op, Node(self.term(left), left[1]), Node(self.term(right), right[1]))             return result         except:             try:                 return Node(self.term(left), left[1])             except:                 raise SyntaxError('Invalid expression')      #выражения в скобках     def paren_expr(self, expr):         d = expr[expr.index('(') + 1:expr.index(')')]         return self.parse_expr(self.lexer.lex_expr(d))      #парсинг     def parse(self, program):         try:             self.prog = program.split('; ')         except:             self.prog = program         astpos = 0         ast = []         while self.pos < len(self.prog):             if self.prog[self.pos] == '':                 self.pos += 1                 continue             d = self.prog[len(self.prog) - 1]             r = len(d) - 1             if d[r] == ';':                 raise SyntaxError('The ";" sign is not needed at the end')             self.lexer.next_token(self.prog[self.pos])             token = self.lexer.sum             if token[0] == 'assign':                 ast.append(Node('ASSIGN', token[1], op2=self.parse_expr(self.lexer.lex_expr(token[2]))))             elif token[0] == 'if':                 d = Lexer()                 r = Parser(d)                 ast.append(Node('IF', self.paren_expr(token[1]), op2=r.parse(token[2])[0]))             elif token[0] == 'while':                 d = Lexer()                 r = Parser(d)                 ast.append(Node('WHILE', self.paren_expr(token[1]), op2=r.parse(token[2])[0]))             elif token[0] == 'else':                 d = Lexer()                 r = Parser(d)                 if ast[astpos - 1].name == 'IF':                     ast[astpos - 1].name = 'ELSE'                     ast[astpos - 1].op3 = r.parse(token[1])[0]                 else:                     raise SyntaxError('Invalid "else" statement')             elif token[0] == 'exit':                 ast.append(Node('EXIT', None))             elif token[0] == 'pass':                 ast.append(Node('PASS', None))             self.pos += 1             astpos += 1         self.pos = 0         return ast

Метод term возвращает ‘ID’ если он увидел то что лексер распознал имя переменной или ‘INT’ если это число.

parse_expr — возвращает узел AST где имя это оператор а op1 это term первого токена а op2 это term второго токена. Если неудача: вернёт term первого токена

paren_expr — находить выражение в скобках и возвращает parse_expr

parse — сам парсер, находит все части программы и парсит их. А точнее наша программа с циклом while (a = 5; while (a < 10) a = a + 1) он находит в ней не «;» а «;» с пробелом. И наша программа становится такой:

['a = 5', 'while (a < 10) a = a + 1']

Дальше если парсер узнает что в последнем элементе, в последнем индексе не «;» то он проходится по каждой части и получает результат лексера для каждой части. Иначе: говорить что-то по типу этого: «зачем тебе «;» в конце?».

Дальше по этим токенам он находит подходящий узел. Если это «else» то он будет обязан найти if в прошлом узле (для этого нужна переменная astpos). Потом обновляем позицию и возвращаем список с AST.

Компилятор

Эта часть главная ведь это компилятор. Вот инструкции:

FETCH переменная - положить на стек значение переменной PUSH число - положить на стек число POP - я не помню зачем это ведь это не нужно будет нам ADD, SUB, MUL, DIV - бинарные операции NOTEQ, EQ, LT, GT - реляционные операции JMP адрес - перейти по адресу JNZ адрес - перейти по адресу если на вершине стека не 0 JZ адрес - перейти по адресу если на вершине стека 0 PASS - игнорировать STORE имя - сделать переменную HALT - конец программы

Теперь определим опкоды для инструкций:

FETCH, STORE, PUSH, POP, ADD, SUB, MUL, DIV, LT, GT, EQ, NOTEQ, JZ, JNZ, JMP, PASS, HALT = range(17)

Вот допустим у нас выражение «a = 1 + 2», и компилятор создаёт эти инструкции:

PUSH 1 PUSH 2 ADD STORE a

Но. Это только визуально. По нашем опкодам это будет так:

[2, 1, 2, 2, 4, 1, 'a']

2 это PUSH

4 это ADD

1 это STORE.

И вот код компилятора:

class Compiler:     def __init__(self):         self.program = []         self.pc = 0      def gen(self, command):         self.program.append(command)         self.pc = self.pc + 1      def compilenode(self, node):         name = node.name         if name == 'INT':             self.gen(PUSH)             self.gen(int(node.op1))         elif name == 'ID':             self.gen(FETCH)             self.gen(node.op1)         elif name == '+':             self.compilenode(node.op1)             self.compilenode(node.op2)             self.gen(ADD)         elif name == '-':             self.compilenode(node.op1)             self.compilenode(node.op2)             self.gen(SUB)         elif name == '/':             self.compilenode(node.op1)             self.compilenode(node.op2)             self.gen(DIV)         elif name == '*':             self.compilenode(node.op1)             self.compilenode(node.op2)             self.gen(MUL)         elif name == '<':             self.compilenode(node.op1)             self.compilenode(node.op2)             self.gen(LT)         elif name == '>':             self.compilenode(node.op1)             self.compilenode(node.op2)             self.gen(GT)         elif name == '!=':             self.compilenode(node.op1)             self.compilenode(node.op2)             self.gen(NOTEQ)         elif name == '==':             self.compilenode(node.op1)             self.compilenode(node.op2)             self.gen(EQ)         elif name == 'ASSIGN':             self.compilenode(node.op2)             self.gen(STORE)             self.gen(node.op1)         elif name == 'IF':             self.compilenode(node.op1)             self.gen(JZ)             addr = self.pc             self.gen(0)             self.compilenode(node.op2)             self.program[addr] = self.pc         elif name == 'ELSE':             self.compilenode(node.op1)             self.gen(JZ)             addr1 = self.pc             self.gen(0)             self.compilenode(node.op2)             self.gen(JMP)             addr2 = self.pc             self.gen(0)             self.program[addr1] = self.pc             self.compilenode(node.op3)             self.program[addr2] = self.pc         elif name == 'WHILE':             if node.op1.op2:                 try:                     r = node.op1.op2                     d = str(int(r.op1) - 1)                     r.op1 = d                 except:                     r = node.op1.op1                     d = str(int(r.op1) - 1)                     r.op1 = d             addr1 = self.pc             self.compilenode(node.op1)             self.gen(JZ)             addr2 = self.pc             self.gen(1)             self.compilenode(node.op2)             self.gen(JMP)             self.gen(addr1)             self.program[addr2] = self.pc         elif name == 'EXIT':             self.gen(HALT)         elif name == 'PASS':             self.gen(PASS)                          def compileast(self, ast):         for i in ast:             self.compilenode(i)         self.gen(HALT)         return self.program

Как вы можете заместить в обработке WHILE, для чисел я отнимаю одно число у этого числа, почему? Потому что когда я делал это то while делал не 5 а 6 раз. И, спасибо статье Пишем примитивный и никому не нужный компилятор / Хабр (не реклама) за то что она дала мне понятие написания if, while, else. Весь компилятор находится в compileast. gen добавляет опкод. А compilenode это главная функция для компиляции.

Теперь мы можем побаловаться с выводом. К примеру:

#в PyShell >>> lexer = Lexer() >>> parser = Parser(lexer) >>> compiler = Compiler() >>>  >>> ast = parser.parse('a = 5; b = 5') >>> print(compiler.compileast(ast)) [2, 5, 1, 'a', 2, 5, 1, 'b'] >>> 

Но теперь перейдём к концу.

Виртуальная машина

И конец нашей поделки, виртуальная машина!

Все мы знаем то что байткод сам себя не выполнит. Для этого нужна виртуальная машина. Вот код виртуальной машины:

class VirtualMachine:     def __init__(self):         pass          def run(self, program):         env = {}         stack = []         pc = 0         while True:             op = program[pc]             if pc < len(program) - 1:                 arg = program[pc + 1]              if op == FETCH:                 try:                     stack.append(env[arg])                 except:                     stack.append(0)                 pc += 2             elif op == STORE:                 env[arg] = stack.pop()                 pc += 2             elif op == PUSH:                 stack.append(arg)                 pc += 2             elif op == ADD:                 stack[-2] += stack[-1]                 stack.pop()                 pc += 1             elif op == SUB:                 stack[-2] -= stack[-1]                 stack.pop()                 pc += 1             elif op == MUL:                 stack[-2] *= stack[-1]                 stack.pop()                 pc += 1             elif op == DIV:                 stack[-2] /= stack[-1]                 stack.pop()                 pc += 1             elif op == LT:                 if stack[-2] <= stack[-1]:                     stack[-2] = 1                 else:                     stack[-2] = 0                 stack.pop()                 pc += 1             elif op == GT:                 if stack[-2] >= stack[-1]:                     stack[-2] = 1                 else:                     stack[-2] = 0                 stack.pop()                 pc += 1             elif op == EQ:                 if stack[-2] == stack[-1]:                     stack[-2] = 1                 else:                     stack[-2] = 0                 stack.pop()                 pc += 1             elif op == NOTEQ:                 if stack[-2] != stack[-1]:                     stack[-2] = 1                 else:                     stack[-2] = 0                 stack.pop()                 pc += 1             elif op == POP:                 stack.append(arg)                 stack.pop()                 pc += 1             elif op == JZ:                 if stack.pop() == 0:                     pc = arg                 else:                     pc += 2             elif op == JNZ:                    if stack.pop() != 0:                       pc = arg                   else:                       pc += 2             elif op == JMP:                 pc = arg             elif op == PASS:                 pc += 1             elif op == HALT:                 break          print('Execution finished')         for i in env.keys():             print(i+': '+str(env[i])) 

Описываю этот код:

Переменная stack это сам стек ведь наша машина стековая.

Переменная env это словарь переменных.

Получаем аргумент если он есть. И выполняем опкод. В конце порадуем пользователя что всё работает и радостно выводим ключ и значение в env.

Теперь когда мы баловались с выводом в компиляторе то мы можем выполнить этот код:

>>> bytecode = compiler.compileast(ast) >>> vm = VirtualMachine() >>> vm.run(bytecode) Execution finished a: 5 b: 5 >>> 

Теперь давайте в бонус сделаем командную строку.

import sys  def run_compiler(prog):     lexer = Lexer()     parser = Parser(lexer)     ast = parser.parse(prog)      compiler = Compiler()     bytecode = compiler.compileast(ast)      vm = VirtualMachine()     vm.run(bytecode)  def cli():     while True:         com = input('>>> ')         try:             run_compiler(com)         except Exception as err:             print('Error: ', end="")             sys.stderr.write(str(err)+'\n')  if __name__ == '__main__':     cli()

Тут объяснений не надо ведь тут и так понятно.

Весь код

import re import sys  FETCH, STORE, PUSH, POP, ADD, SUB, MUL, DIV, LT, GT, EQ, NOTEQ, JZ, JNZ, JMP, PASS, HALT = range(17)  class Lexer:     def __init__(self):         pass      def lexterm(self, term):         if re.match(r'[a-zA-Z][a-zA-Z0-9_]*', term):             return ('id', term)         elif re.match(r'[0-9]+', term):             return ('num', term)      def lex_expr(self, exp):         res = exp.split(' ')         if len(res) == 1:             return (self.lexterm(res[0]), res[0])         first = self.lexterm(res[0])         second = self.lexterm(res[2])         return [first, res[1], second]          def next_token(self, string):         self.sum = None                  d = string.split(' ')         if d[0] == 'while':             d = string.split(')')             try:                 cond = d[0][d[0].index('while') + 6:] + ')'             except:                 raise SyntaxError('Invalid paren expression')             try:                 stmt = d[1][1:]             except:                 raise SyntaxError('Invalid "while" statement')             self.sum = ("while", cond, stmt)             return None         elif d[0] == 'if':             d = string.split(')')             try:                 cond = d[0][d[0].index('if') + 3:] + ')'             except:                 raise SyntaxError('Invalid paren expression')             try:                 stmt = d[1][1:]             except:                 raise SyntaxError('Invalid "if" statement')             self.sum = ("if", cond, stmt)             return None         elif d[0] == 'pass':             self.sum = ("pass", None)             return None         elif d[0] == 'else':             stmt = string.replace('else ', '')             if stmt == '':                 raise SyntaxError('Invalid "else" statement')             self.sum = ("else", stmt)             return None         elif d[0] == 'exit':             self.sum = ('exit', None)             return None         try:             if d[1] == '=':                 name = d[0]                 valu = d[2:]                 pos = 0                 value = ''                 while pos < len(valu):                     if pos + 1 == len(valu):                         value += valu[pos]                     else:                         value += valu[pos] + ' '                     pos += 1                 self.sum = ("assign", name, value)                 return None         except:             pass         raise SyntaxError('Invalid syntax: '+str(string))  class Node:     def __init__(self, name, op1, op2=None, op3=None):         self.name = name         self.op1 = op1         self.op2 = op2         self.op3 = op3      def __repr__(self):         return f"Node(\n name='{self.name}',\n op1={self.op1},\n op2={self.op2},\n op3={self.op3}\n)"  class Parser:     def __init__(self, lexer):         self.pos = 0         self.lexer = lexer      def term(self, value):         if value[0] == 'id':             return 'ID'         elif value[0] == 'num':             return 'INT'      def parse_expr(self, token):         try:             left = token[0]             right = token[2]             op = token[1]              result = Node(op, Node(self.term(left), left[1]), Node(self.term(right), right[1]))             return result         except:             try:                 return Node(self.term(left), left[1])             except:                 raise SyntaxError('Invalid expression')      def paren_expr(self, expr):         d = expr[expr.index('(') + 1:expr.index(')')]         return self.parse_expr(self.lexer.lex_expr(d))      def parse(self, program):         try:             self.prog = program.split('; ')         except:             self.prog = program         astpos = 0         ast = []         while self.pos < len(self.prog):             if self.prog[self.pos] == '':                 self.pos += 1                 continue             d = self.prog[len(self.prog) - 1]             r = len(d) - 1             if d[r] == ';':                 raise SyntaxError('The ";" sign is not needed at the end')             self.lexer.next_token(self.prog[self.pos])             token = self.lexer.sum             if token[0] == 'assign':                 ast.append(Node('ASSIGN', token[1], op2=self.parse_expr(self.lexer.lex_expr(token[2]))))             elif token[0] == 'if':                 d = Lexer()                 r = Parser(d)                 ast.append(Node('IF', self.paren_expr(token[1]), op2=r.parse(token[2])[0]))             elif token[0] == 'while':                 d = Lexer()                 r = Parser(d)                 ast.append(Node('WHILE', self.paren_expr(token[1]), op2=r.parse(token[2])[0]))             elif token[0] == 'else':                 d = Lexer()                 r = Parser(d)                 if ast[astpos - 1].name == 'IF':                     ast[astpos - 1].name = 'ELSE'                     ast[astpos - 1].op3 = r.parse(token[1])[0]                 else:                     raise SyntaxError('Invalid "else" statement')             elif token[0] == 'exit':                 ast.append(Node('EXIT', None))             elif token[0] == 'pass':                 ast.append(Node('PASS', None))             self.pos += 1             astpos += 1         self.pos = 0         return ast  class Compiler:     def __init__(self):         self.program = []         self.pc = 0      def gen(self, command):         self.program.append(command)         self.pc = self.pc + 1      def compilenode(self, node):         name = node.name         if name == 'INT':             self.gen(PUSH)             self.gen(int(node.op1))         elif name == 'ID':             self.gen(FETCH)             self.gen(node.op1)         elif name == '+':             self.compilenode(node.op1)             self.compilenode(node.op2)             self.gen(ADD)         elif name == '-':             self.compilenode(node.op1)             self.compilenode(node.op2)             self.gen(SUB)         elif name == '/':             self.compilenode(node.op1)             self.compilenode(node.op2)             self.gen(DIV)         elif name == '*':             self.compilenode(node.op1)             self.compilenode(node.op2)             self.gen(MUL)         elif name == '<':             self.compilenode(node.op1)             self.compilenode(node.op2)             self.gen(LT)         elif name == '>':             self.compilenode(node.op1)             self.compilenode(node.op2)             self.gen(GT)         elif name == '!=':             self.compilenode(node.op1)             self.compilenode(node.op2)             self.gen(NOTEQ)         elif name == '==':             self.compilenode(node.op1)             self.compilenode(node.op2)             self.gen(EQ)         elif name == 'ASSIGN':             self.compilenode(node.op2)             self.gen(STORE)             self.gen(node.op1)         elif name == 'IF':             self.compilenode(node.op1)             self.gen(JZ)             addr = self.pc             self.gen(0)             self.compilenode(node.op2)             self.program[addr] = self.pc         elif name == 'ELSE':             self.compilenode(node.op1)             self.gen(JZ)             addr1 = self.pc             self.gen(0)             self.compilenode(node.op2)             self.gen(JMP)             addr2 = self.pc             self.gen(0)             self.program[addr1] = self.pc             self.compilenode(node.op3)             self.program[addr2] = self.pc         elif name == 'WHILE':             if node.op1.op2:                 try:                     r = node.op1.op2                     d = str(int(r.op1) - 1)                     r.op1 = d                 except:                     r = node.op1.op1                     d = str(int(r.op1) - 1)                     r.op1 = d             addr1 = self.pc             self.compilenode(node.op1)             self.gen(JZ)             addr2 = self.pc             self.gen(1)             self.compilenode(node.op2)             self.gen(JMP)             self.gen(addr1)             self.program[addr2] = self.pc         elif name == 'EXIT':             self.gen(HALT)         elif name == 'PASS':             self.gen(PASS)                          def compileast(self, ast):         for i in ast:             self.compilenode(i)         self.gen(HALT)         return self.program  class VirtualMachine:     def run(self, program):         env = {}         stack = []         pc = 0         while True:             op = program[pc]             if pc < len(program) - 1:                 arg = program[pc + 1]              if op == FETCH:                 try:                     stack.append(env[arg])                 except:                     stack.append(0)                 pc += 2             elif op == STORE:                 env[arg] = stack.pop()                 pc += 2             elif op == PUSH:                 stack.append(arg)                 pc += 2             elif op == ADD:                 stack[-2] += stack[-1]                 stack.pop()                 pc += 1             elif op == SUB:                 stack[-2] -= stack[-1]                 stack.pop()                 pc += 1             elif op == MUL:                 stack[-2] *= stack[-1]                 stack.pop()                 pc += 1             elif op == DIV:                 stack[-2] /= stack[-1]                 stack.pop()                 pc += 1             elif op == LT:                 if stack[-2] <= stack[-1]:                     stack[-2] = 1                 else:                     stack[-2] = 0                 stack.pop()                 pc += 1             elif op == GT:                 if stack[-2] >= stack[-1]:                     stack[-2] = 1                 else:                     stack[-2] = 0                 stack.pop()                 pc += 1             elif op == EQ:                 if stack[-2] == stack[-1]:                     stack[-2] = 1                 else:                     stack[-2] = 0                 stack.pop()                 pc += 1             elif op == NOTEQ:                 if stack[-2] != stack[-1]:                     stack[-2] = 1                 else:                     stack[-2] = 0                 stack.pop()                 pc += 1             elif op == POP:                 stack.append(arg)                 stack.pop()                 pc += 1             elif op == JZ:                     if stack.pop() == 0:                           pc = arg                     else:                         pc += 2             elif op == JNZ:                      if stack.pop() != 0:                           pc = arg                     else:                           pc += 2             elif op == JMP:                 pc = arg             elif op == PASS:                 pc += 1             elif op == HALT:                 break          print('Execution finished')         for i in env.keys():             print(i+': '+str(env[i]))  def run_compiler(prog):     lexer = Lexer()     parser = Parser(lexer)     ast = parser.parse(prog)      compiler = Compiler()     bytecode = compiler.compileast(ast)      vm = VirtualMachine()     vm.run(bytecode)  def cli():     while True:         com = input('>>> ')         try:             run_compiler(com)         except Exception as err:             print('Error: ', end="")             sys.stderr.write(str(err)+'\n')  if __name__ == '__main__':     cli()

Всего 379 строчек кода. Но работает и правильно.

И вот вам примеры:

a = 5; if (a < 10) a = 10; else a = 5 a = 5; b = 5; c = a + b x = 0; while (x < 10) x = x + 1

Должно работать.

Заключение

Мы с вами написали примитивный компилятор и он будет вам не нужен потому что вы уже получили опыт написания компиляторов с собственной виртуальной машиной. И, только виртуальная машина может вам понадобится.


ссылка на оригинал статьи https://habr.com/ru/articles/901324/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *