diff --git a/README.md b/README.md index a4645b9..1d4aef3 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ These use strings (lists of characters) for filenames. ### Lists - `is_list/1` +- `atomic_list_concat/3` ### Package [`taujson`](https://godoc.org/github.com/guregu/predicates/taujson) diff --git a/list.go b/list.go index 8e2f36d..bc0eb86 100644 --- a/list.go +++ b/list.go @@ -1,7 +1,8 @@ package predicates import ( - "log" + "context" + "strings" "github.com/ichiban/prolog/engine" ) @@ -23,7 +24,6 @@ func IsList(t engine.Term, k func(*engine.Env) *engine.Promise, env *engine.Env) for iter.Next() { } if iter.Err() != nil { - log.Println("ITER ERR", iter.Err()) return engine.Bool(false) } return k(env) @@ -31,3 +31,61 @@ func IsList(t engine.Term, k func(*engine.Env) *engine.Promise, env *engine.Env) return engine.Bool(false) } } + +// AtomicListConcat (atomic_list_concat/3) succeeds if atom represents the members of list joined by seperator. +// This can be used to join strings by passing a ground list, or used to split strings by passing a ground atom. +// +// atomic_list_concat(+List, +Seperator, -Atom). +// atomic_list_concat(-List, +Seperator, +Atom). +func AtomicListConcat(list, seperator, atom engine.Term, k func(*engine.Env) *engine.Promise, env *engine.Env) *engine.Promise { + sep, ok := seperator.(engine.Atom) + if !ok { + return engine.Error(engine.TypeErrorAtom(seperator)) + } + + switch list := env.Resolve(list).(type) { + case engine.Variable: + str, ok := env.Resolve(atom).(engine.Atom) + if !ok { + return engine.Error(engine.ErrInstantiation) + } + split := strings.Split(string(str), string(sep)) + atoms := make([]engine.Term, len(split)) + for i := 0; i < len(split); i++ { + atoms[i] = engine.Atom(split[i]) + } + return engine.Delay(func(context.Context) *engine.Promise { + return engine.Unify(list, engine.List(atoms...), k, env) + }) + case *engine.Compound: + if list.Functor != "." || len(list.Args) != 2 { + return engine.Error(engine.TypeErrorList(list)) + } + var sb strings.Builder + iter := engine.ListIterator{List: list, Env: env} + for i := 0; iter.Next(); i++ { + cur := env.Resolve(iter.Current()) + a, ok := cur.(engine.Atom) + if !ok { + return engine.Error(engine.TypeErrorAtom(a)) + } + if i > 0 { + sb.WriteString(string(sep)) + } + sb.WriteString(string(a)) + } + str := sb.String() + return engine.Delay(func(context.Context) *engine.Promise { + return engine.Unify(atom, engine.Atom(str), k, env) + }) + case engine.Atom: + if list != "[]" { + return engine.Error(engine.TypeErrorList(list)) + } + return engine.Delay(func(context.Context) *engine.Promise { + return engine.Unify(atom, engine.Atom(""), k, env) + }) + default: + return engine.Error(engine.TypeErrorList(list)) + } +} diff --git a/list_test.go b/list_test.go index 22234fb..64a84fc 100644 --- a/list_test.go +++ b/list_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/guregu/predicates/internal" + "github.com/ichiban/prolog/engine" ) func TestIsList(t *testing.T) { @@ -32,3 +33,47 @@ func TestIsList(t *testing.T) { t.Run("term is variable", p.Expect(internal.TestFail, `is_list(X), OK = true.`)) } + +func TestAtomicListConcat(t *testing.T) { + p := internal.NewTestProlog() + p.Register3("atomic_list_concat", AtomicListConcat) + + t.Run("list is ground", func(t *testing.T) { + t.Run("atom is variable", p.Expect([]map[string]engine.Term{ + {"X": engine.Atom("a-b")}, + }, `atomic_list_concat([a, b], '-', X).`)) + + t.Run("atom is variable and seperator is empty", p.Expect([]map[string]engine.Term{ + {"X": engine.Atom("ab")}, + }, `atomic_list_concat([a, b], '', X).`)) + + t.Run("atom is ground", p.Expect(internal.TestOK, + `atomic_list_concat([a, b], '/', 'a/b'), OK = true.`)) + + t.Run("list is empty and atom is variable", p.Expect([]map[string]engine.Term{ + {"X": engine.Atom("")}, + }, `atomic_list_concat([], '-', X).`)) + + t.Run("list is empty and atom is ground", p.Expect(internal.TestOK, + `atomic_list_concat([], '/', ''), OK = true.`)) + }) + + t.Run("atom is ground", func(t *testing.T) { + t.Run("list is variable", p.Expect([]map[string]engine.Term{ + {"X": engine.List(engine.Atom("a"), engine.Atom("b"), engine.Atom("c"))}, + }, `atomic_list_concat(X, '-', 'a-b-c').`)) + + t.Run("list is variable and seperator is empty", p.Expect([]map[string]engine.Term{ + {"X": engine.List(engine.Atom("a"), engine.Atom("b"), engine.Atom("c"))}, + }, `atomic_list_concat(X, '', 'abc').`)) + + t.Run("atom is empty", p.Expect([]map[string]engine.Term{ + {"X": engine.Atom("")}, + }, `atomic_list_concat([], '-', X).`)) + + // seems like Tau and SWI both bind [''] instead of [] to the list here + t.Run("atom is empty and list is var", p.Expect([]map[string]engine.Term{ + {"X": engine.List(engine.Atom(""))}, + }, `atomic_list_concat(X, '/', '').`)) + }) +}