grammar Piggly

rule start
  tSpace?
  block ';'?
  tSpace?
end

rule block
  label_open:tLabelDefinition tSpace
  blockDeclarations:stmtDeclare*
  kwBEGIN
    bodySpace:tSpace
    bodyStub:stubNode
    body:statement*
  blockExceptions?
  kwEND
    ( tSpace label_close:tLabel )?
    tSpace? <Piggly::Parser::Nodes::Block>
  /
  blockDeclarations:stmtDeclare*
  kwBEGIN
    bodySpace:tSpace
    bodyStub:stubNode
    body:statement*
  blockExceptions?
  kwEND
    tSpace? <Piggly::Parser::Nodes::Block>
end

rule statement
  inner:( block ';'
        / stmtAssignment
        / stmtIf
        / stmtCase
        / stmtLoop
        / stmtWhileLoop
        / stmtForLoop
        / stmtForEachLoop
        / stmtExit
        / stmtContinue
        / stmtReturn
        / stmtRaise
        / stmtExecSql
        / stmtNull
        / stmtGetDiag )
  tail:tSpace? <Piggly::Parser::Nodes::Statement>
end

rule stmtAssignment
  lval:lValue tSpace? kwASSIGN ws?
  rval:expressionUntilSemiColon ';' <Piggly::Parser::Nodes::Assignment>
end

rule stmtCase
  kwCASE tSpace
  cases:condWhen+
  else:stmtElse?
  kwEND tSpace kwCASE
    tSpace? ';' <Piggly::Parser::Nodes::Cond>
  /
  kwCASE tSpace
  expr:expressionUntilWhen
  cases:caseWhen+
  else:stmtElse?
  kwEND tSpace kwCASE
    tSpace? ';' <Piggly::Parser::Nodes::Case>
end

rule stmtIf
  kwIF
    condSpace:tSpace?
    condStub:stubNode
    cond:expressionUntilThen
  kwTHEN
    bodySpace:tSpace
    bodyStub:stubNode
    body:statement*
    else:stmtElse?
  kwEND tSpace kwIF tSpace? ';' <Piggly::Parser::Nodes::If>
end

rule stmtElse
  kwELSIF
    condSpace:tSpace?
    condStub:stubNode
    cond:expressionUntilThen
  kwTHEN
    bodySpace:tSpace
    bodyStub:stubNode
    body:statement*
    else:stmtElse? <Piggly::Parser::Nodes::If>
  /
  kwELSE
    bodySpace:tSpace
    bodyStub:stubNode
    body:statement* <Piggly::Parser::Nodes::Else>
end

rule stmtLoop
  label_open:tLabelDefinition tSpace
  cond:kwLOOP
    bodySpace:tSpace
    bodyStub:stubNode
    body:statement*
    doneStub:stubNode
  kwEND tSpace kwLOOP
    ( tSpace label_close:tLabel <Piggly::Parser::Nodes::TLabel> )?
    tSpace? ';' exitStub:stubNode <Piggly::Parser::Nodes::Loop>
  /
  cond:kwLOOP
    bodySpace:tSpace
    bodyStub:stubNode
    body:statement*
    doneStub:stubNode
  kwEND tSpace kwLOOP
    tSpace? ';' exitStub:stubNode <Piggly::Parser::Nodes::Loop>
end

rule stmtWhileLoop
  label_open:tLabelDefinition tSpace
  kwWHILE
    condSpace:tSpace?
    condStub:stubNode
    cond:expressionUntilLoop
  kwLOOP
    bodySpace:tSpace
    bodyStub:stubNode
    body:statement*
  kwEND tSpace kwLOOP
    ( tSpace label_close:tLabel <Piggly::Parser::Nodes::TLabel> )?
    tSpace? ';' <Piggly::Parser::Nodes::WhileLoop>
  /
  kwWHILE
    condSpace:tSpace?
    condStub:stubNode
    cond:expressionUntilLoop
  kwLOOP
    bodySpace:tSpace
    bodyStub:stubNode
    body:statement*
  kwEND tSpace kwLOOP
    tSpace? ';' <Piggly::Parser::Nodes::WhileLoop>
end

rule stmtForEachLoop
  label_open:tLabelDefinition tSpace
  kwFOREACH tSpace tIdentifier tSpace
  kwIN tSpace kwARRAY
    condSpace:tSpace
    cond:expressionUntilLoop
  kwLOOP
    bodySpace:tSpace
    bodyStub:stubNode
    body:statement*
    doneStub:stubNode
  kwEND tSpace kwLOOP
    ( tSpace label_close:tLabel <Piggly::Parser::Nodes::TLabel> )?
    tSpace? ';' exitStub:stubNode <Piggly::Parser::Nodes::ForEachLoop>
  /
  kwFOREACH tSpace tIdentifier tSpace
  kwIN tSpace kwARRAY
    condSpace:tSpace
    cond:expressionUntilLoop
  kwLOOP
    bodySpace:tSpace
    bodyStub:stubNode
    body:statement*
    doneStub:stubNode
  kwEND tSpace kwLOOP
    tSpace? ';' exitStub:stubNode <Piggly::Parser::Nodes::ForEachLoop>
