test_classic.go

  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}