Skip to content

Commit

Permalink
Implement "tmbasic run" CLI command
Browse files Browse the repository at this point in the history
  • Loading branch information
electroly committed Jan 23, 2024
1 parent 5662c90 commit b97a7a5
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 60 deletions.
63 changes: 63 additions & 0 deletions src/compiler/CompiledProgram.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
#include "compiler/CompiledProgram.h"
#include "compiler/makeExeFile.h"
#include "shared/serializedProgramConstants.h"
#include "compiler/TargetPlatform.h"
#include "shared/filesystem.h"

#ifndef _WIN32
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#endif

namespace compiler {

Expand Down Expand Up @@ -59,4 +68,58 @@ std::vector<uint8_t> CompiledProgram::serialize() const {
return writer.vec;
}

static std::string getTempExeFilename(TargetPlatform platform) {
#ifdef _WIN32
auto pid = static_cast<int64_t>(GetCurrentProcessId());
#else
auto pid = static_cast<int64_t>(getpid());
#endif
return fmt::format("tmbasic-debug-{}{}", pid, compiler::getPlatformExeExtension(platform));
}

void CompiledProgram::run() const {
// Write a temporary EXE file
auto pcode = serialize();
auto nativePlatform = compiler::getNativeTargetPlatform();
auto exeData = compiler::makeExeFile(pcode, nativePlatform);
auto tempFilePath = shared::getTempFilePath(getTempExeFilename(nativePlatform));
std::ofstream f{ tempFilePath, std::ios::out | std::ios::binary };
f.write(reinterpret_cast<const char*>(exeData.data()), static_cast<std::streamsize>(exeData.size()));
f.close();
#ifndef _WIN32
chmod(tempFilePath.c_str(), 0777);
#endif

// Execute it.
#ifdef _WIN32
// Shell is always available in Windows, so just use std::system().
auto args = fmt::format("\"{}\"", tempFilePath);
std::system(args.c_str());
#else
// Shell might NOT be available in Linux, so use fork/exec.
// Consider a chroot environment containing tmbasic and nothing else.
auto pid = fork();
if (pid == 0) {
// Child process. Use exec.
execl(tempFilePath.c_str(), tempFilePath.c_str(), nullptr);

// If it was successful, then execution will never reach this point.
// If we're here, then it failed.
std::cerr << "Failed to start the program: " << strerror(errno) << std::endl;

// ChatGPT says it's important to use _exit and not exit here.
_exit(EXIT_FAILURE);
} else if (pid > 0) {
// Parent process. Wait for the child process to exit.
int status{};
waitpid(pid, &status, 0);
} else {
std::cout << "Failed to fork a new process: " << strerror(errno) << std::endl;
}
#endif

// Delete the temp file
shared::deleteFile(tempFilePath);
}

} // namespace compiler
1 change: 1 addition & 0 deletions src/compiler/CompiledProgram.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class CompiledProgram {
std::unordered_map<size_t, CompiledUserType*> userTypesBySourceMemberIndex;

std::vector<uint8_t> serialize() const;
void run() const;
};

} // namespace compiler
61 changes: 1 addition & 60 deletions src/tmbasic/ProgramWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
#include "../../obj/resources/help/helpfile.h"
#include "compiler/compileProgram.h"
#include "compiler/CompilerException.h"
#include "compiler/makeExeFile.h"
#include "compiler/Publisher.h"
#include "compiler/TargetPlatform.h"
#include "shared/DialogPtr.h"
#include "shared/filesystem.h"
#include "shared/Frame.h"
#include "shared/Label.h"
#include "shared/ListViewer.h"
Expand All @@ -24,12 +22,6 @@
#include "vm/Interpreter.h"
#include "vm/Program.h"

#ifndef _WIN32
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#endif

using compiler::SourceMember;
using compiler::SourceMemberType;
using compiler::SourceProgram;
Expand Down Expand Up @@ -486,15 +478,6 @@ void ProgramWindow::publish() {
}
}

static std::string getTempExeFilename(TargetPlatform platform) {
#ifdef _WIN32
auto pid = static_cast<int64_t>(GetCurrentProcessId());
#else
auto pid = static_cast<int64_t>(getpid());
#endif
return fmt::format("tmbasic-debug-{}{}", pid, compiler::getPlatformExeExtension(platform));
}

void ProgramWindow::run() {
compiler::CompiledProgram program;
try {
Expand All @@ -504,50 +487,8 @@ void ProgramWindow::run() {
return;
}

// Write a temporary EXE file
auto pcode = program.serialize();
auto nativePlatform = compiler::getNativeTargetPlatform();
auto exeData = compiler::makeExeFile(pcode, nativePlatform);
auto tempFilePath = shared::getTempFilePath(getTempExeFilename(nativePlatform));
std::ofstream f{ tempFilePath, std::ios::out | std::ios::binary };
f.write(reinterpret_cast<const char*>(exeData.data()), static_cast<std::streamsize>(exeData.size()));
f.close();
#ifndef _WIN32
chmod(tempFilePath.c_str(), 0777);
#endif

// Execute it.
TProgram::application->suspend();

#ifdef _WIN32
// Shell is always available in Windows, so just use std::system().
auto args = fmt::format("\"{}\"", tempFilePath);
std::system(args.c_str());
#else
// Shell might NOT be available in Linux, so use fork/exec.
// Consider a chroot environment containing tmbasic and nothing else.
auto pid = fork();
if (pid == 0) {
// Child process. Use exec.
execl(tempFilePath.c_str(), tempFilePath.c_str(), nullptr);

// If it was successful, then execution will never reach this point.
// If we're here, then it failed.
std::cerr << "Failed to start the program: " << strerror(errno) << std::endl;

// ChatGPT says it's important to use _exit and not exit here.
_exit(EXIT_FAILURE);
} else if (pid > 0) {
// Parent process. Wait for the child process to exit.
int status{};
waitpid(pid, &status, 0);
} else {
std::cout << "Failed to fork a new process: " << strerror(errno) << std::endl;
}
#endif

// Delete the temp file
shared::deleteFile(tempFilePath);
program.run();

std::cout << "\nPress Enter to return to TMBASIC." << std::endl;
std::cin.get();
Expand Down

0 comments on commit b97a7a5

Please sign in to comment.