(*
    BSD 2-Clause License

    Copyright (c) 2018-2019, Anton Krotov
    All rights reserved.
*)

MODULE PARS;

IMPORT PROG, SCAN, ARITH, STRINGS, ERRORS, LISTS, IL, CONSOLE, PATHS, UTILS, C := COLLECTIONS, mConst := CONSTANTS;


CONST

    eCONST*   =  1;  eTYPE*   =  2;  eVAR*     =  3;  eEXPR*    =  4;
    eVREC*    =  5;  ePROC*   =  6;  eVPAR*    =  7;  ePARAM*   =  8;
    eSTPROC*  =  9;  eSTFUNC* = 10;  eSYSFUNC* = 11;  eSYSPROC* = 12;
    eIMP*     = 13;


TYPE

    PATH* = PATHS.PATH;

    PARSER* = POINTER TO rPARSER;

    POSITION* = RECORD (SCAN.POSITION)

        parser*: PARSER

    END;

    EXPR* = RECORD

        obj*:         INTEGER;
        type*:        PROG.TYPE_;
        value*:       ARITH.VALUE;
        stproc*:      INTEGER;
        readOnly*:    BOOLEAN;
        ident*:       PROG.IDENT

    END;

    STATPROC  = PROCEDURE (parser: PARSER);
    EXPRPROC  = PROCEDURE (parser: PARSER; VAR e: EXPR);
    RETPROC   = PROCEDURE (parser: PARSER; e: EXPR; t: PROG.TYPE_; pos: POSITION): BOOLEAN;

    rPARSER = RECORD (C.ITEM)

        fname*:      PATH;
        path:        PATH;
        lib_path:    PATH;
        ext:         PATH;
        modname:     PATH;
        scanner:     SCAN.SCANNER;
        lex*:        SCAN.LEX;
        sym*:        INTEGER;
        unit*:       PROG.UNIT;
        constexp*:   BOOLEAN;
        main*:       BOOLEAN;

        open*:       PROCEDURE (parser: PARSER; modname: ARRAY OF CHAR): BOOLEAN;
        parse*:      PROCEDURE (parser: PARSER);
        StatSeq*:    STATPROC;
        expression*: EXPRPROC;
        designator*: EXPRPROC;
        chkreturn:   RETPROC;

        create*:     PROCEDURE (path, lib_path: PATH; StatSeq: STATPROC; expression, designator: EXPRPROC; chkreturn: RETPROC): PARSER

    END;


VAR

    program*: PROG.PROGRAM;

    parsers: C.COLLECTION;

    lines*: INTEGER;


PROCEDURE destroy* (VAR parser: PARSER);
BEGIN
    IF parser.scanner # NIL THEN
        SCAN.close(parser.scanner)
    END;

    C.push(parsers, parser);
    parser := NIL
END destroy;


PROCEDURE getpos (parser: PARSER; VAR pos: POSITION);
BEGIN
    pos.line   := parser.lex.pos.line;
    pos.col    := parser.lex.pos.col;
    pos.parser := parser
END getpos;


PROCEDURE error* (pos: POSITION; errno: INTEGER);
BEGIN
    ERRORS.ErrorMsg(pos.parser.fname, pos.line, pos.col, errno)
END error;


PROCEDURE check* (condition: BOOLEAN; pos: POSITION; errno: INTEGER);
BEGIN
    IF ~condition THEN
        error(pos, errno)
    END
END check;


PROCEDURE check1* (condition: BOOLEAN; parser: PARSER; errno: INTEGER);
VAR
    pos: POSITION;

BEGIN
    IF ~condition THEN
        getpos(parser, pos);
        error(pos, errno)
    END
END check1;


PROCEDURE Next* (parser: PARSER);
VAR
    errno: INTEGER;

BEGIN
    SCAN.Next(parser.scanner, parser.lex);
    errno := parser.lex.error;
    IF (errno = 0) & (program.target.sys = mConst.Target_iMSP430) THEN
        IF parser.lex.sym = SCAN.lxFLOAT THEN
            errno := -SCAN.lxERROR13
        ELSIF (parser.lex.sym = SCAN.lxCHAR) & (parser.lex.value.typ = ARITH.tWCHAR) THEN
            errno := -SCAN.lxERROR10
        END
    END;

    IF errno # 0 THEN
        check1(FALSE, parser, errno)
    END;
    parser.sym := parser.lex.sym
END Next;


PROCEDURE NextPos (parser: PARSER; VAR pos: POSITION);
BEGIN
    Next(parser);
    getpos(parser, pos)
END NextPos;


PROCEDURE checklex* (parser: PARSER; sym: INTEGER);
VAR
    err: INTEGER;

