I'm working on making a simple interpreted programming language using SLY to generate a AST which I will interpret without using SLY.
Currently I have been able to generate all my tokens and giving them to the parser, but it can't recognize any rule, only empty ones.
Lexer:
from .sly.lex import Lexer
class ALexer(Lexer):
tokens = { ID, NUMBER, STRING, BOOL, PLUS, TIMES, MINUS, DIVIDE, ASSIGN, LPAREN, RPAREN, COMMA, NL }
ignore = ' \t'
# Tokens
@_(r'\d+[[.]?\d*[f]?]?')
def NUMBER(self, t):
endswithF = t.value.endswith('f')
isfloat = endswithF or t.value.find('.') != -1
t.value = float(t.value[:-1] if endswithF else t.value) if isfloat else int(t.value) # Convert to a numeric value
return t
ID = r'[a-zA-Z_][a-zA-Z0-9_]*'
ID['true'] = BOOL
ID['false'] = BOOL
@_(r'".*"')
def STRING(self, t):
t.value = t.value[1:-1]
return t
# Special symbols
PLUS = r'\+'
MINUS = r'-'
TIMES = r'\*'
DIVIDE = r'/'
ASSIGN = r'='
LPAREN = r'\('
RPAREN = r'\)'
COMMA = r','
@_(r'true', r'false')
def BOOL(self, t):
t.value = t.value == 'true'
return t
@_(r'\n')
def NL(self, t):
self.lineno += 1
def error(self, t):
print("Illegal character '%s'" % t.value[0])
self.index += 1
Util classes:
from enum import Enum
class SupportedOp(str, Enum):
CONSTANT = "CONSTANT",
VARIABLE = "VARIABLE",
ARGS_LIST = "ARGS_LIST",
FUNC_CALL = "FUNC_CALL",
STATEMENT = "STATEMENT",
STATEMENT_LIST = "STATEMENT_LIST",
PROGRAM = "PROGRAM",
SUM = '+',
SUB = '-',
DIV = '/',
MUL = '*',
ASSIGNMENT = '='
class ParsedOp(dict):
def __init__(self, op: SupportedOp, *values):
dict.__init__(self, op=op, values=values)
Parser:
class AParser(Parser):
debugfile = 'parser.out'
tokens = ALexer.tokens
precedence = (
#('nonassoc', LESSTHAN, GREATERTHAN), # Nonassociative operators
('left', PLUS, MINUS),
('left', TIMES, DIVIDE)
#('right', UMINUS), # Unary minus operator
)
@_('statement_list')
def program(self, p):
print('program', p[0])
return ParsedOp(SupportedOp.PROGRAM, p[0])
@_('statement BACK_IN_LINES statement_list')
def statement_list(self, p):
print('statement_list', p[0], p[1], p[2])
lst: list = p[1].values[0]
lst.append(p[0])
return ParsedOp(SupportedOp.STATEMENT_LIST, lst)
@_('statement')
def statement_list(self, p):
print('statement_list', p[0])
return ParsedOp(SupportedOp.STATEMENT_LIST, [p[0]])
@_('empty')
def statement_list(self, p):
print('empty statement_list')
return ParsedOp(SupportedOp.STATEMENT_LIST, [])
@_('NL BACK_IN_LINES', 'NL')
def BACK_IN_LINES(self, p):
print('BACK_IN_LINES', p[0])
#unused
return 'NL'
@_('assignment', 'expr')
def statement(self, p):
print('statement', p[0])
return ParsedOp(SupportedOp.STATEMENT, p[0])
@_('ID ASSIGN expr')
def assignment(self, p):
print('assignment', p[0], p[1], p[2])
return ParsedOp(SupportedOp.ASSIGNMENT, p[0], p[2])
@_('expr COMMA expr_list')
def expr_list(self, p):
print('expr_list', p[0], p[1], p[2])
lst: list = p[1].values[0]
lst.append(p[0])
return ParsedOp(SupportedOp.ARGS_LIST, lst)
@_('expr')
def expr_list(self, p):
print('expr_list', p[0])
return ParsedOp(SupportedOp.ARGS_LIST, [p[0]])
@_('empty')
def expr_list(self, p):
print('empty expr_list')
return ParsedOp(SupportedOp.ARGS_LIST, [])
@_('constant')
def expr(self, p):
print('expr', p[0])
return p[0]
@_('ID')
def expr(self, p):
print('expr', p[0])
return ParsedOp(SupportedOp.VARIABLE, p[0])
@_('LPAREN expr RPAREN')
def expr(self, p):
print('expr', p[0], p[1], p[2])
return p[1]
@_('ID LPAREN expr_list RPAREN')
def expr(self, p):
print('expr', p[0], p[1], p[2], p[3])
#if exists p.ID in functions
return ParsedOp(SupportedOp.FUNC_CALL, p.ID, p.expr_list)
@_( 'expr PLUS expr',
'expr MINUS expr',
'expr TIMES expr',
'expr DIVIDE expr')
def expr(self, p):
print('expr', p[0], p[1], p[2])
return ParsedOp(SupportedOp(p[1]), p[0], p[2])
@_('NUMBER', 'STRING', 'BOOL')
def constant(self, p):
print('constant', p[0])
return ParsedOp(SupportedOp.CONSTANT, p[0])
@_('')
def empty(self, p):
print('empty')
pass
def error(self, p):
if p:
print("Syntax error at token", p.type)
# Just discard the token and tell the parser it's okay.
self.errok()
else:
print("Syntax error at EOF")
I don't know if all my rules are ok but I commented every rule leaving uncommented only the "constant" rule which is straightforward, still can't recognize it.
Main:
tokens_out = lexer.ALexer().tokenize(event.get("code"))
tree = AParser().parse(tokens_out)
All my tokens are well recognized so the Lexer is ok. The parser can't recognize any rule. Any idea?