Jump to content

Mad Libs: Difference between revisions

5,777 bytes added ,  9 years ago
→‎{{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.
Because allowing replacement texts to themselves refer to <...> entries (and add their own) is not called for, the complications are avoided. Similarly, 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.
 
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 = 6666666) !LSTASH characters for MSTASH texts.
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 ADDPOOLADDSTASH(TEXT) !Appends an arbitrary text to the pool of stashed texts.
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.
ADDPOOLADDSTASH = NSTASH !Pass a finger back to the caller.
END FUNCTION ADDPOOLADDSTASH !Just an integer.
 
SUBROUTINEINTEGER TABLEFUNCTION ANOTHER(TEXT) !Possibly add TEXT to the table of target texts.
Collects TARGET REPLACEMENT pairs (increasing NTESTS) as directed by INSPECT.
CHARACTER*(*) TEXT !The text of the target.
INTEGER I,IT !Steppers.
DO IANOTHER = 1,NTESTS0 !Step throughPossibly, the knowntext targetis already in the textstable.
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) = ADDPOOLADDSTASH(TEXT)!Stash its text and get a finger to it.
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 SUBROUTINEFUNCTION TABLEANOTHER !Produces entries for TARGET and REPLACEMENT.
 
SUBROUTINE INSPECT(ITX) !Examine the text number X for the special <...> sequence.
Calls for inspection of REPLACEMENT texts as well, should ANOTHER report a new entry.
INTEGER ITX !Fingers the text in STASH via ISTASH(X).
INTEGER MARK !Recalls where the < was found.
CHARACTER*1INTEGER CIT,NEW !AFingers scratchpadto entries in STASH.
MARKINTEGER = 0 I !Uninitialised variables areA badstepper.
DOINTEGER I = ISTASH(IT)SP,ISTASHSTACK(IT + 1STACKLIMIT) - 1 !Step throughPrepare thefor stashedsome textrecursion.
CSP = STASH(I:I)1 !GrabStart with athe characterstarter.
IF STACK(C.EQ."<"1) THEN= X !IsStack it the starter?up.
DO MARK = I WHILE(SP.GT.0) !Yes.While Remembertexts whereare ityet is.uninspected,
ELSE IFIT = STACK(C.EQ.">"SP) THEN !TheFinger ender?one.
SP IF= (MARK.LE.0)SP CALL- CROAK("A1 !Working >down withthe no preceeding <!") !Bahstack.
MARK CALL= TABLE(STASH(MARK:I))0 !Yes. ConsiderUninitialised thevariables spannedare textbad.
DO MARKI = 0 !AndISTASH(IT),ISTASH(IT be+ 1) - 1!Step readythrough tothe checkstashed afreshtext.
END IF (STASH(I:I).EQ."<") THEN !SoIs muchit forthe that character.starter?
END DO MARK = I !OnYes. Remember towhere theit nextis.
ELSE IF (STASH(I:I).EQ.">") THEN !The ender?
IF (L + NMARK.GTLE.LSTASH0) CALL CROAK("OutA > with no ofpreceeding stash<!") !ParanoiaBah.
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.
L SP = LSP + N1 !AdvanceBut I'm still busy with the outputcurrent fingertext.
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,HIT !Fingers.
INTEGER LASTTAIL,MARK,LAST !Scan choppers.
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 !NoFind textits fromlast thischaracter linein yetSTASH.
II:DO I = ISTASH(IT),ISTASH(IT !Find +its 1)first -character 1 !Step along itsin textSTASH.
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.?
NCALL =APPEND(TAIL + 1,MARK - LAST - 1) !Waiting text up to the mark.
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.
HITIT = TARGET(J) !Finger one.
IF (STASH(ISTASH(HITIT):ISTASH(HITIT + 1) - 1) !Its stashed text.
1 .EQ.STASH(MARK:I)) THEN !Matches the suspect text?
HITIT = REPLACEMENT(J) !Yes! Finger the replacement text.
IF (HITIT.GT.0) THEN !Presumably,Null notreplacements acan nullbe textignored.
NIF = ISTASH(HIT + 1SP.GE.STACKLIMIT) -CALL ISTASHCROAK(HIT"StackOverflow!") !CharacterAlways countdiff. thereofmessages.
IFSP (L= SP + N.GT.LSTASH) CALL CROAK("Over replaced!")1 !AlwaysInterrupt the distinctcurrent messagesscan.
STASHSTACKI(L + 1:L + NSP) = STASHI !PlaceRemember where we're forup output.to,
1 STACKL(ISTASH(HITSP):ISTASH(HIT += 1)LAST !And -the 1)!Fromend whereverof itthe istext.
LI = LISTASH(IT) +- N 1 !AdvanceOne thewill outputbe fingeradded shortly, at JJ+1.
END IF !So muchLAST for= ISTASH(IT + 1) - 1 !Preempt the replacementscan-in-progress.
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.
LASTTAIL = I !RememberNormal thetext locationresumes ofat theTAIL >+ 1.
END IF !Enough analysis of that character from the story line.
END DO III = I + 1 !OnThe next to the nextconsider.
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.
WRITE (MSG,*"(A)") STASH(L0:L)//STASH(LAST+1:ISTASH(IT+1)-1) !With the tail end.Yay!
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.
STASH(L + 1:L + N) = STASH(LAST + 1IST:MARK - 1LST) !Place in theThere outputthey linego.
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>
</lang>
 
=={{header|Go}}==
1,220

edits

Cookies help us deliver our services. By using our services, you agree to our use of cookies.