all messages for Guix-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* Adding wc to Bournish
@ 2016-05-24 18:47 Efraim Flashner
  2016-05-25  9:03 ` Ricardo Wurmus
                   ` (4 more replies)
  0 siblings, 5 replies; 23+ messages in thread
From: Efraim Flashner @ 2016-05-24 18:47 UTC (permalink / raw)
  To: guix-devel, Ludovic =?utf-8?Q?Court=C3=A8s?=, Eric Bavier


[-- Attachment #1.1: Type: text/plain, Size: 1469 bytes --]

GSoC just started and so far I have 13 tabs from the guile manual open
on my computer. I almost implemented `du' at the same time as `wc' but
at the time I was getting tripped up with functions returning functions
or returning values.

`wc-l' and `wc-w' are two separate functions ATM. It should be possible
to combine the two and increment the line-count if the delimiter is \n
(or EOF) but I didn't want to spend too much time implementing and
re-implementing the commands.

`wc-w' closes the file after it finishes but `wc-l' doesn't. Good
hygiene? Let the garbage collector take care of it? I also made liberal
use of `make-string', this seems like a memory leak nightmare to me.

I wrapped `file-size' and `wc-command' in checks that the file actually
exists and can be read by the user. One of the things drilled into me
from turning in assignments in school was to wrap the code in checks so
it'd pass the TAs checks.

`wc-command', it doesn't accept arguments and I'm not happy with the
method of printing out the results but it works.

bournish@(guile-user)> wc gpl-3.0.txt
674 5644 35147 gpl-3.0.txt

What should I add to my .guile to auto-load guix.scm from the repo?
(load /path/to/guix/guix.scm) didn't work for me.

-- 
Efraim Flashner   <efraim@flashner.co.il>   אפרים פלשנר
GPG key = A28B F40C 3E55 1372 662D  14F7 41AA E7DC CA3D 8351
Confidentiality cannot be guaranteed on emails sent or received unencrypted

[-- Attachment #1.2: 0001-Bournish-Add-wc-command.patch --]
[-- Type: text/plain, Size: 3259 bytes --]

From 73d46eb504a0b076614eaee88b09c1c56d409a54 Mon Sep 17 00:00:00 2001
From: Efraim Flashner <efraim@flashner.co.il>
Date: Sun, 22 May 2016 14:56:06 +0300
Subject: [PATCH] Bournish: Add `wc' command.

* guix/build/bournish.scm (file-size, wc-c-command, wc-w-command,
wc-l-command, wc-command): New variables.
(%commands): Add wc command.
---
 guix/build/bournish.scm | 52 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 51 insertions(+), 1 deletion(-)

diff --git a/guix/build/bournish.scm b/guix/build/bournish.scm
index 4022796..336a650 100644
--- a/guix/build/bournish.scm
+++ b/guix/build/bournish.scm
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2016 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2016 Efraim Flashner <efraim@flashner.co.il>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -103,6 +104,54 @@ characters."
        ((@ (guix build utils) dump-port) port (current-output-port))
        *unspecified*)))
 
