From 2823fd1896ef38963bd989cde2113c20a1c1ffab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Pluta?= Date: Wed, 3 Jul 2019 10:16:05 +0200 Subject: [PATCH] Create note package --- smnp/AST.py | 223 ------------------- smnp/Note.py | 128 ----------- smnp/OldParser.py | 230 -------------------- smnp/Parser.py | 377 --------------------------------- smnp/ast/node/__init__.py | 0 smnp/ast/node/access.py | 0 smnp/ast/node/assignment.py | 0 smnp/ast/node/asterisk.py | 0 smnp/ast/node/block.py | 0 smnp/ast/node/colon.py | 0 smnp/ast/node/function.py | 0 smnp/ast/node/identifier.py | 0 smnp/ast/node/integer.py | 0 smnp/ast/node/list.py | 0 smnp/ast/node/model.py | 41 ++++ smnp/ast/node/note.py | 0 smnp/ast/node/percent.py | 0 smnp/ast/node/program.py | 0 smnp/ast/node/ret.py | 0 smnp/ast/node/string.py | 0 smnp/ast/parsers/access.py | 0 smnp/ast/parsers/assignment.py | 0 smnp/ast/parsers/asterisk.py | 0 smnp/ast/parsers/block.py | 0 smnp/ast/parsers/colon.py | 0 smnp/ast/parsers/expression.py | 0 smnp/ast/parsers/function.py | 0 smnp/ast/parsers/identifier.py | 0 smnp/ast/parsers/integer.py | 0 smnp/ast/parsers/list.py | 0 smnp/ast/parsers/minus.py | 0 smnp/ast/parsers/note.py | 0 smnp/ast/parsers/ret.py | 0 smnp/ast/parsers/statement.py | 0 smnp/ast/parsers/string.py | 0 smnp/ast/parsers/token.py | 0 smnp/note/__init__.py | 0 smnp/note/interval.py | 22 ++ smnp/note/model.py | 54 +++++ smnp/note/pitch.py | 71 +++++++ 40 files changed, 188 insertions(+), 958 deletions(-) delete mode 100644 smnp/AST.py delete mode 100644 smnp/Note.py delete mode 100644 smnp/OldParser.py delete mode 100644 smnp/Parser.py create mode 100644 smnp/ast/node/__init__.py create mode 100644 smnp/ast/node/access.py create mode 100644 smnp/ast/node/assignment.py create mode 100644 smnp/ast/node/asterisk.py create mode 100644 smnp/ast/node/block.py create mode 100644 smnp/ast/node/colon.py create mode 100644 smnp/ast/node/function.py create mode 100644 smnp/ast/node/identifier.py create mode 100644 smnp/ast/node/integer.py create mode 100644 smnp/ast/node/list.py create mode 100644 smnp/ast/node/model.py create mode 100644 smnp/ast/node/note.py create mode 100644 smnp/ast/node/percent.py create mode 100644 smnp/ast/node/program.py create mode 100644 smnp/ast/node/ret.py create mode 100644 smnp/ast/node/string.py create mode 100644 smnp/ast/parsers/access.py create mode 100644 smnp/ast/parsers/assignment.py create mode 100644 smnp/ast/parsers/asterisk.py create mode 100644 smnp/ast/parsers/block.py create mode 100644 smnp/ast/parsers/colon.py create mode 100644 smnp/ast/parsers/expression.py create mode 100644 smnp/ast/parsers/function.py create mode 100644 smnp/ast/parsers/identifier.py create mode 100644 smnp/ast/parsers/integer.py create mode 100644 smnp/ast/parsers/list.py create mode 100644 smnp/ast/parsers/minus.py create mode 100644 smnp/ast/parsers/note.py create mode 100644 smnp/ast/parsers/ret.py create mode 100644 smnp/ast/parsers/statement.py create mode 100644 smnp/ast/parsers/string.py create mode 100644 smnp/ast/parsers/token.py create mode 100644 smnp/note/__init__.py create mode 100644 smnp/note/interval.py create mode 100644 smnp/note/model.py create mode 100644 smnp/note/pitch.py diff --git a/smnp/AST.py b/smnp/AST.py deleted file mode 100644 index 6016c3b..0000000 --- a/smnp/AST.py +++ /dev/null @@ -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 diff --git a/smnp/Note.py b/smnp/Note.py deleted file mode 100644 index 8b737d6..0000000 --- a/smnp/Note.py +++ /dev/null @@ -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 "")) diff --git a/smnp/OldParser.py b/smnp/OldParser.py deleted file mode 100644 index 1f4f42f..0000000 --- a/smnp/OldParser.py +++ /dev/null @@ -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 diff --git a/smnp/Parser.py b/smnp/Parser.py deleted file mode 100644 index 6bba158..0000000 --- a/smnp/Parser.py +++ /dev/null @@ -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 diff --git a/smnp/ast/node/__init__.py b/smnp/ast/node/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/node/access.py b/smnp/ast/node/access.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/node/assignment.py b/smnp/ast/node/assignment.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/node/asterisk.py b/smnp/ast/node/asterisk.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/node/block.py b/smnp/ast/node/block.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/node/colon.py b/smnp/ast/node/colon.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/node/function.py b/smnp/ast/node/function.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/node/identifier.py b/smnp/ast/node/identifier.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/node/integer.py b/smnp/ast/node/integer.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/node/list.py b/smnp/ast/node/list.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/node/model.py b/smnp/ast/node/model.py new file mode 100644 index 0000000..b863393 --- /dev/null +++ b/smnp/ast/node/model.py @@ -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) \ No newline at end of file diff --git a/smnp/ast/node/note.py b/smnp/ast/node/note.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/node/percent.py b/smnp/ast/node/percent.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/node/program.py b/smnp/ast/node/program.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/node/ret.py b/smnp/ast/node/ret.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/node/string.py b/smnp/ast/node/string.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/parsers/access.py b/smnp/ast/parsers/access.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/parsers/assignment.py b/smnp/ast/parsers/assignment.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/parsers/asterisk.py b/smnp/ast/parsers/asterisk.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/parsers/block.py b/smnp/ast/parsers/block.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/parsers/colon.py b/smnp/ast/parsers/colon.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/parsers/expression.py b/smnp/ast/parsers/expression.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/parsers/function.py b/smnp/ast/parsers/function.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/parsers/identifier.py b/smnp/ast/parsers/identifier.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/parsers/integer.py b/smnp/ast/parsers/integer.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/parsers/list.py b/smnp/ast/parsers/list.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/parsers/minus.py b/smnp/ast/parsers/minus.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/parsers/note.py b/smnp/ast/parsers/note.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/parsers/ret.py b/smnp/ast/parsers/ret.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/parsers/statement.py b/smnp/ast/parsers/statement.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/parsers/string.py b/smnp/ast/parsers/string.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/ast/parsers/token.py b/smnp/ast/parsers/token.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/note/__init__.py b/smnp/note/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/smnp/note/interval.py b/smnp/note/interval.py new file mode 100644 index 0000000..aab27b6 --- /dev/null +++ b/smnp/note/interval.py @@ -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 "")) diff --git a/smnp/note/model.py b/smnp/note/model.py new file mode 100644 index 0000000..7ea8e7f --- /dev/null +++ b/smnp/note/model.py @@ -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) diff --git a/smnp/note/pitch.py b/smnp/note/pitch.py new file mode 100644 index 0000000..c9ef7d9 --- /dev/null +++ b/smnp/note/pitch.py @@ -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 +} \ No newline at end of file