aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/gpu/GrSKSLPrettyPrint.cpp
blob: cb2da7589c5c2169c4edf0f153b2505ab163c7b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/*
 * Copyright 2014 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "GrSKSLPrettyPrint.h"
#include "SkSLString.h"

namespace GrSKSLPrettyPrint {

class GLSLPrettyPrint {
public:
    GLSLPrettyPrint() {}

    SkSL::String prettify(const char** strings, int* lengths, int count, bool countlines) {
        fCountlines = countlines;
        fTabs = 0;
        fLinecount = 1;
        fFreshline = true;

        // If a string breaks while in the middle 'parse until' we need to continue parsing on the
        // next string
        fInParseUntilNewline = false;
        fInParseUntil = false;

        int parensDepth = 0;

        // number 1st line
        this->lineNumbering();
        for (int i = 0; i < count; i++) {
            // setup pretty state
            fIndex = 0;
            fLength = lengths[i];
            fInput = strings[i];

            while (fLength > fIndex) {
                /* the heart and soul of our prettification algorithm.  The rules should hopefully
                 * be self explanatory.  For '#' and '//' tokens we parse until we reach a newline.
                 *
                 * For long style comments like this one, we search for the ending token.  We also
                 * preserve whitespace in these comments WITH THE CAVEAT that we do the newlines
                 * ourselves.  This allows us to remain in control of line numbers, and matching
                 * tabs Existing tabs in the input string are copied over too, but this will look
                 *  funny
                 *
                 * '{' and '}' are handled in basically the same way.  We add a newline if we aren't
                 * on a fresh line, dirty the line, then add a second newline, ie braces are always
                 * on their own lines indented properly.  The one funkiness here is structs print
                 * with the semicolon on its own line.  Its not a problem for a glsl compiler though
                 *
                 * '(' and ')' are basically ignored, except as a sign we need to ignore ';' ala
                 * in for loops.
                 *
                 * ';' means add a new line
                 *
                 * '\t' and '\n' are ignored in general parsing for backwards compatability with
                 * existing shader code and we also have a special case for handling whitespace
                 * at the beginning of fresh lines.
                 *
                 * Otherwise just add the new character to the pretty string, indenting if
                 * necessary.
                 */
                if (fInParseUntilNewline) {
                    this->parseUntilNewline();
                } else if (fInParseUntil) {
                    this->parseUntil(fInParseUntilToken);
                } else if (this->hasToken("#") || this->hasToken("//")) {
                    this->parseUntilNewline();
                } else if (this->hasToken("/*")) {
                    this->parseUntil("*/");
                } else if ('{' == fInput[fIndex]) {
                    this->newline();
                    this->appendChar('{');
                    fTabs++;
                    this->newline();
                } else if ('}' == fInput[fIndex]) {
                    fTabs--;
                    this->newline();
                    this->appendChar('}');
                    this->newline();
                } else if (this->hasToken(")")) {
                    parensDepth--;
                } else if (this->hasToken("(")) {
                    parensDepth++;
                } else if (!parensDepth && this->hasToken(";")) {
                    this->newline();
                } else if ('\t' == fInput[fIndex] || '\n' == fInput[fIndex] ||
                           (fFreshline && ' ' == fInput[fIndex])) {
                    fIndex++;
                } else {
                    this->appendChar(fInput[fIndex]);
                }
            }
        }
        return fPretty;
    }

private:
    void appendChar(char c) {
        this->tabString();
        fPretty.appendf("%c", fInput[fIndex++]);
        fFreshline = false;
    }

    // hasToken automatically consumes the next token, if it is a match, and then tabs
    // if necessary, before inserting the token into the pretty string
    bool hasToken(const char* token) {
        size_t i = fIndex;
        for (size_t j = 0; token[j] && fLength > i; i++, j++) {
            if (token[j] != fInput[i]) {
                return false;
            }
        }
        this->tabString();
        fIndex = i;
        fPretty.append(token);
        fFreshline = false;
        return true;
    }

    void parseUntilNewline() {
        while (fLength > fIndex) {
            if ('\n' == fInput[fIndex]) {
                fIndex++;
                this->newline();
                fInParseUntilNewline = false;
                break;
            }
            fPretty.appendf("%c", fInput[fIndex++]);
            fInParseUntilNewline = true;
        }
    }

    // this code assumes it is not actually searching for a newline.  If you need to search for a
    // newline, then use the function above.  If you do search for a newline with this function
    // it will consume the entire string and the output will certainly not be prettified
    void parseUntil(const char* token) {
        while (fLength > fIndex) {
            // For embedded newlines,  this code will make sure to embed the newline in the
            // pretty string, increase the linecount, and tab out the next line to the appropriate
            // place
            if ('\n' == fInput[fIndex]) {
                this->newline();
                this->tabString();
                fIndex++;
            }
            if (this->hasToken(token)) {
                fInParseUntil = false;
                break;
            }
            fFreshline = false;
            fPretty.appendf("%c", fInput[fIndex++]);
            fInParseUntil = true;
            fInParseUntilToken = token;
        }
    }

    // We only tab if on a newline, otherwise consider the line tabbed
    void tabString() {
        if (fFreshline) {
            for (int t = 0; t < fTabs; t++) {
                fPretty.append("\t");
            }
        }
    }

    // newline is really a request to add a newline, if we are on a fresh line there is no reason
    // to add another newline
    void newline() {
        if (!fFreshline) {
            fFreshline = true;
            fPretty.append("\n");
            this->lineNumbering();
        }
    }

    void lineNumbering() {
        if (fCountlines) {
            fPretty.appendf("%4d\t", fLinecount++);
        }
    }

    bool fCountlines, fFreshline;
    int fTabs, fLinecount;
    size_t fIndex, fLength;
    const char* fInput;
    SkSL::String fPretty;

    // Some helpers for parseUntil when we go over a string length
    bool fInParseUntilNewline;
    bool fInParseUntil;
    const char* fInParseUntilToken;
};

SkSL::String PrettyPrint(const char** strings, int* lengths, int count, bool countlines) {
    GLSLPrettyPrint pp;
    return pp.prettify(strings, lengths, count, countlines);
}

}  // namespace GrSKSLPrettyPrint