'===========================================================================
' Subject: PB ANSI-DRIVER                     Date: Year of 1993/95        
'  Author: Jamshid Khoshrangi                 Code: PB                     
'  Origin: qjackson@direct.ca               Packet: ANSI.ABC
'===========================================================================
$IF 0

  LANSI.BAS               The FSA ANSI-Driver             LANSI.BAS

                              Laleh's ANSI

                              Version  3.1

                LANSI.BAS is now PowerBASIC Compatible!

        Written by Jamshid Khoshrangi (aka "Quinn Tyler Jackson")

   Copyright (C)1993,95 by AhuraMazda(tm) Software.  ALL RIGHTS RESERVED.


  USAGE RIGHTS:

  Although Jamshid Khoshrangi reserves all rights to LANSI.BAS, he grants
  others the right to use it in whole or in part as long as any product
  that uses it explicitly includes in its documentation or opening screen
  (this left to the discretion of the individual programmer) the notice:

       "ANSI emulation provided by Laleh's ANSI (C)1993,95 by AMS."

  DEDICATION:

  This driver is dedicated to my wife Laleh, who has tolerated my sitting
  at a yucky computer terminal for thousands of hours with little or no
  direct return to her.

  UPDATE NOTES:

  UPDATE NOTES: (10 SEP 95)

  I have upgraded LANSI to version 3.0 from version 2.0, and it is
  now compatible 100% with PowerBASIC 3.x.  The upgrade consisted
  mainly of changing a few dozen CONST x statements to %x, and the
  like, as well as using INCR x and DECR x instead of x = x + 1
  and x = x - 1.  During the upgrade, I discovered a few insidious
  glitches in some routines, which I have fixed....

  TECHNICAL NOTES: (31 OCT 93)

  LANSI.BAS was written as an exercise in my investigation into the
  the complexities of Finite State Automata (FSA's).  ANSI graphics
  are particularly suitable to an FSA model, since they rely on a finite
  set of commands and use a type of Reverse Polish Notation (RPN).
  Reverse Polish Notation lends itself amazingly well to efficient
  implementations of Finite State systems.

  This driver includes non-ANSI "ANSI" music support, but this support
  can be turned off by setting the global variable GV.Music is set to
  %FALSE (0).  With this variable set to %FALSE, LANSI treats music strings
  in exactly the same manner a typical ANSI.SYS driver would: it prints
  them to the screen.  This option has been added for ANSI purists.  I have
  yet to call a BBS with such music anyway, but hey, why not have it all?

  Note that the music supported here can be full background.  A 6.5 minute
  song can be downloaded as a series of music sequences in about 5 seconds
  and then played over then next 6.5 minutes in the background on a typical
  2400 baud modem using the system I have implemented here.  This system
  buffers up to 200 "lines" of music sequences.  Many terminals don't allow
  full background music, since they are not written in BASIC and therefore
  have to emulate the PLAY metalanguage.  BASIC has direct access to
  "MB", so this is no problem if double-buffering is used.

  Also, LANSI.BAS supports a subset of the ANSI keyboard redefinition
  capabilities, unlike many other ANSI emulators that totally ignore
  this part of the ANSI standard.  Support for keyboard redefinition can
  be toggled on and off as well.  The only two formats allowed are:

    ESC[{old_ascii_val};"some string"p
    ESC[{old_ascii_val};{new_ascii_val}p

  Since keyboard redefinition is limited to the emulator, there is
  absolutely no chance of so-called ANSI bombs slipping past LANSI and
  into the DOS prompt.

  This code is, for the most part, raw and undocumented.  I've done the
  hard coding; it's up to you to figure out what I've done.  My belief
  is simple in this regard:  if I cannot decipher the code without
  detailed English comments and remarks, then I probably should NOT
  be changing the code!

  If you can understand the raw code, then you might want to go and
  tweak it.  I've found that I do best to leave code I don't "quite"
  understand well-enough-alone.  Be warned that FSA systems are quick,
  but prone to nastiness when incorrectly tweaked.  That's the nature
  of the finite state paradigm.

  Also, you will notice the term "VisiPlex" from time to time.  Just
  ignore it.  This will follow much later, and is included in
  LANSI.BAS just for future compatibility.

  I sincerely hope you find LANSI.BAS useful!

  Jamshid Khoshrangi (aka "Quinn Tyler Jackson")

$ENDIF

%DEBUG = 0

$IF %DEBUG
    $COMPILE MEMORY
    $CPU 80386
$ELSE
    $CODE SEG "AHURAMAZDA"
    $COMPILE UNIT "LANSI.PBU"
    $CPU 8086
$ENDIF

$OPTIMIZE SPEED

%TRUE   = -1
%FALSE  = NOT %TRUE

DEFINT A-Z

TYPE GlobalVarType
    STATE           AS INTEGER      ' What state is the FSA in?

    X               AS INTEGER      ' Cursor ROW
    Y               AS INTEGER      ' Cursor COLUMN
    OldX            AS INTEGER      ' For saving ROW with $e[s
    OldY            AS INTEGER      ' For saving COLUMN with $e[s
    RemoteX         AS INTEGER      ' The remote's ROW
    RemoteY         AS INTEGER      ' The remote's COLUMN

    ScreenHeight    AS INTEGER      ' What is the height of our screen?
    ScreenWidth     AS INTEGER      ' What is the width of our screen?

    Bold            AS INTEGER      ' Bold attribute
    Blink           AS INTEGER      ' Blink attribute
    Reversed        AS INTEGER      ' Reversed attribute
    Concealed       AS INTEGER      ' Concealed attribute

    DesBackspace    AS INTEGER      ' Set to %TRUE if <BACKSPACE>
                                    '     is destructive

    ExpandTab       AS INTEGER      ' Set to %TRUE if <TAB> is expanded
    TabStep         AS INTEGER      ' Number of spaces to expand 1 tab
    LineWrap        AS INTEGER      ' Set to %TRUE if in linewrap mode
    CursorVis       AS INTEGER      ' Set to 1 if cursor is visible
    Music           AS INTEGER      ' Set to %FALSE if in ANSI only mode
    Speaker         AS INTEGER      ' Set to %FALSE if sound turned off
    BeepHz          AS INTEGER      ' Beep tone in Hertz
    BeepDur         AS INTEGER      ' Duration of Beep in ticks

    MapActive       AS INTEGER

    ForeColor       AS INTEGER
    BackColor       AS INTEGER
    ColorAttr       AS INTEGER

    ScreenSeg       AS INTEGER      ' For direct screen writes

    SavedFlag       AS INTEGER      ' Has a $e[s been previously executed?

    VisiPlex        AS INTEGER      ' Are we in VisiPlex mode
    VisiVersion     AS INTEGER      ' If so, what is version of other?
END TYPE

%LOW.LEVEL  = %FALSE
%HIGH.LEVEL = %TRUE

%ANSI.F.BLACK       = 30
%ANSI.F.RED         = 31
%ANSI.F.GREEN       = 32
%ANSI.F.YELLOW      = 33
%ANSI.F.BLUE        = 34
%ANSI.F.MAGENTA     = 35
%ANSI.F.CYAN        = 36
%ANSI.F.WHITE       = 37
%ANSI.B.BLACK       = 40
%ANSI.B.RED         = 41
%ANSI.B.GREEN       = 42
%ANSI.B.YELLOW      = 43
%ANSI.B.BLUE        = 44
%ANSI.B.MAGENTA     = 45
%ANSI.B.CYAN        = 46
%ANSI.B.WHITE       = 47

%STATE.NORMAL           = 0
%STATE.READ.ESC         = 1
%STATE.IN.ANSI          = 2
%STATE.IN.INT.PARAM     = 3
%STATE.READ.SEMICOLON   = 4
%STATE.READ.ANSI.COMMAND= 5

%STATE.READ.OPEN.QUOTE  = 6
%STATE.IN.STRING.LITERAL= 7
%STATE.READ.CLOSE.QUOTE = 8
%STATE.READ.CONTROL.CODE= 9
%STATE.ERROR.RESET.ANSI = 10

%STATE.IN.MUSIC         = 11

%STATE.INTEGER.PUSH     = 12   ' push integer to integer stack
%STATE.STRING.PUSH      = 13   ' push string to string stack

'   TOKEN TYPES:
    '
    '   e = ESC
%TOKEN.ESC          = 1
    '   [ = [
%TOKEN.BRACKET      = 2
    '   0 = 0,1,2,3,4,5,6,7,8,9
%TOKEN.DIGIT        = 3
    '   ; = ;
%TOKEN.SEMICOLON    = 4
    '   H = H,f,A,B,C,D,s,u,J,K,m,h,l,p,n
%TOKEN.ANSI.COMMAND = 5
    '   " = "
%TOKEN.QUOTE        = 6
    '   < = ASCII code less than 32
%TOKEN.CONTROL.CODE = 7
    '   A = Standard A-Z
%TOKEN.ASCII        = 8
    '   M = M
%TOKEN.MUSIC.STRING.START = 9
    '   # = ^N
%TOKEN.ANSI.MUSIC   = 10

TokenTableData:

'        ASCII   Type     ASCII   Type

DATA      "",    01,      "[",    02
DATA      "0",    03,      "1",    03
DATA      "2",    03,      "3",    03
DATA      "4",    03,      "5",    03
DATA      "6",    03,      "7",    03
DATA      "8",    03,      "9",    03
DATA      ";",    04,      "H",    05
DATA      "f",    05,      "A",    05
DATA      "B",    05,      "C",    05
DATA      "D",    05,      "s",    05
DATA      "u",    05,      "J",    05
DATA      "K",    05,      "m",    05
DATA      "h",    05,      "l",    05
DATA      "p",    05,      "R",    05
DATA      "n",    05,      "",    10
DATA      "M",    09,      "",    07
DATA      " ",    08,      "",    07
DATA      "",    07,      "",    07
DATA      "",    07,      "",    07
DATA      "",    07       "",     -1

StateShiftTableData:

' WARNING:
'
'   Any tweaking of this table may be FATAL to the working
'   of this driver!  Unless you ABSOLUTELY understand what
'   you are doing, please DO NOT twiddle these bits!

'            e   [   0   ;   H   "   <   A   M   #
DATA   00,  01, 00, 00, 00, 00, 00, 09, 00, 00, 00
DATA   01,  00, 02, 00, 00, 00, 00, 00, 00, 00, 00
DATA   02,  00, 10, 03, 04, 05, 06, 10, 10, 11, 10
DATA   03,  10, 10, 03, 12, 05, 10, 10, 10, 10, 10
DATA   04,  10, 10, 12, 10, 10, 06, 10, 10, 10, 10
DATA   06,  07, 07, 07, 07, 07, 08, 07, 07, 07, 10
DATA   07,  07, 07, 07, 07, 07, 08, 07, 07, 07, 07
DATA   08,  10, 10, 10, 11, 05, 10, 10, 10, 10, 10
DATA   09,  01, 00, 00, 00, 00, 00, 09, 00, 00, 00
DATA   11,  10, 10, 11, 10, 11, 10, 10, 11, 11, 05

$IF %DEBUG
    DIM AnsiGv AS SHARED GlobalVarType
$ELSE
    DIM AnsiGv AS SHARED GlobalVarType
    EXTERNAL AnsiGv
$ENDIF

DIM TokenTable(255)     AS SHARED INTEGER
DIM StateTable(13, 10)  AS SHARED INTEGER
DIM KeyTable(255)       AS SHARED STRING
DIM MapActive(255)      AS SHARED INTEGER

DIM CharBuffer          AS SHARED STRING
DIM IntStack(10)        AS SHARED INTEGER
DIM StrStack(10)        AS SHARED STRING
DIM IntPtr              AS SHARED INTEGER
DIM StringPtr           AS SHARED INTEGER

DIM MusicBuffer(200)    AS SHARED STRING
DIM BarPtr              AS SHARED INTEGER
DIM TopPtr              AS SHARED INTEGER

'   DEBUG CODE STARTS HERE!

$IF %DEBUG

LansiSystemInit

CLS

OPEN "C:\DOS\TRM\UTILS\TERMINAT.LGO" FOR BINARY AS #1

GET$ #1, LOF(1), Test$

CLOSE #1

FOR i = 1 TO LEN(Test$)

    LansiByteInterpret ASC(MID$(Test$, i, 1))

NEXT i

DO

LOOP UNTIL LEN(INKEY$)

$ENDIF

END

'DEBUG CODE ENDS HERE!

MusicHandler:
    INCR BarPtr
    INCR TotChar, LEN(MusicBuffer(BarPtr))

    SELECT CASE BarPtr
        CASE 201
            BarPtr = 1

        CASE TopPtr
            PLAY "MF" + MusicBuffer(BarPtr)
            OverFlag = %TRUE
            BarPtr = 0
            TopPtr = 0
            PLAY OFF

    END SELECT

    IF TopPtr > 1 THEN
        PLAY MusicBuffer(BarPtr)
    END IF

    MusicBuffer(BarPtr) = ""

RETURN  ' From MusicHandler:

SUB LansiSystemInit () PUBLIC

    StateTableInit
    TokenTableInit

END SUB

SUB BarPush (Score AS STRING)

INCR TopPtr

IF TopPtr = 201 THEN
    NotFirst = %TRUE
    TopPtr = 1
END IF

MusicBuffer(TopPtr) = Score

IF TopPtr = 1 AND NOT NotFirst THEN
    PLAY "MBT255N0N0N0T120"
END IF

END SUB

SUB ControlCodeReact (code AS INTEGER)

%CONT.CTRL.D    = 4
%CONT.CTRL.E    = 5
%CONT.CTRL.G    = 7
%CONT.BACKSPACE = 8
%CONT.TAB       = 9
%CONT.PAGEFEED  = 12
%CONT.CTRL.S    = 19
%CONT.CTRL.X    = 24

SELECT CASE code
    CASE %CONT.CTRL.E
        CursorUp 1

    CASE %CONT.CTRL.X
        CursorDown 1

    CASE %CONT.CTRL.D
        CursorRight 1

    CASE %CONT.CTRL.S
        CursorLeft 1

    CASE %CONT.CTRL.G
        IF AnsiGv.Speaker THEN
            SOUND AnsiGv.BeepHz, AnsiGv.BeepDur
        END IF

    CASE %CONT.PAGEFEED
        LansiScreenClear
   
    CASE %CONT.BACKSPACE
        IF AnsiGv.DesBackspace THEN
            IF AnsiGv.Y > 1 THEN
                DECR AnsiGv.Y
                CursorLocate AnsiGv.X, AnsiGv.Y
                sqjPRINT 32, %HIGH.LEVEL' Print a space
                DECR AnsiGv.Y
                CursorLocate AnsiGv.X, AnsiGv.Y
            END IF
        END IF

    CASE %CONT.TAB
        IF AnsiGv.ExpandTab THEN
            IF AnsiGv.Y + AnsiGv.TabStep < 79 THEN
                FOR i = 1 TO AnsiGv.TabStep
                    sqjPRINT 32, %LOW.LEVEL
                NEXT i
            END IF
        ELSE
            sqjPRINT 32, %HIGH.LEVEL
        END IF
END SELECT

END SUB

SUB CursorDown (RowsDown AS INTEGER)

IF IntPtr = 0 THEN
    RowsDown = 1
END IF

TempX = AnsiGv.X + RowsDown

IF TempX > AnsiGv.ScreenHeight THEN
    TempX = AnsiGv.ScreenHeight
END IF

AnsiGv.X = TempX

CursorLocate AnsiGv.X, AnsiGv.Y

END SUB

SUB CursorLeft (ColsLeft AS INTEGER)

IF IntPtr = 0 THEN
    ColsLeft = 1
END IF

TempY = AnsiGv.Y - ColsLeft
IF TempY < 1 THEN
    TempY = 1
END IF

AnsiGv.Y = TempY

CursorLocate AnsiGv.X, AnsiGv.Y

END SUB

SUB CursorLocate (X AS INTEGER,_
                     Y AS INTEGER)

IF Y > AnsiGv.ScreenWidth THEN
    Y = 1
    IF AnsiGv.LineWrap THEN
        INCR X
    END IF
ELSE
    IF Y < 1 THEN
        Y = 1
    END IF
END IF

IF X > AnsiGv.ScreenHeight THEN
    EXIT SUB
ELSE
    IF X < 1 THEN
        X = 1
    END IF
END IF

AnsiGv.X = X
AnsiGv.Y = Y

IF AnsiGv.CursorVis THEN
    LOCATE AnsiGv.X, AnsiGv.Y, AnsiGv.CursorVis, 6, 7
END IF

END SUB

SUB CursorRestore

IF AnsiGv.SavedFlag THEN
    CursorLocate AnsiGv.OldX, AnsiGv.OldY
END IF

END SUB

SUB CursorRight (ColsRight AS INTEGER)

IF IntPtr = 0 THEN
    ColsRight = 1
END IF

TempY = AnsiGv.Y + ColsRight
IF TempY > AnsiGv.ScreenWidth THEN
    TempY = AnsiGv.ScreenWidth
END IF

AnsiGv.Y = TempY

CursorLocate AnsiGv.X, AnsiGv.Y

END SUB

SUB CursorSave

AnsiGv.SavedFlag = %TRUE

AnsiGv.OldX = AnsiGv.X
AnsiGv.OldY = AnsiGv.Y

END SUB

SUB CursorUp (RowsUp AS INTEGER)

IF IntPtr = 0 THEN
    RowsUp = 1
END IF

TempX = AnsiGv.X - RowsUp

IF TempX < 1 THEN
    TempX = 1
END IF
   
AnsiGv.X = TempX

CursorLocate AnsiGv.X, AnsiGv.Y

END SUB

SUB EOLErase

CursorSave

CursorVis = AnsiGv.CursorVis
AnsiGv.CursorVis = %FALSE

FOR Ptr = AnsiGv.Y TO AnsiGv.ScreenWidth
    sqjPRINT 0, %LOW.LEVEL
NEXT Ptr

AnsiGv.CursorVis = CursorVis
CursorRestore

CursorLocate AnsiGv.X, AnsiGv.Y

END SUB

SUB IntPush

IF LEN(CharBuffer) THEN
    INCR IntPtr
    IntStack(IntPtr) = VAL(CharBuffer)
    CharBuffer = ""
END IF

END SUB

SUB KeyboardMap (KeyCode AS INTEGER,_
                    Redefinition AS STRING)

SELECT CASE KeyCode
    CASE 8474   ' Not a key!  This is the VisiPlex flag!
                ' "8474" is "VISI" dialed on a phone...

        IF LEFT$(Redefinition, 10) = "VisiPlex V" THEN
            AnsiGv.VisiPlex = %TRUE
            AnsiGv.MapActive = %FALSE
            AnsiGv.VisiVersion = VAL(MID$(Redefinition, 11))
        END IF

    CASE ELSE
        SELECT CASE AnsiGv.VisiPlex
            CASE %TRUE
                VisiPlexComReact KeyCode, Redefinition

            CASE %FALSE
                IF KeyCode < 256 THEN
                    KeyTable(KeyCode) = Redefinition
                    MapActive(KeyCode) = %TRUE
                END IF

        END SELECT
END SELECT

END SUB

SUB LinewrapDisable

AnsiGv.LineWrap = %FALSE

END SUB

SUB MusicPlay (Score AS STRING)

SELECT CASE AnsiGv.Music
    CASE %TRUE
        IF AnsiGv.Speaker THEN
            sqjPLAY Score
        END IF

    CASE %FALSE
        FOR i = 1 TO LEN(Score)
            sqjPRINT ASC(MID$(Score, i, 1)), %LOW.LEVEL
        NEXT i
        sqjPRINT 14, %LOW.LEVEL

END SELECT

END SUB

SUB sqjPLAY (Score AS STRING)

Score = UCASE$(MID$(Score,2))

SELECT CASE INSTR(Score, "MB")
    CASE 0
        PLAY Score

    CASE ELSE
        ON PLAY(3) GOSUB MusicHandler
        PLAY ON
        BarPush Score

END SELECT

END SUB

SUB sqjPRINT (Bite AS INTEGER,_
              Level AS INTEGER)

SELECT CASE Level * MapActive(Bite) * AnsiGv.MapActive
    CASE 0
        SELECT CASE Bite
            CASE 13
                AnsiGv.Y = 1

            CASE 10
                IF AnsiGv.X < AnsiGv.ScreenHeight THEN
                    INCR AnsiGv.X
                ELSE
                    '   This forces a screen scroll
                    LOCATE AnsiGv.ScreenHeight + 1, 1
                    PRINT
                END IF

            CASE ELSE
                FPRINT Bite
                INCR AnsiGv.Y

        END SELECT

    CASE ELSE
        FOR i = 1 TO LEN(KeyTable(Bite))
            sqjPRINT ASC(MID$(KeyTable(Bite), i, 1)), %LOW.LEVEL
        NEXT i

END SELECT

CursorLocate AnsiGv.X, AnsiGv.Y

END SUB

SUB RemoteCursorSet (X AS INTEGER,_
                        Y AS INTEGER)

'   This information is mostly useless, and is received as
'   a result of sending a ESC[6n sequence....

AnsiGv.RemoteX = X
AnsiGv.RemoteY = Y

END SUB

SUB ScreenAttrReact ()

SELECT CASE AnsiGv.Reversed
    CASE %TRUE
        TempFore = AnsiGv.BackColor
        TempBack = AnsiGv.ForeColor

    CASE %FALSE
        TempFore = AnsiGv.ForeColor
        TempBack = AnsiGv.BackColor

END SELECT

IF AnsiGv.Bold THEN
    TempFore = TempFore + 8
END IF

IF AnsiGv.Concealed THEN
    TempFore = TempBack
    '   Version 3.1 fix... turns off cursor by force
    LOCATE , , 0
ELSE
    '   Otherwise, we must turn the cursor on by force
    LOCATE , , 1
END IF

AnsiGv.ColorAttr = TempBack * 16 + TempFore

IF AnsiGv.Blink THEN
    BIT SET AnsiGv.ColorAttr, 7
END IF

COLOR TempFore, TempBack

END SUB

SUB ScreenAttrSet (Attribute AS INTEGER)

SELECT CASE Attribute
    CASE 0
        AnsiGv.Bold     = %FALSE
        AnsiGv.Blink    = %FALSE
        AnsiGv.Reversed = %FALSE
        AnsiGv.Concealed= %FALSE
        AnsiGv.ForeColor= 7
        AnsiGv.BackColor= 0

    CASE 1
        AnsiGv.Bold     = %TRUE

    CASE 5
        AnsiGv.Blink    = %TRUE

    CASE 7
        AnsiGv.Reversed = %TRUE

    CASE 8
        AnsiGv.Concealed= %TRUE

    CASE %ANSI.F.BLACK
        AnsiGv.ForeColor= 0

    CASE %ANSI.F.RED
        AnsiGv.ForeColor= 4

    CASE %ANSI.F.GREEN
        AnsiGv.ForeColor= 2

    CASE %ANSI.F.YELLOW
        AnsiGv.ForeColor= 6

    CASE %ANSI.F.BLUE
        AnsiGv.ForeColor= 1

    CASE %ANSI.F.MAGENTA
        AnsiGv.ForeColor= 5

    CASE %ANSI.F.CYAN
        AnsiGv.ForeColor= 3

    CASE %ANSI.F.WHITE
        AnsiGv.ForeColor= 7

    CASE %ANSI.B.BLACK
        AnsiGv.BackColor= 0

    CASE %ANSI.B.RED
        AnsiGv.BackColor= 4

    CASE %ANSI.B.GREEN
        AnsiGv.BackColor= 2

    CASE %ANSI.B.YELLOW
        AnsiGv.BackColor= 6

    CASE %ANSI.B.BLUE
        AnsiGv.BackColor= 1

    CASE %ANSI.B.MAGENTA
        AnsiGv.BackColor= 5

    CASE %ANSI.B.CYAN
        AnsiGv.BackColor= 3

    CASE %ANSI.B.WHITE
        AnsiGv.BackColor= 7

END SELECT

AnsiGv.ColorAttr = AnsiGv.ForeColor + AnsiGv.BackColor * 16

END SUB

SUB LansiScreenClear () PUBLIC

AnsiGv.X = 1
AnsiGv.Y = 1

COLOR AnsiGv.ForeColor, AnsiGv.BackColor

CLS TEXT

CursorLocate AnsiGv.X, AnsiGv.Y

END SUB

SUB ScreenModeSet (Mode AS INTEGER)

SELECT CASE Mode
    CASE 0, 1
        ScreenWidthSet 40

    CASE 2, 3
        ScreenWidthSet 80

    CASE 7
        AnsiGv.LineWrap = %TRUE

END SELECT

END SUB

SUB ScreenWidthSet (Columns AS INTEGER)

WIDTH Columns, 25

AnsiGv.ScreenWidth = Columns

END SUB

SUB StateReact (Bite AS INTEGER)

SELECT CASE AnsiGv.STATE
    CASE %STATE.NORMAL
        sqjPRINT Bite, %HIGH.LEVEL

    CASE %STATE.READ.ESC
        ' No need to "do" anything.

    CASE %STATE.IN.ANSI
        ' No need to "do" anything.

    CASE %STATE.IN.INT.PARAM
        CharBuffer = CharBuffer + CHR$(Bite)

    CASE %STATE.READ.SEMICOLON
        IntPush
        AnsiGv.STATE = %STATE.IN.ANSI
        StateReact 0

    CASE %STATE.READ.ANSI.COMMAND
        SELECT CASE CHR$(Bite)
            CASE "H", "f"
                IntPush
                SELECT CASE IntPtr
                    CASE 2
                        CursorLocate IntStack(1), IntStack(2)

                    CASE 1
                        '   I added this during the debugging of v3.0
                        '   since I had overlooked it for some reason....
                        CursorLocate IntStack(1), 1

                    CASE 0
                        CursorLocate 1, 1

                END SELECT
                IntPtr = 0

            CASE "A"
                IntPush
                CursorUp IntStack(1)
                IntPtr = 0

            CASE "B"
                IntPush
                CursorDown IntStack(1)
                IntPtr = 0

            CASE "C"
                IntPush
                CursorRight IntStack(1)
                IntPtr = 0

            CASE "D"
                IntPush
                CursorLeft IntStack(1)
                IntPtr = 0

            CASE "s"
                CursorSave

            CASE "u"
                CursorRestore

            CASE "J"
                IntPush
                LansiScreenClear
                IntPtr = 0

            CASE "K"
                EOLErase

            CASE "m"
                IntPush
                FOR Ptr = 1 TO IntPtr
                    ScreenAttrSet IntStack(Ptr)
                    '   This was moved here to fix a glitch since
                    '   [0;xxx was not read properly
                    ScreenAttrReact
                NEXT Ptr

                IntPtr = 0

            CASE "h"
                IntPush
                ScreenModeSet IntStack(1)
                IntPtr = 0

            CASE "l"
                IntPush
                '   Version 3.1 fix -- all modes except 7 act as
                '   with "h" command
                SELECT CASE InStack(1)
                    CASE 7
                        LinewrapDisable

                    CASE ELSE
                        ScreenModeSet IntStack(1)

                END SELECT

            CASE "p"
                IntPush
                SELECT CASE IntPtr
                    CASE 1
                        KeyboardMap IntStack(1), StrStack(1)
                        StringPtr   = 0

                    CASE 2
                        KeyboardMap IntStack(1), CHR$(IntStack(2))

                END SELECT

                IntPtr      = 0

            CASE "n"
                IntPush
                SystemReqReact IntStack(1)
                IntPtr = 0

            CASE ""
                StringPush
                MusicPlay StrStack(1)
                StringPtr = 0

            CASE "R"
                IntPush
                RemoteCursorSet IntStack(1), IntStack(2)
                IntPtr = 0

        END SELECT

        AnsiGv.STATE = %STATE.NORMAL

    CASE %STATE.READ.OPEN.QUOTE

    CASE %STATE.IN.STRING.LITERAL
        CharBuffer = CharBuffer + CHR$(Bite)

    CASE %STATE.READ.CLOSE.QUOTE
        StringPush

    CASE %STATE.READ.CONTROL.CODE
        ControlCodeReact Bite

    CASE %STATE.ERROR.RESET.ANSI
        IntPtr      = 0
        StringPtr   = 0
        CharBuffer  = ""
        sqjPRINT Bite, %LOW.LEVEL
        AnsiGv.STATE = %STATE.NORMAL

    CASE %STATE.IN.MUSIC
        CharBuffer = CharBuffer + CHR$(Bite)

    CASE %STATE.INTEGER.PUSH
        IntPush
        AnsiGv.STATE = %STATE.IN.ANSI

    CASE %STATE.STRING.PUSH
        StringPush
        AnsiGv.STATE = %STATE.IN.ANSI

END SELECT

END SUB

SUB StateTableInit ()

RESTORE StateShiftTableData

DIM Tkn(1 TO 10)

FOR STATE = 0 TO 10
    READ STATE
    READ Tkn(1),_
         Tkn(2),_
         Tkn(3),_
         Tkn(4),_
         Tkn(5),_
         Tkn(6),_
         Tkn(7),_
         Tkn(8),_
         Tkn(9),_
         Tkn(10)

    FOR TokenType = 1 TO 10
        StateTable(STATE, TokenType) = Tkn(TokenType)
    NEXT

NEXT STATE

END SUB

SUB StringPush

INCR StringPtr
StrStack(StringPtr) = CharBuffer
CharBuffer = ""

END SUB

SUB SystemReqReact (Request AS INTEGER)

SELECT CASE Request
    CASE 6
        ' Request cursor position!
        ' Put code here that sends cursor position in format:
        '
        '       $e[xx;yyR

    CASE ELSE
        ' DOS's %ANSI.SYS responds just as above!

END SELECT

END SUB

SUB TokenTableInit ()

' Set some default start up values for the global system variables
' These will suffice for most purposes.

AnsiGv.X            = 1
AnsiGv.Y            = 1
AnsiGv.ScreenWidth  = 80
AnsiGv.ScreenHeight = 24
AnsiGv.DesBackspace = %TRUE
AnsiGv.ExpandTab    = %TRUE
AnsiGv.TabStep      = 5
AnsiGv.LineWrap     = %TRUE
AnsiGv.ForeColor    = 7
AnsiGv.BackColor    = 0
AnsiGv.ColorAttr    = &H07
AnsiGv.CursorVis    = 1
AnsiGv.Music        = %TRUE
AnsiGv.Speaker      = %TRUE
AnsiGv.BeepHz       = 300
AnsiGv.BeepDur      = 3
AnsiGv.MapActive    = %TRUE

IF (pbvScrnCard AND 1) = 0 THEN
    AnsiGv.ScreenSeg = &HB800        ' color monitor
ELSE
    AnsiGv.ScreenSeg = &HB000        ' mono monitor
END IF

RESTORE TokenTableData

FOR i = 0 TO 255
    TokenTable(i) = %TOKEN.ASCII
    KeyTable(i) = CHR$(i)
    MapActive(i) = %FALSE
NEXT i

TokenTable(9)  = 7
TokenTable(10) = 8
TokenTable(34) = 6

DO
    READ Char$, TokenType
    IF TokenType > 0 THEN
        TokenTable(ASC(Char$)) = TokenType
    ELSE
        EXIT DO
    END IF
LOOP

CursorLocate AnsiGv.X, AnsiGv.Y

END SUB

SUB VisiPlexComReact (ComType AS INTEGER,_
                         VisiCommand AS STRING)

' VisiPlex driver goes here and responds to the specific VisiPlex
' commands that will be standardized later!  Until then, you'll just
' have to wait.
'
' It will be a simple matter of doing this here:
'
'       VisiDriver ComType, VisiCommand
'
' AND BINGO!  Instant support!

END SUB

SUB LansiByteInterpret (BYVAL Bite AS INTEGER) PUBLIC

AnsiGv.STATE = StateTable(AnsiGv.STATE, TokenTable(Bite))

StateReact Bite

END SUB

SUB FPRINT(BYVAL Char AS INTEGER)

	ScrnSeg = AnsiGv.ScreenSeg

	! push DS                 ; save DS for PowerBASIC

	! mov  AX, ScrnSeg        ; put screen segment in AX
	! mov  ES, AX             ; move to ES

	Row = AnsiGv.X
	! mov  AX, Row            ; put row in AX
	! dec  AX                 ; minus one
	! mov  CX, 160            ; AX =
	! mul  CX                 ;   AX * 160
	! mov  DI, AX             ; put it in DI
	Col = AnsiGv.Y
	! mov  AX, Col            ; put column in AX
	! dec  AX                 ; minus one
	! shl  AX, 1              ; times 2
	! add  DI, AX             ; add to DI

	Attr = AnsiGv.ColorAttr
	! mov  AH, Attr           ; put attribute in AH
	! mov  AL, Char

WriteChar:
	! stosw                   ; write char and attribute to screen
QPExit:
	! pop  DS                 ; restore DS for PowerBASIC

END SUB
