scan.ha (2157B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use bufio; 5 use encoding::utf8; 6 use fmt; 7 use io; 8 use strings; 9 10 export type scanner = struct { 11 in: io::handle, 12 line: str, 13 lineno: size, 14 section: str, 15 }; 16 17 // Creates an INI file scanner. Use [[next]] to read entries. The caller must 18 // call [[finish]] once they're done with this object. 19 export fn scan(in: io::handle) scanner = scanner { 20 in = in, 21 lineno = 1, 22 ... 23 }; 24 25 // Frees resources associated with a [[scanner]]. 26 export fn finish(sc: *scanner) void = { 27 free(sc.line); 28 free(sc.section); 29 }; 30 31 // An entry in an INI file: (section, key, value). 32 export type entry = (const str, const str, const str); 33 34 // Duplicates an [[entry]]. Use [[entry_finish]] to get rid of it. 35 export fn entry_dup(ent: entry) entry = ( 36 strings::dup(ent.0), 37 strings::dup(ent.1), 38 strings::dup(ent.2), 39 ); 40 41 // Frees an [[entry]] previously duplicated with [[entry_dup]]. 42 export fn entry_finish(ent: entry) void = { 43 free(ent.0); 44 free(ent.1); 45 free(ent.2); 46 }; 47 48 // Returns the next entry from an INI file. The return value is overwritten on 49 // subsequent calls, use [[entry_dup]] or [[strings::dup]] to extend the 50 // lifetime of the entry or its fields respectively. 51 export fn next(sc: *scanner) (entry | io::EOF | error) = { 52 for (true) { 53 const line = match (bufio::read_line(sc.in)?) { 54 case let b: []u8 => 55 yield strings::fromutf8(b)?; 56 case io::EOF => 57 return io::EOF; 58 }; 59 defer sc.lineno += 1; 60 61 free(sc.line); 62 sc.line = line; 63 64 const line = strings::trim(sc.line); 65 66 if (len(line) == 0 || strings::hasprefix(line, "#")) { 67 continue; 68 }; 69 70 if (strings::hasprefix(line, "[")) { 71 const end = match (strings::index(line, ']')) { 72 case let idx: size => 73 yield idx; 74 case void => 75 return sc.lineno: syntaxerr; 76 }; 77 free(sc.section); 78 sc.section = strings::dup(strings::sub(line, 1, end)); 79 continue; 80 }; 81 82 const eq = match (strings::index(line, '=')) { 83 case let idx: size => 84 yield idx; 85 case void => 86 return sc.lineno: syntaxerr; 87 }; 88 return ( 89 sc.section, 90 strings::sub(line, 0, eq), 91 strings::sub(line, eq + 1, strings::end), 92 ); 93 }; 94 };