Different form of DCG semicontext notation?
If I do this in SWI prolog:
str --> str("one"), [_], str("eight"), "ight".
str(String), T --> { string_codes(String, [_|T])}, String.
and try to use it, it works:
?- string_codes("oneight", Cs), phrase(str, Cs).
Cs = [111, 110, 101, 105, 103, 104, 116].
However, the same thing inside of a Logtalk object:
:- object(t).
:- public(str//0).
str --> str("one"), [_], str("eight"), "ight".
str(String), T --> { string_codes(String, [_|T])}, String.
:- end_object.
results in this compilation error:
! Instantiation error
! in grammar rule for non-terminal str//1
!
false.
Am I missing anything?
The use of a variable in the place of a push-back list is a non-standard extension by SWI-Prolog that's not accepted by Logtalk and most Prolog systems. Also not accepted in the ISO DCGs standard proposal. Note that Logtalk uses its own, portable, DCGs implementation.
Instead of the non-portable:
p, [t| T] --> q.
Try instead:
p --> q, pb([t| T]).
pb([]) --> [].
pb([C| Cs]) --> [C], pb(Cs).
Thank you. I think my main difficulty lies in the fact that in my real-world, non-reduced version of this, the pushback list makes a lot of sense because the "spillover" it produces is handled by another predicate, and this predicate has no idea about how it is handled so it doesn't drive it there.
My real case: https://github.com/yrashk/aoc2023/blob/master/logtalk/1/aoc23_1.lgt
The diff below simplifies the code by using whole pushback lists. Except that it doesn't work in Logtalk (but it does in SWI).
I wonder if there's any reason to not support this form in Logtalk. Or if there's a better, equally eloquent version of this.
diff --git a/logtalk/1/aoc23_1.lgt b/logtalk/1/aoc23_1.lgt
index e01116e..29c63fe 100644
--- a/logtalk/1/aoc23_1.lgt
+++ b/logtalk/1/aoc23_1.lgt
@@ -14,29 +14,33 @@
solve(File, Solution) :-
phrase_from_file(parse(Solution), File).
+digitize_string(String), T -->
+ { string_codes(String, [T]) },
+ String.
+
digitize(Digit) -->
[C], { char_type(C, digit(Digit)) }.
% Below we are going to push codes for the remainder of numbers
% using semicontext notation so they can be parsed again. It's a bit ugly TBH
% (110: n, 101: e and so on)
-digitize(1), [110, 101] -->
- "one".
-digitize(2), [119, 111] -->
- "two".
-digitize(3), [104, 114, 101, 101] -->
- "three".
-digitize(4), [111, 117, 114] -->
- "four".
-digitize(5), [105, 118, 101] -->
- "five".
-digitize(6), [105, 120] -->
- "six".
-digitize(7), [101, 118, 101, 110] -->
- "seven".
-digitize(8), [105, 103, 104, 116] -->
- "eight".
-digitize(9), [105, 110, 101] -->
- "nine".
+digitize(1) -->
+ digitize_string("one").
+digitize(2) -->
+ digitize_string("two").
+digitize(3) -->
+ digitize_string("three").
+digitize(4) -->
+ digitize_string("four").
+digitize(5) -->
+ digitize_string("five").
+digitize(6) -->
+ digitize_string("six").
+digitize(7) -->
+ digitize_string("seven").
+digitize(8) -->
+ digitize_string("eight").
+digitize(9)-->
+ digitize_string("nine").
parse(Solution) --> parse(Solution0, 0, [_, _]), !, { Solution is Solution0 }.
For reference, here's my example working with the SWI Prolog bit: https://github.com/yrashk/aoc2023/commit/6dd61c96cbbce60a15beb51feb74304c0492c7fe
Btw, write instead:
% the following directive would usually be found in a loader file
:- use_module(library(pure_input), []).
:- object(aoc23_1).
:- info([
author is 'Yurii Rashkovskii',
date is 2023-12-1,
comment is 'Calibration (#1)'
]).
:- use_module(pure_input, [phrase_from_file/2]).
I.e. inside objects, use Logtalk's use_module/2 directive, not the Prolog use_module/2 directive. The meta_predicate/1 directive is not required as Logtalk can parse the meta-predicate template of the pure_input:phrase_from_file/2 predicate.
Looking into puzzle description, I wrote the following portable solution. You will need to load the reader library to try it (logtalk_load(reader(loader))).
:- set_prolog_flag(double_quotes, chars).
:- object(solution).
:- info([
author is 'Paulo Moura',
date is 2023-12-02,
comment is 'Calibration (#1)'
]).
:- public(solution/2).
solution(File, Solution) :-
open(File, read, Stream),
reader::line_to_chars(Stream, Line),
solution(Line, Stream, 0, Solution),
close(Stream).
solution(end_of_file, _, Solution, Solution).
solution([Char| Chars], Stream, Solution0, Solution) :-
once(phrase(calibration(Calibration), [Char| Chars])),
Solution1 is Solution0 + Calibration,
reader::line_to_chars(Stream, Line),
solution(Line, Stream, Solution1, Solution).
calibration(Calibration) -->
first_digit(First),
rest_digits(Digits),
{
last(Digits, First, Second),
Calibration is First*10 + Second
}.
first_digit(Digit) -->
digit(Digit).
first_digit(Digit) -->
[_], first_digit(Digit).
rest_digits([Digit| Digits]) -->
digit(Digit),
rest_digits(Digits).
rest_digits(Digits) -->
[_],
rest_digits(Digits).
rest_digits([]) -->
[].
% as numbers
digit(1) --> "1".
digit(2) --> "2".
digit(3) --> "3".
digit(4) --> "4".
digit(5) --> "5".
digit(6) --> "6".
digit(7) --> "7".
digit(8) --> "8".
digit(9) --> "9".
% as letters (take into account possible overlaps)
digit(1), [e] --> "one".
digit(2), [o] --> "two".
digit(3), [e] --> "three".
digit(4) --> "four".
digit(5), [e] --> "five".
digit(6) --> "six".
digit(7), [n] --> "seven".
digit(8), [t] --> "eight".
digit(9), [e] --> "nine".
last([], Last, Last).
last([Head| Tail], _, Last) :-
last(Tail, Head, Last).
:- end_object.
$ swilgt -q
?- {reader(loader), solution}.
true.
?- solution::solution(sample, Solution).
Solution = 142.
?- solution::solution(part2_sample, Solution).
Solution = 281.
?- solution::solution(input, Solution).
Solution = 54530.
Also note that my alternative solution, being more declarative, is much faster:
?- time(trebuchet::solution('test_files/input', Calibration)).
% 88,064 inferences, 0.011 CPU in 0.012 seconds (88% CPU, 8223364 Lips)
Calibration = 53894.
?- time(aoc23_1::solve(input, A)).
% 3,094,971 inferences, 0.214 CPU in 0.221 seconds (97% CPU, 14477161 Lips)
A = 56049.
Thank you so much for your solution! It's great to see that it is much faster. The only concern I have is the manual overlap extraction in the pushback lists. Do you think there's a good way to make that figured out by the code itself as opposed to having to specify overlaps manually?
Btw, write instead:
% the following directive would usually be found in a loader file :- use_module(library(pure_input), []). :- object(aoc23_1). :- info([ author is 'Yurii Rashkovskii', date is 2023-12-1, comment is 'Calibration (#1)' ]). :- use_module(pure_input, [phrase_from_file/2]).I.e. inside objects, use Logtalk's
use_module/2directive, not the Prologuse_module/2directive. Themeta_predicate/1directive is not required as Logtalk can parse the meta-predicate template of thepure_input:phrase_from_file/2predicate.
Thanks, I've tried the diff below but it doesn't fix the warning:
diff --git a/logtalk/1/aoc23_1.lgt b/logtalk/1/aoc23_1.lgt
index ca0d23f..ab10a26 100644
--- a/logtalk/1/aoc23_1.lgt
+++ b/logtalk/1/aoc23_1.lgt
@@ -6,8 +6,7 @@
comment is 'Calibration (#1)'
]).
-:- use_module(library(pure_input), [phrase_from_file/2]).
-:- meta_predicate(phrase_from_file(2, *)).
+:- use_module(pure_input, [phrase_from_file/2]).
solve(File, Solution) :-
phrase_from_file(parse(Solution), File).
diff --git a/logtalk/1/loader.lgt b/logtalk/1/loader.lgt
index 6c20120..d68a5ac 100644
--- a/logtalk/1/loader.lgt
+++ b/logtalk/1/loader.lgt
@@ -1,4 +1,6 @@
:- use_module(dcgs).
+:- use_module(library(pure_input), []).
+
:- initialization(
(
logtalk_load([solver, aoc23_1]),
I've included your implementation in the repo: https://github.com/yrashk/aoc2023/commit/c52031a62e0bdaace90ba3253bac5c7f446efb6a
Thank you so much for your solution! It's great to see that it is much faster. The only concern I have is the manual overlap extraction in the pushback lists. Do you think there's a good way to make that figured out by the code itself as opposed to having to specify overlaps manually?
I would expect that any solution to construct/infer the overlaps automatically would make the code more complicated and slower to be worth it.
Thanks, I've tried the diff below but it doesn't fix the warning:
I applied my suggested fix manually to your latest git version of the aoc2023 repo (7152e197e259b7f97ffd1e1666a39c41e7d09ad6) and I don't get any warning. See https://github.com/LogtalkDotOrg/logtalk3/discussions/190#discussioncomment-7736768.
Thanks, I've tried the diff below but it doesn't fix the warning:
I applied my suggested fix manually to your latest git version of the
aoc2023repo (7152e197e259b7f97ffd1e1666a39c41e7d09ad6) and I don't get any warning. See #190 (comment).
Oh, I understand now. Thanks a lot for fixing the case!
I also added my solution as an example to Logtalk. It will be in the 3.73.0 release, expected next week.