position.go (2069B)
1 package parse 2 3 import ( 4 "fmt" 5 "io" 6 "strings" 7 "unicode" 8 ) 9 10 // 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. 11 // 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. 12 func Position(r io.Reader, offset int) (line, col int, context string) { 13 l := NewInput(r) 14 line = 1 15 for l.Pos() < offset { 16 c := l.Peek(0) 17 n := 1 18 newline := false 19 if c == '\n' { 20 newline = true 21 } else if c == '\r' { 22 if l.Peek(1) == '\n' { 23 newline = true 24 n = 2 25 } else { 26 newline = true 27 } 28 } else if c >= 0xC0 { 29 var r rune 30 if r, n = l.PeekRune(0); r == '\u2028' || r == '\u2029' { 31 newline = true 32 } 33 } else if c == 0 && l.Err() != nil { 34 break 35 } 36 37 if 1 < n && offset < l.Pos()+n { 38 break 39 } 40 l.Move(n) 41 42 if newline { 43 line++ 44 offset -= l.Pos() 45 l.Skip() 46 } 47 } 48 49 col = len([]rune(string(l.Lexeme()))) + 1 50 context = positionContext(l, line, col) 51 return 52 } 53 54 func positionContext(l *Input, line, col int) (context string) { 55 for { 56 c := l.Peek(0) 57 if c == 0 && l.Err() != nil || c == '\n' || c == '\r' { 58 break 59 } 60 l.Move(1) 61 } 62 rs := []rune(string(l.Lexeme())) 63 64 // cut off front or rear of context to stay between 60 characters 65 limit := 60 66 offset := 20 67 ellipsisFront := "" 68 ellipsisRear := "" 69 if limit < len(rs) { 70 if col <= limit-offset { 71 ellipsisRear = "..." 72 rs = rs[:limit-3] 73 } else if col >= len(rs)-offset-3 { 74 ellipsisFront = "..." 75 col -= len(rs) - offset - offset - 7 76 rs = rs[len(rs)-offset-offset-4:] 77 } else { 78 ellipsisFront = "..." 79 ellipsisRear = "..." 80 rs = rs[col-offset-1 : col+offset] 81 col = offset + 4 82 } 83 } 84 85 // replace unprintable characters by a space 86 for i, r := range rs { 87 if !unicode.IsGraphic(r) { 88 rs[i] = 'ยท' 89 } 90 } 91 92 context += fmt.Sprintf("%5d: %s%s%s\n", line, ellipsisFront, string(rs), ellipsisRear) 93 context += fmt.Sprintf("%s^", strings.Repeat(" ", 6+col)) 94 return 95 }