buninu-linux 0.8.56 → 0.9.56
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/initramfs/bin/tar +34 -46
- package/initramfs/buninu/apps/bunmsh/CHANGELOG.md +103 -0
- package/initramfs/buninu/apps/bunmsh/PORTING.md +133 -15
- package/initramfs/buninu/apps/bunmsh/help/awk.md +38 -0
- package/initramfs/buninu/apps/bunmsh/help/break.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/chmod.md +8 -2
- package/initramfs/buninu/apps/bunmsh/help/cmp.md +29 -0
- package/initramfs/buninu/apps/bunmsh/help/continue.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/cp.md +4 -0
- package/initramfs/buninu/apps/bunmsh/help/diff.md +30 -0
- package/initramfs/buninu/apps/bunmsh/help/egrep.md +26 -0
- package/initramfs/buninu/apps/bunmsh/help/export.md +2 -2
- package/initramfs/buninu/apps/bunmsh/help/expr.md +30 -0
- package/initramfs/buninu/apps/bunmsh/help/fgrep.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/grep.md +4 -0
- package/initramfs/buninu/apps/bunmsh/help/ls.md +4 -2
- package/initramfs/buninu/apps/bunmsh/help/mkdir.md +1 -1
- package/initramfs/buninu/apps/bunmsh/help/print.md +3 -0
- package/initramfs/buninu/apps/bunmsh/help/return.md +23 -0
- package/initramfs/buninu/apps/bunmsh/help/sed.md +8 -3
- package/initramfs/buninu/apps/bunmsh/help/set.md +12 -3
- package/initramfs/buninu/apps/bunmsh/help/tr.md +4 -0
- package/initramfs/buninu/apps/bunmsh/help/trap.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/uniq.md +27 -0
- package/initramfs/buninu/apps/bunmsh/package.json +1 -1
- package/initramfs/buninu/apps/bunmsh/src/awk.js +203 -0
- package/initramfs/buninu/apps/bunmsh/src/diff.js +222 -0
- package/initramfs/buninu/apps/bunmsh/src/fancy-ls.js +15 -4
- package/initramfs/buninu/apps/bunmsh/src/main.js +21 -5
- package/initramfs/buninu/apps/bunmsh/src/sed.js +159 -0
- package/initramfs/buninu/apps/bunmsh/src/shell.js +1283 -540
- package/initramfs/buninu/apps/bunmsh/test/awk-reference.test.js +35 -0
- package/initramfs/buninu/apps/bunmsh/test/awk.test.js +62 -0
- package/initramfs/buninu/apps/bunmsh/test/posix-bootstrap.test.js +595 -0
- package/initramfs/buninu/apps/bunmsh/test/utility-reference-cases.mjs +98 -0
- package/initramfs/buninu/apps/bunmsh/test/utility-reference.test.js +23 -0
- package/initramfs/buninu/apps/jsgotty/client-kitty.mjs +61 -0
- package/initramfs/buninu/apps/jsgotty/client.mjs +16 -0
- package/initramfs/buninu/apps/jsgotty/package-lock.json +2 -2
- package/initramfs/buninu/apps/jsgotty/package.json +1 -1
- package/initramfs/buninu/apps/jsgotty/tests/client-kitty.test.mjs +43 -0
- package/initramfs/etc/resolv.conf +2 -1
- package/initramfs/usr/share/doc/buninu-linux/tar.md +11 -0
- package/package.json +1 -1
- package/scripts/fetch.js +2 -0
- package/test/tar-strip.test.js +36 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
## expr
|
|
2
|
+
|
|
3
|
+
Evaluate integer, string, and basic regular-expression expressions.
|
|
4
|
+
|
|
5
|
+
### Usage
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
expr [--] EXPRESSION
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Options and forms
|
|
12
|
+
|
|
13
|
+
- Arithmetic: `+`, `-`, `*`, `/`, `%`, with parentheses and precedence.
|
|
14
|
+
- Comparisons: `=`, `!=`, `<`, `<=`, `>`, `>=`.
|
|
15
|
+
- `|` selects the first nonempty, nonzero operand; `&` requires both operands.
|
|
16
|
+
- `STRING : BRE` returns the first capture, or the matched prefix length when there is no capture.
|
|
17
|
+
- Status is 0 for a nonempty, nonzero result, 1 for empty or zero, and 2 for an error.
|
|
18
|
+
- Quote operators that the shell would otherwise interpret.
|
|
19
|
+
|
|
20
|
+
### Example
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
expr 2 + 3 '*' 4
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Output:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
14
|
|
30
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
## fgrep
|
|
2
|
+
|
|
3
|
+
Search for fixed strings, equivalent to `grep -F`.
|
|
4
|
+
|
|
5
|
+
### Usage
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
fgrep [OPTIONS] PATTERN [FILE ...]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Options and forms
|
|
12
|
+
|
|
13
|
+
Accepts the same supported options as builtin `grep`.
|
|
14
|
+
|
|
15
|
+
### Example
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
printf 'a.b\naxb\n' | builtin fgrep 'a.b'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Output:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
a.b
|
|
25
|
+
```
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
## ls
|
|
2
2
|
|
|
3
|
-
Use the lsfancy emoji and terminal-width-aware listing.
|
|
3
|
+
Use the lsfancy emoji and terminal-width-aware listing. With `set -o posix`, fallback `ls` omits icons and automatic directory suffixes. Explicit `-F` suffixes still apply; `lsfancy` always retains its icons.
|
|
4
4
|
|
|
5
5
|
### Usage
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
ls [-
|
|
8
|
+
ls [-aAdilhLRtrS1F] [PATH ...]
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Because `-h` means human-readable sizes, use `--help` to display this page.
|
|
@@ -16,6 +16,8 @@ Because `-h` means human-readable sizes, use `--help` to display this page.
|
|
|
16
16
|
- `-A`: Include hidden entries except `.` and `..`.
|
|
17
17
|
- `-d`: List directories themselves rather than their contents.
|
|
18
18
|
- `-l`: Use long format.
|
|
19
|
+
- `-i`: Print inode numbers.
|
|
20
|
+
- `-L`: Follow symbolic links.
|
|
19
21
|
- `-h`: Show human-readable sizes; use `--help` for this page.
|
|
20
22
|
- `-R`: Recurse into directories.
|
|
21
23
|
- `-t`: Sort by modification time.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## return
|
|
2
|
+
|
|
3
|
+
Return from a function or sourced script. Without STATUS, preserve the previous status.
|
|
4
|
+
|
|
5
|
+
### Usage
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
return [STATUS]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Example
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
f () { return 7; }
|
|
15
|
+
f
|
|
16
|
+
echo "$?"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Output:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
7
|
|
23
|
+
```
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
## sed
|
|
2
2
|
|
|
3
|
-
Apply
|
|
3
|
+
Apply editing commands to pattern and hold spaces.
|
|
4
4
|
|
|
5
5
|
### Usage
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
sed [-nEr] [-e SCRIPT] [-i] [SCRIPT] [FILE ...]
|
|
8
|
+
sed [-nEr] [-e SCRIPT | -f FILE] [-i] [SCRIPT] [FILE ...]
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
### Options and forms
|
|
12
12
|
|
|
13
13
|
- `-n`: Suppress automatic output.
|
|
14
14
|
- `-e SCRIPT`, `-eSCRIPT`: Add a script.
|
|
15
|
+
- `-f FILE`, `-fFILE`: Read a script from a file.
|
|
15
16
|
- `-E`, `-r`: Use extended regular expressions.
|
|
16
17
|
- `-i`: Edit files in place.
|
|
17
|
-
-
|
|
18
|
+
- Numeric, last-line (`$`), and regular-expression addresses; ranges, inversion, and `{ ... }` groups.
|
|
19
|
+
- `s///` with `g`, `p`, and occurrence-number flags; `y///` translation.
|
|
20
|
+
- `p`, `P`, `d`, `D`, `q`, `=`, `n`, `N`; hold-space commands `h`, `H`, `g`, `G`, `x`.
|
|
21
|
+
- Labels (`:`), unconditional branches (`b`), and substitution-test branches (`t`, `T`).
|
|
22
|
+
- `a`, `i`, `c` text commands; `r FILE` appends file contents after the current cycle. Missing/unreadable files are skipped. This remains a subset: `w` is not implemented.
|
|
18
23
|
|
|
19
24
|
### Example
|
|
20
25
|
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
## set
|
|
2
2
|
|
|
3
|
-
Print variables
|
|
3
|
+
Print variables, replace positional parameters, or change shell options.
|
|
4
4
|
|
|
5
5
|
### Usage
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
set [-- [ARG ...]]
|
|
8
|
+
set [-eufxv] [+eufxv] [-o NAME | +o NAME] [-- [ARG ...]]
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
### Options and forms
|
|
12
12
|
|
|
13
|
-
-
|
|
13
|
+
- No arguments: print variables.
|
|
14
|
+
- `--`: replace positional parameters, including clearing them with no following arguments.
|
|
15
|
+
- `-`: end options and disable verbose/xtrace; replace positional parameters only when arguments follow.
|
|
16
|
+
- `-e` / `+e`: enable/disable exit on untested failures. Conditions, negation and non-final AND/OR commands suppress it.
|
|
17
|
+
- `-u` / `+u`: enable/disable errors for unset parameters; explicit defaults still work.
|
|
18
|
+
- `-f` / `+f`: disable/enable pathname expansion.
|
|
19
|
+
- `-x` / `+x`: enable/disable execution tracing.
|
|
20
|
+
- `-o` / `+o` without a name: list named option states / reusable set commands.
|
|
21
|
+
- Names: `errexit`, `nounset`, `noglob`, `xtrace`, `verbose`, `posix`. CLI `-v` prints initial input; incremental verbose reading is not yet implemented.
|
|
22
|
+
- `-o posix`: enable compatibility mode. The fallback `ls` prints without icons or automatic directory suffixes; `lsfancy` remains decorative. External programs receive only inherited/exported variables and command-prefix assignments; pipelines and subshells still receive complete shell state. Use `+o posix` to restore the default, which passes all variables to external programs. This option is not a guarantee of complete POSIX semantics.
|
|
14
23
|
|
|
15
24
|
### Example
|
|
16
25
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
## trap
|
|
2
|
+
|
|
3
|
+
Register shell code for EXIT (0) or a catchable signal. No operands lists handlers; ACTION - resets and an empty ACTION ignores the signal in this shell. Signal handlers are deferred to command boundaries. Background process ownership is unchanged.
|
|
4
|
+
|
|
5
|
+
### Usage
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
trap [ACTION SIGNAL ...]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Example
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
trap 'echo done' 0
|
|
15
|
+
exit 0
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Output:
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
done
|
|
22
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
## uniq
|
|
2
|
+
|
|
3
|
+
Collapse adjacent equal lines without sorting. Comparisons preserve bytes.
|
|
4
|
+
|
|
5
|
+
### Usage
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
uniq [-cdu] [--] [INPUT [OUTPUT]]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`-c` prefixes counts; `-d` selects repeated groups; `-u` selects single lines.
|
|
12
|
+
`-` reads standard input or writes standard output. Missing final newlines
|
|
13
|
+
are added. Field/character skipping and other GNU options are unsupported.
|
|
14
|
+
|
|
15
|
+
### Example
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
printf 'a\na\nb\na\n' | uniq
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Output:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
a
|
|
25
|
+
b
|
|
26
|
+
a
|
|
27
|
+
```
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// A tree-walking AWK interpreter. Shell input is data, never JavaScript source.
|
|
2
|
+
class InputString {
|
|
3
|
+
constructor(text) { this.text=String(text); this.numeric=/^[ \t]*[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?[ \t]*$/.test(this.text); }
|
|
4
|
+
}
|
|
5
|
+
const str=v=>v instanceof InputString?v.text:v==null?'':String(v);
|
|
6
|
+
const num=v=>parseFloat(str(v))||0;
|
|
7
|
+
const numeric=v=>typeof v==='number'||v instanceof InputString&&v.numeric;
|
|
8
|
+
const truth=v=>numeric(v)?num(v)!==0:str(v)!=='';
|
|
9
|
+
const unset=new InputString(''); unset.numeric=true;
|
|
10
|
+
const failure=m=>{throw new Error(`awk: ${m}`);};
|
|
11
|
+
function regex(s,flags='') {
|
|
12
|
+
s=str(s).replaceAll('[[:space:]]','[\\s]').replaceAll('[[:digit:]]','[0-9]').replaceAll('[[:alnum:]]','[A-Za-z0-9]').replaceAll('[[:alpha:]]','[A-Za-z]').replaceAll('[[:blank:]]','[ \t]');
|
|
13
|
+
try {return new RegExp(s,flags);}catch(error){failure(`invalid regular expression ${JSON.stringify(s)}: ${error.message}`);}
|
|
14
|
+
}
|
|
15
|
+
function lex(source) {
|
|
16
|
+
const out=[];let i=0,operand=true;
|
|
17
|
+
const push=(kind,value)=>{out.push({kind,value});operand=!(kind==='number'||kind==='string'||kind==='regex'||kind==='name'&&!['print','printf','return','exit','getline','in','delete'].includes(value)||[')',']','++','--'].includes(value));};
|
|
18
|
+
while(i<source.length) {
|
|
19
|
+
const c=source[i];
|
|
20
|
+
if(c==='\\'&&source[i+1]==='\n'){i+=2;continue;}
|
|
21
|
+
if(/[ \t\r]/.test(c)){i++;continue;}
|
|
22
|
+
if(c==='#'){while(i<source.length&&source[i]!=='\n')i++;continue;}
|
|
23
|
+
if(c==='\n'){if(!(out.at(-1)?.kind==='op'&&[',','&&','||'].includes(out.at(-1).value)))push('op',';');i++;continue;}
|
|
24
|
+
if(c==='"'||c==='/'&&operand){
|
|
25
|
+
const delim=c;let value='';i++;let closed=false;
|
|
26
|
+
while(i<source.length){const ch=source[i++];if(ch===delim){closed=true;break;}
|
|
27
|
+
if(ch==='\\') {if(i>=source.length)break;const n=source[i++];
|
|
28
|
+
if(n==='\n')continue;
|
|
29
|
+
if(delim==='/'){value+=n==='/'?'/':'\\'+n;continue;}
|
|
30
|
+
if(/[0-7]/.test(n)){let oct=n;while(oct.length<3&&/[0-7]/.test(source[i]??''))oct+=source[i++];value+=String.fromCharCode(parseInt(oct,8));}
|
|
31
|
+
else value+=({n:'\n',t:'\t',r:'\r',b:'\b',f:'\f',v:'\v',a:'\x07','\\':'\\','"':'"','/':'/'})[n]??n;
|
|
32
|
+
}else value+=ch;
|
|
33
|
+
}
|
|
34
|
+
if(!closed)failure('unterminated '+(delim==='/'?'regular expression':'string'));
|
|
35
|
+
push(delim==='/'?'regex':'string',value);continue;
|
|
36
|
+
}
|
|
37
|
+
const number=/^(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?/.exec(source.slice(i));
|
|
38
|
+
if(number){push('number',Number(number[0]));i+=number[0].length;continue;}
|
|
39
|
+
const name=/^[A-Za-z_][A-Za-z0-9_]*/.exec(source.slice(i));
|
|
40
|
+
if(name){push('name',name[0]);i+=name[0].length;continue;}
|
|
41
|
+
const op=/^(?:\+\+|--|\+=|-=|\*=|\/=|%=|\^=|==|!=|<=|>=|!~|&&|\|\||>>|[{}()[\],;?:$+*\/%^<>=!~|\-])/.exec(source.slice(i));
|
|
42
|
+
if(!op)failure(`unexpected character ${JSON.stringify(c)}`);
|
|
43
|
+
push('op',op[0]);i+=op[0].length;
|
|
44
|
+
}
|
|
45
|
+
out.push({kind:'eof',value:'<eof>'});return out;
|
|
46
|
+
}
|
|
47
|
+
export function compileAwk(source) {
|
|
48
|
+
const tokens=lex(source);let i=0;
|
|
49
|
+
// Quoted strings and regex literals are operands even when their contents
|
|
50
|
+
// happen to spell an operator, separator, or keyword.
|
|
51
|
+
const peek=()=>['string','regex'].includes(tokens[i].kind)?null:tokens[i].value, take=()=>tokens[i++], eat=v=>peek()===v?(i++,true):false;
|
|
52
|
+
const need=v=>{if(!eat(v))failure(`expected ${v}, got ${peek()}`);};
|
|
53
|
+
const separators=()=>{while(eat(';'));};
|
|
54
|
+
const prec={'=':1,'+=':1,'-=':1,'*=':1,'/=':1,'%=':1,'^=':1,'?':2,'||':3,'&&':4,'in':5,'~':5,'!~':5,'==':6,'!=':6,'<':6,'>':6,'<=':6,'>=':6,'concat':7,'+':8,'-':8,'*':9,'/':9,'%':9,'^':11};
|
|
55
|
+
const begins=t=>['number','string','regex'].includes(t.kind)||t.kind==='name'&&!['in','else'].includes(t.value)||['(','$'].includes(t.value);
|
|
56
|
+
function expr(min=0,stopRedirect=false) {
|
|
57
|
+
let t=take(),node;
|
|
58
|
+
if(t.kind==='number'||t.kind==='string')node={type:'literal',value:t.value};
|
|
59
|
+
else if(t.kind==='regex')node={type:'regex',value:t.value};
|
|
60
|
+
else if(t.value==='('){const values=[expr()];while(eat(','))values.push(expr());need(')');node=values.length===1?values[0]:{type:'list',values};}
|
|
61
|
+
else if(['!','+','-','++','--','$'].includes(t.value))node={type:t.value==='$'?'field':'unary',op:t.value,value:expr(t.value==='$'?13:10)};
|
|
62
|
+
else if(t.value==='getline'){
|
|
63
|
+
let target=null;if(tokens[i].kind==='name'||peek()==='$')target=expr(12);
|
|
64
|
+
let file=null;if(eat('<'))file=expr(7);
|
|
65
|
+
node={type:'getline',target,file};
|
|
66
|
+
}
|
|
67
|
+
else if(t.kind==='name'){
|
|
68
|
+
node={type:'variable',name:t.value};
|
|
69
|
+
if(eat('(')){const args=[];if(peek()!==')')do{args.push(expr());}while(eat(','));need(')');node={type:'call',name:t.value,args};}
|
|
70
|
+
else if(eat('[')){const keys=[];do{keys.push(expr());}while(eat(','));need(']');node={type:'index',name:t.value,keys};}
|
|
71
|
+
}else failure(`expected expression, got ${t.value}`);
|
|
72
|
+
while(true){let op=peek();
|
|
73
|
+
if(['++','--'].includes(op)){if(12<min)break;i++;node={type:'postfix',op,value:node};continue;}
|
|
74
|
+
if(stopRedirect&&['>','>>','|'].includes(op))break;
|
|
75
|
+
let implicit=false;if(prec[op]===undefined&&begins(tokens[i])){op='concat';implicit=true;}
|
|
76
|
+
const p=prec[op];if(p===undefined||p<min)break;
|
|
77
|
+
if(!implicit)i++;
|
|
78
|
+
if(op==='?'){const yes=expr();need(':');node={type:'conditional',condition:node,yes,no:expr(p)};continue;}
|
|
79
|
+
const right=expr(p+(p===1||op==='^'?0:1),stopRedirect);node={type:p===1?'assign':'binary',op,left:node,right};
|
|
80
|
+
}
|
|
81
|
+
return node;
|
|
82
|
+
}
|
|
83
|
+
function statement(){separators();
|
|
84
|
+
if(eat('{')){const body=[];separators();while(peek()!=='}'){if(tokens[i].kind==='eof')failure('unterminated block');body.push(statement());separators();}need('}');return {type:'block',body};}
|
|
85
|
+
if(eat('if')){need('(');const condition=expr();need(')');const yes=statement();separators();return {type:'if',condition,yes,no:eat('else')?statement():null};}
|
|
86
|
+
if(eat('while')){need('(');const condition=expr();need(')');return {type:'while',condition,body:statement()};}
|
|
87
|
+
if(eat('do')){const body=statement();separators();need('while');need('(');const condition=expr();need(')');return {type:'do',body,condition};}
|
|
88
|
+
if(eat('for')){need('(');const initial=peek()===';'?null:expr();
|
|
89
|
+
if(initial?.type==='binary'&&initial.op==='in'&&eat(')'))return {type:'forin',initial,body:statement()};
|
|
90
|
+
need(';');const condition=peek()===';'?null:expr();need(';');const increment=peek()===')'?null:expr();need(')');return {type:'for',initial,condition,increment,body:statement()};}
|
|
91
|
+
if(['break','continue','next','nextfile','exit','return'].includes(peek())){const op=take().value;const value=['exit','return'].includes(op)&&![';','}','<eof>'].includes(peek())?expr():null;return {type:'flow',op,value};}
|
|
92
|
+
if(eat('delete'))return {type:'delete',value:expr(12)};
|
|
93
|
+
if(peek()==='print'||peek()==='printf'){
|
|
94
|
+
const op=take().value,args=[];
|
|
95
|
+
if(![';','}','<eof>','>','>>','|'].includes(peek()))do{const value=expr(0,true);args.push(...(value.type==='list'?value.values:[value]));}while(eat(','));
|
|
96
|
+
let redirect=null;if(['>','>>','|'].includes(peek()))redirect={op:take().value,file:expr()};
|
|
97
|
+
return {type:'print',op,args,redirect};
|
|
98
|
+
}
|
|
99
|
+
return {type:'expression',value:expr()};
|
|
100
|
+
}
|
|
101
|
+
const program={begin:[],end:[],rules:[],functions:new Map()};separators();
|
|
102
|
+
while(tokens[i].kind!=='eof'){
|
|
103
|
+
if(eat('function')){const name=take();if(name.kind!=='name')failure('expected function name');need('(');const params=[];if(peek()!==')')do{const p=take();if(p.kind!=='name')failure('expected parameter');params.push(p.value);}while(eat(','));need(')');program.functions.set(name.value,{params,body:statement()});}
|
|
104
|
+
else if(peek()==='BEGIN'||peek()==='END'){const kind=take().value.toLowerCase();separators();if(peek()!=='{')failure(`${kind} requires a block`);program[kind].push(statement());}
|
|
105
|
+
else {let pattern=null,until=null;if(peek()!=='{'){pattern=expr();if(eat(','))until=expr();}
|
|
106
|
+
const action=peek()==='{'?statement():{type:'print',op:'print',args:[]};program.rules.push({pattern,until,action});}
|
|
107
|
+
separators();
|
|
108
|
+
}
|
|
109
|
+
return program;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function executeAwk(program, inputs, options={}) {
|
|
113
|
+
const vars=new Map(Object.entries({FS:' ',OFS:' ',ORS:'\n',RS:'\n',SUBSEP:'\x1c',CONVFMT:'%.6g',OFMT:'%.6g',NR:0,FNR:0,NF:0,FILENAME:'',RSTART:0,RLENGTH:-1,...Object.fromEntries(Object.entries(options.variables??{}).map(([k,v])=>[k,new InputString(v)]))}));
|
|
114
|
+
const arrays=new Map(),locals=[];let record='',fields=[],output='',flow=null,status=0,seed=1,inputIndex=0,recordIndex=0,records=[];
|
|
115
|
+
const readers=new Map(),written=new Set(),ranges=new Map(),pipes=new Map();
|
|
116
|
+
let stderr='';
|
|
117
|
+
function closePipe(command) {
|
|
118
|
+
if(!pipes.has(command))return 0;
|
|
119
|
+
const text=pipes.get(command);pipes.delete(command);
|
|
120
|
+
if(!options.runPipe)failure('output pipes require a shell adapter');
|
|
121
|
+
const p=options.runPipe(command,text);output+=p.stdout??'';stderr+=p.stderr??'';
|
|
122
|
+
return p.status;
|
|
123
|
+
}
|
|
124
|
+
const array=name=>{if(locals.at(-1)?.get(name) instanceof Map)return locals.at(-1).get(name);if(!arrays.has(name))arrays.set(name,new Map());return arrays.get(name);};
|
|
125
|
+
array('ARGV').set('0','awk');(options.argv??[]).forEach((v,i)=>array('ARGV').set(String(i+1),v));vars.set('ARGC',(options.argv??[]).length+1);
|
|
126
|
+
for(const [k,v] of Object.entries(options.env??{}))array('ENVIRON').set(k,new InputString(v));
|
|
127
|
+
const get=name=>{if(locals.at(-1)?.has(name))return locals.at(-1).get(name);return vars.get(name)??unset;};
|
|
128
|
+
const set=(name,value)=>{if(name==='NF'){const n=Math.trunc(num(value));if(n<0)failure('negative NF');fields.length=n;for(let i=0;i<n;i++)fields[i]??=new InputString('');record=fields.map(str).join(str(get('OFS')));}
|
|
129
|
+
if(locals.at(-1)?.has(name)){locals.at(-1).set(name,value);return value;}vars.set(name,value);return value;};
|
|
130
|
+
function split(text,sep){text=str(text);sep=str(sep);if(sep===' ')return text.trim()?text.trim().split(/\s+/):[];if(sep==='')return [...text];return text===''?[]:text.split(sep.length===1?sep:regex(sep));}
|
|
131
|
+
function setRecord(text){record=str(text);fields=split(record,get('FS')).map(x=>new InputString(x));vars.set('NF',fields.length);}
|
|
132
|
+
function ref(node){if(node.type==='variable')return {get:()=>get(node.name),set:v=>set(node.name,v)};
|
|
133
|
+
if(node.type==='index'){const a=array(node.name),k=node.keys.map(x=>str(evaluate(x))).join(str(get('SUBSEP')));return {get:()=>a.get(k)??unset,set:v=>(a.set(k,v),v),remove:()=>a.delete(k)};}
|
|
134
|
+
if(node.type==='field'){const n=Math.trunc(num(evaluate(node.value)));if(n<0)failure('negative field');return {get:()=>n===0?new InputString(record):fields[n-1]??new InputString(''),set:v=>{if(n===0)setRecord(v);else{while(fields.length<n)fields.push(new InputString(''));fields[n-1]=v;vars.set('NF',fields.length);record=fields.map(str).join(str(get('OFS')));}return v;}};}
|
|
135
|
+
failure('expression is not assignable');}
|
|
136
|
+
const pattern=node=>node.type==='regex'?node.value:str(evaluate(node));
|
|
137
|
+
function sprintf(format,args){let at=0;return str(format).replace(/%([-+ #0]*)(\d*)(?:\.(\d+))?([%csdiouxXeEfFgG])/g,(all,flags,width,precision,kind)=>{
|
|
138
|
+
if(kind==='%')return '%';const value=args[at++];let s;
|
|
139
|
+
if(kind==='s')s=str(value).slice(0,precision===undefined?undefined:Number(precision));
|
|
140
|
+
else if(kind==='c')s=numeric(value)?String.fromCharCode(num(value)):str(value).slice(0,1);
|
|
141
|
+
else if(/[diouxX]/.test(kind)){let n=Math.trunc(num(value));if(kind==='u')n>>>=0;s=n.toString(kind==='o'?8:/[xX]/.test(kind)?16:10);if(kind==='X')s=s.toUpperCase();}
|
|
142
|
+
else {const n=num(value),p=precision===undefined?6:Number(precision);s=/f/i.test(kind)?n.toFixed(p):/e/i.test(kind)?n.toExponential(p):String(Number(n.toPrecision(p||1)));if(kind===kind.toUpperCase())s=s.toUpperCase();}
|
|
143
|
+
if(flags.includes('+')&&num(value)>=0&&kind!=='s')s='+'+s;
|
|
144
|
+
return flags.includes('-')?s.padEnd(Number(width),' '):s.padStart(Number(width),flags.includes('0')?'0':' ');
|
|
145
|
+
});}
|
|
146
|
+
function nextRecord(){while(recordIndex>=records.length){if(inputIndex>=inputs.length)return null;const entry=inputs[inputIndex++];if(entry.assignment){set(entry.assignment[0],new InputString(entry.assignment[1]));continue;}vars.set('FILENAME',entry.name);vars.set('FNR',0);const text=entry.text;const rs=str(get('RS'));records=rs===''?text.replace(/^\n+|\n+$/g,'').split(/\n\n+/):text.split(rs.length===1?rs:regex(rs));if(records.at(-1)==='')records.pop();recordIndex=0;}
|
|
147
|
+
vars.set('NR',num(get('NR'))+1);vars.set('FNR',num(get('FNR'))+1);return records[recordIndex++];}
|
|
148
|
+
function call(node){const {name,args}=node;
|
|
149
|
+
if(name==='split'){const values=split(evaluate(args[0]),args[2]?pattern(args[2]):get('FS'));if(args[1]?.type!=='variable')failure('split requires array name');const a=array(args[1].name);a.clear();values.forEach((v,i)=>a.set(String(i+1),new InputString(v)));return values.length;}
|
|
150
|
+
if(name==='sub'||name==='gsub'){const re=regex(pattern(args[0]),name==='gsub'?'g':'');const replacement=str(evaluate(args[1]));const target=args[2]?ref(args[2]):{get:()=>record,set:setRecord};let count=0;
|
|
151
|
+
const text=str(target.get()).replace(re,match=>{count++;let s='';for(let i=0;i<replacement.length;i++){if(replacement[i]==='\\'&&i+1<replacement.length&&['&','\\'].includes(replacement[i+1]))s+=replacement[++i];else s+=replacement[i]==='&'?match:replacement[i];}return s;});target.set(text);return count;}
|
|
152
|
+
if(name==='match'){const s=str(evaluate(args[0])),m=regex(pattern(args[1])).exec(s);set('RSTART',m?m.index+1:0);set('RLENGTH',m?m[0].length:-1);return get('RSTART');}
|
|
153
|
+
if(program.functions.has(name)){const fn=program.functions.get(name),scope=new Map();fn.params.forEach((p,i)=>scope.set(p,args[i]?.type==='variable'&&!locals.at(-1)?.has(args[i].name)&&arrays.has(args[i].name)?array(args[i].name):args[i]?evaluate(args[i]):unset));locals.push(scope);let value='';try{run(fn.body);if(flow?.op==='return'){value=flow.value;flow=null;}}finally{locals.pop();}return value;}
|
|
154
|
+
if(name==='length'&&args[0]?.type==='variable'&&arrays.has(args[0].name))return array(args[0].name).size;
|
|
155
|
+
const a=args.map(evaluate);
|
|
156
|
+
switch(name){
|
|
157
|
+
case 'length':return str(a.length?a[0]:record).length;
|
|
158
|
+
case 'substr':return str(a[0]).substr(Math.max(0,num(a[1])-1),a.length>2?Math.max(0,num(a[2])):undefined);
|
|
159
|
+
case 'index':return str(a[0]).indexOf(str(a[1]))+1;
|
|
160
|
+
case 'sprintf':return sprintf(a[0],a.slice(1));
|
|
161
|
+
case 'tolower':return str(a[0]).toLowerCase();case 'toupper':return str(a[0]).toUpperCase();
|
|
162
|
+
case 'int':return Math.trunc(num(a[0]));case 'sqrt':return Math.sqrt(num(a[0]));case 'log':return Math.log(num(a[0]));case 'exp':return Math.exp(num(a[0]));case 'sin':return Math.sin(num(a[0]));case 'cos':return Math.cos(num(a[0]));case 'atan2':return Math.atan2(num(a[0]),num(a[1]));
|
|
163
|
+
case 'srand':{const old=seed;seed=a.length?num(a[0]):Date.now();return old;}case 'rand':seed=(1664525*seed+1013904223)>>>0;return seed/4294967296;
|
|
164
|
+
case 'close':{const name=str(a[0]);readers.delete(name);written.delete(name);return closePipe(name);}
|
|
165
|
+
default:failure(`unsupported function ${name}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function evaluate(n){switch(n.type){
|
|
169
|
+
case 'literal':return n.value;case 'regex':return Number(regex(n.value).test(record));
|
|
170
|
+
case 'variable':if(n.name==='length')return record.length;return get(n.name);
|
|
171
|
+
case 'index':case 'field':return ref(n).get();case 'call':return call(n);
|
|
172
|
+
case 'conditional':return evaluate(truth(evaluate(n.condition))?n.yes:n.no);
|
|
173
|
+
case 'getline':{let text;if(n.file){const path=str(evaluate(n.file));try{if(!readers.has(path))readers.set(path,{lines:options.readFile(path).split('\n'),i:0});const r=readers.get(path);if(r.lines.at(-1)==='')r.lines.pop();text=r.i<r.lines.length?r.lines[r.i++]:null;}catch{return -1;}}else text=nextRecord();if(text===null)return 0;if(n.target)ref(n.target).set(new InputString(text));else setRecord(text);return 1;}
|
|
174
|
+
case 'unary':case 'postfix':{if(n.op==='++'||n.op==='--'){const r=ref(n.value),v=num(r.get()),next=v+(n.op==='++'?1:-1);r.set(next);return n.type==='postfix'?v:next;}const v=evaluate(n.value);return n.op==='!'?Number(!truth(v)):n.op==='-'?-num(v):num(v);}
|
|
175
|
+
case 'assign':{const r=ref(n.left),v=evaluate(n.right);return r.set(n.op==='='?v:arithmetic(n.op[0],r.get(),v));}
|
|
176
|
+
case 'binary':{const a=evaluate(n.left);if(n.op==='&&')return Number(truth(a)&&truth(evaluate(n.right)));if(n.op==='||')return Number(truth(a)||truth(evaluate(n.right)));if(n.op==='in'){if(n.right.type!=='variable')failure('in requires an array');return Number(array(n.right.name).has(str(a)));}
|
|
177
|
+
if(n.op==='~'||n.op==='!~'){const match=regex(pattern(n.right)).test(str(a));return Number(n.op==='~'?match:!match);}
|
|
178
|
+
const b=evaluate(n.right);if(n.op==='concat')return str(a)+str(b);
|
|
179
|
+
if(['==','!=','<','>','<=','>='].includes(n.op)){const x=numeric(a)&&numeric(b)?num(a):str(a),y=numeric(a)&&numeric(b)?num(b):str(b);return Number(n.op==='=='?x===y:n.op==='!='?x!==y:n.op==='<'?x<y:n.op==='>'?x>y:n.op==='<='?x<=y:x>=y);}
|
|
180
|
+
return arithmetic(n.op,a,b);}
|
|
181
|
+
}failure(`unsupported expression ${n.type}`);}
|
|
182
|
+
function arithmetic(op,a,b){a=num(a);b=num(b);if((op==='/'||op==='%')&&b===0)failure('division by zero');return op==='+'?a+b:op==='-'?a-b:op==='*'?a*b:op==='/'?a/b:op==='%'?a%b:op==='^'?a**b:failure(`unsupported operator ${op}`);}
|
|
183
|
+
function run(n){if(flow)return;switch(n.type){
|
|
184
|
+
case 'block':for(const child of n.body){run(child);if(flow)break;}break;
|
|
185
|
+
case 'expression':evaluate(n.value);break;
|
|
186
|
+
case 'if':if(truth(evaluate(n.condition)))run(n.yes);else if(n.no)run(n.no);break;
|
|
187
|
+
case 'print':{const values=n.args.map(evaluate);const text=n.op==='printf'?sprintf(values[0],values.slice(1)):(values.length?values.map(str).join(str(get('OFS'))):record)+str(get('ORS'));
|
|
188
|
+
if(n.redirect){const file=str(evaluate(n.redirect.file));if(n.redirect.op==='|')pipes.set(file,(pipes.get(file)??'')+text);else {options.writeFile(file,text,n.redirect.op==='>>'||written.has(file));written.add(file);}}else output+=text;break;}
|
|
189
|
+
case 'delete':if(n.value.type==='variable')array(n.value.name).clear();else {const r=ref(n.value);if(!r.remove)failure('delete requires an array');r.remove();}break;
|
|
190
|
+
case 'flow':flow={op:n.op,value:n.value?evaluate(n.value):0};if(n.op==='exit')status=num(flow.value);break;
|
|
191
|
+
case 'while':case 'do':case 'for':case 'forin':{
|
|
192
|
+
if(n.type==='for'&&n.initial)evaluate(n.initial);
|
|
193
|
+
let keys;if(n.type==='forin'){if(n.initial.right.type!=='variable')failure('for-in requires array');keys=[...array(n.initial.right.name).keys()];}
|
|
194
|
+
let first=true,j=0;while(n.type==='forin'?j<keys.length:n.type==='do'&&first||!n.condition||truth(evaluate(n.condition))){first=false;if(keys)ref(n.initial.left).set(new InputString(keys[j++]));run(n.body);if(flow){if(flow.op==='break'){flow=null;break;}if(flow.op==='continue')flow=null;else break;}if(n.type==='for'&&n.increment)evaluate(n.increment);}break;}
|
|
195
|
+
default:failure(`unsupported statement ${n.type}`);
|
|
196
|
+
}}
|
|
197
|
+
for(const b of program.begin){run(b);if(flow)break;}
|
|
198
|
+
if(flow&&flow.op!=='exit')failure(`${flow.op} outside a record`);
|
|
199
|
+
if(!flow&&program.rules.length){let text;while((text=nextRecord())!==null){setRecord(text);for(const rule of program.rules){let yes=!rule.pattern||truth(evaluate(rule.pattern));if(rule.until){yes=ranges.get(rule)||yes;if(yes)ranges.set(rule,!truth(evaluate(rule.until)));}if(yes)run(rule.action);if(flow)break;}if(flow?.op==='exit')break;if(flow&& !['next','nextfile'].includes(flow.op))failure(`${flow.op} outside its context`);if(flow?.op==='nextfile')recordIndex=records.length;flow=null;}}
|
|
200
|
+
flow=null;for(const b of program.end){run(b);if(flow)break;}
|
|
201
|
+
for(const command of pipes.keys())closePipe(command);
|
|
202
|
+
return stderr?{status,stdout:output,stderr}:{status,stdout:output};
|
|
203
|
+
}
|