-rw-r--r-- | src/.gitignore | 3 | ||||
-rw-r--r-- | src/Makefile.am | 15 | ||||
-rw-r--r-- | src/cliche.1 | 99 | ||||
-rw-r--r-- | src/cliche.rl | 331 |
4 files changed, 448 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..090e0ca --- a/dev/null +++ b/src/.gitignore @@ -0,0 +1,3 @@ +/COPYING.cc +/cliche +/cliche.cc diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..d5102a7 --- a/dev/null +++ b/src/Makefile.am @@ -0,0 +1,15 @@ +bin_PROGRAMS = cliche +man_MANS = cliche.1 + +EXTRA_DIST=$(man_MANS) + +cliche_SOURCES = cliche.rl COPYING.cc +CLEANFILES=cliche.cc + +COPYING.cc: ${top_srcdir}/COPYING + (echo 'const char *COPYING=' \ + && $(SED) -e 's/"/\\"/g' -e 's/^/\"/' -e 's/$$/\\n\"/' \ + && echo ';') <$< >$@ || (rm $@;exit 1) + +.rl.cc: + $(RAGEL) -C -o $@ $< diff --git a/src/cliche.1 b/src/cliche.1 new file mode 100644 index 0000000..af61a15 --- a/dev/null +++ b/src/cliche.1 @@ -0,0 +1,99 @@ +.TH cliche 1 "August 27th, 2011" "cliche" "Klever Group (http://www.klever.net/)" +.hla en + +.SH NAME + +cliche \- A tinimalistc template preprocessor for c++ + +.SH SYNOPSYS + +\fBcliche\fR +[\fB-h\fR] [\fB--help\fR] [\fB--usage\fR] +[\fB-V\fR] [\fB--version\fR] +[\fB-L\fR] [\fB--license\fR] +[\fB-o\fR \fIfile\fR] [\fB--output=\fR\fIfile\fR] +[\fB-t\fR \fIcode\fR|\fIoutput\fR] [\fB--top=\fR\fIcode\fR|\fIoutput\fR] +[\fB-C\fR] [\fB-O\fR] + +.SH DESCRIPTION + +cliche preprocesses its input to produce c++ code streaming +out the template while executing embedded c++ code and +expressions. + +An example of cliche input file may look like this: + +.ti 1 +#include <iostream> +.ti 1 +int main() { +.ti 2 + for(int i=0;i<5;++i) { +.ti 3 + <%output> +.ti 4 + <i>Whoa, it's <b><% i %></b> already!</i><br/> +.ti 3 + </%output> +.ti 2 + } +.ti 1 +} + +.SH OPTIONS + +.TP +\fB-o\fR \fIfile\fR, \fB--output=\fR\fIfile\fR +Write output to the specified file. +.TP +\fB-t\fR \fIcode\fR|\fIoutput\fR, \fB--top=\fR\fIcode\fR|\fIoutput\fR +Expect input to have code or output (as if in <%code></%code> or <%output></%output> block) on the top-level. Code templates are suitable for feeding into compiler as is, whereas output templates may be better suited for #include. +.TP +\fB-C\fR +Same as \fB-t code\fR. +.TP +\fB-O\fR +Same as \fB-t output\fR. +.TP +\fB-h\fR, \fB--help\fR, \fB--usage\fR +Display short usage instructions and exit. +.TP +\fB-V\fR, \fB--version\fR +Report version and exit. +.TP +\fB-L\fR, \fB--license\fR +Show licensing terms. + +.SH EXIT STATUS +Unsurprisingly, \fBcliche\fR returns zero in case of success and non-zero otherwise. + +.SH AUTHOR + +Written by Michael Krelin <hacker@klever.net> + + +.SH COPYRIGHT + +Copyright (c) 2011 Klever Group (http://www.klever.net/) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +.SH BUGS + +You tell me. Send reports to <cliche-bugs@klever.net> diff --git a/src/cliche.rl b/src/cliche.rl new file mode 100644 index 0000000..1f1f07f --- a/dev/null +++ b/src/cliche.rl @@ -0,0 +1,331 @@ +#include <getopt.h> +#include <iostream> +#include <cstring> +#include <cassert> +#include <algorithm> +#include <fstream> + +#include "config.h" + +struct line_counting_monkey { + int n; + const char *p; + const char *fn; + + line_counting_monkey(const char *fn_,const char *p_) : fn(fn_), n(1), p(p_) { } + + int lineno(const char *pp) { + if(pp>p) { + n += std::count(p,pp,'\n'); + p = pp; + } + return n; + } +}; + +struct code_writing_monkey { + std::ostream& o; + enum omode_type { + om_code = 0, om_output, om_inline, om_literal, + oms + }; + omode_type omode; + int last_anchor, since_anchor; + + code_writing_monkey(std::ostream& o_) : o(o_), omode(om_code), last_anchor(-1), since_anchor(0) { } + + void modify(omode_type om,line_counting_monkey* lcm=0,const char *p=0) { + static const char *om_transitions[oms][oms] = { + // To: From: + // code output inline literal + { "", "CLICHE_OUTPUT_LITERAL(\n", "CLICHE_STREAM << (", "" }, // code + { ");\n", "", ");\n(CLICHE_STREAM) << (", 0 }, // output + { ");\n", ");\nCLICHE_OUTPUT_LITERAL(\n", "", 0 }, // inline + { " ", 0, 0, "" }, // literal + }; + assert(0 <= omode && omode < oms); + assert(0 <= om && om < oms); + const char *t = om_transitions[omode][om]; + assert(t); // TODO: complain? + o << t; + since_anchor += std::count(t,t+strlen(t),'\n'); + if(lcm && t && *t && om!=omode && p) anchor(*lcm,p); + omode = om; + } + + void prologue() { + assert(omode==om_code); + o << + "#ifndef CLICHE_STREAM\n" + "# define CLICHE_STREAM (std::cout)\n" + "# define CLICHE_STREAM_AUTODEFINED\n" + "#endif\n" + "#ifndef CLICHE_OUTPUT_LITERAL\n" + "# define CLICHE_OUTPUT_LITERAL(sl) (CLICHE_STREAM).write((sl),sizeof(sl)-sizeof(\"\"))\n" + "#endif\n"; + } + void epilogue() { + modify(om_code); + o << "\n" + "#ifdef CLICHE_STREAM_AUTODEFINED\n" + "# undef CLICHE_STREAM\n" + "# undef CLICHE_STREAM_AUTODEFINED\n" + "#endif\n"; + } + + void monkey(const char *d,size_t l=0) { + if(!(l || (l=strlen(d)))) return; + if(omode!=om_output && omode!=om_literal) { + since_anchor += std::count(d,d+l,'\n'); + o.write(d,l); + return; + } + o.put('"'); + const char *p=d; + while(l--) { + char c; + switch(*d) { + case '\r': c='r'; break; + case '\n': c='n'; break; + case '\t': c='t'; break; + case '\a': c='a'; break; + case '\b': c='b'; break; + case '\v': c='v'; break; + case '\f': c='f'; break; + case '\'': case '\"': case '\\': c=*d; break; + case 0: c='0'; break; + default: c=0; break; + }; + if(!c) { + ++d; + continue; + } + if(p!=d) o.write(p,d-p); + o.put('\\'); + if(c=='0') + o.write("000",3); + else + o.put(c); + p=++d; + } + if(p!=d) o.write(p,d-p); + o.write("\"\n",2); ++since_anchor; + } + + void monkey_as(omode_type om,const char *d,size_t l=0,line_counting_monkey *lcm=0,const char *p=0) { modify(om,lcm,p); monkey(d,l); } + + void anchor(line_counting_monkey& lcm,const char *p) { + // modify(om_code); + int l = lcm.lineno(p); + if(last_anchor>0 && last_anchor+since_anchor==l) return; + o << "\n#line " << (since_anchor=0,last_anchor=l) << " \"" << lcm.fn << "\"\n"; + } +}; + + +%%{ + machine cliche; + + linebreak = /[\r\n]/; + + action monkey { + cwm.monkey(ts,te-ts); + } + action monkey_code { + cwm.monkey_as(cwm.om_code,ts,te-ts,&lcm,p); + } + action monkey_output { + cwm.monkey_as(cwm.om_output,ts,te-ts,&lcm,p); + } + action monkey_literal { + cwm.monkey_as(cwm.om_literal,ts,te-ts,&lcm,p); + } + + slashstar_comment := + ( any* :>> '*/' ) ${ cwm.monkey(fpc,1); } @{ fret; }; + + outputblock := |* + '%' (^linebreak)* linebreak { cwm.monkey_as(cwm.om_code,ts+1,te-ts-1,&lcm,p); }; + any=> { fhold; fcall outputline; }; + + *|; + outputline := |* + (^linebreak)* linebreak -- ('</%output>' | '<%code>' | ('<%' space) ) { cwm.monkey_as(cwm.om_output,ts,te-ts,&lcm,p); fret; }; + '<%code>' { cwm.modify(cwm.om_code,&lcm,p); fcall codeblock; }; + '</%output>' { --top; fret; }; + '<%' space { cwm.modify(cwm.om_inline,&lcm,p); fcall inlineblock; }; + (^linebreak)+ -- ( '%' | '<' ) => monkey_output; + any => monkey_output; + *|; + + inlineblock := |* + space '%>' { cwm.modify(cwm.om_code,&lcm,p); fret; }; + "'" ( [^'\\] | /\\./ )* "'" => monkey; + '"' ( [^"\\] | /\\./ )* '"' => monkey; + '/*' { cwm.monkey("/*",2); fcall slashstar_comment; }; + '//' (^linebreak)* (linebreak) => monkey; + any => monkey; + *|; + + literalblock := |* + any => { fhold; fcall literalline; }; + *|; + literalline := |* + (^linebreak)* linebreak -- ('</%literal>' | ('<%' space) ) { cwm.monkey_as(cwm.om_literal,ts,te-ts,&lcm,p); fret; }; + '</%literal>' { --top; fret; }; + '<%' space { cwm.modify(cwm.om_code,&lcm,p); fcall inlineblock; }; + (^linebreak)+ -- ( '%' | '<' ) => monkey_literal; + any => monkey_literal; + *|; + + codeblock := |* + '<%output>' { fcall outputblock; }; + '<%literal>' { fcall literalblock; }; + '</%code>' { fret; }; + "'" ( [^'\\] | /\\./ )* "'" => monkey_code; + '"' ( [^"\\] | /\\./ )* '"' => monkey_code; + '/*' { cwm.monkey("/*",2); fcall slashstar_comment; }; + '//' (^linebreak)* (linebreak) => monkey_code; + any => monkey_code; + *|; + + main := any >{ + fhold; + switch(topmode) { + case code_writing_monkey::om_output: fgoto outputblock; + case code_writing_monkey::om_code: fgoto codeblock; + default: ;/* TODO: WTD? */ + }; + }; +}%% + +%% write data; + +static const char *biname = 0; +static void display_usage() { + std::cerr << PACKAGE " Version " VERSION "\n" + "Copyright (c) 2011 Klever Group\n" + "\n" + " " << biname << " [otpions] [input-file]\n" + "\n" +#ifdef HAVE_GETOPT_LONG + " -h, --help\n" + " --usage display this text\n" + " -V, --version display version number\n" + " -L, --license show license\n" + " -o <file>, --output=<file> write output to the named file\n" + " -t code|output, --top=code|output\n" +#else + " -h display this text\n" + " -V display version number\n" + " -L show license\n" + " -o <file> write output to the named file\n" + " -t code|output\n" +#endif + " set toplevel processing mode [output]\n" + " -C same as -t=code\n" + " -O same as -t=output (default)\n" + "\n"; +} + +int main(int argc,char *argv[]) { + biname = *argv; + std::string ofile; + code_writing_monkey::omode_type topmode = code_writing_monkey::om_output; + while(true) { + static const char shopts[] = "hVLo:t:CO"; +#if HAVE_GETOPT_LONG + static struct option opts[] = { + { "help", no_argument, 0, 'h' }, + { "usage", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'V' }, + { "license", no_argument, 0, 'L' }, + { "output", required_argument, 0, 'o' }, + { "top", required_argument, 0, 't' }, + { NULL, 0, 0, 0 } + }; + int c = getopt_long(argc,argv,shopts,opts,NULL); +#else + int c = getopt(argc,argv,shopts); +#endif + if(c==-1) break; + switch(c) { + case 't': + if(!strcasecmp(optarg,"code")) { + topmode = code_writing_monkey::om_code; + break; + }else if(!strcasecmp(optarg,"output")) { + topmode = code_writing_monkey::om_output; + break; + } + std::cerr << "Unkown '" << optarg << "' mode" << std::endl; + case '?': /* unknown option */ + case 'h': display_usage(); exit(0); break; + case 'V': std::cerr << VERSION << std::endl; exit(0); break; + case 'L': + extern const char *COPYING; + std::cerr << COPYING << std::endl; + exit(0); break; + case 'o': ofile = optarg; break; + case 'C': topmode = code_writing_monkey::om_code; break; + case 'O': topmode = code_writing_monkey::om_output; break; + default: + std::cerr << "Huh?" << std::endl; + exit(1); break; + } + } +#undef LS + if((optind+1)!=argc) { + display_usage(); exit(1); + /* TODO: or use stdin if no parameter specified? */ + } + + std::string ifile = argv[optind]; + if(ofile.empty()) ofile = ifile+".cc"; + std::ifstream ist(ifile.c_str(),std::ios::in); + std::ofstream ost(ofile.c_str(),std::ios::out); + if(!ost) { + std::cerr << "failed to open '" << ofile << "' for writing" << std::endl; + exit(2); + } + + int cs, act; + char *ts, *te; + int stack[128], top=0; + %% write init; + char input[512]; + int have = 0; + char *eof = 0; + code_writing_monkey cwm(ost); + cwm.prologue(); + line_counting_monkey lcm(ifile.c_str(),input); + cwm.anchor(lcm,0); + while(!eof) { + if(have==sizeof(input)) { + std::cerr << "No space to read in" << std::endl; + break; + } + char *p = input+have; + int lw = sizeof(input)-have; + int lp = ist.read(p,lw).gcount(); + char *pe = p+lp; + eof = (lp==lw)?0:pe; + %%write exec; + if(cs==cliche_error) { + std::cerr << "cliche error" << std::endl; + break; + } + if(ts) { + lcm.lineno(ts); + te = ((char*)memmove(input,ts,have=pe-ts)) + (te-ts); + ts = input; + }else{ + lcm.lineno(pe); + have = 0; + } + lcm.p = input; + } + cwm.epilogue(); + return 0; +} +/* vim:set ft=ragel ts=8 sw=8 cin si ai: */ |