forked from vladtepesch/ArduinoMicroStringTemplateEngine
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTemplatePrinter.h
More file actions
383 lines (344 loc) · 11.5 KB
/
TemplatePrinter.h
File metadata and controls
383 lines (344 loc) · 11.5 KB
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
//#################################################################################################
// Very modified Template printer
//
// - Re-written to use the same logic in all routines (state machine).
// - C++ templated to handle variable array sizes without using sizeof()
// - Callback verions added to call routines corresponding to each template placeholder.
// - Placeholders can run from 0 to 99. Can handle single digit and two digit versions.
// - Callback pointer table can be in PROGMEM when template strings are also in PROGMEM.
// - Illegal placeholders are ignored.
// - Single .h file, no .cpp.
//
// The callback idea allows a printed string to be self-populating as the callback routines
// can go off and fetch data to print. The callbacks are passed the Print& pr parameter so
// can do pr.print() themselves.
//
// The point behind the re-write was to allow a simple event-driven refresh of an LCD screen page
// where the values of variables were automatically fetched and formatted and inserted into the
// shell of the screen text.
//
//#################################################################################################
#pragma once
#include <Print.h>
/**
* The original documentation below:
*
* A very simplistic string template engine that helps preventing the usage of string function
* to reduce heap fragmentation.
* Idea is: give it a Print - object that is the output target and a template string along
* with the values to fill in the place holder.
*
* The "%0" to "%9" are the placeholder for the runtime supplied values.
* If the character after the '%' sign is no decimal digit or another '% then just
* a single '%' is printed.
*
* Example:
*
* Instead of creating a String with concatination (or '+') that results in many
* temporary objects write a template string and use this class:
*
* \code{.cpp}
String name ("Dave");
String game ("TicTacToe");
////// instead of
String a = "Hello, " + name + ". I want to play a game. Would you like to play \"" + game + "\" with me?" ;
Serial.print(a);
////// use that
// array with template values:
const char* values[] = {name->c_str(), game->c_str()};
// output to serial - template parameter is C string literal from RAM (initially copied from flash on startup)
TemplatePrinter::printTo(Serial, "Hello, %0. I want to play a game. Would you like to play \"%1\" with me?", values);
////// or
// output to serial - template parameter is inline C string literal from FLASH (without being duplicated to RAM)
TemplatePrinter::printTo(Serial, F("Hello, %0. I want to play a game. Would you like to play \"%1\" with me?"), values);
// output to serial - same as above but uses a pointer to a FLASH string literal
static const char PROGMEM s_greeting[] = R"(Hello, %0. I want to play a game. Would you like to play "%1" with me?)";
TemplatePrinter::printTo(Serial, FPSTR(s_greeting), values);
\endcode
* The above second method also makes it more easy to put whole strings into flash (see 3rd variant);
*
*/
typedef void (TemplateCallback)(Print& pr);
class TemplatePrinter {
public:
/**
* Prints the i_template string (RAM) to pr and replaces occurences of "%0" - "%9" by the strings from the i_values table.
* '%' signs can be escaped by "%%" but will printed on there own if the second character is not a digit
* Extended so %10 - %99 can be used. Note that %NN where NN doesn't exist are just skipped.
*/
template <typename T, size_t N>
static void printTo(Print& pr, const char* i_template, T* const (&i_values)[N]);
//Added version that calls a callback from a table of pointers to functions for each template parameter
//pointer table is in ordinary memory
template <typename T, size_t N>
static void printToCallbacks(Print& pr, const char* i_template, T* const (&i_values)[N]);
/**
* Prints the i_template string (FLASH) to pr and replaces occurences of "%0" - "%9" by the strings from the i_values table.
* '%' signs can be escaped by "%%" but will printed on there own if the second character is not a digit
* Extended so %10 - %99 can be used. Note that %NN where NN doesn't exist are just skipped.
*/
template <typename T, size_t N>
static void printTo(Print& pr, const __FlashStringHelper* i_template, T* const (&i_values)[N]);
//Added version that calls a callback from a table of pointers to functions for each template parameter
//pointer table is in PROGMEM
template <typename T, size_t N>
static void printToCallbacks(Print& pr, const __FlashStringHelper* i_template, T* const (&i_values)[N]);
private:
};
//-------------------------------------------------------------------------------------------------
//in normal memory versions
template <typename T, size_t N>
void TemplatePrinter::printTo(Print& pr, const char* i_template, T* const (&i_values)[N])
{
const char* ps=i_template;
char c;
uint8_t state = 0; //start. look for %
uint8_t idx1;
uint8_t idx2;
for(;;){
c = *ps;
if(!c) { //end terminator found
if(state == 2){ //found 1 digit already, no 2nd digit
if(idx1<N) pr.write(i_values[idx1]);
}
break; //done
}
switch(state){
case 0:
if(c=='%'){ //start of parameter
state = 1;
}else{ //just print
pr.write(c);
}
break;
case 1:
if(c=='%'){
//escaped % with 2nd one
pr.write("%%");
state = 0;
}else{
//look for 1st digit
idx1 = c - '0';
if(idx1 >=0 && idx1 <=9){
//need to see if 2nd digit before doing the parameter
state = 2;
}else{
//not a digit, print out the % and char & go back to look for a %
pr.write("%"); pr.write(c);
state = 0;
}
}
break;
case 2:
//1 digit already found, look for 2nd
idx2 = c - '0';
if(idx2 >=0 && idx2 <=9){
//2 digits found
idx1 = idx1*10+idx2;
if((idx1)<N) pr.write(i_values[idx1]);
state = 0; //back to look for %
}else{
//only 1 digit
if(idx1<N) pr.write(i_values[idx1]);
pr.write(c);
state = 0;
}
break;
default:
break;
}
ps++;
}
}
////////////////////////// Callback version
template <typename T, size_t N>
void TemplatePrinter::printToCallbacks(Print& pr, const char* i_template, T* const (&i_values)[N]){
const char* ps=i_template;
char c;
uint8_t state = 0; //start. look for %
uint8_t idx1;
uint8_t idx2;
for(;;){
c = *ps;
if(!c) { //end terminator found
if(state == 2){ //found 1 digit already, no 2nd digit
if(idx1<N) i_values[idx1](pr);
}
break; //done
}
switch(state){
case 0:
if(c=='%'){ //start of parameter
state = 1;
}else{ //just print
pr.write(c);
}
break;
case 1:
if(c=='%'){
//escaped % with 2nd one
pr.write('%');
state = 0;
}else{
//look for 1st digit
idx1 = c - '0';
if(idx1 >=0 && idx1 <=9){
//need to see if 2nd digit before doing the parameter
state = 2;
}else{
//not a digit, print out the % and char & go back to look for a %
pr.write("%"); pr.write(c);
state = 0;
}
}
break;
case 2:
//1 digit already found, look for 2nd
idx2 = c - '0';
if(idx2 >=0 && idx2 <=9){
//2 digits found
idx1 = idx1*10+idx2;
if((idx1)<N) i_values[idx1](pr);
state = 0; //back to look for %
}else{
//only 1 digit
if(idx1<N) i_values[idx1](pr);
pr.write(c);
state = 0;
}
break;
default:
break;
}
ps++;
}
}
//in flash memory versions - this only works with the i_values table in normal memory
//to put i_values in PROGMEM needs the same idea as the templates with a table of pointers
//The callback version is the one that will be used almost all of the time.
template <typename T, size_t N>
void TemplatePrinter::printTo(Print& pr, const __FlashStringHelper* i_template, T* const (&i_values)[N])
{
PGM_P ps = reinterpret_cast<PGM_P>(i_template);
char c;
uint8_t state = 0; //start. look for %
uint8_t idx1;
uint8_t idx2;
for(;;){
c = pgm_read_byte(ps++);
if(!c) { //end terminator found
if(state == 2){ //found 1 digit already, no 2nd digit
if(idx1<N) pr.write(i_values[idx1]);
}
break; //done
}
switch(state){
case 0:
if(c=='%'){ //start of parameter
state = 1;
}else{ //just print
pr.write(c);
}
break;
case 1:
if(c=='%'){
//escaped % with 2nd one
pr.write("%%");
state = 0;
}else{
//look for 1st digit
idx1 = c - '0';
if(idx1 >=0 && idx1 <=9){
//need to see if 2nd digit before doing the parameter
state = 2;
}else{
//not a digit, print out the % and char & go back to look for a %
pr.write("%"); pr.write(c);
state = 0;
}
}
break;
case 2:
//1 digit already found, look for 2nd
idx2 = c - '0';
if(idx2 >=0 && idx2 <=9){
//2 digits found
idx1 = idx1*10+idx2;
if((idx1)<N) pr.write(i_values[idx1]);
state = 0; //back to look for %
}else{
//only 1 digit
if(idx1<N) pr.write(i_values[idx1]);
pr.write(c);
state = 0;
}
break;
default:
break;
}
}
}
/////// Callback version - both templates and callback function table in PROGMEM
template <typename T, size_t N>
void TemplatePrinter::printToCallbacks(Print& pr, const __FlashStringHelper* i_template, T* const (&i_values)[N])
{
PGM_P ps = reinterpret_cast<PGM_P>(i_template);
char c;
uint8_t state = 0; //start. look for %
uint8_t idx1;
uint8_t idx2;
for(;;){
c = pgm_read_byte(ps++);
if(!c) { //end terminator found
if(state == 2){ //found 1 digit already, no 2nd digit
if(idx1<N) ((T*) pgm_read_ptr(&i_values[idx1]))(pr);
}
break; //done
}
switch(state){
case 0:
if(c=='%'){ //start of parameter
state = 1;
}else{ //just print
pr.write(c);
}
break;
case 1:
if(c=='%'){
//escaped % with 2nd one
pr.write('%');
state = 0;
}else{
//look for 1st digit
idx1 = c - '0';
if(idx1 >=0 && idx1 <=9){
//need to see if 2nd digit before doing the parameter
state = 2;
}else{
//not a digit, print out the % and char & go back to look for a %
pr.write("%"); pr.write(c);
state = 0;
}
}
//ps++;
break;
case 2:
//1 digit already found, look for 2nd
idx2 = c - '0';
if(idx2 >=0 && idx2 <=9){
//2 digits found
idx1 = idx1*10+idx2;
if((idx1)<N) ((T*) pgm_read_ptr(&i_values[idx1]))(pr);
state = 0; //back to look for %
}else{
//only 1 digit
if((idx1)<N) ((T*) pgm_read_ptr(&i_values[idx1]))(pr);
pr.write(c);
state = 0;
}
break;
default:
break;
}
}
}