MXVM 1.8.1
Virtual Machine, Compiler, and Pascal Frontend
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1
6#include "icode.hpp"
7#include "parser.hpp"
8#include <algorithm>
9#include <cstdlib>
10#include <fstream>
11#include <iostream>
12#include <sstream>
13#include <unordered_set>
14#include "argz.hpp"
15#include "version_info.hpp"
16
17
18
19std::string removeComments(const std::string &text) {
20 std::ostringstream stream;
21 enum State {
22 CODE,
23 BRACE_COMMENT,
24 PAREN_COMMENT,
25 STRING
26 };
27 State state = CODE;
28
29 for (size_t i = 0; i < text.length(); ++i) {
30 char c = text[i];
31 char next = (i + 1 < text.length()) ? text[i + 1] : '\0';
32
33 switch (state) {
34 case CODE:
35 if (c == '{') {
36 state = BRACE_COMMENT;
37 } else if (c == '(' && next == '*') {
38 state = PAREN_COMMENT;
39 i++;
40 } else if (c == '\'') {
41 stream << c;
42 state = STRING;
43 } else if (c == '/' && next == '/') {
44
45 while (i < text.length() && text[i] != '\n') {
46 i++;
47 }
48 if (i < text.length()) {
49 stream << '\n';
50 }
51 } else {
52 stream << c;
53 }
54 break;
55
56 case BRACE_COMMENT:
57 if (c == '}') {
58 state = CODE;
59 }
60 break;
61
62 case PAREN_COMMENT:
63 if (c == '*' && next == ')') {
64 state = CODE;
65 i++;
66 }
67 break;
68
69 case STRING:
70 stream << c;
71 if (c == '\'') {
72 if (next == '\'') {
73 stream << next;
74 i++;
75 } else {
76 state = CODE;
77 }
78 }
79 break;
80 }
81 }
82
83 return stream.str();
84}
85
86static bool compileFile(const std::string &inputPath, const std::string &outputPath) {
87 try {
88 std::ifstream file(inputPath);
89 if (!file.is_open()) {
90 std::cerr << "mxx: cannot open file: " << inputPath << "\n";
91 return false;
92 }
93 std::ostringstream buffer;
94 buffer << file.rdbuf();
95
96 if (buffer.str().empty()) {
97 std::cerr << "mxx: file is empty: " << inputPath << "\n";
98 return false;
99 }
100
101 std::string source = buffer.str();
102 pascal::PascalParser parser(removeComments(source));
103 if (!parser.validator.validate(inputPath)) {
104 std::cerr << "mxx: validation failed: " << inputPath << "\n";
105 return false;
106 }
107
109
110 if (parser.isUnitSource()) {
111 auto ast = parser.parseUnit();
112 if (ast) {
113 // For units with uses clause, parse each dependency's interface
114 // to import their declarations (vars, arrays, consts, types, funcs)
115 std::string inputDir = inputPath.substr(0, inputPath.find_last_of("/\\") + 1);
116 if (inputDir.empty()) inputDir = "./";
117
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;
121
122 std::string unitFile = inputDir + dep + ".pas";
123 std::ifstream uf(unitFile);
124 if (!uf.is_open()) {
125 std::string lowerDep = dep;
126 std::transform(lowerDep.begin(), lowerDep.end(), lowerDep.begin(), ::tolower);
127 unitFile = inputDir + lowerDep + ".pas";
128 uf.open(unitFile);
129 }
130 if (uf.is_open()) {
131 std::ostringstream ubuf;
132 ubuf << uf.rdbuf();
133 uf.close();
134 try {
135 pascal::PascalParser unitParser(removeComments(ubuf.str()));
136 auto unitAst = unitParser.parseUnit();
137 if (unitAst) {
138 for (auto &decl : unitAst->interfaceDecls) {
139 if (auto *fd = dynamic_cast<pascal::FuncDeclNode *>(decl.get()))
140 emiter.registerExternalFunc(fd->name, dep);
141 else if (auto *pd = dynamic_cast<pascal::ProcDeclNode *>(decl.get()))
142 emiter.registerExternalFunc(pd->name, dep);
143 else if (auto *vd = dynamic_cast<pascal::VarDeclNode *>(decl.get())) {
144 emiter.registerImportedVar(dep, *vd);
145 continue;
146 } else if (auto *cd = dynamic_cast<pascal::ConstDeclNode *>(decl.get())) {
147 emiter.registerImportedConst(dep, *cd);
148 continue;
149 }
150 ast->interfaceDecls.push_back(std::move(decl));
151 }
152 }
153 } catch (...) {
154 }
155 }
156 }
157
158 emiter.generate(ast.get());
159 }
160 } else {
161 auto ast = parser.parseProgram();
162 if (ast) {
163 // For programs with unit imports, parse each unit's interface
164 // to register function/procedure signatures
165 std::string inputDir = inputPath.substr(0, inputPath.find_last_of("/\\") + 1);
166 if (inputDir.empty()) inputDir = "./";
167
168 for (const auto &dep : ast->uses) {
169 // Skip native modules
170 static const std::unordered_set<std::string> nativeModules = {"io", "std", "string", "sdl", "strlib"};
171 if (nativeModules.count(dep)) continue;
172
173 // Try to find and parse the unit's .pas file for interface declarations
174 std::string unitFile = inputDir + dep + ".pas";
175 std::ifstream uf(unitFile);
176 if (!uf.is_open()) {
177 // Try lowercase
178 std::string lowerDep = dep;
179 std::transform(lowerDep.begin(), lowerDep.end(), lowerDep.begin(), ::tolower);
180 unitFile = inputDir + lowerDep + ".pas";
181 uf.open(unitFile);
182 }
183 if (uf.is_open()) {
184 std::ostringstream ubuf;
185 ubuf << uf.rdbuf();
186 uf.close();
187 try {
188 pascal::PascalParser unitParser(removeComments(ubuf.str()));
189 auto unitAst = unitParser.parseUnit();
190 if (unitAst) {
191 // Register interface declarations as forward declarations in the main AST
192 // and track which functions/procedures are external
193 for (auto &decl : unitAst->interfaceDecls) {
194 if (auto *fd = dynamic_cast<pascal::FuncDeclNode *>(decl.get()))
195 emiter.registerExternalFunc(fd->name, dep);
196 else if (auto *pd = dynamic_cast<pascal::ProcDeclNode *>(decl.get()))
197 emiter.registerExternalFunc(pd->name, dep);
198 else if (auto *vd = dynamic_cast<pascal::VarDeclNode *>(decl.get())) {
199 emiter.registerImportedVar(dep, *vd);
200 continue;
201 } else if (auto *cd = dynamic_cast<pascal::ConstDeclNode *>(decl.get())) {
202 emiter.registerImportedConst(dep, *cd);
203 continue;
204 }
205 ast->block->declarations.push_back(std::move(decl));
206 }
207 }
208 } catch (...) {
209 // Unit source not available; user must ensure correct call signatures
210 }
211 }
212 }
213
214 emiter.generate(ast.get());
215 }
216 }
217
218 {
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";
223 return false;
224 }
225 std::ostringstream output;
226 emiter.writeTo(output);
227 std::string opt = pascal::mxvmOpt(output.str());
228 outFile << opt << "\n";
229 }
230 return true;
231 } catch (const pascal::ParseException &e) {
232 std::cerr << "Parse Error: " << e.what() << "\n";
233 return false;
234 } catch (const std::exception &e) {
235 std::cerr << "Error: " << e.what() << "\n";
236 return false;
237 } catch (const scan::ScanExcept &e) {
238 std::cerr << "Syntax Error: " << e.why() << "\n";
239 return false;
240 }
241}
242
243// Extract the declared program or unit name from a Pascal source file.
244// Returns empty string if not found.
245static std::string extractPasName(const std::string &filePath) {
246 std::ifstream f(filePath);
247 if (!f.is_open()) return {};
248 std::ostringstream buf;
249 buf << f.rdbuf();
250 std::string src = removeComments(buf.str());
251 // Scan tokens: skip whitespace, look for "program"|"unit" then the identifier before ';'
252 size_t i = 0;
253 auto skipWS = [&]() {
254 while (i < src.size() && (src[i] == ' ' || src[i] == '\t' || src[i] == '\r' || src[i] == '\n'))
255 ++i;
256 };
257 auto readIdent = [&]() -> std::string {
258 skipWS();
259 std::string id;
260 while (i < src.size() && (std::isalnum(static_cast<unsigned char>(src[i])) || src[i] == '_'))
261 id += src[i++];
262 return id;
263 };
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;
270 }
271 return {};
272}
273
274int main(int argc, char **argv) {
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");
279
280 bool compile_mode = false;
281 std::string input_file;
282 std::string output_file;
283 std::vector<std::string> files;
284
285 try {
287 int r;
288 while ((r = args.proc(arg)) != -1) {
289 switch (r) {
290 case 'c':
291 compile_mode = true;
292 break;
293 case 'i':
294 input_file = arg.arg_value;
295 break;
296 case 'o':
297 output_file = arg.arg_value;
298 break;
299 case '-':
300 files.push_back(arg.arg_value);
301 break;
302 }
303 }
304 } catch (const mx::ArgException<std::string> &e) {
305 std::cerr << "mxx: " << e.text() << "\n";
306 return EXIT_FAILURE;
307 }
308
309 if (!input_file.empty()) {
310 // -i / -o mode
311 if (output_file.empty()) {
312 std::string name = extractPasName(input_file);
313 if (!name.empty()) {
314 output_file = name + ".mxvm";
315 } else {
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";
319 else
320 output_file += ".mxvm";
321 }
322 }
323 return compileFile(input_file, output_file) ? 0 : EXIT_FAILURE;
324 } else if (compile_mode) {
325 if (files.empty()) {
326 std::cerr << "mxx: -c requires at least one source file\n";
327 return EXIT_FAILURE;
328 }
329 for (const auto &src : files) {
330 std::string name = extractPasName(src);
331 std::string out;
332 if (!name.empty()) {
333 out = name + ".mxvm";
334 } else {
335 out = src;
336 if (out.size() > 4 && out.substr(out.size() - 4) == ".pas")
337 out = out.substr(0, out.size() - 4) + ".mxvm";
338 else
339 out += ".mxvm";
340 }
341 if (!compileFile(src, out))
342 return EXIT_FAILURE;
343 }
344 } else {
345 // Legacy mode: mxx <source> <output>
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";
352 return EXIT_FAILURE;
353 }
354 if (!compileFile(files[0], files[1]))
355 return EXIT_FAILURE;
356 }
357
358 return 0;
359}
Generic command-line argument parser with concept-constrained string types.
Exception type thrown for argument parsing errors.
Definition argz.hpp:102
String text() const
Get the error message.
Definition argz.hpp:110
Generic command-line argument parser supporting single/double-dash options.
Definition argz.hpp:126
int proc(Argument< String > &a)
Process the next argument from the command line.
Definition argz.hpp:276
Argz< String > & addOptionSingle(const int &c, const String &description)
Register a single-dash flag (no value).
Definition argz.hpp:203
bool validate(const std::string &name)
Run semantic validation.
Definition validator.cpp:18
AST visitor that generates MXVM intermediate code.
Definition icode.hpp:178
void registerImportedVar(const std::string &unitName, VarDeclNode &node)
Register imported variable metadata from a dependency unit (no alloc emitted).
Definition icode.hpp:940
void registerImportedConst(const std::string &unitName, ConstDeclNode &node)
Register imported constant values from a dependency unit.
Definition icode.hpp:914
void writeTo(std::ostream &out) const
Write the complete MXVM program to a stream.
Definition icode.hpp:1324
void generate(ASTNode *root)
Generate MXVM code for the entire AST.
Definition icode.hpp:990
void registerExternalFunc(const std::string &funcName, const std::string &unitName)
Register a function or procedure as originating from an external unit.
Definition icode.hpp:903
AST node for constant declarations (const id = value).
Definition ast.hpp:174
AST node for a function declaration.
Definition ast.hpp:194
Exception thrown on parse errors.
Definition parser.hpp:18
Recursive-descent parser for the Pascal language.
Definition parser.hpp:112
std::unique_ptr< ProgramNode > parseProgram()
Parse a complete Pascal program and return the root AST node.
Definition parser.cpp:47
std::unique_ptr< UnitNode > parseUnit()
Parse a Pascal unit and return the root AST node.
Definition parser.cpp:95
bool isUnitSource() const
Check if the source starts with a 'unit' keyword.
Definition parser.cpp:87
mxx::TPValidator validator
semantic validator
Definition parser.hpp:135
AST node for a procedure declaration.
Definition ast.hpp:209
AST node for variable declarations (var id1, id2 : type).
Definition ast.hpp:132
Exception thrown during scanning errors.
Definition scanner.hpp:80
std::string removeComments(const std::string &text)
Definition main.cpp:19
int main(int argc, char **argv)
Definition main.cpp:274
static bool compileFile(const std::string &inputPath, const std::string &outputPath)
Definition main.cpp:86
static std::string extractPasName(const std::string &filePath)
Definition main.cpp:245
int argc(void)
Return the number of stored command-line arguments.
Definition std.c:72
const char * argv(int idx)
Return the command-line argument at the given index.
Definition std.c:76
std::string mxvmOpt(const std::string &text)
Definition icode.cpp:589
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.
Definition argz.hpp:52
String arg_value
parsed value (for value-taking options)
Definition argz.hpp:55
#define VERSION_INFO
MXVM version string.