11#include <unordered_map>
12#include <unordered_set>
21 bool in_str =
false, escaped =
false;
22 for (
size_t i = 0; i < s.size(); ++i) {
27 if (c ==
'/' && i + 1 < s.size() && s[i + 1] ==
'/')
54 bool in_object_section =
false;
57 for (
size_t i = start_index; i <= end_index && i <
scanner.size(); ++i) {
60 if (!in_object_section) {
63 tok.getTokenValue() ==
"section" &&
65 scanner[i + 1].getTokenValue() ==
"object" &&
66 scanner[i + 2].getTokenValue() ==
"{") {
67 in_object_section =
true;
74 if (tok.getTokenValue() ==
"{") {
76 }
else if (tok.getTokenValue() ==
"}") {
77 if (brace_depth <= 0) {
81 if (brace_depth == 0) {
82 in_object_section =
false;
86 objects.insert(tok.getTokenValue());
92 static const std::unordered_map<std::string, OpSpec>
kOpSpecs = {
133 {
"ret", {
"ret", {}}},
134 {
"done", {
"done", {}}},
147 std::string a =
token->getTokenValue();
153 a +=
"." +
token->getTokenValue();
169 std::string num_str =
token->getTokenValue();
170 if (num_str.find(
'.') != std::string::npos) {
171 throw mx::Exception(
"Syntax Error in '" +
filename +
"': Floating point constants must be declared as variables, not used directly in instructions at line " + std::to_string(
token->getLine()));
193 throw mx::Exception(
"Syntax Error in '" +
filename +
"': invalid operand '" +
token->getTokenValue() +
"' at line " + std::to_string(
token->getLine()));
197 std::vector<ParsedOp> out;
199 size_t operand_line =
token->getLine();
203 if (
token &&
token->getLine() != operand_line) {
209 if (
token &&
token->getLine() == operand_line &&
210 !
token->getTokenValue().empty()) {
211 throw mx::Exception(
"Syntax Error in file '" +
filename +
"': Multiple items or missing comma between operands on line " +
212 std::to_string(operand_line));
220 for (
size_t i = 0; i <
scanner.size(); ++i) {
223 if (i + 1 <
scanner.size() &&
scanner[i + 1].getTokenValue() ==
":") {
224 labels[tok.getTokenValue()] = tok.getTokenValue();
231 const std::string &op,
232 const std::vector<ParsedOp> &ops,
233 const std::unordered_map<std::string, Variable> &vars,
234 const std::unordered_map<std::string, std::string> &labels,
235 const std::unordered_set<std::string> &objects,
236 std::vector<UseVar> &usedVarsRef,
237 std::vector<UseLabel> &usedLabelsRef) {
242 const OpSpec &spec = it->second;
247 if ((
int)ops.size() < minArgs || (
int)ops.size() > maxArgs) {
248 throw mx::Exception(
"Syntax Error in '" +
filename +
"': '" + op +
"' expects " + std::to_string(minArgs) + ((maxArgs == INT32_MAX) ?
"..inf" :
".." + std::to_string(maxArgs)) +
" operands; found " + std::to_string(ops.size()));
261 for (
size_t i = 0; i < spec.
fixed.size() && i < ops.size(); ++i) {
262 if (!kindOk(spec.
fixed[i], ops[i].kind)) {
263 throw mx::Exception(
"Syntax Error in '" +
filename +
"': '" + op +
"' operand " + std::to_string((
int)i + 1) +
" has wrong kind");
268 for (
size_t i = spec.
fixed.size(); i < ops.size(); ++i) {
275 auto pushVar = [&](
const ParsedOp &p) {
277 if (p.kind ==
OpKind::Id && p.text.find(
'.') == std::string::npos) {
278 if (!vars.count(p.text)) {
279 std::string msg =
"Syntax Error in '" +
filename +
"': Undefined variable '" + p.text +
"'";
281 msg +=
" at line " + std::to_string(p.at->getLine());
287 else if (p.kind ==
OpKind::Member && p.text.find(
'.') != std::string::npos) {
288 std::string objectName = p.text.substr(0, p.text.find(
'.'));
289 if (!objects.count(objectName)) {
290 std::string msg =
"Syntax Error in '" +
filename +
"': Undefined object '" + objectName +
"'";
292 msg +=
" at line " + std::to_string(p.at->getLine());
297 usedVarsRef.push_back({p.text, p.at});
300 auto pushLabel = [&](
const ParsedOp &p) {
301 if (p.text.find(
'.') == std::string::npos) {
302 usedLabelsRef.push_back({p.text, p.at});
306 if (op ==
"jmp" || op ==
"je" || op ==
"jne" || op ==
"jl" || op ==
"jle" || op ==
"jg" || op ==
"jge" || op ==
"jz" || op ==
"jnz" || op ==
"ja" || op ==
"jb") {
315 if (op ==
"mov" || op ==
"pop" || op ==
"stack_load" || op ==
"alloc" || op ==
"getline" ||
316 op ==
"return" || op ==
"not" || op ==
"neg" || op ==
"to_int" || op ==
"to_float") {
317 if (!ops.empty() &&
isIdLike(ops[0].kind))
321 if (op ==
"add" || op ==
"sub" || op ==
"mul" || op ==
"div" || op ==
"or" || op ==
"and" ||
322 op ==
"xor" || op ==
"mod" || op ==
"cmp") {
323 if (ops.size() >= 1 &&
isIdLike(ops[0].kind))
325 if (ops.size() >= 2 &&
isIdLike(ops[1].kind))
327 if (ops.size() >= 3 &&
isIdLike(ops[2].kind))
332 if (ops.size() >= 1 &&
isIdLike(ops[0].kind))
334 if (ops.size() >= 2 &&
isIdLike(ops[1].kind))
336 if (ops.size() >= 3 &&
isIdLike(ops[2].kind))
341 if (ops.size() >= 1 &&
isIdLike(ops[0].kind))
343 if (ops.size() >= 2 &&
isIdLike(ops[1].kind))
345 if (ops.size() >= 3 &&
isIdLike(ops[2].kind))
350 if (!ops.empty() &&
isIdLike(ops[0].kind))
354 if (op ==
"invoke") {
355 for (
size_t i = 1; i < ops.size(); ++i) {
361 for (
size_t i = 0; i < ops.size(); ++i) {
374 std::unordered_map<std::string, std::string> labels;
375 std::unordered_set<std::string> objects;
376 std::vector<UseVar> usedVars;
377 std::vector<UseLabel> usedLabels;
379 size_t block_start =
index - 1;
380 size_t block_end = block_start;
383 bool found_opening =
false;
384 for (
size_t i = block_start; i <
scanner.size(); ++i) {
385 if (
scanner[i].getTokenValue() ==
"{") {
386 found_opening =
true;
388 }
else if (
scanner[i].getTokenValue() ==
"}") {
390 if (found_opening && brace_count == 0) {
400 std::vector<std::string> lines;
402 std::istringstream code_info(
source);
404 while (std::getline(code_info, l)) {
409 std::vector<std::pair<int, int>> code_ranges;
411 bool seen_section =
false;
412 bool section_is_code =
false;
413 bool in_code =
false;
415 int range_start = -1;
416 for (
size_t i = 0; i <
scanner.size(); ++i) {
421 section_is_code =
false;
425 section_is_code = (t.getTokenValue() ==
"code");
428 if (section_is_code && t.getTokenValue() ==
"{") {
431 range_start = t.getLine();
432 seen_section =
false;
433 section_is_code =
false;
437 if (t.getTokenValue() ==
"{") {
439 }
else if (t.getTokenValue() ==
"}") {
441 if (brace_depth == 0) {
442 int range_end = t.getLine();
443 code_ranges.emplace_back(range_start, range_end);
451 for (
const auto &pr : code_ranges) {
452 const int start = std::max(1, pr.first);
453 const int end = pr.second;
454 for (
int line = start; line <= end; ++line) {
455 const int idx = line - 1;
456 if (idx < 0 || idx >=
static_cast<int>(lines.size()))
458 const std::string &text = lines[idx];
461 "Syntax Error in file '" +
filename +
462 "': Semicolons are not allowed in code section at line " +
463 std::to_string(line) +
": '" + text +
"'");
468 std::unordered_map<int, int> line_instruction_count;
469 auto is_in_code = [&](
int line) ->
bool {
470 for (
const auto &pr : code_ranges) {
471 if (line >= pr.first && line <= pr.second)
476 for (
size_t i = 0; i <
scanner.size(); ++i) {
478 const int line = tok.getLine();
479 if (!is_in_code(line))
484 if (i + 1 <
scanner.size() &&
scanner[i + 1].getTokenValue() ==
":") {
487 if (tok.getTokenValue() ==
"function") {
491 line_instruction_count[line]++;
494 for (
const auto &kv : line_instruction_count) {
495 const int line_num = kv.first;
496 const int count = kv.second;
498 const int idx = line_num - 1;
499 const std::string line_text =
500 (idx >= 0 && idx < static_cast<int>(lines.size())) ? lines[idx] : std::string(
"<unknown>");
502 "': Multiple instructions on same line at line " +
503 std::to_string(line_num) +
": '" + line_text +
"'");
507 auto skipSeparators = [&]() {};
513 if (
match(
"program")) {
527 std::unordered_map<std::string, Variable> vars;
528 for (
auto &n : {
"stdout",
"stdin",
"stderr"}) {
530 vars[n].var_name = n;
540 std::string sectionName =
token->getTokenValue();
546 if (sectionName ==
"module" || sectionName ==
"object") {
564 }
else if (sectionName ==
"data") {
569 (
token->getTokenValue() ==
"int" ||
570 token->getTokenValue() ==
"string" ||
571 token->getTokenValue() ==
"float" ||
572 token->getTokenValue() ==
"ptr" ||
573 token->getTokenValue() ==
"byte" ||
574 token->getTokenValue() ==
"export")) {
575 if (
token->getTokenValue() ==
"export")
578 std::string vtype =
token->getTokenValue();
583 std::string vname =
token->getTokenValue();
584 vars[vname].var_name = vname;
588 if (
match(
",") && vtype ==
"string") {
596 throw mx::Exception(
"Syntax Error in file '" +
filename +
"': string buffer requires number on line " + std::to_string(
token->getLine()));
608 if (vtype ==
"byte") {
610 throw mx::Exception(
"Syntax Error in file '" +
filename +
"': byte must be a valid byte value integer 0-255 on line " + std::to_string(
token->getLine()));
612 int64_t value = std::stoll(
token->getTokenValue(),
nullptr, 0);
613 if (value < 0 || value > 0xFF) {
614 throw mx::Exception(
"Syntax Error in file '" +
filename +
"': byte out of range 0-255 on line: " + std::to_string(
token->getLine()));
618 }
else if (vtype ==
"string") {
622 }
else if (
token->getTokenValue() ==
"null" ||
629 throw mx::Exception(
"Syntax Error in file '" +
filename +
"': Expected value for variable, found: " +
token->getTokenValue() +
" at line " + std::to_string(
token->getLine()));
632 throw mx::Exception(
"Syntax Error in file '" +
filename +
"': Expected variable declaration, found: " +
token->getTokenValue() +
" at line " + std::to_string(
token->getLine()));
638 }
else if (sectionName ==
"code") {
642 size_t old_index =
index;
647 "': Semicolons not allowed in code section at line " +
648 std::to_string(
token->getLine()));
669 std::string op =
token->getTokenValue();
671 throw mx::Exception(
"Syntax Error in file '" +
filename +
"': Unknown instruction '" + op +
"' at line " + std::to_string(
token->getLine()));
676 if (op ==
"ret" || op ==
"done") {
677 std::vector<ParsedOp> emptyOps;
686 throw mx::Exception(
"Syntax Error in file '" +
filename +
"': Unexpected token '" +
token->getTokenValue() +
"' in code section at line " + std::to_string(
token->getLine()));
689 if (old_index ==
index) {
699 throw mx::Exception(
"Syntax Error in file '" +
filename +
"': Unknown section: " + sectionName +
" at line " + std::to_string(
token->getLine()));
708 for (
auto &u : usedLabels) {
709 if (!labels.count(u.name)) {
710 throw mx::Exception(
"Syntax Error in '" +
filename +
"': Undefined label '" + u.name +
"' at line " + std::to_string(u.at->getLine()));
721 if (
token->getTokenValue() != m)
728 throw mx::Exception(
"Syntax Error in '" +
filename +
"': Required: " + r +
" but reached end of file");
730 if (r !=
token->getTokenValue())
732 "Syntax Error in '" +
filename +
"': Required: " + r +
733 " Found: " +
token->getTokenValue() +
734 " at line " + std::to_string(
token->getLine()));
740 if (t !=
token->getTokenType())
749 if (t !=
token->getTokenType())
752 " instead found: " +
token->getTokenValue() +
754 " at line " + std::to_string(
token->getLine()));
General-purpose exception with errno-aware factory method.
const scan::TToken * token
std::vector< ParsedOp > parseOperandList()
Parse a comma-separated list of operands.
ParsedOp parseOperand()
Parse a single instruction operand from the token stream.
bool match(const std::string &m)
Check if the current token value matches and advance.
void require(const std::string &r)
Require the current token value to match, or report an error.
bool validate(const std::string &name)
Run full validation on the source program.
bool peekIs(const std::string &s)
Check if the current token value matches without consuming.
Validator(const std::string &source)
Construct a validator from MXVM source code.
bool next()
Advance to the next token; returns false at end.
std::string tokenTypeToString(types::TokenType t)
Convert a TokenType to its display string.
void collect_objects(std::unordered_set< std::string > &objects, size_t start_index, size_t end_index)
Collect all object names declared between token indices.
void collect_labels(std::unordered_map< std::string, std::string > &labels)
Pre-collect all label declarations and map them to function names.
void validateAgainstSpec(const std::string &op, const std::vector< ParsedOp > &ops, const std::unordered_map< std::string, Variable > &vars, const std::unordered_map< std::string, std::string > &labels, const std::unordered_set< std::string > &objects, std::vector< UseVar > &usedVarsRef, std::vector< UseLabel > &usedLabelsRef)
Validate an instruction's operands against the spec table.
Exception class, hex formatting utilities, and terminal color definitions.
Instruction set enum, operand/instruction structs, variable types, and Variable/Variable_Value defini...
std::vector< std::string > IncType
String representations of Inc opcodes, indexed by enum value.
static const std::unordered_map< std::string, OpSpec > kOpSpecs
@ None
exact fixed operand count required
@ ArgsTail
fixed operands followed by argument-typed trailing operands
@ AnyTail
fixed operands followed by any number of trailing operands
static bool isIdLike(OpKind k)
static bool has_semicolon(const std::string &s)
static bool isImmediate(const OpKind k)
OpKind
Classification of operand kinds for validation.
token::Token< char > TToken
Default token type.
TokenType
Classification of scanned tokens.
@ TT_SYM
symbol / operator token
@ TT_HEX
hexadecimal numeric literal
@ TT_ID
identifier or keyword
@ TT_NUM
decimal numeric literal
Specification of valid operand patterns for an instruction.
std::vector< OpKind > fixed
required fixed operand kinds
A parsed operand with its kind, text, and source location.
const scan::TToken * at
token at the operand's source position
A named variable with type, value, and optional object association.
Validator for MXVM programs — checks variable/label usage and instruction operand specifications.