fauxnix-cli 0.2.0 → 0.3.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/README.md +203 -199
- package/dist/ast.d.ts +7 -1
- package/dist/ast.js +10 -0
- package/dist/cli.js +13 -13
- package/dist/commands/sysinfo.js +1300 -48
- package/dist/encoding.d.ts +9 -0
- package/dist/encoding.js +19 -0
- package/dist/executor.js +230 -105
- package/dist/mcp.js +11 -11
- package/dist/parser.d.ts +2 -0
- package/dist/parser.js +420 -15
- package/dist/registry.js +10 -0
- package/dist/translator.d.ts +16 -1
- package/dist/translator.js +271 -11
- package/package.json +58 -58
package/dist/commands/sysinfo.js
CHANGED
|
@@ -1,11 +1,36 @@
|
|
|
1
|
-
import { wordToString } from '../ast.js';
|
|
1
|
+
import { FauxnixParseError, isUnquotedLiteral, wordToString } from '../ast.js';
|
|
2
2
|
import { lookup, parseWords, psStr, registeredNames } from '../registry.js';
|
|
3
|
-
import { exprOfWord, operandExpr, translateSimple } from '../translator.js';
|
|
3
|
+
import { exprOfWord, operandExpr, translateSimple, wrapTempEnv, encodeSetValExpr, escapeDq, translateCmdSub, normalizeLiteralPath, pathExpr, } from '../translator.js';
|
|
4
4
|
import { handlers as textIoHandlers } from './text-io.js';
|
|
5
5
|
/* ------------------------------------------------------------------ */
|
|
6
6
|
/* Shared TS helpers */
|
|
7
7
|
/* ------------------------------------------------------------------ */
|
|
8
8
|
const NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
9
|
+
/** Case-exact env shadow so `FOO` and `foo` can differ on Windows. */
|
|
10
|
+
function emitSetValPut(nameLit, valueExpr) {
|
|
11
|
+
const n = nameLit.replace(/'/g, "''");
|
|
12
|
+
return [
|
|
13
|
+
'$fx_sv = @()',
|
|
14
|
+
'foreach ($fx_pair in @($env:FAUXNIX_SETVALS -split [string][char]10)) {',
|
|
15
|
+
' $fx_eq = $fx_pair.IndexOf([char]61)',
|
|
16
|
+
' if ($fx_eq -lt 1) { continue }',
|
|
17
|
+
" if ($fx_pair.Substring(0, $fx_eq) -cne '" + n + "') { $fx_sv += $fx_pair }",
|
|
18
|
+
'}',
|
|
19
|
+
"$fx_sv += ('" + n + "' + [string][char]61 + " + encodeSetValExpr(valueExpr) + ')',
|
|
20
|
+
'$env:FAUXNIX_SETVALS = ($fx_sv -join [string][char]10)',
|
|
21
|
+
].join('; ');
|
|
22
|
+
}
|
|
23
|
+
function emitSetValDelRuntime(nameVar) {
|
|
24
|
+
return [
|
|
25
|
+
'$fx_sv = @()',
|
|
26
|
+
'foreach ($fx_pair in @($env:FAUXNIX_SETVALS -split [string][char]10)) {',
|
|
27
|
+
' $fx_eq = $fx_pair.IndexOf([char]61)',
|
|
28
|
+
' if ($fx_eq -lt 1) { continue }',
|
|
29
|
+
' if ($fx_pair.Substring(0, $fx_eq) -cne ' + nameVar + ') { $fx_sv += $fx_pair }',
|
|
30
|
+
'}',
|
|
31
|
+
'$env:FAUXNIX_SETVALS = ($fx_sv -join [string][char]10)',
|
|
32
|
+
].join('; ');
|
|
33
|
+
}
|
|
9
34
|
/**
|
|
10
35
|
* Split a Word into `NAME=rest` at the first literal '='. Returns null when
|
|
11
36
|
* the word contains no '=' (bare name) or is dynamic before the '='.
|
|
@@ -135,7 +160,24 @@ const exportCmd = (args) => {
|
|
|
135
160
|
}
|
|
136
161
|
sets.push(sp);
|
|
137
162
|
}
|
|
138
|
-
return sets
|
|
163
|
+
return sets
|
|
164
|
+
.map((s) => {
|
|
165
|
+
const val = exprOfWord(s.value);
|
|
166
|
+
return ('$fx_exv = ' +
|
|
167
|
+
val +
|
|
168
|
+
'; $env:' +
|
|
169
|
+
s.name +
|
|
170
|
+
' = $fx_exv' +
|
|
171
|
+
"; $env:FAUXNIX_SETVARS = ((@($env:FAUXNIX_SETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne '" +
|
|
172
|
+
s.name.replace(/'/g, "''") +
|
|
173
|
+
"' }) + '" +
|
|
174
|
+
s.name.replace(/'/g, "''") +
|
|
175
|
+
"') -join ';'); $env:FAUXNIX_UNSETVARS = (@($env:FAUXNIX_UNSETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne '" +
|
|
176
|
+
s.name.replace(/'/g, "''") +
|
|
177
|
+
"' }) -join ';'); " +
|
|
178
|
+
emitSetValPut(s.name, '$fx_exv'));
|
|
179
|
+
})
|
|
180
|
+
.join('\n');
|
|
139
181
|
};
|
|
140
182
|
const unset = (args) => {
|
|
141
183
|
const names = stripFlags(args);
|
|
@@ -146,6 +188,9 @@ const unset = (args) => {
|
|
|
146
188
|
'foreach ($fx_n in $fx_ns) {',
|
|
147
189
|
" if ($fx_n -notmatch '^[A-Za-z_][A-Za-z0-9_]*$') { [Console]::Error.WriteLine(\"bash: unset: '\" + $fx_n + \"': not a valid identifier\"); $script:fx_exit = 1; continue }",
|
|
148
190
|
" Remove-Item -LiteralPath ('Env:\\' + $fx_n) -ErrorAction SilentlyContinue",
|
|
191
|
+
" $env:FAUXNIX_SETVARS = (@($env:FAUXNIX_SETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne $fx_n }) -join ';')",
|
|
192
|
+
" $env:FAUXNIX_UNSETVARS = ((@($env:FAUXNIX_UNSETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne $fx_n }) + $fx_n) -join ';')",
|
|
193
|
+
' ' + emitSetValDelRuntime('$fx_n'),
|
|
149
194
|
'}',
|
|
150
195
|
].join('\n');
|
|
151
196
|
};
|
|
@@ -190,11 +235,6 @@ const env = (args, ctx) => {
|
|
|
190
235
|
break;
|
|
191
236
|
}
|
|
192
237
|
const lines = [];
|
|
193
|
-
for (const u of unsets) {
|
|
194
|
-
lines.push("Remove-Item -LiteralPath ('Env:\\' + " + psStr(u) + ') -ErrorAction SilentlyContinue');
|
|
195
|
-
}
|
|
196
|
-
for (const s of sets)
|
|
197
|
-
lines.push('$env:' + s.name + ' = ' + exprOfWord(s.value));
|
|
198
238
|
if (cmdIdx >= 0 && cmdIdx < args.length) {
|
|
199
239
|
const cmdWords = args.slice(cmdIdx);
|
|
200
240
|
lines.push(translateSimple({ kind: 'SimpleCommand', assignments: [], name: cmdWords[0], args: cmdWords.slice(1), redirects: [] }, ctx.position, ctx.hasStdin));
|
|
@@ -202,7 +242,8 @@ const env = (args, ctx) => {
|
|
|
202
242
|
else {
|
|
203
243
|
lines.push(ENV_LIST_PS);
|
|
204
244
|
}
|
|
205
|
-
|
|
245
|
+
// `env NAME=val cmd` / `env -u NAME cmd` must not leak into the session.
|
|
246
|
+
return wrapTempEnv(sets, lines.join('\n'), { unsets });
|
|
206
247
|
};
|
|
207
248
|
const printenv = (args) => {
|
|
208
249
|
const names = stripFlags(args);
|
|
@@ -737,8 +778,36 @@ const colon = () => '';
|
|
|
737
778
|
/* ------------------------------------------------------------------ */
|
|
738
779
|
/* test / [ */
|
|
739
780
|
/* ------------------------------------------------------------------ */
|
|
740
|
-
const TEST_UNARY = new Set([
|
|
741
|
-
|
|
781
|
+
const TEST_UNARY = new Set([
|
|
782
|
+
'-e',
|
|
783
|
+
'-a',
|
|
784
|
+
'-f',
|
|
785
|
+
'-d',
|
|
786
|
+
'-r',
|
|
787
|
+
'-w',
|
|
788
|
+
'-x',
|
|
789
|
+
'-s',
|
|
790
|
+
'-z',
|
|
791
|
+
'-n',
|
|
792
|
+
'-L',
|
|
793
|
+
'-h',
|
|
794
|
+
'-v',
|
|
795
|
+
]);
|
|
796
|
+
const TEST_BINARY = new Set([
|
|
797
|
+
'=',
|
|
798
|
+
'==',
|
|
799
|
+
'!=',
|
|
800
|
+
'-eq',
|
|
801
|
+
'-ne',
|
|
802
|
+
'-lt',
|
|
803
|
+
'-le',
|
|
804
|
+
'-gt',
|
|
805
|
+
'-ge',
|
|
806
|
+
'-nt',
|
|
807
|
+
'-ot',
|
|
808
|
+
'-ef',
|
|
809
|
+
]);
|
|
810
|
+
const TEST_BINARY_KSH = new Set([...TEST_BINARY, '=~', '>', '<']);
|
|
742
811
|
const FX_TN_FN = [
|
|
743
812
|
'function fx-tn($a, $b, $op) {',
|
|
744
813
|
" if ($a -notmatch '^[+-]?[0-9]+$') { [Console]::Error.WriteLine('bash: [: ' + [string]$a + ': integer expression expected'); $script:fx_exit = 2; return $false }",
|
|
@@ -755,114 +824,1251 @@ const FX_TN_FN = [
|
|
|
755
824
|
' return $false',
|
|
756
825
|
'}',
|
|
757
826
|
].join('\n');
|
|
827
|
+
const FX_TNK_FN = [
|
|
828
|
+
'function fx-baseval($num, $base) {',
|
|
829
|
+
' $b = [int]$base',
|
|
830
|
+
' if ($b -lt 2 -or $b -gt 64) { throw "integer expression expected" }',
|
|
831
|
+
' if ([string]$num -eq "") { throw "integer expression expected" }',
|
|
832
|
+
' $v = [bigint]0',
|
|
833
|
+
' foreach ($ch in ([string]$num).ToCharArray()) {',
|
|
834
|
+
' $c = [int]$ch; $d = -1',
|
|
835
|
+
' if ($c -ge 48 -and $c -le 57) { $d = $c - 48 }',
|
|
836
|
+
' elseif ($c -ge 97 -and $c -le 122) { $d = $c - 87 }',
|
|
837
|
+
' elseif ($c -ge 65 -and $c -le 90) { if ($b -le 36) { $d = $c - 55 } else { $d = $c - 29 } }',
|
|
838
|
+
' elseif ($c -eq 64) { $d = 62 }',
|
|
839
|
+
' elseif ($c -eq 95) { $d = 63 }',
|
|
840
|
+
' if ($d -lt 0 -or $d -ge $b) { throw "integer expression expected" }',
|
|
841
|
+
' $v = $v * $b + $d',
|
|
842
|
+
' }',
|
|
843
|
+
' return (fx-i64 ([string]$v))',
|
|
844
|
+
'}',
|
|
845
|
+
'function fx-arith($s) {',
|
|
846
|
+
' $t = [string]$s',
|
|
847
|
+
' if ($t.Trim().Length -eq 0) { return 0 }',
|
|
848
|
+
" $t = [regex]::Replace($t, '\\b(\\d+)#([0-9A-Za-z@_]+)\\b', {",
|
|
849
|
+
' param($m)',
|
|
850
|
+
' return [string](fx-baseval $m.Groups[2].Value $m.Groups[1].Value)',
|
|
851
|
+
' })',
|
|
852
|
+
" $t = [regex]::Replace($t, '0[xX][0-9A-Fa-f]+', {",
|
|
853
|
+
' param($m)',
|
|
854
|
+
' return [string](fx-baseval $m.Value.Substring(2) 16)',
|
|
855
|
+
' })',
|
|
856
|
+
" $t = [regex]::Replace($t, '\\b0[0-9]+\\b', {",
|
|
857
|
+
' param($m)',
|
|
858
|
+
" if ($m.Value -match '[89]') { throw 'integer expression expected' }",
|
|
859
|
+
" $fx_od = $m.Value.TrimStart('0')",
|
|
860
|
+
" if ($fx_od -eq '') { return '0' }",
|
|
861
|
+
' return [string](fx-baseval $fx_od 8)',
|
|
862
|
+
' })',
|
|
863
|
+
' $script:fx_as = $t; $script:fx_ai = 0; $script:fx_skip = $false',
|
|
864
|
+
' $v = fx-acomma',
|
|
865
|
+
' fx-askip',
|
|
866
|
+
' if ($script:fx_ai -lt $script:fx_as.Length) { throw "integer expression expected" }',
|
|
867
|
+
' return $v',
|
|
868
|
+
'}',
|
|
869
|
+
'function fx-askip {',
|
|
870
|
+
' while ($script:fx_ai -lt $script:fx_as.Length -and ($script:fx_as[$script:fx_ai] -eq [char]32 -or $script:fx_as[$script:fx_ai] -eq [char]9)) { $script:fx_ai++ }',
|
|
871
|
+
'}',
|
|
872
|
+
'function fx-aname {',
|
|
873
|
+
' fx-askip',
|
|
874
|
+
' if ($script:fx_ai -ge $script:fx_as.Length) { return $null }',
|
|
875
|
+
' $c0 = [int]$script:fx_as[$script:fx_ai]',
|
|
876
|
+
' if (-not (($c0 -ge 65 -and $c0 -le 90) -or ($c0 -ge 97 -and $c0 -le 122) -or $c0 -eq 95)) { return $null }',
|
|
877
|
+
' $ns = $script:fx_ai; $script:fx_ai++',
|
|
878
|
+
' while ($script:fx_ai -lt $script:fx_as.Length) {',
|
|
879
|
+
' $c = [int]$script:fx_as[$script:fx_ai]',
|
|
880
|
+
' if (($c -ge 48 -and $c -le 57) -or ($c -ge 65 -and $c -le 90) -or ($c -ge 97 -and $c -le 122) -or $c -eq 95) { $script:fx_ai++ } else { break }',
|
|
881
|
+
' }',
|
|
882
|
+
' return $script:fx_as.Substring($ns, $script:fx_ai - $ns)',
|
|
883
|
+
'}',
|
|
884
|
+
'function fx-envset($n, $v) {',
|
|
885
|
+
' $n = [string]$n',
|
|
886
|
+
' $fx_ev = Get-ChildItem Env: | Where-Object { $_.Name -ceq $n } | Select-Object -First 1',
|
|
887
|
+
' $nm = if ($fx_ev) { $fx_ev.Name } else { $n }',
|
|
888
|
+
" Set-Item -LiteralPath ('Env:' + $nm) -Value ([string][long]$v)",
|
|
889
|
+
" $env:FAUXNIX_SETVARS = ((@($env:FAUXNIX_SETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne $n }) + $n) -join ';')",
|
|
890
|
+
" $env:FAUXNIX_UNSETVARS = (@($env:FAUXNIX_UNSETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne $n }) -join ';')",
|
|
891
|
+
' $fx_sv = @()',
|
|
892
|
+
' foreach ($fx_pair in @($env:FAUXNIX_SETVALS -split [string][char]10)) {',
|
|
893
|
+
' $fx_eq = $fx_pair.IndexOf([char]61)',
|
|
894
|
+
' if ($fx_eq -lt 1) { continue }',
|
|
895
|
+
' if ($fx_pair.Substring(0, $fx_eq) -cne $n) { $fx_sv += $fx_pair }',
|
|
896
|
+
' }',
|
|
897
|
+
' $fx_sv += ($n + [string][char]61 + ' +
|
|
898
|
+
'([string][long]$v))',
|
|
899
|
+
' $env:FAUXNIX_SETVALS = ($fx_sv -join [string][char]10)',
|
|
900
|
+
'}',
|
|
901
|
+
'function fx-aget($nm) {',
|
|
902
|
+
' $fx_src = fx-setval-get $nm',
|
|
903
|
+
' if ($null -eq $fx_src) {',
|
|
904
|
+
' $fx_ev = Get-ChildItem Env: | Where-Object { $_.Name -ceq $nm } | Select-Object -First 1',
|
|
905
|
+
' if ($fx_ev) { $fx_src = $fx_ev.Value }',
|
|
906
|
+
" elseif (@($env:FAUXNIX_SETVARS -split ';' | Where-Object { $_ -ceq $nm }).Count -gt 0) { $fx_src = [Environment]::GetEnvironmentVariable([string]$nm) }",
|
|
907
|
+
' }',
|
|
908
|
+
' if ($null -eq $fx_src) { return [long]0 }',
|
|
909
|
+
' if ($null -eq $script:fx_astack) { $script:fx_astack = New-Object System.Collections.Generic.HashSet[string] }',
|
|
910
|
+
' if (-not $script:fx_astack.Add([string]$nm)) { throw "expression recursion level exceeded" }',
|
|
911
|
+
' $saveAs = $script:fx_as; $saveAi = $script:fx_ai; $saveSkip = $script:fx_skip',
|
|
912
|
+
' try { $vv = fx-arith $fx_src }',
|
|
913
|
+
' finally {',
|
|
914
|
+
' $script:fx_as = $saveAs; $script:fx_ai = $saveAi; $script:fx_skip = $saveSkip',
|
|
915
|
+
' [void]$script:fx_astack.Remove([string]$nm)',
|
|
916
|
+
' }',
|
|
917
|
+
' return [long]$vv',
|
|
918
|
+
'}',
|
|
919
|
+
'function fx-i64($s) {',
|
|
920
|
+
' $bi = [bigint]::Parse([string]$s)',
|
|
921
|
+
" $mod = [bigint]::Parse('18446744073709551616')",
|
|
922
|
+
" $half = [bigint]::Parse('9223372036854775808')",
|
|
923
|
+
' $bi = $bi % $mod',
|
|
924
|
+
' if ($bi -lt [bigint]0) { $bi = $bi + $mod }',
|
|
925
|
+
' if ($bi -ge $half) { $bi = $bi - $mod }',
|
|
926
|
+
' return [long]$bi',
|
|
927
|
+
'}',
|
|
928
|
+
'function fx-iadd($a, $b) { return (fx-i64 ([string](([bigint][long]$a) + ([bigint][long]$b)))) }',
|
|
929
|
+
'function fx-isub($a, $b) { return (fx-i64 ([string](([bigint][long]$a) - ([bigint][long]$b)))) }',
|
|
930
|
+
'function fx-imul($a, $b) { return (fx-i64 ([string](([bigint][long]$a) * ([bigint][long]$b)))) }',
|
|
931
|
+
'function fx-ineg($v) {',
|
|
932
|
+
' $v = [long]$v',
|
|
933
|
+
' if ($v -eq [long]::MinValue) { return $v }',
|
|
934
|
+
' return [long](-$v)',
|
|
935
|
+
'}',
|
|
936
|
+
'function fx-anum {',
|
|
937
|
+
' fx-askip',
|
|
938
|
+
' if ($script:fx_ai -lt $script:fx_as.Length -and $script:fx_as[$script:fx_ai] -eq [char]33) {',
|
|
939
|
+
' $script:fx_ai++',
|
|
940
|
+
' $v = fx-anum',
|
|
941
|
+
' if ($v -eq 0) { return 1 }',
|
|
942
|
+
' return 0',
|
|
943
|
+
' }',
|
|
944
|
+
' if ($script:fx_ai -lt $script:fx_as.Length -and $script:fx_as[$script:fx_ai] -eq [char]126) {',
|
|
945
|
+
' $script:fx_ai++',
|
|
946
|
+
' return (fx-ineg (-bnot [long](fx-anum)))',
|
|
947
|
+
' }',
|
|
948
|
+
' if ($script:fx_ai + 1 -lt $script:fx_as.Length -and $script:fx_as[$script:fx_ai] -eq [char]43 -and $script:fx_as[$script:fx_ai + 1] -eq [char]43) {',
|
|
949
|
+
' $save = $script:fx_ai; $script:fx_ai += 2',
|
|
950
|
+
' $nm = fx-aname',
|
|
951
|
+
' if ($nm) {',
|
|
952
|
+
' if ($script:fx_skip) { return 0 }',
|
|
953
|
+
' $cur = fx-iadd (fx-aget $nm) 1; fx-envset $nm $cur; return $cur',
|
|
954
|
+
' }',
|
|
955
|
+
' $script:fx_ai = $save',
|
|
956
|
+
' }',
|
|
957
|
+
' if ($script:fx_ai + 1 -lt $script:fx_as.Length -and $script:fx_as[$script:fx_ai] -eq [char]45 -and $script:fx_as[$script:fx_ai + 1] -eq [char]45) {',
|
|
958
|
+
' $save = $script:fx_ai; $script:fx_ai += 2',
|
|
959
|
+
' $nm = fx-aname',
|
|
960
|
+
' if ($nm) {',
|
|
961
|
+
' if ($script:fx_skip) { return 0 }',
|
|
962
|
+
' $cur = fx-isub (fx-aget $nm) 1; fx-envset $nm $cur; return $cur',
|
|
963
|
+
' }',
|
|
964
|
+
' $script:fx_ai = $save',
|
|
965
|
+
' }',
|
|
966
|
+
' if ($script:fx_ai -lt $script:fx_as.Length -and $script:fx_as[$script:fx_ai] -eq [char]43) {',
|
|
967
|
+
' $script:fx_ai++',
|
|
968
|
+
' return [long](fx-anum)',
|
|
969
|
+
' }',
|
|
970
|
+
' if ($script:fx_ai -lt $script:fx_as.Length -and $script:fx_as[$script:fx_ai] -eq [char]45) {',
|
|
971
|
+
' $script:fx_ai++',
|
|
972
|
+
' return (fx-ineg (fx-anum))',
|
|
973
|
+
' }',
|
|
974
|
+
' fx-askip',
|
|
975
|
+
' if ($script:fx_ai -lt $script:fx_as.Length -and $script:fx_as[$script:fx_ai] -eq [char]40) {',
|
|
976
|
+
' $script:fx_ai++',
|
|
977
|
+
' $v = fx-acomma',
|
|
978
|
+
' fx-askip',
|
|
979
|
+
' if ($script:fx_ai -ge $script:fx_as.Length -or $script:fx_as[$script:fx_ai] -ne [char]41) { throw "integer expression expected" }',
|
|
980
|
+
' $script:fx_ai++',
|
|
981
|
+
' return [long]$v',
|
|
982
|
+
' }',
|
|
983
|
+
' $nm = fx-aname',
|
|
984
|
+
' if ($nm) {',
|
|
985
|
+
' fx-askip',
|
|
986
|
+
' $post = 0',
|
|
987
|
+
' if ($script:fx_ai + 1 -lt $script:fx_as.Length -and $script:fx_as[$script:fx_ai] -eq [char]43 -and $script:fx_as[$script:fx_ai + 1] -eq [char]43) { $script:fx_ai += 2; $post = 1 }',
|
|
988
|
+
' elseif ($script:fx_ai + 1 -lt $script:fx_as.Length -and $script:fx_as[$script:fx_ai] -eq [char]45 -and $script:fx_as[$script:fx_ai + 1] -eq [char]45) { $script:fx_ai += 2; $post = -1 }',
|
|
989
|
+
' if ($script:fx_skip) { return 0 }',
|
|
990
|
+
' $vv = fx-aget $nm',
|
|
991
|
+
' if ($post -ne 0) { fx-envset $nm (fx-iadd $vv $post) }',
|
|
992
|
+
' return [long]$vv',
|
|
993
|
+
' }',
|
|
994
|
+
' $start = $script:fx_ai',
|
|
995
|
+
' while ($script:fx_ai -lt $script:fx_as.Length -and $script:fx_as[$script:fx_ai] -ge [char]48 -and $script:fx_as[$script:fx_ai] -le [char]57) { $script:fx_ai++ }',
|
|
996
|
+
' if ($script:fx_ai -eq $start) { throw "integer expression expected" }',
|
|
997
|
+
' return (fx-i64 $script:fx_as.Substring($start, $script:fx_ai - $start))',
|
|
998
|
+
'}',
|
|
999
|
+
'function fx-apow {',
|
|
1000
|
+
' $v = fx-anum',
|
|
1001
|
+
' fx-askip',
|
|
1002
|
+
' if ($script:fx_ai + 1 -lt $script:fx_as.Length -and $script:fx_as[$script:fx_ai] -eq [char]42 -and $script:fx_as[$script:fx_ai + 1] -eq [char]42) {',
|
|
1003
|
+
' $script:fx_ai += 2',
|
|
1004
|
+
' $r = fx-apow',
|
|
1005
|
+
' if ($r -lt 0) { throw "exponent less than 0" }',
|
|
1006
|
+
' $fx_b = [long]$v; $fx_e = [long]$r; $fx_p = [long]1',
|
|
1007
|
+
' while ($fx_e -gt 0) { if ($fx_e -band 1) { $fx_p = fx-imul $fx_p $fx_b }; $fx_b = fx-imul $fx_b $fx_b; $fx_e = $fx_e -shr 1 }',
|
|
1008
|
+
' return $fx_p',
|
|
1009
|
+
' }',
|
|
1010
|
+
' return $v',
|
|
1011
|
+
'}',
|
|
1012
|
+
'function fx-adiv($a, $b) {',
|
|
1013
|
+
' $a = [long]$a; $b = [long]$b',
|
|
1014
|
+
' if ($b -eq 0) { throw "division by 0" }',
|
|
1015
|
+
' if ($a -eq [long]::MinValue -and $b -eq -1) { return [long]::MinValue }',
|
|
1016
|
+
' $rem = [long]0',
|
|
1017
|
+
' return [math]::DivRem($a, $b, [ref]$rem)',
|
|
1018
|
+
'}',
|
|
1019
|
+
'function fx-amod($a, $b) {',
|
|
1020
|
+
' $a = [long]$a; $b = [long]$b',
|
|
1021
|
+
' if ($b -eq 0) { throw "division by 0" }',
|
|
1022
|
+
' if ($a -eq [long]::MinValue -and $b -eq -1) { return [long]0 }',
|
|
1023
|
+
' $rem = [long]0',
|
|
1024
|
+
' [void][math]::DivRem($a, $b, [ref]$rem)',
|
|
1025
|
+
' return [long]$rem',
|
|
1026
|
+
'}',
|
|
1027
|
+
'function fx-amul {',
|
|
1028
|
+
' $v = fx-apow',
|
|
1029
|
+
' for (;;) {',
|
|
1030
|
+
' fx-askip',
|
|
1031
|
+
' if ($script:fx_ai -ge $script:fx_as.Length) { break }',
|
|
1032
|
+
' $op = $script:fx_as[$script:fx_ai]',
|
|
1033
|
+
' if ($op -ne [char]42 -and $op -ne [char]47 -and $op -ne [char]37) { break }',
|
|
1034
|
+
' $script:fx_ai++',
|
|
1035
|
+
' $r = fx-apow',
|
|
1036
|
+
' if ($op -eq [char]42) { $v = fx-imul $v $r }',
|
|
1037
|
+
' elseif ($op -eq [char]47) { if ($r -eq 0 -and $script:fx_skip) { $v = 0 } else { $v = fx-adiv $v $r } }',
|
|
1038
|
+
' else { if ($r -eq 0 -and $script:fx_skip) { $v = 0 } else { $v = fx-amod $v $r } }',
|
|
1039
|
+
' }',
|
|
1040
|
+
' return $v',
|
|
1041
|
+
'}',
|
|
1042
|
+
'function fx-aadd {',
|
|
1043
|
+
' $v = fx-amul',
|
|
1044
|
+
' for (;;) {',
|
|
1045
|
+
' fx-askip',
|
|
1046
|
+
' if ($script:fx_ai -ge $script:fx_as.Length) { break }',
|
|
1047
|
+
' $op = $script:fx_as[$script:fx_ai]',
|
|
1048
|
+
' if ($op -ne [char]43 -and $op -ne [char]45) { break }',
|
|
1049
|
+
' $script:fx_ai++',
|
|
1050
|
+
' $r = fx-amul',
|
|
1051
|
+
' if ($op -eq [char]43) { $v = fx-iadd $v $r } else { $v = fx-isub $v $r }',
|
|
1052
|
+
' }',
|
|
1053
|
+
' return $v',
|
|
1054
|
+
'}',
|
|
1055
|
+
'function fx-ashift {',
|
|
1056
|
+
' $v = fx-aadd',
|
|
1057
|
+
' for (;;) {',
|
|
1058
|
+
' fx-askip',
|
|
1059
|
+
' if ($script:fx_ai + 1 -ge $script:fx_as.Length) { break }',
|
|
1060
|
+
' $a = $script:fx_as[$script:fx_ai]; $b = $script:fx_as[$script:fx_ai + 1]',
|
|
1061
|
+
' if ($a -eq [char]60 -and $b -eq [char]60) {',
|
|
1062
|
+
' $script:fx_ai += 2; $r = fx-aadd; $v = fx-i64 ([string](([bigint][long]$v) -shl [int]$r))',
|
|
1063
|
+
' } elseif ($a -eq [char]62 -and $b -eq [char]62) {',
|
|
1064
|
+
' $script:fx_ai += 2; $r = fx-aadd; $v = [long]$v -shr [int]$r',
|
|
1065
|
+
' } else { break }',
|
|
1066
|
+
' }',
|
|
1067
|
+
' return $v',
|
|
1068
|
+
'}',
|
|
1069
|
+
'function fx-arel {',
|
|
1070
|
+
' $v = fx-ashift',
|
|
1071
|
+
' for (;;) {',
|
|
1072
|
+
' fx-askip',
|
|
1073
|
+
' if ($script:fx_ai -ge $script:fx_as.Length) { break }',
|
|
1074
|
+
' $a = $script:fx_as[$script:fx_ai]',
|
|
1075
|
+
' $b = if ($script:fx_ai + 1 -lt $script:fx_as.Length) { $script:fx_as[$script:fx_ai + 1] } else { [char]0 }',
|
|
1076
|
+
' if ($a -eq [char]60 -and $b -eq [char]61) { $script:fx_ai += 2; $r = fx-ashift; $v = [long]($v -le $r) }',
|
|
1077
|
+
' elseif ($a -eq [char]62 -and $b -eq [char]61) { $script:fx_ai += 2; $r = fx-ashift; $v = [long]($v -ge $r) }',
|
|
1078
|
+
' elseif ($a -eq [char]60 -and $b -ne [char]60) { $script:fx_ai++; $r = fx-ashift; $v = [long]($v -lt $r) }',
|
|
1079
|
+
' elseif ($a -eq [char]62 -and $b -ne [char]62) { $script:fx_ai++; $r = fx-ashift; $v = [long]($v -gt $r) }',
|
|
1080
|
+
' else { break }',
|
|
1081
|
+
' }',
|
|
1082
|
+
' return $v',
|
|
1083
|
+
'}',
|
|
1084
|
+
'function fx-aeq {',
|
|
1085
|
+
' $v = fx-arel',
|
|
1086
|
+
' for (;;) {',
|
|
1087
|
+
' fx-askip',
|
|
1088
|
+
' if ($script:fx_ai + 1 -ge $script:fx_as.Length) { break }',
|
|
1089
|
+
' $a = $script:fx_as[$script:fx_ai]; $b = $script:fx_as[$script:fx_ai + 1]',
|
|
1090
|
+
' if ($a -eq [char]61 -and $b -eq [char]61) { $script:fx_ai += 2; $r = fx-arel; $v = [long]($v -eq $r) }',
|
|
1091
|
+
' elseif ($a -eq [char]33 -and $b -eq [char]61) { $script:fx_ai += 2; $r = fx-arel; $v = [long]($v -ne $r) }',
|
|
1092
|
+
' else { break }',
|
|
1093
|
+
' }',
|
|
1094
|
+
' return $v',
|
|
1095
|
+
'}',
|
|
1096
|
+
'function fx-abitand {',
|
|
1097
|
+
' $v = fx-aeq',
|
|
1098
|
+
' for (;;) {',
|
|
1099
|
+
' fx-askip',
|
|
1100
|
+
' if ($script:fx_ai -ge $script:fx_as.Length) { break }',
|
|
1101
|
+
' if ($script:fx_as[$script:fx_ai] -ne [char]38) { break }',
|
|
1102
|
+
' if ($script:fx_ai + 1 -lt $script:fx_as.Length -and $script:fx_as[$script:fx_ai + 1] -eq [char]38) { break }',
|
|
1103
|
+
' $script:fx_ai++',
|
|
1104
|
+
' $v = [long]$v -band [long](fx-aeq)',
|
|
1105
|
+
' }',
|
|
1106
|
+
' return $v',
|
|
1107
|
+
'}',
|
|
1108
|
+
'function fx-abitxor {',
|
|
1109
|
+
' $v = fx-abitand',
|
|
1110
|
+
' for (;;) {',
|
|
1111
|
+
' fx-askip',
|
|
1112
|
+
' if ($script:fx_ai -ge $script:fx_as.Length -or $script:fx_as[$script:fx_ai] -ne [char]94) { break }',
|
|
1113
|
+
' $script:fx_ai++',
|
|
1114
|
+
' $v = [long]$v -bxor [long](fx-abitand)',
|
|
1115
|
+
' }',
|
|
1116
|
+
' return $v',
|
|
1117
|
+
'}',
|
|
1118
|
+
'function fx-abitor {',
|
|
1119
|
+
' $v = fx-abitxor',
|
|
1120
|
+
' for (;;) {',
|
|
1121
|
+
' fx-askip',
|
|
1122
|
+
' if ($script:fx_ai -ge $script:fx_as.Length -or $script:fx_as[$script:fx_ai] -ne [char]124) { break }',
|
|
1123
|
+
' if ($script:fx_ai + 1 -lt $script:fx_as.Length -and $script:fx_as[$script:fx_ai + 1] -eq [char]124) { break }',
|
|
1124
|
+
' $script:fx_ai++',
|
|
1125
|
+
' $v = [long]$v -bor [long](fx-abitxor)',
|
|
1126
|
+
' }',
|
|
1127
|
+
' return $v',
|
|
1128
|
+
'}',
|
|
1129
|
+
'function fx-aland {',
|
|
1130
|
+
' $v = fx-abitor',
|
|
1131
|
+
' for (;;) {',
|
|
1132
|
+
' fx-askip',
|
|
1133
|
+
' if ($script:fx_ai + 1 -ge $script:fx_as.Length) { break }',
|
|
1134
|
+
' if ($script:fx_as[$script:fx_ai] -ne [char]38 -or $script:fx_as[$script:fx_ai + 1] -ne [char]38) { break }',
|
|
1135
|
+
' $script:fx_ai += 2',
|
|
1136
|
+
' $old = $script:fx_skip',
|
|
1137
|
+
' if ($v -eq 0) { $script:fx_skip = $true }',
|
|
1138
|
+
' $r = fx-abitor',
|
|
1139
|
+
' $script:fx_skip = $old',
|
|
1140
|
+
' $v = [long](($v -ne 0) -and ($r -ne 0))',
|
|
1141
|
+
' }',
|
|
1142
|
+
' return $v',
|
|
1143
|
+
'}',
|
|
1144
|
+
'function fx-alor {',
|
|
1145
|
+
' $v = fx-aland',
|
|
1146
|
+
' for (;;) {',
|
|
1147
|
+
' fx-askip',
|
|
1148
|
+
' if ($script:fx_ai + 1 -ge $script:fx_as.Length) { break }',
|
|
1149
|
+
' if ($script:fx_as[$script:fx_ai] -ne [char]124 -or $script:fx_as[$script:fx_ai + 1] -ne [char]124) { break }',
|
|
1150
|
+
' $script:fx_ai += 2',
|
|
1151
|
+
' $old = $script:fx_skip',
|
|
1152
|
+
' if ($v -ne 0) { $script:fx_skip = $true }',
|
|
1153
|
+
' $r = fx-aland',
|
|
1154
|
+
' $script:fx_skip = $old',
|
|
1155
|
+
' $v = [long](($v -ne 0) -or ($r -ne 0))',
|
|
1156
|
+
' }',
|
|
1157
|
+
' return $v',
|
|
1158
|
+
'}',
|
|
1159
|
+
'function fx-atern {',
|
|
1160
|
+
' $v = fx-alor',
|
|
1161
|
+
' fx-askip',
|
|
1162
|
+
' if ($script:fx_ai -ge $script:fx_as.Length -or $script:fx_as[$script:fx_ai] -ne [char]63) { return $v }',
|
|
1163
|
+
' $script:fx_ai++',
|
|
1164
|
+
' $old = $script:fx_skip',
|
|
1165
|
+
' if ($v -eq 0) { $script:fx_skip = $true }',
|
|
1166
|
+
' $t = fx-acomma',
|
|
1167
|
+
' $script:fx_skip = $old',
|
|
1168
|
+
' fx-askip',
|
|
1169
|
+
' if ($script:fx_ai -ge $script:fx_as.Length -or $script:fx_as[$script:fx_ai] -ne [char]58) { throw "integer expression expected" }',
|
|
1170
|
+
' $script:fx_ai++',
|
|
1171
|
+
' if ($v -ne 0) { $script:fx_skip = $true }',
|
|
1172
|
+
' $f = fx-atern',
|
|
1173
|
+
' $script:fx_skip = $old',
|
|
1174
|
+
' if ($v -ne 0) { return $t }',
|
|
1175
|
+
' return $f',
|
|
1176
|
+
'}',
|
|
1177
|
+
'function fx-aop {',
|
|
1178
|
+
' fx-askip',
|
|
1179
|
+
' if ($script:fx_ai -ge $script:fx_as.Length) { return $null }',
|
|
1180
|
+
' $rest = $script:fx_as.Substring($script:fx_ai)',
|
|
1181
|
+
" if ($rest.StartsWith('==', [System.StringComparison]::Ordinal)) { return $null }",
|
|
1182
|
+
" foreach ($op in @('<<=', '>>=', '*=', '/=', '%=', '+=', '-=', '&=', '^=', '|=', '=')) {",
|
|
1183
|
+
' if (-not $rest.StartsWith($op, [System.StringComparison]::Ordinal)) { continue }',
|
|
1184
|
+
' $script:fx_ai += $op.Length',
|
|
1185
|
+
' return $op',
|
|
1186
|
+
' }',
|
|
1187
|
+
' return $null',
|
|
1188
|
+
'}',
|
|
1189
|
+
'function fx-aassign {',
|
|
1190
|
+
' fx-askip',
|
|
1191
|
+
' $save = $script:fx_ai',
|
|
1192
|
+
' $nm = fx-aname',
|
|
1193
|
+
' if ($nm) {',
|
|
1194
|
+
' $op = fx-aop',
|
|
1195
|
+
' if ($op) {',
|
|
1196
|
+
' $old = [long]0',
|
|
1197
|
+
" if (-not $script:fx_skip -and $op -ne '=') { $old = [long](fx-aget $nm) }",
|
|
1198
|
+
' $r = [long](fx-aassign)',
|
|
1199
|
+
' if ($script:fx_skip) { return 0 }',
|
|
1200
|
+
' switch ($op) {',
|
|
1201
|
+
" '=' { $v = [long]$r }",
|
|
1202
|
+
" '+=' { $v = fx-iadd $old $r }",
|
|
1203
|
+
" '-=' { $v = fx-isub $old $r }",
|
|
1204
|
+
" '*=' { $v = fx-imul $old $r }",
|
|
1205
|
+
" '/=' { $v = fx-adiv $old $r }",
|
|
1206
|
+
" '%=' { $v = fx-amod $old $r }",
|
|
1207
|
+
" '<<=' { $v = fx-i64 ([string](([bigint][long]$old) -shl [int]$r)) }",
|
|
1208
|
+
" '>>=' { $v = [long]$old -shr [int]$r }",
|
|
1209
|
+
" '&=' { $v = [long]$old -band [long]$r }",
|
|
1210
|
+
" '^=' { $v = [long]$old -bxor [long]$r }",
|
|
1211
|
+
" '|=' { $v = [long]$old -bor [long]$r }",
|
|
1212
|
+
' default { throw "integer expression expected" }',
|
|
1213
|
+
' }',
|
|
1214
|
+
' fx-envset $nm $v',
|
|
1215
|
+
' return [long]$v',
|
|
1216
|
+
' }',
|
|
1217
|
+
' }',
|
|
1218
|
+
' $script:fx_ai = $save',
|
|
1219
|
+
' return [long](fx-atern)',
|
|
1220
|
+
'}',
|
|
1221
|
+
'function fx-acomma {',
|
|
1222
|
+
' $v = fx-aassign',
|
|
1223
|
+
' for (;;) {',
|
|
1224
|
+
' fx-askip',
|
|
1225
|
+
' if ($script:fx_ai -ge $script:fx_as.Length -or $script:fx_as[$script:fx_ai] -ne [char]44) { break }',
|
|
1226
|
+
' $script:fx_ai++',
|
|
1227
|
+
' $v = fx-aassign',
|
|
1228
|
+
' }',
|
|
1229
|
+
' return [long]$v',
|
|
1230
|
+
'}',
|
|
1231
|
+
'function fx-tnk($a, $b, $op) {',
|
|
1232
|
+
' try { $fx_x = fx-arith $a }',
|
|
1233
|
+
" catch { [Console]::Error.WriteLine('bash: [[: ' + [string]$a + ': integer expression expected'); $script:fx_exit = 1; return $false }",
|
|
1234
|
+
' try { $fx_y = fx-arith $b }',
|
|
1235
|
+
" catch { [Console]::Error.WriteLine('bash: [[: ' + [string]$b + ': integer expression expected'); $script:fx_exit = 1; return $false }",
|
|
1236
|
+
' switch ($op) {',
|
|
1237
|
+
" '-eq' { return ($fx_x -eq $fx_y) }",
|
|
1238
|
+
" '-ne' { return ($fx_x -ne $fx_y) }",
|
|
1239
|
+
" '-lt' { return ($fx_x -lt $fx_y) }",
|
|
1240
|
+
" '-le' { return ($fx_x -le $fx_y) }",
|
|
1241
|
+
" '-gt' { return ($fx_x -gt $fx_y) }",
|
|
1242
|
+
" '-ge' { return ($fx_x -ge $fx_y) }",
|
|
1243
|
+
' }',
|
|
1244
|
+
' return $false',
|
|
1245
|
+
'}',
|
|
1246
|
+
].join('\n');
|
|
758
1247
|
function strNe(e) {
|
|
759
1248
|
return "([string](" + e + ") -ne '')";
|
|
760
1249
|
}
|
|
1250
|
+
const FX_ISLINK_FN = [
|
|
1251
|
+
'function fx-islink($p) {',
|
|
1252
|
+
' try {',
|
|
1253
|
+
' $fx_li = Get-Item -LiteralPath ([string]$p) -Force -ErrorAction Stop',
|
|
1254
|
+
' return [bool]($fx_li.Attributes -band [IO.FileAttributes]::ReparsePoint)',
|
|
1255
|
+
' } catch { return $false }',
|
|
1256
|
+
'}',
|
|
1257
|
+
].join('\n');
|
|
1258
|
+
const FX_ENVGET_FN = [
|
|
1259
|
+
'function fx-envexact($n) {',
|
|
1260
|
+
' return (Get-ChildItem Env: | Where-Object { $_.Name -ceq ([string]$n) } | Select-Object -First 1)',
|
|
1261
|
+
'}',
|
|
1262
|
+
'function fx-svdec($s) {',
|
|
1263
|
+
' $s = [string]$s',
|
|
1264
|
+
' $sb = New-Object System.Text.StringBuilder',
|
|
1265
|
+
' $i = 0',
|
|
1266
|
+
' while ($i -lt $s.Length) {',
|
|
1267
|
+
' $c = $s[$i]',
|
|
1268
|
+
' if ($c -eq [char]92 -and ($i + 1) -lt $s.Length) {',
|
|
1269
|
+
' $n2 = $s[$i + 1]',
|
|
1270
|
+
' if ($n2 -eq [char]110) { [void]$sb.Append([char]10); $i += 2; continue }',
|
|
1271
|
+
' if ($n2 -eq [char]114) { [void]$sb.Append([char]13); $i += 2; continue }',
|
|
1272
|
+
' if ($n2 -eq [char]92) { [void]$sb.Append([char]92); $i += 2; continue }',
|
|
1273
|
+
' }',
|
|
1274
|
+
' [void]$sb.Append($c)',
|
|
1275
|
+
' $i++',
|
|
1276
|
+
' }',
|
|
1277
|
+
' return [string]$sb',
|
|
1278
|
+
'}',
|
|
1279
|
+
'function fx-setval-get($n) {',
|
|
1280
|
+
' foreach ($fx_pair in @($env:FAUXNIX_SETVALS -split [string][char]10)) {',
|
|
1281
|
+
' $fx_eq = $fx_pair.IndexOf([char]61)',
|
|
1282
|
+
' if ($fx_eq -lt 1) { continue }',
|
|
1283
|
+
' if ($fx_pair.Substring(0, $fx_eq) -ceq [string]$n) { return (fx-svdec $fx_pair.Substring($fx_eq + 1)) }',
|
|
1284
|
+
' }',
|
|
1285
|
+
' return $null',
|
|
1286
|
+
'}',
|
|
1287
|
+
'function fx-envexplicit($n) {',
|
|
1288
|
+
' $fx_sv = fx-setval-get $n',
|
|
1289
|
+
' if ($null -ne $fx_sv) { return [string]$fx_sv }',
|
|
1290
|
+
' $fx_ev = fx-envexact $n',
|
|
1291
|
+
' if ($fx_ev) { return [string]$fx_ev.Value }',
|
|
1292
|
+
// Windows preserves the provider entry's original casing (notably Path),
|
|
1293
|
+
// even after `$env:PATH = ...`. SETVARS is the case-sensitive proof that
|
|
1294
|
+
// this shell explicitly wrote the requested Bash name, so only here is a
|
|
1295
|
+
// case-insensitive process-environment fallback semantically safe.
|
|
1296
|
+
' $fx_v = [Environment]::GetEnvironmentVariable([string]$n)',
|
|
1297
|
+
" if ($null -eq $fx_v) { return '' }",
|
|
1298
|
+
' return [string]$fx_v',
|
|
1299
|
+
'}',
|
|
1300
|
+
'function fx-home {',
|
|
1301
|
+
" if (@($env:FAUXNIX_SETVARS -split ';' | Where-Object { $_ -ceq 'HOME' }).Count -gt 0) { return (fx-envexplicit 'HOME') }",
|
|
1302
|
+
" if (@($env:FAUXNIX_UNSETVARS -split ';' | Where-Object { $_ -ceq 'HOME' }).Count -gt 0) { return [string]$HOME }",
|
|
1303
|
+
' return [string]$HOME',
|
|
1304
|
+
'}',
|
|
1305
|
+
'function fx-envget($n) {',
|
|
1306
|
+
' $n = [string]$n',
|
|
1307
|
+
" if (@($env:FAUXNIX_UNSETVARS -split ';' | Where-Object { $_ -ceq $n }).Count -gt 0) { return '' }",
|
|
1308
|
+
" if (@($env:FAUXNIX_SETVARS -split ';' | Where-Object { $_ -ceq $n }).Count -gt 0) { return (fx-envexplicit $n) }",
|
|
1309
|
+
" if ($n -ceq 'HOME') { return [string]$HOME }",
|
|
1310
|
+
" if ($n -ceq 'PWD') { return [string]$PWD.Path }",
|
|
1311
|
+
" if ($n -ceq 'USER' -or $n -ceq 'LOGNAME') { return [string]$env:USERNAME }",
|
|
1312
|
+
" if ($n -ceq 'PATH') { return [string]$env:PATH }",
|
|
1313
|
+
" if ($n -ceq 'SHELL') { return 'powershell' }",
|
|
1314
|
+
" if ($n -ceq 'TERM') { return 'xterm-256color' }",
|
|
1315
|
+
" if ($n -ceq 'OLDPWD') { return [string]$env:FAUXNIX_OLDPWD }",
|
|
1316
|
+
" if ($n -ceq '?') { return [string]$fx_prev }",
|
|
1317
|
+
" if ($n -ceq '$') { return [string]$PID }",
|
|
1318
|
+
" if ($n -ceq 'HOSTNAME') { return [string]$env:COMPUTERNAME }",
|
|
1319
|
+
' $fx_ev = fx-envexact $n',
|
|
1320
|
+
" if (-not $fx_ev) { return '' }",
|
|
1321
|
+
' return [string]$fx_ev.Value',
|
|
1322
|
+
'}',
|
|
1323
|
+
].join('\n');
|
|
1324
|
+
/** Like exprOfWord, but $var is an exact-case env lookup (bash, not $env:). */
|
|
1325
|
+
function kshExprOfWord(w) {
|
|
1326
|
+
const expanded = [];
|
|
1327
|
+
let tilde = false;
|
|
1328
|
+
if (w.length > 0 &&
|
|
1329
|
+
w[0].kind === 'Text' &&
|
|
1330
|
+
!w[0].escaped &&
|
|
1331
|
+
w[0].text.startsWith('~')) {
|
|
1332
|
+
tilde = true;
|
|
1333
|
+
const rest = w[0].text.slice(1);
|
|
1334
|
+
if (rest)
|
|
1335
|
+
expanded.push({ kind: 'Text', text: rest });
|
|
1336
|
+
expanded.push(...w.slice(1));
|
|
1337
|
+
}
|
|
1338
|
+
else {
|
|
1339
|
+
expanded.push(...w);
|
|
1340
|
+
}
|
|
1341
|
+
if (tilde && expanded.length === 0)
|
|
1342
|
+
return '(fx-home)';
|
|
1343
|
+
if (!tilde && expanded.length === 1 && expanded[0].kind === 'Var') {
|
|
1344
|
+
return '(fx-envget ' + psStr(expanded[0].name) + ')';
|
|
1345
|
+
}
|
|
1346
|
+
const literal = !tilde && expanded.every((p) => p.kind === 'Text' || p.kind === 'SingleQuoted');
|
|
1347
|
+
if (literal) {
|
|
1348
|
+
const text = expanded.map((p) => p.text).join('');
|
|
1349
|
+
return pathExpr(normalizeLiteralPath(text));
|
|
1350
|
+
}
|
|
1351
|
+
let out = '"';
|
|
1352
|
+
if (tilde)
|
|
1353
|
+
out += '$(fx-home)';
|
|
1354
|
+
const emitPart = (p) => {
|
|
1355
|
+
switch (p.kind) {
|
|
1356
|
+
case 'Text':
|
|
1357
|
+
case 'SingleQuoted':
|
|
1358
|
+
out += escapeDq(String(p.text));
|
|
1359
|
+
break;
|
|
1360
|
+
case 'DoubleQuoted':
|
|
1361
|
+
for (const q of p.parts)
|
|
1362
|
+
emitPart(q);
|
|
1363
|
+
break;
|
|
1364
|
+
case 'Var':
|
|
1365
|
+
out += '$(fx-envget ' + psStr(p.name) + ')';
|
|
1366
|
+
break;
|
|
1367
|
+
case 'CmdSub':
|
|
1368
|
+
out += '$(' + translateCmdSub(p.cmd) + ')';
|
|
1369
|
+
break;
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
for (const p of expanded)
|
|
1373
|
+
emitPart(p);
|
|
1374
|
+
out += '"';
|
|
1375
|
+
return out;
|
|
1376
|
+
}
|
|
1377
|
+
const FX_ISSET_FN = [
|
|
1378
|
+
'function fx-isset($n) {',
|
|
1379
|
+
' $n = [string]$n',
|
|
1380
|
+
" if ($n -eq '') { return $false }",
|
|
1381
|
+
" if ($n -match '^([A-Za-z_][A-Za-z0-9_]*)\\[(0|@|\\*)\\]$') { $n = $Matches[1] }",
|
|
1382
|
+
" elseif ($n -match '^[A-Za-z_][A-Za-z0-9_]*\\[') { return $false }",
|
|
1383
|
+
" if (@($env:FAUXNIX_UNSETVARS -split ';' | Where-Object { $_ -ceq $n }).Count -gt 0) { return $false }",
|
|
1384
|
+
" if (@($env:FAUXNIX_SETVARS -split ';' | Where-Object { $_ -ceq $n }).Count -gt 0) { return $true }",
|
|
1385
|
+
" if (@('HOME','PWD','USER','LOGNAME','PATH','SHELL','TERM','HOSTNAME') | Where-Object { $_ -ceq $n }) { return $true }",
|
|
1386
|
+
" if ($n -ceq 'OLDPWD') { return [bool]$env:FAUXNIX_OLDPWD }",
|
|
1387
|
+
" return [bool](Get-ChildItem Env: | Where-Object { $_.Name -ceq $n } | Select-Object -First 1)",
|
|
1388
|
+
'}',
|
|
1389
|
+
].join('\n');
|
|
1390
|
+
function testVExpr(w) {
|
|
1391
|
+
return '(fx-isset ([string](' + kshExprOfWord(w) + ')))';
|
|
1392
|
+
}
|
|
761
1393
|
function testUnaryExpr(op, w) {
|
|
762
1394
|
switch (op) {
|
|
763
1395
|
case '-e':
|
|
1396
|
+
case '-a':
|
|
764
1397
|
case '-r':
|
|
765
1398
|
case '-w':
|
|
766
1399
|
case '-x':
|
|
767
|
-
return '(Test-Path -LiteralPath ' +
|
|
1400
|
+
return '(Test-Path -LiteralPath ' + kshExprOfWord(w) + ')';
|
|
768
1401
|
case '-f':
|
|
769
|
-
return '(Test-Path -LiteralPath ' +
|
|
1402
|
+
return '(Test-Path -LiteralPath ' + kshExprOfWord(w) + ' -PathType Leaf)';
|
|
770
1403
|
case '-d':
|
|
771
|
-
return '(Test-Path -LiteralPath ' +
|
|
1404
|
+
return '(Test-Path -LiteralPath ' + kshExprOfWord(w) + ' -PathType Container)';
|
|
1405
|
+
case '-L':
|
|
1406
|
+
case '-h':
|
|
1407
|
+
return '(fx-islink ' + kshExprOfWord(w) + ')';
|
|
1408
|
+
case '-v':
|
|
1409
|
+
return testVExpr(w);
|
|
772
1410
|
case '-s':
|
|
773
1411
|
return ('((Test-Path -LiteralPath ' +
|
|
774
|
-
|
|
1412
|
+
kshExprOfWord(w) +
|
|
775
1413
|
' -PathType Leaf) -and ((Get-Item -LiteralPath ' +
|
|
776
|
-
|
|
1414
|
+
kshExprOfWord(w) +
|
|
777
1415
|
' -Force).Length -gt 0))');
|
|
778
1416
|
case '-z':
|
|
779
|
-
return "([string](" +
|
|
1417
|
+
return "([string](" + kshExprOfWord(w) + ") -eq '')";
|
|
780
1418
|
case '-n':
|
|
781
1419
|
default:
|
|
782
|
-
return strNe(
|
|
1420
|
+
return strNe(kshExprOfWord(w));
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
const FX_RE_FN = [
|
|
1424
|
+
'function fx-re($a, $b) {',
|
|
1425
|
+
' try {',
|
|
1426
|
+
' $fx_rm = [regex]::Match([string]$a, (fx-posixre ([string]$b)), [Text.RegularExpressions.RegexOptions]::Singleline)',
|
|
1427
|
+
' if ($fx_rm.Success) {',
|
|
1428
|
+
' $env:BASH_REMATCH = [string]$fx_rm.Value',
|
|
1429
|
+
" $env:FAUXNIX_SETVARS = ((@($env:FAUXNIX_SETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne 'BASH_REMATCH' }) + 'BASH_REMATCH') -join ';')",
|
|
1430
|
+
" $env:FAUXNIX_UNSETVARS = (@($env:FAUXNIX_UNSETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne 'BASH_REMATCH' }) -join ';')",
|
|
1431
|
+
' $fx_enc = ([string]$fx_rm.Value).Replace([string][char]92, ([string][char]92 + [string][char]92)).Replace([string][char]13, ([string][char]92 + [char]114)).Replace([string][char]10, ([string][char]92 + [char]110))',
|
|
1432
|
+
" $fx_sv = @(); foreach ($fx_pair in @($env:FAUXNIX_SETVALS -split [string][char]10)) { $fx_eq = $fx_pair.IndexOf([char]61); if ($fx_eq -lt 1) { continue }; if ($fx_pair.Substring(0, $fx_eq) -cne 'BASH_REMATCH') { $fx_sv += $fx_pair } }",
|
|
1433
|
+
" $fx_sv += ('BASH_REMATCH' + [string][char]61 + $fx_enc); $env:FAUXNIX_SETVALS = ($fx_sv -join [string][char]10)",
|
|
1434
|
+
' return $true',
|
|
1435
|
+
' }',
|
|
1436
|
+
" Remove-Item -LiteralPath 'Env:\\BASH_REMATCH' -ErrorAction SilentlyContinue",
|
|
1437
|
+
" $env:FAUXNIX_SETVARS = (@($env:FAUXNIX_SETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne 'BASH_REMATCH' }) -join ';')",
|
|
1438
|
+
" $env:FAUXNIX_UNSETVARS = ((@($env:FAUXNIX_UNSETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne 'BASH_REMATCH' }) + 'BASH_REMATCH') -join ';')",
|
|
1439
|
+
" $fx_sv = @(); foreach ($fx_pair in @($env:FAUXNIX_SETVALS -split [string][char]10)) { $fx_eq = $fx_pair.IndexOf([char]61); if ($fx_eq -lt 1) { continue }; if ($fx_pair.Substring(0, $fx_eq) -cne 'BASH_REMATCH') { $fx_sv += $fx_pair } }; $env:FAUXNIX_SETVALS = ($fx_sv -join [string][char]10)",
|
|
1440
|
+
' return $false',
|
|
1441
|
+
' }',
|
|
1442
|
+
" catch { [Console]::Error.WriteLine('bash: [[: invalid regular expression'); $script:fx_exit = 2; return $false }",
|
|
1443
|
+
'}',
|
|
1444
|
+
].join('\n');
|
|
1445
|
+
const POSIX_CLASS_INNER = [
|
|
1446
|
+
[/\[:alnum:\]/g, '\\p{L}\\p{Nd}'],
|
|
1447
|
+
[/\[:alpha:\]/g, '\\p{L}'],
|
|
1448
|
+
[/\[:blank:\]/g, ' \\t'],
|
|
1449
|
+
[/\[:cntrl:\]/g, '\\x00-\\x1F\\x7F'],
|
|
1450
|
+
[/\[:digit:\]/g, '0-9'],
|
|
1451
|
+
[/\[:graph:\]/g, '\\x21-\\x7E'],
|
|
1452
|
+
[/\[:lower:\]/g, '\\p{Ll}'],
|
|
1453
|
+
[/\[:print:\]/g, '\\x20-\\x7E'],
|
|
1454
|
+
[/\[:punct:\]/g, '!-/:-@\\[-`{-~'],
|
|
1455
|
+
[/\[:space:\]/g, '\\s'],
|
|
1456
|
+
[/\[:upper:\]/g, '\\p{Lu}'],
|
|
1457
|
+
[/\[:word:\]/g, '\\p{L}\\p{Nd}_'],
|
|
1458
|
+
[/\[:xdigit:\]/g, '0-9A-Fa-f'],
|
|
1459
|
+
];
|
|
1460
|
+
/** Replace `[:name:]` only inside an already-extracted bracket expression. */
|
|
1461
|
+
function replacePosixClassesInBracketInner(s) {
|
|
1462
|
+
let t = s;
|
|
1463
|
+
for (const [re, rep] of POSIX_CLASS_INNER)
|
|
1464
|
+
t = t.replace(re, rep);
|
|
1465
|
+
return t;
|
|
1466
|
+
}
|
|
1467
|
+
/** Index of the `]` that closes the `[` at `i`, skipping `[:name:]`. */
|
|
1468
|
+
function findBracketClose(s, i) {
|
|
1469
|
+
let j = i + 1;
|
|
1470
|
+
if (j < s.length && (s[j] === '!' || s[j] === '^'))
|
|
1471
|
+
j++;
|
|
1472
|
+
if (j < s.length && s[j] === ']')
|
|
1473
|
+
j++;
|
|
1474
|
+
while (j < s.length) {
|
|
1475
|
+
if (s.startsWith('[:', j)) {
|
|
1476
|
+
const end = s.indexOf(':]', j + 2);
|
|
1477
|
+
if (end >= 0) {
|
|
1478
|
+
j = end + 2;
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
if (s[j] === ']')
|
|
1483
|
+
return j;
|
|
1484
|
+
j++;
|
|
1485
|
+
}
|
|
1486
|
+
return -1;
|
|
1487
|
+
}
|
|
1488
|
+
/** POSIX ERE specials that keep their backslash; anything else is the char. */
|
|
1489
|
+
const ERE_KEEP_ESC = new Set([
|
|
1490
|
+
'.',
|
|
1491
|
+
'[',
|
|
1492
|
+
']',
|
|
1493
|
+
'\\',
|
|
1494
|
+
'(',
|
|
1495
|
+
')',
|
|
1496
|
+
'*',
|
|
1497
|
+
'+',
|
|
1498
|
+
'?',
|
|
1499
|
+
'{',
|
|
1500
|
+
'}',
|
|
1501
|
+
'|',
|
|
1502
|
+
'^',
|
|
1503
|
+
'$',
|
|
1504
|
+
'1',
|
|
1505
|
+
'2',
|
|
1506
|
+
'3',
|
|
1507
|
+
'4',
|
|
1508
|
+
'5',
|
|
1509
|
+
'6',
|
|
1510
|
+
'7',
|
|
1511
|
+
'8',
|
|
1512
|
+
'9',
|
|
1513
|
+
'b',
|
|
1514
|
+
'B',
|
|
1515
|
+
'w',
|
|
1516
|
+
'W',
|
|
1517
|
+
's',
|
|
1518
|
+
'S',
|
|
1519
|
+
]);
|
|
1520
|
+
/**
|
|
1521
|
+
* Map a POSIX ERE to a .NET pattern: POSIX classes only inside `[…]`,
|
|
1522
|
+
* and drop .NET-only escapes (`\\d` → `d`) so `[[ 1 =~ $re ]]` with
|
|
1523
|
+
* `re='\\d'` stays false.
|
|
1524
|
+
*/
|
|
1525
|
+
function rewriteEre(s) {
|
|
1526
|
+
let out = '';
|
|
1527
|
+
for (let i = 0; i < s.length; i++) {
|
|
1528
|
+
if (s[i] === '\\') {
|
|
1529
|
+
if (i + 1 >= s.length) {
|
|
1530
|
+
out += '\\\\';
|
|
1531
|
+
break;
|
|
1532
|
+
}
|
|
1533
|
+
const n = s[i + 1];
|
|
1534
|
+
out += ERE_KEEP_ESC.has(n) ? '\\' + n : n;
|
|
1535
|
+
i++;
|
|
1536
|
+
continue;
|
|
1537
|
+
}
|
|
1538
|
+
if (s[i] === '[') {
|
|
1539
|
+
const j = findBracketClose(s, i);
|
|
1540
|
+
if (j < 0) {
|
|
1541
|
+
out += '[';
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1544
|
+
out += '[' + replacePosixClassesInBracketInner(s.slice(i + 1, j)) + ']';
|
|
1545
|
+
i = j;
|
|
1546
|
+
continue;
|
|
1547
|
+
}
|
|
1548
|
+
out += s[i];
|
|
1549
|
+
}
|
|
1550
|
+
return out;
|
|
1551
|
+
}
|
|
1552
|
+
/** Escape glob metacharacters so a quoted/escaped piece stays literal. */
|
|
1553
|
+
function globLitEsc(s) {
|
|
1554
|
+
return s.replace(/([*?[\]\\()\-])/g, '\\$1');
|
|
1555
|
+
}
|
|
1556
|
+
const FX_LIKEESC_FN = [
|
|
1557
|
+
'function fx-likeesc($s) {',
|
|
1558
|
+
' $fx_o = New-Object System.Text.StringBuilder',
|
|
1559
|
+
' foreach ($fx_ch in ([string]$s).ToCharArray()) {',
|
|
1560
|
+
" if (@('*','?','[',']','`') -contains [string]$fx_ch) { [void]$fx_o.Append('`' + $fx_ch) }",
|
|
1561
|
+
' else { [void]$fx_o.Append($fx_ch) }',
|
|
1562
|
+
' }',
|
|
1563
|
+
' $fx_o.ToString()',
|
|
1564
|
+
'}',
|
|
1565
|
+
].join('\n');
|
|
1566
|
+
/** Build a regex pattern, escaping quoted/backslash-escaped portions. */
|
|
1567
|
+
function mergeUnescapedText(w) {
|
|
1568
|
+
const merged = [];
|
|
1569
|
+
for (const p of w) {
|
|
1570
|
+
const last = merged[merged.length - 1];
|
|
1571
|
+
if (p.kind === 'Text' &&
|
|
1572
|
+
!p.escaped &&
|
|
1573
|
+
last &&
|
|
1574
|
+
last.kind === 'Text' &&
|
|
1575
|
+
!last.escaped) {
|
|
1576
|
+
last.text += p.text;
|
|
1577
|
+
}
|
|
1578
|
+
else {
|
|
1579
|
+
merged.push(p.kind === 'Text' ? { ...p } : p);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
return merged;
|
|
1583
|
+
}
|
|
1584
|
+
function regexOperandExpr(w) {
|
|
1585
|
+
if (w.length === 0)
|
|
1586
|
+
return "''";
|
|
1587
|
+
const bits = [];
|
|
1588
|
+
let rest = w;
|
|
1589
|
+
if (w[0].kind === 'Text' && !w[0].escaped && w[0].text.startsWith('~')) {
|
|
1590
|
+
bits.push('[regex]::Escape([string](fx-home))');
|
|
1591
|
+
const after = w[0].text.slice(1);
|
|
1592
|
+
rest = after ? [{ kind: 'Text', text: after }, ...w.slice(1)] : w.slice(1);
|
|
1593
|
+
}
|
|
1594
|
+
for (const p of mergeUnescapedText(rest)) {
|
|
1595
|
+
if (p.kind === 'SingleQuoted') {
|
|
1596
|
+
bits.push('[regex]::Escape(' + psStr(p.text) + ')');
|
|
1597
|
+
}
|
|
1598
|
+
else if (p.kind === 'DoubleQuoted') {
|
|
1599
|
+
bits.push('[regex]::Escape([string](' + kshExprOfWord([p]) + '))');
|
|
1600
|
+
}
|
|
1601
|
+
else if (p.kind === 'Text') {
|
|
1602
|
+
bits.push(p.escaped
|
|
1603
|
+
? '[regex]::Escape(' + psStr(p.text) + ')'
|
|
1604
|
+
: psStr(rewriteEre(p.text)));
|
|
1605
|
+
}
|
|
1606
|
+
else {
|
|
1607
|
+
bits.push('[string](' + kshExprOfWord([p]) + ')');
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
return bits.length === 1 ? bits[0] : '(' + bits.join(' + ') + ')';
|
|
1611
|
+
}
|
|
1612
|
+
/** Build a bash glob string: quoted/escaped metas stay literal. */
|
|
1613
|
+
function globPatternExpr(w) {
|
|
1614
|
+
if (w.length === 0)
|
|
1615
|
+
return "''";
|
|
1616
|
+
const bits = [];
|
|
1617
|
+
let rest = w;
|
|
1618
|
+
if (w[0].kind === 'Text' && !w[0].escaped && w[0].text.startsWith('~')) {
|
|
1619
|
+
bits.push('(fx-gesc (([string](fx-home)).Replace([char]92, [char]47)))');
|
|
1620
|
+
const after = w[0].text.slice(1);
|
|
1621
|
+
rest = after ? [{ kind: 'Text', text: after }, ...w.slice(1)] : w.slice(1);
|
|
1622
|
+
}
|
|
1623
|
+
for (const p of mergeUnescapedText(rest)) {
|
|
1624
|
+
if (p.kind === 'SingleQuoted') {
|
|
1625
|
+
bits.push(psStr(globLitEsc(p.text)));
|
|
1626
|
+
}
|
|
1627
|
+
else if (p.kind === 'DoubleQuoted') {
|
|
1628
|
+
bits.push('(fx-gesc ([string](' + kshExprOfWord([p]) + ')))');
|
|
1629
|
+
}
|
|
1630
|
+
else if (p.kind === 'Text') {
|
|
1631
|
+
bits.push(psStr(p.escaped ? globLitEsc(p.text) : p.text));
|
|
1632
|
+
}
|
|
1633
|
+
else {
|
|
1634
|
+
bits.push('[string](' + kshExprOfWord([p]) + ')');
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
return bits.length === 1 ? bits[0] : '(' + bits.join(' + ') + ')';
|
|
1638
|
+
}
|
|
1639
|
+
/** String operand for [[ compares — do not POSIX-normalize paths. */
|
|
1640
|
+
function stringOperandExpr(w) {
|
|
1641
|
+
if (w.length > 0 && w[0].kind === 'Text' && !w[0].escaped && w[0].text.startsWith('~')) {
|
|
1642
|
+
return '[string](' + kshExprOfWord(w) + ')';
|
|
783
1643
|
}
|
|
1644
|
+
const lit = w.every((p) => p.kind === 'Text' || p.kind === 'SingleQuoted')
|
|
1645
|
+
? w.map((p) => p.text).join('')
|
|
1646
|
+
: null;
|
|
1647
|
+
if (lit !== null)
|
|
1648
|
+
return psStr(lit);
|
|
1649
|
+
return '[string](' + kshExprOfWord(w) + ')';
|
|
784
1650
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
1651
|
+
const FX_POSIX_INNER_PS = [
|
|
1652
|
+
" $inner = $inner.Replace('[:alnum:]', '\\p{L}\\p{Nd}')",
|
|
1653
|
+
" $inner = $inner.Replace('[:alpha:]', '\\p{L}')",
|
|
1654
|
+
" $inner = $inner.Replace('[:blank:]', ' \\t')",
|
|
1655
|
+
" $inner = $inner.Replace('[:cntrl:]', '\\x00-\\x1F\\x7F')",
|
|
1656
|
+
" $inner = $inner.Replace('[:digit:]', '0-9')",
|
|
1657
|
+
" $inner = $inner.Replace('[:graph:]', '\\x21-\\x7E')",
|
|
1658
|
+
" $inner = $inner.Replace('[:lower:]', '\\p{Ll}')",
|
|
1659
|
+
" $inner = $inner.Replace('[:print:]', '\\x20-\\x7E')",
|
|
1660
|
+
" $inner = $inner.Replace('[:punct:]', '!-/:-@\\[-`{-~')",
|
|
1661
|
+
" $inner = $inner.Replace('[:space:]', '\\s')",
|
|
1662
|
+
" $inner = $inner.Replace('[:upper:]', '\\p{Lu}')",
|
|
1663
|
+
" $inner = $inner.Replace('[:word:]', '\\p{L}\\p{Nd}_')",
|
|
1664
|
+
" $inner = $inner.Replace('[:xdigit:]', '0-9A-Fa-f')",
|
|
1665
|
+
].join('\n');
|
|
1666
|
+
const FX_GMATCH_FN = [
|
|
1667
|
+
'function fx-gesc($s) {',
|
|
1668
|
+
' $o = New-Object System.Text.StringBuilder',
|
|
1669
|
+
' foreach ($ch in ([string]$s).ToCharArray()) {',
|
|
1670
|
+
" if (@('*','?','[',']','\\','-','(',')') -contains [string]$ch) { [void]$o.Append('\\') }",
|
|
1671
|
+
' [void]$o.Append($ch)',
|
|
1672
|
+
' }',
|
|
1673
|
+
' return $o.ToString()',
|
|
1674
|
+
'}',
|
|
1675
|
+
'function fx-gposix([char]$ch, [string]$name) {',
|
|
1676
|
+
' $c = [int]$ch',
|
|
1677
|
+
' switch ($name) {',
|
|
1678
|
+
" 'digit' { return ($c -ge 48 -and $c -le 57) }",
|
|
1679
|
+
" 'xdigit' { return (($c -ge 48 -and $c -le 57) -or ($c -ge 65 -and $c -le 70) -or ($c -ge 97 -and $c -le 102)) }",
|
|
1680
|
+
" 'lower' { return [char]::IsLower($ch) }",
|
|
1681
|
+
" 'upper' { return [char]::IsUpper($ch) }",
|
|
1682
|
+
" 'alpha' { return [char]::IsLetter($ch) }",
|
|
1683
|
+
" 'alnum' { return ([char]::IsLetter($ch) -or [char]::IsDigit($ch)) }",
|
|
1684
|
+
" 'word' { return ([char]::IsLetter($ch) -or [char]::IsDigit($ch) -or $ch -eq [char]95) }",
|
|
1685
|
+
' \'blank\' { return ($ch -eq [char]32 -or $ch -eq [char]9) }',
|
|
1686
|
+
' \'space\' { return ($ch -eq [char]32 -or $ch -eq [char]9 -or $ch -eq [char]10 -or $ch -eq [char]11 -or $ch -eq [char]12 -or $ch -eq [char]13) }',
|
|
1687
|
+
' \'cntrl\' { return ($c -le 31 -or $c -eq 127) }',
|
|
1688
|
+
" 'graph' { return ($c -ge 33 -and $c -le 126) }",
|
|
1689
|
+
" 'print' { return ($c -ge 32 -and $c -le 126) }",
|
|
1690
|
+
" 'punct' { return (($c -ge 33 -and $c -le 47) -or ($c -ge 58 -and $c -le 64) -or ($c -ge 91 -and $c -le 96) -or ($c -ge 123 -and $c -le 126)) }",
|
|
1691
|
+
' default { return $false }',
|
|
1692
|
+
' }',
|
|
1693
|
+
'}',
|
|
1694
|
+
'function fx-ginclass([char]$ch, [string]$inner) {',
|
|
1695
|
+
' $i = 0',
|
|
1696
|
+
' while ($i -lt $inner.Length) {',
|
|
1697
|
+
' if ($inner[$i] -eq [char]92 -and ($i + 1) -lt $inner.Length) {',
|
|
1698
|
+
' if ([int]$inner[$i + 1] -ceq [int]$ch) { return $true }',
|
|
1699
|
+
' $i += 2; continue',
|
|
1700
|
+
' }',
|
|
1701
|
+
" if (($i + 1) -lt $inner.Length -and $inner[$i] -eq '[' -and $inner[$i + 1] -eq ':') {",
|
|
1702
|
+
" $k = $inner.IndexOf(':]', $i + 2)",
|
|
1703
|
+
' if ($k -ge 0) {',
|
|
1704
|
+
' if (fx-gposix $ch $inner.Substring($i + 2, $k - $i - 2)) { return $true }',
|
|
1705
|
+
' $i = $k + 2; continue',
|
|
1706
|
+
' }',
|
|
1707
|
+
' }',
|
|
1708
|
+
' if ($i -eq 0 -and $inner[$i] -eq [char]45) {',
|
|
1709
|
+
' if ($ch -eq [char]45) { return $true }',
|
|
1710
|
+
' $i++; continue',
|
|
1711
|
+
' }',
|
|
1712
|
+
" if (($i + 2) -lt $inner.Length -and $inner[$i + 1] -eq '-' -and $inner[$i] -ne [char]92) {",
|
|
1713
|
+
' $lo = [int][char]$inner[$i]; $hi = [int][char]$inner[$i + 2]',
|
|
1714
|
+
' if ([int]$ch -ge $lo -and [int]$ch -le $hi) { return $true }',
|
|
1715
|
+
' $i += 3; continue',
|
|
1716
|
+
' }',
|
|
1717
|
+
' if ([int]$inner[$i] -ceq [int]$ch) { return $true }',
|
|
1718
|
+
' $i++',
|
|
1719
|
+
' }',
|
|
1720
|
+
' return $false',
|
|
1721
|
+
'}',
|
|
1722
|
+
'function fx-gext([string]$p, [int]$pi) {',
|
|
1723
|
+
' $pref = [string]$p[$pi]',
|
|
1724
|
+
' $i = $pi + 2',
|
|
1725
|
+
' $alts = New-Object System.Collections.ArrayList',
|
|
1726
|
+
' $start = $i',
|
|
1727
|
+
' $depth = 1',
|
|
1728
|
+
' while ($i -lt $p.Length -and $depth -gt 0) {',
|
|
1729
|
+
' $c = $p[$i]',
|
|
1730
|
+
' if ($c -eq [char]92 -and ($i + 1) -lt $p.Length) { $i += 2; continue }',
|
|
1731
|
+
" if (($i + 1) -lt $p.Length -and $p[$i + 1] -eq '(' -and @('*','?','@','+','!') -contains [string]$c) { $depth++; $i += 2; continue }",
|
|
1732
|
+
" if ($c -eq ')') {",
|
|
1733
|
+
' $depth--',
|
|
1734
|
+
' if ($depth -eq 0) { [void]$alts.Add($p.Substring($start, $i - $start)); $i++; break }',
|
|
1735
|
+
' $i++; continue',
|
|
1736
|
+
' }',
|
|
1737
|
+
" if ($c -eq '|' -and $depth -eq 1) { [void]$alts.Add($p.Substring($start, $i - $start)); $i++; $start = $i; continue }",
|
|
1738
|
+
' $i++',
|
|
1739
|
+
' }',
|
|
1740
|
+
' return @{ pref = $pref; alts = $alts; after = $i }',
|
|
1741
|
+
'}',
|
|
1742
|
+
'function fx-gok([string]$s, [int]$si, [string]$p, [int]$pi) {',
|
|
1743
|
+
' while ($pi -lt $p.Length) {',
|
|
1744
|
+
' $c = $p[$pi]',
|
|
1745
|
+
' if ($c -eq [char]92) {',
|
|
1746
|
+
' if (($pi + 1) -ge $p.Length) {',
|
|
1747
|
+
' if ($si -ge $s.Length -or $s[$si] -cne [char]92) { return $false }',
|
|
1748
|
+
' $si++; $pi++; continue',
|
|
1749
|
+
' }',
|
|
1750
|
+
' $n = $p[$pi + 1]',
|
|
1751
|
+
' if ($si -ge $s.Length -or $s[$si] -cne $n) { return $false }',
|
|
1752
|
+
' $si++; $pi += 2; continue',
|
|
1753
|
+
' }',
|
|
1754
|
+
" if (($pi + 1) -lt $p.Length -and $p[$pi + 1] -eq '(' -and @('*','?','@','+','!') -contains [string]$c) {",
|
|
1755
|
+
' $ex = fx-gext $p $pi',
|
|
1756
|
+
' $after = [int]$ex.after',
|
|
1757
|
+
' $alts = @($ex.alts)',
|
|
1758
|
+
" if ($ex.pref -eq '!') {",
|
|
1759
|
+
' for ($e = $si; $e -le $s.Length; $e++) {',
|
|
1760
|
+
' $hit = $false',
|
|
1761
|
+
' foreach ($alt in $alts) {',
|
|
1762
|
+
' if (fx-gok $s.Substring($si, $e - $si) 0 ([string]$alt) 0) { $hit = $true; break }',
|
|
1763
|
+
' }',
|
|
1764
|
+
' if (-not $hit) { if (fx-gok $s $e $p $after) { return $true } }',
|
|
1765
|
+
' }',
|
|
1766
|
+
' return $false',
|
|
1767
|
+
' }',
|
|
1768
|
+
" if ($ex.pref -eq '?' -or $ex.pref -eq '*') {",
|
|
1769
|
+
' if (fx-gok $s $si $p $after) { return $true }',
|
|
1770
|
+
' }',
|
|
1771
|
+
" if ($ex.pref -eq '@' -or $ex.pref -eq '?') {",
|
|
1772
|
+
' foreach ($alt in $alts) {',
|
|
1773
|
+
' for ($e = $si; $e -le $s.Length; $e++) {',
|
|
1774
|
+
' if (fx-gok $s.Substring($si, $e - $si) 0 ([string]$alt) 0) {',
|
|
1775
|
+
' if (fx-gok $s $e $p $after) { return $true }',
|
|
1776
|
+
' }',
|
|
1777
|
+
' }',
|
|
1778
|
+
' }',
|
|
1779
|
+
' return $false',
|
|
1780
|
+
' }',
|
|
1781
|
+
' $q = New-Object System.Collections.Generic.Queue[int]',
|
|
1782
|
+
" if ($ex.pref -eq '*') { $q.Enqueue($si) }",
|
|
1783
|
+
' else {',
|
|
1784
|
+
' foreach ($alt in $alts) {',
|
|
1785
|
+
' for ($e = $si; $e -le $s.Length; $e++) {',
|
|
1786
|
+
' if (fx-gok $s.Substring($si, $e - $si) 0 ([string]$alt) 0) { $q.Enqueue($e) }',
|
|
1787
|
+
' }',
|
|
1788
|
+
' }',
|
|
1789
|
+
' }',
|
|
1790
|
+
' $seen = New-Object System.Collections.Generic.HashSet[int]',
|
|
1791
|
+
' while ($q.Count -gt 0) {',
|
|
1792
|
+
' $pos = $q.Dequeue()',
|
|
1793
|
+
' if (-not $seen.Add($pos)) { continue }',
|
|
1794
|
+
' if (fx-gok $s $pos $p $after) { return $true }',
|
|
1795
|
+
' foreach ($alt in $alts) {',
|
|
1796
|
+
' for ($e = $pos + 1; $e -le $s.Length; $e++) {',
|
|
1797
|
+
' if (fx-gok $s.Substring($pos, $e - $pos) 0 ([string]$alt) 0) { $q.Enqueue($e) }',
|
|
1798
|
+
' }',
|
|
1799
|
+
' }',
|
|
1800
|
+
' }',
|
|
1801
|
+
' return $false',
|
|
1802
|
+
' }',
|
|
1803
|
+
" if ($c -eq '*') {",
|
|
1804
|
+
' $pi++',
|
|
1805
|
+
' if ($pi -ge $p.Length) { return $true }',
|
|
1806
|
+
' for ($k = $si; $k -le $s.Length; $k++) { if (fx-gok $s $k $p $pi) { return $true } }',
|
|
1807
|
+
' return $false',
|
|
1808
|
+
' }',
|
|
1809
|
+
" if ($c -eq '?') {",
|
|
1810
|
+
' if ($si -ge $s.Length) { return $false }',
|
|
1811
|
+
' $si++; $pi++; continue',
|
|
1812
|
+
' }',
|
|
1813
|
+
" if ($c -eq '[') {",
|
|
1814
|
+
' $j = $pi + 1',
|
|
1815
|
+
" if ($j -lt $p.Length -and ($p[$j] -eq '!' -or $p[$j] -eq '^')) { $j++ }",
|
|
1816
|
+
" if ($j -lt $p.Length -and $p[$j] -eq ']') { $j++ }",
|
|
1817
|
+
' while ($j -lt $p.Length) {',
|
|
1818
|
+
" if (($j + 1) -lt $p.Length -and $p[$j] -eq '[' -and $p[$j + 1] -eq ':') {",
|
|
1819
|
+
" $k = $p.IndexOf(':]', $j + 2)",
|
|
1820
|
+
' if ($k -ge 0) { $j = $k + 2; continue }',
|
|
1821
|
+
' }',
|
|
1822
|
+
" if ($p[$j] -eq ']') { break }",
|
|
1823
|
+
' $j++',
|
|
1824
|
+
' }',
|
|
1825
|
+
' if ($j -ge $p.Length) {',
|
|
1826
|
+
" if ($si -ge $s.Length -or $s[$si] -cne '[') { return $false }",
|
|
1827
|
+
' $si++; $pi++; continue',
|
|
1828
|
+
' }',
|
|
1829
|
+
' if ($si -ge $s.Length) { return $false }',
|
|
1830
|
+
' $inner = $p.Substring($pi + 1, $j - $pi - 1)',
|
|
1831
|
+
' $neg = $false',
|
|
1832
|
+
" if ($inner.StartsWith('!') -or $inner.StartsWith('^')) { $neg = $true; $inner = $inner.Substring(1) }",
|
|
1833
|
+
' $ok = fx-ginclass $s[$si] $inner',
|
|
1834
|
+
' if ($neg) { $ok = -not $ok }',
|
|
1835
|
+
' if (-not $ok) { return $false }',
|
|
1836
|
+
' $si++; $pi = $j + 1; continue',
|
|
1837
|
+
' }',
|
|
1838
|
+
' if ($si -ge $s.Length -or [int]$s[$si] -cne [int]$c) { return $false }',
|
|
1839
|
+
' $si++; $pi++; continue',
|
|
1840
|
+
' }',
|
|
1841
|
+
' return ($si -eq $s.Length)',
|
|
1842
|
+
'}',
|
|
1843
|
+
'function fx-gmatch($a, $b) {',
|
|
1844
|
+
' $a = [string]$a; $b = [string]$b',
|
|
1845
|
+
' if ($a.Length -ge 3 -and $a[1] -eq [char]58 -and $a[2] -eq [char]92) {',
|
|
1846
|
+
' $a = $a.Replace([char]92, [char]47); $b = $b.Replace([char]92, [char]47)',
|
|
1847
|
+
' } elseif ($b.Length -ge 3 -and $b[1] -eq [char]58 -and $b[2] -eq [char]92) {',
|
|
1848
|
+
' $a = $a.Replace([char]92, [char]47); $b = $b.Replace([char]92, [char]47)',
|
|
1849
|
+
' }',
|
|
1850
|
+
' return [bool](fx-gok $a 0 $b 0)',
|
|
1851
|
+
'}',
|
|
1852
|
+
].join('\n');
|
|
1853
|
+
const FX_POSIXRE_FN = [
|
|
1854
|
+
'function fx-posixre($s) {',
|
|
1855
|
+
' $t = [string]$s',
|
|
1856
|
+
' $sb = New-Object System.Text.StringBuilder',
|
|
1857
|
+
' $i = 0',
|
|
1858
|
+
" $keep = @('.','[',']','\\','(',')','*','+','?','{','}','|','^','$','1','2','3','4','5','6','7','8','9','b','B','w','W','s','S')",
|
|
1859
|
+
' while ($i -lt $t.Length) {',
|
|
1860
|
+
' $c = $t[$i]',
|
|
1861
|
+
" if ($c -eq '\\') {",
|
|
1862
|
+
" if (($i + 1) -ge $t.Length) { throw 'invalid regular expression' }",
|
|
1863
|
+
' $n = $t[$i + 1]',
|
|
1864
|
+
" if ($keep -contains [string]$n) { [void]$sb.Append('\\' + [string]$n) }",
|
|
1865
|
+
' else { [void]$sb.Append([string]$n) }',
|
|
1866
|
+
' $i += 2',
|
|
1867
|
+
' continue',
|
|
1868
|
+
' }',
|
|
1869
|
+
" if ($c -eq '(' -and ($i + 1) -lt $t.Length -and $t[$i + 1] -eq '?') {",
|
|
1870
|
+
" throw 'invalid regular expression'",
|
|
1871
|
+
' }',
|
|
1872
|
+
" if ($c -eq '[') {",
|
|
1873
|
+
' $j = $i + 1',
|
|
1874
|
+
" if ($j -lt $t.Length -and ($t[$j] -eq '!' -or $t[$j] -eq '^')) { $j++ }",
|
|
1875
|
+
" if ($j -lt $t.Length -and $t[$j] -eq ']') { $j++ }",
|
|
1876
|
+
' while ($j -lt $t.Length) {',
|
|
1877
|
+
" if (($j + 1) -lt $t.Length -and $t[$j] -eq '[' -and $t[$j + 1] -eq ':') {",
|
|
1878
|
+
" $k = $t.IndexOf(':]', $j + 2)",
|
|
1879
|
+
' if ($k -ge 0) { $j = $k + 2; continue }',
|
|
1880
|
+
' }',
|
|
1881
|
+
" if ($t[$j] -eq ']') { break }",
|
|
1882
|
+
' $j++',
|
|
1883
|
+
' }',
|
|
1884
|
+
" if ($j -ge $t.Length) { [void]$sb.Append('['); $i++; continue }",
|
|
1885
|
+
' $inner = $t.Substring($i + 1, $j - $i - 1)',
|
|
1886
|
+
FX_POSIX_INNER_PS,
|
|
1887
|
+
" [void]$sb.Append('[' + $inner + ']')",
|
|
1888
|
+
' $i = $j + 1',
|
|
1889
|
+
' continue',
|
|
1890
|
+
' }',
|
|
1891
|
+
' if ($c -eq [char]36) { [void]$sb.Append(([string][char]92) + ([char]122)); $i++; continue }',
|
|
1892
|
+
" if ($c -eq '.') { [void]$sb.Append('(?:[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|.)'); $i++; continue }",
|
|
1893
|
+
' [void]$sb.Append($c)',
|
|
1894
|
+
' $i++',
|
|
1895
|
+
' }',
|
|
1896
|
+
' return $sb.ToString()',
|
|
1897
|
+
'}',
|
|
1898
|
+
].join('\n');
|
|
1899
|
+
const FX_FCOMP_FN = [
|
|
1900
|
+
'function fx-nt($a, $b) {',
|
|
1901
|
+
' $a = [string]$a; $b = [string]$b',
|
|
1902
|
+
' $ea = Test-Path -LiteralPath $a; $eb = Test-Path -LiteralPath $b',
|
|
1903
|
+
' if ($ea -and -not $eb) { return $true }',
|
|
1904
|
+
' if (-not $ea -or -not $eb) { return $false }',
|
|
1905
|
+
' return ((Get-Item -LiteralPath $a -Force).LastWriteTimeUtc -gt (Get-Item -LiteralPath $b -Force).LastWriteTimeUtc)',
|
|
1906
|
+
'}',
|
|
1907
|
+
'function fx-ot($a, $b) {',
|
|
1908
|
+
' $a = [string]$a; $b = [string]$b',
|
|
1909
|
+
' $ea = Test-Path -LiteralPath $a; $eb = Test-Path -LiteralPath $b',
|
|
1910
|
+
' if ($eb -and -not $ea) { return $true }',
|
|
1911
|
+
' if (-not $ea -or -not $eb) { return $false }',
|
|
1912
|
+
' return ((Get-Item -LiteralPath $a -Force).LastWriteTimeUtc -lt (Get-Item -LiteralPath $b -Force).LastWriteTimeUtc)',
|
|
1913
|
+
'}',
|
|
1914
|
+
'function fx-fid($p) {',
|
|
1915
|
+
' $p = [string]$p',
|
|
1916
|
+
' try { $id = ((& fsutil.exe file queryfileid $p 2>$null) | Out-String).Trim() } catch { return \'\' }',
|
|
1917
|
+
" if (-not $id) { return '' }",
|
|
1918
|
+
' return ([IO.Path]::GetPathRoot($p) + \'|\' + $id)',
|
|
1919
|
+
'}',
|
|
1920
|
+
'function fx-ef($a, $b) {',
|
|
1921
|
+
' try {',
|
|
1922
|
+
' $ia = Get-Item -LiteralPath ([string]$a) -Force -ErrorAction Stop',
|
|
1923
|
+
' $ib = Get-Item -LiteralPath ([string]$b) -Force -ErrorAction Stop',
|
|
1924
|
+
' } catch { return $false }',
|
|
1925
|
+
' if ([string]$ia.FullName -ieq [string]$ib.FullName) { return $true }',
|
|
1926
|
+
' $fa = fx-fid $ia.FullName; $fb = fx-fid $ib.FullName',
|
|
1927
|
+
" return ($fa -ne '' -and $fa -eq $fb)",
|
|
1928
|
+
'}',
|
|
1929
|
+
].join('\n');
|
|
1930
|
+
function testBinaryExpr(l, op, r, allowKsh) {
|
|
1931
|
+
if (op === '-nt' || op === '-ot' || op === '-ef') {
|
|
1932
|
+
const lp = kshExprOfWord(l);
|
|
1933
|
+
const rp = kshExprOfWord(r);
|
|
1934
|
+
return '(fx-' + op.slice(1) + ' (' + lp + ') (' + rp + '))';
|
|
1935
|
+
}
|
|
1936
|
+
const le = allowKsh ? stringOperandExpr(l) : '[string](' + kshExprOfWord(l) + ')';
|
|
1937
|
+
const re = allowKsh ? stringOperandExpr(r) : '[string](' + kshExprOfWord(r) + ')';
|
|
1938
|
+
if (op === '=~')
|
|
1939
|
+
return '(fx-re (' + le + ') (' + regexOperandExpr(r) + '))';
|
|
1940
|
+
if (op === '>' || op === '<') {
|
|
1941
|
+
const cmp = '[string]::Compare(' + le + ', ' + re + ', [System.StringComparison]::Ordinal)';
|
|
1942
|
+
return '((' + cmp + ') ' + (op === '>' ? '-gt' : '-lt') + ' 0)';
|
|
1943
|
+
}
|
|
1944
|
+
if (op === '=' || op === '==') {
|
|
1945
|
+
if (allowKsh) {
|
|
1946
|
+
if (r.every((p) => p.kind === 'SingleQuoted' || p.kind === 'DoubleQuoted'))
|
|
1947
|
+
return '(' + le + ' -ceq ' + re + ')';
|
|
1948
|
+
return '(fx-gmatch (' + le + ') (' + globPatternExpr(r) + '))';
|
|
1949
|
+
}
|
|
789
1950
|
return '(' + le + ' -ceq ' + re + ')';
|
|
790
|
-
|
|
1951
|
+
}
|
|
1952
|
+
if (op === '!=') {
|
|
1953
|
+
if (allowKsh) {
|
|
1954
|
+
if (r.every((p) => p.kind === 'SingleQuoted' || p.kind === 'DoubleQuoted'))
|
|
1955
|
+
return '(' + le + ' -cne ' + re + ')';
|
|
1956
|
+
return '(-not (fx-gmatch (' + le + ') (' + globPatternExpr(r) + ')))';
|
|
1957
|
+
}
|
|
791
1958
|
return '(' + le + ' -cne ' + re + ')';
|
|
1959
|
+
}
|
|
1960
|
+
if (allowKsh)
|
|
1961
|
+
return '(fx-tnk (' + le + ') (' + re + ') ' + psStr(op) + ')';
|
|
792
1962
|
return '(fx-tn (' + le + ') (' + re + ') ' + psStr(op) + ')';
|
|
793
1963
|
}
|
|
794
|
-
function parseTestOr(ws, st) {
|
|
795
|
-
const r = parseTestAnd(ws, st);
|
|
1964
|
+
function parseTestOr(ws, st, allowRe) {
|
|
1965
|
+
const r = parseTestAnd(ws, st, allowRe);
|
|
796
1966
|
if (r.error)
|
|
797
1967
|
return r;
|
|
798
1968
|
let expr = r.expr;
|
|
799
|
-
while (st.i < ws.length &&
|
|
1969
|
+
while (st.i < ws.length &&
|
|
1970
|
+
(allowRe ? isUnquotedLiteral(ws[st.i], '||') : wordToString(ws[st.i]) === '-o')) {
|
|
800
1971
|
st.i++;
|
|
801
|
-
const rr = parseTestAnd(ws, st);
|
|
1972
|
+
const rr = parseTestAnd(ws, st, allowRe);
|
|
802
1973
|
if (rr.error)
|
|
803
1974
|
return rr;
|
|
804
1975
|
expr = '(' + expr + ') -or (' + rr.expr + ')';
|
|
805
1976
|
}
|
|
806
1977
|
return { expr, error: null };
|
|
807
1978
|
}
|
|
808
|
-
function parseTestAnd(ws, st) {
|
|
809
|
-
const r = parseTestNot(ws, st);
|
|
1979
|
+
function parseTestAnd(ws, st, allowRe) {
|
|
1980
|
+
const r = parseTestNot(ws, st, allowRe);
|
|
810
1981
|
if (r.error)
|
|
811
1982
|
return r;
|
|
812
1983
|
let expr = r.expr;
|
|
813
|
-
while (st.i < ws.length &&
|
|
1984
|
+
while (st.i < ws.length &&
|
|
1985
|
+
(allowRe ? isUnquotedLiteral(ws[st.i], '&&') : wordToString(ws[st.i]) === '-a')) {
|
|
814
1986
|
st.i++;
|
|
815
|
-
const rr = parseTestNot(ws, st);
|
|
1987
|
+
const rr = parseTestNot(ws, st, allowRe);
|
|
816
1988
|
if (rr.error)
|
|
817
1989
|
return rr;
|
|
818
1990
|
expr = '(' + expr + ') -and (' + rr.expr + ')';
|
|
819
1991
|
}
|
|
820
1992
|
return { expr, error: null };
|
|
821
1993
|
}
|
|
822
|
-
function parseTestNot(ws, st) {
|
|
1994
|
+
function parseTestNot(ws, st, allowRe) {
|
|
823
1995
|
const t = st.i < ws.length ? wordToString(ws[st.i]) : null;
|
|
824
|
-
if (t === '!' && st.i
|
|
1996
|
+
if (t === '!' && (!allowRe || isUnquotedLiteral(ws[st.i], '!'))) {
|
|
1997
|
+
if (st.i + 1 >= ws.length) {
|
|
1998
|
+
if (allowRe)
|
|
1999
|
+
return { expr: null, error: "`!': unary operator expected" };
|
|
2000
|
+
return parseTestAtom(ws, st, allowRe);
|
|
2001
|
+
}
|
|
825
2002
|
st.i++;
|
|
826
|
-
const r = parseTestNot(ws, st);
|
|
2003
|
+
const r = parseTestNot(ws, st, allowRe);
|
|
827
2004
|
if (r.error)
|
|
828
2005
|
return r;
|
|
829
2006
|
return { expr: '(-not (' + r.expr + '))', error: null };
|
|
830
2007
|
}
|
|
831
|
-
return parseTestAtom(ws, st);
|
|
2008
|
+
return parseTestAtom(ws, st, allowRe);
|
|
832
2009
|
}
|
|
833
|
-
function parseTestAtom(ws, st) {
|
|
2010
|
+
function parseTestAtom(ws, st, allowRe) {
|
|
834
2011
|
if (st.i >= ws.length)
|
|
835
2012
|
return { expr: null, error: 'too many arguments' };
|
|
2013
|
+
if (allowRe && isUnquotedLiteral(ws[st.i], '(')) {
|
|
2014
|
+
st.i++;
|
|
2015
|
+
const inner = parseTestOr(ws, st, allowRe);
|
|
2016
|
+
if (inner.error)
|
|
2017
|
+
return inner;
|
|
2018
|
+
if (st.i >= ws.length || !isUnquotedLiteral(ws[st.i], ')')) {
|
|
2019
|
+
return { expr: null, error: "`)' expected" };
|
|
2020
|
+
}
|
|
2021
|
+
st.i++;
|
|
2022
|
+
return inner;
|
|
2023
|
+
}
|
|
836
2024
|
const t = wordToString(ws[st.i]);
|
|
837
2025
|
if (st.i === ws.length - 1) {
|
|
2026
|
+
if (allowRe && TEST_UNARY.has(t) && isUnquotedLiteral(ws[st.i], t)) {
|
|
2027
|
+
return { expr: null, error: t + ': unary operator expected' };
|
|
2028
|
+
}
|
|
2029
|
+
if (allowRe &&
|
|
2030
|
+
isUnquotedLiteral(ws[st.i], t) &&
|
|
2031
|
+
(t === '&&' || t === '||' || t === ')' || t === '(' || t === '<' || t === '>')) {
|
|
2032
|
+
return { expr: null, error: "syntax error near unexpected token `" + t + "'" };
|
|
2033
|
+
}
|
|
838
2034
|
// single remaining word: bash test treats any non-null string as true
|
|
839
|
-
const e =
|
|
2035
|
+
const e = kshExprOfWord(ws[st.i]);
|
|
840
2036
|
st.i++;
|
|
841
2037
|
return { expr: strNe(e), error: null };
|
|
842
2038
|
}
|
|
843
|
-
if (TEST_UNARY.has(t)) {
|
|
2039
|
+
if (TEST_UNARY.has(t) && (!allowRe || isUnquotedLiteral(ws[st.i], t))) {
|
|
844
2040
|
const w = ws[st.i + 1];
|
|
2041
|
+
if (allowRe &&
|
|
2042
|
+
(isUnquotedLiteral(w, '&&') ||
|
|
2043
|
+
isUnquotedLiteral(w, '||') ||
|
|
2044
|
+
isUnquotedLiteral(w, '<') ||
|
|
2045
|
+
isUnquotedLiteral(w, '>') ||
|
|
2046
|
+
isUnquotedLiteral(w, '|'))) {
|
|
2047
|
+
return { expr: null, error: t + ': unary operator expected' };
|
|
2048
|
+
}
|
|
845
2049
|
st.i += 2;
|
|
846
2050
|
return { expr: testUnaryExpr(t, w), error: null };
|
|
847
2051
|
}
|
|
848
|
-
const
|
|
849
|
-
|
|
2052
|
+
const opWord = ws[st.i + 1];
|
|
2053
|
+
const nt = wordToString(opWord);
|
|
2054
|
+
const binaries = allowRe ? TEST_BINARY_KSH : TEST_BINARY;
|
|
2055
|
+
if ((!allowRe || isUnquotedLiteral(opWord, nt)) && binaries.has(nt)) {
|
|
850
2056
|
if (st.i + 2 < ws.length) {
|
|
851
|
-
const expr = testBinaryExpr(ws[st.i], nt, ws[st.i + 2]);
|
|
2057
|
+
const expr = testBinaryExpr(ws[st.i], nt, ws[st.i + 2], allowRe);
|
|
852
2058
|
st.i += 3;
|
|
853
2059
|
return { expr, error: null };
|
|
854
2060
|
}
|
|
855
2061
|
return { expr: null, error: 'OP:' + nt };
|
|
856
2062
|
}
|
|
857
|
-
const e =
|
|
2063
|
+
const e = kshExprOfWord(ws[st.i]);
|
|
858
2064
|
st.i++;
|
|
859
2065
|
return { expr: strNe(e), error: null };
|
|
860
2066
|
}
|
|
861
|
-
function buildTest(ws, label) {
|
|
2067
|
+
function buildTest(ws, label, allowRe = false) {
|
|
862
2068
|
if (ws.length === 0)
|
|
863
2069
|
return '$script:fx_exit = 1';
|
|
864
2070
|
const st = { i: 0 };
|
|
865
|
-
const res = parseTestOr(ws, st);
|
|
2071
|
+
const res = parseTestOr(ws, st, allowRe);
|
|
866
2072
|
let err = null;
|
|
867
2073
|
if (res.error) {
|
|
868
2074
|
err = res.error.startsWith('OP:')
|
|
@@ -873,14 +2079,53 @@ function buildTest(ws, label) {
|
|
|
873
2079
|
err = 'bash: ' + label + ': too many arguments';
|
|
874
2080
|
}
|
|
875
2081
|
if (err !== null) {
|
|
2082
|
+
// [[ syntax errors must abort translation so later ; / && segments
|
|
2083
|
+
// cannot run (bash rejects the whole list).
|
|
2084
|
+
if (label === '[[')
|
|
2085
|
+
throw new FauxnixParseError(err);
|
|
876
2086
|
return '[Console]::Error.WriteLine(' + psStr(err) + '); $script:fx_exit = 2';
|
|
877
2087
|
}
|
|
2088
|
+
const helpers = [FX_TN_FN];
|
|
2089
|
+
if (allowRe && res.expr && res.expr.indexOf('fx-tnk') >= 0) {
|
|
2090
|
+
helpers.push(FX_TNK_FN);
|
|
2091
|
+
if (helpers.indexOf(FX_ENVGET_FN) < 0)
|
|
2092
|
+
helpers.push(FX_ENVGET_FN);
|
|
2093
|
+
}
|
|
2094
|
+
if (allowRe && res.expr && res.expr.indexOf('fx-re') >= 0) {
|
|
2095
|
+
helpers.push(FX_POSIXRE_FN, FX_RE_FN);
|
|
2096
|
+
}
|
|
2097
|
+
if (allowRe &&
|
|
2098
|
+
res.expr &&
|
|
2099
|
+
res.expr.indexOf('fx-posixre') >= 0 &&
|
|
2100
|
+
helpers.indexOf(FX_POSIXRE_FN) < 0)
|
|
2101
|
+
helpers.push(FX_POSIXRE_FN);
|
|
2102
|
+
if (allowRe &&
|
|
2103
|
+
res.expr &&
|
|
2104
|
+
(res.expr.indexOf('fx-gmatch') >= 0 || res.expr.indexOf('fx-gesc') >= 0))
|
|
2105
|
+
helpers.push(FX_GMATCH_FN);
|
|
2106
|
+
if (res.expr && res.expr.indexOf('fx-islink') >= 0)
|
|
2107
|
+
helpers.push(FX_ISLINK_FN);
|
|
2108
|
+
if (res.expr && res.expr.indexOf('fx-isset') >= 0)
|
|
2109
|
+
helpers.push(FX_ISSET_FN);
|
|
2110
|
+
if (allowRe && res.expr && res.expr.indexOf('fx-likeesc') >= 0)
|
|
2111
|
+
helpers.push(FX_LIKEESC_FN);
|
|
2112
|
+
if (res.expr &&
|
|
2113
|
+
(res.expr.indexOf('fx-envget') >= 0 || res.expr.indexOf('fx-home') >= 0))
|
|
2114
|
+
helpers.push(FX_ENVGET_FN);
|
|
2115
|
+
if (res.expr &&
|
|
2116
|
+
(res.expr.indexOf('fx-nt') >= 0 ||
|
|
2117
|
+
res.expr.indexOf('fx-ot') >= 0 ||
|
|
2118
|
+
res.expr.indexOf('fx-ef') >= 0))
|
|
2119
|
+
helpers.push(FX_FCOMP_FN);
|
|
878
2120
|
return [
|
|
879
|
-
|
|
2121
|
+
...helpers,
|
|
880
2122
|
'$fx_tr = ' + res.expr,
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
2123
|
+
allowRe
|
|
2124
|
+
? 'if (-not $fx_tr) { if ($script:fx_exit -ne 2) { $script:fx_exit = 1 } } else { $script:fx_exit = 0 }'
|
|
2125
|
+
: 'if ($script:fx_exit -eq 2) { } elseif (-not $fx_tr) { $script:fx_exit = 1 }',
|
|
2126
|
+
]
|
|
2127
|
+
.filter(Boolean)
|
|
2128
|
+
.join('\n');
|
|
884
2129
|
}
|
|
885
2130
|
const test = (args) => buildTest(args, 'test');
|
|
886
2131
|
const bracket = (args) => {
|
|
@@ -889,6 +2134,12 @@ const bracket = (args) => {
|
|
|
889
2134
|
}
|
|
890
2135
|
return buildTest(args.slice(0, -1), '[');
|
|
891
2136
|
};
|
|
2137
|
+
const dblBracket = (args) => {
|
|
2138
|
+
if (args.length === 0 || !isUnquotedLiteral(args[args.length - 1], ']]')) {
|
|
2139
|
+
return '[Console]::Error.WriteLine(' + psStr("bash: [[: missing `]]'") + '); $script:fx_exit = 2';
|
|
2140
|
+
}
|
|
2141
|
+
return buildTest(args.slice(0, -1), '[[', true);
|
|
2142
|
+
};
|
|
892
2143
|
/* ------------------------------------------------------------------ */
|
|
893
2144
|
/* pushd / popd / dirs */
|
|
894
2145
|
/* ------------------------------------------------------------------ */
|
|
@@ -1119,6 +2370,7 @@ export const handlers = {
|
|
|
1119
2370
|
false: falseCmd,
|
|
1120
2371
|
test,
|
|
1121
2372
|
'[': bracket,
|
|
2373
|
+
'[[': dblBracket,
|
|
1122
2374
|
':': colon,
|
|
1123
2375
|
pushd,
|
|
1124
2376
|
popd,
|