/home/i/wrk/d/bogoyaml/bogoyaml.d

module bogoyaml;

private import std.cstream;
private import std.conv;
private import std.string;
private import std.ctype;
private import std.array;

private import std.c.stdio;

class YamlError : Error {
    this(char[] msg) {
        super(msg);
    }
    this(Object o, char[] msg) {
        super(o.classinfo.name ~ ": " ~ msg);
    }
}

class YamlHolder {
public:
	void dump(char[] filename) {
		auto File file = new File(filename, FileMode.Out);
		dump(file);
	}
	void dump(Stream o) {
		o.writeString("--- # bogoYAML:0.1");
		dumpLocal(o, 0);
	}

	abstract void dumpLocal(Stream o, int depth);

public:
	YamlHolder get(char[] key) {
		throw new YamlError(this, "not associative array");
		return null;
	}
	YamlHolder get(uint key) {
		throw new YamlError(this, "not array");
		return null;
	}

	YamlHolder[char[]] assoc() {
		throw new YamlError(this, "not associative array");
		return null;
	}
	char[][char[]] stringMap() {
		throw new YamlError(this, "not associative array");
		return null;
	}
	int[char[]] integerMap() {
		throw new YamlError(this, "not associative array");
		return null;
	}
	double[char[]] decimalMap() {
		throw new YamlError(this, "not associative array");
		return null;
	}
	void assoc(YamlHolder[char[]] a) {
		throw new YamlError(this, "not associative array");
	}
	void stringMap(char[][char[]] strs) {
		throw new YamlError(this, "not associative array");
	}
	void integerMap(int[char[]] ints) {
		throw new YamlError(this, "not associative array");
	}
	void decimalMap(double[char[]] decs) {
		throw new YamlError(this, "not associative array");
	}

	YamlHolder[] array() {
		throw new YamlError(this, "not array");
		return null;
	}
	char[][] strings() {
		throw new YamlError(this, "not array");
		return null;
	}
	int[] integers() {
		throw new YamlError(this, "not array");
		return null;
	}
	double[] decimals() {
		throw new YamlError(this, "not array");
		return null;
	}
	void array(YamlHolder[] a) {
		throw new YamlError(this, "not array");
	}
	void strings(char[][] strs) {
		throw new YamlError(this, "not array");
	}
	void integers(int[] ints) {
		throw new YamlError(this, "not array");
	}
	void decimals(double[] decs) {
		throw new YamlError(this, "not array");
	}

	char[] str() {
		throw new YamlError(this, "not scalar");
		return null;
	}
	int integer() {
		throw new YamlError(this, "not scalar");
		return 0;
	}
	double decimal() {
		throw new YamlError(this, "not scalar");
		return 0;
	}
	void str(char[] s) {
		throw new YamlError(this, "not scalar");
	}
	void integer(int i) {
		throw new YamlError(this, "not scalar");
	}
	void decimal(double d) {
		throw new YamlError(this, "not scalar");
	}

	bit isAssoc() { return false; }
	bit isArray() { return false; }
	bit isScalar() { return false; }

}

class YamlAssoc : YamlHolder {
public:
	this() {}
	this(YamlHolder[char[]] a) {
		assoc = a;
	}
	this(char[][char[]] strs) {
		stringMap = strs;
	}
	this(int[char[]] ints) {
		integerMap = ints;
	}
	this(double[char[]] decs) {
		decimalMap = decs;
	}

public:
	void dumpLocal(Stream o, int depth) {
		foreach (char[] key; assoc.keys) {
			o.writeLine("");
			for (int i = 0; i < depth; i++) o.write('\t');
			o.writeString(key ~ ": ");
			get(key).dumpLocal(o, depth+1);
		}
	}

public:
	YamlHolder get(char[] key) {
		debug {
			if (!(key in assoc_)) {
				throw new YamlError(this, "key not exists: " ~ key);
			}
		}
		return assoc_[key];
	}

