mirror of https://github.com/acidanthera/audk.git
663 lines
16 KiB
C++
663 lines
16 KiB
C++
/*
|
|
* PCCTSAST.C
|
|
*
|
|
* SOFTWARE RIGHTS
|
|
*
|
|
* We reserve no LEGAL rights to SORCERER -- SORCERER is in the public
|
|
* domain. An individual or company may do whatever they wish with
|
|
* source code distributed with SORCERER or the code generated by
|
|
* SORCERER, including the incorporation of SORCERER, or its output, into
|
|
* commerical software.
|
|
*
|
|
* We encourage users to develop software with SORCERER. However, we do
|
|
* ask that credit is given to us for developing SORCERER. By "credit",
|
|
* we mean that if you incorporate our source code into one of your
|
|
* programs (commercial product, research project, or otherwise) that you
|
|
* acknowledge this fact somewhere in the documentation, research report,
|
|
* etc... If you like SORCERER and have developed a nice tool with the
|
|
* output, please mention that you developed it using SORCERER. In
|
|
* addition, we ask that this header remain intact in our source code.
|
|
* As long as these guidelines are kept, we expect to continue enhancing
|
|
* this system and expect to make other tools available as they are
|
|
* completed.
|
|
*
|
|
* SORCERER 1.00B14 and ANTLR 1.33
|
|
* Terence Parr
|
|
* Parr Research Corporation
|
|
* AHPCRC, University of Minnesota
|
|
* 1992-1998
|
|
*/
|
|
|
|
#define ANTLR_SUPPORT_CODE
|
|
|
|
#include "pcctscfg.h"
|
|
|
|
#include "PCCTSAST.h"
|
|
#include "pccts_stdarg.h"
|
|
|
|
PCCTS_NAMESPACE_STD
|
|
|
|
#include <ctype.h>
|
|
|
|
//#include "SList.h"
|
|
|
|
/* String Scanning/Parsing Stuff */
|
|
|
|
const char *PCCTS_AST::scan_token_tbl[] = { /* MR20 const */
|
|
"invalid", /* 0 */
|
|
"LPAREN", /* 1 */
|
|
"RPAREN", /* 2 */
|
|
"PERCENT", /* 3 */
|
|
"INT", /* 4 */
|
|
"COLON", /* 5 */
|
|
"POUND", /* 6 */
|
|
"PERIOD", /* 7 */
|
|
};
|
|
|
|
void PCCTS_AST::
|
|
addChild(PCCTS_AST *t)
|
|
{
|
|
if ( t==NULL ) return;
|
|
PCCTS_AST *s = down();
|
|
if ( s!=NULL )
|
|
{
|
|
while ( s->right()!=NULL ) s = s->right();
|
|
s->setRight(t);
|
|
}
|
|
else
|
|
this->setDown(t);
|
|
}
|
|
|
|
void PCCTS_AST::
|
|
lisp(FILE *f)
|
|
{
|
|
if ( down() != NULL ) fprintf(f," (");
|
|
lisp_action(f);
|
|
if ( down()!=NULL ) down()->lisp(f);
|
|
if ( down() != NULL ) fprintf(f," )");
|
|
if ( right()!=NULL ) right()->lisp(f);
|
|
}
|
|
|
|
/* build a tree (root child1 child2 ... NULL)
|
|
* If root is NULL, simply make the children siblings and return ptr
|
|
* to 1st sibling (child1). If root is not single node, return NULL.
|
|
*
|
|
* Siblings that are actually sibling lists themselves are handled
|
|
* correctly. For example #( NULL, #( NULL, A, B, C), D) results
|
|
* in the tree ( NULL A B C D ).
|
|
*
|
|
* Requires at least two parameters with the last one being NULL. If
|
|
* both are NULL, return NULL.
|
|
*
|
|
* The down() and right() down/right pointers are used to make the tree.
|
|
*/
|
|
PCCTS_AST *PCCTS_AST::
|
|
make(PCCTS_AST *rt, ...)
|
|
{
|
|
va_list ap;
|
|
register PCCTS_AST *child, *sibling=NULL, *tail, *w;
|
|
PCCTS_AST *root;
|
|
|
|
va_start(ap, rt);
|
|
root = rt;
|
|
|
|
if ( root != NULL )
|
|
if ( root->down() != NULL ) return NULL;
|
|
child = va_arg(ap, PCCTS_AST *);
|
|
while ( child != NULL )
|
|
{
|
|
/* find end of child */
|
|
for (w=child; w->right()!=NULL; w=w->right()) {;}
|
|
if ( sibling == NULL ) {sibling = child; tail = w;}
|
|
else {tail->setRight(child); tail = w;}
|
|
child = va_arg(ap, PCCTS_AST *);
|
|
}
|
|
if ( root==NULL ) root = sibling;
|
|
else root->setDown(sibling);
|
|
va_end(ap);
|
|
return root;
|
|
}
|
|
|
|
/* The following push and pop routines are only used by ast_find_all() */
|
|
|
|
void PCCTS_AST::
|
|
_push(PCCTS_AST **st, int *sp, PCCTS_AST *e)
|
|
{
|
|
(*sp)--;
|
|
require((*sp)>=0, "stack overflow");
|
|
st[(*sp)] = e;
|
|
}
|
|
|
|
PCCTS_AST *PCCTS_AST::
|
|
_pop(PCCTS_AST **st, int *sp)
|
|
{
|
|
PCCTS_AST *e = st[*sp];
|
|
(*sp)++;
|
|
require((*sp)<=MaxTreeStackDepth, "stack underflow");
|
|
return e;
|
|
}
|
|
|
|
/* Find all occurrences of u in t.
|
|
* 'cursor' must be initialized to 't'. It eventually
|
|
* returns NULL when no more occurrences of 'u' are found.
|
|
*/
|
|
PCCTS_AST *PCCTS_AST::
|
|
ast_find_all(PCCTS_AST *u, PCCTS_AST **cursor)
|
|
{
|
|
PCCTS_AST *sib;
|
|
static PCCTS_AST *template_stack[MaxTreeStackDepth];
|
|
static int tsp = MaxTreeStackDepth;
|
|
static int nesting = 0;
|
|
|
|
if ( *cursor == NULL ) return NULL;
|
|
if ( *cursor!=this ) sib = *cursor;
|
|
else {
|
|
/* else, first time--start at top of template 't' */
|
|
tsp = MaxTreeStackDepth;
|
|
sib = this;
|
|
/* bottom of stack is always a NULL--"cookie" indicates "done" */
|
|
_push(template_stack, &tsp, NULL);
|
|
}
|
|
|
|
keep_looking:
|
|
if ( sib==NULL ) /* hit end of sibling list */
|
|
{
|
|
sib = _pop(template_stack, &tsp);
|
|
if ( sib == NULL ) { *cursor = NULL; return NULL; }
|
|
}
|
|
|
|
if ( sib->type() != u->type() )
|
|
{
|
|
/* look for another match */
|
|
if ( sib->down()!=NULL )
|
|
{
|
|
if ( sib->right()!=NULL ) _push(template_stack, &tsp, sib->right());
|
|
sib=sib->down();
|
|
goto keep_looking;
|
|
}
|
|
/* nothing below to try, try next sibling */
|
|
sib=sib->right();
|
|
goto keep_looking;
|
|
}
|
|
|
|
/* found a matching root node, try to match what's below */
|
|
if ( match_partial(sib, u) )
|
|
{
|
|
/* record sibling cursor so we can pick up next from there */
|
|
if ( sib->down()!=NULL )
|
|
{
|
|
if ( sib->right()!=NULL ) _push(template_stack, &tsp, sib->right());
|
|
*cursor = sib->down();
|
|
}
|
|
else if ( sib->right()!=NULL ) *cursor = sib->right();
|
|
else *cursor = _pop(template_stack, &tsp);
|
|
return sib;
|
|
}
|
|
|
|
/* no match, keep searching */
|
|
if ( sib->down()!=NULL )
|
|
{
|
|
if ( sib->right()!=NULL ) _push(template_stack, &tsp, sib->right());
|
|
sib=sib->down();
|
|
}
|
|
else sib = sib->right(); /* else, try to right if zip below */
|
|
goto keep_looking;
|
|
}
|
|
|
|
/* are two trees exactly alike? */
|
|
int PCCTS_AST::
|
|
match(PCCTS_AST *u)
|
|
{
|
|
PCCTS_AST *t = this;
|
|
PCCTS_AST *sib;
|
|
|
|
if ( u==NULL ) return 0;
|
|
|
|
for (sib=t; sib!=NULL&&u!=NULL; sib=sib->right(), u=u->right())
|
|
{
|
|
if ( sib->type() != u->type() ) return 0;
|
|
if ( sib->down()!=NULL )
|
|
if ( !sib->down()->match(u->down()) ) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* Is 'u' a subtree of 't' beginning at the root? */
|
|
int PCCTS_AST::
|
|
match_partial(PCCTS_AST *t, PCCTS_AST *u)
|
|
{
|
|
PCCTS_AST *sib;
|
|
|
|
if ( u==NULL ) return 1;
|
|
if ( t==NULL ) if ( u!=NULL ) return 0; else return 1;
|
|
|
|
for (sib=t; sib!=NULL&&u!=NULL; sib=sib->right(), u=u->right())
|
|
{
|
|
if ( sib->type() != u->type() ) return 0;
|
|
if ( sib->down()!=NULL )
|
|
if ( !match_partial(sib->down(), u->down()) ) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* Walk the template tree 't' (matching against 'this'), filling in the
|
|
* 'labels' array, and setting 'n' according to how many labels were matched.
|
|
*/
|
|
int PCCTS_AST::
|
|
scanmatch(ScanAST *t, PCCTS_AST **labels[], int *n)
|
|
{
|
|
ScanAST *sib;
|
|
PCCTS_AST *u = this;
|
|
|
|
if ( u==NULL ) return 0;
|
|
|
|
for (sib=t; sib!=NULL&&u!=NULL; sib=sib->right(), u=u->right())
|
|
{
|
|
/* make sure tokens match; token of '0' means wildcard match */
|
|
if ( sib->type() != u->type() && sib->type()!=0 ) return 0;
|
|
/* we have a matched token here; set label pointers if exists */
|
|
if ( sib->label_num>0 )
|
|
{
|
|
require(labels!=NULL, "label found in template, but no array of labels");
|
|
(*n)++;
|
|
*(labels[sib->label_num-1]) = u;
|
|
}
|
|
/* match what's below if something there and current node is not wildcard */
|
|
if ( sib->down()!=NULL && sib->type()!=0 )
|
|
{
|
|
if ( sib->down()==NULL ) if ( u->down()!=NULL ) return 0; else return 1;
|
|
if ( !u->down()->scanmatch(sib->down(), labels, n) ) return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void PCCTS_AST::
|
|
insert_after(PCCTS_AST *b)
|
|
{
|
|
PCCTS_AST *end;
|
|
if ( b==NULL ) return;
|
|
/* find end of b's child list */
|
|
for (end=b; end->right()!=NULL; end=end->right()) {;}
|
|
end->setRight(this->right());
|
|
this->setRight(b);
|
|
}
|
|
|
|
void PCCTS_AST::
|
|
append(PCCTS_AST *b)
|
|
{
|
|
PCCTS_AST *end;
|
|
require(b!=NULL, "append: NULL input tree");
|
|
/* find end of child list */
|
|
for (end=this; end->right()!=NULL; end=end->right()) {;}
|
|
end->setRight(b);
|
|
}
|
|
|
|
PCCTS_AST *PCCTS_AST::
|
|
tail()
|
|
{
|
|
PCCTS_AST *end;
|
|
/* find end of child list */
|
|
for (end=this; end->right()!=NULL; end=end->right()) {;}
|
|
return end;
|
|
}
|
|
|
|
PCCTS_AST *PCCTS_AST::
|
|
bottom()
|
|
{
|
|
PCCTS_AST *end;
|
|
/* find end of child list */
|
|
for (end=this; end->down()!=NULL; end=end->down()) {;}
|
|
return end;
|
|
}
|
|
|
|
PCCTS_AST *PCCTS_AST::
|
|
cut_between(PCCTS_AST *a, PCCTS_AST *b)
|
|
{
|
|
PCCTS_AST *end, *ret;
|
|
if (a==NULL||b==NULL) return NULL;
|
|
/* find node pointing to b */
|
|
for (end=a; end->right()!=NULL&&end->right()!=b; end=end->right())
|
|
{;}
|
|
if (end->right()==NULL) return NULL; //ast_cut_between: a,b not connected
|
|
end->setRight(NULL); /* don't want it point to 'b' anymore */
|
|
ret = a->right();
|
|
a->setRight(b);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef NOT_YET
|
|
SList *PCCTS_AST::
|
|
to_slist()
|
|
{
|
|
SList *list = new SList;
|
|
PCCTS_AST *p;
|
|
|
|
for (p=this; p!=NULL; p=p->right())
|
|
{
|
|
list->add(p);
|
|
}
|
|
return list;
|
|
}
|
|
#endif
|
|
|
|
void PCCTS_AST::
|
|
tfree()
|
|
{
|
|
PCCTS_AST *t = this;
|
|
if ( t->down()!=NULL ) t->down()->tfree();
|
|
if ( t->right()!=NULL ) t->right()->tfree();
|
|
delete t;
|
|
}
|
|
|
|
int PCCTS_AST::
|
|
nsiblings()
|
|
{
|
|
PCCTS_AST *t = this;
|
|
int n=0;
|
|
|
|
while ( t!=NULL )
|
|
{
|
|
n++;
|
|
t = t->right();
|
|
}
|
|
return n;
|
|
}
|
|
|
|
PCCTS_AST *PCCTS_AST::
|
|
sibling_index(int i)
|
|
{
|
|
PCCTS_AST *t = this;
|
|
int j=1;
|
|
require(i>0, "sibling_index: i<=0");
|
|
|
|
while ( t!=NULL )
|
|
{
|
|
if ( j==i ) return t;
|
|
j++;
|
|
t = t->right();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Assume this is a root node of a tree--
|
|
* duplicate that node and what's below; ignore siblings of root node.
|
|
*/
|
|
|
|
// MR9 23-Sep-97 RJV
|
|
// MR9
|
|
// MR9 RJV: Original version only duplicated the node and down elements.
|
|
// MR9 Made copies of the pointers to sibling.
|
|
// MR9 Changed call "down()->deepCopy()" to "down()->deepCopyBushy()"
|
|
// MR9
|
|
|
|
PCCTS_AST *PCCTS_AST::
|
|
deepCopy()
|
|
{
|
|
PCCTS_AST *u = this->shallowCopy();
|
|
if ( down()!=NULL ) u->setDown(down()->deepCopyBushy());
|
|
u->setRight(NULL);
|
|
return u;
|
|
}
|
|
|
|
/* Copy all nodes including siblings of root. */
|
|
PCCTS_AST *PCCTS_AST::
|
|
deepCopyBushy()
|
|
{
|
|
PCCTS_AST *u = this->shallowCopy();
|
|
/* copy the rest of the tree */
|
|
if ( down()!=NULL ) u->setDown(down()->deepCopyBushy());
|
|
if ( right()!=NULL ) u->setRight(right()->deepCopyBushy());
|
|
return u;
|
|
}
|
|
|
|
void PCCTS_AST::
|
|
scanast_free(ScanAST *t)
|
|
{
|
|
if ( t == NULL ) return;
|
|
scanast_free( t->down() );
|
|
scanast_free( t->right() );
|
|
free( (char *) t ); // MR1
|
|
}
|
|
|
|
/*
|
|
* scan
|
|
*
|
|
* This function is like scanf(): it attempts to match a template
|
|
* against an input tree. A variable number of tree pointers
|
|
* may be set according to the '%i' labels in the template string.
|
|
* For example:
|
|
*
|
|
* t->ast_scan("#( 6 #(5 %1:4 %2:3) #(1 %3:3 %4:3) )",
|
|
* &w, &x, &y, &z);
|
|
*
|
|
* Naturally, you'd want this converted from
|
|
*
|
|
* t->ast_scan("#( RangeOp #(Minus %1:IConst %2:Var) #(Plus %3:Var %4Var) )",
|
|
* &w, &x, &y, &z);
|
|
*
|
|
* by SORCERER.
|
|
*
|
|
* This function call must be done withing a SORCERER file because SORCERER
|
|
* must convert the token references to the associated token number.
|
|
*
|
|
* This functions parses the template and creates trees which are then
|
|
* matched against the input tree. The labels are set as they are
|
|
* encountered; hence, partial matches may leave some pointers set
|
|
* and some NULL. This routines initializes all argument pointers to NULL
|
|
* at the beginning.
|
|
*
|
|
* This function returns the number of labels matched.
|
|
*/
|
|
int PCCTS_AST::
|
|
ast_scan(char *templ, ...)
|
|
{
|
|
va_list ap;
|
|
ScanAST *tmpl;
|
|
int n, i, found=0;
|
|
PCCTS_AST ***label_ptrs=NULL;
|
|
|
|
va_start(ap, templ);
|
|
|
|
/* make a ScanAST tree out of the template */
|
|
tmpl = stringparser_parse_scanast(templ, &n);
|
|
|
|
/* make an array out of the labels */
|
|
if ( n>0 )
|
|
{
|
|
label_ptrs = (PCCTS_AST ***) calloc(n, sizeof(PCCTS_AST **));
|
|
require(label_ptrs!=NULL, "scan: out of memory");
|
|
for (i=1; i<=n; i++)
|
|
{
|
|
label_ptrs[i-1] = va_arg(ap, PCCTS_AST **);
|
|
*(label_ptrs[i-1]) = NULL;
|
|
}
|
|
}
|
|
|
|
/* match the input tree against the template */
|
|
scanmatch(tmpl, label_ptrs, &found);
|
|
|
|
scanast_free(tmpl);
|
|
free( (char *) label_ptrs); // MR1
|
|
|
|
return found;
|
|
}
|
|
|
|
ScanAST *PCCTS_AST::
|
|
new_scanast(int tok)
|
|
{
|
|
ScanAST *p = (ScanAST *) calloc(1, sizeof(ScanAST));
|
|
//
|
|
// 7-Apr-97 133MR1
|
|
//
|
|
if ( p == NULL ) { // MR1
|
|
fprintf(stderr, "out of mem\n"); // MR1
|
|
exit(PCCTS_EXIT_FAILURE); // MR1
|
|
}; // MR1
|
|
p->_token = tok;
|
|
return p;
|
|
}
|
|
|
|
ScanAST *PCCTS_AST::
|
|
stringparser_parse_scanast(char *templ, int *num_labels)
|
|
{
|
|
StringLexer lex;
|
|
StringParser parser;
|
|
ScanAST *t;
|
|
|
|
stringlexer_init(&lex, templ);
|
|
stringparser_init(&parser, &lex);
|
|
t = stringparser_parse_tree(&parser);
|
|
*num_labels = parser.num_labels;
|
|
return t;
|
|
}
|
|
|
|
void PCCTS_AST::
|
|
stringparser_match(StringParser *parser, int token)
|
|
{
|
|
if ( parser->token != token ) panic("bad tree in scan()");
|
|
}
|
|
|
|
/*
|
|
* Match a tree of the form:
|
|
* (root child1 child2 ... childn)
|
|
* or,
|
|
* node
|
|
*
|
|
* where the elements are integers or labeled integers.
|
|
*/
|
|
ScanAST *PCCTS_AST::
|
|
stringparser_parse_tree(StringParser *parser)
|
|
{
|
|
ScanAST *t=NULL, *root, *child, *last;
|
|
|
|
if ( parser->token != __POUND )
|
|
{
|
|
return stringparser_parse_element(parser);
|
|
}
|
|
stringparser_match(parser,__POUND);
|
|
parser->token = stringscan_gettok(parser->lexer);
|
|
stringparser_match(parser,__LPAREN);
|
|
parser->token = stringscan_gettok(parser->lexer);
|
|
root = stringparser_parse_element(parser);
|
|
while ( parser->token != __RPAREN )
|
|
{
|
|
child = stringparser_parse_element(parser);
|
|
if ( t==NULL ) { t = child; last = t; }
|
|
else { last->_right = child; last = child; }
|
|
}
|
|
stringparser_match(parser,__RPAREN);
|
|
parser->token = stringscan_gettok(parser->lexer);
|
|
root->_down = t;
|
|
return root;
|
|
}
|
|
|
|
ScanAST *PCCTS_AST::
|
|
stringparser_parse_element(StringParser *parser)
|
|
{
|
|
static char ebuf[100];
|
|
int label = 0;
|
|
|
|
if ( parser->token == __POUND )
|
|
{
|
|
return stringparser_parse_tree(parser);
|
|
}
|
|
if ( parser->token == __PERCENT )
|
|
{
|
|
parser->token = stringscan_gettok(parser->lexer);
|
|
stringparser_match(parser,__INT);
|
|
label = atoi(parser->lexer->text);
|
|
parser->num_labels++;
|
|
if ( label==0 ) panic("%%0 is an invalid label");
|
|
parser->token = stringscan_gettok(parser->lexer);
|
|
stringparser_match(parser,__COLON);
|
|
parser->token = stringscan_gettok(parser->lexer);
|
|
/* can label tokens and wildcards */
|
|
if ( parser->token != __INT && parser->token != __PERIOD )
|
|
panic("can only label tokens");
|
|
}
|
|
if ( parser->token == __INT )
|
|
{
|
|
ScanAST *p = new_scanast(atoi(parser->lexer->text));
|
|
parser->token = stringscan_gettok(parser->lexer);
|
|
p->label_num = label;
|
|
return p;
|
|
}
|
|
if ( parser->token == __PERIOD )
|
|
{
|
|
ScanAST *p = new_scanast(0); /* token of 0 is wildcard */
|
|
parser->token = stringscan_gettok(parser->lexer);
|
|
p->label_num = label;
|
|
return p;
|
|
}
|
|
sprintf(ebuf, "mismatch token in scan(): %s", scan_token_str(parser->token));
|
|
panic(ebuf);
|
|
return NULL;
|
|
}
|
|
|
|
void PCCTS_AST::
|
|
stringparser_init(StringParser *parser, StringLexer *input)
|
|
{
|
|
parser->lexer = input;
|
|
parser->token = stringscan_gettok(parser->lexer);
|
|
parser->num_labels = 0;
|
|
}
|
|
|
|
void PCCTS_AST::
|
|
stringlexer_init(StringLexer *scanner, char *input)
|
|
{
|
|
scanner->text[0]='\0';
|
|
scanner->input = input;
|
|
scanner->p = input;
|
|
stringscan_advance(scanner);
|
|
}
|
|
|
|
void PCCTS_AST::
|
|
stringscan_advance(StringLexer *scanner)
|
|
{
|
|
if ( *(scanner->p) == '\0' ) scanner->c = __StringScanEOF;
|
|
scanner->c = *(scanner->p)++;
|
|
}
|
|
|
|
int PCCTS_AST::
|
|
stringscan_gettok(StringLexer *scanner)
|
|
{
|
|
char *index = &scanner->text[0];
|
|
static char ebuf[100];
|
|
|
|
while ( isspace(scanner->c) ) { stringscan_advance(scanner); }
|
|
if ( isdigit(scanner->c) )
|
|
{
|
|
int tok = __INT;
|
|
while ( isdigit(scanner->c) ) {
|
|
*index++ = scanner->c;
|
|
stringscan_advance(scanner);
|
|
}
|
|
*index = '\0';
|
|
return tok;
|
|
}
|
|
switch ( scanner->c )
|
|
{
|
|
case '#' : stringscan_advance(scanner); return __POUND;
|
|
case '(' : stringscan_advance(scanner); return __LPAREN;
|
|
case ')' : stringscan_advance(scanner); return __RPAREN;
|
|
case '%' : stringscan_advance(scanner); return __PERCENT;
|
|
case ':' : stringscan_advance(scanner); return __COLON;
|
|
case '.' : stringscan_advance(scanner); return __PERIOD;
|
|
case '\0' : return __StringScanEOF;
|
|
case __StringScanEOF : return __StringScanEOF;
|
|
default :
|
|
sprintf(ebuf, "invalid char in scan: '%c'", scanner->c);
|
|
panic(ebuf);
|
|
}
|
|
return __StringScanEOF; // never reached
|
|
}
|
|
|
|
const char *PCCTS_AST:: /* MR20 const */
|
|
scan_token_str(int t)
|
|
{
|
|
if ( VALID_SCAN_TOKEN(t) ) return scan_token_tbl[t];
|
|
else if ( t==__StringScanEOF ) return "<end-of-string>";
|
|
else return "<invalid-token>";
|
|
}
|