Skip to content

Commit

Permalink
feat(logic)!: implement our own open/4 predicate
Browse files Browse the repository at this point in the history
  • Loading branch information
bdeneux committed Jun 23, 2023
1 parent 18f8f0d commit ec04a14
Showing 1 changed file with 79 additions and 0 deletions.
79 changes: 79 additions & 0 deletions x/logic/predicate/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package predicate
import (
"context"
"fmt"
"os"
"reflect"
"sort"

Expand Down Expand Up @@ -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))
Expand Down

0 comments on commit ec04a14

Please sign in to comment.