git @ Cat's Eye Technologies Lariat / master impl / Haskell / Main.hs
master

Tree @master (Download .tar.gz)

Main.hs @masterraw · history · blame

-- Copyright (c) 2021-2023 Chris Pressey, Cat's Eye Technologies.
-- Distributed under a 2-clause BSD license.  For more details, see
-- LicenseRef-BSD-2-Clause-X-Lariat.txt in the LICENSES directory.
--
-- SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-Lariat

module Main where

--
-- Test suite for Haskell implementation of Lariat.
-- This "Main" module just runs the tests and exits.
--

import Prelude hiding (abs)
import Data.Char (ord, chr)
import Data.List (nub)
import Data.Lariat (name, var, app, abs, destruct)

--
-- Test helpers
--

-- First, a simple function to express our illustrative tests:
-- Given a list of pairs, show those pairs that are not equal.
-- Anything other than an empty list returned indicates a mistake.

expect [] = []
expect ((a, b):rest) = if (show a) == b then expect rest else ((a, b):expect rest)

--
-- Convenience functions
--

right (Right x) = x
a = name "a"
b = name "b"
c = name "c"
f = name "f"
n = name "n"
m = name "m"
q = name "q"
t = name "t"
x = name "x"
y = name "y"

--
-- Test cases
--

showDestruct t = destruct t
                    (\n   -> "var:" ++ (show n))
                    (\u v -> "app:" ++ (show u) ++ "," ++ (show v))
                    (\u n -> "abs:" ++ (show n) ++ ":" ++ (show u))

testShowDestruct = expect
    [
        (showDestruct (var n),               "\"var:n\""),
        (showDestruct (app (var n) (var m)), "\"app:FreeVar n,FreeVar m\""),
        (showDestruct (abs n (var n)),       "\"abs:y:FreeVar y\"")
    ]


freevars t = nub $ freevars' t [] where
    freevars' t ours = destruct t
                        (\n   -> if elem n ours then [] else [n])
                        (\u v -> freevars' u ours ++ freevars' v ours)
                        (\u n -> freevars' u (n:ours))

testFreeVars = expect
    [
        (freevars (app (abs n (var n)) (var n)),   "[n]"),
        (freevars (abs n (var n)),                 "[]"),
        (freevars (app (var n) (var n)),           "[n]")
    ]


resolve t x = destruct t
                (\n   -> t)
                (\u v -> t)
                (\u n -> replaceAll u n x)

replaceAll t m x = destruct t
                    (\n   -> if n == m then x else (var n))
                    (\u v -> app (replaceAll u m x) (replaceAll v m x))
                    (\u n -> abs n (replaceAll u m x))

testResolve = expect
    [
        (resolve ((app (abs n (var n)) (var n))) (var q), "App (Abs (BoundVar 0)) (FreeVar n)"),
        (resolve (abs n (var m)) (var q),                 "FreeVar m"),
        (resolve (abs n (var n)) (var q),                 "FreeVar q")
    ]


--
-- "beta-reduce a term" example
--

beta t = destruct t
    (\n   -> t)
    (\u v -> destruct u
        (\_   -> app u v)
        (\_ _ -> app u v)
        (\u n -> replaceAll u n v)
    )
    (\_ _ -> t)

testBeta = expect
    [
        (beta (app (abs n (var n)) (var n)),   "FreeVar n"),
        (beta (abs n (var n)),                 "Abs (BoundVar 0)"),
        (beta (var q),                         "FreeVar q")
    ]


isBetaReducible t = destruct t
    (\_   -> False)
    (\u v -> destruct u
        (\_   -> False)
        (\_ _ -> False)
        (\_ _ -> True)
    )
    (\_ _ -> False)

testIsBetaReducible = expect
    [
        (isBetaReducible (app (abs n (var n)) (var n)), "True"),
        (isBetaReducible (abs n (var n)),               "False"),
        (isBetaReducible (var q),                       "False")
    ]


--
-- Returns `Right t'` is the term was rewritten, `Left t` if it was not.
--
reduceOnce t =
    if isBetaReducible t then (Right (beta t)) else destruct t
        (\n   -> Left (var n))
        (\u v ->
            case reduceOnce u of
                Right r -> Right (app r v)
                Left f  ->
                    case reduceOnce v of
                        Right s -> Right (app u s)
                        Left  g -> Left (app f g)
        )
        (\u n -> Left (abs n u))

testReduceOnce = expect
    [
        (reduceOnce (app (abs n (var n)) (var n)),
                    "Right (FreeVar n)"),
        (reduceOnce (abs n (var n)),
                    "Left (Abs (BoundVar 0))"),
        (reduceOnce (var q),
                    "Left (FreeVar q)"),
        (reduceOnce (app (app (abs q (var q)) (abs q (var q))) (var n)),
                    "Right (App (Abs (BoundVar 0)) (FreeVar n))"),
        (reduceOnce (right $ reduceOnce (app (app (abs q (var q)) (abs q (var q))) (var n))),
                    "Right (FreeVar n)"),
        (reduceOnce (app (var n) (app (abs q (var q)) (var m))),
                    "Right (App (FreeVar n) (FreeVar m))")
    ]


--
-- Reduce a term to normal form.
--

normalize t =
    case reduceOnce t of
        Right t' -> normalize t'
        Left  _  -> t


testNormalize = expect
    [
        (normalize (app (abs n (var n)) (var n)),
                   "FreeVar n"),
        (normalize (app
                     (abs x (app (var a) (var x)))
                     (app (abs y (app (var b) (var y))) (var c))
                   ),
                   "App (FreeVar a) (App (FreeVar b) (FreeVar c))"),
        (normalize (app (app (abs x (abs y (app (var y) (var x)))) (var a)) (var b)),
                   "App (FreeVar b) (FreeVar a)"),
        (normalize (app (app (abs x (abs y (app (var y) (var x)))) (var y)) (var x)),
                   "App (FreeVar x) (FreeVar y)"),
        let
            true = (abs t (abs f (var t)))
            false = (abs t (abs f (var f)))
            test = (abs a (abs b (abs c (app (app (var a) (var b)) (var c)))))
        in
            (normalize (app (app (app test true) (var m)) (var n)),
                       "FreeVar m")
    ]




allTests :: String
allTests =
    -- Hugs can only Show tuples up to 5-tuples.  Not 6-tuples.  Thus this nesting:
    case (
           (testShowDestruct, testFreeVars, testResolve, testBeta, testIsBetaReducible),
           (testReduceOnce, testNormalize)
        ) of
        (([],[],[],[],[]), ([], [])) -> "ok"
        other -> error (show other)

main = do
    let x = allTests
    putStrLn x