1cb93a386Sopenharmony_ci/*
2cb93a386Sopenharmony_ci * Copyright 2017 Google Inc.
3cb93a386Sopenharmony_ci *
4cb93a386Sopenharmony_ci * Use of this source code is governed by a BSD-style license that can be
5cb93a386Sopenharmony_ci * found in the LICENSE file.
6cb93a386Sopenharmony_ci */
7cb93a386Sopenharmony_ci
8cb93a386Sopenharmony_ci#ifndef SkJSONWriter_DEFINED
9cb93a386Sopenharmony_ci#define SkJSONWriter_DEFINED
10cb93a386Sopenharmony_ci
11cb93a386Sopenharmony_ci#include "include/core/SkStream.h"
12cb93a386Sopenharmony_ci#include "include/private/SkNoncopyable.h"
13cb93a386Sopenharmony_ci#include "include/private/SkTArray.h"
14cb93a386Sopenharmony_ci
15cb93a386Sopenharmony_ci/**
16cb93a386Sopenharmony_ci *  Lightweight class for writing properly structured JSON data. No random-access, everything must
17cb93a386Sopenharmony_ci *  be generated in-order. The resulting JSON is written directly to the SkWStream supplied at
18cb93a386Sopenharmony_ci *  construction time. Output is buffered, so writing to disk (via an SkFILEWStream) is ideal.
19cb93a386Sopenharmony_ci *
20cb93a386Sopenharmony_ci *  There is a basic state machine to ensure that JSON is structured correctly, and to allow for
21cb93a386Sopenharmony_ci *  (optional) pretty formatting.
22cb93a386Sopenharmony_ci *
23cb93a386Sopenharmony_ci *  This class adheres to the RFC-4627 usage of JSON (not ECMA-404). In other words, all JSON
24cb93a386Sopenharmony_ci *  created with this class must have a top-level object or array. Free-floating values of other
25cb93a386Sopenharmony_ci *  types are not considered valid.
26cb93a386Sopenharmony_ci *
27cb93a386Sopenharmony_ci *  Note that all error checking is in the form of asserts - invalid usage in a non-debug build
28cb93a386Sopenharmony_ci *  will simply produce invalid JSON.
29cb93a386Sopenharmony_ci */
30cb93a386Sopenharmony_ciclass SkJSONWriter : SkNoncopyable {
31cb93a386Sopenharmony_cipublic:
32cb93a386Sopenharmony_ci    enum class Mode {
33cb93a386Sopenharmony_ci        /**
34cb93a386Sopenharmony_ci         *  Output the minimal amount of text. No additional whitespace (including newlines) is
35cb93a386Sopenharmony_ci         *  generated. The resulting JSON is suitable for fast parsing and machine consumption.
36cb93a386Sopenharmony_ci         */
37cb93a386Sopenharmony_ci        kFast,
38cb93a386Sopenharmony_ci
39cb93a386Sopenharmony_ci        /**
40cb93a386Sopenharmony_ci         *  Output human-readable JSON, with indented  objects and arrays, and one value per line.
41cb93a386Sopenharmony_ci         *  Slightly slower than kFast, and produces data that is somewhat larger.
42cb93a386Sopenharmony_ci         */
43cb93a386Sopenharmony_ci        kPretty
44cb93a386Sopenharmony_ci    };
45cb93a386Sopenharmony_ci
46cb93a386Sopenharmony_ci    /**
47cb93a386Sopenharmony_ci     *  Construct a JSON writer that will serialize all the generated JSON to 'stream'.
48cb93a386Sopenharmony_ci     */
49cb93a386Sopenharmony_ci    SkJSONWriter(SkWStream* stream, Mode mode = Mode::kFast)
50cb93a386Sopenharmony_ci            : fBlock(new char[kBlockSize])
51cb93a386Sopenharmony_ci            , fWrite(fBlock)
52cb93a386Sopenharmony_ci            , fBlockEnd(fBlock + kBlockSize)
53cb93a386Sopenharmony_ci            , fStream(stream)
54cb93a386Sopenharmony_ci            , fMode(mode)
55cb93a386Sopenharmony_ci            , fState(State::kStart) {
56cb93a386Sopenharmony_ci        fScopeStack.push_back(Scope::kNone);
57cb93a386Sopenharmony_ci        fNewlineStack.push_back(true);
58cb93a386Sopenharmony_ci    }
59cb93a386Sopenharmony_ci
60cb93a386Sopenharmony_ci    ~SkJSONWriter() {
61cb93a386Sopenharmony_ci        this->flush();
62cb93a386Sopenharmony_ci        delete[] fBlock;
63cb93a386Sopenharmony_ci        SkASSERT(fScopeStack.count() == 1);
64cb93a386Sopenharmony_ci        SkASSERT(fNewlineStack.count() == 1);
65cb93a386Sopenharmony_ci    }
66cb93a386Sopenharmony_ci
67cb93a386Sopenharmony_ci    /**
68cb93a386Sopenharmony_ci     *  Force all buffered output to be flushed to the underlying stream.
69cb93a386Sopenharmony_ci     */
70cb93a386Sopenharmony_ci    void flush() {
71cb93a386Sopenharmony_ci        if (fWrite != fBlock) {
72cb93a386Sopenharmony_ci            fStream->write(fBlock, fWrite - fBlock);
73cb93a386Sopenharmony_ci            fWrite = fBlock;
74cb93a386Sopenharmony_ci        }
75cb93a386Sopenharmony_ci    }
76cb93a386Sopenharmony_ci
77cb93a386Sopenharmony_ci    /**
78cb93a386Sopenharmony_ci     *  Append the name (key) portion of an object member. Must be called between beginObject() and
79cb93a386Sopenharmony_ci     *  endObject(). If you have both the name and value of an object member, you can simply call
80cb93a386Sopenharmony_ci     *  the two argument versions of the other append functions.
81cb93a386Sopenharmony_ci     */
82cb93a386Sopenharmony_ci    void appendName(const char* name) {
83cb93a386Sopenharmony_ci        if (!name) {
84cb93a386Sopenharmony_ci            return;
85cb93a386Sopenharmony_ci        }
86cb93a386Sopenharmony_ci        SkASSERT(Scope::kObject == this->scope());
87cb93a386Sopenharmony_ci        SkASSERT(State::kObjectBegin == fState || State::kObjectValue == fState);
88cb93a386Sopenharmony_ci        if (State::kObjectValue == fState) {
89cb93a386Sopenharmony_ci            this->write(",", 1);
90cb93a386Sopenharmony_ci        }
91cb93a386Sopenharmony_ci        this->separator(this->multiline());
92cb93a386Sopenharmony_ci        this->write("\"", 1);
93cb93a386Sopenharmony_ci        this->write(name, strlen(name));
94cb93a386Sopenharmony_ci        this->write("\":", 2);
95cb93a386Sopenharmony_ci        fState = State::kObjectName;
96cb93a386Sopenharmony_ci    }
97cb93a386Sopenharmony_ci
98cb93a386Sopenharmony_ci    /**
99cb93a386Sopenharmony_ci     *  Adds a new object. A name must be supplied when called between beginObject() and
100cb93a386Sopenharmony_ci     *  endObject(). Calls to beginObject() must be balanced by corresponding calls to endObject().
101cb93a386Sopenharmony_ci     *  By default, objects are written out with one named value per line (when in kPretty mode).
102cb93a386Sopenharmony_ci     *  This can be overridden for a particular object by passing false for multiline, this will
103cb93a386Sopenharmony_ci     *  keep the entire object on a single line. This can help with readability in some situations.
104cb93a386Sopenharmony_ci     *  In kFast mode, this parameter is ignored.
105cb93a386Sopenharmony_ci     */
106cb93a386Sopenharmony_ci    void beginObject(const char* name = nullptr, bool multiline = true) {
107cb93a386Sopenharmony_ci        this->appendName(name);
108cb93a386Sopenharmony_ci        this->beginValue(true);
109cb93a386Sopenharmony_ci        this->write("{", 1);
110cb93a386Sopenharmony_ci        fScopeStack.push_back(Scope::kObject);
111cb93a386Sopenharmony_ci        fNewlineStack.push_back(multiline);
112cb93a386Sopenharmony_ci        fState = State::kObjectBegin;
113cb93a386Sopenharmony_ci    }
114cb93a386Sopenharmony_ci
115cb93a386Sopenharmony_ci    /**
116cb93a386Sopenharmony_ci     *  Ends an object that was previously started with beginObject().
117cb93a386Sopenharmony_ci     */
118cb93a386Sopenharmony_ci    void endObject() {
119cb93a386Sopenharmony_ci        SkASSERT(Scope::kObject == this->scope());
120cb93a386Sopenharmony_ci        SkASSERT(State::kObjectBegin == fState || State::kObjectValue == fState);
121cb93a386Sopenharmony_ci        bool emptyObject = State::kObjectBegin == fState;
122cb93a386Sopenharmony_ci        bool wasMultiline = this->multiline();
123cb93a386Sopenharmony_ci        this->popScope();
124cb93a386Sopenharmony_ci        if (!emptyObject) {
125cb93a386Sopenharmony_ci            this->separator(wasMultiline);
126cb93a386Sopenharmony_ci        }
127cb93a386Sopenharmony_ci        this->write("}", 1);
128cb93a386Sopenharmony_ci    }
129cb93a386Sopenharmony_ci
130cb93a386Sopenharmony_ci    /**
131cb93a386Sopenharmony_ci     *  Adds a new array. A name must be supplied when called between beginObject() and
132cb93a386Sopenharmony_ci     *  endObject(). Calls to beginArray() must be balanced by corresponding calls to endArray().
133cb93a386Sopenharmony_ci     *  By default, arrays are written out with one value per line (when in kPretty mode).
134cb93a386Sopenharmony_ci     *  This can be overridden for a particular array by passing false for multiline, this will
135cb93a386Sopenharmony_ci     *  keep the entire array on a single line. This can help with readability in some situations.
136cb93a386Sopenharmony_ci     *  In kFast mode, this parameter is ignored.
137cb93a386Sopenharmony_ci     */
138cb93a386Sopenharmony_ci    void beginArray(const char* name = nullptr, bool multiline = true) {
139cb93a386Sopenharmony_ci        this->appendName(name);
140cb93a386Sopenharmony_ci        this->beginValue(true);
141cb93a386Sopenharmony_ci        this->write("[", 1);
142cb93a386Sopenharmony_ci        fScopeStack.push_back(Scope::kArray);
143cb93a386Sopenharmony_ci        fNewlineStack.push_back(multiline);
144cb93a386Sopenharmony_ci        fState = State::kArrayBegin;
145cb93a386Sopenharmony_ci    }
146cb93a386Sopenharmony_ci
147cb93a386Sopenharmony_ci    /**
148cb93a386Sopenharmony_ci     *  Ends an array that was previous started with beginArray().
149cb93a386Sopenharmony_ci     */
150cb93a386Sopenharmony_ci    void endArray() {
151cb93a386Sopenharmony_ci        SkASSERT(Scope::kArray == this->scope());
152cb93a386Sopenharmony_ci        SkASSERT(State::kArrayBegin == fState || State::kArrayValue == fState);
153cb93a386Sopenharmony_ci        bool emptyArray = State::kArrayBegin == fState;
154cb93a386Sopenharmony_ci        bool wasMultiline = this->multiline();
155cb93a386Sopenharmony_ci        this->popScope();
156cb93a386Sopenharmony_ci        if (!emptyArray) {
157cb93a386Sopenharmony_ci            this->separator(wasMultiline);
158cb93a386Sopenharmony_ci        }
159cb93a386Sopenharmony_ci        this->write("]", 1);
160cb93a386Sopenharmony_ci    }
161cb93a386Sopenharmony_ci
162cb93a386Sopenharmony_ci    /**
163cb93a386Sopenharmony_ci     *  Functions for adding values of various types. The single argument versions add un-named
164cb93a386Sopenharmony_ci     *  values, so must be called either
165cb93a386Sopenharmony_ci     *  - Between beginArray() and endArray()                                -or-
166cb93a386Sopenharmony_ci     *  - Between beginObject() and endObject(), after calling appendName()
167cb93a386Sopenharmony_ci     */
168cb93a386Sopenharmony_ci    void appendString(const char* value) {
169cb93a386Sopenharmony_ci        this->beginValue();
170cb93a386Sopenharmony_ci        this->write("\"", 1);
171cb93a386Sopenharmony_ci        if (value) {
172cb93a386Sopenharmony_ci            while (*value) {
173cb93a386Sopenharmony_ci                switch (*value) {
174cb93a386Sopenharmony_ci                    case '"': this->write("\\\"", 2); break;
175cb93a386Sopenharmony_ci                    case '\\': this->write("\\\\", 2); break;
176cb93a386Sopenharmony_ci                    case '\b': this->write("\\b", 2); break;
177cb93a386Sopenharmony_ci                    case '\f': this->write("\\f", 2); break;
178cb93a386Sopenharmony_ci                    case '\n': this->write("\\n", 2); break;
179cb93a386Sopenharmony_ci                    case '\r': this->write("\\r", 2); break;
180cb93a386Sopenharmony_ci                    case '\t': this->write("\\t", 2); break;
181cb93a386Sopenharmony_ci                    default: this->write(value, 1); break;
182cb93a386Sopenharmony_ci                }
183cb93a386Sopenharmony_ci                value++;
184cb93a386Sopenharmony_ci            }
185cb93a386Sopenharmony_ci        }
186cb93a386Sopenharmony_ci        this->write("\"", 1);
187cb93a386Sopenharmony_ci    }
188cb93a386Sopenharmony_ci
189cb93a386Sopenharmony_ci    void appendPointer(const void* value) { this->beginValue(); this->appendf("\"%p\"", value); }
190cb93a386Sopenharmony_ci    void appendBool(bool value) {
191cb93a386Sopenharmony_ci        this->beginValue();
192cb93a386Sopenharmony_ci        if (value) {
193cb93a386Sopenharmony_ci            this->write("true", 4);
194cb93a386Sopenharmony_ci        } else {
195cb93a386Sopenharmony_ci            this->write("false", 5);
196cb93a386Sopenharmony_ci        }
197cb93a386Sopenharmony_ci    }
198cb93a386Sopenharmony_ci    void appendS32(int32_t value) { this->beginValue(); this->appendf("%d", value); }
199cb93a386Sopenharmony_ci    void appendS64(int64_t value);
200cb93a386Sopenharmony_ci    void appendU32(uint32_t value) { this->beginValue(); this->appendf("%u", value); }
201cb93a386Sopenharmony_ci    void appendU64(uint64_t value);
202cb93a386Sopenharmony_ci    void appendFloat(float value) { this->beginValue(); this->appendf("%g", value); }
203cb93a386Sopenharmony_ci    void appendDouble(double value) { this->beginValue(); this->appendf("%g", value); }
204cb93a386Sopenharmony_ci    void appendFloatDigits(float value, int digits) {
205cb93a386Sopenharmony_ci        this->beginValue();
206cb93a386Sopenharmony_ci        this->appendf("%.*g", digits, value);
207cb93a386Sopenharmony_ci    }
208cb93a386Sopenharmony_ci    void appendDoubleDigits(double value, int digits) {
209cb93a386Sopenharmony_ci        this->beginValue();
210cb93a386Sopenharmony_ci        this->appendf("%.*g", digits, value);
211cb93a386Sopenharmony_ci    }
212cb93a386Sopenharmony_ci    void appendHexU32(uint32_t value) { this->beginValue(); this->appendf("\"0x%x\"", value); }
213cb93a386Sopenharmony_ci    void appendHexU64(uint64_t value);
214cb93a386Sopenharmony_ci
215cb93a386Sopenharmony_ci#define DEFINE_NAMED_APPEND(function, type) \
216cb93a386Sopenharmony_ci    void function(const char* name, type value) { this->appendName(name); this->function(value); }
217cb93a386Sopenharmony_ci
218cb93a386Sopenharmony_ci    /**
219cb93a386Sopenharmony_ci     *  Functions for adding named values of various types. These add a name field, so must be
220cb93a386Sopenharmony_ci     *  called between beginObject() and endObject().
221cb93a386Sopenharmony_ci     */
222cb93a386Sopenharmony_ci    DEFINE_NAMED_APPEND(appendString, const char *)
223cb93a386Sopenharmony_ci    DEFINE_NAMED_APPEND(appendPointer, const void *)
224cb93a386Sopenharmony_ci    DEFINE_NAMED_APPEND(appendBool, bool)
225cb93a386Sopenharmony_ci    DEFINE_NAMED_APPEND(appendS32, int32_t)
226cb93a386Sopenharmony_ci    DEFINE_NAMED_APPEND(appendS64, int64_t)
227cb93a386Sopenharmony_ci    DEFINE_NAMED_APPEND(appendU32, uint32_t)
228cb93a386Sopenharmony_ci    DEFINE_NAMED_APPEND(appendU64, uint64_t)
229cb93a386Sopenharmony_ci    DEFINE_NAMED_APPEND(appendFloat, float)
230cb93a386Sopenharmony_ci    DEFINE_NAMED_APPEND(appendDouble, double)
231cb93a386Sopenharmony_ci    DEFINE_NAMED_APPEND(appendHexU32, uint32_t)
232cb93a386Sopenharmony_ci    DEFINE_NAMED_APPEND(appendHexU64, uint64_t)
233cb93a386Sopenharmony_ci
234cb93a386Sopenharmony_ci#undef DEFINE_NAMED_APPEND
235cb93a386Sopenharmony_ci
236cb93a386Sopenharmony_ci    void appendFloatDigits(const char* name, float value, int digits) {
237cb93a386Sopenharmony_ci        this->appendName(name);
238cb93a386Sopenharmony_ci        this->appendFloatDigits(value, digits);
239cb93a386Sopenharmony_ci    }
240cb93a386Sopenharmony_ci    void appendDoubleDigits(const char* name, double value, int digits) {
241cb93a386Sopenharmony_ci        this->appendName(name);
242cb93a386Sopenharmony_ci        this->appendDoubleDigits(value, digits);
243cb93a386Sopenharmony_ci    }
244cb93a386Sopenharmony_ci
245cb93a386Sopenharmony_ciprivate:
246cb93a386Sopenharmony_ci    enum {
247cb93a386Sopenharmony_ci        // Using a 32k scratch block gives big performance wins, but we diminishing returns going
248cb93a386Sopenharmony_ci        // any larger. Even with a 1MB block, time to write a large (~300 MB) JSON file only drops
249cb93a386Sopenharmony_ci        // another ~10%.
250cb93a386Sopenharmony_ci        kBlockSize = 32 * 1024,
251cb93a386Sopenharmony_ci    };
252cb93a386Sopenharmony_ci
253cb93a386Sopenharmony_ci    enum class Scope {
254cb93a386Sopenharmony_ci        kNone,
255cb93a386Sopenharmony_ci        kObject,
256cb93a386Sopenharmony_ci        kArray
257cb93a386Sopenharmony_ci    };
258cb93a386Sopenharmony_ci
259cb93a386Sopenharmony_ci    enum class State {
260cb93a386Sopenharmony_ci        kStart,
261cb93a386Sopenharmony_ci        kEnd,
262cb93a386Sopenharmony_ci        kObjectBegin,
263cb93a386Sopenharmony_ci        kObjectName,
264cb93a386Sopenharmony_ci        kObjectValue,
265cb93a386Sopenharmony_ci        kArrayBegin,
266cb93a386Sopenharmony_ci        kArrayValue,
267cb93a386Sopenharmony_ci    };
268cb93a386Sopenharmony_ci
269cb93a386Sopenharmony_ci    void appendf(const char* fmt, ...);
270cb93a386Sopenharmony_ci
271cb93a386Sopenharmony_ci    void beginValue(bool structure = false) {
272cb93a386Sopenharmony_ci        SkASSERT(State::kObjectName == fState ||
273cb93a386Sopenharmony_ci                 State::kArrayBegin == fState ||
274cb93a386Sopenharmony_ci                 State::kArrayValue == fState ||
275cb93a386Sopenharmony_ci                 (structure && State::kStart == fState));
276cb93a386Sopenharmony_ci        if (State::kArrayValue == fState) {
277cb93a386Sopenharmony_ci            this->write(",", 1);
278cb93a386Sopenharmony_ci        }
279cb93a386Sopenharmony_ci        if (Scope::kArray == this->scope()) {
280cb93a386Sopenharmony_ci            this->separator(this->multiline());
281cb93a386Sopenharmony_ci        } else if (Scope::kObject == this->scope() && Mode::kPretty == fMode) {
282cb93a386Sopenharmony_ci            this->write(" ", 1);
283cb93a386Sopenharmony_ci        }
284cb93a386Sopenharmony_ci        // We haven't added the value yet, but all (non-structure) callers emit something
285cb93a386Sopenharmony_ci        // immediately, so transition state, to simplify the calling code.
286cb93a386Sopenharmony_ci        if (!structure) {
287cb93a386Sopenharmony_ci            fState = Scope::kArray == this->scope() ? State::kArrayValue : State::kObjectValue;
288cb93a386Sopenharmony_ci        }
289cb93a386Sopenharmony_ci    }
290cb93a386Sopenharmony_ci
291cb93a386Sopenharmony_ci    void separator(bool multiline) {
292cb93a386Sopenharmony_ci        if (Mode::kPretty == fMode) {
293cb93a386Sopenharmony_ci            if (multiline) {
294cb93a386Sopenharmony_ci                this->write("\n", 1);
295cb93a386Sopenharmony_ci                for (int i = 0; i < fScopeStack.count() - 1; ++i) {
296cb93a386Sopenharmony_ci                    this->write("   ", 3);
297cb93a386Sopenharmony_ci                }
298cb93a386Sopenharmony_ci            } else {
299cb93a386Sopenharmony_ci                this->write(" ", 1);
300cb93a386Sopenharmony_ci            }
301cb93a386Sopenharmony_ci        }
302cb93a386Sopenharmony_ci    }
303cb93a386Sopenharmony_ci
304cb93a386Sopenharmony_ci    void write(const char* buf, size_t length) {
305cb93a386Sopenharmony_ci        if (static_cast<size_t>(fBlockEnd - fWrite) < length) {
306cb93a386Sopenharmony_ci            // Don't worry about splitting writes that overflow our block.
307cb93a386Sopenharmony_ci            this->flush();
308cb93a386Sopenharmony_ci        }
309cb93a386Sopenharmony_ci        if (length > kBlockSize) {
310cb93a386Sopenharmony_ci            // Send particularly large writes straight through to the stream (unbuffered).
311cb93a386Sopenharmony_ci            fStream->write(buf, length);
312cb93a386Sopenharmony_ci        } else {
313cb93a386Sopenharmony_ci            memcpy(fWrite, buf, length);
314cb93a386Sopenharmony_ci            fWrite += length;
315cb93a386Sopenharmony_ci        }
316cb93a386Sopenharmony_ci    }
317cb93a386Sopenharmony_ci
318cb93a386Sopenharmony_ci    Scope scope() const {
319cb93a386Sopenharmony_ci        SkASSERT(!fScopeStack.empty());
320cb93a386Sopenharmony_ci        return fScopeStack.back();
321cb93a386Sopenharmony_ci    }
322cb93a386Sopenharmony_ci
323cb93a386Sopenharmony_ci    bool multiline() const {
324cb93a386Sopenharmony_ci        SkASSERT(!fNewlineStack.empty());
325cb93a386Sopenharmony_ci        return fNewlineStack.back();
326cb93a386Sopenharmony_ci    }
327cb93a386Sopenharmony_ci
328cb93a386Sopenharmony_ci    void popScope() {
329cb93a386Sopenharmony_ci        fScopeStack.pop_back();
330cb93a386Sopenharmony_ci        fNewlineStack.pop_back();
331cb93a386Sopenharmony_ci        switch (this->scope()) {
332cb93a386Sopenharmony_ci            case Scope::kNone:
333cb93a386Sopenharmony_ci                fState = State::kEnd;
334cb93a386Sopenharmony_ci                break;
335cb93a386Sopenharmony_ci            case Scope::kObject:
336cb93a386Sopenharmony_ci                fState = State::kObjectValue;
337cb93a386Sopenharmony_ci                break;
338cb93a386Sopenharmony_ci            case Scope::kArray:
339cb93a386Sopenharmony_ci                fState = State::kArrayValue;
340cb93a386Sopenharmony_ci                break;
341cb93a386Sopenharmony_ci            default:
342cb93a386Sopenharmony_ci                SkDEBUGFAIL("Invalid scope");
343cb93a386Sopenharmony_ci                break;
344cb93a386Sopenharmony_ci        }
345cb93a386Sopenharmony_ci    }
346cb93a386Sopenharmony_ci
347cb93a386Sopenharmony_ci    char* fBlock;
348cb93a386Sopenharmony_ci    char* fWrite;
349cb93a386Sopenharmony_ci    char* fBlockEnd;
350cb93a386Sopenharmony_ci
351cb93a386Sopenharmony_ci    SkWStream* fStream;
352cb93a386Sopenharmony_ci    Mode fMode;
353cb93a386Sopenharmony_ci    State fState;
354cb93a386Sopenharmony_ci    SkSTArray<16, Scope, true> fScopeStack;
355cb93a386Sopenharmony_ci    SkSTArray<16, bool, true> fNewlineStack;
356cb93a386Sopenharmony_ci};
357cb93a386Sopenharmony_ci
358cb93a386Sopenharmony_ci#endif
359