detect redundant clauses through usefulness analysis
This commit is contained in:
1 file changed
+190
@@ -0,0 +1,190 @@
|
|||||||
|
module Tessera.Serialize where
|
||||||
|
|
||||||
|
import Data.Char (isAlphaNum, isDigit)
|
||||||
|
import Data.List (isPrefixOf)
|
||||||
|
|
||||||
|
import Tessera.Check (checkProgram)
|
||||||
|
import Tessera.Env (Env, lookupConstructor)
|
||||||
|
import Tessera.Matrix (Key (..))
|
||||||
|
import Tessera.Parser (parseProgram)
|
||||||
|
import Tessera.Syntax
|
||||||
|
import Tessera.Tree
|
||||||
|
|
||||||
|
data Artifact = Artifact
|
||||||
|
{ artifactFunction :: String
|
||||||
|
, artifactStrategy :: Strategy
|
||||||
|
, artifactTree :: Tree
|
||||||
|
, artifactSource :: String
|
||||||
|
, artifactProgram :: Program
|
||||||
|
, artifactEnv :: Env
|
||||||
|
}
|
||||||
|
|
||||||
|
formatVersion :: String
|
||||||
|
formatVersion = "1"
|
||||||
|
|
||||||
|
encodeArtifact :: String -> String -> Strategy -> Tree -> String
|
||||||
|
encodeArtifact function source strategy tree = unlines
|
||||||
|
[ "tessera-tree " ++ formatVersion
|
||||||
|
, "function " ++ function
|
||||||
|
, "strategy " ++ strategyName strategy
|
||||||
|
, "tree " ++ encodeTree tree
|
||||||
|
, "source-begin"
|
||||||
|
, source
|
||||||
|
, "source-end"
|
||||||
|
]
|
||||||
|
|
||||||
|
encodeTree :: Tree -> String
|
||||||
|
encodeTree tree = case tree of
|
||||||
|
Success index -> "L" ++ show index
|
||||||
|
Failure -> "X"
|
||||||
|
Bind (Occurrence path) name inner -> "B[" ++ joinIntegers path ++ "|" ++ name ++ "]" ++ encodeTree inner
|
||||||
|
Switch (Occurrence path) branches fallback ->
|
||||||
|
"W[" ++ joinIntegers path ++ "]{"
|
||||||
|
++ concatMap encodeBranch branches
|
||||||
|
++ "}[" ++ maybe "" encodeTree fallback ++ "]"
|
||||||
|
|
||||||
|
encodeBranch :: (Key, Tree) -> String
|
||||||
|
encodeBranch (key, tree) = encodeKey key ++ ":" ++ encodeTree tree ++ ";"
|
||||||
|
|
||||||
|
encodeKey :: Key -> String
|
||||||
|
encodeKey key = case key of
|
||||||
|
KeyCon name -> "c" ++ name
|
||||||
|
KeyInt n -> "i" ++ show n
|
||||||
|
KeyBool b -> if b then "bT" else "bF"
|
||||||
|
KeyTuple n -> "t" ++ show n
|
||||||
|
|
||||||
|
joinIntegers :: [Int] -> String
|
||||||
|
joinIntegers [] = ""
|
||||||
|
joinIntegers [x] = show x
|
||||||
|
joinIntegers (x : rest) = show x ++ "," ++ joinIntegers rest
|
||||||
|
|
||||||
|
decodeArtifact :: String -> Either String Artifact
|
||||||
|
decodeArtifact input = do
|
||||||
|
let numbered = zip [1 ..] (lines input)
|
||||||
|
version <- lookupHeader "tessera-tree" numbered
|
||||||
|
if version /= formatVersion then Left ("unsupported tree format version " ++ version) else Right ()
|
||||||
|
function <- lookupHeader "function" numbered
|
||||||
|
strategyText <- lookupHeader "strategy" numbered
|
||||||
|
strategy <- case strategyText of
|
||||||
|
"leftmost" -> Right Leftmost
|
||||||
|
"heuristic" -> Right Heuristic
|
||||||
|
_ -> Left ("unknown strategy " ++ strategyText)
|
||||||
|
treeText <- lookupHeader "tree" numbered
|
||||||
|
tree <- decodeTree treeText
|
||||||
|
source <- extractSource numbered
|
||||||
|
program <- either (Left . renderDiagnostic) Right (parseProgram source)
|
||||||
|
env <- either (Left . renderDiagnostic) Right (checkProgram program)
|
||||||
|
if function `elem` [name | MatchDeclaration name _ _ _ <- program]
|
||||||
|
then Right ()
|
||||||
|
else Left ("function " ++ function ++ " is not declared in the embedded source")
|
||||||
|
validateTree env tree
|
||||||
|
return (Artifact function strategy tree source program env)
|
||||||
|
|
||||||
|
lookupHeader :: String -> [(Int, String)] -> Either String String
|
||||||
|
lookupHeader key numbered =
|
||||||
|
case [drop (length key + 1) line | (_, line) <- numbered, (key ++ " ") `isPrefixOf` line] of
|
||||||
|
(value : _) -> Right value
|
||||||
|
[] -> Left ("missing " ++ key ++ " header")
|
||||||
|
|
||||||
|
extractSource :: [(Int, String)] -> Either String String
|
||||||
|
extractSource numbered = case break (\(_, line) -> line == "source-begin") numbered of
|
||||||
|
(_, _ : rest) -> case break (\(_, line) -> line == "source-end") rest of
|
||||||
|
(body, _ : _) -> Right (unlines (map snd body))
|
||||||
|
_ -> Left "missing source-end marker"
|
||||||
|
_ -> Left "missing source-begin marker"
|
||||||
|
|
||||||
|
renderDiagnostic :: Diagnostic -> String
|
||||||
|
renderDiagnostic (Diagnostic span' message) =
|
||||||
|
"line " ++ show (spanLine span') ++ " column " ++ show (spanColumn span') ++ ": " ++ message
|
||||||
|
|
||||||
|
decodeTree :: String -> Either String Tree
|
||||||
|
decodeTree input = do
|
||||||
|
(tree, rest) <- parseTree input
|
||||||
|
if null rest then Right tree else Left ("trailing characters in tree encoding " ++ take 20 rest)
|
||||||
|
|
||||||
|
parseTree :: String -> Either String (Tree, String)
|
||||||
|
parseTree ('L' : rest) =
|
||||||
|
let (digits, remaining) = span isDigit rest
|
||||||
|
in if null digits then Left "malformed success node" else Right (Success (read digits), remaining)
|
||||||
|
parseTree ('X' : rest) = Right (Failure, rest)
|
||||||
|
parseTree ('B' : '[' : rest) = do
|
||||||
|
let (inside, afterInside) = break (== ']') rest
|
||||||
|
(path, name) <- case break (== '|') inside of
|
||||||
|
(p, '|' : n) -> Right (p, n)
|
||||||
|
_ -> Left "malformed bind node"
|
||||||
|
(inner, remaining) <- parseTree (drop 1 afterInside)
|
||||||
|
Right (Bind (Occurrence (parseIntegers path)) name inner, remaining)
|
||||||
|
parseTree ('W' : '[' : rest) = do
|
||||||
|
let (path, afterPath) = break (== ']') rest
|
||||||
|
afterPath' <- expect ']' afterPath
|
||||||
|
afterBrace <- expect '{' afterPath'
|
||||||
|
(branches, afterBranches) <- parseBranches afterBrace
|
||||||
|
afterBranches' <- expect '}' afterBranches
|
||||||
|
afterBracket <- expect '[' afterBranches'
|
||||||
|
let (fallbackText, afterFallback) = break (== ']') afterBracket
|
||||||
|
fallback <- if null fallbackText then Right Nothing else do
|
||||||
|
(tree, _) <- parseTree fallbackText
|
||||||
|
Right (Just tree)
|
||||||
|
afterFallback' <- expect ']' afterFallback
|
||||||
|
Right (Switch (Occurrence (parseIntegers path)) branches fallback, afterFallback')
|
||||||
|
parseTree _ = Left "malformed tree encoding"
|
||||||
|
|
||||||
|
expect :: Char -> String -> Either String String
|
||||||
|
expect c (x : rest) | x == c = Right rest
|
||||||
|
expect c _ = Left ("expected " ++ [c])
|
||||||
|
|
||||||
|
parseBranches :: String -> Either String ([(Key, Tree)], String)
|
||||||
|
parseBranches input = go input []
|
||||||
|
where
|
||||||
|
go ('}' : rest) acc = Right (reverse acc, '}' : rest)
|
||||||
|
go (c : rest) acc = do
|
||||||
|
(key, rest1) <- parseKey c rest
|
||||||
|
rest2 <- expect ':' rest1
|
||||||
|
(tree, rest3) <- parseTree rest2
|
||||||
|
rest4 <- expect ';' rest3
|
||||||
|
go rest4 ((key, tree) : acc)
|
||||||
|
go [] _ = Left "unterminated branch list"
|
||||||
|
|
||||||
|
parseKey :: Char -> String -> Either String (Key, String)
|
||||||
|
parseKey 'c' rest = let (name, remaining) = span (\c -> isAlphaNum c || c == '_') rest in Right (KeyCon name, remaining)
|
||||||
|
parseKey 'i' rest = let (digits, remaining) = span isDigit rest in Right (KeyInt (read digits), remaining)
|
||||||
|
parseKey 'b' rest = case rest of
|
||||||
|
('T' : remaining) -> Right (KeyBool True, remaining)
|
||||||
|
('F' : remaining) -> Right (KeyBool False, remaining)
|
||||||
|
_ -> Left "malformed boolean key"
|
||||||
|
parseKey 't' rest = let (digits, remaining) = span isDigit rest in Right (KeyTuple (read digits), remaining)
|
||||||
|
parseKey _ _ = Left "malformed branch key"
|
||||||
|
|
||||||
|
parseIntegers :: String -> [Int]
|
||||||
|
parseIntegers [] = []
|
||||||
|
parseIntegers text = map read (splitOn ',' text)
|
||||||
|
|
||||||
|
splitOn :: Char -> String -> [String]
|
||||||
|
splitOn _ [] = []
|
||||||
|
splitOn c text = case break (== c) text of
|
||||||
|
(piece, []) -> [piece]
|
||||||
|
(piece, _ : rest) -> piece : splitOn c rest
|
||||||
|
|
||||||
|
validateTree :: Env -> Tree -> Either String ()
|
||||||
|
validateTree env = go
|
||||||
|
where
|
||||||
|
go tree = case tree of
|
||||||
|
Success _ -> Right ()
|
||||||
|
Failure -> Right ()
|
||||||
|
Bind (Occurrence path) _ inner -> validatePath path >> go inner
|
||||||
|
Switch (Occurrence path) branches fallback -> do
|
||||||
|
validatePath path
|
||||||
|
mapM_ (validateKey env) (map fst branches)
|
||||||
|
mapM_ (go . snd) branches
|
||||||
|
mapM_ go fallback
|
||||||
|
|
||||||
|
validatePath path = if all (>= 0) path then Right () else Left "negative occurrence path"
|
||||||
|
|
||||||
|
validateKey :: Env -> Key -> Either String ()
|
||||||
|
validateKey env key = case key of
|
||||||
|
KeyCon name -> case lookupConstructor env name of
|
||||||
|
Just _ -> Right ()
|
||||||
|
Nothing -> Left ("unknown constructor in tree " ++ name)
|
||||||
|
KeyInt _ -> Right ()
|
||||||
|
KeyBool _ -> Right ()
|
||||||
|
KeyTuple _ -> Right ()
|
||||||
Reference in new issue
Block a user