diff options
author | Akshay <[email protected]> | 2020-10-08 06:23:41 +0100 |
---|---|---|
committer | Akshay <[email protected]> | 2020-10-08 06:23:41 +0100 |
commit | 0775dea2bc79cb1b5ee56f74f8076fc30a394127 (patch) | |
tree | 749e3d9534c3bea145a31cfe332c758720f71d3c | |
parent | 4e60f9745e09959d0ce82810998d683372c0a1d4 (diff) |
init
-rw-r--r-- | CHANGELOG.md | 3 | ||||
-rw-r--r-- | Setup.hs | 2 | ||||
-rw-r--r-- | bin/Main.hs | 26 | ||||
-rw-r--r-- | cabal.project.local | 1 | ||||
-rw-r--r-- | default.nix | 10 | ||||
-rw-r--r-- | lisk.cabal | 55 | ||||
-rw-r--r-- | release.nix | 4 | ||||
-rw-r--r-- | shell.nix | 40 | ||||
-rw-r--r-- | src/Evaluator.hs | 22 | ||||
-rw-r--r-- | src/Operators.hs | 22 | ||||
-rw-r--r-- | src/Parser.hs | 101 | ||||
-rw-r--r-- | tests/Main.hs | 8 | ||||
-rw-r--r-- | tests/Properties.hs | 15 |
13 files changed, 309 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0b5e45b --- /dev/null +++ b/CHANGELOG.md | |||
@@ -0,0 +1,3 @@ | |||
1 | # Revision history for lisk | ||
2 | |||
3 | <!-- populate this as we progress --> | ||
diff --git a/Setup.hs b/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/Setup.hs | |||
@@ -0,0 +1,2 @@ | |||
1 | import Distribution.Simple | ||
2 | main = defaultMain | ||
diff --git a/bin/Main.hs b/bin/Main.hs new file mode 100644 index 0000000..2942566 --- /dev/null +++ b/bin/Main.hs | |||
@@ -0,0 +1,26 @@ | |||
1 | module Main where | ||
2 | |||
3 | import Evaluator (eval) | ||
4 | import Parser (Expr (..), parseLispValue) | ||
5 | import System.Console.Readline | ||
6 | import Text.ParserCombinators.Parsec | ||
7 | |||
8 | readExpr :: String -> Expr | ||
9 | readExpr inp = | ||
10 | case parse parseLispValue "(unknown)" inp of | ||
11 | Left err -> StringLiteral $ show err | ||
12 | Right val -> val | ||
13 | |||
14 | repl :: IO () | ||
15 | repl = do | ||
16 | inp <- readline "(lisk)> " | ||
17 | case inp of | ||
18 | Nothing -> return () | ||
19 | Just ",q" -> return () | ||
20 | Just line -> do | ||
21 | addHistory line | ||
22 | print . eval . readExpr $ line | ||
23 | repl | ||
24 | |||
25 | main :: IO () | ||
26 | main = repl | ||
diff --git a/cabal.project.local b/cabal.project.local new file mode 100644 index 0000000..8909668 --- /dev/null +++ b/cabal.project.local | |||
@@ -0,0 +1 @@ | |||
tests: True | |||
diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..1890d35 --- /dev/null +++ b/default.nix | |||
@@ -0,0 +1,10 @@ | |||
1 | { mkDerivation, base, parsec, readline, stdenv }: | ||
2 | mkDerivation { | ||
3 | pname = "lisk"; | ||
4 | version = "0.1.0.0"; | ||
5 | src = ./.; | ||
6 | isLibrary = false; | ||
7 | isExecutable = true; | ||
8 | executableHaskellDepends = [ base parsec readline ]; | ||
9 | license = stdenv.lib.licenses.gpl3; | ||
10 | } | ||
diff --git a/lisk.cabal b/lisk.cabal new file mode 100644 index 0000000..627815d --- /dev/null +++ b/lisk.cabal | |||
@@ -0,0 +1,55 @@ | |||
1 | cabal-version: >=1.10 | ||
2 | -- Initial package description 'lisk.cabal' generated by 'cabal init'. For | ||
3 | -- further documentation, see http://haskell.org/cabal/users-guide/ | ||
4 | |||
5 | name: lisk | ||
6 | version: 0.1.0.0 | ||
7 | synopsis: a lisp interpreter | ||
8 | description: an educational lisp interpreter written in haskell by DSCRV | ||
9 | -- bug-reports: | ||
10 | license: GPL-3 | ||
11 | -- license-file: LICENSE | ||
12 | author: Akshay | ||
13 | maintainer: [email protected] | ||
14 | -- copyright: | ||
15 | -- category: | ||
16 | build-type: Simple | ||
17 | extra-source-files: CHANGELOG.md | ||
18 | |||
19 | library | ||
20 | default-language: Haskell2010 | ||
21 | build-depends: | ||
22 | base >=4.12 && <4.13, | ||
23 | parsec == 3.*, | ||
24 | mtl >= 2.1 | ||
25 | hs-source-dirs: src | ||
26 | exposed-modules: | ||
27 | Parser, | ||
28 | Evaluator, | ||
29 | Operators | ||
30 | |||
31 | executable lisk | ||
32 | default-language: Haskell2010 | ||
33 | main-is: Main.hs | ||
34 | build-depends: | ||
35 | base >=4.12 && <4.13, | ||
36 | parsec == 3.*, | ||
37 | readline >= 1.0, | ||
38 | lisk | ||
39 | hs-source-dirs: bin | ||
40 | |||
41 | test-suite properties | ||
42 | default-language: Haskell2010 | ||
43 | type: exitcode-stdio-1.0 | ||
44 | main-is: Main.hs | ||
45 | hs-source-dirs: tests | ||
46 | build-depends: | ||
47 | base >=4.12 && <4.13, | ||
48 | parsec == 3.*, | ||
49 | QuickCheck >= 2.4 && < 3, | ||
50 | test-framework >= 0.6 && < 0.9, | ||
51 | test-framework-hunit >= 0.3 && < 0.5, | ||
52 | test-framework-quickcheck2 >= 0.2 && < 0.4, | ||
53 | test-framework-th >= 0.2 && < 0.4, | ||
54 | lisk | ||
55 | other-modules: Properties | ||
diff --git a/release.nix b/release.nix new file mode 100644 index 0000000..8180314 --- /dev/null +++ b/release.nix | |||
@@ -0,0 +1,4 @@ | |||
1 | let | ||
2 | pkgs = import <nixpkgs> {}; | ||
3 | in | ||
4 | pkgs.haskellPackages.callPackage ./default.nix {} | ||
diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..b1a597f --- /dev/null +++ b/shell.nix | |||
@@ -0,0 +1,40 @@ | |||
1 | let | ||
2 | |||
3 | all-hies = fetchTarball { | ||
4 | url = "https://github.com/infinisil/all-hies/tarball/534ac517b386821b787d1edbd855b9966d0c0775"; | ||
5 | sha256 = "0bw1llpwxbh1dnrnbxkj2l0j58s523hjivszf827c3az5i4py1i2"; | ||
6 | }; | ||
7 | |||
8 | pkgs = import <nixpkgs> { | ||
9 | # Pass no config for purity | ||
10 | config = {}; | ||
11 | overlays = [ | ||
12 | (import all-hies {}).overlay | ||
13 | ]; | ||
14 | }; | ||
15 | |||
16 | inherit (pkgs) haskellPackages; | ||
17 | |||
18 | haskellDeps = ps: with ps; [ | ||
19 | base | ||
20 | lens | ||
21 | parsec | ||
22 | mtl | ||
23 | readline | ||
24 | ]; | ||
25 | |||
26 | ghc = haskellPackages.ghcWithPackages haskellDeps; | ||
27 | |||
28 | externalPackages = [ | ||
29 | ghc | ||
30 | pkgs.gdb | ||
31 | pkgs.cabal2nix | ||
32 | haskellPackages.cabal-install | ||
33 | haskellPackages.hie | ||
34 | haskellPackages.hoogle | ||
35 | ]; | ||
36 | in | ||
37 | pkgs.stdenv.mkDerivation { | ||
38 | name = "env"; | ||
39 | buildInputs = externalPackages; | ||
40 | } | ||
diff --git a/src/Evaluator.hs b/src/Evaluator.hs new file mode 100644 index 0000000..f264ee0 --- /dev/null +++ b/src/Evaluator.hs | |||
@@ -0,0 +1,22 @@ | |||
1 | module Evaluator ( | ||
2 | eval | ||
3 | ) where | ||
4 | |||
5 | import Operators | ||
6 | import Parser | ||
7 | import Text.ParserCombinators.Parsec | ||
8 | |||
9 | apply :: String -> [Expr] -> Expr | ||
10 | apply fn args = | ||
11 | case lookup fn primitives of | ||
12 | Just f -> f args | ||
13 | _ -> BoolLiteral False -- TODO: error out instead | ||
14 | |||
15 | eval :: Expr -> Expr | ||
16 | eval v@(StringLiteral s) = v | ||
17 | eval v@(IntLiteral i) = v | ||
18 | eval v@(BoolLiteral b) = v | ||
19 | -- handle quotes as literals | ||
20 | eval (List[Id "quote", val]) = val | ||
21 | eval (List (Id fn : args)) = apply fn $ map eval args | ||
22 | |||
diff --git a/src/Operators.hs b/src/Operators.hs new file mode 100644 index 0000000..e57f885 --- /dev/null +++ b/src/Operators.hs | |||
@@ -0,0 +1,22 @@ | |||
1 | module Operators ( | ||
2 | primitives | ||
3 | ) where | ||
4 | |||
5 | import Parser | ||
6 | |||
7 | primitives :: [(String, [Expr] -> Expr)] | ||
8 | primitives = | ||
9 | [ | ||
10 | ("+", arithmetic (+)) | ||
11 | , ("-", arithmetic (-)) | ||
12 | , ("*", arithmetic (*)) | ||
13 | , ("/", arithmetic div) | ||
14 | ] | ||
15 | |||
16 | arithmetic :: (Integer -> Integer -> Integer) -> [Expr] -> Expr | ||
17 | arithmetic op args = IntLiteral $ foldl1 op $ map unwrapNum args | ||
18 | |||
19 | unwrapNum :: Expr -> Integer | ||
20 | unwrapNum (IntLiteral n) = n | ||
21 | unwrapNum _ = undefined | ||
22 | |||
diff --git a/src/Parser.hs b/src/Parser.hs new file mode 100644 index 0000000..dcbfdb1 --- /dev/null +++ b/src/Parser.hs | |||
@@ -0,0 +1,101 @@ | |||
1 | module Parser ( parseLispValue | ||
2 | , Expr(..) | ||
3 | , parseString | ||
4 | , parseInt | ||
5 | , parseFloat | ||
6 | , parseAtom | ||
7 | , parseList | ||
8 | , parseQuote | ||
9 | , parseDottedList | ||
10 | ) where | ||
11 | |||
12 | import Control.Applicative ((<$>)) | ||
13 | import Control.Monad (liftM) | ||
14 | import Text.ParserCombinators.Parsec | ||
15 | |||
16 | |||
17 | type Ident = String | ||
18 | |||
19 | data Expr = List [Expr] | ||
20 | | DottedList [Expr] Expr | ||
21 | | StringLiteral String | ||
22 | | IntLiteral Integer | ||
23 | | FloatLiteral Double | ||
24 | | BoolLiteral Bool | ||
25 | | Id Ident | ||
26 | deriving (Eq) | ||
27 | |||
28 | parseString :: Parser Expr | ||
29 | parseString = do | ||
30 | char '"' | ||
31 | innards <- many (noneOf "\"") | ||
32 | char '"' | ||
33 | return (StringLiteral innards) | ||
34 | |||
35 | parseInt :: Parser Expr | ||
36 | parseInt = IntLiteral . read <$> many1 digit | ||
37 | |||
38 | parseFloat :: Parser Expr | ||
39 | parseFloat = do | ||
40 | characteristic <- many digit | ||
41 | char '.' | ||
42 | mantissa <- many1 digit | ||
43 | return $ (FloatLiteral . read) $ characteristic ++ "." ++ mantissa | ||
44 | |||
45 | symbol :: Parser Char | ||
46 | symbol = oneOf "!#$%&|*+:/-=<?>@^_~" | ||
47 | |||
48 | parseAtom :: Parser Expr | ||
49 | parseAtom = do | ||
50 | first <- letter <|> symbol | ||
51 | rest <- many (letter <|> symbol <|> digit) | ||
52 | let atom = first:rest | ||
53 | return $ case atom of | ||
54 | "#t" -> BoolLiteral True | ||
55 | "#f" -> BoolLiteral False | ||
56 | _ -> Id atom | ||
57 | |||
58 | whiteSpace :: Parser () | ||
59 | whiteSpace = skipMany1 space | ||
60 | |||
61 | parseList :: Parser Expr | ||
62 | parseList = List <$> sepBy parseLispValue whiteSpace | ||
63 | |||
64 | parseDottedList :: Parser Expr | ||
65 | parseDottedList = do | ||
66 | head <- endBy parseLispValue whiteSpace | ||
67 | char '.' | ||
68 | whiteSpace | ||
69 | DottedList head <$> parseLispValue | ||
70 | |||
71 | parseQuote :: Parser Expr | ||
72 | parseQuote = do | ||
73 | char '\'' | ||
74 | x <- parseLispValue | ||
75 | return $ List [Id "quote", x] | ||
76 | |||
77 | |||
78 | parseLispValue :: Parser Expr | ||
79 | parseLispValue = | ||
80 | try parseAtom | ||
81 | <|> parseString | ||
82 | <|> parseInt | ||
83 | <|> parseQuote | ||
84 | -- TODO: figure out a way to have floats and dotted lists | ||
85 | -- <|> parseFloat | ||
86 | <|> do | ||
87 | char '(' | ||
88 | x <- try parseList <|> parseDottedList | ||
89 | char ')' | ||
90 | return x | ||
91 | <?> "expected lisp value!" | ||
92 | |||
93 | instance Show Expr where | ||
94 | show (DottedList xs x) = "(" ++ unwords (map show xs) ++ " . " ++ show x ++ ")" | ||
95 | show (List xs) = "(" ++ unwords (map show xs) ++ ")" | ||
96 | show (StringLiteral s) = "\"" ++ s ++ "\"" | ||
97 | show (IntLiteral n) = show n | ||
98 | show (FloatLiteral n) = show n | ||
99 | show (BoolLiteral True) = "#t" | ||
100 | show (BoolLiteral False) = "#f" | ||
101 | show (Id i) = i | ||
diff --git a/tests/Main.hs b/tests/Main.hs new file mode 100644 index 0000000..a05ddb1 --- /dev/null +++ b/tests/Main.hs | |||
@@ -0,0 +1,8 @@ | |||
1 | module Main where | ||
2 | |||
3 | import Properties | ||
4 | import Test.Framework.Providers.QuickCheck2 | ||
5 | import Test.Framework.Runners.Console (defaultMain) | ||
6 | import Test.QuickCheck | ||
7 | |||
8 | main = defaultMain [ Properties.tests ] | ||
diff --git a/tests/Properties.hs b/tests/Properties.hs new file mode 100644 index 0000000..b42cd4b --- /dev/null +++ b/tests/Properties.hs | |||
@@ -0,0 +1,15 @@ | |||
1 | {-# LANGUAGE TemplateHaskell #-} | ||
2 | module Properties where | ||
3 | |||
4 | import Parser (Expr (..), | ||
5 | parseLispValue, | ||
6 | parseQuote) | ||
7 | |||
8 | import Test.Framework.Providers.QuickCheck2 | ||
9 | import Test.Framework.TH | ||
10 | import Test.QuickCheck | ||
11 | import Text.ParserCombinators.Parsec | ||
12 | |||
13 | -- some tests would go here hopefully | ||
14 | |||
15 | tests = $testGroupGenerator | ||