# Root: Handles empty arrays, loose lists, or coordinate blocks root ::= (block_sequence | empty_scene)? ws? empty_scene ::= "[]" block_sequence ::= block (ws_or_nl* block)* # A block is a raw tag name or a fully positioned layout primitive block ::= primitive (anchor? vector)? primitive ::= "[" [a-zA-Z0-9_-]+ "]" anchor ::= "[abs]" | "[@" [0-9]+ ("," ws? "@" [0-9]+)* "]" # Vector strings support flat numbers, expressions, nested matrices, or config parameters # Enforces a valid item before and after every comma to break on double commas ,, vector ::= "[" value ("," ws? value)* "]" value ::= nested_array | parameter_assignment | expression | attribute | signed_num | boolean_flag | variable # Captures matrices like [1,1,1.5] or [-0.6,0.6,-0.4] cleanly nested_array ::= "[" value ("," ws? value)* "]" # Captures both taper=(z,0.5) and functional variations like taper(z,0.7) or noise(0.4) parameter_assignment ::= [a-zA-Z_-]+ "=(" ws? [xyz] ws? "," ws? signed_num ws? ")" | [a-zA-Z_-]+ "(" ws? [xyz] ws? "," ws? num ws? ")" | [a-zA-Z_-]+ "(" num ")" # Expressions handle explicit signs directly adjacent to implicit multi-variables expression ::= term (op term)* # Fully parses 'fw * 4', implicit coefficients like '-4fw', '-4fd', or solo variables # FIX: Added sign? variable to cleanly handle prefixed variables like -fd or -hh term ::= variable ("*" (variable | signed_num))? | signed_num variable? | sign? variable | "sym(" ws? expression ws? ")" variable ::= "fw" | "hw" | "fd" | "hd" | "fh" | "hh" | "bd" | "bh" | "@idx" | "@" [0-9]+ boolean_flag ::= "surf" | "-%" # Layout modifier keywords or attributes attribute ::= "noise=" signed_num | "mesh" | "surf" | "-%" # STRICT number definition - only one sign character allowed total positive_num ::= [0-9]+ ("." [0-9]+)? negative_num ::= "-" [0-9]+ ("." [0-9]+)? num ::= positive_num | negative_num # FIX: Changed to sign? positive_num to allow things like +5 or -5 without doubling up a negative_num's minus sign signed_num ::= sign? positive_num | negative_num # SINGLE sign only - completely kills any '--' variants instantly sign ::= "+" | "-" op ::= "+" | "-" | "*" | "/" ws ::= [ \t]+ ws_or_nl ::= [ \t\r\n]+