blob: 9faf83615689abc4dfaf2f533a6a478ee655df0b [file] [log] [blame]
ethannicholasb3058bd2016-07-01 08:22:01 -07001/*
2 * Copyright 2016 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 */
Mike Klein6ad99092016-10-26 10:35:22 -04007
ethannicholasb3058bd2016-07-01 08:22:01 -07008#include "SkSLCompiler.h"
9
ethannicholas5961bc92016-10-12 06:39:56 -070010#include "ast/SkSLASTPrecision.h"
ethannicholas22f939e2016-10-13 13:25:34 -070011#include "SkSLCFGGenerator.h"
Ethan Nicholasce33f102016-12-09 17:22:59 -050012#include "SkSLGLSLCodeGenerator.h"
ethannicholasb3058bd2016-07-01 08:22:01 -070013#include "SkSLIRGenerator.h"
14#include "SkSLParser.h"
15#include "SkSLSPIRVCodeGenerator.h"
16#include "ir/SkSLExpression.h"
17#include "ir/SkSLIntLiteral.h"
ethannicholas5961bc92016-10-12 06:39:56 -070018#include "ir/SkSLModifiersDeclaration.h"
ethannicholasb3058bd2016-07-01 08:22:01 -070019#include "ir/SkSLSymbolTable.h"
ethannicholasddb37d62016-10-20 09:54:00 -070020#include "ir/SkSLUnresolvedFunction.h"
ethannicholas22f939e2016-10-13 13:25:34 -070021#include "ir/SkSLVarDeclarations.h"
ethannicholasb3058bd2016-07-01 08:22:01 -070022#include "SkMutex.h"
23
24#define STRINGIFY(x) #x
25
26// include the built-in shader symbols as static strings
27
ethannicholas5961bc92016-10-12 06:39:56 -070028static const char* SKSL_INCLUDE =
ethannicholasb3058bd2016-07-01 08:22:01 -070029#include "sksl.include"
30;
31
ethannicholas5961bc92016-10-12 06:39:56 -070032static const char* SKSL_VERT_INCLUDE =
ethannicholasb3058bd2016-07-01 08:22:01 -070033#include "sksl_vert.include"
34;
35
ethannicholas5961bc92016-10-12 06:39:56 -070036static const char* SKSL_FRAG_INCLUDE =
ethannicholasb3058bd2016-07-01 08:22:01 -070037#include "sksl_frag.include"
38;
39
40namespace SkSL {
41
Mike Klein6ad99092016-10-26 10:35:22 -040042Compiler::Compiler()
ethannicholasb3058bd2016-07-01 08:22:01 -070043: fErrorCount(0) {
44 auto types = std::shared_ptr<SymbolTable>(new SymbolTable(*this));
45 auto symbols = std::shared_ptr<SymbolTable>(new SymbolTable(types, *this));
ethannicholasd598f792016-07-25 10:08:54 -070046 fIRGenerator = new IRGenerator(&fContext, symbols, *this);
ethannicholasb3058bd2016-07-01 08:22:01 -070047 fTypes = types;
ethannicholasd598f792016-07-25 10:08:54 -070048 #define ADD_TYPE(t) types->addWithoutOwnership(fContext.f ## t ## _Type->fName, \
49 fContext.f ## t ## _Type.get())
ethannicholasb3058bd2016-07-01 08:22:01 -070050 ADD_TYPE(Void);
51 ADD_TYPE(Float);
52 ADD_TYPE(Vec2);
53 ADD_TYPE(Vec3);
54 ADD_TYPE(Vec4);
55 ADD_TYPE(Double);
56 ADD_TYPE(DVec2);
57 ADD_TYPE(DVec3);
58 ADD_TYPE(DVec4);
59 ADD_TYPE(Int);
60 ADD_TYPE(IVec2);
61 ADD_TYPE(IVec3);
62 ADD_TYPE(IVec4);
63 ADD_TYPE(UInt);
64 ADD_TYPE(UVec2);
65 ADD_TYPE(UVec3);
66 ADD_TYPE(UVec4);
67 ADD_TYPE(Bool);
68 ADD_TYPE(BVec2);
69 ADD_TYPE(BVec3);
70 ADD_TYPE(BVec4);
71 ADD_TYPE(Mat2x2);
Ethan Nicholas9e1138d2016-11-21 10:39:35 -050072 types->addWithoutOwnership(SkString("mat2x2"), fContext.fMat2x2_Type.get());
ethannicholasb3058bd2016-07-01 08:22:01 -070073 ADD_TYPE(Mat2x3);
74 ADD_TYPE(Mat2x4);
75 ADD_TYPE(Mat3x2);
76 ADD_TYPE(Mat3x3);
Ethan Nicholas9e1138d2016-11-21 10:39:35 -050077 types->addWithoutOwnership(SkString("mat3x3"), fContext.fMat3x3_Type.get());
ethannicholasb3058bd2016-07-01 08:22:01 -070078 ADD_TYPE(Mat3x4);
79 ADD_TYPE(Mat4x2);
80 ADD_TYPE(Mat4x3);
81 ADD_TYPE(Mat4x4);
Ethan Nicholas9e1138d2016-11-21 10:39:35 -050082 types->addWithoutOwnership(SkString("mat4x4"), fContext.fMat4x4_Type.get());
ethannicholasb3058bd2016-07-01 08:22:01 -070083 ADD_TYPE(GenType);
84 ADD_TYPE(GenDType);
85 ADD_TYPE(GenIType);
86 ADD_TYPE(GenUType);
87 ADD_TYPE(GenBType);
88 ADD_TYPE(Mat);
89 ADD_TYPE(Vec);
90 ADD_TYPE(GVec);
91 ADD_TYPE(GVec2);
92 ADD_TYPE(GVec3);
93 ADD_TYPE(GVec4);
94 ADD_TYPE(DVec);
95 ADD_TYPE(IVec);
96 ADD_TYPE(UVec);
97 ADD_TYPE(BVec);
98
99 ADD_TYPE(Sampler1D);
100 ADD_TYPE(Sampler2D);
101 ADD_TYPE(Sampler3D);
ethannicholas5961bc92016-10-12 06:39:56 -0700102 ADD_TYPE(SamplerExternalOES);
ethannicholasb3058bd2016-07-01 08:22:01 -0700103 ADD_TYPE(SamplerCube);
104 ADD_TYPE(Sampler2DRect);
105 ADD_TYPE(Sampler1DArray);
106 ADD_TYPE(Sampler2DArray);
107 ADD_TYPE(SamplerCubeArray);
108 ADD_TYPE(SamplerBuffer);
109 ADD_TYPE(Sampler2DMS);
110 ADD_TYPE(Sampler2DMSArray);
111
Brian Salomonbf7b6202016-11-11 16:08:03 -0500112 ADD_TYPE(ISampler2D);
113
Brian Salomon2a51de82016-11-16 12:06:01 -0500114 ADD_TYPE(Image2D);
115 ADD_TYPE(IImage2D);
116
Greg Daniel64773e62016-11-22 09:44:03 -0500117 ADD_TYPE(SubpassInput);
118 ADD_TYPE(SubpassInputMS);
119
ethannicholasb3058bd2016-07-01 08:22:01 -0700120 ADD_TYPE(GSampler1D);
121 ADD_TYPE(GSampler2D);
122 ADD_TYPE(GSampler3D);
123 ADD_TYPE(GSamplerCube);
124 ADD_TYPE(GSampler2DRect);
125 ADD_TYPE(GSampler1DArray);
126 ADD_TYPE(GSampler2DArray);
127 ADD_TYPE(GSamplerCubeArray);
128 ADD_TYPE(GSamplerBuffer);
129 ADD_TYPE(GSampler2DMS);
130 ADD_TYPE(GSampler2DMSArray);
131
132 ADD_TYPE(Sampler1DShadow);
133 ADD_TYPE(Sampler2DShadow);
134 ADD_TYPE(SamplerCubeShadow);
135 ADD_TYPE(Sampler2DRectShadow);
136 ADD_TYPE(Sampler1DArrayShadow);
137 ADD_TYPE(Sampler2DArrayShadow);
138 ADD_TYPE(SamplerCubeArrayShadow);
139 ADD_TYPE(GSampler2DArrayShadow);
140 ADD_TYPE(GSamplerCubeArrayShadow);
141
Ethan Nicholas3605ace2016-11-21 15:59:48 -0500142 SkString skCapsName("sk_Caps");
143 Variable* skCaps = new Variable(Position(), Modifiers(), skCapsName,
144 *fContext.fSkCaps_Type, Variable::kGlobal_Storage);
145 fIRGenerator->fSymbolTable->add(skCapsName, std::unique_ptr<Symbol>(skCaps));
146
ethannicholas5961bc92016-10-12 06:39:56 -0700147 Modifiers::Flag ignored1;
148 std::vector<std::unique_ptr<ProgramElement>> ignored2;
Ethan Nicholas9e1138d2016-11-21 10:39:35 -0500149 this->internalConvertProgram(SkString(SKSL_INCLUDE), &ignored1, &ignored2);
ethannicholasddb37d62016-10-20 09:54:00 -0700150 fIRGenerator->fSymbolTable->markAllFunctionsBuiltin();
ethannicholasb3058bd2016-07-01 08:22:01 -0700151 ASSERT(!fErrorCount);
152}
153
154Compiler::~Compiler() {
155 delete fIRGenerator;
156}
157
ethannicholas22f939e2016-10-13 13:25:34 -0700158// add the definition created by assigning to the lvalue to the definition set
159void Compiler::addDefinition(const Expression* lvalue, const Expression* expr,
160 std::unordered_map<const Variable*, const Expression*>* definitions) {
161 switch (lvalue->fKind) {
162 case Expression::kVariableReference_Kind: {
163 const Variable& var = ((VariableReference*) lvalue)->fVariable;
164 if (var.fStorage == Variable::kLocal_Storage) {
165 (*definitions)[&var] = expr;
166 }
167 break;
168 }
169 case Expression::kSwizzle_Kind:
170 // We consider the variable written to as long as at least some of its components have
171 // been written to. This will lead to some false negatives (we won't catch it if you
172 // write to foo.x and then read foo.y), but being stricter could lead to false positives
Mike Klein6ad99092016-10-26 10:35:22 -0400173 // (we write to foo.x, and then pass foo to a function which happens to only read foo.x,
174 // but since we pass foo as a whole it is flagged as an error) unless we perform a much
ethannicholas22f939e2016-10-13 13:25:34 -0700175 // more complicated whole-program analysis. This is probably good enough.
Mike Klein6ad99092016-10-26 10:35:22 -0400176 this->addDefinition(((Swizzle*) lvalue)->fBase.get(),
177 fContext.fDefined_Expression.get(),
ethannicholas22f939e2016-10-13 13:25:34 -0700178 definitions);
179 break;
180 case Expression::kIndex_Kind:
181 // see comments in Swizzle
Mike Klein6ad99092016-10-26 10:35:22 -0400182 this->addDefinition(((IndexExpression*) lvalue)->fBase.get(),
183 fContext.fDefined_Expression.get(),
ethannicholas22f939e2016-10-13 13:25:34 -0700184 definitions);
185 break;
186 case Expression::kFieldAccess_Kind:
187 // see comments in Swizzle
Mike Klein6ad99092016-10-26 10:35:22 -0400188 this->addDefinition(((FieldAccess*) lvalue)->fBase.get(),
189 fContext.fDefined_Expression.get(),
ethannicholas22f939e2016-10-13 13:25:34 -0700190 definitions);
191 break;
192 default:
193 // not an lvalue, can't happen
194 ASSERT(false);
195 }
196}
197
198// add local variables defined by this node to the set
Mike Klein6ad99092016-10-26 10:35:22 -0400199void Compiler::addDefinitions(const BasicBlock::Node& node,
ethannicholas22f939e2016-10-13 13:25:34 -0700200 std::unordered_map<const Variable*, const Expression*>* definitions) {
201 switch (node.fKind) {
202 case BasicBlock::Node::kExpression_Kind: {
203 const Expression* expr = (Expression*) node.fNode;
204 if (expr->fKind == Expression::kBinary_Kind) {
205 const BinaryExpression* b = (BinaryExpression*) expr;
206 if (b->fOperator == Token::EQ) {
207 this->addDefinition(b->fLeft.get(), b->fRight.get(), definitions);
208 }
209 }
210 break;
211 }
212 case BasicBlock::Node::kStatement_Kind: {
213 const Statement* stmt = (Statement*) node.fNode;
214 if (stmt->fKind == Statement::kVarDeclarations_Kind) {
215 const VarDeclarationsStatement* vd = (VarDeclarationsStatement*) stmt;
216 for (const VarDeclaration& decl : vd->fDeclaration->fVars) {
217 if (decl.fValue) {
218 (*definitions)[decl.fVar] = decl.fValue.get();
219 }
220 }
221 }
222 break;
223 }
224 }
225}
226
227void Compiler::scanCFG(CFG* cfg, BlockId blockId, std::set<BlockId>* workList) {
228 BasicBlock& block = cfg->fBlocks[blockId];
229
230 // compute definitions after this block
231 std::unordered_map<const Variable*, const Expression*> after = block.fBefore;
232 for (const BasicBlock::Node& n : block.fNodes) {
233 this->addDefinitions(n, &after);
234 }
235
236 // propagate definitions to exits
237 for (BlockId exitId : block.fExits) {
238 BasicBlock& exit = cfg->fBlocks[exitId];
239 for (const auto& pair : after) {
240 const Expression* e1 = pair.second;
241 if (exit.fBefore.find(pair.first) == exit.fBefore.end()) {
242 exit.fBefore[pair.first] = e1;
243 } else {
244 const Expression* e2 = exit.fBefore[pair.first];
245 if (e1 != e2) {
246 // definition has changed, merge and add exit block to worklist
247 workList->insert(exitId);
248 if (!e1 || !e2) {
249 exit.fBefore[pair.first] = nullptr;
250 } else {
251 exit.fBefore[pair.first] = fContext.fDefined_Expression.get();
252 }
253 }
254 }
255 }
256 }
257}
258
259// returns a map which maps all local variables in the function to null, indicating that their value
260// is initially unknown
261static std::unordered_map<const Variable*, const Expression*> compute_start_state(const CFG& cfg) {
262 std::unordered_map<const Variable*, const Expression*> result;
Mike Klein6ad99092016-10-26 10:35:22 -0400263 for (const auto& block : cfg.fBlocks) {
264 for (const auto& node : block.fNodes) {
ethannicholas22f939e2016-10-13 13:25:34 -0700265 if (node.fKind == BasicBlock::Node::kStatement_Kind) {
266 const Statement* s = (Statement*) node.fNode;
267 if (s->fKind == Statement::kVarDeclarations_Kind) {
268 const VarDeclarationsStatement* vd = (const VarDeclarationsStatement*) s;
269 for (const VarDeclaration& decl : vd->fDeclaration->fVars) {
270 result[decl.fVar] = nullptr;
Mike Klein6ad99092016-10-26 10:35:22 -0400271 }
ethannicholas22f939e2016-10-13 13:25:34 -0700272 }
273 }
274 }
275 }
276 return result;
277}
278
279void Compiler::scanCFG(const FunctionDefinition& f) {
280 CFG cfg = CFGGenerator().getCFG(f);
281
282 // compute the data flow
283 cfg.fBlocks[cfg.fStart].fBefore = compute_start_state(cfg);
284 std::set<BlockId> workList;
285 for (BlockId i = 0; i < cfg.fBlocks.size(); i++) {
286 workList.insert(i);
287 }
288 while (workList.size()) {
289 BlockId next = *workList.begin();
290 workList.erase(workList.begin());
291 this->scanCFG(&cfg, next, &workList);
292 }
293
294 // check for unreachable code
295 for (size_t i = 0; i < cfg.fBlocks.size(); i++) {
Mike Klein6ad99092016-10-26 10:35:22 -0400296 if (i != cfg.fStart && !cfg.fBlocks[i].fEntrances.size() &&
ethannicholas22f939e2016-10-13 13:25:34 -0700297 cfg.fBlocks[i].fNodes.size()) {
Ethan Nicholas9e1138d2016-11-21 10:39:35 -0500298 this->error(cfg.fBlocks[i].fNodes[0].fNode->fPosition, SkString("unreachable"));
ethannicholas22f939e2016-10-13 13:25:34 -0700299 }
300 }
301 if (fErrorCount) {
302 return;
303 }
304
305 // check for undefined variables
306 for (const BasicBlock& b : cfg.fBlocks) {
307 std::unordered_map<const Variable*, const Expression*> definitions = b.fBefore;
308 for (const BasicBlock::Node& n : b.fNodes) {
309 if (n.fKind == BasicBlock::Node::kExpression_Kind) {
310 const Expression* expr = (const Expression*) n.fNode;
311 if (expr->fKind == Expression::kVariableReference_Kind) {
312 const Variable& var = ((VariableReference*) expr)->fVariable;
Mike Klein6ad99092016-10-26 10:35:22 -0400313 if (var.fStorage == Variable::kLocal_Storage &&
ethannicholas22f939e2016-10-13 13:25:34 -0700314 !definitions[&var]) {
315 this->error(expr->fPosition,
316 "'" + var.fName + "' has not been assigned");
Mike Klein6ad99092016-10-26 10:35:22 -0400317 }
ethannicholas22f939e2016-10-13 13:25:34 -0700318 }
319 }
320 this->addDefinitions(n, &definitions);
321 }
322 }
323
324 // check for missing return
325 if (f.fDeclaration.fReturnType != *fContext.fVoid_Type) {
326 if (cfg.fBlocks[cfg.fExit].fEntrances.size()) {
Ethan Nicholas9e1138d2016-11-21 10:39:35 -0500327 this->error(f.fPosition, SkString("function can exit without returning a value"));
ethannicholas22f939e2016-10-13 13:25:34 -0700328 }
329 }
330}
331
Ethan Nicholas9e1138d2016-11-21 10:39:35 -0500332void Compiler::internalConvertProgram(SkString text,
ethannicholas5961bc92016-10-12 06:39:56 -0700333 Modifiers::Flag* defaultPrecision,
ethannicholasb3058bd2016-07-01 08:22:01 -0700334 std::vector<std::unique_ptr<ProgramElement>>* result) {
335 Parser parser(text, *fTypes, *this);
336 std::vector<std::unique_ptr<ASTDeclaration>> parsed = parser.file();
337 if (fErrorCount) {
338 return;
339 }
ethannicholas5961bc92016-10-12 06:39:56 -0700340 *defaultPrecision = Modifiers::kHighp_Flag;
ethannicholasb3058bd2016-07-01 08:22:01 -0700341 for (size_t i = 0; i < parsed.size(); i++) {
342 ASTDeclaration& decl = *parsed[i];
343 switch (decl.fKind) {
344 case ASTDeclaration::kVar_Kind: {
ethannicholas14fe8cc2016-09-07 13:37:16 -0700345 std::unique_ptr<VarDeclarations> s = fIRGenerator->convertVarDeclarations(
Mike Klein6ad99092016-10-26 10:35:22 -0400346 (ASTVarDeclarations&) decl,
ethannicholasb3058bd2016-07-01 08:22:01 -0700347 Variable::kGlobal_Storage);
348 if (s) {
349 result->push_back(std::move(s));
350 }
351 break;
352 }
353 case ASTDeclaration::kFunction_Kind: {
354 std::unique_ptr<FunctionDefinition> f = fIRGenerator->convertFunction(
355 (ASTFunction&) decl);
ethannicholas22f939e2016-10-13 13:25:34 -0700356 if (!fErrorCount && f) {
357 this->scanCFG(*f);
ethannicholasb3058bd2016-07-01 08:22:01 -0700358 result->push_back(std::move(f));
359 }
360 break;
361 }
ethannicholas5961bc92016-10-12 06:39:56 -0700362 case ASTDeclaration::kModifiers_Kind: {
363 std::unique_ptr<ModifiersDeclaration> f = fIRGenerator->convertModifiersDeclaration(
364 (ASTModifiersDeclaration&) decl);
365 if (f) {
366 result->push_back(std::move(f));
367 }
368 break;
369 }
ethannicholasb3058bd2016-07-01 08:22:01 -0700370 case ASTDeclaration::kInterfaceBlock_Kind: {
371 std::unique_ptr<InterfaceBlock> i = fIRGenerator->convertInterfaceBlock(
372 (ASTInterfaceBlock&) decl);
373 if (i) {
374 result->push_back(std::move(i));
375 }
376 break;
377 }
378 case ASTDeclaration::kExtension_Kind: {
379 std::unique_ptr<Extension> e = fIRGenerator->convertExtension((ASTExtension&) decl);
380 if (e) {
381 result->push_back(std::move(e));
382 }
383 break;
384 }
ethannicholas5961bc92016-10-12 06:39:56 -0700385 case ASTDeclaration::kPrecision_Kind: {
386 *defaultPrecision = ((ASTPrecision&) decl).fPrecision;
387 break;
388 }
ethannicholasb3058bd2016-07-01 08:22:01 -0700389 default:
390 ABORT("unsupported declaration: %s\n", decl.description().c_str());
391 }
392 }
393}
394
Ethan Nicholas3605ace2016-11-21 15:59:48 -0500395std::unique_ptr<Program> Compiler::convertProgram(Program::Kind kind, SkString text,
Ethan Nicholasce33f102016-12-09 17:22:59 -0500396 const Program::Settings& settings) {
ethannicholasb3058bd2016-07-01 08:22:01 -0700397 fErrorText = "";
398 fErrorCount = 0;
Ethan Nicholasce33f102016-12-09 17:22:59 -0500399 fIRGenerator->start(&settings);
ethannicholasd598f792016-07-25 10:08:54 -0700400 std::vector<std::unique_ptr<ProgramElement>> elements;
ethannicholas5961bc92016-10-12 06:39:56 -0700401 Modifiers::Flag ignored;
ethannicholasb3058bd2016-07-01 08:22:01 -0700402 switch (kind) {
403 case Program::kVertex_Kind:
Ethan Nicholas9e1138d2016-11-21 10:39:35 -0500404 this->internalConvertProgram(SkString(SKSL_VERT_INCLUDE), &ignored, &elements);
ethannicholasb3058bd2016-07-01 08:22:01 -0700405 break;
406 case Program::kFragment_Kind:
Ethan Nicholas9e1138d2016-11-21 10:39:35 -0500407 this->internalConvertProgram(SkString(SKSL_FRAG_INCLUDE), &ignored, &elements);
ethannicholasb3058bd2016-07-01 08:22:01 -0700408 break;
409 }
ethannicholasddb37d62016-10-20 09:54:00 -0700410 fIRGenerator->fSymbolTable->markAllFunctionsBuiltin();
ethannicholas5961bc92016-10-12 06:39:56 -0700411 Modifiers::Flag defaultPrecision;
412 this->internalConvertProgram(text, &defaultPrecision, &elements);
Ethan Nicholasce33f102016-12-09 17:22:59 -0500413 auto result = std::unique_ptr<Program>(new Program(kind, settings, defaultPrecision, &fContext,
414 std::move(elements),
415 fIRGenerator->fSymbolTable,
416 fIRGenerator->fInputs));
Ethan Nicholas3605ace2016-11-21 15:59:48 -0500417 fIRGenerator->finish();
ethannicholasb3058bd2016-07-01 08:22:01 -0700418 this->writeErrorCount();
Ethan Nicholasce33f102016-12-09 17:22:59 -0500419 if (fErrorCount) {
420 return nullptr;
421 }
422 return result;
423}
424
425
426bool Compiler::toSPIRV(const Program& program, SkWStream& out) {
427 SPIRVCodeGenerator cg(&fContext, &program, this, &out);
428 bool result = cg.generateCode();
429 this->writeErrorCount();
ethannicholasd598f792016-07-25 10:08:54 -0700430 return result;
ethannicholasb3058bd2016-07-01 08:22:01 -0700431}
432
Ethan Nicholasce33f102016-12-09 17:22:59 -0500433bool Compiler::toSPIRV(const Program& program, SkString* out) {
434 SkDynamicMemoryWStream buffer;
435 bool result = this->toSPIRV(program, buffer);
436 if (result) {
437 sk_sp<SkData> data(buffer.detachAsData());
438 *out = SkString((const char*) data->data(), data->size());
439 }
440 return result;
441}
442
443bool Compiler::toGLSL(const Program& program, SkWStream& out) {
444 GLSLCodeGenerator cg(&fContext, &program, this, &out);
445 bool result = cg.generateCode();
446 this->writeErrorCount();
447 return result;
448}
449
450bool Compiler::toGLSL(const Program& program, SkString* out) {
451 SkDynamicMemoryWStream buffer;
452 bool result = this->toGLSL(program, buffer);
453 if (result) {
454 sk_sp<SkData> data(buffer.detachAsData());
455 *out = SkString((const char*) data->data(), data->size());
456 }
457 return result;
458}
459
460
Ethan Nicholas9e1138d2016-11-21 10:39:35 -0500461void Compiler::error(Position position, SkString msg) {
ethannicholasb3058bd2016-07-01 08:22:01 -0700462 fErrorCount++;
463 fErrorText += "error: " + position.description() + ": " + msg.c_str() + "\n";
464}
465
Ethan Nicholas9e1138d2016-11-21 10:39:35 -0500466SkString Compiler::errorText() {
467 SkString result = fErrorText;
ethannicholasb3058bd2016-07-01 08:22:01 -0700468 return result;
469}
470
471void Compiler::writeErrorCount() {
472 if (fErrorCount) {
473 fErrorText += to_string(fErrorCount) + " error";
474 if (fErrorCount > 1) {
475 fErrorText += "s";
476 }
477 fErrorText += "\n";
478 }
479}
480
ethannicholasb3058bd2016-07-01 08:22:01 -0700481} // namespace