diff --git a/.gitignore b/.gitignore index 833bc4e..a0db7e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /target /classes /checkouts +resources/scratch/ profiles.clj pom.xml pom.xml.asc @@ -16,3 +17,7 @@ pom.xml.asc .clj-kondo/ .lsp/ resources/scratch.lsp +Sysout*.lsp +*.pdf + +src/beowulf/scratch.clj diff --git a/CHANGELOG.md b/CHANGELOG.md index 9411b74..38c963d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,17 @@ # Change Log All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). -## [0.2.1] - 2023-03-?? +## [0.3.0] - 2023-04-10 + +### Changed + +- Added property lists in the exact format used by Lisp 1.5; +- Added `ASSOC`, `EFFACE`, `MAPLIST`, `PROG`, and other functions; +- Where there are both interpreted (`EXPR`) and Clojure (`SUBR`) implementations of the same function, the `EXPR` is preferred (it is planned to make this configurable if/when there is a working compiler); +- More error messages/diagnostics are now printed in Old English (it is planned to implement internationalisation so that you can switch to messages you actually understand); +- Documentation improvements. + +## [0.2.1] - 2023-03-30 ### Changed - this is fundamentally a working Lisp. The reader reads S-Expressions fully and M-Expressions at least partially. It is not (yet) a feature complete Lisp 1.5. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d3087e4..0000000 --- a/LICENSE +++ /dev/null @@ -1,277 +0,0 @@ -Eclipse Public License - v 2.0 - - THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE - PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION - OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. - -1. DEFINITIONS - -"Contribution" means: - - a) in the case of the initial Contributor, the initial content - Distributed under this Agreement, and - - b) in the case of each subsequent Contributor: - i) changes to the Program, and - ii) additions to the Program; - where such changes and/or additions to the Program originate from - and are Distributed by that particular Contributor. A Contribution - "originates" from a Contributor if it was added to the Program by - such Contributor itself or anyone acting on such Contributor's behalf. - Contributions do not include changes or additions to the Program that - are not Modified Works. - -"Contributor" means any person or entity that Distributes the Program. - -"Licensed Patents" mean patent claims licensable by a Contributor which -are necessarily infringed by the use or sale of its Contribution alone -or when combined with the Program. - -"Program" means the Contributions Distributed in accordance with this -Agreement. - -"Recipient" means anyone who receives the Program under this Agreement -or any Secondary License (as applicable), including Contributors. - -"Derivative Works" shall mean any work, whether in Source Code or other -form, that is based on (or derived from) the Program and for which the -editorial revisions, annotations, elaborations, or other modifications -represent, as a whole, an original work of authorship. - -"Modified Works" shall mean any work in Source Code or other form that -results from an addition to, deletion from, or modification of the -contents of the Program, including, for purposes of clarity any new file -in Source Code form that contains any contents of the Program. Modified -Works shall not include works that contain only declarations, -interfaces, types, classes, structures, or files of the Program solely -in each case in order to link to, bind by name, or subclass the Program -or Modified Works thereof. - -"Distribute" means the acts of a) distributing or b) making available -in any manner that enables the transfer of a copy. - -"Source Code" means the form of a Program preferred for making -modifications, including but not limited to software source code, -documentation source, and configuration files. - -"Secondary License" means either the GNU General Public License, -Version 2.0, or any later versions of that license, including any -exceptions or additional permissions as identified by the initial -Contributor. - -2. GRANT OF RIGHTS - - a) Subject to the terms of this Agreement, each Contributor hereby - grants Recipient a non-exclusive, worldwide, royalty-free copyright - license to reproduce, prepare Derivative Works of, publicly display, - publicly perform, Distribute and sublicense the Contribution of such - Contributor, if any, and such Derivative Works. - - b) Subject to the terms of this Agreement, each Contributor hereby - grants Recipient a non-exclusive, worldwide, royalty-free patent - license under Licensed Patents to make, use, sell, offer to sell, - import and otherwise transfer the Contribution of such Contributor, - if any, in Source Code or other form. This patent license shall - apply to the combination of the Contribution and the Program if, at - the time the Contribution is added by the Contributor, such addition - of the Contribution causes such combination to be covered by the - Licensed Patents. The patent license shall not apply to any other - combinations which include the Contribution. No hardware per se is - licensed hereunder. - - c) Recipient understands that although each Contributor grants the - licenses to its Contributions set forth herein, no assurances are - provided by any Contributor that the Program does not infringe the - patent or other intellectual property rights of any other entity. - Each Contributor disclaims any liability to Recipient for claims - brought by any other entity based on infringement of intellectual - property rights or otherwise. As a condition to exercising the - rights and licenses granted hereunder, each Recipient hereby - assumes sole responsibility to secure any other intellectual - property rights needed, if any. For example, if a third party - patent license is required to allow Recipient to Distribute the - Program, it is Recipient's responsibility to acquire that license - before distributing the Program. - - d) Each Contributor represents that to its knowledge it has - sufficient copyright rights in its Contribution, if any, to grant - the copyright license set forth in this Agreement. - - e) Notwithstanding the terms of any Secondary License, no - Contributor makes additional grants to any Recipient (other than - those set forth in this Agreement) as a result of such Recipient's - receipt of the Program under the terms of a Secondary License - (if permitted under the terms of Section 3). - -3. REQUIREMENTS - -3.1 If a Contributor Distributes the Program in any form, then: - - a) the Program must also be made available as Source Code, in - accordance with section 3.2, and the Contributor must accompany - the Program with a statement that the Source Code for the Program - is available under this Agreement, and informs Recipients how to - obtain it in a reasonable manner on or through a medium customarily - used for software exchange; and - - b) the Contributor may Distribute the Program under a license - different than this Agreement, provided that such license: - i) effectively disclaims on behalf of all other Contributors all - warranties and conditions, express and implied, including - warranties or conditions of title and non-infringement, and - implied warranties or conditions of merchantability and fitness - for a particular purpose; - - ii) effectively excludes on behalf of all other Contributors all - liability for damages, including direct, indirect, special, - incidental and consequential damages, such as lost profits; - - iii) does not attempt to limit or alter the recipients' rights - in the Source Code under section 3.2; and - - iv) requires any subsequent distribution of the Program by any - party to be under a license that satisfies the requirements - of this section 3. - -3.2 When the Program is Distributed as Source Code: - - a) it must be made available under this Agreement, or if the - Program (i) is combined with other material in a separate file or - files made available under a Secondary License, and (ii) the initial - Contributor attached to the Source Code the notice described in - Exhibit A of this Agreement, then the Program may be made available - under the terms of such Secondary Licenses, and - - b) a copy of this Agreement must be included with each copy of - the Program. - -3.3 Contributors may not remove or alter any copyright, patent, -trademark, attribution notices, disclaimers of warranty, or limitations -of liability ("notices") contained within the Program from any copy of -the Program which they Distribute, provided that Contributors may add -their own appropriate notices. - -4. COMMERCIAL DISTRIBUTION - -Commercial distributors of software may accept certain responsibilities -with respect to end users, business partners and the like. While this -license is intended to facilitate the commercial use of the Program, -the Contributor who includes the Program in a commercial product -offering should do so in a manner which does not create potential -liability for other Contributors. Therefore, if a Contributor includes -the Program in a commercial product offering, such Contributor -("Commercial Contributor") hereby agrees to defend and indemnify every -other Contributor ("Indemnified Contributor") against any losses, -damages and costs (collectively "Losses") arising from claims, lawsuits -and other legal actions brought by a third party against the Indemnified -Contributor to the extent caused by the acts or omissions of such -Commercial Contributor in connection with its distribution of the Program -in a commercial product offering. The obligations in this section do not -apply to any claims or Losses relating to any actual or alleged -intellectual property infringement. In order to qualify, an Indemnified -Contributor must: a) promptly notify the Commercial Contributor in -writing of such claim, and b) allow the Commercial Contributor to control, -and cooperate with the Commercial Contributor in, the defense and any -related settlement negotiations. The Indemnified Contributor may -participate in any such claim at its own expense. - -For example, a Contributor might include the Program in a commercial -product offering, Product X. That Contributor is then a Commercial -Contributor. If that Commercial Contributor then makes performance -claims, or offers warranties related to Product X, those performance -claims and warranties are such Commercial Contributor's responsibility -alone. Under this section, the Commercial Contributor would have to -defend claims against the other Contributors related to those performance -claims and warranties, and if a court requires any other Contributor to -pay any damages as a result, the Commercial Contributor must pay -those damages. - -5. NO WARRANTY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT -PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" -BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR -IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF -TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR -PURPOSE. Each Recipient is solely responsible for determining the -appropriateness of using and distributing the Program and assumes all -risks associated with its exercise of rights under this Agreement, -including but not limited to the risks and costs of program errors, -compliance with applicable laws, damage to or loss of data, programs -or equipment, and unavailability or interruption of operations. - -6. DISCLAIMER OF LIABILITY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT -PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS -SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST -PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE -EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - -7. GENERAL - -If any provision of this Agreement is invalid or unenforceable under -applicable law, it shall not affect the validity or enforceability of -the remainder of the terms of this Agreement, and without further -action by the parties hereto, such provision shall be reformed to the -minimum extent necessary to make such provision valid and enforceable. - -If Recipient institutes patent litigation against any entity -(including a cross-claim or counterclaim in a lawsuit) alleging that the -Program itself (excluding combinations of the Program with other software -or hardware) infringes such Recipient's patent(s), then such Recipient's -rights granted under Section 2(b) shall terminate as of the date such -litigation is filed. - -All Recipient's rights under this Agreement shall terminate if it -fails to comply with any of the material terms or conditions of this -Agreement and does not cure such failure in a reasonable period of -time after becoming aware of such noncompliance. If all Recipient's -rights under this Agreement terminate, Recipient agrees to cease use -and distribution of the Program as soon as reasonably practicable. -However, Recipient's obligations under this Agreement and any licenses -granted by Recipient relating to the Program shall continue and survive. - -Everyone is permitted to copy and distribute copies of this Agreement, -but in order to avoid inconsistency the Agreement is copyrighted and -may only be modified in the following manner. The Agreement Steward -reserves the right to publish new versions (including revisions) of -this Agreement from time to time. No one other than the Agreement -Steward has the right to modify this Agreement. The Eclipse Foundation -is the initial Agreement Steward. The Eclipse Foundation may assign the -responsibility to serve as the Agreement Steward to a suitable separate -entity. Each new version of the Agreement will be given a distinguishing -version number. The Program (including Contributions) may always be -Distributed subject to the version of the Agreement under which it was -received. In addition, after a new version of the Agreement is published, -Contributor may elect to Distribute the Program (including its -Contributions) under the new version. - -Except as expressly stated in Sections 2(a) and 2(b) above, Recipient -receives no rights or licenses to the intellectual property of any -Contributor under this Agreement, whether expressly, by implication, -estoppel or otherwise. All rights in the Program not expressly granted -under this Agreement are reserved. Nothing in this Agreement is intended -to be enforceable by any entity that is not a Contributor or Recipient. -No third-party beneficiary rights are created under this Agreement. - -Exhibit A - Form of Secondary Licenses Notice - -"This Source Code may also be made available under the following -Secondary Licenses when the conditions for such availability set forth -in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), -version(s), and exceptions or additional permissions here}." - - Simply including a copy of this Agreement, including this Exhibit A - is not sufficient to license the Source Code under Secondary Licenses. - - If it is not possible or desirable to put the notice in a particular - file, then You may include the notice in a location (such as a LICENSE - file in a relevant directory) where a recipient would be likely to - look for such a notice. - - You may add additional accurate notices of copyright ownership. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..45547a8 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,333 @@ +### GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +### Preamble + +The licenses for most software are designed to take away your freedom +to share and change it. By contrast, the GNU General Public License is +intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + +We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, +we want its recipients to know that what they have is not the +original, so that any problems introduced by others will not reflect +on the original authors' reputations. + +Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at +all. + +The precise terms and conditions for copying, distribution and +modification follow. + +### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +**0.** This License applies to any program or other work which +contains a notice placed by the copyright holder saying it may be +distributed under the terms of this General Public License. The +"Program", below, refers to any such program or work, and a "work +based on the Program" means either the Program or any derivative work +under copyright law: that is to say, a work containing the Program or +a portion of it, either verbatim or with modifications and/or +translated into another language. (Hereinafter, translation is +included without limitation in the term "modification".) Each licensee +is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the Program +(independent of having been made by running the Program). Whether that +is true depends on what the Program does. + +**1.** You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a +fee. + +**2.** You may modify your copy or copies of the Program or any +portion of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + +**a)** You must cause the modified files to carry prominent notices +stating that you changed the files and the date of any change. + +**b)** You must cause any work that you distribute or publish, that in +whole or in part contains or is derived from the Program or any part +thereof, to be licensed as a whole at no charge to all third parties +under the terms of this License. + +**c)** If the modified program normally reads commands interactively +when run, you must cause it, when started running for such interactive +use in the most ordinary way, to print or display an announcement +including an appropriate copyright notice and a notice that there is +no warranty (or else, saying that you provide a warranty) and that +users may redistribute the program under these conditions, and telling +the user how to view a copy of this License. (Exception: if the +Program itself is interactive but does not normally print such an +announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +**3.** You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + +**a)** Accompany it with the complete corresponding machine-readable +source code, which must be distributed under the terms of Sections 1 +and 2 above on a medium customarily used for software interchange; or, + +**b)** Accompany it with a written offer, valid for at least three +years, to give any third party, for a charge no more than your cost of +physically performing source distribution, a complete machine-readable +copy of the corresponding source code, to be distributed under the +terms of Sections 1 and 2 above on a medium customarily used for +software interchange; or, + +**c)** Accompany it with the information you received as to the offer +to distribute corresponding source code. (This alternative is allowed +only for noncommercial distribution and only if you received the +program in object code or executable form with such an offer, in +accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + +**4.** You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt otherwise +to copy, modify, sublicense or distribute the Program is void, and +will automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + +**5.** You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +**6.** Each time you redistribute the Program (or any work based on +the Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + +**7.** If, as a consequence of a court judgment or allegation of +patent infringement or for any other reason (not limited to patent +issues), conditions are imposed on you (whether by court order, +agreement or otherwise) that contradict the conditions of this +License, they do not excuse you from the conditions of this License. +If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, +then as a consequence you may not distribute the Program at all. For +example, if a patent license would not permit royalty-free +redistribution of the Program by all those who receive copies directly +or indirectly through you, then the only way you could satisfy both it +and this License would be to refrain entirely from distribution of the +Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +**8.** If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + +**9.** The Free Software Foundation may publish revised and/or new +versions of the General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Program does not specify a +version number of this License, you may choose any version ever +published by the Free Software Foundation. + +**10.** If you wish to incorporate parts of the Program into other +free programs whose distribution conditions are different, write to +the author to ask for permission. For software which is copyrighted by +the Free Software Foundation, write to the Free Software Foundation; +we sometimes make exceptions for this. Our decision will be guided by +the two goals of preserving the free status of all derivatives of our +free software and of promoting the sharing and reuse of software +generally. + +**NO WARRANTY** + +**11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +**12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + +### END OF TERMS AND CONDITIONS + +### How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + +Also add information on how to contact you by electronic and paper +mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details + type `show w'. This is free software, and you are welcome + to redistribute it under certain conditions; type `show c' + for details. + +The hypothetical commands \`show w' and \`show c' should show the +appropriate parts of the General Public License. Of course, the +commands you use may be called something other than \`show w' and +\`show c'; they could even be mouse-clicks or menu items--whatever +suits your program. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the program, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright + interest in the program `Gnomovision' + (which makes passes at compilers) written + by James Hacker. + + signature of Ty Coon, 1 April 1989 + Ty Coon, President of Vice + diff --git a/README.md b/README.md index 27de79f..b248e34 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,37 @@ # beowulf -LISP 1.5 is to all Lisp dialects as Beowulf is to Emglish literature. +## Þý liste cræfte spræc + +LISP 1.5 is to all Lisp dialects as Beowulf is to English literature. + +![Beowulf logo](https://simon-brooke.github.io/beowulf/docs/img/beowulf_logo_med.png) + +## Contents + * [What this is](#what-this-is) + + [Status](#status) + + [BUT WHY?!!?!](#but-why-----) + + [Project Target](#project-target) + + [Invoking](#invoking) + + [Building and Invoking](#building-and-invoking) + + [Reader macros](#reader-macros) + + [Functions and symbols implemented](#functions-and-symbols-implemented) + + [Architectural plan](#architectural-plan) + - [resources/lisp1.5.lsp](#resources-lisp15lsp) + - [beowulf/boostrap.clj](#beowulf-boostrapclj) + - [beowulf/host.clj](#beowulf-hostclj) + - [beowulf/read.clj](#beowulf-readclj) + + [Commentary](#commentary) + * [Installation](#installation) + + [Input/output](#input-output) + - [SYSOUT](#sysout) + - [SYSIN](#sysin) + * [Learning Lisp 1.5](#learning-lisp-15) + * [Other Lisp 1.5 resources](#other-lisp-15-resources) + + [Other implmentations](#other-implementations) + + [History resources](#history-resources) + * [License](#license) + +Table of contents generated with markdown-toc ## What this is @@ -9,24 +40,44 @@ objective is to build a complete and accurate implementation of Lisp 1.5 as described in the manual, with, in so far as is possible, exactly the same bahaviour - except as documented below. +### BUT WHY?!!?! + +Because. + +Because Lisp is the only computer language worth learning, and if a thing +is worth learning, it's worth learning properly; which means going back to +the beginning and trying to understand that. + +Because there is, so far as I know, no working implementation of Lisp 1.5 +for modern machines. + +Because I'm barking mad, and this is therapy. + ### Status -Boots to REPL, but few functions yet available. +Working Lisp interpreter, but some key features not yet implemented. * [Project website](https://simon-brooke.github.io/beowulf/). * [Source code documentation](https://simon-brooke.github.io/beowulf/docs/codox/index.html). -* [Test Coverage Report](https://simon-brooke.github.io/beowulf/docs/cloverage/index.html) +### Project Target -### Building and Invoking +The project target is to be able to run the [Wang algorithm for the propositional calculus](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf#page=52) given in chapter 8 of the *Lisp 1.5 Programmer's Manual*. When that runs, the project is as far as I am concerned feature complete. I may keep tinkering with it after that and I'll certainly accept pull requests which are in the spirit of the project (i.e. making Beowulf more usable, and/or implementing parts of Lisp 1.5 which I have not implemented), but this isn't intended to be a new language for doing real work; it's an +educational and archaeological project, not serious engineering. -Build with +Some `readline`-like functionality would be really useful, but my attempt to +integrate [JLine](https://github.com/jline/jline3) has not (yet) been successful. - lein uberjar +An in-core structure editor would be an extremely nice thing, and I may well +implement one. + +You are of course welcome to fork the project and do whatever you like with it! + +### Invoking Invoke with - java -jar target/uberjar/beowulf-0.2.1-SNAPSHOT-standalone.jar --help + java -jar target/uberjar/beowulf-0.3.0-SNAPSHOT-standalone.jar --help (Obviously, check your version number) @@ -35,71 +86,136 @@ Command line arguments as follows: ``` -h, --help Print this message -p PROMPT, --prompt PROMPT Set the REPL prompt to PROMPT - -r INITFILE, --read INITFILE Read Lisp functions from the file INITFILE - -s, --strict Strictly interpret the Lisp 1.5 language, without extensions. + -r INITFILE, --read SYSOUTFILE Read Lisp sysout from the file SYSOUTFILE + (defaults to `resources/lisp1.5.lsp`) + -s, --strict Strictly interpret the Lisp 1.5 language, + without extensions. ``` To end a session, type `STOP` at the command prompt. +### Building and Invoking + +Build with + + lein uberjar + + +### Reader macros + +Currently `SETQ` and `DEFUN` are implemented as reader macros, sort of. It would +now be possible to reimplement them as `FEXPRs` and so the reader macro functionality will probably go away. + ### Functions and symbols implemented -The following functions and symbols are implemented: +| Function | Type | Signature | Implementation | Documentation | +|--------------|----------------|------------------|----------------|----------------------| +| NIL | Lisp variable | ? | | see manual pages 22, 69 | +| T | Lisp variable | ? | | see manual pages 22, 69 | +| F | Lisp variable | ? | | see manual pages 22, 69 | +| ADD1 | Host lambda function | ? | | ? | +| AND | Host lambda function | ? | PREDICATE | `T` if and only if none of my `args` evaluate to either `F` or `NIL`, else `F`. In `beowulf.host` principally because I don't yet feel confident to define varargs functions in Lisp. | +| APPEND | Lisp lambda function | ? | | see manual pages 11, 61 | +| APPLY | Host lambda function | ? | | Apply this `function` to these `arguments` in this `environment` and return the result. For bootstrapping, at least, a version of APPLY written in Clojure. All args are assumed to be symbols or `beowulf.cons-cell/ConsCell` objects. See page 13 of the Lisp 1.5 Programmers Manual. | +| ASSOC | Lisp lambda function, Host lambda function | ? | ? | If a is an association list such as the one formed by PAIRLIS in the above example, then assoc will produce the first pair whose first term is x. Thus it is a table searching function. All args are assumed to be `beowulf.cons-cell/ConsCell` objects. See page 12 of the Lisp 1.5 Programmers Manual. **NOTE THAT** this function is overridden by an implementation in Lisp, but is currently still present for bootstrapping. | +| ATOM | Host lambda function | ? | PREDICATE | Returns `T` if and only if the argument `x` is bound to an atom; else `F`. It is not clear to me from the documentation whether `(ATOM 7)` should return `T` or `F`. I'm going to assume `T`. | +| CAR | Host lambda function | ? | | Return the item indicated by the first pointer of a pair. NIL is treated specially: the CAR of NIL is NIL. | +| CAAAAR | Lisp lambda function | ? | ? | ? | +| CAAADR | Lisp lambda function | ? | ? | ? | +| CAAAR | Lisp lambda function | ? | ? | ? | +| CAADAR | Lisp lambda function | ? | ? | ? | +| CAADDR | Lisp lambda function | ? | ? | ? | +| CAADR | Lisp lambda function | ? | ? | ? | +| CAAR | Lisp lambda function | ? | ? | ? | +| CADAAR | Lisp lambda function | ? | ? | ? | +| CADADR | Lisp lambda function | ? | ? | ? | +| CADAR | Lisp lambda function | ? | ? | ? | +| CADDAR | Lisp lambda function | ? | ? | ? | +| CADDDR | Lisp lambda function | ? | ? | ? | +| CADDR | Lisp lambda function | ? | ? | ? | +| CADR | Lisp lambda function | ? | ? | ? | +| CDAAAR | Lisp lambda function | ? | ? | ? | +| CDAADR | Lisp lambda function | ? | ? | ? | +| CDAAR | Lisp lambda function | ? | ? | ? | +| CDADAR | Lisp lambda function | ? | ? | ? | +| CDADDR | Lisp lambda function | ? | ? | ? | +| CDADR | Lisp lambda function | ? | ? | ? | +| CDAR | Lisp lambda function | ? | ? | ? | +| CDDAAR | Lisp lambda function | ? | ? | ? | +| CDDADR | Lisp lambda function | ? | ? | ? | +| CDDAR | Lisp lambda function | ? | ? | ? | +| CDDDAR | Lisp lambda function | ? | ? | ? | +| CDDDDR | Lisp lambda function | ? | ? | ? | +| CDDDR | Lisp lambda function | ? | ? | ? | +| CDDR | Lisp lambda function | ? | ? | ? | +| CDR | Host lambda function | ? | | Return the item indicated by the second pointer of a pair. NIL is treated specially: the CDR of NIL is NIL. | +| CONS | Host lambda function | ? | | Construct a new instance of cons cell with this `car` and `cdr`. | +| CONSP | Host lambda function | ? | ? | Return `T` if object `o` is a cons cell, else `F`. **NOTE THAT** this is an extension function, not available in strct mode. I believe that Lisp 1.5 did not have any mechanism for testing whether an argument was, or was not, a cons cell. | +| COPY | Lisp lambda function | ? | | see manual pages 62 | +| DEFINE | Host lambda function | ? | PSEUDO-FUNCTION | Bootstrap-only version of `DEFINE` which, post boostrap, can be overwritten in LISP. The single argument to `DEFINE` should be an association list of symbols to lambda functions. See page 58 of the manual. | +| DIFFERENCE | Host lambda function | ? | | ? | +| DIVIDE | Lisp lambda function | ? | | see manual pages 26, 64 | +| DOC | Host lambda function | ? | ? | Open the page for this `symbol` in the Lisp 1.5 manual, if known, in the default web browser. **NOTE THAT** this is an extension function, not available in strct mode. | +| EFFACE | Lisp lambda function | ? | PSEUDO-FUNCTION | see manual pages 63 | +| ERROR | Host lambda function | ? | PSEUDO-FUNCTION | Throw an error | +| EQ | Host lambda function | ? | PREDICATE | Returns `T` if and only if both `x` and `y` are bound to the same atom, else `NIL`. | +| EQUAL | Host lambda function | ? | PREDICATE | This is a predicate that is true if its two arguments are identical S-expressions, and false if they are different. (The elementary predicate `EQ` is defined only for atomic arguments.) The definition of `EQUAL` is an example of a conditional expression inside a conditional expression. NOTE: returns `F` on failure, not `NIL` | +| EVAL | Host lambda function | ? | | Evaluate this `expr` and return the result. If `environment` is not passed, it defaults to the current value of the global object list. The `depth` argument is part of the tracing system and should not be set by user code. All args are assumed to be numbers, symbols or `beowulf.cons-cell/ConsCell` objects. However, if called with just a single arg, `expr`, I'll assume it's being called from the Clojure REPL and will coerce the `expr` to `ConsCell`. | +| FACTORIAL | Lisp lambda function | ? | ? | ? | +| FIXP | Host lambda function | ? | PREDICATE | ? | +| GENSYM | Host lambda function | ? | | Generate a unique symbol. | +| GET | Host lambda function | ? | | From the manual: '`get` is somewhat like `prop`; however its value is car of the rest of the list if the `indicator` is found, and NIL otherwise.' It's clear that `GET` is expected to be defined in terms of `PROP`, but we can't implement `PROP` here because we lack `EVAL`; and we can't have `EVAL` here because both it and `APPLY` depends on `GET`. OK, It's worse than that: the statement of the definition of `GET` (and of) `PROP` on page 59 says that the first argument to each must be a list; But the in the definition of `ASSOC` on page 70, when `GET` is called its first argument is always an atom. Since it's `ASSOC` and `EVAL` which I need to make work, I'm going to assume that page 59 is wrong. | +| GREATERP | Host lambda function | ? | PREDICATE | ? | +| INTEROP | Host lambda function | ? | ? | ? | +| INTERSECTION | Lisp lambda function | ? | ? | ? | +| LENGTH | Lisp lambda function | ? | | see manual pages 62 | +| LESSP | Host lambda function | ? | PREDICATE | ? | +| MAPLIST | Lisp lambda function | ? | FUNCTIONAL | see manual pages 20, 21, 63 | +| MEMBER | Lisp lambda function | ? | PREDICATE | see manual pages 11, 62 | +| MINUSP | Lisp lambda function | ? | PREDICATE | see manual pages 26, 64 | +| NOT | Lisp lambda function | ? | PREDICATE | see manual pages 21, 23, 58 | +| NULL | Lisp lambda function | ? | PREDICATE | see manual pages 11, 57 | +| NUMBERP | Host lambda function | ? | PREDICATE | ? | +| OBLIST | Host lambda function | ? | | Return a list of the symbols currently bound on the object list. **NOTE THAT** in the Lisp 1.5 manual, footnote at the bottom of page 69, it implies that an argument can be passed but I'm not sure of the semantics of this. | +| ONEP | Lisp lambda function | ? | PREDICATE | see manual pages 26, 64 | +| OR | Host lambda function | ? | PREDICATE | `T` if and only if at least one of my `args` evaluates to something other than either `F` or `NIL`, else `F`. In `beowulf.host` principally because I don't yet feel confident to define varargs functions in Lisp. | +| PAIR | Lisp lambda function | ? | | see manual pages 60 | +| PAIRLIS | Lisp lambda function, Host lambda function | ? | ? | This function gives the list of pairs of corresponding elements of the lists `x` and `y`, and APPENDs this to the list `a`. The resultant list of pairs, which is like a table with two columns, is called an association list. Eessentially, it builds the environment on the stack, implementing shallow binding. All args are assumed to be `beowulf.cons-cell/ConsCell` objects. See page 12 of the Lisp 1.5 Programmers Manual. **NOTE THAT** this function is overridden by an implementation in Lisp, but is currently still present for bootstrapping. | +| PLUS | Host lambda function | ? | | ? | +| PRETTY | | ? | ? | ? | +| PRINT | | ? | PSEUDO-FUNCTION | see manual pages 65, 84 | +| PROG | Host nlambda function | ? | | The accursed `PROG` feature. See page 71 of the manual. Lisp 1.5 introduced `PROG`, and most Lisps have been stuck with it ever since. It introduces imperative programming into what should be a pure functional language, and consequently it's going to be a pig to implement. Broadly, `PROG` is a variadic pseudo function called as a `FEXPR` (or possibly an `FSUBR`, although I'm not presently sure that would even work.) The arguments, which are unevaluated, are a list of forms, the first of which is expected to be a list of symbols which will be treated as names of variables within the program, and the rest of which (the 'program body') are either lists or symbols. Lists are treated as Lisp expressions which may be evaulated in turn. Symbols are treated as targets for the `GO` statement. **GO:** A `GO` statement takes the form of `(GO target)`, where `target` should be one of the symbols which occur at top level among that particular invocation of `PROG`s arguments. A `GO` statement may occur at top level in a PROG, or in a clause of a `COND` statement in a `PROG`, but not in a function called from the `PROG` statement. When a `GO` statement is evaluated, execution should transfer immediately to the expression which is the argument list immediately following the symbol which is its target. If the target is not found, an error with the code `A6` should be thrown. **RETURN:** A `RETURN` statement takes the form `(RETURN value)`, where `value` is any value. Following the evaluation of a `RETURN` statement, the `PROG` should immediately exit without executing any further expressions, returning the value. **SET and SETQ:** In addition to the above, if a `SET` or `SETQ` expression is encountered in any expression within the `PROG` body, it should affect not the global object list but instead only the local variables of the program. **COND:** In **strict** mode, when in normal execution, a `COND` statement none of whose clauses match should not return `NIL` but should throw an error with the code `A3`... *except* that inside a `PROG` body, it should not do so. *sigh*. **Flow of control:** Apart from the exceptions specified above, expressions in the program body are evaluated sequentially. If execution reaches the end of the program body, `NIL` is returned. Got all that? Good. | +| PROP | Lisp lambda function | ? | FUNCTIONAL | see manual pages 59 | +| QUOTE | Lisp lambda function | ? | | see manual pages 10, 22, 71 | +| QUOTIENT | Host lambda function | ? | | I'm not certain from the documentation whether Lisp 1.5 `QUOTIENT` returned the integer part of the quotient, or a realnum representing the whole quotient. I am for now implementing the latter. | +| RANGE | Lisp lambda function | ? | ? | ? | +| READ | Host lambda function | ? | PSEUDO-FUNCTION | An implementation of a Lisp reader sufficient for bootstrapping; not necessarily the final Lisp reader. `input` should be either a string representation of a LISP expression, or else an input stream. A single form will be read. | +| REMAINDER | Host lambda function | ? | | ? | +| REPEAT | Lisp lambda function | ? | ? | ? | +| RPLACA | Host lambda function | ? | PSEUDO-FUNCTION | Replace the CAR pointer of this `cell` with this `value`. Dangerous, should really not exist, but does in Lisp 1.5 (and was important for some performance hacks in early Lisps) | +| RPLACD | Host lambda function | ? | PSEUDO-FUNCTION | Replace the CDR pointer of this `cell` with this `value`. Dangerous, should really not exist, but does in Lisp 1.5 (and was important for some performance hacks in early Lisps) | +| SEARCH | Lisp lambda function | ? | FUNCTIONAL | see manual pages 63 | +| SET | Host lambda function | ? | PSEUDO-FUNCTION | Implementation of SET in Clojure. Add to the `oblist` a binding of the value of `var` to the value of `val`. NOTE WELL: this is not SETQ! | +| SUB1 | Lisp lambda function, Host lambda function | ? | | ? | +| SUB2 | Lisp lambda function | ? | ? | ? | +| SUBLIS | Lisp lambda function | ? | | see manual pages 12, 61 | +| SUBST | Lisp lambda function | ? | | see manual pages 11, 61 | +| SYSIN | Host lambda function | ? | ? | Read the contents of the file at this `filename` into the object list. If the file is not a valid Beowulf sysout file, this will probably corrupt the system, you have been warned. File paths will be considered relative to the filepath set when starting Lisp. It is intended that sysout files can be read both from resources within the jar file, and from the file system. If a named file exists in both the file system and the resources, the file system will be preferred. **NOTE THAT** if the provided `filename` does not end with `.lsp` (which, if you're writing it from the Lisp REPL, it won't), the extension `.lsp` will be appended. **NOTE THAT** this is an extension function, not available in strct mode. | +| SYSOUT | Host lambda function | ? | ? | Dump the current content of the object list to file. If no `filepath` is specified, a file name will be constructed of the symbol `Sysout` and the current date. File paths will be considered relative to the filepath set when starting Lisp. **NOTE THAT** this is an extension function, not available in strct mode. | +| TERPRI | | ? | PSEUDO-FUNCTION | see manual pages 65, 84 | +| TIMES | Host lambda function | ? | | ? | +| TRACE | Host lambda function | ? | PSEUDO-FUNCTION | Add this `s` to the set of symbols currently being traced. If `s` is not a symbol or sequence of symbols, does nothing. | +| UNION | Lisp lambda function | ? | ? | ? | +| UNTRACE | Host lambda function | ? | PSEUDO-FUNCTION | Remove this `s` from the set of symbols currently being traced. If `s` is not a symbol or sequence of symbols, does nothing. | +| ZEROP | Lisp lambda function | ? | PREDICATE | see manual pages 26, 64 | -| Symbol | Type | Signature | Documentation | -|--------|------|-----------|---------------| -| NIL | ? | null | ? | -| T | ? | null | ? | -| F | ? | null | ? | -| ADD1 | Host function | ([x]) | ? | -| AND | Host function | ([& args]) | `T` if and only if none of my `args` evaluate to either `F` or `NIL`, else `F`. In `beowulf.host` principally because I don't yet feel confident to define varargs functions in Lisp. | -| APPEND | Host function | ([x y]) | Append the the elements of `y` to the elements of `x`. All args are assumed to be `beowulf.cons-cell/ConsCell` objects. See page 11 of the Lisp 1.5 Programmers Manual. | -| APPLY | Host function | ([function args environment depth]) | Apply this `function` to these `arguments` in this `environment` and return the result. For bootstrapping, at least, a version of APPLY written in Clojure. All args are assumed to be symbols or `beowulf.cons-cell/ConsCell` objects. See page 13 of the Lisp 1.5 Programmers Manual. | -| ATOM | Host function | ([x]) | Returns `T` if and only if the argument `x` is bound to an atom; else `F`. It is not clear to me from the documentation whether `(ATOM 7)` should return `T` or `F`. I'm going to assume `T`. | -| CAR | ? | null | ? | -| CDR | ? | null | ? | -| CONS | ? | null | ? | -| COPY | Lisp function | (X) | ? | -| DEFINE | Host function | ([args]) | Bootstrap-only version of `DEFINE` which, post boostrap, can be overwritten in LISP. The single argument to `DEFINE` should be an assoc list which should be nconc'ed onto the front of the oblist. Broadly, (SETQ OBLIST (NCONC ARG1 OBLIST)) | -| DIFFERENCE | Host function | ([x y]) | ? | -| DIVIDE | Lisp function | (X Y) | ? | -| ERROR | Host function | ([& args]) | Throw an error | -| EQ | Host function | ([x y]) | Returns `T` if and only if both `x` and `y` are bound to the same atom, else `NIL`. | -| EQUAL | Host function | ([x y]) | This is a predicate that is true if its two arguments are identical S-expressions, and false if they are different. (The elementary predicate `EQ` is defined only for atomic arguments.) The definition of `EQUAL` is an example of a conditional expression inside a conditional expression. NOTE: returns `F` on failure, not `NIL` | -| EVAL | Host function | ([expr] [expr env depth]) | Evaluate this `expr` and return the result. If `environment` is not passed, it defaults to the current value of the global object list. The `depth` argument is part of the tracing system and should not be set by user code. All args are assumed to be numbers, symbols or `beowulf.cons-cell/ConsCell` objects. | -| FIXP | Host function | ([x]) | ? | -| GENSYM | Host function | ([]) | Generate a unique symbol. | -| GET | Lisp function | (X Y) | ? | -| GREATERP | Host function | ([x y]) | ? | -| INTEROP | Host function | ([fn-symbol args]) | Clojure (or other host environment) interoperation API. `fn-symbol` is expected to be either 1. a symbol bound in the host environment to a function; or 2. a sequence (list) of symbols forming a qualified path name bound to a function. Lower case characters cannot normally be represented in Lisp 1.5, so both the upper case and lower case variants of `fn-symbol` will be tried. If the function you're looking for has a mixed case name, that is not currently accessible. `args` is expected to be a Lisp 1.5 list of arguments to be passed to that function. Return value must be something acceptable to Lisp 1.5, so either a symbol, a number, or a Lisp 1.5 list. If `fn-symbol` is not found (even when cast to lower case), or is not a function, or the value returned cannot be represented in Lisp 1.5, an exception is thrown with `:cause` bound to `:interop` and `:detail` set to a value representing the actual problem. | -| INTERSECTION | Lisp function | (X Y) | ? | -| LENGTH | Lisp function | (L) | ? | -| LESSP | Host function | ([x y]) | ? | -| MEMBER | Lisp function | (A X) | ? | -| MINUSP | Lisp function | (X) | ? | -| NULL | Lisp function | (X) | ? | -| NUMBERP | Host function | ([x]) | ? | -| OBLIST | Host function | ([]) | Return a list of the symbols currently bound on the object list. **NOTE THAT** in the Lisp 1.5 manual, footnote at the bottom of page 69, it implies that an argument can be passed but I'm not sure of the semantics of this. | -| ONEP | Lisp function | (X) | ? | -| PAIR | Lisp function | (X Y) | ? | -| PLUS | Host function | ([& args]) | ? | -| PRETTY | ? | null | ? | -| PRINT | ? | null | ? | -| PROP | Lisp function | (X Y U) | ? | -| QUOTIENT | Host function | ([x y]) | I'm not certain from the documentation whether Lisp 1.5 `QUOTIENT` returned the integer part of the quotient, or a realnum representing the whole quotient. I am for now implementing the latter. | -| READ | Host function | ([] [input]) | An implementation of a Lisp reader sufficient for bootstrapping; not necessarily the final Lisp reader. `input` should be either a string representation of a LISP expression, or else an input stream. A single form will be read. | -| REMAINDER | Host function | ([x y]) | ? | -| REPEAT | Lisp function | (N X) | ? | -| RPLACA | Host function | ([cell value]) | Replace the CAR pointer of this `cell` with this `value`. Dangerous, should really not exist, but does in Lisp 1.5 (and was important for some performance hacks in early Lisps) | -| RPLACD | Host function | ([cell value]) | Replace the CDR pointer of this `cell` with this `value`. Dangerous, should really not exist, but does in Lisp 1.5 (and was important for some performance hacks in early Lisps) | -| SET | Host function | ([symbol val]) | Implementation of SET in Clojure. Add to the `oblist` a binding of the value of `var` to the value of `val`. NOTE WELL: this is not SETQ! | -| SUB1 | Lisp function | (N) | ? | -| SYSIN | Host function | ([filename]) | Read the contents of the file at this `filename` into the object list. If the file is not a valid Beowulf sysout file, this will probably corrupt the system, you have been warned. File paths will be considered relative to the filepath set when starting Lisp. It is intended that sysout files can be read both from resources within the jar file, and from the file system. If a named file exists in both the file system and the resources, the file system will be preferred. **NOTE THAT** if the provided `filename` does not end with `.lsp` (which, if you're writing it from the Lisp REPL, it won't), the extension `.lsp` will be appended. | -| SYSOUT | Host function | ([] [filepath]) | Dump the current content of the object list to file. If no `filepath` is specified, a file name will be constructed of the symbol `Sysout` and the current date. File paths will be considered relative to the filepath set when starting Lisp. | -| TERPRI | ? | null | ? | -| TIMES | Host function | ([& args]) | ? | -| TRACE | ? | null | ? | -| UNTRACE | ? | null | ? | -| ZEROP | Lisp function | (N) | ? | +Functions described as 'Lisp function' above are defined in the default +sysout file, `resources/lisp1.5.lsp`, which will be loaded by default unless +you specify another initfile on the command line. + +Functions described as 'Host function' are implemented in Clojure, but if you're +brave you can redefine them in Lisp and the Lisp definitions will take precedence +over the Clojure implementations. ### Architectural plan @@ -153,19 +269,6 @@ Intended deviations from the behaviour of the real Lisp reader are as follows: a comment, as most modern Lisps do; but I do not believe Lisp 1.5 had this feature. -### BUT WHY?!!?! - -Because. - -Because Lisp is the only computer language worth learning, and if a thing -is worth learning, it's worth learning properly; which means going back to -the beginning and trying to understand that. - -Because there is, so far as I know, no working implementation of Lisp 1.5 -for modern machines. - -Because I'm barking mad, and this is therapy. - ### Commentary What's surprised me in working on this is how much more polished Lisp 1.5 is @@ -183,11 +286,19 @@ but this is software which is almost sixty years old). ## Installation -At present, clone the source and build it using +Download the latest [release 'uberjar'](https://github.com/simon-brooke/beowulf/releases) and run it using: -`lein uberjar`. +```bash + java -jar +``` -You will require to have [Leiningen](https://leiningen.org/) installed. +Or clone the source and build it using: + +```bash + lein uberjar` +``` + +To build it you will require to have [Leiningen](https://leiningen.org/) installed. ### Input/output @@ -220,7 +331,14 @@ processors, but I failed to find the Lisp source of Lisp functions as a text file, which is why `resources/lisp1.5.lsp` is largely copytyped and reconstructed from the manual. -I'm not at this time aware of any other working Lisp 1.5 implementations. +### Other implementations + +There's an online (browser native) Lisp 1.5 implementation [here](https://pages.zick.run/ichigo/) (source code [here](https://github.com/zick/IchigoLisp)). It +even has a working compiler! + +### History resources + +I'm compiling a [list of links to historical documents on Lisp 1.5](https://simon-brooke.github.io/beowulf/docs/further_reading.html). ## License diff --git a/doc/further_reading.md b/doc/further_reading.md new file mode 100644 index 0000000..3dfd32c --- /dev/null +++ b/doc/further_reading.md @@ -0,0 +1,16 @@ +# Further Reading + +1. [CODING for the MIT-IBM 704 COMPUTER, October 1957](http://bitsavers.org/pdf/mit/computer_center/Coding_for_the_MIT-IBM_704_Computer_Oct57.pdf) + This paper is not about Lisp. But it is about the particular individual computer on which Lisp was first implemented, and it is written in part by members of the Lisp team. I have found it useful in understanding the software environment in which, and the constraints under which, Lisp was written. +2. [MIT AI Memo 1, John McCarthy, September 1958](https://www.softwarepreservation.org/projects/LISP/MIT/AIM-001.pdf) + This is, as far as I can find, the earliest specification document of the Lisp project. +3. [Lisp 1 Programmer's Manual, Phyllis Fox, March 1960](https://bitsavers.org/pdf/mit/rle_lisp/LISP_I_Programmers_Manual_Mar60.pdf) +4. [Lisp 1.5 Programmer's Manual, Michael I. Levin, August 1962](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf#page=81) + This book is essential reading: it documents in some detail the first fully realised Lisp language system. +5. [Early LISP History (1956 - 1959), Herbert Stoyan, August 1984](https://dl.acm.org/doi/pdf/10.1145/800055.802047#page=3) + + +6. [The Roots of Lisp, Paul Graham, 2001](http://www.paulgraham.com/rootsoflisp.html) +6. [The Revenge of the Nerds, Paul Graham, 2002](http://www.paulgraham.com/icad.html) + This is mainly about why to use Lisp as a language for modern commercial software, but has useful insights into where it comes from. + > So the short explanation of why this 1950s language is not obsolete is that it was not technology but math, and math doesn't get stale. \ No newline at end of file diff --git a/doc/lisp1.5.md b/doc/lisp1.5.md new file mode 100644 index 0000000..6042cc8 --- /dev/null +++ b/doc/lisp1.5.md @@ -0,0 +1,5114 @@ + +# LISP 1.5 Programmer's Manual + +**The Computation Center and Research Laboratory of Electronics** + +**Massachusetts Institute of Technology** + +> [John McCarthy](https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)) +> [Paul W. Abrahams](https://mitpress.mit.edu/author/paul-w-abrahams-31449/) +> [Daniel J. Edwards](https://www.chessprogramming.org/Daniel_Edwards) +> [Timothy P. Hart](https://www.chessprogramming.org/Timothy_Hart) + +> The M. I.T. Press +> Massachusetts Institute of Technology +> Cambridge, Massachusetts + +The Research Laboratory af Electronics is an interdepartmental laboratory in which faculty members and graduate students from numerous academic departments conduct research. + +The research reported in this document was made possible in part by support extended the Massachusetts Institute of Technology, Research Laboratory of Electronics, jointly by the U.S. Army, the U.S. Navy (Office of Naval Research), and the U.S. Air Force (Office of Scientific Research) under Contract DA36-039-sc-78108, Department of the Army Task 3-99-25-001-08; and in part by Contract DA-SIG-36-039-61-G14; additional support was received from the National Science Foundation (Grant G-16526) and the National Institutes of Health (Grant MH-04737-02). + +Reproduction in whole or in part is permitted for any purpose of the United States Government. + +SECOND EDITION Fifteenth printing, 1985 + +ISBN 0 262 130 1 1 4 (paperback) + +----- + +#### Note regarding this Markdown document + +This Markdown version of the manual was created by me, [Simon Brooke](mailto:simon@journeyman.cc), by passing the PDF version found at [Software Preservation](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf) through a [PDF to +Markdown processor](https://pdf2md.morethan.io/), and hand-editing the resulting document. + +**This document is not authorised by the copyright holders.** It was made for the purposes of study, only. + +Generally I have tried to keep the text unaltered. Some minor headings, especially of examples, have been deliberately changed in order to aid navigation, and some apparent typographic errors have been corrected. *I have also added spaces between syntactic elements in M-expression examples to aid legibility.* Page numbers are taken from the original. Notes which I have added during editing are *NOTE: given in italics, like this*. + +----- + +## PREFACE + +The over-all design of the LISP Programming System is the work of John McCarthy and is based on his paper "[Recursive Functions of Symbolic Expressions and Their Computation by Machine](http://www-formal.stanford.edu/jmc/recursive/recursive.html)" which was published in Communications of the ACM, April 1960. + +This manual was written by Michael I. Levin. + +The interpreter was programmed by [Stephen B. Russell](https://en.wikipedia.org/wiki/Steve_Russell_(computer_scientist)) and Daniel J. Edwards. The print and read programs were written by John McCarthy, Klim Maling, Daniel J. Edwards, and Paul W. Abrahams. + +The garbage collector and arithmetic features Were written by Daniel J. Edwards. The compiler and assembler were written by Timothy P. Hart and Michael I. Levin. An earlier compiler was written by Robert Brayton. + +The "LISP 1 Programmer's Manual" March 1, 1960, was written by [Phyllis A. Fox](https://en.wikipedia.org/wiki/Phyllis_Fox). Additional programs and suggestions were contributed by the following members of the Artificial Intelligence Group of the Research Laboratory of Electronics: Marvin L. Minsky, Bertram Raphael, Louis Hodes, David M. R. Park, David C. Luckham, Daniel G. Bobrow, James R. Slagle, and Nathaniel Rochester. + +August 17, 1962 + +## TABLE OF CONTENTS + +1. THE LISP LANGUAGE + 1. Symbolic Expressions + 2. Elementary Functions + 3. List Notation + 4. The LISP Meta-language + 5. Syntactic Summary + 6. A Universal LISP Function +2. THE LISP INTERPRETER SYSTEM + 1. Variables + 2. Constants + 3. Functions + 4. Machine Language Functions + 5. Special Forms + 6. Programming for the Interpreter +3. EXTENSION OF THE LISP LANGUAGE + 1. Functional Arguments + 2. Logical Connectives + 3. Predicates and Truth in LISP +4. ARITHMETIC IN LISP + 1. Reading and Printing Numbers + 2. Arithmetic Functions and Predicates + 3. Programming with Arithmetic + 4. The Array Feature +5. THE PROGRAM FEATURE +6. RUNNING THE LISP SYSTEM + 1. Preparing a Card Deck + 2. Tracing + 3. Error Diagnostics + 4. The cons Counter and errorset +7. LIST STRUCTURES + 1. Representation of List Structure + 2. Construction of List Structure + 3. Property Lists + 4. List Structure Operators + 5. The Free-Storage List and the Garbage Collector +8. A COMPLETE LISP PROGRAM - THE WANG ALGORITHM FOR THE PROPOSITIONAL CALCULUS + +## APPENDICES + +A. Functions and Constants in the LISP System +B. The LISP Interpreter +C. The LISP Assembly Program (LAP) +D. The LISP Compiler +E. OVERLORD - The Monitor +F. LISP Input and Output +G. Memory Allocation and the Garbage Collector +H. Recursion and the Push-Down List +I. LISP for SHARE Distribution + +* INDEX TO FUNCTION DESCRIPTIONS +* GLOSSARY + +page 1 + +## I. THE LISP LANGUAGE + +The LISP language is designed primarily for symbolic data processing. It has been used for symbolic calculations in differential and integral calculus, electrical circuit theory, mathematical logic, game playing, and other fields of artificial intelligence. + +LISP is a formal mathematical language. It is therefore possible to give a concise yet complete description of it. Such is the purpose of this first section of the manual. Other sections will describe ways of using LISP to advantage and will explain extensions of the language which make it a convenient programming system. + +LISP differs from most programming languages in three important ways. The first way is in the nature of the data. In the LISP language, all data are in the form of symbolic expressions usually referred to as S-expressions. S-expressions are of indefinite length and have a branching tree type of structure, so that significant sub-expressions can be readily isolated. In the LISP programming system, the bulk of available memory is used for storing S-expressions in the form of list structures. This type of memory organization frees the programmer from the necessity of allocating storage for the different sections of his program. + +The second important part of the LISP language is the source language itself which specifies in what way the S-expressions are to be processed. This consists of recursive functions of S-expressions. Since the notation for the writing of recursive functions of S-expressions is itself outside the S-expression notation, it will be called the meta language. These expressions will therefore be called M-expressions. + +Third, LISP can interpret and execute programs written in the form of S-expressions. Thus, like machine language, and unlike most other higher level languages, it can be used to generate programs for further execution. + +### 1.1 Symbolic Expressions + +The most elementary type of S-expression is the atomic symbol. + +**Definition**: An atomic symbol is a string of no more than thirty numerals and capital letters; the first character must be a letter. + +#### Examples - atomic symbols + +* A +* APPLE +* PART +* EXTRALONGSTRINGOFLETTERS +* A4B66XYZ + +These symbols are called atomic because they are taken as a whole and are not capable of being split within LISP into individual characters, Thus A, B, and AB have no relation to each other except in so far as they are three distinct atomic symbols. + +All S-expressions are built out of atomic symbols and the punctuation marks + +page 2 + +`(` `)` and `.`. The basic operation for forming S-expressions is to combine two of them to produce a larger one. From the two atomic symbols A1 and A2, one can form the S-expression `(A1 . A2)`. + +**Definition**: An S-expression is either an atomic symbol or it is composed of these elements in the following order: a left parenthesis, an S-expression, a dot, an S-expression, and a right parenthesis. + +Notice that this definition is recursive. + +#### Examples - S-expressions + +* ATOM +* (A B) +* (A . (B C)) +* ((A1 . A2) . B) +* ((U V) . (X . Y)) +* ((U VI . (X (Y Z))) + +### 1.2 Elementary Functions + +We shall introduce some elementary functions of S-expressions. To distinguish the functions from the S-expressions themselves, we shall write function names in lower case letters, since atomic symbols consist of only upper case letters. Furthermore, the arguments of functions will be grouped in square brackets rather than parentheses. As a separator or punctuation mark we shall use the semicolon. + +The first function that we shall introduce is the function `cons`. It has two arguments and is in fact the function that is used to build S-expressions from smaller S-expressions. + +#### Examples - the cons function + +``` +cons[A; B]=(A . B) +cons[(A . B); C] = ((A . B) . C) +cons[cons[A; B]; C] = ((A . B) . C) +``` + +The last example is an instance of composition of functions. It is possible to build any S-expression from its atomic components by compositions of the function cons. The next pair of functions do just the opposite of cons. They produce the subexpressions of a given expression. + +The function `car` has one argument. Its value is the first part of its composite argument. `car` of an atomic symbol is undefined. + +*Note that where this says 'car of an atomic symbol is undefined', it seems to mean it literally. There seems to have been no mechanism for distinguishing cons cells from other items in memory, so that the car of, for example, a decimal number could be taken, although the result + +#### Examples - the car function + +``` +car[(A . B)] = A +car[(A . (B1 . B2))] = A +car[((A1 . A2) . B)] = (A1 . A2) +car[A] is undefined +``` + +page 3 + +The function `cdr` has one argument. Its value is the second part of its composite +argument. `cdr` is also undefined if its argument is atomic. + +#### Examples - the cdr function + +``` +cdr[(A . B)] = B +cdr[(A . (B1 . B2))] = (B1 . B2) +cdr[((A1 . A2) . B)] = B +cdr[A] is undefined +car[cdr[(A . (B1 . B2))]] = B1 +car[cdr[(A . B)]] is undefined +car[cons[A; B]] = A +``` + +Given any S-expression, it is possible to produce any subexpression of it by a +suitable composition of `car`s and `cdr`s. If `x` and `y` represent any two S-expressions, +the following identities are true: + +``` +car[ cons[x; y]] = x +cdr[ cons[x; y]] = y +``` + +The following identity is also true for any S-expression x such that x is composite +(non-atomic): + +``` +cons[car[x]; cdr[x]] = x +``` + +The symbols `x` and `y` used in these identities are called variables. In LISP, variables are used to represent S-expressions. In choosing names for variables and functions, we shall use the same type of character strings that are used in forming atomic +symbols, except that we shall use lower case letters. + +A function whose value is either `true` or `false` is called a predicate. In LISP, the +values `true` and `false` are represented by the atomic symbols `T` and `F`, respectively. +A LISP predicate is therefore a function whose value is either `T` or `F`. + +The predicate `eq` is a test for equality on atomic symbols. It is undefined for +non-atomic arguments. *NOTE: this differs from the statement given on [page 57](#page57).* + +#### Examples - eq + +``` +eq[A; A] = T +eq[A; B] = F +eq[A; (A . B)] is undefined +eq[(A . B);(A . B)] is undefined +``` + +The predicate `atom` is true if its argument is an atomic symbol, and false if its +argument is composite. + +#### Examples - atom + +``` +atom[EXTRALONGSTRINGOFLETTERS] = T +atom[(u . v)] = F +atom[car[(u . v)]] = T +``` + +page 4 + +### 1.3 List Notation + +The S-expressions that have been used heretofore have been written in dot notation. It is usually more convenient to be able to write lists of expressions of indefinite length, such as `(A B C D E)`. + +Any S-expression can be expressed in terms of the dot notation. However, LISP has an alternative form of S-expression called the list notation. The list `(m1 m2... mn)` can be defined in terms of dot notation. It is identical to `(m1 . (m2 . (... . (mn . NIL)... )))`. + +The atomic symbol NIL serves as a terminator for lists. The null list `()` is identical to `NIL`. Lists may have sublists. The dot notation and the list notation may be used in the same S-expression, + +Historically, the separator for elements of lists was the comma `(,)`; however, the blank is now generally used. The two are entirely equivalent in LISP. `(A, B, C)` is identical to `(A B C)`. + +#### Examples - list notation + +```lisp +(A B C) = (A . (B . (C . NIL))) +((A B) C) = ((A . (B . NIL)) . (C . NIL)) +(A B (C D)) = (A . (B . ((C . (D . NIL)). NIL))) +(A) = (A . NIL) +((A))=((A . NIL) . NIL) +(A (B . C)) = (A . ((B . C) . NIL)) +``` + +It Is important to become familiar with the results of elementary functions on S-expressions written in list notation. These can always be determined by translating into dot notation. + +#### Examples - list notation 2 + +``` +car[(A B C)] = A +cdr[(A B C)] = (B C) +cons[A; (B C)] = (A B C) +car[((A B) C)] = (A B) +cdr[(A)] = NIL +car[cdr[(A B C)]] = B +``` + +It is convenient to abbreviate multiple `car`s and `cdr`s. This is done by forming function names that begin with `c`, end with `r`, and have several `a`s and `d`s between them. + +#### Examples - composed accessor functions + +``` +cadr[(A B C)] = car[cdr[(A B C)]] = B +caddr[(A B C)] = C +cadadr[(A (B C) D)] = C +``` + +page 5 + +The last `a` or `d` in the name actually signifies the first operation in order to be +performed, since it is nearest to the argument. + +### 1.4 The LISP Meta-language + +We have introduced a type of data called S-expressions, and five elementary functions of S-expressions. We have also discussed the following features of the meta-language. + +1. Function names and variable names are like atomic symbols except that they use lower case letters. +2. The arguments of a function are bound by square brackets and separated from each other by semicolons. +3. Compositions of functions may be written by using nested sets of brackets. These rules allow one to write function definitions such as `third[x]=car[cdr[cdr[x]]]`. + +This function selects the third item on a list. For example, `third` is actually the same function as `caddr`. + +The class of functions that can be formed in this way is quite limited and not very interesting. A much larger class of functions can be defined by means of the conditional expression, a device for providing branches in function definitions. A conditional expression has the following form: + +> where each pi is an expression whose value may be truth or falsity, and each ei is +> any expression. The meaning of a conditional expression is: if p1 is true. then the +> value of e1 is the value of the entire expression. If p1 is false, then if p2 is true +> the value of e2 is the value of the entire expression. The pi are searched from left +> to right until the first true one is found. Then the corresponding ei is selected. If +> none of the pi are true, then the value of the entire expression is undefined. +> +> Each pi or ei can itself be either an S-expression, a function, a composition of +> functions or may itself be another conditional expression. + +#### Example - conditional expression + +`[eq[car[x]; A] -> cons[B; cdr[x]]; T -> x]` + +The atomic symbol `T` represents truth. The value of this expression is obtained +if one replaces `car` of `x` by B if it happens to be A, but leaving `x` unchanged if `car` of +it is not A. + +page 6 + +The main application of conditional expressions is in defining functions recursively. + +#### Example - recursive function + +`ff[x] = [atom[x] -> x; T -> ff[car[x]]]` + +This example defines the function `ff` which selects the first atomic symbol of any +given expression. This expression can be read: If `x` is an atomic symbol, then `x` +itself is the answer. Otherwise the function `ff` is to be applied to car of `x`. + +If `x` is atomic, then the first branch which is `x` will be selected. Otherwise, the +second branch `ff[car[x]]` will be selected, since `T` is always true. + +The definition of `ff` is recursive in that `ff` is actually defined in terms of itself. If +one keeps taking `car` of any S-expression, one will eventually produce an atomic symbol; therefore the process is always well defined. + +Some recursive functions may be well defined for certain arguments only, but infinitely recursive for certain other arguments. When such a function is interpreted in the LISP programming system, it will either use up all of the available memory, or loop until the program is halted artificially. + +We shall now work out the evaluation of `ff[((A. B). C)]`. First, we substitute the +arguments in place of the variable `x` in the definition and obtain +``` +ff[((A . B) . C)]=[atom[((A . B) . C)]->((A . B) . C); T->ff[car[((A . B) . C)]]] +``` +but `((A. B). C)` is not atomic, and so we have +``` += [T->ff [car[((A . B) . C)]] += ff[car[((A . B) . C)]] += ff[(A . B)] +``` +At this point, the definition of ff must be used recursively. Substituting `(A . B)` +for `x` gives +``` += [atom[(A . B)] -> (A . B); T -> ff[car[(A . B)]]] += [T -> ff[car[(A . B)]]] += ff[car[(A . B)]] += ff[A] += [atom[A] -> A; T -> ff[car[A]]] += A +``` + +The conditional expression is useful for defining numerical computations, as well as computations with S-expressions. The absolute value of a number can be defined by +``` +|x| = [x<0 -> -x; T -> x] +``` +The factorial of a non-negative integer can be defined by +``` +n! = [n=0 -> 1; T -> n.[n-l]!] +``` +This recursive definition does not terminate for negative arguments. A function that + +page 7 + +is defined only for certain arguments is called a partial function. + +The Euclidean algorithm for finding the greatest common divisor of two positive integers can be defined by using conditional expressions as follows: + +``` +gcd[x; y]=[x>y -> gcd[y; x]; + rem[y;x]=0 -> x] +``` + +`rem[u; v]` is the remainder when `u` is divided by `v`. + +A detailed discussion of the theory of functions defined recursively by conditional expressions is found in [A Basis for a Mathematical Theory of Computation](http://jmc.stanford.edu/articles/basis/basis.pdf) by J. McCarthy, Proceedings of the Western Joint Computer Conference, May 1961 (published by the Institute of Radio Engineers). + +It is usual for most mathematicians -- exclusive of those devoted to logic -- to use the word 'function' imprecisely, and to apply it to forms such as y2+x. Because we shall later compute with expressions that stand for functions, we need a notation that expresses the distinction between functions and forms. The notation that we shall use is the [lambda notation of Alonzo Church](https://compcalc.github.io/public/church/church_calculi_1941.pdf). + +Let `f`be an expression that stands for a function of two integer variables. It +should make sense to write `f[3; 4]` and to be able to determine the value of this expres- +sion. For example, `sum[3; 4] = 7`. The expression y2 + x does not meet this requirement. +It is not at all clear whether the value of y2 + x[3; 4] is 13 or 19. An expression such as +y2 + x will be called a form rather than a function. A form can be converted to a function by specifying the correspondence between the variables in the form and the arguments of the desired function. + +If ε is a form in the variables x1;... ;xn, then the expression λ[[x1;... ;xn]ε] represents the function of n variables obtained by substituting the n arguments in order for the variables x1;... ;xn, respectively. For example, the function λ[[x; y]; y2 + x] is a function of two variables, and λ[[x; y]; y2 + x][3; 4] =42 + 3 = 19. λ[[x; y]; y2 + x][4; 3] = 32 + 4 = 13. + +*TODO: detail formatting in the above paragraph is still slightly wrong.* + +The variables in a lambda expression are dummy or bound variables because systematically changing them does not alter the meaning of the expression. Thus λ[[u; v]; u2 + v] means the same thing as λ[[x; y]; y2 + x]. + +We shall sometimes use expressions in which a variable is not bound by a lambda. For example, in the function of two variables λ[[x; y]; xn + yn] the variable `n` is not bound. This is called a free variable. It may be regarded as a parameter. Unless `n` has been given a value before trying to compute with this function, the value of the function must be undefined. + +page 8 + +The lambda notation alone is inadequate for naming recursive functions. Not only must the variables be bound, but the name of the function must be bound, since it is used inside an expression to stand for the entire expression. The function `ff` was previously defined by the identity + +`ff[x] = [atom[x] -> x; T -> ff[car[x]]]` + +Using the lambda notation, we can write + +`ff =` λ`[x] = [atom[x] -> x; T -> ff[car[x]]]` + +The equality sign in these identities is actually not part of the LISP meta-language and is only a crutch until we develop the correct notation. The right side of the last equation cannot serve as an expression for the function `ff` because there is nothing to indicate that the occurrence of `ff` inside it stands for the function that is being defined. + +In order to be able to write expressions that bear their own name, we introduce +the label notation. If ε is an expression, and α is its name, we write label[α; ε]. + +The function `ff` can now be written without an equal sign: + +`label[ff =` λ`[[x]; [atom[x] -> x; T -> ff[car[x]]]]` + +In this expression, `x` is a bound variable, and `ff` is a bound function name. + +### 1.5 Syntactic Summary + +[This section is for completeness and may be skipped upon first reading.] + +All parts of the LISP language have now been explained. That which follows is a complete syntactic definition of the LISP language, together with semantic comments. The definition is given in [Backus notation](https://www.softwarepreservation.org/projects/ALGOL/paper/Backus-ICIP-1959.pdf) with the addition of three dots(...) to avoid naming unnecessary syntactic types. + +In Backus notation the symbols `::=`, `<`, `>`, and `|` are used. The rule + +```BNF + ::= | ( . ) +``` + +means that an S-expression is either an atomic symbol, or it is a left parenthesis followed by an S-expression followed by a dot followed by an S-expression followed by a right parenthesis. The vertical bar means "or" , and the angular brackets always enclose elements of the syntax that is being defined. + +#### The Data Language + +```BNF + ::= A|B|C| ... |Z + ::= 0|1|2| ... |9 + ::= + ::= | | +``` +Atomic symbols are the smallest entities in LISP. Their decomposition into characters has no significance. + +page 9 + +```BNF + ::= | + ( . ) | + ( ... ) +``` +When three dots are used in this manner, they mean that any number of the given type of symbol may occur, including none at all. According to this rule, `( )` is a valid S-expression. (It is equivalent to `NIL`. ) + +The dot notation is the fundamental notation of S-expressions, although the list notation is often more convenient. Any S-expression can be written in dot notation. + +#### The Meta-Language + +```BNF + ::= a|b|c| ... |z + ::= + ::= | | +``` +The names of functions and variables are fornied in the same manner as atomic symbols but with lower-case letters. + +```BNF +
::= | + | + [ ... ] | + [ -> ; ... ; -> ] + ::= + ::= + ::= + +``` + +A form is an expression that can be evaluated. A form that is merely a constant has that constant *[sic]* as its value. If a form is a variable, then the value of the form is the S-expression that is bound to that variable at the time when we evaluate the form, + +The third part of this rule states that we may write a function followed by a list of arguments separated by semicolons and enclosed in square brackets. The expressions for the arguments are themselves forms; this indicates that compositions of functions are permitted. + +The last part of this rule gives the format of the conditional expression. This is evaluated by evaluating the forms in the propositional position in order until one is found whose value is `T`. Then the form after the arrow is evaluated and gives the value of the entire expression. + +`::= |` λ`[;] | label[; ]` + +` ::= [; ... ; ]` + +A function can be simply a name. In this case its meaning must be previously understood. A function may be defined by using the lambda notation and establishing a correspondence between the arguments and the variables used in a form. If the function is recursive, it must be given a name by using a label. + +page 10 + +### 1.6 A Universal LISP Function +An interpreter or universal function is one that can compute the value of any given function applied to its arguments when given a description of that function. (Of course, if the function that is being interpreted has infinite recursion, the interpreter will recur infinitely also. ) + +We are now in a position to define the universal LISP function `evalquote[fn;args]`. When `evalquote` is given a function and a list of arguments for that function, it computes the value of the function applied to the arguments. + +LISP functions have S-expressions as arguments. In particular, the argument `fn` of the function `evalquote` must be an S-expression. Since we have been writing functions as M-expressions, it is necessary to translate them into S-expressions. + +The following rules define a method of translating functions written in the meta-language into S-expressions. + +1. If the function is represented by its name, it is translated by changing all of the letters to upper case, making it an atomic symbol. Thus `car` is translated to `CAR`. + +2. If the function uses the lambda notation, then the expression λ`[x1; ...; xn]`ε`]` is translated into (LAMBDA (X1... XN) ε*), where ε* is the translation of ε. +3. If the function begins with label, then the translation of label[α; ε] is (LABEL + α* ε*). + +Forms are translated as follows: + +1. A variable, like a function name, is translated by using uppercase letters. Thus the translation of `var1` is `VAR1`. + +2. The obvious translation of letting a constant translate into itself will not work. Since the translation `x` of is `X`, the translation of `X` must be something else to avoid ambiguity. The solution is to quote it. Thus `X` is translated into `(QUOTE X)`. + +3. The form `fn[arg`1`; ... ;arg`n`]` is translated into `(fn* arg`1* `... arg`n*`)`. + +4. The conditional expression [p1 -> e1; ... ; pn -> en] is translated into + + (COND (p1* e1*) ... (pn* en*)) + +#### Examples - translation, M-expressions to S-expressions + +| M-expressions | S -expressions | +| ---- | ---- | +| X | X | +| car | CAR | +| car[x] | (CAR X) | +| T | (QUOTE T) | +| ff[car[X]] | (FF (CAR X)) | +| [atom[x]-x; T-ff [car [x]]] | (COND ((ATOM X) X) ((QUOTE T) (FF (CAR X)))) | +| label[ff ;h[[x];[atom[x]-x; T-ff[car [X]]]] | (LABEL FF (LAMBDA (X) (COND ((ATOM X) X) ((QUOTE T) (FF (CAR X)))))) | + +Some useful functions for handling S-expressions are given below. Some of them + +page 11 + +are needed as auxiliary functions for `evalquote`. + +`equal[x;y]` + +This is a predicate that is true if its two arguments are identical S-expressions, +and is false if they are different. (The elementary predicate - eq is defined only for +atomic arguments. ) The definition of equal is an example of a conditional expression +inside a conditional expression. + +``` +equal[x; y]=[atom[x] -> [atom[y] -> eq[x; y]; T -> F]; + equal[car[x]; car[y]] -> equal[cdr[x]; cdr[y]]; + T -> F] +``` + +This can be translated into the following S-expression: , + +```lisp +(LABEL EQUAL + (LAMBDA (X Y) + (COND ((ATOM X) (COND ((ATOM Y) (EQ X Y)) + ((QUOTE T) (QUOTE F)))) + ((EQUAL (CAR X) (CAR Y)) (EQUAL (CDR X) (CDR Y))) + ((QUOTE T)(QUOTE F))))) +``` + +#### subst[x; y; z] + +This function gives the result of substituting the S-expression x for all occurrences of the atomic symbol y in the S-expression z. It is defined by + +``` +subst[x; y; z] = [equal[y; z] -> x; + atom[z] - z; + T - cons[subst + [x; y; car[z]]; subst[x; y; cdr[z]]]] +``` + +As an example, we have + +```lisp +SUBST[(X . A); B; ((A . B) . c)] = ((A . (X . A)) . C) +``` + +#### null[x] +This predicate is useful for deciding when a list is exhausted. It is true if and only if its argument is NIL. + +The following functions are useful when S-expressions are regarded as lists. + +#### 1. append[x; y] +``` +append[x; y] = [null[x] -> y; T -> cons[car [x]; append[cdr [x]; y]]] +``` + +An example is + +``` +append[(A B);(C D E)] = (A B C D E) +``` + +#### 2. member[x; y] + +This predicate is true if the S-expression `x` occurs among the elements of the list `y`. We have +``` +member[x; y] = [null[y] -> F; + equal[x; car [y ]] ->T; + T -> member[x; cdr [y ]]] +``` + +#### 3. pairlis[x; y; a] + +page 12 + +This function gives the list of pairs of corresponding elements of the lists `x` and +`y`, and appends this to the list `a`. The resultant list of pairs, which is like a table with +two columns, is called an association list. We have + +``` +pairlis [x; y; a] = [null[x] -> a; + T -> cons[cons[car[x]; car[y]]; + pairlis[cdr[x]; cdr [y]; a]]] +``` + +An example is + +``` +pairlis[(A B C);(U V W);((D . X) (E . Y))] = +((A . U) (B . V) (C . W)(D . X) (E . Y)) +``` + +#### 4. assoc[x; a] + +If `a` is an association list such as the one formed by `pairlis` in the above example, +then `assoc` will produce the first pair whose first term is `x`. Thus it is a table searching +function. We have + +``` +assoc[x; a] = [equal[caar[a]; x] -> car[a]; T -> assoc[x; cdr[a]]] +``` + +An example is + +``` +assoc[B; ((A . (M N)), (B . (CAR X)), (C . (QUOTE M)), (C . (CDR x)))] += (B . (CAR X)) +``` + +##### 5. sublis[a; y] + +Here `a` is assumed to be an association list of the form ((u1. v1)... (un . vn)), +where the `u`s are atomic, and `y` is any S-expression. What `sublis` does, is to treat +the `u`s as variables when they occur in `y`, and to substitute the corresponding `v`s +from the pair list. In order to define `sublis`, we first define an auxiliary function. We have + +``` +sub2[a; z] = [null[a] -> z; eq[caar[a]; z] -> cdar[a]; + T -> sub2[cdr[a]; z]] +``` +and + +``` +sublis[a; y] = [atom[y] -> sub2[a; y]; + T -> cons[sublis[a; car[y]]; sublis[a; cdr[y]]]] +``` + +An example is + +``` +sublis[((X. SHAKESPEARE) (Y. (THE TEMPEST)));(X WROTE Y)] = +(SHAKESPEARE WROTE (THE TEMPEST)) +``` + +The universal function `evalquote` that is about to be defined obeys the following identity. Let `f` be a function written as an M-expression, and let `fn` be its translation. (`fn` is an S-expression. ) Let `f` be a function of n arguments and let args=(arg1... argn), a list of the `n` S-expressions being used as arguments. Then + +`evalquote[fn; args] = f[arg`1`... arg`n`]` + +page 13 + +if either side of the equation is defined at all. + +Example + +| | | +| --------------- | -------------------------------- | +| f | λ[[x; y];cons[car[x]; y]] | +| fn | (LAMBDA (X Y) (CONS (CAR X) Y)) | +| arg1 | (A B) | +| arg2 | (C D) | +| args | ((A B) (C D)) | + +`evalquote[(LAMBDA (X Y) (CONS (CAR X) Y)); ((A B) (C D))] =` +λ`[[x;y];cons[car[x];y]][(A B);(C D)] =` +`(A C D)` + +`evalquote` is defined by using two main functions, called `eval` and `apply`. `apply` handles a function and its arguments, while `eval` handles forms. Each of these functions also has another argument that is used as an association list for storing the values of bound variables and function names. + +*note here that the environment -- the combination of the object list and the pushdown list -- is said to be an assoc list, where, importantly, it isn't. Of course, for the simplest possible Lisp, it would be -- But (to my surprise) Lisp 1.5 is nothing like the simplest possible Lisp.* + +```mexpr +evalquote[fn; x] = apply[fn; x; NIL] +``` + +where +```mexpr +apply[fn; x; a] = + [atom[fn] -> [eq[fn; CAR] -> caar[x] + eq[fn; CDR] -> cdar[x]; + eq[fn; CONS] -> cons[car[x]; cadr[x]]; + eq[fn; ATOM] -> atom[car[x]]; + eq[fn; EQ] -> eq[car[x]; cadr[x]]; + T -> apply[eval[fn; a]; x; a]] + eq[car[fn]; LAMBDA] -> eval[caddr[fn]; pairlis[cadr[fn]; x; a]]; + eq[car[fn]; LABEL] -> apply[caddr [fn]; x; cons[cons[cadr [fn]; + caddr[fn]]; a]]] + +eval[e;a] = [atom[e] -> cdr[assoc[e;a]]; + atom[car[e]] -> [eq[car[e]; QUOTE] -> cadr[e]; + eq[car[e]; COND] -> evcon[cdr[e]; a]; + T -> apply[car[e]; evlis[cdr[el; a]; a]]; + T -> apply[car[e]; evlis [cdr[e]; a]; a]] +``` + +`pairlis` and `assoc` have been previously defined. + +```mexpr +evcon[c; a] = [eval[caar[c]; a] -> eval[cadar[c]; a]; + T -> evcon[cdr [c];a]] +``` + +and + +```mexpr +evlis[m; a] = [null[m] -> NIL; + T -> cons [eval[car [m];a];evlis[cdr [m];a]]] +``` + +page 14 + +We shall explain a number of points about these definitions. + +The first argument for `apply` is a function. If it is an atomic symbol, then there are two possibilities. One is that it is an elementary function: `car`, `cdr`, `cons`, `eq`, or `atom`. In each case, the appropriate function is applied to the argument(s). If it is not one of these, then its meaning has to be looked up in the association list. + +If it begins with `LAMBDA`, then the arguments are paired with the bound variables, and the form is given to `eval` to evaluate. + +If it begins with `LABEL`, then the function name and definition are added to the as- +sociation list, and the inside function is evaluated by apply. + +The first argument of `eval` is a form. If it is atomic, then it must be a variable, and its value is looked up on the association list. + +If `car` of the form is `QUOTE`, then it is a constant, and the value is `cadr` of the form +itself. + +If `car` of the form is `COND`, then it is a conditional expression, and `evcon` evaluates +the propositional terms in order, and choses the form following the first true predicate. + +In all other cases, the form must be a function followed by its arguments. The arguments are then evaluated, and the function is given to apply. + +The LISP Programming System has many added features that have not been described thus far. These will be treated hereafter. At this point, it is worth noting the following points. + +1. In the pure theory of LISP, all functions other than the five basic ones need to be defined each time they are to be used. This is unworkable in a practical sense. The LISP programming system has a larger stock of built-in functions known to the interpreter, and provision for adding as many more as the programmer cares to define. +2. The basic functions `car` and `cdr` were said to be undefined for atomic arguments. In the system, they always have a value, although it may not always be meaningful. +Similarly, the basic predicate `eq` always has a value. The effects of these functions +in unusual cases will be understood after reading the chapter on list structures in the +computer. +3. Except for very unusual cases, one never writes `(QUOTE T)` or `(QUOTE F)`, +but T, and F respectively. +4. There is provision in LISP for computing with fixed and floating point numbers. These are introduced as psuedo-atomic symbols. + +The reader is warned that the definitions of `apply` and `eval` given above are pedagogical devices and are not the same functions as those built into the LISP programming system. Appendix B contains the computer implemented version of these functions and should be used to decide questions about how things really work. + +page 15 + +## II. THE LISP INTERPRETER SYSTEM + +The following example is a LISP program that defines three functions `union`, `intersection`, and `member`, and then applies these functions to some test cases. The functions `union` and `intersection` are to be applied to "sets," each set being represented by a list of atomic symbols. The functions are defined as follows. Note that they are all recursive, and both union and intersection make use of member. + +``` +member[a; x] = [null[x] -> F; eq[a; car[x]] -> T; + T -> member[a; cdr[x]]] + +union[x; y] = [null[x] -> y; + member[car[x];y] -> union[cdr[x]; y]; + T -> cons[car[x]; union[cdr[x]; y]]] + +intersection[x;y] = [null[x] -> NIL; + member[car[x]; y] -> cons[car[x]; intersection[cdr[x]; y]]; + T -> intersection[cdr[x]; y]] +``` + +To define these functions, we use the pseudo-function define. The program looks like +this : + +DEFINE (( +(MEMBER (LAMBDA (A X) (COND ((NULL X) F) +( (EQ A (CAR X) ) T) (T (MEMBER A (CDR X))) ))) +(UNION (LAMBDA (X Y) (COND ((NULL X) Y) ((MEMBER +(CAR X) Y) (UNION (CDR X) Y)) (T (CONS (CAR X) +(UNION (CDR X) Y))) 1)) +(INTERSECTION (LAMBDA (X Y) (COND ((NULL X) NIL) +( (MEMBER (CAR X) Y) (CONS (CAR X) (INTERSECTION +(CDR X) Y))) (T (INTERSECTION (CDR X) Y)) ))) +1) +INTERSECTION ((A1 A2 A3) (A1 A3 A5)) +UNION ((X Y Z) (U V W X)) +This program contains three distinct functions for the LISP interpreter. The first +function is the pseudo-function define. A pseudo-function is a function that is executed +for its effect on the system in core memory, as well as for its value. define causes +these functions to be defined and available within the system. Its value is a list of the +functions defined, in this case (MEMBER UNION INTERSECTION). , +The value of the second function is (A1 A3). The value of the third function is +(Y Z U V W X). An inspection of the way in which the recursion is carried out will show +why the I1elementsN of the Itset" appear in just this order. +Following are some elementary rules for writing LISP 1.5 programs. + +1. A program for execution in LISP consists of a sequence of doublets. The first +list or atomic symbol of each doublet is interpreted as a function. The second is a list + +``` +of arguments for the function. They are evaluated by evalquote, and the value isprinted. +``` + +2. There is no particular card format for writing LISP. Columns 1-72 of anynumber +of cards may be used. Card boundaries are ignored. The format of this program, in- +cluding indentation, was chosen merely for ease of reading. +3. A comma is the equivalent of a blank. Any number of blanks and/or commas can +occur at any point in a program except in the middle of an atomic symbol. +4. Do not use the forms (QUOTE T), (QUOTE F), and (QUOTE NIL). Use T, F, and +NIL instead. +5. Atomic symbols should begin with alphabetical characters to distinguish them +from numbers. +6. Dot notation may be used in LISP 1.5. Any number of blanks before or after the +dot will be ignored. +7. Dotted pairs may occur as elements of a list, and lists may occur as elements +of dotted pairs. For example, + +``` +is a valid S-expression. It could also be written +((A. B). (X. ((C. (E. (F. (G. NIL)))). NIL))) or +((A. B) X (C E F G)) +``` + +8. A form of the type (A B C. D) is an abbreviation for (A. (B. (C. D))). Any +other mixing of commas (spaces) and dots on the same level is an error, e. g. (A. B C). +9. A selection of basic functions is provided with the LISP system. Other functions +may be iytroduced by the programmer. The order in which functions are introduced +is not significant. Any function may make use of any other function. + +``` +2.1 Variables +A variable is a symbol that is used to represent an argument of a function. Thus one +might write "a + b, where a = 341 and b = 216.11 In this situation no confusion can result +and all will agree that the answer is 557. In order to arrive at this result, it is neces- +sary to substitute the actual numbers for the variables, and then add the two number (on +an adding machine for instance). +One reason why there is no ambiguity in this case is that llall and "bl1 are not accept- +able inputs for an adding machine, and it is therefore obvious that they merely represent +the actual arguments. In LISP, the situation can be much more complicated. An atomic +symbol may be either a variable or an actual argument. To further complicate the sit- +uation, a part of an argument may be a variable when a function inside another function +is evaluated. The intuitive approach is no longer adequate. An understanding of the +formalism in use is necessary to do any effective LISP programming. +Lest the prospective LISP user be discouraged at this point, it should be pointed out +that nothing new is going to be introduced here. This section is intended to reinforce +the discussion of Section I. Everything in this section can be derived from the rule for +``` + +translating M-expressions into S-expressions, or alternatively everything in this section +can be inferred from the universal function evalquote of Section I. +The formalism for variables in LISP is the Church lambda notation. The part of the +interpreter that binds variables is called apply. When apply encounters a function be- +ginning with LAMBDA, the list of variables is paired with the list of arguments and added +to the front of the a-list. During the evaluation of the function, variables may be encountered. +They are evaluated by looking them up on the a-list. If a variable has been bound several +times, the last or most recent value is used. The part of the interpreter that does this +is called eval. The following example will illustrate this discussion. Suppose the inter- +preter is given the following doublet: + +fn: (LAMBDA (X Y) (CONS X Y)) +args: (A B) +evalquote will give these arguments to apply. (Look at the universal function of +Section I. ) + +``` +~P~~Y[(LAMBDA (X Y) (CONS X Y)); (A B);NIL] +``` + +- apply will bind the variables and give the function and a-list to eval. + eval[(~~N~ X Y); ((X. A) (Y. B))] + eval will evaluate the variables and give it to cons. + cons[^;^] = (A. B) + The actual interpreter skips one step required by the universal function, namely, + apply[~O~~;(A B);((X. A) (Y. B))]. + +2.2 Constants +It is sometimes assumed that a constant stands for itself as opposed to a variable +which stands for something else. This is not a very workable concept, since the student +who is learning calculus is taught to represent constants by a, b, c... and variables by +x, y, z.... It seems more reasonable to say that one variable is more nearly constant +than another if it is bound at a higher level and changes value less frequently. +In LISP, a variable remains bound within the scope of the LAMBDA that binds it. +When a variable always has a certain value regardless of the current a-list, it will be +called a constant. This is accomplished by means of the property list^1 (p-list) of the +variable symbol. Every atomic symbol has a p-list. When the p-list contains the in- +dicator APVAL, then the symbol is a constant and the next item on the list is the value. + +* eval searches p -lists before a-lists when evaluating variables, thus making it possible + to set constants. + Constants can be made by the programmer. To make the variable X always stand + for (A B C D), use the pseudo-function ~t. + 1. Property lists are discussed in Section VII. + +``` +An interesting type of constant is one that stands for itself. NIL is an example of +this. It can be evaluated repeatedly and will still be NIL. T, F, NIL, and other constants +cannot be used as variables. +``` + +``` +2.3 Functions +``` + +When a symbol stands for a function, the situation is similar to that in which a symbol +stands for an argument. When a function is recursive, it must be given a name. This +is done by means of the form LABEL, which pairs the n&me with the function definition +on the a-list. The name is then bound to the function definition, just as a variable is +bound to its value. +In actual practice, LABEL is seldom used. It is usually more convenient to attach +the name to the definition in a uniform manner. This is done by putting on the property +list of the name,the symbolEXPR followed by the function definition. The pseudo-function +define used at the beginning of this section accomplishes this. When apply interprets +a function represented by an atomic symbol, it searches the p-list of the atomic symbol +before searching the current a-list. Thus a define will override a LABEL. +The fact that most functions are constants defined by the programmer, and not vari- +ables that are modified by the program, is not due to any weakness of the system. On the +contrary, it indicates a richness of the system which we do not know how to exploit very +well. + +``` +2.4 Machine Language Functions +Some functions instead of being defined by S-expressions are coded as closed machine +language subroutines. Such a function will have the indicator SUBR on its property list +followed by a pointer that allows the interpreter to link with the subroutine. There are +three ways in which a subroutine can be present in the system. +1. The subroutine is coded into the LISP system. +``` + +2. The function is hand-coded by the user in the assembly type language, LAP. + 3. The function is first defined by an S-expression, and then compiled by the LISP + compiler. Compiled functions run from 10 to 100 times as fast as they do when they + are interpreted. + +``` +2.5 Special Forms +Normally, eval evaluates the arguments of a function before applying the function +itself. Thus if =l is given (CONS X Y), it will evaluate X and Y, and then cons them. +But if eval is given (QUOTE X), X should not be evaluated. QUOTE is a special form +that prevents its argument from being evaluated. +A special form differs from a function in two ways. Its arguments are not evaluated +before the special form sees them. COND, for example, has a very special way of +``` + +evaluating its arguments by using evcon. The second way which special forms differ +from functions is that they may have an indefinite number of arguments. Special forrrls +have indicators on their property lists called FEXPR and FSUBR for LISP -defined forms +and machine language coded forms, respectively. + +``` +2.6 Programming for the Interpreter +``` + +The purpose of this section is to help the programmer avoid certain common errors. +Example 1 +fn: CAR +args: ((A B)) +The value is A. Note that the interpreter expects a list of arguments. The one argu- +ment for car is (A B). The extra pair of parentheses is necessary. +One could write (LAMBDA (X) (CAR X)) instead of just CAR. This is correct but +unnecessary. + +``` +Example 2 +fn: CONS +args: (A (B. C)) +The value is cons[^;(^. c)] = (A. (B. C)). +The print program will write this as (A B. C). +``` + +Example (^3) - +fn: CONS +args: ((CAR (QUOTE (A. B))) (CDR (QUOTE (C. D)))) +The value of this computation will be ((CAR (QUOTE (A. B))). (CDR (QUOTE (C. D)))). +This is not what the programmer expected. He expected (CAR (QUOTE (A. B))) to +evaluate to A, and expected (A. D) as the value of cons. + +* The interpreter expects a ---- list of arguments. ------- It does not expect a list of expressions +-- that will evaluate to the arguments. Tworcorrect ways of writing this function are listed +below. The first one makes the car and cdr part of a function specified by a LAMBDA. +The second one uses quoted arguments and gets them evaluated by eval with a null a-list. +fn: (LAMBDA (X Y) (CONS (CAR X) (CDR Y))) +args: ((A. B) (C. D)) +fn: EVAL +args: ((CONS (CAR (QUOTE (A. B))) (CDR (QUOTE (C. D)))) NIL) +The value of both of these is (A. D). + +111. ## EXTENSION OF THE LISP LANGUAGE + +``` +Section I of this manual presented a purely formal mathematical system that we +shall call pure LISP. The elements of this formal system are the following. +``` + +1. A set of symbols called S-expressions. +2. A functional notation called M-expressions. +3. A formal mapping of M-expressions into S-expressions. +4. A universal function (written ,IS an M-expression) for interpreting the application +of any function written as an S-expression to its arguments. +Section II introduced the LISP Programming System. The basis of the LISP Pro- +gramming System is the interpreter, or evalquote and its components.. A LISP program +in fact consists of pairs of arguments for evalquote which are interpreted in sequence. +In this section we shall introduce a number of extensions of elementary LISP. These +extensions of elementary LISP are of two sorts. The first includes propositional con- +nectives and functions with functions as arguments, and they are also of a mathematical +nature; the second is peculiar to the LISP Programming System on the IBM 7090 computer. +In all cases, additions to the LISP Programming System are made to conform to the +functional syntax of LISP even though they are not functions. For example, the command +to print an S-expression on the output tape is called print. Syntactically, print is a +function of one argument. It may be used in composition with other functions, and will + +be evaluated in the usual manner, with the inside of the composition being evaluated first. +Its effect is to print its argument on the output tape (or on-line). It is a function only in +the trivial sense that its value happens to be its argument, thus making it an identity +function. +Commands to effect an action such as the operation of input-output, or the defining +functions define and cset discussed in Chapter 11, will be called pseudo-functions. It +is characteristic of the LISP system that all functions including psuedo-functions must +have values. In some cases the value is trivial and may be ignored. +This Chapter is concerned with several extensions of the LISP language that are in +the system. + +``` +3.1 Functional Arguments +Mathematically, it is possible to have functions as arguments of other functions. +For example, in arithmetic one could define a function operate [op;a;b], where op is a +functional argument that specifies which arithmetic operation is to be performed on a +and b. Thus +operate[+;3;4]=7 and +operate[x;3;4]= 12 +``` + +In LISP, functional arguments are extremely useful. A very important function with +a functional argument is maplist. Its M-expression definition is + +maplist[x;fn]=[null[x]-NIL; +T-cons [fn[x];maplis t [cdr [x];fn]]] +An examination of the universal function evalquote will show that the interpreter can +handle maplist and other functions written in this manner without any further addition. +The functional argument is, of course, a function translated into an S-expression. It is +bound to the variable fn and is then used whenever fn is mentioned as a function. The +S-expression for maplist itself is as follows: +(MAPLIST (LAMBDA (X FN) (COND ((NULL X) NIL) +(T (CONS (FN X) (MAPLIST (CDR X) FN))) ))) + +``` +Now suppose we wish to define a function that takes a list and changes it by cons-ing +an X onto every item of the list so that, for example, +change[(^ B (C D))]=((A. X) (B. X) ((C. D). X)) +``` + +``` +Using maplist, we define change by +change[a]=maplist[a;~[[j];cons[car [j];~]]] +``` + +``` +This is not a valid M-expression as defined syntactically in section 1.5 because a +function appears where a form is expected, This can be corrected by modifying the rule +defining an argument so as to include functional arguments: +< argument > :: = 1 c function > +``` + +``` +We also need a special rule to translate functional arguments into S-expression. If +``` + +- fn is a function used as an argument, then it is translated into (FUNCTION fn*). + +``` +Example +(CHANGE (LAMBDA (A) (MAPLIST A (FUNCTION +(LAMBDA (J) (CONS (CAR J) (QUOTE X))) ))) +``` + +``` +An examination of evalquote shows that QUOTE will work instead of FUNCTION, +provided that there are no free variables present. An explanation of how the interpreter +processes the atomic symbol FUNCTION is given in the Appendix B. +3.2 Logical Connectives +``` + +``` +The logical or Boolian connectives are usually considered as primitive operators. +However, in LISP, they can be defined by using conditional expressions: +``` + +``` +In the System, not is a predicate of one argument. However, g& and or are pred- +icates of an indefinite number of arguments, and therefore are special forms. In +``` + +``` +writing M-expressions it is often convenient to use infix notation and write expressions +such as aV bVc for or[a;b;c]. In S-expressions, one must, of course, use prefix no- +tation and write (OR A B C). +The order in which the arguments of and and or are given may be of some significance +in the case in which some of the arguments may not be well defined. The definitions of +these predicated given above show that the value may be defined even if all of the argu- +ments are not. +@ evaluates its arguments from left to right. If one of them is found that is false, +then the value of the is false and no further arguments are evaluated. If the argu- +ments are all evaluated and found to be true, then the value is true. +``` + +- or evaluates its arguments from left to right. If one of them is true, then the value +of the or is true and no further arguments are evaluated. If the arguments are all eval- +uated and found to be false, then the value is false. +3.3 Predicates and Truth in LISP + +Although the rule for translating M-expressions into S-expressions states that T is +(QUOTE T), it was stated that in the system one must always write T instead. Similarly, +one must write F rather than (QUOTE F). The programmer may either accept this +rule blindly or understand the following Humpty-Dumpty semantics. +In the LISP programming system there are two atomic symbols that represent truth +and falsity respectively. These two atomic symbols are *T* and NIL. It is these sym- +bols rather than T and F that are the actual value of all predicates in the system. This +is mainly a coding convenience. +The atomic symbols T and F have APVAL1s whose values are *T* and NIL, re- +spectively. The symbols T and F for constant predicates will work because: + +``` +The forms (QUOTE *T*) and (QUOTE NIL) will also work because +``` + +``` +*T* and NIL both have APVAL.'s that point to themselves. Thus *T* and NIL are +also acceptable because +``` + +``` +But +QUOTE QUOTE F) ;NIL]= F +which is wrong and this is why (QUOTE F) will not work. Note that +``` + +which is wrong but will work for a different reason that will be explained in the +paragraph after next. +There is no formal distinction between a function and a predicate in LISP. A pred- +icate can be defined as a function whose value is either *T* or NIL. This is true of all +predicates in the System. +One may use a form that is not a predicate in a location in which a predicate is called +for, such as in the p position of a conditional expression, or as an argument of a logical +predicate. Semantically, any S-expression that is not NIL will be regarded as truth in +such a case. One consequence of this is that the predicates null and not are identical. +Another consequence is that (QUOTE T) or (QUOTE X) is equivalent to T as a constant +predicate. +The predicate eq - has the following behavior. + +1. If its arguments are different, the value of 3 is NIL. +2. If its arguments are both the same atomic symbol, its value is *T*. +3. If its arguments are both the same, but are not atomic, then the value is *T* or +NIL depending upon whether the arguments are identical in their representation in core +memory. +4. The value of - eq is always *T* or NIL. It is never undefined even if its arguments +are bad. + +``` +ARITHMETIC LISP +``` + +``` +Lisp 1.5 has provision far handling fixed-point and floating-point numbers and log- +ical words. There are functions and predicates in the system for performing arithmetic +and logical operations and making basic tests. +4.1 Reading and Printing Numbers +``` + +``` +Numbers are stored in the computer as though they were a special type of atomic +symbol. This is discussed more thoroughly in section 7.3. The following points should +be noted : +``` + +1. Numbers may occur in S-expressions as though they were atomic symbols. +2. Numbers are constants that evaluate to themselves. They do not need to be quoted. +3. Numbers should not be used as variables or function names. +a. Floating-Point Numbers + +``` +The rules for punching these for the read program are: +``` + +1. A decimal point must be included but not as the first or last character. +2. A plus sign or minus sign may precede the number. The plus sign is not required. +3. Exponent indication is optional. The letter E followed by the exponent to the +base 10 is written directly after the number. The exponent consists of one or two digits +that may be preceded by a plus or minus sign. +4. Absolute values must lie between 2' 28 and 2-I 28 and +5. Significance is limited to 8 decimal digits. +6. Any possible ambiguity between the decimal point and the point used in dot no- +tation may be eliminated by putting spaces before and after the LISP dot. This is not +required when there is no ambiguity. +Following are examples of correct floating-point numbers. These are all different +forms for the same number, and will have the same effect when read in. + +``` +The forms .6E+2 and 60. are incorrect because the decimal point is the first or last +character respectively. +b. Fixed-Point Numbers +These are written as integers with an optional sign. +Examples +-1 7 +327 19 +``` + +``` +c. Octal Numbers or Logical Words +The correct form consists of +1. A sign (optional). +``` + +2. Up to 12 digits (0 through 7). + 3. The letter Q. +4. An optional scale factor. The scale factor is a decimal integer, no sign allowed. + +``` +Example +``` + +``` +The effect of the read program on octal numbers is as follows. +``` + +1. The number is placed in the accumulator three bits per octal digit with zeros +added to the left-hand side to make twelve digits. The rightmost digit is placed in bits +33-35; the twelfth digit is placed in bits P, 1, and 2. +2. The accumulator is shifted left three bits (one octal digit) times the scale factor. +Thus the scale factor is an exponent to the base 8. +3. If there is a negative sign, it is OR-ed into the P bit. The number is then stored +as a logical word. +The examples a through e above will be converted to the following octal words. +Note that because the sign is OR-ed with the 36th numerical bit c, d, and e are equiv- +alent. + +4.2 Arithmetic Functions and Predicates +We shall now list all of the arithmetic functions in the System. They must be given +numbers as arguments; otherwise an error condition will result. The arguments may +be any type of number. A function may be given some fixed-point arguments and some +floating-point arguments at the same time. +If all of the arguments for a function are fixed-point numbers, then the value will +be a fixed-point number. If at least one argument is a floating-point number, then the +value of the function will be a floating-point number. +plus[xl;. -.. ;xn] is a function of any number of arguments whose value is the alge- +braic sum of the arguments. + +difference[^;^] has for its value the algebraic difference of its arguments. + +* minus[x] has for its value -x. + times[xl;.. .;xn] is a function of any number of arguments, whose value is the product + (with correct sign) of its arguments. + addl[x] has xtl for its value. The value is fixed-point or floating-point, depending + on the argument. +* subl[x] has x-1 for its value. The value is fixed-point or floating-point, depending +on the argument. +* max[xl;... ;xn] chooses the largest of its arguments for its value. Note that +max[3;2.0] = 3.0. +* min[xl ;... ;xn] chooses the smallest of its arguments for its value. +* recip[x] computes l/x. The reciprocal of any fixed point number is defined as zero. +quo ti en![^;^] computes the quotient of its arguments. For fixed-point arguments, +the value is the number theoretic quotient. A divide check or floating-point trap will +result in a LISP error. +remainder[^;^] computes the number theoretic remainder for fixed-point numbers, +and the floating-point residue for floating-point arguments. +divide[x;y] = cons[qu~tient[x;~]; con~[remainder[x;~];~~~]] +* e~pt[x;~] = xY. If both x and y are fixed-point numbers, this is computed by iter- +ative multiplication. Otherwise the power is computed by using logarithms. The first +argument cannot be negative. +We shall now list all of the arithmetic predicates in the System. They may have +fixed-point and floating-point arguments mixed freely. The value of a predicate is *T* +or NIL. +les~~[x;~] - is true if x c y, and false otherwise. +greaterp[x;y] is true if x > y. +zerop[x] is true if x=O, or if 1 x IC 3 X +* onep[x] is true if^1 x-^1 ( <^3 X lo-'. +minusp[x] is true if x is / negative. +"-0 is negative. +numberp[x] is true if x is a number (fixed-point or floating-point). +* fixp[x] is true only if x is a fixed-point number. If x is not a number at all, an +error will result. +floatp[x] - is similar to fixp[x] but for floating-point numbers. +equal[x;y] works on any arguments including S-expressions incorporating numbers +inside them. Its value is true if the arguments are identical. Floating-point numbers +must satisfy I x-~ 1 < 3 X 10 -6. +The logical functions operate on 36-bit words. The only acceptable arguments are +fixed-point numbers. These may be read in as octal or decimal integers, or they may +be the result of a previous computation. +logor[xl ;... ;x n ] performs a logical OR on its arguments. + +logand[xl ;... ;xn] performs a logical AND on its arguments. +logxor[xl ;... ;xn] performs an exclusive OR +(OxO=O, 1~0=0~1=1,1~1=0). +leftshift[x;n] = x x 2". The first argument is shifted left by the number of bits spec- +ified by the second argument. If the second argument is negative, the first argument +will be shifted right. + +4.3 Programming with Arithmetic + +The arithmetic functions may be used recursively, just as other functions available +to the interpreter. As an example, we define factorial as it was given in Section I. + +``` +n! = [n = 0 -1; T-n.(n-l)! ] +DEFINE (( +(FACTORIAL (LAMBDA (N) (COND +((ZEROP N) 1) +(T (TIMES N (FACTORIAL (SUB1 N)))) ))) +``` + +### 4.4 The Array Feature + +Provision is made in LISP 1.5 for allocating blocks of storage for data. The data +may consist of numbers, atomic symbols or other S-expressions. +The pseudo-function array reserves space for arrays, and turns the name of an +array into a function that can be used to fill the array or locate any element of it. +Arrays may have up to three indices. Each element (uniquely specified by its co- +ordinates) contains a pointer to an S-expression (see Section VII). +array is a function of one argument which is a list of arrays to be declared. Each +item is a list containing the name of an array, its dimensions, and the word LIST. (Non- +list arrays are reserved for future development~ of the LISP system.) +For example, to make an array called alpha of size 7 X 10, and one called beta - of +size 3 X 4 X 5 one should execute: +array[((A~p~A (7 10) LIST) (BETA (3 4 5) LIST))] +After this has been executed, both arrays exist and their elements are all set to +NIL. Indices range from 0 to n-I. +alpha and - beta are now functions that can be used to set or locate elements of these +respective arrays. +TO set alphai to x, execute - +s j + +``` +To set alpha3, to (A B C) execute - +alpha[s~~; (A B c); 3;4] +``` + +Inside a function or program X might be bound to (A B C), I bound to 3, and J bound +to 4, in which case the setting can be done by evaluating - + +``` +(ALPHA (QUOTE SET) X I J) +``` + +To locate an element of an array, use the array name as a function with the coordi- +nates as axes. Thus any time after executing the previous example - + +``` +alpha[3;4] = (A B C) +``` + +Arrays use marginal indexing for maximum speed. For most efficient results, +specify dimensions in increasing order. ~eta[3;4;5] is better than beta[5;3;4]. +Storage for arrays is located in an area of memory called binary program space. + +## V. THE PROGRAM FEATURE + +The LISP 1 .5 program feature allows the user to write an Algol-like program con- +taining LISP statements to be executed. +An example of the program feature is the function length, which examines a list and +decides how many elements there are in the top level of the list. The value of length is +an integer. +Length is a function of one argurnentL. The program uses two program variables + +- u and y, which can be regarded as storage locations whose contents are to be changed + by the program. In English the program is written: + This is a function of one argument 1. + It is a program with two program variables 2 and 1. + Store 0 in + Store the argument 1 in 2. + A If g contains NIL, then the program is finished, + and the value is whatever is now in 2. + Store in u, cdr of what is now in g. + Store in 1, one more than what is now in + Go to A. + +``` +We now write this program as an M-expression, with a few new notations. This +corresponds line for line with the program written above. +``` + +``` +Rewriting this as an S-expression, we get the following program. +DEFINE (( +(LENGTH (LAMBDA (L) +(PROG (U V) +(SETQ V 0) +(SETQ U L) +(COND ((NULL U) (RETURN V))) +(SETQ U (CDR U)) +(SETQ V (ADD1 V)) +(GO A) 1)) 1) +LENGTH ((A B C D)) +``` + +``` +LENGTH (((X Y) A CAR (N B) (X Y 2))) +``` + +The last two lines are test cases. Their values are four and five, respectively. +The program form has the structure - +(PROG, list of program variables, sequence of statements and atomic' symbols.. .) +An atomic symbol in the list is the location marker for the statement that follows. In +the above example, A is a location marker for the statement beginning with COND. +The first list after the symbol PROG is a list of program variables. If there are +none, then this should be written NIL or (). Program variables are treated much like +bound variables, but they are not bound by LAMBDA. The value of each program vari- +able is NIL until it has been set to something else. +To set a program variable, use the form SET. To set variable PI to 3.14 write +(SET (QUOTE PI) 3.14). SETQ is like SET except that it quotes its first argument. Thus +(SETQ PI 3.14). SETQ is usually more convenient. SET and SETQ can change variables +that are on the a-list from higher level functions. The value of SET or SETQ is the value +of its second argument. +Statements are normally executed in sequence. Executing a statement means eval- +uating it with the current a-list and ignoring its value. Program statements are often +executed for their effect rather than their value. +GO is a form used to cause a transfer. (GO A) will cause the program to continue +at statement A. The form GO can be used only as a statement on the top level of a +PROG or immediately inside a COND which is on the top level of a PROG. +Conditional expressions as program statements have a useful peculiarity. If none +of the propositions are true, instead of an error indication which would otherwise occur, +the program continues with the next statement. This is true only for conditional expres- +sions that are on the top level of a PROG. +RETURN is the normal end of a program. The argument of RETURN is evaluated, +and this is the value of the program. No further statements are executed. +If a program runs out of statements, it returns with the value NIL. +The program feature, like other LISP functions, can be used recursively. The +function rev, which reverses a list and all its sublists is an example of this. +rev[x] = ~rog[[~;z]; +A [null[x]-return[y]; +z:= car[x]; +[atom[z]- go[^]]; +z:= rev[z]; +B y: = cons[^;^]; +x:= cdr[x]; +goiA11 +The function rev will reverse a list on all levels so that +rev[(A ((B C) D))] = ((D (C B)) A) + +``` +VI. RUNNING THE LISP SYSTEM +``` + +``` +6.1 Preparing a Card Deck +``` + +A LISP program consists of several sections called packets. Each packet starts +with an Overlord direction card, followed by a set of doublets for evalquote, and ending +with the word STOP. +Overlord direction cards control tape movement, restoration of the system memory +between packets, and core dumps. A complete listing of Overlord directions is given +in Appendix E. +Overlord direction cards are punched in Share symbolic format; the direction starts +in column 8, and the comments field starts in column 16. Some Overlord cards will +now be described. +TEST: Subsequent doublets are read in until the word STOP is encountered, or until +a read error occurs. The doublets are then evaluated and each doublet with its value +is written on the output tape. If an error occurs, a diagnostic will be written and the +program will continue with the next doublet. When all doublets have been evaluated, +control is returned to Overlord which restores the core memory to what it was before +the TEST by reading in a core memory image from the temporary tape. + +* SET: The doublets are read and interpreted in the same manner as a TEST. However, + when all doublets have been evaluated, the core memory is not restored. Instead, the + core memory is written out onto the temporary tape (overwriting the previous core + image), and becomes the base memory for all remaining packets. Definitions and + other memory changes made during a SET will affect all remaining packets. + Several SET'S during a LISP run will set on top of each other. + A SET will not set if it contains an error. The memory will be restored from the + temporary tape instead. + SETSET: This direction is like SET, except that it will set even if there is an error. +* FIN: End of LISP run. + The reading of doublets is normally terminated by the word STOP. If parentheses + do not count out, STOP will appear to be inside an S-expression and will not be recog- + nized as such. To prevent reading from continuing indefinitely, each packet should end + with STOP followed by a large number of right parentheses. An unpaired right paren- + thesis will cause a read error and terminate reading. + A complete card deck for a LISP run might consist of: + a: LISP loader + b: ID card (Optional) + c: Several Packets + .d: FIN card + e: Two blank cards to prevent card reader from hanging up + The ID card may have any information desired by the computation center. It will be + +printed at the head of the output. + +6.2 Tracing +Tracing is a technique used to debug recursive functions. The tracer prints the +name of a function and its arguments when it is entered, and its value when it is finished. +By tracing certain critical subfunctions, the user can often locate a fault in a large pro- +gram. +Tracing is controlled by the pseudo-function trace, whose argument is a list of func- +tions to be traced. After trace has been executed, tracing will occur whenever these +functions are entered. +When tracing of certain functions is no longer desrred, it can be terminated by the +pseudo-function untrace whose argument is a list of functions that are no longer to be +traced. + +6.3 Error Diagnostics +When an error occurs in a LISP 1 .5 program, 'a diagnostic giving the nature of the +error is printed out. The diagnostic gives the type of error, and the contents of certain +registers at that time. In some cases a back-trace is also printed. This is a list of +functions that were entered recursively but not completed at the time of the error. +In most casee, the program continues with the next doublet. However, certain er- +rors are fatal; in this case control is given to the monitor Overlord. Errors during +Overlord also continue with Overlord. +A complete list of error diagnostics is given below, with comments. + +Interpreter Errors: +A 1 APPLIED FUNCTION CALLED ERROR +The function error will cause an error diagnostic to occur. The argument +(if any) of error will be printed. Error is of some use as a debugging aid. +A 2 FUNCTION OBJECT HAS NO DEFINITION- APPLY +This occurs when an atomic symbol, given as the first argument of apply, +does not have a definition either on its property list or on the a-list of apply. +A 3 CONDITIONAL UNSATISFIED - EVCON +None of the propostiions following COND are true. +A 4 SETQ GIVEN ON NONEXISTEYT PROGRAM VARIABLE - APPLY +A 5 SET GIVEN ON NONEXISTENT PROGRAM VARIABLE - APPLY +A 6 GO REFERS TO A POINT NOT LABELLED - INTER +A 7 TOO MANY ARGUMENTS - SPREAD +The interpreter can handle only 20 arguments for a function. +A 8 UNBOUND VARIABLE - EVAL +The atomic symbol in question is not bound on the a-list for eval nor does it +have an APVAL. + +A 9 FUNCTION OBJECT HAS NO DEFINITION - EVAL +Eva1 expects the first object on a list to be evaluated to be an atomic symbol. +A 8 and A 9 frequently occur when a parenthesis miscount causes the wrong +phrase to be evaluated. + +Compiler Errors : +C 1 CONDITION NOT SATISFIED IN COMPILED FUNCTION + +Character -Handling Functions : +CH 1 TOO MANY CHARACTERS IN PRINT NAME - PACK +CH 2 FLOATING POINT NUMBER OUT OF RANGE - NUMOB +CH 3 TAPE READING ERROR - ADVANCE +The character-handling functions are described in Appendix F. + +Miscellaneous Errors : +F 1 CONS COUNTER TRAP +The cons counter is described in section 6.4. +F 2 FIRST ARGUMENT LIST TOO SHORT - PAIR +F 3 SECOND ARGUMENT LIST TOO SHORT - PAIR +Pair is used by the interpreter to bind variables to arguments. If a function +is given the wrong number of arguments, these errors may occur. + +F 5 STR TRAP - CONTINUING WITH NEXT EVALQUOTE +When the instruction STR is executed, this error occurs. +If sense switch 6 is down when an STR is executed, +control goes to Overlord instead. +G 1 FLOATING POINT TRAP OR DIVIDE CHECK +G 2 OUT OF PUSH - DOWN LIST +The push-down list is the memory device that keeps track of the level of re- +cursion. When recursion becomes very deep, this error will occur. Non- +terminating recursion will cause this error. + +Garbage Collector Errors: +GC 1 FATAL ERROR - RECLAIMER +This error only occurs when the system is so choked that it cannot be restored. +Control goes to Overlord. +GC 2 NOT ENOUGH WORDS COLLECTED - RECLAIMER +This error restores free storage as best it can and continues with the next +doublet. + +Arithmetic Errors: +I1 NOT ENOUGH ROOM FOR ARRAY +Arrays are stored in binary program space. + +``` +I2 FIRST ARGUMENT NEGATIVE - EXPT +I3 BAD ARGUMENT - NUMVAL +I4 BAD ARGUMENT - FIXVAL +Errors I 3 and I 4 will occur when numerical functions are given wrong argu- +ments. +``` + +Lap Errors: +L 1 UNABLE TO DETERMINE ORIGIN +L 2 OUT OF BINARY PROGRAM SPACE +L 3 UNDEFINED SYMBOL +L 4 FIELD CONTAINED SUB - SUBFIELDS +Overlord Errors: +0 1 ERROR IN SIZE CARD - OVERLORD +0 2 INVALID TAPE DESIGNATION - OVERLORD +0 3 NO SIZE CARD - OVERLORD +0 4 BAD DUMP ARGUMENTS - OVERLORD +0 5 BAD INPUT BUT GOING ON ANYHOW - OVERLORD +0 7 OVERLAPPING PARAMETERS - SETUP + +Input -Output Errors: +P 1 PRINl ASKED TO PRINT NON-OBJECT +R 1 FIRST OBJECT ON INPUT LIST IS ILLEGAL - RDA +This error occurs when the read program encounters a character such'as +I1)l1 or ." out of context. This occurs frequently when there is a parenthesis +miscount. +R 2 CONTEXT ERROR WITH DOT NOTATION - RDA +R 3 ILLEGAL CHARACTER - RDA +R 4 END OF FILE ON READ-IN - RDA +R 5 PRINT NAME TOO LONG - RDA +Print names may contain up to 30 BCD characters. +R 6 NUMBER TOO LARGE IN CONVERSION - RDA +6.4 The Cons Counter and Errorset +The cons counter is a useful device for breaking out of program loops. It automat- +ically causes a trap when a certain number of conses have been performed. +The counter is turned on by executing count [n], where n is an integer. If n conses +are performed before the counter is turned off, a trap will occur and an error diagnos- +tic will be given. The counter is turned off by uncount [NIL]. The counter is turned +on and reset each time count [n] is executed. The counter can be turned on so as to +continue counting from the state it was in when last turned off by executing count [NIL]. +The function speak [NIL] gives the number of conses counted since the counter was +last reset. + +errorset is a function available to the interpreter and compiler for making a graceful +retreat from an error condition encountered during a subroutine. +errorset[e;n;m;a] is a pseudo-function with four arguments. If no error occurs, then +errorset can be defined by +errorset[e;n;m;a] = list[eval[e;a]] + +* n is the number of conses permitted before a cons trap will occur. The cons counter +is always on during an errorset; however, when leaving the errorset the counter is al- +ways restored to the value it had before entering the errorset. The on-off status of the +counter will also be restored. +When an error occurs inside an errorset, the error diagnostic will occur if m is +set true, but will not be printed if m is NIL. +If an error occurs inside of an errorset, then the value of errorset is NIL. If vari- +ables bound outside of the errorset have not been altered by using cset or set, and if no +damage has been done by pseudo-functions, it may be possible to continue computation +in a different direction when one path results in an error. + +## VII. LIST STRUCTURES + +In other sections of this manual, lists have been discussed by using the LISP input-output language. In this section, we discuss the representation of lists inside the computer, the nature of property lists of atomic symbols, representation of numbers, and the garbage collector. + +### 7.1 Representation of List Structure + +Lists are not stored in the computer as sequences of BCD characters, but as structural forms built out of computer words as parts of trees. In representing list structure, a computer word will be depicted as a rectangle divided into two sections, the address and decrement. + +add. I dec. + +Each of these is a 15-bit field of the word. +We define a pointer to a computer word as the 15-bit quantity that is the complement +of the address of the word. Thus a pointer to location 77777 would be 00001. +Suppose the decrement of word x contains a pointer to word y. We diagram this as + +We can now give a rule for representing S-expressions in the computer. The repre- +sentation of atomic symbols will be explained in section 7.3. When a computer word +contains a pointer to an atomic symbol in the address or decrement, the atomic symbol +will be written there as + +1: + +The rule for representing non-atomic S-expressions is to start with a word containing +a pointer to car of the expression in the address, and a pointer to c&r of the expression +in the decrement. +Following are some diagrammed S-expressions, shown as they would appear in the +computer. It is convenient to indicate NIL by -- - -- - instead of -- -- -F]. + +It is possible for lists to make use of common subexpressions. ((M. N) X (M. N)) +could also be represented as + + +Circular lists are ordinarily not permitted. They may not be read in; however, they +can occur inside the computer as the result of computations involving certain functions. +Their printed representation is infinite in length. For example, the structure + + +``` +will print as (A B C A B C A. .. +That which follows is an actual assembly listing of the S-expression (A (B (C. A)) +(C. A)) which is diagrammed: +``` + +``` +The atoms Aj B, and C are represented by pointers to locations 12327, 12330, and +12331, respectively. NIL is represented by a pointer to location 00000. +``` + +``` +The advantages of list structures for the storage of symbolic expressions are: +``` + +1. The size and even the number of expressions with which the program will have +to deal cannot be predicted in advance. Therefore, it is difficult to arrange blocks of + +page 37 + +storage of fixed length to contain them. + +2. Registers can be put back on the free.-storage list when they are no longer +needed. Even one register returned to the list is of value, but if expressions are stored +linearly, it is difficult to make use of blocks of registers of odd sizes that may become +available. +3. An expression that occurs as a subexpression of several expressions need be +represented in storage only once, + +### 7.2 Construction of List Structure + +The following simple example has been included to illustrate the exact construction +of list structures. Two types of list structures are shown, and a function for deriving +one from the other is given in LISP. +We assume that we have a list of the form +n, = ((A B C) (D E F),... , (X Y z)), + +which is represented as + +and that we wish to construct a list of the form + +1, = ((A (B c)) (D (E F)),... , (x (Y z))) + +which is represented as + +We consider the typical substructure, (A (B C)) of the second list Q2. This may be +constructed from A, B, and C by the operation +cons [~;cons[cons [B; CO~S[C;NIL]]; NIL]] +or, using the l& function, we can write the same thing as + +In any case, given a list, x, of three atomic symbols, +x = (A B C), +the arguments A, B, and C to be used in the previous construction are found from +A = car[x] +B = cadr[x] +C = caddr[x] +The first step in obtaining P2 from P1 is to define a function, m, of three arguments +which creates (X (Y Z)) from a list of the form (X Y Z). +grp[x] = list[car[x];list[cadr[x];caddr[x]]] +Then - grp is used on the list P1, under the assumption that P1 is of the form given. +For this purpose, a new function, mltgrp, is defined as + +##### mltgrp[P] = [null[P] - NIL;T - cons[grp[car[P]];mltgrp[cdr[~]]]] + +So rnltgrp applied to the list P1 takes each threesome, (X Y Z), in turn and applies - grp +to it to put it in the new form, (X (Y Z)) until the list P1 has been exhausted and the new +list P2 achieved. + +### 7.3 Property Lists + +In other sections, atomic symbols have been considered only as pointers. In this +section the property lists of atomic symbols that begin at the appointed locations are +described. + +Every atomic symbol has a property list. When an atomic symbol is read in for +the first time, a property list is created for it. + +A property list is characterized by having the special constant 777778 (i. e., minus 1) +as the first element of the list. The rest of the list contains various properties of the +atomic symbol. Each property is preceded by an atomic symbol which is called its +indicator. Some of the indicators are: + +| Indicator | Description | +| --------- | ------------------------------------------------------------ | +| PNAME | the BCD print name of the atomic symbol for input-output use. | +| EXPR | S-expression defining a function whose name is the atomic symbol on whose property list the EXPR appears. | +| SUBR | Function defined by a machine language subroutine. | +| APVAL | Permanent value for the atomic symbol considered as a variable. | + +The atomic symbol NIL has two things on its property list - its PNAME, and an +APVAL that gives it a value of NIL. Its property list looks like this: +``` + +``` +-1 I APVAL I PNAME~ +A +1 +I I +``` + +``` +NIL??? +``` + +-I,, -NIL +-APVAL, , -*-I +-*- 1, , *-2 +0 +, +-PNAME , , -*- 1 +-*- 1 +-*- 1 +BCD NIL??? +The print name (PNAME) is depressed two levels to allow for names of more than +six BCD characters. The last word of the print name is filled out with the illegal BCD +character 778 (7). The print name of EXAMPLE would look like this: + + - - - PNAME I I + I + +``` ++ +EXAMPL +``` + +The property list of a machine-language function contains the indicator SUBR +followed by a TXL instruction giving the location of the subroutine and the number of +arguments. For example + +TXL 37721,, 2 1 + +``` +The indicator EXPR points to an S-expression defining a function. The function define +puts EXPR1s on property lists. After defining ff, its property list would look like this + +-1 I + +LAMBDA I + +The function get[x;i] can be used to find a property of x whose indicator is i. The +value of get[X; G] would be (LAMBDA (X) (COW... +A property with its indicator can be removed by remprop[x;i]. +The function deflist[x;i] can be used to put any indicator on a property list. The +first argument is a list of pairs as for define, the second argument is the indicator to +be used. define[x] = deflist[x;Ex~R]. +An indicator on a property list that does not have a property following it is called +a flag. For example, the flag TRACE is a signal that a function is to be traced. Flags +can be put on property lists and removed by using the pseudo-functions - flag and rernflag. +Numbers are represented by a type of atomic symbol in LISP. This word consists +of a word with -1 in the address, certain bits in the tag which specify that it is a number +and what type it is, and a pointer to the number itself in the decrement of this word. +Unlike atomic symbols, numbers are not stored uniquely. +For example, the decimal number 15 is represented as follows: + +### 7.4 List Structure Operators + +The theory of recursive functions developed in Section I will be referred to as elementary LISP. Although this language is universal in terms of computable functions of symbolic expressions, it is not convenient as a programming system without additional tools to increase its power. + +In particular, elementary LISP has no ability to modify list structure. The only basic function that affects list structure is `cons`, and this does not change existing lists, but creates new lists. Functions written in pure LISP such as `subst` do not actually modify their arguments, but make the modifications while copying the original. + +LISP is made general in terms of list structure by means of the basic list operators +`rplaca` and `rplacd`. These operators can be used to replace the address or decrement +or any word in a list. They are used for their effect, as well as for their value, and +are called pseudo-functions. + +`rplaca[x;y]` replaces the address of `x` with `y`. Its value is `x`, but `x` is something different from what it was before. In terms of value, rplaca can be described by the equation +rpla~a[x;~] = c~ns[~;cdr[x]] +But the effect is quite different: there is no cons involved and a new word is not created. + +`rplacd[x;y]` replaces the decrement of `x` with `y`. + +These operators must be used with caution. They can permanently alter existing +definitions and other basic memory. They can be used to create circular lists, which +can cause infinite printing, and look infinite to functions that search, such as `equal` and +`subst`. + +As an example, consider the function mltgrp of section 7.2. This is a list-altering function that alters a copy of its argument. The subfunction - grp rearranges a subgroup + +The original function does this by creating new list structures, and uses four cons's. Because there are only three words in the original, at least one cons is necessary, but + + +- grp can be rewritten by using rplaca and rplacd. + The modification is + +The new word is created by cons[cadr[x];cddr[x]]. A pointer to it is provided by rplaca[cdr[x];cons[cadr[x];cddr[x]]]. + +The other modification is to break the pointer from the second to the third word. +This is done by rplacd[cdr[x];~l~]. +pgrp - is now defined as +pgrp[x] = rplacd[rplaca[cdr[x];cons[cadr[x];cddr[x]]];~l~] +The function - pgrp is used entirely for its effect. Its value is not useful, being the +substructure ((B C)). Therefore a new mltgrp is needed that executes pgrp - and ignores +its value. Since the top level is not to be copied, mltprp should do no consing. +pmltgrp[l] = [null[l] -. NIL; +T -- ~rog2[~g~~[car[~Il~~~~tgr~[cdr[~1111 +prog2 is a function that evaluates its two arguments. Its value is the second argument. +The value of pmltgrp is NIL. pgrp - and - pmltgrp are pseudo-functions. + + +### 7.5 The Free-Storage List and the Garbage Collector + +At any given time only a part of the memory reserved for list structures will actually be in use for storing S-expressions. The remaining registers are arranged in a single list called the free-storage list. A certain register, FREE, in the program contains the location of the first register in this list. When a word is required to form some additional list structure, the first word on the free-storage list is taken and the number in register FREE is changed to become the location of the second word on the free-storage list. No provision need be made for the user to program the return of registers to the free-storage list. + +This return takes place automatically whenever the free -storage list has been exhausted during the running of a LISP program. The program that retrieves the storage is called the garbage collector. + +Any piece of list structure that is accessible to programs in the machine is considered an active list and is not touched by the garbage collector. The active lists are accessible to the program through certain fixed sets of base registers, such as the registers in the list of atomic symbols, the registers that contain partial results of the LISP computation in progress, etc. The list structures involved may be arbitrarily long but each register that is active must be connected to a base register through a car-cdr - chain of registers. Any register that cannot be so reached is not accessible to any program and is nonactive; therefore its contents are no longer of interest. + +The nonactive, i. e. , inaccessible, registers are reclaimed for the free-storage list by the garbage collector as follows. First, every active register that can be reached through a car-cdr chain is marked by setting its sign negative. Whenever a negative register is reached in a chain during this process, the garbage collector knows that therest of the list involving that register has already been marked. Then the garbage collector does a linear sweep of the free-storage area, collecting all registers with a positive sign into a new free-storage list, and restoring the original signs of the active registers. + +Sometimes list structure points to full words such as BCD print names and numbers. The garbage collector cannot mark these words because the sign bit may be in use. The garbage collector must also stop tracing because the pointers in the address and decrement of a full word are not meaningful. + +These problems are solved by putting full words in a reserved section of memory called full-word space. The garbage collector stops tracing as soon as it leaves the free-storage space. Marking in full-word space is accomplished by a bit table. + +### VIII. A COMPLETE LISP PROGRAM - THE WANG ALGORITHM FOR THE PROPOSITIONAL CALCULUS + +This section gives an example of a complete collection of LISP function definitions which were written to define an algorithm. The program was then run on several test cases. The algorithm itself is explained, and is then written in M-expressions. The complete input card deck and the printed output of the run are reprinted here. + +The [Wang Algorithm](https://dl.acm.org/doi/abs/10.1147/rd.41.0002) is a method of deciding whether or not a formula in the propositional calculus is a theorem. The reader will need to know something about the propositional calculus in order to understand this discussion. + +We quote from pages 5 and 6 of Wang's paper: +"The propositional calculus (System P) +Since we are concerned with practical feasibility, it is preferable to use more logical +connectives to begin with when we wish actually to apply the procedure to concrete cases. +For this purpose we use the five usual logical constants .V (not), & (conjunction), V (dis- +junction), 3(implication), E; (biconditional), with their usual interpretations. +"A propositional letter P, Q, R, M or N, et cetera, is a formula (and an "atomic +formulat1). If 4, + are formulae, then N +, + & +, + V +, 4 3 +, + f + are formulae. +If n, p are strings of formulae (each, in particular, might be an empty string or a + +###### single formula) and + is a formula, then IT, +, p is a string and a - p is a sequent + +which, intuitively speaking, is true if and only if either some formula in the string n +(the I1antecedentI1) is false or some formula in the string p (the llconsequent") is true, +i. e., the conjunction of all formulae in the antecedent implies the disjunction of all for- +mulae in the consequent. +"There are eleven rules of derivation. An initial rule states that a sequent with only +atomic formulae (proposition letters) is a theorem if and only if a same formula occurs +on both sides of the arrow. There are two rules for each of the five truth functions - +one introducing it into the antecedent, one introducing it into the consequent. One need +only reflect on the intuitive meaning of the truth functions and the arrow sign to be con- +vinced that these rules are indeed correct. Later on, a proof will be given of their com- +pleteness, i. e., all intuitively valid sequents are provable, and of their consistency, +i. e. , all provable sequents are intuitively valid. + +"PI. Initial rule: if h, 5 are strings of atomic formulae, then h -. 5 is a theorem if +some atomic formula occurs on both sides of the arrow. +"In the ten rules listed below, h and 5 are always strings (possibly empty) of atomic +formulae. As a proof procedure in the usual sense, each proof begins with a finite set +of cases of P1 and continues with successive consequences obtained by the other rules .I1 + +1. Wang, Hao. "Toward Mechanical Mathematics," IBM J. Res. Develop., Vo1.4, + No. 1. January 1960. + +"As will be explained below, a proof looks like a tree structure growing in the wrong +direction. We shall, however, be chiefly interested in doing the step backwards, thereby +incorporating the process of searching for a proof. +"The rules are so designed that given any sequent, we can find the first logical con- +nective, and apply the appropriate rule to eliminate it, thereby resulting in one or two +premises which, taken together, are equivalent to the conclusion. This process can be +repeated until we reach a finite set of sequents with atomic formulae only. Each +connective-free sequent can then be tested for being a theorem or not, by the initial rule. +If all of them are theorems, then the original sequent is a theorem and we obtain a proof; +otherwise we get a counterexample and a disproof. Some simple samples will make this +clear. +"For example, given any theorem of nPrincipia, we can automatically prefix an +arrow to it and apply the rules to look for a proof. When the main connective is 3, it is +simpler, though not necessary, to replace the main connective by an arrow and pro- +ceed. For example: +*2.45. +: (PVQ). 3 .I- P, +*5.21. +:N P &N Q .2. PS Q + +``` +can be rewritten and proved as follows: +*2.45. /v (P).) -wP +(1) -& Pa PVQ +(2) P -Pi"Q +(3) P -P,Q +VALID +*5.21. ---P& &Q .3. P, Q +(l)-P&w Q-PE Q +(2)"P, NQ -P = Q +(3)- Q -P Q,P +(4) -P 5 Q, P,Q +(5) P -Q, P, Q +VALID +(5) Q -Pa P, Q +VALID +``` + +``` +P2a. Rule -0.y: If +, 5 -hap, thenS-h,~+,pd +P2b. Rule&--: If hap -a,+, thenh,~+, p-a. +P3a. Rule -&: If 5 -.A,+, pand5 -h,+,p, thenS*h,(~&~~l,~- +P3b. Rule &-: If h,+,+,p -a, then ha+&+, P-a. +P4a. Rule -- V : If 5 -A,+,+, p, then 5 -h,(SVICl1p +P4b. RuleV -. : If ha+, p--rand h,+, p-a, then X,+V+, p-a. +P5a. Rule-3 : If 5,+ -h,+,p, then 5 -. A, +3+, p. +``` + +``` +P5b. Rule 3- : If h,+, pus and A, p -a,+, then h,+3JC,p-a. +``` + +###### P6a. Rule - : If 4, b - h, +, p and +,^5 - A, +,P , then^5 - +a+, P + +``` +P6b. Rule= -: If +,#,h, p-aand h, p-a,+,#, then h,+ 4, p-a." +``` + +(2) The LISP Program. We define a function theorem[s] whose value is truth or +falsity according to whether the sequent s is theorem. +The sequent + +is represented by the S-expression + +where in each case the ellipsis... denotes missing terms, and where +* denotes the +S-expression for +. +Propositional formulae are represented as follows: + +1. For "atomic formulaeu (Wang's terminology) we use "atomic symbols1' (LISP +terminology). +2. The following table gives our "Cambridge Polish" way of representing proposi- +tional formulae with given main connectives. + +### 1. - + becomes (NOT +*) + +``` +2- +&51 becomes (AND +* +*) +3- +v$ becomes (OR +* S*) +4- +3# becomes (IMPLIES +* #*) +5.+=+ becomes (EQUIV @* +*) +Thus the sequent +``` + +- P &NQ -P =, Q,RVS +is represented by +(ARROW ((AND (NOT P) (NOT Q))) ((EQUIV P Q) (OR R S))) + +``` +The S-function theorem[s] is given in terms of auxiliary functions as follows: +``` + +``` +theorem[s] = thl [NIL;NIL;C~~~ [s] ;caddr[s]] +``` + +###### member[car[a];c] V [atom[car[ a]] - + +``` +thl [[member[car[a];al ] - a1 ;T -c cons[car[ +a];al]];a2;cdr[a];c];~ -. thl [al;[ +``` + +###### member[car[a];a2] - a2;T - cons[ + +``` +car [a];a2]];cdr [a];~]]] +``` + +###### c2;cdr[c]];~ - th2[al ;a2;c 1 ;[ + +#### member[car[c];c2] -, c2;T - cons[ + +``` +car [c];c 2]];cdr [c]]] +th[al ;a2;c 1 ;c2] = [null[a2] - n, null[c2]~thr[car[c2]; +a1 ;a2;c 1 ;cdr[c2]];~ -. tke[car[a2]; +a1 ;cdr[a2];cl ;c2]] +``` + +this the main predicate through which all the recursions take place. theorem, tu +anda2 break up and sort the information in the sequent for the benefit of &. The four +arguments oft& are: +al: atomic formulae on left side of arrow +a2: other formulae on left side of arrow +cl: atomic formulae on right side of arrow +c2: other formulae on right side of arrow +The atomic formulae are kept separate from the others in order to make faster the +detection of the occurrence of formula on both sides of the arrow and the finding of the +next formula to reduce. Each use of& represents one reduction according to one of the +10 rules. The forumla to be reduced is chosen from the left side of the arrow if possible. +According to whether the formula to be reduced is on the left or right we use or g. +We have + +tke[u;al;a2;cl;c2] = [ + +###### car[u] = NOT - th1 r[cadr[u];al;a2;c 1 ;c2] + +``` +car[u] = AND -, th2l[cdr[u];al ;a2;c 1 ;c2]; +``` + +##### car[u] = OR - thlQ[cadr[u];al ;a2;cl ;c2] A thlL + +``` +caddr [u];al ;a2;c 1 ;c2]; +``` + +###### car[u] = IMPLIES - thll[caddr[u];al ;a2;cl ;c2] fi thlr + +``` +cadr[u];al ;a2;c 1 ;c2]; +``` + +###### car[u] = EQUIV - th2P [cdr[u];al ;a2;c 1 ;c2] A th2r + +``` +cdr[u];al ;a2;c 1 ;c2]; +``` + +###### T - error[list[~~~;u;al ;a2;cl;c2]]] + +``` +thr[u;al;a2;cl;c2] = [ +car[u] = NOT -, thl4[cadr[u];al ;a2;cl ;c2]; +``` + +##### car[u] = AND - thlr[cadr[u];al ;a2;cl ;c2] A thlr + +``` +caddr[u];al ;a2;c 1 ;c2]; +``` + +##### car[u] = OR - th2r[cdr[u];al ;a2;c l;c2] + +##### car[u] = IMPLIES - thl 1 [cadr[u];caddr[u];al;a2;c 1 ;c2] + +``` +car[u] = EQUIV -, thl 1 [cadr[u];caddr[u];al ;a2;c 1 ;c2] +thl 1 [caddr[u];cadr[u];al ;a2;cl;c2]; +T -- error[~~~;u;al ;a2;c 1 ;c2]]] +``` + +The functions thld, thlr, th28, th2r, thll distribute the parts of the reduced for- +mula to the appropriate places in the reduced sequent. +These functions are + +##### th2l[v;al ;a2;c 1 ;c2] = [atom[car[v]] - member[car[v];c 11 v + +``` +thlP[cadr[v];cons[car[v];al];a2;c 1 ;c2];~ -- member[ +car [v];c2] v +thld[cadr[v];al ;cons[car[v];a2];cl ;c2]] +``` + +###### th2r [v;al ;a2;c 1 ;c2] = [atom[car[v]] - member[car[v];al] v + +###### thl r[cadr[v];al ;a2;cons[car[v];cl];c2];~ - member[V + +``` +car [v];a2] V +thl r [cadr [v];a 1 ;a2;c 1 ;cons [car [v];c 2111 +``` + +``` +thl~[~l;v2;al;a2;cl;c2] = [atom[vl] -member[vl;cl]~ +thlr[v2;cons[vl;al];a2;cl;c2];~ -member[vl;c2]~ +thl r[v2;al ;cons[vl ;a2];c 1 ;c2]] +Finally the function member ,is defined by +``` + +member [ x;u] = ~lull[u]~[e~ual[x; car[u]]\lmember [x;cdr [u]]] + +The entire card deck is reprinted below, with only the two loader cards, which are +binary, omitted. The function member is not defined because it is already in the sys- +tem. + +* M948-1207 LEVIN, LISP, TEST, 2,3,250,0 +TEST WANG ALGORITHM FOR THE PROPOSITIONAL CALCULUS + +DEFINE(( +(THEOREM (LAMBDA (S) (TH1 NIL NIL (CADR S) (CADDR S)))) + +``` +(THl (LAMBDA (A1 .A2 A C) (COND ((NULL A) +(TH2 A1 A2 NIL NIL C)) (T +(OR (MEMBER (CAR A) C) (COND ((ATOM (CAR A)) +(TH1 (COND ((MEMBER (CAR A) Al) Al) +(T (CONS (CAR A) Al))) A2 (CDR A) C)) +(T (TH1 A1 (COND ((MEMBER (CAR A) A2) A2) +(T (CONS (CAR A) A2))) (CDR A) C)))))))) +``` + +``` +(TH2 (LAMBDA (A1 A2 C1 C2 C) (COND +((NULL C) (TH A1 A2 Cl C2)) +((ATOM (CAR C)) (TH2 A1 A2 (COND +((MEMBER (CAR C) C1) C1) (T +(CONS (CAR C) Cl))) C2 (CDR C))) +(T (TH2 A1 A2 C1 (COND ((MEMBER +(CAR C) C2) C2) (T (CONS (CAR C) C2))) +(CDR C)))))) +``` + +(TH (LAMBDA (A1 A2 C1 C2) (COND ((NULL A2) (AND (NOT (NULL C2)) +(THR (CAR C2) A1 A2 C 1 (CDR C2)))) (T (THL (CAR A2) A1 (CDR A2) +Cl C2))))) + +``` +(THL (LAMBDA (U A1 A2 C1 C2) (COND +((EQ (CAR U) (QUOTE NOT)) (THlR (CADR U) A1 A2 C1 C2)) +((EQ (CAR U) (QUOTE AND)) (TH2L (CDR U) A1 A2 C1 C2)) +((EQ (CAR U) (QUOTE OR)) (AND (THlL (CADR U) A1 A2 C1 C2) +(THlL (CADDR U) A1 A2 Cl C2) )) +((EQ (CAR U) (QUOTE IMPLIES)) (AND (THlL (CADDR U) A1 A2 C1 +C2) (THlR (CADR U) A1 A2 C1 C2) )) +((EQ (CAR U) (QUOTE EQUIV)) (AND (THZL (CDR U) A1 A2 C1 C2) +(THZR (CDR U) A1 A2 C1 C2) )) +(T (ERROR (LIST (QUOTE THL) U A1 A2 C1 C2))) +)I) +``` + +(THR (LAMBDA (U A1 A2 C1 C2) (COND +((EQ (CAR U) (QUOTE NOT)) (TH1 L (CADR U) A1 A2 C 1 C2)) +((EQ (CAR U) (QUOTE AND)) (AND (THlR (CADR U) A1 A2 C1 C2) +(THlR (CADDR U) A1 A2 Cl C2) )) +((EQ (CAR U) (QUOTE OR)) (THZR (CDR U) A1 A2 C1 C2)) +((EQ (CAR U) (QUOTE IMPLIES)) (THl1 (CADR U) (CADDR U) + +``` +A1 A2 C1 C2)) +((EQ (CAR U) (QUOTE EQUIV)) (AND (TH11 (CADR U) (CADDR U) +A1 A2 C1 C2) (THl1 (CADDR U) (CADR U) A1 A2 C1 C2) )) +(T (ERROR (LIST (QUOTE THR) U A1 A2 C1 C2))) +1)) +``` + +(THlL (LAMBDA (V A1 A2 C1 C2) (COND +((ATOM V) (OR (MEMBER V C1) +(TH (CONS V Al) A2 C1 C2) )) +(T (OR (MEMBER V C2) (TH A1 (CONS V A2) C1 C2) )) +))I + +(THlR (LAMBDA (V A1 A2 C1 C2) (COND +((ATOM V) (OR (MEMBER V Al) +(TH A1 A2 (CONS V C1) C2) )) +(T (OR (MEMBER V A2) (TH A1 A2 C1 (CONS V C2)))) +1) + +(TH2L (LAMBDA (V A1 A2 C1 C2) (COND +((ATOM (CAR V)) (OR (MEMBER (CAR V) C1) +(THlL (CADR V) (CONS (CAR V) Al) A2 C1 C2))) +(T (OR (MEMBER (CAR V) C2) (THlL (CADR V) A1 (CONS (CAR V) +A2) C1 C2))) +1)) + +``` +(TH2R (LAMBDA (V A1 A2 Cl C2) (COND +((ATOM (CAR V)) (OR (MEMBER (CAR V) Al) +(THlR (CADR V) A1 A2 (CONS (CAR V) C 1) C2))) +(T (OR (MEMBER (CAR V) A2) (THlR (CADR V) A1 A2 C1 +(CONS (CAR V) C2)))) +1)) +``` + +(TH11 (LAMBDA (V1 V2 A1 A2 C1 C2) (COND +((ATOM V1) (OR (MEMBER V1 C1) (THlR V2 (CONS V1 Al) A2 C1 +C2))) +(T (OR (MEMBER V1 C2) (THlR V2 A1 (CONS V1 A2) C1 C2))) +1)) + +TRACE ((THEOREM TH1 TH2 TH THL THR THlL THlR THEL TH2R TH11)) + +``` +THEOREM +((ARROW (P) ((OR P Q)))) +``` + +``` +UNTRACE ((THEOREM TH1 TH2 THR THL THl L THlR THZL THZR TH11)) +``` + +``` +THEOREM +((ARROW ((OR A (NOT B))) ((IMPLIES (AND P Q) (EQUIV P Q))) )) +``` + +``` +STOP))) 1)) 1)) 1)) +FIN END OF LISP RUN M948- 1207 LEVIN +``` + +``` +This run produced the following output: +``` + +``` +* M948-1207 LEVIN, LISP, TEST, 2,3,250,0 +TEST WANG ALGORITHM FOR THE PROPOSITIONAL CALCULUS +THE TIME (8/ 8 1506.1) HAS COME, THE WALRUS SAID, TO TALK OF MANY THINGS +``` + +... - LEWIS CARROLL - + +``` +FUNCTION EVAUUOTE HAS BEEN ENTERED, ARGUMENTS.. +DEFINE +[ The complete list of definitions read in is omitted to save space.] +``` + +``` +END OF EVALQUOTE, VALUE IS.. +(THEOREM THl TH2 TH THL THR THlL THlR TH2L TH2R TH11) +``` + +``` +FUNCTION EVALQUOTE HAS BEEN ENTERED, ARGUMENTS.. +TRACE +((THEOREM TH1 TH2 TH THL THR THlL THlR TH2L THZR TH11)) +``` + +``` +END OF EVALQUOTE, VALUE IS.. +NIL +``` + +``` +FUNCTION EVALQUOTE HAS BEEN ENTERED, ARGUMENTS.. +THEOREM +``` + +ARGUMENTS OF TH1 +NIL +NIL + +``` +(P) +((OR P Q)) +``` + +``` +ARGUMENTS OF TH1 +(PI +NIL +NIL +((OR P Q)) +``` + +``` +ARGUMENTS OF THZ +(PI +NIL +NIL +NIL +((OR P Q)) +``` + +``` +ARGUMENTS OF TH2 +(PI +NIL +NIL +((OR P Q)) +NIL +``` + +``` +ARGUMENTS OF TH +(PI +NIL +NIL +((OR P Q)) +``` + +``` +ARGUMENTS OF THR +(OR P Q) +(PI +NIL +NIL +NIL +``` + +``` +ARGUMENTS OF THZR +(P Q) +``` + +(PI +NIL +NIL +NIL + +VALUE OF TH2R +*T* + +VALUE OF THR +*T* + +VALUE OF TH +*T* + +VALUE OF TH2 +*T* + +VALUE OF TH2 +ST* + +VALUE OF TH1 +*T* + +VALUE OF TH1 +*T* + +END OF EVALQUOTE, VALUE IS.. +*T* + +FUNCTION EVALQUOTE HAS BEEN ENTERED, ARGUMENTS.. +UNTRACE +((THEOREM TH1 TH2 THR THL THlL THlR THZL TH2R THl1)) + +END OF EVALQUOTE, VALUE IS.. +NIL + +``` +FUNCTION EVALQUOTE HAS BEEN ENTERED, ARGUMENTS.. +THEOREM +((ARROW ((OR A (NOT B))) ((IMPLIES (AND P Q) (EQUIV P Q ))))) +``` + +``` +ARGUMENTS OF TH +NIL +((OR A (NOT B))) +NI L +((IMPLIES (AND P Q) (EQUIV P Q))) +``` + +``` +ARGUMENTS OF TH +(A) +NIL +NIL +((IMPLIES (AND P Q) (EQUIV P Q))) +``` + +``` +ARGUMENTS OF TH +(A) +((AND P Q)) +NIL +((EQUIV P Q)) +``` + +ARGUMENTS OF TH +(Q P A) +NIL +NIL +((EQUIV P Q)) + +``` +VALUE OF TH +*T* +``` + +``` +VALUE OF TH +*T* +``` + +``` +VALUE OF TH +*T* +``` + +``` +ARGUMENTS OF TH +NIL +((NOT B)) +NIL +((IMPLIES (AND P Q) (EQUIV P Q))) +``` + +``` +ARGUMENTS OF TH +NIL +``` + +NIL + +``` +(B) +((IMPLIES (AND P Q) (EQUIV P Q)) +``` + +ARGUMENTS OF TH +NIL + +((AND P Q)) +(B) +((EQUIV P Q)) + +ARGUMENTS OF TH +(Q P) +NIL +(B) +((EQUIV P Q)) + +VALUE OF TH +*T* + +``` +VALUE OF TH +*T* +``` + +VALUE OF TH +*T* + +VALUE OF TH +*T* + +VALUE OF TH +*T* + +``` +END OF EVALQUOTE, VALUE IS.. +*T* +``` + +``` +THE TIME (8/ 8 1506.3) HAS COME, THE WALRUS SAID, TO TALK OF MANY THINGS +``` + +... - LEWIS CARROLL - + +``` +END OF EVALQUOTE OPERATOR +``` + +``` +FIN END OF LISP RUN M948-1207 LEVIN +``` + +``` +END OF LISP JOB +``` + + +## APPENDIX A : FUNCTIONS AND CONSTANTS IN THE LISP SYSTEM + +This appendix contains all functions available in the LISP System as of August 1962. Each entry contains the name of the object, the property under which it is available (e. g., EXPR, FEXPR, SUBR, FSUBR, or APVAL), whether it is a pseudo-function, functional (function having functions as arguments), or predicate, and in some cases a definition of the function as an M-expression. In the case of APVALts, the value is given. + +The LISP Library is a file of BCD cards distributed with the LISP System. It is not intended to be used as input to the computer without being edited first. Have the Library file punched out, and then list the cards. Each Library function is preceded by a title card that must be removed. Some Library entries are in the form of a DEFINE, while some are in the form of an assembly in LAP. Note that some of them have auxiliary functions that must be included. + + +Elementary Functions +car - [x] SUBR +cdr - [x] SUBR +The elementary functions car and &r always have some sort of value rather than +giving an error diagnostic. A chain of &Is applied to an atomic symbol will allow one +to search its property list. Indiscriminate use of these functions past the atomic level +will result in non-list structure and may appear as lengthy or even infinite garbage +expressions if printed. +CAR SXA CARX, 4 +PDX 094 +CLA 0,4 +PAX 084 +PXD 0,4 +CARX AXT **, 4 +TRA 1,4 +CDR SXA CDRX, 4 +PDX O,4 +C LA 0,4 +PDX 0,4 +PXD 0,4 +CDRX AXT **, 4 +TRA 1,4 +``` + +CO~S[X;~] - SUBR + +cons obtains a new word from the free storage list and places its two arguments +in the address and decrement of this word, respectively. It does not check to see if +the arguments are valid list structure. The value of cons is a pointer to the word that + +``` +was just created. If the free storage list has been exhausted, cons calls the garbage +collector to make a new free storage list and then performs the cons operation. +SUBR predicate +The first word on the property list of an atomic symbol contains -1 or 777778 in +the address. The following subroutine depends upon this, and on the fact that NIL is +located at 0 and *T* is located at -1 and has 1 as its complement pointer. +ATOM +``` + +``` +ATOMX +TRUE +``` + +``` +SXA +PDX +CLA +PAX +TXL +CLA +TRA +PXA +AXT +TRA +OCT +``` + +``` +ATOMX, 4 +0,4 +0~4 GET CAR OF ARGUMENT +084 +*t3,4, -2 TRANSFER IF NOT ATOMIC +TRUE IF IT IS ATOMIC +*t2 +O,O NIL IF NOT ATOMIC +**, 4 +1,4 +1000000 +es[x;y - I SUBR predicate +``` + +- eq is true if its two arguments are identical list structure. + EQ STQ + SUB + TZE + PXA + TRA + CLA + TRA + TRUE OCT + X PZE + +``` +X +X +*t3 +0,o +184 +TRUE +1s 4 +1000000 +``` + +``` +TRANSFER IF EQUAL +OTHERWISE VALUE IS NIL +VALUE IS *T* + +equal[x; y] - SUBR predicate + +* equal is true if its arguments are the same S-expression, although they do not have +to be identical list structure in the computer. It uses 7 eq on the atomic level and is +recursive. Floating point numbers in S-expressions are compared for numerical equal- +ity with a floating point tolerance of 3 X loW6. Fixed point numbers are compared for +numerical equality. +FSUBR + +The value of list is a list of its arguments. + +* null[x] SUBR predicate + +``` +The value of & is true if its argument is NIL which is located at 0 in the computer. +NULL TZE *+3 +PXA 080 +TRA 1,4 +CLA TRUE +TRA 1,4 +TRUE OCT 1000000 +``` +page 58 + +#### rplaca[x; y] : SUBR pseudo-function + +#### rplacd[x; y] : SUBR pseudo-function + +These list operators change list structure and can damage the system memory if not +used properly. See [page 41](#page41) for a description of usage. + +### Logical Connectives + +#### and[x1; x2; ... ; xn] : FSUBR predicate + +The arguments of are evaluated in sequence, from left to right, until one is found +that is false, or until the end of the list is reached. The value of and is false or true +respectively. + +#### or[x1; x2; ... ; xn] : FSUBR predicate + +The arguments of or are evaluated in sequence from left to right, until one is found that is true, or until the end of the list is reached. The value of or is true or false respectively. + +#### not [x]: SUBR predicate + +The value of not is true if its argument is false, and false otherwise. + +### Interpreter and Prog Feature + +These are described elsewhere in the manual: +`APPLY, EVAL, EVLIS, QUOTE, LABEL, FUNCTION, PROG, GO, RETURN, SET, SETQ.` + +### Defining Functions and Functions Useful for Property Lists + +#### define [x] : EXPR pseudo-function + +The argument of `define`, `x`, is a list of pairs + +> ((ul vl) (u2 v2) ... (un vn)) + +where each `u` is a name and each `v` is a λ-expression for a function . For each `pair`, define puts an `EXPR` on the property list for `u` pointing to `v`. The function of `define` puts things on at the front of the property list. The value of `define` is the list of `u`s. + +> define[x] = deflist[x; EXPR] + +#### deflist [x; ind] : EXPR pseudo-function + +The function `deflist` is a more general defining function. Its first argument is a list of pairs as for define. Its second argument is the indicator that is to be used. After `deflist` has been executed with (ui vi) among its first argument, the property list of ui will begin: + +If `deflist` or `define` is used twice on the same object with the same indicator, the old value will be replaced by the new one. + +#### attrib[x; e] : SUBR pseudo-function + +The function attrib concatenates its two arguments by changing the last element of its first argument to point to the second argument. Thus it is commonly used to tack something onto the end of a property list. The value of attrib is the second argument. + +For example +attrib[~~; (EXPR (LAMBDA (X) (COND ((ATOM X) X) (T (FF (CAR x))))))] +would put EXPR followed by the LAMBDA expression for FF onto the end of the prop- +erty list for FF. + + +#### prop[x; y; u] : SUBR functional + +The function `prop` searches the list `x` for an item that is `eq` to `y`. If such an element is found, the value of `prop` is the rest of the list beginning immediately after the element. Otherwise the value is `u[]`, where u is a function of no arguments. +``` +prop[x; y; u] = [null[x] -> u[ ]; + eq[car[x];y] -> cdr[x] + T -> prop[cdr[x]; y; u]] +``` +SUBR +``` + +- get is somewhat like prop; however its value is car of the rest of the list if the indi- +cator is found, and NIL otherwise. + +##### get [x;~] = [null[x] - NIL; eq[car [x];~] - cadr [x] + +* cset[ob;val] EXPR pseudo-function + +``` +This pseudo-function is used to create a constant by putting the indicator APVAL +and a value on the property list of an atomic symbol. The first argument should be an +atomic symbol; the second argument is the value is cons[val;N1~]. + +#### csetq[ob; val] : FEXPR pseudo-function + +csetq is like cset - except that it quotes its first argument instead of evaluating it. + +#### remprop[x; ind] : SUBR pseudo-function + +The pseudo-function remprop searches the list, x, looking for all occurrences of the +indicator ind. When such an indicator is found, its name and the succeeding property +are removed from the list. The two "endsn of the list are tied together as indicated by +the dashed line below. + +The value of remprop is NIL. + +When an indicator appears on a property list without a property following it, then +it is called a flag. An example of a flag is the indicator TRACE which informs the inter- +preter that the function on whose property list it appears is to be traced. There are two +pseudo-functions for creating and removing flags respectively. + +#### flag [I; ind] : EXPR pseudo-function + +The pseudo-function flag puts the flag ind on the property list of every atomic symbol +in the list 1. Note that d cannot be an atomic symbol, and must be a list of atomic sym- +bols. The flag is always placed immediately following the first word of the property +list, and the rest of the property list then follows. The value of flag is NIL. No property +list ever receives a duplicated flag. + +#### remflag[l; ind] : EXPR pseudo-function + +remflag removes all occurrences of the indicator ind from the property list of each +atomic symbol in the list 8. It does this by patching around the indicator with a rplacd +in a manner similar to the way remprop works. + +### Table Building and Table Reference Functions + +#### pair [x; y] : SUBR + +The function pair has as value the list of pairs of corresponding elements of the lists x and y. The arguments x and y must be lists of the same number of elements. They should & be atomic symbols. The value is a dotted pair list, i. e. ((a (a p2)... + + pair[x;y] = [prog[u;v; m] + u:= x; + v:= y; + +#### A [null[u] - [null[v] - return[m];~ - error[$2]]] + +``` +[null[v] -, error[~3]]; +m:= cons[cons[car[u];car[v]];m]; +u:= cdr[u]; +v:= cdr[v]; +go[~Il +``` + +#### sassoc[x; y; u] : SUBR functional + +The function sassoc searches y, which is a list of dotted pairs, for a pair whose first +element that is x. If such a pair is found, the value of sassoc is this pair. Otherwise +the function u of no arguments is taken as the value of sassoc. + +#### subst[x; y; z] : SUBR + +The function subst has as value the result of substituting x for all occurrences of +the S-expression y in the S-expression z. + +###### subst[x;y;z] = [equal[y;z] - x + +##### atom[z] - z + +T .- cons[subst[x;y;car [z]];subst [x;y;cdr[e]]]] + +#### sublis [x ; y] : SUBR + +Here x is a list of pairs, +((u1 . v1) (u2 . v2) (un . vn)) +The value of `sublis[x; y]` is the result of substituting each `v` for the corresponding +`u` in `y`. +Note that the following M-expression is different from that given in Section I, though +the result is the same. + +``` +sublis[x;y] = [null[x] -> y; + null[y] -> y; + T -> search[x; + lambda[[j]; equal[y;caar[j]]]; + lambda[[j]; cdar[j]]; + lambda[[j]; [atom[y] -> y; + T -> cons[sublis[x; car[y]]; + sublis[x; cdr[y]]]]]]] +``` + +### List Handling Functions + +#### append [x;y] : SUBR +The function append combines its two arguments into one new list. The value of +append is the resultant list. For example, +append[(^ B) (a1 = (A B C) +``` +append [x;y] = [null[x] -. y ; T - cons [car [x]; append[cdr [x]; y I]] +``` +Note that append copies the top level of the first list; append is like - nconc except that +nconc does not copy its first argument. + +* conc[xl;x2;... ;x n ] : FEXPR pseudo-function + * conc concatenates its arguments by stringing them all together on the top level. For + example, + conc[(~ (B C) D); (F); (G H)] = (A (B C) D F G H). +* conc concatenates its arguments without copying them. Thus it changes existing list +structure and is a pseudo-function. The value of conc is the resulting concatenated list. + +#### nconc [x; y] : SUBR pseudo-function + +The function `nconc` concatenates its arguments without copying the first one. The +operation is identical to that of attrib except that the value is the entire result, (i. e. the +modified first argument, x). + +The program for nconc[x;y] has the program variable m and is as follows: +``` +nconc [x; y ] = prog [[m]; +[null[x] - return[~]] +``` + +#### COPY [X] : SUBR + +This function makes a copy of the list x. The value of copy is the location of the +copied list. + +copy[x] = [null[x] - ~~~;atom[x] - x;T -- cons[copy[car[x]] + +``` +co~[cdr[xllIl +``` + +#### reverse[t] : SUBR + +This is a function to reverse the top level of a list. Thus +reverse[(A B (C. D))] = ((C D) B A)) +reverse[t] = prog[[v]; +u: =t; +``` +A [null[u] - return[v]] + +``` +v:=cons[car[u];v]; +u:=cdr[u]; +so[AlI + +#### member[x; l ] : SUBR predicate + +If the S-expression x is a member of the list l, then the value of member is T. +Otherwise, the value is NIL. + +##### member[x;l] = [null[l] - ~;equal[x;car[L]] -- T + +##### T - member[x;cdr[l]]] + +#### length[x] : SUBR + +The value of length is the number of items in the list x. The list ( ) or NIL has +length 0. + +#### efface[x; Q] : SUBR pseudo-function + +The function efface deletes the first appearance of the item x from the list 8. +``` +efface[x;l ] = [null[l] -. NIL; /' +equal[x;car[8]] .-. cdr[8]; +T -- rplacd[k ;effac e[x;cdr [8]]]] +``` + +These four functionals apply a function, f, to x, then to cdr[x], then to cddr[x], etc. + +Functionals or Functions with Functions as Arguments + +#### maplist [x; f ] : SUBR functional + +The function maplist is a mapping of the list x onto a new list f[x]. + +##### maplist [x; f] = [null[x] - NIL; T - cons[f [x]; maplist [cdr [x]; f]]] + +#### mapcon [x; f ] : SUBR pseudo-functional + +The function mapcon is like the function maplist except that the resultant list is a +concatenated one instead of having been created by cons-ing. +``` +mapcon[x; f ] = [null[x] -> NIL; T - nconc[f [x]; mapcon[cdr [x]; f ]]] +``` + +#### map[x; f ] : SUBR functional +The function map - is like the function maplist except that the value of map is NIL, +and map does not do a cons of the evaluated functions. map is used only when the action +of doing f[x] is important. +The program for map[x;f] has the program variable m and is the following: +map[x;f] = prog[[m]; +m:= x; +LOOP [null[m] -. returnl~~~]]; +f [m]; +m:= cdr[m]; +go[~oopl1 + +#### search[x; p; f; u] : SUBR functional + +The function search looks through a list x for an element that has the property p, and if such an element is found the function f of that element is the value of search. If there is no such element, the function u of one argument x is taken as the value of search (in this case x is, of course, NIL). + +Arithmetic Functions +These are discussed at length in Section IV. +function type number of args +plus FSUBR indef. +minus SUBR 1 + +- value + +difference +times +divide +quotient +remainder +add 1 +sub 1 +max +min +recip +expt +lessp +greaterp +zerop +onep +minusp +numberp +f ixp +float p +log or +logand +logxor +leftshift + +SUBR +FSUBR +SUBR +SUBR +SUBR +SUBR +SUBR +FSUBR +FSUBR +SUBR +SUBR +SUBR +SUBR +SUBR +SUBR +SUBR +SUBR +SUBR +SUBR +FSUBR +FSUBR +FSUBR +SUBR + +predicate +predicate +predicate +predicate +predicate +predicate +predicate +predicate +``` + +``` +2 +indef. +2 +2 +2 +1 +1 +indef. +indef. +1 2 2 2 1 1 1 1 1 1 + +indef. +indef. +indef. +2 + +Xl'X2'... *X n +list [x/~; remainder] + +remainder of x/~ + +largest of xi +smallest of xi +[fixp[x]-0; ~-.quotient[l ;XI] +XY + +x is negative +x is a number +x is a fixed point number +x is a floating point no. +xlVx2V.. .Vx n ORA +x1Ax2A... A xn ANA +x ,*x24... Vxn ERA +x 2Y +array SUBR 1 declares arrays + +### The Compiler and Assembler + +#### compile[x] : SUBR pseudo-function + +The list x contains the names of previously defined functions. They are compiled. + +#### special[x] : SUBR pseudo-function + +The list x contains the names of variables that are to be declared SPECIAL. + +#### unspecial[x] : SUBR pseudo-function + +The list x contains the names of variables that are no longer to be considered +SPECIAL by the compiler. + +#### common[x] : SUBR pseudo-function + +The list x contains the names of variables that are to be declared COMMON. + +#### uncommon[x] : SUBR pseudo-function + +The list x contains the names of variables that are no longer to be considered +COMMON by the compiler. + +#### lap[list; - table] : SUBR pseudo-function + +The assembler LAP is discussed in appendix C. +opd ef ine [x] EXPR pseudo-function +opdefine defines new symbols for the assembler LAP. The argument is a list of +dotted pairs, each pair consisting of symbol and value. + +#### readlap[ ] : EXPR pseudo-function + +readlap reads assembly language input and causes it to be assembled using LAP. +The input follows the STOP card of the packet containing the readlap. Each function to +be read in consists of a list of the two arguments of 2. These are read in successively +until a card containing NIL is encountered. readlap uses remob to remove unwanted +atomic symbols occurring in the listing. For this reason, it should only be used to read +cards that have been produced by punchlap. + +### Input and Output + +#### read[] : SUBR pseudo-function + +The execution of read causes one list to be read from SYSPIT, or from the card +reader. The list that is read is the value of read. + +#### print[x] : SUBR pseudo-function + The execution of - print causes the S-expression x to be printed on SYSPOT and/or + the on-line printer. The value of print is its argument. + punchrx] - SUBR pseudo.-function + The execution of punch causes S-expression x to be punched in BCD card images + on SYSPPT. The value of punch - is its argument. + prin l [x] SUBR pseudo-function +* prinl prints an atomic symbol without terminating the print line. The argument +of - prini must be an atomic symbol. + +#### terpri[] : SUBR pseudo-function + +terpri terminates the print line. + +The character reading, sorting and printing functions are discussed in appendix F. + +startread pack opc har error1 numob +advance unpack dash mknam +endread digit +clearbuff liter + +#### Functions for System Control, Debugging, and Error Processing + +#### trace[x] : EXPR pseudo-function + +The argument of trace is a list of functions. After trace has been executed, the +arguments and values of these functions are printed each time the function is entered +recursively. This is illustrated in the printed output of the Wang Algorithm example. +The value of trace is NIL. Special forms cannot be traced. + +#### untrace [x] : EXPR pseudo-function + +This removes the tracing from all functions in the list x. The value of untrace is NIL. +The following pseudo-functions are described in the section on running the LISP +system: + +--- count, uncount, speak, error, errorset. + +### Miscellaneous Functions ' +prog2 [x;~] SUBR +The value of prog2 is its second argument. It is used mainly to perform two pseudo- +functions. + +- CPl [XI SUBR + * cpA copies its argument which must be a list of a very special type. + +-1 I ILL WORD I I-T&~] + +The copied list is the value of cpi. + +#### gensym[ ] : SUBR + +The function gensym has no arguments. Its value is a new, distinct, and freshly- +created atomic symbol with a print name of the form G00001, G00002,... , G99999. +This function is useful for creating atomic symbols when one is needed; each one +is guaranteed unique. gensym names are not permanent and will not be recognized if +read back in. +FEXPR +The qits in select are evaluated in sequence from left to right until one is found that +qi = +and the value of select is the value of the corresponding ei. If no such qi is found the +value of select is that of e. + +#### tempus -fugit [ ] : SUBR pseudo-function + +Executing this will cause a time statement to appear in the output. The value is +NIL. (tempus-fugit is for MIT users only.) + +#### load[] : SUBR pseudo-function + +Program control is given to the LISP loader which expects octal correction cards, +704 row binary cards, and a transfer card. + +#### plb[] : SUBR pseudo-function + +This is equivalent to pushing "LOAD CARDS " on the console in the middle of a LISP +program. + +#### reclaim[] : SUBR pseudo-function + +Executing this will cause a garbage collection to occur. The value is NIL. + +#### pause[] : SUBR pseudo-function + +Executing this will cause a program halt. Pushing START will cause the program +to continue, returning the value NIL. + +#### excise[x] : SUBR pseudo-function + +If x is NIL, then the compiler will be overwritten with free storage, If x is *T*, +then both the compiler and LAP will be overwritten by free storage. excise may be +executed more than once. The effect of excise[W'*] is somewhat unreliable. It is +recommended that before executing this pair, remprop [*;sYM] be executed. +dump [low;high; mode; title] : SUBR pseudo-function +dump causes memory to be dumped in octal. The dump is from location 1s to +location - high. If the mode is 0, then the dump is straight. If the mode is 1, the words +containing zero in the prefix and tag will be dumped as complement decrements and +addresses. This is convenient for examining list structure. +intern[x] : SUBR pseudo-function +The argument of intern must be a PNAME type of structure, that is, a list of full +words forming a print name. If this print name belongs to an already existing atomic +symbol, this is found, otherwise a new one is created. The value of intern in either +case is an atomic symbol having the specified print name. +r emob[x] : SUBR +This removes the atom x from the object list. It causes the symbol and all its +properties to be lost unless the symbol is referred to by active list structure. When +an atomic symbol has been removed, subsequent reading of its name from input will +create a different atomic symbol. + +### The LISP Library + +The LISP Library is distributed as the second file on the LISP setup tape. To use +any part of it, punch out the entire library and remove the part you wish to use. Be + +sure to strip off comment cards, unnecessary DEFINE cards, and unnecessary cards +that close a define with )). +Some entries in the library have several parts or define more than one function. +EXPR pseudo-function +traceset is a debugging aid. The argument x should be a list of functions names. +Each of these functions must be an EXPR which has a PROG on the top level. traceset +modifies the definition of the function so that every SETQ on the first level inside the +PROG is traced. +For example, suppose a PROG has the statement (SETQ A X). At run time, if this +statment is executed while x has the value (U V), then in addition to setting the variable +a, the function will print out: + +(A =) +(U V) + +untraceset[x] is part of the traceset package. Its argument is a list of functions whose +definitions are to be restored to their original condition. + +punchlap[ 1 EXPR pseudo-function + +punchlap allows one to compile functions and have the results punched out in assem- +bly language LAP. The punched output is in a format suitable for readlap. The func - +tions to be compiled into LAP are placed at the end of the packet following the STOP +card. Each one is read individually and the result is punched out. No assembling into +memory takes place. The process stops when a card containing the word NIL is encoun- +tered after the last function. +Each function must consist of a list of the form (name exp) which is the exact form +for insertion into a define. +Part of punchlap is a dummy definition of - lap. This prevents - lap from being used +within the memory of this packet. The printout from punchlap is not a copy of the + +cards produced; only the internal functions have their LAP printed. The PNAMEs of +atoms in the EXPRs and FEXPRs of punchlapped functions must not contain class C +characters. +pr intpr op[x] EXPR pseudo-function + +If x is an atomic symbol, all of its properties will be printed in the output. Nothing +is changed by printprop. + +punchdef [x] EXPR pseudo-function + +If x is a list of atomic symbols, each one having an EXPR or FEXPR will have its +definition punched out. Nothing is changed. + +APVAL1s +The following is a list of all atoms with APVALts on their property lists in the basic +system and their values. + +APVAL + +BLANK +CHARCOUNT +COMMA +CURCHAR +DOLLAR +EOF +EOR +EQSIGN +F +LPAR +NIL +OBLIST +PERIOD +PLUSS +RPAR +SLASH +STAR +T +*T* + +value + +(BCD blank) +(character count during reading of characters) +J +(current character during reading of characters) +$ +$EOF $ +$EOR$ + +NIL +( +NIL +(bucket sorted object list) + +1. The entire set of objects (atomic symbols) existing in the system can be printed out + by performing + EVAL (OBLIST NIL). + +page 70 + +## APPENDIX B : THE LISP INTERPRETER + +This appendix is written in mixed M-expressions and English. Its purpose is to describe as closely as possible the actual working of the interpreter and PROG feature. The functions `evalquote`, `apply`, `eval`, `evlis`, `evcon`, and the `PROG` feature are defined by using a language that follows the M-expression notation as closely as possible and contains some insertions in English. + +```mexpr +evalquote[fn; args]=[get[fn; FEXPR] v get[fn; FSUBR] -> eval[cons [ fn; args]; NIL]; + T -> apply[fn; args; NIL] +``` + +This definition shows that `evalquote` is capable of handling special forms as a sort of exception. Apply cannot handle special forms and will give error `A2` if given one as its first argument. + +The following definition of `apply` is an enlargement of the one given in Section I. It shows how functional arguments bound by FUNARG are processed, and describes the way in which machine language subroutines are called. + +In this description, `spread` can be regarded as a pseudo-function of one argument. This argument is a list. `spread` puts the individual items of this list into the AC, MQ, +$ARG3,... the standard cells *[general purpose registers]* for transmitting arguments to functions. + +These M-expressions should not be taken too literally. In many cases, the actual +program is a store and transfer where a recursion is suggested by these definitions. + +```mexpr +apply[fn; args; a]=[ + null[fn] -> NIL; + atom[fn] -> [get[fn; EXPR] -> apply[expr ; args ; a]; + get[fn; subr] -> {spread[args]; + $ALIST := a; + TSX subr, 4}; + T -> apply[cdr[sassoc[fn; a; lambda[[];error [A2]]]]; args ;a]; + eq[car[fn]; LABEL] -> apply[caddr[fn];args; + cons[cons[cadr[fn]; + caddr[fn]];a]]; + eq[car[fn]; FUNARG] -> apply[cadr[fn]; args; caddr[fn]]; + eq[car [fn]; LAMBDA] -> eval[caddr[fn]; + nconc [pair[cadr[fn]; args]; a]]; + T -> apply[eval[fn; a]; args; a]] +``` +*NOTE THAT the formatting of this MEXPR is beyond the capabilities of Markdown to reproduce; this is a rational reconstruction, but to be perfectly certain of the interpretation consult the PDF* + + + +----- + +1. The value of get is set aside. This is the meaning of the apparent free or undefined variable. + +page 71 + +```mexpr +eval[form; a]= [ + null[form] -> NIL; + numberp[form] -> form; + atom[form] -> [get[form; APVAL] -> car[apval]; + T -> cdr[sassoc[form; a; lambda[[ ]; error[A8]]]]]; + eq[car[form]; QUOTE] -> cadr[form]; + eq[car[form]; FUNCTION] -> list[FUNARG; cadr[form]; a]; + eq[car [form]; COND] -> evcon[cdr[form]; a]; + eq[car [form]; PROG] -> prog[cdr[form]; a]; + atom[car[form]] -> [get[car [form]; EXPR] -> + apply[expr; evlis[cdr[form]; a]; a]; + get[car[form]; FEXPR] -> + apply[fexpr; list[cdr[form]; a]; a]; + get[car[form]; SUBR] -> {spread[evlis[cdr[form]; a]]; + $ALIST := a; + TSX subr 4}; + get[car[form]; FSUBR] -> {AC := cdr[form]; + MQ := $ALIST := a; + TSX fsubr 4} + T -> eval[cons[cdr[sassoc[car[form]; a; + lambda[[];error[A9]]]]; + cdr[form]]; a]]; + T-apply [car [form];evlis [cdr [form]; a]; a]] + +evcon[c; a] = [null[c] -> error[A3]; + eval[caar[c]; a] -> eval[cadar[a]; a]; + T -> evcon[cdr[ c]; a]] + +evlis[m; a] = maplist[m; lambda[[j]; eval[car[j]; a]]] +``` +### The PROG Feature + +The PROG feature is an FSUBR coded into the system. It can best be explained in +English, although it is possible to define it by using M-expressions. + +1. As soon as the PROG feature is entered, the list of program variables is used +to make a new list in which each one is paired with NIL. This is then appended to the current a-list. Thus each program variable is set to NIL at the entrance to the program. +2. The remainder of the program is searched for atomic symbols that are under- +stood to be location symbols. A go-list is formed in which each location symbol is paired with a pointer into the remainder of the program. +3. When a set or a setq - is encountered, the name of the variable is located on the a-list. The value of the variable (or cdr of the pair) is actually replaced with the new value. + +----- +1. The value of get is set aside. This is the meaning of the apparent free or undefined variable. +2. In the actual system this is handled by an FSUBR rather than as the separate special case as shown here. + +page 72 + +If the variable is bound several times on the a-list, only the first or most recent occurrence is changed. If the current binding of the variable is at a higher level than the entrance to the prog, then the change will remain in effect throughout the scope of that binding, and the old value will be lost. + +If the variable does not occur on the a-list, then error diagnostic `A4` or `A5` will occur. + +4. When a return is encountered at any point, its argument is evaluated and returned as the value of the most recent prog that has been entered. +5. The form go may be used only in two ways. + a. `(GO X)` may occur on the top level of the prog, `x` must be a location symbol of this `prog` and not another one on a higher or lower level. + b. This form may also occur as one of the value parts of a conditional expression, if this conditional expression occurs on the top level of the `prog`. + If a `go` is used incorrectly or refers to a nonexistent location, error diagnostic `A6` will occur. + +6. When the form cond occurs on the top level of a `prog`, it differs from other + `cond`s in the following ways. + a. It is the only instance in which a `go` can occur inside a `cond`. + b. If the `cond` runs out of clauses, error diagnostic `A3` will not occur. Instead, the `prog` will continue with the next statement. + +7. When a statement is executed, this has the following meaning, with the exception + of the special forms `cond`, `go`, `return`, `setq` and the pseudo-function `set`, all of which are peculiar to `prog`. + The statement `s` is executed by performing `eval[s;a]`, where `a` is the current a-list, and then ignoring the value. + +8. If a prog runs out of statements, its value is NIL. + When a prog - is compiled, it will have the same effect as when it is interpreted, although the method of execution is much different; for example, a go is always cornpiled as a transfer. The following points should be noted concerning declared variables.1 + 1. Program variables follow the same rules as h variables do. + a. If a variable is purely local, it need not be declared. + b. Special variables can be used as free variables in compiled functions. They may be set at a lower level than that at which they are bound. + c. Common program variables maintain complete communication between compiled programs and the interpreter. + + 2. & as distinct from setq can only be used to set common variables. + + +----- +1. See Appendix D for an explanation of variable declaration. + +page 73 + +## APPENDIX C : THE LISP ASSEMBLY PROGRAM (LAP) + +lap is a two-pass assembler. It was specifically designed for use by the new com- +piler, but it can also be used for defining functions in machine language, and for making +patches. + +* lap is an entirely internal assembler. Its input is in the form of an S-expression +that remains in core memory during the entire assembly. No input tape is moved during +the assembly. - lap does not produce binary output in the form of cards. It assembles +directly into memory during the second pass. + +Format + +* lap is a pseudo-function with two arguments. The first argument is the listing, the +second argument is the initial symbol table. The value of lap is the final symbol table. +The first item of the listing is always the origin. All remaining items of the listing +are either location symbols if they are atomic symbols other than NIL, or instructions +if they are composite S-expressions or if they are NIL. + +Origin + +The origin informs the assembler where the assembly is to start, and whether it +is to be made available as a LISP function. The origin must have one of the following +formats. + +1. If the origin is an octal or decimal number, then the assembly starts at that +locat ion. +2. If the origin is an atomic symbol other than NIL, then this symbol must have +a permanent value (SYM) on its property list. The value of this SYM is a number speci- +fying the starting location. +3. If the origin is NIL, then the assembly will start in the first available location +in binary program space. If the assembly is successfully completed, then the cell spec- +ifying the first unused location in binary program space is updated. If the assembly +cannot fit in binary program space, an error diagnostic will be given. +4. If the origin is of the form (name type n), then the assembly is in binary pro- +gram space as in the case above. When the assembly is completed, the indicator, type, +is placed on the property list of the atomic symbol name. Following the indicator is a +pointer to a word containing TXL, the first location of the program just assembled in +the address, and the number n in the decrement. type is usually either SUBR or +FSUBR. n is the number of arguments which the subroutine expects. + +Symbols + +Atomic symbols appearing on the listing (except NIL or the first item on the listing) + +page 74 + +are treated as location symbols. The appearance of the symbol defines it as the location +of the next instruction in the listing. During pass one, these symbols and their values +are made into a pair list, and appended to the initial symbol table to form the final sym- +bol table. This is used in pass two to evaluate the symbols when they occur in instruc- +tions. It is also the value of lap. +Symbols occurring on this table are defined only for the current assembly. The +symbol table is discarded after each assembly. +Permanent symbols are defined by putting the indicator SYM followed by a pointer +to a value on their property lists. + +Instructions + +Each instruction is a list of from zero to four fields. Each field is evaluated in the +same manner; however, the fields are combined as follows. + +1. The first field is taken as a full word. +2. The second field is reduced algebraically modulo 2 15, and is OR1ed into the +address part of the word. An arithmetic -1 is reduced to 77777Q. +3. The third field is shifted left 15 bits, and then OR1ed into the word. A tag of +four is written "4". A tag of 2 in an instruction with indirect bits is written "602Qt1. +4. The fourth field is reduced modulo 215 and is OR1ed into the decrement. + +- Fields +Fields are evaluated by testing for each of the following conditions in the order listed,. + +1. If the field is atomic. +a. The atomic symbol NIL has for its value the contents of the cell $ORG. +During an assembly that is not in binary program space, this cell contains the starting +address of the next assembly to go into binary program space. +b. The atomic symbol * has the current location as its value. +c. The symbol table is searched for an atomic symbol that is identical to the +field. +d. If the field is a number, then its numerical value is used. +e. The property list of the atomic field is searched for either a SYM, a SUBR, +or an FSUBR. +2. If the field is of the form (E a ), then the value of the field is the complement of +the address of the S-expression a. The expression a is protected so that it can never +be collected by the garbage collector. +3. If the field is of the form (QUOTE a ), then a literal quantity containing a in +the decrement is created. It is the address of this quantity that is assembled. Quoted +S-expressions are protected against being collected by the garbage collector. A new +literal will not be created if it is equal to one that already exists. +4. If the field is of the form (SPECIAL x), then the value is the address of the +SPECIAL cell on the property list of x. If one does not already exist, it will be created. + +page 75 + +The SPECIAL cell itself (but not the entire atom) is protected against garbage collection. + +5. In all other cases, the field is assumed to be a list of subfields, and their sum +is taken. The subfields must be of types 14 above. + +Error Diagnostics +*L 1* Unable to determine origin. No assembly. +*L 2* Out of binary program space. Second pass cancelled. +*L 3 * Undefined symbol. Assembly incomplete. +*L 4* Type five field contains type five fields inside itself. Assembly incomplete. + +Opdef ine +opdefine is a pseudo-function for defining new quantities for LAP. It puts a SYM +on the property list of the symbol that is being defined. Its argument is a list of pairs. +Each pair is a symbol and its numerical value. Note that these pairs are not "dotted +pairs. + +Example +OPDEFINE ( ( (CLA 500Q8) +(TRA 2Q9) +(LOAD 1000) +(OVBGN 7432Q) ) ) +The following op-codes are defined in the standard system: +AXT +CLA +LDQ +LXA +LXD +PAX +PDX +Examples of the Use of LAP +PXA +PXD +STD +ST0 +STQ +STR +STZ +SUB +SXA +SXD +TIX +Trn +TNX +TNZ +TRA +TSX +TXH +TXI +TXL +TZE +XCA + +Example 1: A LISP function +The predicate greater induces an arbitrary canonical order among atomic symbols. +LAP ( ( (GREATER SUBR 2) (TI& (* 3)) (PXA 0 0) +(TRA 1 4) (CLA (QUOTE *T* ) ) (TRA 1 4) )NIL) +Example 2: A patch + +The instruction TSX 6204Q must be inserted after location 62 17Q.^62 17Q contains +CIA 6243Q and this instruction must be moved to the patch. + +LAP ( (6217Q (TRA NIL) )NIL) +LAP ( (NIL (CLA A) (TSX 6204Q) (TRA B) ) +( (A 6243Q) (B 6220Q) ) ) + +page 76 + +## APPENDIX D : THE LISP COMPILER + +The LISP Compiler is a program written in LISP that translates S-expression defi- +nitions of functions into machine language subroutines. It is an optional feature that +makes programs run many times faster than they would if they were to be interpreted +at run time by the interpreter. +When the compiler is called upon to compile a function, it looks for an EXPR or +FEXPR on the property list of the function name. The compiler then translates this +S-expression into an S-expression that represents a subroutine in the LISP Assembly +Language (LAP). LAP then proceeds to assemble this program into binary program +space. Thus an EXPR, or an FEXPR, has been changed to a SUBR or an FSUBR, +respectively. +Experience has shown that compiled programs run anywhere from 10 to 100 times +as fast as interpreted programs, the time depending upon the nature of the program. +Compiled programs are also more economical with memory than their corresponding +S-expressions, taking only from 50 per cent to 80 per cent as much space.1 +The major part of the compiler is a translator or function from the S-expression +function notation into the assembly language, LAP. The only reasons why the compiler +is regarded as a pseudo-function are that it calls LAP, and it removes EXPRts and +FEXPR1s when it has finished compiling. + +The compiler has an interesting and perhaps unique history. It was developed in +the following steps: + +1. The compiler was written and debugged as a LISP program consisting of a set +of S-expression definitions of functions. Any future change or correction to the com- +piler must start with these definitions; therefore they are included in the LISP Library. +2. The compiler was commanded to compile itself. This operation is called boot - +strapping. It takes more than 5 minutes on the IBM 7090 computer to do this, since +most parts of the compiler are being interpreted during most of this time. +3. To avoid having to repeat the slow bootstrapping operation each time a system +tape is created, the entire compiler was punched out in assembly language by using +punc hlap. +4. When a system tape is to be made, the compiler in assembly language is read +in by using readlap. + +The compiler is called by using the pseudo-function compile. The argument of compile is a list of the names of functions to be compiled. Each atomic symbol on this list +should have either an EXPR or an FEXPR on its property list before being compiled. +The processing of each function occurs in three steps. First, the S-expression for +the function is translated into assembly language. If no S-expression is found, then the +compiler will print this fact and proceed with the next function. Second, the assembly + +----- +1. Since the compiled program is binary program space, which is normally +not otherwise accessible, one gains as free storage the total space formerly occupied +by the S-expression definition. + +page 77 + +language program is assembled by LAP. Finally, if no error has occurred, then the +EXPR or FEXPR is removed from the property list. When certain errors caused by +undeclared free variables occur, the compiler will print a diagnostic and continue. +This diagnostic will be spuriously produced when programs leaning on APVALs are +compiled. +When writing a large LISP program, it is better to debug the individual function defi- +nitions by using the interpreter, and compile them only when they are known to work. +Persons planning to use the compiler should note the following points: + +1. It is not necessary to compile all of the functions that are used in a particular +run. The interpreter is designed to link with compiled functions. Compiled functions +that use interpreted functions will call the interpreter to evaluate these at run time. +2. The order in which functions are compiled is of no significance. It is not even +necessary to have all of the functions defined until they are actually used at run time. +(Special forms are an exception to this rule. They must be defined before any function +that calls them is compiled. ) +3. If the form LABEL is used dynamically, the resulting function will not compile +properly. +4. Free variables in compiled functions must be declared before the function is +compiled. This is discussed at length in this appendix. + +Excise + +The compiler and the assembler LAP can be removed from the system by using +the pseudo-function excise. If excise [NIL] is executed, then the compiler will be +removed. If excise [*T*] is executed, then the compiler and LAP will both be excised. +One may execute excise [NIL] and then excise [*T*] at a later time. When a portion +of the system is excised, the region of memory that it occupied is converted into addi- +tional free-storage space. + +Free Variables +A variable is bound in a particular function when it occurs in a list of bound vari- +ables following the word LAMBDA or PROG. Any variable that is not bound is free. + +Example + +(LAMBDA (A) (PROG (B) +S (SETQ B A) +(COND ((NULL B) (RETURN C))) +(SETQ C (CONS (CAR A) C)) +(GO S) )) +A and B are bound variables, C is a free variable. +When a variable is used free, it must have been bound by a higher level function. +If a program is being run interpretively, and a free variable is used without having been +bound on a higher level, error diagnostic *A^89 will occur. + +page 78 + +If the program is being run compiled, the diagnostic may not occur, and the variable +may have value NIL. +There are three types of variables in compiled functions: ordinary variables, +SPECIAL variables, and COMMON variables. SPECIAL and COMMON variables must +be declared before compiling. Any variable that is not declared will be considered an +ordinary variable. +When functions are translated into subroutines, the concept of a variable is trans- +lated into a location where an argument is stored. If the variable is an ordinary one, +then a storage location for it is set up on the push-down list. Other functions cannot +find this private cell, making it impossible to use it as a. free variable. +SPECIAL variables have the indicator SPECIAL on their property lists. Following +the indicator there is a pointer to a fixed cell. When this variable is bound, the old +value is saved on the push-down list, and the current value is stored in the SPECLAL +cell. When it is no longer bound, the old value must be restored. When a function uses +this variable free, then the quantity in the SPECIAL cell is picked up. +SPECIAL variables are declared by using the pseudo-function special[a], where a +is a list of variable names. This sets up the SPECIAL indicator and creates a SPECIAL +cell. Both the indicator and the cell can be removed by the pseudo-function unspecial[a], +where a is a list of variable names. It is important that the declaration be in effect +at compile time. It may be removed at run time. +The compiler refers to SPECIAL cells, using the LAP field (SPECIAL X) whose +value is the address of the SPECIAL cell. When a variable has been declared, removed, +and then declared again, a new cell is created and is actually a different variable. +SPECIAL variables are inexpensive and will allow free communication among com- +piled functions. They do not increase run time significantly. SPECIAL variables cannot +be communicated between the interpreter and compiled functions. +COMMON variables have the flag COMMON on their property lists; however, this +is only used to inform the compiler that they are COMMON, and is not needed at run +time. COMMON variables are bound on an a-list by the compiled functions. When they +are to be evaluated, is given this a-list. This happens at run time. +The use of COMMON variables will slow down any compiled function using them. +However, they do provide complete communication between interpreted and compiled +functions. +COMMON variables are declared by common[a], where a is a list of variable names. +The declaration can be removed by uncommon[a], where a is a list of variable names. + +Functional Constants +Consider the following definition of a function dot by using an S-expression: +(YDOT (LAMBDA (X Y) (MAPLIST X (FUNCTION +(LAMBDA (J) (CONS (CAR J) Y)) )))) + +page 79 + +Following the word FUNCTION is a functional constant. If we consider it as a sep- +arate function, it is evident that it contains a bound variable "Jtt, and a free variable +"Yfl. This free variable must be declared SPECIAL or COMMON, even though it is +bound in YDOT. + +### Functional Arguments + +MAPLIST can be defined in S-expressions as follows: +(MAPLIST (LAMBDA (L FN) (COND +((NULL L) NIL) +(T (CONS (FN L) (MAPLIST (CDR L) FN))) ))) +The variable FN is used to bind a functional argument. That is, the value of FN +is a function definition. This type of variable must be declared COMMON. + +### Link + +Link is the routine that creates all linkage of compiled functions at run time. +The normal procedure for calling a compiled function is to place the arguments in +the AC, MQj $ARG3,. .. and then to TSX FN,4. However, the first time any call is +executed, there will be an STR in place of the TSX. The address and the decrement +of the STR specify the name of the function that is being called, and the number of argu- +ments that are being transmitted, respectively. The tag contains a 7. If there is no +SUBR or FSUBR for the function that is being called, then link will call the interpreter +that may find an EXPR or FEXPR. If there is a subroutine available, then link will +form the instruction TSX and plant this on top of the STR. + +### Tracing Compiled Functions + +- trace will work for compiled functions, subject to the following restrictions. + 1. The trace must be declared after the function has been compiled. + +2. Once a direct TSX link is made, this particular calling point will not be traced. +(Link will not make a TSX as long as the called function is being traced. ) + +page 80 + +## APPENDIX E : OVERLORD - THE MONITOR + +Overlord is the monitor of the LISP System. It controls the handling of tapes, the +reading and writing of entire core images, the historical memory of the system, and +the taking of dumps. +The LISP System uses 5 tape drives. They are listed here by name together with +their customary addresses. + +SYSTAP Contains the System B7 +SYSTMP Receives the Core Image B3 +SYSPIT Punched Card Input A2 +SYSPOT Printed Output A3 +SYSPPT Punched Card Output A4 +The system tape contains a small bootstrap record with a loader, followed by a very +long record that fills up almost all of the core memory of the machine. +The system is called by the two-card LISP loader which is placed in the on-line card +reader. Octal corrections may be placed between the two cards of this loader. The +format of these will be specified later. +The first loader card causes SYSTAP to be selected and the entire memory is imme- +diately filled. Control then goes to a loader that reads octal correction cards until it +recognizes the second loader card which is a binary transfer card to Overlord. +Overlord reads cards from the input looking for Overlord direction cards. Other +cards are ignored except for the first one which is printed in the output. +Overlord cards either instruct the monitor to perform some specific function or +else signal that a packet of doublets for evaluation is to follow immediately. +Before any packet is read, the entire system is read out onto SYSTMP. It is written +in the same format as SYSTAP, and in fact is a copy of it. After each packet, one of +two things may happen. Either a complete core image is read from SYSTMP, and thus +memory is restored to the condition it was in before the packet was read, or the state +of memory at the finish of the packet is read out onto SYSTMP. In the latter case, all +function definitions and other memory changes are preserved. + +Card Format + +Octal correction cards can alter up to 4 words of memory per card. Each change +specifies an address (5 octal digits) and a word to be placed there (12 octal digits). The +card columns to use are as follows. + +address data word + +page 81 + +Overlord cards have the Overlord direction beginning in column 8. If the card has +no other field, then comments may begin in column 16. Otherwise, the other fields of +the card begin in column 16 and are separated by commas. The comments may begin +after the first blank past column 16. + +### Overlord Cards + +#### TAPE SYSPPT, B4 + +The TAPE Overlord card defines the actual drives to be assigned to the tapes. The +system uses five tapes designated by the names SYSTAP, SYSTMP, SYSPIT, SYSPOT, +and SYSPPT. The actual tape units may range from A0 through C9. + +#### SIZE N1, N2, N3, N4 + +The size card specifies the amount of storage to be allocated to binary program +space, push-down, full words, and free storage in that order. The SIZE card must be +used only once at the time when the system is created from a binary card deck. The +fields are octal or decimal integers. + +#### DUMP Ll,L2,0 + +This Overlord card causes an octal dump of memory to be printed. The first two +fields are octal or decimal integers specifying the range of the dump. The third field +specifies the mode. 0 mode specifies a straight dump. 1 mode specifies that if the +prefix and tag areas of a word are zero, then the complements of the address and decre- +ment are dumped instead. + +#### TEST + +Specifies that a packet is to follow and that memory is to be restored from SYSTMP +after the packet has been evaluated. + +#### TST + +Same as TEST + +#### SET + +The SET card specifies that a packet is to follow and that the memory state following +the evaluation of the packet is to be set onto SYSTMP. If an error occurs during the +evaluation of the packet, then the memory is to be restored from SYSTMP instead. + +#### SETSET + +The SETSET card is like SET except that it sets even if there has been an error. + +#### DEBUG + +This direction is like TEST except that after the doublets have been read in the entire +object list is thrown away, making it impossible to do any further reading (except of +numbers). This makes a considerable amount of free storage available but may cause +trouble if certain atoms that are needed are not protected in some manner. + +#### FIN + +Causes the computer to halt. An end of file mark is written on SYSPOT. An end +of file is written on SYSPPT only if it has been used. If the FIN card was read on-line, +the computer halts after doing these things. If the FIN card came from SYSPIT, then + +page 82 + +SYSPIT is advanced past the next end of file mark before the halt occurs. + +Use of Sense Switches +1 Up-Input comes from SYSPIT. +Down-Input comes from the card reader. +The loader cards and octal correction cards always go on-line. +3 Up-No effect +Down-All output that is written onto either SYSPOT or SYSPPT will also appear +on the on-line printer. +5 Up-No effect +Down-Suppresses output normally written on SYSPOT and SYSPPT. +These switches are interrogated at the beginning of each record. +6 Up-The instruction STR will cause the interpreter to give error diagnostic F 5 +and continue with the next doublet. +Down-The instruction STR will cause control to go to Overlord immediately. +The normal terminating condition of a LISP run is an HPR 77777,7 with all bits of +AC and MQ filled with ones. To return control to Overlord from this condition, push +RESET then START. +After a LISP run, the reel of tape that has been mounted on the SYSTMP drive has +become a system tape containing the basic system plus any changes that have been set +onto it. It may be mounted on the SYSTAP drive for some future run to use definitions +that have been set onto it. + +page 83 + +## APPENDIX F : LISP INPUT AND OUTPUT + +This appendix describes the LISP read and write programs and the character- +manipulation programs. The read and write programs allow one to read and write +S-expressions. The character-manipulating programs allow one to read and write indi- +vidual characters, to process them internally, to break atomic symbols into their con- +stituent characters, and to form atomic symbols from individual characters. +The actual input/output routines are identical for both the LISP read and write, and +the character read and write. Input is always from either SYSPIT or the card reader. +Printed output is always written on SYSPOT and/or the on-line printer. Punched output +is always on SYSPPT and/or the on-line printer. The manner in which these choices +are controlled was described in Appendix E. + +LISP READ PRINT and PUNCH + +The LISP read program reads S-expressions in the form of BCD characters and +translates them into list structures. It recognizes the delimiters ll(lland'l)ll and the +separators. It, and (blank). The comma and blank are completely equivalent. +An atomic symbol, when read in, is compared with existing atomic symbols. If it +has not been encountered previously, a new atomic symbol with its property list is +created. All atomic symbols except numbers and gensyms are on a list called the object +list. This list is made of sublists called buckets. The atomic symbols are thrown into +buckets by a hash process on their names. This speeds up the search that must occur +during reading. +For the purpose of giving a more extended definition of an atomic symbol than was +given in Section I, the 48 BCD characters are divided into the following categories. +ClassA ABC... Z=*/ +ClassB 0 12 34 5 6 78 9t -(ll punch) +Class C ( ) ,. (blank) +Class D $ +Class E - (4-8 punch) +The 4-8 punch should not be used. +Symbols beginning with a Class B character are interpreted as numbers. Some +sort of number conversion is attempted even if it does not make sense. +An ordinary atomic symbol is a sequence of up to 30 characters from classes A, B, +and Dj with the following restrictions. +a. The first character must not be from class B. +b. The first two characters must not be $ $. +c. It must be delimited on either side by a character from class C. +There is a provision for reading in atomic symbols containing arbitrary characters. + +page 84 + +This is done by punching the form $$dsd, where s is any string of up to 30 characters, +and d is any character not contained in the string s. Only the string s is used in +forming the print name of the atomic symbol; d and the dollar signs will not appear when +the atomic symbol is printed out. + +Examples +Input will print as +$ $XAX A +$ $O))( 1)) +$ $-w. )- IJV*) +$$/-*I -. +The operation of the read program is critically dependent upon the parsing of left +and right parentheses. If an S-expression is deficient in one or more right parentheses, ' +reading will continue into the next S-expression. An unmatched right parenthesis, or a +dot that is out of context, will terminate reading and cause an error diagnostic. +The read program is called at the beginning of each packet to read doublets for +evalquote until it comes to the S-expression STOP. read may also be used explicitly +by the programmer. In this case, it will begin reading with the card following the STOP +card because the read buffer is cleared by evalquote after the doublets and STOP have +been read. After this, card boundaries are ignored, and one S-expression is read each +time read is called. read has no arguments. Its value is the S-expression that it reads. +The pseudo-functions print and punch write one S-expression on the printed or +pwched output, respectively. In each case, the print or punch buffer is written out +and cleared so that the next section of output begins on a new record. +prinl is a pseudo-function that prints its argument, which must be an atomic sym- +bol, and does not terminate the print line (unless it is full). +terpri prints what is left in the print buffer, and then clears it. + +### Characters and Character Objects + +Each of the sixty-four 6-bit binary numbers corresponds to a BCD character, if we +include illegal characters. Therefore, in order to manipulate these characters via LISP +functions, each of them has a corresponding object. Of the 64 characters, 48 corre- +spond to characters on the key punch, and the key-punch character is simply that +character. The print names of the remaining characters will be described later. When +a LISP function is described which has a character as either value or argument, we +really mean that it has an object corresponding to a character as value or argument, +respectively. +The first group of legal characters consists of the letters of the alphabet from A +to Z. Each letter is a legitimate atomic symbol, and therefore may be referred to in +a straightforward way, without ambiguity. +The second group of legal characters consists of the digits from 0 to 9. These +must be handled with some care because if a digit is considered as an ordinary integer + +page 85 + +rather than a character a new nonunique object will be created corresponding to it, and +this object will not be the same as the character object for the same digit, even though +it has the same print name. Since the character-handling programs depend on the char- +acter objects being in specific locations, this will lead to error. +The read program has been arranged so that digits 0 through 9 read in as the cor- +responding character objects. These may be used in arithmetic just as any other num- +ber but, even though the result of an arithmetic operation lies between 0 and 9, it will +not point to the corresponding character object. Thus character objects for 0 through +9 may be obtained only by reading them or by manipulation of print names. +The third group of legal characters is the special characters. These correspond +to the remaining characters on the key punch, such as $ and "= ". Since some of these +characters are not legitimate atomic symbols, there is a set of special character-value +objects which can be used to refer to them. +A typical special character -value object, say DOLLAR, has the following structure + +* 1 PNAME I APVAL + I I + +Thus "DOLLAR has value " $ ". +The special character value objects and their permanent values are: +DOLLAR +SLASH +LPAR +RPAR +COMMA +PERIOD +PLUSS +DASH +STAR +BLANK +EQSIGN + + +) + +i + +* (11 punch) + +* + +blank + - - + +The following examples illustrate the use of their objects and their raison datre. +Each example consists of a doublet for evalquote followed by the result. + +Examples + +EVAL (DOLLAR NIL) value is " $ +EVAL ((PRINT PERIOD) NIL) value is ". and If. is also printed. + +page 86 + +The remaining characters are all illegal as far as the key punch is concerned. The +two characters corresponding to 12 and 72 have been reserved for end-of-file and end- +of-record, respectively, The end-of-file character has print name $EOF$ and the end- +of-record character has print name $EOR $; corresponding to these character objects +are two character value objects EOR and EOF, whose values are $EOR $ and $EOF$ +respectively. The rest of the illegal character objects have print names corresponding +to their octal ~epresentations preceded by $IL and followed by $. For instance, the +character 77 corresponds to a character object with print name $IL77$. +The character objects are arranged in the machine so that their first cells occupy +successive storage locations. Thus it is possible to go from a character to the corre- +sponding object or conversely by a single addition or subtraction. This speeds up +character-handling considerably, because it isn't necessary to search property lists +of character objects for their print names; the names may be deduced from the object +locations. + +### Packing and Unpacking Characters + +When a sequence of characters is to be made into either a print name or a numerical +object, the characters must be put one by one into a buffer called BOFFO. BOFFO is +used to store the characters until they are to be combined. It is not available explicitly +to the LISP programmer, but the character-packing functions are described in terms +of their effects on BOFFO. At any point, BOFFO contains a sequence of characters. +Each operation on BOFFO either adds another character at the end of the sequence or +clears BOFFO, i. e., sets BOFFO to the null sequence. The maximum length of the +sequence is 120 characters; an attempt to add more characters will cause an error. +The character-packing functions are: + +1. pack [c] - : SUBR pseudo-function + The argument of packmust be a character object. - pack adds the character + c at the end of the sequence of characters in BOFFO. The value of pack - is NIL. +2. clearbuff [ ] SUBR pseudo -func tion +clearbuff is a function of no arguments. It clears BOFFO and has value NIL. +The contents of BOFFO are undefined until a clearbuff has been performed. +3. mknam [ ] SUBR pseudo -function +mknam is a function of no arguments. Its value is a list of full words con- +taining the characters in BOFFO in packed BCD form. The last word is filled +out with the illegal character code 77 if necessary. After mknam is performed, +BOFFO is automatically cleared. Note that intern [mknam[ ]I yields the object +whose print name is in BOFFO. +4. numob [ ] ' SUBR pseudo -function + numob is a function of no arguments. Its value is the numerical object repre- + sented by the sequence of characters in BOFFO. (Positive decimal integers from + 0 to 9 are converted so as to point to the corresponding character object. ) + +page 87 + +5. unpack [x]: SUBR pseudo-function + This function has as argument a pointer to a full word. unpack considers + the full word to be a set of 6 BCD characters, and has as value a list of these + char act er s ignoring all characters including and following the first 77. +6. h~tern[~name] : SUBR pseudo-function + This function has as argument a pointer to a PNAME type structure such as - + +Its value is the atomic symbol having this print name. If it does not already +exist, then a new atomic symbol will be created. + +### The Character-Classifying Predicates + +*. - liter [c]: SUBR predicate + * liter has as argument a character object. Its value is T if the character +is a letter of the alphabet, and F otherwise. +*. - digit [c]: SUBR predicate + +- digit has as argument a character object. Its value is T if the character +is a digit between 0 and 9, and F otherwise. + +3. opchar [c]: SUBR predicate +opchar has as argument a character object. Its value is T if the character +is t, -, /, *, or =, and F otherwise. opchar treats both minus signs equiva- +lently. +*. - dash [c]: SUBR predicate + +- dash has as argument a character object. Its value is T if the character +is either an 11-punch minus or an 84 punch minus, and F otherwise. + +### The Character -Reading Functions + +The character-reading functions make it possible to read characters one by one from +input. +There is an object CURCHAR whose value is the character most recently read (as +an object). There is also an object CHARCOUNT whose value is an integer object giving +the column just read on the card, i. e., the column number of the character given by +CURCHAR. There are three functions which affect the value of CURCHAR: + +#### 1. startread [ ] : SUBR ps eudo-function +startread is a function of no arguments which causes a new card to be read. The value of startread is the first character on that card, or more precisely, + +page 88 + +the object corresponding to the first character on the card. If an end-of-file +condition exists, the value of startread is $EOF$. The value of CURCHAR +becomes the same as the output of startread, and the value of CHARCOUNT +becomes 1. Both CURCHAR and CHARCOUNT are undefined until a startread +is performed. A startread may be performed before the current card has been +completely read. + +#### 2. advance [ ] : SUBR pseudo -function + +advance is a function of no arguments which causes the next character to be read. The value of advance is that character. After the 72nd character on the card has been read, the next advance will have value $EOR$. After reading $EOR$, the next advance will act like a startread, i. e., will read the first char acter of the next card unless an end-of-file condition exists. The new value of CURCHAR is the same as the output of advance; executing advance also increases the value of CHARCOUNT by 1. However, CHARCOUNT is undefined when CURCHAR is either $EOR $ or $EOF $. + +#### 3. endread [ ] : SUBR pseudo-function + +endread is a function of no arguments which causes the remainder of the card to be read and ignored. endread sets CURCHAR to $EOR$ and leaves CHARCOUNT undefined; the value of endread is always $EOR $. An advance following endread acts like a startread. If CURCHAR already has value $EOR $ and endread is performed, CURCHAR will remain the same and endread will, as usual, have value $EOR $. + +### Diagnostic Function + +#### error 1 [ ]: SUBR pseudo-function + +error1 is a function of no arguments and has value NIL. It should be executed +only while reading characters from a card (or tape). Its effect is to mark the char- +acter just read, i. e., CURCHAR, so that when the end of the card is reached, either +by successive advances or by an endread, the entire card is printed out along with +a visual pointer to the defective character. For a line consisting of ABCDEFG fol- +lowed by blanks, a pointer to C would look like this: + +``` + v +ABCDEFG + A +``` +If error 1 is performed an even number of times on the same character, the A will +not appear. If error1 is performed before the first startread or while CURCHAR +has value $EOR $ or $EOF $, it will have no effect. Executing a startread before +the current card has been completed will cause the error1 printout to be lost. The +card is considered to have been completed when CURCHAR has been set to $EOR$. +Successive endreads will cause the error l printout to be reprinted. Any number +of characters in a given line may be marked by error1. + +page 89 + +## APPENDIX G : MEMORY ALLOCATION AND THE GARBAGE COLLECTOR + +The following diagram shows the way in which space is allocated in the LISP System. + +| Address (octal) | Assigned to | +| --------------- | ------------------------------------------------------------ | +| 77777 | ----- | +| | Loader | +| 77600 | ----- | +| | LAP | +| | Compiler | +| 70000 | ----- | +| | Free storage | +| | Full words | +| | Pushdown list | +| | Binary program space | +| 17000 | | +| | Interpreter, I/O, Read Print, Arithmetic, Overlord, Garbage Collector, and other system coding | +| 00000 | | + +The addresses in this chart are only approximate. The available space is divided +among binary program space, push-down list, full-word space, and free-storage space +as specified on the SIZE card when the system is made. + +When the compiler and LAP are not to be used again, they may be eliminated by +executing the pseudo-function excise. This part of the memory is then converted into +free storage. + +Free storage is the area in the computer where list structures are stored. This +includes the property lists of atomic symbols, the definitions of all EXPRts and +FEXPR1s, evalquote doublets waiting to be executed, APVALts, and partial results of +the computation that is in progress. + +Full-word space is filled with the BCD characters of PNAMEts, the actual numbers + +page 90 + +of numerical atomic structures, and the TXL words of SUBRtsB FSUBRts, and SYMts. +All available words in the free-storage area that are not in use are strung together +in one long list called the free-storage list. Every time a word is needed (for example, +by s) the first word on the free-storage list is used, and the free-storage list is set +to & of what it formerly was. + +Full-word space is handled in the same way. No use is made of consecutive storage +in either of these areas of memory. They are both scrambled. + +When either of these lists is exhausted in the middle of a computation, the garbage +collector is called automatically. Unless the computation is too large for the system, +there are many words in free storage and full-word space that are no longer needed. +The garbage collector locates these by marking those words that are needed. In free +storage, the sign bit is used for marking. In full-word space, there is no room in the +word itself. Marking is done in a bit table which is next to full-word space. + +Since it is important that all needed lists be marked, the garbage collector starts +marking from several base positions including the following: + +1. The object list that includes all atomic symbols except numbers and generated names. This protects the atomic symbols, and all S-expressions that hang on the property lists of atomic symbols. +2. The portion of the push-down list that is currently being used. This protects partial results of the computation that is in progress. +3. The temlis, which is a list of registers scattered throughout the memory where binary programs store list structures that must be protected. + +Marking proceeds as follows. If the cell is in full-word space, then the bit table +is marked. If the cell is in free storage, then the sign is set minus, and car and & +of the cell are marked. If the cell is anywhere else, then nothing is done. +After the marking is done, the new available word lists are made by stringing all +unmarked words together. Finally, the signs in free storage are set plus. + +A garbage collection takes approximately 1 second on the IBM 7090 computer. It +can be recognized by the stationary pattern of the MQ lights. Any trap that prevents +completion of a garbage collection will create a panic condition in memory from which +there is no recovery. + +page 91 + +## APPENDIX H : RECURSION AND THE PUSH-DOWN LIST + +One of the most powerful resources of the LISP language is its ability to accept +function definitions that make use of the very function that is being defined. This may +come about either directly by using the name of the function, or indirectly through a +chain of function definitions that eventually return to the original ones. A definition of +this type is called recursive. Its power lies in its ability to define an algorithm in +terms of itself. + +A recursive definition always has the possibility of not terminating and of being +infinitely regressive. Some recursive definitions may terminate when given certain +inputs and not terminate for others. It is theoretically impossible to determine whether +a definition will terminate in the general case; however, it is often possible to show +that particular cases will or will not terminate. + +LISP is designed in such a way that all functions for which the possibility of recursion +can exist are in fact recursive. This requires that all temporary stored results related +to the computation that is in progress be set aside when a piece of coding is to be used +recursively, and that they be later restored. This is done autorrlatically and need not +be programmed explicitly. + +All saving of temporary results in LISP is performed on a linear block of storage +called the push-down list. Each set of stored data that is moved onto the push-down +list is in a block labeled with its size and the name of the subroutine from which it came. +Since it is in the nature of recursion that the first block to be saved is always the last +block to be restored, it is possible to keep the push-down list compact. + +The frontier of the push-down list can always be found by referring to the cell CPPI. +The decrement of this cell contains the complementary address of the first available +unused location on the push-down list. Index register 1 also contains this quantity, +except during certain nonrecursive subroutines; in these last cases it must be restored +upon leaving these routines. + +There are two types of blocks to be found on the push-down list, those put there by +SAVE, and those put there by MOVE. SAVE blocks are moved from fixed locations +in certain subroutines onto the push-down list, and then moved back to the place where +they came from by UNSAVE. Each block contains parameters that tell UNSAVE how +many words are to be moved, and where they are to be moved to. + +Functions compiled by the LISP compiler do not make use of storage cells located +near the actual programming. All data are stored directly on the push-down list and +referenced by using index register 1. MOVE is used to update CPPI and index regis- +ter 1, to place the arguments on the push-down list, and to set up the parameters for +the push-down block. + +Because pointers to list structures are normally stored on the push-down list, the + +page 92 + +garbage collector must mark the currently active portion of the push-down list during a +garbage collection. Sometimes quantities are placed on the push- down list which should +not be marked. In this case, the sign bit must be negative. Cells on the active portion +of the push-down list having a negative sign bit will not be marked. + +When an error occurs, an examination of the push-down list is an excellent indica- +tion of what was occurring at the time of the error. Since each block on the push-down +list has the name of the function to which it belongs, it is possible to form a list of +these names. This is called the backtrace, and is normally printed out after error +diagnostics. + +page 93 + +## APPENDIX I : LISP FOR SHARE DISTRIBUTION + +The Artificial Intelligence Project at Stanford University has produced a version of +LISP 1.5 to be distributed by SHARE. In the middle of February 1965 the system is +complete and is available from Stanford. The system should be available from SHARE +by the end of March 1965. + +SHARE LISP differs somewhat from the LISP 1.5 system described in the LISP 1.5 +Programmer's Manual, but only in (generally) inessential details. It is hoped that +the changes will be widely hailed as improvements. + +### Verbos and the Garbage Collector + +The garbage collector now prints its message in a single-spaced format; thus, the +amount of paper generated by a program with many constes is somewhat less than for- +merly. Furthermore, the garbage collector printout may be suspended by executing +"VERBOS(N1L)"; and the printout may be reinstated by executing flVERBOS(*T*)tI. + +### Flap Trap + +Every now and then a state of affairs known as floating-point trap occurs - this re- +sults when a floating-point arithmetic instruction generates a number whose exponent +is too large in magnitude for the eight-bit field reserved for it. When this trap oc- +curs and the offending exponent is negative, the obvious thing to do is to call the re- +sult zero. The old system, however, simply printed out a "FLAP TRAPtt error mes- +sage and went on to the next pair of S-expressions to be evaluated. The new system +stores a floating -point zero in the accumulator when an underflow occurs. (There +has, as yet, been no request to have "infinityIt stored in the accumulator when an +overflow occurs.) + +### Time + +The new system prints the time upon entering and leaving evalquote. In fact, two +times are printed, but in a neat, concise, impersonal manner which, it is felt, is +more suitable to the "age of automationu than the quote from Lewis Carroll. The +times are printed in minutes and milliseconds; the first time is the age of the packet - +by definition, this is zero when evalquote is first entered - and the second time is +the age of the system being used. Thus, when evalquote is left, the time printout +tells how much time was spent in the execution of the packet and how much time has +been spent in execution of SET or SETSET packets since the birth of the system plus +the time spent in the packet being finished. This time printout, to be meaningful, +requires the computer to have a millisecond clock in cell 5 (RPQ F 89349, with mil- +lisecond feature). + +page 94 + +It is also possible to determine how much time is required to execute a given func- +tion. llTIMEl()lt initializes two time cells to zero and prints out, in the same format +that is used for the evalquote time printout, two times, and these are both zero. +prints (again in the evalquote time printout format) the time since the last +execution of "TIME()" and the time since the last execution of "TIMEl()". The use +of the time and time1 functions has no effect on the times recorded by evalquote. + +### Lap and Symtab + +Heretofore, lap has not only returned the symbol table as its value but has printed +it out as well. This phenomenon is familiar to those who have much at all to do with + +-- lap or the compiler. The lap in the new system always prints the function name and +the octal location in which the first word of the assembled function is stored. (If the +assembled function is not a SUBR or FSUBR, then only the octal origin of the as- +sembled code is printed.) The printout is left-justified on the output page and has the +form tlpage 95 + +the arguments or value of a traced function are printed the number of function en- +trances will be printed; and if an error occurs, the number of function entrances ac- +complished before the error will be printed. + +The tracecount feature (or alarm-clock trace, as it is called by Marvin Minsky of +M. I. T.) enables a programmer to run a job (preceding the program by "TRACE- +COUNT(O)It), estimate the number of function entrances that occur before the pro- +gram generates an error condition or a wrong answer, and then run the job again, +tracing only the pertinent portion of the execution. + +### Space and Eject + +A small amount of additional control over the form of the data printed by LISP has +been provided in the space and eject functions. + +ttSPACE(*T*)tt causes all output to be double-spaced. nSPACE(NIL)u restores the +spacing to its original status; in particular, the output of the print routine reverts +to single -spacing, and the "END OF EVALQUOTE OPERATORnt printout again ejects +the page before printing. + +"EJECT()tt causes a blank line with a carriage control character of 1 to be printed +on the output tape. The result is a skip to the top of the next page of output. + +### Untime + +This routine is not available to the programmer, but its mention here may prevent +some anxiety. In the event that the program time estimate is exceeded during system +I/O, using the old system, one finds himself in the position of having part of one sys- +tem and part of another stored in core or on the SYSTMP. This situation would be +intolerable if the programmer were trying to save some definitions so that he could +use them later. To avoid this unpleasantness, the system I/O routines have been +modified so that the clock is, in essence, turned off during system 1/0 and three +seconds is automatically added to the elapsed time at the conclusion of the read or +write operation (in a machine with a millisecond core clock this is the case - ma- +chines with 1/60 second core clocks add 50 seconds, but this is easily changed). A +clock trap that would normally have occurred during the execution of the read or +write will be executed before the I/O operation takes place. + +### Tape + +A few programmers with very large programs have long bemoaned the inability +of LISP to communicate between different systems. The functions tape, -- rewind, +-- mprht, mread, and backspace have been designed to alleviate this difficulty. +ttTAPE(s)tt, where s is a list, allows the user to specify up to ten scratch tapes; +if more than ten are specified, only the first ten are used. The value of tape is its +argument. The initial tape settings are, from one to ten, A4, A5, A6, A7, A8, B2, + +page 96 + +B3, B4, B5, B6. The tapes must be specified by the octal number that occurs in the +address portion of a machine-language instruction to rewind that tape; that is, a four- +digit octal number is required - the first (high-order) digit is a 1 if channel A is de- +sired, 2 if channel B is desired; the second digit must be a 2; the third and fourth +are the octal representation of the unit number. Thus, to specify that scratch tapes +one, two, and three are to be tapes A4, B1, and A5, respectively, execute "TAPE +((1204Q 2201Q 1205Q))I1. Only the low-order fifteen bits of the numbers in +he tape +list are used by the tape routines, so it is possible to use decimal integers or floating- +point numb.ers in the tape list without generating errors. + +### Rewind + +llREWIND(x)w rewinds scratch tape x, as specified in the most recently exe- +cuted tape function. For example, if the last tape function executed was 'ITAPE +((1 204Q 2201Q))n, then wREWIND(2)11 will cause tape B1 to be rewound. The value +of rewind is NIL. + +### Mprint + +"MPRINT(x s)I1 prints the S-expression s on scratch tape x. The format of +the output is identical to the normal LISP output, except that sequence numbers are +printed in the rightmost eight columns of the output line and the output line is only +80 characters long (the scratch tape output is generated by the punch routine), and +is suitable for punching or being read by mread. The value of mprint is the list +printed. + +### Mread + +NMREAD(x)lq reads one S-expression from scratch tape x. The value of mread is +the S-expression read. + +### Backspace + +llBACKSPACE(x)" causes scratch tape x to be backspaced one record. Cau- +tion in the use of this function is recommended, for if an S-expression to be read +from tape contains more than 72 characters, then it will occupy more than one record +on the tape, and single backspace will not move the tape all the way back to the be- +ginning of the S-expression. The value of backspace is NIL. + +### Evalquote + +Evalquote is available to the programmer as a LISP function - thus, one may now +write I1(EVALQUOTE APPEND ((A)(B C D)))I1, rather than "(EVAL (QUOTE (APPEND +(A)(B C D))) NIL)", should one desire to do so. + +page 97 + +### Backtrace + +This function was copied (not quite literally) from M. I. T.'s LISP system on the +time-shared 7094. Backtrace is a function of no arguments in which the manner of +specifying the no arguments constitutes, in effect, an argument. The first call of +backtrace, with any argument, or with none, suspends backtrace printouts when er- +rors occur. Thereafter, the value of "BACKTRACE NILu is the backtrace for the +most recent error; and "BACKTRACE xtl, for x not NIL, restores the backtrace +printout to the error routine. Backtrace should always be evaluated by evalquote. + +### Read-In Errors + +A common cause of free-storage or circular list printouts is an error (in paren- +thesis count, usually) during the initial read-in of a packet. The new system causes +the accumulator to be cleared if an error occurs during the initial read-in, so that +the contents of the accumulator are printed as ttNIL1t. + +### Obkeep + +Anyone desperate for a few more words of free storage may make up a list, s, of +all atom names he wants to retain in his personal LISP systems, then execute (in a +SET packet) "OBKEEP(s)". All atoms except those which are members of s will be +eliminated from the object list. + +### Reserved + +"RESERVED NILtt prints the names of the atoms on the object list in alphabetical +order, along with the indicators (not alphabetized, and flags may be missed) on their +property lists. This function should help to solve some of the problems that arise +involving mysterious behavior of compiled functions that worked fine when inter- +preted. + +### Gensym and Symnam + +Gensym now omits leading zeroes from the numeric portions of the print-names of +the symbols it generates; thus, what once looked like ltGOOOO1tt now prints as ltGln. +Furthermore, it is possible to specify a heading word of from zero to six characters +for the gensym symbols by executing symnam. vSYMNAM(NIL)tl causes LISP- +generated symbols to have purely numeric print-names (but they are not numbers). +The numeric portions of the print-names are truncated from the left so as not to +overlap the heading characters. Thus, It SY MNAM(AAAAA)" causes gensym to produce +distinct atoms with the following (not necessarily distinct) print-names: AAAAA1, +AAAAA2,... , AAAAA9, AAAAAO, AAAAA1,.... The argument of symnam +must have the indicator PNAME on its property list. tlSYMNAM(l 2)lt will cause un- +defined results. + +For the convenience of those who find it difficult to get along with the tlCONDn form +of the conditional statement, the following "IF" forms are provided in the new system. + +page 98 + +"IF (a THEN b ELSE c)I1 and "IF (a b c)I1 are equivalent to nCOND ((a b)(T c))". "IF +(a THEN b)n and "IF (a b)" are equivalent to "COND ((a b))". + +"FOR (index i s u dl... dn)", for n less than 17, sets index to the value of i +and skips out of the following loop as soon as the value of u is not NIL: evaluate u, +evaluate dl,... , evaluate dn, evaluate s, go to the beginning of the loop. If i, s, +and u are numbers, then the for - statement is similar to the ALGOL "for index = i +step s until u do begin dl... dn endN. The value of the for statement is the value +of dn the last time it was evaluated. The final value of index is available outside +the for function because cset is used to set the index. + +### Sublis + +Sublis has been re-hand-compiled so that it behaves as if it were defined as fol- +lows : + +``` +null[x] -> e + eq[caar[x];e] -> cdar[x] +T -> subb[cdr[x]] + +111~~1; + +###### T - + +``` +lambda[[u;v]; [ +and[equal[car[e];u];equal[cdr[e];v]] -c e; +T -. cons[u;v] +]][suba[car [el]; suba[cdr [el]] +111CeI~ +The differences between the new sublis and the old one, as far as the programmer is +concerned, are that the new model is faster and the result shares as much storage +as possible with e. + + +Characteristics of the System + +The set-up deck supplied with the SHARE LISP system produces a system tape +with the following properties: + +page 99 + +Size (in words) - +Binary Program Space 14000 octal +Push-Down List 5000 octal +Full-Word Space 4220 octal +Free Storage 22400 octal +System Tape (SYSTAP) B7 +System Temporary Tape (SYSTMP) B6 +System Input Tape (SYSPIT) A2 +System Output Tape (SYSPOT) A3 +System Punch Tape (SYSPPT) A3 + +The console switches may be used to obtain special results: + +SW1 on for LISP input from on-line card reader +SW2 has no effect +SW3 on for LISP output on on-line printer +SW4 has no effect +SW5 on to suppress SYSPOT output +SW6 on to return to overlord after accumulator printout resulting from +error *I? 5*. SW6 off for error printout. + +page 100 + +## Index to function descriptions + +| Function | Call type | Implementation | Pages | +|--------------|------------|------------------|------------------------------| +| ADD1 | SUBR | | [26](#page26), [64](#page64) | +| ADVANCE | SUBR | PSEUDO-FUNCTION | [88](#page88) | +| AND | FSUBR | PREDICATE | [21](#page21), [58](#page58) | +| APPEND | SUBR | | [11](#page11), [61](#page61) | +| APPLY | SUBR | | [70](#page70) | +| ARRAY | SUBR | PSEUDO-FUNCTION | [27](#page27), [64](#page64) | +| ATOM | SUBR | PREDICATE | [3](#page3), [57](#page57) | +| ATTRIB | SUBR | PSEUDO-FUNCTION | [59](#page59) | +| BLANK | APVAL | | [69](#page69), [85](#page85) | +| CAR | SUBR | | [2](#page2), [56](#page56) | +| CDR | SUBR | | [3](#page3), [56](#page56) | +| CHARCOUNT | APVAL | | [69](#page69), [87](#page87) | +| CLEARBUFF | SUBR | PSEUDO-FUNCTION | [86](#page86) | +| COMMA | APVAL | | [69](#page69), [85](#page85) | +| COMMON | SUBR | PSEUDO-FUNCTION | [64](#page64), [78](#page78) | +| COMPILE | SUBR | PSEUDO-FUNCTION | [64](#page64), [76](#page76) | +| CONC | FEXPR | | [61](#page61) | +| COND | FSUBR | | [18](#page18) | +| CONS | SUBR | | [2](#page2), [56](#page56) | +| COPY | SUBR | | [62](#page62) | +| COUNT | SUBR | PSEUDO-FUNCTION | [34](#page34), [66](#page66) | +| CP1 | SUBR | | [66](#page66) | +| CSET | EXPR | PSEUDO-FUNCTION | [17](#page17), [59](#page59) | +| CSETQ | FEXPR | PSEUDO-FUNCTION | [59](#page59) | +| CURCHAR | APVAL | | [69](#page69), [87](#page87) | +| DASH | SUBR | PREDICATE APVAL | [85](#page85), [87](#page87) | +| DEFINE | EXPR | PSEUDO-FUNCTION | [15](#page15), [18](#page18), [58](#page58) | +| DEFLIST | EXPR | PSEUDO-FUNCTION | [41](#page41), [58](#page58) | +| DIFFERENCE | SUBR | | [26](#page26), [64](#page64) | +| DIGIT | SUBR | PREDICATE | [87](#page87) | +| DIVIDE | SUBR | | [26](#page26), [64](#page64) | +| DOLLAR | APVAL | | [69](#page69), [85](#page85) | +| DUMP | SUBR | PSEUDO-FUNCTION | [67](#page67) | +| EFFACE | SUBR | PSEUDO-FUNCTION | [63](#page63) | +| ENDREAD | SUBR | PSEUDO-FUNCTION | [8 8](#page8 8) | +| EOF | APVAL | | [69](#page69), [88](#page88) | +| EOR | APVAL | | [69](#page69), [88](#page88) | +| EQ | SUBR | PREDICATE | [3](#page3), [23](#page23), [57](#page57) | +| EQSIGN | APVAL | | [69](#page69), [85](#page85) | +| EQUAL | SUBR | PREDICATE | [11](#page11), [26](#page26), [57](#page57) | +| ERROR | SUBR | PSEUDO-FUNCTION | [32](#page32), [66](#page66) | +| ERROR1 | SUBR | PSEUDO-FUNCTION | [88](#page88) | +| ERRORSET | SUBR | PSEUDO-FUNCTION | [35](#page35), [66](#page66) | +| EVAL | SUBR | | [71](#page71) | +| EVLIS | SUBR | | [71](#page71) | +| EXCISE | SUBR | PSEUDO-FUNCTION | [67](#page67), [77](#page77) | +| EXPT | SUBR | | [26](#page26), [64](#page64) | +| F | APVAL | | [22](#page22), [69](#page69) | +| FIXP | SUBR | PREDICATE | [26](#page26), [64](#page64) | +| FLAG | EXPR | PSEUDO-FUNCTION | [41](#page41), [60](#page60) | +| FLOATP | SUBR | PREDICATE | [26](#page26), [64](#page64) | +| FUNCTION | FSUBR | | [21](#page21), [71](#page71) | +| GENSYM | SUBR | | [66](#page66) | +| GET | SUBR | | [41](#page41), [59](#page59) | +| GO | FSUBR | PSEUDO-FUNCTION | [30](#page30), [72](#page72) | +| GREATERP | SUBR | PREDICATE | [26](#page26), [64](#page64) | +| INTERN | SUBR | PSEUDO-FUNCTION | [67](#page67), [87](#page87) | +| LABEL | FSUBR | | [8](#page8), [18](#page18), [70](#page70) | +| LAP | SUBR | PSEUDO-FUNCTION | [65](#page65), [73](#page73) | +| LEFTSHIFT | SUBR | | [27](#page27), [64](#page64) | +| LENGTH | SUBR | | [62](#page62) | +| LESSP | SUBR | PREDICATE | [26](#page26), [64](#page64) | +| LIST | FSUBR | | [57](#page57) | +| LITER | SUBR | PREDICATE | [87](#page87) | +| LOAD | SUBR | PSEUDO-FUNCTION | [67](#page67) | +| LOGAND | FSUBR | | [27](#page27), [64](#page64) | +| LOGOR | FSUBR | | [26](#page26), [64](#page64) | +| LOGXOR | FSUBR | | [27](#page27), [64](#page64) | +| LPAR | APVAL | | [69](#page69), [85](#page85) | +| MAP | SUBR | FUNCTIONAL | [63](#page63) | +| MAPCON | SUBR | FUNCTIONAL PSEUDO- FUNCTION | [63](#page63) | +| MAPLIST | SUBR | FUNCTIONAL | [20](#page20), [21](#page21), [63](#page63) | +| MAX | FSUBR | | [26](#page26), [64](#page64) | +| MEMBER | SUBR | PREDICATE | [11](#page11), [62](#page62) | +| MIN | FSUBR | | [26](#page26), [64](#page64) | +| MINUS | SUBR | | [26](#page26), [63](#page63) | +| MINUSP | SUBR | PREDICATE | [26](#page26), [64](#page64) | +| MKNAM | SUBR | | [86](#page86) | +| NCONC | SUBR | PSEUDO-FUNCTION | [62](#page62) | +| NIL | APVAL | | [22](#page22), [69](#page69) | +| NOT | SUBR | PREDICATE | [21](#page21), [23](#page23), [58](#page58) | +| NULL | SUBR | PREDICATE | [11](#page11), [57](#page57) | +| NUMBERP | SUBR | PREDICATE | [26](#page26), [64](#page64) | +| NUMOB | SUBR | PSEUDO-FUNCTION | [86](#page86) | +| OBLIST | APVAL | | [69](#page69) | +| ONEP | SUBR | PREDICATE | [26](#page26), [64](#page64) | +| OPCHAR | SUBR | PREDICATE | [87](#page87) | +| OPDEFINE | EXPR | PSEUDO-FUNCTION | [65](#page65), [75](#page75) | +| OR | FSUBR | PREDICATE | [21](#page21), [58](#page58) | +| PACK | SUBR | PSEUDO-FUNCTION | [86](#page86) | +| PAIR | SUBR | | [60](#page60) | +| PAUSE | SUBR | PSEUDO-FUNCTION | [67](#page67) | +| PERIOD | APVAL | | [69](#page69), [85](#page85) | +| PLB | SUBR | PSEUDO- FUNCTION | [67](#page67) | +| PLUS | FSUBR | | [25](#page25), [63](#page63) | +| PLUSS | APVAL | | [69](#page69), [85](#page85) | +| PRIN1 | SUBR | PSEUDO-FUNCTION | [65](#page65), [84](#page84) | +| PRINT | SUBR | PSEUDO-FUNCTION | [65](#page65), [84](#page84) | +| PRINTPROP | EXPR | PSEUDO-FUNCTION LIBRARY | [68](#page68) | +| PROG | FSUBR | | [29](#page29), [71](#page71) | +| PROG2 | SUBR | | [42](#page42), [66](#page66) | +| PROP | SUBR | FUNCTIONAL | [59](#page59) | +| PUNCH | SUBR | PSEUDO-FUNCTION | [65](#page65), [84](#page84) | +| PUNCHDEF | EXPR | PSEUDO-FUNCTION LIBRARY | [68](#page68) | +| PUNCHLAP | EXPR | PSEUDO-FUNCTION LIBRARY | [68](#page68), [76](#page76) | +| QUOTE | FSUBR | | [10](#page10), [22](#page22), [71](#page71) | +| QUOTIENT | SUBR | | [26](#page26), [64](#page64) | +| READ | SUBR | PSEUDO-FUNCTION | [5](#page5), [84](#page84) | +| READLAP | SUBR | PSEUDO-FUNCTION | [65](#page65), [76](#page76) | +| RECIP | SUBR | | [26](#page26), [64](#page64) | +| RECLAIM | SUBR | PSEUDO-FUNCTION | [67](#page67) | +| REMAINDER | SUBR | | [26](#page26), [64](#page64) | +| REMFLAG | SUBR | PSEUDO-FUNCTION | [41](#page41), [60](#page60) | +| REMOB | SUBR | PSEUDO-FUNCTION | [67](#page67) | +| REMPROP | SUBR | PSEUDO-FUNCTION | [41](#page41), [59](#page59) | +| RETURN | SUBR | PSEUDO-FUNCTION | [30](#page30), [72](#page72) | +| REVERSE | SUBR | | [6 2](#page6 2) | +| RPAR | APVAL | | [69](#page69), [85](#page85) | +| RPLACA | SUBR | PSEUDO-FUNCTION | [41](#page41), [58](#page58) | +| RPLACD | SUBR | PSEUDO-FUNCTION | [41](#page41), [58](#page58) | +| SASSOC | SUBR | FUNCTIONAL | [60](#page60) | +| SEARCH | SUBR | FUNCTIONAL | [63](#page63) | +| SELECT | FEXPR | | [66](#page66) | +| SET | SUBR | PSEUDO-FUNCTION | [30](#page30), [71](#page71) | +| SETQ | FSUBR | PSEUDO-FUNCTION | [30](#page30), [71](#page71) | +| SLASH | APVAL | | [69](#page69), [85](#page85) | +| SPEAK | SUBR | PSEUDO-FUNCTION | [34](#page34), [66](#page66) | +| SPECIAL | SUBR | PSEUDO-FUNCTION | [64](#page64), [78](#page78) | +| STAR | APVAL | | [69](#page69), [85](#page85) | +| STARTREAD | SUBR | PSEUDO-FUNCTION | [87](#page87) | +| SUB1 | SUBR | | [26](#page26), [64](#page64) | +| SUBLIS | SUBR | | [12](#page12), [61](#page61) | +| SUBST | SUBR | | [11](#page11), [61](#page61) | +| T | APVAL | | [22](#page22), [69](#page69) | +| TEMPUS-FUGIT | SUBR | PSEUDO-FUNCTION | [67](#page67) | +| TERPRI | SUBR | PSEUDO-FUNCTION | [65](#page65), [84](#page84) | +| TIMES | FSUBR | | [26](#page26), [64](#page64) | +| TRACE | EXPR | PSEUDO-FUNCTION | [32](#page32), [66](#page66), [79](#page79) | +| TRACESET | EXPR | PSEUDO-FUNCTION LIBRARY | [68](#page68) | +| UNCOMMON | SUBR | PSEUDO-FUNCTION | [64](#page64), [78](#page78) | +| UNCOUNT | SUBR | PSEUDO-FUNCTION | [34](#page34), [66](#page66) | +| UNPACK | SUBR | PSEUDO-FUNCTION | [87](#page87) | +| UNSPECIAL | SUBR | | [64](#page64), [78](#page78) | +| UNTRACE | EXPR | PSEUDO-FUNCTION | [32](#page32), [66](#page66) | +| UNTRACESET | EXPR | PSEUDO-FUNCTION | [68](#page68) | +| ZEROP | SUBR | PREDICATE | [26](#page26), [64](#page64) | + +``` +Symbol or Term +``` + +``` +GLOSSARY +``` + +``` +Definition +``` + +Algol 60 + +algorithm + +a-list + +assembly program + +association list + +``` +An international standard programming language for +describing numerical computation. +A procedure that is unambiguous and sufficiently mecha- +nized so as to be programmable on a computer. +A synonym for association list. +A program that translates from a symbolic instruction +language such as FAP or LAP into the language of a +machine. The statements in an assembly language, with +few exceptions, are one to one with the machine language +instructions to which they translate. Unlike machine +language, an assembly language allows the programmer +to use symbols with mnemonic significance. LISP was +written in the assembly language, FAP. LISP contains +' a small internal assembly program, LAP. +A list of pairs of terms which is equivalent to a table +with two columns. It is used to pair bound variables +with their values. Example: ((VAR1. VAL1) (B. (U V +(W))) (C 2)) +``` + +at om A synonym for atomic symbol. + +atomic symbol + +back trace + +``` +The basic constituent of an S-expression. The legal +atomic symbols are certain strings of letters, digits, and +special characters defined precisely in Appendix F. +Examples: A NIL ATOM A1 METHIMPIKEHOSES +A list of the names of functions that have been entered +but not completed at the time when an error occurs. +``` + +basic functions: car, cdr, These functions are called basic because the entire class +cons, e3 of computable functions of S-expressions can be built from +andatom them by using composition, conditional expressions, and +recursion. + +Boolean form + +bound variable + +``` +compiler +``` + +``` +The special forms involving AND, OR, and NOT, which +can be used to build up propositional expressions. +A variable included in the list of bound variables after +LAMBDA is bound within the scope of the LAMBDA. This +means that its value is the argument corresponding in +position to the occurrence of the variable in the LAMBDA +list. For example, in an expression of the form +((LAMBDA (X Y) E) (QUOTE (A. B)) (QUOTE C)), X has +the value (A. B) and Y the value C at any of their +occurrences in E:. +A program that translates from a source language into +machine (or assembly) language. Unlike most compilers, +the LISP compiler does not need to compile an entire +program before execution. It can compile individual +functions defined by S-expressions into machine language +during a run. +``` + +``` +GLOSSARY +``` + +``` +Symbol or Term Definition +``` + +composition + +conditional expression + +constant + +dot notation + +doublet + +flag + +form + +free-storage list + +free variable + +functional + +``` +To compose a function is to use the value of one function +as an argument for another function. This is usually +written with nested brackets or parentheses. Examples: +cons[car[~];cdr[~]], or as an S-expression (CONS (CAR X) +(CDR Y)); and a+(b.(c+d)). +An expression containing a list of propositional expressions +and corresponding forms as values. The value is the +value of the entire conditional expression. Examples of +different notations for conditional expression: +M-expression: [a<0-b;~-C] +S-expression: (COND ((LESSP A 0) B) (T C)) +Algol 60: if a Type ins +:2 CONS ((G,H), | + | +230 (I,J)) () / +RLN | | oo 7 a | + +O14 (read lines O and 1) +``` + +Of course, this isn't proof. If `CAR` and `CDR` used here are standard IBM 704 assembler mnemonics -- as I believe they are -- then what is `CONS`? It's used in a syntactically identical way. If it also is an assembler mnemonic, then it's hard to believe that, as legend relates, it is short for 'construct'; on the other hand, if it's a label representing an entry point into a subroutine, then why should `CAR` and `CDR` not also be labels? + +----- + +**Edited 3rd April to add:** I've found a document, not related to Lisp (although John McCarthy is credited as one of the authors), which does confirm -- or strictly, amend -- the story. This is the [CODING for the MIT-IBM 704 COMPUTER](http://bitsavers.org/pdf/mit/computer_center/Coding_for_the_MIT-IBM_704_Computer_Oct57.pdf), dating from October 1957. The registers of the 704 were divided into four parts, named respectively the prefix part, the address part, the tag part, and the decrement part, of 3, 15, 3, and 15 bits respectively. The decrement part was not used in addressing; that part of the folklore I was taught isn't right. But the names are correct. Consider [this excerpt](http://bitsavers.org/pdf/mit/computer_center/Coding_for_the_MIT-IBM_704_Computer_Oct57.pdf#page=145) : + +> The address, tag and decrement parts of symbolic instructions are given in that order. In some cases the decrement, tag or address parts are not necessary; therefore the following combinations where OP represents the instruction abbreviation are permissible. + +This doesn't prove there were individual machine instructions with the mnemonics `CAR` and `CDR`; in fact, I'm going to say with some confidence that there were not, by reference to [the table of instructions](http://bitsavers.org/pdf/mit/computer_center/Coding_for_the_MIT-IBM_704_Computer_Oct57.pdf#page=170) appended to the same document. The instructions do have three letter mnemonics, and they do use 'A' and 'D' as abbreviations for 'address' and 'decrement' respectively, but `CAR` and `CDR` are not included. + +So it seems probable that `CAR` and `CDR` were labels for subroutines, as I hypothesised above. But they were quite likely pre-existing subroutines, in use before the instantiation of the Lisp project, because they would be generally useful; and the suggestion that they are contractions of 'contents of the address part' and 'contents of the decrement part', respectively, seem confirmed. + +And, going further down the rabbit hole, [there's this](https://dl.acm.org/doi/pdf/10.1145/800055.802047#page=3). In 1957, before work on the Lisp project started, McCarthy was writing functions to add list processing to the then-new FORTRAN language, on the very same IBM 704 machine. + +> in this time any function that delivered integer values had to have a first letter X. Any function (as opposited to subroutines) had to have a last letter F in its name. Therefore the functions selecting parts of the IBM704 memory register (word) were introduced to be XCSRF, XCPRF, XCDRF, XCTRF and XCARF + +----- + +I think that the answer has to be that if `CAR` and `CDR` had been named by the early Lisp team -- John McCarthy and his immediate colleagues -- they would not have been named as they were. If not `FRST` and `REST`, as in more modern Lisps, then something like `P1` and `P2`. `CAR` and `CDR` are distinctive and memorable (and therefore in my opinion worth preserving) because they very specifically name the parts of a cons cell and of nothing else. + +Let's be clear, here: when `CAR` and `CDR` are used in Lisp, they are returning pointers, certainly -- but not in the sense that one points to a page and the other to a word. Each is an offset into a cell array, which is almost certainly an array of single 36 bit words held on a single page. So both are in effect being used as decrements. Their use in Lisp is an overload onto their original semantic meaning; they are no longer being used for the purpose for which they are named. + +As far as I can tell, these names first appear in print in 1960, both in the Lisp 1 Programmer's Manual referenced above, and in McCarthy's paper [Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I](https://web.archive.org/web/20230328222647/http://www-formal.stanford.edu/jmc/recursive.pdf). The paper was published in April so was presumably written in 1959 + +## Grey Anatomy + +### The Object List + +Lisp keeps track of values by associating them with names. It does so by having what is in effect a global registry of all the names it knows to which values are attached. This being a list processing language, that was of course, in early Lisps, a list: a single specialised first class list known as the 'object list', or `oblist` for short. + +Of course, a list need not just be a list of single items, it can be a list of pairs: it can be a list of pairs of the form `(name . value)`. Hold onto that, because I want to talk about another fundamental part of a working Lisp system, the stack. + +### The Stack + +Considering the case of pure interpreter first, let's think about how a function keeps track of the data it's working on. In order to do its work, it probably calls other functions, to which it passes off data, and they in turn probably call further functions. So, when control returns to our first function, how does it know where its data is? The answer is that each function pushes its argument bindings onto the stack when it starts work, and pops them off again when it exits. So when control returns to a function, its own data is still on the top of the stack. Or, to be precise, actually it doesn't; in practice the function [`EVAL`](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf#page=79) does it for each function in turn. But it doesn't matter: it gets done. + +What is this stack? Well, it's a list of `(name . value)` pairs. At least, it is in pure Lisps; [Clojure](https://clojure.org/), because it runs on the [Java Virtual Machine](https://en.wikipedia.org/wiki/Java_virtual_machine) and interoperates with other software running on the JVM, uses the JVM stack which is a permanently reserved vector of memory never used for anything else. Consequently it cannot be very large; and the consequence of that is that it's very easy to crash JVM programs because they've run out of stack space. + +The advantage of organising your stack as a vector is that on average it's usually slightly more memory efficient, and that it's somewhat faster to access. The disadvantage is you need a contiguous block of memory for it, and once you've run out, you've at best lost both those advantages but in the normal case your program just crashes. Also, the memory you've reserved for the stack isn't available for any other use, even during the most of the time that the stack isn't using most of it. So of course there's a temptation to keep the amount reserved for the stack as small as possible. + +It's this brutal fragility of vector stacks -- which are used by most modern computer languages -- which makes software people so wary of fully exploiting the beauty and power of recursion, and I really think that's a shame. + +The advantage of organising your stack as a list is that, while there is any memory left on the machine at all, you cannot run out of stack. + +### The Spine + +So, there's an object list where you associate names and values, and there's a stack where you associate names and values. But, why do they have to be different? And why do you have to search in two places to find the value of a name? + +The answer is -- or it was, and arguably it should be -- that you don't. The stack can simply be pushed onto the front of the object list. This has multiple advantages. The first and most obvious is that you only have to search in one place for the value associated with a name. + +The second is more subtle: a function can mask a variable in the object list by binding the same name to a new value, and the functions to which it then calls will only see that new value. This is useful if, for example, printed output is usually sent to the user's terminal, but for a particular operation you want to send it to a line printer or to a file on disk. You simply rebind the name of the standard output stream to your chosen output stream, and call the function whose output you want to redirect. + +So, in summary, there's a lot of merit in making the stack and the object list into a single central structure on which the architecture of our Lisp system is built. But there's more we need to record, and it's important. + +### Fields and Properties + +No, I'm not banging on about [land reform](https://www.journeyman.cc/blog/tags-output/Levelling/) again! I'm not a total monomaniac! + +But there's more than one datum we may want to associate with a single name. A list can be more than a set of bindings between single names and single values. There's more than one thing, for example, that I know about my friend Lucy. I know her age. I know her address. I know her height. I know her children. I know her mother. All of this information -- and much more -- should be associated with her. + +In conventional computing systems we'd use a table. We'd put into the table a field -- a column -- for each datum we wanted to store about a class of things of interest. And we'd reserve space to store that datum for every record, whether every record had something to go there of not. Furthermore, we'd have to reserve space in each field for the maximum size of the datum to be stored in it -- so if we needed to store full names for even some of the people we knew, and one of the people whose full name we needed to store (because he's both very important and very irascible) was *Charles Philip Arthur George Windsor*, then we'd have to reserve space for thirty-six characters for the full name of everyone in our records, even if for most of them half that would be enough. + +But if instead of storing a table for each sort of thing on which we hold data, and a row in that table for each item of that sort on which we store data, we simply tagged each thing on which we hold data with those things which are interesting about them? We could tag my friend Lucy with the fact she's on pilgrimage, and what her pilgrimage route is. Those aren't things we need to know about most people, it would be absurdly wasteful to add a column to a `person` table to record `pilgrimage route`. So in a conventional data system we would lose that data. + +Lisp has had, right back from the days of Lisp 1.5 -- so, for sixty-five years -- a different solution. We can give every symbol arbitrarily many, arbitrarily different, properties. A property is a `(name . value)` pair. We don't have to store the same properties for every object. The values of the properties don't have to have a fixed size, and they don't have to take up space they don't need. It's like having a table with as many fields as we choose, and being able to add more fields at any time. + +So, in summary, I knew, in building Beowulf, that I'd have to implement property lists. I just didn't know how I was going to do it. + +## Archaeology + +What I'm doing with Beowulf is trying to better understand the history of Lisp by reconstructing a very early example; in this case, Lisp 1.5, from about 1962, or sixty one years ago. + +I had had the naive assumption that entries on the object list in early Lisps had their `CAR` pointing to the symbol and their `CDR` pointing to the related value. Consequently, in building [beowulf](https://github.com/simon-brooke/beowulf), I could not work out where the property list went. More careful reading of [the text](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf#page=67) implies, but does not explicitly state, that my naive assumption is wrong. + +Instead, it appears that the `CAR` points to the symbol, as expected, but the `CDR` points to the property list; and that on the property list there are privileged properties at least as follows: + +APVAL +: the simple straightforward ordinary value of the symbol, considered as a variable; + +EXPR +: the definition of the function considered as a normal lambda expression (arguments to be evaluated before applying); + +FEXPR +: the definition of a function which should be applied to unevaluated arguments (what InterLisp and Portable Standard Lisp would call ['*nlambda*'](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=fe0f4f19cee0c607d7b229feab26fc9ed559fc9c#page=9)); + +SUBR +: the definition of a compiled subroutine which should be applied to evaluated arguments; + +FSUBR +: the definition of a compiled subroutine which should be applied to unevaluated arguments. + +I think there was also another privileged property value which contained the property considered as a constant, but I haven't yet confirmed that. + +From this it would seem that Lisp 1.5 was not merely a ['Lisp 2'](http://xahlee.info/emacs/emacs/lisp1_vs_lisp2.html) but in fact a 'Lisp 6', with six effectively first class namespaces. In fact it's not as bad as that, because of the way [`EVAL`](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf#page=79) is evaluated. + +Essentially the properties are tried in turn, and only the first value found is used. Thus the heirarchy is + +1. APVAL +2. EXPR +3. FEXPR +4. SUBR +5. FSUBR + +This means that, while the other potential values can be retrieved from the property list, interpreted definitions (if present) will always be preferred to uninterpreted definitions, and lambda function definitions (which evaluate their arguments), where present, will always be preferred to non-lamda definitions, which don't. + +**BUT NOTE THAT** the `APVAL` value is sought only when seeking a variable value for the symbol, while the others are only when seeking a function value, so Lisp 1.5 is a 'Lisp 2', not a 'Lisp 1'. I strongly believe that this is wrong: a function is a value, and should be treated as such. But at the same time I do acknowledge the benefit of being able to store both source and compiled forms of the function as properties of the same symbol. + +## The persistent problem + +There's a view in modern software theory -- with which I strongly hold -- that data should be immutable. Data that changes under you is the source of all sorts of bugs. And in modern multi threaded systems, the act of reading a datum whilst some other process is writing it, or worse, two processes attempting simultaneously to write the same datum, is a source of data corruption and even crashes. So I'm very wary of mutable data; and, in modern systems where we normally have a great deal of space and a lot of processor power, making fresh copies of data structures containing the change we wanted to make is a reasonable price to pay for avoiding a whole class of bugs. + +But early software was not like that. It was always constrained by the limits of the hardware on which it ran, to a degree that we are not. And the experience that we now have of the problems caused by mutable data, they did not have. So it's core to the design of Lisp 1.5 that its lists are mutable; and, indeed, one of the biggest challenges in writing Beowulf has been [implementing mutable lists in Clojure](https://github.com/simon-brooke/beowulf/blob/master/src/beowulf/cons_cell.clj#L19), a language carefully designed to prevent them. + +But, just because Lisp 1.5 lists can be mutable, should they be? And when should they be? + +The problem here is that [spine of the system](#the_spine) I talked about earlier. If we build the execution stack on top of the oblist -- as at present I do -- then if we make a new version of the oblist with changes in it, the new changes will not be in the copy of the oblist that the stack is built on top of; and consequently, they'll be invisible to the running program. + +What I do at present, and what I think may be good enough, is that each time execution returns to the read-eval-print loop, the REPL, the user's command line, I rebuild a new execution stack on the top of the oblist as it exists now. So, if the last operation modified the oblist, the next operation will see the new, modified version. But if someone tried to run some persistent program which was writing stuff to property values and hoping to read them back in the same computation, that wouldn't work, and it would be a very hard bug to trace down. + +So my options are: + +1. To implement `PUT` and `GET` in Clojure, so that they can operate on the current copy of the object list, not the one at the base of the stack. I'm slightly unwilling to do that, because my objective is to make Beowulf ultimately as self-hosting as possible. +2. To implement `PUT` and `GET` in Lisp, and have them destructively modify the working copy of the object list. + +Neither of these particularly appeal. + +## How property lists should work + +I'm still not fully understanding how property lists in Lisp 1.5 are supposed to work. + +### List format + +Firstly, are they association lists comprising dotted pairs of `(property-name . value)`, i.e.: + +>((property-name1 . value1) (property-name2 . value2) ... (property-namen . valuen)) + +I have assumed so, and that is what I presently intend to implement, but the diagrams on [pages 59 and 60](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf#page=67) seem rather to show a flat list of interleaved names and values: + +> (property-name1 value1 property-name2 value2 ... property-namen valuen) + +I cannot see what the benefit of this latter arrangement is, and I'm unwilling to do it, although I think it may be what was done. But if it was done that way, *why* was it done that way? These were bright people, and they certainly knew about association lists. So... I'm puzzled. + +### Function signatures + +To associate the value of a property with a symbol, we need three things: we need the symbol, we need the property name, and we need the value. For this reason, [Portable Standard Lisp](https://www.softwarepreservation.org/projects/LISP/utah/USCP-Portable_Standard_LISP_Users_Manual-TR_10-1984.pdf#page=44) and others has a function `put` with three arguments: + +> `(Put U:id IND:id PROP:any)`: any +> The indicator `IND` with the property `PROP` is placed on the property list of +> the id `U`. If the action of Put occurs, the value of `PROP` is returned. If +> either of `U` and `IND` are not ids the type mismatch error occurs and no +> property is placed. +> `(Put 'Jim 'Height 68)` +> The above returns `68` and places `(Height . 68)` on the property list of the id `Jim` + +Cambridge Lisp is identical to this except in lower case. [InterLisp](https://larrymasinter.net/86-interlisp-manual-opt.pdf#page=37) and several others have `putprop`: + +> `(PUTPROP ATM PROP VAL) [Function]` +> Puts the property `PROP` with value `VAL` on the property list of `ATM`. `VAL` replaces +> any previous value for the property `PROP` on this property list. Returns `VAL`. + +The execrable Common Lisp uses its execrable macro [`setf`](https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node108.html) but really the less said about that the better. + +So I was looking for a function of three arguments to set properties, and I didn't find one. + +There's a function `DEFINE` which takes one argument, an association list of pairs: +```lisp + (function-name . function-definition)` +``` +So how does that work, if what it's doing is setting properties? If all you're passing is pairs of name and definition, where does the property name come from? + +The answer is as follows, taken from [the manual](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf#page=66): + +> #### define [x] : EXPR pseudo-function + +> The argument of `define`, `x`, is a list of pairs + +> > ((ul vl) (u2 v2) ... (un vn)) + +> where each `u` is a name and each `v` is a λ-expression for a function . For each `pair`, define puts an `EXPR` on the property list for `u` pointing to `v`. The function of `define` puts things on at the front of the property list. The value of `define` is the list of `u`s. + +So, in fact, the value of the property being set by `define` is fixed: hard wired, not parameterised. That seems an astonishing decision, until you realise that Lisp 1.5's creators weren't creating their functions one by one, in a playful exploration with their system, but entering them in a batch. + +## Learning by doing + +In fact, when I got over my surprise, I realised that that `(name . function-definition)` list is actually very much like this, which is an excerpt from a sysout from a Beowulf prototype: + +```lisp +(... + (MAPLIST LAMBDA (L F) + (COND ((NULL L) NIL) + ((QUOTE T) (CONS (F (CAR L)) (MAPLIST (CDR L) F))))) + (MEMBER LAMBDA (A X) + (COND ((NULL X) (QUOTE F)) + ((EQ A (CAR X)) (QUOTE T)) + ((QUOTE T) (MEMBER A (CDR X))))) + (MINUSP LAMBDA (X) (LESSP X 0)) + (NOT LAMBDA (X) + (COND (X (QUOTE NIL)) + ((QUOTE T) (QUOTE T)))) + (NULL LAMBDA (X) + (COND ((EQUAL X NIL) (QUOTE T)) + (T (QUOTE F)))) +...) +``` + +I was looking at `DEFINE` and thinking, 'why would one ever want to do that?' and then I found that, behind the scenes, I was actually doing it myself. + +Because the point of a sysout is you don't write it. The point about the REPL -- the Read Eval Print Loop which is the heart of the interactive Lisp development cycle, where you sit playing with things and fiddling with them interactively, and where when one thing works you get onto the next without bothering to make some special effort to record it. + +The point of a sysout is that, at the end of the working day, you invoke one function + +| Function | Type | Signature | Implementation | Documentation | +| -------- | ---- | --------- | -------------- | ------------- | +| SYSOUT | Host function | (SYSOUT); (SYSOUT FILEPATH) | SUBR | Dump the current content of the object list to file. If no `filepath` is specified, a file name will be constructed of the symbol `Sysout` and the current date. File paths will be considered relative to the filepath set when starting Lisp. | + +At the start of the next working day, you load that sysout in and continue your session. + +The sysout captures the entire working state of the machine. No-one types it in, as an operation in itself. Instead, data structures -- corpuses of functions among them -- simply build up on the object list almost casually, as a side effect of the fact that you're enjoying exploring your problem and finding elegant ways of solving it. So `SYSOUT` and `SYSIN` seem to me, as someone who all his adult life has worked with Lisp interactively, as just an automatic part of the cycle of the day. + +## The process of discovery + +The thing is, I don't think anyone is ever going to use Beowulf the way Lisp 1.5 was used. I mean, probably, no one is ever going to use Beowulf at all; but if they did they wouldn't use Beowulf the way Lisp 1.5 was used. + +I'm a second generation software person. I have worked, in my career, with two people who personally knew and had worked with [Alan Turing](https://en.wikipedia.org/wiki/Alan_Turing). I have worked with, and to an extent been mentored by, [Chris Burton](https://www.bcs.org/articles-opinion-and-research/manchester-s-place-in-computing-history-marked/), who in his apprenticeship was part of the team that built the Manchester Mark One, and who in his retirement led the team who restored it. But I never knew the working conditions they were accustomed to. In my first year at university we used card punches, and, later, when we had a bit of seniority, teletypewriters (yes, that's what TTY stands for), but by the time I'd completed my undergraduate degree and become a research associate I had a Xerox 1108 workstation with a huge bitmapped graphic screen, and an optical mouse, goddamit, running InterLisp, all to myself. + +People in the heroic age did not have computers all to themselves. They did not have terminals all to themselves. They didn't sit at a terminal experimenting in the REPL. They wrote their algorithms in pencil on paper. When they were certain they'd got it right, they'd use a card punch to punch a deck of cards carrying the text of the program, and then they were certain they'd got *that* right, they'd drop it into the input hopper. Some time later their batch would run, and the operator would put the consequent printout into their pigeon hole for them to collect. + +(They wrote amazingly clean code, those old masters. I could tell you a story about Chris Burton, the train, and the printer driver, that software people of today simply would not believe. But it's true. And I think that what taught them that discipline was the high cost of even small errors.) + +Lisp 1.5 doesn't have `PUT`, `PUTPROP` or `DEFUN` because setting properties individually, defining functions individually one at a time, was not something they ever thought about doing. And in learning that, I've learned more than I ever expected to about the real nature of Lisp 1.5, and the (great) people who wrote it. + +----- + +## Deeper delving + +After writing, and publishing, this essay, I went on procrastinating, which is what I do when I'm sure I'm missing something; and to procrastinate, I went on reading the earliest design documents of Lisp I could find. And so I came across the MIT AI team's first ever memo, written by John McCarthy in September 1958. And in that, I find this: + +> 3.2.1. First we have those that extract parts of a 704 word and form a word from parts. We shall distinguish the following parts of a word and indicate each of them by a characteristic letter. +> +> | Letter | Description | +> | ---- | ---------------------------- | +> | w | the whole word | +> | p | the prefix (bits s, 1, 2) | +> | i | the indicator (bits 1 and 2) | +> | s | the sign bit | +> | d | the decrement (bits 3-17) | +> | t | the tag (bits 18-20) | +> | a | the address (bits 21-35) | + +In the discussion of functions which access properties on [page 58 of the Lisp 1.5 programmer's manual](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf#page=66), the word 'indicator' is used in preference to 'symbol' for the name of a property: for example + +> The function `deflist` is a more general defining function. Its first argument is a list of pairs as for define. Its second argument is the *indicator* that is to be used. After `deflist` has been executed with (ui vi) among its first argument, the property list of ui will begin: +> +> If `deflist` or `define` is used twice on the same object with the same *indicator*, the old value will be replaced by the new one. + +(my emphasis). + +That use of 'indicator' has been nagging at me for a week. It looks like a term of art. If it's just an ordinary atomic symbol, why isn't it called a symbol? + +Is it an indicator in the special sense of the indicator part of the machine word? If it were, then the property list could just be a flat list of values. And what's been worrying and surprising me is that property lists are shown in the manual as flat lists. Eureka? I don't *think* so. + +The reason I don't think so is that there are only two bits in the indicator part of the word, so only four distinct values; whereas we know that Lisp 1.5 has (at least) five distinct indicator values, `APVAL`, `EXPR`, `FEXPR`, `SUBR` and `FSUBR`. + +Furthermore, on [page 39](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf#page=47), we have: + +> A property list is characterized by having the special constant 777778 (i. e., minus 1) +> as the first element of the list. The rest of the list contains various properties of the +> atomic symbol. Each property is preceded by an *atomic symbol* which is called its +> *indicator*. + +(again, my emphasis) + +But I'm going to hypothesise that the properties were originally intended to be discriminated by the indicator bits in the cons cell, that they were originally coded that way, and that there was some code which depended on property lists being flat lists; and that, when it was discovered that four indicators were not enough and that something else was going to have to be used, the new format of the property list using atomic symbols as indicators was bodged in. + +----- + +So what this is about is I've spent most of a whole day procrastinating, because I'm not exactly sure how I'm going to make the change I've got to make. Versions of Beowulf up to and including 0.2.1 used the naive understanding of the architecture; version 0.3.0 *should* use the corrected version. But before it can, I need to be reasonably confident that I understand what the correct solution is. + +I *shall* implement `PUT`, even though it isn't in the spec, because it's a useful building block on which to build `DEFINE` and `DEFLIS`, both of which are. And also, because `PUT` would have been very easy for the Lisp 1.5 implementers to implement, if it had been relevant to their working environment. And I shall implement property list as flat lists of interleaved 'indicator' symbols and values, even with that nonsense 777778 as a prefix, because now I know (or think I know) that it was a bodge, it seems right in the spirit of historical reconstruction to reconstruct the bodge. diff --git a/docs/codox/beowulf.bootstrap.html b/docs/codox/beowulf.bootstrap.html index 4827d95..cf5ffc7 100644 --- a/docs/codox/beowulf.bootstrap.html +++ b/docs/codox/beowulf.bootstrap.html @@ -1,23 +1,17 @@ -beowulf.bootstrap documentation

beowulf.bootstrap

Lisp as defined in Chapter 1 (pages 1-14) of the Lisp 1.5 Programmer's Manual; that is to say, a very simple Lisp language, which should, I believe, be sufficient in conjunction with the functions provided by beowulf.host, be sufficient to bootstrap the full Lisp 1.5 interpreter..

-

The convention is adopted that functions in this file with names in ALLUPPERCASE are Lisp 1.5 functions (although written in Clojure) and that therefore all arguments must be numbers, symbols or beowulf.cons_cell.ConsCell objects.

APPEND

(APPEND x y)

Append the the elements of y to the elements of x.

-

All args are assumed to be beowulf.cons-cell/ConsCell objects. See page 11 of the Lisp 1.5 Programmers Manual.

APPLY

(APPLY function args environment depth)

Apply this function to these arguments in this environment and return the result.

-

For bootstrapping, at least, a version of APPLY written in Clojure. All args are assumed to be symbols or beowulf.cons-cell/ConsCell objects. See page 13 of the Lisp 1.5 Programmers Manual.

ASSOC

(ASSOC x a)

If a is an association list such as the one formed by PAIRLIS in the above example, then assoc will produce the first pair whose first term is x. Thus it is a table searching function.

-

All args are assumed to be beowulf.cons-cell/ConsCell objects. See page 12 of the Lisp 1.5 Programmers Manual.

ATOM

macro

(ATOM x)

Returns T if and only if the argument x is bound to an atom; else F. It is not clear to me from the documentation whether (ATOM 7) should return T or F. I’m going to assume T.

ATOM?

macro

(ATOM? x)

The convention of returning F from predicates, rather than NIL, is going to tie me in knots. This is a variant of ATOM which returns NIL on failure.

CAAAAR

macro

(CAAAAR x)

TODO: write docs

CAAADR

macro

(CAAADR x)

TODO: write docs

CAAAR

macro

(CAAAR x)

TODO: write docs

CAADAR

macro

(CAADAR x)

TODO: write docs

CAADDR

macro

(CAADDR x)

TODO: write docs

CAADR

macro

(CAADR x)

TODO: write docs

CAAR

macro

(CAAR x)

TODO: write docs

CADAAR

macro

(CADAAR x)

TODO: write docs

CADADR

macro

(CADADR x)

TODO: write docs

CADAR

macro

(CADAR x)

TODO: write docs

CADDAR

macro

(CADDAR x)

TODO: write docs

CADDDR

macro

(CADDDR x)

TODO: write docs

CADDR

macro

(CADDR x)

TODO: write docs

CADR

macro

(CADR x)

TODO: write docs

CDAAAR

macro

(CDAAAR x)

TODO: write docs

CDAADR

macro

(CDAADR x)

TODO: write docs

CDAAR

macro

(CDAAR x)

TODO: write docs

CDADAR

macro

(CDADAR x)

TODO: write docs

CDADDR

macro

(CDADDR x)

TODO: write docs

CDADR

macro

(CDADR x)

TODO: write docs

CDAR

macro

(CDAR x)

TODO: write docs

CDDAAR

macro

(CDDAAR x)

TODO: write docs

CDDADR

macro

(CDDADR x)

TODO: write docs

CDDAR

macro

(CDDAR x)

TODO: write docs

CDDDAR

macro

(CDDDAR x)

TODO: write docs

CDDDDR

macro

(CDDDDR x)

TODO: write docs

CDDDR

macro

(CDDDR x)

TODO: write docs

CDDR

macro

(CDDR x)

TODO: write docs

DEFINE

(DEFINE args)

Bootstrap-only version of DEFINE which, post boostrap, can be overwritten in LISP.

-

The single argument to DEFINE should be an assoc list which should be nconc’ed onto the front of the oblist. Broadly, (SETQ OBLIST (NCONC ARG1 OBLIST))

EQ

(EQ x y)

Returns T if and only if both x and y are bound to the same atom, else NIL.

EQUAL

(EQUAL x y)

This is a predicate that is true if its two arguments are identical S-expressions, and false if they are different. (The elementary predicate EQ is defined only for atomic arguments.) The definition of EQUAL is an example of a conditional expression inside a conditional expression.

-

NOTE: returns F on failure, not NIL

EVAL

(EVAL expr)(EVAL expr env depth)

Evaluate this expr and return the result. If environment is not passed, it defaults to the current value of the global object list. The depth argument is part of the tracing system and should not be set by user code.

-

All args are assumed to be numbers, symbols or beowulf.cons-cell/ConsCell objects.

INTEROP

(INTEROP fn-symbol args)

Clojure (or other host environment) interoperation API. fn-symbol is expected to be either

-
    -
  1. a symbol bound in the host environment to a function; or
  2. -
  3. a sequence (list) of symbols forming a qualified path name bound to a function.
  4. -
-

Lower case characters cannot normally be represented in Lisp 1.5, so both the upper case and lower case variants of fn-symbol will be tried. If the function you’re looking for has a mixed case name, that is not currently accessible.

-

args is expected to be a Lisp 1.5 list of arguments to be passed to that function. Return value must be something acceptable to Lisp 1.5, so either a symbol, a number, or a Lisp 1.5 list.

-

If fn-symbol is not found (even when cast to lower case), or is not a function, or the value returned cannot be represented in Lisp 1.5, an exception is thrown with :cause bound to :interop and :detail set to a value representing the actual problem.

interop-interpret-q-name

(interop-interpret-q-name l)

For interoperation with Clojure, it will often be necessary to pass qualified names that are not representable in Lisp 1.5. This function takes a sequence in the form (PART PART PART... NAME) and returns a symbol in the form PART.PART.PART/NAME. This symbol will then be tried in both that form and lower-cased. Names with hyphens or underscores cannot be represented with this scheme.

lax?

(lax? symbol)

Are we in lax mode? If so. return true; is not, throw an exception with this symbol.

MEMBER

(MEMBER x y)

This predicate is true if the S-expression x occurs among the elements of the list y.

-

All args are assumed to be symbols or beowulf.cons-cell/ConsCell objects. See page 11 of the Lisp 1.5 Programmers Manual.

NILP

macro

(NILP x)

Not part of LISP 1.5: T if o is NIL, else NIL.

NULL

macro

(NULL x)

Returns T if and only if the argument x is bound to NIL; else F.

OBLIST

(OBLIST)

Return a list of the symbols currently bound on the object list.

-

NOTE THAT in the Lisp 1.5 manual, footnote at the bottom of page 69, it implies that an argument can be passed but I’m not sure of the semantics of this.

PAIRLIS

(PAIRLIS x y a)

This function gives the list of pairs of corresponding elements of the lists x and y, and APPENDs this to the list a. The resultant list of pairs, which is like a table with two columns, is called an association list.

-

Eessentially, it builds the environment on the stack, implementing shallow binding.

-

All args are assumed to be beowulf.cons-cell/ConsCell objects. See page 12 of the Lisp 1.5 Programmers Manual.

QUOTE

macro

(QUOTE f)

Quote, but in upper case for LISP 1.5

SET

(SET symbol val)

Implementation of SET in Clojure. Add to the oblist a binding of the value of var to the value of val. NOTE WELL: this is not SETQ!

SUBLIS

(SUBLIS a y)

Here a is assumed to be an association list of the form ((ul . vl)...(un . vn)), where the us are atomic, and y is any S-expression. What SUBLIS does, is to treat the us as variables when they occur in y, and to SUBSTitute the corresponding vs from the pair list.

-

My interpretation is that this is variable binding in the stack frame.

-

All args are assumed to be beowulf.cons-cell/ConsCell objects. See page 12 of the Lisp 1.5 Programmers Manual.

SUBST

(SUBST x y z)

This function gives the result of substituting the S-expression x for all occurrences of the atomic symbol y in the S-expression z.

to-beowulf

(to-beowulf o)

Return a beowulf-native representation of the Clojure object o. Numbers and symbols are unaffected. Collections have to be converted; strings must be converted to symbols.

to-clojure

(to-clojure l)

If l is a beowulf.cons_cell.ConsCell, return a Clojure list having the same members in the same order.

uaf

(uaf l path)

Universal access function; l is expected to be an arbitrary LISP list, path a (clojure) list of the characters a and d. Intended to make declaring all those fiddly #'c[ad]+r' functions a bit easier

\ No newline at end of file +beowulf.bootstrap documentation

beowulf.bootstrap

Lisp as defined in Chapter 1 (pages 1-14) of the Lisp 1.5 Programmer's Manual; that is to say, a very simple Lisp language, which should, I believe, be sufficient in conjunction with the functions provided by beowulf.host, be sufficient to bootstrap the full Lisp 1.5 interpreter..

+

The convention is adopted that functions in this file with names in ALLUPPERCASE are Lisp 1.5 functions (although written in Clojure) and that therefore all arguments must be numbers, symbols or beowulf.cons_cell.ConsCell objects.

APPLY

(APPLY function args environment depth)

Apply this function to these arguments in this environment and return the result.

+

For bootstrapping, at least, a version of APPLY written in Clojure. All args are assumed to be symbols or beowulf.cons-cell/ConsCell objects. See page 13 of the Lisp 1.5 Programmers Manual.

EVAL

(EVAL expr)(EVAL expr env depth)

Evaluate this expr and return the result. If environment is not passed, it defaults to the current value of the global object list. The depth argument is part of the tracing system and should not be set by user code.

+

All args are assumed to be numbers, symbols or beowulf.cons-cell/ConsCell objects. However, if called with just a single arg, expr, I’ll assume it’s being called from the Clojure REPL and will coerce the expr to ConsCell.

find-target

TODO: write docs

PROG

(PROG program env depth)

The accursed PROG feature. See page 71 of the manual.

+

Lisp 1.5 introduced PROG, and most Lisps have been stuck with it ever since. It introduces imperative programming into what should be a pure functional language, and consequently it’s going to be a pig to implement.

+

Broadly, PROG is a variadic pseudo function called as a FEXPR (or possibly an FSUBR, although I’m not presently sure that would even work.)

+

The arguments, which are unevaluated, are a list of forms, the first of which is expected to be a list of symbols which will be treated as names of variables within the program, and the rest of which (the ‘program body’) are either lists or symbols. Lists are treated as Lisp expressions which may be evaulated in turn. Symbols are treated as targets for the GO statement.

+

GO: A GO statement takes the form of (GO target), where target should be one of the symbols which occur at top level among that particular invocation of PROGs arguments. A GO statement may occur at top level in a PROG, or in a clause of a COND statement in a PROG, but not in a function called from the PROG statement. When a GO statement is evaluated, execution should transfer immediately to the expression which is the argument list immediately following the symbol which is its target.

+

If the target is not found, an error with the code A6 should be thrown.

+

RETURN: A RETURN statement takes the form (RETURN value), where value is any value. Following the evaluation of a RETURN statement, the PROG should immediately exit without executing any further expressions, returning the value.

+

SET and SETQ: In addition to the above, if a SET or SETQ expression is encountered in any expression within the PROG body, it should affect not the global object list but instead only the local variables of the program.

+

COND: In strict mode, when in normal execution, a COND statement none of whose clauses match should not return NIL but should throw an error with the code A3except that inside a PROG body, it should not do so. sigh.

+

Flow of control: Apart from the exceptions specified above, expressions in the program body are evaluated sequentially. If execution reaches the end of the program body, NIL is returned.

+

Got all that?

+

Good.

prog-eval

(prog-eval expr vars env depth)

Like EVAL, q.v., except handling symbols, and expressions starting GO, RETURN, SET and SETQ specially.

try-resolve-subroutine

(try-resolve-subroutine subr args)

Attempt to resolve this subr with these args.

\ No newline at end of file diff --git a/docs/codox/beowulf.cons-cell.html b/docs/codox/beowulf.cons-cell.html index d58fe66..b222e03 100644 --- a/docs/codox/beowulf.cons-cell.html +++ b/docs/codox/beowulf.cons-cell.html @@ -1,3 +1,3 @@ -beowulf.cons-cell documentation

beowulf.cons-cell

The fundamental cons cell on which all Lisp structures are built. Lisp 1.5 lists do not necessarily have a sequence as their CDR, and must have both CAR and CDR mutable, so cannot be implemented on top of Clojure lists.

CAR

(CAR x)

Return the item indicated by the first pointer of a pair. NIL is treated specially: the CAR of NIL is NIL.

CDR

(CDR x)

Return the item indicated by the second pointer of a pair. NIL is treated specially: the CDR of NIL is NIL.

CONS

(CONS car cdr)

Construct a new instance of cons cell with this car and cdr.

cons-cell?

(cons-cell? o)

Is this object o a beowulf cons-cell?

F

The canonical false value - different from NIL, which is not canonically false in Lisp 1.5.

LIST

(LIST & args)

TODO: write docs

make-beowulf-list

(make-beowulf-list x)

Construct a linked list of cons cells with the same content as the sequence x.

make-cons-cell

(make-cons-cell car cdr)

Construct a new instance of cons cell with this car and cdr.

MutableSequence

protocol

Like a sequence, but mutable.

members

getCar

(getCar this)

Return the first element of this sequence.

getCdr

(getCdr this)

like more, q.v., but returns List NIL not Clojure nil when empty.

getUid

(getUid this)

Returns a unique identifier for this object

rplaca

(rplaca this value)

replace the first element of this sequence with this value

rplacd

(rplacd this value)

replace the rest (but-first; cdr) of this sequence with this value

pretty-print

(pretty-print cell)(pretty-print cell width level)

This isn’t the world’s best pretty printer but it sort of works.

T

The canonical true value.

\ No newline at end of file +beowulf.cons-cell documentation

beowulf.cons-cell

The fundamental cons cell on which all Lisp structures are built. Lisp 1.5 lists do not necessarily have a sequence as their CDR, and must have both CAR and CDR mutable, so cannot be implemented on top of Clojure lists.

cons-cell?

(cons-cell? o)

Is this object o a beowulf cons-cell?

F

The canonical false value - different from NIL, which is not canonically false in Lisp 1.5.

make-beowulf-list

(make-beowulf-list x)

Construct a linked list of cons cells with the same content as the sequence x.

make-cons-cell

(make-cons-cell car cdr)

Construct a new instance of cons cell with this car and cdr.

MutableSequence

protocol

Like a sequence, but mutable.

members

getCar

(getCar this)

Return the first element of this sequence.

getCdr

(getCdr this)

like more, q.v., but returns List NIL not Clojure nil when empty.

getUid

(getUid this)

Returns a unique identifier for this object

rplaca

(rplaca this value)

replace the first element of this sequence with this value

rplacd

(rplacd this value)

replace the rest (but-first; cdr) of this sequence with this value

pretty-print

(pretty-print cell)(pretty-print cell width level)

This isn’t the world’s best pretty printer but it sort of works.

T

The canonical true value.

\ No newline at end of file diff --git a/docs/codox/beowulf.core.html b/docs/codox/beowulf.core.html index 37468f3..81b6d15 100644 --- a/docs/codox/beowulf.core.html +++ b/docs/codox/beowulf.core.html @@ -1,3 +1,3 @@ -beowulf.core documentation

beowulf.core

Essentially, the -main function and the bootstrap read-eval-print loop.

-main

(-main & opts)

Parse options, print the banner, read the init file if any, and enter the read/eval/print loop.

cli-options

TODO: write docs

repl

(repl prompt)

Read/eval/print loop.

stop-word

TODO: write docs

\ No newline at end of file +beowulf.core documentation

beowulf.core

Essentially, the -main function and the bootstrap read-eval-print loop.

-main

(-main & opts)

Parse options, print the banner, read the init file if any, and enter the read/eval/print loop.

cli-options

TODO: write docs

repl

(repl prompt)

Read/eval/print loop.

stop-word

The word which, if submitted an an input line, will cause Beowulf to quit. Question: should this be forlǣte?

\ No newline at end of file diff --git a/docs/codox/beowulf.gendoc.html b/docs/codox/beowulf.gendoc.html index fc240b4..b272376 100644 --- a/docs/codox/beowulf.gendoc.html +++ b/docs/codox/beowulf.gendoc.html @@ -1,3 +1,4 @@ -beowulf.gendoc documentation

beowulf.gendoc

TODO: write docs

find-documentation

(find-documentation entry)

TODO: write docs

gen-doc-table

(gen-doc-table)

TODO: write docs

host-functions

Functions which we can infer are written in Clojure.

infer-signature

(infer-signature entry)

TODO: write docs

infer-type

(infer-type entry)

TODO: write docs

\ No newline at end of file +beowulf.gendoc documentation

beowulf.gendoc

Generate table of documentation of Lisp symbols and functions.

+

NOTE: this is very hacky. You almost certainly do not want to use this!

find-documentation

(find-documentation entry)

Find appropriate documentation for this entry from the oblist.

gen-doc-table

(gen-doc-table)

TODO: write docs

gen-index

(gen-index)(gen-index url destination)

TODO: write docs

host-functions

Functions which we can infer are written in Clojure. We need to collect these at run-time, not compile time, hence memoised function, not variable.

infer-implementation

(infer-implementation entry)

TODO: write docs

infer-signature

(infer-signature entry)

Infer the signature of the function value of this oblist entry, if any.

infer-type

(infer-type entry)

Try to work out what this entry from the oblist actually represents.

open-doc

(open-doc symbol)

Open the documentation page for this symbol, if known, in the default web browser.

\ No newline at end of file diff --git a/docs/codox/beowulf.host.html b/docs/codox/beowulf.host.html index 11882b0..13e5a53 100644 --- a/docs/codox/beowulf.host.html +++ b/docs/codox/beowulf.host.html @@ -1,4 +1,19 @@ -beowulf.host documentation

beowulf.host

provides Lisp 1.5 functions which can’t be (or can’t efficiently be) implemented in Lisp 1.5, which therefore need to be implemented in the host language, in this case Clojure.

ADD1

(ADD1 x)

TODO: write docs

AND

(AND & args)

T if and only if none of my args evaluate to either F or NIL, else F.

-

In beowulf.host principally because I don’t yet feel confident to define varargs functions in Lisp.

DIFFERENCE

(DIFFERENCE x y)

TODO: write docs

ERROR

(ERROR & args)

Throw an error

FIXP

(FIXP x)

TODO: write docs

GENSYM

(GENSYM)

Generate a unique symbol.

GREATERP

(GREATERP x y)

TODO: write docs

LESSP

(LESSP x y)

TODO: write docs

NUMBERP

(NUMBERP x)

TODO: write docs

PLUS

(PLUS & args)

TODO: write docs

QUOTIENT

(QUOTIENT x y)

I’m not certain from the documentation whether Lisp 1.5 QUOTIENT returned the integer part of the quotient, or a realnum representing the whole quotient. I am for now implementing the latter.

REMAINDER

(REMAINDER x y)

TODO: write docs

RPLACA

(RPLACA cell value)

Replace the CAR pointer of this cell with this value. Dangerous, should really not exist, but does in Lisp 1.5 (and was important for some performance hacks in early Lisps)

RPLACD

(RPLACD cell value)

Replace the CDR pointer of this cell with this value. Dangerous, should really not exist, but does in Lisp 1.5 (and was important for some performance hacks in early Lisps)

SUB1

(SUB1 x)

TODO: write docs

TIMES

(TIMES & args)

TODO: write docs

\ No newline at end of file +beowulf.host documentation

beowulf.host

provides Lisp 1.5 functions which can’t be (or can’t efficiently be) implemented in Lisp 1.5, which therefore need to be implemented in the host language, in this case Clojure.

ADD1

(ADD1 x)

TODO: write docs

AND

(AND & args)

T if and only if none of my args evaluate to either F or NIL, else F.

+

In beowulf.host principally because I don’t yet feel confident to define varargs functions in Lisp.

ASSOC

(ASSOC x a)

If a is an association list such as the one formed by PAIRLIS in the above example, then assoc will produce the first pair whose first term is x. Thus it is a table searching function.

+

All args are assumed to be beowulf.cons-cell/ConsCell objects. See page 12 of the Lisp 1.5 Programmers Manual.

+

NOTE THAT this function is overridden by an implementation in Lisp, but is currently still present for bootstrapping.

ATOM

(ATOM x)

Returns T if and only if the argument x is bound to an atom; else F. It is not clear to me from the documentation whether (ATOM 7) should return T or F. I’m going to assume T.

ATOM?

macro

(ATOM? x)

The convention of returning F from predicates, rather than NIL, is going to tie me in knots. This is a variant of ATOM which returns NIL on failure.

CAAAAR

macro

(CAAAAR x)

TODO: write docs

CAAADR

macro

(CAAADR x)

TODO: write docs

CAAAR

macro

(CAAAR x)

TODO: write docs

CAADAR

macro

(CAADAR x)

TODO: write docs

CAADDR

macro

(CAADDR x)

TODO: write docs

CAADR

macro

(CAADR x)

TODO: write docs

CAAR

macro

(CAAR x)

TODO: write docs

CADAAR

macro

(CADAAR x)

TODO: write docs

CADADR

macro

(CADADR x)

TODO: write docs

CADAR

macro

(CADAR x)

TODO: write docs

CADDAR

macro

(CADDAR x)

TODO: write docs

CADDDR

macro

(CADDDR x)

TODO: write docs

CADDR

macro

(CADDR x)

TODO: write docs

CADR

macro

(CADR x)

TODO: write docs

CAR

(CAR x)

Return the item indicated by the first pointer of a pair. NIL is treated specially: the CAR of NIL is NIL.

CDAAAR

macro

(CDAAAR x)

TODO: write docs

CDAADR

macro

(CDAADR x)

TODO: write docs

CDAAR

macro

(CDAAR x)

TODO: write docs

CDADAR

macro

(CDADAR x)

TODO: write docs

CDADDR

macro

(CDADDR x)

TODO: write docs

CDADR

macro

(CDADR x)

TODO: write docs

CDAR

macro

(CDAR x)

TODO: write docs

CDDAAR

macro

(CDDAAR x)

TODO: write docs

CDDADR

macro

(CDDADR x)

TODO: write docs

CDDAR

macro

(CDDAR x)

TODO: write docs

CDDDAR

macro

(CDDDAR x)

TODO: write docs

CDDDDR

macro

(CDDDDR x)

TODO: write docs

CDDDR

macro

(CDDDR x)

TODO: write docs

CDDR

macro

(CDDR x)

TODO: write docs

CDR

(CDR x)

Return the item indicated by the second pointer of a pair. NIL is treated specially: the CDR of NIL is NIL.

CONS

(CONS car cdr)

Construct a new instance of cons cell with this car and cdr.

CONSP

(CONSP o)

Return T if object o is a cons cell, else F.

+

NOTE THAT this is an extension function, not available in strct mode. I believe that Lisp 1.5 did not have any mechanism for testing whether an argument was, or was not, a cons cell.

DEFINE

(DEFINE a-list)

Bootstrap-only version of DEFINE which, post boostrap, can be overwritten in LISP.

+

The single argument to DEFINE should be an association list of symbols to lambda functions. See page 58 of the manual.

DEFLIST

(DEFLIST a-list indicator)

For each pair in this association list a-list, set the property with this indicator of the symbol which is the first element of the pair to the value which is the second element of the pair. See page 58 of the manual.

DIFFERENCE

(DIFFERENCE x y)

TODO: write docs

DOC

(DOC symbol)

Open the page for this symbol in the Lisp 1.5 manual, if known, in the default web browser.

+

NOTE THAT this is an extension function, not available in strct mode.

EQ

(EQ x y)

Returns T if and only if both x and y are bound to the same atom, else NIL.

EQUAL

(EQUAL x y)

This is a predicate that is true if its two arguments are identical S-expressions, and false if they are different. (The elementary predicate EQ is defined only for atomic arguments.) The definition of EQUAL is an example of a conditional expression inside a conditional expression.

+

NOTE: returns F on failure, not NIL

ERROR

(ERROR & args)

Throw an error

FIXP

(FIXP x)

TODO: write docs

GENSYM

(GENSYM)

Generate a unique symbol.

GET

(GET symbol indicator)

From the manual:

+

get is somewhat like prop; however its value is car of the rest of the list if the indicator is found, and NIL otherwise.’

+

It’s clear that GET is expected to be defined in terms of PROP, but we can’t implement PROP here because we lack EVAL; and we can’t have EVAL here because both it and APPLY depends on GET.

+

OK, It’s worse than that: the statement of the definition of GET (and of) PROP on page 59 says that the first argument to each must be a list; But the in the definition of ASSOC on page 70, when GET is called its first argument is always an atom. Since it’s ASSOC and EVAL which I need to make work, I’m going to assume that page 59 is wrong.

GREATERP

(GREATERP x y)

TODO: write docs

lax?

(lax? symbol)

Are we in lax mode? If so. return true; is not, throw an exception with this symbol.

LESSP

(LESSP x y)

TODO: write docs

LIST

(LIST & args)

TODO: write docs

magic-marker

The unexplained magic number which marks the start of a property list.

NILP

macro

(NILP x)

Not part of LISP 1.5: T if o is NIL, else NIL.

NULL

macro

(NULL x)

Returns T if and only if the argument x is bound to NIL; else F.

NUMBERP

(NUMBERP x)

TODO: write docs

OBLIST

(OBLIST)

Return a list of the symbols currently bound on the object list.

+

NOTE THAT in the Lisp 1.5 manual, footnote at the bottom of page 69, it implies that an argument can be passed but I’m not sure of the semantics of this.

OR

(OR & args)

T if and only if at least one of my args evaluates to something other than either F or NIL, else F.

+

In beowulf.host principally because I don’t yet feel confident to define varargs functions in Lisp.

PAIRLIS

(PAIRLIS x y a)

This function gives the list of pairs of corresponding elements of the lists x and y, and APPENDs this to the list a. The resultant list of pairs, which is like a table with two columns, is called an association list.

+

Eessentially, it builds the environment on the stack, implementing shallow binding.

+

All args are assumed to be beowulf.cons-cell/ConsCell objects. See page 12 of the Lisp 1.5 Programmers Manual.

+

NOTE THAT this function is overridden by an implementation in Lisp, but is currently still present for bootstrapping.

PLUS

(PLUS & args)

TODO: write docs

PUT

(PUT symbol indicator value)

Put this value as the value of the property indicated by this indicator of this symbol. Return value on success.

+

NOTE THAT there is no PUT defined in the manual, but it would have been easy to have defined it so I don’t think this fully counts as an extension.

QUOTIENT

(QUOTIENT x y)

I’m not certain from the documentation whether Lisp 1.5 QUOTIENT returned the integer part of the quotient, or a realnum representing the whole quotient. I am for now implementing the latter.

REMAINDER

(REMAINDER x y)

TODO: write docs

RPLACA

(RPLACA cell value)

Replace the CAR pointer of this cell with this value. Dangerous, should really not exist, but does in Lisp 1.5 (and was important for some performance hacks in early Lisps)

RPLACD

(RPLACD cell value)

Replace the CDR pointer of this cell with this value. Dangerous, should really not exist, but does in Lisp 1.5 (and was important for some performance hacks in early Lisps)

SET

(SET symbol val)

Implementation of SET in Clojure. Add to the oblist a binding of the value of var to the value of val. NOTE WELL: this is not SETQ!

SUB1

(SUB1 x)

TODO: write docs

TIMES

(TIMES & args)

TODO: write docs

TRACE

(TRACE s)

Add this s to the set of symbols currently being traced. If s is not a symbol or sequence of symbols, does nothing.

traced-symbols

Symbols currently being traced.

traced?

(traced? s)

Return true iff s is a symbol currently being traced, else nil.

uaf

(uaf l path)

Universal access function; l is expected to be an arbitrary LISP list, path a (clojure) list of the characters a and d. Intended to make declaring all those fiddly #'c[ad]+r' functions a bit easier

UNTRACE

(UNTRACE s)

Remove this s from the set of symbols currently being traced. If s is not a symbol or sequence of symbols, does nothing.

\ No newline at end of file diff --git a/docs/codox/beowulf.interop.html b/docs/codox/beowulf.interop.html new file mode 100644 index 0000000..9e56d75 --- /dev/null +++ b/docs/codox/beowulf.interop.html @@ -0,0 +1,11 @@ + +beowulf.interop documentation

beowulf.interop

TODO: write docs

INTEROP

(INTEROP fn-symbol args)

Clojure (or other host environment) interoperation API. fn-symbol is expected to be either

+
    +
  1. a symbol bound in the host environment to a function; or
  2. +
  3. a sequence (list) of symbols forming a qualified path name bound to a function.
  4. +
+

Lower case characters cannot normally be represented in Lisp 1.5, so both the upper case and lower case variants of fn-symbol will be tried. If the function you’re looking for has a mixed case name, that is not currently accessible.

+

args is expected to be a Lisp 1.5 list of arguments to be passed to that function. Return value must be something acceptable to Lisp 1.5, so either a symbol, a number, or a Lisp 1.5 list.

+

If fn-symbol is not found (even when cast to lower case), or is not a function, or the value returned cannot be represented in Lisp 1.5, an exception is thrown with :cause bound to :interop and :detail set to a value representing the actual problem.

interpret-qualified-name

(interpret-qualified-name l)

For interoperation with Clojure, it will often be necessary to pass qualified names that are not representable in Lisp 1.5. This function takes a sequence in the form (PART PART PART... NAME) and returns a symbol in the form part.part.part/NAME. This symbol will then be tried in both that form and lower-cased. Names with hyphens or underscores cannot be represented with this scheme.

listify-qualified-name

(listify-qualified-name subr)

We need to be able to print something we can link to the particular Clojure function subr in a form in which Lisp 1.5 is able to read it back in and relink it.

+

This assumes subr is either 1. a string in the format #'beowulf.io/SYSIN or beowulf.io/SYSIN; or 2. something which, when coerced to a string with str, will have such a format.

to-beowulf

(to-beowulf o)

Return a beowulf-native representation of the Clojure object o. Numbers and symbols are unaffected. Collections have to be converted; strings must be converted to symbols.

to-clojure

(to-clojure l)

If l is a beowulf.cons_cell.ConsCell, return a Clojure list having the same members in the same order.

\ No newline at end of file diff --git a/docs/codox/beowulf.io.html b/docs/codox/beowulf.io.html index 1107af5..687c3b5 100644 --- a/docs/codox/beowulf.io.html +++ b/docs/codox/beowulf.io.html @@ -1,11 +1,13 @@ -beowulf.io documentation

beowulf.io

Non-standard extensions to Lisp 1.5 to read and write to the filesystem.

+beowulf.io documentation

beowulf.io

Non-standard extensions to Lisp 1.5 to read and write to the filesystem.

Lisp 1.5 had only READ, which read one S-Expression at a time, and various forms of PRIN* functions, which printed to the line printer. There was also PUNCH, which wrote to a card punch. It does not seem that there was any concept of an interactive terminal.

See Appendix E, OVERLORD - THE MONITOR, and Appendix F, LISP INPUT AND OUTPUT.

For our purposes, to save the current state of the Lisp system it should be sufficient to print the current contents of the oblist to file; and to restore a previous state from file, to overwrite the contents of the oblist with data from that file.

-

Hence functions SYSOUT and SYSIN, which do just that.

SYSIN

(SYSIN filename)

Read the contents of the file at this filename into the object list.

+

Hence functions SYSOUT and SYSIN, which do just that.

default-sysout

TODO: write docs

resolve-subr

(resolve-subr entry)(resolve-subr entry prop)

If this oblist entry references a subroutine, attempt to fix up that reference.

safely-wrap-subr

(safely-wrap-subr entry)

TODO: write docs

safely-wrap-subrs

(safely-wrap-subrs objects)

TODO: write docs

SYSIN

(SYSIN)(SYSIN filename)

Read the contents of the file at this filename into the object list.

If the file is not a valid Beowulf sysout file, this will probably corrupt the system, you have been warned. File paths will be considered relative to the filepath set when starting Lisp.

It is intended that sysout files can be read both from resources within the jar file, and from the file system. If a named file exists in both the file system and the resources, the file system will be preferred.

-

NOTE THAT if the provided filename does not end with .lsp (which, if you’re writing it from the Lisp REPL, it won’t), the extension .lsp will be appended.

SYSOUT

(SYSOUT)(SYSOUT filepath)

Dump the current content of the object list to file. If no filepath is specified, a file name will be constructed of the symbol Sysout and the current date. File paths will be considered relative to the filepath set when starting Lisp.

\ No newline at end of file +

NOTE THAT if the provided filename does not end with .lsp (which, if you’re writing it from the Lisp REPL, it won’t), the extension .lsp will be appended.

+

NOTE THAT this is an extension function, not available in strct mode.

SYSOUT

(SYSOUT)(SYSOUT filepath)

Dump the current content of the object list to file. If no filepath is specified, a file name will be constructed of the symbol Sysout and the current date. File paths will be considered relative to the filepath set when starting Lisp.

+

NOTE THAT this is an extension function, not available in strct mode.

\ No newline at end of file diff --git a/docs/codox/beowulf.manual.html b/docs/codox/beowulf.manual.html new file mode 100644 index 0000000..70497cf --- /dev/null +++ b/docs/codox/beowulf.manual.html @@ -0,0 +1,3 @@ + +beowulf.manual documentation

beowulf.manual

Experimental code for accessing the manual online.

*manual-url*

dynamic

TODO: write docs

format-page-references

(format-page-references fn-symbol)

Format page references from the manual index for the function whose name is fn-symbol.

index

This is data extracted from the index pages of Lisp 1.5 Programmer's Manual. It’s here in the hope that we can automatically link to an online PDF link to the manual when the user invokes a function probably called DOC or HELP.

page-url

(page-url page-no)

Format the URL for the page in the manual with this page-no.

\ No newline at end of file diff --git a/docs/codox/beowulf.oblist.html b/docs/codox/beowulf.oblist.html index 4488f32..47df23c 100644 --- a/docs/codox/beowulf.oblist.html +++ b/docs/codox/beowulf.oblist.html @@ -1,4 +1,5 @@ -beowulf.oblist documentation

beowulf.oblist

A namespace mainly devoted to the object list.

-

Yes, this makes little sense, but if you put it anywhere else you end up in cyclic dependency hell.

*options*

dynamic

Command line options from invocation.

NIL

The canonical empty list symbol.

oblist

The default environment.

\ No newline at end of file +beowulf.oblist documentation

beowulf.oblist

A namespace mainly devoted to the object list and other top level global variables.

+

Yes, this makes little sense, but if you put them anywhere else you end up in cyclic dependency hell.

*options*

dynamic

Command line options from invocation.

NIL

The canonical empty list symbol.

+

TODO: this doesn’t really work, because (from Clojure) (empty? NIL) throws an exception. It might be better to subclass beowulf.cons_cell.ConsCell to create a new singleton class Nil which overrides the empty method of IPersistentCollection?

oblist

The default environment.

\ No newline at end of file diff --git a/docs/codox/beowulf.read.html b/docs/codox/beowulf.read.html index e9893d6..b8ac08a 100644 --- a/docs/codox/beowulf.read.html +++ b/docs/codox/beowulf.read.html @@ -1,9 +1,9 @@ -beowulf.read documentation

beowulf.read

This provides the reader required for boostrapping. It’s not a bad reader - it provides feedback on errors found in the input - but it isn’t the real Lisp reader.

+beowulf.read documentation

beowulf.read

This provides the reader required for boostrapping. It’s not a bad reader - it provides feedback on errors found in the input - but it isn’t the real Lisp reader.

Intended deviations from the behaviour of the real Lisp reader are as follows:

  1. It reads the meta-expression language MEXPR in addition to the symbolic expression language SEXPR, which I do not believe the Lisp 1.5 reader ever did;
  2. It treats everything between a double semi-colon and an end of line as a comment, as most modern Lisps do; but I do not believe Lisp 1.5 had this feature.
-

Both these extensions can be disabled by using the --strict command line switch.

gsp

(gsp s)

Shortcut macro - the internals of read; or, if you like, read-string. Argument s should be a string representation of a valid Lisp expression.

number-lines

(number-lines s)(number-lines s e)

TODO: write docs

READ

(READ)(READ input)

An implementation of a Lisp reader sufficient for bootstrapping; not necessarily the final Lisp reader. input should be either a string representation of a LISP expression, or else an input stream. A single form will be read.

read-from-console

(read-from-console)

Attempt to read a complete lisp expression from the console. NOTE that this will only really work for S-Expressions, not M-Expressions.

strip-line-comments

(strip-line-comments s)

Strip blank lines and comment lines from this string s, expected to be Lisp source.

\ No newline at end of file +

Both these extensions can be disabled by using the --strict command line switch.

gsp

(gsp s)

Shortcut macro - the internals of read; or, if you like, read-string. Argument s should be a string representation of a valid Lisp expression.

number-lines

(number-lines s)(number-lines s e)

TODO: write docs

READ

(READ)(READ input)

An implementation of a Lisp reader sufficient for bootstrapping; not necessarily the final Lisp reader. input should be either a string representation of a LISP expression, or else an input stream. A single form will be read.

read-from-console

(read-from-console)

Attempt to read a complete lisp expression from the console. NOTE that this will only really work for S-Expressions, not M-Expressions.

strip-line-comments

(strip-line-comments s)

Strip blank lines and comment lines from this string s, expected to be Lisp source.

\ No newline at end of file diff --git a/docs/codox/beowulf.reader.char-reader.html b/docs/codox/beowulf.reader.char-reader.html index 72b9707..f337b07 100644 --- a/docs/codox/beowulf.reader.char-reader.html +++ b/docs/codox/beowulf.reader.char-reader.html @@ -1,6 +1,6 @@ -beowulf.reader.char-reader documentation

beowulf.reader.char-reader

Provide sensible line editing, auto completion, and history recall.

+beowulf.reader.char-reader documentation

beowulf.reader.char-reader

Provide sensible line editing, auto completion, and history recall.

None of what’s needed here is really working yet, and a pull request with a working implementation would be greatly welcomed.

What’s needed (rough specification)

    @@ -9,6 +9,5 @@
  1. and scroll back and forward through history, but ideally I’d like this to be the Lisp history (i.e. the history of S-Expressions actually read by READ, rather than the strings which were supplied to READ);
  2. offers potential auto-completions taken from the value of (OBLIST), ideally the current value, not the value at the time the session started;
  3. and offer movement and editing within the line.
  4. -

get-reader

Return a reader, first constructing it if necessary.

-

NOTE THAT this is not settled API. The existence and call signature of this function is not guaranteed in future versions.

read-chars

(read-chars)

A drop-in replacement for clojure.core/read-line, except that line editing and history should be enabled.

-

NOTE THAT this does not work yet, but it is in the API because I hope that it will work later!

\ No newline at end of file + +

TODO: There are multiple problems with JLine; a better solution might be to start from here: https://stackoverflow.com/questions/7931988/how-to-manipulate-control-characters

\ No newline at end of file diff --git a/docs/codox/beowulf.reader.generate.html b/docs/codox/beowulf.reader.generate.html index bd7730a..36c5dd7 100644 --- a/docs/codox/beowulf.reader.generate.html +++ b/docs/codox/beowulf.reader.generate.html @@ -1,6 +1,6 @@ -beowulf.reader.generate documentation

beowulf.reader.generate

Generating S-Expressions from parse trees.

+beowulf.reader.generate documentation

beowulf.reader.generate

Generating S-Expressions from parse trees.

From Lisp 1.5 Programmers Manual, page 10

Note that I’ve retyped much of this, since copy/pasting out of PDF is less than reliable. Any typos are mine.

Quote starts:

@@ -21,4 +21,4 @@ T->ff[car[x]]]]] (COND ((ATOM X) X) ((QUOTE T)(FF (CAR X)))))) -

quote ends

gen-cond

(gen-cond p)

Generate a cond statement from this simplified parse tree fragment p; returns nil if p does not represent a (MEXPR) cond statement.

gen-cond-clause

(gen-cond-clause p)

Generate a cond clause from this simplified parse tree fragment p; returns nil if p does not represent a cond clause.

gen-dot-terminated-list

(gen-dot-terminated-list p)

Generate a list, which may be dot-terminated, from this partial parse tree ‘p’. Note that the function acts recursively and progressively decapitates its argument, so that the argument will not always be a valid parse tree.

gen-fn-call

(gen-fn-call p)

Generate a function call from this simplified parse tree fragment p; returns nil if p does not represent a (MEXPR) function call.

gen-iexpr

(gen-iexpr tree)

TODO: write docs

generate

(generate p)

Generate lisp structure from this parse tree p. It is assumed that p has been simplified.

generate-assign

(generate-assign tree)

Generate an assignment statement based on this tree. If the thing being assigned to is a function signature, then we have to do something different to if it’s an atom.

generate-defn

(generate-defn tree)

TODO: write docs

generate-set

(generate-set tree)

Actually not sure what the mexpr representation of set looks like

strip-leading-zeros

(strip-leading-zeros s)(strip-leading-zeros s prefix)

read-string interprets strings with leading zeros as octal; strip any from this string s. If what’s left is empty (i.e. there were only zeros, return "0".

\ No newline at end of file +

quote ends

gen-cond

(gen-cond p context)

Generate a cond statement from this simplified parse tree fragment p; returns nil if p does not represent a (MEXPR) cond statement.

gen-cond-clause

(gen-cond-clause p context)

Generate a cond clause from this simplified parse tree fragment p; returns nil if p does not represent a cond clause.

gen-dot-terminated-list

(gen-dot-terminated-list p)

Generate a list, which may be dot-terminated, from this partial parse tree ‘p’. Note that the function acts recursively and progressively decapitates its argument, so that the argument will not always be a valid parse tree.

gen-fn-call

(gen-fn-call p context)

Generate a function call from this simplified parse tree fragment p; returns nil if p does not represent a (MEXPR) function call.

gen-iexpr

(gen-iexpr tree context)

TODO: write docs

generate

(generate p)(generate p context)

Generate lisp structure from this parse tree p. It is assumed that p has been simplified.

generate-assign

(generate-assign tree context)

Generate an assignment statement based on this tree. If the thing being assigned to is a function signature, then we have to do something different to if it’s an atom.

generate-defn

(generate-defn tree context)

TODO: write docs

generate-set

(generate-set tree context)

Actually not sure what the mexpr representation of set looks like

strip-leading-zeros

(strip-leading-zeros s)(strip-leading-zeros s prefix)

read-string interprets strings with leading zeros as octal; strip any from this string s. If what’s left is empty (i.e. there were only zeros, return "0".

\ No newline at end of file diff --git a/docs/codox/beowulf.reader.macros.html b/docs/codox/beowulf.reader.macros.html index 7a5424f..b2fa009 100644 --- a/docs/codox/beowulf.reader.macros.html +++ b/docs/codox/beowulf.reader.macros.html @@ -1,3 +1,5 @@ -beowulf.reader.macros documentation

beowulf.reader.macros

Can I implement reader macros? let’s see!

*readmacros*

dynamic

TODO: write docs

expand-macros

(expand-macros form)

TODO: write docs

\ No newline at end of file +beowulf.reader.macros documentation

beowulf.reader.macros

Can I implement reader macros? let’s see!

+

We don’t need (at least, in the Clojure reader) to rewrite forms like 'FOO, because that’s handled by the parser. But we do need to rewrite things which don’t evaluate their arguments, like SETQ, because (unless LABEL does it, which I’m not yet sure of) we’re not yet able to implement things which don’t evaluate arguments.

+

TODO: at this stage, the following should probably also be read macros: DEFINE

*readmacros*

dynamic

TODO: write docs

expand-macros

(expand-macros form)

TODO: write docs

\ No newline at end of file diff --git a/docs/codox/beowulf.reader.parser.html b/docs/codox/beowulf.reader.parser.html index c21ace6..3f91103 100644 --- a/docs/codox/beowulf.reader.parser.html +++ b/docs/codox/beowulf.reader.parser.html @@ -1,3 +1,3 @@ -beowulf.reader.parser documentation

beowulf.reader.parser

The actual parser, supporting both S-expression and M-expression syntax.

parse

Parse a string presented as argument into a parse tree which can then be operated upon further.

\ No newline at end of file +beowulf.reader.parser documentation

beowulf.reader.parser

The actual parser, supporting both S-expression and M-expression syntax.

parse

Parse a string presented as argument into a parse tree which can then be operated upon further.

\ No newline at end of file diff --git a/docs/codox/beowulf.reader.simplify.html b/docs/codox/beowulf.reader.simplify.html index b38c76b..b15b557 100644 --- a/docs/codox/beowulf.reader.simplify.html +++ b/docs/codox/beowulf.reader.simplify.html @@ -1,4 +1,4 @@ -beowulf.reader.simplify documentation

beowulf.reader.simplify

Simplify parse trees. Be aware that this is very tightly coupled with the parser.

remove-nesting

(remove-nesting tree context)

TODO: write docs

remove-optional-space

(remove-optional-space tree)

TODO: write docs

simplify

(simplify p)(simplify p context)

Simplify this parse tree p. If p is an instaparse failure object, throw an ex-info, with p as the value of its :failure key.

-

NOTE THAT it is assumed that remove-optional-space has been run on the parse tree BEFORE it is passed to simplify.

\ No newline at end of file +beowulf.reader.simplify documentation

beowulf.reader.simplify

Simplify parse trees. Be aware that this is very tightly coupled with the parser.

remove-nesting

(remove-nesting tree context)

TODO: write docs

remove-optional-space

(remove-optional-space tree)

TODO: write docs

simplify

(simplify p)

Simplify this parse tree p. If p is an instaparse failure object, throw an ex-info, with p as the value of its :failure key. Calls remove-optional-space before processing.

simplify-tree

(simplify-tree p)(simplify-tree p context)

Simplify this parse tree p. If p is an instaparse failure object, throw an ex-info, with p as the value of its :failure key.

+

NOTE THAT it is assumed that remove-optional-space has been run on the parse tree BEFORE it is passed to simplify-tree.

\ No newline at end of file diff --git a/docs/codox/beowulf.scratch.html b/docs/codox/beowulf.scratch.html new file mode 100644 index 0000000..bdd1149 --- /dev/null +++ b/docs/codox/beowulf.scratch.html @@ -0,0 +1,3 @@ + +beowulf.scratch documentation

beowulf.scratch

This namespace is for temporary functions and is intentionally excluded from Git.

accessor-body

(accessor-body l v)

TODO: write docs

accessor-symbol

(accessor-symbol l)

Generate a symbol by prepending C and appending A to this list of string fragments l.

accessors-generator

(accessors-generator n)

TODO: write docs

manual-index

TODO: write docs

mogrify-plist

(mogrify-plist entry fns)

TODO: write docs

\ No newline at end of file diff --git a/docs/codox/beowulf.trace.html b/docs/codox/beowulf.trace.html index e2df591..1e6a6d3 100644 --- a/docs/codox/beowulf.trace.html +++ b/docs/codox/beowulf.trace.html @@ -1,3 +1,3 @@ -beowulf.trace documentation

beowulf.trace

Tracing of function execution

TRACE

(TRACE s)

Add this symbol s to the set of symbols currently being traced. If s is not a symbol, does nothing.

traced-symbols

Symbols currently being traced.

traced?

(traced? s)

Return true iff s is a symbol currently being traced, else nil.

UNTRACE

(UNTRACE s)

TODO: write docs

\ No newline at end of file +beowulf.trace documentation

beowulf.trace

Tracing of function execution

TRACE

(TRACE s)

Add this symbol s to the set of symbols currently being traced. If s is not a symbol, does nothing.

traced-symbols

Symbols currently being traced.

traced?

(traced? s)

Return true iff s is a symbol currently being traced, else nil.

UNTRACE

(UNTRACE s)

TODO: write docs

\ No newline at end of file diff --git a/docs/codox/css/default.css b/docs/codox/css/default.css index 33f78fe..a445e91 100644 --- a/docs/codox/css/default.css +++ b/docs/codox/css/default.css @@ -1,12 +1,28 @@ body { font-family: Helvetica, Arial, sans-serif; font-size: 15px; + color: limegreen; + background-color: black; +} + +a { + color: lime; +} + +a:active, a:hover { + color: yellowgreen; +} + +a:visited { + color: green; } pre, code { font-family: Monaco, DejaVu Sans Mono, Consolas, monospace; font-size: 9pt; margin: 15px 0; + color: limegreen; + background-color: #111; } h1 { @@ -21,9 +37,13 @@ h2 { font-size: 25px; } +th, td { + vertical-align: top; +} + h5.license { margin: 9px 0 22px 0; - color: #555; + color: lime; font-weight: normal; font-size: 12px; font-style: italic; @@ -43,7 +63,7 @@ h5.license { left: 0; right: 0; height: 22px; - color: #f5f5f5; + color: limegreen; padding: 5px 7px; } @@ -52,8 +72,8 @@ h5.license { right: 0; bottom: 0; overflow: auto; - background: #fff; - color: #333; + background: black; + color: green; padding: 0 18px; } @@ -65,15 +85,15 @@ h5.license { } .sidebar.primary { - background: #e2e2e2; - border-right: solid 1px #cccccc; + background: #080808; + border-right: solid 1px forestgreen; left: 0; width: 250px; } .sidebar.secondary { - background: #f2f2f2; - border-right: solid 1px #d7d7d7; + background: #111; + border-right: solid 1px darkgreen; left: 251px; width: 200px; } @@ -91,8 +111,8 @@ h5.license { } #header { - background: #3f3f3f; - box-shadow: 0 0 8px rgba(0, 0, 0, 0.4); + background: #080808; + box-shadow: 0 0 8px rgba(192, 255, 192, 0.4); z-index: 100; } @@ -117,21 +137,13 @@ h5.license { text-decoration: none; } -#header a { - color: #f5f5f5; -} - -.sidebar a { - color: #333; -} - #header h2 { float: right; font-size: 9pt; font-weight: normal; margin: 4px 3px; padding: 0; - color: #bbb; + color: #5f5; } #header h2 a { @@ -146,11 +158,11 @@ h5.license { } .sidebar h3 a { - color: #444; + color: #4f4; } .sidebar h3.no-link { - color: #636363; + color: green; } .sidebar ul { @@ -175,7 +187,7 @@ h5.license { .sidebar li .no-link { display: block; - color: #777; + color: #7F7; font-style: italic; } @@ -217,8 +229,8 @@ h5.license { } .sidebar li .tree .top { - border-left: 1px solid #aaa; - border-bottom: 1px solid #aaa; + border-left: 1px solid yellowgreen; + border-bottom: 1px solid yellowgreen; height: 19px; } @@ -227,17 +239,17 @@ h5.license { } .sidebar li.branch .tree .bottom { - border-left: 1px solid #aaa; + border-left: 1px solid yellowgreen; } .sidebar.primary li.current a { - border-left: 3px solid #a33; - color: #a33; + border-left: 3px solid goldenrod; + color: goldenrod; } .sidebar.secondary li.current a { - border-left: 3px solid #33a; - color: #33a; + border-left: 3px solid yellow; + color: yellow; } .namespace-index h2 { @@ -275,7 +287,7 @@ h5.license { .public { margin: 0; - border-top: 1px solid #e0e0e0; + border-top: 1px solid lime; padding-top: 14px; padding-bottom: 6px; } @@ -293,7 +305,7 @@ h5.license { } .members h4 { - color: #555; + color: lime; font-weight: normal; font-variant: small-caps; margin: 0 0 5px 0; @@ -304,7 +316,7 @@ h5.license { padding-left: 12px; margin-top: 2px; margin-left: 7px; - border-left: 1px solid #bbb; + border-left: 1px solid #5f5; } #content .members .inner h3 { @@ -357,7 +369,7 @@ h4.dynamic { } h4.added { - color: #508820; + color: #7acc32; } h4.deprecated { @@ -397,7 +409,7 @@ h4.deprecated { .type-sig { clear: both; - color: #088; + color: goldenrod; } .type-sig pre { @@ -407,8 +419,8 @@ h4.deprecated { .usage code { display: block; - color: #008; margin: 2px 0; + color: limegreen; } .usage code:first-child { @@ -476,27 +488,27 @@ p { } .markdown pre > code, .src-link a { - border: 1px solid #e4e4e4; + border: 1px solid lime; border-radius: 2px; } .markdown code:not(.hljs), .src-link a { - background: #f6f6f6; + background: #111; } pre.deps { display: inline-block; margin: 0 10px; - border: 1px solid #e4e4e4; + border: 1px solid lime; border-radius: 2px; padding: 10px; - background-color: #f6f6f6; + background-color: #111; } .markdown hr { border-style: solid; border-top: none; - color: #ccc; + color: goldenrod; } .doc ul, .doc ol { @@ -509,12 +521,12 @@ pre.deps { } .doc table td, .doc table th { - border: 1px solid #dddddd; + border: 1px solid goldenrod; padding: 4px 6px; } .doc table th { - background: #f2f2f2; + background: #111; } .doc dl { @@ -525,7 +537,7 @@ pre.deps { font-weight: bold; margin: 0; padding: 3px 0; - border-bottom: 1px solid #ddd; + border-bottom: 1px solid goldenrod; } .doc dl dd { @@ -534,7 +546,7 @@ pre.deps { } .doc abbr { - border-bottom: 1px dotted #333; + border-bottom: 1px dotted goldenrod; font-variant: none; cursor: help; } @@ -547,5 +559,5 @@ pre.deps { font-size: 70%; padding: 1px 4px; text-decoration: none; - color: #5555bb; + color: lime5bb; } diff --git a/docs/codox/further_reading.html b/docs/codox/further_reading.html new file mode 100644 index 0000000..fa767f4 --- /dev/null +++ b/docs/codox/further_reading.html @@ -0,0 +1,17 @@ + +Further Reading

Further Reading

+
    +
  1. CODING for the MIT-IBM 704 COMPUTER, October 1957 This paper is not about Lisp. But it is about the particular individual computer on which Lisp was first implemented, and it is written in part by members of the Lisp team. I have found it useful in understanding the software environment in which, and the constraints under which, Lisp was written.
  2. +
  3. MIT AI Memo 1, John McCarthy, September 1958 This is, as far as I can find, the earliest specification document of the Lisp project.
  4. +
  5. Lisp 1 Programmer’s Manual, Phyllis Fox, March 1960
  6. +
  7. Lisp 1.5 Programmer’s Manual, Michael I. Levin, August 1962 This book is essential reading: it documents in some detail the first fully realised Lisp language system.
  8. +
  9. Early LISP History (1956 - 1959), Herbert Stoyan, August 1984
  10. +
  11. +

    The Roots of Lisp, Paul Graham, 2001

  12. +
  13. +

    The Revenge of the Nerds, Paul Graham, 2002 This is mainly about why to use Lisp as a language for modern commercial software, but has useful insights into where it comes from.

    +
    +

    So the short explanation of why this 1950s language is not obsolete is that it was not technology but math, and math doesn’t get stale.

    +
  14. +
\ No newline at end of file diff --git a/docs/codox/index.html b/docs/codox/index.html index 2c78db7..80e307d 100644 --- a/docs/codox/index.html +++ b/docs/codox/index.html @@ -1,3 +1,3 @@ -Beowulf 0.2.1-SNAPSHOT

Beowulf 0.2.1-SNAPSHOT

Released under the GPL-2.0-or-later

An implementation of LISP 1.5 in Clojure.

Installation

To install, add the following dependency to your project or build file:

[beowulf "0.2.1-SNAPSHOT"]

Topics

Namespaces

beowulf.bootstrap

Lisp as defined in Chapter 1 (pages 1-14) of the Lisp 1.5 Programmer's Manual; that is to say, a very simple Lisp language, which should, I believe, be sufficient in conjunction with the functions provided by beowulf.host, be sufficient to bootstrap the full Lisp 1.5 interpreter..

beowulf.cons-cell

The fundamental cons cell on which all Lisp structures are built. Lisp 1.5 lists do not necessarily have a sequence as their CDR, and must have both CAR and CDR mutable, so cannot be implemented on top of Clojure lists.

beowulf.core

Essentially, the -main function and the bootstrap read-eval-print loop.

Public variables and functions:

beowulf.host

provides Lisp 1.5 functions which can’t be (or can’t efficiently be) implemented in Lisp 1.5, which therefore need to be implemented in the host language, in this case Clojure.

beowulf.io

Non-standard extensions to Lisp 1.5 to read and write to the filesystem.

Public variables and functions:

beowulf.oblist

A namespace mainly devoted to the object list.

Public variables and functions:

beowulf.read

This provides the reader required for boostrapping. It’s not a bad reader - it provides feedback on errors found in the input - but it isn’t the real Lisp reader.

Public variables and functions:

beowulf.reader.char-reader

Provide sensible line editing, auto completion, and history recall.

Public variables and functions:

beowulf.reader.macros

Can I implement reader macros? let’s see!

Public variables and functions:

beowulf.reader.parser

The actual parser, supporting both S-expression and M-expression syntax.

Public variables and functions:

beowulf.reader.simplify

Simplify parse trees. Be aware that this is very tightly coupled with the parser.

Public variables and functions:

beowulf.trace

Tracing of function execution

Public variables and functions:

\ No newline at end of file +Beowulf 0.3.0-SNAPSHOT

Beowulf 0.3.0-SNAPSHOT

Released under the GPL-2.0-or-later

LISP 1.5 is to all Lisp dialects as Beowulf is to English literature.

Installation

To install, add the following dependency to your project or build file:

[beowulf "0.3.0-SNAPSHOT"]

Topics

Namespaces

beowulf.bootstrap

Lisp as defined in Chapter 1 (pages 1-14) of the Lisp 1.5 Programmer's Manual; that is to say, a very simple Lisp language, which should, I believe, be sufficient in conjunction with the functions provided by beowulf.host, be sufficient to bootstrap the full Lisp 1.5 interpreter..

Public variables and functions:

beowulf.cons-cell

The fundamental cons cell on which all Lisp structures are built. Lisp 1.5 lists do not necessarily have a sequence as their CDR, and must have both CAR and CDR mutable, so cannot be implemented on top of Clojure lists.

beowulf.core

Essentially, the -main function and the bootstrap read-eval-print loop.

Public variables and functions:

beowulf.gendoc

Generate table of documentation of Lisp symbols and functions.

beowulf.host

provides Lisp 1.5 functions which can’t be (or can’t efficiently be) implemented in Lisp 1.5, which therefore need to be implemented in the host language, in this case Clojure.

beowulf.io

Non-standard extensions to Lisp 1.5 to read and write to the filesystem.

beowulf.manual

Experimental code for accessing the manual online.

Public variables and functions:

beowulf.oblist

A namespace mainly devoted to the object list and other top level global variables.

Public variables and functions:

beowulf.read

This provides the reader required for boostrapping. It’s not a bad reader - it provides feedback on errors found in the input - but it isn’t the real Lisp reader.

Public variables and functions:

beowulf.reader.char-reader

Provide sensible line editing, auto completion, and history recall.

Public variables and functions:

    beowulf.reader.macros

    Can I implement reader macros? let’s see!

    Public variables and functions:

    beowulf.reader.parser

    The actual parser, supporting both S-expression and M-expression syntax.

    Public variables and functions:

    beowulf.reader.simplify

    Simplify parse trees. Be aware that this is very tightly coupled with the parser.

    \ No newline at end of file diff --git a/docs/codox/intro.html b/docs/codox/intro.html index c695c6e..2cd54be 100644 --- a/docs/codox/intro.html +++ b/docs/codox/intro.html @@ -1,363 +1,785 @@ -beowulf

    beowulf

    -

    LISP 1.5 is to all Lisp dialects as Beowulf is to Emglish literature.

    +beowulf

    beowulf

    +

    Þý liste cræfte spræc

    +

    LISP 1.5 is to all Lisp dialects as Beowulf is to English literature.

    +

    Beowulf logo

    +

    Contents

    + +Table of contents generated with markdown-toc

    What this is

    A work-in-progress towards an implementation of Lisp 1.5 in Clojure. The objective is to build a complete and accurate implementation of Lisp 1.5 as described in the manual, with, in so far as is possible, exactly the same bahaviour - except as documented below.

    +

    BUT WHY?!!?!

    +

    Because.

    +

    Because Lisp is the only computer language worth learning, and if a thing is worth learning, it’s worth learning properly; which means going back to the beginning and trying to understand that.

    +

    Because there is, so far as I know, no working implementation of Lisp 1.5 for modern machines.

    +

    Because I’m barking mad, and this is therapy.

    Status

    -

    Boots to REPL, but few functions yet available.

    +

    Working Lisp interpreter, but some key features not yet implemented.

    -

    Building and Invoking

    -

    Build with

    -
    lein uberjar
    -
    +

    Project Target

    +

    The project target is to be able to run the Wang algorithm for the propositional calculus given in chapter 8 of the Lisp 1.5 Programmer’s Manual. When that runs, the project is as far as I am concerned feature complete. I may keep tinkering with it after that and I’ll certainly accept pull requests which are in the spirit of the project (i.e. making Beowulf more usable, and/or implementing parts of Lisp 1.5 which I have not implemented), but this isn’t intended to be a new language for doing real work; it’s an educational and archaeological project, not serious engineering.

    +

    Some readline-like functionality would be really useful, but my attempt to integrate JLine has not (yet) been successful.

    +

    An in-core structure editor would be an extremely nice thing, and I may well implement one.

    +

    You are of course welcome to fork the project and do whatever you like with it!

    +

    Invoking

    Invoke with

    -
    java -jar target/uberjar/beowulf-0.2.1-SNAPSHOT-standalone.jar --help
    +
    java -jar target/uberjar/beowulf-0.3.0-SNAPSHOT-standalone.jar --help
     

    (Obviously, check your version number)

    Command line arguments as follows:

      -h, --help                               Print this message
       -p PROMPT, --prompt PROMPT               Set the REPL prompt to PROMPT
    -  -r INITFILE, --read INITFILE             Read Lisp functions from the file INITFILE
    -  -s, --strict                             Strictly interpret the Lisp 1.5 language, without extensions.
    +  -r INITFILE, --read SYSOUTFILE           Read Lisp sysout from the file SYSOUTFILE 
    +                                           (defaults to `resources/lisp1.5.lsp`)
    +  -s, --strict                             Strictly interpret the Lisp 1.5 language, 
    +                                           without extensions.
     

    To end a session, type STOP at the command prompt.

    +

    Building and Invoking

    +

    Build with

    +
    lein uberjar
    +
    +

    Reader macros

    +

    Currently SETQ and DEFUN are implemented as reader macros, sort of. It would now be possible to reimplement them as FEXPRs and so the reader macro functionality will probably go away.

    Functions and symbols implemented

    -

    The following functions and symbols are implemented:

    - + + + - - + + + - - + + + - - + + - - + + + - - + + + - - - + + + + - - + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + + - + + + + + + + + - - + + + - - - + + + + - - + + + - - + + + + + + + + + + + + + + + + + - - + + + - - + + + - - + + + - - - + + + + + + + + + + + - - + + + - - + + + - - + + + - - + + + - - - + + + + - - + + + - - + + + - - + + + + + + + + + + - - + + + - - + + + + + + + + + + - - + + + - - + + + - - + + + - - + + + + + + + + + + - - + + + + + + + + + + - - + + + + + - + - + + + + + + + + - - + + + + + + + + + + - - + + + + + + + + + + - - + + + - - + + + - - + + + - - + + + - - + + + + + + + + + + - - + + + - - + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + - - - + + + + + - - + + - - + + + + + + + + + + + + - + - - + + - - + + +
    Symbol Function Type Signature Implementation Documentation
    NIL Lisp variable ? null ? see manual pages 22, 69
    T Lisp variable ? null ? see manual pages 22, 69
    F Lisp variable ? null ? see manual pages 22, 69
    ADD1 Host function ([x]) Host lambda function ? ?
    AND Host function ([& args]) Host lambda function ? PREDICATE T if and only if none of my args evaluate to either F or NIL, else F. In beowulf.host principally because I don’t yet feel confident to define varargs functions in Lisp.
    APPEND Host function ([x y]) Append the the elements of y to the elements of x. All args are assumed to be beowulf.cons-cell/ConsCell objects. See page 11 of the Lisp 1.5 Programmers Manual. Lisp lambda function ? see manual pages 11, 61
    APPLY Host function ([function args environment depth]) Host lambda function ? Apply this function to these arguments in this environment and return the result. For bootstrapping, at least, a version of APPLY written in Clojure. All args are assumed to be symbols or beowulf.cons-cell/ConsCell objects. See page 13 of the Lisp 1.5 Programmers Manual.
    ASSOC Lisp lambda function, Host lambda function ? ? If a is an association list such as the one formed by PAIRLIS in the above example, then assoc will produce the first pair whose first term is x. Thus it is a table searching function. All args are assumed to be beowulf.cons-cell/ConsCell objects. See page 12 of the Lisp 1.5 Programmers Manual. NOTE THAT this function is overridden by an implementation in Lisp, but is currently still present for bootstrapping.
    ATOM Host function ([x]) Host lambda function ? PREDICATE Returns T if and only if the argument x is bound to an atom; else F. It is not clear to me from the documentation whether (ATOM 7) should return T or F. I’m going to assume T.
    CAR Host lambda function ? Return the item indicated by the first pointer of a pair. NIL is treated specially: the CAR of NIL is NIL.
    CAAAAR Lisp lambda function ? ? ?
    CAAADR Lisp lambda function ? ? ?
    CAAAR Lisp lambda function ? ? ?
    CAADAR Lisp lambda function ? ? ?
    CAADDR Lisp lambda function ? ? ?
    CAADR Lisp lambda function ? ? ?
    CAAR Lisp lambda function ? ? ?
    CADAAR Lisp lambda function ? ? ?
    CADADR Lisp lambda function ? ? ?
    CADAR Lisp lambda function ? ? ?
    CADDAR Lisp lambda function ? ? ?
    CADDDR Lisp lambda function ? ? ?
    CADDR Lisp lambda function ? ? ?
    CADR Lisp lambda function ? ? ?
    CDAAAR Lisp lambda function ? ? ?
    CDAADR Lisp lambda function ? ? ?
    CDAAR Lisp lambda function ? ? ?
    CDADAR Lisp lambda function ? ? ?
    CDADDR Lisp lambda function ? ? ?
    CDADR Lisp lambda function ? ? ?
    CDAR Lisp lambda function ? ? ?
    CDDAAR Lisp lambda function ? ? ?
    CDDADR Lisp lambda function ? ? ?
    CDDAR Lisp lambda function ? ? ?
    CDDDAR Lisp lambda function ? ? ?
    CDDDDR Lisp lambda function ? ? ?
    CDDDR Lisp lambda function ? ? ?
    CDDR Lisp lambda function ? ? null ?
    CDR Host lambda function ? null ? Return the item indicated by the second pointer of a pair. NIL is treated specially: the CDR of NIL is NIL.
    CONS Host lambda function ? null Construct a new instance of cons cell with this car and cdr.
    CONSP Host lambda function ? ? Return T if object o is a cons cell, else F. NOTE THAT this is an extension function, not available in strct mode. I believe that Lisp 1.5 did not have any mechanism for testing whether an argument was, or was not, a cons cell.
    COPY Lisp function (X) Lisp lambda function ? see manual pages 62
    DEFINE Host function ([args]) Bootstrap-only version of DEFINE which, post boostrap, can be overwritten in LISP. The single argument to DEFINE should be an assoc list which should be nconc’ed onto the front of the oblist. Broadly, (SETQ OBLIST (NCONC ARG1 OBLIST)) Host lambda function ? PSEUDO-FUNCTION Bootstrap-only version of DEFINE which, post boostrap, can be overwritten in LISP. The single argument to DEFINE should be an association list of symbols to lambda functions. See page 58 of the manual.
    DIFFERENCE Host function ([x y]) Host lambda function ? ?
    DIVIDE Lisp function (X Y) Lisp lambda function ? see manual pages 26, 64
    DOC Host lambda function ? ? Open the page for this symbol in the Lisp 1.5 manual, if known, in the default web browser. NOTE THAT this is an extension function, not available in strct mode.
    EFFACE Lisp lambda function ? PSEUDO-FUNCTION see manual pages 63
    ERROR Host function ([& args]) Host lambda function ? PSEUDO-FUNCTION Throw an error
    EQ Host function ([x y]) Host lambda function ? PREDICATE Returns T if and only if both x and y are bound to the same atom, else NIL.
    EQUAL Host function ([x y]) Host lambda function ? PREDICATE This is a predicate that is true if its two arguments are identical S-expressions, and false if they are different. (The elementary predicate EQ is defined only for atomic arguments.) The definition of EQUAL is an example of a conditional expression inside a conditional expression. NOTE: returns F on failure, not NIL
    EVAL Host function ([expr] [expr env depth]) Evaluate this expr and return the result. If environment is not passed, it defaults to the current value of the global object list. The depth argument is part of the tracing system and should not be set by user code. All args are assumed to be numbers, symbols or beowulf.cons-cell/ConsCell objects. Host lambda function ? Evaluate this expr and return the result. If environment is not passed, it defaults to the current value of the global object list. The depth argument is part of the tracing system and should not be set by user code. All args are assumed to be numbers, symbols or beowulf.cons-cell/ConsCell objects. However, if called with just a single arg, expr, I’ll assume it’s being called from the Clojure REPL and will coerce the expr to ConsCell.
    FACTORIAL Lisp lambda function ? ? ?
    FIXP Host function ([x]) Host lambda function ? PREDICATE ?
    GENSYM Host function ([]) Host lambda function ? Generate a unique symbol.
    GET Lisp function (X Y) Host lambda function ? From the manual: ‘get is somewhat like prop; however its value is car of the rest of the list if the indicator is found, and NIL otherwise.’ It’s clear that GET is expected to be defined in terms of PROP, but we can’t implement PROP here because we lack EVAL; and we can’t have EVAL here because both it and APPLY depends on GET. OK, It’s worse than that: the statement of the definition of GET (and of) PROP on page 59 says that the first argument to each must be a list; But the in the definition of ASSOC on page 70, when GET is called its first argument is always an atom. Since it’s ASSOC and EVAL which I need to make work, I’m going to assume that page 59 is wrong.
    GREATERP Host function ([x y]) Host lambda function ? PREDICATE ?
    INTEROP Host function ([fn-symbol args]) Clojure (or other host environment) interoperation API. fn-symbol is expected to be either 1. a symbol bound in the host environment to a function; or 2. a sequence (list) of symbols forming a qualified path name bound to a function. Lower case characters cannot normally be represented in Lisp 1.5, so both the upper case and lower case variants of fn-symbol will be tried. If the function you’re looking for has a mixed case name, that is not currently accessible. args is expected to be a Lisp 1.5 list of arguments to be passed to that function. Return value must be something acceptable to Lisp 1.5, so either a symbol, a number, or a Lisp 1.5 list. If fn-symbol is not found (even when cast to lower case), or is not a function, or the value returned cannot be represented in Lisp 1.5, an exception is thrown with :cause bound to :interop and :detail set to a value representing the actual problem. Host lambda function ? ? ?
    INTERSECTION Lisp function (X Y) Lisp lambda function ? ? ?
    LENGTH Lisp function (L) Lisp lambda function ? see manual pages 62
    LESSP Host function ([x y]) Host lambda function ? PREDICATE ?
    MAPLIST Lisp lambda function ? FUNCTIONAL see manual pages 20, 21, 63
    MEMBER Lisp function (A X) Lisp lambda function ? PREDICATE see manual pages 11, 62
    MINUSP Lisp function (X) Lisp lambda function ? PREDICATE see manual pages 26, 64
    NOT Lisp lambda function ? PREDICATE see manual pages 21, 23, 58
    NULL Lisp function (X) Lisp lambda function ? PREDICATE see manual pages 11, 57
    NUMBERP Host function ([x]) Host lambda function ? PREDICATE ?
    OBLIST Host function ([]) Host lambda function ? Return a list of the symbols currently bound on the object list. NOTE THAT in the Lisp 1.5 manual, footnote at the bottom of page 69, it implies that an argument can be passed but I’m not sure of the semantics of this.
    ONEP Lisp function (X) Lisp lambda function ? PREDICATE see manual pages 26, 64
    OR Host lambda function ? PREDICATE T if and only if at least one of my args evaluates to something other than either F or NIL, else F. In beowulf.host principally because I don’t yet feel confident to define varargs functions in Lisp.
    PAIR Lisp function (X Y) Lisp lambda function ? see manual pages 60
    PAIRLIS Lisp lambda function, Host lambda function ? ? This function gives the list of pairs of corresponding elements of the lists x and y, and APPENDs this to the list a. The resultant list of pairs, which is like a table with two columns, is called an association list. Eessentially, it builds the environment on the stack, implementing shallow binding. All args are assumed to be beowulf.cons-cell/ConsCell objects. See page 12 of the Lisp 1.5 Programmers Manual. NOTE THAT this function is overridden by an implementation in Lisp, but is currently still present for bootstrapping.
    PLUS Host function ([& args]) Host lambda function ? ?
    PRETTY ? ? null ?
    PRINT ? null PSEUDO-FUNCTION see manual pages 65, 84
    PROG Host nlambda function ? The accursed PROG feature. See page 71 of the manual. Lisp 1.5 introduced PROG, and most Lisps have been stuck with it ever since. It introduces imperative programming into what should be a pure functional language, and consequently it’s going to be a pig to implement. Broadly, PROG is a variadic pseudo function called as a FEXPR (or possibly an FSUBR, although I’m not presently sure that would even work.) The arguments, which are unevaluated, are a list of forms, the first of which is expected to be a list of symbols which will be treated as names of variables within the program, and the rest of which (the ‘program body’) are either lists or symbols. Lists are treated as Lisp expressions which may be evaulated in turn. Symbols are treated as targets for the GO statement. GO: A GO statement takes the form of (GO target), where target should be one of the symbols which occur at top level among that particular invocation of PROGs arguments. A GO statement may occur at top level in a PROG, or in a clause of a COND statement in a PROG, but not in a function called from the PROG statement. When a GO statement is evaluated, execution should transfer immediately to the expression which is the argument list immediately following the symbol which is its target. If the target is not found, an error with the code A6 should be thrown. RETURN: A RETURN statement takes the form (RETURN value), where value is any value. Following the evaluation of a RETURN statement, the PROG should immediately exit without executing any further expressions, returning the value. SET and SETQ: In addition to the above, if a SET or SETQ expression is encountered in any expression within the PROG body, it should affect not the global object list but instead only the local variables of the program. COND: In strict mode, when in normal execution, a COND statement none of whose clauses match should not return NIL but should throw an error with the code A3except that inside a PROG body, it should not do so. sigh. Flow of control: Apart from the exceptions specified above, expressions in the program body are evaluated sequentially. If execution reaches the end of the program body, NIL is returned. Got all that? Good.
    PROP Lisp function (X Y U) Lisp lambda function ? FUNCTIONAL see manual pages 59
    QUOTE Lisp lambda function ? see manual pages 10, 22, 71
    QUOTIENT Host function ([x y]) Host lambda function ? I’m not certain from the documentation whether Lisp 1.5 QUOTIENT returned the integer part of the quotient, or a realnum representing the whole quotient. I am for now implementing the latter.
    RANGE Lisp lambda function ? ? ?
    READ Host function ([] [input]) Host lambda function ? PSEUDO-FUNCTION An implementation of a Lisp reader sufficient for bootstrapping; not necessarily the final Lisp reader. input should be either a string representation of a LISP expression, or else an input stream. A single form will be read.
    REMAINDER Host function ([x y]) Host lambda function ? ?
    REPEAT Lisp function (N X) Lisp lambda function ? ? ?
    RPLACA Host function ([cell value]) Host lambda function ? PSEUDO-FUNCTION Replace the CAR pointer of this cell with this value. Dangerous, should really not exist, but does in Lisp 1.5 (and was important for some performance hacks in early Lisps)
    RPLACD Host function ([cell value]) Host lambda function ? PSEUDO-FUNCTION Replace the CDR pointer of this cell with this value. Dangerous, should really not exist, but does in Lisp 1.5 (and was important for some performance hacks in early Lisps)
    SEARCH Lisp lambda function ? FUNCTIONAL see manual pages 63
    SET Host function ([symbol val]) Host lambda function ? PSEUDO-FUNCTION Implementation of SET in Clojure. Add to the oblist a binding of the value of var to the value of val. NOTE WELL: this is not SETQ!
    SUB1 Lisp function (N) Lisp lambda function, Host lambda function ? ?
    SUB2 Lisp lambda function ? ? ?
    SUBLIS Lisp lambda function ? see manual pages 12, 61
    SUBST Lisp lambda function ? see manual pages 11, 61
    SYSIN Host function ([filename]) Read the contents of the file at this filename into the object list. If the file is not a valid Beowulf sysout file, this will probably corrupt the system, you have been warned. File paths will be considered relative to the filepath set when starting Lisp. It is intended that sysout files can be read both from resources within the jar file, and from the file system. If a named file exists in both the file system and the resources, the file system will be preferred. NOTE THAT if the provided filename does not end with .lsp (which, if you’re writing it from the Lisp REPL, it won’t), the extension .lsp will be appended. Host lambda function ? ? Read the contents of the file at this filename into the object list. If the file is not a valid Beowulf sysout file, this will probably corrupt the system, you have been warned. File paths will be considered relative to the filepath set when starting Lisp. It is intended that sysout files can be read both from resources within the jar file, and from the file system. If a named file exists in both the file system and the resources, the file system will be preferred. NOTE THAT if the provided filename does not end with .lsp (which, if you’re writing it from the Lisp REPL, it won’t), the extension .lsp will be appended. NOTE THAT this is an extension function, not available in strct mode.
    SYSOUT Host function ([] [filepath]) Dump the current content of the object list to file. If no filepath is specified, a file name will be constructed of the symbol Sysout and the current date. File paths will be considered relative to the filepath set when starting Lisp. Host lambda function ? ? Dump the current content of the object list to file. If no filepath is specified, a file name will be constructed of the symbol Sysout and the current date. File paths will be considered relative to the filepath set when starting Lisp. NOTE THAT this is an extension function, not available in strct mode.
    TERPRI ? null ? PSEUDO-FUNCTION see manual pages 65, 84
    TIMES Host function ([& args]) Host lambda function ? ?
    TRACE Host lambda function ? PSEUDO-FUNCTION Add this s to the set of symbols currently being traced. If s is not a symbol or sequence of symbols, does nothing.
    UNION Lisp lambda function ? ? null ?
    UNTRACE Host lambda function ? null ? PSEUDO-FUNCTION Remove this s from the set of symbols currently being traced. If s is not a symbol or sequence of symbols, does nothing.
    ZEROP Lisp function (N) Lisp lambda function ? PREDICATE see manual pages 26, 64
    +

    Functions described as ‘Lisp function’ above are defined in the default sysout file, resources/lisp1.5.lsp, which will be loaded by default unless you specify another initfile on the command line.

    +

    Functions described as ‘Host function’ are implemented in Clojure, but if you’re brave you can redefine them in Lisp and the Lisp definitions will take precedence over the Clojure implementations.

    Architectural plan

    Not everything documented in this section is yet built. It indicates the direction of travel and intended destination, not the current state.

    resources/lisp1.5.lsp

    @@ -381,19 +803,18 @@
  • It reads the meta-expression language MEXPR in addition to the symbolic expression language SEXPR, which I do not believe the Lisp 1.5 reader ever did;
  • It treats everything between a double semi-colon and an end of line as a comment, as most modern Lisps do; but I do not believe Lisp 1.5 had this feature.
  • -

    BUT WHY?!!?!

    -

    Because.

    -

    Because Lisp is the only computer language worth learning, and if a thing is worth learning, it’s worth learning properly; which means going back to the beginning and trying to understand that.

    -

    Because there is, so far as I know, no working implementation of Lisp 1.5 for modern machines.

    -

    Because I’m barking mad, and this is therapy.

    Commentary

    What’s surprised me in working on this is how much more polished Lisp 1.5 is than legend had led me to believe. The language is remarkably close to Portable Standard Lisp which is in my opinion one of the best and most usable early Lisp implementations.

    What’s even more surprising is how faithful a reimplementation of Lisp 1.5 the first Lisp dialect I learned, Acornsoft Lisp, turns out to have been.

    I’m convinced you could still use Lisp 1.5 for interesting and useful software (which isn’t to say that modern Lisps aren’t better, but this is software which is almost sixty years old).

    Installation

    -

    At present, clone the source and build it using

    -

    lein uberjar.

    -

    You will require to have Leiningen installed.

    +

    Download the latest release ‘uberjar’ and run it using:

    +
        java -jar <path name of uberjar>
    +
    +

    Or clone the source and build it using:

    +
        lein uberjar`
    +
    +

    To build it you will require to have Leiningen installed.

    Input/output

    Lisp 1.5 greatly predates modern computers. It had a facility to print to a line printer, or to punch cards on a punch-card machine, and it had a facility to read system images in from tape; but there’s no file I/O as we would currently understand it, and, because there are no character strings and the valid characters within an atom are limited, it isn’t easy to compose a sensible filename.

    I’ve provided two functions to work around this problem.

    @@ -406,6 +827,9 @@

    The Lisp 1.5 Programmer's Manual is still in print, ISBN 13 978-0-262-13011-0; but it’s also available online.

    Other Lisp 1.5 resources

    The main resource I’m aware of is the Software Preservation Society’s site, here. It has lots of fascinating stuff including full assembler listings for various obsolete processors, but I failed to find the Lisp source of Lisp functions as a text file, which is why resources/lisp1.5.lsp is largely copytyped and reconstructed from the manual.

    -

    I’m not at this time aware of any other working Lisp 1.5 implementations.

    +

    Other implementations

    +

    There’s an online (browser native) Lisp 1.5 implementation here (source code here). It even has a working compiler!

    +

    History resources

    +

    I’m compiling a list of links to historical documents on Lisp 1.5.

    License

    Copyright © 2019 Simon Brooke. Licensed under the GNU General Public License, version 2.0 or (at your option) any later version.

    \ No newline at end of file diff --git a/docs/codox/mexpr.html b/docs/codox/mexpr.html index 299043c..19ef964 100644 --- a/docs/codox/mexpr.html +++ b/docs/codox/mexpr.html @@ -1,8 +1,8 @@ -M-Expressions

    M-Expressions

    -

    M-Expressions (‘mexprs’) are the grammar which John McCarthy origininally used to write Lisp, and the grammar in which many of the function definitions in the Lisp 1.5 Programmer’s Manual are stated. However, I have not seen anywhere a claim that Lisp 1.5 could read M-Expressions, and it is not clear to me whether it was even planned that it should do so.

    -

    Rather, it seems to me probably that M-Expressions were only ever a grammar intended to be written on paper, like Backus Naur Form, to describe and to reason about algorithms.

    +Interpreting M-Expressions

    Interpreting M-Expressions

    +

    M-Expressions (‘mexprs’) are the grammar which John McCarthy origininally used to write Lisp, and the grammar in which many of the function definitions in the Lisp 1.5 Programmer’s Manual are stated. However, I have not seen anywhere a claim that Lisp 1.5 could read M-Expressions, and it is not clear to me whether it was even planned that it should do so, although the discussion on page 10 suggests that it was.

    +

    Rather, it seems to me possible that M-Expressions were only ever a grammar intended to be written on paper, like Backus Naur Form, to describe and to reason about algorithms. I think at the point at which the M-Expression grammar was written, the idea of the universal Lisp function

    I set out to make Beowulf read M-Expressions essentially out of curiousity, to see whether it could be done. I had this idea that if it could be done, I could implement most of Lisp 1.5 simply by copying in the M-Expression definitions out of the manual.

    Consequently, the Beowulf parser can parse the M-Expression grammar as stated in the manual, and generate S-Expressions from it according to the table specified on page 10 of the manual.

    There are two problems with this.

    @@ -28,7 +28,7 @@ ((EQUAL X (QUOTE NIL)) (QUOTE T)) ((QUOTE T) (QUOTE F))))))
    -

    This is certainly more prolix and more awkward, but it also risks being flat wrong.

    +

    This is certainly more prolix and more awkward.

    Is the value of NIL the atom NIL, or is it the empty list ()? If the former, then the translation from the M-Expression above is correct. However, that means that recursive functions which recurse down a list seeking the end will fail. So the latter must be the case.

    NULL is described thus (Ibid, p11):

    @@ -36,6 +36,7 @@

    NIL is used explicitly in an M-Expression for example in the definition of intersection (Ibid, p15).

    I think there is an ambiguity in referencing constants which are not bound to themselves in the M-Expression notation as given in the manual. This is particularly problematic with regards to NIL and F, but there may be others instances.

    +

    However, so long as F is bound to NIL, and NIL is also bound to NIL (both of which are true by default, although changeable by the user), and NIL is the special marker used in the CDR of the last cons cell of a flat list, this is a difference which in practice does not make a difference. I still find it worrying, though, that rebinding variables could lead to disaster.

    Curly braces

    -

    The use of curly braces is not defined in the grammar as stated on page 10. They are not used in the initial definition of APPLY on page 13, but they are used in the more developed restatement on page 70. I believe they are to be read as indicating a DO statement – a list of function calls to be made sequentially but without strict functional dependence on one another – but I don’t find the exposition here particularly clear and I’m not sure of this.

    +

    The use of curly braces is not defined in the grammar as stated on page 10. They are not used in the initial definition of APPLY on page 13, but they are used in the more developed restatement on page 70. I believe they are to be read as indicating a section of assembly code to be assembled by the Lisp Assembly Program – but I don’t find the exposition here particularly clear and I’m not sure of this.

    Consequently, the M-Expression interpreter in Beowulf does not interpret curly braces.

    \ No newline at end of file diff --git a/docs/codox/values.html b/docs/codox/values.html new file mode 100644 index 0000000..6337cb1 --- /dev/null +++ b/docs/codox/values.html @@ -0,0 +1,263 @@ + +The properties of the system, and their values

    The properties of the system, and their values

    +

    here be dragons

    +

    Lisp is the list processing language; that is what its name means. It processes data structures built of lists - which may be lists of lists, or lists of numbers, or lists of any other sort of data item provided for by the designers of the system.

    +

    But how is a list, in a computer, actually implemented?

    +

    They’re implemented as pairs, or, as the manual sometimes rather delightfully called them, ‘doublets’. Pairs of what? Pairs of pointers. Of the two pointers of a pair, the first points to the current entry of the list, and the second, by default, points to the remainder of the list, or, if the end of the list has been reached, to a special datum known as NIL which among other things indicates that the end of the list has been reached. The pair itself is normally referred to as a ‘cons cell’ for reasons which are nerdy and not important just now (all right, because they are constructed using a function called cons, which is in itself believed to be simply an abbreviation of ‘construct’).

    +

    Two functions are used to access the two pointers of the cell. In modern Lisps these functions are called first and rest, because a lot of people who aren’t greybeards find these names easier. But they aren’t the original names. The original names were CAR and CDR.

    +

    Why?

    +

    History

    +

    Lisp was originally written on an IBM 704 computer at Massachusetts Institute of Technology, almost seventy years ago.

    +

    The machine had registers which were not eight, or sixteen, or thirty two, or sixty four, bits wide, or any other number which would seem rational to modern computer scientists, but thirty six. Myth - folk memory - tells us that the machine’s memory was arranged in pages. As I understand it (but this truly is folk memory) the offset within the page of the word to be fetched was known as the ‘decrement’, while the serial number of the page in the addressing sequence was known as the ‘address’. To fetch a word from memory, you first had to select the page using the ‘address’, and secondly the word itself using the ‘decrement’. So there were specific instructions for selecting the address, and the decrement, from the register separately.

    +

    There were two mnemonics for the machine instructions used to access the content of these registers, respectively:

    +
    +
    CAR
    +
    +

    Contents of the Address part of Register; and

    +
    CDR
    +
    +

    Contents of the Decrement part of Register.

    +
    +

    Is this actually true?

    +

    I think so. If you look at page 80 of the Lisp 1 Programmer’s Manual, you will see this:

    +
    TEN                       (the TEN-Mode is entered)
    +
    +O CAR   (((A,B),C)) () \
    +                        |
    +:1 CDR  ((D,(E,F))) ()  |
    +                         > Type ins
    +:2 CONS ((G,H),         |
    +                        |
    +230 (I,J)) ()          /
    +RLN | | oo 7 a |
    +
    +O14 (read lines O and 1)
    +
    +

    Of course, this isn’t proof. If CAR and CDR used here are standard IBM 704 assembler mnemonics – as I believe they are – then what is CONS? It’s used in a syntactically identical way. If it also is an assembler mnemonic, then it’s hard to believe that, as legend relates, it is short for ‘construct’; on the other hand, if it’s a label representing an entry point into a subroutine, then why should CAR and CDR not also be labels?

    +
    +

    Edited 3rd April to add: I’ve found a document, not related to Lisp (although John McCarthy is credited as one of the authors), which does confirm – or strictly, amend – the story. This is the CODING for the MIT-IBM 704 COMPUTER, dating from October 1957. The registers of the 704 were divided into four parts, named respectively the prefix part, the address part, the tag part, and the decrement part, of 3, 15, 3, and 15 bits respectively. The decrement part was not used in addressing; that part of the folklore I was taught isn’t right. But the names are correct. Consider this excerpt :

    +
    +

    The address, tag and decrement parts of symbolic instructions are given in that order. In some cases the decrement, tag or address parts are not necessary; therefore the following combinations where OP represents the instruction abbreviation are permissible.

    +
    +

    This doesn’t prove there were individual machine instructions with the mnemonics CAR and CDR; in fact, I’m going to say with some confidence that there were not, by reference to the table of instructions appended to the same document. The instructions do have three letter mnemonics, and they do use ‘A’ and ‘D’ as abbreviations for ‘address’ and ‘decrement’ respectively, but CAR and CDR are not included.

    +

    So it seems probable that CAR and CDR were labels for subroutines, as I hypothesised above. But they were quite likely pre-existing subroutines, in use before the instantiation of the Lisp project, because they would be generally useful; and the suggestion that they are contractions of ‘contents of the address part’ and ‘contents of the decrement part’, respectively, seem confirmed.

    +

    And, going further down the rabbit hole, there’s this. In 1957, before work on the Lisp project started, McCarthy was writing functions to add list processing to the then-new FORTRAN language, on the very same IBM 704 machine.

    +
    +

    in this time any function that delivered integer values had to have a first letter X. Any function (as opposited to subroutines) had to have a last letter F in its name. Therefore the functions selecting parts of the IBM704 memory register (word) were introduced to be XCSRF, XCPRF, XCDRF, XCTRF and XCARF

    +
    +
    +

    I think that the answer has to be that if CAR and CDR had been named by the early Lisp team – John McCarthy and his immediate colleagues – they would not have been named as they were. If not FRST and REST, as in more modern Lisps, then something like P1 and P2. CAR and CDR are distinctive and memorable (and therefore in my opinion worth preserving) because they very specifically name the parts of a cons cell and of nothing else.

    +

    Let’s be clear, here: when CAR and CDR are used in Lisp, they are returning pointers, certainly – but not in the sense that one points to a page and the other to a word. Each is an offset into a cell array, which is almost certainly an array of single 36 bit words held on a single page. So both are in effect being used as decrements. Their use in Lisp is an overload onto their original semantic meaning; they are no longer being used for the purpose for which they are named.

    +

    As far as I can tell, these names first appear in print in 1960, both in the Lisp 1 Programmer’s Manual referenced above, and in McCarthy’s paper Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I. The paper was published in April so was presumably written in 1959

    +

    Grey Anatomy

    +

    The Object List

    +

    Lisp keeps track of values by associating them with names. It does so by having what is in effect a global registry of all the names it knows to which values are attached. This being a list processing language, that was of course, in early Lisps, a list: a single specialised first class list known as the ‘object list’, or oblist for short.

    +

    Of course, a list need not just be a list of single items, it can be a list of pairs: it can be a list of pairs of the form (name . value). Hold onto that, because I want to talk about another fundamental part of a working Lisp system, the stack.

    +

    The Stack

    +

    Considering the case of pure interpreter first, let’s think about how a function keeps track of the data it’s working on. In order to do its work, it probably calls other functions, to which it passes off data, and they in turn probably call further functions. So, when control returns to our first function, how does it know where its data is? The answer is that each function pushes its argument bindings onto the stack when it starts work, and pops them off again when it exits. So when control returns to a function, its own data is still on the top of the stack. Or, to be precise, actually it doesn’t; in practice the function EVAL does it for each function in turn. But it doesn’t matter: it gets done.

    +

    What is this stack? Well, it’s a list of (name . value) pairs. At least, it is in pure Lisps; Clojure, because it runs on the Java Virtual Machine and interoperates with other software running on the JVM, uses the JVM stack which is a permanently reserved vector of memory never used for anything else. Consequently it cannot be very large; and the consequence of that is that it’s very easy to crash JVM programs because they’ve run out of stack space.

    +

    The advantage of organising your stack as a vector is that on average it’s usually slightly more memory efficient, and that it’s somewhat faster to access. The disadvantage is you need a contiguous block of memory for it, and once you’ve run out, you’ve at best lost both those advantages but in the normal case your program just crashes. Also, the memory you’ve reserved for the stack isn’t available for any other use, even during the most of the time that the stack isn’t using most of it. So of course there’s a temptation to keep the amount reserved for the stack as small as possible.

    +

    It’s this brutal fragility of vector stacks – which are used by most modern computer languages – which makes software people so wary of fully exploiting the beauty and power of recursion, and I really think that’s a shame.

    +

    The advantage of organising your stack as a list is that, while there is any memory left on the machine at all, you cannot run out of stack.

    +

    ### The Spine

    +

    So, there’s an object list where you associate names and values, and there’s a stack where you associate names and values. But, why do they have to be different? And why do you have to search in two places to find the value of a name?

    +

    The answer is – or it was, and arguably it should be – that you don’t. The stack can simply be pushed onto the front of the object list. This has multiple advantages. The first and most obvious is that you only have to search in one place for the value associated with a name.

    +

    The second is more subtle: a function can mask a variable in the object list by binding the same name to a new value, and the functions to which it then calls will only see that new value. This is useful if, for example, printed output is usually sent to the user’s terminal, but for a particular operation you want to send it to a line printer or to a file on disk. You simply rebind the name of the standard output stream to your chosen output stream, and call the function whose output you want to redirect.

    +

    So, in summary, there’s a lot of merit in making the stack and the object list into a single central structure on which the architecture of our Lisp system is built. But there’s more we need to record, and it’s important.

    +

    ### Fields and Properties

    +

    No, I’m not banging on about land reform again! I’m not a total monomaniac!

    +

    But there’s more than one datum we may want to associate with a single name. A list can be more than a set of bindings between single names and single values. There’s more than one thing, for example, that I know about my friend Lucy. I know her age. I know her address. I know her height. I know her children. I know her mother. All of this information – and much more – should be associated with her.

    +

    In conventional computing systems we’d use a table. We’d put into the table a field – a column – for each datum we wanted to store about a class of things of interest. And we’d reserve space to store that datum for every record, whether every record had something to go there of not. Furthermore, we’d have to reserve space in each field for the maximum size of the datum to be stored in it – so if we needed to store full names for even some of the people we knew, and one of the people whose full name we needed to store (because he’s both very important and very irascible) was Charles Philip Arthur George Windsor, then we’d have to reserve space for thirty-six characters for the full name of everyone in our records, even if for most of them half that would be enough.

    +

    But if instead of storing a table for each sort of thing on which we hold data, and a row in that table for each item of that sort on which we store data, we simply tagged each thing on which we hold data with those things which are interesting about them? We could tag my friend Lucy with the fact she’s on pilgrimage, and what her pilgrimage route is. Those aren’t things we need to know about most people, it would be absurdly wasteful to add a column to a person table to record pilgrimage route. So in a conventional data system we would lose that data.

    +

    Lisp has had, right back from the days of Lisp 1.5 – so, for sixty-five years – a different solution. We can give every symbol arbitrarily many, arbitrarily different, properties. A property is a (name . value) pair. We don’t have to store the same properties for every object. The values of the properties don’t have to have a fixed size, and they don’t have to take up space they don’t need. It’s like having a table with as many fields as we choose, and being able to add more fields at any time.

    +

    So, in summary, I knew, in building Beowulf, that I’d have to implement property lists. I just didn’t know how I was going to do it.

    +

    Archaeology

    +

    What I’m doing with Beowulf is trying to better understand the history of Lisp by reconstructing a very early example; in this case, Lisp 1.5, from about 1962, or sixty one years ago.

    +

    I had had the naive assumption that entries on the object list in early Lisps had their CAR pointing to the symbol and their CDR pointing to the related value. Consequently, in building beowulf, I could not work out where the property list went. More careful reading of the text implies, but does not explicitly state, that my naive assumption is wrong.

    +

    Instead, it appears that the CAR points to the symbol, as expected, but the CDR points to the property list; and that on the property list there are privileged properties at least as follows:

    +
    +
    APVAL
    +
    the simple straightforward ordinary value of the symbol, considered as a variable;
    +
    EXPR
    +
    the definition of the function considered as a normal lambda expression (arguments to be evaluated before applying);
    +
    FEXPR
    +
    the definition of a function which should be applied to unevaluated arguments (what InterLisp and Portable Standard Lisp would call nlambda);
    +
    SUBR
    +
    the definition of a compiled subroutine which should be applied to evaluated arguments;
    +
    FSUBR
    +
    the definition of a compiled subroutine which should be applied to unevaluated arguments.
    +
    +

    I think there was also another privileged property value which contained the property considered as a constant, but I haven’t yet confirmed that.

    +

    From this it would seem that Lisp 1.5 was not merely a ‘Lisp 2’ but in fact a ‘Lisp 6’, with six effectively first class namespaces. In fact it’s not as bad as that, because of the way EVAL is evaluated.

    +

    Essentially the properties are tried in turn, and only the first value found is used. Thus the heirarchy is

    +
      +
    1. APVAL
    2. +
    3. EXPR
    4. +
    5. FEXPR
    6. +
    7. SUBR
    8. +
    9. FSUBR
    10. +
    +

    This means that, while the other potential values can be retrieved from the property list, interpreted definitions (if present) will always be preferred to uninterpreted definitions, and lambda function definitions (which evaluate their arguments), where present, will always be preferred to non-lamda definitions, which don’t.

    +

    BUT NOTE THAT the APVAL value is sought only when seeking a variable value for the symbol, while the others are only when seeking a function value, so Lisp 1.5 is a ‘Lisp 2’, not a ‘Lisp 1’. I strongly believe that this is wrong: a function is a value, and should be treated as such. But at the same time I do acknowledge the benefit of being able to store both source and compiled forms of the function as properties of the same symbol.

    +

    The persistent problem

    +

    There’s a view in modern software theory – with which I strongly hold – that data should be immutable. Data that changes under you is the source of all sorts of bugs. And in modern multi threaded systems, the act of reading a datum whilst some other process is writing it, or worse, two processes attempting simultaneously to write the same datum, is a source of data corruption and even crashes. So I’m very wary of mutable data; and, in modern systems where we normally have a great deal of space and a lot of processor power, making fresh copies of data structures containing the change we wanted to make is a reasonable price to pay for avoiding a whole class of bugs.

    +

    But early software was not like that. It was always constrained by the limits of the hardware on which it ran, to a degree that we are not. And the experience that we now have of the problems caused by mutable data, they did not have. So it’s core to the design of Lisp 1.5 that its lists are mutable; and, indeed, one of the biggest challenges in writing Beowulf has been implementing mutable lists in Clojure, a language carefully designed to prevent them.

    +

    But, just because Lisp 1.5 lists can be mutable, should they be? And when should they be?

    +

    The problem here is that spine of the system I talked about earlier. If we build the execution stack on top of the oblist – as at present I do – then if we make a new version of the oblist with changes in it, the new changes will not be in the copy of the oblist that the stack is built on top of; and consequently, they’ll be invisible to the running program.

    +

    What I do at present, and what I think may be good enough, is that each time execution returns to the read-eval-print loop, the REPL, the user’s command line, I rebuild a new execution stack on the top of the oblist as it exists now. So, if the last operation modified the oblist, the next operation will see the new, modified version. But if someone tried to run some persistent program which was writing stuff to property values and hoping to read them back in the same computation, that wouldn’t work, and it would be a very hard bug to trace down.

    +

    So my options are:

    +
      +
    1. To implement PUT and GET in Clojure, so that they can operate on the current copy of the object list, not the one at the base of the stack. I’m slightly unwilling to do that, because my objective is to make Beowulf ultimately as self-hosting as possible.
    2. +
    3. To implement PUT and GET in Lisp, and have them destructively modify the working copy of the object list.
    4. +
    +

    Neither of these particularly appeal.

    +

    How property lists should work

    +

    I’m still not fully understanding how property lists in Lisp 1.5 are supposed to work.

    +

    List format

    +

    Firstly, are they association lists comprising dotted pairs of (property-name . value), i.e.:

    +
    +

    ((property-name1 . value1) (property-name2 . value2) … (property-namen . valuen))

    +
    +

    I have assumed so, and that is what I presently intend to implement, but the diagrams on pages 59 and 60 seem rather to show a flat list of interleaved names and values:

    +
    +

    (property-name1 value1 property-name2 value2 … property-namen valuen)

    +
    +

    I cannot see what the benefit of this latter arrangement is, and I’m unwilling to do it, although I think it may be what was done. But if it was done that way, why was it done that way? These were bright people, and they certainly knew about association lists. So… I’m puzzled.

    +

    Function signatures

    +

    To associate the value of a property with a symbol, we need three things: we need the symbol, we need the property name, and we need the value. For this reason, Portable Standard Lisp and others has a function put with three arguments:

    +
    +

    (Put U:id IND:id PROP:any): any The indicator IND with the property PROP is placed on the property list of the id U. If the action of Put occurs, the value of PROP is returned. If either of U and IND are not ids the type mismatch error occurs and no property is placed. (Put 'Jim 'Height 68) The above returns 68 and places (Height . 68) on the property list of the id Jim

    +
    +

    Cambridge Lisp is identical to this except in lower case. InterLisp and several others have putprop:

    +
    +

    (PUTPROP ATM PROP VAL) [Function] Puts the property PROP with value VAL on the property list of ATM. VAL replaces any previous value for the property PROP on this property list. Returns VAL.

    +
    +

    The execrable Common Lisp uses its execrable macro setf but really the less said about that the better.

    +

    So I was looking for a function of three arguments to set properties, and I didn’t find one.

    +

    There’s a function DEFINE which takes one argument, an association list of pairs:

    +
    	(function-name . function-definition)`
    +
    +

    So how does that work, if what it’s doing is setting properties? If all you’re passing is pairs of name and definition, where does the property name come from?

    +

    The answer is as follows, taken from the manual:

    +
    +

    define [x] : EXPR pseudo-function

    +

    The argument of define, x, is a list of pairs

    +
    +

    ((ul vl) (u2 v2) … (un vn))

    +
    +

    where each u is a name and each v is a λ-expression for a function . For each pair, define puts an EXPR on the property list for u pointing to v. The function of define puts things on at the front of the property list. The value of define is the list of us.

    +
    +

    So, in fact, the value of the property being set by define is fixed: hard wired, not parameterised. That seems an astonishing decision, until you realise that Lisp 1.5’s creators weren’t creating their functions one by one, in a playful exploration with their system, but entering them in a batch.

    +

    Learning by doing

    +

    In fact, when I got over my surprise, I realised that that (name . function-definition) list is actually very much like this, which is an excerpt from a sysout from a Beowulf prototype:

    +
    (...
    +	(MAPLIST LAMBDA (L F) 
    +           (COND ((NULL L) NIL) 
    +                 ((QUOTE T) (CONS (F (CAR L)) (MAPLIST (CDR L) F)))))
    +  (MEMBER LAMBDA (A X)
    +            (COND ((NULL X) (QUOTE F))
    +                  ((EQ A (CAR X)) (QUOTE T)) 
    +                  ((QUOTE T) (MEMBER A (CDR X)))))
    +  (MINUSP LAMBDA (X) (LESSP X 0))
    +  (NOT LAMBDA (X) 
    +       			(COND (X (QUOTE NIL)) 
    +                  ((QUOTE T) (QUOTE T))))
    +  (NULL LAMBDA (X) 
    +        		(COND ((EQUAL X NIL) (QUOTE T)) 
    +                  (T (QUOTE F))))
    +...)
    +
    +

    I was looking at DEFINE and thinking, ‘why would one ever want to do that?’ and then I found that, behind the scenes, I was actually doing it myself.

    +

    Because the point of a sysout is you don’t write it. The point about the REPL – the Read Eval Print Loop which is the heart of the interactive Lisp development cycle, where you sit playing with things and fiddling with them interactively, and where when one thing works you get onto the next without bothering to make some special effort to record it.

    +

    The point of a sysout is that, at the end of the working day, you invoke one function

    + + + + + + + + + + + + + + + + + + + +
    Function Type Signature Implementation Documentation
    SYSOUT Host function (SYSOUT); (SYSOUT FILEPATH) SUBR Dump the current content of the object list to file. If no filepath is specified, a file name will be constructed of the symbol Sysout and the current date. File paths will be considered relative to the filepath set when starting Lisp.
    +

    At the start of the next working day, you load that sysout in and continue your session.

    +

    The sysout captures the entire working state of the machine. No-one types it in, as an operation in itself. Instead, data structures – corpuses of functions among them – simply build up on the object list almost casually, as a side effect of the fact that you’re enjoying exploring your problem and finding elegant ways of solving it. So SYSOUT and SYSIN seem to me, as someone who all his adult life has worked with Lisp interactively, as just an automatic part of the cycle of the day.

    +

    The process of discovery

    +

    The thing is, I don’t think anyone is ever going to use Beowulf the way Lisp 1.5 was used. I mean, probably, no one is ever going to use Beowulf at all; but if they did they wouldn’t use Beowulf the way Lisp 1.5 was used.

    +

    I’m a second generation software person. I have worked, in my career, with two people who personally knew and had worked with Alan Turing. I have worked with, and to an extent been mentored by, Chris Burton, who in his apprenticeship was part of the team that built the Manchester Mark One, and who in his retirement led the team who restored it. But I never knew the working conditions they were accustomed to. In my first year at university we used card punches, and, later, when we had a bit of seniority, teletypewriters (yes, that’s what TTY stands for), but by the time I’d completed my undergraduate degree and become a research associate I had a Xerox 1108 workstation with a huge bitmapped graphic screen, and an optical mouse, goddamit, running InterLisp, all to myself.

    +

    People in the heroic age did not have computers all to themselves. They did not have terminals all to themselves. They didn’t sit at a terminal experimenting in the REPL. They wrote their algorithms in pencil on paper. When they were certain they’d got it right, they’d use a card punch to punch a deck of cards carrying the text of the program, and then they were certain they’d got that right, they’d drop it into the input hopper. Some time later their batch would run, and the operator would put the consequent printout into their pigeon hole for them to collect.

    +

    (They wrote amazingly clean code, those old masters. I could tell you a story about Chris Burton, the train, and the printer driver, that software people of today simply would not believe. But it’s true. And I think that what taught them that discipline was the high cost of even small errors.)

    +

    Lisp 1.5 doesn’t have PUT, PUTPROP or DEFUN because setting properties individually, defining functions individually one at a time, was not something they ever thought about doing. And in learning that, I’ve learned more than I ever expected to about the real nature of Lisp 1.5, and the (great) people who wrote it.

    +
    +

    Deeper delving

    +

    After writing, and publishing, this essay, I went on procrastinating, which is what I do when I’m sure I’m missing something; and to procrastinate, I went on reading the earliest design documents of Lisp I could find. And so I came across the MIT AI team’s first ever memo, written by John McCarthy in September 1958. And in that, I find this:

    +
    +

    3.2.1. First we have those that extract parts of a 704 word and form a word from parts. We shall distinguish the following parts of a word and indicate each of them by a characteristic letter.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Letter Description
    w the whole word
    p the prefix (bits s, 1, 2)
    i the indicator (bits 1 and 2)
    s the sign bit
    d the decrement (bits 3-17)
    t the tag (bits 18-20)
    a the address (bits 21-35)
    +
    +

    In the discussion of functions which access properties on page 58 of the Lisp 1.5 programmer’s manual, the word ‘indicator’ is used in preference to ‘symbol’ for the name of a property: for example

    +
    +

    The function deflist is a more general defining function. Its first argument is a list of pairs as for define. Its second argument is the indicator that is to be used. After deflist has been executed with (ui vi) among its first argument, the property list of ui will begin:

    +

    If deflist or define is used twice on the same object with the same indicator, the old value will be replaced by the new one.

    +
    +

    (my emphasis).

    +

    That use of ‘indicator’ has been nagging at me for a week. It looks like a term of art. If it’s just an ordinary atomic symbol, why isn’t it called a symbol?

    +

    Is it an indicator in the special sense of the indicator part of the machine word? If it were, then the property list could just be a flat list of values. And what’s been worrying and surprising me is that property lists are shown in the manual as flat lists. Eureka? I don’t think so.

    +

    The reason I don’t think so is that there are only two bits in the indicator part of the word, so only four distinct values; whereas we know that Lisp 1.5 has (at least) five distinct indicator values, APVAL, EXPR, FEXPR, SUBR and FSUBR.

    +

    Furthermore, on page 39, we have:

    +
    +

    A property list is characterized by having the special constant 777778 (i. e., minus 1) as the first element of the list. The rest of the list contains various properties of the atomic symbol. Each property is preceded by an atomic symbol which is called its indicator.

    +
    +

    (again, my emphasis)

    +

    But I’m going to hypothesise that the properties were originally intended to be discriminated by the indicator bits in the cons cell, that they were originally coded that way, and that there was some code which depended on property lists being flat lists; and that, when it was discovered that four indicators were not enough and that something else was going to have to be used, the new format of the property list using atomic symbols as indicators was bodged in.

    +
    +

    So what this is about is I’ve spent most of a whole day procrastinating, because I’m not exactly sure how I’m going to make the change I’ve got to make. Versions of Beowulf up to and including 0.2.1 used the naive understanding of the architecture; version 0.3.0 should use the corrected version. But before it can, I need to be reasonably confident that I understand what the correct solution is.

    +

    I shall implement PUT, even though it isn’t in the spec, because it’s a useful building block on which to build DEFINE and DEFLIS, both of which are. And also, because PUT would have been very easy for the Lisp 1.5 implementers to implement, if it had been relevant to their working environment. And I shall implement property list as flat lists of interleaved ‘indicator’ symbols and values, even with that nonsense 777778 as a prefix, because now I know (or think I know) that it was a bodge, it seems right in the spirit of historical reconstruction to reconstruct the bodge.

    \ No newline at end of file diff --git a/docs/favicon.ico b/docs/favicon.ico new file mode 100644 index 0000000..1465b68 Binary files /dev/null and b/docs/favicon.ico differ diff --git a/docs/img/beowulf_logo.png b/docs/img/beowulf_logo.png new file mode 100644 index 0000000..7ec739c Binary files /dev/null and b/docs/img/beowulf_logo.png differ diff --git a/docs/img/beowulf_logo.xcf b/docs/img/beowulf_logo.xcf new file mode 100644 index 0000000..0d2fb32 Binary files /dev/null and b/docs/img/beowulf_logo.xcf differ diff --git a/docs/img/beowulf_logo_favicon.png b/docs/img/beowulf_logo_favicon.png new file mode 100644 index 0000000..660bdbe Binary files /dev/null and b/docs/img/beowulf_logo_favicon.png differ diff --git a/docs/img/beowulf_logo_med.png b/docs/img/beowulf_logo_med.png new file mode 100644 index 0000000..3af5698 Binary files /dev/null and b/docs/img/beowulf_logo_med.png differ diff --git a/docs/index.html b/docs/index.html new file mode 120000 index 0000000..2eb3014 --- /dev/null +++ b/docs/index.html @@ -0,0 +1 @@ +codox/intro.html \ No newline at end of file diff --git a/docs/lisp1.5.html b/docs/lisp1.5.html new file mode 100644 index 0000000..e69de29 diff --git a/project.clj b/project.clj index 501713b..976a128 100644 --- a/project.clj +++ b/project.clj @@ -1,28 +1,37 @@ -(defproject beowulf "0.2.1" +(defproject beowulf "0.3.0-SNAPSHOT" + :aot :all :cloverage {:output "docs/cloverage" - :ns-exclude-regex [#"beowulf\.gendoc"]} - :codox {:metadata {:doc "**TODO**: write docs" + :ns-exclude-regex [#"beowulf\.gendoc" #"beowulf\.scratch"]} + :codox {:html {:transforms [[:head] [:append + [:link {:rel "icon" + :type "image/x-icon" + :href "../img/beowulf_logo_favicon.png"}]]]} + :metadata {:doc "**TODO**: write docs" :doc/format :markdown} :output-path "docs/codox" - :source-uri "https://github.com/simon-brooke/beowulf/blob/master/{filepath}#L{line}"} - :description "An implementation of LISP 1.5 in Clojure" + :source-uri "https://github.com/simon-brooke/beowulf/blob/master/{filepath}#L{line}" + ;; :themes [:journeyman] + } + :description "LISP 1.5 is to all Lisp dialects as Beowulf is to English literature." :license {:name "GPL-2.0-or-later" - :url "https://www.eclipse.org/legal/epl-2.0/"} + :url "https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html"} :dependencies [[org.clojure/clojure "1.11.1"] + [org.clojure/math.combinatorics "0.2.0"] ;; not needed in production builds [org.clojure/math.numeric-tower "0.0.5"] [org.clojure/tools.cli "1.0.214"] [org.clojure/tools.trace "0.7.11"] [clojure.java-time "1.2.0"] [environ "1.2.0"] [instaparse "1.4.12"] - [org.jline/jline "3.23.0"] +;; [org.jline/jline "3.23.0"] [rhizome "0.2.9"] ;; not needed in production builds ] - :main ^:skip-aot beowulf.core + :main beowulf.core :plugins [[lein-cloverage "1.2.2"] [lein-codox "0.10.7"] [lein-environ "1.1.0"]] - :profiles {:uberjar {:aot :all}} + :profiles {:jar {:aot :all} + :uberjar {:aot :all}} :release-tasks [["vcs" "assert-committed"] ["change" "version" "leiningen.release/bump-version" "release"] ["vcs" "commit"] @@ -32,7 +41,5 @@ ["uberjar"] ["change" "version" "leiningen.release/bump-version"] ["vcs" "commit"]] - :target-path "target/%s" - :url "https://github.com/simon-brooke/the-great-game" - ) + :url "https://github.com/simon-brooke/the-great-game") diff --git a/resources/codox/themes/journeyman/css/default.css b/resources/codox/themes/journeyman/css/default.css new file mode 100644 index 0000000..a445e91 --- /dev/null +++ b/resources/codox/themes/journeyman/css/default.css @@ -0,0 +1,563 @@ +body { + font-family: Helvetica, Arial, sans-serif; + font-size: 15px; + color: limegreen; + background-color: black; +} + +a { + color: lime; +} + +a:active, a:hover { + color: yellowgreen; +} + +a:visited { + color: green; +} + +pre, code { + font-family: Monaco, DejaVu Sans Mono, Consolas, monospace; + font-size: 9pt; + margin: 15px 0; + color: limegreen; + background-color: #111; +} + +h1 { + font-weight: normal; + font-size: 29px; + margin: 10px 0 2px 0; + padding: 0; +} + +h2 { + font-weight: normal; + font-size: 25px; +} + +th, td { + vertical-align: top; +} + +h5.license { + margin: 9px 0 22px 0; + color: lime; + font-weight: normal; + font-size: 12px; + font-style: italic; +} + +.document h1, .namespace-index h1 { + font-size: 32px; + margin-top: 12px; +} + +#header, #content, .sidebar { + position: fixed; +} + +#header { + top: 0; + left: 0; + right: 0; + height: 22px; + color: limegreen; + padding: 5px 7px; +} + +#content { + top: 32px; + right: 0; + bottom: 0; + overflow: auto; + background: black; + color: green; + padding: 0 18px; +} + +.sidebar { + position: fixed; + top: 32px; + bottom: 0; + overflow: auto; +} + +.sidebar.primary { + background: #080808; + border-right: solid 1px forestgreen; + left: 0; + width: 250px; +} + +.sidebar.secondary { + background: #111; + border-right: solid 1px darkgreen; + left: 251px; + width: 200px; +} + +#content.namespace-index, #content.document { + left: 251px; +} + +#content.namespace-docs { + left: 452px; +} + +#content.document { + padding-bottom: 10%; +} + +#header { + background: #080808; + box-shadow: 0 0 8px rgba(192, 255, 192, 0.4); + z-index: 100; +} + +#header h1 { + margin: 0; + padding: 0; + font-size: 18px; + font-weight: lighter; + text-shadow: -1px -1px 0px #333; +} + +#header h1 .project-version { + font-weight: normal; +} + +.project-version { + padding-left: 0.15em; +} + +#header a, .sidebar a { + display: block; + text-decoration: none; +} + +#header h2 { + float: right; + font-size: 9pt; + font-weight: normal; + margin: 4px 3px; + padding: 0; + color: #5f5; +} + +#header h2 a { + display: inline; +} + +.sidebar h3 { + margin: 0; + padding: 10px 13px 0 13px; + font-size: 19px; + font-weight: lighter; +} + +.sidebar h3 a { + color: #4f4; +} + +.sidebar h3.no-link { + color: green; +} + +.sidebar ul { + padding: 7px 0 6px 0; + margin: 0; +} + +.sidebar ul.index-link { + padding-bottom: 4px; +} + +.sidebar li { + display: block; + vertical-align: middle; +} + +.sidebar li a, .sidebar li .no-link { + border-left: 3px solid transparent; + padding: 0 10px; + white-space: nowrap; +} + +.sidebar li .no-link { + display: block; + color: #7F7; + font-style: italic; +} + +.sidebar li .inner { + display: inline-block; + padding-top: 7px; + height: 24px; +} + +.sidebar li a, .sidebar li .tree { + height: 31px; +} + +.depth-1 .inner { padding-left: 2px; } +.depth-2 .inner { padding-left: 6px; } +.depth-3 .inner { padding-left: 20px; } +.depth-4 .inner { padding-left: 34px; } +.depth-5 .inner { padding-left: 48px; } +.depth-6 .inner { padding-left: 62px; } + +.sidebar li .tree { + display: block; + float: left; + position: relative; + top: -10px; + margin: 0 4px 0 0; + padding: 0; +} + +.sidebar li.depth-1 .tree { + display: none; +} + +.sidebar li .tree .top, .sidebar li .tree .bottom { + display: block; + margin: 0; + padding: 0; + width: 7px; +} + +.sidebar li .tree .top { + border-left: 1px solid yellowgreen; + border-bottom: 1px solid yellowgreen; + height: 19px; +} + +.sidebar li .tree .bottom { + height: 22px; +} + +.sidebar li.branch .tree .bottom { + border-left: 1px solid yellowgreen; +} + +.sidebar.primary li.current a { + border-left: 3px solid goldenrod; + color: goldenrod; +} + +.sidebar.secondary li.current a { + border-left: 3px solid yellow; + color: yellow; +} + +.namespace-index h2 { + margin: 30px 0 0 0; +} + +.namespace-index h3 { + font-size: 16px; + font-weight: bold; + margin-bottom: 0; +} + +.namespace-index .topics { + padding-left: 30px; + margin: 11px 0 0 0; +} + +.namespace-index .topics li { + padding: 5px 0; +} + +.namespace-docs h3 { + font-size: 18px; + font-weight: bold; +} + +.public h3 { + margin: 0; + float: left; +} + +.usage { + clear: both; +} + +.public { + margin: 0; + border-top: 1px solid lime; + padding-top: 14px; + padding-bottom: 6px; +} + +.public:last-child { + margin-bottom: 20%; +} + +.members .public:last-child { + margin-bottom: 0; +} + +.members { + margin: 15px 0; +} + +.members h4 { + color: lime; + font-weight: normal; + font-variant: small-caps; + margin: 0 0 5px 0; +} + +.members .inner { + padding-top: 5px; + padding-left: 12px; + margin-top: 2px; + margin-left: 7px; + border-left: 1px solid #5f5; +} + +#content .members .inner h3 { + font-size: 12pt; +} + +.members .public { + border-top: none; + margin-top: 0; + padding-top: 6px; + padding-bottom: 0; +} + +.members .public:first-child { + padding-top: 0; +} + +h4.type, +h4.dynamic, +h4.added, +h4.deprecated { + float: left; + margin: 3px 10px 15px 0; + font-size: 15px; + font-weight: bold; + font-variant: small-caps; +} + +.public h4.type, +.public h4.dynamic, +.public h4.added, +.public h4.deprecated { + font-size: 13px; + font-weight: bold; + margin: 3px 0 0 10px; +} + +.members h4.type, +.members h4.added, +.members h4.deprecated { + margin-top: 1px; +} + +h4.type { + color: #717171; +} + +h4.dynamic { + color: #9933aa; +} + +h4.added { + color: #7acc32; +} + +h4.deprecated { + color: #880000; +} + +.namespace { + margin-bottom: 30px; +} + +.namespace:last-child { + margin-bottom: 10%; +} + +.index { + padding: 0; + font-size: 80%; + margin: 15px 0; + line-height: 16px; +} + +.index * { + display: inline; +} + +.index p { + padding-right: 3px; +} + +.index li { + padding-right: 5px; +} + +.index ul { + padding-left: 0; +} + +.type-sig { + clear: both; + color: goldenrod; +} + +.type-sig pre { + padding-top: 10px; + margin: 0; +} + +.usage code { + display: block; + margin: 2px 0; + color: limegreen; +} + +.usage code:first-child { + padding-top: 10px; +} + +p { + margin: 15px 0; +} + +.public p:first-child, .public pre.plaintext { + margin-top: 12px; +} + +.doc { + margin: 0 0 26px 0; + clear: both; +} + +.public .doc { + margin: 0; +} + +.namespace-index .doc { + margin-bottom: 20px; +} + +.namespace-index .namespace .doc { + margin-bottom: 10px; +} + +.markdown p, .markdown li, .markdown dt, .markdown dd, .markdown td { + line-height: 22px; +} + +.markdown li { + padding: 2px 0; +} + +.markdown h2 { + font-weight: normal; + font-size: 25px; + margin: 30px 0 10px 0; +} + +.markdown h3 { + font-weight: normal; + font-size: 20px; + margin: 30px 0 0 0; +} + +.markdown h4 { + font-size: 15px; + margin: 22px 0 -4px 0; +} + +.doc, .public, .namespace .index { + max-width: 680px; + overflow-x: visible; +} + +.markdown pre > code { + display: block; + padding: 10px; +} + +.markdown pre > code, .src-link a { + border: 1px solid lime; + border-radius: 2px; +} + +.markdown code:not(.hljs), .src-link a { + background: #111; +} + +pre.deps { + display: inline-block; + margin: 0 10px; + border: 1px solid lime; + border-radius: 2px; + padding: 10px; + background-color: #111; +} + +.markdown hr { + border-style: solid; + border-top: none; + color: goldenrod; +} + +.doc ul, .doc ol { + padding-left: 30px; +} + +.doc table { + border-collapse: collapse; + margin: 0 10px; +} + +.doc table td, .doc table th { + border: 1px solid goldenrod; + padding: 4px 6px; +} + +.doc table th { + background: #111; +} + +.doc dl { + margin: 0 10px 20px 10px; +} + +.doc dl dt { + font-weight: bold; + margin: 0; + padding: 3px 0; + border-bottom: 1px solid goldenrod; +} + +.doc dl dd { + padding: 5px 0; + margin: 0 0 5px 10px; +} + +.doc abbr { + border-bottom: 1px dotted goldenrod; + font-variant: none; + cursor: help; +} + +.src-link { + margin-bottom: 15px; +} + +.src-link a { + font-size: 70%; + padding: 1px 4px; + text-decoration: none; + color: lime5bb; +} diff --git a/resources/codox/themes/journeyman/css/highlight.css b/resources/codox/themes/journeyman/css/highlight.css new file mode 100644 index 0000000..d0cdaa3 --- /dev/null +++ b/resources/codox/themes/journeyman/css/highlight.css @@ -0,0 +1,97 @@ +/* +github.com style (c) Vasily Polovnyov +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #333; + background: #f8f8f8; +} + +.hljs-comment, +.hljs-quote { + color: #998; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-subst { + color: #333; + font-weight: bold; +} + +.hljs-number, +.hljs-literal, +.hljs-variable, +.hljs-template-variable, +.hljs-tag .hljs-attr { + color: #008080; +} + +.hljs-string, +.hljs-doctag { + color: #d14; +} + +.hljs-title, +.hljs-section, +.hljs-selector-id { + color: #900; + font-weight: bold; +} + +.hljs-subst { + font-weight: normal; +} + +.hljs-type, +.hljs-class .hljs-title { + color: #458; + font-weight: bold; +} + +.hljs-tag, +.hljs-name, +.hljs-attribute { + color: #000080; + font-weight: normal; +} + +.hljs-regexp, +.hljs-link { + color: #009926; +} + +.hljs-symbol, +.hljs-bullet { + color: #990073; +} + +.hljs-built_in, +.hljs-builtin-name { + color: #0086b3; +} + +.hljs-meta { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + background: #fdd; +} + +.hljs-addition { + background: #dfd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/resources/codox/themes/journeyman/theme.edn b/resources/codox/themes/journeyman/theme.edn new file mode 100644 index 0000000..e1fdd5e --- /dev/null +++ b/resources/codox/themes/journeyman/theme.edn @@ -0,0 +1 @@ +{:resources ["css/default.css" "css/highlight.css"]} \ No newline at end of file diff --git a/resources/lisp1.5.lsp b/resources/lisp1.5.lsp index c881745..e56bc7d 100644 --- a/resources/lisp1.5.lsp +++ b/resources/lisp1.5.lsp @@ -1,79 +1,240 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Beowulf Sysout file generated at 2023-03-30T09:40:36.483 +;; Beowulf 0.3.0-SNAPSHOT Sysout file generated at 2023-04-05T23:30:32.954 ;; generated by simon ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -((NIL) - (T . T) - (F) - (ADD1) - (AND) - (APPEND) - (APPLY) - (ATOM) - (CAR) - (CDR) - (CONS) - (COPY LAMBDA (X) - (COND ((NULL X) (QUOTE NIL)) - ((ATOM X) X) - ((QUOTE T) (CONS (COPY (CAR X)) (COPY (CDR X)))))) - (DEFINE) - (DIFFERENCE) - (DIVIDE LAMBDA (X Y) (CONS (QUOTIENT X Y) (CONS (REMAINDER X Y) (QUOTE NIL)))) - (ERROR) - (EQ) - (EQUAL) - (EVAL) - (FIXP) - (GENSYM) - (GET LAMBDA (X Y) - (COND ((NULL X) (QUOTE NIL)) - ((EQ (CAR X) Y) (CAR (CDR X))) - ((QUOTE T) (GET (CDR X) Y)))) - (GREATERP) - (INTEROP) - (INTERSECTION LAMBDA (X Y) - (COND ((NULL X) (QUOTE NIL)) - ((MEMBER (CAR X) Y) (CONS (CAR X) (INTERSECTION (CDR X) Y))) - ((QUOTE T) (INTERSECTION (CDR X) Y)))) - (LENGTH LAMBDA (L) (COND ((EQ NIL L) 0) (T (ADD1 (LENGTH (CDR L)))))) - (LESSP) - (MEMBER LAMBDA (A X) - (COND ((NULL X) (QUOTE F)) - ((EQ A (CAR X)) (QUOTE T)) - ((QUOTE T) (MEMBER A (CDR X))))) - (MINUSP LAMBDA (X) (LESSP X 0)) - (NULL LAMBDA (X) (COND ((EQUAL X NIL) (QUOTE T)) (T (QUOTE F)))) - (NUMBERP) - (OBLIST) - (ONEP LAMBDA (X) (EQ X 1)) - (PAIR LAMBDA (X Y) - (COND ((AND (NULL X) (NULL Y)) NIL) - ((NULL X) (ERROR 'F2)) - ((NULL Y) (ERROR 'F3)) - (T (CONS (CONS (CAR X) (CAR Y)) (PAIR (CDR X) (CDR Y)))))) - (PLUS) - (PRETTY) - (PRINT) - (PROP LAMBDA (X Y U) - (COND ((NULL X) (U)) - ((EQ (CAR X) Y) (CDR X)) - ((QUOTE T) (PROP (CDR X) Y U)))) - (QUOTIENT) - (READ) - (REMAINDER) - (REPEAT LAMBDA (N X) - (COND ((EQ N 0) NIL) - (T (CONS X (REPEAT (SUB1 N) X))))) - (RPLACA) - (RPLACD) - (SET) - (SUB1 LAMBDA (N) (DIFFERENCE N 1)) - (SYSIN) - (SYSOUT) - (TERPRI) - (TIMES) - (TRACE) - (UNTRACE) - (ZEROP LAMBDA (N) (EQ N 0))) +((NIL 32767 APVAL NIL) + (T 32767 APVAL T) + (F 32767 APVAL NIL) + (ADD1 32767 SUBR (BEOWULF HOST ADD1)) + (AND 32767 SUBR (BEOWULF HOST AND)) + (APPEND + 32767 + EXPR + (LAMBDA + (X Y) (COND ((NULL X) Y) (T (CONS (CAR X) (APPEND (CDR X) Y)))))) + (APPLY 32767 SUBR (BEOWULF BOOTSTRAP APPLY)) + (ASSOC + 32767 + EXPR + (LAMBDA + (X L) + (COND + ((NULL L) NIL) + ((AND (CONSP (CAR L)) (EQ (CAAR L) X)) (CAR L)) + (T (ASSOC X (CDR L))))) + SUBR (BEOWULF HOST ASSOC)) + (ATOM 32767 SUBR (BEOWULF HOST ATOM)) + (CAR 32767 SUBR (BEOWULF HOST CAR)) + (CAAAAR 32767 EXPR (LAMBDA (X) (CAR (CAR (CAR (CAR X)))))) + (CAAADR 32767 EXPR (LAMBDA (X) (CAR (CAR (CAR (CDR X)))))) + (CAAAR 32767 EXPR (LAMBDA (X) (CAR (CAR (CAR X))))) + (CAADAR 32767 EXPR (LAMBDA (X) (CAR (CAR (CDR (CAR X)))))) + (CAADDR 32767 EXPR (LAMBDA (X) (CAR (CAR (CDR (CDR X)))))) + (CAADR 32767 EXPR (LAMBDA (X) (CAR (CAR (CDR X))))) + (CAAR 32767 EXPR (LAMBDA (X) (CAR (CAR X)))) + (CADAAR 32767 EXPR (LAMBDA (X) (CAR (CDR (CAR (CAR X)))))) + (CADADR 32767 EXPR (LAMBDA (X) (CAR (CDR (CAR (CDR X)))))) + (CADAR 32767 EXPR (LAMBDA (X) (CAR (CDR (CAR X))))) + (CADDAR 32767 EXPR (LAMBDA (X) (CAR (CDR (CDR (CAR X)))))) + (CADDDR 32767 EXPR (LAMBDA (X) (CAR (CDR (CDR (CDR X)))))) + (CADDR 32767 EXPR (LAMBDA (X) (CAR (CDR (CDR X))))) + (CADR 32767 EXPR (LAMBDA (X) (CAR (CDR X)))) + (CDAAAR 32767 EXPR (LAMBDA (X) (CDR (CAR (CAR (CAR X)))))) + (CDAADR 32767 EXPR (LAMBDA (X) (CDR (CAR (CAR (CDR X)))))) + (CDAAR 32767 EXPR (LAMBDA (X) (CDR (CAR (CAR X))))) + (CDADAR 32767 EXPR (LAMBDA (X) (CDR (CAR (CDR (CAR X)))))) + (CDADDR 32767 EXPR (LAMBDA (X) (CDR (CAR (CDR (CDR X)))))) + (CDADR 32767 EXPR (LAMBDA (X) (CDR (CAR (CDR X))))) + (CDAR 32767 EXPR (LAMBDA (X) (CDR (CAR X)))) + (CDDAAR 32767 EXPR (LAMBDA (X) (CDR (CDR (CAR (CAR X)))))) + (CDDADR 32767 EXPR (LAMBDA (X) (CDR (CDR (CAR (CDR X)))))) + (CDDAR 32767 EXPR (LAMBDA (X) (CDR (CDR (CAR X))))) + (CDDDAR 32767 EXPR (LAMBDA (X) (CDR (CDR (CDR (CAR X)))))) + (CDDDDR 32767 EXPR (LAMBDA (X) (CDR (CDR (CDR (CDR X)))))) + (CDDDR 32767 EXPR (LAMBDA (X) (CDR (CDR (CDR X))))) + (CDDR 32767 EXPR (LAMBDA (X) (CDR (CDR X)))) + (CDR 32767 SUBR (BEOWULF HOST CDR)) + (CONS 32767 SUBR (BEOWULF HOST CONS)) + (CONSP 32767 SUBR (BEOWULF HOST CONSP)) + (COPY + 32767 + EXPR + (LAMBDA + (X) + (COND + ((NULL X) NIL) + ((ATOM X) X) (T (CONS (COPY (CAR X)) (COPY (CDR X))))))) + (DEFINE 32767 SUBR (BEOWULF HOST DEFINE)) + (DIFFERENCE 32767 SUBR (BEOWULF HOST DIFFERENCE)) + (DIVIDE + 32767 + EXPR + (LAMBDA (X Y) (CONS (QUOTIENT X Y) (CONS (REMAINDER X Y) NIL)))) + (DOC 32767 SUBR (BEOWULF HOST DOC)) + (EFFACE + 32767 + EXPR + (LAMBDA + (X L) + (COND + ((NULL L) NIL) + ((EQUAL X (CAR L)) (CDR L)) (T (RPLACD L (EFFACE X (CDR L))))))) + (ERROR 32767 SUBR (BEOWULF HOST ERROR)) + (EQ 32767 SUBR (BEOWULF HOST EQ)) + (EQUAL 32767 SUBR (BEOWULF HOST EQUAL)) + (EVAL 32767 SUBR (BEOWULF BOOTSTRAP EVAL)) + (FACTORIAL + 32767 + EXPR (LAMBDA (N) (COND ((EQ N 1) 1) (T (TIMES N (FACTORIAL (SUB1 N))))))) + (FIXP 32767 SUBR (BEOWULF HOST FIXP)) + (GENSYM 32767 SUBR (BEOWULF HOST GENSYM)) + (GET + 32767 +;; EXPR +;; (LAMBDA +;; (X Y) +;; (COND +;; ((NULL X) NIL) +;; ((EQ (CAR X) Y) (CAR (CDR X))) (T (GET (CDR X) Y)))) + SUBR (BEOWULF HOST GET)) + (GREATERP 32767 SUBR (BEOWULF HOST GREATERP)) + (INTEROP 32767 SUBR (BEOWULF INTEROP INTEROP)) + (INTERSECTION + 32767 + EXPR + (LAMBDA + (X Y) + (COND + ((NULL X) NIL) + ((MEMBER (CAR X) Y) (CONS (CAR X) (INTERSECTION (CDR X) Y))) + (T (INTERSECTION (CDR X) Y))))) + (LENGTH + 32767 + EXPR + (LAMBDA + (L) + (COND ((EQ NIL L) 0) ((CONSP (CDR L)) (ADD1 (LENGTH (CDR L)))) (T 1)))) + (LESSP 32767 SUBR (BEOWULF HOST LESSP)) + (MAPLIST + 32767 + EXPR + (LAMBDA + (L F) + (COND + ((NULL L) NIL) (T (CONS (F (CAR L)) (MAPLIST (CDR L) F)))))) + (MEMBER + 32767 + EXPR + (LAMBDA + (A X) + (COND + ((NULL X) (QUOTE F)) + ((EQ A (CAR X)) T) (T (MEMBER A (CDR X)))))) + (MINUSP 32767 EXPR (LAMBDA (X) (LESSP X 0))) + (NOT 32767 EXPR (LAMBDA (X) (COND (X NIL) (T T)))) + (NULL + 32767 EXPR (LAMBDA (X) (COND ((EQUAL X NIL) T) (T (QUOTE F))))) + (NUMBERP 32767 SUBR (BEOWULF HOST NUMBERP)) + (OBLIST 32767 SUBR (BEOWULF HOST OBLIST)) + (ONEP 32767 EXPR (LAMBDA (X) (EQ X 1))) + (OR 32767 SUBR (BEOWULF HOST OR)) + (PAIR + 32767 + EXPR + (LAMBDA + (X Y) + (COND + ((AND (NULL X) (NULL Y)) NIL) + ((NULL X) (ERROR (QUOTE F2))) + ((NULL Y) (ERROR (QUOTE F3))) + (T (CONS (CONS (CAR X) (CAR Y)) (PAIR (CDR X) (CDR Y))))))) + (PAIRLIS + 32767 + EXPR + (LAMBDA + (X Y A) + (COND + ((NULL X) A) + (T (CONS (CONS (CAR X) (CAR Y)) (PAIRLIS (CDR X) (CDR Y) A))))) + SUBR (BEOWULF HOST PAIRLIS)) + (PLUS 32767 SUBR (BEOWULF HOST PLUS)) + (PRETTY 32767) + (PRINT 32767) + (PROG 32767 FSUBR (BEOWULF HOST PROG)) + (PROP + 32767 + EXPR + (LAMBDA + (X Y U) + (COND + ((NULL X) (U)) + ((EQ (CAR X) Y) (CDR X)) (T (PROP (CDR X) Y U))))) + (QUOTE 32767 EXPR (LAMBDA (X) X)) + (QUOTIENT 32767 SUBR (BEOWULF HOST QUOTIENT)) + (RANGE + 32767 + EXPR + (LAMBDA + (N M) + (COND + ((LESSP M N) NIL) (T (CONS N (RANGE (ADD1 N) M)))))) + (READ 32767 SUBR (BEOWULF READ READ)) + (REMAINDER 32767 SUBR (BEOWULF HOST REMAINDER)) + (REPEAT + 32767 + EXPR + (LAMBDA (N X) (COND ((EQ N 0) NIL) (T (CONS X (REPEAT (SUB1 N) X)))))) + (RPLACA 32767 SUBR (BEOWULF HOST RPLACA)) + (RPLACD 32767 SUBR (BEOWULF HOST RPLACD)) + (SEARCH 32767 EXPR + (LAMBDA (X P F U) + (COND ((NULL X) (U X)) + ((P X) (F X)) + ((QUOTE T) (SEARCH (CDR X) P F U))))) + (SET 32767 SUBR (BEOWULF HOST SET)) + (SUB1 32767 EXPR (LAMBDA (N) (DIFFERENCE N 1)) SUBR (BEOWULF HOST SUB1)) + (SUB2 + 32767 + EXPR + (LAMBDA + (A Z) + (COND + ((NULL A) Z) ((EQ (CAAR A) Z) (CDAR A)) (T (SUB2 (CDAR A) Z))))) + (SUBLIS + 32767 EXPR + (LAMBDA (X Y) + (COND ((NULL X) Y) + ((NULL Y) Y) + ((QUOTE T) (SEARCH X + (LAMBDA (J) (EQUAL Y (CAAR J))) + (LAMBDA (J) (CDAR J)) + (LAMBDA (J) (COND ((ATOM Y) Y) + ((QUOTE T) (CONS + (SUBLIS X (CAR Y)) + (SUBLIS X (CDR Y))))))))))) + (SUBST + 32767 + EXPR + (LAMBDA + (X Y Z) + (COND + ((EQUAL Y Z) X) + ((ATOM Z) Z) + (T (CONS (SUBST X Y (CAR Z)) (SUBST X Y (CDR Z))))))) + (SYSIN 32767 SUBR (BEOWULF IO SYSIN)) + (SYSOUT 32767 SUBR (BEOWULF IO SYSOUT)) + (TERPRI 32767) + (TIMES 32767 SUBR (BEOWULF HOST TIMES)) + (TRACE 32767 SUBR (BEOWULF HOST TRACE)) + (UNION + 32767 + EXPR + (LAMBDA + (X Y) + (COND + ((NULL X) Y) + ((MEMBER (CAR X) Y) (UNION (CDR X) Y)) + (T (CONS (CAR X) (UNION (CDR X) Y)))))) + (UNTRACE 32767 SUBR (BEOWULF HOST UNTRACE)) + (ZEROP 32767 EXPR (LAMBDA (N) (EQ N 0)))) diff --git a/resources/mexpr/append.mexpr.lsp b/resources/mexpr/append.mexpr.lsp new file mode 100644 index 0000000..17df1c5 --- /dev/null +++ b/resources/mexpr/append.mexpr.lsp @@ -0,0 +1,3 @@ +;; page 61 + +append[x; y] = [null[x] -> y; T -> cons[car[x]; append[cdr[x]; y]]] diff --git a/resources/mexpr/apply-2.mexpr.lsp b/resources/mexpr/apply-2.mexpr.lsp deleted file mode 100644 index de4556b..0000000 --- a/resources/mexpr/apply-2.mexpr.lsp +++ /dev/null @@ -1,21 +0,0 @@ -;; see page 70 of Lisp 1.5 Programmers Manual; this expands somewhat -;; on the accounts of eval and apply given on page 13. This is M-expr -;; syntax, obviously. - -;; apply -;; NOTE THAT I suspect there is a typo in the printed manual in line -;; 7 of this definition, namely a missing closing square bracket before -;; the final semi-colon; that has been corrected here. - -apply[fn;args;a] = [ - null[fn] -> NIL; - atom[fn] -> [get[fn;EXPR] -> apply[expr; args; a]; - get[fn;SUBR] -> {spread[args]; - $ALIST := a; - TSX subr4, 4}; - T -> apply[cdr[sassoc[fn; a; λ[[]; error[A2]]]]; args a]]; - eq[car[fn]; LABEL] -> apply[caddr[fn]; args; - cons[cons[cadr[fn];caddr[fn]]; a]]; - eq[car[fn]; FUNARG] -> apply[cadr[fn]; args; caddr[fn]]; - eq[car[fn]; LAMBDA] -> eval[caddr[fn]; nconc[pair[cadr[fn]; args]; a]]; - T -> apply[eval[fn;a]; args; a]] \ No newline at end of file diff --git a/resources/mexpr/apply.mexpr.lsp b/resources/mexpr/apply.mexpr.lsp new file mode 100644 index 0000000..ce32f19 --- /dev/null +++ b/resources/mexpr/apply.mexpr.lsp @@ -0,0 +1,40 @@ +;; see page 70 of Lisp 1.5 Programmers Manual; this expands somewhat +;; on the accounts of eval and apply given on page 13. This is M-expr +;; syntax, obviously. + +;; ## APPLY + +;; NOTE THAT I suspect there is a typo in the printed manual in line +;; 7 of this definition, namely a missing closing square bracket before +;; the final semi-colon; that has been corrected here. + +;; RIGHT! So the 'EXPR' representation of a function is expected to be +;; on the `EXPR` property on the property list of the symbol which is +;; its name; an expression is simply a Lisp S-Expression as a structure +;; of cons cells and atoms in memory. The 'SUBR' representation, expected +;; to be on the `SUBR` property, is literally a subroutine written in +;; assembly code, so what is happening in the curly braces is putting the +;; arguments into processor registers prior to a jump to subroutine - TSX +;; being presumably equivalent to a 6502's JSR call. + +;; This accounts for the difference between this statement and the version +;; on page 12: that is a pure interpreter, which can only call those host +;; functions that are explicitly hard coded in. + +;; This version knows how to recognise subroutines and jump to them, but I +;; think that by implication at least this version can only work if it is +;; itself compiled with the Lisp compiler, since the section in curly braces +;; appears to be intended to be passed to the Lisp assembler. + +;; apply[fn;args;a] = [ +;; null[fn] -> NIL; +;; atom[fn] -> [get[fn;EXPR] -> apply[expr; args; a]; +;; get[fn;SUBR] -> {spread[args]; +;; $ALIST := a; +;; TSX subr4, 4}; +;; T -> apply[cdr[sassoc[fn; a; λ[[]; error[A2]]]]; args a]]; +;; eq[car[fn]; LABEL] -> apply[caddr[fn]; args; +;; cons[cons[cadr[fn];caddr[fn]]; a]]; +;; eq[car[fn]; FUNARG] -> apply[cadr[fn]; args; caddr[fn]]; +;; eq[car[fn]; LAMBDA] -> eval[caddr[fn]; nconc[pair[cadr[fn]; args]; a]]; +;; T -> apply[eval[fn;a]; args; a]] \ No newline at end of file diff --git a/resources/mexpr/assoc.mexpr.lsp b/resources/mexpr/assoc.mexpr.lsp new file mode 100644 index 0000000..48d8143 --- /dev/null +++ b/resources/mexpr/assoc.mexpr.lsp @@ -0,0 +1,22 @@ +;; Page 12 of the manual; this does NOT do what I expect a modern +;; ASSOC to do! + +;; Modern ASSOC would be: +;; assoc[x; l] = [null[l] -> NIL; +;; and[consp[car[l]]; eq[caar[l]; x]] -> cdar[l]; +;; T -> assoc[x; cdr[l]]] + +;; In the Lisp 1.5 statement of ASSOC, there's no account of what should happen +;; if the key (here `x`) is not present on the association list `a`. It seems +;; inevitable that this causes an infinite run up the stack until it fails with +;; stack exhaustion. Consequently this may be right but I'm not implementing it! +;; assoc[x; a] = [equal[caar[a]; x] -> car[a]; +;; T -> assoc[x; cdr[a]]] + +;; Consequently, my solution is a hybrid. It returns the pair from the +;; association list, as the original does, but it traps the end of list +;; condition, as a modern solution would. + +assoc[x; l] = [null[l] -> NIL; + and[consp[car[l]]; eq[caar[l]; x]] -> car[l]; + T -> assoc[x; cdr[l]]] diff --git a/resources/mexpr/efface.mexpr.lsp b/resources/mexpr/efface.mexpr.lsp new file mode 100644 index 0000000..a91c454 --- /dev/null +++ b/resources/mexpr/efface.mexpr.lsp @@ -0,0 +1,6 @@ +;; page 63. I'm not at all sure why an implementation using RPLACD is preferred +;; over a pure functional implementation here. + +efface[x; l] = [null[l] -> NIL; + equal[x; car[l]] -> cdr[l]; + T -> rplacd[l; efface[x; cdr[l]]]] \ No newline at end of file diff --git a/resources/mexpr/maplist.mexpr.lsp b/resources/mexpr/maplist.mexpr.lsp new file mode 100644 index 0000000..e04c38c --- /dev/null +++ b/resources/mexpr/maplist.mexpr.lsp @@ -0,0 +1,4 @@ +;; page 63 + +maplist[l; f] = [null[l] -> nil; + T -> cons[f[car[l]]; maplist[cdr[l]; f]]] \ No newline at end of file diff --git a/resources/mexpr/not.mexpr b/resources/mexpr/not.mexpr new file mode 100644 index 0000000..4aa5b5b --- /dev/null +++ b/resources/mexpr/not.mexpr @@ -0,0 +1 @@ +not[x] = [x = F -> T; x = NIL -> T; T -> F] \ No newline at end of file diff --git a/resources/mexpr/pairlis.mexpr.lsp b/resources/mexpr/pairlis.mexpr.lsp new file mode 100644 index 0000000..7160c7c --- /dev/null +++ b/resources/mexpr/pairlis.mexpr.lsp @@ -0,0 +1,5 @@ +;; page 12 + +pairlis[x;y;a] = [null[x] -> a; + T -> cons[cons[car[x]; car[y]]; + pairlis[cdr[x]; cdr[y]; a]]] \ No newline at end of file diff --git a/resources/mexpr/properties.mexpr.lsp b/resources/mexpr/properties.mexpr.lsp new file mode 100644 index 0000000..e69de29 diff --git a/resources/mexpr/range.mexpr.lsp b/resources/mexpr/range.mexpr.lsp new file mode 100644 index 0000000..2e84d4f --- /dev/null +++ b/resources/mexpr/range.mexpr.lsp @@ -0,0 +1,3 @@ +;; this isn't a standard Lisp 1.5 function + +range[n; m] = [lessp[m; n] -> NIL; T -> cons[n; range[add1[n]; m]]] \ No newline at end of file diff --git a/resources/mexpr/search.mexpr.lsp b/resources/mexpr/search.mexpr.lsp new file mode 100644 index 0000000..bba53c6 --- /dev/null +++ b/resources/mexpr/search.mexpr.lsp @@ -0,0 +1,5 @@ +# page 63 + +search[x; p; f; u] = [null[x] -> u[x]; + p[x] -> f[x]; + T -> search[cdr[x]; p; f; u]] \ No newline at end of file diff --git a/resources/mexpr/sublis.mexpr.lsp b/resources/mexpr/sublis.mexpr.lsp new file mode 100644 index 0000000..f17b5f8 --- /dev/null +++ b/resources/mexpr/sublis.mexpr.lsp @@ -0,0 +1,25 @@ +;; There are two different statements of SUBLIS and SUB2 in the manual, on +;; pages 12 and 61 respectively, although they are said to be semantically +;; equivalent; this is the version from page 12. + +sub2[a; z] = [null[a] -> z; + eq[caar[a]; z] -> cdar[a]; + T -> sub2[cdar[a]; z]] + +sublis[a; y] = [atom[y] -> sub2[a; y]; + T -> cons[sublis[a; car[y]]; + sublis[a; cdr[y]]]] + +;; this is the version from page 61 + +sublis[x;y] = [null[x] -> y; + null[y] -> y; + T -> search[x; + λ[[j]; equal[y; caar[j]]]; + λ[[j]; cdar[j]]; + λ[[j]; [atom[y] -> y; + T -> cons[sublis[x; car[y]]; + sublis[x; cdr[y]]]]]]] + +;; the test for this is: +;; (SUBLIS '((X . SHAKESPEARE) (Y . (THE TEMPEST))) '(X WROTE Y)) \ No newline at end of file diff --git a/resources/mexpr/subst.mexpr.lsp b/resources/mexpr/subst.mexpr.lsp new file mode 100644 index 0000000..3bd330d --- /dev/null +++ b/resources/mexpr/subst.mexpr.lsp @@ -0,0 +1,5 @@ +;; page 11 + +subst[x; y; z] = [equal[y; z] -> x; + atom[z] -> z; + T -> cons[subst[x; y; car[z]]; subst[x; y; cdr[z]]]] \ No newline at end of file diff --git a/resources/sexpr/length.lsp b/resources/sexpr/length.lsp index 5cd02df..cc464ed 100644 --- a/resources/sexpr/length.lsp +++ b/resources/sexpr/length.lsp @@ -1 +1,6 @@ -(SETQ LENGTH '(LAMBDA (L) (COND ((EQ NIL L) 0) (T (ADD1 (LENGTH (CDR L))))))) \ No newline at end of file +(SETQ LENGTH + '(LAMBDA (L) + (COND + ((EQ NIL L) 0) + ((CONSP (CDR L)) (ADD1 (LENGTH (CDR L)))) + (T 0)))) \ No newline at end of file diff --git a/src/beowulf/bootstrap.clj b/src/beowulf/bootstrap.clj index f419944..d530f62 100644 --- a/src/beowulf/bootstrap.clj +++ b/src/beowulf/bootstrap.clj @@ -9,457 +9,271 @@ ALLUPPERCASE are Lisp 1.5 functions (although written in Clojure) and that therefore all arguments must be numbers, symbols or `beowulf.cons_cell.ConsCell` objects." - (:require [clojure.string :as s] - [beowulf.cons-cell :refer [CAR CDR CONS LIST make-beowulf-list make-cons-cell - pretty-print T F]] - [beowulf.host :refer [AND ADD1 DIFFERENCE ERROR FIXP GENSYM GREATERP LESSP - NUMBERP PLUS QUOTIENT - REMAINDER RPLACA RPLACD TIMES]] - [beowulf.io :refer [SYSIN SYSOUT]] - [beowulf.oblist :refer [*options* oblist NIL]] - [beowulf.read :refer [READ]] - [beowulf.trace :refer [TRACE traced? UNTRACE]]) + (:require [beowulf.cons-cell :refer [F make-beowulf-list make-cons-cell + pretty-print T]] + [beowulf.host :refer [ASSOC ATOM CAAR CADAR CADDR CADR CAR CDR GET + LIST NUMBERP PAIRLIS traced?]] + [beowulf.oblist :refer [*options* NIL oblist]]) (:import [beowulf.cons_cell ConsCell] [clojure.lang Symbol])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; -;;; This file is essentially Lisp as defined in Chapter 1 (pages 1-14) of the -;;; Lisp 1.5 Programmer's Manual; that is to say, a very simple Lisp language, -;;; which should, I believe, be sufficient in conjunction with the functions -;;; provided by `beowulf.host`, be sufficient to bootstrap the full Lisp 1.5 -;;; interpreter. +;;; Copyright (C) 2022-2023 Simon Brooke +;;; +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License +;;; as published by the Free Software Foundation; either version 2 +;;; of the License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(declare APPLY EVAL) +(declare APPLY EVAL prog-eval) -(defn lax? - "Are we in lax mode? If so. return true; is not, throw an exception with - this `symbol`." - [symbol] - (when (:strict *options*) - (throw (ex-info (format "%s is not available in Lisp 1.5" symbol) - {:cause :strict - :extension symbol}))) - true) +;;;; The PROGram feature ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defmacro NULL - "Returns `T` if and only if the argument `x` is bound to `NIL`; else `F`." - [x] - `(if (= ~x NIL) T F)) +(def find-target + (memoize + (fn [target body] + (loop [body' body] + (cond + (= body' NIL) (throw (ex-info (str "Mislar GO miercels: `" target "`") + {:phase :lisp + :function 'PROG + :type :lisp + :code :A6 + :target target})) + (= (.getCar body') target) body' + :else (recur (.getCdr body'))))))) -(defmacro NILP - "Not part of LISP 1.5: `T` if `o` is `NIL`, else `NIL`." - [x] - `(if (= ~x NIL) T NIL)) - -(defmacro ATOM - "Returns `T` if and only if the argument `x` is bound to an atom; else `F`. - It is not clear to me from the documentation whether `(ATOM 7)` should return - `T` or `F`. I'm going to assume `T`." - [x] - `(if (or (symbol? ~x) (number? ~x)) T F)) - -(defmacro ATOM? - "The convention of returning `F` from predicates, rather than `NIL`, is going - to tie me in knots. This is a variant of `ATOM` which returns `NIL` - on failure." - [x] - `(if (or (symbol? ~x) (number? ~x)) T NIL)) - -(defn uaf - "Universal access function; `l` is expected to be an arbitrary LISP list, `path` - a (clojure) list of the characters `a` and `d`. Intended to make declaring - all those fiddly `#'c[ad]+r'` functions a bit easier" - [l path] - (cond - (= l NIL) NIL - (empty? path) l - :else - (try - (case (last path) - \a (uaf (.first l) (butlast path)) - \d (uaf (.getCdr l) (butlast path)) - (throw (ex-info (str "uaf: unexpected letter in path (only `a` and `d` permitted): " (last path)) - {:cause :uaf - :detail :unexpected-letter - :expr (last path)}))) - (catch ClassCastException e - (throw (ex-info - (str "uaf: Not a LISP list? " (type l)) - {:cause :uaf - :detail :not-a-lisp-list - :expr l})))))) - -(defmacro CAAR [x] `(uaf ~x '(\a \a))) -(defmacro CADR [x] `(uaf ~x '(\a \d))) -(defmacro CDDR [x] `(uaf ~x '(\d \d))) -(defmacro CDAR [x] `(uaf ~x '(\d \a))) - -(defmacro CAAAR [x] `(uaf ~x '(\a \a \a))) -(defmacro CAADR [x] `(uaf ~x '(\a \a \d))) -(defmacro CADAR [x] `(uaf ~x '(\a \d \a))) -(defmacro CADDR [x] `(uaf ~x '(\a \d \d))) -(defmacro CDDAR [x] `(uaf ~x '(\d \d \a))) -(defmacro CDDDR [x] `(uaf ~x '(\d \d \d))) -(defmacro CDAAR [x] `(uaf ~x '(\d \a \a))) -(defmacro CDADR [x] `(uaf ~x '(\d \a \d))) - -(defmacro CAAAAR [x] `(uaf ~x '(\a \a \a \a))) -(defmacro CAADAR [x] `(uaf ~x '(\a \a \d \a))) -(defmacro CADAAR [x] `(uaf ~x '(\a \d \a \a))) -(defmacro CADDAR [x] `(uaf ~x '(\a \d \d \a))) -(defmacro CDDAAR [x] `(uaf ~x '(\d \d \a \a))) -(defmacro CDDDAR [x] `(uaf ~x '(\d \d \d \a))) -(defmacro CDAAAR [x] `(uaf ~x '(\d \a \a \a))) -(defmacro CDADAR [x] `(uaf ~x '(\d \a \d \a))) -(defmacro CAAADR [x] `(uaf ~x '(\a \a \a \d))) -(defmacro CAADDR [x] `(uaf ~x '(\a \a \d \d))) -(defmacro CADADR [x] `(uaf ~x '(\a \d \a \d))) -(defmacro CADDDR [x] `(uaf ~x '(\a \d \d \d))) -(defmacro CDDADR [x] `(uaf ~x '(\d \d \a \d))) -(defmacro CDDDDR [x] `(uaf ~x '(\d \d \d \d))) -(defmacro CDAADR [x] `(uaf ~x '(\d \a \a \d))) -(defmacro CDADDR [x] `(uaf ~x '(\d \a \d \d))) - -(defn EQ - "Returns `T` if and only if both `x` and `y` are bound to the same atom, - else `NIL`." - [x y] - (cond (and (instance? ConsCell x) - (.equals x y)) T - (and (= (ATOM x) T) (= x y)) T - :else NIL)) - -(defn EQUAL - "This is a predicate that is true if its two arguments are identical - S-expressions, and false if they are different. (The elementary predicate - `EQ` is defined only for atomic arguments.) The definition of `EQUAL` is - an example of a conditional expression inside a conditional expression. - - NOTE: returns `F` on failure, not `NIL`" - [x y] - (cond - (= (ATOM x) T) (if (= x y) T F) - (= (EQUAL (CAR x) (CAR y)) T) (EQUAL (CDR x) (CDR y)) - :else F)) - -(defn SUBST - "This function gives the result of substituting the S-expression `x` for - all occurrences of the atomic symbol `y` in the S-expression `z`." - [x y z] - (cond - (= (EQUAL y z) T) x - (= (ATOM? z) T) z ;; NIL is a symbol - :else - (make-cons-cell (SUBST x y (CAR z)) (SUBST x y (CDR z))))) - -(defn APPEND - "Append the the elements of `y` to the elements of `x`. - - All args are assumed to be `beowulf.cons-cell/ConsCell` objects. - See page 11 of the Lisp 1.5 Programmers Manual." - [x y] - (cond - (= x NIL) y - :else - (make-cons-cell (CAR x) (APPEND (CDR x) y)))) - -(defn MEMBER - "This predicate is true if the S-expression `x` occurs among the elements - of the list `y`. - - All args are assumed to be symbols or `beowulf.cons-cell/ConsCell` objects. - See page 11 of the Lisp 1.5 Programmers Manual." - [x y] - (cond - (= y NIL) F ;; NOTE: returns F on falsity, not NIL - (= (EQUAL x (CAR y)) T) T - :else (MEMBER x (CDR y)))) - -(defn PAIRLIS - "This function gives the list of pairs of corresponding elements of the - lists `x` and `y`, and APPENDs this to the list `a`. The resultant list - of pairs, which is like a table with two columns, is called an - association list. - - Eessentially, it builds the environment on the stack, implementing shallow - binding. - - All args are assumed to be `beowulf.cons-cell/ConsCell` objects. - See page 12 of the Lisp 1.5 Programmers Manual." - [x y a] - (cond - ;; the original tests only x; testing y as well will be a little more - ;; robust if `x` and `y` are not the same length. - (or (= NIL x) (= NIL y)) a - :else (make-cons-cell - (make-cons-cell (CAR x) (CAR y)) - (PAIRLIS (CDR x) (CDR y) a)))) - -(defmacro QUOTE - "Quote, but in upper case for LISP 1.5" - [f] - `(quote ~f)) - -(defn ASSOC - "If a is an association list such as the one formed by PAIRLIS in the above - example, then assoc will produce the first pair whose first term is x. Thus - it is a table searching function. - - All args are assumed to be `beowulf.cons-cell/ConsCell` objects. - See page 12 of the Lisp 1.5 Programmers Manual." - [x a] - (cond - (= NIL a) NIL ;; this clause is not present in the original but is added for - ;; robustness. - (= (EQUAL (CAAR a) x) T) (CAR a) - :else - (ASSOC x (CDR a)))) - -(defn- SUB2 - "Internal to `SUBLIS`, q.v., which SUBSTitutes into a list from a store. - ? I think this is doing variable binding in the stack frame?" - [a z] - (cond - (= NIL a) z - (= (CAAR a) z) (CDAR a) ;; TODO: this looks definitely wrong - :else - (SUB2 (CDR a) z))) - -(defn SUBLIS - "Here `a` is assumed to be an association list of the form - `((ul . vl)...(un . vn))`, where the `u`s are atomic, and `y` is any - S-expression. What `SUBLIS` does, is to treat the `u`s as variables when - they occur in `y`, and to SUBSTitute the corresponding `v`s from the pair - list. - - My interpretation is that this is variable binding in the stack frame. - - All args are assumed to be `beowulf.cons-cell/ConsCell` objects. - See page 12 of the Lisp 1.5 Programmers Manual." - [a y] - (cond - (= (ATOM? y) T) (SUB2 a y) - :else - (make-cons-cell (SUBLIS a (CAR y)) (SUBLIS a (CDR y))))) - -(defn interop-interpret-q-name - "For interoperation with Clojure, it will often be necessary to pass - qualified names that are not representable in Lisp 1.5. This function - takes a sequence in the form `(PART PART PART... NAME)` and returns - a symbol in the form `PART.PART.PART/NAME`. This symbol will then be - tried in both that form and lower-cased. Names with hyphens or - underscores cannot be represented with this scheme." - [l] - (if - (seq? l) - (symbol - (s/reverse - (s/replace-first - (s/reverse - (s/join "." (map str l))) - "." - "/"))) - l)) - -(defn to-beowulf - "Return a beowulf-native representation of the Clojure object `o`. - Numbers and symbols are unaffected. Collections have to be converted; - strings must be converted to symbols." - [o] - (cond - (coll? o) (make-beowulf-list o) - (string? o) (symbol (s/upper-case o)) - :else o)) - -(defn to-clojure - "If l is a `beowulf.cons_cell.ConsCell`, return a Clojure list having the - same members in the same order." - [l] - (cond - (not (instance? beowulf.cons_cell.ConsCell l)) - l - (= (CDR l) NIL) - (list (to-clojure (CAR l))) - :else - (conj (to-clojure (CDR l)) (to-clojure (CAR l))))) - -(defn INTEROP - "Clojure (or other host environment) interoperation API. `fn-symbol` is expected - to be either - - 1. a symbol bound in the host environment to a function; or - 2. a sequence (list) of symbols forming a qualified path name bound to a - function. - - Lower case characters cannot normally be represented in Lisp 1.5, so both the - upper case and lower case variants of `fn-symbol` will be tried. If the - function you're looking for has a mixed case name, that is not currently - accessible. - - `args` is expected to be a Lisp 1.5 list of arguments to be passed to that - function. Return value must be something acceptable to Lisp 1.5, so either - a symbol, a number, or a Lisp 1.5 list. - - If `fn-symbol` is not found (even when cast to lower case), or is not a function, - or the value returned cannot be represented in Lisp 1.5, an exception is thrown - with `:cause` bound to `:interop` and `:detail` set to a value representing the - actual problem." - [fn-symbol args] - (if-not (:strict *options*) - (let - [q-name (if - (seq? fn-symbol) - (interop-interpret-q-name fn-symbol) - fn-symbol) - l-name (symbol (s/lower-case q-name)) - f (cond - (try - (fn? (eval l-name)) - (catch java.lang.ClassNotFoundException e nil)) l-name - (try - (fn? (eval q-name)) - (catch java.lang.ClassNotFoundException e nil)) q-name - :else (throw - (ex-info - (str "INTEROP: unknown function `" fn-symbol "`") - {:cause :interop - :detail :not-found - :name fn-symbol - :also-tried l-name}))) - args' (to-clojure args)] - (print (str "INTEROP: evaluating `" (cons f args') "`")) - (flush) - (let [result (eval (conj args' f))] ;; this has the potential to blow up the world - (println (str "; returning `" result "`")) - - (cond - (instance? beowulf.cons_cell.ConsCell result) result - (coll? result) (make-beowulf-list result) - (symbol? result) result - (string? result) (symbol result) - (number? result) result - :else (throw - (ex-info - (str "INTEROP: Cannot return `" result "` to Lisp 1.5.") - {:cause :interop - :detail :not-representable - :result result}))))) - (throw - (ex-info - (str "INTEROP not allowed in strict mode.") - {:cause :interop - :detail :strict})))) - -(defn OBLIST - "Return a list of the symbols currently bound on the object list. - - **NOTE THAT** in the Lisp 1.5 manual, footnote at the bottom of page 69, it implies - that an argument can be passed but I'm not sure of the semantics of - this." - [] - (when (lax? 'OBLIST) - (if (instance? ConsCell @oblist) - (make-beowulf-list (map CAR @oblist)) +(defn- prog-cond + "Like `EVCON`, q.v. except using `prog-eval` instead of `EVAL` and not + throwing an error if no clause matches." + [clauses vars env depth] + (loop [clauses' clauses] + (if-not (= clauses' NIL) + (let [test (prog-eval (CAAR clauses') vars env depth)] + (if (not (#{NIL F} test)) + (prog-eval (CADAR clauses') vars env depth) + (recur (.getCdr clauses')))) NIL))) -(defn DEFINE - "Bootstrap-only version of `DEFINE` which, post boostrap, can be overwritten - in LISP. +(defn- merge-vars [vars env] + (reduce + #(make-cons-cell + (make-cons-cell %2 (@vars %2)) + env) + env + (keys @vars))) - The single argument to `DEFINE` should be an assoc list which should be - nconc'ed onto the front of the oblist. Broadly, - (SETQ OBLIST (NCONC ARG1 OBLIST))" - [args] - (swap! - oblist - (fn [ob arg1] - (loop [cursor arg1 a arg1] - (if (= (CDR cursor) NIL) - (do - (.rplacd cursor @oblist) - (pretty-print a) - a) - (recur (CDR cursor) a)))) - (CAR args))) +(defn prog-eval + "Like `EVAL`, q.v., except handling symbols, and expressions starting + `GO`, `RETURN`, `SET` and `SETQ` specially." + [expr vars env depth] + (cond + (number? expr) expr + (symbol? expr) (@vars expr) + (instance? ConsCell expr) (case (.getCar expr) + COND (prog-cond (.getCdr expr) + vars env depth) + GO (make-cons-cell + '*PROGGO* (.getCar (.getCdr expr))) + RETURN (make-cons-cell + '*PROGRETURN* + (prog-eval (.getCar (.getCdr expr)) + vars env depth)) + SET (let [v (CADDR expr)] + (swap! vars + assoc + (prog-eval (CADR expr) + vars env depth) + (prog-eval (CADDR expr) + vars env depth)) + v) + SETQ (let [v (CADDR expr)] + (swap! vars + assoc + (CADR expr) + (prog-eval v + vars env depth)) + v) + ;; else + (beowulf.bootstrap/EVAL expr + (merge-vars vars env) + depth)))) -(defn SET - "Implementation of SET in Clojure. Add to the `oblist` a binding of the - value of `var` to the value of `val`. NOTE WELL: this is not SETQ!" - [symbol val] - (when - (swap! - oblist - (fn [ob s v] (make-cons-cell (make-cons-cell s v) ob)) - symbol val) - NIL)) +(defn PROG + "The accursed `PROG` feature. See page 71 of the manual. + + Lisp 1.5 introduced `PROG`, and most Lisps have been stuck with it ever + since. It introduces imperative programming into what should be a pure + functional language, and consequently it's going to be a pig to implement. + + Broadly, `PROG` is a variadic pseudo function called as a `FEXPR` (or + possibly an `FSUBR`, although I'm not presently sure that would even work.) + + The arguments, which are unevaluated, are a list of forms, the first of + which is expected to be a list of symbols which will be treated as names + of variables within the program, and the rest of which (the 'program body') + are either lists or symbols. Lists are treated as Lisp expressions which + may be evaulated in turn. Symbols are treated as targets for the `GO` + statement. + + **GO:** + A `GO` statement takes the form of `(GO target)`, where + `target` should be one of the symbols which occur at top level among that + particular invocation of `PROG`s arguments. A `GO` statement may occur at + top level in a PROG, or in a clause of a `COND` statement in a `PROG`, but + not in a function called from the `PROG` statement. When a `GO` statement + is evaluated, execution should transfer immediately to the expression which + is the argument list immediately following the symbol which is its target. + + If the target is not found, an error with the code `A6` should be thrown. + + **RETURN:** + A `RETURN` statement takes the form `(RETURN value)`, where + `value` is any value. Following the evaluation of a `RETURN` statement, + the `PROG` should immediately exit without executing any further + expressions, returning the value. + + **SET and SETQ:** + In addition to the above, if a `SET` or `SETQ` expression is encountered + in any expression within the `PROG` body, it should affect not the global + object list but instead only the local variables of the program. + + **COND:** + In **strict** mode, when in normal execution, a `COND` statement none of + whose clauses match should not return `NIL` but should throw an error with + the code `A3`... *except* that inside a `PROG` body, it should not do so. + *sigh*. + + **Flow of control:** + Apart from the exceptions specified above, expressions in the program body + are evaluated sequentially. If execution reaches the end of the program + body, `NIL` is returned. + + Got all that? + + Good." + [program env depth] + (let [trace (traced? 'PROG) + vars (atom (reduce merge (map #(assoc {} % NIL) (.getCar program)))) + body (.getCdr program) + targets (set (filter symbol? body))] + (when trace (do + (println "Program:") + (pretty-print program))) ;; for debugging + (loop [cursor body] + (let [step (.getCar cursor)] + (when trace (do (println "Executing step: " step) + (println " with vars: " @vars))) + (cond (= cursor NIL) NIL + (symbol? step) (recur (.getCdr cursor)) + :else (let [v (prog-eval (.getCar cursor) vars env depth)] + (when trace (println " --> " v)) + (if (instance? ConsCell v) + (case (.getCar v) + *PROGGO* (let [target (.getCdr v)] + (if (targets target) + (recur (find-target target body)) + (throw (ex-info (str "Uncynlic GO miercels `" + target "`") + {:phase :lisp + :function 'PROG + :args program + :type :lisp + :code :A6 + :target target + :targets targets})))) + *PROGRETURN* (.getCdr v) + ;; else + (recur (.getCdr cursor))) + (recur (.getCdr cursor))))))))) + +;;;; Tracing execution ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- trace-call + "Show a trace of a call to the function named by this `function-symbol` + with these `args` at this depth." + [function-symbol args depth] + (when (traced? function-symbol) + (let [indent (apply str (repeat depth "-"))] + (println (str indent "> " function-symbol " " args))))) + +(defn- trace-response + "Show a trace of this `response` from the function named by this + `function-symbol` at this depth." + [function-symbol response depth] + (when (traced? function-symbol) + (let [indent (apply str (repeat depth "-"))] + (println (str "<" indent " " function-symbol " " response)))) + response) + +(defn- value + "Seek a value for this symbol `s` by checking each of these indicators in + turn." + ([s] + (value s (list 'APVAL 'EXPR 'FEXPR 'SUBR 'FSUBR))) + ([s indicators] + (when (symbol? s) + (first (remove #(= % NIL) (map #(GET s %) + indicators)))))) + +;;;; APPLY ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn try-resolve-subroutine + "Attempt to resolve this `subr` with these `args`." + [subr args] + (when (and subr (not= subr NIL)) + (try @(resolve subr) + (catch Throwable any + (throw (ex-info "þegnung (SUBR) ne āfand" + {:phase :apply + :function subr + :args args + :type :beowulf} + any)))))) (defn- apply-symbolic "Apply this `funtion-symbol` to these `args` in this `environment` and return the result." - [^Symbol function-symbol ^ConsCell args ^ConsCell environment depth] - (let [fn (try (EVAL function-symbol environment depth) - (catch Throwable any (when (:trace *options*) - (println any)))) - indent (apply str (repeat depth "-"))] - (if (and fn (not= fn NIL)) - (if (traced? function-symbol) - (do - (println (str indent "> " function-symbol " " args)) - (let [r (APPLY fn args environment depth)] - (println (str "<" indent " " r)) - r)) - (APPLY fn args environment depth)) - (case function-symbol ;; there must be a better way of doing this! - ADD1 (apply ADD1 args) - AND (apply AND args) - APPEND (apply APPEND args) - APPLY (apply APPLY args) - ATOM (ATOM? (CAR args)) - CAR (CAAR args) - CDR (CDAR args) - CONS (make-cons-cell (CAR args) (CADR args)) - DEFINE (DEFINE (CAR args)) - DIFFERENCE (DIFFERENCE (CAR args) (CADR args)) - EQ (apply EQ args) - EQUAL (apply EQUAL args) - ERROR (apply ERROR args) - ;; think about EVAL. Getting the environment right is subtle - FIXP (apply FIXP args) - GENSYM (GENSYM) - GREATERP (apply GREATERP args) - INTEROP (when (lax? INTEROP) (apply INTEROP args)) - LESSP (apply LESSP args) - LIST (apply LIST args) - NUMBERP (apply NUMBERP args) - OBLIST (OBLIST) - PLUS (apply PLUS args) - PRETTY (when (lax? 'PRETTY) - (apply pretty-print args)) - PRINT (apply print args) - QUOTIENT (apply QUOTIENT args) - READ (READ) - REMAINDER (apply REMAINDER args) - RPLACA (apply RPLACA args) - RPLACD (apply RPLACD args) - SET (apply SET args) - SYSIN (when (lax? 'SYSIN) - (apply SYSIN args)) - SYSOUT (when (lax? 'SYSOUT) - (if (empty? args) - (SYSOUT) - (apply SYSOUT args))) - TERPRI (println) - TIMES (apply TIMES args) - TRACE (apply TRACE args) - UNTRACE (apply UNTRACE args) - ;; else - (ex-info "No function found" - {:context "APPLY" - :function function-symbol - :args args}))))) + [^Symbol function-symbol args ^ConsCell environment depth] + (trace-call function-symbol args depth) + (let [lisp-fn (value function-symbol '(EXPR FEXPR)) + args' (cond (= NIL args) args + (empty? args) NIL + (instance? ConsCell args) args + :else (make-beowulf-list args)) + subr (value function-symbol '(SUBR FSUBR)) + host-fn (try-resolve-subroutine subr args') + result (cond (and lisp-fn + (not= lisp-fn NIL)) (APPLY lisp-fn args' environment depth) + host-fn (try + (apply host-fn (when (instance? ConsCell args') args')) + (catch Exception any + (throw (ex-info (str "Uncynlic þegnung: " + (.getMessage any)) + {:phase :apply + :function function-symbol + :args args + :type :beowulf} + any)))) + :else (ex-info "þegnung ne āfand" + {:phase :apply + :function function-symbol + :args args + :type :beowulf}))] + (trace-response function-symbol result depth) + result)) (defn APPLY "Apply this `function` to these `arguments` in this `environment` and return @@ -469,39 +283,63 @@ All args are assumed to be symbols or `beowulf.cons-cell/ConsCell` objects. See page 13 of the Lisp 1.5 Programmers Manual." [function args environment depth] - (cond - (= NIL function) (if (:strict *options*) - NIL - (throw (ex-info "NIL is not a function" - {:context "APPLY" - :function "NIL" - :args args}))) - (= (ATOM? function) T) (apply-symbolic function args environment (inc depth)) - (= (first function) 'LAMBDA) (EVAL - (CADDR function) - (PAIRLIS (CADR function) args environment) depth) - (= (first function) 'LABEL) (APPLY - (CADDR function) - args + (trace-call 'APPLY (list function args environment) depth) + (let [result (cond + (= NIL function) (if (:strict *options*) + NIL + (throw (ex-info "NIL sí ne þegnung" + {:phase :apply + :function "NIL" + :args args + :type :beowulf}))) + (= (ATOM function) T) (apply-symbolic function args environment (inc depth)) + :else (case (first function) + LABEL (APPLY + (CADDR function) + args + (make-cons-cell (make-cons-cell - (make-cons-cell - (CADR function) - (CADDR function)) - environment) - depth))) + (CADR function) + (CADDR function)) + environment) + depth) + FUNARG (APPLY (CADR function) args (CADDR function) depth) + LAMBDA (EVAL + (CADDR function) + (PAIRLIS (CADR function) args environment) depth) + (throw (ex-info "Ungecnáwen wyrþan sí þegnung-weard" + {:phase :apply + :function function + :args args + :type :beowulf}))))] + (trace-response 'APPLY result depth) + result)) + +;;;; EVAL ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn- EVCON "Inner guts of primitive COND. All `clauses` are assumed to be `beowulf.cons-cell/ConsCell` objects. Note that tests in Lisp 1.5 - often return `F`, not `NIL`, on failure. + often return `F`, not `NIL`, on failure. If no clause matches, + then, strictly, we throw an error with code `:A3`. - See page 13 of the Lisp 1.5 Programmers Manual." + See pages 13 and 71 of the Lisp 1.5 Programmers Manual." [clauses env depth] - (let [test (EVAL (CAAR clauses) env depth)] - (if - (and (not= test NIL) (not= test 'F)) - (EVAL (CADAR clauses) env depth) - (EVCON (CDR clauses) env depth)))) + (loop [clauses' clauses] + (if-not (= clauses' NIL) + (let [test (EVAL (CAAR clauses') env depth)] + (if (not (#{NIL F} test)) + ;; (and (not= test NIL) (not= test F)) + (EVAL (CADAR clauses') env depth) + (recur (.getCdr clauses')))) + (if (:strict *options*) + (throw (ex-info "Ne ġefōg dǣl in COND" + {:phase :eval + :function 'COND + :args (list clauses) + :type :lisp + :code :A3})) + NIL)))) (defn- EVLIS "Map `EVAL` across this list of `args` in the context of this @@ -515,14 +353,25 @@ (EVAL (CAR args) env depth) (EVLIS (CDR args) env depth)))) -(defn- eval-symbolic [^Symbol s env] - (let [binding (ASSOC s env)] - (if (= binding NIL) - (throw (ex-info (format "No binding for symbol `%s`" s) - {:phase :eval - :symbol s})) - (CDR binding)))) - +(defn- eval-symbolic + [expr env depth] + (let [v (ASSOC expr env) + indent (apply str (repeat depth "-"))] + (when (traced? 'EVAL) + (println (str indent ": EVAL: sceald bindele: " (or v "nil")))) + (if (instance? ConsCell v) + (.getCdr v) + (let [v' (value expr (list 'APVAL))] + (when (traced? 'EVAL) + (println (str indent ": EVAL: deóp bindele: (" expr " . " (or v' "nil") ")"))) + (if v' + v' + (throw (ex-info "Ne tácen-bindele āfand" + {:phase :eval + :function 'EVAL + :args (list expr env depth) + :type :lisp + :code :A8}))))))) (defn EVAL "Evaluate this `expr` and return the result. If `environment` is not passed, @@ -530,34 +379,44 @@ argument is part of the tracing system and should not be set by user code. All args are assumed to be numbers, symbols or `beowulf.cons-cell/ConsCell` - objects." + objects. However, if called with just a single arg, `expr`, I'll assume it's + being called from the Clojure REPL and will coerce the `expr` to `ConsCell`." ([expr] - (EVAL expr @oblist 0)) + (let [expr' (if (and (coll? expr) (not (instance? ConsCell expr))) + (make-beowulf-list expr) + expr)] + (EVAL expr' NIL 0))) ([expr env depth] - (cond - (= (NUMBERP expr) T) expr - (symbol? expr) (eval-symbolic expr env) - (string? expr) (if (:strict *options*) - (throw - (ex-info - (str "EVAL: strings not allowed in strict mode: \"" expr "\"") - {:phase :eval - :detail :strict - :expr expr})) - (symbol expr)) - (= - (ATOM? (CAR expr)) - T) (cond - (= (CAR expr) 'QUOTE) (CADR expr) - (= (CAR expr) 'COND) (EVCON (CDR expr) env depth) - :else (APPLY - (CAR expr) - (EVLIS (CDR expr) env depth) - env - depth)) - :else (APPLY - (CAR expr) - (EVLIS (CDR expr) env depth) - env - depth)))) + (trace-call 'EVAL (list expr env depth) depth) + (let [result (cond + (= NIL expr) NIL ;; it was probably a mistake to make Lisp + ;; NIL distinct from Clojure nil + (= (NUMBERP expr) T) expr + (symbol? expr) (eval-symbolic expr env depth) + (string? expr) (if (:strict *options*) + (throw + (ex-info + (str "EVAL: strings not allowed in strict mode: \"" expr "\"") + {:phase :eval + :detail :strict + :expr expr})) + (symbol expr)) + (= (ATOM (CAR expr)) T) (case (CAR expr) + COND (EVCON (CDR expr) env depth) + FUNCTION (LIST 'FUNARG (CADR expr)) + PROG (PROG (CDR expr) env depth) + QUOTE (CADR expr) + ;; else + (APPLY + (CAR expr) + (EVLIS (CDR expr) env depth) + env + depth)) + :else (APPLY + (CAR expr) + (EVLIS (CDR expr) env depth) + env + depth))] + (trace-response 'EVAL result depth) + result))) diff --git a/src/beowulf/cons_cell.clj b/src/beowulf/cons_cell.clj index a4585d9..fb24730 100644 --- a/src/beowulf/cons_cell.clj +++ b/src/beowulf/cons_cell.clj @@ -5,6 +5,26 @@ of Clojure lists." (:require [beowulf.oblist :refer [NIL]])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Copyright (C) 2022-2023 Simon Brooke +;;; +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License +;;; as published by the Free Software Foundation; either version 2 +;;; of the License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (declare cons-cell?) (def T @@ -16,6 +36,8 @@ false in Lisp 1.5." (symbol "F")) ;; false as distinct from nil +;;;; The actual cons-cell ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defprotocol MutableSequence "Like a sequence, but mutable." (rplaca @@ -31,9 +53,8 @@ [this] "like `more`, q.v., but returns List `NIL` not Clojure `nil` when empty.") (getUid - [this] - "Returns a unique identifier for this object") - ) + [this] + "Returns a unique identifier for this object")) (deftype ConsCell [^:unsynchronized-mutable CAR ^:unsynchronized-mutable CDR uid] ;; Note that, because the CAR and CDR fields are unsynchronised mutable - i.e. @@ -56,7 +77,7 @@ (set! (. this CAR) value) this) (throw (ex-info - (str "Invalid value in RPLACA: `" value "` (" (type value) ")") + (str "Uncynlic miercels in RPLACA: `" value "` (" (type value) ")") {:cause :bad-value :detail :rplaca})))) @@ -71,14 +92,14 @@ (set! (. this CDR) value) this) (throw (ex-info - (str "Invalid value in RPLACD: `" value "` (" (type value) ")") + (str "Uncynlic miercels in RPLACD: `" value "` (" (type value) ")") {:cause :bad-value :detail :rplaca})))) - + (getCar [this] (. this CAR)) (getCdr [this] - (. this CDR)) + (. this CDR)) (getUid [this] (. this uid)) @@ -138,13 +159,19 @@ (cond (instance? ConsCell (. this CDR)) (str " " (subs (.toString (. this CDR)) 1)) (= NIL (. this CDR)) ")" - :else (str " . " (. this CDR)))))) + :else (str " . " (. this CDR) ")"))))) + +;;;; Printing. Here be dragons! ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn- to-string "Printing ConsCells gave me a *lot* of trouble. This is an internal function used by the print-method override (below) in order that the standard Clojure `print` and `str` functions will print ConsCells correctly. The argument `cell` must, obviously, be an instance of `ConsCell`." + ;; TODO: I am deeply suspicious both of this and the defmethod which depends + ;; on it. I *think* they are implicated in the `COPY` bug. If the `toString` + ;; override in `ConsCell` was right, neither of these would be necessary. + ;; see https://github.com/simon-brooke/beowulf/issues/5 [cell] (loop [c cell n 0 @@ -161,23 +188,26 @@ s (to-string car) (cond - (or (nil? cdr) (= cdr NIL)) - ")" - cons? - " " - :else - (str " . " (to-string cdr) ")")))] + (or (nil? cdr) (= cdr NIL)) ")" + cons? " " + :else (str " . " (to-string cdr) ")")))] (if cons? (recur cdr (inc n) ss) ss)) (str c)))) +(defmethod clojure.core/print-method + ;;; I have not worked out how to document defmethod without blowing up the world. + beowulf.cons_cell.ConsCell + [this writer] + (.write writer (to-string this))) + (defn pretty-print "This isn't the world's best pretty printer but it sort of works." - ([^beowulf.cons_cell.ConsCell cell] + ([cell] (println (pretty-print cell 80 0))) - ([^beowulf.cons_cell.ConsCell cell width level] + ([cell width level] (loop [c cell n (inc level) s "("] @@ -185,7 +215,7 @@ (instance? beowulf.cons_cell.ConsCell c) (let [car (.first c) cdr (.getCdr c) - cons? (instance? beowulf.cons_cell.ConsCell cdr) + tail? (instance? beowulf.cons_cell.ConsCell cdr) print-width (count (print-str c)) indent (apply str (repeat n " ")) ss (str @@ -194,7 +224,7 @@ (cond (or (nil? cdr) (= cdr NIL)) ")" - cons? + tail? (if (< (+ (count indent) print-width) width) " " @@ -202,17 +232,15 @@ :else (str " . " (pretty-print cdr width n) ")")))] (if - cons? + tail? (recur cdr n ss) ss)) (str c))))) - -(defmethod clojure.core/print-method - ;;; I have not worked out how to document defmethod without blowing up the world. - beowulf.cons_cell.ConsCell - [this writer] - (.write writer (to-string this))) +(defn cons-cell? + "Is this object `o` a beowulf cons-cell?" + [o] + (instance? beowulf.cons_cell.ConsCell o)) (defn make-cons-cell "Construct a new instance of cons cell with this `car` and `cdr`." @@ -220,14 +248,9 @@ (try (ConsCell. car cdr (gensym "c")) (catch Exception any - (throw (ex-info "Cound not construct cons cell" {:car car + (throw (ex-info "Ne meahte cræfte cons cell" {:car car :cdr cdr} any))))) -(defn cons-cell? - "Is this object `o` a beowulf cons-cell?" - [o] - (instance? beowulf.cons_cell.ConsCell o)) - (defn make-beowulf-list "Construct a linked list of cons cells with the same content as the sequence `x`." @@ -235,6 +258,7 @@ (try (cond (empty? x) NIL + (instance? ConsCell x) (make-cons-cell (.getCar x) (.getCdr x)) (coll? x) (ConsCell. (if (coll? (first x)) @@ -245,39 +269,6 @@ :else NIL) (catch Exception any - (throw (ex-info "Could not construct Beowulf list" + (throw (ex-info "Ne meahte cræfte Beowulf líste" {:content x} any))))) - -(defn CONS - "Construct a new instance of cons cell with this `car` and `cdr`." - [car cdr] - (beowulf.cons_cell.ConsCell. car cdr (gensym "c"))) - -(defn CAR - "Return the item indicated by the first pointer of a pair. NIL is treated - specially: the CAR of NIL is NIL." - [x] - (if - (= x NIL) NIL - (try - (or (.getCar x) NIL) - (catch Exception any - (throw (Exception. - (str "Cannot take CAR of `" x "` (" (.getName (.getClass x)) ")") any)))))) - -(defn CDR - "Return the item indicated by the second pointer of a pair. NIL is treated - specially: the CDR of NIL is NIL." - [x] - (if - (= x NIL) NIL - (try - (.getCdr x) - (catch Exception any - (throw (Exception. - (str "Cannot take CDR of `" x "` (" (.getName (.getClass x)) ")") any)))))) - -(defn LIST - [& args] - (make-beowulf-list args)) \ No newline at end of file diff --git a/src/beowulf/core.clj b/src/beowulf/core.clj index 5ffd477..d20339d 100644 --- a/src/beowulf/core.clj +++ b/src/beowulf/core.clj @@ -1,16 +1,39 @@ (ns beowulf.core "Essentially, the `-main` function and the bootstrap read-eval-print loop." (:require [beowulf.bootstrap :refer [EVAL]] - [beowulf.io :refer [SYSIN]] + [beowulf.io :refer [default-sysout SYSIN]] + [beowulf.oblist :refer [*options* NIL]] [beowulf.read :refer [READ read-from-console]] - [beowulf.oblist :refer [*options* oblist]] [clojure.java.io :as io] [clojure.pprint :refer [pprint]] [clojure.string :refer [trim]] [clojure.tools.cli :refer [parse-opts]]) (:gen-class)) -(def stop-word "STOP") +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Copyright (C) 2022-2023 Simon Brooke +;;; +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License +;;; as published by the Free Software Foundation; either version 2 +;;; of the License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def stop-word + "The word which, if submitted an an input line, will cause Beowulf to quit. + Question: should this be `forlǣte`?" + "STOP") (def cli-options [["-f FILEPATH" "--file-path FILEPATH" @@ -23,13 +46,19 @@ ["-h" "--help"] ["-p PROMPT" "--prompt PROMPT" "Set the REPL prompt to PROMPT" :default "Sprecan::"] - ["-r INITFILE" "--read INITFILE" "Read Lisp system from file INITFILE" - :default "resources/lisp1.5.lsp" + ["-r SYSOUTFILE" "--read SYSOUTFILE" "Read Lisp system from file SYSOUTFILE" + :default default-sysout :validate [#(and (.exists (io/file %)) (.canRead (io/file %))) - "Could not find initfile"]] - ["-s" "--strict" "Strictly interpret the Lisp 1.5 language, without extensions."]]) + "Could not find sysout file"]] + ["-s" "--strict" "Strictly interpret the Lisp 1.5 language, without extensions."] + ["-t" "--time" "Time evaluations."]]) + +(defn- re + "Like REPL, but it isn't a loop and doesn't print." + [input] + (EVAL (READ input) NIL 0)) (defn repl "Read/eval/print loop." @@ -38,11 +67,15 @@ (print prompt) (flush) (try - (let [input (trim (read-from-console))] - (cond - (= input stop-word) (throw (ex-info "\nFærwell!" {:cause :quit})) - input (println (str "> " (print-str (EVAL (READ input) @oblist 0)))) - :else (println))) + (if-let [input (trim (read-from-console))] + (if (= input stop-word) + (throw (ex-info "\nFærwell!" {:cause :quit})) + (println + (str "> " + (print-str (if (:time *options*) + (time (re input)) + (re input)))))) + (println)) (catch Exception e @@ -77,7 +110,7 @@ "\nSprecan '" stop-word "' tó laéfan\n")) (binding [*options* (:options args)] -;; (pprint *options*) + ;; (pprint *options*) (when (:read *options*) (try (SYSIN (:read *options*)) (catch Throwable any @@ -93,5 +126,7 @@ (case (:cause data) :quit nil ;; default - (pprint data)) + (do + (println "STÆFLEAHTER: " (.getMessage e)) + (pprint data))) (println e)))))))) diff --git a/src/beowulf/gendoc.clj b/src/beowulf/gendoc.clj index dc8b5b4..8204ede 100644 --- a/src/beowulf/gendoc.clj +++ b/src/beowulf/gendoc.clj @@ -1,18 +1,48 @@ (ns beowulf.gendoc - (:require [beowulf.oblist :refer [oblist]] - [clojure.string :refer [join replace]])) + "Generate table of documentation of Lisp symbols and functions. + + NOTE: this is *very* hacky. You almost certainly do not want to + use this!" + (:require ;; [beowulf.io :refer [default-sysout SYSIN]] + [beowulf.manual :refer [format-page-references index + *manual-url* page-url]] + [beowulf.oblist :refer [NIL oblist]] + [clojure.java.browse :refer [browse-url]] + [clojure.string :as s])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Copyright (C) 2022-2023 Simon Brooke +;;; +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License +;;; as published by the Free Software Foundation; either version 2 +;;; of the License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def host-functions - "Functions which we can infer are written in Clojure." - (reduce - merge - {} - (map - ns-publics - ['beowulf.bootstrap - 'beowulf.host - 'beowulf.io - 'beowulf.read]))) + "Functions which we can infer are written in Clojure. We need to collect these + at run-time, not compile time, hence memoised function, not variable." + (memoize + (fn [] (reduce + merge + {} + (map + ns-publics + ['beowulf.bootstrap + 'beowulf.host + 'beowulf.io + 'beowulf.read]))))) ;; OK, this, improbably, works. There's probably a better way... ;; (:doc (meta (eval (read-string (str "#'" "beowulf.read" "/" "READ"))))) @@ -30,48 +60,123 @@ (when (keyword? key) (key (get-metadata-for-function function))))) - (defn- get-metadata-for-entry [entry key] - (let [fn (host-functions (symbol (first entry)))] + (let [fn ((host-functions) (symbol (first entry)))] (get-metadata-for-function fn key))) - (defn infer-type + "Try to work out what this `entry` from the oblist actually + represents." [entry] - (cond - (= (second entry) 'LAMBDA) "Lisp function" - (host-functions (first entry)) "Host function" - :else "?")) + (let [interpretation {'APVAL "Lisp variable" + 'EXPR "Lisp lambda function" + 'FEXPR "Lisp nlambda function" + 'SUBR "Host lambda function" + 'FSUBR "Host nlambda function"}] + (s/join ", " + (remove nil? + (map + #(when (some #{%} entry) (interpretation %)) + (keys interpretation)))))) + ;; (cond + ;; (= (nth entry 2) 'EXPR) "Lisp function" + ;; (= (nth entry 2) 'FEXPR) "Labeled form" + ;; ((host-functions) (first entry)) (try (if (fn? (eval (symbol ((host-functions) (first entry))))) + ;; "Host function" + ;; "Host variable") + ;; (catch Exception _ + ;; "?Host macro?")) + ;; :else "Lisp variable")) + +(defn- format-clj-signature + "Format the signature of the Clojure function represented by `symbol` for + Lisp documentation." + [symbol arglists] + (s/join + "; " + (doall + (map + (fn [l] + (s/join (concat (list "(" symbol " ") + (s/join " " (map #(s/upper-case (str %)) l)) (list ")")))) + arglists)))) (defn infer-signature + "Infer the signature of the function value of this oblist `entry`, if any." [entry] (cond - (= (count entry) 1) (get-metadata-for-entry entry :arglists) - (= (second entry) 'LAMBDA) (nth entry 2) + (= (count entry) 1) (format-clj-signature + (first entry) + (get-metadata-for-entry entry :arglists)) + (= (second entry) 'LAMBDA) (str (cons (first entry) (nth entry 2))) :else "?")) +(defn infer-implementation + [entry] + (or (:implementation (index (keyword (first entry)))) "?")) + ;; (case (second entry) + ;; LAMBDA (format "%s-fn" (second entry)) + ;; LABEL (format "%s-fn" (second entry)) + ;; (or (:implementation (index (keyword (first entry)))) (str entry)))) + (defn find-documentation + "Find appropriate documentation for this `entry` from the oblist." [entry] - (cond - (= (count entry) 1) (if-let [doc (get-metadata-for-entry entry :doc)] - (replace doc "\n" " ") - "?") - :else "?")) + (let [k (keyword (first entry))] + (cond + (some #{'SUBR 'FSUBR} entry) (if-let [doc (get-metadata-for-entry entry :doc)] + (s/replace doc "\n" " ") + "?") + (k index) (str "see manual pages " (format-page-references k)) + :else "?"))) (defn gen-doc-table - [] - (join - "\n" - (doall - (concat - '("| Symbol | Type | Signature | Documentation |" - "|--------|------|-----------|---------------|") - (map - #(format "| %s | %s | %s | %s |" - (first %) - (infer-type %) - (infer-signature %) - (find-documentation %)) - @oblist))))) + ([] + ;; (gen-doc-table default-sysout)) + ;; ([sysfile] + ;; (when (= NIL @oblist) + ;; (try (SYSIN sysfile) + ;; (catch Throwable any + ;; (println (.getMessage any) " while reading " sysfile)))) + (s/join + "\n" + (doall + (concat + '("| Function | Type | Signature | Implementation | Documentation |" + "|--------------|----------------|------------------|----------------|----------------------|") + (map + #(format "| %-12s | %-14s | %-16s | %-14s | %-20s |" + (first %) + (infer-type %) + (infer-signature %) + (infer-implementation %) + (find-documentation %)) + @oblist)))))) -;; (println (gen-doc-table)) \ No newline at end of file +(defn gen-index + ([] (gen-index "" "resources/scratch/manual.md")) + ([url destination] + (binding [*manual-url* url] + (spit destination + (with-out-str + (doall + (map + println + (list "## Index" + "" + (gen-doc-table))))))))) + +(defn open-doc + "Open the documentation page for this `symbol`, if known, in the default + web browser." + [symbol] + (let [doc (get-metadata-for-function symbol :doc)] + (if-let [pages (:page-nos (index (keyword symbol)))] + (browse-url (page-url (first pages))) + (if doc + (println doc) + (throw (ex-info "No documentation found" + {:phase :host + :function 'DOC + :args (list symbol) + :type :beowulf})))))) \ No newline at end of file diff --git a/src/beowulf/host.clj b/src/beowulf/host.clj index 44bbd31..48f622d 100644 --- a/src/beowulf/host.clj +++ b/src/beowulf/host.clj @@ -2,28 +2,142 @@ "provides Lisp 1.5 functions which can't be (or can't efficiently be) implemented in Lisp 1.5, which therefore need to be implemented in the host language, in this case Clojure." - (:require [clojure.string :refer [upper-case]] - [beowulf.cons-cell :refer [F make-beowulf-list T]] - ;; note hyphen - this is Clojure... - [beowulf.oblist :refer [NIL]]) - (:import [beowulf.cons_cell ConsCell] - ;; note underscore - same namespace, but Java. + (:require [beowulf.cons-cell :refer [F make-beowulf-list make-cons-cell + pretty-print T]] ;; note hyphen - this is Clojure... + [beowulf.gendoc :refer [open-doc]] + [beowulf.oblist :refer [*options* NIL oblist]] + [clojure.set :refer [union]] + [clojure.string :refer [upper-case]]) + (:import [beowulf.cons_cell ConsCell] ;; note underscore - same namespace, but Java. )) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Copyright (C) 2022-2023 Simon Brooke +;;; +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License +;;; as published by the Free Software Foundation; either version 2 +;;; of the License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; these are CANDIDATES to be host-implemented. only a subset of them MUST be. ;; those which can be implemented in Lisp should be, since that aids ;; portability. -(defn AND - "`T` if and only if none of my `args` evaluate to either `F` or `NIL`, - else `F`. - - In `beowulf.host` principally because I don't yet feel confident to define - varargs functions in Lisp." - [& args] - (if (empty? (filter #(or (= 'F %) (= NIL %) (nil? %)) args)) - 'T - 'F)) + +(defn lax? + "Are we in lax mode? If so. return true; is not, throw an exception with + this `symbol`." + [symbol] + (when (:strict *options*) + (throw (ex-info (format "%s ne āfand innan Lisp 1.5" symbol) + {:type :strict + :phase :host + :function symbol}))) + true) + +;;;; Basic operations on cons cells ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn CONS + "Construct a new instance of cons cell with this `car` and `cdr`." + [car cdr] + (beowulf.cons_cell.ConsCell. car cdr (gensym "c"))) + +(defn CAR + "Return the item indicated by the first pointer of a pair. NIL is treated + specially: the CAR of NIL is NIL." + [x] + (cond + (= x NIL) NIL + (instance? ConsCell x) (or (.getCar x) NIL) + :else (throw (ex-info + (str "Ne can tace CAR of `" x "` (" (.getName (.getClass x)) ")") + {:phase :host + :function 'CAR + :args (list x) + :type :beowulf})))) + +(defn CDR + "Return the item indicated by the second pointer of a pair. NIL is treated + specially: the CDR of NIL is NIL." + [x] + (cond + (= x NIL) NIL + (instance? ConsCell x) (or (.getCdr x) NIL) + :else (throw (ex-info + (str "Ne can tace CDR of `" x "` (" (.getName (.getClass x)) ")") + {:phase :host + :function 'CDR + :args (list x) + :type :beowulf})))) + + +(defn uaf + "Universal access function; `l` is expected to be an arbitrary LISP list, `path` + a (clojure) list of the characters `a` and `d`. Intended to make declaring + all those fiddly `#'c[ad]+r'` functions a bit easier" + [l path] + (cond + (= l NIL) NIL + (empty? path) l + :else + (try + (case (last path) + \a (uaf (.first l) (butlast path)) + \d (uaf (.getCdr l) (butlast path)) + (throw (ex-info (str "uaf: unexpected letter in path (only `a` and `d` permitted): " (last path)) + {:cause :uaf + :detail :unexpected-letter + :expr (last path)}))) + (catch ClassCastException e + (throw (ex-info + (str "uaf: Not a LISP list? " (type l)) + {:cause :uaf + :detail :not-a-lisp-list + :expr l} + e)))))) + +(defmacro CAAR [x] `(uaf ~x '(\a \a))) +(defmacro CADR [x] `(uaf ~x '(\a \d))) +(defmacro CDDR [x] `(uaf ~x '(\d \d))) +(defmacro CDAR [x] `(uaf ~x '(\d \a))) + +(defmacro CAAAR [x] `(uaf ~x '(\a \a \a))) +(defmacro CAADR [x] `(uaf ~x '(\a \a \d))) +(defmacro CADAR [x] `(uaf ~x '(\a \d \a))) +(defmacro CADDR [x] `(uaf ~x '(\a \d \d))) +(defmacro CDDAR [x] `(uaf ~x '(\d \d \a))) +(defmacro CDDDR [x] `(uaf ~x '(\d \d \d))) +(defmacro CDAAR [x] `(uaf ~x '(\d \a \a))) +(defmacro CDADR [x] `(uaf ~x '(\d \a \d))) + +(defmacro CAAAAR [x] `(uaf ~x '(\a \a \a \a))) +(defmacro CAADAR [x] `(uaf ~x '(\a \a \d \a))) +(defmacro CADAAR [x] `(uaf ~x '(\a \d \a \a))) +(defmacro CADDAR [x] `(uaf ~x '(\a \d \d \a))) +(defmacro CDDAAR [x] `(uaf ~x '(\d \d \a \a))) +(defmacro CDDDAR [x] `(uaf ~x '(\d \d \d \a))) +(defmacro CDAAAR [x] `(uaf ~x '(\d \a \a \a))) +(defmacro CDADAR [x] `(uaf ~x '(\d \a \d \a))) +(defmacro CAAADR [x] `(uaf ~x '(\a \a \a \d))) +(defmacro CAADDR [x] `(uaf ~x '(\a \a \d \d))) +(defmacro CADADR [x] `(uaf ~x '(\a \d \a \d))) +(defmacro CADDDR [x] `(uaf ~x '(\a \d \d \d))) +(defmacro CDDADR [x] `(uaf ~x '(\d \d \a \d))) +(defmacro CDDDDR [x] `(uaf ~x '(\d \d \d \d))) +(defmacro CDAADR [x] `(uaf ~x '(\d \a \a \d))) +(defmacro CDADDR [x] `(uaf ~x '(\d \a \d \d))) (defn RPLACA "Replace the CAR pointer of this `cell` with this `value`. Dangerous, should @@ -31,24 +145,39 @@ performance hacks in early Lisps)" [^ConsCell cell value] (if - (instance? ConsCell cell) + (instance? ConsCell cell) (if - (or - (instance? ConsCell value) - (number? value) - (symbol? value) - (= value NIL)) - (do + (or + (instance? ConsCell value) + (number? value) + (symbol? value) + (= value NIL)) + (try (.rplaca cell value) - cell) + cell + (catch Throwable any + (throw (ex-info + (str (.getMessage any) " in RPLACA: `") + {:cause :upstream-error + :phase :host + :function :rplaca + :args (list cell value) + :type :beowulf} + any)))) (throw (ex-info - (str "Invalid value in RPLACA: `" value "` (" (type value) ")") - {:cause :bad-value - :detail :rplaca}))) + (str "Un-ġefōg þing in RPLACA: `" value "` (" (type value) ")") + {:cause :bad-value + :phase :host + :function :rplaca + :args (list cell value) + :type :beowulf}))) (throw (ex-info - (str "Invalid cell in RPLACA: `" cell "` (" (type cell) ")") - {:cause :bad-value - :detail :rplaca})))) + (str "Uncynlic miercels in RPLACA: `" cell "` (" (type cell) ")") + {:cause :bad-cell + :phase :host + :function :rplaca + :args (list cell value) + :type :beowulf})))) (defn RPLACD "Replace the CDR pointer of this `cell` with this `value`. Dangerous, should @@ -56,24 +185,169 @@ performance hacks in early Lisps)" [^ConsCell cell value] (if - (instance? ConsCell cell) + (instance? ConsCell cell) (if - (or - (instance? ConsCell value) - (number? value) - (symbol? value) - (= value NIL)) - (do + (or + (instance? ConsCell value) + (number? value) + (symbol? value) + (= value NIL)) + (try (.rplacd cell value) - cell) + cell + (catch Throwable any + (throw (ex-info + (str (.getMessage any) " in RPLACD: `") + {:cause :upstream-error + :phase :host + :function :rplacd + :args (list cell value) + :type :beowulf} + any)))) (throw (ex-info - (str "Invalid value in RPLACD: `" value "` (" (type value) ")") - {:cause :bad-value - :detail :rplaca}))) + (str "Un-ġefōg þing in RPLACD: `" value "` (" (type value) ")") + {:cause :bad-value + :phase :host + :function :rplacd + :args (list cell value) + :type :beowulf}))) (throw (ex-info - (str "Invalid cell in RPLACD: `" cell "` (" (type cell) ")") - {:cause :bad-value - :detail :rplaca}))));; PLUS + (str "Uncynlic miercels in RPLACD: `" cell "` (" (type cell) ")") + {:cause :bad-cell + :phase :host + :detail :rplacd + :args (list cell value) + :type :beowulf}))));; PLUS + +(defn LIST + [& args] + (make-beowulf-list args)) + +;;;; Basic predicates ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro NULL + "Returns `T` if and only if the argument `x` is bound to `NIL`; else `F`." + [x] + `(if (= ~x NIL) T F)) + +(defmacro NILP + "Not part of LISP 1.5: `T` if `o` is `NIL`, else `NIL`." + [x] + `(if (= ~x NIL) T NIL)) + +(defn ATOM + "Returns `T` if and only if the argument `x` is bound to an atom; else `F`. + It is not clear to me from the documentation whether `(ATOM 7)` should return + `T` or `F`. I'm going to assume `T`." + [x] + (if (or (symbol? x) (number? x)) T F)) + +(defmacro ATOM? + "The convention of returning `F` from predicates, rather than `NIL`, is going + to tie me in knots. This is a variant of `ATOM` which returns `NIL` + on failure." + [x] + `(if (or (symbol? ~x) (number? ~x)) T NIL)) + +(defn EQ + "Returns `T` if and only if both `x` and `y` are bound to the same atom, + else `NIL`." + [x y] + (cond (and (instance? ConsCell x) + (.equals x y)) T + (and (= (ATOM x) T) (= x y)) T + :else NIL)) + +(defn EQUAL + "This is a predicate that is true if its two arguments are identical + S-expressions, and false if they are different. (The elementary predicate + `EQ` is defined only for atomic arguments.) The definition of `EQUAL` is + an example of a conditional expression inside a conditional expression. + + NOTE: returns `F` on failure, not `NIL`" + [x y] + (cond + (= (ATOM x) T) (if (= x y) T F) + (= (EQUAL (CAR x) (CAR y)) T) (EQUAL (CDR x) (CDR y)) + :else F)) + +(defn AND + "`T` if and only if none of my `args` evaluate to either `F` or `NIL`, + else `F`. + + In `beowulf.host` principally because I don't yet feel confident to define + varargs functions in Lisp." + [& args] + ;; (println "AND: " args " type: " (type args) " seq? " (seq? args)) + ;; (println " filtered: " (seq (filter #{F NIL} args))) + (cond (= NIL args) T + (seq? args) (if (seq (filter #{F NIL} args)) F T) + :else T)) + + +(defn OR + "`T` if and only if at least one of my `args` evaluates to something other + than either `F` or `NIL`, else `F`. + + In `beowulf.host` principally because I don't yet feel confident to define + varargs functions in Lisp." + [& args] + ;; (println "OR: " args " type: " (type args) " seq? " (seq? args)) + ;; (println " filtered: " (seq (remove #{F NIL} args))) + (cond (= NIL args) F + (seq? args) (if (seq (remove #{F NIL} args)) T F) + :else F)) + + +;;;; Operations on lists ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; TODO: These are candidates for moving to Lisp urgently! + +(defn ASSOC + "If a is an association list such as the one formed by PAIRLIS in the above + example, then assoc will produce the first pair whose first term is x. Thus + it is a table searching function. + + All args are assumed to be `beowulf.cons-cell/ConsCell` objects. + See page 12 of the Lisp 1.5 Programmers Manual. + + **NOTE THAT** this function is overridden by an implementation in Lisp, + but is currently still present for bootstrapping." + [x a] + (cond + (= NIL a) NIL ;; this clause is not present in the original but is added for + ;; robustness. + (= (EQUAL (CAAR a) x) T) (CAR a) + :else + (ASSOC x (CDR a)))) + +(defn PAIRLIS + "This function gives the list of pairs of corresponding elements of the + lists `x` and `y`, and APPENDs this to the list `a`. The resultant list + of pairs, which is like a table with two columns, is called an + association list. + + Eessentially, it builds the environment on the stack, implementing shallow + binding. + + All args are assumed to be `beowulf.cons-cell/ConsCell` objects. + See page 12 of the Lisp 1.5 Programmers Manual. + + **NOTE THAT** this function is overridden by an implementation in Lisp, + but is currently still present for bootstrapping." + [x y a] + (cond + ;; the original tests only x; testing y as well will be a little more + ;; robust if `x` and `y` are not the same length. + (or (= NIL x) (= NIL y)) a + :else (make-cons-cell + (make-cons-cell (CAR x) (CAR y)) + (PAIRLIS (CDR x) (CDR y) a)))) + +;;;; Arithmetic ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; TODO: When in strict mode, should we limit arithmetic precision to that +;; supported by Lisp 1.5? (defn PLUS [& args] @@ -118,6 +392,16 @@ [x] (if (number? x) T F)) +(defn LESSP + [x y] + (if (< x y) T F)) + +(defn GREATERP + [x y] + (if (> x y) T F)) + +;;;; Miscellaneous ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn GENSYM "Generate a unique symbol." [] @@ -126,13 +410,162 @@ (defn ERROR "Throw an error" [& args] - (throw (ex-info "LISP ERROR" {:cause (apply vector args) - :phase :eval}))) + (throw (ex-info "LISP STÆFLEAHTER" {:args args + :phase :eval + :function 'ERROR + :type :lisp + :code (or (first args) 'A1)}))) -(defn LESSP - [x y] - (< x y)) +;;;; Assignment and the object list ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn GREATERP - [x y] - (> x y)) +(defn OBLIST + "Return a list of the symbols currently bound on the object list. + + **NOTE THAT** in the Lisp 1.5 manual, footnote at the bottom of page 69, it implies + that an argument can be passed but I'm not sure of the semantics of + this." + [] + (if (instance? ConsCell @oblist) + (make-beowulf-list (map CAR @oblist)) + NIL)) + +(def magic-marker + "The unexplained magic number which marks the start of a property list." + (Integer/parseInt "77777" 8)) + +(defn PUT + "Put this `value` as the value of the property indicated by this `indicator` + of this `symbol`. Return `value` on success. + + NOTE THAT there is no `PUT` defined in the manual, but it would have been + easy to have defined it so I don't think this fully counts as an extension." + [symbol indicator value] + (if-let [binding (ASSOC symbol @oblist)] + (if-let [prop (ASSOC indicator (CDDR binding))] + (RPLACD prop value) + (RPLACD binding + (make-cons-cell + magic-marker + (make-cons-cell + indicator + (make-cons-cell value (CDDR binding)))))) + (swap! + oblist + (fn [ob s p v] + (make-cons-cell + (make-beowulf-list (list s magic-marker p v)) + ob)) + symbol indicator value))) + +(defn GET + "From the manual: + + '`get` is somewhat like `prop`; however its value is car of the rest of + the list if the `indicator` is found, and NIL otherwise.' + + It's clear that `GET` is expected to be defined in terms of `PROP`, but + we can't implement `PROP` here because we lack `EVAL`; and we can't have + `EVAL` here because both it and `APPLY` depends on `GET`. + + OK, It's worse than that: the statement of the definition of `GET` (and + of) `PROP` on page 59 says that the first argument to each must be a list; + But the in the definition of `ASSOC` on page 70, when `GET` is called its + first argument is always an atom. Since it's `ASSOC` and `EVAL` which I + need to make work, I'm going to assume that page 59 is wrong." + [symbol indicator] + (let [binding (ASSOC symbol @oblist) + val (cond + (= binding NIL) NIL + (= magic-marker + (CADR binding)) (loop [b binding] + ;; (println "GET loop, seeking " indicator ":") + ;; (pretty-print b) + (if (instance? ConsCell b) + (if (= (CAR b) indicator) + (CADR b) ;; <- this is what we should actually be returning + (recur (CDR b))) + NIL)) + :else (throw + (ex-info "Misformatted property list (missing magic marker)" + {:phase :host + :function :get + :args (list symbol indicator) + :type :beowulf})))] + ;; (println "<< GET returning: " val) + val)) + +(defn DEFLIST + "For each pair in this association list `a-list`, set the property with this + `indicator` of the symbol which is the first element of the pair to the + value which is the second element of the pair. See page 58 of the manual." + [a-list indicator] + (map + #(PUT (CAR %) indicator (CDR %)) + a-list)) + +(defn DEFINE + "Bootstrap-only version of `DEFINE` which, post boostrap, can be overwritten + in LISP. + + The single argument to `DEFINE` should be an association list of symbols to + lambda functions. See page 58 of the manual." + [a-list] + (DEFLIST a-list 'EXPR)) + +(defn SET + "Implementation of SET in Clojure. Add to the `oblist` a binding of the + value of `var` to the value of `val`. NOTE WELL: this is not SETQ!" + [symbol val] + (PUT symbol 'APVAL val)) + +;;;; TRACE and friends ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def traced-symbols + "Symbols currently being traced." + (atom #{})) + +(defn traced? + "Return `true` iff `s` is a symbol currently being traced, else `nil`." + [s] + (try (contains? @traced-symbols s) + (catch Throwable _ nil))) + +(defn TRACE + "Add this `s` to the set of symbols currently being traced. If `s` + is not a symbol or sequence of symbols, does nothing." + [s] + (swap! traced-symbols + #(cond + (symbol? s) (conj % s) + (and (seq? s) (every? symbol? s)) (union % (set s)) + :else %))) + +(defn UNTRACE + "Remove this `s` from the set of symbols currently being traced. If `s` + is not a symbol or sequence of symbols, does nothing." + [s] + (cond + (symbol? s) (swap! traced-symbols #(set (remove (fn [x] (= s x)) %))) + (and (seq? s) (every? symbol? s)) (map UNTRACE s)) + @traced-symbols) + +;;;; Extensions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn DOC + "Open the page for this `symbol` in the Lisp 1.5 manual, if known, in the + default web browser. + + **NOTE THAT** this is an extension function, not available in strct mode." + [symbol] + (when (lax? 'DOC) + (open-doc symbol))) + +(defn CONSP + "Return `T` if object `o` is a cons cell, else `F`. + + **NOTE THAT** this is an extension function, not available in strct mode. + I believe that Lisp 1.5 did not have any mechanism for testing whether an + argument was, or was not, a cons cell." + [o] + (when (lax? 'CONSP) + (if (instance? ConsCell o) 'T 'F))) \ No newline at end of file diff --git a/src/beowulf/interop.clj b/src/beowulf/interop.clj new file mode 100644 index 0000000..d4569fa --- /dev/null +++ b/src/beowulf/interop.clj @@ -0,0 +1,129 @@ +(ns beowulf.interop + (:require [beowulf.cons-cell :refer [make-beowulf-list]] + [beowulf.host :refer [CAR CDR]] + [beowulf.oblist :refer [*options* NIL]] + [clojure.string :as s :refer [last-index-of lower-case split + upper-case]])) + +;;;; INTEROP feature ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn listify-qualified-name + "We need to be able to print something we can link to the particular Clojure + function `subr` in a form in which Lisp 1.5 is able to read it back in and + relink it. + + This assumes `subr` is either + 1. a string in the format `#'beowulf.io/SYSIN` or `beowulf.io/SYSIN`; or + 2. something which, when coerced to a string with `str`, will have such + a format." + [subr] + (make-beowulf-list + (map + #(symbol (upper-case %)) + (remove empty? (split (str subr) #"[#'./]"))))) + + +(defn interpret-qualified-name + "For interoperation with Clojure, it will often be necessary to pass + qualified names that are not representable in Lisp 1.5. This function + takes a sequence in the form `(PART PART PART... NAME)` and returns + a symbol in the form `part.part.part/NAME`. This symbol will then be + tried in both that form and lower-cased. Names with hyphens or + underscores cannot be represented with this scheme." + ([l] + (symbol + (let [n (s/join "." + (concat (map #(lower-case (str %)) (butlast l)) + (list (last l)))) + s (last-index-of n ".")] + (if s + (str (subs n 0 s) "/" (subs n (inc s))) + n))))) + +(defn to-beowulf + "Return a beowulf-native representation of the Clojure object `o`. + Numbers and symbols are unaffected. Collections have to be converted; + strings must be converted to symbols." + [o] + (cond + (coll? o) (make-beowulf-list o) + (string? o) (symbol (s/upper-case o)) + :else o)) + +(defn to-clojure + "If l is a `beowulf.cons_cell.ConsCell`, return a Clojure list having the + same members in the same order." + [l] + (cond + (not (instance? beowulf.cons_cell.ConsCell l)) + l + (= (CDR l) NIL) + (list (to-clojure (CAR l))) + :else + (conj (to-clojure (CDR l)) (to-clojure (CAR l))))) + +(defn INTEROP + "Clojure (or other host environment) interoperation API. `fn-symbol` is expected + to be either + + 1. a symbol bound in the host environment to a function; or + 2. a sequence (list) of symbols forming a qualified path name bound to a + function. + + Lower case characters cannot normally be represented in Lisp 1.5, so both the + upper case and lower case variants of `fn-symbol` will be tried. If the + function you're looking for has a mixed case name, that is not currently + accessible. + + `args` is expected to be a Lisp 1.5 list of arguments to be passed to that + function. Return value must be something acceptable to Lisp 1.5, so either + a symbol, a number, or a Lisp 1.5 list. + + If `fn-symbol` is not found (even when cast to lower case), or is not a function, + or the value returned cannot be represented in Lisp 1.5, an exception is thrown + with `:cause` bound to `:interop` and `:detail` set to a value representing the + actual problem." + [fn-symbol args] + (if-not (:strict *options*) + (let + [q-name (if + (seq? fn-symbol) + (interpret-qualified-name fn-symbol) + fn-symbol) + l-name (symbol (s/lower-case q-name)) + f (cond + (try + (fn? (eval l-name)) + (catch java.lang.ClassNotFoundException _ nil)) l-name + (try + (fn? (eval q-name)) + (catch java.lang.ClassNotFoundException _ nil)) q-name + :else (throw + (ex-info + (str "INTEROP: ungecnáwen þegnung `" fn-symbol "`") + {:cause :interop + :detail :not-found + :name fn-symbol + :also-tried l-name}))) + args' (to-clojure args)] +;; (print (str "INTEROP: eahtiende `" (cons f args') "`")) + (flush) + (let [result (eval (conj args' f))] ;; this has the potential to blow up the world +;; (println (str "; ágiefende `" result "`")) + (cond + (instance? beowulf.cons_cell.ConsCell result) result + (coll? result) (make-beowulf-list result) + (symbol? result) result + (string? result) (symbol result) + (number? result) result + :else (throw + (ex-info + (str "INTEROP: Ne can eahtiende `" result "` to Lisp 1.5.") + {:cause :interop + :detail :not-representable + :result result}))))) + (throw + (ex-info + (str "INTEROP ne āfand innan Lisp 1.5.") + {:cause :interop + :detail :strict})))) diff --git a/src/beowulf/io.clj b/src/beowulf/io.clj index 14d798a..62ead4c 100644 --- a/src/beowulf/io.clj +++ b/src/beowulf/io.clj @@ -15,14 +15,40 @@ oblist with data from that file. Hence functions SYSOUT and SYSIN, which do just that." - (:require [beowulf.cons-cell :refer [pretty-print]] - [beowulf.oblist :refer [*options* oblist]] + (:require [beowulf.cons-cell :refer [make-beowulf-list make-cons-cell + pretty-print]] + [beowulf.host :refer [CADR CAR CDDR CDR]] + [beowulf.interop :refer [interpret-qualified-name + listify-qualified-name]] + [beowulf.oblist :refer [*options* NIL oblist]] [beowulf.read :refer [READ]] [clojure.java.io :refer [file resource]] [clojure.string :refer [ends-with?]] [java-time.api :refer [local-date local-date-time]])) -(defn- full-path +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Copyright (C) 2022-2023 Simon Brooke +;;; +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License +;;; as published by the Free Software Foundation; either version 2 +;;; of the License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def ^:constant default-sysout "lisp1.5.lsp") + +(defn- full-path [fp] (str (if (:filepath *options*) @@ -37,11 +63,31 @@ "" ".lsp"))) +;; (find-var (symbol "beowulf.io/SYSIN")) +;; (@(resolve (symbol "beowulf.host/TIMES")) 2 2) + +(defn safely-wrap-subr + [entry] + (cond (= entry NIL) NIL + (= (CAR entry) 'SUBR) (make-cons-cell + (CAR entry) + (make-cons-cell + (listify-qualified-name (CADR entry)) + (CDDR entry))) + :else (make-cons-cell + (CAR entry) (safely-wrap-subr (CDR entry))))) + +(defn safely-wrap-subrs + [objects] + (make-beowulf-list (map safely-wrap-subr objects))) + (defn SYSOUT "Dump the current content of the object list to file. If no `filepath` is specified, a file name will be constructed of the symbol `Sysout` and the current date. File paths will be considered relative to the filepath - set when starting Lisp." + set when starting Lisp. + + **NOTE THAT** this is an extension function, not available in strct mode." ([] (SYSOUT nil)) ([filepath] @@ -55,7 +101,41 @@ (println (format ";; generated by %s" (System/getenv "USER")))) (println (apply str (repeat 79 ";"))) (println) - (pretty-print @oblist))))) + (let [output (safely-wrap-subrs @oblist)] + (pretty-print output) + ))))) + +(defn resolve-subr + "If this oblist `entry` references a subroutine, attempt to fix up that + reference." + ([entry] + (or (resolve-subr entry 'SUBR) + (resolve-subr entry 'FSUBR))) + ([entry prop] + (cond (= entry NIL) NIL + (= (CAR entry) prop) (try + (make-cons-cell + (CAR entry) + (make-cons-cell + (interpret-qualified-name + (CADR entry)) + (CDDR entry))) + (catch Exception _ + (print "Warnung: ne can āfinde " + (CADR entry)) + (CDDR entry))) + :else (make-cons-cell + (CAR entry) (resolve-subr (CDR entry)))))) + + +(defn- resolve-subroutines + "Attempt to fix up the references to subroutines (Clojure functions) among + these `objects`, being new content for the object list." + [objects] + (make-beowulf-list + (map + resolve-subr + objects))) (defn SYSIN "Read the contents of the file at this `filename` into the object list. @@ -70,16 +150,22 @@ **NOTE THAT** if the provided `filename` does not end with `.lsp` (which, if you're writing it from the Lisp REPL, it won't), the extension `.lsp` - will be appended." - [filename] - (let [fp (file (full-path (str filename))) - file (when (and (.exists fp) (.canRead fp)) fp) - res (try (resource filename) - (catch Throwable _ nil)) - content (try (READ (slurp (or file res))) - (catch Throwable any - (throw (ex-info "Could not read from file" - {:context "SYSIN" - :filepath fp} - any))))] - (swap! oblist #(when (or % (seq content)) content)))) + will be appended. + + **NOTE THAT** this is an extension function, not available in strct mode." + ([] + (SYSIN (or (:read *options*) (str "resources/" default-sysout)))) + ([filename] + (let [fp (file (full-path (str filename))) + file (when (and (.exists fp) (.canRead fp)) fp) + res (try (resource filename) + (catch Throwable _ nil)) + content (try (READ (slurp (or file res))) + (catch Throwable _ + (throw (ex-info "Ne can ārǣde" + {:context "SYSIN" + :filename filename + :filepath fp}))))] + (swap! oblist + #(when (or % (seq content)) + (resolve-subroutines content)))))) diff --git a/src/beowulf/manual.clj b/src/beowulf/manual.clj new file mode 100644 index 0000000..8a36fe5 --- /dev/null +++ b/src/beowulf/manual.clj @@ -0,0 +1,769 @@ +(ns beowulf.manual + "Experimental code for accessing the manual online." + (:require [clojure.string :refer [ends-with? join trim]])) + +(def ^:dynamic *manual-url* + "https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf") + +(def ^:constant index + "This is data extracted from the index pages of `Lisp 1.5 Programmer's Manual`. + It's here in the hope that we can automatically link to an online PDF link + to the manual when the user invokes a function probably called `DOC` or `HELP`." + {:RECIP + {:fn-name "RECIP", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "64"]}, + :QUOTE + {:fn-name "QUOTE", + :call-type "FSUBR", + :implementation "", + :page-nos ["10" "22" "71"]}, + :RECLAIM + {:fn-name "RECLAIM", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["67"]}, + :NUMOB + {:fn-name "NUMOB", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["86"]}, + :EVLIS + {:fn-name "EVLIS", + :call-type "SUBR", + :implementation "", + :page-nos ["71"]}, + :DASH + {:fn-name "DASH", + :call-type "SUBR", + :implementation "PREDICATE APVAL", + :page-nos ["85" "87 "]}, + :EQUAL + {:fn-name "EQUAL", + :call-type "SUBR", + :implementation "PREDICATE", + :page-nos ["11" "26" "57"]}, + :PRIN1 + {:fn-name "PRIN1", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["65" "84"]}, + :REMFLAG + {:fn-name "REMFLAG", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["41" "60"]}, + :DEFINE + {:fn-name "DEFINE", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["15" "18" "58"]}, + :PUNCHLAP + {:fn-name "PUNCHLAP", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION LIBRARY", + :page-nos ["68" "76"]}, + :STARTREAD + {:fn-name "STARTREAD", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["87"]}, + :PERIOD + {:fn-name "PERIOD", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :CP1 + {:fn-name "CP1", + :call-type "SUBR", + :implementation "", + :page-nos ["66"]}, + :NCONC + {:fn-name "NCONC", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["62"]}, + :EQ + {:fn-name "EQ", + :call-type "SUBR", + :implementation "PREDICATE", + :page-nos ["3" "23" "57"]}, + :RPLACD + {:fn-name "RPLACD", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["41" "58"]}, + :PROG2 + {:fn-name "PROG2", + :call-type "SUBR", + :implementation "", + :page-nos ["42" "66"]}, + :UNCOUNT + {:fn-name "UNCOUNT", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["34" "66"]}, + :ERROR1 + {:fn-name "ERROR1", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["88"]}, + :EXPT + {:fn-name "EXPT", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "64"]}, + :NOT + {:fn-name "NOT", + :call-type "SUBR", + :implementation "PREDICATE", + :page-nos ["21" "23" "58"]}, + :SLASH + {:fn-name "SLASH", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :RPLACA + {:fn-name "RPLACA", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["41" "58"]}, + :QUOTIENT + {:fn-name "QUOTIENT", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "64"]}, + :UNPACK + {:fn-name "UNPACK", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["87"]}, + :CONC + {:fn-name "CONC", + :call-type "FEXPR", + :implementation "", + :page-nos ["61"]}, + :CAR + {:fn-name "CAR", + :call-type "SUBR", + :implementation "", + :page-nos ["2" "56"]}, + :GENSYM + {:fn-name "GENSYM", + :call-type "SUBR", + :implementation "", + :page-nos ["66"]}, + :PROP + {:fn-name "PROP", + :call-type "SUBR", + :implementation "FUNCTIONAL ", + :page-nos [" 59"]}, + :MEMBER + {:fn-name "MEMBER", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos ["11" "62"]}, + :UNTRACESET + {:fn-name "UNTRACESET", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["68"]}, + :UNTRACE + {:fn-name "UNTRACE", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["32" "66"]}, + :MINUSP + {:fn-name "MINUSP", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos ["26" "64"]}, + :F + {:fn-name "F", + :call-type "APVAL", + :implementation "", + :page-nos ["22" "69"]}, + :SPECIAL + {:fn-name "SPECIAL", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["64" "78"]}, + :LPAR + {:fn-name "LPAR", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :GO + {:fn-name "GO", + :call-type "FSUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["30" "72"]}, + :MKNAM + {:fn-name "MKNAM", + :call-type "SUBR", + :implementation "", + :page-nos ["86"]}, + :COMMON + {:fn-name "COMMON", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["64" "78"]}, + :NUMBERP + {:fn-name "NUMBERP", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos ["26" "64"]}, + :CONS + {:fn-name "CONS", + :call-type "SUBR", + :implementation "", + :page-nos ["2" "56"]}, + :PLUS + {:fn-name "PLUS", + :call-type "FSUBR", + :implementation "", + :page-nos ["25" "63"]}, + :SET + {:fn-name "SET", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["30" "71"]}, + :DOLLAR + {:fn-name "DOLLAR", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :SASSOC + {:fn-name "SASSOC", + :call-type "SUBR", + :implementation "FUNCTIONAL", + :page-nos ["60"]}, + :SELECT + {:fn-name "SELECT", + :call-type "FEXPR", + :implementation "", + :page-nos ["66"]}, + :OPDEFINE + {:fn-name "OPDEFINE", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["65" "75"]}, + :PAUSE + {:fn-name "PAUSE", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["67"]}, + :AND + {:fn-name "AND", + :call-type "FSUBR", + :implementation "PREDICATE", + :page-nos ["21" "58"]}, + :COMMA + {:fn-name "COMMA", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :EFFACE + {:fn-name "EFFACE", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["63"]}, + :CSETQ + {:fn-name "CSETQ", + :call-type "FEXPR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["59"]}, + :OPCHAR + {:fn-name "OPCHAR", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos [" 87"]}, + :PRINTPROP + {:fn-name "PRINTPROP", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION LIBRARY ", + :page-nos ["68"]}, + :PLB + {:fn-name "PLB", + :call-type "SUBR", + :implementation "PSEUDO- FUNCTION", + :page-nos ["67"]}, + :DIGIT + {:fn-name "DIGIT", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos ["87"]}, + :PUNCHDEF + {:fn-name "PUNCHDEF", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION LIBRARY", + :page-nos ["68"]}, + :ARRAY + {:fn-name "ARRAY", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["27" "64"]}, + :MAX + {:fn-name "MAX", + :call-type "FSUBR", + :implementation "", + :page-nos ["26" "64"]}, + :INTERN + {:fn-name "INTERN", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["67" "87"]}, + :NIL + {:fn-name "NIL", + :call-type "APVAL", + :implementation "", + :page-nos ["22" "69"]}, + :TIMES + {:fn-name "TIMES", + :call-type "FSUBR", + :implementation "", + :page-nos ["26" "64"]}, + :ERROR + {:fn-name "ERROR", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["32" "66"]}, + :PUNCH + {:fn-name "PUNCH", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["65" "84"]}, + :REMPROP + {:fn-name "REMPROP", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["41" "59"]}, + :DIVIDE + {:fn-name "DIVIDE", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "64"]}, + :OR + {:fn-name "OR", + :call-type "FSUBR", + :implementation "PREDICATE ", + :page-nos ["21" "58"]}, + :SUBLIS + {:fn-name "SUBLIS", + :call-type "SUBR", + :implementation "", + :page-nos ["12" "61"]}, + :LAP + {:fn-name "LAP", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["65" "73"]}, + :PROG + {:fn-name "PROG", + :call-type "FSUBR", + :implementation "", + :page-nos ["29" "71"]}, + :T + {:fn-name "T", + :call-type "APVAL", + :implementation "", + :page-nos ["22" "69"]}, + :GREATERP + {:fn-name "GREATERP", + :call-type "SUBR", + :implementation "PREDICATE", + :page-nos ["26" "64"]}, + :CSET + {:fn-name "CSET", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["17" "59"]}, + :FUNCTION + {:fn-name "FUNCTION", + :call-type "FSUBR", + :implementation "", + :page-nos ["21" "71"]}, + :LENGTH + {:fn-name "LENGTH", + :call-type "SUBR", + :implementation "", + :page-nos ["62"]}, + :MINUS + {:fn-name "MINUS", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "63"]}, + :COND + {:fn-name "COND", + :call-type "FSUBR", + :implementation "", + :page-nos ["18"]}, + :APPEND + {:fn-name "APPEND", + :call-type "SUBR", + :implementation "", + :page-nos ["11" "61"]}, + :CDR + {:fn-name "CDR", + :call-type "SUBR", + :implementation "", + :page-nos ["3" "56"]}, + :OBLIST + {:fn-name "OBLIST", + :call-type "APVAL", + :implementation "", + :page-nos ["69"]}, + :READ + {:fn-name "READ", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["5" "84"]}, + :ERRORSET + {:fn-name "ERRORSET", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["35" "66"]}, + :UNCOMMON + {:fn-name "UNCOMMON", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["64" "78"]}, + :EVAL + {:fn-name "EVAL", + :call-type "SUBR", + :implementation "", + :page-nos ["71"]}, + :MIN + {:fn-name "MIN", + :call-type "FSUBR", + :implementation "", + :page-nos ["26" "64"]}, + :PAIR + {:fn-name "PAIR", + :call-type "SUBR", + :implementation "", + :page-nos ["60"]}, + :BLANK + {:fn-name "BLANK", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :SETQ + {:fn-name "SETQ", + :call-type "FSUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["30" "71"]}, + :GET + {:fn-name "GET", + :call-type "SUBR", + :implementation "", + :page-nos ["41" "59"]}, + :PRINT + {:fn-name "PRINT", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["65" "84"]}, + :ENDREAD + {:fn-name "ENDREAD", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["8 8"]}, + :RETURN + {:fn-name "RETURN", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["30" "72"]}, + :LITER + {:fn-name "LITER", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos ["87"]}, + :EOF + {:fn-name "EOF", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "88"]}, + :TRACE + {:fn-name "TRACE", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["32" "66" "79"]}, + :TRACESET + {:fn-name "TRACESET", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION LIBRARY", + :page-nos ["68"]}, + :PACK + {:fn-name "PACK", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["86"]}, + :NULL + {:fn-name "NULL", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos ["11" "57"]}, + :CLEARBUFF + {:fn-name "CLEARBUFF", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["86"]}, + :LESSP + {:fn-name "LESSP", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos ["26" "64"]}, + :TERPRI + {:fn-name "TERPRI", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["65" "84"]}, + :ONEP + {:fn-name "ONEP", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos [" 26" "64"]}, + :EXCISE + {:fn-name "EXCISE", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["67" "77"]}, + :REMOB + {:fn-name "REMOB", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["67"]}, + :MAP + {:fn-name "MAP", + :call-type "SUBR", + :implementation "FUNCTIONAL ", + :page-nos ["63"]}, + :COMPILE + {:fn-name "COMPILE", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["64" "76"]}, + :ADD1 + {:fn-name "ADD1", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "64"]}, + :ADVANCE + {:fn-name "ADVANCE", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["88"]}, + :SEARCH + {:fn-name "SEARCH", + :call-type "SUBR", + :implementation "FUNCTIONAL", + :page-nos ["63"]}, + :APPLY + {:fn-name "APPLY", + :call-type "SUBR", + :implementation "", + :page-nos ["70"]}, + :READLAP + {:fn-name "READLAP", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["65" "76"]}, + :UNSPECIAL + {:fn-name "UNSPECIAL", + :call-type "SUBR", + :implementation "", + :page-nos ["64" "78"]}, + :SUBST + {:fn-name "SUBST", + :call-type "SUBR", + :implementation "", + :page-nos ["11" "61"]}, + :COPY + {:fn-name "COPY", + :call-type "SUBR", + :implementation "", + :page-nos ["62"]}, + :LOGOR + {:fn-name "LOGOR", + :call-type "FSUBR", + :implementation "", + :page-nos ["26" "64"]}, + :LABEL + {:fn-name "LABEL", + :call-type "FSUBR", + :implementation "", + :page-nos ["8" "18" "70"]}, + :FIXP + {:fn-name "FIXP", + :call-type "SUBR", + :implementation "PREDICATE", + :page-nos ["26" "64"]}, + :SUB1 + {:fn-name "SUB1", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "64"]}, + :ATTRIB + {:fn-name "ATTRIB", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["59"]}, + :DIFFERENCE + {:fn-name "DIFFERENCE", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "64"]}, + :REMAINDER + {:fn-name "REMAINDER", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "64"]}, + :REVERSE + {:fn-name "REVERSE", + :call-type "SUBR", + :implementation "", + :page-nos ["6 2"]}, + :EOR + {:fn-name "EOR", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "88"]}, + :PLUSS + {:fn-name "PLUSS", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :TEMPUS-FUGIT + {:fn-name "TEMPUS-FUGIT", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["67"]}, + :LOAD + {:fn-name "LOAD", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["67"]}, + :CHARCOUNT + {:fn-name "CHARCOUNT", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "87"]}, + :RPAR + {:fn-name "RPAR", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :COUNT + {:fn-name "COUNT", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["34" "66"]}, + :SPEAK + {:fn-name "SPEAK", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["34" "66 "]}, + :LOGXOR + {:fn-name "LOGXOR", + :call-type "FSUBR", + :implementation "", + :page-nos ["27" "64"]}, + :FLOATP + {:fn-name "FLOATP", + :call-type "SUBR", + :implementation "PREDICATE", + :page-nos ["26" "64"]}, + :ATOM + {:fn-name "ATOM", + :call-type "SUBR", + :implementation "PREDICATE", + :page-nos ["3" "57"]}, + :EQSIGN + {:fn-name "EQSIGN", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :LIST + {:fn-name "LIST", + :call-type "FSUBR", + :implementation "", + :page-nos ["57"]}, + :MAPLIST + {:fn-name "MAPLIST", + :call-type "SUBR", + :implementation "FUNCTIONAL ", + :page-nos ["20" "21" "63"]}, + :LOGAND + {:fn-name "LOGAND", + :call-type "FSUBR", + :implementation "", + :page-nos ["27" "64"]}, + :FLAG + {:fn-name "FLAG", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["41" "60"]}, + :MAPCON + {:fn-name "MAPCON", + :call-type "SUBR", + :implementation "FUNCTIONAL PSEUDO- FUNCTION", + :page-nos ["63"]}, + :STAR + {:fn-name "STAR", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :CURCHAR + {:fn-name "CURCHAR", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "87"]}, + :DUMP + {:fn-name "DUMP", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["67"]}, + :DEFLIST + {:fn-name "DEFLIST", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["41" "58"]}, + :LEFTSHIFT + {:fn-name "LEFTSHIFT", + :call-type "SUBR", + :implementation "", + :page-nos ["27" "64"]}, + :ZEROP + {:fn-name "ZEROP", + :call-type "SUBR", + :implementation "PREDICATE", + :page-nos ["26" "64"]}}) + +(defn page-url + "Format the URL for the page in the manual with this `page-no`." + [page-no] + (let [n (read-string page-no) + n' (when (and (number? n) + (ends-with? *manual-url* ".pdf")) + ;; annoyingly, the manual has eight pages of front-matter + ;; before numbering starts. + (+ n 8))] + (format + (if (ends-with? *manual-url* ".pdf") "%s#page=%s" "%s#page%s") + *manual-url* + (or n' (trim (str page-no)))))) + +(defn format-page-references + "Format page references from the manual index for the function whose name + is `fn-symbol`." + [fn-symbol] + (let [k (if (keyword? fn-symbol) fn-symbol (keyword fn-symbol))] + (join ", " + (doall + (map + (fn [n] + (let [p (trim n)] + (format "%s" + (page-url p) p))) + (:page-nos (index k))))))) \ No newline at end of file diff --git a/src/beowulf/oblist.clj b/src/beowulf/oblist.clj index 5c36256..38aa999 100644 --- a/src/beowulf/oblist.clj +++ b/src/beowulf/oblist.clj @@ -1,12 +1,38 @@ (ns beowulf.oblist - "A namespace mainly devoted to the object list. + "A namespace mainly devoted to the object list and other top level + global variables. - Yes, this makes little sense, but if you put it anywhere else you end + Yes, this makes little sense, but if you put them anywhere else you end up in cyclic dependency hell." ) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Copyright (C) 2022-2023 Simon Brooke +;;; +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License +;;; as published by the Free Software Foundation; either version 2 +;;; of the License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (def NIL - "The canonical empty list symbol." + "The canonical empty list symbol. + + TODO: this doesn't really work, because (from Clojure) `(empty? NIL)` throws + an exception. It might be better to subclass beowulf.cons_cell.ConsCell to create + a new singleton class Nil which overrides the `empty` method of + IPersistentCollection?" 'NIL) (def oblist diff --git a/src/beowulf/read.clj b/src/beowulf/read.clj index 032c23b..54fcfe4 100644 --- a/src/beowulf/read.clj +++ b/src/beowulf/read.clj @@ -13,10 +13,10 @@ Both these extensions can be disabled by using the `--strict` command line switch." - (:require [beowulf.reader.char-reader :refer [read-chars]] + (:require ;; [beowulf.reader.char-reader :refer [read-chars]] [beowulf.reader.generate :refer [generate]] [beowulf.reader.parser :refer [parse]] - [beowulf.reader.simplify :refer [remove-optional-space simplify]] + [beowulf.reader.simplify :refer [simplify]] [clojure.string :refer [join split starts-with? trim]]) (:import [java.io InputStream] [instaparse.gll Failure])) @@ -28,6 +28,24 @@ ;;; the real Lisp reader. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Copyright (C) 2022-2023 Simon Brooke +;;; +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License +;;; as published by the Free Software Foundation; either version 2 +;;; of the License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn strip-line-comments "Strip blank lines and comment lines from this string `s`, expected to @@ -61,16 +79,18 @@ parse-tree (parse source)] (if (instance? Failure parse-tree) (doall (println (number-lines source parse-tree)) - (throw (ex-info "Parse failed" (assoc parse-tree :source source)))) - (generate (simplify (remove-optional-space parse-tree)))))) + (throw (ex-info "Ne can forstande " (assoc parse-tree :source source)))) + (generate (simplify parse-tree))))) (defn read-from-console "Attempt to read a complete lisp expression from the console. NOTE that this will only really work for S-Expressions, not M-Expressions." [] (loop [r (read-line)] - (if (= (count (re-seq #"\(" r)) + (if (and (= (count (re-seq #"\(" r)) (count (re-seq #"\)" r))) + (= (count (re-seq #"\[" r)) + (count (re-seq #"\]" r)))) r (recur (str r "\n" (read-line)))))) @@ -79,7 +99,7 @@ the final Lisp reader. `input` should be either a string representation of a LISP expression, or else an input stream. A single form will be read." ([] - (gsp (read-chars))) + (gsp (read-from-console))) ([input] (cond (empty? input) (READ) diff --git a/src/beowulf/reader/char_reader.clj b/src/beowulf/reader/char_reader.clj index 0d6ac3e..883f8fa 100644 --- a/src/beowulf/reader/char_reader.clj +++ b/src/beowulf/reader/char_reader.clj @@ -15,36 +15,61 @@ rather than the strings which were supplied to `READ`); 4. offers potential auto-completions taken from the value of `(OBLIST)`, ideally the current value, not the value at the time the session started; - 5. and offer movement and editing within the line." - (:import [org.jline.reader LineReader LineReaderBuilder] - [org.jline.terminal TerminalBuilder])) + 5. and offer movement and editing within the line. + + TODO: There are multiple problems with JLine; a better solution might be + to start from here: + https://stackoverflow.com/questions/7931988/how-to-manipulate-control-characters" + ;; (:import [org.jline.reader LineReader LineReaderBuilder] + ;; [org.jline.terminal TerminalBuilder]) + ) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Copyright (C) 2022-2023 Simon Brooke +;;; +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License +;;; as published by the Free Software Foundation; either version 2 +;;; of the License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; It looks from the example given [here](https://github.com/jline/jline3/blob/master/demo/src/main/java/org/jline/demo/Repl.java) ;; as though JLine could be used to build a perfect line-reader for Beowulf; but it also ;; looks as though you'd need a DPhil in JLine to write it, and I don't have ;; the time. -(def get-reader - "Return a reader, first constructing it if necessary. +;; (def get-reader +;; "Return a reader, first constructing it if necessary. - **NOTE THAT** this is not settled API. The existence and call signature of - this function is not guaranteed in future versions." - (memoize (fn [] - (let [term (.build (.system (TerminalBuilder/builder) true))] - (.build (.terminal (LineReaderBuilder/builder) term)))))) +;; **NOTE THAT** this is not settled API. The existence and call signature of +;; this function is not guaranteed in future versions." +;; (memoize (fn [] +;; (let [term (.build (.system (TerminalBuilder/builder) true))] +;; (.build (.terminal (LineReaderBuilder/builder) term)))))) -(defn read-chars - "A drop-in replacement for `clojure.core/read-line`, except that line editing - and history should be enabled. +;; (defn read-chars +;; "A drop-in replacement for `clojure.core/read-line`, except that line editing +;; and history should be enabled. - **NOTE THAT** this does not work yet, but it is in the API because I hope - that it will work later!" - [] - (let [eddie (get-reader)] - (loop [s (.readLine eddie)] - (if (and (= (count (re-seq #"\(" s)) - (count (re-seq #"\)" s))) - (= (count (re-seq #"\[]" s)) - (count (re-seq #"\]" s)))) - s - (recur (str s " " (.readLine eddie))))))) \ No newline at end of file +;; **NOTE THAT** this does not work yet, but it is in the API because I hope +;; that it will work later!" +;; [] +;; (let [eddie (get-reader)] +;; (loop [s (.readLine eddie)] +;; (if (and (= (count (re-seq #"\(" s)) +;; (count (re-seq #"\)" s))) +;; (= (count (re-seq #"\[]" s)) +;; (count (re-seq #"\]" s)))) +;; s +;; (recur (str s " " (.readLine eddie))))))) \ No newline at end of file diff --git a/src/beowulf/reader/generate.clj b/src/beowulf/reader/generate.clj index 3037f9c..8d4edcc 100644 --- a/src/beowulf/reader/generate.clj +++ b/src/beowulf/reader/generate.clj @@ -59,45 +59,65 @@ [beowulf.reader.macros :refer [expand-macros]] [beowulf.oblist :refer [NIL]] [clojure.math.numeric-tower :refer [expt]] - [clojure.string :refer [upper-case]])) + [clojure.string :refer [upper-case]] + [clojure.tools.trace :refer [deftrace]])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Copyright (C) 2022-2023 Simon Brooke +;;; +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License +;;; as published by the Free Software Foundation; either version 2 +;;; of the License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (declare generate) (defn gen-cond-clause "Generate a cond clause from this simplified parse tree fragment `p`; returns `nil` if `p` does not represent a cond clause." - [p] + [p context] (when (and (coll? p) (= :cond-clause (first p))) (make-beowulf-list (list (if (= (nth p 1) [:quoted-expr [:atom "T"]]) 'T - (generate (nth p 1))) - (generate (nth p 2)))))) + (generate (nth p 1) context)) + (generate (nth p 2) context))))) (defn gen-cond "Generate a cond statement from this simplified parse tree fragment `p`; returns `nil` if `p` does not represent a (MEXPR) cond statement." - [p] + [p context] (when (and (coll? p) (= :cond (first p))) (make-beowulf-list (cons 'COND (map - generate + #(generate % (if (= context :mexpr) :cond-mexpr context)) (rest p)))))) (defn gen-fn-call "Generate a function call from this simplified parse tree fragment `p`; returns `nil` if `p` does not represent a (MEXPR) function call." - [p] + [p context] (when (and (coll? p) (= :fncall (first p)) (= :mvar (first (second p)))) (make-cons-cell - (generate (second p)) - (generate (nth p 2))))) + (generate (second p) context) + (generate (nth p 2) context)))) (defn gen-dot-terminated-list @@ -118,38 +138,50 @@ (generate (first p)) (gen-dot-terminated-list (rest p))))) +;; null[x] = [x = NIL -> T; T -> F] +;; [:defn +;; [:mexpr [:fncall [:mvar "null"] [:bindings [:args [:mexpr [:mvar "x"]]]]]] +;; "=" +;; [:mexpr [:cond +;; [:cond-clause [:mexpr [:iexpr [:lhs [:mexpr [:mvar "x"]]] [:iop "="] [:rhs [:mexpr [:mconst "NIL"]]]]] [:mexpr [:mconst "T"]]] +;; [:cond-clause [:mexpr [:mconst "T"]] [:mexpr [:mconst "F"]]]]]] + (defn generate-defn - [tree] - (make-beowulf-list - (list 'SET - (list 'QUOTE (generate (-> tree second second))) - (list 'QUOTE - (cons 'LAMBDA - (cons (generate (nth (second tree) 2)) - (map generate (-> tree rest rest rest)))))))) + [tree context] + (if (= :mexpr (first tree)) + (generate-defn (second tree) context) + (make-beowulf-list + (list 'PUT + (list 'QUOTE (generate (-> tree second second second) context)) + (list 'QUOTE 'EXPR) + (list 'QUOTE + (cons 'LAMBDA + (list (generate (nth (-> tree second second) 2) context) + (generate (nth tree 3) context)))))))) (defn gen-iexpr - [tree] - (let [bundle (reduce #(assoc %1 (first %2) %2) - {} + [tree context] + (let [bundle (reduce #(assoc %1 (first %2) %2) + {} (rest tree))] - (list (generate (:iop bundle)) - (generate (:lhs bundle)) - (generate (:rhs bundle))))) + (list (generate (:iop bundle) context) + (generate (:lhs bundle) context) + (generate (:rhs bundle) context)))) (defn generate-set "Actually not sure what the mexpr representation of set looks like" - [tree] + [tree context] (throw (ex-info "Not Yet Implemented" {:feature "generate-set"}))) (defn generate-assign "Generate an assignment statement based on this `tree`. If the thing being assigned to is a function signature, then we have to do something different to if it's an atom." - [tree] + [tree context] (case (first (second tree)) - :fncall (generate-defn tree) - (:mvar :atom) (generate-set tree))) + :fncall (generate-defn tree context) + :mexpr (map #(generate % context) (rest (second tree))) + (:mvar :atom) (generate-set tree context))) (defn strip-leading-zeros "`read-string` interprets strings with leading zeros as octal; strip @@ -168,68 +200,77 @@ (defn generate "Generate lisp structure from this parse tree `p`. It is assumed that `p` has been simplified." - [p] - (try - (expand-macros - (if - (coll? p) - (case (first p) - :λ "LAMBDA" - :λexpr (make-cons-cell - (generate (nth p 1)) - (make-cons-cell (generate (nth p 2)) - (generate (nth p 3)))) - :args (make-beowulf-list (map generate (rest p))) - :atom (symbol (second p)) - :bindings (generate (second p)) - :body (make-beowulf-list (map generate (rest p))) - (:coefficient :exponent) (generate (second p)) - :cond (gen-cond p) - :cond-clause (gen-cond-clause p) - :decimal (read-string (apply str (map second (rest p)))) - :defn (generate-assign p) - :dotted-pair (make-cons-cell - (generate (nth p 1)) - (generate (nth p 2))) - :fncall (gen-fn-call p) - :iexpr (gen-iexpr p) - :integer (read-string (strip-leading-zeros (second p))) - :iop (case (second p) - "/" 'DIFFERENCE - "=" 'EQUAL - ">" 'GREATERP - "<" 'LESSP - "+" 'PLUS - "*" 'TIMES + ([p] + (generate p :expr)) + ([p context] + (try + (expand-macros + (if + (coll? p) + (case (first p) + :λ "LAMBDA" + :λexpr (make-cons-cell + (generate (nth p 1) context) + (make-cons-cell (generate (nth p 2) context) + (generate (nth p 3) context))) + :args (make-beowulf-list (map #(generate % context) (rest p))) + :atom (symbol (second p)) + :bindings (generate (second p) context) + :body (make-beowulf-list (map #(generate % context) (rest p))) + (:coefficient :exponent) (generate (second p) context) + :cond (gen-cond p (if (= context :mexpr) :cond-mexpr context)) + :cond-clause (gen-cond-clause p context) + :decimal (read-string (apply str (map second (rest p)))) + :defn (generate-defn p context) + :dotted-pair (make-cons-cell + (generate (nth p 1) context) + (generate (nth p 2) context)) + :fncall (gen-fn-call p context) + :iexpr (gen-iexpr p context) + :integer (read-string (strip-leading-zeros (second p))) + :iop (case (second p) + "/" 'DIFFERENCE + "=" 'EQUAL + ">" 'GREATERP + "<" 'LESSP + "+" 'PLUS + "*" 'TIMES ;; else - (throw (ex-info "Unrecognised infix operator symbol" - {:phase :generate - :fragment p}))) - :list (gen-dot-terminated-list (rest p)) - (:lhs :rhs) (generate (second p)) - :mexpr (generate (second p)) - :mconst (make-beowulf-list - (list 'QUOTE (symbol (upper-case (second p))))) - :mvar (symbol (upper-case (second p))) - :number (generate (second p)) - :octal (let [n (read-string (strip-leading-zeros (second p) "0")) - scale (generate (nth p 3))] - (* n (expt 8 scale))) + (throw (ex-info "Unrecognised infix operator symbol" + {:phase :generate + :fragment p}))) + :list (gen-dot-terminated-list (rest p)) + (:lhs :rhs) (generate (second p) context) + :mexpr (generate (second p) (if (= context :cond-mexpr) context :mexpr)) + :mconst (if (= context :cond-mexpr) + (case (second p) + ("T" "F" "NIL") (symbol (second p)) + ;; else + (list 'QUOTE (symbol (second p)))) + ;; else + (list 'QUOTE (symbol (second p)))) + :mvar (symbol (upper-case (second p))) + :number (generate (second p) context) + :octal (let [n (read-string (strip-leading-zeros (second p) "0")) + scale (generate (nth p 3) context)] + (* n (expt 8 scale))) ;; the quote read macro (which probably didn't exist in Lisp 1.5, but...) - :quoted-expr (make-beowulf-list (list 'QUOTE (generate (second p)))) - :scale-factor (if - (empty? (second p)) 0 - (read-string (strip-leading-zeros (second p)))) - :scientific (let [n (generate (second p)) - exponent (generate (nth p 3))] - (* n (expt 10 exponent))) + :quoted-expr (make-beowulf-list (list 'QUOTE (generate (second p) context))) + :scale-factor (if + (empty? (second p)) 0 + (read-string (strip-leading-zeros (second p)))) + :scientific (let [n (generate (second p) context) + exponent (generate (nth p 3) context)] + (* n (expt 10 exponent))) + :sexpr (generate (second p) :sexpr) + :subr (symbol (second p)) ;; default - (throw (ex-info (str "Unrecognised head: " (first p)) - {:generating p}))) - p)) - (catch Throwable any - (throw (ex-info "Could not generate" - {:generating p} - any))))) + (throw (ex-info (str "Unrecognised head: " (first p)) + {:generating p}))) + p)) + (catch Throwable any + (throw (ex-info "Could not generate" + {:generating p} + any)))))) diff --git a/src/beowulf/reader/macros.clj b/src/beowulf/reader/macros.clj index f8c652c..ad1dd63 100644 --- a/src/beowulf/reader/macros.clj +++ b/src/beowulf/reader/macros.clj @@ -1,22 +1,53 @@ (ns beowulf.reader.macros - "Can I implement reader macros? let's see!" - (:require [beowulf.cons-cell :refer [CONS LIST make-beowulf-list]] - [clojure.string :refer [join]]) - (:import [beowulf.cons_cell ConsCell])) + "Can I implement reader macros? let's see! + + We don't need (at least, in the Clojure reader) to rewrite forms like + `'FOO`, because that's handled by the parser. But we do need to rewrite + things which don't evaluate their arguments, like `SETQ`, because (unless + LABEL does it, which I'm not yet sure of) we're not yet able to implement + things which don't evaluate arguments. -;; We don't need (at least, in the Clojure reader) to rewrite forms like -;; "'FOO", because that's handled by the parser. But we do need to rewrite -;; things which don't evaluate their arguments, like `SETQ`, because (unless -;; LABEL does it, which I'm not yet sure of) we're not yet able to implement -;; things which don't evaluate arguments. + TODO: at this stage, the following should probably also be read macros: + DEFINE" + (:require [beowulf.cons-cell :refer [make-beowulf-list]] + [beowulf.host :refer [CONS LIST]] + [clojure.string :refer [join]])) -;; TODO: at this stage, the following should probably also be read macros: -;; DEFINE +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; We don't need (at least, in the Clojure reader) to rewrite forms like +;;; "'FOO", because that's handled by the parser. But we do need to rewrite +;;; things which don't evaluate their arguments, like `SETQ`, because (unless +;;; LABEL does it, which I'm not yet sure of) we're not yet able to implement +;;; things which don't evaluate arguments. +;;; +;;; TODO: at this stage, the following should probably also be read macros: +;;; DEFINE +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Copyright (C) 2022-2023 Simon Brooke +;;; +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License +;;; as published by the Free Software Foundation; either version 2 +;;; of the License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:dynamic *readmacros* {:car {'DEFUN (fn [f] (LIST 'SET (LIST 'QUOTE (second f)) - (CONS 'LAMBDA (rest (rest f))))) + (LIST 'QUOTE (CONS 'LAMBDA (rest (rest f)))))) 'SETQ (fn [f] (LIST 'SET (LIST 'QUOTE (second f)) (nth f 2)))}}) (defn expand-macros diff --git a/src/beowulf/reader/parser.clj b/src/beowulf/reader/parser.clj index 51783c1..0fd7abe 100644 --- a/src/beowulf/reader/parser.clj +++ b/src/beowulf/reader/parser.clj @@ -2,6 +2,26 @@ "The actual parser, supporting both S-expression and M-expression syntax." (:require [instaparse.core :as i])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Copyright (C) 2022-2023 Simon Brooke +;;; +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License +;;; as published by the Free Software Foundation; either version 2 +;;; of the License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (def parse "Parse a string presented as argument into a parse tree which can then be operated upon further." @@ -9,39 +29,46 @@ (str ;; we tolerate whitespace and comments around legitimate input "raw := expr | opt-comment expr opt-comment;" - ;; top level: we accept mexprs as well as sexprs. + ;; top level: we accept mexprs as well as sexprs. "expr := mexpr | sexpr ;" - ;; comments. I'm pretty confident Lisp 1.5 did NOT have these. + ;; comments. I'm pretty confident Lisp 1.5 did NOT have these. "comment := opt-space <';;'> opt-space #'[^\\n\\r]*';" - ;; there's a notation comprising a left brace followed by mexprs - ;; followed by a right brace which doesn't seem to be documented - ;; but I think must represent a prog(?) + ;; there's a notation comprising a left brace followed by mexprs + ;; followed by a right brace which doesn't seem to be documented + ;; but I think must represent assembly code(?) - ;; "prog := lbrace exprs rbrace;" - ;; mexprs. I'm pretty clear that Lisp 1.5 could never read these, - ;; but it's a convenience. + ;; "assembly := lbrace exprs rbrace;" + + ;; mexprs. I'm pretty clear that Lisp 1.5 could never read these, + ;; but it's a convenience. + + ;; TODO: this works for now but in fact the Programmer's Manual + ;; gives a much simpler formulation of M-expression grammar on + ;; page 9, and of the S-expression grammar on page 8. It would + ;; be worth going back and redoing this from the book. "exprs := expr | exprs;" "mexpr := λexpr | fncall | defn | cond | mvar | mconst | iexpr | number | mexpr comment; - λexpr := λ lsqb bindings semi-colon body rsqb; - λ := 'λ'; + λexpr := λ lsqb bindings semi-colon opt-space body opt-space rsqb; + λ := 'λ' | 'lambda'; bindings := lsqb args rsqb | lsqb rsqb; - body := (mexpr semi-colon opt-space)* mexpr; + body := (opt-space mexpr semi-colon)* opt-space mexpr; fncall := fn-name bindings; lsqb := '['; rsqb := ']'; - lbrace := '{'; - rbrace := '}'; + lbrace := '{'; + rbrace := '}'; defn := mexpr opt-space '=' opt-space mexpr; cond := lsqb (opt-space cond-clause semi-colon opt-space)* cond-clause rsqb; cond-clause := mexpr opt-space arrow opt-space mexpr opt-space; arrow := '->'; - args := mexpr | (opt-space mexpr semi-colon opt-space)* opt-space mexpr opt-space; + args := arg | (opt-space arg semi-colon opt-space)* opt-space arg opt-space; + arg := mexpr; fn-name := mvar; - mvar := #'[a-z]+'; - mconst := #'[A-Z]+'; + mvar := #'[a-z][a-z0-9]*'; + mconst := #'[A-Z][A-Z0-9]*'; semi-colon := ';';" ;; Infix operators appear in mexprs, e.g. on page 7. Ooops! @@ -52,13 +79,13 @@ iexp := mexpr | number | opt-space iexp opt-space; iop := '>' | '<' | '+' | '-' | '*' '/' | '=' ;" - ;; comments. I'm pretty confident Lisp 1.5 did NOT have these. + ;; comments. I'm pretty confident Lisp 1.5 did NOT have these. "opt-comment := opt-space | comment;" "comment := opt-space <';;'> #'[^\\n\\r]*' opt-space;" - ;; sexprs. Note it's not clear to me whether Lisp 1.5 had the quote macro, - ;; but I've included it on the basis that it can do little harm. - "sexpr := quoted-expr | atom | number | dotted-pair | list | sexpr comment; + ;; sexprs. Note it's not clear to me whether Lisp 1.5 had the quote macro, + ;; but I've included it on the basis that it can do little harm. + "sexpr := quoted-expr | atom | number | subr | dotted-pair | list | sexpr comment; list := lpar sexpr rpar | lpar (sexpr sep)* rpar | lpar (sexpr sep)* dot-terminal | lbrace exprs rbrace; list := lpar opt-space sexpr rpar | lpar opt-space (sexpr sep)* rpar | lpar opt-space (sexpr sep)* dot-terminal; dotted-pair := lpar dot-terminal ; @@ -73,7 +100,13 @@ sep := ',' | opt-space; atom := #'[A-Z][A-Z0-9]*';" - ;; Lisp 1.5 supported octal as well as decimal and scientific notation + ;; we need a way of representing Clojure functions on the object list; + ;; subr objects aren't expected to be normally entered on the REPL, but + ;; must be on the object list or functions to which functions are passed + ;; won't be able to access them. + "subr := #'[a-z][a-z.]*/[A-Za-z][A-Za-z0-9]*';" + + ;; Lisp 1.5 supported octal as well as decimal and scientific notation "number := integer | decimal | scientific | octal; integer := #'-?[0-9]+'; decimal := integer dot integer; diff --git a/src/beowulf/reader/simplify.clj b/src/beowulf/reader/simplify.clj index 7e75082..a8057a0 100644 --- a/src/beowulf/reader/simplify.clj +++ b/src/beowulf/reader/simplify.clj @@ -5,7 +5,27 @@ [instaparse.failure :as f]) (:import [instaparse.gll Failure])) -(declare simplify) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Copyright (C) 2022-2023 Simon Brooke +;;; +;;; This program is free software; you can redistribute it and/or +;;; modify it under the terms of the GNU General Public License +;;; as published by the Free Software Foundation; either version 2 +;;; of the License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(declare simplify-tree) (defn remove-optional-space [tree] @@ -28,17 +48,17 @@ (loop [r tree'] (if (and r (vector? r) (keyword? (first r))) (if (= (first r) key) - (recur (simplify (second r) context)) + (recur (simplify-tree (second r) context)) r) r)) tree'))) -(defn simplify +(defn simplify-tree "Simplify this parse tree `p`. If `p` is an instaparse failure object, throw an `ex-info`, with `p` as the value of its `:failure` key. **NOTE THAT** it is assumed that `remove-optional-space` has been run on the - parse tree **BEFORE** it is passed to `simplify`." + parse tree **BEFORE** it is passed to `simplify-tree`." ([p] (if (instance? Failure p) @@ -47,7 +67,7 @@ {:cause :parse-failure :phase :simplify :failure p})) - (simplify p :expr))) + (simplify-tree p :expr))) ([p context] (cond (string? p) p @@ -58,8 +78,8 @@ (case (first p) (:λexpr :args :bindings :body :cond :cond-clause :defn :dot-terminal - :fncall :lhs :quoted-expr :rhs ) (map #(simplify % context) p) - (:arg :expr :coefficient :fn-name :number) (simplify (second p) context) + :fncall :lhs :quoted-expr :rhs ) (map #(simplify-tree % context) p) + (:arg :expr :coefficient :fn-name :number) (simplify-tree (second p) context) (:arrow :dot :e :lpar :lsqb :opt-comment :opt-space :q :quote :rpar :rsqb :semi-colon :sep :space) nil :atom (if @@ -77,28 +97,35 @@ [:fncall [:mvar "cons"] [:args - (simplify (nth p 1) context) - (simplify (nth p 2) context)]] - (map #(simplify % context) p)) - :iexp (simplify (second p) context) + (simplify-tree (nth p 1) context) + (simplify-tree (nth p 2) context)]] + (map #(simplify-tree % context) p)) + :iexp (simplify-tree (second p) context) :iexpr [:iexpr - [:lhs (simplify (second p) context)] - (simplify (nth p 2) context) ;; really should be the operator - [:rhs (simplify (nth p 3) context)]] + [:lhs (simplify-tree (second p) context)] + (simplify-tree (nth p 2) context) ;; really should be the operator + [:rhs (simplify-tree (nth p 3) context)]] :mexpr (if (:strict *options*) (throw (ex-info "Cannot parse meta expressions in strict mode" {:cause :strict})) - (simplify (second p) :mexpr)) + [:mexpr (simplify-tree (second p) :mexpr)]) :list (if (= context :mexpr) [:fncall [:mvar "list"] - [:args (apply vector (map simplify (rest p)))]] - (map #(simplify % context) p)) - :raw (first (remove empty? (map simplify (rest p)))) - :sexpr (simplify (second p) :sexpr) + [:args (apply vector (map simplify-tree (rest p)))]] + (map #(simplify-tree % context) p)) + :raw (first (remove empty? (map simplify-tree (rest p)))) + :sexpr [:sexpr (simplify-tree (second p) :sexpr)] ;;default p))) :else p))) + +(defn simplify + "Simplify this parse tree `p`. If `p` is an instaparse failure object, throw + an `ex-info`, with `p` as the value of its `:failure` key. Calls + `remove-optional-space` before processing." + [p] + (simplify-tree (remove-optional-space p))) \ No newline at end of file diff --git a/src/beowulf/trace.clj b/src/beowulf/trace.clj deleted file mode 100644 index c3807e2..0000000 --- a/src/beowulf/trace.clj +++ /dev/null @@ -1,24 +0,0 @@ -(ns beowulf.trace - "Tracing of function execution") - -(def traced-symbols - "Symbols currently being traced." - (atom #{})) - -(defn traced? - "Return `true` iff `s` is a symbol currently being traced, else `nil`." - [s] - (try (contains? @traced-symbols s) - (catch Throwable _))) - -(defn TRACE - "Add this symbol `s` to the set of symbols currently being traced. If `s` - is not a symbol, does nothing." - [s] - (when (symbol? s) - (swap! traced-symbols #(conj % s)))) - -(defn UNTRACE - [s] - (when (symbol? s) - (swap! traced-symbols #(set (remove (fn [x] (= s x)) %))))) \ No newline at end of file diff --git a/test/beowulf/bootstrap_test.clj b/test/beowulf/bootstrap_test.clj index e7a4d56..f3233af 100644 --- a/test/beowulf/bootstrap_test.clj +++ b/test/beowulf/bootstrap_test.clj @@ -1,20 +1,27 @@ (ns beowulf.bootstrap-test - (:require [clojure.test :refer [deftest testing is]] - [beowulf.cons-cell :refer [CAR CDR make-cons-cell T F]] - [beowulf.bootstrap :refer [APPEND ASSOC ATOM ATOM? CAAAAR CADR - CADDR CADDDR EQ EQUAL MEMBER - PAIRLIS SUBLIS SUBST]] + (:require [beowulf.bootstrap :refer [EVAL]] + [beowulf.cons-cell :refer [F make-cons-cell T]] + [beowulf.host :refer [ASSOC ATOM ATOM? CAAAAR CADDDR CADDR CADR + CAR CDR EQ EQUAL PAIRLIS]] [beowulf.oblist :refer [NIL]] - [beowulf.read :refer [gsp]])) + [beowulf.read :refer [gsp READ]] + [clojure.test :refer [deftest is testing]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; -;;; This file is primarily tests of the functions in `beowulf.eval` - which +;;; This file is primarily tests of the functions in `beowulf.bootstrap` - which ;;; are Clojure functions, but aim to provide sufficient functionality that ;;; Beowulf can get up to the level of running its own code. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn- reps + "'Read eval print string', or 'read eval print single'. + Reads and evaluates one input string, and returns the + output string." + [input] + (with-out-str (print (EVAL (READ input))))) + (deftest atom-tests (testing "ATOM" (let [expected T @@ -63,12 +70,12 @@ (is (= actual expected) "A is CAR of (A B C D)")) (is (thrown-with-msg? Exception - #"Cannot take CAR of `.*" + #"Ne can tace CAR of `.*" (CAR 'T)) "Can't take the CAR of an atom") (is (thrown-with-msg? Exception - #"Cannot take CAR of `.*" + #"Ne can tace CAR of `.*" (CAR 7)) "Can't take the CAR of a number")) (testing "CDR" @@ -82,12 +89,12 @@ (is (= (CAR actual) expected) "the CAR of that cons-cell is B")) (is (thrown-with-msg? Exception - #"Cannot take CDR of `.*" + #"Ne can tace CDR of `.*" (CDR 'T)) "Can't take the CDR of an atom") (is (thrown-with-msg? Exception - #"Cannot take CDR of `.*" + #"Ne can tace CDR of `.*" (CDR 7)) "Can't take the CDR of a number")) (let [s (gsp "((((1 . 2) 3)(4 5) 6)(7 (8 9) (10 11 12) 13) 14 (15 16) 17)")] @@ -153,55 +160,18 @@ actual (EQUAL l m)] (is (= actual expected) "different lists, different content")))) -(deftest substitution-tests - (testing "subst" - (let [expected "((A X . A) . C)" - ;; differs from example in book only because of how the function - ;; `beowulf.cons-cell/to-string` formats lists. - actual (print-str - (SUBST - (gsp "(X . A)") - (gsp "B") - (gsp "((A . B) . C)")))] - (is (= actual expected))))) - -(deftest append-tests - (testing "append" - (let [expected "(A B C . D)" - actual (print-str - (APPEND - (gsp "(A B)") - (gsp "(C . D)")))] - (is (= actual expected))) - (let [expected "(A B C D E)" - actual (print-str - (APPEND - (gsp "(A B)") - (gsp "(C D E)")))] - (is (= actual expected))))) - -(deftest member-tests - (testing "member" - (let [expected 'T - actual (MEMBER - (gsp "ALBERT") - (gsp "(ALBERT BELINDA CHARLIE DORIS ELFREDA FRED)"))] - (is (= actual expected))) - (let [expected 'T - actual (MEMBER - (gsp "BELINDA") - (gsp "(ALBERT BELINDA CHARLIE DORIS ELFREDA FRED)"))] - (is (= actual expected))) - (let [expected 'T - actual (MEMBER - (gsp "ELFREDA") - (gsp "(ALBERT BELINDA CHARLIE DORIS ELFREDA FRED)"))] - (is (= actual expected))) - (let [expected 'F - actual (MEMBER - (gsp "BERTRAM") - (gsp "(ALBERT BELINDA CHARLIE DORIS ELFREDA FRED)"))] - (is (= actual expected))))) +;; TODO: need to reimplement this in lisp_test +;; (deftest substitution-tests +;; (testing "subst" +;; (let [expected "((A X . A) . C)" +;; ;; differs from example in book only because of how the function +;; ;; `beowulf.cons-cell/to-string` formats lists. +;; actual (print-str +;; (SUBST +;; (gsp "(X . A)") +;; (gsp "B") +;; (gsp "((A . B) . C)")))] +;; (is (= actual expected))))) (deftest pairlis-tests (testing "pairlis" @@ -233,12 +203,3 @@ 'D (gsp "((A . (M N)) (B . (CAR X)) (C . (QUOTE M)) (C . (CDR X)))")))] (is (= actual expected))))) - -(deftest sublis-tests - (testing "sublis" - (let [expected "(SHAKESPEARE WROTE (THE TEMPEST))" - actual (print-str - (SUBLIS - (gsp "((X . SHAKESPEARE) (Y . (THE TEMPEST)))") - (gsp "(X WROTE Y)")))] - (is (= actual expected))))) diff --git a/test/beowulf/host_test.clj b/test/beowulf/host_test.clj index da86637..7e5e1ff 100644 --- a/test/beowulf/host_test.clj +++ b/test/beowulf/host_test.clj @@ -1,7 +1,7 @@ (ns beowulf.host-test (:require [clojure.test :refer [deftest is testing]] - [beowulf.cons-cell :refer [CDR F make-beowulf-list T]] - [beowulf.host :refer [DIFFERENCE NUMBERP PLUS RPLACA RPLACD TIMES]] + [beowulf.cons-cell :refer [F make-beowulf-list T]] + [beowulf.host :refer [CDR DIFFERENCE NUMBERP PLUS RPLACA RPLACD TIMES]] [beowulf.oblist :refer [NIL]] [beowulf.read :refer [gsp]])) @@ -15,12 +15,12 @@ (is (= actual expected))) (is (thrown-with-msg? Exception - #"Invalid value in RPLACA.*" + #"Un-ġefōg þing in RPLACA.*" (RPLACA (make-beowulf-list '(A B C D E)) "F")) "You can't represent a string in Lisp 1.5") (is (thrown-with-msg? Exception - #"Invalid cell in RPLACA.*" + #"Uncynlic miercels in RPLACA.*" (RPLACA '(A B C D E) 'F)) "You can't RPLACA into anything which isn't a MutableSequence.") ) diff --git a/test/beowulf/interop_test.clj b/test/beowulf/interop_test.clj index 98290f2..c1e70ea 100644 --- a/test/beowulf/interop_test.clj +++ b/test/beowulf/interop_test.clj @@ -1,6 +1,7 @@ (ns beowulf.interop-test (:require [clojure.test :refer [deftest is testing]] - [beowulf.bootstrap :refer [EVAL INTEROP]] + [beowulf.bootstrap :refer [EVAL]] + [beowulf.interop :refer [INTEROP]] [beowulf.read :refer [gsp]])) diff --git a/test/beowulf/lisp_test.clj b/test/beowulf/lisp_test.clj new file mode 100644 index 0000000..7d9fa64 --- /dev/null +++ b/test/beowulf/lisp_test.clj @@ -0,0 +1,168 @@ +(ns beowulf.lisp-test + "The idea here is to test actual Lisp functions" + (:require [clojure.test :refer [deftest testing is use-fixtures]] + [beowulf.bootstrap :refer [EVAL]] + [beowulf.cons-cell :refer [make-beowulf-list]] + [beowulf.io :refer [SYSIN]] + ;; [beowulf.oblist :refer [NIL]] + [beowulf.read :refer [READ]])) + +(defn- reps + "'Read eval print string', or 'read eval print single'. + Reads and evaluates one input string, and returns the + output string." + [input] + (with-out-str (print (EVAL (READ input))))) + +(use-fixtures :once (fn [f] + (try (when (SYSIN "resources/lisp1.5.lsp") + (f)) + (catch Throwable any + (throw (ex-info "Failed to load Lisp sysout" + {:phase test + :function 'SYSIN + :file "resources/lisp1.5.lsp"} + any)))))) + +(deftest APPEND-tests + (testing "append - dot-terminated lists" + (let [expected "(A B C . D)" + actual (reps "(APPEND '(A B) (CONS 'C 'D))")] + (is (= actual expected))) + (let [expected "(A B C . D)" + actual (reps "(APPEND (CONS 'A (CONS 'B NIL)) (CONS 'C 'D))")] + (is (= actual expected))) + ;; this is failing: https://github.com/simon-brooke/beowulf/issues/5 + (let [expected "(A B C . D)" + actual (reps "(APPEND '(A B) '(C . D))")] + (is (= actual expected)))) + (testing "append - straight lists" + (let [expected "(A B C D E)" + actual (reps "(APPEND '(A B) '(C D E))")] + (is (= actual expected))))) + +(deftest COPY-tests + (testing "copy NIL" + (let [expected "NIL" + actual (with-out-str (print (EVAL (READ "(COPY NIL)"))))] + (is (= actual expected)))) + (testing "copy straight list" + (let [expected (make-beowulf-list '(A B C)) + actual (EVAL (READ "(COPY '(A B C))"))] + (is (= actual expected)))) + (testing "copy assoc list created in READ" + ;; this is failing. Problem in READ? + ;; see https://github.com/simon-brooke/beowulf/issues/5 + (let [expected (READ "((A . 1) (B . 2) (C . 3))") + actual (EVAL (READ "(COPY '((A . 1) (B . 2) (C . 3)))"))] + (is (= actual expected)))) + (testing "copy assoc list created with PAIR" + (let [expected (READ "((A . 1) (B . 2) (C . 3))") + actual (EVAL (READ "(COPY (PAIR '(A B C) '(1 2 3)))"))] + (is (= actual expected))))) + +(deftest DIVIDE-tests + (testing "rational divide" + (let [expected "(4 0)" + input "(DIVIDE 8 2)" + actual (reps input)] + (is (= actual expected)))) + (testing "irrational divide" + (let [expected "(3.142857 1)" + input "(DIVIDE 22 7)" + actual (reps input)] + (is (= actual expected)))) + (testing "divide by zero" + (let [input "(DIVIDE 22 0)"] + (is (thrown-with-msg? clojure.lang.ExceptionInfo + #"Uncynlic þegnung: Divide by zero" + (reps input))))) + + ;; TODO: need to write tests for GET but I don't really + ;; understand what the correct behaviour is. + + (deftest INTERSECTION-tests + (testing "non-intersecting" + (let [expected "NIL" + input "(INTERSECTION '(A B C) '(D E F))" + actual (reps input)] + (is (= actual expected)))) + (testing "intersection with NIL" + (let [expected "NIL" + input "(INTERSECTION '(A B C) NIL)" + actual (reps input)] + (is (= actual expected)))) + (testing "intersection with NIL (2)" + (let [expected "NIL" + input "(INTERSECTION NIL '(A B C))" + actual (reps input)] + (is (= actual expected)))) + (testing "sequential intersection" + (let [expected "(C D)" + input "(INTERSECTION '(A B C D) '(C D E F))" + actual (reps input)] + (is (= actual expected)))) + (testing "non-sequential intersection" + (let [expected "(C D)" + input "(INTERSECTION '(A B C D) '(F D E C))" + actual (reps input)] + (is (= actual expected))))) + + (deftest LENGTH-tests + (testing "length of NIL" + (let [expected "0" + input "(LENGTH NIL)" + actual (reps input)] + (is (= actual expected)))) + (testing "length of simple list" + (let [expected "3" + input "(LENGTH '(1 2 3))" + actual (reps input)] + (is (= actual expected)))) + ;; (testing "length of dot-terminated list" + ;; (let [expected "3" + ;; input "(LENGTH '(1 2 3 . 4))" + ;; actual (reps input)] + ;; (is (= actual expected)))) + (testing "length of assoc list" + (let [expected "3" + input "(LENGTH (PAIR '(A B C) '(1 2 3)))" + actual (reps input)] + (is (= actual expected)))))) + + +(deftest MEMBER-tests + (testing "member" + (let [expected "T" + actual (reps "(MEMBER 'ALBERT '(ALBERT BELINDA CHARLIE DORIS ELFREDA FRED))")] + (is (= actual expected))) + (let [expected "T" + actual (reps "(MEMBER 'BELINDA '(ALBERT BELINDA CHARLIE DORIS ELFREDA FRED))")] + (is (= actual expected))) + (let [expected "T" + actual (reps "(MEMBER 'ELFREDA '(ALBERT BELINDA CHARLIE DORIS ELFREDA FRED))")] + (is (= actual expected))) + (let [expected "F" + actual (reps "(MEMBER 'BERTRAM '(ALBERT BELINDA CHARLIE DORIS ELFREDA FRED))")] + (is (= actual expected))))) + +;; This is failing, and although yes, it does matter, I have not yet tracked the reason. +;; (deftest sublis-tests +;; (testing "sublis" +;; (let [expected "(SHAKESPEARE WROTE (THE TEMPEST))" +;; actual (reps +;; "(SUBLIS +;; '((X . SHAKESPEARE) (Y . (THE TEMPEST))) +;; '(X WROTE Y))")] +;; (is (= actual expected))))) + +(deftest prog-tests + (testing "PROG" + (let [expected "5" + actual (reps "(PROG (X) + (SETQ X 1) + START + (SETQ X (ADD1 X)) + (COND ((EQ X 5) (RETURN X)) + (T (GO START))))")] + (is (= actual expected))))) \ No newline at end of file diff --git a/test/beowulf/mexpr_test.clj b/test/beowulf/mexpr_test.clj index 888e6e7..2f74389 100644 --- a/test/beowulf/mexpr_test.clj +++ b/test/beowulf/mexpr_test.clj @@ -6,7 +6,7 @@ [beowulf.read :refer [gsp]] [beowulf.reader.generate :refer [generate]] [beowulf.reader.parser :refer [parse]] - [beowulf.reader.simplify :refer [simplify]])) + [beowulf.reader.simplify :refer [simplify-tree]])) ;; These tests are taken generally from the examples on page 10 of ;; Lisp 1.5 Programmers Manual: @@ -53,7 +53,7 @@ ;; I suspect as (CAR (LIST A B C)). (let [expected "(CAR (LIST A B C))" - actual (print-str (gsp "car[(A B C)]"))] + actual (print-str (gsp "car[ list[a; b; c]]"))] (is (= actual expected))) )) @@ -68,13 +68,13 @@ (deftest conditional-tests (testing "Conditional expressions" - (let [expected "(COND ((ATOM X) X) ((QUOTE T) (FF (CAR X))))" + (let [expected "(COND ((ATOM X) X) (T (FF (CAR X))))" actual (print-str (gsp "[atom[x]->x; T->ff[car[x]]]"))] (is (= actual expected))) - (let [expected "(LABEL FF (LAMBDA (X) (COND ((ATOM X) X) ((QUOTE T) (FF (CAR X))))))" + (let [expected "(LABEL FF (LAMBDA (X) (COND ((ATOM X) X) (T (FF (CAR X))))))" actual (print-str (generate - (simplify + (simplify-tree (parse "label[ff;λ[[x];[atom[x]->x; T->ff[car[x]]]]]"))))] (is (= actual expected))))) @@ -88,6 +88,6 @@ (deftest assignment-tests (testing "Function assignment" - (let [expected "(SET (QUOTE FF) (QUOTE (LAMBDA (X) (COND ((ATOM X) X) ((QUOTE T) (FF (CAR X)))))))" + (let [expected "(PUT (QUOTE FF) (QUOTE EXPR) (QUOTE (LAMBDA (X) (COND ((ATOM X) X) (T (FF (CAR X)))))))" actual (print-str (gsp "ff[x]=[atom[x] -> x; T -> ff[car[x]]]"))] (is (= actual expected))))) diff --git a/test/beowulf/reader_macro_test.clj b/test/beowulf/reader_macro_test.clj index 228a6a9..0f94111 100644 --- a/test/beowulf/reader_macro_test.clj +++ b/test/beowulf/reader_macro_test.clj @@ -5,7 +5,7 @@ (deftest macro-expansion (testing "Expanding DEFUN" - (let [expected "(SET (QUOTE FACT) (LAMBDA (X) (COND ((ZEROP X) 1) (T (TIMES X (FACT (SUB1 X)))))))" + (let [expected "(SET (QUOTE FACT) (QUOTE (LAMBDA (X) (COND ((ZEROP X) 1) (T (TIMES X (FACT (SUB1 X))))))))" source "(DEFUN FACT (X) (COND ((ZEROP X) 1) (T (TIMES X (FACT (SUB1 X))))))" actual (print-str (gsp source))] (is (= actual expected))))) \ No newline at end of file