guix package: Export generation procedures.
[jackhill/guix/guix.git] / guix / utils.scm
CommitLineData
233e7676 1;;; GNU Guix --- Functional package management for GNU
2cd5c038 2;;; Copyright © 2012, 2013, 2014 Ludovic Courtès <ludo@gnu.org>
56b943de 3;;; Copyright © 2013 Mark H Weaver <mhw@netris.org>
516e3b6f 4;;; Copyright © 2014 Eric Bavier <bavier@member.fsf.org>
e3deeebb 5;;;
233e7676 6;;; This file is part of GNU Guix.
e3deeebb 7;;;
233e7676 8;;; GNU Guix is free software; you can redistribute it and/or modify it
e3deeebb
LC
9;;; under the terms of the GNU General Public License as published by
10;;; the Free Software Foundation; either version 3 of the License, or (at
11;;; your option) any later version.
12;;;
233e7676 13;;; GNU Guix is distributed in the hope that it will be useful, but
e3deeebb
LC
14;;; WITHOUT ANY WARRANTY; without even the implied warranty of
15;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;;; GNU General Public License for more details.
17;;;
18;;; You should have received a copy of the GNU General Public License
233e7676 19;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
e3deeebb
LC
20
21(define-module (guix utils)
00e219d1 22 #:use-module (guix config)
38b3122a 23 #:use-module (srfi srfi-1)
72d86963 24 #:use-module (srfi srfi-9)
01ac19dc 25 #:use-module (srfi srfi-11)
38b3122a 26 #:use-module (srfi srfi-26)
de4c3f26 27 #:use-module (srfi srfi-39)
e3deeebb
LC
28 #:use-module (srfi srfi-60)
29 #:use-module (rnrs bytevectors)
dba6b34b 30 #:use-module ((rnrs io ports) #:select (put-bytevector))
e0fbbc88 31 #:use-module ((guix build utils) #:select (dump-port))
29fa45f4 32 #:use-module ((guix build syscalls) #:select (errno))
c8369cac 33 #:use-module (ice-9 vlist)
38b3122a 34 #:use-module (ice-9 format)
de4c3f26
LC
35 #:autoload (ice-9 popen) (open-pipe*)
36 #:autoload (ice-9 rdelim) (read-line)
98090557 37 #:use-module (ice-9 regex)
72d86963 38 #:use-module (ice-9 match)
8ef3401f 39 #:use-module (ice-9 format)
2cd5c038 40 #:use-module (system foreign)
ddc29a78 41 #:export (bytevector->base16-string
6d800a80 42 base16-string->bytevector
de4c3f26
LC
43
44 %nixpkgs-directory
45 nixpkgs-derivation
ce5d658c 46 nixpkgs-derivation*
de4c3f26 47
900f7267 48 compile-time-value
2cd5c038 49 fcntl-flock
98090557 50 memoize
0af2c24e
LC
51 default-keyword-arguments
52 substitute-keyword-arguments
ff352cfb 53
64fc89b6 54 <location>
ff352cfb
LC
55 location
56 location?
57 location-file
58 location-line
59 location-column
60 source-properties->location
61
98090557 62 gnu-triplet->nix-system
9b48fb88 63 %current-system
9c1edabd 64 %current-target-system
0d1e6ce4
MW
65 version-compare
66 version>?
7db3ff4a 67 guile-version>?
04fd96ca 68 package-name->name+version
2bcfb9e0 69 string-tokenize*
56b943de 70 string-replace-substring
0fdd3bea 71 file-extension
ac10e0e1 72 file-sans-extension
861693f3 73 call-with-temporary-output-file
04d4c8a4 74 with-atomic-file-output
e0fbbc88 75 fold2
516e3b6f
EB
76 fold-tree
77 fold-tree-leaves
7a8024a3
LC
78
79 filtered-port
80 compressed-port
80dea563 81 decompressed-port
01ac19dc
LC
82 call-with-decompressed-port
83 compressed-output-port
84 call-with-compressed-output-port))
e3deeebb 85
38b3122a 86\f
dba6b34b
LC
87;;;
88;;; Compile-time computations.
89;;;
90
91(define-syntax compile-time-value
92 (syntax-rules ()
93 "Evaluate the given expression at compile time. The expression must
94evaluate to a simple datum."
95 ((_ exp)
96 (let-syntax ((v (lambda (s)
97 (let ((val exp))
98 (syntax-case s ()
99 (_ #`'#,(datum->syntax s val)))))))
100 v))))
101
102\f
38b3122a
LC
103;;;
104;;; Base 16.
105;;;
106
107(define (bytevector->base16-string bv)
108 "Return the hexadecimal representation of BV's contents."
109 (define len
110 (bytevector-length bv))
111
112 (let-syntax ((base16-chars (lambda (s)
113 (syntax-case s ()
114 (_
115 (let ((v (list->vector
116 (unfold (cut > <> 255)
117 (lambda (n)
118 (format #f "~2,'0x" n))
119 1+
120 0))))
121 v))))))
122 (define chars base16-chars)
123 (let loop ((i 0)
124 (r '()))
125 (if (= i len)
126 (string-concatenate-reverse r)
127 (loop (+ 1 i)
128 (cons (vector-ref chars (bytevector-u8-ref bv i)) r))))))
129
6d800a80
LC
130(define base16-string->bytevector
131 (let ((chars->value (fold (lambda (i r)
132 (vhash-consv (string-ref (number->string i 16)
133 0)
134 i r))
135 vlist-null
136 (iota 16))))
137 (lambda (s)
138 "Return the bytevector whose hexadecimal representation is string S."
139 (define bv
140 (make-bytevector (quotient (string-length s) 2) 0))
141
142 (string-fold (lambda (chr i)
143 (let ((j (quotient i 2))
144 (v (and=> (vhash-assv chr chars->value) cdr)))
145 (if v
146 (if (zero? (logand i 1))
147 (bytevector-u8-set! bv j
148 (arithmetic-shift v 4))
149 (let ((w (bytevector-u8-ref bv j)))
150 (bytevector-u8-set! bv j (logior v w))))
151 (error "invalid hexadecimal character" chr)))
152 (+ i 1))
153 0
154 s)
155 bv)))
156
de4c3f26
LC
157
158\f
e0fbbc88
LC
159;;;
160;;; Filtering & pipes.
161;;;
162
163(define (filtered-port command input)
164 "Return an input port where data drained from INPUT is filtered through
165COMMAND (a list). In addition, return a list of PIDs that the caller must
101d9f3f
LC
166wait. When INPUT is a file port, it must be unbuffered; otherwise, any
167buffered data is lost."
e0fbbc88 168 (let loop ((input input)
443eb4e9 169 (pids '()))
e0fbbc88
LC
170 (if (file-port? input)
171 (match (pipe)
172 ((in . out)
173 (match (primitive-fork)
174 (0
443eb4e9
LC
175 (dynamic-wind
176 (const #f)
177 (lambda ()
178 (close-port in)
179 (close-port (current-input-port))
180 (dup2 (fileno input) 0)
181 (close-port (current-output-port))
182 (dup2 (fileno out) 1)
183 (catch 'system-error
184 (lambda ()
185 (apply execl (car command) command))
186 (lambda args
187 (format (current-error-port)
188 "filtered-port: failed to execute '~{~a ~}': ~a~%"
189 command (strerror (system-error-errno args))))))
190 (lambda ()
191 (primitive-_exit 1))))
e0fbbc88
LC
192 (child
193 (close-port out)
194 (values in (cons child pids))))))
195
196 ;; INPUT is not a file port, so fork just for the sake of tunneling it
197 ;; through a file port.
198 (match (pipe)
199 ((in . out)
200 (match (primitive-fork)
201 (0
202 (dynamic-wind
203 (const #t)
204 (lambda ()
205 (close-port in)
206 (dump-port input out))
207 (lambda ()
208 (false-if-exception (close out))
443eb4e9 209 (primitive-_exit 0))))
e0fbbc88
LC
210 (child
211 (close-port out)
212 (loop in (cons child pids)))))))))
213
7a8024a3
LC
214(define (decompressed-port compression input)
215 "Return an input port where INPUT is decompressed according to COMPRESSION,
216a symbol such as 'xz."
217 (match compression
218 ((or #f 'none) (values input '()))
219 ('bzip2 (filtered-port `(,%bzip2 "-dc") input))
220 ('xz (filtered-port `(,%xz "-dc") input))
221 ('gzip (filtered-port `(,%gzip "-dc") input))
222 (else (error "unsupported compression scheme" compression))))
223
224(define (compressed-port compression input)
225 "Return an input port where INPUT is decompressed according to COMPRESSION,
226a symbol such as 'xz."
227 (match compression
228 ((or #f 'none) (values input '()))
229 ('bzip2 (filtered-port `(,%bzip2 "-c") input))
230 ('xz (filtered-port `(,%xz "-c") input))
231 ('gzip (filtered-port `(,%gzip "-c") input))
232 (else (error "unsupported compression scheme" compression))))
233
01ac19dc
LC
234(define (call-with-decompressed-port compression port proc)
235 "Call PROC with a wrapper around PORT, a file port, that decompresses data
30ce8012 236read from PORT according to COMPRESSION, a symbol such as 'xz."
01ac19dc
LC
237 (let-values (((decompressed pids)
238 (decompressed-port compression port)))
239 (dynamic-wind
240 (const #f)
241 (lambda ()
01ac19dc
LC
242 (proc decompressed))
243 (lambda ()
244 (close-port decompressed)
245 (unless (every (compose zero? cdr waitpid) pids)
246 (error "decompressed-port failure" pids))))))
247
80dea563
LC
248(define (filtered-output-port command output)
249 "Return an output port. Data written to that port is filtered through
250COMMAND and written to OUTPUT, an output file port. In addition, return a
251list of PIDs to wait for. OUTPUT must be unbuffered; otherwise, any buffered
252data is lost."
253 (match (pipe)
254 ((in . out)
255 (match (primitive-fork)
256 (0
257 (dynamic-wind
258 (const #f)
259 (lambda ()
260 (close-port out)
261 (close-port (current-input-port))
262 (dup2 (fileno in) 0)
263 (close-port (current-output-port))
264 (dup2 (fileno output) 1)
265 (catch 'system-error
266 (lambda ()
267 (apply execl (car command) command))
268 (lambda args
269 (format (current-error-port)
270 "filtered-output-port: failed to execute '~{~a ~}': ~a~%"
271 command (strerror (system-error-errno args))))))
272 (lambda ()
273 (primitive-_exit 1))))
274 (child
275 (close-port in)
276 (values out (list child)))))))
277
278(define (compressed-output-port compression output)
279 "Return an output port whose input is compressed according to COMPRESSION,
280a symbol such as 'xz, and then written to OUTPUT. In addition return a list
281of PIDs to wait for."
282 (match compression
283 ((or #f 'none) (values output '()))
284 ('bzip2 (filtered-output-port `(,%bzip2 "-c") output))
285 ('xz (filtered-output-port `(,%xz "-c") output))
286 ('gzip (filtered-output-port `(,%gzip "-c") output))
287 (else (error "unsupported compression scheme" compression))))
288
01ac19dc
LC
289(define (call-with-compressed-output-port compression port proc)
290 "Call PROC with a wrapper around PORT, a file port, that compresses data
30ce8012 291that goes to PORT according to COMPRESSION, a symbol such as 'xz."
01ac19dc
LC
292 (let-values (((compressed pids)
293 (compressed-output-port compression port)))
294 (dynamic-wind
295 (const #f)
296 (lambda ()
01ac19dc
LC
297 (proc compressed))
298 (lambda ()
299 (close-port compressed)
300 (unless (every (compose zero? cdr waitpid) pids)
301 (error "compressed-output-port failure" pids))))))
302
e0fbbc88 303\f
de4c3f26
LC
304;;;
305;;; Nixpkgs.
306;;;
307
308(define %nixpkgs-directory
900f7267
LC
309 (make-parameter
310 ;; Capture the build-time value of $NIXPKGS.
a09ec3a5
LC
311 (or %nixpkgs
312 (and=> (getenv "NIXPKGS")
313 (lambda (val)
314 ;; Bail out when passed an empty string, otherwise
315 ;; `nix-instantiate' will sit there and attempt to read
316 ;; from its standard input.
317 (if (string=? val "")
318 #f
319 val))))))
de4c3f26 320
fbc93bed 321(define* (nixpkgs-derivation attribute #:optional (system (%current-system)))
de4c3f26 322 "Return the derivation path of ATTRIBUTE in Nixpkgs."
b86b0056 323 (let* ((p (open-pipe* OPEN_READ (or (getenv "NIX_INSTANTIATE")
00e219d1 324 %nix-instantiate)
fbc93bed
LC
325 "-A" attribute (%nixpkgs-directory)
326 "--argstr" "system" system))
de4c3f26
LC
327 (l (read-line p))
328 (s (close-pipe p)))
329 (and (zero? (status:exit-val s))
330 (not (eof-object? l))
331 l)))
332
ce5d658c
LC
333(define-syntax-rule (nixpkgs-derivation* attribute)
334 "Evaluate the given Nixpkgs derivation at compile-time."
335 (compile-time-value (nixpkgs-derivation attribute)))
336
de4c3f26 337\f
2cd5c038
LC
338;;;
339;;; Advisory file locking.
340;;;
341
342(define %struct-flock
343 ;; 'struct flock' from <fcntl.h>.
344 (list short ; l_type
345 short ; l_whence
346 size_t ; l_start
347 size_t ; l_len
348 int)) ; l_pid
349
350(define F_SETLKW
351 ;; On Linux-based systems, this is usually 7, but not always
352 ;; (exceptions include SPARC.) On GNU/Hurd, it's 9.
353 (compile-time-value
354 (cond ((string-contains %host-type "sparc") 9) ; sparc-*-linux-gnu
355 ((string-contains %host-type "linux") 7) ; *-linux-gnu
356 (else 9)))) ; *-gnu*
357
c7445833
LC
358(define F_SETLK
359 ;; Likewise: GNU/Hurd and SPARC use 8, while the others typically use 6.
360 (compile-time-value
361 (cond ((string-contains %host-type "sparc") 8) ; sparc-*-linux-gnu
362 ((string-contains %host-type "linux") 6) ; *-linux-gnu
363 (else 8)))) ; *-gnu*
364
2cd5c038
LC
365(define F_xxLCK
366 ;; The F_RDLCK, F_WRLCK, and F_UNLCK constants.
367 (compile-time-value
368 (cond ((string-contains %host-type "sparc") #(1 2 3)) ; sparc-*-linux-gnu
369 ((string-contains %host-type "hppa") #(1 2 3)) ; hppa-*-linux-gnu
370 ((string-contains %host-type "linux") #(0 1 2)) ; *-linux-gnu
371 (else #(1 2 3))))) ; *-gnu*
372
373(define fcntl-flock
374 (let* ((ptr (dynamic-func "fcntl" (dynamic-link)))
375 (proc (pointer->procedure int ptr `(,int ,int *))))
c7445833 376 (lambda* (fd-or-port operation #:key (wait? #t))
2cd5c038 377 "Perform locking OPERATION on the file beneath FD-OR-PORT. OPERATION
c7445833
LC
378must be a symbol, one of 'read-lock, 'write-lock, or 'unlock. When WAIT? is
379true, block until the lock is acquired; otherwise, thrown an 'flock-error'
380exception if it's already taken."
2cd5c038
LC
381 (define (operation->int op)
382 (case op
383 ((read-lock) (vector-ref F_xxLCK 0))
384 ((write-lock) (vector-ref F_xxLCK 1))
385 ((unlock) (vector-ref F_xxLCK 2))
386 (else (error "invalid fcntl-flock operation" op))))
387
388 (define fd
389 (if (port? fd-or-port)
390 (fileno fd-or-port)
391 fd-or-port))
392
393 ;; XXX: 'fcntl' is a vararg function, but here we happily use the
394 ;; standard ABI; crossing fingers.
395 (let ((err (proc fd
c7445833
LC
396 (if wait?
397 F_SETLKW ; lock & wait
398 F_SETLK) ; non-blocking attempt
2cd5c038
LC
399 (make-c-struct %struct-flock
400 (list (operation->int operation)
401 SEEK_SET
402 0 0 ; whole file
403 0)))))
404 (or (zero? err)
405
406 ;; Presumably we got EAGAIN or so.
9ea3ef26 407 (throw 'flock-error (errno)))))))
2cd5c038
LC
408
409\f
de4c3f26
LC
410;;;
411;;; Miscellaneous.
412;;;
413
414(define (memoize proc)
415 "Return a memoizing version of PROC."
416 (let ((cache (make-hash-table)))
417 (lambda args
418 (let ((results (hash-ref cache args)))
419 (if results
420 (apply values results)
421 (let ((results (call-with-values (lambda ()
422 (apply proc args))
423 list)))
424 (hash-set! cache args results)
425 (apply values results)))))))
98090557 426
0af2c24e
LC
427(define (default-keyword-arguments args defaults)
428 "Return ARGS augmented with any keyword/value from DEFAULTS for
429keywords not already present in ARGS."
430 (let loop ((defaults defaults)
431 (args args))
432 (match defaults
433 ((kw value rest ...)
434 (loop rest
435 (if (assoc-ref kw args)
436 args
437 (cons* kw value args))))
438 (()
439 args))))
440
441(define-syntax substitute-keyword-arguments
442 (syntax-rules ()
443 "Return a new list of arguments where the value for keyword arg KW is
444replaced by EXP. EXP is evaluated in a context where VAR is boud to the
445previous value of the keyword argument."
446 ((_ original-args ((kw var) exp) ...)
447 (let loop ((args original-args)
448 (before '()))
449 (match args
450 ((kw var rest (... ...))
451 (loop rest (cons* exp kw before)))
452 ...
453 ((x rest (... ...))
454 (loop rest (cons x before)))
455 (()
456 (reverse before)))))))
457
98090557
LC
458(define (gnu-triplet->nix-system triplet)
459 "Return the Nix system type corresponding to TRIPLET, a GNU triplet as
460returned by `config.guess'."
461 (let ((triplet (cond ((string-match "^i[345]86-(.*)$" triplet)
462 =>
463 (lambda (m)
464 (string-append "i686-" (match:substring m 1))))
465 (else triplet))))
466 (cond ((string-match "^([^-]+)-([^-]+-)?linux-gnu.*" triplet)
467 =>
468 (lambda (m)
469 ;; Nix omits `-gnu' for GNU/Linux.
470 (string-append (match:substring m 1) "-linux")))
471 ((string-match "^([^-]+)-([^-]+-)?([[:alpha:]]+)([0-9]+\\.?)*$" triplet)
472 =>
473 (lambda (m)
474 ;; Nix strip the version number from names such as `gnu0.3',
475 ;; `darwin10.2.0', etc., and always strips the vendor part.
476 (string-append (match:substring m 1) "-"
477 (match:substring m 3))))
478 (else triplet))))
479
480(define %current-system
481 ;; System type as expected by Nix, usually ARCHITECTURE-KERNEL.
d8eea3d2
LC
482 ;; By default, this is equal to (gnu-triplet->nix-system %host-type).
483 (make-parameter %system))
ff352cfb 484
9c1edabd
LC
485(define %current-target-system
486 ;; Either #f or a GNU triplet representing the target system we are
487 ;; cross-building to.
488 (make-parameter #f))
489
0d1e6ce4
MW
490(define version-compare
491 (let ((strverscmp
492 (let ((sym (or (dynamic-func "strverscmp" (dynamic-link))
493 (error "could not find `strverscmp' (from GNU libc)"))))
494 (pointer->procedure int sym (list '* '*)))))
495 (lambda (a b)
496 "Return '> when A denotes a newer version than B,
497'< when A denotes a older version than B,
498or '= when they denote equal versions."
499 (let ((result (strverscmp (string->pointer a) (string->pointer b))))
500 (cond ((positive? result) '>)
501 ((negative? result) '<)
502 (else '=))))))
503
504(define (version>? a b)
505 "Return #t when A denotes a newer version than B."
506 (eq? '> (version-compare a b)))
507
7db3ff4a
LC
508(define (guile-version>? str)
509 "Return #t if the running Guile version is greater than STR."
510 ;; Note: Using (version>? (version) "2.0.5") or similar doesn't work,
511 ;; because the result of (version) can have a prefix, like "2.0.5-deb1".
512 (version>? (string-append (major-version) "."
513 (minor-version) "."
514 (micro-version))
515 str))
516
9b48fb88
LC
517(define (package-name->name+version name)
518 "Given NAME, a package name like \"foo-0.9.1b\", return two values:
519\"foo\" and \"0.9.1b\". When the version part is unavailable, NAME and
520#f are returned. The first hyphen followed by a digit is considered to
521introduce the version part."
522 ;; See also `DrvName' in Nix.
523
524 (define number?
525 (cut char-set-contains? char-set:digit <>))
526
527 (let loop ((chars (string->list name))
528 (prefix '()))
529 (match chars
530 (()
531 (values name #f))
532 ((#\- (? number? n) rest ...)
533 (values (list->string (reverse prefix))
534 (list->string (cons n rest))))
535 ((head tail ...)
536 (loop tail (cons head prefix))))))
537
0fdd3bea
LC
538(define (file-extension file)
539 "Return the extension of FILE or #f if there is none."
540 (let ((dot (string-rindex file #\.)))
541 (and dot (substring file (+ 1 dot) (string-length file)))))
542
ac10e0e1
LC
543(define (file-sans-extension file)
544 "Return the substring of FILE without its extension, if any."
545 (let ((dot (string-rindex file #\.)))
546 (if dot
547 (substring file 0 dot)
548 file)))
549
2bcfb9e0
LC
550(define (string-tokenize* string separator)
551 "Return the list of substrings of STRING separated by SEPARATOR. This is
552like `string-tokenize', but SEPARATOR is a string."
553 (define (index string what)
554 (let loop ((string string)
555 (offset 0))
556 (cond ((string-null? string)
557 #f)
558 ((string-prefix? what string)
559 offset)
560 (else
561 (loop (string-drop string 1) (+ 1 offset))))))
562
563 (define len
564 (string-length separator))
565
566 (let loop ((string string)
567 (result '()))
568 (cond ((index string separator)
569 =>
570 (lambda (offset)
571 (loop (string-drop string (+ offset len))
572 (cons (substring string 0 offset)
573 result))))
574 (else
575 (reverse (cons string result))))))
576
56b943de
LC
577(define* (string-replace-substring str substr replacement
578 #:optional
579 (start 0)
580 (end (string-length str)))
581 "Replace all occurrences of SUBSTR in the START--END range of STR by
582REPLACEMENT."
583 (match (string-length substr)
584 (0
585 (error "string-replace-substring: empty substring"))
586 (substr-length
587 (let loop ((start start)
588 (pieces (list (substring str 0 start))))
589 (match (string-contains str substr start end)
590 (#f
591 (string-concatenate-reverse
592 (cons (substring str start) pieces)))
593 (index
594 (loop (+ index substr-length)
595 (cons* replacement
596 (substring str start index)
597 pieces))))))))
598
861693f3
LC
599(define (call-with-temporary-output-file proc)
600 "Call PROC with a name of a temporary file and open output port to that
601file; close the file and delete it when leaving the dynamic extent of this
602call."
57db49cc
LC
603 (let* ((directory (or (getenv "TMPDIR") "/tmp"))
604 (template (string-append directory "/guix-file.XXXXXX"))
605 (out (mkstemp! template)))
861693f3
LC
606 (dynamic-wind
607 (lambda ()
608 #t)
609 (lambda ()
610 (proc template out))
611 (lambda ()
612 (false-if-exception (close out))
613 (false-if-exception (delete-file template))))))
04d4c8a4
LC
614
615(define (with-atomic-file-output file proc)
616 "Call PROC with an output port for the file that is going to replace FILE.
617Upon success, FILE is atomically replaced by what has been written to the
618output port, and PROC's result is returned."
619 (let* ((template (string-append file ".XXXXXX"))
620 (out (mkstemp! template)))
621 (with-throw-handler #t
622 (lambda ()
623 (let ((result (proc out)))
624 (close out)
625 (rename-file template file)
626 result))
627 (lambda (key . args)
628 (false-if-exception (delete-file template))))))
861693f3 629
04fd96ca
LC
630(define fold2
631 (case-lambda
632 ((proc seed1 seed2 lst)
633 "Like `fold', but with a single list and two seeds."
634 (let loop ((result1 seed1)
635 (result2 seed2)
636 (lst lst))
637 (if (null? lst)
638 (values result1 result2)
639 (call-with-values
640 (lambda () (proc (car lst) result1 result2))
641 (lambda (result1 result2)
642 (loop result1 result2 (cdr lst)))))))
643 ((proc seed1 seed2 lst1 lst2)
644 "Like `fold', but with a two lists and two seeds."
645 (let loop ((result1 seed1)
646 (result2 seed2)
647 (lst1 lst1)
648 (lst2 lst2))
649 (if (or (null? lst1) (null? lst2))
650 (values result1 result2)
651 (call-with-values
652 (lambda () (proc (car lst1) (car lst2) result1 result2))
653 (lambda (result1 result2)
654 (fold2 proc result1 result2 (cdr lst1) (cdr lst2)))))))))
655
516e3b6f
EB
656(define (fold-tree proc init children roots)
657 "Call (PROC NODE RESULT) for each node in the tree that is reachable from
658ROOTS, using INIT as the initial value of RESULT. The order in which nodes
659are traversed is not specified, however, each node is visited only once, based
660on an eq? check. Children of a node to be visited are generated by
661calling (CHILDREN NODE), the result of which should be a list of nodes that
662are connected to NODE in the tree, or '() or #f if NODE is a leaf node."
663 (let loop ((result init)
664 (seen vlist-null)
665 (lst roots))
666 (match lst
667 (() result)
668 ((head . tail)
669 (if (not (vhash-assq head seen))
670 (loop (proc head result)
671 (vhash-consq head #t seen)
672 (match (children head)
673 ((or () #f) tail)
674 (children (append tail children))))
675 (loop result seen tail))))))
676
677(define (fold-tree-leaves proc init children roots)
678 "Like fold-tree, but call (PROC NODE RESULT) only for leaf nodes."
679 (fold-tree
680 (lambda (node result)
681 (match (children node)
682 ((or () #f) (proc node result))
683 (else result)))
684 init children roots))
685
ff352cfb
LC
686\f
687;;;
688;;; Source location.
689;;;
690
691;; A source location.
692(define-record-type <location>
693 (make-location file line column)
694 location?
695 (file location-file) ; file name
696 (line location-line) ; 1-indexed line
697 (column location-column)) ; 0-indexed column
698
699(define location
700 (memoize
701 (lambda (file line column)
702 "Return the <location> object for the given FILE, LINE, and COLUMN."
703 (and line column file
704 (make-location file line column)))))
705
706(define (source-properties->location loc)
707 "Return a location object based on the info in LOC, an alist as returned
708by Guile's `source-properties', `frame-source', `current-source-location',
709etc."
710 (let ((file (assq-ref loc 'filename))
711 (line (assq-ref loc 'line))
712 (col (assq-ref loc 'column)))
5fe21fbe
LC
713 ;; In accordance with the GCS, start line and column numbers at 1. Note
714 ;; that unlike LINE and `port-column', COL is actually 1-indexed here...
715 (location file (and line (+ line 1)) col)))