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 "gl/GrGLSLPrettyPrint.h"
8
9namespace GrGLSLPrettyPrint {
10
11class GLSLPrettyPrint {
12public:
13    GLSLPrettyPrint() {}
14
15    SkString prettify(const char** strings,
16                      int* lengths,
17                      int count,
18                      bool countlines) {
19        fCountlines = countlines;
20        fTabs = 0;
21        fLinecount = 1;
22        fFreshline = true;
23
24        // If a string breaks while in the middle 'parse until' we need to continue parsing on the
25        // next string
26        fInParseUntilNewline = false;
27        fInParseUntil = false;
28
29        int parensDepth = 0;
30
31        // number 1st line
32        this->lineNumbering();
33        for (int i = 0; i < count; i++) {
34            // setup pretty state
35            fIndex = 0;
36            fLength = lengths[i];
37            fInput = strings[i];
38
39            while (fLength > fIndex) {
40                /* the heart and soul of our prettification algorithm.  The rules should hopefully
41                 * be self explanatory.  For '#' and '//' tokens we parse until we reach a newline.
42                 *
43                 * For long style comments like this one, we search for the ending token.  We also
44                 * preserve whitespace in these comments WITH THE CAVEAT that we do the newlines
45                 * ourselves.  This allows us to remain in control of line numbers, and matching
46                 * tabs Existing tabs in the input string are copied over too, but this will look
47                 *  funny
48                 *
49                 * '{' and '}' are handled in basically the same way.  We add a newline if we aren't
50                 * on a fresh line, dirty the line, then add a second newline, ie braces are always
51                 * on their own lines indented properly.  The one funkiness here is structs print
52                 * with the semicolon on its own line.  Its not a problem for a glsl compiler though
53                 *
54                 * '(' and ')' are basically ignored, except as a sign we need to ignore ';' ala
55                 * in for loops.
56                 *
57                 * ';' means add a new line
58                 *
59                 * '\t' and '\n' are ignored in general parsing for backwards compatability with
60                 * existing shader code and we also have a special case for handling whitespace
61                 * at the beginning of fresh lines.
62                 *
63                 * Otherwise just add the new character to the pretty string, indenting if necessary.
64                 */
65                if (fInParseUntilNewline) {
66                    this->parseUntilNewline();
67                } else if (fInParseUntil) {
68                    this->parseUntil(fInParseUntilToken);
69                } else if (this->hasToken("#") || this->hasToken("//")) {
70                    this->parseUntilNewline();
71                } else if (this->hasToken("/*")) {
72                    this->parseUntil("*/");
73                } else if ('{' == fInput[fIndex]) {
74                    this->newline();
75                    this->appendChar('{');
76                    fTabs++;
77                    this->newline();
78                } else if ('}' == fInput[fIndex]) {
79                    fTabs--;
80                    this->newline();
81                    this->appendChar('}');
82                    this->newline();
83                } else if (this->hasToken(")")) {
84                    parensDepth--;
85                } else if (this->hasToken("(")) {
86                    parensDepth++;
87                } else if (!parensDepth && this->hasToken(";")) {
88                    this->newline();
89                } else if ('\t' == fInput[fIndex] || '\n' == fInput[fIndex] ||
90                        (fFreshline && ' ' == fInput[fIndex])) {
91                    fIndex++;
92                } else {
93                    this->appendChar(fInput[fIndex]);
94                }
95            }
96        }
97        return fPretty;
98    }
99private:
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    SkString 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
196SkString PrettyPrintGLSL(const char** strings,
197                         int* lengths,
198                         int count,
199                         bool countlines) {
200    GLSLPrettyPrint pp;
201    return pp.prettify(strings, lengths, count, countlines);
202}
203
204} // end namespace
205