@@ -9,7 +9,7 @@ import kotlin.math.pow
99class JSON5Lexer (private val source : String ) {
1010 private var pos: Int = 0
1111 private var line: Int = 1
12- private var column: Int = 0
12+ private var column: Int = 1 // Starting column at 1 to match JavaScript implementation
1313 private var currentChar: Char? = null
1414
1515 init {
@@ -44,18 +44,25 @@ class JSON5Lexer(private val source: String) {
4444 if (peek() == ' I' ) {
4545 // Handle -Infinity
4646 val sign = currentChar
47+ val startColumn = column
4748 advance()
4849 if (source.substring(pos, minOf(pos + 8 , source.length)) == " Infinity" ) {
4950 repeat(8 ) { advance() }
50- return Token .NumericToken (if (sign == ' -' ) Double .NEGATIVE_INFINITY else Double .POSITIVE_INFINITY , line, column )
51+ return Token .NumericToken (if (sign == ' -' ) Double .NEGATIVE_INFINITY else Double .POSITIVE_INFINITY , line, startColumn )
5152 } else if (source.substring(pos, minOf(pos + 3 , source.length)) == " NaN" ) {
5253 // Handle -NaN (technically the same as NaN)
5354 repeat(3 ) { advance() }
54- return Token .NumericToken (Double .NaN , line, column )
55+ return Token .NumericToken (Double .NaN , line, startColumn )
5556 }
5657 // Not Infinity/NaN, revert and continue with normal number parsing
5758 pos - = 1
58- column - = 1
59+ if (currentChar == ' \n ' ) {
60+ line - = 1
61+ // Need to find last column position - but in this case we're just handling Infinity/NaN
62+ column = 1
63+ } else {
64+ column - = 1
65+ }
5966 currentChar = sign
6067 }
6168 readNumber()
@@ -70,7 +77,7 @@ class JSON5Lexer(private val source: String) {
7077 throw JSON5Exception .invalidChar(currentChar ? : ' ' , line, column)
7178 }
7279
73- advance() // Skip the 'u'
80+ advance() // Skip 'u'
7481 val hexDigits = StringBuilder ()
7582 repeat(4 ) {
7683 if (currentChar == null || ! currentChar!! .isHexDigit()) {
@@ -90,6 +97,7 @@ class JSON5Lexer(private val source: String) {
9097 // Continue reading the rest of the identifier
9198 while (true ) {
9299 if (currentChar == ' \\ ' ) {
100+ val continueColumn = column
93101 advance() // Skip backslash
94102 if (currentChar != ' u' ) {
95103 throw JSON5Exception .invalidChar(currentChar ? : ' ' , line, column)
@@ -105,7 +113,12 @@ class JSON5Lexer(private val source: String) {
105113 advance()
106114 }
107115
108- buffer.append(identHexDigits.toString().toInt(16 ).toChar())
116+ val continueChar = identHexDigits.toString().toInt(16 ).toChar()
117+ if (! isIdentifierPart(continueChar)) {
118+ throw JSON5Exception .invalidIdentifierChar(line, continueColumn)
119+ }
120+
121+ buffer.append(continueChar)
109122 } else if (currentChar != null && isIdentifierPart(currentChar)) {
110123 buffer.append(currentChar)
111124 advance()
@@ -118,16 +131,26 @@ class JSON5Lexer(private val source: String) {
118131 }
119132 ' /' -> {
120133 // Handle incomplete comments
121- if (peek() == null ) {
122- throw JSON5Exception .invalidChar(' /' , line, column)
134+ val startColumn = column
135+ val lookAhead = peek()
136+ if (lookAhead == null ) {
137+ advance()
138+ throw JSON5Exception .invalidChar(' /' , line, startColumn)
139+ }
140+ if (lookAhead != ' /' && lookAhead != ' *' ) {
141+ advance()
142+ throw JSON5Exception .invalidChar(' /' , line, startColumn)
123143 }
124- throw JSON5Exception .invalidChar(peek() ? : ' ' , line, column + 1 )
144+ throw JSON5Exception .invalidChar(lookAhead , line, column + 1 )
125145 }
126146 else -> {
127147 if (isIdentifierStart(currentChar)) {
128148 readIdentifier()
129149 } else {
130- throw JSON5Exception .invalidChar(currentChar ? : ' ' , line, column)
150+ val c = currentChar ? : ' '
151+ val startColumn = column
152+ advance()
153+ throw JSON5Exception .invalidChar(c, line, startColumn)
131154 }
132155 }
133156 }
@@ -149,7 +172,7 @@ class JSON5Lexer(private val source: String) {
149172 currentChar = source[pos]
150173 if (currentChar == ' \n ' ) {
151174 line++
152- column = 0
175+ column = 1 // Reset to 1 when we encounter a newline
153176 } else {
154177 column++
155178 }
@@ -201,6 +224,9 @@ class JSON5Lexer(private val source: String) {
201224 }
202225 advance()
203226 }
227+ if (currentChar == null ) {
228+ throw JSON5Exception .invalidEndOfInput(line, column)
229+ }
204230 skipWhitespace()
205231 }
206232 }
@@ -233,13 +259,15 @@ class JSON5Lexer(private val source: String) {
233259 }
234260
235261 if (! done) {
236- throw JSON5Exception .invalidEndOfInput(startLine, startColumn)
262+ throw JSON5Exception .invalidEndOfInput(startLine, startColumn + 1 )
237263 }
238264
239- return Token .StringToken (buffer.toString(), line , startColumn)
265+ return Token .StringToken (buffer.toString(), startLine , startColumn)
240266 }
241267
242268 private fun readEscapeSequence (): Char {
269+ val escapeCol = column
270+
243271 when (currentChar) {
244272 ' b' -> {
245273 advance()
@@ -303,30 +331,24 @@ class JSON5Lexer(private val source: String) {
303331 try {
304332 return readHexEscape(2 )
305333 } catch (e: Exception ) {
306- if (currentChar != null ) {
307- throw JSON5Exception .invalidChar(currentChar ? : ' ' , line, column)
308- }
309- throw JSON5Exception .invalidEndOfInput(line, column)
334+ throw JSON5Exception .invalidChar(currentChar ? : ' ' , line, column)
310335 }
311336 }
312337 ' u' -> {
313338 advance()
314339 try {
315340 return readHexEscape(4 )
316341 } catch (e: Exception ) {
317- if (currentChar != null ) {
318- throw JSON5Exception .invalidChar(currentChar ? : ' ' , line, column)
319- }
320- throw JSON5Exception .invalidEndOfInput(line, column)
342+ throw JSON5Exception .invalidChar(currentChar ? : ' ' , line, column)
321343 }
322344 }
323- null -> throw JSON5Exception .invalidEndOfInput(line, column )
345+ null -> throw JSON5Exception .invalidEndOfInput(line, escapeCol )
324346 in ' 1' .. ' 9' -> throw JSON5Exception .invalidChar(currentChar ? : ' ' , line, column)
325347 else -> {
326348 // Just return the character after the backslash (e.g. for \', \", etc.)
327349 val c = currentChar
328350 advance()
329- return c ? : throw JSON5Exception .invalidEndOfInput(line, column )
351+ return c ? : throw JSON5Exception .invalidEndOfInput(line, escapeCol )
330352 }
331353 }
332354 }
@@ -352,6 +374,11 @@ class JSON5Lexer(private val source: String) {
352374 if (source.substring(pos, minOf(pos + 4 , source.length)) == " true" ) {
353375 repeat(4 ) { advance() }
354376 return Token .BooleanToken (true , startLine, startColumn)
377+ } else {
378+ val c = source.getOrNull(pos + 3 )
379+ if (c != null && pos + 3 < source.length) {
380+ throw JSON5Exception .invalidChar(c, line, column + 3 )
381+ }
355382 }
356383
357384 throw JSON5Exception (" Unexpected identifier" , startLine, startColumn)
@@ -404,13 +431,19 @@ class JSON5Lexer(private val source: String) {
404431
405432 // Handle sign
406433 if (currentChar == ' +' ) {
434+ buffer.append(' +' )
407435 advance() // Skip '+'
408436 } else if (currentChar == ' -' ) {
409437 isNegative = true
410438 buffer.append(' -' )
411439 advance() // Skip '-'
412440 }
413441
442+ // Handle number following sign
443+ if (currentChar == null || (! currentChar!! .isDigit() && currentChar != ' .' )) {
444+ throw JSON5Exception .invalidChar(currentChar ? : ' ' , line, column)
445+ }
446+
414447 // Handle hexadecimal notation
415448 if (currentChar == ' 0' && (peek() == ' x' || peek() == ' X' )) {
416449 buffer.append(' 0' )
@@ -473,11 +506,24 @@ class JSON5Lexer(private val source: String) {
473506 var hasExponentPart = false
474507 if (currentChar == ' e' || currentChar == ' E' ) {
475508 buffer.append(currentChar)
509+
510+ // Save position for error reporting
511+ val eColumn = column
476512 advance()
477513
478514 if (currentChar == ' +' || currentChar == ' -' ) {
479515 buffer.append(currentChar)
516+
517+ // Save position for error reporting
518+ val signColumn = column
480519 advance()
520+
521+ // Check for invalid character after exponent sign
522+ if (currentChar == null || ! currentChar!! .isDigit()) {
523+ throw JSON5Exception .invalidChar(currentChar ? : ' ' , line, column)
524+ }
525+ } else if (currentChar == null || ! currentChar!! .isDigit()) {
526+ throw JSON5Exception .invalidChar(currentChar ? : ' ' , line, column)
481527 }
482528
483529 var hasExponentDigits = false
0 commit comments