Pylinting

This commit is contained in:
2023-02-07 17:48:11 +02:00
parent 9ae6f60325
commit 1c4dfb99a0
14 changed files with 1935 additions and 1783 deletions

5
.gitignore vendored
View File

@ -160,7 +160,10 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ .idea/
# VSCode
.vscode
# Debugging file # Debugging file
debug.py debug.py

View File

@ -1,4 +0,0 @@
[DEFAULT]
Duration = 0.25
Scale = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] # Chromatic

View File

@ -1,6 +1,8 @@
from ziffers import * from ziffers import *
from rich import print from rich import print
# FIXME: TO BE REMOVED / CHANGED TO TEST?
if __name__ == "__main__": if __name__ == "__main__":
expressions = { expressions = {
"Pitches": "-2 -1 0 1 2", "Pitches": "-2 -1 0 1 2",
@ -34,7 +36,7 @@ if __name__ == "__main__":
} }
for ex in expressions: for ex in expressions:
try: try:
print(f"Parsed: " + parse_expression(expressions[ex]).text) print("Parsed: " + parse_expression(expressions[ex]).text)
except Exception as e: except Exception as e:
print(f"[red]Failed on {ex}[/red]") print(f"[red]Failed on {ex}[/red]")
# print(f"[red]Failed on {ex}[/red]: {str(e)[0:40]}...") # print(f"[red]Failed on {ex}[/red]: {str(e)[0:40]}...")

View File

@ -1,10 +1,13 @@
from ziffers import * """ Simple REPL for testing Ziffers notation """
# pylint: disable=locally-disabled, redefined-builtin, broad-except
from rich import print from rich import print
from ziffers import parse_expression
EXIT_CONDITION = ("exit", "quit", "") EXIT_CONDITION = ("exit", "quit", "")
if __name__ == "__main__": if __name__ == "__main__":
print(f"[purple]== ZIFFERS REPL ==[/purple]") print("[purple]== ZIFFERS REPL ==[/purple]")
while True: while True:
expr = input("> ") expr = input("> ")
if expr not in EXIT_CONDITION: if expr not in EXIT_CONDITION:

View File

@ -1 +0,0 @@

View File

@ -1,5 +1,6 @@
from ziffers import * """ Test cases for the parser """
import pytest import pytest
from ziffers import parse_expression
def test_can_parse(): def test_can_parse():

View File

@ -1,3 +1,4 @@
""" Tests for the scale module """
from ziffers import scale from ziffers import scale
import pytest import pytest

View File

@ -1,9 +1,10 @@
""" Ziffers classes for the parsed notation """
from dataclasses import dataclass, field from dataclasses import dataclass, field
import itertools
import operator import operator
from typing import Any
import random import random
from .defaults import DEFAULT_OPTIONS from .defaults import DEFAULT_OPTIONS
import itertools
@dataclass @dataclass
class Meta: class Meta:
@ -14,26 +15,26 @@ class Meta:
for key, value in new_values.items(): for key, value in new_values.items():
if hasattr(self, key): if hasattr(self, key):
setattr(self, key, value) setattr(self, key, value)
def update_new(self, new_values): def update_new(self, new_values):
"""Updates new attributes from dict""" """Updates new attributes from dict"""
for key, value in new_values.items(): for key, value in new_values.items():
if hasattr(self, key): if hasattr(self, key):
if getattr(self,key) == None: if getattr(self, key) is None:
setattr(self, key, value) setattr(self, key, value)
@dataclass @dataclass
class Item(Meta): class Item(Meta):
"""Class for all Ziffers text based items""" """Class for all Ziffers text based items"""
text: str text: str
@dataclass @dataclass
class Whitespace(Item): class Whitespace(Item):
"""Class for whitespace""" """Class for whitespace"""
item_type: str = None item_type: str = field(default=None, repr=False, init=False)
@dataclass @dataclass
@ -41,89 +42,90 @@ class DurationChange(Item):
"""Class for changing duration""" """Class for changing duration"""
value: float value: float
key: str = "duration" key: str = field(default="duration", repr=False, init=False)
item_type: str = "change" item_type: str = field(default="change", repr=False, init=False)
@dataclass @dataclass
class OctaveChange(Item): class OctaveChange(Item):
"""Class for changing octave""" """Class for changing octave"""
value: int value: int
key: str = "octave" key: str = field(default="octave", repr=False, init=False)
item_type: str = "change" item_type: str = field(default="change", repr=False, init=False)
@dataclass @dataclass
class OctaveMod(Item): class OctaveAdd(Item):
"""Class for modifying octave""" """Class for modifying octave"""
value: int value: int
key: str = "octave" key: str = field(default="octave", repr=False, init=False)
item_type: str = "add" item_type: str = field(default="add", repr=False, init=False)
@dataclass @dataclass
class Event(Item): class Event(Item):
"""Abstract class for events with duration""" """Abstract class for events with duration"""
duration: float = None duration: float = field(default=None)
@dataclass @dataclass
class Pitch(Event): class Pitch(Event):
"""Class for pitch in time""" """Class for pitch in time"""
pc: int = None pitch_class: int = field(default=None)
duration: float = None duration: float = field(default=None)
octave: int = None octave: int = field(default=None)
@dataclass @dataclass
class RandomPitch(Event): class RandomPitch(Event):
"""Class for random pitch""" """Class for random pitch"""
pc: int = None pitch_class: int = field(default=None)
@dataclass @dataclass
class RandomPercent(Item): class RandomPercent(Item):
"""Class for random percent""" """Class for random percent"""
percent: float = None percent: float = field(default=None)
@dataclass @dataclass
class Chord(Event): class Chord(Event):
"""Class for chords""" """Class for chords"""
pcs: list[Pitch] = None pitch_classes: list[Pitch] = field(default=None)
@dataclass @dataclass
class Function(Event): class Function(Event):
"""Class for functions""" """Class for functions"""
run: str = None run: str = field(default=None)
@dataclass @dataclass
class Sequence(Meta): class Sequence(Meta):
"""Class for sequences of items""" """Class for sequences of items"""
values: list[Item] values: list[Item]
text: str = None text: str = field(default=None)
wrap_start: str = field(default=None, repr=False) wrap_start: str = field(default=None, repr=False)
wrap_end: str = field(default=None, repr=False) wrap_end: str = field(default=None, repr=False)
local_index: int = 0 local_index: int = field(default=0, init=False)
def __post_init__(self): def __post_init__(self):
self.text = self.collect_text() self.text = self.__collect_text()
# TODO: Filter out whitespace if not needed?
# self.values = list(filter(lambda elm: not isinstance(elm, Whitespace), self.values))
def __iter__(self): def __iter__(self):
return self return self
def __next__(self): def __next__(self):
if self.local_index<len(self.values): if self.local_index < len(self.values):
next_item = self.values[self.local_index] next_item = self.values[self.local_index]
self.local_index += 1 self.local_index += 1
return next_item return next_item
@ -138,14 +140,95 @@ class Sequence(Meta):
if key != "text" and hasattr(obj, key): if key != "text" and hasattr(obj, key):
setattr(obj, key, value) setattr(obj, key, value)
def collect_text(self) -> str: def __collect_text(self) -> str:
"""Collect text value from values"""
text = "".join([val.text for val in self.values]) text = "".join([val.text for val in self.values])
if self.wrap_start != None: if self.wrap_start is not None:
text = self.wrap_start + text text = self.wrap_start + text
if self.wrap_end != None: if self.wrap_end is not None:
text = text + self.wrap_end text = text + self.wrap_end
return text return text
def flatten_values(self):
"""Flattens the Ziffers object tree"""
for item in self.values:
if isinstance(item, Sequence):
yield from item.flatten_values()
else:
yield item
@dataclass
class Ziffers(Sequence):
"""Main class for holding options and the current state"""
options: dict = field(default_factory=DEFAULT_OPTIONS)
loop_i: int = 0
iterator: iter = field(default=None, repr=False)
current: Whitespace|DurationChange|OctaveChange|OctaveAdd = field(default=None)
def __post_init__(self):
super().__post_init__()
self.iterator = self.flatten_values()
def __next__(self):
self.current = next(self.iterator)
# Skip whitespace and collect duration & octave changes
while isinstance(
self.current, (Whitespace, DurationChange, OctaveChange, OctaveAdd)
):
if self.current.item_type == "change": # Change options
self.options[self.current.key] = self.current.value
elif self.current.item_type == "add":
if self.current.key in self.options: # Add to existing value
self.options[self.current.key] += self.current.value
else: # Create value if not existing
self.options[self.current.key] = self.current.value
self.current = next(self.iterator) # Skip item
self.current.update_new(self.options)
self.loop_i += 1
return self.current
def take(self, num: int) -> list[Pitch]:
""" Take number of pitch classes from the parsed sequence. Cycles from the beginning.
Args:
num (int): Number of pitch classes to take from the sequence
Returns:
list: List of pitch class items
"""
return list(itertools.islice(itertools.cycle(self), num))
def set_defaults(self, options: dict):
""" Sets options for the parser
Args:
options (dict): Options as a dict
"""
self.options = DEFAULT_OPTIONS | options
# TODO: Refactor these
def pitch_classes(self) -> list[int]:
""" Return list of pitch classes as ints """
return [val.pitch_class for val in self.values if isinstance(val, Pitch)]
def durations(self) -> list[float]:
""" Return list of pitch durations as floats"""
return [val.dur for val in self.values if isinstance(val, Pitch)]
def pairs(self) -> list[tuple]:
""" Return list of pitches and durations """
return [(val.pitch_class, val.dur) for val in self.values if isinstance(val, Pitch)]
def octaves(self) -> list[int]:
""" Return list of octaves """
return [val.octave for val in self.values if isinstance(val, Pitch)]
@dataclass @dataclass
class ListSequence(Sequence): class ListSequence(Sequence):
"""Class for Ziffers list sequences""" """Class for Ziffers list sequences"""
@ -154,11 +237,36 @@ class ListSequence(Sequence):
wrap_end: str = field(default=")", repr=False) wrap_end: str = field(default=")", repr=False)
@dataclass
class Integer(Item):
"""Class for integers"""
value: int
@dataclass
class RandomInteger(Item):
"""Class for random integer"""
min: int
max: int
def __post_init__(self):
if self.min > self.max:
new_max = self.min
self.min = self.max
self.max = new_max
def value(self):
""" Evaluate the random value for the generator """
return random.randint(self.min, self.max)
@dataclass @dataclass
class RepeatedListSequence(Sequence): class RepeatedListSequence(Sequence):
"""Class for Ziffers list sequences""" """Class for Ziffers list sequences"""
repeats: Item = None repeats: RandomInteger | Integer = field(default_factory=Integer(value=1, text="1"))
wrap_start: str = field(default="(:", repr=False) wrap_start: str = field(default="(:", repr=False)
wrap_end: str = field(default=":)", repr=False) wrap_end: str = field(default=":)", repr=False)
@ -181,38 +289,23 @@ class Cyclic(Sequence):
def __post_init__(self): def __post_init__(self):
super().__post_init__() super().__post_init__()
# TODO: Do spaced need to be filtered out? # TODO: Do spaced need to be filtered out?
self.values = [val for val in self.values if isinstance(val,Whitespace)] self.values = [val for val in self.values if isinstance(val, Whitespace)]
def value(self): def value(self):
""" Get the value for the current cycle """
return self.values[self.cycle] return self.values[self.cycle]
def next_cycle(self, cycle: int): def next_cycle(self, cycle: int):
self.cycle = self.cycle+1 """ Evaluate next cycle """
self.cycle = self.cycle + 1
@dataclass
class RandomInteger(Item):
"""Class for random integer"""
min: int
max: int
def __post_init__(self):
if self.min>self.max:
new_max = self.min
self.min = self.max
self.max = new_max
def value(self):
return random.randint(self.min,self.max)
@dataclass @dataclass
class Range(Item): class Range(Item):
"""Class for range""" """Class for range"""
start: int = None start: int = field(default=None)
end: int = None end: int = field(default=None)
ops = { ops = {
@ -239,6 +332,7 @@ class ListOperation(Sequence):
"""Class for list operations""" """Class for list operations"""
def run(self): def run(self):
""" Run operations """
pass pass
@ -270,13 +364,6 @@ class Atom(Item):
value: ... value: ...
@dataclass
class Integer(Item):
"""Class for integers"""
value: int
@dataclass @dataclass
class Euclid(Item): class Euclid(Item):
"""Class for euclidean cycles""" """Class for euclidean cycles"""
@ -284,70 +371,14 @@ class Euclid(Item):
pulses: int pulses: int
length: int length: int
onset: list onset: list
offset: list = None offset: list = field(default=None)
rotate: int = None rotate: int = field(default=None)
@dataclass @dataclass
class RepeatedSequence(Sequence): class RepeatedSequence(Sequence):
"""Class for repeats""" """Class for repeats"""
repeats: Item = None repeats: RandomInteger | Integer = field(default_factory=Integer(value=1, text="1"))
wrap_start: str = field(default="[:", repr=False) wrap_start: str = field(default="[:", repr=False)
wrap_end: str = field(default=":]", repr=False) wrap_end: str = field(default=":]", repr=False)
@dataclass
class Ziffers(Sequence):
"""Main class for holding options and the current state"""
options: dict = field(default_factory=DEFAULT_OPTIONS)
loop_i: int = 0
current: Item = None
it: iter = None
def __post_init__(self):
super().__post_init__()
self.it = iter(self.values)
def __next__(self):
try:
self.current = next(self.it)
# Skip whitespace and collect duration & octave changes
while isinstance(self.current,(Whitespace,DurationChange,OctaveChange,OctaveMod)):
if self.current.item_type == "change":
self.options[self.current.key] = self.current.value
elif self.current.item_type == "add":
if self.current.key in self.options:
self.options[self.current.key] += self.current.value
else:
self.options[self.current.key] = self.current.value
self.current = next(self.it)
except StopIteration: # Start from the beginning
self.current = next(self.it)
self.current.update_new(self.options)
self.loop_i += 1
return self.current
def take(self,num: int) -> list:
return list(itertools.islice(iter(self), num))
def set_defaults(self,options: dict):
self.options = DEFAULT_OPTIONS | options
# TODO: Handle options and generated values
def pcs(self) -> list[int]:
return [val.pc for val in self.values if isinstance(val,Pitch)]
def durations(self) -> list[float]:
return [val.dur for val in self.values if isinstance(val,Pitch)]
def pairs(self) -> list[tuple]:
return [(val.pc,val.dur) for val in self.values if isinstance(val,Pitch)]
def octaves(self) -> list[int]:
return [val.octave for val in self.values if isinstance(val,Pitch)]

View File

@ -1,3 +1,4 @@
""" Common methods used in parsing """
def flatten(arr: list) -> list: def flatten(arr: list) -> list:
"""Flattens array""" """Flattens array"""
return ( return (

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,11 @@
""" Lark transformer for mapping Lark tokens to Ziffers objects """
from lark import Transformer from lark import Transformer
from .classes import ( from .classes import (
Ziffers, Ziffers,
Whitespace, Whitespace,
DurationChange, DurationChange,
OctaveChange, OctaveChange,
OctaveMod, OctaveAdd,
Pitch, Pitch,
RandomPitch, RandomPitch,
RandomPercent, RandomPercent,
@ -25,157 +26,185 @@ from .classes import (
Euclid, Euclid,
RepeatedSequence, RepeatedSequence,
) )
from .common import ( from .common import flatten, sum_dict
flatten,
sum_dict
)
from .defaults import DEFAULT_DURS from .defaults import DEFAULT_DURS
# pylint: disable=locally-disabled, unused-argument, too-many-public-methods, invalid-name
class ZiffersTransformer(Transformer): class ZiffersTransformer(Transformer):
"""Rules for transforming Ziffers expressions into tree."""
"""
Rules for transforming Ziffers expressions into tree.
"""
def start(self, items): def start(self, items):
"""Root for the rules"""
seq = Sequence(values=items[0]) seq = Sequence(values=items[0])
return Ziffers(values=seq,options={}) return Ziffers(values=seq, options={})
def sequence(self, items): def sequence(self, items):
"""Flatten sequence"""
return flatten(items) return flatten(items)
def random_integer(self, s): def random_integer(self, item):
val = s[0][1:-1].split(",") """Parses random integer syntax"""
return RandomInteger(min=val[0], max=val[1], text=s[0].value) val = item[0][1:-1].split(",")
return RandomInteger(min=val[0], max=val[1], text=item[0].value)
def range(self, s): def range(self, item):
val = s[0].split("..") """Parses range syntax"""
return Range(start=val[0], end=val[1], text=s[0]) val = item[0].split("..")
return Range(start=val[0], end=val[1], text=item[0])
def cycle(self, items): def cycle(self, items):
"""Parses cycle"""
values = items[0] values = items[0]
return Cyclic(values=values) return Cyclic(values=values)
def pc(self, s): def pitch_class(self, item):
if len(s) > 1: """Parses pitch class"""
# If there are prefixes
if len(item) > 1:
# Collect&sum prefixes from any order: _qee^s4 etc. # Collect&sum prefixes from any order: _qee^s4 etc.
result = sum_dict(s) result = sum_dict(item)
return Pitch(**result) return Pitch(**result)
else:
val = s[0]
return Pitch(**val)
def pitch(self, s): val = item[0]
return {"pc": int(s[0].value), "text": s[0].value} return Pitch(**val)
def prefix(self, s): def pitch(self, items):
return s[0] """Return pitch class info"""
return {"pitch_class": int(items[0].value), "text": items[0].value}
def oct_change(self, s): def prefix(self, items):
octave = s[0] """Return prefix"""
return [OctaveChange(value=octave["octave"], text=octave["text"]), s[1]] return items[0]
def oct_mod(self, s): def oct_change(self, items):
octave = s[0] """Parses octave change"""
return [OctaveMod(value=octave["octave"], text=octave["text"]), s[1]] octave = items[0]
return [OctaveChange(value=octave["octave"], text=octave["text"]), items[1]]
def escaped_octave(self, s): def oct_mod(self, items):
value = s[0][1:-1] """Parses octave modification"""
return {"octave": int(value), "text": s[0].value} octave = items[0]
return [OctaveAdd(value=octave["octave"], text=octave["text"]), items[1]]
def octave(self, s): def escaped_octave(self, items):
value = sum([1 if char == "^" else -1 for char in s[0].value]) """Return octave info"""
return {"octave": value, "text": s[0].value} value = items[0][1:-1]
return {"octave": int(value), "text": items[0].value}
def chord(self, s): def octave(self, items):
return Chord(pcs=s, text="".join([val.text for val in s])) """Return octave info"""
value = sum(1 if char == "^" else -1 for char in items[0].value)
return {"octave": value, "text": items[0].value}
def dur_change(self, s): def chord(self, items):
durs = s[0] """Parses chord"""
return DurationChange(value=durs[1], text=durs[0]) return Chord(pitch_classes=items, text="".join([val.text for val in items]))
def char_change(self, s): def dur_change(self, items):
"""Parses duration change"""
durs = items[0]
return DurationChange(value=durs["duration"], text=durs["text"])
def char_change(self, items):
"""Return partial duration char info"""
chars = "" chars = ""
durs = 0.0 durs = 0.0
for (dchar, dots) in s: for dchar, dots in items:
val = DEFAULT_DURS[dchar] val = DEFAULT_DURS[dchar]
if dots > 0: if dots > 0:
val = val * (2.0 - (1.0 / (2 * dots))) val = val * (2.0 - (1.0 / (2 * dots)))
chars = chars + (dchar + "." * dots) chars = chars + (dchar + "." * dots)
durs = durs + val durs = durs + val
return [chars, durs] return {"text":chars, "duration":durs}
def dchar_not_prefix(self, s): def dchar_not_prefix(self, items):
dur = s[0].split(".", 1) """Return partial duration char info"""
dur = items[0].split(".", 1)
dots = 0 dots = 0
if len(dur) > 1: if len(dur) > 1:
dots = len(dur[1]) + 1 dots = len(dur[1]) + 1
return [dur[0], dots] return [dur[0], dots]
def escaped_decimal(self, s): def escaped_decimal(self, items):
val = s[0] """Return partial decimal info"""
val = items[0]
val["text"] = "<" + val["text"] + ">" val["text"] = "<" + val["text"] + ">"
return val return val
def random_pitch(self, s): def random_pitch(self, items):
"""Parses random pitch"""
return RandomPitch(text="?") return RandomPitch(text="?")
def random_percent(self, s): def random_percent(self, items):
"""Parses random percent"""
return RandomPercent(text="%") return RandomPercent(text="%")
def duration_chars(self, s): def duration_chars(self, items):
durations = [val[1] for val in s] """Return partial duration info"""
characters = "".join([val[0] for val in s]) durations = [val[1] for val in items]
characters = "".join([val[0] for val in items])
return {"duration": sum(durations), "text": characters} return {"duration": sum(durations), "text": characters}
def dotted_dur(self, s): def dotted_dur(self, items):
key = s[0] """Return partial duration info"""
key = items[0]
val = DEFAULT_DURS[key] val = DEFAULT_DURS[key]
dots = len(s) - 1 dots = len(items) - 1
if dots > 0: if dots > 0:
val = val * (2.0 - (1.0 / (2 * dots))) val = val * (2.0 - (1.0 / (2 * dots)))
return [key + "." * dots, val] return [key + "." * dots, val]
def decimal(self, s): def decimal(self, items):
val = s[0] """Return partial duration info"""
val = items[0]
return {"duration": float(val), "text": val.value} return {"duration": float(val), "text": val.value}
def dot(self, s): def dot(self, items):
"""Return partial duration info"""
return "." return "."
def dchar(self, s): def dchar(self, items):
chardur = s[0].value """Return partial duration info"""
chardur = items[0].value
return chardur return chardur
def WS(self, s): def WS(self, items):
return Whitespace(text=s[0]) """Parse whitespace"""
return Whitespace(text=items[0])
def subdivision(self, items): def subdivision(self, items):
"""Parse subdivision"""
values = flatten(items[0]) values = flatten(items[0])
return Subdivision( return Subdivision(
values=values, text="[" + "".join([val.text for val in values]) + "]" values=values, text="[" + "".join([val.text for val in values]) + "]"
) )
def subitems(self, s): def subitems(self, items):
return s """Return subdivision items"""
return items
# Eval rules # Eval rules
def eval(self, s): def eval(self, items):
val = s[0] """Parse eval"""
val = items[0]
return Eval(values=val) return Eval(values=val)
def operation(self, s): def operation(self, items):
return s """Return partial eval operations"""
return items
def atom(self, s): def atom(self, token):
val = s[0].value """Return partial eval item"""
val = token[0].value
return Atom(value=val, text=val) return Atom(value=val, text=val)
# List rules # List rules
def list(self, items): def list(self, items):
"""Parse list sequence notation, ex: (1 2 3)"""
if len(items) > 1: if len(items) > 1:
prefixes = sum_dict(items[0:-1]) prefixes = sum_dict(items[0:-1])
values = items[-1] values = items[-1]
@ -187,9 +216,10 @@ class ZiffersTransformer(Transformer):
return seq return seq
def repeated_list(self, items): def repeated_list(self, items):
"""Parse repeated list notation ex: (: 1 2 3 :)"""
if len(items) > 2: if len(items) > 2:
prefixes = sum_dict(items[0:-2]) # If there are prefixes prefixes = sum_dict(items[0:-2]) # If there are prefixes
if items[-1] != None: if items[-1] is not None:
seq = RepeatedListSequence( seq = RepeatedListSequence(
values=items[-2], values=items[-2],
repeats=items[-1], repeats=items[-1],
@ -202,7 +232,7 @@ class ZiffersTransformer(Transformer):
seq.update_values(prefixes) seq.update_values(prefixes)
return seq return seq
else: else:
if items[-1] != None: if items[-1] is not None:
seq = RepeatedListSequence( seq = RepeatedListSequence(
values=items[-2], values=items[-2],
repeats=items[-1], repeats=items[-1],
@ -214,54 +244,64 @@ class ZiffersTransformer(Transformer):
) )
return seq return seq
def SIGNED_NUMBER(self, s): def SIGNED_NUMBER(self, token):
val = s.value """Parse integer"""
val = token.value
return Integer(text=val, value=int(val)) return Integer(text=val, value=int(val))
def number(self, s): def number(self, item):
return s """Return partial number (Integer or RandomInteger)"""
return item
def cyclic_number(self, s): def cyclic_number(self, item):
return Cyclic(values=s) """Parse cyclic notation"""
return Cyclic(values=item)
def lisp_operation(self, s): def lisp_operation(self, items):
op = s[0] """Parse lisp like list operation"""
values = s[1:] op = items[0]
values = items[1:]
return Operation( return Operation(
operator=op, operator=op,
values=values, values=values,
text="(+" + "".join([v.text for v in values]) + ")", text="(+" + "".join([v.text for v in values]) + ")",
) )
def operator(self, s): def operator(self, token):
val = s[0].value """Parse operator"""
val = token[0].value
return Operator(text=val) return Operator(text=val)
def list_items(self, s): def list_items(self, items):
return Sequence(values=s) """Parse sequence"""
return Sequence(values=items)
def list_op(self, s): def list_op(self, items):
return ListOperation(values=s) """Parse list operation"""
return ListOperation(values=items)
def euclid(self, s): def euclid(self, items):
params = s[1][1:-1].split(",") """Parse euclid notation"""
init = {"onset": s[0], "pulses": params[0], "length": params[1]} params = items[1][1:-1].split(",")
text = s[0].text + s[1] init = {"onset": items[0], "pulses": params[0], "length": params[1]}
text = items[0].text + items[1]
if len(params) > 2: if len(params) > 2:
init["rotate"] = params[2] init["rotate"] = params[2]
if len(s) > 2: if len(items) > 2:
init["offset"] = s[2] init["offset"] = items[2]
text = text + s[2].text text = text + items[2].text
init["text"] = text init["text"] = text
return Euclid(**init) return Euclid(**init)
def euclid_operator(self, s): def euclid_operator(self, token):
return s.value """Return euclid operators"""
return token.value
def repeat(self, s): def repeat(self, items):
if s[-1] != None: """Parse repeated sequence, ex: [: 1 2 3 :]"""
if items[-1] is not None:
return RepeatedSequence( return RepeatedSequence(
values=s[0], repeats=s[-1], wrap_end=":" + s[-1].text + "]" values=items[0], repeats=items[-1], wrap_end=":" + items[-1].text + "]"
) )
else: else:
return RepeatedSequence(values=s[0], repeats=Integer(value=1, text="1")) return RepeatedSequence(values=items[0], repeats=Integer(value=1, text="1"))

View File

@ -1,7 +1,9 @@
from rich import print """ Module for the parser """
from .mapper import *
from pathlib import Path from pathlib import Path
from lark import Lark from lark import Lark
from .classes import Ziffers
from .mapper import ZiffersTransformer
grammar_path = Path(__file__).parent grammar_path = Path(__file__).parent
grammar = grammar_path / "ziffers.lark" grammar = grammar_path / "ziffers.lark"
@ -14,45 +16,83 @@ ziffers_parser = Lark.open(
transformer=ZiffersTransformer(), transformer=ZiffersTransformer(),
) )
def parse_expression(expr: str):
"""Parse an expression using the Ziffers parser""" def parse_expression(expr: str) -> Ziffers:
"""Parse an expression using the Ziffers parser
Args:
expr (str): Ziffers expression as a string
Returns:
Ziffers: Reutrns Ziffers iterable
"""
return ziffers_parser.parse(expr) return ziffers_parser.parse(expr)
def zparse(expr: str, opts: dict=None):
def zparse(expr: str, opts: dict = None) -> Ziffers:
"""Parses ziffers expression with options
Args:
expr (str): Ziffers expression as a string
opts (dict, optional): Options for parsing the Ziffers expression. Defaults to None.
Returns:
Ziffers: Returns Ziffers iterable parsed with the given options
"""
parsed = parse_expression(expr) parsed = parse_expression(expr)
if opts: if opts:
parsed.set_defaults(opts) parsed.set_defaults(opts)
return parsed return parsed
def z0(expr: str, opts: dict=None):
return zparse(expr,opts)
def z1(expr: str, opts: dict=None): # pylint: disable=invalid-name
return zparse(expr,opts)
def z2(expr: str, opts: dict=None):
return zparse(expr,opts)
def z3(expr: str, opts: dict=None):
return zparse(expr,opts)
def z3(expr: str, opts: dict=None):
return zparse(expr,opts)
def z4(expr: str, opts: dict=None): def z0(expr: str, opts: dict = None) -> Ziffers:
return zparse(expr,opts) """Shortened method name for zparse"""
return zparse(expr, opts)
def z5(expr: str, opts: dict=None):
return zparse(expr,opts)
def z6(expr: str, opts: dict=None): def z1(expr: str, opts: dict = None) -> Ziffers:
return zparse(expr,opts) """Shortened method name for zparse"""
return zparse(expr, opts)
def z7(expr: str, opts: dict=None):
return zparse(expr,opts)
def z8(expr: str, opts: dict=None): def z2(expr: str, opts: dict = None) -> Ziffers:
return zparse(expr,opts) """Shortened method name for zparse"""
return zparse(expr, opts)
def z9(expr: str, opts: dict=None):
return zparse(expr,opts) def z3(expr: str, opts: dict = None) -> Ziffers:
"""Shortened method name for zparse"""
return zparse(expr, opts)
def z4(expr: str, opts: dict = None) -> Ziffers:
"""Shortened method name for zparse"""
return zparse(expr, opts)
def z5(expr: str, opts: dict = None) -> Ziffers:
"""Shortened method name for zparse"""
return zparse(expr, opts)
def z6(expr: str, opts: dict = None) -> Ziffers:
"""Shortened method name for zparse"""
return zparse(expr, opts)
def z7(expr: str, opts: dict = None) -> Ziffers:
"""Shortened method name for zparse"""
return zparse(expr, opts)
def z8(expr: str, opts: dict = None) -> Ziffers:
"""Shortened method name for zparse"""
return zparse(expr, opts)
def z9(expr: str, opts: dict = None) -> Ziffers:
"""Shortened method name for zparse"""
return zparse(expr, opts)

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
// Root for the rules // Root for the rules
?root: sequence -> start ?root: sequence -> start
sequence: (pc | dur_change | oct_mod | oct_change | WS | chord | cycle | random_integer | random_pitch | random_percent | range | list | repeated_list | lisp_operation | list_op | subdivision | eval | euclid | repeat)* sequence: (pitch_class | dur_change | oct_mod | oct_change | WS | chord | cycle | random_integer | random_pitch | random_percent | range | list | repeated_list | lisp_operation | list_op | subdivision | eval | euclid | repeat)*
// Pitch classes // Pitch classes
pc: prefix* pitch pitch_class: prefix* pitch
prefix: (octave | duration_chars | escaped_decimal | escaped_octave) prefix: (octave | duration_chars | escaped_decimal | escaped_octave)
pitch: /-?[0-9TE]/ pitch: /-?[0-9TE]/
escaped_decimal: "<" decimal ">" escaped_decimal: "<" decimal ">"
@ -11,7 +11,7 @@
octave: /[_^]+/ octave: /[_^]+/
// Chords // Chords
chord: pc pc+ chord: pitch_class pitch_class+
// Valid as integer // Valid as integer
?number: SIGNED_NUMBER | random_integer | cyclic_number ?number: SIGNED_NUMBER | random_integer | cyclic_number
@ -47,7 +47,7 @@
// Subdivision // Subdivision
subdivision: "[" subitems "]" subdivision: "[" subitems "]"
subitems: (pc | WS | chord | cycle | subdivision)* subitems: (pitch_class | WS | chord | cycle | subdivision)*
// Control characters modifying future events // Control characters modifying future events
oct_mod: octave WS oct_mod: octave WS