[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: Adding wc to Bournish
From: |
Ludovic Courtès |
Subject: |
Re: Adding wc to Bournish |
Date: |
Thu, 26 May 2016 10:46:21 +0200 |
User-agent: |
Gnus/5.13 (Gnus v5.13) Emacs/24.5 (gnu/linux) |
Hello!
Efraim Flashner <address@hidden> 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’.