Add support for access operator
This commit is contained in:
8
AST.py
8
AST.py
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
31
Evaluator.py
31
Evaluator.py
@@ -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):
|
||||||
|
|||||||
23
Parser.py
23
Parser.py
@@ -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)
|
||||||
|
|||||||
64
Tokenizer.py
64
Tokenizer.py
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user