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 | |
13 | namespace GrShaderUtils { |
14 | |
15 | class GLSLPrettyPrint { |
16 | public: |
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 | |
97 | private: |
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 | |
187 | SkSL::String PrettyPrint(const SkSL::String& string) { |
188 | GLSLPrettyPrint pp; |
189 | return pp.prettify(string); |
190 | } |
191 | |
192 | void 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 | |
201 | GrContextOptions::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 | |