	YamlHolder[char[]] assoc() {
		return assoc_;
	}
	char[][char[]] stringMap() {
		char[][char[]] ret;
		foreach (char[] k; assoc_.keys) {
			ret[k] = assoc_[k].str;
		}
		return ret;
	}
	int[char[]] integerMap() {
		int[char[]] ret;
		foreach (char[] k; assoc_.keys) {
			ret[k] = assoc_[k].integer;
		}
		return ret;
	}
	double[char[]] decimalMap() {
		double[char[]] ret;
		foreach (char[] k; assoc_.keys) {
			ret[k] = assoc_[k].decimal;
		}
		return ret;
	}

	void assoc(YamlHolder[char[]] a) {
		assoc_ = a;
	}
	void stringMap(char[][char[]] strs) {
		foreach (char[] k; strs.keys) {
			assoc_[k] = new YamlScalar(strs[k]);
		}
	}
	void integerMap(int[char[]] ints) {
		foreach (char[] k; ints.keys) {
			assoc_[k] = new YamlScalar(ints[k]);
		}
	}
	void decimalMap(double[char[]] decs) {
		foreach (char[] k; decs.keys) {
			assoc_[k] = new YamlScalar(decs[k]);
		}
	}

	bit isAssoc() { return true; }

private:
	YamlHolder[char[]] assoc_;
}

class YamlArray : YamlHolder {
public:
	this() {}
	this(YamlHolder[] a) {
		array = a;
	}
	this(char[][] strs) {
		strings = strs;
	}
	this(int[] ints) {
		integers = ints;
	}
	this(double[] decs) {
		decimals = decs;
	}

public:
	void dumpLocal(Stream o, int depth) {
		foreach (YamlHolder y; array()) {
			o.writeLine("");
			for (int i = 0; i < depth; i++) o.write('\t');
			o.writeString("- ");
			y.dumpLocal(o, depth+1);
		}
	}

public:
	YamlHolder get(uint key) {
        debug {
			if (key >= array_.length) {
				throw new YamlError(this, "key not exists: " ~
									std.string.toString(key));
			}
		}
		return array_[key];
	}

	YamlHolder[] array() {
		return array_;
	}
	char[][] strings() {
		char[][] ret;
		foreach (YamlHolder y; array_) {
			ret ~= y.str;
		}
		return ret;
	}
	int[] integers() {
		int[] ret;
		foreach (YamlHolder y; array_) {
			ret ~= y.integer;
		}
		return ret;
	}
	double[] decimals() {
		double[] ret;
		foreach (YamlHolder y; array_) {
			ret ~= y.decimal;
		}
		return ret;
	}

	void array(YamlHolder[] a) {
		array_ = a;
	}
	void strings(char[][] strs) {
		foreach (char[] s; strs) {
			array_ ~= new YamlScalar(s);
		} 
	}
	void integers(int[] ints) {
		foreach (int i; ints) {
			array_ ~= new YamlScalar(i);
		} 
	}
	void decimals(double[] decs) {
		foreach (double d; decs) {
			array_ ~= new YamlScalar(d);
		} 
	}

	bit isArray() { return true; }

private:
	YamlHolder[] array_;
}

class YamlScalar : YamlHolder {
public:
	this(char[] s) {
		str = s;
	}
	this(int i) {
		integer = i;
	}
	this(double d) {
		decimal = d;
	}

public:
	void dumpLocal(Stream o, int depth) {
		static char[] esc = " \a\b\f\n\r\t\v\"\\";
		foreach (char c; esc) {
			if (std.string.find(str, c) != -1) {
				str = "\"" ~ str ~ "\"";
				break;
			}
		}
		o.writeString(str);
	}

public:
	char[] str() {
		return str_;
	}
	int integer() {
		return std.conv.toInt(str_);
	}
	double decimal() {
		return atof(str_);
	}

	void str(char[] s) {
		str_ = s;
	}
	void integer(int i) {
		str = std.string.toString(i);
	}
	void decimal(double d) {
		char buf[256];
		sprintf(buf, "%f", d);
		str = buf;
	}

	bit isScalar() { return true; }

private:
	char[] str_;
}

