├── README ├── rapport ├── kapitel │ ├── teori.tex │ ├── forkortningslista.tex │ ├── kallor.tex │ ├── forord.tex │ ├── appendix.tex │ ├── slutsatser.tex │ ├── sammanfattning.tex │ ├── abstract.tex │ ├── diskussion.tex │ ├── metod.tex │ ├── inledning.tex │ └── resultat.tex ├── image1.png ├── parser_1.png ├── hiji_screen3.png ├── opponering │ ├── opponering.pdf │ └── opponering.tex ├── rapport.tex ├── kallor.bib └── MasterThesis.tex ├── haskell.js ├── haskell.utilities.js ├── hiji.css ├── typecheckertests.html ├── test.htm ├── hiji.html ├── haskell.hiji.primitives.js ├── hs └── Prelude.hs ├── typecheckertests.js ├── lib ├── jquery.cookie.js ├── json2.js └── jsparse.js ├── haskell.primitives.js~ ├── haskell.interpreter.js ├── haskell.hiji.js ├── haskell.primitives.js └── haskell.ast.js /README: -------------------------------------------------------------------------------- 1 | Yeeeah, you read me! 2 | -------------------------------------------------------------------------------- /rapport/kapitel/teori.tex: -------------------------------------------------------------------------------- 1 | \section{Teori} 2 | -------------------------------------------------------------------------------- /rapport/kapitel/forkortningslista.tex: -------------------------------------------------------------------------------- 1 | \section{Ordlista} 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /rapport/kapitel/kallor.tex: -------------------------------------------------------------------------------- 1 | \section{Källor} 2 | 3 | 4 | nigger stole my bike 5 | -------------------------------------------------------------------------------- /rapport/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johang88/haskellinjavascript/HEAD/rapport/image1.png -------------------------------------------------------------------------------- /rapport/parser_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johang88/haskellinjavascript/HEAD/rapport/parser_1.png -------------------------------------------------------------------------------- /rapport/hiji_screen3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johang88/haskellinjavascript/HEAD/rapport/hiji_screen3.png -------------------------------------------------------------------------------- /rapport/opponering/opponering.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johang88/haskellinjavascript/HEAD/rapport/opponering/opponering.pdf -------------------------------------------------------------------------------- /rapport/kapitel/forord.tex: -------------------------------------------------------------------------------- 1 | \section{Förord} 2 | TODO: Här tackar vi de som på ett eller annat sätt 3 | hjälpt oss med projektet... 4 | -------------------------------------------------------------------------------- /haskell.js: -------------------------------------------------------------------------------- 1 | var haskell = { 2 | parser: {}, 3 | interpreter: {}, 4 | ast: {}, 5 | primitives: {}, 6 | utilities: {}, 7 | typechecker: {} 8 | }; 9 | -------------------------------------------------------------------------------- /rapport/kapitel/appendix.tex: -------------------------------------------------------------------------------- 1 | \appendix 2 | \section{Bidragsrapport} 3 | Alla gruppmedlemmar var delaktiga i likvärdig utsträckning under planeringsfasen. Större delen av projektet har genomdrivits gemensamt men varje person har haft en inriktning enligt projektets olika delar. Johan Gustafsson har haft ansvar över parsern, Mikael Bung har ansvarat för interpretatorn och det abstrakta syntaxträdet, Mattis Jeppsson för typcheckaren och Adam Bengtsson för HIJi. Rapporten har skrivits enligt samma uppdelning men med ett övergripande ansvar av Adam Bengtsson. Arbetet har i så stor utsträckning som möjligt skett gemensamt och vi har därför både bidragit till och tagit del av varandras respektive ansvarsområde. 4 | -------------------------------------------------------------------------------- /haskell.utilities.js: -------------------------------------------------------------------------------- 1 | (function(utilities){ 2 | utilities.expectType = function(o,t) { 3 | if (!(o instanceof t)) { 4 | throw new Error("Expected " + typeof t + " " + typeof o + " given."); 5 | }; 6 | }; 7 | 8 | utilities.expectTypeOf = function(o, t) { 9 | if ((typeof o) != t) { 10 | throw new Error("Expected " + t + ", " + typeof o + " given."); 11 | }; 12 | }; 13 | 14 | utilities.expectTypeArray = function(os, t) { 15 | for (i in os) { 16 | if (!(os[i] instanceof t)) { 17 | throw new Error("Expected " + typeof t + ", " + typeof os[i] + " given at index " + i); 18 | }; 19 | }; 20 | }; 21 | })(haskell.utilities); 22 | 23 | if (!console) { 24 | var console = { 25 | log: function() {} 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /rapport/kapitel/slutsatser.tex: -------------------------------------------------------------------------------- 1 | \section{Slutsatser} 2 | I den här rapporten har vi visat hur vi har implementerat en haskelltolk Javascript. 3 | Vi har implementerat namngivna och anonyma funktioner, algebraiska datatyper och pattern matching. 4 | Vi har dock inte implementerat typklasser, men stöd för det finns i typcheckaren och parsern. 5 | 6 | Vi har beskrivit hur vi med parser combinators har implementerat Haskells layoutregler och grammatik, en typcheckare som kan typchecka Haskell enligt de regler som haskellstandarden föreskriver samt en interpretator som tolkar Haskell med lat evaluering. Slutligen beskriver vi HIJi, ett enkelt användargränssnitt till haskelltolken. 7 | 8 | En rad förbättringsåtgärder har identifierats för att göra tolken mer användbar för nybörjare. Främst handlar det om att göra ett snyggt interaktivt användargränssnitt till HIJi, men även att generera bättre felmeddelanden i parsern. 9 | Totalt sett har projektet visat sig vara lyckat, även om vi är medvetna om att mycket finns kvar att förbättra. 10 | -------------------------------------------------------------------------------- /hiji.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #000000; 3 | } 4 | 5 | .ghci-look * { 6 | margin:0; 7 | padding:0; 8 | font-family:monospace; 9 | font-size: 12px; 10 | } 11 | 12 | .ghci-look { 13 | cursor:text; 14 | } 15 | 16 | .ghci-look ol{ 17 | width:800px; 18 | height:400px; 19 | background-color: #000; 20 | overflow-x:auto; 21 | overflow-y:scroll; 22 | list-style-type:none; 23 | overflow: hidden; 24 | color: #fff; 25 | } 26 | 27 | .ghci-look .input input { 28 | border:0; 29 | display:inline-block; 30 | margin-left:-1px; 31 | background-color: #000; 32 | width : 90%; 33 | color: #fff; 34 | font-size: 12px; 35 | outline: none; 36 | } 37 | 38 | .ghci-look .modules { 39 | display:inline; 40 | } 41 | 42 | .ghci-look .modules li { 43 | display:inline; 44 | padding-right:0.5em; 45 | } 46 | 47 | .ghci-look .modules li:last-child { 48 | padding-right:0; 49 | } 50 | 51 | .ghci-look .modules:after { 52 | content:">"; 53 | padding-right:0.5em; 54 | } 55 | -------------------------------------------------------------------------------- /rapport/kapitel/sammanfattning.tex: -------------------------------------------------------------------------------- 1 | \renewcommand{\abstractname}{Sammanfattning} 2 | 3 | \begin{abstract} 4 | 5 | Haskell är ett programmeringsspråk med en begränsad användarbas. Vår förhoppning är att göra det enklare för programmerare att lära sig Haskell genom att implementera språket i Javascript. På så vis blir det möjligt att köra Haskell i en webbläsare utan att behöva ladda ner en haskellkompilator, som till exempel \emph{Glasgow Haskell Compiler} (GHC). 6 | I den här rapporten beskriver vi vårt arbete och resultat av att implementera Haskell i Javascript. 7 | \\ 8 | \\ 9 | Resultatet består av en parser, typcheckare, interpretator och ett användargränssnitt liknande GHCi. 10 | Parsern tar användarens indata och konverterar den till en intern datastruktur, kallat abstrakt syntaxträd. Typcheckaren analyserar sedan det abstrakta syntaxträdet för att kontrollera att det ej förekommer några typfel. Om inga fel har påträffats så skickas trädet vidare till interpretatorn som tolkar trädet på ett väldefinierat vis. 11 | \\ 12 | \\ 13 | Resultatet visar att det är möjligt att implementera Haskell i Javascript, men det behövs mycket mer arbete för att skapa en nybörjarvänlig miljö att lära sig Haskell i. 14 | 15 | \end{abstract} 16 | -------------------------------------------------------------------------------- /rapport/kapitel/abstract.tex: -------------------------------------------------------------------------------- 1 | \renewcommand{\abstractname}{Abstract} 2 | \begin{abstract} 3 | Haskell is not a widely used programming language, nor very known to the average programmer. By implementing a subset of the Haskell 98 specification in Javascript our intention is to make it possible to run Haskell in a web browser, thus making it easier for beginners to try Haskell by eliminating the need of downloading Haskell compilers such as the Glasgow Haskell Compiler (GHC). 4 | In this paper we describe the process and result from the project of implementing Haskell in Javascript. 5 | \\ 6 | \\ 7 | Our result consists of a parser, type checker, interpreter and a front end similar to GHCi. 8 | The parser processes the text input, creating an internal structure called abstract syntax tree (AST). The type checker then analysis the AST to confirm that there are no type errors. If no errors have been detected, the AST is sent to the interpreter. The interpreter then interprets the AST in a well defined way. 9 | \\ 10 | \\ 11 | The results show that it is possible to successfully implement Haskell in Javascript, but a lot of more work needs to be done for making a beginner-friendly environment for learning Haskell. 12 | 13 | \end{abstract} 14 | -------------------------------------------------------------------------------- /typecheckertests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test.htm: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Haskelltolk i Javascript 7 | 8 | 9 | 10 | 11 | 12 | 13 | 37 | 38 | 39 |
This is a test page
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /hiji.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | HIJi (GHCi like) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /rapport/rapport.tex: -------------------------------------------------------------------------------- 1 | \documentclass[a4paper, 12pt]{article} 2 | 3 | \usepackage{float} 4 | \usepackage[T1]{fontenc} 5 | \usepackage[utf8]{inputenc} 6 | \usepackage[swedish]{babel} 7 | \usepackage{graphicx} 8 | \usepackage{natbib} % ger Harvard-referenser 9 | \usepackage{graphicx} 10 | \usepackage{listings} 11 | \usepackage{color} 12 | 13 | \setlength{\parskip}{12pt} 14 | \setlength{\parindent}{0pt} 15 | 16 | 17 | \begin{document} 18 | \title{Haskell in Javascript} 19 | \author{ 20 | Adam Bengtsson, 21 | Mikael Bung, 22 | Johan Gustafsson, 23 | Mattis Jeppsson 24 | \\ 25 | Institutionen för data- och informationsteknik 26 | \\ 27 | Chalmers Tekniska Högskola 28 | \\ 29 | 41296 Göteborg 30 | } 31 | \date{\today} 32 | \maketitle 33 | \thispagestyle{empty} 34 | \newpage 35 | \input{kapitel/sammanfattning.tex} 36 | \thispagestyle{empty} 37 | \newpage 38 | \input{kapitel/abstract.tex} 39 | \thispagestyle{empty} 40 | \newpage 41 | 42 | \thispagestyle{empty} 43 | \tableofcontents 44 | \thispagestyle{empty} 45 | \thispagestyle{empty} 46 | \newpage 47 | 48 | \listoffigures 49 | \thispagestyle{empty} 50 | % include all parts 51 | 52 | % \input{kapitel/forkortningslista.tex} 53 | \newpage 54 | \setcounter{page}{1} 55 | \input{kapitel/inledning.tex} 56 | \newpage 57 | \input{kapitel/teori.tex} 58 | \newpage 59 | \input{kapitel/metod.tex} 60 | \newpage 61 | \input{kapitel/resultat.tex} 62 | \newpage 63 | \input{kapitel/diskussion.tex} 64 | \newpage 65 | \input{kapitel/slutsatser.tex} 66 | \newpage 67 | 68 | 69 | % källor 70 | \bibliographystyle{plainnat} 71 | \bibliography{kallor} 72 | 73 | \end{document} 74 | -------------------------------------------------------------------------------- /haskell.hiji.primitives.js: -------------------------------------------------------------------------------- 1 | (function(primitives, ast, interpreter){ 2 | 3 | var showResult = function(result) { 4 | if (result.type == "Data") { 5 | var str = result.identifier; 6 | var op = " "; 7 | 8 | if (str == "I#") { 9 | str = ""; 10 | } else if (str == ":") { 11 | str = ""; 12 | op = ","; 13 | } 14 | 15 | if (result.ptrs) { 16 | var first = true; 17 | for (var i = 0; i < result.ptrs.length; i++) { 18 | if (str.length == 0 && first) { 19 | str = showResult(result.ptrs[i].dereference()); 20 | if (typeof str.str != "undefined") 21 | str = str.str; 22 | first = false; 23 | } else { 24 | var res = showResult(result.ptrs[i].dereference()); 25 | if (typeof res.str != "undefined") 26 | res = res.str; 27 | str = str + op + res; 28 | } 29 | } 30 | } 31 | 32 | return { str: str, isList: op == "," }; 33 | } if (result.force) { 34 | return result.force(); 35 | } else if (result.ptrs) { 36 | return result.ptrs[0].dereference(); 37 | } else { 38 | return result; 39 | } 40 | }; 41 | 42 | 43 | primitives.initHiji = function(env) { 44 | env.bind("hijiContinuation#", primitives.createPrimitive(env, 0, 45 | function(env, args) { 46 | return new interpreter.Data("IO", [new interpreter.HeapPtr(env)]); 47 | })); 48 | 49 | // hijiOutputLine# :: a -> IO () 50 | env.bind("hijiOutputLine#", primitives.createPrimitive(env, 1, 51 | function(env, args) { 52 | var arg = args[0]; 53 | var result = showResult(arg); 54 | if (result.isList) { 55 | result = result.str; 56 | result = result.substring(0, result.length - 3); 57 | result = "[" + result + "]"; 58 | } else if (typeof result.str != "undefined") { 59 | result = result.str; 60 | } 61 | printArea.append($("
  • ").text(result.toString())); 62 | return new interpreter.Data("IO", [new interpreter.HeapPtr(new interpreter.Closure(env, new ast.VariableLookup("()")))]); 63 | })); 64 | }; 65 | })(haskell.primitives, haskell.ast, haskell.interpreter); -------------------------------------------------------------------------------- /hs/Prelude.hs: -------------------------------------------------------------------------------- 1 | {-#MagicHash#-} 2 | module Prelude where 3 | 4 | data Bool = True | False 5 | 6 | data IO a = IO a 7 | 8 | infixl 6 + 9 | infixl 6 - 10 | infixl 7 * 11 | infixr 0 $ 12 | infixr 9 . 13 | infixr 2 || 14 | infixr 3 && 15 | 16 | ($) f x = f x 17 | (.) f g = \x -> f $ g x 18 | 19 | (&&) False _ = False 20 | (&&) True y = y 21 | 22 | (||) x y = case x of 23 | True -> True 24 | False -> y 25 | 26 | not x = case x of 27 | True -> False 28 | False -> True 29 | 30 | otherwise :: Bool 31 | otherwise = True 32 | 33 | id x = x 34 | 35 | 36 | map f xs = case xs of 37 | [] -> [] 38 | (x:xs) -> f x : map f xs 39 | 40 | (++) [] ys = ys 41 | (++) (x:xs) ys = x : (xs ++ ys) 42 | 43 | concat :: [[a]] -> [a] 44 | concat [] = [] 45 | concat (x:xs) = x ++ concat xs 46 | 47 | foldr1 f xs = case xs of 48 | [x] -> x 49 | (x:xs) -> f x $ foldr1 f xs 50 | 51 | foldr _ b [] = b 52 | foldr f b (x:xs) = foldr f (f x b) xs 53 | 54 | foldl _ b [] = b 55 | foldl f b (x:xs) = foldl f (f b x) xs 56 | 57 | reverse = foldl (\a b -> b:a) [] 58 | 59 | flip f a b = f b a 60 | 61 | filter _ [] = [] 62 | filter f (x:xs ) | f x = x : filter f xs 63 | | otherwise = filter f xs 64 | 65 | iterate f x = f x : iterate f (f x) 66 | 67 | zipWith f (a:as) (b:bs) = f a b : zipWith f as bs 68 | zipWith _ _ _ = [] 69 | 70 | head xs = case xs of 71 | (x:_) -> x 72 | 73 | tail xs = case xs of 74 | (_:xs) -> xs 75 | 76 | fix f = let x = f x in x 77 | 78 | fib n = case n of 79 | 0 -> 0 80 | 1 -> 1 81 | _ -> fib (n-2) + fib (n-1) 82 | 83 | data Int = I# Int# 84 | 85 | (+) :: Num a => a -> a -> a 86 | (+) (I# i1) (I# i2) = I# (i1 +# i2) 87 | 88 | (-) (I# i1) (I# i2) = I# (i1 -# i2) 89 | 90 | (*) (I# i1) (I# i2) = I# (i1 *# i2) 91 | 92 | (==) (I# i1) (I# i2) = i1 ==# i2 93 | 94 | (>) (I# i1) (I# i2) = i1 ># i2 95 | 96 | (<) (I# i1) (I# i2) = i1 <# i2 97 | 98 | (<=) (I# i1) (I# i2) = i1 <=# i2 99 | 100 | (>=) (I# i1) (I# i2) = i1 >=# i2 101 | 102 | (%) (I# i1) (I# i2) = I# (remInt# i1 i2) 103 | 104 | stepDebug = stepDebug# 105 | 106 | data Maybe a = Just a | Nothing 107 | 108 | concatMap f xs = concat (map f xs) 109 | 110 | 111 | -- IO monad -_- 112 | (>>) m a = m >>= (\_ -> a) 113 | 114 | (>>=) (IO v) f = f v 115 | 116 | return a = IO a 117 | 118 | fail _ = [] 119 | 120 | -- But Monad Zero is for list monad... 121 | guard True = [undefined] 122 | guard False = mzero 123 | mzero = [] 124 | 125 | 126 | 127 | undefined = undefined 128 | 129 | 130 | catMaybes [] = [] 131 | catMaybes (Nothing:xs) = catMaybes xs 132 | catMaybes ((Just a):xs)= a : catMaybes xs 133 | 134 | alert = alert# 135 | 136 | double m = do 137 | let doubleFunc = (*2) 138 | x <- m 139 | return (doubleFunc x) 140 | 141 | 142 | take 0 _ = [] 143 | take n (x:xs) = x : take (n-1) xs 144 | take _ [] = [] 145 | 146 | length [] = 0 147 | length (_:xs) = 1 + length xs 148 | 149 | 150 | const r _ = r 151 | 152 | -- Enum functions only for int so far, awaiting type classes 153 | enumHelper i p n = case p n of 154 | True -> [] 155 | False -> n : enumHelper i p (n+i) 156 | 157 | enumFrom e1 = enumHelper 1 (const False) e1 158 | 159 | enumFromThen e1 e2 = enumHelper (e2-e1) (const False) e1 160 | 161 | enumFromTo e1 e3 = enumHelper 1 (>e3) e1 162 | 163 | enumFromThenTo e1 e2 e3 = enumHelper (e2-e1) (>e3) e1 164 | -------------------------------------------------------------------------------- /rapport/opponering/opponering.tex: -------------------------------------------------------------------------------- 1 | \documentclass[a4paper, 12pt]{article} 2 | 3 | \usepackage{float} 4 | \usepackage[T1]{fontenc} 5 | \usepackage[utf8]{inputenc} 6 | \usepackage[swedish]{babel} 7 | \usepackage{graphicx} 8 | \usepackage{natbib} % ger Harvard-referenser 9 | \usepackage{graphicx} 10 | \usepackage{listings} 11 | \usepackage{color} 12 | 13 | \setlength{\parskip}{12pt} 14 | \setlength{\parindent}{0pt} 15 | 16 | 17 | \hyphenpenalty=10000 18 | \tolerance=5000 19 | 20 | \begin{document} 21 | 22 | \title{Opponering på Databases for Dummies 2} 23 | \author{ 24 | Adam Bengtsson, 25 | Mikael Bung, 26 | Johan Gustafsson, 27 | Mattis Jeppsson 28 | } 29 | \date{\today} 30 | \maketitle 31 | 32 | 33 | \emph{Databases for dummies 2} är en rapport som beskriver ett program som ska användas för att administrera databaser, utan att som användare behöva några större förkunskaper. Vi har läst rapporten i opponeringssyfte och har en del tankar kring dess innehåll. 34 | 35 | Överlag tycker vi rapporten var intressant och väldigt väl beskriver arbetsgången av projektet. Vi är positiva till att alla användarfall med högsta prioritet framgångsrikt har implementerats. Programmets syfte, att underlätta databashantering, ser ut att vara uppfyllt. Det är dock en del stycken i rapporten som vi skulle vilja få mer utförligt förklarade. 36 | 37 | Från rapporten har vi fått uppfattningen att en hel del förkunskaper om databaser krävs för att kunna använda programmet. Detta får oss att undra om inte alternativa representationer av databaser skulle förenkla för nybörjare. I rapporten beskrivs ej vilka konceptuella modeller som har utforskats för att representera databaser. Till exempel att använda ER-diagram för att på ett grafiskt sätt beskriva databaser och relationer mellan dessa för användare och eliminera svårbegripliga begrepp som till exempel \emph{foreign keys}. 38 | 39 | Vi är också fundersamma över designbeslutet att bara använda sig av en View-klass. I rapporten, sidan 7, rad 9, nämns det att en namnkonvention för att kunna navigera i View-filen var nödvändig då den växt ur proptotion och därmed blivit svår att navigera i. Som motivering till beslutet att inte dela upp View-klassen i flera mindre klasser, nämner rapporten att det vore alltför tidskrävande utan att närmare gå in på orsakerna till detta. 40 | 41 | Det grafiska användargränssnittet som beskrivs har ett modernt och profesionellt utseende, samtidigt som det ser tillräckligt enkelt ut för att användas av nybörjare med tillräckliga förkunskaper. Programmet ser självförklarande ut och liknar andra Windowsprogram i menyer vilket är en fördel när det riktar sig till nybörjare. 42 | 43 | Vidare beskrivs i kapitel 5. Discussion, sidan 22, rad 14, att till en början användes en enda controller-klass. Den hade prestandaproblem på grund av många jämförelser i sin action-listener vilket skapade fördröjningar på flera sekunder. I rapporten hävdas att prestandeproblemen upphörde när controller-klassen delades upp i flera mindre klasser. Vi är fundersamma över hur dessa prestandaproblem var möjliga och skulle gärna se en mer utförlig förklaring kring problemet. 44 | 45 | Vi har även synpunkter på att det i teorikapitlet, sidan 2, finns ett avsnitt som beskriver vad ER-diagram är, utan att rapporten som följer använder dessa. Detta medför att det nämnda avsnittet blir överflödigt för förståelsen av rapporten. 46 | 47 | Slutligen vill vi säga att rapporten har en bra struktur och beskriver programmet som utvecklades på ett fullt tillräckligt vis. 48 | \end{document} 49 | -------------------------------------------------------------------------------- /typecheckertests.js: -------------------------------------------------------------------------------- 1 | var ast = haskell.ast; 2 | var typechecker = haskell.typechecker; 3 | 4 | var astt = new ast.Module( 5 | [ 6 | new ast.Variable( 7 | new ast.VariableBinding("inc"), 8 | new ast.Lambda( 9 | new ast.VariableBinding("x"), 10 | new ast.Application( 11 | new ast.Application( 12 | new ast.VariableLookup("+"), 13 | new ast.VariableLookup("x")), 14 | new ast.Constant(new ast.Num(1))))), 15 | new ast.Variable( 16 | new ast.VariableBinding("inc2"), 17 | new ast.Lambda( 18 | new ast.VariableBinding("x"), 19 | new ast.Application( 20 | new ast.VariableLookup("inc"), 21 | new ast.Application( 22 | new ast.VariableLookup("inc"), 23 | new ast.VariableLookup("x"))))), 24 | new ast.Variable( 25 | new ast.VariableBinding("main"), 26 | new ast.Application( 27 | new ast.VariableLookup("alert"), 28 | new ast.Application( 29 | new ast.VariableLookup("inc2"), 30 | new ast.Constant(new ast.Num(2))))) 31 | ]); 32 | 33 | var asttt = new ast.Application( 34 | new ast.Application( 35 | new ast.VariableLookup("+"), 36 | new ast.VariableLookup("x")), 37 | new ast.Constant(new ast.Num(1))); // ((x + :: (Num -> Num)) 1 :: Num) 38 | /* 39 | * Kinds 40 | * 41 | */ 42 | (function() { 43 | fireunit.compare( 44 | new typechecker.Star().toString(), 45 | "*", 46 | "Star is *"); 47 | fireunit.compare( 48 | new typechecker.Kfun( 49 | new typechecker.Star(), 50 | new typechecker.Star()).toString(), 51 | "*->*", 52 | "Kfun Star Star is *->*"); 53 | fireunit.compare( 54 | new typechecker.Kfun( 55 | new typechecker.Kfun( 56 | new typechecker.Star(), 57 | new typechecker.Star()), 58 | new typechecker.Star()).toString(), 59 | "(*->*)->*", 60 | "Kfun Kfun Star Star Star is (*->*)->*"); 61 | fireunit.compare( 62 | new typechecker.Kfun( 63 | new typechecker.Star(), 64 | new typechecker.Kfun( 65 | new typechecker.Star(), 66 | new typechecker.Star())).toString(), 67 | "*->(*->*)", 68 | "Kfun Star Kfun Star Star is *->(*->*)"); 69 | }) (); 70 | 71 | /* 72 | * Num 73 | * 74 | */ 75 | (function() { 76 | fireunit.compare( 77 | new ast.Num(1).infer(typechecker.emptyEnv()).toString(), 78 | "Num a1", 79 | "1 is Num a1" 80 | ); 81 | fireunit.compare( 82 | new ast.Num(1).infer(typechecker.emptyEnv()).class(), 83 | "Num", 84 | "1 is in Num"); 85 | fireunit.compare( 86 | new ast.Num(1).infer(typechecker.emptyEnv()).type().id(), 87 | "a1", 88 | "1 is a typevariable named a1"); 89 | }) (); 90 | 91 | /* 92 | * VariableLookup 93 | * For example, using 94 | * the Qual and Pred datatypes, the type (Num a) => a -> Int 95 | * can be represented by: 96 | * [IsIn "Num" (TVar (Tyvar "a" Star))] :=> (TVar (Tyvar "a" Star ) `fn` tInt) 97 | * 98 | */ 99 | 100 | (function() { 101 | fireunit.compare( 102 | new ast.VariableLookup("x").infer( 103 | new typechecker.Environment( 104 | {x: new typechecker.Scheme( 105 | [new typechecker.Pred( 106 | "Num", 107 | new typechecker.TVar("a3", new typechecker.Star()))], 108 | new typechecker.TVar("a3", new typechecker.Star()))})).toString(), 109 | "Num a3", 110 | "x is Num a3"); 111 | })(); 112 | 113 | /* 114 | * Const 115 | * 116 | */ 117 | (function() { 118 | })(); 119 | 120 | /* 121 | * Type schemes 122 | * 123 | */ 124 | (function() { 125 | }) (); 126 | 127 | 128 | /* 129 | * NameGen 130 | * 131 | */ 132 | (function() { 133 | fireunit.compare( 134 | new typechecker.NameGen(2).next({}), 135 | "a2", 136 | "should generate next free var"); 137 | fireunit.compare( 138 | new typechecker.NameGen(2).next({"a2":true}), 139 | "a3", 140 | "should get first free varname"); 141 | fireunit.compare( 142 | typechecker.emptyEnv().nextName(), 143 | "a1", 144 | "an environment has an associated name generator"); 145 | }) (); 146 | 147 | fireunit.testDone(); 148 | -------------------------------------------------------------------------------- /rapport/kallor.bib: -------------------------------------------------------------------------------- 1 | @misc{csharp, 2 | author = "A. Hejlsberg, M. Torgersen", 3 | title = "Overview of C# 3.0", 4 | year = 2007, 5 | url = "http://msdn.microsoft.com/en-us/library/bb308966.aspx", 6 | note = "Hämtad 2010-05-15" 7 | } 8 | 9 | @misc{ghc, 10 | title = "Glasgow Haskell Compiler", 11 | url = "http://www.haskell.org/ghc/", 12 | year = 2010, 13 | note = "Hämtad 2010-05-18" 14 | } 15 | 16 | @misc{json, 17 | title = "The application/json Media Type for JavaScript Object Notation (JSON)", 18 | author = "D. Crockford", 19 | url = "http://www.ietf.org/rfc/rfc4627.txt?number=4627", 20 | year = 2006, 21 | note = "Hämtad 2010-05-15" 22 | } 23 | 24 | @misc{jsparse, 25 | title = "JSParse - Javascript Parser Combinators", 26 | url = "http://www.bluishcoder.co.nz/2007/10/javascript-parser-combinators.html", 27 | author = "C. Double", 28 | year = 2010, 29 | note = "Hämtad 2010-05-18" 30 | } 31 | 32 | @misc{jquery, 33 | author = "The jQuery Project", 34 | title = "jQuery", 35 | url = "http://jquery.com/", 36 | year = 2010, 37 | note = "Hämtad 2010-05-18" 38 | } 39 | 40 | @book{jones87, 41 | author = "Peyton-Jones, S.", 42 | year = 1987, 43 | title = "The Implementation of Functional Programming Languages", 44 | publisher = "Prentice Hall", 45 | howpublished = "\url{http://research.microsoft.com/en-us/um/people/simonpj/papers/slpj-book-1987/}", 46 | note = "Hämtad 2010-05-18" 47 | } 48 | 49 | @book{haskell98, 50 | title = "Haskell 98 language and libraries: the Revised Report", 51 | editor = "S. Peyton-Jones", 52 | year = 2003, 53 | publisher = "Cambridge University Press" 54 | } 55 | 56 | @inbook{haskell98chap3, 57 | title = "Haskell 98 Language and Libraries: The Revised Report", 58 | editor = "S. Peyton-Jones", 59 | year = 2003, 60 | publisher = "Cambridge Univeristy Press", 61 | chapter = 3 62 | } 63 | 64 | @inbook{haskell98chap9, 65 | title = "Haskell 98 Language and Libraries: The Revised Report", 66 | editor = "S. Peyton-Jones", 67 | year = 2003, 68 | publisher = "Cambridge Univeristy Press", 69 | chapter = 9 70 | } 71 | 72 | @unpublished{haskell2010, 73 | title = "Haskell 2010 Language Report", 74 | author = "S. Marlow", 75 | year = 2010, 76 | month = "April", 77 | note = "The draft of the report" 78 | } 79 | 80 | @manual{javascript, 81 | title = "ECMAScript Language Specification", 82 | organization = "ECMA International", 83 | edition = 5, 84 | year = 2009 85 | } 86 | 87 | @article{why, 88 | title = "Why Functional Programming Matters", 89 | author = "J. Hughes", 90 | year = 1989, 91 | journal = "The Computer Journal", 92 | pages = "98 - 107", 93 | volume = 32 94 | } 95 | 96 | @article{hudak89, 97 | title = "Conception, Languages Evolution, and Application of Functional Programming", 98 | author = "P. Hudak", 99 | year = 1989, 100 | journal = "ACM Computing Surveys", 101 | pages = "359 - 411", 102 | volume = 21, 103 | } 104 | 105 | @conference{fang01, 106 | author = "X. Fang", 107 | title = "Using a Coding Standard to Improve Program Quality", 108 | booktitle = "Proceedings of the Second Asia-Pacific Conference on Quality Software", 109 | year = 2001, 110 | publisher = "IEEE Computer Society" 111 | } 112 | 113 | @INPROCEEDINGS{jones99, 114 | author = {Jones, M. P.}, 115 | title = {Typing Haskell in Haskell}, 116 | booktitle = {Haskell Workshop}, 117 | year = {1999}, 118 | publisher = {} 119 | } 120 | 121 | @inbook{dragonbookchap6, 122 | title = "Compilers: Principles, Techniques, and Tools (2nd Edition)", 123 | author = {Aho, A. V. and Lam, M. S. and Sethi, R and Ullman, J. D.}, 124 | year = 2006, 125 | publisher = "Addison Wesley", 126 | chapter = 6 127 | } 128 | 129 | @book{pierce02, 130 | author = "Pierce, B. C.", 131 | year = 2002, 132 | title = "Types and Programming Languages", 133 | publisher = "MIT Press", 134 | } 135 | -------------------------------------------------------------------------------- /lib/jquery.cookie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cookie plugin 3 | * 4 | * Copyright (c) 2006 Klaus Hartl (stilbuero.de) 5 | * Dual licensed under the MIT and GPL licenses: 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * http://www.gnu.org/licenses/gpl.html 8 | * 9 | */ 10 | 11 | /** 12 | * Create a cookie with the given name and value and other optional parameters. 13 | * 14 | * @example $.cookie('the_cookie', 'the_value'); 15 | * @desc Set the value of a cookie. 16 | * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true }); 17 | * @desc Create a cookie with all available options. 18 | * @example $.cookie('the_cookie', 'the_value'); 19 | * @desc Create a session cookie. 20 | * @example $.cookie('the_cookie', null); 21 | * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain 22 | * used when the cookie was set. 23 | * 24 | * @param String name The name of the cookie. 25 | * @param String value The value of the cookie. 26 | * @param Object options An object literal containing key/value pairs to provide optional cookie attributes. 27 | * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object. 28 | * If a negative value is specified (e.g. a date in the past), the cookie will be deleted. 29 | * If set to null or omitted, the cookie will be a session cookie and will not be retained 30 | * when the the browser exits. 31 | * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie). 32 | * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie). 33 | * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will 34 | * require a secure protocol (like HTTPS). 35 | * @type undefined 36 | * 37 | * @name $.cookie 38 | * @cat Plugins/Cookie 39 | * @author Klaus Hartl/klaus.hartl@stilbuero.de 40 | */ 41 | 42 | /** 43 | * Get the value of a cookie with the given name. 44 | * 45 | * @example $.cookie('the_cookie'); 46 | * @desc Get the value of a cookie. 47 | * 48 | * @param String name The name of the cookie. 49 | * @return The value of the cookie. 50 | * @type String 51 | * 52 | * @name $.cookie 53 | * @cat Plugins/Cookie 54 | * @author Klaus Hartl/klaus.hartl@stilbuero.de 55 | */ 56 | jQuery.cookie = function(name, value, options) { 57 | if (typeof value != 'undefined') { // name and value given, set cookie 58 | options = options || {}; 59 | if (value === null) { 60 | value = ''; 61 | options.expires = -1; 62 | } 63 | var expires = ''; 64 | if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { 65 | var date; 66 | if (typeof options.expires == 'number') { 67 | date = new Date(); 68 | date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); 69 | } else { 70 | date = options.expires; 71 | } 72 | expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE 73 | } 74 | // CAUTION: Needed to parenthesize options.path and options.domain 75 | // in the following expressions, otherwise they evaluate to undefined 76 | // in the packed version for some reason... 77 | var path = options.path ? '; path=' + (options.path) : ''; 78 | var domain = options.domain ? '; domain=' + (options.domain) : ''; 79 | var secure = options.secure ? '; secure' : ''; 80 | document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); 81 | } else { // only name given, get cookie 82 | var cookieValue = null; 83 | if (document.cookie && document.cookie != '') { 84 | var cookies = document.cookie.split(';'); 85 | for (var i = 0; i < cookies.length; i++) { 86 | var cookie = jQuery.trim(cookies[i]); 87 | // Does this cookie string begin with the name we want? 88 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 89 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 90 | break; 91 | } 92 | } 93 | } 94 | return cookieValue; 95 | } 96 | }; -------------------------------------------------------------------------------- /rapport/kapitel/diskussion.tex: -------------------------------------------------------------------------------- 1 | \section{Diskussion} 2 | Vi har skapat en javascriptapplikation som kan parsa, typchecka och interpretera stora delar av Haskell 98. Det som saknas är fullständigt stöd för typklasser där stödet endast finns i typcheckaren och parsern men fortfarande behöver implementeras i interpretatorn. Detta handlar främst om förmågan att välja rätt instanser av typklasser vid applicering av överlagrade funktioner. 3 | 4 | Sett till planeringen har vi lyckats uppfylla alla milstolpar utom typklasser, dock inte enligt den ordning och tidsplan som ursprungligen planerades. 5 | Vi insåg att det var enklast att utveckla parsern, typcheckaren och interpretatorn parallellt och bestämma individuellt vad som skulle implementeras och i 6 | vilken ordning för att senare, oftast en gång i veckan, samordna och implementera det som behövdes i flera delar. 7 | 8 | Vi har inte implementerat NPlusK-pattern i parsern och då de är borttagna i Haskell 2010 \citep{haskell2010} känner vi att det inte behövs. 9 | 10 | \subsection{Val av språk för implementering} 11 | Tidigt i planeringen tvingades vi välja vilket språk vår implementation skulle 12 | bestå av. Det första alternativet var att först skriva en kompilator för att 13 | kompilera Haskell till Javascript. Därefter skulle så kallad boot-strapping 14 | tillämpas där kompilatorn används för att kompilera sig själv till 15 | Javascript. Eftersom det redan finns parsers och typcheckare för Haskell 16 | skrivna i Haskell skulle projektet mestadels handla om att finna en lämplig 17 | målrepresentation för Haskell i javascriptkod och sedan implementering av en 18 | kodgenerator för denna. Haskells statiska typcheckning och referentiella 19 | transparens skulle också förenkla verifiering av projektets komponenter. 20 | 21 | Dock finns även en del nackdelar med en sådan implementation. Utan särskilda 22 | optimeringar i kompilatorn skulle en målrepresentation nödvändigtvis innehålla 23 | strukturer liknande dem som kan finnas i en interpretator för ett lat evaluerat 24 | språk. Det är ett rimligt antagande att sådana optimeringar på grund av sin komplexitet vore alltför stora för att genomföra i den aktuella tidsramen. Därför anser vi att denna typ av implementation lämpar sig bäst inom 25 | ramarna för ett existerande kompilatorprojekt såsom GHC där nödvändiga optimeringar redan finns inbyggda. 26 | 27 | Det andra alternativet var att skriva allt i Javascript och detta är den 28 | implementationsstrategi som vi till slut bestämde oss för. 29 | En fördel med en sådan implementation är att integrering med annan 30 | javascriptkod blir enkel. 31 | Det har också fördelen att i en interpretator bevaras kodstrukturen och en framtida interaktiv läromiljö får tillgång till mellanliggande körningsdata. 32 | Det första alternativet skulle däremot kräva ett 33 | särskilt integrationslager för att få samma möjligheter. 34 | 35 | 36 | %stor del av vår projekts potentiella användare redan har viss erfarenhet av Javascript och liknande språk och att användande av vårt bibliotek därför blir enklare för dessa än vad motsvarande haskellimplementation skulle bli. Eftersom detta alternativ krävde att vi själva implementerar parser, typcheckare och interpreterare ansåg vi också att det gav oss större möjligheter till lärande. 37 | 38 | \subsection{Framtida förbättringar} 39 | 40 | Syftet med projektet, att skapa en fungerande Haskelltolk i Javascript, har vi lyckats implementera om hänsyn tas till de avgränsningar som är uppsatta. Dock sett till motivationen bakom projektet, att vår Haskelltolk ska kunna användas som grund för att skapa en webbaserad interaktiv läroplattform, så finns det fortfarande mycket kvar att utveckla. Framförallt handlar det om att göra Haskelltolken och HIJi mer lättanvänd för nybörjare inom funktionell programmering. 41 | 42 | I parsern har vi identifierat två förbättringsmöjligheter. För det första, hjälpsamma och förklarande felmeddelanden är en viktigt del av ett utvecklingsverktyg och det generars för tillfället inte av parsern. 43 | Om parsern stöter på ett fel rapporterar den endast att ett fel har inträffat och avslutar parsningsprocessen. 44 | Att förbättra dessa felmeddelanden med exempelvis rad- och kolumnnummer och specifik information om vad för fel som har inträffat skulle göra parsern mer användbar. 45 | För att implementera detta behöver parsern kombinera steg 1 och 2 i parsningen för att rad- och kolumn-nummer ska bevaras korrekt då borttagning av nästlade kommentarer kan påverka dessa. 46 | JSParse behöver modifieras så att det rapporterar var ett fel uppstod och i vilken parser. 47 | 48 | För det andra, konverteringen av icke kontextfri Haskellkod till kontextfri kan förbättras 49 | för att klara av att expandera måsvingar i \emph{[x | let x = 5]}. 50 | För att klara av detta behövs en parser som räknar antal måsvingar, parenteser, 51 | komman och hakparenteser efter \emph{let} och avgöra när det är korrekt att sätta in avslutande måsvingar. 52 | 53 | Även i HIJi finns det förbättringar att göra. 54 | Det som framförallt behöver utvecklas är, för det första, erbjuda en interaktiv tutorial där användaren får instruktioner vad som ska skrivas in i HIJi. Om användaren skriver in rätt uttryck fortsätter tutorialen till nästa nivå. 55 | För det andra, visa typinformation från funktioner genom att hålla musen över funktionsnamnet. 56 | Och tillsist, kunna stega igenom ett program eller funktion för att kunna se vad som händer i varje evalutionssteg. 57 | -------------------------------------------------------------------------------- /rapport/kapitel/metod.tex: -------------------------------------------------------------------------------- 1 | \section{Metod} 2 | 3 | Nedan följer en beskrivning av de arbetsmetoder, mjukvaror och kodbibliotek som vi använt oss av i projektet. 4 | 5 | \subsection{Genomförande} 6 | 7 | % modulbaserat arbete.. 8 | För att implementera en tolk för Haskell behövs en parser för den aktuella syntaxen, en typcheckare för språkets definierade typregler och tillsist en interpretator som tolkar språket efter dess specifikation. 9 | Det upptäcktes tidigt att de tre modulerna, parser, interpretator och typcheckare inte behövde utvecklas sekvensiellt. De tre modulerna integrerar enbart med varandra genom det abstrakta syntaxträdet, vår interna representation av Haskell, vilket medför att det är lätt att utveckla de olika modulerna helt frånskilt från varandra. Figur \ref{fig:tolkens_struktur} visar hur denna interaktion mellan de olika modulerna är tänkt att gå till. Figuren visar även hur webbläsaren kommunicerar genom ett Javascript API och det abstrakta syntaxträdet och inte direkt med de olika komponenterna. 10 | 11 | \begin{figure}[h] 12 | \begin{center} 13 | \includegraphics[width=1.0\textwidth]{image1.png} 14 | \caption{Överblick över tolkens struktur och interaktion} 15 | \label{fig:tolkens_struktur} % Labels must come after caption! 16 | \end{center} 17 | \end{figure} 18 | 19 | Det första delmålet var att göra en haskelliknande implementation av lambda calculus då mer avancerade funktionella programspråksegenskaper kan implementeras som detta \citep{jones87}. 20 | Parsern implementerades med hjälp av ett \emph{parser combinator}-bibliotek kallat JSParse \citep{jsparse}. Det gav oss möjlighet att på ett enkelt sätt implementera både den kontextfria och icke kontextfria delen av Haskell. Parsern skapar ett syntaxträd som skickas vidare till typcheckaren. I typcheckaren dekoreras syntaxträdet med typinformation innan det slutligen skickas till interpretatorn. 21 | 22 | En interaktiv kommandotolk som kan köras i en webbläsare utvecklades. Den gav användaren möjlighet att skriva haskellfunktioner och exekvera dem på ett liknande sätt som i GHCi. 23 | Vi integrerade jQuery \citep{jquery} för att få ett unisont stöd för samtliga webbläsare. jQuery underlättade även arbetet med att skapa ett enkelt och stilrent interaktivt gränssnitt. 24 | 25 | Arbetssättet präglades av en iterativ utvecklingsmetodik med korta utvecklingscyklar. Arbetet delades upp med huvudansvarstagande över var sin modul och utfördes parallellt med varandra. Arbetet skedde dock framförallt i samlad grupp på grund av att det var många designrelaterade problem vi var tvungna att ta ställning till under projektet, till exempel hur vårt abstrakta syntaxträd skulle se ut, och för att det skulle bli enklare när vi skulle börja sammanfoga de olika modulerna med varandra. 26 | Det var också ett bra sätt att snabbt få hjälp av varandra eftersom vi ej visste exakt hur modulerna skulle se ut när vi påbörjade arbetet. Vi fann det därför praktiskt att använda en iterativ modell för att bit för bit utvidga våra moduler. Dock valde vi att implementera typcheckaren i ett steg då vi ansåg att det skulle vara enklare. Detta främst för att vi trodde typklasser var så centralt i typcheckaren att det skulle vara svårt att lägga till det i en andra iteration. 27 | 28 | Eftersom vi arbetade parallellt med olika moduler var vi beroende av ett bra versionshanteringssystem. Bra i vårt fall innebar att det skulle vara enkelt att arbeta i olika grenar, en gren för varje modul, och att det skulle gå snabbt och enkelt att slå ihop dessa förgreningar när vi behövde länka samman två utvecklares arbeten. I början av projektet använda vi oss av Subversion (SVN). Detta berodde framförallt på att det var det versionshanteringssystem som alla i gruppen hade erfarenhet från tidigare. Dock insåg vi att SVN inte var praktiskt att använda när vi arbetade i flera olika grenar i projektet samtidigt. Därför gick valet till att använda Git som är designat från grunden för att på ett enkelt sätt skapa nya och slå samman förgreningar under utvecklingens gång. Vi kunde därmed skapa en förgrening för varje modul och under arbetets gång sammanlänka allas arbeten på ett effektivt sätt. 29 | 30 | På grund av vår parallella arbetsmetod ansåg vi det nödvändigt att arbeta fram en kodstandard så att de olika modulerna skulle stilmässigt likna varandra. Kodstandarden beskriver hur indentering och namngivning av variabler och funktioner ska se ut. Innan ny kod skickades till Git så krävdes det att koden uppfyllde kraven i kodstandarden. 31 | 32 | %\subsection{Javascript} 33 | %Javascript \citep{javascript} är ett programmeringsspråk som framförallt används på klientsidan på webben. Javascript är ett dynamiskt objektorienterat skriptspråk. 34 | %Javascript är det programmeringsspråk som används uteslutande i detta projektet. 35 | 36 | \subsection{Kodbibliotek} 37 | I projektet använder vi oss av tre kodbibliotek för att snabbare kunna utveckla haskelltolken. Nedan följer en kort beskrivning av dessa. 38 | 39 | \subsubsection{JSParse} 40 | Parsern implementeras med hjälp av ett parser combinator bibliotek kallat JSParse. 41 | En parser combinator består av olika funktioner som parsar exempelvis strängar, listor eller blanksteg. 42 | Dessa funktioner kombineras för att skapa mer komplexa parsers. Det ger oss möjlighet att implementera komplexa 43 | parsers för både de kontextfria och icke kontextfria varianterna av Haskell. 44 | 45 | \subsubsection{jQuery} 46 | %jQuery är ett öppet kodbibliotek till Javascript som är dubeellicenserat under MIT License och GPL version 2. 47 | jQuery är designat för att underlätta för utvecklare att modifiera DOM-träd och göra asynkrona javascriptanrop. jQuery användes i projektet för att få likartat stöd i samtliga webbläsare i kommandotolken som utvecklades. 48 | jQuery gav oss även möjlighet att skapa ett enkelt och stilrent interaktivt gränssnitt utan att behöva göra allt från grunden. 49 | jQuery.Cookie, ett tillägg till jQuery, används för att förenkla användandet av kakor. 50 | 51 | \subsubsection{JSON} 52 | JSON \citep{json} är en delmängd av Javascript och används för att utbyta data mellan olika format och programmeringsspråk. 53 | JSON är idag inbyggt i de senaste versionerna av de moderna webbläsarna, men för att få stöd i äldre versioner har vi valt att inkludera JSON som ett externt bibliotek. 54 | 55 | -------------------------------------------------------------------------------- /rapport/kapitel/inledning.tex: -------------------------------------------------------------------------------- 1 | \section{Inledning} 2 | På vissa av Chalmers och Göteborgs Universitets datorvetenskapliga program är den första programmeringskursen i Haskell \citep{haskell98} och för en del av de nya eleverna är inlärningströskeln hög. De studenter som börjar på de datavetenskapliga programmen på Chalmers och Göteborgs Universitet är allt från nybörjare till mycket kompetenta inom programmering. De flesta saknar dock kunskaper kring funktionell programmering. Skillnaden mellan ett funktionellt och ett objektorienterat programmeringsspråk är stora och omställningen hur programmeringsrelaterade problem behöver angripas är inte enkelt för de flesta nybörjare. Vi tror att ett interaktivt webbverktyg skulle kunna sänka tröskeln och underlätta undervisningen. Ett webbverktyg medför även att en kompilator som \emph{Glasgow Haskell Compiler} (GHC) \citep{ghc} ej behöver installeras. Webbens stöd för interaktivitet gör det möjligt att snabbt visa funktionsdeklarationerna för de inbyggda funktionerna och att enkelt evaluera funktionerna och testa sig fram till olika resultat. 3 | 4 | Många programmerare kommer inte i kontakt med funktionell programmering och med hjälp av ett interaktivt webbverktyg som är enkelt för användaren att använda är vår förhoppning att fler programmerare och studenter ska komma i kontakt med funktionell programmering, och i synnerhet Haskell. Då flera moderna objektorienterade programmeringsspråk börjar ta funktionalitet och begrepp från funktionella programmeringsspråk så är det extra viktigt att programmerare kommer i kontakt med funktionell programmering. Ett exempel på detta är \emph{C\#} som i senare versioner har fått stöd för bland annat lambda-funktioner (anonyma funktioner) \citep{csharp}. 5 | 6 | En fördel med att ha tolken på webben är att det enda som behövs för att använda den är en javascriptkompatibel webbläsare, något som följer med i alla moderna operativsystem. Detta betyder att de användare som befinner sig inom vår målgrupp redan har den programvaran som behövs på sina hemdatorer för att använda sig av vårt program. 7 | 8 | % TODO detta e out of context` 9 | Det som gör Haskell speciellt är att det är ett starkt statiskt typcheckat och funktionellt programmeringsspråk med lat evaluering. % TODO: Citera, skriv om ifall det är direktöversatt 10 | % jag bytte från strikt semantik till lat evaluering:p 11 | Att språket är funktionellt innebär bland annat att funktioner är \emph{first-class citizens} och kan därmed användas som parametrar och returneras från andra funktioner precis som vilken annan typ som helst. 12 | 13 | Lat evaluering innebär att evalueringen av ett uttryck inte kommer utföras förrän resultatet av uttrycket behövs. Om uttrycket inte behövs kommer interpretatorn att ignorera det. 14 | Lat evaluering gör att programmeraren inte behöver bry sig om exekveringsordningen av ett program. Detta ger prestandaförbättringar eftersom ett uttryck inte evalueras alls om det inte behövs \citep{hudak89}. 15 | Lat evaluering gör det också möjligt att använda sig av oändliga datastrukturer, till exempel oändliga listor. Språket blir därmed mer uttrycksfullt. 16 | 17 | John Hughes argumenterar för att funktionella språk som stödjer lat evaluering erbjuder större möjligheter än imperativa språk att skriva modulära program. Detta för att funktionella språk som Haskell stödjer \emph{higher order functions}, en funktion som tar en annan funktion som argument, och lat evaluering vilket är tekniker som kan användas för att binda samman olika moduler. 18 | Dessa två programspråksegenskaper bidrar till att program skrivna i Haskell generellt sett är kortare och går fortare att skriva än motsvarande program skrivet i ett imperativt programmeringsspråk \citep{why}. 19 | 20 | Med ovan nämnda resonemang ser vi det som ovärderligt för programmerare att komma i kontakt och lära sig funktionell programmering. 21 | Förhoppningen är att vår haskelltolk i Javascript ska kunna användas som grund för att i framtiden göra en interaktiv läroplattform för nybörjare i funktionell programmering. 22 | 23 | 24 | \subsection{Syfte} 25 | Syftet är en implementera en fungerande haskelltolk i Javascript. Den ska kunna tolka en delmängd av haskellspecifikationen så den kan användas för att göra exempelvis interaktiva tutorials för nybörjare. 26 | Meningen är att dessa ska kunna köras i en vanlig webbläsare utan att ladda ner en haskellkompilator, till exempel GHC, eller behöva lära sig svårbegripliga kommandon. 27 | 28 | \subsection{Metod} 29 | Projektet bestod av att planera, designa och implementera haskelltolken. Vi arbetade efter en iterativ modell där nya funktioner lades till undan för undan. Detta fungerade bra eftersom vi tidigt fick en fungerande prototyp att utgå ifrån. Arbetet delades upp i separata moduler som utvecklades relativt frånskilt från varandra för att undan för undan fasas ihop till det slutgiltiga resultatet. 30 | 31 | %I vårt arbete har vi implementerat parser, typcheckare och interpretator parallellt med varandra och utökar de olika modulernas funktionalitet iterativt. 32 | %Vi hade tänkt följa den här planen genom varje milstolpe genom att utöka parsern, typcheckaren och interpretatorn med ny funktionalitet. 33 | 34 | %Ett första delmål är att göra en enkel implementation av lambda calculus då mer avancerade funktionella programspråksegenskaper kan implementeras som detta \citep{jones87}. 35 | % Parsern implementeras med hjälp av ett parser combinator bibliotek kallat JSParse \citep{jsparse}. Detta ger oss möjlighet att att på ett enkelt sätt implementera både den kontextfria och icke kontextfria delen av Haskell. Parsern skapar ett syntaxträd som skickas vidare till typcheckaren. I typcheckaren dekoreras syntaxträdet med typinformation innan det slutligen skickas till interpretatorn. 36 | 37 | %Vi använder även JSParse, en parser combinator, för att bygga ett syntaxträd som skickas till typcheckaren och interpretatorn. I typcheckaren dekoreras syntaxträdet med typinformation. 38 | 39 | %En interaktiv kommandotolk som kan köras i en webbläsare kommer att utvecklas. Den ska ge användaren möjlighet att skriva Haskell-funktioner och exekvera dem på ett liknande sätt som i GHCi. 40 | %Vi kommer att integrera jQuery \citep{jquery} för att få ett unisont stöd för samtliga webbläsare. jQuery kommer även underlätta arbetet med att skapa ett enkelt och stilrent interaktivt gränssnitt. 41 | 42 | \subsection{Avgränsningar} 43 | Att tolka Haskell i Javascript är inget trivialt projekt och därför valde vi att avgränsa implementationen till de mest centrala och viktiga delarna i haskellspecifikationen. De delar vi valde att fokusera på är: 44 | \begin{itemize} 45 | \item{Lambda-funktioner, namngivna funktioner} 46 | \item{Typer, generella typer, algebraiska datatyper} 47 | \item{Typklasser} 48 | \item{Pattern matching} 49 | \item{Guards} 50 | \end{itemize} 51 | Med dessa delar implementerade kan de flesta enklare haskellprogram köras och bör vara tillräckligt för de flesta nybörjare. Vi beslutade att ej lägga någon större tid på att skapa en användarvänlig webbsida, utan fokus låg på att skapa haskelltolken. Dock valde vi att utveckla en kommandotolk som skulle köras på en webbsida för att kunna kommunicera med haskelltolken. 52 | Slutligen beslutades det att inte optimera haskelltolken utan målet var att göra en fungerande implementation. Detta för att en nybörjare sällan behöver eller kommer skriva program som behöver hög prestanda. 53 | 54 | -------------------------------------------------------------------------------- /haskell.primitives.js~: -------------------------------------------------------------------------------- 1 | (function(primitives, ast, interpreter){ 2 | primitives.prim = function(env) { 3 | // data Char# 4 | 5 | // gtChar# :: Char# -> Char# -> Bool 6 | env.bind("gtChar#", ["a", "b"], gtPrim); 7 | 8 | // geChar# :: Char# -> Char# -> Bool 9 | env.bind("geChar#", ["a", "b"], gePrim); 10 | 11 | // eqChar# :: Char# -> Char# -> Bool 12 | env.bind("eqChar#", ["a", "b"], eqPrim); 13 | 14 | // neChar# :: Char# -> Char# -> Bool 15 | env.bind("neChar#", ["a", "b"], nePrim); 16 | 17 | // ltChar# :: Char# -> Char# -> Bool 18 | env.bind("ltChar#", ["a", "b"], ltPrim); 19 | 20 | // leChar# :: Char# -> Char# -> Bool 21 | env.bind("leChar#", ["a", "b"], lePrim); 22 | 23 | // ord# :: Char# -> Int# 24 | env.bind("ord#", ["a"], 25 | function(env) { 26 | var a = env.lookup("a"); 27 | return a.charCodeAt(0); 28 | }); 29 | 30 | // data Int# 31 | var intSize = 32; 32 | // (+#) :: Int# -> Int# -> Int# 33 | env.bind("+#", ["a", "b"], primAdd(intSize, true)); 34 | // (-#) :: Int# -> Int# -> Int# 35 | env.bind("-#", ["a", "b"], primSub(intSize, true)); 36 | // (*#) :: Int# -> Int# -> Int# 37 | env.bind("*#", ["a", "b"], primMul(intSize, true)); 38 | // mulIntMayOflo# :: Int# -> Int# -> Int# 39 | // quotInt# :: Int# -> Int# -> Int# 40 | env.bind("quotInt#", ["a", "b"], primQuot(intSize, true)); 41 | // remInt# :: Int# -> Int# -> Int# 42 | env.bind("remInt#", ["a", "b"], primRem(intSize, true)); 43 | // negateInt# :: Int# -> Int# 44 | env.bind("negateInt#", ["a", "b"], primNegate(intSize, true)); 45 | // addIntC# :: Int# -> Int# -> (#Int#, Int##) 46 | // subIntC# :: Int# -> Int# -> (#Int#, Int##) 47 | // (>#) :: Int# -> Int# -> Bool 48 | env.bind(">#", ["a", "b"], gtPrim); 49 | // (>=#) :: Int# -> Int# -> Bool 50 | env.bind(">=#", ["a", "b"], gePrim); 51 | // (==#) :: Int# -> Int# -> Bool 52 | env.bind("==#", ["a", "b"], eqPrim); 53 | // (/=#) :: Int# -> Int# -> Bool 54 | env.bind("/=#", ["a", "b"], nePrim); 55 | // (<#) :: Int# -> Int# -> Bool 56 | env.bind("<#", ["a", "b"], ltPrim); 57 | // (<=#) :: Int# -> Int# -> Bool 58 | env.bind("<=#", ["a", "b"], lePrim); 59 | // chr# :: Int# -> Char# 60 | // int2Word# :: Int# -> Word# 61 | env.bind("int2Word#", ["a"], primNarrow(32, false)); 62 | // int2Float# :: Int# -> Float# 63 | env.bind("int2Word#", ["a"], primNarrow(32, true)); 64 | // int2Double# :: Int# -> Double# 65 | env.bind("int2Word#", ["a"], primNarrow(64, true)); 66 | // uncheckedIShiftL# :: Int# -> Int# -> Int# 67 | // uncheckedIShiftRA# :: Int# -> Int# -> Int# 68 | // uncheckedIShiftRL# :: Int# -> Int# -> Int# 69 | }; 70 | 71 | primitives.init = function(env) { 72 | primitives.prim(env); 73 | // (+) :: Num a => a -> a -> a 74 | env.bind("+", createPrimitive(env, ["a", "b"], 75 | function(env) { 76 | var a = forceHead(env.lookup("a")); 77 | var b = forceHead(env.lookup("b")); 78 | return new interpreter.ConstantThunk(new ast.Num(a.value.num+b.value.num)); 79 | })); 80 | 81 | // (-) :: Num a => a -> a -> a 82 | env.bind("-", createPrimitive(env, ["a", "b"], 83 | function(env) { 84 | var a = forceHead(env.lookup("a")); 85 | var b = forceHead(env.lookup("b")); 86 | return new interpreter.ConstantThunk(new ast.Num(a.value.num-b.value.num)); 87 | })); 88 | // (*) :: Num a => a -> a -> a 89 | env.bind("*", createPrimitive(env, ["a", "b"], 90 | function(env) { 91 | var a = forceHead(env.lookup("a")); 92 | var b = forceHead(env.lookup("b")); 93 | return new interpreter.ConstantThunk(new ast.Num(a.value.num*b.value.num)); 94 | })); 95 | // primAlert :: String -> a 96 | env.bind("alert", createPrimitive(env, ["l"], 97 | function(env) { 98 | var l = forceHead(env.lookup("l")); 99 | alert(l.value.num); 100 | return new interpreter.Data("()", []); 101 | })); 102 | 103 | // seq :: a -> b -> b 104 | env.bind("seq", createPrimitive(env, ["a", "b"], 105 | function(env) { 106 | env.lookup("a").forceHead(); 107 | return env.lookup("b"); 108 | })); 109 | 110 | // Can print all different haskell types (including functions...) 111 | // Should be hidden away and only used for the deriving Show implementation. 112 | // defaultShow :: a -> String 113 | env.bind("defaultShow", createPrimitive(env, ["s"], 114 | function(env) { 115 | var t = env.lookup("s"); 116 | })); 117 | 118 | env.bind(":", createDataConstructor(env, ":", 2)); 119 | env.bind("[]", createDataConstructor(env, "[]", 0)); 120 | }; 121 | 122 | function createPrimitive(env, args, func) { 123 | var expr = new ast.Primitive(func); 124 | var argsR = [].concat(args).reverse(); 125 | for (var i in argsR) { 126 | expr = new ast.Lambda(new ast.VariableBinding(argsR[i]), expr); 127 | }; 128 | return new interpreter.Closure(env, expr); 129 | }; 130 | 131 | 132 | function createDataConstructor(env, ident, num) { 133 | var args = []; 134 | for (var i = 0; i b); 166 | }; 167 | 168 | function gePrim(env) { 169 | var a = env.lookup("a"); 170 | var b = env.lookup("b"); 171 | return boxBool(env, a >= b); 172 | }; 173 | 174 | function eqPrim(env) { 175 | var a = env.lookup("a"); 176 | var b = env.lookup("b"); 177 | return boxBool(env, a == b); 178 | }; 179 | 180 | function nePrim(env) { 181 | var a = env.lookup("a"); 182 | var b = env.lookup("b"); 183 | return boxBool(env, a != b); 184 | }; 185 | 186 | function ltPrim(env) { 187 | var a = env.lookup("a"); 188 | var b = env.lookup("b"); 189 | return boxBool(env, a < b); 190 | }; 191 | 192 | function lePrim(env) { 193 | var a = env.lookup("a"); 194 | var b = env.lookup("b"); 195 | return boxBool(env, a <= b); 196 | }; 197 | 198 | function primAdd(bits, twoComplement) { 199 | return function(env) { 200 | var a = env.lookup("a"); 201 | var b = env.lookup("b"); 202 | var result = a + b; 203 | return doPrimOverflow(bits, twoComplement, result); 204 | }; 205 | }; 206 | 207 | function primSub(bits, twoComplement) { 208 | return function(env) { 209 | var a = env.lookup("a"); 210 | var b = env.lookup("b"); 211 | var result = a - b; 212 | return doPrimOverflow(bits, twoComplement, result); 213 | }; 214 | }; 215 | 216 | function primMul(bits, twoComplement) { 217 | return function(env) { 218 | var a = env.lookup("a"); 219 | var b = env.lookup("b"); 220 | var result = a * b; 221 | return doPrimOverflow(bits, twoComplement, result); 222 | }; 223 | }; 224 | 225 | function primQuot(bits, twoComplement) { 226 | return function(env) { 227 | var a = env.lookup("a"); 228 | var b = env.lookup("b"); 229 | var result = parseInt(a / b); 230 | return doPrimOverflow(bits, twoComplement, result); 231 | }; 232 | }; 233 | 234 | function primRem(bits, twoComplement) { 235 | return function(env) { 236 | var a = env.lookup("a"); 237 | var b = env.lookup("b"); 238 | var result = a % b; 239 | return doPrimOverflow(bits, twoComplement, result); 240 | }; 241 | }; 242 | 243 | function primNegate(bits, twoComplement) { 244 | return function(env) { 245 | var a = env.lookup("a"); 246 | var result = -a; 247 | return doPrimOverflow(bits, twoComplement, result); 248 | }; 249 | }; 250 | 251 | function primNarrow(bits, twoComplement) { 252 | return function(env) { 253 | return doPrimNarrow(bits, twoComplement, env.lookup("a")); 254 | }; 255 | }; 256 | 257 | // Narrows a number by chopping of the higher bits 258 | function doPrimNarrow(bits, twoComplement, num) { 259 | num = num & (Math.pow(2, bits+1) - 1); 260 | if (twoComplement && (num & Math.pow(2, bits))) { 261 | return num - Math.pow(2, bits); 262 | }; 263 | return num; 264 | }; 265 | 266 | // Narrows a number by overflowing it 267 | function doPrimOverflow(bits, twoComplement, num) { 268 | return doPrimNarrow(bits, twoComplement, num); 269 | }; 270 | 271 | })(haskell.primitives, haskell.ast, haskell.interpreter); -------------------------------------------------------------------------------- /rapport/MasterThesis.tex: -------------------------------------------------------------------------------- 1 | % USE PDFLATEX WITH THIS DOCUMENT. If you wish to use ordinary latex (why?) then convert the figures in figures/ to eps. 2 | % If you are using Chalmers linux system, you might need to change to a newer tex dist. 3 | % Do so with: vcs-what latex 4 | % look up the newest version and select it, a.t.m that would be: vcs-select -p texlive-20080816 5 | % I recommend using kile in linux. Make it available by adding the unsup software 6 | % echo unsup ~/.vcs4/pathsetup 7 | \documentclass[a4paper,12pt]{article} 8 | 9 | \usepackage[T1]{fontenc} 10 | \usepackage[swedish]{babel} 11 | %\usepackage[latin1]{inputenc} % Use the same as the encoding of the textfile 12 | \usepackage[utf8]{inputenc} % But this is probably the best choice. This doument is currently in utf8 (so is all other files) 13 | \usepackage{amsmath,amsfonts,amsthm,mathtools} % Math. Look up the amsmath manual (google) for many great examples. 14 | \usepackage{graphicx,placeins,float} % Figures and placements, float must be loaded before hypperref 15 | \usepackage[colorlinks,citecolor=blue]{hyperref} % Warning this package conflicts with the package algorithm 16 | \usepackage{fancyhdr,url,tikz} % Other usefull stuff. tikz is good for making drawings (but difficult) 17 | \usepackage{listings} % Source code 18 | % \usepackage{a4wide} % Gives a wider page 19 | \usepackage{mycommands} % Here you can define your personal favourites. See mycommands.cls 20 | \usepackage{pgfplots} % Anoth nice library for plots 21 | 22 | \usepackage{natbib} % ger Harvard-referenser 23 | 24 | 25 | %mina andringar för att få riktiga paragrafer vid radbrytning 26 | \setlength{\parskip}{12pt} 27 | \setlength{\parindent}{0pt} 28 | 29 | 30 | 31 | % mysubtitle, cover* and divisionnameB may be uncommented if nonexistant. 32 | \newcommand{\mytitle}{Haskell in Javascript} 33 | %\newcommand{\mysubtitle}{Some subtitle} 34 | \newcommand{\writtenmonth}{May} 35 | \newcommand{\writtenyear}{2010} 36 | \newcommand{\authors}{Adam Bengtsson\\Mikael Bung\\Johan Gustafsson\\Mattis Jeppsson} 37 | \newcommand{\authorsc}{ Adam Bengtsson, Mikael Bung, Johan Gustafsson, \\Mattis Jeppsson} % again with comma 38 | \newcommand{\YYYYNN}{2010:05} 39 | %\newcommand{\ISSN}{1652-8557} 40 | %\newcommand{\covercaption}{Haskell 98 implementerat i Javascript för att köras i en webbläsare} 41 | % \newcommand{\coverfigure}{figures/COVER_93_iso.png} 42 | \newcommand{\departmentname}{Computer Science and Engineering} 43 | \newcommand{\divisionnameA}{Computer Engineering} 44 | %\newcommand{\divisionnameB}{Fluid Mechanics} 45 | % \newcommand{\nameofprinters}{Chalmers Reproservice} 46 | \newcommand{\nameofprogramme}{Computer Science and Engineering Programme} 47 | \newcommand{\keywords}{Javascript, Haskell, Parser, Interpreter, Type Checker} 48 | % This is just to keep the swedish letters at one place in case there are problems with encoding 49 | \newcommand{\city}{Göteborg} 50 | \newcommand{\country}{Sweden} 51 | \newcommand{\university}{Chalmers University of Technology\\ Gothenburg University} 52 | \newcommand{\thesis}{Bachelor's thesis} 53 | \newcommand{\telephone}{+ 46 (0)31-772 1000} 54 | \newcommand{\postcode}{SE-412 96} 55 | % End of input. 56 | 57 | % Use section number first in numbering 58 | \numberwithin{equation}{section} 59 | \numberwithin{figure}{section} 60 | \numberwithin{table}{section} 61 | 62 | % Setting up the marginals a bit larger 63 | %\textheight=730pt % default 609pt, ~710 if you dont use a4wide 64 | %\headsep=0pt % default 25pt, since we have no headers in this document 65 | %\headheight=0pt% 12 pt 66 | %\voffset=-0.4in % default 0 67 | 68 | % Set up headers to match styleguide 69 | \pagestyle{fancy} 70 | \renewcommand{\headrulewidth}{0pt} 71 | \fancyhead{} 72 | \fancyfoot{} 73 | \fancyfoot[C]{\footnotesize \includegraphics[height=2.5mm]{figures/Logo.pdf}, \textit{\departmentname}, \thesis\ \YYYYNN} 74 | \fancyfoot[RO,LE]{\thepage} 75 | 76 | % To use code use \lstinputlisting[langauge=matlab,style=mystyle]{somematlabfile.m} 77 | % Here is a suitable style for code. 78 | 79 | %\lstset{language=Haskell} 80 | %\lstset{keywordstyle=\color{blue}\textbf} 81 | %\lstset{stringstyle=\color{red}} 82 | 83 | \lstdefinestyle{mystyle}{showstringspaces=false, basicstyle=\scriptsize\ttfamily, 84 | frame=shadowbox, breaklines=true, numbers=left, commentstyle=\color{gray}, 85 | keywordstyle=\color{blue}\textbf, stringstyle=\color{red}} 86 | 87 | % Metadata ion the PDF file (makes it searchable) 88 | \hypersetup{pdfauthor={\authorsc},pdftitle={\mytitle},pdfsubject={\thesis},pdfkeywords={\keywords}} 89 | 90 | % The following part is automatically generated, go to document start 91 | \begin{document} 92 | \thispagestyle{empty} 93 | \begin{tikzpicture}[remember picture,overlay] 94 | \node[yshift=-6.4cm] at (current page.north west) 95 | {\begin{tikzpicture}[remember picture, overlay] 96 | % First draw the grid and then the Logo and avancez logo. 97 | \draw[clip] (0cm,6.4cm)--(\paperwidth,6.4cm)--(\paperwidth,0cm)--(0.725\paperwidth,0cm) 98 | .. controls (0.703\paperwidth,0) and (0.703\paperwidth,0.8cm).. 99 | (0.68\paperwidth,0.8cm)--(0cm,0.8cm)--cycle; 100 | \draw[step=5mm,black] (0,0) grid (\paperwidth,6.4cm); 101 | \node[anchor=west,xshift=2.05cm,yshift=3.27cm,rectangle]{\includegraphics[width=13.2cm]{figures/Logo.pdf}}; 102 | \node[anchor=west,xshift=15.65cm,yshift=3.3cm,rectangle]{\includegraphics[width=3.6cm]{figures/Avancez.pdf}}; 103 | % \node[anchor=west,xshift=1.15cm,yshift=0.1cm,rectangle]{\includegraphics[width=13.2cm]{figures/gulogga.png}}; 104 | 105 | \end{tikzpicture}}; 106 | \end{tikzpicture} 107 | \ \vfill 108 | \makeatletter\@ifundefined{coverfigure}{}{ 109 | \begin{center} 110 | \includegraphics[width=\textwidth,height=0.4\paperheight,keepaspectratio]{\coverfigure} 111 | \end{center} 112 | }\makeatother 113 | {\huge\noindent \mytitle\par} % title, 21 114 | {\large\noindent \ \par} % subtitle, 16 115 | %{\normalsize\noindent\textit{Master of Science Thesis}\par}; 116 | {\normalsize\noindent\textit{\thesis\ in \nameofprogramme}\par} % 14 117 | \vskip 5mm 118 | {\Large\noindent \uppercase\expandafter{\authors}\par}% 18 119 | \vskip 6mm 120 | {\small\noindent 121 | Department of \departmentname\\ 122 | \textit{Division of \divisionnameA} 123 | \makeatletter\@ifundefined{divisionnameB}{}{\textit{and Division of \divisionnameB}}\makeatother\\ 124 | \uppercase\expandafter{\university}\\ 125 | \city, \country\ \writtenyear\\ 126 | \thesis\ \YYYYNN\par} 127 | 128 | \newpage 129 | \thispagestyle{empty} 130 | \mbox{} 131 | 132 | \newpage 133 | \thispagestyle{empty} 134 | \begin{center} 135 | {\uppercase\expandafter{\thesis}\ \YYYYNN\par} 136 | \vskip 40mm 137 | {\Large \mytitle\par} 138 | \vskip 5mm 139 | \makeatletter\@ifundefined{mysubtitle}{}{{\mysubtitle\par}}\makeatother 140 | \vskip 5mm 141 | {\thesis\ in \nameofprogramme\par} % (if applicable) (if applicable) 142 | {\uppercase\expandafter{\authors}\par} 143 | \vfill 144 | {Department of Computer Science\par} 145 | {\textit{Division of \divisionnameA} 146 | \makeatletter\@ifundefined{divisionnameB}{}{\textit{and Division of \divisionnameB}}\makeatother 147 | \par} 148 | {\uppercase\expandafter{\university}\par} 149 | \vskip 2mm 150 | {\city, \country\ \writtenyear\par} 151 | \end{center} 152 | 153 | \newpage 154 | {\noindent \mytitle\\ 155 | \makeatletter\@ifundefined{mysubtitle}{}{\mysubtitle\\}\makeatother 156 | {\uppercase\expandafter{\authors}}\par} 157 | \vskip 10mm 158 | {\noindent \copyright {\uppercase\expandafter{\authorsc}}, \writtenyear\par} 159 | \vskip 20mm 160 | {\noindent \thesis\ \YYYYNN\\ 161 | Department of \departmentname\\ 162 | \makeatletter\@ifundefined{divisionnameB}{}{ and Division of \divisionnameB}\makeatother 163 | \\ 164 | \university\\ 165 | \postcode\ \city\\ 166 | \country\\ 167 | Telephone: \telephone\par} 168 | \vfill 169 | \makeatletter\@ifundefined{covercaption}{}{{\noindent Cover:\\\covercaption\par}\vskip 5mm}\makeatother 170 | {\noindent \nameofprinters 171 | %/ Department of \departmentname 172 | \\\city, \country\ \writtenyear\par} 173 | \thispagestyle{empty} 174 | 175 | \newpage 176 | %\thispagestyle{justpage} 177 | \setcounter{page}{1} 178 | \pagenumbering{Roman} 179 | %{\noindent \mytitle\\ 180 | %\makeatletter\@ifundefined{mysubtitle}{}{\mysubtitle\\}\makeatother 181 | %\thesis\ in \nameofprogramme\\ % (if applicable) 182 | %{\uppercase\expandafter{\authors}}\\ 183 | %Department of \departmentname\\ 184 | %Division of \divisionnameA 185 | %\makeatletter\@ifundefined{divisionnameB}{}{ and Division of \divisionnameB}\makeatother\\ 186 | %\university\par} 187 | 188 | 189 | \hyphenpenalty=10000 190 | \tolerance=5000 191 | 192 | %\hyphenation{dereference,heapptr,expression} %, typcheckaren, miss-lyckande, ghci, haskelltolk, haskell, typinformation, javascript, interpreter, programmering, interpretatorn, functions, haskell-kompilator, lambda, modul, exempelvis, grammatik, interpreteringen, pekar, av-sockrar, upp-byggnad, funktions-defenitioner, returnera, av-sockrade, skickas } 193 | 194 | \phantomsection\addcontentsline{toc}{section}{Abstract}\input{kapitel/abstract} 195 | %\noindent Keywords: \keywords 196 | 197 | % Either a swedish translation, or an emtpy page. 198 | \newpage 199 | \phantomsection\addcontentsline{toc}{section}{Sammanfattning}\input{kapitel/sammanfattning} 200 | 201 | \newpage 202 | \mbox{} 203 | 204 | %\newpage 205 | \phantomsection\addcontentsline{toc}{section}{Innehåll} 206 | \tableofcontents 207 | 208 | % Here you can add preface and notations 209 | %\cleardoublepage 210 | %\phantomsection\addcontentsline{toc}{section}{Preface}\input{Preface} 211 | %\vskip 1cm 212 | %\noindent \city\ \writtenmonth\ \writtenyear\\ 213 | %\authorsc 214 | %\newpage 215 | %\phantomsection\addcontentsline{toc}{section}{Notations}\input{Notations} 216 | 217 | \cleardoublepage 218 | \setcounter{page}{1} 219 | \pagenumbering{arabic} 220 | 221 | 222 | % Real contents of report starts here 223 | % Splitting it up to several files help when working together. 224 | % Floatbarriers prevent figures from beeing placed into the next chapter. 225 | \input{kapitel/inledning}\FloatBarrier 226 | \newpage 227 | %\input{kapitel/teori}\FloatBarrier 228 | %\newpage 229 | \input{kapitel/metod.tex}\FloatBarrier 230 | \newpage 231 | \input{kapitel/resultat.tex}\FloatBarrier 232 | \newpage 233 | \input{kapitel/diskussion.tex}\FloatBarrier 234 | \newpage 235 | \input{kapitel/slutsatser.tex}\FloatBarrier 236 | \newpage 237 | % \input{Method}\FloatBarrier 238 | % \input{Results}\FloatBarrier 239 | % \input{Conclusions} 240 | % \input{Recommendations} 241 | 242 | % And the bilbiography saved as mybib.bib 243 | \bibliographystyle{plainnat} 244 | \bibliography{kallor} 245 | 246 | % Appendices 247 | \appendix 248 | \input{kapitel/appendix.tex}\FloatBarrier 249 | \end{document} 250 | 251 | -------------------------------------------------------------------------------- /haskell.interpreter.js: -------------------------------------------------------------------------------- 1 | (function(interpreter, ast, primitives, utilities){ 2 | 3 | interpreter.loadDeclarations = function(declrs, env) { 4 | var lastfunname = null; 5 | var lastfunenv = []; 6 | // TODO: Remove duplication of Function addition. 7 | for (var i in declrs) { 8 | var decl = declrs[i]; 9 | if (decl.type=="Function") { 10 | if (lastfunname == decl.identifier) { 11 | lastfunenv.push(decl); 12 | } else { 13 | if (lastfunenv.length > 0) { 14 | // perhaps the intial HeapPtr shouldn't point to a weakhead, but here it does... 15 | env.bind(lastfunname, new interpreter.HeapPtr(new interpreter.DelayedApplication(env, lastfunenv[0].patterns.length, lastfunenv, []))); 16 | } 17 | lastfunname = decl.identifier; 18 | lastfunenv = [decl]; 19 | } 20 | continue; 21 | } 22 | if (lastfunenv.length > 0) { 23 | // perhaps the intial HeapPtr shouldn't point to a weakhead, but here it does... 24 | env.bind(lastfunname, new interpreter.HeapPtr(new interpreter.DelayedApplication(env, lastfunenv[0].patterns.length, lastfunenv, []))); 25 | lastfunname = ""; 26 | lastfunenv = []; 27 | } 28 | if (decl.type=="Variable") { 29 | env.patternBind(decl.pattern, new interpreter.HeapPtr(new interpreter.Closure(env, decl.expression))); 30 | } 31 | else if (decl.type=="Data") { 32 | for (var i in decl.constructors) { 33 | constr = decl.constructors[i]; 34 | env.bind(constr.identifier, primitives.createDataConstructorKludge(env, constr.identifier, constr.types.length)); 35 | } 36 | } 37 | } 38 | if (lastfunenv.length > 0) { 39 | // perhaps the intial HeapPtr shouldn't point to a weakhead, but here it does... 40 | env.bind(lastfunname, new interpreter.HeapPtr(new interpreter.DelayedApplication(env, lastfunenv[0].patterns.length, lastfunenv, []))); 41 | } 42 | return env; 43 | }; 44 | 45 | // Creates env from an ast and returns it ! 46 | interpreter.prepare = function(astt, env) { 47 | return interpreter.loadDeclarations(astt.declarations, env); 48 | }; 49 | 50 | // Executes a haskell program 51 | interpreter.execute = function(astt) { 52 | var env = new interpreter.RootEnv(); 53 | // Only fun defs atm 54 | interpreter.prepare(astt, env); 55 | primitives.init(env); 56 | return env.lookup("main").dereference(); 57 | }; 58 | 59 | // Evaluates an expression under an env 60 | interpreter.eval = function(astt, env) { 61 | return (new interpreter.HeapPtr(new interpreter.Closure(env, astt))).dereference(); 62 | }; 63 | 64 | // Live data 65 | /* 66 | data Env = RootEnv [(Identifier, Pattern, HeapPtr)|(Identifier, HeapPtr)] 67 | | ChildEnv [(Identifier, Pattern, HeapPtr)|(Identifier, HeapPtr)] Env 68 | */ 69 | interpreter.Env = function() { 70 | 71 | }; 72 | 73 | interpreter.Env.prototype.substitute = function(pattern, expression) { 74 | var newEnv = this.derive(); 75 | newEnv.patternBind(pattern, expression); 76 | return newEnv; 77 | }; 78 | interpreter.Env.prototype.derive = function() { 79 | return new interpreter.ChildEnv(this); 80 | }; 81 | interpreter.Env.prototype.patternBind = function(pattern, expression) { 82 | var vars = pattern.vars(); 83 | for (var i in vars) { 84 | this.env[vars[i]] = [pattern, expression]; 85 | this.env[vars[i]].type = "unforced"; 86 | } 87 | }; 88 | interpreter.Env.prototype.bind = function(identifier, expr) { 89 | utilities.expectType(expr, interpreter.HeapPtr); 90 | this.env[identifier] = expr; 91 | }; 92 | interpreter.Env.prototype.lookup = function(identifier) { 93 | if (this.env[identifier]==undefined) { 94 | return this.onUndefined(identifier); 95 | }; 96 | var found = this.env[identifier]; 97 | if (found.type == "unforced") { 98 | if (!found[0].match(this, found[1])) { 99 | throw new Error("Unrefutable pattern failed"); 100 | }; 101 | }; 102 | return this.env[identifier]; 103 | }; 104 | interpreter.Env.prototype.onUndefined = function(identifier) { 105 | throw new Error("Identifier not in env " + identifier ); 106 | }; 107 | 108 | 109 | interpreter.RootEnv = function() { 110 | this.env = {}; 111 | this.type = "RootEnv"; 112 | }; 113 | interpreter.RootEnv.prototype = new interpreter.Env(); 114 | var childId = 0; 115 | interpreter.ChildEnv = function(parent) { 116 | this.env = {}; 117 | this.type = "ChildEnv"; 118 | this.parent = parent; 119 | this.id = childId++; 120 | }; 121 | interpreter.ChildEnv.prototype = new interpreter.Env(); 122 | 123 | interpreter.ChildEnv.prototype.onUndefined = function(identifier) { 124 | return this.parent.lookup(identifier); 125 | }; 126 | 127 | /* data HeapPtr = HeapPtr Thunk WeakHead */ 128 | // There's no actuall heap pointer here 129 | // The name is just used to highlight that it's 130 | // use is the same as the heap pointer in a 131 | // standard haskell implementation. 132 | // "dereferencing" a HeapPtr returns the forced 133 | // version of the thunk it points to as well 134 | // as updates the pointer to the whnf. 135 | interpreter.HeapPtr = function(thunk) { 136 | this.thunk = thunk; 137 | this.weakHead = undefined; 138 | // Dereferencing a HeapPtr returns a whnf 139 | this.dereference = function() { 140 | if (this.weakHead == undefined) { 141 | // We'll drive the execution here instead of recursing in the force method 142 | var continuation = this.thunk; 143 | while (continuation instanceof interpreter.Thunk) { 144 | continuation = continuation.force(); 145 | } 146 | this.weakHead = continuation; 147 | this.thunk = null; 148 | } 149 | return this.weakHead; 150 | }; 151 | 152 | this.stringify = function() { 153 | if (this.weakHead == undefined) { 154 | return this.thunk.stringify(); 155 | } 156 | if (this.weakHead.stringify == undefined) { 157 | return this.weakHead; 158 | } 159 | return this.weakHead.stringify(); 160 | }; 161 | }; 162 | 163 | 164 | /* 165 | data Thunk = Closure Env Expression 166 | | ConstantThunk Value 167 | | Data Identifier [HeapPtr] 168 | | Primitive Env Function 169 | | PrimData -- Javascript data 170 | */ 171 | interpreter.Thunk = function() {}; 172 | interpreter.Closure = function(env, expression) { 173 | utilities.expectType(env, interpreter.Env); 174 | utilities.expectType(expression, ast.Expression); 175 | this.type = "Closure"; 176 | this.env = env; 177 | this.expression = expression; 178 | // force returns the whnf 179 | this.force = function() { 180 | // Forcing a closure is the same as evaluating its expression under the closures env 181 | return this.expression.eval(this.env); 182 | }; 183 | 184 | this.stringify = function() { 185 | return this.expression.stringify(); 186 | }; 187 | }; 188 | 189 | interpreter.ConstantThunk = function(value) { 190 | this.type = "ConstantThunk"; 191 | this.value = value; 192 | this.force = function() { 193 | return this; 194 | }; 195 | }; 196 | 197 | interpreter.Closure.prototype = new interpreter.Thunk(); 198 | interpreter.ConstantThunk.prototype = new interpreter.Thunk(); 199 | 200 | /* 201 | data WeakHead = Data Identifier [HeapPtr] 202 | | LambdaAbstraction Env Pattern Expression 203 | | Primitive 204 | */ 205 | interpreter.WeakHead = function() {}; 206 | 207 | interpreter.Data = function(identifier, ptrs) { 208 | utilities.expectTypeArray(ptrs, interpreter.HeapPtr); 209 | this.type = "Data"; 210 | this.identifier = identifier; 211 | this.ptrs = ptrs; 212 | this.getPtrs = function() { 213 | return this.ptrs; 214 | }; 215 | 216 | this.stringify = function() { 217 | return "(" + this.identifier + " " + this.ptrs.map(function(ptr) { 218 | return ptr.stringify(); 219 | }).join(" ") + ")"; 220 | }; 221 | }; 222 | interpreter.Data.prototype = new interpreter.WeakHead(); 223 | 224 | interpreter.LambdaAbstraction = function(env, pattern, expression) 225 | { 226 | this.type="LambdaAbstraction"; 227 | this.env = env; 228 | this.pattern = pattern; 229 | this.expression = expression; 230 | this.apply = function(argument) { 231 | var newEnv = this.env.substitute(this.pattern, argument); 232 | return new interpreter.Closure(newEnv, this.expression); 233 | }; 234 | 235 | this.stringify = function() { 236 | return "(\\" + this.pattern.stringify() + " -> " + this.expression.stringify() + ")" 237 | }; 238 | }; 239 | interpreter.LambdaAbstraction.prototype = new interpreter.WeakHead(); 240 | 241 | // A delayed application represents pattern/guard matches which aren't 242 | // desugered. Eg: f 1 = 2; f 2 = 3; 243 | interpreter.DelayedApplication = function(env, numArgs, funcs, givenArgs) { 244 | this.type="DelayedApplication"; 245 | this.env = env; 246 | this.funcs = funcs; 247 | this.numArgs = numArgs; 248 | this.givenArgs = givenArgs; 249 | this.apply = function(argument) { 250 | var givenArgs = this.givenArgs.concat(); 251 | givenArgs.push(argument); 252 | if (this.numArgs == 1) { 253 | for (var i in this.funcs) { 254 | var newEnv = this.env.derive(); 255 | if (this.funcs[i].patternMatch(newEnv, givenArgs)) { 256 | var matchedFunc = this.funcs[i]; 257 | if (matchedFunc.expression instanceof Array) { 258 | for (var j in matchedFunc.expression) { 259 | var guard = matchedFunc.expression[j][0]; 260 | var expression = matchedFunc.expression[j][1]; 261 | var guardResult = (new interpreter.HeapPtr(new interpreter.Closure(newEnv, guard))).dereference(); 262 | if (guardResult.identifier == "True") { 263 | return new interpreter.Closure(newEnv, expression); 264 | } 265 | } 266 | } else { 267 | return new interpreter.Closure(newEnv, matchedFunc.expression); 268 | } 269 | } 270 | } 271 | throw new Error("Failed pattern match"); 272 | } else { 273 | return new interpreter.DelayedApplication(this.env, this.numArgs - 1, this.funcs, givenArgs); 274 | } 275 | }; 276 | }; 277 | interpreter.DelayedApplication.prototype = new interpreter.WeakHead(); 278 | 279 | })(haskell.interpreter, haskell.ast, haskell.primitives, haskell.utilities); 280 | -------------------------------------------------------------------------------- /haskell.hiji.js: -------------------------------------------------------------------------------- 1 | // TODO omm man bläddrar upp i historyn så ska det man skrivit in sparas 2 | var ENTER = '13'; 3 | var UP = '38'; 4 | var DOWN = '40'; 5 | var is_module_loaded = false; 6 | var modules = new Array(); 7 | 8 | 9 | var commands = new Array(); 10 | commands[":l"] = "LOAD"; 11 | commands[":load"] = "LOAD"; 12 | commands[":h"] = "HELP"; 13 | commands[":help"] = "HELP"; 14 | commands[":t"] = "TYPE"; 15 | commands[":type"] = "TYPE"; 16 | 17 | (function($){ 18 | 19 | var evaluateHaskell = function(line, env) 20 | { 21 | var line_ = '{' + line + '}'; 22 | ast = haskell.parser.parse(line_).ast; 23 | if (ast == undefined){ 24 | return "Syntax Error"; 25 | } 26 | if (ast.type == "DoExpr") { 27 | ast = new haskell.ast.DoExpr(new haskell.ast.Application(new haskell.ast.VariableLookup("hijiOutputLine#"), ast.expr)); 28 | } 29 | var doexpr = new haskell.ast.Do([ast, 30 | new haskell.ast.DoExpr(new haskell.ast.Primitive( 31 | function(env) { 32 | return new haskell.interpreter.Data("IO", [new haskell.interpreter.HeapPtr(env)]); 33 | } 34 | ))]); 35 | console.log("%o", doexpr); 36 | var res = haskell.interpreter.eval(doexpr, env); 37 | return res.ptrs[0].dereference(); 38 | }; 39 | var makeModules = function(modules){ 40 | return ""; 41 | }; 42 | var makeEntered = function(modules, entered){ 43 | return $("
  • " + makeModules(modules) + "" + entered + "
  • "); 44 | }; 45 | var makeResponse = function(response){ 46 | 47 | }; 48 | var makeInput = function(modules){ 49 | return "
  • " + makeModules(modules) + "
  • "; 50 | }; 51 | var makeOutput = function(output) { 52 | console.log("%o", output); 53 | return $("
  • ").text(output.toString()); 54 | }; 55 | 56 | $.fn.startHiji = function() { 57 | 58 | //var modules = new Array(); 59 | var hist = new Array(); 60 | 61 | // history 62 | var hiss = new historry; 63 | 64 | try{ 65 | // load history from cookie 66 | hiss_cookie = $.cookie("hiss"); 67 | if(hiss_cookie != null){ 68 | hiss.history_array = JSON.parse(hiss_cookie); 69 | } 70 | } 71 | catch(err){ 72 | console.log("Error: History not loaded from cookie"); 73 | } 74 | 75 | var env = new haskell.interpreter.RootEnv(); 76 | haskell.primitives.init(env); 77 | haskell.primitives.initHiji(env); 78 | 79 | load_module('hs/Prelude.hs'); 80 | 81 | modules[0] = "Prelude"; 82 | 83 | this.html("
      " + makeInput(modules) + "
    "); 84 | 85 | $("input:text:visible:first").focus(); 86 | 87 | this.keydown(function(e){ 88 | var input = $('input', this); 89 | var line = input.attr("value"); 90 | if(e.which==UP){ 91 | input.attr("value", hiss.older(line)); 92 | } 93 | if(e.which==DOWN){ 94 | input.attr("value", hiss.newer(line)); 95 | } 96 | if (e.which==ENTER){ 97 | // history 98 | hiss.addHistory(line); 99 | try{ 100 | $.cookie("hiss", JSON.stringify(hiss.history_array), {expires: 3 }); 101 | } 102 | catch(err){ 103 | console.log("Error: History not saved to cookie"); 104 | } 105 | input.attr("value",""); 106 | 107 | $('.input', this).replaceWith(makeEntered(modules, line)); 108 | if(isCommand(line)){ 109 | runCommand(line, input, line); 110 | }else 111 | { 112 | try { 113 | var newLine = makeEntered(modules, line); 114 | // Global variable: 115 | printArea = $("ol", this); 116 | env = evaluateHaskell(line, env); 117 | console.log("%o", env); 118 | } 119 | catch(e) { 120 | console.log("%o", e); 121 | }; 122 | } 123 | $("ol",this).append(makeInput(modules)); 124 | 125 | //set focus 126 | $("input:text:visible:first").focus(); 127 | } 128 | }); 129 | 130 | // load a module 131 | function load_module(module){ 132 | is_module_loaded = false; 133 | jQuery.ajax({ 134 | async : false, 135 | url : module, 136 | success: function(prelude_data){ 137 | console.log(prelude_data); 138 | try { 139 | var ast = haskell.parser.parse(prelude_data); 140 | console.log("%o", ast); 141 | if (ast.ast == undefined) { 142 | console.log("Syntax Error"); 143 | } 144 | else { 145 | haskell.interpreter.prepare(ast.ast, env); 146 | is_module_loaded = true; 147 | } 148 | } catch(e) { 149 | console.log("%o", e); 150 | } 151 | } 152 | }); 153 | } 154 | 155 | function isCommand(l){ 156 | var line = trim(l); 157 | if(line.charAt(0) == ':') 158 | return true 159 | else 160 | return false 161 | } 162 | 163 | function runCommand(i, input2, line){ 164 | var input = trim(i); 165 | var command = input.indexOf(" ") != -1 ? input.substr(0, input.indexOf(" ")) : input; 166 | // load module 167 | if(commands[command] == "LOAD"){ 168 | var arg = trim(input.substr(command.length)); 169 | var module_name = arg.substr(0, arg.lastIndexOf('.')); 170 | load_module(arg); 171 | if(is_module_loaded){ 172 | var module_already_in_modules = false; 173 | for(x in modules){ 174 | if(modules[x] == module_name) 175 | module_already_in_modules = true; 176 | } 177 | if(module_already_in_modules == false){ 178 | var newLine = makeEntered(modules, line); 179 | var output = makeOutput("Module " + module_name +" loaded"); 180 | $('.input').after(output).replaceWith(newLine); 181 | modules.push(module_name); 182 | $("ol").append(makeInput(modules)); 183 | }else{ 184 | var newLine = makeEntered(modules, line); 185 | var output = makeOutput("Module " + module_name + " already loaded"); 186 | $('.input').after(output).replaceWith(newLine); 187 | $("ol").append(makeInput(modules)); 188 | } 189 | }else{ 190 | var newLine = makeEntered(modules, line); 191 | var output = makeOutput("Module " + module_name + " not found"); 192 | $('.input').after(output).replaceWith(newLine); 193 | $("ol").append(makeInput(modules)); 194 | } 195 | }else if(commands[command] == "HELP"){ 196 | var newLine = makeEntered(modules, line); 197 | var output_row = new Array(); 198 | output_row.push(makeOutput("Help")); 199 | output_row.push(makeOutput(" ")); 200 | output_row.push(makeOutput("Commands:")); 201 | output_row.push(makeOutput(":l [Module] ... load a module")); 202 | var str = "$('.input')"; 203 | for (var i = output_row.length-1; i>=0; i--){ 204 | str += ".after(" + output_row[i] + ")"; 205 | // $('.input').after(output_row[i]).after(output).replaceWith(newLine); 206 | } 207 | str += ".replaceWith(newLine);"; 208 | alert(str); 209 | eval(str); 210 | // var output = makeOutput("HELP HELP HELP" + "
    " + "asdas"); 211 | // $('.input').after(output1).after(output).replaceWith(newLine); 212 | // $('.input').after(output).replaceWith(newLine); 213 | // $('.input').after(output).replaceWith(newLine); 214 | $("ol").append(makeInput(modules)); 215 | 216 | } else if (commands[command] == "TYPE") { 217 | var arg = trim(input.substr(command.length)); 218 | var ast = haskell.parser.parse('{' + arg + '}').ast.expr; 219 | var tc = haskell.typechecker; 220 | var env = new tc.Environment(new tc.Assumps(), new tc.Subst(), new tc.NameGen()); 221 | var newLine = ""; 222 | try { 223 | var infered = ast.infer(env); 224 | var type = infered.type.apply(env.getSubst()); 225 | var predsSubst = infered.preds.map(function(p) { return p.apply(env.getSubst())}); 226 | var preds = tc.unique(predsSubst.filter( 227 | function (p) { 228 | return tc.any( 229 | p.tv(), 230 | function(t) { 231 | return tc.elem(type.tv(), t); 232 | } 233 | ); 234 | } 235 | )); 236 | var predsString = preds.map(function(p) { return p.toString(); }).join(", "); 237 | if (predsString.length > 0) { 238 | predsString = "(" + predsString + ") => "; 239 | } 240 | newLine = ast.stringify() + " :: " + predsString + type.toString(); 241 | 242 | } catch (x) { 243 | newLine=x; 244 | } 245 | $("ol").append(makeOutput(newLine)); 246 | } 247 | } 248 | }; 249 | 250 | })(jQuery); 251 | 252 | function trim(str){ 253 | return str.replace(/^\s+|\s+$/g,""); 254 | } 255 | 256 | // historry-class with nice name 257 | // !!!WARNING!!! NICE NAME. conflict with javascript 258 | historry = function(input){ 259 | this.pointer = -1; 260 | this.history_array = new Array(); 261 | this.active_value = ""; 262 | 263 | this.addHistory = function(input){ 264 | this.history_array.unshift(input); 265 | this.pointer = -1; 266 | }; 267 | 268 | this.updateHistory = function(input){ 269 | this.history_array[this.pointer] = input; 270 | } 271 | 272 | this.older = function(input){ 273 | 274 | if(this.pointer == -1){ 275 | this.active_value = input; 276 | }else{ 277 | this.updateHistory(input); 278 | } 279 | 280 | this.pointer++; 281 | if(this.pointer >= this.history_array.length){ 282 | this.pointer = this.history_array.length-1 283 | } 284 | return this.history_array[this.pointer]; 285 | }; 286 | 287 | this.newer = function(input){ 288 | 289 | if(this.pointer == -1){ 290 | this.active_value = input; 291 | }else{ 292 | this.updateHistory(input); 293 | } 294 | 295 | this.pointer--; 296 | if(this.pointer < 0){ 297 | this.pointer = -1 298 | return this.active_value; 299 | } 300 | return this.history_array[this.pointer]; 301 | }; 302 | 303 | }; 304 | 305 | 306 | 307 | -------------------------------------------------------------------------------- /haskell.primitives.js: -------------------------------------------------------------------------------- 1 | (function(primitives, ast, interpreter){ 2 | primitives.prim = function(env) { 3 | // data Char# 4 | 5 | // gtChar# :: Char# -> Char# -> Bool 6 | env.bind("gtChar#", createPrimitive(env, 2, gtPrim)); 7 | 8 | // geChar# :: Char# -> Char# -> Bool 9 | env.bind("geChar#", createPrimitive(env, 2, gePrim)); 10 | 11 | // eqChar# :: Char# -> Char# -> Bool 12 | env.bind("eqChar#", createPrimitive(env, 2, eqPrim)); 13 | 14 | // neChar# :: Char# -> Char# -> Bool 15 | env.bind("neChar#", createPrimitive(env, 2, nePrim)); 16 | 17 | // ltChar# :: Char# -> Char# -> Bool 18 | env.bind("ltChar#", createPrimitive(env, 2, ltPrim)); 19 | 20 | // leChar# :: Char# -> Char# -> Bool 21 | env.bind("leChar#", createPrimitive(env, 2, lePrim)); 22 | 23 | // ord# :: Char# -> Int# 24 | env.bind("ord#", createPrimitive(env, 1, 25 | function(env, args) { 26 | return args[0].charCodeAt(0); 27 | })); 28 | 29 | // data Int# 30 | var intSize = 32; 31 | var maxInt = (1 << (intSize-1)); 32 | var minInt = (-1 >>> 1); 33 | // (+#) :: Int# -> Int# -> Int# 34 | env.bind("+#", createPrimitive(env, 2, primAdd(intSize, true))); 35 | // (-#) :: Int# -> Int# -> Int# 36 | env.bind("-#", createPrimitive(env, 2, primSub(intSize, true))); 37 | // (*#) :: Int# -> Int# -> Int# 38 | env.bind("*#", createPrimitive(env, 2, primMul(intSize, true))); 39 | // mulIntMayOflo# :: Int# -> Int# -> Int# 40 | env.bind("mulIntMayOflo#", createPrimitive(env, 2, 41 | function(env, args) { 42 | var filter = 0xFFFF << 16; 43 | return (args[0] & filter) | (args[1] & filter); 44 | })); 45 | // quotInt# :: Int# -> Int# -> Int# 46 | env.bind("quotInt#", createPrimitive(env, 2, primQuot(intSize, true))); 47 | // remInt# :: Int# -> Int# -> Int# 48 | env.bind("remInt#", createPrimitive(env, 2, primRem(intSize, true))); 49 | // negateInt# :: Int# -> Int# 50 | env.bind("negateInt#", createPrimitive(env, 1, primNegate(intSize, true))); 51 | // addIntC# :: Int# -> Int# -> (#Int#, Int##) 52 | env.bind("addIntC#", createPrimitive(env, 2, 53 | function(env, args) { 54 | var result = args[0] + args[1]; 55 | var carry = (result > maxInt) || (result < minInt) 56 | return [doPrimtOverflow(intSize, true, result), 0+carry]; 57 | })); 58 | // subIntC# :: Int# -> Int# -> (#Int#, Int##) 59 | env.bind("subIntC#", createPrimitive(env, 2, 60 | function(env, args) { 61 | var result = args[0] - args[1]; 62 | var carry = (result > maxInt) || (result < minInt) 63 | return [doPrimtOverflow(intSize, true, result), 0+carry]; 64 | })); 65 | // (>#) :: Int# -> Int# -> Bool 66 | env.bind(">#", createPrimitive(env, 2, gtPrim)); 67 | // (>=#) :: Int# -> Int# -> Bool 68 | env.bind(">=#", createPrimitive(env, 2, gePrim)); 69 | // (==#) :: Int# -> Int# -> Bool 70 | env.bind("==#", createPrimitive(env, 2, eqPrim)); 71 | // (/=#) :: Int# -> Int# -> Bool 72 | env.bind("/=#", createPrimitive(env, 2, nePrim)); 73 | // (<#) :: Int# -> Int# -> Bool 74 | env.bind("<#", createPrimitive(env, 2, ltPrim)); 75 | // (<=#) :: Int# -> Int# -> Bool 76 | env.bind("<=#", createPrimitive(env, 2, lePrim)); 77 | // chr# :: Int# -> Char# 78 | env.bind("chr#", createPrimitive(env, 1, 79 | function(env, args) { 80 | return String.fromCharCode(args[0]); 81 | })); 82 | // int2Word# :: Int# -> Word# 83 | env.bind("int2Word#", createPrimitive(env, 1, primNarrow(32, true))); 84 | // int2Float# :: Int# -> Float# 85 | env.bind("int2Float#", createPrimitive(env, 1, primNarrow(32, true))); 86 | // int2Double# :: Int# -> Double# 87 | env.bind("int2Double#", createPrimitive(env, 1, primNarrow(64, true))); 88 | // uncheckedIShiftL# :: Int# -> Int# -> Int# 89 | env.bind("uncheckedIShiftL#", createPrimitive(env, 2, uncheckedIShiftL)); 90 | // uncheckedIShiftRA# :: Int# -> Int# -> Int# 91 | env.bind("uncheckedIShiftRA#", createPrimitive(env, 2, uncheckedIShiftRA)); 92 | // uncheckedIShiftRL# :: Int# -> Int# -> Int# 93 | env.bind("uncheckedIShiftRL#", createPrimitive(env, 2, uncheckedIShiftRL)); 94 | 95 | // plusWord# :: Word# -> Word# -> Word# 96 | // minusWord# :: Word# -> Word# -> Word# 97 | // timesWord# :: Word# -> Word# -> Word# 98 | // quotWord# :: Word# -> Word# -> Word# 99 | // remWord# :: Word# -> Word# -> Word# 100 | // and# :: Word# -> Word# -> Word# 101 | // or# :: Word# -> Word# -> Word# 102 | // xor# :: Word# -> Word# -> Word# 103 | // not# :: Word# -> Word# 104 | // uncheckedShiftL# :: Word# -> Int# -> Word# 105 | // Shift left logical. Result undefined if shift amount is not in the range 0 to word size - 1 inclusive. 106 | // uncheckedShiftRL# :: Word# -> Int# -> Word# 107 | // Shift right logical. Result undefined if shift amount is not in the range 0 to word size - 1 inclusive. 108 | // word2Int# :: Word# -> Int# 109 | // word2Integer# :: Word# -> (#Int#, ByteArr##) 110 | // gtWord# :: Word# -> Word# -> Bool 111 | // geWord# :: Word# -> Word# -> Bool 112 | // eqWord# :: Word# -> Word# -> Bool 113 | // neWord# :: Word# -> Word# -> Bool 114 | // ltWord# :: Word# -> Word# -> Bool 115 | // leWord# :: Word# -> Word# -> Bool 116 | 117 | 118 | // Some extras: 119 | // intToString# :: Int# -> String# 120 | env.bind("intToString#", createPrimitive(env, 1, function(env, args) { 121 | return "" + args[0]; 122 | })); 123 | // alert# -> String# -> () -- This should be IO () 124 | env.bind("alert#", createPrimitive(env, 1, function(env, args) { 125 | // TODO: Are we sure that primitives are evaluated? 126 | alert(args[0]); 127 | return new interpreter.Data("()", []); 128 | })); 129 | // debug# :: a -> () -- This should be IO (), outputs an unforced expression. "as is" 130 | env.bind("debug#", createPrimitive(env, 1, function(env, args) { 131 | console.log("debug#: %o", args[0]); 132 | return new interpreter.Data("()", []); 133 | })); 134 | 135 | env.bind("stepDebug#", parameterCollector(env, 1, function(env, args) { 136 | var step = args[0]; 137 | var pending = [step]; 138 | var stepN = 0; 139 | while (pending.length>0) { 140 | console.log("step %i: %o", ++stepN, step.stringify()); 141 | var newPending = []; 142 | for (var i in pending) { 143 | var v = pending[i].dereference(); 144 | if (v instanceof interpreter.Data) 145 | { 146 | newPending = newPending.concat(v.getPtrs()); 147 | } 148 | } 149 | pending = newPending; 150 | } 151 | console.log("done: ", step.stringify()); 152 | return new interpreter.Data("()", []); 153 | })); 154 | }; 155 | 156 | primitives.init = function(env) { 157 | primitives.prim(env); 158 | // seq :: a -> b -> b 159 | env.bind("seq", createPrimitive(env, 2, 160 | function(env, args) { 161 | args[0].force(); 162 | return args[1]; 163 | })); 164 | 165 | // Can print all different haskell types (including functions...) 166 | // Should be hidden away and only used for the deriving Show implementation. 167 | // defaultShow :: a -> String# 168 | env.bind("defaultShow", createPrimitive(env, 1, 169 | function(env) { 170 | alert("undefined"); 171 | })); 172 | 173 | env.bind(":", createDataConstructor(env, ":", 2)); 174 | env.bind("[]", createDataConstructor(env, "[]", 0)); 175 | }; 176 | 177 | function parameterCollector(env, numArgs, func) { 178 | var args = []; 179 | for (var i = 0; i args[1]); 241 | }; 242 | 243 | function gePrim(env, args) { 244 | return boxBool(env, args[0] >= args[1]); 245 | }; 246 | 247 | function eqPrim(env, args) { 248 | return boxBool(env, args[0] == args[1]); 249 | }; 250 | 251 | function nePrim(env, args) { 252 | return boxBool(env, args[0] != args[1]); 253 | }; 254 | 255 | function ltPrim(env, args) { 256 | return boxBool(env, args[0] < args[1]); 257 | }; 258 | 259 | function lePrim(env, args) { 260 | return boxBool(env, args[0] <= args[1]); 261 | }; 262 | 263 | function primAdd(bits, twoComplement) { 264 | return function(env, args) { 265 | var result = args[0] + args[1]; 266 | return doPrimOverflow(bits, twoComplement, result); 267 | }; 268 | }; 269 | 270 | function primSub(bits, twoComplement) { 271 | return function(env, args) { 272 | var result = args[0] - args[1]; 273 | return doPrimOverflow(bits, twoComplement, result); 274 | }; 275 | }; 276 | 277 | function primMul(bits, twoComplement) { 278 | return function(env, args) { 279 | var result = args[0] * args[1]; 280 | return doPrimOverflow(bits, twoComplement, result); 281 | }; 282 | }; 283 | 284 | function primQuot(bits, twoComplement) { 285 | return function(env, args) { 286 | var result = parseInt(args[0] / args[1]); 287 | return doPrimOverflow(bits, twoComplement, result); 288 | }; 289 | }; 290 | 291 | function primRem(bits, twoComplement) { 292 | return function(env, args) { 293 | var result = args[0] % args[1]; 294 | return doPrimOverflow(bits, twoComplement, result); 295 | }; 296 | }; 297 | 298 | function primNegate(bits, twoComplement) { 299 | return function(env, args) { 300 | var result = -args[0]; 301 | return doPrimOverflow(bits, twoComplement, result); 302 | }; 303 | }; 304 | 305 | function primNarrow(bits, twoComplement) { 306 | return function(env, args) { 307 | return doPrimNarrow(bits, twoComplement, args[0]); 308 | }; 309 | }; 310 | 311 | // Narrows a number by chopping of the higher bits 312 | function doPrimNarrow(bits, twoComplement, num) { 313 | num = num & (Math.pow(2, bits+1) - 1); 314 | if (twoComplement && (num & Math.pow(2, bits))) { 315 | return num - Math.pow(2, bits); 316 | }; 317 | return num; 318 | }; 319 | 320 | // Narrows a number by overflowing it 321 | function doPrimOverflow(bits, twoComplement, num) { 322 | return doPrimNarrow(bits, twoComplement, num); 323 | }; 324 | 325 | function uncheckedIShiftL(env, args) { 326 | return args[0] << args[1]; 327 | }; 328 | 329 | function uncheckedIShiftRA(env, args) { 330 | return args[0] >> args[1]; 331 | }; 332 | 333 | function uncheckedIShiftRL(env, args) { 334 | return args[0] >>> args[1]; 335 | }; 336 | })(haskell.primitives, haskell.ast, haskell.interpreter); -------------------------------------------------------------------------------- /lib/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2010-03-20 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, strict: false */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | if (!this.JSON) { 163 | this.JSON = {}; 164 | } 165 | 166 | (function () { 167 | 168 | function f(n) { 169 | // Format integers to have at least two digits. 170 | return n < 10 ? '0' + n : n; 171 | } 172 | 173 | if (typeof Date.prototype.toJSON !== 'function') { 174 | 175 | Date.prototype.toJSON = function (key) { 176 | 177 | return isFinite(this.valueOf()) ? 178 | this.getUTCFullYear() + '-' + 179 | f(this.getUTCMonth() + 1) + '-' + 180 | f(this.getUTCDate()) + 'T' + 181 | f(this.getUTCHours()) + ':' + 182 | f(this.getUTCMinutes()) + ':' + 183 | f(this.getUTCSeconds()) + 'Z' : null; 184 | }; 185 | 186 | String.prototype.toJSON = 187 | Number.prototype.toJSON = 188 | Boolean.prototype.toJSON = function (key) { 189 | return this.valueOf(); 190 | }; 191 | } 192 | 193 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 194 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 195 | gap, 196 | indent, 197 | meta = { // table of character substitutions 198 | '\b': '\\b', 199 | '\t': '\\t', 200 | '\n': '\\n', 201 | '\f': '\\f', 202 | '\r': '\\r', 203 | '"' : '\\"', 204 | '\\': '\\\\' 205 | }, 206 | rep; 207 | 208 | 209 | function quote(string) { 210 | 211 | // If the string contains no control characters, no quote characters, and no 212 | // backslash characters, then we can safely slap some quotes around it. 213 | // Otherwise we must also replace the offending characters with safe escape 214 | // sequences. 215 | 216 | escapable.lastIndex = 0; 217 | return escapable.test(string) ? 218 | '"' + string.replace(escapable, function (a) { 219 | var c = meta[a]; 220 | return typeof c === 'string' ? c : 221 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 222 | }) + '"' : 223 | '"' + string + '"'; 224 | } 225 | 226 | 227 | function str(key, holder) { 228 | 229 | // Produce a string from holder[key]. 230 | 231 | var i, // The loop counter. 232 | k, // The member key. 233 | v, // The member value. 234 | length, 235 | mind = gap, 236 | partial, 237 | value = holder[key]; 238 | 239 | // If the value has a toJSON method, call it to obtain a replacement value. 240 | 241 | if (value && typeof value === 'object' && 242 | typeof value.toJSON === 'function') { 243 | value = value.toJSON(key); 244 | } 245 | 246 | // If we were called with a replacer function, then call the replacer to 247 | // obtain a replacement value. 248 | 249 | if (typeof rep === 'function') { 250 | value = rep.call(holder, key, value); 251 | } 252 | 253 | // What happens next depends on the value's type. 254 | 255 | switch (typeof value) { 256 | case 'string': 257 | return quote(value); 258 | 259 | case 'number': 260 | 261 | // JSON numbers must be finite. Encode non-finite numbers as null. 262 | 263 | return isFinite(value) ? String(value) : 'null'; 264 | 265 | case 'boolean': 266 | case 'null': 267 | 268 | // If the value is a boolean or null, convert it to a string. Note: 269 | // typeof null does not produce 'null'. The case is included here in 270 | // the remote chance that this gets fixed someday. 271 | 272 | return String(value); 273 | 274 | // If the type is 'object', we might be dealing with an object or an array or 275 | // null. 276 | 277 | case 'object': 278 | 279 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 280 | // so watch out for that case. 281 | 282 | if (!value) { 283 | return 'null'; 284 | } 285 | 286 | // Make an array to hold the partial results of stringifying this object value. 287 | 288 | gap += indent; 289 | partial = []; 290 | 291 | // Is the value an array? 292 | 293 | if (Object.prototype.toString.apply(value) === '[object Array]') { 294 | 295 | // The value is an array. Stringify every element. Use null as a placeholder 296 | // for non-JSON values. 297 | 298 | length = value.length; 299 | for (i = 0; i < length; i += 1) { 300 | partial[i] = str(i, value) || 'null'; 301 | } 302 | 303 | // Join all of the elements together, separated with commas, and wrap them in 304 | // brackets. 305 | 306 | v = partial.length === 0 ? '[]' : 307 | gap ? '[\n' + gap + 308 | partial.join(',\n' + gap) + '\n' + 309 | mind + ']' : 310 | '[' + partial.join(',') + ']'; 311 | gap = mind; 312 | return v; 313 | } 314 | 315 | // If the replacer is an array, use it to select the members to be stringified. 316 | 317 | if (rep && typeof rep === 'object') { 318 | length = rep.length; 319 | for (i = 0; i < length; i += 1) { 320 | k = rep[i]; 321 | if (typeof k === 'string') { 322 | v = str(k, value); 323 | if (v) { 324 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 325 | } 326 | } 327 | } 328 | } else { 329 | 330 | // Otherwise, iterate through all of the keys in the object. 331 | 332 | for (k in value) { 333 | if (Object.hasOwnProperty.call(value, k)) { 334 | v = str(k, value); 335 | if (v) { 336 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 337 | } 338 | } 339 | } 340 | } 341 | 342 | // Join all of the member texts together, separated with commas, 343 | // and wrap them in braces. 344 | 345 | v = partial.length === 0 ? '{}' : 346 | gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + 347 | mind + '}' : '{' + partial.join(',') + '}'; 348 | gap = mind; 349 | return v; 350 | } 351 | } 352 | 353 | // If the JSON object does not yet have a stringify method, give it one. 354 | 355 | if (typeof JSON.stringify !== 'function') { 356 | JSON.stringify = function (value, replacer, space) { 357 | 358 | // The stringify method takes a value and an optional replacer, and an optional 359 | // space parameter, and returns a JSON text. The replacer can be a function 360 | // that can replace values, or an array of strings that will select the keys. 361 | // A default replacer method can be provided. Use of the space parameter can 362 | // produce text that is more easily readable. 363 | 364 | var i; 365 | gap = ''; 366 | indent = ''; 367 | 368 | // If the space parameter is a number, make an indent string containing that 369 | // many spaces. 370 | 371 | if (typeof space === 'number') { 372 | for (i = 0; i < space; i += 1) { 373 | indent += ' '; 374 | } 375 | 376 | // If the space parameter is a string, it will be used as the indent string. 377 | 378 | } else if (typeof space === 'string') { 379 | indent = space; 380 | } 381 | 382 | // If there is a replacer, it must be a function or an array. 383 | // Otherwise, throw an error. 384 | 385 | rep = replacer; 386 | if (replacer && typeof replacer !== 'function' && 387 | (typeof replacer !== 'object' || 388 | typeof replacer.length !== 'number')) { 389 | throw new Error('JSON.stringify'); 390 | } 391 | 392 | // Make a fake root object containing our value under the key of ''. 393 | // Return the result of stringifying the value. 394 | 395 | return str('', {'': value}); 396 | }; 397 | } 398 | 399 | 400 | // If the JSON object does not yet have a parse method, give it one. 401 | 402 | if (typeof JSON.parse !== 'function') { 403 | JSON.parse = function (text, reviver) { 404 | 405 | // The parse method takes a text and an optional reviver function, and returns 406 | // a JavaScript value if the text is a valid JSON text. 407 | 408 | var j; 409 | 410 | function walk(holder, key) { 411 | 412 | // The walk method is used to recursively walk the resulting structure so 413 | // that modifications can be made. 414 | 415 | var k, v, value = holder[key]; 416 | if (value && typeof value === 'object') { 417 | for (k in value) { 418 | if (Object.hasOwnProperty.call(value, k)) { 419 | v = walk(value, k); 420 | if (v !== undefined) { 421 | value[k] = v; 422 | } else { 423 | delete value[k]; 424 | } 425 | } 426 | } 427 | } 428 | return reviver.call(holder, key, value); 429 | } 430 | 431 | 432 | // Parsing happens in four stages. In the first stage, we replace certain 433 | // Unicode characters with escape sequences. JavaScript handles many characters 434 | // incorrectly, either silently deleting them, or treating them as line endings. 435 | 436 | text = String(text); 437 | cx.lastIndex = 0; 438 | if (cx.test(text)) { 439 | text = text.replace(cx, function (a) { 440 | return '\\u' + 441 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 442 | }); 443 | } 444 | 445 | // In the second stage, we run the text against regular expressions that look 446 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 447 | // because they can cause invocation, and '=' because it can cause mutation. 448 | // But just to be safe, we want to reject all unexpected forms. 449 | 450 | // We split the second stage into 4 regexp operations in order to work around 451 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 452 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 453 | // replace all simple value tokens with ']' characters. Third, we delete all 454 | // open brackets that follow a colon or comma or that begin the text. Finally, 455 | // we look to see that the remaining characters are only whitespace or ']' or 456 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 457 | 458 | if (/^[\],:{}\s]*$/. 459 | test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). 460 | replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). 461 | replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 462 | 463 | // In the third stage we use the eval function to compile the text into a 464 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 465 | // in JavaScript: it can begin a block or an object literal. We wrap the text 466 | // in parens to eliminate the ambiguity. 467 | 468 | j = eval('(' + text + ')'); 469 | 470 | // In the optional fourth stage, we recursively walk the new structure, passing 471 | // each name/value pair to a reviver function for possible transformation. 472 | 473 | return typeof reviver === 'function' ? 474 | walk({'': j}, '') : j; 475 | } 476 | 477 | // If the text is not JSON parseable, then a SyntaxError is thrown. 478 | 479 | throw new SyntaxError('JSON.parse'); 480 | }; 481 | } 482 | }()); 483 | -------------------------------------------------------------------------------- /lib/jsparse.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2007 Chris Double. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, 7 | // this list of conditions and the following disclaimer. 8 | // 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, 10 | // this list of conditions and the following disclaimer in the documentation 11 | // and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 14 | // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 15 | // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 16 | // DEVELOPERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 17 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 18 | // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 19 | // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 20 | // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 21 | // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 22 | // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | // 24 | function identity(x) { 25 | return x; 26 | } 27 | 28 | function foldl(f, initial, seq) { 29 | for(var i=0; i< seq.length; ++i) 30 | initial = f(initial, seq[i]); 31 | return initial; 32 | } 33 | 34 | var memoize = true; 35 | 36 | function ParseState(input, index) { 37 | this.input = input; 38 | this.index = index || 0; 39 | this.length = input.length - this.index; 40 | this.cache = new Array(); 41 | this.cache.push(new Array()); 42 | return this; 43 | } 44 | 45 | ParseState.prototype.from = function(index) { 46 | var r = new ParseState(this.input, this.index + index); 47 | r.cache = this.cache; 48 | r.length = this.length - index; 49 | return r; 50 | } 51 | 52 | ParseState.prototype.substring = function(start, end) { 53 | return this.input.substring(start + this.index, (end || this.length) + this.index); 54 | } 55 | 56 | ParseState.prototype.trimLeft = function() { 57 | var s = this.substring(0); 58 | var m = s.match(/^\s+/); 59 | return m ? this.from(m[0].length) : this; 60 | } 61 | 62 | ParseState.prototype.at = function(index) { 63 | return this.input.charAt(this.index + index); 64 | } 65 | 66 | ParseState.prototype.toString = function() { 67 | return 'PS"' + this.substring(0) + '"'; 68 | } 69 | 70 | ParseState.prototype.getCached = function(pid) { 71 | if(!memoize) 72 | return false; 73 | 74 | for (var i = 0; i < this.cache.length; i++) { 75 | var p = this.cache[i][pid]; 76 | if (p) { 77 | return p[this.index]; 78 | } 79 | } 80 | 81 | return false; 82 | } 83 | 84 | ParseState.prototype.putCached = function(pid, cached) { 85 | if(!memoize) 86 | return false; 87 | 88 | var p = this.cache[0][pid]; 89 | if (p) { 90 | p[this.index] = cached; 91 | } else { 92 | p = this.cache[0][pid] = { }; 93 | p[this.index] = cached; 94 | } 95 | } 96 | 97 | ParseState.prototype.pushCacheStack = function() { 98 | this.cache.unshift(new Array()); 99 | } 100 | 101 | ParseState.prototype.popCacheStack = function() { 102 | this.cache.shift(); 103 | } 104 | 105 | function ps(str) { 106 | return new ParseState(str); 107 | } 108 | 109 | // 'r' is the remaining string to be parsed. 110 | // 'matched' is the portion of the string that 111 | // was successfully matched by the parser. 112 | // 'ast' is the AST returned by the successfull parse. 113 | function make_result(r, matched, ast) { 114 | return { remaining: r, matched: matched, ast: ast }; 115 | } 116 | 117 | var parser_id = 0; 118 | 119 | // 'token' is a parser combinator that given a string, returns a parser 120 | // that parses that string value. The AST contains the string that was parsed. 121 | function token(s) { 122 | var pid = parser_id++; 123 | return function(state) { 124 | var savedState = state; 125 | var cached = savedState.getCached(pid); 126 | if(cached) 127 | return cached; 128 | 129 | var r = state.length >= s.length && state.substring(0,s.length) == s; 130 | if(r) 131 | cached = { remaining: state.from(s.length), matched: s, ast: s }; 132 | else 133 | cached = false; 134 | savedState.putCached(pid, cached); 135 | return cached; 136 | }; 137 | } 138 | 139 | // Like 'token' but for a single character. Returns a parser that given a string 140 | // containing a single character, parses that character value. 141 | function ch(c) { 142 | var pid = parser_id++; 143 | return function(state) { 144 | var savedState = state; 145 | var cached = savedState.getCached(pid); 146 | if(cached) 147 | return cached; 148 | var r = state.length >= 1 && state.at(0) == c; 149 | if(r) 150 | cached = { remaining: state.from(1), matched: c, ast: c }; 151 | else 152 | cached = false; 153 | savedState.putCached(pid, cached); 154 | return cached; 155 | }; 156 | } 157 | 158 | // 'range' is a parser combinator that returns a single character parser 159 | // (similar to 'ch'). It parses single characters that are in the inclusive 160 | // range of the 'lower' and 'upper' bounds ("a" to "z" for example). 161 | function range(lower, upper) { 162 | var pid = parser_id++; 163 | return function(state) { 164 | var savedState = state; 165 | var cached = savedState.getCached(pid); 166 | if(cached) 167 | return cached; 168 | 169 | if(state.length < 1) 170 | cached = false; 171 | else { 172 | var ch = state.at(0); 173 | if(ch >= lower && ch <= upper) 174 | cached = { remaining: state.from(1), matched: ch, ast: ch }; 175 | else 176 | cached = false; 177 | } 178 | savedState.putCached(pid, cached); 179 | return cached; 180 | }; 181 | } 182 | 183 | // Helper function to convert string literals to token parsers 184 | // and perform other implicit parser conversions. 185 | function toParser(p) { 186 | return (typeof(p) == "string") ? token(p) : p; 187 | } 188 | 189 | // Parser combinator that returns a parser that 190 | // skips whitespace before applying parser. 191 | function whitespace(p) { 192 | var p = toParser(p); 193 | var pid = parser_id++; 194 | return function(state) { 195 | var savedState = state; 196 | var cached = savedState.getCached(pid); 197 | if(cached) 198 | return cached; 199 | 200 | cached = p(state.trimLeft()); 201 | savedState.putCached(pid, cached); 202 | return cached; 203 | }; 204 | } 205 | 206 | // Parser combinator that passes the AST generated from the parser 'p' 207 | // to the function 'f'. The result of 'f' is used as the AST in the result. 208 | function action(p, f) { 209 | var p = toParser(p); 210 | var pid = parser_id++; 211 | return function(state) { 212 | var savedState = state; 213 | var cached = savedState.getCached(pid); 214 | if(cached) 215 | return cached; 216 | 217 | var x = p(state); 218 | if(x) { 219 | x.ast = f(x.ast); 220 | cached = x; 221 | } 222 | else { 223 | cached = false; 224 | } 225 | savedState.putCached(pid, cached); 226 | return cached; 227 | }; 228 | } 229 | 230 | // Given a parser that produces an array as an ast, returns a 231 | // parser that produces an ast with the array joined by a separator. 232 | function join_action(p, sep) { 233 | return action(p, function(ast) { return ast.join(sep); }); 234 | } 235 | 236 | // Given an ast of the form [ Expression, [ a, b, ...] ], convert to 237 | // [ [ [ Expression [ a ] ] b ] ... ] 238 | // This is used for handling left recursive entries in the grammar. e.g. 239 | // MemberExpression: 240 | // PrimaryExpression 241 | // FunctionExpression 242 | // MemberExpression [ Expression ] 243 | // MemberExpression . Identifier 244 | // new MemberExpression Arguments 245 | function left_factor(ast) { 246 | return foldl(function(v, action) { 247 | return [ v, action ]; 248 | }, 249 | ast[0], 250 | ast[1]); 251 | } 252 | 253 | // Return a parser that left factors the ast result of the original 254 | // parser. 255 | function left_factor_action(p) { 256 | return action(p, left_factor); 257 | } 258 | 259 | // 'negate' will negate a single character parser. So given 'ch("a")' it will successfully 260 | // parse any character except for 'a'. Or 'negate(range("a", "z"))' will successfully parse 261 | // anything except the lowercase characters a-z. 262 | function negate(p) { 263 | var p = toParser(p); 264 | var pid = parser_id++; 265 | return function(state) { 266 | var savedState = state; 267 | var cached = savedState.getCached(pid); 268 | if(cached) 269 | return cached; 270 | 271 | if(state.length >= 1) { 272 | var r = p(state); 273 | if(!r) 274 | cached = make_result(state.from(1), state.at(0), state.at(0)); 275 | else 276 | cached = false; 277 | } 278 | else { 279 | cached = false; 280 | } 281 | savedState.putCached(pid, cached); 282 | return cached; 283 | }; 284 | } 285 | 286 | // 'end_p' is a parser that is successful if the input string is empty (ie. end of parse). 287 | function end_p(state) { 288 | if(state.length == 0) 289 | return make_result(state, undefined, undefined); 290 | else 291 | return false; 292 | } 293 | 294 | // 'nothing_p' is a parser that always fails. 295 | function nothing_p(state) { 296 | return false; 297 | } 298 | 299 | // 'sequence' is a parser combinator that processes a number of parsers in sequence. 300 | // It can take any number of arguments, each one being a parser. The parser that 'sequence' 301 | // returns succeeds if all the parsers in the sequence succeeds. It fails if any of them fail. 302 | function sequence() { 303 | var parsers = []; 304 | for(var i = 0; i < arguments.length; ++i) 305 | parsers.push(toParser(arguments[i])); 306 | var pid = parser_id++; 307 | return function(state) { 308 | var savedState = state; 309 | var cached = savedState.getCached(pid); 310 | if(cached) { 311 | return cached; 312 | } 313 | 314 | var ast = []; 315 | var matched = ""; 316 | var i; 317 | for(i=0; i< parsers.length; ++i) { 318 | var parser = parsers[i]; 319 | var result = parser(state); 320 | if(result) { 321 | state = result.remaining; 322 | if(result.ast != undefined) { 323 | ast.push(result.ast); 324 | matched = matched + result.matched; 325 | } 326 | } 327 | else { 328 | break; 329 | } 330 | } 331 | if(i == parsers.length) { 332 | cached = make_result(state, matched, ast); 333 | } 334 | else 335 | cached = false; 336 | savedState.putCached(pid, cached); 337 | return cached; 338 | }; 339 | } 340 | 341 | // Like sequence, but ignores whitespace between individual parsers. 342 | function wsequence() { 343 | var parsers = []; 344 | for(var i=0; i < arguments.length; ++i) { 345 | parsers.push(whitespace(toParser(arguments[i]))); 346 | } 347 | return sequence.apply(null, parsers); 348 | } 349 | 350 | // 'choice' is a parser combinator that provides a choice between other parsers. 351 | // It takes any number of parsers as arguments and returns a parser that will try 352 | // each of the given parsers in order. The first one that succeeds results in a 353 | // successfull parse. It fails if all parsers fail. 354 | function choice() { 355 | var parsers = []; 356 | for(var i = 0; i < arguments.length; ++i) 357 | parsers.push(toParser(arguments[i])); 358 | var pid = parser_id++; 359 | return function(state) { 360 | var savedState = state; 361 | 362 | var cached = savedState.getCached(pid); 363 | if(cached) { 364 | return cached; 365 | } 366 | 367 | state.pushCacheStack(); 368 | 369 | var i; 370 | for(i=0; i< parsers.length; ++i) { 371 | var parser=parsers[i]; 372 | var result = parser(state); 373 | if(result) { 374 | break; 375 | } 376 | } 377 | 378 | state.popCacheStack(); 379 | 380 | if(i == parsers.length) { 381 | cached = false; 382 | } else { 383 | cached = result; 384 | } 385 | 386 | savedState.putCached(pid, cached); 387 | return cached; 388 | } 389 | } 390 | 391 | // 'butnot' is a parser combinator that takes two parsers, 'p1' and 'p2'. 392 | // It returns a parser that succeeds if 'p1' matches and 'p2' does not, or 393 | // 'p1' matches and the matched text is longer that p2's. 394 | // Useful for things like: butnot(IdentifierName, ReservedWord) 395 | function butnot(p1,p2) { 396 | var p1 = toParser(p1); 397 | var p2 = toParser(p2); 398 | var pid = parser_id++; 399 | 400 | // match a but not b. if both match and b's matched text is shorter 401 | // than a's, a failed match is made 402 | return function(state) { 403 | var savedState = state; 404 | var cached = savedState.getCached(pid); 405 | if(cached) 406 | return cached; 407 | 408 | var br = p2(state); 409 | if(!br) { 410 | cached = p1(state); 411 | } else { 412 | var ar = p1(state); 413 | if(ar && ar.matched.length > br.matched.length) 414 | cached = ar; 415 | else 416 | cached = false; 417 | } 418 | savedState.putCached(pid, cached); 419 | return cached; 420 | } 421 | } 422 | 423 | // 'difference' is a parser combinator that takes two parsers, 'p1' and 'p2'. 424 | // It returns a parser that succeeds if 'p1' matches and 'p2' does not. If 425 | // both match then if p2's matched text is shorter than p1's it is successfull. 426 | function difference(p1,p2) { 427 | var p1 = toParser(p1); 428 | var p2 = toParser(p2); 429 | var pid = parser_id++; 430 | 431 | // match a but not b. if both match and b's matched text is shorter 432 | // than a's, a successfull match is made 433 | return function(state) { 434 | var savedState = sate; 435 | var cached = savedState.getCached(pid); 436 | if(cached) 437 | return cached; 438 | 439 | var br = p2(state); 440 | if(!br) { 441 | cached = p1(state); 442 | } else { 443 | var ar = p1(state); 444 | if(ar.matched.length >= br.matched.length) 445 | cached = br; 446 | else 447 | cached = ar; 448 | } 449 | savedState.putCached(pid, cached); 450 | return cached; 451 | } 452 | } 453 | 454 | 455 | // 'xor' is a parser combinator that takes two parsers, 'p1' and 'p2'. 456 | // It returns a parser that succeeds if 'p1' or 'p2' match but fails if 457 | // they both match. 458 | function xor(p1, p2) { 459 | var p1 = toParser(p1); 460 | var p2 = toParser(p2); 461 | var pid = parser_id++; 462 | 463 | // match a or b but not both 464 | return function(state) { 465 | var savedState = state; 466 | var cached = savedState.getCached(pid); 467 | if(cached) 468 | return cached; 469 | 470 | var ar = p1(state); 471 | var br = p2(state); 472 | if(ar && br) 473 | cached = false; 474 | else 475 | cached = ar || br; 476 | savedState.putCached(pid, cached); 477 | return cached; 478 | } 479 | } 480 | 481 | // A parser combinator that takes one parser. It returns a parser that 482 | // looks for zero or more matches of the original parser. 483 | function repeat0(p) { 484 | var p = toParser(p); 485 | var pid = parser_id++; 486 | 487 | return function(state) { 488 | var savedState = state; 489 | var cached = savedState.getCached(pid); 490 | if(cached) { 491 | return cached; 492 | } 493 | 494 | var ast = []; 495 | var matched = ""; 496 | var result; 497 | while(result = p(state)) { 498 | ast.push(result.ast); 499 | matched = matched + result.matched; 500 | if(result.remaining.index == state.index) 501 | break; 502 | state = result.remaining; 503 | } 504 | cached = make_result(state, matched, ast); 505 | savedState.putCached(pid, cached); 506 | return cached; 507 | } 508 | } 509 | 510 | // A parser combinator that takes one parser. It returns a parser that 511 | // looks for one or more matches of the original parser. 512 | function repeat1(p) { 513 | var p = toParser(p); 514 | var pid = parser_id++; 515 | 516 | return function(state) { 517 | var savedState = state; 518 | var cached = savedState.getCached(pid); 519 | if(cached) 520 | return cached; 521 | 522 | var ast = []; 523 | var matched = ""; 524 | var result= p(state); 525 | if(!result) 526 | cached = false; 527 | else { 528 | while(result) { 529 | ast.push(result.ast); 530 | matched = matched + result.matched; 531 | if(result.remaining.index == state.index) 532 | break; 533 | state = result.remaining; 534 | result = p(state); 535 | } 536 | cached = make_result(state, matched, ast); 537 | } 538 | savedState.putCached(pid, cached); 539 | return cached; 540 | } 541 | } 542 | 543 | // Like repeat but requires at least n matches 544 | function repeatn(p, n) { 545 | var p = toParser(p); 546 | var pid = parser_id++; 547 | 548 | return function(state) { 549 | var savedState = state; 550 | var cached = savedState.getCached(pid); 551 | if (cached) 552 | return cached; 553 | 554 | var ast = []; 555 | var matched = ""; 556 | var result; 557 | var i = 0; 558 | while(result = p(state)) { 559 | ast.push(result.ast); 560 | matched = matched + result.matched; 561 | i++; 562 | if(result.remaining.index == state.index) 563 | break; 564 | state = result.remaining; 565 | } 566 | if (i >= n) 567 | cached = make_result(state, matched, ast); 568 | else 569 | cached = false; 570 | savedState.putCached(pid, cached); 571 | return cached; 572 | } 573 | } 574 | 575 | // A parser combinator that takes one parser. It returns a parser that 576 | // matches zero or one matches of the original parser. 577 | function optional(p) { 578 | var p = toParser(p); 579 | var pid = parser_id++; 580 | return function(state) { 581 | var savedState = state; 582 | var cached = savedState.getCached(pid); 583 | if(cached) 584 | return cached; 585 | var r = p(state); 586 | cached = r || make_result(state, "", false); 587 | savedState.putCached(pid, cached); 588 | return cached; 589 | } 590 | } 591 | 592 | // A parser combinator that ensures that the given parser succeeds but 593 | // ignores its result. This can be useful for parsing literals that you 594 | // don't want to appear in the ast. eg: 595 | // sequence(expect("("), Number, expect(")")) => ast: Number 596 | function expect(p) { 597 | return action(p, function(ast) { return undefined; }); 598 | } 599 | 600 | function chain(p, s, f) { 601 | var p = toParser(p); 602 | 603 | return action(sequence(p, repeat0(action(sequence(s, p), f))), 604 | function(ast) { return [ast[0]].concat(ast[1]); }); 605 | } 606 | 607 | // A parser combinator to do left chaining and evaluation. Like 'chain', it expects a parser 608 | // for an item and for a seperator. The seperator parser's AST result should be a function 609 | // of the form: function(lhs,rhs) { return x; } 610 | // Where 'x' is the result of applying some operation to the lhs and rhs AST's from the item 611 | // parser. 612 | function chainl(p, s) { 613 | var p = toParser(p); 614 | return action(sequence(p, repeat0(sequence(s, p))), 615 | function(ast) { 616 | return foldl(function(v, action) { return action[0](v, action[1]); }, ast[0], ast[1]); 617 | }); 618 | } 619 | 620 | // A parser combinator that returns a parser that matches lists of things. The parser to 621 | // match the list item and the parser to match the seperator need to 622 | // be provided. The AST is the array of matched items. 623 | function list(p, s) { 624 | return chain(p, s, function(ast) { return ast[1]; }); 625 | } 626 | 627 | // Like list, but ignores whitespace between individual parsers. 628 | function wlist() { 629 | var parsers = []; 630 | for(var i=0; i < arguments.length; ++i) { 631 | parsers.push(whitespace(arguments[i])); 632 | } 633 | return list.apply(null, parsers); 634 | } 635 | 636 | // A parser that always returns a zero length match 637 | function epsilon_p(state) { 638 | return make_result(state, "", undefined); 639 | } 640 | 641 | // Allows attaching of a function anywhere in the grammer. If the function returns 642 | // true then parse succeeds otherwise it fails. Can be used for testing if a symbol 643 | // is in the symbol table, etc. 644 | function semantic(f) { 645 | var pid = parser_id++; 646 | return function(state) { 647 | var savedState = state; 648 | var cached = savedState.getCached(pid); 649 | if(cached) 650 | return cached; 651 | cached = f() ? make_result(state, "", undefined) : false; 652 | savedState.putCached(pid, cached); 653 | return cached; 654 | } 655 | } 656 | 657 | // The and predicate asserts that a certain conditional 658 | // syntax is satisfied before evaluating another production. Eg: 659 | // sequence(and("0"), oct_p) 660 | // (if a leading zero, then parse octal) 661 | // It succeeds if 'p' succeeds and fails if 'p' fails. It never 662 | // consume any input however, and doesn't put anything in the resulting 663 | // AST. 664 | function and(p) { 665 | var p = toParser(p); 666 | var pid = parser_id++; 667 | return function(state) { 668 | var savedState = state; 669 | var cached = savedState.getCached(pid); 670 | if(cached) 671 | return cached; 672 | var r = p(state); 673 | cached = r ? make_result(state, "", undefined) : false; 674 | savedState.putCached(pid, cached); 675 | return cached; 676 | } 677 | } 678 | 679 | // The opposite of 'and'. It fails if 'p' succeeds and succeeds if 680 | // 'p' fails. It never consumes any input. This combined with 'and' can 681 | // be used for 'lookahead' and disambiguation of cases. 682 | // 683 | // Compare: 684 | // sequence("a",choice("+","++"),"b") 685 | // parses a+b 686 | // but not a++b because the + matches the first part and peg's don't 687 | // backtrack to other choice options if they succeed but later things fail. 688 | // 689 | // sequence("a",choice(sequence("+", not("+")),"++"),"b") 690 | // parses a+b 691 | // parses a++b 692 | // 693 | function not(p) { 694 | var p = toParser(p); 695 | var pid = parser_id++; 696 | return function(state) { 697 | var savedState = state; 698 | var cached = savedState.getCached(pid); 699 | if(cached) 700 | return cached; 701 | cached = p(state) ? false : make_result(state, "", undefined); 702 | savedState.putCached(pid, cached); 703 | return cached; 704 | } 705 | } 706 | 707 | var ws = whitespace; 708 | 709 | var expectws = function(p) { 710 | return expect(ws(p)); 711 | }; 712 | -------------------------------------------------------------------------------- /haskell.ast.js: -------------------------------------------------------------------------------- 1 | (function(ast, interpreter, utilities) { 2 | expectType = utilities.expectType; 3 | 4 | expectTypeOf = utilities.expectTypeOf; 5 | 6 | expectTypeArray = utilities.expectTypeArray; 7 | 8 | /* 9 | data Module = Module VisibleNames [Import] [Declaration] 10 | */ 11 | ast.Module = function(declarations) { 12 | expectTypeArray(declarations, ast.Declaration); 13 | this.declarations = declarations; 14 | }; 15 | 16 | /* 17 | data VisibleNames = Hiding [NameDef] 18 | | Showing [NameDef] 19 | */ 20 | 21 | /* 22 | data NameDef = Name Identifier | ConstructorName Identifer [NameDef] | NoName 23 | // NoName = ... 24 | */ 25 | 26 | /* 27 | Import = Import Identifier VisibleNames 28 | | ImportQualified Identifer VisibleNames Identifier 29 | */ 30 | 31 | /* 32 | data Type = TypeVariable Identifier 33 | | TypeConstructor Identifier 34 | | TypeApplication Type Type 35 | | TypeTupple Int 36 | */ 37 | 38 | ast.Type = function() {}; 39 | 40 | ast.TypeVariable = function(name) { 41 | expectTypeOf(name, "string"); 42 | this.name = name; 43 | }; 44 | ast.TypeVariable.prototype = new ast.Type(); 45 | ast.TypeVariable.prototype.stringify = function() { 46 | return this.name; 47 | }; 48 | 49 | ast.TypeConstructor = function(name) { 50 | expectTypeOf(name, "string"); 51 | this.name = name; 52 | }; 53 | ast.TypeConstructor.prototype = new ast.Type(); 54 | ast.TypeConstructor.prototype.stringify = function() { 55 | return this.name; 56 | }; 57 | 58 | ast.TypeApplication = function(t1, t2) { 59 | expectType(t1, ast.Type); 60 | expectType(t2, ast.Type); 61 | this.t1 = t1; 62 | this.t2 = t2; 63 | }; 64 | ast.TypeApplication.prototype = new ast.Type(); 65 | ast.TypeApplication.prototype.stringify = function() { 66 | return this.t1.stringify() + " " + this.t2.stringify(); 67 | }; 68 | 69 | ast.TypeTupple = function(size) { 70 | expectTypeOf(size, "number"); 71 | this.size = size; 72 | }; 73 | ast.TypeTupple.prototype = new ast.Type(); 74 | ast.TypeTupple.prototype.stringify = function() { 75 | return "(" + (new Array(this.size)).join(",") + ")"; 76 | }; 77 | 78 | /* 79 | data Constraint = Constraint Identifier Identifier 80 | */ 81 | 82 | ast.Constraint = function(typeclass, typevar) { 83 | expectTypeOf(typeclass, "string"); 84 | expectTypeOf(typevar, "string"); 85 | this.typeclass = typeclass; 86 | this.typevar = typevar; 87 | }; 88 | ast.Constraint.prototype.stringify = function() { 89 | return this.typeclass + " " + this.typevar; 90 | }; 91 | 92 | /* 93 | data TypeConstraint = TypeConstraint [Constraint] Type 94 | */ 95 | ast.TypeConstraint = function(constraints, type) { 96 | expectTypeArray(constraints, ast.Constraint); 97 | expectType(type, ast.Type); 98 | this.constraints = constraints; 99 | this.type = type; 100 | }; 101 | ast.Constraint.prototype.stringify = function() { 102 | var constraints = this.constraints.map(function(p) { return p.stringify(); }).join(", "); 103 | if (constraints.length > 0) { 104 | constraints = "(" + constraints + ") => "; 105 | } 106 | return predsString + this.type.stringify(); 107 | }; 108 | 109 | 110 | /* 111 | data Expression = Constant Value 112 | | Lambda Pattern Expression 113 | | Application Expression Expression 114 | | Let Pattern Expression Expression 115 | | Case Expression [(Pattern, Expression)] 116 | | VariableLookup Identifier 117 | | ExpressionTypeConstraint Expression TypeConstraint 118 | | If Expression Expression Expression 119 | | Do [DoNotation] 120 | | List [Expression] 121 | | ArithmeticSequence Expression (Maybe Expression) (Maybe Expression) 122 | | ListComprehension Expression [ListNotation] 123 | | Primitive Function 124 | */ 125 | // Eval returns a whnf 126 | ast.Expression = function(){}; 127 | ast.Expression.prototype = new Object(); 128 | // Either desugar or the other needs overriding in child objects 129 | ast.Expression.prototype.eval = function(env) { 130 | return this.desugar().eval(env); 131 | }; 132 | ast.Expression.prototype.stringify = function() { 133 | return this.desugar().stringify(); 134 | }; 135 | ast.Expression.prototype.desugar = function() { 136 | return undefined; 137 | }; 138 | 139 | ast.Constant = function(value) { 140 | expectType(value, ast.Value); 141 | this.type ="Constant"; 142 | this.value = value; 143 | }; 144 | ast.Constant.prototype = new ast.Expression(); 145 | ast.Constant.prototype.eval = function(env) { 146 | return this.value.eval(env); 147 | }; 148 | ast.Constant.prototype.stringify = function() { 149 | return this.value.stringify(); 150 | }; 151 | 152 | ast.Lambda = function(pattern, expression) { 153 | expectType(pattern, ast.Pattern); 154 | expectType(expression, ast.Expression); 155 | this.type = "Lambda"; 156 | this.pattern = pattern; 157 | this.expression = expression; 158 | 159 | this.eval = function(env) { 160 | return new interpreter.LambdaAbstraction(env, this.pattern, this.expression); 161 | }; 162 | 163 | this.stringify = function() { 164 | return "(\\" + this.pattern.stringify() + " -> " + this.expression.stringify() + ")"; 165 | }; 166 | }; 167 | ast.Application = function(func, arg) { 168 | expectType(func, ast.Expression); 169 | expectType(arg, ast.Expression); 170 | this.type = "Application"; 171 | this.func = func; 172 | this.arg = arg; 173 | this.eval = function(env) { 174 | var continuation = this.func.eval(env); 175 | while (!(continuation instanceof interpreter.WeakHead)) { 176 | continuation = continuation.force(); 177 | }; 178 | return continuation.apply(new interpreter.HeapPtr(new interpreter.Closure(env, this.arg))); 179 | }; 180 | this.stringify = function() { 181 | return "(" + this.func.stringify() + ") (" + this.arg.stringify() + ")"; 182 | }; 183 | }; 184 | ast.Let = function(declr, expr) { 185 | expectTypeArray(declr, ast.Declaration); 186 | expectType(expr, ast.Expression); 187 | this.type = "Let"; 188 | this.declr = declr; 189 | this.expr = expr; 190 | this.eval = function(env) { 191 | var newEnv = interpreter.loadDeclarations(this.declr, env.derive()); 192 | return new interpreter.Closure(newEnv, this.expr); 193 | }; 194 | this.stringify = function() { 195 | return "let {" + this.declr.map(function (d) { 196 | return d.stringify(); 197 | }).join(";") + "} in " + this.expr.stringify(); 198 | }; 199 | }; 200 | ast.Case = function(expr, cases) { 201 | expectType(expr, ast.Expression); 202 | // TODO: Expect cases [(Pattern, Expression)] 203 | this.type = "Case"; 204 | this.expr = expr; 205 | this.cases = cases; 206 | this.eval = function(env) { 207 | var expr = new interpreter.HeapPtr(new interpreter.Closure(env, this.expr)); 208 | for (var i in this.cases) { 209 | var newEnv = env.derive(); 210 | if (this.cases[i][0].match(newEnv, expr)) { 211 | return new interpreter.Closure(newEnv, this.cases[i][1]); 212 | }; 213 | }; 214 | throw new Error("No matching clause"); 215 | }; 216 | 217 | this.stringify = function() { 218 | return "case " + this.expr.stringify() + " of {" + 219 | this.cases.map(function(c) { 220 | return c[0].stringify() + " -> " + c[1].stringify() + ";"; 221 | }).join("") + "}"; 222 | }; 223 | }; 224 | ast.VariableLookup = function(identifier) { 225 | expectTypeOf(identifier, "string"); 226 | this.type = "VariableLookup"; 227 | this.identifier = identifier; 228 | this.eval = function(env) { 229 | return env.lookup(this.identifier).dereference(); 230 | }; 231 | 232 | this.stringify = function() { 233 | return this.identifier; 234 | }; 235 | }; 236 | ast.ExpressionTypeConstraint = function(expr, type) { 237 | expectType(expr, ast.Expression); 238 | expectType(type, ast.TypeConstraint); 239 | this.expr = expr; 240 | this.type = type; 241 | this.eval = function(env) { 242 | return this.expr.eval(env); 243 | }; 244 | 245 | this.stringify = function() { 246 | return this.expr.stringify() + " :: " + this.type.stringify(); 247 | }; 248 | }; 249 | ast.ExpressionTypeConstraint.prototype = new ast.Expression(); 250 | 251 | ast.If = function(ifExpr, thenExpr, elseExpr) { 252 | expectType(ifExpr, ast.Expression); 253 | expectType(thenExpr, ast.Expression); 254 | expectType(elseExpr, ast.Expression); 255 | this.ifExpr = ifExpr; 256 | this.thenExpr = thenExpr; 257 | this.elseExpr = elseExpr; 258 | this.eval = function(env) { 259 | 260 | }; 261 | 262 | this.stringify = function() { 263 | return "if " + this.ifExpr.stringify() + " then " + this.thenExpr.stringify() + " else " + this.elseExpr.stringify(); 264 | }; 265 | 266 | this.eval = function(env) { 267 | var expr = new interpreter.HeapPtr(new interpreter.Closure(env, this.ifExpr)); 268 | var res = expr.dereference(); 269 | if (new ast.PatternConstructor("True", []).match(env, expr)) { 270 | return this.thenExpr; 271 | } else { 272 | return this.elseExpr; 273 | } 274 | }; 275 | }; 276 | ast.If.prototype = new ast.Expression(); 277 | 278 | ast.Do = function(notations) { 279 | expectTypeArray(notations, ast.DoNotation); 280 | this.type="Do"; 281 | this.notations = notations; 282 | }; 283 | ast.Do.prototype = new ast.Expression(); 284 | ast.Do.prototype.desugar = function() { 285 | var rest = this.notations.concat(); 286 | var first = rest.shift(); 287 | return (first.partDesugar(rest)); 288 | }; 289 | 290 | ast.List = function(expressions) { 291 | expectTypeArray(expressions, ast.Expression); 292 | this.type="List"; 293 | this.expressions = expressions; 294 | }; 295 | ast.List.prototype = new ast.Expression(); 296 | ast.List.prototype.desugar = function() { 297 | // [] = [] 298 | // [1] = 1:[] 299 | // [1,2] = 1:[2] 300 | if (this.expressions.length == 0) { 301 | return new ast.VariableLookup("[]"); 302 | }; 303 | var first = this.expressions[0]; 304 | return new ast.Application(new ast.Application(new ast.VariableLookup(":"), 305 | first), 306 | new ast.List(this.expressions.slice(1)) 307 | ); 308 | }; 309 | 310 | ast.ArithmeticSequence = function(e1, e2, e3) { 311 | expectType(e1, ast.Expression); 312 | if (e2) expectType(e2, ast.Expression); 313 | if (e3) expectType(e3, ast.Expression); 314 | this.type="ArithmeticSequence"; 315 | this.e1 = e1; 316 | this.e2 = e2; 317 | this.e3 = e3; 318 | }; 319 | ast.ArithmeticSequence.prototype = new ast.Expression(); 320 | ast.ArithmeticSequence.prototype.desugar = function() { 321 | // [e1..] = enumFrom e1 322 | // [e1,e2..] = enumFromThen e1 e2 323 | // [e1..e3] = enumFromTo e1 e3 324 | // [e1,e2..e3] = enumFromThenTo e1 e2 e3 325 | var funname = 'enumFrom'; 326 | if (this.e2) funname = funname + 'Then'; 327 | if (this.e3) funname = funname + 'To'; 328 | var application = new ast.Application(new ast.VariableLookup(funname), this.e1); 329 | if (this.e2) application = new ast.Application(application, this.e2); 330 | if (this.e3) application = new ast.Application(application, this.e3); 331 | return application; 332 | }; 333 | 334 | ast.ListComprehension = function(ret, notations) { 335 | expectType(ret, ast.Expression); 336 | expectTypeArray(notations, ast.ListNotation); 337 | this.type="ListComprehensions"; 338 | this.ret = ret; 339 | this.notations = notations; 340 | }; 341 | ast.ListComprehension.prototype = new ast.Expression(); 342 | ast.ListComprehension.prototype.desugar = function() { 343 | if (this.notations.length == 0) { 344 | return (new ast.Application(new ast.Application(new ast.VariableLookup(":"), this.ret), new ast.VariableLookup("[]"))); 345 | } 346 | var first = this.notations[0]; 347 | return first.partDesugar(new ast.ListComprehension(this.ret, 348 | this.notations.slice(1))); 349 | }; 350 | 351 | 352 | 353 | ast.Primitive = function(func) { 354 | expectTypeOf(func, "function"); 355 | this.type="Primitive"; 356 | this.func = func; 357 | this.eval = function(env) { 358 | return this.func(env); 359 | }; 360 | 361 | this.stringify = function() { 362 | return "{primitive function}"; 363 | }; 364 | }; 365 | ast.Lambda.prototype = new ast.Expression(); 366 | ast.Application.prototype = new ast.Expression(); 367 | ast.Let.prototype = new ast.Expression(); 368 | ast.Case.prototype = new ast.Expression(); 369 | ast.VariableLookup.prototype = new ast.Expression(); 370 | ast.Primitive.prototype = new ast.Expression(); 371 | /* ast.Constant.prototype.constructor = ast.Constant; 372 | ast.Lambda.prototype.constructor = ast.Lambda; 373 | ast.Application.prototype.constructor = ast.Application; 374 | ast.Let.prototype.constructor = ast.Let; 375 | ast.Case.prototype.constructor = ast.Case; 376 | ast.VariableLookup.prototype.constructor = ast.VariableLookup; 377 | ast.Primitive.prototype.constructor = ast.Primitive; */ 378 | 379 | /* 380 | data DoNotation = DoLet [Declaration] 381 | | DoBind Pattern Expression 382 | | DoExpr Expression 383 | */ 384 | ast.DoNotation = function(){}; 385 | 386 | ast.DoLet = function(declrs) { 387 | expectTypeArray(declrs, ast.Declaration); 388 | this.type="DoLet"; 389 | this.declrs = declrs; 390 | }; 391 | ast.DoLet.prototype = new ast.DoNotation(); 392 | ast.DoLet.prototype.partDesugar = function(rest) { 393 | // let declr ; do ==> let declr in do 394 | return new ast.Let(this.declrs, new ast.Do(rest)); 395 | }; 396 | 397 | ast.DoBind = function(pattern, expression) { 398 | expectType(pattern, ast.Pattern); 399 | expectType(expression, ast.Expression); 400 | this.type="DoBind"; 401 | this.pattern = pattern; 402 | this.expression = expression; 403 | }; 404 | ast.DoBind.prototype = new ast.DoNotation(); 405 | ast.DoBind.prototype.partDesugar = function(rest) { 406 | // x <- expr ; do ==> expr >>= (a -> case a of x -> do; _ -> fail undefined) 407 | return new ast.Application( 408 | new ast.Application( 409 | new ast.VariableLookup(">>="), 410 | this.expression 411 | ), 412 | new ast.Lambda(new ast.VariableBinding("__todoGenerateunique"), 413 | new ast.Case(new ast.VariableLookup("__todoGenerateunique"), 414 | [[this.pattern, new ast.Do(rest)], 415 | [new ast.Wildcard(),new ast.Application(new ast.VariableLookup("fail"), new ast.VariableLookup("undefined"))]]) 416 | ) 417 | ); 418 | }; 419 | 420 | ast.DoExpr = function(expr) { 421 | expectType(expr, ast.Expression); 422 | this.type="DoExpr"; 423 | this.expr = expr; 424 | }; 425 | ast.DoExpr.prototype = new ast.DoNotation(); 426 | ast.DoExpr.prototype.partDesugar = function(rest) { 427 | if (rest.length == 0) { 428 | return this.expr; 429 | }; 430 | // expr ; do ==> expr >> do 431 | return new ast.Application(new ast.Application(new ast.VariableLookup(">>"), this.expr), new ast.Do(rest)); 432 | }; 433 | 434 | /* 435 | data ListNotation = ListGuard Expression 436 | | ListBind Pattern Expression 437 | | ListLet Pattern Expression 438 | */ 439 | 440 | ast.ListNotation = function() {}; 441 | 442 | ast.ListGuard = function(expr) { 443 | expectType(expr, ast.Expression); 444 | this.type="ListGuard"; 445 | this.expr = expr; 446 | }; 447 | ast.ListGuard.prototype = new ast.ListNotation(); 448 | ast.ListGuard.prototype.partDesugar = function(rest) { 449 | return new ast.Application(new ast.Application(new ast.VariableLookup("concatMap"), 450 | new ast.Lambda(new ast.Wildcard(), rest)), 451 | new ast.Application(new ast.VariableLookup("guard"), 452 | this.expr)); 453 | }; 454 | 455 | ast.ListBind = function(pattern, expr) { 456 | expectType(pattern, ast.Pattern); 457 | expectType(expr, ast.Expression); 458 | this.type="ListBind"; 459 | this.pattern = pattern; 460 | this.expr = expr; 461 | }; 462 | ast.ListBind.prototype = new ast.ListNotation(); 463 | ast.ListBind.prototype.partDesugar = function(rest) { 464 | return new ast.Application(new ast.Application(new ast.VariableLookup("concatMap"), 465 | new ast.Lambda(this.pattern, rest)), 466 | this.expr); 467 | }; 468 | 469 | ast.ListLet = function(decls) { 470 | expectType(decls, ast.Declaration); 471 | this.type="ListLet"; 472 | this.decls = decls 473 | }; 474 | ast.ListLet.prototype = new ast.ListNotation(); 475 | ast.ListLet.prototype.partDesugar = function() { 476 | // let p = expr ==> let p = expr ... 477 | return new ast.DoLet(this.decls); 478 | }; 479 | 480 | 481 | 482 | /* 483 | data Value = Num Int 484 | */ 485 | ast.Value = function() {}; 486 | ast.Num = function(num) { 487 | expectTypeOf(num, "number"); 488 | this.type = "Num"; 489 | this.num = num; 490 | 491 | this.match = function(env, n) { 492 | return (new ast.PatternConstructor("I#", [new ast.ConstantPattern(new ast.PrimitiveValue(this.num))])).match(env, n); 493 | }; 494 | 495 | this.eval = function(env) { 496 | return new interpreter.Data("I#", [new interpreter.HeapPtr(new interpreter.Closure(env, new ast.Constant(new ast.PrimitiveValue(this.num))))]); 497 | }; 498 | 499 | this.stringify = function() { 500 | return this.num; 501 | }; 502 | }; 503 | ast.PrimitiveValue = function(value) { 504 | this.type="PrimitiveValue"; 505 | this.value = value; 506 | this.eval = function(env) { 507 | return this.value; 508 | }; 509 | this.match = function(env, v) { 510 | return this.value == v.dereference(); 511 | }; 512 | 513 | this.stringify = function() 514 | { 515 | return this.value + "#"; 516 | }; 517 | }; 518 | 519 | ast.Num.prototype = new ast.Value(); 520 | ast.PrimitiveValue.prototype = new ast.Value(); 521 | /* 522 | data Declaration = Variable Pattern Expression 523 | | Function Identifier [Pattern] [(Guard, Expression)]|Expression 524 | | Data Identifier [TVar] [Constructor] 525 | | TypeConstraintDeclaration [Identifier] TypeConstraint 526 | */ 527 | 528 | ast.Declaration = function(){}; 529 | 530 | ast.Variable = function(pattern, expression) { 531 | expectType(pattern, ast.Pattern); 532 | expectType(expression, ast.Expression); 533 | this.type = "Variable"; 534 | this.pattern = pattern; 535 | this.expression = expression; 536 | 537 | this.stringify = function() { 538 | return this.pattern.stringify() + " = " + this.expression.stringify(); 539 | }; 540 | }; 541 | 542 | ast.Data = function(identifier, tvars, constructors) { 543 | expectTypeOf(identifier, "string"); 544 | expectTypeArray(tvars, ast.TypeVariable); 545 | expectTypeArray(constructors, ast.Constructor); 546 | this.type = "Data"; 547 | this.identifier = identifier; 548 | this.tvars = tvars; 549 | this.constructors = constructors; 550 | }; 551 | 552 | // expression can either be an expression or a list of [guard, expression] 553 | // where guard is an expression of type Boolean. 554 | ast.Function = function(identifier, patterns, expression) { 555 | expectTypeOf(identifier, "string"); 556 | expectTypeArray(patterns, ast.Pattern); 557 | // expression can be two different kinds... TODO: fix this? 558 | this.type = "Function"; 559 | this.identifier = identifier; 560 | this.patterns = patterns; 561 | this.expression = expression; 562 | this.patternMatch = function(env, givenArgs) { 563 | for (var i in this.patterns) { 564 | if (!this.patterns[i].match(env, givenArgs[i])) { 565 | return false; 566 | } 567 | }; 568 | return true; 569 | }; 570 | 571 | this.stringify = function() { 572 | var exprstr; 573 | if (this.expression instanceof Array) { 574 | exprstr = this.expression.map(function(e) { 575 | return e[0].stringify() + " -> " + e[1].stringify(); 576 | }).join("|"); 577 | } else { 578 | exprstr = this.expression.stringify(); 579 | }; 580 | return this.identifier + this.patterns.map(function(p) { 581 | return p.stringify(); 582 | }).join(" ") + " = " + exprstr; 583 | }; 584 | }; 585 | 586 | ast.TypeConstraintDeclaration = function(funs, type) { 587 | // expectTypeArrayOf(funs, "string"); 588 | expectType(type, ast.TypeConstraint); 589 | this.funs = funs; 590 | this.type = type; 591 | }; 592 | ast.TypeConstraintDeclaration.prototype = new ast.Declaration(); 593 | 594 | ast.Variable.prototype = new ast.Declaration(); 595 | ast.Data.prototype = new ast.Declaration(); 596 | ast.Function.prototype = new ast.Declaration(); 597 | 598 | /* 599 | data Constructor = Constructor Identifier [Type] 600 | */ 601 | 602 | ast.Constructor = function(identifier, types) { 603 | expectTypeOf(identifier, "string"); 604 | expectTypeArray(types, ast.Type); 605 | this.type = "Constructor"; 606 | this.identifier = identifier; 607 | this.types = types; 608 | }; 609 | 610 | /* 611 | Pattern = Constructor Identifier [Pattern] 612 | | VariableBinding Identifier 613 | | Combined Identifier Pattern 614 | | ConstantPattern Value 615 | | Wildcard 616 | */ 617 | 618 | ast.Pattern = function(){}; 619 | 620 | ast.PatternConstructor = function(identifier, patterns) { 621 | expectTypeOf(identifier, "string"); 622 | expectTypeArray(patterns, ast.Pattern); 623 | this.type = "PatternConstructor"; 624 | this.identifier = identifier; 625 | this.patterns = patterns; 626 | this.match = function(env, expr) { 627 | var weakHead = expr.dereference(); 628 | if (this.identifier!=weakHead.identifier) { 629 | return false; 630 | }; 631 | for (var i in this.patterns) { 632 | if (!this.patterns[i].match(env, weakHead.ptrs[i])) { 633 | return false; 634 | }; 635 | }; 636 | return true; 637 | }; 638 | 639 | this.vars = function() { 640 | var vars=[]; 641 | for (var i in this.patterns) { 642 | vars=vars.concat(this.patterns[i].vars()); 643 | }; 644 | return vars; 645 | }; 646 | 647 | this.stringify = function() { 648 | return this.identifier + " " + this.patterns.map(function(p) { 649 | return p.stringify(); 650 | }).join(" "); 651 | }; 652 | }; 653 | ast.VariableBinding = function(identifier) { 654 | expectTypeOf(identifier, "string"); 655 | this.type = "VariableBinding"; 656 | this.identifier = identifier; 657 | this.match = function(env, expr) { 658 | env.bind(this.identifier, expr); 659 | return true; 660 | }; 661 | this.vars = function() { 662 | return [this.identifier]; 663 | }; 664 | 665 | this.stringify = function() { 666 | return this.identifier; 667 | }; 668 | }; 669 | ast.Combined = function(identifier, pattern) { 670 | expectTypeOf(identifier, "string"); 671 | expectType(pattern, ast.Pattern); 672 | this.type = "Combined"; 673 | this.identifier = identifier; 674 | this.pattern = pattern; 675 | this.match = function(env, expr) { 676 | env.bind(this.identifier, expr); 677 | return this.pattern.match(env, expr); 678 | }; 679 | this.vars = function() { 680 | return [this.identifier].concat(this.pattern.vars()); 681 | }; 682 | this.stringify = function() { 683 | return this.identifier + "@(" + this.pattern.stringify() + ")"; 684 | }; 685 | }; 686 | ast.ConstantPattern = function(value) { 687 | expectType(value, ast.Value); 688 | this.type = "ConstantPattern"; 689 | this.value = value; 690 | this.match = function(env, expr) { 691 | return (this.value.match(env, expr)); 692 | }; 693 | this.vars = function() { 694 | return []; 695 | }; 696 | 697 | this.stringify = function() { 698 | return this.value.stringify(); 699 | }; 700 | }; 701 | ast.Wildcard = function() { 702 | this.type = "Wildcard"; 703 | this.match = function(env, expr) { 704 | return true; 705 | }; 706 | this.vars = function() { 707 | return []; 708 | }; 709 | this.stringify = function() { 710 | return "_"; 711 | }; 712 | }; 713 | 714 | ast.PatternConstructor.prototype = new ast.Pattern(); 715 | ast.VariableBinding.prototype = new ast.Pattern(); 716 | ast.Combined.prototype = new ast.Pattern(); 717 | ast.ConstantPattern.prototype = new ast.Pattern(); 718 | ast.Wildcard.prototype = new ast.Pattern(); 719 | })(haskell.ast,haskell.interpreter, haskell.utilities); 720 | -------------------------------------------------------------------------------- /rapport/kapitel/resultat.tex: -------------------------------------------------------------------------------- 1 | \section{Resultat} 2 | % TODO 3 | 4 | Projektets resultat är ett kodbibliotek för en haskelltolk. Den nuvarande statusen av projektet är att parser och interpretator är fullt integrerade med varandra. Typcheckaren är inte integrerad, dock är den färdigutvecklad. För att integrera typcheckaren måste det abstrakta syntaxträdet utvidgas för att ta hänsyn till typinformation. 5 | Koden finns att tillgå på GitHub: \url{http://github.com/johang88/haskellinjavascript}. Nedan följer en mer noggrann genomgång av projektets olika delar. 6 | 7 | \subsection{Parser} 8 | Parserns uppgift är att ta användarens indata och konvertera den till en datastruktur 9 | som är lättare att hantera internt. Denna datastruktur kallas \emph{Abstract Syntax Tree} (AST). 10 | Haskellstandarden har definierat upp en grammatik, ett antal regler, som definierar hur korrekt haskellkod ser ut och hur den ska tolkas. 11 | 12 | Haskell är ett svårt språk att parsa då det inte är kontextfritt på grund av att kodens mening beror på blanksteg, 13 | Haskell tillåter även användardefinierade operatorer vilket också kan påverka kodens mening. 14 | 15 | För att parsa indatan använder vi ett bibliotek för att bygga parsers kallat JSParse. 16 | JSParse ger oss ett antal funktioner som vi använder för att definiera grammatiken och konvertera den till vår interna struktur. 17 | 18 | Som figur \ref{fig:parser_steg} visar, består parsern av tre mindre parsers, den första är en parser som hittar kommentarer och tar bort dessa. 19 | Det andra tar hand om kodens layout och gör om den till kontextfri kod. Den tredje gör om den kontextfria koden till vår AST. 20 | 21 | \begin{figure}[H] 22 | \begin{center} 23 | \includegraphics[width=.5\textwidth]{parser_1.png} 24 | \caption{Parserns olika steg} 25 | \label{fig:parser_steg} % Labels must come after caption! 26 | \end{center} 27 | \end{figure} 28 | 29 | Det var naturligt att dela upp parsern i tre olika steg. De tre olika stegen är skilda från varandra och vi kunde utveckla och testa dem individuellt. 30 | Nedan följer ett exempel på parserns arbetssätt och de tre olika stegen: 31 | \begin{lstlisting} 32 | -- Kommentar 33 | f x = case x of 34 | True -> False 35 | False -> True 36 | \end{lstlisting} 37 | 38 | Efter första steget: 39 | \begin{lstlisting} 40 | f x = case x of 41 | True -> False 42 | False -> True 43 | \end{lstlisting} 44 | 45 | Efter andra steget: 46 | \begin{lstlisting} 47 | f x = case x of { 48 | True -> False; 49 | False -> True 50 | } 51 | \end{lstlisting} 52 | 53 | Efter det tredje steget är en AST genererad. 54 | \begin{lstlisting} 55 | Function("f", Lambda("x", 56 | Case(VariableLookup("x"), [ 57 | [PatternConstructor("True"), PatterConstructor("False")], 58 | [PatternConstructor("False"), PatterConstructor("True")], 59 | ]) 60 | )) 61 | \end{lstlisting} 62 | 63 | \subsubsection{Steg 1 - Ta bort kommentarer} 64 | Det första steget använder en parser som identifierar och tar bort kommentarer. 65 | Haskell har två olika kommentarsstilar, enkelradiga som börjar med \emph{--} och slutar vid första radbytet, och 66 | nästlade som kan gå över flera rader börjar med \emph{\{-} och slutar med \emph{-\}}. 67 | 68 | \subsubsection{Steg 2 - Applicera layoutregler} 69 | Det andra steget applicerar Haskells layoutregler enligt två algoritmer som är definierade i haskellstandarden \citep{haskell98chap9}. Den första delar upp koden i dess ord och symboler samtidigt som den dekoreras med indenteringsnivåer. 70 | Därefter användas den andra för att sätta in måsvingar och semikolon på rätt platser. 71 | När de två algoritmerna är klara sätts koden ihop igen och skickas vidare till nästa steg. 72 | 73 | Ett exempel på en layoutregel är att ett inre block inte får vara mer indenterat än ett omslutande block. 74 | \begin{lstlisting} 75 | case x of 76 | True -> ... 77 | \end{lstlisting} 78 | Här är \emph{True -> ...} ett inre block till \emph{case} och mer indenterat. 79 | 80 | Ett annat exempel är: 81 | \begin{lstlisting} 82 | let x = 5 83 | y = 4 84 | in x + y 85 | \end{lstlisting} 86 | Den korrekta översättningen är: 87 | \begin{lstlisting} 88 | let { x = 5; y = 4 } in x + y 89 | \end{lstlisting} 90 | För att översätta detta korrekt kommer parsern ihåg den aktuella nästlingsnivån av \emph{let}-uttryck och var deras respektive \emph{in}-uttryck befinner sig. 91 | Den avslutande måsvingen sätts in där ett matchande \emph{in}-uttryck påträffas. 92 | 93 | Det finns även uttryck som inte översätts korrekt, exempelvis: 94 | \begin{lstlisting} 95 | [x | let x = 2] 96 | \end{lstlisting} 97 | Den korrekta översättningen är: 98 | \begin{lstlisting} 99 | [x | let { x = 2 }] 100 | \end{lstlisting} 101 | Men i parsern blir det: 102 | \begin{lstlisting} 103 | [ x | let { x = 2 ] } 104 | \end{lstlisting} 105 | Anledningen är att endast nästlingen av \emph{let} och \emph{in} sparas, men här finns inget \emph{in}. 106 | För att lösa felet måste parsern hålla reda på antalet parenteser, måsvingar, hakparenteser och komman efter ett \emph{let}-uttryck och när en symbol som gör det ogiltigt 107 | med en avslutande måsvinge påträffas sätts måsvingen in precis innan symbolen. 108 | 109 | \subsubsection{Steg 3 - Skapa AST} 110 | Det tredje steget är en parser för den kontextfria varianten av Haskell som den är definierad i standarden. 111 | Samtidigt som koden tolkas byggs en AST upp. Parsern består av en liten parser för varje grammatisk regel som är definierad i haskellstandarden. 112 | Dessa parsers kombineras ihop för att bilda den slutgiltiga parsern. Det resulterar i ett träd av parsers, en parser för hela programmet som har flera mindre parsers under sig. 113 | 114 | Exempel på grammatik definierad i Haskellstandarden: 115 | \begin{lstlisting} 116 | gdrhs -> gd = exp [gdrhs] 117 | \end{lstlisting} 118 | Dess motsvarande parser: 119 | \begin{lstlisting} 120 | var gdrhs = gdrhs_action( 121 | repeat1(gdrhs_fix_list_action(sequence(ws(gd), 122 | expectws('='), ws(exp))))); 123 | \end{lstlisting} 124 | 125 | Tittar vi närmare på \emph{gdrhs} ser vi att det är en rekursiv deklaration. Vilket vi implementerar med \emph{repeat1} för att få en lista med minst en upprepning. 126 | \emph{gdrhs\_action} och \emph{gdrhs\_fix\_list\_action} används för att generera det abstrakta syntaxträdet. 127 | 128 | Parsern använder den metod som är specificerad i Haskell 2010 \citep{haskell2010} för att lösa företrädesreglerna (precedence levels) för operatorer då den här metoden är enklare än den som är definierad i Haskell 98. 129 | Anledningen till att företrädesreglerna inte kan definieras direkt i parsern är att Haskell använder sig av användardefinierade företrädesregler. 130 | Metoden fungerar så att den löser företrädesreglerna först efter ett uttryck har parsats till en lista med operatorer 131 | och uttryck, när en operator påträffas i listan slås dess företrädesnivå upp i en tabell och ett träd med 132 | operatorer och uttryck skapas. Till sist används trädet för att generera en AST för uttrycken. 133 | 134 | \subsubsection{JSParse} 135 | Vi använder en modifierad version av JSParse där vi har korrigerat två fel och lagt till fler parsers. Felen vi korrigerade var i \emph{butnot}-parsern och i \emph{choice}-parserns cachefunktion. 136 | \emph{Choice}-parsern cachade resultat från parsers som misslyckades och det cachade resultatet användes i senare parsers, 137 | vi löste det med en stackbaserad cache där cachen för en parser som misslyckas raderas. 138 | JSParse har andra problem som vi inte löst, exempelvis att det inte rapporterar rad- och kolumn-nummer eller parsningsfel korrekt, detta gör att vi inte får bra felmeddelanden. 139 | 140 | Parsers som vi har lagt till: 141 | \begin{enumerate} 142 | \item{\emph{repeatn}: en parser som upprepar en parser minst \emph{n} antal gånger} 143 | \item{\emph{expectws}: en parser som tillåter blanksteg och inte returnerar någon AST, är en kombination av JSParse inbyggda parsers \emph{expect} och \emph{whitespace}} 144 | \end{enumerate} 145 | 146 | \subsection{Typcheckare} 147 | Typcheckarens uppgift är att analysera det abstrakta syntaxträdet, inferera typerna på dess olika 148 | beståndsdelar och avgöra om sättet på vilket de används och interagerar med 149 | andra beståndsdelar är konsekvent. Om så inte är fallet sägs programmet ha 150 | typfel. 151 | 152 | Vår implementation av typcheckaren är starkt influerad av den implementation i Haskell som beskrivs i \citep{jones99} men är omdesignad för att göra sig bättre i Javascript. Det följande avsnittet förutsätter viss förkunskap om typinferens och polymorfiska typer. För en bra introduktion till detta referera till \citep{dragonbookchap6} och för en djupare genomgång \citep{pierce02}. 153 | 154 | \subsubsection{Haskells typsystem} 155 | Jämfört med mer konventionella språk (C, C++, Java etc) skiljer sig Haskell 156 | och övriga statiskt typade funktionella språk på flera sätt. I de senare 157 | medges i många fall att programmeraren själv inte behöver ge explicita 158 | typdeklarationer för variabler och funktioner. Istället arbetar typcheckaren 159 | med en process där kriterier samlas in för varje enskild beståndsdel och 160 | sedan utifrån detta försöker finna en så allmän typ som möjligt eller om 161 | detta inte är möjligt; meddela programmeraren om typfel. Denna process 162 | kallas typinferens. 163 | 164 | Typinferens möjliggör även för polymorfiska typer vilket innebär att 165 | typcheckaren alltid försöker hålla en typ så allmän som möjligt. Om 166 | exempelvis en funktion inte är beroende av funktionalitet associerad med en 167 | specifik typ hos något av sina argument kan data av alla typer användas som 168 | argumentet. 169 | 170 | Typklasser är en form av ad-hoc polymorfism som används flitigt i 171 | Haskell. Faktum är att de är så fundamentala i Haskell att de får en central 172 | roll i typcheckningsprocessen. Deras främsta funktion är att möjliggöra funktionsöverlagring beroende på typer på argument och förväntad returtyp. 173 | 174 | \subsubsection{Representation av kinds, typer, typscheman och typklasser} 175 | För att representera typsystemets olika beståndsdelar använder typcheckaren ett antal olika datastrukturer. Här ger vi en snabb genomgång av dessa för att sedan kunna fokusera på själva typcheckningsprocessen i de sedan följande avsnitten. 176 | 177 | Kinds kan liknas vid en motsvarighet till typer för typkonstruktorer. \emph{*} (uttalas ``stjärna'', eng. ``star'') representerar enkla (nullary) typer som \emph{Integer} och \emph{Integer -> Bool} medan komplexa typer som tar argument representeras med applicering av kinds \emph{k1 -> k2}. Exempelvis har \emph{Maybe} kind \emph{*->*} medan \emph{Maybe Bool} har kind \emph{*}. 178 | 179 | \begin{lstlisting} 180 | data Kind = Star 181 | | Kfun Kind Kind 182 | \end{lstlisting} 183 | 184 | 185 | \begin{lstlisting} 186 | data Type = TVar Id Kind 187 | | TAp Type Type 188 | | TCon Id Kind 189 | | TGen Int 190 | \end{lstlisting} 191 | 192 | \emph{TVar} representerar typvariabler. Dessa har namn som vanligtvis tilldelas internt i typcheckaren. \emph{TAp} representerar applicering av typer och \emph{TCon} representerar typkonstruktorer för konkreta typer. \emph{TGen} representerar 193 | generiska typer och används enbart i samband med kvantifiering av 194 | typer internt i typcheckaren 195 | 196 | Att konstruera en typ med den här notationen är enkelt. Typen \emph{[a] -> 197 | Integer} som är typen för standardfunktionen length i Haskell beskrivs med 198 | \begin{lstlisting} 199 | (TAp 200 | (TAp 201 | (TCon "(->)" (Kfun Star (Kfun Star Star))) 202 | (TAp 203 | (TCon "[]" (Kfun Star Star)) 204 | (TVar "a" Star))) 205 | (TCon "Integer" Star)) 206 | \end{lstlisting} 207 | 208 | För att hålla reda på vilka typklasser en typ tillhör används predikat: 209 | \begin{lstlisting} 210 | data Pred = Pred Id Type 211 | \end{lstlisting} 212 | Ett predikat \emph{Pred className type} säger att typen \emph{type} tillhör typklassen \emph{className}. 213 | 214 | Ofta består typer av flera sammansatta enklare typer där olika typer kan tillhöra olika typklasser. För att modellera detta används kvalificerade typer: 215 | \begin{lstlisting} 216 | data Qual = Qual [Pred] Type 217 | \end{lstlisting} 218 | 219 | Den kvalificerade typen \emph{Monad m => m a} modelleras som: 220 | \begin{lstlisting} 221 | (Qual 222 | [ Pred "Monad" (TVar "m" (Kfun Star Star)) ] 223 | (TAp 224 | (TVar "m" (Kfun Star Star)) 225 | (TVar "a" Star))) 226 | \end{lstlisting} 227 | 228 | För att representera de hela typscheman som typcheckaren får in från användaren används datatypen \emph{Scheme}. 229 | \begin{lstlisting} 230 | data Scheme = Scheme [Kind] Qual 231 | \end{lstlisting} 232 | Eftersom typvariabler (\emph{TVar id kind}) endast skapas internt i typcheckaren representeras här alla generiska typer med \emph{TGen n} där \emph{n} helt enkelt anger att det är den \emph{n}:te generiska typen i typschemat och dess kind finns lagrat på position \emph{n} i \emph{[Kind]} 233 | 234 | För att representera det tidigare exemplet på detta sätt byter vi ut alla identiska förekomster av \emph{TVar id kind} mot identiska \emph{TGen i}: 235 | \begin{lstlisting} 236 | (Scheme 237 | [Kfun Star Star, Star] 238 | (Qual 239 | [Pred "Monad" (TGen 0)] 240 | (TAp 241 | (TGen 0) 242 | (TGen 1)))) 243 | \end{lstlisting} 244 | 245 | För att hålla reda på vilka typklasser och instanser som finns används datatypen \emph{Klass} (\emph{class} är ett reserverat ord i Javascript) som representerar enskilda typklasser och deras instanser samt datatypen \emph{KlassEnvironment} som representerar den totala klassmiljön. 246 | 247 | \subsubsection{Substitutioner} 248 | Substitutionerna är mappningar \emph{typvariabel -> typ} och används av typcheckaren 249 | för att hålla typer uppdaterade efterhand som den får ny 250 | information. Typcheckaren innehåller flera funktioner som opererar på 251 | substitutioner, bland annat sammansättning av substitutioner. %% TODO var ursprungligen "komponering och sammanfognin" ska det vara två olika begrepp? 252 | 253 | \emph{Unifiering} är processen att finna en substitution som gör två typer ekvivalenta. Detta är viktigt exempelvis i uttryck {\bf if} \emph{e1} {\bf then} \emph{e2} {\bf else} \emph{e3}. Här måste typen för \emph{e1} gå att unifiera med typen \emph{Bool} och typen för \emph{e2} och \emph{e3} måste gå att unifiera. Om någon av dessa unifieringar inte är möjliga betyder det att typfel finns. 254 | 255 | \subsubsection{Typinferens} 256 | Typinferens är den sammanfogande delen av typcheckningsprocessen och går ut på att typcheckaren traverserar abstrakta syntaxträdet och samlar de kriterier som måste vara uppfyllda för att programmet ska vara korrekt. 257 | 258 | Eftersom en variabels exakta typ inte är helt fastställd förrän typcheckningsprocessen är färdig görs istället efterhand antaganden om dess typ. För att modellera ett sådant antagande används datatypen \emph{Assump}: 259 | \begin{lstlisting} 260 | data Assump = Assump Id Scheme 261 | \end{lstlisting} 262 | 263 | Typinferensen är beroende av att en antal komponenter finns tillgängliga. Dessa är registret över typklasser och instanser \emph{KlassEnv}, listan över antagna variabeltyper, en generator för unika typvariabler med funktionen \emph{newTVar} och substitutionernas \emph{Subst}. Dessa samlas i objektet \emph{Environment} som skickas som argument till funktionerna i typinferensen. 264 | För att bättre illustrera hur typinfereringsprocessen går till ger vi ett konkret exempel. Koden nedan visar hur typinferering av \emph{if}-uttryck går till. Eftersom vårt projekt avsockrar \emph{if}-uttryck till \emph{case} använder det inte exempelkoden men då den på ett bra sätt illustrerar konceptet väljer vi här ändå att använda den: 265 | 266 | \begin{lstlisting} 267 | ast.If.prototype.infer = function(env) { 268 | var b = this.condExpr.infer(env); 269 | env.unify( 270 | b.type, 271 | new typechecker.TCon("Bool", new typechecker.Star())); 272 | var te = this.thenExpr.infer(env); 273 | val ee = this.elseExpr.infer(env); 274 | env.unify(te.type, ee.type); 275 | return { 276 | preds: te.preds.concat(ee.preds), 277 | type: te.type 278 | }; 279 | }; 280 | \end{lstlisting} 281 | Först infereras typen på villkoret. Detta unifieras med typen \emph{Bool} på raden under. Sedan infereras typerna på \emph{then}- respektive \emph{else}-delarna och unifieras då dessa måste vara av samma typ. Om typerna inte går att unifiera innebär det att dess returtyper är distinkta och att det därför finns typfel. Till sist returneras de insamlade predikaten från både \emph{then}- och \emph{else}-delarna tillsammans med den unifierade typen. 282 | 283 | Av detta exempel kan ett antal slutsatser dras. Typinfereringen sker rekursivt från sammansatta uttryck ner till de alla enklaste literalerna. Att misslyckande av unifiering betyder typfel innebär att inferera-unifiera-cykeln har en roll liknande den infer-check har vid typcheckning i exempelvis C. 284 | 285 | \subsubsection{Defaulting} 286 | Ofta förekommer situationer där tvetydigheter gör att typcheckaren inte kan bestämma en typ. För att förenkla för programmeraren finns därför förbestämda standardtyper att använda i dessa situationer för en del inbyggda typer. Detta användande av förbestämda standartyper kallas för defaulting. 287 | 288 | \subsubsection{Typcheckarens nuvarande status} 289 | I sitt nuvarande tillstånd klarar typcheckaren av att typchecka och typinferera haskellprogram. Detta inkluderar program som använder polymorfiska typer och typklasser. Vi har även stöd för \emph{defaulting}. Dock behöver typcheckaren integreras med interpretatorn för att kunna ladda rätt instans av typklassser vid applicering av överlagrade funktioner under körning. 290 | 291 | \subsection{Interpretatorns struktur} 292 | Interpretatorns uppgift är att tolka det abstrakta syntaxträdet. Under interpreteringen används flera datastrukturer vars uppgift och struktur anges här. 293 | 294 | \subsubsection{Thunk} 295 | En \emph{Thunk} är en avstannad beräkning, en \emph{continuation}. En \emph{Thunk} består av en \emph{Env} och en \emph{Expression}. 296 | 297 | \begin{lstlisting} 298 | data Thunk = Closure Env Expression 299 | \end{lstlisting} 300 | 301 | Haskell måste använda sig av \emph{non-strict evaluation}, vilket innebär att en uträkning inte får köras ifall den inte behövs. När en uträkning körs så innebär det att den resulterar i flertalet \emph{Thunks} för de delar av beräkningen som ännu inte behövs. När värdet av en \emph{Thunk} behövs kommer den att tvingas till en \emph{Weak Head Normal Form} (WHNF) eller en ny \emph{Thunk}. Anledningen till detta är att vi på så sätt minskar användandet av rekursion vilket minskar risken att vi får ett runtime error. 302 | 303 | \subsubsection{Weak Head Normal Form} 304 | En WHNF är ett partiellt evaluerat uttryck. Uttrycket har blivit evaluerat så långt att vi är säkra på att det returnerar något typ av värde, alltså något som inte är \emph{undefined}. Ofta innehåller en WHNF referenser till ännu icke evaluerade uttryck, om de inte gör det sägs uttrycket vara i \emph{Normal Form}. 305 | 306 | \begin{lstlisting} 307 | data WeakHead 308 | = Data Identifier [HeapPtr] 309 | | LambdaAbstraction Env Pattern Expression 310 | | DelayedApplication Env Int [Declaration] [HeapPtr] 311 | | Primitive 312 | \end{lstlisting} 313 | 314 | En \emph{Data} är resultatet av att applicera en algebraisk datakonstruktor på dess argument. Argumenten ges som en lista av \emph{HeapPtr}, det vill säga en lista av evaluerade eller icke evaluerade uttryck. Exempelvis resulterar \emph{Just 1} i en \emph{Data} med Identifiern \emph{Just} och en \emph{HeapPtr} till det icke evaluerade uttrycket 1. 315 | 316 | En \emph{LambdaAbstraction} är körningsrepresentationen av en lambda-funktion, en \emph{Env} är bunden till lambda-funktionen. 317 | 318 | En \emph{DelayedApplication} är ett specialfall av en \emph{LambdaAbstraction}. Vi avsockrar inte Haskells funktionsdeklarationer, vilket innebär att \emph{pattern matching} sker även vid funktionsapplikation och inte bara i \emph{Case-satser}. Detta betyder att vi måste samla alla argument till en funktion innan vi kan avgöra vilket funktionsalternativ som skall användas. En vidare beskrivning finns i kapitlet Declaration. 319 | 320 | En \emph{Primitive} är ett Javascript-värde, till exempel en \emph{integer} eller en \emph{double}. 321 | 322 | \subsubsection{HeapPtr} 323 | De flesta implementationer av Haskell använder sig av lat evaluering, vilket innebär att en \emph{Thunk}, ett uttryck, kommer att tvingas maximalt en gång. I vår implementation används \emph{HeapPtr} som en wrapper runt en \emph{Thunk}, när en \emph{HeapPtr} dereferensera kommer \emph{Thunk} att tvingas till en WHNF och \emph{HeapPtr} uppdateras att peka till denna. Eftersom att tvingandet av en \emph{Thunk} kan resultera i en ny \emph{Thunk} så tvingas thunken i en loop till dess att resultatet är en WHNF. 324 | 325 | \begin{lstlisting} 326 | this.dereference = function() { 327 | if (this.weakHead == undefined) { 328 | // We'll drive the execution here instead of recursing 329 | // in the force method 330 | var continuation = this.thunk; 331 | while (continuation instanceof interpreter.Thunk) { 332 | continuation = continuation.force(); 333 | } 334 | this.weakHead = continuation; 335 | this.thunk = null; 336 | } 337 | return this.weakHead; 338 | }; 339 | \end{lstlisting} 340 | En \emph{HeapPtr} är ett vanligt Javascript-objekt som innehåller funktionen \emph{dereference}. \emph{Dereference} körs när anroparen vill använda den WHNF som \emph{HeapPtr} pekar mot. Om \emph{HeapPtr} inte har blivit dereferenserad innan så kommer dess \emph{Thunk} att tvingas tills det att resultatet är en WHNF och \emph{HeapPtr}-objektet uppdateras till att peka mot denna. 341 | 342 | \subsubsection{Env} 343 | Env är en stack av \emph{Javascript hashes}. Hasharna består av en bindning mellan en \emph{Identifier} och antingen en \emph{HeapPtr} eller ett (\emph{Pattern}, \emph{HeapPtr}) par. Den andra bindningstypen är resultatet av en \emph{VariableDeclaration} där interpretatorn måste utföra en \emph{pattern match} för att avgöra vilken \emph{HeapPtr} som hör till vilken \emph{Identifier}. När interpretatorn läser ut en \emph{Identifier} som är bunden enligt den andra typen av bindning, kommer en \emph{pattern match} att utföras och alla \emph{identifiers} i det \emph{pattern} som matchas kommer att bindas om som den första typen alternativt en \emph{pattern match fail} inträffar och ett \emph{exception} genereras. 344 | 345 | \begin{lstlisting} 346 | Env { 347 | a => ([a, b, 3], [1,2,3]) 348 | b => ([a, b, 3], [1,2,3]) 349 | } 350 | Env.find(a) 351 | Env { 352 | a => 1 353 | b => 2 354 | } 355 | \end{lstlisting} 356 | 357 | \subsection{Abstrakt syntaxträd och evaluering} 358 | Vår AST representeras av javascriptobjekt men dess strukturella uppbyggnad ges här av Haskells datadefinitioner. 359 | 360 | \subsubsection{Declaration} 361 | Den första datatypen av intresse är \emph{Declaration}. En \emph{Declaration} representerar en namnbindning antigen i en moduls globala definitionsområde eller i en \emph{let}- eller \emph{where}-bindning. 362 | 363 | \begin{lstlisting} 364 | data Declaration 365 | = Variable Pattern Expression 366 | | Data Identifier [Constructor] 367 | | Function Identifier [Pattern] 368 | (Either [(Guard, Expression)] Expression) 369 | \end{lstlisting} 370 | 371 | \emph{Variable} är en bindning av typen \emph{p = e} där p är en \emph{Pattern} och e en \emph{Expression}. En \emph{Variable} kan därför binda flera olika symboler på en gång. Till exempel \emph{(1:a:b:[]) = [1,2,3]} binder variabeln a och b. 372 | 373 | \emph{Function} är en representation av Haskells sockrade funktionsdefinitioner. Det är möjligt att avsockra dessa till en \emph{VariableBinding} med hjälp av \emph{Lambda} och \emph{Case} men vi har valt att införa en speciell representation av funktionsdefinitioner. Till skillnad från traditionella kompilatorer så avsockras \emph{Function} aldrig utan håller samma form även under interpreteringen. Tanken bakom detta är att det skall vara lättare att se sammanhanget mellan datan under körning och källkoden vilket gör det lättare att knyta samman den statiska källkoden med den dynamiska körningsdatan. 374 | 375 | En \emph{Data} definierar en algebraisk datatyp med konstruktorerna definierade som nedan 376 | \begin{lstlisting} 377 | data Constructor = Constructor Identifier Int 378 | \end{lstlisting} 379 | Värt att notera här är avsaknaden av typvariabler och typargument, detta kommer att åtgärdas när typcheckaren är integrerad. 380 | 381 | Under interpreteringen av en \emph{Declaration} binds de namn (Identifiers) som definieras av \emph{Declaration} till \emph{Env}-objektet. Vilken typ av bindning som används beror på typen av \emph{Declaration}. En \emph{Variable} ger en \emph{Identifier => (Pattern, Expression)}-bindning medan en \emph{Function} eller \emph{Data} ger en \emph{Identifier => Expression}-bindning. 382 | 383 | \subsubsection{Expression} 384 | En \emph{Expression} är representationen av ett haskelluttryck. 385 | 386 | \begin{lstlisting} 387 | data Expression 388 | = Constant Value 389 | | Lambda Pattern Expression 390 | | Let [Declaration] Expression 391 | | Case Expression [(Pattern, Expression)] 392 | | VariableLookup Identifier 393 | | Do [DoNotation] 394 | | List [Expression] 395 | | ListComprehension Expression [ListNotation] 396 | | ArithmeticSequence Expression (Maybe Expression) 397 | (Maybe Expression) 398 | | Primitive JavascriptFunction 399 | \end{lstlisting} 400 | 401 | När en \emph{Expression} evalueras under en \emph{Env} är resultatet antingen en \emph{Thunk} (continuation, closure) eller en WHNF. Evaluering av en \emph{Constant} resulterar antigen i en primitiv eller en avsockring av konstanten. I interpretatorn så är konstanta nummer, till exempel 1, egentligen en algebraisk datatyp (I\# 1\#) och denna avsockring sker först under körning. När en \emph{Lambda} evalueras returneras en \emph{LambdaAbstraction} med evalueringens \emph{Env} bunden. En \emph{Let} evalueras genom att binda dess \emph{Declarations} till \emph{Env}-objektet och returnera en \emph{Closure} över det nya \emph{Env}-objektet och \emph{Let}-uttryckets \emph{Expression}. En \emph{Case} evalueras genom att den första \emph{Expression} i tuppellistan vars \emph{Pattern} matchar evalueras under den \emph{Env} som resulterar från \emph{pattern matchen}. 402 | \begin{lstlisting} 403 | this.eval = function(env) { 404 | var expr = new interpreter.HeapPtr( 405 | new interpreter.Closure(env, this.expr)); 406 | for (var i in this.cases) { 407 | var newEnv = env.derive(); 408 | if (this.cases[i][0].match(newEnv, expr)) { 409 | return this.cases[i][1].eval(newEnv); 410 | } 411 | } 412 | throw new Error("No matching clause"); 413 | }; 414 | \end{lstlisting} 415 | Detta är \emph{eval}-metoden för ett \emph{Case}-uttryck. Här syns hur en ny \emph{Env} används vid varje alternativ i \emph{Case}-satsen och hur granskaren delas mellan de olika alternativen. 416 | 417 | Att evaluera en \emph{VariableLookup} under en \emph{Env} är det samma som att leta upp en \emph{Identifier} i \emph{Env}-objektet. 418 | 419 | \emph{Do}, \emph{List} och \emph{ArithmeticSequence} är inte evaluerade direkt utan de är avsockrade och sedan evalueras det avsockrade uttrycket. Avsockringsreglerna ges i Haskell 98 standarden \citep{haskell98chap3}. 420 | 421 | En \emph{Primitive} är en wrapper runt en Javascript-funktion. Att evaluera en \emph{Primitive} är det samma som att evaluera funktionen med \emph{Env} som argument. 422 | 423 | \subsubsection{Pattern} 424 | Vi har implementerat fem stycken olika \emph{pattern matches} från Haskell. 425 | \begin{lstlisting} 426 | data Pattern = Constructor Identifier [Pattern] 427 | | VariableBinding Identifier 428 | | Combined Identifier Pattern 429 | | ConstantPattern Value 430 | | Wildcard 431 | \end{lstlisting} 432 | Under en \emph{pattern match} händer två saker. Dels så kontrolleras att uttrycket som matchas verkligen stämmer överens med dess pattern, dels så binds de variabler som definieras i \emph{Pattern} till \emph{Env}. 433 | 434 | En \emph{VariableBinding} matchar alla uttryck och binder uttrycket till \emph{Identifier}. En \emph{Constructor} matchar de uttryck vars WHNF är en \emph{Data} med samma \emph{Identifier} (samt samma typ, detta tvingas dock av typcheckaren) och alla sub-patterns matchar \emph{Data}-argumenten. \emph{Combined} är en sammanslagning av en \emph{VariableBinding} och en annan \emph{Pattern}, i källkoden så har de formen \emph{v@p}. \emph{Combined} matchar de uttryck som matchar dess \emph{Pattern} och binder ett matchat uttryck till \emph{Identifier}. En \emph{ConstantPattern} matchar de uttryck som är exakt lika med värdet. Till sist så matchar en \emph{Wildcard} alla uttryck. 435 | 436 | \subsection{HIJi} 437 | 438 | HIJi är ett program som ger användaren ett GHCi-liknande användargränssnitt till haskelltolken i en webbläsare. 439 | HIJi tar indata genom att funktioner skrivs in i HIJi som sedan tolkas av parsern och i sin tur bygger upp det abstrakta syntaxträdet. Därefter evalueras uttrycket av interpretatorn och resultatet blir synligt i HIJi. 440 | 441 | HIJi har stöd för att ladda externa moduler. Det görs genom att skriva :l \emph{namn-på-modul}. Användaren får då tillgång till alla de funktioner som är skrivna i den modulen. Modulerna måste vara placerade på servern som HIJi laddades ifrån. Modulerna kan ej laddas direkt från användarens hårddisk på grund av att Javascript av säkerhetsskäl ej har läs- och skrivrättigheter av användarens filsystem. HIJi har även en förladdad modul, Prelude, som innehåller en delmängd av de funktioner som GHCi har i sin motsvarighet. 442 | 443 | Den indata som användaren skriver till HIJi sparas i ett objekt för att hantera historiken. För att bläddra i historiken används piltangenterna Upp och Ner. Hela historik-objektet sparas även i en kaka som ett JSON-objekt. Av detta skäl är det möjligt att få tillgång till historiken när en ny session av webbläsaren startas. 444 | 445 | % 446 | \begin{figure}[H] 447 | \begin{center} 448 | \includegraphics[width=1\textwidth]{hiji_screen3.png} 449 | \caption{HIJi användargränssnitt} 450 | \label{fig:hiji} % Labels must come after caption! 451 | \end{center} 452 | \end{figure} 453 | 454 | Figur \ref{fig:hiji} visar hur HIJi ser ut. De första raderna visar, precis som i GHCi, vilka moduler som för närvarande är laddade. I det här exemplet är den förladdade modulen Prelude laddad. Därefter följer en kommandotolk där användaren fritt kan skriva in egna funktioner. Figuren visar ett antal olika exempel på hur HIJi kan användas. 455 | 456 | HIJi är skapat för att likna GHCi i så stor utsträckning som möjligt. 457 | Genom att efterlikna GHCi kommer användare känna igen sig när de tar steget från HIJi till GHCi. Det blir för dem ett naturligt steg och kortar inlärningströskeln. Även för haskellprogrammerare som är vana användare av GHCi blir det lättare att använda sig av HIJi, de behöver inte fundera hur verktyget ska användas. 458 | 459 | \subsection{Prelude} 460 | I våra avgränsningar angav vi vilka delar av haskellspecifikationen vi skulle fokusera på. De delar som vi har fullt stöd för är lambda-funktioner, namngivna funktioner, algebraiska datatyper, pattern matching och guards. 461 | I HIJis förladdade modul, Prelude, har vi definierad upp ett antal funktioner som visar på att vi har implementerat dessa egenskaper från specifikationen. 462 | Alla funktioner som är definierade i Prelude är namngivna funktioner. Ett exempel på en namngiven funktion är \emph{id}. 463 | Algebraiska datatyper stöds och i Prelude har vi definierat upp datatypen Bool och ett par funktioner, däribland (||), som använder sig utav den här datatypen. Funktionen (||) använder sig av pattern matching. Resultatet av \emph{case x of} kommer matcha antingen mot \emph{True} eller \emph{False}. 464 | Funktionen \emph{filter} från Prelude är ett exempel på att guards fungerar. Filter är också ett exempel på att pattern matching och rekursiva funktioner är implementerade. 465 | 466 | \begin{lstlisting} 467 | -- Utdrag ur Prelude.hs 468 | 469 | -- en namngiven funktion 470 | id x = x 471 | 472 | -- datatypen Bool 473 | data Bool = True | False 474 | 475 | -- exempel av pattern match 476 | (||) x y = case x of 477 | True -> True 478 | False -> y 479 | 480 | -- exempel av pattern match, 481 | -- guards och rekursiva funktioner 482 | filter _ [] = [] 483 | filter f (x:xs ) | f x = x : filter f xs 484 | | otherwise = filter f xs 485 | \end{lstlisting} 486 | 487 | --------------------------------------------------------------------------------