PILS programmeringssproget og -systemet er udviklet af mig (Ole Nielsby) fra 1979 til 2008, oprindelig som et forsøg på at forbedre på Lisp, men under inflydelse af Prolog, C++, XSLT og SQL, og visse egenskaber ved dansk talesprog udviklede det sig efterhånden til noget ganske andet.
Ligesom Lisp bruger PILS samme datamodel for programmer og data, men hvor Lisp bruger lister, bruger PILS attributbaserede noder som byggesten for datamodellen. For kortheds skyld kaldes attributterne for ben. Efter inspiration fra Prolog er de simple funktioner i Lisp erstattet af regelsæt hvor data skal indpasses i en pasform, og efter inspiration fra C++ håndteres disse regelsæt som objekter og kan kombineres på mange måder.
PILS er velegnet til projekter af mange slags, så længe der ikke er brug for tung talknusning eller mere finkornet synkronisering end PILS kan tilbyde. PILS er udstyret med en omfattende samling tekst- og listebehandlingsoperationer, og programmeringssystemet er, trods sin lille størrelse, ganske modent og fleksibelt, med understøttelse af fejludpegning i kildeteksterne. Bindinger til juce-biblioteket og flertrådning giver mulighed for velfungerende grafiske brugerflader, selvom bindingerne og biblioteket de er pakket ind i, ikke kan betegnes som fuldt udstyrede.
Kildeteksterne til PILS systemet er public domain (det betyder at du kan gøre hvad der passer dig med dem) pånær grafikbiblioteket Juce, se http://www.rawmaterialsoftware.com/juce/ som p.t. bruges under GPL-licens, hvilket betyder at det samlede PILS system p.t. er underlagt GPL-licensen, men der er udsigt til mere lempelige regler i nær fremtid.
PILS er konstrueret til åbne kildetekster og understøtter ikke sløring eller kompilering.
Militære institutioner og våbenindustri skal ikke vente sig samarbejde fra min side; ellers hjælper jeg gerne med anvendelse af PILS hvis det er muligt.
Jeg har forsøgt at arrangere materialet i en rækkefølge der giver mening, men de lidt indviklede indbyrdes afhængigheder mellem sproget og programmeringssystemet betyder at nogle afsnit nødvendigvis må referere til materiale der forklares senere i dokumentet. Hvis du er nybegynder med PILS, bør du nok først læse det igennem og fatte så meget du nu kan, og så prøve lidt PILS programmering, og derefter læse dokumentet igen.
PILS har – eller forsøger at tilbyde – en programmørvenlig syntaks over en a Lisp/XML-agtig datamodel.
PILS er et rent funktionelt programmeringssprog; noder, lister og tekststrenge kan ikke ændres når de først er skabt. Dette udnyttes internt i fortolkeren til omfattende specialisering af noderne efter deres indhold.
PILS har dynamiske typer og leksikalsk navnebinding. Objekter opbygges ved at binde regelsæt og kombinere dem. Fremmede objekter – såsom juce-komponenter – kan kombineres med PILS objekter.
Fejludpegning i PILS kildekode understøttes af en modellering af ansvar, sammen med en parserfunktion der rapporterer positioner i kildeteksten.
Referencetællerbaseret lagerhåndtering med øjeblikkelig frigivelse sikrer hurtig automatisk frigivelse af resurser.
Bemærk at den form for lagerhåndtering der bruges af Java og .NET, er uegnet som grundlag for PILS datamodellen, der udnytter de faste adresser til hurtige opslag og sorteringer. Der er ingen planer om at tilpasse PILS til disse systemer.
Indbyggede operationer har forrang over de der defineres af regelsæt; du kan ikke omdefinere 2 + 2 til at give 5 , med mindre du bruger et sprogobjekt til at oversætte + til et andet symbol. Men regelsættene kan udvide de indbyggede operatorer ved at behandle tilfælde som ikke håndteres af de indbyggede regler, som beregninger på talpar.
PILS programmer udføres af en fortolker der oprindelig er skrevet i assembler og senere genskrevet i uhumsk C++. Kildeteksterne er tilgængelige men dårligt dokumenterede. Pasformer kompileres til blokke af C++ -objekter med specialiserede metoder for genkendelse.
Grundstenen i PILS datamodellen er noden, bestående af et nodenavn og et eller flere unikt navngivne attributterne, kaldet ben. Nodenavnet og benenes navne kan være vilkårlige konstanter, herunder konstante noder. Benenes værdier er vilkårlige PILS data.
Nodenavnet og benenes navne gemmes i en kliche der er fælles for alle noder med netop den kombination af nodenavn og bennavne, uanset benenes rækkefølge.
Udover noder og klicheer har PILS lister, tal, tekststrenge samt nogle mere specialiserede typer. Tekststrengene er utf-8-kodede, tallene er flydende tal med fuld præcision (doubles, typisk 15-16 cifres nøjagtighed) hvoraf heltallene er et specialtilfælde, lister er følger der tælles fra 1 og behandles efter samlebåndsprincip når det er muligt: når a liste sendes igennem en serie operationer, sker det for et element ad gangen.
Alt efter hvordan fortolkeren håndterer dem, kan alle PILS data klassificeres som konstanter eller udtryk. Både konstanter og udtryk er uforanderlige, men konstanter er kendetegnet ved at der ikke sker noget når de fortolkes, og det eneste der kan indpasses i en konstant pasform, er den selvsame konstant. Udtryk kan derimod resultere i noget andet ved tolkning, og noget andet kan indpasses i et udtryk der står som pasform.
Konstanter har ingen identitet og er altid unikt repræsenterede. Når en konstant dannes, eftersøges den først i en global hashtabel, og hvis en konstant med samme værdi allerede findes, genbruges den.
Alle programmeringskonstruktioner repræsenteres af noder med særlige navne. Sådanne noder samt noder og lister der indeholder dem, er udtryk, alle andre data er konstanter. Udtryk har identitet og kan dermed lokaliseres i kildeteksterne af programmeringssystemet.
Fremmede objekter behandles som konstanter, selvom de – i modsætning til PILS noder og lister – kan have tilstand.
PILS objekter opbygges af regelsæt – lister af regler af form {pasform|handling} . Når et regelsæt tolkes, bindes det til den aktuelle kontekst og danner et objekt der kan udføre et kald ved at prøve de regler hvor kaldet kan indpasses i pasformen. For at et kald skal lykkes, skal handlingen tage ansvar, typisk med ansvarstageren :ok .
Regelsæt kan oprettes dynamisk ved simpel konstruktion af noder af en bestemt form – de kompileres og indekseres automatisk og straks. Bundne regelsæt knyttes sammen i bundter og håndteres under et.
Ved indpasning i pasformer udnyttes konstanternes unikke repræsentation; tit kan komplekse strukturer genkendes eller afvises ud fra simple adressesammenligninger. Der er en omkostning ved at oprette dem, men det opvejes som regel af den hurtige genkendelse, undtagen for talknusning, som sinkes af de indpakninger og opslag der er nødvendige for at indpasse tallene i en datamodel der er konstrueret til genkendelse og søgning.
PILS har et finkornet fejlhåndteringssystem der vistnok ikke findes tilsvarende andetsteds.
I sprog med dynamiske typer kan det være svært at finde fejlkilder, fordi mange fejl udløses på uventede steder i koden, langt væk fra de programmeringsfejl som forårsager dem.
PILS afhjælper dette problem med ansvarsfralæggelse – en regel kan lægge ansvaret for en operation på det udtryk som har kaldt reglen. Det gøres med ansvarsfralæggerne :prøv og :kræv .
Mekanismen har en vis lighed med det der går under navnet structured exception handling, men PILS ansvarshåndtering er mere finkornet og bruger leksikalsk binding.
PILS projekter lagres i PILS biblioteksfiler – flade utf-8-tekstfiler der indeholder en liste over andre brugte biblioteker, efterfulgt af en liste navngivne PILS moduler. Modulnavnene er lister af PILS navne, der vises som et træ af programmeringssystemet.
Når en PILS fil åbnes, oprettes der en programstrop, der sammenfletter filen med de biblioteker den bruger, samt konfigurationsfiler og de biblioteker der udgør programmeringssystemet.
PILS fortolkeren har bindinger til juce-biblioteket. Bindingerne bruges gennem et platformsbibliotek der skjuler visse platformsspecifikke detaljer og i øvrigt muliggør at programmeringen kan ske med danske termer.
PILS leveres som et installationsprogram der er genereret med InnoSetup – installationsbrugerfladen vil være velkendt for de fleste brugere. PILS fortolkeren er statisk linket mod runtime-bibliotekerne i Visual C++ 2008 Express, og kræver ingen specielle biblioteker på maskinen. En afinstalleringsfunktion er inkluderet.
Den indeværende version af PILS er udviklet og testet på en dansk Windows XP.
Tal skrives som vant, med foranstillet - for negative tal, Et eventuelt decimaltegnet skal stå mellem to cifre, både , (komma) og . (punktum) kan bruges. Heltal kan skrives hexadecimalt som i C.
Tekststrenge kan skrives som vilkårlige tegnfølger afgrænset af " (anførselstegn, ASCII kode 34), disse skal skrives dobbelt hvis de forekommer i teksten.
"Dette er en tekststreng med et anførselstegn """
Efter det afsluttende " kan følge en skraber – en sekvens af tocifrede hexadecimale bytes, dobbeltskrevne anførselstegn og skrabetegn.
Skrabetegnene er:
= for LF (Linjeskift, standard i Unix og PILS)
< for CR (Macintosh linjeskift)
> for TAB
~ eller * for NULL
/ for CR+LF (DOS/Windows linjeskift)
Eksempel:
"Denne tekststreng slutter med et linjeskift"=
Efter en skraber kan følge mere tekst.
"Dette er en tekst"="på to linjer"=
Tekststrenge kan deles over flere linjer med - (delestreg).
"Dette er en tekststreng på en enkelt linje "-
"som er fordelt på to linjer i kildeteksten."
Kodningen er altid utf-8, dvs. æ, ø og å repræsenteres som to-byte-sekvenser.
Tal og tekststrenge kan bruges direkte som navne for noder og ben.
PILS har tre indbyggede typer til håndtering af tid.
Tidsstempler lagres som GMT, men skrives i lokal tid med tidszoneangivelse:
2008-12-06T19:00:05.161+01:00
Minutter, sekunder og millisekunder kan udelades, ligesom minutterne kan udelades i tidszoneangivelsen. GMT kan betegnes med +00:00 , +00 eller Z .
Dateringer er abstrakte tidsangivelser bestående af dato og klokkeslæt, men uden tilknytning til en tidszone. De skrives som tidsstempler, blot uden tidszoneangivelsen.
Varigheder skrives som:
1001d5h20m4.567s
for 1001 døgn, 5 timer, 20 minutter, 4 sekunder og 567 millisekunder. Der er ingen understøttelse for uger, måneder og år. Dele af angivelsen kan udelades; f.eks. kan et kvarter skrives:
15m
Varigheder kan være negative:
-3h20m
står for minus tre timer og 20 minutter.
Decimaltegnet er altid . i tidsangivelser, og bruges kun til at skille sekunder fra millisekunder.
Sammensatte konstanter (klicheer, noder og lister) sættes gerne i konstantklammer [ ] . For klicheer er klammerne påkrævede, for noder og lister kan de udelades når sammenhængen udelukker andre læsningsmuligheder. Indenfor konstantklammer gælder forsimplede syntaksregler: de konstruktioner der er nævnt i afsnittet om udtryk er ikke gyldige, navne og operatorer læses som simple konstanter. Eksempel:
2 + 2 er et udtryk der giver værdien 4
[2 + 2] er en liste af tre konstanter 2 , [+] og 2
Klicheer: [nodenavn|ben-navn|ben-navn ...]
Nodekonstanter: node-navn: .ben-navn .ben-værdi ...
Listekonstanter: element, ... idet , efter det sidste element kan udelades
Den tomme liste [] kan udelades når det ikke muliggør fejllæsninger. Indenfor konstantklammer betegner ? den tomme liste.
I konstante noder kan ben-værdierne udelades når de er identiske med benets navn.
Noderne er grådige: når som helst en node begynder, strækker den sig så langt det er muligt. I konsekvens af dette er der sjældent brug for parenteser ved indlejrede kontrolstrukturer.
Indenfor konstantklammer kan listekonstanter med to eller flere elementer skrives på kortform uden kommaer. Lister på kortform kan indlægges i kommaadskilte lister, som i denne matrix:
[1 0 0, 0 1 0, 0 0 1]
Man bør undgå at skrive lister af tekststrenge på kortform, da det kan lede til forvirrende syntaks. Skriv:
s *=* ["skidt", "godt"]
fremfor
s *=* ["skidt" "godt"]
En hale er et ben hvis navn er den tomme liste [] . PILS fortolkeren sammenkæder mange strukturer ved halerne. Halen kan skrives hvor man ønsker det – umiddelbart efter nodehovedet:
node: hale
eller senere, med brug af ; (semikolon)
node: .navn værdi; hale
Halen kan efterfølges af flere ben.
Halen skal adskilles fra det foranstående : eller ; af mellemrum.
Et forben er et ben med samme navn som noden. Dette har ingen speciel betydning i PILS undtagen i ordvælger-noder som beskrevet i det efterfølgende, men hvis forbenet sættes først, kan navnet udelades:
besked: . "Halløj" er det samme som besked: .besked "Halløj"
PILS datamodellen giver stor frihed hvad navne angår: enhver konstant, herunder konstante noder og lister, kan bruges som navn på en node eller et ben, selvom det almindeligste er at bruge navneklicheer som navne.
En navnekliche er en kliche af to tekststrenge [ordklassenøgle|navnestreng] , som typisk frembringes ved at parse et navn med et eventuelt ordklasse-præfix, via en sprognode.
En sprognode oversætter præfixerne til ordvælger-nøgler, og kan oversætte bestemte navnestrenge til bestemte konstanter for et givet præfix, hvorved PILS tilpasses til nøgleord på forskellige sprog. Sprogobjekter bruges også i forbindelse med brugergrænseflader, til oversættelse af navne og meddelelser.
Alle de indbyggede navne der genkendes af PILS fortolkerkernen, har "pils.org/ns/sne" som ordklassenøgle; sne er et akronym for Scandinavian Nerd-English, nemlig den slags engelsk vi bruger her til lands når vi gør os kloge på ting vi ikke ved hvad hedder på dansk.
Ideen om lokaltilpassede – eller flersprogede – programmeringssprog har været lagt på is siden der gik ged i makroerne fra lokaliserede tekstbehandlings- og regnearkprogrammer en gang for længe siden; men PILS er grundlæggende designet til flersproget brug, og det fungerer uden de store problemer.
Parsning styres altid af en sprognode:
[sprog: sprogdefinitionsnode]
hvor sprogdefinitionsnode er en nodekonstant hvis navn er en tekststreng, og hvis tekststreng-benævnte ben har ordklasse-noder som værdier. Benenes navne bruges som ordklasse-præfixer. Der skal være et forben, som gælder for navne uden præfix..
En ordklasse-node er en nodekonstant hvis navn bruges som ordklassenøgle for navne der ikke oversættes. De af ordklassenodens ben der har tekststrenge som navne, definerer oversættelser. Hvis nodenavnet er - , tillades kun oversatte navne; dette er nyttigt for kontrollerede ordklasser.
Sprogdefinitionsnodens hale er en skrive-kontrolstreng på to bytes, hvoraf den første opfattes som flag der styrer brugen af forskellige syntaktiske konventioner ved udskrift, mens den sidste byte bruges som decimaltegn og bør være "." eller "," .
Skrivekontrolflagene er:
0x01
Kontroltegn i tekststrenge skrives som kontrolsekvenser
(beskytter mod dos/unix linjeskift og nul-tegnkoder)
0x02
alle ikke-ASCII-tegn skrives som kontrolsekvenser
(muliggør PILS udskrift som ren ASCII)
0x04
data der ikke overholder utf-8-reglerne, tillades i udskriften
(normalt vil sådanne data blive udskrevet som kontrolsekvenser)
(dette sparer plads når binære data skrives som tekststrenge)
0x10
alle navne skrives som klicheer med tekststrenge
(omstændeligt, men uafhængigt af sprog)
0x20
sukker-fri, udtryk og regelsæt skrives med den grundlæggende nodesyntaks
(instruktivt – viser hvordan PILS parser dine udtryk)
Alle kombinationer er tilladt; 0x02 har forrang for 0x04. Kontrolsekvens-flagene gælder både for navne og tekststrenge; hvis et navn indeholder tegn der skal skrives som kontrolsekvenser, skrives det som tekststreng.
Parseren påvirkes ikke af skrivekontrolstrengen; både "." og "," kan bruges som decimaltegn, uanset hvad der er angives i skrivekontrolstrengen.
PILS starter op med en sprognode omtrent som vist her (gengivet på engelsk):
[ language:
"system": ! standard-ordklassepræfix
."system" ["pils.org/ns/sne":] ! standard-ordklassenøgle
."pils-configuration" ! hjælpe-ordklasse
[-: ! kun de følgende oversatte navne tillades i denne ordklasse
."platform". "juce" ! værdier som bruges af opstartsprocessen
."system". "Win32" ! indsæt selv dit foretrukne OS her
! (andre indstillinger er udeladt her for kortheds skyld)
]
;
""01"." ! skrivekontrolflag 0x01, punktum som decimaltegn
]
I dette særlige tilfælde oversættes navne som pils-configuration:platform til tekststrenge der indgår i styringen af opstartsprocessen og sørger for indkobling af de relevante biblioteker.
PILS kan indstilles til dansk sprogbrug ved at kopiere biblioteket <lib>/pils/danish/pils/config.pils til brugerens Application Data -mappe. Dette biblioteket medtages så i alle kørende programmer, hvorved visse funktioner, specielt sig og siger (på engelsk say og saying ) omstilles til at bruge det danske sprogobjekt der er defineret i <lib>/danish/pils/danish .
For nærværende understøttes kun dansk og engelsk.. Andre sprog – såsom som fransk – kan defineres ved at tilføje disse filer til systemet:
<lib>/pils/french/pils/config.pils
som kontrollerer sprogvalget, og
<lib>/pils/french/pils/french.pils
som definerer sprognoden. En simpel fransk sprognode kunne se sådan ud:
[ language:
"fr":
."fr"
[ "pils.org/ns/fr":
."bon" good
."un" one
."di" say
."blanc" white
]
."anglais" ["pils.org/ns/sne":]
;
""01","
]
Dette vil oversætte navnet un til one (i omstændelig notation: ["pils.org/ns/sne"|"one"] ), mens vin vil blive læst som ["pils.org/ns/fr"|"vin"] idet der ikke er defineret en oversættelse.
Hvis en programmør skriver dette i et fransksproget modul:
di [un bon vin blanc]
og vi udfører det med dansk sprogindstilling, vil resultatet blive:
One god vin hvid
idet ordene ved indlæsning søges oversat til engelsk, og derefter søges udskrevet på dansk – ordet vin udskrives ganske vist på fransk eftersom der ikke er defineret nogen oversættelse for det, men med lidt god vilje forstår vi det jo alligevel.
Der kan defineres simple regler til forbedring af oversættelsen – det danske sprogbibliotek har således en simpel regel der ud fra sammenhængen forsøger at afgøre om det engelske ord no skal oversættes til nej eller ingen, hvilket er ret væsentligt for forståeligheden af meddelelser.
Den danske sprognode dækker så vidt muligt alle ord der bruges i PILS sproget og programmeringssystemet. Den danske terminologi er i øvrigt den originale; den engelske terminologi der bruges internt i fortolkeren er for de fleste ords vedkommende oversat fra dansk.
Bemærk at også de sprogneutrale operatorer defineres som oversættelser; ellers ville de få en dansk ordvælger-nøgle og ikke blive genkendt som indbyggede operatorer.
İstanbul express -metoden – der beskrives i afsnittet om tekstoperationer – viser hvordan oversættelser kan bruges til at tilpasse konverteringerne mellem store og små bogstaver til tyrkisk, hvor prikken over i'et bevares når det skrives med stort.
Vilkårlige tekststrenge kan bruges som navnestrenge, men for det meste bruges regulære navne, der skrives uden anførselstegn.
Et regulært navn er en sekvens af bogstaver, operatortegn + - * / \ ^ # $ % & , sammenligningsoperatortegn < > = ~ , cifre 0 1 2 3 4 5 6 7 8 9 og . (punktum), idet ' @ ` _ og alle tegn uden for ASCII-tegnsættet behandles som bogstaver (herunder æ, ø og å der kodes som utf-8 sekvenser), med de følgende undtagelser:
Et regulært navn kan ikke begynde med et punktum (dette ville blive læst som et ben)
eller med et ciffer eller med en bindestreg efterfulgt af et ciffer (det ville blive læst som et tal)
eller slutte med et operator- eller sammenligningsoperatortegn efterfulgt af et punktum (det ville blive læst som operatoren, opfattet som et almindeligt ord).
Grundlæggende er alt tilladt hvis ikke det kan misforstås af parseren.
Navne der slutter med et operatortegn eller et sammenligningsoperatortegn, klassificeres som henholdsvis operatorer eller sammenligningsoperatorer. Indenfor konstantklammer [] og for nodenavne og nodebenenes navne er dette uden betydning; operatorer og sammenligningsoperatorer kan uden videre bruges som navne på noder og nodeben. Tildelingssymbolet := fås ved at bruge sammenligningsoperatoren = som navn på et ben.
Operatorer og sammenligningsoperatorer kan bruges som almindelige ord idet de afsluttes med et enkelt punktum som ikke regnes med til ordet.
Et præfix er et regulært navn og et : uden mellemrum. Præfixet slås op i ordvælger-noden, og værdien bruges til at oversætte det efterfølgende navn eller kombinere det med en ordvælger-nøgle.
Når der ikke bruges præfix , eller når nul-præfixet ?: bruges foran en tekststreng, bruges sprognodens standard-ordvælger.
Et PILS udtryk kan ikke definere ordvælger-nøgler lokalt som det er tilfældet med XML; de eneste tilladte præfixer er de der defineres af sprognoden. Det er dog muligt at læse og skrive navne der tilhører andre ordvælgers, med kliche-notation:
["ordklassenøgle"|"navnestreng"]
Som en ekstra bekvemmelighed læses {} som en forkortelse for den sprognode der parses efter.
Ordklassenøglen for et præfix kan angives som: præfix:? eller, for sprognodens standard-ordklasse, ?:?
Generelt tolkes noder ved at benenes værdier tolkes og samles til en tilsvarende node (for nodekonstanter vil dette altid resultere i den selvsame node, og derfor sparer fortolkeren arbejdet).
Noder med navnene [] (den tomme liste) eller [|handling] tolkes anderledes og skrives med en særlig notation:
;navn værdi ... for []: .navn værdi ...
;: værdi ... for []: værdi ...
:navn ... for [|handling]: .navn ...
: værdi ... for [|handling]: værdi ...
Noder med tomt navn [] bruges til lokale bindinger:
;navn værdi; ...
Handlingsnoderne bruges til kontrolstrukturer:
:hvis betingelse; udtryk .ellers ellers-udtryk
Et navn der står alene
navn
læses som:
:kald [navn]
I pasformer vil dette binde navnet til den tilsvarende værdi. I udtryk vil den aktuelle kontekst blive gennemsøgt efter bindinger eller regler for navnet.
Hvis navnet efterfølges af en konstant, et regelsæt eller et udtryk i parenteser, dannes en node – dette kaldes en frase.
a 3 læses som :kald [a: 3]
b {x|y} læses som :kald b: {x|y}
Fraser lan have navngivne ben
linje (.fra a .til b) er the same som: :kald linje: .fra a .til b
Forben og skjulte benværdier kan bruges:
besked (.) is the same som: :kald besked: .besked besked
Konstantklammer kan bruges til at danne fraser:
besked [. "Hallo"] er det same som: :kald [besked: .besked "Hallo"]
I pasformer virker konstantfraserne ganske som navne, men udtryksfraserne behandles som almindelige noder, hvilket sjældent giver mening. Ved tolkning tolkes udtryksfraserne før kaldet udføres.
To enheder efter hinanden læses som et objektkald:
(a) (b) er det same som :hvem a .kald b
eller, for at pinde det helt ud: :hvem (:kald [a]) .kald :kald [b]
Hvis den anden enhed er et navn eller en frase, læses det som et metodekald:
a b er det samme som :hvem a .kald [b]
eller, pindet ud: :hvem (:kald [a]) .kald [b]
Objektkald sammenknyttes fra venstre:
a b c er det samme som (a b) c
I pasformer er objektkald i almindelighed meningsløse. Nogle metodekald er reservet til typetjek og forskellige andre formål. Pasformer bør bruge objektkald i omsvøb som : (a) (b) eller, for nu at vise det samme med en mere omstændelig syntaks, ::hvem a .kald b .
Et kald kan afsluttes på en af følgende måder:
Kaldet lykkes og resulterer i en værdi.
2 + 2 giver 4
Kaldet svipser fordi det hverken kan behandles af de indbyggede operationer eller af reglerne i objektet eller konteksten.
2 + "to" svipser, forudsat ingen regler behandler kaldet.
Kaldet fejler, idet en indbygget operation eller en angiven regel detekterer at noget er galt.
{} læs "[shit}" fejler med værdien [fejl: . ?:"Mangler konstant" .start 5 .slut 5]
Fejl signalerer at noget er galt med programmet eller de data det arbejder på, og vil som regel skulle rapporteres til brugeren, hvorimod svipsere tit er et led i en normal procedure og kan ignoreres.
Når en :kald node tolkes, sker dette:
kald -benet tolkes.
Den aktuelle kontekst gennemsøges efter regler eller bindinger der kan behandle kaldet.
Hvis ingen regler eller bindinger kan behandle kaldet, signaleres en svipser.
Når en :hvem .kald node tolkes, sker dette:
Hvis kaldet er en indbygget operation, overlades kontrollen til denne. Hvis den indbyggede operation ikke kan håndtere kaldet, simulerer den de følgende skridt 2 og 3 idet de dele af udtrykket der allerede er tolket, genbruges – så alting er som hvis den indbyggede operation ikke eksisterede. Hvis en samlebåndsoperation som enhver eller fold svipser, er denne rekonstruktion umulig, og svipseren behandles som i 6.
kald -benet tolkes, det kaldes argumentet
hvem -benet tolkes, dette er objektet
objektet gennemsøges efter regler eller bindinger der kan behandle argumentet
Hvis det fejler, fejler udtrykket. Hvis det svipser, oprettes en :hvem .kald -node, der derefter behandles som et kald i den aktuelle kontekst. Dette giver mulighed for at supplere de indbyggede operationer med lokalt definerede regler.
Hvis dette fejler eller svipser og udtrykket blev tolket som handlingen i en :prøv eller :kræv ansvarsunddragelse, behandles fejlen eller svipseren af denne. Hvis udtrykket blev tolket som udtryksdelen af en pasformsbetingelse, afvises betingelsen.
Hvis dette ikke er tilfældet, prøves et :fejl -kald i den aktuelle kontekst. For svipsere konstrueres en fejlværdi, for fejl bruges den angivne fejlværdi.
Hvis :fejl -kaldet svipser, returneres fejlværdien og udførelsen fortsætter.
Bemærk at argumentet tolkes før objektet. Denne konvention er valgt fordi den falder i hak med fortolkerens optimeringsstrategier, specielt samlebåndsoperationer på lister.
Et punktum i fri luft kan bruges til at skille et navn fra en efterfølgende konstant, regelsæt eller parentes, for at undgå at det læses som en frase.
a . 3
læses som:
:hvem (:kald [a]) .kald 3
(Hvis a resulterer i en liste, vil udtrykket udvælge det 3. element.)
Et friluftspunktum kan også bruges til at skille et navn eller en frase fra et foregående element, for at angive at navnet eller frasen skal indlægges i sin egen :kald -node.
a . b
er det samme som
(a) (b)
eller:
:hvem (:kald [a]) .kald :kald [b]
(Hvis a og b giver tekststrenge, vil udtrykket sammenstykke dem.)
Når en operator eller sammenligningsoperator indleder en sekvens eller følger efter en anden operator eller sammenligningsoperator, læses den som foranstillet operator.
+ n
læses som:
:hvem (:kald n) .kald [+]
Når operatorer bruges i midterstilling, har de samme præcedens som sekvenskald.
a + b
læses som
:hvem (:kald a) .kald +: :kald b
Et eksempel:
a b + - * c 14 d
er det samme som:
((a b) + (- (* (c (14))))) d
Sammenligningsoperatorer, dvs. operatorer der slutter med = , < , > eller ~ , har lavere præcedens og samles fra højre
a + a = b + b = c + c
er the same som
(a + a) = ((b + b) = (c + c))
(Ovenstående tjener kun til illustration af præcedensreglerne, udtrykket er ikke meningsfyldt.)
PILS objekter opbygges af bundne regelsæt. Et regelsæt som
{ pasform2 | handling2 }
{ pasform1 | handling1 }
repræsenteres af en node
:regelsæt (;pas pasform2 .handling handling2), (;pas pasform1 .handling handling1)
Når et regelsæt tolkes i en given kontekst, bindes det til konteksten, i form af en node:
:regelsæt .hvor
Når et kald udføres på et sådant bundet regelsæt, prøves reglerne nedefra og op, idet en intern indekseringsmekanisme gør at kun de relevante regler prøves. Prøvning af en regel sker ved at kaldet søges indpasset i reglens pasform, og hvis dette lykkes, tolkes handlingen med de bindinger der blev etableret af indpasningen.
For at en regel skal have virkning, skal ansvaret behandles, typisk ved ansvarstageren :ok , men ansvarshenviserne :prøv og :kræv muliggør mere detaljeret kontrol. Hvis handlingen ikke tager ansvaret, fortsætter søgningen til næste regel. Hvis ingen regel håndterer ansvaret for kaldet, svipser det.
Reglerne kan udtrække information om kaldet med implicitte bindere, hvoraf :hvem er den vigtigste og betegner det objekt hvori reglen er fundet.
Pasformer er data med en struktur svarende til strukturen af de data der skal indpasses. Enhver konstant er en pasform der passer til sig selv og intet andet. Lister passer til lister af same længde og passende elementer. Nodeudtryk passer generelt til noder med same kliche og passende ben, men visse noder har speciel betydning i pasformer.
Variable skrevet som navne, repræsenteret af noder af form :kald [navn] , passer til hvad som helst og bindes dertil. Hvis en variabel findes flere steder i en pasform, skal værdierne på de tilsvarende pladser i kaldet være identiske.
Jokeren – skrevet som ? og repræsenteret som :kald [] – passer til hvad som helst uden forbehold.
Andre særlige noder giver mulighed for aliasser, simple typetjek, sammenligninger med talkonstanter, søgebetingelser og længdeudtræk for tekst og lister, udtrækning af specifikke pladser i en liste af ikke angivet længde, simple oversættelser af værdier, og delvist angivne nodeformer med konstanter til udfyldning af manglende ben.
De særlige nodeformer kan bruges som almindelige noder hvis de lægges i omsvøb:
: node
Udover i regler kan pasformer bruges med => -operatoren i betingelser.
:hvis udtryk => pasform; ...
Selvom PILS i hovedsagen er et rent funktionelt sprog, findes der tildelingsssætninger som bruges i forskellige listebehandlingsoperationer og til at sætte attributter på fremmede objekter..
Den ser sådan ud::
modtager := værdi
modtager := værdi; hale
Ved tolkning af tildelinger tolkes værdi først, hvorefter modtager tolkes med en speciel form for kald der søger efter tildelingsregler eller tildelingsfølsomme indbyggede operationer.
Hvis der er hale på tildelingen, bortkastes resultatet af tildelingskaldet, og hale tolkes og afleveres. Formen med hale svarer til:
(modtager := værdi) og: hale
Tildelingsregler har denne form:
{ modtager := værdi | handling }
Disse regler håndteres særskilt i regelsættene. Generelle regler som {x|...} kan ikke udløses af tildelingerne, og tildelingsreglerne udløses ikke af normale kald, heller ikke hvis kaldet har samme form som en tildeling..
PILS parseren genkender ikke tildelingerne som sådan; de indlæses og repræsenteres som sekvenser der slutter med en grådig node, og repræsenteres som:
:hvem (modtager) .kald (:= værdi)
:hvem (modtager) .kald (:= værdi; hale)
De følgende koventioner gælder kun i udtryk der ikke er i konstantklammer [] .
Når værdien af et navngivet ben er et kald af benets navn, kan værdien udelades.
Dette bruges i pasformer såvel som i almindelige udtryk, i det meget almindelige tilfælde hvor et bens navn bindes til dets værdi, dvs. for navngivne parametre.
Dette gælder også for forbenene, men ikke for halerne.
besked: . er det samme som besked: .besked besked
Et skjult ben bruges tit med :ok -ansvarstageren i simple udtræksregler. En nodes hale kan udtrækkes med denne regel:
{* ;: hale|:ok hale}
eller kortere:
{* ;: ok|:ok} hvilket er det samme som {* ;: ok|:ok ok}
Hvis værdien af et ben indledes med et kald til benets navn, kan dette kald skrives som et friluftspunktum.
Denne regel lægger 1 til sit argument:
{ok|:ok . + 1}
Forben kan prikkes – her kræves ikke mellemrum mellem punktummerne:
besked: .. "!" er det samme som besked: .besked (besked) "!"
En regels pasform kan indledes med et punktum, hvilket gør at det følgende navn eller frase ikke indlægges i et kald. Det letter skrivningen af parameterløse operationer:
{ .navn | ... }
er nemmere at skrive end
{ [navn] | ... }
Eb sekvens af form
afslutning .(uafsluttet-udtryk)
læses som:
uafsluttet-udtryk afslutning
hvor afslutning læses med præcedens som en sekvens, der gerne består af en lang række operationer. Når uafsluttet-udtryk parses og ) nås, indsættes afslutning som en enhed.
a b .(c d:) er det samme som: c d: a b
En kuperet form findes for etbenede handlingsnoder, omsvøb og dobbelt-omsvøb:
udtryk .:navn er det samme som :navn udtryk
udtryk .:: er det samme som :: udtryk
Ansvarshenviseren :kræv og listebyggeren :liste skrives gerne på denne form:
udtryk .:kræv er det samme som :kræv udtryk
(... liste := ...) .:liste er det samme som :liste (... liste := ...)
Halekrøller kan bruges til at give lange og komplekse udtryk en slags fortællende form: man kan skrive et udtryk og teste det, og derefter indlejre det i en node eller kontrolstruktur ved at sætte en halekrølle i enden, uden at ændre det allerede skrevne.
Halekrøllen er dels inspireret af vildsvinene ved Moesgård, dels af denne konstruktion der er ganske almindelig i talt dansk selvom visse skolelærere anser det for noget griseri:
Ting små grisebasser elsker at rode rundt i
Strengt taget skulle ting stå sidst, men talesproget tillader at vi trækker det vigtige frem, og det samme gælder til en vis grad PILS.
PILS understøtter 3 former for kommentarer:
! kommentar (linjen ud, til første CR eller LF)
!! kommentar (kan strække sig over flere linjer) !!
:- kommentardata; udtryk
! og !! -kommentarerne behandles som mellemrum ved parsning; :- kommentarer parses som handlingsnoder og gør det muligt at indlægge kommentardata i udtryk og pasformer.
De svarer løseligt til // kommentar, /*kommentar*/ og #pragma i C++.
De fleste indbyggede operationer er udformet som objektkald der får særbehandling af fortolkeren. De repræsenteres af noder af form :hvem .kald .
De allerfleste af disse operationer virker kun når de udføres direkte, ikke gennem konstruktioner som the (argument) kald (objekt) . Dog virker udtræk af listeelementer ved nummer samt sprogobjekternes metoder også ved indirekte kald.
Bemærk forskellen: Dette er et direkte kald:
{.kliche|:ok [k]} kliche giver [[|handling]|hvor|regelsæt]
Den indbyggede operation kliche udtrækker klicheen af den node der udgør det bundne regelsæt.
Dette er et indirekte kald og udløser .kliche -metoden i regelsættet:
[kliche] kald {.kliche|:ok [k]} giver [k]
Alle talberegninger udføres med flydende tal, de såkaldte doubles (typisk 64 bits med 52 bit binær mantisse, men det kan afhænge af implementationen). Resultatet af alle beregninger prøvekonverteres til et 32 bit heltal med fortegn og tilbage til flydende tal. Hvis dette resultaterne i det samme flydende tal, genkendes tallet som heltal – det er ikke muligt i PILS at skelne mellem det flydende tal 3,0 og heltallet 3. Både heltal og ikke-heltal pakkes ind i unikke objekter, men heltallene fra 0 til 216-1 har præfabrikerede objekter, til fordel for indekseringsberegninger.
Halløjet med fortolkning, indpakning og opslag i konstanttabellen gør at talbehandling er forholdsvis langsom i PILS. Dette er et valg der er truffet i designet: hurtig søgning og genkendelse af data af blandet struktur anses for vigtigere end hurtig talbehandling.
Uendeligheder, tal der ikke er tal osv. understøttes ikke af PILS, og håndteringen af dem er ikke testet særlig grundigt..
Operationer på to tal er
+ addition
- subtraction
* multiplication
/ floating point division (even når supplied with integer argumenterne)
\ integer division
% modulo
Bemærk: alle disse operatorer har samme præcedens: 1 + 2 * 3 giver 9 , ikke 7 .
Foranstillet - (minus) gør som forventet: ;x 7; - x returns -7 .
abs giver den absolute værdi
afrund giver den nærmeste heltallige værdi, idet eksakte halve rundes væk fra 0
afkort giver den nærmeste heltallige i retning mod 0
Operationerne afrund og afkort kan bruges med enhedsangivelse:
x afrund 10
afrunder x til nærmeste hele 10'er.
Disse efterstillede operatorer er lånt fra C++ (altid med double-argumenter):
sin cos tan asin acos atan kvr log exp
(kvr er den danske udgave af sqrt, kvadratrod eller square root.)
Sammenligningsoperatorerne = <> < <= > >= giver 1 hvis sammeligningen er sand, 0 hvis den er falsk; PILS har ingen særlig type til sandhedsværdier.
PILS fosøger at skrive tal så genparsning vil resultere i nøjagtig samme tal, men det virker desværre ikke altid for tal med store eksponenter.
Formateringsstrenge understøttes ikke i PILS.
Almindelige regneoperationer kan bruges på tidsstempler og varigheder i disse kombinationer:
tidsstempel + varighed
datering + varighed
varighed + tidsstempel
varighed + datering
varighed + varighed
tidsstempel - tidsstempel
tidsstempel - varighed
datering - datering
datering - varighed
varighed - varighed
varighed * tal
tal * varighed
varighed / tal
varighed / varighed
varighed \ varighed
tidsstempel afrund varighed
datering afrund varighed
varighed afrund varighed
tidsstempel afkort varighed
datering afkort varighed
varighed afkort varighed
Systemfunktionerne nu og tidsstempel henter begge den aktuelle systemtid, men tidsstempel opjusterer værdien hvis det er nødvendigt for at undgå at give samme værdi flere gange. Denne opjustering har ingen indflydelse på nu -funktionen.
s utf-8 konverterer mellem utf-8-kodede tekststrenge og lister af unicode-tegnværdier som heltal.
s utf-16 konverterer mellem utf-16-kodede tekststrenge med byte-order-mark og tegnværdi-lister
s utf-16le do, little-endian utf-16 uden byte-order-mark
s utf-16be do, big-endian
s bytes konverterer mellem tekststrenge og lister af byte-værdier 0 – 255.
Dette sammenstykker 2 tekststrenge:
(s) (t)
Dette sammenstykker en liste af tekststrenge ss med tekststrengen t som adskillelse:
ss splejs (t)
Dette splitter en sammenstykket tekststreng s i en liste, idet der klippes hvor t forekommer.
s split (t)
Erstatningsoperatoren
s *=* ss
forudsætter at ss er en liste med et lige antal tekststrenge, der opfattes som erstatningspar. For hver plads i s gennemløbes parrene i ss i den orden de står, og hvis et af dem passer, udføres erstatningen og søgningen fortsætter efter den erstattede tekst.
Start/slut-erstatningsoperatoren
s (<)$*=* ss
erstatter starten hhv. slutningen af s og udfører aldrig mere end en erstatning.
Tekststrenge kan sammenlignes med operatorerne = <> < <= > >= . Som altid tester = og <> for identitet; den unikke repræsentation af konstanter sikrer at ens tekststrenge er identiske. De andre operatorer sammenligner byte-vis, uden hensyn til alfabetiseringsregler. Sammenligninger der ikke skelner mellem små og store bogstaver, må foretages ved at bruge småt -operationen på begge operander først. Sortering efter nationale alfabetiseringsregler kan ske ved at bruge orden -operationen med en funktion der genererer kollateringsnøgler – dvs. tekststrenge der ved rå sorteringer havner i den orden der ønskes. For at lave dansk alfabetisering skal der således bruges en kollateringsfunktion der oversætter æ, ø og å til stigende værdier – hvis der sorteres uden kollateringsfunktion, bliver rækkefølgen å, æ, ø, idet tegnkoderne ligger i denne rækkefølge.
En tekststreng kan omsættes til store eller små bogstaver med operatorerne stort og småt :
s stort
s småt
Denne operation skifter første tegn i teksten til stort, mens resten ikke ændres:
s titel
Disse operations er udføres af kode der er genereret ud fra unicode-tabellerne og arbejder direkte på utf-8, og vil fungere med de fleste sprog.
Men desværre er tyrkerne ikke begejstrede for detteher:
"istanbul" titel giver "Istanbul"
Operationen må suppleres med en erstatningsoperation for at give det korrekte resultat:
"istanbul" $*=* ["i", "İ", "ı", "I"] titel giver "İstanbul"
İstanbul-ekspressen er et fif der gør det lidt lettere for tyrkerne.
Modulet [===,] som er tilgængeligt fra alle normale PILS moduler, definerer disse erstatninger for de indbyggede operationer, idet skifteoperationerne kombineres med passende erstatninger:
{ tekst . ([stort] // erstat) | :prøv tekst *=* erstat .:kræv stort }
{ tekst . ([småt] // erstat) | :prøv tekst *=* erstat .:kræv småt }
{ tekst . ([titel] // erstat) | :prøv tekst $*=* erstat .:kræv titel }
Ved at erstatte den sædvanlige standard-ordvælger i sproget med en version der oversætter stort , småt og titel som følger:
{} **=** ;[[?:?]:]
[ [?:?]:
."stort" [stort|"i", "İ", "ı", "I"]
."småt" [småt|"I", "ı", "İ", "I"]
."titel" [titel|"i", "İ", "ı", "I"]
]
og bruge dette sprog til at parse vores testudtryk, får vi:
"istanbul" titel giver "İstanbul"
Således kan omskiftningen mellem små og store bogstaver konfigureres i sprogobjektet.
Tekstsplitteren er en udvidet split -operation, konstrueret til hjælp for leksikalsk analyse af diveres programmeringssprog, men ganske brugbar til tekstanalyse i det hele taget. Tekstsplitteren er et alternativ til såkaldte regular expressions, der bruges til tilsvarende formål i mange andre programmeringssystemer.
En tekstsplitter er en nodekonstant der angiver en grammatik. Halen er en prioriteret liste af mål (på nørdsk hedder det nonterminaler, og de der er nævnt i halen er mulige startsymboler); de tilsvarende ben i noden beskriver veje til målene (på nørdsk kaldet produktioner). Vejene består dels af mål, dels af tekststumper (på nørdsk terminaler) samt nogle kontrol-instruktioner. Mål kan bruges rekursivt, også de mål der ikke findes i den prioriterede liste.
En vej kan bestå af flere alternative spor, der hver kan bestå af flere skridt og kontrolposter efter hinanden. Kontrolposter er skridt der efterprøver en betingelse uden at rykke frem.
s split
[ mål, ...
.mål vej
...
]
Dette udtryk vil gennemarbejde tekststrengen s og levere en liste af par (mål, tekststump) , hvor målene er de overordnede mål i splitterens hale, og tekststumperne er de klip af s der førte til målene. Når ingen mål kan nås fra en position i tekststrengen, dannes et par (, enkeltbyte-tekststump) idet der rykkes frem.
Tomme veje afvises på topniveau og af gentagerne [*: ...] og [+: ...] for at undgå at analysen kører i ring, men i andre sammenhænge er de tilladt.
Gyldige skridt er:
mål – henviser til et mål der skal indgå i sporet
1 – joker, går en enkelt byte frem
"a-z" – en tekststreng på 3 bytes, passerer en byte der skal ligge i området
enhver anden tekststreng – skal passe præcist
[*: vej] – så mange gennemløb som muligt af vejen, eventuelt ingen
[+: vej] – så mange gennemløb som muligt, mindst et
Gyldige kontrolposter er:
[-: vej] – den angivne vej må ikke ikke være farbar
[/: mål] – det sidste hovedmål skal være det angivne
[/: mål, ...] – ...eller et af dem. $ angiver at indeværende topmål er det første.
Bemærk at lister bruges i to niveauer, med forskellig betydning:
spor, ... – et af sporene skal være farbart
Hvert spor kan bestå af flere skridt der skal følge efter hinanden
For tydelighedens skyld bør de spor der udgør en vej, adskilles af kommaer, mens spor der består af flere skridt, skrives på kort listeform, uden kommaer.
Hvor der kun er et enkelt spor som består af flere skridt, skal sporet afsluttes med et komma for at gøre det til en liste af spor, ellers vil hvert skridt blive opfattet som et spor – jf. .hexadecimal herunder.
Denne tekstsplitter genkender hexadecimale tal:
s split
[ hexadecimal,
.hexadecimal "0x" [+: hexdigit],
.hexdigit "0-9", "A-F", "a-f"
]
Dette splitter s i enkelte utf-8-tegn (gyldige) og klumper af ikke-utf-8-konforme data:
s split
[ gyldig skidt
.gyldig ""00"-"7f, ""c0"-"df x, ""e0"-"ef x x, ""f0"-"f7 x x x
.x "80"-"bf
.skidt [+: [-: gyldig] 1,]
]
Instruksen -: kan bruges til test som disse:
[-: 1] – slutningen af teksten
[-: -: instruks] – snydekig fremad, instruks skal lykkes men medregnes ikke
Operationerne i dette afsnit virker på tekststrenge såvel som lister. Tekststrenge bearbejdes byte for byte, så visse operationer kan give uventede resultater med utf-8 bytesekvenser.
Tekststrengs- og listehhåndtering er optimeret så oprettelse af mellemresultater undgås når det er muligt. Disse optimeringer fungerer ikke når mellemresultater bindes til variable – hvis du har hastighedsproblemer med disse operationer, så brug sammenkædede operationer når det er muligt.
I det følgende betegner s og t tekststrenge eller lister, n et ikke-negativt heltal, og op en operator. Et (<) foran en operator – som (<)+# – betyder at operator +# har en dobbeltgænger <+# der arbejder i modsat retning, idet positioner regnes fra tekststrengens eller listens slutning.
s . n – n'te byte/element af s idet der tællles fra 1, svipser hvis ikke 1 <= n <= s antal .
s antal giver antallet af elementer i s. ( s utf-8 antal tæller tegnene i en tekststreng.)
s antal (t) antal ikke-overlappende forekomster af t i s.
s vend – s bagfra. Brug s utf-8 vend utf-8 for at vende en tekst tegnvis.
s (<)+# n – de første n elementer af s; svipser hvis n > s antal .
s (<)-# n – s uden de første n elementer; svipser hvis n > s antal .
s (<)++# n – de første n elementer af s, eller hele s hvis n > s antal .
For lister udføres ++# på samlebånd; så vidt muligt beregnes kun de første n elementer af s.
s (<)--# n – s uden de første n elementerne; "" hvis n > s antal .
I det følgende angiver (<)(+#/-#)op at op har 2 fætre
s +#op t = s +# (s op t)
s -#op t = s -# (s op t)
og alle de tre har bagvendte dobbeltgængere <op <+#op <+#op
Disse kan uden videre bruges med utf-8 multibyte-tegn:
s (<)(+#/-#)=* t – længden af s til og med første forekomst af t, eller 0 hvis t ikke findes i s.
s (<)(+#/-#)^* t – længden af s indtil første forekomst af t, eller s antal hvis t ikke findes.
s (<)(+#/-#)$* t – længden af t hvis s begynder med t, ellers 0 .
Disse kan give uventede resultater hvis t indeholder utf-8 multibyte-tegn.
s (<)(+#/-#)#* t – længden af sammenfaldende start af s og t.
s (<)(+#/-#)~* t – længden af s til med sidste element af en spredt forekomst af t, eller 0 .
s (<)(+#/-#)+* t – antal begyndende elementer i s der også findes et sted i t.
s (<)(+#/-#)-* t – antal begyndende elementerne i s der ikke findes i t.
Som eksempel på hvordan de kombinerede operatorer virker, er her et udtryk der giver navnedelen af et filnavn idet stien og filtypen klippes fra, hvor både \ og / opfattes som sti-separatorer:
fn <+#-* "\/" <-#=* "."
<+#-* operatoren er sikker at bruge selvom filnavnene kan indeholde utf-8 multibyte-tegn, idet \ og / er ASCII-tegn og disse aldrig forekommer i utf-8 multibyte-tegn.
De følgende afsnit beskriver operationer der er specifike for lister.
De indbyggede listeoperationer i PILS virker efter samlebåndsprincippet: i stedet for at konstruere mellemresultater som lister sendes elementerne videre enkeltvis til efterfølgende listeoperationer. Efter den sidste listeoperation opsamles elementerne i en intern struktur, og listen konstrueres når alle elementer er behandlet.
I det følgende er m og n heltal, s og t er lister hvis ikke andet er nævnt.
listevis og enkeltvis er bekvemmelighedsoperationer til situationer hvor der valgfrit kan bruges et enkelt element eller en liste, typisk når der skal angives en eller flere parametre til en funktion.
s listevis er det samme som s kald {e|:ok e,} {& ok|:ok}
Hvis s er a liste, ryger den direkte igennem, ellers indlægges den som eneste element i en liste.
s enkeltvis er det samme som s prøv {ok,|:ok}
Det modsatte af listevis : hvis s er en liste med et enkelt element, udtages det, alle andre værdier videregives bare..
s & t sammenstykker s listevis og t listevis .
s først (e) indskyder elementet e foran elementerne i s, til brug for fold -operation
Operationerne op og ned danner lister af stigende og faldende heltal:
m op (n) m, m + 1, m + 2 ... n eller [] hvis n < m
m ned (n) m, m - 1, ... n eller [] hvis n > m
n op er det same som 1 op (n)
n ned er det same som n ned 1
Lister kan splittes op i mindre lister af en given længde:
s split (n)
s split 2 splitter s i par.
En liste af lister kan samles til en enkelt liste:
s splejs
Eventuelle elementer af s som ikke er lister, medtages i den splejsede liste.
Lister kan opbygges med listebyggeren:
:liste ... liste := værdi ... ...
:liste [mærkat]; ... liste [mærkat] := værdi ...
Dette danner en liste af alle de tildelte værdier. Den afmærkede form giver kontrol over indlejrede listebyggere.
Listebyggere skrives tit som halekrøller: (... liste := værdi ... ...) .:liste
Lister kan filteres med de følgende operationer. Filtrene er typisk regelsæt der ikke behøver parenteser.
s hver (filter)
filter -funktion prøves på alle elementer af s efter tur. Resultaterne sendes videres som en liste, idet svipsere springes over.
s undtagen (filter)
Som hver, men videresender kun de elementer hvor filteret svipser. Resultaterne fra filteret bortkastes.
s benene (tildelings-filter)
Som hver, men for hvert element e på position n (talt fra 1 som altid) prøves tildelingen n := e i filteret, i stedet for bare e.
s enhver (filter)
Som hver , men kræver at filteret behandler alle elementerne.
s find (filter)
Som a hver (filter) 1 men hurtigere – giver det første filterede element, svipser hvis ingen elementer slipper igennem filteret.
Denne operation fjerner dubletter fra en liste, så kun første forekomst af hver værdi slipper igennem.
s forskellig
Eller baseret på en nøglefunktion:
s forskellig (filter)
Filteret prøves på alle elementer i s. Hver gang det giver en konstant der ikke er brugt før, sendes det originale element igennem. Hvis filteret giver node ;navn .værdi og navn er en ubrugt konstant, sendes værdi videre i stedet for det originale element.
Denne operation folder en liste sammen i en tilstand, som returneres.
s fold (tildelings-filter)
Listen skal have mindst et element. Det første element bruges som starttilstand, typisk angivet som:
s først (starttilstand) fold (tildelings-filter)
For alle efterfølgende elementerne e skal assign-filter behandle tildelingen {tilstand := e|...} , og tilstanden sættes til resutatet heraf. Når alle elementer er behandlet, returneres sluttilstanden.
Foldning kan kombineres med en listebygger hvis man har brug for filtrering med tilstand, som vist i dette eksempel:
["Rene", han, "John", "Peter", hun, "Jane", "Susan"]
først [ukendt] fold
{ køn := $ navn | :ok liste := køn, navn; køn }
{ ? := / køn | :ok køn }
.:liste
Resultatet er:
[ukendt "Rene", han "John", han "Peter", hun "Jane", hun "Susan"]
( $ og / er typetjek, som forklaret i kapitlet om pasformer.)
En liste kan sorteres efter nøgler:
s orden (nøgle-funktion)
s orden [ok] (simpel sortering med triviel nøglefunktion)
Det kræves at nøglefunktion virker på alle elementerne i s. Resultatet er kopi af listen, sorteret efter nøglerne. Tal og tidsstempler sammenlignes numerisk, tekststrengene sammenlignes byte for byte uden hensyn til alfabetiseringsregler. Alfabetisk sortering opnås ved at bruge en nøglefunktion der genererer kollateringsnøgler. Sortering efter flere nøgler kan udføres ved at lade nøglefunktionen danne lister; første element i listen er det mest betydende.
Udtræk af et enkelt element ud fra en nøgles størrelse kan ske med:
s mindst (nøgle-funktion)
s størst (nøgle-funktion)
Simpelt udtræk af det mindste eller største element kan ske med en triviel nøglefunktion:
s mindst [ok]
s størst [ok]
Dette eksempel udtrækker den længste tekststreng i en liste::
s størst {? $ ok|:ok}
Hvis den største nøgle findes for flere elementer, udtrækkes det første.
Gruppering af data efter nøgler:
s grupperne (nøgle-funktion)
nøgle-funktion prøves på alle listeelementerne; den bør enten give et konstant navn eller en node ;navn .værdi hvor navn er en konstant nøgle, eller svipse; alt andet får operationen til at fejle.
Operationen danner en node som for hver forskellig nøgle har et ben med nøglen som navn og en liste af de elementer der gav denne nøgle som værdi; men for de elementer hvor nøglefunktionen giver en ;navn .værdi -node, indlægges værdi i stedet for det originale element.
grupperne: .navn værdi-liste ...
Hvis der ingen nøgler dannes, giver operationen den tomme liste [] .
Dette eksempel:
15 op grupperne {n|:ok {} skriv (n) antal}{13|?}
grupperer heltallene fra 1 til 15 efter antal cifre, idet 13 udelades. Resultatet er:
[grupperne: .2 10 11 12 14 15 .1 1 2 3 4 5 6 7 8 9]
For hver nøgleværdi opregnes værdierne i samme orden som i den original liste, men nøglernes orden er vilkårlig, som det altid er tilfælde med benene i en node.
Hvis der kun er brug for en enkelt repræsentant for hver nøgleværdi:
s første (nøgle-funktion)
s sidste (nøgle-funktion)
virker som grupperne , men opsamler kun det første/sidste element for hver nøgleværdi, i stedet for en liste af alle elementerne.
s eneste (nøgle-funktion)
som første , men fejler hvis en nøgleværdi forekommer mere end én gang
s foldene (kombineret-nøglefunktion-og-tildelingsfilter)
som første , men når en brugt nøgle kommer igen, kræves der at den den ny værdi kan foldes med filterets tildelingsregler, og resultatet heraf huskes nu som nøglens værdi. Hele udtrykket svipser hvis en foldning svipser.
Her er et eksempel der tæller en vareliste sammen:
[æble 1, appelsin 2, banan 3, æble 4]
foldene {slags, antal|:ok ;navn slags .værdi antal} {a := b|:prøv a + b}
giver
[foldene: .æble 5 .banan 3 .appelsin 2]
idet værdier for æble foldes af tildelingsreglen som lægger dem sammen.
Operationen tværs vender en liste af lister af ens længder på den anden led:
[en 1, to 2, tre 3] tværs giver [en to tre, 1 2 3]
Noder med normale navne kan opbygges ved simpel tolkning:
hovsa: 1 + 2 giver nodekonstanten [hovsa: 3]
Handlings- og bindingsnoder opbygges på lignende vis med omsvøb:
: 4 + 5 + 6
er det same som
::hvem (:hvem 4 .kald +: 5) .kald +: 6
og giver 9 + 6 .
:: 4 + 5 + 6 giver : 15 – det dobbelte omsvøb genererer et enkelt omsvøb.
Tolkning af et regelsæt i omsvøb resulterer i et nyt regelsæt hvor alle pasformer og handlinger fra det oprindelige regelsæt er tolket.
En nodes ben kan behandles efter tur:
node benene (tildelings-filter)
For hvert ben forsøges tildelingen navn := værdi i filteret; resultaterne videregives som liste idet svipsere ignoreres.
En node kan kopieres uden et navngivet ben:
node uden (ben-navn)
Et ben kan tilføjes eller erstattes med en angivet værdi:
node flet (ben-name, ben-værdi)
To noder kan flettes sammen:
a flet (b)
Benene i b har forrang for ben i a med samme navnen. Nodenavnet fra b bruges hvis det ikke er [] ; i så fald bruges nodenavnet fra a.
Hvis et af argumenterne til flet er [] , er resultatet det andet argument, undtagen i dette tilfælde:
[] flet (ikke-tom-liste)
som svipser hvis ikke listen er et pair (navn, værdi) hvor navn er en konstant. I dette tilfælde resulterer operationen i en node pyt: .navn værdi
Denne konvention gør det nemt at bygge noder med flet, ud fra den tomme liste.
Noder eller klicheer kan udstyres med et andet navn:
a hoved (h)
De etbenede klicheer der bruges som navne i PILS, kan opbygges med operationen
ordklassenøgle // navnestreng
Operationen virker med alle konstanter, men bruges mest med tekststrenge.
Dyb erstatning i noder og lister kan ske med:
s **=** filter
Først får filter et forsøg på at behandle s i et enkelt kald. Hvis det lykkes, returneres resultatet uden videre behandling. Hvis det svipser og s er en node eller en liste, behandles benene, idet først tildelingen n := værdi hvor n er benets navn eller nummer, og derefter værdi prøves i filter; hvis begge forsøg svipser og værdi er en node eller liste, kaldes videre ud i denne. Hvor filterkaldene lykkes og resulterer i ændrede værdier, genereres nye noder eller lister hvis resultatet af operationen skal bruges.
Dette eksempel nulstiller alle tal undtagen pris-ben som fordoubles. Bemærk at tildelingen prøves først, uanset rækkefølgen af reglerne.
s **=** {[pris] := # ok|:ok . * 2} {#?|:ok 0}
Hvis operationen bruges i en sammenhæng hvor resultatet ikke bruges (typisk en listeygger), vælger PILS fortolkeren automatisk en hurtigere version der ikke genererer den omformede node.
Dette eksempel bygger en liste af alle nodenavnene i s:
s **=** {navn * ?|liste := navn} .:liste
Tip: hvis filteret kun består af tildelingsregler, kan det ikke behandle hele s i ét hug. Hvis den øverste regel (som er den der prøves sidst) har formen
{ ? := ok | :ok }
virker søgningen kun i et niveau.
PILS udtryk kan tolkes (en ekstra gang) med operatoren --- der kan bruges både mellemstillet og foranstillet:
--- e (kan også skrives e ---. ) tolker værdien af e i de aktuelle bindinger.
e --- c tolker værdien af e i bindingerne angivet ved c .
Udtryk kan citeres med :citat -sætningen:
:citat e eller, skrevet med halekrølle: e .:citat
Resultatet er e som det står, uden tolkning.
Bemærk: :citat -sætningen bruges sjældent i PILS. Der er ingen grund til at bruge den på konstante noder og lister.
Navne kan bindes til værdierne lokalt:
;navn værdi; hale
værdi is tolkes i den aktuelle kontekst k, og hale tolkes i konteksten ;navn v; k hvor v er den tolkede værdi.
Flere navne kan bindes med én bindingsnode:
;navn1 værdi1 .navn2 værdi2 ...; hale
Alle værdierne tolkes i den aktuelle kontekst k i vilkårlig orden. (Fremtidige versioner af PILS fortolkeren vil måske tolke dem samtidigt.)
Alle konstanter undtagen [] kan bindes som navne.
Et objekt – typisk angivet ved et regelsæt, her kaldet reglerne – kan indflettes i konteksten:
:brug reglerne;
udtryk
eller
udtryk
===
reglerne
De to former har same virkning: reglerne tolkes i den aktuelle kontekst og kombineres med denne til en kontekst hvori udtryk tolkes.
Et betinget udtryk har denne generelle form:
:hvis betingelse; udtryk .ellers ellers-udtryk
hvor .ellers -delen kan udelades:
:hvis betingelse; udtryk svarer til :hvis betingelse; udtryk .ellers []
En liste af betingelser kan bruges i stedet for en enkelt; den betragtes som opfyldt hvis alle betingelserne er opfyldt.
En betingelse kan have disse former:
udtryk (positiv betingelse)
udtryk => pasform (pasbetingelse)
udtryk +> pasform (positiv pasbetingelse)
En betingelse prøves ved at udtryk tolkes og værdien testes. For positive betingelse skal værdien være et positivt heltal, for pasbetingelser skal den passe i pasformen. Hvis pasformen indeholder variable, bindes disse, gældende både for eventuelle efterfølgende betingelser og for udtryk, men aldrig for ellers-udtryk.
Hvis en variabel optræder to eller flere gange i samme pasform, skal de tilsvarende værdier være identiske, men hvis samme variabel optræder i pasformer for forskellige betingelser, oprettes nye bindinger, hvoraf den sidste vil skjule de foregående.
For positive pasbetingelser skal værdien være et positivt heltal og passe i pasformen, der bør være en simpel variabel eller ? . Positive pasformer bruges typisk med søgeoperationer, til at teste for en træffer og binde dens position.
Hvis en udtrykket i en positiv betingelse fejler eller svipser, betragtes det som en fejl, hvorimod pasbetingelser og positive pasbetingelser stiltiende afvises.
Et kald kan forsøges med prøv operationen:
(argument) prøv (filter)
Det virker som kald -operationen, men hvis kaldet svipser, videregives argumentet som det er.
Gentagelse kan udføres med gentag operation:
(argument) gentag (filter)
Dette virker som prøv -operation, men filter anvendes igen og igen indtil det svipser, hvorpå den sidste værdi returneres.
Dette eksempel lister heltallene fra 1 til 10 (vi glemmer lige at 10 op er en nemmere måde.):
1 gentag {n <= 10|:ok liste := n; n + 1} .:liste
Bearbejdning af en konstant så længe det ændrer detn:
(argument) igen (filter)
argument tolkes og skal give en konstant, ellers fejler operation. Derpå anvendes filter igen og igen, hvilket skal resultere i konstanter, ellers fejler operationen. Når resultatet er det samme som den sidste værdi, returneres det.
1 igen {x|:ok 1 / x + 1}
svarer til:
1 gentag {x|;x' 1 / x + 1; :hvis x' <> x; :ok x'}
og giver 1.618033988749895 på en x86 (operationen vil måske fejle på maskiner hvor flydende tal håndteres anderledes).
Som beskyttelse mod uendelige løkker har igen -operatoren et simpelt indbygget tjek mod cykliske sekvenser: ved iteration nummer 64, 128, 256, 512, 1024... opsamles værdien og sammenlignes med de the følgende værdier. Hvis den kommer igen efter mindst én mellemliggende værdi, antages det at der er tale om en cyklus, og operationen fejler. I eksempler som ovenstående kan afrundingsfejl bevirke at værdien ender med at skifte mellemto eller flere værdierne – uden tjekket mod cykliske sekvenser ville det resultere i en uendelig løkke.
:udgang -sætningen ligner :liste -sætningen:
:udgang ... udgang := værdi ... ...
:udgang [mærkat]; ... udgang [mærkat] := værdi ...
Ligesom :liste -sætningen skrives :udgang -sætningen gerne med halekrølle:
(... udgang := værdi ... ...) .:udgang
Hvis tildelingen udføres, afleveres værdi straks som resultat af :udgang -sætningen.
To udtryk kan kombineres med operatorerne og , eller og pyt :
e1 og: e2
e1 tolkes. Hvis det lykkes, bortkastes resultat og e2 tolkes. Hvis det fejler eller svipser, gælder dette for hele udtrykket, og e2 ignoreres.
e1 eller: e2
e1 tolkes og returneres hvis det lykkes. Hvis e1 fejler eller svipser, tolkes e2.
e1 pyt: e2
e1 tolkes og resultatet ignoreres, uanset om det fejler, svipser eller lykkes. Derefter tolkes e2.
Som alternativ til formen (objekt) (argument) kan objektkald angives med kald -operationen:
(argument) kald (objekt)
Operationen anvender objekt på argument .
Dette adskiller sig fra (objekt) (argument) på følgende måde:
Formen (objekt) (argument) kan genkende argument som en indbygget operation. Det sker ikke med den eksplicitte kald -operation.
Hvis objektkaldet svipser, vil (objekt) (argument) formen danne en node :hvem objekt .kald argument og kalde den i den aktuelle kontekst, så bagved liggende regler får mulighed for at håndtere operations der svipser. Det sker ikke med den eksplicitte kald -operation.
I den aktuelle version af PILS fortolkeren er tolkningsrækkefølgen i begge tilfælde den omvendte af den tekstlige rækkefølge – i (objekt) (argument) -formen tolkes argument først, i det eksplicitte kald tolkes objekt først.
Omkald - noder af form
kald: argument
har særlig betydning når de bruges som objekter: de udskifter argumentet med objekt. Brugen af det kan illustreres med dette udtryk:
liste hver {emne|:prøv emne pris}
Udtrykket giver en liste af priserne fra de listeemner der har en pris. Men en nemmere og hurtigere måde er:
liste hver [kald: pris]
Dette virker som følger: hvert emne fra liste forsøges behandlet af objektet [kald: pris] , som håndterer dem ved at prøve at kalde theres pris -metoder.
To objekter a og b kan kombineres til et bredt bundt med +++ -operatoren:
base +++ udvidelse
Når det udvidende bundt kaldes, kaldes udvidelse først. Hvis det svipser, kaldes base.
Bred bundtning svarer til subklasser i traditionel objektorienteret programmering, men bundtning er dynamisk og sker direkte på objekter, idet PILS ikke har klasser.
Hvis udvidelse er [] , giver +++ -operationen simpelthen base .
Et smalt bundt kan oprettes med -> -operatoren:
a -> b
Når dette bundt kaldes, kaldes a først, og hvis det lykkes, kaldes b på resultatet.
Smalle bundter kan ikke behandle tildelinger.
Bemærk: -> -operator har præcedens som sammenligningsoperatorne og samles fra højre. Præcedensen afledes af det sidste tegn i operatoren.
Normalt vil :hvem -binderen binde til hele bundtet hvis den bruges i et objekt i et bundt, således at bundtets enkelte objekter kan kalde metoder på det samede bundt, svarende til virtuelle kald i traditionel objektorientering. Et objekt kan isoleres ved at indlægge det i en hvem: -node:
hvem: objekt
Det bevirker at :hvem-bindere i objekt bindes til objekt alene, uanset om det indgår i bundtning.
Til brug for programmeringssystemets indpakning af platformspecifikke objekter findes en udvidet form:
hvem: . hvem-binding; objekt
hvor hvem-binding specifikt angiver værdien af :hvem -bindinger i objekt.
Pasformer bruges i regler og pasbetingelser; de angiver en struktur som data skal indpasses i. I det følgende gennemgås betydningen af forskellige PILS udtryk når de bruges i pasformer.
En konstant kan stå for den selvsame konstant og intet andet.
En variabel – typisk skrevet som et regulært navn, repræsenteret ved en node :kald [navn] – er en stedfortræder der kan stå for hvad som helst og binder det til navnet. Hvis et navn bindes flere gange i samme pasform, skal værdierne være identiske, og der genereres kun én binding.
Jokeren ? eller i omstændelig notation: :kald [] står for hvad som helst og ignorerer værdien.
For alle andre konstanter end [] behandles :kald konstant som en variabel.
OBS: denne regel:
{ fac (0) | :ok 1 }
gør ikke hvad det umiddelbart ser ud til. Frasen fac (0) læses som :kald [fac: 0] der kan stå for hvad som hels t og binder navnet [fac: 0] til værdien.
En liste som pasform står for en liste af samme længde, med elementer der passer til pasformens elementer.
En node som pasform står for en node med samme nodenavn og ben med samme navne og værdier der passer til pasformens tilsvarende ben – undtagen i de tilfælde der er angivet i de følgende afsnit. Som altid er benenes rækkefølge irrelevant.
For konstante noder og lister er kravet om samme struktur og passende værdier ensbetydende med at der skal være tale om den eksakt samme konstant. Det testes med en simpel og hurtig adressesammenligning, uanset konstantens størrelse og art.
Når handlingsnoder bruges i pasformer og skal tages for pålydende – altså, dække over en tilsvarende handlingsnode – skal de sættes i omsvøb.
En pasform i omsvøb:
: pasform
er det samme som hvis pasform stod der direkte, bort set fra at eventuelle særlige regler for node-pasformer ignoreres. Dette muliggør angivelse af nodeformer som ellers ville have speciel betydning. Det følger heraf at et dobbelt omsvøb i pasformen
:: x
står for et enkelt omsvøb i data – det yderste omsvøb betyder at det inderste omsvøb behandles som en almindelig node og står for en node af samme struktur, altså et omsvøb.
Disse operationer angiver typetjek; s er typisk en variabel eller ? .
# s – tal
% s – heltal
+ s – heltal > 0, det samme som % s > 0 , se nedenstående
s antal – heltal >= 0, det samme som % s >= 0
s tid – tidsstempel
s varighed – varighed
s datering – datering
$ s – tekststreng
+$ s – ikke-tom tekststreng, det samme som s $ (? > 0) , se nedenstående
++$ s – tekststreng med 2 eller flere bytes, det samme som s $ (? > 1)
& s – liste
+& s – ikke-tom liste, det samme som s & (? > 0)
++& s – liste af length > 1, det samme som s & (? > 1)
* s – node
/ s – kliche
= s – vilkårlig konstant
s benene – node eller liste
Konstant-typetjekket = kan kombineres med andre typetjek:
= & s – liste konstant
Platformsbindingerne kan definere yderligere typetjek for systemobjekter.
Søgeoperatorerne (<)=* (<)^* (<)$* (<)#* (<)~* (<)+* t (<)-* kan bruges på direkte angivne tekststrenge og listekonstanter. Det resulterende heltal kan ikke udtrækkes men skal være > 0 .
Dette står for en tekststreng der begynder med "http://" og slutter med "/" :
s $* "http://" <$* "/"
Dette står for en liste der skal indeholde navnet [sprog] :
s =* [sprog,]
(Bemærk kommaet; søgeoperationerne bruger lister, ikke enkelte elementer.)
Klicheer med enkelt attribut som [ordklassenøgle|navnestreng] kan splittes i en pasform som:
ordklassenøgle // navnestreng
idet ordklassenøgle og navnestreng kan stå for vilkårlige konstanter, men typisk er det tekststrenge.
Denne pasform står for et navn i standard-ordklassen, idet navn bindes til hele navnet og navnestreng til navnestrengen.
navn = ?:? // namestring
Opsplitning af klicheer med flere ben understøttes ikke; det er fravalgt fordi benenes udefinerede rækkefølge ville give diffuse regler.
Pasformer som alias = pasform kræver at data passer til både alias og pasform. Typisk er alias en variabel der bindes til en node eller liste som analyseres yderligere af pasform.
En værdi kan forudsættes forskellig fra en given konstant:
s <> konstant
eller sammenlignes med talkonstanter:
s op k
k op s
hvor k er en talkonstant og op en af operatorerne < > <= >=
Sammenligningerne kan skrives i kæde. Denne pasform står for et heltal i intervallet 10 – 20:
10 <= % x <= 20
Bemærk: kædeskrevne sammenligninger understøttes kun i pasformer. I beregningsudtryk vil en sammenligning som 10 <= x <= 20 give 0 uanset værdien af x .
Længden af lister og tekststrenge kan udtrækkes med disse udgaver af typetjekkene:
liste & længde
tekststreng $ længde
Specifikke krav kan stilles til længden ved at bruge sammenligninger. Denne pasform står for en tekst med mellem 5 og 10 bytes.
tekststreng $ (5 <= længde <= 10)
Specifikke listeelementerne kan udtrækkes ved indeksering med heltal.. Positive heltal udtrækker elementer talt fra starten af listen, som altid tælles der fra 1 . 0 udtrækker det sidste element, -1 det næstsidste osv..
Dette binder 3. element fra en liste til variablen e , idet resten af listen ignoreres:
(e) 3
Dette står for en liste q med identisk start- og slutelement e :
q = (e) 1 = (e) 0
Når navnløse noder (hvilket er det samme som noder med nodenavnet [] ) bruges som pasformer uden omsvøb, er nodens navn uspecificeret.
;x
står for en node med et x -ben og ingen andre ben, idet x bindes til benets værdi.
Nodens navn kan udtrækkes:
h ;x
Dette er som ;x men h is bindes til nodenavnet.
Brug af node-typetjekkeren * med en nodepasform åbner for noder med flere ben end de angivne:
* punkt: .x .y kan stå for noder som punkt: .x .y .z osv.
Pytter åbner for noder der mangler de angivne ben, idet pyttens konstanter bruges i stedet for de manglende ben:
(navn: .ben ...) pyt [.ben ...]
Dette kan kombineres med * -typetjekkeren for også at tillade ekstra ben:
(* navn: .ben ...) pyt [.ben ...]
I forbindelse med pytter behandles nodenavnet [] på lige fod med andre navne. Hvis alle de angivne ben er dækket af pytten, tillades selve nodenavnet som stedfortræder for en benløs node (idet benløse noder ikke eksisterer i PILS).
Pytter i pasformer er indført for at muliggøre automatisk udfyldning af manglende parametre.
Konstantudskiftere udfører simple udskiftninger før indpasning i den berørte pasform. Der er to varianter:
pasform kald [.nøgle erstatning ...]
Nøglerne er de eneste mulige værdier, og den tilsvarende erstatning indpasses i pasform.
pasform prøv [.nøgle erstatning ...]
Nøglerne udskiftes, men andre værdier passerer uændret igennem.
Konstantoversættere er blandt andet indført for at håndtere oversættelse mellem talkoder og symbolske navne ved systeminterfacing.
De fulde navne kan udtrækkes af fil- og mappeobjekter:
fil fil: filnavn
mappe mappe: sti
PILS udvidelser til systemobjekter kan udtrækkes:
systemobjekt når: pils-udvidelse
PILS er generelt et fortolket sprog, men alle pasformer kompileres til en mellemform kaldet quicksteps, som udføres betydelig hurtigere end fortolkede udtryk. Quicksteps opdaterer aldrig referencetællere, opretter aldrig objekter og søger aldrig i konteksten undervejs. Først når en indpasning er lykkedes, oprettes eventuelle bindinger og referencetællere opdateres.
Kompileringen er hurtig og sker automatisk når som helst pasbetingelser og regelsæt læses af parseren eller genereres ved tolkning af PILS udtryk.
Regelsøgning er optimeret ved indeksering af reglerne i et regelsæt. For at en pasform kan indekseres, skal yderste niveau være en fuldt specificeret liste eller node, eller en konstant.
Aliasser påvirker ikke indekseringen, så regler som { alias = indekserbar-pasform | ... } er indekserbare.
Tildelingsregler indekseres efter tildelingens venstreside og holdes adskilt fra de øvrige regler.
Indekseringen er usynlig for programmøren, når der ses bort fra udførelseshastigheden. Ved opbygning af store regelsæt, såsom tilstandsmaskiner for parsere, bør reglerne så vidt muligt være indekserbare, og ikke-indekserbare regler bør samles i klumper, helst øverst i regelsættet.
Når en regelhandling påbegyndes, er reglen uskyldig: det er ikke afgjort om reglen vil lykkes, fejle eller svipse. De ansvarsrelaterede konstruktioner der beskrives i det følgende, er kun gyldige i reglens uskyldige tilstand; hvis de bruges efter at ansvaret er taget, fejler de.
PILS har fem ansvarsforskydere:
:ok hale
:prøv udtryk
:kræv udtryk (skrives gerne som udtryk .:kræv )
:muligvis udtryk
:fejl udtryk
Ansvarsforskydernes leksikalske binding og deres tilknytning til specifikke operationer frem for kodeblokke muliggør en mere finkornet fejlhåndtering end try-catch -konstruktionen der bruges i mange udbredte sprog.
Ansvarstageren :ok er den almindeligste måde at få et resultat ud af en regel på. Ansvaret for kaldet tages, og udtryk tolkes og afleveres. Eventuelle ansvarsforskydere i udtryk fejler; fortolkeren har bortkastet den information der muliggjorde forskydning af ansvaret.
Konstruktionen svarer til return-sætningen i C og lignende sprog, men med garanti for haleudfladning.
Implementationsnote: Når :ok -ansvarstageren er den første og eneste handling bort set fra eventuelle forudgående bindere, bortoptimerer kompileren stakoperationerne til håndtering af ansvar. Hvis halen er en konstant, en variabel der er bundet af reglen, eller en simpel node- eller listekonstruktion baseret alene på konstanter og reglens egne variable, kompileres reglen helt igennem, og bindingsnoden bortoptimeres.
Ansvarsforskyderen :prøv forsøger at videregive ansvaret til det øverste niveau i udtryk, uden selv at tage ansvar. Hvis det øverste niveau i udtryk lykkes eller fejler, sker det samme for det originale kald, med haleudfladning hele vejen. Hvis det svipser, returnerer :prøv -konstruktionen den tomme liste. Reglen vil så svipse, hvis ikke en anden konstruktion tager ansvaret.
I modsætning til try -sætningen i C++ og lignende sprog gælder :prøv ikke for deludtryk der fejler eller svipser.
Ansvarsfralæggeren :kræv bruges til delresultater der er nødvendige men ikke tilstrækkelige for at reglen kan lykkes.
Hvis den yderste operation lykkes, gives den som resultat af :kræv -sætningen og indgår i det omgivende udtryk. Hvis den fejler, videregives til det originale kald. En svipser får en eventuel igangværende :prøv -sætning for same regel til at svipse og aflevere den tomme liste. Hvis ingen :prøv -sætning er aktiv, svipser reglen.
Hvis et krævet udtryk har deludtryk der kan fejle eller svipse, kan de dækkes af indlejrede :kræv -ansvarsfralæggere. Halekrølle-formen udtryk .:kræv er praktisk til dette brug.
Den forsigtige ansvarstager :muligvis tolker og afleverer udtryk , men tillader i modsætning til :ok indlejrede ansvarsforskydere, typisk :kræv , der har samme virkning som i en :prøv -ansvarsforskyder.
Ansvarsfralæggeren :fejl får det originale kald til at fejle med den angivne værdi.
Disse bindere:
:selv hale
:hvem hale
:hvor hale
:hvad hale
binder deres navne til data omkring kaldet, som følger.
selv binder til det bundne regelsæt hvor reglen optræder, og kun dette regelsæt.
hvem binder til det kaldte objekt, som kan være et bundt hvori det bundne regelsæt indgår. Hvis regelsættet ikke er bundtet, er hvem det same som selv .
For simple kald uden angivelse af objekt vil hvem binde til den kontekst hvorfra kaldet blev foretaget, dvs. det samme som hvor .
hvor binder til konteksten for det originale kald.
hvad binder til det udtryk der udløste kaldet.
Binderne selv og hvem bruges typisk når et objekt skal kalde sine egne metoder. Brug af hvem svarer i denne sammenhæng omtrent til OO sprogenes virtuelle kald.
Binderne hvor og hvad bruges til at stedfæste fejl. Når ansvaret for et kald sendes videre til et andet kald med :prøv eller :kræv , vil bindere i viderekaldet opføre sig som følger:
:hvor og :hvad binder til det original kald.
:selv og :hvem binder til viderekaldet.
Bindere og ansvarsforskydere findes i mærkede versioner til brug i indlejrede regler.
De mærkede bindere
:selv [mærkat]; hale
:hvem [mærkat]; hale
:hvor [mærkat]; hale
:hvad [mærkat]; hale
binder navn [mærkat] i stedet for navn .
De mærkede ansvarsforskydere
:ok [mærkat]; hale
:prøv [mærkat]; hale
:kræv [mærkat]; udtryk
:tak [mærkat]; udtryk
:fejl [mærkat]; udtryk
bruges med en mærkat:
:mærkat [mærkat]; hale
og tillader en indre regel at agere på vegne af en ydre regel:
{ ...
| ... :mærkat [mærkat]; ...
... { ... | ... :ok [mærkat]; ... }
}
Generelt bruges mærkater sjældent, men PILS redigeringen bruger dem til håndtering af syntaksfejl.
Den finkornede håndtering af ansvar gør det nemmere at sætte fingeren på en fejlkilde. Et af de største problemer ved løst typede sprog viser sig i situationer som den der her skitseres med et simpelt eksempel:
! Annas program forsøger at fordoble et tal ved hjælp af Bennys fordobler-bibliotek.
dobbelt "MMVII"
===
! Bennys bibliotek bruger Børges bibliotek:
{ dobbelt: x | :ok gang (x .med 2) }
===
! Børges biblioteksfunktion forventer talværdier, ikke tekststrenge med romertal:
{ gang: x .med | :ok x * med }
Når Anna kører sit program, vil operationen x * med svipse og udløse en fejlmeddelelse i Børges bibliotek, hvad der godt kan virke forvirrende på Anne, eftersom hun ikke aner at hun bruger Børges bibliotek igennem Bennys.
Halfdan Rasmussen har beskrevet det rammende:
Bennys bukser brændte,
Børge råbte: åh!
Børge havde nemlig
Bennys bukser på.
Traditionelt vil man i en sådan situation granske stakdump eller skridte igennem koden for at finde fejlkilden. Men i PILS kan man gennem hensigtsmæssig brug af ansvarsbegrebet typisk straks slå ned på fejlkinden.
Med ansvarsforskyderne :prøv og :kræv kan en biblioteksfunktion udføre specifikke operationer på vegne af kaldet af funktionen, så kaldet får skylden hvis noget går galt.
Når Børge bliver træt af brok fra folk der bruger hans biblioteksfunktion med forkerte parametre, laver han måske en revideret udgave der forholder sig pessimistisk til argumenterne og ikke stoler på at de har de forventede operationer. Anna vil nu have denne situation:
dobbelt "MMVII" ! Anna's application
===
{ dobbelt: x | :ok gang (x .med 2) } ! Bennys bibliotek
===
{ gang: x .med | :prøv x * med } ! Børges reviderede bibliotek
Nu får operationen gang (x .med 2) i Bennys bibliotek skylden. Benny kunne så revidere sit bibliotek, så Anna får denne situtation:
dobbelt "MMVII" ! Anna's application
===
{ dobbelt: x | :prøv gang (x .med 2) } ! Bennys reviderede bibliotek
===
{ gang: x .med | :prøv x * med } ! Børges reviderede bibliotek
Nu skydes skylden straks på kaldet dobbelt "MMVII" – og vi får udpeget den rygende tændstik i Annas hånd. Mysteriet er opklaret.
Ovenstående lille eksempel er kun til illustration af konceptet; til simple beregninger af den art vil et typetjek i pasformen være tilstrækkeligt. Men i situationer hvor parametrene er objekter der formodes at understøtte visse metoder, er typetjekkene ikke til megen hjælp, idet PILS ikke har klasser eller interfaces der kan testes mod. I stedet kan metodekaldene foretages med ansvarsfralæggelse, så det kald der har leveret parametrene, får skylden hvis disse ikke understøtter de forventede metoder.
PILS er indrettet så denne teknik stort set ikke kræver ekstra skrivearbejde.
PILS kernen understøtter udvidelse af datamodellen med konstanttyper, som kan behandles af parseren ved udvidelser af denne.
Når parseren møder en startkrølparentes { , spoler den frem idet der holdes styr på kommentarer, tekststrenge og indlejrede parenteser. Hvis den finder en slutkrølparentes } uden en mellemliggende bjælke | , prøves parserudvidelserne på de mellemliggende tegn.
De ekstra typer levers af platformsbindingerne og kan være forskellige for forskellige platforme.
Normalt er PILS data tilstandsløse og uforanderlige, men nogle få tilstandsbærende objekter er indført i sproget for at håndtere grænseflader til en foranderlig verden. De er udformet så risikoen for resurselækager er lille, ved specielle forholdsregler omkring de konstruktioner der gør det muligt at opbygge cirkulære strukturer.
En kanal er en nodekonstant af form
[kanal: nøgle]
hvor nøgle er en vilkårlig konstant. Operationen
kanal lyt (lytter)
opretter en kontakt, der i hele sin levetid forbinder lytter med kanal og ikke har andre funktioner. Den eneste måde kontakten kan afbrydes på, er at tabe alle referencer til den så den fjernes af PILS afrydningen.
Når en kanal udsættes for et kald, videresendes det til kanalens lyttere efter tur, idet de sidst indsatte lyttere prøves først, indtil en af dem tager ansvaret, som hvis lyttterne havde været bundtet med +++ -operatoren, bort set fra at :hvem binder til kontakten.
Som alle konstanter er kanaler unikke. Hvis et udtryk som (kanal: nøgle) udføres flere gange for samme nøgle, genbruges kanalen. PILS programmeringssystemet bruger kanaler til at holde redigeringer af PILS filer og moduler unikke, ved at bruge fil- og modulnavne i kanalnøgler.
Kanaler er trådsikre.
Når PILS bruges med et vinduesbaseret brugerfladesystem, vil vinduernes levetid generelt være styret af brugeren eller platformen, ikke af PILS. Sådanne objekter kaldes udefrastyrede, og PILS håndterer dem gennem indpakningsobjekter der håndteres som konstanter, med metoder som defineres af platformsbindingerne.
Huskere er udefrastyrede objekter der kan have tilknyttede PILS objekter og herigennem kan referere cirkulært til hinanden.
PILS bindingerne bruger platformens hændelses-håndtering (på nørdsk: event handling) til at holde styr på hvornår udefrastyrede objekter bliver nedlagt – når det sker, blænder PILS indpakningsobjektet og frigiver eventuelle tilknyttede data hvis objektet er en husker.
Data kan tilknyttes en huskekage og hentes igen:
huskekage husk . nøgle := værdi;
...
huskekage husk . nøgle
Værdierne kan overskrives, men nøglerne kan ikke slettes når de først er oprettet. Værdierne kan kun udtrækkes hvis man har de tilsvarende nøgler; der er ingen mulighed for systematisk gennemløb af huskekager.
Noder af form [strop: nøgle] kan bruges som en slags kanaler, men de kan kun have én lytter, og kun når de er hægtet på en eller flere udefrastyrede objekter. Hvis de udefrastyrede objekter nedlægges, taber stropperne straks hukommelsen.
Stropper bruges af programmeringsystemet til programstropper der hægtes på alle programmets vinduer. Nå alle de vinduer der hører til et program, taber programstroppen hukommelsen og frigiver dermed programmets resurser.
Ved opstart af programmer hæftes stropperne midlertidigt på universalnøglem-
Fil- og mappeobjekter giver adgang til simple operations på filer og filsystemer.
En mappe er det der på nørdsk hedder et directory.
Læsning og skrivning er kun understøttet som afsluttede operationer på hele filer. Fil- og mappeobjekter er ret beset kun indpakninger af navnene, med adgangsbillet filsystemet; konceptet åbne filer findes ikke i PILS.
Fil- og mappenavnene angives med fuld sti, med / som skilletegn på alle systemer, også MS Windows. Det sædvanlige MS Windows-skilletegn \ bruges ikke i PILS filnavne.
Mappenavne har altid et afsluttende / .
Funktionerne fil og mappe er metoder på universalnøglen og virker ikke uden den. Ved at skjule universalnøglen kan der konstrueres sandkasser hvor fremmed kode kan udføres med begrænset adgang til filsystemet.
fil (filnavn)
datafil (filnavn)
opretter et filobjekt – fil er den indbyggede funktion, datafil er en indpakning der bruges til dataafhængige moduler.
Filnavnet kan udtrækkes ned
fil navn
Filen kan læses og skrives i et hug:
fil tekst
fil tekst := tekst
Læsning og skrivning sker som rå bytes – eventuelle konverteringer udføres som særskilte operationer:
fil tekst bytes utf-8
indlæser en ANSI-kodet fil som en utf-8-kodet tekststreng.
fil tekst := tekst utf-8 bytes
skriver en utf-8-kodet tekststreng som en ANSI-kodet fil.
Filer kan manipuleres med disse operationer:
fil (filnavn) kopiér (nyt-filnavn)
fil (filnavn) flyt (nyt-filnavn)
fil (filnavn) slet
Resultat af kopiér og flyt er filobjektet for nyt-filnavn.
Eksistensen af filer kan testes med
fil ok
som giver fil hvis filen findes, og ellers svipser
fil læsbar giver 1 hvis læsning er mulig og tilladt, ellers 0 .
fil skrivbar tilsvarende for skrivning
fil antal giver filens størrelse i bytes
fil tidsstempel læser tidsstemplet for ændring af filen
fil tidsstempel (tid) sætter tidsstemplet for ændring af filen
Der er ingen specialiserede operationer til opsplitning af filnavne i dele; dette gøres nemt med de almindelige operationer på tekststrenge:
fn <-#-* "/" giver mappens navn
fn <+#-* "/" giver filnavnet uden mappe
fn <+#-* "/" <-#=* "." giver filnavnet uden mappe og type
fn <+#-* "/" <+#=* "." --# 1 giver filens type
fn <-# (fil navn <+#-* "/" <=* ".") giver filnavnet uden type
fn <+#-* "/" <-#=* "." giver filnavnet uden mappe og type
mappe (mappe-navn)
opretter et mappeobjekt. mappe-navn skal afsluttes med skilletegnet / og kan udtrækkes med:
mappe navn
Filer og undermapper kan udtrækkes:
mappe filerne giver en liste af filobjekter
mappe mapperne giver en liste af mappeobjekter
Filtrering kan udføres som separat skridt. – dette eksempel finder filer af type .pils :
mappe filerne hver {ok fil (? <$* ".pils")|:ok}
Dyb søgning i en mappe efter filer kan udføres med en rekursiv regel:
mappe kald {m|:hvem :ok m filerne & (m mapperne enhver (hvem) splejs)}
Tomme mapper kan oprettes og slettes:
mappe opret
mappe slet
Filtering af filer og mapper understøttes ved brugen af fil og mappe som operatorer i pasformer.
fil fil (fn) dækker over en fil idet fn dækker over navnet
mappe mappe (mn) dækker over en mappe idet mn dækker over navnet
Dette eksempel lister alle .pils filer i brugerens dokumentmappe med tidsstempler, idet mapper af navn backup og deres undermapper udelades
mappe (platform sti dokumenterne) kald
{ m mappe (?) | :hvem :ok m filerne & (m mapperne hver (hvem) splejs) }
{ ? mappe (? <$* "/backup/") | ? }
hver { fil fil (filnavn <$* ".pils") | :ok filnavn, fil tidsstempel }
Zipfiler kan læses med:
fil (zipfilnavn) zip
som giver en liste af lister (sti, data, tidsstempel) for alle pakkede filer i zipfilen.
En zipfil kan oprettes med:
fil (zipfilenavn) zip := posterne
Bemærk: skrivning af zipfiler er p.t. ikke implementeret i jucePILS.
Ligesom ved almindelige filnavne bruges / som skilletegn, selvom zip-formatet bruger \ internt.
Der er ingen understøttelse for ændringer eller tilføjelser til zipfilerne, udtrækning af enkelte filer eller læsning/skrivning af andre arkivformater.
Bemærk: OpenOffice-pakken lagrer sine dokumenter i zip-format. HTML-udgaven af PILS dokumentationen er skrevet med OpenOffice Writer og konverteret til HTML af et PILS script.
Med arbejdstråde kan man undgå at PILS brugergrænsefladen låser under langvarige beregninger, og udnytte multiprocessorsystemer til ad udføre beregninger parallelt.
PILS tråde og knuder er beregnet til dette og har ikke den finkornede synkroniseringsmekanik der kræves til proceskontrol. Til gengæld er man fri for at bekymre sig om låsninger der går i hårdknude (på nørdsk: deadlocks), og det er umuligt at kalde objekter fra en forkert tråd.
Biblioteket lib/pils/english/compute/compute.pils tilbyder a simpel indpakning af brugen af en enkelt arbedstråd til parsning af filer eller lignende.
:tråd udtryk
Denne konstruktion kan kun bruges i hovedtråden. En arbejdstråd oprettes og startes, og [] afleveres som resultat. Arbejdstråden tolker udtryk, bortkaster resultatet og afsluttes.
Systemkald og brugergrænsefladeoperationer kan ikke udføres direkte fra arbejdstråde. Men en arbejdstråd kan låne hovedtråden ved at kalde en knude.
Den eneste form for synkronisering der findes i PILS er knudekaldet, dvs. kald af metoder på et objekt indpakket i en knude:
(knude: objekt) metode
Knuden omdirigerer alle kald af objektet fra arbejdstråde til hovedtråden, som behandler dem når den ikke er optaget af andre opgaver. Den kaldende arbejdstråd blokeres indtil hovedtråden har udført kaldet.
Der er ingen mulighed for at sætte en arbejdstråd i pausetilstand for at vække den senere.
Når kaldet lykkes, fejler eller svipser, sættes arbejdstråden i en tilstand der afspeljer dette, og genstartes. Trådskiftet er generelt gennemsigtigt, men haleudfladning og samlebåndsoperationer på lister holdes inden for den enkelte tråd, og ansvarstagere, listebyggere og udgange fejler hvis man forsøger at bruge dem over trådgrænser.
Generelt bør arbejdstråde udføre selvstændige beregninger det meste af tiden, og kun lejlighedsvis bruge knuder for at tilgå systemet, opdatere brugergrænsefladen og udveksle information. Hvis en arbejdstråd bliver hængende i et knudekald, vil PILS systemet være låst.
I hovedtråden behandles knudekald som alle andre kald, uden nogen form for synkronisering.
Det PILS modul der viser fejlmeldinger, er indlagt i en knude, så når en arbejdstråd udløser en fejlmelding, vil den blive vist i hovedtråden. Arbejdstråden kører videre efter at fejlmeldingen er udløst.
Under opbygning af brugerflader er det sommetider nødvendigt at sætte operationer i kø til senere udførelse, typisk efter at det vindue der er under konstruktion, er vist og opmålt.
:senere udtryk
udtryk lægges i kø idet den aktuelle kontekst vedhæftes, og [] afleveres straks som resultat. Når hovedtråden er ledig, tolkes udtryk i den vedhæftede kontekst, og det hele bortkastes.
Efternølere kan dannes både fra hovedtråden og arbejdstrådene; de udføres altid i hovedtråden.
Et PILS program er en samling moduler, indeholdt i en eller flere biblioteksfiler. De moduler og biblioteker der er involveret i kørslen af et program, samles i en programstrop.
Programstropperne administreres af biblioteket pils/english/system/system.pils der ved opstart findes ud fra den eksekverbare fils mappe og dens omgivende mapper, hvorefter modulet [pils system boot] parses og kaldes.
De moduler i systembiblioteket der administrerer programstropperne, bruger selv en forenklet form for referencer til hinanden. De bør ikke ændres hvis man ikke ved hvad man gør – hvis de går i kludder, trækker de PILS fejlmeldingssystemet med sig, og man må ty til primitive og besværlige metoder for at finde ud af hvad der går galt.
De grundlæggende byggeklodser i the PILS programmeringssystemet kaldes moduler. Generelt er et module et navngivet PILS udtryk, som typisk resulterer i et bundet regelsæt, som er det der ses udefra..
{ ... | } ! synlige regler
...
===
private definitioner
I biblioteksfilerne lagres hvert modul sammen med et modulhoved der indeholder modulets navn og nogle attributter, hvoraf .tidsstempel angiver hvornår modulet sidst blev redigeret, og .sprog angiver et PILS sprogobjekt som bruges til parsning af modulets tekst.
Modulerne er det der på nørdsk hedder singletons, på programbasis: når flere dokumenter er åbne samtidig, har de hver sin programstrop med egne eksemplarer af modulerne. Desuden dannes der ekstra eksemplarer som kan eksistere sideløbende med de gamle hvis modulerne redigeres mens de er i brug.
Et modulnavn er en liste af PILS navne. PILS redigeringen præsenterer dem som et træ, idet fælles begyndelser slås sammen.
Et modul har uden videre adgang til sine ophav; for eksempel kan et modul [spil bræt skak] bruge de synlige regler fra [spil bræt] og [spil,] .
Et modul kan derimod ikke umiddelbart kalde sine egne metoder. Hvis en regel har brug for at kalde andre regler i det same regelsæt eller bundt, må det ske gennem binderne :hvem eller :selv .
Moduler kan referere til hinanden ved hele modulnavnet:
@ [spil bræt skak] refererer altid til [spil bræt skak]
Eller ved relative navne. I et modul ved navn [spil bræt] vil
@ skak
referere til [spil bræt skak] , [spil skak] eller [skak,] , mens
@ . [skak brikker]
refererer til [spil bræt skak brikker] , [spil skak brikker] eller [skak brikker]
Absolutte modulreferencer håndteres af en funktionsregel {@: modulnavn|...} mens relative modulreferencer håndteres af et hjælpeobjekt ved navn @ .
Både absolutte og relative modulreferencer kan bruge beregnede modulnavne.
Pr. konvention har mange moduler en metode hvis navn er sidste led i modulnavnet. Hjælpeobjektet @@ gør at man slipper for at skrive navnet to gange:
@@ bræt (...) er det samme som @ bræt bræt (...)
@@ bræt er det samme som @ bræt bræt
Funktionalitet som er almindeligt brugt i en applikation, kan defineres som regler i et modul hvis sidste navneled er === . Reglerne kan bruges direkte fra alle andre moduler i den pågældende gren af modultræet.
Systembiblioteket system.pils indeholder et modul [===,] med funktionalitet der kan bruges fra alle PILS moduler.
Modulreferencer er som udgangspunkt dynamiske: hvis et modul ændres mens det er i brug, vil referencer til modulet herefter resultere i en instans af den nyeste version.
Modulreferencer der er udført før rettelsen blev gemt, vil fortsat referere til den gamle version.
Denne forskel er væsentlig når man eksperimenterer med ændringer i en kørende applikation: den funktionalitet der tilgås gennem dynamiske modulreferencer, ændres øjeblikkelig når modulerne redigeres, mens funktionalitet der ligger i objekter som blev oprettet ved applikationens start, kræver en genstart hvis en ændret udgave skal testes.
Fremgangsmåden med genstart for hver ændring er stadig almindeligt udbredt når der arbejdes med kompilerede sprog, men der er i almindelighed ingen grund til at gøre det i PILS.
Første gang et modul refereres i et kørende program, vil programmeringssystem instantiere det ved at parse og tolke udtrykket. Instansen huskes i programstroppen og vil blive genbrugt næste gang modulet refereres, hvis det ikke er redigeret i mellemtiden.
Når flere PILS programmer kører, har de hver deres egen programstrop og adskilt instantiering.
Når et modul ændres ved redigering, fjernes det fra instanshukommelsen i alle berørte programstropper, sammen med eventuelle andre modulinstanser som refererede modulet under instantiering. De gamle instanser er stadig brugbare, men nye referencer vil oprette nye instanser.
Hvis et modul under instantiering bruger data fra tekstfiler, XML-filer, regneark eller lignende, kan det være nødvendigt at geninstantiere modulet når filerne er ændrede, for at afspejle de ændrede data i PILS programmet. Det understøttes med funktionerne
datafil (filnavn)
som opretter et filobjekt og desuden registrerer modulets afhængighed af den tidsstemplede fil hvis det sker under instantiering af modulet, og
datafiltjek
som tjekker tidsstemplerne på samtlige datafiler der er registreret i programmet, og fjerner de modulinstanser fra instanshukommelsen der afhænger af filer hvis tidsstempel er ændret.
Navnet dette kan bruges til udtræk af forskellige egenskaber ved det aktuelle modul eller program.
dette program filnavn
dette program sti
dette program sprog
henter filnavn, sti og sprog af det program der har instantieret modulet, mens
dette modul navn
dette modul tekst
dette modul udtryk
dette modul sprog
dette modul bibliotek
henter modulets attributter.
Fejlfinderen er afhængig af denne konvention, navnet dette (engelsk: this ) bør ikke bruges til andre formål.
Hvert modul har en tilknyttet sprogattribut som angiver et sprognavn, som kan bestå af flere led.
Modulets sprog findes ved at [pils sprog] sættes foran sprognavnet, og dette opfattes som en relativ modulreference, svarende til udtrykket
@ . ([pils sprog] & sprognavn)
For at processen ikke skal gå i ring, henviser sprognavnet [system] altid det sprogobjekt der blev brugt til opstart af PILS.
Typisk er sprogmoduler PILS sprogobjekter, eventuelt med nationale oversættelser af de ord der bruges i PILS systemet, eller med program- eller biblioteksspecifike ordvalgspræfixer som j: der bruges i bindingerne til juce -biblioteket, eller eller til håndtering af XML-baserede formater såsom OpenOffice dokumenter.
Alternativt kan man definere sin egen parser i et sprogmodul. Et simpelt eksempel er sproget tekst , der afleverer modulteksten uden at parse den, hvilket er nyttigt hvis man vil gemme tekstdata i PILS biblioteker.
PILS redigeringen stiller de relevante sprogmoduler til rådighed i menuen.
Et bibliotek er en fil der indeholder moduler og eventuelt references til andre biblioteker. Når et bibliotek indlæses, indlæses de biblioteker det refererer til også, hvis det ikke allerede er sket.
Et PILS program er et bibliotek der bruges som applikation.
Når brugeren åbner et program, indlæses det sammen med de biblioteker det bruger. Alle bibliotekerne sammenflettes så i et programbibliotek der indeholder alle moduler fra alle bibliotekerne.
Et programbilioteks levetid styres af programstroppen, som hægtes på alle de hovedvinduer der oprettes fra programmet. Programmet frigives når det sidste af dets vinduer nedlægges.
Når et bibliotek bruges som en del af et programbibliotek, rettes dets modulreferencer mod hele programbibliokteket.
Fire biblioteker – systembiblioteket, redigeringen, platformsbiblioteket og et brugerkonfigurationsbibliotek – skal være indlæst for at PILS systemet fungerer. Systembiblioteket, redigeringen og platformsbiblioteket findes ud fra relative stier fra den eksekverbare fil, mens brugerkonfigurationsbiblioteket vælges ved opstart, ud fra computerens sprogindstillinger.
Hvis din computer er sat til dansk sprog, vil konfigurations- og sprogfilerne for dansk blive indlæst hvis de eksisterer, og sig -funktionen – der bør bruges til alle meddelelser og navne i brugergrænsefladen af PILS programmerne – vil så bruge dette sprog og forsøge at præsentere meddelelser på noget der ligner dansk.
Indtil videre er kun dansk og engelsk understøttet.
Ved opstart forsøger PILS fortolkeren at kontakte en eventuelt allerede kørende instans og videregive kommandolinjen til denne. Kun hvis det ikke lykkes, starter fortolkeren i en ny instans.
Det første fortolkeren gør er at danner PILS et objekt der skal kunne behandle kommandolinjerne. Dette objekt defineres af modulet [pils system start] i baduljeflikkeren.
Dette kompliceres en smule af at baduljerne oprettes og nedlægges løbende, så kanalen [kanal: pils: kommandolinje] bruges til at sende forespørgsler til en aktiv badulje med en passende instans af modulet [pils system start] .
MS Windows-installationspakken definerer operationerne open og edit for PILS filtypen i registreringsdatabasen; de kendes fra hinanden ved at edit-kommandoen har -edit indskudt i kommandolinjen. Dette opfattes af en regel i [pils system start] og bevirker at redigeringen åbnes, uanset om der er defineret en anden handling i [pils kør start] .
PILS programmer er oprettes, redigeres og testes med PILS redigering – en simple tekstredigering med faneblade for de enkelte moduler og faciliteter til søgning og navigation i PILS bibliotekerne samt kald af testfunktioner.
Et PILS redigeringsvindue begrænser sig altid til et enkelt bibliotek. Hvis flere bibliotekern skal åbnes for redigering, bruges flere vinduer i hver sin badulje.
Redigeringen findes i PILS biblioteket editor.pils og kører i baduljen for det bibliotek der redigeres. Bibliotekerne kan ændre redigeringens opførsel ved at overskrive dens moduler.
Når et modul gemmes, fjernes de moduler fra instanshukommelserne der direkte eller indirekte refererede modulet under instantiering..
Bemærk: ændring af redigeringens moduler bør udføres med forsigtighed. Hvis et ødelagt redigeringsmodul gør at redigeringen ikke virker, kan redigeringen jo ikke bruges til at reparere skaden med, og man er henvist til enten at bruge en anden tekstredigering til reparation af fejlen, eller genetablere en tidligere udgave af PILS redigeringen.
Opret en tom fil med type .pils , åbn den og vælg det sprog du vil bruge.
Filen
lib/pils/sprog/pils/ny.pils
bliver så kopieret oven i den tomme fil og åbnet.
Det sprog du vælger, bliver programmets sprog. Det bruges til at gemme og vise modulnavnene og vil blive valgt som sprog når du opretter ny moduler. Du kan skifte filsproget senere.
Bemærk: Brugergrænsefladens sprog styres af computerens sprogindstilling, ikke af programmets sprog. Hvis du skriver dine programmer med engelske indstillinger og kører dem på et system med danske sprogindstillinger, eller omvendt, vil PILS forsøget at oversætte brugerfladen.
Ved dobbeltklik på en PILS fil kaldes funktionen åbn i modulet [pils kør kommando åbn] . Hvis modulet ikke er overskrevet, vil det åbne filen for redigering.
Kommandoen Rediger kalder tilsvarende funktionen rediger i modulet [pils kør kommando rediger] , der ligeledes åbner filen for redigering.
Under opbygning af et PILS program vil man typisk starte programmet ved at åbne redigeringen og køre en testfunktion. Når programmet skal afpudses og eventuelt offentliggøres, kan man så oprette et modul [pils kør kommando åbn] og definere en regel:
{ .åbn | ... }
Redigeringen kan stadig åbnes med Rediger-kommandoen.
På kommandolinjen bruges det engelske ord -edit (med bindestreg foran) til at differentiere Rediger-kommandoen fra Åbn, som er standardkommandoen.
Når du åbner redigeringen, vil det the sidst ændrede modul i biblioteksfilen blive vist i a faneblad.
Redigeringen som sådan er simpel – de sædvanlige tastaturgenveje kan bruges:
Ctrl-C kopiér
Ctrl-X klip
Ctrl-V indsæt
Ctrl-Z fortryd
Ctrl-Y gendan
Ctrl-S gem
Der gemmes altid til filen med det samme. Du kan ikke teste et modul uden at gemme det.
Forsøg på at gemme moduler med syntaksfejl afvises, og og fejlen markeres i teksten.
Tip: Hvis du vil gemme et modul med syntaksfejl, så sæt sproget til tekst som beskrevet herunder.
Ctrl-M viser programmets moduler som et træ.
Et modul åbnes ved at aktivere flasken i træet med dobbeltklik eller Enter -tasten. Brug Esc -tasten til at lukke træet.
Undermoduler kan oprettes med:
Ctrl-N opretter et undermodul til det aktuelle modul.
Moduler i roden kan oprettes ved at oprette et undermodul til et eksisterende modul og flytte det ned ved at bruge Modul-menuen:
Modul->flyt->ned flytter modulet med undermodules mod roden
Modul->flyt->op->nabo flytter modulet med undermodules op under nabomodul
Ctrl-K kopierer modulet med undermoduler til et andet navn.
Ctrl-R omdøber modulet og undermodulerne.
Modul->slet nedlægger modulet hvis det er tomt og ikke har undermodules.
Modul->flyt->bibliotek->bibliotek flytter til et andet bibliotek, med undermoduler.
Tip: Det er med vilje gjort lidt besværligt at nedlægge moduler, så man ikke mister kode hvis man rammer en forkert tast. Hvis du skal slette mange moduler som led i en oprydning, så opret et bibliotek og brug det som skraldespand: indstil dit bibliotek til at bruge skraldespanden, flyt så de uønskede moduler derover, fjern skraldespanden fra brugslisten og slet den.
Vælg det ønskede sprog fra Sprog -menuen, og gem modulet med Ctrl-S .
Menupunktet system refererer altid til det sprogobjekt der bruges til opstart af PILS. De andre punkter refererer til sprogmoduler.
Et sprog kan tilføjes til menuen ved at oprette et modul
pils sprog navn
og skrive et sprogobjekt eller et udtryk der giver et sprogobjekt eller et objekt med en læs -metode, som sprogene tekst og tekstliste der defineres af moduler i redigeringsbiblioteket – tekst giver simpelthen modulteksten, mens tekstliste opdeler den i linjer.
Sprogmoduler kan sættes på en gren i modultræet:
job j pils sprog mit-sprog
definerer et sprog der kun kan bruges i moduler hvis navn begynder med job j . Hvis et moduls sprog er sat til et sprog der er specifik for en gren af modultræet, bør modulet ikke flyttes uden for grenen, da det vil gøre sprogindstillingen ugyldig.
juce bindingsbiblioteket – der pakker juce-bindingerne ind – bruger et sådant specifikt sprog som knytter præfixet j: til den ordklasse der bruges til klasser og metoder i juce.
Programmets sprogindstilling kan ændres hvis man ikke er tilfreds med det oprindeligt valgte sprog. Først skal et passende sprogbibliotek tilføjes til listen af brugte biblioteker:
<lib>/sprog/pils/sprog
Derefter bruges menupunktet Fil->indstillingerne->sprog-> sprog til at sætte programmets sprog.
Dette udløser en rekonstruktion af redigeringsvinduet for at sikre at modulnavne vises korrekt.
Ændring af programmets sprog påvirker ikke sprogvalget for moduler der allerede er oprettet.
Et program eller bibliotek kan indstilles til at bruge andre PILS bibliotekerne med menupunktet
Bibliotekerne->brug
Biblioteker kan indsættes i og fjernes fra listen med Insert og Delete -tasterne. Insert -tasten åbner en filvalgsdialog, og det valgte filnavn forkortes idet filtypen .pils udelades og begyndelsen af filstien erstattes af et af de følgende indledninger hvis muligt:
<lib>/ – mappen lib/pils/ i PILS installationen
<doc>/ – brugerens dokumentmappe
<.>/ – det nærværende biblioteks mappe
<..>/ – den mappe der omgiver det nærværende biblioteks mappe
Denne omskrivning bidrager til at opretholde referencerne når PILS programmer flyttes.
Hvis du arbejder med mange biblioteker, vil du lejlighedsvis få brug for at søge på tværs af biblioktekerne for at finde en stump kode du har glemt hvor er.
Ctrl-D
Åbner et detektivpanel med to søgestriber – en høj og en lav – og et modultræ. Ud over de velkendte Kun hele ord og Forskel på store og små bogstaver understøtter detektivpanelet en Strukturel søgemåde, som læser søgeteksten som et PILS udtryk og bruger den som en pasform Det gør det muligt at søge efter konstruktioner af en bestemt form, uden hensyn til hvordan de er skrevet.
Mens søgeteksten skrives eller redigeres, opdateres modultræet løbende, idet alle moduler med træffere samt vejene ud til dem markeres.
Når et modul med træffere vælges i træet, vises en liste med linjenumre for træfferne. Når linjenumrene markeres, markeres træfferne automatisk i redigeringsvinduer for de respektive biblioteker.
Bibliotekerne har filtypen .pils og er utf-8 tekstfiler med CR+LF (DOS/Windows-konventionen) som linjeskift. Om nødvendigt kan de redigeres with MS Windows Notepad eller lignende. Programstrophåndteringen ignorerer de ekstra bytes (BOM) som Windows-applicationer som Notepad indskyder forrest i utf-8-filer.)
Et PILS program eller bibliotek består af et eventuelt program-hoved og en række moduler, adskilt af markører:
bibliotekshoved><:modulhoved><;modultekst><:modulhoved><;modultekst ...
idet enhver anden forekomst af >< kodes som ><. (med tilføjet punktum) for at undgå forveksling med markørerne. Deres orden har ingen betydning, men de sorteres dem ved rå sammenligninger af modulhovederne udskrevet som tekst, hvilket er bekvemt hvis der arbejdes med rå tekstredigering eller versionsstyringssystemer.
Modulerne består af et modulhoved og en tekst. Modulhovedet er en nodekonstant [modul: . ...] skrevet i programfilens sprog med modulnavnet i forbenet og derudover ben for modulets attributter, som sprog og tidsstempel . Modulnavnet er en liste af 1 eller flere PILS navne.
Bibliotekshovedet indeholder generel information om filen og skrives på engelsk. Et eventuelt language -ben angiver programfilens sprog, og et use -ben angiver referencer til andre biblioteker.
Når et bibliotek indlæses, lagres modulerne som ben i en node, idet benenes navne er modulnavnene mens deres værdier er modulhovederne med modulets tekst som forben i stedet for modulnavnet. Bibliotekshovedet lagres i halebenet.
Regler af form
{ .navn | :ok testudtryk }
kan aktiveres gennem et moduls Test -menu når modulet er i gemt/uændret tilstand. Aktivering af menupunktet kalder det pågældende navn, og hvis kaldet lykkes, vises resultatet i et Resultat -vindue. Hvis resultate ikke skal vises, kan ansvarstageren udelades:
{ .navn | testudtryk }
En eventuel kviktest-funktion
{ .test | :ok testudtryk }
kan aktiveres med tastaturgenvejen
Ctrl+T
som først gemmer modulet hvis det ikke allerede er gemt, og derefter kalder kvikttestfunktionen hvis den findes.
Testmoduler definerer testfunktioner der kan bruges fra alle moduler i en gren af modultræet.
Lad os antage at du arbejder med et modul ved navn [a b c] . Når du gemmer modulet eller trykker Ctrl-T, afsøges disse moduler for test funktioner i den angivne rækkefølge:
[a b c]
[a b c test]
[a b test]
[a test]
[test,]
Hver testfunktion bindes til det første modul hvor den findes.
Testfunktioner i modulet [test,] kan bruges hvorsomhelst. Men hvis man har mange testfunktioner, bør de lægges ud i de grene af modul træet hvor de er relevante, af hensyn til overskueligheden af testmenuerne.
Hvis man skal teste et bibliotek der bruges af forskellige programmer,kan det være praktisk at indlægge et program i biblioteksbaduljen, så dette programs moduler er tilgængelige fra bibliotekets testfunktioner – men ikke fra andre programmer der bruger biblioteket. Et sådant program kaldes et testprojekt.
Åbn bibliotekets Brug bibliotekerne -panel, tilføj testprogrammet, og markér det med Ctrl-T . Testprogrammet vil nu blive medtaget i bibliotekets egen badulje, men ikke i de baduljer der bruger biblioteket.