All pastes #977480 Raw Edit

Someone

public text v1 · immutable
·published 2008-04-08 21:22 UTC
rendered paste body
{- Copyright 2008 Debris <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)   = "<atomic type> " 
defGetTypeParams (BooleanType True)  = "<boolean true> "
defGetTypeParams (BooleanType False) = "<boolean false> "
defGetTypeParams (IntegerType item)  = "<integer> "
defGetTypeParams (ListType item)     = "<list> "
defGetTypeParams (PairType x y)      = "<pair> "
defGetTypeParams (StringType item)   = "<string> "