Skip to content

Commit

Permalink
add variable_names write-option
Browse files Browse the repository at this point in the history
  • Loading branch information
ichiban committed Apr 2, 2022
1 parent f609059 commit a5dd91e
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 56 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ $(go env GOPATH)/bin/1pl [<file>...]
- `Options` is a list of write options listed below:
- **`quoted(Bool)`** `Bool` is either `true` or `false`. If `true`, atoms and functors will be quoted as needed.
- **`ignore_ops(Bool)`** `Bool` is either `true` or `false`. If `true`, operators will be written in functional notation.
- **`variable_names(VN_list)`** `VN_list` is a proper list which element is a form of `A = V` where `A` is an atom and `V` is a variable. Each occurrence of `V` will be replaced by the leftmost and unquoted `A`.
- **`numbervars(Bool)`** `Bool` is either `true` or `false`. If `true`, terms `'$VAR'(0)`, `'$VAR'(1)`, ... will be written as `A`, `B`, ...
- **`write_term(Term, Options)`** Equivalent to `current_output(S), write_term(S, Term, Options)`. *ISO*
- **`write(S_or_a, Term)`** Equivalent to `write_term(S_or_a, Term, [numbervars(true)])`. *ISO*
Expand Down
2 changes: 1 addition & 1 deletion engine/atom.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (a Atom) Apply(args ...Term) Term {

// Unparse emits tokens that represent the atom.
func (a Atom) Unparse(emit func(Token), _ *Env, opts ...WriteOption) {
wto := defaultWriteTermOptions
wto := defaultWriteOptions
for _, o := range opts {
o(&wto)
}
Expand Down
86 changes: 59 additions & 27 deletions engine/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -1377,47 +1377,79 @@ func (state *State) Write(w io.Writer, t Term, env *Env, opts ...WriteOption) er
}

func writeTermOption(state *State, option Term, env *Env) (WriteOption, error) {
type optionIndicator struct {
functor, arg Atom
}

var oi optionIndicator
switch option := env.Resolve(option).(type) {
switch o := env.Resolve(option).(type) {
case Variable:
return nil, ErrInstantiation
case *Compound:
if len(option.Args) != 1 {
return nil, domainErrorWriteOption(option)
if len(o.Args) != 1 {
return nil, domainErrorWriteOption(o)
}

switch v := env.Resolve(option.Args[0]).(type) {
if o.Functor == "variable_names" {
vns := variableNames(o.Args[0], env)
if vns == nil {
return nil, domainErrorWriteOption(o)
}
return WithVariableNames(vns), nil
}

var b bool
switch v := env.Resolve(o.Args[0]).(type) {
case Variable:
return nil, ErrInstantiation
case Atom:
oi = optionIndicator{functor: option.Functor, arg: v}
switch v {
case "true":
b = true
case "false":
b = false
default:
return nil, domainErrorWriteOption(o)
}
default:
return nil, domainErrorWriteOption(o)
}

switch o.Functor {
case "quoted":
return WithQuoted(b), nil
case "ignore_ops":
return state.WithIgnoreOps(b), nil
case "numbervars":
return WithNumberVars(b), nil
default:
return nil, domainErrorWriteOption(option)
return nil, domainErrorWriteOption(o)
}
default:
return nil, domainErrorWriteOption(option)
return nil, domainErrorWriteOption(o)
}
}

switch oi {
case optionIndicator{functor: "quoted", arg: "true"}:
return WithQuoted(true), nil
case optionIndicator{functor: "quoted", arg: "false"}:
return WithQuoted(false), nil
case optionIndicator{functor: "ignore_ops", arg: "true"}:
return state.WithIgnoreOps(true), nil
case optionIndicator{functor: "ignore_ops", arg: "false"}:
return state.WithIgnoreOps(false), nil
case optionIndicator{functor: "numbervars", arg: "true"}:
return WithNumberVars(true), nil
case optionIndicator{functor: "numbervars", arg: "false"}:
return WithNumberVars(false), nil
default:
return nil, domainErrorWriteOption(option)
func variableNames(vnList Term, env *Env) map[Variable]Atom {
vns := map[Variable]Atom{}
iter := ListIterator{List: vnList, Env: env}
for iter.Next() {
vn, ok := env.Resolve(iter.Current()).(*Compound)
if !ok || vn.Functor != "=" || len(vn.Args) != 2 {
return nil
}
n, ok := env.Resolve(vn.Args[0]).(Atom)
if !ok {
return nil
}
v, ok := env.Resolve(vn.Args[1]).(Variable)
if !ok {
return nil
}
if _, ok := vns[v]; ok {
continue
}
vns[v] = n
}
if err := iter.Err(); err != nil {
return nil
}
return vns
}

// CharCode converts a single-rune Atom char to an Integer code, or vice versa.
Expand Down
98 changes: 96 additions & 2 deletions engine/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3908,6 +3908,100 @@ func TestState_WriteTerm(t *testing.T) {
})
})

t.Run("variable_names", func(t *testing.T) {
t.Run("ok", func(t *testing.T) {
var m mockTerm
m.On("Unparse", mock.Anything, (*Env)(nil), mock.Anything).Once()
defer m.AssertExpectations(t)

ok, err := state.WriteTerm(s, &m, List(&Compound{
Functor: "variable_names",
Args: []Term{List(
Atom("=").Apply(Atom("foo"), Variable("X")),
Atom("=").Apply(Atom("bar"), Variable("X")),
Atom("=").Apply(Atom("baz"), Variable("Y")),
)},
}), Success, nil).Force(context.Background())
assert.NoError(t, err)
assert.True(t, ok)

assert.Equal(t, ops, m.ops)
assert.Equal(t, map[Variable]Atom{
"X": "foo", // The leftmost is used.
"Y": "baz",
}, m.variableNames)
assert.Equal(t, 1200, m.priority)
})

t.Run("argument is not a proper list", func(t *testing.T) {
var m mockTerm
defer m.AssertExpectations(t)

_, err := state.WriteTerm(s, &m, List(&Compound{
Functor: "variable_names",
Args: []Term{Atom("=").Apply(Atom("foo"), Variable("X"))},
}), Success, nil).Force(context.Background())
assert.Equal(t, domainErrorWriteOption(&Compound{
Functor: "variable_names",
Args: []Term{Atom("=").Apply(Atom("foo"), Variable("X"))},
}), err)
})

t.Run("element is not name=variable", func(t *testing.T) {
var m mockTerm
defer m.AssertExpectations(t)

_, err := state.WriteTerm(s, &m, List(&Compound{
Functor: "variable_names",
Args: []Term{List(
Atom("foo"),
)},
}), Success, nil).Force(context.Background())
assert.Equal(t, domainErrorWriteOption(&Compound{
Functor: "variable_names",
Args: []Term{List(
Atom("foo"),
)},
}), err)
})

t.Run("name is not an atom", func(t *testing.T) {
var m mockTerm
defer m.AssertExpectations(t)

_, err := state.WriteTerm(s, &m, List(&Compound{
Functor: "variable_names",
Args: []Term{List(
Atom("=").Apply(Integer(0), Variable("X")),
)},
}), Success, nil).Force(context.Background())
assert.Equal(t, domainErrorWriteOption(&Compound{
Functor: "variable_names",
Args: []Term{List(
Atom("=").Apply(Integer(0), Variable("X")),
)},
}), err)
})

t.Run("variable is not a variable", func(t *testing.T) {
var m mockTerm
defer m.AssertExpectations(t)

_, err := state.WriteTerm(s, &m, List(&Compound{
Functor: "variable_names",
Args: []Term{List(
Atom("=").Apply(Atom("foo"), Integer(0)),
)},
}), Success, nil).Force(context.Background())
assert.Equal(t, domainErrorWriteOption(&Compound{
Functor: "variable_names",
Args: []Term{List(
Atom("=").Apply(Atom("foo"), Integer(0)),
)},
}), err)
})
})

t.Run("numbervars", func(t *testing.T) {
t.Run("false", func(t *testing.T) {
var m mockTerm
Expand Down Expand Up @@ -4039,7 +4133,7 @@ func TestState_WriteTerm(t *testing.T) {

type mockTerm struct {
mock.Mock
writeTermOptions
writeOptions
}

func (m *mockTerm) String() string {
Expand All @@ -4055,7 +4149,7 @@ func (m *mockTerm) Unify(t Term, occursCheck bool, env *Env) (*Env, bool) {
func (m *mockTerm) Unparse(emit func(Token), env *Env, opts ...WriteOption) {
_ = m.Called(emit, env, opts)
for _, o := range opts {
o(&m.writeTermOptions)
o(&m.writeOptions)
}
}

Expand Down
24 changes: 12 additions & 12 deletions engine/compound.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ func (c *Compound) Unify(t Term, occursCheck bool, env *Env) (*Env, bool) {

// Unparse emits tokens that represent the compound.
func (c *Compound) Unparse(emit func(Token), env *Env, opts ...WriteOption) {
wto := defaultWriteTermOptions
wo := defaultWriteOptions
for _, o := range opts {
o(&wto)
o(&wo)
}

if c.Functor == "." && len(c.Args) == 2 {
Expand All @@ -53,7 +53,7 @@ func (c *Compound) Unparse(emit func(Token), env *Env, opts ...WriteOption) {
return
}

if op := wto.ops.find(c.Functor, len(c.Args)); op != nil {
if op := wo.ops.find(c.Functor, len(c.Args)); op != nil {
[...]func(operator, func(Token), *Env, ...WriteOption){
operatorSpecifierFX: c.unparseFX,
operatorSpecifierFY: c.unparseFY,
Expand All @@ -66,7 +66,7 @@ func (c *Compound) Unparse(emit func(Token), env *Env, opts ...WriteOption) {
return
}

if n, ok := env.Resolve(c.Args[0]).(Integer); ok && wto.numberVars && c.Functor == "$VAR" && len(c.Args) == 1 {
if n, ok := env.Resolve(c.Args[0]).(Integer); ok && wo.numberVars && c.Functor == "$VAR" && len(c.Args) == 1 {
c.unparseNumberVar(n, emit)
return
}
Expand All @@ -75,7 +75,7 @@ func (c *Compound) Unparse(emit func(Token), env *Env, opts ...WriteOption) {
}

func (c *Compound) unparseFX(op operator, emit func(Token), env *Env, opts ...WriteOption) {
wto := defaultWriteTermOptions
wto := defaultWriteOptions
for _, o := range opts {
o(&wto)
}
Expand All @@ -89,7 +89,7 @@ func (c *Compound) unparseFX(op operator, emit func(Token), env *Env, opts ...Wr
}

func (c *Compound) unparseFY(op operator, emit func(Token), env *Env, opts ...WriteOption) {
wto := defaultWriteTermOptions
wto := defaultWriteOptions
for _, o := range opts {
o(&wto)
}
Expand All @@ -103,7 +103,7 @@ func (c *Compound) unparseFY(op operator, emit func(Token), env *Env, opts ...Wr
}

func (c *Compound) unparseXF(op operator, emit func(Token), env *Env, opts ...WriteOption) {
wto := defaultWriteTermOptions
wto := defaultWriteOptions
for _, o := range opts {
o(&wto)
}
Expand All @@ -117,7 +117,7 @@ func (c *Compound) unparseXF(op operator, emit func(Token), env *Env, opts ...Wr
}

func (c *Compound) unparseYF(op operator, emit func(Token), env *Env, opts ...WriteOption) {
wto := defaultWriteTermOptions
wto := defaultWriteOptions
for _, o := range opts {
o(&wto)
}
Expand All @@ -131,7 +131,7 @@ func (c *Compound) unparseYF(op operator, emit func(Token), env *Env, opts ...Wr
}

func (c *Compound) unparseXFX(op operator, emit func(Token), env *Env, opts ...WriteOption) {
wto := defaultWriteTermOptions
wto := defaultWriteOptions
for _, o := range opts {
o(&wto)
}
Expand All @@ -146,7 +146,7 @@ func (c *Compound) unparseXFX(op operator, emit func(Token), env *Env, opts ...W
}

func (c *Compound) unparseXFY(op operator, emit func(Token), env *Env, opts ...WriteOption) {
wto := defaultWriteTermOptions
wto := defaultWriteOptions
for _, o := range opts {
o(&wto)
}
Expand All @@ -161,7 +161,7 @@ func (c *Compound) unparseXFY(op operator, emit func(Token), env *Env, opts ...W
}

func (c *Compound) unparseYFX(op operator, emit func(Token), env *Env, opts ...WriteOption) {
wto := defaultWriteTermOptions
wto := defaultWriteOptions
for _, o := range opts {
o(&wto)
}
Expand All @@ -176,7 +176,7 @@ func (c *Compound) unparseYFX(op operator, emit func(Token), env *Env, opts ...W
}

func (c *Compound) unparseList(emit func(Token), env *Env, opts ...WriteOption) {
wto := defaultWriteTermOptions
wto := defaultWriteOptions
for _, o := range opts {
o(&wto)
}
Expand Down
Loading

0 comments on commit a5dd91e

Please sign in to comment.