/*
	Little Smalltalk, version 2
	Written by Tim Budd, Oregon State University, July 1987

	Method parser - parses the textual description of a method,
	generating bytecodes and literals.

	This parser is based around a simple minded recursive descent
	parser.
	It is used both by the module that builds the initial virtual image,
	and by a primitive when invoked from a running Smalltalk system.

	The latter case could, if the bytecode interpreter were fast enough,
	be replaced by a parser written in Smalltalk.  This would be preferable,
	but not if it slowed down the system too terribly.

	To use the parser the routine setInstanceVariables must first be
	called with a class object.  This places the appropriate instance
	variables into the memory buffers, so that references to them
	can be correctly encoded.

	As this is recursive descent, you should read it SDRAWKCAB !
		(from bottom to top)
*/
# include <stdio.h>
# include "env.h"
# include "memory.h"
# include "names.h"
# include "interp.h"
# include "lex.h"
# ifdef STRING
# include <string.h>
# endif
# ifdef STRINGS
# include <strings.h>
# endif

		/* all of the following limits could be increased (up to
			256) without any trouble.  They are kept low 
			to keep memory utilization down */

# define codeLimit 256		/* maximum number of bytecodes permitted */
# define literalLimit 32	/* maximum number of literals permitted */
# define temporaryLimit 16	/* maximum number of temporaries permitted */
# define argumentLimit 16	/* maximum number of arguments permitted */
# define instanceLimit 16	/* maximum number of instance vars permitted */
# define methodLimit 32		/* maximum number of methods permitted */

extern object binSyms[];
extern object keySyms[];
extern char *unStrs[], *binStrs[], *keyStrs[];

static boolean parseok;			/* parse still ok? */
static int codeTop;			/* top position filled in code array */
static byte codeArray[codeLimit];	/* bytecode array */
static int literalTop;			/*  ... etc. */
static object literalArray[literalLimit];
static int temporaryTop;
static char *temporaryName[temporaryLimit];
static int argumentTop;
static char *argumentName[argumentLimit];
static int instanceTop;
static char *instanceName[instanceLimit];

static int maxTemporary;		/* highest temporary see so far */
static char selector[80];		/* message selector */

static boolean inBlock;			/* true if compiling a block */
static boolean optimizedBlock;		/* true if compiling optimized block */

setInstanceVariables(aClass)
object aClass;
{	int i, limit;
	object vars;

	if (aClass == nilobj)
		instanceTop = 0;
	else {
		setInstanceVariables(basicAt(aClass, superClassInClass));
		vars = basicAt(aClass, variablesInClass);
		if (vars != nilobj) {
			limit = objectSize(vars);
			for (i = 1; i <= limit; i++)
				instanceName[++instanceTop] = charPtr(basicAt(vars, i));
			}
		}
}

compilWarn(str1, str2)
char *str1, *str2;
{
	ignore fprintf(stderr,"compiler warning: %s %s\n", str1, str2);
}

compilError(str1, str2)
char *str1, *str2;
{
	ignore fprintf(stderr,"compiler error: %s %s\n", str1, str2);
	parseok = false;
}

static object newChar(value)
int value;
{	object newobj;

	newobj = allocObject(1);
	basicAtPut(newobj, 1, newInteger(value));
	setClass(newobj, globalSymbol("Char"));
	return(newobj);
}

static object newByteArray(size)
int size;
{	object newobj;

	newobj = allocByte(size);
	setClass(newobj, globalSymbol("ByteArray"));
	return(newobj);
}

static genCode(value)
int value;
{
	if (codeTop >= codeLimit)
		compilError("too many bytecode instructions in method","");
	else
		codeArray[codeTop++] = value;
}

static genInstruction(high, low)
int high, low;
{
	if (low >= 16) {
		genInstruction(0, high);
		genCode(low);
		}
	else
		genCode(high * 16 + low);
}

