module Data.Kicad.PcbnewExpr.Parse
( parse
, parseWithFilename
, fromSExpr
)
where
import Data.Either
import Data.Maybe
import Lens.Family2 (over)
import Data.List (intersperse)
import Text.Read (readMaybe)
import Text.Parsec.Pos (newPos)
import Control.Applicative ((<$>), (<*>))
import Data.Kicad.SExpr hiding (parse, parseWithFilename)
import qualified Data.Kicad.SExpr as SExpr (parseWithFilename)
import Data.Kicad.PcbnewExpr.PcbnewExpr
import Data.Kicad.Util (headOr)
parse :: String -> Either String PcbnewExpr
parse = parseWithFilename ""
parseWithFilename :: String -> String -> Either String PcbnewExpr
parseWithFilename filename =
either Left fromSExpr . SExpr.parseWithFilename filename
fromSExpr :: SExpr -> Either String PcbnewExpr
fromSExpr (List _ (Atom pos kw:sxs)) = case kw of
"module" -> PcbnewExprModule <$> asPcbnewModule sxs
"pad" -> PcbnewExprItem <$> asPcbnewPad sxs
"fp_text" -> PcbnewExprItem <$> asPcbnewFpText sxs
"fp_arc" -> PcbnewExprItem <$> asPcbnewFpArc sxs
"fp_poly" -> PcbnewExprItem <$> asPcbnewFpPoly sxs
"layer" -> PcbnewExprAttribute <$> asPcbnewLayer sxs
"at" -> PcbnewExprAttribute <$> asPcbnewAt sxs
"effects" -> PcbnewExprAttribute <$> asPcbnewEffects sxs
"font" -> PcbnewExprAttribute <$> asPcbnewFont sxs
"layers" -> PcbnewExprAttribute <$> asPcbnewLayers sxs
"pts" -> PcbnewExprAttribute <$> asPcbnewPts sxs
"xyz" -> PcbnewExprAttribute <$> asPcbnewXyz sxs
"model" -> PcbnewExprAttribute <$> asPcbnewModel sxs
"drill" -> PcbnewExprAttribute <$> asPcbnewDrill sxs
"size" -> PcbnewExprAttribute <$> asXy PcbnewSize sxs
"start" -> PcbnewExprAttribute <$> asXy PcbnewStart sxs
"end" -> PcbnewExprAttribute <$> asXy PcbnewEnd sxs
"center" -> PcbnewExprAttribute <$> asXy PcbnewCenter sxs
"rect_delta" -> PcbnewExprAttribute <$> asXy PcbnewRectDelta sxs
"xy" -> PcbnewExprAttribute <$> asXy PcbnewXy sxs
"offset" -> PcbnewExprAttribute <$> asXy PcbnewOffset sxs
"scale" -> PcbnewExprAttribute <$> asXyz PcbnewModelScale sxs
"rotate" -> PcbnewExprAttribute <$> asXyz PcbnewModelRotate sxs
"descr" -> PcbnewExprAttribute <$> asString PcbnewDescr sxs
"tags" -> PcbnewExprAttribute <$> asString PcbnewTags sxs
"path" -> PcbnewExprAttribute <$> asString PcbnewPath sxs
"attr" -> PcbnewExprAttribute <$> asString PcbnewAttr sxs
"tedit" -> PcbnewExprAttribute <$> asString PcbnewTedit sxs
"angle" -> PcbnewExprAttribute <$> asDouble PcbnewAngle sxs
"thickness" -> PcbnewExprAttribute <$> asDouble PcbnewThickness sxs
"width" -> PcbnewExprAttribute <$> asDouble PcbnewWidth sxs
"justify" -> PcbnewExprAttribute <$> asPcbnewJustifyT sxs
"thermal_gap"
-> PcbnewExprAttribute <$> asDouble PcbnewThermalGap sxs
"thermal_width"
-> PcbnewExprAttribute <$> asDouble PcbnewThermalWidth sxs
"solder_paste_margin_ratio"
-> PcbnewExprAttribute <$> asDouble PcbnewPasteMarginRatio sxs
"solder_paste_margin"
-> PcbnewExprAttribute <$> asDouble PcbnewPasteMargin sxs
"solder_mask_margin"
-> PcbnewExprAttribute <$> asDouble PcbnewMaskMargin sxs
"clearance"
-> PcbnewExprAttribute <$> asDouble PcbnewClearance sxs
"solder_paste_ratio"
-> PcbnewExprAttribute <$> asDouble PcbnewSolderPasteRatio sxs
"fp_line"
-> PcbnewExprItem <$> asFp defaultPcbnewFpLine sxs
"fp_circle"
-> PcbnewExprItem <$> asFp defaultPcbnewFpCircle sxs
"autoplace_cost180"
-> PcbnewExprAttribute <$> asInt PcbnewAutoplaceCost180 sxs
"autoplace_cost90"
-> PcbnewExprAttribute <$> asInt PcbnewAutoplaceCost90 sxs
"zone_connect"
-> PcbnewExprAttribute <$> asInt PcbnewZoneConnect sxs
"roundrect_rratio"
-> PcbnewExprAttribute <$> asDouble PcbnewRoundrectRratio sxs
"die_length"
-> PcbnewExprAttribute <$> asDouble PcbnewDieLength sxs
_ -> Left $ "Error in " ++ (show pos) ++ ": unknown expression type '" ++ kw ++ "'"
fromSExpr sx@(Atom _ s) = case s of
"italic" -> Right $ PcbnewExprAttribute PcbnewItalic
"hide" -> Right $ PcbnewExprAttribute PcbnewHide
"locked" -> Right $ PcbnewExprAttribute PcbnewLocked
"placed" -> Right $ PcbnewExprAttribute PcbnewPlaced
_ -> expecting "'italic' or 'hide' or 'locked' " sx
fromSExpr x = expecting "List _ with a key or a string atom" x
asPcbnewModule :: [SExpr] -> Either String PcbnewModule
asPcbnewModule (Atom _ n:xs) =
interpretRest xs defaultPcbnewModule { pcbnewModuleName = n }
where
interpretRest [] m = Right m
interpretRest (sx:sxs) m = case fromSExpr sx of
Left err -> Left err
Right (PcbnewExprAttribute (PcbnewLayer layer)) ->
interpretRest sxs m {pcbnewModuleLayer = layer}
Right (PcbnewExprItem item) ->
interpretRest sxs (over moduleItems (++[item]) m)
Right (PcbnewExprAttribute attr) ->
interpretRest sxs (over moduleAttrs (++[attr]) m)
Right _ -> expecting "layer, items or attributes" sx
asPcbnewModule (x:_) = expecting "module name" x
asPcbnewModule x = expecting' "module name" x
asPcbnewFpText :: [SExpr] -> Either String PcbnewItem
asPcbnewFpText (t:s:a:xs) = interpretType
where
interpretType = case t of
(Atom _ "reference") ->
interpretString (defaultPcbnewFpText {fpTextType = FpTextReference})
(Atom _ "value") ->
interpretString (defaultPcbnewFpText {fpTextType = FpTextValue})
(Atom _ "user") ->
interpretString (defaultPcbnewFpText {fpTextType = FpTextUser})
_ -> expecting "'reference', 'value' or 'user'" t
interpretString fp_text = case s of
(Atom _ string) -> interpretAt fp_text {fpTextStr = string}
_ -> expecting "string" s
interpretAt fp_text = case fromSExpr a of
Left err -> Left err
Right (PcbnewExprAttribute (PcbnewAt at)) ->
interpretRest xs fp_text {itemAt = at}
_ -> expecting "'at' expression (e.g. '(at 1.0 1.0)')" a
interpretEffects [] fp_text = fp_text
interpretEffects (e:efs) fp_text = case e of
(PcbnewJustify js) ->
interpretEffects efs (over fpTextJustify (++ js) fp_text)
(PcbnewFont size thickness italic) ->
interpretEffects efs
(fp_text
{ itemSize = size
, fpTextThickness = thickness
, fpTextItalic = italic
}
)
_ -> fp_text
interpretRest [] fp_text = Right fp_text
interpretRest (sx:sxs) fp_text = case fromSExpr sx of
Left err -> Left err
Right (PcbnewExprAttribute (PcbnewLayer layer)) ->
interpretRest sxs (fp_text {itemLayer = layer})
Right (PcbnewExprAttribute (PcbnewFpTextEffects effects)) ->
interpretRest sxs (interpretEffects effects fp_text)
Right (PcbnewExprAttribute PcbnewHide) ->
interpretRest sxs (fp_text {fpTextHide = True})
_ -> expecting "layer or effects expression or 'hide'" sx
asPcbnewFpText x = expecting' "a text-type, text, 'at' and layer" x
asFp :: PcbnewItem -> [SExpr] -> Either String PcbnewItem
asFp defaultFp (s:e:xs) = interpretStart defaultFp
where
interpretStart fp_shape = case fromSExpr s of
Left err -> Left err
Right (PcbnewExprAttribute (PcbnewStart start)) ->
interpretEnd fp_shape {itemStart = start}
Right (PcbnewExprAttribute (PcbnewCenter center)) ->
interpretEnd fp_shape {itemStart = center}
Right _ -> expecting "start (e.g. '(start 1.0 1.0)')" s
interpretEnd fp_shape = case fromSExpr e of
Left err -> Left err
Right (PcbnewExprAttribute (PcbnewEnd end)) ->
interpretRest xs fp_shape {itemEnd = end}
Right _ -> expecting "end (e.g. '(end 1.0 1.0)')" e
interpretRest [] fp_shape = Right fp_shape
interpretRest (sx:sxs) fp_shape = case fromSExpr sx of
Left err -> Left err
Right (PcbnewExprAttribute (PcbnewWidth d))
-> interpretRest sxs fp_shape {itemWidth = d}
Right (PcbnewExprAttribute (PcbnewLayer d))
-> interpretRest sxs fp_shape {itemLayer = d}
Right _ -> expecting "width or layer" sx
asFp _ x = expecting' "fp_line (or fp_circle) start (center), end and attributes" x
asPcbnewFpArc :: [SExpr] -> Either String PcbnewItem
asPcbnewFpArc (s:e:xs) = interpretStart defaultPcbnewFpArc
where
interpretStart fp_arc = case fromSExpr s of
Left err -> Left err
Right (PcbnewExprAttribute (PcbnewStart start)) ->
interpretEnd fp_arc {itemStart = start}
Right _ -> expecting "start (e.g. '(start 1.0 1.0)')" s
interpretEnd fp_arc = case fromSExpr e of
Left err -> Left err
Right (PcbnewExprAttribute (PcbnewEnd end)) ->
interpretRest xs fp_arc {itemEnd = end}
Right _ -> expecting "end (e.g. '(end 1.0 1.0)')" e
interpretRest [] fp_arc = Right fp_arc
interpretRest (sx:sxs) fp_arc = case fromSExpr sx of
Left err -> Left err
Right (PcbnewExprAttribute (PcbnewWidth d))
-> interpretRest sxs fp_arc {itemWidth = d}
Right (PcbnewExprAttribute (PcbnewLayer d))
-> interpretRest sxs fp_arc {itemLayer = d}
Right (PcbnewExprAttribute (PcbnewAngle d))
-> interpretRest sxs fp_arc {fpArcAngle = d}
Right _ -> expecting "width, layer or angle" sx
asPcbnewFpArc x = expecting' "fp_arc start, end and attributes" x
asPcbnewFpPoly :: [SExpr] -> Either String PcbnewItem
asPcbnewFpPoly xs = interpretRest xs defaultPcbnewFpPoly
where
interpretRest [] fp_poly = Right fp_poly
interpretRest (sx:sxs) fp_poly = case fromSExpr sx of
Left err -> Left err
Right (PcbnewExprAttribute (PcbnewPts d))
-> interpretRest sxs fp_poly {fpPolyPts = d}
Right (PcbnewExprAttribute (PcbnewWidth d))
-> interpretRest sxs fp_poly {itemWidth = d}
Right (PcbnewExprAttribute (PcbnewLayer d))
-> interpretRest sxs fp_poly {itemLayer = d}
Right _ -> expecting "width, layer or 'pts'" sx
asPcbnewPad :: [SExpr] -> Either String PcbnewItem
asPcbnewPad (n:t:s:xs) = interpretNumber
where
interpretNumber = case n of
(Atom _ num) -> interpretType defaultPcbnewPad {padNumber = num}
_ -> expecting "string designating pad number" n
interpretType :: PcbnewItem -> Either String PcbnewItem
interpretType pad = case t of
(Atom _ str) -> case strToPadType str of
Just d -> interpretShape pad {padType = d}
Nothing ->
expecting "pad type (e.g. 'smd')" t
_ -> expecting "pad type string (e.g. 'smd')" t
interpretShape :: PcbnewItem -> Either String PcbnewItem
interpretShape pad = case s of
(Atom _ str) -> case strToPadShape str of
Just d -> interpretRest xs pad {padShape = d}
Nothing ->
expecting "pad shape (e.g. 'circle')" s
_ -> expecting "pad shape string (e.g. 'circle')" s
interpretRest :: [SExpr] -> PcbnewItem -> Either String PcbnewItem
interpretRest [] pad = Right pad
interpretRest (sx:sxs) pad = case fromSExpr sx of
Left err -> Left err
Right (PcbnewExprAttribute (PcbnewAt d))
-> interpretRest sxs pad {itemAt = d}
Right (PcbnewExprAttribute (PcbnewLayers d))
-> interpretRest sxs pad {padLayers = d}
Right (PcbnewExprAttribute (PcbnewSize d))
-> interpretRest sxs pad {itemSize = d}
Right (PcbnewExprAttribute a@(PcbnewDrill _))
-> pushToAttrs sxs a pad
Right (PcbnewExprAttribute a@(PcbnewRectDelta _))
-> pushToAttrs sxs a pad
Right (PcbnewExprAttribute a@(PcbnewMaskMargin _))
-> pushToAttrs sxs a pad
Right (PcbnewExprAttribute a@(PcbnewPasteMarginRatio _))
-> pushToAttrs sxs a pad
Right (PcbnewExprAttribute a@(PcbnewPasteMargin _))
-> pushToAttrs sxs a pad
Right (PcbnewExprAttribute a@(PcbnewClearance _))
-> pushToAttrs sxs a pad
Right (PcbnewExprAttribute a@(PcbnewZoneConnect _))
-> pushToAttrs sxs a pad
Right (PcbnewExprAttribute a@(PcbnewThermalWidth _))
-> pushToAttrs sxs a pad
Right (PcbnewExprAttribute a@(PcbnewThermalGap _))
-> pushToAttrs sxs a pad
Right (PcbnewExprAttribute a@(PcbnewRoundrectRratio _))
-> pushToAttrs sxs a pad
Right (PcbnewExprAttribute a@(PcbnewDieLength _))
-> pushToAttrs sxs a pad
_ -> expecting "at, size, drill, layers , margins etc. or nothing" sx
pushToAttrs sxs a pad = interpretRest sxs (over padAttributes (++[a]) pad)
asPcbnewPad xs = expecting' "number, type and shape" xs
asPcbnewLayer :: [SExpr] -> Either String PcbnewAttribute
asPcbnewLayer [sx] = onePcbnewLayer sx
asPcbnewLayer x = expecting' "only one layer name" x
onePcbnewLayer :: SExpr -> Either String PcbnewAttribute
onePcbnewLayer (Atom _ n) = case strToLayer n of
Just l -> Right $ PcbnewLayer l
Nothing -> Left ("-> Unknown layer name: " ++ n)
onePcbnewLayer x = expecting "layer name" x
asPcbnewAt :: [SExpr] -> Either String PcbnewAttribute
asPcbnewAt sx@(Atom _ x:[Atom _ y]) = case readXy x y of
Just xy -> Right $ PcbnewAt $ defaultPcbnewAtT {pcbnewAtPoint = xy}
Nothing -> expecting' "x y coordinates" sx
asPcbnewAt sx@(Atom _ x:Atom _ y:[Atom _ o]) = case readXyz x y o of
Just (x', y', o') -> Right $ PcbnewAt $ PcbnewAtT (x',y') o'
Nothing -> expecting' "x y coordinates and orientation" sx
asPcbnewAt l@[List _ _] = asXyz PcbnewModelAt l
asPcbnewAt x =
expecting' "x y coordinates and orientation" x
readXy :: String -> String -> Maybe (Double, Double)
readXy x y = do
x' <- readMaybeDouble x
y' <- readMaybeDouble y
return (x', y')
readXyz :: String -> String -> String -> Maybe (Double, Double, Double)
readXyz x y z = do
x' <- readMaybeDouble x
y' <- readMaybeDouble y
z' <- readMaybeDouble z
return (x', y', z')
asPcbnewEffects :: [SExpr] -> Either String PcbnewAttribute
asPcbnewEffects xs = interpretRest xs []
where
interpretRest [] effects = Right (PcbnewFpTextEffects effects)
interpretRest (sx:sxs) effects = case fromSExpr sx of
Left err -> Left err
Right (PcbnewExprAttribute justify@(PcbnewJustify _))
-> interpretRest sxs (justify:effects)
Right (PcbnewExprAttribute font@(PcbnewFont _ _ _))
-> interpretRest sxs (font:effects)
Right (PcbnewExprAttribute PcbnewHide)
-> interpretRest sxs (PcbnewHide:effects)
_ -> expecting "font or justify expression" sx
asPcbnewFont :: [SExpr] -> Either String PcbnewAttribute
asPcbnewFont xs = interpretRest xs defaultPcbnewFont
where
interpretRest [] font = Right font
interpretRest (sx:sxs) font = case fromSExpr sx of
Left err -> Left err
Right (PcbnewExprAttribute (PcbnewSize size)) ->
interpretRest sxs font {pcbnewFontSize = size}
Right (PcbnewExprAttribute (PcbnewThickness t)) ->
interpretRest sxs font {pcbnewFontThickness = t}
Right (PcbnewExprAttribute PcbnewItalic) ->
interpretRest sxs font {pcbnewFontItalic = True}
Right _ -> expecting "size, thickness or 'italic'" sx
asXy :: ((Double, Double) -> a) -> [SExpr] -> Either String a
asXy constructor sx@[Atom _ x, Atom _ y] = case readXy x y of
Just xy -> Right $ constructor xy
Nothing -> expecting' "two floats (e.g. 1.0 1.0)" sx
asXy _ x = expecting' "two floats (e.g. 1.0 1.0)" x
asPcbnewPts :: [SExpr] -> Either String PcbnewAttribute
asPcbnewPts = fmap PcbnewPts . foldr interpretXys (Right [])
where interpretXys sx z = case fromSExpr sx of
Left err -> Left err
Right (PcbnewExprAttribute (PcbnewXy xy))
-> Right (xy:) <*> z
Right _ -> expecting "'xy' (e.g. '(xy 1.0 1.0)')" sx
asString :: (String -> PcbnewAttribute) -> [SExpr] -> Either String PcbnewAttribute
asString pcbnew [Atom _ s] = Right $ pcbnew s
asString _ x = expecting' "string" x
asPcbnewLayers :: [SExpr] -> Either String PcbnewAttribute
asPcbnewLayers [] = Right $ PcbnewLayers []
asPcbnewLayers xs = let layers = map onePcbnewLayer xs in case lefts layers of
[] -> Right $ PcbnewLayers $ map (\(PcbnewLayer l) -> l) $ rights layers
_ -> Left $ "Could not fromSExpr layers:\n"
++ unlines (map ("\t\t"++) (lefts layers))
asDouble :: (Double -> PcbnewAttribute) -> [SExpr] -> Either String PcbnewAttribute
asDouble constructor [sx@(Atom _ d)] = case readMaybeDouble d of
Just d' -> Right $ constructor d'
Nothing -> expecting "one float (e.g. '1.0')" sx
asDouble _ x = expecting' "one float (e.g. '1.0')" x
asInt :: (Int -> PcbnewAttribute) -> [SExpr] -> Either String PcbnewAttribute
asInt constructor [sx@(Atom _ i)] = case readMaybe i of
Just i' -> Right $ constructor i'
Nothing -> expecting "one int (e.g. '1')" sx
asInt _ x = expecting' "one int (e.g. '1')" x
asPcbnewDrill :: [SExpr] -> Either String PcbnewAttribute
asPcbnewDrill xs = interpretRest xs defaultPcbnewDrillT
where
interpretRest [] drill = Right $ PcbnewDrill drill
interpretRest (sx:sxs) drill = case sx of
Atom _ "oval" -> interpretRest sxs drill {pcbnewDrillOval = True}
(List _ _) -> case fromSExpr sx of
Left err -> Left err
Right (PcbnewExprAttribute (PcbnewOffset xy))
-> interpretRest sxs drill {pcbnewDrillOffset = Just xy}
Right _ -> expecting "offset or nothing" sx
Atom _ d -> case readMaybeDouble d of
Just d' -> if isNothing (pcbnewDrillSize drill)
then interpretRest sxs drill
{ pcbnewDrillSize = Just (d',d') }
else interpretRest sxs drill
{ pcbnewDrillSize =
fmap (\(x,_) -> (x,d')) (pcbnewDrillSize drill)
}
Nothing -> expecting "float, 'oval' or offset" sx
asPcbnewXyz :: [SExpr] -> Either String PcbnewAttribute
asPcbnewXyz sx@(Atom _ x:Atom _ y:[Atom _ z]) = case readXyz x y z of
Just xyz -> Right $ PcbnewXyz xyz
Nothing -> expecting' "three floats" sx
asPcbnewXyz x = expecting' "three floats" x
asXyz :: (PcbnewAttribute -> a) -> [SExpr] -> Either String a
asXyz constructor [l@(List _ _)] = case fromSExpr l of
Left err -> Left err
Right (PcbnewExprAttribute xyz) -> Right $ constructor xyz
Right _ -> expecting "xyz (e.g. '(xyz 1 1 1)')" l
asXyz _ x = expecting' "xyz (e.g. '(xyz 1 1 1)')" x
asPcbnewModel :: [SExpr] -> Either String PcbnewAttribute
asPcbnewModel (Atom _ p:xs) = interpretRest xs defaultPcbnewModel {pcbnewModelPath = p}
where
interpretRest [] model = Right model
interpretRest (sx:sxs) model = case fromSExpr sx of
Left err -> Left err
Right (PcbnewExprAttribute (PcbnewModelAt (PcbnewXyz xyz))) ->
interpretRest sxs model {pcbnewModelAt = xyz}
Right (PcbnewExprAttribute (PcbnewModelScale (PcbnewXyz xyz))) ->
interpretRest sxs model {pcbnewModelScale = xyz}
Right (PcbnewExprAttribute (PcbnewModelRotate (PcbnewXyz xyz))) ->
interpretRest sxs model {pcbnewModelRotate = xyz}
Right _ -> expecting "only at, scale and rotate" sx
asPcbnewModel x = expecting' "model path, at, scale and rotate" x
justifyOneOf :: String
justifyOneOf = "one of '"
++ concat (intersperse ", " (fmap justifyToString [minBound..]))
++ "'"
asPcbnewJustifyT :: [SExpr] -> Either String PcbnewAttribute
asPcbnewJustifyT sx = case lefts js of
[] -> Right (PcbnewJustify (rights js))
es -> Left (headOr "" es)
where js = fmap oneJustifyT sx
oneJustifyT :: SExpr -> Either String PcbnewJustifyT
oneJustifyT sx@(Atom _ s) = case strToJustify s of
Just j -> Right j
Nothing -> expecting justifyOneOf sx
oneJustifyT x = expecting justifyOneOf x
expecting :: String -> SExpr -> Either String a
expecting x y =
Left $ "Error in " ++ pos ++ ": expecting " ++ x ++ " but got " ++
nothing_or (strip_brackets (write y)) ++ " instead"
where
nothing_or y' = case y' of
"" -> "nothing"
_ -> "'" ++ y' ++ "'"
strip_brackets y' = case head y' of
'(' -> tail . init $ y'
_ -> y'
pos = show (getPos y)
expecting' :: String -> [SExpr] -> Either String a
expecting' x y = expecting x $ List (newPos "" 0 0) y
readMaybeDouble :: String -> Maybe Double
readMaybeDouble str@(c1:c2:rest) = case c1 of
'.' -> readMaybe ('0':str)
'-' -> case c2 of
'.' -> readMaybe ('-':'0':rest)
_ -> readMaybe str
_ -> readMaybe str
readMaybeDouble str = readMaybe str