diff --git a/source/elna/extended.d b/source/elna/extended.d index 0473cee..ff06a0c 100644 --- a/source/elna/extended.d +++ b/source/elna/extended.d @@ -3,7 +3,333 @@ */ module elna.extended; +import core.stdc.errno; +import core.stdc.stdio; +import std.sumtype; +import std.typecons; +import tanya.os.error; +import tanya.container.array; +import tanya.container.string; + +/** + * File handle abstraction. + */ struct File { - @disable this(this); + /// Plattform dependent file type. + alias Handle = FILE*; + + /// Uninitialized file handle value. + enum Handle invalid = null; + + /** + * Relative position. + */ + enum Whence + { + /// Relative to the start of the file. + set = SEEK_SET, + /// Relative to the current cursor position. + currentt = SEEK_CUR, + /// Relative from the end of the file. + end = SEEK_END, + } + + /** + * File open modes. + */ + enum Mode + { + /// Open the file for reading. + read = 1 << 0, + /// Open the file for writing. The stream is positioned at the beginning + /// of the file. + write = 1 << 1, + /// Open the file for writing and remove its contents. + truncate = 1 << 2, + /// Open the file for writing. The stream is positioned at the end of + /// the file. + append = 1 << 3, + } + + private enum Status + { + invalid, + owned, + borrowed, + } + + private union Storage + { + Handle handle; + ErrorCode errorCode; + } + private Storage storage; + private Status status = Status.invalid; + + @disable this(scope return ref File f); + @disable this(); + + /** + * Closes the file. + */ + ~this() @nogc nothrow + { + if (this.status == Status.owned) + { + fclose(this.storage.handle); + } + this.storage.handle = invalid; + this.status = Status.invalid; + } + + /** + * Construct the object with the given system handle. The won't be claused + * in the descructor if this constructor is used. + * + * Params: + * handle = File handle to be wrapped by this structure. + */ + this(Handle handle) @nogc nothrow pure @safe + { + this.storage.handle = handle; + this.status = Status.borrowed; + } + + /** + * Returns: Plattform dependent file handle. + */ + @property Handle handle() @nogc nothrow pure @trusted + { + return valid ? this.storage.handle : invalid; + } + + /** + * Returns: An error code if an error has occurred. + */ + @property ErrorCode errorCode() @nogc nothrow pure @safe + { + return valid ? ErrorCode() : this.storage.errorCode; + } + + /** + * Returns: Whether a valid, opened file is represented. + */ + @property bool valid() @nogc nothrow pure @safe + { + return this.status != Status.invalid; + } + + /** + * Transfers the file into invalid state. + * + * Returns: The old file handle. + */ + Handle reset() @nogc nothrow pure @safe + { + if (!valid) + { + return invalid; + } + auto oldHandle = handle; + + this.status = Status.invalid; + this.storage.errorCode = ErrorCode(); + + return oldHandle; + } + + /** + * Sets stream position in the file. + * + * Params: + * offset = File offset. + * whence = File position to add the offset to. + * + * Returns: Error code if any. + */ + ErrorCode seek(size_t offset, Whence whence) @nogc nothrow + { + if (!valid) + { + return ErrorCode(ErrorCode.ErrorNo.badDescriptor); + } + if (fseek(this.storage.handle, offset, whence)) + { + return ErrorCode(cast(ErrorCode.ErrorNo) errno); + } + return ErrorCode(); + } + + /** + * Returns: Current offset or an error. + */ + SumType!(ErrorCode, size_t) tell() @nogc nothrow + { + if (!valid) + { + return typeof(return)(ErrorCode(ErrorCode.ErrorNo.badDescriptor)); + } + auto result = ftell(this.storage.handle); + + if (result < 0) + { + return typeof(return)(ErrorCode(cast(ErrorCode.ErrorNo) errno)); + } + return typeof(return)(cast(size_t) result); + } + + /** + * Params: + * buffer = Destination buffer. + * + * Returns: Bytes read. $(D_PSYMBOL ErrorCode.ErrorNo.success) means that + * while reading the file an unknown error has occurred. + */ + SumType!(ErrorCode, size_t) read(ubyte[] buffer) @nogc nothrow + { + if (!valid) + { + return typeof(return)(ErrorCode(ErrorCode.ErrorNo.badDescriptor)); + } + const bytesRead = fread(buffer.ptr, 1, buffer.length, this.storage.handle); + if (bytesRead == buffer.length || eof()) + { + return typeof(return)(bytesRead); + } + return typeof(return)(ErrorCode()); + } + + /** + * Params: + * buffer = Source buffer. + * + * Returns: Bytes written. $(D_PSYMBOL ErrorCode.ErrorNo.success) means that + * while reading the file an unknown error has occurred. + */ + SumType!(ErrorCode, size_t) write(const(ubyte)[] buffer) @nogc nothrow + { + if (!valid) + { + return typeof(return)(ErrorCode(ErrorCode.ErrorNo.badDescriptor)); + } + const bytesWritten = fwrite(buffer.ptr, 1, buffer.length, this.storage.handle); + if (bytesWritten == buffer.length) + { + return typeof(return)(buffer.length); + } + return typeof(return)(ErrorCode()); + } + + /** + * Returns: EOF status of the file. + */ + bool eof() @nogc nothrow + { + return valid && feof(this.storage.handle) != 0; + } + + /** + * Constructs a file object that will be closed in the destructor. + * + * Params: + * filename = The file to open. + * + * Returns: Opened file or an error. + */ + static File open(const(char)* filename, BitFlags!Mode mode) @nogc nothrow + { + char[3] modeBuffer = "\0\0\0"; + + if (mode.truncate) + { + modeBuffer[0] = 'w'; + if (mode.read) + { + modeBuffer[1] = '+'; + } + } + else if (mode.append) + { + modeBuffer[0] = 'a'; + if (mode.read) + { + modeBuffer[1] = '+'; + } + } + else if (mode.read) + { + modeBuffer[0] = 'r'; + if (mode.write) + { + modeBuffer[1] = '+'; + } + } + + auto newHandle = fopen(filename, modeBuffer.ptr); + auto newFile = File(newHandle); + + if (newHandle is null) + { + newFile.status = Status.invalid; + newFile.storage.errorCode = ErrorCode(cast(ErrorCode.ErrorNo) errno); + } + else + { + if (mode == BitFlags!Mode(Mode.write)) + { + rewind(newHandle); + } + newFile.status = Status.owned; + } + + return newFile; + } +} + +/** + * Reads the whole file and returns its contents. + * + * Params: + * sourceFilename = Source filename. + * + * Returns: File contents or an error. + * + * See_Also: $(D_PSYMBOL File.read) + */ +SumType!(ErrorCode, Array!ubyte) readFile(String sourceFilename) @nogc +{ + enum size_t bufferSize = 255; + auto sourceFile = File.open(sourceFilename.toStringz, BitFlags!(File.Mode)(File.Mode.read)); + + if (!sourceFile.valid) + { + return typeof(return)(sourceFile.errorCode); + } + Array!ubyte sourceText; + size_t totalRead; + size_t bytesRead; + do + { + sourceText.length = sourceText.length + bufferSize; + const readStatus = sourceFile + .read(sourceText[totalRead .. $].get) + .match!( + (ErrorCode errorCode) => nullable(errorCode), + (size_t bytesRead_) { + bytesRead = bytesRead_; + return Nullable!ErrorCode(); + } + ); + if (!readStatus.isNull) + { + return typeof(return)(readStatus.get); + } + totalRead += bytesRead; + } + while (bytesRead == bufferSize); + + sourceText.length = totalRead; + + return typeof(return)(sourceText); } diff --git a/source/main.d b/source/main.d index 71d23cd..ccf390a 100644 --- a/source/main.d +++ b/source/main.d @@ -5,47 +5,43 @@ import elna.lexer; import elna.parser; import elna.generator; import elna.ir; +import elna.extended; +import std.sumtype; +import std.typecons; +import tanya.container.array; import tanya.container.string; import tanya.memory.allocator; import tanya.memory.mmappool; +import tanya.os.error; -private char[] readSource(size_t N)(string source, out char[N] buffer) @nogc +private Nullable!String readSource(string source) @nogc { - memcpy(buffer.ptr, source.ptr, source.length + 1); - buffer[source.length] = '\0'; - auto handle = fopen(buffer.ptr, "r"); - if (handle is null) - { - perror(buffer.ptr); - return null; - } - fseek(handle, 0, SEEK_END); - size_t fsize = ftell(handle); - rewind(handle); + enum size_t bufferSize = 255; + auto sourceFilename = String(source); - fread(buffer.ptr, fsize, 1, handle); - fclose(handle); - buffer[fsize] = '\0'; - - return buffer[0 .. fsize]; + return readFile(sourceFilename).match!( + (ErrorCode errorCode) { + perror(sourceFilename.toStringz); + return Nullable!String(); + }, + (Array!ubyte contents) => nullable(String(cast(char[]) contents.get)) + ); } int main(string[] args) { - char[255] buffer; - defaultAllocator = MmapPool.instance; if (args.length < 2) { return 4; } - auto sourceText = readSource(args[1], buffer); - if (sourceText is null) + auto sourceText = readSource(args[1]); + if (sourceText.isNull) { return 3; } - auto tokens = lex(sourceText); + auto tokens = lex(sourceText.get.get); if (tokens.length == 0) { printf("Lexical analysis failed.\n");