forked from chalk/chalk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
array.lua
186 lines (160 loc) Β· 6.52 KB
/
array.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
--[[
derived from documentation and reference implementation at:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf
Attributions and copyright licensing by Mozilla Contributors is licensed under CC-BY-SA 2.5
These are heuristcs tested across 300+KLOC of JS translated to Lua
It isn't perfect, but it works for dozens of packages in the React, Jest, and GraphQL ecosystem
In those dozens of packages, hundreds and hundreds of tests were ported to Lua and passing
Additionally, the packages were integrated into a 500+KLOC 60fps product and shipped to 150+M global users
Reimplemented by Matt Hargett, 2023
]]
--!strict
local exports = {}
export type Array<T> = { [number]: T }
type Object = { [string]: any }
exports.indexOf = function<T>(haystack: Array<T>, needle: T, startIndex_: number?): number
local length = #haystack
local startIndex = if startIndex_ == nil
then 1
else if startIndex_ < 1 then math.max(1, length - math.abs(startIndex_)) else startIndex_
for i = startIndex, length, 1 do
if haystack[i] == needle then
return i
end
end
-- we maintain the JS not found value, as it's often checked for explicitly and as < 0
return -1
end
-- we pulled a few refinement ideas from this stackoverflow article, but found:
-- 1. no single one answer worked enough of the time in terms of transliterated JS expectations
-- 2. most had very poor accuracy versus performance tradeoffs
-- https://stackoverflow.com/questions/7526223/how-do-i-know-if-a-table-is-an-array/20958869#20958869
exports.isArray = function(val: any): boolean
if type(val) ~= "table" then
return false
end
if next(val) == nil then
-- it's table with nothing in it, which we express is an array
-- this works 99% of the time for transliterated Lua
return true
end
local tableLength = #val
if tableLength == 0 then
-- getting past the preceding clause says the table isn't an empty iterable
-- if the length of the table is reported as 0, that means it has non-numeric indices
return false
end
-- the slow part, verifying each index is a positive, whole number. a Lua VM built-in would be nice.
for key, _ in pairs(val) do
if type(key) ~= "number" then
return false
end
if key < 1 then
-- Lua arrays start at 1, a 0 index means it's not a pure Lua array
return false
end
-- Lua TODO: would math.floor be faster? needs a benchmark
if (key % 1) ~= 0 then
-- if the number key isn't a whole number, it's not a pure Lua array
return false
end
if key > tableLength then
-- if we get a numeric key larger than the length operator reports, this isn't a contiguous array
return false
end
if key ~= tableLength then
-- if we're not at the end of a contiguous array, the value in the index slot should be non-nil
if nil == val[key + 1] then
return false
end
end
end
return true
end
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join
exports.join = function<T>(array: Array<T>, separator: string?): string
-- this behavior is relied on by GraphQL and jest
return if 0 == #array
then ""
-- some lua implementations of concat don't behave when passed nil
else table.concat(array, separator or ",")
end
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
exports.reverse = function(array: Array<any>): Array<any>
local end_ = #array
local start = 1
while start < end_ do
-- this one-liner is a Lua idiom for swapping two values
array[start], array[end_] = array[end_], array[start]
end_ -= 1
start += 1
end
return array
end
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
exports.slice = function<T>(array: Array<T>, startIndex_: number?, endIndex_: number?): Array<T>
local length = #array
-- JS translates indices > length to be length; in Lua, we translate to length + 1
local endIndex = if (nil == endIndex_ or endIndex_ > length + 1)
then length + 1
else if endIndex_ < 1 then math.max(1, length - math.abs(endIndex_)) else endIndex_
-- JS translates negative indices to 0; in Lua, we translate it to 1
local startIndex = if startIndex_ == nil
then 1
else if startIndex_ < 1 then math.max(1, length - math.abs(startIndex_)) else startIndex_
local i = 1
local index = startIndex
local result = {}
while index < endIndex do
result[i] = array[index]
i += 1
index += 1
end
return result
end
-- this replicates the behavior that mixed Arrays of numbers and strings containing numbers
-- sort the *number* 6 before the *string* 6, and before *userdata* 6, which is relied upon by jest and GraphQL
local function builtinSort<T>(one: T, another: T): boolean
return type(another) .. tostring(another) > type(one) .. tostring(one)
end
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
type Comparator = (one: any, another: any) -> number
exports.sort = function<T>(array: Array<T>, compare: Comparator?): Array<T>
-- Lua BUG: this is a workaround for a typr solver bug where it says template types aren't compatible with `any`
local translateJsSortReturnToLuaSortReturn: (any, any) -> boolean = if compare == nil
then builtinSort
else function<T>(x: T, y: T): boolean
return 0 > compare(x, y)
end
table.sort(array, translateJsSortReturnToLuaSortReturn)
return array
end
type callbackFunction<T, U> = (value: T, index: number, array: Array<T>) -> U
type callbackFunctionWithSelfArgument<T, U> = (self: Object, value: T, index: number, array: Array<T>) -> U
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
exports.map = function<T, U>(
arr: Array<T>,
callback: callbackFunction<T, U> | callbackFunctionWithSelfArgument<T, U>,
selfArgument: Object?
): Array<U>
local i = 1
local length = #arr
local result = {}
if selfArgument == nil then
while i <= length do
local inputValue = arr[i]
-- Lua BUG: type solver says callback isn't callable, but it is
result[i] = (callback :: callbackFunction<T, U>)(inputValue, i, arr)
i += 1
end
else
while i <= length do
local inputValue = arr[i]
-- Lua BUG: type solver says callback isn't callable, but it is
result[i] = (callback :: callbackFunctionWithSelfArgument<T, U>)(selfArgument, inputValue, i, arr)
i += 1
end
end
return result
end
return exports