Yes-no Questions, and Others -- library(ask)

The file library(ask) defines a set of commands for asking questions whose answer is a single character, and for asking for file names (see lib-ofi-oth-ask).

library(ask) uses several commands from library(prompt), but if you want to use them in your program you should explicitly include the directive

     :- ensure_loaded(library(prompt)).
     

in your program. The principal such command is prompt/1, which is used to print the question or prompt.


yesno(+Question)
writes Question (using write/1) to the terminal, regardless of the current output stream, and reads an answer. The prompt is followed by ? , so you should not put a question mark in the question yourself. The answer is the first character typed in response; anything following on the same line will be thrown away. If the answer is y or Y, yesno/1 succeeds. If the answer is n or N, yesno/1 fails. Otherwise it repeats the question. The user has to explicitly type a y or n before it will stop. Because the rest of the line is thrown away, the user can type yes, Yes, You'd better not, and so forth with exactly the same effect as a plain y. If the user just presses <RET>, that is not taken as yes.
yesno(+Question, +Default)
is like yesno/1 except that

For example,

          yesno('Do you want an extended trace', yes)
          

prints

          Do you want an extended trace [y]? _
          

and leaves the terminal's cursor where the underscore is. If the user presses <RET>, this call to yesno/1 will succeed. If the user answers yes it will succeed. If the user answers no it will fail. If the first non-layout character of the user's answer is neither n, N, y, nor Y, the question will be repeated.

