Create new type: map (dictionary) with all support for it
This commit is contained in:
5
grammar
5
grammar
@@ -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>
|
||||||
|
|||||||
@@ -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
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.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 = {
|
||||||
|
|||||||
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():
|
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())
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
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):
|
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
|
|
||||||
#
|
|
||||||
@@ -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),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class TokenType(Enum):
|
|||||||
CLOSE_ANGLE = '>'
|
CLOSE_ANGLE = '>'
|
||||||
ASTERISK = '*'
|
ASTERISK = '*'
|
||||||
ASSIGN = '='
|
ASSIGN = '='
|
||||||
|
ARROW = '->'
|
||||||
COMMA = ','
|
COMMA = ','
|
||||||
MINUS = '-'
|
MINUS = '-'
|
||||||
DOT = '.'
|
DOT = '.'
|
||||||
|
|||||||
@@ -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, {
|
||||||
|
|||||||
@@ -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})"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user