-
Notifications
You must be signed in to change notification settings - Fork 4
/
interface.go
171 lines (152 loc) · 3.93 KB
/
interface.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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package gotalog
import (
"fmt"
"io"
"strings"
)
// Database holds and generates state for asserted facts and rules.
// We're mirroring the original implementation's use of 'database'. Unfortunately,
// this was used to describe a number of different uses for tables mapping From
// some string id to some type in the original implementation.
type Database interface {
newPredicate(n string, a int) *predicate
assert(c *clause) error
retract(c *clause) error
}
// Term contains either a variable or a constant.
type Term struct {
isConstant bool
// If term is a constant, value is the constant value.
// If term is not a constant (ie, is a variable), value contains
// the variable's id.
value string
}
// LiteralDefinition defines a literal PredicateName(Term0, Term1, ...).
type LiteralDefinition struct {
PredicateName string
Terms []Term
}
// CommandType differentiates different possible datalog commands.
type CommandType int
const (
// Assert - this fact will be added to a database upon application.
Assert CommandType = iota
// Query - this command will return the results of querying a database
// upon application.
Query
// Retract - remove a fact from a database.
Retract
)
// DatalogCommand a command to mutate or query a gotalog database.
type DatalogCommand struct {
Head LiteralDefinition
Body []LiteralDefinition
CommandType CommandType
}
// Parse consumes a reader, producing a slice of datalogCommands.
func Parse(input io.Reader) ([]DatalogCommand, error) {
s := newScanner(input)
commands := make([]DatalogCommand, 0)
for {
c, finished, err := s.scanOneCommand()
if err != nil || finished {
return commands, err
}
commands = append(commands, c)
}
}
// Scan iterates through a io reader, throwing commands into a channel as
// they are read from the reader.
func Scan(input io.Reader) (chan DatalogCommand, chan error) {
commands := make(chan DatalogCommand, 1000)
errors := make(chan error)
s := newScanner(input)
go func() {
for {
c, finished, err := s.scanOneCommand()
if err != nil {
errors <- err
break
}
if finished {
break
}
commands <- c
}
close(errors)
close(commands)
}()
return commands, errors
}
// Apply applies a single command.
// TODO: do we really need this and ApplyAll?
func Apply(cmd DatalogCommand, db Database) (*Result, error) {
head := buildLiteral(cmd.Head, db)
switch cmd.CommandType {
case Assert:
body := make([]literal, len(cmd.Body))
for i, ml := range cmd.Body {
body[i] = buildLiteral(ml, db)
}
err := db.assert(&clause{
head: head,
body: body,
})
return nil, err
case Query:
res := ask(head)
return &res, nil
case Retract:
body := make([]literal, len(cmd.Body))
for i, ml := range cmd.Body {
body[i] = buildLiteral(ml, db)
}
db.retract(&clause{
head: head,
body: body,
}) // really, no errors can happen?
return nil, nil
}
return nil, fmt.Errorf("bogus command - this should never happen")
}
// Result contain deduced facts that match a query.
type Result struct {
Name string
Arity int
Answers [][]Term
}
// ApplyAll iterates over a slice of commands, executes each in turn
// on a provided database, and accumulates and then returns results.
func ApplyAll(cmds []DatalogCommand, db Database) (results []Result, err error) {
for _, cmd := range cmds {
res, err := Apply(cmd, db)
if err != nil {
return results, err
}
if res != nil {
results = append(results, *res)
}
}
return
}
// ToString reformats results for display.
// Coincidentally, it also generates valid datalog.
func ToString(results []Result) string {
str := ""
for _, result := range results {
for _, terms := range result.Answers {
str += result.Name
if len(terms) > 0 {
str += "("
termStrings := make([]string, len(terms))
for i, t := range terms {
termStrings[i] = t.value
}
str += strings.Join(termStrings, ", ")
str += ")"
}
str += ".\n"
}
}
return str
}