Skip to content

Compatibility Parser

Deliberate differences in how a line is parsed, dispatched and evaluated. SharpMUSH does not intend to change any of these to match PennMUSH.

The command word is not evaluated for built-in dispatch

Section titled “The command word is not evaluated for built-in dispatch”

A choice.

PennMUSH evaluates the first word of a command line before matching it, so [strcat(th,ink)] hello runs think.
SharpMUSH matches the built-in command word literally.
Why. A command name that only exists after evaluation cannot be checked against a lock, a hook or a restriction before it runs.
Workaround. Build command names as literal text, or dispatch through @switch.

> think [strcat(th,ink)] hello
think hello

This is specifically about matching a built-in command name. It does not contradict the $-command rule in compatibility matched: once built-in dispatch has not matched, $-commands are matched against the fully evaluated line (a separate, later stage).

Malformed expressions are an error, not silent text

Section titled “Malformed expressions are an error, not silent text”

A choice.

PennMUSH evaluates almost anything: an unclosed [, (, or {, or a trailing \ still produces output.
SharpMUSH answers #-1 PARSER FAILURE for an unbalanced expression.
Why. Silent recovery turns a typo into output that looks deliberate.
Workaround. Balance your brackets. Orphaned closers with no opener, such as a stray ], are still literal text, as in PennMUSH.

> think [add(1,2)
#-1 PARSER FAILURE: ...
> think add(1,2)]
3]

Function arguments are not evaluated before an argument-count error

Section titled “Function arguments are not evaluated before an argument-count error”

A choice.

PennMUSH runs the arguments for their side effects first: not(setq(0,x),1) sets %q0 and then reports the arity error.
SharpMUSH validates the argument count first, so those side effects do not happen.
Why. A miscalled function should not change the world.
Workaround. Do not rely on side effects in the arguments of a miscalled function.

> think [not(setq(0,x),1)]%q0
#-1 FUNCTION (NOT) EXPECTS AT MOST 1 ARGUMENTS BUT GOT 2

A choice.

PennMUSH continues past a function-invocation, recursion, call-depth or output limit.
SharpMUSH stops evaluating the rest of the expression.
Why. The limits exist to bound the work a single line can cause; continuing past one does not.
Workaround. Expect no output after the point where a limit is reached.

An attribute that calls itself reaches the recursion limit. PennMUSH goes on to append an x for every call that had already started and counts the result, answering 86 here; SharpMUSH stops:

> &RECURSE me=[u(me/RECURSE)]x
> think strlen(u(me/RECURSE))
#-1 FUNCTION RECURSION LIMIT EXCEEDED

call_limit counts calls, not parser recursion

Section titled “call_limit counts calls, not parser recursion”

A choice.

PennMUSH counts every recursive call of its expression parser against call_limit (each bracket, function argument and attribute body, src/parse.c:2128) and ships it at 100.
SharpMUSH counts function calls and attribute evaluations, about a third as many for the same code, and starts a new game at 1000.
Why. At 100 the call limit would trip before function_recursion_limit (50) on ordinary self-recursion, so &X me=[u(me/X)] would report CALL LIMIT EXCEEDED where PennMUSH reports FUNCTION RECURSION LIMIT EXCEEDED. 1000 keeps the recursion limit the one that answers.
Workaround. None needed. A game that wants a tighter bound can lower it with @config/set.

PennMUSH answers 100:

> think config(call_limit)
1000

A choice.

PennMUSH splices each list element into the pattern as text and then evaluates it, so element text containing softcode executes.
SharpMUSH binds ## to the iteration register (equivalent to %iL) and does not re-evaluate element text.
Why. Splice-then-evaluate is a code-injection path: any list whose contents a player can influence becomes executable.
Workaround. Use u() or an explicit evaluation where you meant to run the element.

> think iter(a b,##)
a b
> &CODE me=[add(1,2)]
> think iter(CODE,u(##))
3

Function names cannot be produced by evaluation

Section titled “Function names cannot be produced by evaluation”

A choice.

PennMUSH builds the called function’s name from evaluated output, so [setq(0,add)]%q0(1,2) calls add.
SharpMUSH recognises function names lexically; a name that only appears after substitution is ordinary text.
Why. The same reason as the command word: a call that does not exist until evaluation cannot be restricted.
Workaround. switch() on the name, or store the call in an attribute and u() it.

PennMUSH answers 3 to the first line:

> think [setq(0,add)]%q0(1,2)
add(1,2)
> think switch(add,add,add(1,2),sub,sub(1,2))
3

A choice.

PennMUSH keeps a tab character typed straight after a function’s ( or an argument’s ,, and trims only spaces there, so strlen(<tab>a) is 2.
SharpMUSH treats a tab in that position like a newline, as layout that separates tokens, and drops it: strlen(<tab>a) is 1, and strlen(<tab>) is 0 where PennMUSH answers 1. A tab anywhere else in an argument is kept, so strlen(a<tab>b) is 3 on both.
Why. It lets softcode be indented over several lines, with tabs, without changing what it evaluates to.
Workaround. Write the tab as %t, or put anything evaluated, even an empty [space(0)], in front of it. %t is kept in every position on both servers:

> think strlen(%ta)
2
> think strlen(a%tb)
3

Unescaped commas are never absorbed by a final argument

Section titled “Unescaped commas are never absorbed by a final argument”

A choice.

PennMUSH lets the last argument of functions such as pemit(), emit() and capstr() swallow extra unescaped commas (capstr(a,b,c) capitalises the string a,b,c), though it now warns that this is deprecated.
SharpMUSH treats every comma as an argument separator, so capstr(a,b,c) is a too-many-arguments error.
Why. It avoids silently changing what counts as an argument.
Workaround. Escape the commas (\,) or brace the text.

> think capstr({a,b,c})
A,b,c

Where SharpMUSH gives a function an optional argument PennMUSH does not have (see compatibility arguments), a PennMUSH-era call with an unescaped comma does not fail: it passes that argument instead. strlen() is one.

A choice.

PennMUSH has no equivalent.
SharpMUSH answers #-1 EXCEPTION: <json> when an internal error escapes a command, and notifies the player with the same text.
Why. Producing no output at all is indistinguishable from a command that did nothing on purpose.
Workaround. See exception for the payload and what a mortal versus a wizard is shown.
No example. A command that crashes is a defect, and none is kept around to show this with. Once one is found it is fixed, so there is no command whose output could stand as the example.

A choice.

PennMUSH answers many failures with a bare #-1 (or #-2, #-3), sometimes notifying the reason separately and often not giving one at all.
SharpMUSH puts the reason after the number: #-1 PERMISSION DENIED, #-1 NO MATCH, #-1 NO ZONE SET, #-1 NO DROP-TO, #-2 I DON'T KNOW WHICH ONE YOU MEAN, #-2 VARIABLE DESTINATION, #-3 HOME, and so on. Where PennMUSH notified a reason and returned a bare #-1, as hidden() did, the reason is now the return value and nothing is notified. A function that looks an object up still says “I can’t see that here.” when the match fails, as PennMUSH’s does.
Why. A bare #-1 reads the same whether the object was missing, refused, or simply had nothing to report, and a notification sent from inside iter() arrives once per element.
Workaround. Test the prefix, as PennMUSH’s own test suite does: strmatch(%0,#-*), or t(), which is false for anything starting #-. Code that compares with = or eq() against exactly #-1 needs the prefix test instead. namelist() keeps one-word #-1 and #-2 entries, because each entry is a position in a list.

> think zone(me)
#-1 NO ZONE SET
> think [strmatch(zone(me),#-*)] [t(zone(me))]
1 0

A set function’s delimiter can be longer than one character

Section titled “A set function’s delimiter can be longer than one character”

A choice.

Affects setunion() setdiff() setinter() setsymdiff().
PennMUSH answers #-1 SEPARATOR MUST BE ONE CHARACTER to a <delimiter> longer than one character, once either list is non-empty.
SharpMUSH splits both lists on the whole <delimiter> and joins the answer with it, unless an <osep> is given.
Why. Data held in softcode lists often already contains every single character.
Workaround. Use a one-character <delimiter>; it means the same on both servers.

> think setunion(a::b,b::c,::)
a::b::c
> think setinter(a::b,b::c,::)
b

A choice.

Affects mailstats() maildstats() mailfstats() with no <player>.
PennMUSH turns a Wizard’s empty <player> into “all mail” (extmail.c:2238-2242), then rejects that as no player before its all-mail branch is reached. The Wizard is told ”: No such player.” and gets nothing back.
SharpMUSH gives a Wizard their own statistics, as it gives everyone else.
Why. mailstats() documents mailstats([<player>]) as your own statistics, and PennMUSH’s answer is an error nobody could have relied on.
Workaround. None needed: mailstats(me) means the same on both servers.

> think words(mailstats())
2

A choice.

PennMUSH has no %> substitution, so it evaluates %> to a plain >.
SharpMUSH substitutes the output of the last command run in the same queue entry: after @dig Kitchen, %> is the new room’s dbref. A typed line starts with it empty. See command output.
Why. It lets one command’s result feed the next without a search or a register.
Workaround. Write a plain >, which means the same on both servers. Importing a PennMUSH database names every attribute that uses %>.

PennMUSH answers a>b to the first line:

> think a%>b
ab
> think a>b
a>b

A choice.

PennMUSH has no command piping. It evaluates %| to a plain |, and runs ;| as a ; followed by a command that starts with | (; | too).
SharpMUSH pipes as TinyMUX does: in an action list, a command followed by ;| has what it shows you passed to the next command, which reads it as %|. Everywhere else %| is empty. See piping.
Why. It lets softcode use what a command shows, which has no function of its own (look), and TinyMUX code that pipes runs unchanged.
Workaround. Write a plain |, which means the same on both servers. No command starts with | on either server, so ;| had no use in PennMUSH. Importing a PennMUSH database names every attribute that uses %| or ;|.

PennMUSH answers a|b to the first line:

> think a%|b
ab
> think a|b
a|b