-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathutils.lisp
155 lines (134 loc) · 5.43 KB
/
utils.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
;;; Copyright 2015-2020 Google LLC
;;;
;;; Use of this source code is governed by an MIT-style
;;; license that can be found in the LICENSE file or at
;;; https://opensource.org/licenses/MIT.
;;; Package contains utilities for Bazel Lisp compilation tool.
;;;
(defpackage #:bazel.utils
(:use #:cl)
(:export #:octet
#:octets
#:simple-octets
#:nconcf
#:prefixp
#:strip-prefix
#:delete-existing
#:delete-read-only
#:dohash
#:split
#:to-keyword
#:write-stringz
#:read-stringz
#:read-u64
#:write-u64
#:with-continue-on-error
#:funcall-named
#:funcall-named*))
(in-package #:bazel.utils)
(deftype octet () '(unsigned-byte 8))
(deftype octets () '(vector octet))
(deftype simple-octets () '(and octets simple-array))
(define-modify-macro nconcf (&rest lists) nconc
"Helper macro doing an nconc and setf to the first argument.")
(defun prefixp (prefix string)
"Test if STRING starts with the PREFIX."
(declare (string string prefix))
(let ((len (length prefix)))
(and (<= len (length string)) (string= string prefix :end1 len))))
(defun strip-prefix (prefix string)
"If the STRING is prefixed with the PREFIX, remove it, and return (values stripped t).
Otherwise return the complete string and NIL."
(declare (string prefix string))
(if (prefixp prefix string)
(values (subseq string (length prefix)) t)
(values string nil)))
(defun delete-existing (filename)
"Remove FILENAME from disk if it exists and its directory is writable"
(when (probe-file filename)
(delete-file filename)))
(defun delete-read-only (filename &optional (mode :supersede))
"Remove FILENAME from disk if it is readonly.
MODE can be :APPEND or :SUPERSEDE. True if file was deleted."
(when (probe-file filename)
(handler-case
(with-open-file (f filename :direction :output :if-exists mode) nil)
(file-error () (delete-file filename)))))
(defmacro dohash ((k v table) &body body)
"Iterate through the hash TABLE binding the keys to K and values to V."
`(loop for ,k being the hash-keys in ,table using (hash-value ,v) do ,@body))
(defun split (string &key (by #\Space))
"Split the STRING by the separator BY into a list. Empty strings are not included."
(declare (type (or string null) string) (character by))
(when string
(loop for start fixnum = 0 then (1+ pos)
for pos = (position by string :start start)
for part = (subseq string start pos)
when (plusp (length (the string part)))
collect part
while pos)))
(defun to-keyword (string)
"Transforms the STRING designator into a keyword.
The string is interned in the upper case into the keyword package."
(intern (string-upcase string) :keyword))
(defun write-stringz (string out)
"Write a 0 terminated STRING to the OUT stream."
(declare (string string) (stream out))
(loop for c across string do
;; This assumes that the CHAR-CODE is an octet.
(write-byte (char-code c) out))
(write-byte 0 out))
(defun read-stringz (stream)
"Read a 0 terminated string from the STREAM."
(declare (stream stream))
(coerce
(loop for code = (read-byte stream nil)
until (zerop code)
collect (code-char code))
'string))
(defun read-u64 (in)
"Reads 8 bytes from the IN stream and returns an integer."
(declare (stream in))
(let ((u64 0))
(declare (type (unsigned-byte 64) u64))
(dotimes (i 8)
(setf (ldb (byte 8 (* i 8)) u64) (read-byte in)))
u64))
(defun write-u64 (u64 out)
"Writes 8 bytes representation of u64 to the OUT stream."
(declare (type (unsigned-byte 64) u64) (stream out))
(dotimes (i 8)
(write-byte (ldb (byte 8 (* i 8)) u64) out)))
(defun funcall-named (name &rest args)
"Call a function with NAME composed of package and function name. Passes ARGS to the function.
If the package is not found, nothing is called and NIL is returned."
(let ((split (split name :by #\:)))
(assert (= 2 (length split))) ; NOLINT
(let ((package (find-package (first split))))
(when package
(let ((function (find-symbol (second split) package)))
(assert function) ; NOLINT
(assert (fboundp function)) ; NOLINT
(apply function args))))))
(defun funcall-named* (name &rest args)
"Call a function with NAME composed of package and function name. Passes ARGS to the function.
If the function is not found, nothing is called and NIL is returned."
(let ((split (split name :by #\:)))
(assert (= 2 (length split))) ; NOLINT
(let* ((package (find-package (first split)))
(function (and package (find-symbol (second split) package))))
(when (and function (fboundp function))
(apply function args)))))
(defun %with-continue-on-error (function test)
"Call FUNCTION while wrapping it conditionally (TEST) into a HANDLER-BIND that
continues from the error if the continue restart is found."
(if test
(handler-bind ((error
(lambda (c) (declare (ignore c))
(let ((continue (find-restart 'continue)))
(when continue (invoke-restart continue))))))
(funcall function))
(funcall function)))
(defmacro with-continue-on-error ((&key (when t)) &body body)
"Call CONTINUE for all errors. WHEN is an optional condition form."
`(%with-continue-on-error (lambda () ,@body) ,when))