rail-compiler-editor-0.3.0.0: src/RailCompiler/InstructionPointer.hs
{- |
Module : Instruction Pointer
Description : Helper module for Lexer to actually move on code.
Maintainer : Christian H. et al.
License : MIT
-}
module InstructionPointer (
-- constructors
start, crash, crashfrom, ipmerge, IP(IP),
RelDirection(Left, Right, Forward),
Direction (N, NE, E, SE, S, SW, W, NW),
-- movement
junctionturns, lambdadirs, move, turnaround,
-- attribute/pseudoattribute functions
dir, nodecount, posx, posy, count, path, visited,
-- other
posdir, absolute, nodeadd, turn, current, charat
)
where
import InterfaceDT as IDT
import Data.Maybe
import qualified Data.Map as Map
-- |An absolute direction.
data Direction = N | NE | E | SE | S | SW | W | NW deriving (Ord, Eq, Show)
-- |A relative direction.
data RelDirection = Left | Forward | Right deriving (Eq, Show)
-- |Instruction pointer consisting of position and an orientation.
data IP =
IP {
-- |Number of processed characters since start of current function.
count :: Int,
-- |Current X position.
posx :: Int,
-- |Current Y position.
posy :: Int,
-- |Current 'Direction'.
dir :: Direction,
-- |Determines if the instruction pointer is on a left or right path of a Junction
path :: RelDirection,
-- |Map of Position and Direction to Id
known :: Map.Map (Int, Int, Direction) Int
}
deriving (Show)
instance Eq IP
where
(==) ipl ipr = posx ipl == posx ipr && posy ipl == posy ipr && dir ipl == dir ipr
-- |Initial value for the instruction pointer at the start of a function.
start :: IP
start = IP 0 0 0 SE Forward (Map.singleton (0, 0, SE) 1)
-- |An instruction pointer representing a "crash" (fatal error).
crash :: IP
crash = IP 0 (-1) (-1) NW Forward Map.empty
crashfrom :: IP -> IP
crashfrom = ipmerge crash
ipmerge :: IP -> IP -> IP
ipmerge ipl ipr = ipl{known = known ipr}
turn :: Char -> IP -> IP
turn '@' ip = turnaround ip
turn '|' ip
| dir ip `elem` [NW, N, NE] = ip{dir = N}
| dir ip `elem` [SW, S, SE] = ip{dir = S}
turn '/' ip
| dir ip `elem` [N, NE, E] = ip{dir = NE}
| dir ip `elem` [S, SW, W] = ip{dir = SW}
turn '-' ip
| dir ip `elem` [NE, E, SE] = ip{dir = E}
| dir ip `elem` [SW, S, NW] = ip{dir = W}
turn '\\' ip
| dir ip `elem` [W, NW, N] = ip{dir = NW}
| dir ip `elem` [E, SE, S] = ip{dir = SE}
turn _ ip = ip
-- |Move the instruction pointer in a relative direction.
move :: IP -- ^Current instruction pointer.
-> RelDirection -- ^Relative direction to move in.
-> IP -- ^New instruction pointer.
move ip reldir = ip{count = newcount, posx = newx, posy = newy, dir = absolute ip reldir}
where
(newy, newx) = posdir ip reldir
newcount = count ip + 1
-- |Get the 'Char' at the current position of the instruction pointer.
current :: IDT.Grid2D -- ^Line representation of the current function.
-> IP -- ^Current instruction pointer.
-> Char -- ^'Char' at the current IP position.
current code ip = charat code (posy ip, posx ip)
-- |Returns 'Char' at given position, @\' \'@ if position is invalid.
charat :: IDT.Grid2D -- ^Line representation of current function.
-> (Int, Int) -- ^Position as (x, y) coordinate.
-> Char -- ^'Char' at given position.
charat code _ | Map.size code == 0 = ' '
charat code (y, _) | isNothing (Map.lookup y code) = ' '
charat code (y, x)
| isNothing (Map.lookup x line) = ' '
| otherwise = fromJust (Map.lookup x line)
where
line = fromJust (Map.lookup y code)
-- returns instruction pointers turned for (False, True)
junctionturns :: IDT.Grid2D -> IP -> (IP, IP)
junctionturns code ip = tuplecheck $ tuplemove $ addpath $ turning (current code ip) ip
where
check ip = if current code ip == primary ip then ip else crashfrom ip
tuplecheck (ipl, ipr) = (check ipl, check ipr)
turning char ip
| char == '<' = case dir ip of
E -> (ip{dir = NE}, ip{dir = SE})
SW -> (ip{dir = SE}, ip{dir = W})
NW -> (ip{dir = W}, ip{dir = NE})
_ -> (crashfrom ip, crashfrom ip)
| char == '>' = case dir ip of
W -> (ip{dir = SW}, ip{dir = NW})
SE -> (ip{dir = E}, ip{dir = SW})
NE -> (ip{dir = NW}, ip{dir = E})
_ -> (crashfrom ip, crashfrom ip)
| char == '^' = case dir ip of
S -> (ip{dir = SE}, ip{dir = SW})
NE -> (ip{dir = N}, ip{dir = SE})
NW -> (ip{dir = SW}, ip{dir = N})
_ -> (crashfrom ip, crashfrom ip)
| char == 'v' = case dir ip of
N -> (ip{dir = NW}, ip{dir = NE})
SE -> (ip{dir = NE}, ip{dir = S})
SW -> (ip{dir = S}, ip{dir = NW})
_ -> (crashfrom ip, crashfrom ip)
| otherwise = (ip, ip)
-- returns insturction pointers turned for (Lambda, Reflected)
lambdadirs :: IP -> (IP, IP)
lambdadirs ip = addpath (ip, turnaround ip)
-- moves both pointers one step
tuplemove :: (IP, IP) -> (IP, IP)
tuplemove (ipl, ipr) = (move ipl Forward, move ipr Forward)
nodecount :: IP -> Int
nodecount ip = Map.size (known ip)
nodeadd :: IP -> Int -> IP
nodeadd ip newnode = ip{count = 0, known = Map.insert (posx ip, posy ip, dir ip) newnode (known ip)}
-- saves path information in instruction pointers
addpath :: (IP, IP) -> (IP, IP)
addpath (ipl, ipr) = (ipl{path = InstructionPointer.Left}, ipr{path = InstructionPointer.Right})
-- make a 180° turn on instruction pointer
turnaround :: IP -> IP
turnaround ip = ip{dir = absolute ip{dir = absolute ip{dir = absolute ip{dir = absolute ip InstructionPointer.Left} InstructionPointer.Left} InstructionPointer.Left} InstructionPointer.Left}
-- |Get ID of the node that has been already visited using the current IP
-- (direction and coordinates).
visited :: IP -- ^Instruction pointer to use.
-> Int -- ^ID of visited node or 0 if none.
visited ip = Map.findWithDefault 0 (posx ip, posy ip, dir ip) (known ip)
-- |Get the position of a specific heading.
posdir :: IP -- ^Current instruction pointer.
-> RelDirection -- ^Current relative direction.
-> (Int, Int) -- ^New position that results from the given relative movement.
posdir ip reldir = posabsdir ip (absolute ip reldir)
-- |Get the position of an absolute direction.
posabsdir :: IP -- ^Current instruction pointer.
-> Direction -- ^Current absolute direction.
-> (Int, Int) -- ^New position that results from the given absolute movement.
posabsdir ip N = (posy ip - 1, posx ip)
posabsdir ip NE = (posy ip - 1, posx ip + 1)
posabsdir ip E = (posy ip, posx ip + 1)
posabsdir ip SE = (posy ip + 1, posx ip + 1)
posabsdir ip S = (posy ip + 1, posx ip)
posabsdir ip SW = (posy ip + 1, posx ip - 1)
posabsdir ip W = (posy ip, posx ip - 1)
posabsdir ip NW = (posy ip - 1, posx ip - 1)
-- |Convert a relative direction into a relative one.
absolute :: IP -- ^Current instruction pointer.
-> RelDirection -- ^Relative direction to convert.
-> Direction -- ^Equivalent absolute direction.
absolute x Forward = dir x
absolute (IP {dir=N}) InstructionPointer.Left = NW
absolute (IP {dir=N}) InstructionPointer.Right = NE
absolute (IP {dir=NE}) InstructionPointer.Left = N
absolute (IP {dir=NE}) InstructionPointer.Right = E
absolute (IP {dir=E}) InstructionPointer.Left = NE
absolute (IP {dir=E}) InstructionPointer.Right = SE
absolute (IP {dir=SE}) InstructionPointer.Left = E
absolute (IP {dir=SE}) InstructionPointer.Right = S
absolute (IP {dir=S}) InstructionPointer.Left = SE
absolute (IP {dir=S}) InstructionPointer.Right = SW
absolute (IP {dir=SW}) InstructionPointer.Left = S
absolute (IP {dir=SW}) InstructionPointer.Right = W
absolute (IP {dir=W}) InstructionPointer.Left = SW
absolute (IP {dir=W}) InstructionPointer.Right = NW
absolute (IP {dir=NW}) InstructionPointer.Left = W
absolute (IP {dir=NW}) InstructionPointer.Right = N
-- what is the primary rail for the given direction?
-- mainly used to check if junctions turn away correctly
primary :: IP -> Char
primary ip
| dir ip `elem` [N, S] = '|'
| dir ip `elem` [E, W] = '-'
| dir ip `elem` [NE, SW] = '/'
| dir ip `elem` [NW, SE] = '\\'
-- vim:ts=2 sw=2 et