Euphoria
texttemplate.cc
Go to the documentation of this file.
1 #include "core/texttemplate.h"
2 
3 #include <utility>
4 
5 #include "assert/assert.h"
6 
7 #include "core/textfileparser.h"
8 #include "io/vfs.h"
9 #include "io/vfs_path.h"
10 #include "base/stringutils.h"
11 #include "base/stringbuilder.h"
12 
13 
14 namespace eu::core
15 {
16 
17 
19 
20 
21 Defines::Defines() = default;
22 
23 
24 bool
25 Defines::is_defined(const std::string& name) const
26 {
27  const auto found = values.find(name);
28  return found != values.end();
29 }
30 
31 
32 std::string
33 Defines::get_value(const std::string& name) const
34 {
35  const auto found = values.find(name);
36  if(found == values.end())
37  {
38  return "";
39  }
40 
41  return found->second;
42 }
43 
44 
45 void
46 Defines::remove(const std::string& name)
47 {
48  values.erase(name);
49 }
50 
51 
52 void
53 Defines::set(const std::string& name, const std::string& value)
54 {
55  values[name] = value;
56 }
57 
58 
60 
61 
63 
64 
65 bool
67 {
68  return !errors.empty();
69 }
70 
71 
72 void
74 (
75  const std::optional<io::FilePath>& file,
76  int line,
77  int /*unused*/,
78  const std::string& error
79 )
80 {
81  const auto file_name
82  = file.has_value()
83  ? file.value().path
84  : "<no_file>"
85  ;
86  const std::string message = fmt::format("{}({}): {}", file_name, line, error);
87  errors.push_back(message);
88 }
89 
90 
91 std::string
93 {
94  auto ss = StringBuilder{};
95 
96  for(const auto& mess: errors)
97  {
98  ss.add_string(mess);
99  ss.add_char('\n');
100  }
101 
102  return ss.to_string();
103 }
104 
105 
107 
108 
110 {
111  TemplateNode() = default;
112  virtual ~TemplateNode() = default;
113 
114  TemplateNode(const TemplateNode&) = delete;
116  void operator=(const TemplateNode&) = delete;
117  void operator=(TemplateNode&&) = delete;
118 
119  virtual void build_string
120  (
121  Defines* defines,
122  StringBuilder* out,
124  ) = 0;
125 };
126 
127 
128 // ------------------------------------------------------------------------
129 
130 
132 {
133  std::string text;
134 
135  explicit TemplateNodeString(std::string t)
136  : text(std::move(t))
137  {
138  }
139 
140  void build_string(Defines* /*defines*/, StringBuilder* out, TemplateErrorList* /*error*/) override
141  {
142  ASSERT(out);
143  out->add_string(text);
144  }
145 };
146 
147 
148 // ------------------------------------------------------------------------
149 
150 
152 {
153  std::vector<std::shared_ptr<TemplateNode>> nodes;
154 
155  TemplateNodeList() = default;
156 
157  void add(const std::shared_ptr<TemplateNode>& node)
158  {
159  nodes.push_back(node);
160  }
161 
162  void build_string(Defines* defines, StringBuilder* out, TemplateErrorList* error) override
163  {
164  for(const auto& node: nodes)
165  {
166  node->build_string(defines, out, error);
167  }
168  }
169 };
170 
171 
172 // ------------------------------------------------------------------------
173 
174 
176 {
178 
179  void
181  {
182  ASSERT(defines);
183  core::Defines my_defines = *defines;
184  TemplateNodeList::build_string(&my_defines, out, error);
185  }
186 };
187 
188 
189 // ------------------------------------------------------------------------
190 
191 
193 {
194  std::string name;
195  std::shared_ptr<TemplateNode> node;
196 
197  TemplateNodeIfdef(std::string aname, std::shared_ptr<TemplateNode> anode)
198  : name(std::move(aname))
199  , node(std::move(anode))
200  {
201  }
202 
203  void
205  {
206  ASSERT(defines);
207  if(defines->is_defined(name))
208  {
209  node->build_string(defines, out, error);
210  }
211  }
212 };
213 
214 
215 // ------------------------------------------------------------------------
216 
217 
219 {
220  std::string name;
221 
222  explicit TemplateNodeEval(std::string n)
223  : name(std::move(n))
224  {
225  }
226 
227  void build_string(Defines* defines, StringBuilder* out, TemplateErrorList* error) override
228  {
229  ASSERT(out);
230  ASSERT(defines);
231 
232  if(error != nullptr && !defines->is_defined(name))
233  {
234  // todo(Gustav): add file, line and column
235  error->add_error
236  (
237  std::nullopt,
238  0,
239  0,
240  fmt::format("{} is not defined", name)
241  );
242  }
243 
244  out->add_string(defines->get_value(name));
245  }
246 };
247 
248 // ------------------------------------------------------------------------
249 
251 {
252  std::string name;
253  std::string value;
254 
255  TemplateNodeSet(std::string aname, std::string avalue)
256  : name(std::move(aname))
257  , value(std::move(avalue))
258  {
259  }
260 
261  void build_string(Defines* defines, StringBuilder* out, TemplateErrorList* /*error*/) override
262  {
263  ASSERT(out);
264  ASSERT(defines);
265 
266  defines->set(name, value);
267  }
268 };
269 
270 
272 
273 
274 enum class TokenType
275 {
276  text,
277  if_def,
278  eval,
279  ident,
280  end,
281  set,
282  string,
283  include,
285 };
286 
287 std::string to_string(TokenType t)
288 {
289  switch(t)
290  {
291 #define CASE(V) case TokenType::V: return #V
292  CASE(text);
293  CASE(if_def);
294  CASE(eval);
295  CASE(ident);
296  CASE(end);
297  CASE(set);
298  CASE(include);
299  CASE(string);
300  CASE(end_of_file);
301 #undef CASE
302  }
303 
304  return "<UNHANDLED_CASE>";
305 }
306 
307 
308 struct Token
309 {
311  std::string value;
312  int line;
313  int column;
314 
315  Token(TokenType t, int l, int c, std::string v = "")
316  : type(t)
317  , value(std::move(v))
318  , line(l)
319  , column(c)
320  {
321  }
322 
323  [[nodiscard]] std::string
324  to_string() const
325  {
326  return fmt::format("{}({})", core::to_string(type), get_first_chars_with_ellipsis(value));
327  }
328 };
329 
330 
331 std::vector<Token> tokenize
332 (
333  const std::string& content,
335  const io::FilePath& file
336 )
337 {
338  ASSERT(error);
339 
340  auto parser = TextfileParser::from_string(content);
341  std::vector<Token> r;
342 
343  bool inside = false;
344 
346  int buffer_line = parser.get_line();
347  int buffer_column = parser.get_column();
348 
349  while(parser.has_more())
350  {
351  if(inside)
352  {
353  parser.skip_spaces(true);
354  if(is_ident_start(parser.peek_char()))
355  {
356  const int line = parser.get_line();
357  const int column = parser.get_column();
358  const std::string ident = parser.read_ident();
359  if(ident == "ifdef")
360  {
361  r.emplace_back(TokenType::if_def, line, column);
362  }
363  else if(ident == "end")
364  {
365  r.emplace_back(TokenType::end, line, column);
366  }
367  else if(ident == "eval")
368  {
369  r.emplace_back(TokenType::eval, line, column);
370  }
371  else if(ident == "set")
372  {
373  r.emplace_back(TokenType::set, line, column);
374  }
375  else if(ident == "include")
376  {
377  r.emplace_back(TokenType::include, line, column);
378  }
379  else
380  {
381  r.emplace_back(TokenType::ident, line, column, ident);
382  }
383  }
384  else if(parser.peek_char() == '@')
385  {
386  const int line = parser.get_line();
387  const int column = parser.get_column();
388  parser.advance_char();
389  r.emplace_back(TokenType::eval, line, column);
390  }
391  else if(parser.peek_char() == '\"')
392  {
393  const int line = parser.get_line();
394  const int column = parser.get_column();
395  const std::string& str = parser.read_string();
396  r.emplace_back(TokenType::string, line, column, str);
397  }
398  else if(parser.peek_char(0) == '}' && parser.peek_char(1) == '}')
399  {
400  parser.advance_char();
401  parser.advance_char();
402  inside = false;
403  buffer.clear();
404  buffer_line = parser.get_line();
405  buffer_column = parser.get_column();
406  }
407  else
408  {
409  // parser error
410  return {};
411  }
412  }
413  else
414  {
415  // outside of the {{ }}
416  if(parser.peek_char(0) == '{' && parser.peek_char(1) == '{')
417  {
418  const std::string b = buffer.to_string();
419  buffer.clear();
420  if(!b.empty())
421  {
422  buffer_line = parser.get_line();
423  buffer_column = parser.get_column();
424  r.emplace_back(TokenType::text, buffer_line, buffer_column, b);
425  }
426  parser.advance_char();
427  parser.advance_char();
428  parser.skip_spaces(true);
429  inside = true;
430  }
431  else
432  {
433  buffer.add_char(parser.read_char());
434  }
435  }
436  }
437 
438  if(inside)
439  {
440  error->add_error( file, parser.get_line(), parser.get_column(), "Expected end marker }}");
441  }
442 
443  const std::string buffer_str = buffer.to_string();
444  if(!buffer_str.empty())
445  {
446  r.emplace_back(TokenType::text, buffer_line, buffer_column, buffer_str);
447  }
448 
449  return r;
450 }
451 
452 
454 
455 
457 {
458  std::vector<Token> tokens;
459  std::size_t next_token_index = 0;
460  std::size_t size;
461 
462  explicit TokenReader(const std::vector<Token>& input)
463  : tokens{input}
464  , size{input.size()}
465  {
466  }
467 
468  void advance()
469  {
470  next_token_index += 1;
471  }
472 
473  const Token& read()
474  {
475  const Token& lex = peek();
476  advance();
477  return lex;
478  }
479 
480  [[nodiscard]] bool has_more() const
481  {
482  return next_token_index < size;
483  }
484 
485  [[nodiscard]] const Token& peek() const
486  {
487  if(has_more())
488  {
489  return tokens[next_token_index];
490  }
491  static const Token end_of_file {TokenType::end_of_file, 0, 0};
492  return end_of_file;
493  }
494 
495  [[nodiscard]] int get_line() const
496  {
497  return peek().line;
498  }
499 
500  [[nodiscard]] int get_column() const
501  {
502  return peek().column;
503  }
504 };
505 
506 
508 
509 
511 (
512  std::shared_ptr<TemplateNodeList>* nodes,
513  TokenReader* reader,
514  TemplateErrorList* errors,
515  const io::FilePath& file,
516  bool expect_end,
517  io::FileSystem* fs
518 );
519 
520 
522 (
523  io::FileSystem* fs,
524  const io::FilePath& path,
526  std::shared_ptr<TemplateNodeList>* nodes
527 )
528 {
529  if(fs == nullptr)
530  {
531  error->add_error
532  (
533  path,
534  0,
535  0,
536  fmt::format("Missing filesystem, Failed to read {}", path)
537  );
538  return;
539  }
540  ASSERT(nodes);
541  auto content = fs->read_file_to_string(path);
542  if(!content)
543  {
544  error->add_error(path, 0, 0, fmt::format("Failed to open {}", path));
545  return;
546  }
547  auto reader = TokenReader{tokenize(*content, error, path)};
548  parse_template_list(nodes, &reader, error, path, false, fs);
549 }
550 
551 
552 // ------------------------------------------------------------------------
553 
554 
555 std::shared_ptr<TemplateNodeString> parse_text
556 (
557  TokenReader* reader,
558  TemplateErrorList* /*unused*/,
559  const io::FilePath& /*unused*/,
560  io::FileSystem* /*unused*/
561 )
562 {
563  ASSERT(reader);
564  const Token& lex = reader->read();
565  ASSERT(lex.type == TokenType::text);
566 
567 
568  std::shared_ptr<TemplateNodeString> ret {new TemplateNodeString {lex.value}};
569  return ret;
570 }
571 
572 
573 std::shared_ptr<TemplateNodeEval> parse_eval
574 (
575  TokenReader* reader,
576  TemplateErrorList* errors,
577  const io::FilePath& file,
578  io::FileSystem* /*unused*/
579 )
580 {
581  ASSERT(reader);
582  const Token& lex = reader->read();
583 
584  if(lex.type == TokenType::ident)
585  {
586  std::shared_ptr<TemplateNodeEval> ret{new TemplateNodeEval{lex.value}};
587  return ret;
588  }
589 
590  errors->add_error
591  (
592  file,
593  reader->get_line(),
594  reader->get_column(),
595  fmt::format("Reading EVAL, expected IDENT but found {}", lex.to_string())
596  );
597  std::shared_ptr<TemplateNodeEval> ret{new TemplateNodeEval{"parse_error"}};
598  return ret;
599 }
600 
601 
602 std::shared_ptr<TemplateNodeSet> parse_set
603 (
604  TokenReader* reader,
605  TemplateErrorList* errors,
606  const io::FilePath& file,
607  io::FileSystem* /*unused*/
608 )
609 {
610  ASSERT(reader);
611  const Token& name = reader->read();
612 
613  if(name.type != TokenType::ident)
614  {
615  errors->add_error
616  (
617  file,
618  reader->get_line(),
619  reader->get_column(),
620  fmt::format("Reading SET, expected IDENT but found {}", name.to_string())
621  );
622  std::shared_ptr<TemplateNodeSet> ret{new TemplateNodeSet{"parse_error", "parse_error"}};
623  return ret;
624  }
625 
626  const Token& val = reader->read();
627 
628  if(val.type != TokenType::string)
629  {
630  errors->add_error
631  (
632  file,
633  reader->get_line(),
634  reader->get_column(),
635  fmt::format("Reading SET, expected STRING but found {}", val.to_string())
636  );
637  std::shared_ptr<TemplateNodeSet> ret{new TemplateNodeSet{name.value, "parse_error"}};
638  return ret;
639  }
640 
641  std::shared_ptr<TemplateNodeSet> ret {new TemplateNodeSet {name.value, val.value}};
642  return ret;
643 }
644 
645 
646 std::shared_ptr<TemplateNodeIfdef> parse_ifdef
647 (
648  TokenReader* reader,
649  TemplateErrorList* errors,
650  const io::FilePath& file,
651  io::FileSystem* fs
652 )
653 {
654  ASSERT(reader);
655  const Token& lex = reader->read();
656 
657  if(lex.type == TokenType::ident)
658  {
659  std::shared_ptr<TemplateNodeList> children{new TemplateNodeList{}};
660  parse_template_list(&children, reader, errors, file, true, fs);
661  std::shared_ptr<TemplateNodeIfdef> ret{new TemplateNodeIfdef {lex.value, children}};
662  return ret;
663  }
664  errors->add_error
665  (
666  file,
667  reader->get_line(),
668  reader->get_column(),
669  fmt::format("Reading IFDEF, expected IDENT but found {}", lex.to_string())
670  );
671  std::shared_ptr<TemplateNodeString> dummy { new TemplateNodeString {"parse_error"}};
672  std::shared_ptr<TemplateNodeIfdef> ret { new TemplateNodeIfdef {"parse_error", dummy}};
673  return ret;
674 }
675 
676 
677 std::shared_ptr<TemplateNodeList> parse_include
678 (
679  TokenReader* reader,
680  TemplateErrorList* errors,
681  const io::FilePath& file,
682  io::FileSystem* fs
683 )
684 {
685  ASSERT(reader);
686  const Token& lex = reader->read();
687 
688  if(lex.type == TokenType::string)
689  {
690  std::shared_ptr<TemplateNodeList> ret
691  {
693  };
694  const auto file_argument = io::FilePath::from_script(lex.value);
695  if(file_argument.has_value() == false)
696  {
697  errors->add_error
698  (
699  file,
700  reader->get_line(),
701  reader->get_column(),
702  fmt::format("Invalid path {}", lex.value)
703  );
704  return ret;
705  }
706  const auto resolved_file = io::resolve_relative
707  (
708  file_argument.value(),
709  file.get_directory()
710  );
711  if(resolved_file.has_value())
712  {
714  (
715  fs,
716  resolved_file.value(),
717  errors,
718  &ret
719  );
720  }
721  else
722  {
723  errors->add_error
724  (
725  file,
726  reader->get_line(),
727  reader->get_column(),
728  fmt::format("Unable to open {}", file_argument.value())
729  );
730  }
731 
732  return ret;
733  }
734  errors->add_error
735  (
736  file,
737  reader->get_line(),
738  reader->get_column(),
739  fmt::format("Reading INCLUDE, expected STRING but found {}", lex.to_string())
740  );
741  std::shared_ptr<TemplateNodeList> ret {new TemplateNodeList {}};
742  return ret;
743 }
744 
745 
747 (
748  std::shared_ptr<TemplateNodeList>* nodes,
749  TokenReader* reader,
750  TemplateErrorList* errors,
751  const io::FilePath& file,
752  bool expect_end,
753  io::FileSystem* fs
754 )
755 {
756  ASSERT(nodes);
757  std::shared_ptr<TemplateNodeList>& list = *nodes;
758 
759  ASSERT(reader);
760  while(!errors->has_errors() && reader->has_more() && (!expect_end || reader->peek().type != TokenType::end))
761  {
762  switch(reader->peek().type)
763  {
764  case TokenType::text:
765  list->add(parse_text(reader, errors, file, fs));
766  break;
767  case TokenType::eval:
768  reader->advance();
769  list->add(parse_eval(reader, errors, file, fs));
770  break;
771  case TokenType::if_def:
772  reader->advance();
773  list->add(parse_ifdef(reader, errors, file, fs));
774  break;
775  case TokenType::set:
776  reader->advance();
777  list->add(parse_set(reader, errors, file, fs));
778  break;
779  case TokenType::include:
780  reader->advance();
781  list->add(parse_include(reader, errors, file, fs));
782  break;
783  default:
784  errors->add_error
785  (
786  file,
787  reader->get_line(),
788  reader->get_column(),
789  fmt::format("Reading LIST {}, Found unexpected {}", expect_end, reader->peek().to_string())
790  );
791  return;
792  }
793  }
794 
795  if(errors->has_errors())
796  {
797  return;
798  }
799 
800  if(expect_end)
801  {
802  auto end = reader->read(); // skip end
803  if(end.type != TokenType::end)
804  {
805  errors->add_error
806  (
807  file,
808  reader->get_line(),
809  reader->get_column(),
810  fmt::format("Reading LIST, expected END but found {}", end.to_string())
811  );
812  }
813  }
814 }
815 
816 
818 
819 
821  : nodes(new TemplateNodeList {})
822 {
823  const auto file = io::FilePath{"~/from_string"};
824  auto reader = TokenReader{tokenize(text, &errors, file)};
825  parse_template_list(&nodes, &reader, &errors, file, false, nullptr);
826 }
827 
828 
830  : nodes(new TemplateNodeList {})
831 {
832  ASSERT(fs);
834 }
835 
836 
838 
839 
841 {
842  StringBuilder ss;
843 
844  if(errors.has_errors())
845  {
846  return "";
847  }
848 
849  if(nodes)
850  {
851  core::Defines my_defines = defines;
852  nodes->build_string(&my_defines, &ss, &errors);
853  }
854 
855  return ss.to_string();
856 }
857 
858 
859 }
#define ASSERT(x)
Definition: assert.h:29
std::string get_first_chars_with_ellipsis(const std::string &str, unsigned int count)
Definition: stringutils.cc:40
constexpr ParseResult error
no error occurred
Definition: argparse.h:73
std::shared_ptr< TemplateNodeEval > parse_eval(TokenReader *reader, TemplateErrorList *errors, const io::FilePath &file, io::FileSystem *)
std::string to_string(const EnumValue &v)
Definition: enum.cc:175
std::vector< Token > tokenize(const std::string &content, TemplateErrorList *error, const io::FilePath &file)
std::shared_ptr< TemplateNodeIfdef > parse_ifdef(TokenReader *reader, TemplateErrorList *errors, const io::FilePath &file, io::FileSystem *fs)
std::shared_ptr< TemplateNodeList > parse_include(TokenReader *reader, TemplateErrorList *errors, const io::FilePath &file, io::FileSystem *fs)
bool is_ident_start(char c)
std::shared_ptr< TemplateNodeSet > parse_set(TokenReader *reader, TemplateErrorList *errors, const io::FilePath &file, io::FileSystem *)
void load_from_filesystem_to_node_list(io::FileSystem *fs, const io::FilePath &path, TemplateErrorList *error, std::shared_ptr< TemplateNodeList > *nodes)
std::shared_ptr< TemplateNodeString > parse_text(TokenReader *reader, TemplateErrorList *, const io::FilePath &, io::FileSystem *)
void parse_template_list(std::shared_ptr< TemplateNodeList > *nodes, TokenReader *reader, TemplateErrorList *errors, const io::FilePath &file, bool expect_end, io::FileSystem *fs)
std::optional< DirPath > resolve_relative(const DirPath &base)
Definition: vfs_path.cc:349
int line
Definition: nlp_sentence.cc:91
std::string buffer
Definition: nlp_sentence.cc:87
String utility functions.
std::string to_string()
Complete the builder and return the resulting string.
StringBuilder & add_string(const std::string &str)
StringBuilder & add_char(char c)
std::string build_string(const Defines &defines)
std::shared_ptr< TemplateNodeList > nodes
Definition: texttemplate.h:54
CompiledTextTemplate(const std::string &text)
std::map< std::string, std::string > values
Definition: texttemplate.h:19
void remove(const std::string &name)
Definition: texttemplate.cc:46
std::string get_value(const std::string &name) const
Definition: texttemplate.cc:33
bool is_defined(const std::string &name) const
Definition: texttemplate.cc:25
void set(const std::string &name, const std::string &value)
Definition: texttemplate.cc:53
std::vector< std::string > errors
Definition: texttemplate.h:32
void add_error(const std::optional< io::FilePath > &file, int line, int column, const std::string &error)
Definition: texttemplate.cc:74
std::string get_combined_errors() const
Definition: texttemplate.cc:92
TemplateNodeEval(std::string n)
void build_string(Defines *defines, StringBuilder *out, TemplateErrorList *error) override
void build_string(Defines *defines, StringBuilder *out, TemplateErrorList *error) override
std::shared_ptr< TemplateNode > node
TemplateNodeIfdef(std::string aname, std::shared_ptr< TemplateNode > anode)
void build_string(Defines *defines, StringBuilder *out, TemplateErrorList *error) override
std::vector< std::shared_ptr< TemplateNode > > nodes
void add(const std::shared_ptr< TemplateNode > &node)
void build_string(core::Defines *defines, StringBuilder *out, TemplateErrorList *error) override
TemplateNodeSet(std::string aname, std::string avalue)
void build_string(Defines *defines, StringBuilder *out, TemplateErrorList *) override
void build_string(Defines *, StringBuilder *out, TemplateErrorList *) override
TemplateNodeString(std::string t)
void operator=(const TemplateNode &)=delete
virtual void build_string(Defines *defines, StringBuilder *out, TemplateErrorList *error)=0
void operator=(TemplateNode &&)=delete
TemplateNode(const TemplateNode &)=delete
TemplateNode(TemplateNode &&)=delete
virtual ~TemplateNode()=default
static TextfileParser from_string(const std::string &str)
std::size_t next_token_index
const Token & peek() const
const Token & read()
std::vector< Token > tokens
TokenReader(const std::vector< Token > &input)
Token(TokenType t, int l, int c, std::string v="")
std::string value
std::string to_string() const
DirPath get_directory() const
Definition: vfs_path.cc:137
static std::optional< FilePath > from_script(const std::string &path)
apply only minor changes, return null on invalid
Definition: vfs_path.cc:46
std::optional< std::string > read_file_to_string(const FilePath &path)
Definition: vfs.cc:171
#define CASE(V)
UiText * nodes
Definition: ui_text.cc:132