! CIF PARSER   Version 3.9   Oct 1982   J. Gordon Hughes
! Program to analyse a CIF description.

!%record %format POINTFM (%integer X, Y)

! Portability comments -
!$IF VMS 
!%include "edwin_dir:TXSTACK.INC"
!$IF APM
!%include "edwin:TXSTACK.INC"
!$IF EMAS
!%include "ECSC17.IMP77SPECS"
!%include "ECSLIB.EDWIN_TXS"
!$FINISH

! The user is expected to provide the following routines -
{%external} %routine %spec D W BOX (%integer L, W, %record (POINTFM) %name C,D)
{%external} %routine %spec D W CALL (%integer CELL, %real %array %name TX (0:8))
{%external} %routine %spec D W COMMENT (%string(255) COMMENT)
{%external} %routine %spec D W DEF  (%integer ID, INFO)
{%external} %routine %spec D W LAYER (%string (63) NAME)
{%external} %routine %spec D W POLYGON (%integer NP,
                                      %record (POINTFM) %array %name P(1:*))
{%external} %routine %spec D W USER CODE (%integer CODE, %string (255) LINE)
{%external} %routine %spec D W WIRE (%integer WIDTH, NP,
                                   %record (POINTFM) %array %name P(1:*))

! D W stands for 'deal with', no doubt you were wondering.

{%external} %routine ANALYSE CIF (%integer %name LINES, STATEMENTS, WERRORS, FERRORS, %integer MAX ELEMENTS)

%routine %spec SKIP SYM

%real R SCALE
%switch CS(32:96); ! Other command symbols are blanks and will have been skiped.
%integer NEST, NS, X, Y, L, W, ID, SFAULTS, COMMAND, INDEF, IGNORE, LVALID, NUM ELEMENTS, OLD LAYER
%record (POINTFM) P, D
%record (POINTFM) %array POINTS (1:MAX ELEMENTS)
%record (TRANSFM) TT, OT, NT, UNITY
%string (255) STR
%const %string (31) LAYER NOT VALID = "No layer is currently defined"
%const %integer TRUE = 1, FALSE = 0
%const %integer MAX VALID INT = 16_FFFFFF
%own %integer INIT = FALSE, NL in user code = false, NL in comment = false
%own %integer INCLUDE DEPTH = 0

%routine FAIL (%string(255) REASON, %integer MODE)
   %integer ST
   ST = OUT STREAM
   SELECT OUTPUT (0)
   PRINT SYMBOL (MODE)
   PRINT STRING (" Line")
   WRITE (LINES,4)
   SPACE
   SPACE
   PRINT STRING (REASON)
   NEWLINE
   SELECT OUTPUT (ST)
%end

%routine WARN (%string(255) REASON)
   FAIL (REASON,'?')
   W ERRORS = W ERRORS + 1
%end

%routine FAULT (%string(255) REASON)
   FAIL (REASON,'*')
   F ERRORS = F ERRORS + 1
   %if COMMAND#'E' %start
       SKIP SYM %until NEXT SYMBOL = ';'
       SKIP SYMBOL
   %finish
   IGNORE = TRUE
   %signal 14
%end

%routine SKIP SYM
   LINES = LINES + 1 %if NEXT SYMBOL = NL
   SKIP SYMBOL
%end

%integer %fn BLANK
   %integer NS
   NS = NEXT SYMBOL
   %result = FALSE %if 'A' <= NS <= 'Z' %or '0' <= NS <= '9'
   %result = FALSE %if NS = '-' %or NS = ';' %or NS = ')' %or NS = '('
   %result = TRUE
%end

%routine SKIP BLANKS
   SKIP SYM %while BLANK = TRUE
%end

%routine SKIP SEPS
   SKIP SYM %while 'A' <= NEXT SYMBOL <= 'Z' %or BLANK = TRUE
%end

%integer %fn GET INT (%integer %name D)
   %integer NS,SIGN
   SKIP SEPS
   NS = NEXT SYMBOL
   %result = FALSE %unless '0'<= NS <='9' %or NS = '-'
   SIGN = 1
   SIGN = -1 %and SKIP SYMBOL %and NS = NEXT SYMBOL %if NS = '-'
   %result = FALSE %unless '0' <= NS <= '9'
   D = 0
   %while '0' <= NS <= '9' %cycle
      FAULT ("Integer too large") %if D > MAX VALID INT//10
      D = D * 10 - '0' + NS
      SKIP SYMBOL
      NS = NEXT SYMBOL
   %repeat
   D = D * SIGN
   %result = TRUE
%end

