import re from templates import GLSL_BLOCK_TEMPLATE from variables import VarSet, slugify VAR_PATTERN = re.compile(flags=re.VERBOSE, pattern=r''' # long form ${ ... } syntax \${ (?:\s*(?P(?: # optional type prefix ident # identifiers (always dynamic) | (?:(?:const|dynamic)\s+)? # optional const/dynamic modifiers (?:float|u?int) # base type | swizzle # swizzle mask | (?:i|u)?vecType # vector type (for mask) )):)? (?P[^{}]+) } | \$(?P\w+) # reference to captured variable | @(?P\w+) # reference to locally defined var ''') class FmtSpec(object): def __init__(self, ctype='ident_t', fmtstr='_%hx', wrap_expr=lambda name, expr: expr, fmt_expr=lambda name: name): self.ctype = ctype self.fmtstr = fmtstr self.wrap_expr = wrap_expr self.fmt_expr = fmt_expr @staticmethod def wrap_var(type, dynamic=False): if dynamic: return lambda name, expr: f'sh_var_{type}(sh, "{name}", {expr}, true)' else: return lambda name, expr: f'sh_const_{type}(sh, "{name}", {expr})' @staticmethod def wrap_fn(fn): return lambda name: f'{fn}({name})' VAR_TYPES = { # identifiers: get mapped as-is 'ident': FmtSpec(), # normal variables: get mapped as shader constants 'int': FmtSpec(wrap_expr=FmtSpec.wrap_var('int')), 'uint': FmtSpec(wrap_expr=FmtSpec.wrap_var('uint')), 'float': FmtSpec(wrap_expr=FmtSpec.wrap_var('float')), # constant variables: get printed directly into the source code 'const int': FmtSpec(ctype='int', fmtstr='%d'), 'const uint': FmtSpec(ctype='unsigned', fmtstr='uint(%u)'), 'const float': FmtSpec(ctype='float', fmtstr='float(%f)'), # dynamic variables: get loaded as shader variables 'dynamic int': FmtSpec(wrap_expr=FmtSpec.wrap_var('int', dynamic=True)), 'dynamic uint': FmtSpec(wrap_expr=FmtSpec.wrap_var('uint', dynamic=True)), 'dynamic float': FmtSpec(wrap_expr=FmtSpec.wrap_var('float', dynamic=True)), # component mask types 'swizzle': FmtSpec(ctype='uint8_t', fmtstr='%s', fmt_expr=FmtSpec.wrap_fn('sh_swizzle')), 'ivecType': FmtSpec(ctype='uint8_t', fmtstr='%s', fmt_expr=FmtSpec.wrap_fn('sh_float_type')), 'uvecType': FmtSpec(ctype='uint8_t', fmtstr='%s', fmt_expr=FmtSpec.wrap_fn('sh_float_type')), 'vecType': FmtSpec(ctype='uint8_t', fmtstr='%s', fmt_expr=FmtSpec.wrap_fn('sh_float_type')), } def stringify(value, strip): end = '\\n"' if strip: end = '"' value = re.sub(r'(?:\/\*[^\*]*\*\/|\/\/[^\n]+|^\s*)', '', value) return '"' + value.replace('\\', '\\\\').replace('"', '\\"') + end def commentify(value, strip): if strip: return '' return '/*' + value.replace('/*', '[[').replace('*/', ']]') + '*/' # Represents a statement + its enclosed variables class Statement(object): def __init__(self, linenr=0): super().__init__() self.linenr = linenr self.vars = VarSet() def add_var(self, ctype, expr, name=None): return self.vars.add_var(ctype, expr, name, self.linenr) def render(self): raise NotImplementedError @staticmethod def parse(text_orig, **kwargs): raise NotImplementedError # Represents a single line of GLSL class GLSLLine(Statement): class GLSLVar(object): # variable reference def __init__(self, fmt, var): self.fmt = fmt self.var = var def __init__(self, text, strip=False, **kwargs): super().__init__(**kwargs) self.refs = [] self.strip = strip # produce two versions of line, one for printf() and one for append() text = text.rstrip() self.rawstr = stringify(text, strip) self.fmtstr = stringify(re.sub(VAR_PATTERN, self.handle_var, text.replace('%', '%%')), strip) def handle_var(self, match): # local @var if match['var']: self.refs.append(match['var']) return '%d' # captured $var type = match['type'] name = match['name'] expr = match['expr'] or name name = name or slugify(expr) fmt = VAR_TYPES[type or 'ident'] self.refs.append(fmt.fmt_expr(self.add_var( ctype = fmt.ctype, expr = fmt.wrap_expr(name, expr), name = name, ))) if fmt.ctype == 'ident_t': return commentify(name, self.strip) + fmt.fmtstr else: return fmt.fmtstr # Represents an entire GLSL block class GLSLBlock(Statement): def __init__(self, line): super().__init__(linenr=line.linenr) self.lines = [] self.refs = [] self.append(line) def append(self, line): assert isinstance(line, GLSLLine) self.lines.append(line) self.refs += line.refs self.vars.merge(line.vars) def render(self): return GLSL_BLOCK_TEMPLATE.render(block=self) # Represents a statement which can either take a single line or a block class BlockStart(Statement): def __init__(self, multiline=False, **kwargs): super().__init__(**kwargs) self.multiline = multiline def add_brace(self, text): if self.multiline: text += ' {' return text # Represents an @if class IfCond(BlockStart): def __init__(self, cond, inner=False, **kwargs): super().__init__(**kwargs) self.cond = cond if inner else self.add_var('bool', expr=cond) def render(self): return self.add_brace(f'if ({self.cond})') # Represents an @else class Else(BlockStart): def __init__(self, closing, **kwargs): super().__init__(**kwargs) self.closing = closing def render(self): text = '} else' if self.closing else 'else' return self.add_brace(text) # Represents a normal (integer) @for loop, or an (unsigned 8-bit) bitmask loop class ForLoop(BlockStart): def __init__(self, var, op, bound, **kwargs): super().__init__(**kwargs) self.comps = op == ':' self.bound = self.add_var('uint8_t' if self.comps else 'int', expr=bound) self.var = var self.op = op def render(self): if self.comps: loopstart = f'uint8_t _mask = {self.bound}, {self.var}' loopcond = f'_mask && ({self.var} = __builtin_ctz(_mask), 1)' loopstep = f'_mask &= ~(1u << {self.var})' else: loopstart = f'int {self.var} = 0' loopcond = f'{self.var} {self.op} {self.bound}' loopstep = f'{self.var}++' return self.add_brace(f'for ({loopstart}; {loopcond}; {loopstep})') # Represents a @switch block class Switch(Statement): def __init__(self, expr, **kwargs): super().__init__(**kwargs) self.expr = self.add_var('unsigned', expr=expr) def render(self): return f'switch ({self.expr}) {{' # Represents a @case label class Case(Statement): def __init__(self, label, **kwargs): super().__init__(**kwargs) self.label = label def render(self): return f'case {self.label}:' # Represents a @default line class Default(Statement): def render(self): return 'default:' # Represents a @break line class Break(Statement): def render(self): return 'break;' # Represents a single closing brace class EndBrace(Statement): def render(self): return '}' # Shitty regex-based statement parser PATTERN_IF = re.compile(flags=re.VERBOSE, pattern=r''' @\s*if\s* # '@if' (?P@)? # optional leading @ \((?P.+)\)\s* # (condition) (?P{)?\s* # optional trailing { $''') PATTERN_ELSE = re.compile(flags=re.VERBOSE, pattern=r''' @\s*(?P})?\s* # optional leading } else\s* # 'else' (?P{)?\s* # optional trailing { $''') PATTERN_FOR = re.compile(flags=re.VERBOSE, pattern=r''' @\s*for\s+\( # '@for' ( (?P\w+)\s* # loop variable name (?P(?:\<=?|:))(?=[\w\s])\s* # '<', '<=' or ':', followed by \s or \w (?P[^\s].*)\s* # loop boundary expression \)\s*(?P{)?\s* # ) and optional trailing { $''') PATTERN_SWITCH = re.compile(flags=re.VERBOSE, pattern=r''' @\s*switch\s* # '@switch' \((?P.+)\)\s*{ # switch expression $''') PATTERN_CASE = re.compile(flags=re.VERBOSE, pattern=r''' @\s*case\s* # '@case' (?P