Mad Libs is a phrasal template word game where one player prompts another for a list of words to substitute for blanks in a story, usually with funny results.

Task
Mad Libs
You are encouraged to solve this task according to the task description, using any language you may know.

Write a program to create a Mad Libs like story. The program should read a multiline story from the input. The story will be terminated with a blank line. Then, find each replacement to be made within the story, ask the user for a word to replace it with, and make all the replacements. Stop when there are none left and print the final story.

The input should be in the form:

<name> went for a walk in the park. <he or she>
found a <noun>. <name> decided to take it home.

It should then ask for a name, a he or she and a noun (<name> gets replaced both times with the same value.)

This page uses content from Wikipedia. The original article was at Mad Libs. The list of authors can be seen in the page history. As with Rosetta Code, the text of Wikipedia is available under the GNU FDL. (See links for details on variance)

Ada

The fun of Mad Libs is not knowing the story ahead of time, so the program reads the story template from a text file. The name of the text file is given as a command line argument.

<lang Ada>with Ada.Text_IO, Ada.Command_Line, String_Helper;

procedure Madlib is

  use String_Helper;
  Text: Vector := Get_Vector(Ada.Command_Line.Argument(1));
  M, N: Natural;

begin

  -- search for templates and modify the text accordingly
  for I in Text.First_Index .. Text.Last_Index loop
     loop
        Search_Brackets(Text.Element(I), "<", ">", M, N);
     exit when M=0; -- "M=0" means "not found"
        Ada.Text_IO.Put_Line("Replacement for " & Text.Element(I)(M .. N) & "?");
        declare
           Old: String := Text.Element(I)(M .. N);
           New_Word: String := Ada.Text_IO.Get_Line;
        begin
           for J in I .. Text.Last_Index loop
              Text.Replace_Element(J, Replace(Text.Element(J), Old, New_Word));
           end loop;
        end;
     end loop;
  end loop;
  -- write the text
  for I in Text.First_Index .. Text.Last_Index loop
     Ada.Text_IO.Put_Line(Text.Element(I));
  end loop;

end Madlib;</lang>

It uses an auxiliary package String_Helper for simple string functions;

<lang Ada>with Ada.Containers.Indefinite_Vectors;

package String_Helper is

  function Index(Source: String; Pattern: String) return Natural;
  procedure Search_Brackets(Source: String;
                            Left_Bracket: String;
                            Right_Bracket: String;
                            First, Last: out Natural);
     -- returns indices of first pair of brackets in source
     -- indices are zero if no such brackets are found
  function Replace(Source: String; Old_Word: String; New_Word: String)
                  return String;
  package String_Vec is new Ada.Containers.Indefinite_Vectors
    (Index_Type   => Positive,
     Element_Type => String);
  type Vector is new String_Vec.Vector with null record;
  function Get_Vector(Filename: String) return Vector;

end String_Helper;</lang>

Here is the implementation of String_Helper:

<lang Ada>with Ada.Strings.Fixed, Ada.Text_IO;

