ranger-compiler 3.1.1 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2527 -0
- package/LICENSE +28 -0
- package/LICENSE-MIT +21 -0
- package/README.md +1650 -1875
- package/dist/Lang.rgr +10946 -5662
- package/dist/README.md +3 -2
- package/dist/api.d.ts +2023 -61
- package/dist/api.js +68930 -27355
- package/dist/lib/ACEEditor.rgr +2 -0
- package/dist/lib/Ajax.rgr +2 -0
- package/dist/lib/CmdParams.rgr +2 -0
- package/dist/lib/Crypto.rgr +2 -0
- package/dist/lib/DOMLib.rgr +2 -0
- package/dist/lib/Engine3D.rgr +2 -0
- package/dist/lib/ImmutableVector.rgr +2 -0
- package/dist/lib/IndexedDB.rgr +2 -0
- package/dist/lib/IsoDate/DateMath.rgr +2 -0
- package/dist/lib/IsoDate/IsoCalendar.rgr +2 -0
- package/dist/lib/IsoDate/IsoDateParse.rgr +2 -0
- package/dist/lib/IsoDateLib.rgr +2 -0
- package/dist/lib/JSON.rgr +4906 -48
- package/dist/lib/JinxProcess.rgr +2 -0
- package/dist/lib/RangerProcess.rgr +2 -0
- package/dist/lib/Regex/RegexMatch.rgr +2 -0
- package/dist/lib/RegexLib.rgr +2 -0
- package/dist/lib/SQL.rgr +2 -0
- package/dist/lib/ServiceLib.rgr +2 -0
- package/dist/lib/Shell.rgr +326 -0
- package/dist/lib/Storage.rgr +2 -0
- package/dist/lib/Time.rgr +2 -0
- package/dist/lib/Timers.rgr +2 -0
- package/dist/lib/TypedArrays.rgr +2 -0
- package/dist/lib/ViewLib.rgr +2 -0
- package/dist/lib/WebLib.rgr +2 -0
- package/dist/lib/WebServerLib.rgr +2 -0
- package/dist/lib/apple/AppleAppBuilder.rgr +572 -0
- package/dist/lib/apple/AppleAppSpec.rgr +201 -0
- package/dist/lib/apple/AppleDevice.rgr +295 -0
- package/dist/lib/apple/AppleDeviceDoctor.rgr +453 -0
- package/dist/lib/apple/AppleSigning.rgr +379 -0
- package/dist/lib/apple/AppleSimulator.rgr +248 -0
- package/dist/lib/apple/AppleTarget.rgr +185 -0
- package/dist/lib/apple/AppleToolchain.rgr +458 -0
- package/dist/lib/apple/README.md +311 -0
- package/dist/lib/apple/apple_test.rgr +669 -0
- package/dist/lib/core/README.md +251 -0
- package/dist/lib/core/RgBase.rgr +313 -0
- package/dist/lib/core/RgNum.rgr +653 -0
- package/dist/lib/core/RgText.rgr +680 -0
- package/dist/lib/core/RgU32.rgr +309 -0
- package/dist/lib/ranger-dir.rgr +27 -5
- package/dist/lib/shell_test.rgr +193 -0
- package/dist/lib/stdlib.rgr +1176 -665
- package/dist/lib/stdops.rgr +2 -0
- package/dist/package.json +1 -1
- package/dist/rgrc.js +73013 -37819
- package/dist/stdops.rgr +2 -0
- package/package.json +1121 -205
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
; SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
; =============================================================================
|
|
4
|
+
; RgU32 — 32-bit unsigned algebra that means the same thing on every target
|
|
5
|
+
; =============================================================================
|
|
6
|
+
; SHA-256, HMAC, clz32, imul, ToInt32 and every checksum stand on this file.
|
|
7
|
+
;
|
|
8
|
+
; It is written the way it is because of measurements, not taste. The same four
|
|
9
|
+
; expressions, compiled by this compiler and run:
|
|
10
|
+
;
|
|
11
|
+
; es6 python go cpp
|
|
12
|
+
; 2147483647 + 1 2147483648 2147483648 2147483648 -2147483648
|
|
13
|
+
; bit_shl 1 31 -2147483648 2147483648 2147483648 -2147483648
|
|
14
|
+
; bit_shl 1 40 256 1099511627776 1099511627776 0
|
|
15
|
+
; bit_ushr (0-1) 4 268435455 268435455 1152921504606846975
|
|
16
|
+
;
|
|
17
|
+
; Three separate conclusions, each of which rules out an obvious implementation:
|
|
18
|
+
;
|
|
19
|
+
; 1. `int` IS 32-BIT SIGNED ON C++. Plain ARITHMETIC overflows there — the
|
|
20
|
+
; first row is not a shift, it is `+`. So a u32 cannot be held as an int in
|
|
21
|
+
; [0, 2^32); that range does not exist on C++. Every intermediate in this
|
|
22
|
+
; file therefore stays below 2^31, and the u32 is carried as a SIGNED
|
|
23
|
+
; 32-bit BIT PATTERN, which is exactly the range C++ has and exactly what
|
|
24
|
+
; es6's bitwise operators already produce.
|
|
25
|
+
;
|
|
26
|
+
; 2. `bit_shl` IS NOT PORTABLE. On es6 it is JavaScript's `<<`: the result
|
|
27
|
+
; wraps to 32 bits and the COUNT is taken mod 32, so `1 << 40` is 256. On
|
|
28
|
+
; C++ it is undefined past the width and answers 0. On go and python it
|
|
29
|
+
; grows without bound. Shifts here are therefore bounded to 0..31 and
|
|
30
|
+
; re-wrapped, and never used to build a value wider than the pattern.
|
|
31
|
+
;
|
|
32
|
+
; 3. `bit_ushr` IS THREE DIFFERENT OPERATORS. 32-bit on es6 and python,
|
|
33
|
+
; 64-bit on go, C++, rust and swift. It is NOT USED ANYWHERE in this file.
|
|
34
|
+
; `shr` below is built out of a mask, an arithmetic shift and one
|
|
35
|
+
; re-inserted sign bit instead.
|
|
36
|
+
;
|
|
37
|
+
; What IS safe on all four, and is all this file relies on:
|
|
38
|
+
; - `bit_and` / `bit_or` / `bit_xor` / `bit_not` on two signed 32-bit patterns.
|
|
39
|
+
; Wider targets sign-extend both operands identically above bit 31, so the
|
|
40
|
+
; low 32 bits and the sign agree with the narrow ones.
|
|
41
|
+
; - `+`, `-`, `*` and `bit_shl`/`bit_shr` on values that stay under 2^31.
|
|
42
|
+
; - Literals up to 2147483647. Nothing here writes a wider one: 2^32-1 and
|
|
43
|
+
; 2^31 cannot be spelled as int literals on the C++ target at all.
|
|
44
|
+
;
|
|
45
|
+
; The cost is that and/or/xor over full patterns are single operators while
|
|
46
|
+
; `add` and `wrap` split into 16-bit halves. That is a few instructions instead
|
|
47
|
+
; of one, and it is the price of the same answer everywhere. BigIntNum.rgr made
|
|
48
|
+
; the same trade for the same reason ("a product of two limbs is at most 2^30,
|
|
49
|
+
; which is inside a 32-bit int on every target this compiles to").
|
|
50
|
+
;
|
|
51
|
+
; NOT HERE YET: `mul` (i.e. Math.imul). A 32x32 product mod 2^32 needs a 16x16
|
|
52
|
+
; partial product, and 65535 * 65535 is 4294836225 — past what a C++ int holds,
|
|
53
|
+
; so it needs 8-bit limbs and four more partial products. SHA-256, SHA-1, HMAC
|
|
54
|
+
; and the CRC family use only add/xor/and/not/rot/shr, so nothing is blocked;
|
|
55
|
+
; this is written down rather than half-done.
|
|
56
|
+
; =============================================================================
|
|
57
|
+
|
|
58
|
+
Import "RgNum.rgr"
|
|
59
|
+
|
|
60
|
+
class RgU32 {
|
|
61
|
+
|
|
62
|
+
; ---- constants that cannot be written as literals -----------------------
|
|
63
|
+
|
|
64
|
+
; 2^31, as a double, because it does not fit a C++ int.
|
|
65
|
+
static sfn twoPow31D:double () {
|
|
66
|
+
return 2147483648.0
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static sfn twoPow32D:double () {
|
|
70
|
+
return 4294967296.0
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
; The bit pattern of 0x80000000, i.e. int32 min. Built by subtraction
|
|
74
|
+
; because the literal 2147483648 overflows an int on the C++ target.
|
|
75
|
+
static sfn signBit:int () {
|
|
76
|
+
def m:int 2147483647
|
|
77
|
+
return (0 - (m + 1))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
; ---- halves -------------------------------------------------------------
|
|
81
|
+
; Everything that has to survive a wider target goes through these. Each
|
|
82
|
+
; half is in [0, 65535], so every product and sum below stays under 2^31.
|
|
83
|
+
|
|
84
|
+
static sfn loHalf:int (v:int) {
|
|
85
|
+
return (bit_and v 65535)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static sfn hiHalf:int (v:int) {
|
|
89
|
+
return (bit_and (bit_shr v 16) 65535)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
; Rebuild a signed 32-bit pattern from two 16-bit halves. The high half's
|
|
93
|
+
; top bit becomes the sign, applied by SUBTRACTION rather than by shifting a
|
|
94
|
+
; 1 into bit 31 — shifting there is the undefined case on C++.
|
|
95
|
+
static sfn fromHalves:int (hi:int lo:int) {
|
|
96
|
+
def h:int (bit_and hi 65535)
|
|
97
|
+
def l:int (bit_and lo 65535)
|
|
98
|
+
; (h & 0x7FFF) << 16 is at most 0x7FFF0000, which fits every target.
|
|
99
|
+
def r:int (bit_or (bit_shl (bit_and h 32767) 16) l)
|
|
100
|
+
if ((bit_and h 32768) != 0) {
|
|
101
|
+
return (r + (RgU32.signBit()))
|
|
102
|
+
}
|
|
103
|
+
return r
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
; Force any int into the signed 32-bit pattern its low 32 bits describe.
|
|
107
|
+
; This is ECMAScript's ToInt32 over an integer, and it is what makes a
|
|
108
|
+
; go/python result agree with an es6 one.
|
|
109
|
+
static sfn wrap32:int (v:int) {
|
|
110
|
+
return (RgU32.fromHalves((RgU32.hiHalf(v)) (RgU32.loHalf(v))))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
; ---- logical ------------------------------------------------------------
|
|
114
|
+
; Safe as single operators — see the header. Named to avoid the global
|
|
115
|
+
; operator namespace, which a static method may not enter.
|
|
116
|
+
|
|
117
|
+
static sfn band:int (a:int b:int) {
|
|
118
|
+
return (bit_and a b)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static sfn bor:int (a:int b:int) {
|
|
122
|
+
return (bit_or a b)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
static sfn bxor:int (a:int b:int) {
|
|
126
|
+
return (bit_xor a b)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static sfn bnot:int (a:int) {
|
|
130
|
+
return (RgU32.wrap32((bit_not a)))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
; ---- shifts -------------------------------------------------------------
|
|
134
|
+
; Counts are taken mod 32, as ECMAScript specifies, and then handled in
|
|
135
|
+
; range so no target sees an out-of-width shift.
|
|
136
|
+
|
|
137
|
+
static sfn shiftCount:int (n:int) {
|
|
138
|
+
def c:int (bit_and n 31)
|
|
139
|
+
return c
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static sfn shl:int (v:int n:int) {
|
|
143
|
+
def c:int (RgU32.shiftCount(n))
|
|
144
|
+
if (c == 0) {
|
|
145
|
+
return (RgU32.wrap32(v))
|
|
146
|
+
}
|
|
147
|
+
; Shift the halves separately: the wide targets would otherwise carry
|
|
148
|
+
; bits past bit 31 that es6 has already dropped.
|
|
149
|
+
def lo:int (RgU32.loHalf(v))
|
|
150
|
+
def hi:int (RgU32.hiHalf(v))
|
|
151
|
+
def full:int 0
|
|
152
|
+
if (c < 16) {
|
|
153
|
+
def newLo:int (bit_and (bit_shl lo c) 65535)
|
|
154
|
+
def carry:int (bit_shr lo (16 - c))
|
|
155
|
+
def newHi:int (bit_and ((bit_shl hi c) + carry) 65535)
|
|
156
|
+
full = (RgU32.fromHalves(newHi newLo))
|
|
157
|
+
} {
|
|
158
|
+
def d:int (c - 16)
|
|
159
|
+
def newHi:int (bit_and (bit_shl lo d) 65535)
|
|
160
|
+
full = (RgU32.fromHalves(newHi 0))
|
|
161
|
+
}
|
|
162
|
+
return full
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
; LOGICAL right shift: zeroes come in at the top. bit_ushr is unusable, so
|
|
166
|
+
; the sign bit is dropped, the rest shifted arithmetically, and the dropped
|
|
167
|
+
; bit re-inserted at its new position.
|
|
168
|
+
static sfn shr:int (v:int n:int) {
|
|
169
|
+
def c:int (RgU32.shiftCount(n))
|
|
170
|
+
if (c == 0) {
|
|
171
|
+
return (RgU32.wrap32(v))
|
|
172
|
+
}
|
|
173
|
+
def body:int (bit_and v 2147483647)
|
|
174
|
+
def r:int (bit_shr body c)
|
|
175
|
+
if (v < 0) {
|
|
176
|
+
r = (bit_or r (bit_shl 1 (31 - c)))
|
|
177
|
+
}
|
|
178
|
+
return r
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
; ARITHMETIC right shift: the sign bit is replicated. This one `bit_shr`
|
|
182
|
+
; already does correctly on every target, once the input is a real pattern.
|
|
183
|
+
static sfn sar:int (v:int n:int) {
|
|
184
|
+
def c:int (RgU32.shiftCount(n))
|
|
185
|
+
if (c == 0) {
|
|
186
|
+
return (RgU32.wrap32(v))
|
|
187
|
+
}
|
|
188
|
+
return (bit_shr (RgU32.wrap32(v)) c)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static sfn rotl:int (v:int n:int) {
|
|
192
|
+
def c:int (RgU32.shiftCount(n))
|
|
193
|
+
if (c == 0) {
|
|
194
|
+
return (RgU32.wrap32(v))
|
|
195
|
+
}
|
|
196
|
+
def left:int (RgU32.shl(v c))
|
|
197
|
+
def right:int (RgU32.shr(v (32 - c)))
|
|
198
|
+
return (bit_or left right)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
static sfn rotr:int (v:int n:int) {
|
|
202
|
+
def c:int (RgU32.shiftCount(n))
|
|
203
|
+
if (c == 0) {
|
|
204
|
+
return (RgU32.wrap32(v))
|
|
205
|
+
}
|
|
206
|
+
def right:int (RgU32.shr(v c))
|
|
207
|
+
def left:int (RgU32.shl(v (32 - c)))
|
|
208
|
+
return (bit_or left right)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
; ---- arithmetic ---------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
; (a + b) mod 2^32. Through the halves, because `a + b` itself overflows a
|
|
214
|
+
; C++ int — which is signed-overflow undefined behaviour there, not merely a
|
|
215
|
+
; wrap this code could rely on.
|
|
216
|
+
static sfn addU:int (a:int b:int) {
|
|
217
|
+
def lo:int ((RgU32.loHalf(a)) + (RgU32.loHalf(b)))
|
|
218
|
+
def carry:int (bit_shr lo 16)
|
|
219
|
+
def hi:int (((RgU32.hiHalf(a)) + (RgU32.hiHalf(b))) + carry)
|
|
220
|
+
return (RgU32.fromHalves((bit_and hi 65535) (bit_and lo 65535)))
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
static sfn sub:int (a:int b:int) {
|
|
224
|
+
return (RgU32.addU(a (RgU32.addU((RgU32.bnot(b)) 1))))
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
; ---- unsigned view ------------------------------------------------------
|
|
228
|
+
; The pattern as a NON-NEGATIVE value. It has to be a double: [0, 2^32) is
|
|
229
|
+
; not an int range on the C++ target.
|
|
230
|
+
|
|
231
|
+
static sfn toUnsignedD:double (v:int) {
|
|
232
|
+
if (v >= 0) {
|
|
233
|
+
return (to_double v)
|
|
234
|
+
}
|
|
235
|
+
return ((to_double v) + (RgU32.twoPow32D()))
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
; A non-negative double in [0, 2^32) back to a pattern.
|
|
239
|
+
static sfn fromUnsignedD:int (d:double) {
|
|
240
|
+
def two32:double (RgU32.twoPow32D())
|
|
241
|
+
def x:double d
|
|
242
|
+
; Reduce into range without to_int, which saturates at 32 bits on C++.
|
|
243
|
+
def hiD:double 0.0
|
|
244
|
+
def loD:double 0.0
|
|
245
|
+
if (x < 0.0) {
|
|
246
|
+
x = (x + two32)
|
|
247
|
+
}
|
|
248
|
+
if (x >= two32) {
|
|
249
|
+
x = (x - (two32 * (RgNum.floorD((x / two32)))))
|
|
250
|
+
}
|
|
251
|
+
hiD = (RgNum.floorD((x / 65536.0)))
|
|
252
|
+
loD = (x - (hiD * 65536.0))
|
|
253
|
+
return (RgU32.fromHalves((to_int hiD) (to_int loD)))
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
; ---- bit counting -------------------------------------------------------
|
|
257
|
+
|
|
258
|
+
; Leading zeroes in the 32-bit pattern; 32 for zero. This is Math.clz32.
|
|
259
|
+
static sfn clz:int (v:int) {
|
|
260
|
+
def p:int (RgU32.wrap32(v))
|
|
261
|
+
if (p == 0) {
|
|
262
|
+
return 32
|
|
263
|
+
}
|
|
264
|
+
if (p < 0) {
|
|
265
|
+
return 0
|
|
266
|
+
}
|
|
267
|
+
def n:int 0
|
|
268
|
+
def probe:int 1073741824
|
|
269
|
+
while (n < 31) {
|
|
270
|
+
if ((bit_and p probe) != 0) {
|
|
271
|
+
return (n + 1)
|
|
272
|
+
}
|
|
273
|
+
probe = (bit_shr probe 1)
|
|
274
|
+
n = (n + 1)
|
|
275
|
+
}
|
|
276
|
+
return 31
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
static sfn popcount:int (v:int) {
|
|
280
|
+
def lo:int (RgU32.loHalf(v))
|
|
281
|
+
def hi:int (RgU32.hiHalf(v))
|
|
282
|
+
def n:int 0
|
|
283
|
+
def i:int 0
|
|
284
|
+
while (i < 16) {
|
|
285
|
+
if ((bit_and (bit_shr lo i) 1) != 0) {
|
|
286
|
+
n = (n + 1)
|
|
287
|
+
}
|
|
288
|
+
if ((bit_and (bit_shr hi i) 1) != 0) {
|
|
289
|
+
n = (n + 1)
|
|
290
|
+
}
|
|
291
|
+
i = (i + 1)
|
|
292
|
+
}
|
|
293
|
+
return n
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
; ---- byte access --------------------------------------------------------
|
|
297
|
+
; Big-endian, because every digest and every length field is.
|
|
298
|
+
|
|
299
|
+
static sfn byteAt:int (v:int i:int) {
|
|
300
|
+
def sh:int (8 * (3 - i))
|
|
301
|
+
return (bit_and (RgU32.shr(v sh)) 255)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
static sfn fromBytesBE:int (b0:int b1:int b2:int b3:int) {
|
|
305
|
+
def hi:int ((bit_shl (bit_and b0 255) 8) + (bit_and b1 255))
|
|
306
|
+
def lo:int ((bit_shl (bit_and b2 255) 8) + (bit_and b3 255))
|
|
307
|
+
return (RgU32.fromHalves(hi lo))
|
|
308
|
+
}
|
|
309
|
+
}
|
package/dist/lib/ranger-dir.rgr
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
; SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
1
3
|
|
|
2
4
|
systemclass UIElement {
|
|
3
5
|
es6 DOMElement
|
|
@@ -118,24 +120,35 @@ class DirListReader {
|
|
|
118
120
|
(create_polyfill
|
|
119
121
|
"
|
|
120
122
|
|
|
121
|
-
#ifdef
|
|
123
|
+
#ifdef _WIN32
|
|
122
124
|
#include <windows.h>
|
|
123
125
|
#else
|
|
126
|
+
#include <dirent.h>
|
|
124
127
|
#endif
|
|
125
128
|
|
|
126
129
|
std::vector<std::string> __cpp_list_directory_files(std::string dirName) {
|
|
127
|
-
WIN32_FIND_DATA data;
|
|
128
130
|
std::vector<std::string> fileList;
|
|
131
|
+
#ifdef _WIN32
|
|
132
|
+
WIN32_FIND_DATA data;
|
|
129
133
|
std::string query = (dirName + \"\\\\*\");
|
|
130
|
-
HANDLE hFind = FindFirstFile(query.c_str(), &data);
|
|
131
|
-
|
|
134
|
+
HANDLE hFind = FindFirstFile(query.c_str(), &data);
|
|
132
135
|
if ( hFind != INVALID_HANDLE_VALUE ) {
|
|
133
136
|
do {
|
|
134
137
|
std::string name(data.cFileName);
|
|
135
138
|
fileList.push_back( name );
|
|
136
139
|
} while (FindNextFile(hFind, &data));
|
|
137
140
|
FindClose(hFind);
|
|
138
|
-
}
|
|
141
|
+
}
|
|
142
|
+
#else
|
|
143
|
+
DIR* dir = opendir(dirName.c_str());
|
|
144
|
+
if (dir != nullptr) {
|
|
145
|
+
struct dirent* ent;
|
|
146
|
+
while ((ent = readdir(dir)) != nullptr) {
|
|
147
|
+
fileList.push_back(std::string(ent->d_name));
|
|
148
|
+
}
|
|
149
|
+
closedir(dir);
|
|
150
|
+
}
|
|
151
|
+
#endif
|
|
139
152
|
return fileList;
|
|
140
153
|
}
|
|
141
154
|
"
|
|
@@ -147,6 +160,15 @@ std::vector<std::string> __cpp_list_directory_files(std::string dirName) {
|
|
|
147
160
|
is_directory cmdFiles:boolean (path:string) {
|
|
148
161
|
templates {
|
|
149
162
|
es6 ( "require('fs').statSync(" (e 1) ").isDirectory()" )
|
|
163
|
+
cpp ( "r_cpp_is_directory(" (e 1) ")" (imp "<sys/stat.h>") (imp "<string>"
|
|
164
|
+
(polyfill "
|
|
165
|
+
static int r_cpp_is_directory(const std::string& path) {
|
|
166
|
+
struct stat st;
|
|
167
|
+
if (stat(path.c_str(), &st) != 0) { return 0; }
|
|
168
|
+
return S_ISDIR(st.st_mode) ? 1 : 0;
|
|
169
|
+
}
|
|
170
|
+
")
|
|
171
|
+
)
|
|
150
172
|
}
|
|
151
173
|
}
|
|
152
174
|
is_file cmdFiles:boolean (path:string) {
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
; SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
; =============================================================================
|
|
4
|
+
; shell_test.rgr -- `lib/Shell.rgr` and the `run_process_result` operator.
|
|
5
|
+
; =============================================================================
|
|
6
|
+
;
|
|
7
|
+
; These tests really do start child processes: the point of this library is that
|
|
8
|
+
; a Ranger program can call another program, and a test that mocked that away
|
|
9
|
+
; would be testing nothing. Everything it runs is `sh`, `printenv` and `ls`,
|
|
10
|
+
; which every POSIX machine has.
|
|
11
|
+
;
|
|
12
|
+
; npm run shell:test
|
|
13
|
+
;
|
|
14
|
+
; The same file compiled to another target and run there is how the operator's
|
|
15
|
+
; other backends are checked -- the assertions do not know which language they
|
|
16
|
+
; are in.
|
|
17
|
+
; =============================================================================
|
|
18
|
+
|
|
19
|
+
Import "Shell.rgr"
|
|
20
|
+
|
|
21
|
+
class ShellTest {
|
|
22
|
+
|
|
23
|
+
def passed:int 0
|
|
24
|
+
def failed:int 0
|
|
25
|
+
|
|
26
|
+
; Argument vectors, built rather than written inline.
|
|
27
|
+
;
|
|
28
|
+
; A one-element typed array literal handed straight to a call is flattened
|
|
29
|
+
; to the bare string on the Rust target (ISSUES.md #84), and ANY array
|
|
30
|
+
; literal is lost when the call taking it is immediately dereferenced on
|
|
31
|
+
; every target (#85). Every vector in this file goes through one of these
|
|
32
|
+
; instead. They also read better than the literal does.
|
|
33
|
+
sfn one:[string] (a:string) {
|
|
34
|
+
def v:[string]
|
|
35
|
+
push v a
|
|
36
|
+
return v
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
sfn two:[string] (a:string b:string) {
|
|
40
|
+
def v:[string]
|
|
41
|
+
push v a
|
|
42
|
+
push v b
|
|
43
|
+
return v
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
sfn four:[string] (a:string b:string c:string d:string) {
|
|
47
|
+
def v:[string]
|
|
48
|
+
push v a
|
|
49
|
+
push v b
|
|
50
|
+
push v c
|
|
51
|
+
push v d
|
|
52
|
+
return v
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fn check:void (name:string cond:boolean) {
|
|
56
|
+
if cond {
|
|
57
|
+
passed = passed + 1
|
|
58
|
+
} {
|
|
59
|
+
failed = failed + 1
|
|
60
|
+
print " FAIL " + name
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn run:void () {
|
|
65
|
+
def sh:Shell (new Shell)
|
|
66
|
+
|
|
67
|
+
; --- capturing --------------------------------------------------------
|
|
68
|
+
def hello:ShellResult (sh.capture("sh" (ShellTest.two("-c" "echo hello-shell"))))
|
|
69
|
+
this.check("a captured command succeeds" (hello.ok()))
|
|
70
|
+
this.check("and its output comes back trimmed" ((hello.outText()) == "hello-shell"))
|
|
71
|
+
this.check("and nothing is on stderr" ((hello.errText()) == ""))
|
|
72
|
+
this.check("and it was not skipped" (hello.skipped == false))
|
|
73
|
+
|
|
74
|
+
; An exit code and a stderr are two different answers and both are
|
|
75
|
+
; needed: a tool that failed usually says why on stderr and says
|
|
76
|
+
; nothing at all through its status.
|
|
77
|
+
def bad:ShellResult (sh.capture("sh" (ShellTest.two("-c" "echo to-err 1>&2; exit 7"))))
|
|
78
|
+
this.check("a failing command reports its code" (bad.code == 7))
|
|
79
|
+
this.check("and it failed" (bad.failed()))
|
|
80
|
+
this.check("and stderr came back separately" ((bad.errText()) == "to-err"))
|
|
81
|
+
this.check("the failure describes itself" ((indexOf (bad.describeFailure()) "exit 7") > 0))
|
|
82
|
+
this.check("and names the command" ((indexOf (bad.describeFailure()) "to-err") > 0))
|
|
83
|
+
|
|
84
|
+
; --- streaming --------------------------------------------------------
|
|
85
|
+
; Nothing to assert about what reached the terminal, but the exit code
|
|
86
|
+
; still has to arrive, and that is the half a build driver acts on.
|
|
87
|
+
def streamed:ShellResult (sh.run("sh" (ShellTest.two("-c" "exit 5"))))
|
|
88
|
+
this.check("a streamed command reports its code" (streamed.code == 5))
|
|
89
|
+
this.check("and captures nothing" ((streamed.out == "") && (streamed.err == "")))
|
|
90
|
+
|
|
91
|
+
; --- the conveniences -------------------------------------------------
|
|
92
|
+
this.check("text answers the trimmed stdout" ((sh.text("sh" (ShellTest.two("-c" "printf one")))) == "one"))
|
|
93
|
+
this.check("text answers nothing for a failure" ((sh.text("sh" (ShellTest.two("-c" "printf one; exit 1")))) == ""))
|
|
94
|
+
this.check("succeeds is true for a zero exit" (sh.succeeds("sh" (ShellTest.two("-c" "true")))))
|
|
95
|
+
this.check("and false otherwise" ((sh.succeeds("sh" (ShellTest.two("-c" "false")))) == false))
|
|
96
|
+
|
|
97
|
+
def lines:ShellResult (sh.capture("sh" (ShellTest.two("-c" "printf 'a\nb\nc\n'"))))
|
|
98
|
+
this.check("output splits into lines" ((array_length (lines.outLines())) == 3))
|
|
99
|
+
this.check("and the first one is readable on its own" ((lines.outFirstLine()) == "a"))
|
|
100
|
+
def empty:ShellResult (sh.capture("sh" (ShellTest.two("-c" "true"))))
|
|
101
|
+
this.check("no output is no lines" ((array_length (empty.outLines())) == 0))
|
|
102
|
+
this.check("and no first line" ((empty.outFirstLine()) == ""))
|
|
103
|
+
|
|
104
|
+
; --- finding a program ------------------------------------------------
|
|
105
|
+
this.check("a program on PATH is found" (sh.isInstalled("sh")))
|
|
106
|
+
this.check("and one that is not is not" ((sh.isInstalled("no-such-program-xyz")) == false))
|
|
107
|
+
def where@(optional):string (sh.which("sh"))
|
|
108
|
+
this.check("which answers a path" (!null? where))
|
|
109
|
+
if (!null? where) {
|
|
110
|
+
this.check("and it looks like one" ((indexOf (unwrap where) "/") == 0))
|
|
111
|
+
}
|
|
112
|
+
def nowhere@(optional):string (sh.which("no-such-program-xyz"))
|
|
113
|
+
this.check("which answers absent for a program that is not there" (null? nowhere))
|
|
114
|
+
|
|
115
|
+
; --- the working directory --------------------------------------------
|
|
116
|
+
def inLib:Shell (new Shell)
|
|
117
|
+
inLib.cwd = "lib"
|
|
118
|
+
; Bound rather than chained: a call result that is immediately
|
|
119
|
+
; dereferenced loses the array literal in its arguments (ISSUES.md
|
|
120
|
+
; #85), and on JavaScript the failure is a runtime type error rather
|
|
121
|
+
; than a compile one.
|
|
122
|
+
def hereArgs:[string] (ShellTest.one("Shell.rgr"))
|
|
123
|
+
def here:ShellResult (inLib.capture("ls" hereArgs))
|
|
124
|
+
this.check("cwd is where the command runs" (here.ok()))
|
|
125
|
+
def elsewhereArgs:[string] (ShellTest.one("package.json"))
|
|
126
|
+
def elsewhere:ShellResult (inLib.capture("ls" elsewhereArgs))
|
|
127
|
+
this.check("and a file that is elsewhere is not there" (elsewhere.failed()))
|
|
128
|
+
|
|
129
|
+
; --- the environment --------------------------------------------------
|
|
130
|
+
def env:Shell (new Shell)
|
|
131
|
+
env.setEnv("RANGER_SHELL_PROBE" "set-by-ranger")
|
|
132
|
+
this.check("an environment entry reaches the child" ((env.text("printenv" (ShellTest.one("RANGER_SHELL_PROBE")))) == "set-by-ranger"))
|
|
133
|
+
; MERGED, not replaced: a child that lost PATH could not find anything,
|
|
134
|
+
; and the failure would look like the program being missing.
|
|
135
|
+
this.check("and PATH survives it" ((strlen (env.text("printenv" (ShellTest.one("PATH"))))) > 0))
|
|
136
|
+
env.setEnv("RANGER_SHELL_PROBE" "replaced")
|
|
137
|
+
this.check("setting one again replaces it" ((env.text("printenv" (ShellTest.one("RANGER_SHELL_PROBE")))) == "replaced"))
|
|
138
|
+
this.check("rather than adding a second" ((array_length env.env) == 1))
|
|
139
|
+
this.check("and the logged line shows it" ((indexOf (env.commandLine("cc" (ShellTest.one("-c")))) "RANGER_SHELL_PROBE=replaced") == 0))
|
|
140
|
+
|
|
141
|
+
; --- a program that is not there --------------------------------------
|
|
142
|
+
; Every backend answers a non-zero code; which one differs, because the
|
|
143
|
+
; targets that go through a shell get the shell's 127.
|
|
144
|
+
def missing:ShellResult (sh.capture("no-such-program-xyz" (ShellTest.one("x"))))
|
|
145
|
+
this.check("a missing program is a failure, not a crash" (missing.failed()))
|
|
146
|
+
|
|
147
|
+
; --- quoting ----------------------------------------------------------
|
|
148
|
+
; The arguments are passed as a vector, so nothing here is ever re-read
|
|
149
|
+
; by a shell. The quoting is for the LOG line -- and a build driver
|
|
150
|
+
; whose log cannot be pasted back into a terminal is a build driver
|
|
151
|
+
; whose log is decorative.
|
|
152
|
+
def line:string (sh.commandLine("xcrun" (ShellTest.four("-sdk" "iphonesimulator" "swiftc" "/Users/me/My Projects/a.swift"))))
|
|
153
|
+
this.check("a plain argument is left alone" ((indexOf line "-sdk iphonesimulator") > 0))
|
|
154
|
+
this.check("a path with a space is quoted" ((indexOf line "'/Users/me/My Projects/a.swift'") > 0))
|
|
155
|
+
this.check("an empty argument is visible" ((sh.commandLine("x" (ShellTest.one("")))) == "x ''"))
|
|
156
|
+
|
|
157
|
+
; ...and the vector really is a vector: this argument would be three
|
|
158
|
+
; words and a redirection if anything in the chain went through a shell.
|
|
159
|
+
def literal:ShellResult (sh.capture("printf" (ShellTest.two("%s" "a b > c"))))
|
|
160
|
+
this.check("an argument is never re-parsed as syntax" ((literal.out) == "a b > c"))
|
|
161
|
+
|
|
162
|
+
; --- the dry run ------------------------------------------------------
|
|
163
|
+
; The reason a driver written on this can be tested at all.
|
|
164
|
+
def dry:Shell (new Shell)
|
|
165
|
+
dry.dryRun = true
|
|
166
|
+
def d:ShellResult (dry.run("rm" (ShellTest.two("-rf" "/"))))
|
|
167
|
+
this.check("a dry run does not run" (d.skipped))
|
|
168
|
+
this.check("and answers success" (d.ok()))
|
|
169
|
+
this.check("and records what it would have run" ((array_length dry.commands) == 1))
|
|
170
|
+
this.check("as a readable line" ((itemAt dry.commands 0) == "rm -rf /"))
|
|
171
|
+
def d2:ShellResult (dry.capture("ls" (ShellTest.one("-la"))))
|
|
172
|
+
this.check("captures are recorded too" ((array_length dry.commands) == 2))
|
|
173
|
+
this.check("and answer no output" ((d2.out == "") && (d2.err == "")))
|
|
174
|
+
|
|
175
|
+
; Every command is logged whether it ran or not, which is what makes a
|
|
176
|
+
; log a record rather than a highlight reel.
|
|
177
|
+
def logged:Shell (new Shell)
|
|
178
|
+
logged.capture("sh" (ShellTest.two("-c" "true")))
|
|
179
|
+
logged.capture("sh" (ShellTest.two("-c" "false")))
|
|
180
|
+
this.check("a real shell logs everything it ran" ((array_length logged.commands) == 2))
|
|
181
|
+
this.check("and remembers the last exit code" (logged.lastCode == 1))
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
sfn m@(main):void () {
|
|
185
|
+
def t:ShellTest (new ShellTest)
|
|
186
|
+
print "Shell and run_process_result"
|
|
187
|
+
t.run()
|
|
188
|
+
print " " + t.passed + " passed, " + t.failed + " failed"
|
|
189
|
+
if (t.failed > 0) {
|
|
190
|
+
exit 1
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|