Skip to content
scheme-compat.lisp 6.34 KiB
Newer Older
;;;; -*- lisp -*-
; A thin emulation layer to run simple Scheme code within CL.
#+xcvb
(module
 (:depends-on
  ("/fare-utils"
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
   "/exscribe/packages"
   "/exscribe/specials")))

#| "
This code aims to provide a very thin layer of Scheme compatibility in CL.
It is meant to leverage simple code written with Scheme syntax;
it does NOT try to actually implement deep Scheme semantics.
If you want a real Scheme-in-CL implementation, there are other places to look.
The goal is for me to more easily migrate my former Scribe documents,
and otherwise allow to reuse and share code between Scheme and CL
while minizing the amount of things to change on either side.

Here are some particular implementation choices and limitations:

* We don't do any extraneous error checking.
 If the Scheme program wasn't debugged, we don't care to support it.
 This allows for a very shallow implementation of Scheme primitives
 with less strict Common Lisp primitives that behave well
 in cases defined as erroneous or left undefined by the Scheme standard.

* We don't provide a unified namespace for functions and variables,
 but reuse what the Common Lisp evaluator provides.

  = Be sure to use (funcall ...) and (function ...) as appropriate;
   The macros funcall*, apply* and map* are provided to wrap (function ...)
   around their first argument. You can't use map, which means something
  different in Scheme and CL; use mapcar or map* instead.

  = be sure to not try to bind T or NIL or any other constant as a variable.
   you may have to rename a few variables in your Scheme program.

* We don't support distinction of NIL () and #f or inexact numbers.

* We won't provide any kind of support for first-class continuations,
 They could be added on top of Screamer, I suppose.

* I implement features on a need basis. I won't proactively implement
 any features from RnRS or SRFIs. But it's easy enough to port those features
 that you need, and I'll accept patches.

* We only support the symbols declared in package scheme-makeup,
 plus the following from the CL package:
	cons car cdr c([ad]+)r list
	let let*

* We recommend that you use the following CL features that are in Scribe
 if not in RnRS:
   labels => together with let, a good replacement for letrec and named let.

* Anything that clashes with CL semantics is unsupported; sometimes we provide
 some equivalent functionality under a different name:
   call-with-current-continuation => unsupported
   letrec => unsupported, because it doesn't distinguish namespaces. Use labels.
   let => named let is unsupported. Use labels.
   map => Use map* or mapcar.
   for-each => use for-each* or for-each


Reference for Scheme and CL semantics:
	http://swiss.csail.mit.edu/~jaffer/r5rs_toc.html
	http://lisp.org/HyperSpec/

If we really wanted to implement lisp1 semantics for let, letrec, etc.,
we could example a lisp1 binding for x to value into
  (macrolet ((,x (&rest ,args) `(funcall ,,x ,@,args)))
    (let ((,x ,value))
      ,@body))

|#

(in-package :scheme-compat)

(defparameter *scheme-true* t)
(defparameter *scheme-false* nil)
(defparameter else t)

;;; define
(defun process-scheme-formals (formals)
  (cond
    ((null formals) nil)
    ((symbolp formals) `(&rest ,formals))
    ((consp formals)
     (cons (car formals) (process-scheme-formals (cdr formals))))
    (t (error "bad scheme formals"))))
(defun make-function-definition (name formals body &optional (defun 'defun))
  `(,defun ,name ,(process-scheme-formals formals) ,@body))
(defmacro define (name &rest rest)
  (if (symbolp name)
      `(defparameter ,name ,@rest)
      (make-function-definition (first name) (rest name) rest)))

;;; forms implementable as simple aliases
(defmacro defalias (simpleargs sch cl)
  `(defun ,sch ,simpleargs (,cl ,@simpleargs)))
(defmacro defaliases (args &rest r)
  `(progn ,@(loop for (sch . cl) in (plist->alist r)
		  collect `(defalias ,args ,sch ,cl))))
(defmacro defxalias (sch cl)
  `(defun ,sch (&rest r) (apply ',cl r)))
(defmacro defxaliases (&rest r)
  `(progn ,@(loop for (sch . cl) in (plist->alist r)
		  collect `(defxalias ,sch ,cl))))
(defmacro defmalias (sch cl)
  `(defmacro ,sch (&rest r) `(,',cl ,@r)))
(defmacro defmaliases (&rest r)
  `(progn ,@(loop for (sch . cl) in (plist->alist r)
		  collect `(defmalias ,sch ,cl))))
(defmacro defvalias (sch cl)
  `(define-symbol-macro ,sch ,cl))
(defmacro defvaliases (&rest r)
  `(progn ,@(loop for (sch . cl) in (plist->alist r)
		  collect `(defvalias ,sch ,cl))))

(defaliases (x)
    string? stringp
    symbol? symbolp
    number? numberp
    integer? integerp
    pair? consp
    list? listp
    null? null
    zero? zerop
    string-length length
    symbol->string symbol-name
    number->string princ-to-string)
(defaliases (x y)
    eq? eq
    eqv? eql
    equal? equal
    remainder rem
    modulo mod)
(defxaliases
    display princ
    substring subseq
    ;map mapcar
    for-each mapc)
(defvaliases
    current-input-port *standard-input*
    current-output-port *standard-output*)
(defmaliases
    begin progn)

(defmacro set! (var val)
  `(progn (setq ,var ,val) nil))

(defmacro define-macro (name &rest rest)
  (make-function-definition (first name) (rest name) rest 'defmacro))


;;; slightly different forms
(defun string-append (&rest r)
  (apply 'concatenate 'string r))
(defun string->symbol (x)
  (intern x :exscribe-user))

;;; Scribe-only
(defun keyword->string (x)
  (if (keywordp x) (conc-string #\: x) (error "~A isn't a keyword" x)))
(defaliases (x)
    file-exists? probe-file
    keyword? keywordp)
(defvaliases
    *scribe-format* exscribe::*exscribe-mode*
    *scribe-header* exscribe::*header*
    *scribe-footer* exscribe::*footer*
    *scribe-background* exscribe::*background*
    *scribe-foreground* exscribe::*foreground*
    *scribe-tbackground* exscribe::*title-background*
    *scribe-sbackground* exscribe::*section-title-background*)

;;; Syntax
(defun set-scheme-macro-characters ()
  (set-dispatch-macro-character #\# #\t
      #'(lambda (stream subchar arg)
	  (declare (ignore stream subchar arg))
	  *scheme-true*))
  (set-dispatch-macro-character #\# #\f
      #'(lambda (stream subchar arg)
	  (declare (ignore stream subchar arg))
	  *scheme-false*))
  (set-dispatch-macro-character #\# #\!
      #'(lambda (stream subchar arg)
	  (declare (ignore subchar arg))
	  (let ((x (read stream)))
	    (case x
	      ((key) '&key)
	      ((rest) '&rest)
	      (t (error "unrecognized form #!~A" x))))))
  t)