package body String_Helper is

  function Index(Source: String; Pattern: String) return Natural is
  begin
     return Ada.Strings.Fixed.Index(Source => Source, Pattern => Pattern);
  end Index;
  procedure Search_Brackets(Source: String;
                            Left_Bracket: String;
                            Right_Bracket: String;
                            First, Last: out Natural) is
  begin
     First := Index(Source, Left_Bracket);
     if First = 0 then
        Last := 0; -- not found
     else
        Last := Index(Source(First+1 .. Source'Last), Right_Bracket);
        if Last = 0 then
           First := 0; -- not found;
        end if;
     end if;
  end Search_Brackets;
  function Replace(Source: String; Old_Word: String; New_Word: String)
                  return String is
     L: Positive := Old_Word'Length;
     I: Natural := Index(Source, Old_Word);
  begin
     if I = 0 then
        return Source;
     else
        return Source(Source'First .. I-1) & New_Word
          & Replace(Source(I+L .. Source'Last), Old_Word, New_Word);
     end if;
  end Replace;
  function Get_Vector(Filename: String) return Vector is
     F: Ada.Text_IO.File_Type;
     T: Vector;
  begin
     Ada.Text_IO.Open(F, Ada.Text_IO.In_File, Filename);
     while not Ada.Text_IO.End_Of_File(F) loop
        T.Append(Ada.Text_IO.Get_Line(F));
     end loop;
     Ada.Text_IO.Close(F);
     return T;
  end Get_Vector;

end String_Helper;</lang>

A sample run (with the story template in t.txt):

./madlib t.txt
Replacement for <name>?
Hilla, the hypohondraic,
Replacement for <he or she>?
She
Replacement for <noun>?
headache
Hilla, the hypohondraic, went for a walk in the park. She
found a headache. Hilla, the hypohondraic, decided to take it home.

Aime

<lang aime>integer i; file f; data b; list l; record r;

f_affix(f, "/dev/stdin");

o_text("Enter the blank line terminated story:\n");

while (0 < f_b_line(f, b)) {

   l_append(l, b);

}

i = 0; while (i < l_length(l)) {

   integer p, q;
   text s, t;
   b = l_q_data(l, i);
   while ((p = b_index(b, '<')) ^ -1) {
       q = b_probe(b, p, '>');
       if (q ^ -1) {
           s = cut(b_string(b), p + 1, q - p - 1);
           b_erase(b, p, q);
           if (!r_key(r, s)) {
               o_text(cat3("Replacement for `", s, ":'\n"));
               f_line(f, t);
               r_put(r, s, t);
           }
           b_paste(b, p, r_query(r, s));
       }
   }
   i += 1;

}

while (l_length(l)) {

   o_text(b_string(l_head(l)));
   o_newline();
   l_delete(l, 0);

}</lang>

ALGOL 68

Works with: ALGOL 68G version Any - tested with release 2.8.win32

<lang algol68># Mad Libs style story generation #

  1. gets the story template from the file f. The template terminates with #
  2. a blank line #
  3. The user is then promoted for the replacements for the <word> markers #
  4. and the story is printed with the substitutions made #

PROC story = ( REF FILE f )VOID: BEGIN

   # a linked list of strings, used to hold the story template          #
   MODE STRINGLIST = STRUCT( STRING text, REF STRINGLIST next );
   # a linked list of pairs of strings, used to hold the replacements   #
   MODE REPLACEMENTLIST = STRUCT( STRING word
                                , STRING replacement
                                , REF REPLACEMENTLIST next
                                );
   # NIL reference for marking the end of a STRINGLIST                  #
   REF STRINGLIST      nil stringlist      = NIL;
   # NIL reference for marking the end of a REPLACEMENTLIST             #
   REF REPLACEMENTLIST nil replacementlist = NIL;


   # returns "text" with trailing spaces removed                        #
   OP RTRIM = ( STRING text )STRING:
   BEGIN
       INT trim pos := UPB text;
       FOR text pos FROM UPB text BY -1 TO LWB text WHILE text[ text pos ] = " "
       DO
           trim pos := text pos - 1
       OD;
       text[ LWB text : trim pos ]
   END; # RTRIM #
   # looks for word in the dictionary. If it is found, replacement is   #
   # set to its replacement and TRUE is returned. If word not present,  #
   # FALSE is returned - uses recursion                                 #
   PROC find replacement = ( STRING word
                           , REF STRING replacement
                           , REF REPLACEMENTLIST dictionary
                           )BOOL:
       IF   dictionary IS nil replacementlist
       THEN
           FALSE
       ELIF word OF dictionary = word
       THEN
           replacement := replacement OF dictionary;
           TRUE
       ELSE
           find replacement( word, replacement, next OF dictionary )
       FI; # find replacement #


   # read the story template                                            #
   # the result has a dummy element so "next OF next" is always valid   #
   REF STRINGLIST     story := HEAP STRINGLIST := ( "dummy", nil stringlist );
   REF REF STRINGLIST next  := story;
   # read the story template, terminates with a blank line              #
   WHILE
       STRING text;
       get( f, ( text, newline ) );
       text := RTRIM text;
       text /= ""
   DO
       # add the line to the end of the list #
       next := ( next OF next ) := HEAP STRINGLIST := ( text, nil stringlist )
   OD;
   # find the <word> markers in the story and replace them with the     #
   # user's chosen text - we ignore the dummy element at the start      #
   REF REPLACEMENTLIST dictionary := nil replacementlist;
   REF STRINGLIST      line       := story;
   WHILE line := next OF line;
         line ISNT nil stringlist
   DO
       # have a line of text - replace all the <word> markers in it     #
       STRING word, replacement;
       INT start pos, end pos;
       WHILE char in string( "<", start pos, text OF line )
         AND char in string( ">", end pos,   text OF line )
       DO
           # have a marker, get it from the line                        #
           word := ( text OF line )[ start pos : end pos ];
           # get its replacement                                        #
           IF NOT find replacement( word, replacement, dictionary )
           THEN
               # we don't already have a replacement for word           #
               # get one from the user and add it to the dictionary     #
               print( ( "What should replace ", word, "? " ) );
               read( ( replacement, newline ) );
               dictionary := HEAP REPLACEMENTLIST := ( word, replacement, dictionary )
           FI;
           # replace <word> with the replacement                        #
           text OF line := ( text OF line )[ : start pos - 1 ]
                         + replacement
                         + ( text OF line )[ end pos + 1 : ]
       OD
   OD;
   # print the story, ignoring the dummy element at the start           #
   line := story;
   WHILE line := next OF line;
         line ISNT nil stringlist
   DO
       print( ( text OF line, newline ) )
   OD

END; # story #

main:(

   # we read the template from stand in (the keyboard unless it's been  #
   # redirected) we could prompt the user for a template file name,     #
   # open it and read that instead...                                   #
   print( ( "Please Enter the story template terminated by a blank line", newline ) );
   story( stand in )

)</lang>

Output:
Please Enter the story template terminated by a blank line
<name> went for a walk in the park. <he or she>
found a <noun>. <name> decided to take it home.

What should replace <name>? Al
What should replace <he or she>? They
What should replace <noun>? NOTETY STYLE pack
Al went for a walk in the park. They
found a NOTETY STYLE pack. Al decided to take it home.

AutoHotkey

Like some other examples, this prompts the user for a text file template.
AutoHotkey is interestingly well suited for this task... <lang AHK>FileSelectFile, filename, , %A_ScriptDir%, Select a Mad Libs template, *.txt If ErrorLevel ExitApp ; the user canceled the file selection FileRead, contents, %filename% pos := match := 0 While pos := RegExMatch(contents, "<[^>]+>", match, pos+strLen(match)) { InputBox, repl, Mad Libs, Enter a replacement for %match%: If ErrorLevel ; cancelled inputbox ExitApp StringReplace, contents, contents, %match%, %repl%, All } MsgBox % contents</lang>

Sample Output
Han Solo went for a walk in the park. She
found a flagpole. Han Solo decided to take it home.

AWK

<lang AWK>

  1. syntax: GAWK -f MAD_LIBS.AWK

BEGIN {

   print("enter story:")

} { story_arr[++nr] = $0

   if ($0 ~ /^ *$/) {
     exit
   }
   while ($0 ~ /[<>]/) {
     L = index($0,"<")
     R = index($0,">")
     changes_arr[substr($0,L,R-L+1)] = ""
     sub(/</,"",$0)
     sub(/>/,"",$0)
   }

} END {

   PROCINFO["sorted_in"] = "@ind_str_asc"
   print("enter values for:")
   for (i in changes_arr) { # prompt for replacement values
     printf("%s ",i)
     getline rec
     sub(/ +$/,"",rec)
     changes_arr[i] = rec
   }
   printf("\nrevised story:\n")
   for (i=1; i<=nr; i++) { # print the story
     for (j in changes_arr) {
       gsub(j,changes_arr[j],story_arr[i])
     }
     printf("%s\n",story_arr[i])
   }
   exit(0)

} </lang>

output:

enter story:
<name> went for a walk in the park. <he or she>
found a <noun>. <name> decided to take it home.

enter values for:
<he or she> She
<name> Barbara
<noun> flower

revised story:
Barbara went for a walk in the park. She
found a flower. Barbara decided to take it home.

C++

<lang cpp>#include <iostream>

  1. include <string>

using namespace std;

int main() {

 string story, input;
 //Loop
 while(true)
 {
   //Get a line from the user
   getline(cin, input);
   //If it's blank, break this loop
   if(input == "\r")
     break;
   //Add the line to the story
   story += input;
 }
 //While there is a '<' in the story
 int begin;
 while((begin = story.find("<")) != string::npos)
 {
   //Get the category from between '<' and '>'
   int end = story.find(">");
   string cat = story.substr(begin + 1, end - begin - 1);
   //Ask the user for a replacement
   cout << "Give me a " << cat << ": ";
   cin >> input;
   //While there's a matching category 
   //in the story
   while((begin = story.find("<" + cat + ">")) != string::npos)
   {
     //Replace it with the user's replacement
     story.replace(begin, cat.length()+2, input);
   }
 }
 //Output the final story
 cout << endl << story;
 return 0;

}</lang>

C#

<lang csharp>using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; namespace madLibs {

   class Program {
       static void Main(string[] args) {
           string name, sex, addThis, thing;
           bool isMale = false;
           Console.Write("Enter a name: ");
           name = Console.ReadLine();
           while(isMale == false) {
               Console.Write("Is that a male or female name? [m/f] ");
               sex = Console.ReadLine().ToLower().ToCharArray()[0].ToString();
               if(sex == "m") { isMale = true; } else if(sex == "f") { break; }
           }
           if (isMale){ addThis = "He "; }else{ addThis = "She "; }
           Console.Write("Enter a thing: ");
           thing = Console.ReadLine();
           Console.WriteLine(Environment.NewLine + String.Format(("{0} went for a walk in the park. " + addThis + 
               "found a {1}. {0} decided to take it home."), name, thing));
           Console.ReadKey();
       }
   }

}</lang>

D

Translation of: Ruby

<lang d>import std.stdio, std.regex, std.algorithm, std.string, std.array;

void main() {

   writeln("Enter a story template, terminated by an empty line:");
   string story;
   while (true) {
       auto line = stdin.readln().strip();
       if (line.empty) break;
       story ~= line ~ "\n";
   }
   auto re = regex("<.+?>", "g");
   auto fields = story.match(re).map!q{a.hit}().array().sort().uniq();
   foreach (field; fields) {
       writef("Enter a value for '%s': ", field[1 .. $ - 1]);
       story = story.replace(field, stdin.readln().strip());
   }
   writeln("\nThe story becomes:\n", story);

}</lang>

Output:
Enter a story template, terminated by an empty line:
<name> went for a walk in the park. <he or she>
found a <noun>. <name> decided to take it home.

Enter a value for 'he or she': She
Enter a value for 'name': Monica
Enter a value for 'noun': cockerel

The story becomes:
Monica went for a walk in the park. She
found a cockerel. Monica decided to take it home.

Erlang

<lang erlang>-module(madlib). -compile(export_all).

main() ->

   main([]).

main([]) ->

   madlib(standard_io);

main([File]) ->

   {ok, F} = file:open(File,read),
   madlib(F).

madlib(Device) ->

   {Dict, Lines} = parse(Device),
   Substitutions = prompt(Dict),
   print(Substitutions, Lines).

prompt(Dict) ->

   Keys = dict:fetch_keys(Dict),
   lists:foldl(fun (K,D) ->
                       S = io:get_line(io_lib:format("Please name a ~s: ",[K])),
                       dict:store(K, lists:reverse(tl(lists:reverse(S))), D)
               end, Dict, Keys).

print(Dict,Lines) ->

   lists:foreach(fun (Line) ->
                         io:format("~s",[substitute(Dict,Line)])
                 end, Lines).

substitute(Dict,Line) ->

   Keys = dict:fetch_keys(Dict),
   lists:foldl(fun (K,L) ->
                       re:replace(L,K,dict:fetch(K,Dict),[global,{return,list}])
               end, Line, Keys).

parse(Device) ->

   parse(Device, dict:new(),[]).

parse(Device, Dict,Lines) ->

   case io:get_line(Device,"") of
       eof ->
           {Dict, lists:reverse(Lines)};
       "\n" ->
           {Dict, lists:reverse(Lines)};
       Line ->
           parse(Device, parse_line(Dict, Line), [Line|Lines])
   end.

parse_line(Dict, Line) ->

   {match,Matches} = re:run(Line,"<.*?>",[global,{capture,[0],list}]),
   lists:foldl(fun ([M],D) ->
                       dict:store(M,"",D)
               end, Dict, Matches).</lang>

This version can be called via either madlib:main() or madlib:main(File) to read from standard_in or from a file. It utilizes the re module to both collect and substitute the words to substitute. The dict module is used as a mapping between variables and the players desired replacement. dict acts as an immutable hash, dict:store/3 returns a new dictionary with a new or updated key.

Output: <lang erlang>68> madlib:main("test.mad"). Please name a <noun>: banana Please name a <name>: Jack Please name a <he or she>: She Jack went for a walk in the park. She found a banana. Jack decided to take it home.ok 69> </lang>

Go

Variance: The fun of Mad Libs is not knowing the story ahead of time, so instead of asking the player to enter the story template, my program asks the player to enter the file name of a story template (with contents presumably unknown to the player.) <lang go>package main

import (

   "bufio"
   "fmt"
   "io/ioutil"
   "log"
   "os"
   "regexp"
   "strings"

)

func main() {

   pat := regexp.MustCompile("<.+?>")
   if len(os.Args) != 2 {
       fmt.Println("usage: madlib <story template file>")
       return
   }
   b, err := ioutil.ReadFile(os.Args[1])
   if err != nil {
       log.Fatal(err)
   }
   tmpl := string(b)
   s := []string{}          // patterns in order of appearance
   m := map[string]string{} // mapping from patterns to replacements
   for _, p := range pat.FindAllString(tmpl, -1) {
       if _, ok := m[p]; !ok {
           m[p] = ""
           s = append(s, p)
       }
   }
   fmt.Println("Enter replacements:")
   br := bufio.NewReader(os.Stdin)
   for _, p := range s {
       for {
           fmt.Printf("%s: ", p[1:len(p)-1])
           r, isPre, err := br.ReadLine()
           if err != nil {
               log.Fatal(err)
           }
           if isPre {
               log.Fatal("you're not playing right. :P")
           }
           s := strings.TrimSpace(string(r))
           if s == "" {
               fmt.Println("  hmm?")
               continue
           }
           m[p] = s
           break
       }
   }
   fmt.Println("\nYour story:\n")
   fmt.Println(pat.ReplaceAllStringFunc(tmpl, func(p string) string {
       return m[p]
   }))

}</lang> Sample run:

Enter replacements:
character name: Wonko the Sane
third person pronoun for character: he
noun: wild weasel

Your story:

Wonko the Sane went for a walk in the park. he
found a wild weasel. Wonko the Sane decided to take it home.

Haskell

This will read a template story via stdin with no arguments, or read from a file if given as an argument. <lang Haskell>import System.IO import System.Environment import qualified Data.Map as M

getLines :: IO [String] getLines = getLines' [] >>= return . reverse

   where
     getLines' xs = do
       line <- getLine
       case line of
         [] -> return xs
         _  -> getLines' $ line:xs

prompt :: String -> IO String prompt p = putStr p >> hFlush stdout >> getLine

getKeyword :: String -> Maybe String getKeyword ('<':xs) = getKeyword' xs []

   where
     getKeyword' []        _   = Nothing
     getKeyword' (x:'>':_) acc = Just $ '<' : (reverse $ '>':x:acc)
     getKeyword' (x:xs)    acc = getKeyword' xs $ x:acc

getKeyword _ = Nothing

parseText :: String -> M.Map String String -> IO String parseText [] _ = return [] parseText line@(l:lx) keywords = do

 case getKeyword line of
   Nothing      -> parseText lx keywords >>= return . (l:)
   Just keyword -> do
     let rest = drop (length keyword) line
     case M.lookup keyword keywords of
       Nothing        -> do
                        newword <- prompt $ "Enter a word for " ++ keyword ++ ": "
                        rest'   <- parseText rest $ M.insert keyword newword keywords
                        return $ newword ++ rest'
       Just knownword -> do
                        rest' <- parseText rest keywords
                        return $ knownword ++ rest'

main :: IO () main = do

 args    <- getArgs
 nlines  <- case args of
              []    -> getLines >>= return . unlines
              arg:_ -> readFile arg
 nlines' <- parseText nlines M.empty
 putStrLn ""
 putStrLn nlines'</lang>

J

The associative array class defined here has general use. <lang J> coclass 'AA' NB. associative array

create=: verb define

empty KEYS=: DATA =: 

)

destroy=: codestroy

put=: dyad define NB. DATUM put KEY

DATA=: DATA , < x
KEYS=: KEYS , < y
EMPTY

)

get=: verb define NB. get KEY

(KEYS (i. <) y) {:: DATA

)

cocurrent'base'

get =: dyad define NB. OBJECT get KEY

try.
 get__x y
catch.
 smoutput '?' ,~ ": y
 RV =. 1!:1<1
 RV put__x y
 RV
end.

)

STORY =: 0 :0 <name> went for a walk in the park. <he or she> found a <noun>. <name> decided to take it home. )

madlib =: verb define NB. madlib STORY

I =. y i. '<'
if. I = # y do. y return. end.         NB. no substitutions
HEAD =. I {. y
TAIL =. I }. y
A =. }. (<;.1~ '<'&=) '<' , TAIL  NB. the story is parsed by '<'
B =. (({. ; }.)~ >:@:i.&'>')&> A
NB.+-----------+------------------------------+
NB.|<name>     | went for a walk in the park. |
NB.+-----------+------------------------------+
NB.|<he or she>| found a                      |
NB.+-----------+------------------------------+
NB.|<noun>     |.                             |
NB.+-----------+------------------------------+
NB.|<name>     | decided to take it home.     |
NB.+-----------+------------------------------+
AA =. conew'AA'
create__AA
SUBSTITUTIONS =. AA&get&.> _ 1 {. B
codestroy__AA
HEAD , ; SUBSTITUTIONS ,. 0 1 }. B

) </lang>

   madlib STORY
