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