Kirjan esimerkkikääntäjä

Tässä kirjan ykkösluvun kääntäjä toteutettuna TIEA341 Haskellilla.

Tämä olisi merkittävästi kompaktimpi ja laajennettavampi TIES341 kurssin jälkeen.

TIES341 kurssi itseasiassa alkaa tästä esimerkistä ja siellä opetellaan ohjelmointitekniikat, joilla tälläisiä asioita saadaan tehty verrattain tuskattomasti.

{-#OPTIONS_GHC -Wall#-}
module Main where
import Prelude hiding (lex)
import Data.Char
import Test.QuickCheck

-- LEXER

data Op     = Plus | Mul
    deriving (Eq,Ord,Show)

data Lexeme = Digit Int |  OpenParen |  CloseParen | Operator Op
    deriving (Eq,Ord,Show)

lex :: String -> [Lexeme]
lex []           = []
lex ('(' : rest) = OpenParen : lex rest
lex (')' : rest) = CloseParen : lex rest
lex (n : rest) | isDigit n     = Digit (read [n]) : lex rest
               | n == '+'      = Operator Plus : lex rest
               | n == '*'      = Operator Mul : lex rest
               | isSeparator n = lex rest
               | n == '\n'     = lex rest
               | otherwise     = error $ "Lexer error at char: " ++ show n

-- PARSER

data AST = Number Int
         | Infix AST Op AST
         deriving Show

parse :: [Lexeme] -> (AST, [Lexeme])
parse []               = error "Expected more input"
parse (Digit n : rest) = (Number n, rest)
parse (OpenParen : rest) =
  let parseOp (Operator o : r) = (o, r)
      parseOp _                = error "Expected an operator"
      parseClose (CloseParen : r) = r
      parseClose (other      : _) = error ("Expected ')', got " ++ show other)
      parseClose []               = error "Unexpected EOF"
      (left , rest2) = parse rest
      (op   , rest3) = parseOp rest2
      (right, rest4) = parse rest3
      rest5          = parseClose rest4
  in  (Infix left op right, rest5)
parse (other : _) = error ("Expected a digit or an '(', got" ++ show other)

parseOnly :: [Lexeme] -> AST
parseOnly s = case parse s of
  (ast, []   ) -> ast
  (_  , stuff) -> error ("Got extra stuff at the end: " ++ show stuff)

-- CODE GENERATOR

data ASM = PUSH Int | ADD | MULT | PRINT deriving (Eq,Show)

codeGen :: AST -> [ASM]
codeGen a = case a of
  Number d           -> [PUSH d]
  Infix left o right -> codeGen left ++ codeGen right ++ genOp o
 where
  genOp Plus = [ADD]
  genOp Mul  = [MULT]

-- INTERPRETERS

interpret :: AST -> Int
interpret a = case a of
  Number n              -> n
  Infix left Plus right -> interpret left + interpret right
  Infix left Mul  right -> interpret left * interpret right

interpretASM :: [Int] -> [String] -> [ASM] -> (Int, [String])
interpretASM (x : _) out [] = (x, out)
interpretASM [] _ [] = error "Program produced no output"
interpretASM stack out (instr : remainder) = case instr of
  PUSH i -> interpretASM (i : stack) out remainder
  ADD    -> case stack of
    (x : y : rest) -> interpretASM (x + y : rest) out remainder
    _              -> error "Stack underflow!"
  MULT -> case stack of
    (x : y : rest) -> interpretASM (x * y : rest) out remainder
    _              -> error "Stack underflow!"
  PRINT -> case stack of
    (x : xs) -> interpretASM (x : xs) (show x : out) remainder
    _        -> error "Stack underflow!"

-- TESTING

instance Arbitrary Op where
    arbitrary = oneof [pure Mul, pure Plus]
instance Arbitrary AST where
    arbitrary =
     sized $ \n -> if n == 0
        then Number <$> arbitrary
        else oneof [Number <$> arbitrary
                   ,resize (n-1)
                        (Infix <$> arbitrary <*> arbitrary <*> arbitrary)]

essential :: AST -> Bool
essential ast = (fst . interpretASM [] [] . codeGen) ast == interpret ast

-- MAIN

main :: IO ()
main = do
  input <- getContents
  let ast = parseOnly (lex input)
      asm = (++ [PRINT]) (codeGen ast)
  putStrLn "CODE:"
  mapM_ print (asm)
  putStrLn "Result:"
  let (_, out) = interpretASM [] [] asm
  mapM_ putStrLn out

These are the current permissions for this document; please modify if needed. You can always modify these permissions from the manage page.