Started working on builtins
This commit is contained in:
parent
cf3a12bf0d
commit
78720eeb5b
|
@ -5,6 +5,7 @@ import (
|
||||||
"github.com/chzyer/readline"
|
"github.com/chzyer/readline"
|
||||||
"log"
|
"log"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
|
"ucl.lmika.dev/ucl/builtins"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -14,7 +15,10 @@ func main() {
|
||||||
}
|
}
|
||||||
defer rl.Close()
|
defer rl.Close()
|
||||||
|
|
||||||
inst := ucl.New()
|
inst := ucl.New(
|
||||||
|
ucl.WithModule(builtins.OS()),
|
||||||
|
ucl.WithModule(builtins.FS(nil)),
|
||||||
|
)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
|
@ -83,7 +83,7 @@ var scanner = lexer.MustStateful(lexer.Rules{
|
||||||
{"RC", `\}`, nil},
|
{"RC", `\}`, nil},
|
||||||
{"NL", `[;\n][; \n\t]*`, nil},
|
{"NL", `[;\n][; \n\t]*`, nil},
|
||||||
{"PIPE", `\|`, nil},
|
{"PIPE", `\|`, nil},
|
||||||
{"Ident", `[-]*[a-zA-Z_][\w-]*`, nil},
|
{"Ident", `[-]*[a-zA-Z_:][\w-:]*`, nil},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
var parser = participle.MustBuild[astScript](participle.Lexer(scanner),
|
var parser = participle.MustBuild[astScript](participle.Lexer(scanner),
|
||||||
|
|
56
ucl/builtins/fs.go
Normal file
56
ucl/builtins/fs.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package builtins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"ucl.lmika.dev/ucl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fsHandlers struct {
|
||||||
|
fs fs.FS
|
||||||
|
}
|
||||||
|
|
||||||
|
func FS(fs fs.FS) ucl.Module {
|
||||||
|
fsh := fsHandlers{fs: fs}
|
||||||
|
|
||||||
|
return ucl.Module{
|
||||||
|
Name: "fs",
|
||||||
|
Builtins: map[string]ucl.BuiltinHandler{
|
||||||
|
"lines": fsh.lines,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fh fsHandlers) openFile(name string) (fs.File, error) {
|
||||||
|
if fh.fs == nil {
|
||||||
|
return os.Open(name)
|
||||||
|
}
|
||||||
|
return fh.fs.Open(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fh fsHandlers) lines(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var fname string
|
||||||
|
if err := args.Bind(&fname); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := fh.openFile(fname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
lines := make([]string, 0)
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
lines = append(lines, scanner.Text())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines, nil
|
||||||
|
}
|
37
ucl/builtins/fs_test.go
Normal file
37
ucl/builtins/fs_test.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package builtins_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
"ucl.lmika.dev/ucl"
|
||||||
|
"ucl.lmika.dev/ucl/builtins"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testFS = fstest.MapFS{
|
||||||
|
"test.txt": &fstest.MapFile{
|
||||||
|
Data: []byte("these\nare\nlines"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFS_Cat(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
descr string
|
||||||
|
eval string
|
||||||
|
want any
|
||||||
|
}{
|
||||||
|
{descr: "read file", eval: `fs:lines "test.txt"`, want: []string{"these", "are", "lines"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.descr, func(t *testing.T) {
|
||||||
|
inst := ucl.New(
|
||||||
|
ucl.WithModule(builtins.FS(testFS)),
|
||||||
|
)
|
||||||
|
res, err := inst.Eval(context.Background(), tt.eval)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
40
ucl/builtins/os.go
Normal file
40
ucl/builtins/os.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package builtins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"ucl.lmika.dev/ucl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type osHandlers struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func OS() ucl.Module {
|
||||||
|
osh := osHandlers{}
|
||||||
|
|
||||||
|
return ucl.Module{
|
||||||
|
Name: "os",
|
||||||
|
Builtins: map[string]ucl.BuiltinHandler{
|
||||||
|
"env": osh.env,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oh osHandlers) env(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var envName string
|
||||||
|
if err := args.Bind(&envName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok := os.LookupEnv(envName)
|
||||||
|
if ok {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var defValue any
|
||||||
|
if err := args.Bind(&defValue); err == nil {
|
||||||
|
return defValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
36
ucl/builtins/os_test.go
Normal file
36
ucl/builtins/os_test.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package builtins_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
"ucl.lmika.dev/ucl"
|
||||||
|
"ucl.lmika.dev/ucl/builtins"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOS_Env(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
descr string
|
||||||
|
eval string
|
||||||
|
want any
|
||||||
|
}{
|
||||||
|
{descr: "env value", eval: `os:env "MY_ENV"`, want: "my env value"},
|
||||||
|
{descr: "missing env value", eval: `os:env "MISSING_THING"`, want: ""},
|
||||||
|
{descr: "default env value (str)", eval: `os:env "MISSING_THING" "my default"`, want: "my default"},
|
||||||
|
{descr: "default env value (int)", eval: `os:env "MISSING_THING" 1352`, want: 1352},
|
||||||
|
{descr: "default env value (nil)", eval: `os:env "MISSING_THING" ()`, want: nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.descr, func(t *testing.T) {
|
||||||
|
t.Setenv("MY_ENV", "my env value")
|
||||||
|
|
||||||
|
inst := ucl.New(
|
||||||
|
ucl.WithModule(builtins.OS()),
|
||||||
|
)
|
||||||
|
res, err := inst.Eval(context.Background(), tt.eval)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,12 @@ func displayResult(ctx context.Context, inst *Inst, res object) (err error) {
|
||||||
if _, err = fmt.Fprintln(inst.out, "(nil)"); err != nil {
|
if _, err = fmt.Fprintln(inst.out, "(nil)"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case listable:
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
if err = displayResult(ctx, inst, v.Index(i)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
if _, err = fmt.Fprintln(inst.out, v.String()); err != nil {
|
if _, err = fmt.Fprintln(inst.out, v.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
13
ucl/inst.go
13
ucl/inst.go
|
@ -29,6 +29,19 @@ func WithMissingBuiltinHandler(handler MissingBuiltinHandler) InstOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithModule(module Module) InstOption {
|
||||||
|
return func(i *Inst) {
|
||||||
|
for name, builtin := range module.Builtins {
|
||||||
|
i.SetBuiltin(module.Name+":"+name, builtin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Module struct {
|
||||||
|
Name string
|
||||||
|
Builtins map[string]BuiltinHandler
|
||||||
|
}
|
||||||
|
|
||||||
func New(opts ...InstOption) *Inst {
|
func New(opts ...InstOption) *Inst {
|
||||||
rootEC := &evalCtx{}
|
rootEC := &evalCtx{}
|
||||||
rootEC.root = rootEC
|
rootEC.root = rootEC
|
||||||
|
|
|
@ -94,6 +94,8 @@ func (u userBuiltin) invoke(ctx context.Context, args invocationArgs) (object, e
|
||||||
|
|
||||||
func bindArg(v interface{}, arg object) error {
|
func bindArg(v interface{}, arg object) error {
|
||||||
switch t := v.(type) {
|
switch t := v.(type) {
|
||||||
|
case *interface{}:
|
||||||
|
*t, _ = toGoValue(arg)
|
||||||
case *string:
|
case *string:
|
||||||
*t = arg.String()
|
*t = arg.String()
|
||||||
case *int:
|
case *int:
|
||||||
|
|
Loading…
Reference in a new issue