end

rule stmtForLoop
  label_open:tLabelDefinition tSpace
  kwFOR tSpace identifierList tSpace
  kwIN
    condSpace:tSpace
    cond:( stmtForSql / expressionUntilLoop )
  kwLOOP
    bodySpace:tSpace
    bodyStub:stubNode
    body:statement*
    doneStub:stubNode
  kwEND tSpace kwLOOP
    ( tSpace label_close:tLabel <Piggly::Parser::Nodes::TLabel> )?
    tSpace? ';' exitStub:stubNode <Piggly::Parser::Nodes::ForLoop>
  /
  kwFOR tSpace identifierList tSpace
  kwIN
    condSpace:tSpace
    cond:( stmtForSql / expressionUntilLoop )
  kwLOOP
    bodySpace:tSpace
    bodyStub:stubNode
    body:statement*
    doneStub:stubNode
  kwEND tSpace kwLOOP
    tSpace? ';' exitStub:stubNode <Piggly::Parser::Nodes::ForLoop>
end

rule stmtForSql
  sqlKeyword tSpace expressionUntilLoop <Piggly::Parser::Nodes::Sql>
end

rule stmtExit
  bodyStub:stubNode
  body:( kwEXIT (tSpace label:tLabel <Piggly::Parser::Nodes::TLabel> )? )
  tSpace? ';' <Piggly::Parser::Nodes::Exit>
  /
  body:( kwEXIT (tSpace label:tLabel <Piggly::Parser::Nodes::TLabel> )? )
  tSpace
  kwWHEN
    condSpace:tSpace
    condStub:stubNode
    cond:expressionUntilSemiColon ';' <Piggly::Parser::Nodes::ExitWhen>
end

rule stmtContinue
  bodyStub:stubNode
  body:( kwCONTINUE (tSpace label:tLabel <Piggly::Parser::Nodes::TLabel> )? )
    tSpace? ';' <Piggly::Parser::Nodes::Continue>
  /
  body:( kwCONTINUE (tSpace label:tLabel <Piggly::Parser::Nodes::TLabel> )? )
    tSpace
  kwWHEN
    condSpace:tSpace
    condStub:stubNode
    cond:expressionUntilSemiColon ';' <Piggly::Parser::Nodes::ContinueWhen>
end

rule stmtReturn
  bodyStub:stubNode
  body:(kwRETURN ( tSpace? &';'
                 / tSpace kwNEXT tSpace expressionUntilSemiColon tSpace?
                 / tSpace kwQUERY tSpace expressionUntilSemiColon
                 / expressionUntilSemiColon )) ';' <Piggly::Parser::Nodes::Return>
end

rule stmtRaise
  kwRAISE tSpace
  level:( kwWARNING
        / kwNOTICE
        / kwINFO
        / kwLOG
        / kwDEBUG )
  ( tSpace expr:expressionUntilSemiColon )? ';' <Piggly::Parser::Nodes::Raise>
  /
  bodyStub:stubNode
  body:(kwRAISE ( tSpace level:kwEXCEPTION tSpace? )? expr:expressionUntilSemiColon) ';' <Piggly::Parser::Nodes::Throw>
end

rule stmtExecSql
  sqlKeyword tSpace expressionUntilSemiColon ';' <Piggly::Parser::Nodes::Sql>
end

rule stmtGetDiag
  kwGET tSpace ( kwSTACKED tSpace )? kwDIAGNOSTICS tSpace expressionUntilSemiColon ';' <Piggly::Parser::Nodes::Expression>
end

rule stmtNull
  kwNULL tSpace? ';'
end

#############################################################################

rule stmtDeclare
  kwDECLARE tSpace varDeclaration*
end

rule varDeclaration
  name:tIdentifier tSpace ( kwCONSTANT tSpace )?
  type:tType ( tSpace  kwNOT tSpace kwNULL )?
             ( tSpace? kwASSIGN tSpace? rval:expressionUntilSemiColon )?
  tSpace?
  ';' tSpace?
end

rule identifierList
  tIdentifier ( tSpace? ',' tSpace? tIdentifier )*
end

rule blockExceptions
  kwEXCEPTION tSpace cases:exceptionCase*
end

