-- Implementation of Quowong Liu's esoteric language "conser" in Haskell 98.
-- Version 0.0. Copyright 2002 Ben Rudiak-Gould. Released under the GPL.
import List
import Char
main = interact conser
conser = decodeOutput . run . compile . functionSplit . tokenize
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
data Pair = Pair Set Set deriving (Eq, Show)
type Set = [Pair]
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
opCar = '<'
opCdr = '>'
opOpen = '('
opClose = ')'
opCons = ','
opXn = '&'
opDiff = '!'
opIs = '='
opEnd = '.'
operators = [opCar,opCdr,opOpen,opClose,opCons,opXn,opDiff,opIs,opEnd]
comment = '"'
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
tokenize :: String -> [String]
tokenize [] = []
tokenize (ch:rest) =
if isSpace ch then
tokenize rest
else if ch `elem` operators then
([ch] : tokenize rest)
else if ch == comment then
tokenize (tail (dropWhile (/= comment) rest))
else
let (token,rest2) = break (\ch -> (isSpace ch) || (ch `elem` operators) || (ch == comment)) rest
in ((ch : token) : tokenize rest2)
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
type AssocList a b = [(a,b)]
type StringAssoc b = AssocList String b
assoclistLookup :: Eq a => AssocList a b -> a -> Maybe b
assoclistLookup al key =
case find ((== key) . fst) al of
Nothing -> Nothing
Just (key,value) -> Just value
assoclistMap :: (a->b->c) -> AssocList a b -> AssocList a c
assoclistMap f [] = []
assoclistMap f ((k,v):kvs) = (k,f k v) : assoclistMap f kvs
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
type RawFunction = ([String],[String]) -- params, code
type CompiledFunction = [Pair]->Set
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
parseFatal func msg =
error ("In function '" ++ func ++ "': " ++ msg)
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
functionSplit :: [String] -> StringAssoc RawFunction
functionSplit [] = []
functionSplit (name:rest) =
let (params,is_body) = span (/= [opIs]) rest in
if null is_body then
parseFatal name "no '=' found before end of file"
else
let (body,rest) = span (/= [opEnd]) (tail is_body) in
if null rest then
parseFatal name "no ':' found before end of file"
else
(name,(params,body)) : functionSplit (tail rest)
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
compile :: StringAssoc RawFunction -> StringAssoc CompiledFunction
compile funcs =
let compiledFuncs = assoclistMap (\k v -> (encode compiledFuncs (parse funcs k v))) funcs
in compiledFuncs
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
run :: StringAssoc CompiledFunction -> Set
run compiledFuncs =
case assoclistLookup compiledFuncs "main" of
Nothing -> error "No main function"
Just f -> f []
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
decodeOutput :: Set -> String
decodeOutput [] = ""
decodeOutput (x:[]) = decodeOutputPair x
decodeOutput (x:xs) = (decodeOutputPair x) ++ " " ++ (decodeOutput xs)
decodeOutputPair (Pair x y) = "(" ++ (decodeOutput x) ++ "," ++ (decodeOutput y) ++ ")"
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
data CExpr = CArgRef Int
| CFuncall String [CExpr]
| CUnion [CExpr]
| CIntersect CExpr CExpr
| CDifference CExpr CExpr
| CCons CExpr CExpr
| CCar CExpr
| CCdr CExpr
deriving Show
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
parse :: StringAssoc RawFunction -> String -> RawFunction -> CExpr
parse funcs name (params,body) =
let (ast, remainingInput) = parseTerm funcs name params body in
case remainingInput of
[] -> ast
x:xs -> parseFatal name ("unexpected token '" ++ x ++ "' after end of function")
parseTerm :: StringAssoc RawFunction -> String -> [String] -> [String] -> (CExpr, [String])
parseTerm funcs name params [] = parseFatal name "unexpected end of function (expected a term)"
parseTerm funcs name params (x:xs) =
-- (...)
if x == [opOpen] then
parseParenthesized funcs name params xs
-- car and cdr
else if x == [opCar] then
let (ast, remainingInput) = parseTerm funcs name params xs in
(CCar ast, remainingInput)
else if x == [opCdr] then
let (ast, remainingInput) = parseTerm funcs name params xs in
(CCdr ast, remainingInput)
-- reference to formal parameter
else case elemIndex x params of
Just i -> (CArgRef i, xs)
Nothing ->
-- reference to function
case assoclistLookup funcs x of
Just f -> parseFuncall funcs name params x (length (fst f)) xs
Nothing -> parseFatal name ("unexpected token '" ++ x ++ "' (expected a term)")
parseParenthesized funcs name params input =
let (union1, input2) = parseUnion funcs name params input in
case input2 of
[] -> parseFatal name "Unexpected end of function in parenthesized expression"
(x:xs) ->
if x == [opClose] then
(union1, xs)
else if x `elem` [[opCons],[opXn],[opDiff]] then
let (union2, input3) = parseUnion funcs name params xs in
if null input3 || head input3 /= [opClose] then
parseFatal name "missing ')' after cons/intersect/difference operation"
else
(if x == [opCons] then CCons union1 union2
else if x == [opXn] then CIntersect union1 union2
else {-if x == [opDiff] then-} CDifference union1 union2
, tail input3)
else
parseFatal name "missing ')' after union operation"
parseFuncall funcs name params subname numArgs input =
let (args, remainingInput) = parseMultiTerm funcs name params input numArgs
in (CFuncall subname args, remainingInput)
parseMultiTerm funcs name params input 0 = ([], input)
parseMultiTerm funcs name params input n =
let (firstTerm, input2) = parseTerm funcs name params input
(remainingTerms, input3) = parseMultiTerm funcs name params input2 (n-1)
in (firstTerm:remainingTerms, input3)
parseUnion funcs name params [] = (CUnion [], [])
parseUnion funcs name params input@(x:xs) =
if x `elem` [[opClose],[opCons],[opXn],[opDiff]] then
(CUnion [], input)
else
let (firstTerm, input2) = parseTerm funcs name params input
(CUnion remainingTerms, input3) = parseUnion funcs name params input2
in (CUnion (firstTerm:remainingTerms), input3)
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
encode :: StringAssoc CompiledFunction -> CExpr -> CompiledFunction
encode funcs (CArgRef n) = \args -> [args !! n]
encode funcs (CFuncall name subargs) = \args ->
let Just func = assoclistLookup funcs name
compiledArgs = map (($ args).(encode funcs)) subargs
in foldl union [] (map func (multiCartesianProduct compiledArgs))
encode funcs (CUnion []) = \args -> []
encode funcs (CUnion (x:xs)) = \args ->
union (encode funcs x args) (encode funcs (CUnion xs) args)
encode funcs (CIntersect s t) = \args ->
intersect (encode funcs s args) (encode funcs t args)
encode funcs (CDifference s t) = \args ->
(encode funcs s args) \\ (encode funcs t args)
encode funcs (CCons s t) = \args ->
[Pair (encode funcs s args) (encode funcs t args)]
encode funcs (CCar s) = \args->
foldl union [] (map car (encode funcs s args))
where car (Pair p q) = p
encode funcs (CCdr s) = \args->
foldl union [] (map cdr (encode funcs s args))
where cdr (Pair p q) = q
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
cartesianProduct :: [a] -> [[a]] -> [[a]]
cartesianProduct [] _ = []
cartesianProduct _ [] = []
cartesianProduct (x:xs) ys =
(map (x:) ys) ++ (cartesianProduct xs ys)
multiCartesianProduct = foldr cartesianProduct [[]]
               (
geocities.com/qpliu)