static int genLiteral(aLiteral)
object aLiteral;
{
	if (literalTop >= literalLimit)
		compilError("too many literals in method","");
	else {
		literalArray[++literalTop] = aLiteral;
		incr(aLiteral);
		}
	return(literalTop - 1);
}

static char *glbsyms[] = {"nil", "true", "false", "smalltalk", "globalNames",
0 };

static boolean nameTerm(name)
char *name;
{	int i;
	boolean done = false;
	boolean isSuper = false;
	object newterm;

	/* it might be self or super */
	if (streq(name, "self") || streq(name, "super")) {
		genInstruction(PushArgument, 0);
		done = true;
		if (streq(name,"super")) isSuper = true;
		}

	/* or it might be a temporary */
	if (! done)
		for (i = 1; (! done) && ( i <= temporaryTop ) ; i++)
			if (streq(name, temporaryName[i])) {
				genInstruction(PushTemporary, i-1);
				done = true;
				}

	/* or it might be an argument */
	if (! done)
		for (i = 1; (! done) && (i <= argumentTop ) ; i++)
			if (streq(name, argumentName[i])) {
				genInstruction(PushArgument, i);
				done = true;
				}

	/* or it might be an instance variable */
	if (! done)
		for (i = 1; (! done) && (i <= instanceTop); i++) {
			if (streq(name, instanceName[i])) {
				genInstruction(PushInstance, i-1);
				done = true;
				}
			}

	/* or it might be a global constant */
	if (! done)
		for (i = 0; (! done) && glbsyms[i]; i++)
			if (streq(name, glbsyms[i])) {
				genInstruction(PushConstant, i+4);
				done = true;
				}

	/* not anything else, it must be a global */
	/* see if we know of it first */
	if (! done) { 
		newterm = globalSymbol(name);
		if (newterm != nilobj) {
			genInstruction(PushLiteral, genLiteral(newterm));
			done = true;
			}
		}

	/* otherwise, must look it up at run time */
	if (! done) {
		genInstruction(PushGlobal, genLiteral(newSymbol(name)));
		}

	return(isSuper);
}

static int parseArray()
{	int i, size, base;
	object newLit, obj;

	base = literalTop;
	ignore nextToken();
	while (parseok && (token != closing)) {
		switch(token) {
			case arraybegin:
				ignore parseArray();
				break;

			case intconst:
				ignore genLiteral(newInteger(tokenInteger));
				ignore nextToken();
				break;

			case floatconst:
				ignore genLiteral(newFloat(tokenFloat));
				ignore nextToken();
				break;

			case nameconst: case namecolon: case symconst:
				ignore genLiteral(newSymbol(tokenString));
				ignore nextToken();
				break;

			case binary:
				if (streq(tokenString, "(")) {
					ignore parseArray();
					}
				else {
					ignore genLiteral(newSymbol(tokenString));
					ignore nextToken();
					}
				break;

			case charconst:
				ignore genLiteral(newChar(
					newInteger(tokenInteger)));
				ignore nextToken();
				break;

			case strconst:
				ignore genLiteral(newStString(tokenString));
				ignore nextToken();
				break;

			default:
				compilError("illegal text in literal array",
					tokenString);
				ignore nextToken();
				break;
		}
	}

	if (parseok)
		if (! streq(tokenString, ")"))
			compilError("array not terminated by right parenthesis",
				tokenString);
		else
			ignore nextToken();
	size = literalTop - base;
	newLit = newArray(size);
	for (i = size; i >= 1; i--) {
		obj = literalArray[literalTop];
		basicAtPut(newLit, i, obj);
		decr(obj);
		literalArray[literalTop] = nilobj;
		literalTop = literalTop - 1;
		}
	return(genLiteral(newLit));
}

