25 Commits

Author SHA1 Message Date
Bartłomiej Pluta
aca6e6bb55 Create tools for compiling waves 2019-07-30 16:51:35 +02:00
Bartłomiej Pluta
a7de7f0279 Optimise time of generating overtones 2019-07-30 15:12:32 +02:00
Bartłomiej Pluta
7e55fe4c1a Add 'plot' function and remove deprecated 'percent' type 2019-07-30 13:59:18 +02:00
Bartłomiej Pluta
8abae7c2ff Add decay to synthetiser 2019-07-29 22:11:30 +02:00
Bartłomiej Pluta
07f08b0557 Add support for integers in passing overtones to synth 2019-07-29 17:59:38 +02:00
Bartłomiej Pluta
13b069dc7d Add support for non-quoted (identifier) map keys being used as string 2019-07-29 17:53:41 +02:00
Bartłomiej Pluta
73ea88d8d9 Overload synth function to accept notes without config object 2019-07-29 17:19:41 +02:00
Bartłomiej Pluta
a5875425fc Enable passing config map to synth function 2019-07-28 21:20:11 +02:00
Bartłomiej Pluta
0dcf5287e1 Add polyphony AND add overtones do synthesed tones 2019-07-28 19:48:39 +02:00
Bartłomiej Pluta
75dcacce67 Merge branch 'add-float-type' 2019-07-28 09:32:31 +02:00
Bartłomiej Pluta
70687ddc02 Fix relation operators between floats and integers 2019-07-27 12:52:30 +02:00
Bartłomiej Pluta
d802c58eee Add Float function to convert strings and integers to floats 2019-07-27 12:48:17 +02:00
Bartłomiej Pluta
c9a3fc070b Add Integer function to convert strings and floats to integers 2019-07-26 20:59:41 +02:00
Bartłomiej Pluta
b126f83824 Enable basic support for evaluating float types 2019-07-25 13:37:31 +02:00
Bartłomiej Pluta
6dc503ba86 Enable parsing float types 2019-07-25 13:07:53 +02:00
Bartłomiej Pluta
6222dccaac Improve float type tokenizer 2019-07-25 13:02:33 +02:00
Bartłomiej Pluta
0657214aa3 Create tokenizer for float type 2019-07-25 12:51:48 +02:00
Bartłomiej Pluta
3feec0839b Fix scope leakage after exit function 2019-07-16 23:59:34 +02:00
Bartłomiej Pluta
56ca69246d Merge branch 'add-filtering-clause-to-loop-operator' 2019-07-16 13:25:22 +02:00
Bartłomiej Pluta
5a2508e804 Move parenthesed expression to atom 2019-07-16 10:23:30 +02:00
Bartłomiej Pluta
ea28ab6235 Fix leaking scope of function to outer scope 2019-07-16 10:18:00 +02:00
Bartłomiej Pluta
6e9e252b86 Create 'read' function 2019-07-15 23:54:21 +02:00
Bartłomiej Pluta
17ef5be057 Remove unnecessary functions from module 2019-07-15 21:10:05 +02:00
Bartłomiej Pluta
44e63ed18d Add optional filtering expression to loop operator 2019-07-15 20:43:33 +02:00
Bartłomiej Pluta
83c7b92741 Merge branch 'optional-function-args' 2019-07-15 20:26:48 +02:00
43 changed files with 618 additions and 305 deletions

View File

@@ -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,

View File

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

View File

@@ -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"
)

View File

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

View File

@@ -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) ]))

View File

@@ -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 ]

View File

@@ -0,0 +1,4 @@
from smnp.module.float.function import float
functions = [ float.function ]
methods = []

View File

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

View File

@@ -0,0 +1,4 @@
from smnp.module.integer.function import integer
functions = [ integer.function ]
methods = []

View File

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

View File

@@ -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 ]

View File

@@ -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')

View File

@@ -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),
)

View File

@@ -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 ]

View File

@@ -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')

View File

@@ -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 = []

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

View File

@@ -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')

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

View File

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

View File

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

View File

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

View File

@@ -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 = []

View File

@@ -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):

View File

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

View File

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

View File

@@ -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),

View File

@@ -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):

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

View File

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

View File

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

View File

@@ -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])

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,

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

View File

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

View File

@@ -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):

View File

@@ -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, {