%integer %fn GET POINT (%record (POINTFM) %name P)
   %result = FALSE %unless GET INT (P_X) = TRUE
!BUG!write (p_x,1)
   FAULT ("Point in path only contains one digit") %unless GET INT (P_Y) = TRUE
!BUG!write (p_y,1)
   P_X = INT(P_X * R SCALE)
   P_Y = INT(P_Y * R SCALE)
!DIAG!write (p_x, 1); write (p_y,1)
   %result = TRUE
%end

%routine CHECK SEMI
   SKIP BLANKS
   FAULT ("Semi-colon missing") %if NEXT SYMBOL#';'
   SKIP SYMBOL
%end

%routine GET PATH
   ! Read a path of co-ordinates
   %record (POINTFM) %name P, NP
   %integer CH

   FAULT ("No points in Polygon or Wire path") %unless GET POINT (POINTS(1)) = TRUE
   NUM ELEMENTS = 2
   %while GET POINT (POINTS(NUM ELEMENTS)) = TRUE %cycle
      P == POINTS (NUM ELEMENTS-1)
      NP == POINTS (NUM ELEMENTS)
      NUM ELEMENTS = NUM ELEMENTS + 1 %unless NUM ELEMENTS > 1 %and P_X=NP_X %and P_Y=NP_Y
      FAULT ("Too many points in path") %if NUM ELEMENTS> MAX ELEMENTS
   %repeat
   WARN ("Redundant information after path description") %if NEXT SYMBOL # ';'
   %cycle
      READ SYMBOL (CH)
      LINES = LINES + 1 %if CH = NL
   %repeat %until CH=';'
   NUM ELEMENTS = NUM ELEMENTS -1 ; ! To balance  the books!
%end

%integer %fn GET US INT (%integer %name D)
   ! This is called when an Un-signed integer is required
   %result = FALSE %unless GET INT (D) = TRUE
   FAULT ("Un-wanted negative integer ".ITOS(D,0)) %if D<0
   %result = TRUE
%end

%routine GET LAYER NAME (%string(*) %name S)
   %const %integer MAX L NAME LENGTH = 4
   %integer NC

   SKIP BLANKS
   NC = NEXT SYMBOL
   %unless 'A' <= NC <= 'Z' %or '0' <= NC <= '9' %start
      FAULT ("Invalid symbol `".TO STRING(NC)."' in layer name")
   %finish
   S = ""
   %cycle
      SKIP SYMBOL
      S = S.TO STRING(NC)
      %exit %if LENGTH(S)>MAX L NAME LENGTH
      NC = NEXT SYMBOL
      %exit %unless 'A' <= NC <= 'Z' %or '0' <= NC <= '9'
   %repeat
   LINES = LINES + 1 %if NC = NL
   FAULT ("Layer name `".S."' too long") %and S="" %if LENGTH(S) > 4
%end

