cnc-spec-compiler-0.2.0.0: Intel/Cnc/Spec/CncGrammar.y
{
{-# LANGUAGE DeriveDataTypeable #-}
module Intel.Cnc.Spec.CncGrammar where
import Intel.Cnc.Spec.CncLexer hiding (main)
import Intel.Cnc.Spec.AST
import Intel.Cnc.Spec.SrcLoc
--import Data.Char
import StringTable.Atom
--import Data.Data
import Text.PrettyPrint.HughesPJClass
import Debug.Trace
}
-- (Based on example from Simon Marlow.)
-- These are similar macros to those used by the GHC parser:
-- define L0 L noSrcSpan
-- define L1 sL (getLoc $1)
-- Both arguments are Lexemes:
-- One or both arguments can be Decorated instead:
-- Here is a praticularly painful special case where we have a possibly
-- empty list on the right end. We take any source info that is there
-- and fall back to the second to last token otherwise.
-- For now we enable BOTH the new syntax and the legacy one:
-- First thing to declare is the name of your parser,
-- and the type of the tokens the parser reads.
%name parse_cnc
%tokentype { Lexeme }
-- The parser will be of type [Token] -> ?, where ? is determined by the
-- production rules. Now we declare all the possible tokens:
%token
tuple { L _ LVarId "tuple" }
var { L _ LVarId _ }
qvar { L _ LQVarId _ }
int { L _ LInteger _ }
"->" { L _ LReservedOp "->" }
"<-" { L _ LReservedOp "<-" }
"::" { L _ LReservedOp "::" }
-- ':' { L _ LReservedOp ":" }
':' { L _ LSpecial ":" }
'(' { L _ LSpecial "(" }
')' { L _ LSpecial ")" }
'[' { L _ LSpecial "[" }
']' { L _ LSpecial "]" }
';' { L _ LSpecial ";" }
',' { L _ LSpecial "," }
'<' { L _ LReservedOp "<" }
'>' { L _ LReservedOp ">" }
'=' { L _ LVarOp "=" }
'+' { L _ LVarOp "+" }
'-' { L _ LVarOp "-" }
-- '*' { L _ LVarOp "*" }
'*' { L _ LSpecial "*" }
'/' { L _ LVarOp "/" }
op { L _ LVarOp _ }
-- step { L _ LReservedId "step" }
mod { L _ LReservedId "module" }
tags { L _ LReservedId "tags" }
items { L _ LReservedId "items" }
steps { L _ LReservedId "steps" }
reductions { L _ LReservedId "reductions" }
dense { L _ LReservedId "dense" }
prescribes { L _ LReservedId "prescribes" }
type { L _ LReservedId "type" }
-- FUTURE WORK: Reserving space in the grammar for presently
-- unimplemented features [2011.04.12]:
constrain { L _ LReservedId "constrain" }
eof { L _ LEOF _ }
-- comment { L _ LComment _ }
-- The left hand side are the names of the terminals or tokens,
-- and the right hand side is how to pattern match them.
%nonassoc '<' '>' '<=' '>=' '==' '='
%left '+' '-'
%left '*' '/'
-- Like yacc, we include %% here, for no real reason.
%%
-- Now the production rules.
----------------------------------------------------------------------------------------------------
File :: { [PStatement SrcSpan] }
File : Statements { $1 }
Statements : Statement Statements { $1 ++ $2 }
| Statement { $1 }
| eof { [] }
-- Statement : Terminated_Relation { [$1] }
-- | Terminated_Decl { $1 }
-- Terminated_Relation : Relation ';' { $1 }
-- Terminated_Decl : Decl ';' { $1 }
Statement : Relation ';' { [$1] }
| Decl ';' { $1 }
| type var '=' Type ';' { [TypeDef (lexPointSpan $2) (tAL $2) $4] }
-- | type var Type ';' { [TypeDef (lexPointSpan $2) (tAL $2) $3] }
-- These are just for better errors:
| Relation eof { parseErrorSDoc (getDecor $1) $ text "Premature end of file, possible missing semi-colon." }
| Decl eof { parseErrorSDoc (getDecorLs $1) $ text "Premature end of file, possible missing semi-colon." }
-- reduce/reduce conflict
-- | Relation Instance { parseErrorSDoc (getDecor $2) $ text "Possible missing semi-colon." }
Decl :: { [PStatement SrcSpan] }
Decl
: steps VarLs { map (\x -> DeclareSteps (lexSpan $1) (tAL x)) $2 }
-- Here we try particularly hard to get good source location info:
| constrain Instance ':' TagExps { [Constraints (cLLS $1 (lexSpan $3) $4) $2 $4] }
-- One additional shift/reduce conflict if we do not use a separator:
| constrain Instance TagExps { [Constraints (cLLS $1 (getDecor $2) $3) $2 $3] }
--| constrain TagExps { [Constraints (lexSpan $1) (InstName "foo") []] }
--| constrain Instance { [Constraints (lexSpan $1) (InstName "foo") []] }
--| constrain var { [Constraints (lexSpan $1) (InstName "foo") []] }
{- #if 0 -}
| Mods tags var { [DeclareTags (cLL $2 $3) (tAL $3) Nothing] }
-- [2010.07.20] I am having a strange problem making Mods optional:
-- | Mods tags '<' Type '>' var { [DeclareTags (lexSpan $2) (lexStr $6) (Just $4)] }
| Mod Mods tags '<' Type '>' var { [DeclareTags (cLL $3 $7) (tAL $7) (Just $5)] }
| tags '<' Type '>' var { [DeclareTags (combineSrcSpans (lexSpan $1) (lexSpan $>)) (tAL $5) (Just $3)] }
-- TODO: Rearrange this so that the arbitrary numbers of template args
-- are accepted and an error is thrown if the wrong number are present:
| Mods items var { [DeclareItems (cLL $2 $3) (tAL $3) Nothing] }
| Mods items '<' Type ',' Type '>' var { [DeclareItems (cLL $2 $8) (tAL $8) (Just ($4, $6))] }
-- Reductions are similar to item collections except they are parameterized by a reduction op.
-- | Mods reductions var '(' var ')' { [DeclareReductions (cLL $2 $3) (tAL $3) (tAL $5) Nothing] }
-- | Mods reductions '<' Type ',' Type '>' var '(' var ')'
-- { [DeclareReductions (cLL $2 $8) (tAL $8) (tAL $10) (Just ($4, $6))] }
| Mods reductions var '(' var ',' Exp ')' { [DeclareReductions (cLL $2 $3) (tAL $3) (tAL $5) $7 Nothing] }
| Mods reductions '<' Type ',' Type '>' var '(' var ',' Exp ')'
{ [DeclareReductions (cLL $2 $10) (tAL $8) (tAL $10) $12 (Just ($4, $6))] }
| '<' Type var '>' { [DeclareTags (combineSrcSpans (lexSpan $1) (lexSpan $>)) (tAL $3) (Just $2)] }
-- Inexplicable problem with this TagExps version, maybe because of '>' not being special...
--| '<' Type var ':' TagExps '>' { [DeclareTags (lexSpan $3) (tAL $3) (Just $2)] }
| '<' Type var ':' VarsOnlyHack '>' { [DeclareTags (combineSrcSpans (lexSpan $1) (lexSpan $>)) (tAL $3) (Just $2)] }
| '[' Type var '<' Type '>' ':' TagExps ']' { [DeclareItems (combineSrcSpans (lexSpan $1) (lexSpan $>)) (tAL $3) (Just ($5, $2))] }
| '[' Type var '<' Type '>' ']' { [DeclareItems (combineSrcSpans (lexSpan $1) (lexSpan $>)) (tAL $3) (Just ($5, $2))] }
VarLs : var { [$1] }
| var ',' VarLs { $1 : $3 }
-- Modifier keywords can precede declarations.
Mods : {- empty -} { [] }
| Mod Mods { $1 : $2 }
Mod : dense { "dense" }
Relation :: { PStatement SrcSpan }
Relation : Instances Chain { Chain $1 $2 }
Chain :: { [RelLink SrcSpan] }
Chain : { [] }
-- | Link { [$1] }
| Link Chain { $1 : $2 }
Link : "->" Instances { ProduceLink (lexSpan $1) $2 }
| "<-" Instances { RevProduceLink (lexSpan $1) $2 }
| "::" Instances { PrescribeLink (lexSpan $1) $2 }
| prescribes Instances { PrescribeLink (lexSpan $1) $2 }
Instances :: { [CollectionInstance SrcSpan] }
Instances
: { [] }
| Instance { [$1] }
| Instance ',' Instances { $1 : $3 }
Instance
: var { InstName (lexSpan $1) (lexStr $1) }
| var '[' TagExps ']' { InstItemCol (combineSrcSpans (lexSpan $1) (lexSpan $>)) (lexStr $1) $3 }
-- | var '<' TagExps '>' { InstStepOrTags (combineSrcSpans (lexSpan $1) (lexSpan $>)) (lexStr $1) $3 }
| var '(' TagExps ')' { InstStepOrTags (combineSrcSpans (lexSpan $1) (lexSpan $>)) (lexStr $1) $3 }
| '<' Var '>' { InstTagCol (combineSrcSpans (lexSpan $1) (lexSpan $>)) (lexStr $2) [] }
| '(' Var ')' { InstStepCol (combineSrcSpans (lexSpan $1) (lexSpan $>)) (lexStr $2) [] }
| '[' Var ']' { InstItemCol (combineSrcSpans (lexSpan $1) (lexSpan $>)) (lexStr $2) [] }
-- TEMP FIXME: Again, problem here with full tag exps:
| '<' Var ':' VarsOnlyHack '>' { InstTagCol (combineSrcSpans (lexSpan $1) (lexSpan $>)) (lexStr $2) $4 }
| '(' Var ':' TagExps ')' { InstStepCol (combineSrcSpans (lexSpan $1) (lexSpan $>)) (lexStr $2) $4 }
| '[' Var ':' TagExps ']' { InstItemCol (combineSrcSpans (lexSpan $1) (lexSpan $>)) (lexStr $2) $4 }
TagExps :: { [Exp SrcSpan] }
TagExps : { [] }
| Exp { [$1] }
| Exp ',' TagExps { $1 : $3 }
-- TEMP, HACK:
VarsOnlyHack : { [] }
| var { [Var (lexSpan $1) (tAL $1)] }
| var ',' VarsOnlyHack { Var (lexSpan $1) (tAL $1) : $3 }
-- This is just for catching errors:
Var : var { $1 }
-- This error checking must be introduced CAREFULLY or it can yield errors. Not every position can be checked.
-- Sadly, this error checking should go EVERYWHERE:
| tags { parseErrorSDoc (lexSpan $1) $ text "Keyword 'tags' used incorrectly." }
| items { parseErrorSDoc (lexSpan $1) $ text "Keyword 'items' used incorrectly." }
| reductions { parseErrorSDoc (lexSpan $1) $ text "Keyword 'reductions' used incorrectly." }
| steps { parseErrorSDoc (lexSpan $1) $ text "Keyword 'steps' used incorrectly." }
| dense { parseErrorSDoc (lexSpan $1) $ text "Keyword 'dense' used incorrectly." }
| prescribes { parseErrorSDoc (lexSpan $1) $ text "Keyword 'prescribes' used incorrectly." }
| constrain { parseErrorSDoc (lexSpan $1) $ text "Keyword 'constrain' used incorrectly." }
| mod { parseErrorSDoc (lexSpan $1) $ text "Keyword 'module' used incorrectly." }
-- | step { parseErrorSDoc (lexSpan $1) $ text "Keyword 'step' used incorrectly." }
Exp :: { Exp SrcSpan } -- The haskell type of the result of parsing this syntax class.
Exp : var { Var (lexSpan $1) (tAL $1) }
| qvar { Var (lexSpan $1) (tAL $1) }
| int { Lit (lexSpan $1) (LitInt $ read (lexStr $1)) }
| '(' Exp ')' { $2 }
-- Function application:
| var '(' TagExps ')' { App (combineSrcSpans (lexSpan $1) (getDecor (last $3)))
(Var (lexSpan $1) (tAL $1)) $3 }
-- Tuples? *** TODO ***.
-- Including explicit productions for arithmetic just to handle precedence/associativity:
| Exp '+' Exp { App (combExpSpans $1 $3) (Var (combineSrcSpans (getDecor $1) (getDecor $>)) (toAtom "+")) [$1, $3] }
| Exp '-' Exp { App (combExpSpans $1 $3) (Var (combineSrcSpans (getDecor $1) (getDecor $>)) (toAtom "-")) [$1, $3] }
| Exp '*' Exp { App (combExpSpans $1 $3) (Var (combineSrcSpans (getDecor $1) (getDecor $>)) (toAtom "*")) [$1, $3] }
| Exp '/' Exp { App (combExpSpans $1 $3) (Var (combineSrcSpans (getDecor $1) (getDecor $>)) (toAtom "/")) [$1, $3] }
-- These need to be handled because they are lexed differently, being reserved characters:
| Exp '<' Exp { App (combExpSpans $1 $3) (Var (combineSrcSpans (getDecor $1) (getDecor $>)) (toAtom "<")) [$1, $3] }
| Exp '>' Exp { App (combExpSpans $1 $3) (Var (combineSrcSpans (getDecor $1) (getDecor $>)) (toAtom ">")) [$1, $3] }
| Exp op Exp { App (combExpSpans $1 $3) (Var (combineSrcSpans (getDecor $1) (getDecor $>)) (tAL $2)) [$1, $3] }
Type
: var { TSym (toAtom $ lexStr $1) }
| dense Type { TDense $2 }
| Type '*' { TPtr $1 }
| '(' Types ')' { TTuple $2 }
| tuple '<' Types '>' { TTuple $3 }
Types : { [] }
| Type { [$1] }
| Type ',' Types { $1 : $3 }
-- We are simply returning the parsed data structure! Now we need
-- some extra code, to support this parser, and make in complete:
----------------------------------------------------------------------------------------------------
{
-- Combine two lexical tokens for a source span:
cLL a b = (lexSpan a) `combineSrcSpans` (lexSpan b)
-- more obscure, combine two tokens and the first (if it exists) of a list:
cLLS a b c = combineSrcSpans (lexSpan a) $ combineSrcSpans b (getDecorLs c)
getDecorLs [] = srcLocSpan noSrcLoc
getDecorLs (h:t) = getDecor h
-- All parsers must declair this function, which is called when an error
-- is detected. Note that currently we do no error recovery.
happyError :: [Lexeme] -> a
happyError [] = error "Parse error. Strange - it's not before any token that I know of..."
happyError ls =
let loc = lexLoc $ head ls in
error$ "Parse error before token at location : \n " ++
show (pPrint loc) ++
(if srcColumn loc <= 1
then "\n(An error at the beginning of the line like this could be a missing semi-colon on the previous line.)\n"
else "")
parseErrorSDoc span doc =
error $ show $ text "\n\nPARSE ERROR!\n " <> doc $$
text "At location: " <> pPrint span
-- Now we declare the datastructure that we are parsing.
runCncParser :: String -> String -> [PStatement SrcSpan]
runCncParser file str =
let notcomment = filter (not . is_comment) $ scan_to_list str in
-- FIXME:
-- Here's a hack that's a bit ineffecient. We POST-FACTO put the right filename in the
-- sourceloc decorations. It would be better to do it right the first time.
--
-- NOTE [2010.07.23] This has another problem. Parse errors will say "unknown file".
-- I guess I need to thread through a reader monad.
map (mapDecor (srcSpanSetFileName file)) $
-- For now filter out comments before parsing:
if null notcomment
then error "ERROR: Specification file contains no CnC statements!"
else parse_cnc $ notcomment
is_comment ( L _ LComment _ ) = True
is_comment _ = False
lexStr :: Lexeme -> String
lexStr (L _ _ str) = str
tAL = toAtom . lexStr
lexLoc :: Lexeme -> SrcLoc
lexLoc (L (AlexPn n l c) _ _) = (SrcLoc "" l c)
lexSpan :: Lexeme -> SrcSpan
-- [2010.07.23] We can do a little better by looking at the length of
-- the string and stretching the src location to include all of it.
-- DANGER, we assume that Lexemes stay on one line!! (not true of multiline comments)
lexSpan (L (AlexPn n l c) _ str) =
let start = mkSrcLoc "" l c
end = mkSrcLoc "" l (c + length str)
in srcLocSpan start `combineSrcSpans` srcLocSpan end
-- Not a span, just a single point:
--lexPointSpan (L (AlexPn n l c) _ _) = srcLocSpan (SrcLoc "unknownfile" l c)
lexPointSpan = srcLocSpan . lexLoc
-- Combine the spans in two expressions.
combExpSpans e1 e2 = combineSrcSpans (getDecor e1) (getDecor e2)
quit = print "runCnc failed\n"
}