1 // =================================
  2 // Copyright (c) 2024 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System;
  7 using System.Collections;
  8 
  9 namespace System.Lex
 10 {
 11     public List<int> ComputeLineStartIndeces(const ustring& text)
 12     {
 13         List<int> indeces;
 14         indeces.Add(0);
 15         int state = 0;
 16         int n = cast<int>(text.Length());
 17         for (int i = 0; i < n; ++i;)
 18         {
 19             uchar c = text[i];
 20             switch (state)
 21             {
 22                 case 0:
 23                 {
 24                     indeces.Add(i);
 25                     if (c != '\n')
 26                     {
 27                         state = 1;
 28                     }
 29                     break;
 30                 }
 31                 case 1:
 32                 {
 33                     if (c == '\n')
 34                     {
 35                         state = 0;
 36                     }
 37                     break;
 38                 }
 39             }
 40         }
 41         indeces.Add(n);
 42         return indeces;
 43     }
 44 
 45     public class SourceFile
 46     {
 47         public SourceFile(ustring&& content_List<int>&& lineStartIndeces_) : content(content_)lineStartIndeces(lineStartIndeces_)
 48         {
 49         }
 50         public inline const ustring& Content() const
 51         {
 52             return content;
 53         }
 54         public inline uchar* Begin() const
 55         {
 56             return content.Chars();
 57         }
 58         public inline uchar* End() const
 59         {
 60             return content.Chars() + content.Length();
 61         }
 62         public inline const List<int>& LineStartIndeces() const
 63         {
 64             return lineStartIndeces;
 65         }
 66         [nodiscard]
 67         public Result<string> GetLine(int lineNumber) const
 68         {
 69             int start = lineStartIndeces[lineNumber];
 70             int len = -1;
 71             if (lineNumber < lineStartIndeces.Count() - 1)
 72             {
 73                 len = lineStartIndeces[lineNumber + 1] - start;
 74             }
 75             ustring line;
 76             if (len != -1)
 77             {
 78                 line = content.Substring(startlen);
 79             }
 80             else
 81             {
 82                 line = content.Substring(start);
 83             }
 84             ustring trimmedLine = TrimEnd(line);
 85             auto result = ToUtf8(trimmedLine);
 86             if (result.Error())
 87             {
 88                 return Result<string>(ErrorId(result.GetErrorId()));
 89             }
 90             return Result<string>(result.Value());
 91         }
 92         private ustring content;
 93         private List<int> lineStartIndeces;
 94     }
 95 
 96     public class FileMap
 97     {
 98         public FileMap()
 99         {
100         }
101         public int MapFileName(const string& fileName)
102         {
103             int fileIndex = cast<int>(fileNames.Count());
104             fileNames.Add(fileName);
105             return fileIndex;
106         }
107         public bool HasFileName(int fileIndex) const
108         {
109             return fileIndex >= 0 && fileIndex < fileNames.Count();
110         }
111         public const List<string>& FileNames() const
112         {
113             return fileNames;
114         }
115         public const string& GetFileName(int fileIndex) const
116         {
117             return fileNames[fileIndex];
118         }
119         public void AddSourceFile(int fileIndexustring&& contentList<int>&& lineStartIndeces)
120         {
121             sourceFileMap[fileIndex] = SourceFile(contentlineStartIndeces);
122         }
123         public SourceFile* GetSourceFile(int fileIndex) const
124         {
125             auto it = sourceFileMap.Find(fileIndex);
126             if (it != sourceFileMap.End())
127             {
128                 return &(it->second);
129             }
130             else
131             {
132                 return null;
133             }
134         }
135         public Result<SourceFile*> GetOrReadSourceFile(int fileIndex) const
136         {
137             SourceFile* sourceFile = GetSourceFile(fileIndex);
138             if (sourceFile != null)
139             {
140                 return Result<SourceFile*>(sourceFile);
141             }
142             else
143             {
144                 auto readResult = System.IO.File.ReadAllText(fileNames[fileIndex]);
145                 if (readResult.Error()) return Result<SourceFile*>(ErrorId(readResult.GetErrorId()));
146                 const string& content = readResult.Value();
147                 auto utf32Result = ToUtf32(content);
148                 if (utf32Result.Error()) return Result<SourceFile*>(ErrorId(utf32Result.GetErrorId()));
149                 ustring text = Rvalue(utf32Result.Value());
150                 List<int> lineStartIndeces = ComputeLineStartIndeces(text);
151                 AddSourceFile(fileIndexRvalue(text)Rvalue(lineStartIndeces));
152                 return GetSourceFile(fileIndex);
153             }
154         }
155         private List<string> fileNames;
156         private Map<intSourceFile> sourceFileMap;
157     }
158 
159     [nodiscard]
160     public Result<string> MakeMessage(const string& messageconst Span& spanint fileIndexFileMap& fileMap)
161     {
162         string msg = message;
163         string fileName;
164         if (fileMap.HasFileName(fileIndex))
165         {
166             fileName = fileMap.GetFileName(fileIndex);
167         }
168         SourceFile* sourceFile = fileMap.GetSourceFile(fileIndex);
169         if (sourceFile != null)
170         {
171             LineColLen lineColLen = SpanToLineColLen(spansourceFile->LineStartIndeces());
172             System.IO.StringWriter writer;
173             auto lineResult = sourceFile->GetLine(lineColLen.line);
174             if (lineResult.Error())
175             {
176                 return Result<string>(ErrorId(lineResult.GetErrorId()));
177             }
178             writer << message << ": " << fileName << ":" << lineColLen.line << ":\n" << lineResult.Value() << "\n" << 
179                 string(' 'lineColLen.col - 1) << string('^'lineColLen.len) << endl();
180             msg = writer.GetString();
181         }
182         return Result<string>(msg);
183     }
184 }