// BLEX1.bcpl - BCPL Compiler -- Lexical Analyzer, Part 1.
// Copyright Xerox Corporation 1980
// Last modified on Wed 01 Nov 72 0009.49 by  jec.

//   Last modified by Butterfield, February 12, 1979  4:23 PM
//   - reformatting; Readsymb, get from - 1/23/79
//   - Readsymb, convert get names to lowercase for DictionaryEntry - 1/26

//   Readsymb   Read the next lexical token and write it on the LEX file

get "blexx"   //  Declarations of values for lexemes.

//----------------------------------------------------------------------------
static  // Scalars used by LEX
//----------------------------------------------------------------------------
[
ReadAhead = false  // true if a token has been read ahead into ExtraItem.
ExtraItem = nil  //  Holds a token that has been read ahead, if any.
LastItem = 0  // Holds last item processed by LexOut
NewLineptr = 0  // holds pointer to last $*N seen
NLPending = false  // true if a newline was just seen
Chkind = Empty  // Character type, as in above manifest declaration.
]


//  The following routine is the lexical processor for BCPL.  Calling it causes the next lexeme
//  to be read from the input stream and the appropriate canonic symbol to be stored into Symb.
//  In addition, there may be data in the V-vector, as follows...
//   Symb      V!0      V!1, V!2, ...
//   NUMBER      value
//   CHARCONST   value
//   NAME      dict pointer
//   STRINGCONST   n      n words of the string, packed.
//   SECTBRA      n      the label, as a packed string n words long
//   SECTKET      n      ditto

//  Readsymb writes out the lexeme on the LEX file, along with the necessary
//  information from V.  Nextsymb in CAE reads this file and puts the info back into V.

// This routine also handles the preprocessor rules regarding insertion of
// SEMICOLON and DO where needed, and processes the GET directive.
// This routine also handles the elimintation of control characters
// which are added to formatted files by BRAVO 9/16/75

