packages feed

arduino-copilot-1.0.0: src/Copilot/Arduino/Main.hs

module Copilot.Arduino.Main (arduino) where

import Language.Copilot (Spec, interpret, reify)
import Copilot.Compile.C99 (compile)
import Copilot.Arduino.Internals
import System.Directory
import System.Posix.Temp (mkdtemp)
import System.FilePath
import Control.Monad.Writer
import Data.List (isInfixOf)
import Options.Applicative

-- | Typically your Arduino program's main will be implemented using this.
-- For example:
--
-- > {-# LANGUAGE RebindableSyntax #-}
-- > 
-- > import Copilot.Arduino
-- > 
-- > main = arduino $ do
-- >	led =: flashing
-- > 	delay =: constant 100
--
-- Running this program compiles the `Sketch` into C code using copilot, and
-- writes it to a .ino file. That can be built and uploaded to your Arduino
-- using the Arduino IDE, or any other toolchain for Arduino sketches.
--
-- This also supports interpreting a `Sketch`, without loading it onto an
-- Arduino. Run the program with parameters "-i 4" to display what it
-- would do on the first 4 iterations. The output will look something like
-- this:
--
-- > delay:         digitalWrite: 
-- > (100)          (13,false)    
-- > (100)          (13,true)     
-- > (100)          (13,false)    
-- > (100)          (13,true)     
arduino :: Sketch () -> IO ()
arduino (Sketch s) = go =<< execParser opts
  where
	opts = info (parseCmdLine <**> helper)
		( fullDesc
		<> progDesc "Run this program with no options to generate an Arduino sketch."
		)

	go o = case interpretSteps o of
		Just n -> interpret n spec
		Nothing -> writeIno spec (mconcat fs)
	
	(is, fs) = unzip (execWriter s)
	spec = sequence_ is

data CmdLine = CmdLine
	{ interpretSteps :: Maybe Integer
	}


parseCmdLine :: Parser CmdLine
parseCmdLine = CmdLine
	<$> optional (option auto
		( long "interpret"
		<> short 'i'
		<> help "use copilot to interpret the program, displaying what it would do"
		<> metavar "NUM"
		))

writeIno :: Spec -> Framework -> IO ()
writeIno spec framework = do
	-- This could be a lot prettier, unfortunately copilot only exports
	-- an interface that writes the generated code to a file.
	-- And, the .c file includes a .h file that will make it fail to
	-- build when used in the .ino file, so that include has to be
	-- filtered out.
	toptmpdir <- getTemporaryDirectory
	mytmpdir <- mkdtemp (toptmpdir </> "copilot")
	reify spec >>= compile (mytmpdir </> "copilot")
	c <- lines <$> readFile (mytmpdir </> "copilot.c")
	let c' = filter (Prelude.not . isInfixOf "#include \"") c
	-- Use a name for the ino file that will let the Arduino IDE find it.
	d <- getCurrentDirectory
	let dirbase = takeFileName d
	writeFile (addExtension dirbase "ino") $ 
		unlines $ sketchFramework framework c'
	removeDirectoryRecursive mytmpdir