1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . IO ;
4+ using System . Text ;
5+
6+ using MiKoSolutions . SemanticParsers . TypeScript . Yaml ;
7+
8+ namespace MiKoSolutions . SemanticParsers . TypeScript
9+ {
10+ public sealed class CharacterPositionFinder : IDisposable
11+ {
12+ private const int NewLine = 10 ; // '\n'
13+ private const int CarriageReturn = 13 ; // '\r'
14+
15+ private readonly List < MapInfo > _lineNumberToLengthAndCountMap ;
16+ private readonly List < LineInfo > _characterPositionToLineInfoMap ;
17+
18+ private CharacterPositionFinder ( List < MapInfo > lineNumberToLengthAndCountMap , List < LineInfo > characterPositionToLineInfoMap )
19+ {
20+ _lineNumberToLengthAndCountMap = lineNumberToLengthAndCountMap ;
21+ _characterPositionToLineInfoMap = characterPositionToLineInfoMap ;
22+ }
23+
24+ public static CharacterPositionFinder CreateFrom ( string filePath , Encoding encoding )
25+ {
26+ var lineNumber = 1 ;
27+ var count = - 1 ;
28+
29+ var capacity = ( int ) new FileInfo ( filePath ) . Length + 1 ;
30+
31+ var map = new List < MapInfo > ( capacity / 4 )
32+ {
33+ new MapInfo ( 0 , count ) ,
34+ } ;
35+
36+ var charPosToLineMap = new List < LineInfo > ( capacity )
37+ {
38+ new LineInfo ( 1 , 1 ) ,
39+ } ;
40+
41+ var lineLength = 0 ;
42+
43+ using ( var reader = new StreamReader ( filePath , encoding ) )
44+ {
45+ while ( ! reader . EndOfStream )
46+ {
47+ lineLength ++ ;
48+ count ++ ;
49+
50+ charPosToLineMap . Insert ( count , new LineInfo ( lineNumber , lineLength ) ) ;
51+
52+ var index = reader . Read ( ) ;
53+ switch ( index )
54+ {
55+ case NewLine :
56+ {
57+ map . Insert ( lineNumber ++ , new MapInfo ( lineLength , count ) ) ;
58+ lineLength = 0 ;
59+ break ;
60+ }
61+
62+ case CarriageReturn :
63+ {
64+ // additional line break character ?
65+ var next = reader . Peek ( ) ;
66+ if ( next == NewLine )
67+ {
68+ // read over the character
69+ reader . Read ( ) ;
70+
71+ lineLength ++ ;
72+ count ++ ;
73+
74+ charPosToLineMap . Insert ( count , new LineInfo ( lineNumber , lineLength ) ) ;
75+ }
76+
77+ map . Insert ( lineNumber ++ , new MapInfo ( lineLength , count ) ) ;
78+ lineLength = 0 ;
79+ break ;
80+ }
81+ }
82+ }
83+ }
84+
85+ map . Insert ( lineNumber , new MapInfo ( lineLength , count ) ) ;
86+
87+ return new CharacterPositionFinder ( map , charPosToLineMap ) ;
88+ }
89+
90+ public void Dispose ( )
91+ {
92+ _lineNumberToLengthAndCountMap . Clear ( ) ;
93+ _characterPositionToLineInfoMap . Clear ( ) ;
94+ }
95+
96+ public int GetCharacterPosition ( LineInfo lineInfo ) => GetCharacterPosition ( lineInfo . LineNumber , lineInfo . LinePosition ) ;
97+
98+ public int GetCharacterPosition ( int lineNumber , int linePosition )
99+ {
100+ if ( lineNumber == 0 && linePosition == 0 )
101+ {
102+ return 0 ;
103+ }
104+
105+ var info = _lineNumberToLengthAndCountMap [ lineNumber - 1 ] ; // get previous line and then add the line position
106+
107+ return info . CharacterCount + linePosition ;
108+ }
109+
110+ public int GetLineLength ( LineInfo lineInfo ) => GetLineLength ( lineInfo . LineNumber ) ;
111+
112+ public int GetLineLength ( int lineNumber )
113+ {
114+ var info = _lineNumberToLengthAndCountMap [ lineNumber ] ;
115+
116+ return info . LineLength ;
117+ }
118+
119+ public LineInfo GetLineInfo ( int characterPosition )
120+ {
121+ if ( characterPosition >= 0 )
122+ {
123+ return _characterPositionToLineInfoMap [ characterPosition ] ;
124+ }
125+
126+ return LineInfo . None ;
127+ }
128+
129+ private struct MapInfo : IEquatable < MapInfo >
130+ {
131+ internal readonly int LineLength ;
132+ internal readonly int CharacterCount ;
133+
134+ internal MapInfo ( int lineLength , int characterCount )
135+ {
136+ LineLength = lineLength ;
137+ CharacterCount = characterCount ;
138+ }
139+
140+ public static bool operator == ( MapInfo left , MapInfo right ) => left . Equals ( right ) ;
141+
142+ public static bool operator != ( MapInfo left , MapInfo right ) => ! left . Equals ( right ) ;
143+
144+ public bool Equals ( MapInfo other ) => LineLength == other . LineLength && CharacterCount == other . CharacterCount ;
145+
146+ public override bool Equals ( object obj ) => obj is MapInfo other && Equals ( other ) ;
147+
148+ public override int GetHashCode ( )
149+ {
150+ unchecked
151+ {
152+ return ( LineLength * 397 ) ^ CharacterCount ;
153+ }
154+ }
155+
156+ public override string ToString ( ) => string . Concat ( nameof ( LineLength ) + "=" , LineLength , " " + nameof ( CharacterCount ) + "=" , CharacterCount ) ;
157+ }
158+ }
159+ }
0 commit comments