text: // main.cpp
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include "../../source/core/slang-secure-crt.h"
  
  #include "../../source/core/slang-list.h"
  #include "../../source/core/slang-string.h"
  #include "../../source/core/slang-string-util.h"
  
  using namespace Slang;
  
  typedef Slang::UnownedStringSlice StringSpan;
  
  struct Node
  {
      enum class Flavor
      {
          text,   // Ordinary text to write to output
          escape, // Meta-level code (statements)
          splice, // Meta-level expression to splice into output
      };
  
      // What sort of node is this?
      Flavor      flavor;
  
      // The text of this node for `Flavor::text`
      StringSpan  span;
  
      // The body of this node for other flavors
      Node*       body;
  
      // The next node in the document
      Node*       next;
  };
  
  // Information about a source file
  struct SourceFile
  {
      char const* inputPath;
      StringSpan  text;
      Node*       node;
  };
  
  void addNode(
      Node**&         ioLink,
      Node::Flavor    flavor,
      char const*     spanBegin,
      char const*     spanEnd)
  {
      Node* node = new Node();
      node->flavor = flavor;
      node->span = StringSpan(spanBegin, spanEnd);
      node->next = nullptr;
  
      *ioLink = node;
      ioLink = &node->next;
  }
  
  void addNode(
      Node**&         ioLink,
      Node::Flavor    flavor,
      Node*           body)
  {
      Node* node = new Node();
      node->flavor = flavor;
      node->body = body;
      node->next = nullptr;
  
      *ioLink = node;
      ioLink = &node->next;
  }
  
  bool isAlpha(int c)
  {
      return ((c >= 'a') && (c <= 'z'))
          || ((c >= 'A') && (c <= 'Z'))
          || (c == '_');
  }
  
  void addTextSpan(
      Node**&         ioLink,
      char const*     spanBegin,
      char const*     spanEnd)
  {
      // Don't add an empty text span.
      if (spanBegin == spanEnd)
          return;
  
      addNode(ioLink, Node::Flavor::text, spanBegin, spanEnd);
  }
  
  void addSpliceSpan(
      Node**&         ioLink,
      Node*           body)
  {
      addNode(ioLink, Node::Flavor::splice, body);
  }
  
  void addEscapeSpan(
      Node**&         ioLink,
      Node*           body)
  {
      addNode(ioLink, Node::Flavor::escape, body);
  }
  
  void addEscapeSpan(
      Node**&         ioLink,
      char const*     spanBegin,
      char const*     spanEnd)
  {
      Node* body = nullptr;
      Node** link = &body;
  
      addTextSpan(link, spanBegin, spanEnd);
  
      return addEscapeSpan(ioLink, body);
  }
  
  bool isIdentifierChar(int c)
  {
      if (c >= 'a' && c <= 'z')   return true;
      if (c >= 'A' && c <= 'Z')   return true;
      if (c == '_')               return true;
  
      return false;
  
  }
  
  struct Reader
  {
      char const* cursor;
      char const* end;
  };
  
  int peek(Reader const& reader)
  {
      if (reader.cursor == reader.end)
          return EOF;
  
      return *reader.cursor;
  }
  
  int get(Reader& reader)
  {
      if (reader.cursor == reader.end)
          return -1;
  
      return *reader.cursor++;
  }
  
  void handleNewline(Reader& reader, int c)
  {
      int d = peek(reader);
      if ((c ^ d) == ('\r' ^ '\n'))
      {
          get(reader);
      }
  }
  
  bool isHorizontalSpace(int c)
  {
      return (c == ' ') || (c == '\t');
  }
  
  void skipHorizontalSpace(Reader& reader)
  {
      while (isHorizontalSpace(peek(reader)))
          get(reader);
  }
  
  void skipOptionalNewline(Reader& reader)
  {
      switch (peek(reader))
      {
      default:
          break;
  
      case '\r': case '\n':
          {
              int c = get(reader);
              handleNewline(reader, c);
          }
          break;
      }
  }
  
  typedef unsigned int NodeReadFlags;
  enum
  {
      kNodeReadFlag_AllowEscape = 1 << 0,
  };
  
  Node* readBody(
      Reader&         reader,
      NodeReadFlags   flags,
      char            openChar,
      int             openCount,
      char            closeChar)
  {
      while (peek(reader) == openChar)
      {
          get(reader);
          openCount++;
      }
  
      Node* nodes = nullptr;
      Node** link = &nodes;
  
      bool atStartOfLine = true;
      int depth = 0;
  
      char const* spanBegin = reader.cursor;
      char const* lineBegin = reader.cursor;
      for (;;)
      {
          int c = get(reader);
  
          switch (c)
          {
          default:
              atStartOfLine = false;
              break;
  
          case EOF:
              {
                  addTextSpan(link, spanBegin, reader.cursor);
                  return nodes;
              }
  
          case '{': case '(':
              if (c == openChar)
              {
                  depth++;
              }
              atStartOfLine = false;
              break;
  
          case ')': case '}':
              if (c == closeChar)
              {
                  char const* spanEnd = reader.cursor - 1;
  
                  if (openCount == 1)
                  {
                      if (depth == 0)
                      {
                          // We are at the end of the body.
                          addTextSpan(link, spanBegin, spanEnd);
                          return nodes;
                      }
  
                      depth--;
                  }
                  else
                  {
                      // Count how many closing chars are stacked up
  
                      int closeCount = 1;
                      while (peek(reader) == closeChar)
                      {
                          get(reader);
                          closeCount++;
                      }
  
                      if (closeCount == openCount)
                      {
                          // We are at the end of the body.
                          addTextSpan(link, spanBegin, spanEnd);
                          return nodes;
                      }
                  }
              }
              atStartOfLine = false;
              break;
  
  
          case ' ': case '\t':
              break;
  
          case '\r': case '\n':
              {
                  addTextSpan(link, spanBegin, reader.cursor);
  
                  handleNewline(reader, c);
  
                  lineBegin = reader.cursor;
                  spanBegin = reader.cursor;
                  atStartOfLine = true;
              }
              break;
  
          case '$':
              {
                  // If this is the start of a splice, then
                  // the end of the preceding raw-text space
                  // will be the byte before `$`
                  char const* spanEnd = reader.cursor - 1;
  
                  if (peek(reader) == '(')
                  {
                      // This appears to be an expression splice.
                      //
                      // We must end the preceding span.
                      //
                      addTextSpan(link, spanBegin, spanEnd);
  
                      Node* body = readBody(
                          reader,
                          0,
                          '(',
                          0,
                          ')');
  
                      addSpliceSpan(link, body);
  
                      spanBegin = reader.cursor;
                      atStartOfLine = false;
                  }
                  else if (peek(reader) == '{')
                  {
                      // This is the start of a block-structured escape, which will
                      // end at a matching `}`.
  
                      addTextSpan(link, spanBegin, lineBegin);
  
                      Node* body = readBody(
                          reader,
                          0,
                          '{',
                          0,
                          '}');
  
                      addEscapeSpan(link, body);
  
                      spanBegin = reader.cursor;
                      atStartOfLine = false;
                  }
                  else if (atStartOfLine && peek(reader) == ':')
                  {
                      // This is a statement escape, which will
                      // continue to the end of the line.
                      //
                      // The spliced text begins *after* the `:`
                      get(reader);
                      char const* spliceBegin = reader.cursor;
  
                      // The preceding text span will end at the
                      // start of this line.
                      addTextSpan(link, spanBegin, lineBegin);
  
                      // Any indentation on this line will be ignored.
  
                      // Read up to end of line.
                      for (;;)
                      {
                          int c = get(reader);
                          switch (c)
                          {
                          default:
                              continue;
  
                          case EOF:
                              break;
  
                          case '\r':
                          case '\n':
                              handleNewline(reader, c);
                              break;
                          }
  
                          break;
                      }
  
                      addEscapeSpan(link, spliceBegin, reader.cursor);
  
                      spanBegin = reader.cursor;
                      lineBegin = reader.cursor;
                  }
                  else if (atStartOfLine && isIdentifierChar(peek(reader)))
                  {
                      // This is a statement splice, which will use a {}-enclosed
                      // body for the template to generate.
  
                      // Consume an optional identifier
                      while (isIdentifierChar(peek(reader)))
                          get(reader);
  
                      // Consume optional horizontal space
                      skipHorizontalSpace(reader);
  
                      // Consume an optional `()`-enclosed block (strip
                      // all but the outer-most `()`.
  
                      // optional space/newline/space before `{`
                      skipHorizontalSpace(reader);
                      skipOptionalNewline(reader);
                      skipHorizontalSpace(reader);
  
                      throw 99;
                  }
                  else
                  {
                      // Doesn't seem to be a splice at all, just
                      // a literal `$` in the output.
                      atStartOfLine = false;
                  }
              }
              break;
          }
      }
  
  }
  
  Node* readInput(
      char const*     inputBegin,
      char const*     inputEnd)
  {
      Reader reader;
      reader.cursor = inputBegin;
      reader.end = inputEnd;
  
      return readBody(
          reader,
          kNodeReadFlag_AllowEscape,
          -2,
          0,
          -2);
  }
  
  void emitRaw(
      FILE* stream,
      char const* begin,
      char const* end)
  {
      // We will write the raw text to our output file.
  
      // TODO: need to output `#line` directives as well
  
      fputs("sb << \"", stream);
      for( char const* cc = begin; cc != end; ++cc )
      {
          int c = *cc;
          switch( c )
          {
          case '\\':
              fputs("\\\\", stream);
              break;
  
          case '\r': break;
          case '\t': fputs("\\t", stream); break;
          case '\"': fputs("\\\"", stream); break;
          case '\n':
              fputs("\\n\";\n", stream);
              fputs("sb << \"", stream);
              break;
  
          default:
              if((c >= 32) && (c <= 126))
              {
                  fputc(c, stream);
              }
              else
              {
                  assert(false);
              }
          }
  
      }
      fprintf(stream, "\";\n");
  }
  
  void emitCode(
      FILE*   stream,
      char const* begin,
      char const* end)
  {
      for( auto cc = begin; cc != end; ++cc )
      {
          if(*cc == '\r')
              continue;
  
          fputc(*cc, stream);
      }
  }
  
  void emit(
      FILE*       stream,
      char const* text)
  {
      fprintf(stream, "%s", text);
  }
  
  void emit(
      FILE*       stream,
      StringSpan const&   span)
  {
      fprintf(stream, "%.*s", int(span.end() - span.begin()), span.begin());
  }
  
  bool isASCIIPrintable(int c)
  {
      return (c >= 0x20) && (c <= 0x7E);
  }
  
  void emitStringLiteralText(
      FILE*               stream,
      StringSpan const&   span)
  {
      char const* cursor = span.begin();
      char const* end = span.end();
  
      while (cursor != end)
      {
          int c = *cursor++;
          switch (c)
          {
          case '\r': case '\n':
              fprintf(stream, "\\n");
              break;
  
          case '\t':
              fprintf(stream, "\\t");
              break;
  
          case ' ':
              fprintf(stream, " ");
              break;
  
          case '"':
              fprintf(stream, "\\\"");
              break;
  
          case '\\':
              fprintf(stream, "\\\\");
              break;
  
          default:
              if (isASCIIPrintable(c))
              {
                  fprintf(stream, "%c", c);
              }
              else
              {
                  fprintf(stream, "%03u", c);
              }
              break;
          }
      }
  }
  
  void emitSimpleText(
      FILE*               stream,
      StringSpan const&   span)
  {
      UnownedStringSlice content(span), line;
      while (StringUtil::extractLine(content, line))
      {
          // Write the line
          fwrite(line.begin(), 1, line.size(), stream);
  
          // Specially handle the 'final line', excluding an empty line after \n.
          // We can detect, as if input ends with 'cr/lf' combination, content.begin == span.end(), else if content.begin() == nullptr.
          if (content.begin() == nullptr || content.begin() == span.end())
          {
              break;
          }
  
          fprintf(stream, "\n");
      }
  }
  
  void emitCodeNodes(
      FILE*   stream,
      Node*   node)
  {
      for (auto nn = node; nn; nn = nn->next)
      {
          switch (nn->flavor)
          {
          case Node::Flavor::text:
              emitSimpleText(stream, nn->span);
              emit(stream, "\n");
              break;
  
          default:
              throw "unexpected";
              break;
          }
      }
  }
  
  // Given line starts and a location, find the line number. Returns -1 if not found
  static Index _findLineIndex(const List<UnownedStringSlice>& lineBreaks, const char* location)
  {
      if (location == nullptr)
      {
          return -1;
      }
  
      // Use a binary chop to find the associated line
      Index lo = 0;
      Index hi = lineBreaks.getCount();
  
      while (lo + 1 < hi)
      {
          const auto mid = (hi + lo) >> 1;
          const auto midOffset = lineBreaks[mid].begin();
          if (midOffset <= location)
          {
              lo = mid;
          }
          else
          {
              hi = mid;
          }
      }
  
      return lo;
  }
  
  void emitTemplateNodes(
      SourceFile* sourceFile,
      FILE*   stream,
      Node*   node)
  {
      // Work out
      List<UnownedStringSlice> lineBreaks;
      StringUtil::calcLines(sourceFile->text, lineBreaks);
  
      Node* prev = nullptr;
      for (auto nn = node; nn; prev = nn, nn = nn->next)
      {
          // If we transition from escape to text, insert line number directive
          bool enable = true;
          if (enable && prev && prev->flavor == Node::Flavor::escape && nn->flavor == Node::Flavor::text)
          {
              // Find the line
              Index lineIndex = _findLineIndex(lineBreaks, nn->span.begin());
              // If found, output the directive
              if (lineIndex >= 0)
              {
                  StringBuilder buf;
                  buf << "SLANG_RAW(\"#line " << (lineIndex + 1) << " \\\"" << sourceFile->inputPath << "\\\"\")\n";
  
                  emit(stream, buf.getUnownedSlice());
              }
          }
  
          switch (nn->flavor)
          {
          case Node::Flavor::text:
              emit(stream, "SLANG_RAW(\"");
              emitStringLiteralText(stream, nn->span);
              emit(stream, "\")\n");
              break;
  
          case Node::Flavor::splice:
              emit(stream, "SLANG_SPLICE(");
              emitCodeNodes(stream, nn->body);
              emit(stream, ")\n");
              break;
  
          case Node::Flavor::escape:
              emitCodeNodes(stream, nn->body);
              break;
          }  
      }
  }
  
  void usage(char const* appName)
  {
      fprintf(stderr, "usage: %s <input>\n", appName);
  }
  
  SlangResult readAllText(char const * fileName, String& stringOut)
  {
      FILE * f;
      fopen_s(&f, fileName, "rb");
      if (!f)
      {
          stringOut = "";
          return SLANG_FAIL;
      }
      else
      {
          stringOut = 
          fseek(f, 0, SEEK_END);
          auto size = ftell(f);
  
          StringRepresentation* stringRep = StringRepresentation::createWithCapacityAndLength(size, size);
          stringOut = String(stringRep);
  
          char * buffer = stringRep->getData();
          memset(buffer, 0, size);
          fseek(f, 0, SEEK_SET);
          fread(buffer, sizeof(char), size, f);
          fclose(f);
  
          return SLANG_OK;
      }
  }
  
  void writeAllText(char const *srcFileName, char const* fileName, const char* content)
  {
      FILE * f = nullptr;
      fopen_s(&f, fileName, "wb");
      if (!f)
      {
          printf("%s(0): error G0001: cannot write file %s\n", srcFileName, fileName);
      }
      else
      {
          fwrite(content, 1, strlen(content), f);
          fclose(f);
      }
  }
  
  #define PARSE_HANDLER(NAME) \
      Node* NAME(StringSpan const& text)
  
  typedef PARSE_HANDLER((*ParseHandler));
  
  PARSE_HANDLER(parseTemplateFile)
  {
      // Read a template node!
      return readInput(text.begin(), text.end());
  }
  
  PARSE_HANDLER(parseCxxFile)
  {
      // TODO: "scrape" the source file for metadata
      return nullptr;
  }
  
  PARSE_HANDLER(parseUnknownFile)
  {
      // Don't process files we don't know how to handle.
      return nullptr;
  }
  
  
  
  Node* parseSourceFile(SourceFile* file)
  {
      auto path = file->inputPath;
      auto text = file->text;
  
      static const struct
      {
          char const*     extension;
          ParseHandler    handler;
      } kHandlers[] =
      {
          { ".meta.slang",    &parseTemplateFile },
          { ".meta.cpp",      &parseTemplateFile },
          { ".cpp",           &parseCxxFile },
          { "",               &parseUnknownFile },
      };
  
      for (auto hh : kHandlers)
      {
          if (UnownedTerminatedStringSlice(path).endsWith(hh.extension))
          {
              return hh.handler(text);
          }
      }
  
      return nullptr;
  }
  
  
  
  SourceFile* parseSourceFile(char const* path)
  {
      FILE* inputStream;
      fopen_s(&inputStream, path, "rb");
      fseek(inputStream, 0, SEEK_END);
      size_t inputSize = ftell(inputStream);
      fseek(inputStream, 0, SEEK_SET);
  
      char* input = (char*)malloc(inputSize + 1);
      fread(input, inputSize, 1, inputStream);
      input[inputSize] = 0;
  
      char const* inputEnd = input + inputSize;
      StringSpan span = StringSpan(input, inputEnd);
  
      SourceFile* sourceFile = new SourceFile();
      sourceFile->inputPath = path;
      sourceFile->text = span;
  
      Node* node = parseSourceFile(sourceFile);
  
      sourceFile->node = node;
      return sourceFile;
  }
  
  List<SourceFile*> gSourceFiles;
  
  int main(
      int     argc,
      const char*const*  argv)
  {
      // Parse command-line arguments.
      List<const char*> inputPaths;
      char const* appName = "slang-generate";
  
      {
          const char*const* argCursor = argv;
          const char*const* argEnd = argv + argc;
          // Copy the app name
          if( argCursor != argEnd )
          {
              appName = *argCursor++;
          }
          // Copy the input paths
          for (; argCursor != argEnd; ++argCursor)
          {
              inputPaths.add(*argCursor);
          }
      }
  
      if(inputPaths.getCount() == 0)
      {
          usage(appName);
          exit(1);
      }
  
      // Read each input file and process it according
      // to the type of treatment it requires.
      for (const char* inputPath: inputPaths)
      {
          SourceFile* sourceFile = parseSourceFile(inputPath);
          if (sourceFile)
          {
              gSourceFiles.add(sourceFile);
          }
      }
  
      // Once all inputs have been read, we can start
      // to produce output files by expanding templates.
      for (auto sourceFile : gSourceFiles)
      {
          auto inputPath = sourceFile->inputPath;
          auto node = sourceFile->node;
  
          // write output to a temporary file first
          StringBuilder outputPath;
          outputPath << inputPath << ".temp.h";
  
          FILE* outputStream;
          fopen_s(&outputStream, outputPath.getBuffer(), "w");
  
          emitTemplateNodes(sourceFile, outputStream, node);
  
          fclose(outputStream);
  
          // update final output only when content has changed
          StringBuilder outputPathFinal;
          outputPathFinal << inputPath << ".h";
  
          String allTextOld, allTextNew;
          readAllText(outputPathFinal.getBuffer(), allTextOld);
          readAllText(outputPath.getBuffer(), allTextNew);
          if (allTextOld != allTextNew)
          {
              writeAllText(inputPath, outputPathFinal.getBuffer(), allTextNew.getBuffer());
          }
          remove(outputPath.getBuffer());
      }
  
      return 0;
  }
  