<name>?
Dave
<he or she>?
he
<noun>?
9
Dave went for a walk in the park. he
found a 9. Dave decided to take it home.

Java

This is extremely messy code. There's bound to be a more optimal way of doing this.

<lang Java>import java.util.Map; import java.util.HashMap; import java.util.Scanner; import java.util.StringTokenizer;

public class MadLibs { public static void main(String[] args) { Scanner s=new Scanner(System.in); String line; StringBuffer storybuffer=new StringBuffer();

//Accept lines until empty line is entered while(!(line=s.nextLine()).isEmpty()) storybuffer.append(" "+line);

//Remove first space storybuffer.delete(0, 1); String story=storybuffer.toString(); //Split StringTokenizer str=new StringTokenizer(story); String word; StringBuffer finalstory=new StringBuffer();

//Store added elements Map<String,String> hash=new HashMap<String,String>(); while(str.hasMoreTokens()) { word=str.nextToken(); if(word.contains("<")) { String add=""; //Element prompt could be more than one word if(!word.contains(">")) { //Build multi-word prompt String phrase=""; do{ phrase+=word+" "; }while(!(word=str.nextToken()).contains(">")); word=phrase+word; } //Account for element placeholder being immediately followed by . or , or whatever. if(word.charAt(word.length()-1)!='>') add=word.substring(word.lastIndexOf('>')+1);

//Store id of element in hash table String id=word.substring(0,word.lastIndexOf('>')+1); String value;

if(!hash.containsKey(id)) { //New element System.out.println("Enter a "+ id); value=s.nextLine()+add; hash.put(id, value); } //Previously entered element else value=hash.get(id); word=value; } finalstory.append(word+" "); } System.out.println(finalstory.toString()); s.close(); } }</lang>

Output:

<name> went for a walk in the park. <he or she> 
found a <noun>. <name> decided to take it home.

Enter a <name>
Champak
Enter a <he or she>
He
Enter a <noun>
hippo

Champak went for a walk in the park. He found a hippo. Champak decided to take it home. 

Icon and Unicon

This just runs with the sample. It would be much more fun with a database of randomly selected story templates. <lang Icon>procedure main()

  ml := "<name> went for a walk in the park. There <he or she> _
         found a <noun>. <name> decided to take it home."  # sample
  MadLib(ml)                                               # run it

end

link strings

procedure MadLib(story)

  write("Please provide words for the following:")
  V := []
  story ? while ( tab(upto('<')), put(V,tab(upto('>')+1)) )
  every writes(v := !set(V)," : ") do 
     story := replace(story,v,read())
  write("\nYour MadLib follows:\n",story)

end</lang>

strings.icn provides replace

Sample output:

Please provide words for the following:
<noun> : keys
<he or she> : she
<name> : Goldilocks

Your MadLib follows:
Goldilocks went for a walk in the park. There she found a keys. Goldilocks decided to take it home.

Liberty BASIC

<lang lb>temp$="<name> went for a walk in the park. <he or she> found a <noun>. <name> decided to take it home." k = instr(temp$,"<") while k

   replace$   = mid$(temp$,k,instr(temp$,">")-k + 1)
   print "Replace:";replace$;" with:"; :input with$
   while k
       temp$  = left$(temp$,k-1) + with$ + mid$(temp$,k + len(replace$))
       k      = instr(temp$,replace$,k)
   wend

k = instr(temp$,"<") wend print temp$ wait </lang>

Mathematica / Wolfram Language

Like some other examples, this prompts the user for a filename for a story template rather than reading the story in from user input. <lang Mathematica>filename = InputString["Enter the filename of the story template: "]; text = Import[filename]; listofblanks = StringCases[text, RegularExpression["<[^>]+>"]] // Union; listofanswers = {}; Do[

answer = InputString["Enter a/an: " <> listofblanksi];
AppendTo[listofanswers, answer];
, {i, 1, Length[listofblanks]}
]

Do[

text = StringReplace[text, listofblanksi -> listofanswersi]
, {i, 1, Length[listofblanks]}
]

text</lang>

Sample Output
George went for a walk in the park. he
found a car. George decided to take it home.

Nimrod

Translation of: Python

<lang nimrod>import rdstdin, re, algorithm, sequtils, strutils

