diff --git a/.gitignore b/.gitignore index 0d7a9b9..b42088d 100644 --- a/.gitignore +++ b/.gitignore @@ -75,4 +75,8 @@ bgpool *.gscc *.gsccasm *.cscc -*.csccasm \ No newline at end of file +*.csccasm +*.gscp +*.gscasmp +*.cscp +*.cscasmp \ No newline at end of file diff --git a/src/acts/compiler/gsc_compiler.cpp b/src/acts/compiler/gsc_compiler.cpp index ef74d04..58ea908 100644 --- a/src/acts/compiler/gsc_compiler.cpp +++ b/src/acts/compiler/gsc_compiler.cpp @@ -414,13 +414,14 @@ class AscmNodeJump : public AscmNodeOpCode { }; struct PreProcessorOption { - std::set defines{}; + std::unordered_set defines{}; }; class GscCompilerOption { public: bool m_help{}; bool m_computeDevOption{}; + const char* m_preproc{}; VmInfo* m_vmInfo{}; Platform m_platform{ Platform::PLATFORM_PC }; const char* m_outFileName{ "compiled"}; @@ -471,6 +472,13 @@ class GscCompilerOption { else if (!strcmp("-d", arg) || !_strcmpi("--dbg", arg)) { m_computeDevOption = true; } + else if (!_strcmpi("--preproc", arg)) { + if (i + 1 == endIndex) { + LOG_ERROR("Missing value for param: {}!", arg); + return false; + } + m_preproc = args[++i]; + } else if (*arg == '-') { if (arg[1] == 'D' && arg[2]) { processorOpt.defines.insert(arg + 2); @@ -501,6 +509,7 @@ class GscCompilerOption { LOG_INFO("-d --dbg : Add dev options"); LOG_INFO("-o --output [f] : Set output file (without extension), default: 'compiled'"); LOG_INFO("-D[name] : Define variable"); + LOG_DEBUG("--preproc [f] : Export preproc result into f"); } }; @@ -3225,14 +3234,234 @@ size_t FindEndLineDelta(const char* d) { return d - s; } -bool ApplyPreProcessor(std::string& str, PreProcessorOption& opt) { - const char* start = str.data(); - char* d = str.data(); +bool TrimDefineVal(std::string& val) { + if (val.empty()) { + return false; + } + size_t start{}; + size_t end{ val.length() }; + + while (start < val.length() && isspace(val[start])) { + start++; + } + + while (end > start && isspace(val[end - 1])) { + end--; + } + + if (end > start) { + val = val.substr(start, end - start); + for (size_t i = 0; i < val.length(); i++) { + if (isspace(val[i])) { + return false; + } + } + return true; + } + + return false; +} + +bool HasOnlySpaceAfter(const std::string_view& val, size_t idx) { + for (size_t i = idx; i < val.length(); i++) { + if (!isspace(val[i])) { + return false; + } + } + return true; +} + +inline void SetBlankChar(char& c) { + if (!isspace(c)) { + c = ' '; // keep same struct + } +} + +bool ApplyPreProcessorComments(InputInfo& info, std::string& str, PreProcessorOption& opt) { + size_t idx{}; + char* data = str.data(); + char* dataEnd = data + str.length(); + + while (*data) { + char c = data[0]; + + if (c == '"') { + // skip string + data++; + while (data != dataEnd) { + if (data[0] == '\\') { + data++; + if (data == dataEnd) { + break; // invalid \? + } + } + else if (data[0] == '"') { + data++; + break; + } + data++; + } + } + else if (c == '/' && data[1] == '/') { + // skip line comment + + SetBlankChar(*(data++)); // / + do { + SetBlankChar(*(data++)); + } while (data != dataEnd && *data != '\n'); + } + else if (c == '/' && data[1] == '*') { + // skip comment + SetBlankChar(*(data++)); // / + + do { + SetBlankChar(*(data++)); + } while (data != dataEnd && !(data[0] == '*' && data[1] == '/')); + + if (data == dataEnd) { + info.PrintLineMessage(alogs::LVL_ERROR, nullptr, "No end for multiline comments"); + return false; + } + SetBlankChar(*(data++)); + SetBlankChar(*(data++)); // */ + } + else { + data++; + } + } - // todo return true; } +bool ApplyPreProcessor(InputInfo& info, std::string& str, PreProcessorOption& opt) { + if (!ApplyPreProcessorComments(info, str, opt)) { + return false; + } + size_t lineStart{}; + size_t lineIdx{}; + bool err{}; + + std::stack eraseCtx{}; + + while (lineStart < str.length()) { + lineIdx++; + size_t next = str.find("\n", lineStart); + + if (next == std::string::npos) { + next = str.length(); // last line + } + + std::string_view line{ str.data() + lineStart, str.data() + next }; + if (line.starts_with("#ifdef")) { + std::string define{ line.substr(6) }; + if (define.length() < 1 || !isspace(define[0])) { + info.PrintLineMessage(alogs::LVL_ERROR, lineIdx, 0, "#ifdef should be used with a parameter"); + err = true; + } + else if (!TrimDefineVal(define)) { + info.PrintLineMessage(alogs::LVL_ERROR, lineIdx, 0, "#ifdef should be used with one valid parameter"); + err = true; + } + else { + bool forceDel = eraseCtx.size() && eraseCtx.top(); + + if (!forceDel) { + forceDel = !opt.defines.contains(define); + } + eraseCtx.push(forceDel); + } + } + else if (line.starts_with("#ifndef")) { + std::string define{ line.substr(7) }; + if (define.length() < 1 || !isspace(define[0])) { + info.PrintLineMessage(alogs::LVL_ERROR, lineIdx, 0, "#ifndef should be used with a parameter"); + err = true; + } + else if (!TrimDefineVal(define)) { + info.PrintLineMessage(alogs::LVL_ERROR, lineIdx, 0, "#ifndef should be used with one valid parameter"); + err = true; + } + else { + bool del = eraseCtx.size() && eraseCtx.top(); + + if (!del) { + del = opt.defines.contains(define); + } + eraseCtx.push(del); + } + } + else if (line.starts_with("#else")) { + if (!HasOnlySpaceAfter(line, 5)) { + info.PrintLineMessage(alogs::LVL_ERROR, lineIdx, 0, "#else can't have parameters"); + err = true; + } + else if (eraseCtx.empty()) { + info.PrintLineMessage(alogs::LVL_ERROR, lineIdx, 0, "usage of #else without start if"); + err = true; + } + else { + bool curr = eraseCtx.top(); + eraseCtx.pop(); + + if (!eraseCtx.empty()) { + // at least 2, we need to check the parent ctx + if (!eraseCtx.top()) { + eraseCtx.push(false); + } + else { + eraseCtx.push(!curr); + } + } + else { + eraseCtx.push(!curr); + } + } + } + else if (line.starts_with("#endif")) { + if (!HasOnlySpaceAfter(line, 6)) { + info.PrintLineMessage(alogs::LVL_ERROR, lineIdx, 0, "#endif can't have parameters"); + err = true; + } + else if (eraseCtx.empty()) { + info.PrintLineMessage(alogs::LVL_ERROR, lineIdx, 0, "usage of #endif without start if"); + err = true; + } + else { + eraseCtx.pop(); + } + } + else if (line.starts_with("#define")) { + std::string define{ line.substr(7) }; + if (define.length() < 1 || !isspace(define[0])) { + info.PrintLineMessage(alogs::LVL_ERROR, lineIdx, 0, "#define should be used with a parameter"); + err = true; + } + else if (!TrimDefineVal(define)) { + info.PrintLineMessage(alogs::LVL_ERROR, lineIdx, 0, "#define should be used with one valid parameter"); + err = true; + } + else { + opt.defines.insert(define); + } + } + else { + if (eraseCtx.empty() || !eraseCtx.top()) { + lineStart = next + 1; + continue; + } + } + + + for (size_t i = lineStart; i < next; i++) { + SetBlankChar(str[i]); + } + + lineStart = next + 1; + } + + return !err; +} + int compiler(Process& proc, int argc, const char* argv[]) { GscCompilerOption opt; if (!opt.Compute(argv, 2, argc) || opt.m_help) { @@ -3300,15 +3529,19 @@ int compiler(Process& proc, int argc, const char* argv[]) { } } - if (!ApplyPreProcessor(info.gscData, opt.processorOpt)) { + if (!ApplyPreProcessor(info, info.gscData, opt.processorOpt)) { LOG_ERROR("Error when applying preprocessor on GSC data"); return tool::BASIC_ERROR; } - if (!ApplyPreProcessor(info.cscData, opt.processorOpt)) { + if (!ApplyPreProcessor(info, info.cscData, opt.processorOpt)) { LOG_ERROR("Error when applying preprocessor on CSC data"); return tool::BASIC_ERROR; } + if (opt.m_preproc) { + utils::WriteFile(opt.m_preproc, info.gscData); + } + ANTLRInputStream is{ info.gscData }; gscLexer lexer{ &is }; diff --git a/src/acts/tools/gsc_opcodes.cpp b/src/acts/tools/gsc_opcodes.cpp index 0d7139a..929def6 100644 --- a/src/acts/tools/gsc_opcodes.cpp +++ b/src/acts/tools/gsc_opcodes.cpp @@ -115,6 +115,9 @@ namespace tool::gsc::opcode { case '\v': out << "\\v"; break; + case '"': + out << "\\\""; + break; default: if (*str < 0x20) { out << "\\" << std::oct << (int)(*str) << std::dec; @@ -3485,7 +3488,7 @@ class OPCodeInfoGetString : public OPCodeInfo { base += 4; if (str) { - out << "\"" << str << "\"\n"; + PrintFormattedString(out << "\"", str) << "\"\n"; if (context.m_runDecompiler) { context.PushASMCNode(new ASMContextNodeString(str)); } diff --git a/test/gsc-compiler/unit_tests.gsc b/test/gsc-compiler/unit_tests.gsc index 755146d..bb8bd12 100644 --- a/test/gsc-compiler/unit_tests.gsc +++ b/test/gsc-compiler/unit_tests.gsc @@ -1,9 +1,18 @@ #namespace unittest; +#define UNIT_TEST + +/* + * Print log message + */ function test_print(msg) { + // print message shieldlog("UNITTEST: " + msg); } +/* + * Run debug test + */ function autoexec run_tests() { test_print("Begin"); @@ -15,9 +24,12 @@ function autoexec run_tests() { test_print("End"); - level notify(#"acts_end_test"); + level notify(#"acts_end_test"); // notify end message to stop the thread in case of error } +/* + * Run local var tests + */ function local() { test_print("-- local"); a = 0; @@ -60,8 +72,11 @@ function local() { a0 = [1, 2, 3]; } +/* + * Run local structs tests + */ function local_structs() { - test_print("-- local array"); + test_print("-- local structs"); s = { #a: { @@ -97,6 +112,9 @@ function local_structs() { } +/* + * Run level object tests + */ function level_object() { test_print("level object"); level.acts = {}; @@ -144,6 +162,9 @@ function level_object() { test_print("level object array clear: isdefined: " + isdefined(level.acts.a)); } +/* + * Run func calls tests + */ function func_calls() { test_print("func calls"); @@ -269,6 +290,9 @@ function ev_notif_waiter() { } } +/* + * Run notif tests + */ function notif_tests() { test_print("-- notif"); s = {}; diff --git a/test/gsc-parser/preproc.gsc b/test/gsc-parser/preproc.gsc new file mode 100644 index 0000000..7b2f153 --- /dev/null +++ b/test/gsc-parser/preproc.gsc @@ -0,0 +1,24 @@ +#define AAA +#define BBB + +#ifdef AAA +aaa +#else +not aaa +#endif + + +#ifdef AAA +#ifdef AAA +aaa +#else +not aaa +#endif +#else +not aaa +#ifndef BBB +#ifdef AAA +aaa +#endif +#endif +#endif \ No newline at end of file