packages feed

vintage-basic-1.0.3: src/Language/VintageBasic/Tokenizer.hs

-- | Finds and tokenizes BASIC keywords, in preparation for parsing. This allows
-- keywords to be read even if there are no spaces around them. Even though
-- the standard disallows it, many BASIC implementations allowed this to save
-- memory or screen real estate. The down side is that longer variable names
-- are not practical, since they might contain keywords.

module Language.VintageBasic.Tokenizer
    (Token(..),TokenizedLine,isDataTok,isRemTok,charTokTest,taggedCharToksToString,isStringTok,
     isBuiltinTok,taggedTokensP,tokenP,printToken) where

import Data.Char(toLower,toUpper)
import Language.VintageBasic.Builtins(Builtin,builtinToStrAssoc)
import Language.VintageBasic.LexCommon
import Text.ParserCombinators.Parsec

type TokenizedLine = Tagged [Tagged Token]

-- | Parses one or more whitespace characters, producing a space token.
spaceTokP :: Parser Token
spaceTokP = whiteSpaceChar >> whiteSpace >> return SpaceTok

data Token = StringTok { getStringTokString :: String } | RemTok { getRemTokString :: String }
           | DataTok { getDataTokString :: String }
           | CommaTok | ColonTok | SemiTok | LParenTok | RParenTok
           | DollarTok | PercentTok
           | EqTok | NETok | LETok | LTTok | GETok | GTTok
           | PlusTok | MinusTok | MulTok | DivTok | PowTok
           | AndTok | OrTok | NotTok
           | BuiltinTok Builtin
           | LetTok | DimTok | OnTok | GoTok | SubTok | ReturnTok
           | IfTok | ThenTok | ForTok | ToTok | StepTok | NextTok
           | PrintTok | InputTok | RandomizeTok | ReadTok | RestoreTok
           | DefTok | FnTok | EndTok | StopTok
           | SpaceTok | DotTok | CharTok { getCharTokChar :: Char }
             deriving (Eq,Show)

caseInsensitiveChar :: Char -> Parser Char
caseInsensitiveChar c = char (toLower c) <|> char (toUpper c)

keyword :: String -> Parser String
keyword s = try (mapM caseInsensitiveChar s) <?> ("keyword " ++ s)

stringTokP :: Parser Token
-- no special escape chars allowed
stringTokP =
    do _ <- char '"'
       s <- manyTill anyChar (char '"')
       whiteSpace
       return (StringTok s)

isStringTok :: Token -> Bool
isStringTok (StringTok _) = True
isStringTok _ = False

remTokP :: Parser Token
remTokP = do _ <- keyword "REM"
             s <- many anyChar
             return (RemTok s)

isRemTok :: Token -> Bool
isRemTok (RemTok _) = True
isRemTok _ = False

dataTokP :: Parser Token
dataTokP =
    do _ <- keyword "DATA"
       s <- many anyChar
       return (DataTok s)

isDataTok :: Token -> Bool
isDataTok (DataTok _) = True
isDataTok _ = False

charTokP :: Parser Token
charTokP = do c <- legalChar; return (CharTok (toUpper c))

charTokTest :: (Char -> Bool) -> Token -> Bool
charTokTest f (CharTok c) = f c
charTokTest _ _ = False

taggedCharToksToString :: [Tagged Token] -> String
taggedCharToksToString = map (getCharTokChar . getTaggedVal)

strToTokAssoc :: [(String, Token)]
strToTokAssoc =
    [
     (",",         CommaTok),
     (":",         ColonTok),
     (";",         SemiTok),
     ("(",         LParenTok),
     (")",         RParenTok),
     ("$",         DollarTok),
     ("%",         PercentTok),
     ("=",         EqTok),
     ("<>",        NETok),
     ("<=",        LETok),
     ("<",         LTTok),
     (">=",        GETok),
     (">",         GTTok),
     ("+",         PlusTok),
     ("-",         MinusTok),
     ("*",         MulTok),
     ("/",         DivTok),
     ("^",         PowTok),
     (".",         DotTok),
     ("AND",       AndTok),
     ("OR",        OrTok),
     ("NOT",       NotTok),
     ("LET",       LetTok),
     ("DIM",       DimTok),
     ("ON",        OnTok),
     ("GO",        GoTok),
     ("SUB",       SubTok),
     ("RETURN",    ReturnTok),
     ("IF",        IfTok),
     ("THEN",      ThenTok),
     ("FOR",       ForTok),
     ("TO",        ToTok),
     ("STEP",      StepTok),
     ("NEXT",      NextTok),
     ("PRINT",     PrintTok),
     ("?",         PrintTok),
     ("INPUT",     InputTok),
     ("RANDOMIZE", RandomizeTok),
     ("READ",      ReadTok),
     ("RESTORE",   RestoreTok),
     ("DEF",       DefTok),
     ("FN",        FnTok),
     ("END",       EndTok),
     ("STOP",      StopTok)
  ] ++ [(s, BuiltinTok b) | (b,s) <- builtinToStrAssoc]

tokToStrAssoc :: [(Token, String)]
tokToStrAssoc = [(t,s) | (s,t) <- strToTokAssoc]

anyTokP :: Parser Token
anyTokP = choice ([spaceTokP, stringTokP, remTokP, dataTokP]
                  ++ [do _ <- keyword s; whiteSpace; return t | (s,t) <- strToTokAssoc]
                  ++ [charTokP]) <?> "LEGAL BASIC CHARACTER"

isBuiltinTok :: Token -> Bool
isBuiltinTok (BuiltinTok _) = True
isBuiltinTok _              = False

taggedTokenP :: Parser (Tagged Token)
taggedTokenP =
    do pos <- getPosition
       tok <- anyTokP
       return (Tagged pos tok)

-- | The main parser used to read a series of tokens from a string.
taggedTokensP :: Parser [Tagged Token]
taggedTokensP =
    do toks <- many taggedTokenP
       eof <?> ""
       return toks

-- | The single-token parser used at the parser level
tokenP :: (Token -> Bool) -> GenParser (Tagged Token) () (Tagged Token)
tokenP test = token (printToken . getTaggedVal) getPosTag testTaggedToken
    where testTaggedToken (Tagged pos tok) =
            if test tok then Just (Tagged pos tok) else Nothing

-- | Prettyprint a token for error reporting or debugging.
printToken :: Token -> String
printToken tok =
    case (lookup tok tokToStrAssoc) of
        (Just s) -> s
        Nothing ->
            case tok of
                (CharTok c) -> [c]
                (DataTok s) -> "DATA" ++ s
                (RemTok s) -> "REM" ++ s
                SpaceTok -> " "
                (StringTok s) -> "\"" ++ s ++ "\""
                _ -> error "printToken: unrecognized token."