Skip to content

Commit 4e1519e

Browse files
committed
Parse scopes
1 parent 9ea0738 commit 4e1519e

File tree

3 files changed

+324
-5
lines changed

3 files changed

+324
-5
lines changed

src/player.moo

Lines changed: 304 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,25 @@ object PLAYER
126126
return !is_player(args[1]);
127127
endverb
128128

129+
verb command_scope_for (this none this) owner: HACKER flags: "rxd"
130+
"Contribute the player and their held inventory to the command scope.";
131+
{actor, ?context = []} = args;
132+
if (actor != this)
133+
return {this};
134+
endif
135+
entries = `pass(@args) ! E_TYPE, E_VERBNF => {this}';
136+
typeof(entries) == LIST || (entries = {entries});
137+
inventory = this.contents;
138+
inventory || return entries;
139+
for item in (inventory)
140+
if (!valid(item))
141+
continue;
142+
endif
143+
entries = {@entries, item};
144+
endfor
145+
return entries;
146+
endverb
147+
129148
verb mk_emote_event (this none this) owner: HACKER flags: "rxd"
130149
return $event:mk_emote(this, $sub:nc(), " ", args[1]):with_this(this.location);
131150
endverb
@@ -164,4 +183,288 @@ object PLAYER
164183
endif
165184
return <$look, [what -> this, title -> this:name(), description -> description], {@this.contents}>;
166185
endverb
167-
endobject
186+
187+
verb command_environment (this none this) owner: HACKER flags: "rxd"
188+
"Assemble the environment list used for command parsing.";
189+
{command, ?options = []} = args;
190+
"Capture contextual state for downstream scope hooks.";
191+
context = ['actor -> this, 'command -> command, 'options -> options];
192+
location = this.location;
193+
"Gather ambient command holders from the actor and their location.";
194+
ambient_providers = this:_command_ambient_providers(context);
195+
ambient = this:_collect_command_ambient(ambient_providers, context);
196+
"Seed the breadth-first scope walk with the player and their location.";
197+
seeds = this:_command_scope_initial(context);
198+
seeds || (seeds = {this});
199+
queue = seeds;
200+
processed = {};
201+
environment = {};
202+
while (length(queue) > 0)
203+
current = queue[1];
204+
if (length(queue) == 1)
205+
queue = {};
206+
else
207+
queue = queue[2..$];
208+
endif
209+
if (typeof(current) != OBJ)
210+
continue;
211+
endif
212+
if (!valid(current))
213+
continue;
214+
endif
215+
if (current in processed)
216+
continue;
217+
endif
218+
processed = {@processed, current};
219+
"Ask each scope provider for additional entries.";
220+
additions = `current:command_scope_for(this, context) ! E_TYPE, E_VERBNF => {current}';
221+
typeof(additions) == LIST || (additions = {additions});
222+
for entry in (additions)
223+
normalized = this:_normalize_command_scope_entry(entry);
224+
if (!normalized)
225+
continue;
226+
endif
227+
if (typeof(normalized) == OBJ)
228+
entry_obj = normalized;
229+
else
230+
entry_obj = normalized[1];
231+
endif
232+
queue = this:_queue_scope_object(queue, processed, entry_obj);
233+
if (!this:_scope_entry_visible(entry_obj, normalized, context))
234+
continue;
235+
endif
236+
"Record every normalized entry so we can layer ordering afterwards.";
237+
environment = this:_merge_scope_environment(environment, normalized);
238+
endfor
239+
endwhile
240+
"Recreate the classic MOO ordering: player → inventory → room → room contents.";
241+
actor_entry = 0;
242+
location_entry = 0;
243+
ambient_extras = {};
244+
for entry in (ambient)
245+
entry_obj = this:_entry_object(entry);
246+
if (entry_obj == this && !actor_entry)
247+
actor_entry = entry;
248+
continue;
249+
endif
250+
if (valid(location) && entry_obj == location && !location_entry)
251+
location_entry = entry;
252+
continue;
253+
endif
254+
ambient_extras = {@ambient_extras, entry};
255+
endfor
256+
actor_entry || (actor_entry = this);
257+
if (valid(location))
258+
location_entry || (location_entry = location);
259+
endif
260+
inventory_entries = this:_select_entries_by_location(environment, this);
261+
location_entries = valid(location) ? this:_select_entries_by_location(environment, location) | {};
262+
final_env = {};
263+
final_env = this:_merge_scope_environment(final_env, actor_entry);
264+
for entry in (inventory_entries)
265+
final_env = this:_merge_scope_environment(final_env, entry);
266+
endfor
267+
"Place the room and its visible contents immediately after the actor's inventory.";
268+
if (valid(location))
269+
final_env = this:_merge_scope_environment(final_env, location_entry);
270+
for entry in (location_entries)
271+
final_env = this:_merge_scope_environment(final_env, entry);
272+
endfor
273+
endif
274+
"Append any extra ambient affordances after the core room structure.";
275+
for entry in (ambient_extras)
276+
final_env = this:_merge_scope_environment(final_env, entry);
277+
endfor
278+
"Finally include any remaining scoped objects (containers, fixtures, etc.).";
279+
for entry in (environment)
280+
final_env = this:_merge_scope_environment(final_env, entry);
281+
endfor
282+
final_env || return {this};
283+
return final_env;
284+
endverb
285+
286+
verb _command_scope_initial (this none this) owner: HACKER flags: "rxd"
287+
"Return the initial seed objects for command scope gathering.";
288+
{context} = args;
289+
seeds = {this};
290+
location = this.location;
291+
valid(location) && (seeds = {@seeds, location});
292+
return seeds;
293+
endverb
294+
295+
verb _normalize_command_scope_entry (this none this) owner: HACKER flags: "rxd"
296+
"Coerce a scope entry into either OBJ or {OBJ, aliases...}.";
297+
{entry} = args;
298+
typeof(entry) == ERR && return 0;
299+
typeof(entry) == OBJ && return entry;
300+
typeof(entry) != LIST && return 0;
301+
entry && typeof(entry[1]) == OBJ || return 0;
302+
length(entry) == 1 && return entry[1];
303+
return {entry[1], @entry[2..$]};
304+
endverb
305+
306+
verb _merge_scope_environment (this none this) owner: HACKER flags: "rxd"
307+
"Insert or merge a scope entry into the environment list.";
308+
{environment, entry} = args;
309+
typeof(entry) == OBJ && return this:_merge_scope_entry_obj(environment, entry);
310+
typeof(entry) != LIST && return environment;
311+
entry_obj = entry[1];
312+
idx = this:_find_scope_entry(environment, entry_obj);
313+
idx || return {@environment, entry};
314+
current = environment[idx];
315+
typeof(current) == OBJ && return this:_replace_scope_entry(environment, idx, entry);
316+
names = entry[2..$];
317+
names || return environment;
318+
merged = this:_merge_scope_names(current[2..$], names);
319+
environment[idx] = {entry_obj, @merged};
320+
return environment;
321+
endverb
322+
323+
verb _merge_scope_entry_obj (this none this) owner: HACKER flags: "rxd"
324+
"Insert an object-only entry if it is not already present.";
325+
{environment, entry_obj} = args;
326+
idx = this:_find_scope_entry(environment, entry_obj);
327+
idx && return environment;
328+
return {@environment, entry_obj};
329+
endverb
330+
331+
verb _merge_scope_names (this none this) owner: HACKER flags: "rxd"
332+
"Combine alias lists without duplicates.";
333+
{existing, extras} = args;
334+
extras || return existing;
335+
typeof(existing) != LIST && (existing = {});
336+
for name in (extras)
337+
if (name in existing)
338+
continue;
339+
endif
340+
existing = {@existing, name};
341+
endfor
342+
return existing;
343+
endverb
344+
345+
verb _find_scope_entry (this none this) owner: HACKER flags: "rxd"
346+
"Locate the index of an object inside the environment list.";
347+
{environment, entry_obj} = args;
348+
idx = 1;
349+
for entry in (environment)
350+
candidate = typeof(entry) == OBJ ? entry | entry[1];
351+
candidate == entry_obj && return idx;
352+
idx = idx + 1;
353+
endfor
354+
return 0;
355+
endverb
356+
357+
verb _replace_scope_entry (this none this) owner: HACKER flags: "rxd"
358+
"Replace an environment entry at a specific index.";
359+
{environment, idx, entry} = args;
360+
environment[idx] = entry;
361+
return environment;
362+
endverb
363+
364+
verb _queue_scope_object (this none this) owner: HACKER flags: "rxd"
365+
"Queue an object for later scope expansion if it has not been processed.";
366+
{queue, processed, entry_obj} = args;
367+
typeof(entry_obj) == OBJ || return queue;
368+
valid(entry_obj) || return queue;
369+
entry_obj in processed && return queue;
370+
for pending in (queue)
371+
pending == entry_obj && return queue;
372+
endfor
373+
return {@queue, entry_obj};
374+
endverb
375+
376+
verb _scope_entry_visible (this none this) owner: HACKER flags: "rxd"
377+
"Ask an object whether it should be exposed in the command scope.";
378+
{entry_obj, entry, context} = args;
379+
typeof(entry_obj) == OBJ || return false;
380+
valid(entry_obj) || return false;
381+
visible = `entry_obj:command_scope_visible(this, context) ! ANY => false';
382+
return visible ? true | false;
383+
endverb
384+
385+
verb _command_ambient_providers (this none this) owner: HACKER flags: "rxd"
386+
"Return the list of objects allowed to provide ambient verbs.";
387+
{context} = args;
388+
providers = {this};
389+
location = this.location;
390+
valid(location) && (providers = {@providers, location});
391+
return providers;
392+
endverb
393+
394+
verb _collect_command_ambient (this none this) owner: HACKER flags: "rxd"
395+
"Gather ambient verb holders from the allowed providers.";
396+
{providers, context} = args;
397+
ambient = {};
398+
typeof(providers) != LIST && return ambient;
399+
for provider in (providers)
400+
if (typeof(provider) != OBJ)
401+
continue;
402+
endif
403+
if (!valid(provider))
404+
continue;
405+
endif
406+
entries = `provider:command_ambient_for(this, context) ! E_TYPE, E_VERBNF => {provider}';
407+
typeof(entries) == LIST || (entries = {entries});
408+
for entry in (entries)
409+
normalized = this:_normalize_command_scope_entry(entry);
410+
if (!normalized)
411+
continue;
412+
endif
413+
if (typeof(normalized) == OBJ)
414+
ambient_obj = normalized;
415+
else
416+
ambient_obj = normalized[1];
417+
endif
418+
if (!this:_ambient_entry_visible(ambient_obj, normalized, context))
419+
continue;
420+
endif
421+
ambient = this:_merge_scope_environment(ambient, normalized);
422+
endfor
423+
endfor
424+
return ambient;
425+
endverb
426+
427+
verb _ambient_entry_visible (this none this) owner: HACKER flags: "rxd"
428+
"Check whether an ambient entry should be exposed.";
429+
{ambient_obj, entry, context} = args;
430+
typeof(ambient_obj) == OBJ || return false;
431+
valid(ambient_obj) || return false;
432+
visible = `ambient_obj:command_ambient_visible(this, context) ! ANY => false';
433+
return visible ? true | false;
434+
endverb
435+
436+
verb _select_entries_by_location (this none this) owner: HACKER flags: "rxd"
437+
"Return entries whose underlying object is located inside the target container.";
438+
{entries, container} = args;
439+
result = {};
440+
typeof(entries) != LIST && return result;
441+
typeof(container) != OBJ && return result;
442+
for entry in (entries)
443+
entry_obj = this:_entry_object(entry);
444+
if (!entry_obj)
445+
continue;
446+
endif
447+
if (entry_obj == container)
448+
continue;
449+
endif
450+
if (entry_obj.location != container)
451+
continue;
452+
endif
453+
result = {@result, entry};
454+
endfor
455+
return result;
456+
endverb
457+
458+
verb _entry_object (this none this) owner: HACKER flags: "rxd"
459+
"Extract the object referred to by a scope entry.";
460+
{entry} = args;
461+
if (typeof(entry) == OBJ)
462+
return entry;
463+
endif
464+
if (typeof(entry) == LIST && entry && typeof(entry[1]) == OBJ)
465+
return entry[1];
466+
endif
467+
return 0;
468+
endverb
469+
470+
endobject

src/room.moo

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,26 @@ object ROOM
3535
return true;
3636
endverb
3737

38+
verb command_scope_for (this none this) owner: HACKER flags: "rxd"
39+
"Expose the room and visible occupants to the command scope.";
40+
{actor, ?context = []} = args;
41+
"In case we have a parent that wants to establish initial occupancy, ask it first...";
42+
entries = `pass(@args) ! E_TYPE, E_VERBNF => {this}';
43+
"Add every visible occupant so they can be matched as direct objects.";
44+
visible = this:contents();
45+
for visible_obj in (visible)
46+
if (!valid(visible_obj))
47+
continue;
48+
endif
49+
entries = {@entries, visible_obj};
50+
endfor
51+
return entries;
52+
endverb
53+
3854
verb announce (this none this) owner: HACKER flags: "rxd"
3955
{event} = args;
4056
for who in (this:contents())
4157
`who:tell(event) ! E_VERBNF';
4258
endfor
4359
endverb
44-
endobject
60+
endobject

src/sysobj.moo

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ object SYSOBJ
9292
"Just choose to ignore empty commands...";
9393
length(args) == 0 && return true;
9494
command = argstr;
95-
pc = parse_command(command, {player, @player.contents, player.location, @player.location.contents}, true);
96-
env = {player, @player.contents, player.location, @player.location.contents};
95+
env = player:command_environment(command, ['complex -> true]);
96+
pc = parse_command(command, env, true);
9797
if (pc["dobj"] == #-2)
9898
dobj_candidates = pc["ambiguous_dobj"];
9999
else
@@ -124,4 +124,4 @@ object SYSOBJ
124124
notify(connection(), "I don't understand that.");
125125
return true;
126126
endverb
127-
endobject
127+
endobject

0 commit comments

Comments
 (0)