/'* \file fb-doc_parser.bas
\brief Source code for the \ref Parser class.

This file contains the source code for the Parser class. It's used 
to scan the FB input for relevant constructs and call the matching 
function in the \ref EmitterIF.

'/

#INCLUDE ONCE "fb-doc_parser.bi"


/'* \brief The constructor
\param Em a pointer to the emitter interface to use

Initialize the start values. We get the pointer to the EmitterIF to 
use and we create a short token list (just two entries) for usage 
inside the parser. This token list is static and it doesn't change 
its adress (unlike the #Parser::Tok list that may shift when growing).

'/
CONSTRUCTOR Parser(BYVAL Em AS EmitterIF PTR)
  STATIC AS INTEGER ToLi(...) = {0, 0, 0, 0, 0, 0}

  Emit = Em
  StaTok = @ToLi(0)
  A = @ToLi(1)
  L = @ToLi(2)
END CONSTRUCTOR


/'* \brief Snippet to skip to the next token '/
#DEFINE SKIP Tk += 3


/'* \brief Move to the next comma and one step beyond
\returns the (doubled) number of steps gone in the token list (or 
          MSG_ERROR on error and MSG_STOP on the end of the token list)

We step trough the token list and search for the next comma, 
skipping nested pairs of parenthesis. When we find a comma we walk 
one step beyond. Otherwise we stop at the next right parenthesis or 
the next colon or line end.

'/
FUNCTION Parser.skipOverComma() AS INTEGER
  VAR t = Tk, kl = 0
  WHILE Tk < TkEnd
    SELECT CASE AS CONST *Tk
    CASE MSG_ERROR, TOK_EOS :                            RETURN Tk - t
    CASE TOK_COMMA : IF kl <= 0 THEN              SKIP : RETURN Tk - t
    CASE TOK_BROPN, TOK_KLOPN : kl += 1
    CASE TOK_BRCLO, TOK_KLCLO : kl -= 1 : IF kl < 0 THEN RETURN Tk - t
    END SELECT : SKIP
  WEND
END FUNCTION


/'* \brief Move to the matching right parenthesis and one step beyond
\returns the (doubled) number of steps gone in the token list (or 
          MSG_ERROR on error and MSG_STOP on the end of the token list)

Step through the token list and search for the next right 
parenthesis, skipping nested pairs of paranethesis. When we find a
matching right parenthesis we walk one step beyond. Else we 
stop at the next colon or line end.

'/
FUNCTION Parser.skipOverBrclo() AS INTEGER
  VAR t = Tk, kl = 0
  WHILE Tk < TkEnd
    SELECT CASE AS CONST *Tk
    CASE MSG_ERROR, TOK_EOS                         : RETURN Tk - t
    CASE TOK_BROPN : kl += 1
    CASE TOK_BRCLO : kl -= 1 : IF kl <= 0 THEN SKIP : RETURN Tk - t
    END SELECT : SKIP
  WEND
END FUNCTION


/'* \brief Move to the end of statement and one step beyond
\returns the (doubled) number of steps gone in the token list (or 
          MSG_ERROR on error and MSG_STOP on the end of the token list)

Step through the token list and search for the end of the current 
statement. Then walk one step beyond, if not end of token list.

'/
FUNCTION Parser.skipOverColon() AS INTEGER
  VAR t = Tk, fl = 0
  WHILE Tk < TkEnd
    IF *Tk = TOK_EOS THEN fl = 1 ELSE IF fl THEN RETURN Tk - t
    SKIP
  WEND : RETURN Tk - t
END FUNCTION


/'* \brief Evaluate a name, dimension and initializer in the token list
\param MinTk the token to start at
\param DeclMod the modus (normal or declaration)
\returns the (doubled) number of steps gone in the token list (or 
          MSG_ERROR on error and MSG_STOP on the end of the token list)

Check the token list for a declaration of a name, starting at the 
current token (ie behind a VAR keyword). Also checking for 
parenthesis to specify a dimension and the equal '=' character of an 
initializer. After execution the current position is at the next 
token after the equal character, or behind the closing parenthesis, 
or the name. An error is returned if there is no word at the current 
position.

'/
FUNCTION Parser.demuxNam(BYVAL MinTk AS INTEGER = TOK_WORD, BYVAL DeclMod AS INTEGER = 0) AS INTEGER
  DimTok = 0
  IniTok = 0
  BitTok = 0
  
  IF *Tk >= MinTk THEN NamTok = Tk           ELSE NamTok = 0 : RETURN MSG_ERROR
  DO
    SELECT CASE AS CONST *Tk
    CASE TOK_BROPN, TOK_EQUAL
                 IF Tk > NamTok THEN EXIT DO ELSE NamTok = 0 : RETURN MSG_ERROR
    CASE  TOK_DOT : IF *(Tk + 3) < MinTk     THEN NamTok = 0 : RETURN MSG_ERROR
    CASE TOK_WORD : IF *(Tk + 3) <> TOK_DOT THEN SKIP : EXIT DO
    CASE ELSE
      IF LevelCount = 0 ORELSE *Tk < MinTk   THEN NamTok = 0 : RETURN MSG_ERROR
      SKIP : EXIT DO
    END SELECT : SKIP
  LOOP : IF DeclMod THEN                                       RETURN Tk - NamTok

  IF *Tk = TOK_BROPN THEN DimTok = Tk : skipOverBrclo()
  IF *Tk = TOK_EQUAL THEN IniTok = Tk                        : RETURN Tk - NamTok
  IF 0 = LevelCount ORELSE _
     *Tk <> TOK_EOS ORELSE _
     Buf[*(Tk + 1)] <> ASC(":") THEN                           RETURN Tk - NamTok

  VAR x = Tk + 3 '                        check for bitfield declaration
  IF *x >= MinTk THEN
    x += 3
    IF *x = TOK_EOS ANDALSO Buf[*(x + 1)] <> ASC(":") THEN _
                                        BitTok = Tk : Tk = x : RETURN Tk - NamTok
  END IF

  SELECT CASE AS CONST *x
  CASE TOK_EQUAL : BitTok = Tk : Tk = x : IniTok = x
  CASE TOK_COMMA : BitTok = Tk : Tk = x
  CASE ELSE 
    FOR i AS INTEGER = *(Tk + 1) + 1 TO *(Tk + 4) - 1
      SELECT CASE AS CONST Buf[i]
      CASE ASC(" "), ASC(!"\t"), ASC(!"\v")
      CASE ASC("0") TO ASC("9") : BitTok = Tk : EXIT FOR
      CASE ELSE : EXIT FOR
      END SELECT
    NEXT
  END SELECT : RETURN Tk - NamTok
END FUNCTION


/'* \brief Evaluate a function declaration in the token list
\returns the the result of demuxTyp() (or MSG_ERROR on error and 
          MSG_STOP on the end of the token list)

Pre-check the token list for a declaration of a function, starting at
the token behind the DECLARE statement. When OK, use demuxTyp() to 
evaluate the type of the declaration.

'/
FUNCTION Parser.demuxDecl() AS INTEGER
  SELECT CASE AS CONST *Tk
  CASE TOK_CONS, TOK_STAT, TOK_VIRT, TOK_ABST : DivTok = Tk : SKIP
  CASE ELSE : DivTok = 0
  END SELECT

  FunTok = Tk : DimTok = 0 : IniTok = 0
  SELECT CASE AS CONST *Tk
  CASE TOK_CTOR, TOK_DTOR
    SKIP : IF *Tk = TOK_WORD THEN NamTok = Tk : SKIP ELSE NamTok = 0
    IF *Tk = TOK_EOS THEN RETURN TOK_EOS ELSE RETURN demuxTyp(1)
  CASE TOK_OPER
    DO
      SKIP
      IF *Tk = TOK_BROPN THEN EXIT DO
      IF *Tk < TOK_BROPN THEN EXIT SELECT
    LOOP UNTIL Tk >= TkEnd : RETURN demuxTyp(1)
  CASE TOK_PROP
    SKIP : IF *Tk = TOK_WORD THEN NamTok = Tk ELSE EXIT SELECT
    SKIP : IF *Tk = TOK_EOS THEN RETURN TOK_EOS ELSE RETURN demuxTyp(1)
  CASE TOK_SUB, TOK_FUNC
    SKIP : IF *Tk = TOK_WORD THEN NamTok = Tk ELSE EXIT SELECT
    SKIP : IF *Tk = TOK_LIB THEN SKIP : IF *Tk = TOK_QUOTE THEN SKIP ELSE EXIT SELECT
    IF *Tk = TOK_EOS THEN RETURN TOK_EOS ELSE RETURN demuxTyp(1)
  END SELECT : TypTok = 0 : FunTok = 0 : NamTok = 0 : RETURN MSG_ERROR
END FUNCTION


/'* \brief Evaluate a type declaration in the token list
\param DeclMod the modus (normal or declaration)
\returns the (doubled) number of steps gone in the token list (or 
          MSG_ERROR on error and MSG_STOP on the end of the token list)

Check the token list for a type declaration. Starting at the AS 
statement we read the name of the type and all following 'PTR' 
statements. The type may be a SUB, FUNCTION, PROPERTY, ... After 
execution the current position is at the next token behind the last 
PTR, or at the type name, or at the closing parenthesis. An error is 
returned if there is no word at the current position, or the token 
list doesn't start with 'AS', or the next token isn't a word.

'/
FUNCTION Parser.demuxTyp(BYVAL DeclMod AS INTEGER = 0) AS INTEGER
  VAR t = Tk
  ParTok = 0
  CalTok = 0
  AliTok = 0
  TypTok = 0
  As_Tok = 0
  Co1Tok = 0
  Co2Tok = 0
  PtrTok = 0
  PtrCount = 0

  IF 0 = DeclMod THEN
    DivTok = 0
    IF *Tk = TOK_AS THEN As_Tok = Tk : SKIP    ELSE RETURN MSG_ERROR

    SELECT CASE AS CONST *Tk
    CASE TOK_SUB  : FunTok = Tk
    CASE TOK_FUNC : FunTok = Tk
    CASE ELSE
      IF *Tk = TOK_CONS THEN Co1Tok = Tk : SKIP
      IF *Tk < TOK_BYTE ORELSE *Tk > TOK_WORD THEN  RETURN MSG_ERROR
      TypTok = Tk : FunTok = 0
    END SELECT : SKIP
  END IF

  SELECT CASE AS CONST *Tk
  CASE TOK_EQUAL
    IF FunTok THEN TypTok = 0                     : RETURN MSG_ERROR
    IniTok = Tk                                   : RETURN Tk - t
  CASE TOK_BROPN : IF 0 = FunTok               THEN RETURN Tk - t
    ParTok = Tk : skipOverBrclo()
  CASE ELSE
    IF 0 = FunTok THEN
      IF *Tk = TOK_CONS THEN
        Co2Tok = Tk : SKIP
        IF *Tk <> TOK_PTR THEN TypTok = 0         : RETURN MSG_ERROR
      ELSE
        IF *Tk <> TOK_PTR THEN                      RETURN Tk - t
      END IF
  
      PtrTok = Tk : WHILE *Tk = TOK_PTR : PtrCount += 1 : SKIP : WEND

      IF *Tk = TOK_EQUAL THEN IniTok = Tk
                                                    RETURN Tk - t
    END IF

    IF *Tk = TOK_EOS THEN RETURN IIF(*FunTok = TOK_FUNC, MSG_ERROR, TOK_EOS)

    SELECT CASE AS CONST *Tk
    CASE TOK_CDEC : CalTok = Tk : SKIP
    CASE TOK_STCL : CalTok = Tk : SKIP
    CASE TOK_PASC : CalTok = Tk : SKIP
    END SELECT

    IF *Tk = TOK_OVER THEN SKIP
    IF *Tk = TOK_ALIA THEN
      AliTok = Tk
      SKIP : IF *Tk <> TOK_QUOTE THEN TypTok = 0  : RETURN MSG_ERROR
      SKIP
    END IF

    IF *Tk = TOK_BROPN THEN ParTok = Tk : skipOverBrclo()
  END SELECT

  IF *Tk = TOK_AS THEN
    As_Tok = Tk : SKIP
    IF *Tk = TOK_CONS THEN Co1Tok = Tk : SKIP
    TypTok = Tk : SKIP
    IF *Tk = TOK_CONS THEN
      Co2Tok = Tk : SKIP
      IF *Tk <> TOK_PTR THEN FunTok = 0: TypTok = 0 : RETURN MSG_ERROR
    END IF
    PtrTok = Tk : WHILE *Tk = TOK_PTR : PtrCount += 1 : SKIP : WEND
  ELSE
    IF *FunTok = TOK_FUNC THEN RETURN MSG_ERROR
  END IF ' !!! ToDo STATIC, EXPORT

  IF *Tk = TOK_STAT THEN
    SKIP : IF *Tk = TOK_EXPO THEN SKIP
  ELSE
    IF *Tk = TOK_EXPO THEN SKIP : IF *Tk = TOK_STAT THEN SKIP
  END IF : RETURN Tk - t
END FUNCTION


/'* \brief Evaluate a list of names
\param Export_ the function to call on each find

Scan the token list for variable declarations and call the emitter 
for each find  (ie for constructs like DIM AS BYTE Nam1, Nam2(5) = 
{0,1,2,3,4,5}, Nam3, ...).

'/
SUB Parser.parseListNam(BYVAL Export_ AS EmitFunc)
  VAR count = 1
  TypTok = 0 : FunTok = 0
  DO
    Tk1 = Tk
    IF MSG_ERROR >= demuxNam() THEN : Errr("name expected") : skipOverComma() : ELSE
    skipOverComma() : ListCount = count : Export_(@THIS) : count += 1 : END IF
  LOOP UNTIL *Tk <= TOK_COMMA
END SUB


/'* \brief Evaluate a list of declarations
\param Export_ the function to call on each find

Scan the token list for variable declarations and call the emitter 
for each find  (ie for constructs like DIM Nam1 AS BYTE, Nam2(5) AS 
UBYTE = {0,1,2,3,4,5}, Nam3 AS STRING, ...).

'/
SUB Parser.parseListNamTyp(BYVAL Export_ AS EmitFunc)
  DO
    Tk1 = Tk
    IF MSG_ERROR >= demuxNam() THEN : Errr("name expected") : skipOverComma() : ELSE
    IF MSG_ERROR >= demuxTyp() THEN : Errr("type expected") : skipOverComma() : ELSE
    skipOverComma() : ListCount = 0 : Export_(@THIS) : END IF : END IF
  LOOP UNTIL *Tk <= TOK_COMMA
END SUB


/'* \brief Evaluate the context of an ENUM block
\param Export_ the function to call on each find

The emitter calls us to evaluate statements inside an ENUM block. So 
we stop after finding a statement and call the emitter handler 
one-by-one.

'/
SUB Parser.parseBlockEnum(BYVAL Export_ AS EmitFunc)
  VAR lico = 0
  TypTok = 0 : FunTok = 0 : LevelCount += 1
  WHILE Tk < TkEnd
    Tk1 = Tk
    SELECT CASE AS CONST *Tk
    CASE TOK_END
      SKIP : IF *Tk = TOK_ENUM THEN EXIT WHILE
      Errr("END ENUM expected") : EXIT WHILE
    CASE TOK_LATTE : skipOverColon()
    CASE ELSE 
      IF MSG_ERROR >= demuxNam() THEN Errr("name expected") : skipOverColon() : EXIT SELECT
      ListCount = lico
      skipOverComma() : IF *Tk = TOK_EOS THEN lico = 0 : skipOverColon() ELSE lico += 1
      Export_(@THIS)
    END SELECT
  WEND : skipOverColon() : LevelCount -= 1
END SUB


/'* \brief Evaluate the context of a block
\param Export_ the function to call on each find

The emitter calls us to evaluate constructs inside a block (TYPE / 
UNION). So we stop after finding a statement and call the emitter 
handler one-by-one.

'/
SUB Parser.parseBlockTyUn(BYVAL Export_ AS EmitFunc)
  VAR endtok = *Tk1
  LevelCount += 1

  DO
    Tk1 = Tk
    VAR nextok = *(Tk + 3)
    SELECT CASE AS CONST nextok
    CASE TOK_AS, TOK_BROPN
      BitTok = 0
      IF MSG_ERROR >= demuxNam(TOK_ABST) THEN _
        IF Errr("name expected") = MSG_ERROR THEN CONTINUE DO _
                                             ELSE EXIT DO
      IF MSG_ERROR >= demuxTyp() THEN _
        IF Errr("type expected") = MSG_ERROR THEN CONTINUE DO _
                                             ELSE EXIT DO
      *NamTok = TOK_WORD : ListCount = 0 : Export_(@THIS)
      skipOverColon()

    CASE ELSE
      SELECT CASE AS CONST *Tk1
      CASE TOK_LATTE : skipOverColon()
      CASE TOK_AS
        BitTok = 0
        IF MSG_ERROR >= demuxTyp() THEN _
          IF Errr("type expected") = MSG_ERROR THEN CONTINUE DO _
                                               ELSE EXIT DO
        IF MSG_ERROR >= demuxNam(TOK_ABST) THEN _
          IF Errr("name expected") = MSG_ERROR THEN CONTINUE DO _
                                               ELSE EXIT DO
  
        skipOverComma()
        *NamTok = TOK_WORD : ListCount = 0 : Export_(@THIS)
        IF *Tk >= TOK_ABST THEN parseListNam(Export_)
        skipOverColon()
      CASE TOK_END
        IF nextok = endtok                   THEN EXIT DO
        IF Errr("not supported") < MSG_ERROR THEN EXIT DO
        skipOverColon()
  
      CASE TOK_DECL : SKIP
        IF MSG_ERROR >= demuxDecl() THEN IF Errr("syntax error " & SubStr(NamTok)) = MSG_ERROR _
                                    THEN skipOverColon() : EXIT SELECT ELSE EXIT DO
        skipOverColon() : Export_(@THIS)
  
      CASE TOK_PUBL, TOK_PRIV, TOK_PROT
        skipOverColon() : Export_(@THIS)
  
      CASE TOK_ENUM, TOK_TYPE, TOK_UNIO, TOK_CLAS
        VAR n = BlockNam
        IF nextok = TOK_WORD THEN SKIP : BlockNam = SubStr ELSE BlockNam = ""
        skipOverColon() : Export_(@THIS) : BlockNam = n
      CASE ELSE
        skipOverColon() : IF Errr("not supported") < MSG_ERROR THEN EXIT DO
      END SELECT
    END SELECT
  LOOP UNTIL Tk >= TkEnd : skipOverColon() : LevelCount -= 1
END SUB


/'* \brief Evaluate a parameter list
\param Export_ the function to call on each find

Scan the token list for a parameter list. Evaluate declaration 
specifiers (BYVAL / BYREF) and handle ellipsis, parameters without 
name, initializers and empty lists.

'/
SUB Parser.parseListPara(BYVAL Export_ AS EmitFunc)
  VAR count = 0, t = Tk
  Tk = ParTok
  SKIP
  DO
    Tk1 = Tk
    By_Tok = 0
    SELECT CASE AS CONST *Tk
    CASE TOK_BRCLO : IF count > 0 THEN Errr("parameter expected")
      ListCount = count : EXIT DO
    CASE TOK_TRIDO : NamTok = Tk : TypTok = 0 : FunTok = 0
    CASE ELSE
      IF *Tk = TOK_BYVA ORELSE *Tk = TOK_BYRE THEN By_Tok = Tk : SKIP
      IF *Tk <> TOK_AS ANDALSO *Tk >= TOK_ABST THEN
        NamTok = Tk : SKIP
        IF *Tk = TOK_BROPN THEN DimTok = Tk : skipOverBrclo()
      ELSE
        NamTok = 0
      END IF
      IF MSG_ERROR >= demuxTyp()  THEN Errr("type expected") : skipOverComma() : CONTINUE DO
    END SELECT
    skipOverComma() : count += 1 : ListCount = count : Export_(@THIS)
  LOOP UNTIL *Tk < TOK_COMMA : By_Tok = 0 : Tk = t
END SUB


/'* \brief Evaluate a TYPE or CLASS statement
\returns MSG_ERROR (or MSG_STOP on the end of the token list)

The pre-parser found a TYPE or CLASS keyword. In case of TYPE we 
tokenize the line and check if it is an alias declaration. In that 
case we call the emitter for a declaration on that single line. 
Otherwise we tokenize the complete block and call the emitter for a 
TYPE block. Or we call Errr() on syntax problems.

Note: the C emitter creates
 - 'typedef Typ Nam' for 'TYPE AS Typ Nam'
 - 'typedef struct Typ Nam' for 'TYPE Nam AS Typ'

'/
FUNCTION Parser._TYPE_() AS INTEGER
  IF 3 > tokenize(TO_COLON) THEN RETURN Errr("syntax error")

  DimTok = 0 : IniTok = 0
  IF *StaTok = TOK_TYPE THEN
    IF *Tk = TOK_AS THEN
      IF MSG_ERROR >= demuxTyp() THEN          RETURN Errr("type expected")
      IF *Tk = TOK_WORD THEN NamTok = Tk  ELSE RETURN Errr("name expected1")
      skipOverComma()
      Emit->Decl_(@THIS)                     : RETURN MSG_ERROR
    ELSE
      IF *Tk = TOK_WORD THEN NamTok = Tk : SKIP ELSE RETURN Errr("name expected2")
      IF *Tk = TOK_AS THEN
        IF MSG_ERROR >= demuxTyp() THEN        RETURN Errr("type expected")
        skipOverComma()
        Emit->Decl_(@THIS)                   : RETURN MSG_ERROR
      END IF
    END IF
  ELSE
    IF *Tk = TOK_WORD THEN NamTok = Tk    ELSE RETURN Errr("name expected3")
  END IF
  BlockNam = SubStr(NamTok)
  IF 9 > tokenize(TO_END_BLOCK) THEN           RETURN Errr("syntax error")
  Tk1 = StaTok
  Emit->Clas_(@THIS) :                         RETURN MSG_ERROR
END FUNCTION


/'* \brief Evaluate a variable declaration
\returns MSG_ERROR (or MSG_STOP on the end of the token list)

The pre-parser found a VAR, DIM, CONST, EXTERN, COMMON or STATIC 
keyword. We tokenize the current line and send the result to the 
emitter, or we call Errr() on syntax problems.

'/
FUNCTION Parser._VAR_() AS INTEGER
  IF 3 > tokenize(TO_COLON) THEN    RETURN Errr("syntax error")
  IF *Tk <> TOK_SHAR THEN ShaTok = 0 ELSE ShaTok = Tk : SKIP

  IF *Tk = TOK_AS THEN
    IF MSG_ERROR >= demuxTyp() THEN RETURN Errr("type expected")
    IF MSG_ERROR >= demuxNam() THEN RETURN Errr("name expected")
  ELSE
    IF MSG_ERROR >= demuxNam() THEN RETURN Errr("name expected")
    IF *Tk = TOK_AS THEN demuxTyp() ELSE TypTok = 0 : FunTok = 0
    IF 0 = TypTok ANDALSO 0 = FunTok THEN
      SELECT CASE AS CONST *StaTok
      CASE TOK_VAR, TOK_CONS
        IF  0 = IniTok THEN         RETURN Errr("initialization expected")
      CASE ELSE                   : RETURN Errr("type expected")
      END SELECT
    END IF
  END IF

  skipOverComma()
  Emit->Decl_(@THIS)
  IF *Tk <= TOK_EOS THEN RETURN MSG_ERROR
  IF NamTok > TypTok THEN parseListNam(Emit->Decl_) _
                     ELSE parseListNamTyp(Emit->Decl_)
  RETURN MSG_ERROR
END FUNCTION


/'* \brief Evaluate an ENUM block
\returns MSG_ERROR (or MSG_STOP on the end of the token list)

The pre-parser found an ENUM keyword. We tokenize the context of the 
complete block and call the emitter, or we call Errr() on 
syntax problems.

'/
FUNCTION Parser._ENUM_() AS INTEGER
  IF 9 > tokenize(TO_END_BLOCK) THEN RETURN Errr("syntax error")
  IF *Tk = TOK_WORD THEN BlockNam = SubStr : SKIP ELSE BlockNam = ""
  IF *Tk = TOK_EOS THEN skipOverColon() ELSE RETURN Errr("syntax errorrrr")
  Tk1 = StaTok
  Emit->Enum_(@THIS)
  RETURN MSG_ERROR
END FUNCTION


/'* \brief Evaluate an UNION block
\returns MSG_ERROR (or MSG_STOP on the end of the token list)

The pre-parser found an UNION keyword. We tokenize the context of the 
complete block and call the emitter, or we call Errr() on 
syntax problems.

'/
FUNCTION Parser._UNION_() AS INTEGER
  IF 9 > tokenize(TO_END_BLOCK) THEN RETURN Errr("syntax error")
  IF *Tk <> TOK_WORD THEN RETURN Errr("name expected")
  BlockNam = SubStr
  skipOverColon()
  Tk1 = StaTok
  Emit->Unio_(@THIS)
  RETURN MSG_ERROR
END FUNCTION


/'* \brief Evaluate a function
\returns MSG_ERROR (or MSG_STOP on the end of the token list)

The pre-parser found a SUB, FUNCTION, CONSTRUCTOR, DESTRUCTOR, 
PROPERTY or OPERATOR keyword. We tokenize the complete block, 
evaluate the name and type and send the result to the emitter, or we 
call Errr() on syntax problems.

'/
FUNCTION Parser._FUNCTION_() AS INTEGER
  SELECT CASE AS CONST ToLast
  CASE TOK_PUBL, TOK_PRIV : DivTok = StaTok
  CASE ELSE : DivTok = 0
  END SELECT
  IF 9 > tokenize(TO_END_BLOCK) THEN RETURN Errr("syntax error")
  FunTok = StaTok

  IF DivTok THEN DivTok = Tk1
  IF MSG_ERROR >= demuxNam(TOK_WORD, 1) THEN RETURN Errr("name expected")
  IF MSG_ERROR >= demuxTyp(1) THEN RETURN Errr("syntax error")

  FOR i AS INTEGER = 0 TO 1
    SELECT CASE AS CONST *Tk
    CASE TOK_STAT : SKIP ' !!! ToDo
    CASE TOK_EXPO : SKIP ' !!! ToDo
    CASE ELSE : EXIT FOR
    END SELECT
  NEXT
  skipOverBrclo()
  Emit->Func_(@THIS)
  RETURN MSG_ERROR
END FUNCTION


/'* \brief Evaluate a forward declaration
\returns MSG_ERROR (or MSG_STOP on the end of the token list)

The pre-parser found a DECLARE keyword. We tokenize the current line 
and send the result to the emitter, or we call Errr() on syntax 
problems.

'/
FUNCTION Parser._DECLARE_() AS INTEGER
  IF 3 > tokenize(TO_COLON) THEN RETURN Errr("syntax error")
  IF MSG_ERROR >= demuxDecl() THEN Errr("syntax error!!")
  Emit->Decl_(@THIS)
  RETURN MSG_ERROR
END FUNCTION


/'* \brief Evaluate an \#INCLUDE line
\returns MSG_ERROR (or MSG_STOP on the end of the token list)

The pre-parser found an \#INCLUDE line. We tokenize the line and call
the emitter, or we call Errr() handler on syntax problems. It's up 
to the emitter function to follow the source tree (= create a new 
parser and load the file).

'/
FUNCTION Parser._INCLUDE_() AS INTEGER
  IF 3 > tokenize(TO_EOL) THEN RETURN Errr("syntax error")
  IF *Tk = TOK_ONCE THEN DivTok = Tk : SKIP ELSE DivTok = 0
  IF *Tk <> TOK_QUOTE THEN RETURN Errr("file name expected")
  NamTok = Tk
  Emit->Incl_(@THIS)
END FUNCTION


/'* \brief Evaluate a \#MACRO declaration
\returns MSG_ERROR (or MSG_STOP on the end of the token list)

The pre-parser found a \#MACRO block. We tokenize the block and call 
the emitter, or we call Errr() handler on syntax problems.

'/
FUNCTION Parser._MACRO_() AS INTEGER
  IF 3 > tokenize(TO_END_BLOCK) THEN RETURN Errr("syntax error")
  IF *Tk <> TOK_WORD THEN RETURN Errr("name expected")
  NamTok = Tk : SKIP
  IF *Tk <> TOK_BROPN THEN RETURN Errr("'()' expected")
  ParTok = Tk
  skipOverBrclo()
  Emit->Defi_(@THIS)
  RETURN MSG_ERROR
END FUNCTION


/'* \brief Evaluate a \#DEFINE declaration
\returns MSG_ERROR (or MSG_STOP on the end of the token list)

The pre-parser found a \#DEFINE line. We tokenize the line and call 
the emitter, or we call Errr() handler on syntax problems.

'/
FUNCTION Parser._DEFINE_() AS INTEGER
  IF 3 > tokenize(TO_EOL) THEN RETURN Errr("syntax error")
  IF *Tk < TOK_ABST ORELSE *Tk > TOK_WORD THEN RETURN Errr("name expected")
  NamTok = Tk
  Tk = TkEnd - 3
  Emit->Defi_(@THIS)
  RETURN MSG_ERROR
END FUNCTION


/'* \brief Emit an error message
\param E the reason for the error message
\returns MSG_ERROR (and MSG_STOP at end of file)

Create an error message and call the error function of the emitter. 
It depends on the emitter if and where the error is shown.

'/
FUNCTION Parser.Errr(BYREF E AS STRING) AS INTEGER
  VAR z = InLines
  IF LevelCount THEN ' !!! ToDo: counter init for Geany!
    FOR i AS INTEGER = *(Tk + 1) TO *(TkEnd + 1) 
      IF Buf[i] = ASC(!"\n") THEN z -= 1
    NEXT
  END IF
  ErrMsg = "-error(" & z & "): " & E ' & _
           '"! (... " & MID(Buf, IIF(Po > 20, Po - 40, 1), 20)
  SELECT CASE AS CONST *StaTok
  CASE TOK_DIM  : ErrMsg &= " (DIM)"
  CASE TOK_RDIM : ErrMsg &= " (REDIM)"
  CASE TOK_VAR  : ErrMsg &= " (VAR)"
  CASE TOK_CONS : ErrMsg &= " (CONST)"
  CASE TOK_STAT : ErrMsg &= " (STATIC)"
  CASE TOK_COMM : ErrMsg &= " (COMMON)"
  CASE TOK_EXRN : ErrMsg &= " (EXTERN)"
  CASE TOK_TYPE : ErrMsg &= " (TYPE)"
  CASE TOK_CLAS : ErrMsg &= " (CLASS)"
  CASE TOK_SUB  : ErrMsg &= " (SUB)"
  CASE TOK_FUNC : ErrMsg &= " (FUNCTION)"
  CASE TOK_PROP : ErrMsg &= " (PROPERTY)"
  CASE TOK_CTOR : ErrMsg &= " (CONSTRUCTOR)"
  CASE TOK_DTOR : ErrMsg &= " (DESTRUCTOR)"
  CASE TOK_NAMS : ErrMsg &= " (NAMESPACE)"
  CASE TOK_SCOP : ErrMsg &= " (SCOPE)"
  CASE TOK_ENUM : ErrMsg &= " (ENUM)"
  CASE TOK_UNIO : ErrMsg &= " (UNION)"
  CASE TOK_DECL : ErrMsg &= " (DECLARE)"
  CASE TOK_DEFI : ErrMsg &= " (#DEFINE)"
  CASE TOK_MACR : ErrMsg &= " (#MACRO)"
  CASE ELSE : ErrMsg &= " (???)"
  END SELECT
  Emit->_Error_(@THIS)

  RETURN IIF(Buf[Po] = 0, MSG_STOP, MSG_ERROR)
END FUNCTION


/'*
\brief Check word at current parser position
\returns: The token for the word at current position

This function checks the word at the current parser position. In 
case of a keyword the matching token gets returned. Otherwise \ref 
Parser::TOK_WORD gets returned.

'/
FUNCTION Parser.getToken() AS INTEGER
  SELECT CASE AS CONST Buf[*A]
  CASE ASC("A"), ASC("a")
    SELECT CASE USubStr
    CASE "AS" : RETURN TOK_AS
    CASE "ALIAS" : RETURN TOK_ALIA
    CASE "ABSTRACT" : RETURN TOK_ABST
    END SELECT
  CASE ASC("B"), ASC("b")
    SELECT CASE USubStr
    CASE "BYTE" : RETURN TOK_BYTE
    CASE "BYREF" : RETURN TOK_BYRE
    CASE "BYVAL" : RETURN TOK_BYVA
    END SELECT
  CASE ASC("C"), ASC("c")
    SELECT CASE USubStr
    CASE "CDECL" : RETURN TOK_CDEC
    CASE "CLASS" : RETURN TOK_CLAS
    CASE "CONST" : RETURN TOK_CONS
    CASE "COMMON" : RETURN TOK_COMM
    CASE "CONSTRUCTOR" : RETURN TOK_CTOR
    END SELECT
  CASE ASC("D"), ASC("d")
    SELECT CASE USubStr
    CASE "DIM" : RETURN TOK_DIM
    CASE "DOUBLE" : RETURN TOK_DOUB
    CASE "DEFINE" : IF ToLast = TOK_LATTE THEN RETURN TOK_DEFI
    CASE "DECLARE" : RETURN TOK_DECL
    CASE "DESTRUCTOR" : RETURN TOK_DTOR
    END SELECT
  CASE ASC("E"), ASC("e")
    SELECT CASE USubStr
    CASE "END" : RETURN TOK_END
    CASE "ENUM" : RETURN TOK_ENUM
    CASE "EXTERN" : RETURN TOK_EXRN
    CASE "EXPORT" : RETURN TOK_EXPO
    CASE "EXTENDS" : RETURN TOK_EXDS
    CASE "ENDMACRO" : IF ToLast = TOK_LATTE THEN RETURN TOK_EMAC
    END SELECT
  CASE ASC("F"), ASC("f")
    SELECT CASE USubStr
    CASE "FUNCTION" :  RETURN TOK_FUNC
    CASE "FIELD" : RETURN TOK_FILD
    END SELECT
  CASE ASC("I"), ASC("i")
    SELECT CASE USubStr
    CASE "INCLUDE" : IF ToLast = TOK_LATTE THEN RETURN TOK_INCL
    CASE "INTEGER" : RETURN TOK_INT
    END SELECT
  CASE ASC("L"), ASC("l")
    SELECT CASE USubStr
    CASE "LIB" : RETURN TOK_LIB
    CASE "LONG" : RETURN TOK_LONG
    CASE "LONGINT" : RETURN TOK_LINT
    END SELECT
  CASE ASC("M"), ASC("m") : IF USubStr = "MACRO" THEN IF ToLast = TOK_LATTE THEN RETURN TOK_MACR
  CASE ASC("N"), ASC("n") : IF USubStr = "NAMESPACE" THEN RETURN TOK_NAMS
  CASE ASC("O"), ASC("o")
    SELECT CASE USubStr
    CASE "ONCE" : RETURN TOK_ONCE
    CASE "OPERATOR" : RETURN TOK_OPER
    CASE "OVERLOAD" : RETURN TOK_OVER
    END SELECT
  CASE ASC("P"), ASC("p")
    SELECT CASE USubStr
    CASE "PTR" : RETURN TOK_PTR
    CASE "POINTER" : RETURN TOK_PTR
    CASE "PASCAL" : RETURN TOK_PASC
    CASE "PUBLIC" : RETURN TOK_PUBL
    CASE "PRIVATE" : RETURN TOK_PRIV
    CASE "PROPERTY" : RETURN TOK_PROP
    CASE "PROTECTED" : RETURN TOK_PROT
    END SELECT
  CASE ASC("R"), ASC("r") : IF USubStr = "REDIM" THEN RETURN TOK_RDIM
  CASE ASC("S"), ASC("s")
    SELECT CASE USubStr
    CASE "SUB" : RETURN TOK_SUB
    CASE "SCOPE" : RETURN TOK_SCOP
    CASE "SHORT" : RETURN TOK_SHOR
    CASE "SINGLE" : RETURN TOK_SING
    CASE "SHARED" : RETURN TOK_SHAR
    CASE "STRING" : RETURN TOK_STRI
    CASE "STATIC" : RETURN TOK_STAT
    CASE "STDCALL" : RETURN TOK_STCL
    END SELECT
  CASE ASC("T"), ASC("t") : IF USubStr = "TYPE" THEN RETURN TOK_TYPE
  CASE ASC("U"), ASC("u")
    SELECT CASE USubStr
    CASE "UNION" : RETURN TOK_UNIO
    CASE "UBYTE" : RETURN TOK_UBYT
    CASE "ULONG" : RETURN TOK_ULNG
    CASE "ULONGINT" : RETURN TOK_ULIN
    CASE "UINTEGER" : RETURN TOK_UINT
    CASE "USHORT" : RETURN TOK_USHO
    END SELECT
  CASE ASC("V"), ASC("v")
    SELECT CASE USubStr
    CASE "VAR" : RETURN TOK_VAR
    CASE "VIRTUAL" : RETURN TOK_VIRT
    END SELECT
  CASE ASC("W"), ASC("w") : IF USubStr = "WSTRING" THEN RETURN TOK_WSTR
  CASE ASC("Z"), ASC("z") : IF USubStr = "ZSTRING" THEN RETURN TOK_ZSTR
  END SELECT : RETURN TOK_WORD
END FUNCTION


/'* \brief Find the end of a Quote

This snippet is used to find the end of a quoted string. It checks 
if the string uses escape sequences and evaluates '\\"' . It stops at 
the last double quote (if Buf[Po] isn't ASC(!"\\"") then the end of 
the buffer is reached).

'/
#MACRO SCAN_QUOTE(Buf,Po)
  VAR esc = IIF(Buf[Po - 1] = ASC("!"), 1, 0)
  DO
    Po += 1
    SELECT CASE AS CONST Buf[Po]
    CASE 0, ASC(!"\n") : Po -= 1 : EXIT DO
    CASE ASC("\") : IF esc THEN Po += 1
    CASE ASC("""") : IF Buf[Po + 1] = ASC("""") THEN Po += 1 ELSE EXIT DO
    END SELECT
  LOOP
#ENDMACRO


/'* \brief Find the end of a line end comment

This snippet is used to find the end of a line end comment. It 
checks for a ASC(!"\n") and evaluates line concatenations ( _ ) on 
the way. It stops at the line end (if Buf[Po] isn't ASC(!"\n") then 
the end of the buffer is reached).

'/
#MACRO SCAN_SL_COMM(Buf,Po)
  DO
    Po += 1
    SELECT CASE AS CONST Buf[Po]
    CASE 0, ASC(!"\n") : EXIT DO
    END SELECT
  LOOP
#ENDMACRO


/'* \brief Find the end of a multi line comment

This snippet is used to find the end of a multi line comment block. 
It stops at the characters ' and / (if Buf[Po] isn't ASC("/") then the 
end of the buffer is reached).

'/
#MACRO SCAN_ML_COMM()
  Po += 2
  DO
    SELECT CASE AS CONST Buf[Po]
    CASE 0 : EXIT DO
    CASE ASC(!"\n") : InLines += 1
    CASE ASC("'")
      SELECT CASE AS CONST Buf[Po + 1]
      CASE 0 : EXIT DO
      CASE ASC("/") : Po += 1 : EXIT DO
      END SELECT
    END SELECT : Po += 1
  LOOP
#ENDMACRO


/'* \brief Find the end of a word

This snippet is used to find the end of a word in the input buffer. 
Po is at the first non-word character when done.

'/
#MACRO SCAN_WORD()
  *A = Po
  Po += 1
  DO
    SELECT CASE AS CONST Buf[Po]
    CASE ASC("0") TO ASC("9"), _
         ASC("A") TO ASC("Z"), _
         ASC("a") TO ASC("z"), ASC("_") : Po += 1
    CASE ELSE : EXIT DO
    END SELECT
  LOOP
#ENDMACRO

'* snippet to set the current token
#DEFINE SETOK(_T_,_A_,_L_) Tok &= MKI(_T_) & MKI(_A_) & MKI(_L_) : ToLast = _T_

'* snippet to exit in case of end of file
#DEFINE EXIT_STOP  SETOK(MSG_STOP, Po, 1) : Po -= 1 : EXIT DO

/'* \brief Parse a relevant construct, create a token list
\param Stop_ the condition where to end parsing
\returns the length of the token list in byte (at least 8)

Start at the current position of the input buffer to check each 
character. Sort the input in to a token list. The token list 
contains three values in each entry: the type of the input, its 
start and its length.

The end of this process gets specified by the Stop_ parameter. It's 
one of the #EoS_Modi enumerators.

'/
FUNCTION Parser.tokenize(BYVAL Stop_ AS EoS_Modi) AS INTEGER
  VAR coca = 0, endcount = 0, newblock = 0 ', currTok = MSG_ERROR, fl_end = 0
  VAR tok_begin = LEN(Tok) SHR 2
  ToLast = IIF(*StaTok = TOK_TYPE, TOK_EOS, MSG_ERROR)
  A += 3 : L += 3
  DO
    SELECT CASE AS CONST Buf[Po]
    CASE 0 : EXIT_STOP
    CASE ASC(!"\n") : InLines += 1 : IF coca THEN coca = 0 : EXIT SELECT
      IF ToLast = TOK_EOS THEN EXIT SELECT
      SETOK(TOK_EOS, Po, 1) : IF Stop_ >= TO_EOL       THEN EXIT DO
      IF newblock THEN endcount += 1 : newblock = 0
    CASE   ASC(":")
      SETOK(TOK_EOS, Po, 1)   : IF Stop_ >= TO_COLON   THEN EXIT DO
      IF newblock THEN endcount += 1 : newblock = 0
    CASE   ASC(",") : SETOK(TOK_COMMA, Po, 1)
    CASE   ASC(".")
      IF Buf[Po + 1] <> ASC(".") ORELSE Buf[Po + 2] <> ASC(".") ORELSE Buf[Po + 3] = ASC(".") _
        THEN SETOK(TOK_DOT, Po, 1) _
        ELSE SETOK(TOK_TRIDO, Po, 3) : Po += 3        : CONTINUE DO
    CASE   ASC("=") : SETOK(TOK_EQUAL, Po, 1)
    CASE   ASC("{"), ASC("[") : SETOK(TOK_KLOPN, Po, 1)
    CASE   ASC("}"), ASC("]") : SETOK(TOK_KLCLO, Po, 1)
    CASE   ASC("(")           : SETOK(TOK_BROPN, Po, 1)
    CASE   ASC(")")           : SETOK(TOK_BRCLO, Po, 1)
    CASE   ASC("#") : SETOK(TOK_LATTE, Po, 1)

    CASE ASC("A") TO ASC("Z"), ASC("a") TO ASC("z"), ASC("_")
      SCAN_WORD()
      *L = Po - *A : IF *L = 1 ANDALSO Buf[*A] = ASC("_") THEN coca = *A : CONTINUE DO

      VAR pretok = ToLast
      SETOK(getToken(), *A, *L)
      newblock = 0
      IF pretok = TOK_END THEN
        SELECT CASE AS CONST *StaTok
        CASE TOK_SUB, TOK_FUNC, TOK_PROP, TOK_OPER, TOK_CTOR, TOK_DTOR, TOK_ENUM
          IF ToLast = *StaTok THEN Stop_ = TO_COLON
          CONTINUE DO
        CASE TOK_UNIO, TOK_TYPE, TOK_CLAS
          IF ToLast <> *StaTok THEN CONTINUE DO
        CASE ELSE : CONTINUE DO
        END SELECT
        endcount -= 1 : IF endcount < 0 THEN Stop_ = TO_COLON
      ELSE
        SELECT CASE AS CONST *StaTok
        CASE TOK_UNIO, TOK_TYPE, TOK_CLAS
          IF pretok <> TOK_EOS THEN CONTINUE DO
          IF ToLast = *StaTok THEN newblock = 1
        CASE TOK_MACR : IF ToLast = TOK_EMAC THEN Stop_ = TO_COLON
        END SELECT
      END IF : CONTINUE DO

    CASE ASC("""")
      *A = Po
      SCAN_QUOTE(Buf,Po)
      SETOK(TOK_QUOTE, *A, Po - *A + 1)
      IF Buf[Po] <> ASC("""") THEN EXIT_STOP
    CASE ASC("'")
      SCAN_SL_COMM(Buf,Po)
      CONTINUE DO
    CASE ASC("/") : IF Buf[Po + 1] <> ASC("'") THEN   EXIT SELECT
      SCAN_ML_COMM()
      IF Buf[Po] <> ASC("/") THEN EXIT_STOP
    END SELECT : Po += 1
  LOOP : A -= 3 : L -= 3 : Po += 1

  Tk1 = CAST(INTEGER PTR, SADD(Tok))
  TkEnd = Tk1 + (LEN(Tok) SHR 2) - 3
  Tk = Tk1 + tok_begin
  RETURN TkEnd - Tk
END FUNCTION


/'* \brief Pre-parse all FB source code, search relevant constructs
\returns the length of the output code, if any

Search the input buffer for a relevant construct. Start detailed 
parsing on each relevant construct. Otherways skip the current line. 
Export comments on the way.

'/
SUB Parser.pre_parse()
  Fin = LEN(Buf) - 1
  *L = 0
  Po = 0
  ToLast = MSG_ERROR
  InLines = 1
  Tok = ""
  Emit->_Init_(@THIS)
  DO
    SELECT CASE AS CONST Buf[Po] '                           search word
    CASE 0 : EXIT DO
    CASE ASC(!"\n") : *L = 0 : InLines += 1
    CASE   ASC(":") : *L = 0 : ToLast = 0
    CASE   ASC("#") : ToLast = TOK_LATTE
    CASE ASC("""")
      SCAN_QUOTE(Buf,Po)
      ToLast = TOK_QUOTE : IF Buf[Po] <> ASC("""") THEN EXIT DO
    CASE ASC("'")
      SCAN_SL_COMM(Buf,Po)
      CONTINUE DO
    CASE ASC("/") : IF Buf[Po + 1] <> ASC("'") THEN EXIT SELECT
      SCAN_ML_COMM()
      IF Buf[Po] <> ASC(!"/") THEN EXIT DO

    CASE ASC("_"), ASC("A") TO ASC("Z"), ASC("a") TO ASC("z")
      SCAN_WORD() :  IF *L THEN ToLast = 0 : CONTINUE DO'     skip word, if not first
      *L = Po - *A : IF *L = 1 ANDALSO Buf[*A] = ASC("_") THEN ToLast = 0 : *L = 0 : CONTINUE DO

      ListCount = 0
      LevelCount = 0
      *StaTok = getToken()
      SELECT CASE AS CONST *StaTok
      CASE TOK_SUB, TOK_FUNC, TOK_PROP, TOK_OPER, TOK_CTOR, TOK_DTOR
                      IF _FUNCTION_() = MSG_STOP THEN EXIT DO
      CASE TOK_DIM, TOK_RDIM, TOK_VAR, TOK_CONS, TOK_COMM, TOK_EXRN, TOK_EXPO, TOK_STAT
                      IF      _VAR_() = MSG_STOP THEN EXIT DO
      CASE TOK_TYPE, TOK_CLAS
                      IF     _TYPE_() = MSG_STOP THEN EXIT DO
      CASE TOK_UNIO : IF    _UNION_() = MSG_STOP THEN EXIT DO
      CASE TOK_ENUM : IF     _ENUM_() = MSG_STOP THEN EXIT DO
      CASE TOK_DECL : IF  _DECLARE_() = MSG_STOP THEN EXIT DO
      CASE TOK_DEFI : IF   _DEFINE_() = MSG_STOP THEN EXIT DO
      CASE TOK_MACR : IF    _MACRO_() = MSG_STOP THEN EXIT DO
      CASE TOK_INCL : IF  _INCLUDE_() = MSG_STOP THEN EXIT DO
      CASE TOK_PUBL, TOK_PRIV
        SETOK(*StaTok, *A, *L) : *L = 0 : CONTINUE DO
      'CASE TOK_NAMS : ' !!! ToDo
      'CASE TOK_SCOP : ' !!! ToDo 
      CASE ELSE :  ToLast = *StaTok : CONTINUE DO
      END SELECT : ToLast = *StaTok : Tok = "" : *L = 0 : CONTINUE DO
    END SELECT : Po += 1
  LOOP : Emit->_Exit_(@THIS)
END SUB


/'* \brief Read a buffer from a file and parse
\param File the name of the file to translate
\returns the translated code (if any)

Read a file in to input buffer. Start detailed parsing on each 
relevant construct. Otherways skip the current line. Export comments 
on the way. Do nothing if file doesn't exist or isn't readable.

'/
SUB Parser.File_(BYREF File AS STRING)
  VAR fnr = FREEFILE
  IF OPEN(File FOR INPUT AS #fnr) _
    THEN ERROUT("Couldn't read file """ & File & """ (ERR = " & ERR & ")"): EXIT SUB
  Buf = STRING(LOF(fnr) + 1, 0)
  GET #fnr, , Buf
  CLOSE #fnr

  pre_parse()
END SUB


/'* \brief Read a buffer from pipe StdIn and parse
\returns the translated code (or an informal text)

Get all characters from StdIn. Start the parsing process. If there 
is no input (an empty line) then load file Options::EmitFile and 
output its context. Otherwise call function \ref 
EmitterIF::_Empty_(). (Useful for generating templates in \em 
--geany mode.)

'/
SUB Parser.StdIn()
  Buf = ""
  VAR fnr = FREEFILE
  OPEN CONS FOR INPUT AS #fnr
    WHILE NOT EOF(fnr)
      DIM AS UBYTE z
      GET #fnr, , z
      Buf &= CHR(z)
    WEND
  CLOSE #fnr

  IF LEN(Buf) < 3 THEN
    fnr = FREEFILE
    IF OPEN(OPT->EmitFile FOR INPUT AS #fnr) THEN Emit->_Empty_(@THIS) : EXIT SUB
    VAR txt = STRING(LOF(fnr), 0)
    GET #fnr, , txt
    CLOSE #fnr

    PRINT #Ocha, txt; :  EXIT SUB
  END IF

  pre_parse() : IF Tk1 THEN EXIT SUB

  PRINT #Ocha, _
         "'                     " & PROG_NAME & ": no --geany output:" & _
    NL & "'                        select either a line" & _
    NL & "'         DIM, COMMON, CONST, EXTERN, STATIC, DECLARE, #DEFINE" & _
    NL & "'                              or a block" & _
    NL & "'                       ENUM, UNION, TYPE, #MACRO" & _
    NL & "'                                or a" & _
    NL & "'                      SUB, FUNCTION or PROPERTY" & _
    NL & "'                            declaration or" & _
    NL & "'                   place the cursor in an empty line" & _
    NL;
END SUB


/'* \brief The current token
\returns the token the parser currently stopping at

This property returns the parser token (where the parser currently 
stops).

'/
PROPERTY Parser.CurTok() AS INTEGER PTR
  RETURN Tk
END PROPERTY

/'* \brief The initialization of a bitfield
\returns the bitfield initialization

This property returns the initialization of a bitfield in a TYPE / UNION
block. The size of the bitfield may either be an integer number or a 
macro (= TOK_WORD).

'/
PROPERTY Parser.BitIni() AS STRING
  IF *(BitTok + 3) = TOK_WORD THEN RETURN ": " & SubStr(BitTok + 3)
  VAR a = *(BitTok + 1) + 1, l = 0
  FOR i AS INTEGER = a TO *(BitTok + 4) - 1
    SELECT CASE AS CONST Buf[i]
    CASE ASC(" "), ASC(!"\t"), ASC(!"\v") : IF l THEN EXIT FOR
    CASE ASC("0") TO ASC("9") : IF l THEN l += 1 ELSE a = i : l = 1
    CASE ELSE : EXIT FOR
    END SELECT
  NEXT : RETURN ": " & MID(Buf, a + 1, l)
END PROPERTY

/'* \brief The dimension of a variable
\returns the name including round or squared brackets

This property reads the dimension of a variable from the input buffer.

'/
PROPERTY Parser.Bracket(BYVAL T AS INTEGER PTR) AS STRING
  VAR kl = 0, p = *(T + 1), l = 0
  WHILE T < TkEnd
    SELECT CASE AS CONST *T
    CASE TOK_COMMA : IF kl <= 0 THEN l = *(T + 1) - p : EXIT WHILE
    CASE TOK_KLOPN, TOK_BROPN : kl += 1
    CASE TOK_KLCLO, TOK_BRCLO : kl -= 1
      IF kl <= 0 THEN l = *(T + 1) - p  + 1 : EXIT WHILE
    END SELECT : T += 3
  WEND : RETURN MID(Buf, p + 1, l)
END PROPERTY

/'* \brief The variable initialization
\returns the initialiser

This property reads the initialization of a variable from the input 
buffer.

'/
PROPERTY Parser.VarIni() AS STRING
  VAR kl = 0, i = *(IniTok + 1), p = i
  DO
    i += 1
    SELECT CASE AS CONST Buf[i]
    CASE 0, ASC(!"\n"), ASC(!"\r"), ASC("'") : EXIT DO
    CASE ASC("_") : IF Buf[i + 1] < ASC("0") THEN EXIT DO
    CASE ASC("""")
      VAR esc = IIF(Buf[i - 1] = ASC("!"), 1, 0)
      DO
        i += 1
        SELECT CASE AS CONST Buf[i]
        CASE 0, ASC(!"\n") : i -= 1 : EXIT DO
        CASE ASC("\") : IF esc THEN i += 1
        CASE ASC("""") : IF Buf[i + 1] = ASC("""") THEN i += 1 ELSE EXIT DO
        END SELECT
      LOOP
    CASE ASC("("), ASC("{"), ASC("[") : kl += 1
    CASE ASC(")"), ASC("}"), ASC("]") : kl -= 1 : IF kl <  0 THEN EXIT DO
    CASE ASC(",")                               : IF kl <= 0 THEN EXIT DO
    END SELECT
  LOOP : RETURN " " & RTRIM(MID(Buf, p + 1, i - p), ANY !" \t\v")
END PROPERTY

/'* \brief Context of current token
\returns the context of the current token

This property returns the context of the current token from the 
input buffer.

'/
PROPERTY Parser.SubStr() AS STRING
  IF 0 = Tk THEN RETURN " ?? 0 ?? "
  RETURN MID(Buf, *(Tk + 1) + 1, *(Tk + 2))
END PROPERTY

/'* \brief Check current token position, extract word if string type
\param T The token to read
\returns the context of the token T

This property returns the context of the token T from the 
input buffer.

'/
PROPERTY Parser.SubStr(BYVAL T AS INTEGER PTR) AS STRING
  IF 0 = T THEN RETURN " ?? 0 ?? "
  RETURN MID(Buf, *(T + 1) + 1, *(T + 2))
END PROPERTY


/'* \brief Check current token position, extract word if string type
\returns the current word in upper case

This property returns the context of the current parser position in the 
input buffer in upper case characters.

'/
PROPERTY Parser.USubStr() AS STRING
  RETURN UCASE(MID(Buf, *A + 1, Po - *A))
END PROPERTY

