Reland "Use SkSL "offset" to actually mean "line""

This reverts commit a909dd6b8d8d6540f382e848c959792379f19365.

Turns out those reportPendingErrors() calls I removed were in fact
necessary, just not on any of the CQ bots.

Change-Id: I8be0898ac0b41dbb703a35f705cac06ca716c0b7
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/453077
Reviewed-by: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
diff --git a/include/sksl/DSLVar.h b/include/sksl/DSLVar.h
index 059bbab..16343ec 100644
--- a/include/sksl/DSLVar.h
+++ b/include/sksl/DSLVar.h
@@ -60,57 +60,57 @@
     virtual VariableStorage storage() const = 0;
 
     DSLExpression x() {
-        return DSLExpression(*this).x();
+        return DSLExpression(*this, PositionInfo()).x();
     }
 
     DSLExpression y() {
-        return DSLExpression(*this).y();
+        return DSLExpression(*this, PositionInfo()).y();
     }
 
     DSLExpression z() {
-        return DSLExpression(*this).z();
+        return DSLExpression(*this, PositionInfo()).z();
     }
 
     DSLExpression w() {
-        return DSLExpression(*this).w();
+        return DSLExpression(*this, PositionInfo()).w();
     }
 
     DSLExpression r() {
-        return DSLExpression(*this).r();
+        return DSLExpression(*this, PositionInfo()).r();
     }
 
     DSLExpression g() {
-        return DSLExpression(*this).g();
+        return DSLExpression(*this, PositionInfo()).g();
     }
 
     DSLExpression b() {
-        return DSLExpression(*this).b();
+        return DSLExpression(*this, PositionInfo()).b();
     }
 
     DSLExpression a() {
-        return DSLExpression(*this).a();
+        return DSLExpression(*this, PositionInfo()).a();
     }
 
     DSLExpression field(skstd::string_view name) {
-        return DSLExpression(*this).field(name);
+        return DSLExpression(*this, PositionInfo()).field(name);
     }
 
     DSLPossibleExpression operator[](DSLExpression&& index);
 
     DSLPossibleExpression operator++() {
-        return ++DSLExpression(*this);
+        return ++DSLExpression(*this, PositionInfo());
     }
 
     DSLPossibleExpression operator++(int) {
-        return DSLExpression(*this)++;
+        return DSLExpression(*this, PositionInfo())++;
     }
 
     DSLPossibleExpression operator--() {
-        return --DSLExpression(*this);
+        return --DSLExpression(*this, PositionInfo());
     }
 
     DSLPossibleExpression operator--(int) {
-        return DSLExpression(*this)--;
+        return DSLExpression(*this, PositionInfo())--;
     }
 
 protected:
diff --git a/include/sksl/SkSLErrorReporter.h b/include/sksl/SkSLErrorReporter.h
index 2c90ad8..af949c3 100644
--- a/include/sksl/SkSLErrorReporter.h
+++ b/include/sksl/SkSLErrorReporter.h
@@ -35,40 +35,21 @@
     static PositionInfo Capture() { return PositionInfo(); }
 #endif // __has_builtin(__builtin_FILE) && __has_builtin(__builtin_LINE)
 
-    static PositionInfo Offset(const char* file, const char* text, int offset) {
-        PositionInfo result(file, -1);
-        result.fText = text;
-        result.fOffset = offset;
-        return result;
-    }
-
     const char* file_name() const {
         return fFile;
     }
 
     int line() {
-        if (fLine == -1) {
-            if (fOffset == -1 || !fText) {
-                return -1;
-            }
-            fLine = 1;
-            for (int i = 0; i < fOffset; i++) {
-                if (fText[i] == '\n') {
-                    ++fLine;
-                }
-            }
-        }
         return fLine;
     }
 
+    // Temporary method until we finish replacing offset with line
     int offset() {
-        return fOffset;
+        return fLine;
     }
 
 private:
     const char* fFile = nullptr;
-    const char* fText = nullptr;
-    int32_t fOffset = -1;
     int32_t fLine = -1;
 };
 
