Files
zeekayandClaude Opus 5 2c2f0e6bf3 Go 1.27.1, and the code it can say more simply
Modules declare go 1.27.1 and the build images follow. go fix applied the
modernizers, and the interpreter's parseArg, evaluateExprAs and
evaluateOptExprAs are now generic methods on the parser and program state
they take. omitzero is not applied, since it changes what the JSON says.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-16 19:52:45 -07:00

165 lines
3.3 KiB
Go

package parser
import (
"fmt"
"strings"
)
type Position struct {
Character int
Line int
}
type Range struct {
Start Position
End Position
}
type Ranged interface {
GetRange() Range
}
func (r Range) GetRange() Range { return r }
func (p1 *Position) GtEq(p2 Position) bool {
if p1.Line == p2.Line {
return p1.Character >= p2.Character
}
return p1.Line > p2.Line
}
func (p *Position) AsRange() Range {
// position >= r.Start && r.End >= position
return Range{Start: *p, End: *p}
}
func (r Range) Contains(position Position) bool {
// position >= r.Start && r.End >= position
return position.GtEq(r.Start) && r.End.GtEq(position)
}
// Pre: valid range (e.g. start <= end)
func (r Range) ShowOnSource(source string) string {
lines := strings.Split(source, "\n")
errorLines := lines[r.Start.Line : r.End.Line+1]
separator := " | "
showLine := func(index int, line string) string {
// %3d creates left (whitespace) padding so that the width is at least 3
digit := fmt.Sprintf("%3d", index)
// code line
return digit + separator + line + "\n"
}
showError := func(lineOffset int, line string) string {
digitPadding := strings.Repeat(" ", 3)
srcLine := r.Start.Line + lineOffset
var errStartChar int
if r.Start.Line == srcLine {
errStartChar = r.Start.Character
} else {
errStartChar = 0
}
var errEndChar int
if r.End.Line == srcLine {
errEndChar = r.End.Character
} else {
errEndChar = len(line)
}
errorIndicator := strings.Repeat("~", errEndChar-errStartChar)
leftWs := strings.Repeat(" ", errStartChar)
return digitPadding + separator + leftWs + errorIndicator
}
var buf strings.Builder
for lineOffset, line := range errorLines {
// Separate previous result with a newline
if lineOffset != 0 {
buf.WriteString("\n")
}
srcLine := r.Start.Line + lineOffset
if srcLine != 0 && lineOffset == 0 {
buf.WriteString(showLine(srcLine-1, lines[srcLine-1]))
}
buf.WriteString(showLine(srcLine, line))
buf.WriteString(showError(lineOffset, line))
if srcLine != len(lines)-1 && lineOffset == len(errorLines)-1 {
buf.WriteString("\n" + showLine(srcLine+1, lines[srcLine+1]))
}
}
return buf.String()
}
// Those functions are mostly used as test utilities
func indexOfOccurrence(src string, substr string, occurence int) int {
// TODO this function can probably be simplified
offset := strings.Index(src, substr)
if offset == -1 {
return -1
}
for ; occurence > 0; occurence-- {
shift := offset + len(substr)
shiftedOffset := strings.Index(src[shift:], substr)
if shiftedOffset == -1 {
return -1
}
offset += shiftedOffset + len(substr)
}
return offset
}
func PositionOf(src string, substr string) *Position {
return PositionOfIndexed(src, substr, 0)
}
func PositionOfIndexed(src string, substr string, occurrence int) *Position {
// TODO make offset to position utility
offset := indexOfOccurrence(src, substr, occurrence)
if offset == -1 {
return nil
}
pos := Position{}
for thisOffset, rune := range src {
if thisOffset == offset {
break
}
if rune == '\n' {
pos.Line++
pos.Character = 0
} else {
pos.Character++
}
}
return &pos
}
func RangeOfIndexed(src string, substr string, occurrence int) Range {
start := *PositionOfIndexed(src, substr, occurrence)
end := start
end.Character += len(substr)
return Range{
Start: start,
End: end,
}
}