/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 == '"') {
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) {
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];
}