Lu'!
Une petite version C++ d'un interpréteur très légèrement optimisé sur les branchements (mais vraiment très légèrement, d'autant que les maps, c'est pas l'idée du siècle). J'ai choisi une bande circulaire pour rendre la partie interprétation noexcept.
J'ai essayé de rendre l'ensemble le plus robuste possible aux erreurs d'exécution (c'est ce qui m'a pris le plus de temps au pifomètre) et de trouver les endroits qui peuvent encore poser problème (signalé en annotations dans le code), tout en écrivant des contrats basiques sur les fonctions (et en mettant des indices sur les raisons qui font que le contrat est valide). L'ensemble me semble const-correct.
(Petite note : un fichier qui contiendrait des caractères pas trop catholiques ne passerait pas forcément bien dans la moulinette, vous êtes prévenus ).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | #include <cassert> #include <algorithm> #include <fstream> #include <iostream> #include <string> #include <iterator> #include <array> #include <stdexcept> #include <map> using str_citer = std::string::const_iterator; using jumps = std::map<str_citer, str_citer>; /* Returns : true if c is a BF instruction false otherwise Exception : noexcept */ inline bool is_an_inst(char c) noexcept { return c == '+' || c == '-' || c == '>' || c == '<' || c == '.' || c == ',' || c == '[' || c == ']'; } str_citer compute_jumps(std::string const& prg, str_citer start, jumps& from_to, jumps& to_from); void extract_program(std::ifstream& ifs, std::string& program); void interpret(std::string const& program, jumps const& from_to, jumps const& to_from) noexcept; int main(int argc, char* argv[]){ if(argc != 2) throw std::invalid_argument{ "use : ./exec file_name" }; std::ifstream ifs{ argv[1], std::ios::in }; //warning ! remaining exception : std::string allocation (x2) if(!ifs) throw std::runtime_error{ std::string{ argv[1] } + " does not exists" }; //at this point : // - ifs is valid std::string prg{}; extract_program(ifs, prg); //at this point : // - ifs is not valid anymore // - prg is a pure BF program jumps from_to, to_from; //exception : unmatched ] if(compute_jumps(prg, std::begin(prg), from_to, to_from) != std::end(prg)) throw std::runtime_error{ "This program is not valid (unbalanced braces)." }; //at this point : // - prg is a pure BF program // - from_to contains jumps from [ to ] and no occurrence of std::end(prg) // - to_from contains jumps from ] to [ and no occurrence of std::end(prg) interpret(prg, from_to, to_from); return 0; } /* Pre : - ifs valid Post : - prg is pure BF - ifs is not valid anymore Exceptions : - IO exceptions on getline (unlikely) - allocation exception on program += and line allocation - remove_if cannot raise exception here - clear is noexcept - resize should not allocate (size is inferior) */ void extract_program(std::ifstream& ifs, std::string& program){ using std::begin; using std::end; program.clear(); for(std::string line{}; getline(ifs, line); ) program += line; //we only keep BF instructions auto e = std::remove_if(begin(program), end(program), [](char c){ return !is_an_inst(c); }); //remove final moved characters program.resize(e - begin(program)); } /* Pre : - prg is pure BF - std::begin(prg) <= it <= std::end(prg) Post : - from_to contains jumps from [ to ] * and no occurrence of std::end(prg) - to_from contains jumps from ] to [ * and no occurrence of std::end(prg) * between Pre(it) and Return value Returns : - iterator to ] or std::end(prg) (if this last happens in a rec call it is an error) Exceptions : - runtime_error (parsing) - allocation error on inserting in the map - recursive call exceptions */ str_citer compute_jumps(std::string const& prg, str_citer it, jumps& from_to, jumps& to_from) { //invariant : - std::begin(prg) <= it <= std::end(prg) // - from_to and to_from contains all visited jumps while(it != std::end(prg)){ if(*it == '['){ str_citer begin { it }; it = compute_jumps(prg, it+1, from_to, to_from); //exception : unmatched [ if(it == std::end(prg)) throw std::runtime_error{ "This program is not valid (unbalanced braces)." }; assert(begin != std::end(prg)); //obvious assert(it != std::end(prg)); //obvious //guarantee : from_to an to_from does not contain std::end(prg) from_to[begin] = it; to_from[it] = begin; } else if(*it == ']') return it; ++it; } return std::end(prg); } /* Pre : - prg is a pure BF program - from_to contains jumps from [ to ] and no occurrence of std::end(prg) - to_from contains jumps from ] to [ and no occurrence of std::end(prg) Post: - nothing special : standard output as been updated with output Exceptions : noexcept */ void interpret(std::string const& program, jumps const& from_to, jumps const& to_from) noexcept{ using std::begin; using std::end; std::array<char, 30000> data; data.fill(0); decltype(data)::iterator dp { begin(data) }; str_citer pc { begin(program) }; // Invariant : - std::begin(program) <= pc <= std::end(program) (== causes end loop) // - std::begin(data) <= dp < std::end(program) while(pc != end(program)){ char const byte { *dp }; switch(*pc){ case '+': ++(*dp); break; case '-': --(*dp); break; case '>': ++dp; break; case '<': --dp; break; case '.': std::cout<<byte; break; case ',': *dp = static_cast<char>(getchar()); break; case '[': if(!*dp) pc = (*from_to.find(pc)).second; break; case ']': if( *dp) pc = (*to_from.find(pc)).second; break; default : assert(false && "Something really bad happened"); } if(dp == end(data)) dp = begin(data); ++pc; } } |
Le tout compile avec tout ce tas d'options :
1 2 3 4 | CXXFLAGS=-Wall -Wextra -Werror -pedantic-errors -Wold-style-cast -Woverloaded-virtual -Wfloat-equal -Wwrite-strings -Wpointer-arith -Wcast-qual -Wcast-align -Wconversion -Wshadow -Weffc++ -Wredundant-decls -Wdouble-promotion -Winit-self -Wswitch-default -Wswitch-enum -Wundef -Wlogical-op -Winline -Wunused -Wuninitialized -std=c++1y -O2 -DNDEBUG |
Sur ma machine, ça affiche Mandelbrot en 34s (en même temps c'est de l'interprété pur à La RacheTM ). Et l'exécution en DEBUG marche aussi évidemment .
Voilà, voilà .