diff --git a/README.md b/README.md index 1d4aef3..388e450 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,11 @@ These use strings (lists of characters) for filenames. - `is_list/1` - `atomic_list_concat/3` +### Atoms + +- `downcase_atom/2` +- `upcase_atom/2` + ### Package [`taujson`](https://godoc.org/github.com/guregu/predicates/taujson) These predicates are intended to be compatible with Tau Prolog's [`library(js)`](http://tau-prolog.org/documentation#js). diff --git a/atom.go b/atom.go new file mode 100644 index 0000000..ab1ca80 --- /dev/null +++ b/atom.go @@ -0,0 +1,58 @@ +package predicates + +import ( + "context" + "strings" + + "github.com/ichiban/prolog/engine" +) + +// DowncaseAtom (downcase_atom/2) converts atom into its lowercase equivalent. +// +// downcase_atom(+Atom, -LowerCase). +func DowncaseAtom(atom, lowercase engine.Term, k func(*engine.Env) *engine.Promise, env *engine.Env) *engine.Promise { + var a engine.Atom + switch atom := env.Resolve(atom).(type) { + case engine.Variable: + return engine.Error(engine.ErrInstantiation) + case engine.Atom: + a = atom + default: + return engine.Error(engine.TypeErrorAtom(atom)) + } + + switch low := env.Resolve(lowercase).(type) { + case engine.Atom, engine.Variable: + transformed := engine.Atom(strings.ToLower(string(a))) + return engine.Delay(func(context.Context) *engine.Promise { + return engine.Unify(low, transformed, k, env) + }) + default: + return engine.Error(engine.TypeErrorAtom(low)) + } +} + +// UpcaseAtom (upcase_atom/2) converts atom into its uppercase equivalent. +// +// upcase_atom(+Atom, -UpperCase). +func UpcaseAtom(atom, uppercase engine.Term, k func(*engine.Env) *engine.Promise, env *engine.Env) *engine.Promise { + var a engine.Atom + switch atom := env.Resolve(atom).(type) { + case engine.Variable: + return engine.Error(engine.ErrInstantiation) + case engine.Atom: + a = atom + default: + return engine.Error(engine.TypeErrorAtom(atom)) + } + + switch low := env.Resolve(uppercase).(type) { + case engine.Atom, engine.Variable: + transformed := engine.Atom(strings.ToUpper(string(a))) + return engine.Delay(func(context.Context) *engine.Promise { + return engine.Unify(low, transformed, k, env) + }) + default: + return engine.Error(engine.TypeErrorAtom(low)) + } +} diff --git a/atom_test.go b/atom_test.go new file mode 100644 index 0000000..635486b --- /dev/null +++ b/atom_test.go @@ -0,0 +1,54 @@ +package predicates + +import ( + "testing" + + "github.com/guregu/predicates/internal" + "github.com/ichiban/prolog/engine" +) + +func TestUpcaseAtom(t *testing.T) { + p := internal.NewTestProlog() + p.Register2("upcase_atom", UpcaseAtom) + + t.Run("lower is variable", func(t *testing.T) { + t.Run("atom is lowercase", p.Expect([]map[string]engine.Term{ + {"X": engine.Atom("ABC")}, + }, `upcase_atom('abc', X).`)) + + t.Run("atom is uppercase", p.Expect([]map[string]engine.Term{ + {"X": engine.Atom("ABC")}, + }, `upcase_atom('ABC', X).`)) + }) + + t.Run("lower is ground", func(t *testing.T) { + t.Run("atom is lowercase", p.Expect(internal.TestOK, + `upcase_atom('abc', 'ABC'), OK = true.`)) + + t.Run("atom is uppercase", p.Expect(internal.TestOK, + `upcase_atom('ABC', 'ABC'), OK = true.`)) + }) +} + +func TestDowncaseAtom(t *testing.T) { + p := internal.NewTestProlog() + p.Register2("downcase_atom", DowncaseAtom) + + t.Run("lower is variable", func(t *testing.T) { + t.Run("atom is lowercase", p.Expect([]map[string]engine.Term{ + {"X": engine.Atom("abc")}, + }, `downcase_atom('abc', X).`)) + + t.Run("atom is uppercase", p.Expect([]map[string]engine.Term{ + {"X": engine.Atom("abc")}, + }, `downcase_atom('ABC', X).`)) + }) + + t.Run("lower is ground", func(t *testing.T) { + t.Run("atom is lowercase", p.Expect(internal.TestOK, + `downcase_atom('abc', 'abc'), OK = true.`)) + + t.Run("atom is uppercase", p.Expect(internal.TestOK, + `downcase_atom('ABC', 'abc'), OK = true.`)) + }) +}