diff --git a/grammar b/grammar index 07c728e..8333f38 100644 --- a/grammar +++ b/grammar @@ -17,6 +17,7 @@ PITCH_MODIFIER = 'b' | '#' ::= | ::= | '=' | | ::= | + ::= | ::= '.' | '.' ::= '*' ::= | | | | @@ -34,6 +35,10 @@ PITCH_MODIFIER = 'b' | '#' ::= ? + ::= '{' '}' | '{' + ::= ', ' | ']' + ::= '->' | '->' | '->' + ::= '<' '>' | '<' ::= ', ' | '>' ::= | diff --git a/smnp/ast/node/expression.py b/smnp/ast/node/expression.py index 9a476a0..97b5c52 100644 --- a/smnp/ast/node/expression.py +++ b/smnp/ast/node/expression.py @@ -55,11 +55,13 @@ class ExpressionNode(Node): from smnp.ast.node.note import NoteLiteralNode from smnp.ast.node.identifier import IdentifierNode from smnp.ast.node.list import ListNode + from smnp.ast.node.map import MapNode return Parser.oneOf( IntegerLiteralNode.parse, StringLiteralNode.parse, NoteLiteralNode.parse, IdentifierNode.parse, + MapNode.parse, ListNode.parse ) \ No newline at end of file diff --git a/smnp/ast/node/map.py b/smnp/ast/node/map.py new file mode 100644 index 0000000..c7064b0 --- /dev/null +++ b/smnp/ast/node/map.py @@ -0,0 +1,59 @@ +from smnp.ast.node.access import AccessNode +from smnp.ast.node.expression import ExpressionNode +from smnp.ast.node.integer import IntegerLiteralNode +from smnp.ast.node.iterable import abstractIterableParser +from smnp.ast.node.none import NoneNode +from smnp.ast.node.note import NoteLiteralNode +from smnp.ast.node.string import StringLiteralNode +from smnp.ast.parser import Parser +from smnp.token.type import TokenType + +class MapEntry(ExpressionNode): + def __init__(self, pos): + super().__init__(pos) + self.children = [NoneNode(), NoneNode()] + + @property + def key(self): + return self[0] + + @key.setter + def key(self, value): + self[0] = value + + @property + def value(self): + return self[1] + + @value.setter + def value(self, value): + self[1] = value + +class MapNode(AccessNode): + + @classmethod + def _literalParser(cls): + return abstractIterableParser(MapNode, TokenType.OPEN_CURLY, TokenType.CLOSE_CURLY, cls._entryParser()) + + @classmethod + def _entryParser(cls): + def createNode(key, arrow, value): + node = MapEntry(key.pos) + node.key = key + node.value = value + return node + + return Parser.allOf( + cls._keyParser(), + Parser.terminalParser(TokenType.ARROW), + ExpressionNode.parse, + createNode=createNode + ) + + @classmethod + def _keyParser(cls): + return Parser.oneOf( + IntegerLiteralNode._literalParser(), + StringLiteralNode._literalParser(), + NoteLiteralNode._literalParser(), + ) \ No newline at end of file diff --git a/smnp/environment/factory.py b/smnp/environment/factory.py index c181378..ccfdff4 100644 --- a/smnp/environment/factory.py +++ b/smnp/environment/factory.py @@ -1,6 +1,6 @@ from smnp.environment.environment import Environment from smnp.library.function import display, sleep, semitones, interval, combine, flat, wait, rand, tuplet, synth, pause, \ - transpose, type, exit, duration, octave, debug + transpose, type, exit, duration, octave, debug, get from smnp.type.model import Type @@ -26,6 +26,7 @@ def createEnvironment(): methods = [ duration.function, octave.function, + get.function ] variables = { diff --git a/smnp/library/function/get.py b/smnp/library/function/get.py new file mode 100644 index 0000000..f8b0b27 --- /dev/null +++ b/smnp/library/function/get.py @@ -0,0 +1,25 @@ +from smnp.error.runtime import RuntimeException +from smnp.library.model import CombinedFunction, Function +from smnp.library.signature import signature, ofType, ofTypes +from smnp.type.model import Type + +_signature1 = signature(ofType(Type.LIST), ofType(Type.INTEGER)) +def _function1(env, list, index): + try: + return list.value[index] + except KeyError: + raise RuntimeException(f"Attempt to access item which is outside the list", None) + + +_signature2 = signature(ofType(Type.MAP), ofTypes(Type.INTEGER, Type.STRING, Type.NOTE)) +def _function2(env, map, key): + try: + return map.value[key] + except KeyError: + raise RuntimeException(f"Attempt to access unknown key in map", None) + +function = CombinedFunction( + 'get', + Function(_signature1, _function1), + Function(_signature2, _function2) +) \ No newline at end of file diff --git a/smnp/main.py b/smnp/main.py index db465b9..6f41788 100644 --- a/smnp/main.py +++ b/smnp/main.py @@ -6,7 +6,7 @@ from smnp.program.interpreter import Interpreter def main(): try: - Interpreter.interpretFile(sys.argv[1]) + Interpreter.interpretFile(sys.argv[1], printAst=True) except SmnpException as e: print(e.message()) diff --git a/smnp/note/model.py b/smnp/note/model.py index 118a3c9..d41e674 100644 --- a/smnp/note/model.py +++ b/smnp/note/model.py @@ -44,7 +44,13 @@ class Note: def __repr__(self): return self.__str__() - + + def __eq__(self, other): + return self.note == other.note and self.octave == other.octave and self.duration == other.duration and self.dot == other.dot + + def __hash__(self): + return hash(self.__str__()) + @staticmethod def checkInterval(a, b): return b._intRepr() - a._intRepr() diff --git a/smnp/runtime/evaluators/access.py b/smnp/runtime/evaluators/access.py index 58857ca..41e26d4 100644 --- a/smnp/runtime/evaluators/access.py +++ b/smnp/runtime/evaluators/access.py @@ -4,6 +4,7 @@ from smnp.error.runtime import RuntimeException from smnp.runtime.evaluator import Evaluator from smnp.runtime.evaluators.expression import expressionEvaluator from smnp.runtime.evaluators.iterable import abstractIterableEvaluator +from smnp.runtime.tools import updatePos class AccessEvaluator(Evaluator): @@ -20,8 +21,11 @@ class AccessEvaluator(Evaluator): raise RuntimeException(f"Unknown property '{right.value}' of type '{left.type.name.lower()}'", right.pos) if type(right) == FunctionCallNode: - arguments = abstractIterableEvaluator(expressionEvaluator(True))(right.arguments, environment) - return environment.invokeMethod(left, right.name.value, arguments) + try: + arguments = abstractIterableEvaluator(expressionEvaluator(True))(right.arguments, environment) + return environment.invokeMethod(left, right.name.value, arguments) + except RuntimeException as e: + raise updatePos(e, right) # # def evaluateAccess(access, environment): diff --git a/smnp/runtime/evaluators/expression.py b/smnp/runtime/evaluators/expression.py index fcb15f3..79c2c3f 100644 --- a/smnp/runtime/evaluators/expression.py +++ b/smnp/runtime/evaluators/expression.py @@ -5,6 +5,7 @@ from smnp.ast.node.identifier import IdentifierNode from smnp.ast.node.integer import IntegerLiteralNode from smnp.ast.node.invocation import FunctionCallNode from smnp.ast.node.list import ListNode +from smnp.ast.node.map import MapNode from smnp.ast.node.note import NoteLiteralNode from smnp.ast.node.string import StringLiteralNode from smnp.error.runtime import RuntimeException @@ -24,6 +25,7 @@ def expressionEvaluator(doAssert=False): from smnp.runtime.evaluators.access import AccessEvaluator from smnp.runtime.evaluators.assignment import AssignmentEvaluator from smnp.runtime.evaluators.asterisk import AsteriskEvaluator + from smnp.runtime.evaluators.map import MapEvaluator result = Evaluator.oneOf( Evaluator.forNodes(FunctionCallEvaluator.evaluate, FunctionCallNode), Evaluator.forNodes(StringEvaluator.evaluate, StringLiteralNode), @@ -33,7 +35,8 @@ def expressionEvaluator(doAssert=False): Evaluator.forNodes(ListEvaluator.evaluate, ListNode), Evaluator.forNodes(AccessEvaluator.evaluate, AccessNode), Evaluator.forNodes(AssignmentEvaluator.evaluate, AssignmentNode), - Evaluator.forNodes(AsteriskEvaluator.evaluate, AsteriskNode) + Evaluator.forNodes(AsteriskEvaluator.evaluate, AsteriskNode), + Evaluator.forNodes(MapEvaluator.evaluate, MapNode) )(node, environment) if doAssert and result.result and result.value.type == Type.VOID: diff --git a/smnp/runtime/evaluators/function.py b/smnp/runtime/evaluators/function.py index 0cec009..539e50b 100644 --- a/smnp/runtime/evaluators/function.py +++ b/smnp/runtime/evaluators/function.py @@ -6,6 +6,7 @@ from smnp.library.signature import signature, listOfMatchers, ofType from smnp.runtime.evaluator import Evaluator, evaluate from smnp.runtime.evaluators.expression import expressionEvaluator from smnp.runtime.evaluators.iterable import abstractIterableEvaluator +from smnp.runtime.tools import updatePos from smnp.type.model import Type @@ -13,9 +14,12 @@ class FunctionCallEvaluator(Evaluator): @classmethod def evaluator(cls, node, environment): - name = node.name.value - arguments = abstractIterableEvaluator(expressionEvaluator(True))(node.arguments, environment) - return environment.invokeFunction(name, arguments) + try: + name = node.name.value + arguments = abstractIterableEvaluator(expressionEvaluator(True))(node.arguments, environment) + return environment.invokeFunction(name, arguments) + except RuntimeException as e: + raise updatePos(e, node) class FunctionDefinitionEvaluator(Evaluator): diff --git a/smnp/runtime/evaluators/identifier.py b/smnp/runtime/evaluators/identifier.py index 3bf6afb..552194b 100644 --- a/smnp/runtime/evaluators/identifier.py +++ b/smnp/runtime/evaluators/identifier.py @@ -1,5 +1,6 @@ from smnp.error.runtime import RuntimeException from smnp.runtime.evaluator import Evaluator +from smnp.runtime.tools import updatePos class IdentifierEvaluator(Evaluator): @@ -9,5 +10,4 @@ class IdentifierEvaluator(Evaluator): try: return environment.findVariable(node.value) except RuntimeException as e: - e.pos = node.pos - raise e + raise updatePos(e, node) diff --git a/smnp/runtime/evaluators/map.py b/smnp/runtime/evaluators/map.py new file mode 100644 index 0000000..c3e2ca3 --- /dev/null +++ b/smnp/runtime/evaluators/map.py @@ -0,0 +1,11 @@ +from smnp.runtime.evaluator import Evaluator, evaluate +from smnp.type.model import Type + + +class MapEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + keys = [ evaluate(entry.key, environment).value for entry in node.children ] + values = [ evaluate(entry.value, environment).value for entry in node.children ] + return Type.map(dict(zip(keys, values))) \ No newline at end of file diff --git a/smnp/runtime/tools.py b/smnp/runtime/tools.py index 2b3e239..e3607fc 100644 --- a/smnp/runtime/tools.py +++ b/smnp/runtime/tools.py @@ -1,18 +1,4 @@ -def flatListNode(listNode): - if len(listNode.children[0].children) == 1: - return [] - return _flatListNode(listNode.children[0], []) - - -def _flatListNode(listItemNode, list = []): - if len(listItemNode.children) == 2: - value = listItemNode.children[0] - next = listItemNode.children[1] - list.append(value) - _flatListNode(next, list) - return list - - - - # NEW AST - # \ No newline at end of file +def updatePos(exception, node): + if exception.pos is None: + exception.pos = node.pos + return exception \ No newline at end of file diff --git a/smnp/token/tokenizer.py b/smnp/token/tokenizer.py index 1b7af8d..13ba9b1 100644 --- a/smnp/token/tokenizer.py +++ b/smnp/token/tokenizer.py @@ -21,6 +21,7 @@ tokenizers = ( defaultTokenizer(TokenType.CLOSE_ANGLE), defaultTokenizer(TokenType.ASTERISK), defaultTokenizer(TokenType.ASSIGN), + defaultTokenizer(TokenType.ARROW), defaultTokenizer(TokenType.COMMA), defaultTokenizer(TokenType.MINUS), defaultTokenizer(TokenType.DOT), diff --git a/smnp/token/type.py b/smnp/token/type.py index 4be3ad3..ebbea9a 100644 --- a/smnp/token/type.py +++ b/smnp/token/type.py @@ -12,6 +12,7 @@ class TokenType(Enum): CLOSE_ANGLE = '>' ASTERISK = '*' ASSIGN = '=' + ARROW = '->' COMMA = ',' MINUS = '-' DOT = '.' diff --git a/smnp/type/model.py b/smnp/type/model.py index 03b5981..2fbe02e 100644 --- a/smnp/type/model.py +++ b/smnp/type/model.py @@ -9,6 +9,7 @@ class Type(Enum): INTEGER = (int, lambda x: str(x)) STRING = (str, lambda x: x) LIST = (list, lambda x: f"[{', '.join([e.stringify() for e in x])}]") + MAP = (dict, lambda x: '{' + ', '.join(f"'{k.stringify()}' -> '{v.stringify()}'" for k, v in x.items()) + '}') PERCENT = (float, lambda x: f"{int(x * 100)}%") NOTE = (Note, lambda x: x.note.name) TYPE = (None, lambda x: str(x.type.name.lower())) @@ -33,6 +34,12 @@ class Type(Enum): "size": Type.integer(len(value)) }) + @staticmethod + def map(value): + return Value(Type.MAP, value, { + "size": Type.integer(len(value)) + }) + @staticmethod def note(value): return Value(Type.NOTE, value, { diff --git a/smnp/type/value.py b/smnp/type/value.py index e0d2705..b132a7f 100644 --- a/smnp/type/value.py +++ b/smnp/type/value.py @@ -28,6 +28,12 @@ class Value: return self + def __eq__(self, other): + return self.type == other.type and self.value == other.value + + def __hash__(self): + return hash(self.type) ^ hash(self.value) + def __str__(self): return f"{self.type.name}({self.value})"