Create new type: map (dictionary) with all support for it

This commit is contained in:
Bartłomiej Pluta
2019-07-09 00:11:39 +02:00
parent a3dfae73f1
commit d23e7a1276
17 changed files with 150 additions and 29 deletions

View File

@@ -17,6 +17,7 @@ PITCH_MODIFIER = 'b' | '#'
<expr> ::= <note> <access> | <note> <expr> ::= <note> <access> | <note>
<expr> ::= <identifier> <access> | <identifier> '=' <expr> | <functionCall> | <identifier> <expr> ::= <identifier> <access> | <identifier> '=' <expr> | <functionCall> | <identifier>
<expr> ::= <list> <access> | <list> <expr> ::= <list> <access> | <list>
<expr> ::= <map> <access> | <map>
<expr> ::= <expr> '.' <identifier> | <expr> '.' <functionCall> <expr> ::= <expr> '.' <identifier> | <expr> '.' <functionCall>
<expr> ::= <asteriskExpr> '*' <expr> <expr> ::= <asteriskExpr> '*' <expr>
<asteriskExpr> ::= <integer> | <string> | <note> | <identifier> | <list> <asteriskExpr> ::= <integer> | <string> | <note> | <identifier> | <list>
@@ -34,6 +35,10 @@ PITCH_MODIFIER = 'b' | '#'
<typedVariable> ::= <type> <typeSpecifier>? <identifier> <typedVariable> ::= <type> <typeSpecifier>? <identifier>
<map> ::= '{' '}' | '{' <mapEntry> <mapTail>
<mapTail> ::= <mapEntry> ', ' <mapTail> | ']'
<mapEntry> ::= <integer> '->' <expr> | <string> '->' <expr> | <note> '->' <expr>
<typeSpecifier> ::= '<' '>' | '<' <typeSpecifierItem> <typeSpecifierTail> <typeSpecifier> ::= '<' '>' | '<' <typeSpecifierItem> <typeSpecifierTail>
<typeSpecifierTail> ::= <typeSpecifierItem> ', ' <typeSpecifierTail> | '>' <typeSpecifierTail> ::= <typeSpecifierItem> ', ' <typeSpecifierTail> | '>'
<typeSpecifierItem> ::= <type> | <type> <typeSpecifier> <typeSpecifierItem> ::= <type> | <type> <typeSpecifier>

View File

@@ -55,11 +55,13 @@ class ExpressionNode(Node):
from smnp.ast.node.note import NoteLiteralNode from smnp.ast.node.note import NoteLiteralNode
from smnp.ast.node.identifier import IdentifierNode from smnp.ast.node.identifier import IdentifierNode
from smnp.ast.node.list import ListNode from smnp.ast.node.list import ListNode
from smnp.ast.node.map import MapNode
return Parser.oneOf( return Parser.oneOf(
IntegerLiteralNode.parse, IntegerLiteralNode.parse,
StringLiteralNode.parse, StringLiteralNode.parse,
NoteLiteralNode.parse, NoteLiteralNode.parse,
IdentifierNode.parse, IdentifierNode.parse,
MapNode.parse,
ListNode.parse ListNode.parse
) )

59
smnp/ast/node/map.py Normal file
View 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(),
)

View File

