Refactor tokenizer

This commit is contained in:
Bartłomiej Pluta
2019-07-03 01:55:08 +02:00
parent 8313d2dcfd
commit f826516d8f
41 changed files with 589 additions and 296 deletions

223
smnp/AST.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

4
smnp/__main__.py Normal file
View File

@@ -0,0 +1,4 @@
from smnp.main import main
if __name__ == "__main__":
main()

View 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
View File

4
smnp/error/runtime.py Normal file
View 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
View 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
View 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
View File

@@ -0,0 +1 @@
__all__ = ["tokenize"]

54
smnp/token/model.py Normal file
View 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
View 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"]

View File

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View 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
View 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
View 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