summaryrefslogtreecommitdiffabout
path: root/src
Side-by-side diff
Diffstat (limited to 'src') (more/less context) (ignore whitespace changes)
-rw-r--r--src/.gitignore3
-rw-r--r--src/Makefile.am15
-rw-r--r--src/cliche.199
-rw-r--r--src/cliche.rl331
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: */