This commit is contained in:
ITOH
2021-03-08 17:59:26 +01:00
parent 91286a0bbc
commit ecbd30e160
5 changed files with 1022 additions and 57 deletions
View File
+19
View File
@@ -0,0 +1,19 @@
import { startBot } from "./src/bot.ts";
startBot({
token: "Nzg1MTM3Mjg5MDcyMDgyOTY1.X8zeFA.vP6lAmWqO2wy9-uOyV4__wNMy8o",
intents: ["GUILDS", "GUILD_MESSAGES", "GUILD_EMOJIS"],
eventHandlers: {
ready() {
console.log("Successfully connected to gateway");
},
messageCreate(message) {
if (message.content === "ping") {
message.reply("Pong using Discordeno!");
}
},
guildEmojisUpdate(g, e, c) {
console.log(e);
},
},
});
+421
View File
@@ -0,0 +1,421 @@
/**
* Custom implementation of a double ended queue.
*/
export class Denque<T> {
#head = 0;
#tail = 0;
#capacity?: number = undefined;
#capacityMask = 0x3;
#list = new Array(4);
constructor(array?: T[], options?: IDenqueOptions) {
if (options?.capacity) this.#capacity = options.capacity;
if (Array.isArray(array)) {
this.fromArray(array);
}
}
/**
* Returns the item at the specified index from the list.
* 0 is the first element, 1 is the second, and so on...
* Elements at negative values are that many from the end: -1 is one before the end
* (the last element), -2 is two before the end (one before last), etc.
*/
peekAt(index: number) {
let i = index;
// expect a number or return undefined
if (i !== (i | 0)) {
return void 0;
}
let len = this.size();
if (i >= len || i < -len) return undefined;
if (i < 0) i += len;
i = (this.#head + i) & this.#capacityMask;
return this.#list[i];
}
/**
* Alias for peekAt()
*/
get(i: number) {
return this.peekAt(i);
}
/**
* Returns the first item in the list without removing it.
*/
peek() {
if (this.#head === this.#tail) return undefined;
return this.#list[this.#head];
}
/**
* Alias for peek()
*/
peekFront() {
return this.peek();
}
/**
* Returns the item that is at the back of the queue without removing it.
* Uses peekAt(-1)
*/
peekBack() {
return this.peekAt(-1);
}
/**
* Returns the current length of the queue
*/
get length() {
return this.size();
}
/**
* Return the number of items on the list, or 0 if empty.
*/
size() {
if (this.#head === this.#tail) return 0;
if (this.#head < this.#tail) return this.#tail - this.#head;
else return this.#capacityMask + 1 - (this.#head - this.#tail);
}
/**
* Add an item at the beginning of the list.
*/
unshift(item: T) {
if (item === undefined) return this.size();
let len = this.#list.length;
this.#head = (this.#head - 1 + len) & this.#capacityMask;
this.#list[this.#head] = item;
if (this.#tail === this.#head) this.growArray();
if (this.#capacity && this.size() > this.#capacity) this.pop();
if (this.#head < this.#tail) return this.#tail - this.#head;
else return this.#capacityMask + 1 - (this.#head - this.#tail);
}
/**
* Remove and return the first item on the list,
* Returns undefined if the list is empty.
*/
shift() {
let head = this.#head;
if (head === this.#tail) return undefined;
let item = this.#list[head];
this.#list[head] = undefined;
this.#head = (head + 1) & this.#capacityMask;
if (
head < 2 && this.#tail > 10000 && this.#tail <= this.#list.length >>> 2
) {
this.shrinkArray();
}
return item;
}
/**
* Add an item to the bottom of the list.
*/
push(item: T) {
if (item === undefined) return this.size();
let tail = this.#tail;
this.#list[tail] = item;
this.#tail = (tail + 1) & this.#capacityMask;
if (this.#tail === this.#head) {
this.growArray();
}
if (this.#capacity && this.size() > this.#capacity) {
this.shift();
}
if (this.#head < this.#tail) return this.#tail - this.#head;
else return this.#capacityMask + 1 - (this.#head - this.#tail);
}
/**
* Remove and return the last item on the list.
* Returns undefined if the list is empty.
*/
pop() {
let tail = this.#tail;
if (tail === this.#head) return undefined;
let len = this.#list.length;
this.#tail = (tail - 1 + len) & this.#capacityMask;
let item = this.#list[this.#tail];
this.#list[this.#tail] = undefined;
if (this.#head < 2 && tail > 10000 && tail <= len >>> 2) this.shrinkArray();
return item;
}
/**
* Remove and return the item at the specified index from the list.
* Returns undefined if the list is empty.
*/
removeOne(index: number) {
let i = index;
// expect a number or return undefined
if (i !== (i | 0)) {
return void 0;
}
if (this.#head === this.#tail) return void 0;
let size = this.size();
let len = this.#list.length;
if (i >= size || i < -size) return void 0;
if (i < 0) i += size;
i = (this.#head + i) & this.#capacityMask;
let item = this.#list[i];
let k;
if (index < size / 2) {
for (k = index; k > 0; k--) {
this.#list[i] = this.#list[(i = (i - 1 + len) & this.#capacityMask)];
}
this.#list[i] = void 0;
this.#head = (this.#head + 1 + len) & this.#capacityMask;
} else {
for (k = size - 1 - index; k > 0; k--) {
this.#list[i] = this.#list[(i = (i + 1 + len) & this.#capacityMask)];
}
this.#list[i] = void 0;
this.#tail = (this.#tail - 1 + len) & this.#capacityMask;
}
return item;
}
/**
* Remove number of items from the specified index from the list.
* Returns array of removed items.
* Returns undefined if the list is empty.
*/
remove(index: number, count: number) {
let i = index;
let removed;
let del_count = count;
// expect a number or return undefined
if (i !== (i | 0)) {
return void 0;
}
if (this.#head === this.#tail) return void 0;
let size = this.size();
let len = this.#list.length;
if (i >= size || i < -size || count < 1) return void 0;
if (i < 0) i += size;
if (count === 1 || !count) {
removed = new Array(1);
removed[0] = this.removeOne(i);
return removed;
}
if (i === 0 && i + count >= size) {
removed = this.toArray();
this.clear();
return removed;
}
if (i + count > size) count = size - i;
let k;
removed = new Array(count);
for (k = 0; k < count; k++) {
removed[k] = this.#list[(this.#head + i + k) & this.#capacityMask];
}
i = (this.#head + i) & this.#capacityMask;
if (index + count === size) {
this.#tail = (this.#tail - count + len) & this.#capacityMask;
for (k = count; k > 0; k--) {
this.#list[(i = (i + 1 + len) & this.#capacityMask)] = void 0;
}
return removed;
}
if (index === 0) {
this.#head = (this.#head + count + len) & this.#capacityMask;
for (k = count - 1; k > 0; k--) {
this.#list[(i = (i + 1 + len) & this.#capacityMask)] = void 0;
}
return removed;
}
if (i < size / 2) {
this.#head = (this.#head + index + count + len) & this.#capacityMask;
for (k = index; k > 0; k--) {
this.unshift(this.#list[(i = (i - 1 + len) & this.#capacityMask)]);
}
i = (this.#head - 1 + len) & this.#capacityMask;
while (del_count > 0) {
this.#list[(i = (i - 1 + len) & this.#capacityMask)] = void 0;
del_count--;
}
if (index < 0) this.#tail = i;
} else {
this.#tail = i;
i = (i + count + len) & this.#capacityMask;
for (k = size - (count + index); k > 0; k--) {
this.push(this.#list[i++]);
}
i = this.#tail;
while (del_count > 0) {
this.#list[(i = (i + 1 + len) & this.#capacityMask)] = void 0;
del_count--;
}
}
if (this.#head < 2 && this.#tail > 10000 && this.#tail <= len >>> 2) {
this.shrinkArray();
}
return removed;
}
/**
* Native splice implementation.
* Remove number of items from the specified index from the list and/or add new elements.
* Returns array of removed items or empty array if count == 0.
* Returns undefined if the list is empty.
*/
splice(index: number, count: number) {
let i = index;
// expect a number or return undefined
if (i !== (i | 0)) {
return void 0;
}
let size = this.size();
if (i < 0) i += size;
if (i > size) return void 0;
if (arguments.length > 2) {
let k;
let temp;
let removed;
let arg_len = arguments.length;
let len = this.#list.length;
let arguments_index = 2;
if (!size || i < size / 2) {
temp = new Array(i);
for (k = 0; k < i; k++) {
temp[k] = this.#list[(this.#head + k) & this.#capacityMask];
}
if (count === 0) {
removed = [];
if (i > 0) {
this.#head = (this.#head + i + len) & this.#capacityMask;
}
} else {
removed = this.remove(i, count);
this.#head = (this.#head + i + len) & this.#capacityMask;
}
while (arg_len > arguments_index) {
this.unshift(arguments[--arg_len]);
}
for (k = i; k > 0; k--) {
this.unshift(temp[k - 1]);
}
} else {
temp = new Array(size - (i + count));
let leng = temp.length;
for (k = 0; k < leng; k++) {
temp[k] =
this.#list[(this.#head + i + count + k) & this.#capacityMask];
}
if (count === 0) {
removed = [];
if (i != size) {
this.#tail = (this.#head + i + len) & this.#capacityMask;
}
} else {
removed = this.remove(i, count);
this.#tail = (this.#tail - leng + len) & this.#capacityMask;
}
while (arguments_index < arg_len) {
this.push(arguments[arguments_index++]);
}
for (k = 0; k < leng; k++) {
this.push(temp[k]);
}
}
return removed;
} else {
return this.remove(i, count);
}
}
/**
* Soft clear - does not reset capacity.
*/
clear() {
this.#head = 0;
this.#tail = 0;
}
/**
* Returns true or false whether the list is empty.
*/
isEmpty() {
return this.#head === this.#tail;
}
/**
* Returns an array of all queue items.
*/
toArray() {
return this.copyArray(false);
}
/**
* -------------
* INTERNALS
* -------------
*/
/**
* Fills the queue with items from an array
* For use in the constructor
*/
private fromArray(array: T[]) {
for (var i = 0; i < array.length; i++) this.push(array[i]);
}
/**
* Clones the array either fully or partially
*/
private copyArray(fullCopy: boolean) {
let newArray = [];
let list = this.#list;
let len = list.length;
let i;
if (fullCopy || this.#head > this.#tail) {
for (i = this.#head; i < len; i++) newArray.push(list[i]);
for (i = 0; i < this.#tail; i++) newArray.push(list[i]);
} else {
for (i = this.#head; i < this.#tail; i++) newArray.push(list[i]);
}
return newArray;
}
/**
* Grows the internal list array.
*/
private growArray() {
if (this.#head) {
// copy existing data, head to end, then beginning to tail.
this.#list = this.copyArray(true);
this.#head = 0;
}
// head is at 0 and array is now full, safe to extend
this.#tail = this.#list.length;
this.#list.length *= 2;
this.#capacityMask = (this.#capacityMask << 1) | 1;
}
/**
* Shrinks the internal list array.
*/
shrinkArray() {
console.log("list", this.#list.length);
this.#list.length >>>= 1;
console.log("mask", this.#capacityMask);
this.#capacityMask >>>= 1;
}
}
export interface IDenqueOptions {
capacity?: number;
}
const f = new Denque(["a", "b", "c", "a", "d"]);
console.log(f.toArray());
console.log(f.removeOne(1));
console.log(f.toArray());
+80 -57
View File
@@ -1,23 +1,48 @@
import { cacheHandlers } from "../api/controllers/cache.ts";
import { Role } from "../api/structures/mod.ts";
import { Channel, Guild, Member, Role } from "../api/structures/mod.ts";
import { botID } from "../bot.ts";
import { Errors, Permission, Permissions } from "../types/mod.ts";
async function getCached(table: "guild", key: string | Guild): Promise<Guild>;
async function getCached(
table: "channel",
key: string | Channel,
): Promise<Channel>;
async function getCached(
table: "member",
key: string | Member,
): Promise<Member>;
async function getCached(
table: "guild" | "channel" | "member",
key: string | Guild | Channel | Member,
) {
const cached = typeof key === "string"
? // @ts-ignore -
(await cacheHandlers.get(`${table}s`, key))
: key;
if (!cached || typeof cached === "string") {
throw new Error(Errors[`${table.toUpperCase}_NOT_FOUND` as Errors]);
}
return cached;
}
/** Calculates the permissions this member has in the given guild */
export async function calculateBasePermissions(
memberID: string,
guildID: string
guild: string | Guild,
member: string | Member,
) {
const guild = await cacheHandlers.get("guilds", guildID);
if (!guild) throw new Error(Errors.GUILD_NOT_FOUND);
const member = await cacheHandlers.get("members", memberID);
if (!member) throw new Error(Errors.MEMBER_NOT_FOUND);
console.log(guild, await cacheHandlers.get("guilds", "800080308921696296"));
console.log(guild);
guild = await getCached("guild", guild);
console.log(guild);
member = await getCached("member", member);
console.log(member);
let permissions = 0n;
// Calculate the role permissions bits, @everyone role is not in memberRoleIDs so we need to pass guildID manualy
permissions |= [...(member.guilds.get(guildID)?.roles || []), guildID]
.map((id) => guild.roles.get(id)?.permissions)
permissions |= [...(member.guilds.get(guild.id)?.roles || []), guild.id]
.map((id) => (guild as Guild).roles.get(id)?.permissions)
// Removes any edge case undefined
.filter((id) => id)
.reduce((bits, perms) => {
@@ -26,33 +51,31 @@ export async function calculateBasePermissions(
}, 0n);
// If the memberID is equal to the guild ownerID he automatically has every permission so we add ADMINISTRATOR permission
if (guild.ownerID === memberID) permissions |= 8n;
if (guild.ownerID === member.id) permissions |= 8n;
// Return the members permission bits as a string
return permissions.toString();
}
/** Calculates the permissions this member has for the given Channel */
export async function calculateChannelOverwrites(
memberID: string,
channelID: string
channel: string | Channel,
member: string | Member,
) {
const channel = await cacheHandlers.get("channels", channelID);
if (!channel) throw new Error(Errors.CHANNEL_NOT_FOUND);
channel = await getCached("channel", channel);
// This is a DM channel so return ADMINISTRATOR permission
if (!channel.guildID) return "8";
member = await getCached("member", member);
// Get all the role permissions this member already has
let permissions = BigInt(
await calculateBasePermissions(memberID, channel.guildID)
await calculateBasePermissions(channel.guildID, member),
);
const member = await cacheHandlers.get("members", memberID);
if (!member) throw new Error(Errors.MEMBER_NOT_FOUND);
// First calculate @everyone overwrites since these have the lowest priority
const overwriteEveryone = channel?.permissionOverwrites.find(
(overwrite) => overwrite.id === channel.guildID
(overwrite) => overwrite.id === (channel as Channel).guildID,
);
if (overwriteEveryone) {
// First remove denied permissions since denied < allowed
@@ -79,7 +102,7 @@ export async function calculateChannelOverwrites(
// Third calculate member specific overwrites since these have the highest priority
const overwriteMember = overwrites.find(
(overwrite) => overwrite.id === memberID
(overwrite) => overwrite.id === (member as Member).id,
);
if (overwriteMember) {
permissions &= ~BigInt(overwriteMember.deny);
@@ -92,25 +115,25 @@ export async function calculateChannelOverwrites(
/** Checks if the given permission bits are matching the given permissions. `ADMINISTRATOR` always returns `true` */
export function validatePermissions(
permissionBits: string,
permissions: Permission[]
permissions: Permission[],
) {
if (BigInt(permissionBits) & 8n) return true;
return permissions.every(
(permission) =>
// Check if permission is in permissionBits
BigInt(permissionBits) & BigInt(Permissions[permission])
BigInt(permissionBits) & BigInt(Permissions[permission]),
);
}
/** Checks if the given member has these permissions in the given guild */
export async function hasGuildPermissions(
memberID: string,
guildID: string,
permissions: Permission[]
memberID: string,
permissions: Permission[],
) {
// First we need the role permission bits this member has
const basePermissions = await calculateBasePermissions(memberID, guildID);
const basePermissions = await calculateBasePermissions(guildID, memberID);
// Second use the validatePermissions function to check if the member has every permission
return validatePermissions(basePermissions, permissions);
}
@@ -118,7 +141,7 @@ export async function hasGuildPermissions(
/** Checks if the bot has these permissions in the given guild */
export function botHasGuildPermissions(
guildID: string,
permissions: Permission[]
permissions: Permission[],
) {
// Since Bot is a normal member we can use the hasRolePermissions() function
return hasGuildPermissions(botID, guildID, permissions);
@@ -126,14 +149,14 @@ export function botHasGuildPermissions(
/** Checks if the given member has these permissions for the given channel */
export async function hasChannelPermissions(
memberID: string,
channelID: string,
permissions: Permission[]
memberID: string,
permissions: Permission[],
) {
// First we need the overwrite bits this member has
const channelOverwrites = await calculateChannelOverwrites(
memberID,
channelID
channelID,
);
// Second use the validatePermissions function to check if the member has every permission
return validatePermissions(channelOverwrites, permissions);
@@ -142,7 +165,7 @@ export async function hasChannelPermissions(
/** Checks if the bot has these permissions f0r the given channel */
export function botHasChannelPermissions(
channelID: string,
permissions: Permission[]
permissions: Permission[],
) {
// Since Bot is a normal member we can use the hasRolePermissions() function
return hasChannelPermissions(botID, channelID, permissions);
@@ -151,23 +174,23 @@ export function botHasChannelPermissions(
/** Returns the permissions that are not in the given permissionBits */
export function missingPermissions(
permissionBits: string,
permissions: Permission[]
permissions: Permission[],
) {
if (BigInt(permissionBits) & 8n) return [];
return permissions.filter(
(permission) => !(BigInt(permissionBits) & BigInt(Permissions[permission]))
(permission) => !(BigInt(permissionBits) & BigInt(Permissions[permission])),
);
}
/** Throws an error if this member has not all of the given permissions */
export async function requireGuildPermissions(
memberID: string,
guildID: string,
permissions: Permission[]
memberID: string,
permissions: Permission[],
) {
// First we need the role permissions bits this member has
const permissionBits = await calculateBasePermissions(memberID, guildID);
const permissionBits = await calculateBasePermissions(guildID, memberID);
// Second check if the member is missing any permissions
const missing = missingPermissions(permissionBits, permissions);
if (missing.length) {
@@ -179,7 +202,7 @@ export async function requireGuildPermissions(
/** Throws an error if the bot does not have all permissions */
export function requireBotGuildPermissions(
guildID: string,
permissions: Permission[]
permissions: Permission[],
) {
// Since Bot is a normal member we can use the throwOnMissingGuildPermission() function
return requireGuildPermissions(botID, guildID, permissions);
@@ -187,9 +210,9 @@ export function requireBotGuildPermissions(
/** Throws an error if this member has not all of the given permissions */
export async function requireChannelPermissions(
memberID: string,
channelID: string,
permissions: Permission[]
memberID: string,
permissions: Permission[],
) {
// First we need the channel overwrite bits this member has
const permissionBits = await calculateChannelOverwrites(memberID, channelID);
@@ -204,7 +227,7 @@ export async function requireChannelPermissions(
/** Throws an error if the bot has not all of the given channel permissions */
export function requireBotChannelPermissions(
channelID: string,
permissions: Permission[]
permissions: Permission[],
) {
// Since Bot is a normal member we can use the throwOnMissingChannelPermission() function
return requireChannelPermissions(botID, channelID, permissions);
@@ -226,23 +249,25 @@ export function calculateBits(permissions: Permission[]) {
.reduce(
// Get the bit value for this permission and assign it to bits
(bits, perm) => (bits |= BigInt(Permissions[perm])),
0n
0n,
)
.toString();
}
// TODO: move memberID to first position
/** Gets the highest role from the member in this guild */
export async function highestRole(guildID: string, memberID: string) {
const guild = await cacheHandlers.get("guilds", guildID);
if (!guild) throw new Error(Errors.GUILD_NOT_FOUND);
export async function highestRole(
guild: string | Guild,
member: string | Member,
) {
guild = await getCached("guild", guild);
// Get the roles from the member
const memberRoles = (
await cacheHandlers.get("members", memberID)
)?.guilds.get(guildID)?.roles;
await getCached("member", member)
).guilds.get(guild.id)?.roles;
// This member has no roles so the highest one is the @everyone role
if (!memberRoles) return guild.roles.get(guildID) as Role;
if (!memberRoles) return guild.roles.get(guild.id) as Role;
let memberHighestRole: Role | undefined;
@@ -268,12 +293,11 @@ export async function highestRole(guildID: string, memberID: string) {
/** Checks if the first role is higher than the second role */
export async function higherRolePosition(
guildID: string,
guild: string | Guild,
roleID: string,
otherRoleID: string
otherRoleID: string,
) {
const guild = await cacheHandlers.get("guilds", guildID);
if (!guild) throw new Error(Errors.GUILD_NOT_FOUND);
guild = await getCached("guild", guild);
const role = guild.roles.get(roleID);
const otherRole = guild.roles.get(otherRoleID);
@@ -289,15 +313,14 @@ export async function higherRolePosition(
/** Checks if the member has a higher position than the given role */
export async function isHigherPosition(
guildID: string,
guild: string | Guild,
memberID: string,
compareRoleID: string
compareRoleID: string,
) {
const guild = await cacheHandlers.get("guilds", guildID);
if (!guild) throw new Error(Errors.GUILD_NOT_FOUND);
guild = await getCached("guild", guild);
if (guild.ownerID === memberID) return true;
const memberHighestRole = await highestRole(guildID, memberID);
return higherRolePosition(guildID, memberHighestRole.id, compareRoleID);
const memberHighestRole = await highestRole(guild.id, memberID);
return higherRolePosition(guild.id, memberHighestRole.id, compareRoleID);
}
+502
View File
@@ -0,0 +1,502 @@
/** // deno-lint-ignore-file */
/** Allows easy way to add a prop to a base object when needing to use complicated getters solution. */
// deno-lint-ignore no-explicit-any
export function createNewProp(value: any): Partial<PropertyDescriptor> {
return { configurable: true, enumerable: true, writable: true, value };
}
interface IDenqueOptions {
capacity?: number;
}
interface Denque<T> {
/**
* Return the number of items on the list, or 0 if empty.
* @returns {number}
*/
size: number;
/**
* Add an item to the bottom of the list.
* @param item
*/
push(item: T): number;
/**
* Add an item at the beginning of the list.
* @param item
*/
unshift(item: T): number;
/**
* Remove and return the last item on the list.
* Returns undefined if the list is empty.
* @returns {*}
*/
pop(): T | undefined;
removeBack(): T | undefined;
/**
* Remove and return the first item on the list,
* Returns undefined if the list is empty.
* @returns {*}
*/
shift(): T | undefined;
/**
* Returns the item that is at the back of the queue without removing it.
* Uses peekAt(-1)
*/
peekBack: T | undefined;
/**
* Alias for peek()
* @returns {*}
*/
peekFront: T | undefined;
/**
* Returns the item at the specified index from the list.
* 0 is the first element, 1 is the second, and so on...
* Elements at negative values are that many from the end: -1 is one before the end
* (the last element), -2 is two before the end (one before last), etc.
* @param index
* @returns {*}
*/
peekAt(index: number): T | undefined;
/**
* Returns the first item in the list without removing it.
*/
peek: T;
/**
* Alias for peekAt()
* @param i
* @returns {*}
*/
get(index: number): T | undefined;
/**
* Remove number of items from the specified index from the list.
* Returns array of removed items.
* Returns undefined if the list is empty.
* @param index
* @param count
* @returns {array}
*/
remove(index: number, count: number): T[] | any;
/**
* Remove and return the item at the specified index from the list.
* Returns undefined if the list is empty.
* @param index
* @returns {*}
*/
removeOne(index: number): T | undefined;
/**
* Native splice implementation.
* Remove number of items from the specified index from the list and/or add new elements.
* Returns array of removed items or empty array if count == 0.
* Returns undefined if the list is empty.
*
* @param index
* @param count
* @param {...*} [elements]
* @returns {array}
*/
splice(index: number, count: number, ...item: T[]): T[] | undefined;
/**
* Returns true or false whether the list is empty.
* @returns {boolean}
*/
isEmpty: boolean;
/**
* Soft clear - does not reset capacity.
*/
clear(): void;
toString(): string;
toArray(): T[];
/**
* Returns the current length of the queue
* @return {Number}
*/
length: number;
_head: number;
_tail: number;
_capacity: number;
_capacityMask: number;
_list: T[];
/**
* Fills the queue with items from an array
* For use in the constructor
* @param array
* @private
*/
_fromArray(array: T[]): void;
/**
*
* @param fullCopy
* @returns {Array}
* @private
*/
_copyArray(fullCopy: boolean): T[];
/**
* Grows the internal list array.
* @private
*/
_growArray(): void;
/**
* Shrinks the internal list array.
* @private
*/
_shrinkArray(): void;
}
// deno-lint-ignore no-explicit-any
const baseDenque: Partial<Denque<any>> = {
peekAt(index) {
// expect a number or return undefined
if (index !== (index | 0)) {
return void 0;
}
if (index >= this.size! || index < this.size!) return undefined;
if (index < 0) index += this.size!;
index = (this._head! + index) & this._capacityMask!;
return this._list![index];
},
get(index) {
return this.peekAt!(index);
},
get peek() {
if (this._head === this._tail) return undefined;
return this._list![this._head!];
},
get peekFront() {
return this.peek!;
},
get peekBack() {
return this.peekAt!(-1);
},
unshift(item) {
if (item === undefined) return this.size!;
this._head = (this._head! - 1 + this._list?.length!) & this._capacityMask!;
this._list![this._head] = item;
if (this._tail === this._head) this._growArray!();
if (this._capacity && this.size! > this._capacity) this.pop!();
if (this._head < this._tail!) return this._tail! - this._head;
else return this._capacityMask! + 1 - (this._head - this._tail!);
},
shift() {
const head = this._head!;
if (head === this._tail) return undefined;
this._list![head] = undefined;
this._head = (head + 1) & this._capacityMask!;
const item = this._list![head];
if (
head < 2 &&
this._tail! > 10000 &&
this._tail! <= this._list!.length >>> 2
) {
this._shrinkArray!();
}
return item;
},
// push(item) {
// if (item === undefined) return this.size!;
// let tail = this._tail!;
// this._list![tail] = item;
// this._tail = (tail + 1) & this._capacityMask!;
// if (this._tail === this._head) {
// this._growArray!();
// }
// if (this._capacity && this.size! > this._capacity) {
// this.shift!();
// }
// if (this._head! < this._tail) return this._tail - this._head!;
// else return this._capacityMask! + 1 - (this._head! - this._tail);
// },
push(item) {
if (item === undefined) return this.size!;
this._list![this._tail!] = item;
this._tail! += 1 & this._capacityMask!;
if (this._tail === this._head) {
this._growArray!();
}
if (this._capacity && this.size! > this._capacity) {
this.shift!();
}
if (this._head! < this._tail!) return this._tail! - this._head!;
else return this._capacityMask! + 1 - (this._head! - this._tail!);
},
pop() {
if (this._tail === this._head!) return undefined;
this._tail! = this._tail! - 1 + this._list!.length & this._capacityMask!;
const item = this._list![this._tail!];
this._list![this._tail!] = undefined;
if (
this._head! < 2 && this._tail! > 10000 &&
this._tail! <= this._list!.length >>> 2
) {
this._shrinkArray!();
}
return item;
},
removeOne(index) {
// expect a number or return undefined
if (index !== (index | 0)) return;
if (this._head === this._tail) return;
const size = this.size!;
var len = this._list!.length;
if (index >= size || index < -size) return;
if (index < 0) index += size;
index = (this._head! + index) & this._capacityMask!;
var item = this._list![index];
var k;
if (index < size / 2) {
for (k = index; k > 0; k--) {
this._list![index] =
this._list![(index = (index - 1 + len) & this._capacityMask!)];
}
this._list![index] = void 0;
this._head = (this._head! + 1 + len) & this._capacityMask!;
} else {
for (k = size - 1 - index; k > 0; k--) {
this._list![index] =
this._list![(index = (index + 1 + len) & this._capacityMask!)];
}
this._list![index] = void 0;
this._tail = (this._tail! - 1 + len) & this._capacityMask!;
}
return item;
},
remove(index, count) {
var i = index;
var removed;
var delCount = count;
// expect a number or return undefined
if (i !== (i | 0)) {
return void 0;
}
if (this._head === this._tail) return void 0;
var size = this.size!;
var len = this._list!.length;
if (i >= size || i < -size || count < 1) return void 0;
if (i < 0) i += size;
if (count === 1 || !count) {
removed = new Array(1);
removed[0] = this.removeOne!(i);
return removed;
}
if (i === 0 && i + count >= size) {
removed = this.toArray!();
this.clear!();
return removed;
}
if (i + count > size) count = size - i;
var k;
removed = new Array(count);
for (k = 0; k < count; k++) {
removed[k] = this._list![(this._head! + i + k) & this._capacityMask!];
}
i = (this._head! + i) & this._capacityMask!;
if (index + count === size) {
this._tail = (this._tail! - count + len) & this._capacityMask!;
for (k = count; k > 0; k--) {
this._list![(i = (i + 1 + len) & this._capacityMask!)] = void 0;
}
return removed;
}
if (index === 0) {
this._head = (this._head! + count + len) & this._capacityMask!;
for (k = count - 1; k > 0; k--) {
this._list![(i = (i + 1 + len) & this._capacityMask!)] = void 0;
}
return removed;
}
if (i < size / 2) {
this._head = (this._head! + index + count + len) & this._capacityMask!;
for (k = index; k > 0; k--) {
this.unshift!(this._list![(i = (i - 1 + len) & this._capacityMask!)]);
}
i = (this._head - 1 + len) & this._capacityMask!;
while (delCount > 0) {
this._list![(i = (i - 1 + len) & this._capacityMask!)] = void 0;
delCount--;
}
if (index < 0) this._tail = i;
} else {
this._tail = i;
i = (i + count + len) & this._capacityMask!;
for (k = size - (count + index); k > 0; k--) {
this.push!(this._list![i++]);
}
i = this._tail;
while (delCount > 0) {
this._list![(i = (i + 1 + len) & this._capacityMask!)] = void 0;
delCount--;
}
}
if (this._head! < 2 && this._tail! > 10000 && this._tail! <= len >>> 2) {
this._shrinkArray!();
}
return removed;
},
splice(index, count) {
var i = index;
// expect a number or return undefined
if (i !== (i | 0)) {
return void 0;
}
var size = this.size!;
if (i < 0) i += size;
if (i > size) return void 0;
if (arguments.length > 2) {
var k, temp, removed;
var argLen = arguments.length;
var len = this._list!.length;
var arguments_index = 2;
if (!size || i < size / 2) {
temp = new Array(i);
for (k = 0; k < i; k++) {
temp[k] = this._list![(this._head! + k) & this._capacityMask!];
}
if (count === 0) {
removed = [];
if (i > 0) {
this._head = (this._head! + i + len) & this._capacityMask!;
}
} else {
removed = this.remove!(i, count);
this._head = (this._head! + i + len) & this._capacityMask!;
}
while (argLen > arguments_index) {
this.unshift!(arguments[--argLen]);
}
for (k = i; k > 0; k--) {
this.unshift!(temp[k - 1]);
}
} else {
temp = new Array(size - (i + count));
var leng = temp.length;
for (k = 0; k < leng; k++) {
temp[k] = this._list![
(this._head! + i + count + k) & this._capacityMask!
];
}
if (count === 0) {
removed = [];
if (i != size) {
this._tail = (this._head! + i + len) & this._capacityMask!;
}
} else {
removed = this.remove!(i, count);
this._tail = (this._tail! - leng + len) & this._capacityMask!;
}
while (arguments_index < argLen) {
this.push!(arguments[arguments_index++]);
}
for (k = 0; k < leng; k++) {
this.push!(temp[k]);
}
}
return removed;
} else {
return this.remove!(i, count);
}
},
clear() {
this._head = 0;
this._tail = 0;
},
get isEmpty() {
return this._head === this._tail;
},
toArray() {
return this._copyArray!(true);
},
get size() {
if (this._head === this._tail) return 0;
if (this._head! < this._tail!) return this._tail! - this._head!;
else return this._capacityMask! + 1 - (this._head! - this._tail!);
},
_fromArray(array) {
for (var i = 0; i < array.length; i++) this.push!(array[i]);
},
_copyArray(fullCopy: boolean) {
let newArray = [];
let list = this._list!;
let len = list.length;
let i;
if (fullCopy || this._head! > this._tail!) {
for (i = this._head!; i < len; i++) newArray.push(list[i]);
for (i = 0; i < this._tail!; i++) newArray.push(list[i]);
} else {
for (i = this._head!; i < this._tail!; i++) newArray.push(list[i]);
}
return newArray;
},
_growArray() {
if (this._head) {
// copy existing data, head to end, then beginning to tail.
this._list = this._copyArray!(true);
this._head = 0;
}
// head is at 0 and array is now full, safe to extend
this._tail = this._list!.length;
this._list!.length *= 2;
this._capacityMask = (this._capacityMask! << 1) | 1;
},
_shrinkArray() {
console.log("list", this._list?.length);
this._list!.length >>>= 1;
console.log("len", this._capacityMask!);
this._capacityMask! >>>= 1;
},
get length() {
return this.size!;
},
};
/**
* Custom implementation of a double ended queue.
*/
function Denque<T>(array?: T[], options?: IDenqueOptions): Denque<T> {
const denque = Object.create(baseDenque, {
_head: createNewProp(0),
_tail: createNewProp(0),
_capacity: createNewProp(options?.capacity),
_capacityMask: createNewProp(0x3),
_list: createNewProp(new Array(4)),
}) as Denque<T>;
if (Array.isArray(array)) {
denque._fromArray(array);
}
return denque;
}
const f = Denque<string>(["a", "b", "c", "d", "e"]);
console.log(f.toArray());
console.log(f.removeOne(1));
console.log(f);
console.log(f.toArray());
// import Denque from "https://esm.sh/denque@1.5.0";
// const f = new Denque(["a", "a", "a", "a", "s"]);
// console.log(f);