Skip to content

Commit d918bd4

Browse files
committed
syntax: allow bracket globs in array elements
1 parent 88fac5c commit d918bd4

3 files changed

Lines changed: 63 additions & 6 deletions

File tree

syntax/filetests_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4914,6 +4914,20 @@ var fileTests = []fileTestCase{
49144914
}},
49154915
}}}, LangBash),
49164916
),
4917+
fileTest(
4918+
[]string{`a=([i])`},
4919+
langFile(&CallExpr{Assigns: []*Assign{{
4920+
Name: lit("a"),
4921+
Array: arrValues(word(lit("[i]"))),
4922+
}}}, LangBash|LangZsh),
4923+
),
4924+
fileTest(
4925+
[]string{`a=("foo"[0-9])`},
4926+
langFile(&CallExpr{Assigns: []*Assign{{
4927+
Name: lit("a"),
4928+
Array: arrValues(word(dblQuoted(lit("foo")), lit("[0-9]"))),
4929+
}}}, LangBash|LangZsh),
4930+
),
49174931
fileTest(
49184932
[]string{"a]b"},
49194933
langFile(litStmt("a]b")),

syntax/lexer.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ skipSpace:
325325
}
326326
p.next()
327327
case '[':
328-
if p.quote == arrayElems {
328+
if p.quote == arrayElems && p.arrayIndexAssign() {
329329
p.rune()
330330
p.tok = leftBrack
331331
} else {
@@ -1066,6 +1066,54 @@ func (p *Parser) zshNumRange() bool {
10661066
return len(rest) > 0 && rest[0] == '>'
10671067
}
10681068

1069+
// arrayIndexAssign peeks ahead after a '[' in an array literal and reports
1070+
// whether it begins an indexed assignment like [x]=y rather than a word that
1071+
// happens to start with a bracket glob, like [0-9] or [[:space:]].
1072+
func (p *Parser) arrayIndexAssign() bool {
1073+
rest := p.bs[p.bsp:]
1074+
depth := 1
1075+
var quote byte
1076+
escaped := false
1077+
for i, b := range rest {
1078+
if quote != 0 {
1079+
if escaped {
1080+
escaped = false
1081+
continue
1082+
}
1083+
if quote == '"' && b == '\\' {
1084+
escaped = true
1085+
continue
1086+
}
1087+
if b == quote {
1088+
quote = 0
1089+
}
1090+
continue
1091+
}
1092+
if escaped {
1093+
escaped = false
1094+
continue
1095+
}
1096+
switch b {
1097+
case '\\':
1098+
escaped = true
1099+
case '\'', '"':
1100+
quote = b
1101+
case '[':
1102+
depth++
1103+
case ']':
1104+
depth--
1105+
if depth == 0 {
1106+
return i+1 < len(rest) && rest[i+1] == '='
1107+
}
1108+
}
1109+
}
1110+
// If we can't find a matching closing bracket in the buffered input, keep the
1111+
// existing indexed-assignment tokenization and let the parser report any
1112+
// syntax errors. This keeps the lookahead conservative and avoids changing
1113+
// unrelated incomplete forms.
1114+
return true
1115+
}
1116+
10691117
func (p *Parser) advanceLitNone(r rune) {
10701118
p.eqlOffs = -1
10711119
tok := _LitWord

syntax/parser_test.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,11 +1693,6 @@ var errorCases = []errorCase{
16931693
"a=([i)",
16941694
langErr("1:4: reached `)` without matching `[` with `]`", LangBash|LangZsh),
16951695
),
1696-
errCase(
1697-
"a=([i])",
1698-
langErr("1:4: `[x]` must be followed by `=`", LangBash|LangZsh),
1699-
flipConfirmAll, // TODO: why is this valid?
1700-
),
17011696
errCase(
17021697
"a[i]=(y)",
17031698
langErr("1:5: arrays cannot be nested", LangBash),

0 commit comments

Comments
 (0)