blob: 536720218eda934fb8705398633402692c81fb6d [file] [log] [blame]
joshualittbab82ed2014-08-08 09:41:42 -07001/*
Brian Osmanac9be9d2019-05-01 10:29:34 -04002 * Copyright 2019 Google LLC
joshualittbab82ed2014-08-08 09:41:42 -07003 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
Brian Osmanac9be9d2019-05-01 10:29:34 -04007
8#include "include/core/SkString.h"
9#include "src/gpu/GrShaderUtils.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050010#include "src/sksl/SkSLString.h"
joshualittbab82ed2014-08-08 09:41:42 -070011
Brian Osmanac9be9d2019-05-01 10:29:34 -040012namespace GrShaderUtils {
joshualittbab82ed2014-08-08 09:41:42 -070013
14class GLSLPrettyPrint {
15public:
16 GLSLPrettyPrint() {}
17
Brian Osman6c431d52019-04-15 16:31:54 -040018 SkSL::String prettify(const SkSL::String& string) {
joshualittbab82ed2014-08-08 09:41:42 -070019 fTabs = 0;
joshualittbab82ed2014-08-08 09:41:42 -070020 fFreshline = true;
21
joshualitt43466a12015-02-13 17:18:27 -080022 // If a string breaks while in the middle 'parse until' we need to continue parsing on the
23 // next string
24 fInParseUntilNewline = false;
25 fInParseUntil = false;
26
joshualittbab82ed2014-08-08 09:41:42 -070027 int parensDepth = 0;
joshualitt43466a12015-02-13 17:18:27 -080028
Brian Osman6c431d52019-04-15 16:31:54 -040029 // setup pretty state
30 fIndex = 0;
31 fLength = string.length();
32 fInput = string.c_str();
joshualitt43466a12015-02-13 17:18:27 -080033
Brian Osman6c431d52019-04-15 16:31:54 -040034 while (fLength > fIndex) {
35 /* the heart and soul of our prettification algorithm. The rules should hopefully
36 * be self explanatory. For '#' and '//' tokens we parse until we reach a newline.
37 *
38 * For long style comments like this one, we search for the ending token. We also
39 * preserve whitespace in these comments WITH THE CAVEAT that we do the newlines
40 * ourselves. This allows us to remain in control of line numbers, and matching
41 * tabs Existing tabs in the input string are copied over too, but this will look
42 * funny
43 *
44 * '{' and '}' are handled in basically the same way. We add a newline if we aren't
45 * on a fresh line, dirty the line, then add a second newline, ie braces are always
46 * on their own lines indented properly. The one funkiness here is structs print
47 * with the semicolon on its own line. Its not a problem for a glsl compiler though
48 *
49 * '(' and ')' are basically ignored, except as a sign we need to ignore ';' ala
50 * in for loops.
51 *
52 * ';' means add a new line
53 *
54 * '\t' and '\n' are ignored in general parsing for backwards compatability with
55 * existing shader code and we also have a special case for handling whitespace
56 * at the beginning of fresh lines.
57 *
58 * Otherwise just add the new character to the pretty string, indenting if
59 * necessary.
60 */
61 if (fInParseUntilNewline) {
62 this->parseUntilNewline();
63 } else if (fInParseUntil) {
64 this->parseUntil(fInParseUntilToken);
65 } else if (this->hasToken("#") || this->hasToken("//")) {
66 this->parseUntilNewline();
67 } else if (this->hasToken("/*")) {
68 this->parseUntil("*/");
69 } else if ('{' == fInput[fIndex]) {
70 this->newline();
71 this->appendChar('{');
72 fTabs++;
73 this->newline();
74 } else if ('}' == fInput[fIndex]) {
75 fTabs--;
76 this->newline();
77 this->appendChar('}');
78 this->newline();
79 } else if (this->hasToken(")")) {
80 parensDepth--;
81 } else if (this->hasToken("(")) {
82 parensDepth++;
83 } else if (!parensDepth && this->hasToken(";")) {
84 this->newline();
85 } else if ('\t' == fInput[fIndex] || '\n' == fInput[fIndex] ||
86 (fFreshline && ' ' == fInput[fIndex])) {
87 fIndex++;
88 } else {
89 this->appendChar(fInput[fIndex]);
joshualittbab82ed2014-08-08 09:41:42 -070090 }
91 }
Brian Osman6c431d52019-04-15 16:31:54 -040092
joshualittbab82ed2014-08-08 09:41:42 -070093 return fPretty;
94 }
Brian Salomone334c592017-05-15 11:00:58 -040095
joshualittbab82ed2014-08-08 09:41:42 -070096private:
97 void appendChar(char c) {
98 this->tabString();
99 fPretty.appendf("%c", fInput[fIndex++]);
100 fFreshline = false;
101 }
102
103 // hasToken automatically consumes the next token, if it is a match, and then tabs
104 // if necessary, before inserting the token into the pretty string
105 bool hasToken(const char* token) {
106 size_t i = fIndex;
107 for (size_t j = 0; token[j] && fLength > i; i++, j++) {
108 if (token[j] != fInput[i]) {
109 return false;
110 }
111 }
112 this->tabString();
113 fIndex = i;
114 fPretty.append(token);
115 fFreshline = false;
116 return true;
117 }
118
119 void parseUntilNewline() {
120 while (fLength > fIndex) {
121 if ('\n' == fInput[fIndex]) {
122 fIndex++;
123 this->newline();
joshualitt43466a12015-02-13 17:18:27 -0800124 fInParseUntilNewline = false;
joshualittbab82ed2014-08-08 09:41:42 -0700125 break;
126 }
127 fPretty.appendf("%c", fInput[fIndex++]);
joshualitt43466a12015-02-13 17:18:27 -0800128 fInParseUntilNewline = true;
joshualittbab82ed2014-08-08 09:41:42 -0700129 }
130 }
131
132 // this code assumes it is not actually searching for a newline. If you need to search for a
133 // newline, then use the function above. If you do search for a newline with this function
134 // it will consume the entire string and the output will certainly not be prettified
135 void parseUntil(const char* token) {
136 while (fLength > fIndex) {
137 // For embedded newlines, this code will make sure to embed the newline in the
138 // pretty string, increase the linecount, and tab out the next line to the appropriate
139 // place
140 if ('\n' == fInput[fIndex]) {
141 this->newline();
142 this->tabString();
143 fIndex++;
144 }
145 if (this->hasToken(token)) {
joshualitt43466a12015-02-13 17:18:27 -0800146 fInParseUntil = false;
joshualittbab82ed2014-08-08 09:41:42 -0700147 break;
148 }
149 fFreshline = false;
150 fPretty.appendf("%c", fInput[fIndex++]);
joshualitt43466a12015-02-13 17:18:27 -0800151 fInParseUntil = true;
152 fInParseUntilToken = token;
joshualittbab82ed2014-08-08 09:41:42 -0700153 }
154 }
155
156 // We only tab if on a newline, otherwise consider the line tabbed
157 void tabString() {
158 if (fFreshline) {
159 for (int t = 0; t < fTabs; t++) {
160 fPretty.append("\t");
161 }
162 }
163 }
164
165 // newline is really a request to add a newline, if we are on a fresh line there is no reason
166 // to add another newline
167 void newline() {
168 if (!fFreshline) {
169 fFreshline = true;
170 fPretty.append("\n");
joshualittbab82ed2014-08-08 09:41:42 -0700171 }
172 }
173
Brian Osman6c431d52019-04-15 16:31:54 -0400174 bool fFreshline;
175 int fTabs;
joshualittbab82ed2014-08-08 09:41:42 -0700176 size_t fIndex, fLength;
joshualitt43466a12015-02-13 17:18:27 -0800177 const char* fInput;
Brian Osman93ba0a42017-08-14 14:48:10 -0400178 SkSL::String fPretty;
joshualitt43466a12015-02-13 17:18:27 -0800179
180 // Some helpers for parseUntil when we go over a string length
181 bool fInParseUntilNewline;
182 bool fInParseUntil;
183 const char* fInParseUntilToken;
joshualittbab82ed2014-08-08 09:41:42 -0700184};
185
Brian Osman6c431d52019-04-15 16:31:54 -0400186SkSL::String PrettyPrint(const SkSL::String& string) {
joshualittbab82ed2014-08-08 09:41:42 -0700187 GLSLPrettyPrint pp;
Brian Osman6c431d52019-04-15 16:31:54 -0400188 return pp.prettify(string);
joshualittbab82ed2014-08-08 09:41:42 -0700189}
190
Brian Osmanac9be9d2019-05-01 10:29:34 -0400191// Prints shaders one line at the time. This ensures they don't get truncated by the adb log.
192void PrintLineByLine(const char* header, const SkSL::String& text) {
193 SkSL::String pretty = PrettyPrint(text);
194 SkDebugf("%s\n", header);
195 SkTArray<SkString> lines;
196 SkStrSplit(pretty.c_str(), "\n", kStrict_SkStrSplitMode, &lines);
197 for (int i = 0; i < lines.count(); ++i) {
198 SkDebugf("%4i\t%s\n", i + 1, lines[i].c_str());
199 }
200}
201
202} // namespace