Add support for access operator
This commit is contained in:
8
AST.py
8
AST.py
@@ -210,6 +210,14 @@ class ListItemNode(Node):
|
||||
self.children.append(value)
|
||||
|
||||
self.value = self.children[0]
|
||||
|
||||
class AccessNode(Node):
|
||||
def __init__(self, element, property, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
self.children.extend([element, property])
|
||||
|
||||
self.element = self.children[0]
|
||||
self.property = self.children[1]
|
||||
|
||||
class CloseListNode(Node):
|
||||
pass
|
||||
|
||||
@@ -8,6 +8,7 @@ from Error import RuntimeException
|
||||
from NoiseDetector import waitForSound
|
||||
from Parser import parseNote
|
||||
from Tokenizer import Token, TokenType, tokenizeNote
|
||||
from functools import reduce
|
||||
|
||||
types = {
|
||||
int: 'integer',
|
||||
@@ -19,9 +20,10 @@ types = {
|
||||
}
|
||||
|
||||
class Environment():
|
||||
def __init__(self, scopes, functions):
|
||||
def __init__(self, scopes, functions, methods):
|
||||
self.scopes = scopes
|
||||
self.functions = functions
|
||||
self.methods = methods
|
||||
self.customFunctions = {}
|
||||
self.callStack = []
|
||||
|
||||
@@ -188,6 +190,37 @@ def changeOctave(args, env):
|
||||
return args[0].withOctave(args[1])
|
||||
return # invalid signature
|
||||
|
||||
def tupletList(n, m, list):
|
||||
return [note.withDuration(note.duration * n / m) for note in list]
|
||||
|
||||
def tuplet(args, env):
|
||||
if len(args) > 2 and type(args[0]) == int and type(args[1]) == int and all(type(x) == Note for x in args[2:]):
|
||||
n = args[0] # how many notes
|
||||
m = args[1] # instead of number of notes (3-tuplet: 3 instead 2; 5-tuplet: 5 instead 4 etc.)
|
||||
return returnElementOrList(tupletList(n, m, args[2:]))
|
||||
elif len(args) == 3 and type(args[0]) == int and type(args[1]) == int and type(args[2]) == list and all(type(x) == Note for x in args[2]):
|
||||
n = args[0]
|
||||
m = args[1]
|
||||
l = args[2]
|
||||
return returnElementOrList(tupletList(n, m, l))
|
||||
else:
|
||||
pass # not valid signature
|
||||
|
||||
def combine(args, env):
|
||||
if all(type(x) == list for x in args):
|
||||
return reduce((lambda x, y: x + y), args)
|
||||
|
||||
def flat(args, env):
|
||||
return _flat(args, [])
|
||||
|
||||
def _flat(input, output = []):
|
||||
for item in input:
|
||||
if type(item) == list:
|
||||
_flat(item, output)
|
||||
else:
|
||||
output.append(item)
|
||||
return output
|
||||
|
||||
def createEnvironment():
|
||||
functions = {
|
||||
'print': doPrint,
|
||||
@@ -206,13 +239,26 @@ def createEnvironment():
|
||||
'wait': waitForSound,
|
||||
'read': read,
|
||||
'debug': lambda args, env: print(args),
|
||||
'tuplet': tuplet,
|
||||
'combine': combine,
|
||||
'flat': flat,
|
||||
'exit': exit
|
||||
|
||||
}
|
||||
|
||||
methods = {
|
||||
str: {},
|
||||
list: {},
|
||||
float: {},
|
||||
Note: {
|
||||
'synth': Synth.play
|
||||
},
|
||||
type(None): {},
|
||||
}
|
||||
|
||||
variables = {
|
||||
"bpm": 120
|
||||
}
|
||||
|
||||
return Environment([ variables ], functions)
|
||||
return Environment([ variables ], functions, methods)
|
||||
|
||||
|
||||
35
Evaluator.py
35
Evaluator.py
@@ -76,6 +76,35 @@ def _flatListNode(listItemNode, list = []):
|
||||
_flatListNode(child2, list)
|
||||
return list
|
||||
|
||||
def evaluateAccess(access, environment):
|
||||
|
||||
element = evaluate(access.element, environment)
|
||||
#TODO: narazie tylko metody działają
|
||||
e = evaluateMethodCall(element, access.property, environment)
|
||||
return e
|
||||
|
||||
def evaluateMethodCall(element, functionCall, environment):
|
||||
funcName = functionCall.identifier.identifier
|
||||
arguments = evaluateList(functionCall.arguments, environment)
|
||||
arguments.insert(0, element)
|
||||
#for name, function in environment.customFunctions.items():
|
||||
#if funcName == name:
|
||||
#if len(function['params']) != len(arguments):
|
||||
#raise RuntimeException(functionCall.pos, f"Calling '{funcName}' requires {len(function['params'])} and {len(arguments)} was passed")
|
||||
#environment.scopes.append({ function['params'][i].identifier: v for i, v in enumerate(arguments) })
|
||||
#returnValue = None
|
||||
#for node in function['body']:
|
||||
#if not isinstance(node, ReturnNode):
|
||||
#evaluate(node, environment)
|
||||
#else:
|
||||
#returnValue = evaluateReturn(node, environment)
|
||||
#environment.scopes.pop(-1)
|
||||
#return returnValue
|
||||
for name, definition in environment.methods[type(element)].items():
|
||||
if name == funcName:
|
||||
return definition(arguments, environment)
|
||||
raise RuntimeException(functionCall.pos, f"Method '{funcName}' does not exist")
|
||||
|
||||
def evaluateFunctionCall(functionCall, environment):
|
||||
funcName = functionCall.identifier.identifier
|
||||
arguments = evaluateList(functionCall.arguments, environment)
|
||||
@@ -157,7 +186,7 @@ def evaluateColon(colon, environment):
|
||||
return list(range(colon.a.value, colon.b.value+1))
|
||||
raise RuntimeException(colon.pos, "Invalid colon arguments")
|
||||
|
||||
def evaluate(input, environment):
|
||||
def evaluate(input, environment):
|
||||
if isinstance(input, Program):
|
||||
return evaluateProgram(input, environment)
|
||||
if isinstance(input, IntegerLiteralNode):
|
||||
@@ -172,7 +201,9 @@ def evaluate(input, environment):
|
||||
return evaluateFunctionDefinition(input, environment)
|
||||
if isinstance(input, FunctionCallNode):
|
||||
return evaluateFunctionCall(input, environment)
|
||||
if isinstance(input, CommaNode):
|
||||
if isinstance(input, AccessNode):
|
||||
return evaluateAccess(input, environment)
|
||||
if isinstance(input, CommaNode):
|
||||
return evaluateComma(input, environment)
|
||||
if isinstance(input, BlockNode):
|
||||
return evaluateBlock(input, environment)
|
||||
|
||||
23
Parser.py
23
Parser.py
@@ -108,7 +108,7 @@ def parseNote(input, parent):
|
||||
durationString += value[consumedChars]
|
||||
consumedChars += 1
|
||||
duration = int(durationString)
|
||||
if consumedChars < len(value) and value[consumedChars] == '.':
|
||||
if consumedChars < len(value) and value[consumedChars] == 'd':
|
||||
dot = True
|
||||
consumedChars += 1
|
||||
|
||||
@@ -280,6 +280,26 @@ def parseReturn(input, parent):
|
||||
return node
|
||||
return None
|
||||
|
||||
# access -> expr '.' expr
|
||||
#TODO: dodać dziedziczenie wszystkich expressions po jednym typie ExpressionNode
|
||||
# i potem sprawdzać przy wszystkich parent.pop(-1) czy pobrany z parenta element
|
||||
# jest rzeczywiście wyrażeniem, bo teraz możliwe jest np. {}.fun()
|
||||
def parseAccess(input, parent):
|
||||
if input.current().type == TokenType.DOT:
|
||||
token = input.current()
|
||||
input.ahead()
|
||||
|
||||
element = parent.pop(-1)
|
||||
|
||||
property = parseExpression(input, parent)
|
||||
|
||||
node = AccessNode(element, property, parent, token.pos)
|
||||
element.parent = node
|
||||
property.parent = node
|
||||
|
||||
return node
|
||||
return None
|
||||
|
||||
def parseStatement(input, parent):
|
||||
stmt = runParsers(input, parent, [
|
||||
parseBlock,
|
||||
@@ -316,6 +336,7 @@ def parseExpression(input, parent):
|
||||
parseNote,
|
||||
parseList,
|
||||
parseIdentifierOrFunctionCallOrAssignment,
|
||||
parseAccess,
|
||||
])
|
||||
|
||||
colon = parseColon(expr, input, parent)
|
||||
|
||||
64
Tokenizer.py
64
Tokenizer.py
@@ -67,6 +67,7 @@ class TokenType(Enum):
|
||||
MINUS = 15
|
||||
FUNCTION = 16
|
||||
RETURN = 17
|
||||
DOT = 18
|
||||
|
||||
class Token:
|
||||
def __init__(self, type, value, pos):
|
||||
@@ -79,19 +80,18 @@ class Token:
|
||||
return self.__str__()
|
||||
|
||||
def tokenizeOpenParen(input, current, line):
|
||||
if input[current] == '(':
|
||||
return (1, Token(TokenType.OPEN_PAREN, input[current], (line, current)))
|
||||
return tokenizeChar(TokenType.OPEN_PAREN, '(', input, current, line)
|
||||
|
||||
def tokenizeChar(type, char, input, current, line):
|
||||
if input[current] == char:
|
||||
return (1, Token(type, input[current], (line, current)))
|
||||
return (0, None)
|
||||
|
||||
def tokenizeCloseParen(input, current, line):
|
||||
if input[current] == ')':
|
||||
return (1, Token(TokenType.CLOSE_PAREN, input[current], (line, current)))
|
||||
return (0, None)
|
||||
return tokenizeChar(TokenType.CLOSE_PAREN, ')', input, current, line)
|
||||
|
||||
def tokenizeAsterisk(input, current, line):
|
||||
if input[current] == '*':
|
||||
return (1, Token(TokenType.ASTERISK, input[current], (line, current)))
|
||||
return (0, None)
|
||||
return tokenizeChar(TokenType.ASTERISK, '*', input, current, line)
|
||||
|
||||
def tokenizeString(input, current, line):
|
||||
if input[current] == '"':
|
||||
@@ -99,7 +99,7 @@ def tokenizeString(input, current, line):
|
||||
char = ''
|
||||
consumedChars = 1
|
||||
while char != '"':
|
||||
if char is None:
|
||||
if char is None: #TODO!!!
|
||||
print("String not terminated")
|
||||
char = input[current + consumedChars]
|
||||
value += char
|
||||
@@ -123,32 +123,22 @@ def tokenizeIdentifier(input, current, line):
|
||||
return tokenizeRegexPattern(TokenType.IDENTIFIER, r'\w', input, current, line)
|
||||
|
||||
def tokenizeComma(input, current, line):
|
||||
if input[current] == ',':
|
||||
return (1, Token(TokenType.COMMA, input[current], (line, current)))
|
||||
return (0, None)
|
||||
return tokenizeChar(TokenType.COMMA, ',', input, current, line)
|
||||
|
||||
def tokenizeInteger(input, current, line):
|
||||
return tokenizeRegexPattern(TokenType.INTEGER, r'\d', input, current, line)
|
||||
|
||||
def tokenizeOpenBracket(input, current, line):
|
||||
if input[current] == '{':
|
||||
return (1, Token(TokenType.OPEN_BRACKET, input[current], (line, current)))
|
||||
return (0, None)
|
||||
return tokenizeChar(TokenType.OPEN_BRACKET, '{', input, current, line)
|
||||
|
||||
def tokenizeCloseBracket(input, current, line):
|
||||
if input[current] == '}':
|
||||
return (1, Token(TokenType.CLOSE_BRACKET, input[current], (line, current)))
|
||||
return (0, None)
|
||||
return tokenizeChar(TokenType.CLOSE_BRACKET, '}', input, current, line)
|
||||
|
||||
def tokenizeAssign(input, current, line):
|
||||
if input[current] == '=':
|
||||
return (1, Token(TokenType.ASSIGN, input[current], (line, current)))
|
||||
return (0, None)
|
||||
return tokenizeChar(TokenType.ASSIGN, '=', input, current, line)
|
||||
|
||||
def tokenizeColon(input, current, line):
|
||||
if input[current] == ':':
|
||||
return (1, Token(TokenType.COLON, input[current], (line, current)))
|
||||
return (0, None)
|
||||
return tokenizeChar(TokenType.COLON, ':', input, current, line)
|
||||
|
||||
def tokenizeComment(input, current, line):
|
||||
if input[current] == '#':
|
||||
@@ -180,26 +170,26 @@ def tokenizeNote(input, current, line):
|
||||
consumedChars += 1
|
||||
|
||||
if current+consumedChars < len(input) and input[current+consumedChars] == '.':
|
||||
value += input[current+consumedChars]
|
||||
duration = input[current+consumedChars]
|
||||
consumedChars += 1
|
||||
while current+consumedChars < len(input) and re.match(r'\d', input[current+consumedChars]):
|
||||
value += input[current+consumedChars]
|
||||
duration += input[current+consumedChars]
|
||||
consumedChars += 1
|
||||
if current+consumedChars < len(input) and input[current+consumedChars] == '.':
|
||||
value += input[current+consumedChars]
|
||||
if current+consumedChars < len(input) and input[current+consumedChars] == 'd':
|
||||
duration += input[current+consumedChars]
|
||||
consumedChars += 1
|
||||
if len(duration) > 1:
|
||||
value += duration
|
||||
else:
|
||||
consumedChars -= 1
|
||||
return (consumedChars, Token(TokenType.NOTE, value, (line, current)))
|
||||
return (0, None)
|
||||
|
||||
def tokenizePercent(input, current, line):
|
||||
if input[current] == '%':
|
||||
return (1, Token(TokenType.PERCENT, input[current], (line, current)))
|
||||
return (0, None)
|
||||
return tokenizeChar(TokenType.PERCENT, '%', input, current, line)
|
||||
|
||||
def tokenizeMinus(input, current, line):
|
||||
if input[current] == '-':
|
||||
return (1, Token(TokenType.MINUS, input[current], (line, current)))
|
||||
return (0, None)
|
||||
return tokenizeChar(TokenType.MINUS, '-', input, current, line)
|
||||
|
||||
def tokenizeFunction(input, current, line):
|
||||
return tokenizeKeyword(TokenType.FUNCTION, 'function', input, current, line)
|
||||
@@ -212,6 +202,9 @@ def tokenizeKeyword(type, keyword, input, current, line):
|
||||
def tokenizeReturn(input, current, line):
|
||||
return tokenizeKeyword(TokenType.RETURN, 'return', input, current, line)
|
||||
|
||||
def tokenizeDot(input, current, line):
|
||||
return tokenizeChar(TokenType.DOT, '.', input, current, line)
|
||||
|
||||
tokenizers = (
|
||||
tokenizeOpenParen,
|
||||
tokenizeCloseParen,
|
||||
@@ -229,8 +222,9 @@ tokenizers = (
|
||||
tokenizeColon,
|
||||
tokenizePercent,
|
||||
tokenizeMinus,
|
||||
tokenizeDot,
|
||||
tokenizeComment,
|
||||
tokenizeWhitespaces
|
||||
tokenizeWhitespaces,
|
||||
)
|
||||
|
||||
def doTokenize(lines):
|
||||
|
||||
5
main.py
5
main.py
@@ -12,9 +12,9 @@ if __name__ == "__main__":
|
||||
|
||||
env = createEnvironment()
|
||||
|
||||
tokens = tokenize(lines)
|
||||
tokens = tokenize(lines)
|
||||
|
||||
ast = parse(tokens)
|
||||
ast = parse(tokens)
|
||||
|
||||
evaluate(ast, env)
|
||||
except SyntaxException as e:
|
||||
@@ -23,3 +23,4 @@ if __name__ == "__main__":
|
||||
print(e.msg)
|
||||
except KeyboardInterrupt:
|
||||
print("Program interrupted")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user