From f1792e08f3547152cc1dbfc0bdb93f36964559c4 Mon Sep 17 00:00:00 2001 From: Jeff Muizelaar Date: Wed, 22 Apr 2026 08:56:35 -0400 Subject: [PATCH] Rewrite line number computation in WSL Instead of computing the line number on demand, keep track of it as we lex. Previously, we ended up spending a quadratic amount of time computing the line number because we'd do something like: for every index: count the number line breaks up to this index. Fixes #298 --- WSL/Lexer.js | 23 ++++++++++++++++------- WSL/LexerToken.js | 5 +++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/WSL/Lexer.js b/WSL/Lexer.js index ba30359d..cbe86013 100644 --- a/WSL/Lexer.js +++ b/WSL/Lexer.js @@ -35,11 +35,12 @@ class Lexer { this._text = text; this._index = 0; this._stack = []; + this._lineNumber = lineNumberOffset; } get lineNumber() { - return this.lineNumberForIndex(this._index); + return this._lineNumber; } get origin() { return this._origin; } @@ -51,17 +52,22 @@ class Lexer { get originKind() { return this._originKind; } - lineNumberForIndex(index) + _countNewlines(str, length) { - let matches = this._text.substring(0, index).match(/\n/g); - return (matches ? matches.length : 0) + this._lineNumberOffset; + let count = 0; + for (let i = 0; i < length; i++) { + if (str.charCodeAt(i) === 10) + count++; + } + return count; } - get state() { return {index: this._index, stack: this._stack.concat()}; } + get state() { return {index: this._index, stack: this._stack.concat(), lineNumber: this._lineNumber}; } set state(value) { this._index = value.index; this._stack = value.stack; + this._lineNumber = value.lineNumber; } static _textIsIdentifierImpl(text) @@ -87,7 +93,7 @@ class Lexer { let result = (kind) => { let text = RegExp.lastMatch; - let token = new LexerToken(this, this._index, kind, text); + let token = new LexerToken(this, this._index, kind, text, this._lineNumber); this._index += text.length; return token; }; @@ -96,13 +102,16 @@ class Lexer { for (;;) { relevantText = this._text.substring(this._index); if (/^\s+/.test(relevantText)) { - this._index += RegExp.lastMatch.length; + let ws = RegExp.lastMatch; + this._lineNumber += this._countNewlines(ws, ws.length); + this._index += ws.length; continue; } if (/^\/\*/.test(relevantText)) { let endIndex = relevantText.search(/\*\//); if (endIndex < 0) this.fail("Unterminated comment"); + this._lineNumber += this._countNewlines(relevantText, endIndex); this._index += endIndex; continue; } diff --git a/WSL/LexerToken.js b/WSL/LexerToken.js index 9e06ee68..8bcccb97 100644 --- a/WSL/LexerToken.js +++ b/WSL/LexerToken.js @@ -25,12 +25,13 @@ "use strict"; class LexerToken { - constructor(lexer, index, kind, text) + constructor(lexer, index, kind, text, lineNumber) { this._lexer = lexer; this._index = index; this._kind = kind; this._text = text; + this._lineNumber = lineNumber; } get lexer() @@ -65,7 +66,7 @@ class LexerToken { get lineNumber() { - return this._lexer.lineNumberForIndex(this._index); + return this._lineNumber; } get originString()