split.ha (2483B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use io; 5 use memio; 6 use strings; 7 8 // Invalid shell syntax. 9 export type syntaxerr = !void; 10 11 // Converts an error into a human-friendly string. 12 export fn strerror(err: syntaxerr) str = "Invalid shell syntax"; 13 14 // Splits a string of arguments according to shell quoting. The result must be 15 // freed using [[strings::freeall]] when the caller is done processing it. 16 export fn split(in: const str) ([]str | syntaxerr) = { 17 let iter = strings::iter(in); 18 19 let s = memio::dynamic(); 20 defer io::close(&s)!; 21 22 let slice: []str = []; 23 let first = true; 24 let dirty = false; 25 26 for (let r => strings::next(&iter)) { 27 dirty = true; 28 switch (r) { 29 case ' ', '\t', '\n' => 30 for (let r => strings::next(&iter)) { 31 if (r != ' ' && r != '\t' && r != '\n') { 32 strings::prev(&iter); // Unget 33 break; 34 }; 35 }; 36 if (!first) { 37 const item = memio::string(&s)!; 38 append(slice, strings::dup(item)); 39 memio::reset(&s); 40 }; 41 dirty = false; 42 case '\\' => 43 scan_backslash(&s, &iter)?; 44 case '"' => 45 scan_double(&s, &iter)?; 46 case '\'' => 47 scan_single(&s, &iter)?; 48 case => 49 memio::appendrune(&s, r)!; 50 }; 51 52 if (first) { 53 first = false; 54 }; 55 }; 56 57 if (dirty) { 58 const item = memio::string(&s)!; 59 append(slice, strings::dup(item)); 60 }; 61 62 return slice; 63 }; 64 65 fn scan_backslash(out: io::handle, in: *strings::iterator) (void | syntaxerr) = { 66 const r = match (strings::next(in)) { 67 case let r: rune => 68 yield r; 69 case done => 70 return syntaxerr; 71 }; 72 73 // The <backslash> and <newline> shall be removed before splitting the 74 // input into tokens. Since the escaped <newline> is removed entirely 75 // from the input and is not replaced by any white space, it cannot 76 // serve as a token separator 77 if (r == '\n') { 78 return; 79 }; 80 81 memio::appendrune(out, r)!; 82 }; 83 84 fn scan_double(out: io::handle, in: *strings::iterator) (void | syntaxerr) = { 85 for (true) { 86 const r = match (strings::next(in)) { 87 case let r: rune => 88 yield r; 89 case done => 90 return syntaxerr; 91 }; 92 93 switch (r) { 94 case '"' => 95 break; 96 case '\\' => 97 scan_backslash(out, in)?; 98 case => 99 memio::appendrune(out, r)!; 100 }; 101 }; 102 }; 103 104 fn scan_single(out: io::handle, in: *strings::iterator) (void | syntaxerr) = { 105 for (true) { 106 const r = match (strings::next(in)) { 107 case let r: rune => 108 yield r; 109 case done => 110 return syntaxerr; 111 }; 112 113 if (r == '\'') { 114 break; 115 }; 116 memio::appendrune(out, r)!; 117 }; 118 };