-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
michael.burzan
committed
Jan 25, 2025
1 parent
31bf972
commit 6c24fac
Showing
8 changed files
with
347 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,214 @@ | ||
#include "VirtualMachine.h" | ||
#include "Util.h" | ||
#include <filesystem> | ||
#include <format> | ||
#include <ostream> | ||
#include <fstream> | ||
#include <vector> | ||
|
||
template <typename... Args> void print(std::format_string<Args...> fmt, Args&&... args) { | ||
std::print(std::cout, fmt, std::forward<Args>(args)...); | ||
} | ||
|
||
struct PasmOption { | ||
std::string output_file; | ||
}; | ||
|
||
void fatal() { | ||
print("pasm: fatal: no input file specified\n Type pasm -h for help.\n\n"); | ||
} | ||
|
||
void usage() { | ||
print("Usage: pasm [options...] filename\n"); | ||
print(" pasm -v\n"); | ||
print("Options (values in brackets indicate defaults):\n\n"); | ||
print(" -h show this text and exit\n"); | ||
print(" -v print the pasm version number and exit\n"); | ||
print(" -o outfile write output to outfile\n"); | ||
} | ||
|
||
void version() { | ||
print("pasm version 0.0.1 compiled on ...\n"); | ||
} | ||
|
||
void print_usage_and_exit_if(bool condition) { | ||
if (condition) { | ||
usage(); | ||
std::exit(0); | ||
} | ||
} | ||
|
||
auto options(const std::vector<std::string>& args) -> PasmOption { | ||
PasmOption opt; | ||
for (std::size_t i = 1; i < args.size(); ++i) { | ||
if (args[i] == "-v") { | ||
version(); | ||
std::exit(0); | ||
} | ||
if (args[i] == "-h") { | ||
print_usage_and_exit_if(true); | ||
} | ||
if (args[i] == "-o") { | ||
print_usage_and_exit_if(i + 1 >= args.size()); | ||
opt.output_file = args[i + 1]; | ||
i++; | ||
} | ||
} | ||
return opt; | ||
} | ||
|
||
void inc_index_if_whitespace(std::size_t& i, const std::string& str) { | ||
if (i >= str.length()) { | ||
return; | ||
} | ||
if (str[i] == ' ' || str[i] == '\t' || str[i] == '\n') { | ||
for (; i < str.length(); ++i) { | ||
if (str[i] != ' ' && str[i] != '\t' && str[i] != '\n') { | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
|
||
void store_value_until_whitespace(std::size_t& i, std::string& value, const std::string& str) { | ||
for (; i < str.length(); ++i) { | ||
if (str[i] == ' ' || str[i] == '\t' || str[i] == '\n') { | ||
break; | ||
} | ||
value += str[i]; | ||
} | ||
} | ||
|
||
void extract_value(std::string& value, const std::string& line) { | ||
std::size_t i = 0; | ||
inc_index_if_whitespace(i, line); | ||
store_value_until_whitespace(i, value, line); | ||
} | ||
|
||
void parse_function_entry_item(std::size_t& i, std::string& value, const std::string& entry, std::size_t line_nr) { | ||
inc_index_if_whitespace(i, entry); | ||
store_value_until_whitespace(i, value, entry); | ||
if (value.length() == 0) { | ||
std::string trimmed_entry = trim_copy(entry); | ||
print("pasm: Missing function entry item in line {}! Content is {}\n", line_nr, trimmed_entry); | ||
exit(1); | ||
} | ||
} | ||
|
||
auto make_function_entry(const std::string& entry, std::size_t line_nr) -> FunctionEntry { | ||
std::string fname; | ||
std::string argC; | ||
std::string label; | ||
std::size_t i = 0; | ||
|
||
parse_function_entry_item(i, fname, entry, line_nr); | ||
parse_function_entry_item(i, argC, entry, line_nr); | ||
parse_function_entry_item(i, label, entry, line_nr); | ||
inc_index_if_whitespace(i, entry); | ||
if (i < entry.length()) { | ||
print("pasm: parsing error in function section line {}! valid format is: [function name] [argument] [count label] " | ||
"not {}\n", | ||
line_nr, entry); | ||
std::exit(1); | ||
} | ||
bool is_zero = false; | ||
if (argC.length() == 1 && argC[0] == '0') { | ||
is_zero = true; | ||
} | ||
uint8_t arg_count = std::atoi(argC.c_str()); | ||
|
||
if (arg_count == 0 && !is_zero) { | ||
print("pasm: parsing error in function section in line {}! argument count is not a number: {}\n", line_nr, argC); | ||
std::exit(1); | ||
} | ||
|
||
return FunctionEntry(fname, arg_count, label); | ||
} | ||
|
||
auto parse_function_entries(std::ifstream& stream, std::size_t& line_nr) -> std::vector<FunctionEntry> { | ||
std::string entry; | ||
std::vector<FunctionEntry> f_entries; | ||
bool valid_block = false; | ||
while (!std::getline(stream, entry).eof()) { | ||
line_nr++; | ||
std::string first_item; | ||
extract_value(first_item, entry); | ||
if (first_item.length() > 0 && first_item[0] == '#') { | ||
// comment | ||
continue; | ||
} | ||
if (first_item == "$end") { | ||
valid_block = true; | ||
break; | ||
} | ||
FunctionEntry e = make_function_entry(entry, line_nr); | ||
f_entries.push_back(e); | ||
} | ||
if (!valid_block) { | ||
print("pasm: parsing error in function section! function section not closed with $end\n"); | ||
std::exit(1); | ||
} | ||
return f_entries; | ||
} | ||
|
||
void check_is_stream_open(std::ifstream& stream, const std::string& file) { | ||
if (!stream.is_open()) { | ||
if (!std::filesystem::is_regular_file(file)) { | ||
print("pasm: fatal: unable to open file `{}' File path doesn't refer to a regular file\n", file); | ||
} else { | ||
print("pasm: fatal: unable to open file `{}' Possible cause:\n", file); | ||
print(" - No read access to the file.\n"); | ||
print(" - The file is locked by another process.\n"); | ||
print(" - A file system error occured.\n"); | ||
print(" - Unknown error.\n"); | ||
} | ||
std::exit(1); | ||
} | ||
} | ||
|
||
void run(const PasmOption& opt, const std::string& file) { | ||
std::ifstream stream(file, std::ios::binary); | ||
check_is_stream_open(stream, file); | ||
std::string line; | ||
std::size_t line_nr = 0; | ||
std::vector<FunctionEntry> function_entries; | ||
while (!std::getline(stream, line).eof()) { | ||
line_nr++; | ||
std::string header; | ||
extract_value(header, line); | ||
if (header.length() > 0 && header[0] == '#') { | ||
continue; | ||
} | ||
if (header == "$function_section") { | ||
if (function_entries.size() == 0) { | ||
function_entries = parse_function_entries(stream, line_nr); | ||
} else { | ||
print("pasm: fatal: multiple function_section found in line {}!", line_nr); | ||
std::exit(0); | ||
} | ||
} | ||
if (header == "$code_section") { | ||
// parse_code_section(stream, line_nr); | ||
} | ||
} | ||
} | ||
|
||
auto main(int argc, char** argv) -> int { | ||
if (argc < 2) { | ||
fatal(); | ||
return 1; | ||
} | ||
std::vector<std::string> args(argv, argv + argc); | ||
PasmOption opt; | ||
if (argv[1][0] == '-') { | ||
opt = options(args); | ||
} | ||
std::string input_file = *(args.end() - 1); | ||
if (!std::filesystem::exists(input_file)) { | ||
print("pasm: fatal: unable to open input file `{}' No such file or directory\n", input_file); | ||
std::exit(1); | ||
} | ||
|
||
run(opt, input_file); | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
$function_section | ||
2 foo_label | ||
$end | ||
# RESULT:pasm: Missing function entry item in line 2! Content is 2 foo_label |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
$function_section | ||
foo 2 foo_label | ||
#RESULT:pasm: parsing error in function section! function section not closed with $end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
$function_section | ||
foo a foo_label | ||
$end | ||
#RESULT:pasm: parsing error in function section in line 2! argument count is not a number: a |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
$function_section | ||
foo 2 foo_label | ||
$end | ||
# RESULT: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import subprocess | ||
import os | ||
import re | ||
|
||
# Pfade anpassen | ||
PASM_EXECUTABLE = "./pasm" # Pfad zur ausführbaren Datei | ||
TEST_DIR = "./tests/pasm_tests" # Ordner mit den Testdateien | ||
|
||
def run_test(input_file): | ||
# Input-Datei lesen | ||
with open(input_file, "r") as file: | ||
lines = file.readlines() | ||
|
||
# Erwartetes Ergebnis auslesen (z. B. # RESULT: [...]) | ||
expected_result = None | ||
for line in lines: | ||
match = re.match(r"#[ ]*[Rr][Ee][Ss][Uu][Ll][Tt]:\s*(.*)", line) | ||
if match: | ||
expected_result = match.group(1).strip() | ||
break | ||
|
||
if expected_result is None: | ||
print(f"❌ Expected result not definied in {input_file}!") | ||
return False | ||
|
||
# `pasm` mit der Input-Datei ausführen | ||
try: | ||
result = subprocess.run( | ||
[PASM_EXECUTABLE, input_file], | ||
input="".join(lines), # Input übergeben | ||
text=True, # Textausgabe aktivieren | ||
capture_output=True # Ausgabe abfangen | ||
) | ||
except FileNotFoundError: | ||
print(f"❌ Executable '{PASM_EXECUTABLE}' not found!") | ||
return False | ||
|
||
# Ausgabe überprüfen | ||
actual_result = result.stdout.strip() | ||
if len(expected_result) > 0 and actual_result == expected_result: | ||
print(f"✅ Test {os.path.basename(input_file)} passed!") | ||
return True | ||
else: | ||
if len(expected_result)==0: | ||
if result.returncode == 0: | ||
print(f"✅ Test {os.path.basename(input_file)} passed!") | ||
return True | ||
print(f"❌ Test {os.path.basename(input_file)} failed!") | ||
print(f" Expected: {expected_result}") | ||
print(f" Result: {actual_result}") | ||
return False | ||
|
||
def main(): | ||
# Alle `.input`-Dateien im Testverzeichnis finden | ||
input_files = [f for f in os.listdir(TEST_DIR) if f.endswith(".pasm")] | ||
|
||
if not input_files: | ||
print("❌ No input file found!") | ||
return | ||
|
||
# Tests ausführen | ||
passed = 0 | ||
failed = 0 | ||
for input_file in input_files: | ||
if run_test(os.path.join(TEST_DIR, input_file)): | ||
passed += 1 | ||
else: | ||
failed += 1 | ||
|
||
# Zusammenfassung | ||
print("\n--- Test conclusion ---") | ||
print(f"Passed: {passed}") | ||
print(f"Failed: {failed}") | ||
print(f"Sum: {len(input_files)}") | ||
|
||
if __name__ == "__main__": | ||
main() |