1 module io; 2 3 import std.range; 4 import std.stdio; 5 import std.typecons; 6 7 Input read(string name, Flag!"offset" offset = No.offset) 8 { 9 return read(name, File(name), offset); 10 } 11 12 Input read(string name, File file, Flag!"offset" offset = No.offset) 13 { 14 import std.algorithm : joiner; 15 import std.array : array; 16 17 auto text = cast(char[]) file.byChunk(4096).joiner.array; 18 19 return Input(name, text, offset); 20 } 21 22 struct Input 23 { 24 private string name; 25 26 private const(char)[] text; 27 28 private size_t index_; 29 30 private size_t begin; 31 32 private size_t line = 1; 33 34 private size_t col = 1; // offset, for offset-based positions 35 36 private Flag!"offset" offset; 37 38 this(string name, const(char)[] text, Flag!"offset" offset = No.offset) @nogc nothrow 39 { 40 this.name = name; 41 this.text = text; 42 this.offset = offset; 43 if (offset) 44 line = col = 0; 45 } 46 47 void popFront() 48 in (!empty) 49 { 50 import std.utf : stride; 51 52 const lineBreak = front == '\n'; 53 54 if (lineBreak && !offset) 55 { 56 ++line; 57 col = 0; 58 } 59 index_ += text[index_ .. $].stride; 60 ++col; 61 if (lineBreak) 62 begin = index_; 63 } 64 65 dchar front() const 66 { 67 return empty ? 0 : text[index_ .. $].front; 68 } 69 70 bool empty() const @nogc nothrow 71 { 72 return text[index_ .. $].empty; 73 } 74 75 Position position() const pure @safe 76 { 77 import std.algorithm : find; 78 79 const end = text.length - text[begin .. $].find('\n').length; 80 81 return Position(name, line, col, text[begin .. end]); 82 } 83 84 const(char)[] sliceFrom(size_t begin) const @nogc nothrow 85 in (begin <= index) 86 { 87 return text[begin .. index_]; 88 } 89 90 size_t index() const @nogc nothrow 91 { 92 return index_; 93 } 94 } 95 96 const UndefPos = Position(); 97 98 struct Position 99 { 100 private string name; 101 102 private size_t line; 103 104 private size_t col; // offset, if line is 0 105 106 private const(char)[] text; 107 108 string toString() const @safe 109 { 110 import std.array : appender; 111 112 auto writer = appender!string; 113 114 toString(writer); 115 return writer[]; 116 } 117 118 void toString(W)(ref W writer) const 119 if (isOutputRange!(W, char)) 120 { 121 import std.algorithm : map; 122 import std.format : format; 123 import std.utf : count; 124 125 if (this == UndefPos) 126 { 127 writer.put("unknown position"); 128 return; 129 } 130 131 if (line == 0) // offset position 132 { 133 const link = format!"%s@%s"(name, col); 134 135 writer.put(link); 136 return; 137 } 138 139 const link = format!"%s:%s:%s"(name, line, col); 140 141 writer.put(link); 142 writer.put(' '); 143 writer.put(text); 144 writer.put('\n'); 145 writer.put(' '); 146 writer.put(' '.repeat(link.count)); 147 writer.put(text.take(col - 1).map!(c => (c == '\t') ? c : ' ')); 148 writer.put('^'); 149 } 150 } 151 152 @("convert position to string") 153 unittest 154 { 155 import std.string : outdent, strip; 156 157 const position = Position("äöü.txt", 42, 2, "äöü"); 158 const expected = ` 159 äöü.txt:42:2 äöü 160 ^ 161 `.outdent.strip; 162 163 assert(position.toString == expected); 164 } 165 166 @("convert offset position to string") 167 unittest 168 { 169 import std.string : outdent, strip; 170 171 const position = Position("äöü.txt", 0, 42, "äöü"); 172 const expected = ` 173 äöü.txt@42 174 `.outdent.strip; 175 176 assert(position.toString == expected); 177 } 178 179 @("convert unknown position to string") 180 unittest 181 { 182 import std.string : outdent, strip; 183 184 const expected = ` 185 unknown position 186 `.outdent.strip; 187 188 assert(UndefPos.toString == expected); 189 }