mirror of
https://github.com/Kapendev/parin.git
synced 2025-04-28 22:19:55 +03:00
Core is now a package.
This commit is contained in:
parent
7bb8fb64e8
commit
f0524a7105
25 changed files with 79 additions and 3530 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -14,4 +14,5 @@ popka-test-*
|
||||||
*.o
|
*.o
|
||||||
*.obj
|
*.obj
|
||||||
*.lst
|
*.lst
|
||||||
libpopka*
|
lib*
|
||||||
|
dub.selections.json
|
||||||
|
|
|
@ -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.
|
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.
|
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
|
## Attributes and BetterC Support
|
||||||
|
|
||||||
This project offers support for some attributes (`@safe`, `@nogc`, `nothrow`) and aims for good compatibility with BetterC.
|
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.
|
All the helper functions are inside the [raylibpp.d](source/popka/vendor/ray/raylibpp.d) file.
|
||||||
|
|
||||||
```d
|
```d
|
||||||
import popka.vendor.ray;
|
import popka.ray;
|
||||||
|
|
||||||
bool rayLoop() {
|
bool rayLoop() {
|
||||||
BeginDrawing();
|
BeginDrawing();
|
||||||
|
|
3
TODO.md
3
TODO.md
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
## NOW
|
## NOW
|
||||||
|
|
||||||
Cleaning done.
|
It's time to clean popka...
|
||||||
|
I do this because I am thinking of taking part in a game jam.
|
||||||
|
|
||||||
## IDEAS
|
## IDEAS
|
||||||
|
|
||||||
|
|
3
dub.json
3
dub.json
|
@ -6,6 +6,9 @@
|
||||||
"description": "A lightweight and beginner-friendly 2D game engine for the D programming language.",
|
"description": "A lightweight and beginner-friendly 2D game engine for the D programming language.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"name": "popka",
|
"name": "popka",
|
||||||
|
"dependencies": {
|
||||||
|
"joka": "*"
|
||||||
|
},
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "pdefault",
|
"name": "pdefault",
|
||||||
|
|
|
@ -25,57 +25,58 @@ enum webDir = buildPath(".", "web");
|
||||||
|
|
||||||
|
|
||||||
enum defaultDUBContent = `{
|
enum defaultDUBContent = `{
|
||||||
"name" : "game",
|
"name" : "game",
|
||||||
"description" : "A game made with Popka.",
|
"description" : "A game made with Popka.",
|
||||||
"authors" : ["Name"],
|
"authors" : ["Name"],
|
||||||
"copyright" : "Copyright © 2024, Name",
|
"copyright" : "Copyright © 2024, Name",
|
||||||
"license" : "proprietary",
|
"license" : "proprietary",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"popka": "*"
|
"joka": "*",
|
||||||
},
|
"popka": "*"
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "linux",
|
|
||||||
"targetType": "executable",
|
|
||||||
"platforms": ["linux"],
|
|
||||||
"dflags": ["-i"],
|
|
||||||
"lflags": ["-L.", "-rpath=$$ORIGIN"],
|
|
||||||
"libs": [
|
|
||||||
"raylib",
|
|
||||||
"GL",
|
|
||||||
"m",
|
|
||||||
"pthread",
|
|
||||||
"dl",
|
|
||||||
"rt",
|
|
||||||
"X11"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
"configurations": [
|
||||||
"name": "windows",
|
{
|
||||||
"targetType": "executable",
|
"name": "linux",
|
||||||
"platforms": ["windows"],
|
"targetType": "executable",
|
||||||
"dflags": ["-i"],
|
"platforms": ["linux"],
|
||||||
"libs": [
|
"dflags": ["-i"],
|
||||||
"raylib"
|
"lflags": ["-L.", "-rpath=$$ORIGIN"],
|
||||||
]
|
"libs": [
|
||||||
},
|
"raylib",
|
||||||
{
|
"GL",
|
||||||
"name": "osx",
|
"m",
|
||||||
"targetType": "executable",
|
"pthread",
|
||||||
"platforms": ["osx"],
|
"dl",
|
||||||
"dflags": ["-i"],
|
"rt",
|
||||||
"lflags": ["-L.", "-rpath", "@executable_path/"],
|
"X11"
|
||||||
"libs": [
|
]
|
||||||
"raylib.500"
|
},
|
||||||
]
|
{
|
||||||
},
|
"name": "windows",
|
||||||
{
|
"targetType": "executable",
|
||||||
"name": "web",
|
"platforms": ["windows"],
|
||||||
"targetType": "staticLibrary",
|
"dflags": ["-i"],
|
||||||
"targetName": "webgame",
|
"libs": [
|
||||||
"dflags": ["-mtriple=wasm32-unknown-unknown-wasm", "-checkaction=halt", "-betterC", "--release", "-i"]
|
"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"]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -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.
|
|
||||||
}
|
|
|
@ -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)");
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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
|
@ -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;
|
|
|
@ -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);
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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)*;
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -4,12 +4,9 @@
|
||||||
/// The dialogue module is a versatile dialogue system,
|
/// The dialogue module is a versatile dialogue system,
|
||||||
/// enabling the creation of interactive conversations and branching narratives.
|
/// enabling the creation of interactive conversations and branching narratives.
|
||||||
|
|
||||||
module popka.game.dialogue;
|
module popka.dialogue;
|
||||||
|
|
||||||
import popka.core.containers;
|
import popka.engine;
|
||||||
import popka.core.io;
|
|
||||||
import popka.core.ascii;
|
|
||||||
import popka.game.engine;
|
|
||||||
|
|
||||||
@safe @nogc nothrow:
|
@safe @nogc nothrow:
|
||||||
|
|
|
@ -3,16 +3,11 @@
|
||||||
|
|
||||||
/// The engine module functions as a lightweight 2D game engine.
|
/// The engine module functions as a lightweight 2D game engine.
|
||||||
|
|
||||||
module popka.game.engine;
|
module popka.engine;
|
||||||
|
|
||||||
import ray = popka.vendor.ray;
|
import ray = popka.ray;
|
||||||
import popka.core.ascii;
|
|
||||||
import popka.core.colors;
|
public import joka;
|
||||||
import popka.core.containers;
|
|
||||||
import popka.core.faults;
|
|
||||||
import popka.core.io;
|
|
||||||
import popka.core.math;
|
|
||||||
import popka.core.types;
|
|
||||||
|
|
||||||
@trusted @nogc nothrow:
|
@trusted @nogc nothrow:
|
||||||
|
|
|
@ -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;
|
|
|
@ -3,5 +3,5 @@
|
||||||
|
|
||||||
module popka;
|
module popka;
|
||||||
|
|
||||||
public import popka.core;
|
public import popka.dialogue;
|
||||||
public import popka.game;
|
public import popka.engine;
|
||||||
|
|
8
source/popka/ray/package.d
Normal file
8
source/popka/ray/package.d
Normal 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;
|
|
@ -76,7 +76,7 @@
|
||||||
*
|
*
|
||||||
**********************************************************************************************/
|
**********************************************************************************************/
|
||||||
|
|
||||||
module popka.vendor.ray.raylib;
|
module popka.ray.raylib;
|
||||||
|
|
||||||
// import core.stdc.config;
|
// import core.stdc.config;
|
||||||
// import core.stdc.stdarg;
|
// import core.stdc.stdarg;
|
|
@ -3,9 +3,9 @@
|
||||||
|
|
||||||
/// The raylibpp module contains helper functions to reduce some raylib boilerplate.
|
/// 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 = DrawPixel;
|
||||||
alias drawPixel = DrawPixelV;
|
alias drawPixel = DrawPixelV;
|
|
@ -104,7 +104,7 @@
|
||||||
*
|
*
|
||||||
**********************************************************************************************/
|
**********************************************************************************************/
|
||||||
|
|
||||||
module popka.vendor.ray.rlgl;
|
module popka.ray.rlgl;
|
||||||
|
|
||||||
@nogc nothrow extern(C):
|
@nogc nothrow extern(C):
|
||||||
|
|
8
source/popka/vendor/ray/package.d
vendored
8
source/popka/vendor/ray/package.d
vendored
|
@ -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;
|
|
Loading…
Add table
Add a link
Reference in a new issue