packages feed

graft-0.1.0.0: examples/ExactUpdate.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}

-- Run with: cabal run graft-example
-- The tree is written out by hand; Graft has no lexer or parser.
module Main (main) where

import qualified Data.Text as Text
import qualified Data.Text.IO as TextIO
import Graft
import qualified Graft.Ast as Ast
import qualified Graft.Cursor as Cursor
import qualified Graft.Edit as Edit
import qualified Graft.Green as Green
import Text.Read (readMaybe)

data Kind = Root | Expr | Name | Number | Trivia deriving (Eq, Show)

-- In a library, export the view types but not their constructors, so that
-- views are made only by casting.
newtype Expression = Expression (Cursor Kind)
instance AstView Expression where
  type AstViewKind Expression = Kind
  fromCursor = Ast.matchNode (== Expr) Expression
  toCursor (Expression cursor) = cursor
instance AstNode Expression

newtype NumberLiteral = NumberLiteral (Cursor Kind)
instance AstView NumberLiteral where
  type AstViewKind NumberLiteral = Kind
  fromCursor = Ast.matchToken (== Number) NumberLiteral
  toCursor (NumberLiteral cursor) = cursor
instance AstToken NumberLiteral

expressionNumber :: Expression -> Maybe NumberLiteral
expressionNumber = Ast.token . toCursor

-- Graft doesn't interpret token text: reading the value and choosing a new
-- spelling are up to the application. A Number token can still hold text
-- that doesn't parse as a number.
numberValue :: NumberLiteral -> Maybe Integer
numberValue = readMaybe . Text.unpack . Green.render . Cursor.green . toCursor

setNumberValue :: Integer -> NumberLiteral -> Maybe (GreenElement Kind)
setNumberValue value literal = do
  oldValue <- numberValue literal
  let focus = toCursor literal
      tree = Cursor.tree focus
  if oldValue == value
    then pure tree -- Same value: keep the spelling, such as 001.
    else either (const Nothing) Just $
      Edit.apply tree [Edit.replace focus (Token (Green.token Number (Text.pack (show value))))]

updateValue :: Integer -> GreenElement Kind -> Maybe (GreenElement Kind)
updateValue value root = do
  expression <- Ast.child (Cursor.fromGreen root)
  literal <- expressionNumber expression
  setNumberValue value literal

original :: GreenElement Kind
original = Node (Green.node Root
  [ Token (Green.token Name "x")
  , Token (Green.token Trivia "  = ")
  , Node (Green.node Expr [Token (Green.token Number "001")])
  , Token (Green.token Trivia " -- keep\r\n")
  ])

main :: IO ()
main = mapM_ display [Just original, updateValue 1 original, updateValue 2 original]
  where
    display Nothing = fail "Missing expression, missing number, or invalid numeric text"
    display (Just root) = TextIO.putStr (Green.render root)