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
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).