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