/* * 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-2008, 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" 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 * * */ void Pattern::ParseFile(const std::string & file_name) { ParseFile( file_name.c_str() ); } void Pattern::ParseFile(const char * file_name) { std::string file; if( directory.empty() ) SplitUnixDirectory(file_name, directory, file); else file = file_name; file.insert(file.begin(), '\"'); item_root.directives.clear(); item_root.directives.push_back( file ); CreateTreeReadInclude(item_root); } void Pattern::Directory(const char * d) { directory = d; } void Pattern::Directory(const std::string & d) { directory = d; } 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 char * name) { // for security reason 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 file_name(directory); if( !file_name.empty() ) file_name += '/'; file_name += name; std::ifstream file(file_name.c_str()); if( !file ) return CreateMsg("can't open:", name); // or we can give the whole path here (file_name) else { std::string result; std::getline(file, result, '\0'); return result; } } int Pattern::ReadCharInText() { if( *itext==0 || *itext=='[' ) return -1; if( *itext == '\\' ) { if( *(itext+1)=='\\' || *(itext+1)=='[' || *(itext+1)==']' ) ++itext; } return *(itext++); } void Pattern::SkipWhiteCharacters() { while( *itext==' ' || *itext=='\t' ) ++itext; } std::string Pattern::ReadDirective() { std::string directive; SkipWhiteCharacters(); while( (*itext>='a' && *itext<='z') || (*itext>='A' && *itext<='Z') || (*itext>='0' && *itext<='9') || *itext=='_' || *itext=='-' || *itext=='.' || *itext=='#' ) { directive += *itext; ++itext; } return directive; } std::string Pattern::ReadString(bool skip_first_quote) { std::string directive; SkipWhiteCharacters(); if( *itext != '\"' ) return directive; // string is signed by its first character equal (") if( !skip_first_quote ) directive += '\"'; for( ++itext ; *itext && *itext!='\"' && *itext!='\n' ; ++itext ) directive += *itext; if( *itext != '\"' ) // the second quotation mark (") is missing directive.clear(); else ++itext; // the second quotation mark (") we don't add into the 'directive' return directive; } std::string 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 ) { std::string directive = ReadDirective(); if( directive.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; std::string directive = ReadDirective(); if( !directive.empty() ) { item.directives.push_back(directive); directive = ReadDirectiveOrString(); if( !directive.empty() ) item.directives.push_back(directive); } if( item.directives.size() != 2 ) item.type = Item::item_err; } void Pattern::ReadDirectiveIfindex(Item & item) { item.type = Item::item_ifindex; std::string directive = ReadDirective(); if( !directive.empty() ) { item.directives.push_back(directive); directive = ReadDirective(); if( !directive.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; std::string directive = ReadDirective(); if( !directive.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; std::string directive = ReadString(); if( !directive.empty() ) item.directives.push_back(directive); else item.type = Item::item_err; } void Pattern::ReadDirectiveDef(Item & item) { item.type = Item::item_def; std::string directive = ReadDirective(); if( !directive.empty() ) { item.directives.push_back(directive); directive = ReadDirectiveOrString(); if( !directive.empty() ) item.directives.push_back(directive); } if( item.directives.size() != 2 ) item.type = Item::item_err; } void Pattern::CreateTreeReadItemDirective(Item & item) { ++itext; std::string directive = ReadDirective(); if( directive == "if-any" ) ReadDirectiveIfany(item); else if( directive == "if-no" ) ReadDirectiveIfno(item); else if( directive == "if-one" ) ReadDirectiveIfone(item); else if( directive == "is" ) ReadDirectiveIs(item); else if( directive == "if-index" ) ReadDirectiveIfindex(item); else if( directive == "end" ) item.type = Item::item_end; else if( directive == "else" ) item.type = Item::item_else; else if( directive == "for" ) ReadDirectiveFor(item); else if( directive == "include" ) ReadDirectiveInclude(item); else if( directive == "def" ) ReadDirectiveDef(item); else if( directive == "#" ) 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; 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( item.directives.empty() || item.directives[0].empty() || item.directives[0][0]!='\"' ) return; std::string file_text = ReadFile( item.directives[0].c_str()+1 ); const char * itext_copy = itext; itext = file_text.c_str(); CreateTree(item); itext = itext_copy; } 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 ) 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; out_string.clear(); iter = 0; } Info::Info(std::ostringstream & o) : out(o) { Clear(); } bool Info::IsTrue() const { if( result || !out_string.empty() ) return true; return false; } bool Info::IsFalse() const { return !IsTrue(); } /* * * 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) { // przetestowac to jeszcze !!!! (wczesniej sprawdzenie bylo w Find) !! 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) { info.out_string = function->variable; } void Generator::Call(Functions::Function * function, Info & info) { info.Clear(); info.iter = function->iter; 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) { Functions::Function * function; if( Find(name, &function) ) { Call(function, info); 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], info1); if( !info1.out_string.empty() ) output_stream << info1.out_string; } 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, info1) ) // maybe it should only be treated as a false? (when there is no such a function/variable) return; if( info1.IsTrue() ) ++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, info1) && info1.IsTrue() ) { // 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, info1) && info1.IsTrue() ) { // there is no sense to go through all functions ++how_many_true; break; } } MakeTextIf_go(item, how_many_true > 0 ); } void Generator::MakeTextIs(Pattern::Item & item) { if( item.directives.size() != 2 ) return; if( !Call(item.directives[0], info1) ) return; bool result = false; if( !item.directives[1].empty() && item.directives[1][0]=='\"' ) { // second directive is a string if( std::strcmp(info1.out_string.c_str(), item.directives[1].c_str()+1) == 0 ) result = true; } else { if( Call(item.directives[1], info2) ) { if( info1.result==info2.result && info1.out_string==info2.out_string ) result = true; } } 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].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], &function) ) return; bool result = false; if( item.directives[1] == "odd" ) { if( (function->iter & 1) == 1 ) result = true; } else if( item.directives[1] == "even" ) { if( (function->iter & 1) == 0 ) result = true; } else if( item.directives[1] == "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 = 3000; if( item.directives.size() != 1 ) return; Functions::Function * function; if( !Find(item.directives[0], &function) ) return; if( function->is_for ) { CreateMsg(output_stream, item.directives[0].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].c_str(), "function exceeded a limit for a [for] statement"); break; } Call(function, info1); if( info1.IsFalse() ) 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].empty() && item.directives[1][0]=='\"' ) { // second directive is a string functions.Insert(item.directives[0], item.directives[1].c_str() + 1); } else { Functions::Function * function; if( Find(item.directives[1], &function) ) { if( function->type == Functions::function ) functions.Insert(item.directives[0], function->user_function); else functions.Insert(item.directives[0], function->variable); } } } void Generator::MakeText(Pattern::Item & item) { const int max_loop = 10000; 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_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