hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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 };