util.scm 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. ;; guile-charting
  2. ;; Copyright (C) 2007, 2014 Andy Wingo <wingo at pobox dot com>
  3. ;; This library is free software; you can redistribute it and/or
  4. ;; modify it under the terms of the GNU Lesser General Public
  5. ;; License as published by the Free Software Foundation; either
  6. ;; version 3 of the License, or (at your option) any later version.
  7. ;;
  8. ;; This library is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. ;; Lesser General Public License for more details.
  12. ;;
  13. ;; You should have received a copy of the GNU Lesser General Public
  14. ;; License along with this library; if not, see
  15. ;; <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;;
  18. ;;
  19. ;;
  20. ;;; Code:
  21. (define-module (charting util)
  22. #:use-module (ice-9 optargs)
  23. #:use-module (cairo)
  24. #:export (lambda-with-kwargs
  25. define-with-kwargs
  26. cairo-text-width))
  27. ;; these taken from guile-lib and soundscrape
  28. (define (until pred? list)
  29. "Returns the first elements of @var{list} for which @var{pred?} is false."
  30. (if (or (eq? list '()) (pred? (car list)))
  31. '()
  32. (cons (car list) (until pred? (cdr list)))))
  33. (define (cairo-text-width cr text)
  34. (cairo-text-extents:width (cairo-text-extents cr text)))
  35. (define-macro (define-macro-with-docs name-and-args docs . body)
  36. "Define a macro with documentation."
  37. `(define-macro ,name-and-args ,docs ,@body))
  38. (define-macro-with-docs (lambda-with-kwargs BINDINGS . BODY)
  39. "Defines a function that takes keyword arguments.
  40. @var{bindings} is a list of bindings, each of which may either be a
  41. symbol or a two-element symbol-and-default-value list. Symbols without
  42. specified default values will default to @code{#f}.
  43. For example:
  44. @example
  45. (define frobulate (lambda/kwargs (foo (bar 13) (baz 42))
  46. (list foo bar baz)))
  47. (frobulate) @result{} (#f 13 42)
  48. (frobulate #:baz 3) @result{} (#f 13 3)
  49. (frobulate #:foo 3) @result{} (3 13 42)
  50. (frobulate 3 4) @result{} (3 4 42)
  51. (frobulate 1 2 3) @result{} (1 2 3)
  52. (frobulate #:baz 2 #:bar 1) @result{} (#f 1 2)
  53. (frobulate 10 20 #:foo 3) @result{} (3 20 42)
  54. @end example
  55. This function differs from the standard @code{lambda*} provided by Guile
  56. in that invoking the function will accept positional arguments.
  57. As an example, the @code{lambda/kwargs} behaves more intuitively in the
  58. following case:
  59. @example
  60. ((lambda* (#:optional (bar 42) #:key (baz 73))
  61. (list bar baz))
  62. 1 2) @result{} (1 73)
  63. ((lambda/kwargs ((bar 42) (baz 73))
  64. (list bar baz))
  65. 1 2) @result{} (1 2)
  66. @end example
  67. The fact that @code{lambda*} accepts the extra @samp{2} argument is
  68. probably just a bug. In any case, @code{lambda/kwargs} does the right
  69. thing.
  70. "
  71. (or (list? BINDINGS)
  72. (error "lambda/kwargs bindings must be a list"))
  73. (let ((lambda-gensym (gensym))
  74. (args-gensym (gensym))
  75. (positional (gensym))
  76. (keyword (gensym))
  77. (nbindings (length BINDINGS))
  78. (CANONICAL-BINDINGS (map (lambda (x)
  79. (if (list? x) x (list x #f)))
  80. BINDINGS))
  81. (VARIABLES (map (lambda (x) (if (list? x) (car x) x))
  82. BINDINGS)))
  83. `(let ((,lambda-gensym
  84. (lambda ,args-gensym
  85. ,@(if (string? (car BODY)) (list (car BODY)) '())
  86. (let* ((,positional ((@@ (charting util) until)
  87. keyword? ,args-gensym))
  88. (,keyword (list-tail ,args-gensym (length ,positional))))
  89. (if (> (length ,positional) ,nbindings)
  90. (error "Too many positional arguments."))
  91. ((@ (ice-9 optargs) let-optional) ,positional
  92. ,CANONICAL-BINDINGS
  93. ;; ,@(map car CANONICAL-BINDINGS)
  94. ((@ (ice-9 optargs) let-keywords) ,keyword
  95. #f
  96. ,(map list VARIABLES VARIABLES)
  97. ,@(if (string? (car BODY)) (cdr BODY) BODY)))))))
  98. (set-procedure-property! ,lambda-gensym
  99. 'arglist
  100. '(() () ,CANONICAL-BINDINGS #f #f))
  101. ,lambda-gensym)))
  102. (define-macro-with-docs (define-with-kwargs what . body)
  103. "Defines a function that takes kwargs. @xref{charting util
  104. lambda/kwargs}, for more information.
  105. "
  106. `(define ,(car what) ((@ (charting util) lambda-with-kwargs) ,(cdr what) ,@body)))