3

I was writing tests for my parser, using a method which might not be the best, but has been working for me so far. The tests assumed perfectly defined AST representation for every code block, like so:

(parse "x = 5") `shouldBe` (Block [Assignment [LVar "x"] [Number 5.0]])

However, when I moved to more complex cases, a need for more "fuzzy" verification arised:

(parse "t.x = 5") `shouldBe` (Block [Assignment [LFieldRef (Var "t") (StringLiteral undefined "x")] [Number 5.0]])

I put in undefined in this example to showcase the field I don't want to be compared to the result of parse (It's a source position of a string literal). Right now the only way of fixing that I see is rewriting the code to make use of shouldSatisfy instead of shouldBe, which I'll have to do if I don't find any other solution.

Bartek Banachewicz
  • 38,596
  • 7
  • 91
  • 135
  • The best way I can think to do this is to extend your type with logic variables and then unify instead of check equality. It's a powerful technique, but a bit non-trivial. If you build your type as a fixed point over a functor then it ought to relatively easy to slide the logic variables in. – J. Abrahamson Sep 17 '14 at 13:59

2 Answers2

1

You can write a normalizePosition function which replaces all the position data in your AST with some fixed dummyPosition value, and then use shouldBe against a pattern built from the same dummy value.

If the AST is very involved, consider writing this normalization using Scrap-Your-Boilerplate.

chi
  • 111,837
  • 3
  • 133
  • 218
  • One disadvantage with this approach is that it is quite possible to write a buggy version of `normalizePosition` that still type checks (esp. when you use `syb`). Parameterizing over the location type and using a `Functor` instance solves this problem and is less work (assuming you can derive the instances). – Simon Hengel Nov 01 '14 at 05:20
0

One way to solve this is to parametrize your AST over source locations:

{-# LANGUAGE DeriveFunctor #-}

data AST a = ...
  deriving (Eq, Show, Functor)

Your parse function would then return an AST with SourceLocations:

parse :: String -> AST SourceLocation

As we derived a Functor instance above, we can easily replace source locations with something else, e.g. ():

import Data.Functor ((<$))

parseTest :: String -> AST ()
parseTest input = () <$ parse input

Now, just use parseTest instead of parse in your specs.

Simon Hengel
  • 411
  • 3
  • 7