Skip to content

Commit

Permalink
Merge pull request #249 from ichiban/refine-terms
Browse files Browse the repository at this point in the history
refine Term
  • Loading branch information
ichiban authored Aug 28, 2022
2 parents aa576b8 + 8ba639e commit e59f5c2
Show file tree
Hide file tree
Showing 40 changed files with 2,837 additions and 3,192 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.18

- name: Build
run: go build -v ./...
Expand Down
3 changes: 2 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ We use the same VM opcodes you can find in the original paper:
Also, we've added some extra opcodes:

- `opCut` to perform cut operation
- `opList` to handle lists
- `opPartial` to handle partial lists

### Registers

Expand All @@ -46,6 +48,5 @@ We use the same registers you can find in the original paper:

Also, we've added some extra registers:

- `pi` to store procedure indicators instead of `xr`
- `env` to keep track of variable bindings (environment)
- `cutParent` to keep track of cut parent
4 changes: 0 additions & 4 deletions bootstrap.pl
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,6 @@
:- built_in(false/0).
false :- fail.

:- built_in(append/3).
append([], L, L).
append([X|L1], L2, [X|L3]) :- append(L1, L2, L3).

:- built_in(member/2).
member(X, [X|_]).
member(X, [_|Xs]) :- member(X, Xs).
Expand Down
5 changes: 5 additions & 0 deletions cmd/1pl/interpreter.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"fmt"
"io"

"github.com/ichiban/prolog"
Expand All @@ -12,5 +13,9 @@ func New(r io.Reader, w io.Writer) *prolog.Interpreter {
i := prolog.New(r, w)
i.Register2("call_nth", i.CallNth)
i.Register4("skip_max_list", engine.SkipMaxList)
i.Register2("go_string", func(term, s engine.Term, k func(*engine.Env) *engine.Promise, env *engine.Env) *engine.Promise {
engine.GoStringEnv = env
return engine.Unify(s, engine.Atom(fmt.Sprintf("%#v", term)), k, env)
})
return i
}
5 changes: 5 additions & 0 deletions cmd/1pl/interpreter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,9 @@ func TestNew(t *testing.T) {
assert.NoError(t, p.QuerySolution(`\+call_nth(1, 0).`).Err())
assert.NoError(t, p.QuerySolution(`\+call_nth(V, 0).`).Err())
})

t.Run("go_string", func(t *testing.T) {
p := New(nil, nil)
assert.NoError(t, p.QuerySolution(`go_string("foo", '"foo"').`).Err())
})
}
2 changes: 1 addition & 1 deletion cmd/1pl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ Type Ctrl-C or 'halt.' to exit.
func goal(i *prolog.Interpreter, pi engine.ProcedureIndicator, args []engine.Term, env *engine.Env) string {
goal, _ := pi.Apply(args...)
var buf bytes.Buffer
_ = goal.WriteTerm(&buf, &engine.WriteOptions{Quoted: true}, env)
_ = engine.WriteTerm(&buf, goal, &engine.WriteOptions{Quoted: true}, env)
return buf.String()
}