ask(+Question, -Answer)
writes Question to the terminal as yesno/1 would, and reads a single character Answer. Answer must be a "graphic" character (a printing character other than space). ask/2 will continue asking until it is given such a character. The remainder of the input line will be thrown away.
ask(+Question, +Default, -Answer)
uses Default as the default character the way that yesno/2 does, and mentions the default in brackets just before the question mark. If the user presses carriage return, Default will be returned as his Answer. Answer can be instantiated, in which case the call to ask/2 or ask/3 will fail if the user does not give that answer. For example, yesno/2 could (almost) have been defined as
          yesno(Question, Default) :-
                  ask(Question, Default, 0'y).
          

ask_chars(+Prompt, +MinLength, +MaxLength, -Answer)
writes Prompt to the terminal, and reads a line of characters from it. This response must contain between MinLength and MaxLength characters inclusive, otherwise the question will be repeated until an answer of satisfactory length is obtained. Leading and/or trailing layout characters are retained in the result, and are counted when determining the length of the answer. The list of character codes read is unified with Answer. Note that a colon and a space (: ) are added to the Prompt, so don't add such punctuation yourself. The end-user can find out what sort of input is required by typing a line that starts with a question mark. Therefore it is not possible to read such a line as data. See prompted_line/2 in library(prompt).

Examples:

          | ?- ask_chars('Label', 1, 8, Answer).
          Label: 213456789
          Please enter between 1 and 8 characters.
          Do not add a full stop unless it is part of the answer.
          Label: four
          
          Answer = "four"
          
          | ?- ask_chars('Heading', 1, 30, Answer).
          Heading: ?
          Please enter between 1 and 30 characters.
          Do not add a full stop unless it is part of the answer.
          Heading:    three leading spaces
          
          Answer = "   three leading spaces"
          

ask_number(+Prompt, +Default, -Answer)
writes Prompt on the terminal, and reads a line from it in response. If, after "garbage" characters are thrown away, the line read represents a Prolog number, that number is unified with Answer. The "garbage" characters that are thrown away are layout characters (including spaces and tabs), underscores _, and plus signs +. For example, the input + 123_456 would be treated as if the user had typed 123456. The conversion is done by number_chars/2. If the user entered an integer, Answer will be unified with an integer. If the user entered a floating-point number, Answer will be unified with a floating-point number. No conversion is done. If the line contains only "garbage" characters and there is a Default argument, Answer is unified with Default. This happens regardless of whether or not Default is a number. If the input is unacceptable, the question will be repeated after an explanation of what is expected. The user can type ? for help. Examples:
          | ?- ask_number('Pick a number', X).
          Pick a number: ?
          Please enter a number followed by RETURN
          Pick a number: 27
          
          X = 27
          
          | ?- ask_number('Say cheese', X).
          Say cheese:
          Please enter a number followed by RETURN
          Say cheese: 3 . 141 _ 593
          
          X = 3.14159
          
          | ?- ask_number('Your guess', '100%', X).
          Your guess [100%]: 38.
          Please enter a number followed by RETURN
          Your guess [100%]: 38
          
          X = 38
          
          | ?- ask_number('Your guess', '100%', X).
          Your guess [100%]: <RET>
          
          X = '100%'
          

ask_number(+Prompt, +Lower, +Upper[, +Default], -Answer)
These two predicates are a combination of ask_between/[4,5] and ask_number/[2,3]. They write the prompt to the terminal, read a line from it in response, throw away "garbage" characters, try to parse the result as a number, and check that it is between the Lower and Upper bounds. Lower and Upper may severally be integers or floating point numbers. Answer will be unified with an integer if the user typed an integer, with a floating-point number if the user typed a floating-point number, or with whatever Default happens to be if there is a Default and the user entered an empty line. If you want a floating-point result whatever the user typed, you will have to do your own conversion with is/2. Examples:
          | ?- ask_number('Enter temperature in Fahrenheit',
                           32.0, 212.0, 77.0, Temp).
          Enter temperature in Fahrenheit [77.0]: 10
          Please enter a number between 32.0 and 212.0 followed
          by RETURN
          Enter temperature in Fahrenheit [77.0]: 68
          
          Temp = 68
          

ask_file(+Question, -Filename)
same as ask_file/3.
ask_file(+Question, +Mode, -FileName)
writes Question to the terminal and reads a filename from the terminal, regardless of the current I/O streams. If the user presses <RET>, ask_file/3 just fails; an empty filename is taken as an indication that the user has finished entering file names. A reply beginning with a question mark will cause a brief help message to be printed (explaining that a filename is wanted, and how to enter one), and the question will be repeated. Otherwise, ask_file/3 checks that the file can be opened in the mode specified by Mode (read, write, or append). If it is not possible to open the file in mode Mode, the operating system's error result is reported and the question is repeated. If it is possible to open the file in this mode, the name of the file is returned as FileName. However, ask_file/3 does not open the file for you, it simply checks that it is possible to open the file. Here is an example "dialogue":
          | ?- ask_file('Where should the cross-reference go? ',
               write, File).
          Where should the cross-reference go? ?
          Please enter the name of a file that can be opened
          in write mode, followed by <RET>.  To end this
          operation, just type <RET> with no filename.
          Where should the cross-reference go? call.pl
          
          ! Permission error: cannot access file 'call.pl'
          ! O/S error : Permission denied
          ! goal:  can_open_file('call.pl',write,warn)
          
          | ?- ask_file('Where should the cross-reference go? ',
               write, File).
          Where should the cross-reference go? call.xref
          
          File = 'call.xref'
          
          | ?- ask_file('Next file: ', read, File).
          Next file: call.pl
          
          ! Permission error: cannot access file 'call.pl'
          ! O/S error : Permission denied
          ! goal:  can_open_file('call.pl',read,warn)
          
          | ?- ask_file('Next file: ', read, File).
          Next file: call.xref
          
          ! Existence error in can_open_file/3
          ! file call.xref does not exist
          ! O/S error : No such file or directory
          ! goal:  can_open_file('call.xref',read,warn)
          

Points to note:


ask_between(+Prompt, +Lower, +Upper[, +Default], -Answer)
writes Prompt on the terminal, and reads a line in response. If the line read represents a Prolog integer between Lower and Upper inclusive, this line is unified with Answer. The line may contain only digits and perhaps a leading minus sign. If the line is empty and there is a Default argument, Answer is unified with Default. This happens regardless of whether Default is an integer or in the indicated range. If the answer read is not acceptable, the user is told what sort of answer is wanted and is prompted again. For example, after defining
          p(X) :-
             ask_between('Number of samples',1,20,
                          [none],X),
             integer(X).
          

the following conversation might take place.

          | ?- p(X).
          Number of samples [none]: ?
          Please enter an integer between 1 and 20
          Do not add a full stop.
          Number of samples [none]: 0
          Please enter an integer between 1 and 20
          Do not add a full stop.
          Number of samples [none]: 9
          
          X = 9
          
          | ?- p(X).
          Number of samples [none]: <RET>
          no
          

The prompt that is printed is Prompt [Default]: if there is a Default argument, Prompt: otherwise, so that you can use the same prompt whether or not there is a default argument.

ask_oneof(+Prompt, +Constants[, +Default], -Answer)

prints Prompt on the terminal, and reads a line in response. Constants should be a list of constants (terms that are acceptable as the first argument of name/2). If the user's response is the full name of one of the constants, Answer is unified with that constant. Failing that, if the user's response is a prefix of exactly one of the constants, Answer is unified with that constant. If the response is just <RET>, and there is a Default argument, Answer is unified with Default (which need not be a constant, nor need it be an element of Constants). If nothing else works, the user is told what sort of response is wanted, and is prompted again.

The prompt that is printed is Prompt [Default]: if there is a Default argument, Prompt: otherwise, so that you can use the same prompt whether or not there is a default argument.

You should find it straightforward to define your own simple queries using this kit. As a general rule, try to arrange things so that if the user types a question mark s/he is told what sort of response is wanted. All the queries defined in this section do that.

The commands for reading English sentences do nothing special when their input is a single question mark. Here is an example of how you can build a query from them that does something sensible in this case.

     ask_sentence(Prompt, Sentence) :-
         repeat,
             prompt(Prompt),
             read_in(X),
             (   X = [?] ->
                 format(user_output,
                     'Please enter an English sentence.~n', []),
                 fail
             ;   true
             ),
         !,
         Sentence = X.