Mad Libs: Difference between revisions
→{{header|Fortran}}: Enable recursive usage...
(→{{header|Go}}: Add Fortran.) |
(→{{header|Fortran}}: Enable recursive usage...) |
||
Line 756:
The absence of a "current length" notion also means that when an assignment is made to a character variable, if the source is shorter than the recipient, trailing spaces will be supplied, possibly in very large numbers. A statement <code>STASH = "Short"</code> would copy five characters and then follow with spaces to the end. This can be avoided by specifying <code>STASH(1:5) = "Short"</code> with the usual risk of miscounting in the programming. Character variables typically contain trailing spaces, and the rule for comparison ignores them. Thus "Short" and "Short " are deemed equal. For this reason, the < and > bounding the target texts are included in their string: "<Short>" and "<Short >" are definitely not equal.
There is only token checking for ill-formed stories. The checking for array overflow and the like is present on principle, but is unlikely to be exercised in simple runs, and so only uncivilised messages are evoked. Although allowing replacement texts to themselves refer to <...> entries (and add their own) is not called for, the notion adds to the entertainment value, so... This is most easily done via recursion. There has never been any difficulty in having a Fortran subprogrammes invoke itself or otherwise engage in recursion, it is just that early implementations did not support any sort of stack mechanism to handle the returns to the correct places, just one level of return, and so it was said that recursion is impossible in Fortran. But it is not actually a language problem, as demonstrated by the Burroughs 6700 implementation, whereby it just worked. This has been ignored in later versions of Fortran, which require the use of the magic word RECURSIVE for all those subprogrammes that may dabble in unnatural practices.
Otherwise, the older styles of Fortran could dabble in recursion only via the provision of an array and a counter and some control code to save and restore whatever state is necessary for the task at hand. This is done in WRITESTORY where the line being transcribed to output has its scan interrupted by the replacement text, which is scanned for replacements, after which the interrupted scan is resumed. The earlier version without this extension merely scanned each line character-by-character via a DO-loop, using the bounds of the text being scanned. Some compilers allow the index and bound of a DO-loop to be adjusted during the progress of the loop (as when the scan of one text would be interrupted by a new scan of a replacement) however many do not and some even declare such attempts an error. So instead a DO WHILE statement is used. Older Fortrans would of course lack such constructions, and so many GO TO statements would appear.
An example of the resulting opportunities for confusion:
Reads a story in template form, containing special entries such as <dog's name> amongst the text.
You will be invited to supply a replacement text for each such entry, as encountered,
after which the story will be presented with your substitutions made.
Here goes... Reading file Madlib.txt
Enter your text for <name>: Joe <adjective>
Enter your text for <he or she>: He was poor but
Enter your text for <adjective>: the lucky
Enter your text for <noun>: banknote with <name> written on it
Righto!
Joe the lucky went for a walk in the park. He was poor but
found a banknote with Joe the lucky written on it. Joe the lucky decided to take it home.
<lang Fortran>
Line 763 ⟶ 783:
DATA MSG,KBD,INF/6,5,10/ !Output, input, some disc file.
INTEGER LSTASH,NSTASH,MSTASH !Prepare a common text stash.
PARAMETER (LSTASH = 246810, MSTASH =
CHARACTER*(LSTASH) STASH !The pool.
INTEGER ISTASH(MSTASH + 1) !Fingers start positions, and thus end positions by extension.
Line 773 ⟶ 793:
INTEGER NTESTS,TARGET(MANYTESTS),REPLACEMENT(MANYTESTS) !Thus.
DATA NSTORY,NTESTS/0,0/ !No story lines, and no tests.
INTEGER STACKLIMIT !A recursion limit.
PARAMETER (STACKLIMIT = 28) !This should suffice.
CONTAINS
Line 808 ⟶ 830:
ISTASH(NSTASH + 1) = L + 1 !The start point of the following text.
EATTEXT = NSTASH !STASH(ISTASH(n):ISTASH(n + 1) - 1) has text n.
RETURN !All well.
66 EATTEXT = 0 !Sez: "No text".
END FUNCTION EATTEXT !Rather odd side effects.
INTEGER FUNCTION
CHARACTER*(*) TEXT !The stuff.
INTEGER I !A finger.
IF (NSTASH.GE.MSTASH) CALL CROAK("The text pool is crowded!") !Alas.
I = ISTASH(NSTASH + 1) !First unused character.
Line 820 ⟶ 843:
NSTASH = NSTASH + 1 !Count in another entry.
ISTASH(NSTASH + 1) = I + LEN(TEXT) !The new "first available" position.
END FUNCTION
Collects TARGET REPLACEMENT pairs (increasing NTESTS) as directed by INSPECT.
CHARACTER*(*) TEXT !The text of the target.
INTEGER I,IT !Steppers.
DO I = 1,NTESTS !So, step through the known target texts.
IT = TARGET(I) !Finger a target text.
IF (TEXT.EQ.STASH(ISTASH(IT):ISTASH(IT + 1) - 1)) RETURN !Already have this one.
Line 832 ⟶ 857:
IF (NTESTS.GE.MANYTESTS) CALL CROAK("Too many tests!") !Oh dear.
NTESTS = NTESTS + 1 !Count in another.
TARGET(NTESTS) =
ANOTHER = NTESTS !My caller will want to know which test.
WRITE (MSG,1) TEXT !Now request the replacement text.
1 FORMAT ("Enter your text for ",A,": ",$) !Obviously, the $ indicates "no new line".
REPLACEMENT(NTESTS) = EATTEXT(KBD) !Zero for "no text".
END
SUBROUTINE INSPECT(
Calls for inspection of REPLACEMENT texts as well, should ANOTHER report a new entry.
INTEGER
INTEGER MARK !Recalls where the < was found.
DO
SP
MARK
DO
ELSE IF (STASH(I:I).EQ.">") THEN !The ender?
NEW = ANOTHER(STASH(MARK:I)) !Consider the spanned text.
IF (NEW.GT.0) THEN !If that became a new table entry,
IF (SP.GE.STACKLIMIT) CALL CROAK("Stack overflow!") !Its replacement is to be inspected.
STACK(SP) = REPLACEMENT(NEW) !So, stack it for later.
END IF !So much for that <...> apparition.
MARK = 0 !Be ready to check afresh for the next.
END IF !So much for that character.
END DO !On to the next.
END DO !So much for that stacked entry.
END SUBROUTINE INSPECT !WRITESTORY will rescan the story lines.
Line 867 ⟶ 906:
END SUBROUTINE READSTORY!Simple enough, anyway.
SUBROUTINE WRITESTORY !Applying the replacements, with replacement replacement too.
Co-opts the as-yet unused space in STASH as its output scratchpad.
Can't rely on changing the index and bounds of a DO-loop on the fly.
INTEGER LINE,IT,I,J !Steppers.
INTEGER L,L0,N
INTEGER
INTEGER SP,STACKI(STACKLIMIT),STACKL(STACKLIMIT) !Ah, recursion.
L0 = ISTASH(NSTASH + 1) !The first available place in the stash.
LL:DO LINE = 1,NSTORY !Step through the lines of the story.
L = L0 - 1 !Syncopation for my output finger.
SP = 0 !Start with the task in hand.
IT = STORY(LINE) !Finger the stashed line.
LAST = ISTASH(IT + 1) - 1 !
TAIL = I - 1 !Syncopation. No text from this line yet.
666 II:DO WHILE(I.LE.LAST) !Step along its text.
IF (STASH(I:I).EQ."<") THEN !Trouble starter?
MARK = I !Yes. Remember where.
ELSE IF (STASH(I:I).EQ.">") THEN !The corresponding ender
▲ IF (L + N.GT.LSTASH) CALL CROAK("Out of stash!") !Paranoia.
STASH(L + 1:L + N) = STASH(LAST + 1:MARK - 1) !Place in the output line.▼
▲ L = L + N !Advance the output finger.
JJ:DO J = 1,NTESTS !Step through the target texts.
IF (STASH(ISTASH(
1 .EQ.STASH(MARK:I)) THEN !Matches the suspect text?
IF (
END IF !To work along the replacement text.
EXIT JJ !Found the target, so the search is finished.
END IF !Otherwise,
END DO JJ !Try the next target text.
END IF !Enough analysis of that character from the story line.
END DO II !Perhaps we've finished this text.
WRITE (MSG,*) STASH(L0:L)//STASH(LAST+1:ISTASH(IT+1)-1) !With the tail end.▼
IF (SP.GT.0) THEN !Yes! But, were we interrupted in a previous scan?
CALL APPEND(TAIL + 1,LAST)!Yes! Roll the tail of the just-finished scan.
TAIL = STACKI(SP) !The stacked value of I was fingering a >.
LAST = STACKL(SP) !And this was the end of the text.
SP = SP - 1 !So we've recovered where the scan was.
I = TAIL + 1 !And this is the next to look at.
GO TO 666 !Proceed to do so.
END IF !But if all is unstacked,
CALL APPEND(TAIL + 1,LAST) !Don't forget the tail end.
END DO LL !On to the next story line.
CONTAINS !An assistant, defined after usage...
SUBROUTINE APPEND(IST,LST) !Has access to L.
INTEGER IST,LST !To copy STASH(IST:LST) to the scratchpad.
INTEGER N !The number of characters to copy.
N = LST - IST + 1 !So find out.
IF (N.LE.0) RETURN !Avoid relying on zero-length action.
IF (L + N.GT.LSTASH) CALL CROAK("Out of stash!") !Oh dear.
L = L + N !Advance my oputput finger.
END SUBROUTINE APPEND !Few invocations, if with tricky parameters.
END SUBROUTINE WRITESTORY !Just a sequence of lines.
END MODULE MADLIB !Enough of that.
PROGRAM MADLIBBER !See, for example, https://en.wikipedia.org/wiki/Mad_Libs
USE MADLIB
WRITE (MSG,1) !It's polite to explain.
1 FORMAT ("Reads a story in template form, containing special ",
2 "entries such as <dog's name> amongst the text.",/,
3 "You will be invited to supply a replacement text for each "
4 "such entry, as encountered,",/,
5 "after which the story will be presented with your ",
6 "substitutions made.",//,
6 "Here goes... Reading file Madlib.txt",/)
OPEN(INF,STATUS="OLD",ACTION="READ",FORM="FORMATTED",
1 FILE = "Madlib.txt")
CALL READSTORY(INF)
CLOSE(INF)
WRITE (MSG,*)
WRITE (MSG,*) " Righto!"
WRITE (MSG,*)
CALL WRITESTORY
END</lang>
=={{header|Go}}==
|