/* * This file is a part of EZC -- Easy templating in C++ * and is distributed under the (new) BSD licence. * Author: Tomasz Sowa */ /* * Copyright (c) 2007-2010, Tomasz Sowa * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name Tomasz Sowa nor the names of contributors to this * project may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include "ezc.h" #ifdef EZC_USE_WINIX_LOGGER #include "core/log.h" #endif namespace Ezc { void CreateMsg(std::ostringstream & o, const char * type, const char * arg) { o << ""; } std::string CreateMsg(const char * type, const char * arg) { std::ostringstream buffer; CreateMsg(buffer, type, arg); return buffer.str(); } /* this method splits the input file name into a directory and a file name e.g if 'name' is "/this/is/dummy/file.ezc" the result will be: dir = "/this/is/dummy" file = "file.ezc" */ void SplitUnixDirectory(const char * name, std::string & dir, std::string & file) { dir.clear(); file.clear(); int i; for( i=0 ; name[i] != 0 ; ++i ); if( i == 0 ) return; for( --i ; i>=0 && name[i]!='\\' && name[i]!='/' ; --i ); if( i < 0 ) { file.assign(name); } else { if( i == 0 ) // we're leaving one '/' in the directory // (we have only the root directory) dir.assign(name, 1); else dir.assign(name, i); file.assign(name + i + 1 ); } return; } void SplitUnixDirectory(const std::string & name, std::string & dir, std::string & file) { SplitUnixDirectory(name.c_str(), dir, file); } /* * * Pattern * * */ Pattern::Pattern() { Clear(); } void Pattern::ParseFile(const std::string & file_name) { ParseFile( file_name.c_str() ); } void Pattern::ParseFile(const char * file_name) { Item::Directive file; file.is_text = true; if( directory.empty() ) SplitUnixDirectory(file_name, directory, file.name); else file.name = file_name; item_root.directives.clear(); item_root.directives.push_back( file ); include_level = 0; CreateTreeReadIncludeSkipAllowFlag(item_root); } void Pattern::ParseString(const std::string & str) { itext = str.c_str(); include_level = 0; CreateTree(item_root); } void Pattern::ParseString(const char * str) { itext = str; include_level = 0; CreateTree(item_root); } void Pattern::Directory(const char * dir, const char * dir2) { directory = dir; if( !dir2 ) directory2.clear(); else directory2 = dir2; } void Pattern::Directory(const std::string & dir) { directory = dir; directory2.clear(); } void Pattern::Directory(const std::string & dir, const std::string & dir2) { directory = dir; directory2 = dir2; } void Pattern::Clear() { item_root.Clear(); allow_include = true; delete_all_white = false; } bool Pattern::CheckFileName(const char * name) { // very simple testing -- this path 'some..name' is incorrect as well // (in the future it can be changed) for( ; *name ; ++name ) { if( *name=='.' && *(name+1)=='.' ) return false; } return true; } /* 'name' must be a relative path */ std::string Pattern::ReadFile(const std::string & name) { return ReadFile(name.c_str()); } /* 'name' must be a relative path */ std::string Pattern::ReadFile(const char * name) { // for security reasons we can't open a file which has two dots // somewhere in its name '..' if( !CheckFileName(name) ) return CreateMsg("incorrect file name:", name); std::string result; if( !ReadFileFromDir(directory, name, result) ) if( !ReadFileFromDir(directory2, name, result) ) return CreateMsg("can't open: ", name); return result; } /* 'name' must be a relative path */ bool Pattern::ReadFileFromDir(const std::string & dir, const char * name, std::string & result) { if( dir.empty() ) return false; std::string file_name(dir); file_name += '/'; file_name += name; std::ifstream file(file_name.c_str()); if( !file ) return false; std::getline(file, result, '\0'); #ifdef EZC_USE_WINIX_LOGGER log << log3 << "EZC: read pattern: " << file_name << logend; #endif return true; } int Pattern::ReadCharInText() { if( *itext==0 || *itext=='[' ) return -1; if( *itext == '\\' ) { if( *(itext+1)=='\\' || *(itext+1)=='[' || *(itext+1)==']' ) ++itext; } return *(itext++); } bool Pattern::IsWhite(int c ) { if( c==' ' || c=='\t' || c==13 ) return true; return false; } void Pattern::SkipWhiteCharacters() { while( IsWhite(*itext) ) ++itext; } void Pattern::CheckWhiteAndDelete(std::string & s) { std::string::size_type i; if( s.empty() ) return; for(i=0 ; i='a' && *itext<='z') || (*itext>='A' && *itext<='Z') || (*itext>='0' && *itext<='9') || *itext=='_' || *itext=='-' || *itext=='.' || *itext=='#' ) { directive.name += *itext; ++itext; } return directive; } // string can have a quote character (escaped with a backslash) e.g. "sample text \"with quotes\"" // use \\ to insert one backslash Pattern::Item::Directive Pattern::ReadString() { Item::Directive directive; directive.is_text = true; SkipWhiteCharacters(); // string is signed by its first character equal (") if( *itext != '\"' ) return directive; // skipping the first quote character ++itext; for( ; *itext && *itext!='\"' && *itext!='\n' ; ++itext ) { if( itext[0]=='\\' && itext[1]=='\"' ) { directive.name += '\"'; ++itext; } else if( itext[0]=='\\' && itext[1]=='\\' ) { directive.name += '\\'; ++itext; } else { directive.name += *itext; } } if( *itext != '\"' ) // the second quotation mark (") is missing directive.name.clear(); else ++itext; return directive; } Pattern::Item::Directive Pattern::ReadDirectiveOrString() { SkipWhiteCharacters(); if( *itext == '\"' ) { // we've got string return ReadString(); } else { return ReadDirective(); } } void Pattern::CreateTreeReadItemDirectiveCheckEnding(Item & item) { SkipWhiteCharacters(); if( *itext != ']' ) { item.type = Item::item_err; while( *itext!=0 && *itext!=']' ) ++itext; } if( *itext == ']' ) ++itext; } void Pattern::ReadDirectiveIfany(Item & item) { item.type = Item::item_ifany; while( true ) { Item::Directive directive = ReadDirective(); if( directive.name.empty() ) break; item.directives.push_back(directive); } if( item.directives.empty() ) item.type = Item::item_err; } void Pattern::ReadDirectiveIfno(Item & item) { ReadDirectiveIfany(item); if( item.type == Item::item_ifany ) item.type = Item::item_ifno; } void Pattern::ReadDirectiveIfone(Item & item) { ReadDirectiveIfany(item); if( item.type == Item::item_ifany ) item.type = Item::item_ifone; } void Pattern::ReadDirectiveIs(Item & item) { item.type = Item::item_is; Item::Directive directive = ReadDirectiveOrString(); if( directive.is_text || !directive.name.empty() ) { item.directives.push_back(directive); directive = ReadDirectiveOrString(); if( directive.is_text || !directive.name.empty() ) item.directives.push_back(directive); } if( item.directives.size() != 2 ) item.type = Item::item_err; } void Pattern::ReadDirectiveIsno(Item & item) { ReadDirectiveIs(item); if( item.type == Item::item_is ) item.type = Item::item_isno; } void Pattern::ReadDirectiveIfindex(Item & item) { item.type = Item::item_ifindex; Item::Directive directive = ReadDirective(); if( !directive.name.empty() ) { item.directives.push_back(directive); directive = ReadDirective(); if( !directive.name.empty() ) item.directives.push_back(directive); } if( item.directives.size() != 2 ) item.type = Item::item_err; } void Pattern::ReadDirectiveFor(Item & item) { item.type = Item::item_for; Item::Directive directive = ReadDirective(); if( !directive.name.empty() ) item.directives.push_back(directive); else item.type = Item::item_err; } void Pattern::ReadDirectiveComment(Item & item) { item.type = Item::item_comment; while( *itext && *itext!=']' && *itext!='\n' ) ++itext; } void Pattern::ReadDirectiveInclude(Item & item) { item.type = Item::item_include; Item::Directive directive = ReadString(); if( !directive.name.empty() ) item.directives.push_back(directive); else item.type = Item::item_err; } void Pattern::ReadDirectiveDef(Item & item) { item.type = Item::item_def; Item::Directive directive = ReadDirective(); if( !directive.name.empty() ) { item.directives.push_back(directive); directive = ReadDirectiveOrString(); if( directive.is_text || !directive.name.empty() ) item.directives.push_back(directive); } if( item.directives.size() != 2 ) item.type = Item::item_err; } void Pattern::CreateTreeReadItemDirective(Item & item) { ++itext; Item::Directive directive = ReadDirective(); if( directive.name == "if-any" ) ReadDirectiveIfany(item); else if( directive.name == "if-no" ) ReadDirectiveIfno(item); else if( directive.name == "if-one" ) ReadDirectiveIfone(item); else if( directive.name == "is" ) ReadDirectiveIs(item); else if( directive.name == "is-no" ) ReadDirectiveIsno(item); else if( directive.name == "if-index" ) ReadDirectiveIfindex(item); else if( directive.name == "end" ) item.type = Item::item_end; else if( directive.name == "else" ) item.type = Item::item_else; else if( directive.name == "for" ) ReadDirectiveFor(item); else if( directive.name == "include" ) ReadDirectiveInclude(item); else if( directive.name == "def" ) ReadDirectiveDef(item); else if( directive.name == "#" ) ReadDirectiveComment(item); else { // user defined item.directives.push_back(directive); item.type = Item::item_normal; } CreateTreeReadItemDirectiveCheckEnding(item); } void Pattern::CreateTreeReadItemText(Item & item) { int c; while( (c = ReadCharInText()) != -1 ) item.text += c; if( delete_all_white ) CheckWhiteAndDelete(item.text); item.type = Item::item_text; } bool Pattern::CreateTreeReadItem(Item & item) { item.Clear(); if( *itext == '[' ) { CreateTreeReadItemDirective(item); return true; } else if( *itext ) { CreateTreeReadItemText(item); return true; } // the end of the string return false; } void Pattern::CreateTreeReadInclude(Item & item) { if( !allow_include ) return; CreateTreeReadIncludeSkipAllowFlag(item); } void Pattern::CreateTreeReadIncludeSkipAllowFlag(Item & item) { if( item.directives.empty() || !item.directives[0].is_text || item.directives[0].name.empty() ) return; if( include_level >= 100 ) { #ifdef EZC_USE_WINIX_LOGGER log << log1 << "EZC: infinite loop in \"include\" directive" << logend; #endif return; } ++include_level; std::string file_text = ReadFile( item.directives[0].name ); const char * itext_old = itext; itext = file_text.c_str(); CreateTree(item); itext = itext_old; --include_level; } void Pattern::CreateTreeReadIf(Item & item) { Item * pitem = item.AddItem(); CreateTree(*pitem); if( pitem->LastItemType() == Item::item_else ) { pitem->DeleteLastItem(); pitem = item.AddItem(); CreateTree(*pitem); } pitem->DeleteLastItem(); // it deletes [end] from the tree } void Pattern::CreateTreeReadFor(Item & item) { Item * pitem = item.AddItem(); CreateTree(*pitem); pitem->DeleteLastItem(); // it deletes [end] from the tree } void Pattern::CreateTree(Item & item) { item.Clear(); item.type = Item::item_container; Item item_temp; while( CreateTreeReadItem(item_temp) ) { if( item_temp.type == Item::item_comment ) continue; Item * pitem = item.AddItem(item_temp); if( item_temp.type==Item::item_end || item_temp.type==Item::item_else ) return; if( pitem->type == Item::item_ifany || pitem->type == Item::item_ifno || pitem->type == Item::item_ifone || pitem->type == Item::item_ifindex || pitem->type == Item::item_is || pitem->type == Item::item_isno ) CreateTreeReadIf(*pitem); if( pitem->type == Item::item_for ) CreateTreeReadFor(*pitem); if( pitem->type == Item::item_include ) CreateTreeReadInclude(*pitem); } } /* * * Pattern::Item * * */ Pattern::Item * Pattern::Item::AddItem(const Pattern::Item * porg) { Item * pitem; if( porg ) pitem = new Item(*porg); else pitem = new Item(); item_table.push_back(pitem); return pitem; } Pattern::Item * Pattern::Item::AddItem(const Pattern::Item & porg) { return AddItem(&porg); } void Pattern::Item::ClearItems() { std::vector::iterator i = item_table.begin(); for( ; i != item_table.end() ; ++i ) delete *i; item_table.clear(); } void Pattern::Item::Clear() { ClearItems(); type = item_none; text.clear(); directives.clear(); } Pattern::Item::ItemType Pattern::Item::LastItemType() { if( item_table.empty() ) return item_none; return item_table.back()->type; } void Pattern::Item::DeleteLastItem() { if( item_table.empty() ) return; delete item_table.back(); item_table.erase( item_table.end() - 1 ); } Pattern::Item::Item() { type = item_none; } Pattern::Item::Item(const Pattern::Item & i) : type(i.type), text(i.text), directives(i.directives) { CopyItemTable(i); } Pattern::Item & Pattern::Item::operator=(const Pattern::Item & i) { type = i.type; text = i.text; directives = i.directives; CopyItemTable(i); return *this; } void Pattern::Item::CopyItemTable(const Pattern::Item & item) { std::vector::const_iterator i = item.item_table.begin(); for( ; i != item.item_table.end() ; ++i) AddItem( *i ); } Pattern::Item::~Item() { ClearItems(); } /* * * Info * * */ void Info::Clear() { // default settings indicate 'false' result = false; is = 0; iter = 0; } Info::Info(std::ostringstream & o) : out(o) { Clear(); } /* * * Functions * * */ Functions::Function::Function() { type = Functions::variable; user_function = 0; iter = 0; is_for = false; is_running = false; } void Functions::Insert(const std::string & key, UserFunction ufunction) { Function f; f.type = function; f.user_function = ufunction; functions_table[key] = f; } void Functions::Insert(const std::string & key, const char * var) { Function f; f.type = variable; f.variable = var; functions_table[key] = f; } void Functions::Insert(const std::string & key, const std::string & var) { Function f; f.type = variable; f.variable = var; functions_table[key] = f; } bool Functions::Find(const std::string & key, Function ** fun) { FunctionsTable::iterator i = functions_table.find( key ); if( i == functions_table.end() ) return false; *fun = &(i->second); return true; } void Functions::Clear() { functions_table.clear(); } /* * * Generator * * */ bool Generator::Find(const std::string & key, Functions::Function ** function) { if( !functions.Find(key, function) ) { CreateMsg(output_stream, "can't find", key.c_str() ); return false; } return true; } void Generator::CallUserFunction(Functions::Function * function, Info & info) { if( function->is_running ) { // recurrences are not allowed CreateMsg(output_stream, "the function is currently running" ); return; } function->is_running = true; (function->user_function)(info); function->is_running = false; } void Generator::CallVariable(Functions::Function * function, Info & info) { if( info.is || function->is_for ) return; info.out << function->variable; } void Generator::Call(Functions::Function * function, Info & info, const std::string * is) { info.Clear(); info.iter = function->iter; info.is = is; if( function->type == Functions::function ) CallUserFunction(function, info); else CallVariable(function, info); } // return: true if a function or variable was found and called bool Generator::Call(const std::string & name, Info & info, Functions::Function ** pfun, const std::string * is) { Functions::Function * function; if( Find(name, &function) ) { Call(function, info, is); if( pfun ) *pfun = function; return true; } return false; } Generator::Generator(std::ostringstream & o, Pattern & p, Functions & f) : output_stream(o), pattern(p), functions(f), info1(o), info2(o) { } void Generator::Generate() { loop = 0; MakeText( pattern.item_root ); } void Generator::MakeTextContainer(Pattern::Item & item) { std::vector::iterator i = item.item_table.begin(); for( ; i != item.item_table.end() ; ++i ) { MakeText(**i); if( loop < 0 ) break; } } void Generator::MakeTextNormal(Pattern::Item & item) { if( item.directives.size() != 1 ) return; Call(item.directives[0].name, info1); } void Generator::MakeTextIf_go(Pattern::Item & item, bool result) { if( result ) { if( item.item_table.size() > 0 ) MakeText( *item.item_table[0] ); } else { // second element can be (or not -- it's from [else]) if( item.item_table.size() > 1 ) MakeText( *item.item_table[1] ); } } void Generator::MakeTextIfany(Pattern::Item & item) { std::vector::iterator d = item.directives.begin(); unsigned how_many_true = 0; for( ; d != item.directives.end() ; ++d ) { if( !Call(d->name, info1) ) // maybe it should only be treated as a false? (when there is no such a function/variable) return; if( info1.result ) ++how_many_true; } MakeTextIf_go(item, how_many_true == item.directives.size() ); } void Generator::MakeTextIfno(Pattern::Item & item) { std::vector::iterator d = item.directives.begin(); unsigned how_many_true = 0; for( ; d != item.directives.end() ; ++d ) { if( Call(d->name, info1) && info1.result ) { // there is no sense to go through all functions ++how_many_true; break; } } MakeTextIf_go(item, how_many_true == 0 ); } void Generator::MakeTextIfone(Pattern::Item & item) { std::vector::iterator d = item.directives.begin(); int how_many_true = 0; for( ; d != item.directives.end() ; ++d ) { if( Call(d->name, info1) && info1.result ) { // there is no sense to go through all functions ++how_many_true; break; } } MakeTextIf_go(item, how_many_true > 0 ); } bool Generator::MakeTextIsFunText(const std::string & fun_name, const std::string & text, bool & res) { Functions::Function * function; if( !Find(fun_name, &function) ) return false; if( function->type == Functions::function ) { Call(function, info1, &text); res = info1.result; } else { // this is a variable res = (function->variable == text); } return true; } bool Generator::MakeTextIsFunFun(const std::string & fun_name1, const std::string & fun_name2, bool & res) { Functions::Function * function1, * function2; if( !Find(fun_name1, &function1) ) return false; if( !Find(fun_name2, &function2) ) return false; if( function1->type == Functions::function && function2->type == Functions::function ) { Call(function1, info1); Call(function2, info2); res = (info1.result == info2.result); } else if( function1->type == Functions::function ) { Call(function1, info1, &fun_name2); // only the former is a function res = info1.result; } else if( function2->type == Functions::function ) { Call(function2, info2, &fun_name1); // only the latter is a function res = info2.result; } else res = (function1->variable == function2->variable); // both are variables return false; } // if 'isno' is true that means we're generating tag [is-no ...] // default: false void Generator::MakeTextIs(Pattern::Item & item, bool isno) { if( item.directives.size() != 2 ) return; bool result = false; if( item.directives[0].is_text && item.directives[1].is_text ) { // both directives are text result = (item.directives[0].name == item.directives[1].name); } else if( item.directives[0].is_text ) { // first directive is text, second not if( !MakeTextIsFunText(item.directives[1].name, item.directives[0].name, result) ) return; } else if( item.directives[1].is_text ) { // second directive is text, first not if( !MakeTextIsFunText(item.directives[0].name, item.directives[1].name, result) ) return; } else { // both directive are not text MakeTextIsFunFun(item.directives[0].name, item.directives[1].name, result); } if( isno ) result = !result; MakeTextIf_go(item, result); } bool Generator::MakeTextIfindexnumber(Pattern::Item & item, Functions::Function * function, bool & result) { if( item.directives.size() != 2 ) return false; const char * number_text = item.directives[1].name.c_str(); char * last_char; int number = (int)strtol(number_text, &last_char, 10); if( *last_char == '\0' ) { if( function->iter == number ) result = true; // we don't have to set result as false (false is default) } else { CreateMsg(output_stream, "if-index: syntax error"); return false; } return true; } void Generator::MakeTextIfindex(Pattern::Item & item) { if( item.directives.size() != 2 ) return; // we actually don't call a function (or variable) here // but only reading an iteration index Functions::Function * function; if( !Find(item.directives[0].name, &function) ) return; bool result = false; if( item.directives[1].name == "odd" ) { if( (function->iter & 1) == 1 ) result = true; } else if( item.directives[1].name == "even" ) { if( (function->iter & 1) == 0 ) result = true; } else if( item.directives[1].name == "first" ) { if( function->iter == 0 ) result = true; } else { if( !MakeTextIfindexnumber(item, function, result) ) return; } MakeTextIf_go(item, result); } void Generator::MakeTextFor(Pattern::Item & item) { const int max_loop = 5000; if( item.directives.size() != 1 ) return; Functions::Function * function; if( !Find(item.directives[0].name, &function) ) return; if( function->is_for ) { CreateMsg(output_stream, item.directives[0].name.c_str(), "this function is already used in a [for] statement"); return; } for( function->is_for = true, function->iter=0 ; loop != -1 ; ++function->iter ) { if( function->iter >= max_loop ) { CreateMsg(output_stream, item.directives[0].name.c_str(), "function exceeded a limit for a [for] statement"); break; } Call(function, info1); if( !info1.result ) break; if( item.item_table.size() > 0 ) MakeText( *item.item_table[0] ); } function->is_for = false; function->iter = 0; } void Generator::MakeTextDefine(Pattern::Item & item) { if( item.directives.size() != 2 ) return; if( item.directives[1].is_text ) { functions.Insert(item.directives[0].name, item.directives[1].name); } else { Functions::Function * function; if( Find(item.directives[1].name, &function) ) { if( function->type == Functions::function ) functions.Insert(item.directives[0].name, function->user_function); else functions.Insert(item.directives[0].name, function->variable); } } } void Generator::MakeText(Pattern::Item & item) { const int max_loop = 50000; if( loop == -1 ) return; if( ++loop > max_loop ) { loop = -1; CreateMsg(output_stream, "Generator exceeded allowed number of elements"); return; } switch( item.type ) { case Pattern::Item::item_text: output_stream << item.text; break; case Pattern::Item::item_container: MakeTextContainer(item); break; case Pattern::Item::item_normal: MakeTextNormal(item); break; case Pattern::Item::item_ifany: MakeTextIfany(item); break; case Pattern::Item::item_ifno: MakeTextIfno(item); break; case Pattern::Item::item_ifone: MakeTextIfone(item); break; case Pattern::Item::item_ifindex: MakeTextIfindex(item); break; case Pattern::Item::item_is: MakeTextIs(item); break; case Pattern::Item::item_isno: MakeTextIs(item, true); break; case Pattern::Item::item_for: MakeTextFor(item); break; case Pattern::Item::item_def: MakeTextDefine(item); break; case Pattern::Item::item_err: CreateMsg(output_stream, "a wrong directive"); break; default: break; } } } // namespace Ezc