2018-12-17 13:41:24 +00:00
package parse
import (
"fmt"
"io"
"strings"
"github.com/tdewolff/parse/v2/buffer"
)
// Position returns the line and column number for a certain position in a file. It is useful for recovering the position in a file that caused an error.
// It only treates \n, \r, and \r\n as newlines, which might be different from some languages also recognizing \f, \u2028, and \u2029 to be newlines.
2019-06-09 00:01:48 +00:00
func Position ( r io . Reader , offset int ) ( line , col int , context string ) {
2018-12-17 13:41:24 +00:00
l := buffer . NewLexer ( r )
line = 1
for {
c := l . Peek ( 0 )
2019-06-09 00:01:48 +00:00
if c == 0 && l . Err ( ) != nil || offset == l . Pos ( ) {
2018-12-17 13:41:24 +00:00
col = l . Pos ( ) + 1
context = positionContext ( l , line , col )
return
}
2019-06-09 00:01:48 +00:00
nNewline := 0
2018-12-17 13:41:24 +00:00
if c == '\n' {
2019-06-09 00:01:48 +00:00
nNewline = 1
2018-12-17 13:41:24 +00:00
} else if c == '\r' {
if l . Peek ( 1 ) == '\n' {
2019-06-09 00:01:48 +00:00
nNewline = 2
2018-12-17 13:41:24 +00:00
} else {
2019-06-09 00:01:48 +00:00
nNewline = 1
}
} else if c >= 0xC0 {
if r , n := l . PeekRune ( 0 ) ; r == '\u2028' || r == '\u2029' {
nNewline = n
}
} else {
l . Move ( 1 )
}
if nNewline > 0 {
if offset < l . Pos ( ) + nNewline {
// move onto offset position, let next iteration handle it
l . Move ( offset - l . Pos ( ) )
continue
2018-12-17 13:41:24 +00:00
}
2019-06-09 00:01:48 +00:00
l . Move ( nNewline )
2018-12-17 13:41:24 +00:00
line ++
offset -= l . Pos ( )
l . Skip ( )
}
}
}
func positionContext ( l * buffer . Lexer , line , col int ) ( context string ) {
for {
c := l . Peek ( 0 )
if c == 0 && l . Err ( ) != nil || c == '\n' || c == '\r' {
break
}
l . Move ( 1 )
}
2019-06-09 00:01:48 +00:00
// cut off front or rear of context to stay between 60 characters
2018-12-17 13:41:24 +00:00
b := l . Lexeme ( )
2019-06-09 00:01:48 +00:00
limit := 60
offset := 20
ellipsisFront := ""
ellipsisRear := ""
if limit < len ( b ) {
if col <= limit - offset {
ellipsisRear = "..."
b = b [ : limit - 3 ]
} else if col >= len ( b ) - offset - 3 {
ellipsisFront = "..."
col -= len ( b ) - offset - offset - 7
b = b [ len ( b ) - offset - offset - 4 : ]
} else {
ellipsisFront = "..."
ellipsisRear = "..."
b = b [ col - offset - 1 : col + offset ]
col = offset + 4
}
}
// replace unprintable characters by a space
2018-12-17 13:41:24 +00:00
for i , c := range b {
if c < 0x20 || c == 0x7F {
b [ i ] = ' '
}
}
2019-06-09 00:01:48 +00:00
context += fmt . Sprintf ( "%5d: %s%s%s\n" , line , ellipsisFront , string ( b ) , ellipsisRear )
2018-12-17 13:41:24 +00:00
context += fmt . Sprintf ( "%s^" , strings . Repeat ( " " , col + 6 ) )
return
}