Linköpings universitet Innovativ Programmering TDP019 Datorspråk VT 2011 JAIL Just Another Interpreted Language Thomas Hall thoha054@student.liu.se Henrik Karlsson henka628@student.liu.se
Innehåll 1 Inledning...3 1.1 Bakgrund...3 1.2 Syfte...3 2 Användarhandledning...3 2.1 Målgrupp...3 2.2 Komma igång med Jail...3 2.3 Att programmera i Jail...4 2.4 Konstruktioner i språket...4 2.4.1 Vårt första program...4 2.4.2 Kommentarer...4 2.4.3 Deklarera och tilldela variabler...4 2.4.4 Aritmetiska operationer och utskrift...4 2.4.5 Villkorssatser (if-satser)...5 2.4.6 While-satser...5 2.4.7 For-loopar...6 2.4.8 Funktioner...6 3 Systemdokumentation...6 3.1 Grammatik...6 3.2 Översiktlig beskrivning av Jail...9 3.3 Tokens...10 3.4 Parsning...10 3.5 Noder...10 3.6 Interpretator...10 3.7 Omgivning...11 3.8 Kodstandard...11 4 Utvärdering...11 4.1 Vad vi hade för tankar om kursen...12 4.2 Problem på vägen...12 4.2.1 Rekursiv grammatik...12 4.2.2 Omgivningar...12 4.3 Ändringar från grundidén...13 4.4 Lärdomar från projektet...13 4.4.1 Jobba i en grupp...13 4.4.2 Förstå andra språk bättre...13 2
1 Inledning I den här rapporten kommer vi ta upp hur användaren använder sig av Jail, vilken syntax som finns i språket och vilka funktioner och konstruktioner som språket har. 1.1 Bakgrund Den här rapporten är skriven för kursen TDP019 Projekt: Datorspråk, på Tekniska högskolan vid Linköpings universitet. Språkets namn, Jail, är en förkortning av Just Another Interpreted Language, som är lite av ett internt skämt, det syftar på att språktypen är den vanligaste bland alla IP-studenters språktyper, samt en känd kompilator som heter Yet Another Compiler Compiler. 1.2 Syfte Syftet med språket är inte att användaren ska få något effektivt, snabbt och enkelt sätt att skriva programkod i utan mer för vår egen skull. Vi ville lära oss grunderna till hur de flesta språk är uppbyggda och vilka konstruktioner och funktioner man måste skapa för att ett språk ska kännas komplett, därför valde vi en syntax som kändes enkel att skriva och sen fick vi implementera det vare sig det språket i sig blev bättre/sämre än något annat. 2 Användarhandledning I användarhandledningen kommer vi leda dig genom i Jails specifika syntax och konstruktioner. Efter att ha läst handledningen ska du kunna skriva enklare program med hjälp av språket. 2.1 Målgrupp Språket Jail är inte riktad till någon speciell målgrupp, utan kanske mer för användare som är intresserade av att prova på en ny slags syntax. Språket är inte anpassat till nybörjare då det är väldigt likt C++/Ruby/Python i sin syntax, men det är heller inte anpassat för avancerade användare då vi inte har några svåra konstruktioner eller funktioner. Jail är något slags mellanting. 2.2 Komma igång med Jail För att få en smärtfri körning rekommeras Ubuntu eller något annat Linux-system då man ska programmera Jail-kod. Eftersom att Jail-parsern är skriven med Ruby-kod så kräver språket att användaren har Ruby installerat. Sedan krävs det bara att användaren installerar programspråket med det medföljande installeringsskriptet. Efter installationen är det möjligt att starta Jailinterpretern från vilken katalog som helst i terminalen, samt exekvera program skrivna med Jail-kod genom att lägga till en sökväg i början av varje Jail-fil (#!/usr/bin/env jail). För att installera Jail följer du dessa enkla steg: 1) Packa upp tar-filen. 2) Navigera till de uppackade filerna. 3) Kör Install-filen. 3
2.3 Att programmera i Jail Jail är ett imperativt språk som använder en syntax som är lik de flesta stora programmeringsspråken. När man programmerar i Jail behöver man inte berätta för språket att programmet startar med hjälp av något nyckelord. Språket har egentligen inget krav på indentering, men vi rekommerar att alltid använda det då det både underlättar för både programmeraren själv och alla som läser koden i efterhand. För att köra kod som är skriven med Jail så har du två alternativ: antingen kan du spara koden i ett textdokument och sedan köra den från terminalen, eller så kan du köra den interaktiva tolken genom att skriva jail i en terminal. 2.4 Konstruktioner i språket Här visar vi några enkla exempel på hur kodsyntaxen ser ut i Jail. 2.4.1 Vårt första program Här visas ett enkelt exempel på hur man kan skapa ett program, som skriver ut Hello World! på skärmen: output Hello World ; 2.4.2 Kommentarer Kommentarer i Jail gör man med #-tecknet. Allt efter detta tecken på samma rad kommer att ignoreras av parsern och bara stå som kommentar till koden. Exempel: output Hello World ; #skriver ut Hello World! 2.4.3 Deklarera och tilldela variabler För att deklarera variabler i Jail måste man ange vilken datatyp variabeln ska ha. Man kan även tilldela att värde då man deklarerar den. Exempel: int summa; summa = 15; string mittnamn = Peter ; #skapar en variabel med datatypen integer #tilldelar värdet 15 till variabeln summa #deklarerar och tilldelar den på samma rad 2.4.4 Aritmetiska operationer och utskrift Eftersom att Jail skiljer på satser och uttryck kan man inte bara skriva ett uttryck för att räkna ut det, men man kan till exempel skriva ut ett uttryck eller tilldela variabler med aritmetiska operationer för att se vad det blir. Exempel: 4
int sum = 5+5; int sumtwo = 5; int totsum = sum + sumtwo; output totsum; #binder 5+5 till sum #tilldelar värdet av 10 + 5 till totsum #skriver ut värdet till variabeln totsum 2.4.5 Villkorssatser (if-satser) I Jail så kommer koden efter if-satsen att köras om villkoret är sant, precis som i de flesta andra språk. Något som skiljer är dock att man måste berätta för Jail när if-satsen är slut. Det gör man med nyckelordet if. Exempel: int a = 5; if (a IS 4) output Bil ; elif (a < 3) output Buss ; output Cykel ; if #om a är lika med 4 så... #skriver ut Bil #annars om a är mindre än 3 så... #skriver ut Buss #om inget av de ovanståe stämmer... #skriver ut cykel #if- och elifsatsen är falska, skriver ut Cykel Det som kan stå innanför parentesen är relationsuttryck. Det betyder att man jämför det som står på de olika sidorna om relationsoperatorn. De olika relationsoperatorerna som finns i Jail är: Lika med: x IS y Inte lika med: x ISNOT y Större än: x > y Mindre än: x < y Större eller lika med: x >= y Mindre eller lika med: x <= y 5
2.4.6 While-satser En while-sats i Jail fungerar precis som de flesta andra språk, men precis som i if-satsen så måste man även här berätta för Jail när while-satsen är slut, med nyckelordet while. Så länge uttrycket inom parentesen är sant kommer koden mellan while och while att köras. Exempel: int a = 1; while (a < 10) #medan a är under 10... output a; #skriv ut a a++; #öka värdet på a med 1 while #sluta loopa Alla relationsoperatorer som fungerar i if-satser fungerar även i while-satser. 2.4.7 For-satser En for-sats i Jail är väldigt lik en for-sats i C++. Den kräver att användaren skriver en for-sats på ett specifikt sätt. Jail är mycket mer strikt i hur du kan använda en for-sats än t.ex. C++, då en for-sats ska användas för iteration i språket. Precis som i if- och while-satser så förväntas programmeraren att skriva när satsen är slut, med nyckelordet for. Exempel: for (int a = 0; a < 10; a++) output a; for #här sätter vi upp vilka krav vi har, när loopen ska sluta #skriv ut a varje loop #sluta loopa Detta betyder egentligen: a har värdet 0, kör koden mellan for och for tills uttrycket är falskt. Öka värdet på a med ett varje iteration (loop). Om Jail körde detta program skulle reslutatet bli att alla siffror mellan 0 och 10 skulle skrivas ut på skärmen. 2.4.8 Funktioner Funktioner i Jail fungerar som i de flesta andra språk. Har man en return i funktionen så kommer det värdet att returneras när man ropar på funktionen, om man inte har någon return så returneras 0 från funktionen. Exempel på en enkel funktion: fn enkelfunk(string x;) output x; fn fn( hejsan! ) #skapar en funktion med en inparameter x #skriver ut x #avslutar funktionen #ropar på funktionen, ger x värdet hejsan! Denna funktion kommer skriva ut hejsan! på skärmen. 6
3 Systemdokumentation Systemdokumentationen kommer ta upp hur Jail är uppbyggt, allt från att hämta tokens till att ordna med dess omgivningar. Efter att ha läst den kommer läsaren ha en förstå för hur Jail ser ut i språkkärnan samt hur vi tänkte när vi implementerade språket. 3.1 Grammatik <program> ::= <satser> <satser> ::= <sats> <satser> <sats> <sats> ::= <repetition> <deklareravar> <deklarerafunktion> <tilldelning> <utmatning> <inmatning> <villkor> <retursats> <funktionsanrop> <deklareravar> ::= <datatyp> <var> ";" <datatyp> <var> "=" <uttryck> ";" <tilldelning> ::= <var> <tilldelningsop> <uttryck> ";" <var> "+" "+" ";" <var> " " " " ";" <utmatning> ::= "output" <uttryck> ";" <inmatning> ::= "input" <var> <villkor> ::= "if" "(" <predikatuttryck> ")" <satser> "if" "if" "(" <predikatuttryck> ")" <satser> <elifs> "" <satser> "if" "if" "(" <predikatuttryck> ")" <satser> <elifs> "if" "if" "(" <predikatuttryck> ")" <satser> "" <satser" "if" <elifvillkor> ::= "elif" "(" <predikatuttryck> ")" <satser> <elifs> ::= <elifvillkor> <elifs> <elifvillkor> 7
<repetition> ::= <forloop> <whileloop> <whileloop> ::= "while" "(" <predikatuttryck> ")" <satser> "while" <forloop> ::= "for" "(" <deklareravar> <relationsuttryck> ";" <tilldelning> ")" <satser> "for" <retursats> ::= "return" <uttryck> <uttryck> ::= <funktionsanrop> <predikatuttryck> <additionsuttryck> <string> <additionsuttryck> ::= <multiuttryck> <additionsuttryck> "+" <multiuttryck> <additionsuttryck> " " <multiuttryck> <multiuttryck> ::= <aritmparentes> <multiuttryck> "*" <multiuttryck> <multiuttryck> "/" <multiuttryck> <relationsuttryck> ::= <relationsoperand> <relationsop> <relationsoperand> <relationsoperand> ::= <additionsuttryck> <var> <tal> <logikuttryck> ::= <boolean> <logikoperand> <logikop> <logikuttryck> <logikoperand> <logikop> <logikoperand> <logikoperand> ::= <relationsuttryck> <boolean> <predikatuttryck> ::= <logikuttryck> <relationsuttryck> <logikop> ::= "AND" "OR" 8
<relationsop> ::= ">" "=" "<" "=" "IS" ">" "<" "ISNOT" <tilldelningsop> ::= "*" "=" "/" "=" "+" "=" " " "=" "=" <datatyp> ::= "int" "string" "bool" "float" <deklarerafunktion> ::= "fn" <var> "(" <parametrar> ")" <satser> "fn" <parametrar> ::= <parameter> <parametrar> <parameter> <parameter> ::= <deklareravar> <funktionsanrop> ::= <var> "(" <arguments> ")" ";" <arguments> ::= <argument> <arguments> "," <argument> <argument> ::= <uttryck> <boolean> ::= "true" "false" <aritmparentes> ::= "(" <additionsuttryck> ")" <var> <tal> <var> ::= /^[a za Z]+/ " " /^[a za Z]+/ <string> ::= '"' String '"' <tal> ::= Float " " Float Integer " " Integer 9
3.2 Översiktlig beskrivning av Jail Jail är ett imperativt språk med väldigt grundläggande konstruktioner och funktioner. Man kan säga att dess syntax är väldigt lik C++, men språket använder en interpretator och är inte kompilerat. Jail är ganska starkt typat då programmeraren måste följa en strikt struktur i t.ex. for-loopar, if-satser eller variabeldeklaration. Jail skiljer uttryck från satser. Det betyder att matematiska uttryck inte kan räknas ut genom att bara skriva t.ex. 3 + 4. Om användaren vill veta vad 3 + 4 blir måste den t.ex. kombinera det med en utmatning: output 3 + 4; #skriver ut 7 3.3 Tokens Jail plockar ut sex olika typer av tokens: Blanksteg, som vi ignorerar Jail är inte indenteringsstyrt. Integers, siffror utan decimaltecken. Floats, siffror med decimaltecken. Kommentarer, dessa ignoreras också då man inte vill exekvera dem. Bokstäver, allt mellan a z, både som versaler och gemener. Övrigt, alla specialtecken och annat som ingen annan token plockar upp. 3.4 Parsning Ett av det viktigaste momenten när man skapar ett språk är att sätta upp grammatiska regler, det är grunden till hela språket så kallad parsning. Med Jail var vi väldigt noga med att skapa dessa grundregler eftersom att alla senare grammatikregler bygger på dem. Det betyder att om man börjar på ett bra sätt så kommer många kommande problem att lösa sig själva. I vår parsning använder vi oss av väldigt mycket rekursion. Det är ett måste för att inte behöva lösa alla problem senare i noderna. Det största problemet i Jails parsning var med aritmetiska beräkningar. Vi försökte lösa det helt själva utan att titta på hur andra stora språk hade löst det. Det visade sig vara svårare än väntat, efter att ha tittat på Pythons grammatik så krävdes det ett par ändringar sedan fungerade allt. 3.5 Noder I Jail skapar vi olika noder för alla olika parsningsregler, med ett fåtal undantag. I noderna gör vi alla felkontroller och det är här vår kod möter konstruktionspråket, dvs Ruby. Eftersom Jail har olika omgivningar beroe var i programmet pekaren befinner sig så krävs det att man hanterar omgivningar någonstans, det görs i våra noder. Noderna får in information från parsningen och leder programmet åt det håll det ska beroe på vad dem får in. Sedan översätter den de kommandon den får av Jail (därav ett interpreterat språk) och utför dem med Ruby. Därför är det viktigt att ha ett bra språk till att skapa ett annat språk, då det är dess konstruktioner som begränsar hur bra man kan implementera sitt egna nya språk. 10
3.6 Interpretator I Jail har vi skapat en egen interpretator. Interpretatorn exekverar den kod den får in direkt, om inte användaren skriver in: if-satser for-loopar while-loopar funktioner Detta är alla funktioner som Jail kräver att användaren avslutar med ett. Då väntar interpretatorn med att exekvera tills t.ex. if-satsen är färdigskriven för att inte tvinga programmeraren att skriva allt på en rad. 3.7 Omgivning I Jail finns det en global omgivning, variabler som skapas där kan man komma åt från hela programmet. Sedan skapas separata omgivningar i alla loopar, if-satser och funktioner. Det betyder att om man skapar en variabel int x i en global omgivning, då kan man skapa ytterligare en variabel int x inuti en if-sats utan att påverka det globala x:et. Exempel: int x = 5; #skapar en variabel x if (x > 3) #villkoret är sant int x = 7; #här skapas en ny variabel x, bara i if-omgivningen if #när vi nu avslutar if-satsen har det globala x:et värdet 5 Om användaren väljer att inte skapa ytterligare en variabel int x i if-omgivningen, då kan man ändra det globala x:et genom att tilldela den ett nytt värde, exempel: int x = 5; #skapar en variabel x if (x > 3) #villkoret är sant x = 7; #här ändras det globala x:et if #när vi nu avslutar if-satsen har det globala x:et värdet 7 Detta realiserar vi genom att använda olika funktioner. Då pekaren befinner sig i en global omgivning och vi deklarerar variabler, då läggs variabeln in i en hash-tabell som nyckel med dess värde som värde. När programmet befinner sig i t.ex. en if-sats, då skapas en ny hash-tabell där den gamla globala omgivningen hamnar innästlad. Om användaren skapar en ny variabel i det här läget så läggs variabeln och dess värde i den nya hashen, men på en nivå högre än den gamla globala omgivningen. Om man sedan gör en utmatning på variabeln x, som både finns i den globala omgivningen och den lokala, då letar Jail först efter variabeln x i den lokala omgivningen. Om den inte hittar en sådan variabel så hoppar den in i nästa omgivning, i vårt fall den globala omgivningen, och letar efter x där. Om x inte blivit deklarerad i varken den lokala eller den globala omgivningen så kommer Jail skicka ut ett felmeddelande. Genom denna behandling av omgivningar så spelar det ingen roll hur djupt nästlad koden är, Jail hittar rätt ändå. 11
3.8 Kodstandard I Jail har vi inte använt någon strikt kodstandard. Vi har försökt döpa variabler och klasser till logiska namn så vi inte behövt kommentera, utan att koden själv ska vara så lättläst att det inte är nödvändigt att beskriva vad koden gör. I vår parser har vi använt svenska namn för att det kändes minst förvirrande i början av projektet. Alla noder och hela parsern är skrivet med camelcase, men där klassnamn alltid börjar med stor bokstav. 4 Utvärdering Här tar vi upp vilka tankar och problem som uppkom under kursens gång samt vilka lärdomar kursen givit oss. 4.1 Vad vi hade för tankar om kursen Då denna kurs började visste vi inte riktigt vad vi skulle vänta oss. Att göra ett eget språk, det lät som en omöjlighet. Efter kursen tdp007 så hade vi fått känna på hur RD-parsern fungerade. Det gjorde att man fick en tanke om hur man egentligen började då man skapade ett språk. I den kursen fick vi övningar som lärde oss att plocka ut tokens och sedan matcha dessa för att kunna jämföra sanningsvärden, redan då började man tänka sig hur man skulle implementera det riktigt språket som kom vi nästa terminstillfälle. Genom hela tdp015 hade vi möten med Anders Haraldson som visade oss grunderna till hur man skulle bygga sin grammatik, så att man parsade på ett korrekt sätt och hur man matchade själva programdelen med satser och uttryck. Den upptrappningen av lärdom gjorde att man på ett väldigt pedagogiskt sätt, ett litet steg i taget, förstod hur allt hängde ihop på ett bra sätt. Då nästa termin började och vi skulle börja implementera språket, då hade vi redan byggt klart vårt språk i våra huvuden. 4.2 Problem på vägen Utöver dessa två problem som beskrivs nedan har det självklart funnits småproblem på vägen, men inget som stoppat upp oss mer än några minuter i programmerandet. Vi tror att det gått väldigt smärtfritt på grund av att vår grammatik har varit väldigt bra skriven och att våra noder har hanterat alla problem i sin egen nod, så vi inte spridit dålig kod runt om i programmet. 4.2.1 Rekursiv grammatik Eftersom vi redan hade skrivit en stor del av grammatiken innan vi började implementera språket så var det lätt att komma i gång. Det vi dock inte visste var hur strikt grammatiken behövde skrivas för att den skulle både generell och förhindra eventuella syntaxfel som kunde uppstå. Första problemet vi stötte på var då vi skulle matcha satser, dock visste vi inte att det var ett problem förrän vi började skriva flera satser i koden. Allt vi hade implementerat fungerade väldigt bra då det bara var en rad kod, men så fort det blev fler satser så ignorerade den allt förutom den första satsen. I efterhand känns lösningen självklar, men vid det tillfället var det otroligt svårt att komma förbi problemet. Vi försökte returnera den ena satsen samt den andra satsen men det blev bara fel. Sedan kom vi på idén att skicka in satserna i en nod och ta hand om problemet där. När vi väl hade skickat in satserna dit var det väldigt enkelt att se hur problemet skulle lösas. Efter att ha fixat satser kände vi att vi hade lärt oss hur grammatikdelen fungerade. Det kändes inte svårt längre tills vi kom fram till aritmetiska uttryck. Utan att titta på stora språk så som Python, C++ eller Ruby försökte vi skapa egna rekursiva matchningar, där vi skulle ta hand om prioritet och parenteser. Efter att ha suttit med samma problem i över två dagar så bestämde vi oss för att titta hur 12
Python hade löst det problemet. Vi kopierade grundidén och helt plötsligt så var alla våra problem lösta. 4.2.2 Omgivningar Att ordna omgivningar har nog varit det svåraste med projektet, precis som Anders Haraldsson sa att det brukar vara. För det första måste man ha en idé om hur man vill ta hand om de olika omgivningarna, för det andra så måste den idén fungera. Våra omgivningar i Jail skapas först på en global nivå, sedan då man går ner i en nästlad nivå (t.ex. i en if-sats) då skapas en ny omgivning där den globala omgivningen blir inskickad i en nästlad hash-tabell. Det gör att om vi skapar en variabel int x globalt, och sedan skapar ytterligare en int x inuti en if-sats och sedan skriver ut variabeln inne i den lokala if-satsen då letar Jail upp den närmsta existerande x:et, i vårt fall i den lokala if-satsen och skriver ut dess värde. Genom att lägga upp omgivningarna på detta sätt så spelar det ingen roll att vi nästlar 15 gånger. Jail kommer hitta rätt ändå, genom att leta i den första lokala omgivningen hittar den inte x där kommer den hoppa in i den övre nästlingen och leta där, osv. Då vi kodade omgivningen så gällde det att ha tungan rätt i mun som man säger, det är otroligt lätt att missa ett @-tecken eller helt enkelt använda Rubys syntax fel. När det sedan blir en stor mängd kod är det väldigt svårt att lokalisera var felet ligger, därför har vi varit väldigt noga med att testköra koden så ofta som möjligt för att hitta eventuella fel direkt. int x = 5; fn funktion(int y;) int z = 7; output x; fn output z; output x; #skapar ett x i en global omgivning #skapar en funktion #skapar ett z lokalt i funktionens omgivning #härifrån kommer vi åt x, eftersom den ligger globalt #slut på funktionen #detta går inte, z ligger lokalt i funktionens omgivning #detta går, x ligger i den globala omgivningen 4.3 Ändringar från grundidén Då kursen började hade vi tankar på ett göra just ett imperativt språk som hade en syntax som var lik C++, och den idén har vi hållit fast vid. Vi hade också planer på att skapa en unik datatyp, en slags trädtyp som man kunde binda värden till på olika grenar av den. Den idén slopades ganska fort eftersom vi inte kunde komma på någon riktig användning till den. Vår ambitionsnivå har redan från starten varit hög, men vi kände ingen anledning till att skapa ett nytt och innovativt språk, utan vi ville ha med alla grundstenar och göra dem väldigt bra istället. Jail har aldrig varit tänkt som ett språk som skulle vara överlägset något annat, utan vi skapade ett språk för att lära oss för att ge oss mer lärdom hur befintliga språk är konstruerade. Nu i efterhand kan vi säga att vi lyckades, vi har lärt oss otroligt mycket under projektets gång och det har varit det mest lärorika projektet under vår tid i IP. 4.4 Lärdomar från projektet Detta tar upp på vilka olika sätt vi lärt oss saker genom projektet. 4.4.1 Jobba i en grupp Förutom att skapa ett eget språk så har vi lärt oss att jobba bra i en grupp. Vi har delat upp arbetet väldigt bra och jobbat bredvid varandra, på så sätt har det blivit en väldigt effektiv arbetsfördelning. 13
Så fort den ena av oss stötte på problem så kunde den andra hjälpa snabbt genom att se koden med ett annat perspektiv och tankesätt, för det är väldigt lätt att bli låst vid sin egen kod och inte se självklara fel man gjort. Att ett projekt aldrig blir klart förrän tiden är slut stämmer väldigt bra, därför är det viktigt att båda jobbar i ett projekt som detta. Det är många moment som måste klaras av och tiden räcker egentligen aldrig till, det finns alltid rum för förbättringar och utökningar. 4.4.2 Förstå andra språk bättre Då vi går igenom alla steg som krävs för att skapa ett språk blir man medveten om hur andra språk fungerar på ett helt annat sätt än tidigare. Vi förstår att ett programmeringsspråk ständigt behöver uppdateras för det är så enkelt att hitta ett litet kryphål i grammatikreglerna eller helt enkelt för att hänga med i utvecklingen av hur vi använder språk. Något vi också har lärt oss är tyngden av implementationsspråket, använder man ett språk som är dåligt att implementera med är det svårt att skapa ett språk som är annat än dåligt. 14
Bilagor Nod.rb #!/usr/bin/env ruby # -*- coding: utf-8 -*- # TODO listor @@omg = {} $forbiddenthings = %w[fixnum String Float NilClass Array] class Scope # Hanterar omgivning attr_accessor :omg def initialize @@omgivning = @@omg @@omgivning["top"] = nil def Scope.setScope(omg) @@omgivning = omg def Scope.getScope @@omgivning Scope.new class ScopeIter def initialize(omg) @omg = omg def next if @omg.has_key? "top" return nil 15
@omg = @omg["omg"] def getnewscope scope = {} scope["omg"] = Scope.getScope return scope def lookup (var) omg = Scope.getScope return var.eval if isnotvar(var) varname = if var.class == VarNod var.var var if omg.has_key? varname if omg[varname].class == FunNod ret = omg[varname].eval(var.params) return ret return omg[varname] iter = ScopeIter.new(omg) while (true) omg = iter.next break if omg.nil? if omg.has_key? varname if omg[varname].class == FunNod return omg[varname].eval(var.params) return omg[varname] 16
raise "The variable #{varname} is not declared" def set_var(var, varde) valueexist = false omg = Scope.getScope if omg.has_key? var omg[var] = varde valueexist = true elsif iter = ScopeIter.new(omg) while (true) omg = iter.next if (!valueexist && (omg.has_key? var)) omg[var] = varde valueexist = true break break if omg.has_key? "top" if!valueexist raise "The variable #{var} is not declared!" Scope.setScope(omg) def setoldscope(omg) Scope.setScope(omg["omg"]) class SatsNod attr_accessor :stmt, :stmts def initialize(stmt, *stmts) @stmt = stmt @stmts = stmts 17
def eval () if @stmts.nil? @stmt.eval @stmts << @stmt @stmts.each do stmt return stmt if isreturn(stmt) stmt.eval class Program def initialize (stmts) @stmts = stmts def eval if @stmts.respond_to? "each" @stmts.each { stmt stmt.eval} @stmts.eval unless $forbiddenthings.include? @stmts.class nil class DeklVar attr_accessor :var, :varde def initialize (var,typ, varde = nil) @var = var @omg = {} @typ = typ @varde = varde def eval @omg = Scope.getScope 18
@namedvar = @var.getstuff if @varde.nil? case @typ when "int" then @varde = 0 when "float" then @varde = 0 when "string" then @varde = "" when "bool" then @varde = true @varde = @varde.eval unless @varde.class == FunNod @omg[@namedvar] = @varde Scope.setScope(@omg) class UtskriftNod attr_accessor :str def initialize(str) @str =str def eval @omg = Scope.getScope @utskrift = lookup(@str) puts @utskrift class TilldelningNod attr_accessor :var, :tillduttr, :omg def initialize (var, op,uttr) @var = var @tillduttr = uttr @op = op @omg = {} def eval 19
@omg = Scope.getScope varde = lookup(@tillduttr) if @tillduttr.class == VarNod varde = @tillduttr.eval @namedvar = @var.getstuff oldvalue = lookup(@namedvar) if!aritmallowed(oldvalue, varde) raise "Syntax error!!!\n Illegal operation #{oldvalue.class} #{@op} #{varde.class}" case @op when "+=" then set_var(@namedvar, (oldvalue + varde)) when "-=" then set_var(@namedvar, (oldvalue - varde)) when "*=" then set_var(@namedvar, (oldvalue * varde)) when "/=" then set_var(@namedvar, (oldvalue / varde)) when "=" then set_var(@namedvar, varde) when "++" then set_var(@namedvar, (oldvalue + varde)) when "--" then set_var(@namedvar, (oldvalue - varde)) Scope.setScope(@omg) class AritmNod attr_accessor :op1, :op, :op2 def initialize(var1, op, var2) @op1 = var1 @op2 = var2 @op = op def eval case @op when '*' then return (@op1.eval * @op2.eval) when '/' then return (@op1.eval / @op2.eval) when '+' then return (@op1.eval + @op2.eval) when '-' then return (@op1.eval - @op2.eval) 20
class RelationsNod attr_accessor :lho, :op, :rho #left/right hand op def initialize(lho, op, rho) @lho = lho @rho = rho @op = op def eval omg = Scope.getScope case @op when '>' then return (@lho.eval > @rho.eval) when '>=' then return (@lho.eval >= @rho.eval) when '<' then return (@lho.eval < @rho.eval) when '<=' then return (@lho.eval <= @rho.eval) when 'IS' then return (@lho.eval == @rho.eval) when 'ISNOT' then return (@lho.eval!= @rho.eval) #--------Repetion------------------------------ class ForNod attr_accessor :uttr, :tilldelning, :stmt, :omg def initialize(decl,uttryck, tilldelning, stmt) @uttr = uttryck @tilldelning = tilldelning @stmts = stmt @omg = {} @decl = decl def eval @omg = getnewscope Scope.setScope(@omg) @decl.eval while (@uttr.eval) do 21
if @stmts.respond_to? "each" @stmts.each do stmt if isreturn(stmt) setoldscope(@omg) return @stmts @stmt.eval retval = @stmts.eval if isreturn(retval) puts "japp" #TODO setoldscope(@omg) return retval @tilldelning.eval setoldscope(@omg) class WhileNod def initialize(uttryck, *stmts) @uttryck = uttryck @stmts = stmts @omg = {} def eval @omg = getnewscope Scope.setScope(@omg) while(@uttryck.eval) if @stmts.respond_to? "each" @stmts.each do stmt stmt.eval @stmts.eval 22
setoldscope(@omg) #------Villkor----------------------------------- class IfNod attr_accessor :omg def initialize(relutr, elif, els, *sats) @relutr = relutr @sats = sats @elif = elif @els = els @omg = {} def eval @omg = getnewscope Scope.setScope(@omg) runned = false if (@relutr.eval) @sats.each do stmts if stmts.respond_to? "each" #TODO Kontrollera om denna någonsin körs fortfarande stmts.each do stmt if stmts.class == ReturnNod setoldscope(@omg) return stmts stmt.eval if stmts.class == ReturnNod setoldscope(@omg) return stmts ret = stmts.eval if isreturn(ret) setoldscope(@omg) 23
return ret runned = true elsif @elif.each do elif if (elif[0].eval and!runned) elif[1].each do elifstmt if isreturn(elifstmt) setoldscope(@omg) return elifstmt elifstmt.eval unless elifstmt.class == Array runned = true if (!runned) if @els.respond_to? "each" # Om mer än en sats @els.each do enelsesats if enelsesats.class == ReturnNod setoldscope(@omg) return enelsesats enelsesats.eval if @els.class == ReturnNod setoldscope(@omg) return @els @els.eval setoldscope(@omg) 24
# ------- Funktioner class FunNod def initialize(args, stmts) @args = args @stmts = stmts @omg = {} @returnvalue = nil def eval(args) @args = @args.params.flatten #reder ut nästlingen av listan args = args[0].params.flatten if args.length!= @args.length raise "Incorrect number of arguments, expected #{@args.length} got #{params.length}" params.each_with_index do value, index if isnotvar(value) @args[index].varde = IntegerNod.new(value.value) @args[index].varde = IntegerNod.new(lookUp(value)) @omg = getnewscope Scope.setScope(@omg) if @args.respond_to? "each" @args.each { param param.eval} @args.eval @stmts = @stmts.params.flatten if @stmts.respond_to? "params" if @stmts.respond_to? "each" @stmts.each do stmt if stmt.class == ReturnNod @returnvalue = stmt.eval.eval break; returned = stmt.eval if returned.class == ReturnNod 25
return returned.eval returned = @stmts.eval if returned.class == ReturnNod @returnvalue = returned.eval #p @omg setoldscope(@omg) @returnvalue = 0 return @returnvalue class ParamList attr_accessor :params def initialize(*params) @params = params def eval @params def << var @params << var class ReturnNod attr_accessor :value def initialize(value) @value = value def eval @value 26
#--------Datatyper------------------------------ class LogikNod def initialize(lho, op, rho) @lho = lho @rho = rho @op = op def eval case @op when "AND" returnvar = (@lho.eval && @rho.eval) when "OR" returnvar = (@lho.eval @rho.eval) when "ISNOT" returnvar = (@lho.eval!= @rho.eval) when "IS" returnvar = (@lho.eval == @rho.eval) return returnvar class IntegerNod attr_accessor :value def initialize(i) @value = i def eval return @value def to_s "#{@value}" 27
class NegIntegerNod def initialize(i) @neginteger = -i def eval return @neginteger class FloatNod attr_accessor :value def initialize(f) @value = f def eval return @value class StringNod attr_accessor :value def initialize(s) @value = s def eval return @value class BoolNod attr_accessor :value def initialize(b) @value = b 28
def eval return @value class ListNod attr_accessor :value def initialize(lst) @value = lst def eval return @value class HashNod attr_accessor :value def initialize(h) @value = h def eval return @value class NegNod def initialize(i) if i.class == StringNod @in = i.eval.reverse elsif i.class == String @in = lookup(i).reverse @in = -i.eval def eval 29
return @in class VarNod attr_reader :params, :var def initialize(var, *params) @var = var @params = params def eval lookup(self) def getstuff return @var # Funktion som kontrollerar om aritm operation är tillåten mellan två operander def aritmallowed(lho, rho) if lho.class == rho.class then return true elsif lho.class == NilClass then return true elsif lho.class == TrueClass && rho.class == FalseClass then return true elsif lho.class == FalseClass && rho.class == TrueClass then return true return false def isreturn(stmt) if stmt.class == ReturnNod return true return false def isnotvar(var) return true if var.class == StringNod return true if var.class == IntegerNod 30
return true if var.class == FloatNod return true if var.class == BoolNod return true if var.class == AritmNod return true if var.class == ParamList return true if var.class == LogikNod return true if var.class == RelationsNod return false Jail.rb #!/usr/bin/env ruby # * coding: utf 8 * require './opt/.jail/rdparse' require './opt/.jail/node' class Jail # Just Another Interpreted Language JAIL # attr_accessor def initialize @looper = Loop.new @jailparser = Parser.new("jail breaker") do #Lexer token(/\s+/) # Whitespaces token(/\d+\.\d+/) { z z.to_f} #Float token(/\d+/) { z z.to_i} # Integers #token(/"[^;^,^\(^\)^\"]*"/) { z z.to_s} # Strings token(/\#.*/) # Comments token(/[a Za z]+/) { z z} # Chars token(/./) { z z} #Alles # ze prgrammmm start :program do match(:satser) { satser Program.new(satser).eval} rule :satser do match(:sats) { sats sats} match(:satser, :sats) { satser, sats SatsNod.new(sats, satser)} 31
rule :sats do match(:return){ z z} match(:repetition) { z z} match(:deklareravar) { z z} match(:deklarerafunktion) { z z} match(:tilldelning) { z z} match(:utmatning) { z z} match(:inmatning) { z z} match(:villkor) { z z} match(:funktionsanrop) { z z} rule :funcsatser do match(:funcsats) { sats ParamList.new(sats)} match(:funcsatser, :funcsats) { satser, sats ParamList.new( satser << sats)} rule :funcsats do match(:repetition) { z z} match(:deklareravar) { z z} match(:deklarerafunktion) { z z} match(:tilldelning) { z z} match(:utmatning) { z z} match(:inmatning) { z z} match(:villkor) { z z} match(:return) { z z} #match(:funktionsanrop) { z z} rule :deklareravar do match(:datatyp, :var, ";") { m, n, _ DeclVar.new(n)} match(:datatyp, :var, "=", :uttryck, ";"){ m,n,_,z, DeclVar.new(n,z)} rule :tilldelning do match(:var, :tilldelningsop, :uttryck, ";") { var, op, uttr, _ TilldelningNod.new(var, op, uttr)} match(:var, "+", "+", ";") { var, op1, op2, _ TilldelningNod.new(var,op1+op2, IntegerNod.new(1))} match(:var, " ", " ", ";") { var, op1, op2, _ TilldelningNod.new(var, op1+op2, IntegerNod.new(1))} 32
rule :utmatning do match("output", :uttryck, ";") { _, z, _ UtskriftNod.new(z)} rule :inmatning do match("input", :var) rule :villkor do match("if", "(", :predikatuttryck, ")", :satser, "if"){ _, _, z, _, x, _ IfNod.new(z, Array.new,Array.new, x)} match("if", "(", :predikatuttryck, ")", :satser, :elifs, "", :satser, "if") { _, _, ifrel, _, ifsatser, elifs, _, satser, _ IfNod.new(ifrel,[elifs],satser,ifsatser)} match("if", "(", :predikatuttryck, ")", :satser, :elifs, "if") { _, _, ifrel, _, ifsatser, elifs, _ IfNod.new(ifrel, [elifs], Array.new, ifsatser)} match("if", "(", :predikatuttryck, ")", :satser, "", :satser, "if") { _, _, ifrel, _, ifsatser, _, satser, _ IfNod.new(ifrel, Array.new, satser, ifsatser)} rule :elifvillkor do match("elif", "(", :predikatuttryck, ")", :satser) { _, _, z, _, x [z,[ x]]} rule :elifs do match(:elifvillkor) match(:elifs, :elifvillkor) { _, z z} # Repetition rule :repetition do match(:forloop) match(:whileloop) rule :whileloop do match("while", "(", :predikatuttryck, ")", :satser, "while") { _, _, preduttr, _, satser, _ WhileNod.new(predUttr, satser)} rule :forloop do match("for", "(",:deklareravar, :relationsuttryck, ";",:tilldelning, ")", :satser, "for"){ _,_,decl,rutr,_, tilldelning, _, sats,_ ForNod.new(decl,rutr,tilldelning,sats)}# Mer grejs här 33
rule :newline do match(/\\n/) { z z} rule :uttryck do match(:funktionsanrop) match(:predikatuttryck) match(:additionsuttryck) match(:string) rule :additionsuttryck do match(:multiuttryck) match(:additionsuttryck, "+", :multiuttryck) { a, b, c AritmNod.new(a,b,c)} match(:additionsuttryck, " ", :multiuttryck) { a, b, c AritmNod.new(a,b,c)} rule :multiuttryck do match(:aritmparentes) match(:multiuttryck, "*", :multiuttryck) { a, b, c AritmNod.new(a,b,c)} match(:multiuttryck, "/", :multiuttryck) { a, b, c AritmNod.new(a,b,c)} rule :relationsuttryck do match(:relationsoperand, :relationsop, :relationsoperand) { a,b,c RelationsNod.new(a,b,c)} rule :relationsoperand do match(:additionsuttryck) match(:var) match(:tal) rule :logikuttryck do match(:boolean) match(:logikoperand, :logikop, :logikuttryck) { a,b,c LogikNod.new(a,b,c)} match(:logikoperand, :logikop, :logikoperand) { a,b,c LogikNod.new(a,b,c)} 34
rule :logikoperand do match(:relationsuttryck) match(:boolean) rule :predikatuttryck do match(:logikuttryck) match(:relationsuttryck) rule :logikop do match("and") match("or") rule :relationsop do match(">", "=") { z,x z+x} match("<", "=") { z,x z+x} match("is") { z z} match(">") match("<") match("isnot") { z z} rule :tilldelningsop do match("*", "=") { z,x z+x} match("/", "=") { z,x z+x} match("+", "=") { z,x z+x} match(" ", "=") { z,x z+x} match("=") rule :datatyp do match("int") match("string") match("bool") match("float") 35
rule :deklarerafunktion do match("fn", :var, "(", :parametrar, ")", :funcsatser, "fn") { _, funknamn, _, param, _, satser, _ DeclVar.new(funkNamn, FunNod.new(param, satser))} rule :parametrar do match(:parameter) { para ParamList.new(para)} match(:parametrar, :parameter) { para1, para2 ParamList.new(para1 << para2)} rule :parameter do match(:deklareravar) rule :funktionsanrop do match(:var, "(", :arguments, ")", ";") { var, _, arguments, _, _ VarNod.new(var.var, arguments)} rule :arguments do match(:argument) { arg ParamList.new(arg)} match(:arguments, ",", :argument) { arg1, _, arg2 ParamList.new(arg1 << arg2)} rule :argument do match(:uttryck) rule :return do match("return", :uttryck, ";") { _, uttryck,_ ReturnNod.new(uttryck)} rule :boolean do match("true") { _ BoolNod.new(true)} match("false") { _ BoolNod.new(false)} 36
rule :aritmparentes do match("(", :additionsuttryck, ")") { _, z, _ z} match(:var) match(:tal) rule :var do match(/^[a za Z]+/) { m VarNod.new(m)} match(" ", /^[a za Z]+/) { _, z NegNod.new(z)} rule :string do #match(string) { z StringNod.new(z)} rule :tal do match(float) { z FloatNod.new(z)} match(" ", Float) { _, z NegNod.new(z)} match(integer) { z IntegerNod.new(z)} match(" ", Integer) { _, z NegNod.new(z)} def done(str) ["quit","exit","bye",""].include?(str.chomp) def loop (str) if( str.start_with? "if" or str.start_with? "for" or str.start_with? "while" or str.start_with? "fn") return true return false def start print "<JAIL>" str = gets #@jailparser.logger.level = Logger::WARN 37
if done(str) elsif loop(str) @looper.read(str) until @looper.done str = gets @looper.read(str) begin puts "#{@jailparser.parse @looper.ret_string}" rescue Exception => e puts e.message @looper.reset start #print "#{@jailparser.parse str}" begin @jailparser.parse str rescue Exception => e puts e.message start def run(file = nil) @file = File.open(file, "r") unless file.nil? @jailparser.logger.level = Logger::WARN str = @file.gets if str.nil? @file.close elsif loop(str) @looper.read(str) until @looper.done str = @file.gets @looper.read(str) begin 38
@jailparser.parse(@looper.ret_string) rescue Exception => e puts e.message @looper.reset run begin @jailparser.parse str rescue Exception => e puts e.message run class Loop # # En hjälpklass för att ta han om nästlingen av loopar. # attr_reader :ret_string def initialize @counter = 0 @ret_string = String.new def read (str) @ret_string += str if (str.start_with? "if" or str.start_with? "for" or str.start_with? "while" or str.start_with? "fn") @counter += 1 elsif (str._with? "if\n" or str._with? "for\n" or str._with? "while\n" or str._with? "fn\n") @counter = 1 def done 39
return @counter == 0 def reset @ret_string = "" @counter = 0 if ARGV.length == 1 filename = ARGV[0] if File.exist? filename Jail.new.run(filename) puts "The file #{filename} does not exist" Jail.new.start Install #!/bin/bash sudo cp./jail.rb /usr/bin/jail sudo chmod +x /usr/bin/jail sudo mkdir p /opt/.jail sudo cp./node.rb /opt/.jail/node.rb sudo cp./rdparse.rb /opt/.jail/rdparse.rb 40