diff --git a/src/sksl/SkSLDSLParser.cpp b/src/sksl/SkSLDSLParser.cpp
index 439aa08..8b283e9 100644
--- a/src/sksl/SkSLDSLParser.cpp
+++ b/src/sksl/SkSLDSLParser.cpp
@@ -103,7 +103,7 @@
     , fSettings(settings)
     , fKind(kind)
     , fText(std::make_unique<String>(std::move(text)))
-    , fPushback(Token::Kind::TK_NONE, -1, -1) {
+    , fPushback(Token::Kind::TK_NONE, /*offset=*/-1, /*length=*/-1, /*line=*/-1) {
     // We don't want to have to worry about manually releasing all of the objects in the event that
     // an error occurs
     fSettings.fAssertDSLObjectsReleased = false;
@@ -193,15 +193,15 @@
 }
 
 PositionInfo DSLParser::position(Token t) {
-    return this->position(t.fOffset);
+    return this->position(t.fLine);
 }
 
 PositionInfo DSLParser::position(int offset) {
-    return PositionInfo::Offset("<unknown>", fText->c_str(), offset);
+    return PositionInfo("<unknown>", offset);
 }
 
 void DSLParser::error(Token token, String msg) {
-    this->error(token.fOffset, msg);
+    this->error(token.fLine, msg);
 }
 
 void DSLParser::error(int offset, String msg) {
@@ -303,7 +303,7 @@
     switch (lookahead.fKind) {
         case Token::Kind::TK_SEMICOLON:
             this->nextToken();
-            this->error(lookahead.fOffset, "expected a declaration, but found ';'");
+            this->error(lookahead, "expected a declaration, but found ';'");
             return false;
         default:
             break;
@@ -443,13 +443,13 @@
 void DSLParser::globalVarDeclarationEnd(PositionInfo pos, const dsl::DSLModifiers& mods,
         dsl::DSLType baseType, skstd::string_view name) {
     using namespace dsl;
-    int offset = this->peek().fOffset;
+    int line = this->peek().fLine;
     DSLType type = baseType;
     DSLExpression initializer;
-    if (!this->parseArrayDimensions(offset, &type)) {
+    if (!this->parseArrayDimensions(line, &type)) {
         return;
     }
-    if (!this->parseInitializer(offset, &initializer)) {
+    if (!this->parseInitializer(line, &initializer)) {
         return;
     }
     DSLGlobalVar first(mods, type, name, std::move(initializer), pos);
@@ -462,15 +462,15 @@
         if (!this->expectIdentifier(&identifierName)) {
             return;
         }
-        if (!this->parseArrayDimensions(offset, &type)) {
+        if (!this->parseArrayDimensions(line, &type)) {
             return;
         }
         DSLExpression anotherInitializer;
-        if (!this->parseInitializer(offset, &anotherInitializer)) {
+        if (!this->parseInitializer(line, &anotherInitializer)) {
             return;
         }
         DSLGlobalVar next(mods, type, this->text(identifierName), std::move(anotherInitializer),
-                          this->position(offset));
+                this->position(line));
         Declare(next);
         AddToSymbolTable(next, this->position(identifierName));
     }
@@ -482,13 +482,13 @@
 DSLStatement DSLParser::localVarDeclarationEnd(PositionInfo pos, const dsl::DSLModifiers& mods,
         dsl::DSLType baseType, skstd::string_view name) {
     using namespace dsl;
-    int offset = this->peek().fOffset;
+    int line = this->peek().fLine;
     DSLType type = baseType;
     DSLExpression initializer;
-    if (!this->parseArrayDimensions(offset, &type)) {
+    if (!this->parseArrayDimensions(line, &type)) {
         return {};
     }
-    if (!this->parseInitializer(offset, &initializer)) {
+    if (!this->parseInitializer(line, &initializer)) {
         return {};
     }
     DSLVar first(mods, type, name, std::move(initializer), pos);
@@ -501,15 +501,15 @@
         if (!this->expectIdentifier(&identifierName)) {
             return result;
         }
-        if (!this->parseArrayDimensions(offset, &type)) {
+        if (!this->parseArrayDimensions(line, &type)) {
             return result;
         }
         DSLExpression anotherInitializer;
-        if (!this->parseInitializer(offset, &anotherInitializer)) {
+        if (!this->parseInitializer(line, &anotherInitializer)) {
             return result;
         }
         DSLVar next(mods, type, this->text(identifierName), std::move(anotherInitializer),
-                    this->position(offset));
+                this->position(line));
         DSLWriter::AddVarDeclaration(result, next);
         AddToSymbolTable(next, this->position(identifierName));
     }
@@ -617,8 +617,7 @@
         }
     }
     if (fields.empty()) {
-        this->error(name.fOffset,
-                    "struct '" + this->text(name) + "' must contain at least one field");
+        this->error(name, "struct '" + this->text(name) + "' must contain at least one field");
     }
     return dsl::Struct(this->text(name), SkMakeSpan(fields), this->position(name));
 }
@@ -1662,10 +1661,10 @@
             return std::move(result);
         }
         case Token::Kind::TK_DOT: {
-            int offset = this->peek().fOffset;
+            int line = this->peek().fLine;
             skstd::string_view text;
             if (this->identifier(&text)) {
-                return this->swizzle(offset, std::move(base), text);
+                return this->swizzle(line, std::move(base), text);
             }
             [[fallthrough]];
         }
@@ -1679,13 +1678,13 @@
             // identifiers that directly follow the float
             Token id = this->nextRawToken();
             if (id.fKind == Token::Kind::TK_IDENTIFIER) {
-                return this->swizzle(next.fOffset, std::move(base), field + this->text(id));
+                return this->swizzle(next.fLine, std::move(base), field + this->text(id));
             } else if (field.empty()) {
                 this->error(next, "expected field name or swizzle mask after '.'");
                 return {{DSLExpression::Poison()}};
             }
             this->pushback(id);
-            return this->swizzle(next.fOffset, std::move(base), field);
+            return this->swizzle(next.fLine, std::move(base), field);
         }
         case Token::Kind::TK_LPAREN: {
             ExpressionArray args;
@@ -1702,7 +1701,7 @@
                 }
             }
             this->expect(Token::Kind::TK_RPAREN, "')' to complete function arguments");
-            return this->call(next.fOffset, std::move(base), std::move(args));
+            return this->call(next.fLine, std::move(base), std::move(args));
         }
         case Token::Kind::TK_PLUSPLUS:
             return std::move(base)++;
@@ -1761,8 +1760,9 @@
         }
         default:
             this->nextToken();
-            this->error(t.fOffset, "expected expression, but found '" + this->text(t) + "'");
+            this->error(t, "expected expression, but found '" + this->text(t) + "'");
             fEncounteredFatalError = true;
+            break;
     }
     return {};
 }
