Create new type: map (dictionary) with all support for it
This commit is contained in:
@@ -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
|
||||
)
|
||||
59
smnp/ast/node/map.py
Normal file
59
smnp/ast/node/map.py
Normal file
@@ -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(),
|
||||
)
|
||||
@@ -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 = {
|
||||
|
||||
25
smnp/library/function/get.py
Normal file
25
smnp/library/function/get.py
Normal file
@@ -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)
|
||||
)
|
||||
@@ -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())
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
11
smnp/runtime/evaluators/map.py
Normal file
11
smnp/runtime/evaluators/map.py
Normal file
@@ -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)))
|
||||
@@ -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
|
||||
#
|
||||
def updatePos(exception, node):
|
||||
if exception.pos is None:
|
||||
exception.pos = node.pos
|
||||
return exception
|
||||
@@ -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),
|
||||
|
||||
@@ -12,6 +12,7 @@ class TokenType(Enum):
|
||||
CLOSE_ANGLE = '>'
|
||||
ASTERISK = '*'
|
||||
ASSIGN = '='
|
||||
ARROW = '->'
|
||||
COMMA = ','
|
||||
MINUS = '-'
|
||||
DOT = '.'
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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})"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user