From 8313d2dcfd8c4798d716721f70a8ec5ab266f6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Pluta?= Date: Wed, 3 Jul 2019 00:38:08 +0200 Subject: [PATCH] Add support for access operator --- AST.py | 8 +++++++ Environment.py | 50 +++++++++++++++++++++++++++++++++++++-- Evaluator.py | 35 +++++++++++++++++++++++++-- Parser.py | 23 +++++++++++++++++- Tokenizer.py | 64 +++++++++++++++++++++++--------------------------- main.py | 5 ++-- 6 files changed, 143 insertions(+), 42 deletions(-) diff --git a/AST.py b/AST.py index acaa4b0..6016c3b 100644 --- a/AST.py +++ b/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 diff --git a/Environment.py b/Environment.py index 1c3d5b8..195198f 100644 --- a/Environment.py +++ b/Environment.py @@ -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) diff --git a/Evaluator.py b/Evaluator.py index 9056022..98e13c7 100644 --- a/Evaluator.py +++ b/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) diff --git a/Parser.py b/Parser.py index 65358cc..6bba158 100644 --- a/Parser.py +++ b/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) diff --git a/Tokenizer.py b/Tokenizer.py index 730eadc..5b20bc3 100644 --- a/Tokenizer.py +++ b/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): diff --git a/main.py b/main.py index 466f21f..759c7bc 100644 --- a/main.py +++ b/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") +