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