diff --git a/src/sksl/SkSLDSLParser.h b/src/sksl/SkSLDSLParser.h
index d496c8b..aed2a19 100644
--- a/src/sksl/SkSLDSLParser.h
+++ b/src/sksl/SkSLDSLParser.h
@@ -330,7 +330,7 @@
 
         DSLParser* fParser;
         Token fPushbackCheckpoint;
-        int32_t fLexerCheckpoint;
+        SkSL::Lexer::Checkpoint fLexerCheckpoint;
         ForwardingErrorReporter fErrorReporter;
         ErrorReporter* fOldErrorReporter;
         bool fOldEncounteredFatalError;
diff --git a/src/sksl/SkSLErrorReporter.cpp b/src/sksl/SkSLErrorReporter.cpp
index 797b5e9..6be9dd0 100644
--- a/src/sksl/SkSLErrorReporter.cpp
+++ b/src/sksl/SkSLErrorReporter.cpp
@@ -21,30 +21,16 @@
     this->handleError(msg, position);
 }
 
-void ErrorReporter::error(int offset, skstd::string_view msg) {
+void ErrorReporter::error(int line, skstd::string_view msg) {
     if (msg.contains(Compiler::POISON_TAG)) {
         // don't report errors on poison values
         return;
     }
-    if (offset == -1) {
+    if (line == -1) {
         ++fErrorCount;
         fPendingErrors.push_back(String(msg));
     } else {
-        this->error(msg, this->position(offset));
-    }
-}
-
-PositionInfo ErrorReporter::position(int offset) const {
-    if (fSource && offset >= 0) {
-        int line = 1;
-        for (int i = 0; i < offset; i++) {
-            if (fSource[i] == '\n') {
-                ++line;
-            }
-        }
-        return PositionInfo(/*file=*/nullptr, line);
-    } else {
-        return PositionInfo();
+        this->error(msg, PositionInfo(/*file=*/nullptr, line));
     }
 }
 
diff --git a/src/sksl/SkSLLexer.cpp b/src/sksl/SkSLLexer.cpp
index ac59ca6..d6a6245 100644
--- a/src/sksl/SkSLLexer.cpp
+++ b/src/sksl/SkSLLexer.cpp
@@ -844,13 +844,13 @@
     // a bit.
     int32_t startOffset = fOffset;
     if (startOffset == (int32_t)fText.length()) {
-        return Token(Token::Kind::TK_END_OF_FILE, startOffset, 0);
+        return Token(Token::Kind::TK_END_OF_FILE, startOffset, 0, fLine);
     }
     State state = 1;
     for (;;) {
         if (fOffset >= (int32_t)fText.length()) {
             if (kAccepts[state] == -1) {
-                return Token(Token::Kind::TK_END_OF_FILE, startOffset, 0);
+                return Token(Token::Kind::TK_END_OF_FILE, startOffset, 0, fLine);
             }
             break;
         }
@@ -864,9 +864,12 @@
         }
         state = newState;
         ++fOffset;
+        if (c == '\n') {
+            ++fLine;
+        }
     }
     Token::Kind kind = (Token::Kind)kAccepts[state];
-    return Token(kind, startOffset, fOffset - startOffset);
+    return Token(kind, startOffset, fOffset - startOffset, fLine);
 }
 
 }  // namespace SkSL
