-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathislisp-sys.lisp
424 lines (383 loc) · 15.3 KB
/
islisp-sys.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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
;;;; -*- Mode:Lisp; indent-tabs-mode:nil -*-
;;;; islisp-sys.lisp
;;;; Copyright (c) 2011-2024 Derek Newhall
;;;; SPDX-License-Identifier: CDDL-1.1
;;;;
;;;; This contains all the implementation details required by
;;;; functions.lisp and specials.lisp.
(in-package #:islisp-sys)
;;; Set up useful errors
;; For syntax errors
(define-condition <violation> (serious-condition)
((form :initarg form
:accessor violation-form)
(description :initarg :description
:accessor violation-description)))
(defmacro violation (form format-string &rest arguments)
"Helper macro for signaling a syntax violation."
`(let ((description (apply #'format nil ,format-string (list ,@arguments))))
(error (make-condition '<violation>
:form ,form
:description description))))
(defun signal-domain-error (object expected-class)
(let ((err (make-condition 'type-error
:datum object
:expected-type expected-class)))
(error err)))
(defun signal-control-error ()
(error (make-instance 'control-error)))
(defun signal-program-error (fname error-id)
(declare (ignore fname error-id))
(error (make-instance 'program-error)))
;; Per spec, program-error takes no parameters, but TISL has these slots
#|`(error (make-instance <program-error>
'function fname
'error-id error-id))|#
(defun signal-arity-error (fname len1 len2)
(declare (ignore len1 len2))
(signal-program-error fname 'arity-error))
(defun assert-arity (fname args minlength &optional maxlength)
"Signals an error if the length of args doesn't fit within
the bounds of minlegnth and maxlength."
(unless (and (>= (length args) minlength)
(if maxlength
(<= (length args) maxlength)
t))
(signal-arity-error fname (length args) minlength)))
(defun islisp-keyword-p (obj)
"Predicate for whether the object is an ISLisp keyword."
;; Correspond to :cl, :symbol, and :keyword-object in the reader
(or (keywordp obj)
(and (symbolp obj)
(> (length (symbol-name obj)) 0)
(char= (char (symbol-name obj) 0) #\:))))
;(typep obj 'islisp-sys:<keyword>)))
(defun identifierp (name)
"Returns true if name is a valid identifier (a symbol and not reserved)."
(and (symbolp name)
(char/= (char (symbol-name name) 0) #\&)
(not (islisp-keyword-p name))))
(defun assure-identifier (name form)
"Signals a violation if NAME is not a valid identifier."
(if (identifierp name)
name
(islisp-sys:violation form
"~A cannot be used as an identifier."
name)))
;;; Set up built-in classes
(defun cl-class-name (class-name)
"Returns the CL class name for the ISLisp class name."
(case class-name
(islisp:<object> 'cl:t)
(islisp:<basic-array> 'cl:array)
(islisp:<basic-array*> 'cl:simple-array) ; ???
(islisp:<general-array*> 'cl:simple-array)
(islisp:<basic-vector> 'cl:vector)
(islisp:<general-vector> 'cl:simple-vector)
(islisp:<string> 'cl:string)
(islisp:<character> 'cl:character)
(islisp:<function> 'cl:function)
(islisp:<generic-function> 'cl:generic-function)
(islisp:<standard-generic-function> 'cl:standard-generic-function)
(islisp:<list> 'cl:list)
(islisp:<cons> 'cl:cons)
(islisp:<null> 'cl:null)
(islisp:<symbol> 'cl:symbol)
(islisp:<number> 'cl:number)
(islisp:<float> 'cl:float)
(islisp:<integer> 'cl:integer)
(islisp:<serious-condition> 'cl:serious-condition)
(islisp:<error> 'cl:error)
(islisp:<arithmetic-error> 'cl:arithmetic-error)
(islisp:<division-by-zero> 'cl:division-by-zero)
(islisp:<floating-point-overflow> 'cl:floating-point-overflow)
(islisp:<floating-point-underflow> 'cl:floating-point-underflow)
(islisp:<control-error> 'cl:control-error)
(islisp:<parse-error> 'islisp:<parse-error>)
(islisp:<program-error> 'cl:program-error)
(islisp:<domain-error> 'cl:type-error)
(islisp:<undefined-entity> 'cl:cell-error)
(islisp:<unbound-variable> 'cl:unbound-variable)
(islisp:<undefined-function> 'cl:undefined-function)
(islisp:<simple-error> 'cl:simple-error)
(islisp:<stream-error> 'cl:stream-error)
(islisp:<end-of-stream> 'cl:end-of-file)
(islisp:<built-in-class> 'cl:built-in-class)
(islisp:<standard-class> 'cl:standard-class)
(islisp:<class> 'cl:class) ;; NOT STANDARD
(islisp:<standard-object> 'cl:standard-object)
(islisp:<stream> 'cl:stream)
(islisp:<sequence> 'cl:sequence) ;; NOT STANDARD
(islisp:<storage-exhausted> 'cl:storage-condition)
(t class-name)))
(defun cl-class (class-name)
"Returns the CL class for the ISLisp class name."
(find-class (cl-class-name class-name)))
;;; Fix various ISLisp-specific things
(defun fix-dynamic-name (name &optional (package (find-package '#:islisp-user)))
"Takes a symbol and returns a mangled name for use in ISLisp."
(let ((dynamic-name (concatenate 'string (symbol-name '%DYNAMIC-)
(symbol-name name))))
(intern dynamic-name package)))
(defun dynamic-var-p (sym)
"Predicate for whether a symbol is an ISLisp dynamic variable."
;; This check used to be more complicated...
(let ((name (symbol-name sym)))
(and (> (length name) 9)
(string= (subseq name 0 9) "%DYNAMIC-")
(boundp sym))))
(defun param-eq (param symbol)
"Compares two lambda list parameter names.
Does a string comparison instead of EQ to get around package issues."
(string-equal (symbol-name param)
(symbol-name symbol)))
(defun fix-lambda-list (lambda-list)
"Takes an ISLisp lambda list and returns one usable by CL."
(mapcar (lambda (param)
(cond ((or (eq param 'cl::&rest)
(eq param 'islisp::&rest)
(eq param :rest))
'cl::&rest)
((or (param-eq param '&optional)
(param-eq param '&body)
(param-eq param '&key)
(param-eq param '&aux)
(param-eq param '&whole))
(cl:warn "Suspicious parameter in lambda list: ~A"
param)
param)
((keywordp param)
(cl:warn "Keyword parameters are implementation dependent: ~A"
param)
param)
(t param)))
lambda-list))
(defun fix-method-parameter-profile (parameter-profile)
"Takes a list of ISLisp method parameters and returns
one usable for CL's DEFMETHOD."
(mapcar #'(lambda (param)
;; TODO: assure-arity 2
(if (consp param)
(list (first param) (cl-class-name (second param)))
param))
parameter-profile))
(defun fix-slot-spec (slot-spec)
"Takes a slot specification and returns a cons of
of (valid-cl-slot-opts . other-defining-forms)
for use by DEFCLASS."
(if (identifierp slot-spec)
(cons slot-spec nil)
(let ((opts '())
(defs '()))
;;(assert (consp spec))
;; FIXME: signal-domain-error
(let ((slot-name (car slot-spec))
(slot-opts (cdr slot-spec))
(opt nil)) ; flips between the key and value
(dolist (slot-opt slot-opts)
(if (null opt)
(setq opt slot-opt)
(progn
(ecase opt
;; Standard CL ones - return as is
((:reader :writer :accessor :initarg :initform)
(push opt opts)
(push slot-opt opts)
(setq opt nil))
;; Create new method for :boundp
((:boundp)
(setq opt nil)
(assert (identifierp slot-opt))
(push `(defmethod ,slot-opt (instance)
(cl:slot-boundp instance ',slot-name))
defs))))))
;; Return (valid-cl-slot-opts . other-defining-forms)
(cons (cons slot-name (nreverse opts))
(nreverse defs))))))
(defun quote-symbol-p (sym)
"Predicate used for testing if a form is quoted.
Depending on the reader, QUOTE could be from CL or ISLISP."
(or (eq sym 'cl:quote)
(eq sym 'islisp:quote)))
(defvar *format-directives*
"~ABCDGORSTX%&"
"The format directives allowed by ISLisp")
(defun validate-format-string (string objs)
"When given a format control string and its arguments,
returns the string if it is a valid control string for ISLisp;
otherwise, returns a symbol denoting why it is not valid:
:arity-error, :unknown-format-control, :bad-numeric-format-control"
(let ((tildep nil)
(numberp nil)
(num-obj-dirs 0))
(dotimes (i (length string) (if (>= (length objs) num-obj-dirs)
string
'arity-error))
(let ((char (char-upcase (char string i))))
(if (not tildep)
;; Check if tilde
(if (char= char #\~)
(setq tildep t))
;; If we have a ~...
(if (digit-char-p char)
;; If followed by a number
(setq numberp t)
(progn
;; See if the format directive is good
(if (not (position char *format-directives*))
(return :unknown-format-control)
;; If we have a number, ensure it's only ~nR or ~nT
(if numberp
(unless (or (char= char #\R)
(char= char #\T))
(return :bad-numeric-format-control))))
;; Count the number of directives seen that use arguments
;; ~nT, ~%, ~&, and ~~ don't consume an object
(unless (position char "~T%&")
(incf num-obj-dirs))
(setq tildep nil)
(setq numberp nil))))))))
(defun valid-number-p (string)
"Predicate for whether a string represents a valid ISLisp number.
NOTE: does not trim any whitespace."
;; integers: [+|-] d*
;; floats: [+|-] d* [. d*] [e|E [+|-] d*]
(if (or (zerop (length string))
(not (find (char string 0) "+-0123456789")))
nil
(do ((decimal-seen-p nil)
(e-seen-p nil)
(i (if (find (char string 0) "+-")
1
0)
(1+ i)))
((>= i (length string))
;; Final check for trailing E
(if (digit-char-p (char string (1- (length string))))
t
nil))
(let ((char (char string i)))
(cond ((digit-char-p char)) ; Do nothing on digits
((char= char #\.) ; Decimal
(if decimal-seen-p
(return nil)
(setq decimal-seen-p t)))
((or (char= char #\e) ; E
(char= char #\E))
(if e-seen-p
(return nil)
(progn
(setq e-seen-p t)
;; Skip any + or - following the E
(if (and (> (length string) (+ i 2))
(find (char string (1+ i)) "-+"))
(incf i)))))
(t ; Anything else
(return nil)))))))
(defun validate-variable-list (bindings)
"Validates the variable list for use in ISLisp's LET or LET*."
;; Make sure we have a list
(when (not (consp bindings))
(violation bindings
"~S is not a valid variable list."
bindings))
;; Check every definition
(dolist (binding bindings)
(cond ((not (consp binding))
(violation binding
"~S is not a valid variable list pair."
binding))
((/= (length binding) 2)
(violation binding
"Bad number of elements in variable list pair ~S."
binding))
((islisp-keyword-p (car binding))
(violation binding
"Keyword ~A cannot be used as a variable name."
(car binding)))))
bindings)
(defun validate-function-list (bindings)
"Validates the variable list for use in ISLisp's FLET or LABELS."
;; Make sure we have a list
(when (not (consp bindings))
(violation bindings
"~S is not a valid variable list."
bindings))
;; Check every definition
(dolist (binding bindings)
(cond ((not (consp binding))
(violation binding
"~S is not a valid function list defintion."
binding))
((< (length binding) 2)
(violation binding
"Bad number of elements in function list definition ~S."
binding))
((islisp-keyword-p (car binding))
(violation binding
"Keyword ~A cannot be used as a function name."
(car binding)))))
;; Make sure no duplicate names
(let ((fnames (mapcar #'car bindings)))
(dolist (fname fnames)
(if (> (count fname fnames) 1)
(violation bindings
"Function definition for ~A duplicated."
fname))))
bindings)
;;; REPL helper
(defun read-new-value ()
"Used as an :interactive argument for SIGNAL-CONDITION."
(format t "Enter a new value: ")
(eval (islisp-read)))
;;; Array accessors
(defun set-aref (obj array indeces)
"Function version of (setf (aref ,array ,@indeces) ,obj)."
;; I've been a professional Common Lisp programmer for 20 years, and I
;; just now learned about ROW-MAJOR-AREF...
(setf (row-major-aref array (apply #'array-row-major-index array indeces))
obj))
;;; Extension utility functions
(defun host-implementation-feature ()
#+sbcl :sbcl
#+ccl :ccl
#+cmu :cmu
#+clisp :clisp
#+lispworks :lispworks
#+allegro :allegro
#+abcl :abcl
#+ecl :ecl
#+genera :genera
#+corman :cormanlisp
#+mezzano :mezzano
#+mocl :mocl
#+mcl :mcl
#+openmcl :openmcl
#+mkcl :mkcl
#+clasp :clasp
#+scl :scl
#+xcl :xcl
)
(defun exit (&optional status)
(declare (ignorable status))
(when (null status)
(setq status 0))
#+sbcl (sb-ext:exit :code status)
#+clisp (ext:exit status)
#+ccl (ccl:quit status)
#+cmu (ext:quit status)
#+allegro (excl:exit status)
#+lispworks (lispworks:quit :status status)
)
(defun islisp-import (&rest symbols)
"Imports functions from one package into ISLISP-USER.
Example: (islisp-sys:import 'cl:load 'cl:eval)"
(dolist (symbol (cond ((not (listp symbols)) ; (import 'cl:foo)
(list symbols))
((listp (car symbols)) ; (import (list 'cl:foo 'cl:bar))
(car symbols))
(t ; (import 'cl:foo 'cl:bar)
symbols)))
(let ((islisp-symbol (intern (symbol-name symbol)
(find-package '#:islisp-user))))
(setf (symbol-function islisp-symbol) (symbol-function symbol)))))