From 0775dea2bc79cb1b5ee56f74f8076fc30a394127 Mon Sep 17 00:00:00 2001 From: Akshay Date: Thu, 8 Oct 2020 10:53:41 +0530 Subject: init --- CHANGELOG.md | 3 ++ Setup.hs | 2 ++ bin/Main.hs | 26 ++++++++++++++ cabal.project.local | 1 + default.nix | 10 ++++++ lisk.cabal | 55 ++++++++++++++++++++++++++++ release.nix | 4 +++ shell.nix | 40 +++++++++++++++++++++ src/Evaluator.hs | 22 ++++++++++++ src/Operators.hs | 22 ++++++++++++ src/Parser.hs | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/Main.hs | 8 +++++ tests/Properties.hs | 15 ++++++++ 13 files changed, 309 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 Setup.hs create mode 100644 bin/Main.hs create mode 100644 cabal.project.local create mode 100644 default.nix create mode 100644 lisk.cabal create mode 100644 release.nix create mode 100644 shell.nix create mode 100644 src/Evaluator.hs create mode 100644 src/Operators.hs create mode 100644 src/Parser.hs create mode 100644 tests/Main.hs create mode 100644 tests/Properties.hs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0b5e45b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# Revision history for lisk + + diff --git a/Setup.hs b/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +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 @@ +module Main where + +import Evaluator (eval) +import Parser (Expr (..), parseLispValue) +import System.Console.Readline +import Text.ParserCombinators.Parsec + +readExpr :: String -> Expr +readExpr inp = + case parse parseLispValue "(unknown)" inp of + Left err -> StringLiteral $ show err + Right val -> val + +repl :: IO () +repl = do + inp <- readline "(lisk)> " + case inp of + Nothing -> return () + Just ",q" -> return () + Just line -> do + addHistory line + print . eval . readExpr $ line + repl + +main :: IO () +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 @@ +{ mkDerivation, base, parsec, readline, stdenv }: +mkDerivation { + pname = "lisk"; + version = "0.1.0.0"; + src = ./.; + isLibrary = false; + isExecutable = true; + executableHaskellDepends = [ base parsec readline ]; + license = stdenv.lib.licenses.gpl3; +} diff --git a/lisk.cabal b/lisk.cabal new file mode 100644 index 0000000..627815d --- /dev/null +++ b/lisk.cabal @@ -0,0 +1,55 @@ +cabal-version: >=1.10 +-- Initial package description 'lisk.cabal' generated by 'cabal init'. For +-- further documentation, see http://haskell.org/cabal/users-guide/ + +name: lisk +version: 0.1.0.0 +synopsis: a lisp interpreter +description: an educational lisp interpreter written in haskell by DSCRV +-- bug-reports: +license: GPL-3 +-- license-file: LICENSE +author: Akshay +maintainer: nerdy@peppe.rs +-- copyright: +-- category: +build-type: Simple +extra-source-files: CHANGELOG.md + +library + default-language: Haskell2010 + build-depends: + base >=4.12 && <4.13, + parsec == 3.*, + mtl >= 2.1 + hs-source-dirs: src + exposed-modules: + Parser, + Evaluator, + Operators + +executable lisk + default-language: Haskell2010 + main-is: Main.hs + build-depends: + base >=4.12 && <4.13, + parsec == 3.*, + readline >= 1.0, + lisk + hs-source-dirs: bin + +test-suite properties + default-language: Haskell2010 + type: exitcode-stdio-1.0 + main-is: Main.hs + hs-source-dirs: tests + build-depends: + base >=4.12 && <4.13, + parsec == 3.*, + QuickCheck >= 2.4 && < 3, + test-framework >= 0.6 && < 0.9, + test-framework-hunit >= 0.3 && < 0.5, + test-framework-quickcheck2 >= 0.2 && < 0.4, + test-framework-th >= 0.2 && < 0.4, + lisk + 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 @@ +let + pkgs = import {}; +in + 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 @@ +let + + all-hies = fetchTarball { + url = "https://github.com/infinisil/all-hies/tarball/534ac517b386821b787d1edbd855b9966d0c0775"; + sha256 = "0bw1llpwxbh1dnrnbxkj2l0j58s523hjivszf827c3az5i4py1i2"; + }; + + pkgs = import { + # Pass no config for purity + config = {}; + overlays = [ + (import all-hies {}).overlay + ]; + }; + + inherit (pkgs) haskellPackages; + + haskellDeps = ps: with ps; [ + base + lens + parsec + mtl + readline + ]; + + ghc = haskellPackages.ghcWithPackages haskellDeps; + + externalPackages = [ + ghc + pkgs.gdb + pkgs.cabal2nix + haskellPackages.cabal-install + haskellPackages.hie + haskellPackages.hoogle + ]; +in +pkgs.stdenv.mkDerivation { + name = "env"; + buildInputs = externalPackages; +} 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 @@ +module Evaluator ( + eval + ) where + +import Operators +import Parser +import Text.ParserCombinators.Parsec + +apply :: String -> [Expr] -> Expr +apply fn args = + case lookup fn primitives of + Just f -> f args + _ -> BoolLiteral False -- TODO: error out instead + +eval :: Expr -> Expr +eval v@(StringLiteral s) = v +eval v@(IntLiteral i) = v +eval v@(BoolLiteral b) = v +-- handle quotes as literals +eval (List[Id "quote", val]) = val +eval (List (Id fn : args)) = apply fn $ map eval args + 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 @@ +module Operators ( + primitives + ) where + +import Parser + +primitives :: [(String, [Expr] -> Expr)] +primitives = + [ + ("+", arithmetic (+)) + , ("-", arithmetic (-)) + , ("*", arithmetic (*)) + , ("/", arithmetic div) + ] + +arithmetic :: (Integer -> Integer -> Integer) -> [Expr] -> Expr +arithmetic op args = IntLiteral $ foldl1 op $ map unwrapNum args + +unwrapNum :: Expr -> Integer +unwrapNum (IntLiteral n) = n +unwrapNum _ = undefined + 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 @@ +module Parser ( parseLispValue + , Expr(..) + , parseString + , parseInt + , parseFloat + , parseAtom + , parseList + , parseQuote + , parseDottedList + ) where + +import Control.Applicative ((<$>)) +import Control.Monad (liftM) +import Text.ParserCombinators.Parsec + + +type Ident = String + +data Expr = List [Expr] + | DottedList [Expr] Expr + | StringLiteral String + | IntLiteral Integer + | FloatLiteral Double + | BoolLiteral Bool + | Id Ident + deriving (Eq) + +parseString :: Parser Expr +parseString = do + char '"' + innards <- many (noneOf "\"") + char '"' + return (StringLiteral innards) + +parseInt :: Parser Expr +parseInt = IntLiteral . read <$> many1 digit + +parseFloat :: Parser Expr +parseFloat = do + characteristic <- many digit + char '.' + mantissa <- many1 digit + return $ (FloatLiteral . read) $ characteristic ++ "." ++ mantissa + +symbol :: Parser Char +symbol = oneOf "!#$%&|*+:/-=@^_~" + +parseAtom :: Parser Expr +parseAtom = do + first <- letter <|> symbol + rest <- many (letter <|> symbol <|> digit) + let atom = first:rest + return $ case atom of + "#t" -> BoolLiteral True + "#f" -> BoolLiteral False + _ -> Id atom + +whiteSpace :: Parser () +whiteSpace = skipMany1 space + +parseList :: Parser Expr +parseList = List <$> sepBy parseLispValue whiteSpace + +parseDottedList :: Parser Expr +parseDottedList = do + head <- endBy parseLispValue whiteSpace + char '.' + whiteSpace + DottedList head <$> parseLispValue + +parseQuote :: Parser Expr +parseQuote = do + char '\'' + x <- parseLispValue + return $ List [Id "quote", x] + + +parseLispValue :: Parser Expr +parseLispValue = + try parseAtom + <|> parseString + <|> parseInt + <|> parseQuote + -- TODO: figure out a way to have floats and dotted lists + -- <|> parseFloat + <|> do + char '(' + x <- try parseList <|> parseDottedList + char ')' + return x + "expected lisp value!" + +instance Show Expr where + show (DottedList xs x) = "(" ++ unwords (map show xs) ++ " . " ++ show x ++ ")" + show (List xs) = "(" ++ unwords (map show xs) ++ ")" + show (StringLiteral s) = "\"" ++ s ++ "\"" + show (IntLiteral n) = show n + show (FloatLiteral n) = show n + show (BoolLiteral True) = "#t" + show (BoolLiteral False) = "#f" + 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 @@ +module Main where + +import Properties +import Test.Framework.Providers.QuickCheck2 +import Test.Framework.Runners.Console (defaultMain) +import Test.QuickCheck + +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 @@ +{-# LANGUAGE TemplateHaskell #-} +module Properties where + +import Parser (Expr (..), + parseLispValue, + parseQuote) + +import Test.Framework.Providers.QuickCheck2 +import Test.Framework.TH +import Test.QuickCheck +import Text.ParserCombinators.Parsec + +-- some tests would go here hopefully + +tests = $testGroupGenerator -- cgit v1.2.3