improvements in loading from DML - issue #58

This commit is contained in:
Vadim Lopatin 2015-04-01 15:14:00 +03:00
parent bec083326b
commit b62334e5c7
3 changed files with 412 additions and 26 deletions

View File

@ -445,14 +445,28 @@ extern (C) int UIAppMain(string[] args) {
layout.addChild((new TextWidget(null, "Text widget3 with very long text"d)).textColor(0x004000));
layout.addChild(new VSpacer()); // vertical spacer to fill extra space
/*
import dlangui.core.parser;
Widget w = parseML(q{
TextWidget {
VerticalLayout {
id: vlayout
margins: Rect { left: 5; right: 3; top: 2; bottom: 4 }
padding: Rect { 5, 4, 3, 2 } // same as Rect { left: 5; top: 4; right: 3; bottom: 2 }
TextWidget {
/* this widget can be accessed via id myLabel1
e.g. w.childById!TextWidget("myLabel1")
*/
id: myLabel1
text: "Some text"; padding: 5
}
TextWidget {
id: myLabel2
text: SOME_TEXT_RESOURCE_ID; margins: 5
}
}
});
Log.d("id=", w.id);
*/
Log.d("id=", w.id, " text=", w.text, " padding=", w.padding, " margins=", w.margins, " lbl1=", w.childById!TextWidget("myLabel1").text, " lbl2=", w.childById!TextWidget("myLabel2").text);
destroy(w);
layout.childById("BTN1").onClickListener = (delegate (Widget w) { Log.d("onClick ", w.id); return true; });
layout.childById("BTN2").onClickListener = (delegate (Widget w) { Log.d("onClick ", w.id); return true; });

View File