YamlHolder yamlLoad(Stream s) {
	char[] input = s.toString();
	input ~= '\0';

	debug (Internal) dout.writeLine("load done");

	return yamlLoad_(input);
}

YamlHolder yamlLoad(char[] filename) {
	debug (Internal) dout.writeLine("load " ~ filename);

	auto File file = new File(filename);
	return yamlLoad(file);
}

private:

void p(char[] msg) {
	debug (Internal) {
		dout.writeLine(msg);
	}
}

class YamlParserContext {
public:
	char[] input;
	int position;
	YamlHolder[] stack;
	int depth;
	char[] key;

	this(char[] i) {
		input = i;
		position = 0;
		stack.length = 10;
	}

	char current() { return input[position]; }
	char current(int offset) {
		return input[position+offset];
	}

	char[] substr(int offset) {
		return input[position .. position+offset];
	}
	char[] substr() {
		return input[position .. input.length];
	}

	void goForward() { position++; }
	void goBackward() { position--; }
	void goForward(int step) { position += step; }
	void goBackward(int step) { position -= step; }

	char[] toString() {
		int lines = 1;
		int pos = 0;
		int linepos = 0;
		char prev = '\0';
		foreach (char now; input) {
			if (now == '\r' || (now == '\n' && prev != '\r')) {
				lines++;
				linepos = 0;
			}
			prev = now;
			pos++;
			linepos++;
			if (position <= pos) break;
		}

		return
			" in line" ~ std.string.toString(lines) ~
			" pos" ~ std.string.toString(position - pos);
	}

	bit ignoreComment() {
		if (current == '#') {
			while (current != '\n' && current != '\r') {
				goForward();
			}
			if (current == '\r' && current(1) == '\n') goForward();
			goBackward();
			return true;
		}
		return false;
	}

	bit procLineEnd() {
		int first = position;

		while (current == ' ' || current == '\t' || ignoreComment()) {
			goForward();
		}

		switch (current()) {
		case '\r':
			if (current(1) == '\n') goForward();
		case '\n':
			goForward();
		case '\0':
			return true;
		default:
			position = first;
			return false;
		}
	}

	bit isEnd() {
		return current == '\0';
	}

	void checkStackSize(int d) {
		while (d >= stack.length) {
			stack.length = stack.length * 2;
		}
	}

	void add(YamlHolder holder) {
		if (!holder.isScalar()) {
			stack[depth] = holder;
		}

		for (int d = depth-1; d >= 0; d--) {
			if (stack[d] !is null) {
				if (stack[d].isArray()) {
					YamlHolder[] a = stack[d].array;
					a ~= holder;
					stack[d].array = a;
					break;
				}
				else if (stack[d].isAssoc()) {
					assert(key !is null);

					YamlHolder[char[]] a = stack[d].assoc;
					a[key] = holder;
					stack[d].assoc = a;
					break;
				}
				else {
					assert(false);
				}
			}
		}
	}

	void throwError(char[] msg) {
		throw new YamlError("Parser: " ~ msg ~ toString());
	}

