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]
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

View File

@@ -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)

View File

@@ -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)
@@ -172,6 +201,8 @@ def evaluate(input, environment):
return evaluateFunctionDefinition(input, environment)
if isinstance(input, FunctionCallNode):
return evaluateFunctionCall(input, environment)
if isinstance(input, AccessNode):
return evaluateAccess(input, environment)
if isinstance(input, CommaNode):
return evaluateComma(input, environment)
if isinstance(input, BlockNode):

View File

@@ -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)

View File

@@ -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):

View File

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