static boolean term()
{	boolean superTerm = false;	/* true if term is pseudo var super */

	if (token == nameconst) {
		superTerm = nameTerm(tokenString);
		ignore nextToken();
		}
	else if (token == intconst) {
		if ((tokenInteger >= 0) && (tokenInteger <= 2))
			genInstruction(PushConstant, tokenInteger);
		else
			genInstruction(PushLiteral, 
				genLiteral(newInteger(tokenInteger)));
		ignore nextToken();
		}
	else if (token == floatconst) {
		genInstruction(PushLiteral, genLiteral(newFloat(tokenFloat)));
		ignore nextToken();
		}
	else if ((token == binary) && streq(tokenString, "-")) {
		ignore nextToken();
		if (token == intconst) {
			if (tokenInteger == 1)
				genInstruction(PushConstant, 3);
			else
				genInstruction(PushLiteral, 
					genLiteral(newInteger( - tokenInteger)));
			}
		else if (token == floatconst) {
			genInstruction(PushLiteral,
				genLiteral(newFloat(-tokenFloat)));
			}
		else
			compilError("negation not followed",
				"by number");
		ignore nextToken();
		}
	else if (token == charconst) {
		genInstruction(PushLiteral,
			genLiteral(newChar(tokenInteger)));
		ignore nextToken();
		}
	else if (token == symconst) {
		genInstruction(PushLiteral,
			genLiteral(newSymbol(tokenString)));
		ignore nextToken();
		}
	else if (token == strconst) {
		genInstruction(PushLiteral,
			genLiteral(newStString(tokenString)));
		ignore nextToken();
		}
	else if (token == arraybegin) {
		genInstruction(PushLiteral, parseArray());
		}
	else if ((token == binary) && streq(tokenString, "(")) {
		ignore nextToken();
		expression();
		if (parseok)
			if ((token != closing) || ! streq(tokenString, ")"))
				compilError("Missing Right Parenthesis","");
			else
				ignore nextToken();
		}
	else if ((token == binary) && streq(tokenString, "<"))
		parsePrimitive();
	else if ((token == binary) && streq(tokenString, "["))
		block();
	else
		compilError("invalid expression start", tokenString);

	return(superTerm);
}

static parsePrimitive()
{	int primitiveNumber, argumentCount;

	if (nextToken() != intconst)
		compilError("primitive number missing","");
	primitiveNumber = tokenInteger;
	ignore nextToken();
	argumentCount = 0;
	while (parseok && ! ((token == binary) && streq(tokenString, ">"))) {
		ignore term();
		argumentCount++;
		}
	genInstruction(DoPrimitive, argumentCount);
	genCode(primitiveNumber);
	ignore nextToken();
}

static genMessage(toSuper, argumentCount, messagesym)
boolean toSuper;
int argumentCount;
object messagesym;
{
	if (toSuper) {
		genInstruction(DoSpecial, SendToSuper);
		genCode(argumentCount);
		}
	else
		genInstruction(SendMessage, argumentCount);
	genCode(genLiteral(messagesym));
}

static boolean unaryContinuation(superReceiver)
boolean superReceiver;
{	int i;
	boolean sent;
	object messagesym;

	while (parseok && (token == nameconst)) {
		/* first check to see if it could be a temp by mistake */
		for (i=1; i < temporaryTop; i++)
			if (streq(tokenString, temporaryName[i]))
				compilWarn("message same as temporary:",
					tokenString);
		for (i=1; i < argumentTop; i++)
			if (streq(tokenString, argumentName[i]))
				compilWarn("message same as argument:",
					tokenString);
		/* the next generates too many spurious messages */
		/* for (i=1; i < instanceTop; i++)
			if (streq(tokenString, instanceName[i]))
				compilWarn("message same as instance",
					tokenString); */

		sent = false;
		messagesym = newSymbol(tokenString);
		/* check for built in messages */
		if (! superReceiver)
			for (i = 0; (! sent) && unStrs[i] ; i++)
				if (streq(tokenString, unStrs[i])) {
					genInstruction(SendUnary, i);
					sent = true;
					}
		if (! sent) {
			genMessage(superReceiver, 0, messagesym);
			}
		/* once a message is sent to super, reciever is not super */
		superReceiver = false;
		ignore nextToken();
		}
	return(superReceiver);
}

