Source code for pySSV.ssv_pragma_parser

#  Copyright (c) 2023-2024 Thomas Mathieson.
#  Distributed under the terms of the MIT license.
import argparse
import sys
from typing import Optional, List, Dict

import pcpp  # type: ignore

from .ssv_logging import log
from .ssv_shader_args_tokenizer import SSVShaderArgsTokenizer


[docs] class SSVTemplatePragmaData(argparse.Namespace): command: str # Define/Arg name: str author: Optional[str] = None description: Optional[str] = None # Stage shader_stage: Optional[List[str]] = None # Arg # name: str = None non_positional: bool = False action: Optional[str] = None default: Optional[str] = None choices: Optional[List[str]] = None const: Optional[str] = None # description: list[str] = None # input_primitive primitive_type: Optional[str] = None def __init__(self, **kwargs): super().__init__(**kwargs)
[docs] class SSVShaderPragmaData(argparse.Namespace): template: str args: List[str] = [] def __init__(self, **kwargs): super().__init__(**kwargs)
# noinspection PyShadowingBuiltins
[docs] class SSVTemplatePragmaParser(pcpp.Preprocessor): """ This class is responsible for parsing #pragma definitions in SSV shader templates. Refer to :ref:`writing-shader-templates` for details on writing shader templates. """ def __init__(self): super(SSVTemplatePragmaParser, self).__init__() # Template pragma parser self._pragma_parse = argparse.ArgumentParser(prog="SSV Shader Preprocessor") sub_parsers = self._pragma_parse.add_subparsers(dest="command") define_parser = sub_parsers.add_parser("define", help="Defines a new SSV shader template") define_parser.add_argument("name", type=str, help="The name the shader template. The template's filename should be in the form: " "'template_<name>.glsl'.") define_parser.add_argument("--author", "-a", type=str, help="The shader template's author.") define_parser.add_argument("--description", "-d", type=str, help="A brief description of the shader template and what it does.") stage_parser = sub_parsers.add_parser("stage", help="Specifies a shader stage to compile this template for") stage_parser.add_argument("shader_stage", choices=["vertex", "fragment", "tess_control", "tess_evaluation", "geometry", "compute"], nargs="+") arg_parser = sub_parsers.add_parser("arg", help="Defines an argument to be passed into this shader template when " "evaluating it. Arguments are passed into the shader as compiler " "defines; the names of the arguments are transformed to uppercase and " "prefixed with 'T_'") arg_parser.add_argument("name", type=str, help="The name of the argument to be passed in to the shader; prefixing the name with " "an underscore implies the '--non_positional' flag") arg_parser.add_argument("--non_positional", "-n", action="store_true", help="Treat this as a non-positional argument; it's name is automatically prefixed " "with '--'") # arg_parser.add_argument("--type", "-t", type=str, help="") arg_parser.add_argument("--action", "-a", default="store", choices=["store", "store_const", "store_true", "store_false"], help="What to do when this argument is encountered. See the argparse docs for details " "on the different actions: " "https://docs.python.org/3.11/library/argparse.html#action") arg_parser.add_argument("--default", type=str, help="The default value for this argument if it isn't specified") arg_parser.add_argument("--choices", "-c", type=str, nargs="+", action="extend", help="Limits the valid values of this argument to those specified here") arg_parser.add_argument("--const", type=str, help="When using the 'store_const' action, specifies what value to store") arg_parser.add_argument("--description", "-d", help="A brief description of the argument and the value it expects. Note that for " "implementation reasons the description can't contain dashes.") input_primitive_parser = sub_parsers.add_parser("input_primitive", help="Specifies what primitive type the vertex data should be " "treated as.") input_primitive_parser.add_argument("primitive_type", choices=["POINTS", "LINES", "TRIANGLES"], default="TRIANGLES") self._pragma_args = []
[docs] def on_include_not_found(self, is_malformed, is_system_include, curdir, includepath): """ *Used internally by the parser.* """ raise pcpp.OutputDirective(pcpp.Action.IgnoreAndPassThrough)
[docs] def on_error(self, file, line, msg): """ *Used internally by the parser.* """ log(f"[{file}:{line}] {msg}") raise ValueError(f"[{file}:{line}] {msg}")
[docs] def on_directive_unknown(self, directive, toks, ifpassthru, precedingtoks): """ *Used internally by the parser.* """ if directive.value != "pragma" or len(toks) <= 2: return super(SSVTemplatePragmaParser, self).on_directive_unknown(directive, toks, ifpassthru, precedingtoks) if toks[0].value == "SSVTemplate": # print(f"Found SSV pragma: {SSVShaderArgsTokenizer.correct_tokens(toks[2:], self)}") self._pragma_args.append(SSVShaderArgsTokenizer.correct_tokens(toks[2:], self)) # else: # log(f"[{directive.source}:{directive.lineno}] Unrecognised #pragma directive: {''.join(toks)}", # severity=logging.DEBUG) return True
[docs] def parse(self, input, source=None, ignore=None) -> Dict[str, List[SSVTemplatePragmaData]]: """ Parses the #pragma directives of a shader template. :param input: the source of the shader template. :param source: the path to the source file. :param ignore: :return: a dictionary of parsed shader template commands. """ if ignore is None: ignore = {} self._pragma_args = [] # Set up the parser super(SSVTemplatePragmaParser, self).parse(input, source, ignore) # Parse all tokens while self.token(): pass args = [self._pragma_parse.parse_args(args, namespace=SSVTemplatePragmaData()) for args in self._pragma_args] # Group arguments into a dictionary args_dict: Dict[str, List[SSVTemplatePragmaData]] = {} for arg in args: if arg.command in args_dict: args_dict[arg.command].append(arg) else: args_dict[arg.command] = [arg] return args_dict
[docs] def write(self, oh=sys.stdout): """ The pragma parser only parses #pragma arguments, it should not be used to preprocess shader files. :param oh: unused """ raise NotImplementedError("The SSV pragma parser is strictly read only.")
[docs] class SSVShaderPragmaParser(pcpp.Preprocessor): """ This class is responsible for parsing #pragma definitions in SSV shaders. """ def __init__(self): super(SSVShaderPragmaParser, self).__init__() # Template pragma parser self._pragma_parse = argparse.ArgumentParser(prog="SSV Shader Preprocessor", prefix_chars="`") self._pragma_parse.add_argument("template", type=str) self._pragma_parse.add_argument("args", type=str, nargs="*") self._pragma_args = None
[docs] def on_directive_unknown(self, directive, toks, ifpassthru, precedingtoks): """ *Used internally by the parser.* """ if directive.value != "pragma" or len(toks) <= 2: return super(SSVShaderPragmaParser, self).on_directive_unknown(directive, toks, ifpassthru, precedingtoks) if toks[0].value == "SSV": # log(f"Found SSV pragma: {''.join(tok_strs)}") if self._pragma_args is not None: raise ValueError("Shader contains multiple shader template pragma directives! Only one is allowed.") self._pragma_args = SSVShaderArgsTokenizer.correct_tokens(toks[2:], self) # else: # log(f"[{directive.source}:{directive.lineno}] Unrecognised #pragma directive: {''.join([t.value for t in toks])}", # severity=logging.DEBUG) return True
[docs] def parse(self, input, source=None, ignore=None) -> SSVShaderPragmaData: """ Parses the #pragma directives of a shader. :param input: the source of the shader. :param source: the path to the source file. :param ignore: :return: an object of parsed shader template info. """ if ignore is None: ignore = {} self._pragma_args = None # Set up the parser super(SSVShaderPragmaParser, self).parse(input, source, ignore) # Parse all tokens while self.token(): pass if self._pragma_args is None: raise ValueError( "Shader does not use a shader template! Did you remember to add #pragma SSV [...] to your shader?") return self._pragma_parse.parse_args(self._pragma_args, namespace=SSVShaderPragmaData())
[docs] def write(self, oh=sys.stdout): """ The pragma parser only parses #pragma arguments, it should not be used to preprocess shader files. :param oh: unused """ raise NotImplementedError("The SSV pragma parser is strictly read only.")