hare

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

split.ha (2437B)


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