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