Refactor tokenizer
This commit is contained in:
223
smnp/AST.py
Normal file
223
smnp/AST.py
Normal file
@@ -0,0 +1,223 @@
|
||||
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
|
||||
10
smnp/Audio.py
Normal file
10
smnp/Audio.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import pysine
|
||||
from Note import Note, NotePitch
|
||||
import time
|
||||
|
||||
BREAK = 0
|
||||
|
||||
|
||||
#TODO: duration powinno byc w prawdziwych nutach: 4 = cwierc, 2 = pol etc.
|
||||
|
||||
|
||||
219
smnp/Evaluator.py
Normal file
219
smnp/Evaluator.py
Normal file
@@ -0,0 +1,219 @@
|
||||
from Tokenizer import tokenize, TokenType
|
||||
from Parser import parse
|
||||
from AST import *
|
||||
from Note import Note
|
||||
from Error import RuntimeException
|
||||
|
||||
def evaluateProgram(program, environment):
|
||||
for node in program.children:
|
||||
evaluate(node, environment)
|
||||
|
||||
def evaluateInteger(integer, environment):
|
||||
return integer.value
|
||||
|
||||
def evaluatePercent(percent, environment):
|
||||
return percent.value.value * 0.01
|
||||
|
||||
def evaluateIdentifier(identifier, environment):
|
||||
value = environment.findVariable(identifier.identifier)
|
||||
return value
|
||||
|
||||
def evaluateString(string, environment):
|
||||
value = string.value
|
||||
for scope in reversed(environment.scopes):
|
||||
for k, v in scope.items():
|
||||
value = value.replace('{' + k + '}', objectString(v)) #TODO: poprawic
|
||||
return value
|
||||
|
||||
def objectString(obj):
|
||||
if isinstance(obj, str):
|
||||
return obj
|
||||
if isinstance(obj, int):
|
||||
return str(obj)
|
||||
if isinstance(obj, Note):
|
||||
return obj.note.name
|
||||
if isinstance(obj, list):
|
||||
return "(" + ", ".join([objectString(v) for v in obj]) + ")"
|
||||
if isinstance(obj, float):
|
||||
return f"{int(obj*100)}%"
|
||||
if obj is None:
|
||||
raise RuntimeException(None, f"Trying to interpret void")
|
||||
raise RuntimeException(None, f"Don't know how to interpret {str(obj)}")
|
||||
|
||||
def evaluateNote(note, environment):
|
||||
return note.value
|
||||
|
||||
def evaluateFunctionDefinition(definition, environment):
|
||||
name = definition.name
|
||||
params = list([p for p in flatListNode(definition.parameters) if not isinstance(p, CommaNode)])
|
||||
body = definition.body
|
||||
|
||||
if not isinstance(definition.parent, Program):
|
||||
raise RuntimeException(name.pos, f"Functions can be defined only on the top level of script")
|
||||
|
||||
for p in params:
|
||||
if not isinstance(p, IdentifierNode):
|
||||
raise RuntimeException(p.pos, "Parameter of function definition must be an identifier")
|
||||
|
||||
if name.identifier in environment.customFunctions or name.identifier in environment.functions:
|
||||
raise RuntimeException(name.pos, f"Function '{name.identifier}' already exists")
|
||||
|
||||
environment.customFunctions[name.identifier] = {
|
||||
'params': params,
|
||||
'body': flatListNode(body)
|
||||
}
|
||||
|
||||
def flatListNode(listNode):
|
||||
if len(listNode.children[0].children) == 1:
|
||||
return []
|
||||
return _flatListNode(listNode.children[0], [])
|
||||
|
||||
def _flatListNode(listItemNode, list = []):
|
||||
if len(listItemNode.children) == 2:
|
||||
child1 = listItemNode.children[0]
|
||||
child2 = listItemNode.children[1]
|
||||
list.append(child1)
|
||||
_flatListNode(child2, list)
|
||||
return list
|
||||
|
||||
def evaluateAccess(access, environment):
|
||||
|
||||
element = evaluate(access.element, environment)
|
||||
#TODO: narazie tylko metody działają
|
||||
e = evaluateMethodCall(element, access.property, environment)
|
||||
return e
|
||||
|
||||
def evaluateMethodCall(element, functionCall, environment):
|
||||
funcName = functionCall.identifier.identifier
|
||||
arguments = evaluateList(functionCall.arguments, environment)
|
||||
arguments.insert(0, element)
|
||||
#for name, function in environment.customFunctions.items():
|
||||
#if funcName == name:
|
||||
#if len(function['params']) != len(arguments):
|
||||
#raise RuntimeException(functionCall.pos, f"Calling '{funcName}' requires {len(function['params'])} and {len(arguments)} was passed")
|
||||
#environment.scopes.append({ function['params'][i].identifier: v for i, v in enumerate(arguments) })
|
||||
#returnValue = None
|
||||
#for node in function['body']:
|
||||
#if not isinstance(node, ReturnNode):
|
||||
#evaluate(node, environment)
|
||||
#else:
|
||||
#returnValue = evaluateReturn(node, environment)
|
||||
#environment.scopes.pop(-1)
|
||||
#return returnValue
|
||||
for name, definition in environment.methods[type(element)].items():
|
||||
if name == funcName:
|
||||
return definition(arguments, environment)
|
||||
raise RuntimeException(functionCall.pos, f"Method '{funcName}' does not exist")
|
||||
|
||||
def evaluateFunctionCall(functionCall, environment):
|
||||
funcName = functionCall.identifier.identifier
|
||||
arguments = evaluateList(functionCall.arguments, environment)
|
||||
for name, function in environment.customFunctions.items():
|
||||
if funcName == name:
|
||||
if len(function['params']) != len(arguments):
|
||||
raise RuntimeException(functionCall.pos, f"Calling '{funcName}' requires {len(function['params'])} and {len(arguments)} was passed")
|
||||
environment.scopes.append({ function['params'][i].identifier: v for i, v in enumerate(arguments) })
|
||||
returnValue = None
|
||||
for node in function['body']:
|
||||
if not isinstance(node, ReturnNode):
|
||||
evaluate(node, environment)
|
||||
else:
|
||||
returnValue = evaluateReturn(node, environment)
|
||||
environment.scopes.pop(-1)
|
||||
return returnValue
|
||||
for name, definition in environment.functions.items():
|
||||
if name == funcName:
|
||||
return definition(arguments, environment)
|
||||
raise RuntimeException(functionCall.pos, f"Function '{funcName}' does not exist")
|
||||
|
||||
def evaluateReturn(returnNode, environment):
|
||||
return evaluate(returnNode.value, environment)
|
||||
|
||||
def evaluateComma(comma, environment):
|
||||
pass
|
||||
|
||||
def evaluateBlock(block, environment):
|
||||
environment.scopes.append({})
|
||||
for node in flatListNode(block):
|
||||
evaluate(node, environment)
|
||||
environment.scopes.pop(-1)
|
||||
|
||||
def evaluateList(list, environment):
|
||||
return [evaluate(e, environment) for e in flatListNode(list) if not isinstance(e, CommaNode)]
|
||||
|
||||
def evaluateAssignment(assignment, environment):
|
||||
target = assignment.target.identifier
|
||||
value = evaluate(assignment.value, environment)
|
||||
scopeOfExistingVariable = environment.findVariableScope(target)
|
||||
if scopeOfExistingVariable is not None:
|
||||
scopeOfExistingVariable[target] = value
|
||||
else:
|
||||
environment.scopes[-1][target] = value
|
||||
|
||||
def evaluateAsterisk(asterisk, environment):
|
||||
count = evaluate(asterisk.iterator, environment)
|
||||
if isinstance(count, int):
|
||||
for i in range(count):
|
||||
if isinstance(asterisk.iterator, IdentifierNode):
|
||||
environment.scopes[-1][f"_{asterisk.iterator.identifier}"] = i+1
|
||||
else:
|
||||
environment.scopes[-1]["_"] = i+1
|
||||
evaluate(asterisk.statement, environment)
|
||||
if isinstance(asterisk.iterator, IdentifierNode):
|
||||
del environment.scopes[-1][f"_{asterisk.iterator.identifier}"]
|
||||
else:
|
||||
environment.scopes[-1]["_"] = i+1
|
||||
elif isinstance(count, list):
|
||||
for i, v in enumerate(count):
|
||||
if isinstance(asterisk.iterator, IdentifierNode):
|
||||
environment.scopes[-1][f"_{asterisk.iterator.identifier}"] = i+1
|
||||
environment.scopes[-1][f"{asterisk.iterator.identifier}_"] = v
|
||||
else:
|
||||
environment.scopes[-1]["_"] = i+1
|
||||
environment.scopes[-1]["__"] = v
|
||||
evaluate(asterisk.statement, environment)
|
||||
if isinstance(asterisk.iterator, IdentifierNode):
|
||||
del environment.scopes[-1][f"_{asterisk.iterator.identifier}"]
|
||||
del environment.scopes[-1][f"{asterisk.iterator.identifier}_"]
|
||||
else:
|
||||
del environment.scopes[-1]["_"]
|
||||
del environment.scopes[-1]["__"]
|
||||
|
||||
def evaluateColon(colon, environment):
|
||||
if isinstance(colon.a, NoteLiteralNode) and isinstance(colon.b, NoteLiteralNode):
|
||||
return Note.range(colon.a.value, colon.b.value)
|
||||
elif isinstance(colon.a, IntegerLiteralNode) and isinstance(colon.b, IntegerLiteralNode):
|
||||
return list(range(colon.a.value, colon.b.value+1))
|
||||
raise RuntimeException(colon.pos, "Invalid colon arguments")
|
||||
|
||||
def evaluate(input, environment):
|
||||
if isinstance(input, Program):
|
||||
return evaluateProgram(input, environment)
|
||||
if isinstance(input, IntegerLiteralNode):
|
||||
return evaluateInteger(input, environment)
|
||||
if isinstance(input, PercentNode):
|
||||
return evaluatePercent(input, environment)
|
||||
if isinstance(input, StringLiteralNode):
|
||||
return evaluateString(input, environment)
|
||||
if isinstance(input, NoteLiteralNode):
|
||||
return evaluateNote(input, environment)
|
||||
if isinstance(input, FunctionDefinitionNode):
|
||||
return evaluateFunctionDefinition(input, environment)
|
||||
if isinstance(input, FunctionCallNode):
|
||||
return evaluateFunctionCall(input, environment)
|
||||
if isinstance(input, AccessNode):
|
||||
return evaluateAccess(input, environment)
|
||||
if isinstance(input, CommaNode):
|
||||
return evaluateComma(input, environment)
|
||||
if isinstance(input, BlockNode):
|
||||
return evaluateBlock(input, environment)
|
||||
if isinstance(input, ListNode):
|
||||
return evaluateList(input, environment)
|
||||
if isinstance(input, AssignmentNode):
|
||||
return evaluateAssignment(input, environment)
|
||||
if isinstance(input, AsteriskNode):
|
||||
return evaluateAsterisk(input, environment)
|
||||
if isinstance(input, ColonNode):
|
||||
return evaluateColon(input, environment)
|
||||
if isinstance(input, IdentifierNode):
|
||||
return evaluateIdentifier(input, environment)
|
||||
58
smnp/NoiseDetector.py
Normal file
58
smnp/NoiseDetector.py
Normal file
@@ -0,0 +1,58 @@
|
||||
import numpy as np
|
||||
import sounddevice as sd
|
||||
import os
|
||||
from collections import deque
|
||||
from statistics import mean
|
||||
|
||||
class NoiseDetector:
|
||||
def __init__(self, noiseTreshold=300, silenceTreshold=10, bufferSize=20):
|
||||
self.reachedNoise = False
|
||||
self.reachedSilence = False
|
||||
self.noiseTreshold = noiseTreshold
|
||||
self.silenceTreshold = silenceTreshold
|
||||
self.buffer = deque([])
|
||||
self.bufferSize = bufferSize
|
||||
self.mean = 0
|
||||
|
||||
def callback(self, indata, outdata, frames, time):
|
||||
volumeNorm = int(np.linalg.norm(indata)*10)
|
||||
|
||||
if len(self.buffer) < self.bufferSize:
|
||||
self.buffer.append(volumeNorm)
|
||||
|
||||
if len(self.buffer) == self.bufferSize:
|
||||
self.buffer.rotate(1)
|
||||
self.buffer.popleft()
|
||||
self.buffer.appendleft(volumeNorm)
|
||||
self.mean = mean(self.buffer)
|
||||
|
||||
if self.mean > self.noiseTreshold:
|
||||
self.reachedNoise = True
|
||||
if self.reachedNoise and self.mean < self.silenceTreshold:
|
||||
self.reachedSilence = True
|
||||
|
||||
def waitForComplete(self):
|
||||
with sd.InputStream(callback=self.callback):
|
||||
while not self.reachedSilence:
|
||||
sd.sleep(10)
|
||||
|
||||
def test(self):
|
||||
with sd.InputStream(callback=self.callback):
|
||||
while True:
|
||||
print(f"Mic level: {self.mean}", end="\r")
|
||||
sd.sleep(200)
|
||||
|
||||
|
||||
def waitForSound(args, env):
|
||||
noiseTreshold = 300
|
||||
silenceTreshold = 10
|
||||
if len(args) == 2 and all(isinstance(arg, int) for arg in args):
|
||||
noiseTreshold = args[0]
|
||||
silenceTreshold = args[1]
|
||||
elif len(args) == 1 and isinstance(args[0], int):
|
||||
noiseTreshold = args[0]
|
||||
elif len(args) > 2:
|
||||
return # not valid signature
|
||||
detector = NoiseDetector(noiseTreshold, silenceTreshold, 20)
|
||||
detector.waitForComplete()
|
||||
|
||||
128
smnp/Note.py
Normal file
128
smnp/Note.py
Normal file
@@ -0,0 +1,128 @@
|
||||
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 ""))
|
||||
230
smnp/OldParser.py
Normal file
230
smnp/OldParser.py
Normal file
@@ -0,0 +1,230 @@
|
||||
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
Normal file
377
smnp/Parser.py
Normal file
@@ -0,0 +1,377 @@
|
||||
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
|
||||
43
smnp/Synth.py
Normal file
43
smnp/Synth.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import numpy as np
|
||||
import sounddevice as sd
|
||||
from Note import Note
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
FS = 44100
|
||||
|
||||
|
||||
def play(args, env):
|
||||
if len(args) > 0 and isinstance(args[0], list):
|
||||
playList(args[0], env)
|
||||
return
|
||||
playList(args, env)
|
||||
|
||||
def playList(notes, env):
|
||||
bpm = env.findVariable("bpm", int)
|
||||
if all(isinstance(x, Note) or isinstance(x, int) for x in notes):
|
||||
for x in notes:
|
||||
if isinstance(x, Note):
|
||||
playNote(x, bpm)
|
||||
if isinstance(x, int):
|
||||
doPause(x, bpm)
|
||||
|
||||
def playNote(note, bpm):
|
||||
frequency = note.toFrequency()
|
||||
duration = 60 * 4 / note.duration / bpm
|
||||
duration *= 1.5 if note.dot else 1
|
||||
sine(frequency, duration)
|
||||
|
||||
def sine(frequency, duration):
|
||||
samples = (np.sin(2*np.pi*np.arange(FS*duration)*frequency/FS)).astype(np.float32)
|
||||
sd.play(samples, FS)
|
||||
time.sleep(duration)
|
||||
|
||||
def doPause(value, bpm):
|
||||
time.sleep(60 * 4 / value / bpm)
|
||||
|
||||
def pause(args, env):
|
||||
bpm = env.findVariable("bpm")
|
||||
value = args[0]
|
||||
doPause(value, bpm)
|
||||
0
smnp/__init__.py
Normal file
0
smnp/__init__.py
Normal file
4
smnp/__main__.py
Normal file
4
smnp/__main__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from smnp.main import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
251
smnp/environment/Environment.py
Normal file
251
smnp/environment/Environment.py
Normal file
@@ -0,0 +1,251 @@
|
||||
import sys
|
||||
from parser.Environment import objectString
|
||||
from Note import *
|
||||
import random
|
||||
import Synth
|
||||
import time
|
||||
from Error import RuntimeException
|
||||
from NoiseDetector import waitForSound
|
||||
from Parser import parseNote
|
||||
from Tokenizer import Token, TokenType, tokenizeNote
|
||||
from functools import reduce
|
||||
|
||||
types = {
|
||||
int: 'integer',
|
||||
str: 'string',
|
||||
list: 'list',
|
||||
float: 'percent',
|
||||
Note: 'note',
|
||||
type(None): 'void'
|
||||
}
|
||||
|
||||
class Environment():
|
||||
def __init__(self, scopes, functions, methods):
|
||||
self.scopes = scopes
|
||||
self.functions = functions
|
||||
self.methods = methods
|
||||
self.customFunctions = {}
|
||||
self.callStack = []
|
||||
|
||||
def findVariable(self, name, type=None):
|
||||
for scope in reversed(self.scopes):
|
||||
if name in scope:
|
||||
value = scope[name]
|
||||
if type is not None:
|
||||
if isinstance(value, type):
|
||||
return value
|
||||
else:
|
||||
return value
|
||||
raise RuntimeException(None, f"Variable '{name}' is not declared" + ("" if type is None else f" (expected type: {types[type]})"))
|
||||
|
||||
def findVariableScope(self, name, type=None):
|
||||
for scope in reversed(self.scopes):
|
||||
if name in scope:
|
||||
if type is not None:
|
||||
if isinstance(scope[name], type):
|
||||
return scope
|
||||
else:
|
||||
return scope
|
||||
|
||||
def doPrint(args, env):
|
||||
print("".join([objectString(arg) for arg in args]))
|
||||
|
||||
def semitonesList(list):
|
||||
for x in list:
|
||||
if not isinstance(x, Note) and not isistance(x, int):
|
||||
pass # invalid arguments
|
||||
withoutPauses = tuple(filter(lambda x: isinstance(x, Note), list))
|
||||
r = [Note.checkInterval(withoutPauses[i-1], withoutPauses[i]) for i, _ in enumerate(withoutPauses) if i != 0]
|
||||
return r
|
||||
|
||||
def returnElementOrList(list):
|
||||
return list[0] if len(list) == 1 else list
|
||||
|
||||
def semitones(args, env):
|
||||
if len(args) > 0 and isinstance(args[0], list):
|
||||
return returnElementOrList(semitonesList(args[0]))
|
||||
return returnElementOrList(semitonesList(args))
|
||||
|
||||
def intervalList(list):
|
||||
r = [intervalToString(x) for x in semitonesList(list)]
|
||||
return returnElementOrList(r)
|
||||
|
||||
def interval(args, env):
|
||||
if len(args) > 0 and isinstance(args[0], list):
|
||||
return intervalList(args[0])
|
||||
return intervalList(args)
|
||||
|
||||
def transposeTo(args, env):
|
||||
if len(args) > 1 and isinstance(args[0], Note) and all(isinstance(x, list) for i, x in enumerate(args) if i != 0):
|
||||
target = args[0]
|
||||
result = []
|
||||
for i, notes in enumerate(args):
|
||||
if i == 0:
|
||||
continue
|
||||
if len(notes) > 0:
|
||||
first = notes[0]
|
||||
semitones = semitonesList([first, target])[0]
|
||||
result.append([note.transpose(semitones) for note in notes if isinstance(note, Note)])
|
||||
else:
|
||||
result.append([])
|
||||
return returnElementOrList(result)
|
||||
else:
|
||||
pass # not valid signature
|
||||
|
||||
|
||||
def transpose(args, env):
|
||||
if len(args) > 1 and isinstance(args[0], int) and all(isinstance(arg, list) for i, arg in enumerate(args) if i != 0):
|
||||
value = args[0]
|
||||
transposed = []
|
||||
for i, arg in enumerate(args):
|
||||
if i == 0:
|
||||
continue
|
||||
if not isinstance(arg, list):
|
||||
return # is not list
|
||||
transposed.append([note.transpose(value) for note in arg if isinstance(note, Note)])
|
||||
return returnElementOrList(transposed)
|
||||
if len(args) > 1 and all(isinstance(arg, Note) for i, arg in enumerate(args) if i != 0):
|
||||
value = args[0]
|
||||
transposed = [note.transpose(value) for i, note in enumerate(args) if i != 0]
|
||||
return returnElementOrList(transposed)
|
||||
else:
|
||||
return # not valid signature
|
||||
|
||||
def objectType(args, env):
|
||||
if len(args) == 1:
|
||||
return types[type(args[0])]
|
||||
else:
|
||||
pass # not valid signature
|
||||
|
||||
def exit(args, env):
|
||||
if len(args) == 1 and isinstance(args[0], int):
|
||||
sys.exit(args[0])
|
||||
else:
|
||||
pass # not valid signature
|
||||
|
||||
def sleep(args, env):
|
||||
if len(args) == 1 and isinstance(args[0], int):
|
||||
time.sleep(args[0])
|
||||
else:
|
||||
pass # not valid signature
|
||||
|
||||
def rand(args, env):
|
||||
if not all(isinstance(x, list) and len(x) == 2 and isinstance(x[0], float) for x in args):
|
||||
return # not valid signature
|
||||
if sum([x[0] for x in args]) != 1.0:
|
||||
return # not sums to 100%
|
||||
choice = random.random()
|
||||
acc = 0
|
||||
for e in args:
|
||||
acc += e[0]
|
||||
if choice <= acc:
|
||||
return e[1]
|
||||
|
||||
def read(args, env):
|
||||
if len(args) == 2 and isinstance(args[0], str) and isinstance(args[1], str):
|
||||
print(args[0], end="")
|
||||
value = input()
|
||||
if args[1] == "integer":
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError as v:
|
||||
pass # not int
|
||||
elif args[1] == "string":
|
||||
return value
|
||||
elif args[1] == "note":
|
||||
chars, token = tokenizeNote(value, 0, 0)
|
||||
if chars == 0:
|
||||
return # not note
|
||||
return parseNote([token], None).value
|
||||
else:
|
||||
pass # invalid type
|
||||
elif len(args) == 1 and isinstance(args[0], str):
|
||||
print(args[0], end="")
|
||||
return input()
|
||||
elif len(args) == 0:
|
||||
return input()
|
||||
else:
|
||||
pass # not valid signature
|
||||
|
||||
def changeDuration(args, env):
|
||||
if len(args) == 2 and isinstance(args[0], Note) and isinstance(args[1], int):
|
||||
return args[0].withDuration(args[1])
|
||||
return # invalid signature
|
||||
|
||||
def changeOctave(args, env):
|
||||
if len(args) == 2 and isinstance(args[0], Note) and isinstance(args[1], int):
|
||||
return args[0].withOctave(args[1])
|
||||
return # invalid signature
|
||||
|
||||
def tupletList(n, m, list):
|
||||
return [note.withDuration(note.duration * n / m) for note in list]
|
||||
|
||||
def tuplet(args, env):
|
||||
if len(args) > 2 and type(args[0]) == int and type(args[1]) == int and all(type(x) == Note for x in args[2:]):
|
||||
n = args[0] # how many notes
|
||||
m = args[1] # instead of number of notes (3-tuplet: 3 instead 2; 5-tuplet: 5 instead 4 etc.)
|
||||
return returnElementOrList(tupletList(n, m, args[2:]))
|
||||
elif len(args) == 3 and type(args[0]) == int and type(args[1]) == int and type(args[2]) == list and all(type(x) == Note for x in args[2]):
|
||||
n = args[0]
|
||||
m = args[1]
|
||||
l = args[2]
|
||||
return returnElementOrList(tupletList(n, m, l))
|
||||
else:
|
||||
pass # not valid signature
|
||||
|
||||
def combine(args, env):
|
||||
if all(type(x) == list for x in args):
|
||||
return reduce((lambda x, y: x + y), args)
|
||||
|
||||
def flat(args, env):
|
||||
return _flat(args, [])
|
||||
|
||||
def _flat(input, output = []):
|
||||
for item in input:
|
||||
if type(item) == list:
|
||||
_flat(item, output)
|
||||
else:
|
||||
output.append(item)
|
||||
return output
|
||||
|
||||
def createEnvironment():
|
||||
functions = {
|
||||
'print': doPrint,
|
||||
'synth': Synth.play,
|
||||
'pause': Synth.pause,
|
||||
'type': objectType,
|
||||
'sample': sample,
|
||||
'semitones': semitones,
|
||||
'interval': interval,
|
||||
'transpose': transpose,
|
||||
'transposeTo': transposeTo,
|
||||
'sleep': sleep,
|
||||
'random': rand,
|
||||
'changeDuration': changeDuration,
|
||||
'changeOctave': changeOctave,
|
||||
'wait': waitForSound,
|
||||
'read': read,
|
||||
'debug': lambda args, env: print(args),
|
||||
'tuplet': tuplet,
|
||||
'combine': combine,
|
||||
'flat': flat,
|
||||
'exit': exit
|
||||
|
||||
}
|
||||
|
||||
methods = {
|
||||
str: {},
|
||||
list: {},
|
||||
float: {},
|
||||
Note: {
|
||||
'synth': Synth.play
|
||||
},
|
||||
type(None): {},
|
||||
}
|
||||
|
||||
variables = {
|
||||
"bpm": 120
|
||||
}
|
||||
|
||||
return Environment([ variables ], functions, methods)
|
||||
|
||||
0
smnp/error/__init__.py
Normal file
0
smnp/error/__init__.py
Normal file
4
smnp/error/runtime.py
Normal file
4
smnp/error/runtime.py
Normal file
@@ -0,0 +1,4 @@
|
||||
class RuntimeException(Exception):
|
||||
def __init__(self, pos, msg):
|
||||
posStr = "" if pos is None else f"[line {pos[0]+1}, col {pos[1]+1}]"
|
||||
self.msg = f"Syntax error {posStr}:\n{msg}"
|
||||
4
smnp/error/syntax.py
Normal file
4
smnp/error/syntax.py
Normal file
@@ -0,0 +1,4 @@
|
||||
class SyntaxException(Exception):
|
||||
def __init__(self, pos, msg):
|
||||
posStr = "" if pos is None else f"[line {pos[0]+1}, col {pos[1]+1}]"
|
||||
self.msg = f"Syntax error {posStr}:\n{msg}"
|
||||
29
smnp/main.py
Normal file
29
smnp/main.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import sys
|
||||
from smnp.error.syntax import SyntaxException
|
||||
from smnp.error.runtime import RuntimeException
|
||||
from smnp.token.tokenizer import tokenize
|
||||
#from Tokenizer import tokenize
|
||||
#from Parser import parse
|
||||
#from Evaluator import evaluate
|
||||
#from Environment import createEnvironment
|
||||
#from Error import SyntaxException, RuntimeException
|
||||
|
||||
def main():
|
||||
try:
|
||||
with open(sys.argv[1], 'r') as source:
|
||||
lines = [line.rstrip('\n') for line in source.readlines()]
|
||||
|
||||
#env = createEnvironment()
|
||||
|
||||
tokens = tokenize(lines)
|
||||
print(tokens)
|
||||
#ast = parse(tokens)
|
||||
|
||||
#evaluate(ast, env)
|
||||
except SyntaxException as e:
|
||||
print(e.msg)
|
||||
except RuntimeException as e:
|
||||
print(e.msg)
|
||||
except KeyboardInterrupt:
|
||||
print("Program interrupted")
|
||||
|
||||
1
smnp/token/__init__.py
Normal file
1
smnp/token/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__all__ = ["tokenize"]
|
||||
54
smnp/token/model.py
Normal file
54
smnp/token/model.py
Normal file
@@ -0,0 +1,54 @@
|
||||
class Token:
|
||||
def __init__(self, type, value, pos):
|
||||
self.type = type
|
||||
self.value = value
|
||||
self.pos = pos
|
||||
def __str__(self):
|
||||
return "Token(" + str(self.type) + ", '" + self.value + "', " + str(self.pos) + ")"
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
class TokenList:
|
||||
def __init__(self, tokens = []):
|
||||
self.tokens = tokens
|
||||
self.cursor = 0
|
||||
self.snap = 0
|
||||
|
||||
def append(self, token):
|
||||
self.tokens.append(token)
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.tokens[index]
|
||||
|
||||
def current(self):
|
||||
if self.cursor >= len(self.tokens):
|
||||
raise RuntimeError(f"Cursor points to not existing token! Cursor = {self.cursor}, len = {len(self.tokens)}")
|
||||
return self.tokens[self.cursor]
|
||||
|
||||
def next(self, number=1):
|
||||
return self.tokens[self.cursor + number]
|
||||
|
||||
def prev(self, number=1):
|
||||
return self.tokens[self.cursor - number]
|
||||
|
||||
def hasMore(self, count=1):
|
||||
return self.cursor + count < len(self.tokens)
|
||||
|
||||
def hasCurrent(self):
|
||||
return self.cursor < len(self.tokens)
|
||||
|
||||
def ahead(self):
|
||||
self.cursor += 1
|
||||
|
||||
def snapshot(self):
|
||||
self.snapshot = self.cursor
|
||||
|
||||
def reset(self):
|
||||
self.cursor = self.snapshot
|
||||
return self.tokens[self.cursor]
|
||||
|
||||
def __str__(self):
|
||||
return f"[Cursor: {self.cursor}\n{', '.join([str(token) for token in self.tokens])}]"
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
81
smnp/token/tokenizer.py
Normal file
81
smnp/token/tokenizer.py
Normal file
@@ -0,0 +1,81 @@
|
||||
import sys
|
||||
import time
|
||||
import re
|
||||
from smnp.error.syntax import SyntaxException
|
||||
from smnp.token.type import TokenType
|
||||
from smnp.token.model import Token, TokenList
|
||||
from smnp.token.tools import tokenizeChar, tokenizeRegexPattern
|
||||
from smnp.token.tokenizers.paren import tokenizeOpenParen, tokenizeCloseParen
|
||||
from smnp.token.tokenizers.asterisk import tokenizeAsterisk
|
||||
from smnp.token.tokenizers.whitespace import tokenizeWhitespaces
|
||||
from smnp.token.tokenizers.identifier import tokenizeIdentifier
|
||||
from smnp.token.tokenizers.comma import tokenizeComma
|
||||
from smnp.token.tokenizers.string import tokenizeString
|
||||
from smnp.token.tokenizers.integer import tokenizeInteger
|
||||
from smnp.token.tokenizers.bracket import tokenizeOpenBracket, tokenizeCloseBracket
|
||||
from smnp.token.tokenizers.assign import tokenizeAssign
|
||||
from smnp.token.tokenizers.colon import tokenizeColon
|
||||
from smnp.token.tokenizers.comment import tokenizeComment
|
||||
from smnp.token.tokenizers.note import tokenizeNote
|
||||
from smnp.token.tokenizers.function import tokenizeFunction
|
||||
from smnp.token.tokenizers.ret import tokenizeReturn
|
||||
from smnp.token.tokenizers.percent import tokenizePercent
|
||||
from smnp.token.tokenizers.minus import tokenizeMinus
|
||||
from smnp.token.tokenizers.dot import tokenizeDot
|
||||
|
||||
tokenizers = (
|
||||
tokenizeOpenParen,
|
||||
tokenizeCloseParen,
|
||||
tokenizeAsterisk,
|
||||
tokenizeString,
|
||||
tokenizeFunction,
|
||||
tokenizeReturn,
|
||||
tokenizeInteger,
|
||||
tokenizeNote,
|
||||
tokenizeIdentifier,
|
||||
tokenizeComma,
|
||||
tokenizeOpenBracket,
|
||||
tokenizeCloseBracket,
|
||||
tokenizeAssign,
|
||||
tokenizeColon,
|
||||
tokenizePercent,
|
||||
tokenizeMinus,
|
||||
tokenizeDot,
|
||||
tokenizeComment,
|
||||
tokenizeWhitespaces,
|
||||
)
|
||||
|
||||
filters = [
|
||||
lambda token: token.type is not None,
|
||||
lambda token: token.type != TokenType.COMMENT
|
||||
]
|
||||
|
||||
def tokenize(lines):
|
||||
tokens = []
|
||||
for lineNumber, line in enumerate(lines):
|
||||
current = 0
|
||||
while current < len(line):
|
||||
consumedChars, token = combinedTokenizer(line, current, lineNumber)
|
||||
|
||||
if consumedChars == 0:
|
||||
raise SyntaxException((lineNumber, current), f"Unknown symbol '{line[current]}'")
|
||||
|
||||
current += consumedChars
|
||||
tokens.append(token)
|
||||
|
||||
return TokenList(filterTokens(filters, tokens))
|
||||
|
||||
def combinedTokenizer(line, current, lineNumber):
|
||||
for tokenizer in tokenizers:
|
||||
consumedChars, token = tokenizer(line, current, lineNumber)
|
||||
if consumedChars > 0:
|
||||
return (consumedChars, token)
|
||||
return (0, None)
|
||||
|
||||
def filterTokens(filters, tokens):
|
||||
if not filters:
|
||||
return tokens
|
||||
|
||||
return filterTokens(filters[1:], (token for token in tokens if filters[0](token)))
|
||||
|
||||
__all__ = ["tokenize"]
|
||||
0
smnp/token/tokenizers/__init__.py
Normal file
0
smnp/token/tokenizers/__init__.py
Normal file
5
smnp/token/tokenizers/assign.py
Normal file
5
smnp/token/tokenizers/assign.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from smnp.token.tools import tokenizeChar
|
||||
from smnp.token.type import TokenType
|
||||
|
||||
def tokenizeAssign(input, current, line):
|
||||
return tokenizeChar(TokenType.ASSIGN, '=', input, current, line)
|
||||
5
smnp/token/tokenizers/asterisk.py
Normal file
5
smnp/token/tokenizers/asterisk.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from smnp.token.tools import tokenizeChar
|
||||
from smnp.token.type import TokenType
|
||||
|
||||
def tokenizeAsterisk(input, current, line):
|
||||
return tokenizeChar(TokenType.ASTERISK, '*', input, current, line)
|
||||
8
smnp/token/tokenizers/bracket.py
Normal file
8
smnp/token/tokenizers/bracket.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from smnp.token.tools import tokenizeChar
|
||||
from smnp.token.type import TokenType
|
||||
|
||||
def tokenizeOpenBracket(input, current, line):
|
||||
return tokenizeChar(TokenType.OPEN_BRACKET, '{', input, current, line)
|
||||
|
||||
def tokenizeCloseBracket(input, current, line):
|
||||
return tokenizeChar(TokenType.CLOSE_BRACKET, '}', input, current, line)
|
||||
5
smnp/token/tokenizers/colon.py
Normal file
5
smnp/token/tokenizers/colon.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from smnp.token.tools import tokenizeChar
|
||||
from smnp.token.type import TokenType
|
||||
|
||||
def tokenizeColon(input, current, line):
|
||||
return tokenizeChar(TokenType.COLON, ':', input, current, line)
|
||||
5
smnp/token/tokenizers/comma.py
Normal file
5
smnp/token/tokenizers/comma.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from smnp.token.tools import tokenizeChar
|
||||
from smnp.token.type import TokenType
|
||||
|
||||
def tokenizeComma(input, current, line):
|
||||
return tokenizeChar(TokenType.COMMA, ',', input, current, line)
|
||||
13
smnp/token/tokenizers/comment.py
Normal file
13
smnp/token/tokenizers/comment.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from smnp.token.type import TokenType
|
||||
from smnp.token.model import Token
|
||||
|
||||
def tokenizeComment(input, current, line):
|
||||
if input[current] == '#':
|
||||
consumedChars = 0
|
||||
value = ''
|
||||
while current+consumedChars < len(input):
|
||||
value += input[current+consumedChars]
|
||||
consumedChars += 1
|
||||
pass
|
||||
return (consumedChars, Token(TokenType.COMMENT, value, (line, current)))
|
||||
return (0, None)
|
||||
5
smnp/token/tokenizers/dot.py
Normal file
5
smnp/token/tokenizers/dot.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from smnp.token.tools import tokenizeChar
|
||||
from smnp.token.type import TokenType
|
||||
|
||||
def tokenizeDot(input, current, line):
|
||||
return tokenizeChar(TokenType.DOT, '.', input, current, line)
|
||||
5
smnp/token/tokenizers/function.py
Normal file
5
smnp/token/tokenizers/function.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from smnp.token.tools import tokenizeKeyword
|
||||
from smnp.token.type import TokenType
|
||||
|
||||
def tokenizeFunction(input, current, line):
|
||||
return tokenizeKeyword(TokenType.FUNCTION, 'function', input, current, line)
|
||||
5
smnp/token/tokenizers/identifier.py
Normal file
5
smnp/token/tokenizers/identifier.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from smnp.token.tools import tokenizeRegexPattern
|
||||
from smnp.token.type import TokenType
|
||||
|
||||
def tokenizeIdentifier(input, current, line):
|
||||
return tokenizeRegexPattern(TokenType.IDENTIFIER, r'\w', input, current, line)
|
||||
5
smnp/token/tokenizers/integer.py
Normal file
5
smnp/token/tokenizers/integer.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from smnp.token.tools import tokenizeRegexPattern
|
||||
from smnp.token.type import TokenType
|
||||
|
||||
def tokenizeInteger(input, current, line):
|
||||
return tokenizeRegexPattern(TokenType.INTEGER, r'\d', input, current, line)
|
||||
5
smnp/token/tokenizers/minus.py
Normal file
5
smnp/token/tokenizers/minus.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from smnp.token.tools import tokenizeChar
|
||||
from smnp.token.type import TokenType
|
||||
|
||||
def tokenizeMinus(input, current, line):
|
||||
return tokenizeChar(TokenType.MINUS, '-', input, current, line)
|
||||
37
smnp/token/tokenizers/note.py
Normal file
37
smnp/token/tokenizers/note.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import re
|
||||
from smnp.token.type import TokenType
|
||||
from smnp.token.model import Token
|
||||
|
||||
def tokenizeNote(input, current, line):
|
||||
consumedChars = 0
|
||||
value = ''
|
||||
if input[current] == '@':
|
||||
consumedChars += 1
|
||||
value += input[current]
|
||||
if input[current+consumedChars] in ('C', 'c', 'D', 'd', 'E', 'e', 'F', 'f', 'G', 'g', 'A', 'a', 'H', 'h', 'B', 'b'):
|
||||
value += input[current+consumedChars]
|
||||
consumedChars += 1
|
||||
|
||||
if current+consumedChars < len(input) and input[current+consumedChars] in ('b', '#'):
|
||||
value += input[current+consumedChars]
|
||||
consumedChars += 1
|
||||
|
||||
if current+consumedChars < len(input) and re.match(r'\d', input[current+consumedChars]):
|
||||
value += input[current+consumedChars]
|
||||
consumedChars += 1
|
||||
|
||||
if current+consumedChars < len(input) and input[current+consumedChars] == '.':
|
||||
duration = input[current+consumedChars]
|
||||
consumedChars += 1
|
||||
while current+consumedChars < len(input) and re.match(r'\d', input[current+consumedChars]):
|
||||
duration += input[current+consumedChars]
|
||||
consumedChars += 1
|
||||
if current+consumedChars < len(input) and input[current+consumedChars] == 'd':
|
||||
duration += input[current+consumedChars]
|
||||
consumedChars += 1
|
||||
if len(duration) > 1:
|
||||
value += duration
|
||||
else:
|
||||
consumedChars -= 1
|
||||
return (consumedChars, Token(TokenType.NOTE, value, (line, current)))
|
||||
return (0, None)
|
||||
8
smnp/token/tokenizers/paren.py
Normal file
8
smnp/token/tokenizers/paren.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from smnp.token.tools import tokenizeChar
|
||||
from smnp.token.type import TokenType
|
||||
|
||||
def tokenizeOpenParen(input, current, line):
|
||||
return tokenizeChar(TokenType.OPEN_PAREN, '(', input, current, line)
|
||||
|
||||
def tokenizeCloseParen(input, current, line):
|
||||
return tokenizeChar(TokenType.CLOSE_PAREN, ')', input, current, line)
|
||||
5
smnp/token/tokenizers/percent.py
Normal file
5
smnp/token/tokenizers/percent.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from smnp.token.tools import tokenizeChar
|
||||
from smnp.token.type import TokenType
|
||||
|
||||
def tokenizePercent(input, current, line):
|
||||
return tokenizeChar(TokenType.PERCENT, '%', input, current, line)
|
||||
5
smnp/token/tokenizers/ret.py
Normal file
5
smnp/token/tokenizers/ret.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from smnp.token.tools import tokenizeKeyword
|
||||
from smnp.token.type import TokenType
|
||||
|
||||
def tokenizeReturn(input, current, line):
|
||||
return tokenizeKeyword(TokenType.RETURN, 'return', input, current, line)
|
||||
16
smnp/token/tokenizers/string.py
Normal file
16
smnp/token/tokenizers/string.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from smnp.token.type import TokenType
|
||||
from smnp.token.model import Token
|
||||
|
||||
def tokenizeString(input, current, line):
|
||||
if input[current] == '"':
|
||||
value = input[current]
|
||||
char = ''
|
||||
consumedChars = 1
|
||||
while char != '"':
|
||||
if char is None: #TODO!!!
|
||||
print("String not terminated")
|
||||
char = input[current + consumedChars]
|
||||
value += char
|
||||
consumedChars += 1
|
||||
return (consumedChars, Token(TokenType.STRING, value, (line, current)))
|
||||
return (0, None)
|
||||
4
smnp/token/tokenizers/whitespace.py
Normal file
4
smnp/token/tokenizers/whitespace.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from smnp.token.tools import tokenizeRegexPattern
|
||||
|
||||
def tokenizeWhitespaces(input, current, line):
|
||||
return tokenizeRegexPattern(None, r'\s', input, current, line)
|
||||
21
smnp/token/tools.py
Normal file
21
smnp/token/tools.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import re
|
||||
from smnp.token.model import Token
|
||||
|
||||
def tokenizeChar(type, char, input, current, line):
|
||||
if input[current] == char:
|
||||
return (1, Token(type, input[current], (line, current)))
|
||||
return (0, None)
|
||||
|
||||
def tokenizeRegexPattern(type, pattern, input, current, line):
|
||||
consumedChars = 0
|
||||
value = ''
|
||||
|
||||
while current+consumedChars < len(input) and re.match(pattern, input[current+consumedChars]):
|
||||
value += input[current+consumedChars]
|
||||
consumedChars += 1
|
||||
return (consumedChars, Token(type, value, (line, current)) if consumedChars > 0 else None)
|
||||
|
||||
def tokenizeKeyword(type, keyword, input, current, line):
|
||||
if len(input) >= current+len(keyword) and input[current:current+len(keyword)] == keyword:
|
||||
return (len(keyword), Token(type, keyword, (line, current)))
|
||||
return (0, None)
|
||||
21
smnp/token/type.py
Normal file
21
smnp/token/type.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from enum import Enum
|
||||
|
||||
class TokenType(Enum):
|
||||
OPEN_PAREN = 1
|
||||
CLOSE_PAREN = 2
|
||||
ASTERISK = 3
|
||||
STRING = 4
|
||||
IDENTIFIER = 5
|
||||
COMMA = 6
|
||||
INTEGER = 7
|
||||
OPEN_BRACKET = 8
|
||||
CLOSE_BRACKET = 9
|
||||
ASSIGN = 10
|
||||
COLON = 11
|
||||
NOTE = 12
|
||||
COMMENT = 13
|
||||
PERCENT = 14
|
||||
MINUS = 15
|
||||
FUNCTION = 16
|
||||
RETURN = 17
|
||||
DOT = 18
|
||||
Reference in New Issue
Block a user