@@ -1,6 +1,6 @@
from smnp.environment.environment import Environment from smnp.environment.environment import Environment
from smnp.library.function import display, sleep, semitones, interval, combine, flat, wait, rand, tuplet, synth, pause, \ 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 from smnp.type.model import Type
@@ -26,6 +26,7 @@ def createEnvironment():
methods = [ methods = [
duration.function, duration.function,
octave.function, octave.function,
get.function
] ]
variables = { variables = {

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

View File

@@ -6,7 +6,7 @@ from smnp.program.interpreter import Interpreter
def main(): def main():
try: try:
Interpreter.interpretFile(sys.argv[1]) Interpreter.interpretFile(sys.argv[1], printAst=True)
except SmnpException as e: except SmnpException as e:
print(e.message()) print(e.message())

View File

@@ -44,7 +44,13 @@ class Note:
def __repr__(self): def __repr__(self):
return self.__str__() 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 @staticmethod
def checkInterval(a, b): def checkInterval(a, b):
return b._intRepr() - a._intRepr() return b._intRepr() - a._intRepr()

View File

@@ -4,6 +4,7 @@ from smnp.error.runtime import RuntimeException
from smnp.runtime.evaluator import Evaluator from smnp.runtime.evaluator import Evaluator
from smnp.runtime.evaluators.expression import expressionEvaluator from smnp.runtime.evaluators.expression import expressionEvaluator
from smnp.runtime.evaluators.iterable import abstractIterableEvaluator from smnp.runtime.evaluators.iterable import abstractIterableEvaluator
from smnp.runtime.tools import updatePos
class AccessEvaluator(Evaluator): 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) raise RuntimeException(f"Unknown property '{right.value}' of type '{left.type.name.lower()}'", right.pos)
if type(right) == FunctionCallNode: if type(right) == FunctionCallNode:
arguments = abstractIterableEvaluator(expressionEvaluator(True))(right.arguments, environment) try:
return environment.invokeMethod(left, right.name.value, arguments) 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): # def evaluateAccess(access, environment):

View File

@@ -5,6 +5,7 @@ from smnp.ast.node.identifier import IdentifierNode
from smnp.ast.node.integer import IntegerLiteralNode from smnp.ast.node.integer import IntegerLiteralNode
from smnp.ast.node.invocation import FunctionCallNode from smnp.ast.node.invocation import FunctionCallNode
from smnp.ast.node.list import ListNode 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.note import NoteLiteralNode
from smnp.ast.node.string import StringLiteralNode from smnp.ast.node.string import StringLiteralNode
from smnp.error.runtime import RuntimeException 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.access import AccessEvaluator
from smnp.runtime.evaluators.assignment import AssignmentEvaluator from smnp.runtime.evaluators.assignment import AssignmentEvaluator
from smnp.runtime.evaluators.asterisk import AsteriskEvaluator from smnp.runtime.evaluators.asterisk import AsteriskEvaluator
from smnp.runtime.evaluators.map import MapEvaluator
result = Evaluator.oneOf( result = Evaluator.oneOf(
Evaluator.forNodes(FunctionCallEvaluator.evaluate, FunctionCallNode), Evaluator.forNodes(FunctionCallEvaluator.evaluate, FunctionCallNode),
Evaluator.forNodes(StringEvaluator.evaluate, StringLiteralNode), Evaluator.forNodes(StringEvaluator.evaluate, StringLiteralNode),
@@ -33,7 +35,8 @@ def expressionEvaluator(doAssert=False):
Evaluator.forNodes(ListEvaluator.evaluate, ListNode), Evaluator.forNodes(ListEvaluator.evaluate, ListNode),
Evaluator.forNodes(AccessEvaluator.evaluate, AccessNode), Evaluator.forNodes(AccessEvaluator.evaluate, AccessNode),
Evaluator.forNodes(AssignmentEvaluator.evaluate, AssignmentNode), Evaluator.forNodes(AssignmentEvaluator.evaluate, AssignmentNode),
Evaluator.forNodes(AsteriskEvaluator.evaluate, AsteriskNode) Evaluator.forNodes(AsteriskEvaluator.evaluate, AsteriskNode),
Evaluator.forNodes(MapEvaluator.evaluate, MapNode)
)(node, environment) )(node, environment)
if doAssert and result.result and result.value.type == Type.VOID: if doAssert and result.result and result.value.type == Type.VOID:

View File

