Simple custom listing: sharing a macro (TADS 3 adv3) (EDITED)

Hey TADS users! As it is frequently desirable to give your game objects some livelier description than "It contains a thing." , it is also somewhat frustrating to have to type this whole garble of jargon for every single little item with a change:

+ Surface 'surface' 'surface' "This is a surface that needs a custom lister. "
	descContentsLister: surfaceDescContentsLister {
		showListPrefixWide(itemCount,pov,parent) {
			"Hanging on <<parent.theName>> <<itemCount==1 ? 'is' : 'are'>> "; 
			}
		showListSuffixWide(itemCount,pov,parent) {
			", swaying now and then with the breeze. " ;
			}
		showListEmpty(pov,parent) {
			"\^<<parent.theName>> looks peculiarly useless with nothing hanging on it. " ;
			}
		}
// then repeat this whole block once for the contents lister, 
// and another time for the look in lister...

That prompted me to make myself a simpler solution, and so now having made it, Iā€™d be glad if anyone else could find it useful too.

(EDIT: As of posting this, I am still pretty new to the ā€œmacro languageā€ of TADS 3ā€¦ I donā€™t think the original posted code worked properly. Iā€™ve added some parentheses that I think fixed itā€¦

EDIT2: The way this is currently coded, I donā€™t think you can use dynamic embeddings in the showListEmpty method. It will evaluate the string one time and use that version for every call to showListEmpty even if you thought you were including a <<ifThis ? ā€˜then thatā€™ : ā€˜otherwise thisā€™>> within it.
If anybody knows macro language well enough to fix that, Iā€™d be glad to hearā€¦)

The macros are each simply a two-letter code followed by three arguments: prefix, suffix, and list empty. The whole paragraph above could be rewritten as

+ Surface 'surface' 'surface' "This is a surface that needs a custom lister. "
	DL('Hanging on <<parent.theName>> <<is_are>> ',
	    ', swaying now and then with the breeze. ',
	    '\^<<parent.theName>> looks peculiarly useless with nothing hanging on it. ')

Details:

  • There are macros corresponding to the four main lister properties of all Things (and I also added one for openingLister):
    (for contentsLister) CL(pref,suf,empty)
    (for descContentsLister) DL(pref,suf,empty)
    (for inlineContentsLister) IL(pref,suf,empty)
    (for lookInLister) LL(pref,suf,empty)
    (for openingLister) OL(pref,suf,empty)

  • The macros use a custom method to select the appropriate lister based on what kind of container it is. This can be useful (primarily for lookInListers) if you donā€™t want to override showListEmpty: it will automatically use the correct preposition for "Thereā€™s nothing in/on/under/behind the thing. "

  • If you donā€™t wish to override showListEmpty, pass nil for the third argument. If you use the macro at all, youā€™ll need to supply prefix and suffix strings.

  • is_are is a supplementary macro which expands to (itemCount==1 ? 'is' : 'are').

  • Note that if you need a custom lister that tweaks which objects are listed and when, you will probably have to define the lister normally or modify this macro to include it.

And here is the code you can copy and paste:

// don't forget to put these in a header file or at the beginning of each source file!

#define CL(pref,suf,sle) initCL() { local lister = selectLister('cl'); \
	local pm = method(itemCount,pov,parent) { "<<pref>>" ; } ; \
	local sm = method(itemCount,pov,parent) { "<<suf>>" ; } ; \
	lister.macroUsesCustomSLE = (sle) ; \
	if(macroUsesCustomSLE!=nil) { local em = method(pov,parent) { "<<sle>>" ; } ; \
	lister.setMethod(&showListEmpty,em); } \
	lister.setMethod(&showListPrefixWide, pm); \
	lister.setMethod(&showListSuffixWide, sm); \
	contentsLister = lister; }
#define DL(pref,suf,sle) initDL() { local lister = selectLister('dcl'); \
	local pm = method(itemCount,pov,parent) { "<<pref>>" ; } ; \
	local sm = method(itemCount,pov,parent) { "<<suf>>" ; } ; \
	lister.macroUsesCustomSLE = (sle) ; \
	if(macroUsesCustomSLE!=nil) { local em = method(pov,parent) { "<<sle>>" ; } ; \
	lister.setMethod(&showListEmpty,em); } \
	lister.setMethod(&showListPrefixWide, pm); \
	lister.setMethod(&showListSuffixWide, sm); \
	descContentsLister = lister; }
