vintage-basic-1.0: src/Language/VintageBasic/Parser.hs
-- | Parses BASIC source code, in tokenized form, to produce abstract syntax.
-- Also used at runtime to input values.
module Language.VintageBasic.Parser(exprP,statementListP) where
import Data.Char
import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Expr
import Language.VintageBasic.FloatParser
import Language.VintageBasic.LexCommon
import Language.VintageBasic.Syntax
import Language.VintageBasic.Tokenizer
-- | The number of significant letters at the start of a variable name.
varSignifLetters :: Int
varSignifLetters = 2
-- | The number of digits (following the letters) of a variable name that are significant.
varSignifDigits :: Int
varSignifDigits = 1
type TokParser = GenParser (Tagged Token) ()
skipSpace :: TokParser ()
skipSpace = skipMany $ tokenP (==SpaceTok)
lineNumP :: TokParser Int
lineNumP =
do s <- many1 (tokenP (charTokTest isDigit) <?> "") <?> "LINE NUMBER"
skipSpace
return (read (map (getCharTokChar . getTaggedVal) s))
-- LITERALS
floatLitP :: TokParser Literal
floatLitP =
do v <- floatP
skipSpace
return (FloatLit v)
stringLitP :: TokParser Literal
stringLitP =
do tok <- tokenP isStringTok
return (StringLit (getStringTokString (getTaggedVal tok)))
litP :: TokParser Literal
litP = floatLitP <|> stringLitP
-- VARIABLES
varBaseP :: TokParser String
varBaseP = do ls <- many1 (tokenP (charTokTest isAlpha))
ds <- many (tokenP (charTokTest isDigit))
return (taggedCharToksToString (take varSignifLetters ls
++ take varSignifDigits ds))
floatVarNameP :: TokParser VarName
floatVarNameP = do
name <- varBaseP
return (VarName FloatType name)
intVarNameP :: TokParser VarName
intVarNameP = do
name <- varBaseP
tokenP (==PercentTok)
return (VarName IntType name)
stringVarNameP :: TokParser VarName
stringVarNameP = do
name <- varBaseP
tokenP (==DollarTok)
return (VarName StringType name)
-- Look for string and int vars first because of $ and % suffixes.
varNameP :: TokParser VarName
varNameP = do
vn <- try stringVarNameP <|> try intVarNameP <|> floatVarNameP
skipSpace
return vn
scalarVarP :: GenParser (Tagged Token) () Var
scalarVarP = do
vn <- varNameP
return (ScalarVar vn)
arrVarP :: GenParser (Tagged Token) () Var
arrVarP = do
vn <- varNameP
xs <- argsP
return (ArrVar vn xs)
varP :: GenParser (Tagged Token) () Var
varP = try arrVarP <|> scalarVarP
-- BUILTINS
builtinXP :: TokParser Expr
builtinXP = do
(Tagged _ (BuiltinTok b)) <- tokenP isBuiltinTok
xs <- argsP
return (BuiltinX b xs)
-- EXPRESSIONS
litXP :: TokParser Expr
litXP =
do v <- litP
return (LitX v)
varXP :: TokParser Expr
varXP =
do v <- varP
return (VarX v)
argsP :: TokParser [Expr]
argsP =
do tokenP (==LParenTok)
xs <- sepBy exprP (tokenP (==CommaTok))
tokenP (==RParenTok)
return xs
fnXP :: TokParser Expr
fnXP = do
tokenP (==FnTok)
vn <- varNameP
args <- argsP
return (FnX vn args)
parenXP :: TokParser Expr
parenXP =
do tokenP (==LParenTok)
x <- exprP
tokenP (==RParenTok)
return (ParenX x)
primXP :: TokParser Expr
primXP = parenXP <|> litXP <|> builtinXP <|> fnXP <|> varXP
opTable :: OperatorTable (Tagged Token) () Expr
opTable =
[[prefix MinusTok MinusX, prefix PlusTok id],
[binary PowTok (BinX PowOp) AssocRight],
[binary MulTok (BinX MulOp) AssocLeft, binary DivTok (BinX DivOp) AssocLeft],
[binary PlusTok (BinX AddOp) AssocLeft, binary MinusTok (BinX SubOp) AssocLeft],
[binary EqTok (BinX EqOp) AssocLeft, binary NETok (BinX NEOp) AssocLeft,
binary LTTok (BinX LTOp) AssocLeft, binary LETok (BinX LEOp) AssocLeft,
binary GTTok (BinX GTOp) AssocLeft, binary GETok (BinX GEOp) AssocLeft],
[prefix NotTok NotX],
[binary AndTok (BinX AndOp) AssocLeft],
[binary OrTok (BinX OrOp) AssocLeft]]
binary :: Token -> (Expr -> Expr -> Expr) -> Assoc -> Operator (Tagged Token) () Expr
binary tok fun assoc =
Infix (do tokenP (==tok); return fun) assoc
prefix :: Token -> (Expr -> Expr) -> Operator (Tagged Token) () Expr
prefix tok fun =
Prefix (do tokenP (==tok); return fun)
-- | Parses a BASIC expression from tokenized source.
exprP :: TokParser Expr
exprP = buildExpressionParser opTable primXP
-- STATEMENTS
letSP :: TokParser Statement
letSP = do
optionally (tokenP (==LetTok))
v <- varP
tokenP (==EqTok)
x <- exprP
return (LetS v x)
gotoSP :: TokParser Statement
gotoSP = do
try (tokenP (==GoTok) >> tokenP (==ToTok))
n <- lineNumP
return (GotoS n)
gosubSP :: TokParser Statement
gosubSP = do
try (tokenP (==GoTok) >> tokenP (==SubTok))
n <- lineNumP
return (GosubS n)
returnSP :: TokParser Statement
returnSP =
do tokenP (==ReturnTok)
return ReturnS
onGotoSP :: TokParser Statement
onGotoSP = try $ do
tokenP (==OnTok)
x <- exprP
tokenP (==GoTok)
tokenP (==ToTok)
ns <- sepBy1 lineNumP (tokenP (==CommaTok))
return (OnGotoS x ns)
onGosubSP :: TokParser Statement
onGosubSP = try $ do
tokenP (==OnTok)
x <- exprP
tokenP (==GoTok)
tokenP (==SubTok)
ns <- sepBy1 lineNumP (tokenP (==CommaTok))
return (OnGosubS x ns)
ifSP :: TokParser Statement
ifSP =
do tokenP (==IfTok)
x <- exprP
tokenP (==ThenTok)
target <- try ifSPGoto <|> statementListP
return (IfS x target)
ifSPGoto :: TokParser [Tagged Statement]
ifSPGoto =
do pos <- getPosition
n <- lineNumP
return [Tagged pos (GotoS n)]
forSP :: TokParser Statement
forSP = do
tokenP (==ForTok)
vn <- varNameP
tokenP (==EqTok)
x1 <- exprP
tokenP (==ToTok)
x2 <- exprP
x3 <- option (LitX (FloatLit 1)) (tokenP (==StepTok) >> exprP)
return (ForS vn x1 x2 x3)
-- | Parses a @NEXT@ and an optional variable list.
nextSP :: TokParser Statement
nextSP = do
tokenP (==NextTok)
vns <- sepBy varNameP (tokenP (==CommaTok))
if length vns > 0
then return (NextS (Just vns))
else return (NextS Nothing)
printSP :: TokParser Statement
printSP =
do tokenP (==PrintTok)
xs <- many printExprP
return (PrintS xs)
printExprP :: TokParser Expr
printExprP = emptySeparatorP <|> nextZoneP <|> exprP
emptySeparatorP :: TokParser Expr
emptySeparatorP = do
tokenP (==SemiTok)
return EmptySeparatorX
nextZoneP :: TokParser Expr
nextZoneP = do
tokenP (==CommaTok)
return NextZoneX
inputSP :: TokParser Statement
inputSP =
do tokenP (==InputTok)
ps <- option Nothing inputPrompt
vs <- sepBy1 varP (tokenP (==CommaTok))
return (InputS ps vs)
inputPrompt :: TokParser (Maybe String)
inputPrompt =
do (StringLit p) <- stringLitP
tokenP (==SemiTok)
return (Just p)
endSP :: TokParser Statement
endSP =
do tokenP (==EndTok)
return EndS
stopSP :: TokParser Statement
stopSP =
do tokenP (==StopTok)
return StopS
arrDeclP :: TokParser (VarName, [Expr])
arrDeclP = do
vn <- varNameP
xs <- argsP
skipSpace
return (vn, xs)
dimSP :: TokParser Statement
dimSP = do
tokenP (==DimTok)
arrDecls <- sepBy1 arrDeclP (tokenP (==CommaTok))
return (DimS arrDecls)
randomizeSP :: TokParser Statement
randomizeSP = do
_ <- tokenP (==RandomizeTok)
return RandomizeS
readSP :: TokParser Statement
readSP = do
tokenP (==ReadTok)
vs <- sepBy1 varP (tokenP (==CommaTok))
return (ReadS vs)
restoreSP :: TokParser Statement
restoreSP = do
tokenP (==RestoreTok)
maybeLineNum <- optionally lineNumP
return (RestoreS maybeLineNum)
dataSP :: TokParser Statement
dataSP = do
(Tagged _ (DataTok s)) <- tokenP isDataTok
return (DataS s)
defFnSP :: TokParser Statement
defFnSP = do
tokenP (==DefTok)
tokenP (==FnTok)
name <- varNameP
tokenP (==LParenTok)
params <- sepBy1 varNameP (tokenP (==CommaTok))
tokenP (==RParenTok)
tokenP (==EqTok)
expr <- exprP
return (DefFnS name params expr)
remSP :: TokParser Statement
remSP =
do tok <- tokenP isRemTok
return (RemS (getRemTokString (getTaggedVal tok)))
statementP :: TokParser (Tagged Statement)
statementP = do
input <- getInput
let pos = getPosTag (head input)
st <- choice [printSP, inputSP, gotoSP, gosubSP, returnSP, onGotoSP, onGosubSP,
ifSP, forSP, nextSP, endSP, stopSP, randomizeSP, dimSP, readSP, restoreSP, dataSP,
remSP, defFnSP, letSP]
return (Tagged pos st)
-- | Parses a list of statements from a tokenized BASIC source line.
statementListP :: TokParser [Tagged Statement]
statementListP = do
many (tokenP (==ColonTok))
sl <- sepEndBy1 statementP (many1 (tokenP (==ColonTok)))
eol <?> ": OR END OF LINE"
return sl
anyTokenP :: TokParser (Tagged Token)
anyTokenP = tokenP (const True)
eol :: TokParser ()
eol = try (do {
tok <- anyTokenP;
unexpected (printToken (getTaggedVal tok));
} <|> return ()) <?> "END OF LINE"