1+ import { EXIT_CODE } from "../../../constants" ;
2+ import { VirtualFolder } from "../../virtual-drive" ;
3+ import { Command } from "../command" ;
4+ import { Shell } from "../shell" ;
5+
6+ const toExitCode = ( result : boolean ) => result ? EXIT_CODE . success : EXIT_CODE . generalError ;
7+
8+ const UNARY_OPERATORS : Record < string , ( value : string , workingDirectory : VirtualFolder ) => boolean > = {
9+ "-z" : ( value ) => value . length === 0 ,
10+ "-n" : ( value ) => value . length > 0 ,
11+ "-f" : ( value , workingDirectory ) => workingDirectory . navigate ( value ) ?. isFile ( ) ?? false ,
12+ "-d" : ( value , workingDirectory ) => workingDirectory . navigate ( value ) ?. isFolder ( ) ?? false ,
13+ "-e" : ( value , workingDirectory ) => workingDirectory . navigate ( value ) != null ,
14+ } ;
15+
16+ const BINARY_OPERATORS : Record < string , ( left : string , right : string ) => boolean > = {
17+ "=" : ( left , right ) => left === right ,
18+ "==" : ( left , right ) => left === right ,
19+ "!=" : ( left , right ) => left !== right ,
20+ "-eq" : ( left , right ) => Number ( left ) === Number ( right ) ,
21+ "-ne" : ( left , right ) => Number ( left ) !== Number ( right ) ,
22+ "-lt" : ( left , right ) => Number ( left ) < Number ( right ) ,
23+ "-le" : ( left , right ) => Number ( left ) <= Number ( right ) ,
24+ "-gt" : ( left , right ) => Number ( left ) > Number ( right ) ,
25+ "-ge" : ( left , right ) => Number ( left ) >= Number ( right ) ,
26+ } ;
27+
28+ class ConditionalParser {
29+ tokens : string [ ] ;
30+ index = 0 ;
31+ workingDirectory : VirtualFolder ;
32+
33+ constructor ( tokens : string [ ] , workingDirectory : VirtualFolder ) {
34+ this . tokens = tokens ;
35+ this . workingDirectory = workingDirectory ;
36+ }
37+
38+ parse ( ) : boolean {
39+ return this . parseOr ( ) ;
40+ }
41+
42+ parseOr ( ) : boolean {
43+ let result = this . parseAnd ( ) ;
44+ while ( this . tokens [ this . index ] === "||" ) {
45+ this . index ++ ;
46+ result = this . parseAnd ( ) || result ;
47+ }
48+ return result ;
49+ }
50+
51+ parseAnd ( ) : boolean {
52+ let result = this . parsePrimary ( ) ;
53+ while ( this . tokens [ this . index ] === "&&" ) {
54+ this . index ++ ;
55+ result = this . parsePrimary ( ) && result ;
56+ }
57+ return result ;
58+ }
59+
60+ parsePrimary ( ) : boolean {
61+ const token = this . tokens [ this . index ++ ] ;
62+
63+ if ( token === "!" ) return ! this . parsePrimary ( ) ;
64+
65+ if ( token === "(" ) {
66+ const result = this . parseOr ( ) ;
67+ this . index ++ ; // Skip ")"
68+ return result ;
69+ }
70+
71+ if ( token in UNARY_OPERATORS ) {
72+ const value = this . tokens [ this . index ++ ] ;
73+ return UNARY_OPERATORS [ token ] ( value , this . workingDirectory ) ;
74+ }
75+
76+ const nextToken = this . tokens [ this . index ] ;
77+ if ( nextToken in BINARY_OPERATORS ) {
78+ this . index ++ ;
79+ const right = this . tokens [ this . index ++ ] ;
80+ return BINARY_OPERATORS [ nextToken ] ( token , right ) ;
81+ }
82+
83+ return token . length > 0 ;
84+ }
85+ }
86+
87+ export const doubleBracket = new Command ( )
88+ . setName ( "[[" )
89+ . setRequireArgs ( true )
90+ . setManual ( {
91+ purpose : "Evaluate a conditional expression" ,
92+ usage : "[[ expression ]]" ,
93+ } )
94+ . setExecute ( function ( this : Command , args , { stderr, workingDirectory } ) {
95+ if ( args . at ( - 1 ) !== "]]" )
96+ return Shell . writeError ( stderr , this . name , "missing `]]'" ) ;
97+
98+ const tokens = args . slice ( 0 , - 1 ) ;
99+ if ( tokens . length === 0 ) return EXIT_CODE . generalError ;
100+
101+ try {
102+ return toExitCode ( new ConditionalParser ( tokens , workingDirectory ) . parse ( ) ) ;
103+ } catch {
104+ return EXIT_CODE . misuseOfBuiltins ;
105+ }
106+ } ) ;
0 commit comments