Add support for access operator

This commit is contained in:
Bartłomiej Pluta
2019-07-03 00:38:08 +02:00
parent 7b176b66c8
commit 8313d2dcfd
6 changed files with 143 additions and 42 deletions

8
AST.py
View File

@@ -211,5 +211,13 @@ class ListItemNode(Node):
self.value = self.children[0] 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): class CloseListNode(Node):
pass pass

View File

@@ -8,6 +8,7 @@ from Error import RuntimeException
from NoiseDetector import waitForSound from NoiseDetector import waitForSound
from Parser import parseNote from Parser import parseNote
from Tokenizer import Token, TokenType, tokenizeNote from Tokenizer import Token, TokenType, tokenizeNote
from functools import reduce
types = { types = {
int: 'integer', int: 'integer',
@@ -19,9 +20,10 @@ types = {
} }
class Environment(): class Environment():
def __init__(self, scopes, functions): def __init__(self, scopes, functions, methods):
self.scopes = scopes self.scopes = scopes
self.functions = functions self.functions = functions
self.methods = methods
self.customFunctions = {} self.customFunctions = {}
self.callStack = [] self.callStack = []
@@ -188,6 +190,37 @@ def changeOctave(args, env):
return args[0].withOctave(args[1]) return args[0].withOctave(args[1])
return # invalid signature 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(): def createEnvironment():
functions = { functions = {
'print': doPrint, 'print': doPrint,
@@ -206,13 +239,26 @@ def createEnvironment():
'wait': waitForSound, 'wait': waitForSound,
'read': read, 'read': read,
'debug': lambda args, env: print(args), 'debug': lambda args, env: print(args),
'tuplet': tuplet,
'combine': combine,
'flat': flat,
'exit': exit 'exit': exit
} }
methods = {
str: {},
list: {},
float: {},
Note: {
'synth': Synth.play
},
type(None): {},
}
variables = { variables = {
"bpm": 120 "bpm": 120
} }
return Environment([ variables ], functions) return Environment([ variables ], functions, methods)

View File

@@ -76,6 +76,35 @@ def _flatListNode(listItemNode, list = []):
_flatListNode(child2, list) _flatListNode(child2, list)
return 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): def evaluateFunctionCall(functionCall, environment):
funcName = functionCall.identifier.identifier funcName = functionCall.identifier.identifier
arguments = evaluateList(functionCall.arguments, environment) arguments = evaluateList(functionCall.arguments, environment)
@@ -172,6 +201,8 @@ def evaluate(input, environment):
return evaluateFunctionDefinition(input, environment) return evaluateFunctionDefinition(input, environment)
if isinstance(input, FunctionCallNode): if isinstance(input, FunctionCallNode):
return evaluateFunctionCall(input, environment) return evaluateFunctionCall(input, environment)
if isinstance(input, AccessNode):
return evaluateAccess(input, environment)
if isinstance(input, CommaNode): if isinstance(input, CommaNode):
return evaluateComma(input, environment) return evaluateComma(input, environment)
if isinstance(input, BlockNode): if isinstance(input, BlockNode):

View File

@@ -108,7 +108,7 @@ def parseNote(input, parent):
durationString += value[consumedChars] durationString += value[consumedChars]
consumedChars += 1 consumedChars += 1
duration = int(durationString) duration = int(durationString)
if consumedChars < len(value) and value[consumedChars] == '.': if consumedChars < len(value) and value[consumedChars] == 'd':
dot = True dot = True
consumedChars += 1 consumedChars += 1
@@ -280,6 +280,26 @@ def parseReturn(input, parent):
return node return node
return None 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): def parseStatement(input, parent):
stmt = runParsers(input, parent, [ stmt = runParsers(input, parent, [
parseBlock, parseBlock,
@@ -316,6 +336,7 @@ def parseExpression(input, parent):
parseNote, parseNote,
parseList, parseList,
parseIdentifierOrFunctionCallOrAssignment, parseIdentifierOrFunctionCallOrAssignment,
parseAccess,
]) ])
colon = parseColon(expr, input, parent) colon = parseColon(expr, input, parent)

View File

