Skip to content

Commit 2cd3773

Browse files
authored
Add verification pass. (#23)
1 parent aade4b2 commit 2cd3773

File tree

5 files changed

+102
-6
lines changed

5 files changed

+102
-6
lines changed

lib/prelude.eu

+3-3
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ with-meta: __WITHMETA
392392
export: :suppress
393393
associates: :left
394394
precedence: :meta }
395-
(e // m): v with-meta(m)
395+
(e // m): e with-meta(m)
396396

397397
` { doc: "`meta e` - retrieve expression metadata for e"
398398
export: :suppress }
@@ -514,7 +514,7 @@ zip-with: map2
514514

515515
` { doc: "`zip-apply(fs, vs)` - apply functions in list `fs` to corresponding values in list `vs`, stopping when shorter list is exhausted."
516516
export: :suppress }
517-
zip-apply(fs, vs): zip-with(apply1, fs, vs)
517+
zip-apply(fs, vs): zip-with(cat, vs, fs)
518518

519519
` { doc: "`reverse(l) - reverse list `l`"
520520
example: reverse([1,2,3]) //=> [3,2,1]
@@ -614,7 +614,7 @@ by-value(p?): p? ∘ value
614614
select-items(f, b): b elements filter(f)
615615

616616
` "`match-select-values` - return list of values from block `b` with keys matching regex `re`."
617-
match-select-values(re, b): b select(by-key-match(re)) map(value)
617+
match-select-values(re, b): b select-items(by-key-match(re)) map(value)
618618

619619
` "`select-values(p?, b)` - return items from block `b` where values match predicate `p?`"
620620
select-values(p?, b): b map(value) filter(p?)

src/Eucalypt/Core/Error.hs

+4
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ data EvaluationError
5252
| Panic String CoreExpShow
5353
| Bug String CoreExpShow
5454
| EmptyList CoreExpShow
55+
| VerifyOperatorsFailed CoreExpShow
56+
| VerifyUnresolvedVar CoreBindingName
5557
| NoSource
5658
| AssertionFailed CoreExpShow
5759

@@ -86,6 +88,8 @@ instance Show EvaluationError where
8688
show (InvalidArgConvention (CoreExpShow exprs)) = "Args passed incorrectly. " ++ pprint exprs
8789
show (UnexpectedEndOfExpression (CoreExpShow expr)) = "UnexpectedEndOfExpression after " ++ pprint expr
8890
show (EmptyList (CoreExpShow expr)) = "List was empty in " ++ pprint expr
91+
show (VerifyOperatorsFailed (CoreExpShow expr)) = "Unresolved operator in " ++ pprint expr
92+
show (VerifyUnresolvedVar name) = "Unresolved variable in " ++ name
8993
show (Panic message (CoreExpShow expr)) = "Panic - " ++ message ++ " in " ++ pprint expr
9094
show (Bug message (CoreExpShow expr)) = "BUG! " ++ message ++ " - " ++ pprint expr
9195
show (AssertionFailed (CoreExpShow expr)) = "Assertion Failed. " ++ pprint expr

src/Eucalypt/Core/Verify.hs

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{-# LANGUAGE RankNTypes #-}
2+
{-|
3+
Module : Eucalypt.Core.Verify
4+
Description : Checks to run prior to evaluation
5+
Copyright : (c) Greg Hawkins, 2018
6+
License :
7+
Maintainer : [email protected]
8+
Stability : experimental
9+
-}
10+
11+
module Eucalypt.Core.Verify where
12+
13+
import Bound
14+
import Data.Foldable
15+
import Eucalypt.Core.Error
16+
import Eucalypt.Core.Syn
17+
18+
-- | Run all the check functions throughout the tree
19+
runChecks :: Show b => CoreExp b -> [EvaluationError]
20+
runChecks expr = verify noSoup expr ++ map (VerifyUnresolvedVar . show) (toList expr)
21+
22+
-- | Apply a check function to every level in the syntax tree
23+
verify ::
24+
Show b
25+
=> (forall a. Show a =>
26+
(CoreExp a -> [EvaluationError]))
27+
-> CoreExp b
28+
-> [EvaluationError]
29+
verify f e@(CoreLet bs b) =
30+
let shallow = f e
31+
deep = foldMap id (verify f (unscope b) : map (verify f . unscope . snd) bs)
32+
in shallow ++ deep
33+
verify f e@(CoreLambda _ b) =
34+
let shallow = f e
35+
deep = verify f (unscope b)
36+
in shallow ++ deep
37+
verify f e@(CoreMeta m expr) =
38+
f e ++ verify f m ++ verify f expr
39+
verify f e@(CoreTraced expr) =
40+
f e ++ verify f expr
41+
verify f e@(CoreChecked ck expr) =
42+
f e ++ verify f ck ++ verify f expr
43+
verify f e@(CoreBlock expr) =
44+
f e ++ verify f expr
45+
verify f e@(CoreList exprs) =
46+
f e ++ concatMap (verify f) exprs
47+
verify f e@(CoreArgTuple exprs) =
48+
f e ++ concatMap (verify f) exprs
49+
verify f e@(CoreOperator _ _ expr) =
50+
f e ++ verify f expr
51+
verify f e = f e
52+
53+
noSoup :: Show a => CoreExp a -> [EvaluationError]
54+
noSoup o@(CoreOpSoup _) = [(VerifyOperatorsFailed . CoreExpShow) o]
55+
noSoup _ = []

src/Eucalypt/Driver/Evaluator.hs

+8-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ where
44

55
import Control.Applicative ((<|>))
66
import Control.Exception.Safe (try)
7-
import Control.Monad (forM_, when)
7+
import Control.Monad (forM_, when, unless)
88
import Data.Bifunctor
99
import qualified Data.ByteString as BS
1010
import Data.Either (partitionEithers)
@@ -23,6 +23,7 @@ import Eucalypt.Core.Pretty
2323
import Eucalypt.Core.Syn
2424
import Eucalypt.Core.Target
2525
import Eucalypt.Core.Unit
26+
import Eucalypt.Core.Verify
2627
import Eucalypt.Driver.Error (CommandError(..))
2728
import Eucalypt.Driver.IOSource (prepareIOUnit)
2829
import Eucalypt.Driver.Input (Input(..), InputMode(..), Locator(..))
@@ -237,20 +238,24 @@ evaluate opts whnfM = do
237238
when (cmd == ListTargets)
238239
(listTargets opts targets >> exitSuccess)
239240

240-
-- Stage 5: form an expression to evaluate from the source or
241+
-- Stage 4: form an expression to evaluate from the source or
241242
-- command line and embed it in the core tree
242243
evaluand <- formEvaluand opts targets core
243244
when (cmd == DumpEvalSubstituted)
244245
(putStrLn (pprint evaluand) >> exitSuccess)
245246

246-
-- Stage 6: cook operator soups to resolve all fixities and prepare
247+
-- Stage 5: cook operator soups to resolve all fixities and prepare
247248
-- a final tree for evaluation
248249
cookedEvaluand <- runFixityPass evaluand
249250
when (cmd == DumpCooked)
250251
(putStrLn (pprint cookedEvaluand) >> exitSuccess)
251252
when (cmd == DumpFinalCore)
252253
(putStrLn (pprint cookedEvaluand) >> exitSuccess)
253254

255+
-- Stage 6: run final checks
256+
let failures = runChecks cookedEvaluand
257+
unless (null failures) $ reportErrors failures >> exitFailure
258+
254259
-- Stage 7: drive the evaluation by rendering it
255260
render cookedEvaluand >>= \case
256261
Left s -> reportErrors [s] >> return (ExitFailure 1)

test/Eucalypt/Core/VerifySpec.hs

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
module Eucalypt.Core.VerifySpec
2+
( main
3+
, spec
4+
) where
5+
6+
import Eucalypt.Core.Syn
7+
import Eucalypt.Core.Verify
8+
import Test.Hspec
9+
10+
main :: IO ()
11+
main = hspec spec
12+
13+
spec :: Spec
14+
spec = do
15+
describe "Soup checks" $ do
16+
it "discovers soup" $
17+
runChecks (lam ["x", "y"] $ soup [var "x", var "+", var "y"])
18+
`shouldSatisfy`
19+
(not . null)
20+
it "passes fully cooked expressions" $
21+
runChecks (lam ["x", "y"] $ app (bif "ADD") [var "x", var "y"])
22+
`shouldSatisfy`
23+
null
24+
describe "Closed checks" $ do
25+
it "discovers free vars" $
26+
runChecks (lam ["x", "y", "+"] $ soup [var "x", var "+", var "s"])
27+
`shouldSatisfy`
28+
(not . null)
29+
it "passes closed expressions" $
30+
runChecks (lam ["x", "y", "+"] $ app (bif "ADD") [var "x", var "y"])
31+
`shouldSatisfy`
32+
null

0 commit comments

Comments
 (0)