diff --git a/src/sksl/SkSLLexer.h b/src/sksl/SkSLLexer.h
index 19e3390..2c150bf 100644
--- a/src/sksl/SkSLLexer.h
+++ b/src/sksl/SkSLLexer.h
@@ -106,14 +106,14 @@
         TK_NONE,
     };
 
-    Token() : fKind(Kind::TK_NONE), fOffset(-1), fLength(-1) {}
+    Token() {}
+    Token(Kind kind, int32_t offset, int32_t length, int32_t line)
+            : fKind(kind), fOffset(offset), fLength(length), fLine(line) {}
 
-    Token(Kind kind, int32_t offset, int32_t length)
-            : fKind(kind), fOffset(offset), fLength(length) {}
-
-    Kind fKind;
-    int fOffset;
-    int fLength;
+    Kind fKind = Kind::TK_NONE;
+    int32_t fOffset = -1;
+    int32_t fLength = -1;
+    int32_t fLine = -1;
 };
 
 class Lexer {
@@ -121,17 +121,27 @@
     void start(skstd::string_view text) {
         fText = text;
         fOffset = 0;
+        fLine = 1;
     }
 
     Token next();
 
-    int32_t getCheckpoint() const { return fOffset; }
+    struct Checkpoint {
+        int32_t fOffset;
+        int32_t fLine;
+    };
 
-    void rewindToCheckpoint(int32_t checkpoint) { fOffset = checkpoint; }
+    Checkpoint getCheckpoint() const { return {fOffset, fLine}; }
+
+    void rewindToCheckpoint(Checkpoint checkpoint) {
+        fOffset = checkpoint.fOffset;
+        fLine = checkpoint.fLine;
+    }
 
 private:
     skstd::string_view fText;
     int32_t fOffset;
+    int32_t fLine;
 };
 
 }  // namespace SkSL
diff --git a/src/sksl/SkSLParser.cpp b/src/sksl/SkSLParser.cpp
index 95ad7ac..09d65ae 100644
--- a/src/sksl/SkSLParser.cpp
+++ b/src/sksl/SkSLParser.cpp
@@ -89,7 +89,7 @@
 
 Parser::Parser(skstd::string_view text, SymbolTable& symbols, ErrorReporter& errors)
 : fText(text)
