fixes; add more files to sample projects

This commit is contained in:
Vadim Lopatin 2015-01-20 14:21:34 +03:00
parent fd763e525a
commit d580165beb
10 changed files with 1341 additions and 609 deletions

View File

@ -194,7 +194,6 @@
<File path="src\ddc\lexer\exceptions.d" />
<File path="src\ddc\lexer\Lexer.d" />
<File path="src\ddc\lexer\LexerException.d" />
<File path="src\ddc\lexer\LineStream.d" />
<File path="src\ddc\lexer\SourceEncodingException.d" />
<File path="src\ddc\lexer\textsource.d" />
<File path="src\ddc\lexer\Tokenizer.d" />

View File

@ -1,7 +1,6 @@
// D grammar - according to http://dlang.org/grammar
module ddc.lexer.Lexer;
import ddc.lexer.LineStream;
import ddc.lexer.Tokenizer;
/** Lexem type constants */
@ -280,9 +279,4 @@ class AltDeclarator : Lexem {
class Lexer
{
LineStream _lineStream;
this(LineStream lineStream)
{
_lineStream = lineStream;
}
}

View File

@ -1,589 +0,0 @@
module ddc.lexer.LineStream;
import std.stream;
import ddc.lexer.exceptions;
import std.stdio;
import std.conv;
import ddc.lexer.textsource;
class LineStream : SourceLines {
public enum EncodingType {
ASCII,
UTF8,
UTF16BE,
UTF16LE,
UTF32BE,
UTF32LE
};
static immutable uint LINE_POSITION_UNDEFINED = uint.max;
static immutable int TEXT_BUFFER_SIZE = 1024;
static immutable int BYTE_BUFFER_SIZE = 512;
static immutable int QUARTER_BYTE_BUFFER_SIZE = BYTE_BUFFER_SIZE / 4;
InputStream _stream;
string _filename;
SourceFile _file;
ubyte[] _buf; // stream reading buffer
uint _pos; // reading position of stream buffer
uint _len; // number of bytes in stream buffer
bool _streamEof; // true if input stream is in EOF state
uint _line; // current line number
uint _textPos; // start of text line in text buffer
uint _textLen; // position of last filled char in text buffer + 1
dchar[] _textBuf; // text buffer
bool _eof; // end of file, no more lines
override @property SourceFile file() { return _file; }
@property string filename() { return _file.filename; }
override @property uint line() { return _line; }
@property EncodingType encoding() { return _encoding; }
override @property int errorCode() { return _errorCode; }
override @property string errorMessage() { return _errorMessage; }
override @property int errorLine() { return _errorLine; }
override @property int errorPos() { return _errorPos; }
immutable EncodingType _encoding;
int _errorCode;
string _errorMessage;
uint _errorLine;
uint _errorPos;
protected this(InputStream stream, SourceFile file, EncodingType encoding, ubyte[] buf, uint offset, uint len) {
_file = file;
_stream = stream;
_encoding = encoding;
_buf = buf;
_len = len;
_pos = offset;
_streamEof = _stream.eof;
}
// returns slice of bytes available in buffer
uint readBytes() {
uint bytesLeft = _len - _pos;
if (_streamEof || bytesLeft > QUARTER_BYTE_BUFFER_SIZE)
return bytesLeft;
if (_pos > 0) {
for (uint i = 0; i < bytesLeft; i++)
_buf[i] = _buf[i + _pos];
_len = bytesLeft;
_pos = 0;
}
uint bytesRead = cast(uint)_stream.read(_buf[_len .. BYTE_BUFFER_SIZE]);
_len += bytesRead;
_streamEof = _stream.eof;
return _len - _pos; //_buf[_pos .. _len];
}
// when bytes consumed from byte buffer, call this method to update position
void consumedBytes(uint count) {
_pos += count;
}
// reserve text buffer for specified number of characters, and return pointer to first free character in buffer
dchar * reserveTextBuf(uint len) {
// create new text buffer if necessary
if (_textBuf == null) {
if (len < TEXT_BUFFER_SIZE)
len = TEXT_BUFFER_SIZE;
_textBuf = new dchar[len];
return _textBuf.ptr;
}
uint spaceLeft = cast(uint)_textBuf.length - _textLen;
if (spaceLeft >= len)
return _textBuf.ptr + _textLen;
// move text to beginning of buffer, if necessary
if (_textPos > _textBuf.length / 2) {
uint charCount = _textLen - _textPos;
dchar * p = _textBuf.ptr;
for (uint i = 0; i < charCount; i++)
p[i] = p[i + _textPos];
_textLen = charCount;
_textPos = 0;
}
// resize buffer if necessary
if (_textLen + len > _textBuf.length) {
// resize buffer
uint newsize = cast(uint)_textBuf.length * 2;
if (newsize < _textLen + len)
newsize = _textLen + len;
_textBuf.length = newsize;
}
return _textBuf.ptr + _textLen;
}
void appendedText(uint len) {
//writeln("appended ", len, " chars of text"); //:", _textBuf[_textLen .. _textLen + len]);
_textLen += len;
}
void setError(int code, string message, uint errorLine, uint errorPos) {
_errorCode = code;
_errorMessage = message;
_errorLine = errorLine;
_errorPos = errorPos;
}
// override to decode text
abstract uint decodeText();
override public dchar[] readLine() {
if (_errorCode != 0) {
//writeln("error ", _errorCode, ": ", _errorMessage, " in line ", _errorLine);
return null; // error detected
}
if (_eof) {
//writeln("EOF found");
return null;
}
_line++;
uint p = 0;
uint eol = LINE_POSITION_UNDEFINED;
uint eof = LINE_POSITION_UNDEFINED;
uint lastchar = LINE_POSITION_UNDEFINED;
do {
if (_errorCode != 0) {
//writeln("error ", _errorCode, ": ", _errorMessage, " in line ", _errorLine);
return null; // error detected
}
uint charsLeft = _textLen - _textPos;
if (p >= charsLeft) {
uint decodedChars = decodeText();
if (_errorCode != 0) {
return null; // error detected
}
charsLeft = _textLen - _textPos;
if (decodedChars == 0) {
eol = charsLeft;
eof = charsLeft;
lastchar = charsLeft;
break;
}
}
for (; p < charsLeft; p++) {
dchar ch = _textBuf[_textPos + p];
if (ch == 0x0D) {
lastchar = p;
if (p == charsLeft - 1) {
// need one more char to check if it's 0D0A or just 0D eol
//writeln("read one more char for 0D0A detection");
decodeText();
if (_errorCode != 0) {
return null; // error detected
}
charsLeft = _textLen - _textPos;
}
dchar ch2 = (p < charsLeft - 1) ? _textBuf[_textPos + p + 1] : 0;
if (ch2 == 0x0A)
eol = p + 2;
else
eol = p + 1;
break;
} else if (ch == 0x0A || ch == 0x2028 || ch == 0x2029) {
// single char eoln
lastchar = p;
eol = p + 1;
break;
} else if (ch == 0 || ch == 0x001A) {
// eof
//writeln("EOF char found");
lastchar = p;
eol = eof = p + 1;
break;
}
}
} while (eol == LINE_POSITION_UNDEFINED);
uint lineStart = _textPos;
uint lineEnd = _textPos + lastchar;
_textPos += eol; // consume text
if (eof != LINE_POSITION_UNDEFINED) {
_eof = true;
//writeln("Setting eof flag. lastchar=", lastchar, ", p=", p, ", lineStart=", lineStart);
if (lineStart >= lineEnd) {
//writeln("lineStart >= lineEnd -- treat as eof");
return null; // eof
}
}
// return slice with decoded line
return _textBuf[lineStart .. lineEnd];
}
// factory for string parser
public static LineStream create(string code, string filename = "") {
uint len = cast(uint)code.length;
ubyte[] data = new ubyte[len + 3];
for (uint i = 0; i < len; i++)
data[i + 3] = code[i];
// BOM for UTF8
data[0] = 0xEF;
data[1] = 0xBB;
data[2] = 0xBF;
MemoryStream stream = new MemoryStream(data);
return create(stream, filename);
}
// factory
public static LineStream create(InputStream stream, string filename) {
ubyte[] buf = new ubyte[BYTE_BUFFER_SIZE];
buf[0] = buf[1] = buf[2] = buf[3] = 0;
if (!stream.isOpen)
return null;
uint len = cast(uint)stream.read(buf);
if (buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) {
return new Utf8LineStream(stream, filename, buf, len);
} else if (buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0xFE && buf[3] == 0xFF) {
return new Utf32beLineStream(stream, filename, buf, len);
} else if (buf[0] == 0xFF && buf[1] == 0xFE && buf[2] == 0x00 && buf[3] == 0x00) {
return new Utf32leLineStream(stream, filename, buf, len);
} else if (buf[0] == 0xFE && buf[1] == 0xFF) {
return new Utf16beLineStream(stream, filename, buf, len);
} else if (buf[0] == 0xFF && buf[1] == 0xFE) {
return new Utf16leLineStream(stream, filename, buf, len);
} else {
return new AsciiLineStream(stream, filename, buf, len);
}
}
protected bool invalidCharFlag;
protected void invalidCharError() {
uint pos = _textLen - _textPos + 1;
setError(1, "Invalid character in line " ~ to!string(_line) ~ ":" ~ to!string(pos), _line, pos);
}
}
class AsciiLineStream : LineStream {
this(InputStream stream, string filename, ubyte[] buf, uint len) {
super(stream, new SourceFile(filename), EncodingType.ASCII, buf, 0, len);
}
override uint decodeText() {
if (invalidCharFlag) {
invalidCharError();
return 0;
}
uint bytesAvailable = readBytes();
ubyte * bytes = _buf.ptr + _pos;
if (bytesAvailable == 0)
return 0; // nothing to decode
uint len = bytesAvailable;
ubyte* b = bytes;
dchar* text = reserveTextBuf(len);
uint i = 0;
for (; i < len; i++) {
ubyte ch = b[i];
if (ch & 0x80) {
// invalid character
invalidCharFlag = true;
break;
}
text[i] = ch;
}
consumedBytes(i);
appendedText(i);
return len;
}
}
class Utf8LineStream : LineStream {
this(InputStream stream, string filename, ubyte[] buf, uint len) {
super(stream, new SourceFile(filename), EncodingType.UTF8, buf, 3, len);
}
override uint decodeText() {
if (invalidCharFlag) {
invalidCharError();
return 0;
}
uint bytesAvailable = readBytes();
ubyte * bytes = _buf.ptr + _pos;
if (bytesAvailable == 0)
return 0; // nothing to decode
uint len = bytesAvailable;
uint chars = 0;
ubyte* b = bytes;
dchar* text = reserveTextBuf(len);
uint i = 0;
for (; i < len; i++) {
uint ch = 0;
uint ch0 = b[i];
uint bleft = len - i;
uint bread = 0;
if (!(ch0 & 0x80)) {
// 0x00..0x7F single byte
ch = ch0;
bread = 1;
} if ((ch0 & 0xE0) == 0xC0) {
// two bytes 110xxxxx 10xxxxxx
if (bleft < 2)
break;
uint ch1 = b[i + 1];
if ((ch1 & 0xC0) != 0x80) {
invalidCharFlag = true;
break;
}
ch = ((ch0 & 0x1F) << 6) | ((ch1 & 0x3F));
bread = 2;
} if ((ch0 & 0xF0) == 0xE0) {
// three bytes 1110xxxx 10xxxxxx 10xxxxxx
if (bleft < 3)
break;
uint ch1 = b[i + 1];
uint ch2 = b[i + 2];
if ((ch1 & 0xC0) != 0x80 || (ch2 & 0xC0) != 0x80) {
invalidCharFlag = true;
break;
}
ch = ((ch0 & 0x0F) << 12) | ((ch1 & 0x1F) << 6) | ((ch2 & 0x3F));
bread = 3;
} if ((ch0 & 0xF8) == 0xF0) {
// four bytes 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
if (bleft < 4)
break;
uint ch1 = b[i + 1];
uint ch2 = b[i + 2];
uint ch3 = b[i + 3];
if ((ch1 & 0xC0) != 0x80 || (ch2 & 0xC0) != 0x80 || (ch3 & 0xC0) != 0x80) {
invalidCharFlag = true;
break;
}
ch = ((ch0 & 0x07) << 18) | ((ch1 & 0x3F) << 12) | ((ch2 & 0x3F) << 6) | ((ch3 & 0x3F));
bread = 4;
} if ((ch0 & 0xFC) == 0xF8) {
// five bytes 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
if (bleft < 5)
break;
uint ch1 = b[i + 1];
uint ch2 = b[i + 2];
uint ch3 = b[i + 3];
uint ch4 = b[i + 4];
if ((ch1 & 0xC0) != 0x80 || (ch2 & 0xC0) != 0x80 || (ch3 & 0xC0) != 0x80 || (ch4 & 0xC0) != 0x80) {
invalidCharFlag = true;
break;
}
ch = ((ch0 & 0x03) << 24) | ((ch1 & 0x3F) << 18) | ((ch2 & 0x3F) << 12) | ((ch3 & 0x3F) << 6) | ((ch4 & 0x3F));
bread = 5;
} if ((ch0 & 0xFE) == 0xFC) {
// six bytes 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
if (bleft < 6)
break;
uint ch1 = b[i + 1];
uint ch2 = b[i + 2];
uint ch3 = b[i + 3];
uint ch4 = b[i + 4];
uint ch5 = b[i + 5];
if ((ch1 & 0xC0) != 0x80 || (ch2 & 0xC0) != 0x80 || (ch3 & 0xC0) != 0x80 || (ch4 & 0xC0) != 0x80 || (ch5 & 0xC0) != 0x80) {
invalidCharFlag = true;
break;
}
ch = ((ch0 & 0x01) << 30) | ((ch1 & 0x3F) << 24) | ((ch2 & 0x3F) << 18) | ((ch3 & 0x3F) << 12) | ((ch4 & 0x3F) << 6) | ((ch5 & 0x3F));
bread = 5;
}
if ((ch >= 0xd800 && ch < 0xe000) || (ch > 0x10FFFF)) {
invalidCharFlag = true;
break;
}
if (ch < 0x10000) {
text[chars++] = ch;
} else {
uint lo = ch & 0x3FF;
uint hi = ch >> 10;
text[chars++] = (0xd800 | hi);
text[chars++] = (0xdc00 | lo);
}
i += bread - 1;
}
consumedBytes(i);
appendedText(chars);
uint bleft = len - i;
if (_streamEof && bleft > 0)
invalidCharFlag = true; // incomplete character at end of stream
return chars;
}
}
class Utf16beLineStream : LineStream {
this(InputStream stream, string filename, ubyte[] buf, uint len) {
super(stream, new SourceFile(filename), EncodingType.UTF16BE, buf, 2, len);
}
override uint decodeText() {
if (invalidCharFlag) {
invalidCharError();
return 0;
}
uint bytesAvailable = readBytes();
ubyte * bytes = _buf.ptr + _pos;
if (bytesAvailable == 0)
return 0; // nothing to decode
uint len = bytesAvailable;
uint chars = 0;
ubyte* b = bytes;
dchar* text = reserveTextBuf(len / 2 + 1);
uint i = 0;
for (; i < len - 1; i += 2) {
uint ch0 = b[i];
uint ch1 = b[i + 1];
uint ch = (ch0 << 8) | ch1;
// TODO: check special cases
text[chars++] = ch;
}
consumedBytes(i);
appendedText(chars);
uint bleft = len - i;
if (_streamEof && bleft > 0)
invalidCharFlag = true; // incomplete character at end of stream
return chars;
}
}
class Utf16leLineStream : LineStream {
this(InputStream stream, string filename, ubyte[] buf, uint len) {
super(stream, new SourceFile(filename), EncodingType.UTF16LE, buf, 2, len);
}
override uint decodeText() {
if (invalidCharFlag) {
invalidCharError();
return 0;
}
uint bytesAvailable = readBytes();
ubyte * bytes = _buf.ptr + _pos;
if (bytesAvailable == 0)
return 0; // nothing to decode
uint len = bytesAvailable;
uint chars = 0;
ubyte* b = bytes;
dchar* text = reserveTextBuf(len / 2 + 1);
uint i = 0;
for (; i < len - 1; i += 2) {
uint ch0 = b[i];
uint ch1 = b[i + 1];
uint ch = (ch1 << 8) | ch0;
// TODO: check special cases
text[chars++] = ch;
}
consumedBytes(i);
appendedText(chars);
uint bleft = len - i;
if (_streamEof && bleft > 0)
invalidCharFlag = true; // incomplete character at end of stream
return chars;
}
}
class Utf32beLineStream : LineStream {
this(InputStream stream, string filename, ubyte[] buf, uint len) {
super(stream, new SourceFile(filename), EncodingType.UTF32BE, buf, 4, len);
}
override uint decodeText() {
if (invalidCharFlag) {
invalidCharError();
return 0;
}
uint bytesAvailable = readBytes();
ubyte * bytes = _buf.ptr + _pos;
if (bytesAvailable == 0)
return 0; // nothing to decode
uint len = bytesAvailable;
uint chars = 0;
ubyte* b = bytes;
dchar* text = reserveTextBuf(len / 2 + 1);
uint i = 0;
for (; i < len - 3; i += 4) {
uint ch0 = b[i];
uint ch1 = b[i + 1];
uint ch2 = b[i + 2];
uint ch3 = b[i + 3];
uint ch = (ch0 << 24) | (ch1 << 16) | (ch2 << 8) | ch3;
if ((ch >= 0xd800 && ch < 0xe000) || (ch > 0x10FFFF)) {
invalidCharFlag = true;
break;
}
text[chars++] = ch;
}
consumedBytes(i);
appendedText(chars);
uint bleft = len - i;
if (_streamEof && bleft > 0)
invalidCharFlag = true; // incomplete character at end of stream
return chars;
}
}
class Utf32leLineStream : LineStream {
this(InputStream stream, string filename, ubyte[] buf, uint len) {
super(stream, new SourceFile(filename), EncodingType.UTF32LE, buf, 4, len);
}
override uint decodeText() {
if (invalidCharFlag) {
invalidCharError();
return 0;
}
uint bytesAvailable = readBytes();
ubyte * bytes = _buf.ptr + _pos;
if (bytesAvailable == 0)
return 0; // nothing to decode
uint len = bytesAvailable;
uint chars = 0;
ubyte* b = bytes;
dchar* text = reserveTextBuf(len / 2 + 1);
uint i = 0;
for (; i < len - 3; i += 4) {
uint ch3 = b[i];
uint ch2 = b[i + 1];
uint ch1 = b[i + 2];
uint ch0 = b[i + 3];
uint ch = (ch0 << 24) | (ch1 << 16) | (ch2 << 8) | ch3;
if ((ch >= 0xd800 && ch < 0xe000) || (ch > 0x10FFFF)) {
invalidCharFlag = true;
break;
}
text[chars++] = ch;
}
consumedBytes(i);
appendedText(chars);
uint bleft = len - i;
if (_streamEof && bleft > 0)
invalidCharFlag = true; // incomplete character at end of stream
return chars;
}
}
unittest {
static if (false) {
import std.stdio;
import std.conv;
import std.utf;
//string fname = "C:\\projects\\d\\ddc\\ddclexer\\src\\ddc\\lexer\\LineStream.d";
//string fname = "/home/lve/src/d/ddc/ddclexer/" ~ __FILE__; //"/home/lve/src/d/ddc/ddclexer/src/ddc/lexer/Lexer.d";
//string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf8.d";
//string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf16be.d";
//string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf16le.d";
//string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf32be.d";
string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf32le.d";
writeln("opening file");
std.stream.File f = new std.stream.File(fname);
scope(exit) { f.close(); }
try {
LineStream lines = LineStream.create(f, fname);
for (;;) {
dchar[] s = lines.readLine();
if (s is null)
break;
writeln("line " ~ to!string(lines.line()) ~ ":" ~ toUTF8(s));
}
if (lines.errorCode != 0) {
writeln("Error ", lines.errorCode, " ", lines.errorMessage, " -- at line ", lines.errorLine, " position ", lines.errorPos);
} else {
writeln("EOF reached");
}
} catch (Exception e) {
writeln("Exception " ~ e.toString);
}
}
}
// LAST LINE

View File

@ -58,24 +58,24 @@ class SimpleDSyntaxHighlighter : SyntaxHighlighter {
//writeln("EOF token");
break;
}
uint newPos = token.pos;
uint newLine = token.line;
uint newPos = token.pos - 1;
uint newLine = token.line - 1;
if (category) {
// fill with category
for (uint i = tokenLine - 1; i <= newLine - 1; i++) {
uint start = i > tokenLine - 1 ? 0 : tokenPos;
uint end = i < newLine - 1 ? lines[i].length : tokenPos;
for (uint j = start; j < end; j++) {
assert(i < _props.length);
if (j - 1 < _props[i].length)
_props[i][j - 1] = category;
}
//if (category) {
// fill with category
for (uint i = tokenLine; i <= newLine; i++) {
uint start = i > tokenLine ? 0 : tokenPos;
uint end = i < newLine ? lines[i].length : tokenPos;
for (uint j = start; j < end; j++) {
assert(i < _props.length);
if (j - 1 < _props[i].length)
_props[i][j - 1] = category;
}
}
//}
TokenType t = token.type;
// handle token
// handle token - convert to category
if (t == TokenType.COMMENT) {
category = TokenCategory.Comment;
} else if (t == TokenType.KEYWORD) {

View File

@ -0,0 +1,287 @@
/**
This module implements object collections.
Copyright: Vadim Lopatin, 2014
License: Boost License 1.0
Authors: Vadim Lopatin, coolreader.org@gmail.com
*/
module collections;
import std.algorithm;
/**
Array based collection of items.
Retains item order when during add/remove operations.
Optionally destroys removed objects when instanciated with ownItems = true.
*/
struct Collection(T, bool ownItems = false) {
private T[] _items;
private size_t _len;
/// returns true if there are no items in collection
@property bool empty() { return _len == 0; }
/// returns number of items in collection
@property size_t length() { return _len; }
/// returns currently allocated capacity (may be more than length)
@property size_t size() { return _items.length; }
/// change capacity (e.g. to reserve big space to avoid multiple reallocations)
@property void size(size_t newSize) {
if (_len > newSize)
length = newSize; // shrink
_items.length = newSize;
}
/// returns number of items in collection
@property void length(size_t newSize) {
if (newSize < _len) {
// shrink
static if (is(T == class) || is(T == struct)) {
// clear items
for (size_t i = newSize; i < _len; i++) {
static if (ownItems)
destroy(_items[i]);
_items[i] = T.init;
}
}
} else if (newSize > _len) {
// expand
if (_items.length < newSize)
_items.length = newSize;
}
_len = newSize;
}
/// access item by index
ref T opIndex(size_t index) {
assert(index < _len);
return _items[index];
}
/// insert new item in specified position
void add(T item, size_t index = size_t.max) {
if (index > _len)
index = _len;
if (_items.length <= _len) {
if (_items.length < 4)
_items.length = 4;
else
_items.length = _items.length * 2;
}
if (index < _len) {
for (size_t i = _len; i > index; i--)
_items[i] = _items[i - 1];
}
_items[index] = item;
_len++;
}
/// add all items from other collection
void addAll(ref Collection!(T, ownItems) v) {
for (int i = 0; i < v.length; i++)
add(v[i]);
}
/// support for appending (~=, +=) and removing by value (-=)
ref Collection opOpAssign(string op)(T item) {
static if (op.equal("~") || op.equal("+")) {
// append item to end of collection
add(item);
return this;
} else if (op.equal("-")) {
// remove item from collection, if present
removeValue(item);
return this;
} else {
assert(false, "not supported opOpAssign " ~ op);
}
}
/// returns index of first occurence of item, size_t.max if not found
size_t indexOf(T item) {
for (size_t i = 0; i < _len; i++)
if (_items[i] == item)
return i;
return size_t.max;
}
/// remove single item, returning removed item
T remove(size_t index) {
assert(index < _len);
T result = _items[index];
for (size_t i = index; i + 1 < _len; i++)
_items[i] = _items[i + 1];
_items[_len] = T.init;
_len--;
return result;
}
/// remove single item by value - if present in collection, returning true if item was found and removed
bool removeValue(T value) {
size_t index = indexOf(value);
if (index == size_t.max)
return false;
T res = remove(index);
static if (ownItems)
destroy(res);
return true;
}
/// support of foreach with reference
int opApply(int delegate(ref T param) op) {
int result = 0;
for (size_t i = 0; i < _len; i++) {
result = op(_items[i]);
if (result)
break;
}
return result;
}
/// remove all items
void clear() {
static if (is(T == class) || is(T == struct)) {
/// clear references
for(size_t i = 0; i < _len; i++) {
static if (ownItems)
destroy(_items[i]);
_items[i] = T.init;
}
}
_len = 0;
_items = null;
}
//===================================
// stack/queue-like ops
/// remove first item
@property T popFront() {
if (empty)
return T.init; // no items
return remove(0);
}
/// insert item at beginning of collection
void pushFront(T item) {
add(item, 0);
}
/// remove last item
@property T popBack() {
if (empty)
return T.init; // no items
return remove(length - 1);
}
/// insert item at end of collection
void pushBack(T item) {
add(item);
}
/// peek first item
@property T front() {
if (empty)
return T.init; // no items
return _items[0];
}
/// peek last item
@property T back() {
if (empty)
return T.init; // no items
return _items[_len - 1];
}
/// removes all items on destroy
~this() {
clear();
}
}
/** object list holder, owning its objects - on destroy of holder, all own objects will be destroyed */
struct ObjectList(T) {
protected T[] _list;
protected int _count;
/** returns count of items */
@property int count() const { return _count; }
/** get item by index */
T get(int index) {
assert(index >= 0 && index < _count, "child index out of range");
return _list[index];
}
/// get item by index
T opIndex(int index) {
return get(index);
}
/** add item to list */
T add(T item) {
if (_list.length <= _count) // resize
_list.length = _list.length < 4 ? 4 : _list.length * 2;
_list[_count++] = item;
return item;
}
/** add item to list */
T insert(T item, int index = -1) {
if (index > _count || index < 0)
index = _count;
if (_list.length <= _count) // resize
_list.length = _list.length < 4 ? 4 : _list.length * 2;
for (int i = _count; i > index; i--)
_list[i] = _list[i - 1];
_list[index] = item;
_count++;
return item;
}
/** find child index for item, return -1 if not found */
int indexOf(T item) {
if (item is null)
return -1;
for (int i = 0; i < _count; i++)
if (_list[i] == item)
return i;
return -1;
}
/** find child index for item by id, return -1 if not found */
static if (__traits(hasMember, T, "compareId")) {
int indexOf(string id) {
for (int i = 0; i < _count; i++)
if (_list[i].compareId(id))
return i;
return -1;
}
}
/** remove item from list, return removed item */
T remove(int index) {
assert(index >= 0 && index < _count, "child index out of range");
T item = _list[index];
for (int i = index; i < _count - 1; i++)
_list[i] = _list[i + 1];
_count--;
return item;
}
/** Replace item with another value, destroy old value. */
void replace(T item, int index) {
T old = _list[index];
_list[index] = item;
destroy(old);
}
/** Replace item with another value, destroy old value. */
void replace(T newItem, T oldItem) {
int idx = indexOf(oldItem);
if (newItem is null) {
if (idx >= 0) {
T item = remove(idx);
destroy(item);
}
} else {
if (idx >= 0)
replace(newItem, idx);
else
add(newItem);
}
}
/** remove and destroy all items */
void clear() {
for (int i = 0; i < _count; i++) {
destroy(_list[i]);
_list[i] = null;
}
_count = 0;
}
~this() {
clear();
}
}

View File

@ -0,0 +1,135 @@
/**
This module contains some type definitions.
*/
module types;
/** 2D point */
struct Point {
/// x coordinate
int x;
/// y coordinate
int y;
this(int x0, int y0) {
x = x0;
y = y0;
}
}
/** 2D rectangle */
struct Rect {
/// x coordinate of top left corner
int left;
/// y coordinate of top left corner
int top;
/// x coordinate of bottom right corner
int right;
/// y coordinate of bottom right corner
int bottom;
/// returns average of left, right
@property int middlex() { return (left + right) / 2; }
/// returns average of top, bottom
@property int middley() { return (top + bottom) / 2; }
/// returns middle point
@property Point middle() { return Point(middlex, middley); }
/// add offset to horizontal and vertical coordinates
void offset(int dx, int dy) {
left += dx;
right += dx;
top += dy;
bottom += dy;
}
/// expand rectangle dimensions
void expand(int dx, int dy) {
left -= dx;
right += dx;
top -= dy;
bottom += dy;
}
/// shrink rectangle dimensions
void shrink(int dx, int dy) {
left += dx;
right -= dx;
top += dy;
bottom -= dy;
}
/// for all fields, sets this.field to rc.field if rc.field > this.field
void setMax(Rect rc) {
if (left < rc.left)
left = rc.left;
if (right < rc.right)
right = rc.right;
if (top < rc.top)
top = rc.top;
if (bottom < rc.bottom)
bottom = rc.bottom;
}
/// returns width of rectangle (right - left)
@property int width() { return right - left; }
/// returns height of rectangle (bottom - top)
@property int height() { return bottom - top; }
/// constructs rectangle using left, top, right, bottom coordinates
this(int x0, int y0, int x1, int y1) {
left = x0;
top = y0;
right = x1;
bottom = y1;
}
/// returns true if rectangle is empty (right <= left || bottom <= top)
@property bool empty() {
return right <= left || bottom <= top;
}
/// translate rectangle coordinates by (x,y) - add deltax to x coordinates, and deltay to y coordinates
void moveBy(int deltax, int deltay) {
left += deltax;
right += deltax;
top += deltay;
bottom += deltay;
}
/// moves this rect to fit rc bounds, retaining the same size
void moveToFit(ref Rect rc) {
if (right > rc.right)
moveBy(rc.right - right, 0);
if (bottom > rc.bottom)
moveBy(0, rc.bottom - bottom);
if (left < rc.left)
moveBy(rc.left - left, 0);
if (top < rc.top)
moveBy(0, rc.top - top);
}
/// updates this rect to intersection with rc, returns true if result is non empty
bool intersect(Rect rc) {
if (left < rc.left)
left = rc.left;
if (top < rc.top)
top = rc.top;
if (right > rc.right)
right = rc.right;
if (bottom > rc.bottom)
bottom = rc.bottom;
return right > left && bottom > top;
}
/// returns true if this rect has nonempty intersection with rc
bool intersects(Rect rc) {
if (rc.left >= right || rc.top >= bottom || rc.right <= left || rc.bottom <= top)
return false;
return true;
}
/// returns true if point is inside of this rectangle
bool isPointInside(Point pt) {
return pt.x >= left && pt.x < right && pt.y >= top && pt.y < bottom;
}
/// returns true if point is inside of this rectangle
bool isPointInside(int x, int y) {
return x >= left && x < right && y >= top && y < bottom;
}
/// this rectangle is completely inside rc
bool isInsideOf(Rect rc) {
return left >= rc.left && right <= rc.right && top >= rc.top && bottom <= rc.bottom;
}
bool opEquals(Rect rc) {
return left == rc.left && right == rc.right && top == rc.top && bottom == rc.bottom;
}
}

View File

@ -0,0 +1,340 @@
// Written in the D programming language.
/**
This module contains cross-platform file access utilities
Synopsis:
----
import exlib.files;
----
Copyright: Vadim Lopatin, 2014
License: Boost License 1.0
Authors: Vadim Lopatin, coolreader.org@gmail.com
*/
module dlangui.core.files;
import std.algorithm;
private import dlangui.core.logger;
private import std.process;
private import std.path;
private import std.file;
private import std.utf;
version (Windows) {
/// path delimiter (\ for windows, / for others)
immutable char PATH_DELIMITER = '\\';
} else {
/// path delimiter (\ for windows, / for others)
immutable char PATH_DELIMITER = '/';
}
/// Filesystem root entry / bookmark types
enum RootEntryType : uint {
/// filesystem root
ROOT,
/// current user home
HOME,
/// removable drive
REMOVABLE,
/// fixed drive
FIXED,
/// network
NETWORK,
/// cd rom
CDROM,
/// sd card
SDCARD,
/// custom bookmark
BOOKMARK,
}
/// Filesystem root entry item
struct RootEntry {
private RootEntryType _type;
private string _path;
private dstring _display;
this(RootEntryType type, string path, dstring display = null) {
_type = type;
_path = path;
_display = display;
if (display is null) {
_display = toUTF32(baseName(path));
}
}
/// Returns type
@property RootEntryType type() { return _type; }
/// Returns path
@property string path() { return _path; }
/// Returns display label
@property dstring label() { return _display; }
/// Returns icon resource id
@property string icon() {
switch (type) {
case RootEntryType.NETWORK:
return "folder-network";
case RootEntryType.BOOKMARK:
return "folder-bookmark";
case RootEntryType.CDROM:
return "drive-optical";
case RootEntryType.FIXED:
return "drive-harddisk";
case RootEntryType.HOME:
return "user-home";
case RootEntryType.ROOT:
return "computer";
case RootEntryType.SDCARD:
return "media-flash-sd-mmc";
case RootEntryType.REMOVABLE:
return "device-removable-media";
default:
return "folder-blue";
}
}
}
/// Returns
@property RootEntry homeEntry() {
return RootEntry(RootEntryType.HOME, homePath);
}
/// returns array of system root entries
@property RootEntry[] getRootPaths() {
RootEntry[] res;
res ~= RootEntry(RootEntryType.HOME, homePath);
version (posix) {
res ~= RootEntry(RootEntryType.ROOT, "/", "File System"d);
}
version (Windows) {
import win32.windows;
uint mask = GetLogicalDrives();
for (int i = 0; i < 26; i++) {
if (mask & (1 << i)) {
char letter = cast(char)('A' + i);
string path = "" ~ letter ~ ":\\";
dstring display = ""d ~ letter ~ ":"d;
// detect drive type
RootEntryType type;
uint wtype = GetDriveTypeA(("" ~ path).ptr);
//Log.d("Drive ", path, " type ", wtype);
switch (wtype) {
case DRIVE_REMOVABLE:
type = RootEntryType.REMOVABLE;
break;
case DRIVE_REMOTE:
type = RootEntryType.NETWORK;
break;
case DRIVE_CDROM:
type = RootEntryType.CDROM;
break;
default:
type = RootEntryType.FIXED;
break;
}
res ~= RootEntry(type, path, display);
}
}
}
return res;
}
/// returns true if directory is root directory (e.g. / or C:\)
bool isRoot(string path) {
string root = rootName(path);
if (path.equal(root))
return true;
return false;
}
/// returns parent directory for specified path
string parentDir(string path) {
return buildNormalizedPath(path, "..");
}
/// check filename with pattern (currently only *.ext pattern is supported)
bool filterFilename(string filename, string pattern) {
if (pattern.equal("*.*"))
return true; // matches any
if (pattern.length < 3)
return false;
if (pattern[0] != '*' || pattern[1] != '.')
return false;
return filename.endsWith(pattern[1..$]);
}
/// Filters file name by pattern list
bool filterFilename(string filename, string[] filters) {
if (filters.length == 0)
return true; // no filters - show all
foreach(pattern; filters) {
if (filterFilename(filename, pattern))
return true;
}
return false;
}
/** List directory content
Optionally filters file names by filter.
Result will be placed into entries array.
Returns true if directory exists and listed successfully, false otherwise.
*/
bool listDirectory(string dir, bool includeDirs, bool includeFiles, bool showHiddenFiles, string[] filters, ref DirEntry[] entries) {
entries.length = 0;
if (!isDir(dir)) {
return false;
}
if (!isRoot(dir) && includeDirs) {
entries ~= DirEntry(appendPath(dir, ".."));
}
try {
DirEntry[] dirs;
DirEntry[] files;
foreach (DirEntry e; dirEntries(dir, SpanMode.shallow)) {
string fn = baseName(e.name);
if (!showHiddenFiles && fn.startsWith("."))
continue;
if (e.isDir) {
dirs ~= e;
} else if (e.isFile) {
files ~= e;
}
}
if (includeDirs)
foreach(DirEntry e; dirs)
entries ~= e;
if (includeFiles)
foreach(DirEntry e; files)
if (filterFilename(e.name, filters))
entries ~= e;
return true;
} catch (FileException e) {
return false;
}
}
/** Returns true if char ch is / or \ slash */
bool isPathDelimiter(char ch) {
return ch == '/' || ch == '\\';
}
/// Returns current directory
@property string currentDir() {
return getcwd();
}
/** Returns current executable path only, including last path delimiter - removes executable name from result of std.file.thisExePath() */
@property string exePath() {
string path = thisExePath();
int lastSlash = 0;
for (int i = 0; i < path.length; i++)
if (path[i] == PATH_DELIMITER)
lastSlash = i;
return path[0 .. lastSlash + 1];
}
/// Returns user's home directory
@property string homePath() {
string path;
version (Windows) {
path = environment.get("USERPROFILE");
if (path is null)
path = environment.get("HOME");
} else {
path = environment.get("HOME");
}
if (path is null)
path = "."; // fallback to current directory
return path;
}
/**
Returns application data directory
On unix, it will return path to subdirectory in home directory - e.g. /home/user/.subdir if ".subdir" is passed as a paramter.
On windows, it will return path to subdir in APPDATA directory - e.g. C:\Users\User\AppData\Roaming\.subdir.
*/
string appDataPath(string subdir = null) {
string path;
version (Windows) {
path = environment.get("APPDATA");
}
if (path is null)
path = homePath;
if (subdir !is null) {
path ~= PATH_DELIMITER;
path ~= subdir;
}
return path;
}
/// Converts path delimiters to standard for platform inplace in buffer(e.g. / to \ on windows, \ to / on posix), returns buf
char[] convertPathDelimiters(char[] buf) {
foreach(ref ch; buf) {
version (Windows) {
if (ch == '/')
ch = '\\';
} else {
if (ch == '\\')
ch = '/';
}
}
return buf;
}
/** Converts path delimiters to standard for platform (e.g. / to \ on windows, \ to / on posix) */
string convertPathDelimiters(string src) {
char[] buf = src.dup;
return cast(string)convertPathDelimiters(buf);
}
/** Appends file path parts with proper delimiters e.g. appendPath("/home/user", ".myapp", "config") => "/home/user/.myapp/config" */
string appendPath(string[] pathItems ...) {
char[] buf;
foreach (s; pathItems) {
if (buf.length && !isPathDelimiter(buf[$-1]))
buf ~= PATH_DELIMITER;
buf ~= s;
}
return convertPathDelimiters(buf).dup;
}
/** Appends file path parts with proper delimiters (as well converts delimiters inside path to system) to buffer e.g. appendPath("/home/user", ".myapp", "config") => "/home/user/.myapp/config" */
char[] appendPath(char[] buf, string[] pathItems ...) {
foreach (s; pathItems) {
if (buf.length && !isPathDelimiter(buf[$-1]))
buf ~= PATH_DELIMITER;
buf ~= s;
}
return convertPathDelimiters(buf);
}
/** Split path into elements, e.g. /home/user/dir1 -> ["home", "user", "dir1"], "c:\dir1\dir2" -> ["c:", "dir1", "dir2"] */
string[] splitPath(string path) {
string[] res;
int start = 0;
for (int i = 0; i <= path.length; i++) {
char ch = i < path.length ? path[i] : 0;
if (ch == '\\' || ch == '/' || ch == 0) {
if (start < i)
res ~= path[start .. i].dup;
start = i + 1;
}
}
return res;
}

View File

@ -0,0 +1,408 @@
/**
This module contains UI internationalization support implementation.
UIString struct provides string container which can be either plain unicode string or id of string resource.
Translation strings are being stored in translation files, consisting of simple key=value pair lines:
---
STRING_RESOURCE_ID=Translation text 1
ANOTHER_STRING_RESOURCE_ID=Translation text 2
---
Supports fallback to another translation file (e.g. default language).
If string resource is not found neither in main nor fallback translation files, UNTRANSLATED: RESOURCE_ID will be returned.
String resources must be placed in i18n subdirectory inside one or more resource directories (set using Platform.instance.resourceDirs
property on application initialization).
File names must be language code with extension .ini (e.g. en.ini, fr.ini, es.ini)
If several files for the same language are found in (different directories) their content will be merged. It's useful to merge string resources
from DLangUI framework with resources of application.
Set interface language using Platform.instance.uiLanguage in UIAppMain during initialization of application settings:
---
Platform.instance.uiLanguage = "en";
---
Synopsis:
----
import exlib.i18n;
// use global i18n object to get translation for string ID
dstring translated = i18n.get("STR_FILE_OPEN");
// as well, you can specify fallback value - to return if translation is not found
dstring translated = i18n.get("STR_FILE_OPEN", "Open..."d);
// UIString type can hold either string resource id or dstring raw value.
UIString text;
// assign resource id as string (will remove dstring value if it was here)
text = "ID_FILE_EXIT";
// or assign raw value as dstring (will remove id if it was here)
text = "some text"d;
// assign both resource id and fallback value - to use if string resource is not found
text = UIString("ID_FILE_EXIT", "Exit"d);
// i18n.get() will automatically be invoked when getting UIString value (e.g. using alias this).
dstring translated = text;
----
Copyright: Vadim Lopatin, 2014
License: Boost License 1.0
Authors: Vadim Lopatin, coolreader.org@gmail.com
*/
module exlib.i18n;
import exlib.logger;
import exlib.files;
private import exlib.linestream;
private import std.utf;
private import std.algorithm;
/**
Container for UI string - either raw value or string resource ID
Set resource id (string) or plain unicode text (dstring) to it, and get dstring.
*/
struct UIString {
/** if not null, use it, otherwise lookup by id */
private dstring _value;
/** id to find value in translator */
private string _id;
/** create string with i18n resource id */
this(string id) {
_id = id;
}
/** create string with raw value */
this(dstring value) {
_value = value;
}
/** create string with resource id and raw value as fallback for missing translations */
this(string id, dstring fallbackValue) {
_id = id;
_value = fallbackValue;
}
/// Returns string resource id
@property string id() const { return _id; }
/// Sets string resource id
@property void id(string ID) {
_id = ID;
_value = null;
}
/** Get value (either raw or translated by id) */
@property dstring value() const {
if (_id !is null) // translate ID to dstring
return i18n.get(_id, _value); // get from resource, use _value as fallback
return _value;
}
/** Set raw value using property */
@property void value(dstring newValue) {
_value = newValue;
}
/** Assign raw value */
ref UIString opAssign(dstring rawValue) {
_value = rawValue;
_id = null;
return this;
}
/** Assign string resource id */
ref UIString opAssign(string ID) {
_id = ID;
_value = null;
return this;
}
/** Default conversion to dstring */
alias value this;
}
/**
UIString item collection
Based on array.
*/
struct UIStringCollection {
private UIString[] _items;
private int _length;
/** Returns number of items */
@property int length() { return _length; }
/** Slice */
UIString[] opIndex() {
return _items[0 .. _length];
}
/** Slice */
UIString[] opSlice() {
return _items[0 .. _length];
}
/** Slice */
UIString[] opSlice(size_t start, size_t end) {
return _items[start .. end];
}
/** Read item by index */
UIString opIndex(size_t index) {
return _items[index];
}
/** Modify item by index */
UIString opIndexAssign(UIString value, size_t index) {
_items[index] = value;
return _items[index];
}
/** Return unicode string for item by index */
dstring get(size_t index) {
return _items[index].value;
}
/** Assign UIStringCollection */
void opAssign(ref UIStringCollection items) {
clear();
addAll(items);
}
/** Append UIStringCollection */
void addAll(ref UIStringCollection items) {
foreach (UIString item; items) {
add(item);
}
}
/** Assign array of string resource IDs */
void opAssign(string[] items) {
clear();
addAll(items);
}
/** Append array of string resource IDs */
void addAll(string[] items) {
foreach (string item; items) {
add(item);
}
}
/** Assign array of unicode strings */
void opAssign(dstring[] items) {
clear();
addAll(items);
}
/** Append array of unicode strings */
void addAll(dstring[] items) {
foreach (dstring item; items) {
add(item);
}
}
/** Remove all items */
void clear() {
_items.length = 0;
_length = 0;
}
/** Insert resource id item into specified position */
void add(string item, int index = -1) {
UIString s;
s = item;
add(s, index);
}
/** Insert unicode string item into specified position */
void add(dstring item, int index = -1) {
UIString s;
s = item;
add(s, index);
}
/** Insert UIString item into specified position */
void add(UIString item, int index = -1) {
if (index < 0 || index > _length)
index = _length;
if (_items.length < _length + 1) {
if (_items.length < 8)
_items.length = 8;
else
_items.length = _items.length * 2;
}
for (size_t i = _length; i > index; i--) {
_items[i] = _items[i + 1];
}
_items[index] = item;
_length++;
}
/** Remove item with specified index */
void remove(int index) {
if (index < 0 || index >= _length)
return;
for (size_t i = index; i < _length - 1; i++)
_items[i] = _items[i + 1];
_length--;
}
/** Return index of first item with specified text or -1 if not found. */
int indexOf(dstring str) {
for (int i = 0; i < _length; i++) {
if (_items[i].value.equal(str))
return i;
}
return -1;
}
/** Return index of first item with specified string resource id or -1 if not found. */
int indexOf(string strId) {
for (int i = 0; i < _length; i++) {
if (_items[i].id.equal(strId))
return i;
}
return -1;
}
/** Return index of first item with specified string or -1 if not found. */
int indexOf(UIString str) {
if (str.id !is null)
return indexOf(str.id);
return indexOf(str.value);
}
}
/** UI Strings internationalization translator */
synchronized class UIStringTranslator {
private UIStringList _main;
private UIStringList _fallback;
private string[] _resourceDirs;
/** Looks for i18n directory inside one of passed dirs, and uses first found as directory to read i18n files from */
void findTranslationsDir(string[] dirs ...) {
_resourceDirs.length = 0;
import std.file;
foreach(dir; dirs) {
string path = appendPath(dir, "i18n/");
if (exists(path) && isDir(path)) {
Log.i("Adding i18n dir ", path);
_resourceDirs ~= path;
}
}
}
/** Convert resource path - append resource dir if necessary */
string[] convertResourcePaths(string filename) {
if (filename is null)
return null;
bool hasPathDelimiters = false;
foreach(char ch; filename)
if (ch == '/' || ch == '\\')
hasPathDelimiters = true;
string[] res;
if (!hasPathDelimiters && _resourceDirs.length) {
foreach (dir; _resourceDirs)
res ~= dir ~ filename;
} else {
res ~= filename;
}
return res;
}
/// create empty translator
this() {
_main = new shared UIStringList();
_fallback = new shared UIStringList();
}
/** Load translation file(s) */
bool load(string mainFilename, string fallbackFilename = null) {
_main.clear();
_fallback.clear();
bool res = _main.load(convertResourcePaths(mainFilename));
if (fallbackFilename !is null) {
res = _fallback.load(convertResourcePaths(fallbackFilename)) || res;
}
return res;
}
/** Translate string ID to string (returns "UNTRANSLATED: id" for missing values) */
dstring get(string id, dstring fallbackValue = null) {
if (id is null)
return null;
dstring s = _main.get(id);
if (s !is null)
return s;
s = _fallback.get(id);
if (s !is null)
return s;
if (fallbackValue.length > 0)
return fallbackValue;
return "UNTRANSLATED: "d ~ toUTF32(id);
}
}
/** UI string translator */
private shared class UIStringList {
private dstring[string] _map;
/// remove all items
void clear() {
_map.destroy();
}
/// set item value
void set(string id, dstring value) {
_map[id] = value;
}
/// get item value, null if translation is not found for id
dstring get(string id) const {
if (id in _map)
return _map[id];
return null;
}
/// load strings from stream
bool load(std.stream.InputStream stream) {
dlangui.core.linestream.LineStream lines = dlangui.core.linestream.LineStream.create(stream, "");
int count = 0;
for (;;) {
dchar[] s = lines.readLine();
if (s is null)
break;
int eqpos = -1;
int firstNonspace = -1;
int lastNonspace = -1;
for (int i = 0; i < s.length; i++)
if (s[i] == '=') {
eqpos = i;
break;
} else if (s[i] != ' ' && s[i] != '\t') {
if (firstNonspace == -1)
firstNonspace = i;
lastNonspace = i;
}
if (eqpos > 0 && firstNonspace != -1) {
string id = toUTF8(s[firstNonspace .. lastNonspace + 1]);
dstring value = s[eqpos + 1 .. $].dup;
set(id, value);
count++;
}
}
return count > 0;
}
/// load strings from file (utf8, id=value lines)
bool load(string[] filenames) {
clear();
bool res = false;
foreach(filename; filenames) {
import std.stream;
import std.file;
try {
debug Log.d("Loading string resources from file ", filename);
if (!exists(filename) || !isFile(filename)) {
Log.e("File does not exist: ", filename);
continue;
}
std.stream.File f = new std.stream.File(filename);
scope(exit) { f.close(); }
res = load(f) || res;
} catch (StreamFileException e) {
Log.e("Cannot read string resources from file ", filename);
}
}
return res;
}
}
/** Global UI translator object */
shared UIStringTranslator i18n;
shared static this() {
i18n = new shared UIStringTranslator();
}

View File

@ -0,0 +1,155 @@
// Written in the D programming language.
/**
This module provides logging utilities.
Use Log class static methods.
Synopsis:
----
import exlib.logger;
// setup:
// use stderror for logging
setStderrLogger();
// set log level
setLogLevel(LogLeve.Debug);
// usage:
// log debug message
Log.d("mouse clicked at ", x, ",", y);
// log error message
Log.e("exception while reading file", e);
----
Copyright: Vadim Lopatin, 2014
License: Boost License 1.0
Authors: Vadim Lopatin, coolreader.org@gmail.com
*/
module exlib.logger;
import std.stdio;
import std.datetime;
/// Log levels
enum LogLevel : int {
/// Fatal error, cannot resume
Fatal,
/// Error
Error,
/// Warning
Warn,
/// Informational message
Info,
/// Debug message
Debug,
/// Tracing message
Trace
}
/// Returns timestamp in milliseconds since 1970 UTC similar to Java System.currentTimeMillis()
long currentTimeMillis() {
return std.datetime.Clock.currStdTime / 10000;
}
/**
Logging utilities
Setup example:
----
// use stderror for logging
setStderrLogger();
// set log level
setLogLevel(LogLeve.Debug);
----
Logging example:
----
// log debug message
Log.d("mouse clicked at ", x, ",", y);
// log error message
Log.e("exception while reading file", e);
----
*/
synchronized class Log {
static:
private LogLevel logLevel = LogLevel.Info;
private std.stdio.File logFile;
/// Redirects output to stdout
void setStdoutLogger() {
logFile = stdout;
}
/// Redirects output to stderr
void setStderrLogger() {
logFile = stderr;
}
/// Redirects output to file
void setFileLogger(File file) {
logFile = file;
}
/// Sets log level (one of LogLevel)
void setLogLevel(LogLevel level) {
logLevel = level;
}
/// Log level to name helper function
string logLevelName(LogLevel level) {
switch (level) {
case LogLevel.Fatal: return "F";
case LogLevel.Error: return "E";
case LogLevel.Warn: return "W";
case LogLevel.Info: return "I";
case LogLevel.Debug: return "D";
case LogLevel.Trace: return "V";
default: return "?";
}
}
/// Log message with arbitrary log level
void log(S...)(LogLevel level, S args) {
if (logLevel >= level && logFile.isOpen) {
SysTime ts = Clock.currTime();
logFile.writef("%04d-%02d-%02d %02d:%02d:%02d.%03d %s ", ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, ts.fracSec.msecs, logLevelName(level));
logFile.writeln(args);
logFile.flush();
}
}
/// Log verbose / trace message
void v(S...)(S args) {
if (logLevel >= LogLevel.Trace && logFile.isOpen)
log(LogLevel.Trace, args);
}
/// Log debug message
void d(S...)(S args) {
if (logLevel >= LogLevel.Debug && logFile.isOpen)
log(LogLevel.Debug, args);
}
/// Log info message
void i(S...)(S args) {
if (logLevel >= LogLevel.Info && logFile.isOpen)
log(LogLevel.Info, args);
}
/// Log warn message
void w(S...)(S args) {
if (logLevel >= LogLevel.Warn && logFile.isOpen)
log(LogLevel.Warn, args);
}
/// Log error message
void e(S...)(S args) {
if (logLevel >= LogLevel.Error && logFile.isOpen)
log(LogLevel.Error, args);
}
/// Log fatal error message
void f(S...)(S args) {
if (logLevel >= LogLevel.Fatal && logFile.isOpen)
log(LogLevel.Fatal, args);
}
}

View File

@ -1,6 +1,9 @@
#!/usr/bin/env rdmd
// Computes average line length for standard input.
import std.stdio;
import exlib.logger;
import exlib.files;
import exlib.i18n;
void main()
{