Search code examples
pythonparsinglexersly

How to avoid printing a variable without using the say function in python sly?


So I am using a python package sly which has a lexer and parser class. I am making my own programming language called NoobPy. So currently, the code will open test.noob and read each line and parse it. Now, if I were to define a variable, let's say x, and just write x in a line, it would print it, and I don't want that. I want it to print only if it's passed in the say function which I made. Lexer class

class NoobpyLexer(Lexer):
    tokens = {NUMBER, STRING, FALSE, TRUE, NAME, WHILE, IF, ELSE, SAY,
              PLUS, MINUS, TIMES, DIVIDE, ASSIGN,
              EQ, LT, LE, GT, GE, NEQ}

    literals = {'(', ')', ':'}

    # String containing ignored characters
    ignore = ' \t'

    # Regular expression rules for tokens
    STRING = r'\".*?\"'
    PLUS = r'\+'
    MINUS = r'-'
    TIMES = r'\*'
    DIVIDE = r'/'
    EQ = r'=='
    NEQ = r'!='
    ASSIGN = r'='
    LE = r'<='
    GE = r'>='
    LT = r'<'
    GT = r'>'

    @_(r'\d+')
    def NUMBER(self, t):
        t.value = int(t.value)
        return t

    # @_(r'^((true$|false$)$)')
    # def BOOL(self, t):
    #     return t

    @_(r'true')
    def TRUE(self, t):
        return t

    @_(r'false')
    def FALSE(self, t):
        return t

    # Identifiers and keywords
    NAME = r'\b(?!((true$|false$)$)\b)\w+'  # [a-zA-Z_][a-zA-Z0-9_]*$
    NAME['if'] = IF
    NAME['else'] = ELSE
    NAME['while'] = WHILE
    NAME['say'] = SAY

    ignore_comment = r'\#.*'

    # Line number tracking
    @_(r'\n+')
    def ignore_newline(self, t):
        self.lineno += t.value.count('\n')

    def error(self, t):
        print("t: ", t)
        print('Line %d: Bad character %r' % (self.lineno, t.value[0]))
        self.index += 1

Parser class

class NoobpyParser(Parser):
    # Get the token list from the lexer (required)
    tokens = NoobpyLexer.tokens
    log = logging.getLogger()
    log.setLevel(logging.ERROR)
    # debugfile = 'parser.out'

    precedence = (
        ('left', PLUS, MINUS),
        ('left', TIMES, DIVIDE),
        ('right', UMINUS)
    )

    def __init__(self):
        self.variables = {}

    @_('')
    def statement(self, p):
        pass

    @_('SAY expr')
    def statement(self, p):
        return 'say', p.expr

    @_('NAME')
    def expr(self, p):
        return 'var', p.NAME

    @_('var_assign')
    def statement(self, p):
        return p.var_assign

    @_('NAME ASSIGN expr')
    def var_assign(self, p):
        return 'var_assign', p.NAME, p.expr

    @_('expr')
    def statement(self, p):
        return p.expr

    @_('expr PLUS expr')
    def expr(self, p):
        return 'add', p.expr0, p.expr1

    @_('expr MINUS expr')
    def expr(self, p):
        return 'sub', p.expr0, p.expr1

    @_('expr TIMES expr')
    def expr(self, p):
        return 'mul', p.expr0, p.expr1

    @_('expr DIVIDE expr')
    def expr(self, p):
        return 'div', p.expr0, p.expr1

    @_('MINUS expr %prec UMINUS')
    def expr(self, p):
        expression = list(p.expr)
        if isinstance(expression[1], tuple):
            res = 0
            for i in expression[1]:
                res += i
            expression[1] = res
        expression[1] = -expression[1]
        return expression

    @_('expr EQ expr')
    def expr(self, p):
        return 'eq', p.expr0, p.expr1

    @_('"(" expr ")"')
    def expr(self, p):
        return p.expr

    @_('NUMBER')
    def expr(self, p):
        return 'num', p.NUMBER

    @_('STRING')
    def expr(self, p):
        return 'str', p.STRING

    @_('TRUE')
    def expr(self, p):
        return p.TRUE

    @_('FALSE')
    def expr(self, p):
        return p.FALSE

Execute class

class NoobpyExecute:
    def __init__(self, tree, variables):
        self.variables = variables
        result = self.walkTree(tree)
        if result is None:
            pass
        elif result is not None and type(result) in [int, float]:
            print(result)
        elif isinstance(result, str):
            print(result)
        elif isinstance(result, bool):
            if result is True:
                print("true")
            else:
                print("false")

    def walkTree(self, node):
        if isinstance(node, int):
            return node
        if isinstance(node, str):
            return node

        if node is None:
            return None

        if node[0] == 'say':
            return self.walkTree(node[1])
        if node[0] == 'num':
            return node[1]
        if node[0] == 'str':
            return node[1]
        if node[0] == 'eq':
            return self.walkTree(node[1]) == self.walkTree(node[2])

        if node[0] == 'add':
            return self.walkTree(node[1]) + self.walkTree(node[2])
        elif node[0] == 'sub':
            return self.walkTree(node[1]) - self.walkTree(node[2])
        elif node[0] == 'mul':
            return self.walkTree(node[1]) * self.walkTree(node[2])
        elif node[0] == 'div':
            return self.walkTree(node[1]) / self.walkTree(node[2])

        if node[0] == 'var_assign':
            self.variables[node[1]] = self.walkTree(node[2])

        if node[0] == 'var':
            try:
                return self.variables[node[1]]
            except LookupError:
                print("Undefined name '{}'".format(node[1]))
                return 0

This:

if __name__ == '__main__':
    lexer = NoobpyLexer()
    parser = NoobpyParser()
    variables = {}

    args = argparse.ArgumentParser()
    args.add_argument(metavar='filename', dest="filename", type=str, help='name of the file you want to run')
    args = args.parse_args()

    with open(args.filename) as file:
        for line in file.readlines():
            tree = parser.parse(lexer.tokenize(line))
            NoobpyExecute(tree, variables)

Example test.noob

x = 2
x

^ prints 2


Solution

  • In your NoobPy constructor, you print out the result of evaluating the syntax tree (unless it's None, which will happen if you evaluate an assignment):

            if result is None:
                pass
            elif result is not None and type(result) in [int, float]:
                print(result)
            elif isinstance(result, str):
                print(result)
            elif isinstance(result, bool):
                if result is True:
                    print("true")
                else:
                    print("false")
    

    Leaving aside the fact that all that could be simplified, the code seems to clearly indicate that the intention of printing the result of the evaluation. If you now don't want to print the result of the evaluation, you shouldn't print the result of the evaluation.

    When you see a say function in the tree, you return the result of evaluating its argument:

            if node[0] == 'say':
                return self.walkTree(node[1]) 
    
    

    If you want the say function to have the effect of printing the result of the evaluation of its argument, you should print the result of the evaluation of its argument instead of returning the result of the evaluation of its argument (or as well as returning the result, depending on what you think the semantics of say are).