# Notebook from jelic98/raf_pp_materials
Path: Notebooks/09_Runner.ipynb

# Vežbe 9: Runner

Runner nije deo kompajlera, već je to [interpreter](https://en.wikipedia.org/wiki/Interpreter_(computing)) koji prolaskom kroz AST indirektno izvršava izvorni kod.

![pp-01](https://i.postimg.cc/SNmFQ6X0/pp-01.png)

Autor: Lazar Jelić

Repozitorijum: https://github.com/jelic98/raf_pp_materials_____no_output_____Importovanje neophodnog modula za enumeraciju klasa tokena._____no_output_____

from enum import Enum, auto_____no_output_____

Klasa **Class** definiše sve moguće klase leksema koje se mogu naći u izvornom kodu._____no_output_____

class Class(Enum):
    PLUS = auto()
    MINUS = auto()
    STAR = auto()
    FWDSLASH = auto()
    PERCENT = auto()

    OR = auto()
    AND = auto()
    NOT = auto()

    EQ = auto()
    NEQ = auto()
    LT = auto()
    GT = auto()
    LTE = auto()
    GTE = auto()

    LPAREN = auto()
    RPAREN = auto()
    LBRACKET = auto()
    RBRACKET = auto()
    LBRACE = auto()
    RBRACE = auto()

    ASSIGN = auto()
    SEMICOLON = auto()
    COMMA = auto()

    TYPE = auto()
    INT = auto()
    CHAR = auto()
    STRING = auto()

    IF = auto()
    ELSE = auto()
    WHILE = auto()
    FOR = auto()

    BREAK = auto()
    CONTINUE = auto()
    RETURN = auto()

    ADDRESS = auto()

    ID = auto()
    EOF = auto()_____no_output_____

Klasa **Token** predstavlja uređeni par (klasa, leksema).

Medota **str** vraća string reprezentaciju tokena koja se koristi u procesu pronalaženja grešaka._____no_output_____

class Token:
    def __init__(self, class_, lexeme):
        self.class_ = class_
        self.lexeme = lexeme

    def __str__(self):
        return "<{} {}>".format(self.class_, self.lexeme)_____no_output_____

Klasa **Lekser** sadrži metode za leksičku analizu izvornog koda.

Metoda **lex** formira niz tokena pozivajući metodu **next_token**.

Metoda **next_token** konstruiše token odgovarajuće klase pozivajući metodu **next_char**.

Metoda **next_char** pomera pokazivač na sledeći karakter.

Metoda **read_keyword** konstruiše token ključne reči pod uslovom da je trenutni karakter slovo.

Metoda **read_string** konstruiše token string literala pod uslovom da je trenutni karakter znak navodnika.

Metoda **read_char** konstruiše token literala karaktera pod uslovom da je trenutni karakter apostrof.

Metoda **read_int** konstruiše token literala celog broja pod uslovom da je trenutni karakter cifra.

Metoda **read_space** ne konstruiše token, ali pomera pokazivač na prvi sledeći karakter koji nije razmak.

Metoda **die** se koristi u slučaju da je lekser pročitao neočekivani karakter._____no_output_____

class Lexer:
    def __init__(self, text):
        self.text = text
        self.len = len(text)
        self.pos = -1

    def read_space(self):
        while self.pos + 1 < self.len and self.text[self.pos + 1].isspace():
            self.next_char()

    def read_int(self):
        lexeme = self.text[self.pos]
        while self.pos + 1 < self.len and self.text[self.pos + 1].isdigit():
            lexeme += self.next_char()
        return int(lexeme)

    def read_char(self):
        self.pos += 1
        lexeme = self.text[self.pos]
        self.pos += 1
        return lexeme

    def read_string(self):
        lexeme = ''
        while self.pos + 1 < self.len and self.text[self.pos + 1] != '"':
            lexeme += self.next_char()
        self.pos += 1
        return lexeme

    def read_keyword(self):
        lexeme = self.text[self.pos]
        while self.pos + 1 < self.len and self.text[self.pos + 1].isalnum():
            lexeme += self.next_char()
        if lexeme == 'if':
            return Token(Class.IF, lexeme)
        elif lexeme == 'else':
            return Token(Class.ELSE, lexeme)
        elif lexeme == 'while':
            return Token(Class.WHILE, lexeme)
        elif lexeme == 'for':
            return Token(Class.FOR, lexeme)
        elif lexeme == 'break':
            return Token(Class.BREAK, lexeme)
        elif lexeme == 'continue':
            return Token(Class.CONTINUE, lexeme)
        elif lexeme == 'return':
            return Token(Class.RETURN, lexeme)
        elif lexeme == 'int' or lexeme == 'char' or lexeme == 'void':
            return Token(Class.TYPE, lexeme)
        return Token(Class.ID, lexeme)

    def next_char(self):
        self.pos += 1
        if self.pos >= self.len:
            return None
        return self.text[self.pos]

    def next_token(self):
        self.read_space()
        curr = self.next_char()
        if curr is None:
            return Token(Class.EOF, curr)
        token = None
        if curr.isalpha():
            token = self.read_keyword()
        elif curr.isdigit():
            token = Token(Class.INT, self.read_int())
        elif curr == '\'':
            token = Token(Class.CHAR, self.read_char())
        elif curr == '"':
            token = Token(Class.STRING, self.read_string())
        elif curr == '+':
            token = Token(Class.PLUS, curr)
        elif curr == '-':
            token = Token(Class.MINUS, curr)
        elif curr == '*':
            token = Token(Class.STAR, curr)
        elif curr == '/':
            token = Token(Class.FWDSLASH, curr)
        elif curr == '%':
            token = Token(Class.PERCENT, curr)
        elif curr == '&':
            curr = self.next_char()
            if curr == '&':
                token = Token(Class.AND, '&&')
            else:
                token = Token(Class.ADDRESS, '&')
                self.pos -= 1
        elif curr == '|':
            curr = self.next_char()
            if curr == '|':
                token = Token(Class.OR, '||')
            else:
                self.die(curr)
        elif curr == '!':
            curr = self.next_char()
            if curr == '=':
                token = Token(Class.NEQ, '!=')
            else:
                token = Token(Class.NOT, '!')
                self.pos -= 1
        elif curr == '=':
            curr = self.next_char()
            if curr == '=':
                token = Token(Class.EQ, '==')
            else:
                token = Token(Class.ASSIGN, '=')
                self.pos -= 1
        elif curr == '<':
            curr = self.next_char()
            if curr == '=':
                token = Token(Class.LTE, '<=')
            else:
                token = Token(Class.LT, '<')
                self.pos -= 1
        elif curr == '>':
            curr = self.next_char()
            if curr == '=':
                token = Token(Class.GTE, '>=')
            else:
                token = Token(Class.GT, '>')
                self.pos -= 1
        elif curr == '(':
            token = Token(Class.LPAREN, curr)
        elif curr == ')':
            token = Token(Class.RPAREN, curr)
        elif curr == '[':
            token = Token(Class.LBRACKET, curr)
        elif curr == ']':
            token = Token(Class.RBRACKET, curr)
        elif curr == '{':
            token = Token(Class.LBRACE, curr)
        elif curr == '}':
            token = Token(Class.RBRACE, curr)
        elif curr == ';':
            token = Token(Class.SEMICOLON, curr)
        elif curr == ',':
            token = Token(Class.COMMA, curr)
        else:
            self.die(curr)
        return token

    def lex(self):
        tokens = []
        while True:
            curr = self.next_token()
            tokens.append(curr)
            if curr.class_ == Class.EOF:
                break
        return tokens

    def die(self, char):
        raise SystemExit("Unexpected character: {}".format(char))_____no_output_____

Klasa **Node** predstavlja baznu klasu za formiranje AST, a klase koje je nasleđuju odgovaraju svakoj ispravnoj semantičkoj strukturi._____no_output_____

class Node():
    pass

class Program(Node):
    def __init__(self, nodes):
        self.nodes = nodes

class Decl(Node):
    def __init__(self, type_, id_):
        self.type_ = type_
        self.id_ = id_

class ArrayDecl(Node):
    def __init__(self, type_, id_, size, elems):
        self.type_ = type_
        self.id_ = id_
        self.size = size
        self.elems = elems

class ArrayElem(Node):
    def __init__(self, id_, index):
        self.id_ = id_
        self.index = index

class Assign(Node):
    def __init__(self, id_, expr):
        self.id_ = id_
        self.expr = expr

class If(Node):
    def __init__(self, cond, true, false):
        self.cond = cond
        self.true = true
        self.false = false

class While(Node):
    def __init__(self, cond, block):
        self.cond = cond
        self.block = block

class For(Node):
    def __init__(self, init, cond, step, block):
        self.init = init
        self.cond = cond
        self.step = step
        self.block = block

class FuncImpl(Node):
    def __init__(self, type_, id_, params, block):
        self.type_ = type_
        self.id_ = id_
        self.params = params
        self.block = block

class FuncCall(Node):
    def __init__(self, id_, args):
        self.id_ = id_
        self.args = args

class Block(Node):
    def __init__(self, nodes):
        self.nodes = nodes

class Params(Node):
    def __init__(self, params):
        self.params = params

class Args(Node):
    def __init__(self, args):
        self.args = args

class Elems(Node):
    def __init__(self, elems):
        self.elems = elems

class Break(Node):
    pass

class Continue(Node):
    pass

class Return(Node):
    def __init__(self, expr):
        self.expr = expr

class Type(Node):
    def __init__(self, value):
        self.value = value

class Int(Node):
    def __init__(self, value):
        self.value = value

class Char(Node):
    def __init__(self, value):
        self.value = value

class String(Node):
    def __init__(self, value):
        self.value = value

class Id(Node):
    def __init__(self, value):
        self.value = value

class BinOp(Node):
    def __init__(self, symbol, first, second):
        self.symbol = symbol
        self.first = first
        self.second = second

class UnOp(Node):
    def __init__(self, symbol, first):
        self.symbol = symbol
        self.first = first_____no_output_____

Klasa **Visitor** predstavlja baznu klasu za obilazak AST.

Metoda **visit** u trenutnom objektu traži metodu koja odgovara tipu prosleđenog čvora.

Metoda **die** se koristi u slučaju da tražena metoda ne postoji, tj. u slučaju kada je potrebno obići čvor čiji tip nije podržan._____no_output_____

class Visitor():
    def visit(self, parent, node):
        method = 'visit_' + type(node).__name__
        visitor = getattr(self, method, self.die)
        return visitor(parent, node)

    def die(self, parent, node):
        method = 'visit_' + type(node).__name__
        raise SystemExit("Missing method: {}".format(method))_____no_output_____

Importovanje neophodnih modula za čuvanje unutrašnjeg stanja objekta._____no_output_____

from functools import wraps
import pickle_____no_output_____

Klasa **Parser** sadrži metode za semantičku analizu izvornog koda koje će iz prosleđenog [FIFO niza](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)) tokena formirati AST čvor po čvor.

Metoda **parse** formira AST pomoću [Visitor dizajn šablona](https://sourcemaking.com/design_patterns/visitor) pozivom metode **program**.

Metoda **program** konstruiše AST čvor za deklaraciju globalnih promenljivih i implementaciju funkcija.

Metoda **id_** konstruiše AST čvor za identifikator.

Metoda **decl** konstruiše AST čvor za deklaraciju skalarne promenljive, niza ili funkcije.

Metoda **if_** konstruiše AST čvor za ispitivanje uslova, blok koji se izvršava u slučaju da je uslov tačan i opcioni blok koji se izvršava u slučaju da uslov nije tačan.

Metoda **while_** konstruiše AST čvor za ispitivanje uslova i blok koji se izvršava sve dok je uslov tačan.

Metoda **for_** konstruiše AST čvor za inicijalizaciju iteratora, ispitivanje uslova, inkrementiranje iteratora i blok koji se izvršava sve dok je uslov tačan.

Metoda **block** konstruiše AST čvor za blok instrukcija koje se izvršavaju u okviru neke semantičke celine.

Metoda **params** konstruiše AST čvor za deklarisane parametre funkcije. Svaki parametar ima naziv i tip.

Metoda **args** konstruiše AST čvor za prosleđene argumente pozivu funkcije. Svaki argument ima naziv i vrednost.

Metoda **elems** konstruiše AST čvor za definisane elemente pri inicijalizaciji niza.

Metoda **return_** konstruiše AST čvor za prekid funkcije uz opciono vraćanje vrednosti.

Metoda **break_** konstruiše AST čvor za prekid petlje.

Metoda **continue_** konstruiše AST čvor za skok na sledeću iteraciju petlje.

Metoda **type_** konstruiše AST čvor za tip podataka, tj. "int", "char" ili "void".

Metoda **factor** konstruiše AST čvor za matematičke operacije visokog prioriteta, tj. unarne operacije.

Metoda **term** konstruiše AST čvor za matematičke operacije srednjeg prioriteta, tj. multiplikativne operacije.

Metoda **expr** konstruiše AST čvor za matematičke operacije niskog prioriteta, tj. aditivne operacija.

Metoda **compare** konstruiše AST čvor za poređenje dva logička operanda.

Metoda **logic** konstruiše AST čvor za logičku konjunkciju i disjunkciju.

Metoda **eat** uzima token za početka niza i proverava da li njegova klasa odgovara prosleđenoj klasi.

Metoda **is_func_call** proverava da li trenutni identifikator odgovara pozivu ili implementaciji funkcije. Nakon provere vraća parser u originalno stanje.

Metoda **restorable** se dodaje kao anotacija drugoj metodi koja menja unutrašnje stanje objekta, a potrebno je da se objekat po završetku funkcije vrati u originalno stanje.

Metoda **die** se koristi u slučaju da se dogodi bilo koja greška.

Metoda **die_deriv** se koristi u slučaju da pročitani token ne odgovara sementičkoj strukturi koja se trenutno formira.

Metoda **die_type** se koristi u slučaju da klasa tokena sa početka niza ne odgovara klasi prosleđenoj pozivu metode **eat**._____no_output_____

class Parser:
    def __init__(self, tokens):
        self.tokens = tokens
        self.curr = tokens.pop(0)
        self.prev = None

    def restorable(call):
        @wraps(call)
        def wrapper(self, *args, **kwargs):
            state = pickle.dumps(self.__dict__)
            result = call(self, *args, **kwargs)
            self.__dict__ = pickle.loads(state)
            return result
        return wrapper

    def eat(self, class_):
        if self.curr.class_ == class_:
            self.prev = self.curr
            self.curr = self.tokens.pop(0)
        else:
            self.die_type(class_.name, self.curr.class_.name)

    def program(self):
        nodes = []
        while self.curr.class_ != Class.EOF:
            if self.curr.class_ == Class.TYPE:
                nodes.append(self.decl())
            else:
                self.die_deriv(self.program.__name__)
        return Program(nodes)

    def id_(self):
        is_array_elem = self.prev.class_ != Class.TYPE
        id_ = Id(self.curr.lexeme)
        self.eat(Class.ID)
        if self.curr.class_ == Class.LPAREN and self.is_func_call():
            self.eat(Class.LPAREN)
            args = self.args()
            self.eat(Class.RPAREN)
            return FuncCall(id_, args)
        elif self.curr.class_ == Class.LBRACKET and is_array_elem:
            self.eat(Class.LBRACKET)
            index = self.expr()
            self.eat(Class.RBRACKET)
            id_ = ArrayElem(id_, index)
        if self.curr.class_ == Class.ASSIGN:
            self.eat(Class.ASSIGN)
            expr = self.expr()
            return Assign(id_, expr)
        else:
            return id_

    def decl(self):
        type_ = self.type_()
        id_ = self.id_()
        if self.curr.class_ == Class.LBRACKET:
            self.eat(Class.LBRACKET)
            size = None
            if self.curr.class_ != Class.RBRACKET:
                size = self.expr()
            self.eat(Class.RBRACKET)
            elems = None
            if self.curr.class_ == Class.ASSIGN:
                self.eat(Class.ASSIGN)
                self.eat(Class.LBRACE)
                elems = self.elems()
                self.eat(Class.RBRACE)
            self.eat(Class.SEMICOLON)
            return ArrayDecl(type_, id_, size, elems)
        elif self.curr.class_ == Class.LPAREN:
            self.eat(Class.LPAREN)
            params = self.params()
            self.eat(Class.RPAREN)
            self.eat(Class.LBRACE)
            block = self.block()
            self.eat(Class.RBRACE)
            return FuncImpl(type_, id_, params, block)
        else:
            self.eat(Class.SEMICOLON)
            return Decl(type_, id_)

    def if_(self):
        self.eat(Class.IF)
        self.eat(Class.LPAREN)
        cond = self.logic()
        self.eat(Class.RPAREN)
        self.eat(Class.LBRACE)
        true = self.block()
        self.eat(Class.RBRACE)
        false = None
        if self.curr.class_ == Class.ELSE:
            self.eat(Class.ELSE)
            self.eat(Class.LBRACE)
            false = self.block()
            self.eat(Class.RBRACE)
        return If(cond, true, false)

    def while_(self):
        self.eat(Class.WHILE)
        self.eat(Class.LPAREN)
        cond = self.logic()
        self.eat(Class.RPAREN)
        self.eat(Class.LBRACE)
        block = self.block()
        self.eat(Class.RBRACE)
        return While(cond, block)

    def for_(self):
        self.eat(Class.FOR)
        self.eat(Class.LPAREN)
        init = self.id_()
        self.eat(Class.SEMICOLON)
        cond = self.logic()
        self.eat(Class.SEMICOLON)
        step = self.id_()
        self.eat(Class.RPAREN)
        self.eat(Class.LBRACE)
        block = self.block()
        self.eat(Class.RBRACE)
        return For(init, cond, step, block)

    def block(self):
        nodes = []
        while self.curr.class_ != Class.RBRACE:
            if self.curr.class_ == Class.IF:
                nodes.append(self.if_())
            elif self.curr.class_ == Class.WHILE:
                nodes.append(self.while_())
            elif self.curr.class_ == Class.FOR:
                nodes.append(self.for_())
            elif self.curr.class_ == Class.BREAK:
                nodes.append(self.break_())
            elif self.curr.class_ == Class.CONTINUE:
                nodes.append(self.continue_())
            elif self.curr.class_ == Class.RETURN:
                nodes.append(self.return_())
            elif self.curr.class_ == Class.TYPE:
                nodes.append(self.decl())
            elif self.curr.class_ == Class.ID:
                nodes.append(self.id_())
                self.eat(Class.SEMICOLON)
            else:
                self.die_deriv(self.block.__name__)
        return Block(nodes)

    def params(self):
        params = []
        while self.curr.class_ != Class.RPAREN:
            if len(params) > 0:
                self.eat(Class.COMMA)
            type_ = self.type_()
            id_ = self.id_()
            params.append(Decl(type_, id_))
        return Params(params)

    def args(self):
        args = []
        while self.curr.class_ != Class.RPAREN:
            if len(args) > 0:
                self.eat(Class.COMMA)
            args.append(self.expr())
        return Args(args)

    def elems(self):
        elems = []
        while self.curr.class_ != Class.RBRACE:
            if len(elems) > 0:
                self.eat(Class.COMMA)
            elems.append(self.expr())
        return Elems(elems)

    def return_(self):
        self.eat(Class.RETURN)
        expr = self.expr()
        self.eat(Class.SEMICOLON)
        return Return(expr)

    def break_(self):
        self.eat(Class.BREAK)
        self.eat(Class.SEMICOLON)
        return Break()

    def continue_(self):
        self.eat(Class.CONTINUE)
        self.eat(Class.SEMICOLON)
        return Continue()

    def type_(self):
        type_ = Type(self.curr.lexeme)
        self.eat(Class.TYPE)
        return type_

    def factor(self):
        if self.curr.class_ == Class.INT:
            value = Int(self.curr.lexeme)
            self.eat(Class.INT)
            return value
        elif self.curr.class_ == Class.CHAR:
            value = Char(self.curr.lexeme)
            self.eat(Class.CHAR)
            return value
        elif self.curr.class_ == Class.STRING:
            value = String(self.curr.lexeme)
            self.eat(Class.STRING)
            return value
        elif self.curr.class_ == Class.ID:
            return self.id_()
        elif self.curr.class_ in [Class.MINUS, Class.NOT, Class.ADDRESS]:
            op = self.curr
            self.eat(self.curr.class_)
            first = None
            if self.curr.class_ == Class.LPAREN:
                self.eat(Class.LPAREN)
                first = self.logic()
                self.eat(Class.RPAREN)
            else:
                first = self.factor()
            return UnOp(op.lexeme, first)
        elif self.curr.class_ == Class.LPAREN:
            self.eat(Class.LPAREN)
            first = self.logic()
            self.eat(Class.RPAREN)
            return first
        elif self.curr.class_ == Class.SEMICOLON:
            return None
        else:
            self.die_deriv(self.factor.__name__)

    def term(self):
        first = self.factor()
        while self.curr.class_ in [Class.STAR, Class.FWDSLASH, Class.PERCENT]:
            if self.curr.class_ == Class.STAR:
                op = self.curr.lexeme
                self.eat(Class.STAR)
                second = self.factor()
                first = BinOp(op, first, second)
            elif self.curr.class_ == Class.FWDSLASH:
                op = self.curr.lexeme
                self.eat(Class.FWDSLASH)
                second = self.factor()
                first = BinOp(op, first, second)
            elif self.curr.class_ == Class.PERCENT:
                op = self.curr.lexeme
                self.eat(Class.PERCENT)
                second = self.factor()
                first = BinOp(op, first, second)
        return first

    def expr(self):
        first = self.term()
        while self.curr.class_ in [Class.PLUS, Class.MINUS]:
            if self.curr.class_ == Class.PLUS:
                op = self.curr.lexeme
                self.eat(Class.PLUS)
                second = self.term()
                first = BinOp(op, first, second)
            elif self.curr.class_ == Class.MINUS:
                op = self.curr.lexeme
                self.eat(Class.MINUS)
                second = self.term()
                first = BinOp(op, first, second)
        return first

    def compare(self):
        first = self.expr()
        if self.curr.class_ == Class.EQ:
            op = self.curr.lexeme
            self.eat(Class.EQ)
            second = self.expr()
            return BinOp(op, first, second)
        elif self.curr.class_ == Class.NEQ:
            op = self.curr.lexeme
            self.eat(Class.NEQ)
            second = self.expr()
            return BinOp(op, first, second)
        elif self.curr.class_ == Class.LT:
            op = self.curr.lexeme
            self.eat(Class.LT)
            second = self.expr()
            return BinOp(op, first, second)
        elif self.curr.class_ == Class.GT:
            op = self.curr.lexeme
            self.eat(Class.GT)
            second = self.expr()
            return BinOp(op, first, second)
        elif self.curr.class_ == Class.LTE:
            op = self.curr.lexeme
            self.eat(Class.LTE)
            second = self.expr()
            return BinOp(op, first, second)
        elif self.curr.class_ == Class.GTE:
            op = self.curr.lexeme
            self.eat(Class.GTE)
            second = self.expr()
            return BinOp(op, first, second)
        else:
            return first

    def logic_term(self):
        first = self.compare()
        while self.curr.class_ == Class.AND:
            op = self.curr.lexeme
            self.eat(Class.AND)
            second = self.compare()
            first = BinOp(op, first, second)
        return first

    def logic(self):
        first = self.logic_term()
        while self.curr.class_ == Class.OR:
            op = self.curr.lexeme
            self.eat(Class.OR)
            second = self.logic_term()
            first = BinOp(op, first, second)
        return first

    @restorable
    def is_func_call(self):
        try:
            self.eat(Class.LPAREN)
            self.args()
            self.eat(Class.RPAREN)
            return self.curr.class_ != Class.LBRACE
        except:
            return False

    def parse(self):
        return self.program()

    def die(self, text):
        raise SystemExit(text)

    def die_deriv(self, fun):
        self.die("Derivation error: {}".format(fun))

    def die_type(self, expected, found):
        self.die("Expected: {}, Found: {}".format(expected, found))_____no_output_____

Klasa **Symbol** služi za konstrukciju objekata u tabeli simbola ugrađenoj u AST.

Metoda **copy** pravi kopiju trenutnog simbola i koristi se u fazi interpretiranja kako bi se podržala rekurzija._____no_output_____

class Symbol:
    def __init__(self, id_, type_, scope):
        self.id_ = id_
        self.type_ = type_
        self.scope = scope

    def __str__(self):
        return "<{} {} {}>".format(self.id_, self.type_, self.scope)

    def copy(self):
        return Symbol(self.id_, self.type_, self.scope)_____no_output_____

Klasa **Symbols** predstavlja tabelu simbola i sadrži metode za modifikaciju njenog sadržaja. Konstruisani objekat će se ugraditi u odgovarajući čvor AST, tj. u čvor tipa **Block** u kome su simboli vidljivi.

Metoda **put** dodaje novi simbol u tabelu na osnovu prosleđenog identifikatora, tipa podataka i okruženja u kome je simbol vidljiv.

Metoda **get** vraća simbol iz tabele na osnovu prosleđenog identifikatora.

Metoda **contains** proverava da li tabela sadrži simbol na osnovu prosleđenog identifikatora.

Metoda **remove** uklanja simbol iz tabele na osnovu prosleđenog identifikatora._____no_output_____

class Symbols:
    def __init__(self):
        self.symbols = {}

    def put(self, id_, type_, scope):
        self.symbols[id_] = Symbol(id_, type_, scope)

    def get(self, id_):
        return self.symbols[id_]

    def contains(self, id_):
        return id_ in self.symbols

    def remove(self, id_):
        del self.symbols[id_]

    def __len__(self):
        return len(self.symbols)

    def __str__(self):
        out = ""
        for _, value in self.symbols.items():
            if len(out) > 0:
                out += "\n"
            out += str(value)
        return out

    def __iter__(self):
        return iter(self.symbols.values())

    def __next__(self):
        return next(self.symbols.values())_____no_output_____

Klasa **Symbolizer** sadrži metode za formiranje tabele simbola. Nije potrebno obići sve čvorove AST, već samo deklaracije simbola i blokove instrukcija u kojima se mogu naći te deklaracije.

Metoda **symbolize** formira tabelu simbola rekurzivim pozivom **visit** metode._____no_output_____

class Symbolizer(Visitor):
    def __init__(self, ast):
        self.ast = ast

    def visit_Program(self, parent, node):
        node.symbols = Symbols()
        for n in node.nodes:
            self.visit(node, n)

    def visit_Decl(self, parent, node):
        parent.symbols.put(node.id_.value, node.type_.value, id(parent))

    def visit_ArrayDecl(self, parent, node):
        node.symbols = Symbols()
        parent.symbols.put(node.id_.value, node.type_.value, id(parent))

    def visit_ArrayElem(self, parent, node):
        pass

    def visit_Assign(self, parent, node):
        pass

    def visit_If(self, parent, node):
        self.visit(node, node.true)
        if node.false is not None:
            self.visit(node, node.false)

    def visit_While(self, parent, node):
        self.visit(node, node.block)

    def visit_For(self, parent, node):
        self.visit(node, node.block)

    def visit_FuncImpl(self, parent, node):
        parent.symbols.put(node.id_.value, node.type_.value, id(parent))
        self.visit(node, node.block)
        self.visit(node, node.params)

    def visit_FuncCall(self, parent, node):
        pass

    def visit_Block(self, parent, node):
        node.symbols = Symbols()
        for n in node.nodes:
            self.visit(node, n)

    def visit_Params(self, parent, node):
        node.symbols = Symbols()
        for p in node.params:
            self.visit(node, p)
            self.visit(parent.block, p)

    def visit_Args(self, parent, node):
        pass

    def visit_Elems(self, parent, node):
        pass

    def visit_Break(self, parent, node):
        pass

    def visit_Continue(self, parent, node):
        pass

    def visit_Return(self, parent, node):
        pass

    def visit_Type(self, parent, node):
        pass

    def visit_Int(self, parent, node):
        pass

    def visit_Char(self, parent, node):
        pass

    def visit_String(self, parent, node):
        pass

    def visit_Id(self, parent, node):
        pass

    def visit_BinOp(self, parent, node):
        pass

    def visit_UnOp(self, parent, node):
        pass

    def symbolize(self):
        self.visit(None, self.ast)_____no_output_____

Importovanje neophodnog modula za proveru regularnih izraza._____no_output_____

import re_____no_output_____

Klasa **Runner** sadrži metode za interpretiranje AST, tj. izvršavanje programa.

Metoda **run** pokreće izvršavanje programa rekurzivim pozivom **visit** metode.

Metoda **get_symbol** vraća stavku iz tabele simbola trenutno aktivnog okruženja na osnovu prosleđenog identifikatora.

Metoda **init_scope** inicijalizuje okruženje simbola i dodaje ga na stek trenutno aktivnih okruženja, tj. blokova instrukcija. Ovo se dešava pri početku izvršavanja funkcije ili bilo kog bloka instrukcija.

Metoda **clear_scope** uklanja trenutno aktivno okuženje sa steka. Ovo se dešava pri završetku izvršavanja funkcije ili bilo kog bloka instrukcija._____no_output_____

class Runner(Visitor):
    def __init__(self, ast):
        self.ast = ast
        self.global_ = {}
        self.local = {}
        self.scope = []
        self.return_ = False

    def get_symbol(self, node):
        id_ = node.value
        for scope in reversed(self.scope):
            if scope in self.local:
                curr_scope = self.local[scope][-1]
                if id_ in curr_scope:
                    return curr_scope[id_]
        return self.global_[id_]

    def init_scope(self, node):
        scope = id(node)
        if scope not in self.local:
            self.local[scope] = []
        self.local[scope].append({})
        for s in node.symbols:
            self.local[scope][-1][s.id_] = s.copy()

    def clear_scope(self, node):
        scope = id(node)
        self.local[scope].pop()

    def visit_Program(self, parent, node):
        for s in node.symbols:
            self.global_[s.id_] = s.copy()
        for n in node.nodes:
            self.visit(node, n)

    def visit_Decl(self, parent, node):
        id_ = self.get_symbol(node.id_)
        id_.value = None

    def visit_ArrayDecl(self, parent, node):
        pass

    def visit_ArrayElem(self, parent, node):
        pass

    def visit_Assign(self, parent, node):
        id_ = self.visit(node, node.id_)
        value = self.visit(node, node.expr)
        if isinstance(value, Symbol):
            value = value.value
        id_.value = value

    def visit_If(self, parent, node):
        cond = self.visit(node, node.cond)
        if cond:
            self.init_scope(node.true)
            self.visit(node, node.true)
            self.clear_scope(node.true)
        else:
            if node.false is not None:
                self.init_scope(node.false)
                self.visit(node, node.false)
                self.clear_scope(node.false)

    def visit_While(self, parent, node):
        cond = self.visit(node, node.cond)
        while cond:
            self.init_scope(node.block)
            self.visit(node, node.block)
            self.clear_scope(node.block)
            cond = self.visit(node, node.cond)

    def visit_For(self, parent, node):
        self.visit(node, node.init)
        cond = self.visit(node, node.cond)
        while cond:
            self.init_scope(node.block)
            self.visit(node, node.block)
            self.clear_scope(node.block)
            self.visit(node, node.step)
            cond = self.visit(node, node.cond)

    def visit_FuncImpl(self, parent, node):
        id_ = self.get_symbol(node.id_)
        id_.params = node.params
        id_.block = node.block
        if node.id_.value == 'main':
            self.init_scope(node.block)
            self.visit(node, node.block)
            self.clear_scope(node.block)

    def visit_FuncCall(self, parent, node):
        func = node.id_.value
        args = node.args.args
        if func == 'printf':
            format_ = args[0].value
            format_ = format_.replace('\\n', '\n')
            for a in args[1:]:
                if isinstance(a, Int):
                    format_ = format_.replace('%d', a.value, 1)
                elif isinstance(a, Char):
                    format_ = format_.replace('%c', chr(a.value), 1)
                elif isinstance(a, String):
                    format_ = format_.replace('%s', a.value, 1)
                elif isinstance(a, Id):
                    id_ = self.visit(node.args, a)
                    value = id_.value
                    if id_.type_ == 'char':
                        value = chr(value)
                    format_ = re.sub('%[dcs]', str(value), format_, 1)
                else:
                    value = self.visit(node.args, a)
                    format_ = re.sub('%[dcs]', str(value), format_, 1)
            print(format_, end='')
        elif func == 'scanf':
            format_ = args[0].value
            inputs = input().split()
            matches = re.findall('%[dcs]', format_)
            for i, m in enumerate(matches):
                id_ = self.visit(node.args, args[i + 1])
                if m == '%d':
                    id_.value = int(inputs[i])
                elif m == '%c':
                    id_.value = ord(inputs[i][0])
        else:
            impl = self.global_[func]
            self.init_scope(impl.block)
            self.visit(node, node.args)
            result = self.visit(node, impl.block)
            self.clear_scope(impl.block)
            self.return_ = False
            return result

    def visit_Block(self, parent, node):
        result = None
        scope = id(node)
        self.scope.append(scope)
        for n in node.nodes:
            if self.return_:
                break
            if isinstance(n, Break):
                break
            elif isinstance(n, Continue):
                continue
            elif isinstance(n, Return):
                self.return_ = True
                if n.expr is not None:
                    result = self.visit(n, n.expr)
            else:
                self.visit(node, n)
        self.scope.pop()
        return result

    def visit_Params(self, parent, node):
        pass

    def visit_Args(self, parent, node):
        func = parent.id_.value
        impl = self.global_[func]
        for p, a in zip(impl.params.params, node.args):
            arg = self.visit(impl.block, a)
            id_ = self.visit(impl.block, p.id_)
            id_.value = arg
            if isinstance(arg, Symbol):
                id_.value = arg.value

    def visit_Elems(self, parent, node):
        pass

    def visit_Break(self, parent, node):
        pass

    def visit_Continue(self, parent, node):
        pass

    def visit_Return(self, parent, node):
        pass

    def visit_Type(self, parent, node):
        pass

    def visit_Int(self, parent, node):
        return node.value

    def visit_Char(self, parent, node):
        return ord(node.value)

    def visit_String(self, parent, node):
        return node.value

    def visit_Id(self, parent, node):
        return self.get_symbol(node)

    def visit_BinOp(self, parent, node):
        first = self.visit(node, node.first)
        if isinstance(first, Symbol):
            first = first.value
        second = self.visit(node, node.second)
        if isinstance(second, Symbol):
            second = second.value
        if node.symbol == '+':
            return int(first) + int(second)
        elif node.symbol == '-':
            return int(first) - int(second)
        elif node.symbol == '*':
            return int(first) * int(second)
        elif node.symbol == '/':
            return int(first) / int(second)
        elif node.symbol == '%':
            return int(first) % int(second)
        elif node.symbol == '==':
            return first == second
        elif node.symbol == '!=':
            return first != second
        elif node.symbol == '<':
            return int(first) < int(second)
        elif node.symbol == '>':
            return int(first) > int(second)
        elif node.symbol == '<=':
            return int(first) <= int(second)
        elif node.symbol == '>=':
            return int(first) >= int(second)
        elif node.symbol == '&&':
            bool_first = first != 0
            bool_second = second != 0
            return bool_first and bool_second
        elif node.symbol == '||':
            bool_first = first != 0
            bool_second = second != 0
            return bool_first or bool_second
        else:
            return None

    def visit_UnOp(self, parent, node):
        first = self.visit(node, node.first)
        backup_first = first
        if isinstance(first, Symbol):
            first = first.value
        if node.symbol == '-':
            return -first
        elif node.symbol == '!':
            bool_first = first != 0
            return not bool_first
        elif node.symbol == '&':
            return backup_first
        else:
            return None

    def run(self):
        self.visit(None, self.ast)_____no_output_____

Testiranje implementacije.

**NAPOMENA:** Ovo je delimična implementacija interpretera što znači da će pokušaj interpretiranja test primera 3, 8 i 9 rezultovati u grešku. Potpuna implementacija interpretera se po planu izučava u sledećem terminu vežbi._____no_output_____

test_id = '01'
path = f'/content/drive/Shareddrives/Materijali 2020 2021/5. semestar/Programski prevodioci/Vezbe/data/test/{test_id}/src.c'

with open(path, 'r') as source:
    text = source.read()

    lexer = Lexer(text)
    tokens = lexer.lex()

    parser = Parser(tokens)
    ast = parser.parse()

    symbolizer = Symbolizer(ast)
    symbolizer.symbolize()

    runner = Runner(ast)
    runner.run()_____no_output_____