#include <cstring>
#include <filesystem>
#include <format>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include "libfut.hpp"
static std::string slurp(std::ifstream &stream)
{
std::ostringstream oss;
oss << stream.rdbuf();
return oss.str();
}
class FileGenHost : public FuConsoleHost
{
std::vector<const char *> resourceDirs;
std::string outputDirectory;
std::string currentFilename;
std::ofstream currentFile;
void readResource(std::string_view name, const FuPrefixExpr *expr, std::vector<uint8_t> &content)
{
std::ifstream stream;
for (const char *dir : resourceDirs) {
stream.open(std::filesystem::path(dir) / name, std::ios_base::binary);
if (stream) {
std::string input = slurp(stream);
content.assign(input.begin(), input.end());
return;
}
}
stream.open(std::string{name}, std::ios_base::binary);
if (stream) {
std::string input = slurp(stream);
content.assign(input.begin(), input.end());
}
else
reportStatementError(expr, std::format("File '{}' not found", name));
}
protected:
std::string_view toDirectory(std::string_view path) override
{
if (std::filesystem::is_directory(path))
return path;
outputDirectory = std::filesystem::path(path).parent_path().string();
return outputDirectory;
}
public:
void addResourceDir(const char *path)
{
resourceDirs.push_back(path);
}
int getResourceLength(std::string_view name, const FuPrefixExpr *expr) override
{
auto p = getResources()->try_emplace(std::string(name));
std::vector<uint8_t> &content = p.first->second;
if (p.second)
readResource(name, expr, content);
return content.size();
}
std::ostream *createFile(std::string_view directory, std::string_view filename) override
{
currentFilename = directory.empty() ? filename : (std::filesystem::path(directory) / filename).string();
currentFile.open(currentFilename, std::ios_base::binary);
return ¤tFile;
}
void closeFile() override
{
currentFile.close();
if (hasErrors())
std::remove(currentFilename.c_str());
else if (!currentFile) {
std::cerr << currentFilename << ": ERROR: " << strerror(errno) << '\n';
setErrors(true);
}
}
};
static bool parseAndResolve(FuParser *parser, const std::vector<const char *> &files, FuSema *sema, FuConsoleHost *host)
{
for (const char *file : files) {
std::ifstream stream(file);
std::string input = slurp(stream);
if (!stream) {
std::cerr << file << ": ERROR: " << strerror(errno) << '\n';
return false;
}
parser->parse(file, reinterpret_cast<const uint8_t *>(input.data()), input.size());
}
if (host->hasErrors())
return false;
sema->process();
return !host->hasErrors();
}
int main(int argc, char **argv)
{
FileGenHost host;
FuParser parser;
std::vector<const char *> inputFiles;
std::vector<const char *> referencedFiles;
const char *lang = nullptr;
const char *outputFile = nullptr;
const char *namespace_ = "";
for (int i = 1; i < argc; i++) {
const char *arg = argv[i];
if (arg[0] != '-')
inputFiles.push_back(arg);
else if (strcmp(arg, "--help") == 0) {
FuConsoleHost::usage("fut");
return 0;
}
else if (strcmp(arg, "--version") == 0) {
std::cout << "Fusion Transpiler " << FuConsoleHost::version << " (C++)\n";
return 0;
}
else if (arg[1] != '\0' && arg[2] == '\0' && i + 1 < argc) {
switch (arg[1]) {
case 'l':
lang = argv[++i];
break;
case 'o':
outputFile = argv[++i];
break;
case 'n':
namespace_ = argv[++i];
break;
case 'D': {
const char *symbol = argv[++i];
if (strcmp(symbol, "true") == 0 || strcmp(symbol, "false") == 0) {
std::cerr << "fut: '" << symbol << "' is reserved\n";
return 1;
}
parser.addPreSymbol(symbol);
}
break;
case 'r':
referencedFiles.push_back(argv[++i]);
break;
case 'I':
host.addResourceDir(argv[++i]);
break;
default:
std::cerr << "fut: ERROR: Unknown option: " << arg << '\n';
return 1;
}
}
else {
std::cerr << "fut: ERROR: Unknown option: " << arg << '\n';
return 1;
}
}
if (outputFile == nullptr || inputFiles.empty()) {
FuConsoleHost::usage("fut");
return 1;
}
FuSema sema;
parser.setHost(&host);
sema.setHost(&host);
std::shared_ptr<FuSystem> system = FuSystem::new_();
FuScope *parent = system.get();
FuProgram references;
if (!referencedFiles.empty()) {
references.init(parent, system.get(), &host);
if (!parseAndResolve(&parser, referencedFiles, &sema, &host))
return 1;
parent = &references;
}
FuProgram program;
program.init(parent, system.get(), &host);
if (!parseAndResolve(&parser, inputFiles, &sema, &host))
return 1;
if (lang != nullptr) {
host.emit(&program, lang, namespace_, outputFile);
return host.hasErrors() ? 1 : 0;
}
for (ptrdiff_t i = strlen(outputFile); --i >= 0; ) {
char c = outputFile[i];
if (c == '.') {
if (i >= 2
&& (outputFile[i - 2] == '.' || outputFile[i - 2] == ',')
&& strncmp(outputFile + i - 1, "d.ts", 4) == 0
&& (outputFile[i + 3] == '\0' || outputFile[i + 3] == ','))
continue;
const char *extBegin = outputFile + i + 1;
std::string outputBase { outputFile, extBegin };
int exitCode = 0;
for (;;) {
const char *extEnd = strchr(extBegin, ',');
if (extEnd == nullptr)
break;
std::string outputExt { extBegin, extEnd };
host.emit(&program, outputExt, namespace_, outputBase + outputExt);
if (host.hasErrors()) {
host.setErrors(false);
exitCode = 1;
}
extBegin = extEnd + 1;
}
host.emit(&program, extBegin, namespace_, outputBase + extBegin);
return host.hasErrors() ? 1 : exitCode;
}
if (c == '/' || c == '\\' || c == ':')
break;
}
std::cerr << "fut: ERROR: Don't know what language to translate to: no extension in '" << outputFile << "' and no '-l' option\n";
return 1;
}