/*-
 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
 *
 * Copyright (c) 2021 Tobias Kortkamp <tobik@FreeBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "config.h"

#include <sys/types.h>
#include <inttypes.h>
#include <stdbool.h>

#include "peg.h"
#include "peg/grammar.h"
#include "peg/mtree.h"

// XXX This grammar does not yet handle
// - line continuations
// - end of line comments (also no \# for filenames)
 
// Prototypes
static RULE(character);
static RULE(comment);
static RULE(eol);
static RULE(file);
static RULE(filemode);
static RULE(filename);
static RULE(hex_number);
static RULE(keyword);
static RULE(keyword_0);
static RULE(keyword_cksum);
static RULE(keyword_device);
static RULE(keyword_device_format);
static RULE(keyword_device_value);
static RULE(keyword_device_value_0);
static RULE(keyword_device_value_1);
static RULE(keyword_flags);
static RULE(keyword_gid);
static RULE(keyword_gname);
static RULE(keyword_link);
static RULE(keyword_md5);
static RULE(keyword_mode);
static RULE(keyword_nlink);
static RULE(keyword_rmd160);
static RULE(keyword_sha1);
static RULE(keyword_sha256);
static RULE(keyword_sha384);
static RULE(keyword_sha512);
static RULE(keyword_size);
static RULE(keyword_tags);
static RULE(keyword_tags_value);
static RULE(keyword_tags_value_0);
static RULE(keyword_tags_value_1);
static RULE(keyword_tags_value_character);
static RULE(keyword_tags_value_delimiter);
static RULE(keyword_time);
static RULE(keyword_type);
static RULE(keyword_type_values);
static RULE(keyword_uid);
static RULE(keyword_uname);
static RULE(main_0);
static RULE(number);
static RULE(numeric_value);
static RULE(setstate_);
static RULE(unsetallstate);
static RULE(unsetstate_);
static RULE(value);
static RULE(whitespace);

RULE(whitespace) { return CHAR(' ', '\t'); }

RULE(character) {
	// isprint without space
	if (!RANGE(041, 0176))
	return REJECT;
	return ACCEPT;
}

RULE(number) { return RANGE('0', '9'); }

RULE(eol) {
	if (ANY(whitespace))
	if (CHAR('\n'))
	return ACCEPT;
	return REJECT;
}

RULE(filename) {
	return MATCH(value); // TODO
}

RULE(filemode) {
	return MATCH(value); // TODO
}

RULE(numeric_value) { return SOME(number); }
RULE(value) { return SOME(character); }

RULE(hex_number) {
	if (!RANGE('0', '9'))
	if (!RANGE('a', 'f'))
	if (!RANGE('A', 'F'))
	return ERROR("expected hex number");
	return ACCEPT;
}

RULE(keyword_device) {
	if (CAPTURE(STRING("device"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(MATCH(keyword_device_value), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_device_value) {
	if (!MATCH(keyword_device_value_0))
	if (!MATCH(keyword_device_value_1))
	if (!SOME(number))
	return REJECT;
	return ACCEPT;
}

RULE(keyword_device_value_0) {
	if (MATCH(keyword_device_format))
	if (CHAR(','))
	if (SOME(number))
	if (CHAR(','))
	if (SOME(number))
	if (CHAR(','))
	if (SOME(number))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_device_value_1) {
	if (MATCH(keyword_device_format))
	if (CHAR(','))
	if (SOME(number))
	if (CHAR(','))
	if (SOME(number))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_device_format) {
	return STRING("native", "386bsd", "4bsd", "bsdos", "freebsd", "hpux", "isc", "linux", "netbsd", "osf1", "sco", "solaris", "sunos", "svr3", "svr4", "ultrix");
}

RULE(keyword_flags) {
	if (CAPTURE(STRING("flags"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(MATCH(value), 0, PEG_MTREE_KEYWORD_VALUE)) // TODO
	return ACCEPT;
	return REJECT;
}

RULE(keyword_link) {
	if (CAPTURE(STRING("link"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(MATCH(filename), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_mode) {
	if (CAPTURE(STRING("mode"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(MATCH(filemode), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_nlink) {
	if (CAPTURE(STRING("nlink"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(SOME(number), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_cksum) {
	if (CAPTURE(STRING("cksum"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(SOME(number), 0, PEG_MTREE_KEYWORD_VALUE)) // TODO?
	return ACCEPT;
	return REJECT;
}

RULE(keyword_md5) {
	if (CAPTURE(STRING("md5", "md5digest"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(REPEAT(hex_number, 32), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_rmd160) {
	if (CAPTURE(STRING("ripemd160digest", "rmd160", "rmd160digest"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(REPEAT(hex_number, 40), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_sha1) {
	if (CAPTURE(STRING("sha1", "sha1digest"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(REPEAT(hex_number, 40), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_sha256) {
	if (CAPTURE(STRING("sha256", "sha256digest"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(REPEAT(hex_number, 64), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_sha384) {
	if (CAPTURE(STRING("sha384", "sha384digest"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(REPEAT(hex_number, 96), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_sha512) {
	if (CAPTURE(STRING("sha512", "sha512digest"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(REPEAT(hex_number, 128), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_size) {
	if (CAPTURE(STRING("size"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(MATCH(numeric_value), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_tags) {
	if (CAPTURE(STRING("tags"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(SOME(keyword_tags_value), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_tags_value_character) {
	// isprint without space and ,
	if (!RANGE(041, 053))
	if (!RANGE(055, 0176))
	return REJECT;
	return ACCEPT;
}

RULE(keyword_tags_value_delimiter) { return CHAR(','); }

RULE(keyword_tags_value) {
	if (!MATCH(keyword_tags_value_0))
	if (!MATCH(keyword_tags_value_1))
	return REJECT;
	return ACCEPT;
}

RULE(keyword_tags_value_0) {
	if (ANY(keyword_tags_value_delimiter))
	if (SOME(keyword_tags_value_character))
	if (MATCH(keyword_tags_value_delimiter))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_tags_value_1) {
	if (ANY(keyword_tags_value_delimiter))
	if (MATCH(keyword_tags_value_character))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_time) {
	if (STRING("time"))
	if (CHAR('='))
	if (MATCH(value)) // TODO
	return ACCEPT;
	return REJECT;
}

RULE(keyword_type) {
	if (CAPTURE(STRING("type"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(MATCH(keyword_type_values), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_type_values) {
	if (!STRING("block", "char", "dir", "fifo", "file", "link", "socket"))
	return ERROR("invald value for 'type' keyword");
	return ACCEPT;
}

RULE(keyword_gid) {
	if (CAPTURE(STRING("gid"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(MATCH(numeric_value), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_uid) {
	if (CAPTURE(STRING("uid"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(MATCH(numeric_value), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_gname) {
	if (CAPTURE(STRING("gname"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(MATCH(value), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_uname) {
	if (CAPTURE(STRING("uname"), 0, PEG_MTREE_KEYWORD))
	if (CHAR('='))
	if (CAPTURE(MATCH(value), 0, PEG_MTREE_KEYWORD_VALUE))
	return ACCEPT;
	return REJECT;
}

RULE(keyword) {
	if (SOME(whitespace))
	if (MATCH(keyword_0))
	return ACCEPT;
	return REJECT;
}

RULE(keyword_0) {
	if (!STRING("ignore", "ignore="))
	if (!STRING("nochange", "nochange="))
	if (!STRING("optional", "optional="))
	if (!MATCH(keyword_device))
	if (!MATCH(keyword_flags))
	if (!MATCH(keyword_gid))
	if (!MATCH(keyword_gname))
	if (!MATCH(keyword_link))
	if (!MATCH(keyword_mode))
	if (!MATCH(keyword_nlink))
	if (!MATCH(keyword_cksum))
	if (!MATCH(keyword_md5))
	if (!MATCH(keyword_rmd160))
	if (!MATCH(keyword_sha1))
	if (!MATCH(keyword_sha256))
	if (!MATCH(keyword_sha384))
	if (!MATCH(keyword_sha512))
	if (!MATCH(keyword_size))
	if (!MATCH(keyword_tags))
	if (!MATCH(keyword_time))
	if (!MATCH(keyword_type))
	if (!MATCH(keyword_uid))
	if (!MATCH(keyword_uname))
	return REJECT;
	return ACCEPT;
}

RULE(comment) {
	if (ANY(whitespace))
	if (CHAR('#'))
	if (MATCH_TO(eol))
	if (MATCH(eol))
	return ACCEPT;
	return REJECT;
}

RULE(file) {
	if (ANY(whitespace))
	if (CAPTURE(MATCH(filename), 0, PEG_MTREE_FILENAME))
	if (ANY(keyword))
	if (CAPTURE(MATCH(eol), 0, PEG_MTREE_FILENAME_ACCEPT))
	return ACCEPT;
	return REJECT;
}

RULE(setstate_) {
	if (ANY(whitespace))
	if (STRING("/set"))
	if (ANY(keyword))
	if (MATCH(eol))
	return ACCEPT;
	return REJECT;
}

RULE(unsetstate_) {
	if (ANY(whitespace))
	if (STRING("/unset"))
	if (SOME(keyword))
	if (MATCH(eol))
	return ACCEPT;
	return REJECT;
}

RULE(unsetallstate) {
	if (ANY(whitespace))
	if (STRING("/unset"))
	if (SOME(whitespace))
	if (STRING("all"))
	if (MATCH(eol))
	return ACCEPT;
	return REJECT;
}

RULE(main_0) {
	if (!MATCH(comment))
	if (!MATCH(setstate_))
	if (!MATCH(unsetallstate))
	if (!MATCH(unsetstate_))
	if (!MATCH(file))
	if (!MATCH(eol))
	return REJECT;
	return ACCEPT;
}

RULE(peg_mtree_decode) {
	if (SOME(main_0))
	if (EOS())
	return ACCEPT;
	return REJECT;
}
