srsh 0.8.0 → 1.0.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.
- checksums.yaml +4 -4
- data/LICENSE +12 -3
- data/README.md +446 -8
- data/bin/srsh +71 -0
- data/docs/assets/slut.txt +4 -0
- data/docs/assets/srsh-mark.svg +12 -0
- data/docs/css/style.css +696 -0
- data/docs/index.html +703 -0
- data/docs/js/app.js +203 -0
- data/examples/bridge.rsh +8 -0
- data/examples/calculator.rsh +253 -0
- data/examples/defer.rsh +14 -0
- data/examples/hot.rsh +14 -0
- data/examples/meta.rsh +20 -0
- data/examples/modules/text.rsh +6 -0
- data/examples/modules.rsh +6 -0
- data/examples/paste.rsh +15 -0
- data/examples/plugin.rb +8 -0
- data/examples/power.rsh +65 -0
- data/examples/tour.rsh +38 -0
- data/ext/srsh_native/extconf.rb +3 -0
- data/ext/srsh_native/srsh_native.c +48 -0
- data/language-docs/LANGUAGE.md +670 -0
- data/language-docs/MIGRATION.md +44 -0
- data/language-docs/SECURITY.md +44 -0
- data/lib/srsh/app.rb +261 -0
- data/lib/srsh/builtins.rb +492 -0
- data/lib/srsh/editor.rb +530 -0
- data/lib/srsh/errors.rb +23 -0
- data/lib/srsh/history.rb +74 -0
- data/lib/srsh/language/evaluator.rb +1175 -0
- data/lib/srsh/language/lexer.rb +316 -0
- data/lib/srsh/language/parser.rb +997 -0
- data/lib/srsh/language/token.rb +5 -0
- data/lib/srsh/language/values.rb +392 -0
- data/lib/srsh/paths.rb +29 -0
- data/lib/srsh/plugins.rb +59 -0
- data/lib/srsh/process_identity.rb +38 -0
- data/lib/srsh/security.rb +38 -0
- data/lib/srsh/shell/executor.rb +1182 -0
- data/lib/srsh/shell/job.rb +101 -0
- data/lib/srsh/shell/lexer.rb +114 -0
- data/lib/srsh/shell/terminal.rb +26 -0
- data/lib/srsh/state.rb +136 -0
- data/lib/srsh/theme.rb +108 -0
- data/lib/srsh/version.rb +3 -0
- data/lib/srsh.rb +11 -5
- metadata +61 -14
- data/exe/srsh +0 -6
- data/lib/srsh/runner.rb +0 -2416
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#include <ruby.h>
|
|
2
|
+
#include <string.h>
|
|
3
|
+
#ifdef __linux__
|
|
4
|
+
#include <sys/prctl.h>
|
|
5
|
+
#endif
|
|
6
|
+
|
|
7
|
+
static VALUE ident_end(VALUE self, VALUE str, VALUE offv) {
|
|
8
|
+
Check_Type(str, T_STRING);
|
|
9
|
+
long off = NUM2LONG(offv);
|
|
10
|
+
long len = RSTRING_LEN(str);
|
|
11
|
+
const unsigned char *p = (const unsigned char *)RSTRING_PTR(str);
|
|
12
|
+
if (off < 0 || off > len) rb_raise(rb_eArgError, "offset out of range");
|
|
13
|
+
|
|
14
|
+
long i = off;
|
|
15
|
+
while (i < len) {
|
|
16
|
+
unsigned char c = p[i];
|
|
17
|
+
if (!(c == '_' || (c >= 'a' && c <= 'z') ||
|
|
18
|
+
(c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))) break;
|
|
19
|
+
i++;
|
|
20
|
+
}
|
|
21
|
+
return LONG2NUM(i);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static VALUE ascii_space(VALUE self, VALUE chv) {
|
|
25
|
+
int c = NUM2INT(chv);
|
|
26
|
+
return (c == ' ' || c == '\t' || c == '\r' || c == '\n') ? Qtrue : Qfalse;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static VALUE set_process_name(VALUE self, VALUE namev) {
|
|
30
|
+
#ifdef __linux__
|
|
31
|
+
const char *name = StringValueCStr(namev);
|
|
32
|
+
char short_name[16];
|
|
33
|
+
memset(short_name, 0, sizeof(short_name));
|
|
34
|
+
strncpy(short_name, name, sizeof(short_name) - 1);
|
|
35
|
+
return prctl(PR_SET_NAME, (unsigned long)short_name, 0, 0, 0) == 0 ? Qtrue : Qfalse;
|
|
36
|
+
#else
|
|
37
|
+
(void)self;
|
|
38
|
+
(void)namev;
|
|
39
|
+
return Qfalse;
|
|
40
|
+
#endif
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
void Init_srsh_native(void) {
|
|
44
|
+
VALUE m = rb_define_module("SrshNative");
|
|
45
|
+
rb_define_singleton_method(m, "ident_end", ident_end, 2);
|
|
46
|
+
rb_define_singleton_method(m, "ascii_space?", ascii_space, 1);
|
|
47
|
+
rb_define_singleton_method(m, "set_process_name", set_process_name, 1);
|
|
48
|
+
}
|
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
# RSH language guide
|
|
2
|
+
|
|
3
|
+
RSH is the programming language inside Simple Ruby Shell. It is meant for shell work first, but it is not restricted to shell-shaped programs.
|
|
4
|
+
|
|
5
|
+
There are two rules worth knowing before the syntax dump:
|
|
6
|
+
|
|
7
|
+
1. normal Unix commands should still look normal;
|
|
8
|
+
2. when RSH crosses a boundary, the syntax should tell you which boundary it crossed.
|
|
9
|
+
|
|
10
|
+
That is why `|`, `|>`, `$()` and `bridge` are different things instead of one overloaded mega-pipe.
|
|
11
|
+
|
|
12
|
+
## Commands and values
|
|
13
|
+
|
|
14
|
+
This is a Unix pipeline:
|
|
15
|
+
|
|
16
|
+
```rsh
|
|
17
|
+
printf 'c\na\nb\n' | sort
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This is an RSH value pipeline:
|
|
21
|
+
|
|
22
|
+
```rsh
|
|
23
|
+
[3, 1, 2] |> sort |> map(::x => x * 10)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This brings command output into RSH:
|
|
27
|
+
|
|
28
|
+
```rsh
|
|
29
|
+
kernel := $(uname -r)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This calls C without starting another process:
|
|
33
|
+
|
|
34
|
+
```rsh
|
|
35
|
+
bridge c from "@self"
|
|
36
|
+
strlen(cstr) -> usize
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
= c.strlen("abc")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Those are four different execution/data models, so they are four visibly different forms.
|
|
43
|
+
|
|
44
|
+
## Bindings
|
|
45
|
+
|
|
46
|
+
```rsh
|
|
47
|
+
name := "Robert"
|
|
48
|
+
count := 4
|
|
49
|
+
$counted := "environment value"
|
|
50
|
+
$EDITOR := "vim"
|
|
51
|
+
|
|
52
|
+
count += 1
|
|
53
|
+
name ++= "!"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`:=` always creates a binding in the current lexical scope. Compound assignment updates the nearest existing local.
|
|
57
|
+
|
|
58
|
+
A leading `$` on an assignment means the process environment. Worker tasks are not allowed to mutate the process environment behind the owner thread's back.
|
|
59
|
+
|
|
60
|
+
At the shell-command layer, locals and environment variables can be expanded with `$name`, `${name}`, `$1`, `$?`, and `$!`.
|
|
61
|
+
|
|
62
|
+
## Literals
|
|
63
|
+
|
|
64
|
+
```rsh
|
|
65
|
+
42
|
|
66
|
+
-12
|
|
67
|
+
0xff
|
|
68
|
+
0b1010
|
|
69
|
+
0o755
|
|
70
|
+
1_000_000
|
|
71
|
+
3.14159
|
|
72
|
+
2.5e6
|
|
73
|
+
|
|
74
|
+
yes
|
|
75
|
+
no
|
|
76
|
+
void
|
|
77
|
+
|
|
78
|
+
"hello #{name}"
|
|
79
|
+
'literal #{name}'
|
|
80
|
+
[[raw text #{name}]]
|
|
81
|
+
|
|
82
|
+
[1, 2, 3]
|
|
83
|
+
%[name: "srsh", ready: yes]
|
|
84
|
+
1 .. 10
|
|
85
|
+
0 ..< 10
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Double strings interpolate full RSH expressions. Single and raw strings do not.
|
|
89
|
+
|
|
90
|
+
`%[...]` is a map literal. Braces are intentionally not block syntax in RSH, which keeps shell text and language blocks from fighting over the same punctuation.
|
|
91
|
+
|
|
92
|
+
## Operators
|
|
93
|
+
|
|
94
|
+
From loose to tight, the useful groups are roughly:
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
??
|
|
98
|
+
or ||
|
|
99
|
+
and &&
|
|
100
|
+
== != === !== =~ !~ in
|
|
101
|
+
< <= > >=
|
|
102
|
+
|>
|
|
103
|
+
.. ..<
|
|
104
|
+
+ - ++
|
|
105
|
+
* / %
|
|
106
|
+
**
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`++` is string concatenation. `+` stays numeric unless coercion makes sense.
|
|
110
|
+
|
|
111
|
+
`===` / `!==` are strict comparisons. `==` / `!=` keep the shell-friendly numeric comparison behavior.
|
|
112
|
+
|
|
113
|
+
Regex operators use a string pattern:
|
|
114
|
+
|
|
115
|
+
```rsh
|
|
116
|
+
? name =~ "^[A-Z]" => = "starts uppercase"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Value pipelines
|
|
120
|
+
|
|
121
|
+
`|>` sends the left value to the function on the right as its first argument:
|
|
122
|
+
|
|
123
|
+
```rsh
|
|
124
|
+
files := glob("src/**/*.rb")
|
|
125
|
+
|> reject(::p => contains(p, "/vendor/"))
|
|
126
|
+
|> map(::p => basename(p))
|
|
127
|
+
|> uniq
|
|
128
|
+
|> sort
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The parser treats `|>` as an expression continuation, so long pipelines can be laid out vertically without backslashes.
|
|
132
|
+
|
|
133
|
+
Normal `|` is reserved for process pipelines. That separation is one of the main RSH design choices.
|
|
134
|
+
|
|
135
|
+
## Functions and lambdas
|
|
136
|
+
|
|
137
|
+
Readable function:
|
|
138
|
+
|
|
139
|
+
```rsh
|
|
140
|
+
fn scale(x, by := 2)
|
|
141
|
+
return x * by
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Expression function:
|
|
146
|
+
|
|
147
|
+
```rsh
|
|
148
|
+
fn scale(x, by := 2) => x * by
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The expression can start on the next physical line too, which is handy while pasting:
|
|
152
|
+
|
|
153
|
+
```rsh
|
|
154
|
+
fn scale(x, by := 2) =>
|
|
155
|
+
x * by
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Hot spelling:
|
|
159
|
+
|
|
160
|
+
```rsh
|
|
161
|
+
:: scale(x, by := 2) => x * by
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Anonymous callables:
|
|
165
|
+
|
|
166
|
+
```rsh
|
|
167
|
+
::x => x * 2
|
|
168
|
+
::(a, b) => a + b
|
|
169
|
+
::(head, *tail) => tail |> len
|
|
170
|
+
:: => clock()
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Named functions are first-class values:
|
|
174
|
+
|
|
175
|
+
```rsh
|
|
176
|
+
fn square(x) => x * x
|
|
177
|
+
= 1 .. 10 |> map(square) |> sum
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Closures capture lexical values.
|
|
181
|
+
|
|
182
|
+
## If / guards
|
|
183
|
+
|
|
184
|
+
Readable:
|
|
185
|
+
|
|
186
|
+
```rsh
|
|
187
|
+
if score >= 90
|
|
188
|
+
emit "great"
|
|
189
|
+
else
|
|
190
|
+
emit "keep going"
|
|
191
|
+
end
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
One line:
|
|
195
|
+
|
|
196
|
+
```rsh
|
|
197
|
+
if score >= 90 => emit "great"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Hot form:
|
|
201
|
+
|
|
202
|
+
```rsh
|
|
203
|
+
? score >= 90
|
|
204
|
+
emit "great"
|
|
205
|
+
:?
|
|
206
|
+
emit "keep going"
|
|
207
|
+
.?
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Hot guard:
|
|
211
|
+
|
|
212
|
+
```rsh
|
|
213
|
+
? score >= 90 => emit "great"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Loops
|
|
217
|
+
|
|
218
|
+
Readable:
|
|
219
|
+
|
|
220
|
+
```rsh
|
|
221
|
+
each users -> user
|
|
222
|
+
= user.name
|
|
223
|
+
end
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Hot:
|
|
227
|
+
|
|
228
|
+
```rsh
|
|
229
|
+
@ users -> user
|
|
230
|
+
= user.name
|
|
231
|
+
.@
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
One-line forms:
|
|
235
|
+
|
|
236
|
+
```rsh
|
|
237
|
+
each users -> user => = user.name
|
|
238
|
+
@ users -> user => = user.name
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Pairs can destructure in the loop head:
|
|
242
|
+
|
|
243
|
+
```rsh
|
|
244
|
+
each %[a: 1, b: 2] -> key, value
|
|
245
|
+
= "#{key}=#{value}"
|
|
246
|
+
end
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Integers iterate `0...N`.
|
|
250
|
+
|
|
251
|
+
While loops:
|
|
252
|
+
|
|
253
|
+
```rsh
|
|
254
|
+
while pending
|
|
255
|
+
work()
|
|
256
|
+
end
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
or:
|
|
260
|
+
|
|
261
|
+
```rsh
|
|
262
|
+
@? pending
|
|
263
|
+
work()
|
|
264
|
+
.@
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
`break` / `continue` have hot aliases `^!` / `^>`.
|
|
268
|
+
|
|
269
|
+
## Destructuring
|
|
270
|
+
|
|
271
|
+
```rsh
|
|
272
|
+
head, second, *rest := [10, 20, 30, 40]
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
A single rest name is allowed and must be last.
|
|
276
|
+
|
|
277
|
+
## Pattern matching
|
|
278
|
+
|
|
279
|
+
```rsh
|
|
280
|
+
match status
|
|
281
|
+
| 200..299 => = "ok"
|
|
282
|
+
| [401,403] => = "auth"
|
|
283
|
+
| ? it >= 500 => = "server"
|
|
284
|
+
| _ => = "other"
|
|
285
|
+
end
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Hot form:
|
|
289
|
+
|
|
290
|
+
```rsh
|
|
291
|
+
?? status
|
|
292
|
+
| 200..299 ->
|
|
293
|
+
= "ok"
|
|
294
|
+
| _ ->
|
|
295
|
+
= "other"
|
|
296
|
+
.??
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Patterns can be values, ranges, lists, partial maps, prototype references, or guard expressions beginning with `?` / `when`.
|
|
300
|
+
|
|
301
|
+
## Safe access and null coalescing
|
|
302
|
+
|
|
303
|
+
```rsh
|
|
304
|
+
cfg := json(readfile("config.json"))
|
|
305
|
+
host := cfg?.server?.host ?? "localhost"
|
|
306
|
+
first := cfg?.hosts?[0] ?? host
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Normal `.` and `[]` are strict. `?.` and `?[]` return `void` when the access cannot be completed.
|
|
310
|
+
|
|
311
|
+
## Functional collection toolbox
|
|
312
|
+
|
|
313
|
+
```text
|
|
314
|
+
map filter reject fold find any all count sum
|
|
315
|
+
each sort uniq flat zip enumerate take drop chunk group
|
|
316
|
+
tap partial compose
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Most collection operations also have method form:
|
|
320
|
+
|
|
321
|
+
```rsh
|
|
322
|
+
= [1,2,3,4].filter(::x => x % 2 == 0).map(::x => x ** 2).sum()
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Use whichever reads better.
|
|
326
|
+
|
|
327
|
+
## Prototypes and traits
|
|
328
|
+
|
|
329
|
+
RSH uses composable prototypes rather than class inheritance.
|
|
330
|
+
|
|
331
|
+
```rsh
|
|
332
|
+
trait Printable
|
|
333
|
+
fn show()
|
|
334
|
+
= "#{self.name}=#{self.value}"
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
proto Counter(name, start := 0) with Printable
|
|
339
|
+
slot name := name
|
|
340
|
+
slot value := start
|
|
341
|
+
|
|
342
|
+
fn inc(by := 1)
|
|
343
|
+
self.value += by
|
|
344
|
+
return self
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
counter := Counter("requests", 10)
|
|
349
|
+
counter.inc()
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
`self` is implicit in methods. Slot compound updates use synchronized object updates.
|
|
353
|
+
|
|
354
|
+
Reflection helpers include `fields()`, `methods()`, `protoof()`, `is()` and `clone()`.
|
|
355
|
+
|
|
356
|
+
## Namespaces
|
|
357
|
+
|
|
358
|
+
Inline namespace:
|
|
359
|
+
|
|
360
|
+
```rsh
|
|
361
|
+
space build
|
|
362
|
+
root := "out"
|
|
363
|
+
fn artifact(name) => root ++ "/" ++ name
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
= build.artifact("app")
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
A `space` can hold bindings, functions, tasks, prototypes, traits, nested spaces, bridges, modules and code declarations.
|
|
370
|
+
|
|
371
|
+
## Modules
|
|
372
|
+
|
|
373
|
+
```rsh
|
|
374
|
+
use "./lib/net.rsh" as net
|
|
375
|
+
= net.fetch(url)
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
The imported file runs inside a namespace rather than dumping its locals into the caller.
|
|
379
|
+
|
|
380
|
+
Relative paths are resolved against the current script when SRSH can determine one. Recursive import cycles are rejected.
|
|
381
|
+
|
|
382
|
+
## Cleanup with `defer`
|
|
383
|
+
|
|
384
|
+
Single cleanup:
|
|
385
|
+
|
|
386
|
+
```rsh
|
|
387
|
+
fn work()
|
|
388
|
+
tmp := make_tmp()
|
|
389
|
+
defer rmfile(tmp)
|
|
390
|
+
return use_tmp(tmp)
|
|
391
|
+
end
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Block cleanup:
|
|
395
|
+
|
|
396
|
+
```rsh
|
|
397
|
+
defer
|
|
398
|
+
unlock()
|
|
399
|
+
rmfile(tmp)
|
|
400
|
+
end
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Defers are LIFO and run when the current script/function execution scope leaves, including through `return` and exceptions.
|
|
404
|
+
|
|
405
|
+
## Structured errors
|
|
406
|
+
|
|
407
|
+
```rsh
|
|
408
|
+
try
|
|
409
|
+
cfg := json(readfile("config.json"))
|
|
410
|
+
catch err
|
|
411
|
+
= err.message
|
|
412
|
+
cfg := %[]
|
|
413
|
+
finally
|
|
414
|
+
audit("attempted")
|
|
415
|
+
end
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
The caught error is an RSH map-like value with at least `.type` and `.message`.
|
|
419
|
+
|
|
420
|
+
Hot/value style:
|
|
421
|
+
|
|
422
|
+
```rsh
|
|
423
|
+
result := attempt(:: => risky())
|
|
424
|
+
? !result.ok => = result.error.message
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
`fail(message)` raises an RSH runtime error. `assert(condition, message)` is available too.
|
|
428
|
+
|
|
429
|
+
## Async tasks
|
|
430
|
+
|
|
431
|
+
```rsh
|
|
432
|
+
task fetch(url)
|
|
433
|
+
return cmd("curl", "-fsS", url).check().out
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
jobs := urls |> map(fetch)
|
|
437
|
+
results := await_all(jobs)
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Expression task:
|
|
441
|
+
|
|
442
|
+
```rsh
|
|
443
|
+
task square_later(x) => x * x
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
and, like functions, the body can be on the next physical line after `=>`.
|
|
447
|
+
|
|
448
|
+
Spawn a callable immediately:
|
|
449
|
+
|
|
450
|
+
```rsh
|
|
451
|
+
job := &:: => expensive_io()
|
|
452
|
+
= job.await(2.0)
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Task methods:
|
|
456
|
+
|
|
457
|
+
```text
|
|
458
|
+
.await([timeout])
|
|
459
|
+
.done()
|
|
460
|
+
.status()
|
|
461
|
+
.cancel()
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
`race(tasks)` returns the winner and cancels losers. `await_all(tasks)` preserves order and cancels still-running siblings when one fails.
|
|
465
|
+
|
|
466
|
+
## Atoms and channels
|
|
467
|
+
|
|
468
|
+
Shared scalar-ish state:
|
|
469
|
+
|
|
470
|
+
```rsh
|
|
471
|
+
hits := atom(0)
|
|
472
|
+
hits.swap(::n => n + 1)
|
|
473
|
+
= hits.get()
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Channels:
|
|
477
|
+
|
|
478
|
+
```rsh
|
|
479
|
+
ch := chan(16)
|
|
480
|
+
ch.send(value)
|
|
481
|
+
value := ch.recv(1.0)
|
|
482
|
+
ch.close()
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
A capacity of zero is currently an unbounded queue; positive capacities apply backpressure.
|
|
486
|
+
|
|
487
|
+
## Thread and process parallelism
|
|
488
|
+
|
|
489
|
+
```rsh
|
|
490
|
+
rows := parallel(inputs, fetch, 8)
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
`parallel` uses Ruby threads. Good for files, network calls and subprocess waits.
|
|
494
|
+
|
|
495
|
+
```rsh
|
|
496
|
+
hashes := pmap(files, hash_one, cpu_count())
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
`pmap` uses Unix fork workers when available, so CPU-heavy Ruby work can use multiple cores even under CRuby's GVL. Results preserve input order.
|
|
500
|
+
|
|
501
|
+
## Shell command values
|
|
502
|
+
|
|
503
|
+
```rsh
|
|
504
|
+
job := cmd("git", "rev-parse", "--verify", ref)
|
|
505
|
+
result := job.result()
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Methods:
|
|
509
|
+
|
|
510
|
+
```text
|
|
511
|
+
.argv() copy of argv
|
|
512
|
+
.run() inherit terminal, return status
|
|
513
|
+
.result() capture stdout/stderr/status
|
|
514
|
+
.capture() stdout as a string
|
|
515
|
+
.check() result, but raise on nonzero status
|
|
516
|
+
.task() run structured command in an RSH task
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
Use `cmd()` when filenames/URLs/data should stay argv and should not be interpreted as shell code.
|
|
520
|
+
|
|
521
|
+
## C ABI bridges
|
|
522
|
+
|
|
523
|
+
Readable declaration:
|
|
524
|
+
|
|
525
|
+
```rsh
|
|
526
|
+
bridge libc from "libc.so.6"
|
|
527
|
+
getpid() -> i32
|
|
528
|
+
strlen(cstr) -> usize
|
|
529
|
+
gethostname(ptr, usize) -> i32
|
|
530
|
+
end
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
Call it like a namespace:
|
|
534
|
+
|
|
535
|
+
```rsh
|
|
536
|
+
= libc.getpid()
|
|
537
|
+
= libc.strlen("hello")
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
`@self` binds against the current process:
|
|
541
|
+
|
|
542
|
+
```rsh
|
|
543
|
+
bridge c from "@self"
|
|
544
|
+
strlen(cstr) -> usize
|
|
545
|
+
end
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Writable memory:
|
|
549
|
+
|
|
550
|
+
```rsh
|
|
551
|
+
buf := cbuf(256)
|
|
552
|
+
libc.gethostname(buf, buf.size())
|
|
553
|
+
= buf.string()
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
`cbuf` methods:
|
|
557
|
+
|
|
558
|
+
```text
|
|
559
|
+
.size()
|
|
560
|
+
.address()
|
|
561
|
+
.ptr()
|
|
562
|
+
.read([offset], [length])
|
|
563
|
+
.write(string, [offset])
|
|
564
|
+
.string([max])
|
|
565
|
+
.clear()
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
ABI type names:
|
|
569
|
+
|
|
570
|
+
```text
|
|
571
|
+
void bool
|
|
572
|
+
i8 u8 i16 u16 i32 u32 i64 u64
|
|
573
|
+
isize usize
|
|
574
|
+
f32 f64
|
|
575
|
+
cstr ptr
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Bridges are intentionally thin. They resolve the dynamic symbol once and then call the native function directly. Read the security document: the wrong signature can crash the process.
|
|
579
|
+
|
|
580
|
+
## Files, strings, JSON and paths
|
|
581
|
+
|
|
582
|
+
Useful hot-script helpers include:
|
|
583
|
+
|
|
584
|
+
```text
|
|
585
|
+
readfile writefile appendfile
|
|
586
|
+
exists file dir glob stat
|
|
587
|
+
mkdirp rmfile cpfile mvfile
|
|
588
|
+
basename dirname ext
|
|
589
|
+
json json_dump
|
|
590
|
+
lines words replace
|
|
591
|
+
upper lower trim split join
|
|
592
|
+
shellquote
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
These are for values. Normal shell commands remain available when a dedicated Unix tool is the better choice.
|
|
596
|
+
|
|
597
|
+
## Metascripting
|
|
598
|
+
|
|
599
|
+
Parsed code block:
|
|
600
|
+
|
|
601
|
+
```rsh
|
|
602
|
+
code cleanup
|
|
603
|
+
rm -rf build/tmp
|
|
604
|
+
end
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
Inspect or execute later:
|
|
608
|
+
|
|
609
|
+
```rsh
|
|
610
|
+
= sourceof(cleanup)
|
|
611
|
+
run(cleanup)
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
Dynamic expression/code helpers:
|
|
615
|
+
|
|
616
|
+
```text
|
|
617
|
+
eval(string)
|
|
618
|
+
code(string)
|
|
619
|
+
run(code_or_string)
|
|
620
|
+
valid(string [, "expr"])
|
|
621
|
+
sourceof(code)
|
|
622
|
+
locals()
|
|
623
|
+
fns()
|
|
624
|
+
protos()
|
|
625
|
+
traits()
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
Parsed `code ... end` is preferable when the source is known ahead of time.
|
|
629
|
+
|
|
630
|
+
## Shell strictness options
|
|
631
|
+
|
|
632
|
+
At the command prompt or in startup config:
|
|
633
|
+
|
|
634
|
+
```sh
|
|
635
|
+
option pipefail yes
|
|
636
|
+
option nounset yes
|
|
637
|
+
option noclobber yes
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
Shortcut:
|
|
641
|
+
|
|
642
|
+
```sh
|
|
643
|
+
option strict yes
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
`strict` means `pipefail + nounset`. RSH deliberately does not copy Bash `set -e`; its context-sensitive behavior is too easy to misunderstand. Use structured errors/checking when failure really matters.
|
|
647
|
+
|
|
648
|
+
## Interactive multiline input
|
|
649
|
+
|
|
650
|
+
The REPL asks the parser whether the input is complete. This works for blocks *and* expressions:
|
|
651
|
+
|
|
652
|
+
```text
|
|
653
|
+
> values := [
|
|
654
|
+
... 1,
|
|
655
|
+
... 2,
|
|
656
|
+
... 3
|
|
657
|
+
... ]
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
It also handles a command ending in `|`, `&&`, `||`, redirection, an unclosed quote, command substitution, or a trailing backslash as incomplete shell input.
|
|
661
|
+
|
|
662
|
+
Background jobs use the same process-group model as the interactive shell. `jobs`, `fg`, `bg`, and `wait %N` operate on those jobs; `exec command ...` replaces SRSH with a command when a wrapper no longer needs to stay around.
|
|
663
|
+
|
|
664
|
+
That behavior is what makes pasting larger snippets practical; the shell no longer has to recognize every possible multiline construct with a pile of prompt regexes.
|
|
665
|
+
|
|
666
|
+
## Compatibility forms
|
|
667
|
+
|
|
668
|
+
The original 0.8 spellings still exist, including `if/else/end`, `while/end`, `times/end` and old `fn name args ... end` functions.
|
|
669
|
+
|
|
670
|
+
The newer hot forms are there for short code, not to force old scripts into a new costume.
|