Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/LuaLib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export enum LuaLibFeature {
Iterator = "Iterator",
Map = "Map",
Set = "Set",
WeakMap = "WeakMap",
WeakSet = "WeakSet",
StringReplace = "StringReplace",
StringSplit = "StringSplit",
StringConcat = "StringConcat",
Expand All @@ -33,6 +35,8 @@ const luaLibDependencies: {[lib in LuaLibFeature]?: LuaLibFeature[]} = {
Iterator: [LuaLibFeature.Symbol],
Map: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol],
Set: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol],
WeakMap: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol],
WeakSet: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol],
};

export class LuaLib {
Expand Down
6 changes: 6 additions & 0 deletions src/LuaTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3544,6 +3544,12 @@ export class LuaTransformer {
case "Set":
this.importLuaLibFeature(LuaLibFeature.Set);
return;
case "WeakMap":
this.importLuaLibFeature(LuaLibFeature.WeakMap);
return;
case "WeakSet":
this.importLuaLibFeature(LuaLibFeature.WeakSet);
return;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/lualib/Set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class Set<TValue> {
const arr = other as TValue[];
this.size = arr.length;
for (const value of arr) {
this.items[value as any] = true as any;
this.items[value as any] = true;
}
}
}
Expand Down
49 changes: 49 additions & 0 deletions src/lualib/WeakMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
declare function setmetatable<T extends object>(obj: T, metatable: any): T;

class WeakMap<TKey extends object, TValue> {
private items: {[key: string]: TValue}; // Type of key is actually TKey

constructor(other: Iterable<[TKey, TValue]> | Array<[TKey, TValue]>) {
this.items = {};
setmetatable(this.items, { __mode: 'k' });

if (other) {
const iterable = other as Iterable<[TKey, TValue]>;
if (iterable[Symbol.iterator]) {
// Iterate manually because WeakMap is compiled with ES5 which doesn't support Iterables in for...of
const iterator = iterable[Symbol.iterator]();
while (true) {
const result = iterator.next();
if (result.done) {
break;
}
const value: [TKey, TValue] = result.value; // Ensures index is offset when tuple is accessed
this.set(value[0], value[1]);
}
} else {
for (const kvp of other as Array<[TKey, TValue]>) {
this.items[kvp[0] as any] = kvp[1];
}
}
}
}

public delete(key: TKey): boolean {
const contains = this.has(key);
this.items[key as any] = undefined;
return contains;
}

public get(key: TKey): TValue {
return this.items[key as any];
}

public has(key: TKey): boolean {
return this.items[key as any] !== undefined;
}

public set(key: TKey, value: TValue): WeakMap<TKey, TValue> {
this.items[key as any] = value;
return this;
}
}
44 changes: 44 additions & 0 deletions src/lualib/WeakSet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
declare function setmetatable<T extends object>(obj: T, metatable: any): T;

class WeakSet<TValue extends object> {
private items: {[key: string]: boolean}; // Key type is actually TValue

constructor(other: Iterable<TValue> | TValue[]) {
this.items = {};
setmetatable(this.items, { __mode: 'k' });

if (other) {
const iterable = other as Iterable<TValue>;
if (iterable[Symbol.iterator]) {
// Iterate manually because WeakSet is compiled with ES5 which doesn't support Iterables in for...of
const iterator = iterable[Symbol.iterator]();
while (true) {
const result = iterator.next();
if (result.done) {
break;
}
this.add(result.value);
}
} else {
for (const value of other as TValue[]) {
this.items[value as any] = true;
}
}
}
}

public add(value: TValue): WeakSet<TValue> {
this.items[value as any] = true;
return this;
}

public delete(value: TValue): boolean {
const contains = this.has(value);
this.items[value as any] = undefined;
return contains;
}

public has(value: TValue): boolean {
return this.items[value as any] === true;
}
}
4 changes: 2 additions & 2 deletions test/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,14 @@ export function transpileAndExecute(
tsStr: string,
compilerOptions?: CompilerOptions,
luaHeader?: string,
tsHeader?: string
tsHeader?: string,
ignoreDiagnosticsOverride = process.argv[2] === "--ignoreDiagnostics"
): any
{
const wrappedTsString = `declare function JSONStringify(p: any): string;
${tsHeader ? tsHeader : ""}
function __runTest(): any {${tsStr}}`;

const ignoreDiagnosticsOverride = process.argv[2] === "--ignoreDiagnostics";
const lua = `${luaHeader ? luaHeader : ""}
${transpileString(wrappedTsString, compilerOptions, ignoreDiagnosticsOverride)}
return __runTest();`;
Expand Down
134 changes: 134 additions & 0 deletions test/unit/lualib/weakMap.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { Expect, Test } from "alsatian";
import * as util from "../../src/util";

export class WeakMapTests {
private initRefsTs = `let ref = {};
let ref2 = () => {};`;

@Test("weakMap constructor")
public weakMapConstructor(): void
{
const result = util.transpileAndExecute(this.initRefsTs + `
let mymap = new WeakMap([[ref, 1]]);
return mymap.get(ref);
`);

Expect(result).toBe(1);
}

@Test("weakMap iterable constructor")
public weakMapIterableConstructor(): void
{
const result = util.transpileAndExecute(this.initRefsTs + `
let mymap = new WeakMap([[ref, 1], [ref2, 2]]);
return mymap.has(ref) && mymap.has(ref2);
`);

Expect(result).toBe(true);
}

@Test("weakMap iterable constructor map")
public weakMapIterableConstructor2(): void
{
const result = util.transpileAndExecute(this.initRefsTs + `
let mymap = new WeakMap(new Map([[ref, 1], [ref2, 2]]));
return mymap.has(ref) && mymap.has(ref2);
`);

Expect(result).toBe(true);
}

@Test("weakMap delete")
public weakMapDelete(): void
{
const contains = util.transpileAndExecute(this.initRefsTs + `
let mymap = new WeakMap([[ref, true], [ref2, true]]);
mymap.delete(ref2);
return mymap.has(ref) && !mymap.has(ref2);
`);

Expect(contains).toBe(true);
}

@Test("weakMap get")
public weakMapGet(): void
{
const result = util.transpileAndExecute(this.initRefsTs + `
let mymap = new WeakMap([[ref, 1], [{}, 2]]);
return mymap.get(ref);
`);

Expect(result).toBe(1);
}

@Test("weakMap get missing")
public weakMapGetMissing(): void
{
const result = util.transpileAndExecute(this.initRefsTs + `
let mymap = new WeakMap([[{}, true]]);
return mymap.get({});
`);

Expect(result).toBe(undefined);
}

@Test("weakMap has")
public weakMapHas(): void
{
const contains = util.transpileAndExecute(this.initRefsTs + `
let mymap = new WeakMap([[ref, true]]);
return mymap.has(ref);
`);

Expect(contains).toBe(true);
}

@Test("weakMap has false")
public weakMapHasFalse(): void
{
const contains = util.transpileAndExecute(this.initRefsTs + `
let mymap = new WeakMap([[ref, true]]);
return mymap.has(ref2);
`);

Expect(contains).toBe(false);
}

@Test("weakMap has null")
public weakMapHasNull(): void
{
const contains = util.transpileAndExecute(this.initRefsTs + `
let mymap = new WeakMap([[{}, true]]);
return mymap.has(null);
`);

Expect(contains).toBe(false);
}

@Test("weakMap set")
public weakMapSet(): void
{
const init = this.initRefsTs + `
let mymap = new WeakMap();
mymap.set(ref, 5);
`;

const has = util.transpileAndExecute(init + `return mymap.has(ref);`);
Expect(has).toBe(true);

const value = util.transpileAndExecute(init + `return mymap.get(ref)`);
Expect(value).toBe(5);
}

@Test("weakMap has no map features")
public weakMapHasNoMapFeatures(): void
{
const transpileAndExecute = (tsStr: string) => util.transpileAndExecute(tsStr, undefined, undefined, undefined, true);
Expect(transpileAndExecute(`return new WeakMap().size`)).toBe(undefined);
Expect(() => transpileAndExecute(`new WeakMap().clear()`)).toThrow();
Expect(() => transpileAndExecute(`new WeakMap().keys()`)).toThrow();
Expect(() => transpileAndExecute(`new WeakMap().values()`)).toThrow();
Expect(() => transpileAndExecute(`new WeakMap().entries()`)).toThrow();
Expect(() => transpileAndExecute(`new WeakMap().forEach(() => {})`)).toThrow();
}
}
87 changes: 87 additions & 0 deletions test/unit/lualib/weakSet.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Expect, Test } from "alsatian";
import * as util from "../../src/util";

export class WeakSetTests {
private initRefsTs = `let ref = {};
let ref2 = () => {};`;

@Test("weakSet constructor")
public weakSetConstructor(): void
{
const result = util.transpileAndExecute(this.initRefsTs + `
let myset = new WeakSet([ref]);
return myset.has(ref)
`);

Expect(result).toBe(true);
}

@Test("weakSet iterable constructor")
public weakSetIterableConstructor(): void
{
const result = util.transpileAndExecute(this.initRefsTs + `
let myset = new WeakSet([ref, ref2]);
return myset.has(ref) && myset.has(ref2);
`);

Expect(result).toBe(true);
}

@Test("weakSet iterable constructor set")
public weakSetIterableConstructorSet(): void
{
const result = util.transpileAndExecute(this.initRefsTs + `
let myset = new WeakSet(new Set([ref, ref2]));
return myset.has(ref) && myset.has(ref2);
`);

Expect(result).toBe(true);
}

@Test("weakSet add")
public weakSetAdd(): void
{
const result = util.transpileAndExecute(this.initRefsTs + `
let myset = new WeakSet();
myset.add(ref);
return myset.has(ref);
`);

Expect(result).toBe(true);
}

@Test("weakSet add different references")
public weakSetAddDifferentReferences(): void
{
const result = util.transpileAndExecute(this.initRefsTs + `
let myset = new WeakSet();
myset.add({});
return myset.has({});
`);

Expect(result).toBe(false);
}

@Test("weakSet delete")
public weakSetDelete(): void
{
const contains = util.transpileAndExecute(this.initRefsTs + `
let myset = new WeakSet([ref, ref2]);
myset.delete(ref);
return myset.has(ref2) && !myset.has(ref);
`);
Expect(contains).toBe(true);
}

@Test("weakSet has no set features")
public weakSetHasNoSetFeatures(): void
{
const transpileAndExecute = (tsStr: string) => util.transpileAndExecute(tsStr, undefined, undefined, undefined, true);
Expect(transpileAndExecute(`return new WeakSet().size`)).toBe(undefined);
Expect(() => transpileAndExecute(`new WeakSet().clear()`)).toThrow();
Expect(() => transpileAndExecute(`new WeakSet().keys()`)).toThrow();
Expect(() => transpileAndExecute(`new WeakSet().values()`)).toThrow();
Expect(() => transpileAndExecute(`new WeakSet().entries()`)).toThrow();
Expect(() => transpileAndExecute(`new WeakSet().forEach(() => {})`)).toThrow();
}
}