Created
September 11, 2019 17:54
-
-
Save Johann150/0c3c1faf96bb92e185c0aa930bb34c22 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
https://esolangs.org/wiki/Procedural_Footnote_Language | |
conforming to version 1.0.2 of the specification | |
other avaiable specification versions: | |
version 1.1 @ https://github.com/vasilescur/pfl/wiki/Procedural-Footnote-Language-Specification | |
The C++ compiler has to be set to at least version C++ 11. | |
*/ | |
#include<cmath> | |
#include<string> | |
#include<iostream> | |
#include<iomanip> | |
#include<fstream> | |
#include<vector> | |
#include<chrono> | |
#include<ctime> | |
#include<sstream> | |
#include<stdexcept> | |
std::string ud_name,ud_first,ud_last,ud_initial,ud_company,ud_street,ud_city,ud_region,ud_postal,ud_country,ud_lang; | |
enum class pfl_error{ | |
OK,FSE,IFA,MDA,MFA,NOT,TMI,UFA,UPM,UVN | |
}; | |
void error(pfl_error e){ | |
switch(e){ | |
case pfl_error::FSE: | |
std::cerr<<"FSE (Footnote Sequence Error)"<<std::endl; | |
exit(1); | |
case pfl_error::IFA: | |
std::cerr<<"IFA (Improper Footnote Alert)"<<std::endl; | |
exit(2); | |
case pfl_error::MDA: | |
std::cerr<<"MDA (Malformed Delimiter Alert)"<<std::endl; | |
exit(3); | |
case pfl_error::MFA: | |
std::cerr<<"MFA (Missing Footnote Alert)"<<std::endl; | |
exit(4); | |
case pfl_error::NOT: | |
std::cerr<<"NOT (Not a PLF Document)"<<std::endl; | |
exit(5); | |
case pfl_error::TMI: | |
std::cerr<<"TMI (Too Much Information)"<<std::endl; | |
exit(6); | |
case pfl_error::UFA: | |
std::cerr<<"UFA (Unassigned Footnote Alert)"<<std::endl; | |
exit(7); | |
case pfl_error::UPM: | |
std::cerr<<"UPM (Unexpected PFLEND Marker)"<<std::endl; | |
exit(8); | |
case pfl_error::UVN: | |
std::cerr<<"UVN (Unrecognized Version Number)\nThis parser only supports version 1.0 ."<<std::endl; | |
exit(9); | |
} | |
} | |
std::vector<struct footnote*> footnotes; | |
class limit{ | |
unsigned long max,min,index; | |
public: | |
limit(unsigned long _max,unsigned long _min):max(_max),min(_min),index(0){} | |
operator bool()const{ | |
return index-min<max&&index<min; | |
} | |
bool use(){ | |
index++; | |
return *this; | |
} | |
std::string get_index(){ | |
return std::to_string(index); | |
} | |
}; | |
struct footnote{ | |
unsigned long min=0,*max,idx=0; | |
std::string content; | |
bool used=false; | |
footnote():max(nullptr),content(""){} | |
footnote(std::string _content):max(nullptr),content(_content){} | |
footnote(std::string _content,unsigned long _max,unsigned long _min):max(new unsigned long(_max)),min(_min),content(_content){} | |
~footnote(){ | |
if(max) delete max; | |
} | |
virtual std::string evaluate(bool eval_footnotes){ | |
idx++; | |
if(idx<=min||(max&&idx-min>*max)){ | |
return ""; | |
}else{ | |
std::string buf=content; | |
size_t p=0; | |
while(buf.find('[',p)!=std::string::npos) | |
evaluate(buf,p,eval_footnotes); | |
return buf; | |
} | |
} | |
private: | |
// a utility function to parse the end part of a function that expects a number | |
unsigned long number(std::string& buf,size_t& p)const{ | |
size_t q=p; // save beginning position | |
while(buf[q]=='['){ | |
evaluate(buf,q,true); | |
q=p; // that has been resolved, go back and look if it has to be resolved again | |
} | |
try{ | |
unsigned long l=std::stoul(buf.substr(p),&q); | |
q+=p; // account for substr in stol | |
if(buf[q]!=']') | |
throw pfl_error::MDA; | |
p=q; // set to end | |
return l; | |
}catch(std::invalid_argument){ | |
throw pfl_error::MDA; | |
} | |
throw 0; // just to calm the compiler | |
} | |
// a utility function to try to parse a number without changes to the buffer if it fails | |
unsigned long try_number(std::string& buf,size_t& p)const{ | |
std::string buf_cpy=buf; | |
size_t p_cpy=p; | |
try{ | |
unsigned long l=number(buf_cpy,p); | |
buf=buf_cpy; | |
return l; | |
}catch(pfl_error){ | |
// revert changes | |
buf=buf_cpy; | |
p=p_cpy; | |
// then rethrow | |
throw; | |
} | |
} | |
// just a utility function to parse the part of a function that should contain two numbers. | |
std::pair<unsigned long,unsigned long> numbers(std::string& buf,size_t& p)const{ | |
size_t q=p; // save beginning position | |
while(buf[q]=='['){ | |
evaluate(buf,q,true); | |
q=p; // that has been resolved, go back and look if it has to be resolved again | |
} | |
// all resolved, should now be a number | |
try{ | |
unsigned long a=std::stoul(buf.substr(p),&q); | |
q+=p; // account for substr in stol | |
if(buf[q]!=':') | |
throw pfl_error::MDA; | |
// number loaded, to the second argument | |
p=q+1; // skip colon | |
unsigned long b=number(buf,p); | |
return std::make_pair(a,b); | |
}catch(std::invalid_argument){ | |
throw pfl_error::MDA; | |
} | |
throw 0; // just to calm the compiler | |
} | |
/* | |
This method handles all the functions because they are not to be used in the BODY section but exclusively inside the FOOTNOTES section. | |
That is also why they have to be resolved, before the footnote's content is (re)placed into the BODY section. | |
*/ | |
void evaluate(std::string& buf,size_t& p,bool eval_footnotes)const{ | |
if(buf.substr(p,3)=="[[]"){ | |
// escaped left bracket | |
buf.replace(p,3,"["); | |
p+=3; | |
}else if(buf.substr(p,3)=="[]]"){ | |
// escaped right bracket | |
buf.replace(p,3,"]"); | |
p+=3; | |
}else if(buf.substr(p,5)=="[ABC]"){ | |
buf.replace(p,5,"ABCDEFGHIJKLMNOPQRSTUVWXYZ"); | |
p+=26; | |
}else if(buf.substr(p,5)=="[ADD:"){ | |
size_t q=p+5; | |
std::pair<unsigned long,unsigned long> nums=numbers(buf,q); | |
std::stringstream s; | |
s<<(nums.first+nums.second); | |
buf.replace(p,q-p+1,s.str()); | |
p+=s.str().size(); | |
}else if(buf.substr(p,5)=="[AND:"){ | |
size_t q=p+5; | |
std::pair<unsigned long,unsigned long> nums=numbers(buf,q); | |
std::stringstream s; | |
s<<(nums.first&&nums.second); | |
buf.replace(p,q-p+1,s.str()); | |
p+=s.str().size(); | |
}else if(buf.substr(p,7)=="[ASCII:"){ | |
try{ | |
size_t q; | |
char c=std::stoi(buf.substr(p+7),&q,16); | |
if(buf[q]!=']'||c>127) // only 7-bit ASCII is allowed | |
throw pfl_error::MDA; | |
buf[p]=c; | |
buf.erase(p+1,9); | |
p++; | |
}catch(std::invalid_argument){ | |
throw pfl_error::MDA; | |
} | |
}else if(buf.substr(p,6)=="[BEEP]"){ | |
buf.replace(p,6,"\a"); | |
p++; | |
}else if(buf.substr(p,6)=="[DATE]"){ | |
std::stringstream s; | |
std::time_t t=std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); | |
s<<std::put_time(std::localtime(&t),"%x"); | |
buf.replace(p,6,s.str()); | |
p+=s.str().size(); | |
}else if(buf.substr(p,7)=="[FALSE]"){ | |
buf.replace(p,7,"0"); | |
p++; | |
}else if(buf.substr(p,4)=="[GT:"){ | |
size_t q=p+4; | |
std::pair<unsigned long,unsigned long> nums=numbers(buf,q); | |
std::stringstream s; | |
s<<(nums.first>nums.second); | |
buf.replace(p,q-p+1,s.str()); | |
p+=s.str().size(); | |
}else if(buf.substr(p,5)=="[HEX:"){ | |
if(buf[p+6]!=']') | |
throw pfl_error::MDA; | |
char c=buf[p+5]; | |
std::stringstream s; | |
s<<std::hex<<(int)c; | |
buf.replace(p,7,s.str()); | |
p+=s.str().size(); | |
}else if(buf.substr(p,4)=="[HS]"){ | |
buf.replace(p,4,footnotes.at(0)->evaluate(false)); | |
p+=4; | |
}else if(buf.substr(p,4)=="[IF:"){ | |
bool cond=true; | |
size_t q=p+4; // save beginning position | |
while(buf[q]=='['){ | |
evaluate(buf,q,false); | |
q=p+4; // that has been resolved, go back and look if it has to be resolved again | |
} | |
if(buf[p+4]=='['){ | |
// all functions should be resolved, check if condition is another footnote (-> always considered true) | |
cond=true; | |
q=buf.find(']',p+4)+2;// skip ']' and ':' | |
}else if(buf[p+4]==':'){ | |
// empty condition | |
cond=false; | |
q++; | |
}else{ | |
try{ | |
unsigned long l=std::stoul(buf.substr(p+4),&q); | |
q+=p+4; // account for substr in stol | |
if(buf[q]!=':') | |
throw pfl_error::MDA; | |
cond=(l!=0); | |
q++; // skip ':' | |
}catch(std::invalid_argument){ | |
// condition is not empty and not a number, must be true | |
cond=true; | |
q=buf.find(':',p+4)+1; // skip content and ':' | |
} | |
} | |
std::string a; | |
for(size_t lvl=0;!(lvl==0&&(buf[q]==':'||buf[q]==']'));q++){ | |
if(buf[q]=='[') lvl++; | |
if(buf[q]==']') lvl--; | |
a+=buf[q]; | |
} | |
if(buf[q]==':'){ | |
q++; // skip ':' | |
std::string b; | |
for(size_t lvl=0;!(lvl==0&&(buf[q]==':'||buf[q]==']'));q++){ | |
if(buf[q]=='[') lvl++; | |
if(buf[q]==']') lvl--; | |
b+=buf[q]; | |
} | |
buf.replace(p,q-p+1,cond?a:b); | |
}else if(buf[q]!=']'){ | |
throw pfl_error::MDA; | |
}else{ | |
q++; | |
buf.replace(p,q-p,cond?a:""); | |
} | |
}else if(buf.substr(p,7)=="[INDEX:"){ | |
size_t q=p+7; | |
unsigned long num=number(buf,q); | |
if(num>=footnotes.size()) | |
throw pfl_error::MFA; | |
std::string s=std::to_string(footnotes[num]->idx); | |
buf.replace(p,q-p+1,s); | |
p+=s.size(); | |
}else if(buf.substr(p,7)=="[INPUT]"){ | |
std::cout<<">"; | |
std::string in; | |
std::getline(std::cin,in); | |
buf.replace(p,7,in); | |
}else if(buf.substr(p,4)=="[IS:"){ | |
size_t q=p+4; | |
std::pair<unsigned long,unsigned long> nums=numbers(buf,q); | |
std::stringstream s; | |
s<<(nums.first==nums.second); | |
buf.replace(p,q-p+1,s.str()); | |
p+=s.str().size(); | |
}else if(buf.substr(p,5)=="[LEN:"){ | |
size_t q=buf.find_first_of(']',p+5); | |
std::stringstream s; | |
s<<(q-(p+5)); | |
buf.replace(p,q-p+1,s.str()); | |
p+=s.str().size(); | |
}else if(buf.substr(p,4)=="[LT:"){ | |
size_t q=p+4; | |
std::pair<unsigned long,unsigned long> nums=numbers(buf,q); | |
std::stringstream s; | |
s<<(nums.first<nums.second); | |
buf.replace(p,q-p+1,s.str()); | |
p+=s.str().size(); | |
}else if(buf.substr(p,5)=="[NOT:"){ | |
size_t q=p+4; | |
unsigned long l=number(buf,q); | |
std::stringstream s; | |
s<<(!l); | |
buf.replace(p,q-p+1,s.str()); | |
p+=s.str().size(); | |
}else if(buf.substr(p,4)=="[OR:"){ | |
size_t q=p+4; | |
std::pair<unsigned long,unsigned long> nums=numbers(buf,q); | |
std::stringstream s; | |
s<<(nums.first||nums.second); | |
buf.replace(p,q-p+1,s.str()); | |
p+=s.str().size(); | |
}else if(buf.substr(p,5)=="[ORD:"){ | |
size_t q=p+5; | |
unsigned long num=number(buf,q); | |
std::stringstream s; | |
s<<num; | |
if(num==11||num==12||num==13){ | |
s<<"th"; | |
}else if(num%10==1){ | |
s<<"st"; | |
}else if(num%10==2){ | |
s<<"nd"; | |
}else if(num%10==3){ | |
s<<"rd"; | |
}else{ | |
s<<"th"; | |
} | |
buf.replace(p,q-p+1,s.str()); | |
p+=s.str().size(); | |
}else if(buf.substr(p,7)=="[PRIME:"){ | |
size_t q=p+7; | |
unsigned long num=number(buf,q); | |
// prime test | |
bool prime=true; | |
for(unsigned long l=2;l<static_cast<unsigned long>(ceil(sqrt(num)));l++){ | |
if(num%l==0){ | |
prime=false; | |
break; | |
} | |
} | |
buf.replace(p,q-p+1,prime?"1":"0"); | |
p++; | |
}else if(buf.substr(p,5)=="[RET]"){ | |
buf.replace(p,5,"\n"); | |
p++; | |
}else if(buf.substr(p,7)=="[SPACE]"){ | |
buf.replace(p,7," "); | |
p++; | |
}else if(buf.substr(p,5)=="[SUB:"){ | |
size_t q=p+5; | |
std::pair<unsigned long,unsigned long> nums=numbers(buf,q); | |
std::stringstream s; | |
s<<(nums.first-nums.second); | |
buf.replace(p,q-p+1,s.str()); | |
p+=s.str().length(); | |
}else if(buf.substr(p,5)=="[TAB]"){ | |
buf.replace(p,5,"\t"); | |
p++; | |
}else if(buf.substr(p,6)=="[TIME]"){ | |
std::time_t t=std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); | |
std::stringstream s; | |
s<<std::put_time(std::localtime(&t),"%X"); | |
buf.replace(p,6,s.str()); | |
p+=s.str().size(); | |
}else if(buf.substr(p,6)=="[TRUE]"){ | |
buf.replace(p,6,"1"); | |
p++; | |
}else if(buf.substr(p,10)=="[USERDATA:"){ | |
if(buf.substr(p+10,9)=="FULLNAME]"){ | |
buf.replace(p,19,ud_name); | |
}else if(buf.substr(p+10,10)=="FIRSTNAME]"){ | |
buf.replace(p,20,ud_first); | |
}else if(buf.substr(p+10,9)=="LASTNAME]"){ | |
buf.replace(p,19,ud_last); | |
}else if(buf.substr(p+10,8)=="INITIAL]"){ | |
buf.replace(p,18,ud_initial); | |
}else if(buf.substr(p+10,8)=="COMPANY]"){ | |
buf.replace(p,18,ud_company); | |
}else if(buf.substr(p+10,7)=="STREET]"){ | |
buf.replace(p,17,ud_street); | |
}else if(buf.substr(p+10,5)=="CITY]"){ | |
buf.replace(p,15,ud_city); | |
}else if(buf.substr(p+10,7)=="REGION]"){ | |
buf.replace(p,17,ud_region); | |
}else if(buf.substr(p+10,7)=="POSTAL]"){ | |
buf.replace(p,17,ud_postal); | |
}else if(buf.substr(p+10,8)=="COUNTRY]"){ | |
buf.replace(p,18,ud_country); | |
}else if(buf.substr(p+10,9)=="LANGUAGE]"){ | |
buf.replace(p,19,ud_lang); | |
}else{ | |
throw pfl_error::MDA; | |
} | |
}else if(buf.substr(p,5)=="[VER]"){ | |
buf.replace(p,5,"1.0.2"); | |
p+=3; | |
}else if(buf.substr(p,5)=="[XOR:"){ | |
size_t q=p+5; | |
std::pair<unsigned long,unsigned long> nums=numbers(buf,q); | |
std::stringstream s; | |
s<<((!nums.first)!=(!nums.second)); | |
buf.replace(p,q-p+1,s.str()); | |
p+=s.str().size(); | |
}else if(buf.substr(p,5)=="[ZEN]"){ | |
buf.erase(p,5); | |
}else if(buf[p]=='['){ | |
size_t q=buf.find_first_not_of("0123456789",p+1); | |
if(q==std::string::npos||q==p+1||buf[q]!=']') | |
throw pfl_error::MDA; | |
unsigned long num=std::stoul(buf.substr(p+1,q-p-1)); | |
if(footnotes.size()<=num) | |
throw pfl_error::MFA; | |
if(eval_footnotes){ | |
std::string r=footnotes.at(num)->evaluate(false); | |
buf.replace(p,q-p+1,r); | |
}else{ | |
p=q+1; | |
} | |
}else{ | |
// only go to next character, there is nothing to be done here | |
p++; | |
} | |
} | |
}; | |
struct honorable_salutation:public footnote{ | |
bool declared=false; | |
std::string evaluate(bool)override{ | |
idx++; | |
return "Hi Sherry!"; | |
} | |
}; | |
int main(int argc,char** argv){ | |
std::streambuf *cinbuf=std::cin.rdbuf(); // save cin buffer in case it is redirected | |
if(argc>1){ | |
// redirect file to stdin | |
std::ifstream*f=new std::ifstream(argv[1]); | |
if(!f->is_open()){ | |
std::cerr<<"?file"<<std::endl; | |
return 1; | |
} | |
std::cin.rdbuf(f->rdbuf()); | |
} | |
std::string body; | |
std::string buf; | |
footnotes.push_back(new honorable_salutation()); // add HS footnote | |
while(std::getline(std::cin,buf)){ | |
if(buf=="[PFL1.0]"||buf=="[PFL1.0.1]"||buf=="[PFL1.0.2]") | |
break; | |
size_t i=buf.find("[PFL"); | |
if(i!=std::string::npos){ | |
if(i!=0||buf.find_first_of(']')==std::string::npos) | |
error(pfl_error::NOT); | |
if(buf=="[PFLEND]"){ | |
error(pfl_error::UPM); | |
}else if(buf!="[PFL1.0]"&&buf!="[PFL1.0.1]"&&buf!="[PFL1.0.2]"){ | |
error(pfl_error::UVN); | |
} | |
} | |
while((i=buf.find("[",i))!=std::string::npos){ | |
if(buf.substr(i,3)=="[[]"||buf.substr(i,3)=="[]]"){ | |
// just escaped brackets | |
i+=3; | |
continue; | |
}else if(buf.substr(i,4)=="[HS]"){ | |
// honorable salutation | |
i+=4; | |
continue; | |
} | |
i=buf.find_first_not_of("0123456789",i); | |
if(i==std::string::npos||buf[i++]!=']') | |
error(pfl_error::MDA); | |
} | |
body+=buf+'\n'; | |
} | |
if(std::cin.eof()) | |
// [PFLEND] is definitely missing | |
error(pfl_error::NOT); | |
// body loaded, start of footnote section | |
while(std::getline(std::cin,buf)){ | |
if(buf.empty()) continue; // ignore newlines | |
if(!buf[0]=='[') | |
error(pfl_error::IFA); | |
// create a new entry for the footnote about to be parsed | |
footnotes.push_back(new footnote()); | |
footnote* f=footnotes.back(); | |
size_t p=buf.find_first_not_of("0123456789",1); | |
if(p==1){ | |
if(buf.substr(1,2)=="HS"){ | |
delete footnotes.back(); | |
footnotes.pop_back(); | |
f=footnotes.at(0); | |
static_cast<honorable_salutation*>(f)->declared=true; | |
p=3; | |
}else if(buf.substr(1,7)=="PFLEND]"){ | |
delete footnotes.back(); | |
footnotes.pop_back(); | |
break; | |
}else{ | |
error(pfl_error::IFA); | |
} | |
}else if(p==std::string::npos){ | |
error(pfl_error::MDA); | |
}else if(std::stoul(buf.substr(1,p-1))!=footnotes.size()-1){ | |
error(pfl_error::FSE); | |
} | |
buf.erase(0,p); | |
if(buf[0]==':'){ | |
// there is a first limit | |
p=buf.find_first_not_of("0123456789",1); | |
if(p==1||p==std::string::npos) error(pfl_error::IFA); // the limit is missing | |
unsigned long max=std::stoul(buf.substr(1,p-1)); | |
buf.erase(0,p); // get rid of colon and number | |
if(buf[0]==':'){ | |
// there is a second limit | |
p=buf.find_first_not_of("0123456789",1); | |
if(p==1||p==std::string::npos) error(pfl_error::IFA); // the limit is missing | |
unsigned long min=std::stoul(buf.substr(1,p-1)); | |
buf.erase(0,p); // get rid of colon and number | |
f->max=new unsigned long(max); | |
f->min=min-1; | |
}else{ | |
f->max=new unsigned long(max); | |
f->min=0; | |
} | |
} | |
if(buf[0]!=']') // footnote delimiter has to close | |
error(pfl_error::MDA); | |
if(f!=footnotes.at(0)){ | |
if(buf[1]!=' '||buf.size()==2) // footnote has to have content | |
error(pfl_error::IFA); | |
f->content=buf.substr(2); | |
}else{ | |
// special HS footnote, has to be empty | |
if(buf.size()!=1) | |
error(pfl_error::IFA); | |
} | |
} | |
// the last line loaded should contain [PFLEND] | |
if(buf.find("[PFLEND]")==std::string::npos) | |
error(pfl_error::NOT); | |
// all footnotes loaded, start parsing body | |
// reset to normal cin | |
std::cin.rdbuf(cinbuf); | |
size_t p=0; | |
while(p<body.size()){ | |
p=body.find_first_of('[',p); | |
if(p==std::string::npos) // no more open brackets | |
break; | |
size_t q=body.find_first_not_of("0123456789",p+1); | |
if(q==std::string::npos||q==p+1||body[q]!=']') | |
error(pfl_error::MDA); | |
unsigned long num=std::stoul(body.substr(p+1,q-p-1)); | |
if(footnotes.size()<=num) | |
error(pfl_error::MFA); | |
try{ | |
std::string r=footnotes.at(num)->evaluate(true); | |
body.replace(p,q-p+1,r); | |
p+=r.size(); | |
}catch(pfl_error e){ | |
error(e); | |
} | |
} | |
if(static_cast<honorable_salutation*>(footnotes[0])->declared&&footnotes[0]->idx==0) | |
error(pfl_error::UFA); | |
for(size_t i=1;i<footnotes.size();i++){ | |
if(footnotes[i]->idx==0) | |
error(pfl_error::UFA); | |
} | |
std::cout<<body<<std::flush; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Wow! I stumbled across this just now and I think it's hilarious someone else decided to write an interpreter for PFL!
I'm the author of this one and never thought anybody would see it, much less make their own :)