TL;DR: use the top level to interactively (and iteratively) write/test/debug a rule. Esp. if you are just parsing, don’t worry about generating output just yet. It is strictly more difficult IMHO.
A trick I saw just a couple of days back here is to use portray_text/1
. It is hiding here: https://www.swi-prolog.org/pldoc/doc/_SWI_/library/portray_text.pl
Here is what happens:
Welcome to SWI-Prolog (threaded, 64 bits, version 8.3.14-26-g81109f540)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.
For online help and background, visit https://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).
?- X = `foo`.
X = [102, 111, 111].
?- portray_text(true).
true.
?- X = `foo`.
X = `foo`.
This addresses a part of your question.
“How to develop DCGs”… well, this is a bigger topic. I am sure there is a lot of personal preferences here. I do prefer a bottom up approach. If I look at the input you are trying to parse, I would probably first define a DCG that parses a line (including the newline at the end), like this:
password_rule(policy_password(Lower, Upper, Char, Password)) -->
integer(Lower), "-", integer(Upper), " ", [Char], ": ",
nonblanks(Password),
"\n".
In order to write that, if I can’t just type it in straight away, I would usually start from the beginning, using the 3-argument version of phrase to see what is left to parse. I would do this directly on the top level. So here is what my interactive session might look like:
Welcome to SWI-Prolog (threaded, 64 bits, version 8.3.14-26-g81109f540)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.
For online help and background, visit https://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).
?- portray_text(true).
true.
?- use_module(library(dcg/basics)).
true.
?- phrase(integer(Lower), `1-3 b: cdefg`, Rest).
Lower = 1,
Rest = `-3 b: cdefg`.
?- phrase(( integer(Lower), "-", integer(Upper), ": " ), `1-3 b: cdefg`, Rest).
false. % WAT?
?- phrase(( integer(Lower), "-", integer(Upper), ":" ), `1-3 b: cdefg`, Rest).
false. % WAT? Oh yeah I forgot the letter before the colon, silly me
?- phrase(( integer(Lower), "-", integer(Upper) ), `1-3 b: cdefg`, Rest).
Lower = 1,
Upper = 3,
Rest = ` b: cdefg`.
?- phrase(( integer(Lower), "-", integer(Upper), " ", [Char], ": " ), `1-3 b: cdefg`, Rest).
Lower = 1,
Upper = 3,
Char = 98,
Rest = `cdefg`.
?- phrase(( integer(Lower), "-", integer(Upper), " ", [Char], ": ", nonblanks(Password) ), `1-3 b: cdefg`, Rest).
Lower = 1,
Upper = 3,
Char = 98,
Password = `cdefg`,
Rest = [].
Great, seems I am done with one line. I just need to remember to add the newline for the complete rule.
Now, to parse the whole file, I would need to just parse a sequence of lines. An “idiom” that you can find in the implementation of library(dcg/basics) would look like this:
all_rules([R|Rules]) -->
password_rule(R),
!,
all_rules(Rules).
all_rules([]) --> [].
Now you can use phrase_from_file/2 like this:
parse_rules(File, Rules) :-
phrase_from_file(all_rules(Rules), File).
With the example I found on the website, I get:
?- parse_rules("d02-example.input", Rules).
Rules = [policy_password(1, 3, 97, `abcde`),
policy_password(1, 3, 98, `cdefg`),
policy_password(2, 9, 99, `ccccccccc`)
].