static boolean binaryContinuation(superReceiver)
boolean superReceiver;
{	int i;
	boolean sent, superTerm;
	object messagesym;

	superReceiver = unaryContinuation(superReceiver);
	while (parseok && (token == binary)) {
		messagesym = newSymbol(tokenString);
		ignore nextToken();
		superTerm = term();
		ignore unaryContinuation(superTerm);
		sent = false;
		/* check for built in messages */
		if (! superReceiver) {
			for (i = 0; (! sent) && binStrs[i]; i++)
				if (messagesym == binSyms[i]) {
					genInstruction(SendBinary, i);
					sent = true;
					}

			}
		if (! sent) {
			genMessage(superReceiver, 1, messagesym);
			}
		superReceiver = false;
		}
	return(superReceiver);
}

static int optimizeBlock(instruction, dopop)
int instruction;
boolean dopop;
{	int location;
	boolean saveOB;

	genInstruction(DoSpecial, instruction);
	location = codeTop;
	genCode(0);
	if (dopop)
		genInstruction(DoSpecial, PopTop);
	ignore nextToken();
	if (streq(tokenString, "[")) {
		ignore nextToken();
		saveOB = optimizedBlock;
		optimizedBlock = true;
		body();
		optimizedBlock = saveOB;
		if (! streq(tokenString, "]"))
			compilError("missing close","after block");
		ignore nextToken();
		}
	else {
		ignore binaryContinuation(term());
		genInstruction(SendUnary, 3 /* value command */);
		}
	codeArray[location] = codeTop;
	return(location);
}

static boolean keyContinuation(superReceiver)
boolean superReceiver;
{	int i, j, argumentCount;
	boolean sent, superTerm;
	object messagesym;
	char pattern[80];

	superReceiver = binaryContinuation(superReceiver);
	if (token == namecolon) {
		if (streq(tokenString, "ifTrue:")) {
			i = optimizeBlock(BranchIfFalse, false);
			if (streq(tokenString, "ifFalse:")) {
				codeArray[i] = codeTop + 3;
				ignore optimizeBlock(Branch, true);
				}
			}
		else if (streq(tokenString, "ifFalse:")) {
			i = optimizeBlock(BranchIfTrue, false);
			if (streq(tokenString, "ifTrue:")) {
				codeArray[i] = codeTop + 3;
				ignore optimizeBlock(Branch, true);
				}
			}
		else if (streq(tokenString, "whileTrue:")) {
			j = codeTop;
			genInstruction(DoSpecial, Duplicate);
			genInstruction(SendUnary, 3 /* value command */);
			i = optimizeBlock(BranchIfFalse, false);
			genInstruction(DoSpecial, PopTop);
			genInstruction(DoSpecial, Branch);
			genCode(j);
			codeArray[i] = codeTop;
			genInstruction(DoSpecial, PopTop);
			}
		else if (streq(tokenString, "and:"))
			ignore optimizeBlock(AndBranch, false);
		else if (streq(tokenString, "or:"))
			ignore optimizeBlock(OrBranch, false);
		else {
			pattern[0] = '\0';
			argumentCount = 0;
			while (parseok && (token == namecolon)) {
				ignore strcat(pattern, tokenString);
				argumentCount++;
				ignore nextToken();
				superTerm = term();
				ignore binaryContinuation(superTerm);
				}
			sent = false;

			/* check for predefined messages */
			messagesym = newSymbol(pattern);
			if (! superReceiver) {
				for (i = 0; (! sent) && binStrs[i]; i++)
					if (messagesym == binSyms[i]) {
						sent = true;
						genInstruction(SendBinary, i);
						}

				for (i = 0; (! sent) && keyStrs[i]; i++)
					if (messagesym == keySyms[i]) {
						genInstruction(SendKeyword, i);
						sent = true;
						}
				}

			if (! sent) {
				genMessage(superReceiver, argumentCount, messagesym);
				}
			}
		superReceiver = false;
		}
	return(superReceiver);
}