rule exceptionCase
  # cannot provide branch-coverage on cond
  kwWHEN
    condSpace:tSpace
    cond:expressionUntilThen
  kwTHEN
    bodySpace:tSpace
    bodyStub:stubNode
    body:statement* <Piggly::Parser::Nodes::Catch>
end

rule caseWhen
  # cannot provide branch-coverage on cond
  kwWHEN
    condSpace:tSpace
    cond:expressionUntilThen
  kwTHEN
    bodySpace:tSpace
    bodyStub:stubNode
    body:statement* <Piggly::Parser::Nodes::CaseWhen>
end

rule condWhen
  kwWHEN
    condSpace:tSpace
    condStub:stubNode
    cond:expressionUntilThen
  kwTHEN
    bodySpace:tSpace
    bodyStub:stubNode
    body:statement* <Piggly::Parser::Nodes::CondWhen>
end

## EXPRESSIONS
#############################################################################

rule expressionUntilSemiColon
  head:tSpace? expr:( tString / skipWords / tSpace !';' / !tSpace [^;] )*
  tail:tSpace? &';' <Piggly::Parser::Nodes::Expression>
end

rule expressionUntilClosingBracket
  head:tSpace? expr:( tString / skipWords / tSpace !']' / !tSpace [^\]] )+
  tail:tSpace? &']' <Piggly::Parser::Nodes::Expression>
end

rule expressionUntilThen
  head:tSpace? expr:( tString / skipWords / tSpace !kwTHEN / !tSpace !kwTHEN . )+
  tail:tSpace &kwTHEN <Piggly::Parser::Nodes::Expression>
end

rule expressionUntilWhen
  head:tSpace? expr:( tString / skipWords / tSpace !kwWHEN / !tSpace !kwWHEN . )+
  tail:tSpace &kwWHEN <Piggly::Parser::Nodes::Expression>
end

rule expressionUntilLoop
  head:tSpace? expr:( tString / skipWords / tSpace !kwLOOP / !tSpace !kwLOOP . )+
  tail:tSpace &kwLOOP <Piggly::Parser::Nodes::Expression>
end

rule skipWords
  [a-z0-9_]+ <Piggly::Parser::Nodes::TextNode>
end

#############################################################################

rule ws
  [ \t\n\v\f\r]+ <Piggly::Parser::Nodes::TextNode>
end

rule dollarQuoteMarker
  '$' tag:( [a-z\200-\377_] [a-z\200-\377_0-9]* )? '$' <Piggly::Parser::Nodes::TDollarQuoteMarker>
end

rule stubNode
  '' <Piggly::Parser::Nodes::StubNode>
end

rule notImplemented
  '' <Piggly::Parser::Nodes::NotImplemented>
end

rule lValue
  tIdentifier
    ( '[' expressionUntilClosingBracket ']' )*
    ( [.] lValue )*

  <Piggly::Parser::Nodes::Assignable>
end

## BASIC TOKENS
#############################################################################

rule tEOF
  !.
end

rule tLabelDefinition
  '<<' tSpace? tLabel tSpace? '>>' <Piggly::Parser::Nodes::TLabel>
end

rule tLabel
  tIdentifier
end