	char[] getScalar(bit colonEnd) {
		p("get scalar");

		if (procLineEnd()) return "";

		if (current == '\'' || current == '"') {
			char c = current;

			goForward();

			int pos = std.string.find(substr(), c);

			if (pos == -1) {
				if (c == '\'') {
					throwError("unmatched single quote");
				}
				else {
					throwError("unmatched double quote");
				}
			}

			char[] ret = substr(pos);
			pos++;
			position += pos;

			if (colonEnd) {
				while (current == ' ' || current == '\t') {
					goForward();
				}
			}
			else {
				if (!procLineEnd()) {
					if (c == '\'') {
						throwError("garbage after single quote");
					}
					else {
						throwError("garbage after double quote");
					}
				}
			}

			if (c == '"') {
				// it ends failure with "\\n", and it's slow...
				ret = std.string.replace(ret, "\\a", "\a");
				ret = std.string.replace(ret, "\\b", "\b");
				ret = std.string.replace(ret, "\\f", "\f");
				ret = std.string.replace(ret, "\\n", "\n");
				ret = std.string.replace(ret, "\\r", "\r");
				ret = std.string.replace(ret, "\\t", "\t");
				ret = std.string.replace(ret, "\\v", "\v");
				ret = std.string.replace(ret, "\\\"", "\"");
				ret = std.string.replace(ret, "\\ ", " ");
				ret = std.string.replace(ret, "\\\\", "\\");
			}

			p("=" ~ ret);

			return ret;
		}
		else {
			int pos;
			if (colonEnd) {
				pos = std.string.find(substr(), ':');
				if (pos == -1) return "";
			}
			else {
				int crpos = std.string.find(substr(), '\r');
				int lfpos = std.string.find(substr(), '\n');
				if (lfpos != -1 && (crpos == -1 || lfpos < crpos)) {
					pos = lfpos;
				}
				else if (crpos != -1) {
					pos = crpos;
				}
				else {
					pos = input.length - position - 1;
				}

				int sharppos = std.string.find(substr(), '#');
				if (sharppos != -1 && sharppos < pos) {
					pos = sharppos;
				}
			}

			char[] ret = substr(pos);
			position += pos;

			while (ret[ret.length-1] == ' ' || ret[ret.length-1] == '\t') {
				ret.length = ret.length - 1;
			}

			p("=" ~ ret);

			return ret;
		}
	}

	invariant {
		assert(depth < stack.length);
	}
}

class YamlParserState {
	YamlParserState parse(YamlParserContext c) {
		context = c;
		return parse_();
	}

	abstract YamlParserState parse_();

protected:
	void throwError(char[] msg) {
		throw new YamlError(this, msg ~ context.toString());
	}

protected:
	YamlParserContext context;
}

class YamlParserFirstLine : YamlParserState {
	YamlParserState parse_() {
		p("first line");

		with (context) {
			while (isspace(current) || ignoreComment()) {
				goForward();
			}

			if (substr(3) == "---") {
				goForward(3);
			}
			else {
				throwError("garbage before document header");
			}

			if (!procLineEnd()) {
				throwError("garbage after document header");
			}

			if (isEnd()) return null;
			else return new YamlParserLineHead();
		}
	}
}

class YamlParserLineHead : YamlParserState {
	YamlParserState parse_() {
		p("line head");

		with (context) {
			// skip emtpy line
			while (procLineEnd()) {
				if (isEnd()) return null;
			}

			int d = 0;
			while (isspace(current)) {
				goForward();
				d++;
			}

			checkStackSize(d);

			for (int i = depth; i > d; i--) {
				stack[i] = null;
			}

			depth = d;

			if (current == '-') return new YamlParserArray();
			else return new YamlParserAssoc();
		}
	}
}

class YamlParserArray : YamlParserState {
	YamlParserState parse_() {
		p("array");

		with (context) {
			if (stack[depth] is null) {
				add(new YamlArray());
			}

			goForward();

			while (current == ' ' || current == '\t' || ignoreComment()) {
				goForward();
			}

			if (!procLineEnd()) {
				depth++;
				add(new YamlScalar(getScalar(false)));
				depth--;
			}

			return new YamlParserLineHead();
		}
	}
}

class YamlParserAssoc : YamlParserState {
	YamlParserState parse_() {
		p("assoc");

		with (context) {
			if (stack[depth] is null) {
				add(new YamlAssoc());
			}

			key = getScalar(true);

			if (current != ':') {
				throwError("colon expected");
			}

			goForward();

			while (current == ' ' || current == '\t' || ignoreComment()) {
				goForward();
			}

			if (!procLineEnd()) {
				depth++;
				add(new YamlScalar(getScalar(false)));
				depth--;
			}

			return new YamlParserLineHead();
		}
	}
}

YamlHolder yamlLoad_(char[] input) {
	auto YamlParserContext context = new YamlParserContext(input);

	auto YamlParserState state = new YamlParserFirstLine();

	try {
		while ((state = state.parse(context)) !is null) {}
	}
	catch (ArrayBoundsError e) {
		context.throwError("premature end");
	}

	if (context.stack.length == 0) return null;
	else return context.stack[0];
}