Core is now a package.

This commit is contained in:
Kapendev 2024-08-08 11:08:55 +03:00
parent 7bb8fb64e8
commit f0524a7105
25 changed files with 79 additions and 3530 deletions

3
.gitignore vendored
View file

@ -14,4 +14,5 @@ popka-test-*
*.o
*.obj
*.lst
libpopka*
lib*
dub.selections.json

View file

@ -70,12 +70,6 @@ For more info about exporting to web, read [this](#web-support).
For an initial understanding, the [examples](examples) folder and the [engine.d](source/popka/game/engine.d) file can be a good starting point.
You can also read the [TOUR.md](TOUR.md) file for a more in-depth overview of the engine's functionalities.
## Project Layout
* [core](source/popka/core): A standard library designed specifically for game development.
* [vendor](source/popka/vendor): A collection of third-party libraries.
* [game](source/popka/game): A set of tools for creating 2D games.
## Attributes and BetterC Support
This project offers support for some attributes (`@safe`, `@nogc`, `nothrow`) and aims for good compatibility with BetterC.
@ -97,7 +91,7 @@ Additionally, it provides helper functions to reduce some boilerplate code.
All the helper functions are inside the [raylibpp.d](source/popka/vendor/ray/raylibpp.d) file.
```d
import popka.vendor.ray;
import popka.ray;
bool rayLoop() {
BeginDrawing();

View file

@ -2,7 +2,8 @@
## NOW
Cleaning done.
It's time to clean popka...
I do this because I am thinking of taking part in a game jam.
## IDEAS

View file

@ -6,6 +6,9 @@
"description": "A lightweight and beginner-friendly 2D game engine for the D programming language.",
"license": "MIT",
"name": "popka",
"dependencies": {
"joka": "*"
},
"configurations": [
{
"name": "pdefault",

View file

@ -25,57 +25,58 @@ enum webDir = buildPath(".", "web");
enum defaultDUBContent = `{
"name" : "game",
"description" : "A game made with Popka.",
"authors" : ["Name"],
"copyright" : "Copyright © 2024, Name",
"license" : "proprietary",
"dependencies": {
"popka": "*"
},
"configurations": [
{
"name": "linux",
"targetType": "executable",
"platforms": ["linux"],
"dflags": ["-i"],
"lflags": ["-L.", "-rpath=$$ORIGIN"],
"libs": [
"raylib",
"GL",
"m",
"pthread",
"dl",
"rt",
"X11"
]
"name" : "game",
"description" : "A game made with Popka.",
"authors" : ["Name"],
"copyright" : "Copyright © 2024, Name",
"license" : "proprietary",
"dependencies": {
"joka": "*",
"popka": "*"
},
{
"name": "windows",
"targetType": "executable",
"platforms": ["windows"],
"dflags": ["-i"],
"libs": [
"raylib"
]
},
{
"name": "osx",
"targetType": "executable",
"platforms": ["osx"],
"dflags": ["-i"],
"lflags": ["-L.", "-rpath", "@executable_path/"],
"libs": [
"raylib.500"
]
},
{
"name": "web",
"targetType": "staticLibrary",
"targetName": "webgame",
"dflags": ["-mtriple=wasm32-unknown-unknown-wasm", "-checkaction=halt", "-betterC", "--release", "-i"]
}
]
"configurations": [
{
"name": "linux",
"targetType": "executable",
"platforms": ["linux"],
"dflags": ["-i"],
"lflags": ["-L.", "-rpath=$$ORIGIN"],
"libs": [
"raylib",
"GL",
"m",
"pthread",
"dl",
"rt",
"X11"
]
},
{
"name": "windows",
"targetType": "executable",
"platforms": ["windows"],
"dflags": ["-i"],
"libs": [
"raylib"
]
},
{
"name": "osx",
"targetType": "executable",
"platforms": ["osx"],
"dflags": ["-i"],
"lflags": ["-L.", "-rpath", "@executable_path/"],
"libs": [
"raylib.500"
]
},
{
"name": "web",
"targetType": "staticLibrary",
"targetName": "webgame",
"dflags": ["-mtriple=wasm32-unknown-unknown-wasm", "-checkaction=halt", "-betterC", "--release", "-i"]
}
]
}
`;

View file

@ -1,832 +0,0 @@
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
/// The `ascii` module provides functions designed to assist with ascii strings.
module popka.core.ascii;
import popka.core.containers;
import popka.core.traits;
import popka.core.types;
public import popka.core.faults;
@safe @nogc nothrow:
enum digitChars = "0123456789";
enum upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
enum lowerChars = "abcdefghijklmnopqrstuvwxyz";
enum alphaChars = upperChars ~ lowerChars;
enum spaceChars = " \t\v\r\n\f";
version (Windows) {
enum pathSep = '\\';
enum otherPathSep = '/';
} else {
enum pathSep = '/';
enum otherPathSep = '\\';
}
struct ToStrOptions {
ubyte doublePrecision = 2;
}
bool isDigit(char c) {
return c >= '0' && c <= '9';
}
bool isDigit(IStr str) {
foreach (c; str) {
if (!isDigit(c)) return false;
}
return true;
}
bool isUpper(char c) {
return c >= 'A' && c <= 'Z';
}
bool isUpper(IStr str) {
foreach (c; str) {
if (!isUpper(c)) return false;
}
return true;
}
bool isLower(char c) {
return c >= 'a' && c <= 'z';
}
bool isLower(IStr str) {
foreach (c; str) {
if (!isLower(c)) return false;
}
return true;
}
bool isAlpha(char c) {
return isLower(c) || isUpper(c);
}
bool isAlpha(IStr str) {
foreach (c; str) {
if (!isAlpha(c)) return false;
}
return true;
}
bool isSpace(char c) {
foreach (sc; spaceChars) {
if (c == sc) return true;
}
return false;
}
bool isSpace(IStr str) {
foreach (c; str) {
if (!isSpace(c)) return false;
}
return true;
}
bool isCStr(IStr str) {
return str.length != 0 && str[$ - 1] == '\0';
}
char toUpper(char c) {
return isLower(c) ? cast(char) (c - 32) : c;
}
void toUpper(Str str) {
foreach (ref c; str) {
c = toUpper(c);
}
}
char toLower(char c) {
return isUpper(c) ? cast(char) (c + 32) : c;
}
void toLower(Str str) {
foreach (ref c; str) {
c = toLower(c);
}
}
@trusted
Sz length(ICStr str) {
Sz result = 0;
while (str[result] != '\0') {
result += 1;
}
return result;
}
bool equals(IStr str, IStr other) {
return str == other;
}
bool equals(IStr str, char other) {
return equals(str, charToStr(other));
}
bool equalsNoCase(IStr str, IStr other) {
if (str.length != other.length) return false;
foreach (i; 0 .. str.length) {
if (toUpper(str[i]) != toUpper(other[i])) return false;
}
return true;
}
bool equalsNoCase(IStr str, char other) {
return equalsNoCase(str, charToStr(other));
}
bool startsWith(IStr str, IStr start) {
if (str.length < start.length) return false;
return str[0 .. start.length] == start;
}
bool startsWith(IStr str, char start) {
return startsWith(str, charToStr(start));
}
bool endsWith(IStr str, IStr end) {
if (str.length < end.length) return false;
return str[$ - end.length .. $] == end;
}
bool endsWith(IStr str, char end) {
return endsWith(str, charToStr(end));
}
int count(IStr str, IStr item) {
int result = 0;
if (str.length < item.length || item.length == 0) return result;
foreach (i; 0 .. str.length - item.length) {
if (str[i .. i + item.length] == item) {
result += 1;
i += item.length - 1;
}
}
return result;
}
int count(IStr str, char item) {
return count(str, charToStr(item));
}
int findStart(IStr str, IStr item) {
if (str.length < item.length || item.length == 0) return -1;
foreach (i; 0 .. str.length - item.length + 1) {
if (str[i .. i + item.length] == item) return cast(int) i;
}
return -1;
}
int findStart(IStr str, char item) {
return findStart(str, charToStr(item));
}
int findEnd(IStr str, IStr item) {
if (str.length < item.length || item.length == 0) return -1;
foreach_reverse (i; 0 .. str.length - item.length + 1) {
if (str[i .. i + item.length] == item) return cast(int) i;
}
return -1;
}
int findEnd(IStr str, char item) {
return findEnd(str, charToStr(item));
}
IStr trimStart(IStr str) {
IStr result = str;
while (result.length > 0) {
if (isSpace(result[0])) result = result[1 .. $];
else break;
}
return result;
}
IStr trimEnd(IStr str) {
IStr result = str;
while (result.length > 0) {
if (isSpace(result[$ - 1])) result = result[0 .. $ - 1];
else break;
}
return result;
}
IStr trim(IStr str) {
return str.trimStart().trimEnd();
}
IStr removePrefix(IStr str, IStr prefix) {
if (str.startsWith(prefix)) {
return str[prefix.length .. $];
} else {
return str;
}
}
IStr removeSuffix(IStr str, IStr suffix) {
if (str.endsWith(suffix)) {
return str[0 .. $ - suffix.length];
} else {
return str;
}
}
IStr advance(IStr str, Sz amount) {
if (str.length < amount) {
return str[$ .. $];
} else {
return str[amount .. $];
}
}
void copyChars(Str str, IStr source, Sz startIndex = 0) {
if (str.length < source.length) {
assert(0, "Destination string `{}` must be at least as long as the source string `{}`.".format(str, source));
}
foreach (i, c; source) {
str[startIndex + i] = c;
}
}
void copy(ref Str str, IStr source, Sz startIndex = 0) {
copyChars(str, source, startIndex);
str = str[0 .. startIndex + source.length];
}
IStr pathDir(IStr path) {
auto end = findEnd(path, pathSep);
if (end == -1) {
return ".";
} else {
return path[0 .. end];
}
}
IStr pathConcat(IStr[] args...) {
static char[1024][4] buffers = void;
static byte bufferIndex = 0;
if (args.length == 0) {
return ".";
}
bufferIndex = (bufferIndex + 1) % buffers.length;
auto result = buffers[bufferIndex][];
auto length = 0;
foreach (i, arg; args) {
result.copyChars(arg, length);
length += arg.length;
if (i != args.length - 1) {
result.copyChars(charToStr(pathSep), length);
length += 1;
}
}
result = result[0 .. length];
return result;
}
IStr skipValue(ref inout(char)[] str, IStr separator) {
if (str.length < separator.length || separator.length == 0) {
str = str[$ .. $];
return "";
}
foreach (i; 0 .. str.length - separator.length) {
if (str[i .. i + separator.length] == separator) {
auto line = str[0 .. i];
str = str[i + separator.length .. $];
return line;
}
}
auto line = str[0 .. $];
if (str[$ - separator.length .. $] == separator) {
line = str[0 .. $ - 1];
}
str = str[$ .. $];
return line;
}
IStr skipValue(ref inout(char)[] str, char separator) {
return skipValue(str, charToStr(separator));
}
IStr skipLine(ref inout(char)[] str) {
return skipValue(str, '\n');
}
IStr boolToStr(bool value) {
return value ? "true" : "false";
}
IStr charToStr(char value) {
static char[1] buffer = void;
auto result = buffer[];
result[0] = value;
result = result[0 .. 1];
return result;
}
IStr unsignedToStr(ulong value) {
static char[64] buffer = void;
auto result = buffer[];
if (value == 0) {
result[0] = '0';
result = result[0 .. 1];
} else {
auto digitCount = 0;
for (auto temp = value; temp != 0; temp /= 10) {
result[$ - 1 - digitCount] = (temp % 10) + '0';
digitCount += 1;
}
result = result[$ - digitCount .. $];
}
return result;
}
IStr signedToStr(long value) {
static char[64] buffer = void;
auto result = buffer[];
if (value < 0) {
auto temp = unsignedToStr(-value);
result[0] = '-';
result.copy(temp, 1);
} else {
auto temp = unsignedToStr(value);
result.copy(temp, 0);
}
return result;
}
IStr doubleToStr(double value, ulong precision = 2) {
static char[64] buffer = void;
if (precision == 0) {
return signedToStr(cast(long) value);
}
auto result = buffer[];
auto cleanNumber = value;
auto rightDigitCount = 0;
while (cleanNumber != cast(double) (cast(long) cleanNumber)) {
rightDigitCount += 1;
cleanNumber *= 10;
}
// Add extra zeros at the end if needed.
// I do this because it makes it easier to remove the zeros later.
if (precision > rightDigitCount) {
foreach (j; 0 .. precision - rightDigitCount) {
rightDigitCount += 1;
cleanNumber *= 10;
}
}
// Digits go in the buffer from right to left.
auto cleanNumberStr = signedToStr(cast(long) cleanNumber);
auto i = result.length;
// Check two cases: 0.NN, N.NN
if (cast(long) value == 0) {
i -= cleanNumberStr.length;
result.copyChars(cleanNumberStr, i);
foreach (j; 0 .. rightDigitCount - cleanNumberStr.length) {
i -= 1;
result[i] = '0';
}
i -= 2;
result.copyChars("0.", i);
} else {
i -= rightDigitCount;
result.copyChars(cleanNumberStr[$ - rightDigitCount .. $], i);
i -= 1;
result[i] = '.';
i -= cleanNumberStr.length - rightDigitCount;
result.copyChars(cleanNumberStr[0 .. $ - rightDigitCount], i);
}
// Remove extra zeros at the end if needed.
if (precision < rightDigitCount) {
result = result[0 .. $ - rightDigitCount + precision];
}
return result[i .. $];
}
@trusted
IStr cStrToStr(ICStr value) {
return value[0 .. value.length];
}
IStr enumToStr(T)(T value) {
switch (value) {
static foreach (member; __traits(allMembers, T)) {
mixin("case T." ~ member ~ ": return member;");
}
default: assert(0, "WTF!");
}
}
IStr toStr(T)(T value, ToStrOptions options = ToStrOptions()) {
static if (isCharType!T) {
return charToStr(value);
} else static if (isBoolType!T) {
return boolToStr(value);
} else static if (isUnsignedType!T) {
return unsignedToStr(value);
} else static if (isSignedType!T) {
return signedToStr(value);
} else static if (isFloatingType!T) {
return doubleToStr(value, options.doublePrecision);
} else static if (isStrType!T) {
return value;
} else static if (isCStrType!T) {
return cStrToStr(value);
} else static if (isEnumType!T) {
return enumToStr(value);
} else static if (__traits(hasMember, T, "toStr")) {
return value.toStr();
} else {
static assert(0, funcImplementationErrorMessage!(T, "toStr"));
}
}
Result!bool toBool(IStr str) {
if (str == "false") {
return Result!bool(false);
} else if (str == "true") {
return Result!bool(true);
} else {
return Result!bool(Fault.invalid);
}
}
Result!ulong toUnsigned(IStr str) {
if (str.length == 0 || str.length >= 18) {
return Result!ulong(Fault.invalid);
} else {
if (str.length == 1 && str[0] == '+') {
return Result!ulong(Fault.invalid);
}
ulong value = 0;
ulong level = 1;
foreach_reverse (i, c; str[(str[0] == '+' ? 1 : 0) .. $]) {
if (isDigit(c)) {
value += (c - '0') * level;
level *= 10;
} else {
return Result!ulong(Fault.invalid);
}
}
return Result!ulong(value);
}
}
Result!ulong toUnsigned(char c) {
if (isDigit(c)) {
return Result!ulong(c - '0');
} else {
return Result!ulong(Fault.invalid);
}
}
Result!long toSigned(IStr str) {
if (str.length == 0 || str.length >= 18) {
return Result!long(Fault.invalid);
} else {
auto temp = toUnsigned(str[(str[0] == '-' ? 1 : 0) .. $]);
return Result!long(str[0] == '-' ? -temp.value : temp.value, temp.fault);
}
}
Result!long toSigned(char c) {
if (isDigit(c)) {
return Result!long(c - '0');
} else {
return Result!long(Fault.invalid);
}
}
Result!double toDouble(IStr str) {
auto dotIndex = findStart(str, '.');
if (dotIndex == -1) {
auto temp = toSigned(str);
return Result!double(temp.value, temp.fault);
} else {
auto left = toSigned(str[0 .. dotIndex]);
auto right = toSigned(str[dotIndex + 1 .. $]);
if (left.isNone || right.isNone) {
return Result!double(Fault.invalid);
} else if (str[dotIndex + 1] == '-' || str[dotIndex + 1] == '+') {
return Result!double(Fault.invalid);
} else {
auto sign = str[0] == '-' ? -1 : 1;
auto level = 10;
foreach (i; 1 .. str[dotIndex + 1 .. $].length) {
level *= 10;
}
return Result!double(left.value + sign * (right.value / (cast(double) level)));
}
}
}
Result!double toDouble(char c) {
if (isDigit(c)) {
return Result!double(c - '0');
} else {
return Result!double(Fault.invalid);
}
}
Result!T toEnum(T)(IStr str) {
switch (str) {
static foreach (member; __traits(allMembers, T)) {
mixin("case " ~ member.stringof ~ ": return Result!T(T." ~ member ~ ");");
}
default: return Result!T(Fault.invalid);
}
}
@trusted
Result!ICStr toCStr(IStr str) {
static char[1024] buffer = void;
if (buffer.length < str.length) {
return Result!ICStr(Fault.invalid);
} else {
auto value = buffer[];
value.copyChars(str);
value[str.length] = '\0';
return Result!ICStr(value.ptr);
}
}
// TODO: Check if the args count is the same with the `{}` count and also add extra stuff.
IStr format(A...)(IStr formatStr, A args) {
static char[1024][8] buffers = void;
static byte bufferIndex = 0;
bufferIndex = (bufferIndex + 1) % buffers.length;
auto result = buffers[bufferIndex][];
auto resultIndex = 0;
auto formatStrIndex = 0;
auto argIndex = 0;
while (formatStrIndex < formatStr.length) {
auto c1 = formatStr[formatStrIndex];
auto c2 = formatStrIndex + 1 >= formatStr.length ? '+' : formatStr[formatStrIndex + 1];
if (c1 == '{' && c2 == '}' && argIndex < args.length) {
static foreach (i, arg; args) {
if (i == argIndex) {
auto temp = toStr(arg);
foreach (i, c; temp) {
result[resultIndex + i] = c;
}
resultIndex += temp.length;
formatStrIndex += 2;
argIndex += 1;
goto loopExit;
}
}
loopExit:
} else {
result[resultIndex] = c1;
resultIndex += 1;
formatStrIndex += 1;
}
}
result = result[0 .. resultIndex];
return result;
}
// TODO: Check if the args count is the same with the `{}` count and also add extra stuff.
void formatl(A...)(ref LStr text, IStr formatStr, A args) {
text.clear();
auto formatStrIndex = 0;
auto argIndex = 0;
while (formatStrIndex < formatStr.length) {
auto c1 = formatStr[formatStrIndex];
auto c2 = formatStrIndex + 1 >= formatStr.length ? '+' : formatStr[formatStrIndex + 1];
if (c1 == '{' && c2 == '}' && argIndex < args.length) {
static foreach (i, arg; args) {
if (i == argIndex) {
auto temp = toStr(arg);
foreach (i, c; temp) {
text.append(c);
}
formatStrIndex += 2;
argIndex += 1;
goto loopExit;
}
}
loopExit:
} else {
text.append(c1);
formatStrIndex += 1;
}
}
}
// Function test.
@trusted
unittest {
enum TestEnum {
one,
two,
}
char[128] buffer = void;
Str str;
assert(isDigit("0123456789?") == false);
assert(isDigit("0123456789") == true);
assert(isUpper("hello") == false);
assert(isUpper("HELLO") == true);
assert(isLower("HELLO") == false);
assert(isLower("hello") == true);
assert(isSpace(" \t\r\n ") == true);
assert(isCStr("hello") == false);
assert(isCStr("hello\0") == true);
str = buffer[];
str.copy("Hello");
assert(str == "Hello");
str.toUpper();
assert(str == "HELLO");
str.toLower();
assert(str == "hello");
str = buffer[];
str.copy("Hello\0");
assert(isCStr(str) == true);
assert(str.ptr.length + 1 == str.length);
str = buffer[];
str.copy("Hello");
assert(str.equals("HELLO") == false);
assert(str.equalsNoCase("HELLO") == true);
assert(str.startsWith("H") == true);
assert(str.startsWith("Hell") == true);
assert(str.startsWith("Hello") == true);
assert(str.endsWith("o") == true);
assert(str.endsWith("ello") == true);
assert(str.endsWith("Hello") == true);
str = buffer[];
str.copy("hello hello world.");
assert(str.count("hello") == 2);
assert(str.findStart("HELLO") == -1);
assert(str.findStart("hello") == 0);
assert(str.findEnd("HELLO") == -1);
assert(str.findEnd("hello") == 6);
str = buffer[];
str.copy(" Hello world. ");
assert(str.trimStart() == "Hello world. ");
assert(str.trimEnd() == " Hello world.");
assert(str.trim() == "Hello world.");
assert(str.removePrefix("Hello") == str);
assert(str.trim().removePrefix("Hello") == " world.");
assert(str.removeSuffix("world.") == str);
assert(str.trim().removeSuffix("world.") == "Hello ");
assert(str.advance(0) == str);
assert(str.advance(1) == str[1 .. $]);
assert(str.advance(str.length) == "");
assert(str.advance(str.length + 1) == "");
assert(pathConcat("one", "two").pathDir() == "one");
assert(pathConcat("one").pathDir() == ".");
str = buffer[];
str.copy("one, two ,three,");
assert(skipValue(str, ',') == "one");
assert(skipValue(str, ',') == " two ");
assert(skipValue(str, ',') == "three");
assert(skipValue(str, ',') == "");
assert(str.length == 0);
assert(boolToStr(false) == "false");
assert(boolToStr(true) == "true");
assert(charToStr('L') == "L");
assert(unsignedToStr(0) == "0");
assert(unsignedToStr(69) == "69");
assert(signedToStr(0) == "0");
assert(signedToStr(69) == "69");
assert(signedToStr(-69) == "-69");
assert(signedToStr(-69) == "-69");
assert(doubleToStr(0.00, 0) == "0");
assert(doubleToStr(0.00, 1) == "0.0");
assert(doubleToStr(0.00, 2) == "0.00");
assert(doubleToStr(0.00, 3) == "0.000");
assert(doubleToStr(0.60, 1) == "0.6");
assert(doubleToStr(0.60, 2) == "0.60");
assert(doubleToStr(0.60, 3) == "0.600");
assert(doubleToStr(0.09, 1) == "0.0");
assert(doubleToStr(0.09, 2) == "0.09");
assert(doubleToStr(0.09, 3) == "0.090");
assert(doubleToStr(69.0, 1) == "69.0");
assert(doubleToStr(69.0, 2) == "69.00");
assert(doubleToStr(69.0, 3) == "69.000");
assert(cStrToStr("Hello\0") == "Hello");
assert(enumToStr(TestEnum.one) == "one");
assert(enumToStr(TestEnum.two) == "two");
assert(toBool("F").isSome == false);
assert(toBool("F").unwrapOr() == false);
assert(toBool("T").isSome == false);
assert(toBool("T").unwrapOr() == false);
assert(toBool("false").isSome == true);
assert(toBool("false").unwrapOr() == false);
assert(toBool("true").isSome == true);
assert(toBool("true").unwrapOr() == true);
assert(toUnsigned("1_069").isSome == false);
assert(toUnsigned("1_069").unwrapOr() == 0);
assert(toUnsigned("+1069").isSome == true);
assert(toUnsigned("+1069").unwrapOr() == 1069);
assert(toUnsigned("1069").isSome == true);
assert(toUnsigned("1069").unwrapOr() == 1069);
assert(toUnsigned('+').isSome == false);
assert(toUnsigned('+').unwrapOr() == 0);
assert(toUnsigned('0').isSome == true);
assert(toUnsigned('0').unwrapOr() == 0);
assert(toUnsigned('9').isSome == true);
assert(toUnsigned('9').unwrapOr() == 9);
assert(toSigned("1_069").isSome == false);
assert(toSigned("1_069").unwrapOr() == 0);
assert(toSigned("-1069").isSome == true);
assert(toSigned("-1069").unwrapOr() == -1069);
assert(toSigned("+1069").isSome == true);
assert(toSigned("+1069").unwrapOr() == 1069);
assert(toSigned("1069").isSome == true);
assert(toSigned("1069").unwrapOr() == 1069);
assert(toSigned('+').isSome == false);
assert(toSigned('+').unwrapOr() == 0);
assert(toSigned('0').isSome == true);
assert(toSigned('0').unwrapOr() == 0);
assert(toSigned('9').isSome == true);
assert(toSigned('9').unwrapOr() == 9);
assert(toDouble("1_069").isSome == false);
assert(toDouble("1_069").unwrapOr() == 0);
assert(toDouble(".1069").isSome == false);
assert(toDouble(".1069").unwrapOr() == 0);
assert(toDouble("1069.").isSome == false);
assert(toDouble("1069.").unwrapOr() == 0);
assert(toDouble(".").isSome == false);
assert(toDouble(".").unwrapOr() == 0);
assert(toDouble("-1069.-69").isSome == false);
assert(toDouble("-1069.-69").unwrapOr() == 0);
assert(toDouble("-1069.+69").isSome == false);
assert(toDouble("-1069.+69").unwrapOr() == 0);
assert(toDouble("-1069").isSome == true);
assert(toDouble("-1069").unwrapOr() == -1069);
assert(toDouble("+1069").isSome == true);
assert(toDouble("+1069").unwrapOr() == 1069);
assert(toDouble("1069").isSome == true);
assert(toDouble("1069").unwrapOr() == 1069);
assert(toDouble("1069.0").isSome == true);
assert(toDouble("1069.0").unwrapOr() == 1069);
assert(toDouble("-1069.0095").isSome == true);
assert(toDouble("-1069.0095").unwrapOr() == -1069.0095);
assert(toDouble("+1069.0095").isSome == true);
assert(toDouble("+1069.0095").unwrapOr() == 1069.0095);
assert(toDouble("1069.0095").isSome == true);
assert(toDouble("1069.0095").unwrapOr() == 1069.0095);
assert(toDouble("-0.0095").isSome == true);
assert(toDouble("-0.0095").unwrapOr() == -0.0095);
assert(toDouble("+0.0095").isSome == true);
assert(toDouble("+0.0095").unwrapOr() == 0.0095);
assert(toDouble("0.0095").isSome == true);
assert(toDouble("0.0095").unwrapOr() == 0.0095);
assert(toDouble('+').isSome == false);
assert(toDouble('+').unwrapOr() == 0);
assert(toDouble('0').isSome == true);
assert(toDouble('0').unwrapOr() == 0);
assert(toDouble('9').isSome == true);
assert(toDouble('9').unwrapOr() == 9);
assert(toEnum!TestEnum("?").isSome == false);
assert(toEnum!TestEnum("?").unwrapOr() == TestEnum.one);
assert(toEnum!TestEnum("one").isSome == true);
assert(toEnum!TestEnum("one").unwrapOr() == TestEnum.one);
assert(toEnum!TestEnum("two").isSome == true);
assert(toEnum!TestEnum("two").unwrapOr() == TestEnum.two);
assert(toCStr("Hello").unwrapOr().length == "Hello".length);
assert(toCStr("Hello").unwrapOr().cStrToStr() == "Hello");
// TODO: Write tests for `format` when it is done.
}

View file

@ -1,95 +0,0 @@
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
/// The `colors` module provides color-related types and functions.
module popka.core.colors;
import popka.core.ascii;
import popka.core.traits;
import popka.core.types;
@safe @nogc nothrow:
enum blank = Color();
enum black = Color(0);
enum white = Color(255);
enum red = Color(255, 0, 0);
enum green = Color(0, 255, 0);
enum blue = Color(0, 0, 255);
enum yellow = Color(255, 255, 0);
enum magenta = Color(255, 0, 255);
enum cyan = Color(0, 255, 255);
enum gray1 = toRgb(0x202020);
enum gray2 = toRgb(0x606060);
enum gray3 = toRgb(0x9f9f9f);
enum gray4 = toRgb(0xdfdfdf);
alias gray = gray1;
struct Color {
ubyte r;
ubyte g;
ubyte b;
ubyte a;
enum length = 4;
enum zero = Color(0, 0, 0, 0);
enum one = Color(1, 1, 1, 1);
@safe @nogc nothrow:
pragma(inline, true)
this(ubyte r, ubyte g, ubyte b, ubyte a = 255) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
pragma(inline, true)
this(ubyte r) {
this(r, r, r, 255);
}
mixin addRgbaOps!(Color, length);
Color alpha(ubyte a) {
return Color(r, g, b, a);
}
IStr toStr() {
return "({} {} {} {})".format(r, g, b, a);
}
}
Color toRgb(uint rgb) {
return Color(
(rgb & 0xFF0000) >> 16,
(rgb & 0xFF00) >> 8,
(rgb & 0xFF),
);
}
Color toRgba(uint rgba) {
return Color(
(rgba & 0xFF000000) >> 24,
(rgba & 0xFF0000) >> 16,
(rgba & 0xFF00) >> 8,
(rgba & 0xFF),
);
}
unittest {
assert(toRgb(0xff0000) == red);
assert(toRgb(0x00ff00) == green);
assert(toRgb(0x0000ff) == blue);
assert(toRgba(0xff0000ff) == red);
assert(toRgba(0x00ff00ff) == green);
assert(toRgba(0x0000ffff) == blue);
assert(black.toStr() == "(0 0 0 255)");
assert(black.alpha(69).toStr() == "(0 0 0 69)");
}

View file

@ -1,597 +0,0 @@
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
/// The `containers` module provides various data structures.
module popka.core.containers;
import popka.core.ascii;
import popka.core.stdc;
import popka.core.types;
@safe @nogc nothrow:
enum defaultListCapacity = 64;
alias LStr = List!char;
alias LStr16 = List!wchar;
alias LStr32 = List!dchar;
struct List(T) {
T[] items;
Sz capacity;
@safe @nogc nothrow:
this(const(T)[] args...) {
foreach (arg; args) {
append(arg);
}
}
this(List!T list) {
foreach (item; list.items) {
append(item);
}
}
this(FlagList!T list) {
foreach (item; list.items) {
append(item);
}
}
T[] opSlice(Sz dimension)(Sz i, Sz j) {
return items[i .. j];
}
T[] opIndex() {
return items[];
}
// D calls this function when the slice operator is used. Does something but I do not remember what lol.
T[] opIndex(T[] slice) {
return slice;
}
// D will let you get the pointer of the array item if you return a ref value.
ref T opIndex(Sz i) {
return items[i];
}
@trusted
void opIndexAssign(const(T) rhs, Sz i) {
items[i] = cast(T) rhs;
}
@trusted
void opIndexOpAssign(IStr op)(const(T) rhs, Sz i) {
mixin("items[i] " ~ op ~ "= cast(T) rhs;");
}
bool opEquals(List!T rhs) {
return items == rhs.items;
}
@trusted
bool opEquals(const(T)[] rhs) {
return items == cast(T[]) rhs;
}
Sz opDollar(Sz dimension)() {
return items.length;
}
Sz length() {
return items.length;
}
@trusted
T* ptr() {
return items.ptr;
}
@trusted
void append(const(T)[] args...) {
foreach (arg; args) {
Sz newLength = length + 1;
if (newLength > capacity) {
capacity = findListCapacity(newLength);
items = (cast(T*) realloc(items.ptr, capacity * T.sizeof))[0 .. newLength];
} else {
items = items.ptr[0 .. newLength];
}
items[$ - 1] = cast(T) arg;
}
}
void remove(Sz i) {
items[i] = items[$ - 1];
items = items[0 .. $ - 1];
}
T pop() {
if (length > 0) {
T temp = items[$ - 1];
remove(length - 1);
return temp;
} else {
return T.init;
}
}
@trusted
void reserve(Sz capacity) {
auto targetCapacity = findListCapacity(capacity);
if (targetCapacity > this.capacity) {
this.capacity = targetCapacity;
items = (cast(T*) realloc(items.ptr, this.capacity * T.sizeof))[0 .. length];
}
}
void resize(Sz length) {
if (length <= this.length) {
items = items[0 .. length];
} else {
reserve(length);
foreach (i; 0 .. length - this.length) {
append(T.init);
}
}
}
@trusted
void fill(const(T) value) {
foreach (ref item; items) {
item = cast(T) value;
}
}
void clear() {
items = items[0 .. 0];
}
@trusted
void free() {
.free(items.ptr);
items = [];
capacity = 0;
}
}
struct FlagList(T) {
List!T data;
List!bool flags;
Sz hotIndex;
Sz openIndex;
Sz length;
@safe @nogc nothrow:
this(const(T)[] args...) {
foreach (arg; args) {
append(arg);
}
}
this(List!T list) {
foreach (item; list.items) {
append(item);
}
}
this(FlagList!T list) {
data.resize(list.data.length);
flags.resize(list.flags.length);
foreach (i; 0 .. flags.length) {
data[i] = list.data[i];
flags[i] = list.flags[i];
}
hotIndex = list.hotIndex;
openIndex = list.openIndex;
length = list.length;
}
ref T opIndex(Sz i) {
if (!has(i)) {
assert(0, "ID `[{}]` does not exist.".format(i));
}
return data[i];
}
@trusted
void opIndexAssign(const(T) rhs, Sz i) {
if (!has(i)) {
assert(0, "ID `[{}]` does not exist.".format(i));
}
data[i] = cast(T) rhs;
}
@trusted
void opIndexOpAssign(IStr op)(const(T) rhs, Sz i) {
if (!has(i)) {
assert(0, "ID `[{}]` does not exist.".format(i));
}
mixin("data[i] ", op, "= cast(T) rhs;");
}
Sz capacity() {
return data.capacity;
}
@trusted
T* ptr() {
return data.ptr;
}
bool has(Sz id) {
return id < flags.length && flags[id];
}
@trusted
void append(const(T)[] args...) {
foreach (arg; args) {
if (openIndex == flags.length) {
data.append(arg);
flags.append(true);
hotIndex = openIndex;
openIndex = flags.length;
length += 1;
} else {
auto isFull = true;
foreach (i; openIndex .. flags.length) {
if (!flags[i]) {
data[i] = arg;
flags[i] = true;
hotIndex = i;
openIndex = i;
isFull = false;
break;
}
}
if (isFull) {
data.append(arg);
flags.append(true);
hotIndex = flags.length - 1;
openIndex = flags.length;
}
length += 1;
}
}
}
void remove(Sz i) {
if (!has(i)) {
assert(0, "ID `[{}]` does not exist.".format(i));
}
flags[i] = false;
hotIndex = i;
if (i < openIndex) {
openIndex = i;
}
length -= 1;
}
@trusted
void fill(const(T) value) {
foreach (ref item; items) {
item = cast(T) value;
}
}
void clear() {
data.clear();
flags.clear();
hotIndex = 0;
openIndex = 0;
length = 0;
}
void free() {
data.free();
flags.free();
hotIndex = 0;
openIndex = 0;
length = 0;
}
auto ids() {
struct Range {
bool[] flags;
Sz id;
bool empty() {
return id == flags.length;
}
Sz front() {
return id;
}
void popFront() {
id += 1;
while (id != flags.length && !flags[id]) {
id += 1;
}
}
}
Sz id = 0;
while (id < flags.length && !flags[id]) {
id += 1;
}
return Range(flags.items, id);
}
auto items() {
struct Range {
T[] data;
bool[] flags;
Sz id;
bool empty() {
return id == flags.length;
}
ref T front() {
return data[id];
}
void popFront() {
id += 1;
while (id != flags.length && !flags[id]) {
id += 1;
}
}
}
Sz id = 0;
while (id < flags.length && !flags[id]) {
id += 1;
}
return Range(data.items, flags.items, id);
}
}
struct Grid(T) {
List!T tiles;
Sz rowCount;
Sz colCount;
@safe @nogc nothrow:
this(Sz rowCount, Sz colCount) {
resize(rowCount, colCount);
}
T[] opIndex() {
return tiles[];
}
ref T opIndex(Sz row, Sz col) {
if (!has(row, col)) {
assert(0, "Tile `[{}, {}]` does not exist.".format(row, col));
}
return tiles[colCount * row + col];
}
void opIndexAssign(T rhs, Sz row, Sz col) {
if (!has(row, col)) {
assert(0, "Tile `[{}, {}]` does not exist.".format(row, col));
}
tiles[colCount * row + col] = rhs;
}
void opIndexOpAssign(IStr op)(T rhs, Sz row, Sz col) {
if (!has(row, col)) {
assert(0, "Tile `[{}, {}]` does not exist.".format(row, col));
}
mixin("tiles[colCount * row + col] " ~ op ~ "= rhs;");
}
Sz opDollar(Sz dimension)() {
static if (dimension == 0) {
return rowCount;
} else static if (dimension == 1) {
return colCount;
} else {
assert(0, "WTF!");
}
}
Sz length() {
return tiles.length;
}
Sz capacity() {
return tiles.capacity;
}
@trusted
T* ptr() {
return tiles.ptr;
}
bool has(Sz row, Sz col) {
return row < rowCount && col < colCount;
}
void resize(Sz rowCount, Sz colCount) {
this.tiles.resize(rowCount * colCount);
this.rowCount = rowCount;
this.colCount = colCount;
}
void fill(T value) {
tiles.fill(value);
}
void clear() {
tiles.clear();
rowCount = 0;
colCount = 0;
}
void free() {
tiles.free();
rowCount = 0;
colCount = 0;
}
}
Sz findListCapacity(Sz length) {
Sz result = defaultListCapacity;
while (result < length) {
result *= 2;
}
return result;
}
// Function test.
unittest {
assert(findListCapacity(0) == defaultListCapacity);
assert(findListCapacity(defaultListCapacity) == defaultListCapacity);
assert(findListCapacity(defaultListCapacity + 1) == defaultListCapacity * 2);
}
// List test.
unittest {
LStr text;
text = LStr();
assert(text.length == 0);
assert(text.capacity == 0);
assert(text.ptr == null);
text = LStr("abc");
assert(text.length == 3);
assert(text.capacity == defaultListCapacity);
assert(text.ptr != null);
text.free();
assert(text.length == 0);
assert(text.capacity == 0);
assert(text.ptr == null);
text = LStr("Hello world!");
assert(text.length == "Hello world!".length);
assert(text.capacity == defaultListCapacity);
assert(text.ptr != null);
assert(text[] == text.items);
assert(text[0] == text.items[0]);
assert(text[0 .. $] == text.items[0 .. $]);
assert(text[0] == 'H');
text[0] = 'h';
assert(text[0] == 'h');
text.append("!!");
assert(text == "hello world!!!");
assert(text.pop() == '!');
assert(text.pop() == '!');
assert(text == "hello world!");
text.resize(0);
assert(text == "");
assert(text.length == 0);
assert(text.capacity == defaultListCapacity);
assert(text.pop() == char.init);
text.resize(1);
assert(text[0] == char.init);
assert(text.length == 1);
assert(text.capacity == defaultListCapacity);
text.clear();
text.reserve(5);
assert(text.length == 0);
assert(text.capacity == defaultListCapacity);
text.reserve(defaultListCapacity + 1);
assert(text.length == 0);
assert(text.capacity == defaultListCapacity * 2);
text.free();
}
// FlagList test.
unittest {
FlagList!int numbers;
numbers = FlagList!int();
assert(numbers.length == 0);
assert(numbers.capacity == 0);
assert(numbers.ptr == null);
assert(numbers.hotIndex == 0);
assert(numbers.openIndex == 0);
numbers = FlagList!int(1, 2, 3);
assert(numbers.length == 3);
assert(numbers.capacity == defaultListCapacity);
assert(numbers.ptr != null);
assert(numbers.hotIndex == 2);
assert(numbers.openIndex == 3);
assert(numbers[0] == 1);
assert(numbers[1] == 2);
assert(numbers[2] == 3);
assert(numbers.has(0) == true);
assert(numbers.has(1) == true);
assert(numbers.has(2) == true);
assert(numbers.has(3) == false);
numbers.remove(1);
assert(numbers.has(0) == true);
assert(numbers.has(1) == false);
assert(numbers.has(2) == true);
assert(numbers.has(3) == false);
assert(numbers.hotIndex == 1);
assert(numbers.openIndex == 1);
numbers.append(1);
assert(numbers.has(0) == true);
assert(numbers.has(1) == true);
assert(numbers.has(2) == true);
assert(numbers.has(3) == false);
assert(numbers.hotIndex == 1);
assert(numbers.openIndex == 1);
numbers.append(4);
assert(numbers.has(0) == true);
assert(numbers.has(1) == true);
assert(numbers.has(2) == true);
assert(numbers.has(3) == true);
assert(numbers.hotIndex == 3);
assert(numbers.openIndex == 4);
numbers.clear();
numbers.append(1);
assert(numbers.has(0) == true);
assert(numbers.has(1) == false);
assert(numbers.has(2) == false);
assert(numbers.has(3) == false);
assert(numbers.hotIndex == 0);
assert(numbers.openIndex == 1);
numbers.free();
assert(numbers.length == 0);
assert(numbers.capacity == 0);
assert(numbers.ptr == null);
assert(numbers.hotIndex == 0);
assert(numbers.openIndex == 0);
}
// Grid test
unittest {
Grid!int numbers;
numbers = Grid!int();
assert(numbers.length == 0);
assert(numbers.capacity == 0);
assert(numbers.ptr == null);
assert(numbers.rowCount == 0);
assert(numbers.colCount == 0);
numbers = Grid!int(8, 8);
assert(numbers.length == 8 * 8);
assert(numbers.capacity == defaultListCapacity);
assert(numbers.ptr != null);
assert(numbers.rowCount == 8);
assert(numbers.colCount == 8);
assert(numbers[0, 0] == 0);
assert(numbers[7, 7] == 0);
assert(numbers.has(7, 8) == false);
assert(numbers.has(8, 7) == false);
assert(numbers.has(8, 8) == false);
numbers.free();
assert(numbers.length == 0);
assert(numbers.capacity == 0);
assert(numbers.ptr == null);
assert(numbers.rowCount == 0);
assert(numbers.colCount == 0);
}

View file

@ -1,103 +0,0 @@
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
/// The `faults` module provides a set of codes and data structures for error handling.
module popka.core.faults;
import popka.core.ascii;
import popka.core.traits;
@safe @nogc nothrow:
enum Fault : ubyte {
none,
some,
invalid,
overflow,
cantFind,
cantOpen,
cantClose,
cantRead,
cantWrite,
}
struct Result(T) {
static if (isNumberType!T) {
T value = 0;
} else {
T value;
}
Fault fault = Fault.some;
@safe @nogc nothrow:
this(T value) {
this.value = value;
this.fault = Fault.none;
}
this(Fault fault) {
this.fault = fault;
}
this(T value, Fault fault) {
if (fault) {
this.fault = fault;
} else {
this.value = value;
this.fault = Fault.none;
}
}
T unwrap() {
if (fault) {
assert(0, "Fault `{}` was detected.".format(fault));
}
return value;
}
T unwrapOr(T dflt) {
if (fault) {
return dflt;
} else {
return value;
}
}
T unwrapOr() {
return value;
}
bool isNone() {
return fault != 0;
}
bool isSome() {
return fault == 0;
}
}
// Result test.
unittest {
assert(Result!int().isNone == true);
assert(Result!int().isSome == false);
assert(Result!int().unwrapOr() == 0);
assert(Result!int(0).isNone == false);
assert(Result!int(0).isSome == true);
assert(Result!int(0).unwrapOr() == 0);
assert(Result!int(69).isNone == false);
assert(Result!int(69).isSome == true);
assert(Result!int(69).unwrapOr() == 69);
assert(Result!int(Fault.none).isNone == false);
assert(Result!int(Fault.none).isSome == true);
assert(Result!int(Fault.none).unwrapOr() == 0);
assert(Result!int(Fault.some).isNone == true);
assert(Result!int(Fault.some).isSome == false);
assert(Result!int(Fault.some).unwrapOr() == 0);
assert(Result!int(69, Fault.none).isNone == false);
assert(Result!int(69, Fault.none).isSome == true);
assert(Result!int(69, Fault.none).unwrapOr() == 69);
assert(Result!int(69, Fault.some).isNone == true);
assert(Result!int(69, Fault.some).isSome == false);
assert(Result!int(69, Fault.some).unwrapOr() == 0);
}

View file

@ -1,91 +0,0 @@
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
/// The `io` module provides input and output functions such as file reading.
module popka.core.io;
import popka.core.ascii;
import popka.core.stdc;
import popka.core.traits;
import popka.core.types;
public import popka.core.containers;
public import popka.core.faults;
@safe @nogc nothrow:
@trusted
void printf(A...)(IStr text, A args) {
.fputs(format("{}\0", format(text, args)).ptr, .stdout);
}
@trusted
void printfln(A...)(IStr text, A args) {
.fputs(format("{}\n\0", format(text, args)).ptr, .stdout);
}
void print(A...)(A args) {
static foreach (arg; args) {
printf("{}", arg);
}
}
void println(A...)(A args) {
static foreach (arg; args) {
printf("{}", arg);
}
printf("\n");
}
@trusted
Fault readTextIntoBuffer(IStr path, ref LStr text) {
auto file = fopen(toCStr(path).unwrapOr(), "rb");
if (file == null) {
return Fault.cantOpen;
}
if (fseek(file, 0, SEEK_END) != 0) {
fclose(file);
return Fault.cantRead;
}
auto fileSize = ftell(file);
if (fileSize == -1) {
fclose(file);
return Fault.cantRead;
}
if (fseek(file, 0, SEEK_SET) != 0) {
fclose(file);
return Fault.cantRead;
}
text.resize(fileSize);
fread(text.items.ptr, fileSize, 1, file);
if (fclose(file) != 0) {
return Fault.cantClose;
}
return Fault.none;
}
Result!LStr readText(IStr path) {
LStr value;
return Result!LStr(value, readTextIntoBuffer(path, value));
}
@trusted
Fault writeText(IStr path, IStr text) {
auto file = fopen(toCStr(path).unwrapOr(), "w");
if (file == null) {
return Fault.cantOpen;
}
fwrite(text.ptr, char.sizeof, text.length, file);
if (fclose(file) != 0) {
return Fault.cantClose;
}
return Fault.none;
}
// Function test.
unittest {
assert(readText("").isSome == false);
assert(writeText("", "") != Fault.none);
}

File diff suppressed because it is too large Load diff

View file

@ -1,14 +0,0 @@
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
module popka.core;
public import popka.core.ascii;
public import popka.core.colors;
public import popka.core.containers;
public import popka.core.faults;
public import popka.core.io;
public import popka.core.math;
public import popka.core.traits;
public import popka.core.types;
public import popka.core.unions;

View file

@ -1,142 +0,0 @@
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
/// The `stdc` module provides access to the C standard library.
module popka.core.stdc;
@nogc nothrow extern(C):
// types.l
version (WebAssembly) {
alias CLong = int;
alias CULong = uint;
} else {
alias CLong = long;
alias CULong = ulong;
}
// math.h
float sqrtf(float x);
float sqrt(double x);
float sinf(float x);
float sin(double x);
float cosf(float x);
float cos(double x);
// stdlib.h
void* malloc(size_t size);
void* realloc(void* ptr, size_t size);
void free(void* ptr);
// stdio.h
alias FILE = void;
enum SEEK_SET = 0;
enum SEEK_CUR = 1;
enum SEEK_END = 2;
enum STDIN_FILENO = 0;
enum STDOUT_FILENO = 1;
enum STDERR_FILENO = 2;
// NOTE: Code from the D standard library.
version (CRuntime_Microsoft) {
FILE* __acrt_iob_func(int hnd); // VS2015+, reimplemented in msvc.d for VS2013-
FILE* stdin()() { return __acrt_iob_func(0); }
FILE* stdout()() { return __acrt_iob_func(1); }
FILE* stderr()() { return __acrt_iob_func(2); }
} else version (CRuntime_Glibc) {
extern __gshared FILE* stdin;
extern __gshared FILE* stdout;
extern __gshared FILE* stderr;
} else version (Darwin) {
extern __gshared FILE* __stdinp;
extern __gshared FILE* __stdoutp;
extern __gshared FILE* __stderrp;
alias __stdinp stdin;
alias __stdoutp stdout;
alias __stderrp stderr;
} else version (FreeBSD) {
extern __gshared FILE* __stdinp;
extern __gshared FILE* __stdoutp;
extern __gshared FILE* __stderrp;
alias __stdinp stdin;
alias __stdoutp stdout;
alias __stderrp stderr;
} else version (NetBSD) {
extern __gshared FILE[3] __sF;
auto __stdin()() { return &__sF[0]; }
auto __stdout()() { return &__sF[1]; }
auto __stderr()() { return &__sF[2]; }
alias __stdin stdin;
alias __stdout stdout;
alias __stderr stderr;
} else version (OpenBSD) {
extern __gshared FILE[3] __sF;
auto __stdin()() { return &__sF[0]; }
auto __stdout()() { return &__sF[1]; }
auto __stderr()() { return &__sF[2]; }
alias __stdin stdin;
alias __stdout stdout;
alias __stderr stderr;
} else version (DragonFlyBSD) {
extern __gshared FILE* __stdinp;
extern __gshared FILE* __stdoutp;
extern __gshared FILE* __stderrp;
alias __stdinp stdin;
alias __stdoutp stdout;
alias __stderrp stderr;
} else version (Solaris) {
extern __gshared FILE[_NFILE] __iob;
auto stdin()() { return &__iob[0]; }
auto stdout()() { return &__iob[1]; }
auto stderr()() { return &__iob[2]; }
} else version (CRuntime_Bionic) {
extern __gshared FILE[3] __sF;
auto stdin()() { return &__sF[0]; }
auto stdout()() { return &__sF[1]; }
auto stderr()() { return &__sF[2]; }
} else version (CRuntime_Musl) {
extern __gshared FILE* stdin;
extern __gshared FILE* stdout;
extern __gshared FILE* stderr;
} else version (CRuntime_Newlib) {
__gshared struct _reent {
int _errno;
__sFILE* _stdin;
__sFILE* _stdout;
__sFILE* _stderr;
}
_reent* __getreent();
pragma(inline, true) {
auto stdin()() { return __getreent()._stdin; }
auto stdout()() { return __getreent()._stdout; }
auto stderr()() { return __getreent()._stderr; }
}
} else version (CRuntime_UClibc) {
extern __gshared FILE* stdin;
extern __gshared FILE* stdout;
extern __gshared FILE* stderr;
} else version (WASI) {
extern __gshared FILE* stdin;
extern __gshared FILE* stdout;
extern __gshared FILE* stderr;
} else {
extern __gshared FILE* stdin;
extern __gshared FILE* stdout;
extern __gshared FILE* stderr;
}
FILE* fopen(const(char)* filename, const(char)* mode);
CLong ftell(FILE* stream);
int fseek(FILE* stream, CLong offset, int origin);
size_t fread(void* ptr, size_t size, size_t count, FILE* stream);
int fclose(FILE* stream);
int fputs(const(char)* str, FILE* stream);
size_t fwrite(const(void)* buffer, size_t size, size_t count, FILE* stream);

View file

@ -1,301 +0,0 @@
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
/// The `traits` module provides compile-time functions such as type checking.
module popka.core.traits;
import popka.core.types;
@safe @nogc nothrow:
alias AliasArgs(A...) = A;
bool isBoolType(T)() {
return is(T == bool) ||
is(T == const(bool)) ||
is(T == immutable(bool));
}
bool isUnsignedType(T)() {
return is(T == ubyte) ||
is(T == const(ubyte)) ||
is(T == immutable(ubyte)) ||
is(T == ushort) ||
is(T == const(ushort)) ||
is(T == immutable(ushort)) ||
is(T == uint) ||
is(T == const(uint)) ||
is(T == immutable(uint)) ||
is(T == ulong) ||
is(T == const(ulong)) ||
is(T == immutable(ulong));
}
bool isSignedType(T)() {
return is(T == byte) ||
is(T == const(byte)) ||
is(T == immutable(byte)) ||
is(T == short) ||
is(T == const(short)) ||
is(T == immutable(short)) ||
is(T == int) ||
is(T == const(int)) ||
is(T == immutable(int)) ||
is(T == long) ||
is(T == const(long)) ||
is(T == immutable(long));
}
bool isIntegerType(T)() {
return isUnsignedType!T || isSignedType!T;
}
bool isFloatingType(T)() {
return is(T == float) ||
is(T == const(float)) ||
is(T == immutable(float)) ||
is(T == double) ||
is(T == const(double)) ||
is(T == immutable(double));
}
bool isNumberType(T)() {
return isIntegerType!T || isFloatingType!T;
}
bool isCharType(T)() {
return is(T == char) ||
is(T == const(char)) ||
is(T == immutable(char));
}
bool isPrimaryType(T)() {
return isBoolType!T ||
isUnsignedType!T ||
isSignedType!T ||
isDoubleType!T ||
isCharType!T;
}
bool isArrayType(T)() {
return is(T : const(A)[N], A, N);
}
bool isPtrType(T)() {
return is(T : const(void)*);
}
bool isSliceType(T)() {
return is(T : const(A)[], A);
}
bool isEnumType(T)() {
return is(T == enum);
}
bool isStructType(T)() {
return is(T == struct);
}
bool isStrType(T)() {
return is(T : IStr);
}
bool isCStrType(T)() {
return is(T : ICStr);
}
bool hasMember(T, IStr name)() {
return __traits(hasMember, T, name);
}
int findInAliasArgs(T, A...)() {
int result = -1;
static foreach (i, TT; A) {
static if (is(T == TT)) {
result = i;
}
}
return result;
}
bool isInAliasArgs(T, A...)() {
return findInAliasArgs!(T, A) != -1;
}
IStr funcImplementationErrorMessage(T, IStr func)() {
return "Type `" ~ T.stringof ~ "` does not implement the `" ~ func ~ "` function.";
}
IStr toCleanNumber(alias i)() {
enum str = i.stringof;
static if (str.length >= 3 && (((str[$ - 1] == 'L' || str[$ - 1] == 'l') && (str[$ - 2] == 'U' || str[$ - 2] == 'u')) || ((str[$ - 1] == 'U' || str[$ - 1] == 'u') && (str[$ - 2] == 'L' || str[$ - 2] == 'l')))) {
return str[0 .. $ - 2];
} else static if (str.length >= 2 && (str[$ - 1] == 'U' || str[$ - 1] == 'u')) {
return str[0 .. $ - 1];
} else static if (str.length >= 2 && (str[$ - 1] == 'L' || str[$ - 1] == 'l')) {
return str[0 .. $ - 1];
} else {
return str;
}
}
mixin template addXyzwOps(T, Sz N) {
static assert(N >= 1 && N <= 4, "Vector `" ~ T.stringof ~ "` must have a dimension between 1 and 4.");
pragma(inline, true)
T opUnary(IStr op)() {
static if (N == 1) {
return T(
mixin(op, "x"),
);
} else static if (N == 2) {
return T(
mixin(op, "x"),
mixin(op, "y"),
);
} else static if (N == 3) {
return T(
mixin(op, "x"),
mixin(op, "y"),
mixin(op, "z"),
);
} else static if (N == 4) {
return T(
mixin(op, "x"),
mixin(op, "y"),
mixin(op, "z"),
mixin(op, "w"),
);
}
}
pragma(inline, true)
T opBinary(IStr op)(T rhs) {
static if (N == 1) {
return T(
mixin("x", op, "rhs.x"),
);
} else static if (N == 2) {
return T(
mixin("x", op, "rhs.x"),
mixin("y", op, "rhs.y"),
);
} else static if (N == 3) {
return T(
mixin("x", op, "rhs.x"),
mixin("y", op, "rhs.y"),
mixin("z", op, "rhs.z"),
);
} else static if (N == 4) {
return T(
mixin("x", op, "rhs.x"),
mixin("y", op, "rhs.y"),
mixin("z", op, "rhs.z"),
mixin("w", op, "rhs.w"),
);
}
}
pragma(inline, true)
void opOpAssign(IStr op)(T rhs) {
static if (N == 1) {
mixin("x", op, "=rhs.x;");
} else static if (N == 2) {
mixin("x", op, "=rhs.x;");
mixin("y", op, "=rhs.y;");
} else static if (N == 3) {
mixin("x", op, "=rhs.x;");
mixin("y", op, "=rhs.y;");
mixin("z", op, "=rhs.z;");
} else static if (N == 4) {
mixin("x", op, "=rhs.x;");
mixin("y", op, "=rhs.y;");
mixin("z", op, "=rhs.z;");
mixin("w", op, "=rhs.w;");
}
}
}
mixin template addRgbaOps(T, Sz N) {
static assert(N >= 1 && N <= 4, "Color `" ~ T.stringof ~ "` must have a dimension between 1 and 4.");
pragma(inline, true)
T opUnary(IStr op)() {
static if (N == 1) {
return T(
mixin(op, "r"),
);
} else static if (N == 2) {
return T(
mixin(op, "r"),
mixin(op, "g"),
);
} else static if (N == 3) {
return T(
mixin(op, "r"),
mixin(op, "g"),
mixin(op, "b"),
);
} else static if (N == 4) {
return T(
mixin(op, "r"),
mixin(op, "g"),
mixin(op, "b"),
mixin(op, "a"),
);
}
}
pragma(inline, true)
T opBinary(IStr op)(T rhs) {
static if (N == 1) {
return T(
mixin("r", op, "rhs.r"),
);
} else static if (N == 2) {
return T(
mixin("r", op, "rhs.r"),
mixin("g", op, "rhs.g"),
);
} else static if (N == 3) {
return T(
mixin("r", op, "rhs.r"),
mixin("g", op, "rhs.g"),
mixin("b", op, "rhs.b"),
);
} else static if (N == 4) {
return T(
mixin("r", op, "rhs.r"),
mixin("g", op, "rhs.g"),
mixin("b", op, "rhs.b"),
mixin("a", op, "rhs.a"),
);
}
}
pragma(inline, true)
void opOpAssign(IStr op)(T rhs) {
static if (N == 1) {
mixin("r", op, "=rhs.r;");
} else static if (N == 2) {
mixin("r", op, "=rhs.r;");
mixin("g", op, "=rhs.g;");
} else static if (N == 3) {
mixin("r", op, "=rhs.r;");
mixin("g", op, "=rhs.g;");
mixin("b", op, "=rhs.b;");
} else static if (N == 4) {
mixin("r", op, "=rhs.r;");
mixin("g", op, "=rhs.g;");
mixin("b", op, "=rhs.b;");
mixin("a", op, "=rhs.a;");
}
}
}
// Function test.
unittest {
assert(isInAliasArgs!(int, AliasArgs!(float)) == false);
assert(isInAliasArgs!(int, AliasArgs!(float, int)) == true);
}

View file

@ -1,28 +0,0 @@
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
/// The `types` module provides a collection of type definitions.
module popka.core.types;
// Popka utilizes two groups of attributes:
// 1. @safe @nogc nothrow
// 2. @trusted @nogc nothrow
@safe @nogc nothrow:
alias Sz = size_t;
alias Str = char[];
alias Str16 = wchar[];
alias Str32 = dchar[];
alias IStr = const(char)[];
alias IStr16 = const(wchar)[];
alias IStr32 = const(dchar)[];
alias CStr = char*;
alias CStr16 = wchar*;
alias CStr32 = dchar*;
alias ICStr = const(char)*;
alias ICStr16 = const(wchar)*;
alias ICStr32 = const(dchar)*;

View file

@ -1,196 +0,0 @@
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
/// The `unions` module provides functions and data structures for working with unions.
module popka.core.unions;
import popka.core.types;
import popka.core.traits;
@safe @nogc nothrow:
alias VariantKind = int;
struct None {}
union VariantValue(A...) {
static assert(A.length != 0, "Arguments must contain at least one element.");
static foreach (i, T; A) {
static if (i == 0 && isNumberType!T) {
mixin("T ", "member", toCleanNumber!i, "= 0;");
} else {
mixin("T ", "member", toCleanNumber!i, ";");
}
}
enum length = A.length;
alias Types = A;
}
struct Variant(A...) {
VariantValue!A value;
VariantKind kind;
alias value this;
@safe @nogc nothrow:
static foreach (i, T; A) {
@trusted
this(T value) {
this.value = *(cast(VariantValue!A*) &value);
this.kind = i;
}
}
static foreach (i, T; A) {
@trusted
void opAssign(T rhs) {
value = *(cast(VariantValue!A*) &rhs);
kind = i;
}
}
IStr kindName() {
static foreach (i, T; A) {
if (kind == i) {
return T.stringof;
}
}
assert(0, "WTF!");
}
bool isKind(T)() {
static assert(isInAliasArgs!(T, A), "Type `" ~ T.stringof ~ "` is not part of the variant.");
return kind == findInAliasArgs!(T, A);
}
@trusted
ref A[0] base() {
return member0;
}
@trusted
ref T get(T)() {
if (isKind!T) {
mixin("return ", "value.member", findInAliasArgs!(T, A), ";");
} else {
static foreach (i, TT; A) {
if (i == kind) {
assert(0, "Value is `" ~ A[i].stringof ~ "` and not `" ~ T.stringof ~ "`.");
}
}
assert(0, "WTF!");
}
}
@trusted
auto call(IStr func, AA...)(AA args) {
switch (kind) {
static foreach (i, T; A) {
static assert(hasMember!(T, func), funcImplementationErrorMessage!(T, func));
mixin("case ", i, ": return value.member", toCleanNumber!i, ".", func, "(args);");
}
default: assert(0, "WTF!");
}
}
template kindOf(T) {
static assert(isInAliasArgs!(T, A), "Type `" ~ T.stringof ~ "` is not part of the variant.");
enum kindOf = findInAliasArgs!(T, A);
}
template kindNameOf(T) {
static assert(isInAliasArgs!(T, A), "Type `" ~ T.stringof ~ "` is not part of the variant.");
enum kindNameOf = T.stringof;
}
}
T toVariant(T)(VariantKind kind) {
static assert(isVariantType!T, "Type `" ~ T.stringof ~ "` is not a variant.");
T result;
static foreach (i, Type; T.Types) {
if (i == kind) {
static if (isNumberType!Type) {
result = cast(Type) 0;
} else {
result = Type.init;
}
goto loopExit;
}
}
loopExit:
return result;
}
T toVariant(T)(IStr kindName) {
static assert(isVariantType!T, "Type `" ~ T.stringof ~ "` is not a variant.");
T result;
static foreach (i, Type; T.Types) {
if (Type.stringof == kindName) {
static if (isNumberType!Type) {
result = cast(Type) 0;
} else {
result = Type.init;
}
goto loopExit;
}
}
loopExit:
return result;
}
bool isVariantType(T)() {
return is(T : Variant!A, A...);
}
mixin template addBase(T) {
T base;
alias base this;
}
// Variant test.
unittest {
alias Number = Variant!(float, double);
assert(Number().kindName == "float");
assert(Number().isKind!float == true);
assert(Number().isKind!double == false);
assert(Number().get!float() == 0);
assert(Number(0.0f).kindName == "float");
assert(Number(0.0f).isKind!float == true);
assert(Number(0.0f).isKind!double == false);
assert(Number(0.0f).get!float() == 0);
assert(Number(0.0).isKind!float == false);
assert(Number(0.0).isKind!double == true);
assert(Number(0.0).kindName == "double");
assert(Number(0.0).get!double() == 0);
assert(Number.kindOf!float == 0);
assert(Number.kindOf!double == 1);
assert(Number.kindNameOf!float == "float");
assert(Number.kindNameOf!double == "double");
auto number = Number();
number = 0.0;
assert(number.get!double() == 0);
number = 0.0f;
assert(number.get!float() == 0);
number.get!float() += 69.0f;
assert(number.get!float() == 69);
auto numberPtr = &number.get!float();
*numberPtr *= 10;
assert(number.get!float() == 690);
}
// Function test.
unittest {
alias Number = Variant!(float, double);
assert(toVariant!Number(Number.kindOf!float).get!float() == 0);
assert(toVariant!Number(Number.kindOf!double).get!double() == 0);
assert(toVariant!Number(Number.kindNameOf!float).get!float() == 0);
assert(toVariant!Number(Number.kindNameOf!double).get!double() == 0);
}

View file

@ -4,12 +4,9 @@
/// The dialogue module is a versatile dialogue system,
/// enabling the creation of interactive conversations and branching narratives.
module popka.game.dialogue;
module popka.dialogue;
import popka.core.containers;
import popka.core.io;
import popka.core.ascii;
import popka.game.engine;
import popka.engine;
@safe @nogc nothrow:

View file

@ -3,16 +3,11 @@
/// The engine module functions as a lightweight 2D game engine.
module popka.game.engine;
module popka.engine;
import ray = popka.vendor.ray;
import popka.core.ascii;
import popka.core.colors;
import popka.core.containers;
import popka.core.faults;
import popka.core.io;
import popka.core.math;
import popka.core.types;
import ray = popka.ray;
public import joka;
@trusted @nogc nothrow:

View file

@ -1,7 +0,0 @@
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
module popka.game;
public import popka.game.dialogue;
public import popka.game.engine;

View file

@ -3,5 +3,5 @@
module popka;
public import popka.core;
public import popka.game;
public import popka.dialogue;
public import popka.engine;

View file

@ -0,0 +1,8 @@
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
module popka.ray;
public import popka.ray.raylib;
public import popka.ray.raylibpp;
public import popka.ray.rlgl;

View file

@ -76,7 +76,7 @@
*
**********************************************************************************************/
module popka.vendor.ray.raylib;
module popka.ray.raylib;
// import core.stdc.config;
// import core.stdc.stdarg;

View file

@ -3,9 +3,9 @@
/// The raylibpp module contains helper functions to reduce some raylib boilerplate.
module popka.vendor.ray.raylibpp;
module popka.ray.raylibpp;
import popka.vendor.ray.raylib;
import popka.ray.raylib;
alias drawPixel = DrawPixel;
alias drawPixel = DrawPixelV;

View file

@ -104,7 +104,7 @@
*
**********************************************************************************************/
module popka.vendor.ray.rlgl;
module popka.ray.rlgl;
@nogc nothrow extern(C):

View file

@ -1,8 +0,0 @@
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
module popka.vendor.ray;
public import popka.vendor.ray.raylib;
public import popka.vendor.ray.raylibpp;
public import popka.vendor.ray.rlgl;