Stack Exchange network consists of 183 Q&A communities includingStack Overflow, the largest, most trusted online community for developers to learn, share their knowledge, and build their careers.
Visit Stack ExchangeI recently wrote code to parse lambda expressions written in a lisp like format, using a small hand-rolled parsing library.
I do not implementThe code does a single pass without any sort of tokenizer since. This might be a little odd, but because the domain I think that such would make the code more complex, and we have very rigid white-space rules.
do notation a good choice given my goals?of course keeping my initial goals in mind.
I recently wrote code to parse lambda expressions in a lisp like format, using a small hand-rolled parsing library.
I do not implement any sort of tokenizer since I think that such would make the code more complex, and we have very rigid white-space rules.
do notation a good choice?I recently wrote code to parse lambda expressions written in a lisp like format, using a small hand-rolled parsing library.
The code does a single pass without any tokenizer. This might be a little odd, but because the domain I think that such would make the code more complex, and we have very rigid white-space rules.
do notation a good choice given my goals?of course keeping my initial goals in mind.
I recently wrote code to parse lambda expressions in a lisp like format, using a small hand-rolled parsing library.
This code was written as a part ofthis code-golf challenge not as code-golf, but as a demo for the concept. My goals for this were
I chose Haskell for this because:
The code then consists of a minimalist "library" of simple helper functions, and the definition of the actual parser. If it is important, you should be able to see exactly how it is presented via the link above.
I'd like to get an idea of how well I achieved these goals and what steps could be taken to better present the idea through code.
The goal is to verify that a string is a properly formatted lambda expression, consisting of
abs (abstraction) to introduce a new variable.var (variable) to use a variable.app (application) to apply one lambda to another.formatted as a lisp list.
For example
(abs x (app (var x) (var x)))Is a valid expression representing\$\lambda x. x x\$.
Additionally we want to verify the expression doesn't use undeclared variables and doesn't shadow any variables.
So
(abs y (var x))is not valid becausex is not declared. And
(abs y (abs y (abs x (var x))))is not valid becausey is shadowed.
A precise grammar for the valid expressions is given
\$S\left(C\right) := \left\{\begin{array}[rr] \\color{green}{\texttt{(var }}x\color{green}{\texttt{)}} & \textrm{ where } x\in C \\\color{green}{\texttt{(abs }} x\texttt{ }S(C\cup\{x\})\color{green}{\texttt{)}} & \textrm{ where } x\notin C\\\color{green}{\texttt{(app }} S(C)\texttt{ }S(C)\color{green}{\texttt{)}} &\end{array}\right.\$
Where valid expressions are strings spanned by this grammar starting from\$S(\{\})\$.
There are two extra things to note:
import Control.Applicative ( liftA2 , Alternative (..) )import Control.Monad ( guard )newtype Parser a = Parser (String -> [(String, a)])apply :: Parser a -> String -> [(String, a)]apply (Parser p) s = p s instance Functor Parser where fmap func (Parser p) = Parser $ fmap (map (fmap func)) pinstance Applicative Parser where pure x = Parser ( \ string -> [(string, x)] ) liftA2 f (Parser p1) (Parser p2) = Parser ( \ string -> do (rem1, res1) <- p1 string (rem2, res2) <- p2 rem1 return (rem2, f res1 res2) )instance Monad Parser where Parser p >>= f = Parser ( \ string -> do (rem1, res1) <- p string apply (f res1) rem1 )instance Alternative Parser where empty = Parser ( \ _ -> [] ) Parser p1 <|> Parser p2 = Parser $ ( \ string -> p1 string ++ p2 string )charBy :: (Char -> Bool) -> Parser CharcharBy predicate = Parser go where go (a : b) = [ (b, a) | predicate a ] go [] = []char :: Char -> Parser Charchar x = charBy (x==)string :: String -> Parser Stringstring = traverse charchoice :: [Parser a] -> Parser achoice = foldr (<|>) emptylambda :: [String] -> Parser ()lambda variables = do char '(' choice [ do string "abs " name <- some $ charBy (`elem` ['a'..'z']) guard $ notElem name variables char ' ' lambda (name : variables) , do string "var " choice (map string variables) return () , do string "app " lambda variables char ' ' lambda variables ] char ')' return ()This uses monadic parsing to get the job done. I build a parser type implement the necessary type classes and a couple of helper functions. The actual parser follows the same structure as the given grammar.
I do not implement any sort of tokenizer since I think that such would make the code more complex, and we have very rigid white-space rules.
I welcome all feedback on how to make this code more presentable. However the specific questions I would like to prompt are:
do notation a good choice?