#define LL(pref,suf,sle) initLL() { local lister = selectLister('lil'); \
	local pm = method(itemCount,pov,parent) { "<<pref>>" ; } ; \
	local sm = method(itemCount,pov,parent) { "<<suf>>" ; } ; \
	lister.macroUsesCustomSLE = (sle) ; \
	if(macroUsesCustomSLE!=nil) { local em = method(pov,parent) { "<<sle>>" ; } ; \
	lister.setMethod(&showListEmpty,em); } \
	lister.setMethod(&showListPrefixWide, pm); \
	lister.setMethod(&showListSuffixWide, sm); \
	lookInLister = lister; }
#define IL(pref,suf,sle) initIL() { local lister = selectLister('icl'); \
	local pm = method(itemCount,pov,parent) { "<<pref>>" ; } ; \
	local sm = method(itemCount,pov,parent) { "<<suf>>" ; } ; \
	lister.macroUsesCustomSLE = (sle) ; \
	if(macroUsesCustomSLE!=nil) { local em = method(pov,parent) { "<<sle>>" ; } ; \
	lister.setMethod(&showListEmpty,em); } \
	lister.setMethod(&showListPrefixWide, pm); \
	lister.setMethod(&showListSuffixWide, sm); \
	inlineContentsLister = lister; }
#define OL(pref,suf,sle) initOL() { local lister = selectLister('ool'); \
	local pm = method(itemCount,pov,parent) { "<<pref>>" ; } ; \
	local sm = method(itemCount,pov,parent) { "<<suf>>" ; } ; \
	lister.macroUsesCustomSLE = (sle) ; \
	if(macroUsesCustomSLE!=nil) { local em = method(pov,parent) { "<<sle>>" ; } ; \
	lister.setMethod(&showListEmpty,em); } \
	lister.setMethod(&showListPrefixWide, pm); \
	lister.setMethod(&showListSuffixWide, sm); \
	openingLister = lister; }

#define is_are (itemCount == 1 ? 'is' : 'are')
// used in custom lister selection method
#define oK ofKind

// and these adds don't need to be in the header
modify Thing
	initializeThing() { inherited; initListers(); }
	initListers() { initCL(); initDL(); initLL(); initIL(); initOL();  }
        // these will be overridden based on macros used
	initCL() { }	initDL() { }	initLL() { }	initIL() { }	initOL() { }
	selectLister(type) {switch(type) {
	  case 'cl' : if(oK(Surface)) return new surfaceContentsLister;
		else if(oK(Underside)) return new undersideContentsLister;
		else if(oK(RearContainer) || oK(RearSurface)) return new rearContentsLister;
		else return new thingContentsLister; 
	  case 'dcl' : if(oK(Openable)) return new openableDescContentsLister;
		else if(oK(Surface)) return new surfaceDescContentsLister;
		else if(oK(Underside)) return new undersideDescContentsLister;
		else if(oK(RearContainer) || oK(RearSurface)) return new rearDescContentsLister;
		else return new thingDescContentsLister; 
	  case 'lil' : if(oK(Surface)) return new surfaceLookInLister;
		else if(oK(Underside)) return new undersideLookUnderLister;
		else if(oK(RearContainer) || oK(RearSurface)) return new rearLookBehindLister;
		else return new thingLookInLister; 
	  case 'icl' : if(oK(Surface)) return new surfaceInlineContentsLister;
		else if(oK(Underside)) return new undersideInlineContentsLister;
		else if(oK(RearContainer) || oK(RearSurface)) return new rearInlineContentsLister;
		else return new inlineListingContentsLister;
	  case 'ool' : return new openableOpeningLister;
	  default: return nil; 
		} }
	;

/*I didn't know how else to get rid of a constant compiler warning (an expression always evaluated to nil) without using this property vv*/

modify Lister
	macroUsesCustomSLE = nil
	;
2 Likes