  1. let templ = readLineFromStdin "Enter your story: "

const templ = """<name> went for a walk in the park. <he or she> found a <noun>. <name> decided to take it home."""

echo "The story template is:\n", templ var fields = templ.findAll re"<[^>]+>" fields.sort(cmp) fields = deduplicate fields let values = readLineFromStdin("\nInput a comma-separated list of words to replace the following items\n " & fields.join(",") & ": ").split(",")

var story = templ for f,v in zip(fields, values).items:

 story = story.replace(f, v)

echo "\nThe story becomes:\n\n", story</lang> Sample run:

The story template is:
<name> went for a walk in the park. <he or she>
found a <noun>. <name> decided to take it home.

Input a comma-separated list of words to replace the following items
  <he or she>,<name>,<noun>: She,Monica L.,cockerel

The story becomes:

Monica L. went for a walk in the park. She
found a cockerel. Monica L. decided to take it home.

Perl

Use the name of the file with a story as the parameter to the programme. <lang perl>#!/usr/bin/perl use warnings; use strict;

my $template = shift; open my $IN, '<', $template or die $!; my $story = do { local $/ ; <$IN> };

my %blanks; undef $blanks{$_} for $story =~ m/<(.*?)>/g;

for my $blank (sort keys %blanks) {

   print "$blank: ";
   chomp (my $replacement = <>);
   $blanks{$blank} = $replacement;

}

$story =~ s/<(.*?)>/$blanks{$1}/g; print $story;</lang>

Perl 6

<lang perl6>my $story = slurp(@*ARGS.shift); my %words; $story.=subst(/ '<' (.*?) '>' /, { %words{$0} //= prompt "$0? " }, :g ); say $story;</lang> Sample run:

$ madlibs walk
name? Phydeaux
He or She? She
noun? flea
Phydeaux went for a walk in the park. She
found a flea. Phydeaux decided to take it home.

Pike

this solution uses readline to make editing more convenient. <lang Pike>#!/usr/bin/pike

Stdio.Readline readln = Stdio.Readline();

void print_help() {

   write(#"Write a Story.

Names or objects in the story can be made variable by referencing them as <person> <object>, etc. End the story with an empty line.

Type show to read the story. You will be asked to fill the variables, and the the story will be shown.

Type help to see this message again. Type exit to quit.

"); }

void add_line(string input) {

   array variables = parse_for_variables(input);
   write("Found variables: %{\"%s\" %}\n", variables);
   story += input+"\n";

}

array parse_for_variables(string input) {

   array vars = Array.flatten(array_sscanf(input, "%*[^<>]%{<%[^<>]>%*[^<>]%}%*[^<>]"));
   return Array.uniq(vars);

}

mapping fill_variables(string story) {

   array vars = parse_for_variables(story);
   mapping variables = ([]);
   foreach(vars;; string name)
   {
       string value = readln->read(sprintf("Please name a%s %s: ", (<'a','e','i','o','u'>)[name[1]]?"":"n", name));
       if (value != "")
           variables["<"+name+">"] = value;
   }
   return variables;

}

void show_story(string story) {

   mapping variables = fill_variables(story);
   write("\n"+replace(story, variables));

}

void do_exit() {

   exit(0);

}

mapping functions = ([ "help":print_help,

                      "show":show_story,
                      "exit":do_exit,
                    ]);

string story = "";

void main() {

   Stdio.Readline.History readline_history = Stdio.Readline.History(512);
   readln->enable_history(readline_history);

   string prompt="> ";

   print_help();
   while(1)
   {
       string input=readln->read(prompt);
       if(!input)
           exit(0);
       if(input == "")
           show_story(story);
       else if (functions[input])
           functions[input]();
       else add_line(input);
   }

}</lang>

Sample output:

Write a Story.

Names or objects in the story can be made variable by 
referencing them as <person> <object>, etc.
End the story with an empty line.

Type show to read the story. You will be asked to fill the variables, 
and the the story will be shown.

Type help to see this message again.
Type exit to quit.

> <person> is a programmer.
Found variables: "person" 
> <he or she> created <website> for all of us to enjoy.
Found variables: "he or she" "website" 
> 
Please name a person: Michael
Please name a he or she: he
Please name a website: RosettaCode

Michael is a programmer.
he created RosettaCode for all of us to enjoy.
> 
Please name a person: Guilaumme
Please name a he or she: he
Please name a website: PLEAC

Guilaumme is a programmer.
he created PLEAC for all of us to enjoy.
> exit

PicoLisp

This function extends the syntax a bit to be able to express different words with the same description, the syntax is <name:description>, if the description is omitted the name is used instead, keeping backwards compatibility with the syntax used in the task description: <lang PicoLisp>(de madlib (Template)

  (setq Template (split (chop Template) "<" ">"))
  (let (Reps () Text ())
     (while Template
        (push 'Text (pop 'Template))
        (let? Rep (mapcar pack (split (pop 'Template) ":"))
           (if (assoc (car Rep) Reps)
              (push 'Text (cdr @))
              (until (and
                        (prin "Gimme a(n) " (or (cadr Rep) (car Rep)) ": ")
                        (clip (in NIL (line)))
                        (push 'Text @)
                        (push 'Reps (cons (car Rep) @)) )
                 (prinl "Huh? I got nothing.") ) ) ) )
     (prinl (need 30 '-))
     (prinl (flip Text)) ) )

</lang>

This runs the example:

(madlib
   "<name> went for a walk in the park. <he or she>
found a <noun>. <name> decided to take it home." )

Gimme a(n) name: Mr. T
Gimme a(n) he or she: he   
Gimme a(n) noun: fool
------------------------------
Mr. T went for a walk in the park. he
found a fool. Mr. T decided to take it home.

Example with the extended syntax:

(madlib
   "<1:name> went for a walk in the park. <2:he or she>
found a <3:noun (singular)>. <1> decided to take it home.

On <4:his or her> way home, <2> found a <5:noun (singular)>,
two <7:noun (plural)> and a <8:noun (singular)>." )

Gimme a(n) name: MC Hammer
Gimme a(n) he or she: he       
Gimme a(n) noun (singular): pair of baggy pants 
Gimme a(n) his or her: his                
Gimme a(n) noun (singular): hammer
Gimme a(n) noun (plural): STOP signs
Gimme a(n) noun (singular): clock     
------------------------------
MC Hammer went for a walk in the park. he
found a pair of baggy pants. MC Hammer decided to take it home.

On his way home, he found a hammer,
two STOP signs and a clock.

PL/I

<lang PL/I>(stringrange, stringsize): /* 2 Nov. 2013 */ Mad_Libs: procedure options (main);

  declare (line, left, right) character (100) varying;
  declare true bit(1) value ('1'b), false bit (1) value ('0'b);
  declare name    character (20) varying, seen_name    bit (1) initial (false);
  declare pronoun character (20) varying, seen_pronoun bit (1) initial (false);
  declare noun    character (20) varying, seen_noun    bit (1) initial (false);
  declare replaced_all bit (1);
  declare in file input;
  open file (in) title ('/MADLIBS.DAT,type(text),recsize(100)');
  do forever;
     get file (in) edit (line) (L);
     if line =  then leave;
     do until (replaced_all);
        replaced_all = true;
        if index(line, '<name>') > 0 then
           if seen_name then
              do until (index(line, '<name>') = 0);
                 call split(line, '<name>', left, right);
                 line = left || name || right;
                 replaced_all = false;
              end;
           else
              do;
                 put skip list ('Please type a name:');
                 get edit (name) (L);
                 seen_name = true; replaced_all = false;
              end;
        if index(line, '<he or she>') > 0 then
           if seen_pronoun then
              do until (index(line, '<he or she>') = 0);
                 call split(line, '<he or she>', left, right);
                 line = left || pronoun || right;
                 replaced_all = false;
              end;
           else
              do;
                  put skip list ('Please type a pronoun (he or she):');
                  get edit (pronoun) (L);
                  seen_pronoun = true; replaced_all = false;
              end;
        if index(line, '<noun>') > 0 then
           if seen_noun then
              do until (index(line, '<noun>') = 0);
                 call split(line, '<noun>', left, right);
                 line = left || noun || right;
                 replaced_all = false;
              end;
           else
              do;
                 put skip list ('Please type a noun:');
                 get edit (noun) (L);
                 seen_noun = true; replaced_all = false;
              end;
        end;
     put skip list (line);
  end;

split: procedure (line, text, Left, Right);

  declare (line, text, left, right) character (*) varying;
  declare i fixed binary;
  i = index(line, text);
  left  = substr(line, 1, i-1);
  right = substr(line, i+length(text), length(line) - (i + length(text)) + 1 );

end split;

end Mad_Libs;</lang>

Please type a name: 
Please type a pronoun (he or she): 
Please type a noun: 

John went for a walk in the park. he 
found a dog. John decided to take it home. 

Python

<lang python>import re

  1. Optional Python 2.x compatibility
  2. try: input = raw_input
  3. except: pass

template = <name> went for a walk in the park. <he or she> found a <noun>. <name> decided to take it home.

def madlibs(template):

   print('The story template is:\n' + template)
   fields = sorted(set( re.findall('<[^>]+>', template) ))
   values = input('\nInput a comma-separated list of words to replace the following items'
                  '\n  %s: ' % ','.join(fields)).split(',')
   story = template
   for f,v in zip(fields, values):
       story = story.replace(f, v)
   print('\nThe story becomes:\n\n' + story)

madlibs(template)</lang>

Sample output
The story template is:
<name> went for a walk in the park. <he or she>
found a <noun>. <name> decided to take it home.

Input a comma-separated list of words to replace the following items
  <he or she>,<name>,<noun>: She,Monica L.,cockerel

The story becomes:

Monica L. went for a walk in the park. She
found a cockerel. Monica L. decided to take it home.

Racket

Instead of writing the story in the console, it reads from a file given by the player, this is mainly to keep surprise about the final text <lang Racket>(define (get-mad-libs file)

 (with-input-from-file file
   (lambda ()
     (for/fold ((text ""))
       ((line (in-lines)))
       (string-append text line "\n")))))


(define (replace-context mad-libs)

 (define matches
   (regexp-match* #rx"<[a-zA-Z0-9 ]*>" mad-libs))
 (map 
  (lambda (context)
    (display (format "~a?" context))
    (cons context (read-line)))
  (remove-duplicates matches)))

(define (play-mad-libs)

 (display "Tell me a file to play Mad Libs: ")
 (define text (get-mad-libs (read-line)))
 (define matches (replace-context text))
 
 (display
  (for/fold ((mad-story text))
    ((change (in-list matches)))
    (string-replace mad-story (car change) (cdr change)))))

(play-mad-libs)</lang>

Output with the story from this page

Tell me a file to play Mad Libs: mad lib.txt
<name>?Don Quixote
<he or she>?he
<noun>?Windmill
Don Quixote went for a walk in the park. he
found a Windmill. Don Quixote decided to take it home

REBOL

<lang rebol> t: {<name> went for a walk in the park. <he or she> found a <noun>. <name> decided to take it home.} view layout [a: area wrap t btn "Done" [x: a/text unview]] w: copy [] parse x [any [to "<" copy brackets thru ">" (append w brackets)] to end] w: unique w foreach i w [replace/all x i ask join i ": "] alert x </lang>


==REXX==r <lang rexx>/*REXX program prompts user for a template substitutions within a story.*/ @.=;  !.=0; #=0; @= /*assign some defaults. */ parse arg iFID . /*allow use to specify input file*/ if iFID== then iFID="MAD_LIBS.TXT" /*Not specified? Use a default.*/

  do recs=1  while  lines(iFID)\==0   /*read the input file 'til done. */
  @.recs=linein(iFID);   @=@ @.recs   /*read a record, append it to  @ */
  if @.recs=  then leave            /*Read a blank line?  We're done.*/
  end  /*recs*/

recs=recs-1 /*adjust for E─O─F or blank line.*/

    do  forever                       /*look for templates in the text.*/
    parse var  @   '<'   ?   '>'   @  /*scan for  <ααα>  stuff in text.*/
    if ?=   then leave              /*if no   ααα,  then we're done. */
    if !.?    then iterate            /*already asked?   Keep scanning.*/
    !.?=1                             /*mark this   ααα   as  "found". */
           do  forever                /*prompt user for a replacement. */
           say '─────────── please enter a word or phrase to replace: ' ?
           parse pull ans;         if ans\=  then leave
           end   /*forever*/
    #=#+1                             /*bump the template counter.     */
    old.# = '<'?">";    new.# = ans   /*assign "old" name & "new" name.*/
    end   /*forever*/

say; say copies('═',79) /*display a blank and a fence. */

    do m=1  for recs                  /*display the text, line for line*/
                      do n=1  for #   /*perform substitutions in text. */
                      @.m = changestr(old.n, @.m, new.n)
                      end   /*n*/
    say @.m                           /*display (new) substituted text.*/
    end   /*m*/

say copies('═',79) /*display a final (output) fence.*/

                                      /*stick a fork in it, we're done.*/</lang>

Some older REXXes don't have a   changestr   bif, so one is included here ──►   CHANGESTR.REX.

output when using the default input fileID

─────────── please enter a word or phrase to replace:  name
Mary
─────────── please enter a word or phrase to replace:  he or she
she
─────────── please enter a word or phrase to replace:  noun
little lamb

═══════════════════════════════════════════════════════════════════════════════
Mary went for a walk in the park. she
found a little lamb.  Mary decided to take it home.
═══════════════════════════════════════════════════════════════════════════════

Ruby

<lang ruby>puts "Enter a story, terminated by an empty line:" story = "" until (line = gets).chomp.empty?

 story << line

end

story.scan(/(?<=[<]).+?(?=[>])/).uniq.each do |var|

 print "Enter a value for '#{var}': "
 story.gsub!(/<#{var}>/, gets.chomp)

end

puts puts story</lang>

Example

Enter a story, terminated by an empty line:
<name> went for a walk in the park. <he or she>
found a <noun>. <name> decided to take it home.

Enter a value for 'name': FOO
Enter a value for 'he or she': BAR
Enter a value for 'noun': BAZ

FOO went for a walk in the park. BAR
found a BAZ. FOO decided to take it home.

Run BASIC

<lang runbasic>temp$="<name> went for a walk in the park. <he or she> found a <noun>. <name> decided to take it home." k = instr(temp$,"<") while k

   replace$   = mid$(temp$,k,instr(temp$,">")-k + 1)
   print "Replace:";replace$;" with:"; :input with$
   while k
       temp$  = left$(temp$,k-1) + with$ + mid$(temp$,k + len(replace$))
       k      = instr(temp$,replace$,k)
   wend 

k = instr(temp$,"<") wend print temp$ wait</lang> Output:

Replace:<name> with:?Fred
Replace:<he or she> with:?he
Replace:<noun> with:?cat
Fred went for a walk in the park. he found a cat. Fred decided to take it home.

Scala

<lang Scala>object MadLibs extends App{

 val input = "<name> went for a walk in the park. <he or she>\nfound a <noun>. <name> decided to take it home."
 println(input)
 println
 val todo = "(<[^>]+>)".r
 val replacements = todo.findAllIn(input).toSet.map{found: String =>
   found -> readLine(s"Enter a $found ")
 }.toMap
 val output = todo.replaceAllIn(input, found => replacements(found.matched))
 println
 println(output)

}</lang> Output:

<name> went for a walk in the park. <he or she>
found a <noun>. <name> decided to take it home.

Enter a <name> Foo
Enter a <he or she> He
Enter a <noun> bar

Foo went for a walk in the park. He
found a bar. Foo decided to take it home.

Seed7

<lang seed7>$ include "seed7_05.s7i";

const proc: main is func

 local
   var string: story is "";
   var string: line is "";
   var integer: pos1 is 0;
   var integer: pos2 is 1;
   var string: field is "";
 begin
   writeln("Enter a story template, terminated by an empty line:");
   repeat
     readln(line);
     if line <> "" then
       story &:= line & "\n";
     end if;
   until line = "";
   pos1 := pos(story, '<');
   while pos1 <> 0 and pos2 <> 0 do
     pos2 := pos(story, '>', pos1);
     if pos2 <> 0 then
       field := story[pos1 .. pos2];
       write("Enter a value for " <& field <& ": ");
       story := replace(story, field, getln(IN));
       pos1 := pos(story, '<', pos1);
     end if;
   end while;
   writeln;
   writeln("The story becomes:");
   write(story);
 end func;</lang>
Output:
Enter a story template, terminated by an empty line:
<name> went for a walk in the park. <he or she>
found a <noun>. <name> decided to take it home.

Enter a value for <name>: Katharine
Enter a value for <he or she>: she
Enter a value for <noun>: goldbar

The story becomes:
Katharine went for a walk in the park. she
found a goldbar. Katharine decided to take it home.

Tcl

<lang tcl>package require Tcl 8.5

  1. Read the template...

puts [string repeat "-" 70] puts "Enter the story template, ending with a blank line" while {[gets stdin line] > 0} {

   append content $line "\n"

}

  1. Read the mapping...

puts [string repeat "-" 70] set mapping {} foreach piece [regexp -all -inline {<[^>]+>} $content] {

   if {[dict exists $mapping $piece]} continue
   puts -nonewline "Give me a $piece: "
   flush stdout
   dict set mapping $piece [gets stdin]

}

  1. Apply the mapping and print...

puts [string repeat "-" 70] puts -nonewline [string map $mapping $content] puts [string repeat "-" 70]</lang> Sample session:

----------------------------------------------------------------------
Enter the story template, ending with a blank line
<name> went for a walk in the park. <he or she>
found a <noun>. <name> decided to take it home.

----------------------------------------------------------------------
Give me a <name>: Wonko the Sane
Give me a <he or she>: He
Give me a <noun>: wild weasel
----------------------------------------------------------------------
Wonko the Sane went for a walk in the park. He
found a wild weasel. Wonko the Sane decided to take it home.
----------------------------------------------------------------------