module TurboDef where import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map import Prelude hiding (fmap, pure, (<*>), (*>), (>>=)) -- Arithmetic expressions with real number (Double) operands. data RealExpr = RLit Double -- literal/constant floating point number | RVar String -- variable | RealExpr :+: RealExpr -- plus | RealExpr :-: RealExpr -- minus | RealExpr :*: RealExpr -- times | RealExpr :/: RealExpr -- divide deriving (Eq, Ord, Read, Show) infixl 6 :+:, :-: infixl 7 :*:, :/: -- Statements in the Turbo language. data Stmt = String := RealExpr -- assignment, the string is var name | PenDown -- set pen to down (touch paper) state | PenUp -- set pen to up (away from paper) state | Turn RealExpr -- turn counterclockwise by given degrees -- negative angle just means clockwise | Forward RealExpr -- move by given distance units (in current direction) -- negative distance just means backward -- trig is involved; also mind degree vs radian -- if pen is down, this means LineTo -- if pen is up, this means MoveTo | Seq [Stmt] -- sequential compound statement. run in given order | For String RealExpr RealExpr [Stmt] -- for var=expr1 to expr2 do ... -- it is up to you whether to evaluate expr2 just once at the beginning -- or re-evaluate every iteration deriving (Eq, Ord, Read, Show) infix 5 := -- Turbo state: -- 1. Dictionary of variables->values. -- It is directly values because for simplicity vars are global. -- 2. Durrent direction (degrees away from x-axis, counterclockwise, e.g., -- 0 points east, 90 points north). -- 3. Pen state (True means touching paper). data TurboMem = TurboMem (Map String Double) Double Bool deriving (Eq, Show) -- Initial Turbo state: No variables set, direction is 0 degrees, pen is up. initTurboMem = TurboMem Map.empty 0 False -- Interpreter model and connectives. data Turbo a = MkTurbo (TurboMem -> (TurboMem, a)) unTurbo :: Turbo a -> TurboMem -> (TurboMem, a) unTurbo (MkTurbo f) = f get :: Turbo TurboMem get = MkTurbo (\s0 -> (s0, s0)) put :: TurboMem -> Turbo () put m = MkTurbo (\_ -> (m , ())) modify :: (TurboMem -> TurboMem) -> Turbo () modify f = get >>= \s -> put (f s) pure :: a -> Turbo a pure a = MkTurbo (\s -> (s, a)) (>>=) :: Turbo a -> (a -> Turbo b) -> Turbo b MkTurbo t >>= k = MkTurbo (\s0 -> case t s0 of (s1, a) -> unTurbo (k a) s1) infixl 1 >>= fmap :: (a -> b) -> Turbo a -> Turbo b fmap f turbo = turbo >>= \a -> pure (f a) liftA2 :: (a -> b -> c) -> Turbo a -> Turbo b -> Turbo c liftA2 f t1 t2 = t1 >>= \a -> t2 >>= \b -> pure (f a b) (<*>) :: Turbo (a -> b) -> Turbo a -> Turbo b (<*>) = liftA2 (\f x -> f x) (*>) :: Turbo a -> Turbo b -> Turbo b (*>) = liftA2 (\_ y -> y) infixl 4 <*>, *> -- Representation of one command for SVG paths. In both cases the two fields -- are for how much to move in the x direction and the y direction. data SVGPathCmd = MoveTo Double Double -- move without drawing | LineTo Double Double -- draw and move deriving (Eq, Ord, Read, Show)