13#include <unordered_set>
20 std::ostringstream stream;
29 for (
size_t i = 0; i < text.length(); ++i) {
31 char next = (i + 1 < text.length()) ? text[i + 1] :
'\0';
36 state = BRACE_COMMENT;
37 }
else if (c ==
'(' && next ==
'*') {
38 state = PAREN_COMMENT;
40 }
else if (c ==
'\'') {
43 }
else if (c ==
'/' && next ==
'/') {
45 while (i < text.length() && text[i] !=
'\n') {
48 if (i < text.length()) {
63 if (c ==
'*' && next ==
')') {
86static bool compileFile(
const std::string &inputPath,
const std::string &outputPath) {
88 std::ifstream file(inputPath);
89 if (!file.is_open()) {
90 std::cerr <<
"mxx: cannot open file: " << inputPath <<
"\n";
93 std::ostringstream buffer;
94 buffer << file.rdbuf();
96 if (buffer.str().empty()) {
97 std::cerr <<
"mxx: file is empty: " << inputPath <<
"\n";
101 std::string source = buffer.str();
104 std::cerr <<
"mxx: validation failed: " << inputPath <<
"\n";
115 std::string inputDir = inputPath.substr(0, inputPath.find_last_of(
"/\\") + 1);
116 if (inputDir.empty()) inputDir =
"./";
118 static const std::unordered_set<std::string> nativeModules = {
"io",
"std",
"string",
"sdl",
"strlib"};
119 for (
const auto &dep : ast->uses) {
120 if (nativeModules.count(dep))
continue;
122 std::string unitFile = inputDir + dep +
".pas";
123 std::ifstream uf(unitFile);
125 std::string lowerDep = dep;
126 std::transform(lowerDep.begin(), lowerDep.end(), lowerDep.begin(), ::tolower);
127 unitFile = inputDir + lowerDep +
".pas";
131 std::ostringstream ubuf;
138 for (
auto &decl : unitAst->interfaceDecls) {
150 ast->interfaceDecls.push_back(std::move(decl));
165 std::string inputDir = inputPath.substr(0, inputPath.find_last_of(
"/\\") + 1);
166 if (inputDir.empty()) inputDir =
"./";
168 for (
const auto &dep : ast->uses) {
170 static const std::unordered_set<std::string> nativeModules = {
"io",
"std",
"string",
"sdl",
"strlib"};
171 if (nativeModules.count(dep))
continue;
174 std::string unitFile = inputDir + dep +
".pas";
175 std::ifstream uf(unitFile);
178 std::string lowerDep = dep;
179 std::transform(lowerDep.begin(), lowerDep.end(), lowerDep.begin(), ::tolower);
180 unitFile = inputDir + lowerDep +
".pas";
184 std::ostringstream ubuf;
193 for (
auto &decl : unitAst->interfaceDecls) {
205 ast->block->declarations.push_back(std::move(decl));
219 std::fstream outFile;
220 outFile.open(outputPath, std::ios::out);
221 if (!outFile.is_open()) {
222 std::cerr <<
"mxx: cannot open output file: " << outputPath <<
"\n";
225 std::ostringstream output;
228 outFile << opt <<
"\n";
232 std::cerr <<
"Parse Error: " << e.what() <<
"\n";
234 }
catch (
const std::exception &e) {
235 std::cerr <<
"Error: " << e.what() <<
"\n";
238 std::cerr <<
"Syntax Error: " << e.why() <<
"\n";
246 std::ifstream f(filePath);
247 if (!f.is_open())
return {};
248 std::ostringstream buf;
253 auto skipWS = [&]() {
254 while (i < src.size() && (src[i] ==
' ' || src[i] ==
'\t' || src[i] ==
'\r' || src[i] ==
'\n'))
257 auto readIdent = [&]() -> std::string {
260 while (i < src.size() && (std::isalnum(
static_cast<unsigned char>(src[i])) || src[i] ==
'_'))
264 std::string kw = readIdent();
265 std::string lkw = kw;
266 std::transform(lkw.begin(), lkw.end(), lkw.begin(), ::tolower);
267 if (lkw ==
"program" || lkw ==
"unit") {
268 std::string name = readIdent();
269 if (!name.empty())
return name;
276 args.
addOptionSingle(
'c',
"Compile one or more Pascal source files to .mxvm bytecode")
277 .addOptionSingleValue(
'i',
"Input Pascal source file")
278 .addOptionSingleValue(
'o',
"Output .mxvm file");
280 bool compile_mode =
false;
281 std::string input_file;
282 std::string output_file;
283 std::vector<std::string> files;
288 while ((r = args.
proc(arg)) != -1) {
305 std::cerr <<
"mxx: " << e.
text() <<
"\n";
309 if (!input_file.empty()) {
311 if (output_file.empty()) {
314 output_file = name +
".mxvm";
316 output_file = input_file;
317 if (output_file.size() > 4 && output_file.substr(output_file.size() - 4) ==
".pas")
318 output_file = output_file.substr(0, output_file.size() - 4) +
".mxvm";
320 output_file +=
".mxvm";
323 return compileFile(input_file, output_file) ? 0 : EXIT_FAILURE;
324 }
else if (compile_mode) {
326 std::cerr <<
"mxx: -c requires at least one source file\n";
329 for (
const auto &src : files) {
333 out = name +
".mxvm";
336 if (out.size() > 4 && out.substr(out.size() - 4) ==
".pas")
337 out = out.substr(0, out.size() - 4) +
".mxvm";
346 if (files.size() != 2) {
347 std::cerr <<
argv[0] <<
" v" <<
VERSION_INFO <<
"\n(C) 2026 LostSideDead Software\n\n";
348 std::cerr <<
"Usage:\n"
349 <<
" " <<
argv[0] <<
" <source.pas> <output.mxvm>\n"
350 <<
" " <<
argv[0] <<
" -i <source.pas> [-o <output.mxvm>]\n"
351 <<
" " <<
argv[0] <<
" -c <source1.pas> [source2.pas ...]\n";
Generic command-line argument parser with concept-constrained string types.
Exception type thrown for argument parsing errors.
String text() const
Get the error message.
Generic command-line argument parser supporting single/double-dash options.
int proc(Argument< String > &a)
Process the next argument from the command line.
Argz< String > & addOptionSingle(const int &c, const String &description)
Register a single-dash flag (no value).
bool validate(const std::string &name)
Run semantic validation.
AST visitor that generates MXVM intermediate code.
void registerImportedVar(const std::string &unitName, VarDeclNode &node)
Register imported variable metadata from a dependency unit (no alloc emitted).
void registerImportedConst(const std::string &unitName, ConstDeclNode &node)
Register imported constant values from a dependency unit.
void writeTo(std::ostream &out) const
Write the complete MXVM program to a stream.
void generate(ASTNode *root)
Generate MXVM code for the entire AST.
void registerExternalFunc(const std::string &funcName, const std::string &unitName)
Register a function or procedure as originating from an external unit.
AST node for constant declarations (const id = value).
AST node for a function declaration.
Exception thrown on parse errors.
Recursive-descent parser for the Pascal language.
std::unique_ptr< ProgramNode > parseProgram()
Parse a complete Pascal program and return the root AST node.
std::unique_ptr< UnitNode > parseUnit()
Parse a Pascal unit and return the root AST node.
bool isUnitSource() const
Check if the source starts with a 'unit' keyword.
mxx::TPValidator validator
semantic validator
AST node for a procedure declaration.
AST node for variable declarations (var id1, id2 : type).
Exception thrown during scanning errors.
std::string removeComments(const std::string &text)
int main(int argc, char **argv)
static bool compileFile(const std::string &inputPath, const std::string &outputPath)
static std::string extractPasName(const std::string &filePath)
int argc(void)
Return the number of stored command-line arguments.
const char * argv(int idx)
Return the command-line argument at the given index.
std::string mxvmOpt(const std::string &text)
Pascal-to-MXVM code generator using the Visitor pattern over the Pascal AST.
Pascal recursive-descent parser producing a Pascal AST.
A parsed or registered command-line argument definition.
String arg_value
parsed value (for value-taking options)
#define VERSION_INFO
MXVM version string.