forked from mndrix/golog
-
Notifications
You must be signed in to change notification settings - Fork 0
/
database.go
145 lines (121 loc) · 3.65 KB
/
database.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package golog
import . "fmt"
import . "github.com/mndrix/golog/term"
import . "github.com/mndrix/golog/util"
import (
"bytes"
"github.com/mndrix/ps"
)
// Database is an immutable Prolog database. All write operations on the
// database produce a new database without affecting the previous one.
// A database is a mapping from predicate indicators (foo/3) to clauses.
// The database may or may not implement indexing. It's unusual to
// interact with databases directly. One usually calls methods on Machine
// instead.
type Database interface {
// Asserta adds a term to the database at the start of any existing
// terms with the same name and arity.
Asserta(Term) Database
// Assertz adds a term to the database at the end of any existing
// terms with the same name and arity.
Assertz(Term) Database
// Candidates() returns a list of clauses that might unify with a term.
// Returns error if no predicate with appropriate
// name and arity has been defined.
Candidates(Term) ([]Term, error)
// Candidates_() is like Candidates() but panics on error.
Candidates_(Term) []Term
// ClauseCount returns the number of clauses in the database
ClauseCount() int
// String returns a string representation of the entire database
String() string
}
// NewDatabase returns a new, empty database
func NewDatabase() Database {
var db mapDb
db.clauseCount = 0
db.predicates = ps.NewMap()
return &db
}
type mapDb struct {
clauseCount int // number of clauses in the database
predicates ps.Map // term indicator => *clauses
}
func (self *mapDb) Asserta(term Term) Database {
return self.assert('a', term)
}
func (self *mapDb) Assertz(term Term) Database {
return self.assert('z', term)
}
func (self *mapDb) assert(side rune, term Term) Database {
var newMapDb mapDb
var cs *clauses
// find the indicator under which this term is classified
indicator := term.Indicator()
if IsClause(term) {
// ':-' uses the indicator of its head term
indicator = Head(term).Indicator()
}
oldClauses, ok := self.predicates.Lookup(indicator)
if ok { // clauses exist for this predicate
switch side {
case 'a':
cs = oldClauses.(*clauses).cons(term)
case 'z':
cs = oldClauses.(*clauses).snoc(term)
}
} else { // brand new predicate
cs = newClauses().snoc(term)
}
newMapDb.clauseCount = self.clauseCount + 1
newMapDb.predicates = self.predicates.Set(indicator, cs)
return &newMapDb
}
func (self *mapDb) Candidates_(t Term) []Term {
ts, err := self.Candidates(t)
if err != nil {
panic(err)
}
return ts
}
func (self *mapDb) Candidates(t Term) ([]Term, error) {
indicator := t.Indicator()
cs, ok := self.predicates.Lookup(indicator)
if !ok { // this predicate hasn't been defined
return nil, Errorf("Undefined predicate: %s", indicator)
}
// quick return for an atom term
if !IsCompound(t) {
return cs.(*clauses).all(), nil
}
// ignore clauses that can't possibly unify with our term
candidates := make([]Term, 0)
cs.(*clauses).forEach(func(clause Term) {
if !IsCompound(clause) {
Debugf(" ... discarding. Not compound term\n")
return
}
head := clause
if IsClause(clause) {
head = Head(clause)
}
if t.(*Compound).MightUnify(head.(*Compound)) {
Debugf(" ... adding to candidates: %s\n", clause)
candidates = append(candidates, clause)
}
})
Debugf(" final candidates = %s\n", candidates)
return candidates, nil
}
func (self *mapDb) ClauseCount() int {
return self.clauseCount
}
func (self *mapDb) String() string {
var buf bytes.Buffer
keys := self.predicates.Keys()
for _, key := range keys {
cs, _ := self.predicates.Lookup(key)
cs.(*clauses).forEach(func(t Term) { Fprintf(&buf, "%s.\n", t) })
}
return buf.String()
}