support for SDL file format in addition to JSON - for dlangide dub.sdl support

This commit is contained in:
Vadim Lopatin 2016-10-20 12:28:01 +03:00
parent 391a9d8b38
commit c369ee182f
1 changed files with 386 additions and 8 deletions

View File

@ -52,12 +52,19 @@ enum SettingType {
NULL NULL
} }
/// settings file format
enum SettingsFileFormat {
JSON,
SDL,
}
/// Settings object whith file information /// Settings object whith file information
class SettingsFile { class SettingsFile {
protected Setting _setting; protected Setting _setting;
protected string _filename; protected string _filename;
protected SysTime _lastModificationTime; protected SysTime _lastModificationTime;
protected bool _loaded; protected bool _loaded;
protected SettingsFileFormat _format = SettingsFileFormat.JSON;
@property Setting setting() { return _setting; } @property Setting setting() { return _setting; }
@property Setting copySettings() { @property Setting copySettings() {
@ -1576,9 +1583,11 @@ final class Setting {
private static struct JsonParser { private static struct JsonParser {
string json; string json;
int pos; int pos;
void initialize(string s) { bool allowEol; // for SDL parsing where EOLs are meaningful
void initialize(string s, bool allowEol) {
json = s; json = s;
pos = 0; pos = 0;
this.allowEol = allowEol;
} }
/// returns current char /// returns current char
@property char peek() { @property char peek() {
@ -1641,10 +1650,42 @@ final class Setting {
static import std.ascii; static import std.ascii;
return std.ascii.isAlphaNum(ch) || ch == '_'; return std.ascii.isAlphaNum(ch) || ch == '_';
} }
/// skip spaces and comments, return next available character
@property char skipSpaces() { @property char skipSpaces() {
static import std.ascii; static import std.ascii;
for(;pos < json.length;pos++) { for(;pos < json.length;pos++) {
char ch = json[pos]; char ch = json[pos];
char nextch = pos + 1 < json.length ? json[pos + 1] : 0;
if (allowEol && ch == '\n')
break;
if (ch == '#' || (ch == '/' && nextch == '/') || (ch == '-' && nextch == '-')) {
// skip one line comment // or # or --
pos++;
for(;pos < json.length;pos++) {
ch = json[pos];
if (ch == '\n')
break;
}
if (allowEol && ch == '\n')
break;
continue;
} else if (ch == '/' && nextch == '*') {
// skip multiline /* */ comment
pos += 2;
for(;pos < json.length;pos++) {
ch = json[pos];
nextch = pos + 1 < json.length ? json[pos + 1] : 0;
if (ch == '*' && nextch == '/') {
pos += 2;
break;
}
}
continue;
} else if (ch == '\\' && nextch == '\n') {
// continue to next line
pos += 2;
continue;
}
if (!std.ascii.isWhite(ch)) if (!std.ascii.isWhite(ch))
break; break;
} }
@ -1669,17 +1710,18 @@ final class Setting {
@property string parseString() { @property string parseString() {
char[] res; char[] res;
char ch = peek; char ch = peek;
if (ch != '\"') char quoteChar = ch;
if (ch != '\"' && ch != '`')
error("cannot parse string"); error("cannot parse string");
for (;;) { for (;;) {
ch = nextChar; ch = nextChar;
if (!ch) if (!ch)
error("unexpected end of file while parsing string"); error("unexpected end of file while parsing string");
if (ch == '\"') { if (ch == quoteChar) {
nextChar; nextChar;
return cast(string)res; return cast(string)res;
} }
if (ch == '\\') { if (ch == '\\' && quoteChar != '`') {
// escape sequence // escape sequence
ch = nextChar; ch = nextChar;
switch (ch) { switch (ch) {
@ -1718,7 +1760,7 @@ final class Setting {
} }
@property string parseIdent() { @property string parseIdent() {
char ch = peek; char ch = peek;
if (ch == '\"') { if (ch == '\"' || ch == '`') {
return parseString; return parseString;
} }
char[] res; char[] res;
@ -1733,7 +1775,7 @@ final class Setting {
} }
} }
} else } else
error("cannot parse string"); error("cannot parse ident");
return cast(string)res; return cast(string)res;
} }
bool parseKeyword(string ident) { bool parseKeyword(string ident) {
@ -1898,13 +1940,349 @@ final class Setting {
void parseJSON(string s) { void parseJSON(string s) {
clear(SettingType.NULL); clear(SettingType.NULL);
JsonParser parser; JsonParser parser;
parser.initialize(s); parser.initialize(convertEols(s), false);
parseJSON(parser); parseJSON(parser);
} }
/// SDL identifiers to be converted to JSON array (name should be changed, with 's' suffix)
private static immutable (string[]) identsToConvertToArrays = [
"subPackage", // in JSON it's subPackages
"configuration", // in JSON it's configurations
"buildType", // in JSON it's buildTypes
];
/// SDL identifiers to be converted to JSON object (name should be changed, with 's' suffix)
private static immutable (string[]) identsToConvertToObjects = [
"dependency", // in JSON it's dependencies
"subConfiguration", // in JSON it's subConfigurations
];
/// SDL identifiers of JSON array w/o name conversion
private static immutable (string[]) arrayIdents = [
"authors",
"x:ddoxFilterArgs",
"sourcePaths",
"importPaths",
"buildOptions",
"libs",
"sourceFiles",
"buildRequirements",
"excludedSourceFiles",
"copyFiles",
"versions",
"debugVersions",
"stringImportPaths",
"preGenerateCommands",
"postGenerateCommands",
"preBuildCommands",
"postBuildCommands",
"dflags",
"lflags",
"platforms",
];
protected bool isArrayItemNameIdent(string ident) {
foreach(s; identsToConvertToArrays) {
if (ident == s)
return true;
}
return false;
}
protected bool isObjectItemNameIdent(string ident) {
foreach(s; identsToConvertToObjects) {
if (ident == s)
return true;
}
return false;
}
protected bool isArrayIdent(string ident) {
foreach(s; arrayIdents) {
if (ident == s)
return true;
}
return false;
}
private void skipEol(ref JsonParser parser) {
char ch = parser.skipSpaces;
if (ch == 0)
return;
if (ch == '\n') {
parser.nextChar;
return;
}
parser.error("end of line expected");
}
private void parseSDLAttributes(ref JsonParser parser, bool ignorePlatformAttribute = true) {
string attrName;
Setting attrValue;
for (;;) {
char ch = parser.skipSpaces;
if (ch == 0)
return;
if (ch == '\n') {
parser.nextChar;
return;
}
if (!JsonParser.isAlpha(ch))
parser.error("attr=value expected");
attrName = parser.parseIdent();
attrValue = new Setting();
ch = parser.skipSpaces;
if (ch != '=')
parser.error("= expected after " ~ attrName);
ch = parser.nextChar; // skip '='
ch = parser.skipSpaces;
if (ch == '\"' || ch == '`') {
// string value
string v = parser.parseString;
attrValue = v;
if (!ignorePlatformAttribute || attrName != "platform")
this[attrName] = attrValue;
continue;
}
if (JsonParser.isAlpha(ch)) {
string v = parser.parseIdent;
if (v == "true" || v == "on") {
attrValue = true;
this[attrName] = attrValue;
continue;
}
if (v == "false" || v == "off") {
attrValue = false;
this[attrName] = attrValue;
continue;
}
parser.error("unexpected attribue value " ~ v);
}
parser.error("only string and boolean values supported for SDL attributes now");
}
}
// peek platform="value" from current line
private string peekSDLPlatformAttribute(ref JsonParser parser) {
string res = null;
int oldpos = parser.pos; // save position
for(;;) {
char ch = parser.skipSpaces;
if (ch == 0 || ch == '\n' || ch == '{' || ch == '}')
break;
if (parser.isAlpha(ch)) {
string ident = parser.parseIdent;
ch = parser.skipSpaces;
if (ch != '=')
continue;
parser.nextChar;
ch = parser.skipSpaces;
string attrvalue;
if (ch == '\"' || ch == '`')
attrvalue = parser.parseString;
else if (parser.isAlpha(ch))
attrvalue = parser.parseIdent;
if (ident == "platform") {
res = attrvalue;
break;
}
} else if (ch == '\"' || ch == '`') {
string str = parser.parseString;
} else if (ch == '=') {
parser.nextChar;
continue;
}
}
parser.pos = oldpos; // restore position
return res;
}
private void skipPlatformAttribute(ref JsonParser parser) {
char ch = parser.skipSpaces;
int oldpos = parser.pos;
if (parser.isAlpha(ch)) {
string attrName = parser.parseIdent;
if (attrName == "platform") {
ch = parser.skipSpaces;
if (ch == '=') {
parser.nextChar;
ch = parser.skipSpaces;
string value = parser.parseString;
return; // skipped platform attribute
}
}
}
// no changes
parser.pos = oldpos;
}
private Setting parseSDL(ref JsonParser parser, bool insideCurly = false) {
//static import std.ascii;
for (;;) {
// looking for ident
char ch = parser.skipSpaces;
if (ch == 0)
break;
if (ch == '\n') {
parser.nextChar; // skip
continue;
}
if (ch == '}') {
if (!insideCurly)
parser.error("unexpected }");
parser.nextChar; // skip
return this;
}
string ident = parser.parseIdent();
if (!ident.length)
parser.error("identifier expected");
ch = parser.skipSpaces;
string platform = peekSDLPlatformAttribute(parser);
bool isArrayConvName = isArrayItemNameIdent(ident);
bool isObjectConvName= isObjectItemNameIdent(ident);
bool isArrayName = isArrayIdent(ident) || isArrayConvName;
if (isArrayConvName || isObjectConvName) {
import std.algorithm : endsWith;
if (ident.endsWith("y"))
ident = ident[0 .. $-1] ~ "ies"; // e.g. dependency->dependencies
else if (!ident.endsWith("s"))
ident = ident ~ "s"; // a.g. author->authors
}
if (platform.length)
ident = ident ~ "-" ~ platform;
Setting valueObj = this[ident]; // looking for existing object
if (!valueObj) { // create if not exist
valueObj = new Setting();
this[ident] = valueObj;
}
if (isArrayName) {
if (!valueObj.isArray) {
// convert to array
valueObj.clear(SettingType.ARRAY);
}
}
// now we have identifier
if (ch == '\"' || ch == '`') {
string value = parser.parseString;
skipPlatformAttribute(parser);
ch = parser.skipSpaces;
if (ch == '{') {
/*
ident "name" {
//...
}
*/
parser.nextChar; // skip {
Setting obj = isArrayName ? new Setting() : valueObj;
obj["name"] = value;
obj.parseSDL(parser, true);
if (isArrayName)
valueObj.array = valueObj.array ~ obj;
continue;
}
if (JsonParser.isAlpha(ch)) {
// ident=value pairs after "name"
Setting obj = (isArrayName || isObjectConvName) ? new Setting() : valueObj;
if (!isObjectConvName)
obj["name"] = value;
obj.parseSDLAttributes(parser);
if (isArrayName)
valueObj.array = valueObj.array ~ obj;
else if (isObjectConvName)
valueObj[value] = obj;
continue;
}
if (isArrayName) {
Setting[] values = valueObj.array;
Setting svalue = new Setting();
svalue = value;
values ~= svalue;
for (;;) {
skipPlatformAttribute(parser);
ch = parser.skipSpaces;
if (ch == '\n' || ch == 0)
break;
if (ch == '\"' || ch == '`') {
value = parser.parseString;
svalue = new Setting();
svalue = value;
values ~= svalue;
} else
parser.error("array of strings expected");
}
valueObj.array = values;
} else {
if (isObjectConvName) {
string svalue = parser.parseString;
valueObj[value] = svalue;
} else {
valueObj = value;
}
}
skipPlatformAttribute(parser);
skipEol(parser);
continue;
} else if (ch == '{') {
// object
parser.nextChar; // skip {
if (isArrayName) {
Setting[] values = valueObj.array;
Setting item = new Setting();
item.clear(SettingType.OBJECT);
item.parseSDL(parser, true);
values ~= item;
valueObj.array = values;
} else {
valueObj.parseSDL(parser, true);
}
continue;
} else {
parser.error("cannot parse SDL value");
}
}
if (insideCurly)
parser.error("} expected");
return this;
}
void parseSDL(string s) {
clear(SettingType.NULL);
JsonParser parser;
parser.initialize(convertEols(s), true);
parseSDL(parser);
}
/// convert CR LF, LF CR, LF, CR to '\n' eol format
static string convertEols(string src) {
char[] res;
res.assumeSafeAppend;
for (int i = 0; i < src.length; i++) {
char ch = src[i];
if (ch == '\r' || ch == '\n') {
char nextch = i + 1 < src.length ? src[i + 1] : 0;
if (nextch != ch && (ch == '\r' || ch == '\n')) {
// pair \r\n or \n\r
res ~= '\n';
i++;
} else {
// single \r or \n
res ~= '\n';
}
} else {
res ~= ch;
}
}
return res.dup;
}
/// load from file; autodetect SDL format using ".sdl" and ".SDL" extension mask; returns true if loaded successfully
bool load(string filename) { bool load(string filename) {
try { try {
import std.algorithm : endsWith;
string s = readText(filename); string s = readText(filename);
if (filename.endsWith(".sdl") || filename.endsWith(".SDL"))
parseSDL(s);
else
parseJSON(s); parseJSON(s);
return true; return true;
} catch (Exception e) { } catch (Exception e) {