PropaFP-0.1.0.0: src/PropaFP/Parsers/Lisp/Parser.hs
module PropaFP.Parsers.Lisp.Parser
( tokenize
, parse
, parseSequence
, analyzeExpression
, analyzeExpressionSequence
, isScientificNumber) where
import PropaFP.Parsers.Lisp.DataTypes
import Prelude
import GHC.Utils.Misc (readRational)
import qualified Data.Scientific as S
import qualified Data.List as L
-- Constants.
symbolCharacters :: String
symbolCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_!?-+*/%<>#.=^"
numberCharacters :: String
numberCharacters = "0123456789."
isSymbolCharacter :: Char -> Bool
isSymbolCharacter ch = elem ch symbolCharacters
isNumberCharacter :: Char -> Bool
isNumberCharacter ch = elem ch numberCharacters
isSymbol :: String -> Bool
isSymbol = all isSymbolCharacter
-- Fixed issue with parsing -.1, multiple decimal points
isNumber :: String -> Bool
isNumber [] = True
isNumber [c] = elem c "0123456789"
isNumber ('-' : cs) = isNumber cs
isNumber (c : cs) = elem c "0123456789" && all isNumberCharacter cs && L.length (L.filter ('.' ==) cs) <= 1
isScientificNumber :: String -> Bool
isScientificNumber [] = True
isScientificNumber [_c] = False
isScientificNumber ('-' : cs) = isScientificNumber cs
isScientificNumber (c : cs) =
elem c "0123456789" &&
case L.break (== 'e') cs of
(_, []) -> False -- e is not in cs
(beforeE, _e : afterE) ->
all isNumberCharacter beforeE &&
case afterE of
[] -> False
('-' : ecs) -> all (`elem` "0123456789") ecs
ecs -> all (`elem` "0123456789") ecs
&& L.length (L.filter ('.' ==) cs) <= 1
-- The "tokenize" function is the first phase of converting the source code of
-- a Lisp program into an abstract syntax tree. It performs lexical analysis on
-- a String representation of a Lisp program by extracting a list of tokens.
tokenize :: String -> [String]
tokenize [] = []
tokenize (x:xs)
| x == ';' = tokenize $ dropWhile (/= '\n') xs -- Remove comments
| x == '(' = [x] : tokenize xs
| x == ')' = [x] : tokenize xs
| isNumberCharacter x = tokenizeNumber (x:xs) "" False
| isSymbolCharacter x = tokenizeSymbol (x:xs) ""
| otherwise = tokenize xs
tokenizeNumber :: String -> String -> Bool -> [String]
tokenizeNumber [] number foundE = [number]
tokenizeNumber (x:xs) number foundE
| isNumberCharacter x = tokenizeNumber xs (number ++ [x]) foundE
| ('e' == x) && not foundE = tokenizeNumber xs (number ++ [x]) True -- Support scientific numbers
| otherwise = number : tokenize (x:xs)
tokenizeSymbol :: String -> String -> [String]
tokenizeSymbol [] symbol = [symbol]
tokenizeSymbol (x:xs) number
| isSymbolCharacter x = tokenizeSymbol xs (number ++ [x])
| otherwise = number : tokenize (x:xs)
-- The "parse" function is the second phase of converting the source code of
-- a Lisp program into an abstract syntax tree. It takes the list of tokens
-- generated by "tokenize" and scans it until a valid Expression is created.
-- The newly built expression along with the remaining tokens are returned.
-- The Expressions that are returned are "primitive". They are entirely
-- comprised of elements such as boolean and numeric constants with the only
-- compound element being a "pair".
parse :: [String] -> (Expression, [String])
parse [] = (Null, [])
parse (x:xs)
| x == "(" = parseList xs
| "#t" == x = ((Boolean True), xs)
| "#f" == x = ((Boolean False), xs)
| "null" == x = ((Null), xs)
| isScientificNumber x = ((Number (toRational (read x :: S.Scientific))), xs)
| isNumber x = ((Number (readRational x)), xs)
| isSymbol x = ((Variable x), xs)
| otherwise = (Null, [])
-- A helper function to parse a list. A list is defined as either
-- Null or a Pair who's second element is a list.
parseList :: [String] -> (Expression, [String])
parseList [] = (Null, [])
parseList tokens@(x:xs)
| x == ")" = (Null, xs)
| otherwise = ((Pair expr1 expr2), rest2)
where (expr1, rest1) = parse tokens
(expr2, rest2) = parseList rest1
-- A helper function that takes the list of tokens generated by "tokenize"
-- and continues parsing until all the constituent Expressions are extracted.
parseSequence :: [String] -> [Expression]
parseSequence [] = []
parseSequence tokens = expr : parseSequence rest
where (expr, rest) = parse tokens
-- The "analyzeExpression" function implements the third and final phase of
-- converting the source code of a Lisp program into an abstract syntax tree.
-- It takes a "primitive" Expression as input and converts it into a more
-- sophisticated abstract syntax tree Expression such as Lambda, Application,
-- If, Define, etc.
analyzeExpression :: Expression -> Expression
analyzeExpression Null = Null
analyzeExpression (Number number) = (Number number)
analyzeExpression (Boolean bool) = (Boolean bool)
analyzeExpression (Variable variable) = (Variable variable)
analyzeExpression pair@(Pair first second)
| isIfExpression pair = buildIfExpression pair
| isLambdaExpression pair = buildLambdaExpression pair
| isDefinitionExpression pair = buildDefinitionExpression pair
| isCondExpression pair = buildCondExpression pair
-- New special forms to be added here.
| otherwise = buildApplicationExpression pair
isIfExpression :: Expression -> Bool
isIfExpression (Pair (Variable value) _) = value == "if"
isIfExpression _ = False
buildIfExpression :: Expression -> Expression
buildIfExpression (Pair _ (Pair predicate (Pair thenClause (Pair elseClause Null)))) =
If (analyzeExpression predicate) (analyzeExpression thenClause) (analyzeExpression elseClause)
isLambdaExpression :: Expression -> Bool
isLambdaExpression (Pair (Variable value) _) = value == "lambda"
isLambdaExpression _ = False
buildLambdaExpression :: Expression -> Expression
buildLambdaExpression (Pair _ (Pair parameters (Pair body Null))) =
Lambda (pairToList parameters) (analyzeExpression body)
isDefinitionExpression :: Expression -> Bool
isDefinitionExpression (Pair (Variable value) _) = value == "define"
isDefinitionExpression _ = False
buildDefinitionExpression :: Expression -> Expression
buildDefinitionExpression (Pair _ (Pair variable (Pair value Null))) =
Definition variable (analyzeExpression value)
isCondExpression :: Expression -> Bool
isCondExpression (Pair (Variable value) _) = value == "cond"
isCondExpression _ = False
buildCondExpression :: Expression -> Expression
buildCondExpression (Pair _ second) = buildCondExpressionHelper second
buildCondExpressionHelper :: Expression -> Expression
buildCondExpressionHelper (Null) = (Cond [])
buildCondExpressionHelper (Pair (Pair predicate (Pair expression Null)) other) =
(Cond ((analyzeExpression predicate, analyzeExpression expression) : cases))
where (Cond cases) = buildCondExpressionHelper other
buildApplicationExpression :: Expression -> Expression
buildApplicationExpression (Pair operator operands) =
Application (analyzeExpression operator) (map analyzeExpression (pairToList operands))
analyzeExpressionSequence :: [Expression] -> [Expression]
analyzeExpressionSequence = map analyzeExpression