@@ -67,6 +67,7 @@ class TokenType(Enum):
MINUS = 15 MINUS = 15
FUNCTION = 16 FUNCTION = 16
RETURN = 17 RETURN = 17
DOT = 18
class Token: class Token:
def __init__(self, type, value, pos): def __init__(self, type, value, pos):
@@ -79,19 +80,18 @@ class Token:
return self.__str__() return self.__str__()
def tokenizeOpenParen(input, current, line): def tokenizeOpenParen(input, current, line):
if input[current] == '(': return tokenizeChar(TokenType.OPEN_PAREN, '(', input, current, line)
return (1, Token(TokenType.OPEN_PAREN, input[current], (line, current)))
def tokenizeChar(type, char, input, current, line):
if input[current] == char:
return (1, Token(type, input[current], (line, current)))
return (0, None) return (0, None)
def tokenizeCloseParen(input, current, line): def tokenizeCloseParen(input, current, line):
if input[current] == ')': return tokenizeChar(TokenType.CLOSE_PAREN, ')', input, current, line)
return (1, Token(TokenType.CLOSE_PAREN, input[current], (line, current)))
return (0, None)
def tokenizeAsterisk(input, current, line): def tokenizeAsterisk(input, current, line):
if input[current] == '*': return tokenizeChar(TokenType.ASTERISK, '*', input, current, line)
return (1, Token(TokenType.ASTERISK, input[current], (line, current)))
return (0, None)
def tokenizeString(input, current, line): def tokenizeString(input, current, line):
if input[current] == '"': if input[current] == '"':
@@ -99,7 +99,7 @@ def tokenizeString(input, current, line):
char = '' char = ''
consumedChars = 1 consumedChars = 1
while char != '"': while char != '"':
if char is None: if char is None: #TODO!!!
print("String not terminated") print("String not terminated")
char = input[current + consumedChars] char = input[current + consumedChars]
value += char value += char
@@ -123,32 +123,22 @@ def tokenizeIdentifier(input, current, line):
return tokenizeRegexPattern(TokenType.IDENTIFIER, r'\w', input, current, line) return tokenizeRegexPattern(TokenType.IDENTIFIER, r'\w', input, current, line)
def tokenizeComma(input, current, line): def tokenizeComma(input, current, line):
if input[current] == ',': return tokenizeChar(TokenType.COMMA, ',', input, current, line)
return (1, Token(TokenType.COMMA, input[current], (line, current)))
return (0, None)
def tokenizeInteger(input, current, line): def tokenizeInteger(input, current, line):
return tokenizeRegexPattern(TokenType.INTEGER, r'\d', input, current, line) return tokenizeRegexPattern(TokenType.INTEGER, r'\d', input, current, line)
def tokenizeOpenBracket(input, current, line): def tokenizeOpenBracket(input, current, line):
if input[current] == '{': return tokenizeChar(TokenType.OPEN_BRACKET, '{', input, current, line)
return (1, Token(TokenType.OPEN_BRACKET, input[current], (line, current)))
return (0, None)
def tokenizeCloseBracket(input, current, line): def tokenizeCloseBracket(input, current, line):
if input[current] == '}': return tokenizeChar(TokenType.CLOSE_BRACKET, '}', input, current, line)
return (1, Token(TokenType.CLOSE_BRACKET, input[current], (line, current)))
return (0, None)
def tokenizeAssign(input, current, line): def tokenizeAssign(input, current, line):
if input[current] == '=': return tokenizeChar(TokenType.ASSIGN, '=', input, current, line)
return (1, Token(TokenType.ASSIGN, input[current], (line, current)))
return (0, None)
def tokenizeColon(input, current, line): def tokenizeColon(input, current, line):
if input[current] == ':': return tokenizeChar(TokenType.COLON, ':', input, current, line)
return (1, Token(TokenType.COLON, input[current], (line, current)))
return (0, None)
def tokenizeComment(input, current, line): def tokenizeComment(input, current, line):
if input[current] == '#': if input[current] == '#':
@@ -180,26 +170,26 @@ def tokenizeNote(input, current, line):
consumedChars += 1 consumedChars += 1
if current+consumedChars < len(input) and input[current+consumedChars] == '.': if current+consumedChars < len(input) and input[current+consumedChars] == '.':
value += input[current+consumedChars] duration = input[current+consumedChars]
consumedChars += 1 consumedChars += 1
while current+consumedChars < len(input) and re.match(r'\d', input[current+consumedChars]): while current+consumedChars < len(input) and re.match(r'\d', input[current+consumedChars]):
value += input[current+consumedChars] duration += input[current+consumedChars]
consumedChars += 1 consumedChars += 1
if current+consumedChars < len(input) and input[current+consumedChars] == '.': if current+consumedChars < len(input) and input[current+consumedChars] == 'd':
value += input[current+consumedChars] duration += input[current+consumedChars]
consumedChars += 1 consumedChars += 1
if len(duration) > 1:
value += duration
else:
consumedChars -= 1
return (consumedChars, Token(TokenType.NOTE, value, (line, current))) return (consumedChars, Token(TokenType.NOTE, value, (line, current)))
return (0, None) return (0, None)
def tokenizePercent(input, current, line): def tokenizePercent(input, current, line):
if input[current] == '%': return tokenizeChar(TokenType.PERCENT, '%', input, current, line)
return (1, Token(TokenType.PERCENT, input[current], (line, current)))
return (0, None)
def tokenizeMinus(input, current, line): def tokenizeMinus(input, current, line):
if input[current] == '-': return tokenizeChar(TokenType.MINUS, '-', input, current, line)
return (1, Token(TokenType.MINUS, input[current], (line, current)))
return (0, None)
def tokenizeFunction(input, current, line): def tokenizeFunction(input, current, line):
return tokenizeKeyword(TokenType.FUNCTION, 'function', 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): def tokenizeReturn(input, current, line):
return tokenizeKeyword(TokenType.RETURN, 'return', input, current, line) return tokenizeKeyword(TokenType.RETURN, 'return', input, current, line)
def tokenizeDot(input, current, line):
return tokenizeChar(TokenType.DOT, '.', input, current, line)
tokenizers = ( tokenizers = (
tokenizeOpenParen, tokenizeOpenParen,
tokenizeCloseParen, tokenizeCloseParen,
@@ -229,8 +222,9 @@ tokenizers = (
tokenizeColon, tokenizeColon,
tokenizePercent, tokenizePercent,
tokenizeMinus, tokenizeMinus,
tokenizeDot,
tokenizeComment, tokenizeComment,
tokenizeWhitespaces tokenizeWhitespaces,
) )
def doTokenize(lines): def doTokenize(lines):

View File

@@ -23,3 +23,4 @@ if __name__ == "__main__":
print(e.msg) print(e.msg)
except KeyboardInterrupt: except KeyboardInterrupt:
print("Program interrupted") print("Program interrupted")