packages feed

ion-1.0.0.0: src/Ivory/Language/Ion/Examples/Example.hs

{- |
Module: Example
Description: Example Ion modules & code generation
Copyright: (c) 2015 Chris Hodapp

-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE TypeOperators #-}

module Main where

import           Data.Word

import           Ivory.Language
import           Ivory.Compile.C.CmdlineFrontend

import           Ivory.Language.Ion.Base
import           Ivory.Language.Ion.Code
import           Ivory.Language.Ion.Operators

main :: IO ()
main = do
  let ivoryOpts = initialOpts { scErrors = False
                              , srcLocs = True
                              , outDir = Nothing
                              }
  ionCompile ivoryOpts "simpleSchedule" simpleSchedule
  ionCompile ivoryOpts "timer" exampleTimer
  ionCompile ivoryOpts "exampleChain" exampleChain
  ionCompile ivoryOpts "giant_ugly_test" test
  return ()

printf :: Def ('[IString] :-> Sint32)
printf = importProc "printf" "stdio.h"

-- void foo(int16_t)
foo :: Def ('[] :-> ())
foo = importProc "foo" "something.h"

-- void bar(int32_t)
bar :: Def ('[] :-> ())
bar = importProc "bar" "something.h"

-- uint16_t get_value(int32_t)
get_value :: Def ('[] :-> Uint16)
get_value = importProc "get_value" "something.h"

-- bool get_flag(void)
get_flag :: Def ('[] :-> IBool)
get_flag = importProc "get_flag" "something.h"

simpleSchedule :: Ion ()
simpleSchedule = ion "schedule" $ do
  
  period 100 $ do
    variousPhases

  cond ((>? 10) <$> call get_value) $ do
    ivoryEff $ comment "get_value() > 10"
    cond (call get_flag) $ do
      ivoryEff $ comment "get_value() > 10 && get_flag()"

variousPhases :: Ion ()
variousPhases = do
    phase 1 $ ivoryEff $ do
      comment "period 100, phase 1"
      call_ foo
    phase 10 $ ion "optional_tag" $ ivoryEff $ do
      comment "period 100, phase 10"
      call_ bar
    disable $ phase 20 $ ivoryEff $ do
      comment "shouldn't even appear in code"
      call_ foo
      call_ bar
    delay 50 $ do
      p <- getSched
      ivoryEff $ do
        comment "Should be phase 100 + 50"
        comment ("Reported sched: " ++ show p)
      delay 10 $ ion "moreDelay" $ do
        p <- getSched
        ivoryEff $ do
          comment "Should be phase 100 + 50 + 10"
          comment ("Reported sched: " ++ show p)
      phase 1 $ do
        ivoryEff $ comment "Should override to phase 1"
    period 1000 $ do
      ivoryEff $ comment "Should override all other period"

-- This returns its own entry procedure (init).  The schedule procedure
-- must be called at regular intervals for the timer to function.
exampleTimer :: Ion (Def ('[] ':-> ()))
exampleTimer = ion "timer" $ mdo
  -- Note the use of 'mdo' so that we can define things in a more
  -- logical order.
  
  -- Timer is initialized with a Uint16; procedure called at
  -- expiration is fixed at compile-time:
  timer1 <- period 1 $ timer (Proxy :: Proxy Uint16) expire

  -- Initialization procedure:
  init <- newProc $ body $ do
    -- Trigger the timer for 1000 ticks:
    startTimer timer1 1000
  
  expire <- newProc $ body $ do
    call_ printf "Timer expired!\r\n"

  return init

-- | This is an example of chaining together a variety of calls and
-- async callbacks in continuation-passing style.
exampleChain :: Ion (Def ('[] ':-> ()))
exampleChain = mdo
  let error :: Def ('[Uint32] :-> ())
      error = importProc "assert_error" "foo.h"

  -- Chain together four calls with different values.  The final
  -- call is the 'success' function.
  init <- exampleSend 0x1234 error =<<
          adapt_0_1 =<< exampleSend 0x2345 error =<<
          adapt_0_1 =<< exampleSend 0x3456 error =<<
          adapt_0_1 =<< exampleSend 0x4567 error success
  -- adapt_0_1 is required to match the success callback (which takes a
  -- single Uint16) with the entry function of 'exampleSend' (which takes no
  -- arguments).

  success <- newProc $ \_ -> body $ do
    call_ printf "All calls succeeded!\r\n"

  return init

-- | This definition accepts a payload to transmit, an error callback,
-- and a success callback; it returns the entry function which
-- transmits that value, awaits the async call, and if the result is
-- correct, calls the success callback.  If any of these steps go
-- wrong, it calls the error handler with an error code, and proceeds
-- no further.
exampleSend :: Word16 -- ^ Payload value (or something like that)
               -> Def ('[Uint32] ':-> ()) -- ^ Error callback
               -> Def ('[Uint16] ':-> ()) -- ^ Success callback
               -> Ion (Def ('[] ':-> ()))
exampleSend payload err succ = mdo
  -- Make up a hypothetical function which takes a Uint16 payload to
  -- transmit, and a function pointer to a callback.  It returns a
  -- Uint32 that is an error code.  The function pointer itself takes
  -- a Uint16 which is the value received, and returns nothing.
  let transmit_async :: Def ('[Uint16, ProcPtr ('[Uint16] :-> ())] :-> Uint32)
      transmit_async = importProc "transmit_async" "foo.h"

  write <- newProc $ body $ do
    comment $ "Transmit value: " ++ show payload
    -- Tell transmit_async to transmit this, and call us back at 'recv'
    -- (which we define after):
    errCode <- call transmit_async (fromIntegral payload) $ procPtr recv
    -- Check for a nonzero error code:
    ifte_ (errCode /=? 0)
      (call_ err errCode)
      $ return ()

  recv <- newProc $ \value -> body $ do
    -- Say that hypothetically we should have received the same value
    -- back, so check this first:
    ifte_ (value /=? fromIntegral payload)
      -- If a mismatch, then call the error handler with some code:
      (call_ err 0x12345678)
      -- Otherwise, call the success handler:
      $ call_ succ value

  return write

-- Problems with this spec should be fixed but it's good to have
-- around as an example:
leakageBug :: Ion ()
leakageBug = ion "leakageBug" $ do
  period 200 $ do
    expr <- newProc $ body $ retVoid
    initTimer <- period 1 $ timer (Proxy :: Proxy Uint16) expr
    ion "otherstuff" $ ivoryEff $ do
      comment "Should be period 200 (inherited)"

-- Likewise, problems with this spec should be fixed but it's good to
-- have around as an example:
lostAttribBug :: Ion ()
lostAttribBug = period 200 $ ion "lostAttribBug" $ do
  phase 100 $ ion "moreStuff" $ do
    p <- getSched
    ivoryEff $ do
      comment "Phase 100"
      comment ("Reported sched: " ++ show p)
    delay 3 $ do
      p <- getSched
      ivoryEff $ do
        comment "Should be phase 103"
        comment ("Reported sched: " ++ show p)
      delay 10 $ ion "moreDelay" $ do
        p <- getSched
        ivoryEff $ do
          comment "Should be phase 113"
          comment ("Reported sched: " ++ show p)

baz :: Ion ()
baz = ion "extBaz1" $ phase 10 $ do
  ivoryEff $ comment "should be phase 10"
  phase 20 $ ivoryEff $ comment "should be phase 20"

baz2 :: Ion ()
baz2 = phase 10 $ ion "extBaz2" $ do
  ivoryEff $ comment "should be phase 10"

delayTest :: Ion ()
delayTest = ion "delayTest" $ period 100 $ do
  ivoryEff $ comment "should be phase 0"
  delay 10 $ ion "named" $ ivoryEff $ comment "delay 10 #1"
  delay 10 $ ivoryEff $ comment "delay 10 #2"
  delay 10 $ ivoryEff $ comment "delay 10 #3"
  ion "delayTest2" $ do
    delay 20 $ ivoryEff $ comment "should have inherited delay"

-- | The below does nothing useful, but is left here because it served
-- to illuminate many pesky bugs in Ion.
test :: Ion ()
test = ion "Foo" $ do

  test <- areaP' (Proxy :: Proxy (Stored Uint16)) "testMem" Nothing

  leakageBug

  lostAttribBug

  period 20 $ do
    ivoryEff $ comment "period 20a"
    ivoryEff $ comment "period 20b"
    ivoryEff $ comment "period 20c"
    ivoryEff $ comment "period 20d"
    period 30 $ ivoryEff $ comment "period 30 overwriting 20"

  period 1 $ disable $ do
    ivoryEff $ comment "shouldn't appear in code"
    period 30 $ ivoryEff $ comment "also shouldn't appear in code"
    undefined

  -- Period 1:
  ion "Bar" $ do
    ivoryEff $ comment "Foo.Bar"
    ivoryEff $ comment "Foo.Bar 2"

  ion "Baz" $ period 1500 $ do
    ivoryEff $ comment "Foo.Baz period 15"
    ivoryEff $ comment "Foo.Baz period 15b"

  period 75 $ do
    baz
    baz2

  -- FIXME: delayTest improperly inherits phase 10 from baz2.
  period 100 $ do
    delayTest

  disable $ ion "disabled" $ period 60000 $ do
    ivoryEff $ comment "Should be disabled"

  cond (return false) $ ion "condTest" $ do
    ivoryEff $ comment "Conditional test"
    ion "condTest1" $ ivoryEff $ comment "Conditional test sub 1"
    ion "condTest2" $ ivoryEff $ comment "Conditional test sub 2"
    ion "condTest3" $ ivoryEff $ comment "Conditional test sub 3"
    cond (return true) $ ion "twoConds" $ do
      ivoryEff $ comment "Two conditions"
      ion "condTest4" $ ivoryEff $ comment "Also two conditions"


  cond (return true) $ ion "condTest2" $ do
    ivoryEff $ comment "Should have just one condition"