include <tbset.h>
include	"lexoper.h"

define	MAXSTACK	100
define	SZ_VALSTACK	3*SZ_LINE

# PARSER -- Parse a rule base file
#
# This procedure uses a simple operator precedence parser. Every token
# retrieved from the file is either an identifier or an operator.
# Identifiers are pushed onto an identifier stack. Operators are
# pushed onto a separate operator stack. When an operator is read
# whose priority is less than that of the operator on top of the
# operator stack, the operator on the stack is popped and passed to
# a procedure which performs the appropriate action, using the
# identifiers on the identifier stack. This continues until all
# operators of higher priority have been processed, or the stack is
# empty. Syntax checking is done by checking that the identifier
# stack contains the correct number and type of identifier and that
# identifiers and operators alternate in the input. The priority of 
# each operator is implicit in the integer which is used to represent 
# it. For more information on operator precedence parsers, see "Writing 
# Interactive Compilers and Interpreters" by P.J Brown, pp. 149-151.
#
# B.Simon	25-Apr-88	Original
# B.Simon	15-Jan-99	Skip rules with columns not in table

procedure parser (rbase, itp, dbg, target, action)

char	rbase[ARB]	# i: Rule base name
pointer	itp		# i: Input table descriptor
int	dbg		# i: Debug file descriptor
pointer	target		# o: Target table descriptor
pointer	action		# o: Action table descriptor
#--
include "parser.com"

bool	done, expect_id
int	idtop, optop, oper, tabtop, missing
int	opstack[MAXSTACK]
pointer	sp, rb, work, value, valstack, nxtval, colname, colval
pointer	idstack[MAXSTACK]

#string	find_error  "Column name or type mismatch"
string	stack_error "Stack overflow"
string	oper_error  "Operator expected"
string	ident_error "Identifier expected"

string	wrkname     "The parser changed the work table to the following:"

int	gstrcpy(), putstackt(), numstack()
pointer	initstack()

errchk	initstack, lexinit, lexer, lexclose, syntax, typecheck, mkrules

begin
	# Allocate dynamic memory for strings

	call smark (sp)
	call salloc (value, SZ_LINE, TY_CHAR)
	call salloc (valstack, SZ_VALSTACK, TY_CHAR)

	# Initialize the lexical analyzer

	call lexinit (rbase, rb)

	# Create tables used by parser

	target = initstack (itp, "_FIRST,_LAST,_USED")
	action = initstack (itp, "")
	work =  initstack (itp, "")

	# Save copy of table pointers in common block

	ptgt = target
	pact = action
	pwrk = work

	# Initialize stack pointers

	idtop = 0
	optop = 0
	nxtval = valstack

	missing = NO
	done = false
	expect_id = true

	repeat {

	    # Get next operator from rule base

	    call lexer (rb, oper, Memc[value], SZ_LINE)

	    # First case: operator is identifier, push on id stack

	    if (oper == IDOPR) {
		if (expect_id) {
		    idtop = idtop + 1
		    if (idtop > MAXSTACK)
			call syntax (rb, stack_error)

		    idstack[idtop] = nxtval
		    nxtval = gstrcpy (Memc[value], Memc[nxtval], SZ_LINE) +
			     nxtval + 1
		} else {
		    call syntax (rb, oper_error)
		}

	    # Second case: operator is not identifier

	    } else {
		if (oper != ENDOPR && expect_id)
		    call syntax (rb, ident_error)

		# Process all operators whose priorities are >=
		# the operator just read from the rule base

		repeat {
		    if (optop == 0)
			break
		    if (oper > opstack[optop])
			break

		    # Perform semantic actions associated with operators

		    switch (opstack[optop]) {
		    case ENDOPR:
			call typecheck (rb, idstack, idtop, NO_IDENT, NO_IDENT)
			done = true
		    case SEPOPR:
			call typecheck (rb, idstack, idtop, NO_IDENT, NO_IDENT)
		    case IMPOPR:
			call typecheck (rb, idstack, idtop, PHRASE, CLAUSE)
			if (missing == NO) {
			    call mkrules (work, target, action)
			} else {
			    missing = NO
			    tabtop = numstack (work)
			    call tbrdel (work, 1, tabtop)
			}
			idtop = idtop - 2
		    case OROPR:
			call typecheck (rb, idstack, idtop, CLAUSE, CLAUSE)
			idtop = idtop - 1
			idstack[idtop] = CLAUSE
		    case ANDOPR:
			call typecheck (rb, idstack, idtop, PHRASE, PHRASE)
			if (missing == NO)
			    call andstack (work)

			idtop = idtop - 1
			idstack[idtop] = PHRASE
		    case EQOPR:
			call typecheck (rb, idstack, idtop, NAME, NAME)
			colval = idstack[idtop]
			colname = idstack[idtop-1]
			nxtval = colname
			call pushstack (work)
			if (putstackt (work, Memc[colname], Memc[colval])== NO)
			    missing = YES

			idtop = idtop - 1
			idstack[idtop] = PHRASE			
		    }

		    optop = optop - 1

		    # Debug prints

		    tabtop = numstack (work)
		    call dbg_rules (work, wrkname, 1, tabtop, dbg)

		} until (done)

		# Push the operator just read on the operator stack

		optop = optop + 1
		if (optop > MAXSTACK)
		    call syntax (rb, stack_error)
		opstack[optop] = oper
	    }

	    # Operators and identifiers should alternate in the input

	    expect_id = ! expect_id

	} until (done)

	call freestack (work)
	call lexclose (rb)
	call sfree (sp)
end

# TYPECHECK -- Check the number and type of identifiers on the stack

procedure typecheck (rb, idstack, idtop, type1, type2)

pointer	rb		# i: Rule base descriptor
pointer	idstack[ARB]	# i: Identifier stack
int	idtop		# i: Top of identifier stack
pointer	type1		# i: Type expected for one below stack top
pointer	type2		# i: Type expected for stack top
#--
int	itype
pointer	id, type[2]

string	bad_type "Operator out of order"
string	too_few  "Missing identifier"
string	too_many "Unexpected end of rule"

begin
	type[1] = type1
	type[2] = type2

	do itype = 1, 2 {
	    switch (type[itype]) {
	    case CLAUSE:

		if (idtop < itype)
		    call syntax (rb, too_few)
		id = idstack[idtop+itype-2]

		# a phrase is also a clause

		if (!(id == PHRASE || id == CLAUSE))
		    call syntax (rb, bad_type)

	    case PHRASE:

		if (idtop < itype)
		    call syntax (rb, too_few)
		id = idstack[idtop+itype-2]
		if (id != PHRASE)
		    call syntax (rb, bad_type)

	    case NO_IDENT:

		if (idtop >= itype)
		    call syntax (rb, too_many)

	    case NAME:

		if (idtop < itype)
		    call syntax (rb, too_few)
		id = idstack[idtop+itype-2]
		if (id <= 0)
		    call syntax (rb, bad_type)

	    }
	}

end

# SYNTAX -- Print a syntax error message

procedure syntax (rb, errmsg)

pointer	rb		# i: Rule base descriptor
char	errmsg[ARB]	# i: Error message
#--
include "parser.com"

begin
	# Remove temporary tables

	call freestack (ptgt)
	call freestack (pact)
	call freestack (pwrk)

	# Print the line where the error was detected

	call eprintf ("Syntax error on line %d\n%s%*t^\n")
	    call pargi (RB_NLINE(rb))
	    call pargstr (RB_LINE(rb))
	    call pargi (RB_INDEX(rb))

	# Close the rules file and send the error message

	call lexclose (rb)
	call error (ERR, errmsg)

end