-, fPushback(Token::Kind::TK_NONE, -1, -1)
+, fPushback(Token::Kind::TK_NONE, /*offset=*/-1, /*length=*/-1, /*line=*/-1)
 , fSymbols(symbols)
 , fErrors(&errors) {
     fLexer.start(text);
@@ -119,7 +119,7 @@
 std::unique_ptr<ASTFile> Parser::compilationUnit() {
     fFile = std::make_unique<ASTFile>();
     fFile->fNodes.reserve(fText.size() / 10);  // a typical program is approx 10:1 for chars:nodes
-    ASTNode::ID result = this->createNode(/*offset=*/0, ASTNode::Kind::kFile);
+    ASTNode::ID result = this->createNode(/*offset=*/1, ASTNode::Kind::kFile);
     fFile->fRoot = result;
     for (;;) {
         switch (this->peek().fKind) {
diff --git a/src/sksl/SkSLParser.h b/src/sksl/SkSLParser.h
index d83e34f..699984b 100644
--- a/src/sksl/SkSLParser.h
+++ b/src/sksl/SkSLParser.h
@@ -301,7 +301,7 @@
 
         Parser* fParser;
         Token fPushbackCheckpoint;
-        int32_t fLexerCheckpoint;
+        Lexer::Checkpoint fLexerCheckpoint;
         ForwardingErrorReporter fErrorReporter;
         ErrorReporter* fOldErrorReporter;
         bool fDone = false;
diff --git a/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp b/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp
index d42b6e2..47e80a0 100644
--- a/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp
+++ b/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp
@@ -3554,7 +3554,7 @@
     }
     // Make sure we have a main() function.
     if (!main) {
-        fContext.fErrors->error(/*offset=*/0, "program does not contain a main() function");
+        fContext.fErrors->error(/*offset=*/-1, "program does not contain a main() function");
         return;
     }
     // Emit interface blocks.
diff --git a/src/sksl/dsl/DSLCore.cpp b/src/sksl/dsl/DSLCore.cpp
index 272fc8c..6885a7b 100644
--- a/src/sksl/dsl/DSLCore.cpp
+++ b/src/sksl/dsl/DSLCore.cpp
@@ -149,7 +149,7 @@
         for (DSLVar& v : vars) {
             statements.push_back(Declare(v, pos).release());
         }
-        return SkSL::Block::MakeUnscoped(/*offset=*/-1, std::move(statements));
+        return SkSL::Block::MakeUnscoped(pos.line(), std::move(statements));
     }
 
     static void Declare(DSLGlobalVar& var, PositionInfo pos) {
@@ -191,7 +191,7 @@
 
     static DSLPossibleStatement For(DSLStatement initializer, DSLExpression test,
                                     DSLExpression next, DSLStatement stmt, PositionInfo pos) {
-        return ForStatement::Convert(DSLWriter::Context(), /*offset=*/-1,
+        return ForStatement::Convert(DSLWriter::Context(), pos.line(),
                                      initializer.releaseIfPossible(), test.releaseIfPossible(),
                                      next.releaseIfPossible(), stmt.release(),
                                      DSLWriter::SymbolTable());
@@ -216,14 +216,14 @@
             if (baseType->isArray()) {
                 baseType = &baseType->componentType();
             }
-            DSLWriter::IRGenerator().checkVarDeclaration(/*offset=*/-1, field.fModifiers.fModifiers,
+            DSLWriter::IRGenerator().checkVarDeclaration(pos.line(), field.fModifiers.fModifiers,
                     baseType, Variable::Storage::kInterfaceBlock);
             GetErrorReporter().reportPendingErrors(field.fPosition);
             skslFields.push_back(SkSL::Type::Field(field.fModifiers.fModifiers, field.fName,
                                                    &field.fType.skslType()));
         }
         const SkSL::Type* structType = DSLWriter::SymbolTable()->takeOwnershipOfSymbol(
-                SkSL::Type::MakeStructType(/*offset=*/-1, typeName, std::move(skslFields)));
+                SkSL::Type::MakeStructType(pos.line(), typeName, std::move(skslFields)));
         DSLType varType = arraySize > 0 ? Array(structType, arraySize) : DSLType(structType);
         DSLGlobalVar var(modifiers, varType, !varName.empty() ? varName : typeName);
         // Interface blocks can't be declared, so we always need to mark the var declared ourselves.
@@ -233,14 +233,14 @@
         }
         const SkSL::Variable* skslVar = DSLWriter::Var(var);
         if (skslVar) {
-            auto intf = std::make_unique<SkSL::InterfaceBlock>(/*offset=*/-1,
+            auto intf = std::make_unique<SkSL::InterfaceBlock>(pos.line(),
                     *skslVar, typeName, varName, arraySize, DSLWriter::SymbolTable());
             DSLWriter::IRGenerator().scanInterfaceBlock(*intf);
             DSLWriter::ProgramElements().push_back(std::move(intf));
             if (varName.empty()) {
                 const std::vector<SkSL::Type::Field>& structFields = structType->fields();
                 for (size_t i = 0; i < structFields.size(); ++i) {
-                    DSLWriter::SymbolTable()->add(std::make_unique<SkSL::Field>(/*offset=*/-1,
+                    DSLWriter::SymbolTable()->add(std::make_unique<SkSL::Field>(pos.line(),
                                                                                 skslVar,
                                                                                 i));
                 }
@@ -252,7 +252,7 @@
         return var;
     }
 
-    static DSLPossibleStatement Return(DSLExpression value, PositionInfo pos) {
+    static DSLStatement Return(DSLExpression value, PositionInfo pos) {
         // Note that because Return is called before the function in which it resides exists, at
         // this point we do not know the function's return type. We therefore do not check for
         // errors, or coerce the value to the correct type, until the return statement is actually
diff --git a/src/sksl/dsl/DSLStatement.cpp b/src/sksl/dsl/DSLStatement.cpp
index af2d9aa..080491f 100644
--- a/src/sksl/dsl/DSLStatement.cpp
+++ b/src/sksl/dsl/DSLStatement.cpp
@@ -83,11 +83,12 @@
 }
 
 DSLStatement operator,(DSLStatement left, DSLStatement right) {
+    int line = left.fStatement->fOffset;
     StatementArray stmts;
     stmts.reserve_back(2);
     stmts.push_back(left.release());
     stmts.push_back(right.release());
-    return DSLStatement(SkSL::Block::MakeUnscoped(/*offset=*/-1, std::move(stmts)));
+    return DSLStatement(SkSL::Block::MakeUnscoped(line, std::move(stmts)));
 }
 
 } // namespace dsl
diff --git a/src/sksl/dsl/DSLType.cpp b/src/sksl/dsl/DSLType.cpp
index c2d13ca..03258f9 100644
--- a/src/sksl/dsl/DSLType.cpp
+++ b/src/sksl/dsl/DSLType.cpp
@@ -221,7 +221,8 @@
 }
 
 DSLType Array(const DSLType& base, int count, PositionInfo pos) {
-    count = base.skslType().convertArraySize(DSLWriter::Context(), DSLExpression(count).release());
+    count = base.skslType().convertArraySize(DSLWriter::Context(),
+            DSLExpression(count, pos).release());
     DSLWriter::ReportErrors(pos);
     if (!count) {
         return DSLType(kPoison_Type);
diff --git a/src/sksl/dsl/DSLVar.cpp b/src/sksl/dsl/DSLVar.cpp
index 70d0115..37de479 100644
--- a/src/sksl/dsl/DSLVar.cpp
+++ b/src/sksl/dsl/DSLVar.cpp
@@ -158,12 +158,12 @@
 
 
 DSLPossibleExpression DSLVarBase::operator[](DSLExpression&& index) {
-    return DSLExpression(*this)[std::move(index)];
+    return DSLExpression(*this, PositionInfo())[std::move(index)];
 }
 
 DSLPossibleExpression DSLVarBase::assign(DSLExpression expr) {
-    return DSLWriter::ConvertBinary(DSLExpression(*this).release(), SkSL::Token::Kind::TK_EQ,
-                                    expr.release());
+    return DSLWriter::ConvertBinary(DSLExpression(*this, PositionInfo()).release(),
+            SkSL::Token::Kind::TK_EQ, expr.release());
 }
 
 DSLPossibleExpression DSLVar::operator=(DSLExpression expr) {
@@ -184,7 +184,7 @@
         DSLWriter::ReportError("type does not support method calls", pos);
         return nullptr;
     }
-    return DSLWriter::ConvertField(DSLExpression(*this).release(), methodName);
+    return DSLWriter::ConvertField(DSLExpression(*this, PositionInfo()).release(), methodName);
 }
 
 DSLPossibleExpression DSLGlobalVar::eval(DSLExpression x, PositionInfo pos) {
diff --git a/src/sksl/ir/SkSLConstructorScalarCast.cpp b/src/sksl/ir/SkSLConstructorScalarCast.cpp
index c249075..7473608 100644
--- a/src/sksl/ir/SkSLConstructorScalarCast.cpp
+++ b/src/sksl/ir/SkSLConstructorScalarCast.cpp
@@ -65,7 +65,7 @@
     }
     // We can cast scalar literals at compile-time.
     if (arg->is<Literal>()) {
-        return Literal::Make(arg->fOffset, arg->as<Literal>().value(), &type);
+        return Literal::Make(offset, arg->as<Literal>().value(), &type);
     }
     return std::make_unique<ConstructorScalarCast>(offset, type, std::move(arg));
 }
diff --git a/src/sksl/lex/Main.cpp b/src/sksl/lex/Main.cpp
index 143d53a..37cc312 100644
--- a/src/sksl/lex/Main.cpp
+++ b/src/sksl/lex/Main.cpp
@@ -51,19 +51,18 @@
     out << R"(
     };
 
-    )" << token << R"(()
-    : fKind(Kind::TK_NONE)
-    , fOffset(-1)
-    , fLength(-1) {}
+    )" << token << "() {}";
 
-    )" << token << R"((Kind kind, int32_t offset, int32_t length)
+    out << token << R"((Kind kind, int32_t offset, int32_t length, int32_t line)
     : fKind(kind)
     , fOffset(offset)
-    , fLength(length) {}
+    , fLength(length)
+    , fLine(line) {}
 
-    Kind fKind;
-    int fOffset;
-    int fLength;
+    Kind fKind      = Kind::TK_NONE;
+    int32_t fOffset = -1;
+    int32_t fLength = -1;
+    int32_t fLine   = -1;
 };
 
 class )" << lexer << R"( {
@@ -71,21 +70,29 @@
     void start(skstd::string_view text) {
         fText = text;
         fOffset = 0;
+        fLine = 1;
     }
 
     )" << token << R"( next();
 
-    int32_t getCheckpoint() const {
-        return fOffset;
+    struct Checkpoint {
+        int32_t fOffset;
+        int32_t fLine;
+    };
+
+    Checkpoint getCheckpoint() const {
+        return {fOffset, fLine};
     }
 
-    void rewindToCheckpoint(int32_t checkpoint) {
-        fOffset = checkpoint;
+    void rewindToCheckpoint(Checkpoint checkpoint) {
+        fOffset = checkpoint.fOffset;
+        fLine = checkpoint.fLine;
     }
 
 private:
     skstd::string_view fText;
     int32_t fOffset;
+    int32_t fLine;
 };
 
 } // namespace
@@ -154,13 +161,13 @@
     // a bit.
     int32_t startOffset = fOffset;
     if (startOffset == (int32_t)fText.length()) {
-        return )" << token << "(" << token << R"(::Kind::TK_END_OF_FILE, startOffset, 0);
+        return )" << token << "(" << token << R"(::Kind::TK_END_OF_FILE, startOffset, 0, fLine);
     }
     State state = 1;
     for (;;) {
         if (fOffset >= (int32_t)fText.length()) {
             if (kAccepts[state] == -1) {
-                return Token(Token::Kind::TK_END_OF_FILE, startOffset, 0);
+                return Token(Token::Kind::TK_END_OF_FILE, startOffset, 0, fLine);
             }
             break;
         }
@@ -174,9 +181,12 @@
         }
         state = newState;
         ++fOffset;
+        if (c == '\n') {
+            ++fLine;
+        }
     }
     Token::Kind kind = ()" << token << R"(::Kind) kAccepts[state];
-    return )" << token << R"((kind, startOffset, fOffset - startOffset);
+    return )" << token << R"((kind, startOffset, fOffset - startOffset, fLine);
 }
 
 } // namespace
diff --git a/tests/sksl/shared/StructMaxDepth.asm.frag b/tests/sksl/shared/StructMaxDepth.asm.frag
index 2df0733..06d568d 100644
--- a/tests/sksl/shared/StructMaxDepth.asm.frag
+++ b/tests/sksl/shared/StructMaxDepth.asm.frag
@@ -1,4 +1,4 @@
 ### Compilation failed:
 
-error: 1: program does not contain a main() function
+error: program does not contain a main() function
 1 error