@ -1,9 +1,13 @@
module dlangui.core.parser;
import dlangui.core.linestream;
import dlangui.core.collections;
import dlangui.core.types;
import dlangui.widgets.widget;
import dlangui.widgets.metadata;
import std.conv : to;
import std.algorithm : equal, min, max;
import std.utf : toUTF32, toUTF8;
class ParserException : Exception {
protected string _msg;
@ -54,6 +58,10 @@ enum TokenType : ushort {
semicolon,
/// , operator
comma,
/// - operator
minus,
/// + operator
plus,
/// [
curlyOpen,
/// ]
@ -191,7 +199,7 @@ class Tokenizer {
private char[] _stringbuf;
protected ref const(Token) parseString() {
_token.type = TokenType.str;
skipChar(); // skip "
//skipChar(); // skip "
bool lastBackslash = false;
_stringbuf.length = 0;
for(;;) {
@ -258,6 +266,19 @@ class Tokenizer {
div++;
}
_token.floatvalue = cast(double)n + (div > 0 ? cast(double)n2 / div : 0.0);
string suffix;
if (ch == '%') {
suffix ~= ch;
ch = skipChar();
} else {
while (ch >= 'a' && ch <= 'z') {
suffix ~= ch;
ch = skipChar();
}
}
if (isAlphaNum(ch) || ch == '.')
return parseError();
_token.text = suffix;
return _token;
}
@ -272,8 +293,21 @@ class Tokenizer {
}
if (ch == '.')
return parseFloating(n);
string suffix;
if (ch == '%') {
suffix ~= ch;
ch = skipChar();
} else {
while (ch >= 'a' && ch <= 'z') {
suffix ~= ch;
ch = skipChar();
}
}
if (isAlphaNum(ch) || ch == '.')
return parseError();
_token.type = TokenType.integer;
_token.intvalue = n;
_token.text = suffix;
return _token;
}
@ -346,6 +380,8 @@ class Tokenizer {
case ':': return parseOp(TokenType.colon);
case ';': return parseOp(TokenType.semicolon);
case ',': return parseOp(TokenType.comma);
case '-': return parseOp(TokenType.minus);
case '+': return parseOp(TokenType.plus);
case '{': return parseOp(TokenType.curlyOpen);
case '}': return parseOp(TokenType.curlyClose);
case '(': return parseOp(TokenType.open);
@ -357,8 +393,17 @@ class Tokenizer {
}
}
string getContextSource() {
string s = toUTF8(_lineText);
if (_pos == 0)
return " near `^^^" ~ s[0..min($,30)] ~ "`";
if (_pos >= _len)
return " near `" ~ s[max(_len - 30, 0) .. $] ~ "^^^`";
return " near `" ~ s[max(_pos - 15, 0) .. _pos] ~ "^^^" ~ s[_pos .. min(_pos + 15, $)] ~ "`";
}
void emitError(string msg) {
throw new ParserException(msg, _filename, _token.line, _token.pos);
throw new ParserException(msg ~ getContextSource(), _filename, _token.line, _token.pos);
}
void emitError(string msg, ref const Token token) {
@ -369,8 +414,11 @@ class Tokenizer {
class MLParser {
protected string _code;
protected string _filename;
protected bool _ownContext;
protected Widget _context;
protected Widget _currentWidget;
protected Tokenizer _tokenizer;
protected Collection!Widget _treeStack;
protected this(string code, string filename = "", Widget context = null) {
_code = code;
@ -381,30 +429,52 @@ class MLParser {
protected Token _token;
/// move to next token
protected void nextToken() {
_token = _tokenizer.nextToken();
Log.d("parsed token: ", _token.type, " ", _token.line, ":", _token.pos, " ", _token.text);
}
/// throw exception if current token is eof
protected void checkNoEof() {
if (_token.type == TokenType.eof)
error("unexpected end of file");
}
/// move to next token, throw exception if eof
protected void nextTokenNoEof() {
nextToken();
checkNoEof();
}
protected void skipWhitespaceAndEols() {
for (;;) {
nextToken();
if (_token.type != TokenType.eol && _token.type != TokenType.whitespace && _token.type != TokenType.comment)
break;
nextToken();
}
if (_token.type == TokenType.error)
_tokenizer.emitError("error while parsing ML code");
error("error while parsing ML code");
}
protected void skipWhitespaceAndEolsNoEof() {
skipWhitespaceAndEols();
checkNoEof();
}
protected void skipWhitespaceNoEof() {
skipWhitespace();
checkNoEof();
}
protected void skipWhitespace() {
for (;;) {
nextToken();
if (_token.type != TokenType.whitespace && _token.type != TokenType.comment)
break;
nextToken();
}
if (_token.type == TokenType.error)
_tokenizer.emitError("error while parsing ML code");
error("error while parsing ML code");
}
protected void error(string msg) {
@ -422,25 +492,234 @@ class MLParser {
if (_context)
error("Context widget is already specified, but identifier " ~ name ~ " is found");
_context = createWidget(name);
_ownContext = true;
}
protected int applySuffix(int value, string suffix) {
if (suffix.length > 0) {
if (suffix.equal("px")) {
// do nothing, value is in px by default
} else if (suffix.equal("pt")) {
value = makePointSize(value);
} else if (suffix.equal("%")) {
value = makePercentSize(value);
} else
error("unknown number suffix: " ~ suffix);
}
return value;
}
protected void setIntProperty(string propName, int value, string suffix = null) {
value = applySuffix(value, suffix);
if (!_currentWidget.setProperty(propName, value))
error("unknown int property " ~ propName);
}
protected void setFloatProperty(string propName, double value) {
if (!_currentWidget.setProperty(propName, value))
error("unknown double property " ~ propName);
}
protected void setRectProperty(string propName, Rect value) {
if (!_currentWidget.setProperty(propName, value))
error("unknown Rect property " ~ propName);
}
protected void setStringProperty(string propName, string value) {
if (propName.equal("id")) {
if (!_currentWidget.setProperty(propName, value))
error("cannot set id property for widget");
return;
}
dstring v = toUTF32(value);
if (!_currentWidget.setProperty(propName, v))
error("unknown string property " ~ propName);
}
protected void setIdentProperty(string propName, string value) {
if (propName.equal("id")) {
if (!_currentWidget.setProperty(propName, value))
error("cannot set id property for widget");
return;
}
if (value.equal("FILL") || value.equal("FILL_PARENT"))
setIntProperty(propName, FILL_PARENT);
else if (value.equal("WRAP") || value.equal("WRAP_CONTENT"))
setIntProperty(propName, WRAP_CONTENT);
else if (!_currentWidget.setProperty(propName, value))
error("unknown ident property " ~ propName);
}
protected void parseRectProperty(string propName) {
// current token is Rect
int[4] values = [0, 0, 0, 0];
nextToken();
skipWhitespaceAndEolsNoEof();
if (_token.type != TokenType.curlyOpen)
error("{ expected after Rect");
nextToken();
skipWhitespaceAndEolsNoEof();
int index = 0;
for (;;) {
if (_token.type == TokenType.curlyClose)
break;
if (_token.type == TokenType.integer) {
if (index >= 4)
error("too many values in Rect");
int n = applySuffix(_token.intvalue, _token.text);
values[index++] = n;
nextToken();
skipWhitespaceAndEolsNoEof();
if (_token.type == TokenType.comma || _token.type == TokenType.semicolon) {
nextToken();
skipWhitespaceAndEolsNoEof();
}
} else if (_token.type == TokenType.ident) {
string name = _token.text;
nextToken();
skipWhitespaceAndEolsNoEof();
if (_token.type != TokenType.colon)
error(": expected after property name " ~ name ~ " in Rect definition");
nextToken();
skipWhitespaceNoEof();
if (_token.type != TokenType.integer)
error("integer expected as Rect property value");
int n = applySuffix(_token.intvalue, _token.text);
if (name.equal("left"))
values[0] = n;
else if (name.equal("top"))
values[1] = n;
else if (name.equal("right"))
values[2] = n;
else if (name.equal("bottom"))
values[3] = n;
else
error("unknown property " ~ name ~ " in Rect");
nextToken();
skipWhitespaceNoEof();
if (_token.type == TokenType.comma || _token.type == TokenType.semicolon) {
nextToken();
skipWhitespaceAndEolsNoEof();
}
} else {
error("invalid Rect definition");
}
}
setRectProperty(propName, Rect(values[0], values[1], values[2], values[3]));
}
protected void parseProperty() {
if (_token.type != TokenType.ident)
error("identifier expected");
string propName = _token.text;
nextToken();
skipWhitespaceNoEof();
if (_token.type == TokenType.colon) { // :
nextTokenNoEof(); // skip :
skipWhitespaceNoEof();
if (_token.type == TokenType.integer)
setIntProperty(propName, _token.intvalue, _token.text);
else if (_token.type == TokenType.minus || _token.type == TokenType.plus) {
int sign = _token.type == TokenType.minus ? -1 : 1;
nextTokenNoEof(); // skip :
skipWhitespaceNoEof();
if (_token.type == TokenType.integer) {
setIntProperty(propName, _token.intvalue * sign, _token.text);
} else if (_token.type == TokenType.floating) {
setFloatProperty(propName, _token.floatvalue * sign);
} else
error("number expected after + and -");
} else if (_token.type == TokenType.floating)
setFloatProperty(propName, _token.floatvalue);
else if (_token.type == TokenType.str)
setStringProperty(propName, _token.text);
else if (_token.type == TokenType.ident) {
if (_token.text.equal("Rect")) {
parseRectProperty(propName);
} else {
setIdentProperty(propName, _token.text);
}
} else
error("int, float, string or identifier are expected as property value");
nextTokenNoEof();
skipWhitespaceNoEof();
if (_token.type == TokenType.semicolon) {
// separated by ;
nextTokenNoEof();
skipWhitespaceAndEolsNoEof();
return;
} else if (_token.type == TokenType.eol) {
nextTokenNoEof();
skipWhitespaceAndEolsNoEof();
return;
} else if (_token.type == TokenType.curlyClose) {
// it was last property in object
return;
}
error("; eol or } expected after property definition");
} else if (_token.type == TokenType.curlyOpen) { // { -- start of object
Widget s = createWidget(propName);
parseWidgetProperties(s);
} else {
error(": or { expected after identifier");
}
}
protected void parseWidgetProperties(Widget w) {
if (_token.type != TokenType.curlyOpen) // {
error("{ is expected");
_treeStack.pushBack(w);
if (_currentWidget)
_currentWidget.addChild(w);
_currentWidget = w;
nextToken(); // skip {
skipWhitespaceAndEols();
for (;;) {
checkNoEof();
if (_token.type == TokenType.curlyClose) // end of object's internals
break;
parseProperty();
}
if (_token.type != TokenType.curlyClose) // {
error("{ is expected");
nextToken(); // skip }
skipWhitespaceAndEols();
_treeStack.popBack();
_currentWidget = _treeStack.peekBack();
}
protected Widget parse() {
skipWhitespaceAndEols();
if (_token.type == TokenType.ident) {
createContext(_token.text);
try {
nextToken();
skipWhitespaceAndEols();
if (_token.type == TokenType.ident) {
createContext(_token.text);
nextToken();
skipWhitespaceAndEols();
}
if (_token.type != TokenType.curlyOpen) // {
error("{ is expected");
if (!_context)
error("No context widget is specified!");
parseWidgetProperties(_context);
skipWhitespaceAndEols();
if (_token.type != TokenType.eof) // {
error("end of file expected");
return _context;
} catch (Exception e) {
Log.e("exception while parsing ML", e);
if (_context && _ownContext)
destroy(_context);
_context = null;
throw e;
}
if (_token.type != TokenType.curlyOpen) // {
_tokenizer.emitError("{ is expected");
if (!_context)
_tokenizer.emitError("No context widget is specified!");
skipWhitespaceAndEols();
if (_token.type != TokenType.curlyClose) // {
_tokenizer.emitError("} is expected");
skipWhitespaceAndEols();
if (_token.type != TokenType.eof) // {
_tokenizer.emitError("end of file expected");
return _context;
}
~this() {

View File

@ -1408,9 +1408,43 @@ class Widget {
}
} else {
// search only across children of this widget
for (int i = childCount - 1; i >= 0; i--)
for (int i = childCount - 1; i >= 0; i--) {
if (id.equal(child(i).id))
return child(i);
}
}
// not found
return null;
}
/// find child of specified type T by id, returns null if not found or cannot be converted to type T
T childById(T)(string id, bool deepSearch = true) {
if (deepSearch) {
// search everywhere inside child tree
if (compareId(id)) {
T found = cast(T)this;
if (found)
return found;
}
// lookup children
for (int i = childCount - 1; i >= 0; i--) {
Widget res = child(i).childById(id);
if (res !is null) {
T found = cast(T)res;
if (found)
return found;
}
}
} else {
// search only across children of this widget
for (int i = childCount - 1; i >= 0; i--) {
Widget w = child(i);
if (id.equal(w.id)) {
T found = cast(T)w;
if (found)
return found;
}
}
}
// not found
return null;
@ -1441,6 +1475,19 @@ class Widget {
/// set string property value, for ML loaders
bool setProperty(string name, string value) {
if (name.equal("id")) {
id = value;
return true;
}
if (name.equal("text")) {
text = UIString(value);
return true;
}
return false;
}
/// set string property value, for ML loaders
bool setProperty(string name, dstring value) {
if (name.equal("text")) {
text = UIString(value);
return true;
@ -1457,8 +1504,46 @@ class Widget {
return false;
}
/// set string property value, for ML loaders
bool setProperty(string name, bool value) {
if (name.equal("enabled")) {
enabled = value;
return true;
}
if (name.equal("clickable")) {
clickable = value;
return true;
}
if (name.equal("checkable")) {
checkable = value;
return true;
}
if (name.equal("checked")) {
checked = value;
return true;
}
return false;
}
/// set double property value, for ML loaders
bool setProperty(string name, double value) {
if (name.equal("alpha")) {
int n = cast(int)(value * 255);
setProperty(name, n);
}
return false;
}
/// set int property value, for ML loaders
bool setProperty(string name, int value) {
if (name.equal("alpha")) {
if (value < 0)
value = 0;
else if (value > 255)
value = 255;
alpha = cast(ushort)value;
return true;
}
if (name.equal("minWidth")) {
minWidth = value;
return true;
@ -1491,6 +1576,14 @@ class Widget {
backgroundColor = cast(uint)value;
return true;
}
if (name.equal("margins")) { // use same value for all sides
margins = Rect(value, value, value, value);
return true;
}
if (name.equal("padding")) { // use same value for all sides
padding = Rect(value, value, value, value);
return true;
}
return false;
}