+(define (file-contents file)
+  `(make-stream
+     (port->stream (open-input-file ,file) read-char)))
+
+(define (file-size file)
+  (if (and (file-exists? file)
+           (access? file 4))
+    (stat:size (stat file))
+    *unspecified*))
+
+(define (wc-c-command file)
+  ((@@ (guix build bournish) file-size) file))
+
+(define (wc-w-command file)
+  (let* ((input-file (open-file file "r"))
+         (line       (make-string 80))
+         (word-size  (read-delimited! " \t\n" line input-file))
+         (word-count 0))
+    (while (not (port-closed? input-file))
+           (if (not (zero? word-size))
+             (set! word-count (1+ word-count)))
+           (set! line (make-string 80))
+           (if (not (eof-object? (peek-char input-file)))
+             (set! word-size (read-delimited! " \t\n" line input-file))
+             (close-input-port input-file)))
+    word-count))
+
+(define (wc-l-command file)
+  (let* ((input-file (open-file file "r"))
+         (line       (read-line input-file))
+         (line-count 0))
+    (while (not (eof-object? line))
+           (set! line-count (1+ line-count))
+           (set! line (read-line input-file)))
+    line-count))
+
+(define (wc-command file)
+  (if (and (file-exists? file) (access? file 4))
+     (let* ((wc-l ((@@ (guix build bournish) wc-l-command) file))
+            (wc-w ((@@ (guix build bournish) wc-w-command) file))
+            (wc-c ((@@ (guix build bournish) wc-c-command) file)))
+       (begin 
+         (display wc-l)(display #\space)
+         (display wc-w)(display #\space)
+         (display wc-c)(display #\space)
+         (display file)
+         (newline)))))
+
 (define (help-command . _)
   (display "\
 Hello, this is Bournish, a minimal Bourne-like shell in Guile!
@@ -129,7 +178,8 @@ commands such as 'ls' and 'cd'; it lacks globbing, pipes---everything.\n"))
     ("help"   ,help-command)
     ("ls"     ,ls-command)
     ("which"  ,which-command)
-    ("cat"    ,cat-command)))
+    ("cat"    ,cat-command)
+    ("wc"     ,wc-command)))
 
 (define (read-bournish port env)
   "Read a Bournish expression from PORT, and return the corresponding Scheme
-- 
2.8.3


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

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

* Re: Adding wc to Bournish
  2016-05-24 18:47 Adding wc to Bournish Efraim Flashner
@ 2016-05-25  9:03 ` Ricardo Wurmus
  2016-05-25  9:43   ` Efraim Flashner
  2016-05-25  9:26 ` Ricardo Wurmus
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 23+ messages in thread
From: Ricardo Wurmus @ 2016-05-25  9:03 UTC (permalink / raw)
  To: Efraim Flashner; +Cc: guix-devel


Efraim Flashner <efraim@flashner.co.il> writes:

> +(define (wc-command file)
> +  (if (and (file-exists? file) (access? file 4))
> +     (let* ((wc-l ((@@ (guix build bournish) wc-l-command) file))
> +            (wc-w ((@@ (guix build bournish) wc-w-command) file))
> +            (wc-c ((@@ (guix build bournish) wc-c-command) file)))
> +       (begin 
> +         (display wc-l)(display #\space)
> +         (display wc-w)(display #\space)
> +         (display wc-c)(display #\space)
> +         (display file)
> +         (newline)))))

Have you considered using “format” instead of “display”?  The last
“begin” could just be this:

  (format #t "~a ~a ~a ~a\n"
    wc-l wc-w wc-c file)

or

  (format #t "~{~a ~}\n"
    (list wc-l wc-w wc-c file))

Why is it necessary to do “(@@ (guix build bournish) ...)”?  Can it not
be imported once?

~~ Ricardo

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

* Re: Adding wc to Bournish
  2016-05-24 18:47 Adding wc to Bournish Efraim Flashner
  2016-05-25  9:03 ` Ricardo Wurmus
@ 2016-05-25  9:26 ` Ricardo Wurmus
  2016-05-25 10:05   ` Efraim Flashner
  2016-05-26  8:46 ` Ludovic Courtès
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 23+ messages in thread
From: Ricardo Wurmus @ 2016-05-25  9:26 UTC (permalink / raw)
  To: Efraim Flashner; +Cc: guix-devel


Efraim Flashner <efraim@flashner.co.il> writes:

> +(define (wc-l-command file)
> +  (let* ((input-file (open-file file "r"))
> +         (line       (read-line input-file))
> +         (line-count 0))
> +    (while (not (eof-object? line))
> +           (set! line-count (1+ line-count))
> +           (set! line (read-line input-file)))
> +    line-count))

It’s unusual for me to see the use of “while” and “set!” in Scheme code.
You could do this in a functional manner using a fold (see SRFI-1) or
with file streams (see SRFI-41), which also provides a stream-fold.

The idea with a fold is that you have a function that takes a value
(e.g. from a list or a stream) and an intermediate result.  The function
does something to the value and then returns a new intermediate result.

Here’s a fold over a list of symbols implementing a count:

   (fold
      (lambda (_ res) (+ res 1))  ; increase the result
      0                           ; start at 0
      '(hello world bye))         ; items to count

If you had a file stream, where each element represents one line, you
can fold over all lines in much the same way to get a count.  You could
use the same framework with a different stream element generator
(reading one word or byte at a time instead of one line at a time) to
implement the other features of “wc”.

There’s an example of how to define a file stream in the Guile manual in
the documentation for SRFI-41.

~~ Ricardo

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

* Re: Adding wc to Bournish
  2016-05-25  9:03 ` Ricardo Wurmus
@ 2016-05-25  9:43   ` Efraim Flashner
  0 siblings, 0 replies; 23+ messages in thread
From: Efraim Flashner @ 2016-05-25  9:43 UTC (permalink / raw)
  To: Ricardo Wurmus; +Cc: guix-devel

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

On Wed, May 25, 2016 at 11:03:34AM +0200, Ricardo Wurmus wrote:
> 
> Efraim Flashner <efraim@flashner.co.il> writes:
> 
> > +(define (wc-command file)
> > +  (if (and (file-exists? file) (access? file 4))
> > +     (let* ((wc-l ((@@ (guix build bournish) wc-l-command) file))
> > +            (wc-w ((@@ (guix build bournish) wc-w-command) file))
> > +            (wc-c ((@@ (guix build bournish) wc-c-command) file)))
> > +       (begin 
> > +         (display wc-l)(display #\space)
> > +         (display wc-w)(display #\space)
> > +         (display wc-c)(display #\space)
> > +         (display file)
> > +         (newline)))))
> 
> Have you considered using “format” instead of “display”?  The last
> “begin” could just be this:
> 
>   (format #t "~a ~a ~a ~a\n"
>     wc-l wc-w wc-c file)
> 
> or
> 
>   (format #t "~{~a ~}\n"
>     (list wc-l wc-w wc-c file))

I looked at the manual more and I found for-each which does what I was
looking for before which gave me this:

(for-each display (list wc-l #\space wc-w #\space wc-c #\space file))

but I do like the (format #t "~{~a ~}\n" (list ...)) better. I'm working
on trying to get it to read flags and the variable size of the list
works better with that.

format vs display, I knew I wanted display over write but I hadn't come
across format yet. If I don't need to specify #\space to get a space
then that sounds great.

> 
> Why is it necessary to do “(@@ (guix build bournish) ...)”?  Can it not
> be imported once?

When you say it out loud then it seems obvious it shouldn't need to be
imported more than once. I've also read more about let vs let* and I've
changed some to let.

> 
> ~~ Ricardo

Thanks!

-- 
Efraim Flashner   <efraim@flashner.co.il>   אפרים פלשנר
GPG key = A28B F40C 3E55 1372 662D  14F7 41AA E7DC CA3D 8351
Confidentiality cannot be guaranteed on emails sent or received unencrypted

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

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

* Re: Adding wc to Bournish
  2016-05-25  9:26 ` Ricardo Wurmus
@ 2016-05-25 10:05   ` Efraim Flashner
  0 siblings, 0 replies; 23+ messages in thread
From: Efraim Flashner @ 2016-05-25 10:05 UTC (permalink / raw)
  To: Ricardo Wurmus; +Cc: guix-devel

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

On Wed, May 25, 2016 at 11:26:11AM +0200, Ricardo Wurmus wrote:
> 
> Efraim Flashner <efraim@flashner.co.il> writes:
> 
> > +(define (wc-l-command file)
> > +  (let* ((input-file (open-file file "r"))
> > +         (line       (read-line input-file))
> > +         (line-count 0))
> > +    (while (not (eof-object? line))
> > +           (set! line-count (1+ line-count))
> > +           (set! line (read-line input-file)))
> > +    line-count))
> 
> It’s unusual for me to see the use of “while” and “set!” in Scheme code.
> You could do this in a functional manner using a fold (see SRFI-1) or
> with file streams (see SRFI-41), which also provides a stream-fold.

I'm going to put set! in the GOTO box. GOTO worked well enough when I
learned QBasic, but just because its implemented doesn't mean I should
still use it. (ie: its there but try not to use it)

> 
> The idea with a fold is that you have a function that takes a value
> (e.g. from a list or a stream) and an intermediate result.  The function
> does something to the value and then returns a new intermediate result.

I already like this much better

> 
> Here’s a fold over a list of symbols implementing a count:
> 
>    (fold
>       (lambda (_ res) (+ res 1))  ; increase the result
>       0                           ; start at 0
>       '(hello world bye))         ; items to count
> 
> If you had a file stream, where each element represents one line, you
> can fold over all lines in much the same way to get a count.  You could
> use the same framework with a different stream element generator
> (reading one word or byte at a time instead of one line at a time) to
> implement the other features of “wc”.

I'll take another look at streams and rework that. I still think asking
the filesystem how big the file is works well for `wc -c', but `wc -l'
and `wc -w' should really be part of the same function.

> 
> There’s an example of how to define a file stream in the Guile manual in
> the documentation for SRFI-41.

That looks like a good place to start then :)

> 
> ~~ Ricardo

-- 
Efraim Flashner   <efraim@flashner.co.il>   אפרים פלשנר
GPG key = A28B F40C 3E55 1372 662D  14F7 41AA E7DC CA3D 8351
Confidentiality cannot be guaranteed on emails sent or received unencrypted

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

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

* Re: Adding wc to Bournish
  2016-05-24 18:47 Adding wc to Bournish Efraim Flashner
  2016-05-25  9:03 ` Ricardo Wurmus
  2016-05-25  9:26 ` Ricardo Wurmus
@ 2016-05-26  8:46 ` Ludovic Courtès
  2016-05-26 17:50   ` Efraim Flashner
  2016-05-26 19:27 ` Efraim Flashner
  2016-06-05 12:40 ` Efraim Flashner
  4 siblings, 1 reply; 23+ messages in thread
From: Ludovic Courtès @ 2016-05-26  8:46 UTC (permalink / raw)
  To: Efraim Flashner; +Cc: guix-devel

Hello!

Efraim Flashner <efraim@flashner.co.il> skribis:

> GSoC just started and so far I have 13 tabs from the guile manual open
> on my computer. I almost implemented `du' at the same time as `wc' but
> at the time I was getting tripped up with functions returning functions
> or returning values.

Nice!

Some comments to complement what Ricardo et al. wrote:

> +(define (wc-w-command file)
> +  (let* ((input-file (open-file file "r"))
> +         (line       (make-string 80))
> +         (word-size  (read-delimited! " \t\n" line input-file))
> +         (word-count 0))
> +    (while (not (port-closed? input-file))
> +           (if (not (zero? word-size))
> +             (set! word-count (1+ word-count)))
> +           (set! line (make-string 80))
> +           (if (not (eof-object? (peek-char input-file)))
> +             (set! word-size (read-delimited! " \t\n" line input-file))
> +             (close-input-port input-file)))
> +    word-count))

First, regarding the port not being closed, it’s a good idea to use
‘call-with-input-file’ instead of ‘open-file’: ‘call-with-input-file’
makes sure the port gets closed when its “dynamic extent” is left, that
is, regardless of whether we leave the procedure “normally” or via an
exception.

  (call-with-input-file file
    (lambda (port)
      …))

Also, imperative programming Should Be Avoided; see “Coding Style” in
the manual.  ;-)  We don’t use an i/o monad or any such thing, but
‘set!’ and string modifications is definitely frowned upon.

As Ricardo suggests, you could use ‘port->stream’ and ‘stream-fold’ to
iterate over the characters read from the port.  I suspect that’d be
rather slow though, at least on 2.0, so another option is something
like:

  (define (lines+chars port)
    ;; Return the number of lines and number of chars read from PORT.
    (let loop ((lines 1) (chars 0))
      (match (read-char port)
        ((? eof-object?)              ;done!
         (values lines port))
        (#\newline                    ;recurse
         (loop (+ 1 lines) (+ 1 chars)))
        (_                            ;recurse
         (loop lines (+ 1 chars))))))

  (define (wc-command file)
    (let-values (((lines chars)
                  (call-with-input-file file lines+chars)))
      (format #t "~a ~a ~a~%" lines chars file)))

> +(define (wc-command file)
> +  (if (and (file-exists? file) (access? file 4))

This check is not needed and is subject to a race condition (“TOCTTOU”);
just let ‘call-with-input-file’ error out if the file cannot be read.

Bonus point: catch ‘system-error’ exceptions and report the inability to
open the file in a nice user-friendly way (but really, don’t bother
about it for now.)

> +     (let* ((wc-l ((@@ (guix build bournish) wc-l-command) file))
> +            (wc-w ((@@ (guix build bournish) wc-w-command) file))
> +            (wc-c ((@@ (guix build bournish) wc-c-command) file)))
> +       (begin 
> +         (display wc-l)(display #\space)
> +         (display wc-w)(display #\space)
> +         (display wc-c)(display #\space)
> +         (display file)
> +         (newline)))))

Remember that Bournish is a compiler that compiles Bash to Scheme.
So we must distinguish the support functions that are used at run time,
such as ‘ls-command-implementation’, from what the Scheme code that the
compiler emits (compile time).

In the case of ‘ls’, when the compiler encounters ‘ls’ in the input, it
emits this code:

  ((@@ (guix build bournish) ls-command-implementation))

‘ls-command-implementation’ is the implementation that is called when we
run the compiled program.

Thus, you must similarly distinguish those two stages by providing:

  1. A ‘wc-command-implementation’ procedure that implements ‘wc’;

  2. A ‘wc-command’ procedure that emits the code that calls
     ‘wc-command-implementation’; so something like:

        (define (wc-command args)
          `((@@ (guix build bournish) wc-command-implementation)
            ,@args))

     Better yet, ‘wc-command’ could check for the presence of “-l” or
     “-c” at compile time and emit a call to the right thing.

HTH!

Ludo’.

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

* Re: Adding wc to Bournish
  2016-05-26  8:46 ` Ludovic Courtès
@ 2016-05-26 17:50   ` Efraim Flashner
  0 siblings, 0 replies; 23+ messages in thread
From: Efraim Flashner @ 2016-05-26 17:50 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: guix-devel

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

On Thu, May 26, 2016 at 10:46:21AM +0200, Ludovic Courtès wrote:
> 
> As Ricardo suggests, you could use ‘port->stream’ and ‘stream-fold’ to
> iterate over the characters read from the port.  I suspect that’d be
> rather slow though, at least on 2.0, so another option is something
> like:
> 
>   (define (lines+chars port)
>     ;; Return the number of lines and number of chars read from PORT.
>     (let loop ((lines 1) (chars 0))
>       (match (read-char port)
>         ((? eof-object?)              ;done!
>          (values lines port))
>         (#\newline                    ;recurse
>          (loop (+ 1 lines) (+ 1 chars)))
>         (_                            ;recurse
>          (loop lines (+ 1 chars))))))
> 
>   (define (wc-command file)
>     (let-values (((lines chars)
>                   (call-with-input-file file lines+chars)))
>       (format #t "~a ~a ~a~%" lines chars file)))
> 

Are you suggesting just dropping the word count part of `wc'? I've been
thinking about it, and the simplest way I can think of to describe a
word is a space followed by a character, or, to use the char-sets from
the guile manual, a character from char-set:whitespace followed by a
character from char-set:graphic. I can compare (read-char port) and
(peek-char port) to get a word count (possibly).

> > +(define (wc-command file)
> > +  (if (and (file-exists? file) (access? file 4))
> 
> This check is not needed and is subject to a race condition (“TOCTTOU”);
> just let ‘call-with-input-file’ error out if the file cannot be read.
> 
> Bonus point: catch ‘system-error’ exceptions and report the inability to
> open the file in a nice user-friendly way (but really, don’t bother
> about it for now.)
> 

I'm still wrapping my head around the following part. My wife says when
I work I scowl at the computer a lot and mutter :)
> 
> Remember that Bournish is a compiler that compiles Bash to Scheme.
> So we must distinguish the support functions that are used at run time,
> such as ‘ls-command-implementation’, from what the Scheme code that the
> compiler emits (compile time).
> 
> In the case of ‘ls’, when the compiler encounters ‘ls’ in the input, it
> emits this code:
> 
>   ((@@ (guix build bournish) ls-command-implementation))
> 
> ‘ls-command-implementation’ is the implementation that is called when we
> run the compiled program.
> 
> Thus, you must similarly distinguish those two stages by providing:
> 
>   1. A ‘wc-command-implementation’ procedure that implements ‘wc’;
> 
>   2. A ‘wc-command’ procedure that emits the code that calls
>      ‘wc-command-implementation’; so something like:
> 
>         (define (wc-command args)
>           `((@@ (guix build bournish) wc-command-implementation)
>             ,@args))
> 
>      Better yet, ‘wc-command’ could check for the presence of “-l” or
>      “-c” at compile time and emit a call to the right thing.

I checked with coreutil's 'wc', and it emits in its particular order
whether you call 'wc -l -c' or 'wc -lc' or 'wc -cl'

> 
> HTH!
> 
> Ludo’.


-- 
Efraim Flashner   <efraim@flashner.co.il>   אפרים פלשנר
GPG key = A28B F40C 3E55 1372 662D  14F7 41AA E7DC CA3D 8351
Confidentiality cannot be guaranteed on emails sent or received unencrypted

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

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

* Re: Adding wc to Bournish
  2016-05-24 18:47 Adding wc to Bournish Efraim Flashner
                   ` (2 preceding siblings ...)
  2016-05-26  8:46 ` Ludovic Courtès
@ 2016-05-26 19:27 ` Efraim Flashner
  2016-05-27 13:37   ` Eric Bavier
  2016-06-05 12:40 ` Efraim Flashner
  4 siblings, 1 reply; 23+ messages in thread
From: Efraim Flashner @ 2016-05-26 19:27 UTC (permalink / raw)
  To: guix-devel, Ludovic =?utf-8?Q?Court=C3=A8s?=, Eric Bavier


[-- Attachment #1.1: Type: text/plain, Size: 812 bytes --]

Here's where I'm currently at with 'wc'. Currently I'm getting an error
about having the wrong number of arguments for lambda (I assume)

;;; /home/efraim/workspace/guix/guix/build/bournish.scm:143:2: warning:
wrong number of arguments to `#<tree-il (lambda () (lambda-case (((lines
words chars) #f #f #f () (lines-24244 words-24245 chars-24246)) (apply
(toplevel format) (const #t) (const "~a ~a ~a ~a~%") (lexical lines
lines-24244) (lexical words words-24245) (lexical chars chars-24246)
(lexical file file-24242)))))>'

It's not ready as-is, but I wanted to show what I had so far :)

-- 
Efraim Flashner   <efraim@flashner.co.il>   אפרים פלשנר
GPG key = A28B F40C 3E55 1372 662D  14F7 41AA E7DC CA3D 8351
Confidentiality cannot be guaranteed on emails sent or received unencrypted

[-- Attachment #1.2: 0001-Bournish-Add-wc-command.patch --]
[-- Type: text/plain, Size: 3656 bytes --]

From f909c39530c727f414c8c827bdbccb2285ad1cc5 Mon Sep 17 00:00:00 2001
From: Efraim Flashner <efraim@flashner.co.il>
Date: Sun, 22 May 2016 14:56:06 +0300
Subject: [PATCH] Bournish: Add `wc' command.

* guix/build/bournish.scm (file-size, wc-c-command, wc-w-command,
wc-l-command, wc-command): New variables.
(%commands): Add wc command.
---
 guix/build/bournish.scm | 56 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 55 insertions(+), 1 deletion(-)

diff --git a/guix/build/bournish.scm b/guix/build/bournish.scm
index 4022796..d133796 100644
--- a/guix/build/bournish.scm
+++ b/guix/build/bournish.scm
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2016 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2016 Efraim Flashner <efraim@flashner.co.il>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -21,11 +22,13 @@
   #:use-module (system base compile)
   #:use-module (system repl command)
   #:use-module (system repl common)
+  ;#:use-module (ice-9 streams)
   #:use-module (ice-9 rdelim)
   #:use-module (ice-9 match)
   #:use-module (ice-9 ftw)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-41)
   #:export (%bournish-language))
 
 ;;; Commentary:
@@ -103,6 +106,56 @@ characters."
        ((@ (guix build utils) dump-port) port (current-output-port))
        *unspecified*)))
 
+(define (file-size file)
+    (stat:size (stat file)))
+
+(define (wc-c-command file)
+  ;; Faster when only `wc -c' is called
+  (file-size file))
+
+(define (wc-l-command file)
+  ;; Faster when only `wc -l' is called
+  (stream-length
+    (stream-filter
+      (lambda (chr)
+        (char=? chr #\newline))
+      (port->stream (open-file file "r")))))
+
+;(define (wc-l-command file)
+;  (call-with-input-file file
+;    (lambda (port)
+
+(define (lines+chars port)
+  ;; Return the number of lines and number of chars read from PORT.
+  (let loop ((lines 0) (words 0) (chars 0))
+    (match (read-char port)
+      ((? eof-object?)              ;done!
+       (values lines words chars))
+      (#\newline                    ;recurse
+       (loop (1+ lines) (1+ words) (1+ chars)))
+      ((and (char-set<= ? char-set:blank) ; need to fix/replace the '?'
+            (char-set<= (peek-char port) char-set:graphic))
+       (loop lines (1+ words) (1+ chars)))
+      (_                            ;recurse
+       (loop lines words (1+ chars))))))
+
+(define (wc-command file)
+  ((lambda (lines words chars) ; lambda doesn't like 3 variables
+     (format #t "~a ~a ~a ~a~%" lines words chars file))
+   (call-with-input-file file lines+chars)))
+
+;  let-values is undefined
+;  (let-values (((lines words chars)
+;    (call-with-input-file file lines+chars)))
+;      (format #t "~a ~a ~a ~a~%" lines chars words file)))
+
+;(define (wc-command file)
+;  (let ((wc-l (wc-l-command file))
+;        (wc-w (wc-w-command file))
+;        (wc-c (wc-c-command file)))
+;    (format #t "~{~a ~}\n"
+;            (list wc-l wc-w wc-c file))))
+
 (define (help-command . _)
   (display "\
 Hello, this is Bournish, a minimal Bourne-like shell in Guile!
@@ -129,7 +182,8 @@ commands such as 'ls' and 'cd'; it lacks globbing, pipes---everything.\n"))
     ("help"   ,help-command)
     ("ls"     ,ls-command)
     ("which"  ,which-command)
-    ("cat"    ,cat-command)))
+    ("cat"    ,cat-command)
+    ("wc"     ,wc-command)))
 
 (define (read-bournish port env)
   "Read a Bournish expression from PORT, and return the corresponding Scheme
-- 
2.8.3


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

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

* Re: Adding wc to Bournish
  2016-05-26 19:27 ` Efraim Flashner
@ 2016-05-27 13:37   ` Eric Bavier
  2016-05-27 15:28     ` Taylan Ulrich Bayırlı/Kammer
  0 siblings, 1 reply; 23+ messages in thread
From: Eric Bavier @ 2016-05-27 13:37 UTC (permalink / raw)
  To: Efraim Flashner; +Cc: guix-devel

On 2016-05-26 14:27, Efraim Flashner wrote:
> Here's where I'm currently at with 'wc'. Currently I'm getting an error
> about having the wrong number of arguments for lambda (I assume)
[...]
> +(define (wc-command file)
> +  ((lambda (lines words chars) ; lambda doesn't like 3 variables
> +     (format #t "~a ~a ~a ~a~%" lines words chars file))
> +   (call-with-input-file file lines+chars)))
[...]
> 
> ;;; /home/efraim/workspace/guix/guix/build/bournish.scm:143:2: warning:
> wrong number of arguments to `#<tree-il (lambda () (lambda-case 
> (((lines
> words chars) #f #f #f () (lines-24244 words-24245 chars-24246)) (apply
> (toplevel format) (const #t) (const "~a ~a ~a ~a~%") (lexical lines
> lines-24244) (lexical words words-24245) (lexical chars chars-24246)
> (lexical file file-24242)))))>'

I think you might just be missing a 'call-with-values':

(define (wc-command file)
   (call-with-values
     (call-with-input-file file lines+chars)
     (lambda (lines words chars) ...)))

-- 
`~Eric

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

* Re: Adding wc to Bournish
  2016-05-27 13:37   ` Eric Bavier
@ 2016-05-27 15:28     ` Taylan Ulrich Bayırlı/Kammer
  2016-05-27 15:32       ` Thompson, David
  0 siblings, 1 reply; 23+ messages in thread
From: Taylan Ulrich Bayırlı/Kammer @ 2016-05-27 15:28 UTC (permalink / raw)
  To: Eric Bavier; +Cc: guix-devel

Eric Bavier <ericbavier@openmailbox.org> writes:

> On 2016-05-26 14:27, Efraim Flashner wrote:
>> Here's where I'm currently at with 'wc'. Currently I'm getting an error
>> about having the wrong number of arguments for lambda (I assume)
> [...]
>> +(define (wc-command file)
>> +  ((lambda (lines words chars) ; lambda doesn't like 3 variables
>> +     (format #t "~a ~a ~a ~a~%" lines words chars file))
>> +   (call-with-input-file file lines+chars)))
> [...]
>>
>> ;;; /home/efraim/workspace/guix/guix/build/bournish.scm:143:2: warning:
>> wrong number of arguments to `#<tree-il (lambda () (lambda-case
>> (((lines
>> words chars) #f #f #f () (lines-24244 words-24245 chars-24246)) (apply
>> (toplevel format) (const #t) (const "~a ~a ~a ~a~%") (lexical lines
>> lines-24244) (lexical words words-24245) (lexical chars chars-24246)
>> (lexical file file-24242)))))>'
>
> I think you might just be missing a 'call-with-values':
>
> (define (wc-command file)
>   (call-with-values
>     (call-with-input-file file lines+chars)
>     (lambda (lines words chars) ...)))

I believe this should be

    (define (wc-command file)
      (call-with-values
        (lambda ()
          (call-with-input-file file lines+chars))
        (lambda (lines words chars)
          ...)))

since both arguments to call-with-values must be procedures.


By the way, I prefer SRFI-11 let-values (standardized in R7RS):

    (define (wc-command file)
      (let-values (((lines words chars)
                    (call-with-input-file file lines+chars)))
        ...))

The large number of parentheses involved are annoying at first, but as I
used it more often I grew accustomed to it and aren't bothered at all
anymore, neither in writing nor reading the code.  I also had some valid
uses of let*-values occasionally; I find it neat how it allows "piping"
a number of different values through a sequence of procedures, without
having to allocate any intermediate data structures.

Taylan

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

* Re: Adding wc to Bournish
  2016-05-27 15:28     ` Taylan Ulrich Bayırlı/Kammer
@ 2016-05-27 15:32       ` Thompson, David
  0 siblings, 0 replies; 23+ messages in thread
From: Thompson, David @ 2016-05-27 15:32 UTC (permalink / raw)
  To: Taylan Ulrich Bayırlı/Kammer; +Cc: guix-devel

On Fri, May 27, 2016 at 11:28 AM, Taylan Ulrich Bayırlı/Kammer
<taylanbayirli@gmail.com> wrote:

> By the way, I prefer SRFI-11 let-values (standardized in R7RS):
>
>     (define (wc-command file)
>       (let-values (((lines words chars)
>                     (call-with-input-file file lines+chars)))
>         ...))
>
> The large number of parentheses involved are annoying at first, but as I
> used it more often I grew accustomed to it and aren't bothered at all
> anymore, neither in writing nor reading the code.  I also had some valid
> uses of let*-values occasionally; I find it neat how it allows "piping"
> a number of different values through a sequence of procedures, without
> having to allocate any intermediate data structures.

I second this.  I got very good mileage out of let*-values while
making my game for the last Lisp Game Jam.

https://git.dthompson.us/lisp-game-jam-2016-spring.git/blob/HEAD:/lisparuga/world.scm#l273

- Dave

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

* Re: Adding wc to Bournish
  2016-05-24 18:47 Adding wc to Bournish Efraim Flashner
                   ` (3 preceding siblings ...)
  2016-05-26 19:27 ` Efraim Flashner
@ 2016-06-05 12:40 ` Efraim Flashner
  2016-06-05 20:37   ` Ludovic Courtès
  4 siblings, 1 reply; 23+ messages in thread
From: Efraim Flashner @ 2016-06-05 12:40 UTC (permalink / raw)
  To: guix-devel, Ludovic =?utf-8?Q?Court=C3=A8s?=, Eric Bavier


[-- Attachment #1.1: Type: text/plain, Size: 682 bytes --]

I've been working more on this and I think it's much better now.
Currently it checks for `-l' or `-c' flags in the first position and
with some borrowing of code from the ls-command I've worked out
returning an error (out of order) when a file doesn't exist. And if
there's no flag then it attempts to read the first argument as a file
also.

bournish@(guile-user)> wc -l gpl-3.0.txt foo guix_cve
foo: No such file or directory
674 gpl-3.0.txt
25 guix_cve


-- 
Efraim Flashner   <efraim@flashner.co.il>   אפרים פלשנר
GPG key = A28B F40C 3E55 1372 662D  14F7 41AA E7DC CA3D 8351
Confidentiality cannot be guaranteed on emails sent or received unencrypted

[-- Attachment #1.2: 0001-Bournish-Add-wc-command.patch --]
[-- Type: text/plain, Size: 3826 bytes --]

From 0c37b8106ca8cf65e86029bdf89dfe50b7df8f87 Mon Sep 17 00:00:00 2001
From: Efraim Flashner <efraim@flashner.co.il>
Date: Sun, 22 May 2016 14:56:06 +0300
Subject: [PATCH] Bournish: Add `wc' command.

* guix/build/bournish.scm (%commands): Add wc command.
---
 guix/build/bournish.scm | 63 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 62 insertions(+), 1 deletion(-)

diff --git a/guix/build/bournish.scm b/guix/build/bournish.scm
index 4022796..480ce5f 100644
--- a/guix/build/bournish.scm
+++ b/guix/build/bournish.scm
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2016 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2016 Efraim Flashner <efraim@flashner.co.il>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -25,7 +26,9 @@
   #:use-module (ice-9 match)
   #:use-module (ice-9 ftw)
   #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-11)
   #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-41)
   #:export (%bournish-language))
 
 ;;; Commentary:
@@ -103,6 +106,63 @@ characters."
        ((@ (guix build utils) dump-port) port (current-output-port))
        *unspecified*)))
 
+(define (file-size file)
+    (stat:size (stat file)))
+
+(define (wc-c-command file)
+  ;; Faster when only `wc -c' is called
+  (file-size file))
+
+(define (wc-l-command file)
+  ;; Faster when only `wc -l' is called
+  (stream-length
+    (stream-filter
+      (lambda (chr)
+        (char=? chr #\newline))
+      (port->stream (open-file file "r")))))
+
+(define (lines+chars port)
+  ;; Return the number of lines and number of chars read from PORT.
+  ;; TODO: Also return the number of words.
+  (let loop ((lines 0) (chars 0))
+    (match (read-char port) ; get the next char ready
+      ((? eof-object?)              ;done!
+       (values lines chars))
+      (#\newline                    ;recurse
+       (loop (1+ lines) (1+ chars)))
+      (_                            ;recurse
+       (loop lines (1+ chars))))))
+
+(define* (wc-command-implementation file #:optional args)
+  (let-values (((lines chars)
+                (call-with-input-file file lines+chars)))
+    (match args
+      (#\l
+       (format #t "~a ~a~%" lines file))
+      (#\c
+       (format #t "~a ~a~%" chars file))
+      (_
+       (format #t "~a ~a ~a~%" lines chars file)))))
+
+(define (wc-command args . rest)
+  (let* ((flags (cond ((string=? args "-l") #\l)
+                      ((string=? args "-c") #\c)
+                      (else #\nul)))    ; no flags, "args" is a file
+         (files (filter (lambda (file)
+                          (catch 'system-error
+                            (lambda ()
+                              (lstat file))
+                            (lambda args
+                               (let ((errno (system-error-errno args)))
+                                (format (current-error-port) "~a: ~a~%"
+                                        file (strerror errno))
+                                #f))))
+                        (if (char=? flags #\nul) (cons args rest) rest))))
+    (for-each
+      (lambda (file)
+        ((@@ (guix build bournish) wc-command-implementation) file flags))
+    files)))
+
 (define (help-command . _)
   (display "\
 Hello, this is Bournish, a minimal Bourne-like shell in Guile!
@@ -129,7 +189,8 @@ commands such as 'ls' and 'cd'; it lacks globbing, pipes---everything.\n"))
     ("help"   ,help-command)
     ("ls"     ,ls-command)
     ("which"  ,which-command)
-    ("cat"    ,cat-command)))
+    ("cat"    ,cat-command)
+    ("wc"     ,wc-command)))
 
 (define (read-bournish port env)
   "Read a Bournish expression from PORT, and return the corresponding Scheme
-- 
2.8.3


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

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

* Re: Adding wc to Bournish
  2016-06-05 12:40 ` Efraim Flashner
@ 2016-06-05 20:37   ` Ludovic Courtès
  2016-06-07  7:41     ` Efraim Flashner
  0 siblings, 1 reply; 23+ messages in thread
From: Ludovic Courtès @ 2016-06-05 20:37 UTC (permalink / raw)
  To: Efraim Flashner; +Cc: guix-devel

Efraim Flashner <efraim@flashner.co.il> skribis:

> I've been working more on this and I think it's much better now.
> Currently it checks for `-l' or `-c' flags in the first position and
> with some borrowing of code from the ls-command I've worked out
> returning an error (out of order) when a file doesn't exist. And if
> there's no flag then it attempts to read the first argument as a file
> also.

Neat, thanks for the update!

> From 0c37b8106ca8cf65e86029bdf89dfe50b7df8f87 Mon Sep 17 00:00:00 2001
> From: Efraim Flashner <efraim@flashner.co.il>
> Date: Sun, 22 May 2016 14:56:06 +0300
> Subject: [PATCH] Bournish: Add `wc' command.
>
> * guix/build/bournish.scm (%commands): Add wc command.

Incomplete.  :-)

[...]

> +(define (file-size file)
> +    (stat:size (stat file)))
> +
> +(define (wc-c-command file)
> +  ;; Faster when only `wc -c' is called
> +  (file-size file))
> +
> +(define (wc-l-command file)
> +  ;; Faster when only `wc -l' is called
> +  (stream-length
> +    (stream-filter
> +      (lambda (chr)
> +        (char=? chr #\newline))
> +      (port->stream (open-file file "r")))))
> +
> +(define (lines+chars port)
> +  ;; Return the number of lines and number of chars read from PORT.
> +  ;; TODO: Also return the number of words.
> +  (let loop ((lines 0) (chars 0))
> +    (match (read-char port) ; get the next char ready
> +      ((? eof-object?)              ;done!
> +       (values lines chars))
> +      (#\newline                    ;recurse
> +       (loop (1+ lines) (1+ chars)))
> +      (_                            ;recurse
> +       (loop lines (1+ chars))))))
> +
> +(define* (wc-command-implementation file #:optional args)
> +  (let-values (((lines chars)
> +                (call-with-input-file file lines+chars)))
> +    (match args
> +      (#\l
> +       (format #t "~a ~a~%" lines file))
> +      (#\c
> +       (format #t "~a ~a~%" chars file))
> +      (_
> +       (format #t "~a ~a ~a~%" lines chars file)))))
> +
> +(define (wc-command args . rest)
> +  (let* ((flags (cond ((string=? args "-l") #\l)
> +                      ((string=? args "-c") #\c)
> +                      (else #\nul)))    ; no flags, "args" is a file
> +         (files (filter (lambda (file)
> +                          (catch 'system-error
> +                            (lambda ()
> +                              (lstat file))
> +                            (lambda args
> +                               (let ((errno (system-error-errno args)))
> +                                (format (current-error-port) "~a: ~a~%"
> +                                        file (strerror errno))
> +                                #f))))
> +                        (if (char=? flags #\nul) (cons args rest) rest))))
> +    (for-each
> +      (lambda (file)
> +        ((@@ (guix build bournish) wc-command-implementation) file flags))
> +    files)))

As discussed at
<https://lists.gnu.org/archive/html/guix-devel/2016-05/msg00782.html>,
remember that ‘wc-command’ is called by the compiler to generate Scheme
code from the input shell code.  Thus, it must emit code that does the
job.  However, here, it does the job directly, at compilation time, and
emits the result of ‘for-each’ as code.

I’ll commit a couple of fixes for bugs I just found and that prevent us
from doing:

  (compile "ls" #:from %bournish-language #:to 'scheme).

This is useful to clearly understand what code is generated from the
input.

Ludo’.

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

* Re: Adding wc to Bournish
  2016-06-05 20:37   ` Ludovic Courtès
@ 2016-06-07  7:41     ` Efraim Flashner
  2016-06-08 15:43       ` Ludovic Courtès
  0 siblings, 1 reply; 23+ messages in thread
From: Efraim Flashner @ 2016-06-07  7:41 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: guix-devel


[-- Attachment #1.1: Type: text/plain, Size: 3142 bytes --]

On Sun, Jun 05, 2016 at 10:37:12PM +0200, Ludovic Courtès wrote:
> Efraim Flashner <efraim@flashner.co.il> skribis:
> > +
> > +(define* (wc-command-implementation file #:optional args)
> > +  (let-values (((lines chars)
> > +                (call-with-input-file file lines+chars)))
> > +    (match args
> > +      (#\l
> > +       (format #t "~a ~a~%" lines file))
> > +      (#\c
> > +       (format #t "~a ~a~%" chars file))
> > +      (_
> > +       (format #t "~a ~a ~a~%" lines chars file)))))
> > +
> > +(define (wc-command args . rest)
> > +  (let* ((flags (cond ((string=? args "-l") #\l)
> > +                      ((string=? args "-c") #\c)
> > +                      (else #\nul)))    ; no flags, "args" is a file
> > +         (files (filter (lambda (file)
> > +                          (catch 'system-error
> > +                            (lambda ()
> > +                              (lstat file))
> > +                            (lambda args
> > +                               (let ((errno (system-error-errno args)))
> > +                                (format (current-error-port) "~a: ~a~%"
> > +                                        file (strerror errno))
> > +                                #f))))
> > +                        (if (char=? flags #\nul) (cons args rest) rest))))
> > +    (for-each
> > +      (lambda (file)
> > +        ((@@ (guix build bournish) wc-command-implementation) file flags))
> > +    files)))
> 
> As discussed at
> <https://lists.gnu.org/archive/html/guix-devel/2016-05/msg00782.html>,
> remember that ‘wc-command’ is called by the compiler to generate Scheme
> code from the input shell code.  Thus, it must emit code that does the
> job.  However, here, it does the job directly, at compilation time, and
> emits the result of ‘for-each’ as code.

copied from that email:
Thus, you must similarly distinguish those two stages by providing:

1. A ‘wc-command-implementation’ procedure that implements ‘wc’;

2. A ‘wc-command’ procedure that emits the code that calls
wc-command-implementation’; so something like:

(define (wc-command args)
 `((@@ (guix build bournish) wc-command-implementation) ,@args))

Better yet, ‘wc-command’ could check for the presence of “-l” or “-c” at
compile time and emit a call to the right thing.

> 
> I’ll commit a couple of fixes for bugs I just found and that prevent us
> from doing:
> 
>   (compile "ls" #:from %bournish-language #:to 'scheme).
> 
> This is useful to clearly understand what code is generated from the
> input.
> 
> Ludo’.

I've refactored the code so now in wc-command it checks if theres a flag
or not, and then passes the list of files to wc-command-implementation
to do the actual computation.

Does it make sense to switch it to something like the case-lambda setup
that `ls' uses or is for-each ok?

-- 
Efraim Flashner   <efraim@flashner.co.il>   אפרים פלשנר
GPG key = A28B F40C 3E55 1372 662D  14F7 41AA E7DC CA3D 8351
Confidentiality cannot be guaranteed on emails sent or received unencrypted

[-- Attachment #1.2: 0001-bournish-Add-wc-command.patch --]
[-- Type: text/plain, Size: 4065 bytes --]

From ebce5076177314bfd17a53019b3f6b6888762b01 Mon Sep 17 00:00:00 2001
From: Efraim Flashner <efraim@flashner.co.il>
Date: Sun, 22 May 2016 14:56:06 +0300
Subject: [PATCH] bournish: Add `wc' command.

* guix/build/bournish.scm (file-size, wc-c-command, wc-l-command,
lines+chars, wc-command, wc-command-implementation): New variables.
(%commands): Add wc command.
---
 guix/build/bournish.scm | 64 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 63 insertions(+), 1 deletion(-)

diff --git a/guix/build/bournish.scm b/guix/build/bournish.scm
index 1f17e0a..46e6e1c 100644
--- a/guix/build/bournish.scm
+++ b/guix/build/bournish.scm
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2016 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2016 Efraim Flashner <efraim@flashner.co.il>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -25,7 +26,9 @@
   #:use-module (ice-9 match)
   #:use-module (ice-9 ftw)
   #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-11)
   #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-41)
   #:export (%bournish-language))
 
 ;;; Commentary:
@@ -103,6 +106,64 @@ characters."
        ((@ (guix build utils) dump-port) port (current-output-port))
        *unspecified*)))
 
+(define (file-size file)
+    (stat:size (stat file)))
+
+(define (wc-c-command file)
+  ;; Faster when only `wc -c' is called
+  (file-size file))
+
+(define (wc-l-command file)
+  ;; Faster when only `wc -l' is called
+  (stream-length
+    (stream-filter
+      (lambda (chr)
+        (char=? chr #\newline))
+      (port->stream (open-file file "r")))))
+
+(define (lines+chars port)
+  ;; Return the number of lines and number of chars read from PORT.
+  ;; TODO: Also return the number of words.
+  (let loop ((lines 0) (chars 0))
+    (match (read-char port) ; get the next char ready
+      ((? eof-object?)              ;done!
+       (values lines chars))
+      (#\newline                    ;recurse
+       (loop (1+ lines) (1+ chars)))
+      (_                            ;recurse
+       (loop lines (1+ chars))))))
+
+(define* (wc-command-implementation filelist #:optional args)
+  (let ((files (filter (lambda (file)
+                         (catch 'system-error
+                           (lambda ()
+                             (lstat file))
+                           (lambda args
+                              (let ((errno (system-error-errno args)))
+                               (format (current-error-port) "~a: ~a~%"
+                                       file (strerror errno))
+                               #f))))
+                       filelist)))
+    (for-each
+      (lambda (file)
+        (let-values (((lines chars)
+                      (call-with-input-file file lines+chars)))
+                    (match args
+                      (#\l
+                       (format #t "~a ~a~%" lines file))
+                      (#\c
+                       (format #t "~a ~a~%" chars file))
+                      (_
+                       (format #t "~a ~a ~a~%" lines chars file)))))
+      files)))
+
+(define (wc-command args . rest)
+  (let* ((flags (cond ((string=? args "-l") #\l)
+                      ((string=? args "-c") #\c)
+                      (else #\nul))))    ; no flags, "args" is a file
+    ((@@ (guix build bournish) wc-command-implementation)
+     (if (char=? flags #\nul) (cons args rest) rest) flags)))
+
 (define (help-command . _)
   (display "\
 Hello, this is Bournish, a minimal Bourne-like shell in Guile!
@@ -129,7 +190,8 @@ commands such as 'ls' and 'cd'; it lacks globbing, pipes---everything.\n"))
     ("help"   ,help-command)
     ("ls"     ,ls-command)
     ("which"  ,which-command)
-    ("cat"    ,cat-command)))
+    ("cat"    ,cat-command)
+    ("wc"     ,wc-command)))
 
 (define (read-bournish port env)
   "Read a Bournish expression from PORT, and return the corresponding Scheme
-- 
2.8.3


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

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

* Re: Adding wc to Bournish
  2016-06-07  7:41     ` Efraim Flashner
@ 2016-06-08 15:43       ` Ludovic Courtès
  2016-06-14  9:27         ` Efraim Flashner
  0 siblings, 1 reply; 23+ messages in thread
From: Ludovic Courtès @ 2016-06-08 15:43 UTC (permalink / raw)
  To: Efraim Flashner; +Cc: guix-devel

Efraim Flashner <efraim@flashner.co.il> skribis:

> On Sun, Jun 05, 2016 at 10:37:12PM +0200, Ludovic Courtès wrote:

[...]

>> I’ll commit a couple of fixes for bugs I just found and that prevent us
>> from doing:
>> 
>>   (compile "ls" #:from %bournish-language #:to 'scheme).

(This should be be ‘read-and-compile’, not ‘compile’.)

Done in f82c58539e1f7b9b864e68ea2ab0c6a17c15fbb5.  Take a look at
tests/bournish.scm for examples of what is expected.

> From ebce5076177314bfd17a53019b3f6b6888762b01 Mon Sep 17 00:00:00 2001
> From: Efraim Flashner <efraim@flashner.co.il>
> Date: Sun, 22 May 2016 14:56:06 +0300
> Subject: [PATCH] bournish: Add `wc' command.
>
> * guix/build/bournish.scm (file-size, wc-c-command, wc-l-command,
> lines+chars, wc-command, wc-command-implementation): New variables.
> (%commands): Add wc command.

[...]

> +(define* (wc-command-implementation filelist #:optional args)

‘files’, not ‘filelist’.

> +  (let ((files (filter (lambda (file)
> +                         (catch 'system-error
> +                           (lambda ()
> +                             (lstat file))
> +                           (lambda args
> +                              (let ((errno (system-error-errno args)))
> +                               (format (current-error-port) "~a: ~a~%"
> +                                       file (strerror errno))
> +                               #f))))

‘stat’ rather than ‘fstat’.

> +    (for-each
> +      (lambda (file)
> +        (let-values (((lines chars)
> +                      (call-with-input-file file lines+chars)))
> +                    (match args
> +                      (#\l
> +                       (format #t "~a ~a~%" lines file))
> +                      (#\c
> +                       (format #t "~a ~a~%" chars file))
> +                      (_
> +                       (format #t "~a ~a ~a~%" lines chars file)))))
> +      files)))

OK.

> +(define (wc-command args . rest)
> +  (let* ((flags (cond ((string=? args "-l") #\l)
> +                      ((string=? args "-c") #\c)
> +                      (else #\nul))))    ; no flags, "args" is a file

I’d rather make it:

  (define (wc-commands . args)
    (cond ((member "-l" args) …)
          ((member "-c" args) …)
          (else …)))

Instead of the #\nul thing, I think it’d be best to have separate
procedures for -l, -c, and the other case.

> +    ((@@ (guix build bournish) wc-command-implementation)
> +     (if (char=? flags #\nul) (cons args rest) rest) flags)))

This is still not emitting code.  :-)  IOW, there should be a quasiquote
here.

You can see that by running:

  (use-modules (system base compile) (guix build bournish))
  (read-and-compile (open-input-string "wc -l foo")
                    #:from %bournish-language #:to 'scheme)

This should return something like:

  `((@ (guix build bournish) wc-l-command-implementation) '("foo"))

Makes sense?  We’re almost done.

Please take a look at
<https://www.gnu.org/software/guix/manual/html_node/Coding-Style.html>
to make the last review super fast.  ;-)

Thank you!

Ludo’.

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

* Re: Adding wc to Bournish
  2016-06-08 15:43       ` Ludovic Courtès
@ 2016-06-14  9:27         ` Efraim Flashner
  2016-06-14  9:57           ` Ricardo Wurmus
  0 siblings, 1 reply; 23+ messages in thread
From: Efraim Flashner @ 2016-06-14  9:27 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: guix-devel


[-- Attachment #1.1: Type: text/plain, Size: 4552 bytes --]

On Wed, Jun 08, 2016 at 05:43:09PM +0200, Ludovic Courtès wrote:
> Efraim Flashner <efraim@flashner.co.il> skribis:
> 
> > On Sun, Jun 05, 2016 at 10:37:12PM +0200, Ludovic Courtès wrote:
> 
> [...]
> 
> >> I’ll commit a couple of fixes for bugs I just found and that prevent us
> >> from doing:
> >> 
> >>   (compile "ls" #:from %bournish-language #:to 'scheme).
> 
> (This should be be ‘read-and-compile’, not ‘compile’.)
> 
> Done in f82c58539e1f7b9b864e68ea2ab0c6a17c15fbb5.  Take a look at
> tests/bournish.scm for examples of what is expected.
> 

ok

> > From ebce5076177314bfd17a53019b3f6b6888762b01 Mon Sep 17 00:00:00 2001
> > From: Efraim Flashner <efraim@flashner.co.il>
> > Date: Sun, 22 May 2016 14:56:06 +0300
> > Subject: [PATCH] bournish: Add `wc' command.
> >
> > * guix/build/bournish.scm (file-size, wc-c-command, wc-l-command,
> > lines+chars, wc-command, wc-command-implementation): New variables.
> > (%commands): Add wc command.
> 
> [...]
> 
> > +(define* (wc-command-implementation filelist #:optional args)
> 
> ‘files’, not ‘filelist’.
> 

ok

> > +  (let ((files (filter (lambda (file)
> > +                         (catch 'system-error
> > +                           (lambda ()
> > +                             (lstat file))
> > +                           (lambda args
> > +                              (let ((errno (system-error-errno args)))
> > +                               (format (current-error-port) "~a: ~a~%"
> > +                                       file (strerror errno))
> > +                               #f))))
> 
> ‘stat’ rather than ‘fstat’.
> 

ok

> > +    (for-each
> > +      (lambda (file)
> > +        (let-values (((lines chars)
> > +                      (call-with-input-file file lines+chars)))
> > +                    (match args
> > +                      (#\l
> > +                       (format #t "~a ~a~%" lines file))
> > +                      (#\c
> > +                       (format #t "~a ~a~%" chars file))
> > +                      (_
> > +                       (format #t "~a ~a ~a~%" lines chars file)))))
> > +      files)))
> 
> OK.
> 

In the end I went with 3 separate functions for the three commands
(`wc', `wc -l', `wc -c') like you mentioned later.

> > +(define (wc-command args . rest)
> > +  (let* ((flags (cond ((string=? args "-l") #\l)
> > +                      ((string=? args "-c") #\c)
> > +                      (else #\nul))))    ; no flags, "args" is a file
> 
> I’d rather make it:
> 
>   (define (wc-commands . args)
>     (cond ((member "-l" args) …)
>           ((member "-c" args) …)
>           (else …)))
> 
> Instead of the #\nul thing, I think it’d be best to have separate
> procedures for -l, -c, and the other case.

This also looks nicer, and I was able to strip the `-l' or `-c' before
passing the ,args to the implementation function.

> 
> > +    ((@@ (guix build bournish) wc-command-implementation)
> > +     (if (char=? flags #\nul) (cons args rest) rest) flags)))
> 
> This is still not emitting code.  :-)  IOW, there should be a quasiquote
> here.
> 
> You can see that by running:
> 
>   (use-modules (system base compile) (guix build bournish))
>   (read-and-compile (open-input-string "wc -l foo")
>                     #:from %bournish-language #:to 'scheme)
> 
> This should return something like:
> 
>   `((@ (guix build bournish) wc-l-command-implementation) '("foo"))
> 

got this one fixed, which in turn broke the implementation code ;) In
the end I went with not a list, so it came out like this:

scheme@(guile-user)> (read-and-compile (open-input-string "wc -l foo bar baz") #:from %bournish-language #:to 'scheme)
$1 = ((@@ (guix build bournish) wc-l-command-implementation) "foo" "bar" "baz")

> Makes sense?  We’re almost done.
> 
> Please take a look at
> <https://www.gnu.org/software/guix/manual/html_node/Coding-Style.html>
> to make the last review super fast.  ;-)
> 
> Thank you!
> 
> Ludo’.

I've attached what hopefully is the last patch for wc :) I took (and put
somewhere safe) the other code I wrote that does wc-c by calling (stat
file) and the one for wc-l that opens the file as a port and reads for
#\newlines.

-- 
Efraim Flashner   <efraim@flashner.co.il>   אפרים פלשנר
GPG key = A28B F40C 3E55 1372 662D  14F7 41AA E7DC CA3D 8351
Confidentiality cannot be guaranteed on emails sent or received unencrypted

[-- Attachment #1.2: 0001-bournish-Add-wc-command.patch --]
[-- Type: text/plain, Size: 5043 bytes --]

From 60a473e123a5da4e3592c67aae8002bf11cca633 Mon Sep 17 00:00:00 2001
From: Efraim Flashner <efraim@flashner.co.il>
Date: Sun, 22 May 2016 14:56:06 +0300
Subject: [PATCH] bournish: Add `wc' command.

* guix/build/bournish.scm (lines+chars, wc-commands,
wc-command-implementation, wc-l-command-implementation,
wc-c-command-implementation): New variables.
(%commands): Add wc command.
---
 guix/build/bournish.scm | 79 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 78 insertions(+), 1 deletion(-)

diff --git a/guix/build/bournish.scm b/guix/build/bournish.scm
index 1f17e0a..a4c5b83 100644
--- a/guix/build/bournish.scm
+++ b/guix/build/bournish.scm
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2016 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2016 Efraim Flashner <efraim@flashner.co.il>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -25,6 +26,7 @@
   #:use-module (ice-9 match)
   #:use-module (ice-9 ftw)
   #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-11)
   #:use-module (srfi srfi-26)
   #:export (%bournish-language))
 
@@ -103,6 +105,80 @@ characters."
        ((@ (guix build utils) dump-port) port (current-output-port))
        *unspecified*)))
 
+(define (lines+chars port)
+  ;; Return the number of lines and number of chars read from PORT.
+  ;; TODO: Also return the number of words.
+  (let loop ((lines 0) (chars 0))
+    (match (read-char port) ; get the next char ready
+      ((? eof-object?)              ;done!
+       (values lines chars))
+      (#\newline                    ;recurse
+       (loop (1+ lines) (1+ chars)))
+      (_                            ;recurse
+       (loop lines (1+ chars))))))
+
+(define wc-command-implementation
+  (lambda files
+    (let ((files (filter (lambda (file)
+                           (catch 'system-error
+                             (lambda ()
+                               (stat file))
+                             (lambda args
+                               (let ((errno (system-error-errno args)))
+                                 (format (current-error-port) "~a: ~a~%"
+                                         file (strerror errno))
+                                 #f))))
+                         files)))
+      (for-each
+        (lambda (file)
+          (let-values (((lines chars)
+                        (call-with-input-file file lines+chars)))
+                      (format #t "~a ~a ~a~%" lines chars file)))
+        files))))
+
+(define wc-l-command-implementation
+  (lambda files
+    (let ((files (filter (lambda (file)
+                           (catch 'system-error
+                             (lambda ()
+                               (stat file))
+                             (lambda args
+                               (let ((errno (system-error-errno args)))
+                                 (format (current-error-port) "~a: ~a~%"
+                                         file (strerror errno))
+                                 #f))))
+                         files)))
+      (for-each
+        (lambda (file)
+          (let-values (((lines chars)
+                        (call-with-input-file file lines+chars)))
+                      (format #t "~a ~a~%" lines file)))
+        files))))
+
+(define wc-c-command-implementation
+  (lambda files
+    (let ((files (filter (lambda (file)
+                           (catch 'system-error
+                             (lambda ()
+                               (stat file))
+                             (lambda args
+                               (let ((errno (system-error-errno args)))
+                               (format (current-error-port) "~a: ~a~%"
+                                       file (strerror errno))
+                               #f))))
+                         files)))
+      (for-each
+        (lambda (file)
+          (let-values (((lines chars)
+                        (call-with-input-file file lines+chars)))
+                      (format #t "~a ~a~%" chars file)))
+        files))))
+
+(define (wc-commands . args)
+  (cond ((member "-l" args) `((@@ (guix build bournish) wc-l-command-implementation) ,@(delete "-l" args)))
+        ((member "-c" args) `((@@ (guix build bournish) wc-c-command-implementation) ,@(delete "-c" args)))
+        (else `((@@ (guix build bournish) wc-command-implementation) ,@args))))
+
 (define (help-command . _)
   (display "\
 Hello, this is Bournish, a minimal Bourne-like shell in Guile!
@@ -129,7 +205,8 @@ commands such as 'ls' and 'cd'; it lacks globbing, pipes---everything.\n"))
     ("help"   ,help-command)
     ("ls"     ,ls-command)
     ("which"  ,which-command)
-    ("cat"    ,cat-command)))
+    ("cat"    ,cat-command)
+    ("wc"     ,wc-commands)))
 
 (define (read-bournish port env)
   "Read a Bournish expression from PORT, and return the corresponding Scheme
-- 
2.8.4


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

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

* Re: Adding wc to Bournish
  2016-06-14  9:27         ` Efraim Flashner
@ 2016-06-14  9:57           ` Ricardo Wurmus
  2016-06-14 10:20             ` Efraim Flashner
  0 siblings, 1 reply; 23+ messages in thread
From: Ricardo Wurmus @ 2016-06-14  9:57 UTC (permalink / raw)
  To: Efraim Flashner; +Cc: guix-devel


Efraim Flashner <efraim@flashner.co.il> writes:

> +(define wc-command-implementation
> +  (lambda files
> +    (let ((files (filter (lambda (file)
> +                           (catch 'system-error
> +                             (lambda ()
> +                               (stat file))
> +                             (lambda args
> +                               (let ((errno (system-error-errno args)))
> +                                 (format (current-error-port) "~a: ~a~%"
> +                                         file (strerror errno))
> +                                 #f))))
> +                         files)))
> +      (for-each
> +        (lambda (file)
> +          (let-values (((lines chars)
> +                        (call-with-input-file file lines+chars)))
> +                      (format #t "~a ~a ~a~%" lines chars file)))
> +        files))))
> +
> +(define wc-l-command-implementation
> +  (lambda files
> +    (let ((files (filter (lambda (file)
> +                           (catch 'system-error
> +                             (lambda ()
> +                               (stat file))
> +                             (lambda args
> +                               (let ((errno (system-error-errno args)))
> +                                 (format (current-error-port) "~a: ~a~%"
> +                                         file (strerror errno))
> +                                 #f))))
> +                         files)))
> +      (for-each
> +        (lambda (file)
> +          (let-values (((lines chars)
> +                        (call-with-input-file file lines+chars)))
> +                      (format #t "~a ~a~%" lines file)))
> +        files))))
> +
> +(define wc-c-command-implementation
> +  (lambda files
> +    (let ((files (filter (lambda (file)
> +                           (catch 'system-error
> +                             (lambda ()
> +                               (stat file))
> +                             (lambda args
> +                               (let ((errno (system-error-errno args)))
> +                               (format (current-error-port) "~a: ~a~%"
> +                                       file (strerror errno))
> +                               #f))))
> +                         files)))
> +      (for-each
> +        (lambda (file)
> +          (let-values (((lines chars)
> +                        (call-with-input-file file lines+chars)))
> +                      (format #t "~a ~a~%" chars file)))
> +        files))))

It looks to me that the filter function is the same in all of these
procedures.  Even the actual implementation, i.e. the for-each over the
resulting files is almost exactly the same.

This could be simplified.  If only the format expression differs then
you could abstract this difference away.  You could still have three
different procedures, but they can be the result of evaluating a
higher-order function.

It also seems to me that you could use syntactic sugar to simplify
“(define something (lambda ...))” to “(define (something ...))”.

~~ Ricardo

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

* Re: Adding wc to Bournish
  2016-06-14  9:57           ` Ricardo Wurmus
@ 2016-06-14 10:20             ` Efraim Flashner
  2016-06-14 10:50               ` Efraim Flashner
  0 siblings, 1 reply; 23+ messages in thread
From: Efraim Flashner @ 2016-06-14 10:20 UTC (permalink / raw)
  To: Ricardo Wurmus; +Cc: guix-devel

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

On Tue, Jun 14, 2016 at 11:57:26AM +0200, Ricardo Wurmus wrote:
> 
> Efraim Flashner <efraim@flashner.co.il> writes:
> 
> > +(define wc-command-implementation
> > +  (lambda files
> > +    (let ((files (filter (lambda (file)
> > +                           (catch 'system-error
> > +                             (lambda ()
> > +                               (stat file))
> > +                             (lambda args
> > +                               (let ((errno (system-error-errno args)))
> > +                                 (format (current-error-port) "~a: ~a~%"
> > +                                         file (strerror errno))
> > +                                 #f))))
> > +                         files)))
> > +      (for-each
> > +        (lambda (file)
> > +          (let-values (((lines chars)
> > +                        (call-with-input-file file lines+chars)))
> > +                      (format #t "~a ~a ~a~%" lines chars file)))
> > +        files))))
> > +
> > +(define wc-l-command-implementation
> > +  (lambda files
> > +    (let ((files (filter (lambda (file)
> > +                           (catch 'system-error
> > +                             (lambda ()
> > +                               (stat file))
> > +                             (lambda args
> > +                               (let ((errno (system-error-errno args)))
> > +                                 (format (current-error-port) "~a: ~a~%"
> > +                                         file (strerror errno))
> > +                                 #f))))
> > +                         files)))
> > +      (for-each
> > +        (lambda (file)
> > +          (let-values (((lines chars)
> > +                        (call-with-input-file file lines+chars)))
> > +                      (format #t "~a ~a~%" lines file)))
> > +        files))))
> > +
> > +(define wc-c-command-implementation
> > +  (lambda files
> > +    (let ((files (filter (lambda (file)
> > +                           (catch 'system-error
> > +                             (lambda ()
> > +                               (stat file))
> > +                             (lambda args
> > +                               (let ((errno (system-error-errno args)))
> > +                               (format (current-error-port) "~a: ~a~%"
> > +                                       file (strerror errno))
> > +                               #f))))
> > +                         files)))
> > +      (for-each
> > +        (lambda (file)
> > +          (let-values (((lines chars)
> > +                        (call-with-input-file file lines+chars)))
> > +                      (format #t "~a ~a~%" chars file)))
> > +        files))))
> 
> It looks to me that the filter function is the same in all of these
> procedures.  Even the actual implementation, i.e. the for-each over the
> resulting files is almost exactly the same.
> 
> This could be simplified.  If only the format expression differs then
> you could abstract this difference away.  You could still have three
> different procedures, but they can be the result of evaluating a
> higher-order function.
> 
> It also seems to me that you could use syntactic sugar to simplify
> “(define something (lambda ...))” to “(define (something ...))”.
> 
> ~~ Ricardo

It's already calling `((@@ (guix build bournish)
wc-l-command-implementation) ,@(delete "-l" args)), I could try changing
the ,@(delete part to ,@((@@ (guix build bournish) only-files) ,@(delete
"-l" args)) and then the various implementation functions will be just
printing the results

-- 
Efraim Flashner   <efraim@flashner.co.il>   אפרים פלשנר
GPG key = A28B F40C 3E55 1372 662D  14F7 41AA E7DC CA3D 8351
Confidentiality cannot be guaranteed on emails sent or received unencrypted

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

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

* Re: Adding wc to Bournish
  2016-06-14 10:20             ` Efraim Flashner
@ 2016-06-14 10:50               ` Efraim Flashner
  2016-06-14 11:08                 ` Ricardo Wurmus
  2016-06-15 13:56                 ` Ludovic Courtès
  0 siblings, 2 replies; 23+ messages in thread
From: Efraim Flashner @ 2016-06-14 10:50 UTC (permalink / raw)
  To: Ricardo Wurmus; +Cc: guix-devel


[-- Attachment #1.1: Type: text/plain, Size: 4374 bytes --]

On Tue, Jun 14, 2016 at 01:20:29PM +0300, Efraim Flashner wrote:
> On Tue, Jun 14, 2016 at 11:57:26AM +0200, Ricardo Wurmus wrote:
> > 
> > Efraim Flashner <efraim@flashner.co.il> writes:
> > 
> > > +(define wc-command-implementation
> > > +  (lambda files
> > > +    (let ((files (filter (lambda (file)
> > > +                           (catch 'system-error
> > > +                             (lambda ()
> > > +                               (stat file))
> > > +                             (lambda args
> > > +                               (let ((errno (system-error-errno args)))
> > > +                                 (format (current-error-port) "~a: ~a~%"
> > > +                                         file (strerror errno))
> > > +                                 #f))))
> > > +                         files)))
> > > +      (for-each
> > > +        (lambda (file)
> > > +          (let-values (((lines chars)
> > > +                        (call-with-input-file file lines+chars)))
> > > +                      (format #t "~a ~a ~a~%" lines chars file)))
> > > +        files))))
> > > +
> > > +(define wc-l-command-implementation
> > > +  (lambda files
> > > +    (let ((files (filter (lambda (file)
> > > +                           (catch 'system-error
> > > +                             (lambda ()
> > > +                               (stat file))
> > > +                             (lambda args
> > > +                               (let ((errno (system-error-errno args)))
> > > +                                 (format (current-error-port) "~a: ~a~%"
> > > +                                         file (strerror errno))
> > > +                                 #f))))
> > > +                         files)))
> > > +      (for-each
> > > +        (lambda (file)
> > > +          (let-values (((lines chars)
> > > +                        (call-with-input-file file lines+chars)))
> > > +                      (format #t "~a ~a~%" lines file)))
> > > +        files))))
> > > +
> > > +(define wc-c-command-implementation
> > > +  (lambda files
> > > +    (let ((files (filter (lambda (file)
> > > +                           (catch 'system-error
> > > +                             (lambda ()
> > > +                               (stat file))
> > > +                             (lambda args
> > > +                               (let ((errno (system-error-errno args)))
> > > +                               (format (current-error-port) "~a: ~a~%"
> > > +                                       file (strerror errno))
> > > +                               #f))))
> > > +                         files)))
> > > +      (for-each
> > > +        (lambda (file)
> > > +          (let-values (((lines chars)
> > > +                        (call-with-input-file file lines+chars)))
> > > +                      (format #t "~a ~a~%" chars file)))
> > > +        files))))
> > 
> > It looks to me that the filter function is the same in all of these
> > procedures.  Even the actual implementation, i.e. the for-each over the
> > resulting files is almost exactly the same.
> > 
> > This could be simplified.  If only the format expression differs then
> > you could abstract this difference away.  You could still have three
> > different procedures, but they can be the result of evaluating a
> > higher-order function.
> > 
> > It also seems to me that you could use syntactic sugar to simplify
> > “(define something (lambda ...))” to “(define (something ...))”.
> > 
> > ~~ Ricardo
> 
> It's already calling `((@@ (guix build bournish)
> wc-l-command-implementation) ,@(delete "-l" args)), I could try changing
> the ,@(delete part to ,@((@@ (guix build bournish) only-files) ,@(delete
> "-l" args)) and then the various implementation functions will be just
> printing the results
> 

It turns out I forgot that calling only-files from wc-commands would
make it evaluate too soon, so I stuck it inside the different
implementations. Factoring out the check if a file exists or not could
also apply to the ls-command-implementation too later.


-- 
Efraim Flashner   <efraim@flashner.co.il>   אפרים פלשנר
GPG key = A28B F40C 3E55 1372 662D  14F7 41AA E7DC CA3D 8351
Confidentiality cannot be guaranteed on emails sent or received unencrypted

[-- Attachment #1.2: 0001-bournish-Add-wc-command.patch --]
[-- Type: text/plain, Size: 3926 bytes --]

From 09eef9cd841a7d212e024be0609168611923696b Mon Sep 17 00:00:00 2001
From: Efraim Flashner <efraim@flashner.co.il>
Date: Sun, 22 May 2016 14:56:06 +0300
Subject: [PATCH] bournish: Add `wc' command.

* guix/build/bournish.scm (lines+chars, only-files, wc-commands,
wc-command-implementation, wc-l-command-implementation,
wc-c-command-implementation): New variables.
(%commands): Add wc command.
---
 guix/build/bournish.scm | 58 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 57 insertions(+), 1 deletion(-)

diff --git a/guix/build/bournish.scm b/guix/build/bournish.scm
index 1f17e0a..a8f6591 100644
--- a/guix/build/bournish.scm
+++ b/guix/build/bournish.scm
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2016 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2016 Efraim Flashner <efraim@flashner.co.il>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -25,6 +26,7 @@
   #:use-module (ice-9 match)
   #:use-module (ice-9 ftw)
   #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-11)
   #:use-module (srfi srfi-26)
   #:export (%bournish-language))
 
@@ -103,6 +105,59 @@ characters."
        ((@ (guix build utils) dump-port) port (current-output-port))
        *unspecified*)))
 
+(define (lines+chars port)
+  ;; Return the number of lines and number of chars read from PORT.
+  ;; TODO: Also return the number of words.
+  (let loop ((lines 0) (chars 0))
+    (match (read-char port) ; get the next char ready
+      ((? eof-object?)              ;done!
+       (values lines chars))
+      (#\newline                    ;recurse
+       (loop (1+ lines) (1+ chars)))
+      (_                            ;recurse
+       (loop lines (1+ chars))))))
+
+(define (only-files files)
+  (filter (lambda (file)
+            (catch 'system-error
+              (lambda ()
+                (stat file))
+              (lambda args
+                (let ((errno (system-error-errno args)))
+                  (format (current-error-port) "~a: ~a~%"
+                          file (strerror errno))
+                  #f))))
+          files))
+
+(define (wc-command-implementation . files)
+  (for-each
+    (lambda (file)
+      (let-values (((lines chars)
+                    (call-with-input-file file lines+chars)))
+                  (format #t "~a ~a ~a~%" lines chars file)))
+    ((@@ (guix build bournish) only-files) files)))
+
+(define (wc-l-command-implementation . files)
+  (for-each
+    (lambda (file)
+      (let-values (((lines chars)
+                    (call-with-input-file file lines+chars)))
+                  (format #t "~a ~a~%" lines file)))
+    ((@@ (guix build bournish) only-files) files)))
+
+(define (wc-c-command-implementation . files)
+  (for-each
+    (lambda (file)
+      (let-values (((lines chars)
+                    (call-with-input-file file lines+chars)))
+                  (format #t "~a ~a~%" chars file)))
+    ((@@ (guix build bournish) only-files) files)))
+
+(define (wc-commands . args)
+  (cond ((member "-l" args) `((@@ (guix build bournish) wc-l-command-implementation) ,@(delete "-l" args)))
+        ((member "-c" args) `((@@ (guix build bournish) wc-c-command-implementation) ,@(delete "-c" args)))
+        (else `((@@ (guix build bournish) wc-command-implementation) ,@args))))
+
 (define (help-command . _)
   (display "\
 Hello, this is Bournish, a minimal Bourne-like shell in Guile!
@@ -129,7 +184,8 @@ commands such as 'ls' and 'cd'; it lacks globbing, pipes---everything.\n"))
     ("help"   ,help-command)
     ("ls"     ,ls-command)
     ("which"  ,which-command)
-    ("cat"    ,cat-command)))
+    ("cat"    ,cat-command)
+    ("wc"     ,wc-commands)))
 
 (define (read-bournish port env)
   "Read a Bournish expression from PORT, and return the corresponding Scheme
-- 
2.8.4


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

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

* Re: Adding wc to Bournish
  2016-06-14 10:50               ` Efraim Flashner
@ 2016-06-14 11:08                 ` Ricardo Wurmus
  2016-06-15 13:56                 ` Ludovic Courtès
  1 sibling, 0 replies; 23+ messages in thread
From: Ricardo Wurmus @ 2016-06-14 11:08 UTC (permalink / raw)
  To: Efraim Flashner; +Cc: guix-devel


Efraim Flashner <efraim@flashner.co.il> writes:

>> It's already calling `((@@ (guix build bournish)
>> wc-l-command-implementation) ,@(delete "-l" args)), I could try changing
>> the ,@(delete part to ,@((@@ (guix build bournish) only-files) ,@(delete
>> "-l" args)) and then the various implementation functions will be just
>> printing the results
>> 
>
> It turns out I forgot that calling only-files from wc-commands would
> make it evaluate too soon, so I stuck it inside the different
> implementations. Factoring out the check if a file exists or not could
> also apply to the ls-command-implementation too later.

I don’t understand this (but don’t let this delay you).  I don’t see any
“only-files” in your patch.  To delay evaluation you can always wrap
expressions in “(lambda _ ...)”.

I was thinking of a first simplification like this:


(define (existing-files file)
  (catch 'system-error
    (lambda ()
      (stat file))
    (lambda args
      (let ((errno (system-error-errno args)))
        (format (current-error-port) "~a: ~a~%"
                file (strerror errno))
        #f))))

(define (wc-print file)
  (let-values (((lines chars)
                (call-with-input-file file lines+chars)))
    (format #t "~a ~a ~a~%" lines chars file)))

(define (wc-l-print file)
  (let-values (((lines chars)
                (call-with-input-file file lines+chars)))
    (format #t "~a ~a~%" lines file)))

(define (wc-c-print file)
  (let-values (((lines chars)
                (call-with-input-file file lines+chars)))
    (format #t "~a ~a~%" chars file)))

(define (wc-command-implementation files)
  (for-each wc-print (filter existing-files files)))

(define (wc-l-command-implementation files)
  (for-each wc-l-print (filter existing-files files)))

(define (wc-c-command-implementation files)
  (for-each wc-c-print (filter existing-files files)))


Even at this point there’s a lot of repetition that we could get rid of,
but maybe that’s too zealous.  Note that I didn’t follow this whole
discussion, so I could be completely off target.

~~ Ricardo

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

* Re: Adding wc to Bournish
  2016-06-14 10:50               ` Efraim Flashner
  2016-06-14 11:08                 ` Ricardo Wurmus
@ 2016-06-15 13:56                 ` Ludovic Courtès
  2016-06-15 20:28                   ` Efraim Flashner
  1 sibling, 1 reply; 23+ messages in thread
From: Ludovic Courtès @ 2016-06-15 13:56 UTC (permalink / raw)
  To: Efraim Flashner; +Cc: guix-devel, David Thompson

Efraim Flashner <efraim@flashner.co.il> skribis:

> From 09eef9cd841a7d212e024be0609168611923696b Mon Sep 17 00:00:00 2001
> From: Efraim Flashner <efraim@flashner.co.il>
> Date: Sun, 22 May 2016 14:56:06 +0300
> Subject: [PATCH] bournish: Add `wc' command.
>
> * guix/build/bournish.scm (lines+chars, only-files, wc-commands,
> wc-command-implementation, wc-l-command-implementation,
> wc-c-command-implementation): New variables.

s/variables/procedures/ :-)

> (%commands): Add wc command.

[...]

> +(define (only-files files)
> +  (filter (lambda (file)
> +            (catch 'system-error
> +              (lambda ()
> +                (stat file))
> +              (lambda args
> +                (let ((errno (system-error-errno args)))
> +                  (format (current-error-port) "~a: ~a~%"
> +                          file (strerror errno))
> +                  #f))))
> +          files))
> +
> +(define (wc-command-implementation . files)
> +  (for-each
> +    (lambda (file)
> +      (let-values (((lines chars)
> +                    (call-with-input-file file lines+chars)))
> +                  (format #t "~a ~a ~a~%" lines chars file)))
> +    ((@@ (guix build bournish) only-files) files)))

I prefer the approach Ricardo suggested, I think it’s more concise and
clearer:

  https://lists.gnu.org/archive/html/guix-devel/2016-06/msg00525.html

Also, note that @@ would not be needed here; @@ serves to access private
bindings within a specific module:

  https://www.gnu.org/software/guile/manual/html_node/Using-Guile-Modules.html

Last, do not miss the bit about docstrings at:

  https://www.gnu.org/software/guix/manual/html_node/Formatting-Code.html

:-)

With these changes, we’re all set.  Thanks!

From a GSoC viewpoint, I think we must move to the compilation part
now.  Specifically, I think the next step is to start parsing Bash
code.

For that we could use SILex + parse-lalr, but these are not the nicest
tools for the job.  Better tools would be “parsing expression grammars”
(the (ice-9 peg) module in Guile 2.1) or parser combinators, though I
don’t think there’s a directly usable Guile library for that.  Maybe
Eric or David can comment?

The goal is to have a parser that returns an abstract syntax tree (AST)
as an sexp:

  (parse "(cd /foo; ls $HOME) && echo *.a ; echo done")
  =>
  '(sequence
     (success-sequence
       (subshell
         (sequence (command "cd" "/foo")
                   (command "ls" (variable-ref "HOME"))))
       (command "echo" (glob "*.a")))
     (command "echo" "done"))

Thoughts?

Ludo’.

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

* Re: Adding wc to Bournish
  2016-06-15 13:56                 ` Ludovic Courtès
@ 2016-06-15 20:28                   ` Efraim Flashner
  2016-06-23  8:34                     ` Ludovic Courtès
  0 siblings, 1 reply; 23+ messages in thread
From: Efraim Flashner @ 2016-06-15 20:28 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: guix-devel, David Thompson


[-- Attachment #1.1: Type: text/plain, Size: 3621 bytes --]

On Wed, Jun 15, 2016 at 03:56:43PM +0200, Ludovic Courtès wrote:
> Efraim Flashner <efraim@flashner.co.il> skribis:
> 
> > From 09eef9cd841a7d212e024be0609168611923696b Mon Sep 17 00:00:00 2001
> > From: Efraim Flashner <efraim@flashner.co.il>
> > Date: Sun, 22 May 2016 14:56:06 +0300
> > Subject: [PATCH] bournish: Add `wc' command.
> >
> > * guix/build/bournish.scm (lines+chars, only-files, wc-commands,
> > wc-command-implementation, wc-l-command-implementation,
> > wc-c-command-implementation): New variables.
> 
> s/variables/procedures/ :-)
> 
> > (%commands): Add wc command.
> 
> [...]
> 
> > +(define (only-files files)
> > +  (filter (lambda (file)
> > +            (catch 'system-error
> > +              (lambda ()
> > +                (stat file))
> > +              (lambda args
> > +                (let ((errno (system-error-errno args)))
> > +                  (format (current-error-port) "~a: ~a~%"
> > +                          file (strerror errno))
> > +                  #f))))
> > +          files))
> > +
> > +(define (wc-command-implementation . files)
> > +  (for-each
> > +    (lambda (file)
> > +      (let-values (((lines chars)
> > +                    (call-with-input-file file lines+chars)))
> > +                  (format #t "~a ~a ~a~%" lines chars file)))
> > +    ((@@ (guix build bournish) only-files) files)))
> 
> I prefer the approach Ricardo suggested, I think it’s more concise and
> clearer:
> 
>   https://lists.gnu.org/archive/html/guix-devel/2016-06/msg00525.html
> 
> Also, note that @@ would not be needed here; @@ serves to access private
> bindings within a specific module:
> 
>   https://www.gnu.org/software/guile/manual/html_node/Using-Guile-Modules.html
> 
> Last, do not miss the bit about docstrings at:
> 
>   https://www.gnu.org/software/guix/manual/html_node/Formatting-Code.html
> 
> :-)
> 
> With these changes, we’re all set.  Thanks!

I've attached another patch

> 
> From a GSoC viewpoint, I think we must move to the compilation part
> now.  Specifically, I think the next step is to start parsing Bash
> code.

This part took much longer than I thought it would. Going forward I'll
try to be more pro-active on IRC about asking for help.

> 
> For that we could use SILex + parse-lalr, but these are not the nicest
> tools for the job.  Better tools would be “parsing expression grammars”
> (the (ice-9 peg) module in Guile 2.1) or parser combinators, though I
> don’t think there’s a directly usable Guile library for that.  Maybe
> Eric or David can comment?
> 

I get lalr as look-ahead left->right parser and found this¹, but what's SILex?
I also found peg in the master branch of the guile manual².

> The goal is to have a parser that returns an abstract syntax tree (AST)
> as an sexp:
> 
>   (parse "(cd /foo; ls $HOME) && echo *.a ; echo done")
>   =>
>   '(sequence
>      (success-sequence
>        (subshell
>          (sequence (command "cd" "/foo")
>                    (command "ls" (variable-ref "HOME"))))
>        (command "echo" (glob "*.a")))
>      (command "echo" "done"))
> 
> Thoughts?
> 
> Ludo’.

¹ https://www.gnu.org/software/guile/manual/html_node/LALR_00281_0029-Parsing.html#LALR_00281_0029-Parsing
² https://www.gnu.org/software/guile/docs/master/guile.html/PEG-Parsing.html#PEG-Parsing

-- 
Efraim Flashner   <efraim@flashner.co.il>   אפרים פלשנר
GPG key = A28B F40C 3E55 1372 662D  14F7 41AA E7DC CA3D 8351
Confidentiality cannot be guaranteed on emails sent or received unencrypted

[-- Attachment #1.2: 0001-bournish-Add-wc-command.patch --]
[-- Type: text/plain, Size: 3778 bytes --]

From 432d742639e193a29aeedc2f080c349494fa2a8f Mon Sep 17 00:00:00 2001
From: Efraim Flashner <efraim@flashner.co.il>
Date: Sun, 22 May 2016 14:56:06 +0300
Subject: [PATCH] bournish: Add `wc' command.

* guix/build/bournish.scm (lines+chars, only-files, wc-print,
wc-l-print, wc-c-print, wc-commands, wc-command-implementation,
wc-l-command-implementation, wc-c-command-implementation): New procedures.
(%commands): Add wc command.
---
 guix/build/bournish.scm | 56 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 55 insertions(+), 1 deletion(-)

diff --git a/guix/build/bournish.scm b/guix/build/bournish.scm
index 1f17e0a..97c1b43 100644
--- a/guix/build/bournish.scm
+++ b/guix/build/bournish.scm
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2016 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2016 Efraim Flashner <efraim@flashner.co.il>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -25,6 +26,7 @@
   #:use-module (ice-9 match)
   #:use-module (ice-9 ftw)
   #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-11)
   #:use-module (srfi srfi-26)
   #:export (%bournish-language))
 
@@ -103,6 +105,57 @@ characters."
        ((@ (guix build utils) dump-port) port (current-output-port))
        *unspecified*)))
 
+(define (lines+chars port)
+  ;; Return the number of lines and number of chars read from PORT.
+  ;; TODO: Also return the number of words.
+  (let loop ((lines 0) (chars 0))
+    (match (read-char port) ; get the next char ready
+      ((? eof-object?)              ;done!
+       (values lines chars))
+      (#\newline                    ;recurse
+       (loop (1+ lines) (1+ chars)))
+      (_                            ;recurse
+       (loop lines (1+ chars))))))
+
+(define (only-files file)
+  (catch 'system-error
+    (lambda ()
+      (stat file))
+    (lambda args
+      (let ((errno (system-error-errno args)))
+        (format (current-error-port) "~a: ~a~%"
+                file (strerror errno))
+        #f))))
+
+(define (wc-print file)
+  (let-values (((lines chars)
+                (call-with-input-file file lines+chars)))
+              (format #t "~a ~a ~a~%" lines chars file)))
+
+(define (wc-l-print file)
+  (let-values (((lines chars)
+                (call-with-input-file file lines+chars)))
+              (format #t "~a ~a~%" lines file)))
+
+(define (wc-c-print file)
+  (let-values (((lines chars)
+                (call-with-input-file file lines+chars)))
+              (format #t "~a ~a~%" chars file)))
+
+(define (wc-command-implementation . files)
+  (for-each wc-print (filter only-files files)))
+
+(define (wc-l-command-implementation . files)
+  (for-each wc-l-print (filter only-files files)))
+
+(define (wc-c-command-implementation . files)
+  (for-each wc-c-print (filter only-files files)))
+
+(define (wc-commands . args)
+  (cond ((member "-l" args) `((@@ (guix build bournish) wc-l-command-implementation) ,@(delete "-l" args)))
+        ((member "-c" args) `((@@ (guix build bournish) wc-c-command-implementation) ,@(delete "-c" args)))
+        (else `((@@ (guix build bournish) wc-command-implementation) ,@args))))
+
 (define (help-command . _)
   (display "\
 Hello, this is Bournish, a minimal Bourne-like shell in Guile!
@@ -129,7 +182,8 @@ commands such as 'ls' and 'cd'; it lacks globbing, pipes---everything.\n"))
     ("help"   ,help-command)
     ("ls"     ,ls-command)
     ("which"  ,which-command)
-    ("cat"    ,cat-command)))
+    ("cat"    ,cat-command)
+    ("wc"     ,wc-commands)))
 
 (define (read-bournish port env)
   "Read a Bournish expression from PORT, and return the corresponding Scheme
-- 
2.8.4


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

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

* Re: Adding wc to Bournish
  2016-06-15 20:28                   ` Efraim Flashner
@ 2016-06-23  8:34                     ` Ludovic Courtès
  0 siblings, 0 replies; 23+ messages in thread
From: Ludovic Courtès @ 2016-06-23  8:34 UTC (permalink / raw)
  To: Efraim Flashner; +Cc: guix-devel, David Thompson

Hello!

Efraim Flashner <efraim@flashner.co.il> skribis:


[...]

>> I prefer the approach Ricardo suggested, I think it’s more concise and
>> clearer:
>> 
>>   https://lists.gnu.org/archive/html/guix-devel/2016-06/msg00525.html
>> 
>> Also, note that @@ would not be needed here; @@ serves to access private
>> bindings within a specific module:
>> 
>>   https://www.gnu.org/software/guile/manual/html_node/Using-Guile-Modules.html
>> 
>> Last, do not miss the bit about docstrings at:
>> 
>>   https://www.gnu.org/software/guix/manual/html_node/Formatting-Code.html
>> 
>> :-)
>> 
>> With these changes, we’re all set.  Thanks!
>
> I've attached another patch

Sorry for taking so long!  I’ve applied the patch with minor cosmetic
changes: procedure names, docstrings, and formatting (please take a
look).

>> From a GSoC viewpoint, I think we must move to the compilation part
>> now.  Specifically, I think the next step is to start parsing Bash
>> code.
>
> This part took much longer than I thought it would. Going forward I'll
> try to be more pro-active on IRC about asking for help.
>
>> 
>> For that we could use SILex + parse-lalr, but these are not the nicest
>> tools for the job.  Better tools would be “parsing expression grammars”
>> (the (ice-9 peg) module in Guile 2.1) or parser combinators, though I
>> don’t think there’s a directly usable Guile library for that.  Maybe
>> Eric or David can comment?
>> 
>
> I get lalr as look-ahead left->right parser and found this¹, but what's SILex?

SILex is a lexer: it turns character sequences into tokens, that can
then be passed to the parser.

I think SILex no longer has a home on the Internet, but you can find a
copy at
<http://git.savannah.gnu.org/cgit/guile-rpc.git/tree/modules/silex>.
Guile-RPC uses both SILex and ‘parse-lalr’, so it could serve as an
example, but again, PEG is probably preferable (Dave’s
guile-parser-combinator library is not ready for prime time, per a
recent discussion on guile-user@gnu.org.)

>> The goal is to have a parser that returns an abstract syntax tree (AST)
>> as an sexp:
>> 
>>   (parse "(cd /foo; ls $HOME) && echo *.a ; echo done")
>>   =>
>>   '(sequence
>>      (success-sequence
>>        (subshell
>>          (sequence (command "cd" "/foo")
>>                    (command "ls" (variable-ref "HOME"))))
>>        (command "echo" (glob "*.a")))
>>      (command "echo" "done"))
>> 
>> Thoughts?

So let’s focus on this part now.  Feel free to ping me/us while
researching the best approach or tools to work on it!

Thanks,
Ludo’.

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

end of thread, other threads:[~2016-06-23  8:34 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2016-05-24 18:47 Adding wc to Bournish Efraim Flashner
2016-05-25  9:03 ` Ricardo Wurmus
2016-05-25  9:43   ` Efraim Flashner
2016-05-25  9:26 ` Ricardo Wurmus
2016-05-25 10:05   ` Efraim Flashner
2016-05-26  8:46 ` Ludovic Courtès
2016-05-26 17:50   ` Efraim Flashner
2016-05-26 19:27 ` Efraim Flashner
2016-05-27 13:37   ` Eric Bavier
2016-05-27 15:28     ` Taylan Ulrich Bayırlı/Kammer
2016-05-27 15:32       ` Thompson, David
2016-06-05 12:40 ` Efraim Flashner
2016-06-05 20:37   ` Ludovic Courtès
2016-06-07  7:41     ` Efraim Flashner
2016-06-08 15:43       ` Ludovic Courtès
2016-06-14  9:27         ` Efraim Flashner
2016-06-14  9:57           ` Ricardo Wurmus
2016-06-14 10:20             ` Efraim Flashner
2016-06-14 10:50               ` Efraim Flashner
2016-06-14 11:08                 ` Ricardo Wurmus
2016-06-15 13:56                 ` Ludovic Courtès
2016-06-15 20:28                   ` Efraim Flashner
2016-06-23  8:34                     ` Ludovic Courtès

Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/guix.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.