BEGIN

    IF parser.sym # sym THEN

        CASE sym OF
        |SCAN.lxCOMMA:   err := 65
        |SCAN.lxRROUND:  err := 33
        |SCAN.lxPOINT:   err := 26
        |SCAN.lxIDENT:   err := 22
        |SCAN.lxRSQUARE: err := 71
        |SCAN.lxRCURLY:  err := 35
        |SCAN.lxUNDEF:   err := 34
        |SCAN.lxTHEN:    err := 88
        |SCAN.lxEND:     err := 27
        |SCAN.lxDO:      err := 89
        |SCAN.lxUNTIL:   err := 90
        |SCAN.lxCOLON:   err := 53
        |SCAN.lxOF:      err := 67
        |SCAN.lxASSIGN:  err := 96
        |SCAN.lxTO:      err := 57
        |SCAN.lxLROUND:  err := 64
        |SCAN.lxEQ:      err := 32
        |SCAN.lxSEMI:    err := 24
        |SCAN.lxRETURN:  err := 38
        |SCAN.lxMODULE:  err := 21
        |SCAN.lxSTRING:  err := 66
        END;

        check1(FALSE, parser, err)
    END
END checklex;


PROCEDURE ExpectSym* (parser: PARSER; sym: INTEGER);
BEGIN
    Next(parser);
    checklex(parser, sym)
END ExpectSym;


PROCEDURE ImportList (parser: PARSER);
VAR
    name:    SCAN.IDENT;
    parser2: PARSER;
    pos:     POSITION;
    alias:   BOOLEAN;
    unit:    PROG.UNIT;
    ident:   PROG.IDENT;

