From ec04a14b55a2c1a7c37ba7cc12e8db1cbfbf81ed Mon Sep 17 00:00:00 2001 From: Benjamin DENEUX Date: Tue, 20 Jun 2023 11:55:31 +0200 Subject: [PATCH] feat(logic)!: implement our own open/4 predicate --- x/logic/predicate/file.go | 79 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/x/logic/predicate/file.go b/x/logic/predicate/file.go index 11643e1e..b77d8ebe 100644 --- a/x/logic/predicate/file.go +++ b/x/logic/predicate/file.go @@ -3,6 +3,7 @@ package predicate import ( "context" "fmt" + "os" "reflect" "sort" @@ -57,6 +58,84 @@ func SourceFile(vm *engine.VM, file engine.Term, cont engine.Cont, env *engine.E return engine.Delay(promises...) } +// ioMode describes what operations you can perform on the stream. +type ioMode int + +const ( + // ioModeRead means you can read from the stream. + ioModeRead = ioMode(os.O_RDONLY) + // ioModeWrite means you can write to the stream. + ioModeWrite = ioMode(os.O_CREATE | os.O_WRONLY) + // ioModeAppend means you can append to the stream. + ioModeAppend = ioMode(os.O_APPEND) | ioModeWrite +) + +var ( + atomRead = engine.NewAtom("read") + atomWrite = engine.NewAtom("write") + atomAppend = engine.NewAtom("append") +) + +func (m ioMode) Term() engine.Term { + return [...]engine.Term{ + ioModeRead: atomRead, + ioModeWrite: atomWrite, + ioModeAppend: atomAppend, + }[m] +} + +// Open opens SourceSink in mode and unifies with stream. +func Open(vm *engine.VM, sourceSink, mode, stream, options engine.Term, k engine.Cont, env *engine.Env) *engine.Promise { + var name string + switch s := env.Resolve(sourceSink).(type) { + case engine.Variable: + return engine.Error(fmt.Errorf("open/4: source cannot be a variable")) + case engine.Atom: + name = s.String() + default: + return engine.Error(fmt.Errorf("open/4: invalid domain for source, should be an atom, give %T", s)) + } + + var streamMode ioMode + switch m := env.Resolve(mode).(type) { + case engine.Variable: + return engine.Error(fmt.Errorf("open/4: streamMode cannot be a variable")) + case engine.Atom: + var ok bool + streamMode, ok = map[engine.Atom]ioMode{ + atomRead: ioModeRead, + atomWrite: ioModeWrite, + atomAppend: ioModeAppend, + }[m] + if !ok { + return engine.Error(fmt.Errorf("open/4: invalid open mode (read | write | append)")) + } + default: + return engine.Error(fmt.Errorf("open/4: invalid domain for open mode, should be an atom, give %T", m)) + } + + if _, ok := env.Resolve(stream).(engine.Variable); !ok { + return engine.Error(fmt.Errorf("open/4: stream can only be a variable, give %T", env.Resolve(stream))) + } + + if streamMode != ioModeRead { + return engine.Error(fmt.Errorf("open/4: only read mode is allowed here")) + } + + f, err := vm.FS.Open(name) + if err != nil { + return engine.Error(fmt.Errorf("open/4: failed open stream: %w", err)) + } + s := engine.NewInputTextStream(f) + + iter := engine.ListIterator{List: options, Env: env} + for iter.Next() { + return engine.Error(fmt.Errorf("open/4: options is not allowed here")) + } + + return engine.Unify(vm, stream, s, k, env) +} + func getLoadedSources(vm *engine.VM) map[string]struct{} { loadedField := reflect.ValueOf(vm).Elem().FieldByName("loaded").MapKeys() loaded := make(map[string]struct{}, len(loadedField))