Trying out larl parser
This commit is contained in:
@ -1 +1,5 @@
|
||||
from .parser import *
|
||||
from .mapper import *
|
||||
from .classes import *
|
||||
from .common import *
|
||||
from .defaults import *
|
||||
|
||||
BIN
ziffers/__pycache__/ZiffersData.cpython-311.pyc
Normal file
BIN
ziffers/__pycache__/ZiffersData.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ziffers/__pycache__/ZiffersTransformer.cpython-311.pyc
Normal file
BIN
ziffers/__pycache__/ZiffersTransformer.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ziffers/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
ziffers/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ziffers/__pycache__/classes.cpython-311.pyc
Normal file
BIN
ziffers/__pycache__/classes.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ziffers/__pycache__/common.cpython-311.pyc
Normal file
BIN
ziffers/__pycache__/common.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ziffers/__pycache__/defaults.cpython-311.pyc
Normal file
BIN
ziffers/__pycache__/defaults.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ziffers/__pycache__/mapper.cpython-311.pyc
Normal file
BIN
ziffers/__pycache__/mapper.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ziffers/__pycache__/parser.cpython-311.pyc
Normal file
BIN
ziffers/__pycache__/parser.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ziffers/__pycache__/transformer.cpython-311.pyc
Normal file
BIN
ziffers/__pycache__/transformer.cpython-311.pyc
Normal file
Binary file not shown.
68
ziffers/classes.py
Normal file
68
ziffers/classes.py
Normal file
@ -0,0 +1,68 @@
|
||||
from dataclasses import dataclass, asdict
|
||||
|
||||
@dataclass
|
||||
class Meta:
|
||||
text: str
|
||||
|
||||
@dataclass
|
||||
class Duration(Meta):
|
||||
dur: float
|
||||
|
||||
@dataclass
|
||||
class Octave(Meta):
|
||||
oct: int
|
||||
|
||||
@dataclass
|
||||
class Event(Meta):
|
||||
dur: float = None
|
||||
|
||||
@dataclass
|
||||
class Pitch(Event):
|
||||
pc: int = None
|
||||
dur: float = None
|
||||
oct: int = None
|
||||
|
||||
@dataclass
|
||||
class RandomPitch(Event):
|
||||
pc: int = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Chord(Event):
|
||||
pcs: list[Pitch] = None
|
||||
|
||||
@dataclass
|
||||
class Function(Event):
|
||||
run: str = None
|
||||
|
||||
@dataclass
|
||||
class Ziffers:
|
||||
values: list[Event]
|
||||
dict = asdict
|
||||
text: str = None
|
||||
def __post_init__(self):
|
||||
self.text = self.collect_text()
|
||||
def collect_text(self):
|
||||
return "".join([val.text for val in self.values])
|
||||
|
||||
@dataclass
|
||||
class Sequence(Meta):
|
||||
values: list[Event]
|
||||
|
||||
@dataclass
|
||||
class Subdivision(Meta):
|
||||
values: list[Event]
|
||||
|
||||
@dataclass
|
||||
class Cyclic(Sequence):
|
||||
cycle: int = 0
|
||||
|
||||
@dataclass
|
||||
class RandomPitch(Meta):
|
||||
min: int
|
||||
max: int
|
||||
|
||||
@dataclass
|
||||
class Range(Meta):
|
||||
start: int
|
||||
end: int
|
||||
2
ziffers/common.py
Normal file
2
ziffers/common.py
Normal file
@ -0,0 +1,2 @@
|
||||
def flatten(arr) -> list:
|
||||
return flatten(arr[0]) + (flatten(arr[1:]) if len(arr) > 1 else []) if type(arr) is list else [arr]
|
||||
37
ziffers/defaults.py
Normal file
37
ziffers/defaults.py
Normal file
@ -0,0 +1,37 @@
|
||||
default_durs = {
|
||||
'm': 8.0, # 15360/1920
|
||||
'k': 10240/1920, # ~5.333
|
||||
'l': 4.0, # 7680/1920
|
||||
'd.': 3.0, #
|
||||
'p': 5120/1920, # ~2.666
|
||||
'd': 2.0, # 3840/1920
|
||||
'w.': 1.5, # 2280/1920
|
||||
'c': 2560/1920, # ~1.333
|
||||
'w': 1.0, # 1920/1920
|
||||
'h..': 0.875, # 1680/1920
|
||||
'h.': 0.75, # 1440/1920
|
||||
'y': 1280/1920, # ~0.666
|
||||
'h': 0.5, # 960/1920 - 1/2
|
||||
'q..': 840/1920, # ~0.4375
|
||||
'q.': 0.375, # 720/1920
|
||||
'n': 640/1920, # ~0.333
|
||||
'q': 0.25, # 480/1920 - 1/4
|
||||
'e..': 420/1920, # = 0.218
|
||||
'e.': 0.1875, # 360/1920
|
||||
'a': 320/1920, # 0.167 - 1/8
|
||||
'e': 0.125, # 240/1920
|
||||
's..': 210/1920, # ~0.10937
|
||||
's.': 180/1920, # ~0.0937
|
||||
'f': 160/1920, # ~0.083 - 1/16
|
||||
's': 0.0625, # 120/1920
|
||||
't..': 105/1920, # ~0.0546
|
||||
't.': 90/1920, # ~0.0468
|
||||
'x': 80/1920, # ~0.042 - 1/32
|
||||
't': 60/1920, # ~0.031
|
||||
'u.': 45/1920, # ~0.023
|
||||
'g': 40/1920, # ~0.021 - 1/64
|
||||
'u': 30/1920, # ~0.016
|
||||
'j': 15/1920, # ~0.0078 - 1/128
|
||||
'o': 8/1920, # ~0.00416
|
||||
'z': 0.0 # 0
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
|
||||
ebnf = r"""
|
||||
ziffers = (bar / octup / octdown / subdiv / escape / rhythm / float / chord / pc / ws?)+
|
||||
|
||||
escape = (lt (chord / pc) gt)
|
||||
subdiv = (lbra ziffers rbra)
|
||||
|
||||
chord = pc{2,}
|
||||
pc = (neg_pc / pc_basic)
|
||||
neg_pc = (~r"-" pc)
|
||||
pc_basic = ~r"[0-9TE]"
|
||||
|
||||
rhythm = ~r"[mklpdcwyhnqaefsxtgujoz]"
|
||||
|
||||
float = ~r"\d+\.\d+"
|
||||
|
||||
lpar = "("
|
||||
rpar = ")"
|
||||
lbra = "["
|
||||
rbra = "]"
|
||||
lcbra = "{"
|
||||
rcbra = "}"
|
||||
lt = "<"
|
||||
gt = ">"
|
||||
|
||||
|
||||
octup = "^"
|
||||
octdown = "_"
|
||||
|
||||
bar = "|"
|
||||
|
||||
plus = "+"
|
||||
minus = "-"
|
||||
times = "*"
|
||||
div = "/"
|
||||
|
||||
emptyline = ws+
|
||||
comma = ","
|
||||
ws = ~"\s*"
|
||||
"""
|
||||
90
ziffers/mapper.py
Normal file
90
ziffers/mapper.py
Normal file
@ -0,0 +1,90 @@
|
||||
from lark import Lark, Transformer
|
||||
from classes import *
|
||||
from common import flatten
|
||||
from defaults import default_durs
|
||||
from collections import Counter
|
||||
|
||||
class ZiffersTransformer(Transformer):
|
||||
|
||||
def root(self, items):
|
||||
return Ziffers(flatten(items))
|
||||
|
||||
def list(self,items):
|
||||
values = flatten(items[0].values)
|
||||
return Sequence(values=values,text="("+"".join([val.text for val in values])+")")
|
||||
|
||||
def randompitch(self,s):
|
||||
val = s[0][1:-1].split(",")
|
||||
return RandomPitch(min=val[0],max=val[1],text=s[0])
|
||||
|
||||
def range(self,s):
|
||||
val = s[0].split("..")
|
||||
return Range(start=val[0],end=val[1],text=s[0])
|
||||
|
||||
def cycle(self, items):
|
||||
values = items[0].values
|
||||
no_spaces = [val for val in values if type(val)!=Meta]
|
||||
return Cyclic(values=no_spaces,text="<"+"".join([val.text for val in values])+">")
|
||||
|
||||
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]
|
||||
return Pitch(**result)
|
||||
else:
|
||||
val = s[0]
|
||||
return Pitch(**val)
|
||||
|
||||
def pitch(self,s):
|
||||
return {"pc":int(s[0].value),"text":s[0].value}
|
||||
|
||||
def prefix(self,s):
|
||||
return s[0]
|
||||
|
||||
def oct_change(self,s):
|
||||
octave = s[0]
|
||||
return [Octave(oct=octave["oct"],text=octave["text"]),s[1]]
|
||||
|
||||
def octave(self,s):
|
||||
value = sum([1 if char=='^' else -1 for char in s[0].value])
|
||||
return {"oct": value, "text":s[0].value}
|
||||
|
||||
def chord(self,s):
|
||||
return Chord(pcs=s,text="".join([val.text for val in s]))
|
||||
|
||||
def dur_change(self,s):
|
||||
duration = s[0]
|
||||
return [Duration(dur=duration["dur"], text=duration["text"]),s[1]]
|
||||
|
||||
def duration(self,s):
|
||||
durations = [val[1] for val in s]
|
||||
characters = "".join([val[0] for val in s])
|
||||
return {"dur": sum(durations), "text":characters[::-1]}
|
||||
|
||||
def dur(self,s):
|
||||
key = s[0]
|
||||
val = default_durs[key]
|
||||
dots = len(s)-1
|
||||
if(dots>1):
|
||||
val = val * (2.0-(1.0/(2*dots)))
|
||||
return [key+"."*dots,val]
|
||||
|
||||
def dot(self,s):
|
||||
return "."
|
||||
|
||||
def dchar(self,s):
|
||||
chardur = s[0].value
|
||||
return chardur
|
||||
|
||||
def WS(self,s):
|
||||
return Meta(text=s[0])
|
||||
|
||||
def subdivision(self,items):
|
||||
values = flatten(items[0])
|
||||
return Subdivision(values=values,text="["+"".join([val.text for val in values])+"]")
|
||||
|
||||
def subitems(self,s):
|
||||
return s
|
||||
@ -1,57 +1,21 @@
|
||||
from parsimonious.grammar import Grammar
|
||||
from parsimonious.nodes import NodeVisitor
|
||||
from rich import print
|
||||
from .ebnf import ebnf
|
||||
from mapper import *
|
||||
from pathlib import Path
|
||||
from lark import Lark
|
||||
|
||||
__all__ = ('ZiffersVisitor', 'parse_expression',)
|
||||
grammar_path = Path(__file__).parent
|
||||
grammar = grammar_path / "ziffers.lark"
|
||||
|
||||
GRAMMAR = Grammar(ebnf)
|
||||
ziffers_parser = Lark.open(grammar, rel_to=__file__, start='value', parser='lalr', transformer=ZiffersTransformer())
|
||||
|
||||
class ZiffersVisitor(NodeVisitor):
|
||||
|
||||
"""
|
||||
Visitor for the Ziffers syntax.
|
||||
"""
|
||||
|
||||
def visit_ziffers(self, node, children):
|
||||
"""
|
||||
Top-level visiter. Will traverse and build something out of a complete and valid
|
||||
Ziffers expression.
|
||||
"""
|
||||
print(f"Node: {node}, Children: {children}")
|
||||
result = {'ziffers': []}
|
||||
|
||||
for i in children:
|
||||
if i[0] in (None, [], {}) and isinstance(i[0], dict):
|
||||
continue
|
||||
try:
|
||||
result['ziffers'].append(i[0])
|
||||
except Exception as e:
|
||||
print(f"[red]Error in ziffers:[/red] {e}")
|
||||
pass
|
||||
return result
|
||||
|
||||
def visit_pc(self, node, children):
|
||||
return {node.expr_name: node.text}
|
||||
|
||||
def visit_escape(self, node, children):
|
||||
return {node.expr_name: node.text}
|
||||
|
||||
# def visit_subdiv(self, node, visited_children):
|
||||
# key, values = visited_children
|
||||
# ret)rn {key, dict(values)}
|
||||
|
||||
def generic_visit(self, node, children):
|
||||
"""
|
||||
This method seem to be the generic method to include in any NodeVisitor.
|
||||
Probably better to keep it as is for the moment. This is appending a whole
|
||||
lot of garbage to the final expression because I don't really know how to
|
||||
write it properly...
|
||||
"""
|
||||
return children or node
|
||||
|
||||
def parse_expression(expression: str) -> dict:
|
||||
tree = GRAMMAR.parse(expression)
|
||||
visitor = ZiffersVisitor()
|
||||
return visitor.visit(tree)
|
||||
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)>)"))
|
||||
|
||||
23
ziffers/ziffers.lark
Normal file
23
ziffers/ziffers.lark
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
?value: root
|
||||
root: (pc | dur_change | oct_change | WS | chord | cycle | randompitch | range | list | subdivision)*
|
||||
list: "(" root ")"
|
||||
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
|
||||
octave: /[_^]+/
|
||||
chord: pc pc+
|
||||
dur_change: duration WS
|
||||
duration: dur+
|
||||
dur: dchar dot*
|
||||
dchar: /[mklpdcwyhnqaefsxtgujzo]/
|
||||
dot: "."
|
||||
subitems: (pc | WS | chord | cycle | subdivision)*
|
||||
subdivision: "[" subitems "]"
|
||||
|
||||
%import common.SIGNED_NUMBER
|
||||
%import common.WS
|
||||
Reference in New Issue
Block a user