/************************************************************************* * * HTCd - Copyright (C) 1998-2006 Henrik Rydberg * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include static const charset escaped("%$[]{};"); static const charset prename("\"'`^~*,_=!\\"); static const charset spec("+-*/%<=>^_!~&|,?:;"); static const charset spec_latex("+-*/<=>^_;"); static const charset delim("\"{}()\\"); static const charset delim_latex("{}()[]\\"); static const charset specws=spec|delim|cset_ws; static const charset specws_latex=spec_latex|delim_latex|cset_ws; static const charset tokname=cset_alphareal; static const charset pretok=tokname|prename; const mstring begin_tag="begin",end_left="\\end{",end_right="}"; int is_tokname(const sref& s) { return s.back()!='.'&&pretok.test(s.front())&&s.right(1).find_not_of(tokname)<0; } int is_arglist(const sref& s) { const char* p=s.begin(); int opt=0; if(*p=='?') { ++p; opt=1; } while(*p=='x') ++p; while(*p=='r') ++p; if(!opt&&*p=='?') ++p; if(*p=='*') ++p; return !*p; } /////////////////////////////////////////////////////////// static sref getlocal(const sref& in) { int w=0,i=0; for(;i0&&in[i-1]=='.') return in.left(i-1); else return in.left(i); } static int skip_quotes(const sref& in) throw(merror_t) { int s=in.skip(cset_quotes); if(s<0) THROW("parse: unmatched '\"'"); return s; } static int skip_braces(const sref& in) throw(merror_t) { int s=in.skip(cset_braces); if(s<0) THROW("parse: unmatched '{'"); return s; } static int skip_para(const sref& in) throw(merror_t) { int s=in.skip(cset_para); if(s<0) THROW("parse: unmatched '('"); return s; } static int skip_indices(const sref& in) throw(merror_t) { int s=in.skip(cset_indices); if(s<0) THROW("parse: unmatched '['"); return s; } static int skip_dollars(const sref& in) throw(merror_t) { int s=in.skip(cset_dollars); //if(s<0) THROW("parse: unmatched '$'"); return s; } /////////////////////////////////////////////////////////// static int count_args(const sref& in) throw(merror_t) { sref e=in.right_first_not_of(cset_ws); int s,n=0; while((s=skip_braces(e))>0||(s=skip_para(e))>0) { e=e.adv(s).right_first_not_of(cset_ws); n++; } return n; } static int past_args(const sref& in) throw(merror_t) { sref p=in,e=in.right_first_not_of(cset_ws); int s; while((s=skip_braces(e))>0||(s=skip_para(e))>0) { e=(p=e.adv(s)).right_first_not_of(cset_ws); } return p-in; } static int parse_args(Env& env,runnable_t* ftok,const sref& in,int inbegend) throw(merror_t) { env.clear_args(); sref option,p=in,e=in.right_first_not_of(cset_ws); int s,narg=0,carg=count_args(e); while((s=e.skip(cset_braces))>0||(s==0&&(s=e.skip(cset_para))>0)) { if(ftok->arg[narg]=='?'&&cargarg.size()) narg++; switch(ftok->arg[narg]){ case 'x': env.xpush(e.left(s)); break; case 'r': case '*': env.rpush(e.left(s)); break; case '?': option=e.left(s); break; case 0: THROW("htc: too many args in "<name); default: THROW("htc: bad argument descriptor in "<name); } e=(p=e.adv(s)).right_first_not_of(cset_ws); if(ftok->arg[narg]!='*') narg++; } if(s<0) THROW("htc: unmatched parenthesis in function "<name); if(inbegend) { mstring tag=end_left+ftok->name+end_right; s=p.find(tag); if(s<0) THROW("htc: unmatched begin/end in function "<name); if(ftok->arg[narg]=='?'&&cargarg.size()) narg++; switch(ftok->arg[narg]){ case 'x': env.xpush(p.left(s)); break; case 'r': case '*': env.rpush(p.left(s)); break; case '?': option=p.left(s); break; case 0: THROW("htc: too many args in begin/end "<name); default: THROW("htc: bad argument descriptor in "<name); } p=p.adv(s+tag.size()); } if(ftok->arg.find('?')>=0&&env.argsarg.size()) env.rpush(option); return p-in; } int Env::check_arg(const runnable_t& ftok) const { static const charset tag("xr?"); const char* p=ftok.arg.begin(); int x=0,r=0,o=0; while(tag.test(*p)) { switch(*p++) { case 'x': x++; break; case 'r': r++; break; case '?': r++; o++; break; } } if(*p=='*') { o++; ++p; } if(*p||x!=xsize()||o>1||o==1&&rsize()expand(*this); else x->status=OP_PARSE; swrite sw(x->result); eval(sw,x,0); } void Env::rpush(const sref& in) throw(merror_t) { expr_t* x=new expr_t(in(1,-3)); rarg.push_back(x); args++; if(*in=='(') x->expand(*this); else x->status=OP_PARSE; } /////////////////////////////////////////////////////////// static int indent(sref& p) { if(cset_newl.test(p.front())) { p=p.right_first_not_of(cset_ws); return 1; } else return 0; } static int backindent(sref& p) { if(cset_newl.test(p.leftand_last_not_of(cset_spaces).back())) { p=p.leftand_last_not_of(cset_ws); return 1; } else return 0; } static int pastsemi(sref& p) { int s=p.find_not_of(cset_spaces); if(s>=0&&p[s]==';') return indent(p=p.adv(s+1)); else return 0; } static int past_func(const sref& in) throw(merror_t) { sref e=getname(in.popf()),p=in.adv(e); return p.adv(past_args(p))-in; } static int parse_func(mstream& out,Env& env,const sref& in,int& run) throw(merror_t) { run=0; sref e=getname(in),p=in.adv(e); mstring name; swrite sw(name); env.eval(sw,e,0); if(env.is_arg(name)) { //if(count_args(p)) THROW("htc: expected no argument after "<parg(out,atoi(name)-1); return p-in; } int inbegend=0; if(name==begin_tag) { sref next=p.right_first_not_of(cset_ws); int s=skip_braces(next); if(s>0) { name=next(1,s-2); p=next.adv(s); inbegend=1; } } tok_t* tok=env.toks->get(name); if(!tok) { if(!(tok=env.loc.get(name))||!tok->is_func()) tok=0; } if(!tok) return 0; if(!tok->is_runnable()) { //if(count_args(p)) THROW("htc: expected no argument after "<body; return p-in; } runnable_t* ftok=(runnable_t*)tok; if(env.is_clear(ftok)) { p=p.adv(parse_args(env,ftok,p,inbegend)); run=1; env.eval(out,ftok,1); } else p=p.adv(past_args(p)); return p-in; } void Env::parse(mstream& out,const sref& in) throw(merror_t) { static const charset sensors=escaped|cset_newl|'\\'; epilog.clear(); int s,run; sref p=in; indent(p); backindent(p); sref e=p; while(*p) { switch(*p) { case ';': case '}': case ']': out.put(*p); p=p.popf(); break; case '%': if(p-e>0) { out.put(*p); p=p.popf(); } else e=p=p.past_nl().right_first_not_of(cset_ws); break; case '\r': p=p.popf(); break; case '\n': out.put(*p); p=p.popf(); if(cset_newl.test(*p.right_first_not_of(cset_spaces))) out<0) { eval(out,p(1,s-2),0); if(pastsemi(p=p.adv(s))) e=p; } } else { out.put(*p); p=p.popf(); } break; case '[': if(mod&tok_t::PUBLIC){ if((s=skip_indices(p))>0) { expr(out,p(1,s-2)); if(pastsemi(p=p.adv(s))) e=p; } } else { out.put(*p); p=p.popf(); } break; case '$': if(mod&tok_t::PUBLIC){ if((s=skip_dollars(p))>0) { latex_env(*this).expr(out,p(1,s-2)); if(pastsemi(p=p.adv(s))) e=p; } } else { out.put(*p); p=p.popf(); } break; case '\\': p=p.popf(); if(p.empty()) out.put('\\'); else if(escaped.test(*p)) { out.put(*p); p=p.popf(); } else { if((s=parse_func(out,*this,p,run))>0) { if(pastsemi(p=p.adv(s))) e=p; } else out.put('\\'); } break; default: if((s=p.find_of(sensors))>=0) { out<status&OP_PARSE) parse(out,x->expr); else x->eval(out,*this,OP_ALL); } /////////////////////////////////////////////////////////// void Env::eval(mstream& out,const sref& in,int child) throw(merror_t) { Env nenv(*this); if(child) nenv.up=this; nenv.parse(out,in); } void Env::eval(mstream& out,expr_t* x,int child) throw(merror_t) { Env nenv(*this); if(child) nenv.up=this; nenv.parse(out,x); } void Env::eval(mstream& out,runnable_t* ftok,int child) throw(merror_t) { if(child&&!check_arg(*ftok)) THROW("htc: bad number of args; expected "<<*ftok); if(ftok->is_binary()) ((binary_t*)ftok)->func(out,*this); else if(ftok->is_func()) eval(out,&((func_t*)ftok)->x,child); else THROW("htc: unknown runnable"); } ////////////////////////////////////////////////////// int Env::split(sref& first,sref& rest) throw(merror_t) { int s; rest=rest.right_first_not_of(cset_ws); if(*rest=='\\') { first=rest.left(past_func(rest)); rest=rest.adv(first); } else if(spec.test(*rest)) { first=rest.left_first_not_of(spec); rest=rest.adv(first); } else if((s=skip_quotes(rest))>0||(s=skip_braces(rest))>0) { first=rest.left(s); rest=rest.adv(first); } else if((s=skip_para(rest))>0) { first=rest(1,s-2); rest=rest.right(s); } else { first=getlocal(rest); rest=rest.adv(first); } return first.nempty(); } void Env::term(mstream& out,expr_t* x) throw(merror_t) { if(x->expr.empty()) x->status=0; else if(*x->expr=='\"') { out<expr(1,-3); x->status=0; } else if(*x->expr=='{') { eval(out,x->expr(1,-3),0); x->status=0; } else if(x->expr.find_not_of(cset_real)<0) { out<expr; x->status=0; } else if(*x->expr=='\\') { sref p=x->expr.popf(); if(p.empty()) { out.put('\\'); x->status=0; } else if(escaped.test(*p)) { out.put(*p); x->status=0; } else { int run; if(parse_func(out,*this,p,run)>0) x->status=run?OP_LOCAL:OP_GLOBAL; else { out.put('\\')<expr; x->status=OP_NINIT; } } } else { mstring name; swrite sw(name); eval(sw,x->expr,0); out<status=OP_LOCAL; } } /////////////////////////////////////////////////////////////////// int latex_env::split(sref& first,sref& rest) throw(merror_t) { int s; rest=rest.right_first_not_of(cset_ws); if(*rest=='\\') { first=rest.left(past_func(rest)); rest=rest.adv(first); } else if(spec.test(*rest)) { first=rest.left_first_not_of(spec); rest=rest.adv(first); } else if((s=skip_braces(rest))>0||(s=skip_para(rest))>0||(s=skip_indices(rest))>0) { first=rest.left(s); rest=rest.adv(first); } else { first=rest.left_first_of(specws); rest=rest.adv(first); } return first.nempty(); } void latex_env::term(mstream& out,expr_t* x) throw(merror_t) { if(x->expr.empty()) x->status=0; else if(*x->expr=='\\') { eval(out,x->expr,0); x->status=0; } else if(*x->expr=='{') { eval(out,x->expr(1,-3),0); x->status=0; } else if(*x->expr=='('||*x->expr=='[') { out.put(x->expr.front()); expr(out,x->expr(1,-3)); out.put(x->expr.back()); x->status=0; } else { out<expr; x->status=0; } }