Skip to content

Commit

Permalink
-
Browse files Browse the repository at this point in the history
  • Loading branch information
michael.burzan committed Jan 25, 2025
1 parent 31bf972 commit 6c24fac
Show file tree
Hide file tree
Showing 8 changed files with 347 additions and 0 deletions.
34 changes: 34 additions & 0 deletions include/Util.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,38 @@ template <class X, class Y> struct is_comparable {

template <class X, class Y> constexpr bool is_comparable_v = is_comparable<X, Y>::value;

// trim from start (in place)
inline void ltrim(std::string& s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); }));
}

// trim from end (in place)
inline void rtrim(std::string& s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), s.end());
}

// trim from both ends (in place)
inline void trim(std::string& s) {
rtrim(s);
ltrim(s);
}

// trim from start (copying)
inline std::string ltrim_copy(std::string s) {
ltrim(s);
return s;
}

// trim from end (copying)
inline std::string rtrim_copy(std::string s) {
rtrim(s);
return s;
}

// trim from both ends (copying)
inline std::string trim_copy(std::string s) {
trim(s);
return s;
}

#endif
12 changes: 12 additions & 0 deletions include/VirtualMachine.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <cstdlib>
#include <functional>
#include <iostream>
#include <iterator>
#include <utility>
#include <variant>

Expand All @@ -19,6 +20,10 @@ struct FunctionEntry {
: _name(std::move(name)), _argument_count(arg_count), _address(adr) {
}

FunctionEntry(std::string name, uint8_t arg_count, const std::string& label)
: _name(std::move(name)), _argument_count(arg_count), _label(label) {
}

auto name() const -> const std::string& {
return _name;
}
Expand All @@ -28,11 +33,18 @@ struct FunctionEntry {
auto address() const -> std::size_t {
return _address;
}
auto label() const -> const std::string& {
return _label;
}
void address(std::size_t adr) {
_address = adr;
}

private:
std::string _name;
uint8_t _argument_count;
std::size_t _address;
std::string _label;
};

template <class VM> using NativeFunction = std::function<ResultOr<bool>(VM* vm, const std::vector<VMType>&)>;
Expand Down
209 changes: 209 additions & 0 deletions src/pasm/pasm.cpp
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;
}
4 changes: 4 additions & 0 deletions tests/pasm_tests/syntax_failed1.pasm
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
3 changes: 3 additions & 0 deletions tests/pasm_tests/syntax_failed2.pasm
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
4 changes: 4 additions & 0 deletions tests/pasm_tests/syntax_failed3.pasm
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
4 changes: 4 additions & 0 deletions tests/pasm_tests/syntax_test.pasm
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
$function_section
foo 2 foo_label
$end
# RESULT:
77 changes: 77 additions & 0 deletions toolchain/executable_tester.py
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()

0 comments on commit 6c24fac

Please sign in to comment.