forked from EricEve/adv3lite
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoutput.t
2016 lines (1760 loc) · 69.5 KB
/
output.t
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#charset "us-ascii"
#include "advlite.h"
/*
* ***************************************************************************
* output.t
*
* This module forms part of the adv3Lite library (c) 2012-13 Eric Eve, based
* heavily in parts on the equivalent code in adv3 (c) Micheal J. Roberts.
*/
/* ------------------------------------------------------------------------ */
/*
* The standard library output function. We set this up as the default
* display function (for double-quoted strings and for "<< >>"
* embeddings). Code can also call this directly to display items.
*/
say(val)
{
/*
* Use the dmsg() function to pass a string value through the message
* substitution parameter filter, otherwise output the value directly.
*/
if(dataType(val) == TypeSString)
{
dmsg(val);
}
else
oSay(val);
}
/*
* Send a value straight to the output stream without any further message
* filtering.
*/
oSay(val)
{
outputManager.curOutputStream.writeToStream(val);
}
/*
* A version of say() that avoids the cquote filter that can make havoc of
* some HTML strings, especially those generated by HRef (the cquote filter
* turns straight quotes into typographical ones, which is undesirable when
* straight quotes are used as part of HTML markup).
*/
htmlSay(val)
{
try
{
/* Deactivate the cquote filter. */
cquoteOutputFilter.deactivate();
/* Display the text */
oSay(val);
}
finally
{
/* reactivate the cquote filter */
cquoteOutputFilter.activate();
}
}
/*
* A version of say() that only produces output if the player can see obj (or,
* optionally, sense obj by some other sense passed as a canXXX method of the
* Query object via the prop parameter) */
senseSay(val, obj, prop = &canSee)
{
if(Q.(prop)(gPlayerChar, obj))
say(val);
}
/* ------------------------------------------------------------------------ */
/*
* Generate a string for showing quoted text. We simply enclose the
* text in a <Q>...</Q> tag sequence and return the result.
*/
withQuotes(txt)
{
return '<q><<txt>></q>';
}
/* ------------------------------------------------------------------------ */
/*
* Output Manager. This object contains global code for displaying text
* on the console.
*
* The output manager is transient because we don't want its state to be
* saved and restored; the output manager state is essentially part of
* the intepreter user interface, which is not affected by save and
* restore.
*/
transient outputManager: object
/*
* Switch to a new active output stream. Returns the previously
* active output stream, so that the caller can easily restore the
* old output stream if the new output stream is to be established
* only for a specific duration.
*/
setOutputStream(ostr)
{
local oldStr;
/* remember the old stream for a moment */
oldStr = curOutputStream;
/* set the new output stream */
curOutputStream = ostr;
/*
* return the old stream, so the caller can restore it later if
* desired
*/
return oldStr;
}
/*
* run the given function, using the given output stream as the
* active default output stream
*/
withOutputStream(ostr, func)
{
/* establish the new stream */
local oldStr = setOutputStream(ostr);
/* make sure we restore the old active stream on the way out */
try
{
/* invoke the callback */
(func)();
}
finally
{
/* restore the old output stream */
setOutputStream(oldStr);
}
}
/* the current output stream - start with the main text stream */
curOutputStream = mainOutputStream
/*
* Is the UI running in HTML mode? This tells us if we have a full
* HTML UI or a text-only UI. Full HTML mode applies if we're
* running on a Multimedia TADS interpreter, or we're using the Web
* UI, which runs in a separate browser and is thus inherently
* HTML-capable.
*
* (The result can't change during a session, since it's a function
* of the game and interpreter capabilities, so we store the result
* on the first evaluation to avoid having to recompute it on each
* query. Since 'self' is a static object, we'll recompute this each
* time we run the program, which is important because we could save
* the game on one interpreter and resume the session on a different
* interpreter with different capabilities.)
*/
htmlMode = (self.htmlMode = checkHtmlMode())
;
/* ------------------------------------------------------------------------ */
/*
* Output Stream. This class provides a stream-oriented interface to
* displaying text on the console. "Stream-oriented" means that we write
* text as a sequential string of characters.
*
* Output streams are always transient, since they track the system user
* interface in the interpreter. The interpreter does not save its UI
* state with a saved position, so objects such as output streams that
* track the UI state should not be saved either.
*/
class OutputStream: PreinitObject
/*
* Write a value to the stream. If the value is a string, we'll
* display the text of the string; if it's nil, we'll ignore it; if
* it's anything else, we'll try to convert it to a string (with the
* toString() function) and display the resulting text.
*/
writeToStream(val)
{
/* if we have any prefix text, output it first */
if(prefix != nil)
{
writeFromStream(prefix);
prefix = nil;
}
/* convert the value to a string */
switch(dataType(val))
{
case TypeSString:
/*
* it's a string - no conversion is needed, but if it's
* empty, it doesn't count as real output (so don't notify
* anyone, and don't set any output flags)
*/
if (val == '')
return;
break;
case TypeNil:
/* nil - don't display anything for this */
return;
case TypeInt:
case TypeObject:
/* convert integers and objects to strings */
val = toString(val);
break;
}
/* run it through our output filters */
val = applyFilters(val);
/*
* if, after filtering, we're not writing anything at all,
* there's nothing left to do
*/
if (val == nil || val == '')
return;
/* write the text to our underlying system stream */
writeFromStream(val);
}
/*
* Watch the stream for output. It's sometimes useful to be able to
* call out to some code and determine whether or not the code
* generated any text output. This routine invokes the given
* callback function, monitoring the stream for output; if any
* occurs, we'll return true, otherwise we'll return nil.
*/
watchForOutput(func)
{
local mon;
/* set up a monitor filter on the stream */
addOutputFilter(mon = new MonitorFilter());
/* catch any exceptions so we can remove our filter before leaving */
try
{
/* invoke the callback */
(func)();
/* return the monitor's status, indicating if output occurred */
return mon.outputFlag;
}
finally
{
/* remove our monitor filter */
removeOutputFilter(mon);
}
}
/*
* Call the given function, capturing all text output to this stream
* in the course of the function call. Return a string containing
* the captured text.
*/
captureOutput(func, [args])
{
/* install a string capture filter */
local filter = new StringCaptureFilter();
addOutputFilter(filter);
/* make sure we don't leave without removing our capturer */
try
{
/* invoke the function */
(func)(args...);
/* return the text that we captured */
return filter.txt_;
}
finally
{
/* we're done with our filter, so remove it */
removeOutputFilter(filter);
}
}
/*
* A Version of captureOutput that ignores an Exit Exception. This can be
* used to attempt to retrieve the string value of an output filter that
* threw an exit exeption.
*/
captureOutputIgnoreExit(func, [args])
{
/* install a string capture filter */
local filter = new StringCaptureFilter();
addOutputFilter(filter);
/* make sure we don't leave without removing our capturer */
try
{
/* invoke the function */
(func)(args...);
/* return the text that we captured */
return filter.txt_;
}
catch(ExitSignal ex)
{
return filter.txt_;
}
finally
{
/* we're done with our filter, so remove it */
removeOutputFilter(filter);
}
}
/* my associated input manager, if I have one */
myInputManager = nil
/* dynamic construction */
construct()
{
/*
* Set up filter list. Output streams are always transient, so
* make our filter list transient as well.
*/
filterList_ = new transient Vector(10);
}
/* execute pre-initialization */
execute()
{
/* do the same set-up we would do for dynamic construction */
construct();
}
/*
* Write text out from this stream; this writes to the lower-level
* stream underlying this stream. This routine is intended to be
* called only from within this class.
*
* Each output stream is conceptually "stacked" on top of another,
* lower-level stream. At the bottom of the stack is usually some
* kind of physical device, such as the display, or a file on disk.
*
* This method must be defined in each subclass to write to the
* appropriate underlying stream. Most subclasses are specifically
* designed to sit atop a system-level stream, such as the display
* output stream, so most implementations of this method will call
* directly to a system-level output function.
*/
writeFromStream(txt) { }
/*
* The list of active filters on this stream, in the order in which
* they are to be called. This should normally be initialized to a
* Vector in each instance.
*/
filterList_ = []
/*
* Add an output filter. The argument is an object of class
* OutputFilter, or any object implementing the filterText() method.
*
* Filters are always arranged in a "stack": the last output filter
* added is the first one called during output. This method thus
* adds the new filter at the "top" of the stack.
*/
addOutputFilter(filter)
{
/* add the filter to the end of our list */
filterList_.append(filter);
}
/*
* Add an output filter at a given point in the filter stack: add
* the filter so that it is "below" the given existing filter in the
* stack. This means that the new filter will be called just after
* the existing filter during output.
*
* If 'existingFilter' isn't in the stack of existing filters, we'll
* add the new filter at the "top" of the stack.
*/
addOutputFilterBelow(newFilter, existingFilter)
{
/* find the existing filter in our list */
local idx = filterList_.indexOf(existingFilter);
/*
* If we found the old filter, add the new filter below the
* existing filter in the stack, which is to say just before the
* old filter in our vector of filters (since we call the
* filters in reverse order of the list).
*
* If we didn't find the existing filter, simply add the new
* filter at the top of the stack, by appending the new filter
* at the end of the list.
*/
if (idx != nil)
filterList_.insertAt(idx, newFilter);
else
filterList_.append(newFilter);
}
/*
* Remove an output filter. Since filters are arranged in a stack,
* only the LAST output filter added may be removed. It's an error
* to remove a filter other than the last one.
*/
removeOutputFilter(filter)
{
/* get the filter count */
local len = filterList_.length();
/* make sure it's the last filter */
if (len == 0 || filterList_[len] != filter)
t3DebugTrace(T3DebugBreak);
/* remove the filter from my list */
filterList_.removeElementAt(len);
}
/* call the filters */
applyFilters(val)
{
/*
* Run through the list, applying each filter in turn. We work
* backwards through the list from the last element, because the
* filter list is a stack: the last element added is the topmost
* element of the stack, so it must be called first.
*/
for (local i in filterList_.length()..1 step -1 ; val != nil ; )
val = filterList_[i].filterText(self, val);
/* return the result of all of the filters */
return val;
}
/*
* Apply the current set of text transformation filters to a string.
* This applies only the non-capturing filters; we skip any capture
* filters.
*/
applyTextFilters(val)
{
/* run through the filter stack from top to bottom */
for (local i in filterList_.length()..1 step -1 ; val != nil ; )
{
/* skip capturing filters */
local f = filterList_[i];
if (f.ofKind(CaptureFilter))
continue;
/* apply the filter */
val = f.filterText(self, val);
}
/* return the result */
return val;
}
/*
* Receive notification from the input manager that we have just
* ended reading a line of input from the keyboard.
*/
inputLineEnd()
{
/* an input line ending doesn't look like a paragraph */
justDidPara = nil;
}
/*
* Internal state: we just wrote a paragraph break, and there has
* not yet been any intervening text. By default, we set this to
* true initially, so that we suppress any paragraph breaks at the
* very start of the text.
*/
justDidPara = true
/*
* Internal state: we just wrote a character that suppresses
* paragraph breaks that immediately follow. In this state, we'll
* suppress any paragraph marker that immediately follows, but we
* won't suppress any other characters.
*/
justDidParaSuppressor = nil
/* Text to be output before anything else */
prefix = nil
/* Set the prefix to txt */
setPrefix(txt)
{
prefix = txt;
}
;
/*
* The OutputStream for the main text area.
*
* This object is transient because the output stream state is
* effectively part of the interpreter user interface, which is not
* affected by save and restore.
*/
transient mainOutputStream: OutputStream
/*
* The main text area is the same place where we normally read
* command lines from the keyboard, so associate this output stream
* with the primary input manager.
*/
myInputManager = inputManager
/* the current command transcript */
curTranscript = nil
/* we sit atop the system-level main console output stream */
writeFromStream(txt)
{
/* write the text to the console */
aioSay(txt);
}
;
/* ------------------------------------------------------------------------ */
/*
* Paragraph manager. We filter strings as they're about to be sent to
* the console to convert paragraph markers (represented in the source
* text using the "style tag" format, <.P>) into a configurable display
* rendering.
*
* We also process the zero-spacing paragraph, <.P0>. This doesn't
* generate any output, but otherwise acts like a paragraph break in that
* it suppresses any paragraph breaks that immediately follow.
*
* The special marker <./P0> cancels the effect of a <.P0>. This can be
* used if you want to ensure that a newline or paragraph break is
* displayed, even if a <.P0> was just displayed.
*
* Our special processing ensures that paragraph tags interact with one
* another and with other display elements specially:
*
* - A run of multiple consecutive paragraph tags is treated as a single
* paragraph tag. This property is particularly important because it
* allows code to write out a paragraph marker without having to worry
* about whether preceding code or following code add paragraph markers
* of their own; if redundant markers are found, we'll filter them out
* automatically.
*
* - We can suppress paragraph markers following other specific
* sequences. For example, if the paragraph break is rendered as a blank
* line, we might want to suppress an extra blank line for a paragraph
* break after an explicit blank line.
*
* - We can suppress other specific sequences following a paragraph
* marker. For example, if the paragraph break is rendered as a newline
* plus a tab, we could suppress whitespace following the paragraph
* break.
*
* The paragraph manager should always be instantiated with transient
* instances, because this object's state is effectively part of the
* interpreter user interface, which doesn't participate in save and
* restore.
*/
class ParagraphManager: OutputFilter
/*
* Rendering - this is what we display on the console to represent a
* paragraph break. By default, we'll display a blank line.
*/
renderText = '\b'
/*
* Flag: show or hide paragraph breaks immediately after input. By
* default, we do not show paragraph breaks after an input line.
*/
renderAfterInput = nil
/*
* Preceding suppression. This is a regular expression that we
* match to individual characters. If the character immediately
* preceding a paragraph marker matches this expression, we'll
* suppress the paragraph marker in the output. By default, we'll
* suppress a paragraph break following a blank line, because the
* default rendering would add a redundant blank line.
*/
suppressBefore = static new RexPattern('\b')
/*
* Following suppression. This is a regular expression that we
* match to individual characters. If the character immediately
* following a paragraph marker matches this expression, we'll
* suppress the character. We'll apply this to each character
* following a paragraph marker in turn until we find one that does
* not match; we'll suppress all of the characters that do match.
* By default, we suppress additional blank lines after a paragraph
* break.
*/
suppressAfter = static new RexPattern('[\b\n]')
/* pre-compile some regular expression patterns we use a lot */
leadingMultiPat = static new RexPattern('(<langle><dot>[pP]0?<rangle>)+')
leadingSinglePat = static new RexPattern(
'<langle><dot>([pP]0?|/[pP]0)<rangle>')
/* process a string that's about to be written to the console */
filterText(ostr, txt)
{
local ret;
/* we don't have anything in our translated string yet */
ret = '';
/* keep going until we run out of string to process */
while (txt != '')
{
local len;
local match;
local p0;
local unp0;
/*
* if we just wrote a paragraph break, suppress any
* character that matches 'suppressAfter', and suppress any
* paragraph markers that immediately follow
*/
if (ostr.justDidPara)
{
/* check for any consecutive paragraph markers */
if ((len = rexMatch(leadingMultiPat, txt)) != nil)
{
/* discard the consecutive <.P>'s, and keep going */
txt = txt.substr(len + 1);
continue;
}
/* check for a match to the suppressAfter pattern */
if (rexMatch(suppressAfter, txt) != nil)
{
/* discard the suppressed character and keep going */
txt = txt.substr(2);
continue;
}
}
/*
* we have a character other than a paragraph marker, so we
* didn't just scan a paragraph marker
*/
ostr.justDidPara = nil;
/*
* if we just wrote a suppressBefore character, discard any
* leading paragraph markers
*/
if (ostr.justDidParaSuppressor
&& (len = rexMatch(leadingMultiPat, txt)) != nil)
{
/* remove the paragraph markers */
txt = txt.substr(len + 1);
/*
* even though we're not rendering the paragraph, note
* that a logical paragraph just started
*/
ostr.justDidPara = true;
/* keep going */
continue;
}
/* presume we won't find a <.p0> or <./p0> */
p0 = unp0 = nil;
/* find the next paragraph marker */
match = rexSearch(leadingSinglePat, txt);
if (match == nil)
{
/*
* there are no more paragraph markers - copy the
* remainder of the input string to the output
*/
ret += txt;
txt = '';
/* we just did something other than a paragraph */
ostr.justDidPara = nil;
}
else
{
/* add everything up to the paragraph break to the output */
ret += txt.substr(1, match[1] - 1);
/* get the rest of the string following the paragraph mark */
txt = txt.substr(match[1] + match[2]);
/* note if we found a <.p0> or <./p0> */
p0 = (match[3] is in ('<.p0>', '<.P0>'));
unp0 = (match[3] is in ('<./p0>', '<./P0>'));
/*
* note that we just found a paragraph marker, unless
* this is a <./p0>
*/
ostr.justDidPara = !unp0;
}
/*
* If the last character we copied out is a suppressBefore
* character, note for next time that we have a suppressor
* pending. Likewise, if we found a <.p0> rather than a
* <.p>, this counts as a suppressor.
*/
ostr.justDidParaSuppressor =
(p0 || rexMatch(suppressBefore,
ret.substr(ret.length(), 1)) != nil);
/*
* if we found a paragraph marker, and we didn't find a
* leading suppressor character just before it, add the
* paragraph rendering
*/
if (ostr.justDidPara && !ostr.justDidParaSuppressor)
ret += renderText;
}
/* return the translated string */
return ret;
}
;
/* the paragraph manager for the main output stream */
transient mainParagraphManager: ParagraphManager
;
/* ------------------------------------------------------------------------ */
/*
* Output Filter
*/
class OutputFilter: object
/*
* Apply the filter - this should be overridden in each filter. The
* return value is the result of filtering the string.
*
* 'ostr' is the OutputStream to which the text is being written,
* and 'txt' is the original text to be displayed.
*/
filterText(ostr, txt) { return txt; }
;
/* ------------------------------------------------------------------------ */
/*
* Output monitor filter. This is a filter that leaves the filtered
* text unchanged, but keeps track of whether any text was seen at all.
* Our 'outputFlag' is true if we've seen any output, nil if not.
*/
class MonitorFilter: OutputFilter
/* filter text */
filterText(ostr, val)
{
/* if the value is non-empty, note the output */
if (val != nil && val != '')
outputFlag = true;
/* return the input value unchanged */
return val;
}
/* flag: has any output occurred for this monitor yet? */
outputFlag = nil
;
/* ------------------------------------------------------------------------ */
/*
* Capture Filter. This is an output filter that simply captures all of
* the text sent through the filter, sending nothing out to the
* underlying stream.
*
* The default implementation simply discards the incoming text.
* Subclasses can keep track of the text in memory, in a file, or
* wherever desired.
*/
class CaptureFilter: OutputFilter
/*
* Filter the text. We simply discard the text, passing nothing
* through to the underlying stream.
*/
filterText(ostr, txt)
{
/* leave nothing for the underlying stream */
return nil;
}
;
/*
* "Switchable" capture filter. This filter can have its blocking
* enabled or disabled. When blocking is enabled, we capture
* everything, leaving nothing to the underlying stream; when disabled,
* we pass everything through to the underyling stream unchanged.
*/
class SwitchableCaptureFilter: CaptureFilter
/* filter the text */
filterText(ostr, txt)
{
/*
* if we're blocking output, return nothing to the underlying
* stream; if we're disabled, return the input unchanged
*/
return (isBlocking ? nil : txt);
}
/*
* Blocking enabled: if this is true, we'll capture all text passed
* through us, leaving nothing to the underyling stream. Blocking
* is enabled by default.
*/
isBlocking = true
;
/*
* String capturer. This is an implementation of CaptureFilter that
* saves the captured text to a string.
*/
class StringCaptureFilter: CaptureFilter
/* filter text */
filterText(ostr, txt)
{
/* add the text to my captured text so far */
addText(txt);
}
/* add to my captured text */
addText(txt)
{
/* append the text to my string of captured text */
txt_ += txt;
}
/* my captured text so far */
txt_ = ''
;
/* ------------------------------------------------------------------------ */
/*
* Style tag. This defines an HTML-like tag that can be used in output
* text to display an author-customizable substitution string.
*
* Each StyleTag object defines the name of the tag, which can be
* invoked in output text using the syntax "<.name>" - we require the
* period after the opening angle-bracket to plainly distinguish the
* sequence as a style tag, not a regular HTML tag.
*
* Each StyleTag also defines the text string that should be substituted
* for each occurrence of the "<.name>" sequence in output text, and,
* optionally, another string that is substituted for occurrences of the
* "closing" version of the tag, invoked with the syntax "<./name>".
*/
class StyleTag: object
/* name of the tag - the tag appears in source text in <.xxx> notation */
tagName = ''
/*
* opening text - this is substituted for each instance of the tag
* without a '/' prefix
*/
openText = ''
/*
* Closing text - this is substituted for each instance of the tag
* with a '/' prefix (<./xxx>). Note that non-container tags don't
* have closing text at all.
*/
closeText = ''
;
/*
* HtmlStyleTag - this is a subclass of StyleTag that provides different
* rendering depending on whether the interpreter is in HTML mode or not.
* In HTML mode, we display our htmlOpenText and htmlCloseText; when not
* in HTML mode, we display our plainOpenText and plainCloseText.
*/
class HtmlStyleTag: StyleTag
openText = (outputManager.htmlMode ? htmlOpenText : plainOpenText)
closeText = (outputManager.htmlMode ? htmlCloseText : plainCloseText)
/* our HTML-mode opening and closing text */
htmlOpenText = ''
htmlCloseText = ''
/* our plain (non-HTML) opening and closing text */
plainOpenText = ''
plainCloseText = ''
;
/*
* Define our default style tags. We name all of these StyleTag objects
* so that authors can easily change the expansion text strings at
* compile-time with the 'modify' syntax, or dynamically at run-time by
* assigning new strings to the appropriate properties of these objects.
*/
/*
* <.roomname> - we use this to display the room's name in the
* description of a room (such as in a LOOK AROUND command, or when
* entering a new location). By default, we display the room name in
* boldface on a line by itself.
*/
roomnameStyleTag: StyleTag 'roomname' '\n<b>' '</b><br>\n';
/* <.roomdesc> - we use this to display a room's long description */
roomdescStyleTag: StyleTag 'roomdesc' '' '';
/* <.roomcontents> - we use this to display a room's contents */
roomcontentsStyleTag: StyleTag 'roomcontents' '' '';
/*
* <.roompara> - we use this to separate paragraphs within a room's long
* description
*/
roomparaStyleTag: StyleTag 'roompara' '<.p>\n';
/*
* <.inputline> - we use this to display the text actually entered by the
* user on a command line. Note that this isn't used for the prompt text
* - it's used only for the command-line text itself.
*/
inputlineStyleTag: HtmlStyleTag 'inputline'
/* in HTML mode, switch in and out of TADS-Input font */
htmlOpenText = '<font face="tads-input">'
htmlCloseText = '</font>'
/* in plain mode, do nothing */
plainOpenText = ''
plainCloseText = ''
;
/*
* <.a> (named in analogy to the HTML <a> tag) - we use this to display
* hyperlinked text. Note that this goes *inside* an HTML <a> tag - this
* doesn't do the actual linking (the true <a> tag does that), but rather
* allows customized text formatting for hyperlinked text.
*/
hyperlinkStyleTag: HtmlStyleTag 'a'
;
/* <.statusroom> - style for the room name in a status line */
statusroomStyleTag: HtmlStyleTag 'statusroom'
htmlOpenText = '<b>'
htmlCloseText = '</b>'
;
/* <.statusscore> - style for the score in a status line */
statusscoreStyleTag: HtmlStyleTag 'statusscore'
htmlOpenText = '<i>'
htmlCloseText = '</i>'
;
/*
* <.parser> - style for messages explicitly from the parser.
*
* By default, we do nothing special with these messages. Many games
* like to use a distinctive notation for parser messages, to make it
* clear that the messages are "meta" text that's not part of the story
* but rather specific to the game mechanics; one common convention is
* to put parser messages in [square brackets].
*
* If the game defines a special appearance for parser messages, for
* consistency it might want to use the same appearance for notification
* messages displayed with the <.notification> tag (see
* notificationStyleTag).
*/
parserStyleTag: StyleTag 'parser'
openText = ''
closeText = ''
;
/*
* <.notification> - style for "notification" messages, such as score
* changes and messages explaining how facilities (footnotes, exit
* lists) work the first time they come up.
*
* By default, we'll put notifications in parentheses. Games that use
* [square brackets] for parser messages (i.e., for the <.parser> tag)
* might want to use the same notation here for consistency.
*/
notificationStyleTag: StyleTag 'notification'
openText = '('