1/*
2 * Copyright 2019 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "include/core/SkString.h"
9#include "include/gpu/GrContextOptions.h"
10#include "src/gpu/GrShaderUtils.h"
11#include "src/sksl/SkSLString.h"
12
13namespace GrShaderUtils {
14
15class GLSLPrettyPrint {
16public:
17 GLSLPrettyPrint() {}
18
19 SkSL::String prettify(const SkSL::String& string) {
20 fTabs = 0;
21 fFreshline = true;
22
23 // If a string breaks while in the middle 'parse until' we need to continue parsing on the
24 // next string
25 fInParseUntilNewline = false;
26 fInParseUntil = false;
27
28 int parensDepth = 0;
29
30 // setup pretty state
31 fIndex = 0;
32 fLength = string.length();
33 fInput = string.c_str();
34
35 while (fLength > fIndex) {
36 /* the heart and soul of our prettification algorithm. The rules should hopefully
37 * be self explanatory. For '#' and '//' tokens we parse until we reach a newline.
38 *
39 * For long style comments like this one, we search for the ending token. We also
40 * preserve whitespace in these comments WITH THE CAVEAT that we do the newlines
41 * ourselves. This allows us to remain in control of line numbers, and matching
42 * tabs Existing tabs in the input string are copied over too, but this will look
43 * funny
44 *
45 * '{' and '}' are handled in basically the same way. We add a newline if we aren't
46 * on a fresh line, dirty the line, then add a second newline, ie braces are always
47 * on their own lines indented properly. The one funkiness here is structs print
48 * with the semicolon on its own line. Its not a problem for a glsl compiler though
49 *
50 * '(' and ')' are basically ignored, except as a sign we need to ignore ';' ala
51 * in for loops.
52 *
53 * ';' means add a new line
54 *
55 * '\t' and '\n' are ignored in general parsing for backwards compatability with
56 * existing shader code and we also have a special case for handling whitespace
57 * at the beginning of fresh lines.
58 *
59 * Otherwise just add the new character to the pretty string, indenting if
60 * necessary.
61 */
62 if (fInParseUntilNewline) {
63 this->parseUntilNewline();
64 } else if (fInParseUntil) {
65 this->parseUntil(fInParseUntilToken);
66 } else if (this->hasToken("#") || this->hasToken("//")) {
67 this->parseUntilNewline();
68 } else if (this->hasToken("/*")) {
69 this->parseUntil("*/");
70 } else if ('{' == fInput[fIndex]) {
71 this->newline();
72 this->appendChar('{');
73 fTabs++;
74 this->newline();
75 } else if ('}' == fInput[fIndex]) {
76 fTabs--;
77 this->newline();
78 this->appendChar('}');
79 this->newline();
80 } else if (this->hasToken(")")) {
81 parensDepth--;
82 } else if (this->hasToken("(")) {
83 parensDepth++;
84 } else if (!parensDepth && this->hasToken(";")) {
85 this->newline();
86 } else if ('\t' == fInput[fIndex] || '\n' == fInput[fIndex] ||
87 (fFreshline && ' ' == fInput[fIndex])) {
88 fIndex++;
89 } else {
90 this->appendChar(fInput[fIndex]);
91 }
92 }
93
94 return fPretty;
95 }
96
97private:
98 void appendChar(char c) {
99 this->tabString();
100 fPretty.appendf("%c", fInput[fIndex++]);
101 fFreshline = false;
102 }
103
104 // hasToken automatically consumes the next token, if it is a match, and then tabs
105 // if necessary, before inserting the token into the pretty string
106 bool hasToken(const char* token) {
107 size_t i = fIndex;
108 for (size_t j = 0; token[j] && fLength > i; i++, j++) {
109 if (token[j] != fInput[i]) {
110 return false;
111 }
112 }
113 this->tabString();
114 fIndex = i;
115 fPretty.append(token);
116 fFreshline = false;
117 return true;
118 }
119
120 void parseUntilNewline() {
121 while (fLength > fIndex) {
122 if ('\n' == fInput[fIndex]) {
123 fIndex++;
124 this->newline();
125 fInParseUntilNewline = false;
126 break;
127 }
128 fPretty.appendf("%c", fInput[fIndex++]);
129 fInParseUntilNewline = true;
130 }
131 }
132
133 // this code assumes it is not actually searching for a newline. If you need to search for a
134 // newline, then use the function above. If you do search for a newline with this function
135 // it will consume the entire string and the output will certainly not be prettified
136 void parseUntil(const char* token) {
137 while (fLength > fIndex) {
138 // For embedded newlines, this code will make sure to embed the newline in the
139 // pretty string, increase the linecount, and tab out the next line to the appropriate
140 // place
141 if ('\n' == fInput[fIndex]) {
142 this->newline();
143 this->tabString();
144 fIndex++;
145 }
146 if (this->hasToken(token)) {
147 fInParseUntil = false;
148 break;
149 }
150 fFreshline = false;
151 fPretty.appendf("%c", fInput[fIndex++]);
152 fInParseUntil = true;
153 fInParseUntilToken = token;
154 }
155 }
156
157 // We only tab if on a newline, otherwise consider the line tabbed
158 void tabString() {
159 if (fFreshline) {
160 for (int t = 0; t < fTabs; t++) {
161 fPretty.append("\t");
162 }
163 }
164 }
165
166 // newline is really a request to add a newline, if we are on a fresh line there is no reason
167 // to add another newline
168 void newline() {
169 if (!fFreshline) {
170 fFreshline = true;
171 fPretty.append("\n");
172 }
173 }
174
175 bool fFreshline;
176 int fTabs;
177 size_t fIndex, fLength;
178 const char* fInput;
179 SkSL::String fPretty;
180
181 // Some helpers for parseUntil when we go over a string length
182 bool fInParseUntilNewline;
183 bool fInParseUntil;
184 const char* fInParseUntilToken;
185};
186
187SkSL::String PrettyPrint(const SkSL::String& string) {
188 GLSLPrettyPrint pp;
189 return pp.prettify(string);
190}
191
192void VisitLineByLine(const SkSL::String& text,
193 const std::function<void(int lineNumber, const char* lineText)>& visitFn) {
194 SkTArray<SkString> lines;
195 SkStrSplit(text.c_str(), "\n", kStrict_SkStrSplitMode, &lines);
196 for (int i = 0; i < lines.count(); ++i) {
197 visitFn(i + 1, lines[i].c_str());
198 }
199}
200
201GrContextOptions::ShaderErrorHandler* DefaultShaderErrorHandler() {
202 class GrDefaultShaderErrorHandler : public GrContextOptions::ShaderErrorHandler {
203 public:
204 void compileError(const char* shader, const char* errors) override {
205 SkDebugf("Shader compilation error\n"
206 "------------------------\n");
207 PrintLineByLine(shader);
208 SkDebugf("Errors:\n%s\n", errors);
209 SkDEBUGFAIL("Shader compilation failed!");
210 }
211 };
212
213 static GrDefaultShaderErrorHandler gHandler;
214 return &gHandler;
215}
216
217} // namespace
218