rule tIdentifier
  ( '"' [^"]+ '"' )+ <Piggly::Parser::Nodes::TIdentifier>
  / !keyword ( [a-z\200-\377_] [a-z\200-\377_0-9$]* ) <Piggly::Parser::Nodes::TIdentifier>
end

# not context sensitive so opening and closing tag might not match
rule tString
  openTag:dollarQuoteMarker content:(!dollarQuoteMarker .)* closeTag:dollarQuoteMarker <Piggly::Parser::Nodes::TString> /
  "E'" content:("''" / [^'])* "'" <Piggly::Parser::Nodes::TString> /
  "'"  content:("''" / [^'])* "'" <Piggly::Parser::Nodes::TString>
end

# beware this is quite broad and might match things it shouldn't
rule tType
  [a-z\200-\377_]
  ( '(' rType ')' /
    '[' rType ']' /
    [a-z\200-\377_0-9$%]+ '.'? /
    ws !( kwAS / kwNOT / kwASSIGN / kwDEFAULT ) )* <Piggly::Parser::Nodes::TDatatype>
end

rule rType
  ( '(' rType ')' /
    '[' rType ']' /
    [^\(\)\[\]]+ )*
end

rule tSpace
  ws !tComment <Piggly::Parser::Nodes::TextNode>
  /
  ( ws / tComment )+
end

rule tComment
  '/*' content:(!'*/' .)* '*/' <Piggly::Parser::Nodes::TComment> /
  '--' content:[^\n]* ("\n" / tEOF) <Piggly::Parser::Nodes::TComment>
end

##
#############################################################################

rule tBinary
  "b'" [01]+ "'"
end

rule tHex
  "x'" [0123456789abcdef]+ "'"
end

rule tNumber
  tBinary / tHex /
  sign:[+-]?        '.' [0-9]+ exponent:('e' [+-]? [0-9]+)? /
  sign:[+-]? [0-9]+ '.' [0-9]* exponent:('e' [+-]? [0-9]+)? /
  sign:[+-]? [0-9]+ '.'?       exponent:('e' [+-]? [0-9]+)?
end

rule tLiteral
  tString (tSpace? '::' tSpace? tType) /
  tString /
  tNumber (tSpace? '::' tSpace? tType) /
  tNumber /
  'cast' tSpace? '(' tSpace? tString tSpace kwAS tSpace tType tSpace? ')' /
  'cast' tSpace? '(' tSpace? tNumber tSpace kwAS tSpace tType tSpace? ')'
end

## KEYWORDS
#############################################################################

rule sqlKeyword
  ( 'insert'
  / 'select'
  / 'update'
  / 'delete'
  / 'perform'
  / 'execute'
  / 'open'
  / 'close'
  / 'lock'
  / 'fetch'
  / 'move'
  / 'truncate'
  / 'create'
  / 'drop'
  / 'alter'
  / 'commit'
  / 'copy'
  / 'create'
  / 'set'
  / 'start'
  / 'notify'
  / 'with' ) ![a-z0-9] <Piggly::Parser::Nodes::TKeyword>
end

rule keyword
  # WHEN is checked first as a minor optimization to distinguish
  #   CONTINUE tIdentifier WHEN cond;
  #   EXIT tIdentifier WHEN cond;
  #
  #   CONTINUE WHEN cond;
  #   EXIT WHEN cond;

  kwWHEN / kwAS / kwASSIGN / kwALIAS / kwBEGIN / kwBY / kwCASE / kwARRAY
  / kwCONSTANT / kwCONTINUE / kwCURSOR / kwDEBUG / kwDECLARE / kwDEFAULT
  / kwDIAGNOSTICS / kwELSE / kwELSIF / kwEND / kwEXCEPTION / kwEXECUTE / kwEXIT
  / kwFOR / kwFROM / kwGET / kwIF / kwIN / kwINFO / kwINSERT / kwINTO
  / kwIS / kwLOG / kwLOOP / kwNOT / kwNOTICE / kwNULL / kwFOREACH
  / kwOR / kwRAISE / kwRENAME / kwRESULTOID / kwRETURN
  / kwREVERSE / kwROWCOUNT / kwSCROLL / kwSTRICT / kwTHEN / kwTO / kwTYPE
  / kwWARNING / kwWHILE
end

# this terminates keywords
rule x
  [^a-z0-9_]
end

rule kwAS
  'as' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwASSIGN
  ':=' <Piggly::Parser::Nodes::TKeyword> / '=' <Piggly::Parser::Nodes::TKeyword>
end

rule kwALIAS
  'alias' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwARRAY
  'array' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwBEGIN
  'begin' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwBY
  'by' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwCASE
  'case' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwCONSTANT
  'constant' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwCONTINUE
  'continue' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwCURSOR
  'cursor' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwDEBUG
  'debug' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwDECLARE
  'declare' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwDEFAULT
  'default' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwDIAGNOSTICS
  'diagnostics' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwELSE
  'else' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwELSIF
  ('elsif'/'elseif') ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwEND
  'end' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwEXCEPTION
  'exception' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwEXECUTE
  'execute' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwEXIT
  'exit' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwFOR
  'for' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwFOREACH
  'foreach' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwFROM
  'from' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwGET
  'get' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwIF
  'if' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwIN
  'in' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwINFO
  'info' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwINSERT
  'insert' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwINTO
  'into' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwIS
  'is' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwLOG
  'log' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwLOOP
  'loop' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwNEXT
  'next' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwNOT
  'not' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwNOTICE
  'notice' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwNULL
  'null' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwOR
  'or' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwPERFORM
  'perform' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwQUERY
  'query' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwRAISE
  'raise' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwRENAME
  'rename' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwRESULTOID
  'result_oid' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwRETURN
  'return' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwREVERSE
  'reverse' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwROWCOUNT
  'row_count' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwSCROLL
  'scroll' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwSTACKED
  'stacked' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwSTRICT
  'strict' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwTHEN
  'then' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwTO
  'to' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwTYPE
  'type' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwWARNING
  'warning' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwWHEN
  'when' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

rule kwWHILE
  'while' ![a-z0-9_] <Piggly::Parser::Nodes::TKeyword>
end

end