static continuation(superReceiver)
boolean superReceiver;
{
	superReceiver = keyContinuation(superReceiver);

	while (parseok && (token == closing) && streq(tokenString, ";")) {
		genInstruction(DoSpecial, Duplicate);
		ignore nextToken();
		ignore keyContinuation(superReceiver);
		genInstruction(DoSpecial, PopTop);
		}
}

static expression()
{	boolean superTerm;

	superTerm = term();
	if (parseok)
		continuation(superTerm);
}

static assignment(name)
char *name;
{	int i;
	boolean done;

	done = false;

	/* it might be a temporary */
	for (i = 1; (! done) && (i <= temporaryTop); i++)
		if (streq(name, temporaryName[i])) {
			genInstruction(PopTemporary, i-1);
			done = true;
			}

	/* or it might be an instance variable */
	for (i = 1; (! done) && (i <= instanceTop); i++)
		if (streq(name, instanceName[i])) {
			genInstruction(PopInstance, i-1);
			done = true;
			}

	if (! done)
		compilError("assignment to unknown name", name);
}

static statement()
{	char assignname[80];
	boolean superReceiver = false;

	if ((token == binary) && streq(tokenString, "^")) {
		ignore nextToken();
		expression();
		if (inBlock)
			genInstruction(DoSpecial, BlockReturn);
		else
			genInstruction(DoSpecial, StackReturn);
		}
	else if (token == nameconst) {	/* possible assignment */
		ignore strcpy(assignname, tokenString);
		ignore nextToken();
		if ((token == binary) && streq(tokenString, "<-")) {
			ignore nextToken();
			expression();
			if (inBlock || optimizedBlock)
				if ((token == closing) && streq(tokenString,"]"))
					genInstruction(DoSpecial, Duplicate);
			assignment(assignname);
			if (inBlock && (token == closing) &&
				streq(tokenString, "]"))
				genInstruction(DoSpecial, StackReturn);
			}
		else {		/* not an assignment after all */
			superReceiver = nameTerm(assignname);
			continuation(superReceiver);
			if ((token == closing) && streq(tokenString, "]")) {
				if (inBlock && ! optimizedBlock)
					genInstruction(DoSpecial, StackReturn);
				}
			else
				genInstruction(DoSpecial, PopTop);
			}
		}
	else {
		expression();
		if ((token == closing) && streq(tokenString, "]")) {
			if (inBlock && ! optimizedBlock)
				genInstruction(DoSpecial, StackReturn);
			}
		else
			genInstruction(DoSpecial, PopTop);
		}
}

static body()
{
	if (inBlock || optimizedBlock)
		if ((token == closing) && streq(tokenString, "]")) {
			genInstruction(PushConstant, 4);
			if (! optimizedBlock)
				genInstruction(DoSpecial, StackReturn);
			return;
			}

	while(parseok) {
		statement();
		if (token == closing)
			if (streq(tokenString,".")) {
				ignore nextToken();
				if (token == inputend)
					break;
				}
			else
				break;
		else
			if (token == inputend)
				break;
		else {
			compilError("invalid statement ending; token is ",
				tokenString);
			}
		}
}

static block()
{	int saveTemporary, argumentCount, fixLocation;
	boolean saveInBlock, saveOB;
	object tempsym;

	saveTemporary = temporaryTop;
	argumentCount = 0;
	ignore nextToken();
	if ((token == binary) && streq(tokenString, ":")) {
		while (parseok && (token == binary) && streq(tokenString,":")) {
			if (nextToken() != nameconst)
				compilError("name must follow colon",
					"in block argument list");
		        if (++temporaryTop > maxTemporary)
				maxTemporary = temporaryTop;
			argumentCount++;
			if (temporaryTop > temporaryLimit)
				compilError("too many temporaries in method","");
			else {
				tempsym = newSymbol(tokenString);
				temporaryName[temporaryTop] = charPtr(tempsym);
				}
			ignore nextToken();
			}
		if ((token != binary) || ! streq(tokenString, "|"))
			compilError("block argument list must be terminated",
					"by |");
		ignore nextToken();
		}
	genInstruction(CreateBlock, argumentCount);
	if (argumentCount != 0){
		genCode(saveTemporary + 1);
		}
	fixLocation = codeTop;
	genCode(0);
	saveInBlock = inBlock;
	saveOB = optimizedBlock;
	inBlock = true;
	optimizedBlock = false;
	body();
	if ((token == closing) && streq(tokenString, "]"))
		ignore nextToken();
	else
		compilError("block not terminated by ]","");
	codeArray[fixLocation] = codeTop;
	inBlock = saveInBlock;
	optimizedBlock = saveOB;
	temporaryTop = saveTemporary;
}

