Create note package
This commit is contained in:
223
smnp/AST.py
223
smnp/AST.py
@@ -1,223 +0,0 @@
|
||||
from enum import Enum
|
||||
from Note import Note
|
||||
|
||||
class Node:
|
||||
def __init__(self, parent, pos):
|
||||
self.children = []
|
||||
self.parent = parent
|
||||
self.pos = pos
|
||||
for child in self.children:
|
||||
child.parent = self
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
def __len__(self):
|
||||
return len(self.children)
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.children[index]
|
||||
|
||||
def append(self, node):
|
||||
node.parent = self
|
||||
self.children.append(node)
|
||||
|
||||
def pop(self, index):
|
||||
return self.children.pop(index)
|
||||
|
||||
def _print(self, level):
|
||||
string = f"{pad(level)}{self.__class__.__name__}({self.parent.__class__.__name__}):\n"
|
||||
for child in self.children:
|
||||
if isinstance(child, str) or isinstance(child, int) or isinstance(child, Note):
|
||||
string += pad(level+1) + f"'{child}'\n"
|
||||
else:
|
||||
string += child._print(level+1)
|
||||
return string
|
||||
|
||||
def __str__(self):
|
||||
return self._print(0)
|
||||
|
||||
def pad(level):
|
||||
return (" " * level)
|
||||
|
||||
class Program(Node):
|
||||
def __init__(self):
|
||||
Node.__init__(self, None, (-1, -1))
|
||||
|
||||
#def __str__(self):
|
||||
#return "Program:\n" + "\n".join([str(e) for e in self.children])
|
||||
|
||||
def print(self):
|
||||
print(self._print(0))
|
||||
|
||||
class BlockNode(Node):
|
||||
def __init__(self, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
|
||||
#def __str__(self):
|
||||
#return "B{\n" + "\n".join([str(e) for e in self.children]) + "\n}"
|
||||
|
||||
class BlockItemNode(Node):
|
||||
def __init__(self, statement, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
self.children.append(statement)
|
||||
self.statement = self.children[0]
|
||||
|
||||
class CloseBlockNode(Node):
|
||||
def __init__(self, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
|
||||
class ListNode(Node):
|
||||
def __init__(self, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
|
||||
#def __str__(self):
|
||||
#return "@(" + ", ".join([str(e) for e in self.children]) + ")"
|
||||
|
||||
class IdentifierNode(Node):
|
||||
def __init__(self, identifier, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
self.children.append(identifier)
|
||||
|
||||
self.identifier = self.children[0]
|
||||
|
||||
#def __str__(self):
|
||||
#return f"L'{self.identifier}'"
|
||||
|
||||
class AssignmentNode(Node):
|
||||
def __init__(self, target, value, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
self.children.extend([target, value])
|
||||
|
||||
self.target = self.children[0]
|
||||
self.value = self.children[1]
|
||||
|
||||
#def __str__(self):
|
||||
#return f"A[{self.target} = {self.value}]"
|
||||
|
||||
class AsteriskNode(Node):
|
||||
def __init__(self, iterator, statement, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
self.children.extend([iterator, statement])
|
||||
|
||||
self.iterator = self.children[0]
|
||||
self.statement = self.children[1]
|
||||
|
||||
#def __str__(self):
|
||||
#return f"*({self.iterator}: {self.statement})"
|
||||
|
||||
class ColonNode(Node):
|
||||
def __init__(self, a, b, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
self.children.extend([a, b])
|
||||
|
||||
self.a = self.children[0]
|
||||
self.b = self.children[1]
|
||||
|
||||
#def __str__(self):
|
||||
#return f":({self.a}, {self.b})"
|
||||
|
||||
class ExpressionNode(Node):
|
||||
def __init__(self, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
|
||||
#def __str__(self):
|
||||
#return f"{self.__class__.__name__}('{self.value}')"
|
||||
|
||||
class IntegerLiteralNode(ExpressionNode):
|
||||
def __init__(self, value, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
self.children.append(value)
|
||||
|
||||
self.value = self.children[0]
|
||||
|
||||
#def __str__(self):
|
||||
#return f"i'{self.value}'"
|
||||
|
||||
class StringLiteralNode(ExpressionNode):
|
||||
def __init__(self, value, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
self.children.append(value)
|
||||
|
||||
self.value = self.children[0]
|
||||
|
||||
#def __str__(self):
|
||||
#return f"s'{self.value}'"
|
||||
|
||||
class NoteLiteralNode(ExpressionNode):
|
||||
def __init__(self, value, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
self.children.append(value)
|
||||
|
||||
self.value = self.children[0]
|
||||
|
||||
#def __str__(self):
|
||||
#return f"n'{self.value.note}[{self.value.octave}, {self.value.duration}]'"
|
||||
|
||||
class FunctionCallNode(Node):
|
||||
def __init__(self, identifier, arguments, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
self.children.extend([identifier, arguments])
|
||||
|
||||
self.identifier = self.children[0]
|
||||
self.arguments = self.children[1]
|
||||
|
||||
#def __str__(self):
|
||||
#return f"F({self.identifier}: {self.arguments})"
|
||||
|
||||
class CommaNode(Node):
|
||||
def __init__(self, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
|
||||
#def __str__(self):
|
||||
#return "[,]"
|
||||
|
||||
class PercentNode(Node):
|
||||
def __init__(self, value, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
self.children.append(value)
|
||||
|
||||
self.value = self.children[0]
|
||||
|
||||
#def __str__(self):
|
||||
#return f"%'{self.value}'"
|
||||
|
||||
class FunctionDefinitionNode(Node):
|
||||
def __init__(self, name, parameters, body, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
self.children.extend([name, parameters, body])
|
||||
|
||||
self.name = self.children[0]
|
||||
self.parameters = self.children[1]
|
||||
self.body = self.children[2]
|
||||
|
||||
#def __str__(self):
|
||||
#return f"$F'{self.name}{self.parameters}{self.body}"
|
||||
|
||||
class ReturnNode(Node):
|
||||
def __init__(self, value, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
self.children.append(value)
|
||||
|
||||
self.value = self.children[0]
|
||||
|
||||
#def __str__(self):
|
||||
#return f"Ret({self.value})"
|
||||
|
||||
class ListItemNode(Node):
|
||||
def __init__(self, value, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
self.children.append(value)
|
||||
|
||||
self.value = self.children[0]
|
||||
|
||||
class AccessNode(Node):
|
||||
def __init__(self, element, property, parent, pos):
|
||||
Node.__init__(self, parent, pos)
|
||||
self.children.extend([element, property])
|
||||
|
||||
self.element = self.children[0]
|
||||
self.property = self.children[1]
|
||||
|
||||
class CloseListNode(Node):
|
||||
pass
|
||||
128
smnp/Note.py
128
smnp/Note.py
@@ -1,128 +0,0 @@
|
||||
from enum import Enum
|
||||
import math
|
||||
from Error import SyntaxException
|
||||
|
||||
class NotePitch(Enum):
|
||||
C = 0
|
||||
CIS = 1
|
||||
D = 2
|
||||
DIS = 3
|
||||
E = 4
|
||||
F = 5
|
||||
FIS = 6
|
||||
G = 7
|
||||
GIS = 8
|
||||
A = 9
|
||||
AIS = 10
|
||||
H = 11
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
def toFrequency(self):
|
||||
return {
|
||||
NotePitch.C: 16.35,
|
||||
NotePitch.CIS: 17.32,
|
||||
NotePitch.D: 18.35,
|
||||
NotePitch.DIS: 19.45,
|
||||
NotePitch.E: 20.60,
|
||||
NotePitch.F: 21.83,
|
||||
NotePitch.FIS: 23.12,
|
||||
NotePitch.G: 24.50,
|
||||
NotePitch.GIS: 25.96,
|
||||
NotePitch.A: 27.50,
|
||||
NotePitch.AIS: 29.17,
|
||||
NotePitch.H: 30.87
|
||||
}[self]
|
||||
|
||||
@staticmethod
|
||||
def checkInterval(a, b):
|
||||
return a.value - b.value
|
||||
|
||||
@staticmethod
|
||||
def toPitch(string):
|
||||
try:
|
||||
map = { 'c': NotePitch.C, 'c#': NotePitch.CIS, 'db': NotePitch.CIS, 'd': NotePitch.D,
|
||||
'd#': NotePitch.DIS, 'eb': NotePitch.DIS, 'e': NotePitch.E, 'fb': NotePitch.E, 'e#': NotePitch.F,
|
||||
'f': NotePitch.F, 'f#': NotePitch.FIS, 'gb': NotePitch.FIS, 'g': NotePitch.G, 'g#': NotePitch.GIS,
|
||||
'ab': NotePitch.GIS, 'a': NotePitch.A, 'a#': NotePitch.AIS, 'b': NotePitch.AIS, 'h': NotePitch.H
|
||||
}
|
||||
return map[string.lower()]
|
||||
except KeyError as e:
|
||||
raise SyntaxException(None, f"Note '{string}' does not exist")
|
||||
|
||||
class Note:
|
||||
def __init__(self, note, octave = 4, duration = 4, dot = False):
|
||||
if type(note) == str:
|
||||
self.note = NotePitch.toPitch(note)
|
||||
else:
|
||||
self.note = note
|
||||
self.octave = octave
|
||||
self.duration = duration
|
||||
self.dot = dot
|
||||
|
||||
def toFrequency(self):
|
||||
return self.note.toFrequency() * 2 ** self.octave
|
||||
|
||||
def transpose(self, interval):
|
||||
origIntRepr = self._intRepr()
|
||||
transposedIntRepr = origIntRepr + interval
|
||||
return Note._fromIntRepr(transposedIntRepr, self.duration, self.dot)
|
||||
|
||||
def withDuration(self, duration):
|
||||
return Note(self.note, self.octave, duration, self.dot)
|
||||
|
||||
def withOctave(self, octave):
|
||||
return Note(self.note, octave, self.duration, self.dot)
|
||||
|
||||
def withDot(self):
|
||||
return Note(self.note, self.octave, self.duration, True)
|
||||
|
||||
def withoutDot(self):
|
||||
return Note(self.note, self.octave, self.duration, False)
|
||||
|
||||
def _intRepr(self):
|
||||
return self.octave * len(NotePitch) + self.note.value
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.note}({self.octave}')[{self.duration}{'.' if self.dot else ''}]"
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
@staticmethod
|
||||
def _fromIntRepr(intRepr, duration = 4, dot = False):
|
||||
note = NotePitch(intRepr % len(NotePitch))
|
||||
octave = int(intRepr / len(NotePitch))
|
||||
return Note(note, octave, duration, dot)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def checkInterval(a, b):
|
||||
return b._intRepr() - a._intRepr()
|
||||
|
||||
@staticmethod
|
||||
def range(a, b):
|
||||
return [Note._fromIntRepr(x) for x in range(a._intRepr(), b._intRepr()+1)]
|
||||
|
||||
def intervalToString(interval):
|
||||
octaveInterval = int(abs(interval) / len(NotePitch))
|
||||
pitchInterval = abs(interval) % len(NotePitch)
|
||||
pitchIntervalName = {
|
||||
0: "1",
|
||||
1: "2m",
|
||||
2: "2M",
|
||||
3: "3m",
|
||||
4: "3M",
|
||||
5: "4",
|
||||
6: "5d/4A",
|
||||
7: "5",
|
||||
8: "6m",
|
||||
9: "6M",
|
||||
10: "7m",
|
||||
11: "7M"
|
||||
}
|
||||
return (str(pitchIntervalName[pitchInterval]) + (f"(+{octaveInterval}')" if octaveInterval > 0 else ""))
|
||||
@@ -1,230 +0,0 @@
|
||||
from Tokenizer import *
|
||||
from Note import *
|
||||
from AST import *
|
||||
from Error import SyntaxException
|
||||
|
||||
def expectedFound(expected, found):
|
||||
raise SyntaxException(None, f"Expected: {expected}, found: {found}")
|
||||
|
||||
def assertType(expected, found):
|
||||
if expected != found:
|
||||
raise SyntaxException(None, f"Expected: {expected}, found: {found}")
|
||||
|
||||
def parseInteger(input, parent):
|
||||
token = input.pop(0)
|
||||
return IntegerLiteralNode(int(token.value), parent, token.pos)
|
||||
|
||||
def parseString(input, parent):
|
||||
token = input.pop(0)
|
||||
return StringLiteralNode(token.value[1:-1], parent, token.pos)
|
||||
|
||||
def parseNote(input, parent):
|
||||
token = input.pop(0)
|
||||
value = token.value
|
||||
consumedChars = 1
|
||||
notePitch = value[consumedChars]
|
||||
consumedChars += 1
|
||||
octave = 4
|
||||
duration = 4
|
||||
dot = False
|
||||
if consumedChars < len(value) and value[consumedChars] in ('b', '#'):
|
||||
notePitch += value[consumedChars]
|
||||
consumedChars += 1
|
||||
if consumedChars < len(value) and re.match(r'\d', value[consumedChars]):
|
||||
octave = int(value[consumedChars])
|
||||
consumedChars += 1
|
||||
if consumedChars < len(value) and value[consumedChars] == '.':
|
||||
consumedChars += 1
|
||||
durationString = ''
|
||||
while consumedChars < len(value) and re.match(r'\d', value[consumedChars]):
|
||||
durationString += value[consumedChars]
|
||||
consumedChars += 1
|
||||
duration = int(durationString)
|
||||
if consumedChars < len(value) and value[consumedChars] == '.':
|
||||
dot = True
|
||||
consumedChars += 1
|
||||
|
||||
return NoteLiteralNode(Note(notePitch, octave, duration, dot), parent, token.pos)
|
||||
|
||||
def parseComma(input, parent):
|
||||
token = input.pop(0)
|
||||
return CommaNode(parent, token.pos)
|
||||
|
||||
def parseList(input, parent):
|
||||
token = input.pop(0)
|
||||
|
||||
node = ListNode(parent, token.pos)
|
||||
|
||||
while input[0].type != TokenType.CLOSE_PAREN:
|
||||
element = parseArrayElement(input, node)
|
||||
if element is None:
|
||||
raise SyntaxException(input[0].pos, "Invalid element '{input[0].value}'")
|
||||
node.append(element)
|
||||
|
||||
if input[0].type != TokenType.CLOSE_PAREN:
|
||||
expectedFound(TokenType.CLOSE_PAREN, input[0].type)
|
||||
input.pop(0)
|
||||
|
||||
return node
|
||||
|
||||
def parseBlock(input, parent):
|
||||
token = input.pop(0)
|
||||
|
||||
block = BlockNode(parent, token.pos)
|
||||
|
||||
while input[0].type != TokenType.CLOSE_BRACKET:
|
||||
block.append(parseToken(input, block))
|
||||
|
||||
if input[0].type != TokenType.CLOSE_BRACKET:
|
||||
expectedFound(TokenType.CLOSE_BRACKET, input[0].type)
|
||||
input.pop(0)
|
||||
|
||||
return block
|
||||
|
||||
|
||||
def parseAsterisk(input, parent):
|
||||
token = input.pop(0)
|
||||
|
||||
iterator = parent.pop(-1)
|
||||
value = parseStatement(input, parent)
|
||||
|
||||
asterisk = AsteriskStatementNode(iterator, value, parent, token.pos)
|
||||
iterator.parent = asterisk
|
||||
value.parent = asterisk
|
||||
return asterisk
|
||||
|
||||
def parseNoteOrColon(input, parent):
|
||||
note = parseNote(input, parent)
|
||||
if len(input) > 1 and input[0].type == TokenType.COLON:
|
||||
token = input.pop(0)
|
||||
b = parseNote(input, parent)
|
||||
if b is None:
|
||||
raise SyntaxException(input[0].pos, f"Invalid colon argument '{input[0].value}'")
|
||||
colon = ColonNode(note, b, parent, token.pos)
|
||||
note.parent = colon
|
||||
b.parent = colon
|
||||
return colon
|
||||
|
||||
return note
|
||||
|
||||
def parseIntegerOrColonOrPercent(input, parent):
|
||||
integer = parseInteger(input, parent)
|
||||
if len(input) > 1 and input[0].type == TokenType.COLON:
|
||||
token = input.pop(0)
|
||||
b = parseInteger(input, parent)
|
||||
if b is None:
|
||||
raise SyntaxException(input[0].pos, f"Invalid colon argument '{input[0].value}'")
|
||||
colon = ColonNode(integer, b, parent, token.pos)
|
||||
integer.parent = colon
|
||||
b.parent = colon
|
||||
return colon
|
||||
|
||||
if len(input) > 0 and input[0].type == TokenType.PERCENT:
|
||||
input.pop(0)
|
||||
percent = PercentNode(integer, parent, integer.pos)
|
||||
integer.parent = percent
|
||||
return percent
|
||||
|
||||
return integer
|
||||
|
||||
def parseFunctionCallOrAssignOrIdentifier(input, parent):
|
||||
token = input.pop(0)
|
||||
identifier = IdentifierNode(token.value, parent, token.pos)
|
||||
# Function call
|
||||
if len(input) > 0 and input[0].type == TokenType.OPEN_PAREN:
|
||||
arguments = parseList(input, parent)
|
||||
func = FunctionCallNode(identifier, arguments, parent, token.pos)
|
||||
identifier.parent = func
|
||||
arguments.parent = func
|
||||
return func
|
||||
# Assign
|
||||
if len(input) > 1 and input[0].type == TokenType.ASSIGN:
|
||||
token = input.pop(0)
|
||||
value = parseExpression(input, parent) #
|
||||
assign = AssignExpression(identifier, value, parent, token.pos)
|
||||
identifier.parent = assign
|
||||
value.parent = assign
|
||||
return assign
|
||||
|
||||
return identifier
|
||||
|
||||
def parseMinus(input, parent):
|
||||
token = input.pop(0)
|
||||
|
||||
value = parseInteger(input, parent)
|
||||
|
||||
return IntegerLiteralNode(-value.value, parent, token.pos)
|
||||
|
||||
def parseFunctionDefinition(input, parent):
|
||||
input.pop(0)
|
||||
|
||||
assertType(TokenType.IDENTIFIER, input[0].type)
|
||||
token = input.pop(0)
|
||||
name = IdentifierNode(token.value, parent, token.pos)
|
||||
|
||||
assertType(TokenType.OPEN_PAREN, input[0].type)
|
||||
parameters = parseList(input, parent)
|
||||
|
||||
assertType(TokenType.OPEN_BRACKET, input[0].type)
|
||||
body = parseBlock(input, parent)
|
||||
|
||||
func = FunctionDefinitionNode(name, parameters, body, parent, token.pos)
|
||||
name.parent = func
|
||||
parameters.parent = func
|
||||
body.parent = func
|
||||
return func
|
||||
|
||||
def parseReturn(input, parent):
|
||||
token = input.pop(0)
|
||||
|
||||
value = parseExpression(input, parent)
|
||||
|
||||
returnNode = ReturnNode(value, parent, token.pos)
|
||||
value.parent = returnNode
|
||||
return returnNode
|
||||
|
||||
def parseExpression(input, parent):
|
||||
type = input[0].type
|
||||
if type == TokenType.FUNCTION:
|
||||
return parseFunctionDefinition(input, parent)
|
||||
if type == TokenType.RETURN:
|
||||
return parseReturn(input, parent)
|
||||
if type == TokenType.MINUS:
|
||||
return parseMinus(input, parent)
|
||||
if type == TokenType.INTEGER:
|
||||
return parseIntegerOrColonOrPercent(input, parent)
|
||||
if type == TokenType.STRING:
|
||||
return parseString(input, parent)
|
||||
if type == TokenType.NOTE:
|
||||
return parseNoteOrColon(input, parent)
|
||||
if type == TokenType.IDENTIFIER:
|
||||
return parseFunctionCallOrAssignOrIdentifier(input, parent)
|
||||
if type == TokenType.OPEN_PAREN:
|
||||
return parseList(input, parent)
|
||||
raise SyntaxException(input[0].pos, f"Unexpected character '{input[0].value}'")
|
||||
|
||||
def parseArrayElement(input, parent):
|
||||
type = input[0].type
|
||||
if type == TokenType.COMMA:
|
||||
return parseComma(input, parent)
|
||||
return parseExpression(input, parent)
|
||||
|
||||
def parseStatement(input, parent):
|
||||
type = input[0].type
|
||||
if type == TokenType.OPEN_BRACKET:
|
||||
return parseBlock(input, parent)
|
||||
if type == TokenType.ASTERISK:
|
||||
return parseAsterisk(input, parent)
|
||||
|
||||
return parseExpression(input, parent)
|
||||
|
||||
def parseToken(input, parent):
|
||||
#import pdb; pdb.set_trace()
|
||||
return parseStatement(input, parent)
|
||||
|
||||
|
||||
def parse(input):
|
||||
root = Program()
|
||||
while len(input) > 0:
|
||||
root.append(parseToken(input, root))
|
||||
return root
|
||||
377
smnp/Parser.py
377
smnp/Parser.py
@@ -1,377 +0,0 @@
|
||||
from AST import *
|
||||
from Tokenizer import TokenType
|
||||
from Error import SyntaxException
|
||||
from Note import Note, NotePitch
|
||||
import re
|
||||
|
||||
def assertToken(expected, input):
|
||||
if not input.hasCurrent():
|
||||
raise SyntaxException(None, f"Expected '{expected}'")
|
||||
if expected != input.current().type:
|
||||
raise SyntaxException(input.current().pos, f"Expected '{expected}', found '{input.current().value}'")
|
||||
|
||||
def runParsers(input, parent, parsers):
|
||||
for parser in parsers:
|
||||
value = parser(input, parent)
|
||||
if value is not None:
|
||||
return value
|
||||
return None
|
||||
|
||||
def returnAndGoAhead(input, getValue):
|
||||
value = getValue(input.current())
|
||||
input.ahead()
|
||||
return value
|
||||
|
||||
# int -> INTEGER
|
||||
def parseInteger(input, parent):
|
||||
if input.current().type == TokenType.INTEGER:
|
||||
integer = IntegerLiteralNode(int(input.current().value), parent, input.current().pos)
|
||||
input.ahead()
|
||||
|
||||
return integer
|
||||
return None
|
||||
|
||||
# percent -> int '%'
|
||||
def parseIntegerAndPercent(input, parent):
|
||||
integer = parseInteger(input, parent)
|
||||
if integer is not None and input.hasCurrent() and input.current().type == TokenType.PERCENT:
|
||||
percent = PercentNode(integer, parent, input.current().pos)
|
||||
integer.parent = percent
|
||||
input.ahead()
|
||||
|
||||
return percent
|
||||
return integer
|
||||
|
||||
# string -> STRING
|
||||
def parseString(input, parent):
|
||||
if input.current().type == TokenType.STRING:
|
||||
string = StringLiteralNode(input.current().value[1:len(input.current().value)-1], parent, input.current().pos)
|
||||
input.ahead()
|
||||
|
||||
return string
|
||||
return None
|
||||
|
||||
# id -> IDENTIFIER
|
||||
def parseIdentifier(input, parent):
|
||||
if input.current().type == TokenType.IDENTIFIER:
|
||||
identifier = IdentifierNode(input.current().value, parent, input.current().pos)
|
||||
input.ahead()
|
||||
|
||||
return identifier
|
||||
return None
|
||||
|
||||
def parseIdentifierOrFunctionCallOrAssignment(input, parent):
|
||||
identifier = parseIdentifier(input, parent)
|
||||
if identifier is not None and input.hasCurrent():
|
||||
if input.current().type == TokenType.ASSIGN:
|
||||
token = input.current()
|
||||
input.ahead()
|
||||
|
||||
expr = parseExpression(input, parent)
|
||||
|
||||
assignment = AssignmentNode(identifier, expr, parent, token.pos)
|
||||
identifier.parent = assignment
|
||||
expr.parent = assignment
|
||||
|
||||
return assignment
|
||||
|
||||
args = parseList(input, parent)
|
||||
if args is not None:
|
||||
functionCall = FunctionCallNode(identifier, args, parent, identifier.pos)
|
||||
args.parent = functionCall
|
||||
identifier.parent = functionCall
|
||||
return functionCall
|
||||
|
||||
return identifier
|
||||
|
||||
# note -> NOTE
|
||||
def parseNote(input, parent):
|
||||
if input.current().type == TokenType.NOTE:
|
||||
token = input.current()
|
||||
value = token.value
|
||||
consumedChars = 1
|
||||
notePitch = value[consumedChars]
|
||||
consumedChars += 1
|
||||
octave = 4
|
||||
duration = 4
|
||||
dot = False
|
||||
if consumedChars < len(value) and value[consumedChars] in ('b', '#'):
|
||||
notePitch += value[consumedChars]
|
||||
consumedChars += 1
|
||||
if consumedChars < len(value) and re.match(r'\d', value[consumedChars]):
|
||||
octave = int(value[consumedChars])
|
||||
consumedChars += 1
|
||||
if consumedChars < len(value) and value[consumedChars] == '.':
|
||||
consumedChars += 1
|
||||
durationString = ''
|
||||
while consumedChars < len(value) and re.match(r'\d', value[consumedChars]):
|
||||
durationString += value[consumedChars]
|
||||
consumedChars += 1
|
||||
duration = int(durationString)
|
||||
if consumedChars < len(value) and value[consumedChars] == 'd':
|
||||
dot = True
|
||||
consumedChars += 1
|
||||
|
||||
input.ahead()
|
||||
return NoteLiteralNode(Note(notePitch, octave, duration, dot), parent, token.pos)
|
||||
return None
|
||||
|
||||
# list -> CLOSE_PAREN | expr listTail
|
||||
def parseList(input, parent):
|
||||
if input.current().type == TokenType.OPEN_PAREN:
|
||||
node = ListNode(parent, input.current().pos)
|
||||
input.ahead()
|
||||
|
||||
# list -> CLOSE_PAREN (end of list)
|
||||
if input.hasCurrent() and input.current().type == TokenType.CLOSE_PAREN:
|
||||
close = CloseListNode(node, input.current().pos)
|
||||
node.append(close)
|
||||
input.ahead()
|
||||
return node
|
||||
|
||||
# list -> expr listTail
|
||||
if input.hasCurrent():
|
||||
token = input.current()
|
||||
expr = parseExpression(input, node)
|
||||
item = ListItemNode(expr, node, token.pos)
|
||||
expr.parent = item
|
||||
node.append(item)
|
||||
listTail = parseListTail(input, item)
|
||||
item.append(listTail)
|
||||
return node
|
||||
return None
|
||||
|
||||
|
||||
# listTail -> COMMA expr listTail | CLOSE_PAREN
|
||||
def parseListTail(input, parent):
|
||||
# listTail -> CLOSE_PAREN
|
||||
if input.hasCurrent() and input.current().type == TokenType.CLOSE_PAREN:
|
||||
close = CloseListNode(parent, input.current().pos)
|
||||
input.ahead()
|
||||
return close
|
||||
|
||||
# listTail -> COMMA expr listTail
|
||||
if input.hasCurrent() and input.hasMore():
|
||||
assertToken(TokenType.COMMA, input)
|
||||
input.ahead()
|
||||
expr = rollup(parseExpression)(input, parent)
|
||||
if expr is not None:
|
||||
item = ListItemNode(expr, parent, expr.pos)
|
||||
expr.parent = item
|
||||
listTail = parseListTail(input, item)
|
||||
item.append(listTail)
|
||||
listTail.parent = item
|
||||
return item
|
||||
|
||||
return None
|
||||
|
||||
# colon -> expr ':' expr
|
||||
def parseColon(input, parent):
|
||||
if input.hasMore() and input.current().type == TokenType.COLON:
|
||||
expr1 = parent.pop(-1)
|
||||
|
||||
token = input.current()
|
||||
input.ahead()
|
||||
|
||||
expr2 = parseExpression(input, parent)
|
||||
|
||||
colon = ColonNode(expr1, expr2, parent, token.pos)
|
||||
expr1.parent = colon
|
||||
expr2.parent = colon
|
||||
return colon
|
||||
return None
|
||||
|
||||
# minus -> '-' int
|
||||
def parseMinus(input, parent):
|
||||
if input.current().type == TokenType.MINUS:
|
||||
token = input.current()
|
||||
input.ahead()
|
||||
|
||||
expr = parseInteger(input, parent)
|
||||
|
||||
return IntegerLiteralNode(-expr.value, parent, token.pos)
|
||||
|
||||
|
||||
# block -> '{' '}' | '{' blockItem
|
||||
def parseBlock(input, parent):
|
||||
# '{'
|
||||
if input.current().type == TokenType.OPEN_BRACKET:
|
||||
token = input.current()
|
||||
input.ahead()
|
||||
|
||||
node = BlockNode(parent, token.pos)
|
||||
|
||||
# '}'
|
||||
if input.hasCurrent() and input.current().type == TokenType.CLOSE_BRACKET:
|
||||
input.ahead()
|
||||
return node
|
||||
|
||||
# blockItem
|
||||
if input.hasCurrent():
|
||||
item = parseBlockItem(input, node)
|
||||
node.append(item)
|
||||
return node
|
||||
|
||||
return None
|
||||
|
||||
# blockItem -> stmt | '}'
|
||||
def parseBlockItem(input, parent):
|
||||
# '}'
|
||||
if input.hasCurrent() and input.current().type == TokenType.CLOSE_BRACKET:
|
||||
close = CloseBlockNode(parent, input.current().pos)
|
||||
input.ahead()
|
||||
return close
|
||||
|
||||
if input.hasCurrent():
|
||||
stmt = parseStatement(input, parent)
|
||||
|
||||
if stmt is not None:
|
||||
item = BlockItemNode(stmt, parent, stmt.pos)
|
||||
stmt.parent = item
|
||||
nextBlockItem = parseBlockItem(input, item)
|
||||
if nextBlockItem != None:
|
||||
item.append(nextBlockItem)
|
||||
return item
|
||||
|
||||
return None
|
||||
|
||||
def rollup(parser):
|
||||
def _rollup(input, parent):
|
||||
node = Node(None, (-1, -1))
|
||||
elem = parser(input, node)
|
||||
while elem is not None:
|
||||
node.append(elem)
|
||||
elem = parser(input, node)
|
||||
return node.children[0] if len(node.children) > 0 else None
|
||||
return _rollup
|
||||
|
||||
def parseFunctionDefinition(input, parent):
|
||||
if input.current().type == TokenType.FUNCTION:
|
||||
token = input.current()
|
||||
input.ahead()
|
||||
|
||||
assertToken(TokenType.IDENTIFIER, input)
|
||||
identifier = parseIdentifier(input, parent)
|
||||
|
||||
assertToken(TokenType.OPEN_PAREN, input)
|
||||
args = parseList(input, parent)
|
||||
|
||||
assertToken(TokenType.OPEN_BRACKET, input)
|
||||
body = parseBlock(input, parent)
|
||||
|
||||
function = FunctionDefinitionNode(identifier, args, body, parent, token.pos)
|
||||
identifier.parent = function
|
||||
args.parent = function
|
||||
body.parent = function
|
||||
|
||||
return function
|
||||
return None
|
||||
|
||||
def parseReturn(input, parent):
|
||||
if input.current().type == TokenType.RETURN:
|
||||
token = input.current()
|
||||
input.ahead()
|
||||
|
||||
expr = parseExpression(input, parent)
|
||||
|
||||
node = ReturnNode(expr, parent, token.pos)
|
||||
expr.parent = node
|
||||
|
||||
return node
|
||||
return None
|
||||
|
||||
# access -> expr '.' expr
|
||||
#TODO: dodać dziedziczenie wszystkich expressions po jednym typie ExpressionNode
|
||||
# i potem sprawdzać przy wszystkich parent.pop(-1) czy pobrany z parenta element
|
||||
# jest rzeczywiście wyrażeniem, bo teraz możliwe jest np. {}.fun()
|
||||
def parseAccess(input, parent):
|
||||
if input.current().type == TokenType.DOT:
|
||||
token = input.current()
|
||||
input.ahead()
|
||||
|
||||
element = parent.pop(-1)
|
||||
|
||||
property = parseExpression(input, parent)
|
||||
|
||||
node = AccessNode(element, property, parent, token.pos)
|
||||
element.parent = node
|
||||
property.parent = node
|
||||
|
||||
return node
|
||||
return None
|
||||
|
||||
def parseStatement(input, parent):
|
||||
stmt = runParsers(input, parent, [
|
||||
parseBlock,
|
||||
parseExpression,
|
||||
parseFunctionDefinition,
|
||||
parseReturn
|
||||
])
|
||||
|
||||
asterisk = parseAsterisk(stmt, input, parent)
|
||||
if asterisk is not None:
|
||||
return asterisk
|
||||
|
||||
return stmt
|
||||
|
||||
# asterisk -> expr '*' stmt
|
||||
def parseAsterisk(expr, input, parent):
|
||||
if input.hasMore() and input.current().type == TokenType.ASTERISK:
|
||||
token = input.current()
|
||||
input.ahead()
|
||||
|
||||
stmt = parseStatement(input, parent)
|
||||
|
||||
asterisk = AsteriskNode(expr, stmt, parent, token.pos)
|
||||
expr.parent = asterisk
|
||||
stmt.parent = asterisk
|
||||
return asterisk
|
||||
return None
|
||||
|
||||
def parseExpression(input, parent):
|
||||
expr = runParsers(input, parent, [
|
||||
parseIntegerAndPercent,
|
||||
parseMinus,
|
||||
parseString,
|
||||
parseNote,
|
||||
parseList,
|
||||
parseIdentifierOrFunctionCallOrAssignment,
|
||||
parseAccess,
|
||||
])
|
||||
|
||||
colon = parseColon(expr, input, parent)
|
||||
if colon is not None:
|
||||
return colon
|
||||
|
||||
return expr
|
||||
|
||||
# colon -> expr ':' expr
|
||||
def parseColon(expr1, input, parent):
|
||||
if input.hasCurrent() and input.current().type == TokenType.COLON:
|
||||
token = input.current()
|
||||
input.ahead()
|
||||
expr2 = parseExpression(input, parent)
|
||||
|
||||
if expr2 is None:
|
||||
raise SyntaxException(input.current().pos, f"Expected expression '{input.current().value}'")
|
||||
colon = ColonNode(expr1, expr2, parent, token.pos)
|
||||
expr1.parent = colon
|
||||
expr2.parent = colon
|
||||
return colon
|
||||
return None
|
||||
|
||||
def parseToken(input, parent):
|
||||
value = runParsers(input, parent, [
|
||||
parseStatement
|
||||
])
|
||||
|
||||
if value is None:
|
||||
raise SyntaxException(None, "Unknown statement") #TODO
|
||||
|
||||
return value
|
||||
|
||||
def parse(input):
|
||||
root = Program()
|
||||
while input.hasCurrent():
|
||||
root.append(parseToken(input, root))
|
||||
return root
|
||||
0
smnp/ast/node/__init__.py
Normal file
0
smnp/ast/node/__init__.py
Normal file
0
smnp/ast/node/access.py
Normal file
0
smnp/ast/node/access.py
Normal file
0
smnp/ast/node/assignment.py
Normal file
0
smnp/ast/node/assignment.py
Normal file
0
smnp/ast/node/asterisk.py
Normal file
0
smnp/ast/node/asterisk.py
Normal file
0
smnp/ast/node/block.py
Normal file
0
smnp/ast/node/block.py
Normal file
0
smnp/ast/node/colon.py
Normal file
0
smnp/ast/node/colon.py
Normal file
0
smnp/ast/node/function.py
Normal file
0
smnp/ast/node/function.py
Normal file
0
smnp/ast/node/identifier.py
Normal file
0
smnp/ast/node/identifier.py
Normal file
0
smnp/ast/node/integer.py
Normal file
0
smnp/ast/node/integer.py
Normal file
0
smnp/ast/node/list.py
Normal file
0
smnp/ast/node/list.py
Normal file
41
smnp/ast/node/model.py
Normal file
41
smnp/ast/node/model.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from smnp.note.model import Note
|
||||
|
||||
class Node:
|
||||
def __init__(self, parent, pos):
|
||||
self.children = []
|
||||
self.parent = parent
|
||||
self.pos = pos
|
||||
for child in self.children:
|
||||
child.parent = self
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
def __len__(self):
|
||||
return len(self.children)
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.children[index]
|
||||
|
||||
def append(self, node):
|
||||
node.parent = self
|
||||
self.children.append(node)
|
||||
|
||||
def pop(self, index):
|
||||
return self.children.pop(index)
|
||||
|
||||
def _print(self, level):
|
||||
string = f"{pad(level)}{self.__class__.__name__}({self.parent.__class__.__name__}):\n"
|
||||
for child in self.children:
|
||||
if isinstance(child, str) or isinstance(child, int) or isinstance(child, Note):
|
||||
string += pad(level + 1) + f"'{child}'\n"
|
||||
else:
|
||||
string += child._print(level + 1)
|
||||
return string
|
||||
|
||||
def __str__(self):
|
||||
return self._print(0)
|
||||
|
||||
|
||||
def pad(level):
|
||||
return (" " * level)
|
||||
0
smnp/ast/node/note.py
Normal file
0
smnp/ast/node/note.py
Normal file
0
smnp/ast/node/percent.py
Normal file
0
smnp/ast/node/percent.py
Normal file
0
smnp/ast/node/program.py
Normal file
0
smnp/ast/node/program.py
Normal file
0
smnp/ast/node/ret.py
Normal file
0
smnp/ast/node/ret.py
Normal file
0
smnp/ast/node/string.py
Normal file
0
smnp/ast/node/string.py
Normal file
0
smnp/ast/parsers/access.py
Normal file
0
smnp/ast/parsers/access.py
Normal file
0
smnp/ast/parsers/assignment.py
Normal file
0
smnp/ast/parsers/assignment.py
Normal file
0
smnp/ast/parsers/asterisk.py
Normal file
0
smnp/ast/parsers/asterisk.py
Normal file
0
smnp/ast/parsers/block.py
Normal file
0
smnp/ast/parsers/block.py
Normal file
0
smnp/ast/parsers/colon.py
Normal file
0
smnp/ast/parsers/colon.py
Normal file
0
smnp/ast/parsers/expression.py
Normal file
0
smnp/ast/parsers/expression.py
Normal file
0
smnp/ast/parsers/function.py
Normal file
0
smnp/ast/parsers/function.py
Normal file
0
smnp/ast/parsers/identifier.py
Normal file
0
smnp/ast/parsers/identifier.py
Normal file
0
smnp/ast/parsers/integer.py
Normal file
0
smnp/ast/parsers/integer.py
Normal file
0
smnp/ast/parsers/list.py
Normal file
0
smnp/ast/parsers/list.py
Normal file
0
smnp/ast/parsers/minus.py
Normal file
0
smnp/ast/parsers/minus.py
Normal file
0
smnp/ast/parsers/note.py
Normal file
0
smnp/ast/parsers/note.py
Normal file
0
smnp/ast/parsers/ret.py
Normal file
0
smnp/ast/parsers/ret.py
Normal file
0
smnp/ast/parsers/statement.py
Normal file
0
smnp/ast/parsers/statement.py
Normal file
0
smnp/ast/parsers/string.py
Normal file
0
smnp/ast/parsers/string.py
Normal file
0
smnp/ast/parsers/token.py
Normal file
0
smnp/ast/parsers/token.py
Normal file
0
smnp/note/__init__.py
Normal file
0
smnp/note/__init__.py
Normal file
22
smnp/note/interval.py
Normal file
22
smnp/note/interval.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from smnp.note.pitch import NotePitch
|
||||
|
||||
semitonesToIntervalName = {
|
||||
0: "1",
|
||||
1: "2m",
|
||||
2: "2M",
|
||||
3: "3m",
|
||||
4: "3M",
|
||||
5: "4",
|
||||
6: "5d/4A",
|
||||
7: "5",
|
||||
8: "6m",
|
||||
9: "6M",
|
||||
10: "7m",
|
||||
11: "7M"
|
||||
}
|
||||
|
||||
def intervalToString(interval):
|
||||
octaveInterval = int(abs(interval) / len(NotePitch))
|
||||
pitchInterval = abs(interval) % len(NotePitch)
|
||||
|
||||
return (str(semitonesToIntervalName[pitchInterval]) + (f"(+{octaveInterval}')" if octaveInterval > 0 else ""))
|
||||
54
smnp/note/model.py
Normal file
54
smnp/note/model.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from smnp.note.pitch import NotePitch
|
||||
|
||||
class Note:
|
||||
def __init__(self, note, octave = 4, duration = 4, dot = False):
|
||||
if type(note) == str:
|
||||
self.note = NotePitch.toPitch(note)
|
||||
else:
|
||||
self.note = note
|
||||
self.octave = octave
|
||||
self.duration = duration
|
||||
self.dot = dot
|
||||
|
||||
def toFrequency(self):
|
||||
return self.note.toFrequency() * 2 ** self.octave
|
||||
|
||||
def transpose(self, interval):
|
||||
origIntRepr = self._intRepr()
|
||||
transposedIntRepr = origIntRepr + interval
|
||||
return Note._fromIntRepr(transposedIntRepr, self.duration, self.dot)
|
||||
|
||||
def withDuration(self, duration):
|
||||
return Note(self.note, self.octave, duration, self.dot)
|
||||
|
||||
def withOctave(self, octave):
|
||||
return Note(self.note, octave, self.duration, self.dot)
|
||||
|
||||
def withDot(self):
|
||||
return Note(self.note, self.octave, self.duration, True)
|
||||
|
||||
def withoutDot(self):
|
||||
return Note(self.note, self.octave, self.duration, False)
|
||||
|
||||
def _intRepr(self):
|
||||
return self.octave * len(NotePitch) + self.note.value
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.note}({self.octave}')[{self.duration}{'.' if self.dot else ''}]"
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
@staticmethod
|
||||
def checkInterval(a, b):
|
||||
return b._intRepr() - a._intRepr()
|
||||
|
||||
@staticmethod
|
||||
def range(a, b):
|
||||
return [Note._fromIntRepr(x) for x in range(a._intRepr(), b._intRepr()+1)]
|
||||
|
||||
@staticmethod
|
||||
def _fromIntRepr(intRepr, duration = 4, dot = False):
|
||||
note = NotePitch(intRepr % len(NotePitch))
|
||||
octave = int(intRepr / len(NotePitch))
|
||||
return Note(note, octave, duration, dot)
|
||||
71
smnp/note/pitch.py
Normal file
71
smnp/note/pitch.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from enum import Enum
|
||||
from smnp.error.syntax import SyntaxException
|
||||
|
||||
class NotePitch(Enum):
|
||||
C = 0
|
||||
CIS = 1
|
||||
D = 2
|
||||
DIS = 3
|
||||
E = 4
|
||||
F = 5
|
||||
FIS = 6
|
||||
G = 7
|
||||
GIS = 8
|
||||
A = 9
|
||||
AIS = 10
|
||||
H = 11
|
||||
|
||||
def toFrequency(self):
|
||||
return {
|
||||
NotePitch.C: 16.35,
|
||||
NotePitch.CIS: 17.32,
|
||||
NotePitch.D: 18.35,
|
||||
NotePitch.DIS: 19.45,
|
||||
NotePitch.E: 20.60,
|
||||
NotePitch.F: 21.83,
|
||||
NotePitch.FIS: 23.12,
|
||||
NotePitch.G: 24.50,
|
||||
NotePitch.GIS: 25.96,
|
||||
NotePitch.A: 27.50,
|
||||
NotePitch.AIS: 29.17,
|
||||
NotePitch.H: 30.87
|
||||
}[self]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
#@staticmethod
|
||||
#def checkInterval(a, b):
|
||||
#return a.value - b.value
|
||||
|
||||
@staticmethod
|
||||
def toPitch(string): #TODO: token zamiast stringa, żeby można było wziąć pos
|
||||
try:
|
||||
return stringToPitch[string.lower()]
|
||||
except KeyError as e:
|
||||
raise SyntaxException(None, f"Note '{string}' does not exist")
|
||||
|
||||
stringToPitch = {
|
||||
'c': NotePitch.C,
|
||||
'c#': NotePitch.CIS,
|
||||
'db': NotePitch.CIS,
|
||||
'd': NotePitch.D,
|
||||
'd#': NotePitch.DIS,
|
||||
'eb': NotePitch.DIS,
|
||||
'e': NotePitch.E,
|
||||
'fb': NotePitch.E,
|
||||
'e#': NotePitch.F,
|
||||
'f': NotePitch.F,
|
||||
'f#': NotePitch.FIS,
|
||||
'gb': NotePitch.FIS,
|
||||
'g': NotePitch.G,
|
||||
'g#': NotePitch.GIS,
|
||||
'ab': NotePitch.GIS,
|
||||
'a': NotePitch.A,
|
||||
'a#': NotePitch.AIS,
|
||||
'b': NotePitch.AIS,
|
||||
'h': NotePitch.H
|
||||
}
|
||||
Reference in New Issue
Block a user