I’m implementing a toy programming language. I want to write a function that performs a one-step evaluation on a given term and return fancy errors that annotate which part of the term raised the error. How do I do that? So imagine we have an AST:
data Term = If Term Term Term
| IsZero Term
| AddOne Term
| Num Int
| MyTrue | MyFalse
deriving (Show)
and I have a function that evaluates a term and returns a fancy error (String):
eval :: Term -> Either String Term
This function performs one-step evaluation, so it makes a small progress:
eval $ If (IsZero (AddOne (Num 14))) (Num 100) (Num 120)
-- should be:
-- Right (If (IsZero (Num 15)) (Num 100) (Num 120))
and when it fails, it should return an error message that (1) pretty-prints the term, and (2) indicates which part of the term was responsible for the error:
main = do
let res = eval $ If (IsZero (AddOne MyFalse)) (Num 100) (Num 120)
case res of
Right term -> putStrLn $ show term
Left err -> putStrLn err
-- should print:
-- if (iszero (addone myfalse)) 100 120
-- ------- Expected number, got something else
I can implement pretty-printing (1) using prettyprinter. But to do (2), the eval function will need access to the spans for the pretty-printed terms (and any of its subterms). This means I will probably need another datatype that represents terms enriched with their span info:
-- A span is ((beginRow, beginCol), (endRow, endCol))
type Span = ((Int, Int), (Int, Int))
data SpannedTerm = If Span SpannedTerm SpannedTerm SpannedTerm
| IsZero Span SpannedTerm
| AddOne Span SpannedTerm
| Num Span Int
| MyTrue Span | MyFalse Span
deriving (Show)
I will probably need a function that converts a term into a pretty-printed representation and a spanned term:
convert :: Term -> (String, SpannedTerm)>
and the eval function needs to take a spanned term and return a normal term:
eval' :: SpannedTerm -> Either String Term
My problem is: how do we write convert? Does prettyprinter have any kind of feature that would help me get the span information for each (sub)term? On the readme for prettyprinter it says:
More complex uses of annotations include e.g. … adding source locations to show where a certain piece of output comes from. Idris is a project that makes extensive use of such a feature.
So I wonder maybe it is possible to get what I want? Note that these span information cannot come from a parser because (1) eval keeps changing the term and (2) the span information depends on how prettyprinter layouts the term.