I'm trying to write a getline predicate in Prolog that will grab an entire line. I'm running into problems when trying to use sformat to append strings:
getline(_, Str, 10) :-
format('NL. String: ~s\n', [Str]).
getline(_, Str, -1) :-
format('EOF. String: ~s\n', [Str]).
getline(InStr, Str, Cd) :-
sformat(NewString, '~s~c', [Str, Cd]),
get_code(InStr, C),
getline(InStr, NewString, C).
getline(InStr, Str) :-
get_code(InStr, C),
getline(InStr, Str, C).
test(InFile) :-
open(InFile, read, InStr),
getline(InStr, Line).
running the goal test("in.txt"). with the above gets me this error:
ERROR: Prolog initialisation failed:
ERROR: format/3: Illegal argument to format sequence ~s: _G940
It seems like _G940 is some sort of pointer, but I'm not sure where to go from there. Any help would be appreciated!
EDIT: Now it kinda works (i.e. no errors), but still leaves the problem of getting the complete string back up to the caller without causing the same problem again:
getline(_, Strn, 10) :-
format('NL. String: ~s\n', [Strn]).
getline(_, Strn, -1) :-
format('EOF. String: ~s\n', [Strn]).
getline(InStrm, Strn, Cd) :-
format(string(NewString), '~s~c', [Strn, Cd]),
get_code(InStrm, C),
getline(InStrm, NewString, C).
getline(InStrm) :-
get_code(InStrm, Cd),
getline(InStrm, '', Cd).
test(InFile) :-
open(InFile, read, InStrm),
getline(InStrm).
I have adapted your code a bit so that it now (1) reads the first line from the given text file, and (2) returns the first line of that text file in a string to the caller.
:- use_module(library(readutil)).
test(File, String):-
setup_call_cleanup(
open(File, read, In),
read_line_to_string(In, String),
close(In)
).
Notice that I use library readutil to do part of the job
Also, I use setup_call_cleanup/3 to make sure the stream gets closed in case reading a line fails or throws an exception.
Hope this helps!
Related
There are Prolog predicates that output error messages, like load_files/1 in case the file could not be found:
error(existence_error(source_sink,'/home/foo/nothinghere.txt'),_3724).
These error messages are just printed to stdout but are not returned as prolog return value.
In my case, load_files/1 gets called deep down as part of a procedure that I don't want to modify. I just deliver the file name and wait for the return value. But as far as I understand, the return value, in this case, is either True or an error. Is there any way for me to redirect the error output to the return value so that I can actually do something with the output I am getting?
You could use catch/3.
?- catch(load_files('/tmp/exists.prolog'), E, true).
true.
?- catch(load_files('/tmp/notexists.prolog'), E, true).
E = error(existence_error(source_sink, '/tmp/notexists.prolog'), _).
catch(:Goal, +Catcher, :Recover) is used for catching throw(_) from the :Goal.
Catcher is unified with the argument of throw/1.
Recover is called using call/1.
In the above example E unifies with any error. We do not want that, so we can do something like:
catchFileErrors(G, F) :-
catch(G, error(existence_error(source_sink, F), _), true).
?- catchFileErrors(load_files('exists.prolog'), E).
true.
?- catchFileErrors(load_files('notexists.prolog'), E).
E = 'notexists.prolog'.
You can pass a goal as the last argument to catch if you have a strategy to recover from the error.
update :-
write("Name?:"),
read(Name),
assert(Name),nl,
write("Age?:"),
read(Age),
assert(Age),
write("Continue(y or n)?:"),
read(Respond),
process(Respond).
process(y) :-
write('Name?:'),
read(Name),
assert(Name),nl,
write("Age?:"),
read(Age),
assert(Age),
repeat,
write("y or n"),
read(Respond),
process(Respond).
process(n) :- !.
I want to run this Prolog to assert in the name and age, but when I write age for the number, it shows
?- update.
Name?:fred.
Age?:|: 25.
ERROR: Type error: `callable' expected, found `25' (an integer)
ERROR: In:
ERROR: [9] assert(25)
ERROR: [8] update at c:/example.pl:11
ERROR: [7] <user>
?-
How to figure out this problem.
Problem 1
Incorrect input for assert/1
The problem is not with just Age it is with any input that uses assert, e.g.
?- update.
Name?:Fred
|: .
ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR: [9] assert(_4940)
ERROR: [8] update at c:/example.pl:8
ERROR: [7] <user>
?- update.
Name?:Jim.
ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR: [9] assert(_5826)
ERROR: [8] update at c:/example.pl:8
ERROR: [7] <user>
The problem is that assert/1 is not being given a fact or rule.
assert/1 says:
Assert a clause (fact or rule) into the database.
See facts and rules
In the example above Fred is not a fact because it does not end with a period (.).
In the example above with Jim. a period was given but because Jim starts with a capital letter, it is not a fact or rule but a variable.
When the age is entered as a number, again this is not a fact or rule it is an integer.
Problem 2
Use of read/1 which says:
Read the next Prolog term from the current input stream and unify it with Term.
When reading a Prolog term the input must end with a period.
This not only requires the input to be a term, but end with a . which is even more confusing given the prompt, e.g Age. Most of the examples you find do what you did, the corrected code below does what you want.
Problem 3
Competing ways or repeating.
The code is using two ways:
Use of repeat/0
It is recursive, e.g.
process(y) :-
...
process(Respond).
This is making it hard to get the code working.
Problem 4
Duplicate code, e.g.
write("Name?:"),
read(Name),
assert(Name),nl,
write("Age?:"),
read(Age),
assert(Age),
write("Continue(y or n)?:"),
read(Respond),
process(Respond).
Duplicated code is more likely to lead to problems when one copy is corrected and the other copy is not corrected.
Problem 1 fix
Make the input a fact before storing in the database with assert/1, e.g.
Values in variables
Name
Age
Variables converted to facts by adding a functor
name(Name)
age(Age)
The facts used with assert/1
assert(name(Name))
assert(age(Age))
Problem 2 fix
Use read_string/5, e.g.
read_string(user, "\n", "\r", End, Name)
This reads the input into the variable Name as a string. Now that the input is a string, and not a Prolog term, the period is no longer required. There are predicates that operate on strings.
Problem 3 fix
Use the recursion form and remove repeat/0.
This could also use repeat/0 instead of recursion. The corrected code below uses recursion to demonstrate the change to process/1.
Problem 4 fix
Just refactor the code. You can see this in the corrected code at the end.
Now with the fixes in place.
Change 1
Since the input for continue is no longer a term, e.g. y or n, but a string, the parameter for process needs to be a string, e.g.
process("y") :-
process("n") :-
Change 2
Age will be asserted as a string but would be better asserted as an integer.
number_string/2 can solve this, e.g.
number_string(Age_n,Age),
assert(age(Age_n))
Change 3
user27815 Asked in a comment:
do you need the cut in process("n") :- !. ?
Since
process(Respond).
is not creating a choice point, the cut is not needed.
Corrected code:
update :-
% Respond will be read as a string and not as a term, so it needs "".
process("y").
process("y") :-
write('Name: '),
read_string(user, "\n", "\r", End, Name),
assert(name(Name)),
write("Age: "),
read_string(user, "\n", "\r", End, Age),
number_string(Age_n,Age),
assert(age(Age_n)),
write("Continue: (y or n) "),
read_string(user, "\n", "\r", End, Respond),
process(Respond).
process("n").
Example run:
?- update.
Name: Fred
Age: 30
Continue: (y or n) y
Name: Jim
Age: 21
Continue: (y or n) n
true.
To check that the database was updated use listing/1
?- listing(name/1).
:- dynamic name/1.
name("Fred").
name("Jim").
true.
?- listing(age/1).
:- dynamic age/1.
age(30).
age(21).
true.
A free enhancement.
Keeping the facts of name and age separate doesn't keep the relation between them intact. A better solution would be a person fact with both Name and Age values.
Here is the necessary modified code.
update :-
% Respond will be read as a string and not as a term, so it needs "".
process("y").
process("y") :-
write('Name: '),
read_string(user, "\n", "\r", End, Name),
write("Age: "),
read_string(user, "\n", "\r", End, Age),
number_string(Age_n,Age),
assert(person(Name,Age_n)),
write("Continue: (y or n) "),
read_string(user, "\n", "\r", End, Respond),
process(Respond).
process("n").
Example run:
?- update.
Name: Fred
Age: 30
Continue: (y or n) y
Name: Jim
Age: 21
Continue: (y or n) n
true.
To check that the database was updated use listing/1
?- listing(person/2).
:- dynamic person/2.
person("Fred", 30).
person("Jim", 21).
true.
After noticing your deleted answer.
In your deleted answer you have
?- person(name(N), age(A)).
N = nancy,
A = 22;
N= steve,
A = 100;
true.
The change needed for this variation of the fact to be created is
assert(person(name(Name),age(Age_n)))
however that might not be the optimal way to go.
In Prolog, positions typically indicate the meaning of a value, e.g. first position is name and second position is age. In this variation by adding the functors name and age to the fact person/2 you are duplicating known knowledge, but more importantly the possibility the amount of work Prolog has to do.
For example:
If the fact was person(Name,Age). to get at Name and Age Prolog only needs one unification. But with person(Name,Age). Prolog now needs to unify with person(name(nancy),age(22)) then to get Name has to unify again with name(nancy) and to get Age has to unify with age(22). You could also use person(name(Name),age(Age)). which requires only one unification, but now makes your code more verbose.
When first learning Prolog this crutch helps, but when working with larger data sets, this starts to impact performance.
Another item of note in your deleted answer is that the names of the people are still based on using read/1, e.g. nancy and steve. While a lot of Prolog examples do this, there is no requirement to keep them as such, they can be strings. Odds are the code will never need to exactly match on nancy or steve and instead will always reference them as a value in a variable. The nice thing about keeping them as strings is that when writing them out, they will appear correctly as Nancy and Steve.
This is because assert does not work on variables. It asserts a fact or rule; in other words, assert(something) asserts that something must be true.
From the SWI-Prolog documentation:
Assert a clause (fact or rule) into the database.
An integer value is not a rule or a fact. It is (in this case) an integer, not something that evaluates to a boolean value. There's no point in asserting a value.
I would write some helpers:
read_assert(P,V) :- format('~w ? ',[P]), read(V), A =.. [P,V], assert(A).
?- maplist(read_assert, [name,age], Vs).
name ? capellic.
age ? 99.
Vs = [capellic, 99].
?- name(N).
N = capellic.
I'm writing a list of lists to a file with
choice(2, X):-
nl, write('\tRead roster from a file:'),nl, write('\tEnter file name: '),read(N),
open(N,write,Stream), write(Stream, X), nl(Stream),close(Stream), write('\tRoster stored.'),nl,nl,menu(X).
then I'm reading it with
choice(1, X):-
nl, write('\tStore roster to a file:'),nl, write('\tEnter file name: '),read(N),
open(N,read,Stream), read(Stream, Y), close(Stream), write('\tRoster stored.'),nl,nl,menu(Y).
this is the contents of a sample from choice(2,X) that I tried to read with choice(1,X).
[[[49,48,48],[100,97,109,105,101,110],100]]
when I try to read it gives the error
ERROR: r2:1:44: Syntax error: Unexpected end of file
You get this error because read/2 can only read full prolog terms terminated with .. Contents of your file aren't terminated with dot, so end of file is "unexpected".
My suggestion is that you should modify that:
open(N,read,Stream), read(Stream, Y), close(Stream)
Into that:
open(N,read,Stream), read_line_to_codes(Stream, Codes), close(Stream), atom_codes(Atom, Codes), atom_to_term(Atom, Y, [])
Above code reads one line of character data, converts it to atom and then converts it to prolog term unified with Y.
This is my input and output from interpreter to prove it works:
?- open('test2.txt',read,Stream), read_line_to_codes(Stream, Codes), close(Stream), atom_codes(Atom, Codes), atom_to_term(Atom, List, []).
List = [[[49, 48, 48], [100, 97, 109, 105, 101, 110], 100]].
Contents of file test2.txt: [[[49,48,48],[100,97,109,105,101,110],100]]
(no dot on the end)
As #Grzegorz correctly said, to be able to read a term with read it needs to end with a full stop.
But I don't think that a correct solution to your problem is to read as codes. I think the better way is just to write full stop to the file.
The recommended way, I believe, is to use write_term with fullstop(true) option instead of write. But this is not working on my version of SWI-Prolog.
The easiest way is just to write full stop explicitly with write(Stream, '.').
At some point of my program I have an atom formed by what previously were also atoms, and I want to remove the character spaces within it so that later I can use without any problem:
term_to_atom(Result, 'second2(second2),region(ºMediterranean Sea),months(no_value),third3(third3),recog(ºNew Type),distance(no_value)').
and obtain this
Result = (second2(second2), region(ºMediterraneanSea), months(no_value), third3(third3), recog(ºNewType), distance(no_value))
or also the original would work
Result = (second2(second2), region(ºMediterranean Sea), months(no_value), third3(third3), recog(ºNew Type), distance(no_value))
because if I don't delete those character spaces then term_to_atom will complain about it. How can I solve it?
You can use this procedure:
strip_spaces(S, NoSpaces) :-
atom_codes(S, Cs), delete(Cs, 0' , X), atom_codes(NoSpaces, X).
but delete/3 is deprecated. Another possibility is
strip_spaces(S, NoSpaces) :-
atomic_list_concat(L, ' ', S),
atomic_list_concat(L, NoSpaces).
Either of these will 'eat' each space, but from your problem description, in comments you exchanged with gusbro, this doesn't seems to me the right way to go. Changing the literals seems at DB interface could ask for trouble later.
Parsing your input to a list, instead of a conjunction, can be done with DCGs:
:- [library(http/dcg_basics)].
parse_result(X, R) :-
atom_codes(X, Cs),
phrase(parse_list(R), Cs).
parse_list([T|Ts]) -->
parse_term(T), (",", parse_list(Ts) ; {Ts=[]}).
parse_term(T) -->
string(F), "(", string(Arg), ")",
{atom_codes(Fa,F), atom_codes(Arga,Arg), T =.. [Fa,Arga]}.
I am using SWI-Prolog and am confused why the option library would be written to give the following outputs:
?- option(a(A), [a=1, a=2, a(3)]).
A = 3.
?- option(b(B), [b=1, b=2]).
B = 1.
I would expect A=1 ... Looking through the option library code though, this result is clearly intended (git link), but why is this not a bug?
option(Opt, Options) :- % make option processing stead-fast
arg(1, Opt, OptVal),
nonvar(OptVal), !,
functor(Opt, OptName, 1),
functor(Gen, OptName, 1),
option(Gen, Options),
Opt = Gen.
option(Opt, Options) :-
get_option(Opt, Options), !.
get_option(Opt, Options) :-
memberchk(Opt, Options), !.
get_option(Opt, Options) :-
functor(Opt, OptName, 1),
arg(1, Opt, OptVal),
memberchk(OptName=OptVal, Options), !.
Since memberchk/2 (which is semi-deterministic, i.e., it succeeds at most once) is used in the code you quote, non-determinism (A=1 ; A = 2 ; etc.) seems explicitly not intended. If anything, contradictory options should maybe raise a domain error, no?
The documentation says the Name = Value syntax is deprecated in favor of the Name(Value) syntax.
It seems reasonable this would be represented in the library(option) code by checking first for the preferred form.