Skip to content

Instantly share code, notes, and snippets.

@Johann150
Created September 11, 2019 17:54
Show Gist options
  • Save Johann150/0c3c1faf96bb92e185c0aa930bb34c22 to your computer and use it in GitHub Desktop.
Save Johann150/0c3c1faf96bb92e185c0aa930bb34c22 to your computer and use it in GitHub Desktop.
/*
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;
}
@vasilescur
Copy link

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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment