diff --git a/.gitignore b/.gitignore index b1cb160..1188230 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,11 @@ # Byte-compiled / optimized / DLL files -__pycache__/ +**/__pycache__/ *.py[cod] *$py.class +# Pytest +.pytest_cache + # C extensions *.so @@ -159,3 +162,6 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +# Debugging file +debug.py + diff --git a/main.py b/main.py index 5f305fa..fa9eba9 100644 --- a/main.py +++ b/main.py @@ -7,7 +7,7 @@ if __name__ == "__main__": 'Chords': "0 024 2 246", 'Note lengths': "w 0 h 1 q 2 e 3 s 4", 'Subdivision': "[1 2 [3 4]]", - 'Decimal durations': "0.25 0 1 [0.333]2 3", + 'Decimal durations': "0.25 0 1 <0.333>2 3", 'Octaves': "^ 0 ^ 1 _ 2 _ 3", 'Escaped octave': "<2> 1 <1>1<-2>3", 'Roman chords': "i ii iii+4 iv+5 v+8 vi+10 vii+20", @@ -32,8 +32,9 @@ if __name__ == "__main__": 'Functions': "(0 1 2 3){x%3==0?x-2:x+2}", 'Polynomials': "(-10..10){(x**3)*(x+1)%12}", } - for expression in expressions: - try: - parse_expression(expression) + for ex in expressions: + try: + print(f"Parsed: "+parse_expression(expressions[ex]).text) except Exception as e: - print(f"[red]Failed on {expression}[/red]: {str(e)[0:40]}...") + print(f"[red]Failed on {ex}[/red]") + #print(f"[red]Failed on {ex}[/red]: {str(e)[0:40]}...") diff --git a/tests/__pycache__/__init__.cpython-311.pyc b/tests/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index ce98779..0000000 Binary files a/tests/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/__pycache__/test_parser.cpython-311-pytest-7.2.1.pyc b/tests/__pycache__/test_parser.cpython-311-pytest-7.2.1.pyc deleted file mode 100644 index c36b66e..0000000 Binary files a/tests/__pycache__/test_parser.cpython-311-pytest-7.2.1.pyc and /dev/null differ diff --git a/tests/test_parser.py b/tests/test_parser.py index 8f454a1..074a076 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,4 +1,5 @@ from ziffers import * +import pytest def test_can_parse(): expressions = [ @@ -24,10 +25,22 @@ def test_can_parse(): print(results) assert all(results) -#print(ziffers_parser.parse("[1 [2 3]]")) -#print(ziffers_parser.parse("(1 (1,3) 1..3)")) -#print(ziffers_parser.parse("_^ q _qe^3 qww_4 _123 <1 2>")) -#print(ziffers_parser.parse("q _2 _ 3 ^ 343")) -#print(ziffers_parser.parse("2 qe2 e4").values) -#print(ziffers_parser.parse("q 2 <3 343>")) -#print(ziffers_parser.parse("q (2 <3 343 (3 4)>)")) +@pytest.mark.parametrize( + "pattern", + [ + "1 2 3", + "q3 e4 s5", + ], +) +def test_parsing_text(pattern: str): + assert parse_expression(pattern).text == pattern + +@pytest.mark.parametrize( + "pattern,expected", + [ + ("1 2 3", [1,2,3]), + ("q2 eq3", [2,3]), + ], +) +def test_pcs(pattern: str, expected: list): + assert parse_expression(pattern).pcs == expected \ No newline at end of file diff --git a/ziffers/__pycache__/ZiffersData.cpython-311.pyc b/ziffers/__pycache__/ZiffersData.cpython-311.pyc deleted file mode 100644 index 95e428b..0000000 Binary files a/ziffers/__pycache__/ZiffersData.cpython-311.pyc and /dev/null differ diff --git a/ziffers/__pycache__/ZiffersTransformer.cpython-311.pyc b/ziffers/__pycache__/ZiffersTransformer.cpython-311.pyc deleted file mode 100644 index fed3bd5..0000000 Binary files a/ziffers/__pycache__/ZiffersTransformer.cpython-311.pyc and /dev/null differ diff --git a/ziffers/__pycache__/__init__.cpython-310.pyc b/ziffers/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index f84025e..0000000 Binary files a/ziffers/__pycache__/__init__.cpython-310.pyc and /dev/null differ diff --git a/ziffers/__pycache__/__init__.cpython-311.pyc b/ziffers/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 542df8f..0000000 Binary files a/ziffers/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/ziffers/__pycache__/classes.cpython-311.pyc b/ziffers/__pycache__/classes.cpython-311.pyc deleted file mode 100644 index c937db5..0000000 Binary files a/ziffers/__pycache__/classes.cpython-311.pyc and /dev/null differ diff --git a/ziffers/__pycache__/common.cpython-311.pyc b/ziffers/__pycache__/common.cpython-311.pyc deleted file mode 100644 index b26c5e5..0000000 Binary files a/ziffers/__pycache__/common.cpython-311.pyc and /dev/null differ diff --git a/ziffers/__pycache__/defaults.cpython-311.pyc b/ziffers/__pycache__/defaults.cpython-311.pyc deleted file mode 100644 index b40e672..0000000 Binary files a/ziffers/__pycache__/defaults.cpython-311.pyc and /dev/null differ diff --git a/ziffers/__pycache__/ebnf.cpython-310.pyc b/ziffers/__pycache__/ebnf.cpython-310.pyc deleted file mode 100644 index b18163a..0000000 Binary files a/ziffers/__pycache__/ebnf.cpython-310.pyc and /dev/null differ diff --git a/ziffers/__pycache__/example.cpython-310.pyc b/ziffers/__pycache__/example.cpython-310.pyc deleted file mode 100644 index 31be1f4..0000000 Binary files a/ziffers/__pycache__/example.cpython-310.pyc and /dev/null differ diff --git a/ziffers/__pycache__/mapper.cpython-311.pyc b/ziffers/__pycache__/mapper.cpython-311.pyc deleted file mode 100644 index 853b10a..0000000 Binary files a/ziffers/__pycache__/mapper.cpython-311.pyc and /dev/null differ diff --git a/ziffers/__pycache__/parser.cpython-310.pyc b/ziffers/__pycache__/parser.cpython-310.pyc deleted file mode 100644 index 906f886..0000000 Binary files a/ziffers/__pycache__/parser.cpython-310.pyc and /dev/null differ diff --git a/ziffers/__pycache__/parser.cpython-311.pyc b/ziffers/__pycache__/parser.cpython-311.pyc deleted file mode 100644 index c59c69a..0000000 Binary files a/ziffers/__pycache__/parser.cpython-311.pyc and /dev/null differ diff --git a/ziffers/__pycache__/transformer.cpython-311.pyc b/ziffers/__pycache__/transformer.cpython-311.pyc deleted file mode 100644 index dc3dc3f..0000000 Binary files a/ziffers/__pycache__/transformer.cpython-311.pyc and /dev/null differ diff --git a/ziffers/classes.py b/ziffers/classes.py index 7d82b78..8c8175d 100644 --- a/ziffers/classes.py +++ b/ziffers/classes.py @@ -5,11 +5,15 @@ class Meta: text: str @dataclass -class Duration(Meta): +class DurationChange(Meta): dur: float @dataclass -class Octave(Meta): +class OctaveChange(Meta): + oct: int + +@dataclass +class OctaveMod(Meta): oct: int @dataclass @@ -25,7 +29,6 @@ class Pitch(Event): @dataclass class RandomPitch(Event): pc: int = None - @dataclass class Chord(Event): @@ -42,8 +45,10 @@ class Ziffers: text: str = None def __post_init__(self): self.text = self.collect_text() - def collect_text(self): + def collect_text(self) -> str: return "".join([val.text for val in self.values]) + def pcs(self) -> list[int]: + return [val.pc for val in self.values if type(val) is Pitch] @dataclass class Sequence(Meta): diff --git a/ziffers/mapper.py b/ziffers/mapper.py index 78b3e05..1721342 100644 --- a/ziffers/mapper.py +++ b/ziffers/mapper.py @@ -2,7 +2,6 @@ from lark import Transformer from .classes import * from .common import flatten from .defaults import default_durs -from collections import Counter class ZiffersTransformer(Transformer): @@ -28,11 +27,14 @@ class ZiffersTransformer(Transformer): def pc(self, s): if(len(s)>1): - counter = Counter() - for d in s: - counter.update(d) - result = dict(counter) - result["text"] = result["text"][::-1] + # Collect&sum prefixes from any order: _qee^s4 etc. + result = s[0] + for hash in s[1:]: + for key in hash.keys(): + if key in result: + result[key] = result[key] + hash[key] + else: + result[key] = hash[key] return Pitch(**result) else: val = s[0] @@ -46,7 +48,15 @@ class ZiffersTransformer(Transformer): def oct_change(self,s): octave = s[0] - return [Octave(oct=octave["oct"],text=octave["text"]),s[1]] + return [OctaveChange(oct=octave["oct"],text=octave["text"]),s[1]] + + def oct_mod(self,s): + octave = s[0] + return [OctaveMod(oct=octave["oct"],text=octave["text"]),s[1]] + + def escaped_octave(self,s): + value = s[0][1:-1] + return {"oct": int(value), "text":s[0].value} def octave(self,s): value = sum([1 if char=='^' else -1 for char in s[0].value]) @@ -57,14 +67,19 @@ class ZiffersTransformer(Transformer): def dur_change(self,s): duration = s[0] - return [Duration(dur=duration["dur"], text=duration["text"]),s[1]] + return [DurationChange(dur=duration["dur"], text=duration["text"]),s[1]] - def duration(self,s): + def escaped_decimal(self,s): + val = s[0] + val["text"] = "<"+val["text"]+">" + return val + + def duration_chars(self,s): durations = [val[1] for val in s] characters = "".join([val[0] for val in s]) - return {"dur": sum(durations), "text":characters[::-1]} + return {"dur": sum(durations), "text":characters} - def dur(self,s): + def dotted_dur(self,s): key = s[0] val = default_durs[key] dots = len(s)-1 @@ -72,6 +87,10 @@ class ZiffersTransformer(Transformer): val = val * (2.0-(1.0/(2*dots))) return [key+"."*dots,val] + def decimal(self,s): + val = s[0] + return {"dur": float(val),"text": val.value} + def dot(self,s): return "." diff --git a/ziffers/parser.py b/ziffers/parser.py index b131aaa..4862b59 100644 --- a/ziffers/parser.py +++ b/ziffers/parser.py @@ -9,13 +9,4 @@ grammar = grammar_path / "ziffers.lark" ziffers_parser = Lark.open(grammar, rel_to=__file__, start='value', parser='lalr', transformer=ZiffersTransformer()) def parse_expression(expr): - return ziffers_parser.parse(expr) - -if __name__ == '__main__': - print(ziffers_parser.parse("[1 [2 3]]")) - #print(ziffers_parser.parse("(1 (1,3) 1..3)")) - #print(ziffers_parser.parse("_^ q _qe^3 qww_4 _123 <1 2>")) - #print(ziffers_parser.parse("q _2 _ 3 ^ 343")) - #print(ziffers_parser.parse("2 qe2 e4").values) - #print(ziffers_parser.parse("q 2 <3 343>")) - #print(ziffers_parser.parse("q (2 <3 343 (3 4)>)")) + return ziffers_parser.parse(expr) \ No newline at end of file diff --git a/ziffers/ziffers.lark b/ziffers/ziffers.lark index 6fcd6d5..1b6c7bd 100644 --- a/ziffers/ziffers.lark +++ b/ziffers/ziffers.lark @@ -1,19 +1,23 @@ ?value: root - root: (pc | dur_change | oct_change | WS | chord | cycle | randompitch | range | list | subdivision)* + root: (pc | dur_change | oct_mod | oct_change | WS | chord | cycle | randompitch | range | list | subdivision)* list: "(" root ")" - randompitch: /[\(][-?0-9][,][-?0-9][\)]/ - range: /[-?0-9]\.\.[-?0-9]/ + randompitch: /\(-?[0-9],-?[0-9]\)/ + range: /-?[0-9]\.\.-?[0-9]/ cycle: "<" root ">" pc: prefix* pitch - pitch: /[-?0-9TE]/ - prefix: octave | duration - oct_change: octave WS + pitch: /-?[0-9TE]/ + prefix: (octave | duration_chars | escaped_decimal | escaped_octave) + oct_change: escaped_octave WS + escaped_octave: /<-?[0-9]>/ + oct_mod: octave WS octave: /[_^]+/ chord: pc pc+ - dur_change: duration WS - duration: dur+ - dur: dchar dot* + escaped_decimal: "<" decimal ">" + dur_change: (duration_chars | decimal) WS + duration_chars: dotted_dur+ + dotted_dur: dchar dot* + decimal: /-?[0-9]+\.[0-9]+/ dchar: /[mklpdcwyhnqaefsxtgujzo]/ dot: "." subitems: (pc | WS | chord | cycle | subdivision)*