summaryrefslogtreecommitdiffabout
authorMichael Krelin <hacker@klever.net>2011-08-26 21:22:00 (UTC)
committer Michael Krelin <hacker@klever.net>2011-08-26 21:22:00 (UTC)
commit1d00b262ddf0d6c3207a4b796d48899ed79bffcd (patch) (side-by-side diff)
tree03d745873ba13ffb0e2fe1831ecb41f7c0b05758
downloadcliche-0.0.zip
cliche-0.0.tar.gz
cliche-0.0.tar.bz2
initial commit into the public repository0.0
Signed-off-by: Michael Krelin <hacker@klever.net>
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--.gitignore18
-rw-r--r--AUTHORS3
-rw-r--r--COPYING19
-rw-r--r--ChangeLog0
-rw-r--r--Makefile.am15
-rw-r--r--NEWS.xml6
-rw-r--r--NEWS.xsl24
-rw-r--r--README16
-rw-r--r--autogen.bash6
-rw-r--r--configure.ac23
-rw-r--r--src/.gitignore3
-rw-r--r--src/Makefile.am15
-rw-r--r--src/cliche.199
-rw-r--r--src/cliche.rl331
-rw-r--r--test/.gitignore22
-rw-r--r--test/Makefile.am32
-rw-r--r--test/r01_closing_at_the_line_start.clichec16
-rw-r--r--test/r01_closing_at_the_line_start.expected8
-rw-r--r--test/r02_anchor_after_literal.clichec8
-rw-r--r--test/r02_anchor_after_literal.expected3
-rw-r--r--test/r03_percent_after_empty.clichec8
-rw-r--r--test/r03_percent_after_empty.expected6
-rw-r--r--test/r04_too_seamless_transitions.clichec9
-rw-r--r--test/r04_too_seamless_transitions.expected3
24 files changed, 693 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8523ddd
--- a/dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+/INSTALL
+Makefile
+Makefile.in
+/aclocal.m4
+/autom4te.cache/
+/config.h
+/config.h.in
+/config.log
+/config.status
+/configure
+/depcomp
+/install-sh
+/missing
+/stamp-h1
+.deps/
+/NEWS
+*.o
+*.cliche.cc
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..a9fb0c7
--- a/dev/null
+++ b/AUTHORS
@@ -0,0 +1,3 @@
+Klever dissected:
+ Michael 'hacker' Krelin <hacker@klever.net>
+ Leonid Ivanov <kamel@klever.net>
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..61a9dd2
--- a/dev/null
+++ b/COPYING
@@ -0,0 +1,19 @@
+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.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..e69de29
--- a/dev/null
+++ b/ChangeLog
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..77e96ad
--- a/dev/null
+++ b/Makefile.am
@@ -0,0 +1,15 @@
+SUBDIRS=src test
+
+all-local: NEWS
+
+NEWS: NEWS.xsl NEWS.xml
+ $(XSLTPROC) -o $@ $^
+EXTRA_DIST = NEWS.xml NEWS.xsl
+
+ISSUEFILES = $$(find ${top_srcdir} -type f '(' \
+ -name '*.rl' -or -name '*.h' \
+ ')' ) \
+ ${top_srcdir}/configure.ac
+issues: todo fixme xxx
+todo fixme xxx:
+ @grep --color=auto -in '$@:' ${ISSUEFILES} || true
diff --git a/NEWS.xml b/NEWS.xml
new file mode 100644
index 0000000..106fbbd
--- a/dev/null
+++ b/NEWS.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="us-ascii"?>
+<news>
+ <version version="0.0" date="Aug 27th, 2011">
+ <ni>Initial release</ni>
+ </version>
+</news>
diff --git a/NEWS.xsl b/NEWS.xsl
new file mode 100644
index 0000000..7c71307
--- a/dev/null
+++ b/NEWS.xsl
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="us-ascii"?>
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ >
+ <xsl:output
+ method="text"
+ encoding="us-ascii"
+ media-type="text/plain" />
+
+ <xsl:template match="news">
+ <xsl:apply-templates/>
+ </xsl:template>
+ <xsl:template match="version">
+ <xsl:value-of select="concat(@version,' (',@date,')&#xA;')"/>
+ <xsl:apply-templates/>
+ </xsl:template>
+ <xsl:template match="ni">
+ <xsl:text> - </xsl:text>
+ <xsl:apply-templates mode="text"/>
+ <xsl:text>&#xA;</xsl:text>
+ </xsl:template>
+ <xsl:template match="*|text()"/>
+
+</xsl:stylesheet>
diff --git a/README b/README
new file mode 100644
index 0000000..fbaa062
--- a/dev/null
+++ b/README
@@ -0,0 +1,16 @@
+Constructs:
+
+code embedded in output:
+<%code> ++i; </%code>
+% ++i;
+
+output embedded in code:
+int i=0;
+<%output>whoa, it's <% i++ %>!</%output>
+
+embed literal in the code:
+#define URL "http://www.klever.net/"
+<%literal>
+ a text that is going to be there is a string
+ with some possibilities <% URL %>
+</%literal>
diff --git a/autogen.bash b/autogen.bash
new file mode 100644
index 0000000..14798fd
--- a/dev/null
+++ b/autogen.bash
@@ -0,0 +1,6 @@
+#!/bin/bash
+ aclocal \
+&& autoheader \
+&& automake -a \
+&& autoconf \
+&& ./configure "$@"
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..4ecdb31
--- a/dev/null
+++ b/configure.ac
@@ -0,0 +1,23 @@
+AC_INIT([cliche],[0.0],[cliche-bugs@klever.net])
+AC_CONFIG_SRCDIR([src/cliche.rl])
+AC_CONFIG_HEADERS([config.h])
+AM_INIT_AUTOMAKE([check-news dist-bzip2 parallel-tests color-tests])
+
+AC_PROG_INSTALL
+AC_PROG_CXX
+AC_PATH_PROG([RAGEL],[ragel],[false])
+test "$RAGEL" = "false" && AC_MSG_ERROR([No ragel FSM compiler found])
+AC_PROG_SED
+
+AC_PATH_PROG([XSLTPROC],[xsltproc],[true])
+
+AC_CHECK_FUNC([getopt_long],[
+ AC_DEFINE([HAVE_GETOPT_LONG],[1],[define to make use of getopt_long])
+])
+
+AC_CONFIG_FILES([
+ Makefile
+ src/Makefile
+ test/Makefile
+])
+AC_OUTPUT
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: */
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644
index 0000000..54191cb
--- a/dev/null
+++ b/test/.gitignore
@@ -0,0 +1,22 @@
+/test-suite.log
+
+/r01_closing_at_the_line_start
+/r01_closing_at_the_line_start.out
+/r01_closing_at_the_line_start.diff
+/r01_closing_at_the_line_start.check
+/r01_closing_at_the_line_start.log
+/r02_anchor_after_literal
+/r02_anchor_after_literal.out
+/r02_anchor_after_literal.diff
+/r02_anchor_after_literal.check
+/r02_anchor_after_literal.log
+/r03_percent_after_empty
+/r03_percent_after_empty.out
+/r03_percent_after_empty.diff
+/r03_percent_after_empty.check
+/r03_percent_after_empty.log
+/r04_too_seamless_transitions
+/r04_too_seamless_transitions.out
+/r04_too_seamless_transitions.diff
+/r04_too_seamless_transitions.check
+/r04_too_seamless_transitions.log
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644
index 0000000..705278d
--- a/dev/null
+++ b/test/Makefile.am
@@ -0,0 +1,32 @@
+TESTS=\
+ r01_closing_at_the_line_start.clichec \
+ r02_anchor_after_literal.clichec \
+ r03_percent_after_empty.clichec \
+ r04_too_seamless_transitions.clichec
+
+EXTRA_DIST=$(TESTS)
+CLEANFILES = $(basename $(TESTS)) \
+ $(foreach s,.out .diff,$(addsuffix $s,$(basename $(TESTS))))
+.INTERMEDIATE: $(CLEANFILES)
+
+TEST_EXTENSIONS=.clichec
+CLICHEC_LOG_COMPILER=test_clichec() { \
+ $(MAKE) "$$(basename $$1 .clichec)"{,.{out,diff,check}} ;\
+}; test_clichec
+
+gitignore: .gitignore
+.gitignore: Makefile
+ for t in ${TESTS} ; do for f in "$${t%.*}"{,.{out,diff,check,log}} ; do \
+ grep -q "^/$$f" .gitignore || echo "/$$f" >>.gitignore ;\
+ done done
+
+CLICHE=${top_builddir}/src/cliche
+SUFFIXES=.clichec .cc .out .diff .check
+.clichec.cc:
+ $(CLICHE) -C -o $@ $<
+%.out: %
+ ${builddir}/$< >$@
+%.diff: ${builddir}/%.out ${srcdir}/%.expected
+ @-diff -u "$*.expected" "$*.out" >"$@"
+%.check: %.diff
+ @test -s "$<" && { cat "$*.out";cp "$*.out" "$*.unexpected" ; exit 1 ; } || true
diff --git a/test/r01_closing_at_the_line_start.clichec b/test/r01_closing_at_the_line_start.clichec
new file mode 100644
index 0000000..11ebd8f
--- a/dev/null
+++ b/test/r01_closing_at_the_line_start.clichec
@@ -0,0 +1,16 @@
+#include <iostream>
+int main() {
+const char *t0 =
+<%literal>
+
+ test-literal
+
+</%literal>;
+std::cout << t0;
+
+<%output>
+
+ test-output
+
+</%output>
+}
diff --git a/test/r01_closing_at_the_line_start.expected b/test/r01_closing_at_the_line_start.expected
new file mode 100644
index 0000000..7854811
--- a/dev/null
+++ b/test/r01_closing_at_the_line_start.expected
@@ -0,0 +1,8 @@
+
+
+ test-literal
+
+
+
+ test-output
+
diff --git a/test/r02_anchor_after_literal.clichec b/test/r02_anchor_after_literal.clichec
new file mode 100644
index 0000000..c2049c0
--- a/dev/null
+++ b/test/r02_anchor_after_literal.clichec
@@ -0,0 +1,8 @@
+#include <iostream>
+int main() {
+const char *l3 = <%literal>line 3</%literal> ":"; int nl3 = __LINE__;
+std::cout << l3 << nl3 << std::endl;
+{<%output>line 5 (<% __LINE__ %>)</%output>}
+int l6 = __LINE__;
+std::cout << std::endl << "line 6: " << l6 << std::endl;
+};
diff --git a/test/r02_anchor_after_literal.expected b/test/r02_anchor_after_literal.expected
new file mode 100644
index 0000000..b53c949
--- a/dev/null
+++ b/test/r02_anchor_after_literal.expected
@@ -0,0 +1,3 @@
+line 3:3
+line 5 (5)
+line 6: 6
diff --git a/test/r03_percent_after_empty.clichec b/test/r03_percent_after_empty.clichec
new file mode 100644
index 0000000..62c82eb
--- a/dev/null
+++ b/test/r03_percent_after_empty.clichec
@@ -0,0 +1,8 @@
+#include <iostream>
+int main() {<%output>
+Test for broken handling of '^% ' line following the empty line.
+Like this:
+
+% std::cout << 1 << std::endl;
+
+</%output>}
diff --git a/test/r03_percent_after_empty.expected b/test/r03_percent_after_empty.expected
new file mode 100644
index 0000000..f2ee7b2
--- a/dev/null
+++ b/test/r03_percent_after_empty.expected
@@ -0,0 +1,6 @@
+
+Test for broken handling of '^% ' line following the empty line.
+Like this:
+
+1
+
diff --git a/test/r04_too_seamless_transitions.clichec b/test/r04_too_seamless_transitions.clichec
new file mode 100644
index 0000000..c313013
--- a/dev/null
+++ b/test/r04_too_seamless_transitions.clichec
@@ -0,0 +1,9 @@
+#include <iostream>
+int main() {
+int a=1,b=2;
+<%output>
+The transition like this - <% a %><% b %> should not glue 'ab' in the source together.
+Really.
+</%output>
+}
+
diff --git a/test/r04_too_seamless_transitions.expected b/test/r04_too_seamless_transitions.expected
new file mode 100644
index 0000000..2ab4e9f
--- a/dev/null
+++ b/test/r04_too_seamless_transitions.expected
@@ -0,0 +1,3 @@
+
+The transition like this - 12 should not glue 'ab' in the source together.
+Really.