hare

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

split.ha (2620B)


      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 (true) {
     27 		const r = match (strings::next(&iter)) {
     28 		case let r: rune =>
     29 			yield r;
     30 		case void =>
     31 			break;
     32 		};
     33 
     34 		dirty = true;
     35 		switch (r) {
     36 		case ' ', '\t', '\n' =>
     37 			for (true) match (strings::next(&iter)) {
     38 			case let r: rune =>
     39 				if (r != ' ' && r != '\t' && r != '\n') {
     40 					strings::prev(&iter); // Unget
     41 					break;
     42 				};
     43 			case void =>
     44 				break;
     45 			};
     46 			if (!first) {
     47 				const item = memio::string(&s)!;
     48 				append(slice, strings::dup(item));
     49 				memio::reset(&s);
     50 			};
     51 			dirty = false;
     52 		case '\\' =>
     53 			scan_backslash(&s, &iter)?;
     54 		case '"' =>
     55 			scan_double(&s, &iter)?;
     56 		case '\'' =>
     57 			scan_single(&s, &iter)?;
     58 		case =>
     59 			memio::appendrune(&s, r)!;
     60 		};
     61 
     62 		if (first) {
     63 			first = false;
     64 		};
     65 	};
     66 
     67 	if (dirty) {
     68 		const item = memio::string(&s)!;
     69 		append(slice, strings::dup(item));
     70 	};
     71 
     72 	return slice;
     73 };
     74 
     75 fn scan_backslash(out: io::handle, in: *strings::iterator) (void | syntaxerr) = {
     76 	const r = match (strings::next(in)) {
     77 	case let r: rune =>
     78 		yield r;
     79 	case void =>
     80 		return syntaxerr;
     81 	};
     82 
     83 	// The <backslash> and <newline> shall be removed before splitting the
     84 	// input into tokens. Since the escaped <newline> is removed entirely
     85 	// from the input and is not replaced by any white space, it cannot
     86 	// serve as a token separator
     87 	if (r == '\n') {
     88 		return;
     89 	};
     90 
     91 	memio::appendrune(out, r)!;
     92 };
     93 
     94 fn scan_double(out: io::handle, in: *strings::iterator) (void | syntaxerr) = {
     95 	for (true) {
     96 		const r = match (strings::next(in)) {
     97 		case let r: rune =>
     98 			yield r;
     99 		case void =>
    100 			return syntaxerr;
    101 		};
    102 
    103 		switch (r) {
    104 		case '"' =>
    105 			break;
    106 		case '\\' =>
    107 			scan_backslash(out, in)?;
    108 		case =>
    109 			memio::appendrune(out, r)!;
    110 		};
    111 	};
    112 };
    113 
    114 fn scan_single(out: io::handle, in: *strings::iterator) (void | syntaxerr) = {
    115 	for (true) {
    116 		const r = match (strings::next(in)) {
    117 		case let r: rune =>
    118 			yield r;
    119 		case void =>
    120 			return syntaxerr;
    121 		};
    122 
    123 		if (r == '\'') {
    124 			break;
    125 		};
    126 		memio::appendrune(out, r)!;
    127 	};
    128 };