Search code examples
calendarlispcommon-lispastronomy

Start working with large Lisp "library" (Calendrical Calculations)


I used to write small Lisp programs, and medium programs in a derivative, Skill, by Cadence Design Systems. I now want to use the "library" of Lisp functions that comes with Calendrical Calculations and which can be downloaded (for personal use) from Cambridge University.

I installed the Lipstick package (which I have not worked with before) on my Windows 10 computer. (At least I've used Emacs). I've been using Practical Common Lisp by Peter Seibel (2005) as a text.

I want to start by giving individual function calls in the REPL, so I want to "install" the "library" in the REPL. I put the words in quotes because I'm not sure the proper Lisp words to use. I tried to just load the file and that failed, so I tried

(compile-file "calendar.l")

and the response in the REPL buffer was

; compiling file "c:/Users/card9/lispstick/calendar.l" (written 19 JUN 2022 01:12:02 PM):
; compiling (IN-PACKAGE "CC4")

The response in the other buffer, sldb[2], was

    The name "CC4" does not designate any package.
    [Condition of type SB-Kernel:SIMPLE-PACKAGE-ERROR]The name "CC4" does not designate any package.
    [Condition of type SB-Kernel:SIMPLE-PACKAGE-ERROR]

So my question is what changes should I make to the Lisp file with all the calendar functions so I can compile and load it, or alternatively, what other approach should I use to access the functions?


Solution

  • This is both very old code and not well written. You would probably stand more chance of making it work easily if you used a less strict compiler than SBCL: I can compile and load it in LispWorks and Clozure CL but I eventually lost the will to live when trying to make SBCL compile it.

    See below for a suggested workaround which may work with SBCL.

    Something you can fix: the package problems

    These are the first things you will run across, and the first version of this answer dealt only with this. This code appears to rely on semantics for in-package which went away in the 1980s (specifically, it seems, 1989)!

    The right fix (see below for an easier one perhaps) is to change the

    (in-package "CC4")
    ...
    (export ...)
    

    to something like

    (defpackage "CC4"
      (:use "CL")
      (:export ...))
    
    (in-package "CC4")
    

    Where the exported names should be turned into either strings (with the string containing the upper-cased name), or, perhaps easier, uninterned symbols (prefix name by #:, so (export ... 'foo ...) turns into (:export ... #:foo ...) in the defpackage form).

    An easier fix which involves a smaller edit to the code, and is thus probably preferable, would be to place, before the (in-package "CC4") form, this:

    (defpackage "CC4"
      (:use "CL"))
    

    Another fix is just to predefine the package which avoids editing the file: see below.

    Once you have done this you should have at least a shot at compiling the file. If that works (there may be other problems) then you can load it and would then need to say

    (use-package "CC4")
    

    defconstant problems, part one

    There are a number of places where defconstant is used where the value of the constant depends on other functions, the first being

    (defconstant jd-epoch
      ;; TYPE moment
      ;; Fixed time of start of the julian day number.
      (rd -1721424.5L0))
    

    But the value of something defined with defconstant needs to be known at compile time:

    If a defconstant form appears as a top level form, the compiler must recognize that name names a constant variable. An implementation may choose to evaluate the value-form at compile time, load time, or both. Therefore, users must ensure that the initial-value can be evaluated at compile time (regardless of whether or not references to name appear in the file) and that it always evaluates to the same value.

    And this is not the case when compiling the file.

    The fix for this is either to wrap the functions called to compute the values of any defconstant form in a suitable eval-when, so

    (defun rd (tee)
      ...)
    

    Would become

    (eval-when (:compile-toplevel :load-toplevel :execute)
      (defun rd (tee)
        ...))
    

    And this looks OK, but then you get this:

    (defconstant icelandic-epoch
      ;; TYPE fixed-date
      ;; Fixed date of start of the Icelandic calendar.
      (fixed-from-gregorian (gregorian-date 1 april 19)))
    

    And now fixed-from-gregorian and gregorian-date need to be wrapped in eval-when as does anything they call or refer to.

    Well, other than tracing through the code to find all the functions which need to be wrapped there are two workarounds to this problem.

    The first is to change the defconstants to defparameters and hope that nothing in the code tries to assign or otherwise muck around with the variable. This will reduce performance but that probably does not matter.

    The other is to do the traditional thing that terrible systems like this often used to require in the prehistory of CL: before you attempt to compile the system, load it interpreted. Here is a little wrapper file which will try to do this, and which also predefines the package:

    ;;;;; Try to compile & load calendar.l
    ;;;
    
    (in-package :cl-user)
    
    ;;; Define the package to avoid doom
    ;;;
    (defpackage "CC4"
      (:use "CL"))
    
    (let ((source (merge-pathnames (pathname "calendar.l") *load-truename*)))
      ;; Load the source, compile it and load that.
      (load source)
      (load (compile-file source)))
    

    defconstants part two: order

    If you look at the code you will find at line 1596 a definition of fixed-from-icelandic which uses summer free. summer is defined as a constant at line 4142. So this means that there is essentially no way that compile-file can do a decent job of compiling fixed-from-icelandic: the free variable needs to be known to be a constant before it's compiled.

    This is also resolved by loading the file interpreted first however.

    defconstants part three: the final collapse

    This is where I gave up with SBCL. SBCL has a very strict interpretation of defconstant, based I think on the clause cited above from the spec. That means that things like

    (defconstant foo '(1 2))
    

    Are essentially impossible in SBCL. There are workarounds described behind that link.

    Well, that means that there's really no chance of compiling this code with SBCL because there are lots of cases like this, and still less are there chances of compiling it after loading the interpreted version, which we need to do.

    Other CL implementations are less strict about this and will be happy.

    Giving up, and a workaround

    It would probably be possible to get SBCL to compile some derivative of this code, but it would need to be significantly modified. Even less fussy compilers spit out a lot of justified warnings.

    However my strong suspicion is that this code has never been compiled. So one workaround would be to simply not try and compile the file: modify the loader shim to be:

    ;;;;; Try to load calendar.l
    ;;;
    
    (in-package :cl-user)
    
    ;;; Define the package to avoid doom
    ;;;
    (defpackage "CC4"
      (:use "CL"))
    
    ;;; Don't even try to compile the file
    (load (merge-pathnames (pathname "calendar.l") *load-truename*))
    

    Doing that will let even SBCL load the file. SBCL will spit out a lot of warnings as it compiles individual definitions, since it's a compiler-only implementation.

    Whether the resulting thing will then work is another question, but it will load.


    A note: it's not usually this bad

    This is a particularly unfortunate thing to start with: most CL code is not anything like as awful as this.