Как стать автором
Обновить

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

Время на прочтение13 мин
Количество просмотров6.1K

Всем привет, сегодня мы с вами напишем компилятор на 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

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

Заключение

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

Теги:
Хабы:
+23
Комментарии3

Публикации

Работа

Data Scientist
46 вакансий

Ближайшие события