static temporaries()
{	object tempsym;

	temporaryTop = 0;
	if ((token == binary) && streq(tokenString, "|")) {
		ignore nextToken();
		while (token == nameconst) {
			if (++temporaryTop > maxTemporary)
				maxTemporary = temporaryTop;
			if (temporaryTop > temporaryLimit)
				compilError("too many temporaries in method","");
			else {
				tempsym = newSymbol(tokenString);
				temporaryName[temporaryTop] = charPtr(tempsym);
				}
			ignore nextToken();
			}
		if ((token != binary) || ! streq(tokenString, "|"))
			compilError("temporary list not terminated by bar","");
		else
			ignore nextToken();
		}
}

static messagePattern()
{	object argsym;

	argumentTop = 0;
	ignore strcpy(selector, tokenString);
	if (token == nameconst)		/* unary message pattern */
		ignore nextToken();
	else if (token == binary) {	/* binary message pattern */
		ignore nextToken();
		if (token != nameconst) 
			compilError("binary message pattern not followed by name",selector);
		argsym = newSymbol(tokenString);
		argumentName[++argumentTop] = charPtr(argsym);
		ignore nextToken();
		}
	else if (token == namecolon) {	/* keyword message pattern */
		selector[0] = '\0';
		while (parseok && (token == namecolon)) {
			ignore strcat(selector, tokenString);
			ignore nextToken();
			if (token != nameconst)
				compilError("keyword message pattern",
					"not followed by a name");
			if (++argumentTop > argumentLimit)
				compilError("too many arguments in method","");
			argsym = newSymbol(tokenString);
			argumentName[argumentTop] = charPtr(argsym);
			ignore nextToken();
			}
		}
	else
		compilError("illegal message selector", tokenString);
}

boolean parse(method, text)
object method;
char *text;
{	int i;
	object bytecodes, theLiterals;
	byte *bp;

	lexinit(text);
	parseok = true;
	codeTop = 0;
	literalTop = temporaryTop = argumentTop =0;
	maxTemporary = 0;
	inBlock = optimizedBlock = false;

	messagePattern();
	if (parseok)
		temporaries();
	if (parseok)
		body();
	if (parseok)
		genInstruction(DoSpecial, SelfReturn);

	if (! parseok)
		basicAtPut(method, bytecodesInMethod, nilobj);
	else {
		bytecodes = newByteArray(codeTop);
		bp = bytePtr(bytecodes);
		for (i = 0; i < codeTop; i++) {
			bp[i] = codeArray[i];
			}
		basicAtPut(method, messageInMethod, newSymbol(selector));
		basicAtPut(method, bytecodesInMethod, bytecodes);
		if (literalTop > 0) {
			theLiterals = newArray(literalTop);
			for (i = 1; i <= literalTop; i++) {
				basicAtPut(theLiterals, i, literalArray[i]);
				decr(literalArray[i]);
				}
			basicAtPut(method, literalsInMethod, theLiterals);
			}
		else
			basicAtPut(method, literalsInMethod, nilobj);
		basicAtPut(method, stackSizeInMethod, newInteger(6));
		basicAtPut(method, temporarySizeInMethod,
			newInteger(1 + maxTemporary));
		basicAtPut(method, textInMethod, newStString(text));
		return(true);
		}
	return(false);
}
