/*****
 NAME
	ccmdparser.m - source code for CCmdParser class
 VERSION
	$Id$
 CHANGELOG
	$Log$
 */

#include <ctype.h>

#include <coconut/ccmdparser.h>
#include <coconut/ccmdexit.h>
#include <coconut/cerror.h>
#include <coconut/ctree.h>
#include <coconut/dconststr.h>

/* mathematical operation */
#include <coconut/cconststr.h>
#include <coconut/cstring.h>
#include <coconut/cexpconst.h>
#include <coconut/cexpfactory.h>
#include <coconut/cint.h>

/* each command objects */
#include <coconut/ccmdenv.h>
#include <coconut/ccmdexit.h>
#include <coconut/ccmdnum.h>
#include <coconut/ccmdprint.h>
#include <coconut/ccmdtext.h>

/* make enum list */
typedef enum {
	unknown_element_id	= 0,
#	define TAG_NAME(IDENT,VALUE)	IDENT ## _id,
#	include <coconut/dcmdxml.h>
	last_element_id
} element_t ;

typedef enum {
	unknown_attr_id		= 0,
#	define ATTR_NAME(IDENT,VALUE)	IDENT ## _id,
#	include <coconut/dcmdxml.h>
	last_attr_id
} attr_t ;

typedef enum {
	unknown_attr_value_id	= 0,
#	define ATTR_VALUE(IDENT,VALUE)	IDENT ## _id,
#	include <coconut/dcmdxml.h>
	last_attr_value_id
} attr_value_t ;

@implementation CCmdParser

- init
{
	id 	result ;
	/* the hash table must be initialized befor calling "setupNameTable"
	 */
	result = [super init] ;
	if(result){
		[self setupNameTable] ;
	}
	return result ;
}

- (void) dealloc
{
	[super dealloc] ;
}

- parse: (id <PXMLTree>) tree
{
	return [self parse: [tree rootNode] name: [tree inputFileName]] ;
}

- parse: (id <PXMLNode>) node name: (id <PBasicStr>) name
{
	id <PTree>	cmdtree ;	

	[super setInputName: name] ;

	/* generate command list */
	cmdtree = [[CTree alloc] init] ;
	[self parseNode: cmdtree node: node] ;
	return cmdtree ;
}

- parseNode: (id <PTree>) cmdtree node: (id <PXMLNode>) node
{
	const utf8_char *	tagname ;
	id 			result ;
	int			nameid ;

	result = nil ;
	for( ; node ; node = [node next]){
		if(![node isElementNode])
			continue ;
		tagname = [node tagName] ;
		nameid = [super searchTagName: tagname] ;
		switch((element_t) nameid){
		  case env_element_id:
		  	[super verbose: node format: "env command"] ;
			result = [self parseEnvNode: cmdtree node: node] ;
		  break ;
		  case script_element_id:
		  	[super verbose: node format: "script command"] ;
			result = [self parseNode: cmdtree node: [node child]] ;
		  break ;
		  case exit_element_id:
		  	[super verbose: node format: "exit command"] ;
			result = [self parseExitNode: cmdtree node: node] ;
		  break ;
		  case print_element_id:
		  	[super verbose: node format: "print command"] ;
			result = [self parsePrintNode: cmdtree node: node] ;
		  break ;
		  default:
			[super error: node format:
			  "unknown tag name \"%s\"", tagname] ;
			result = [CError illegal_format] ;
		  break ;
		}
		if(result)
			return result ;
	}
	return result ;
}

