dc

[hare] Tiny reverse polish desk calculator
Log | Files | Refs | README | LICENSE

dc.ha (5916B)


      1 use ascii;
      2 use bufio;
      3 use encoding::utf8;
      4 use fmt;
      5 use fs;
      6 use getopt;
      7 use io;
      8 use math;
      9 use os;
     10 use os::exec;
     11 use strconv;
     12 use strings;
     13 
     14 let S: []f64 = [];
     15 
     16 export fn main() void = {
     17 	const help: [_]getopt::help = [
     18 		"desk calculator",
     19 		"[file ...]",
     20 	];
     21 	const cmd = getopt::parse(os::args, help...);
     22 	defer getopt::finish(&cmd);
     23 
     24 	static const buf: [1]u8 = [0...];
     25 
     26 	for (let i = 0z; i < len(cmd.args); i += 1) {
     27 		const filename = switch (cmd.args[i]) {
     28 		case "-" =>
     29 			fmt::fatal("dc: invalid filename '-'");
     30 		case "" =>
     31 			fmt::fatal("dc: invalid filename ''");
     32 		case =>
     33 			yield cmd.args[i];
     34 		};
     35 
     36 		const file = match (os::open(filename)) {
     37 		case let f: io::file =>
     38 			yield f;
     39 		case let err: fs::error =>
     40 			fmt::fatal("dc: {} '{}'", fs::strerror(err), filename);
     41 		};
     42 		defer io::close(file)!;
     43 
     44 		const in = bufio::buffered(file, buf, []);
     45 		match (dc(&in)) {
     46 		case void =>
     47 			void;
     48 		case io::error =>
     49 			fmt::fatal("dc: IO error");
     50 		};
     51 	};
     52 
     53 	const in = bufio::buffered(os::stdin, buf, []);
     54 	match (dc(&in)) {
     55 	case void =>
     56 		void;
     57 	case io::error =>
     58 		fmt::fatal("dc: IO error");
     59 	};
     60 };
     61 
     62 fn dc(in: *bufio::bufstream) (void | io::error) = {
     63 	for (true) {
     64 		const r = match (bufio::scanrune(&in.stream)) {
     65 		case utf8::invalid =>
     66 			fmt::fatal("dc: invalid utf8 input");
     67 		case io::error =>
     68 			fmt::fatal("dc: IO error");
     69 		case io::EOF =>
     70 			break;
     71 		case let r: rune =>
     72 			yield r;
     73 		};
     74 
     75 		if (!ascii::isgraph(r)) {
     76 			continue;
     77 		};
     78 
     79 		if (ascii::isdigit(r)) {
     80 			bufio::unreadrune(in, r);
     81 			push(scan_number(in));
     82 			continue;
     83 		};
     84 
     85 		if (r == '_') {
     86 			push(-scan_number(in));
     87 			continue;
     88 		};
     89 
     90 		switch (r) {
     91 		// misc
     92 		case 'q' =>
     93 			os::exit(0);
     94 		case '!' =>
     95 			const cmdline = match (bufio::scanline(&in.stream)) {
     96 			case io::error =>
     97 				fmt::fatal("dc: IO error");
     98 			case io::EOF =>
     99 				fmt::errorln("dc: no shell command given")?;
    100 				continue;
    101 			case let input: []u8 =>
    102 				yield match (strings::try_fromutf8(input)) {
    103 				case utf8::invalid =>
    104 					fmt::errorln("dc: invalid shell command input")?;
    105 					continue;
    106 				case let c: str =>
    107 					yield c;
    108 				};
    109 			};
    110 			const argv = strings::split(cmdline, " ");
    111 			if (len(argv) == 0) {
    112 				continue;
    113 			};
    114 			const cmd = os::exec::cmd(argv[0], argv[1..]...)!; //TODO
    115 
    116 			const pipe = os::exec::pipe();
    117 			os::exec::addfile(&cmd, pipe.1, os::stdout_file);
    118 
    119 			const proc = os::exec::start(&cmd)!; //TODO
    120 			io::close(pipe.1)!;
    121 
    122 			let data = io::drain(pipe.0);
    123 			io::close(pipe.0)!;
    124 
    125 			const status = os::exec::wait(&proc);
    126 		// printing
    127 		case 'p' =>
    128 			if (len(S) == 0) {
    129 				fmt::errorln("dc: stack empty")?;
    130 				continue;
    131 			};
    132 			fmt::println(peek())?;
    133 		case 'n' =>
    134 			if (len(S) == 0) {
    135 				fmt::errorln("dc: stack empty")?;
    136 				continue;
    137 			};
    138 			fmt::println(pop())?;
    139 		case 'f' =>
    140 			for (let i = len(S) - 1; i < len(S); i -= 1) {
    141 				fmt::println(S[i])?;
    142 			};
    143 		// stack control
    144 		case 'c' =>
    145 			clear();
    146 		case 'd' =>
    147 			if (len(S) == 0) {
    148 				fmt::errorln("dc: stack empty")?;
    149 				continue;
    150 			};
    151 			push(peek());
    152 		case 'r' =>
    153 			if (len(S) < 2) {
    154 				fmt::errorln("dc: stack has too few elements")?;
    155 				continue;
    156 			};
    157 			const b = pop();
    158 			const a = pop();
    159 			push(b);
    160 			push(a);
    161 		case 'R' =>
    162 			let n = pop(): int;
    163 			if (n > 1) {
    164 				const l = len(S): int;
    165 				const n = if (n < l) n else l;
    166 				const i = l - n;
    167 				const a = S[i];
    168 				delete(S[i]);
    169 				append(S, a);
    170 			} else if (n < -1) {
    171 				const l = len(S): int;
    172 				const n = if (-n < l) -n else l;
    173 				const i = l - n;
    174 				const a = pop();
    175 				insert(S[i], a);
    176 			};
    177 		// arithmetic
    178 		case '+' =>
    179 			if (len(S) < 2) {
    180 				fmt::errorln("dc: stack has too few elements")?;
    181 				continue;
    182 			};
    183 			const b = pop();
    184 			const a = pop();
    185 			push(a + b);
    186 		case '-' =>
    187 			if (len(S) < 2) {
    188 				fmt::errorln("dc: stack has too few elements")?;
    189 				continue;
    190 			};
    191 			const b = pop();
    192 			const a = pop();
    193 			push(a - b);
    194 		case '*' =>
    195 			if (len(S) < 2) {
    196 				fmt::errorln("dc: stack has too few elements")?;
    197 				continue;
    198 			};
    199 			const b = pop();
    200 			const a = pop();
    201 			push(a * b);
    202 		case '/' =>
    203 			if (len(S) < 2) {
    204 				fmt::errorln("dc: stack has too few elements")?;
    205 				continue;
    206 			};
    207 			const b = pop();
    208 			const a = pop();
    209 			push(a / b);
    210 		case '%' =>
    211 			if (len(S) < 2) {
    212 				fmt::errorln("dc: stack has too few elements")?;
    213 				continue;
    214 			};
    215 			const b = pop();
    216 			const a = pop();
    217 			push(math::modf64(a, b));
    218 		case '^' =>
    219 			if (len(S) < 2) {
    220 				fmt::errorln("dc: stack has too few elements")?;
    221 				continue;
    222 			};
    223 			const b = pop();
    224 			const a = pop();
    225 			push(math::powf64(a, b));
    226 		case 'v' =>
    227 			if (len(S) < 1) {
    228 				fmt::errorln("dc: stack has too few elements")?;
    229 				continue;
    230 			};
    231 			const a = pop();
    232 			push(math::sqrtf64(a));
    233 		case =>
    234 			fmt::errorfln("dc: unimplemented '{}'", r)?;
    235 		};
    236 	};
    237 };
    238 
    239 @noreturn fn usage_exit(help: []getopt::help) void = {
    240 	getopt::printusage(os::stderr, os::args[0], help);
    241 	os::exit(1);
    242 };
    243 
    244 fn scan_number(in: *bufio::bufstream) f64 = {
    245 	let num: []u8 = [];
    246 	defer free(num);
    247 	let seen_decimal = false;
    248 	for (true) {
    249 		const r = match (bufio::scanrune(in)) {
    250 		case utf8::invalid =>
    251 			fmt::fatal("dc: invalid utf8 input");
    252 		case io::error =>
    253 			fmt::fatal("dc: IO error");
    254 		case io::EOF =>
    255 			yield ' ';
    256 		case let r: rune =>
    257 			yield r;
    258 		};
    259 		if (ascii::isdigit(r) || (!seen_decimal && r == '.')) {
    260 			append(num, r: u32: u8);
    261 			if (r == '.') {
    262 				seen_decimal = true;
    263 			};
    264 		} else {
    265 			bufio::unreadrune(in, r);
    266 			match (strconv::stof64(strings::fromutf8(num))) {
    267 			case (strconv::invalid | strconv::overflow) =>
    268 				abort("dc: invalid numerical input");
    269 			case let n: f64 =>
    270 				return n;
    271 			};
    272 		};
    273 	};
    274 	abort("Unreachable");
    275 };
    276 
    277 fn pop() f64 = {
    278 	const a = S[len(S) - 1];
    279 	delete(S[len(S) - 1]);
    280 	return a;
    281 };
    282 
    283 fn push(el: f64) void = {
    284 	append(S, el);
    285 };
    286 
    287 fn peek() f64 = {
    288 	return S[len(S) - 1];
    289 };
    290 
    291 fn clear() void = {
    292 	for (len(S) != 0) {
    293 		delete(S[len(S) - 1]);
    294 	};
    295 };