Allow defining multiple variables with one `let`.
parent
ced4dbeb7b
commit
05d5abec6d
|
@ -0,0 +1,11 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.yml]
|
||||||
|
indent_size = 2
|
|
@ -9,7 +9,7 @@ Exit the prompt with `Ctrl-c` (or equivalent).
|
||||||
|
|
||||||
### Example session
|
### Example session
|
||||||
```
|
```
|
||||||
>> let D = (\x. x x) in let F = (\f. f (f y)) in D (F (\x. x))
|
>> let D = (\x. x x); F = (\f. f (f y)) in D (F (\x. x))
|
||||||
(y y)
|
(y y)
|
||||||
>> let T = (\f x. f (f x)) in (\f x. T (T (T (T T))) f x) (\x. x) y
|
>> let T = (\f x. f (f x)) in (\f x. T (T (T (T T))) f x) (\x. x) y
|
||||||
y
|
y
|
||||||
|
@ -29,4 +29,5 @@ and spaces are used to separate variables rather than commas.
|
||||||
* Applications are left-associative: `M N P` may be written instead of `((M N) P)`.
|
* Applications are left-associative: `M N P` may be written instead of `((M N) P)`.
|
||||||
* The body of an abstraction or let expression extends as far right as possible: `\x. M N` means `\x.(M N)` and not `(\x. M) N`.
|
* The body of an abstraction or let expression extends as far right as possible: `\x. M N` means `\x.(M N)` and not `(\x. M) N`.
|
||||||
* A sequence of abstractions may be contracted: `\foo. \bar. \baz. N` may be abbreviated as `\foo bar baz. N`.
|
* A sequence of abstractions may be contracted: `\foo. \bar. \baz. N` may be abbreviated as `\foo bar baz. N`.
|
||||||
* Variables may be bound using let expressions: `let x = N in M` is syntactic sugar for `(\x. N) M`.
|
* Variables may be bound using let expressions: `let x = N in M` is syntactic sugar for `(\x. N) M`.
|
||||||
|
* Multiple variables may be defined in one let expression: `let x = N; y = O in M`
|
||||||
|
|
|
@ -19,7 +19,7 @@ data Expression
|
||||||
instance TextShow Expression where
|
instance TextShow Expression where
|
||||||
showb (Variable var) = fromText var
|
showb (Variable var) = fromText var
|
||||||
showb (Application ef ex) = "(" <> showb ef <> " " <> showb ex <> ")"
|
showb (Application ef ex) = "(" <> showb ef <> " " <> showb ex <> ")"
|
||||||
showb (Abstraction var body) = "(\\" <> fromText var <> ". " <> showb body <> ")"
|
showb (Abstraction var body) = "(λ" <> fromText var <> ". " <> showb body <> ")"
|
||||||
|
|
||||||
instance Show Expression where
|
instance Show Expression where
|
||||||
show = T.unpack . showt
|
show = T.unpack . showt
|
||||||
|
|
|
@ -8,52 +8,58 @@ import LambdaCalculus.Expression
|
||||||
import Text.Parsec hiding (spaces)
|
import Text.Parsec hiding (spaces)
|
||||||
import Text.Parsec.Text
|
import Text.Parsec.Text
|
||||||
|
|
||||||
spaces :: Parser ()
|
keywords :: [Text]
|
||||||
spaces = void $ many1 space
|
keywords = ["let", "in"]
|
||||||
|
|
||||||
variableName :: Parser Text
|
-- | A keyword is an exact string which is not part of an identifier.
|
||||||
variableName = do
|
|
||||||
notFollowedBy anyKeyword
|
|
||||||
T.pack <$> many1 letter
|
|
||||||
where anyKeyword = choice $ map keyword keywords
|
|
||||||
keywords = ["let", "in"]
|
|
||||||
|
|
||||||
-- | Match an exact string which is not just a substring
|
|
||||||
-- of a larger variable name.
|
|
||||||
keyword :: Text -> Parser ()
|
keyword :: Text -> Parser ()
|
||||||
keyword kwd = try $ do
|
keyword kwd = do
|
||||||
void $ string (T.unpack kwd)
|
void $ string (T.unpack kwd)
|
||||||
notFollowedBy letter
|
notFollowedBy letter
|
||||||
|
|
||||||
|
-- | An identifier is a sequence of letters which is not a keyword.
|
||||||
|
identifier :: Parser Text
|
||||||
|
identifier = do
|
||||||
|
notFollowedBy anyKeyword
|
||||||
|
T.pack <$> many1 letter
|
||||||
|
where anyKeyword = choice $ map (try . keyword) keywords
|
||||||
|
|
||||||
variable :: Parser Expression
|
variable :: Parser Expression
|
||||||
variable = Variable <$> variableName
|
variable = Variable <$> identifier
|
||||||
|
|
||||||
|
spaces :: Parser ()
|
||||||
|
spaces = skipMany1 space
|
||||||
|
|
||||||
application :: Parser Expression
|
application :: Parser Expression
|
||||||
application = foldl1 Application <$> sepEndBy1 applicationTerm spaces
|
application = foldl1 Application <$> sepEndBy1 applicationTerm spaces
|
||||||
where applicationTerm :: Parser Expression
|
where applicationTerm :: Parser Expression
|
||||||
applicationTerm = variable <|> abstraction <|> let_ <|> grouping
|
applicationTerm = abstraction <|> grouping <|> let_ <|> variable
|
||||||
where grouping :: Parser Expression
|
where grouping :: Parser Expression
|
||||||
grouping = between (char '(') (char ')') expression
|
grouping = between (char '(') (char ')') expression
|
||||||
|
|
||||||
abstraction :: Parser Expression
|
abstraction :: Parser Expression
|
||||||
abstraction = do
|
abstraction = do
|
||||||
char '\\'
|
char '\\' <|> char 'λ' ; optional spaces
|
||||||
optional spaces
|
names <- sepEndBy1 identifier spaces
|
||||||
names <- sepEndBy1 variableName spaces
|
char '.'
|
||||||
char '.'
|
body <- expression
|
||||||
optional spaces
|
pure $ foldr Abstraction body names
|
||||||
body <- expression
|
|
||||||
pure $ foldr Abstraction body names
|
|
||||||
|
|
||||||
let_ :: Parser Expression
|
let_ :: Parser Expression
|
||||||
let_ = do
|
let_ = do
|
||||||
keyword "let"
|
try (keyword "let") ; spaces
|
||||||
name <- between spaces (optional spaces) variableName
|
defs <- sepBy1 definition (char ';' *> optional spaces)
|
||||||
char '='
|
keyword "in"
|
||||||
value <- expression
|
body <- expression
|
||||||
keyword "in"
|
pure $ foldr (uncurry letExpr) body defs
|
||||||
body <- expression
|
where definition :: Parser (Text, Expression)
|
||||||
pure $ Application (Abstraction name body) value
|
definition = do
|
||||||
|
name <- identifier ; optional spaces
|
||||||
|
char '='
|
||||||
|
value <- expression
|
||||||
|
pure (name, value)
|
||||||
|
letExpr :: Text -> Expression -> Expression -> Expression
|
||||||
|
letExpr name value body = Application (Abstraction name body) value
|
||||||
|
|
||||||
expression :: Parser Expression
|
expression :: Parser Expression
|
||||||
expression = optional spaces *> (abstraction <|> let_ <|> application <|> variable) <* optional spaces
|
expression = optional spaces *> (abstraction <|> let_ <|> application <|> variable) <* optional spaces
|
||||||
|
|
|
@ -60,6 +60,7 @@ main = defaultMain $
|
||||||
, testCase "unary application" $ parseExpression "(x)" @?= Right (Variable "x")
|
, testCase "unary application" $ parseExpression "(x)" @?= Right (Variable "x")
|
||||||
, testCase "application shorthand" $ parseExpression "a b c d" @?= Right (Application (Application (Application (Variable "a") (Variable "b")) (Variable "c")) (Variable "d"))
|
, testCase "application shorthand" $ parseExpression "a b c d" @?= Right (Application (Application (Application (Variable "a") (Variable "b")) (Variable "c")) (Variable "d"))
|
||||||
, testCase "let" $ parseExpression "let x = \\y.y in x" @?= Right (Application (Abstraction "x" (Variable "x")) (Abstraction "y" (Variable "y")))
|
, testCase "let" $ parseExpression "let x = \\y.y in x" @?= Right (Application (Abstraction "x" (Variable "x")) (Abstraction "y" (Variable "y")))
|
||||||
|
, testCase "multi-let" $ parseExpression "let x = y; y = z in x y" @?= Right (Application (Abstraction "x" (Application (Abstraction "y" (Application (Variable "x") (Variable "y"))) (Variable "z"))) (Variable "y"))
|
||||||
, testCase "ttttt" $ parseExpression "(\\T f x.(T (T (T (T T)))) f x) (\\f x.f (f x)) (\\x.x) y"
|
, testCase "ttttt" $ parseExpression "(\\T f x.(T (T (T (T T)))) f x) (\\f x.f (f x)) (\\x.x) y"
|
||||||
@?= Right ttttt
|
@?= Right ttttt
|
||||||
, testGroup "Redundant whitespace"
|
, testGroup "Redundant whitespace"
|
||||||
|
|
Loading…
Reference in New Issue