- parseEnvNode: (id <PTree>) cmdtree node: (id <PXMLNode>) node
{	
	id <PCmdEnv>	cmdenv ;
	id <PString>	namestr ;
	id <PString>	strstr ;

	namestr = [self getNameAttr: node warning: TRUE] ;
	strstr = [self getStringAttr: node warning: TRUE] ;
	if(namestr){
		/* check the name matches with identifier */
		if([CString isIdentifier: [namestr ptr]]){
			cmdenv = [[CCmdEnv alloc] init] ;

			/* if "string" attribute is given, it means
			   "setenv" */
			if(strstr){
				[cmdenv setNameAndValue: [namestr ptr]
				  value: [strstr ptr]] ;
			} else {
				[cmdenv setName: [namestr ptr]] ;
			}

			[cmdtree appendSibling: cmdenv] ;
			[cmdenv release] ;
		} else {
			[super warning: node format:
			  "the enviroment variable name \"%s\" is not "
			  "an identifier.", [namestr ptr]] ;
		}
	}
	[namestr release] ;
	[strstr release] ;
	return nil ;
}

- parseExitNode: (id <PTree>) cmdtree node: (id <PXMLNode>) node
{
	id <PExp>	exp ;
	id <PCmdExit>	cmd ;

	/* get "value" attribute */
	if((exp = [self getValueAttr: node warning: TRUE]) == nil){
		[super warning: node format:
		  "exit value is not given. '0' is assumed."] ;
		exp = [CExpConst newExpConstInt: 0 format: dec_format] ;
	}
	/* make CCmdExit command */
	cmd = [[CCmdExit alloc] initCmdExit: exp] ;
	/* add the command to the tree */
	[cmdtree appendSibling: cmd] ;
	return nil ;
}

- parsePrintNode: (id <PTree>) cmdtree node: (id <PXMLNode>) node
{
	id <PCmdPrint>	cmdprint ;
	id <PCmdText>	cmdtext ;
	id <PString>	string ;
	id <PConstStr>	conststr ;
	id <PXMLNode>	child ;

	cmdprint = [[CCmdPrint alloc] init] ;

	/* search "string" attribute. */
	if((string = [self getStringAttr: node warning: FALSE]) != nil){
		/* the string will be the content of print command */
		cmdtext = [[CCmdText alloc] init] ;
		[cmdtext appendStr: string] ;
		[cmdtext doPrintWithNewline: TRUE] ;
		[cmdprint appendChild: cmdtext] ;
		[string release] ;
		[cmdtext release] ;
	} else {
		/* add context to the command */
		for(child = [node child] ; child ; child = [child next]){
			if([child isElementNode]){
				[self parsePrintChildNode: cmdprint
				  node: child] ;
			} else if([child isTextNode]){
				conststr = [child content] ;
				[self parsePrintText: cmdprint str: conststr] ;
				[conststr release] ;
			}
		}
	}
	/* add the command to the tree */
	[cmdtree appendSibling: cmdprint] ;
	return nil ;
}

- parsePrintChildNode: (id <PCmdPrint>) prtcmd node: (id <PXMLNode>) node
{
	id <PCmdNum>		cmdnum ;
	id <PCmdEnv>		cmdenv ;
	id <PNumber>		number ;
	id <PString>		namestr ;
	const utf8_char *	tagname ;
	id 			result ;
	int			nameid ;
	char			c ;

	tagname = [node tagName] ; nameid = [super searchTagName: tagname] ;
	result = nil ;
	switch((element_t) nameid){
	  case newline_char_element_id:
	  case tab_char_element_id:
	  	c = nameid == newline_char_element_id ? '\n' : '\t' ;
	  	number = [CInt newInt: c format: char_format] ;
	  	cmdnum = [[CCmdNum alloc] initCmdNum: number] ;
		[prtcmd appendChild: cmdnum] ;
		[cmdnum release] ;
		[number release] ;
		result = nil ;
	  break ;
	  case env_element_id:
	  	namestr = [self getNameAttr: node warning: TRUE] ;
		if(namestr){
			/* chech the name matches with identifier */
			if([CString isIdentifier: [namestr ptr]]){
				cmdenv = [[CCmdEnv alloc] init] ;
				[cmdenv setName: [namestr ptr]] ;
				[prtcmd appendChild: cmdenv] ;
				[cmdenv release] ;
			} else {
				[super warning: node format:
				  "the enviroment variable name \"%s\" is not "
				  "an identifier.", [namestr ptr]] ;
			}
		}
		[namestr release] ;
	  break ;
	  default:
		[super error: node format:
		  "unknown tag name \"%s\" for print command", tagname];
		result = [CError illegal_format] ;
	  break ;
	}
	return result ;
}

