-
Notifications
You must be signed in to change notification settings - Fork 1
/
logic.rb
172 lines (155 loc) · 4.91 KB
/
logic.rb
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
172
require "set"
class Logic
def initialize
@database = Hash.new { |h, k| h[k]=[] }
end
def tell &block
@mode = :tell
instance_eval(&block)
@database
end
def query frames = [{}], &block
@mode = :query
@frames = frames
instance_eval(&block)
@frames
end
def ask &block
display compute(&block)
end
def compute &block
@rule_application_id = 0
bindings = query(&block)
begin
bindings.peek
rescue StopIteration
return []
end
@query_vars = Set.new
@mode = :record_vars
instance_eval(&block)
resolve = ->(atom, frame){
if atom.is_a? Symbol
frame.include?(atom) ? resolve.(frame[atom], frame) : atom
else
atom
end
}
bindings.map { |frame|
Hash[@query_vars.map { |var| [var, resolve.(var, frame)] }]
}
end
def display bindings
puts '=>'
puts 'No' if bindings == []
bindings.each { |frame|
puts 'Yes' if frame == {}
frame.each_pair.with_index { |(var, val), j|
val = val.is_a?(Symbol) ? '_unbound_' : val
if j == frame.size-1
puts ":#{var} = #{val};"
else
puts ":#{var} = #{val}"
end
}
}
end
def predicate pred_name, *args, &block
if @mode == :tell
data = block_given? ? [pred_name, args, block] : [pred_name, args]
@database[pred_name] << data # Could a set help?
elsif @mode == :query
@frames = [] if not @database.include? pred_name
return if @frames == []
@frames = @frames.lazy.flat_map do |frame|
@database[pred_name].lazy.flat_map do |entry|
if not entry[-1].is_a? Proc
match_result = pattern_match args, entry[1], frame
match_result == 'failed' ? [] : [match_result]
else
clean_rule = rename_rule entry
unify_result = unify_match args, clean_rule[1], frame
unify_result == 'failed' ? [] : query([unify_result], &clean_rule[-1])
end
end
end
elsif @mode == :rename_vars
new_vars = args.map { |arg| arg.is_a?(Symbol) ? ":#{arg}#{@rule_application_id}" : arg }
@rule_body << "#{pred_name} #{new_vars.join(', ')};"
elsif @mode == :record_vars
@query_vars.merge(args.select { |e| e.is_a? Symbol })
end
end
def rename_rule rule
@rule_application_id += 1
vars = rule[1].map { |var| var.is_a?(Symbol) ? "#{var}#{@rule_application_id}".to_sym : var }
@rule_body = ""
@mode = :rename_vars
instance_eval(&rule[-1])
[rule[0], vars, eval("Proc.new { #{@rule_body} }")]
end
def extend_if_consistent var, dat, frame
if frame.include? var
pattern_match frame[var], dat, frame
else
frame.merge var => dat
end
end
def extend_if_possible var, val, frame
if frame.has_key? var
unify_match frame[var], val, frame
elsif val.is_a? Symbol
if frame.has_key? val
unify_match var, frame[val], frame
else
frame.merge var => val
end
elsif depends_on? val, var, frame
'failed'
else
frame.merge var => val
end
end
def depends_on? exp, var, frame
if exp.is_a? Symbol
if var == exp
true
else
frame.has_key?(exp) ? depends_on?(frame[exp], var, frame) : false
end
elsif exp.is_a? Array
exp.any? {|e| depends_on? e, var, frame }
else
false
end
end
def pattern_match pat, dat, frame
if frame == "failed"
"failed"
elsif pat == dat
frame
elsif pat.is_a? Symbol
extend_if_consistent pat, dat, frame
elsif pat.is_a?(Array) && dat.is_a?(Array)
pat.zip(dat).reduce(frame) {|memo,(pattern,data)| pattern_match pattern, data, memo }
else
"failed"
end
end
def unify_match p1, p2, frame
if frame == 'failed'
'failed'
elsif p1 == p2
frame
elsif p1.is_a? Symbol
extend_if_possible p1, p2, frame
elsif p2.is_a? Symbol
extend_if_possible p2, p1, frame
elsif p1.is_a?(Array) && p2.is_a?(Array)
p1.zip(p2).reduce(frame) {|memo,(p_1,p_2)| unify_match p_1, p_2, memo }
else
'failed'
end
end
alias method_missing predicate
end