Repository navigation
Expand file tree
/
Copy pathstdlib.slisp
More file actions
1545 lines (1233 loc) · 40.2 KB
/
Copy pathstdlib.slisp
File metadata and controls
1545 lines (1233 loc) · 40.2 KB
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
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; stdlib.lisp - Lisp standard library, pre-pended to user programs.
;;
;; We're consistent with argument usage so:
;;
;; n is an integer.
;; x is anything (cons, list, int, character, string, lambda, whatever).
;; xs is a list.
;; fn is a callable function, which in practice means a lambda.
;;
;; Some functions might have different arguments, but if they are anything listed
;; above then their value will always be as expected.
;;
;;; Contents
;; 0. Core
;; 1. Compatibility
;; 2. Printing
;; 3. Filesystem Functions
;; 4. Macros
;; 5. Maths functions
;; 5.1 Base conversion
;; 5.2 Maths functions
;; 6. List Creation
;; 7. List Utility Functions
;; 8. System functions.
;; 9. alist functions.
;; 10. plist functions.
;; 11. Conversion functions.
;; 12. Utility functions
;;
;; I tried to order the functions alphabetically, where it made sense.
;;; 0. Core
;;
;; We define a bunch of primitives inside our compiler/template.tmpl file,
;; these become immediately callable, however if we rely on that then we
;; lose the argument-checking facility.
;;
;; So we've defined our assembly-implemented primitives with a "sys_"-prefix
;; and here we present them to callers.
;;
(defun < (a b)
"Return true if A < B"
(sys_< a b))
(defun <= (a b)
"Return true if A <= B"
(sys_<= a b))
(defun > (a b)
"Return true if A > B"
(sys_> a b))
(defun >= (a b)
"Return true if A >= B"
(sys_>= a b))
(defun = (a b)
"Return true if A = B"
(sys_= a b))
(defun % (a b)
"Perform A modulus B"
(sys_% a b))
(defun alloc (n)
"Allocate N bytes of string memory, and fill it with ASCII.
This is only supposed to be used alongside the `syscall` function.
NOTE There is no corresponding free function, the garbage collector will
reap orphaned memory."
(sys_alloc n))
(defun alen (str)
"Return the allocated size of the given string."
(sys_alen str))
(defun car (xs)
"Return the first item from the list"
(sys_car xs))
(defun cdr (xs)
"Return the tail of the list."
(sys_cdr xs))
(defun chr (x)
"Convert the given integer to a character."
(sys_chr x))
(defun cons (a b)
"Join A and B into a list"
(sys_cons a b))
(defun environment ()
"Return a list of all known environmental variables, and their values."
(sys_environment))
(defun exit (n)
"Terminate execution, with the specified integer status-code."
(sys_exit n))
(defun explode (str)
"Convert a string to a list of characters.
NOTE: This stops at the first NULL byte (0x00)."
(sys_explode str))
(defun implode (xs)
"Convert a list of characters to a string."
(sys_implode xs))
(defun int (x)
"Convert the given thing to an integer, if possible."
(sys_int x))
(defun newline ()
"Print a newline."
(sys_newline))
(defun not (x)
"Return true when given NIL, otherwise return NIL"
(sys_not x))
(defun isqrt (x)
"Perform an integer square root of the given number."
(sys_isqrt x))
(defun nth (xs n)
"Return the Nth item from the given list."
(sys_nth xs n))
(defun nth! (xs n x)
"Change the value of the Nth item from the given list, in-place."
(sys_nth! xs n x))
(defun ord (c)
"Return the ASCII character code of the given character."
(sys_ord c))
(defun package (str)
"Return the contents of the specified embedded package, if available."
(sys_package str))
(defun packages ()
"Return the names of all available embedded packages."
(sys_packages))
;; NOP: This is a special form in our compiler
;; However if we make this then we can pretend
;; we load packages.
(defun require (x))
;; Load all embedded packages, again this is a nop
;; for compiled code, but inception can use it.
(defun require-all ()
"Load all packages which are embedded in our run-time."
(map (lambda (p) (require p)) (packages)))
(defun split (str c)
"Split a string on the given character.
Return a list of (BEFORE . AFTER)."
(sys_split str c))
(defun sqrt (x)
"Return the floating-point square root of the given number."
(sys_sqrt x))
(defun stdlib ()
"Return the contents of the stdlib.slisp standard-library."
(sys_stdlib))
(defun strat (str n)
"Return the integer value of the character at offset N for the given string.
This ignores embedded NULLs which would otherwise break substr and explode."
(sys_strat str n))
(defun strat! (str n x)
"Update the content of the given string, by changing the byte at offset N to be X."
(sys_strat! str n x))
(defun strcat (a b)
"Return the result of concatenating the two specified strings into one."
(sys_strcat a b))
(defun strcmp (a b)
"Compare the two specified strings, return zero if equal."
(sys_strcmp a b))
(defun strdup (str)
"Return a heap-allocated copy of the given string."
(sys_strdup str))
(defun string (x)
"Convert the given item into a string, if possible."
(sys_string x))
(defun strlen (str)
"Return the length, in bytes, of the given string."
(sys_strlen str))
;; Route to a wrapper to allow passing a specific number of arguments to our syscall primitive.
(defun syscall (&args)
"Make an arbitrary Linux kernel system-call.
The first argument is the number of the function to call, after that the arguments are call-specific."
(let ((len (length args)))
(cond
((= len 1) (syscall_linux_1 (nth args 0)))
((= len 2) (syscall_linux_2 (nth args 0) (nth args 1)))
((= len 3) (syscall_linux_3 (nth args 0) (nth args 1) (nth args 2)))
((= len 4) (syscall_linux_4 (nth args 0) (nth args 1) (nth args 2) (nth args 3)))
((= len 5) (syscall_linux_5 (nth args 0) (nth args 1) (nth args 2) (nth args 3) (nth args 4)))
((= len 6) (syscall_linux_6 (nth args 0) (nth args 1) (nth args 2) (nth args 3) (nth args 4) (nth args 5)))
(t nil))))
(defun syscall_linux_1 (a)
"Invoke a kernel system-call with one argument."
(sys_syscall a nil nil nil nil nil))
(defun syscall_linux_2 (a b)
"Invoke a kernel system-call with two arguments."
(sys_syscall a b nil nil nil nil))
(defun syscall_linux_3 (a b c)
"Invoke a kernel system-call with three arguments."
(sys_syscall a b c nil nil nil))
(defun syscall_linux_4 (a b c d)
"Invoke a kernel system-call with four arguments."
(sys_syscall a b c d nil nil))
(defun syscall_linux_5 (a b c d e)
"Invoke a kernel system-call with five arguments."
(sys_syscall a b c d e nil))
(defun syscall_linux_6 (a b c d e f)
"Invoke a kernel system-call with six arguments."
(sys_syscall a b c d e f))
;;; 1. Compatibility
;; Functions here are added for compatibility with other lisp dialects,
;; to allow easy copying/pasting of code.
(defun ! (x)
"Shorthand for \"not\"."
(not x))
(defun != (a b)
"Return true if A != B"
(not (= a b)))
(defun list? (x)
"Return true if the given item is a list"
(cons? x))
(defun string< (a b)
"Return true if A < B."
(< (strcmp a b) 0))
(defun string> (a b)
"Return true if A > B."
(> (strcmp a b) 0))
(defun string= (a b)
"Return true if A = B."
(= (strcmp a b) 0))
;; 4
(defun caar (x) (car (car x)))
(defun cadr (x) (car (cdr x)))
(defun cdar (x) (cdr (car x)))
(defun cddr (x) (cdr (cdr x)))
;; 8
(defun caaar (x) (car (car (car x))))
(defun caadr (x) (car (car (cdr x))))
(defun cadar (x) (car (cdr (car x))))
(defun caddr (x) (car (cdr (cdr x))))
(defun cdaar (x) (cdr (car (car x))))
(defun cdadr (x) (cdr (car (cdr x))))
(defun cddar (x) (cdr (cdr (car x))))
(defun cdddr (x) (cdr (cdr (cdr x))))
;; 16
(defun caaaar (x) (car (car (car (car x)))))
(defun caaadr (x) (car (car (car (cdr x)))))
(defun caadar (x) (car (car (cdr (car x)))))
(defun caaddr (x) (car (car (cdr (cdr x)))))
(defun cadaar (x) (car (cdr (car (car x)))))
(defun cadadr (x) (car (cdr (car (cdr x)))))
(defun caddar (x) (car (cdr (cdr (car x)))))
(defun cadddr (x) (car (cdr (cdr (cdr x)))))
(defun cdaaar (x) (cdr (car (car (car x)))))
(defun cdaadr (x) (cdr (car (car (cdr x)))))
(defun cdadar (x) (cdr (car (cdr (car x)))))
(defun cdaddr (x) (cdr (car (cdr (cdr x)))))
(defun cddaar (x) (cdr (cdr (car (car x)))))
(defun cddadr (x) (cdr (cdr (car (cdr x)))))
(defun cdddar (x) (cdr (cdr (cdr (car x)))))
(defun cddddr (x) (cdr (cdr (cdr (cdr x)))))
(defun eq (a b)
"Compatiliby wrapper, the same as \"=\"."
(= a b))
(defun eq? (a b)
"Compatiliby wrapper, the same as \"=\"."
(= a b))
(defun equal (a b)
"Compatiliby wrapper, the same as \"=\"."
(= a b))
(defun null? (x)
"Is the given item nil?"
(nil? x))
;; Ensure we have our syscall IDs defined
(require syscall)
;;; 2. Printing
;;
;; Note that our `print' function accepts a variable number of arguments
;; and will print each one in turn.
(defun print (&xs)
"Print everything we're given.
This function accepts variadic arguments, and maps against a local lambda to
do the right thing for each entry in the given list."
(map (lambda (x)
(cond
((char? x) (putc x))
((float? x) (printfloat x))
((int? x) (printint x))
((lambda? x) (printstr "<lambda>"))
((nil? x) (printstr "<nil>"))
((str? x) (printstr x))
((cons? x) (do
(putc #\()
(print_cons x) ; List items, separated by spaces
(putc #\))))
(t (printstr "unknown type")))) xs))
(defun println (&xs)
"Print everything as `print' would. Then print a newline."
(map (lambda (x) (print x)) xs)
(newline))
(defun print_cons (xs)
"Print the contents of the list X, recursively."
(print (car xs))
(if (nil? (cdr xs))
nil
(if (cons? (cdr xs))
(do
(print " ")
(print_cons (cdr xs)))
(do
(printstr " . ")
(print (cdr xs))))))
;;; 3. Filesystem Functions
;;
;; These are functions which work with files, directories, and
;; their names/paths.
;;
(defun dir? (path)
"Is the given path a directory?"
(let ((res (stat path)))
(if res
(if (= 1 (car res))
t))))
(defconst O_RDONLY 0)
(defconst O_DIRECTORY 65536)
(defun entries (path)
"Return a list of directory entries beneath PATH, or nil on failure.
The implementation here is either horrid, or awesome. You decide."
(let ((fd 0)
(size 8192) ; size of the pool we read entries into.
(buf (alloc size)) ; Allocate that pool for the results.
(result nil)
(n 0)
(pos 0))
(set! fd (syscall SYS_open path (+ O_RDONLY O_DIRECTORY) 0))
(if (< fd 0)
nil
(do
;; Read a batch of directory entries.
(set! n (syscall SYS_getdents64 fd buf size))
(while (> n 0)
(set! pos 0)
;; Walk the linux_dirent64 records.
(while (< pos n)
;; offset 16: uint16 d_reclen
;; offset 18: uint8 d_type
;; offset 19: char[] d_name
(let ((reclen (u16 buf (+ pos 16)))
(name-pos (+ pos 19))
(name-len 0))
;; Find the NUL terminator in d_name.
(while (and
(< name-len (- reclen 19))
(not (= (u8 buf (+ name-pos name-len)) 0)))
(set! name-len (+ name-len 1)))
;; Allocate name + terminating NUL to copy the entry's filename into.
(let ((name (alloc (+ name-len 1)))
(i 0))
;; Copy the name byte-by-byte.
(while (< i name-len)
(strat! name i
(u8 buf (+ name-pos i)))
(set! i (+ i 1)))
;; Add the terminating NUL.
(strat! name name-len 0)
;; Add name to the result list.
(set! result (cons name result)))
(set! pos (+ pos reclen))))
(set! n (syscall SYS_getdents64 fd buf size)))
(syscall SYS_close fd)
(if (< n 0) nil result)))))
(defun exists? (path)
"Does the named path exist?"
(cons? (stat path)))
(defun fclose (handle)
"Close the given handle.
As a special case closing a NIL file-handle is valid"
(cond
((nil? handle) nil)
((int? handle)
(let ((res 0))
(set! res (syscall SYS_close handle))
(if (>= res 0)
res
nil)))
(t (do (println "Calling fclose with an invalid handle-type") (exit 1)))))
(defun fopen (str mode)
"Open a file with the given mode.
Mode is \"r\" for read, \"w\" for write, and \"a\" for append."
(let ((flags 0)
(handle 0))
(cond
((= 0 (strcmp mode "a")) (set! flags 1089)) ; O_WRONLY|O_CREAT|O_APPEND
((= 0 (strcmp mode "r")) (set! flags 0)) ; O_RDONLY
((= 0 (strcmp mode "w")) (set! flags 577)) ; O_WRONLY|O_CREAT|O_TRUNC
(t (do (println "unknown mode for fopen:" mode ) (exit 1))))
(set! handle (syscall SYS_open str flags (octal "0o644")))
(if (>= handle 0)
handle
nil)))
(defconst SEEK_END 2)
(defconst SEEK_START 0)
(defun fread (handle)
"Read all available data from the given file-handle.
It is assumed the filehandle came from fopen, but nil is also accepted."
(if (nil? handle)
nil
(let ((size 0))
(set! size (syscall SYS_lseek handle 0 SEEK_END))
(if (<= size 0)
nil
(do
(if (< (syscall SYS_lseek handle 0 SEEK_START) 0)
nil
(let ((mem (alloc size))
(res (syscall SYS_read handle mem size)))
(if (< res 0)
nil
mem))))))))
(defun fwrite (handle data len)
"Write the given data, of length LEN, to the specified file-handle.
It is assumed the filehandle came from fopen, but nil is also accepted."
(if (nil? handle)
nil
(let ((res (syscall SYS_write handle data len)))
(if (>= res 0)
res
nil))))
(defun file? (path)
"Is the given path a file?"
(let ((res (stat path)))
(if res
(if (= 0 (car res))
t))))
(defun mkdir (path)
"Make the given directory.
Note that this does not create parents, use (mkdirs) for that."
(if (< (syscall SYS_mkdir path (octal "0o755")) 0)
nil
1))
(defun mkdirs (path)
"Make the given directory, creating parent directories as necessary"
(mkdirs-helper (split-all path #\/) nil))
(defun mkdirs-helper (parts current)
(if (nil? parts)
t
(let ((next (if (nil? current)
(car parts)
(strcat (strcat current "/")
(car parts)))))
;; make the next part if this part already exist.
(if (not (dir? next))
(mkdir next))
(mkdirs-helper (cdr parts) next))))
(defun which (binary)
"Return the complete path to the file, on the system $PATH, if possible."
(let ((path (split-all (getenv "PATH") #\:))
(res (filter path (lambda (dir) (exists? (join (list dir "/" binary)))))))
(if res
(join (list (car res) "/" binary)))))
(defun rmdir (path)
(if (< (syscall SYS_rmdir path) 0)
nil
1))
;; undocumented
(defun u8 (buf pos)
"Read and return a 8-bit value from the specified offset in the given buffer."
(strat buf pos))
;; undocumented
(defun u16 (buf pos)
"Read and return a 16-bit value from the specified offset in the given buffer."
(+ (u8 buf pos) (* (u8 buf (+ pos 1)) 256)))
;; undocumented
(defun u32 (buf pos)
"Read and return a 32-bit value from the specified offset in the given buffer."
(+ (u16 buf pos) (* (u16 buf (+ pos 2)) 65536)))
;; undocumented
(defun u64 (buf pos)
"Read and return a 64-bit value from the specified offset in the given buffer."
(+ (u32 buf pos) (* (u32 buf (+ pos 4)) 4294967296)))
;; undocumented
(defun u64! (buf pos val)
(sys_u64! buf pos val))
(defun stat (path)
"Return details of the given filesystem entry.
This returns a list of the form (TYPE SIZE MODE)."
;; buf holds the return value from the stat call.
;; 64-bit Linux (x86_64): The struct stat structure is typically 144 bytes so this string is 145 bytes long.
(let ((buf " "))
(let ((result (syscall SYS_stat path buf)))
(if (< result 0)
nil ; stat failed
(do
(let ((type (& (u32 buf 24) 61440)) ; type is file type
(mode (& (u32 buf 24) 511)) ; mode holds permission bits
(size (u64 buf 48))) ; file size
(cond
((= type 32768) (set! type 0)) ; FILE -> 0
((= type 16384) (set! type 1)) ; DIR -> 1
((= type 40960) (set! type 2)) ; LINK -> 2
((= type 8192) (set! type 3)) ; CHR -> 3
((= type 24576) (set! type 4)) ; BLOCK -> 4
((= type 4096) (set! type 5)) ; FIFO -> 5
((= type 49152) (set! type 6)) ; SOCKET -> 6
(t (set! type -1)) ; UNKNOWN -> -1
)
(list type size mode))))))) ; return the list
(defun unlink (path)
"Delete the given file from the filesystem.
Use (rmdir) to remove directories instead of files."
(if (< (syscall SYS_unlink path) 0)
nil
1))
;;; 4. Macros
;;
;; Some trivial macros, implementing useful things.
(defmacro and (&xs)
"Return the value of the last argument, so long as every argument up to
that point is non-nil.
Stops, and returns nil, as soon as an argument is nil, without evaluating
any remaining remaining arguments."
(if (nil? xs)
t
(if (nil? (cdr xs))
(car xs)
`(if ,(car xs) (and ,@(cdr xs)) nil))))
(defmacro benchmark (&body)
"Return the time taken to run the given body."
`(let ((n (now)))
,@body
(- (now) n)))
(defmacro cond (&clauses)
"Evaluate each clause's test, in turn, stopping at the first one that
is non-nil and returning the value of that clause's last body-expression
(or the test's own value, if the clause has no body).
Returns nil on no match"
(if (nil? clauses)
nil
`(if ,(car (car clauses))
(do ,@(cdr (car clauses)))
(cond ,@(cdr clauses)))))
(defmacro dolist (lst &body)
"Anaphoric list iteration: for each element of LST, bind it to 'it'
and run BODY for side-effects. Returns nil."
`(let ((dolist-rest ,lst))
(while dolist-rest
(let ((it (car dolist-rest)))
,@body)
(set! dolist-rest (cdr dolist-rest)))))
(defmacro or (&xs)
"Return the value of the first non-nil argument, it does not evaluate remaining arguments."
(if (nil? xs)
nil
(if (nil? (cdr xs))
(car xs)
`(let ((or-value ,(car xs)))
(if or-value or-value (or ,@(cdr xs)))))))
(defmacro time (&body)
"Return the last expression executed, but report on the elapsed time."
`(let ((start (now)))
(let ((result (do ,@body)))
(println "Elapsed: " (- (now) start) "ms")
result)))
(defmacro when (c &body)
"Run an unlimited number of expressions when the given condition is true."
`(if ,c (do ,@body)))
(defmacro unless (c &body)
"Run an unlimited number of expressions when the given condition is false."
`(if ,c nil (do ,@body)))
;;; 5. Maths functions
;;
;; These are mostly about testing for specific values, or
;; processing items of lists.
;;; 5.1 base conversions
(defun digit-value (ch)
"return the value of a given number, used for parsing binary/hex/octal strings"
(cond
((= ch "0") 0)
((= ch "1") 1)
((= ch "2") 2)
((= ch "3") 3)
((= ch "4") 4)
((= ch "5") 5)
((= ch "6") 6)
((= ch "7") 7)
((= ch "8") 8)
((= ch "9") 9)
((= ch "a") 10)
((= ch "A") 10)
((= ch "b") 11)
((= ch "B") 11)
((= ch "c") 12)
((= ch "C") 12)
((= ch "d") 13)
((= ch "D") 13)
((= ch "e") 14)
((= ch "E") 14)
((= ch "f") 15)
((= ch "F") 15)
(t -1)))
(defun integer-base? (text prefix base)
"Is the given text a valid number in the specified base?"
(let ((len (strlen text))
(pos 2)
(digit 0)
(ok t))
(if (< len 3)
nil
(if (or (!= (substr text 0 1) "0")
(and (!= (substr text 1 1) prefix)
(!= (substr text 1 1) (upper prefix))))
nil
(do
(while (and ok (< pos len))
(set! digit (digit-value (substr text pos 1)))
(if (or (< digit 0)
(>= digit base))
(set! ok nil))
(set! pos (+ pos 1)))
ok)))))
(defun parse-integer (text prefix base)
(let ((value 0)
(pos 2)
(len (strlen text))
(digit 0))
;; Assume the caller has already verified the prefix.
(while (< pos len)
(set! digit (digit-value (substr text pos 1)))
(if (or (< digit 0) (>= digit base))
(do
(println "Invalid digit")
(exit 1)))
(set! value (+ (* value base) digit))
(set! pos (+ pos 1)))
value))
(defun binary (text)
"Parse the given text as a binary number, complete with an 0b prefix.
See-also dec2binary."
(parse-integer text "b" 2))
(defun binary? (text)
(integer-base? text "b" 2))
(defun hex (text)
"Parse the given text as a hex number, complete with an 0x prefix.
See-also dec2hex."
(parse-integer text "x" 16))
(defun hex? (text)
(integer-base? text "x" 16))
(defun octal (text)
"Parse the given text as a binary number, complete with an 0o prefix.
See-also dec2octal"
(parse-integer text "o" 8))
(defun octal? (text)
(integer-base? text "o" 8))
;;; 5.1.1 base conversions from decimal
(defun base-digit (value)
"Return the character representation of a digit value 0-15."
(cond
((= value 0) "0")
((= value 1) "1")
((= value 2) "2")
((= value 3) "3")
((= value 4) "4")
((= value 5) "5")
((= value 6) "6")
((= value 7) "7")
((= value 8) "8")
((= value 9) "9")
((= value 10) "A")
((= value 11) "B")
((= value 12) "C")
((= value 13) "D")
((= value 14) "E")
((= value 15) "F")
(t nil)))
(defun decimal-base (value prefix base)
"Convert a decimal integer to a number in the specified base."
(let ((result "")
(remainder 0)
(digit "")
(negative nil))
;; Remember if our value was negative
(if (< value 0)
(do
(set! negative t)
(set! value (- 0 value))))
;; Zero is a special case because the loop below would not run.
(if (= value 0)
(set! result "0")
(while (> value 0)
(set! remainder (% value base))
(set! digit (base-digit remainder))
(set! result (strcat digit result))
(set! value (/ value base))))
;; Restore sign if we need to.
(if negative (set! result (strcat "-" result)))
;; Minimum width is two digits; mostly for hex digits.
(if (= (strlen result) 1)
(set! result (strcat "0" result)))
(strcat prefix result)))
(defun dec2binary (value)
"Convert a decimal integer to binary, with a 0b prefix.
See also binary, and binary?"
(decimal-base value "0b" 2))
(defun dec2hex (value)
"Convert a decimal integer to hexadecimal, with a 0x prefix.
See also hex, and hex?"
(decimal-base value "0x" 16))
(defun dec2octal (value)
"Convert a decimal integer to octal, with a 0o prefix.
See also octal, and octal?"
(decimal-base value "0o" 8))
;;; 5.2 maths functions
;; Define the usual maths
(defun + (a b)
"Addition operation."
(sys_plus a b))
(defun - (a &b)
"Subtraction operation."
(if (car b)
(sys_minus a (car b))
(sys_minus 0 a)))
(defun * (a b)
"Multiplication operation."
(sys_multiply a b))
(defun / (a &b)
"Division operation."
(if (car b)
(sys_divide a (car b))
(sys_divide 1 (+ a 0.0))))
(defun & (a b)
"Binary AND between two integers."
(sys_and a b))
(defun | (a b)
"Binary OR between two integers."
(sys_or a b))
(defun ^ (a b)
"Binary XOR between two integers."
(sys_xor a b))
(defun abs (n)
"Return the absolute value of N, as a positive number"
(if (< n 0) (- 0 n) n))
(defun even? (n)
"Return 1 if the number is even, nil otherwise."
(= (% n 2 ) 0))
(defun max (xs)
"Return the maximum integer from the list of numbers supplied."
(if (nil? xs)
xs
(reduce xs
(lambda (a b)
(if (< a b) b a))
(car xs))))
(defun min (xs)
"Return the smallest integer from the list of numbers supplied."
(if (nil? xs)
xs
(reduce xs
(lambda (a b)
(if (< a b) a b))
(car xs))))
(defun neg? (n)
"Return 1 if the number is negative, nil otherwise."
(< n 0))
(defun odd? (n)
"Return 1 if the number is odd, nil otherwise."
(= (% n 2 ) 1))
(defun one? (n)
"Return true if the number is one, NIL otherwise."
(= n 1))
(defun pos? (n)
"Return 1 if the number is positive, NIL otherwise."
(> n 0))
(defun pow (x y)
"Return X to the power of Y."
(if (= 0 y)
1
(* x (pow x (- y 1)))))
;; Sum all the numbers in the given list
(defun sum (xs)
"Return the sum of all the numbers in the list"
(if xs
(+ (car xs)
(sum (cdr xs)))
0))
(defun zero? (n)
"Return true if the number is zero, NIL otherwise."
(= n 0))
;;; 6. List Creation
;;
;; These are functions which are designed to create lists,
;; with ascending values for example.
(defmacro list (&xs)
"Build a list containing the value of each argument, in order.
(list a b c) is the same as (cons a (cons b (cons c nil)))."
(if (nil? xs)
nil
`(cons ,(car xs) (list ,@(cdr xs)))))
(defun nat (n)
"Create a list of numbers ranging from 1-N, inclusive."
(range 1 n 1))
(defun range (start end step)
"Create a list of numbers between the start and end bounds, inclusive.
Incrementing by the given step each time."