{- Copyright 2008 Debris This file is part of def. def is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. def is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with def; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA $Id: def.hs,v 0.1 2008/07/04 08:16 debris $ -} -- Import main libraries. -- Environment, and the parsec monad. module Main where import Monad import System.Environment import Control.Monad.Error import Text.ParserCombinators.Parsec hiding (spaces) -- main application entry point. -- parse input expression on args. main :: IO () main = getArgs >>= putStrLn . show . defEvaluate . defReadExpression . (!! 0) -- Read input expression. -- checks for errors using symbols found. defReadExpression :: String -> Type defReadExpression input = case parse defParseExpression "" input of Left err -> StringType $ "Syntax error at: " ++ show err Right val -> val -- "Syntax error: Symbol expected at" ++ show err -- Symbols definition. -- oneOf should be working by defining symbols to be any -- of the instances we mention in the string attached. defSymbols :: Parser Char defSymbols = oneOf "!$%&|*+-/:<=>?@^_~#" -- Spaces definition. -- Spaces should not be taken in consideration, so we use -- the skipMany1 function to skip them out of the parser. defSpaces :: Parser () defSpaces = skipMany1 space -- Type definitions. -- Types are currently somekind limited but they should -- be enough to perform some computation. data Type = AtomicType String | BooleanType Bool | IntegerType Integer | ListType [Type] | PairType [Type] Type | StringType String -- Error definitions. -- Those should provide desirable error checking -- capabilities to the language. data Errors = ArityError Integer [Type] | DefaultError String | FuncError String String | ParseError ParseError | SyntaxError String [Type] | TypeError String Type | VarError String String -- Error providing. -- Those functions should be able to show -- the current error definition to the user. defErrors :: Errors -> String defErrors (ArityError exp arity) = "Expected " ++ show exp ++ " args; found: " ++ defGetValues arity defErrors (FuncError msg func) = msg ++ ": " ++ show func defErrors (ParseError msg) = "Error at " ++ show msg defErrors (SyntaxError msg syn) = msg ++ ": " ++ show syn defErrors (TypeError exp types) = "Expected " ++ show exp ++ " type: found: " ++ show types defErrors (VarError msg var) = msg ++ ": " ++ var -- Instance needed to actually -- be able to show the error itself. instance Show Errors where show = defErrors -- Parse atomic type. -- Parses an atomic type of the language. -- This can be either a primitive or a boolean result. defParseAtomic :: Parser Type defParseAtomic = do x <- letter <|> defSymbols y <- many (letter <|> digit <|> defSymbols) let item = [x] ++ y return $ case item of "#true" -> BooleanType True "#false" -> BooleanType False otherwise -> AtomicType item -- Parse integer type. -- Parses an integer number by explicity considering -- every character as a digit. defParseInteger :: Parser Type defParseInteger = liftM (IntegerType . read) $ many1 digit -- Parse list type. -- Parses a list of types, not delimited -- by any characters, but just by spaces. defParseList :: Parser Type defParseList = liftM ListType $ sepBy defParseExpression defSpaces -- Parses a pair type. -- As in scheme a pair is composed by two -- lists separated by punctuation mark. defParsePair :: Parser Type defParsePair = do head <- endBy defParseExpression defSpaces tail <- char '.' >> defSpaces >> defParseExpression return $ PairType head tail -- Parses a quoted type. -- Quoted types evade computation on lists, -- wich might be used then as data. defParseQuote :: Parser Type defParseQuote = do char '\'' item <- defParseExpression return $ ListType [AtomicType "quote", item] -- Parses a string type. -- A string is just deilimited by double quotes. -- The drawback is we have to avoid escape characters. defParseString :: Parser Type defParseString = do char '"' item <- many (noneOf "\"") char '"' return $ StringType item -- Parses a language construction. -- The language is made of list or pair lexeme -- evaluation nodes, we just traverse them. defParseConstruct :: Parser Type defParseConstruct = do char '(' item <- (try defParseList) <|> defParsePair char ')' return item -- Parse a full expression. -- Expressions are made of lists, that get parsed -- by trying to get a list or a pair from them. defParseExpression :: Parser Type defParseExpression = defParseAtomic <|> defParseInteger <|> defParseQuote <|> defParseString <|> defParseConstruct -- Show values for constructions. -- This is needed for the parser to display -- to the user what he actually typed and what -- was the expected expression. defValue :: Type -> String defValue (AtomicType item) = item defValue (BooleanType True) = "#true" defValue (BooleanType False) = "#false" defValue (IntegerType item) = show item defValue (ListType item) = "(" ++ defGetValues item ++ ")" defValue (PairType x y) = "(" ++ defGetValues x ++ " . " ++ defValue y ++ ")" defValue (StringType item) = "\"" ++ item ++ "\"" -- Be able to get the items of a list of values -- This is needed for displaying values on -- lists and pairs. defGetValues :: [Type] -> String defGetValues = unwords . map defValue -- Let's declare now the instance that will -- actually show the types to the user. instance Show Type where show = defValue -- Finally we evaluate expressions on the -- values we get to be able to display them. defEvaluate :: Type -> Type defEvaluate item @ (BooleanType _) = item defEvaluate item @ (IntegerType _) = item defEvaluate item @ (StringType _) = item defEvaluate (ListType [AtomicType "quote", item]) = item defEvaluate (ListType (AtomicType function : args)) = defApply function $ map defEvaluate args -- We need to define how to apply a function -- binded to it's arguments to be able to handle -- primitive expressions. defApply :: String -> [Type] -> Type defApply function args = maybe (BooleanType False) ($ args) $ lookup function defBuiltins -- Builtin functions for our language. -- May be larger at some time. defBuiltins :: [(String, [Type] -> Type)] defBuiltins = [("+" , defIntegerOp (+)), ("-" , defIntegerOp (-)), ("*" , defIntegerOp (*)), ("/" , defIntegerOp div), ("type" , defGetTypeOp (++))] -- Integer operations. -- Maybe it's possible for this to work on both integers -- and decimal or fractional numbers. defIntegerOp :: (Integer -> Integer -> Integer) -> [Type] -> Type defIntegerOp op params = IntegerType $ foldl1 op $ map defGetIntegerParams params -- Get parameters. -- Parameters for numeric operations are easy to get, -- but we should take care of booleans too. defGetIntegerParams :: Type -> Integer defGetIntegerParams (BooleanType True) = 1 defGetIntegerParams (BooleanType False) = 0 defGetIntegerParams (IntegerType args) = args defGetIntegerParams (ListType [args]) = defGetIntegerParams args defGetIntegerParams (StringType args) = let item = reads args in fst $ item !! 0 -- Get types from expressions. -- This adds everything into a simple atom -- that composes the whole type flags. defGetTypeOp :: (String -> String -> String) -> [Type] -> Type defGetTypeOp op params = AtomicType $ (foldl1 op $ map defGetTypeParams params) -- Show values for constructions. -- This is needed for the parser to display -- to the user what he actually typed and what -- was the expected expression. defGetTypeParams :: Type -> String defGetTypeParams (AtomicType item) = " " defGetTypeParams (BooleanType True) = " " defGetTypeParams (BooleanType False) = " " defGetTypeParams (IntegerType item) = " " defGetTypeParams (ListType item) = " " defGetTypeParams (PairType x y) = " " defGetTypeParams (StringType item) = " "