position.go 2.0 KB

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