-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathUJSHOP_Parser.rb
168 lines (153 loc) · 6.41 KB
/
UJSHOP_Parser.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
require_relative '../HyperTensioN/parsers/JSHOP_Parser'
module UJSHOP_Parser
include JSHOP_Parser
extend self
attr_reader :axioms, :rewards, :attachments
AND = 'and'
OR = 'or'
#-----------------------------------------------
# Define expression
#-----------------------------------------------
def define_expression(name, group)
raise "Error with #{name}" unless group.instance_of?(Array)
return unless first = group[0]
# Add implicit conjunction to expression
group.unshift(first = AND) if first.instance_of?(Array)
if first == AND or first == OR
if group.size > 2 then group.drop(1).each {|g| define_expression(name, g)}
elsif group.size == 2 then define_expression(name, group.replace(group[1]))
else raise "Unexpected zero arguments for #{first} in #{name}"
end
elsif first == NOT
raise "Expected single argument for not in #{name}" if group.size != 2
define_expression(name, group[1])
elsif first == 'call'
raise "Expected function name in #{name} call" unless group[1].instance_of?(String)
group.drop(2).each {|g| define_expression(name, g) if g.instance_of?(Array) and g[0] == first}
elsif first == 'assign'
raise "Expected 2 arguments for assign in #{name}" if group.size != 3
raise "Unexpected #{group[1]} as variable to assign in #{name}" unless group[1].start_with?('?')
define_expression(name, group[2]) if group[2].instance_of?(Array) and group[2][0] == 'call'
elsif a = @axioms.assoc(first)
raise "Axiom #{first} defined with arity #{a[1].size}, unexpected arity #{group.size.pred} in #{name}" if a[1].size != group.size.pred
elsif a = @attachments.assoc(first)
raise "Attachment #{first} defined with arity up to #{a.size.pred}, unexpected arity #{group.size.pred} in #{name}" if a.size < group.size
else @predicates[first.freeze] ||= false
end
end
#-----------------------------------------------
# Define effects
#-----------------------------------------------
def define_effects(name, group)
raise "Error with #{name} effect" unless group.instance_of?(Array)
group.each {|pre,| pre != NOT ? @predicates[pre.freeze] = true : raise("Unexpected not in #{name} effect") if pre != 'call'}
end
#-----------------------------------------------
# Parse operator
#-----------------------------------------------
def parse_operator(op)
op.shift
raise 'Operator without name definition' unless (name = op[0].shift).instance_of?(String)
name.sub!(/^!!/,'invisible_') or name.delete_prefix!('!')
raise "#{name} redefined" if @operators.assoc(name)
raise "#{name} have size #{op.size} instead of 4 or more" if op.size < 4
@operators << operator = [name, op.shift, op.shift]
# Preconditions
define_expression("#{name} precondition", operator[2])
# Effects
if op.size <= 3
define_effects(name, operator[4] = op.shift)
define_effects(name, operator[3] = op.shift)
operator << (op.empty? ? 1 : op.shift.to_f)
else
i = 0
until op.empty?
operator << (op[0].instance_of?(String) ? op.shift : "#{name}_#{i}")
define_effects(name, del = op.shift)
define_effects(name, add = op.shift)
operator.push(add, del, op.shift.to_f)
i += 1
end
end
end
#-----------------------------------------------
# Parse method
#-----------------------------------------------
def parse_method(met)
met.shift
# Method may already have decompositions associated
if method = @methods.assoc(name = (group = met.shift).shift)
raise "Expected same parameters for method #{name}" if method[1] != group
else @methods << method = [name, group]
end
until met.empty?
# Optional label, add index for the unlabeled decompositions
if met[0].instance_of?(String)
label = met.shift
raise "#{name} redefined #{label} decomposition" if method.drop(2).assoc(label)
else label = "case_#{method.size - 2}"
end
# Preconditions
define_expression("#{name} precondition", precond = met.shift)
# Subtasks
raise "Error with #{name} subtasks" unless (group = met.shift).instance_of?(Array)
method << [label, precond, group.each {|pre,| pre.sub!(/^!!/,'invisible_') or pre.delete_prefix!('!')}]
end
end
#-----------------------------------------------
# Parse axiom
#-----------------------------------------------
def parse_axiom(ax)
ax.shift
# Variable names are replaced, only arity must match
if axiom = @axioms.assoc(name = (param = ax.shift).shift)
raise "Axiom #{name} defined with arity #{axiom[1].size}, unexpected arity #{param.size}" if param.size != axiom[1].size
else @axioms << axiom = [name, Array.new(param.size) {|i| "?parameter#{i}"}]
end
# Expand constant parameters to equality call
const_param = []
param.zip(axiom[1]) {|p,pi| const_param << ['call', '=', pi, p] unless p.start_with?('?')}
while exp = ax.shift
if exp.instance_of?(String)
label = exp
raise "Expected axiom definition after label #{label} in #{name}" unless exp = ax.shift
else label = "case #{axiom.size - 2 >> 1}"
end
# Add constant parameters to expression if any
exp.flatten.each {|value|
if value.start_with?('?') and i = param.index(value)
value.replace(axiom[1][i])
end
}
exp = [AND, *const_param, exp] unless const_param.empty?
define_expression("axiom #{name}", exp)
axiom.push(label, exp)
end
end
#-----------------------------------------------
# Parse domain
#-----------------------------------------------
def parse_domain(domain_filename)
if (tokens = scan_tokens(domain_filename)).instance_of?(Array) and tokens.size == 3 and tokens[0] == 'defdomain'
@domain_name = tokens[1]
@operators = []
@methods = []
@predicates = {}
@axioms = []
@rewards = []
@attachments = []
tokens = tokens[2]
while group = tokens.shift
case group[0]
when ':operator' then parse_operator(group)
when ':method' then parse_method(group)
when ':-' then parse_axiom(group)
when ':rewards' then (@rewards = group).shift
when ':attachments' then (@attachments = group).shift
else raise "#{group[0]} is not recognized in domain"
end
end
else raise "File #{domain_filename} does not match domain pattern"
end
end
end