@@ -6,6 +6,7 @@ from smnp.library.signature import signature, listOfMatchers, ofType
from smnp.runtime.evaluator import Evaluator, evaluate from smnp.runtime.evaluator import Evaluator, evaluate
from smnp.runtime.evaluators.expression import expressionEvaluator from smnp.runtime.evaluators.expression import expressionEvaluator
from smnp.runtime.evaluators.iterable import abstractIterableEvaluator from smnp.runtime.evaluators.iterable import abstractIterableEvaluator
from smnp.runtime.tools import updatePos
from smnp.type.model import Type from smnp.type.model import Type
@@ -13,9 +14,12 @@ class FunctionCallEvaluator(Evaluator):
@classmethod @classmethod
def evaluator(cls, node, environment): def evaluator(cls, node, environment):
name = node.name.value try:
arguments = abstractIterableEvaluator(expressionEvaluator(True))(node.arguments, environment) name = node.name.value
return environment.invokeFunction(name, arguments) arguments = abstractIterableEvaluator(expressionEvaluator(True))(node.arguments, environment)
return environment.invokeFunction(name, arguments)
except RuntimeException as e:
raise updatePos(e, node)
class FunctionDefinitionEvaluator(Evaluator): class FunctionDefinitionEvaluator(Evaluator):

View File

@@ -1,5 +1,6 @@
from smnp.error.runtime import RuntimeException from smnp.error.runtime import RuntimeException
from smnp.runtime.evaluator import Evaluator from smnp.runtime.evaluator import Evaluator
from smnp.runtime.tools import updatePos
class IdentifierEvaluator(Evaluator): class IdentifierEvaluator(Evaluator):
@@ -9,5 +10,4 @@ class IdentifierEvaluator(Evaluator):
try: try:
return environment.findVariable(node.value) return environment.findVariable(node.value)
except RuntimeException as e: except RuntimeException as e:
e.pos = node.pos raise updatePos(e, node)
raise e

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

View File

@@ -1,18 +1,4 @@
def flatListNode(listNode): def updatePos(exception, node):
if len(listNode.children[0].children) == 1: if exception.pos is None:
return [] exception.pos = node.pos
return _flatListNode(listNode.children[0], []) return exception
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
#

View File

@@ -21,6 +21,7 @@ tokenizers = (
defaultTokenizer(TokenType.CLOSE_ANGLE), defaultTokenizer(TokenType.CLOSE_ANGLE),
defaultTokenizer(TokenType.ASTERISK), defaultTokenizer(TokenType.ASTERISK),
defaultTokenizer(TokenType.ASSIGN), defaultTokenizer(TokenType.ASSIGN),
defaultTokenizer(TokenType.ARROW),
defaultTokenizer(TokenType.COMMA), defaultTokenizer(TokenType.COMMA),
defaultTokenizer(TokenType.MINUS), defaultTokenizer(TokenType.MINUS),
defaultTokenizer(TokenType.DOT), defaultTokenizer(TokenType.DOT),

View File

@@ -12,6 +12,7 @@ class TokenType(Enum):
CLOSE_ANGLE = '>' CLOSE_ANGLE = '>'
ASTERISK = '*' ASTERISK = '*'
ASSIGN = '=' ASSIGN = '='
ARROW = '->'
COMMA = ',' COMMA = ','
MINUS = '-' MINUS = '-'
DOT = '.' DOT = '.'

View File

@@ -9,6 +9,7 @@ class Type(Enum):
INTEGER = (int, lambda x: str(x)) INTEGER = (int, lambda x: str(x))
STRING = (str, lambda x: x) STRING = (str, lambda x: x)
LIST = (list, lambda x: f"[{', '.join([e.stringify() for e in 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)}%") PERCENT = (float, lambda x: f"{int(x * 100)}%")
NOTE = (Note, lambda x: x.note.name) NOTE = (Note, lambda x: x.note.name)
TYPE = (None, lambda x: str(x.type.name.lower())) TYPE = (None, lambda x: str(x.type.name.lower()))
@@ -33,6 +34,12 @@ class Type(Enum):
"size": Type.integer(len(value)) "size": Type.integer(len(value))
}) })
@staticmethod
def map(value):
return Value(Type.MAP, value, {
"size": Type.integer(len(value))
})
@staticmethod @staticmethod
def note(value): def note(value):
return Value(Type.NOTE, value, { return Value(Type.NOTE, value, {

View File

@@ -28,6 +28,12 @@ class Value:
return self 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): def __str__(self):
return f"{self.type.name}({self.value})" return f"{self.type.name}({self.value})"