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

View File

@@ -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
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.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 = {

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():
try:
Interpreter.interpretFile(sys.argv[1])
Interpreter.interpretFile(sys.argv[1], printAst=True)
except SmnpException as e:
print(e.message())

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -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, {

View File

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