package parse import "fmt" import "strings" // Position stores the position of something within a file. For memory's sake, // positions in the same file should be created using the same File string, and // positions on the same line should be created using the same Line string. type Position struct { File string // The name of the file Line string // the text of the line the position is on Row int // Line number, starting at 0 Start int // Starting column number, starting at 0 End int // Terminating column number, starting at 0 } // String returns a string representation of the position of the form: // FILE:ROW+1:START+1 // Note that the row and column numbers as displayed are increased by one from // their normal zero-indexed state, because most editors display row and column // numbers starting at 1:1. func (pos Position) String () string { return fmt.Sprintf("%s:%d:%d", pos.File, pos.Row + 1, pos.Start + 1) } // Format produces a formatted printout of where the position is in its file. func (pos Position) Format () string { line := formatTabs(pos.Line) output := fmt.Sprintf("%-4d | %s\n ", pos.Row + 1, line) start := pos.Start end := pos.End if end <= start { end = start + 1 } start = getXInTabbedString(pos.Line, start) end = getXInTabbedString(pos.Line, end) for index := range line { if index >= end { break } if index >= start{ output += "^" } else { output += " " } } return output } func getXInTabbedString (tabbed string, column int) int { var x int for index, ch := range tabbed { if index >= column { return x } if ch == '\t' { nextStop := ((x / 8) + 1) * 8 x = nextStop - 1 } x ++ } return x } func formatTabs (tabbed string) string { var result string var x int for _, ch := range tabbed { if ch == '\t' { nextStop := ((x / 8) + 1) * 8 result += strings.Repeat(" ", nextStop - x) x = nextStop - 1 } else { result += string(ch) } x ++ } return result } // Union attempts to return the union of two positions. Since a position cannot // span multiple lines or files, the resulting position will be on the same line // and in the same file as the method reciever. func (pos Position) Union (other Position) Position { switch { case other.File != pos.File: case other.Row < pos.Row: pos.Start = 0 case other.Row > pos.Row: pos.End = len(pos.Line) default: if other.Start < pos.Start { pos.Start = other.Start } if other.End > pos.End { pos.End = other.End } } return pos }