unofficial mirror of guile-devel@gnu.org 
 help / color / mirror / Atom feed
* Hygienic rewrite of (ice-9 expect)
@ 2023-04-15 15:10 Daniel Dinnyes
  2023-05-21 22:26 ` Daniel Dinnyes
  2023-05-28 13:39 ` Maxime Devos
  0 siblings, 2 replies; 18+ messages in thread
From: Daniel Dinnyes @ 2023-04-15 15:10 UTC (permalink / raw)
  To: guile-devel

[-- Attachment #1: Type: text/plain, Size: 7923 bytes --]

Hi everyone,

I am new to this mailing list, and writing this because I have encountered
some issues with ice-9 expect
<https://git.savannah.gnu.org/cgit/guile.git/tree/module/ice-9/expect.scm>:

The procedural expect macro has a serious problem:

This syntax expects a procedure as the test expression, with 2 arguments
(actual content read, and boolean EOF flag). The primary problem is that
when the test expression is not a identifier bound to such a procedure, but
instead an expression which creates such a procedure (aka "factory"), then
the expression will be evaluated anew for each character read from
expect-port. This is a serious problem: when constructing that procedure
has significant computational costs, or side effects (eg. opening a port to
a new log file), as it can be seen in my code here
<https://github.com/dadinn/system-setup-tests/blob/expect-bug/run.scm#L476>.
(This is a branch of some of my KVM-based E2E testing code, which uses the
original expect macro implementation, just to demonstrate the problem).

I had written a workaround for this problem, by binding the evaluation of
the test expression outside the expansion of the expect macro (as it can be
seen here
<https://github.com/dadinn/system-setup-tests/blob/master/run.scm#L29>).
The problem with this was, that it doesn't support the full capabilities of
the original expect macro, and debilitates it by only allowing for a single
conditional branch. Also, I haven't verified, but I suspicious it would
possibly even break the behaviour for the => syntax of the expect-strings
macro.

To implement a more complete version of this (and also as an exercise to
understand hygienic macros), I've attempted to rewrite my workaround using
syntax-case. I've ran into some issues, and my implementation didn't expand
as I expected. After some discussion on IRC, we've concluded
<http://snailgeist.com/irc-logs/?message_id=136306> that this was because
the current (ice-9 expect) implementation is written using unhygienic
defmacro syntax, which doesn't work well together with the hygienic
syntax-case. It was suggested I should rather rewrite expect completely to
be hygienic instead.

I've eventually completed the rewrite using syntax-case here
<https://github.com/dadinn/common/blob/feature/expect/expect.scm>, and
would be happy to contribute it back to the ice-9 module!
I would point out the following about this implementation:

   1. It works!!! I had some quite extensive E2E testing code written
   before here
   <https://github.com/dadinn/system-setup-tests/blob/develop/run.scm>,
   which I think really stretches the possibilities with (ice-9 expect).
   Switching to the new implementation was simple, and works flawlessly!
   2. It is backwards compatible! I've gone extra lengths to make sure the
   code would be a backwards compatible, a drop-in replacement.
   3. Introduced new feature for the procedural expect macro, such that the
   second argument of the procedure gets passed the currently read char, or #f
   if it was an EOF character. There is multiple reasons this is superior to
   passing just a boolean eof? flag argument:
   1. the same logic can be implemented as with the eof? boolean, just by
         negating the condition
         2. passing the currently read char avoids having to do the
         inefficient operation to retrieve the currently read char by
cutting off
         the last character from the end of the content. Currently it
is necessary
         to do this, if the logic wants something additional to be
done with the
         current char (eg. as it can be seen in my code here
         <https://github.com/dadinn/system-setup-tests/blob/develop/run.scm#L200>
         )
         3. one such case of additional use is making the expect-char-proc
         property obsolete, as the predicate procedure is now able to
execute such
         logic too, besides other more advanced logging scenarios
(like it can be
         seen in my code here
         <https://github.com/dadinn/system-setup-tests/blob/develop/run.scm#L205>:
         here besides logging everything to STDOUT, and also to a main
log-file, I
         also log for each expect macro call the contents it tries to compare
         against into a separately named minor log-file (useful for
debugging), and
         also to ensure that we actually do not write to STDOUT when
using expect on
         multiple parallel processes)
         4. To ensure that everything stays backwards compatible, this new
         feature is only enabled if the new expect-pass-char? binding is
         set to #t in the lexical context (like here
         <https://github.com/dadinn/system-setup-tests/commit/21bddce3cd1c14f8834adaee4ff75f5b1b7a7b04>).
         Otherwise, by default this feature is disabled, and boolean eof?
         flag argument is passed just like before.
         4. There is a better support for using default value/expressions
   for the various parameters the macros depend on in their lexical contexts.
   The old macro had to export default values defined in its module, so as to
   ensure the defaults are available. This either clutters the module's global
   context where (ice-9 expect) is used, or the defaults are not available
   when using something like ((ice-9 expect) #:select (expect
   expect-string)).  The new defaults are calculated based on the lexical
   context where the macros are used, and expands into the correct default
   values when they are not defined. For example when expect-port is not
   defined in the lexical context, then the default value used is going to be
   the result of the (current-input-port) expression. I would like someone
   to review whether I've implemented this correctly, as it seems the
   current-input-port identifier default for expect-port gets captured in
   the expansion. Not sure why that happens.
   5. The expect-strings macro supports the => syntax, which passes the
   output(s) of the test predicate to a procedure, instead of just evaluating
   a body. This is fully supported, but additionally, the => syntax now
   also works with the procedural expect macro too! I haven't verified if the
   original implementation did this too, but at least it wasn't described in
   the documentation!
   6. As an added bonus, I have added a simplistic implementation for an
   interact macro. It uses threads to read from STDIO, and expects expect-port
   to be an input-output-port, so that it can output the lines read from STDIO
   into the same port. Not too advanced, as it doesn't support a full terminal
   interface, with autocomplete, etc... but works well enough for my use-case
   to interact with a KVM process via serial port.
   7. mnieper said <http://snailgeist.com/irc-logs/?message_id=136211> that
   I am not using syntax-case idiomatically, and my style is more in the form
   of a syntax-rules-based state-machine.
   I am quite happy with how it is currently, but would be open to be
   convinced if there is a superior way of expressing all this.
   8. I currently have various tests sprinkled around my implementation, to
   manually demonstrate expansions, and executions.
   Also, I have added some automated unit tests for the bind-locally helper
   procedure, which I used for checking whether parameters are defined in the
   lexical scope.
   9. I am not happy with how I am managing my project structure, and how
   my modules can reference each other (having to call add-to-load-path
   everywhere).
   Also, I think the way I am using srfi-64 tests is a bit bothersome, as
   they always get executed when my modules are loaded, and I have to globally
   disable logging to files.
   I would like some recommendations how to do this better!

If the community likes this implementation, I would be happy to
apply/rebase my changes onto the official repo, and then we can go from
there!

Best regards,
Daniel Dinnyes

[-- Attachment #2: Type: text/html, Size: 9125 bytes --]

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2024-02-23 17:53 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-04-15 15:10 Hygienic rewrite of (ice-9 expect) Daniel Dinnyes
2023-05-21 22:26 ` Daniel Dinnyes
2023-05-23 10:48   ` Daniel Dinnyes
2023-05-28 13:39 ` Maxime Devos
2023-05-29  1:40   ` Daniel Dinnyes
2023-05-29 21:33     ` Maxime Devos
2023-06-11 15:48       ` Daniel Dinnyes
2023-06-18 23:37         ` Daniel Dinnyes
2023-07-05 11:57           ` Maxime Devos
2023-09-24  1:02             ` Daniel Dinnyes
2023-09-30 20:22               ` Maxime Devos
2023-10-27  1:03                 ` Daniel Dinnyes
2023-10-27 16:58                   ` Maxime Devos
2023-11-18 11:29                     ` Daniel Dinnyes
2023-11-19 23:47                       ` Maxime Devos
2023-11-19 23:49                         ` Daniel Dinnyes
2024-02-23 17:53                           ` Daniel Dinnyes
2023-06-25 20:34         ` Maxime Devos

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).