- parsePrintText: (id <PCmdPrint>) cmdprint str: (id <PConstStr>) str
{
	id <PCmdText>		cmdtext ;
	id <PString>		line ;
	const utf8_char *	head ;
	const utf8_char *	tail ;
	const utf8_char *	ptr ;
	utf8_char		c ;
	int			len ;
	u_int			spaces ;
	u_int			tablen ;

	head = [str ptr] ; tail = head + [str length] - 1 ;

	/* remove "^[ \n]*\n" from head */
	for(ptr = head ; (c = *ptr) != '\0' ; ptr++)
		if(c == '\n')
			head = ptr + 1 ;
		else if(!isspace(c))
			break ;
	/* remove spaces from tail */
	for( ; head<=tail ; tail--){
		c = *tail ;
		if(!isspace(c))
			break ;
	}
	/* generate text item */
	if((len = tail - head + 1) > 0){
		cmdtext = [[CCmdText alloc] init] ;
		[cmdtext appendPtr: head length: len] ;

		/* get spaces from the text */
		tablen = [CString defaultTabLength] ;
		if((spaces = [cmdprint firstLeftSpaces]) == 0){
			if((line = [[cmdtext text] moveToHead]) != nil){
				spaces = [CString countSpaces: [line ptr] tab:
				  tablen] ;
			}
		}
		if(spaces > 0){
			[[cmdtext text] removeLeftSpaces: spaces tab: tablen] ;
			[cmdprint setFirstLeftSpaces: spaces] ;
		}
		[cmdprint appendChild: cmdtext] ;
		[cmdtext release] ;
	}
	return nil ;
}

- (id <PExp>) getValueAttr: (id <PXMLNode>) node warning: (boolean) dowarn
{
	id <PString>	strval ;
	id <PExp>	exp ;

	strval = [self getAttr: node name: VALUE_STR warning: dowarn] ;
	if(strval == nil){
		/* the warning message is generated in above method */
		return nil ;
	}
	if((exp = [CExpFactory stringToExp: strval]) == nil){
		[super warning: node format:
		  "the expression \"%s\" is not correct.", [strval ptr]] ;
	}
	return exp ;
}

- (id <PString>) getNameAttr: (id <PXMLNode>) node warning: (boolean) dowarn
{
	return [self getAttr: node name: NAME_STR warning: dowarn] ;
}

- (id <PString>) getStringAttr: (id <PXMLNode>) node warning: (boolean) dowarn
{
	id <PString>	string ;

	string =  [self getAttr: node name: STRING_STR warning: dowarn] ;
	[string removeSideSpaces] ;
	[string expandEscape] ;
	return string ;
}

- (id <PString>) getAttr: (id <PXMLNode>) node name: (const utf8_char *) name
    warning: (boolean) dowarn
{
	id <PConstStr>	cstr ;
	id <PString>	str = nil ;
	id <PXMLNode>	child ;

	/* first, search current attribute value */
	if((cstr = [node getAttrValueByName: name]) == nil){
		if((child = [node searchChildByTagName: name level: 1]) != nil){
			cstr = [child getChildText] ;
		}
	}
	if(cstr != nil){
		str = [CString newStringFromBasicStr: cstr] ;
		[str removeSideSpaces] ;
	} else if(dowarn){
		[super warning: node 
		  format: "the attribute \"%s\" is required.", name] ;
	}
	[cstr release] ;
	return str ;
}

- setupNameTable
{
	/* setup tag/attr name/attr value table */
#	define TAG_NAME(NAME, STR) \
	  [super addTagName: STR value: NAME ## _id] ;
#	define ATTR_NAME(NAME, STR) \
	  [super addAttrName: STR value: NAME ## _id] ;
#	define ATTR_VALUE(NAME, STR) \
	  [super addAttrValue: STR value: NAME ## _id] ;
#	include <coconut/dcmdxml.h>
	return nil ;
}

@end