%routine DO INCLUDE FILE (%string (127) STR)
   %integer OSTM, STM, S, L, W, E
   %on 3,9 %start
       %if EVENT_SUB=2 %start
           WARN ("Include file nesting too large with """.STR."""")
       %finish %else %start
           WARN ("Include file """.STR.""" does not exist or no access")
       %finish
       %return
   %finish

   STM = IN STREAM + 1
   OPEN INPUT (STM, STR)
   SELECT INPUT (STM)
   INIT = FALSE
   INCLUDE DEPTH = INCLUDE DEPTH + 1
   L = LINES;   S = STATEMENTS;   W = WERRORS;   E = FERRORS
   ANALYSE CIF (LINES, STATEMENTS, WERRORS, FERRORS, MAX ELEMENTS)
   OSTM = OUT STREAM
   SELECT OUTPUT (0)
   SPACES (INCLUDE DEPTH * 2)
   PRINT STRING ("Include file """.IN FILE NAME.""" contained ")
   WRITE (STATEMENTS, 0)
   PRINT STRING (" statements")
   %if WERRORS#0 %or FERRORS#0 %start
       PRINT STRING (", ")
       WRITE (FERRORS, 0);   PRINT STRING (" fault")
       PRINT SYMBOL ('s') %if FERRORS#1
       PRINT STRING (" and ")
       WRITE (WERRORS, 0);   PRINT STRING (" warning")
       PRINT SYMBOL ('s') %if WERRORS#1
   %finish
   NEWLINE
   INCLUDE DEPTH = INCLUDE DEPTH - 1
   LINES = L;   STATEMENTS = S;   WERRORS = W;   FERRORS = E
   SELECT OUTPUT (OSTM)
   SELECT INPUT (STM-1)
%end

   %on 9,14 %start
       %if event_event=9 %start
           STR = "Unexpected end of CIF, "
           %if INDEF = TRUE %start
               D W DEF ('F', FERRORS-SFAULTS)
               STR = STR."Definition Finish and End assumed"
           %finish %else STR = STR."End assumed"
           WARN (STR)
           INIT = FALSE
           %return
       %finish
       ! 14 is signaled after a fault is found, analysis starts at the next command.
   %finish

   %if INIT=FALSE %start
       R SCALE = 1
       LINES = 1
       WERRORS = 0
       FERRORS = 0
       STATEMENTS = 0
       IGNORE = FALSE
       L VALID = FALSE
       OLD LAYER = FALSE
       INDEF = FALSE
       UNITY TRANSFORM (UNITY)
       INIT = TRUE
   %finish

   %cycle

      SKIP BLANKS
      READ SYMBOL (COMMAND)
      STATEMENTS = STATEMENTS + 1
      -> CS(COMMAND)

CS('B'): ! Box
         FAULT ("Invalid Box length specification") %unless GET US INT (L) = TRUE
         L = INT(L * R SCALE)
         FAULT ("Invalid Box width specification") %unless GET US INT (W) = TRUE
         W = INT(W * R SCALE)
         FAULT ("Box centre point wrong") %unless GET POINT (P) = TRUE;     ! Centre
         ! Optional direction vector may follow
         D_X = 1 %unless GET INT (D_X) = TRUE
         D_Y = 0 %unless GET INT (D_Y) = TRUE
         CHECK SEMI

         %continue %if IGNORE = TRUE
         FAULT(LAYER NOT VALID) %if LVALID = FALSE
         WARN ("Zero area box ignored") %and %continue %if L=0 %or W=0
         D W BOX (L, W, P, D)
         %continue

CS('C'): ! Call definition with possible transformation
         FAULT ("Invalid definition description ".TO STRING (NEXT SYMBOL)) %unless GET US INT (X) = TRUE
         ! Optional transformation now follows.
         NT = UNITY
         OT = UNITY
         %cycle
            SKIP BLANKS
            READ SYMBOL (NS)
            %exit %if NS=';'
            FAULT ("Invalid symbol call transformation '".TO STRING(NS)."'") %if NS#'M' %and NS#'T' %and NS#'R'
            %if NS='T' %start
                FAULT ("Invalid symbol call Transformation Vector") %unless GET POINT(P) = TRUE
                TRANSLATE TRANSFORM (P_X, P_Y, TT)
            %finish
            %if NS='M' %start
                SKIP BLANKS
                READ SYMBOL (NS)
                FAULT("Invalid symbol call mirroring operation") %unless NS='X' %or NS='Y'
                MIRROR X TRANSFORM (TT) %if NS = 'X'
                MIRROR Y TRANSFORM (TT) %if NS = 'Y'
            %finish
            %if NS='R' %start
                FAULT ("Invalid symbol call Rotation vector") %unless GET POINT(D) = TRUE
                ROT DV TRANSFORM (D_X, D_Y, TT)
            %finish
            COMPOSE TRANSFORM (OT,TT,NT)
            OT = NT
         %repeat

         %continue %if IGNORE = TRUE

         D W CALL (X, NT_A)
         %continue

CS('D'): ! Start Defn, End Defn, Delete Defn.
         IGNORE = FALSE
         ! Coming back to normal analysis to give better error mess.
         SKIP BLANKS
         READ SYMBOL (NS)
         FAULT ("Invalid symbol definition operation '".TO STRING(NS)."'") %unless NS='F' %or NS='S' %or NS='D'

         %if NS='F' %start; ! No more parameters
             CHECK SEMI
             FAULT ("Too many Definition Finishes") %if INDEF = FALSE
             D W DEF ('F', FERRORS-SFAULTS)
             INDEF = FALSE
             R SCALE = 1
             LVALID = OLD LAYER
             %continue
         %finish

         FAULT ("Invalid definition description ".TO STRING(NEXT SYMBOL)) %unless GET US INT (ID) = TRUE
         %if NS='D' %start
             CHECK SEMI
             Y = 0
             FAULT ("DD is not allowed inside a definition") %if INDEF = TRUE
             D W DEF ('D',ID)
             %continue
         %finish

         ! Optional scaling factor
         X = 1 %unless GET US INT (X) = TRUE
         WARN ("Zero value for DS scaling ignored") %and X=1 %if X=0
         Y = 1 %unless GET US INT (Y) = TRUE
         %if Y=0 %start
             WARN ("DS scaling factor included a division by 0 - IGNORED")
             R SCALE = 1
         %finish %else R SCALE = X/Y
         CHECK SEMI

         %if INDEF = TRUE %start
             WARN ("Missing Definition Finish")
             D W DEF ('F', FERRORS-SFAULTS)
         %finish
         D W DEF ('S',ID)
         OLD LAYER = LVALID
         LVALID = FALSE
         INDEF = TRUE
         SFAULTS = FERRORS; ! Used to find if any faults occurred during defn.
         %continue

CS('E'): ! End
         FAULT ("Missing Definition Finish at End") %if INDEF = TRUE
         INIT = FALSE %if INCLUDE DEPTH = 0
         %return

CS('L'): ! Layer specification
         GET LAYER NAME (STR)
         CHECK SEMI
         IGNORE = FALSE
         LVALID = TRUE
         D W LAYER (STR)
         %continue

CS('P'): ! Polygon
         GET PATH

         WARN ("Polygon with less than 3 points ignored") %and %continue %if NUM ELEMENTS<3
         %continue %if IGNORE = TRUE
         FAULT (LAYER NOT VALID) %if L VALID = FALSE

         D W POLYGON (NUM ELEMENTS, POINTS)
         %continue

CS('R'): ! Round Flash
         FAULT ("Invalid flash diameter spec") %unless GET US INT(W) = TRUE
         W = INT(W * R SCALE)
         FAULT ("Flash centre not correctly specified") %unless GET POINT (POINTS(1)) = TRUE
         CHECK SEMI

         WARN ("Zero diameter flash ignored") %and %continue %if W=0
         %continue %if IGNORE = TRUE
         FAULT (LAYER NOT VALID) %if L VALID = FALSE

         D W WIRE (W, 1, POINTS)
         %continue

CS('W'): ! Wire
         FAULT ("Invalid wire width spec") %unless GET US INT (W) = TRUE
         W = INT(W * R SCALE)
         GET PATH

         %continue %if IGNORE = TRUE
         FAULT (LAYER NOT VALID) %if L VALID = FALSE

         D W WIRE (W, NUM ELEMENTS, POINTS)
         %continue

CS(';'): ! Ignore separators
         %continue

CS('('): ! Comment
         NEST = 1
         STR = ""
         %cycle
            %cycle
               READ SYMBOL (NS)
               STR = STR.TO STRING (NS) %if LENGTH(STR)<255
            %repeat %until NS=')' %or NS='(' %or NS=NL
            NEST = NEST-1 %if NS=')'
            %exit %if NEST = 0
            NEST = NEST+1 %if NS='('
            %if NS = NL %start
                LINES = LINES + 1
                WARN ("Comment crosses line boundary") %if NL in comment # TRUE
                NL in comment = true
            %finish
         %repeat
         SKIP BLANKS
         SKIP SYMBOL %and -> CS('(') %if NEXT SYMBOL = '('; ! More comment!!
         CHECK SEMI
         LENGTH(STR) = LENGTH(STR) - 1 ; ! Loose the last ')'
         D W COMMENT (STR)
         %continue

CS('0'): ! CIF include file
         WARN ("Missing Definition Finish at Include") %and D W DEF ('F', 1) %if INDEF=TRUE
         SKIP SYMBOL %while NEXT SYMBOL <=' '
         STR = ""
         %cycle
            READ SYMBOL (NS)
            %exit %if NS = ';'
            STR = STR.TO STRING(NS) %if LENGTH(STR)<255 %and NS>' '
         %repeat
         do include file (str)
         %continue

CS('1'): ! Comment to be typed
         NEST = OUT STREAM
         SELECT OUTPUT (0)
         PRINT STRING ("Comment: ")
         %cycle
            READ SYMBOL (NS)
            %exit %if NS = ';'
            PRINT SYMBOL (NS)
         %repeat
         NEWLINE
         SELECT OUTPUT (NEST)
         %continue

CS('2'): CS('3'): CS('4'): CS('5'): CS('6'): CS('7'): CS('8'): CS('9'): ! User expansions
         STR = ""
         SKIP SYMBOL %while NEXT SYMBOL <=' '
         %cycle
            ! Only 1st. 255 chars of comment significant
            READ SYMBOL (NS)
            %exit %if NS = ';'
            %if NS = NL %start
                WARN ("User Code string crosses line boundary") %if NL in user code # TRUE
                NL in user code = TRUE
            %finish
            STR = STR.TO STRING (NS) %if LENGTH(STR)<255
         %repeat
         D W USER CODE (COMMAND-'0', STR)
         %continue

CS(*): FAULT ("Invalid command symbol '".TO STRING (COMMAND)."'")
%repeat

%end

%end %of %file
