[DevState] Added and tested with python sly

This commit is contained in:
Sven Heidemann 2020-09-24 20:44:16 +02:00
parent fa2a42dcc8
commit c537a95043
14 changed files with 521 additions and 20 deletions

223
src/Basic.py Normal file
View File

@ -0,0 +1,223 @@
from sly import Lexer
from sly import Parser
class BasicLexer(Lexer):
tokens = {NAME, NUMBER, STRING, IF, THEN, ELSE, FOR, FUN, TO, ARROW, EQEQ}
ignore = '\t '
literals = {'=', '+', '-', '/', '*', '(', ')', ',', ';'}
# Define tokens
IF = r'IF'
THEN = r'THEN'
ELSE = r'ELSE'
FOR = r'FOR'
FUN = r'FUN'
TO = r'TO'
ARROW = r'->'
NAME = r'[a-zA-Z_][a-zA-Z0-9_]*'
STRING = r'\".*?\"'
EQEQ = r'=='
@_(r'\d+')
def NUMBER(self, t):
t.value = int(t.value)
return t
@_(r'#.*')
def COMMENT(self, t):
pass
@_(r'\n+')
def newline(self, t):
self.lineno = t.value.count('\n')
class BasicParser(Parser):
tokens = BasicLexer.tokens
precedence = (
('left', '+', '-'),
('left', '*', '/'),
('right', 'UMINUS'),
)
def __init__(self):
self.env = {}
@_('')
def statement(self, p):
pass
@_('FOR var_assign TO expr THEN statement')
def statement(self, p):
return ('for_loop', ('for_loop_setup', p.var_assign, p.expr), p.statement)
@_('IF condition THEN statement ELSE statement')
def statement(self, p):
return ('if_stmt', p.condition, ('branch', p.statement0, p.statement1))
@_('FUN NAME "(" ")" ARROW statement')
def statement(self, p):
return ('fun_def', p.NAME, p.statement)
@_('NAME "(" ")"')
def statement(self, p):
return ('fun_call', p.NAME)
@_('expr EQEQ expr')
def condition(self, p):
return ('condition_eqeq', p.expr0, p.expr1)
@_('var_assign')
def statement(self, p):
return p.var_assign
@_('NAME "=" expr')
def var_assign(self, p):
return ('var_assign', p.NAME, p.expr)
@_('NAME "=" STRING')
def var_assign(self, p):
return ('var_assign', p.NAME, p.STRING)
@_('expr')
def statement(self, p):
return (p.expr)
@_('expr "+" expr')
def expr(self, p):
return ('add', p.expr0, p.expr1)
@_('expr "-" expr')
def expr(self, p):
return ('sub', p.expr0, p.expr1)
@_('expr "*" expr')
def expr(self, p):
return ('mul', p.expr0, p.expr1)
@_('expr "/" expr')
def expr(self, p):
return ('div', p.expr0, p.expr1)
@_('"-" expr %prec UMINUS')
def expr(self, p):
return p.expr
@_('NAME')
def expr(self, p):
return ('var', p.NAME)
@_('NUMBER')
def expr(self, p):
return ('num', p.NUMBER)
class BasicExecute:
def __init__(self, tree, env):
self.env = env
result = self.walkTree(tree)
if result is not None and isinstance(result, int):
print(result)
if isinstance(result, str) and result[0] == '"':
print(result)
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] == 'program':
if node[1] == None:
self.walkTree(node[2])
else:
self.walkTree(node[1])
self.walkTree(node[2])
if node[0] == 'num':
return node[1]
if node[0] == 'str':
return node[1]
if node[0] == 'if_stmt':
result = self.walkTree(node[1])
if result:
return self.walkTree(node[2][1])
return self.walkTree(node[2][2])
if node[0] == 'condition_eqeq':
return self.walkTree(node[1]) == self.walkTree(node[2])
if node[0] == 'fun_def':
self.env[node[1]] = node[2]
if node[0] == 'fun_call':
try:
return self.walkTree(self.env[node[1]])
except LookupError:
print("Undefined function '%s'" % node[1])
return 0
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.env[node[1]] = self.walkTree(node[2])
return node[1]
if node[0] == 'var':
try:
return self.env[node[1]]
except LookupError:
print("Undefined variable '" + node[1] + "' found!")
return 0
if node[0] == 'for_loop':
if node[1][0] == 'for_loop_setup':
loop_setup = self.walkTree(node[1])
loop_count = self.env[loop_setup[0]]
loop_limit = loop_setup[1]
for i in range(loop_count + 1, loop_limit + 1):
res = self.walkTree(node[2])
if res is not None:
print(res)
self.env[loop_setup[0]] = i
del self.env[loop_setup[0]]
if node[0] == 'for_loop_setup':
return (self.walkTree(node[1]), self.walkTree(node[2]))
if __name__ == '__main__':
lexer = BasicLexer()
parser = BasicParser()
env = {}
while True:
try:
text = input('basic > ')
except EOFError:
break
if text:
tokens = lexer.tokenize(text)
tree = parser.parse(tokens)
for t in tree:
print(t)
# BasicExecute(tree, env)

