Cleanup: fix warnings, fix indentation, upgrade dependencies.
parent
59a55acdc6
commit
f73e78fcdb
|
@ -3,9 +3,6 @@ root = true
|
||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 2
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
[*.yml]
|
|
||||||
indent_size = 2
|
|
||||||
|
|
|
@ -10,8 +10,8 @@ jobs:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Install latest Haskell Stack
|
- name: Install Haskell toolchain
|
||||||
uses: actions/setup-haskell@v1.1
|
uses: haskell/actions/setup@v1
|
||||||
with:
|
with:
|
||||||
enable-stack: true
|
enable-stack: true
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
{-# LANGUAGE LambdaCase #-}
|
|
||||||
module Main where
|
module Main where
|
||||||
|
|
||||||
import Control.Monad (forever)
|
import Control.Monad (forever)
|
||||||
|
|
28
package.yaml
28
package.yaml
|
@ -15,18 +15,38 @@ extra-source-files:
|
||||||
default-extensions:
|
default-extensions:
|
||||||
- BlockArguments
|
- BlockArguments
|
||||||
- ImportQualifiedPost
|
- ImportQualifiedPost
|
||||||
|
- LambdaCase
|
||||||
- OverloadedStrings
|
- OverloadedStrings
|
||||||
|
- PatternSynonyms
|
||||||
- ViewPatterns
|
- ViewPatterns
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
- base >= 4.13 && < 5
|
- base >= 4.14 && < 5
|
||||||
- parsec >= 3.1 && < 4
|
- parsec >= 3.1 && < 4
|
||||||
- text >= 1.2 && < 2
|
- text >= 1.2 && < 2
|
||||||
- text-show >= 3.8 && < 4
|
- text-show >= 3.9 && < 4
|
||||||
- unordered-containers >= 0.2.10 && < 0.3
|
- unordered-containers >= 0.2.13 && < 0.3
|
||||||
|
|
||||||
library:
|
library:
|
||||||
source-dirs: src
|
source-dirs: src
|
||||||
|
ghc-options:
|
||||||
|
- -Weverything
|
||||||
|
# Useless Safe Haskell warnings
|
||||||
|
- -Wno-missing-safe-haskell-mode
|
||||||
|
- -Wno-unsafe
|
||||||
|
- -Wno-safe
|
||||||
|
# Other stupid warnings
|
||||||
|
- -Wno-implicit-prelude
|
||||||
|
- -Wno-missing-deriving-strategies
|
||||||
|
# Less stupid warnings, but I still don't care
|
||||||
|
- -Wno-unused-do-bind
|
||||||
|
- -Wno-all-missed-specialisations
|
||||||
|
# Explicit import lists are generally a good thing, but I don't want them
|
||||||
|
# in certain cases (e.g. importing my own modules, task-specific modules like the parser).
|
||||||
|
- -Wno-missing-import-lists
|
||||||
|
# I intentionally include unused top-level bindings
|
||||||
|
# as a way of documenting and explaining concepts.
|
||||||
|
- -Wno-unused-top-binds
|
||||||
|
|
||||||
executables:
|
executables:
|
||||||
jtm-lambda-calculus:
|
jtm-lambda-calculus:
|
||||||
|
@ -50,7 +70,7 @@ tests:
|
||||||
dependencies:
|
dependencies:
|
||||||
- jtm-lambda-calculus
|
- jtm-lambda-calculus
|
||||||
- generic-random >= 1.2 && < 2
|
- generic-random >= 1.2 && < 2
|
||||||
- QuickCheck >= 2.13 && < 3
|
- QuickCheck >= 2.14 && < 3
|
||||||
- tasty >= 1.2 && < 2
|
- tasty >= 1.2 && < 2
|
||||||
- tasty-hunit >= 0.10 && < 0.11
|
- tasty-hunit >= 0.10 && < 0.11
|
||||||
- tasty-quickcheck >= 0.10.1 && < 0.11
|
- tasty-quickcheck >= 0.10.1 && < 0.11
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module LambdaCalculus
|
module LambdaCalculus
|
||||||
( module LambdaCalculus.Expression
|
( module LambdaCalculus.Expression
|
||||||
, eagerEval, lazyEval
|
, eagerEval, lazyEval
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.List (elemIndex, find)
|
import Data.List (elemIndex, find)
|
||||||
import Data.Maybe (fromJust)
|
import Data.Maybe (fromJust)
|
||||||
|
@ -38,47 +38,51 @@ closed = HS.null . freeVariables
|
||||||
-- i.e. one can be converted to the other using only alpha-conversion.
|
-- i.e. one can be converted to the other using only alpha-conversion.
|
||||||
alphaEquivalent :: Expression -> Expression -> Bool
|
alphaEquivalent :: Expression -> Expression -> Bool
|
||||||
alphaEquivalent = alphaEquivalent' [] []
|
alphaEquivalent = alphaEquivalent' [] []
|
||||||
where alphaEquivalent' :: [Text] -> [Text] -> Expression -> Expression -> Bool
|
where
|
||||||
alphaEquivalent' ctx1 ctx2 (Variable v1) (Variable v2)
|
alphaEquivalent' :: [Text] -> [Text] -> Expression -> Expression -> Bool
|
||||||
-- Two variables are alpha-equivalent if they are bound in the same location.
|
alphaEquivalent' ctx1 ctx2 (Variable v1) (Variable v2)
|
||||||
= bindingSite ctx1 v1 == bindingSite ctx2 v2
|
-- Two variables are alpha-equivalent if they are bound in the same location.
|
||||||
alphaEquivalent' ctx1 ctx2 (Application ef1 ex1) (Application ef2 ex2)
|
= bindingSite ctx1 v1 == bindingSite ctx2 v2
|
||||||
-- Two applications are alpha-equivalent if their components are alpha-equivalent.
|
alphaEquivalent' ctx1 ctx2 (Application ef1 ex1) (Application ef2 ex2)
|
||||||
= alphaEquivalent' ctx1 ctx2 ef1 ef2
|
-- Two applications are alpha-equivalent if their components are alpha-equivalent.
|
||||||
&& alphaEquivalent' ctx1 ctx2 ex1 ex2
|
= alphaEquivalent' ctx1 ctx2 ef1 ef2
|
||||||
alphaEquivalent' ctx1 ctx2 (Abstraction v1 b1) (Abstraction v2 b2)
|
&& alphaEquivalent' ctx1 ctx2 ex1 ex2
|
||||||
-- Two abstractions are alpha-equivalent if their bodies are alpha-equivalent.
|
alphaEquivalent' ctx1 ctx2 (Abstraction v1 b1) (Abstraction v2 b2)
|
||||||
= alphaEquivalent' (v1 : ctx1) (v2 : ctx2) b1 b2
|
-- Two abstractions are alpha-equivalent if their bodies are alpha-equivalent.
|
||||||
|
= alphaEquivalent' (v1 : ctx1) (v2 : ctx2) b1 b2
|
||||||
|
alphaEquivalent' _ _ _ _ = False
|
||||||
|
|
||||||
-- | The binding site of a variable is either the index of its binder
|
-- | The binding site of a variable is either the index of its binder
|
||||||
-- or, if it is unbound, the name of the free variable.
|
-- or, if it is unbound, the name of the free variable.
|
||||||
bindingSite :: [Text] -> Text -> Either Text Int
|
bindingSite :: [Text] -> Text -> Either Text Int
|
||||||
bindingSite ctx var = maybeToRight var $ var `elemIndex` ctx
|
bindingSite ctx var = maybeToRight var $ var `elemIndex` ctx
|
||||||
where maybeToRight :: b -> Maybe a -> Either b a
|
where maybeToRight :: b -> Maybe a -> Either b a
|
||||||
maybeToRight default_ = maybe (Left default_) Right
|
maybeToRight default_ = maybe (Left default_) Right
|
||||||
|
|
||||||
-- | Substitution is the process of replacing all free occurrences of a variable in one expression with another expression.
|
-- | Substitution is the process of replacing all free occurrences of a variable in one expression with another expression.
|
||||||
substitute :: Text -> Expression -> Expression -> Expression
|
substitute :: Text -> Expression -> Expression -> Expression
|
||||||
substitute var1 value unmodified@(Variable var2)
|
substitute var1 value unmodified@(Variable var2)
|
||||||
| var1 == var2 = value
|
| var1 == var2 = value
|
||||||
| otherwise = unmodified
|
| otherwise = unmodified
|
||||||
substitute var value (Application ef ex)
|
substitute var value (Application ef ex)
|
||||||
= Application (substitute var value ef) (substitute var value ex)
|
= Application (substitute var value ef) (substitute var value ex)
|
||||||
substitute var1 value unmodified@(Abstraction var2 body)
|
substitute var1 value unmodified@(Abstraction var2 body)
|
||||||
| var1 == var2 = unmodified
|
| var1 == var2 = unmodified
|
||||||
| otherwise = Abstraction var2' $ substitute var1 value $ alphaConvert var2 var2' body
|
| otherwise = Abstraction var2' $ substitute var1 value $ alphaConvert var2 var2' body
|
||||||
where var2' :: Text
|
where
|
||||||
var2' = escapeName (freeVariables value) var2
|
var2' :: Text
|
||||||
|
var2' = escapeName (freeVariables value) var2
|
||||||
|
|
||||||
alphaConvert :: Text -> Text -> Expression -> Expression
|
alphaConvert :: Text -> Text -> Expression -> Expression
|
||||||
alphaConvert oldName newName expr = substitute oldName (Variable newName) expr
|
alphaConvert oldName newName expr = substitute oldName (Variable newName) expr
|
||||||
-- | Generate a new name which isn't present in the set, based on the old name.
|
-- | Generate a new name which isn't present in the set, based on the old name.
|
||||||
escapeName :: HashSet Text -> Text -> Text
|
escapeName :: HashSet Text -> Text -> Text
|
||||||
escapeName env name = fromJust $ find (not . free) names
|
escapeName env name = fromJust $ find (not . free) names
|
||||||
where names :: [Text]
|
where names :: [Text]
|
||||||
names = name : map (`T.snoc` '\'') names
|
names = name : map (`T.snoc` '\'') names
|
||||||
free :: Text -> Bool
|
|
||||||
free = (`HS.member` env)
|
free :: Text -> Bool
|
||||||
|
free = (`HS.member` env)
|
||||||
|
|
||||||
-- | Returns True if the top-level expression is reducible by beta-reduction.
|
-- | Returns True if the top-level expression is reducible by beta-reduction.
|
||||||
betaRedex :: Expression -> Bool
|
betaRedex :: Expression -> Bool
|
||||||
|
@ -110,22 +114,25 @@ whnf (Application (Abstraction _ _) _) = False
|
||||||
whnf (Abstraction var1 (Application fe (Variable var2)))
|
whnf (Abstraction var1 (Application fe (Variable var2)))
|
||||||
= var1 /= var2 || var1 `freeIn` fe
|
= var1 /= var2 || var1 `freeIn` fe
|
||||||
whnf (Application ef _) = whnf ef
|
whnf (Application ef _) = whnf ef
|
||||||
|
whnf _ = True
|
||||||
|
|
||||||
eval :: (Expression -> Expression) -> Expression -> Expression
|
eval :: (Expression -> Expression) -> Expression -> Expression
|
||||||
eval strategy = eval'
|
eval strategy = eval'
|
||||||
where eval' :: Expression -> Expression
|
where
|
||||||
eval' (Application ef ex) =
|
eval' :: Expression -> Expression
|
||||||
case ef' of
|
eval' (Application ef ex) =
|
||||||
-- Beta-reduction
|
case ef' of
|
||||||
Abstraction var body -> eval' $ substitute var ex' body
|
-- Beta-reduction
|
||||||
_ -> Application ef' ex'
|
Abstraction var body -> eval' $ substitute var ex' body
|
||||||
where ef' = eval' ef
|
_ -> Application ef' ex'
|
||||||
ex' = strategy ex
|
where
|
||||||
eval' unmodified@(Abstraction var1 (Application ef (Variable var2)))
|
ef' = eval' ef
|
||||||
-- Eta-reduction
|
ex' = strategy ex
|
||||||
| var1 == var2 && not (var1 `freeIn` ef) = eval' ef
|
eval' unmodified@(Abstraction var1 (Application ef (Variable var2)))
|
||||||
| otherwise = unmodified
|
-- Eta-reduction
|
||||||
eval' x = x
|
| var1 == var2 && not (var1 `freeIn` ef) = eval' ef
|
||||||
|
| otherwise = unmodified
|
||||||
|
eval' x = x
|
||||||
|
|
||||||
-- | Reduce an expression to normal form.
|
-- | Reduce an expression to normal form.
|
||||||
eagerEval :: Expression -> Expression
|
eagerEval :: Expression -> Expression
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
module LambdaCalculus.Expression where
|
module LambdaCalculus.Expression
|
||||||
|
( Expression (Variable, Application, Abstraction)
|
||||||
|
, ast2expr, expr2ast
|
||||||
|
, pattern Lets, pattern Abstractions, pattern Applications
|
||||||
|
, viewLet, viewAbstraction, viewApplication
|
||||||
|
, basicShow
|
||||||
|
) where
|
||||||
|
|
||||||
-- The definition of Expression is in its own file because:
|
-- The definition of Expression is in its own file because:
|
||||||
-- * Expression and AbstractSyntax should not be in the same file
|
-- * Expression and AbstractSyntax should not be in the same file
|
||||||
|
@ -10,18 +16,21 @@ module LambdaCalculus.Expression where
|
||||||
-- with all of these irrelevant conversion operators.
|
-- with all of these irrelevant conversion operators.
|
||||||
|
|
||||||
import Data.Bifunctor (first, second)
|
import Data.Bifunctor (first, second)
|
||||||
|
import Data.List.NonEmpty (NonEmpty ((:|)), fromList, toList)
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Data.Text qualified as T
|
import Data.Text qualified as T
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import LambdaCalculus.Parser.AbstractSyntax (AbstractSyntax)
|
import LambdaCalculus.Parser.AbstractSyntax (AbstractSyntax)
|
||||||
import LambdaCalculus.Parser.AbstractSyntax qualified as AST
|
import LambdaCalculus.Parser.AbstractSyntax qualified as AST
|
||||||
import TextShow
|
import TextShow (Builder, fromText, TextShow, showb, showt)
|
||||||
|
|
||||||
data Expression
|
data Expression
|
||||||
= Variable Text
|
= Variable Text
|
||||||
| Application Expression Expression
|
-- | Function application: `(f x)`.
|
||||||
| Abstraction Text Expression
|
| Application Expression Expression
|
||||||
deriving (Eq, Generic)
|
-- | Lambda abstraction: `(λx. e)`.
|
||||||
|
| Abstraction Text Expression
|
||||||
|
deriving (Eq, Generic)
|
||||||
|
|
||||||
-- | A naive implementation of 'show', which does not take advantage of any syntactic sugar
|
-- | A naive implementation of 'show', which does not take advantage of any syntactic sugar
|
||||||
-- and always emits optional parentheses.
|
-- and always emits optional parentheses.
|
||||||
|
@ -33,26 +42,36 @@ basicShow (Abstraction var body) = "(λ" <> fromText var <> ". " <> showb body <
|
||||||
-- | Convert from an abstract syntax tree to an expression.
|
-- | Convert from an abstract syntax tree to an expression.
|
||||||
ast2expr :: AbstractSyntax -> Expression
|
ast2expr :: AbstractSyntax -> Expression
|
||||||
ast2expr (AST.Variable name) = Variable name
|
ast2expr (AST.Variable name) = Variable name
|
||||||
ast2expr (AST.Application []) = Abstraction "x" (Variable "x")
|
ast2expr (AST.Application (x :| [])) = ast2expr x
|
||||||
ast2expr (AST.Application [x]) = ast2expr x
|
ast2expr (AST.Application xs) = foldl1 Application $ map ast2expr (toList xs)
|
||||||
ast2expr (AST.Application xs) = foldl1 Application $ map ast2expr xs
|
|
||||||
ast2expr (AST.Abstraction [] body) = ast2expr body
|
|
||||||
ast2expr (AST.Abstraction names body) = foldr Abstraction (ast2expr body) names
|
ast2expr (AST.Abstraction names body) = foldr Abstraction (ast2expr body) names
|
||||||
ast2expr (AST.Let defs body) = foldr (uncurry letExpr . second ast2expr) (ast2expr body) defs
|
ast2expr (AST.Let defs body) = foldr (uncurry letExpr . second ast2expr) (ast2expr body) defs
|
||||||
where letExpr :: Text -> Expression -> Expression -> Expression
|
where
|
||||||
letExpr name val body = Application (Abstraction name body) val
|
letExpr :: Text -> Expression -> Expression -> Expression
|
||||||
|
letExpr name val body' = Application (Abstraction name body') val
|
||||||
|
|
||||||
-- | View nested applications of abstractions as a list.
|
-- | View nested applications of abstractions as a list.
|
||||||
|
pattern Lets :: [(Text, Expression)] -> Expression -> Expression
|
||||||
|
pattern Lets defs body <- (viewLet -> (defs@(_:_), body))
|
||||||
|
|
||||||
viewLet :: Expression -> ([(Text, Expression)], Expression)
|
viewLet :: Expression -> ([(Text, Expression)], Expression)
|
||||||
viewLet (Application (Abstraction var body) x) = first ((var, x) :) (viewLet body)
|
viewLet (Application (Abstraction var body) x) = first ((var, x) :) (viewLet body)
|
||||||
viewLet x = ([], x)
|
viewLet x = ([], x)
|
||||||
|
|
||||||
-- | View nested abstractions as a list.
|
-- | View nested abstractions as a list.
|
||||||
|
pattern Abstractions :: [Text] -> Expression -> Expression
|
||||||
|
pattern Abstractions names body <- (viewAbstraction -> (names@(_:_), body))
|
||||||
|
|
||||||
viewAbstraction :: Expression -> ([Text], Expression)
|
viewAbstraction :: Expression -> ([Text], Expression)
|
||||||
viewAbstraction (Abstraction name body) = first (name :) (viewAbstraction body)
|
viewAbstraction (Abstraction name body) = first (name :) (viewAbstraction body)
|
||||||
viewAbstraction x = ([], x)
|
viewAbstraction x = ([], x)
|
||||||
|
|
||||||
-- | View left-nested applications as a list.
|
-- | View left-nested applications as a list.
|
||||||
|
pattern Applications :: [Expression] -> Expression
|
||||||
|
pattern Applications exprs <- (viewApplication -> (exprs@(_:_:_)))
|
||||||
|
|
||||||
|
{-# COMPLETE Abstractions, Applications, Variable :: Expression #-}
|
||||||
|
|
||||||
viewApplication :: Expression -> [Expression]
|
viewApplication :: Expression -> [Expression]
|
||||||
viewApplication (Application ef ex) = ex : viewApplication ef
|
viewApplication (Application ef ex) = ex : viewApplication ef
|
||||||
viewApplication x = [x]
|
viewApplication x = [x]
|
||||||
|
@ -61,9 +80,9 @@ viewApplication x = [x]
|
||||||
--
|
--
|
||||||
-- This function will use let, and applications and abstractions of multiple values when possible.
|
-- This function will use let, and applications and abstractions of multiple values when possible.
|
||||||
expr2ast :: Expression -> AbstractSyntax
|
expr2ast :: Expression -> AbstractSyntax
|
||||||
expr2ast (viewLet -> (defs@(_:_), body)) = AST.Let (map (second expr2ast) defs) $ expr2ast body
|
expr2ast (Lets defs body) = AST.Let (fromList $ map (second expr2ast) defs) $ expr2ast body
|
||||||
expr2ast (viewAbstraction -> (names@(_:_), body)) = AST.Abstraction names $ expr2ast body
|
expr2ast (Abstractions names body) = AST.Abstraction (fromList names) $ expr2ast body
|
||||||
expr2ast (viewApplication -> es@(_:_:_)) = AST.Application $ map expr2ast $ reverse es
|
expr2ast (Applications exprs) = AST.Application $ fromList $ map expr2ast $ reverse exprs
|
||||||
expr2ast (Variable name) = AST.Variable name
|
expr2ast (Variable name) = AST.Variable name
|
||||||
|
|
||||||
instance TextShow Expression where
|
instance TextShow Expression where
|
||||||
|
|
|
@ -1,18 +1,13 @@
|
||||||
module LambdaCalculus.Parser
|
module LambdaCalculus.Parser (parseAST, parseExpression) where
|
||||||
( parseAST, parseExpression
|
|
||||||
) where
|
|
||||||
|
|
||||||
import Control.Applicative ((*>))
|
import Data.List.NonEmpty (fromList)
|
||||||
import Control.Monad (void)
|
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import Data.Text qualified as T
|
||||||
import LambdaCalculus.Expression (Expression, ast2expr)
|
import LambdaCalculus.Expression (Expression, ast2expr)
|
||||||
import qualified LambdaCalculus.Expression as Expr
|
|
||||||
import LambdaCalculus.Parser.AbstractSyntax
|
import LambdaCalculus.Parser.AbstractSyntax
|
||||||
import Text.Parsec hiding (label, token)
|
import Text.Parsec hiding (label, token)
|
||||||
import Text.Parsec qualified
|
import Text.Parsec qualified
|
||||||
import Text.Parsec.Text (Parser)
|
import Text.Parsec.Text (Parser)
|
||||||
import TextShow
|
|
||||||
|
|
||||||
label :: String -> Parser a -> Parser a
|
label :: String -> Parser a -> Parser a
|
||||||
label = flip Text.Parsec.label
|
label = flip Text.Parsec.label
|
||||||
|
@ -26,10 +21,10 @@ keywords = ["let", "in"]
|
||||||
-- | A keyword is an exact string which is not part of an identifier.
|
-- | A keyword is an exact string which is not part of an identifier.
|
||||||
keyword :: Text -> Parser ()
|
keyword :: Text -> Parser ()
|
||||||
keyword kwd = label (T.unpack kwd) $ do
|
keyword kwd = label (T.unpack kwd) $ do
|
||||||
try do
|
try do
|
||||||
string $ T.unpack kwd
|
string $ T.unpack kwd
|
||||||
notFollowedBy letter
|
notFollowedBy letter
|
||||||
spaces
|
spaces
|
||||||
|
|
||||||
-- | An identifier is a sequence of letters which is not a keyword.
|
-- | An identifier is a sequence of letters which is not a keyword.
|
||||||
identifier :: Parser Identifier
|
identifier :: Parser Identifier
|
||||||
|
@ -48,22 +43,22 @@ grouping :: Parser AbstractSyntax
|
||||||
grouping = label "grouping" $ between (token '(') (token ')') expression
|
grouping = label "grouping" $ between (token '(') (token ')') expression
|
||||||
|
|
||||||
application :: Parser AbstractSyntax
|
application :: Parser AbstractSyntax
|
||||||
application = Application <$> many2 applicationTerm
|
application = Application . fromList <$> many2 applicationTerm
|
||||||
where applicationTerm :: Parser AbstractSyntax
|
where applicationTerm = abstraction <|> let_ <|> grouping <|> variable
|
||||||
applicationTerm = abstraction <|> let_ <|> grouping <|> variable
|
|
||||||
|
|
||||||
abstraction :: Parser AbstractSyntax
|
abstraction :: Parser AbstractSyntax
|
||||||
abstraction = label "lambda abstraction" $ Abstraction <$> between lambda (token '.') (many1 identifier) <*> expression
|
abstraction = label "lambda abstraction" $ Abstraction <$> between lambda (token '.') (fromList <$> many1 identifier) <*> expression
|
||||||
where lambda = label "lambda" $ (char '\\' <|> char 'λ') *> spaces
|
where lambda = label "lambda" $ (char '\\' <|> char 'λ') *> spaces
|
||||||
|
|
||||||
let_ :: Parser AbstractSyntax
|
let_ :: Parser AbstractSyntax
|
||||||
let_ = Let <$> between (keyword "let") (keyword "in") definitions <*> expression
|
let_ = Let <$> between (keyword "let") (keyword "in") (fromList <$> definitions) <*> expression
|
||||||
where definitions :: Parser [Definition]
|
where
|
||||||
definitions = flip sepBy1 (token ';') do
|
definitions :: Parser [Definition]
|
||||||
name <- identifier
|
definitions = flip sepBy1 (token ';') do
|
||||||
token '='
|
name <- identifier
|
||||||
value <- expression
|
token '='
|
||||||
pure (name, value)
|
value <- expression
|
||||||
|
pure (name, value)
|
||||||
|
|
||||||
expression :: Parser AbstractSyntax
|
expression :: Parser AbstractSyntax
|
||||||
expression = label "expression" $ abstraction <|> let_ <|> try application <|> grouping <|> variable
|
expression = label "expression" $ abstraction <|> let_ <|> try application <|> grouping <|> variable
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
module LambdaCalculus.Parser.AbstractSyntax
|
module LambdaCalculus.Parser.AbstractSyntax
|
||||||
( AbstractSyntax (..), Definition, Identifier
|
( AbstractSyntax (..), Definition, Identifier
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Bifunctor (first)
|
import Data.Bifunctor (first)
|
||||||
|
import Data.List.NonEmpty (NonEmpty ((:|)), fromList, toList)
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Data.Text qualified as T
|
import Data.Text qualified as T
|
||||||
import TextShow
|
import TextShow (Builder, TextShow, showb, showt, toText, fromText)
|
||||||
|
|
||||||
-- | The abstract syntax tree reflects the structure of the externally-visible syntax.
|
-- | The abstract syntax tree reflects the structure of the externally-visible syntax.
|
||||||
--
|
--
|
||||||
|
@ -14,42 +15,59 @@ import TextShow
|
||||||
-- would become unnecessarily complicated, because the same expression
|
-- would become unnecessarily complicated, because the same expression
|
||||||
-- can be represented in terms of multiple abstract syntax trees.
|
-- can be represented in terms of multiple abstract syntax trees.
|
||||||
data AbstractSyntax
|
data AbstractSyntax
|
||||||
= Variable Identifier
|
= Variable Identifier
|
||||||
| Application [AbstractSyntax]
|
-- There is no technical reason for the AST to forbid nullary applications and so forth.
|
||||||
| Abstraction [Identifier] AbstractSyntax
|
-- However the parser rejects them to avoid confusing edge cases like `let x=in`,
|
||||||
| Let [Definition] AbstractSyntax
|
-- so they're forbidden here too so that the syntax tree can't contain data
|
||||||
|
-- that the parser would refuse to accept.
|
||||||
|
--
|
||||||
|
-- As a matter of curiosity, here's why `let x=in` was syntactically valid:
|
||||||
|
-- 1. Parentheses in `let` statements are optional, infer them: `let x=()in()`.
|
||||||
|
-- 2. Insert optional whitespace: `let x = () in ()`.
|
||||||
|
-- 3. Nullary application expands to the identity function because
|
||||||
|
-- the identity function is the left identity of function application:
|
||||||
|
-- `let x=(\x.x) in \x.x`.
|
||||||
|
--
|
||||||
|
-- | n-ary function application: `(f x_1 x_2 ... x_n)`.
|
||||||
|
| Application (NonEmpty AbstractSyntax)
|
||||||
|
-- | Lambda abstraction over n variables: `(λx_1 x_2 ... x_n. e)`
|
||||||
|
| Abstraction (NonEmpty Identifier) AbstractSyntax
|
||||||
|
-- | Let expressions (syntactic sugar) binding `n` variables:
|
||||||
|
-- `let x_1 = e_1; x_2 = e_2; ... x_n = e_n`.
|
||||||
|
| Let (NonEmpty Definition) AbstractSyntax
|
||||||
type Definition = (Identifier, AbstractSyntax)
|
type Definition = (Identifier, AbstractSyntax)
|
||||||
type Identifier = Text
|
type Identifier = Text
|
||||||
|
|
||||||
-- I'm surprised this isn't in base somewhere.
|
-- I'm surprised this isn't in base somewhere.
|
||||||
unsnoc :: [a] -> ([a], a)
|
unsnoc :: NonEmpty a -> ([a], a)
|
||||||
unsnoc [x] = ([], x)
|
unsnoc (x :| []) = ([], x)
|
||||||
unsnoc (x : xs) = first (x :) (unsnoc xs)
|
unsnoc (x :| xs) = first (x :) (unsnoc (fromList xs))
|
||||||
|
|
||||||
instance TextShow AbstractSyntax where
|
instance TextShow AbstractSyntax where
|
||||||
showb = unambiguous
|
showb = unambiguous
|
||||||
where
|
where
|
||||||
unambiguous, ambiguous :: AbstractSyntax -> Builder
|
-- Parentheses are often optional to the parser, but not in every context.
|
||||||
unambiguous (Variable name) = fromText name
|
-- The `unambigous` printer is used in contexts where parentheses are optional, and does not include them;
|
||||||
-- There's no technical reason for the AST to forbid nullary applications and so forth.
|
-- the `ambiguous` printer is used when omitting parentheses could result in an incorrect parse.
|
||||||
-- However the parser rejects them to avoid confusing edge cases like `let x=in`,
|
unambiguous, ambiguous :: AbstractSyntax -> Builder
|
||||||
-- so they're forbidden here too so that `show` will never print anything the parser would refuse to accept.
|
unambiguous (Variable name) = fromText name
|
||||||
unambiguous (Application []) = error "Empty applications are currently disallowed."
|
unambiguous (Application (unsnoc -> (es, final))) = foldr (\e es' -> ambiguous e <> " " <> es') final' es
|
||||||
unambiguous (Application (unsnoc -> (es, final))) = foldr (\e es' -> ambiguous e <> " " <> es') final' es
|
where
|
||||||
where final' = case final of
|
final' = case final of
|
||||||
Application _ -> ambiguous final
|
Application _ -> ambiguous final
|
||||||
_ -> unambiguous final
|
_ -> unambiguous final
|
||||||
unambiguous (Abstraction [] _) = error "Empty lambdas are currently disallowed."
|
unambiguous (Abstraction names body) = "λ" <> names' <> ". " <> unambiguous body
|
||||||
unambiguous (Abstraction names body) = "λ" <> fromText (T.intercalate " " names) <> ". " <> unambiguous body
|
where names' = fromText (T.intercalate " " $ toList names)
|
||||||
unambiguous (Let [] body) = error "Empty lets are currently disallowed."
|
unambiguous (Let defs body) = "let " <> defs' <> " in " <> unambiguous body
|
||||||
unambiguous (Let defs body) = "let " <> fromText (T.intercalate "; " $ map (toText . showDef) defs) <> " in " <> unambiguous body
|
where
|
||||||
where showDef :: Definition -> Builder
|
defs' = fromText (T.intercalate "; " $ map (toText . showDef) $ toList defs)
|
||||||
showDef (name, val) = fromText name <> " = " <> unambiguous val
|
|
||||||
|
|
||||||
-- | Adds a grouper if omitting it could result in ambiguous syntax.
|
showDef :: Definition -> Builder
|
||||||
-- (Which is to say, the parser would parse it wrong because a different parse has a higher priority.)
|
showDef (name, val) = fromText name <> " = " <> unambiguous val
|
||||||
ambiguous e@(Variable _) = unambiguous e
|
|
||||||
ambiguous e = "(" <> unambiguous e <> ")"
|
-- | Adds a grouper if omitting it could result in ambiguous syntax.)
|
||||||
|
ambiguous e@(Variable _) = unambiguous e
|
||||||
|
ambiguous e = "(" <> unambiguous e <> ")"
|
||||||
|
|
||||||
instance Show AbstractSyntax where
|
instance Show AbstractSyntax where
|
||||||
show = T.unpack . showt
|
show = T.unpack . showt
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
# Nightly is required for the ImportQualifiedPost extension from GHC 8.10.
|
resolver: lts-17.5
|
||||||
# An LTS resolver will be used once GHC 8.10 is in an LTS.
|
|
||||||
resolver: nightly-2020-11-03
|
|
||||||
packages:
|
packages:
|
||||||
- .
|
- .
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
packages: []
|
packages: []
|
||||||
snapshots:
|
snapshots:
|
||||||
- completed:
|
- completed:
|
||||||
size: 544004
|
size: 565266
|
||||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2020/11/3.yaml
|
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/17/5.yaml
|
||||||
sha256: f6988e9a2c92219dc8ff0ebd2d420ede3425fa08cb6613ba47f1bc97c9925aa8
|
sha256: 78e8ebabf11406261abbc95b44f240acf71802630b368888f6d758de7fc3a2f7
|
||||||
original: nightly-2020-11-03
|
original: lts-17.5
|
||||||
|
|
12
test/Spec.hs
12
test/Spec.hs
|
@ -23,16 +23,18 @@ instance Arbitrary T.Text where
|
||||||
-- This should evaluate to `y y`.
|
-- This should evaluate to `y y`.
|
||||||
dfi :: Expression
|
dfi :: Expression
|
||||||
dfi = Application d (Application f i)
|
dfi = Application d (Application f i)
|
||||||
where d = Abstraction "x" $ Application (Variable "x") (Variable "x")
|
where
|
||||||
f = Abstraction "f" $ Application (Variable "f") (Application (Variable "f") (Variable "y"))
|
d = Abstraction "x" $ Application (Variable "x") (Variable "x")
|
||||||
i = Abstraction "x" $ Variable "x"
|
f = Abstraction "f" $ Application (Variable "f") (Application (Variable "f") (Variable "y"))
|
||||||
|
i = Abstraction "x" $ Variable "x"
|
||||||
|
|
||||||
-- This should evalaute to `y`.
|
-- This should evalaute to `y`.
|
||||||
ttttt :: Expression
|
ttttt :: Expression
|
||||||
ttttt = Application (Application (Application f t) (Abstraction "x" (Variable "x"))) (Variable "y")
|
ttttt = Application (Application (Application f t) (Abstraction "x" (Variable "x"))) (Variable "y")
|
||||||
where t = Abstraction "f" $ Abstraction "x" $
|
where
|
||||||
|
t = Abstraction "f" $ Abstraction "x" $
|
||||||
Application (Variable "f") (Application (Variable "f") (Variable "x"))
|
Application (Variable "f") (Application (Variable "f") (Variable "x"))
|
||||||
f = Abstraction "T" $ Abstraction "f" $ Abstraction "x" $
|
f = Abstraction "T" $ Abstraction "f" $ Abstraction "x" $
|
||||||
Application (Application
|
Application (Application
|
||||||
(Application (Variable "T")
|
(Application (Variable "T")
|
||||||
(Application (Variable "T")
|
(Application (Variable "T")
|
||||||
|
|
Loading…
Reference in New Issue