Expand Down
83 changes: 4 additions & 79 deletions engine/atom.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bufio"
"bytes"
"fmt"
"io"
"regexp"
"strings"
)
Expand All @@ -17,89 +16,15 @@ var (
// Atom is a prolog atom.
type Atom string

// Unify unifies the atom with t.
func (a Atom) Unify(t Term, occursCheck bool, env *Env) (*Env, bool) {
switch t := env.Resolve(t).(type) {
case Atom:
return env, a == t
case Variable:
return t.Unify(a, occursCheck, env)
default:
return env, false
}
}

// Apply returns a Compound which Functor is the Atom and Args are the arguments. If the arguments are empty,
// Apply returns a Compound which Functor is the Atom and args are the arguments. If the arguments are empty,
// then returns itself.
func (a Atom) Apply(args ...Term) Term {
if len(args) == 0 {
return a
}
return &Compound{
Functor: a,
Args: args,
}
}

// WriteTerm writes the Atom to the io.Writer.
func (a Atom) WriteTerm(w io.Writer, opts *WriteOptions, _ *Env) error {
ew := errWriter{w: w}
openClose := (opts.left != (operator{}) || opts.right != (operator{})) && opts.ops.defined(a)

if openClose {
if opts.left.name != "" && opts.left.specifier.class() == operatorClassPrefix {
_, _ = fmt.Fprint(&ew, " ")
}
_, _ = fmt.Fprint(&ew, "(")
opts = opts.withLeft(operator{}).withRight(operator{})
}

if opts.Quoted && needQuoted(a) {
_ = a.writeTermQuoted(&ew, opts)
} else {
_ = a.writeTermUnquoted(&ew, opts)
}

if openClose {
_, _ = fmt.Fprint(&ew, ")")
}

return ew.err
}

func (a Atom) writeTermQuoted(w io.Writer, opts *WriteOptions) error {
ew := errWriter{w: w}
if opts.left != (operator{}) && needQuoted(opts.left.name) { // Avoid 'FOO''BAR'.
_, _ = fmt.Fprint(&ew, " ")
}
_, _ = fmt.Fprint(&ew, quote(string(a)))
if opts.right != (operator{}) && needQuoted(opts.right.name) { // Avoid 'FOO''BAR'.
_, _ = fmt.Fprint(&ew, " ")
}
return ew.err
}

func (a Atom) writeTermUnquoted(w io.Writer, opts *WriteOptions) error {
ew := errWriter{w: w}
if (letterDigit(opts.left.name) && letterDigit(a)) || (graphic(opts.left.name) && graphic(a)) {
_, _ = fmt.Fprint(&ew, " ")
}
_, _ = fmt.Fprint(&ew, string(a))
if (letterDigit(opts.right.name) && letterDigit(a)) || (graphic(opts.right.name) && graphic(a)) {
_, _ = fmt.Fprint(&ew, " ")
}
return ew.err
}

// Compare compares the atom to another term.
func (a Atom) Compare(t Term, env *Env) int64 {
switch t := env.Resolve(t).(type) {
case Variable, Float, Integer:
return 1
case Atom:
return int64(strings.Compare(string(a), string(t)))
default:
return -1
return &compound{
functor: a,
args: args,
}
}

Expand Down
111 changes: 0 additions & 111 deletions engine/atom_test.go
Original file line number Diff line number Diff line change
@@ -1,113 +1,2 @@
package engine

import (
"bytes"
"testing"

"github.com/stretchr/testify/assert"
)

func TestAtom_Unify(t *testing.T) {
unit := Atom("foo")

t.Run("atom", func(t *testing.T) {
env, ok := unit.Unify(Atom("foo"), false, nil)
assert.True(t, ok)
assert.Nil(t, env)

env, ok = unit.Unify(Atom("bar"), false, env)
assert.False(t, ok)
assert.Nil(t, env)
})

t.Run("integer", func(t *testing.T) {
env, ok := unit.Unify(Integer(1), false, nil)
assert.False(t, ok)
assert.Nil(t, env)
})

t.Run("variable", func(t *testing.T) {
t.Run("free", func(t *testing.T) {
v := Variable("X")

env, ok := unit.Unify(v, false, nil)
assert.True(t, ok)
assert.Equal(t, unit, env.Resolve(v))
})
t.Run("bound to the same value", func(t *testing.T) {
v := Variable("X")
env := NewEnv().
Bind(v, unit)
env, ok := unit.Unify(v, false, env)
assert.True(t, ok)
assert.Equal(t, unit, env.Resolve(v))
})
t.Run("bound to a different value", func(t *testing.T) {
v := Variable("X")
env := NewEnv().
Bind(v, Atom("bar"))
_, ok := unit.Unify(v, false, env)
assert.False(t, ok)
})
})

t.Run("compound", func(t *testing.T) {
env, ok := unit.Unify(&Compound{
Functor: "foo",
Args: []Term{Atom("foo")},
}, false, nil)
assert.False(t, ok)
assert.Nil(t, env)
})
}

func TestAtom_WriteTerm(t *testing.T) {
tests := []struct {
atom Atom
opts WriteOptions
output string
}{
{atom: `a`, opts: WriteOptions{Quoted: false}, output: `a`},
{atom: `a`, opts: WriteOptions{Quoted: true}, output: `a`},
{atom: "\a\b\f\n\r\t\v\x00\\'\"`", opts: WriteOptions{Quoted: false}, output: "\a\b\f\n\r\t\v\x00\\'\"`"},
{atom: "\a\b\f\n\r\t\v\x00\\'\"`", opts: WriteOptions{Quoted: true}, output: "'\\a\\b\\f\\n\\r\\t\\v\\x0\\\\\\\\'\"`'"},
{atom: `,`, opts: WriteOptions{Quoted: false}, output: `,`},
{atom: `,`, opts: WriteOptions{Quoted: true}, output: `','`},
{atom: `[]`, opts: WriteOptions{Quoted: false}, output: `[]`},
{atom: `[]`, opts: WriteOptions{Quoted: true}, output: `[]`},
{atom: `{}`, opts: WriteOptions{Quoted: false}, output: `{}`},
{atom: `{}`, opts: WriteOptions{Quoted: true}, output: `{}`},

{atom: `-`, output: `-`},
{atom: `-`, opts: WriteOptions{ops: operators{"+": {}, "-": {}}, left: operator{specifier: operatorSpecifierFY, name: "+"}}, output: ` (-)`},
{atom: `-`, opts: WriteOptions{ops: operators{"+": {}, "-": {}}, right: operator{name: "+"}}, output: `(-)`},

{atom: `X`, opts: WriteOptions{Quoted: true, left: operator{name: `F`}}, output: ` 'X'`}, // So that it won't be 'F''X'.
{atom: `X`, opts: WriteOptions{Quoted: true, right: operator{name: `F`}}, output: `'X' `}, // So that it won't be 'X''F'.

{atom: `foo`, opts: WriteOptions{left: operator{name: `bar`}}, output: ` foo`}, // So that it won't be barfoo.
{atom: `foo`, opts: WriteOptions{right: operator{name: `bar`}}, output: `foo `}, // So that it won't be foobar.
}

var buf bytes.Buffer
for _, tt := range tests {
t.Run(string(tt.atom), func(t *testing.T) {
buf.Reset()
assert.NoError(t, tt.atom.WriteTerm(&buf, &tt.opts, nil))
assert.Equal(t, tt.output, buf.String())
})
}
}

func TestAtom_Compare(t *testing.T) {
var m mockTerm
defer m.AssertExpectations(t)

assert.Equal(t, int64(-1), Atom("a").Compare(&m, nil))
assert.Equal(t, int64(-1), Atom("a").Compare(Atom("b"), nil))
assert.Equal(t, int64(0), Atom("a").Compare(Atom("a"), nil))
assert.Equal(t, int64(1), Atom("b").Compare(Atom("a"), nil))
assert.Equal(t, int64(1), Atom("a").Compare(Variable("X"), nil))
assert.Equal(t, int64(1), Atom("a").Compare(Float(0), nil))
assert.Equal(t, int64(1), Atom("a").Compare(Integer(0), nil))
}
Loading

0 comments on commit e59f5c2

Please sign in to comment.