31
src/CCLang/Interpreter.py Normal file
View File

@ -0,0 +1,31 @@
from typing import Optional
from CCLang.Parser import Parser
from Interpreter.Validator import Validator
from CCLang.Lexer import Lexer
from Interpreter.Repo import Repo
from Interpreter.Utils import Utils
from Models.AbstractSyntaxTree.AbstractSyntaxTree import AbstractSyntaxTree
class Interpreter:
def __init__(self, repo: Repo, utils: Utils) -> None:
self.__repo = repo
self.__utils = utils
# self.__lexer = Lexer(repo, utils)
# self.__parser = Parser(repo, utils)
# self.__validator = Validator(repo, utils)
def interpret(self, line_str: str) -> None:
"""
Interprets code line
:param line_str:
:return:
"""
lexer = Lexer()
parser = Parser()
env = {}
ast = parser.parse(lexer.tokenize(line_str))
if ast is not None:
print(ast)

108
src/CCLang/Lexer.py Normal file
View File

@ -0,0 +1,108 @@
from sly import Lexer as SlyLexer
from Models.CCLang.TokenDefinition import TokenDefinition
class Lexer(SlyLexer):
# Ignored pattern
ignore = '\t '
# ignore_comment = r'(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/)|(//.*)|([#].*)'
ignore_comment = r'([#].*|(//.*))'
ignore_newline = r'\n+'
tokens = {
LIBRARY,
CLASS,
FUNCTION,
VARIABLE,
USE,
FROM,
OUTPUT,
INPUT,
LENGTH,
RANGE,
EXIT,
IF,
ELSEIF,
ELSE,
CONTINUE,
IN,
RETURN,
WHILE,
FOR,
PUBLIC,
THIS,
LBRACE,
RBRACE,
LPARAN,
RPARAN,
LBRACKET,
RBRACKET,
SEMICOLON,
COLON,
COMMA,
POINT,
PLUS,
MINUS,
ASTERIK,
SLASH,
EQUAL,
CARET,
TRUE,
FALSE,
STRING,
NUMBER,
EMPTY,
NAME
}
# token definition
LIBRARY = TokenDefinition.Library.value
CLASS = TokenDefinition.Class.value
FUNCTION = TokenDefinition.Function.value
VARIABLE = TokenDefinition.Variable.value
USE = TokenDefinition.Use.value
FROM = TokenDefinition.From.value
OUTPUT = TokenDefinition.Output.value
INPUT = TokenDefinition.Input.value
LENGTH = TokenDefinition.Length.value
RANGE = TokenDefinition.Range.value
EXIT = TokenDefinition.Exit.value
IF = TokenDefinition.If.value
ELSEIF = TokenDefinition.ElseIf.value
ELSE = TokenDefinition.Else.value
CONTINUE = TokenDefinition.Continue.value
IN = TokenDefinition.In.value
RETURN = TokenDefinition.Return.value
WHILE = TokenDefinition.While.value
FOR = TokenDefinition.For.value
PUBLIC = TokenDefinition.Public.value
THIS = TokenDefinition.This.value
LBRACE = TokenDefinition.Left_Brace.value
RBRACE = TokenDefinition.Right_Brace.value
LPARAN = TokenDefinition.Left_Parenthesis.value
RPARAN = TokenDefinition.Right_Parenthesis.value
LBRACKET = TokenDefinition.Left_Bracket.value
RBRACKET = TokenDefinition.Right_Bracket.value
SEMICOLON = TokenDefinition.Semicolon.value
COLON = TokenDefinition.Colon.value
COMMA = TokenDefinition.Comma.value
POINT = TokenDefinition.Point.value
PLUS = TokenDefinition.Plus.value
MINUS = TokenDefinition.Minus.value
ASTERIK = TokenDefinition.Asterisk.value
SLASH = TokenDefinition.Slash.value
EQUAL = TokenDefinition.Equal.value
CARET = TokenDefinition.Caret.value
TRUE = TokenDefinition.BoolTrue.value
FALSE = TokenDefinition.BoolFalse.value
STRING = TokenDefinition.String.value
NUMBER = TokenDefinition.Number.value
EMPTY = TokenDefinition.Empty.value
NAME = TokenDefinition.Name.value
def error(self, t):
print("Illegal character '%s'" % t.value[0])
# self.index += 1

