github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/schemadsl/input/inputsource.go (about)

     1  package input
     2  
     3  import (
     4  	"fmt"
     5  )
     6  
     7  // BytePosition represents the byte position in a piece of code.
     8  type BytePosition int
     9  
    10  // Position represents a position in an arbitrary source file.
    11  type Position struct {
    12  	// LineNumber is the 0-indexed line number.
    13  	LineNumber int
    14  
    15  	// ColumnPosition is the 0-indexed column position on the line.
    16  	ColumnPosition int
    17  }
    18  
    19  // Source represents the path of a source file.
    20  type Source string
    21  
    22  // RangeForRunePosition returns a source range over this source file.
    23  func (is Source) RangeForRunePosition(runePosition int, mapper PositionMapper) SourceRange {
    24  	return is.RangeForRunePositions(runePosition, runePosition, mapper)
    25  }
    26  
    27  // PositionForRunePosition returns a source position over this source file.
    28  func (is Source) PositionForRunePosition(runePosition int, mapper PositionMapper) SourcePosition {
    29  	return runeIndexedPosition{is, mapper, runePosition}
    30  }
    31  
    32  // PositionFromLineAndColumn returns a source position at the given line and column in this source file.
    33  func (is Source) PositionFromLineAndColumn(lineNumber int, columnPosition int, mapper PositionMapper) SourcePosition {
    34  	return lcIndexedPosition{is, mapper, Position{lineNumber, columnPosition}}
    35  }
    36  
    37  // RangeForRunePositions returns a source range over this source file.
    38  func (is Source) RangeForRunePositions(startRune int, endRune int, mapper PositionMapper) SourceRange {
    39  	return sourceRange{is, runeIndexedPosition{is, mapper, startRune}, runeIndexedPosition{is, mapper, endRune}}
    40  }
    41  
    42  // RangeForLineAndColPositions returns a source range over this source file.
    43  func (is Source) RangeForLineAndColPositions(start Position, end Position, mapper PositionMapper) SourceRange {
    44  	return sourceRange{is, lcIndexedPosition{is, mapper, start}, lcIndexedPosition{is, mapper, end}}
    45  }
    46  
    47  // PositionMapper defines an interface for converting rune position <-> line+col position
    48  // under source files.
    49  type PositionMapper interface {
    50  	// RunePositionToLineAndCol converts the given 0-indexed rune position under the given source file
    51  	// into a 0-indexed line number and column position.
    52  	RunePositionToLineAndCol(runePosition int, path Source) (int, int, error)
    53  
    54  	// LineAndColToRunePosition converts the given 0-indexed line number and column position under the
    55  	// given source file into a 0-indexed rune position.
    56  	LineAndColToRunePosition(lineNumber int, colPosition int, path Source) (int, error)
    57  
    58  	// TextForLine returns the text for the specified line number.
    59  	TextForLine(lineNumber int, path Source) (string, error)
    60  }
    61  
    62  // SourceRange represents a range inside a source file.
    63  type SourceRange interface {
    64  	// Source is the input source for this range.
    65  	Source() Source
    66  
    67  	// Start is the starting position of the source range.
    68  	Start() SourcePosition
    69  
    70  	// End is the ending position (inclusive) of the source range. If the same as the Start,
    71  	// this range represents a single position.
    72  	End() SourcePosition
    73  
    74  	// ContainsPosition returns true if the given range contains the given position.
    75  	ContainsPosition(position SourcePosition) (bool, error)
    76  
    77  	// AtStartPosition returns a SourceRange located only at the starting position of this range.
    78  	AtStartPosition() SourceRange
    79  
    80  	// String returns a (somewhat) human-readable form of the range.
    81  	String() string
    82  }
    83  
    84  // SourcePosition represents a single position in a source file.
    85  type SourcePosition interface {
    86  	// Source is the input source for this position.
    87  	Source() Source
    88  
    89  	// RunePosition returns the 0-indexed rune position in the source file.
    90  	RunePosition() (int, error)
    91  
    92  	// LineAndColumn returns the 0-indexed line number and column position in the source file.
    93  	LineAndColumn() (int, int, error)
    94  
    95  	// LineText returns the text of the line for this position.
    96  	LineText() (string, error)
    97  
    98  	// String returns a (somewhat) human-readable form of the position.
    99  	String() string
   100  }
   101  
   102  // sourceRange implements the SourceRange interface.
   103  type sourceRange struct {
   104  	source Source
   105  	start  SourcePosition
   106  	end    SourcePosition
   107  }
   108  
   109  func (sr sourceRange) Source() Source {
   110  	return sr.source
   111  }
   112  
   113  func (sr sourceRange) Start() SourcePosition {
   114  	return sr.start
   115  }
   116  
   117  func (sr sourceRange) End() SourcePosition {
   118  	return sr.end
   119  }
   120  
   121  func (sr sourceRange) AtStartPosition() SourceRange {
   122  	return sourceRange{sr.source, sr.start, sr.end}
   123  }
   124  
   125  func (sr sourceRange) ContainsPosition(position SourcePosition) (bool, error) {
   126  	if position.Source() != sr.source {
   127  		return false, nil
   128  	}
   129  
   130  	startRune, err := sr.start.RunePosition()
   131  	if err != nil {
   132  		return false, err
   133  	}
   134  
   135  	endRune, err := sr.end.RunePosition()
   136  	if err != nil {
   137  		return false, err
   138  	}
   139  
   140  	positionRune, err := position.RunePosition()
   141  	if err != nil {
   142  		return false, err
   143  	}
   144  
   145  	return positionRune >= startRune && positionRune <= endRune, nil
   146  }
   147  
   148  func (sr sourceRange) String() string {
   149  	return fmt.Sprintf("%v -> %v", sr.start, sr.end)
   150  }
   151  
   152  // runeIndexedPosition implements the SourcePosition interface over a rune position.
   153  type runeIndexedPosition struct {
   154  	source       Source
   155  	mapper       PositionMapper
   156  	runePosition int
   157  }
   158  
   159  func (ris runeIndexedPosition) Source() Source {
   160  	return ris.source
   161  }
   162  
   163  func (ris runeIndexedPosition) RunePosition() (int, error) {
   164  	return ris.runePosition, nil
   165  }
   166  
   167  func (ris runeIndexedPosition) LineAndColumn() (int, int, error) {
   168  	if ris.runePosition == 0 {
   169  		return 0, 0, nil
   170  	}
   171  	if ris.mapper == nil {
   172  		return -1, -1, fmt.Errorf("nil mapper")
   173  	}
   174  	return ris.mapper.RunePositionToLineAndCol(ris.runePosition, ris.source)
   175  }
   176  
   177  func (ris runeIndexedPosition) String() string {
   178  	return fmt.Sprintf("%s@%v (rune)", ris.source, ris.runePosition)
   179  }
   180  
   181  func (ris runeIndexedPosition) LineText() (string, error) {
   182  	lineNumber, _, err := ris.LineAndColumn()
   183  	if err != nil {
   184  		return "", err
   185  	}
   186  
   187  	return ris.mapper.TextForLine(lineNumber, ris.source)
   188  }
   189  
   190  // lcIndexedPosition implements the SourcePosition interface over a line and colu,n position.
   191  type lcIndexedPosition struct {
   192  	source     Source
   193  	mapper     PositionMapper
   194  	lcPosition Position
   195  }
   196  
   197  func (lcip lcIndexedPosition) Source() Source {
   198  	return lcip.source
   199  }
   200  
   201  func (lcip lcIndexedPosition) String() string {
   202  	return fmt.Sprintf("%s@%v:%v (line/col)", lcip.source, lcip.lcPosition.LineNumber, lcip.lcPosition.ColumnPosition)
   203  }
   204  
   205  func (lcip lcIndexedPosition) RunePosition() (int, error) {
   206  	if lcip.lcPosition.LineNumber == 0 && lcip.lcPosition.ColumnPosition == 0 {
   207  		return 0, nil
   208  	}
   209  	if lcip.mapper == nil {
   210  		return -1, fmt.Errorf("nil mapper")
   211  	}
   212  	return lcip.mapper.LineAndColToRunePosition(lcip.lcPosition.LineNumber, lcip.lcPosition.ColumnPosition, lcip.source)
   213  }
   214  
   215  func (lcip lcIndexedPosition) LineAndColumn() (int, int, error) {
   216  	return lcip.lcPosition.LineNumber, lcip.lcPosition.ColumnPosition, nil
   217  }
   218  
   219  func (lcip lcIndexedPosition) LineText() (string, error) {
   220  	if lcip.mapper == nil {
   221  		return "", fmt.Errorf("nil mapper")
   222  	}
   223  	return lcip.mapper.TextForLine(lcip.lcPosition.LineNumber, lcip.source)
   224  }