151 using uint64_t = std::uint64_t;
152 using report_callback = std::function<void(
const std::smatch &,
const char *)>;
159 std::vector<SharedVar> shared_vars_;
176 if (filename.find(
".msl") != std::string::npos) {
179 if (filename.find(
".glsl") != std::string::npos) {
182 if (filename.find(
".hh") != std::string::npos) {
191 const std::string &filename,
192 bool do_parse_function,
193 bool do_small_type_linting,
194 report_callback report_error,
198 report_error(std::smatch(),
"Unknown file type");
201 str = remove_comments(
str, report_error);
202 threadgroup_variables_parsing(
str);
203 parse_builtins(
str, filename);
205 if (do_parse_function) {
206 parse_library_functions(
str);
209 include_parse(
str, report_error);
210 pragma_once_linting(
str, filename, report_error);
212 str = preprocessor_directive_mutation(
str);
213 str = swizzle_function_mutation(
str);
215 str = loop_unroll(
str, report_error);
216 str = assert_processing(
str, filename);
217 static_strings_parsing(
str);
218 str = static_strings_mutation(
str);
219 str = printf_processing(
str, report_error);
220 quote_linting(
str, report_error);
222 global_scope_constant_linting(
str, report_error);
223 matrix_constructor_linting(
str, report_error);
224 array_constructor_linting(
str, report_error);
225 if (do_small_type_linting) {
226 small_type_linting(
str, report_error);
230 str = using_mutation(
str, report_error);
231 str = namespace_mutation(
str, report_error);
232 str = namespace_separator_mutation(
str);
234 str = enum_macro_injection(
str);
235 str = default_argument_mutation(
str);
236 str = argument_reference_mutation(
str);
237 str = variable_reference_mutation(
str, report_error);
238 str = template_definition_mutation(
str, report_error);
239 str = template_call_mutation(
str);
242 if (language ==
GLSL) {
243 str = matrix_constructor_mutation(
str);
246 str = argument_decorator_macro_injection(
str);
247 str = array_constructor_macro_injection(
str);
248 r_metadata = metadata;
249 return line_directive_prefix(filename) +
str + threadgroup_variables_suffix();
255 auto no_err_report = [](std::smatch,
const char *) {};
257 return process(
GLSL,
str,
"",
false,
false, no_err_report, unused);
261 using regex_callback = std::function<void(
const std::smatch &)>;
264 void regex_global_search(
const std::string &
str,
265 const std::regex ®ex,
266 regex_callback callback)
269 string::const_iterator it =
str.begin();
270 for (smatch match; regex_search(it,
str.end(), match, regex); it = match.suffix().first) {
275 template<
typename ReportErrorF>
276 std::string remove_comments(
const std::string &
str,
const ReportErrorF &report_error)
278 std::string out_str =
str;
281 size_t start, end = 0;
282 while ((start = out_str.find(
"/*", end)) != std::string::npos) {
283 end = out_str.find(
"*/", start + 2);
284 if (end == std::string::npos) {
287 for (
size_t i = start;
i < end + 2; ++
i) {
288 if (out_str[
i] !=
'\n') {
294 if (end == std::string::npos) {
296 report_error(std::smatch(),
"Malformed multi-line comment.");
302 size_t start, end = 0;
303 while ((start = out_str.find(
"//", end)) != std::string::npos) {
304 end = out_str.find(
'\n', start + 2);
305 if (end == std::string::npos) {
308 for (
size_t i = start;
i < end; ++
i) {
313 if (end == std::string::npos) {
315 report_error(std::smatch(),
"Malformed single line comment, missing newline.");
320 std::regex regex(R
"((\ )*?\n)");
321 return std::regex_replace(out_str, regex,
"\n");
324 std::string template_definition_mutation(
const std::string &
str, report_callback &report_error)
326 if (
str.find(
"template") == std::string::npos) {
330 std::string out_str =
str;
333 std::regex regex(R
"(template<([\w\d\n\,\ ]+)>(\s\w+\s)(\w+)\()");
334 out_str = std::regex_replace(out_str, regex, "#define $3_TEMPLATE($1)$2$3@(");
338 size_t start, end = 0;
339 while ((start = out_str.find(
"_TEMPLATE(", end)) != std::string::npos) {
341 end = out_str.find(
")", start);
342 std::string arg_list = out_str.substr(start, end - start);
343 arg_list = std::regex_replace(arg_list, std::regex(R
"(\w+ (\w+))"), "$1");
344 out_str.replace(start, end - start, arg_list);
347 out_str.substr(start),
'{',
'}');
348 if (template_body.empty()) {
353 "Template function declaration is missing closing bracket or has empty body.");
356 size_t body_end = out_str.find(
'{', start) + 1 + template_body.size();
358 std::string macro_body = out_str.substr(start, body_end - start);
360 macro_body = std::regex_replace(macro_body, std::regex(R
"(\n)"), " \\\n");
366 macro_body.substr(10 + macro_args.length() + 1),
'(',
')');
368 macro_args = std::regex_replace(macro_args, std::regex(R
"(\s)"), "");
369 std::vector<std::string> macro_args_split =
split_string(macro_args,
',');
371 std::string fn_name_suffix =
"_";
372 bool all_args_in_function_signature =
true;
373 for (std::string macro_arg : macro_args_split) {
374 fn_name_suffix +=
"##" + macro_arg +
"##_";
376 if (std::regex_search(fn_args, std::regex(R
"(\b)" + macro_arg + R"(\b)")) == false) {
377 all_args_in_function_signature =
false;
380 if (all_args_in_function_signature) {
385 size_t end_of_fn_name = macro_body.find(
"@");
386 macro_body.replace(end_of_fn_name, 1, fn_name_suffix);
388 out_str.replace(start, body_end - start, macro_body);
394 std::regex regex_instance(R
"(template \w+ (\w+)<([\w+\,\ \n]+)>\(([\w+\ \,\n]+)\);)");
397 out_str = std::regex_replace(out_str, regex_instance,
"$1_TEMPLATE($2)/*$3*/");
401 if (out_str.find(
"template<") != std::string::npos) {
402 std::regex regex_declaration(R
"(\btemplate<)");
403 regex_global_search(out_str, regex_declaration, [&](const std::smatch &match) {
404 report_error(match,
"Template declaration unsupported syntax");
407 if (out_str.find(
"template ") != std::string::npos) {
408 std::regex regex_instance(R
"(\btemplate )");
409 regex_global_search(out_str, regex_instance, [&](const std::smatch &match) {
410 report_error(match,
"Template instantiation unsupported syntax");
417 std::string template_call_mutation(std::string &
str)
421 if (std::regex_search(
str, match, std::regex(R
"(([\w\d]+)<([\w\d\n, ]+)>)")) == false) {
424 const std::string template_name = match[1].str();
425 const std::string template_args = match[2].str();
427 std::string replacement =
"TEMPLATE_GLUE" +
428 std::to_string(
char_count(template_args,
',') + 1) +
"(" +
429 template_name +
", " + template_args +
")";
436 std::string remove_quotes(
const std::string &
str)
438 return std::regex_replace(
str, std::regex(R
"(["'])"), " ");
441 void include_parse(
const std::string &
str, report_callback report_error)
444 std::regex regex(R
"(#(\s*)include\s*\"(\w+\.\w+)\")");
446 regex_global_search(str, regex, [&](const std::smatch &match) {
447 std::string indent = match[1].str();
449 if (!indent.empty()) {
450 report_error(match,
"#include directives must not be inside #if clause");
452 std::string dependency_name = match[2].str();
454 if (dependency_name ==
"gpu_glsl_cpp_stubs.hh") {
458 if (dependency_name.find(
"info.hh") != std::string::npos) {
462 metadata.dependencies.emplace_back(dependency_name);
466 void pragma_once_linting(
const std::string &
str,
467 const std::string &filename,
468 report_callback report_error)
470 if (filename.find(
"_lib.") == std::string::npos) {
473 if (
str.find(
"\n#pragma once") == std::string::npos) {
475 report_error(match,
"Library files must contain #pragma once directive.");
479 std::string loop_unroll(
const std::string &
str, report_callback report_error)
481 if (
str.find(
"[[gpu::unroll") == std::string::npos) {
487 std::string definition;
491 std::string init_statement;
493 std::string test_statement;
495 std::string iter_statement;
497 std::string body_prefix;
510 std::vector<Loop> loops;
512 auto add_loop = [&](Loop &loop,
513 const std::smatch &match,
516 std::string suffix = match.suffix().str();
518 loop.body =
'{' + loop.body +
'}';
519 loop.definition_line = line - lines_in_content;
520 loop.body_line = line;
521 loop.end_line = loop.body_line +
line_count(loop.body);
524 if (loop.body.find(
" break;") != std::string::npos ||
525 loop.body.find(
" continue;") != std::string::npos)
529 std::string modified_body = loop.body;
531 std::regex regex_loop(R
"( (for|while|do) )");
532 regex_global_search(loop.body, regex_loop, [&](const std::smatch &match) {
533 std::string inner_scope = get_content_between_balanced_pair(match.suffix(),
'{',
'}');
534 replace_all(modified_body, inner_scope,
"");
538 if (modified_body.find(
" continue;") != std::string::npos) {
539 report_error(match,
"Error: Unrolled loop cannot contain \"continue\" statement.");
542 std::regex regex_switch(R
"( switch )");
543 regex_global_search(loop.body, regex_switch, [&](const std::smatch &match) {
544 std::string inner_scope = get_content_between_balanced_pair(match.suffix(),
'{',
'}');
545 replace_all(modified_body, inner_scope,
"");
549 if (modified_body.find(
" break;") != std::string::npos) {
550 report_error(match,
"Error: Unrolled loop cannot contain \"break\" statement.");
553 loops.emplace_back(loop);
559 std::regex regex(R
"(( *))"
560 R"(\[\[gpu::unroll\]\])"
562 R"(\s*((?:uint|int)\s+(\w+)\s+=\s+(-?\d+));)"
563 R
"(\s*((\w+)\s+(>|<)(=?)\s+(-?\d+)))"
564 R
"(\s*(?:&&)?\s*([^;)]+)?;)"
565 R
"(\s*(((\w+)(\+\+|\-\-))[^\)]*))"
570 regex_global_search(str, regex, [&](const std::smatch &match) {
571 std::string counter_1 = match[3].str();
572 std::string counter_2 = match[6].str();
573 std::string counter_3 = match[13].str();
575 std::string content = match[0].str();
578 line +=
line_count(match.prefix().str()) + lines_in_content;
580 if ((counter_1 != counter_2) || (counter_1 != counter_3)) {
581 report_error(match,
"Error: Non matching loop counter variable.");
590 loop.iter_count = std::abs(end -
init);
592 std::string condition = match[7].str();
593 if (condition.empty()) {
594 report_error(match,
"Error: Unsupported condition in unrolled loop.");
597 std::string
equal = match[8].str();
599 loop.iter_count += 1;
602 std::string iter = match[14].str();
604 if (condition ==
">") {
605 report_error(match,
"Error: Unsupported condition in unrolled loop.");
608 else if (iter ==
"--") {
609 if (condition ==
"<") {
610 report_error(match,
"Error: Unsupported condition in unrolled loop.");
614 report_error(match,
"Error: Unsupported for loop expression. Expecting ++ or --");
617 loop.definition = content;
618 loop.indent = match[1].str();
619 loop.init_statement = match[2].str();
620 if (!match[10].
str().empty()) {
621 loop.test_statement =
"if (" + match[10].str() +
") ";
623 loop.iter_statement = match[11].str();
624 loop.body_prefix = match[15].str();
626 add_loop(loop, match, line, lines_in_content);
631 std::regex regex(R
"(( *))"
632 R"(\[\[gpu::unroll\((\d+)\)\]\])"
641 regex_global_search(str, regex, [&](const std::smatch &match) {
642 std::string content = match[0].str();
646 line +=
line_count(match.prefix().str()) + lines_in_content;
649 loop.iter_count = std::stol(match[2].
str());
650 loop.definition = content;
651 loop.indent = match[1].str();
652 loop.init_statement = match[3].str();
653 loop.test_statement =
"if (" + match[4].str() +
") ";
654 loop.iter_statement = match[5].str();
655 loop.body_prefix = match[13].str();
657 add_loop(loop, match, line, lines_in_content);
664 for (
const Loop &loop : loops) {
665 std::string replacement = loop.indent +
"{ " + loop.init_statement +
";";
666 for (
int64_t i = 0;
i < loop.iter_count;
i++) {
667 replacement += std::string(
"\n#line ") + std::to_string(loop.body_line + 1) +
"\n";
668 replacement += loop.indent + loop.test_statement + loop.body;
669 replacement += std::string(
"\n#line ") + std::to_string(loop.definition_line + 1) +
"\n";
670 replacement += loop.indent + loop.iter_statement +
";";
671 if (
i == loop.iter_count - 1) {
672 replacement += std::string(
"\n#line ") + std::to_string(loop.end_line + 1) +
"\n";
673 replacement += loop.indent +
"}";
677 std::string replaced = loop.definition + loop.body;
684 if (
out.find(
"[[gpu::unroll") != std::string::npos) {
685 regex_global_search(
str, std::regex(R
"(\[\[gpu::unroll)"), [&](const std::smatch &match) {
686 report_error(match,
"Error: Incompatible format for [[gpu::unroll]].");
693 std::string namespace_mutation(
const std::string &
str, report_callback report_error)
695 if (
str.find(
"namespace") == std::string::npos) {
702 std::regex regex(R
"(namespace (\w+(?:\:\:\w+)*))");
703 regex_global_search(str, regex, [&](const std::smatch &match) {
704 std::string namespace_name = match[1].str();
707 if (content.find(
"namespace") != std::string::npos) {
708 report_error(match,
"Nested namespaces are unsupported.");
712 std::string out_content = content;
715 std::regex regex(R
"([\n>] ?(?:const )?(\w+) (\w+)\(?)");
716 regex_global_search(content, regex, [&](const std::smatch &match) {
717 std::string return_type = match[1].str();
718 if (return_type ==
"template") {
722 std::string function = match[2].str();
725 std::regex regex(R
"(([^:\w]))" + function + R"(([\s\(<]))");
726 out_content = std::regex_replace(
727 out_content, regex, "$1" + namespace_name +
"::" + function +
"$2");
730 replace_all(
out,
"namespace " + namespace_name +
" {" + content +
"}", out_content);
737 std::string using_mutation(
const std::string &
str, report_callback report_error)
741 if (
str.find(
"using ") == string::npos) {
745 if (
str.find(
"using namespace ") != string::npos) {
746 regex_global_search(
str, regex(R
"(\busing namespace\b)"), [&](const smatch &match) {
748 "Unsupported `using namespace`. "
749 "Add individual `using` directives for each needed symbol.");
754 string next_str =
str;
759 regex regex_using(R
"(\busing (?:(\w+) = )?(([\w\:<>]+)::(\w+));)");
762 while (regex_search(next_str, match, regex_using)) {
763 const string using_definition = match[0].str();
764 const string alias = match[1].str();
765 const string to = match[2].str();
766 const string namespace_prefix = match[3].str();
767 const string symbol = match[4].str();
768 const string prefix = match.prefix().str();
769 const string suffix = match.suffix().str();
773 if (prefix.back() ==
'\n') {
776 out_str +
'}',
'{',
'}',
true);
777 if (parent_scope.empty()) {
778 report_error(match,
"The `using` keyword is not allowed in global scope.");
783 const string ns_keyword =
"namespace ";
784 size_t pos = out_str.rfind(ns_keyword, out_str.size() - parent_scope.size());
785 if (
pos == string::npos) {
786 report_error(match,
"Couldn't find `namespace` keyword at beginning of scope.");
789 size_t start =
pos + ns_keyword.size();
790 size_t end = out_str.size() - parent_scope.size() - start - 2;
791 const string namespace_scope = out_str.substr(start, end);
792 if (namespace_scope != namespace_prefix) {
795 "The `using` keyword is only allowed in namespace scope to make visible symbols "
796 "from the same namespace declared in another scope, potentially from another "
802 next_str = using_definition + suffix;
804 const bool replace_fn = alias.empty();
806 const string from = !alias.empty() ? alias : symbol;
812 const regex regex(R
"(([^:\w]))" + from + R"(([\s)" + (replace_fn ? R"(\()" : "") +
"])");
814 const string out_scope = regex_replace(in_scope, regex,
"$1" + to +
"$2");
815 replace_all(next_str, using_definition + in_scope, out_scope);
820 if (out_str.find(
"using ") != string::npos) {
821 regex_global_search(out_str, regex(R
"(\busing\b)"), [&](const smatch &match) {
822 report_error(match,
"Unsupported `using` keyword usage.");
828 std::string namespace_separator_mutation(
const std::string &
str)
840 std::string preprocessor_directive_mutation(
const std::string &
str)
843 std::regex regex(R
"(#\s*(?:include|pragma once)[^\n]*)");
844 return std::regex_replace(
str, regex,
"");
847 std::string swizzle_function_mutation(
const std::string &
str)
850 std::regex regex(R
"((\.[rgbaxyzw]{2,4})\(\))");
852 return std::regex_replace(
str, regex,
"$1 ");
855 void threadgroup_variables_parsing(
const std::string &
str)
857 std::regex regex(R
"(shared\s+(\w+)\s+(\w+)([^;]*);)");
858 regex_global_search(str, regex, [&](const std::smatch &match) {
859 shared_vars_.push_back({match[1].str(), match[2].str(), match[3].str()});
863 void parse_library_functions(
const std::string &
str)
865 using namespace metadata;
866 std::regex regex_func(R
"(void\s+(\w+)\s*\(([^)]+\))\s*\{)");
867 regex_global_search(str, regex_func, [&](const std::smatch &match) {
868 std::string name = match[1].str();
869 std::string args = match[2].str();
874 std::regex regex_arg(R
"((?:(const|in|out|inout)\s)?(\w+)\s([\w\[\]]+)(?:,|\)))");
875 regex_global_search(args, regex_arg, [&](const std::smatch &arg) {
876 std::string qualifier = arg[1].str();
877 std::string type = arg[2].str();
878 if (qualifier.empty() || qualifier ==
"const") {
881 fn.arguments.emplace_back(
884 metadata.functions.emplace_back(fn);
888 void parse_builtins(
const std::string &
str,
const std::string &filename)
890 const bool skip_drw_debug = filename.find(
"draw_debug_draw_lib.glsl") != std::string::npos ||
891 filename.find(
"draw_debug_draw_display_vert.glsl") !=
893 using namespace metadata;
895 std::string tokens[] = {
"gl_FragCoord",
897 "gl_GlobalInvocationID",
899 "gl_LocalInvocationID",
900 "gl_LocalInvocationIndex",
909#ifdef WITH_GPU_SHADER_ASSERT
913 for (
auto &token : tokens) {
914 if (skip_drw_debug && token ==
"drw_debug_") {
917 if (
str.find(token) != std::string::npos) {
918 metadata.builtins.emplace_back(
Builtin(
hash(token)));
923 template<
typename ReportErrorF>
924 std::string printf_processing(
const std::string &
str,
const ReportErrorF &report_error)
926 std::string out_str =
str;
929 size_t start, end = 0;
930 while ((start = out_str.find(
"printf(", end)) != std::string::npos) {
931 end = out_str.find(
';', start);
932 if (end == std::string::npos) {
936 int bracket_depth = 0;
938 for (
size_t i = start;
i < end; ++
i) {
939 if (out_str[
i] ==
'(') {
942 else if (out_str[
i] ==
')') {
945 else if (bracket_depth == 1 && out_str[
i] ==
',') {
951 report_error(std::smatch(),
"Too many parameters in printf. Max is 99.");
955 out_str[start +
sizeof(
"printf") - 4] =
'$';
956 out_str[start +
sizeof(
"printf") - 3] = ((arg_len / 10) > 0) ? (
'0' + arg_len / 10) :
'$';
957 out_str[start +
sizeof(
"printf") - 2] =
'0' + arg_len % 10;
966 std::regex regex(R
"(pri\$\$?(\d{1,2})\()");
967 out_str = std::regex_replace(out_str, regex, "{uint c_ = print_header($1u, ");
970 std::regex regex(R
"(\@)");
971 out_str = std::regex_replace(out_str, regex, "); c_ = print_data(c_,");
974 std::regex regex(R
"(\$)");
975 out_str = std::regex_replace(out_str, regex, "; }");
980 std::string assert_processing(
const std::string &
str,
const std::string &filepath)
982 std::string filename = std::regex_replace(filepath, std::regex(R
"((?:.*)\/(.*))"), "$1");
984 std::regex regex(R
"(\bassert\(([^;]*)\))");
985 std::string replacement;
986#ifdef WITH_GPU_SHADER_ASSERT
987 replacement =
"if (!($1)) { printf(\"Assertion failed: ($1), file " + filename +
988 ", line %d, thread (%u,%u,%u).\\n\", __LINE__, GPU_THREAD.x, GPU_THREAD.y, "
993 return std::regex_replace(
str, regex, replacement);
997 static uint32_t hash_string(
const std::string &
str)
1004 void static_strings_parsing(
const std::string &
str)
1006 using namespace metadata;
1008 std::regex regex(R
"("(?:[^"])*")");
1009 regex_global_search(str, regex, [&](const std::smatch &match) {
1010 std::string
format = match[0].str();
1011 metadata.printf_formats.emplace_back(metadata::PrintfFormat{hash_string(
format),
format});
1015 std::string static_strings_mutation(std::string
str)
1018 for (
const metadata::PrintfFormat &
format : metadata.printf_formats) {
1019 const std::string &str_var =
format.format;
1020 std::regex escape_regex(R
"([\\\.\^\$\+\(\)\[\]\{\}\|\?\*])");
1021 std::string str_regex = std::regex_replace(str_var, escape_regex, "\\$&");
1023 std::regex regex(str_regex);
1024 str = std::regex_replace(
str, regex, std::to_string(hash_string(str_var)) +
'u');
1029 std::string enum_macro_injection(std::string
str)
1062 std::regex regex(R
"(enum\s+((\w+)\s*(?:\:\s*\w+\s*)?)\{(\n[^}]+)\n\};)");
1063 str = std::regex_replace(str,
1065 "_enum_decl(_$1)$3 _enum_end\n"
1066 "#define $2 _enum_type(_$2)");
1070 std::regex regex(R
"(,(\s*_enum_end))");
1071 str = std::regex_replace(str, regex, "$1");
1080 std::string default_argument_mutation(std::string
str)
1082 using namespace std;
1085 str, [&](
int ,
int ,
char & ) { match++; });
1092 vector<pair<string, string>> mutations;
1097 regex regex_func(R
"(\n((\w+)\s+(\w+)\s*\()([^{]+))");
1098 regex_global_search(str, regex_func, [&](const smatch &match) {
1099 const string prefix = match[1].str();
1100 const string return_type = match[2].str();
1101 const string func_name = match[3].str();
1103 const string suffix =
")\n{";
1106 line +=
line_count(match.prefix().str()) + lines_in_content;
1108 if (args.find(
'=') == string::npos) {
1112 const bool has_non_void_return_type = return_type !=
"void";
1118 string args_defined;
1122 string with_default = match[0].str();
1123 string no_default = with_default;
1125 for (
const string &arg : args_split) {
1126 regex regex(R
"(((?:const )?\w+)\s+(\w+)( = (.+))?)");
1128 regex_search(arg, match, regex);
1130 string arg_type = match[1].str();
1131 string arg_name = match[2].str();
1132 string arg_assign = match[3].str();
1133 string arg_value = match[4].str();
1135 if (!arg_value.empty()) {
1136 string body = func_name +
"(" + args_called + arg_value +
");";
1137 if (has_non_void_return_type) {
1138 body =
" return " + body;
1145 body +
"\n}\n" + overloads;
1149 if (!args_defined.empty()) {
1150 args_defined +=
", ";
1152 args_defined += arg_type +
' ' + arg_name;
1153 args_called += arg_name +
", ";
1157 string body_content =
'{' +
1161 string last_line_directive =
1164 mutations.emplace_back(with_default + body_content,
1165 no_default + body_content + overloads + last_line_directive);
1168 for (
auto mutation : mutations) {
1176 std::string matrix_constructor_mutation(
const std::string &
str)
1178 if (
str.find(
"mat") == std::string::npos) {
1182 std::regex regex_parenthesis(R
"(\bmat([234])\()");
1183 std::string out = std::regex_replace(str, regex_parenthesis, "mat$1x$1(");
1186 std::regex regex(R
"(\bmat(2x2|3x3|4x4)\()");
1187 return std::regex_replace(
out, regex,
"__mat$1(");
1191 std::string argument_reference_mutation(std::string &
str)
1194 bool valid_match =
false;
1198 if ((parenthesis_depth == 1 || parenthesis_depth == 2) && bracket_depth == 0) {
1211 std::regex regex_parenthesis(R
"((\w+ )\(@(\w+)\))");
1212 std::string out = std::regex_replace(str, regex_parenthesis, "$1@$2");
1214 std::regex regex(R
"((?:const)?(\s*)(\w+)\s+\@(\w+)(\[\d*\])?)");
1215 return std::regex_replace(
out, regex,
"$1 inout $2 $3$4");
1219 std::string variable_reference_mutation(
const std::string &
str, report_callback report_error)
1221 using namespace std;
1223 bool valid_match =
false;
1224 string next_str =
str;
1227 if (parenthesis_depth == 0) {
1240 regex regex_ref(R
"(\ ?(?:const)?\s*\w+\s+\@(\w+) =\s*([^;]+);)");
1243 while (regex_search(next_str, match, regex_ref)) {
1244 const string definition = match[0].str();
1245 const string name = match[1].str();
1246 const string value = match[2].str();
1247 const string prefix = match.prefix().str();
1248 const string suffix = match.suffix().str();
1253 if (value.find(
"++") != string::npos || value.find(
"--") != string::npos) {
1254 report_error(match,
"Reference definitions cannot have side effects.");
1257 if (value.find(
"(") != string::npos) {
1258 report_error(match,
"Reference definitions cannot contain function calls.");
1261 if (value.find(
"[") != string::npos) {
1264 if (index_var.find(
' ') != string::npos) {
1266 "Array subscript inside reference declaration must be a single variable or "
1267 "a constant, not an expression.");
1272 string scope_depth =
" }";
1273 bool found_var =
false;
1274 while (!found_var) {
1278 if (scope.empty()) {
1282 scope = regex_replace(scope, regex(R
"(\{[^\}]*\})"), "{}");
1284 regex regex_definition(R
"((const)? \w+ )" + index_var + " =");
1285 smatch match_definition;
1286 if (regex_search(scope, match_definition, regex_definition)) {
1288 if (match_definition[1].matched ==
false) {
1289 report_error(match,
"Array subscript variable must be declared as const qualified.");
1296 "Cannot locate array subscript variable declaration. "
1297 "If it is a global variable, assign it to a temporary const variable for "
1298 "indexing inside the reference.");
1305 if (scope.empty()) {
1306 report_error(match,
"Reference is defined inside a global or unterminated scope.");
1309 string original = definition + scope;
1310 string modified = original;
1313 string newlines(
line_count(definition),
'\n');
1317 modified = regex_replace(
1318 modified, regex(R
"(([^\.])\b)" + name + R"(\b([^(]))"), "$1" + value +
"$2");
1321 next_str = definition + suffix;
1326 out_str += next_str;
1330 std::string argument_decorator_macro_injection(
const std::string &
str)
1333 std::regex regex(R
"((out|inout|in|shared)\s+(\w+)\s+(\w+))");
1334 return std::regex_replace(
str, regex,
"$1 $2 _$1_sta $3 _$1_end");
1337 std::string array_constructor_macro_injection(
const std::string &
str)
1340 std::regex regex(R
"(=\s*(\w+)\s*\[[^\]]*\]\s*\()");
1341 return std::regex_replace(
str, regex,
"= ARRAY_T($1) ARRAY_V(");
1345 void matrix_constructor_linting(
const std::string &
str, report_callback report_error)
1348 if (
str.find(
"mat") == std::string::npos &&
str.find(
"float") == std::string::npos) {
1352 std::regex regex(R
"(\s(?:mat(?:\d|\dx\d)|float\dx\d)\()");
1353 regex_global_search(str, regex, [&](const std::smatch &match) {
1356 bool has_floating_point_arg = args.find(
'.') != std::string::npos;
1358 if (arg_count != 1 || has_floating_point_arg) {
1363 "Matrix constructor is not cross API compatible. "
1364 "Use to_floatNxM to reshape the matrix or use other constructors instead.";
1365 report_error(match, msg);
1370 void global_scope_constant_linting(
const std::string &
str, report_callback report_error)
1373 std::regex regex(R
"(const \w+ \w+ =)");
1374 regex_global_search(str, regex, [&](const std::smatch &match) {
1376 if (match.prefix().str().back() ==
'\n') {
1378 "Global scope constant expression found. These get allocated per-thread in MSL. "
1379 "Use Macro's or uniforms instead.";
1380 report_error(match, msg);
1385 void quote_linting(
const std::string &
str, report_callback report_error)
1387 std::regex regex(R
"(["'])");
1388 regex_global_search(str, regex, [&](const std::smatch &match) {
1390 const char *msg =
"Quotes are forbidden in GLSL.";
1391 report_error(match, msg);
1395 void array_constructor_linting(
const std::string &
str, report_callback report_error)
1397 std::regex regex(R
"(=\s*(\w+)\s*\[[^\]]*\]\s*\()");
1398 regex_global_search(str, regex, [&](const std::smatch &match) {
1401 "Array constructor is not cross API compatible. Use type_array instead of type[].";
1402 report_error(match, msg);
1406 template<
typename ReportErrorF>
1407 void small_type_linting(
const std::string &
str,
const ReportErrorF &report_error)
1409 std::regex regex(R
"(\su?(char|short|half)(2|3|4)?\s)");
1410 regex_global_search(str, regex, [&](const std::smatch &match) {
1411 report_error(match,
"Small types are forbidden in shader interfaces.");
1415 std::string threadgroup_variables_suffix()
1417 if (shared_vars_.empty()) {
1421 std::stringstream suffix;
1437 suffix <<
"#undef MSL_SHARED_VARS_ARGS\n";
1439 suffix <<
"#undef MSL_SHARED_VARS_ASSIGN\n";
1441 suffix <<
"#undef MSL_SHARED_VARS_DECLARE\n";
1443 suffix <<
"#undef MSL_SHARED_VARS_PASS\n";
1483 std::stringstream args, assign, declare, pass;
1486 for (SharedVar &var : shared_vars_) {
1487 char sep = first ?
' ' :
',';
1489 args << sep <<
"threadgroup " << var.type <<
"(&_" << var.name <<
")" << var.array;
1490 assign << (first ?
':' :
',') << var.name <<
"(_" << var.name <<
")";
1491 declare <<
"threadgroup " << var.type <<
' ' << var.name << var.array <<
";";
1492 pass << sep << var.name;
1496 suffix <<
"#define MSL_SHARED_VARS_ARGS " << args.str() <<
"\n";
1497 suffix <<
"#define MSL_SHARED_VARS_ASSIGN " << assign.str() <<
"\n";
1498 suffix <<
"#define MSL_SHARED_VARS_DECLARE " << declare.str() <<
"\n";
1499 suffix <<
"#define MSL_SHARED_VARS_PASS (" << pass.str() <<
")\n";
1502 return suffix.str();
1505 std::string line_directive_prefix(
const std::string &filepath)
1507 std::string filename = std::regex_replace(filepath, std::regex(R
"((?:.*)\/(.*))"), "$1");
1509 std::stringstream suffix;
1510 suffix <<
"#line 1 ";
1517 if (!filename.empty()) {
1518 suffix <<
"\"" << filename <<
"\"";
1523 hash_value = (hash_value ^ (hash_value >> 32)) & (~
uint64_t(0) >> 33);
1524 suffix << std::to_string(
uint64_t(hash_value));
1527 return suffix.str();
1533 char start_delimiter,
1535 const bool backwards =
false)
1538 size_t start = std::string::npos;
1539 size_t end = std::string::npos;
1542 std::swap(start_delimiter, end_delimiter);
1545 for (
size_t i = 0;
i <
input.length(); ++
i) {
1546 size_t idx = backwards ? (
input.length() - 1) -
i :
i;
1547 if (
input[idx] == start_delimiter) {
1553 else if (
input[idx] == end_delimiter) {
1555 if (
balance == 0 && start != std::string::npos) {
1558 std::swap(start, end);
1560 return input.substr(start + 1, end - start - 1);
1570 const char start_delimiter,
1571 const char end_delimiter,
1578 for (
char &string_char :
str) {
1579 if (string_char == start_delimiter) {
1582 else if (string_char == end_delimiter) {
1585 else if (depth > 0 && string_char == from) {
1593 static std::vector<std::string>
split_string(
const std::string &
str,
const char delimiter)
1595 std::vector<std::string> substrings;
1596 std::stringstream ss(
str);
1599 while (std::getline(ss, item, delimiter)) {
1600 substrings.push_back(item);
1608 const char delimiter,
1609 const char pair_start,
1610 const char pair_end)
1612 const char safe_char =
'@';
1614 str, pair_start, pair_end, delimiter, safe_char);
1622 static void replace_all(std::string &
str,
const std::string &from,
const std::string &to)
1627 size_t start_pos = 0;
1628 while ((start_pos =
str.find(from, start_pos)) != std::string::npos) {
1629 str.replace(start_pos, from.length(), to);
1630 start_pos += to.length();
1636 for (
char &string_char :
str) {
1637 if (string_char == from) {
1645 return std::count(
str.begin(),
str.end(), c);
1659 str,
'&', [&](
size_t pos,
int parenthesis_depth,
int bracket_depth,
char &c) {
1660 if (
pos > 0 &&
pos <=
str.length() - 2) {
1662 char prev_char =
str[
pos - 1];
1663 char next_char =
str[
pos + 1];
1665 if (prev_char ==
' ' || prev_char ==
'(') {
1666 if (next_char !=
' ' && next_char !=
'\n' && next_char !=
'&' && next_char !=
'=') {
1667 callback(parenthesis_depth, bracket_depth, c);
1678 std::function<
void(
int,
int,
char &)> callback)
1681 str,
'=', [&](
size_t pos,
int parenthesis_depth,
int bracket_depth,
char &c) {
1682 if (
pos > 0 &&
pos <=
str.length() - 2) {
1684 char prev_char =
str[
pos - 1];
1685 char next_char =
str[
pos + 1];
1687 if (prev_char ==
' ' && next_char ==
' ') {
1688 if (parenthesis_depth == 1 && bracket_depth == 0) {
1689 callback(parenthesis_depth, bracket_depth, c);
1700 std::function<
void(
size_t,
int,
int,
char &)> callback)
1703 int parenthesis_depth = 0;
1704 int bracket_depth = 0;
1705 for (
char &c :
str) {
1706 if (c == search_char) {
1707 callback(
pos, parenthesis_depth, bracket_depth, c);
1709 else if (c ==
'(') {
1710 parenthesis_depth++;
1712 else if (c ==
')') {
1713 parenthesis_depth--;
1715 else if (c ==
'{') {
1718 else if (c ==
'}') {