BEGIN
    alias := FALSE;

    REPEAT

        ExpectSym(parser, SCAN.lxIDENT);
        name := parser.lex.ident;

        getpos(parser, pos);

        IF ~alias THEN
            ident := PROG.addIdent(parser.unit, name, PROG.idMODULE);
            check(ident # NIL, pos, 30)
        END;

        Next(parser);

        IF (parser.sym = SCAN.lxCOMMA) OR (parser.sym = SCAN.lxSEMI) THEN
            alias := FALSE;
            unit := PROG.getUnit(program, name);

            IF unit # NIL THEN
                check(unit.closed, pos, 31)
            ELSE
                parser2 := parser.create(parser.path, parser.lib_path,
                    parser.StatSeq, parser.expression, parser.designator, parser.chkreturn);

                IF ~parser2.open(parser2, name.s) THEN
                    IF parser.path # parser.lib_path THEN
                        destroy(parser2);
                        parser2 := parser.create(parser.lib_path, parser.lib_path,
                            parser.StatSeq, parser.expression, parser.designator, parser.chkreturn);
                        check(parser2.open(parser2, name.s), pos, 29)
                    ELSE
                        error(pos, 29)
                    END
                END;

                parser2.parse(parser2);
                unit := parser2.unit;
                destroy(parser2)
            END;
            IF unit = program.sysunit THEN
                parser.unit.sysimport := TRUE
            END;
            ident.unit := unit

        ELSIF parser.sym = SCAN.lxASSIGN THEN
            alias := TRUE

        ELSE
            check1(FALSE, parser, 28)
        END

    UNTIL parser.sym = SCAN.lxSEMI;

    Next(parser)

END ImportList;


PROCEDURE QIdent (parser: PARSER; forward: BOOLEAN): PROG.IDENT;
VAR
    ident: PROG.IDENT;
    unit:  PROG.UNIT;

BEGIN
    ASSERT(parser.sym = SCAN.lxIDENT);

    ident := PROG.getIdent(parser.unit, parser.lex.ident, FALSE);

    IF ~forward THEN
        check1(ident # NIL, parser, 48)
    END;

    IF (ident # NIL) & (ident.typ = PROG.idMODULE) THEN
        unit := ident.unit;
        ExpectSym(parser, SCAN.lxPOINT);
        ExpectSym(parser, SCAN.lxIDENT);
        ident := PROG.getIdent(unit, parser.lex.ident, FALSE);
        check1((ident # NIL) & ident.export, parser, 48)
    END

    RETURN ident
END QIdent;


PROCEDURE strcmp* (VAR v: ARITH.VALUE; v2: ARITH.VALUE; operator: INTEGER);
VAR
    str: SCAN.LEXSTR;
    string1, string2: SCAN.IDENT;
    bool: BOOLEAN;

BEGIN

    IF v.typ = ARITH.tCHAR THEN
        ASSERT(v2.typ = ARITH.tSTRING);
        ARITH.charToStr(v, str);
        string1 := SCAN.enterid(str);
        string2 := v2.string(SCAN.IDENT)
    END;

    IF v2.typ = ARITH.tCHAR THEN
        ASSERT(v.typ = ARITH.tSTRING);
        ARITH.charToStr(v2, str);
        string2 := SCAN.enterid(str);
        string1 := v.string(SCAN.IDENT)
    END;

    IF v.typ = v2.typ THEN
        string1 := v.string(SCAN.IDENT);
        string2 := v2.string(SCAN.IDENT)
    END;

    CASE operator OF
    |SCAN.lxEQ: bool := string1.s =  string2.s
    |SCAN.lxNE: bool := string1.s #  string2.s
    |SCAN.lxLT: bool := string1.s <  string2.s
    |SCAN.lxGT: bool := string1.s >  string2.s
    |SCAN.lxLE: bool := string1.s <= string2.s
    |SCAN.lxGE: bool := string1.s >= string2.s
    END;

    ARITH.setbool(v, bool)
END strcmp;


PROCEDURE ConstExpression* (parser: PARSER; VAR v: ARITH.VALUE);
VAR
    e: EXPR;
    pos: POSITION;

BEGIN
    getpos(parser, pos);
    parser.constexp := TRUE;
    parser.expression(parser, e);
    parser.constexp := FALSE;
    check(e.obj = eCONST, pos, 62);
    v := e.value
END ConstExpression;


PROCEDURE FieldList (parser: PARSER; rec: PROG.TYPE_);
VAR
    name:   SCAN.IDENT;
    export: BOOLEAN;
    pos:    POSITION;

BEGIN
    ASSERT(parser.sym = SCAN.lxIDENT);

    WHILE parser.sym = SCAN.lxIDENT DO

        getpos(parser, pos);

        name := parser.lex.ident;

        Next(parser);

        export := parser.sym = SCAN.lxMUL;

        IF export THEN
            check1(parser.unit.scopeLvl = 0, parser, 61);
            Next(parser)
        END;

        check(PROG.addField(rec, name, export), pos, 30);

        IF parser.sym = SCAN.lxCOMMA THEN
            ExpectSym(parser, SCAN.lxIDENT)
        ELSE
            checklex(parser, SCAN.lxCOLON)
        END

    END

END FieldList;


PROCEDURE FormalParameters (parser: PARSER; type: PROG.TYPE_);
VAR
    ident: PROG.IDENT;


    PROCEDURE FPSection (parser: PARSER; type: PROG.TYPE_);
    VAR
        ident:   PROG.IDENT;
        exit:    BOOLEAN;
        vPar:    BOOLEAN;
        dim:     INTEGER;
        t0, t1:  PROG.TYPE_;

    BEGIN
        vPar := parser.sym = SCAN.lxVAR;
        IF vPar THEN
            Next(parser)
        END;

        checklex(parser, SCAN.lxIDENT);
        exit := FALSE;

        WHILE (parser.sym = SCAN.lxIDENT) & ~exit DO
            check1(PROG.addParam(type, parser.lex.ident, vPar), parser, 30);
            Next(parser);
            IF parser.sym = SCAN.lxCOMMA THEN
                ExpectSym(parser, SCAN.lxIDENT)
            ELSIF parser.sym = SCAN.lxCOLON THEN
                Next(parser);
                dim := 0;
                WHILE parser.sym = SCAN.lxARRAY DO
                    INC(dim);
                    check1(dim <= PROG.MAXARRDIM, parser, 84);
                    ExpectSym(parser, SCAN.lxOF);
                    Next(parser)
                END;
                checklex(parser, SCAN.lxIDENT);
                ident := QIdent(parser, FALSE);
                check1(ident.typ = PROG.idTYPE, parser, 68);

                t0 := ident.type;
                t1 := t0;

                WHILE dim > 0 DO
                    t1 := PROG.enterType(program, PROG.tARRAY, -1, 0, parser.unit);
                    t1.base := t0;
                    t0 := t1;
                    DEC(dim)
                END;

                PROG.setParams(type, t1);
                Next(parser);
                exit := TRUE
            ELSE
                checklex(parser, SCAN.lxCOLON)
            END
        END

    END FPSection;


BEGIN
    IF parser.sym = SCAN.lxLROUND THEN

        Next(parser);

        IF (parser.sym = SCAN.lxVAR) OR (parser.sym = SCAN.lxIDENT) THEN
            FPSection(parser, type);
            WHILE parser.sym = SCAN.lxSEMI DO
                Next(parser);
                FPSection(parser, type)
            END
        END;

        checklex(parser, SCAN.lxRROUND);
        Next(parser);

        IF parser.sym = SCAN.lxCOLON THEN
            ExpectSym(parser, SCAN.lxIDENT);
            ident := QIdent(parser, FALSE);
            check1(ident.typ = PROG.idTYPE, parser, 68);
            check1(~(ident.type.typ IN {PROG.tRECORD, PROG.tARRAY}), parser, 69);
            check1( ~(ODD(type.call) & (ident.type.typ = PROG.tREAL)), parser, 113);
            type.base := ident.type;
            Next(parser)
        ELSE
            type.base := NIL
        END

    END
END FormalParameters;


PROCEDURE sysflag (parser: PARSER; proc: BOOLEAN): INTEGER;
VAR
    res, sf: INTEGER;

BEGIN
    IF parser.lex.s = "stdcall" THEN
        sf := PROG.sf_stdcall
    ELSIF parser.lex.s = "stdcall64" THEN
        sf := PROG.sf_stdcall64
    ELSIF parser.lex.s = "ccall" THEN
        sf := PROG.sf_ccall
    ELSIF parser.lex.s = "ccall16" THEN
        sf := PROG.sf_ccall16
    ELSIF parser.lex.s = "win64" THEN
        sf := PROG.sf_win64
    ELSIF parser.lex.s = "systemv" THEN
        sf := PROG.sf_systemv
    ELSIF parser.lex.s = "windows" THEN
        sf := PROG.sf_windows
    ELSIF parser.lex.s = "linux" THEN
        sf := PROG.sf_linux
    ELSIF parser.lex.s = "code" THEN
        sf := PROG.sf_code
    ELSIF parser.lex.s = "noalign" THEN
        sf := PROG.sf_noalign
    ELSE
        check1(FALSE, parser, 124)
    END;

    check1(sf IN program.target.sysflags, parser, 125);

    IF proc THEN
        check1(sf IN PROG.proc_flags, parser, 123)
    ELSE
        check1(sf IN PROG.rec_flags, parser, 123)
    END;

    CASE sf OF
    |PROG.sf_stdcall:
        res := PROG.stdcall
    |PROG.sf_stdcall64:
        res := PROG.stdcall64
    |PROG.sf_ccall:
        res := PROG.ccall
    |PROG.sf_ccall16:
        res := PROG.ccall16
    |PROG.sf_win64:
        res := PROG.win64
    |PROG.sf_systemv:
        res := PROG.systemv
    |PROG.sf_code:
        res := PROG.code
    |PROG.sf_windows:
        IF program.target.sys IN {mConst.Target_iConsole, mConst.Target_iGUI, mConst.Target_iDLL} THEN
            res := PROG.stdcall
        ELSIF program.target.sys IN {mConst.Target_iConsole64, mConst.Target_iGUI64, mConst.Target_iDLL64} THEN
            res := PROG.win64
        END
    |PROG.sf_linux:
        IF program.target.sys IN {mConst.Target_iELF32, mConst.Target_iELFSO32} THEN
            res := PROG.ccall16
        ELSIF program.target.sys IN {mConst.Target_iELF64, mConst.Target_iELFSO64} THEN
            res := PROG.systemv
        END
    |PROG.sf_noalign:
        res := PROG.noalign
    END

    RETURN res
END sysflag;


PROCEDURE procflag (parser: PARSER; VAR import: IL.IMPORT_PROC; isProc: BOOLEAN): INTEGER;
VAR
    call: INTEGER;
    dll, proc: SCAN.LEXSTR;
    pos: POSITION;

BEGIN

    import := NIL;

    IF parser.sym = SCAN.lxLSQUARE THEN
        getpos(parser, pos);
        check1(parser.unit.sysimport, parser, 54);
        Next(parser);
        call := sysflag(parser, TRUE);
        Next(parser);
        IF parser.sym = SCAN.lxMINUS THEN
            Next(parser);
            INC(call)
        END;
        IF ~isProc THEN
            checklex(parser, SCAN.lxRSQUARE)
        END;
        IF parser.sym = SCAN.lxCOMMA THEN
            ExpectSym(parser, SCAN.lxSTRING);
            dll := parser.lex.s;
            ExpectSym(parser, SCAN.lxCOMMA);
            ExpectSym(parser, SCAN.lxSTRING);
            proc := parser.lex.s;
            Next(parser);
            import := IL.AddImp(dll, proc)
        END;
        checklex(parser, SCAN.lxRSQUARE);
        Next(parser)
    ELSE
        CASE program.target.bit_depth OF
        |16: call := PROG.default16
        |32: call := PROG.default32
        |64: call := PROG.default64
        END
    END;

    IF import # NIL THEN
        check(~(program.target.sys IN {mConst.Target_iELF32, mConst.Target_iELF64, mConst.Target_iELFSO32,
            mConst.Target_iELFSO64, mConst.Target_iMSP430}), pos, 70)
    END

    RETURN call
END procflag;


PROCEDURE type (parser: PARSER; VAR t: PROG.TYPE_; flags: SET);
CONST
    comma   = 0;
    closed  = 1;
    forward = 2;

VAR
    arrLen:     ARITH.VALUE;
    typeSize:   ARITH.VALUE;
    ident:      PROG.IDENT;
    unit:       PROG.UNIT;
    pos, pos2:  POSITION;
    fieldType:  PROG.TYPE_;
    baseIdent:  SCAN.IDENT;
    a, b:       INTEGER;
    RecFlag:    INTEGER;
    import:     IL.IMPORT_PROC;

BEGIN
    unit := parser.unit;
    t := NIL;

    IF parser.sym = SCAN.lxIDENT THEN
        ident := QIdent(parser, forward IN flags);

        IF ident # NIL THEN
            check1(ident.typ = PROG.idTYPE, parser, 49);
            t := ident.type;
            check1(t # NIL, parser, 50);
            IF closed IN flags THEN
                check1(t.closed, parser, 50)
            END
        END;

        Next(parser)

    ELSIF (parser.sym = SCAN.lxARRAY) OR ((parser.sym = SCAN.lxCOMMA) & (comma IN flags)) THEN

        IF parser.sym = SCAN.lxARRAY THEN
            getpos(parser, pos2)
        END;
        NextPos(parser, pos);

        ConstExpression(parser, arrLen);

        check(arrLen.typ = ARITH.tINTEGER, pos, 43);
        check(ARITH.check(arrLen),         pos, 39);
        check(ARITH.getInt(arrLen) > 0,    pos, 51);

        t := PROG.enterType(program, PROG.tARRAY, -1, ARITH.getInt(arrLen), unit);

        IF parser.sym = SCAN.lxCOMMA THEN
            type(parser, t.base, {comma, closed})
        ELSIF parser.sym = SCAN.lxOF THEN
            Next(parser);
            type(parser, t.base, {closed})
        ELSE
            check1(FALSE, parser, 47)
        END;

        t.align := t.base.align;

        a := t.length;
        b := t.base.size;
        check(ARITH.mulInt(a, b), pos2, 104);
        check(ARITH.setInt(typeSize, a), pos2, 104);
        t.size := a;

        t.closed := TRUE

    ELSIF parser.sym = SCAN.lxRECORD THEN
        getpos(parser, pos2);
        Next(parser);

        t := PROG.enterType(program, PROG.tRECORD, 0, 0, unit);
        t.align := 1;

        IF parser.sym = SCAN.lxLSQUARE THEN
            check1(parser.unit.sysimport, parser, 54);
            Next(parser);
            RecFlag := sysflag(parser, FALSE);
            t.noalign := RecFlag = PROG.noalign;
            ExpectSym(parser, SCAN.lxRSQUARE);
            Next(parser)
        END;

        IF parser.sym = SCAN.lxLROUND THEN
            check1(~t.noalign, parser, 111);
            ExpectSym(parser, SCAN.lxIDENT);
            getpos(parser, pos);

            type(parser, t.base, {closed});

            check(t.base.typ IN {PROG.tRECORD, PROG.tPOINTER}, pos, 52);

            IF t.base.typ = PROG.tPOINTER THEN
                t.base := t.base.base;
                check(t.base # NIL, pos, 55)
            END;

            check(~t.base.noalign, pos, 112);

            checklex(parser, SCAN.lxRROUND);
            Next(parser);

            t.size := t.base.size;
            IF t.base.align > t.align THEN
                t.align := t.base.align
            END
        ELSE
            t.base := program.stTypes.tANYREC
        END;

        WHILE parser.sym = SCAN.lxIDENT DO
            FieldList(parser, t);

            ASSERT(parser.sym = SCAN.lxCOLON);
            Next(parser);

            type(parser, fieldType, {closed});
            check(PROG.setFields(t, fieldType), pos2, 104);

            IF (fieldType.align > t.align) & ~t.noalign THEN
                t.align := fieldType.align
            END;

            IF parser.sym = SCAN.lxSEMI THEN
                ExpectSym(parser, SCAN.lxIDENT)
            ELSE
                checklex(parser, SCAN.lxEND)
            END
        END;

        t.closed := TRUE;

        IL.AddRec(t.base.num);

        IF ~t.noalign THEN
            check(UTILS.Align(t.size, t.align), pos2, 104);
            check(ARITH.setInt(typeSize, t.size), pos2, 104)
        END;

        checklex(parser, SCAN.lxEND);
        Next(parser)

    ELSIF parser.sym = SCAN.lxPOINTER THEN
        ExpectSym(parser, SCAN.lxTO);
        Next(parser);

        t := PROG.enterType(program, PROG.tPOINTER, program.target.adr, 0, unit);
        t.align := program.target.adr;

        getpos(parser, pos);

        IF parser.sym = SCAN.lxIDENT THEN
            baseIdent := parser.lex.ident
        END;

        type(parser, t.base, {forward});

        IF t.base # NIL THEN
            check(t.base.typ = PROG.tRECORD, pos, 58)
        ELSE
            PROG.frwPtr(unit, t, baseIdent, pos)
        END

    ELSIF parser.sym = SCAN.lxPROCEDURE THEN
        NextPos(parser, pos);
        t := PROG.enterType(program, PROG.tPROCEDURE, program.target.adr, 0, unit);
        t.align := program.target.adr;
        t.call := procflag(parser, import, FALSE);
        FormalParameters(parser, t)
    ELSE
        check1(FALSE, parser, 49)
    END

END type;


PROCEDURE IdentDef (parser: PARSER; typ: INTEGER; VAR name: SCAN.IDENT): PROG.IDENT;
VAR
    ident:  PROG.IDENT;
    pos:    POSITION;

BEGIN
    ASSERT(parser.sym = SCAN.lxIDENT);

    name := parser.lex.ident;
    getpos(parser, pos);
    ident := PROG.addIdent(parser.unit, name, typ);
    check(ident # NIL, pos, 30);
    ident.pos := pos;
    Next(parser);

    IF parser.sym = SCAN.lxMUL THEN
        check1(ident.global, parser, 61);
        ident.export := TRUE;
        Next(parser)
    END

    RETURN ident
END IdentDef;


PROCEDURE ConstTypeDeclaration (parser: PARSER; const: BOOLEAN);
VAR
    ident: PROG.IDENT;
    name:  SCAN.IDENT;
    pos:   POSITION;

BEGIN
    IF const THEN
        ident := IdentDef(parser, PROG.idNONE, name)
    ELSE
        ident := IdentDef(parser, PROG.idTYPE, name)
    END;

    checklex(parser, SCAN.lxEQ);
    NextPos(parser, pos);

    IF const THEN
        ConstExpression(parser, ident.value);
        IF ident.value.typ = ARITH.tINTEGER THEN
            check(ARITH.check(ident.value), pos, 39)
        ELSIF ident.value.typ = ARITH.tREAL THEN
            check(ARITH.check(ident.value), pos, 40)
        END;
        ident.typ  := PROG.idCONST;
        ident.type := PROG.getType(program, ident.value.typ)
    ELSE
        type(parser, ident.type, {})
    END;

    checklex(parser, SCAN.lxSEMI);
    Next(parser)

END ConstTypeDeclaration;


PROCEDURE VarDeclaration (parser: PARSER);
VAR
    ident: PROG.IDENT;
    name:  SCAN.IDENT;
    t:     PROG.TYPE_;

BEGIN

    REPEAT
        ident := IdentDef(parser, PROG.idVAR, name);

        IF parser.sym = SCAN.lxCOMMA THEN
            ExpectSym(parser, SCAN.lxIDENT)
        ELSIF parser.sym = SCAN.lxCOLON THEN
            Next(parser);
            type(parser, t, {});
            PROG.setVarsType(parser.unit, t);
            checklex(parser, SCAN.lxSEMI);
            Next(parser)
        ELSE
            checklex(parser, SCAN.lxCOLON)
        END

    UNTIL parser.sym # SCAN.lxIDENT

END VarDeclaration;


PROCEDURE DeclarationSequence (parser: PARSER): BOOLEAN;
VAR
    ptr: PROG.FRWPTR;
    endmod: BOOLEAN;
    pos: POSITION;


    PROCEDURE ProcDeclaration (parser: PARSER): BOOLEAN;
    VAR
        proc:       PROG.IDENT;
        endname,
        name:       SCAN.IDENT;
        param:      PROG.PARAM;
        unit:       PROG.UNIT;
        ident:      PROG.IDENT;
        e:          EXPR;
        pos, pos1,
        pos2:       POSITION;
        label:      INTEGER;
        enter:      IL.COMMAND;
        call:       INTEGER;
        t:          PROG.TYPE_;
        import:     IL.IMPORT_PROC;
        endmod, b:  BOOLEAN;
        fparams:    SET;
        variables:  LISTS.LIST;
        int, flt:   INTEGER;
        comma:      BOOLEAN;
        code:       ARITH.VALUE;
        codeProc:   BOOLEAN;

    BEGIN
        endmod := FALSE;

        unit := parser.unit;

        call := procflag(parser, import, TRUE);

        getpos(parser, pos);
        pos1 := pos;
        checklex(parser, SCAN.lxIDENT);

        IF import # NIL THEN
            proc := IdentDef(parser, PROG.idIMP, name);
            proc.import := import;
            program.procs.last(PROG.PROC).import := import
        ELSE
            proc := IdentDef(parser, PROG.idPROC, name)
        END;

        check(PROG.openScope(unit, proc.proc), pos, 116);

        proc.type := PROG.enterType(program, PROG.tPROCEDURE, program.target.adr, 0, unit);
        t := proc.type;
        t.align := program.target.adr;
        t.call  := call;

        FormalParameters(parser, t);

        codeProc := call IN {PROG.code, PROG._code};

        IF call IN {PROG.systemv, PROG._systemv} THEN
            check(t.parSize <= PROG.MAXSYSVPARAM, pos, 120)
        END;

        param := t.params.first(PROG.PARAM);
        WHILE param # NIL DO
            ident := PROG.addIdent(unit, param.name, PROG.idPARAM);
            ASSERT(ident # NIL);
            ident.type := param.type;
            ident.offset := param.offset;
            IF param.vPar THEN
                ident.typ := PROG.idVPAR
            END;
            param := param.next(PROG.PARAM)
        END;

        IF import = NIL THEN
            label := IL.NewLabel();
            proc.proc.label := label
        END;

        IF codeProc THEN
            enter := IL.EnterC(label);
            comma := FALSE;
            WHILE (parser.sym # SCAN.lxSEMI) OR comma DO
                getpos(parser, pos2);
                ConstExpression(parser, code);
                check(code.typ = ARITH.tINTEGER, pos2, 43);
                IF program.target.sys # mConst.Target_iMSP430 THEN
                    check(ARITH.range(code, 0, 255), pos2, 42)
                END;
                IL.AddCmd(IL.opCODE, ARITH.getInt(code));
                comma := parser.sym = SCAN.lxCOMMA;
                IF comma THEN
                    Next(parser)
                ELSE
                    checklex(parser, SCAN.lxSEMI)
                END
            END
        END;

        checklex(parser, SCAN.lxSEMI);
        Next(parser);

        IF import = NIL THEN

            IF parser.main & proc.export & program.dll THEN
                IF program.obj THEN
                    check((proc.name.s # "lib_init") & (proc.name.s # "version"), pos, 114)
                END;
                IL.AddExp(label, proc.name.s);
                proc.proc.used := TRUE
            END;

            IF ~codeProc THEN
                b := DeclarationSequence(parser)
            END;

            program.locsize := 0;
            IF call IN {PROG._win64, PROG.win64} THEN
                fparams := PROG.getFloatParamsPos(proc.type, 3, int, flt);
                enter := IL.Enter(label, LSL(ORD(fparams), 5) + MIN(proc.type.parSize, 4))
            ELSIF call IN {PROG._systemv, PROG.systemv} THEN
                fparams := PROG.getFloatParamsPos(proc.type, PROG.MAXSYSVPARAM - 1, int, flt);
                enter := IL.Enter(label, -(LSL(ORD(fparams), 5) + proc.type.parSize))
            ELSIF codeProc THEN

            ELSE
                enter := IL.Enter(label, 0)
            END;
            proc.proc.enter := enter;

            IF ~codeProc & (parser.sym = SCAN.lxBEGIN) THEN
                Next(parser);
                parser.StatSeq(parser)
            END;

            IF ~codeProc & (t.base # NIL) THEN
                checklex(parser, SCAN.lxRETURN);
                NextPos(parser, pos);
                parser.expression(parser, e);
                check(parser.chkreturn(parser, e, t.base, pos), pos, 87)
            END;

            IF ~codeProc THEN
                proc.proc.leave := IL.Leave(t.base # NIL, (t.base # NIL) & (t.base.typ = PROG.tREAL), program.locsize,
                    t.parSize * ORD((t.call IN PROG.callee_clean_up) OR (t.call IN {PROG.systemv, PROG._systemv})));
                enter.param2 := program.locsize;
                checklex(parser, SCAN.lxEND)
            ELSE
                proc.proc.leave := IL.LeaveC()
            END;

            IF program.target.sys = mConst.Target_iMSP430 THEN
                check((enter.param2 * ORD(~codeProc) + proc.type.parSize) * 2 + 16 < program.target.options.ram, pos1, 63)
            END
        END;

        IF parser.sym = SCAN.lxEND THEN
            ExpectSym(parser, SCAN.lxIDENT);
            getpos(parser, pos);
            endname := parser.lex.ident;
            IF ~codeProc & (import = NIL) THEN
                check(endname = name, pos, 60);
                ExpectSym(parser, SCAN.lxSEMI);
                Next(parser)
            ELSE
                IF endname = parser.unit.name THEN
                    ExpectSym(parser, SCAN.lxPOINT);
                    Next(parser);
                    endmod := TRUE
                ELSIF endname = name THEN
                    ExpectSym(parser, SCAN.lxSEMI);
                    Next(parser)
                ELSE
                    error(pos, 60)
                END
            END
        END;

        IF ~codeProc & (import = NIL) THEN
            variables := LISTS.create(NIL);
        ELSE
            variables := NIL
        END;

        PROG.closeScope(unit, variables);

        IF ~codeProc & (import = NIL) THEN
            enter.variables := variables
        END

        RETURN endmod
    END ProcDeclaration;


BEGIN
    IF parser.sym = SCAN.lxCONST THEN
        Next(parser);
        WHILE parser.sym = SCAN.lxIDENT DO
            ConstTypeDeclaration(parser, TRUE)
        END
    END;

    IF parser.sym = SCAN.lxTYPE THEN
        Next(parser);
        WHILE parser.sym = SCAN.lxIDENT DO
            ConstTypeDeclaration(parser, FALSE)
        END
    END;

    ptr := PROG.linkPtr(parser.unit);
    IF ptr # NIL THEN
        pos.line := ptr.pos.line;
        pos.col  := ptr.pos.col;
        pos.parser := parser;
        IF ptr.notRecord THEN
            error(pos, 58)
        ELSE
            error(pos, 48)
        END
    END;

    IF parser.sym = SCAN.lxVAR THEN
        Next(parser);
        IF parser.sym = SCAN.lxIDENT THEN
            VarDeclaration(parser)
        END
    END;

    endmod := FALSE;
    WHILE ~endmod & (parser.sym = SCAN.lxPROCEDURE) DO
        Next(parser);
        endmod := ProcDeclaration(parser)
    END

    RETURN endmod
END DeclarationSequence;


PROCEDURE parse (parser: PARSER);
VAR
    unit:     PROG.UNIT;
    label:    INTEGER;
    name:     INTEGER;
    endmod:   BOOLEAN;
    errlabel: INTEGER;
    errno:    INTEGER;

BEGIN
    ASSERT(parser # NIL);
    ASSERT(parser.scanner # NIL);

    ExpectSym(parser, SCAN.lxMODULE);
    ExpectSym(parser, SCAN.lxIDENT);

    IF ~parser.main THEN
        check1(parser.lex.s = parser.modname, parser, 23)
    END;

    unit := PROG.newUnit(program, parser.lex.ident);

    parser.unit := unit;

    ExpectSym(parser, SCAN.lxSEMI);

    Next(parser);
    IF parser.sym = SCAN.lxIMPORT THEN
        ImportList(parser)
    END;

    CONSOLE.String("compiling "); CONSOLE.String(unit.name.s);
    IF parser.unit.sysimport THEN
        CONSOLE.String(" (SYSTEM)")
    END;
    CONSOLE.Ln;

    label := IL.NewLabel();
    IL.AddJmpCmd(IL.opJMP, label);

    name := IL.putstr(unit.name.s);

    errlabel := IL.NewLabel();
    IL.SetLabel(errlabel);
    IL.StrAdr(name);
    IL.Param1;
    IL.AddCmd0(IL.opERR);

    FOR errno := 1 TO LEN(IL.codes.errlabels) - 1 DO
        IL.SetErrLabel(errno);
        IL.AddCmd(IL.opPUSHC, errno);
        IL.AddJmpCmd(IL.opJMP, errlabel)
    END;

    endmod := DeclarationSequence(parser);

    IL.SetLabel(label);

    IF ~endmod THEN

        IF parser.sym = SCAN.lxBEGIN THEN
            Next(parser);
            parser.StatSeq(parser)
        END;

        checklex(parser, SCAN.lxEND);

        ExpectSym(parser, SCAN.lxIDENT);
        check1(parser.lex.s = unit.name.s, parser, 25);
        ExpectSym(parser, SCAN.lxPOINT)
    END;

    INC(lines, parser.lex.pos.line);
    PROG.closeUnit(unit)
END parse;


PROCEDURE open (parser: PARSER; modname: ARRAY OF CHAR): BOOLEAN;
BEGIN
    ASSERT(parser # NIL);

    STRINGS.append(parser.fname, modname);
    STRINGS.append(parser.fname, parser.ext);
    STRINGS.append(parser.modname, modname);

    parser.scanner := SCAN.open(parser.fname)

    RETURN parser.scanner # NIL
END open;


PROCEDURE NewParser (): PARSER;
VAR
    pars:  PARSER;
    citem: C.ITEM;

BEGIN
    citem := C.pop(parsers);
    IF citem = NIL THEN
        NEW(pars)
    ELSE
        pars := citem(PARSER)
    END

    RETURN pars
END NewParser;


PROCEDURE create* (path, lib_path: PATH; StatSeq: STATPROC; expression, designator: EXPRPROC; chkreturn: RETPROC): PARSER;
VAR
    parser: PARSER;

BEGIN
    parser := NewParser();

    parser.path     := path;
    parser.lib_path := lib_path;
    parser.ext      := mConst.FILE_EXT;
    parser.fname    := path;
    parser.modname  := "";
    parser.scanner  := NIL;
    parser.unit     := NIL;
    parser.constexp := FALSE;
    parser.main     := FALSE;

    parser.open       := open;
    parser.parse      := parse;
    parser.StatSeq    := StatSeq;
    parser.expression := expression;
    parser.designator := designator;
    parser.chkreturn  := chkreturn;
    parser.create     := create

    RETURN parser
END create;


PROCEDURE init* (bit_depth, target: INTEGER; options: PROG.OPTIONS);
BEGIN
    program := PROG.create(bit_depth, target, options);
    parsers := C.create();
    lines   := 0
END init;


END PARS.