module loader.Cfg; private { //import loader.Loader; import utils.fileWrapper.File; import utils.Memory; import maths.Vec; import std.conv; import std.string : toString, splitlines; import mintl.sortedaa : SortedAA; static import mintl.mem; } enum FieldType : byte { String, Number, Binary, Array, Boolean, Directive, None } template TypeToFieldType(T) { static if (is(T == char[])) const FieldType TypeToFieldType = FieldType.String; else static if (is(T == int)) const FieldType TypeToFieldType = FieldType.Number; else static if (is(T == float)) const FieldType TypeToFieldType = FieldType.Number; else static if (is(T == double)) const FieldType TypeToFieldType = FieldType.Number; else static if (is(T == bool)) const FieldType TypeToFieldType = FieldType.Boolean; else static if (is(T == Directive)) const FieldType TypeToFieldType = FieldType.Directive; else static assert (false); } struct Directive { char[] name; Array param; } struct Field { union { char[] string; double number; void[] binary; Array* array_; bool boolean; Directive* directive_; } Array* array() { assert (FieldType.Array == type); return array_; } void array(Array a) { Array[] arr; if (array_ is null) arr.alloc(1); else arr = array_[0..1]; arr[0] = a; array_ = &arr[0]; } void array(Array* a) { array_ = a; } Directive* directive() { assert (FieldType.Directive == type); return directive_; } void directive(char[] name, Array param) { Directive[] dir; if (directive_ is null) dir.alloc(1); else dir = directive_[0..1]; dir[0].name = name; dir[0].param = param; directive_ = &dir[0]; } void directive(Directive* d) { directive_ = d; } FieldType type = FieldType.None; void destroy() { //printf("Field.destroy (%0.8x)\n", this); if (FieldType.String == type) { string.free(); } else if (FieldType.Binary == type) { binary.free(); } else if (FieldType.Array == type) { assert (array_ !is null); array_.destroy(); Array[] a = array_[0..1]; a.free(); array_ = null; } else if (FieldType.Directive == type) { assert (directive_ !is null); directive_.param.destroy(); Directive[] d = directive_[0..1]; d.free(); directive_ = null; } type = FieldType.None; //printf("<- Field.destroy\n"); } char[] toString() { switch (type) { case FieldType.String: return "str:'" ~ string ~ "'"; case FieldType.Number: return "nbr:" ~ .toString(number); case FieldType.Binary: return "binary:'" ~ .toString(binary.length); case FieldType.Array: return "array:{\n" ~ array.toString ~ "}"; case FieldType.Boolean: return (boolean ? "bool:true" : "bool:false"); case FieldType.Directive: return "directive:(" ~ directive.name ~ "\n" ~ directive.param.toString ~ ")"; case FieldType.None: return "notype"; default: assert (false); } } T get(T)() { static if (is(T == char[])) return string; static if (is(T == int)) return cast(int)number; static if (is(T == float)) return cast(float)number; static if (is(T == double)) return number; static if (is(T == bool)) return boolean; static if (is(T == Directive)) return *directive; } } struct FieldTuple { Field[] fields; uint numFields; void addField(Field f) { //fields ~= f; append(fields, f, &numFields); } void compress() { fields.realloc(numFields); foreach (inout f; fields) { if (FieldType.Array == f.type) { f.array.compress(); } else if (FieldType.Directive == f.type) { f.directive.param.compress(); } } } void destroy() { //printf("FieldTuple.destroy\n"); foreach (inout f; fields[0 .. numFields]) { f.destroy(); } fields.free(); numFields = 0; //printf("<- FieldTuple.destroy\n"); } char[] toString() { char[] result; foreach (i, inout f; fields[0 .. numFields]) { if (0 == i) result ~= f.toString(); else result ~= " " ~ f.toString(); } return result; } } /** An array of optionally named FieldTuple's. Names need not be unique, as they are indexable. */ struct Array { FieldTuple[] fields; char[][] fieldNames; uint numFields; uint numFieldNames; SortedAA!(char[], int[], false, mintl.mem.Malloc) namedFields; void addFieldTuple(inout FieldTuple ft, char[] name = null) { if (name !is null) { //namedFields[name] ~= fields.length; //append(namedFields[name], fields.length); int[]* nfields = namedFields.get(name); if (nfields is null) { int[] emptyArray; namedFields[name] = emptyArray; nfields = namedFields.get(name); assert (nfields !is null); } (*nfields).append(numFields); } //fields ~= ft; //fieldNames ~= name; append(fields, ft, &numFields); append(fieldNames, name, &numFieldNames); } void compress() { fields.realloc(numFields); fieldNames.realloc(numFieldNames); foreach (inout ft; fields) { ft.compress(); } } void destroy() { debug printf("Array.destroy\n"); foreach (inout n; fieldNames[0 .. numFieldNames]) { n.free(); } fieldNames.free(); numFieldNames = 0; foreach (inout int[] nf; namedFields) { nf.free(); } namedFields.clear(); foreach (inout f; fields[0 .. numFields]) { f.destroy(); } fields.free(); numFields = 0; debug printf("<- Array.destroy\n"); } /// char[] toString() { char[] result; foreach (i, f; fields[0 .. numFields]) { char[] data; if (fieldNames[i] !is null) { data ~= fieldNames[i] ~ ":"; } data ~= f.toString(); foreach (line; .splitlines(data)) { result ~= " " ~ line ~ "\n"; } } return result; } /** Number of fields of the specified name in the array */ int count(char[] name) { if (namedFields.get(name) is null) return 0; return namedFields[name].length; } /** Number of fields in the array */ int count() { return numFields; } /** Length of a named tuple Params: name = the name of the field tuple index = optional index of the tuple */ int tupleLength(char[] name, int index = 0) { return fields[namedFields[name][index]].numFields; } /** Length of i-th tuple */ int tupleLength(int index) { return fields[index].numFields; } /** Get a named child from a specific tuple Params: name = name of the tuple index = i-th tuple of this name tupleIndex = i-th item in the tuple */ Array child(char[] name, int index = 0, int tupleIndex = 0) { auto item = fields[namedFields[name][index]].fields[tupleIndex]; assert (FieldType.Array == item.type); return *item.array; } /** Get a child from a specific tuple Params: index = i-th tuple tupleIndex = i-th item in the tuple */ Array child(int index = 0, int tupleIndex = 0) { auto item = fields[index].fields[tupleIndex]; assert (FieldType.Array == item.type); return *item.array; } /** Check whether this Array contains a named child Params: name = name of the tuple index = i-th tuple of this name tupleIndex = i-th item in the tuple */ bool hasChild(char[] name, int index = 0, int tupleIndex = 0) { auto indices = namedFields.get(name); if (indices is null) return false; if (indices.length <= index) return false; auto tuple = fields[(*indices)[index]]; if (tuple.numFields <= tupleIndex) return false; auto item = tuple.fields[tupleIndex]; return FieldType.Array == item.type; } /** Check whether this Array contains a named field */ bool hasField(char[] name) { return count(name) > 0; } /** Get a named field from the Array Params: name = name of the tuple index = i-th tuple of this name tupleIndex = i-th item in the tuple */ T simpleFieldAccess(T)(char[] name, int index = 0, int tupleIndex = 0) { assert (namedFields.get(name) !is null); assert (namedFields[name].length > index); assert (fields[namedFields[name][index]].numFields > tupleIndex); auto item = fields[namedFields[name][index]].fields[tupleIndex]; assert (item.type == TypeToFieldType!(T), "'" ~ name ~ "' : " ~ .toString(cast(int)item.type) ~ " : "); return item.get!(T); } alias simpleFieldAccess!(char[]) string_; /// ditto alias simpleFieldAccess!(int) int_; /// ditto alias simpleFieldAccess!(float) float_; /// ditto alias simpleFieldAccess!(double) double_; /// ditto alias simpleFieldAccess!(bool) bool_; /// ditto alias simpleFieldAccess!(Directive) directive; /// ditto /// ditto vec2 vec2_(char[] name, int index = 0, int tupleIndex = 0) { return vec2(float_(name, index, tupleIndex), float_(name, index, tupleIndex+1)); } /// ditto vec3 vec3_(char[] name, int index = 0, int tupleIndex = 0) { return vec3(float_(name, index, tupleIndex), float_(name, index, tupleIndex+1), float_(name, index, tupleIndex+2)); } /// ditto vec4 vec4_(char[] name, int index = 0, int tupleIndex = 0) { return vec4(float_(name, index, tupleIndex), float_(name, index, tupleIndex+1), float_(name, index, tupleIndex+2), float_(name, index, tupleIndex+3)); } /// ditto vec2i vec2i_(char[] name, int index = 0, int tupleIndex = 0) { return vec2i(int_(name, index, tupleIndex), int_(name, index, tupleIndex+1)); } /// ditto vec3i vec3i_(char[] name, int index = 0, int tupleIndex = 0) { return vec3i(int_(name, index, tupleIndex), int_(name, index, tupleIndex+1), int_(name, index, tupleIndex+2)); } /// ditto vec4i vec4i_(char[] name, int index = 0, int tupleIndex = 0) { return vec4i(int_(name, index, tupleIndex), int_(name, index, tupleIndex+1), int_(name, index, tupleIndex+2), int_(name, index, tupleIndex+3)); } /** Get an array of fields from tuples Params: name = name of the tuple result = the place to put the result into. Must match exactly in size. tupleIndex = tuple offset stride = stride for the destination */ void simpleArrayAccess(T)(char[] name, T[] result, int tupleIndex = 0, int stride = 1) { int[] idxArray = namedFields[name]; assert (idxArray.length * stride == result.length); int dst = 0; foreach (idx; idxArray) { assert (fields[idx].fields[tupleIndex].type == TypeToFieldType!(T)); result[dst] = fields[idx].fields[tupleIndex].get!(T); dst += stride; } } alias simpleArrayAccess!(char[]) string_; /// ditto alias simpleArrayAccess!(int) int_; /// ditto alias simpleArrayAccess!(float) float_; /// ditto alias simpleArrayAccess!(double) double_; /// ditto alias simpleArrayAccess!(bool) bool_; /// ditto void vectorArrayAccess(V)(char[] name, V[] result, int tupleIndex = 0, int stride = 1) { for (int i = 0; i < V.dim; ++i) { simpleArrayAccess!(V.flt)(name, (cast(V.flt*)result.ptr + i)[0..result.length * V.dim], tupleIndex + i, i + stride * V.dim); } } alias vectorArrayAccess!(vec2) vec2_; /// ditto alias vectorArrayAccess!(vec3) vec3_; /// ditto alias vectorArrayAccess!(vec4) vec4_; /// ditto alias vectorArrayAccess!(vec2i) vec2i_; /// ditto alias vectorArrayAccess!(vec3i) vec3i_; /// ditto alias vectorArrayAccess!(vec4i) vec4i_; /// ditto /** Get a field from the Array Params: index = i-th tuple tupleIndex = i-th item in the tuple */ T simpleIndexedFieldAccess(T)(int index = 0, int tupleIndex = 0) { auto item = fields[index].fields[tupleIndex]; assert (TypeToFieldType!(T) == item.type); return item.get!(T); } alias simpleIndexedFieldAccess!(char[]) string_; /// ditto alias simpleIndexedFieldAccess!(int) int_; /// ditto alias simpleIndexedFieldAccess!(float) float_; /// ditto alias simpleIndexedFieldAccess!(double) double_; /// ditto alias simpleIndexedFieldAccess!(bool) bool_; /// ditto alias simpleIndexedFieldAccess!(Directive) directive; /// ditto /// ditto vec2 vec2_(int index = 0, int tupleIndex = 0) { return vec2(float_(index, tupleIndex), float_(index, tupleIndex+1)); } /// ditto vec3 vec3_(int index = 0, int tupleIndex = 0) { return vec3(float_(index, tupleIndex), float_(index, tupleIndex+1), float_(index, tupleIndex+2)); } /// ditto vec4 vec4_(int index = 0, int tupleIndex = 0) { return vec4(float_(index, tupleIndex), float_(index, tupleIndex+1), float_(index, tupleIndex+2), float_(index, tupleIndex+3)); } /// ditto vec2i vec2i_(int index = 0, int tupleIndex = 0) { return vec2i(int_(index, tupleIndex), int_(index, tupleIndex+1)); } /// ditto vec3i vec3i_(int index = 0, int tupleIndex = 0) { return vec3i(int_(index, tupleIndex), int_(index, tupleIndex+1), int_(index, tupleIndex+2)); } /// ditto vec4i vec4i_(int index = 0, int tupleIndex = 0) { return vec4i(int_(index, tupleIndex), int_(index, tupleIndex+1), int_(index, tupleIndex+2), int_(index, tupleIndex+3)); } /** Get an array of fields from tuples Params: result = the place to put the result into. Must match exactly in size. tupleIndex = tuple offset stride = stride for the destination */ void simpleIndexedArrayAccess(T)(T[] result, int tupleIndex = 0, int stride = 1) { assert (numFields * stride == result.length, .toString(numFields * stride) ~ ` == ` ~ .toString(result.length)); int dst = 0; foreach (idx, field; fields[0 .. numFields]) { assert (field.fields[tupleIndex].type == TypeToFieldType!(T)); result[dst] = field.fields[tupleIndex].get!(T); dst += stride; } } alias simpleIndexedArrayAccess!(char[]) string_; /// ditto alias simpleIndexedArrayAccess!(int) int_; /// ditto alias simpleIndexedArrayAccess!(float) float_; /// ditto alias simpleIndexedArrayAccess!(double) double_; /// ditto alias simpleIndexedArrayAccess!(bool) bool_; /// ditto void vectorIndexedArrayAccess(V)(V[] result, int tupleIndex = 0, int stride = 1) { for (int i = 0; i < V.dim; ++i) { simpleIndexedArrayAccess!(V.flt)((cast(V.flt*)result.ptr + i)[0..result.length * V.dim], tupleIndex + i, stride * V.dim); } } alias vectorIndexedArrayAccess!(vec2) vec2_; /// ditto alias vectorIndexedArrayAccess!(vec3) vec3_; /// ditto alias vectorIndexedArrayAccess!(vec4) vec4_; /// ditto alias vectorIndexedArrayAccess!(vec2i) vec2i_; /// ditto alias vectorIndexedArrayAccess!(vec3i) vec3i_; /// ditto alias vectorIndexedArrayAccess!(vec4i) vec4i_; /// ditto } /** Parses a simple, yet poweful config file format. Examples: --- pixelformat = { bits = 32 depth = 16 } xres = 800 yres = 600 title = 'Deadlock' fullscreen = false --- TODO: full file format specification */ class CfgLoader { bool latentDirectiveParsing = false; /** Load the specified file. Return this. */ CfgLoader load(char[] filename) { File srcFile = new File(filename); scope (exit) { srcFile.close(); delete srcFile; } getc = delegate char() { if (srcFile.eof) { _end = true; return char.init; } char x = '\r'; while ('\r' == x) { x = srcFile.getc(); } return x; }; parseArray(root, char.init, true); root.compress(); return this; } /** Parse the specified data buffer. Return this. */ CfgLoader loadFromMem(char[] data) { getc = delegate char() { if (data.length > 0) { char res = data[0]; data = data[1..$]; //writefln(res); return res; } else { _end = true; return char.init; } }; parseArray(root, char.init, true); root.compress(); return this; } alias void function(inout Array param, inout FieldTuple fieldTuple) DirectiveHandler; /** Add a handler function for a custom directive Params: dname = name of the directive handler = a function taking an Array of params and returning results into fieldTuple */ void addCustomDirective(char[] dname, DirectiveHandler handler) { customDirectives[dname] = handler; } /** The parsing results. Destroyed in CfgParser's dtor. Use dropResult() to prevent that. */ Array result() { return root; } /** Destroy all the data associated with this CfgLoader. May be useful to load another config. */ void destroy() { root.destroy(); root = root.init; discardWindow(0); _end = false; } /** This will cause the resulting data not to be lost during a call to destroy() or during dtor execution */ void dropResult() { root = root.init; } ~this() { destroy(); destroyWindowBuffer(); } protected: char delegate() getc; DirectiveHandler[char[]] customDirectives; void parseArray(inout Array array, char blockEnd, bool topLevel = false) { while (true) { //writefln("white2<"); skipWhite(); //writefln(">white2"); if (inputEnd) { if (!topLevel) { inputEndError(); } else { return; } } char firstChar = window(0); if ('\n' == firstChar || ',' == firstChar) { discardWindow(1); } else if (blockEnd == firstChar) { discardWindow(1); return; } else if (validNameChar(firstChar, true) && !curIdent("true") && !curIdent("false")) { // a named value char[] name = parseName(); skipWhite(); //printf("name: %.*s\n", name); if (window(0) == '=') { discardWindow(1); skipWhite(); FieldTuple f; parseValue(f); array.addFieldTuple(f, name); } else { // no value, just a name FieldTuple f; array.addFieldTuple(f, name); } } else { //writefln("noname field"); FieldTuple f; parseValue(f); array.addFieldTuple(f, null); } } assert (topLevel); } bool curIdent(char[] str) { foreach (i, c; str) { if (window(i) != c || inputEnd) return false; } return !validNameChar(window(str.length), false); } void parseValue(inout FieldTuple fieldTuple) { while (!inputEnd) { char firstChar = window(0); if ('-' == firstChar || '.' == firstChar || (firstChar >= '0' && firstChar <= '9')) { //writefln("parsing a nbr"); int lim = 1; while (!inputEnd && validNumberChar(window(lim))) { ++lim; } Field field; field.number = toDouble(window(0, lim)); field.type = FieldType.Number; fieldTuple.addField(field); //writefln("got a nbr: ", field.number); discardWindow(-1); } else if ('(' == firstChar) { discardWindow(1); char[] directiveName = parseName(); Array param; parseArray(param, ')'); processDirective(directiveName, param, fieldTuple); directiveName.free(); param.destroy(); } else if ('{' == firstChar) { discardWindow(1); Array param; parseArray(param, '}'); processDirective(`array`, param, fieldTuple); param.destroy(); } else if ('\'' == firstChar) { // string Field field; field.type = FieldType.String; uint stringLen; discardWindow(1); void addChar(char c) { /+field.string.realloc(field.string.length + 1); field.string[$-1] = c;+/ field.string.append(c, &stringLen); } bool escaped = false; while (true) { if ('\\' == window(0)) { discardWindow(1); if (escaped) { addChar('\\'); } escaped = !escaped; } else if ('\'' == window(0)) { discardWindow(1); if (escaped) { escaped = false; addChar('\''); } else { break; } } else { if (escaped) { addChar('\\'); escaped = false; } addChar(window(0)); discardWindow(1); } } field.string.realloc(stringLen); fieldTuple.addField(field); //printf("got a str: '%.*s'\n", field.string); } else if (curIdent("true")) { Field field; field.boolean = true; field.type = FieldType.Boolean; fieldTuple.addField(field); discardWindow(4); } else if (curIdent("false")) { Field field; field.boolean = false; field.type = FieldType.Boolean; fieldTuple.addField(field); discardWindow(5); } else if ('}' == firstChar) { // end of data directive return; } else if (')' == firstChar) { // end of directive != data return; } else if (',' == firstChar || '\n' == firstChar) { // next array item return; } else //writefln("white<"); skipWhite(); //writefln(">white"); } } void processDirective(char[] directiveName, inout Array param, inout FieldTuple fieldTuple) { switch (directiveName) { case `array`: { Field f; f.type = FieldType.Array; f.array = param; fieldTuple.addField(f); param = Array.init; // do not let destroy() be called on it } break; case `include`: { CfgLoader ldr = new CfgLoader; ldr.latentDirectiveParsing = this.latentDirectiveParsing; assert (param.numFields == 1); assert (param.fields[0].numFields == 1); assert (FieldType.String == param.fields[0].fields[0].type); //writefln("include<"); ldr.load(param.fields[0].fields[0].string); //writefln(">include"); Field f; f.type = FieldType.Array; f.array = ldr.result; fieldTuple.addField(f); ldr.root = Array.init; } break; case `flatten`: { foreach (inout tuple; param.fields[0 .. param.numFields]) { foreach (inout field; tuple.fields[0 .. tuple.numFields]) { flattenAndErase(field, fieldTuple); field = Field.init; } } } break; default: { if (directiveName in customDirectives) { customDirectives[directiveName](param, fieldTuple); return; } if (latentDirectiveParsing) { Field f; f.type = FieldType.Directive; f.directive(directiveName, param); fieldTuple.addField(f); param = Array.init; // do not let destroy() be called on it return; } assert (false, directiveName); } } } static void flattenAndErase(Field f, inout FieldTuple fieldTuple) { if (FieldType.Array != f.type) { fieldTuple.addField(f); } else { foreach (inout tuple; f.array.fields[0 .. f.array.numFields]) { foreach (inout field; tuple.fields[0 .. tuple.numFields]) { flattenAndErase(field, fieldTuple); field = Field.init; } } } } void skipWhite() { void skipLine() { while (!inputEnd) { if (window(0) == '\n') { return; } discardWindow(1); } } void skipBlock() { int depth = 1; while (!inputEnd) { if (window(0) == '+') { if (window(1) == '/') { discardWindow(2); --depth; if (0 == depth) return; else continue; } } else if (window(0) == '/') { if (window(1) == '+') { discardWindow(2); ++depth; continue; } } discardWindow(1); } } while (!inputEnd) { if (window(0) == ' ' || window(0) == '\t') { discardWindow(1); continue; } if (window(0) == '/') { if (window(1) == '/') { return skipLine(); } if (window(1) == '+') { discardWindow(2); skipBlock(); continue; } } break; } } bool inputEnd() { return _end; } char[] parseName() { char[] res; uint nameLen = 0; for (int i = 0; true; ++i) { char c = window(i); if (inputEnd) break; if (validNameChar(c, 0 == i)) { append(res, c, &nameLen); } else { break; } } assert (nameLen > 0); assert (res !is null); debug printf("realloc from %d to %d, ptr=%0.8x\n", res.length, nameLen, res.ptr); res.realloc(nameLen); discardWindow(-1); return res; } bool validNameChar(char c, bool first = false) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (!first && c >= '0' && c <= '9')|| c == '_'; } bool validNumberChar(char c, bool first = false) { return (c >= '0' && c <= '9') || c == '.' || c == '-' || (!first && (c == '_' || c == 'e' || c == '+')); } void inputEndError() { assert (false); } /** Params: offset = offset for window.length */ void discardWindow(int offset) { assert (_windowBuffer.ptr + _windowStart == _window.ptr); if (offset > 0) { _windowStart += offset; _window = _window[offset .. $]; } else { _windowStart += _window.length + offset; _window = _window[$+offset .. $]; } if (0 == _window.length) { _window = _windowBuffer[0..0]; _windowStart = 0; } else { assert (_windowBuffer.ptr + _windowStart == _window.ptr); } } void destroyWindowBuffer() { _windowBuffer.free(); _window = null; } char window(int idx) { // make sure we have this char // track the lines and character indices of beginning and end of window assert (idx >= 0); if (_windowBuffer.length <= idx + _windowStart) { _windowBuffer.realloc(idx + _windowStart + 1); } if (_window.length <= idx) { int fillFrom = _window.length; _window = _windowBuffer[_windowStart .. _windowStart + idx + 1]; foreach (inout c; _window[fillFrom .. $]) { c = getc(); } } return _window[idx]; } char[] window(int start, int postEnd) { return _window[start .. postEnd]; } Array root; char[] _window; int _windowStart; bool _end = false; char[] _windowBuffer; }