Compare commits
25 Commits
optional-f
...
add-polyph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aca6e6bb55 | ||
|
|
a7de7f0279 | ||
|
|
7e55fe4c1a | ||
|
|
8abae7c2ff | ||
|
|
07f08b0557 | ||
|
|
13b069dc7d | ||
|
|
73ea88d8d9 | ||
|
|
a5875425fc | ||
|
|
0dcf5287e1 | ||
|
|
75dcacce67 | ||
|
|
70687ddc02 | ||
|
|
d802c58eee | ||
|
|
c9a3fc070b | ||
|
|
b126f83824 | ||
|
|
6dc503ba86 | ||
|
|
6222dccaac | ||
|
|
0657214aa3 | ||
|
|
3feec0839b | ||
|
|
56ca69246d | ||
|
|
5a2508e804 | ||
|
|
ea28ab6235 | ||
|
|
6e9e252b86 | ||
|
|
17ef5be057 | ||
|
|
44e63ed18d | ||
|
|
83c7b92741 |
@@ -27,6 +27,10 @@ class IntegerLiteral(Atom):
|
||||
pass
|
||||
|
||||
|
||||
class FloatLiteral(Atom):
|
||||
pass
|
||||
|
||||
|
||||
class StringLiteral(Atom):
|
||||
pass
|
||||
|
||||
@@ -47,6 +51,10 @@ def IntegerParser(input):
|
||||
return Parser.terminal(TokenType.INTEGER, createNode=IntegerLiteral.withValue)(input)
|
||||
|
||||
|
||||
def FloatParser(input):
|
||||
return Parser.terminal(TokenType.FLOAT, createNode=FloatLiteral.withValue)(input)
|
||||
|
||||
|
||||
def StringParser(input):
|
||||
return Parser.terminal(TokenType.STRING, createNode=StringLiteral.withValue)(input)
|
||||
|
||||
@@ -66,6 +74,7 @@ def TypeLiteralParser(input):
|
||||
def LiteralParser(input):
|
||||
return Parser.oneOf(
|
||||
IntegerParser,
|
||||
FloatParser,
|
||||
StringParser,
|
||||
NoteParser,
|
||||
BoolParser,
|
||||
@@ -78,8 +87,18 @@ def AtomParser(input):
|
||||
from smnp.ast.node.identifier import IdentifierParser
|
||||
from smnp.ast.node.list import ListParser
|
||||
from smnp.ast.node.map import MapParser
|
||||
from smnp.ast.node.expression import ExpressionParser
|
||||
|
||||
parentheses = Parser.allOf(
|
||||
Parser.terminal(TokenType.OPEN_PAREN),
|
||||
Parser.doAssert(ExpressionParser, "expression"),
|
||||
Parser.terminal(TokenType.CLOSE_PAREN),
|
||||
createNode=lambda open, expr, close: expr,
|
||||
name="grouping parentheses"
|
||||
)
|
||||
|
||||
return Parser.oneOf(
|
||||
parentheses,
|
||||
LiteralParser,
|
||||
IdentifierParser,
|
||||
ListParser,
|
||||
|
||||
@@ -25,7 +25,7 @@ class Or(BinaryOperator):
|
||||
class Loop(BinaryOperator):
|
||||
def __init__(self, pos):
|
||||
super().__init__(pos)
|
||||
self.children.append(NoneNode())
|
||||
self.children.extend([NoneNode(), NoneNode()])
|
||||
|
||||
@property
|
||||
def parameters(self):
|
||||
@@ -35,13 +35,22 @@ class Loop(BinaryOperator):
|
||||
def parameters(self, value):
|
||||
self[3] = value
|
||||
|
||||
@property
|
||||
def filter(self):
|
||||
return self[4]
|
||||
|
||||
@filter.setter
|
||||
def filter(self, value):
|
||||
self[4] = value
|
||||
|
||||
@classmethod
|
||||
def loop(cls, left, parameters, operator, right):
|
||||
def loop(cls, left, parameters, operator, right, filter):
|
||||
node = cls(left.pos)
|
||||
node.left = left
|
||||
node.parameters = parameters
|
||||
node.operator = operator
|
||||
node.right = right
|
||||
node.filter = filter
|
||||
return node
|
||||
|
||||
|
||||
@@ -94,11 +103,19 @@ def LoopParser(input):
|
||||
name="loop parameters"
|
||||
)
|
||||
|
||||
loopFilter = Parser.allOf(
|
||||
Parser.terminal(TokenType.PERCENT),
|
||||
Parser.doAssert(ExpressionWithoutLoopParser, "filter as bool expression"),
|
||||
createNode=lambda percent, expr: expr,
|
||||
name="loop filter"
|
||||
)
|
||||
|
||||
return Parser.allOf(
|
||||
ExpressionWithoutLoopParser,
|
||||
Parser.optional(loopParameters),
|
||||
Parser.terminal(TokenType.DASH, createNode=Operator.withValue),
|
||||
StatementParser,
|
||||
Parser.optional(loopFilter),
|
||||
createNode=Loop.loop,
|
||||
name="dash-loop"
|
||||
)(input)
|
||||
|
||||
@@ -11,26 +11,11 @@ class Power(BinaryOperator):
|
||||
pass
|
||||
|
||||
def FactorParser(input):
|
||||
from smnp.ast.node.expression import ExpressionParser
|
||||
|
||||
parentheses = Parser.allOf(
|
||||
Parser.terminal(TokenType.OPEN_PAREN),
|
||||
Parser.doAssert(ExpressionParser, "expression"),
|
||||
Parser.terminal(TokenType.CLOSE_PAREN),
|
||||
createNode=lambda open, expr, close: expr,
|
||||
name="grouping parentheses"
|
||||
)
|
||||
|
||||
factorOperands = Parser.oneOf(
|
||||
parentheses,
|
||||
UnitParser,
|
||||
name="factor operands"
|
||||
)
|
||||
|
||||
powerFactor = Parser.leftAssociativeOperatorParser(
|
||||
factorOperands,
|
||||
UnitParser,
|
||||
[TokenType.DOUBLE_ASTERISK],
|
||||
factorOperands,
|
||||
UnitParser,
|
||||
lambda left, op, right: Power.withValues(left, op, right),
|
||||
name="power operator"
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from smnp.ast.node.atom import LiteralParser
|
||||
from smnp.ast.node.identifier import IdentifierLiteralParser
|
||||
from smnp.ast.node.iterable import abstractIterableParser
|
||||
from smnp.ast.node.model import Node
|
||||
from smnp.ast.node.operator import BinaryOperator, Operator
|
||||
@@ -31,12 +32,15 @@ class Map(Node):
|
||||
|
||||
def MapParser(input):
|
||||
from smnp.ast.node.expression import ExpressionParser
|
||||
keyParser = LiteralParser
|
||||
keyParser = Parser.oneOf(
|
||||
LiteralParser,
|
||||
IdentifierLiteralParser
|
||||
)
|
||||
valueParser = ExpressionParser
|
||||
|
||||
mapEntryParser = Parser.allOf(
|
||||
keyParser,
|
||||
Parser.terminal(TokenType.ARROW, createNode=Operator.withValue, doAssert=True),
|
||||
Parser.terminal(TokenType.ARROW, createNode=Operator.withValue),
|
||||
Parser.doAssert(valueParser, "expression"),
|
||||
createNode=MapEntry.withValues
|
||||
)
|
||||
|
||||
@@ -81,7 +81,8 @@ class Environment():
|
||||
if function.name == name:
|
||||
signatureCheckresult = function.signature.check(args)
|
||||
if signatureCheckresult[0]:
|
||||
self.scopes.append(function.defaultArgs)
|
||||
self.appendScope(function.defaultArgs)
|
||||
appendedScopeIndex = len(self.scopes)-1
|
||||
self.scopes[-1].update({ argName: argValue for argName, argValue in zip(function.arguments, list(signatureCheckresult[1:])) })
|
||||
self.callStack.append(CallStackItem(name))
|
||||
result = Type.void()
|
||||
@@ -90,7 +91,8 @@ class Environment():
|
||||
except Return as r:
|
||||
result = r.value
|
||||
self.callStack.pop(-1)
|
||||
self.scopes.pop(-1)
|
||||
self.popScope(mergeVariables=False)
|
||||
self.removeScopesAfter(appendedScopeIndex)
|
||||
return (True, result)
|
||||
raise IllegalFunctionInvocationException(f"{function.name}{function.signature.string}", f"{name}{argsTypesToString(args)}")
|
||||
return (False, None)
|
||||
@@ -140,6 +142,20 @@ class Environment():
|
||||
else:
|
||||
return scope
|
||||
|
||||
def appendScope(self, variables=None):
|
||||
if variables is None:
|
||||
variables = {}
|
||||
|
||||
self.scopes.append(variables)
|
||||
|
||||
def popScope(self, mergeVariables=True):
|
||||
lastScope = self.scopes.pop(-1)
|
||||
if mergeVariables:
|
||||
self.scopes[-1].update(lastScope)
|
||||
|
||||
def removeScopesAfter(self, index):
|
||||
del self.scopes[index:]
|
||||
|
||||
def scopesToString(self):
|
||||
return "Scopes:\n" + ("\n".join([ f" [{i}]: {scope}" for i, scope in enumerate(self.scopes) ]))
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from smnp.module import system, mic, note, iterable, sound, synth, string, util
|
||||
from smnp.module import system, mic, note, iterable, sound, synth, string, util, integer, float
|
||||
|
||||
functions = [ *system.functions, *mic.functions, *note.functions, *iterable.functions, *sound.functions, *synth.functions, *string.functions, *util.functions ]
|
||||
methods = [ *system.methods, *mic.methods, *note.methods, *iterable.methods, *sound.methods, *synth.methods, *string.methods, *util.methods ]
|
||||
functions = [ *system.functions, *mic.functions, *note.functions, *iterable.functions, *sound.functions, *synth.functions, *string.functions, *util.functions, *integer.functions, *float.functions ]
|
||||
methods = [ *system.methods, *mic.methods, *note.methods, *iterable.methods, *sound.methods, *synth.methods, *string.methods, *util.methods, *integer.methods, *float.methods ]
|
||||
4
smnp/module/float/__init__.py
Normal file
4
smnp/module/float/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from smnp.module.float.function import float
|
||||
|
||||
functions = [ float.function ]
|
||||
methods = []
|
||||
0
smnp/module/float/function/__init__.py
Normal file
0
smnp/module/float/function/__init__.py
Normal file
26
smnp/module/float/function/float.py
Normal file
26
smnp/module/float/function/float.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from smnp.function.model import CombinedFunction, Function
|
||||
from smnp.function.signature import signature
|
||||
from smnp.type.model import Type
|
||||
from smnp.type.signature.matcher.type import ofType
|
||||
|
||||
_signature1 = signature(ofType(Type.INTEGER))
|
||||
def _function1(env, value):
|
||||
return Type.float(float(value.value))
|
||||
|
||||
|
||||
_signature2 = signature(ofType(Type.STRING))
|
||||
def _function2(env, value):
|
||||
return Type.float(float(value.value))
|
||||
|
||||
|
||||
_signature3 = signature(ofType(Type.FLOAT))
|
||||
def _function3(env, value):
|
||||
return value
|
||||
|
||||
|
||||
function = CombinedFunction(
|
||||
'Float',
|
||||
Function(_signature1, _function1),
|
||||
Function(_signature2, _function2),
|
||||
Function(_signature3, _function3),
|
||||
)
|
||||
4
smnp/module/integer/__init__.py
Normal file
4
smnp/module/integer/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from smnp.module.integer.function import integer
|
||||
|
||||
functions = [ integer.function ]
|
||||
methods = []
|
||||
0
smnp/module/integer/function/__init__.py
Normal file
0
smnp/module/integer/function/__init__.py
Normal file
25
smnp/module/integer/function/integer.py
Normal file
25
smnp/module/integer/function/integer.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from smnp.function.model import CombinedFunction, Function
|
||||
from smnp.function.signature import signature
|
||||
from smnp.type.model import Type
|
||||
from smnp.type.signature.matcher.type import ofType
|
||||
|
||||
_signature1 = signature(ofType(Type.FLOAT))
|
||||
def _function1(env, value):
|
||||
return Type.integer(int(value.value))
|
||||
|
||||
|
||||
_signature2 = signature(ofType(Type.STRING))
|
||||
def _function2(env, value):
|
||||
return Type.integer(int(value.value))
|
||||
|
||||
|
||||
_signature3 = signature(ofType(Type.INTEGER))
|
||||
def _function3(env, value):
|
||||
return value
|
||||
|
||||
function = CombinedFunction(
|
||||
'Integer',
|
||||
Function(_signature1, _function1),
|
||||
Function(_signature2, _function2),
|
||||
Function(_signature3, _function3),
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
from smnp.module.iterable.function import combine, map, get
|
||||
from smnp.module.iterable.function import map, get
|
||||
|
||||
functions = [ combine.function, map.function ]
|
||||
functions = [ map.function ]
|
||||
methods = [ get.function ]
|
||||
@@ -1,18 +0,0 @@
|
||||
from functools import reduce
|
||||
|
||||
from smnp.function.model import Function
|
||||
from smnp.function.signature import varargSignature
|
||||
from smnp.type.model import Type
|
||||
from smnp.type.signature.matcher.type import ofTypes
|
||||
|
||||
_signature = varargSignature(ofTypes(Type.LIST))
|
||||
def _function(env, vararg):
|
||||
if len(vararg) == 1:
|
||||
return vararg[0]
|
||||
|
||||
combined = reduce(lambda x, y: x.value + y.value, vararg)
|
||||
return Type.list(combined)
|
||||
|
||||
|
||||
function = Function(_signature, _function, 'combine')
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
from smnp.function.model import CombinedFunction, Function
|
||||
from smnp.function.signature import signature
|
||||
from smnp.note.model import Note
|
||||
from smnp.type.model import Type
|
||||
from smnp.type.signature.matcher.type import ofType
|
||||
|
||||
_signature1 = signature(ofType(Type.INTEGER))
|
||||
def _function1(env, upper):
|
||||
return Type.list([ Type.integer(i) for i in range(upper.value + 1)])
|
||||
|
||||
|
||||
_signature2 = signature(ofType(Type.INTEGER), ofType(Type.INTEGER))
|
||||
def _function2(env, lower, upper):
|
||||
return Type.list([ Type.integer(i) for i in range(lower.value, upper.value + 1)])
|
||||
|
||||
|
||||
_signature3 = signature(ofType(Type.INTEGER), ofType(Type.INTEGER), ofType(Type.INTEGER))
|
||||
def _function3(env, lower, upper, step):
|
||||
return Type.list([ Type.integer(i) for i in range(lower.value, upper.value + 1, step.value)])
|
||||
|
||||
|
||||
_signature4 = signature(ofType(Type.NOTE), ofType(Type.NOTE))
|
||||
def _function4(env, lower, upper):
|
||||
return Type.list([Type.note(n) for n in Note.range(lower.value, upper.value)])
|
||||
|
||||
|
||||
# TODO
|
||||
# signature5 = range(note lower, note upper, integer step) OR step = "diatonic" | "chromatic" | "augmented" | "diminish"
|
||||
|
||||
function = CombinedFunction(
|
||||
'range',
|
||||
Function(_signature1, _function1),
|
||||
Function(_signature2, _function2),
|
||||
Function(_signature3, _function3),
|
||||
Function(_signature4, _function4),
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
from smnp.module.string.function import concat, stringify
|
||||
from smnp.module.string.function import stringify
|
||||
|
||||
functions = [ concat.function ]
|
||||
functions = []
|
||||
methods = [ stringify.function ]
|
||||
@@ -1,11 +0,0 @@
|
||||
from smnp.function.model import Function
|
||||
from smnp.function.signature import varargSignature
|
||||
from smnp.type.model import Type
|
||||
from smnp.type.signature.matcher.type import ofType
|
||||
|
||||
_signature = varargSignature(ofType(Type.STRING))
|
||||
def _function(env, vararg):
|
||||
return Type.string("".join([ arg.value for arg in vararg ]))
|
||||
|
||||
|
||||
function = Function(_signature, _function, 'concat')
|
||||
@@ -1,4 +1,4 @@
|
||||
from smnp.module.synth.function import synth, pause
|
||||
from smnp.module.synth.function import synth, pause, plot, compile
|
||||
|
||||
functions = [ synth.function, pause.function ]
|
||||
functions = [ synth.function, pause.function, plot.function, compile.function ]
|
||||
methods = []
|
||||
141
smnp/module/synth/function/compile.py
Normal file
141
smnp/module/synth/function/compile.py
Normal file
@@ -0,0 +1,141 @@
|
||||
from smnp.error.runtime import RuntimeException
|
||||
from smnp.function.model import Function, CombinedFunction
|
||||
from smnp.function.signature import varargSignature
|
||||
from smnp.module.synth.lib.wave import compilePolyphony
|
||||
|
||||
from smnp.type.model import Type
|
||||
from smnp.type.signature.matcher.list import listOf
|
||||
from smnp.type.signature.matcher.type import ofTypes, ofType
|
||||
|
||||
|
||||
DEFAULT_BPM = 120
|
||||
DEFAULT_OVERTONES = [0.4, 0.3, 0.1, 0.1, 0.1]
|
||||
DEFAULT_DECAY = 4
|
||||
DEFAULT_ATTACK = 100
|
||||
|
||||
|
||||
def getBpm(config):
|
||||
key = Type.string("bpm")
|
||||
if key in config.value:
|
||||
bpm = config.value[key]
|
||||
if bpm.type != Type.INTEGER or bpm.value <= 0:
|
||||
raise RuntimeException("The 'bpm' property must be positive integer", None)
|
||||
|
||||
return bpm.value
|
||||
|
||||
return DEFAULT_BPM
|
||||
|
||||
|
||||
def getOvertones(config):
|
||||
key = Type.string("overtones")
|
||||
if key in config.value:
|
||||
overtones = config.value[key]
|
||||
rawOvertones = [overtone.value for overtone in overtones.value]
|
||||
if overtones.type != Type.LIST or not all(overtone.type in [Type.FLOAT, Type.INTEGER] for overtone in overtones.value):
|
||||
raise RuntimeException("The 'overtones' property must be list of floats", None)
|
||||
|
||||
if len(rawOvertones) < 1:
|
||||
raise RuntimeException("The 'overtones' property must contain one overtone at least", None)
|
||||
|
||||
if any(overtone < 0 for overtone in rawOvertones):
|
||||
raise RuntimeException("The 'overtones' property mustn't contain negative values", None)
|
||||
|
||||
if sum(rawOvertones) > 1.0:
|
||||
raise RuntimeException("The 'overtones' property must contain overtones which sum is not greater than 1.0", None)
|
||||
|
||||
return rawOvertones
|
||||
|
||||
return DEFAULT_OVERTONES
|
||||
|
||||
|
||||
def getDecay(config):
|
||||
key = Type.string("decay")
|
||||
if key in config.value:
|
||||
decay = config.value[key]
|
||||
if not decay.type in [Type.INTEGER, Type.FLOAT] or decay.value < 0:
|
||||
raise RuntimeException("The 'decay' property must be non-negative integer or float", None)
|
||||
|
||||
return decay.value
|
||||
|
||||
return DEFAULT_DECAY
|
||||
|
||||
|
||||
def getAttack(config):
|
||||
key = Type.string("attack")
|
||||
if key in config.value:
|
||||
attack = config.value[key]
|
||||
if not attack.type in [Type.INTEGER, Type.FLOAT] or attack.value < 0:
|
||||
raise RuntimeException("The 'attack' property must be non-negative integer or float", None)
|
||||
|
||||
return attack.value
|
||||
|
||||
return DEFAULT_ATTACK
|
||||
|
||||
|
||||
class Config:
|
||||
def __init__(self, bpm, overtones, decay, attack):
|
||||
self.bpm = bpm
|
||||
self.overtones = overtones
|
||||
self.decay = decay
|
||||
self.attack = attack
|
||||
|
||||
@staticmethod
|
||||
def default():
|
||||
return Config(DEFAULT_BPM, DEFAULT_OVERTONES, DEFAULT_DECAY, DEFAULT_ATTACK)
|
||||
|
||||
|
||||
_signature1 = varargSignature(listOf(Type.NOTE, Type.INTEGER))
|
||||
def _function1(env, notes):
|
||||
return Type.list([Type.float(float(m)) for m in __function1(notes)])
|
||||
|
||||
|
||||
def __function1(notes):
|
||||
return compilePolyphony([note.value for note in notes], Config.default())
|
||||
|
||||
|
||||
_signature2 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER))
|
||||
def _function2(env, notes):
|
||||
return Type.list([Type.float(float(m)) for m in __function2(notes)])
|
||||
|
||||
|
||||
def __function2(notes):
|
||||
return compilePolyphony([ notes ], Config.default())
|
||||
|
||||
|
||||
_signature3 = varargSignature(listOf(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
|
||||
def _function3(env, config, notes):
|
||||
return Type.list([ Type.float(float(m)) for m in __function3(config, notes) ])
|
||||
|
||||
|
||||
def __function3(config, notes):
|
||||
rawNotes = [note.value for note in notes]
|
||||
|
||||
bpm = getBpm(config)
|
||||
overtones = getOvertones(config)
|
||||
decay = getDecay(config)
|
||||
attack = getAttack(config)
|
||||
|
||||
return compilePolyphony(rawNotes, Config(bpm, overtones, decay, attack))
|
||||
|
||||
|
||||
_signature4 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
|
||||
def _function4(env, config, notes):
|
||||
return Type.list([ Type.float(float(m)) for m in __function4(config, notes) ])
|
||||
|
||||
|
||||
def __function4(config, notes):
|
||||
bpm = getBpm(config)
|
||||
overtones = getOvertones(config)
|
||||
decay = getDecay(config)
|
||||
attack = getAttack(config)
|
||||
|
||||
return compilePolyphony([ notes ], Config(bpm, overtones, decay, attack))
|
||||
|
||||
|
||||
function = CombinedFunction(
|
||||
'wave',
|
||||
Function(_signature1, _function1),
|
||||
Function(_signature2, _function2),
|
||||
Function(_signature3, _function3),
|
||||
Function(_signature4, _function4),
|
||||
)
|
||||
@@ -1,13 +1,13 @@
|
||||
from smnp.function.model import Function
|
||||
from smnp.function.signature import signature
|
||||
from smnp.module.synth.lib import player
|
||||
from smnp.module.synth.lib.wave import pause
|
||||
from smnp.type.model import Type
|
||||
from smnp.type.signature.matcher.type import ofTypes
|
||||
|
||||
_signature = signature(ofTypes(Type.INTEGER))
|
||||
def _function(env, value):
|
||||
bpm = env.findVariable('bpm')
|
||||
player.pause(value.value, bpm.value)
|
||||
pause(value.value, bpm.value)
|
||||
|
||||
|
||||
function = Function(_signature, _function, 'pause')
|
||||
13
smnp/module/synth/function/plot.py
Normal file
13
smnp/module/synth/function/plot.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from smnp.function.model import Function
|
||||
from smnp.function.signature import signature
|
||||
from smnp.module.synth.lib.wave import plot
|
||||
from smnp.type.model import Type
|
||||
from smnp.type.signature.matcher.list import listOf
|
||||
|
||||
_signature = signature(listOf(Type.FLOAT))
|
||||
def _function(env, wave):
|
||||
rawWave = [ m.value for m in wave.value ]
|
||||
plot(rawWave)
|
||||
|
||||
|
||||
function = Function(_signature, _function, 'plotWave')
|
||||
@@ -1,13 +1,47 @@
|
||||
from smnp.function.model import Function
|
||||
from smnp.function.signature import signature
|
||||
from smnp.module.synth.lib.player import play
|
||||
from smnp.function.model import Function, CombinedFunction
|
||||
from smnp.function.signature import varargSignature
|
||||
from smnp.module.synth.function import compile
|
||||
from smnp.module.synth.lib.wave import play
|
||||
from smnp.type.model import Type
|
||||
from smnp.type.signature.matcher.type import ofType
|
||||
from smnp.type.signature.matcher.list import listOf
|
||||
from smnp.type.signature.matcher.type import ofTypes, ofType
|
||||
|
||||
_signature = signature(ofType(Type.NOTE))
|
||||
def _function(env, note):
|
||||
bpm = env.findVariable('bpm')
|
||||
play(note.value, bpm.value)
|
||||
_signature1 = varargSignature(listOf(Type.NOTE, Type.INTEGER))
|
||||
def _function1(env, notes):
|
||||
wave = compile.__function1(notes)
|
||||
play(wave)
|
||||
|
||||
|
||||
function = Function(_signature, _function, 'synthNote')
|
||||
_signature2 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER))
|
||||
def _function2(env, notes):
|
||||
wave = compile.__function2(notes)
|
||||
play(wave)
|
||||
|
||||
|
||||
_signature3 = varargSignature(listOf(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
|
||||
def _function3(env, config, notes):
|
||||
wave = compile.__function3(config, notes)
|
||||
play(wave)
|
||||
|
||||
|
||||
_signature4 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
|
||||
def _function4(env, config, notes):
|
||||
wave = compile.__function4(config, notes)
|
||||
play(wave)
|
||||
|
||||
|
||||
_signature5 = varargSignature(listOf(Type.FLOAT))
|
||||
def _function5(env, waves):
|
||||
for wave in waves:
|
||||
rawWave = [m.value for m in wave.value]
|
||||
play(rawWave)
|
||||
|
||||
|
||||
function = CombinedFunction(
|
||||
'synth',
|
||||
Function(_signature1, _function1),
|
||||
Function(_signature2, _function2),
|
||||
Function(_signature3, _function3),
|
||||
Function(_signature4, _function4),
|
||||
Function(_signature5, _function5)
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
import time
|
||||
|
||||
from smnp.module.synth.lib.wave import sine
|
||||
from smnp.note.model import Note
|
||||
|
||||
|
||||
def playNotes(notes, bpm):
|
||||
for note in notes:
|
||||
{
|
||||
Note: play,
|
||||
int: pause
|
||||
}[type(note)](note, bpm)
|
||||
|
||||
|
||||
def play(note, bpm):
|
||||
frequency = note.toFrequency()
|
||||
duration = 60 * 4 / note.duration / bpm
|
||||
duration *= 1.5 if note.dot else 1
|
||||
sine(frequency, duration)
|
||||
|
||||
|
||||
def pause(value, bpm):
|
||||
time.sleep(60 * 4 / value / bpm)
|
||||
|
||||
@@ -1,12 +1,78 @@
|
||||
import time
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import sounddevice as sd
|
||||
|
||||
from smnp.type.model import Type
|
||||
|
||||
FS = 44100
|
||||
|
||||
|
||||
def pause(value, bpm):
|
||||
time.sleep(60 * 4 / value / bpm)
|
||||
|
||||
|
||||
def plot(wave):
|
||||
X = np.arange(len(wave))
|
||||
plt.plot(X, wave)
|
||||
plt.show()
|
||||
|
||||
|
||||
def play(wave):
|
||||
sd.play(wave)
|
||||
time.sleep(len(wave) / FS)
|
||||
|
||||
|
||||
def compilePolyphony(notes, config):
|
||||
compiledLines = [1 / len(notes) * compileNotes(line, config) for line in notes]
|
||||
return sum(adjustSize(compiledLines))
|
||||
|
||||
|
||||
def adjustSize(compiledLines):
|
||||
maxSize = max(len(line) for line in compiledLines)
|
||||
|
||||
return [np.concatenate([line, np.zeros(maxSize - len(line))]) for line in compiledLines]
|
||||
|
||||
|
||||
def compileNotes(notes, config):
|
||||
dispatcher = {
|
||||
Type.NOTE: lambda note, overtones: sineForNote(note.value, config),
|
||||
Type.INTEGER: lambda note, overtones: silenceForPause(note.value, config)
|
||||
}
|
||||
|
||||
return np.concatenate([dispatcher[note.type](note, config) for note in notes])
|
||||
|
||||
|
||||
def sineForNote(note, config):
|
||||
frequency = note.toFrequency()
|
||||
duration = 60 * 4 / note.duration / config.bpm
|
||||
duration *= 1.5 if note.dot else 1
|
||||
return sound(frequency, duration, config)
|
||||
|
||||
|
||||
def sound(frequency, duration, config):
|
||||
return attack(decay(sum(overtone * sine((i+1) * frequency, duration) for i, overtone in enumerate(config.overtones) if overtone > 0), config), config)
|
||||
|
||||
|
||||
def decay(wave, config):
|
||||
magnitude = np.exp(-config.decay/len(wave) * np.arange(len(wave)))
|
||||
|
||||
return magnitude * wave
|
||||
|
||||
|
||||
def attack(wave, config):
|
||||
magnitude = -np.exp(-config.attack / len(wave) * np.arange(len(wave)))+1 \
|
||||
if config.attack > 0 \
|
||||
else np.ones(len(wave))
|
||||
|
||||
return magnitude * wave
|
||||
|
||||
|
||||
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)
|
||||
return (np.sin(2 * np.pi * np.arange(FS * duration) * frequency / FS)).astype(np.float32)
|
||||
|
||||
|
||||
def silenceForPause(value, config):
|
||||
duration = 60 * 4 / value / config.bpm
|
||||
return np.zeros(int(FS * duration))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from smnp.module.system.function import sleep, display, displayln, debug, exit, type
|
||||
from smnp.module.system.function import sleep, display, displayln, debug, exit, type, read
|
||||
|
||||
functions = [ debug.function, display.function, displayln.function, exit.function, sleep.function, type.function ]
|
||||
functions = [ debug.function, display.function, displayln.function, exit.function, sleep.function, type.function, read.function ]
|
||||
methods = []
|
||||
@@ -1,3 +1,72 @@
|
||||
from smnp.error.runtime import RuntimeException
|
||||
from smnp.function.model import CombinedFunction, Function
|
||||
from smnp.function.signature import signature
|
||||
from smnp.token.tokenizers.bool import boolTokenizer
|
||||
from smnp.token.tokenizers.note import noteTokenizer
|
||||
from smnp.type.model import Type
|
||||
from smnp.type.signature.matcher.type import ofType
|
||||
|
||||
_signature1 = signature()
|
||||
def _function1(env):
|
||||
value = input()
|
||||
return Type.string(value)
|
||||
|
||||
|
||||
_signature2 = signature(ofType(Type.STRING))
|
||||
def _function2(env, prompt):
|
||||
print(prompt.value, end="")
|
||||
value = input()
|
||||
return Type.string(value)
|
||||
|
||||
|
||||
_signature3 = signature(ofType(Type.TYPE))
|
||||
def _function3(env, type):
|
||||
value = input()
|
||||
return getValueAccordingToType(value, type)
|
||||
|
||||
|
||||
def getValueAccordingToType(value, type):
|
||||
try:
|
||||
if type.value == Type.STRING:
|
||||
return Type.string(value)
|
||||
|
||||
if type.value == Type.INTEGER:
|
||||
return Type.integer(int(value))
|
||||
|
||||
if type.value == Type.BOOL:
|
||||
consumedChars, token = boolTokenizer(value, 0, 0)
|
||||
if consumedChars > 0:
|
||||
return Type.bool(token.value)
|
||||
|
||||
return ValueError()
|
||||
|
||||
if type.value == Type.NOTE:
|
||||
consumedChars, token = noteTokenizer(value, 0, 0)
|
||||
if consumedChars > 0:
|
||||
return Type.note(token.value)
|
||||
|
||||
raise ValueError()
|
||||
|
||||
raise RuntimeException(f"Type {type.value.name.lower()} is not suuported", None)
|
||||
|
||||
except ValueError:
|
||||
raise RuntimeException(f"Invalid value '{value}' for type {type.value.name.lower()}", None)
|
||||
|
||||
|
||||
_signature4 = signature(ofType(Type.STRING), ofType(Type.TYPE))
|
||||
def _function4(env, prompt, type):
|
||||
print(prompt.value, end="")
|
||||
value = input()
|
||||
return getValueAccordingToType(value, type)
|
||||
|
||||
|
||||
function = CombinedFunction(
|
||||
'read',
|
||||
Function(_signature1, _function1),
|
||||
Function(_signature2, _function2),
|
||||
Function(_signature3, _function3),
|
||||
Function(_signature4, _function4)
|
||||
)
|
||||
|
||||
# TODO read function
|
||||
# def read(args, env):
|
||||
|
||||
@@ -8,11 +8,7 @@ class AssignmentEvaluator(Evaluator):
|
||||
def evaluator(cls, node, environment):
|
||||
target = node.left.value
|
||||
value = expressionEvaluator(doAssert=True)(node.right, environment).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult
|
||||
scopeOfExistingVariable = environment.findVariableScope(target)
|
||||
if scopeOfExistingVariable is None:
|
||||
environment.scopes[-1][target] = value
|
||||
else:
|
||||
scopeOfExistingVariable[target] = value
|
||||
environment.scopes[-1][target] = value
|
||||
|
||||
return value
|
||||
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
from smnp.ast.node.identifier import Identifier
|
||||
from smnp.runtime.evaluator import evaluate, Evaluator, EvaluationResult
|
||||
from smnp.runtime.evaluators.expression import expressionEvaluator
|
||||
from smnp.type.model import Type
|
||||
|
||||
|
||||
class AsteriskEvaluator(Evaluator):
|
||||
|
||||
@classmethod
|
||||
def evaluator(cls, node, environment):
|
||||
iterator = expressionEvaluator(doAssert=True)(node.iterator, environment).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult
|
||||
return Evaluator.oneOf(
|
||||
cls._numberIteratorAsteriskEvaluator(iterator),
|
||||
cls._listIteratorAsteriskEvaluator(iterator),
|
||||
cls._mapIteratorAsteriskEvaluator(iterator)
|
||||
)(node, environment).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult
|
||||
|
||||
@classmethod
|
||||
def _numberIteratorAsteriskEvaluator(cls, evaluatedIterator):
|
||||
def evaluator(node, environment):
|
||||
if evaluatedIterator.type == Type.INTEGER:
|
||||
results = []
|
||||
automaticVariable = cls._automaticNamedVariable(node.iterator, environment, "_")
|
||||
for i in range(evaluatedIterator.value):
|
||||
environment.scopes[-1][automaticVariable] = Type.integer(i + 1)
|
||||
result = evaluate(node.statement, environment).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult
|
||||
if result is None or result.type == Type.VOID:
|
||||
results = None
|
||||
|
||||
if results is not None:
|
||||
results.append(result)
|
||||
|
||||
del environment.scopes[-1][automaticVariable]
|
||||
|
||||
return EvaluationResult.OK(Type.list(results).decompose() if results is not None else Type.void())
|
||||
|
||||
return EvaluationResult.FAIL()
|
||||
|
||||
return evaluator
|
||||
|
||||
@classmethod
|
||||
def _automaticNamedVariable(cls, iteratorNode, environment, prefix=''):
|
||||
if type(iteratorNode) == Identifier:
|
||||
return cls._automaticVariableName(environment, prefix, iteratorNode.value, False)
|
||||
else:
|
||||
return cls._automaticVariableName(environment, prefix, '', True)
|
||||
|
||||
@classmethod
|
||||
def _automaticVariableName(cls, environment, prefix='', suffix='', startWithNumber=False):
|
||||
number = 1 if startWithNumber else ''
|
||||
variableName = lambda x: f"{prefix}{x}{suffix}"
|
||||
while environment.findVariableScope(variableName(number)) is not None:
|
||||
if number == '':
|
||||
number = 1
|
||||
else:
|
||||
number += 1
|
||||
|
||||
return variableName(number)
|
||||
|
||||
@classmethod
|
||||
def _listIteratorAsteriskEvaluator(cls, evaluatedIterator):
|
||||
def evaluator(node, environment):
|
||||
if evaluatedIterator.type == Type.LIST:
|
||||
results = []
|
||||
automaticVariableKey = cls._automaticNamedVariable(node.iterator, environment, "_")
|
||||
automaticVariableValue = cls._automaticNamedVariable(node.iterator, environment, "__")
|
||||
for i, v in enumerate(evaluatedIterator.value):
|
||||
environment.scopes[-1][automaticVariableKey] = Type.integer(i + 1)
|
||||
environment.scopes[-1][automaticVariableValue] = v
|
||||
result = evaluate(node.statement, environment).value # TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult
|
||||
if result is not None and result.type != Type.VOID:
|
||||
results.append(result)
|
||||
|
||||
del environment.scopes[-1][automaticVariableKey]
|
||||
del environment.scopes[-1][automaticVariableValue]
|
||||
|
||||
return EvaluationResult.OK(Type.list(results).decompose())
|
||||
|
||||
return EvaluationResult.FAIL()
|
||||
|
||||
return evaluator
|
||||
|
||||
@classmethod
|
||||
def _mapIteratorAsteriskEvaluator(cls, evaluatedIterator):
|
||||
def evaluator(node, environment):
|
||||
if evaluatedIterator.type == Type.MAP:
|
||||
results = []
|
||||
automaticVariableKey = cls._automaticNamedVariable(node.iterator, environment, "_")
|
||||
automaticVariableValue = cls._automaticNamedVariable(node.iterator, environment, "__")
|
||||
for k, v in evaluatedIterator.value.items():
|
||||
environment.scopes[-1][automaticVariableKey] = k
|
||||
environment.scopes[-1][automaticVariableValue] = v
|
||||
result = evaluate(node.statement, environment).value # TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult
|
||||
if result is not None and result.type != Type.VOID:
|
||||
results.append(result)
|
||||
|
||||
del environment.scopes[-1][automaticVariableKey]
|
||||
del environment.scopes[-1][automaticVariableValue]
|
||||
|
||||
return EvaluationResult.OK(Type.list(results).decompose())
|
||||
|
||||
return EvaluationResult.FAIL()
|
||||
|
||||
return evaluator
|
||||
@@ -1,10 +1,11 @@
|
||||
from smnp.ast.node.atom import StringLiteral, IntegerLiteral, NoteLiteral, BoolLiteral, TypeLiteral
|
||||
from smnp.ast.node.atom import StringLiteral, IntegerLiteral, NoteLiteral, BoolLiteral, TypeLiteral, FloatLiteral
|
||||
from smnp.ast.node.identifier import Identifier
|
||||
from smnp.ast.node.list import List
|
||||
from smnp.ast.node.map import Map
|
||||
from smnp.error.runtime import RuntimeException
|
||||
from smnp.runtime.evaluator import Evaluator
|
||||
from smnp.runtime.evaluator import Evaluator, EvaluationResult
|
||||
from smnp.runtime.evaluators.expression import expressionEvaluator
|
||||
from smnp.runtime.evaluators.float import FloatEvaluator
|
||||
from smnp.runtime.evaluators.iterable import abstractIterableEvaluator
|
||||
from smnp.runtime.tools.error import updatePos
|
||||
from smnp.type.model import Type
|
||||
@@ -58,12 +59,15 @@ class MapEvaluator(Evaluator):
|
||||
@classmethod
|
||||
def evaluator(cls, node, environment):
|
||||
map = {}
|
||||
exprEvaluator = expressionEvaluator(doAssert=True)
|
||||
keyEvaluator = Evaluator.oneOf(
|
||||
Evaluator.forNodes(lambda node, environment: EvaluationResult.OK(Type.string(node.value)), Identifier),
|
||||
expressionEvaluator(doAssert=True)
|
||||
)
|
||||
for entry in node.children:
|
||||
key = exprEvaluator(entry.key, environment).value
|
||||
key = keyEvaluator(entry.key, environment).value
|
||||
if key in map:
|
||||
raise RuntimeException(f"Duplicated key '{key.stringify()}' found in map", entry.pos)
|
||||
map[key] = exprEvaluator(entry.value, environment).value
|
||||
map[key] = expressionEvaluator(doAssert=True)(entry.value, environment).value
|
||||
|
||||
return Type.map(map)
|
||||
|
||||
@@ -85,6 +89,7 @@ class AtomEvaluator(Evaluator):
|
||||
return Evaluator.oneOf(
|
||||
Evaluator.forNodes(StringEvaluator.evaluate, StringLiteral),
|
||||
Evaluator.forNodes(IntegerEvaluator.evaluate, IntegerLiteral),
|
||||
Evaluator.forNodes(FloatEvaluator.evaluate, FloatLiteral),
|
||||
Evaluator.forNodes(NoteEvaluator.evaluate, NoteLiteral),
|
||||
Evaluator.forNodes(BoolEvaluator.evaluate, BoolLiteral),
|
||||
Evaluator.forNodes(TypeEvaluator.evaluate, TypeLiteral),
|
||||
|
||||
@@ -5,12 +5,12 @@ class BlockEvaluator(Evaluator):
|
||||
|
||||
@classmethod
|
||||
def evaluator(cls, node, environment):
|
||||
environment.scopes.append({})
|
||||
environment.appendScope()
|
||||
|
||||
for child in node.children:
|
||||
evaluate(child, environment)
|
||||
|
||||
environment.scopes.pop(-1)
|
||||
environment.popScope()
|
||||
|
||||
#
|
||||
# def evaluateBlock(block, environment):
|
||||
|
||||
9
smnp/runtime/evaluators/float.py
Normal file
9
smnp/runtime/evaluators/float.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from smnp.runtime.evaluator import Evaluator
|
||||
from smnp.type.model import Type
|
||||
|
||||
|
||||
class FloatEvaluator(Evaluator):
|
||||
|
||||
@classmethod
|
||||
def evaluator(cls, node, environment):
|
||||
return Type.float(node.value)
|
||||
@@ -13,23 +13,23 @@ class LoopEvaluator(Evaluator):
|
||||
parameters = [ identifier.value for identifier in node.parameters ] if type(node.parameters) != NoneNode() else []
|
||||
|
||||
try:
|
||||
environment.scopes.append({})
|
||||
environment.appendScope()
|
||||
|
||||
output = {
|
||||
Type.INTEGER: cls.numberEvaluator,
|
||||
Type.BOOL: cls.boolEvaluator,
|
||||
Type.LIST: cls.listEvaluator,
|
||||
Type.MAP: cls.mapEvaluator
|
||||
}[iterator.type](node, environment, iterator, parameters)
|
||||
}[iterator.type](node, environment, iterator, parameters, node.filter)
|
||||
|
||||
environment.scopes.pop(-1)
|
||||
environment.popScope()
|
||||
except KeyError:
|
||||
raise RuntimeException(f"The {iterator.type.name.lower()} type cannot stand as an iterator for loop statement", node.left.pos)
|
||||
|
||||
return Type.list(output)
|
||||
|
||||
@classmethod
|
||||
def numberEvaluator(cls, node, environment, evaluatedIterator, parameters):
|
||||
def numberEvaluator(cls, node, environment, evaluatedIterator, parameters, filter):
|
||||
output = []
|
||||
|
||||
|
||||
@@ -40,14 +40,28 @@ class LoopEvaluator(Evaluator):
|
||||
if len(parameters) > 0:
|
||||
environment.scopes[-1][parameters[0]] = Type.integer(i)
|
||||
|
||||
output.append(evaluate(node.right, environment).value)
|
||||
if cls.doFilter(filter, environment):
|
||||
output.append(evaluate(node.right, environment).value)
|
||||
|
||||
|
||||
|
||||
return output
|
||||
|
||||
@classmethod
|
||||
def boolEvaluator(cls, node, environment, evaluatedIterator, parameters):
|
||||
def doFilter(cls, filter, environment):
|
||||
if type(filter) is not NoneNode:
|
||||
evaluation = expressionEvaluator(doAssert=True)(filter, environment).value
|
||||
if evaluation.type != Type.BOOL:
|
||||
raise RuntimeException(f"Expected {Type.BOOL.name.lower()} as filter expression, found {evaluation.type.name.lower()}", filter.pos)
|
||||
|
||||
return evaluation.value
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def boolEvaluator(cls, node, environment, evaluatedIterator, parameters, filter):
|
||||
output = []
|
||||
|
||||
if len(parameters) > 0:
|
||||
@@ -55,13 +69,14 @@ class LoopEvaluator(Evaluator):
|
||||
|
||||
condition = evaluatedIterator
|
||||
while condition.value:
|
||||
output.append(evaluate(node.right, environment).value)
|
||||
if cls.doFilter(filter, environment):
|
||||
output.append(evaluate(node.right, environment).value)
|
||||
condition = expressionEvaluator(doAssert=True)(node.left, environment).value
|
||||
|
||||
return output
|
||||
|
||||
@classmethod
|
||||
def listEvaluator(cls, node, environment, evaluatedIterator, parameters):
|
||||
def listEvaluator(cls, node, environment, evaluatedIterator, parameters, filter):
|
||||
output = []
|
||||
|
||||
if len(parameters) > 2:
|
||||
@@ -74,13 +89,14 @@ class LoopEvaluator(Evaluator):
|
||||
environment.scopes[-1][parameters[0]] = Type.integer(i)
|
||||
environment.scopes[-1][parameters[1]] = value
|
||||
|
||||
output.append(evaluate(node.right, environment).value)
|
||||
if cls.doFilter(filter, environment):
|
||||
output.append(evaluate(node.right, environment).value)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
@classmethod
|
||||
def mapEvaluator(cls, node, environment, evaluatedIterator, parameters):
|
||||
def mapEvaluator(cls, node, environment, evaluatedIterator, parameters, filter):
|
||||
output = []
|
||||
|
||||
if len(parameters) > 3:
|
||||
@@ -99,6 +115,7 @@ class LoopEvaluator(Evaluator):
|
||||
environment.scopes[-1][parameters[2]] = value
|
||||
i += 1
|
||||
|
||||
output.append(evaluate(node.right, environment).value)
|
||||
if cls.doFilter(filter, environment):
|
||||
output.append(evaluate(node.right, environment).value)
|
||||
|
||||
return output
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
from smnp.error.runtime import RuntimeException
|
||||
from smnp.runtime.evaluator import Evaluator
|
||||
from smnp.runtime.evaluators.expression import expressionEvaluator
|
||||
from smnp.type.model import Type
|
||||
|
||||
|
||||
class MapEvaluator(Evaluator):
|
||||
|
||||
@classmethod
|
||||
def evaluator(cls, node, environment):
|
||||
map = {}
|
||||
exprEvaluator = expressionEvaluator(doAssert=True)
|
||||
for entry in node.children:
|
||||
key = exprEvaluator(entry.key, environment).value
|
||||
if key in map:
|
||||
raise RuntimeException(f"Duplicated key '{key.stringify()}' found in map", entry.pos)
|
||||
map[key] = exprEvaluator(entry.value, environment).value
|
||||
|
||||
return Type.map(map)
|
||||
@@ -11,6 +11,7 @@ class MinusEvaluator(Evaluator):
|
||||
try:
|
||||
return {
|
||||
Type.INTEGER: cls.evaluateForInteger,
|
||||
Type.FLOAT: cls.evaluateForFloat,
|
||||
Type.STRING: cls.evaluateForString,
|
||||
Type.LIST: cls.evaluateForList
|
||||
}[value.type](value.value)
|
||||
@@ -19,9 +20,12 @@ class MinusEvaluator(Evaluator):
|
||||
|
||||
@classmethod
|
||||
def evaluateForInteger(cls, value):
|
||||
|
||||
return Type.integer(-value)
|
||||
|
||||
@classmethod
|
||||
def evaluateForFloat(cls, value):
|
||||
return Type.float(-value)
|
||||
|
||||
@classmethod
|
||||
def evaluateForString(cls, value):
|
||||
return Type.string(value[::-1])
|
||||
|
||||
@@ -10,11 +10,12 @@ class PowerEvaluator(Evaluator):
|
||||
def evaluator(cls, node, environment):
|
||||
left = expressionEvaluator(doAssert=True)(node.left, environment).value
|
||||
right = expressionEvaluator(doAssert=True)(node.right, environment).value
|
||||
supportedTypes = [Type.INTEGER, Type.FLOAT]
|
||||
|
||||
if left.type != Type.INTEGER:
|
||||
raise RuntimeException( f"Operator '{node.operator.value}' is supported only by {Type.INTEGER.name.lower()} type", node.left.pos)
|
||||
if not left.type in supportedTypes:
|
||||
raise RuntimeException(f"Operator '{node.operator.value}' is supported only by {Type.INTEGER.name.lower()} type", node.left.pos)
|
||||
|
||||
if right.type != Type.INTEGER:
|
||||
raise RuntimeException( f"Operator '{node.operator.value}' is supported only by {Type.INTEGER.name.lower()} type", node.right.pos)
|
||||
if not right.type in supportedTypes:
|
||||
raise RuntimeException(f"Operator '{node.operator.value}' is supported only by {[t.name.lower() for t in supportedTypes]} type", node.right.pos)
|
||||
|
||||
return Type.integer(int(left.value ** right.value))
|
||||
@@ -10,22 +10,36 @@ class ProductEvaluator(Evaluator):
|
||||
def evaluator(cls, node, environment):
|
||||
left = expressionEvaluator(doAssert=True)(node.left, environment).value
|
||||
right = expressionEvaluator(doAssert=True)(node.right, environment).value
|
||||
supportedTypes = [Type.INTEGER, Type.FLOAT]
|
||||
|
||||
if left.type != Type.INTEGER:
|
||||
if not left.type in supportedTypes:
|
||||
raise RuntimeException(
|
||||
f"Operator '{node.operator.value}' is supported only by {Type.INTEGER.name.lower()} type", node.left.pos)
|
||||
f"Operator '{node.operator.value}' is supported only by {[t.name.lower() for t in supportedTypes]} type", node.left.pos)
|
||||
|
||||
if right.type != Type.INTEGER:
|
||||
if not right.type in supportedTypes:
|
||||
raise RuntimeException(
|
||||
f"Operator '{node.operator.value}' is supported only by {Type.INTEGER.name.lower()} type", node.right.pos)
|
||||
f"Operator '{node.operator.value}' is supported only by {[t.name.lower() for t in supportedTypes]} type", node.right.pos)
|
||||
|
||||
if node.operator.value == "*":
|
||||
return Type.integer(int(left.value * right.value))
|
||||
return getProperTypeProvider(left.value * right.value)
|
||||
|
||||
if node.operator.value == "/":
|
||||
if right.value == 0:
|
||||
raise RuntimeException("Attempt to divide by 0", node.right.pos)
|
||||
return Type.integer(int(left.value / right.value))
|
||||
|
||||
value = left.value / right.value
|
||||
|
||||
if left.type == right.type == Type.INTEGER and int(value) == value:
|
||||
return Type.integer(int(value))
|
||||
|
||||
return getProperTypeProvider(value)
|
||||
|
||||
raise RuntimeError("This line should never be reached")
|
||||
|
||||
|
||||
def getProperTypeProvider(value):
|
||||
return {
|
||||
int: lambda v: Type.integer(v),
|
||||
float: lambda v: Type.float(v)
|
||||
}[type(value)](value)
|
||||
|
||||
|
||||
@@ -21,15 +21,21 @@ class RelationEvaluator(Evaluator):
|
||||
|
||||
@classmethod
|
||||
def equalOperatorEvaluator(cls, left, operator, right):
|
||||
if left.type in [Type.INTEGER, Type.FLOAT] and right.type in [Type.INTEGER, Type.FLOAT]:
|
||||
return Type.bool(left.value == right.value)
|
||||
|
||||
return Type.bool(left.type == right.type and left.value == right.value)
|
||||
|
||||
@classmethod
|
||||
def notEqualOperatorEvaluator(cls, left, operator, right):
|
||||
if left.type in [Type.INTEGER, Type.FLOAT] and right.type in [Type.INTEGER, Type.FLOAT]:
|
||||
return Type.bool(left.value != right.value)
|
||||
|
||||
return Type.bool(left.type != right.type or left.value != right.value)
|
||||
|
||||
@classmethod
|
||||
def otherRelationOperatorsEvaluator(cls, left, operator, right):
|
||||
if left.type == right.type == Type.INTEGER:
|
||||
if left.type in [Type.INTEGER, Type.FLOAT] and right.type in [Type.INTEGER, Type.FLOAT]:
|
||||
if operator.value == ">":
|
||||
return Type.bool(left.value > right.value)
|
||||
|
||||
@@ -40,7 +46,7 @@ class RelationEvaluator(Evaluator):
|
||||
return Type.bool(left.value < right.value)
|
||||
|
||||
if operator.value == "<=":
|
||||
return Type.bool(left.value < right.value)
|
||||
return Type.bool(left.value <= right.value)
|
||||
|
||||
raise RuntimeException(f"Operator {operator.value} is not supported by {left.type.name.lower()} and {right.type.name.lower()} types", operator.pos)
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ class SumEvaluator(Evaluator):
|
||||
left = expressionEvaluator(doAssert=True)(node.left, environment).value
|
||||
right = expressionEvaluator(doAssert=True)(node.right, environment).value
|
||||
|
||||
if left.type == right.type == Type.INTEGER:
|
||||
return cls.integerEvaluator(left, node.operator, right)
|
||||
if left.type in [Type.INTEGER, Type.FLOAT] and right.type in [Type.INTEGER, Type.FLOAT]:
|
||||
return cls.numberEvaluator(left, node.operator, right)
|
||||
|
||||
if left.type == right.type == Type.STRING:
|
||||
return cls.stringEvaluator(left, node.operator, right)
|
||||
@@ -23,15 +23,17 @@ class SumEvaluator(Evaluator):
|
||||
if left.type == right.type == Type.MAP:
|
||||
return cls.mapEvaluator(left, node.operator, right)
|
||||
|
||||
raise RuntimeException(f"Operator {node.operator.value} is not supported by {left.type.name.lower()} and {right.type.name.lower()} types", node.operator.pos)
|
||||
raise RuntimeException(
|
||||
f"Operator {node.operator.value} is not supported by {left.type.name.lower()} and {right.type.name.lower()} types",
|
||||
node.operator.pos)
|
||||
|
||||
@classmethod
|
||||
def integerEvaluator(cls, left, operator, right):
|
||||
def numberEvaluator(cls, left, operator, right):
|
||||
if operator.value == "+":
|
||||
return Type.integer(left.value + right.value)
|
||||
return getProperTypeProvider(left.value + right.value)
|
||||
|
||||
if operator.value == "-":
|
||||
return Type.integer(left.value - right.value)
|
||||
return getProperTypeProvider(left.value - right.value)
|
||||
|
||||
raise RuntimeError("This line should never be reached")
|
||||
|
||||
@@ -64,3 +66,10 @@ class SumEvaluator(Evaluator):
|
||||
raise RuntimeException(f"Operator {operator.value} is not supported by map types", operator.pos)
|
||||
|
||||
raise RuntimeError("This line should never be reached")
|
||||
|
||||
|
||||
def getProperTypeProvider(value):
|
||||
return {
|
||||
int: lambda v: Type.integer(v),
|
||||
float: lambda v: Type.float(v)
|
||||
}[type(value)](value)
|
||||
|
||||
@@ -2,6 +2,7 @@ from smnp.error.syntax import SyntaxException
|
||||
from smnp.token.model import TokenList
|
||||
from smnp.token.tokenizers.bool import boolTokenizer
|
||||
from smnp.token.tokenizers.comment import commentTokenizer
|
||||
from smnp.token.tokenizers.float import floatTokenizer
|
||||
from smnp.token.tokenizers.identifier import identifierTokenizer
|
||||
from smnp.token.tokenizers.keyword import typeTokenizer
|
||||
from smnp.token.tokenizers.note import noteTokenizer
|
||||
@@ -29,6 +30,7 @@ tokenizers = (
|
||||
defaultTokenizer(TokenType.CLOSE_ANGLE),
|
||||
defaultTokenizer(TokenType.SEMICOLON),
|
||||
defaultTokenizer(TokenType.ASTERISK),
|
||||
defaultTokenizer(TokenType.PERCENT),
|
||||
defaultTokenizer(TokenType.ASSIGN),
|
||||
defaultTokenizer(TokenType.COMMA),
|
||||
defaultTokenizer(TokenType.SLASH),
|
||||
@@ -40,6 +42,7 @@ tokenizers = (
|
||||
defaultTokenizer(TokenType.DOT),
|
||||
|
||||
# Types
|
||||
separated(floatTokenizer),
|
||||
mapValue(separated(regexPatternTokenizer(TokenType.INTEGER, r'\d')), int),
|
||||
stringTokenizer,
|
||||
noteTokenizer,
|
||||
|
||||
17
smnp/token/tokenizers/float.py
Normal file
17
smnp/token/tokenizers/float.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from smnp.token.model import Token
|
||||
from smnp.token.tools import regexPatternTokenizer, keywordTokenizer, allOf
|
||||
from smnp.token.type import TokenType
|
||||
|
||||
|
||||
def createToken(pos, beforeDot, dot, afterDot):
|
||||
rawValue = f"{beforeDot.value}.{afterDot.value}"
|
||||
value = float(rawValue)
|
||||
return Token(TokenType.FLOAT, value, pos, rawValue)
|
||||
|
||||
|
||||
floatTokenizer = allOf(
|
||||
regexPatternTokenizer(TokenType.INTEGER, r'\d'),
|
||||
keywordTokenizer(None, "."),
|
||||
regexPatternTokenizer(TokenType.INTEGER, r'\d'),
|
||||
createToken=createToken
|
||||
)
|
||||
@@ -61,3 +61,19 @@ def mapValue(tokenizer, mapper):
|
||||
return (0, None)
|
||||
|
||||
return tokenize
|
||||
|
||||
def allOf(*tokenizers, createToken):
|
||||
def combinedTokenizer(input, current, line):
|
||||
consumedChars = 0
|
||||
tokens = []
|
||||
for tokenizer in tokenizers:
|
||||
consumed, token = tokenizer(input, current+consumedChars, line)
|
||||
if consumed > 0:
|
||||
consumedChars += consumed
|
||||
tokens.append(token)
|
||||
else:
|
||||
return (0, None)
|
||||
|
||||
return (consumedChars, createToken((current, line), *tokens))
|
||||
|
||||
return combinedTokenizer
|
||||
@@ -14,6 +14,7 @@ class TokenType(Enum):
|
||||
CLOSE_ANGLE = '>'
|
||||
SEMICOLON = ';'
|
||||
ASTERISK = '*'
|
||||
PERCENT = '%'
|
||||
ASSIGN = '='
|
||||
ARROW = '->'
|
||||
COMMA = ','
|
||||
@@ -29,6 +30,7 @@ class TokenType(Enum):
|
||||
NOT = 'not'
|
||||
INTEGER = 'integer'
|
||||
STRING = 'string'
|
||||
FLOAT = 'float'
|
||||
NOTE = 'note'
|
||||
BOOL = 'bool'
|
||||
TYPE = 'type'
|
||||
@@ -44,7 +46,6 @@ class TokenType(Enum):
|
||||
AS = 'as'
|
||||
IDENTIFIER = 'identifier'
|
||||
COMMENT = 'comment'
|
||||
PERCENT = 'percent'
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
|
||||
@@ -8,10 +8,10 @@ from smnp.type.value import Value
|
||||
|
||||
class Type(Enum):
|
||||
INTEGER = (int, lambda x: str(x))
|
||||
FLOAT = (float, lambda x: str(x))
|
||||
STRING = (str, lambda x: x)
|
||||
LIST = (list, lambda x: f"[{', '.join([e.stringify() for e in x])}]")
|
||||
MAP = (dict, lambda x: '{' + ', '.join(f"'{k.stringify()}' -> '{v.stringify()}'" for k, v in x.items()) + '}')
|
||||
PERCENT = (float, lambda x: f"{int(x * 100)}%")
|
||||
NOTE = (Note, lambda x: x.note.name)
|
||||
BOOL = (bool, lambda x: str(x).lower())
|
||||
SOUND = (Sound, lambda x: x.file)
|
||||
@@ -25,6 +25,10 @@ class Type(Enum):
|
||||
def integer(value):
|
||||
return Value(Type.INTEGER, value, {})
|
||||
|
||||
@staticmethod
|
||||
def float(value):
|
||||
return Value(Type.FLOAT, value, {})
|
||||
|
||||
@staticmethod
|
||||
def string(value):
|
||||
return Value(Type.STRING, value, {
|
||||
|
||||
Reference in New Issue
Block a user