Skip to content

Assignment statement grammar (version 2)

Tree address code generation pass

ANTLR 4.x listener and visitor implementation for intermediate code generation (Three addresses code)

@author: Morteza Zakeri, (http://webpages.iust.ac.ir/morteza_zakeri/) @date: 20201017

  • Compiler generator: ANTLR4.x
  • Target language(s): Python3.x,

-Changelog: -- v2.1.0 --- Add support for AST intermediate representation using module ast_pass --- Change compiler_pass module to three_address_code_pass -- v2.0.0 --- Add attributes for grammar rules which are used to hold type and intermediate language_apps of rules.

  • Reference: Compiler book by Dr. Saeed Parsa (http://parsa.iust.ac.ir/)
  • Course website: http://parsa.iust.ac.ir/courses/compilers/
  • Laboratory website: http://reverse.iust.ac.ir/

ThreeAddressCodeGenerator2Listener (AssignmentStatement2Listener)

Type checking and generating three address language_apps (optimizing number of temporary variables)

Source code in language_apps\assignment_statement_v2\three_address_code_pass.py
class ThreeAddressCodeGenerator2Listener(AssignmentStatement2Listener):
    """
    Type checking and generating three address language_apps (optimizing number of temporary variables)
    """

    def __init__(self):
        print('Listener2 call!')
        self.temp_counter = 0

    def create_temp(self):
        self.temp_counter += 1
        return 'T' + str(self.temp_counter)

    def remove_temp(self):
        self.temp_counter -= 1

    def get_temp(self):
        return 'T' + str(self.temp_counter)

    @classmethod
    def is_temp(cls, variable):
        if variable[0] == 'T':
            return True
        return False

    # ------------------
    # Rule number
    def exitNumber_float(self, ctx: AssignmentStatement2Parser.Number_floatContext):
        ctx.type_attr = 'float'
        ctx.value_attr = float(ctx.getText())

    def exitNumber_int(self, ctx: AssignmentStatement2Parser.Number_intContext):
        ctx.type_attr = 'int'
        ctx.value_attr = int(ctx.getText())

    # ------------------
    # Rule factor
    def exitFact_expr(self, ctx: AssignmentStatement2Parser.Fact_exprContext):
        ctx.type_attr = ctx.expr().type_attr
        ctx.value_attr = ctx.expr().value_attr

    def exitFact_id(self, ctx: AssignmentStatement2Parser.Fact_idContext):

        ctx.type_attr = 'string'
        ctx.value_attr = ctx.getText()

    def exitFact_number(self, ctx: AssignmentStatement2Parser.Fact_numberContext):
        ctx.type_attr = ctx.number().type_attr
        ctx.value_attr = ctx.number().value_attr

    # ------------------
    # Rule term
    def exitTerm_fact_mutiply(self, ctx: AssignmentStatement2Parser.Term_fact_mutiplyContext):
        if ctx.term().type_attr != ctx.factor().type_attr:
            print('Semantic error: Cannot multiply {0} and {1}'.format(ctx.term().type_attr, ctx.factor().type_attr))
            quit(-1)
        else:
            if ctx.term().type_attr == 'float':
                ctx.type_attr = 'float'
                ctx.value_attr = ctx.term().value_attr * ctx.factor().value_attr
            elif ctx.term().type_attr == 'int':
                ctx.type_attr = 'int'
                ctx.value_attr = ctx.term().value_attr * ctx.factor().value_attr
            else:
                ctx.type_attr = 'string'
                if self.is_temp(ctx.term().value_attr):
                    ctx.value_attr = ctx.term().value_attr
                    if self.is_temp(ctx.factor().value_attr):
                        self.remove_temp()
                elif self.is_temp(ctx.factor().value_attr):
                    ctx.value_attr = ctx.factor().value_attr
                else:
                    ctx.value_attr = self.create_temp()
            print('{0} = {1} * {2}'.format(ctx.value_attr, ctx.term().value_attr, ctx.factor().value_attr))

    def exitTerm_fact_divide(self, ctx: AssignmentStatement2Parser.Term_fact_mutiplyContext):
        if ctx.term().type_attr != ctx.factor().type_attr:
            print('Semantic error: Cannot divide {0} and {1}'.format(ctx.term().type_attr, ctx.factor().type_attr))
            quit(-1)
        else:
            if ctx.term().type_attr == 'float':
                ctx.type_attr = 'float'
                ctx.value_attr = ctx.term().value_attr / ctx.factor().value_attr
            elif ctx.term().type_attr == 'int':
                ctx.type_attr = 'int'
                ctx.value_attr = int(ctx.term().value_attr / ctx.factor().value_attr)
            else:
                ctx.type_attr = 'string'
                if self.is_temp(ctx.term().value_attr):
                    ctx.value_attr = ctx.term().value_attr
                    if self.is_temp(ctx.factor().value_attr):
                        self.remove_temp()
                elif self.is_temp(ctx.factor().value_attr):
                    ctx.value_attr = ctx.factor().value_attr
                else:
                    ctx.value_attr = self.create_temp()
            print('{0} = {1} / {2}'.format(ctx.value_attr, ctx.term().value_attr, ctx.factor().value_attr))

    def exitFactor3(self, ctx: AssignmentStatement2Parser.Factor3Context):
        ctx.type_attr = ctx.factor().type_attr
        ctx.value_attr = ctx.factor().value_attr

    # ------------------
    # Rule expr
    def exitExpr_term_plus(self, ctx: AssignmentStatement2Parser.Expr_term_plusContext):
        if ctx.expr().type_attr != ctx.term().type_attr:
            print('Semantic error: Cannot plus {0} and {1}'.format(ctx.expr().type_attr, ctx.term().type_attr))
            quit(-1)
        else:
            if ctx.term().type_attr == 'float':
                ctx.type_attr = 'float'
                ctx.value_attr = ctx.expr().value_attr + ctx.term().value_attr
            elif ctx.term().type_attr == 'int':
                ctx.type_attr = 'int'
                ctx.value_attr = ctx.expr().value_attr + ctx.term().value_attr
            else:
                ctx.type_attr = 'string'
                if self.is_temp(ctx.expr().value_attr):
                    ctx.value_attr = ctx.expr().value_attr
                    if self.is_temp(ctx.term().value_attr):
                        self.remove_temp()
                elif self.is_temp(ctx.term().value_attr):
                    ctx.value_attr = ctx.term().value_attr
                else:
                    ctx.value_attr = self.create_temp()
            print('{0} = {1} + {2}'.format(ctx.value_attr, ctx.expr().value_attr, ctx.term().value_attr))

    def exitExpr_term_minus(self, ctx: AssignmentStatement2Parser.Expr_term_minusContext):
        if ctx.expr().type_attr != ctx.term().type_attr:
            print('Semantic error: Cannot subtract {0} and {1}'.format(ctx.expr().type_attr, ctx.term().type_attr))
            quit(-1)
        else:
            if ctx.term().type_attr == 'float':
                ctx.type_attr = 'float'
                ctx.value_attr = ctx.expr().value_attr - ctx.term().value_attr
            elif ctx.term().type_attr == 'int':
                ctx.type_attr = 'int'
                ctx.value_attr = ctx.expr().value_attr - ctx.term().value_attr
            else:
                ctx.type_attr = 'string'
                if self.is_temp(ctx.expr().value_attr):
                    ctx.value_attr = ctx.expr().value_attr
                    if self.is_temp(ctx.term().value_attr):
                        self.remove_temp()
                elif self.is_temp(ctx.term().value_attr):
                    ctx.value_attr = ctx.term().value_attr
                else:
                    ctx.value_attr = self.create_temp()
            print('{0} = {1} - {2}'.format(ctx.value_attr, ctx.expr().value_attr, ctx.term().value_attr))

    def exitTerm4(self, ctx: AssignmentStatement2Parser.Term4Context):
        ctx.type_attr = ctx.term().type_attr
        ctx.value_attr = ctx.term().value_attr

    # ------------------
    # Rule expr
    def exitAssign(self, ctx: AssignmentStatement2Parser.AssignContext):
        ctx.type_attr = ctx.expr().type_attr
        ctx.value_attr = ctx.expr().value_attr
        print('Assign statement: "{0} = {1}"\nAssign type: "{2}"'.format(ctx.ID().getText(), ctx.value_attr,
                                                                         ctx.type_attr))

ThreeAddressCodeGenerator2Visitor (AssignmentStatement2Visitor)

Type checking and generating three address language_apps (optimizing number of temporary variables) Utilizing ANTLR 4.x Visitor mechanism

Source code in language_apps\assignment_statement_v2\three_address_code_pass.py
class ThreeAddressCodeGenerator2Visitor(AssignmentStatement2Visitor):
    """
    Type checking and generating three address language_apps (optimizing number of temporary variables)
    Utilizing ANTLR 4.x Visitor mechanism
    """

    def __init__(self):
        print('Visitor2 call!')
        self.temp_counter = 0

    def create_temp(self):
        self.temp_counter += 1
        return 'T' + str(self.temp_counter)

    def remove_temp(self):
        self.temp_counter -= 1

    def get_temp(self):
        return 'T' + str(self.temp_counter)

    @classmethod
    def is_temp(cls, variable):
        if variable[0] == 'T':
            return True
        return False

    def visitStart(self, ctx: AssignmentStatement2Parser.StartContext):
        self.visit(tree=ctx.prog())

    def visitProg(self, ctx: AssignmentStatement2Parser.ProgContext):
        if ctx.getChildCount() == 2:
            self.visit(tree=ctx.prog())
        ctx.type_attr, ctx.value_attr = self.visit(tree=ctx.assign())
        return ctx.type_attr, ctx.value_attr

    def visitAssign(self, ctx: AssignmentStatement2Parser.AssignContext):
        ctx.type_attr, ctx.value_attr = self.visit(tree=ctx.expr())
        print('Assign statement: "{0} = {1}"\nAssign type: "{2}"'.format(ctx.ID().getText(), ctx.value_attr,
                                                                         ctx.type_attr))
        return ctx.type_attr, ctx.value_attr

    # ------------------
    # Rule expr
    def visitExpr_term_plus(self, ctx: AssignmentStatement2Parser.Expr_term_plusContext):
        ctx.expr().type_attr, ctx.expr().value_attr = self.visit(tree=ctx.expr())
        ctx.term().type_attr, ctx.term().value_attr = self.visit(tree=ctx.term())
        if ctx.expr().type_attr != ctx.term().type_attr:
            print('Semantic error: Cannot plus {0} and {1}'.format(ctx.expr().type_attr, ctx.term().type_attr))
            quit(-1)
        else:
            if ctx.term().type_attr == 'float':
                ctx.type_attr = 'float'
                ctx.value_attr = ctx.expr().value_attr + ctx.term().value_attr
            elif ctx.term().type_attr == 'int':
                ctx.type_attr = 'int'
                ctx.value_attr = ctx.expr().value_attr + ctx.term().value_attr
            else:
                ctx.type_attr = 'string'
                if self.is_temp(ctx.expr().value_attr):
                    ctx.value_attr = ctx.expr().value_attr
                    if self.is_temp(ctx.term().value_attr):
                        self.remove_temp()
                elif self.is_temp(ctx.term().value_attr):
                    ctx.value_attr = ctx.term().value_attr
                else:
                    ctx.value_attr = self.create_temp()
            print('{0} = {1} + {2}'.format(ctx.value_attr, ctx.expr().value_attr, ctx.term().value_attr))
            return ctx.type_attr, ctx.value_attr

    def visitExpr_term_minus(self, ctx: AssignmentStatement2Parser.Expr_term_minusContext):
        ctx.expr().type_attr, ctx.expr().value_attr = self.visit(tree=ctx.expr())
        ctx.term().type_attr, ctx.term().value_attr = self.visit(tree=ctx.term())
        if ctx.expr().type_attr != ctx.term().type_attr:
            print('Semantic error: Cannot plus {0} and {1}'.format(ctx.expr().type_attr, ctx.term().type_attr))
            quit(-1)
        else:
            if ctx.term().type_attr == 'float':
                ctx.type_attr = 'float'
                ctx.value_attr = ctx.expr().value_attr - ctx.term().value_attr
            elif ctx.term().type_attr == 'int':
                ctx.type_attr = 'int'
                ctx.value_attr = ctx.expr().value_attr - ctx.term().value_attr
            else:
                ctx.type_attr = 'string'
                if self.is_temp(ctx.expr().value_attr):
                    ctx.value_attr = ctx.expr().value_attr
                    if self.is_temp(ctx.term().value_attr):
                        self.remove_temp()
                elif self.is_temp(ctx.term().value_attr):
                    ctx.value_attr = ctx.term().value_attr
                else:
                    ctx.value_attr = self.create_temp()
            print('{0} = {1} - {2}'.format(ctx.value_attr, ctx.expr().value_attr, ctx.term().value_attr))
            return ctx.type_attr, ctx.value_attr

    def visitTerm4(self, ctx: AssignmentStatement2Parser.Term4Context):
        ctx.type_attr, ctx.value_attr = self.visit(ctx.term())
        return ctx.type_attr, ctx.value_attr

    # ------------------
    # Rule term
    def visitTerm_fact_mutiply(self, ctx: AssignmentStatement2Parser.Term_fact_mutiplyContext):
        ctx.term().type_attr, ctx.term().value_attr = self.visit(tree=ctx.term())
        ctx.factor().type_attr, ctx.factor().value_attr = self.visit(tree=ctx.factor())
        if ctx.term().type_attr != ctx.factor().type_attr:
            print('Semantic error: Cannot multiply {0} and {1}'.format(ctx.term().type_attr, ctx.factor().type_attr))
            quit(-1)
        else:
            if ctx.term().type_attr == 'float':
                ctx.type_attr = 'float'
                ctx.value_attr = ctx.term().value_attr * ctx.factor().value_attr
            elif ctx.term().type_attr == 'int':
                ctx.type_attr = 'int'
                ctx.value_attr = ctx.term().value_attr * ctx.factor().value_attr
            else:
                ctx.type_attr = 'string'
                if self.is_temp(ctx.term().value_attr):
                    ctx.value_attr = ctx.term().value_attr
                    if self.is_temp(ctx.factor().value_attr):
                        self.remove_temp()
                elif self.is_temp(ctx.factor().value_attr):
                    ctx.value_attr = ctx.factor().value_attr
                else:
                    ctx.value_attr = self.create_temp()
            print('{0} = {1} * {2}'.format(ctx.value_attr, ctx.term().value_attr, ctx.factor().value_attr))
            return ctx.type_attr, ctx.value_attr

    def visitTerm_fact_divide(self, ctx: AssignmentStatement2Parser.Term_fact_divideContext):
        ctx.term().type_attr, ctx.term().value_attr = self.visit(tree=ctx.term())
        ctx.factor().type_attr, ctx.factor().value_attr = self.visit(tree=ctx.factor())
        if ctx.term().type_attr != ctx.factor().type_attr:
            print('Semantic error: Cannot multiply {0} and {1}'.format(ctx.term().type_attr, ctx.factor().type_attr))
            quit(-1)
        else:
            if ctx.term().type_attr == 'float':
                ctx.type_attr = 'float'
                ctx.value_attr = ctx.term().value_attr / ctx.factor().value_attr
            elif ctx.term().type_attr == 'int':
                ctx.type_attr = 'int'
                ctx.value_attr = int(ctx.term().value_attr / ctx.factor().value_attr)
            else:
                ctx.type_attr = 'string'
                if self.is_temp(ctx.term().value_attr):
                    ctx.value_attr = ctx.term().value_attr
                    if self.is_temp(ctx.factor().value_attr):
                        self.remove_temp()
                elif self.is_temp(ctx.factor().value_attr):
                    ctx.value_attr = ctx.factor().value_attr
                else:
                    ctx.value_attr = self.create_temp()
            print('{0} = {1} / {2}'.format(ctx.value_attr, ctx.term().value_attr, ctx.factor().value_attr))
            return ctx.type_attr, ctx.value_attr

    def visitFactor3(self, ctx: AssignmentStatement2Parser.Factor3Context):
        ctx.type_attr, ctx.value_attr = self.visit(tree=ctx.factor())
        return ctx.type_attr, ctx.value_attr

    # ------------------
    # Rule factor
    def visitFact_expr(self, ctx: AssignmentStatement2Parser.Fact_exprContext):
        return self.visit(tree=ctx.expr())

    def visitFact_id(self, ctx: AssignmentStatement2Parser.Fact_idContext):
        return 'string', ctx.ID().getText()

    def visitFact_number(self, ctx: AssignmentStatement2Parser.Fact_numberContext):
        return self.visit(tree=ctx.number())

    # ------------------
    # Rule number
    def visitNumber_float(self, ctx: AssignmentStatement2Parser.Number_floatContext):
        return 'float', float(ctx.FLOAT().getText())

    def visitNumber_int(self, ctx: AssignmentStatement2Parser.Number_intContext):
        return 'int', int(ctx.INT().getText())

ThreeAddressCodeGeneratorListener (AssignmentStatement2Listener)

Type checking and generating three address language_apps (not optimized)

Source code in language_apps\assignment_statement_v2\three_address_code_pass.py
class ThreeAddressCodeGeneratorListener(AssignmentStatement2Listener):
    """
    Type checking and generating three address language_apps (not optimized)
    """

    def __init__(self):
        print('Listener call!')
        self.temp_counter = 0

    def create_temp(self):
        self.temp_counter += 1
        return 'T' + str(self.temp_counter)

    # ------------------
    # Rule number
    def exitNumber_float(self, ctx: AssignmentStatement2Parser.Number_floatContext):
        ctx.type_attr = 'float'
        ctx.value_attr = float(ctx.getText())

    def exitNumber_int(self, ctx: AssignmentStatement2Parser.Number_intContext):
        ctx.type_attr = 'int'
        ctx.value_attr = int(ctx.getText())

    # ------------------
    # Rule factor
    def exitFact_expr(self, ctx: AssignmentStatement2Parser.Fact_exprContext):
        ctx.type_attr = ctx.expr().type_attr
        ctx.value_attr = ctx.expr().value_attr

    def exitFact_id(self, ctx: AssignmentStatement2Parser.Fact_idContext):
        ctx.type_attr = 'string'
        ctx.value_attr = str(ctx.getText())

    def exitFact_number(self, ctx: AssignmentStatement2Parser.Fact_numberContext):
        ctx.type_attr = ctx.number().type_attr
        ctx.value_attr = ctx.number().value_attr

    # ------------------
    # Rule term
    def exitTerm_fact_mutiply(self, ctx: AssignmentStatement2Parser.Term_fact_mutiplyContext):
        if ctx.term().type_attr != ctx.factor().type_attr:
            print('Semantic error: Cannot multiply {0} and {1}'.format(ctx.term().type_attr, ctx.factor().type_attr))
            quit(-1)
        else:
            if ctx.term().type_attr == 'float':
                ctx.type_attr = 'float'
                ctx.value_attr = ctx.term().value_attr * ctx.factor().value_attr
            elif ctx.term().type_attr == 'int':
                ctx.type_attr = 'int'
                ctx.value_attr = ctx.term().value_attr * ctx.factor().value_attr
            else:
                ctx.type_attr = 'string'
                ctx.value_attr = self.create_temp()
            print('{0} = {1} * {2}'.format(ctx.value_attr, ctx.term().value_attr, ctx.factor().value_attr))

    def exitTerm_fact_divide(self, ctx: AssignmentStatement2Parser.Term_fact_mutiplyContext):
        if ctx.term().type_attr != ctx.factor().type_attr:
            print('Semantic error: Cannot divide {0} and {1}'.format(ctx.term().type_attr, ctx.factor().type_attr))
            quit(-1)
        else:
            if ctx.term().type_attr == 'float':
                ctx.type_attr = 'float'
                ctx.value_attr = ctx.term().value_attr / ctx.factor().value_attr
            elif ctx.term().type_attr == 'int':
                ctx.type_attr = 'int'
                ctx.value_attr = int(ctx.term().value_attr / ctx.factor().value_attr)
            else:
                ctx.type_attr = 'string'
                ctx.value_attr = self.create_temp()
            print('{0} = {1} / {2}'.format(ctx.value_attr, ctx.term().value_attr, ctx.factor().value_attr))

    def exitFactor3(self, ctx: AssignmentStatement2Parser.Factor3Context):
        ctx.type_attr = ctx.factor().type_attr
        ctx.value_attr = ctx.factor().value_attr

    # ------------------
    # Rule expr
    def exitExpr_term_plus(self, ctx: AssignmentStatement2Parser.Expr_term_plusContext):
        if ctx.expr().type_attr != ctx.term().type_attr:
            print('Semantic error: Cannot plus {0} and {1}'.format(ctx.expr().type_attr, ctx.term().type_attr))
            quit(-1)
        else:
            if ctx.term().type_attr == 'float':
                ctx.type_attr = 'float'
                ctx.value_attr = ctx.expr().value_attr + ctx.term().value_attr
            elif ctx.term().type_attr == 'int':
                ctx.type_attr = 'int'
                ctx.value_attr = ctx.expr().value_attr + ctx.term().value_attr
            else:
                ctx.type_attr = 'string'
                ctx.value_attr = self.create_temp()
            print('{0} = {1} + {2}'.format(ctx.value_attr, ctx.expr().value_attr, ctx.term().value_attr))

    def exitExpr_term_minus(self, ctx: AssignmentStatement2Parser.Expr_term_minusContext):
        if ctx.expr().type_attr != ctx.term().type_attr:
            print('Semantic error: Cannot subtract {0} and {1}'.format(ctx.expr().type_attr, ctx.term().type_attr))
            quit(-1)
        else:
            if ctx.term().type_attr == 'float':
                ctx.type_attr = 'float'
                ctx.value_attr = ctx.expr().value_attr - ctx.term().value_attr
            elif ctx.term().type_attr == 'int':
                ctx.type_attr = 'int'
                ctx.value_attr = ctx.expr().value_attr - ctx.term().value_attr
            else:
                ctx.type_attr = 'string'
                ctx.value_attr = self.create_temp()
            print('{0} = {1} - {2}'.format(ctx.value_attr, ctx.expr().value_attr, ctx.term().value_attr))

    def exitTerm4(self, ctx: AssignmentStatement2Parser.Term4Context):
        ctx.type_attr = ctx.term().type_attr
        ctx.value_attr = ctx.term().value_attr

    # ------------------
    # Rule expr
    def exitAssign(self, ctx: AssignmentStatement2Parser.AssignContext):
        ctx.type_attr = ctx.expr().type_attr
        ctx.value_attr = ctx.expr().value_attr
        print('Assign statement: "{0} = {1}"\nAssign type: "{2}"'.format(ctx.ID().getText(), ctx.value_attr,
                                                                         ctx.type_attr))

ThreeAddressCodeGeneratorVisitor (AssignmentStatement2Visitor)

Type checking and generating three address language_apps (not optimized regarding to the number of temporary variables) Utilizing ANTLR 4.x Visitor mechanism

Source code in language_apps\assignment_statement_v2\three_address_code_pass.py
class ThreeAddressCodeGeneratorVisitor(AssignmentStatement2Visitor):
    """
    Type checking and generating three address language_apps (not optimized regarding to the number of temporary variables)
    Utilizing ANTLR 4.x Visitor mechanism
    """

    def __init__(self):
        print('Visitor call!')
        self.temp_counter = 0

    def create_temp(self):
        self.temp_counter += 1
        return 'T' + str(self.temp_counter)

    def visitStart(self, ctx: AssignmentStatement2Parser.StartContext):
        self.visit(tree=ctx.prog())

    def visitProg(self, ctx: AssignmentStatement2Parser.ProgContext):
        if ctx.getChildCount() == 2:
            self.visit(tree=ctx.prog())
        ctx.type_attr, ctx.value_attr = self.visit(tree=ctx.assign())
        return ctx.type_attr, ctx.value_attr

    def visitAssign(self, ctx: AssignmentStatement2Parser.AssignContext):
        ctx.type_attr, ctx.value_attr = self.visit(tree=ctx.expr())
        print('Assign statement: "{0} = {1}"\nAssign type: "{2}"'.format(ctx.ID().getText(), ctx.value_attr,
                                                                         ctx.type_attr))
        return ctx.type_attr, ctx.value_attr

    # ------------------
    # Rule expr
    def visitExpr_term_plus(self, ctx: AssignmentStatement2Parser.Expr_term_plusContext):
        ctx.expr().type_attr, ctx.expr().value_attr = self.visit(tree=ctx.expr())
        ctx.term().type_attr, ctx.term().value_attr = self.visit(tree=ctx.term())
        if ctx.expr().type_attr != ctx.term().type_attr:
            print('Semantic error: Cannot plus {0} and {1}'.format(ctx.expr().type_attr, ctx.term().type_attr))
            quit(-1)
        else:
            if ctx.term().type_attr == 'float':
                ctx.type_attr = 'float'
                ctx.value_attr = ctx.expr().value_attr + ctx.term().value_attr
            elif ctx.term().type_attr == 'int':
                ctx.type_attr = 'int'
                ctx.value_attr = ctx.expr().value_attr + ctx.term().value_attr
            else:
                ctx.type_attr = 'string'
                ctx.value_attr = self.create_temp()
            print('{0} = {1} + {2}'.format(ctx.value_attr, ctx.expr().value_attr, ctx.term().value_attr))
            return ctx.type_attr, ctx.value_attr

    def visitExpr_term_minus(self, ctx: AssignmentStatement2Parser.Expr_term_minusContext):
        ctx.expr().type_attr, ctx.expr().value_attr = self.visit(tree=ctx.expr())
        ctx.term().type_attr, ctx.term().value_attr = self.visit(tree=ctx.term())
        if ctx.expr().type_attr != ctx.term().type_attr:
            print('Semantic error: Cannot plus {0} and {1}'.format(ctx.expr().type_attr, ctx.term().type_attr))
            quit(-1)
        else:
            if ctx.term().type_attr == 'float':
                ctx.type_attr = 'float'
                ctx.value_attr = ctx.expr().value_attr - ctx.term().value_attr
            elif ctx.term().type_attr == 'int':
                ctx.type_attr = 'int'
                ctx.value_attr = ctx.expr().value_attr - ctx.term().value_attr
            else:
                ctx.type_attr = 'string'
                ctx.value_attr = self.create_temp()
            print('{0} = {1} - {2}'.format(ctx.value_attr, ctx.expr().value_attr, ctx.term().value_attr))
            return ctx.type_attr, ctx.value_attr

    def visitTerm4(self, ctx: AssignmentStatement2Parser.Term4Context):
        ctx.type_attr, ctx.value_attr = self.visit(ctx.term())
        return ctx.type_attr, ctx.value_attr

    # ------------------
    # Rule term
    def visitTerm_fact_mutiply(self, ctx: AssignmentStatement2Parser.Term_fact_mutiplyContext):
        ctx.term().type_attr, ctx.term().value_attr = self.visit(tree=ctx.term())
        ctx.factor().type_attr, ctx.factor().value_attr = self.visit(tree=ctx.factor())
        if ctx.term().type_attr != ctx.factor().type_attr:
            print('Semantic error: Cannot multiply {0} and {1}'.format(ctx.term().type_attr, ctx.factor().type_attr))
            quit(-1)
        else:
            if ctx.term().type_attr == 'float':
                ctx.type_attr = 'float'
                ctx.value_attr = ctx.term().value_attr * ctx.factor().value_attr
            elif ctx.term().type_attr == 'int':
                ctx.type_attr = 'int'
                ctx.value_attr = ctx.term().value_attr * ctx.factor().value_attr
            else:
                ctx.type_attr = 'string'
                ctx.value_attr = self.create_temp()
            print('{0} = {1} * {2}'.format(ctx.value_attr, ctx.term().value_attr, ctx.factor().value_attr))
            return ctx.type_attr, ctx.value_attr

    def visitTerm_fact_divide(self, ctx: AssignmentStatement2Parser.Term_fact_divideContext):
        ctx.term().type_attr, ctx.term().value_attr = self.visit(tree=ctx.term())
        ctx.factor().type_attr, ctx.factor().value_attr = self.visit(tree=ctx.factor())
        if ctx.term().type_attr != ctx.factor().type_attr:
            print('Semantic error: Cannot multiply {0} and {1}'.format(ctx.term().type_attr, ctx.factor().type_attr))
            quit(-1)
        else:
            if ctx.term().type_attr == 'float':
                ctx.type_attr = 'float'
                ctx.value_attr = ctx.term().value_attr / ctx.factor().value_attr
            elif ctx.term().type_attr == 'int':
                ctx.type_attr = 'int'
                ctx.value_attr = int(ctx.term().value_attr / ctx.factor().value_attr)
            else:
                ctx.type_attr = 'string'
                ctx.value_attr = self.create_temp()
            print('{0} = {1} / {2}'.format(ctx.value_attr, ctx.term().value_attr, ctx.factor().value_attr))
            return ctx.type_attr, ctx.value_attr

    def visitFactor3(self, ctx: AssignmentStatement2Parser.Factor3Context):
        ctx.type_attr, ctx.value_attr = self.visit(tree=ctx.factor())
        return ctx.type_attr, ctx.value_attr

    # ------------------
    # Rule factor
    def visitFact_expr(self, ctx: AssignmentStatement2Parser.Fact_exprContext):
        return self.visit(tree=ctx.expr())

    def visitFact_id(self, ctx: AssignmentStatement2Parser.Fact_idContext):
        return 'string', ctx.ID().getText()

    def visitFact_number(self, ctx: AssignmentStatement2Parser.Fact_numberContext):
        return self.visit(tree=ctx.number())

    # ------------------
    # Rule number
    def visitNumber_float(self, ctx: AssignmentStatement2Parser.Number_floatContext):
        return 'float', float(ctx.FLOAT().getText())

    def visitNumber_int(self, ctx: AssignmentStatement2Parser.Number_intContext):
        return 'int', int(ctx.INT().getText())

Abstract syntax tree (AST) generation pass

ANTLR 4.x listener and visitor implementation for intermediate code generation (abstract syntax trees)

@author: Morteza Zakeri, (http://webpages.iust.ac.ir/morteza_zakeri/) @date: 20201117

  • Compiler generator: ANTRL4.x
  • Target language(s): Python3.x,

-Changelog: -- v2.1.0 --- Add support for AST visualization with dummy nodes --- Add support for AST intermediate representation using module ast_pass --- Change compiler_pass module to three_address_code_pass -- v2.0.0 --- Add attributes for grammar rules which are used to hold type and intermediate language_apps of rules.

  • Reference: Compiler book by Dr. Saeed Parsa (http://parsa.iust.ac.ir/)
  • Course website: http://parsa.iust.ac.ir/courses/compilers/
  • Laboratory website: http://reverse.iust.ac.ir/

ASTListener (AssignmentStatement2Listener)

Source code in language_apps\assignment_statement_v2\abstract_syntax_tree_pass.py
class ASTListener(AssignmentStatement2Listener):
    """

    """
    def __init__(self):
        self.ast = AST()  # Data structure for holding the abstract syntax tree
        self.q = queue.Queue()  # Use to print and visualize AST
        self.g = nx.DiGraph()  # Use to visualize AST
        # self.q.empty()
        # print('Q=', )

    def print_tree(self, node=None, level=1):
        if node is None:
            # print()
            return
        # if not self.q.empty():
        #     print('Parent:', self.q.get().value)
        # print('\t'*level, end='')
        print()
        while node is not None:
            current_node = node
            print(current_node.value, end='')  # alt+196 = ───, alt+178=▓
            if node.child is not None:
                # self.q.put(node)
                self.g.add_edge(current_node, node.child, edge_type='C', color='red')
                self.q.put(node.child)
            else:
                tn = TreeNode(value='▓', child=None, brother=None)
                self.g.add_edge(current_node, tn, edge_type='C', color='red')
            node = node.brother
            if node is not None:
                print('\t───\t', end='')
                self.g.add_edge(current_node, node, edge_type='B', color='blue')
            else:
                tn = TreeNode(value='▓', child=None, brother=None)
                self.g.add_edge(current_node, tn, edge_type='B', color='blue')

        if not self.q.empty():
            self.print_tree(node=self.q.get(), level=level + 1)

    def print_tree2(self, node=None):
        pass

    def exitAssign(self, ctx: AssignmentStatement2Parser.AssignContext):
        idPntr = self.ast.make_node(value=ctx.ID().getText(), child=None, brother=ctx.expr().value_attr)
        assPntr = self.ast.make_node(value=":=", child=idPntr, brother=None)
        ctx.value_attr = assPntr
        self.ast.root = assPntr
        self.print_tree(node=self.ast.root, level=1)

    def exitExpr_term_plus(self, ctx: AssignmentStatement2Parser.Expr_term_plusContext):
        self.ast.add_brother(ctx.expr().value_attr, ctx.term().value_attr)
        exprPntr = self.ast.make_node(value="+", child=ctx.expr().value_attr, brother=None)
        ctx.value_attr = exprPntr

    def exitExpr_term_minus(self, ctx: AssignmentStatement2Parser.Expr_term_plusContext):
        self.ast.add_brother(ctx.expr().value_attr, ctx.term().value_attr)
        exprPntr = self.ast.make_node(value="-", child=ctx.expr().value_attr, brother=None)
        ctx.value_attr = exprPntr

    def exitTerm4(self, ctx: AssignmentStatement2Parser.Term4Context):
        ctx.value_attr = ctx.term().value_attr

    # ----------------------
    def exitTerm_fact_mutiply(self, ctx: AssignmentStatement2Parser.Term_fact_mutiplyContext):
        self.ast.add_brother(ctx.term().value_attr, ctx.factor().value_attr)
        termPntr = self.ast.make_node(value="*", child=ctx.term().value_attr, brother=None)
        ctx.value_attr = termPntr

    def exitTerm_fact_divide(self, ctx: AssignmentStatement2Parser.Term_fact_divideContext):
        self.ast.add_brother(ctx.term().value_attr, ctx.factor().value_attr)
        termPntr = self.ast.make_node(value="/", child=ctx.term().value_attr, brother=None)
        ctx.value_attr = termPntr

    def exitFactor3(self, ctx: AssignmentStatement2Parser.Factor3Context):
        ctx.value_attr = ctx.factor().value_attr

    # ---------------------
    def exitFact_expr(self, ctx: AssignmentStatement2Parser.Fact_exprContext):
        ctx.value_attr = ctx.expr().value_attr

    def exitFact_id(self, ctx: AssignmentStatement2Parser.Fact_idContext):
        idPntr = self.ast.make_node(value=ctx.ID().getText(), child=None, brother=None)
        ctx.value_attr = idPntr

    def exitFact_number(self, ctx: AssignmentStatement2Parser.Fact_numberContext):
        ctx.value_attr = ctx.number().value_attr

    # ----------------------
    def exitNumber_float(self, ctx: AssignmentStatement2Parser.Number_floatContext):
        numberPntr = self.ast.make_node(value=ctx.FLOAT().getText(), child=None, brother=None)
        ctx.value_attr = numberPntr

    def exitNumber_int(self, ctx: AssignmentStatement2Parser.Number_intContext):
        numberPntr = self.ast.make_node(value=ctx.INT().getText(), child=None, brother=None)
        ctx.value_attr = numberPntr

Main driver

Main script for grammar AssignmentStatement2 (version 2) Contains attributes for holding rule type and rule intermediate representations (AST and Three-addresses codes)

author

Morteza Zakeri, (http://webpages.iust.ac.ir/morteza_zakeri/)

date

20201029

  • Compiler generator: ANTLR 4.x
  • Target language(s): Python 3.8.x

Install pygraphviz

To draw AST as a binary tree you need to install the pygraphviz 1- download and install graphviz (for Windows/ Linux) 2- add graphviz to system path 3- install pygraphviz using the following command python -m pip install --global-option=build_ext --global-option="-IC:\Program Files\Graphviz\include" --global-option="-LC:\Program Files\Graphviz\lib" pygraphviz

Changelog

v2.1.1

  • Add visualization with Graphviz.

v2.1.0

  • Add support for AST intermediate representation using module ast_pass
  • Change compiler_pass module to three_address_code_pass

v2.0.0

  • Add attributes for grammar rules which are used to hold type and intermediate language_apps of rules.

Refs

  • Reference: Compiler book by Dr. Saeed Parsa (http://parsa.iust.ac.ir/)
  • Course website: http://parsa.iust.ac.ir/courses/compilers/
  • Laboratory website: http://reverse.iust.ac.ir/

draw(g=None)

Draw abstract syntax tree

Parameters:

Name Type Description Default
g nx.DiGraph) None

Returns:

Type Description

None

Source code in language_apps\assignment_statement_v2\assignment_statement2main.py
def draw(g: nx.DiGraph = None):
    """

    Draw abstract syntax tree

    Args:

        g (nx.DiGraph) :

    Returns:

        None

    """

    pos = graphviz_layout(
        G=g,
        prog='dot',
        # prog='circo',
    )
    # pos = hierarchy_pos(G=g,)

    # pos = nx.kamada_kawai_layout(G=g)
    # pos = nx.bipartite_layout(G=g, nodes=g.nodes)
    # pos = nx.spectral_layout(G=g)
    # pos = nx.spiral_layout(G=g)
    # pos = nx.spiral_layout(G=g)

    colors = [g[u][v]['color'] for u, v in g.edges]
    nx.draw(g,
            with_labels=False,
            node_size=500,
            node_color='black',
            edge_color=colors,
            pos=pos,
            )
    edge_labels = nx.get_edge_attributes(g, 'edge_type')
    # print('#', edge_labels)
    nx.draw_networkx_edge_labels(g, pos, edge_labels=edge_labels, )

    node_labels = {}
    for node in g.nodes():
        # set the node name as the key and the label as its value
        node_labels[node] = node.value
    nx.draw_networkx_labels(g, pos, node_labels, font_size=12, font_color='w')
    plt.savefig('../../docs/figs/ast4.png')
    plt.show()

draw_graphviz(g=None)

Visualize abstract syntax tree with Graphviz

Arges:

 g (nx.DiGraph): The abstract syntax tree to be converted to the dot file

Returns:

Type Description

None

References:

[1] https://graphviz.org/Gallery/directed/psg.html
Source code in language_apps\assignment_statement_v2\assignment_statement2main.py
def draw_graphviz(g: nx.DiGraph = None):
    """

    Visualize abstract syntax tree with Graphviz

    Arges:

         g (nx.DiGraph): The abstract syntax tree to be converted to the dot file

    Returns:

        None

    References:

        [1] https://graphviz.org/Gallery/directed/psg.html

    """
    pydot_graph = nx.drawing.nx_pydot.to_pydot(g)
    # nx.drawing.nx_pydot.write_dot(func_graph, self.cfg_path + str(self.domain_name) + '.dot')

    pydot_graph2 = pydot.Dot("", graph_type="digraph")
    nid = 0
    for u, v in g.edges:
        if u.value == u'\u2593':
            # u.value = 'NULL'
            node_u = pydot.Node(name=f'node_id_{nid}', label=u.value, shape='box')
            nid += 1
        else:
            node_u = pydot.Node(name=u.value, label=u.value, shape='box')
        if v.value == u'\u2593':
            # v.value = 'NULL'
            node_v = pydot.Node(name=f'node_id_{nid}', label=v.value, shape='box')
            nid += 1
        else:
            node_v = pydot.Node(name=v.value, label=v.value, shape='box')
        print(u.value, v.value)

        # edge_obj_dict = dict()
        # edge_obj_dict.update({'color': g[u][v]['color']})
        # edge_obj_dict.update({'label': g[u][v]['edge_type']})
        edge_ = pydot.Edge(src=node_u, dst=node_v, color=g[u][v]['color'], label=g[u][v]['edge_type'])
        pydot_graph2.add_node(node_u)
        pydot_graph2.add_node(node_v)
        pydot_graph2.add_edge(edge_)

    pydot_graph2.write('../../docs/figs/ast2gv.dot', encoding='utf-8', )
    # pydot_graph2.write_png('../../docs/figs/ast2.png')
    result = subprocess.run(
        ['dot', '-Tpng',
         '../../docs/figs/ast2gv.dot',
         '-o',
         '../../docs/figs/ast2gv.png'
         ],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE
    )
    print(result.returncode)
    error_ = result.stderr.decode('utf-8')
    print(error_)

hierarchy_pos(G, root=None, width=1.0, vert_gap=0.2, vert_loc=0, xcenter=0.5)

From Joel's answer at https://stackoverflow.com/a/29597209/2966723. Licensed under Creative Commons Attribution-Share Alike

If the graph is a tree this will return the positions to plot this in a hierarchical layout.

Parameters:

Name Type Description Default
G nx.Graph

the graph (must be a tree)

required
root nx.Node

the root node of current branch

None
width float

horizontal space allocated for this branch - avoids overlap with other branches

1.0
vert_gap float

gap between levels of hierarchy

0.2
vert_loc float

vertical location of root

0
xcenter float

horizontal location of root

0.5
Source code in language_apps\assignment_statement_v2\assignment_statement2main.py
def hierarchy_pos(G, root=None, width=1., vert_gap=0.2, vert_loc=0, xcenter=0.5):
    """
    From Joel's answer at https://stackoverflow.com/a/29597209/2966723.
    Licensed under Creative Commons Attribution-Share Alike

    If the graph is a tree this will return the positions to plot this in a
    hierarchical layout.

    Args:

        G (nx.Graph): the graph (must be a tree)

        root (nx.Node): the root node of current branch

        - if the tree is directed and this is not given, the root will be found and used

        - if the tree is directed and this is given, then the positions will be just for the descendants of this node.

        - if the tree is undirected and not given,then a random choice will be used.

        width (float): horizontal space allocated for this branch - avoids overlap with other branches

        vert_gap (float): gap between levels of hierarchy

        vert_loc (float): vertical location of root

        xcenter (float): horizontal location of root
    """

    if not nx.is_tree(G):
        raise TypeError('cannot use hierarchy_pos on a graph that is not a tree')

    if root is None:
        if isinstance(G, nx.DiGraph):
            root = next(iter(nx.topological_sort(G)))  # allows back compatibility with nx version 1.11
        else:
            root = random.choice(list(G.nodes))

    def _hierarchy_pos(G, root, width=1., vert_gap=0.2, vert_loc=0, xcenter=0.5, pos=None, parent=None):
        """

        see hierarchy_pos docstring for most arguments

        pos: a dict saying where all nodes go if they have been assigned
        parent: parent of this branch. - only affects it if non-directed

        """

        if pos is None:
            pos = {root: (xcenter, vert_loc)}
        else:
            pos[root] = (xcenter, vert_loc)
        children = list(G.neighbors(root))
        if not isinstance(G, nx.DiGraph) and parent is not None:
            children.remove(parent)
        if len(children) != 0:
            dx = width / len(children)
            nextx = xcenter - width / 2 - dx / 2
            for child in children:
                nextx += dx
                pos = _hierarchy_pos(
                    G,
                    child,
                    width=dx,
                    vert_gap=vert_gap,
                    vert_loc=vert_loc - vert_gap, xcenter=nextx,
                    pos=pos, parent=root
                )
        return pos

    return _hierarchy_pos(G, root, width, vert_gap, vert_loc, xcenter)

main(args)

Create lexer and parser and execute AST listener

Parameters:

Name Type Description Default
args Arg required

Returns:

Type Description

None

Source code in language_apps\assignment_statement_v2\assignment_statement2main.py
def main(args):
    """
    Create lexer and parser and execute AST listener

    Args:

        args (Arg):

    Returns:

        None

    """

    # Step 1: Load input source into stream
    stream = FileStream(args.file, encoding='utf8')
    print('Input language_apps:\n{0}'.format(stream))
    print('Result:')

    # Step 2: Create an instance of AssignmentStLexer
    lexer = AssignmentStatement2Lexer(stream)

    # Step 3: Convert the input source into a list of tokens
    token_stream = CommonTokenStream(lexer)

    # Step 4: Create an instance of the AssignmentStParser
    parser = AssignmentStatement2Parser(token_stream)

    # Step 5: Create parse tree
    parse_tree = parser.start()


    # Step 6: Create an instance of AssignmentStListener
    code_generator_listener = ThreeAddressCodeGeneratorListener()
    # code_generator_listener = ThreeAddressCodeGenerator2Listener()
    # ast_generator = ASTListener()

    # Step 7(a): Walk parse tree with a customized listener (Automatically)
    walker = ParseTreeWalker()
    walker.walk(t=parse_tree, listener=code_generator_listener)  # or
    # walker.walk(t=parse_tree, listener=ast_generator)

    # print('\nG=', ast_generator.g.edges)
    # draw(g=ast_generator.g)
    # draw_graphviz(g=ast_generator.g)

    # Step 7(b): Walk parse tree with a customize visitor (Manually)
    # code_generator_vistor = ThreeAddressCodeGeneratorVisitor()
    # code_generator_vistor = ThreeAddressCodeGenerator2Visitor()
    # code_generator_vistor.visitStart(ctx=parse_tree.getRuleContext())