1 // Hyperbolic Rogue -- HyperRogue streams
2 // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file hprint.cpp
5  *  \brief Routines related to displaying various things, and writing them to console and files. Nicer than the standard C++ streams.
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 EX FILE *debugfile;
12 
13 #if HDR
14 #define DF_INIT              1 // always display these
15 #define DF_MSG               2 // always display these
16 #define DF_WARN              4 // always display these
17 #define DF_ERROR             8 // always display these
18 #define DF_STEAM            16
19 #define DF_GRAPH            32
20 #define DF_TURN             64
21 #define DF_FIELD           128
22 #define DF_GEOM            256
23 #define DF_MEMORY          512
24 #define DF_TIME           1024 // a flag to display timestamps
25 #define DF_GP             2048
26 #define DF_POLY           4096
27 #define DF_LOG            8192
28 #define DF_VERTEX        16384
29 #define DF_KEYS "imwesxufgbtoplv"
30 #endif
31 
32 EX int debugflags = DF_INIT | DF_ERROR | DF_WARN | DF_MSG | DF_TIME | DF_LOG;
33 
34 EX string s0;
35 
its(int i)36 EX string its(int i) { char buf[64]; sprintf(buf, "%d", i); return buf; }
37 
itsh8(int i)38 EX string itsh8(int i) {static char buf[16]; sprintf(buf, "%08X", i); return buf; }
39 
40 EX string fts(ld x, int prec IS(6)) {
41   std::stringstream ss;
42   ss.precision(prec);
43   ss << x;
44   return ss.str();
45   }
46 
47 EX map<void*, int> pointer_indices;
48 
index_pointer(void * v)49 EX string index_pointer(void *v) {
50   if(v == nullptr) return "0";
51   if(!pointer_indices.count(v)) {
52     int s = isize(pointer_indices);
53     pointer_indices[v] = s;
54     }
55   int i = pointer_indices[v];
56   string res;
57   while(true) { res += ('A' + (i % 26)); i /= 26; if(!i) break; i--; }
58   return res;
59   }
60 
61 EX int stamplen = 6;
62 
get_stamp()63 EX string get_stamp() {
64   if(stamplen == 0) return "";
65   int t = SDL_GetTicks();
66   int pow10 = 1;
67   for(int i=0; i<stamplen; i++) pow10 *= 10;
68   if(t < 0) t = pow10 - 1;
69   t %= pow10;
70   string s = its(t);
71   while(isize(s) < stamplen) s = "0" + s;
72   return s;
73   }
74 
75 #if HDR
ONOFF(bool b)76 inline string ONOFF(bool b) { return b ? XLAT("ON") : XLAT("OFF"); }
77 
78 struct hstream {
79   virtual void write_char(char c) = 0;
write_charshr::hstream80   virtual void write_chars(const char* c, size_t q) { while(q--) write_char(*(c++)); }
81   virtual char read_char() = 0;
read_charshr::hstream82   virtual void read_chars(char* c, size_t q) { while(q--) *(c++) = read_char(); }
get_vernumhr::hstream83   virtual color_t get_vernum() { return VERNUM_HEX; }
84 
writehr::hstream85   template<class T> void write(const T& t) { hwrite(*this, t); }
readhr::hstream86   template<class T> void read(T& t) { hread(*this, t); }
gethr::hstream87   template<class T> T get() { T t; hread(*this, t); return t; }
get_rawhr::hstream88   template<class T> T get_raw() { T t; hread_raw(*this, t); return t; }
89   };
90 
hwrite_raw(hstream & hs,const T & c)91 template<class T> void hwrite_raw(hstream& hs, const T& c) { hs.write_chars((char*) &c, sizeof(T)); }
hread_raw(hstream & hs,T & c)92 template<class T> void hread_raw(hstream& hs, T& c) { hs.read_chars((char*) &c, sizeof(T)); }
93 
hwrite(hstream & hs,const T & c)94 template<class T, typename = typename std::enable_if<std::is_integral<T>::value || std::is_enum<T>::value>::type> void hwrite(hstream& hs, const T& c) { hwrite_raw(hs, c); }
hread(hstream & hs,T & c)95 template<class T, typename = typename std::enable_if<std::is_integral<T>::value || std::is_enum<T>::value>::type> void hread(hstream& hs, T& c) { hread_raw(hs, c); }
96 
hwrite(hstream & hs,const string & s)97 inline void hwrite(hstream& hs, const string& s) {
98   if(isize(s) >= 255) {
99     hs.write_char((char)255);
100     hs.write<int>(isize(s));
101     }
102   else
103     hs.write_char(isize(s));
104   for(char c: s) hs.write_char(c);
105   }
hread(hstream & hs,string & s)106 inline void hread(hstream& hs, string& s) {
107   s = ""; int l = (unsigned char) hs.read_char();
108   if(l == 255) l = hs.get<int>();
109   for(int i=0; i<l; i++) s += hs.read_char();
110   }
hwrite(hstream & hs,const ld & h)111 inline void hwrite(hstream& hs, const ld& h) { double d = h; hs.write_chars((char*) &d, sizeof(double)); }
hread(hstream & hs,ld & h)112 inline void hread(hstream& hs, ld& h) { double d; hs.read_chars((char*) &d, sizeof(double)); h = d; }
113 
hwrite(hstream & hs,const array<T,X> & a)114 template<class T, size_t X> void hwrite(hstream& hs, const array<T, X>& a) { for(auto &ae: a) hwrite(hs, ae); }
hread(hstream & hs,array<T,X> & a)115 template<class T, size_t X> void hread(hstream& hs, array<T, X>& a) { for(auto &ae: a) hread(hs, ae); }
116 
hread(hstream & hs,hyperpoint & h)117 inline void hread(hstream& hs, hyperpoint& h) { for(int i=0; i<MDIM; i++) hread(hs, h[i]); }
hwrite(hstream & hs,hyperpoint h)118 inline void hwrite(hstream& hs, hyperpoint h) { for(int i=0; i<MDIM; i++) hwrite(hs, h[i]); }
119 
hwrite(hstream & hs,const vector<T> & a)120 template<class T> void hwrite(hstream& hs, const vector<T>& a) { hwrite<int>(hs, isize(a)); for(auto &ae: a) hwrite(hs, ae); }
hread(hstream & hs,vector<T> & a)121 template<class T> void hread(hstream& hs, vector<T>& a) { a.resize(hs.get<int>()); for(auto &ae: a) hread(hs, ae); }
122 
hwrite(hstream & hs,const map<T,U> & a)123 template<class T, class U> void hwrite(hstream& hs, const map<T,U>& a) {
124   hwrite<int>(hs, isize(a)); for(auto &ae: a) hwrite(hs, ae.first, ae.second);
125   }
hread(hstream & hs,map<T,U> & a)126 template<class T, class U> void hread(hstream& hs, map<T,U>& a) {
127   a.clear();
128   int N = hs.get<int>();
129   for(int i=0; i<N; i++) {
130     T key; hread(hs, key);
131     hread(hs, a[key]);
132     }
133   }
134 
hwrite(hstream & hs,const C & c,const C1 & c1,const CS &...cs)135 template<class C, class C1, class... CS> void hwrite(hstream& hs, const C& c, const C1& c1, const CS&... cs) { hwrite(hs, c); hwrite(hs, c1, cs...); }
hread(hstream & hs,C & c,C1 & c1,CS &...cs)136 template<class C, class C1, class... CS> void hread(hstream& hs, C& c, C1& c1, CS&... cs) { hread(hs, c); hread(hs, c1, cs...); }
137 
hstream_exceptionhr::hstream_exception138 struct hstream_exception : hr_exception { hstream_exception() {} };
139 
140 struct fhstream : hstream {
141   color_t vernum;
142   FILE *f;
fhstreamhr::fhstream143   explicit fhstream() { f = NULL; vernum = VERNUM_HEX; }
fhstreamhr::fhstream144   explicit fhstream(const string pathname, const char *mode) { f = fopen(pathname.c_str(), mode); vernum = VERNUM_HEX; }
~fhstreamhr::fhstream145   ~fhstream() { if(f) fclose(f); }
get_vernumhr::fhstream146   color_t get_vernum() override { return vernum; }
write_charhr::fhstream147   void write_char(char c) override { write_chars(&c, 1); }
write_charshr::fhstream148   void write_chars(const char* c, size_t i) override { if(fwrite(c, i, 1, f) != 1) throw hstream_exception(); }
read_charshr::fhstream149   void read_chars(char* c, size_t i) override { if(fread(c, i, 1, f) != 1) throw hstream_exception(); }
read_charhr::fhstream150   char read_char() override { char c; read_chars(&c, 1); return c; }
151   };
152 
153 struct shstream : hstream {
154   color_t vernum;
155   string s;
156   int pos;
shstreamhr::shstream157   explicit shstream(const string& t = "") : s(t) { pos = 0; vernum = VERNUM_HEX; }
get_vernumhr::shstream158   color_t get_vernum() override { return vernum; }
write_charhr::shstream159   void write_char(char c) override { s += c; }
read_charhr::shstream160   char read_char() override { if(pos == isize(s)) throw hstream_exception(); return s[pos++]; }
161   };
162 
print(hstream & hs)163 inline void print(hstream& hs) {}
164 
sprint(const CS &...cs)165 template<class... CS> string sprint(const CS&... cs) { shstream hs; print(hs, cs...); return hs.s; }
166 
print(hstream & hs,const C & c,const C1 & c1,const CS &...cs)167 template<class C, class C1, class... CS> void print(hstream& hs, const C& c, const C1& c1, const CS&... cs) { print(hs, c); print(hs, c1, cs...); }
168 
println(hstream & hs,const CS &...cs)169 template<class... CS> void println(hstream& hs, const CS&... cs) { print(hs, cs...); hs.write_char('\n'); }
170 
spaced(int i)171 inline string spaced(int i) { return its(i); }
spaced(color_t col)172 inline string spaced(color_t col) { return itsh8(col); }
spaced(const string & s)173 inline string spaced(const string& s) { return s; }
spaced(ld x)174 inline string spaced(ld x) { return fts(x, 10); }
spaced_of(T a[],int q)175 template<class T> string spaced_of(T a[], int q) { string s = spaced(a[0]); for(int i=1; i<q; i++) s += ' ', s += spaced(a[i]); return s; }
spaced(const array<T,i> & a)176 template<class T, int i> string spaced(const array<T,i>& a) { return spaced_of(&a[0], isize(a)); }
spaced(const C & c,const C1 & c1,const CS &...cs)177 template<class C, class C1, class... CS> string spaced(const C& c, const C1& c1, const CS&... cs) { return spaced(c) + " " + spaced(c1, cs...); }
178 
179 bool scan(fhstream& hs, int&);
180 bool scan(fhstream& hs, ld&);
181 bool scan(fhstream& hs, string&);
182 bool scan(fhstream& hs, color_t& c);
scan(fhstream & hs,C & c,C1 & c1,CS &...cs)183 template<class C, class C1, class... CS> bool scan(fhstream& hs, C& c, C1& c1, CS&... cs) { return scan(hs, c) && scan(hs, c1, cs...); }
184 
185 string scanline(fhstream& hs);
scan(fhstream & hs)186 template<class T> T scan(fhstream& hs) { T t {}; scan(hs, t); return t; }
187 
188 // copied from: https://stackoverflow.com/questions/16387354/template-tuple-calling-a-function-on-each-element
189 
190 namespace detail
191 {
192     template<int... Is>
193     struct seq { };
194 
195     template<int N, int... Is>
196     struct gen_seq : gen_seq<N - 1, N - 1, Is...> { };
197 
198     template<int... Is>
199     struct gen_seq<0, Is...> : seq<Is...> { };
200 
201     template<typename T, typename F, int... Is>
for_each(T && t,F f,seq<Is...>)202     void for_each(T&& t, F f, seq<Is...>)
203     {
204         auto l = { (f(std::get<Is>(t)), 0)... }; ignore(l);
205     }
206 }
207 
208 template<typename... Ts, typename F>
for_each_in_tuple(std::tuple<Ts...> const & t,F f)209 void for_each_in_tuple(std::tuple<Ts...> const& t, F f)
210 {
211     detail::for_each(t, f, detail::gen_seq<sizeof...(Ts)>());
212 }
213 
print(hstream & hs,const string & s)214 inline void print(hstream& hs, const string& s) { hs.write_chars(s.c_str(), isize(s)); }
print(hstream & hs,int i)215 inline void print(hstream& hs, int i) { print(hs, its(i)); }
print(hstream & hs,ld x)216 inline void print(hstream& hs, ld x) { print(hs, fts(x, 6)); }
print(hstream & hs,color_t col)217 inline void print(hstream& hs, color_t col) { print(hs, itsh8(col)); }
218 
print(hstream & hs,const walker<T> & w)219 template<class T> void print(hstream& hs, const walker<T>& w) { print(hs, "[", w.at, "/", w.spin, "/", w.mirrored, "]"); }
220 
221 struct comma_printer {
222   bool first;
223   hstream& hs;
operator ()hr::comma_printer224   template<class T> void operator() (const T& t) { if(first) first = false; else print(hs, ","); print(hs, t); }
comma_printerhr::comma_printer225   comma_printer(hstream& hs) : first(true), hs(hs) {}
226   };
227 
print(hstream & hs,const array<T,X> & a)228 template<class T, size_t X> void print(hstream& hs, const array<T, X>& a) { print(hs, "("); comma_printer c(hs); for(const T& t: a) c(t); print(hs, ")"); }
print(hstream & hs,const vector<T> & a)229 template<class T> void print(hstream& hs, const vector<T>& a) { print(hs, "("); comma_printer c(hs); for(const T& t: a) c(t); print(hs, ")"); }
230 
print(hstream & hs,const map<T,U> & a)231 template<class T, class U> void print(hstream& hs, const map<T,U>& a) { print(hs, "("); comma_printer c(hs); for(auto& t: a) c(t); print(hs, ")"); }
232 
print(hstream & hs,const hyperpoint h)233 inline void print(hstream& hs, const hyperpoint h) { print(hs, (const array<ld, MAXMDIM>&)h); }
print(hstream & hs,const transmatrix T)234 inline void print(hstream& hs, const transmatrix T) {
235   print(hs, "("); comma_printer c(hs);
236   for(int i=0; i<MDIM; i++)
237   for(int j=0; j<MDIM; j++) c(T[i][j]);
238   print(hs, ")"); }
239 
print(hstream & hs,const shiftpoint h)240 inline void print(hstream& hs, const shiftpoint h) { print(hs, h.h, "@", h.shift); }
print(hstream & hs,const shiftmatrix T)241 inline void print(hstream& hs, const shiftmatrix T) { print(hs, T.T, "@", T.shift); }
242 
print(hstream & hs,const pair<T,U> & t)243 template<class T, class U> void print(hstream& hs, const pair<T, U> & t) { print(hs, "(", t.first, ",", t.second, ")"); }
244 
print(hstream & hs,const tuple<T...> & t)245 template<class... T> void print(hstream& hs, const tuple<T...> & t) {
246   print(hs, "(");
247   comma_printer p(hs);
248   for_each_in_tuple(t, p);
249   print(hs, ")");
250   }
251 
252 #ifndef SPECIAL_LOGGER
special_log(char c)253 inline void special_log(char c) { if(debugfile) fputc(c, debugfile); putchar(c); }
254 #endif
255 
256 #if !CAP_SDL && CAP_TIMEOFDAY
257 int SDL_GetTicks();
258 #endif
259 
260 struct logger : hstream {
261   int indentation;
262   bool doindent;
loggerhr::logger263   explicit logger() { doindent = false; }
264   void write_char(char c) override;
read_charhr::logger265   char read_char() override { throw hstream_exception(); }
266   };
267 
268 extern logger hlog;
println_log(T...t)269 template<class... T> void println_log(T... t) { println(hlog, t...); }
print_log(T...t)270 template<class... T> void print_log(T... t) { print(hlog, t...); }
271 
272 #ifdef __GNUC__
273 __attribute__((__format__ (__printf__, 1, 2)))
274 #endif
format(const char * fmt,...)275 inline string format(const char *fmt, ...) {
276   char buf[1000];
277   va_list ap;
278   va_start(ap, fmt);
279   vsnprintf(buf, 1000, fmt, ap);
280   va_end(ap);
281   return buf;
282   }
283 
print(hstream & hs,heptagon * h)284 inline void print(hstream& hs, heptagon* h) { print(hs, "H", index_pointer(h)); }
print(hstream & hs,cell * h)285 inline void print(hstream& hs, cell* h) { print(hs, "C", index_pointer(h)); }
print(hstream & hs,hrmap * h)286 inline void print(hstream& hs, hrmap* h) { print(hs, "M", index_pointer(h)); }
287 
print(hstream & hs,cellwalker cw)288 inline void print(hstream& hs, cellwalker cw) {
289   if(cw.at) print(hs, "[", cw.at, "/", cw.at->type, ":", cw.spin, ":", cw.mirrored, "]");
290   else print(hs, "[NULL]");
291   }
292 
293 struct indenter {
294   dynamicval<int> ind;
295 
indenterhr::indenter296   explicit indenter(int i = 2) : ind(hlog.indentation, hlog.indentation + (i)) {}
297   };
298 
299 struct indenter_finish : indenter {
indenter_finishhr::indenter_finish300   explicit indenter_finish(bool b = true): indenter(b ? 2:0) {}
~indenter_finishhr::indenter_finish301   ~indenter_finish() { if(hlog.indentation != ind.backup) println(hlog, "(done)"); }
302   };
303 
304 #endif
305 
write_char(char c)306 void logger::write_char(char c) {
307   if(doindent) {
308     doindent = false;
309     if(debugflags & DF_TIME) {
310       string s = get_stamp();
311       if(s != "") { for(char c: s) special_log(c); special_log(' '); }
312       }
313     for(int i=0; i<indentation; i++) special_log(' ');
314     }
315   special_log(c);
316   if(c == 10) {
317     doindent = true;
318     if(debugfile) fflush(debugfile);
319     }
320   }
321 
print(hstream & hs,cld x)322 EX void print(hstream& hs, cld x) {
323   int parts = 0;
324   if(kz(real(x))) {
325     print(hs, real(x));
326     parts++;
327     }
328 
329   if(kz(imag(x))) {
330     if(parts && imag(x) > 0) print(hs, "+");
331     parts++;
332     print(hs, imag(x), "i");
333     }
334 
335   if(!parts) print(hs, 0);
336   }
337 
338 EX string fts_fixed(ld x, int prec IS(6)) {
339   std::stringstream ss;
340   ss.precision(prec);
341   ss << std::fixed << x;
342   return ss.str();
343   }
344 
scan(fhstream & hs,int & i)345 bool scan(fhstream& hs, int& i) { return fscanf(hs.f, "%d", &i) == 1; }
scan(fhstream & hs,color_t & c)346 bool scan(fhstream& hs, color_t& c) { return fscanf(hs.f, "%x", &c) == 1; }
scan(fhstream & hs,ld & x)347 bool scan(fhstream& hs, ld& x) { return fscanf(hs.f, "%lf", &x) == 1; }
scan(fhstream & hs,string & s)348 bool scan(fhstream& hs, string& s) { char t[10000]; t[0] = 0; int err = fscanf(hs.f, "%9500s", t); s = t; return err == 1 && t[0]; }
scanline(fhstream & hs)349 string scanline(fhstream& hs) { char buf[10000]; buf[0] = 0; ignore(fgets(buf, 10000, hs.f)); return buf; }
350 
351 /*
352 string fts_smartdisplay(ld x, int maxdisplay) {
353   string rv;
354   if(x > 1e9 || x < -1e9) retrun fts(x);
355   if(x<0) { rv = "-"; x = -x; }
356   int i = int(x);
357   rv += its(i);
358   x -= i;
359   bool nonzero = i;
360   if(x == 0) return rv;
361   if(x < 1e-9 && nonzero) return rv;
362   rv += ".";
363   while(maxdisplay > 0) {
364     x *= 10;
365     rv += '0' + int(x);
366     if(int(x)) nonzero = true;
367     x -= int(x);
368     if(x == 0) return rv;
369     if(x < 1e-9 && nonzero) return rv;
370     maxdisplay--;
371     }
372   } */
373 
cts(char c)374 EX string cts(char c) { char buf[8]; buf[0] = c; buf[1] = 0; return buf; }
llts(long long i)375 EX string llts(long long i) {
376     // sprintf does not work on Windows IIRC
377     if(i < 0) return "-" + llts(-i);
378     if(i < 10) return its((int) i);
379     return llts(i/10) + its(i%10);
380 }
itsh(unsigned int i)381 EX string itsh(unsigned int i) {static char buf[16]; sprintf(buf, "%03X", i); return buf; }
itsh(int i)382 EX string itsh(int i) {static char buf[16]; sprintf(buf, "%03X", i); return buf; }
itsh2(int i)383 EX string itsh2(int i) {static char buf[16]; sprintf(buf, "%02X", i); return buf; }
384 
itsh(unsigned long long i)385 EX string itsh(unsigned long long i) {
386   int i0 = int(i);
387   int i1 = int(i >> 32);
388   if(i1) return itsh(i1) + itsh8(i0);
389   else return itsh(i0);
390   }
391 
392 EX logger hlog;
393 
394 // kz: utility for printing
395 // if it is close to 0, assume it is floating errors
396 
kz(ld x)397 EX ld kz(ld x) {
398   if(abs(x) < 1e-6) return 0;
399   return x;
400   }
401 
kz(hyperpoint h)402 EX hyperpoint kz(hyperpoint h) {
403   for(int d=0; d<MAXMDIM; d++) h[d] = kz(h[d]);
404   return h;
405   }
406 
kz(transmatrix h)407 EX transmatrix kz(transmatrix h) {
408   for(int d=0; d<MAXMDIM; d++)
409   for(int e=0; e<MAXMDIM; e++)
410     h[d][e] = kz(h[d][e]);
411   return h;
412   }
413 
414 #if HDR
kz(vector<T> v)415 template<class T> vector<T> kz(vector<T> v) {
416   for(auto& el: v) el = kz(el);
417   return v;
418   }
419 #endif
420 
pick123()421 EX string pick123() { return cts('1' + rand() % 3); }
pick12()422 EX string pick12() { return cts('1' + rand() % 2); }
423 
424 #if HDR
serialize(const T & data)425 template<class T> string serialize(const T& data) {
426   shstream shs;
427   hwrite(shs, data);
428   return shs.s;
429   }
430 
deserialize(const string & s)431 template<class T> T deserialize(const string& s) {
432   shstream shs;
433   shs.s = s;
434   T data;
435   hread(shs, data);
436   return data;
437   }
438 #endif
439 
as_hexstring(string o)440 EX string as_hexstring(string o) {
441   string res;
442   for(char x: o) {
443     char buf[4];
444     sprintf(buf, "%02X", (unsigned char)(x));
445     res += buf;
446     }
447   return res;
448   }
449 
from_hexstring(string o)450 EX string from_hexstring(string o) {
451   string res;
452   for(int i=0; i<isize(o); i+=2) {
453     char buf[4];
454     buf[0] = o[i];
455     buf[1] = o[i+1];
456     buf[2] = 0;
457     unsigned x;
458     sscanf(buf, "%02X", &x);
459     res += char(x);
460     }
461   return res;
462   }
463 
as_cstring(string o)464 EX string as_cstring(string o) {
465   string s = "string(\"";
466   for(char c: o)
467     s += format("\\x%02x", (unsigned char) c);
468   s += format("\", %d)", isize(o));
469   return s;
470   }
471 
as_nice_cstring(string o)472 EX string as_nice_cstring(string o) {
473   string s = "\"";
474   for(char c: o)
475     if(c >= 32 && c < 126)
476       s += c;
477     else if(c == 10)
478       s += "\\n";
479     else
480       s += format("\\x%02x", (unsigned char) c);
481   s += "\"";
482   return s;
483   }
484 
485 #if HDR
486 #if ISANDROID
487 #define DEBB(r,x)
488 #define DEBB0(r,x)
489 #define DEBBI(r,x)
490 #else
491 #define DEBB(r,x) { if(debugflags & (r)) { println_log x; } }
492 #define DEBB0(r,x) { if(debugflags & (r)) { print_log x; } }
493 #define DEBBI(r,x) { if(debugflags & (r)) { println_log x; } } indenter_finish _debbi(debugflags & (r));
494 #endif
495 #endif
496 
497 #if CAP_GMP
its(mpq_class x)498 EX string its(mpq_class x) { std::stringstream ss; ss << x; return ss.str(); }
print(hstream & hs,const mpq_class & x)499 EX void print(hstream& hs, const mpq_class& x) {
500   std::stringstream ss; ss << x; print(hs, ss.str());
501   }
502 #endif
503 
504 #if HDR
lalign(int len,T...t)505 template<class... T> string lalign(int len, T... t) {
506   shstream hs;
507   print(hs, t...);
508   while(isize(hs.s) < len) hs.s += " ";
509   return hs.s;
510   }
511 #endif
512 }
513