61
src/CCLang/Parser.py Normal file
View File

@ -0,0 +1,61 @@
from sly import Parser as SlyParser
from CCLang.Lexer import Lexer
class Parser(SlyParser):
tokens = Lexer.tokens
#precedence = (
# ('left', '+', '-'),
# ('left', '*', '/'),
# ('right', 'UMINUS'),
#)
def __init__(self):
self.env = {}
@_('')
def statement(self, p):
pass
def error(self, p):
print(f'ERROR: {p}')
pass
# lib definition
@_('PUBLIC LIBRARY NAME LBRACE')
def statement(self, p):
return ('lib_def', p.NAME, True)
@_('LIBRARY NAME LBRACE')
def statement(self, p):
return ('lib_def', p.NAME, False)
# class definition
@_('PUBLIC CLASS NAME LBRACE')
def statement(self, p):
return ('class_def', p.NAME, True)
@_('CLASS NAME LBRACE')
def statement(self, p):
return ('class_def', p.NAME, False)
# func definition
@_('PUBLIC FUNCTION NAME LPARAN statement RPARAN COLON type LBRACE')
def statement(self, p):
return ('func_def', p.NAME, True)
@_('FUNCTION NAME LPARAN RPARAN COLON type LBRACE')
def statement(self, p):
return ('func_def', p.NAME, False)
# types
@_('EMPTY')
def type(self, p):
return ('type', p.EMPTY)
# right brace
@_('RBRACE')
def statement(self, p):
return ('end', p.RBRACE)

0
src/CCLang/__init__.py Normal file
View File

View File

@ -1,8 +1,5 @@
// hi1 # hi1
# hi2 // hi2
/*
hi3
*/
public lib Main { public lib Main {
class Program { class Program {

View File

@ -2,7 +2,6 @@ from typing import Optional
from Interpreter.Validator import Validator from Interpreter.Validator import Validator
from Interpreter.Lexer import Lexer from Interpreter.Lexer import Lexer
# from Interpreter.Parser_Old import Parser
from Interpreter.Parser import Parser from Interpreter.Parser import Parser
from Interpreter.Repo import Repo from Interpreter.Repo import Repo
from Interpreter.Utils import Utils from Interpreter.Utils import Utils
@ -27,10 +26,10 @@ class Interpreter:
tokens = [] tokens = []
ast: Optional[AbstractSyntaxTree] = None ast: Optional[AbstractSyntaxTree] = None
if self.__repo.is_error is None: if self.__repo.error is None:
tokens = self.__lexer.tokenize(line_str) tokens = self.__lexer.tokenize(line_str)
if self.__repo.is_error is None: if self.__repo.error is None:
ast = self.__parser.parse(tokens) ast = self.__parser.parse(tokens)
""" print('#####\n') """ print('#####\n')

View File

@ -70,4 +70,4 @@ class Repo:
self.bool_values = [Booleans.Right.value, Booleans.Wrong.value] self.bool_values = [Booleans.Right.value, Booleans.Wrong.value]
# runtime # runtime
self.is_error = None self.error = None

View File

@ -16,6 +16,11 @@ class Utils:
print(f'-> {text}') print(f'-> {text}')
def error(self, error: Error) -> None: def error(self, error: Error) -> None:
self.__repo.is_error = error self.__repo.error = error
print(colored(f'Error in line {self.__repo.line_number}\n{self.__repo.is_error.msg}', 'red')) print(colored(f'Error in line {self.__repo.line_number}\n{self.__repo.error.msg}', 'red'))
# exit()
def runtime_error(self, error: Error) -> None:
self.__repo.error = error
print(colored(f'{self.__repo.error.msg}', 'red'))
# exit() # exit()

View File

@ -0,0 +1,67 @@
from enum import Enum
class TokenDefinition(Enum):
""" Keywords """
# define keywords
Library = r'lib'
Class = r'class'
Function = r'func'
Variable = r'var'
Use = r'use'
From = r'from'
# builtin functions
Output = r'output'
Input = r'input'
Length = r'length'
Range = r'range'
Exit = r'exit'
# normal keywords
If = r'if'
ElseIf = r'elseif'
Else = r'else'
Continue = r'continue'
In = r'in'
Return = r'return'
# loops
While = r'while'
For = r'for'
# access
Public = r'public'
This = r'this'
""" Chars """
# format
Left_Brace = r'\{'
Right_Brace = r'\}'
Left_Parenthesis = r'\('
Right_Parenthesis = r'\)'
Left_Bracket = r'\['
Right_Bracket = r'\]'
Semicolon = r'\;'
Colon = r'\:'
Comma = r'\,'
Point = r'\.'
# expr
Plus = r'\+'
Minus = r'\-'
Asterisk = r'\*'
Slash = r'\/'
Equal = r'\='
Caret = r'\^'
""" Values """
# bool
BoolTrue = r'true'
BoolFalse = r'false'
Name = r'[a-zA-Z_][a-zA-Z0-9_]*'
String = r'\".*?\"'
Number = r'\d+'
""" Datatypes """
Empty = r'empty'

View File

View File

@ -4,6 +4,7 @@ from enum import Enum
class ErrorCodes(Enum): class ErrorCodes(Enum):
StartFailed = 'Start failed' StartFailed = 'Start failed'
FileNotFound = 'File not found' FileNotFound = 'File not found'
WrongFileType = 'Wrong file type'
Unknown = 'Unknown {}' Unknown = 'Unknown {}'
Inaccessible = '{} inaccessible' Inaccessible = '{} inaccessible'

View File

@ -1,5 +1,6 @@
from Interpreter.Validator import Validator from Interpreter.Validator import Validator
from Interpreter.Interpreter import Interpreter from Interpreter.Interpreter import Interpreter
from CCLang.Interpreter import Interpreter as CCLangInterpreter
from Interpreter.Utils import Utils from Interpreter.Utils import Utils
from Interpreter.Repo import Repo from Interpreter.Repo import Repo
@ -10,3 +11,4 @@ class ServiceInitializer:
self.repo = Repo() self.repo = Repo()
self.utils = Utils(self.repo) self.utils = Utils(self.repo)
self.interpreter = Interpreter(self.repo, self.utils) self.interpreter = Interpreter(self.repo, self.utils)
self.cclang_interpreter = CCLangInterpreter(self.repo, self.utils)

View File

@ -12,6 +12,7 @@ class Main:
self.__utils = self.__services.utils self.__utils = self.__services.utils
self.__repo = self.__services.repo self.__repo = self.__services.repo
self.__interpreter = self.__services.interpreter self.__interpreter = self.__services.interpreter
self.__cclang_interpreter = self.__services.cclang_interpreter
def console(self) -> None: def console(self) -> None:
""" """
@ -19,9 +20,9 @@ class Main:
:return: :return:
""" """
i = 0 i = 0
while self.__repo.is_error is not None: while self.__repo.error is None:
self.__repo.line_number = i + 1 self.__repo.line_number = i + 1
self.__interpreter.interpret(self.__repo.line_number, input('> ')) self.__interpreter.interpret(input('> '))
i += 1 i += 1
def files(self, file: str) -> None: def files(self, file: str) -> None:
@ -30,13 +31,19 @@ class Main:
:param file: :param file:
:return: :return:
""" """
if os.path.isfile(file): if not os.path.isfile(file):
self.__utils.runtime_error(Error(ErrorCodes.FileNotFound))
return
if not file.endswith('.cc'):
self.__utils.runtime_error(Error(ErrorCodes.WrongFileType))
return
f = open(file, 'r', encoding='utf-8').readlines() f = open(file, 'r', encoding='utf-8').readlines()
for i in range(0, len(f)): for i in range(0, len(f)):
self.__repo.line_number = i + 1 self.__repo.line_number = i + 1
self.__interpreter.interpret(f[i]) # self.__interpreter.interpret(f[i])
else: self.__cclang_interpreter.interpret(f[i])
self.__utils.is_error(Error(ErrorCodes.FileNotFound))
if __name__ == '__main__': if __name__ == '__main__':