//----------------------------------------------------------------------------
let Readsymb() be
//----------------------------------------------------------------------------
[
 let getList = false;  // used to indicate within get a, b, ... from "file"
 manifest
    [
    getFirst = 1; getNew = 2; getOld = 3;
    getFrom = 3; getNewFrom = 5; getOldFrom = 6;
    ]
 static [ outflag = true; T = nil ]
 if ReadAhead do  // Do we already have a symbol in hand?
  [ ReadAhead=false; LexOut(ExtraItem); return; ]  // Yes, return it.
 let Vp = 0; V!0 = 0  //  Counter of characters read.
 unless Chkind eq Empty goto M
Next:  // Come here to read next character.
 Rch();
L:
 Chkind = Kind(Ch)
M:
 Curline = Chline  // Set the line pointer for the char last read
 switchon Chkind into  // What sort of character is it?
    [
    case Ignorable:  // Skip spaces and such.
     [ Rch() repeatwhile Ch eq $*s % Ch eq $*t; goto L ]
    case Digit: [ let n = DoNumber(10); return; ]
    case BravoTail:  // Bravo formatting information follows ↑Z up to *n
     [ Rch() repeatuntil Ch eq $*n % Ch eq #777; goto L; ]

    case Capital:   //  Start of an identifier
    case Small:   //  Start of a possible reserved word
       [
       static [ FirstName = true; UpperCase = false ]
       let SmallSW = Chkind eq Small  // To remember if it is all lower case
       let DigitSW = false  // To remember if it has a digit in it
          [
          switchon Chkind into
             [
             default: break  // Not a character that is in identifiers.
             case Digit: DigitSW = true
             case Capital: SmallSW = false
             case Small:  //  Might still be reserved
             if Vp ge Vmax break   //  Name too long.
             Vp = Vp + 1; V!Vp = Ch     //  Store the character.
             Rch(); Chkind = Kind(Ch)   //  Read the next one.
             ]
          ] repeat
       if Vp > NAMELENGTH do [ LEXreport(11); Vp = NAMELENGTH ]
       V!0 = Vp;  // The word has been scanned.
       if SWUpperCase then for i = 1 to V!0 do
        if $a le V!i & V!i le $z do V!i = V!i+($A-$a)
       if SWLowerCase then for i = 1 to V!0 do  //currently never set
        if $A le V!i & V!i le $Z do V!i = V!i+($a-$A)
       let Name = vec NAMELENGTH/Bytesperword; Packstring(V,Name)
       if FirstName do
          [
          if SWUpperCase % not SmallSW do SWUpperCase, UpperCase = true, true
          FirstName = false
          ]
       if UpperCase & not DigitSW do
          [
          for i = 1 to V!0 do if $A le V!i & V!i le $Z do V!i = V!i + ($a-$A)
          SmallSW = true
          ]
       let r = 0  // see if it's a reserved word
       if SmallSW do r = ReservedWord()
       if getList ne false then test getList gr getFrom
          ifnot
             [
             let entry = DictionaryEntry(Name)
             let new = Dictionary!(entry-1)<<SYMPTR.Virgin
             if getList eq getFirst then getList = new? getNew, getOld;
             if getList ne (new? getNew, getOld) then LEXreport(4);
             test Ch eq $,
                ifnot getList = getList + getFrom;
                ifso Rch();
             while Ch eq $*s % Ch eq $*t % Ch eq $*n do Rch();
             Chkind = Kind(Ch);
             if Chkind ne Capital & Chkind ne Small then LEXreport(4);
             Vp = 0; goto M;
             ]
          ifso
             [
             while Ch eq $*s % Ch eq $*t % Ch eq $*n do Rch();
             test r eq FROM & Ch eq $*"
                ifnot LEXreport(4);
                ifso 
                   [
                   let new = GetFilename(Name, getList eq getOldFrom);
                   test getList eq getNewFrom
                       ifnot if new then LEXreport(4);
                       ifso
                        [ OpenSource(Name, true); Chkind=Empty; Readsymb(); ]
                   ]
             return;
             ]

       switchon r into  //  handle "get", "newname", and "from"
          [
          case GET:
             [
             while Ch eq $*s % Ch eq $*t do Rch()
             if Ch ne $*" then
                [
                Chkind = Kind(Ch);
                if Chkind ne Capital & Chkind ne Small then LEXreport(4);
                getList = getFirst; Vp = 0; goto M;
                ]
             if GetFilename(Name) then
              [ OpenSource(Name); Chkind = Empty; Readsymb(); ]
             return;
             ]
          case NEWNAME:
             [
             outflag = false; Readsymb(); outflag = true
             V!0 = -(Dictionary!(T-1)<<SYMPTR.Virgin); LexOut(NUMBER) 
             return
             ]
          default:  // not 0, "get", "newname", or "from"
           [ LexOut(r); return; ]
          case FROM:  // from is only reserved in get
          case 0:  // not a reserved word
          ]
       T = DictionaryEntry(Name)  // not reserved, so look the name up
       V!0 = T   //  and return the dict pointer
       if outflag then LexOut(NAME)   //  with a NAME lexeme
       return
       ]
    default:  // not simple, branch on the character rather than the type.
    ]
 Chkind = Empty   //  Mark the character as having been used.
 switchon Ch into
    [
    case $#:  // An octal number.
       [
       Rch(); unless $0 le Ch & Ch le $7 do LEXreport(6)
       let n = DoNumber(8); return
       ]
    case $[:
    case $]:
       [
       let x = Ch and VV = vec BRACKETLENGTH  // Remember what it was.
          [
          Rch(); Chkind = Kind(Ch); unless Chkind ge Digit break
          Vp = Vp + 1; if Vp ge Vmax break; VV!Vp = Ch
          ] repeat
       if Vp gr BRACKETLENGTH do [ LEXreport(7); Vp = BRACKETLENGTH ]
       test Vp eq 0
          ifso LexOut(x eq $[ ? SECTBRA, SECTKET)
          ifnot  // Pack the label (test backwards to keep brackets balanced)
           [ VV!0=Vp; Packstring(VV, V); LexOut(x eq $] ? NAMEKET, NAMEBRA) ]
       return   
       ]
    case $@: LexOut(RV); return
    case $.: LexOut(DOT); return
    case $(: LexOut(RBRA); return
    case $): LexOut(RKET); return
    case $+: LexOut(PLUS); return
    case $,: LexOut(COMMA); return
    case $;: LexOut(SEMICOLON); return
    case $&: LexOut(LOGAND); return
    case $%: LexOut(LOGOR); return
    case $=:
       [
       Rch(); if Ch eq $> do [ LexOut(HEFALUMP); return ]
       LexOut(ASS); Chkind = Kind(Ch); return
       ]

    case $<:
       [
       Rch(); if Ch eq $< do [ LexOut(LEFTLUMP); return ]
       if Ch eq $= do [ LexOut(LE); return ]
       LexOut(LS); Chkind = Kind(Ch); return
       ]
    case $>:
       [
       Rch()
       if Ch eq $> do [ LexOut(RIGHTLUMP); return ]
       if Ch eq $= do [ LexOut(GE); return ]
       LexOut(GR); Chkind = Kind(Ch); return
       ]
    case $**: LexOut(MULT) ; return
    case $/:   //  Division or comment
       [
       Rch()
       if Ch ne $/ do [ LexOut(DIV); Chkind = Kind(Ch); return ]  // Division.
       Rch() repeatuntil Ch eq $*n % Ch eq #777   //  Skip a comment.
       goto L   //  Process the terminator as the char just read
       ]
    case #12:  // Line Feed -- like *n
    case #14:  // Form Feed -- like *n 
    case $*n: NLPending = true; NewLineptr = Curline; goto Next
    case $!: LexOut(VECAP) ; return
    case $?: LexOut(COND) ; return
    case $-: LexOut(MINUS); return
    case $::  LexOut(COLON); return
    case $↑:  LexOut(UPLUMP); return
    case $*":  // String quote and
    case $$: DoString(); return    //  Character constant.
    case #777:  // End of a file.
    EOF:
       [
       let done = CloseSource();
       if V!0 ne 0 % done & not SWPrepare then
        [ LexWrite(SEMICOLON); LexOut(END); ]
       if done then [ Symb = END; return ]; Ch = $*n; goto L  // fake a *n 
       ]
    default:
       [
       LEXreport(8)
       WriteS("The character is *""); WW(Ch)
       WriteS("*" and has ascii code "); WriteO(Ch)
       WriteS(" octal*n")
       goto Next
       ]
    ]
]

//----------------------------------------------------------------------------
and GetFilename(name, parenthesize; numargs na) = valof
//----------------------------------------------------------------------------
[
let Vp = 1;
[ Rch(); if $A le Ch & Ch le $Z do Ch = Ch+($a-$A); V!Vp = Ch; Vp = Vp+1 ]
 repeatuntil Ch eq $*" % Ch eq $*n
unless Ch eq $*" do [ LEXreport(4) ]  // include the "
V!0 = Vp - 1; Packstring(V, name)
///*DCS* two functions:
///* 1) notify user of gets, as they are got
///* 2) ignore all but first request for same get file prints
///* -- "foo" for first get, "(foo)" for subsequent ones
///* enters foo" into Dictionary, in order to tell.
let T = DictionaryEntry(name)
let new = Dictionary!(T-1)<<SYMPTR.Virgin ne 0
name!0 = ((name!0 rshift 8 - 1) lshift 8) +(name!0 & 377b); WW($*s)
if na ls 2 then parenthesize = not new
test parenthesize
   ifso [ WW($(); WriteS(name); WW($)); ]
   ifnot [ WriteS(name); ]
resultis new
]