working on expand

This commit is contained in:
sawka
2022-11-19 14:05:38 -08:00
parent d469fed490
commit 39ac41c448
3 changed files with 250 additions and 1 deletions
+205
View File
@@ -0,0 +1,205 @@
package shparse
import (
"bytes"
"fmt"
"mvdan.cc/sh/v3/expand"
)
const MaxExpandLen = 64 * 1024
type ExpandInfo struct {
HasTilde bool // only ~ as the first character when SimpleExpandContext.HomeDir is set
HasVar bool // $x, $$, ${...}
HasGlob bool // *, ?, [, {
HasExtGlob bool // ?(...) ... ?*+@!
HasHistory bool // ! (anywhere)
HasSpecial bool // subshell, arith
}
type ExpandContext struct {
HomeDir string
}
func expandSQ(buf *bytes.Buffer, rawLit []rune) {
// no info specials
buf.WriteString(string(rawLit))
}
// TODO implement our own ANSI single quote formatter
func expandANSISQ(buf *bytes.Buffer, rawLit []rune) {
// no info specials
str, _, _ := expand.Format(nil, string(rawLit), nil)
buf.WriteString(str)
}
func expandLiteral(buf *bytes.Buffer, info *ExpandInfo, rawLit []rune) {
var lastBackSlash bool
var lastExtGlob bool
var lastDollar bool
for _, ch := range rawLit {
if ch == 0 {
break
}
if lastBackSlash {
lastBackSlash = false
if ch == '\n' {
// special case, backslash *and* newline are ignored
continue
}
buf.WriteRune(ch)
continue
}
if ch == '\\' {
lastBackSlash = true
lastExtGlob = false
lastDollar = false
continue
}
if ch == '*' || ch == '?' || ch == '[' || ch == '{' {
info.HasGlob = true
}
if ch == '`' {
info.HasSpecial = true
}
if ch == '!' {
info.HasHistory = true
}
if lastExtGlob && ch == '(' {
info.HasExtGlob = true
}
if lastDollar && (ch != ' ' && ch != '"' && ch != '\'' && ch != '(' || ch != '[') {
info.HasVar = true
}
if lastDollar && (ch == '(' || ch == '[') {
info.HasSpecial = true
}
lastExtGlob = (ch == '?' || ch == '*' || ch == '+' || ch == '@' || ch == '!')
lastDollar = (ch == '$')
buf.WriteRune(ch)
}
if lastBackSlash {
buf.WriteByte('\\')
}
}
// will also work for partial double quoted strings
func expandDQLiteral(buf *bytes.Buffer, info *ExpandInfo, rawVal []rune) {
var lastBackSlash bool
var lastDollar bool
for _, ch := range rawVal {
if ch == 0 {
break
}
if lastBackSlash {
lastBackSlash = false
if ch == '"' || ch == '\\' || ch == '$' || ch == '`' {
buf.WriteRune(ch)
continue
}
buf.WriteRune('\\')
buf.WriteRune(ch)
continue
}
if ch == '\\' {
lastBackSlash = true
lastDollar = false
continue
}
// similar to expandLiteral, but no globbing
if ch == '`' {
info.HasSpecial = true
}
if ch == '!' {
info.HasHistory = true
}
if lastDollar && (ch != ' ' && ch != '"' && ch != '\'' && ch != '(' || ch != '[') {
info.HasVar = true
}
if lastDollar && (ch == '(' || ch == '[') {
info.HasSpecial = true
}
lastDollar = (ch == '$')
buf.WriteRune(ch)
}
// in a valid parsed DQ string, you cannot have a trailing backslash (because \" would not end the string)
// still putting the case here though in case we ever deal with incomplete strings (e.g. completion)
if lastBackSlash {
buf.WriteByte('\\')
}
}
func simpleExpandSubs(buf *bytes.Buffer, info *ExpandInfo, ectx ExpandContext, word *WordType, pos int) {
fmt.Printf("expand subs: %v\n", word)
parts := word.Subs
startPos := word.contentStartPos()
for _, part := range parts {
remainingLen := pos - startPos
if remainingLen <= 0 {
break
}
simpleExpandWord(buf, info, ectx, part, remainingLen)
startPos += len(part.Raw)
}
}
func simpleExpandWord(buf *bytes.Buffer, info *ExpandInfo, ectx ExpandContext, word *WordType, pos int) {
if pos >= word.contentEndPos() {
pos = len(word.Raw)
}
if pos <= word.contentStartPos() {
return
}
switch word.Type {
case WordTypeLit:
if word.QC.cur() == WordTypeDQ {
expandDQLiteral(buf, info, word.Raw[:pos])
return
}
expandLiteral(buf, info, word.Raw[:pos])
case WordTypeSQ:
expandSQ(buf, word.Raw[word.contentStartPos():pos])
return
case WordTypeDSQ:
expandANSISQ(buf, word.Raw[word.contentStartPos():pos])
return
case WordTypeDQ, WordTypeDDQ:
simpleExpandSubs(buf, info, ectx, word, pos)
return
case WordTypeGroup:
simpleExpandSubs(buf, info, ectx, word, pos)
return
case WordTypeSimpleVar:
return
case WordTypeVarBrace:
return
default:
if pos > len(word.Raw) {
pos = len(word.Raw)
}
info.HasSpecial = true
buf.WriteString(string(word.Raw[:pos]))
return
}
}
func SimpleExpandPrefix(ectx ExpandContext, word *WordType, pos int) (string, ExpandInfo) {
var buf bytes.Buffer
var info ExpandInfo
simpleExpandWord(&buf, &info, ectx, word, pos)
return buf.String(), info
}
func SimpleExpand(ectx ExpandContext, word *WordType) (string, ExpandInfo) {
return SimpleExpandPrefix(ectx, word, len(word.Raw))
}
+14 -1
View File
@@ -188,9 +188,22 @@ func (w *WordType) isBlank() bool {
return w.Type == WordTypeLit && len(w.Raw) == 0
}
func (w *WordType) contentEndPos() int {
if !w.Complete {
return len(w.Raw)
}
wmeta := wordMetaMap[w.Type]
return len(w.Raw) - wmeta.SuffixLen
}
func (w *WordType) contentStartPos() int {
wmeta := wordMetaMap[w.Type]
return wmeta.PrefixLen
}
func (w *WordType) uncompletable() bool {
switch w.Type {
case WordTypeRaw, WordTypeOp, WordTypeKey, WordTypeDPP, WordTypePP, WordTypeDB:
case WordTypeRaw, WordTypeOp, WordTypeKey, WordTypeDPP, WordTypePP, WordTypeDB, WordTypeBQ, WordTypeDP:
return true
default:
+31
View File
@@ -157,3 +157,34 @@ func TestCompPos(t *testing.T) {
testCompPos(t, `ls abc"$(ls $"echo $(ls ./[*]x) foo)" `, true, 1, true, false, false)
}
func testExpand(t *testing.T, str string, pos int, expStr string, expInfo *ExpandInfo) {
ectx := ExpandContext{HomeDir: "/Users/mike"}
words := Tokenize(str)
if len(words) == 0 {
t.Errorf("could not tokenize any words from %q", str)
return
}
word := words[0]
output, info := SimpleExpandPrefix(ectx, word, pos)
if output != expStr {
t.Errorf("error expanding %q, output:%q exp:%q", str, output, expStr)
} else {
fmt.Printf("expand: %q (%d) => %q\n", str, pos, output)
}
if expInfo != nil {
if info != *expInfo {
t.Errorf("error expanding %q, info:%v exp:%v", str, info, expInfo)
}
}
}
func TestExpand(t *testing.T) {
testExpand(t, "hello", 3, "hel", nil)
testExpand(t, "he\\$xabc", 6, "he$xa", nil)
testExpand(t, "he${x}abc", 6, "he$xa", nil)
testExpand(t, "'hello\"mike'", 8, "hello\"m", nil)
testExpand(t, `$'abc\x01def`, 10, "abc\x01d", nil)
testExpand(t, `$((2 + 2))`, 6, "$((2 +", &ExpandInfo{HasSpecial: true})
testExpand(t, `abc"def"`, 6, "abcde", nil)
}