aboutsummaryrefslogblamecommitdiffstats
path: root/command.c
blob: ab3b13da7da3af15b19ce0a3f5e81785ed04dfaa (plain) (tree)
1
2
3
4
5
6
7





                                                                                        
 
















                                                                            







                                                                                



               
                                               


































                                                                          









                         




















                                                                  
                                                                





                                          
                                                                  


































                                                               
   




                                            

                                         




                                                     
                 
 
/* Copyright (c) 2021 nytpu <alex@nytpu.com>
 * SPDX-License-Identifier: GPL-3.0-only
 * For more license details, see LICENSE or <https://www.gnu.org/licenses/gpl-3.0.html>.
 */

#include "command.h"

#include "common.h"

#include <ctype.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

static char *l;

/* This part is ~~ripped off~~ roughly adapted from FreeBSD's implementation
 * https://cgit.freebsd.org/src/tree/bin/ed/main.c#n284
 * I really have no real idea how it works at some parts
 * Also see LICENSE_FREEBSD
 */

#define SKIP_BLANKS()                                                          \
	while (*l != '\0' && isspace(*l)) ++l
#define MUST_BE_FIRST()                                                        \
	if (!first) {                                                          \
		error("Invalid address");                                      \
		return -1;                                                     \
	}
#define MIN(a, b) ((a) < (b) ? (a) : (b))

static RowNum
parse_num(void)
{
	RowNum num = (RowNum)strtol(l, &l, 10);
	if (num == 0) {
		if (errno == EINVAL) {
			error("Invalid address");
		} else if (errno == ERANGE) {
			error("Address out of range");
		}
	}
	return num;
}

// -2 means done, -1 means err
static RowNum
parse_address(struct command *c)
{
	RowNum addr = E.crow;

	SKIP_BLANKS();
	bool first = true;
	const char *sav;
	char ch;
	for (sav = l;; first = false) {
		switch (ch = *l) {
		case '+':
		case '-':
		case '^':
		case ' ':
			++l;
			SKIP_BLANKS();
			if (isdigit(*l)) {
				RowNum n = parse_num();
				addr += (ch == '-' || ch == '^') ? -n : n;
			} else if (!isspace(ch)) {
				addr += (ch == '-' || ch == '^') ? -1 : 1;
			}
			break;
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			MUST_BE_FIRST();
			addr = parse_num();
			break;
		case '.':
		case '$':
			MUST_BE_FIRST();
			++l;
			addr = (ch == '.') ? E.crow : E.numrows;
			break;
		case '/':
		case '?':
		case '\'':
			error("Not yet implmented");
			return -1;
		case ',':
		case ';':
			if (first) {
				++l;
				++c->cnt;
				c->end = (ch == ';') ? E.crow : 1;
				RowNum a = parse_address(c);
				if (a < 0) { addr = E.numrows; }
				break;
			}
			// fallthrough
		default:
			if (sav == l) {
				return -2;
			} else if (addr < 0 || addr > E.numrows) {
				error("Invalid address");
				return -1;
			} else {
				return addr;
			}
		}
	}
	// unreachable
}

static int
parse_address_range(struct command *c)
{
	RowNum addr;
	c->cnt = 0;

	c->start = c->end = E.crow;
	while ((addr = parse_address(c)) >= 0) {
		++c->cnt;
		c->start = c->end;
		c->end = addr;
		if (*l != ',' && *l != ';') {
			break;
		} else if (*l++ == ';') {
			E.crow = addr;
		}
	}
	if ((c->cnt = MIN(c->cnt, 2)) == 1 || c->end != addr) {
		c->start = c->end;
	}
	return (addr == -1) ? -1 : 0;
}

/* end FreeBSD ripoff */

int
parse_command(struct command *c, char *line)
{
	l = line;

	SKIP_BLANKS();
	int ret = parse_address_range(c);
	if (ret < 0) return ret;

	// TODO: this can't even be called a "parser"
	SKIP_BLANKS();
	c->suffix = '\0';
	c->command = *l;
	return 0;
}