Added single and double quoted string
All checks were successful
Build / build (push) Successful in 1m57s
All checks were successful
Build / build (push) Successful in 1m57s
Also started working on string interpolation
This commit is contained in:
parent
a30c012bcd
commit
8a416f2bb9
38
ucl/ast.go
38
ucl/ast.go
|
@ -8,9 +8,30 @@ import (
|
||||||
"github.com/alecthomas/participle/v2/lexer"
|
"github.com/alecthomas/participle/v2/lexer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type astStringStringSpan struct {
|
||||||
|
Pos lexer.Position
|
||||||
|
Chars *string `parser:"@SingleChar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type astDoubleStringSpan struct {
|
||||||
|
Pos lexer.Position
|
||||||
|
Chars *string `parser:"@Char"`
|
||||||
|
Escaped *string `parser:"| @Escaped"`
|
||||||
|
IdentRef *string `parser:"| @IdentRef"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type astDoubleString struct {
|
||||||
|
Spans []astDoubleStringSpan `parser:"StringStart @@* StringEnd"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type astSingleString struct {
|
||||||
|
Spans []astStringStringSpan `parser:"SingleStringStart @@* SingleStringEnd"`
|
||||||
|
}
|
||||||
|
|
||||||
type astLiteral struct {
|
type astLiteral struct {
|
||||||
Str *string `parser:"@String"`
|
StrInter *astDoubleString `parser:"@@"`
|
||||||
Int *int `parser:"| @Int"`
|
SingleStrInter *astSingleString `parser:"| @@"`
|
||||||
|
Int *int `parser:"| @Int"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type astIdentNames struct {
|
type astIdentNames struct {
|
||||||
|
@ -91,7 +112,8 @@ var scanner = lexer.MustStateful(lexer.Rules{
|
||||||
"Root": {
|
"Root": {
|
||||||
{"Whitespace", `[ \t]+`, nil},
|
{"Whitespace", `[ \t]+`, nil},
|
||||||
{"Comment", `[#].*`, nil},
|
{"Comment", `[#].*`, nil},
|
||||||
{"String", `"(\\"|[^"])*"`, nil},
|
{"StringStart", `"`, lexer.Push("String")},
|
||||||
|
{"SingleStringStart", `'`, lexer.Push("SingleString")},
|
||||||
{"Int", `[-]?[0-9][0-9]*`, nil},
|
{"Int", `[-]?[0-9][0-9]*`, nil},
|
||||||
{"DOLLAR", `\$`, nil},
|
{"DOLLAR", `\$`, nil},
|
||||||
{"COLON", `\:`, nil},
|
{"COLON", `\:`, nil},
|
||||||
|
@ -106,6 +128,16 @@ var scanner = lexer.MustStateful(lexer.Rules{
|
||||||
{"PIPE", `\|`, nil},
|
{"PIPE", `\|`, nil},
|
||||||
{"Ident", `[-]*[a-zA-Z_][\w-]*`, nil},
|
{"Ident", `[-]*[a-zA-Z_][\w-]*`, nil},
|
||||||
},
|
},
|
||||||
|
"String": {
|
||||||
|
{"Escaped", `\\.`, nil},
|
||||||
|
{"StringEnd", `"`, lexer.Pop()},
|
||||||
|
{"IdentRef", `\$[-]*[a-zA-Z_][\w-]*`, nil},
|
||||||
|
{"Char", `[^$"\\]+`, nil},
|
||||||
|
},
|
||||||
|
"SingleString": {
|
||||||
|
{"SingleStringEnd", `'`, lexer.Pop()},
|
||||||
|
{"SingleChar", `[^']+`, nil},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
var parser = participle.MustBuild[astScript](participle.Lexer(scanner),
|
var parser = participle.MustBuild[astScript](participle.Lexer(scanner),
|
||||||
participle.Elide("Whitespace", "Comment"))
|
participle.Elide("Whitespace", "Comment"))
|
||||||
|
|
54
ucl/eval.go
54
ucl/eval.go
|
@ -3,7 +3,7 @@ package ucl
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type evaluator struct {
|
type evaluator struct {
|
||||||
|
@ -245,18 +245,64 @@ func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astList
|
||||||
|
|
||||||
func (e evaluator) evalLiteral(ctx context.Context, ec *evalCtx, n *astLiteral) (Object, error) {
|
func (e evaluator) evalLiteral(ctx context.Context, ec *evalCtx, n *astLiteral) (Object, error) {
|
||||||
switch {
|
switch {
|
||||||
case n.Str != nil:
|
case n.StrInter != nil:
|
||||||
uq, err := strconv.Unquote(*n.Str)
|
sval, err := e.interpolateDoubleQuotedString(ctx, ec, n.StrInter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return StringObject(uq), nil
|
return sval, nil
|
||||||
|
case n.SingleStrInter != nil:
|
||||||
|
sval, err := e.interpolateSingleQuotedString(ctx, ec, n.SingleStrInter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sval, nil
|
||||||
case n.Int != nil:
|
case n.Int != nil:
|
||||||
return intObject(*n.Int), nil
|
return intObject(*n.Int), nil
|
||||||
}
|
}
|
||||||
return nil, errors.New("unhandled literal type")
|
return nil, errors.New("unhandled literal type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e evaluator) interpolateSingleQuotedString(ctx context.Context, ec *evalCtx, s *astSingleString) (Object, error) {
|
||||||
|
var sb strings.Builder
|
||||||
|
for _, n := range s.Spans {
|
||||||
|
switch {
|
||||||
|
case n.Chars != nil:
|
||||||
|
sb.WriteString(*n.Chars)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StringObject(sb.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e evaluator) interpolateDoubleQuotedString(ctx context.Context, ec *evalCtx, s *astDoubleString) (Object, error) {
|
||||||
|
var sb strings.Builder
|
||||||
|
for _, n := range s.Spans {
|
||||||
|
switch {
|
||||||
|
case n.Chars != nil:
|
||||||
|
sb.WriteString(*n.Chars)
|
||||||
|
case n.Escaped != nil:
|
||||||
|
switch (*n.Escaped)[1:] {
|
||||||
|
case "\\":
|
||||||
|
sb.WriteByte('\\')
|
||||||
|
case "n":
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
case "t":
|
||||||
|
sb.WriteByte('\t')
|
||||||
|
case "$":
|
||||||
|
sb.WriteByte('$')
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unrecognised escaped pattern: \\" + *n.Escaped)
|
||||||
|
}
|
||||||
|
case n.IdentRef != nil:
|
||||||
|
identVal := (*n.IdentRef)[1:]
|
||||||
|
if v, ok := ec.getVar(identVal); ok && v != nil {
|
||||||
|
sb.WriteString(v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StringObject(sb.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e evaluator) evalSub(ctx context.Context, ec *evalCtx, n *astPipeline) (Object, error) {
|
func (e evaluator) evalSub(ctx context.Context, ec *evalCtx, n *astPipeline) (Object, error) {
|
||||||
pipelineRes, err := e.evalPipeline(ctx, ec, n)
|
pipelineRes, err := e.evalPipeline(ctx, ec, n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -111,6 +111,18 @@ func TestBuiltins_Echo(t *testing.T) {
|
||||||
echo "world" # command after this
|
echo "world" # command after this
|
||||||
;
|
;
|
||||||
`, want: "Hello\nworld\n"},
|
`, want: "Hello\nworld\n"},
|
||||||
|
{desc: "interpolated string 1", expr: `set what "world" ; echo "Hello, $what"`, want: "Hello, world\n"},
|
||||||
|
{desc: "interpolated string 2", expr: `set what "world" ; echo "Hello, \$what"`, want: "Hello, $what\n"},
|
||||||
|
{desc: "interpolated string 3", expr: `echo "separate\nlines\n\tand tabs"`, want: "separate\nlines\n\tand tabs\n"},
|
||||||
|
{desc: "interpolated string 4", expr: `set what "Hello" ; set where "world" ; echo "$what, $where"`, want: "Hello, world\n"},
|
||||||
|
{desc: "interpolated string 5", expr: `
|
||||||
|
foreach [123 "foo" true ()] { |x|
|
||||||
|
echo "[[$x]]"
|
||||||
|
}
|
||||||
|
`, want: "[[123]]\n[[foo]]\n[[true]]\n[[]]\n"},
|
||||||
|
{desc: "single quote string 1", expr: `echo 'Hello, world'`, want: "Hello, world\n"},
|
||||||
|
{desc: "single quote string 2", expr: `echo 'No $vars here'`, want: "No $vars here\n"},
|
||||||
|
{desc: "single quote string 3", expr: `echo 'No \escape \nhere'`, want: "No \\escape \\nhere\n"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
Loading…
Reference in a new issue