1// Copyright (c) 2017, Daniel MartΓ <mvdan@mvdan.cc>
2// See LICENSE for licensing information
3
4package interp
5
6import (
7 "fmt"
8
9 "mvdan.cc/sh/v3/syntax"
10)
11
12const illegalTok = 0
13
14type testParser struct {
15 eof bool
16 val string
17 rem []string
18
19 err func(err error)
20}
21
22func (p *testParser) errf(format string, a ...any) {
23 p.err(fmt.Errorf(format, a...))
24}
25
26func (p *testParser) next() {
27 if p.eof || len(p.rem) == 0 {
28 p.eof = true
29 p.val = ""
30 return
31 }
32 p.val = p.rem[0]
33 p.rem = p.rem[1:]
34}
35
36func (p *testParser) followWord(fval string) *syntax.Word {
37 if p.eof {
38 p.errf("%s must be followed by a word", fval)
39 }
40 w := &syntax.Word{Parts: []syntax.WordPart{
41 &syntax.Lit{Value: p.val},
42 }}
43 p.next()
44 return w
45}
46
47func (p *testParser) classicTest(fval string, pastAndOr bool) syntax.TestExpr {
48 var left syntax.TestExpr
49 if pastAndOr {
50 left = p.testExprBase(fval)
51 } else {
52 left = p.classicTest(fval, true)
53 }
54 if left == nil || p.eof || p.val == ")" {
55 return left
56 }
57 opStr := p.val
58 op := testBinaryOp(p.val)
59 if op == illegalTok {
60 p.errf("not a valid test operator: %s", p.val)
61 }
62 b := &syntax.BinaryTest{
63 Op: op,
64 X: left,
65 }
66 p.next()
67 switch b.Op {
68 case syntax.AndTest, syntax.OrTest:
69 if b.Y = p.classicTest(opStr, false); b.Y == nil {
70 p.errf("%s must be followed by an expression", opStr)
71 }
72 default:
73 b.Y = p.followWord(opStr)
74 }
75 return b
76}
77
78func (p *testParser) testExprBase(fval string) syntax.TestExpr {
79 if p.eof || p.val == ")" {
80 return nil
81 }
82 op := testUnaryOp(p.val)
83 switch op {
84 case syntax.TsNot:
85 u := &syntax.UnaryTest{Op: op}
86 p.next()
87 u.X = p.classicTest(op.String(), false)
88 return u
89 case syntax.TsParen:
90 pe := &syntax.ParenTest{}
91 p.next()
92 pe.X = p.classicTest(op.String(), false)
93 if p.val != ")" {
94 p.errf("reached %s without matching ( with )", p.val)
95 }
96 p.next()
97 return pe
98 case illegalTok:
99 return p.followWord(fval)
100 default:
101 u := &syntax.UnaryTest{Op: op}
102 p.next()
103 if p.eof {
104 // make [ -e ] fall back to [ -n -e ], i.e. use
105 // the operator as an argument
106 return &syntax.Word{Parts: []syntax.WordPart{
107 &syntax.Lit{Value: op.String()},
108 }}
109 }
110 u.X = p.followWord(op.String())
111 return u
112 }
113}
114
115// testUnaryOp is an exact copy of syntax's.
116func testUnaryOp(val string) syntax.UnTestOperator {
117 switch val {
118 case "!":
119 return syntax.TsNot
120 case "(":
121 return syntax.TsParen
122 case "-e", "-a":
123 return syntax.TsExists
124 case "-f":
125 return syntax.TsRegFile
126 case "-d":
127 return syntax.TsDirect
128 case "-c":
129 return syntax.TsCharSp
130 case "-b":
131 return syntax.TsBlckSp
132 case "-p":
133 return syntax.TsNmPipe
134 case "-S":
135 return syntax.TsSocket
136 case "-L", "-h":
137 return syntax.TsSmbLink
138 case "-k":
139 return syntax.TsSticky
140 case "-g":
141 return syntax.TsGIDSet
142 case "-u":
143 return syntax.TsUIDSet
144 case "-G":
145 return syntax.TsGrpOwn
146 case "-O":
147 return syntax.TsUsrOwn
148 case "-N":
149 return syntax.TsModif
150 case "-r":
151 return syntax.TsRead
152 case "-w":
153 return syntax.TsWrite
154 case "-x":
155 return syntax.TsExec
156 case "-s":
157 return syntax.TsNoEmpty
158 case "-t":
159 return syntax.TsFdTerm
160 case "-z":
161 return syntax.TsEmpStr
162 case "-n":
163 return syntax.TsNempStr
164 case "-o":
165 return syntax.TsOptSet
166 case "-v":
167 return syntax.TsVarSet
168 case "-R":
169 return syntax.TsRefVar
170 default:
171 return illegalTok
172 }
173}
174
175// testBinaryOp is like syntax's, but with -a and -o, and without =~.
176func testBinaryOp(val string) syntax.BinTestOperator {
177 switch val {
178 case "-a":
179 return syntax.AndTest
180 case "-o":
181 return syntax.OrTest
182 case "==", "=":
183 return syntax.TsMatch
184 case "!=":
185 return syntax.TsNoMatch
186 case "-nt":
187 return syntax.TsNewer
188 case "-ot":
189 return syntax.TsOlder
190 case "-ef":
191 return syntax.TsDevIno
192 case "-eq":
193 return syntax.TsEql
194 case "-ne":
195 return syntax.TsNeq
196 case "-le":
197 return syntax.TsLeq
198 case "-ge":
199 return syntax.TsGeq
200 case "-lt":
201 return syntax.TsLss
202 case "-gt":
203 return syntax.TsGtr
204 default:
205 return illegalTok
206 }
207}