logtalk3 icon indicating copy to clipboard operation
logtalk3 copied to clipboard

Different form of DCG semicontext notation?

Open yrashk opened this issue 2 years ago • 15 comments

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?

yrashk avatar Dec 01 '23 21:12 yrashk

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.

pmoura avatar Dec 01 '23 22:12 pmoura

Instead of the non-portable:

p, [t| T] --> q.

Try instead:

p --> q, pb([t| T]).

pb([]) --> [].
pb([C| Cs]) --> [C], pb(Cs).

pmoura avatar Dec 01 '23 22:12 pmoura

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.

yrashk avatar Dec 01 '23 22:12 yrashk

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 }.

yrashk avatar Dec 01 '23 22:12 yrashk

For reference, here's my example working with the SWI Prolog bit: https://github.com/yrashk/aoc2023/commit/6dd61c96cbbce60a15beb51feb74304c0492c7fe

yrashk avatar Dec 01 '23 23:12 yrashk

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.

pmoura avatar Dec 02 '23 08:12 pmoura

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.

pmoura avatar Dec 02 '23 10:12 pmoura

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.

pmoura avatar Dec 02 '23 10:12 pmoura

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?

yrashk avatar Dec 02 '23 15:12 yrashk

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.

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]),

yrashk avatar Dec 02 '23 16:12 yrashk

I've included your implementation in the repo: https://github.com/yrashk/aoc2023/commit/c52031a62e0bdaace90ba3253bac5c7f446efb6a

yrashk avatar Dec 02 '23 16:12 yrashk

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.

pmoura avatar Dec 02 '23 16:12 pmoura

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.

pmoura avatar Dec 02 '23 16:12 pmoura

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 #190 (comment).

Oh, I understand now. Thanks a lot for fixing the case!

yrashk avatar Dec 02 '23 16:12 yrashk

I also added my solution as an example to Logtalk. It will be in the 3.73.0 release, expected next week.

pmoura avatar Dec 02 '23 17:12 pmoura