SLED - Schemy LISP voor DOS
Kenmerken
- Scheme-achtige LISP-interpreter voor DOS (of DOSBox).
- Werkt in 16-bit Real Mode.
- Klein geheugenmodel (Small Memory Model).
- Staartaanroep-optimalisatie (Tail-Call Optimization - TCO).
- Lexicale scoping.
- Mark & Sweep Garbage Collector.
- Trampoline-evaluator.
- Ongeveer 1,1K regels code (C89).
- 8 speciale vormen (Special Forms).
- 21 ingebouwde functies.
- Standaardbibliotheek met 21 functies.
- Executable van 11K.
- Open-source (0BSD).
Over SLED
Voor een overzicht van de beschikbare symbolen, speciale vormen, ingebouwde functies en de standaardbibliotheek, zie de index.
Gegevens
Er zijn twee fundamentele gegevenstypen: Pairs (paren) en Atoms (atomen/niet-paren). Atomen komen voor in drie varianten: Symbolen, Closures (functies) en enkele Speciale Symbolen.
Symbolen
Symbolen zijn unieke namen en bestaan uit een combinatie van maximaal 16 van de volgende tekens: a b c d e f g h i j k l m n o p q r s t u v w x y z 1 2 3 4 5 6 7 8 9 0 - . ? _ waarbij de punt (.) niet het eerste teken mag zijn.
Daarnaast kan elk printbaar ASCII-teken deel uitmaken van een symbool wanneer het wordt voorafgegaan door het escape-teken \ (backslash), met uitzondering van (, ), ' en $. Voorbeeld: This\ is\ a\ sym\!
Hoofdletters worden geaccepteerd, maar worden omgezet naar kleine letters, tenzij het teken is geëscaped.
Speciale Symbolen
Er zijn enkele vooraf gedefinieerde speciale symbolen die door SLED worden beheerd, zoals nil, wat "lege lijst" betekent. Zie de index voor details.
Quote
Een quote betekent "niet evalueren". Via de speciale vorm quote wordt een symbool geregistreerd: (quote sym)
Voor het gemak kan de korte syntax ' worden gebruikt: 'sym
In essentie verklaart quoting iets als data in plaats van code.
Pairs (Paren)
Paren bestaan uit een kop (head) en een staart (tail), die elk een atoom of een ander paar bevatten. Een paar kan als data worden gemaakt met de punt (.): '(a . x) of als resultaat van de ingebouwde functie cons: (cons 'a 'x)
De elementen van een paar (kop en staart) zijn onveranderlijk (immutable).
Lijsten
Een lijst is een reeks paren waarbij elke staart wijst naar een ander paar, behalve de laatste, wiens staart de waarde nil heeft, wat gelijkstaat aan (). Voorbeelden van lijsten:
nil'()'(a . nil)'(a . (b . nil))
Een lijst kan als data worden gemaakt via: '(a b) of als resultaat van de functie list uit de standaardbibliotheek: (list 'a 'b)
Incorrecte Lijsten (Improper Lists)
Een incorrecte lijst eindigt niet met nil, bijvoorbeeld: (a . (b . (c . d)))
Associatielijsten (Association Lists)
Een associatielijst is een lijst waarbij elk element een paar (associatie) is: ((a . x) (b . y) (c . z)) Het kopgedeelte van zo'n paar wordt de key (sleutel) genoemd en de staart de value (waarde).
S-Expressies
Een symbolische expressie (S-expressie) is een datastructuur gedefinieerd als: Een S-expressie is ofwel een atoom, ofwel een paar van S-expressies. In Lisp, Scheme, en in het bijzonder in SLED, worden S-expressies gebruikt voor zowel data als broncode.
Getallen
Het SLED-systeem bevat geen numerieke typen. Natuurlijke getallen (niet-negatieve gehele getallen) kunnen echter worden geëmuleerd met lijsten:
'(): nul'(nil): één'(nil nil): twee'(nil nil nil): drie
Dit zijn tally numerals (streepgetallen), waarbij de cardinaliteit de grootte vertegenwoordigt, vergelijkbaar met von Neumann-ordinale getallen. De functies inc, dec en zero? vergemakkelijken teltaken.
Code
In LISP wordt niet-gequoted data geëvalueerd als code.
Bindingen
Een binding koppelt een symbool aan een bepaalde payload van data en wordt aangemaakt via de speciale vorm define: (define a 'x)
Closures
Closures zijn functies samen met een omgeving, en resulteren uit de speciale vorm lambda: (define fun (lambda (arg1 arg2) (print arg1) (print arg2)))
Functie-aanroep
Het eerste element van een niet-gequotede lijst wordt geïnterpreteerd als een expressie die evalueert tot een functie, en de overige elementen als argumenten voor die functie: (fun arg1 arg2)
De functie-evaluatie is eager: eerst worden de argument-expressies geëvalueerd, daarna vindt de functie-aanroep plaats met de geëvalueerde argumenten.
Argumenten
Argumenten worden geëvalueerd en als een lijst van waarden aan een functie doorgegeven. Dit betekent dat functieparameters op verschillende manieren kunnen worden opgezet:
(lambda x ...): x is een lijst(lambda (x y) ...): gedestructureerde lijst met elementen x en y(lambda (x y . z) ...): z is een lijst (die standaardnilis)
Optionele argumenten kunnen als een lijst worden doorgegeven, zoals z hierboven.
Recursie
LISP's vertrouwen op recursie in plaats van iteratie. Recursie verwijst naar een functie die zichzelf aanroept. Twee functies van SLED helpen om een stack overflow bij diepe recursies te voorkomen: de trampoline-evaluator en staartaanroep-optimalisatie (TCO). TCO werkt voor lambda, let, begin, if, ifnil en apply. Daarnaast werkt TCO voor cons als de recursie plaatsvindt in het tweede argument.
Fouten
Een fout tijdens de evaluatie van een expressie springt terug naar het hoogste niveau (top-level), waar kan worden getest of er een fout is opgetreden. Een fout kan niet binnen een expressie worden opgevangen.
Ingebouwde Functies
Een set functies is ingebouwd in het SLED-executable om interactie met het systeem en kernfunctionaliteit mogelijk te maken. Zie de index voor details.
Standaardbibliotheek
Naast de kernfuncties is een set typische functies geïmplementeerd als een standaardbibliotheek in het bestand sled.scm. Zie de index voor details. De standaardbibliotheek kan worden uitgebreid met aanvullende eigen definities.
Speciale Vormen
Bepaalde vormen lijken op functies, maar dat zijn ze niet. Deze zogenaamde special forms volgen niet het gedrag van functies, maar gebruiken wel dezelfde syntax. Bijvoorbeeld: if evalueert zijn argumenten niet voordat de vorm wordt opgelost. Zie de index voor details.
Onveranderlijkheid (Immutability)
Speciale symbolen, speciale vormen, ingebouwde functies en de inhoud van de standaardbibliotheek zijn onveranderlijk in SLED. Bovendien kunnen speciale vormen en ingebouwde functies niet worden overschaduwd (shadowed). De standaardbibliotheek en aangrenzende eigen definities kunnen niet opnieuw worden gedefinieerd.
Systeem
SLED is een DOS-applicatie.
Bestandsnamen
Bestandsnamen moeten voldoen aan de DOS 8.3 naamgeving (maximaal 8 tekens voor de bestandsnaam, een punt, maximaal 3 tekens voor de extensie). De aanbevolen extensie voor scripts is .scm vanwege de syntactische gelijkenis met Scheme; bijvoorbeeld de standaardbibliotheek heet sled.scm. De interpreter controleert de extensie echter niet.
Opstarten
De eerste actie die sled onderneemt is het laden van de standaardbibliotheek (sled.scm), die in dezelfde map als het SLED.EXE executable moet staan. Alle symbolen en hun waarden die uit de standaardbibliotheek worden geladen, worden onveranderlijk.
Command-Line Argumenten
Het sled binary heeft vier wederzijds uitsluitende command-line argumenten:
C:\> sled /?: Toont de helppagina.C:\> sled code.scm: Pad naar een Lisp-bronbestand dat geladen wordt vóór de REPL start, maar ná de standaardbibliotheek.C:\> sled /B code.scm: "Batch mode", sluit af na uitvoering.C:\> sled /I code.scm: "Ignore errors", gedraagt zich als/Bmaar gaat door met de uitvoering na een fout.
Het bestandspad moet altijd het laatste argument zijn.
Bestandspad
In de interpreter moeten backslashes (\) in het pad worden geëscaped, aangezien het pad een symbool wordt en een enkele backslash geen toegestaan symboolteken is. C:\> sled to\my\code.scm (load 'to\\my\\code.scm)
Het bestandspad valt ook onder de limiet van 16 tekens.
REPL
Zodra sled is gestart, begint de read-eval-print-loop (REPL) met een prompt: sled> De REPL leest invoer, evalueert deze, print het resultaat en toont opnieuw de prompt. Voorbeeld: sled> (println 'hello _ 'world)
Uitgebreide Tekens
Een valkuil zijn uitgebreide (two-byte) tekens, die niet worden ondersteund. Een voorbeeld is het gebruik van de pijltjestoetsen in de REPL, wat resulteert in een α (alpha) in de standaard input-echo. Deze tekens vervuilen de inputstream en kunnen een fout in een invoerregel veroorzaken, zelfs als ze zijn gewist.
Afsluiten
Er zijn twee reguliere manieren om sled af te sluiten:
- Het dollarteken
$op het hoogste niveau, wat de parser vertelt om af te sluiten:sled> $ - De ingebouwde functie
(exit), die bij evaluatie afsluit.
Commentaren
Commentaren worden genegeerd door de parser en beginnen met een puntkomma (;). Alle tekens tot de volgende regelafbreking worden genegeerd. Traditioneel geeft het aantal opeenvolgende puntkomma's de semantiek aan, vergelijkbaar met Markdown-koppen:
;;;;hoofdtitle;;;sectietitel;;begin van regel;einde van regel
Daarnaast worden blokcommentaren gerealiseerd via de speciale vorm comment: (comment ...) Haken binnen een comment-vorm moeten in balans zijn:
(comment ()): OK(comment (): → fout
Let op: een comment-vorm kan niet worden gequoted: '(comment test) → fout.
Onderbreken
Om een pure berekening te onderbreken, gebruik je CTRL+Break. Voor het onderbreken van invoer is CTRL+C beschikbaar.
Help
In de REPL kan het symbool ? worden gebruikt om een lijst van speciale vormen, ingebouwde functies en symbolen uit de standaardbibliotheek op te vragen.
Limieten
Als real-mode DOS-programma heeft SLED meerdere beperkingen:
- De heap heeft 12288 nodes.
- De symbolentabel heeft 2048 tekens.
- De standaardbibliotheek verbruikt ongeveer 5% van de nodes en tekens.
Index
Speciale Symbolen
$: Sluit de REPL af (werkt alleen vanaf de prompt).?: Overzicht van speciale vormen, ingebouwde functies en standaardbibliotheek (werkt alleen vanaf de prompt).': Alias voor de speciale vormquote.ans: Bevat het resultaat van de laatste form op het hoogste niveau die een waarde produceerde.err: Markeert een foutstatus.nil: Vertegenwoordigt de lege lijst en is de enige waarde die als "false" wordt geëvalueerd. Gelijk aan'().self: Maakt anonieme recursie mogelijk. Binnen een closure verwijst het naar de omhullende lambda.true: Evalueert naar true. Gebruik als generieke "true"-waarde.ver: Evalueert naar een symbool dat de versie van SLED aangeeft.
Speciale Vormen
begin <body1> ... <bodyN>: Evalueert argumenten sequentieel en retourneert de waarde van het laatste argument.comment <arg1> ... <argN>: Wordt niet geëvalueerd; dient als blokcommentaar. Haken moeten gebalanceerd zijn.define <sym> <arg>: Creëert een nieuwe binding van het tweede argument aan het eerste symbool. Beïnvloedt altijd de globale binding.if <arg1> <arg2> [<arg3>]: Evalueertarg1; als dit nietnilis, wordtarg2geretourneerd, andersarg3(ofnil).ifnil <arg1> <arg2>: Retourneert het resultaat vanarg1als dit nietnilis, andersarg2.lambda (<arg1> ... <argN>) <body1> ... <bodyN>: Creëert een functie met argumenten als gedestructureerde lijst en een sequentieel geëvalueerd lichaam.let (<arg1> <arg2>) <body1> ... <bodyN>: Creëert een scope met één lokale binding. Meerdere bindingen vereisen genestelets.quote <arg>: Retourneert het argument ongeëvalueerd.
Ingebouwde Functies
apply <fun> <lst>: Evalueert de eerste argument-functie met de tweede argument-lijst als argumenten.atom? <arg>: Retourneerttrueals het argument een atoom is.cons <arg1> <arg2>: Retourneert een paar metarg1als kop enarg2als staart.defined? <sym>: Retourneerttrueals het symbool is gedefinieerd in de globale scope.empty? <arg>: Retourneerttrueals het argument de lege lijst is.env: Print de huidige door de gebruiker gedefinieerde symbolen.eof? <arg>: Retourneerttrueals het argument een EOF (End-Of-File) of EOT (End-Of-Transmission) symbool is.equiv? <arg1> <arg2>: Controleert op ondiepe gelijkheid (shallow equality). Gebruik voor atomen.error <sym> [<arg>]: Gooit een fout, onderbreekt de evaluatie en print een foutmelding en optionele reden.exit: Sluit de interpreter of REPL af.gc [<arg>]: Activeert garbage collection. Print node-gebruik als het argument nietnilis.head <arg>: Retourneert het kopgedeelte van een cons-cel of lijst (correspondeert metcar).load <sym> [<arg>]: Evalueert de inhoud van het bestand op het opgegeven pad. Kan maximaal twee keer genest worden.newline: Print een regelafbreking.print <arg1> ... <argN>: Print argumenten naar de standaard output.proc? <arg>: Retourneerttrueals het argument een closure of ingebouwde functie is (niet voor speciale vormen).read: Leest een symbool als een regel invoer vanaf de standaard input.restart [<sym>]: Reset en herstart de interpreter; optioneel wordt een bestand geladen. Alle definities gaan verloren.symbol? <arg>: Retourneerttrueals het argument een symbool is.tail <arg>: Retourneert het staartgedeelte van een cons-cel (correspondeert metcdr).value <sym>: Lost de waarde van het symbool op, rekening houdend met lexicale scope.
Standaard Aliases
_: Alias voor het spatie-teken\.br: Alias voornewline.nil?: Alias voorempty?.not: Alias voorempty?(om resultaten van predicaten om te keren).quit: Alias voorexit.zero?: Alias voorempty?(voor tally-getallen).
Standaardbibliotheek
and? <arg1> <arg2>: Binaire predicaat; retourneerttrueals beide argumenten nietnilzijn.append <lst1> <lst2>: Concateneert de tweede lijst aan het einde van de eerste.assert <arg> <sym>: Print het tweede argument en veroorzaakt een fout als het eerste argumentnilis.compose <arg> <fun1> ... <funN>: Pijplijnt unaire functies: het resultaat van de vorige functie is het argument voor de volgende.dec <arg>: Retourneert de staart van een lijst (om tally-getallen te decrementeren).equal? <arg1> <arg2>: Controleert of argumenten recursief gelijk zijn. Gebruik voor paren en lijsten.error?: Thunk-predicaat; retourneerttrueals de vorige evaluatie in een fout resulteerde.get <sym> <lst>: Retourneert de waarde gekoppeld aan het symbool in de associatielijst, andersnil.id <arg>: Identiteitsfunctie; retourneert het argument.inc <arg>: Voegtniltoe aan het begin van een lijst (om tally-getallen te incrementeren).list <arg1> ... <argN>: Bouwt een lijst van de gegeven argumenten.list? <arg>: Retourneerttrueals het argument een correcte lijst is.map <fun> <lst>: Past de functie toe op elk element van de lijst en retourneert de lijst met resultaten.member <arg> <lst>: Retourneert het paar uit de lijst waarvan de kop gelijk is aan het argument, andersnil.or? <arg1> <arg2>: Binaire predicaat; retourneerttrueals een van de argumenten nietnilis.pair? <arg>: Retourneerttrueals het argument geen atoom is.printid <arg1> [<arg2>]: Print het argument (en optioneel een prefix) gevolgd door een regelafbreking, en retourneert het argument.println <arg1> ... <argN>: Print argumenten naar de standaard output inclusief een regelafbreking.put <sym> <arg> <lst>: Update of voegt een paar toe aan de associatielijst.reverse <lst>: Keert de volgorde van de lijst om.shorter? <lst1> <lst2>: Retourneerttrueals de eerste lijst minder elementen heeft dan de tweede.
Gebruik
SLED is gemaakt voor een disk operating system zoals FreeDOS of MS-DOS. Buiten DOS is een emulator zoals DOSBox, DOSBox-X of DOSBox-Staging vereist.
- Op Linux, BSD, MacOS of Unix kan het shell-script
./sled.shworden uitgevoerd (selecteert automatisch de geïnstalleerde DOSBox). - Voor het bouwen van SLED is de Microsoft C Compiler of Open Watcom nodig, samen met
make. - Bouwen met MS C 6.0A:
make build_msc - Bouwen met Open Watcom v2:
make build_owc - Uitvoeren:
make run - Tests draaien:
make tests - Benchmark draaien:
make bench(Takeuchi-functie).
Links
- DOS
- Kilo LISP
- PC Scheme (Implementatie en vergelijking van LISP-interpreters voor MS-DOS)
- Gratis Lisp-ontwikkelomgevingen voor DOS
- S-expressions
Dit project door gramian is gelicentieerd onder de 0BSD (Zero-Clause BSD) licentie.
Groetjes,