Beginnings of a PostScript interpreter
Getty Ritter
8 years ago
1 | name: endsay | |
2 | version: 0.1.0.0 | |
3 | -- synopsis: | |
4 | -- description: | |
5 | license: BSD3 | |
6 | license-file: LICENSE | |
7 | author: Getty Ritter <gettylefou@gmail.com> | |
8 | maintainer: Getty Ritter <gettylefou@gmail.com> | |
9 | copyright: ©2016 Getty Ritter | |
10 | -- category: | |
11 | build-type: Simple | |
12 | cabal-version: >= 1.12 | |
13 | ||
14 | executable endsay | |
15 | hs-source-dirs: src | |
16 | main-is: Main.hs | |
17 | other-modules: Types | |
18 | Parse | |
19 | default-extensions: OverloadedStrings, | |
20 | ScopedTypeVariables | |
21 | ghc-options: -Wall | |
22 | build-depends: base >=4.7 && <4.9 | |
23 | , containers | |
24 | , text | |
25 | , transformers | |
26 | , vector | |
27 | , bytestring | |
28 | , unordered-containers | |
29 | , megaparsec | |
30 | , semigroups | |
31 | default-language: Haskell2010 |
1 | module Main where | |
2 | ||
3 | import Run | |
4 | ||
5 | main :: IO () | |
6 | main = do | |
7 | let program = "5 dup mul =" | |
8 | runEndsay program run >>= print |
1 | {-# LANGUAGE RankNTypes #-} | |
2 | ||
3 | module PSNum where | |
4 | ||
5 | import Types | |
6 | ||
7 | data PSNum | |
8 | = PSInt Integer | |
9 | | PSReal Double | |
10 | deriving (Eq, Show) | |
11 | ||
12 | toPSNum :: Object -> Endsay PSNum | |
13 | toPSNum (OInteger n) = return (PSInt n) | |
14 | toPSNum (OReal n) = return (PSReal n) | |
15 | toPSNum _ = error ".." | |
16 | ||
17 | fromPSNum :: PSNum -> Object | |
18 | fromPSNum (PSInt n) = OInteger n | |
19 | fromPSNum (PSReal n) = OReal n | |
20 | ||
21 | promoteNum :: (forall a. Num a => a -> a -> a) -> PSNum -> PSNum -> PSNum | |
22 | promoteNum op (PSInt x) (PSInt y) = PSInt (x `op` y) | |
23 | promoteNum op (PSInt x) (PSReal y) = PSReal (fromIntegral x `op` y) | |
24 | promoteNum op (PSReal x) (PSInt y) = PSReal (x `op` fromIntegral y) | |
25 | promoteNum op (PSReal x) (PSReal y) = PSReal (x `op` y) | |
26 | ||
27 | resNum :: (forall a. (Num a, Ord a) => a -> a -> b) -> PSNum -> PSNum -> b | |
28 | resNum op (PSInt x) (PSInt y) = (x `op` y) | |
29 | resNum op (PSInt x) (PSReal y) = (fromIntegral x `op` y) | |
30 | resNum op (PSReal x) (PSInt y) = (x `op` fromIntegral y) | |
31 | resNum op (PSReal x) (PSReal y) = (x `op` y) | |
32 | ||
33 | overNum :: (forall a. Num a => a -> a) -> PSNum -> PSNum | |
34 | overNum op (PSInt x) = PSInt (op x) | |
35 | overNum op (PSReal x) = PSReal (op x) | |
36 | ||
37 | instance Num PSNum where | |
38 | (+) = promoteNum (+) | |
39 | (-) = promoteNum (-) | |
40 | (*) = promoteNum (*) | |
41 | negate = overNum negate | |
42 | abs = overNum abs | |
43 | signum = overNum signum | |
44 | fromInteger = PSInt | |
45 | ||
46 | instance Ord PSNum where | |
47 | compare = resNum compare |
1 | module Parse where | |
2 | ||
3 | import Control.Monad (when) | |
4 | import Data.ByteString (ByteString) | |
5 | import qualified Data.ByteString as BS | |
6 | import Data.Char (ord) | |
7 | import Data.List.NonEmpty (NonEmpty(..)) | |
8 | import Data.Text (Text) | |
9 | import qualified Data.Text as T | |
10 | import Text.Megaparsec | |
11 | import Text.Megaparsec.Text | |
12 | ||
13 | import Types | |
14 | ||
15 | -- Okay, yeah, 'tokenize' would be better, sure | |
16 | ||
17 | initialParserState :: Text -> State Text | |
18 | initialParserState t = State | |
19 | { stateInput = t | |
20 | , statePos = SourcePos "<file>" (unsafePos 1) (unsafePos 1) :| [] | |
21 | , stateTabWidth = unsafePos 8 | |
22 | } | |
23 | ||
24 | pNextParsedObject :: State Text -> Either String (ParsedObject, State Text) | |
25 | pNextParsedObject st = result | |
26 | where result = case runParser' (pWhitespace *> pParsedObject) st of | |
27 | (_, Left err) -> Left (show err) | |
28 | (s, Right v) -> Right (v, s) | |
29 | ||
30 | pWhitespace :: Parser () | |
31 | pWhitespace = skipMany spaceChar | |
32 | ||
33 | specialChars :: String | |
34 | specialChars = "()<>[]{}/% \t\r\n" | |
35 | ||
36 | pParsedObject :: Parser ParsedObject | |
37 | pParsedObject = | |
38 | uncurry PORadix <$> try pRadix | |
39 | <|> POReal <$> pReal | |
40 | <|> POInteger <$> pInteger | |
41 | <|> POString <$> pString | |
42 | <|> POByteString <$> pByteString | |
43 | <|> POSymbol <$> pSymbol | |
44 | <|> POName <$> pName | |
45 | ||
46 | pSign :: Num a => Parser (a -> a) | |
47 | pSign = negate <$ char '-' | |
48 | <|> id <$ char '+' | |
49 | <|> pure id | |
50 | ||
51 | pRadix :: Parser (Int, Integer) | |
52 | pRadix = do | |
53 | base <- read <$> some digitChar | |
54 | _ <- char '#' | |
55 | when (base < 2 || base > 36) $ | |
56 | fail ("Invalid base: " ++ show base) | |
57 | num <- some (digitForBase base) | |
58 | return (base, readAsBase base num) | |
59 | ||
60 | readAsBase :: Num a => Int -> String -> a | |
61 | readAsBase _ [] = error "[unreachable]" | |
62 | readAsBase _ [c] = numValOf c | |
63 | readAsBase b (c:cs) = (numValOf c * fromIntegral b) + readAsBase b cs | |
64 | ||
65 | digitForBase :: Int -> Parser Char | |
66 | digitForBase n = oneOf chars | |
67 | where chars = take n ['0'..'9'] ++ | |
68 | take (n-1) ['A'..'Z'] ++ | |
69 | take (n-1) ['a'..'z'] | |
70 | ||
71 | pReal :: Parser Double | |
72 | pReal = fail "unimplemented" | |
73 | ||
74 | pInteger :: Parser Integer | |
75 | pInteger = do | |
76 | sign <- pSign | |
77 | num <- some digitChar | |
78 | return $ sign (read num) | |
79 | ||
80 | pString :: Parser Text | |
81 | pString = T.pack <$> (char '(' *> go) | |
82 | where go = | |
83 | "" <$ char ')' | |
84 | <|> (++) <$> some (noneOf ['(',')','\\']) <*> go -- XXX: handle rest! | |
85 | ||
86 | pByteString :: Parser ByteString | |
87 | pByteString = char '<' *> (go <$> some hexDigitChar) <* char '>' | |
88 | where go = BS.pack . toVals | |
89 | toVals (x:y:xs) = (numValOf x * 16 + numValOf y) : toVals xs | |
90 | toVals (x:[]) = [ numValOf x * 16 ] | |
91 | toVals [] = [] | |
92 | ||
93 | numValOf :: Num b => Char -> b | |
94 | numValOf n | |
95 | | n >= 'a' && n <= 'z' = fromIntegral (ord n) - 0x57 | |
96 | | n >= 'A' && n <= 'Z' = fromIntegral (ord n) - 0x37 | |
97 | | otherwise = fromIntegral (ord n) - 0x30 | |
98 | ||
99 | pSymbol :: Parser Text | |
100 | pSymbol = T.pack <$> (char '/' *> many (noneOf specialChars)) | |
101 | ||
102 | pName :: Parser Text | |
103 | pName = T.pack <$> some (noneOf specialChars) |
1 | module Pretty where | |
2 | ||
3 | import Data.Monoid ((<>)) | |
4 | import qualified Data.Text as T | |
5 | import qualified Data.Text.IO as T | |
6 | ||
7 | class PSShow t where | |
8 | psShow :: t -> Text | |
9 | ||
10 | instance PSShow Object where | |
11 | psShow (OBool True) = "true" | |
12 | psShow (OBool False) = "false" | |
13 | psShow (OInteger i) = "0" | |
14 | psShow (OString t) = "(" <> t <> ")" | |
15 | ||
16 | psPrint :: PSShow t => t -> IO () | |
17 | psPrint = T.putStrLn . psShow |
1 | module Run where | |
2 | ||
3 | import Control.Monad ((=<<), forever) | |
4 | import Control.Monad.IO.Class | |
5 | import Control.Monad.Trans.Class (lift) | |
6 | import Control.Monad.Trans.Except | |
7 | import Control.Monad.Trans.State | |
8 | import Data.HashMap.Strict (HashMap) | |
9 | import qualified Data.HashMap.Strict as HM | |
10 | import Data.Monoid ((<>), Last(..)) | |
11 | import qualified Data.Sequence as S | |
12 | import Data.Sequence (Seq, (<|), ViewL(..)) | |
13 | import Data.Text (Text) | |
14 | ||
15 | import Types | |
16 | import Parse | |
17 | import PSNum | |
18 | ||
19 | pop :: Endsay Object | |
20 | pop = Endsay $ do | |
21 | st <- lift get | |
22 | let x :< xs = S.viewl (isOpStack st) | |
23 | lift $ put st { isOpStack = xs } | |
24 | return x | |
25 | ||
26 | push :: Object -> Endsay () | |
27 | push o = Endsay $ lift $ modify (\ s -> s { isOpStack = o <| isOpStack s }) | |
28 | ||
29 | pushObject :: ParsedObject -> Endsay () | |
30 | pushObject (POInteger n) = push (OInteger n) | |
31 | pushObject (POReal n) = push (OReal n) | |
32 | pushObject (PORadix _ n) = push (OInteger n) | |
33 | pushObject (POString t) = push (OString t) | |
34 | pushObject (POByteString t) = return () | |
35 | pushObject (POSymbol t) = push (OName t) | |
36 | pushObject (POName t) = runCommand t | |
37 | ||
38 | runCommand :: Text -> Endsay () | |
39 | runCommand t = do | |
40 | obj <- findName t | |
41 | case obj of | |
42 | Nothing -> liftIO $ putStrLn "Name not found" | |
43 | Just (OBuiltInOperator (BuiltIn b)) -> b | |
44 | ||
45 | runEndsay :: Text -> Endsay a -> IO (Either ESErr a, InterpreterState) | |
46 | runEndsay t (Endsay mote) = | |
47 | runStateT (runExceptT mote) (initialInterpreterState t) | |
48 | -- runStateT (runInterp mote) (runStateT (initialInterpreterState t) (runInterp mote)) | |
49 | ||
50 | findName :: Text -> Endsay (Maybe Object) | |
51 | findName t = do | |
52 | InterpreterState { isDictStack = ds } <- Endsay $ lift get | |
53 | return $ getLast $ foldr (\ d r -> Last (HM.lookup t d) <> r) (Last Nothing) ds | |
54 | ||
55 | step :: Endsay () | |
56 | step = do | |
57 | st <- Endsay $ lift get | |
58 | case pNextParsedObject (isParseState st) of | |
59 | Left err -> Endsay $ throwE (ESErr "unable to parse another thing") | |
60 | Right (po, st') -> do | |
61 | pushObject po | |
62 | Endsay $ lift $ modify $ \ s -> s { isParseState = st' } | |
63 | ||
64 | run :: Endsay () | |
65 | run = forever step | |
66 | ||
67 | initialInterpreterState :: Text -> InterpreterState | |
68 | initialInterpreterState t = InterpreterState | |
69 | { isSource = t | |
70 | , isParseState = initialParserState t | |
71 | , isOpStack = S.empty | |
72 | , isDictStack = S.singleton $ basicDict | |
73 | , isExecStack = S.empty | |
74 | } | |
75 | ||
76 | builtin :: Endsay () -> Object | |
77 | builtin = OBuiltInOperator . BuiltIn | |
78 | ||
79 | basicDict :: HashMap Text Object | |
80 | basicDict = HM.fromList | |
81 | [ ("add", builtin $ do | |
82 | x <- toPSNum =<< pop | |
83 | y <- toPSNum =<< pop | |
84 | push (fromPSNum (x + y)) | |
85 | ) | |
86 | , ("sub", builtin $ do | |
87 | x <- toPSNum =<< pop | |
88 | y <- toPSNum =<< pop | |
89 | push (fromPSNum (x - y)) | |
90 | ) | |
91 | , ("mul", builtin $ do | |
92 | x <- toPSNum =<< pop | |
93 | y <- toPSNum =<< pop | |
94 | push (fromPSNum (x * y)) | |
95 | ) | |
96 | , ("lt", builtin $ do | |
97 | x <- toPSNum =<< pop | |
98 | y <- toPSNum =<< pop | |
99 | push (OBool (x < y)) | |
100 | ) | |
101 | , ("gt", builtin $ do | |
102 | x <- toPSNum =<< pop | |
103 | y <- toPSNum =<< pop | |
104 | push (OBool (x > y)) | |
105 | ) | |
106 | , ("dup", builtin $ do | |
107 | x <- pop | |
108 | push x | |
109 | push x | |
110 | ) | |
111 | , ("exch", builtin $ do | |
112 | x <- pop | |
113 | y <- pop | |
114 | push x | |
115 | push x | |
116 | ) | |
117 | , ("=", builtin $ pop >>= liftIO . print) | |
118 | ] |
1 | module Stdlib where |
1 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | |
2 | {-# LANGUAGE RankNTypes #-} | |
3 | ||
4 | module Types where | |
5 | ||
6 | import Control.Monad.Trans.Except (ExceptT) | |
7 | import Control.Monad.Trans.State (StateT) | |
8 | import Control.Monad.IO.Class (MonadIO) | |
9 | import Data.ByteString (ByteString) | |
10 | import Data.HashMap.Strict (HashMap) | |
11 | import Data.Sequence (Seq) | |
12 | import Data.Text (Text) | |
13 | import Data.Vector (Vector) | |
14 | import qualified Text.Megaparsec as MP | |
15 | ||
16 | data InterpreterState = InterpreterState | |
17 | { isSource :: Text | |
18 | , isParseState :: MP.State Text | |
19 | , isOpStack :: Seq Object | |
20 | , isDictStack :: Seq (HashMap Text Object) | |
21 | , isExecStack :: Seq Object | |
22 | } deriving (Show) | |
23 | ||
24 | data BuiltIn = BuiltIn (Endsay ()) | |
25 | ||
26 | instance Show BuiltIn where show _ = "BuiltIn { ... }" | |
27 | ||
28 | type Fragment = Vector Object | |
29 | ||
30 | data ESErr = ESErr String deriving (Eq, Show) | |
31 | ||
32 | newtype Endsay a = | |
33 | Endsay { runInterp :: ExceptT ESErr (StateT InterpreterState IO) a } | |
34 | deriving (Functor, Applicative, Monad, MonadIO) | |
35 | ||
36 | data Object | |
37 | = OBool Bool | |
38 | | OFontId () | |
39 | | OInteger Integer | |
40 | | OMark () | |
41 | | OName Text | |
42 | | ONull | |
43 | | OOperator () | |
44 | | OBuiltInOperator BuiltIn | |
45 | | OReal Double | |
46 | | OArray (Vector Object) | |
47 | | ODictionary (HashMap Text Object) | |
48 | | OFile () | |
49 | -- | OGState () | |
50 | -- | OPackedArray () | |
51 | | OSave () | |
52 | | OString Text | |
53 | deriving (Show) | |
54 | ||
55 | data ParsedObject | |
56 | = POInteger Integer | |
57 | | POReal Double | |
58 | | PORadix Int Integer | |
59 | | POString Text | |
60 | | POByteString ByteString | |
61 | | POSymbol Text | |
62 | | POName Text | |
63 | deriving (Eq, Show) | |
64 | ||
65 | data SourceLiteral = SourceLiteral | |
66 | { slObject :: ParsedObject | |
67 | , slLine :: Int | |
68 | , slColumn :: Int | |
69 | , slText :: Text | |
70 | } deriving (Eq, Show) |