From 709119c90c5707308ccbb9a118ccf949c3d3b6cd Mon Sep 17 00:00:00 2001 From: cnf13 Date: Sat, 31 Jan 2026 21:18:13 -0500 Subject: [PATCH 1/4] WIP Refactor --- nvse/nvse/Compiler/AST/AST.h | 620 +++++++++ nvse/nvse/Compiler/AST/ASTForward.h | 65 + nvse/nvse/Compiler/Compilation.cpp | 1175 ++++++++++++++++ nvse/nvse/Compiler/Compilation.h | 258 ++++ nvse/nvse/Compiler/Lexer/Lexer.cpp | 464 +++++++ nvse/nvse/Compiler/Lexer/Lexer.h | 235 ++++ nvse/nvse/Compiler/NVSECompilerUtils.cpp | 523 +++++++ nvse/nvse/Compiler/NVSECompilerUtils.h | 162 +++ nvse/nvse/Compiler/NVSETreePrinter.cpp | 529 ++++++++ nvse/nvse/Compiler/NVSETreePrinter.h | 44 + nvse/nvse/Compiler/NVSETypeChecker.cpp | 937 +++++++++++++ nvse/nvse/Compiler/NVSETypeChecker.h | 63 + nvse/nvse/Compiler/Parser/Parser.cpp | 1053 +++++++++++++++ nvse/nvse/Compiler/Parser/Parser.h | 80 ++ .../Compiler/Passes/VariableResolution.cpp | 189 +++ .../nvse/Compiler/Passes/VariableResolution.h | 104 ++ nvse/nvse/Compiler/Visitor.cpp | 189 +++ nvse/nvse/Compiler/Visitor.h | 43 + nvse/nvse/Hooks_Script.cpp | 30 +- nvse/nvse/NVSEAst.h | 481 ------- nvse/nvse/NVSECompiler.cpp | 1196 ----------------- nvse/nvse/NVSECompiler.h | 256 ---- nvse/nvse/NVSECompilerUtils.cpp | 541 -------- nvse/nvse/NVSECompilerUtils.h | 160 --- nvse/nvse/NVSELexer.cpp | 458 ------- nvse/nvse/NVSELexer.h | 211 --- nvse/nvse/NVSEParser.cpp | 1017 -------------- nvse/nvse/NVSEParser.h | 76 -- nvse/nvse/NVSEScope.h | 115 -- nvse/nvse/NVSETreePrinter.cpp | 524 -------- nvse/nvse/NVSETreePrinter.h | 42 - nvse/nvse/NVSETypeChecker.cpp | 964 ------------- nvse/nvse/NVSETypeChecker.h | 65 - nvse/nvse/NVSEVisitor.h | 71 - nvse/nvse/ScriptUtils.cpp | 28 +- nvse/nvse/nvse.vcxproj.filters | 35 +- 36 files changed, 6781 insertions(+), 6222 deletions(-) create mode 100644 nvse/nvse/Compiler/AST/AST.h create mode 100644 nvse/nvse/Compiler/AST/ASTForward.h create mode 100644 nvse/nvse/Compiler/Compilation.cpp create mode 100644 nvse/nvse/Compiler/Compilation.h create mode 100644 nvse/nvse/Compiler/Lexer/Lexer.cpp create mode 100644 nvse/nvse/Compiler/Lexer/Lexer.h create mode 100644 nvse/nvse/Compiler/NVSECompilerUtils.cpp create mode 100644 nvse/nvse/Compiler/NVSECompilerUtils.h create mode 100644 nvse/nvse/Compiler/NVSETreePrinter.cpp create mode 100644 nvse/nvse/Compiler/NVSETreePrinter.h create mode 100644 nvse/nvse/Compiler/NVSETypeChecker.cpp create mode 100644 nvse/nvse/Compiler/NVSETypeChecker.h create mode 100644 nvse/nvse/Compiler/Parser/Parser.cpp create mode 100644 nvse/nvse/Compiler/Parser/Parser.h create mode 100644 nvse/nvse/Compiler/Passes/VariableResolution.cpp create mode 100644 nvse/nvse/Compiler/Passes/VariableResolution.h create mode 100644 nvse/nvse/Compiler/Visitor.cpp create mode 100644 nvse/nvse/Compiler/Visitor.h delete mode 100644 nvse/nvse/NVSEAst.h delete mode 100644 nvse/nvse/NVSECompiler.cpp delete mode 100644 nvse/nvse/NVSECompiler.h delete mode 100644 nvse/nvse/NVSECompilerUtils.cpp delete mode 100644 nvse/nvse/NVSECompilerUtils.h delete mode 100644 nvse/nvse/NVSELexer.cpp delete mode 100644 nvse/nvse/NVSELexer.h delete mode 100644 nvse/nvse/NVSEParser.cpp delete mode 100644 nvse/nvse/NVSEParser.h delete mode 100644 nvse/nvse/NVSEScope.h delete mode 100644 nvse/nvse/NVSETreePrinter.cpp delete mode 100644 nvse/nvse/NVSETreePrinter.h delete mode 100644 nvse/nvse/NVSETypeChecker.cpp delete mode 100644 nvse/nvse/NVSETypeChecker.h delete mode 100644 nvse/nvse/NVSEVisitor.h diff --git a/nvse/nvse/Compiler/AST/AST.h b/nvse/nvse/Compiler/AST/AST.h new file mode 100644 index 00000000..4cca8931 --- /dev/null +++ b/nvse/nvse/Compiler/AST/AST.h @@ -0,0 +1,620 @@ +#pragma once +#include "nvse/ScriptTokens.h" + +#include "ASTForward.h" + +#include "nvse/Compiler/Visitor.h" +#include "nvse/Compiler/Lexer/Lexer.h" + +namespace Compiler { + struct AST { + NVSEToken name; + std::vector globalVars; + std::vector blocks; + std::unordered_map> m_mpFunctions + {}; + + // Required NVSE plugins + std::unordered_map m_mpPluginRequirements{}; + + AST( + NVSEToken name, + std::vector globalVars, + std::vector blocks + ) : name(std::move(name)), + globalVars(std::move(globalVars)), + blocks(std::move(blocks)) { + } + + void Accept(Visitor* visitor) { + visitor->Visit(this); + } + }; + + struct Statement { + size_t line = 0; + + // Some statements store type such as return and block statement + Token_Type detailedType = kTokenType_Invalid; + + virtual ~Statement() = default; + + virtual void Accept(Visitor* t) = 0; + + template + bool IsType() { + return dynamic_cast(this); + } + }; + + namespace Statements { + struct Begin : Statement { + NVSEToken name; + std::optional param; + std::shared_ptr block; + CommandInfo* beginInfo; + + Begin( + const NVSEToken& name, + std::optional param, + std::shared_ptr block, + CommandInfo* beginInfo + ) : name(name), + param(std::move(param)), + block(std::move(block)), + beginInfo(beginInfo) { + line = name.line; + } + + void Accept(Visitor* visitor) override { + visitor->VisitBeginStmt(this); + } + }; + + struct UDFDecl : Statement { + NVSEToken token; + std::optional name; + std::vector> args; + StmtPtr body; + bool is_udf_decl = false; + + UDFDecl( + const NVSEToken& token, + std::vector> args, + StmtPtr body + ) : token(token), + args(std::move(args)), + body(std::move(body)) { + line = token.line; + } + + UDFDecl( + const NVSEToken& token, + const NVSEToken& name, + std::vector> args, + StmtPtr body + ) : token(token), + name(name), + args(std::move(args)), + body(std::move(body)) { + line = token.line; + } + + void Accept(Visitor* visitor) override { + visitor->VisitFnStmt(this); + } + }; + + struct VarDecl : Statement { + struct Declaration { + NVSEToken token; + ExprPtr expr = nullptr; + std::shared_ptr info = nullptr; + }; + + NVSEToken type; + std::vector declarations{}; + + VarDecl( + NVSEToken type, + const std::vector& declarations + ) : type(std::move(type)), + declarations(declarations) + { + } + + VarDecl( + NVSEToken type, + const NVSEToken& name, + ExprPtr value + ) : type(std::move(type)) + { + declarations.emplace_back(name, std::move(value)); + } + + void Accept(Visitor* visitor) override { + visitor->VisitVarDeclStmt(this); + } + }; + + struct ExpressionStatement : Statement { + ExprPtr expr; + + explicit ExpressionStatement(ExprPtr expr) : expr(std::move(expr)) {} + + void Accept(Visitor* visitor) override { + visitor->VisitExprStmt(this); + } + }; + + struct For : Statement { + StmtPtr init; + ExprPtr cond; + ExprPtr post; + std::shared_ptr block; + + For( + StmtPtr init, + ExprPtr cond, + ExprPtr post, + std::shared_ptr block + ) : init(std::move(init)), + cond(std::move(cond)), + post(std::move(post)), + block(std::move(block)) + { + } + + void Accept(Visitor* visitor) override { + visitor->VisitForStmt(this); + } + }; + + struct ForEach : Statement { + std::vector> declarations; + ExprPtr rhs; + std::shared_ptr block; + bool decompose = false; + + ForEach( + std::vector> declarations, + ExprPtr rhs, + std::shared_ptr block, + bool decompose + ) : declarations(std::move(declarations)), + rhs(std::move(rhs)), + block(std::move(block)), + decompose(decompose) + { + } + + void Accept(Visitor* visitor) override { + visitor->VisitForEachStmt(this); + } + }; + + struct If : Statement { + ExprPtr cond; + + std::shared_ptr block; + std::shared_ptr elseBlock; + + If( + ExprPtr cond, + std::shared_ptr&& block, + std::shared_ptr&& elseBlock = nullptr + ) : cond(std::move(cond)), + block(std::move(block)), + elseBlock(std::move(elseBlock)) { + } + + void Accept(Visitor* visitor) override { + visitor->VisitIfStmt(this); + } + }; + + struct Return : Statement { + ExprPtr expr{}; + + Return() = default; + explicit Return(ExprPtr expr) : expr(std::move(expr)) {} + + void Accept(Visitor* visitor) override { + visitor->VisitReturnStmt(this); + } + }; + + struct Continue : Statement { + Continue() = default; + + void Accept(Visitor* visitor) override { + visitor->VisitContinueStmt(this); + } + }; + + struct Break : Statement { + Break() = default; + + void Accept(Visitor* visitor) override { + visitor->VisitBreakStmt(this); + } + }; + + struct While : Statement { + ExprPtr cond; + StmtPtr block; + + While(ExprPtr cond, StmtPtr block) + : cond(std::move(cond)), block(std::move(block)) { + } + + void Accept(Visitor* visitor) override { + visitor->VisitWhileStmt(this); + } + }; + + struct Block : Statement { + std::vector statements; + + explicit Block(std::vector statements) + : statements(std::move(statements)) { + } + + void Accept(Visitor* visitor) override { + visitor->VisitBlockStmt(this); + } + }; + + struct ShowMessage : Statement { + std::shared_ptr message; + std::vector> args{}; + uint32_t message_time{}; + + ShowMessage( + const std::shared_ptr& message, + const std::vector>& args, + const uint32_t messageTime + ) : message(message), + args(args), + message_time(messageTime) { + } + + void Accept(Visitor* visitor) override { + visitor->VisitShowMessageStmt(this); + } + }; + } + + struct Expr { + size_t line = 0; + Token_Type type = kTokenType_Invalid; + + virtual ~Expr() = default; + virtual void Accept(Visitor* t) = 0; + + template + bool IsType() { + return dynamic_cast(this); + } + }; + + namespace Expressions { + struct AssignmentExpr : Expr { + NVSEToken token; + ExprPtr left; + ExprPtr expr; + + AssignmentExpr(NVSEToken token, ExprPtr left, ExprPtr expr) : token( + std::move(token) + ), + left(std::move(left)), + expr(std::move(expr)) { + line = this->token.line; + } + + void Accept(Visitor* t) override { + t->VisitAssignmentExpr(this); + } + }; + + struct TernaryExpr : Expr { + NVSEToken token; + ExprPtr cond; + ExprPtr left; + ExprPtr right; + + TernaryExpr( + NVSEToken token, + ExprPtr cond, + ExprPtr left, + ExprPtr right + ) : token(std::move(token)), + cond(std::move(cond)), + left(std::move(left)), + right(std::move(right)) { + line = this->token.line; + } + + void Accept(Visitor* t) override { + t->VisitTernaryExpr(this); + } + }; + + struct InExpr : Expr { + ExprPtr lhs; + NVSEToken token; + std::vector values{}; + ExprPtr expression{}; + bool isNot; + + InExpr( + ExprPtr lhs, + NVSEToken token, + std::vector exprs, + bool isNot + ) : lhs(std::move(lhs)), + token(std::move(token)), + values(std::move(exprs)), + isNot(isNot) { + line = this->token.line; + } + + InExpr(ExprPtr lhs, NVSEToken token, ExprPtr expr, bool isNot) : lhs( + std::move(lhs) + ), + token(std::move(token)), + expression(std::move(expr)), + isNot(isNot) { + line = this->token.line; + } + + void Accept(Visitor* visitor) override { + visitor->VisitInExpr(this); + } + }; + + struct BinaryExpr : Expr { + NVSEToken op; + ExprPtr left, right; + + BinaryExpr(NVSEToken op, ExprPtr left, ExprPtr right) : op(std::move(op)), + left(std::move(left)), + right(std::move(right)) { + line = this->op.line; + } + + void Accept(Visitor* t) override { + t->VisitBinaryExpr(this); + } + }; + + struct UnaryExpr : Expr { + NVSEToken op; + ExprPtr expr; + bool postfix; + + UnaryExpr(NVSEToken token, ExprPtr expr, const bool postfix) : op( + std::move(token) + ), + expr(std::move(expr)), + postfix(postfix) { + line = this->op.line; + } + + void Accept(Visitor* t) override { + t->VisitUnaryExpr(this); + } + }; + + struct SubscriptExpr : Expr { + NVSEToken op; + + ExprPtr left; + ExprPtr index; + + SubscriptExpr(NVSEToken token, ExprPtr left, ExprPtr index) : op( + std::move(token) + ), + left(std::move(left)), + index(std::move(index)) { + line = this->op.line; + } + + void Accept(Visitor* visitor) override { + visitor->VisitSubscriptExpr(this); + } + }; + + struct CallExpr : Expr { + ExprPtr left; + NVSEToken token; + std::vector args; + + // Set by typechecker + CommandInfo* cmdInfo = nullptr; + + CallExpr(ExprPtr left, NVSEToken token, std::vector args) : left( + std::move(left) + ), + token(std::move(token)), + args(std::move(args)) { + line = this->token.line; + } + + void Accept(Visitor* t) override { + t->VisitCallExpr(this); + } + }; + + struct GetExpr : Expr { + ExprPtr left; + NVSEToken token; + NVSEToken identifier; + + // Resolved in typechecker + VariableInfo* var_info = nullptr; + const char* reference_name = nullptr; + + + GetExpr( + NVSEToken token, + ExprPtr left, + NVSEToken identifier + ) : left(std::move(left)), + token(std::move(token)), + identifier(std::move(identifier)) + { + line = this->token.line; + } + + void Accept(Visitor* t) override { + t->VisitGetExpr(this); + } + }; + + struct BoolExpr : Expr { + bool value; + NVSEToken token; + + BoolExpr( + NVSEToken&& token, + const bool value + ) : value(value), + token(std::move(token)) + { + line = this->token.line; + } + + void Accept(Visitor* t) override { + t->VisitBoolExpr(this); + } + }; + + struct NumberExpr : Expr { + double value; + // For some reason axis enum is one byte and the rest are two? + int enum_len; + bool is_fp; + NVSEToken token; + + NumberExpr( + NVSEToken token, + const double value, + const bool isFp, + const int enumLen = 0 + ) : value(value), + enum_len(enumLen), + is_fp(isFp), + token(std::move(token)) + { + line = this->token.line; + } + + void Accept(Visitor* t) override { + t->VisitNumberExpr(this); + } + }; + + struct StringExpr : Expr { + NVSEToken token; + + explicit StringExpr(NVSEToken token) : + token(std::move(token)) + { + line = this->token.line; + } + + void Accept(Visitor* t) override { + t->VisitStringExpr(this); + } + }; + + struct IdentExpr : Expr { + NVSEToken token; + TESForm* form = nullptr; + + // Set during typechecker variable resolution so that compiler can reference + std::shared_ptr varInfo{ nullptr }; + + explicit IdentExpr(NVSEToken token) : + token(std::move(token)) + { + line = this->token.line; + } + + void Accept(Visitor* t) override { + t->VisitIdentExpr(this); + } + }; + + struct ArrayLiteralExpr : Expr { + NVSEToken token; + std::vector values; + + ArrayLiteralExpr( + NVSEToken token, + std::vector values + ) : token(std::move(token)), + values(std::move(values)) + { + line = this->token.line; + } + + void Accept(Visitor* t) override { + return t->VisitArrayLiteralExpr(this); + } + }; + + struct MapLiteralExpr : Expr { + std::vector values; + NVSEToken token; + + MapLiteralExpr( + NVSEToken&& token, + std::vector&& values + ) : + values{ std::move(values) }, + token{ std::move(token) } { + line = this->token.line; + } + + void Accept(Visitor* t) override { + return t->VisitMapLiteralExpr(this); + } + }; + + struct GroupingExpr : Expr { + ExprPtr expr; + + explicit GroupingExpr(ExprPtr expr) + : expr(std::move(expr)) { + } + + void Accept(Visitor* t) override { + t->VisitGroupingExpr(this); + } + }; + + struct LambdaExpr : Expr { + std::vector> args; + StmtPtr body; + + LambdaExpr( + std::vector>&& args, + StmtPtr body + ) + : args(std::move(args)), body(std::move(body)) { + } + + void Accept(Visitor* t) override { + t->VisitLambdaExpr(this); + } + + // Set via type checker + struct { + std::vector paramTypes{}; + Token_Type returnType = kTokenType_Invalid; + } typeinfo; + }; + } +} diff --git a/nvse/nvse/Compiler/AST/ASTForward.h b/nvse/nvse/Compiler/AST/ASTForward.h new file mode 100644 index 00000000..ed03abb5 --- /dev/null +++ b/nvse/nvse/Compiler/AST/ASTForward.h @@ -0,0 +1,65 @@ +#pragma once + +#include + +namespace Compiler { + struct AST; + + struct Statement; + struct Expr; + + using ExprPtr = std::shared_ptr; + using StmtPtr = std::shared_ptr; + + namespace Statements { + struct Begin; + struct UDFDecl; + struct VarDecl; + + struct ExpressionStatement; + struct For; + struct ForEach; + struct If; + struct Return; + struct Continue; + struct Break; + struct While; + struct Block; + struct ShowMessage; + } + + namespace Expressions { + struct AssignmentExpr; + struct TernaryExpr; + struct InExpr; + struct BinaryExpr; + struct UnaryExpr; + struct SubscriptExpr; + struct CallExpr; + struct GetExpr; + struct BoolExpr; + struct NumberExpr; + struct StringExpr; + struct IdentExpr; + struct MapLiteralExpr; + struct ArrayLiteralExpr; + struct GroupingExpr; + struct LambdaExpr; + } + + struct VarInfo { + size_t index; + std::string original_name; + std::string remapped_name; + bool pre_existing = false; + + Script::VariableType type; + Token_Type detailed_type; + + struct { + bool is_lambda; + std::vector param_types{}; + Token_Type return_type = kTokenType_Invalid; + } lambda_type_info; + }; +} \ No newline at end of file diff --git a/nvse/nvse/Compiler/Compilation.cpp b/nvse/nvse/Compiler/Compilation.cpp new file mode 100644 index 00000000..d02863ca --- /dev/null +++ b/nvse/nvse/Compiler/Compilation.cpp @@ -0,0 +1,1175 @@ +#include "Compilation.h" + +#include "../Commands_Array.h" +#include "../Commands_MiscRef.h" +#include "../Commands_Scripting.h" +#include "../Hooks_Script.h" +#include "../PluginAPI.h" + +#include + +#include "NVSECompilerUtils.h" + +namespace Compiler { + enum OPCodes { + OP_LET = 0x1539, + OP_EVAL = 0x153A, + OP_WHILE = 0x153B, + OP_LOOP = 0x153C, + OP_FOREACH = 0x153D, + OP_FOREACH_ALT = 0x1670, + OP_AR_EXISTS = 0x1671, + OP_TERNARY = 0x166E, + OP_CALL = 0x1545, + OP_SET_MOD_LOCAL_DATA = 0x1549, + OP_GET_MOD_LOCAL_DATA = 0x1548, + OP_SET_FUNCTION_VALUE = 0x1546, + OP_MATCHES_ANY = 0x166F, + OP_AR_LIST = 0x1567, + OP_AR_MAP = 0x1568, + OP_AR_FIND = 0x1557, + OP_AR_BAD_NUMERIC_INDEX = 0x155F, + OP_CONTINUE = 0x153E, + OP_BREAK = 0x153F, + OP_VERSION = 0x1676, + }; + + void Compilation::PrintScriptInfo() { + + // Debug print local info + CompDbg("\n[Locals]\n"); + for (int i = 0; i < engineScript->varList.Count(); i++) { + auto item = engineScript->varList.GetNthItem(i); + CompDbg("%d: %s %s\n", item->idx, item->name.CStr(), usedVars.contains(item->name.CStr()) ? "" : "(unused)"); + } + + CompDbg("\n"); + + // Refs + CompDbg("[Refs]\n"); + for (int i = 0; i < engineScript->refList.Count(); i++) { + const auto ref = engineScript->refList.GetNthItem(i); + if (ref->varIdx) { + CompDbg("%d: (Var %d)\n", i, ref->varIdx); + } + else { + CompDbg("%d: %s\n", i, ref->form->GetEditorID()); + } + } + + CompDbg("\n"); + + // Script data + CompDbg("[Data]\n"); + for (int i = 0; i < engineScript->info.dataLength; i++) { + CompDbg("%02X ", engineScript->data[i]); + } + + CompInfo("\n\n"); + CompInfo("[Requirements]\n"); + for (const auto& [plugin, version] : ast.m_mpPluginRequirements) { + if (!_stricmp(plugin.c_str(), "nvse")) { + CompInfo("%s [%d.%d.%d]\n", plugin.c_str(), version >> 24 & 0xFF, version >> 16 & 0xFF, version >> 4 & 0xFF); + } + else { + CompInfo("%s [%d]\n", plugin.c_str(), version); + } + } + + CompDbg("\n"); + CompDbg("\nNum compiled bytes: %d\n", engineScript->info.dataLength); + } + + bool Compilation::Compile() { + CompDbg("\n==== COMPILER ====\n\n"); + + insideNvseExpr.push(false); + loopIncrements.push(nullptr); + statementCounter.push(0); + scriptStart.push(0); + + ast.Accept(this); + + if (!partial) { + engineScript->SetEditorID(scriptName.c_str()); + } + + engineScript->info.compiled = true; + engineScript->info.dataLength = data.size(); + engineScript->info.numRefs = engineScript->refList.Count(); + engineScript->info.varCount = engineScript->varList.Count(); + engineScript->info.unusedVariableCount = engineScript->info.varCount - usedVars.size(); + engineScript->data = static_cast(FormHeap_Allocate(data.size())); + memcpy(engineScript->data, data.data(), data.size()); + + PrintScriptInfo(); + + return true; + } + + void Compilation::Visit(AST* nvScript) { + // Compile the script name + scriptName = nvScript->name.lexeme; + + // Dont allow naming script the same as another form, unless that form is the script itself + const auto comp = strcmp(scriptName.c_str(), originalScriptName); + if (ResolveObjReference(scriptName, false) && comp && !partial) { + //TODO throw std::runtime_error(std::format("Error: Form name '{}' is already in use.\n", scriptName)); + } + + // SCN + AddU32(static_cast(ScriptParsing::ScriptStatementCode::ScriptName)); + + // Add script requirement opcodes + for (const auto& [plugin, version] : ast.m_mpPluginRequirements) { + StartCall(OP_VERSION); + StartManualArg(); + AddString(plugin); + FinishManualArg(); + StartManualArg(); + AddU8('L'); + AddU32(version); + FinishManualArg(); + FinishCall(); + } + + for (auto& global_var : nvScript->globalVars) { + global_var->Accept(this); + } + + for (auto& block : nvScript->blocks) { + block->Accept(this); + } + } + + void Compilation::VisitBeginStmt(Statements::Begin* stmt) { + auto name = stmt->name.lexeme; + + // Shouldn't be null + const CommandInfo* beginInfo = stmt->beginInfo; + + // OP_BEGIN + AddU16(static_cast(ScriptParsing::ScriptStatementCode::Begin)); + + auto beginPatch = AddU16(0x0); + auto beginStart = data.size(); + + AddU16(beginInfo->opcode); + + const auto blockPatch = AddU32(0x0); + + // Add param shit here? + if (stmt->param.has_value()) { + auto param = stmt->param.value(); + + // Num args + AddU16(0x1); + + if (beginInfo->params[0].typeID == kParamType_Integer) { + AddU8('n'); + AddU32(static_cast(std::get(param.value))); + } + + // All other instances use ref? + else { + // Try to resolve the global ref + if (auto ref = ResolveObjReference(param.lexeme)) { + AddU8('r'); + AddU16(ref); + } + else { + throw std::runtime_error(std::format("Unable to resolve form '{}'.", param.lexeme)); + } + } + } + else { + if (stmt->beginInfo->numParams > 0) { + AddU16(0x0); + } + } + + SetU16(beginPatch, data.size() - beginStart); + const auto blockStart = data.size(); + + CompileBlock(stmt->block, true); + + // OP_END + AddU32(static_cast(ScriptParsing::ScriptStatementCode::End)); + + SetU32(blockPatch, data.size() - blockStart); + } + + void Compilation::VisitFnStmt(Statements::UDFDecl* stmt) { + // OP_BEGIN + AddU16(static_cast(ScriptParsing::ScriptStatementCode::Begin)); + + // OP_MODE_LEN + const auto opModeLenPatch = AddU16(0x0); + const auto opModeStart = data.size(); + + // OP_MODE + AddU16(0x0D); + + // SCRIPT_LEN + auto scriptLenPatch = AddU32(0x0); + + // BYTECODE VER + AddU8(0x1); + + // arg count + AddU8(stmt->args.size()); + + // add args + for (const auto& arg : stmt->args) { + for (auto& [_, __, var] : arg->declarations) { + AddU16(var->index); + AddU8(var->type); + } + } + + // NUM_ARRAY_ARGS, always 0 + AddU8(0x0); + + auto scriptLenStart = data.size(); + + // Patch mode len + SetU16(opModeLenPatch, data.size() - opModeStart); + + // Compile script + CompileBlock(stmt->body, false); + + // OP_END + AddU32(static_cast(ScriptParsing::ScriptStatementCode::End)); + + SetU32(scriptLenPatch, data.size() - scriptLenStart); + } + + void Compilation::VisitVarDeclStmt(Statements::VarDecl* stmt) { + const uint8_t varType = GetScriptTypeFromToken(stmt->type); + + // Since we are doing multiple declarations at once, manually handle count here + statementCounter.top()--; + + for (const auto& [token, value, info] : stmt->declarations) { + auto& name = info->remapped_name; + + // Compile lambdas differently + // Does not affect params as they cannot have value specified + if (value->IsType()) { + // To do a similar thing on access + lambdaVars.insert(name); + + StartCall(OP_SET_MOD_LOCAL_DATA); + StartManualArg(); + AddString(std::format("__temp_{}_lambda_{}", scriptName, info->index)); + FinishManualArg(); + AddCallArg(value); + FinishCall(); + continue; + } + + for (int i = 0; i < statementCounter.size(); i++) { + CompDbg(" "); + } + + CompDbg("Defined global variable %s\n", name.c_str()); + + // if (var->isGlobal) { + // AddVar(name, varType); + // } + + if (!value) { + continue; + } + + statementCounter.top()++; + + StartCall(OP_LET); + StartManualArg(); + + // Add arg to stack + AddU8('V'); + AddU8(varType); + AddU16(0); + AddU16(info->index); + + // Build expression + value->Accept(this); + + // OP_ASSIGN + AddU8(0); + + FinishManualArg(); + FinishCall(); + } + } + + void Compilation::VisitExprStmt(const Statements::ExpressionStatement* stmt) { + // These do not need to be wrapped in op_eval + if (stmt->expr->IsType() || stmt->expr->IsType()) { + stmt->expr->Accept(this); + } + else if (stmt->expr) { + StartCall(OP_EVAL); + AddCallArg(stmt->expr); + FinishCall(); + } + else { + // Decrement counter as this is a NOP + statementCounter.top()--; + } + } + + void Compilation::VisitForStmt(Statements::For* stmt) { + if (stmt->init) { + stmt->init->Accept(this); + statementCounter.top()++; + } + + // Store loop increment to be generated before OP_LOOP/OP_CONTINUE + if (stmt->post) { + loopIncrements.push(std::make_shared(stmt->post)); + } + else { + loopIncrements.push(nullptr); + } + + // This is pretty much a copy of the while loop code, + // but it does something slightly different with the loopIncrements stack + + // OP_WHILE + AddU16(OP_WHILE); + + // Placeholder OP_LEN + auto exprPatch = AddU16(0x0); + auto exprStart = data.size(); + + auto jmpPatch = AddU32(0x0); + + // 1 param + AddU8(0x1); + + // Compile / patch condition + const auto condStart = data.size(); + const auto condPatch = AddU16(0x0); + insideNvseExpr.push(true); + stmt->cond->Accept(this); + insideNvseExpr.pop(); + SetU16(condPatch, data.size() - condStart); + + // Patch OP_LEN + SetU16(exprPatch, data.size() - exprStart); + + // Compile block + CompileBlock(stmt->block, true); + + // If we have a loop increment (for loop) + // Emit that before OP_LOOP + if (loopIncrements.top()) { + loopIncrements.top()->Accept(this); + statementCounter.top()++; + } + + // OP_LOOP + AddU16(OP_LOOP); + AddU16(0x0); + + // OP_LOOP is an extra statement + statementCounter.top()++; + + // Patch jmp + SetU32(jmpPatch, data.size() - scriptStart.top()); + + loopIncrements.pop(); + } + + void Compilation::VisitForEachStmt(Statements::ForEach* stmt) { + if (stmt->decompose) { + // Try to resolve second var if there is one + std::shared_ptr var1 = nullptr; + if (stmt->declarations[0]) { + var1 = stmt->declarations[0]->declarations[0].info; + } + + // Try to resolve second var if there is one + std::shared_ptr var2 = nullptr; + if (stmt->declarations.size() == 2 && stmt->declarations[1]) { + var2 = stmt->declarations[1]->declarations[0].info; + } + + // OP_FOREACH + AddU16(OP_FOREACH_ALT); + + // Placeholder OP_LEN + const auto exprPatch = AddU16(0x0); + const auto exprStart = data.size(); + const auto jmpPatch = AddU32(0x0); + + // Num args + // Either 2 or 3, depending on: + // "for ([int iKey, _] in { "1"::2 "3"::4 }" - 3 args, even though one can be an invalid variable + // VS: + // "for ([int iKey] in { "1"::2 "3"::4 }" - 2 args + AddU8(1 + stmt->declarations.size()); + insideNvseExpr.push(true); + + // Arg 1 - the source array + auto argStart = data.size(); + auto argPatch = AddU16(0x0); + stmt->rhs->Accept(this); + SetU16(argPatch, data.size() - argStart); + SetU16(exprPatch, data.size() - exprStart); + + // Arg 2 + // If there's two vars (including "_"), the 2nd arg for ForEachAlt is the valueIter. + // However, our syntax has key arg first: "for ([int iKey, int iValue] in { "1"::2 "3"::4 })" + // Thus pass iValue arg as 2nd arg instead of 3rd, even though it's the 2nd var in the statement. + if (stmt->declarations.size() == 2) { + argStart = data.size(); + argPatch = AddU16(0x0); + + // Arg 2 - valueIterVar + if (var2) { + AddU8('V'); + AddU8(var2->type); + AddU16(0); + AddU16(var2->index); + } + else { + // OptionalEmpty token + AddU8('K'); + } + SetU16(argPatch, data.size() - argStart); + SetU16(exprPatch, data.size() - exprStart); + + // Arg 3 - pass keyIterVar last + argStart = data.size(); + argPatch = AddU16(0x0); + + if (var1) { + AddU8('V'); + AddU8(var1->type); + AddU16(0); + AddU16(var1->index); + } + else { + // OptionalEmpty token + AddU8('K'); + } + SetU16(argPatch, data.size() - argStart); + SetU16(exprPatch, data.size() - exprStart); + } + else { // assume 1 + argStart = data.size(); + argPatch = AddU16(0x0); + AddU8('V'); + AddU8(var1 != nullptr ? var1->type : 0); + AddU16(0); + if (var1) { + AddU16(var1->index); + } + else { + AddU16(0); + } + SetU16(argPatch, data.size() - argStart); + SetU16(exprPatch, data.size() - exprStart); + } + + insideNvseExpr.pop(); + + loopIncrements.push(nullptr); + CompileBlock(stmt->block, true); + loopIncrements.pop(); + + // OP_LOOP + AddU16(OP_LOOP); + AddU16(0x0); + statementCounter.top()++; + + // Patch jmp + SetU32(jmpPatch, data.size() - scriptStart.top()); + } + else { + // Get variable info + const auto varDecl = stmt->declarations[0]; + if (!varDecl) { + throw std::runtime_error("Unexpected compiler error."); + } + + const auto var = varDecl->declarations[0].info; + + // OP_FOREACH + AddU16(OP_FOREACH); + + // Placeholder OP_LEN + const auto exprPatch = AddU16(0x0); + const auto exprStart = data.size(); + + const auto jmpPatch = AddU32(0x0); + + // Num args + AddU8(0x1); + + // Arg 1 len + const auto argStart = data.size(); + const auto argPatch = AddU16(0x0); + + insideNvseExpr.push(true); + AddU8('V'); + AddU8(var->type); + AddU16(0); + AddU16(var->index); + + stmt->rhs->Accept(this); + AddU8(kOpType_In); + insideNvseExpr.pop(); + + SetU16(argPatch, data.size() - argStart); + SetU16(exprPatch, data.size() - exprStart); + + loopIncrements.push(nullptr); + CompileBlock(stmt->block, true); + loopIncrements.pop(); + + // OP_LOOP + AddU16(OP_LOOP); + AddU16(0x0); + statementCounter.top()++; + + // Patch jmp + SetU32(jmpPatch, data.size() - scriptStart.top()); + } + } + + void Compilation::VisitIfStmt(Statements::If* stmt) { + // OP_IF + AddU16(static_cast(ScriptParsing::ScriptStatementCode::If)); + + // Placeholder OP_LEN + auto exprPatch = AddU16(0x0); + auto exprStart = data.size(); + + // Placeholder JMP_OPS + auto jmpPatch = AddU16(0x0); + + // Placeholder EXP_LEN + auto compPatch = AddU16(0x0); + auto compStart = data.size(); + + // OP_PUSH + AddU8(0x20); + + // Enter NVSE eval + AddU8(0x58); + StartCall(OP_EVAL); + AddCallArg(stmt->cond); + FinishCall(); + + // Patch lengths + SetU16(compPatch, data.size() - compStart); + SetU16(exprPatch, data.size() - exprStart); + + // Patch JMP_OPS + SetU16(jmpPatch, CompileBlock(stmt->block, true)); + + // Build OP_ELSE + if (stmt->elseBlock) { + statementCounter.top()++; + + // OP_ELSE + AddU16(static_cast(ScriptParsing::ScriptStatementCode::Else)); + + // OP_LEN + AddU16(0x02); + + // Placeholder JMP_OPS + auto elsePatch = AddU16(0x0); + + // Compile else block + SetU16(elsePatch, CompileBlock(stmt->elseBlock, true)); + } + + // OP_ENDIF + statementCounter.top()++; + AddU16(static_cast(ScriptParsing::ScriptStatementCode::EndIf)); + AddU16(0x0); + } + + void Compilation::VisitReturnStmt(Statements::Return* stmt) { + // Compile SetFunctionValue if we have a return value + if (stmt->expr) { + StartCall(OP_SET_FUNCTION_VALUE); + AddCallArg(stmt->expr); + FinishCall(); + } + + // Emit op_return + AddU32(static_cast(ScriptParsing::ScriptStatementCode::Return)); + } + + void Compilation::VisitContinueStmt(Statements::Continue* stmt) { + if (loopIncrements.top()) { + loopIncrements.top()->Accept(this); + statementCounter.top()++; + } + + AddU32(OP_CONTINUE); + } + + void Compilation::VisitBreakStmt(Statements::Break* stmt) { + AddU32(OP_BREAK); + } + + void Compilation::VisitWhileStmt(Statements::While* stmt) { + // OP_WHILE + AddU16(OP_WHILE); + + // Placeholder OP_LEN + const auto exprPatch = AddU16(0x0); + const auto exprStart = data.size(); + + const auto jmpPatch = AddU32(0x0); + + // 1 param + AddU8(0x1); + + // Compile / patch condition + auto condStart = data.size(); + auto condPatch = AddU16(0x0); + insideNvseExpr.push(true); + stmt->cond->Accept(this); + insideNvseExpr.pop(); + SetU16(condPatch, data.size() - condStart); + + // Patch OP_LEN + SetU16(exprPatch, data.size() - exprStart); + + // Compile block + loopIncrements.push(nullptr); + CompileBlock(stmt->block, true); + loopIncrements.pop(); + + // OP_LOOP + AddU16(OP_LOOP); + AddU16(0x0); + statementCounter.top()++; + + // Patch jmp + SetU32(jmpPatch, data.size() - scriptStart.top()); + } + + void Compilation::VisitBlockStmt(Statements::Block* stmt) { + for (auto& statement : stmt->statements) { + statement->Accept(this); + statementCounter.top()++; + } + } + + void Compilation::VisitAssignmentExpr(Expressions::AssignmentExpr* expr) { + // Assignment as standalone statement + if (!insideNvseExpr.top()) { + StartCall(OP_LET); + StartManualArg(); + expr->left->Accept(this); + expr->expr->Accept(this); + AddU8(tokenOpToNVSEOpType[expr->token.type]); + FinishManualArg(); + FinishCall(); + } + + // Assignment as an expression + else { + // Build expression + expr->left->Accept(this); + expr->expr->Accept(this); + + // Assignment opcode + AddU8(tokenOpToNVSEOpType[expr->token.type]); + } + } + + void Compilation::VisitTernaryExpr(Expressions::TernaryExpr* expr) { + StartCall(OP_TERNARY); + AddCallArg(expr->cond); + AddCallArg(expr->left); + AddCallArg(expr->right); + FinishCall(); + } + + void Compilation::VisitInExpr(Expressions::InExpr* expr) { + // Value list provided + if (!expr->values.empty()) { + StartCall(OP_MATCHES_ANY); + AddCallArg(expr->lhs); + for (auto arg : expr->values) { + AddCallArg(arg); + } + FinishCall(); + } + // Array + else { + StartCall(OP_AR_EXISTS); + AddCallArg(expr->expression); + AddCallArg(expr->lhs); + FinishCall(); + } + + if (expr->isNot) { + AddU8(tokenOpToNVSEOpType[NVSETokenType::Bang]); + } + } + + void Compilation::VisitBinaryExpr(Expressions::BinaryExpr* expr) { + expr->left->Accept(this); + expr->right->Accept(this); + AddU8(tokenOpToNVSEOpType[expr->op.type]); + } + + void Compilation::VisitUnaryExpr(Expressions::UnaryExpr* expr) { + if (expr->postfix) { + // Slight hack to get postfix operators working + expr->expr->Accept(this); + expr->expr->Accept(this); + AddU8('B'); + AddU8(1); + if (expr->op.type == NVSETokenType::PlusPlus) { + AddU8(tokenOpToNVSEOpType[NVSETokenType::Plus]); + } + else { + AddU8(tokenOpToNVSEOpType[NVSETokenType::Minus]); + } + AddU8(tokenOpToNVSEOpType[NVSETokenType::Eq]); + + AddU8('B'); + AddU8(1); + if (expr->op.type == NVSETokenType::PlusPlus) { + AddU8(tokenOpToNVSEOpType[NVSETokenType::Minus]); + } + else { + AddU8(tokenOpToNVSEOpType[NVSETokenType::Plus]); + } + } + else { + expr->expr->Accept(this); + AddU8(tokenOpToNVSEOpType[expr->op.type]); + } + } + + void Compilation::VisitSubscriptExpr(Expressions::SubscriptExpr* expr) { + expr->left->Accept(this); + expr->index->Accept(this); + AddU8(tokenOpToNVSEOpType[NVSETokenType::LeftBracket]); + } + + void Compilation::VisitCallExpr(Expressions::CallExpr* expr) { + if (!expr->cmdInfo) { + throw std::runtime_error("expr->cmdInfo was null. Please report this as a bug."); + } + + // Handle call command separately, unique parse function + if (expr->cmdInfo->parse == kCommandInfo_Call.parse) { + if (insideNvseExpr.top()) { + AddU8('X'); + AddU16(0x0); // SCRV + } + + AddU16(OP_CALL); + const auto callLengthStart = data.size() + (insideNvseExpr.top() ? 0 : 2); + const auto callLengthPatch = AddU16(0x0); + insideNvseExpr.push(true); + + // Bytecode ver + AddU8(1); + + auto exprLengthStart = data.size(); + auto exprLengthPatch = AddU16(0x0); + + // Resolve identifier + expr->args[0]->Accept(this); + SetU16(exprLengthPatch, data.size() - exprLengthStart); + + // Add args + AddU8(expr->args.size() - 1); + for (int i = 1; i < expr->args.size(); i++) { + auto argStartInner = data.size(); + auto argPatchInner = AddU16(0x0); + expr->args[i]->Accept(this); + SetU16(argPatchInner, data.size() - argStartInner); + } + + SetU16(callLengthPatch, data.size() - callLengthStart); + + insideNvseExpr.pop(); + return; + } + + // See if we should wrap inside let + // Need to do this in the case of something like + // player.AddItem(Caps001, 10) to pass player on stack and use 'x' + // annoying but want to keep the way these were passed consistent, especially in case of + // Quest.target.AddItem() + if (expr->left && !insideNvseExpr.top()) { + // OP_EVAL + StartCall(OP_EVAL); + StartManualArg(); + expr->Accept(this); + FinishManualArg(); + FinishCall(); + return; + } + + StartCall(expr->cmdInfo, expr->left); + for (auto arg : expr->args) { + auto numExpr = dynamic_cast(arg.get()); + if (numExpr && numExpr->enum_len > 0) { + arg->Accept(this); + callBuffers.top().numArgs++; + } + else { + AddCallArg(arg); + } + } + FinishCall(); + } + + void Compilation::VisitShowMessageStmt(Statements::ShowMessage* stmt) { + static uint16_t opcode = g_scriptCommands.GetByName("ShowMessage")->opcode; + + AddU16(opcode); + + const auto callLengthPatch = AddU16(0x0); + const auto callLengthStart = data.size(); + AddU16(0x1); + AddU8('r'); + + const auto messageRef = ResolveObjReference(stmt->message->token.lexeme); + AddU16(messageRef); + + AddU16(stmt->args.size()); + for (const auto& arg : stmt->args) { + if (arg->varInfo->type == Script::eVarType_Float) { + AddU8('f'); + } + else { + AddU8('s'); + } + AddU16(arg->varInfo->index); + } + AddU32(stmt->message_time); + + SetU16(callLengthPatch, data.size() - callLengthStart); + } + + void Compilation::VisitGetExpr(Expressions::GetExpr* expr) { + const auto refIdx = ResolveObjReference(expr->reference_name); + if (!refIdx) { + throw std::runtime_error(std::format("Unable to resolve reference '{}'.", expr->reference_name)); + } + + // Put ref on stack + AddU8('V'); + AddU8(expr->var_info->type); + AddU16(refIdx); + AddU16(expr->var_info->idx); + } + + void Compilation::VisitBoolExpr(Expressions::BoolExpr* expr) { + AddU8('B'); + AddU8(expr->value); + } + + void Compilation::VisitNumberExpr(Expressions::NumberExpr* expr) { + if (expr->is_fp) { + AddF64(expr->value); + } + else if (expr->enum_len) { + if (expr->enum_len == 1) { + AddU8(static_cast(expr->value)); + } + else { + AddU16(static_cast(expr->value)); + } + } + else { + if (expr->value <= UINT8_MAX) { + AddU8('B'); + AddU8(static_cast(expr->value)); + } + else if (expr->value <= UINT16_MAX) { + AddU8('I'); + AddU16(static_cast(expr->value)); + } + else { + AddU8('L'); + AddU32(static_cast(expr->value)); + } + } + } + + void Compilation::VisitStringExpr(Expressions::StringExpr* expr) { + AddString(std::get(expr->token.value)); + } + + void Compilation::VisitIdentExpr(Expressions::IdentExpr* expr) { + // Resolve in scope + const auto var = expr->varInfo; + std::string name; + if (var) { + name = var->remapped_name; + } + else { + name = expr->token.lexeme; + } + usedVars.emplace(name); + + // If this is a lambda var, inline it as a call to GetModLocalData + if (lambdaVars.contains(name)) { + if (!var) { + throw std::runtime_error("Unexpected compiler error. Lambda var info was null."); + } + + StartCall(OP_GET_MOD_LOCAL_DATA); + StartManualArg(); + AddString(std::format("__temp_{}_lambda_{}", scriptName, var->index)); + FinishManualArg(); + FinishCall(); + return; + } + + // Local variable + if (var) { + for (int i = 0; i < statementCounter.size(); i++) { + CompDbg(" "); + } + + CompDbg("Read from global variable %s\n", name.c_str()); + AddU8('V'); + AddU8(var->type); + AddU16(0); + AddU16(var->index); + } + + else { + // Try to look up as object reference + if (auto refIdx = ResolveObjReference(name)) { + auto form = engineScript->refList.GetNthItem(refIdx - 1)->form; + if (form->typeID == kFormType_TESGlobal) { + AddU8('G'); + } + else { + AddU8('R'); + } + AddU16(refIdx); + } + } + } + + void Compilation::VisitMapLiteralExpr(Expressions::MapLiteralExpr* expr) { + StartCall(OP_AR_MAP); + for (const auto& val : expr->values) { + AddCallArg(val); + } + FinishCall(); + } + + void Compilation::VisitArrayLiteralExpr(Expressions::ArrayLiteralExpr* expr) { + StartCall(OP_AR_LIST); + for (const auto& val : expr->values) { + AddCallArg(val); + } + FinishCall(); + } + + void Compilation::VisitGroupingExpr(Expressions::GroupingExpr* expr) { + expr->expr->Accept(this); + } + + void Compilation::VisitLambdaExpr(Expressions::LambdaExpr* expr) { + // PARAM_LAMBDA + AddU8('F'); + + // Arg size + const auto argSizePatch = AddU32(0x0); + const auto argStart = data.size(); + + // Compile lambda + { + scriptStart.push(data.size()); + + // SCN + AddU32(static_cast(ScriptParsing::ScriptStatementCode::ScriptName)); + + // OP_BEGIN + AddU16(0x10); + + // OP_MODE_LEN + const auto opModeLenPatch = AddU16(0x0); + const auto opModeStart = data.size(); + + // OP_MODE + AddU16(0x0D); + + // SCRIPT_LEN + const auto scriptLenPatch = AddU32(0x0); + + // BYTECODE VER + AddU8(0x1); + + // arg count + AddU8(expr->args.size()); + + // add args + for (const auto& arg : expr->args) { + for (const auto& [_, token, info] : arg->declarations) { + AddU16(info->index); + AddU8(info->type); + } + } + + // NUM_ARRAY_ARGS, always 0 + AddU8(0x0); + + auto scriptLenStart = data.size(); + + // Patch mode len + SetU16(opModeLenPatch, data.size() - opModeStart); + + // Compile script + insideNvseExpr.push(false); + CompileBlock(expr->body, false); + insideNvseExpr.pop(); + + // OP_END + AddU32(0x11); + + // Different inside of lambda for some reason? + SetU32(scriptLenPatch, data.size() - scriptLenStart); + + scriptStart.pop(); + } + + SetU32(argSizePatch, data.size() - argStart); + } + + uint32_t Compilation::CompileBlock(StmtPtr stmt, bool incrementCurrent) { + for (int i = 0; i < statementCounter.size(); i++) { + CompDbg(" "); + } + CompDbg("Entering block\n"); + + // Get sub-block statement count + statementCounter.push(0); + stmt->Accept(this); + const auto newStatementCount = statementCounter.top(); + statementCounter.pop(); + + // Lambdas do not add to parent block stmt count + // Others do + if (incrementCurrent) { + statementCounter.top() += newStatementCount; + } + + for (int i = 0; i < statementCounter.size(); i++) { + CompDbg(" "); + } + CompDbg("Exiting Block. Size %d\n", newStatementCount); + + return newStatementCount; + } + + void Compilation::StartCall(CommandInfo* cmd, ExprPtr stackRef) { + // Handle stack refs + if (stackRef) { + stackRef->Accept(this); + } + + if (insideNvseExpr.top()) { + if (stackRef) { + AddU8('x'); + } + else { + AddU8('X'); + } + AddU16(0x0); // SCRV + } + AddU16(cmd->opcode); + + // Call size + CallBuffer buf{}; + buf.parse = &cmd->parse; + buf.stackRef = stackRef; + buf.startPos = data.size() + (insideNvseExpr.top() ? 0 : 2); + buf.startPatch = AddU16(0x0); + + if (isDefaultParse(cmd->parse)) { + buf.argPos = AddU16(0x0); + } + else { + buf.argPos = AddU8(0x0); + } + + callBuffers.push(buf); + } + + void Compilation::FinishCall() { + auto buf = callBuffers.top(); + callBuffers.pop(); + + SetU16(buf.startPatch, data.size() - buf.startPos); + + if (isDefaultParse(*buf.parse)) { + SetU16(buf.argPos, buf.numArgs); + } + else { + SetU8(buf.argPos, buf.numArgs); + } + + // Handle stack refs + if (buf.stackRef) { + AddU8(tokenOpToNVSEOpType[NVSETokenType::Dot]); + } + } + + void Compilation::StartCall(uint16_t opcode, ExprPtr stackRef) { + StartCall(g_scriptCommands.GetByOpcode(opcode), stackRef); + } + + void Compilation::PerformCall(uint16_t opcode) { + StartCall(opcode); + FinishCall(); + } + + void Compilation::AddCallArg(ExprPtr arg) { + // Make NVSE aware + if (isDefaultParse(*callBuffers.top().parse)) { + AddU16(0xFFFF); + } + + const auto argStartInner = data.size(); + const auto argPatchInner = AddU16(0x0); + + insideNvseExpr.push(true); + arg->Accept(this); + insideNvseExpr.pop(); + + SetU16(argPatchInner, data.size() - argStartInner); + callBuffers.top().numArgs++; + } + + void Compilation::StartManualArg() { + // Make NVSE aware + if (isDefaultParse(*callBuffers.top().parse)) { + AddU16(0xFFFF); + } + + callBuffers.top().argStart = data.size(); + callBuffers.top().argPatch = AddU16(0x0); + insideNvseExpr.push(true); + } + + void Compilation::FinishManualArg() { + insideNvseExpr.pop(); + SetU16(callBuffers.top().argPatch, data.size() - callBuffers.top().argStart); + callBuffers.top().numArgs++; + } +} \ No newline at end of file diff --git a/nvse/nvse/Compiler/Compilation.h b/nvse/nvse/Compiler/Compilation.h new file mode 100644 index 00000000..a4563ad2 --- /dev/null +++ b/nvse/nvse/Compiler/Compilation.h @@ -0,0 +1,258 @@ +#pragma once +#include +#include +#include +#include + +#include + +#include "Parser.h" +#include "Visitor.h" + +#include "nvse/GameScript.h" +#include "nvse/GameAPI.h" +#include "nvse/GameObjects.h" + +class Script; + +namespace Compiler { + struct CallBuffer { + size_t startPos{}; + size_t startPatch{}; + size_t argPos{}; + size_t numArgs{}; + size_t argStart{}; + size_t argPatch{}; + CommandInfo* cmdInfo{}; + Cmd_Parse* parse{}; + ExprPtr stackRef; + }; + + class Compilation : Visitor { + public: + Script* engineScript; + bool partial; + AST& ast; + + const char* originalScriptName; + std::string scriptName{}; + + std::stack callBuffers{}; + + std::stack insideNvseExpr{}; + + // When we enter a for loop, push the increment condition on the stack + // Insert this before any continue / break + std::stack loopIncrements{}; + + // Count number of statements compiled + // Certain blocks such as 'if' need to know how many ops to skip + // We also inject for loop increment statement before continue in loops + // This is to count that + std::stack statementCounter{}; + + // To set unused var count at end of script + std::set usedVars{}; + + // Keep track of lambda vars as these get inlined + std::set lambdaVars{}; + + std::stack scriptStart{}; + + // Look up a local variable, or create it if not already defined + uint16_t AddVar(const std::string& identifier, const uint8_t type) { + if (const auto info = engineScript->GetVariableByName(identifier.c_str())) { + if (info->type != type) { + // Remove from ref list if it was a ref prior + if (info->type == Script::eVarType_Ref) { + if (auto* v = engineScript->refList.FindFirst([&](const Script::RefVariable* cur) { return cur->varIdx == info->idx; })) { + engineScript->refList.Remove(v); + } + } + + // Add to ref list if new ref + if (type == Script::eVarType_Ref) { + auto ref = New(); + ref->name = String(); + ref->name.Set(identifier.c_str()); + ref->varIdx = info->idx; + engineScript->refList.Append(ref); + } + + info->type = type; + } + + return info->idx; + } + + auto varInfo = New(); + varInfo->name = String(); + varInfo->name.Set(identifier.c_str()); + varInfo->type = type; + varInfo->idx = engineScript->varList.Count() + 1; + engineScript->varList.Append(varInfo); + + if (type == Script::eVarType_Ref) { + auto ref = New(); + ref->name = String(); + ref->name.Set(identifier.c_str()); + ref->varIdx = varInfo->idx; + engineScript->refList.Append(ref); + } + + return static_cast(varInfo->idx); + } + + uint16_t ResolveObjReference(std::string identifier, const bool add = true) { + TESForm* form; + if (_stricmp(identifier.c_str(), "player") == 0) { + // PlayerRef (this is how the vanilla compiler handles it so I'm changing it for consistency and to fix issues) + form = LookupFormByID(0x14); + } + else { + form = GetFormByID(identifier.c_str()); + } + + if (form) { + // only persistent refs can be used in scripts + if (const auto refr = DYNAMIC_CAST(form, TESForm, TESObjectREFR); refr && !refr->IsPersistent()) { + throw std::runtime_error(std::format("Object reference '{}' must be persistent.", identifier)); + } + + // See if form is already registered + uint16_t i = 1; + for (auto cur = engineScript->refList.Begin(); !cur.End(); cur.Next()) { + if (cur->form == form) { + return i; + } + + i++; + } + + if (add) { + const auto ref = New(); + ref->name = String(); + ref->name.Set(identifier.c_str()); + ref->form = form; + engineScript->refList.Append(ref); + return static_cast(engineScript->refList.Count()); + } + + return 1; + } + + return 0; + } + + // Compiled bytes + std::vector data{}; + + size_t AddU8(const uint8_t byte) { + data.emplace_back(byte); + + return data.size() - 1; + } + + size_t AddU16(const uint16_t bytes) { + data.emplace_back(bytes & 0xFF); + data.emplace_back(bytes >> 8 & 0xFF); + + return data.size() - 2; + } + + size_t AddU32(const uint32_t bytes) { + data.emplace_back(bytes & 0xFF); + data.emplace_back(bytes >> 8 & 0xFF); + data.emplace_back(bytes >> 16 & 0xFF); + data.emplace_back(bytes >> 24 & 0xFF); + + return data.size() - 4; + } + + size_t AddF64(double value) { + const auto bytes = reinterpret_cast(&value); + + AddU8('Z'); + for (int i = 0; i < 8; i++) { + AddU8(bytes[i]); + } + + return data.size() - 8; + } + + size_t AddString(const std::string& str) { + AddU8('S'); + AddU16(str.size()); + for (char c : str) { + AddU8(c); + } + + return data.size() - str.size(); + } + + void SetU8(const size_t index, const uint8_t byte) { + data[index] = byte; + } + + void SetU16(const size_t index, const uint16_t byte) { + data[index] = byte & 0xFF; + data[index + 1] = byte >> 8 & 0xFF; + } + + void SetU32(const size_t index, const uint32_t byte) { + data[index] = byte & 0xFF; + data[index + 1] = byte >> 8 & 0xFF; + data[index + 2] = byte >> 16 & 0xFF; + data[index + 3] = byte >> 24 & 0xFF; + } + + Compilation(Script* script, bool partial, AST& ast) + : engineScript(script), partial(partial), ast(ast), originalScriptName(script->GetEditorID()) { + } + + void PrintScriptInfo(); + bool Compile(); + + void Visit(AST* nvScript) override; + void VisitBeginStmt(Statements::Begin* stmt) override; + void VisitFnStmt(Statements::UDFDecl* stmt) override; + void VisitVarDeclStmt(Statements::VarDecl* stmt) override; + void VisitExprStmt(const Statements::ExpressionStatement* stmt) override; + void VisitForStmt(Statements::For* stmt) override; + void VisitForEachStmt(Statements::ForEach* stmt) override; + void VisitIfStmt(Statements::If* stmt) override; + void VisitReturnStmt(Statements::Return* stmt) override; + void VisitContinueStmt(Statements::Continue* stmt) override; + void VisitBreakStmt(Statements::Break* stmt) override; + void VisitWhileStmt(Statements::While* stmt) override; + void VisitBlockStmt(Statements::Block* stmt) override; + void VisitShowMessageStmt(Statements::ShowMessage* stmt) override; + + void VisitAssignmentExpr(Expressions::AssignmentExpr* expr) override; + void VisitTernaryExpr(Expressions::TernaryExpr* expr) override; + void VisitInExpr(Expressions::InExpr* expr) override; + void VisitBinaryExpr(Expressions::BinaryExpr* expr) override; + void VisitUnaryExpr(Expressions::UnaryExpr* expr) override; + void VisitSubscriptExpr(Expressions::SubscriptExpr* expr) override; + void VisitCallExpr(Expressions::CallExpr* expr) override; + void VisitGetExpr(Expressions::GetExpr* expr) override; + void VisitBoolExpr(Expressions::BoolExpr* expr) override; + void VisitNumberExpr(Expressions::NumberExpr* expr) override; + void VisitStringExpr(Expressions::StringExpr* expr) override; + void VisitIdentExpr(Expressions::IdentExpr* expr) override; + void VisitMapLiteralExpr(Expressions::MapLiteralExpr* expr) override; + void VisitArrayLiteralExpr(Expressions::ArrayLiteralExpr* expr) override; + void VisitGroupingExpr(Expressions::GroupingExpr* expr) override; + void VisitLambdaExpr(Expressions::LambdaExpr* expr) override; + + uint32_t CompileBlock(StmtPtr stmt, bool incrementCurrent); + void FinishCall(); + void StartCall(CommandInfo* cmd, ExprPtr stackRef = nullptr); + void StartCall(uint16_t opcode, ExprPtr stackRef = nullptr); + void PerformCall(uint16_t opcode); + void AddCallArg(ExprPtr arg); + void StartManualArg(); + void FinishManualArg(); + }; + +} \ No newline at end of file diff --git a/nvse/nvse/Compiler/Lexer/Lexer.cpp b/nvse/nvse/Compiler/Lexer/Lexer.cpp new file mode 100644 index 00000000..a11d32bf --- /dev/null +++ b/nvse/nvse/Compiler/Lexer/Lexer.cpp @@ -0,0 +1,464 @@ +#include "Lexer.h" + +#include +#include + +#include "nvse/Compiler/NVSECompilerUtils.h" + +namespace Compiler { + Lexer::Lexer(const std::string& input) : pos(0) { + auto inputStr = std::string{ input }; + + // Replace tabs with 4 spaces + size_t it; + while ((it = inputStr.find('\t')) != std::string::npos) { + inputStr.replace(it, 1, " "); + } + + // Messy way of just getting string lines to start for later error reporting + std::string::size_type pos = 0; + std::string::size_type prev = 0; + while ((pos = inputStr.find('\n', prev)) != std::string::npos) { + lines.push_back(inputStr.substr(prev, pos - prev - 1)); + prev = pos + 1; + } + lines.push_back(inputStr.substr(prev)); + + this->input = inputStr; + } + + // Unused for now, working though + std::deque Lexer::lexString() { + std::stringstream resultString; + + auto start = pos - 1; + auto startLine = line; + auto startCol = column; + + std::deque results{}; + + while (pos < input.size()) { + char c = input[pos++]; + + if (c == '\\') { + if (pos >= input.size()) throw std::runtime_error("Unexpected end of input after escape character."); + if (char next = input[pos++]; next == '\\' || next == 'n' || next == '"') { + if (next == 'n') { + resultString << '\n'; + } + else { + resultString << next; + } + } + else { + throw std::runtime_error("Invalid escape sequence."); + } + } + else if (c == '\n') { + throw std::runtime_error("Unexpected end of line."); + } + else if (c == '"') { + if (line != startLine) { + throw std::runtime_error("Multiline strings are not allowed."); + } + + // Push final result as string + NVSEToken token{}; + token.type = NVSETokenType::String; + token.lexeme = '"' + resultString.str() + '"'; + token.value = resultString.str(); + token.line = line; + token.column = startCol; + results.push_back(token); + + // Update column + column = pos; + + return results; + } + else if (c == '$' && pos < input.size() && input[pos] == '{' && (pos == start + 1 || input[pos - 2] != '\\')) { + // Push previous result as string + results.push_back(MakeToken(NVSETokenType::String, '"' + resultString.str() + '"', resultString.str())); + + NVSEToken startInterpolate{}; + startInterpolate.type = NVSETokenType::Interp; + startInterpolate.lexeme = "${"; + startInterpolate.column = startCol + (pos - start); + startInterpolate.line = line; + results.push_back(startInterpolate); + + pos += 1; + NVSEToken tok; + while ((tok = GetNextToken(false)).type != NVSETokenType::RightBrace) { + if (tok.type == NVSETokenType::Eof) { + throw std::runtime_error("Unexpected end of file."); + } + results.push_back(tok); + } + + NVSEToken endInterpolate{}; + endInterpolate.type = NVSETokenType::EndInterp; + endInterpolate.lexeme = "}"; + endInterpolate.column = column - 1; + endInterpolate.line = line; + results.push_back(endInterpolate); + + resultString = {}; + startCol = pos; + } + else { + resultString << c; + } + } + + throw std::runtime_error("Unexpected end of input in string."); + } + + NVSEToken Lexer::GetNextToken(bool useStack) { + if (!tokenStack.empty() && useStack) { + auto tok = tokenStack.front(); + tokenStack.pop_front(); + return tok; + } + + // Skip over comments and whitespace in this block. + { + // Using an enum instead of two bools, since it's impossible to be in a singleline comment and a multiline comment at once. + enum class InWhatComment + { + None = 0, + SingleLine, + MultiLine + } inWhatComment = InWhatComment::None; + + while (pos < input.size()) { + if (!std::isspace(input[pos]) && inWhatComment == InWhatComment::None) { + if (pos < input.size() - 2 && input[pos] == '/') { + if (input[pos + 1] == '/') { + inWhatComment = InWhatComment::SingleLine; + pos += 2; + } + else if (input[pos + 1] == '*') + { + inWhatComment = InWhatComment::MultiLine; + pos += 2; + column += 2; // multiline comment could end on the same line it's declared on and have valid code after itself. + } + else { + break; // found start of next token + } + } + else { + break; // found start of next token + } + } + + if (inWhatComment == InWhatComment::MultiLine && + (pos < input.size() - 2) && input[pos] == '*' && input[pos + 1] == '/') + { + inWhatComment = InWhatComment::None; + pos += 2; + column += 2; // multiline comment could end on the same line it's declared on and have valid code after itself. + continue; // could be entering another comment right after this one; + // Don't want to reach the end of the loop and increment `pos` before that. + } + + if (input[pos] == '\n') { + line++; + column = 1; + if (inWhatComment == InWhatComment::SingleLine) { + inWhatComment = InWhatComment::None; + } + } + else if (input[pos] == '\r' && (pos < input.size() - 1) && input[pos + 1] == '\n') { + line++; + pos++; + column = 1; + if (inWhatComment == InWhatComment::SingleLine) { + inWhatComment = InWhatComment::None; + } + } + else { + column++; + } + + pos++; + } + } + + if (pos >= input.size()) { + return MakeToken(NVSETokenType::Eof, ""); + } + + char current = input[pos]; + if (std::isdigit(current)) { + int base = 10; + if (current == '0' && pos + 2 < input.size()) { + if (std::tolower(input[pos + 1]) == 'b') { + base = 2; + pos += 2; + current = input[pos]; + } + else if (std::tolower(input[pos + 1]) == 'x') { + base = 16; + pos += 2; + current = input[pos]; + } + } + + size_t len; + double value; + try { + if (base == 16 || base == 2) { + value = std::stoll(&input[pos], &len, base); + } + else { + value = std::stod(&input[pos], &len); + } + } + catch (const std::invalid_argument& ex) { + throw std::runtime_error("Invalid numeric literal."); + } + catch (const std::out_of_range& ex) { + throw std::runtime_error("Numeric literal value out of range."); + } + + if (input[pos + len - 1] == '.') { + len--; + } + pos += len; + column += len; + + // Handle 0b/0x + if (base == 2 || base == 16) { + len += 2; + } + + return MakeToken(NVSETokenType::Number, input.substr(pos - len, len), value); + } + + { + // Check if potential identifier has at least 1 alphabetical character. + // Must either start with underscores, or an alphabetical character. + bool isValidIdentifier = std::isalnum(current); + if (!isValidIdentifier && current == '_') { + size_t lookaheadPos = pos + 1; + while (lookaheadPos < input.size()) { + if (std::isalpha(input[lookaheadPos])) { + isValidIdentifier = true; + break; + } + else if (input[lookaheadPos] != '_') { + break; + } + ++lookaheadPos; + } + } + + if (isValidIdentifier) { + // Extract identifier + size_t start = pos; + while (pos < input.size() && (std::isalnum(input[pos]) || input[pos] == '_')) { + ++pos; + } + std::string identifier = input.substr(start, pos - start); + + // Keywords + if (_stricmp(identifier.c_str(), "if") == 0) return MakeToken(NVSETokenType::If, identifier); + if (_stricmp(identifier.c_str(), "else") == 0) return MakeToken(NVSETokenType::Else, identifier); + if (_stricmp(identifier.c_str(), "while") == 0) return MakeToken(NVSETokenType::While, identifier); + if (_stricmp(identifier.c_str(), "fn") == 0) return MakeToken(NVSETokenType::Fn, identifier); + if (_stricmp(identifier.c_str(), "return") == 0) return MakeToken(NVSETokenType::Return, identifier); + if (_stricmp(identifier.c_str(), "for") == 0) return MakeToken(NVSETokenType::For, identifier); + if (_stricmp(identifier.c_str(), "name") == 0) return MakeToken(NVSETokenType::Name, identifier); + if (_stricmp(identifier.c_str(), "continue") == 0) return MakeToken(NVSETokenType::Continue, identifier); + if (_stricmp(identifier.c_str(), "break") == 0) return MakeToken(NVSETokenType::Break, identifier); + if (_stricmp(identifier.c_str(), "in") == 0) return MakeToken(NVSETokenType::In, identifier); + if (_stricmp(identifier.c_str(), "not") == 0) return MakeToken(NVSETokenType::Not, identifier); + if (_stricmp(identifier.c_str(), "match") == 0) return MakeToken(NVSETokenType::Match, identifier); + + if (_stricmp(identifier.c_str(), "int") == 0) return MakeToken(NVSETokenType::IntType, identifier); + if (_stricmp(identifier.c_str(), "bool") == 0) return MakeToken(NVSETokenType::IntType, identifier); + if (_stricmp(identifier.c_str(), "double") == 0) return MakeToken(NVSETokenType::DoubleType, identifier); + if (_stricmp(identifier.c_str(), "float") == 0) return MakeToken(NVSETokenType::DoubleType, identifier); + if (_stricmp(identifier.c_str(), "ref") == 0) return MakeToken(NVSETokenType::RefType, identifier); + if (_stricmp(identifier.c_str(), "string") == 0) return MakeToken(NVSETokenType::StringType, identifier); + if (_stricmp(identifier.c_str(), "array") == 0) return MakeToken(NVSETokenType::ArrayType, identifier); + + if (_stricmp(identifier.c_str(), "true") == 0) return MakeToken(NVSETokenType::Bool, identifier, 1); + if (_stricmp(identifier.c_str(), "false") == 0) return MakeToken(NVSETokenType::Bool, identifier, 0); + if (_stricmp(identifier.c_str(), "null") == 0) return MakeToken(NVSETokenType::Number, identifier, 0); + + + return MakeToken(NVSETokenType::Identifier, identifier); + } + } + + if (current == '"') { + pos++; + auto results = lexString(); + auto tok = results.front(); + results.pop_front(); + + for (auto r : results) { + tokenStack.push_back(r); + } + + return tok; + } + + pos++; + switch (current) { + // Operators + case '+': { + if (Match('=')) { + return MakeToken(NVSETokenType::PlusEq, "+="); + } + if (Match('+')) { + return MakeToken(NVSETokenType::PlusPlus, "++"); + } + return MakeToken(NVSETokenType::Plus, "+"); + } + case '-': { + if (Match('=')) { + return MakeToken(NVSETokenType::MinusEq, "-="); + } + if (Match('-')) { + return MakeToken(NVSETokenType::MinusMinus, "--"); + } + if (Match('>')) { + return MakeToken(NVSETokenType::Arrow, "->"); + } + return MakeToken(NVSETokenType::Minus, "-"); + } + case '*': + if (Match('=')) { + return MakeToken(NVSETokenType::StarEq, "*="); + } + return MakeToken(NVSETokenType::Star, "*"); + case '/': + if (Match('=')) { + return MakeToken(NVSETokenType::SlashEq, "/="); + } + return MakeToken(NVSETokenType::Slash, "/"); + case '%': + if (Match('=')) { + return MakeToken(NVSETokenType::ModEq, "%="); + } + return MakeToken(NVSETokenType::Mod, "%"); + case '^': + if (Match('=')) { + return MakeToken(NVSETokenType::PowEq, "^="); + } + return MakeToken(NVSETokenType::Pow, "^"); + case '=': + if (Match('=')) { + return MakeToken(NVSETokenType::EqEq, "=="); + } + return MakeToken(NVSETokenType::Eq, "="); + case '!': + if (Match('=')) { + return MakeToken(NVSETokenType::BangEq, "!="); + } + return MakeToken(NVSETokenType::Bang, "!"); + case '<': + if (Match('=')) { + return MakeToken(NVSETokenType::LessEq, "<="); + } + if (Match('<')) { + return MakeToken(NVSETokenType::LeftShift, "<<"); + } + return MakeToken(NVSETokenType::Less, "<"); + case '>': + if (Match('=')) { + return MakeToken(NVSETokenType::GreaterEq, ">="); + } + if (Match('>')) { + return MakeToken(NVSETokenType::RightShift, ">>"); + } + return MakeToken(NVSETokenType::Greater, ">"); + case '|': + if (Match('|')) { + return MakeToken(NVSETokenType::LogicOr, "||"); + } + if (Match('=')) { + return MakeToken(NVSETokenType::BitwiseOrEquals, "|="); + } + return MakeToken(NVSETokenType::BitwiseOr, "|"); + case '~': + return MakeToken(NVSETokenType::BitwiseNot, "~"); + case '&': + if (Match('&')) { + return MakeToken(NVSETokenType::LogicAnd, "&&"); + } + if (Match('=')) { + return MakeToken(NVSETokenType::BitwiseAndEquals, "&="); + } + return MakeToken(NVSETokenType::BitwiseAnd, "&"); + case '$': return MakeToken(NVSETokenType::Dollar, "$"); + case '#': return MakeToken(NVSETokenType::Pound, "#"); + + // Braces + case '{': return MakeToken(NVSETokenType::LeftBrace, "{"); + case '}': return MakeToken(NVSETokenType::RightBrace, "}"); + case '[': return MakeToken(NVSETokenType::LeftBracket, "["); + case ']': return MakeToken(NVSETokenType::RightBracket, "]"); + case '(': return MakeToken(NVSETokenType::LeftParen, "("); + case ')': return MakeToken(NVSETokenType::RightParen, ")"); + + // Misc + case ',': return MakeToken(NVSETokenType::Comma, ","); + case ';': return MakeToken(NVSETokenType::Semicolon, ";"); + case '?': return MakeToken(NVSETokenType::Ternary, "?"); + case ':': + if (Match(':')) { + return MakeToken(NVSETokenType::MakePair, "::"); + } + return MakeToken(NVSETokenType::Slice, ":"); + case '.': + return MakeToken(NVSETokenType::Dot, "."); + case '_': + return MakeToken(NVSETokenType::Underscore, "_"); + default: throw std::runtime_error("Unexpected character"); + } + + throw std::runtime_error("Unexpected character"); + } + + bool Lexer::Match(char c) { + if (pos >= input.size()) { + return false; + } + + if (input[pos] == c) { + pos++; + return true; + } + + return false; + } + + NVSEToken Lexer::MakeToken(NVSETokenType type, std::string lexeme) { + NVSEToken t(type, lexeme); + t.line = line; + t.column = column; + column += lexeme.length(); + return t; + } + + NVSEToken Lexer::MakeToken(NVSETokenType type, std::string lexeme, double value) { + NVSEToken t(type, lexeme, value); + t.line = line; + t.column = column; + column += lexeme.length(); + return t; + } + + NVSEToken Lexer::MakeToken(NVSETokenType type, std::string lexeme, std::string value) { + NVSEToken t(type, lexeme, value); + t.line = line; + t.column = column; + column += lexeme.length(); + return t; + } +} \ No newline at end of file diff --git a/nvse/nvse/Compiler/Lexer/Lexer.h b/nvse/nvse/Compiler/Lexer/Lexer.h new file mode 100644 index 00000000..78d3ac7e --- /dev/null +++ b/nvse/nvse/Compiler/Lexer/Lexer.h @@ -0,0 +1,235 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace Compiler { + enum class NVSETokenType { + // Keywords + If, + Else, + While, + Fn, + Return, + For, + Name, + Continue, + Break, + Export, + Match, + + // Types + IntType, + DoubleType, + RefType, + StringType, + ArrayType, + + // Operators + Plus, PlusEq, PlusPlus, + Minus, Negate, MinusEq, MinusMinus, + Star, StarEq, + Slash, SlashEq, + Mod, ModEq, + Pow, PowEq, + Eq, EqEq, + Less, Greater, + LessEq, + GreaterEq, + NotEqual, + Bang, BangEq, + LogicOr, + LogicOrEquals, + LogicAnd, + LogicAndEquals, + BitwiseOr, + BitwiseOrEquals, + BitwiseAnd, + BitwiseAndEquals, + LeftShift, + RightShift, + BitwiseNot, + Dollar, Pound, + Box, Unbox, + + // Braces + LeftBrace, RightBrace, + LeftBracket, RightBracket, + LeftParen, RightParen, + + // Literals + String, + Number, + Identifier, + Bool, + + // Misc + Comma, + Semicolon, + Ternary, + Slice, + Not, + In, + Eof, + Dot, + Interp, + EndInterp, + MakePair, + Arrow, + Underscore, + }; + + static const char* TokenTypeStr[]{ + // Keywords + "If", + "Else", + "While", + "Fn", + "Return", + "For", + "Name", + "Continue", + "Break", + "Export", + "Match", + + // Types + "IntType", + "DoubleType", + "RefType", + "StringType", + "ArrayType", + + // Operators + "Plus", + "PlusEq", + "PlusPlus", + "Minus", + "Negate", + "MinusEq", + "MinusMinus", + "Star", + "StarEq", + "Slash", + "SlashEq", + "Mod", + "ModEq", + "Pow", + "PowEq", + "Eq", + "EqEq", + "Less", + "Greater", + "LessEq", + "GreaterEq", + "NotEqual", + "Bang", + "BangEq", + "LogicOr", + "LogicOrEquals", + "LogicAnd", + "LogicAndEquals", + "BitwiseOr", + "BitwiseOrEquals", + "BitwiseAnd", + "BitwiseAndEquals", + "LeftShift", + "RightShift", + "BitwiseNot", + "Dollar", + "Pound", + + // These two get set by parser as they are context dependent + "Box", + "Unbox", + + // Braces + "LeftBrace", + "RightBrace", + "LeftBracket", + "RightBracket", + "LeftParen", + "RightParen", + + // Literals + "String", + "Number", + "Identifier", + "Bool", + + // Misc + "Comma", + "Semicolon", + "Ternary", + "Slice", + "Not", + "In", + "End", + "Dot", + "Interp", + "EndInterp", + "Arrow", + "Underscore", + }; + + struct NVSEToken { + NVSETokenType type; + std::variant value; + size_t line = 1; + size_t column = 0; + std::string lexeme; + + NVSEToken(NVSEToken&& other) noexcept { + this->operator=(std::move(other)); + } + + NVSEToken(const NVSEToken& other) noexcept { + this->type = other.type; + this->value = other.value; + this->line = other.line; + this->column = other.column; + this->lexeme = other.lexeme; + } + + NVSEToken& operator=(NVSEToken&& other) noexcept { + this->type = other.type; + this->value = std::move(other.value); + this->line = other.line; + this->column = other.column; + this->lexeme = std::move(other.lexeme); + + return *this; + } + + NVSEToken() : type(NVSETokenType::Eof), lexeme(""), value(std::monostate{}) {} + NVSEToken(NVSETokenType t) : type(t), lexeme(""), value(std::monostate{}) {} + NVSEToken(NVSETokenType t, std::string lexeme) : type(t), lexeme(lexeme), value(std::monostate{}) {} + NVSEToken(NVSETokenType t, std::string lexeme, double value) : type(t), lexeme(lexeme), value(value) {} + NVSEToken(NVSETokenType t, std::string lexeme, std::string value) : type(t), lexeme(lexeme), value(value) {} + }; + + class Lexer { + std::string input; + size_t pos; + + // For string interpolation + std::deque tokenStack{}; + + public: + size_t column = 1; + size_t line = 1; + std::vector lines{}; + + Lexer(const std::string& input); + std::deque lexString(); + + NVSEToken GetNextToken(bool useStack); + bool Match(char c); + NVSEToken MakeToken(NVSETokenType type, std::string lexeme); + NVSEToken MakeToken(NVSETokenType type, std::string lexeme, double value); + NVSEToken MakeToken(NVSETokenType type, std::string lexeme, std::string value); + }; +} \ No newline at end of file diff --git a/nvse/nvse/Compiler/NVSECompilerUtils.cpp b/nvse/nvse/Compiler/NVSECompilerUtils.cpp new file mode 100644 index 00000000..168c1cd3 --- /dev/null +++ b/nvse/nvse/Compiler/NVSECompilerUtils.cpp @@ -0,0 +1,523 @@ +#include "NVSECompilerUtils.h" + +#include "Lexer/Lexer.h" + +inline bool consoleAllocated = false; + +namespace Compiler { + void allocateConsole() { +#ifdef EDITOR + if (!consoleAllocated) { + AllocConsole(); + freopen("CONOUT$", "w", stdout); + consoleAllocated = true; + + HWND hwnd = GetConsoleWindow(); + if (hwnd != NULL) { + HMENU hMenu = GetSystemMenu(hwnd, FALSE); + if (hMenu != NULL) DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND); + } + } +#endif + } + + void CompClear() { + if (consoleAllocated) { + system("CLS"); + } + } + + void CompDbg(const char* fmt, ...) { + allocateConsole(); + + va_list argList; + va_start(argList, fmt); +#if defined(EDITOR) && defined(_DEBUG) + vprintf(fmt, argList); +#else + v_DMESSAGE(fmt, argList); +#endif + va_end(argList); + } + + void CompInfo(const char* fmt, ...) { + allocateConsole(); + + va_list argList; + va_start(argList, fmt); +#if defined(EDITOR) + vprintf(fmt, argList); +#else + v_MESSAGE(fmt, argList); +#endif + va_end(argList); + } + + void CompErr(const char* fmt, ...) { + allocateConsole(); + + va_list argList; + va_start(argList, fmt); +#ifdef EDITOR + vprintf(fmt, argList); +#else + vShowRuntimeError(nullptr, fmt, argList); +#endif + va_end(argList); + } + + std::unordered_map tokenOpToNVSEOpType{ + { NVSETokenType::EqEq, kOpType_Equals }, + { NVSETokenType::LogicOr, kOpType_LogicalOr }, + { NVSETokenType::LogicAnd, kOpType_LogicalAnd }, + { NVSETokenType::Greater, kOpType_GreaterThan }, + { NVSETokenType::GreaterEq, kOpType_GreaterOrEqual }, + { NVSETokenType::Less, kOpType_LessThan }, + { NVSETokenType::LessEq, kOpType_LessOrEqual }, + { NVSETokenType::Bang, kOpType_LogicalNot }, + { NVSETokenType::BangEq, kOpType_NotEqual }, + + { NVSETokenType::Plus, kOpType_Add }, + { NVSETokenType::Minus, kOpType_Subtract }, + { NVSETokenType::Star, kOpType_Multiply }, + { NVSETokenType::Slash, kOpType_Divide }, + { NVSETokenType::Mod, kOpType_Modulo }, + { NVSETokenType::Pow, kOpType_Exponent }, + + { NVSETokenType::Eq, kOpType_Assignment }, + { NVSETokenType::PlusEq, kOpType_PlusEquals }, + { NVSETokenType::MinusEq, kOpType_MinusEquals }, + { NVSETokenType::StarEq, kOpType_TimesEquals }, + { NVSETokenType::SlashEq, kOpType_DividedEquals }, + { NVSETokenType::ModEq, kOpType_ModuloEquals }, + { NVSETokenType::PowEq, kOpType_ExponentEquals }, + + { NVSETokenType::MakePair, kOpType_MakePair }, + { NVSETokenType::Slice, kOpType_Slice }, + + // Logical + { NVSETokenType::BitwiseAnd, kOpType_BitwiseAnd }, + { NVSETokenType::BitwiseOr, kOpType_BitwiseOr }, + { NVSETokenType::BitwiseAndEquals, kOpType_BitwiseAndEquals }, + { NVSETokenType::BitwiseOrEquals, kOpType_BitwiseOrEquals }, + { NVSETokenType::LeftShift, kOpType_LeftShift }, + { NVSETokenType::RightShift, kOpType_RightShift }, + + // Unary + { NVSETokenType::Negate, kOpType_Negation }, + { NVSETokenType::Dollar, kOpType_ToString }, + { NVSETokenType::Pound, kOpType_ToNumber }, + { NVSETokenType::Box, kOpType_Box }, + { NVSETokenType::Unbox, kOpType_Dereference }, + { NVSETokenType::LeftBracket, kOpType_LeftBracket }, + { NVSETokenType::Dot, kOpType_Dot }, + { NVSETokenType::BitwiseNot, kOpType_BitwiseNot } + }; + + // Copied for testing from ScriptAnalyzer.cpp + const UInt32 g_gameParseCommands[] = { + 0x5B1BA0, 0x5B3C70, 0x5B3CA0, 0x5B3C40, 0x5B3CD0, + reinterpret_cast(Cmd_Default_Parse) + }; + const UInt32 g_messageBoxParseCmds[] = { + 0x5B3CD0, 0x5B3C40, 0x5B3C70, 0x5B3CA0 + }; + + bool isDefaultParse(Cmd_Parse parse) { + return Contains(g_gameParseCommands, reinterpret_cast(parse)) || + reinterpret_cast(parse) == 0x005C67E0; + } + +#ifdef EDITOR + constexpr uint32_t ACTOR_VALUE_ADDRESS = 0x491300; + constexpr uint32_t SEX_0 = 0xE9AB18; + constexpr uint32_t SEX_1 = 0xE9AB1C; + constexpr uint32_t MISC_STAT = 0x52E790; +#else + constexpr uint32_t ACTOR_VALUE_ADDRESS = 0x66EB40; + constexpr uint32_t SEX_0 = 0x104A0EC; + constexpr uint32_t SEX_1 = 0x104A0F4; + constexpr uint32_t MISC_STAT = 0x4D5EB0; +#endif + + void resolveVanillaEnum( + const ParamInfo* info, + const char* str, + uint32_t* val, + uint32_t* len + ) { + uint32_t i = -1; + *val = -1; + *len = 2; + switch (info->typeID) { + case kParamType_ActorValue: + i = CdeclCall(ACTOR_VALUE_ADDRESS, str); + *val = i < 77 ? i : -1; + return; + case kParamType_Axis: + if (strlen(str) == 1) { + const auto c = str[0] & 0xDF; + if (c < 'X' || c > 'Z') { + return; + } + *val = c; + *len = 1; + } + return; + case kParamType_AnimationGroup: + for (i = 0; i < 245 && StrCompare( + g_animGroupInfos[i].name, + str + ); i++) {} + *val = i < 245 ? i : -1; + return; + case kParamType_Sex: + if (!StrCompare(*reinterpret_cast(SEX_0), str)) { + *val = 0; + } + if (!StrCompare(*reinterpret_cast(SEX_1), str)) { + *val = 1; + } + return; + case kParamType_CrimeType: + if (IsStringInteger(str) && ((i = atoi(str)) <= 4)) { + *val = i; + } + return; + case kParamType_FormType: + for (auto& [formId, name] : g_formTypeNames) { + if (!StrCompare(name, str)) { + *val = formId; + } + } + return; + case kParamType_MiscellaneousStat: + i = CdeclCall(MISC_STAT, str); + *val = i < 43 ? i : -1; + return; + case kParamType_Alignment: + for (i = 0; (i < 5) && StrCompare( + g_alignmentTypeNames[i], + str + ); i++) {} + *val = i < 5 ? i : -1; + return; + case kParamType_EquipType: + for (i = 0; (i < 14) && StrCompare(g_equipTypeNames[i], str); i++) { + } + *val = i < 14 ? i : -1; + return; + case kParamType_CriticalStage: + for (i = 0; (i < 5) && StrCompare( + g_criticalStageNames[i], + str + ); i++) {} + *val = i == 5 ? -1 : i; + return; + } + } + +#if EDITOR + constexpr UInt32 p_mapMarker = 0xEDDA34; + constexpr UInt32 g_isContainer = 0x63D740; +#else + constexpr UInt32 p_mapMarker = 0x11CA224; + constexpr UInt32 g_isContainer = 0x55D310; +#endif + + bool doesFormMatchParamType(TESForm* form, const ParamType type) { + if (!form) + return false; + + switch (type) { + case kParamType_ObjectID: + if (!form || !kInventoryType[form->typeID]) { + return false; + } + break; + case kParamType_ObjectRef: + if (!form || !DYNAMIC_CAST(form, TESForm, TESObjectREFR)) { + return false; + } + break; + case kParamType_Actor: +#if EDITOR + if (!form || !form->IsActor_InEditor()) { + return false; + } +#else + if (!form || !form->IsActor_Runtime()) { + return false; + } +#endif + break; + case kParamType_MapMarker: + if (!form || NOT_ID(form, TESObjectREFR) || (((TESObjectREFR*)form) + ->baseForm != *(TESForm**)p_mapMarker)) { + return false; + } + break; + case kParamType_Container: + if (!form || !DYNAMIC_CAST(form, TESForm, TESObjectREFR) || ! + ThisStdCall(g_isContainer, form)) { + return false; + } + break; + case kParamType_SpellItem: + if (!form || (NOT_ID(form, SpellItem) && + NOT_ID(form, TESObjectBOOK))) { + return false; + } + break; + case kParamType_Cell: + if (!form || NOT_ID(form, TESObjectCELL) || !(((TESObjectCELL*)form) + ->cellFlags & 1)) { + return false; + } + break; + case kParamType_MagicItem: + if (!form || !DYNAMIC_CAST(form, TESForm, MagicItem)) { + return false; + } + break; + case kParamType_Sound: + if (!form || NOT_ID(form, TESSound)) { + return false; + } + break; + case kParamType_Topic: + if (!form || NOT_ID(form, TESTopic)) { + return false; + } + break; + case kParamType_Quest: + if (!form || NOT_ID(form, TESQuest)) { + return false; + } + break; + case kParamType_Race: + if (!form || NOT_ID(form, TESRace)) { + return false; + } + break; + case kParamType_Class: + if (!form || NOT_ID(form, TESClass)) { + return false; + } + break; + case kParamType_Faction: + if (!form || NOT_ID(form, TESFaction)) { + return false; + } + break; + case kParamType_Global: + if (!form || NOT_ID(form, TESGlobal)) { + return false; + } + break; + case kParamType_Furniture: + if (!form || (NOT_ID(form, TESFurniture) && NOT_ID( + form, + BGSListForm + ))) { + return false; + } + break; + case kParamType_TESObject: + if (!form || !DYNAMIC_CAST(form, TESForm, TESObject)) { + return false; + } + break; + case kParamType_ActorBase: + if (!form || (NOT_ID(form, TESNPC) && NOT_ID(form, TESCreature))) { + return false; + } + break; + case kParamType_WorldSpace: + if (!form || NOT_ID(form, TESWorldSpace)) { + return false; + } + break; + case kParamType_AIPackage: + if (!form || NOT_ID(form, TESPackage)) { + return false; + } + break; + case kParamType_CombatStyle: + if (!form || NOT_ID(form, TESCombatStyle)) { + return false; + } + break; + case kParamType_MagicEffect: + if (!form || NOT_ID(form, EffectSetting)) { + return false; + } + break; + case kParamType_WeatherID: + if (!form || NOT_ID(form, TESWeather)) { + return false; + } + break; + case kParamType_NPC: + if (!form || NOT_ID(form, TESNPC)) { + return false; + } + break; + case kParamType_Owner: + if (!form || (NOT_ID(form, TESFaction) && NOT_ID(form, TESNPC))) { + return false; + } + break; + case kParamType_EffectShader: + if (!form || NOT_ID(form, TESEffectShader)) { + return false; + } + break; + case kParamType_FormList: + if (!form || NOT_ID(form, BGSListForm)) { + return false; + } + break; + case kParamType_MenuIcon: + if (!form || NOT_ID(form, BGSMenuIcon)) { + return false; + } + break; + case kParamType_Perk: + if (!form || NOT_ID(form, BGSPerk)) { + return false; + } + break; + case kParamType_Note: + if (!form || NOT_ID(form, BGSNote)) { + return false; + } + break; + case kParamType_ImageSpaceModifier: + if (!form || NOT_ID(form, TESImageSpaceModifier)) { + return false; + } + break; + case kParamType_ImageSpace: + if (!form || NOT_ID(form, TESImageSpace)) { + return false; + } + break; + case kParamType_EncounterZone: + if (!form || NOT_ID(form, BGSEncounterZone)) { + return false; + } + break; + case kParamType_IdleForm: + if (!form || NOT_ID(form, TESIdleForm)) { + return false; + } + break; + case kParamType_Message: + if (!form || NOT_ID(form, BGSMessage)) { + return false; + } + break; + case kParamType_InvObjOrFormList: + if (!form || (NOT_ID(form, BGSListForm) && !kInventoryType[form-> + typeID])) { + return false; + } + break; + case kParamType_NonFormList: +#if RUNTIME + if (!form || NOT_ID(form, BGSListForm)) { + return false; + } +#else + if (!form || (NOT_ID(form, BGSListForm) && !form->Unk_33())) { + return false; + } +#endif + break; + case kParamType_SoundFile: + if (!form || NOT_ID(form, BGSMusicType)) { + return false; + } + break; + case kParamType_LeveledOrBaseChar: + if (!form || (NOT_ID(form, TESNPC) && NOT_ID( + form, + TESLevCharacter + ))) { + return false; + } + break; + case kParamType_LeveledOrBaseCreature: + if (!form || (NOT_ID(form, TESCreature) && NOT_ID( + form, + TESLevCreature + ))) { + return false; + } + break; + case kParamType_LeveledChar: + if (!form || NOT_ID(form, TESLevCharacter)) { + return false; + } + break; + case kParamType_LeveledCreature: + if (!form || NOT_ID(form, TESLevCreature)) { + return false; + } + break; + case kParamType_LeveledItem: + if (!form || NOT_ID(form, TESLevItem)) { + return false; + } + break; + case kParamType_AnyForm: + if (!form) { + return false; + } + break; + case kParamType_Reputation: + if (!form || NOT_ID(form, TESReputation)) { + return false; + } + break; + case kParamType_Casino: + if (!form || NOT_ID(form, TESCasino)) { + return false; + } + break; + case kParamType_CasinoChip: + if (!form || NOT_ID(form, TESCasinoChips)) { + return false; + } + break; + case kParamType_Challenge: + if (!form || NOT_ID(form, TESChallenge)) { + return false; + } + break; + case kParamType_CaravanMoney: + if (!form || NOT_ID(form, TESCaravanMoney)) { + return false; + } + break; + case kParamType_CaravanCard: + if (!form || NOT_ID(form, TESCaravanCard)) { + return false; + } + break; + case kParamType_CaravanDeck: + if (!form || NOT_ID(form, TESCaravanDeck)) { + return false; + } + break; + case kParamType_Region: + if (!form || NOT_ID(form, TESRegion)) { + return false; + } + break; + } + + return true; + } +} \ No newline at end of file diff --git a/nvse/nvse/Compiler/NVSECompilerUtils.h b/nvse/nvse/Compiler/NVSECompilerUtils.h new file mode 100644 index 00000000..0513fd0d --- /dev/null +++ b/nvse/nvse/Compiler/NVSECompilerUtils.h @@ -0,0 +1,162 @@ +#pragma once +#include + +#include "Lexer/Lexer.h" +#include "../ScriptTokens.h" +#include "../ScriptUtils.h" + +namespace Compiler { + // Define tokens + extern std::unordered_map tokenOpToNVSEOpType; + + inline Script::VariableType GetScriptTypeFromTokenType(NVSETokenType t) { + switch (t) { + case NVSETokenType::DoubleType: + return Script::eVarType_Float; + case NVSETokenType::IntType: + return Script::eVarType_Integer; + case NVSETokenType::RefType: + return Script::eVarType_Ref; + case NVSETokenType::ArrayType: + return Script::eVarType_Array; + case NVSETokenType::StringType: + return Script::eVarType_String; + default: + return Script::eVarType_Invalid; + } + } + + inline Script::VariableType GetScriptTypeFromToken(NVSEToken t) { + return GetScriptTypeFromTokenType(t.type); + } + + inline Token_Type GetBasicTokenType(Token_Type type) { + switch (type) { + case kTokenType_Array: + case kTokenType_ArrayVar: + return kTokenType_Array; + case kTokenType_Number: + case kTokenType_NumericVar: + return kTokenType_Number; + case kTokenType_String: + case kTokenType_StringVar: + return kTokenType_String; + case kTokenType_Ref: + case kTokenType_RefVar: + return kTokenType_Ref; + } + + return type; + } + + inline Token_Type NVSETokenType_To_TokenType(NVSETokenType type) { + switch (type) { + case NVSETokenType::StringType: + return kTokenType_StringVar; + case NVSETokenType::ArrayType: + return kTokenType_ArrayVar; + case NVSETokenType::RefType: + return kTokenType_RefVar; + // Short + case NVSETokenType::DoubleType: + case NVSETokenType::IntType: + return kTokenType_NumericVar; + default: + return kTokenType_Ambiguous; + } + } + + inline Token_Type VariableType_To_TokenType(Script::VariableType type) { + switch (type) { + case Script::eVarType_Float: + case Script::eVarType_Integer: + return kTokenType_Number; + case Script::eVarType_String: + return kTokenType_String; + case Script::eVarType_Array: + return kTokenType_Array; + case Script::eVarType_Ref: + return kTokenType_Ref; + case Script::eVarType_Invalid: + default: + return kTokenType_Invalid; + } + } + + inline Token_Type TokenType_To_Variable_TokenType(Token_Type type) { + switch (type) { + case kTokenType_Number: + return kTokenType_NumericVar; + case kTokenType_String: + return kTokenType_StringVar; + case kTokenType_Ref: + return kTokenType_RefVar; + case kTokenType_Array: + return kTokenType_ArrayVar; + default: + return kTokenType_Invalid; + } + } + + inline NVSEParamType TokenType_To_ParamType(Token_Type tt) { + switch (tt) { + case kTokenType_Number: + return kNVSEParamType_Number; + case kTokenType_Form: + case kTokenType_Ref: + return kNVSEParamType_Form; + case kTokenType_Array: + return kNVSEParamType_Array; + case kTokenType_String: + return kNVSEParamType_String; + } + + throw std::runtime_error( + std::format("Type '{}' is not a basic type! Please report this as a bug.", TokenTypeToString(tt)) + ); + } + + inline bool CanUseDotOperator(Token_Type tt) { + if (tt == kTokenType_Form) { + return true; + } + + if (GetBasicTokenType(tt) == kTokenType_Ref) { + return true; + } + + if (tt == kTokenType_Ambiguous) { + return true; + } + + if (tt == kTokenType_ArrayElement) { + return true; + } + + return false; + } + + inline const char* GetBasicParamTypeString(NVSEParamType pt) { + switch (pt) { + case kNVSEParamType_Number: + return "Number"; + case kNVSEParamType_Form: + return "Form/Ref"; + case kNVSEParamType_Array: + return "Array"; + case kTokenType_String: + return "String"; + } + + return ""; + } + + void CompDbg(const char* fmt, ...); + void CompInfo(const char* fmt, ...); + void CompErr(const char* fmt, ...); + + bool isDefaultParse(Cmd_Parse parse); + + void resolveVanillaEnum(const ParamInfo* info, const char* str, uint32_t* val, uint32_t* len); + bool doesFormMatchParamType(TESForm* form, ParamType type); +} diff --git a/nvse/nvse/Compiler/NVSETreePrinter.cpp b/nvse/nvse/Compiler/NVSETreePrinter.cpp new file mode 100644 index 00000000..48ecac58 --- /dev/null +++ b/nvse/nvse/Compiler/NVSETreePrinter.cpp @@ -0,0 +1,529 @@ +#include "NVSETreePrinter.h" +#include + +#include "NVSECompilerUtils.h" +#include "Parser.h" +#include "AST/AST.h" + +namespace Compiler { + void NVSETreePrinter::PrintTabs(const bool debugOnly) { + for (int i = 0; i < curTab; i++) { + if (debugOnly) { + CompDbg(" "); + } + else { + CompInfo(" "); + } + } + } + + void NVSETreePrinter::Visit(AST* script) { + CompDbg("\n==== AST ====\n\n"); + PrintTabs(); + CompDbg("name: %s\n", script->name.lexeme.c_str()); + PrintTabs(); + if (!script->globalVars.empty()) { + CompDbg("globals\n"); + curTab++; + for (auto& g : script->globalVars) { + g->Accept(this); + } + curTab--; + } + CompDbg("blocks\n"); + curTab++; + for (auto& b : script->blocks) { + b->Accept(this); + } + curTab--; + } + + void NVSETreePrinter::VisitBeginStmt(Statements::Begin* stmt) { + PrintTabs(); + CompDbg("begin %s %s\n", stmt->name.lexeme.c_str(), stmt->param.has_value() ? stmt->param->lexeme.c_str() : ""); + curTab++; + PrintTabs(); + CompDbg("body\n"); + curTab++; + stmt->block->Accept(this); + curTab--; + curTab--; + } + + void NVSETreePrinter::VisitFnStmt(Statements::UDFDecl* stmt) { + PrintTabs(); + CompDbg("fn\n"); + curTab++; + PrintTabs(); + CompDbg("args\n"); + curTab++; + for (auto var_decl_stmt : stmt->args) { + var_decl_stmt->Accept(this); + } + curTab--; + PrintTabs(); + CompDbg("body\n"); + curTab++; + stmt->body->Accept(this); + curTab--; + curTab--; + } + + void NVSETreePrinter::VisitVarDeclStmt(Statements::VarDecl* stmt) { + PrintTabs(); + CompDbg("vardecl\n"); + + curTab++; + for (auto [name, value, info] : stmt->declarations) { + PrintTabs(); + CompDbg("%s\n", name.lexeme.c_str()); + if (value) { + curTab++; + value->Accept(this); + curTab--; + } + } + + curTab--; + } + + void NVSETreePrinter::VisitExprStmt(const Statements::ExpressionStatement* stmt) { + PrintTabs(); + CompDbg("exprstmt\n"); + curTab++; + if (stmt->expr) { + stmt->expr->Accept(this); + } + curTab--; + } + void NVSETreePrinter::VisitForStmt(Statements::For* stmt) { + PrintTabs(); + CompDbg("for\n"); + + curTab++; + + if (stmt->init) { + PrintTabs(); + CompDbg("init\n"); + curTab++; + stmt->init->Accept(this); + curTab--; + } + + if (stmt->cond) { + PrintTabs(); + CompDbg("cond\n"); + curTab++; + stmt->cond->Accept(this); + curTab--; + } + + if (stmt->post) { + PrintTabs(); + CompDbg("post\n"); + curTab++; + stmt->post->Accept(this); + curTab--; + } + + PrintTabs(); + CompDbg("block\n"); + curTab++; + stmt->block->Accept(this); + curTab--; + + curTab--; + } + + void NVSETreePrinter::VisitForEachStmt(Statements::ForEach* stmt) { + PrintTabs(); + CompDbg("foreach\n"); + + curTab++; + + PrintTabs(); + CompDbg("elem\n"); + curTab++; + for (auto decl : stmt->declarations) { + if (decl) { + decl->Accept(this); + } + } + curTab--; + + PrintTabs(); + CompDbg("in\n"); + curTab++; + stmt->rhs->Accept(this); + curTab--; + + PrintTabs(); + CompDbg("block\n"); + curTab++; + stmt->block->Accept(this); + curTab--; + + curTab--; + } + + void NVSETreePrinter::VisitIfStmt(Statements::If* stmt) { + PrintTabs(); + CompDbg("if\n"); + + curTab++; + + PrintTabs(); + CompDbg("cond\n"); + curTab++; + stmt->cond->Accept(this); + curTab--; + + PrintTabs(); + CompDbg("block\n"); + curTab++; + stmt->block->Accept(this); + curTab--; + + if (stmt->elseBlock) { + PrintTabs(); + CompDbg("else\n"); + curTab++; + stmt->elseBlock->Accept(this); + curTab--; + } + + curTab--; + } + void NVSETreePrinter::VisitReturnStmt(Statements::Return* stmt) { + PrintTabs(); + CompDbg("return\n"); + + if (stmt->expr) { + curTab++; + PrintTabs(); + CompDbg("value\n"); + curTab++; + stmt->expr->Accept(this); + curTab--; + curTab--; + } + } + + void NVSETreePrinter::VisitContinueStmt(Statements::Continue* stmt) { + PrintTabs(); + CompDbg("continue"); + } + + void NVSETreePrinter::VisitBreakStmt(Statements::Break* stmt) { + PrintTabs(); + CompDbg("break"); + } + + void NVSETreePrinter::VisitWhileStmt(Statements::While* stmt) { + PrintTabs(); + CompDbg("while\n"); + + curTab++; + + PrintTabs(); + CompDbg("cond\n"); + curTab++; + stmt->cond->Accept(this); + curTab--; + + PrintTabs(); + CompDbg("block\n"); + curTab++; + stmt->block->Accept(this); + curTab--; + + curTab--; + } + + void NVSETreePrinter::VisitBlockStmt(Statements::Block* stmt) { + for (auto stmt : stmt->statements) { + stmt->Accept(this); + } + } + + void NVSETreePrinter::VisitAssignmentExpr(Expressions::AssignmentExpr* expr) { + PrintTabs(); + CompDbg("assignment\n"); + curTab++; + PrintTabs(); + CompDbg("op: %s\n", expr->token.lexeme.c_str()); + PrintTabs(); + CompDbg("lhs:\n"); + curTab++; + expr->left->Accept(this); + curTab--; + PrintTabs(); + CompDbg("rhs\n"); + curTab++; + expr->expr->Accept(this); + curTab--; + curTab--; + } + + void NVSETreePrinter::VisitTernaryExpr(Expressions::TernaryExpr* expr) { + PrintTabs(); + CompDbg("ternary\n"); + curTab++; + + PrintTabs(); + CompDbg("cond\n"); + curTab++; + expr->cond->Accept(this); + curTab--; + + if (expr->left) { + PrintTabs(); + CompDbg("lhs\n"); + curTab++; + expr->left->Accept(this); + curTab--; + } + + PrintTabs(); + CompDbg("rhs\n"); + curTab++; + expr->right->Accept(this); + curTab--; + + curTab--; + } + + void NVSETreePrinter::VisitInExpr(Expressions::InExpr* expr) { + PrintTabs(); + CompDbg("inexpr\n"); + curTab++; + PrintTabs(); + CompDbg("isNot: %s\n", expr->isNot ? "y" : "n"); + PrintTabs(); + CompDbg("expr\n"); + curTab++; + expr->lhs->Accept(this); + curTab--; + PrintTabs(); + CompDbg("val\n"); + curTab++; + if (expr->expression) { + expr->expression->Accept(this); + } + else { + for (auto val : expr->values) { + val->Accept(this); + } + } + curTab--; + curTab--; + } + + void NVSETreePrinter::VisitBinaryExpr(Expressions::BinaryExpr* expr) { + PrintTabs(); + CompDbg("binary: %s\n", expr->op.lexeme.c_str()); + curTab++; + PrintTabs(); + CompDbg("lhs\n"); + curTab++; + expr->left->Accept(this); + curTab--; + PrintTabs(); + CompDbg("rhs\n"); + curTab++; + expr->right->Accept(this); + curTab--; + + PrintTabs(); + CompDbg("detailed type: %s\n", TokenTypeToString(expr->type)); + + curTab--; + } + + void NVSETreePrinter::VisitUnaryExpr(Expressions::UnaryExpr* expr) { + PrintTabs(); + CompDbg("unary: %s\n", expr->op.lexeme.c_str()); + curTab++; + expr->expr->Accept(this); + + PrintTabs(); + CompDbg("detailed type: %s\n", TokenTypeToString(expr->type)); + + curTab--; + } + + void NVSETreePrinter::VisitSubscriptExpr(Expressions::SubscriptExpr* expr) { + PrintTabs(); + CompDbg("subscript\n"); + + curTab++; + PrintTabs(); + CompDbg("expr\n"); + curTab++; + expr->left->Accept(this); + curTab--; + + PrintTabs(); + CompDbg("[]\n"); + curTab++; + expr->index->Accept(this); + curTab--; + + PrintTabs(); + CompDbg("detailed type: %s\n", TokenTypeToString(expr->type)); + + curTab--; + } + + void NVSETreePrinter::VisitCallExpr(Expressions::CallExpr* expr) { + PrintTabs(); + CompDbg("call : %s\n", expr->token.lexeme.c_str()); + curTab++; + + if (expr->left) { + PrintTabs(); + CompDbg("expr\n"); + curTab++; + expr->left->Accept(this); + curTab--; + } + + if (!expr->args.empty()) { + PrintTabs(); + CompDbg("args\n"); + curTab++; + for (const auto& exp : expr->args) { + exp->Accept(this); + } + curTab--; + } + + PrintTabs(); + CompDbg("detailed type: %s\n", TokenTypeToString(expr->type)); + + curTab--; + } + + void NVSETreePrinter::VisitGetExpr(Expressions::GetExpr* expr) { + PrintTabs(); + CompDbg("get\n"); + curTab++; + PrintTabs(); + CompDbg("expr\n"); + curTab++; + expr->left->Accept(this); + curTab--; + PrintTabs(); + CompDbg("token: %s\n", expr->identifier.lexeme.c_str()); + + PrintTabs(); + CompDbg("detailed type: %s\n", TokenTypeToString(expr->type)); + + curTab--; + } + + void NVSETreePrinter::VisitBoolExpr(Expressions::BoolExpr* expr) { + PrintTabs(); + if (expr->value) { + CompDbg("boolean: true\n"); + } + else { + CompDbg("boolean: false\n"); + } + + curTab++; + PrintTabs(); + CompDbg("detailed type: %s\n", TokenTypeToString(expr->type)); + curTab--; + } + + void NVSETreePrinter::VisitNumberExpr(Expressions::NumberExpr* expr) { + PrintTabs(); + CompDbg("number: %0.5f\n", expr->value); + + curTab++; + PrintTabs(); + CompDbg("detailed type: %s\n", TokenTypeToString(expr->type)); + curTab--; + } + + void NVSETreePrinter::VisitStringExpr(Expressions::StringExpr* expr) { + PrintTabs(); + CompDbg("string: %s\n", expr->token.lexeme.c_str()); + + curTab++; + PrintTabs(); + CompDbg("detailed type: %s\n", TokenTypeToString(expr->type)); + curTab--; + } + + void NVSETreePrinter::VisitIdentExpr(Expressions::IdentExpr* expr) { + PrintTabs(); + CompDbg("ident: %s\n", expr->token.lexeme.c_str()); + + curTab++; + PrintTabs(); + CompDbg("detailed type: %s\n", TokenTypeToString(expr->type)); + curTab--; + } + + void NVSETreePrinter::VisitMapLiteralExpr(Expressions::MapLiteralExpr* expr) {} + + void NVSETreePrinter::VisitArrayLiteralExpr(Expressions::ArrayLiteralExpr* expr) { + PrintTabs(); + CompDbg("array literal\n"); + curTab++; + PrintTabs(); + CompDbg("values\n"); + curTab++; + for (auto val : expr->values) { + val->Accept(this); + } + curTab--; + PrintTabs(); + CompDbg("detailed type: %s\n", TokenTypeToString(expr->type)); + curTab--; + } + + void NVSETreePrinter::VisitGroupingExpr(Expressions::GroupingExpr* expr) { + PrintTabs(); + CompDbg("grouping\n"); + curTab++; + expr->expr->Accept(this); + curTab--; + + curTab++; + PrintTabs(); + CompDbg("detailed type: %s\n", TokenTypeToString(expr->type)); + curTab--; + } + + void NVSETreePrinter::VisitLambdaExpr(Expressions::LambdaExpr* expr) { + PrintTabs(); + CompDbg("lambda\n"); + curTab++; + + if (!expr->args.empty()) { + PrintTabs(); + CompDbg("args\n"); + curTab++; + for (auto& arg : expr->args) { + arg->Accept(this); + } + curTab--; + } + + PrintTabs(); + CompDbg("body\n"); + curTab++; + expr->body->Accept(this); + curTab--; + + curTab--; + + curTab++; + PrintTabs(); + CompDbg("detailed type: %s\n", TokenTypeToString(expr->type)); + curTab--; + } +} diff --git a/nvse/nvse/Compiler/NVSETreePrinter.h b/nvse/nvse/Compiler/NVSETreePrinter.h new file mode 100644 index 00000000..745f2dc9 --- /dev/null +++ b/nvse/nvse/Compiler/NVSETreePrinter.h @@ -0,0 +1,44 @@ +#pragma once +#include "Visitor.h" + +namespace Compiler { + class NVSETreePrinter : public Visitor { + int curTab = 0; + void PrintTabs(const bool debugOnly = true); + + public: + NVSETreePrinter() = default; + + void Visit(AST* script) override; + void VisitBeginStmt(Statements::Begin* stmt) override; + void VisitFnStmt(Statements::UDFDecl* stmt) override; + void VisitVarDeclStmt(Statements::VarDecl* stmt) override; + + void VisitExprStmt(const Statements::ExpressionStatement* stmt) override; + void VisitForStmt(Statements::For* stmt) override; + void VisitForEachStmt(Statements::ForEach* stmt) override; + void VisitIfStmt(Statements::If* stmt) override; + void VisitReturnStmt(Statements::Return* stmt) override; + void VisitContinueStmt(Statements::Continue* stmt) override; + void VisitBreakStmt(Statements::Break* stmt) override; + void VisitWhileStmt(Statements::While* stmt) override; + void VisitBlockStmt(Statements::Block* stmt) override; + + void VisitAssignmentExpr(Expressions::AssignmentExpr* expr) override; + void VisitTernaryExpr(Expressions::TernaryExpr* expr) override; + void VisitInExpr(Expressions::InExpr* expr) override; + void VisitBinaryExpr(Expressions::BinaryExpr* expr) override; + void VisitUnaryExpr(Expressions::UnaryExpr* expr) override; + void VisitSubscriptExpr(Expressions::SubscriptExpr* expr) override; + void VisitCallExpr(Expressions::CallExpr* expr) override; + void VisitGetExpr(Expressions::GetExpr* expr) override; + void VisitBoolExpr(Expressions::BoolExpr* expr) override; + void VisitNumberExpr(Expressions::NumberExpr* expr) override; + void VisitStringExpr(Expressions::StringExpr* expr) override; + void VisitIdentExpr(Expressions::IdentExpr* expr) override; + void VisitMapLiteralExpr(Expressions::MapLiteralExpr* expr) override; + void VisitArrayLiteralExpr(Expressions::ArrayLiteralExpr* expr) override; + void VisitGroupingExpr(Expressions::GroupingExpr* expr) override; + void VisitLambdaExpr(Expressions::LambdaExpr* expr) override; + }; +} \ No newline at end of file diff --git a/nvse/nvse/Compiler/NVSETypeChecker.cpp b/nvse/nvse/Compiler/NVSETypeChecker.cpp new file mode 100644 index 00000000..6001b39a --- /dev/null +++ b/nvse/nvse/Compiler/NVSETypeChecker.cpp @@ -0,0 +1,937 @@ +#include +#include +#include "NVSECompilerUtils.h" + +#include "NVSETypeChecker.h" +#include "AST/AST.h" + +#include "../ScriptTokens.h" +#include "../GameForms.h" +#include "../Commands_Scripting.h" +#include "../GameData.h" +#include "../Hooks_Script.h" +#include "../ScriptUtils.h" +#include "../PluginAPI.h" + +#define WRAP_ERROR(expr) \ + try { \ + expr; \ +} \ + catch (std::runtime_error& er) { \ + CompErr("%s\n", er.what()); \ + } + +std::string getTypeErrorMsg(Token_Type lhs, Token_Type rhs) { + return std::format("Cannot convert from {} to {}", TokenTypeToString(lhs), TokenTypeToString(rhs)); +} + +namespace Compiler { + void NVSETypeChecker::error(size_t line, std::string msg) { + hadError = true; + throw std::runtime_error(std::format("[line {}] {}", line, msg)); + } + + void NVSETypeChecker::error(size_t line, size_t column, std::string msg) { + hadError = true; + throw std::runtime_error(std::format("[line {}:{}] {}", line, column, msg)); + } + + bool NVSETypeChecker::check() { + WRAP_ERROR(script->Accept(this)) + + return !hadError; + } + + void NVSETypeChecker::Visit(AST* script) { + // Add nvse as requirement + script->m_mpPluginRequirements["nvse"] = PACKED_NVSE_VERSION; + + // Add all #version statements to script requirements + for (const auto& [plugin, version] : g_currentCompilerPluginVersions.top()) { + auto nameLowered = plugin; + std::ranges::transform(nameLowered.begin(), nameLowered.end(), nameLowered.begin(), [](unsigned char c) { return std::tolower(c); }); + script->m_mpPluginRequirements[nameLowered] = version; + } + + for (const auto& global : script->globalVars) { + global->Accept(this); + + // Dont allow initializers in global scope + for (auto& [name, value, _] : dynamic_cast(global.get())->declarations) { + if (value) { + WRAP_ERROR(error(name.line, "Variable initializers are not allowed in global scope.")) + } + } + } + + // Need to associate / start indexing after existing non-temp script vars + // if (engineScript && engineScript->varList.Count() > 0) { + // for (const auto var : engineScript->varList) { + // if (strncmp(var->name.CStr(), "__temp", strlen("__temp")) != 0) { + // scopes.top()->varIndex++; + // } + // } + // } + + // Pre-process blocks - check for duplicate blocks + std::unordered_map> mpTypeToModes{}; + std::vector functions{}; + bool foundFn = false, foundEvent = false; + for (const auto& block : script->blocks) { + if (const auto b = dynamic_cast(block.get())) { + if (foundFn) { + WRAP_ERROR(error(b->name.line, "Cannot have a function block and an event block in the same script.")) + } + + foundEvent = true; + + std::string name = b->name.lexeme; + std::string param{}; + if (b->param.has_value()) { + param = b->param->lexeme; + } + + if (mpTypeToModes.contains(name) && mpTypeToModes[name].contains(param)) { + WRAP_ERROR(error(b->name.line, "Duplicate block declaration.")) + continue; + } + + if (!mpTypeToModes.contains(name)) { + mpTypeToModes[name] = std::unordered_set{}; + } + mpTypeToModes[name].insert(param); + } + + if (const auto b = dynamic_cast(block.get())) { + if (foundEvent) { + WRAP_ERROR(error(b->token.line, "Cannot have a function block and an event block in the same script.")) + } + + if (foundFn) { + WRAP_ERROR(error(b->token.line, "Duplicate block declaration.")) + } + + foundFn = true; + } + } + + for (const auto& block : script->blocks) { + block->Accept(this); + } + } + + void NVSETypeChecker::VisitFnStmt(Statements::UDFDecl* stmt) { + for (const auto& decl : stmt->args) { + WRAP_ERROR(decl->Accept(this)) + } + + returnType.emplace(); + stmt->body->Accept(this); + returnType.pop(); + } + + void NVSETypeChecker::VisitVarDeclStmt(Statements::VarDecl* stmt) { + auto detailedType = NVSETokenType_To_TokenType(stmt->type.type); + for (auto [name, expr, varInfo] : stmt->declarations) { + // // See if variable has already been declared + // if (auto var = scopes.top()->resolveVariable(name.lexeme, false)) { + // WRAP_ERROR(error(name.line, std::format("Variable with name '{}' has already been defined in the current scope (at line {})\n", name.lexeme, var->token.line))); + // continue; + // } + + if (g_scriptCommands.GetByName(name.lexeme.c_str())) { + WRAP_ERROR(error(name.line, std::format("Variable name '{}' conflicts with a command with the same name.", name.lexeme))); + continue; + } + + // Warn if name shadows a form + if (auto form = GetFormByID(name.lexeme.c_str())) { + auto modName = DataHandler::Get()->GetActiveModList()[form->GetModIndex()]->name; +#ifdef EDITOR + CompInfo("[line %d] Info: Variable with name '%s' shadows a form with the same name from mod '%s'\n", + name.line, name.lexeme.c_str(), modName); +#else + CompInfo("Info: Variable with name '%s' shadows a form with the same name from mod '%s'. This is NOT an error. Do not contact the mod author.", name.lexeme.c_str(), modName); +#endif + } + + // if (auto shadowed = scopes.top()->resolveVariable(name.lexeme, true)) { + // #ifdef EDITOR + // CompInfo("[line %d] Info: Variable with name '%s' shadows a variable with the same name in outer scope. (Defined at line %d)\n", + // name.line, name.lexeme.c_str(), shadowed->token.line, shadowed->token.column); + // #endif + // } + + if (expr) { + expr->Accept(this); + auto rhsType = expr->type; + if (s_operators[kOpType_Assignment].GetResult(detailedType, rhsType) == kTokenType_Invalid) { + WRAP_ERROR(error(name.line, getTypeErrorMsg(rhsType, detailedType))) + return; + } + } + + if (!varInfo) { + error(expr->line, "Unexpected compiler failure. Please report this as a bug."); + } + + varInfo->detailed_type = detailedType; + + // Set lambda info + if (expr->IsType()) { + auto* lambda = dynamic_cast(expr.get()); + varInfo->lambda_type_info.is_lambda = true; + varInfo->lambda_type_info.return_type = lambda->typeinfo.returnType; + varInfo->lambda_type_info.param_types = lambda->typeinfo.paramTypes; + } + + // Assign this new scope var to this statment for lookup in compiler + // stmt->scopeVars.push_back(scopes.top()->addVariable(name.lexeme, var)); + stmt->detailedType = detailedType; + } + } + + void NVSETypeChecker::VisitExprStmt(const Statements::ExpressionStatement* stmt) { + if (stmt->expr) { + stmt->expr->Accept(this); + } + } + + void NVSETypeChecker::VisitForStmt(Statements::For* stmt) { + if (stmt->init) { + WRAP_ERROR(stmt->init->Accept(this)) + } + + if (stmt->cond) { + WRAP_ERROR( + stmt->cond->Accept(this); + + // Check if condition can evaluate to bool + const auto lType = stmt->cond->type; + const auto oType = s_operators[kOpType_Equals].GetResult(lType, kTokenType_Boolean); + if (oType != kTokenType_Boolean) { + error(stmt->line, std::format("Invalid expression type ('{}') for loop condition.", TokenTypeToString(oType))); + } + + stmt->cond->type = oType; + ) + } + + if (stmt->post) { + WRAP_ERROR(stmt->post->Accept(this)) + } + + insideLoop.push(true); + stmt->block->Accept(this); + insideLoop.pop(); + } + + void NVSETypeChecker::VisitForEachStmt(Statements::ForEach* stmt) { + for (const auto& decl : stmt->declarations) { + if (decl) { + WRAP_ERROR(decl->Accept(this)) + } + } + stmt->rhs->Accept(this); + + WRAP_ERROR( + if (stmt->decompose) { + // TODO + // What should I check here? + } + else { + // Get type of lhs identifier -- this is way too verbose + const auto lType = stmt->declarations[0]->detailedType; + const auto rType = stmt->rhs->type; + if (s_operators[kOpType_In].GetResult(lType, rType) == kTokenType_Invalid) { + error(stmt->line, std::format("Invalid types '{}' and '{}' passed to for-in expression.", + TokenTypeToString(lType), TokenTypeToString(rType))); + } + } + ) + + insideLoop.push(true); + stmt->block->Accept(this); + insideLoop.pop(); + } + + void NVSETypeChecker::VisitIfStmt(Statements::If* stmt) { + WRAP_ERROR( + stmt->cond->Accept(this); + + // Check if condition can evaluate to bool + const auto lType = stmt->cond->type; + if (!CanConvertOperand(lType, kTokenType_Boolean)) { + error(stmt->line, std::format("Invalid expression type '{}' for if statement.", TokenTypeToString(lType))); + stmt->cond->type = kTokenType_Invalid; + } + else { + stmt->cond->type = kTokenType_Boolean; + } + ) + stmt->block->Accept(this); + + if (stmt->elseBlock) { + stmt->elseBlock->Accept(this); + } + } + + void NVSETypeChecker::VisitReturnStmt(Statements::Return* stmt) { + if (stmt->expr) { + stmt->expr->Accept(this); + + const auto basicType = GetBasicTokenType(stmt->expr->type); + auto& [type, line] = returnType.top(); + + if (type != kTokenType_Invalid) { + if (!CanConvertOperand(basicType, type)) { + const auto msg = std::format( + "Return type '{}' not compatible with return type defined previously. ('{}' at line {})", + TokenTypeToString(basicType), + TokenTypeToString(type), + line); + + error(line, msg); + } + } + else { + type = basicType; + line = stmt->line; + } + + stmt->detailedType = stmt->expr->type; + } + else { + stmt->detailedType = kTokenType_Empty; + } + } + + void NVSETypeChecker::VisitContinueStmt(Statements::Continue* stmt) { + if (insideLoop.empty() || !insideLoop.top()) { + error(stmt->line, "Keyword 'continue' not valid outside of loops."); + } + } + + void NVSETypeChecker::VisitBreakStmt(Statements::Break* stmt) { + if (insideLoop.empty() || !insideLoop.top()) { + error(stmt->line, "Keyword 'break' not valid outside of loops."); + } + } + + void NVSETypeChecker::VisitWhileStmt(Statements::While* stmt) { + WRAP_ERROR( + stmt->cond->Accept(this); + + // Check if condition can evaluate to bool + const auto lType = stmt->cond->type; + const auto rType = kTokenType_Boolean; + const auto oType = s_operators[kOpType_Equals].GetResult(lType, rType); + if (oType != kTokenType_Boolean) { + error(stmt->line, "Invalid expression type for while loop."); + } + stmt->cond->type = oType; + ) + + insideLoop.push(true); + stmt->block->Accept(this); + insideLoop.pop(); + } + + void NVSETypeChecker::VisitBlockStmt(Statements::Block* stmt) { + for (const auto& statement : stmt->statements) { + WRAP_ERROR(statement->Accept(this)) + } + } + + void NVSETypeChecker::VisitAssignmentExpr(Expressions::AssignmentExpr* expr) { + expr->left->Accept(this); + expr->expr->Accept(this); + + const auto lType = expr->left->type; + const auto rType = expr->expr->type; + const auto oType = s_operators[tokenOpToNVSEOpType[expr->token.type]].GetResult(lType, rType); + if (oType == kTokenType_Invalid) { + const auto msg = std::format("Invalid types '{}' and '{}' for operator {} ({}).", + TokenTypeToString(lType), TokenTypeToString(rType), expr->token.lexeme, + TokenTypeStr[static_cast(expr->token.type)]); + error(expr->line, msg); + return; + } + + // Probably always true + if (expr->left->IsType()) { + const auto* ident = dynamic_cast(expr->left.get()); + if (ident->varInfo && ident->varInfo->lambda_type_info.is_lambda) { + error(expr->line, "Cannot assign to a variable that is holding a lambda."); + return; + } + + if (expr->expr->type == kTokenType_Lambda) { + error(expr->line, "Cannot assign a lambda to a variable after it has been declared."); + return; + } + } + + expr->type = oType; + expr->left->type = oType; + } + + void NVSETypeChecker::VisitTernaryExpr(Expressions::TernaryExpr* expr) { + WRAP_ERROR( + expr->cond->Accept(this); + + // Check if condition can evaluate to bool + const auto lType = expr->cond->type; + const auto oType = s_operators[kOpType_Equals].GetResult(lType, kTokenType_Boolean); + if (oType == kTokenType_Invalid) { + error(expr->line, std::format("Invalid expression type '{}' for if statement.", TokenTypeToString(lType))); + expr->cond->type = kTokenType_Invalid; + } + else { + expr->cond->type = oType; + } + ) + + WRAP_ERROR(expr->left->Accept(this)) + WRAP_ERROR(expr->right->Accept(this)) + if (!CanConvertOperand(expr->right->type, GetBasicTokenType(expr->left->type))) { + error(expr->line, std::format("Incompatible value types ('{}' and '{}') specified for ternary expression.", + TokenTypeToString(expr->left->type), TokenTypeToString(expr->right->type))); + } + + expr->type = expr->left->type; + } + + void NVSETypeChecker::VisitInExpr(Expressions::InExpr* expr) { + expr->lhs->Accept(this); + + if (!expr->values.empty()) { + int idx = 0; + for (const auto& val : expr->values) { + idx++; + val->Accept(this); + + const auto lhsType = expr->lhs->type; + const auto rhsType = val->type; + const auto outputType = s_operators[tokenOpToNVSEOpType[NVSETokenType::EqEq]].GetResult(lhsType, rhsType); + if (outputType == kTokenType_Invalid) { + WRAP_ERROR( + const auto msg = std::format("Value {} (type '{}') cannot compare against the {} specified on lhs of 'in' expression.", idx, + TokenTypeToString(rhsType), TokenTypeToString(lhsType)); + error(expr->token.line, msg); + ) + } + } + } + // Any other expression, compiles to ar_find + else { + expr->expression->Accept(this); + if (expr->expression->type != kTokenType_Ambiguous) { + if (expr->expression->type != kTokenType_Array && expr->expression->type != kTokenType_ArrayVar) { + WRAP_ERROR( + const auto msg = std::format("Expected array for 'in' expression (Got '{}').", TokenTypeToString(expr->expression->type)); + error(expr->token.line, msg); + ) + } + } + } + + expr->type = kTokenType_Boolean; + } + + void NVSETypeChecker::VisitBinaryExpr(Expressions::BinaryExpr* expr) { + expr->left->Accept(this); + expr->right->Accept(this); + + const auto lhsType = expr->left->type; + const auto rhsType = expr->right->type; + const auto outputType = s_operators[tokenOpToNVSEOpType[expr->op.type]].GetResult(lhsType, rhsType); + if (outputType == kTokenType_Invalid) { + const auto msg = std::format("Invalid types '{}' and '{}' for operator {} ({}).", + TokenTypeToString(lhsType), TokenTypeToString(rhsType), expr->op.lexeme, + TokenTypeStr[static_cast(expr->op.type)]); + error(expr->op.line, msg); + return; + } + + expr->type = outputType; + } + + void NVSETypeChecker::VisitUnaryExpr(Expressions::UnaryExpr* expr) { + expr->expr->Accept(this); + + if (expr->postfix) { + const auto lType = expr->expr->type; + const auto rType = kTokenType_Number; + const auto oType = s_operators[tokenOpToNVSEOpType[expr->op.type]].GetResult(lType, rType); + if (oType == kTokenType_Invalid) { + error(expr->op.line, std::format("Invalid operand type '{}' for operator {} ({}).", + TokenTypeToString(lType), + expr->op.lexeme, TokenTypeStr[static_cast(expr->op.type)])); + } + + expr->type = oType; + } + // -/!/$ + else { + const auto lType = expr->expr->type; + const auto rType = kTokenType_Invalid; + const auto oType = s_operators[tokenOpToNVSEOpType[expr->op.type]].GetResult(lType, rType); + if (oType == kTokenType_Invalid) { + error(expr->op.line, std::format("Invalid operand type '{}' for operator {} ({}).", + TokenTypeToString(lType), + expr->op.lexeme, TokenTypeStr[static_cast(expr->op.type)])); + } + expr->type = oType; + } + } + + void NVSETypeChecker::VisitSubscriptExpr(Expressions::SubscriptExpr* expr) { + expr->left->Accept(this); + expr->index->Accept(this); + + const auto lhsType = expr->left->type; + const auto indexType = expr->index->type; + const auto outputType = s_operators[kOpType_LeftBracket].GetResult(lhsType, indexType); + if (outputType == kTokenType_Invalid) { + error(expr->op.line, + std::format("Expression type '{}' not valid for operator [].", TokenTypeToString(indexType))); + return; + } + + expr->type = outputType; + } + + struct CallCommandInfo { + int funcIndex{}; + int argStart{}; + }; + + std::unordered_map callCmds = { + {"Call", {0, 1}}, + {"CallAfterFrames", {1, 3}}, + {"CallAfterSeconds", {1, 3}}, + {"CallForSeconds", {1, 3}}, + }; + + CallCommandInfo* getCallCommandInfo(const char* name) { + for (auto& [key, value] : callCmds) { + if (!_stricmp(key, name)) { + return &value; + } + } + + return nullptr; + } + + void NVSETypeChecker::VisitCallExpr(Expressions::CallExpr* expr) { + auto checkLambdaArgs = [&](Expressions::IdentExpr* ident, int startArgs) { + const auto& paramTypes = ident->varInfo->lambda_type_info.param_types; + + for (auto i = startArgs; i < expr->args.size(); i++) { + const auto& arg = expr->args[i]; + + const auto idx = i - startArgs + 1; + + // Too many args passed, handled below + if (i - 1 >= paramTypes.size()) { + break; + } + + const auto expected = paramTypes[i - 1]; + if (!ExpressionParser::ValidateArgType(static_cast(expected), arg->type, true, nullptr)) { + WRAP_ERROR( + error(expr->token.line, std::format("Invalid expression for lambda parameter {}. (Expected {}, got {})", idx, GetBasicParamTypeString(expected), TokenTypeToString(arg->type))); + ) + } + } + + const auto numArgsPassed = max(0, (int)expr->args.size() - startArgs); + if (numArgsPassed != paramTypes.size()) { + WRAP_ERROR( + error(expr->token.line, std::format("Invalid number of parameters specified for lambda '{}' (Expected {}, got {}).", ident->token.lexeme, paramTypes.size(), numArgsPassed)); + ) + } + }; + + std::string name = expr->token.lexeme; + const auto cmd = g_scriptCommands.GetByName(name.c_str()); + + // Try to get the script command by lexeme + if (!cmd) { + // // See if its a lambda call + // if (const auto &var = scopes.top()->resolveVariable(name)) { + // if (var->lambdaTypeInfo.isLambda) { + // // Turn this current expr from 'lambda(args)' into 'call(lambda, args)' + // std::vector newArgs{}; + // newArgs.push_back(std::make_shared(expr->token)); + // expr->token.lexeme = "call"; + // for (const auto& arg : expr->args) { + // newArgs.push_back(arg); + // } + // expr->args = newArgs; + // + // expr->Accept(this); + // return; + // } + // } + + error(expr->token.line, std::format("Invalid command '{}'.", name)); + return; + } + expr->cmdInfo = cmd; + + // Add command to script requirements + if (const auto plugin = g_scriptCommands.GetParentPlugin(cmd)) { + auto nameLowered = std::string(plugin->name); + std::ranges::transform(nameLowered.begin(), nameLowered.end(), nameLowered.begin(), [](unsigned char c) { return std::tolower(c); }); + + if (!script->m_mpPluginRequirements.contains(nameLowered)) { + script->m_mpPluginRequirements[std::string(nameLowered)] = plugin->version; + } + else { + script->m_mpPluginRequirements[std::string(nameLowered)] = max(script->m_mpPluginRequirements[std::string(nameLowered)], plugin->version); + } + } + + if (expr->left) { + expr->left->Accept(this); + } + + if (expr->left && !CanUseDotOperator(expr->left->type)) { + WRAP_ERROR(error(expr->token.line, "Left side of '.' must be a form or reference.")) + } + + // Check for calling reference if not an object script + if (engineScript) { + if (cmd->needsParent && !expr->left && engineScript->Type() != Script::eType_Object && engineScript->Type() != Script::eType_Magic) { + WRAP_ERROR(error(expr->token.line, std::format("Command '{}' requires a calling reference.", expr->token.lexeme))) + } + } + + // Normal (nvse + vanilla) calls + int argIdx = 0; + int paramIdx = 0; + for (; paramIdx < cmd->numParams && argIdx < expr->args.size(); paramIdx++) { + auto param = &cmd->params[paramIdx]; + auto arg = expr->args[argIdx]; + bool convertedEnum = false; + + if (isDefaultParse(cmd->parse) || !_stricmp(cmd->longName, "ShowMessage")) { + // Try to resolve identifiers as vanilla enums + const auto ident = dynamic_cast(arg.get()); + + uint32_t idx = -1; + uint32_t len = 0; + if (ident) { + resolveVanillaEnum(param, ident->token.lexeme.c_str(), &idx, &len); + } + if (idx != -1) { + CompDbg("[line %d] INFO: Converting identifier '%s' to enum index %d\n", arg->line, ident->token.lexeme.c_str(), idx); + arg = std::make_shared(NVSEToken{}, static_cast(idx), false, len); + arg->type = kTokenType_Number; + convertedEnum = true; + } + else { + WRAP_ERROR(arg->Accept(this)) + } + + if (ident && arg->type == kTokenType_Form) { + // Extract form from param + if (!doesFormMatchParamType(ident->form, static_cast(param->typeID))) { + if (!param->isOptional) { + WRAP_ERROR( + error(expr->token.line, std::format("Invalid expression for parameter {}. Expected {}.", argIdx + 1, param->typeStr)); + ) + } + } + else { + argIdx++; + continue; + } + } + } + else { + WRAP_ERROR(arg->Accept(this)) + } + + // Try to resolve as nvse param + if (!convertedEnum && !ExpressionParser::ValidateArgType(static_cast(param->typeID), arg->type, !isDefaultParse(cmd->parse), cmd)) { + if (!param->isOptional) { + WRAP_ERROR( + error(expr->token.line, std::format("Invalid expression for parameter {}. (Expected {}, got {}).", argIdx + 1, param->typeStr, TokenTypeToString(arg->type))); + ) + } + } + else { + expr->args[argIdx] = arg; + argIdx++; + } + } + + int numRequiredArgs = 0; + for (paramIdx = 0; paramIdx < cmd->numParams; paramIdx++) { + if (!cmd->params[paramIdx].isOptional) { + numRequiredArgs++; + } + } + + bool enoughArgs = expr->args.size() >= numRequiredArgs; + if (!enoughArgs) { + WRAP_ERROR( + error(expr->token.line, std::format("Invalid number of parameters specified to {} (Expected {}, got {}).", expr->token.lexeme, numRequiredArgs, expr->args.size())); + ) + } + + // Check lambda args differently + if (const auto callInfo = getCallCommandInfo(cmd->longName)) { + if (!enoughArgs) { + return; + } + + const auto& callee = expr->args[callInfo->funcIndex]; + const auto ident = dynamic_cast(callee.get()); + const auto lambdaCallee = ident && ident->varInfo && ident->varInfo->lambda_type_info.is_lambda; + + // Handle each call command separately as args to check are in different positions + // TODO: FIX THE CALL COMMAND + // Manually visit remaining args for call commands, since they operate differently from all other commands + while (argIdx < expr->args.size()) { + expr->args[argIdx]->Accept(this); + argIdx++; + } + + if (lambdaCallee) { + checkLambdaArgs(ident, callInfo->argStart); + expr->type = ident->varInfo->lambda_type_info.return_type; + } + else { + expr->type = kTokenType_Ambiguous; + } + + return; + } + + auto type = ToTokenType(g_scriptCommands.GetReturnType(cmd)); + if (type == kTokenType_Invalid) { + type = kTokenType_Ambiguous; + } + expr->type = type; + } + + void NVSETypeChecker::VisitGetExpr(Expressions::GetExpr* expr) { + expr->left->Accept(this); + + // Resolve variable type from form + // Try to resolve lhs reference + // Should be ident here + const auto ident = dynamic_cast(expr->left.get()); + if (!ident || expr->left->type != kTokenType_Form) { + error(expr->token.line, "Member access not valid here. Left side of '.' must be a form or persistent reference."); + } + + const auto form = ident->form; + const auto& lhsName = ident->token.lexeme; + const auto& rhsName = expr->identifier.lexeme; + + TESScriptableForm* scriptable = nullptr; + switch (form->typeID) { + case kFormType_TESObjectREFR: { + const auto refr = DYNAMIC_CAST(form, TESForm, TESObjectREFR); + scriptable = DYNAMIC_CAST(refr->baseForm, TESForm, TESScriptableForm); + break; + } + case kFormType_TESQuest: { + scriptable = DYNAMIC_CAST(form, TESForm, TESScriptableForm); + break; + } + default: { + error(expr->line, "Unexpected form type found."); + } + } + + if (scriptable && scriptable->script) { + if (const auto varInfo = scriptable->script->GetVariableByName(rhsName.c_str())) { + const auto detailedType = VariableType_To_TokenType(static_cast(varInfo->type)); + const auto detailedTypeConverted = TokenType_To_Variable_TokenType(detailedType); + if (detailedTypeConverted == kTokenType_Invalid) { + expr->type = detailedType; + } + else { + expr->type = detailedTypeConverted; + } + expr->var_info = varInfo; + expr->reference_name = form->GetEditorID(); + return; + } + + error(expr->line, std::format("Variable {} not found on form {}.", rhsName, lhsName)); + } + + error(expr->line, std::format("Unable to resolve type for '{}.{}'.", lhsName, rhsName)); + } + + void NVSETypeChecker::VisitBoolExpr(Expressions::BoolExpr* expr) { + expr->type = kTokenType_Boolean; + } + + void NVSETypeChecker::VisitNumberExpr(Expressions::NumberExpr* expr) { + if (!expr->is_fp) { + if (expr->value > UINT32_MAX) { + WRAP_ERROR(error(expr->token.line, "Maximum value for integer literal exceeded. (Max: " + std::to_string(UINT32_MAX) + ")")) + } + } + + expr->type = kTokenType_Number; + } + + void NVSETypeChecker::VisitMapLiteralExpr(Expressions::MapLiteralExpr* expr) { + if (expr->values.empty()) { + WRAP_ERROR(error(expr->token.line, + "A map literal cannot be empty, as the key type cannot be deduced.")) + return; + } + + // Check that all expressions are pairs + for (int i = 0; i < expr->values.size(); i++) { + const auto& val = expr->values[i]; + val->Accept(this); + if (val->type != kTokenType_Pair && val->type != kTokenType_Ambiguous) { + WRAP_ERROR(error(expr->token.line, + std::format("Value {} is not a pair and is not valid for a map literal.", i + 1))) + return; + } + } + + // Now check key types + auto lhsType = kTokenType_Invalid; + for (int i = 0; i < expr->values.size(); i++) { + if (expr->values[i]->type != kTokenType_Pair) { + continue; + } + + // Try to check the key + const auto pairPtr = dynamic_cast(expr->values[i].get()); + if (!pairPtr) { + continue; + } + + if (pairPtr->op.type != NVSETokenType::MakePair && pairPtr->left->type != kTokenType_Pair) { + continue; + } + + if (lhsType == kTokenType_Invalid) { + lhsType = GetBasicTokenType(pairPtr->left->type); + } + else { + if (lhsType != pairPtr->left->type) { + auto msg = std::format( + "Key for value {} (type '{}') specified for map literal conflicts with key type of previous value ('{}').", + i, + TokenTypeToString(pairPtr->left->type), + TokenTypeToString(lhsType)); + + WRAP_ERROR(error(expr->token.line, msg)) + } + } + } + + expr->type = kTokenType_Array; + } + + void NVSETypeChecker::VisitArrayLiteralExpr(Expressions::ArrayLiteralExpr* expr) { + if (expr->values.empty()) { + expr->type = kTokenType_Array; + return; + } + + for (const auto& val : expr->values) { + val->Accept(this); + + if (val->type == kTokenType_Pair) { + WRAP_ERROR(error(val->line, "Invalid type inside of array literal. Expected array, string, ref, or number.")) + } + } + + auto lhsType = expr->values[0]->type; + for (int i = 1; i < expr->values.size(); i++) { + if (!CanConvertOperand(expr->values[i]->type, lhsType)) { + auto msg = std::format( + "Value {} (type '{}') specified for array literal conflicts with the type already specified in first element ('{}').", + i, + TokenTypeToString(expr->values[i]->type), + TokenTypeToString(lhsType)); + + WRAP_ERROR(error(expr->token.line, msg)) + } + } + + expr->type = kTokenType_Array; + } + + void NVSETypeChecker::VisitStringExpr(Expressions::StringExpr* expr) { + expr->type = kTokenType_String; + } + + void NVSETypeChecker::VisitIdentExpr(Expressions::IdentExpr* expr) { + const auto name = expr->token.lexeme; + + // if (const auto localVar = scopes.top()->resolveVariable(name)) { + // expr->tokenType = localVar->detailedType; + // expr->varInfo = localVar; + // return; + // } + + if (expr->varInfo) { + expr->type = expr->varInfo->detailed_type; + return; + } + + TESForm* form; + if (!_stricmp(name.c_str(), "player")) { + form = LookupFormByID(0x14); + } + else { + form = GetFormByID(name.c_str()); + } + + if (!form) { + expr->type = kTokenType_Invalid; + error(expr->token.line, std::format("Unable to resolve identifier '{}'.", name)); + } + + if (form->typeID == kFormType_TESGlobal) { + expr->type = kTokenType_Global; + } + else { + expr->type = kTokenType_Form; + expr->form = form; + } + } + + void NVSETypeChecker::VisitGroupingExpr(Expressions::GroupingExpr* expr) { + WRAP_ERROR( + expr->expr->Accept(this); + expr->type = expr->expr->type; + ) + } + + void NVSETypeChecker::VisitLambdaExpr(Expressions::LambdaExpr* expr) { + returnType.emplace(); + + for (const auto& decl : expr->args) { + WRAP_ERROR(decl->Accept(this)) + + expr->typeinfo.paramTypes.push_back(TokenType_To_ParamType(GetBasicTokenType(decl->detailedType))); + } + + insideLoop.push(false); + expr->body->Accept(this); + insideLoop.pop(); + + expr->type = kTokenType_Lambda; + expr->typeinfo.returnType = returnType.top().returnType; + + returnType.pop(); + } +} \ No newline at end of file diff --git a/nvse/nvse/Compiler/NVSETypeChecker.h b/nvse/nvse/Compiler/NVSETypeChecker.h new file mode 100644 index 00000000..96cb98eb --- /dev/null +++ b/nvse/nvse/Compiler/NVSETypeChecker.h @@ -0,0 +1,63 @@ +#pragma once +#include + +#include "../ScriptTokens.h" + +#include +#include "Visitor.h" + +namespace Compiler { + class NVSETypeChecker : Visitor { + struct ReturnInfo { + Token_Type returnType{ kTokenType_Invalid }; + size_t line{ 0 }; + }; + + bool hadError = false; + AST* script; + Script* engineScript; + + std::stack insideLoop{}; + std::stack returnType{}; + + bool bScopedGlobal = false; + + void error(size_t line, std::string msg); + void error(size_t line, size_t column, std::string msg); + + public: + NVSETypeChecker(AST* script, Script* engineScript) : script(script), engineScript(engineScript) {} + bool check(); + + void Visit(AST* script) override; + void VisitFnStmt(Statements::UDFDecl* stmt) override; + void VisitVarDeclStmt(Statements::VarDecl* stmt) override; + void VisitExprStmt(const Statements::ExpressionStatement* stmt) override; + void VisitForStmt(Statements::For* stmt) override; + void VisitForEachStmt(Statements::ForEach* stmt) override; + void VisitIfStmt(Statements::If* stmt) override; + void VisitReturnStmt(Statements::Return* stmt) override; + void VisitContinueStmt(Statements::Continue* stmt) override; + void VisitBreakStmt(Statements::Break* stmt) override; + void VisitWhileStmt(Statements::While* stmt) override; + void VisitBlockStmt(Statements::Block* stmt) override; + void VisitAssignmentExpr(Expressions::AssignmentExpr* expr) override; + void VisitTernaryExpr(Expressions::TernaryExpr* expr) override; + void VisitInExpr(Expressions::InExpr* expr) override; + void VisitBinaryExpr(Expressions::BinaryExpr* expr) override; + void VisitUnaryExpr(Expressions::UnaryExpr* expr) override; + void VisitSubscriptExpr(Expressions::SubscriptExpr* expr) override; + void RegisterPluginDependency(CommandInfo* cmd); + void VisitCallExpr(Expressions::CallExpr* expr) override; + void VisitGetExpr(Expressions::GetExpr* expr) override; + void VisitBoolExpr(Expressions::BoolExpr* expr) override; + void VisitNumberExpr(Expressions::NumberExpr* expr) override; + void VisitMapLiteralExpr(Expressions::MapLiteralExpr* expr) override; + void VisitArrayLiteralExpr(Expressions::ArrayLiteralExpr* expr) override; + void VisitStringExpr(Expressions::StringExpr* expr) override; + void VisitIdentExpr(Expressions::IdentExpr* expr) override; + void VisitGroupingExpr(Expressions::GroupingExpr* expr) override; + void VisitLambdaExpr(Expressions::LambdaExpr* expr) override; + }; + +} \ No newline at end of file diff --git a/nvse/nvse/Compiler/Parser/Parser.cpp b/nvse/nvse/Compiler/Parser/Parser.cpp new file mode 100644 index 00000000..050ba8a1 --- /dev/null +++ b/nvse/nvse/Compiler/Parser/Parser.cpp @@ -0,0 +1,1053 @@ +#include "Parser.h" + +#include +#include +#include +#include +#include "nvse/Hooks_Script.h" +#include "nvse/PluginManager.h" +#include "nvse/Compiler/NVSECompilerUtils.h" + +namespace Compiler { + Parser::Parser(Lexer& tokenizer) : lexer(tokenizer) { + Advance(); + } + + std::optional Parser::Parse() { + + CompDbg("==== PARSER ====\n\n"); + + try { + Expect(NVSETokenType::Name, "Expected 'name' as first statement of script."); + auto expr = Primary(); + if (!dynamic_cast(expr.get())) { + Error(previousToken, "Expected identifier."); + } + Expect(NVSETokenType::Semicolon, "Expected ';'."); + + std::vector globals; + std::vector blocks{}; + auto& pluginVersions = g_currentCompilerPluginVersions.top(); + const auto &name = dynamic_cast(expr.get())->token; + + while (currentToken.type != NVSETokenType::Eof) { + try { + // Version statements + // TODO: Extract this to a pre-processor + if (Match(NVSETokenType::Pound)) { + if (!Match(NVSETokenType::Identifier)) { + Error(currentToken, "Expected 'version'."); + } + + if (_stricmp(previousToken.lexeme.c_str(), "version")) { + Error(currentToken, "Expected 'version'."); + } + + Expect(NVSETokenType::LeftParen, "Expected '('."); + auto plugin = Expect(NVSETokenType::String, "Expected plugin name."); + Expect(NVSETokenType::Comma, "Expected ','."); + auto pluginName = std::get(plugin.value); + std::ranges::transform(pluginName.begin(), pluginName.end(), pluginName.begin(), [](unsigned char c) { return std::tolower(c); }); + if (StrEqual(pluginName.c_str(), "nvse")) { + auto major = static_cast(std::get(Expect(NVSETokenType::Number, "Expected major version").value)); + int minor = 255; + int beta = 255; + + if (Match(NVSETokenType::Comma)) { + minor = static_cast(std::get(Expect(NVSETokenType::Number, "Expected minor version").value)); + } + if (Match(NVSETokenType::Comma)) { + beta = static_cast(std::get(Expect(NVSETokenType::Number, "Expected beta version").value)); + } + pluginVersions[pluginName] = MAKE_NEW_VEGAS_VERSION(major, minor, beta); + } + else { // handle versions for plugins + auto pluginVersion = static_cast(std::get(Expect(NVSETokenType::Number, "Expected plugin version").value)); + auto* pluginInfo = g_pluginManager.GetInfoByName(pluginName.c_str()); + if (!pluginInfo) [[unlikely]] { + Error(std::format("No plugin with name {} could be found.\n", pluginName)); + } + pluginVersions[pluginName] = pluginVersion; + } + Expect(NVSETokenType::RightParen, "Expected ')'."); + } + // Get event or udf block + else if (PeekBlockType()) { + blocks.emplace_back(Begin()); + } + else if (Match(NVSETokenType::Fn)) { + auto fnToken = previousToken; + //auto ident = Expect(NVSETokenType::Identifier, "Expected identifier."); + auto args = ParseArgs(); + auto fnDecl = std::make_shared(fnToken, std::move(args), BlockStatement()); + fnDecl->is_udf_decl = true; + blocks.emplace_back(fnDecl); + } + else if (Peek(NVSETokenType::IntType) || Peek(NVSETokenType::DoubleType) || Peek(NVSETokenType::RefType) || + Peek(NVSETokenType::ArrayType) || Peek(NVSETokenType::StringType)) + { + globals.emplace_back(VarDecl()); + Expect(NVSETokenType::Semicolon, "Expected ';' at end of statement."); + } + else { + Error(currentToken, "Expected variable declaration, block type (GameMode, MenuMode, ...), or 'fn'."); + } + } + catch (NVSEParseError& e) { + hadError = true; + CompErr("%s\n", e.what()); + Synchronize(); + } + } + + return AST(name, std::move(globals), std::move(blocks)); + } + catch (NVSEParseError& e) { + hadError = true; + CompErr("%s\n", e.what()); + return std::optional{}; + } + } + + StmtPtr Parser::Begin() { + ExpectBlockType("Expected block type"); + + auto blockName = previousToken; + auto blockNameStr = blockName.lexeme; + + std::optional mode{}; + CommandInfo* beginInfo = nullptr; + for (auto& info : g_eventBlockCommandInfos) { + if (!_stricmp(info.longName, blockNameStr.c_str())) { + beginInfo = &info; + break; + } + } + + if (Match(NVSETokenType::MakePair)) { + if (beginInfo->numParams == 0) { + Error(currentToken, "Cannot specify argument for this block type."); + } + + if (Match(NVSETokenType::Number)) { + if (beginInfo->params[0].typeID != kParamType_Integer) { + Error(currentToken, "Expected identifier."); + } + + auto modeToken = previousToken; + if (modeToken.lexeme.contains('.')) { + Error(currentToken, "Expected integer."); + } + + mode = modeToken; + } + else if (Match(NVSETokenType::Identifier)) { + if (beginInfo->params[0].typeID == kParamType_Integer) { + Error(currentToken, "Expected number."); + } + + mode = previousToken; + } + else { + Error(currentToken, "Expected identifier or number."); + } + } + else { + if (beginInfo->numParams > 0 && !beginInfo->params[0].isOptional) { + Error(currentToken, "Missing required parameter for block type '" + blockName.lexeme + "'"); + } + } + + if (!Peek(NVSETokenType::LeftBrace)) { + Error(currentToken, "Expected '{'."); + } + return std::make_shared(blockName, mode, BlockStatement(), beginInfo); + } + + StmtPtr Parser::Statement() { + if ( + Peek(NVSETokenType::Identifier) && + !_stricmp(currentToken.lexeme.c_str(), "ShowMessage") + ) { + const auto ident = std::make_shared(currentToken); + + Advance(); + Expect(NVSETokenType::LeftParen, "Expected '('"); + + Expect(NVSETokenType::Identifier, "Expected message form"); + const auto messageIdent = std::make_shared(previousToken); + + std::vector> messageArgs{}; + + uint32_t messageTime = 0; + while (Match(NVSETokenType::Comma)) { + if (Peek(NVSETokenType::Number)) { + const auto numericLiteral = NumericLiteral(); + if (numericLiteral->is_fp) { + Error(previousToken, "Duration must be an integer."); + } + + messageTime = static_cast(numericLiteral->value); + break; + } + + const auto identToken = Expect(NVSETokenType::Identifier, "Expected identifier."); + messageArgs.emplace_back(std::make_shared(identToken)); + } + + Expect(NVSETokenType::RightParen, "Expected ')'"); + Expect(NVSETokenType::Semicolon, "Expected ';'"); + + return std::make_shared(messageIdent, messageArgs, messageTime); + } + + if (Peek(NVSETokenType::For)) { + return ForStatement(); + } + + if (Peek(NVSETokenType::If)) { + return IfStatement(); + } + + if (Peek(NVSETokenType::Match)) { + return MatchStatement(); + } + + if (Peek(NVSETokenType::Return)) { + return ReturnStatement(); + } + + if (Match(NVSETokenType::Break)) { + auto token = previousToken; + Expect(NVSETokenType::Semicolon, "Expected ';' at end of statement."); + return std::make_shared(); + } + + if (Match(NVSETokenType::Continue)) { + auto token = previousToken; + Expect(NVSETokenType::Semicolon, "Expected ';' at end of statement."); + return std::make_shared(); + } + + if (Peek(NVSETokenType::While)) { + return WhileStatement(); + } + + if (Peek(NVSETokenType::LeftBrace)) { + return BlockStatement(); + } + + if (PeekType()) { + auto expr = VarDecl(); + Expect(NVSETokenType::Semicolon, "Expected ';' at end of statement."); + return expr; + } + + return ExpressionStatement(); + } + + std::shared_ptr Parser::VarDecl(bool allowValue, bool allowOnlyOneVarDecl) { + if (!MatchesType()) { + Error(currentToken, "Expected type."); + } + auto varType = previousToken; + std::vector declarations{}; + do { + auto name = Expect(NVSETokenType::Identifier, "Expected identifier."); + ExprPtr value{ nullptr }; + + if (Match(NVSETokenType::Eq)) { + if (!allowValue) { + Error(previousToken, "Variable definition is not allowed here."); + } + value = Expression(); + } + + declarations.emplace_back(name, value); + if (allowOnlyOneVarDecl) { + break; + } + } while (Match(NVSETokenType::Comma)); + + return std::make_shared(varType, std::move(declarations)); + } + + StmtPtr Parser::ForStatement() { + Match(NVSETokenType::For); + Expect(NVSETokenType::LeftParen, "Expected '(' after 'for'."); + + // Optional expression + bool forEach = false; + StmtPtr init = { nullptr }; + if (!Peek(NVSETokenType::Semicolon)) { + // for (array aIter in [1::1, 2::2]).. + if (MatchesType()) { + auto type = previousToken; + auto ident = Expect(NVSETokenType::Identifier, "Expected identifier."); + + if (Match(NVSETokenType::In)) { + auto decl = std::make_shared(type, ident, nullptr); + + ExprPtr rhs = Expression(); + Expect(NVSETokenType::RightParen, "Expected ')'."); + + std::shared_ptr block = BlockStatement(); + return std::make_shared(std::vector{ decl }, std::move(rhs), std::move(block), false); + } + + ExprPtr value{ nullptr }; + if (Match(NVSETokenType::Eq)) { + value = Expression(); + } + Expect(NVSETokenType::Semicolon, "Expected ';' after loop initializer."); + init = std::make_shared(type, ident, value); + } + // for ([int key, int value] in [1::1, 2::2]). + // for ([_, int value] in [1::1, 2::2]). + // for ([int key, _] in [1::1, 2::2]). + // for ([int key] in [1::1, 2::2]). + // for ([int value] in [1, 2]). + else if (Match(NVSETokenType::LeftBracket)) { + std::vector> decls{}; + + // LHS + if (Match(NVSETokenType::Underscore)) { + decls.push_back(nullptr); + } + else { + decls.push_back(VarDecl(false, true)); + } + + // RHS + if (Match(NVSETokenType::Comma)) { + if (Match(NVSETokenType::Underscore)) { + decls.push_back(nullptr); + } + else { + decls.push_back(VarDecl(false, true)); + } + } + + Expect(NVSETokenType::RightBracket, "Expected ']'."); + Expect(NVSETokenType::In, "Expected 'in'."); + + ExprPtr rhs = Expression(); + Expect(NVSETokenType::RightParen, "Expected ')'."); + + std::shared_ptr block = BlockStatement(); + return std::make_shared(decls, std::move(rhs), std::move(block), true); + } + else { + init = Statement(); + if (!init->IsType()) { + Error(currentToken, "Expected variable declaration or assignment."); + } + } + } + + // Default to true condition + ExprPtr cond = std::make_shared(NVSEToken{ NVSETokenType::Bool, "true", 1 }, true); + if (!Peek(NVSETokenType::Semicolon)) { + cond = Expression(); + Expect(NVSETokenType::Semicolon, "Expected ';' after loop condition."); + } + + ExprPtr incr = { nullptr }; + if (!Peek(NVSETokenType::RightParen)) { + incr = Expression(); + } + Expect(NVSETokenType::RightParen, "Expected ')'."); + + std::shared_ptr block = BlockStatement(); + return std::make_shared(std::move(init), std::move(cond), std::move(incr), std::move(block)); + } + + StmtPtr Parser::IfStatement() { + Match(NVSETokenType::If); + auto token = previousToken; + + Expect(NVSETokenType::LeftParen, "Expected '(' after 'if'."); + auto cond = Expression(); + Expect(NVSETokenType::RightParen, "Expected ')' after 'if' condition."); + auto block = BlockStatement(); + std::shared_ptr elseBlock = nullptr; + if (Match(NVSETokenType::Else)) { + if (Peek(NVSETokenType::If)) { + std::vector statements{}; + statements.emplace_back(IfStatement()); + elseBlock = std::make_shared(std::move(statements)); + } + else { + elseBlock = BlockStatement(); + } + } + + return std::make_shared(std::move(cond), std::move(block), std::move(elseBlock)); + } + + StmtPtr Parser::MatchStatement() { + Match(NVSETokenType::Match); + + Expect(NVSETokenType::LeftParen, "Expected '(' after 'match'."); + auto cond = Expression(); + Expect(NVSETokenType::RightParen, "Expected ')' after 'match' expression."); + + Expect(NVSETokenType::LeftBrace, "Expected '{' after 'match' declaration"); + + std::vector> matchCases{}; + std::shared_ptr defaultCase{}; + + do { + if (Match(NVSETokenType::Underscore)) { + if (defaultCase) { + Error(currentToken, "Only one default case can be specified."); + } + + Expect(NVSETokenType::Arrow, "Expected '->' after match case value"); + defaultCase = BlockStatement(); + continue; + } + + auto caseVal = Expression(); + auto eqExpr = std::make_shared(NVSEToken{ NVSETokenType::EqEq, "==" }, cond, caseVal); + Expect(NVSETokenType::Arrow, "Expected '->' after match case value"); + + const auto curIfStmt = std::make_shared(std::move(eqExpr), BlockStatement()); + + matchCases.push_back(curIfStmt); + + const auto caseCount = matchCases.size(); + if (caseCount > 1) { + matchCases[caseCount - 1]->elseBlock = std::make_shared(std::vector{ curIfStmt }); + } + } while (!Peek(NVSETokenType::RightBrace)); + + if (defaultCase) { + matchCases[matchCases.size() - 1]->elseBlock = defaultCase; + } + + Expect(NVSETokenType::RightBrace, "Expected '}'"); + + return matchCases[0]; + } + + StmtPtr Parser::ReturnStatement() { + Match(NVSETokenType::Return); + auto token = previousToken; + if (Match(NVSETokenType::Semicolon)) { + return std::make_shared(); + } + + auto expr = std::make_shared(Expression()); + Expect(NVSETokenType::Semicolon, "Expected ';' at end of statement."); + return expr; + } + + StmtPtr Parser::WhileStatement() { + Match(NVSETokenType::While); + auto token = previousToken; + + Expect(NVSETokenType::LeftParen, "Expected '(' after 'while'."); + auto cond = Expression(); + Expect(NVSETokenType::RightParen, "Expected ')' after 'while' condition."); + auto block = BlockStatement(); + + return std::make_shared(std::move(cond), std::move(block)); + } + + std::shared_ptr Parser::BlockStatement() { + Expect(NVSETokenType::LeftBrace, "Expected '{'."); + + std::vector statements{}; + while (!Match(NVSETokenType::RightBrace)) { + try { + statements.emplace_back(Statement()); + } + catch (NVSEParseError e) { + hadError = true; + CompErr("%s\n", e.what()); + Synchronize(); + + if (currentToken.type == NVSETokenType::Eof) { + break; + } + } + } + + return std::make_shared(std::move(statements)); + } + + StmtPtr Parser::ExpressionStatement() { + // Allow empty expression statements + if (Match(NVSETokenType::Semicolon)) { + return std::make_shared(nullptr); + } + + auto expr = Expression(); + if (!Match(NVSETokenType::Semicolon)) { + Error(previousToken, "Expected ';' at end of statement."); + } + return std::make_shared(std::move(expr)); + } + + ExprPtr Parser::Expression() { + return Assignment(); + } + + ExprPtr Parser::Assignment() { + ExprPtr left = Slice(); + + if (Match(NVSETokenType::Eq) || Match(NVSETokenType::PlusEq) || Match(NVSETokenType::MinusEq) || + Match(NVSETokenType::StarEq) || Match(NVSETokenType::SlashEq) || Match(NVSETokenType::ModEq) || Match( + NVSETokenType::PowEq) || Match(NVSETokenType::BitwiseOrEquals) || Match(NVSETokenType::BitwiseAndEquals)) { + auto token = previousToken; + + const auto prevTok = previousToken; + ExprPtr value = Assignment(); + + if (left->IsType() || left->IsType() || left->IsType()) { + return std::make_shared(token, std::move(left), std::move(value)); + } + + Error(prevTok, "Invalid assignment target."); + } + + return left; + } + + ExprPtr Parser::Slice() { + ExprPtr left = Ternary(); + + while (Match(NVSETokenType::Slice)) { + const auto op = previousToken; + ExprPtr right = Ternary(); + left = std::make_shared(op, std::move(left), std::move(right)); + } + + return left; + } + + ExprPtr Parser::Ternary() { + ExprPtr cond = LogicalOr(); + + while (Match(NVSETokenType::Ternary)) { + auto token = previousToken; + + ExprPtr left; + if (Match(NVSETokenType::Slice)) { + left = cond; + } + else { + left = LogicalOr(); + Expect(NVSETokenType::Slice, "Expected ':'."); + } + + auto right = LogicalOr(); + cond = std::make_shared(token, std::move(cond), std::move(left), std::move(right)); + } + + return cond; + } + + ExprPtr Parser::LogicalOr() { + ExprPtr left = LogicalAnd(); + + while (Match(NVSETokenType::LogicOr)) { + auto op = previousToken; + ExprPtr right = LogicalAnd(); + left = std::make_shared(op, std::move(left), std::move(right)); + } + + return left; + } + + ExprPtr Parser::LogicalAnd() { + ExprPtr left = Equality(); + + while (Match(NVSETokenType::LogicAnd)) { + const auto op = previousToken; + ExprPtr right = Equality(); + left = std::make_shared(op, std::move(left), std::move(right)); + } + + return left; + } + + ExprPtr Parser::Equality() { + ExprPtr left = Comparison(); + + while (Match(NVSETokenType::EqEq) || Match(NVSETokenType::BangEq)) { + const auto op = previousToken; + ExprPtr right = Comparison(); + left = std::make_shared(op, std::move(left), std::move(right)); + } + + return left; + } + + ExprPtr Parser::Comparison() { + ExprPtr left = In(); + + while (Match(NVSETokenType::Less) || Match(NVSETokenType::LessEq) || Match(NVSETokenType::Greater) || Match( + NVSETokenType::GreaterEq)) { + const auto op = previousToken; + ExprPtr right = In(); + left = std::make_shared(op, std::move(left), std::move(right)); + } + + return left; + } + + ExprPtr Parser::In() { + ExprPtr left = BitwiseOr(); + + bool isNot = Match(NVSETokenType::Not); + if (Match(NVSETokenType::In)) { + const auto op = previousToken; + + if (Match(NVSETokenType::LeftBracket)) { + std::vector values{}; + while (!Match(NVSETokenType::RightBracket)) { + if (!values.empty()) { + Expect(NVSETokenType::Comma, "Expected ','."); + } + values.emplace_back(Expression()); + } + + return std::make_shared(left, op, values, isNot); + } + + auto expr = Expression(); + return std::make_shared(left, op, expr, isNot); + } + + if (isNot) { + Error(currentToken, "Expected 'in'."); + } + + return left; + } + + ExprPtr Parser::BitwiseOr() { + ExprPtr left = BitwiseAnd(); + + while (Match(NVSETokenType::BitwiseOr)) { + const auto op = previousToken; + ExprPtr right = BitwiseAnd(); + left = std::make_shared(op, std::move(left), std::move(right)); + } + + return left; + } + + ExprPtr Parser::BitwiseAnd() { + ExprPtr left = Shift(); + + while (Match(NVSETokenType::BitwiseAnd)) { + const auto op = previousToken; + ExprPtr right = Shift(); + left = std::make_shared(op, std::move(left), std::move(right)); + } + + return left; + } + + ExprPtr Parser::Shift() { + ExprPtr left = Term(); + + while (Match(NVSETokenType::LeftShift) || Match(NVSETokenType::RightShift)) { + const auto op = previousToken; + ExprPtr right = Term(); + left = std::make_shared(op, std::move(left), std::move(right)); + } + + return left; + } + + // term -> factor ((+ | -) factor)?; + ExprPtr Parser::Term() { + ExprPtr left = Factor(); + + while (Match(NVSETokenType::Plus) || Match(NVSETokenType::Minus)) { + const auto op = previousToken; + ExprPtr right = Factor(); + left = std::make_shared(op, std::move(left), std::move(right)); + } + + return left; + } + + ExprPtr Parser::Factor() { + ExprPtr left = Pair(); + + while (Match(NVSETokenType::Star) || Match(NVSETokenType::Slash) || Match(NVSETokenType::Mod) || Match( + NVSETokenType::Pow)) { + const auto op = previousToken; + ExprPtr right = Pair(); + left = std::make_shared(op, std::move(left), std::move(right)); + } + + return left; + } + + ExprPtr Parser::Pair() { + ExprPtr left = Unary(); + + while (Match(NVSETokenType::MakePair)) { + auto op = previousToken; + ExprPtr right = Unary(); + left = std::make_shared(op, std::move(left), std::move(right)); + } + + return left; + } + + ExprPtr Parser::Unary() { + if (Match(NVSETokenType::Bang) || Match(NVSETokenType::Minus) || Match(NVSETokenType::Dollar) || Match( + NVSETokenType::Pound) || Match(NVSETokenType::BitwiseAnd) || Match(NVSETokenType::Star) || Match(NVSETokenType::BitwiseNot)) { + auto op = previousToken; + ExprPtr right = Unary(); + + // Convert these two in case of box/unbox + if (op.type == NVSETokenType::BitwiseAnd) { + op.type = NVSETokenType::Box; + } + + if (op.type == NVSETokenType::Star) { + op.type = NVSETokenType::Unbox; + } + + if (op.type == NVSETokenType::Minus) { + op.type = NVSETokenType::Negate; + } + + return std::make_shared(op, std::move(right), false); + } + + return Postfix(); + } + + ExprPtr Parser::Postfix() { + ExprPtr expr = Call(); + + while (Match(NVSETokenType::LeftBracket)) { + auto token = previousToken; + auto inner = Expression(); + Expect(NVSETokenType::RightBracket, "Expected ']'."); + + expr = std::make_shared(token, std::move(expr), std::move(inner)); + } + + if (Match(NVSETokenType::PlusPlus) || Match(NVSETokenType::MinusMinus)) { + if (!expr->IsType() && !expr->IsType()) { + Error(previousToken, "Invalid operand for '++', expected identifier."); + } + + auto op = previousToken; + return std::make_shared(op, std::move(expr), true); + } + + return expr; + } + + ExprPtr Parser::Call() { + ExprPtr expr = Primary(); + + while (Match(NVSETokenType::Dot) || Match(NVSETokenType::LeftParen)) { + if (previousToken.type == NVSETokenType::Dot) { + if (!expr->IsType() && + !expr->IsType() && + !expr->IsType() && + !expr->IsType()) { + Error(currentToken, "Invalid member access."); + } + + auto token = previousToken; + const auto ident = Expect(NVSETokenType::Identifier, "Expected identifier."); + + if (Match(NVSETokenType::LeftParen)) { + std::vector args{}; + while (!Match(NVSETokenType::RightParen)) { + args.emplace_back(std::move(Expression())); + + if (!Peek(NVSETokenType::RightParen) && !Match(NVSETokenType::Comma)) { + Error(currentToken, "Expected ',' or ')'."); + } + } + expr = std::make_shared(std::move(expr), ident, std::move(args)); + } + else { + expr = std::make_shared(token, std::move(expr), ident); + } + } + else { + // Can only call on ident or get expr + if (!expr->IsType()) { + Error(currentToken, "Invalid callee."); + } + + auto ident = dynamic_cast(expr.get())->token; + + std::vector args{}; + while (!Match(NVSETokenType::RightParen)) { + args.emplace_back(std::move(Expression())); + + if (!Peek(NVSETokenType::RightParen) && !Match(NVSETokenType::Comma)) { + Error(currentToken, "Expected ',' or ')'."); + } + } + expr = std::make_shared(nullptr, ident, std::move(args)); + } + } + + return expr; + } + + ExprPtr Parser::Primary() { + if (Match(NVSETokenType::Bool)) { + return std::make_shared( + NVSEToken{ previousToken }, + std::get(previousToken.value) + ); + } + + if (Peek(NVSETokenType::Number)) { + return NumericLiteral(); + } + + if (Match(NVSETokenType::String)) { + ExprPtr expr = std::make_shared(previousToken); + while (Match(NVSETokenType::Interp)) { + auto inner = std::make_shared(NVSEToken{ NVSETokenType::Dollar, "$" }, Expression(), false); + Expect(NVSETokenType::EndInterp, "Expected '}'"); + expr = std::make_shared(NVSEToken{ NVSETokenType::Plus, "+" }, expr, inner); + if (Match(NVSETokenType::String) && previousToken.lexeme.length() > 2) { + auto endStr = std::make_shared(previousToken); + expr = std::make_shared(NVSEToken{ NVSETokenType::Plus, "+" }, expr, endStr); + } + } + return expr; + } + + if (Match(NVSETokenType::Identifier)) { + return std::make_shared(previousToken); + } + + if (Match(NVSETokenType::LeftParen)) { + ExprPtr expr = Expression(); + Expect(NVSETokenType::RightParen, "Expected ')' after expression."); + return std::make_shared(std::move(expr)); + } + + if (Peek(NVSETokenType::LeftBracket)) { + return ArrayLiteral(); + } + + if (Peek(NVSETokenType::LeftBrace)) { + return MapLiteral(); + } + + if (Match(NVSETokenType::Fn)) { + return FnExpr(); + } + + Error(currentToken, "Expected expression."); + return ExprPtr{ nullptr }; + } + + std::shared_ptr Parser::NumericLiteral() { + Match(NVSETokenType::Number); + + // Double literal + if (std::ranges::find(previousToken.lexeme, '.') != previousToken.lexeme.end()) { + return std::make_shared(previousToken, std::get(previousToken.value), true); + } + + // Int literal + return std::make_shared(previousToken, floor(std::get(previousToken.value)), false); + } + + // Only called when 'fn' token is matched + ExprPtr Parser::FnExpr() { + auto token = previousToken; + auto args = ParseArgs(); + + if (Match(NVSETokenType::Arrow)) { + // Build call stmt + auto callExpr = std::make_shared(nullptr, NVSEToken{ NVSETokenType::Identifier, "SetFunctionValue" }, + std::vector{ Expression() }); + StmtPtr exprStmt = std::make_shared(callExpr); + auto block = std::make_shared(std::vector{ exprStmt }); + return std::make_shared(std::move(args), block); + } + + return std::make_shared(std::move(args), BlockStatement()); + } + + ExprPtr Parser::ArrayLiteral() { + Expect(NVSETokenType::LeftBracket, "Expected '['."); + const auto tok = previousToken; + + std::vector values{}; + while (!Match(NVSETokenType::RightBracket)) { + if (!values.empty()) { + Expect(NVSETokenType::Comma, "Expected ','."); + } + values.emplace_back(Expression()); + } + + return std::make_shared(tok, values); + } + + ExprPtr Parser::MapLiteral() { + Expect(NVSETokenType::LeftBrace, "Expected '{'."); + auto tok = previousToken; + + std::vector values{}; + while (!Match(NVSETokenType::RightBrace)) { + if (!values.empty()) { + Expect(NVSETokenType::Comma, "Expected ','."); + } + values.emplace_back(Expression()); + } + + return std::make_shared(std::move(tok), std::move(values)); + } + + std::vector> Parser::ParseArgs() { + Expect(NVSETokenType::LeftParen, "Expected '(' after 'fn'."); + + std::vector> args{}; + while (!Match(NVSETokenType::RightParen)) { + if (!Match(NVSETokenType::IntType) && !Match(NVSETokenType::DoubleType) && !Match(NVSETokenType::RefType) && + !Match(NVSETokenType::ArrayType) && !Match(NVSETokenType::StringType)) { + Error(currentToken, "Expected type."); + } + auto type = previousToken; + auto ident = Expect(NVSETokenType::Identifier, "Expected identifier."); + auto decl = std::make_shared(type, ident, nullptr); + + if (!Peek(NVSETokenType::RightParen) && !Match(NVSETokenType::Comma)) { + Error(currentToken, "Expected ',' or ')'."); + } + + args.emplace_back(std::move(decl)); + } + + return args; + } + + void Parser::Advance() { + previousToken = std::move(currentToken); + + // Bubble up lexer errors + try { + currentToken = lexer.GetNextToken(true); + } + catch (std::runtime_error& er) { + CompErr("[line %d] %s\n", lexer.line, er.what()); + } + } + + bool Parser::Match(NVSETokenType type) { + if (currentToken.type == type) { + Advance(); + return true; + } + + return false; + } + + bool Parser::MatchesType() { + return Match(NVSETokenType::IntType) || Match(NVSETokenType::DoubleType) || Match(NVSETokenType::RefType) || + Match(NVSETokenType::ArrayType) || Match(NVSETokenType::StringType); + } + + bool Parser::Peek(NVSETokenType type) const { + if (currentToken.type == type) { + return true; + } + + return false; + } + + bool Parser::PeekType() const { + return Peek(NVSETokenType::IntType) || Peek(NVSETokenType::DoubleType) || Peek(NVSETokenType::RefType) || + Peek(NVSETokenType::ArrayType) || Peek(NVSETokenType::StringType); + } + + bool Parser::PeekBlockType() const { + if (!Peek(NVSETokenType::Identifier)) { + return false; + } + + const auto identifier = currentToken.lexeme; + return std::ranges::any_of(g_eventBlockCommandInfos, [&](const CommandInfo& ci) { + return !_stricmp(ci.longName, identifier.c_str()); + }); + } + + NVSEToken Parser::ExpectBlockType(const std::string&& message) { + if (!PeekBlockType()) { + Error(currentToken, message); + return previousToken; + } + + const auto identifier = currentToken.lexeme; + for (const auto& g_eventBlockCommandInfo : g_eventBlockCommandInfos) { + if (!_stricmp(g_eventBlockCommandInfo.longName, identifier.c_str())) { + Advance(); + return previousToken; + } + } + + Error(currentToken, message); + return previousToken; + } + + void Parser::Error(std::string message) { + panicMode = true; + hadError = true; + + throw NVSEParseError(std::format("Error: {}", message)); + } + + void Parser::Error(NVSEToken token, std::string message) { + panicMode = true; + hadError = true; + + throw NVSEParseError(std::format("[line {}:{}] {}", token.line, token.column, message)); + } + + NVSEToken Parser::Expect(NVSETokenType type, std::string message) { + if (!Match(type)) { + Error(currentToken, std::move(message)); + } + + return previousToken; + } + + void Parser::Synchronize() { + Advance(); + + while (currentToken.type != NVSETokenType::Eof) { + if (previousToken.type == NVSETokenType::Semicolon) { + panicMode = false; + return; + } + + switch (currentToken.type) { + case NVSETokenType::If: + case NVSETokenType::While: + case NVSETokenType::For: + case NVSETokenType::Return: + case NVSETokenType::RightBrace: + panicMode = false; + return; + default:; + } + + Advance(); + } + } +} diff --git a/nvse/nvse/Compiler/Parser/Parser.h b/nvse/nvse/Compiler/Parser/Parser.h new file mode 100644 index 00000000..dd202300 --- /dev/null +++ b/nvse/nvse/Compiler/Parser/Parser.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + +#include +#include + +namespace Compiler { + class NVSEParseError : public std::runtime_error { + public: + NVSEParseError(const std::string& message) : std::runtime_error(message) {}; + }; + + class Parser { + public: + Parser(Lexer& tokenizer); + std::optional Parse(); + + private: + Lexer& lexer; + NVSEToken currentToken; + NVSEToken previousToken; + bool panicMode = false; + bool hadError = false; + + std::shared_ptr FnDecl(); + std::shared_ptr VarDecl(bool allowValue = true, bool allowOnlyOneVarDecl = false); + + StmtPtr Begin(); + StmtPtr Statement(); + StmtPtr ExpressionStatement(); + StmtPtr ForStatement(); + StmtPtr IfStatement(); + StmtPtr MatchStatement(); + StmtPtr ReturnStatement(); + StmtPtr WhileStatement(); + std::shared_ptr BlockStatement(); + + ExprPtr Expression(); + ExprPtr Assignment(); + ExprPtr Ternary(); + ExprPtr Pair(); + ExprPtr LogicalOr(); + ExprPtr LogicalAnd(); + ExprPtr Slice(); + ExprPtr Equality(); + ExprPtr Comparison(); + ExprPtr BitwiseOr(); + ExprPtr BitwiseAnd(); + ExprPtr Shift(); + ExprPtr In(); + ExprPtr Term(); + ExprPtr Factor(); + ExprPtr Unary(); + ExprPtr Postfix(); + ExprPtr Call(); + ExprPtr FnExpr(); + ExprPtr ArrayLiteral(); + ExprPtr MapLiteral(); + std::vector> ParseArgs(); + ExprPtr Primary(); + std::shared_ptr NumericLiteral(); + + void Advance(); + bool Match(NVSETokenType type); + bool MatchesType(); + bool Peek(NVSETokenType type) const; + bool PeekType() const; + + bool PeekBlockType() const; + NVSEToken ExpectBlockType(const std::string&& message); + + void Error(std::string message); + void Error(NVSEToken token, std::string message); + NVSEToken Expect(NVSETokenType type, std::string message); + void Synchronize(); + }; + +} diff --git a/nvse/nvse/Compiler/Passes/VariableResolution.cpp b/nvse/nvse/Compiler/Passes/VariableResolution.cpp new file mode 100644 index 00000000..c5eeb5b3 --- /dev/null +++ b/nvse/nvse/Compiler/Passes/VariableResolution.cpp @@ -0,0 +1,189 @@ +#include "VariableResolution.h" + +#include "nvse/Compiler/AST/AST.h" +#include "nvse/Compiler/NVSECompilerUtils.h" + +namespace Compiler::Passes { + std::shared_ptr VariableResolution::EnterScope() { + scopes.emplace(std::make_shared(false, currentScope.get())); + currentScope = scopes.top(); + return currentScope; + } + + std::shared_ptr VariableResolution::LeaveScope() { + scopes.pop(); + currentScope = scopes.top(); + return currentScope; + } + + void VariableResolution::VisitVarDeclStmt(Statements::VarDecl* stmt) { + for (auto & [token, expr, info] : stmt->declarations) { + const auto varType = GetScriptTypeFromTokenType(stmt->type.type); + if (const auto var = currentScope->AddVariable(token.lexeme, varType, globalVars, tempVars)) { + info = var; + + if (expr) { + expr->Accept(this); + } + } + } + } + + void VariableResolution::Visit(AST* script) { + // Need to associate / start indexing after existing non-temp script vars + if (pScript && pScript->varList.Count() > 0) { + for (const auto var : pScript->varList) { + if (strncmp(var->name.CStr(), "__temp", strlen("__temp")) != 0) { + const std::string varName{ var->name.CStr() }; + auto added = currentScope->AddVariable(varName, static_cast(var->type), globalVars, tempVars); + added->pre_existing = true; + } + } + } + + for (const auto& global : script->globalVars) { + global->Accept(this); + } + + for (const auto& block : script->blocks) { + if (const auto fnDecl = dynamic_cast(block.get())) { + for (const auto& arg : fnDecl->args) { + arg->Accept(this); + } + + EnterScope(); + fnDecl->body->Accept(this); + LeaveScope(); + } else { + block->Accept(this); + } + } + + for (const auto& var : globalVars) { + CompDbg("Created variable [Original Name: %s, New Name: %s, Index: %ul]\n", var->original_name.c_str(), var->remapped_name.c_str(), var->index); + } + + // Assign temp var indices + auto idx = globalVars.size() + 1; + for (const auto &var : tempVars) { + var->index = idx++; + + CompDbg("Created variable [Original Name: %s, New Name: %s, Index: %ul]\n", var->original_name.c_str(), var->remapped_name.c_str(), var->index); + } + + // Create engine var list + if (pScript) { + pScript->varList.DeleteAll(); + pScript->refList.DeleteAll(); + + for (const auto &var : globalVars) { + const auto pVarInfo = New(); + pVarInfo->type = var->type; + pVarInfo->name = String(); + pVarInfo->name.Set(var->remapped_name.c_str()); + pVarInfo->idx = var->index; + pScript->varList.Append(pVarInfo); + + // Rebuilding ref list + if (var->type == Script::eVarType_Ref) { + const auto ref = New(); + ref->name = String(); + ref->name.Set(var->remapped_name.c_str()); + ref->varIdx = var->index; + pScript->refList.Append(ref); + } + } + + for (const auto& var : tempVars) { + const auto pVarInfo = New(); + pVarInfo->type = var->type; + pVarInfo->name = String(); + pVarInfo->name.Set(var->remapped_name.c_str()); + pVarInfo->idx = var->index; + pScript->varList.Append(pVarInfo); + + // Rebuilding ref list + if (var->type == Script::eVarType_Ref) { + const auto ref = New(); + ref->name = String(); + ref->name.Set(var->remapped_name.c_str()); + ref->varIdx = var->index; + pScript->refList.Append(ref); + } + } + + pScript->info.varCount = pScript->varList.Count(); + pScript->info.numRefs = pScript->refList.Count(); + } + } + + void VariableResolution::VisitBeginStmt(Statements::Begin* stmt) { + EnterScope(); + stmt->block->Accept(this); + LeaveScope(); + } + + void VariableResolution::VisitFnStmt(Statements::UDFDecl* stmt) { + EnterScope(); + + for (const auto &arg : stmt->args) { + arg->Accept(this); + } + stmt->body->Accept(this); + + LeaveScope(); + } + + void VariableResolution::VisitForStmt(Statements::For* stmt) { + EnterScope(); + Visitor::VisitForStmt(stmt); + LeaveScope(); + } + + void VariableResolution::VisitForEachStmt(Statements::ForEach* stmt) { + EnterScope(); + Visitor::VisitForEachStmt(stmt); + LeaveScope(); + } + + void VariableResolution::VisitIfStmt(Statements::If* stmt) { + EnterScope(); + stmt->cond->Accept(this); + stmt->block->Accept(this); + LeaveScope(); + + if (stmt->elseBlock) { + EnterScope(); + stmt->elseBlock->Accept(this); + LeaveScope(); + } + } + + void VariableResolution::VisitWhileStmt(Statements::While* stmt) { + EnterScope(); + stmt->cond->Accept(this); + stmt->block->Accept(this); + LeaveScope(); + } + + void VariableResolution::VisitBlockStmt(Statements::Block* stmt) { + EnterScope(); + for (const auto &line : stmt->statements) { + line->Accept(this); + } + LeaveScope(); + } + + void VariableResolution::VisitIdentExpr(Expressions::IdentExpr* expr) { + expr->varInfo = currentScope->Lookup(expr->token.lexeme); + } + + void VariableResolution::VisitLambdaExpr(Expressions::LambdaExpr* expr) { + EnterScope(); + for (const auto &arg : expr->args) { + arg->Accept(this); + } + expr->body->Accept(this); + LeaveScope(); + } +} diff --git a/nvse/nvse/Compiler/Passes/VariableResolution.h b/nvse/nvse/Compiler/Passes/VariableResolution.h new file mode 100644 index 00000000..fe2ded52 --- /dev/null +++ b/nvse/nvse/Compiler/Passes/VariableResolution.h @@ -0,0 +1,104 @@ +#pragma once +#include + +#include +#include +#include +#include +#include + +#include "nvse/Compiler/NVSECompilerUtils.h" + +namespace Compiler::Passes { + struct Scope { + bool bIsRoot = false; + Scope* parent = nullptr; + std::map> variables{}; + + std::shared_ptr AddVariable( + const std::string& name, + const Script::VariableType type, + std::vector> &globalVars, + std::vector> &tempVars + ) { + if (variables.contains(name)) { + const auto& existing = variables[name]; + + if (existing->pre_existing) { + existing->type = type; + existing->pre_existing = false; + return existing; + } + + return nullptr; + } + + const auto var = std::make_shared(globalVars.size() + 1, name); + var->pre_existing = false; + var->type = type; + var->detailed_type = VariableType_To_TokenType(type); + + if (bIsRoot) { + var->remapped_name = var->original_name; + globalVars.push_back(var); + } + else { + var->remapped_name = "__temp_" + var->original_name + "_" + std::to_string(tempVars.size() + 1); + tempVars.push_back(var); + } + + variables[name] = var; + + return var; + } + + std::shared_ptr Lookup(const std::string& name) { + const auto existing = variables.find(name); + if (existing != variables.end()) { + return existing->second; + } + + if (parent) { + return parent->Lookup(name); + } + + return nullptr; + } + }; + + class VariableResolution : Visitor { + std::vector> globalVars{}; + std::vector> tempVars{}; + + Script* pScript = nullptr; + + std::stack> scopes{}; + std::shared_ptr currentScope{}; + std::shared_ptr EnterScope(); + std::shared_ptr LeaveScope(); + + public: + static bool Resolve(Script *pScript, AST* pAST) { + auto resolver = VariableResolution{ pScript }; + resolver.Visit(pAST); + return true; + } + + VariableResolution(Script* pScript) : pScript{ pScript } { + scopes.emplace(std::make_shared(true, nullptr)); + currentScope = scopes.top(); + } + + void Visit(AST* script) override; + void VisitBeginStmt(Statements::Begin* stmt) override; + void VisitFnStmt(Statements::UDFDecl* stmt) override; + void VisitVarDeclStmt(Statements::VarDecl* stmt) override; + void VisitForStmt(Statements::For* stmt) override; + void VisitForEachStmt(Statements::ForEach* stmt) override; + void VisitIfStmt(Statements::If* stmt) override; + void VisitWhileStmt(Statements::While* stmt) override; + void VisitBlockStmt(Statements::Block* stmt) override; + void VisitIdentExpr(Expressions::IdentExpr* expr) override; + void VisitLambdaExpr(Expressions::LambdaExpr* expr) override; + }; +} diff --git a/nvse/nvse/Compiler/Visitor.cpp b/nvse/nvse/Compiler/Visitor.cpp new file mode 100644 index 00000000..4221b9e0 --- /dev/null +++ b/nvse/nvse/Compiler/Visitor.cpp @@ -0,0 +1,189 @@ +#include "Visitor.h" +#include "Parser.h" +#include + +#include + +namespace Compiler { + + void Visitor::Visit(AST* script) { + for (const auto& global : script->globalVars) { + global->Accept(this); + } + + for (const auto& block : script->blocks) { + block->Accept(this); + } + } + + void Visitor::VisitBeginStmt(Statements::Begin* stmt) { + stmt->block->Accept(this); + } + + void Visitor::VisitFnStmt(Statements::UDFDecl* stmt) { + for (const auto& decl : stmt->args) { + decl->Accept(this); + } + + stmt->body->Accept(this); + } + + void Visitor::VisitVarDeclStmt(Statements::VarDecl* stmt) { + for (auto& [name, expr, _] : stmt->declarations) { + if (expr) { + expr->Accept(this); + } + } + } + + void Visitor::VisitExprStmt(const Statements::ExpressionStatement* stmt) { + if (stmt->expr) { + stmt->expr->Accept(this); + } + } + + void Visitor::VisitForStmt(Statements::For* stmt) { + if (stmt->init) { + stmt->init->Accept(this); + } + + if (stmt->cond) { + stmt->cond->Accept(this); + } + + stmt->post->Accept(this); + stmt->block->Accept(this); + } + + void Visitor::VisitForEachStmt(Statements::ForEach* stmt) { + for (const auto& decl : stmt->declarations) { + if (decl) { + decl->Accept(this); + } + } + stmt->rhs->Accept(this); + stmt->block->Accept(this); + } + + void Visitor::VisitIfStmt(Statements::If* stmt) { + stmt->cond->Accept(this); + stmt->block->Accept(this); + + if (stmt->elseBlock) { + stmt->elseBlock->Accept(this); + } + } + + void Visitor::VisitReturnStmt(Statements::Return* stmt) { + if (stmt->expr) { + stmt->expr->Accept(this); + } + } + + void Visitor::VisitContinueStmt(Statements::Continue* stmt) {} + + void Visitor::VisitBreakStmt(Statements::Break* stmt) {} + + void Visitor::VisitWhileStmt(Statements::While* stmt) { + stmt->cond->Accept(this); + stmt->block->Accept(this); + } + + void Visitor::VisitBlockStmt(Statements::Block* stmt) { + for (const auto& statement : stmt->statements) { + statement->Accept(this); + } + } + + void Visitor::VisitShowMessageStmt(Statements::ShowMessage* stmt) { + stmt->message->Accept(this); + for (const auto& statement : stmt->args) { + statement->Accept(this); + } + } + + void Visitor::VisitAssignmentExpr(Expressions::AssignmentExpr* expr) { + expr->left->Accept(this); + expr->expr->Accept(this); + } + + void Visitor::VisitTernaryExpr(Expressions::TernaryExpr* expr) { + expr->cond->Accept(this); + expr->left->Accept(this); + expr->right->Accept(this); + } + + void Visitor::VisitInExpr(Expressions::InExpr* expr) { + expr->lhs->Accept(this); + + if (!expr->values.empty()) { + for (const auto& val : expr->values) { + val->Accept(this); + } + } + + // Any other expression, compiles to ar_find + else { + expr->expression->Accept(this); + } + } + + void Visitor::VisitBinaryExpr(Expressions::BinaryExpr* expr) { + expr->left->Accept(this); + expr->right->Accept(this); + } + + void Visitor::VisitUnaryExpr(Expressions::UnaryExpr* expr) { + expr->expr->Accept(this); + } + + void Visitor::VisitSubscriptExpr(Expressions::SubscriptExpr* expr) { + expr->left->Accept(this); + expr->index->Accept(this); + } + + void Visitor::VisitCallExpr(Expressions::CallExpr* expr) { + if (expr->left) { + expr->left->Accept(this); + } + for (const auto& arg : expr->args) { + arg->Accept(this); + } + } + + void Visitor::VisitGetExpr(Expressions::GetExpr* expr) { + expr->left->Accept(this); + } + + void Visitor::VisitBoolExpr(Expressions::BoolExpr* expr) {} + + void Visitor::VisitNumberExpr(Expressions::NumberExpr* expr) {} + + void Visitor::VisitMapLiteralExpr(Expressions::MapLiteralExpr* expr) { + for (const auto& val : expr->values) { + val->Accept(this); + } + } + + void Visitor::VisitArrayLiteralExpr(Expressions::ArrayLiteralExpr* expr) { + for (const auto& val : expr->values) { + val->Accept(this); + } + } + + void Visitor::VisitStringExpr(Expressions::StringExpr* expr) {} + + void Visitor::VisitIdentExpr(Expressions::IdentExpr* expr) {} + + void Visitor::VisitGroupingExpr(Expressions::GroupingExpr* expr) { + expr->expr->Accept(this); + } + + void Visitor::VisitLambdaExpr(Expressions::LambdaExpr* expr) { + for (const auto& decl : expr->args) { + decl->Accept(this); + } + + expr->body->Accept(this); + } +} \ No newline at end of file diff --git a/nvse/nvse/Compiler/Visitor.h b/nvse/nvse/Compiler/Visitor.h new file mode 100644 index 00000000..4754a01e --- /dev/null +++ b/nvse/nvse/Compiler/Visitor.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +namespace Compiler { + class Visitor { + public: + virtual ~Visitor() = default; + + virtual void Visit(AST* script); + + virtual void VisitBeginStmt(Statements::Begin* stmt); + virtual void VisitFnStmt(Statements::UDFDecl* stmt); + virtual void VisitVarDeclStmt(Statements::VarDecl* stmt); + virtual void VisitExprStmt(const Statements::ExpressionStatement* stmt); + virtual void VisitForStmt(Statements::For* stmt); + virtual void VisitForEachStmt(Statements::ForEach* stmt); + virtual void VisitIfStmt(Statements::If* stmt); + virtual void VisitReturnStmt(Statements::Return* stmt); + virtual void VisitContinueStmt(Statements::Continue* stmt); + virtual void VisitBreakStmt(Statements::Break* stmt); + virtual void VisitWhileStmt(Statements::While* stmt); + virtual void VisitBlockStmt(Statements::Block* stmt); + virtual void VisitShowMessageStmt(Statements::ShowMessage* stmt); + + virtual void VisitAssignmentExpr(Expressions::AssignmentExpr* expr); + virtual void VisitTernaryExpr(Expressions::TernaryExpr* expr); + virtual void VisitInExpr(Expressions::InExpr* expr); + virtual void VisitBinaryExpr(Expressions::BinaryExpr* expr); + virtual void VisitUnaryExpr(Expressions::UnaryExpr* expr); + virtual void VisitSubscriptExpr(Expressions::SubscriptExpr* expr); + virtual void VisitCallExpr(Expressions::CallExpr* expr); + virtual void VisitGetExpr(Expressions::GetExpr* expr); + virtual void VisitBoolExpr(Expressions::BoolExpr* expr); + virtual void VisitNumberExpr(Expressions::NumberExpr* expr); + virtual void VisitStringExpr(Expressions::StringExpr* expr); + virtual void VisitIdentExpr(Expressions::IdentExpr* expr); + virtual void VisitArrayLiteralExpr(Expressions::ArrayLiteralExpr* expr); + virtual void VisitMapLiteralExpr(Expressions::MapLiteralExpr* expr); + virtual void VisitGroupingExpr(Expressions::GroupingExpr* expr); + virtual void VisitLambdaExpr(Expressions::LambdaExpr* expr); + }; +} diff --git a/nvse/nvse/Hooks_Script.cpp b/nvse/nvse/Hooks_Script.cpp index ec781f35..e37ca1c1 100644 --- a/nvse/nvse/Hooks_Script.cpp +++ b/nvse/nvse/Hooks_Script.cpp @@ -17,14 +17,15 @@ #include "GameAPI.h" #include "GameData.h" -#include "NVSECompiler.h" -#include "NVSECompilerUtils.h" -#include "NVSELexer.h" -#include "NVSEParser.h" -#include "NVSETreePrinter.h" -#include "NVSETypeChecker.h" +#include "Compiler/Compilation.h" +#include "Compiler/NVSECompilerUtils.h" +#include "Compiler/Lexer/Lexer.h" +#include "Compiler/Parser/Parser.h" +#include "Compiler/NVSETreePrinter.h" +#include "Compiler/NVSETypeChecker.h" #include "PluginManager.h" #include "ScriptDataCache.h" +#include "Compiler/Passes/VariableResolution.h" // a size of ~1KB should be enough for a single line of code char s_ExpressionParserAltBuffer[0x500] = {0}; @@ -533,21 +534,22 @@ PrecompileResult __stdcall HandleBeginCompile(ScriptBuffer* buf, Script* script) // See if new compiler should override script compiler // First token on first line should be 'name' if (!_strnicmp(buf->scriptText, "name", 4)) { - CompInfo("\n========================================\n\n"); + Compiler::CompInfo("\n========================================\n\n"); // Just convert script buffer to a string auto program = std::string(buf->scriptText); - NVSELexer lexer(program); - NVSEParser parser(lexer); + Compiler::Lexer lexer(program); + Compiler::Parser parser(lexer); if (auto astOpt = parser.Parse(); astOpt.has_value()) { auto ast = std::move(astOpt.value()); + Compiler::Passes::VariableResolution::Resolve(script, &ast); - auto tc = NVSETypeChecker(&ast, script); + auto tc = Compiler::NVSETypeChecker(&ast, script); bool typeCheckerPass = tc.check(); - auto tp = NVSETreePrinter(); + auto tp = Compiler::NVSETreePrinter(); ast.Accept(&tp); if (!typeCheckerPass) { @@ -555,7 +557,7 @@ PrecompileResult __stdcall HandleBeginCompile(ScriptBuffer* buf, Script* script) } try { - NVSECompiler comp{script, buf->partialScript, ast}; + Compiler::Compilation comp{script, buf->partialScript, ast}; comp.Compile(); // Only set script name if not partial @@ -566,7 +568,7 @@ PrecompileResult __stdcall HandleBeginCompile(ScriptBuffer* buf, Script* script) printf("Script compiled successfully.\n"); } catch (std::runtime_error &er) { - CompErr("Script compilation failed: %s\n", er.what()); + Compiler::CompErr("Script compilation failed: %s\n", er.what()); return PrecompileResult::kPrecompile_Failure; } } else { @@ -612,7 +614,7 @@ PrecompileResult __stdcall HandleBeginCompile(ScriptBuffer* buf, Script* script) else { // handle versions for plugins auto* pluginInfo = g_pluginManager.GetInfoByName(pluginName.c_str()); if (!pluginInfo) [[unlikely]] { - CompErr("Script compilation failed: No plugin with name %s could be found.\n", pluginName.c_str()); + Compiler::CompErr("Script compilation failed: No plugin with name %s could be found.\n", pluginName.c_str()); return PrecompileResult::kPrecompile_Failure; } diff --git a/nvse/nvse/NVSEAst.h b/nvse/nvse/NVSEAst.h deleted file mode 100644 index 4b266953..00000000 --- a/nvse/nvse/NVSEAst.h +++ /dev/null @@ -1,481 +0,0 @@ -#pragma once -#include "NVSELexer.h" -#include "NVSEScope.h" -#include "ScriptTokens.h" -#include "NVSEVisitor.h" - -struct VarDeclStmt; -class NVSEVisitor; -struct Expr; -struct Stmt; - -using ExprPtr = std::shared_ptr; -using StmtPtr = std::shared_ptr; - -struct NVSEScript { - NVSEToken name; - std::vector globalVars; - std::vector blocks; - std::unordered_map> m_mpFunctions{}; - - // Required NVSE plugins - std::unordered_map m_mpPluginRequirements{}; - - NVSEScript(NVSEToken name, std::vector globalVars, std::vector blocks) - : name(std::move(name)), globalVars(std::move(globalVars)), blocks(std::move(blocks)) {} - - void Accept(NVSEVisitor* visitor) { - visitor->VisitNVSEScript(this); - } -}; - -struct Stmt { - size_t line = 0; - - // Some statements store type such as return and block statement - Token_Type detailedType = kTokenType_Invalid; - - // Set later in type checker, used in compiler for local generation - std::shared_ptr scope {}; - - virtual ~Stmt() = default; - - virtual void Accept(NVSEVisitor* t) = 0; - - template - bool IsType() { - return dynamic_cast(this); - } -}; - -struct BeginStmt : Stmt { - NVSEToken name; - std::optional param; - StmtPtr block; - CommandInfo* beginInfo; - - BeginStmt(const NVSEToken& name, std::optional param, StmtPtr block, CommandInfo* beginInfo) : name(name), - param(std::move(param)), block(std::move(block)), beginInfo(beginInfo) { - line = name.line; - } - - void Accept(NVSEVisitor* visitor) override { - visitor->VisitBeginStmt(this); - } -}; - -struct FnDeclStmt : Stmt { - NVSEToken token; - std::optional name; - std::vector> args; - StmtPtr body; - - FnDeclStmt(const NVSEToken& token, std::vector> args, StmtPtr body) : token(token), - args(std::move(args)), body(std::move(body)) { - line = token.line; - } - - FnDeclStmt(const NVSEToken& token, const NVSEToken &name, std::vector> args, StmtPtr body) : token(token), - name(name), args(std::move(args)), body(std::move(body)) { - line = token.line; - } - - void Accept(NVSEVisitor* visitor) override { - visitor->VisitFnStmt(this); - } -}; - -struct VarDeclStmt : Stmt { - NVSEToken type; - std::vector> values{}; - - // Set during type checker so that compiler can look up stack index - std::vector> scopeVars{}; - - VarDeclStmt(NVSEToken type, std::vector> values) - : type(std::move(type)), values(std::move(values)) {} - - VarDeclStmt(NVSEToken type, NVSEToken name, ExprPtr value) : type(std::move(type)) { - values.emplace_back(name, std::move(value)); - } - - void Accept(NVSEVisitor* visitor) override { - visitor->VisitVarDeclStmt(this); - } -}; - -struct ExprStmt : Stmt { - ExprPtr expr; - - ExprStmt(ExprPtr expr) : expr(std::move(expr)) {} - - void Accept(NVSEVisitor* visitor) override { - visitor->VisitExprStmt(this); - } -}; - -struct ForStmt : Stmt { - StmtPtr init; - ExprPtr cond; - ExprPtr post; - std::shared_ptr block; - - ForStmt(StmtPtr init, ExprPtr cond, ExprPtr post, std::shared_ptr block) : init(std::move(init)), - cond(std::move(cond)), post(std::move(post)), block(std::move(block)) {} - - void Accept(NVSEVisitor* visitor) override { - visitor->VisitForStmt(this); - } -}; - -struct ForEachStmt : Stmt { - std::vector> declarations; - ExprPtr rhs; - std::shared_ptr block; - bool decompose = false; - - ForEachStmt(std::vector> declarations, ExprPtr rhs, std::shared_ptr block, bool decompose) : declarations(std::move(declarations)), rhs(std::move(rhs)), - block(std::move(block)), decompose(decompose) {} - - void Accept(NVSEVisitor* visitor) override { - visitor->VisitForEachStmt(this); - } -}; - -struct IfStmt : Stmt { - NVSEToken token; - ExprPtr cond; - StmtPtr block; - StmtPtr elseBlock; - - IfStmt(NVSEToken token, ExprPtr cond, StmtPtr block, StmtPtr elseBlock) : token(std::move(token)), - cond(std::move(cond)), - block(std::move(block)), - elseBlock(std::move(elseBlock)) {} - - void Accept(NVSEVisitor* visitor) override { - visitor->VisitIfStmt(this); - } -}; - -struct ReturnStmt : Stmt { - NVSEToken token; - ExprPtr expr; - - ReturnStmt(NVSEToken token, ExprPtr expr) : token(std::move(token)), expr(std::move(expr)) { - line = this->token.line; - } - - void Accept(NVSEVisitor* visitor) override { - visitor->VisitReturnStmt(this); - } -}; - -struct ContinueStmt : Stmt { - NVSEToken token; - ContinueStmt(NVSEToken token) : token(std::move(token)) { - line = this->token.line; - } - - void Accept(NVSEVisitor* visitor) override { - visitor->VisitContinueStmt(this); - } -}; - -struct BreakStmt : Stmt { - NVSEToken token; - BreakStmt(NVSEToken token) : token(std::move(token)) { - line = this->token.line; - } - - void Accept(NVSEVisitor* visitor) override { - visitor->VisitBreakStmt(this); - } -}; - -struct WhileStmt : Stmt { - NVSEToken token; - ExprPtr cond; - StmtPtr block; - - WhileStmt(NVSEToken token, ExprPtr cond, StmtPtr block) : token(std::move(token)), cond(std::move(cond)), - block(std::move(block)) { - line = this->token.line; - } - - void Accept(NVSEVisitor* visitor) override { - visitor->VisitWhileStmt(this); - } -}; - -struct BlockStmt : Stmt { - std::vector statements; - - BlockStmt(std::vector statements) : statements(std::move(statements)) {} - - void Accept(NVSEVisitor* visitor) override { - visitor->VisitBlockStmt(this); - } -}; - -struct Expr { - size_t line = 0; - Token_Type tokenType = kTokenType_Invalid; - - virtual ~Expr() = default; - virtual void Accept(NVSEVisitor* t) = 0; - - template - bool IsType() { - return dynamic_cast(this); - } -}; - -struct AssignmentExpr : Expr { - NVSEToken token; - ExprPtr left; - ExprPtr expr; - - AssignmentExpr(NVSEToken token, ExprPtr left, ExprPtr expr) : token(std::move(token)), left(std::move(left)), - expr(std::move(expr)) { - line = this->token.line; - } - - void Accept(NVSEVisitor* t) override { - t->VisitAssignmentExpr(this); - } -}; - -struct TernaryExpr : Expr { - NVSEToken token; - ExprPtr cond; - ExprPtr left; - ExprPtr right; - - TernaryExpr(NVSEToken token, ExprPtr cond, ExprPtr left, ExprPtr right) : token(std::move(token)), - cond(std::move(cond)), - left(std::move(left)), - right(std::move(right)) { - line = this->token.line; - } - - void Accept(NVSEVisitor* t) override { - t->VisitTernaryExpr(this); - } -}; - -struct InExpr : Expr { - ExprPtr lhs; - NVSEToken token; - std::vector values{}; - ExprPtr expression{}; - bool isNot; - - InExpr(ExprPtr lhs, NVSEToken token, std::vector exprs, bool isNot) : lhs(std::move(lhs)), token(std::move(token)), values(std::move(exprs)), isNot(isNot) { - line = this->token.line; - } - - InExpr(ExprPtr lhs, NVSEToken token, ExprPtr expr, bool isNot) : lhs(std::move(lhs)), token(std::move(token)), expression(std::move(expr)), isNot(isNot) { - line = this->token.line; - } - - void Accept(NVSEVisitor *visitor) override { - visitor->VisitInExpr(this); - } -}; - -struct BinaryExpr : Expr { - NVSEToken op; - ExprPtr left, right; - - BinaryExpr(NVSEToken token, ExprPtr left, ExprPtr right) : op(std::move(token)), left(std::move(left)), - right(std::move(right)) { - line = this->op.line; - } - - void Accept(NVSEVisitor* t) override { - t->VisitBinaryExpr(this); - } -}; - -struct UnaryExpr : Expr { - NVSEToken op; - ExprPtr expr; - bool postfix; - - UnaryExpr(NVSEToken token, ExprPtr expr, bool postfix) : op(std::move(token)), expr(std::move(expr)), - postfix(postfix) { - line = this->op.line; - } - - void Accept(NVSEVisitor* t) override { - t->VisitUnaryExpr(this); - } -}; - -struct SubscriptExpr : Expr { - NVSEToken op; - - ExprPtr left; - ExprPtr index; - - SubscriptExpr(NVSEToken token, ExprPtr left, ExprPtr index) : op(std::move(token)), left(std::move(left)), - index(std::move(index)) { - line = this->op.line; - } - - void Accept(NVSEVisitor* visitor) override { - visitor->VisitSubscriptExpr(this); - } -}; - -struct CallExpr : Expr { - ExprPtr left; - NVSEToken token; - std::vector args; - - // Set by typechecker - CommandInfo* cmdInfo = nullptr; - - CallExpr(ExprPtr left, NVSEToken token, std::vector args) : left(std::move(left)), token(std::move(token)), args(std::move(args)) { - line = this->token.line; - } - - void Accept(NVSEVisitor* t) override { - t->VisitCallExpr(this); - } -}; - -struct GetExpr : Expr { - NVSEToken token; - ExprPtr left; - NVSEToken identifier; - - // Resolved in typechecker - VariableInfo* varInfo = nullptr; - const char* referenceName = nullptr; - - GetExpr(NVSEToken token, ExprPtr left, NVSEToken identifier) : token(std::move(token)), left(std::move(left)), - identifier(std::move(identifier)) { - line = this->token.line; - } - - void Accept(NVSEVisitor* t) override { - t->VisitGetExpr(this); - } -}; - -struct BoolExpr : Expr { - NVSEToken token; - bool value; - - BoolExpr(NVSEToken token, bool value) : token(std::move(token)), value(value) { - line = this->token.line; - } - - void Accept(NVSEVisitor* t) override { - t->VisitBoolExpr(this); - } -}; - -struct NumberExpr : Expr { - NVSEToken token; - double value; - bool isFp; - - // For some reason axis enum is one byte and the rest are two? - int enumLen; - - NumberExpr(NVSEToken token, double value, bool isFp, int enumLen = 0) : token(std::move(token)), value(value), isFp(isFp), enumLen(enumLen) { - line = this->token.line; - } - - void Accept(NVSEVisitor* t) override { - t->VisitNumberExpr(this); - } -}; - -struct StringExpr : Expr { - NVSEToken token; - - StringExpr(NVSEToken token) : token(std::move(token)) { - line = this->token.line; - } - - void Accept(NVSEVisitor* t) override { - t->VisitStringExpr(this); - } -}; - -struct IdentExpr : Expr { - NVSEToken token; - TESForm* form; - - // Set during typechecker variable resolution so that compiler can reference - std::shared_ptr varInfo {nullptr}; - - IdentExpr(NVSEToken token) : token(std::move(token)) { - line = this->token.line; - } - - void Accept(NVSEVisitor* t) override { - t->VisitIdentExpr(this); - } -}; - -struct ArrayLiteralExpr : Expr { - NVSEToken token; - std::vector values; - - ArrayLiteralExpr(NVSEToken token, std::vector values) : token(token), values(values) { - line = this->token.line; - } - - void Accept(NVSEVisitor* t) override { - return t->VisitArrayLiteralExpr(this); - } -}; - -struct MapLiteralExpr : Expr { - NVSEToken token; - std::vector values; - - MapLiteralExpr(NVSEToken token, std::vector values) : token(token), values(values) { - line = this->token.line; - } - - void Accept(NVSEVisitor* t) override { - return t->VisitMapLiteralExpr(this); - } -}; - -struct GroupingExpr : Expr { - ExprPtr expr; - - GroupingExpr(ExprPtr expr) : expr(std::move(expr)) {} - - void Accept(NVSEVisitor* t) override { - t->VisitGroupingExpr(this); - } -}; - -struct LambdaExpr : Expr { - NVSEToken token; - std::vector> args; - StmtPtr body; - - LambdaExpr(NVSEToken token, std::vector> args, StmtPtr body) : token(std::move(token)), - args(std::move(args)), body(std::move(body)) { - line = this->token.line; - } - - void Accept(NVSEVisitor* t) override { - t->VisitLambdaExpr(this); - } - - // Set via type checker - struct { - std::vector paramTypes{}; - Token_Type returnType = kTokenType_Invalid; - } typeinfo; -}; \ No newline at end of file diff --git a/nvse/nvse/NVSECompiler.cpp b/nvse/nvse/NVSECompiler.cpp deleted file mode 100644 index 61a865fc..00000000 --- a/nvse/nvse/NVSECompiler.cpp +++ /dev/null @@ -1,1196 +0,0 @@ -#include "NVSECompiler.h" - -#include "Commands_Array.h" -#include "Commands_MiscRef.h" -#include "Commands_Scripting.h" -#include "Hooks_Script.h" -#include "NVSECompilerUtils.h" -#include "NVSEParser.h" -#include "PluginAPI.h" - -enum OPCodes { - OP_LET = 0x1539, - OP_EVAL = 0x153A, - OP_WHILE = 0x153B, - OP_LOOP = 0x153C, - OP_FOREACH = 0x153D, - OP_FOREACH_ALT = 0x1670, - OP_AR_EXISTS = 0x1671, - OP_TERNARY = 0x166E, - OP_CALL = 0x1545, - OP_SET_MOD_LOCAL_DATA = 0x1549, - OP_GET_MOD_LOCAL_DATA = 0x1548, - OP_SET_FUNCTION_VALUE = 0x1546, - OP_MATCHES_ANY = 0x166F, - OP_AR_LIST = 0x1567, - OP_AR_MAP = 0x1568, - OP_AR_FIND = 0x1557, - OP_AR_BAD_NUMERIC_INDEX = 0x155F, - OP_CONTINUE = 0x153E, - OP_BREAK = 0x15EF, - OP_VERSION = 0x1676, -}; - -void NVSECompiler::ClearTempVars() { - // Clear any vars that start with __global - std::set deletedIndices{}; - for (int i = engineScript->varList.Count() - 1; i >= 0; i--) { - auto var = engineScript->varList.GetNthItem(i); - if (!strncmp(var->name.CStr(), "__temp", strlen("__temp"))) { - CompDbg("Deleting script var: %s\n", var->name.CStr()); - engineScript->varList.RemoveNth(i); - deletedIndices.insert(i + 1); - } - } - - // Now delete any refs with these ids - for (int i = engineScript->refList.Count() - 1; i >= 0; i--) { - auto var = engineScript->refList.GetNthItem(i); - if (deletedIndices.contains(var->varIdx)) { - engineScript->refList.RemoveNth(i); - CompDbg("Deleting ref: %d\n", i); - } - } -} - -void NVSECompiler::PatchScopedGlobals() { - for (auto &[var, patches] : tempGlobals) { - const auto dataIdx = AddVar(var->GetName(), var->variableType); - for (const auto idx : patches) { - SetU16(idx, dataIdx); - } - } -} - -void NVSECompiler::PrintScriptInfo() { - // Debug print local info - CompDbg("\n[Locals]\n"); - for (int i = 0; i < engineScript->varList.Count(); i++) { - auto item = engineScript->varList.GetNthItem(i); - CompDbg("%d: %s %s\n", item->idx, item->name.CStr(), usedVars.contains(item->name.CStr()) ? "" : "(unused)"); - } - - CompDbg("\n"); - - // Refs - CompDbg("[Refs]\n"); - for (int i = 0; i < engineScript->refList.Count(); i++) { - const auto ref = engineScript->refList.GetNthItem(i); - if (ref->varIdx) { - CompDbg("%d: (Var %d)\n", i, ref->varIdx); - } - else { - CompDbg("%d: %s\n", i, ref->form->GetEditorID()); - } - } - - CompDbg("\n"); - - // Script data - CompDbg("[Data]\n"); - for (int i = 0; i < engineScript->info.dataLength; i++) { - CompDbg("%02X ", engineScript->data[i]); - } - - CompInfo("\n\n"); - CompInfo("[Requirements]\n"); - for (const auto &[plugin, version] : ast.m_mpPluginRequirements) { - if (!_stricmp(plugin.c_str(), "nvse")) { - CompInfo("%s [%d.%d.%d]\n", plugin.c_str(), version >> 24 & 0xFF, version >> 16 & 0xFF, version >> 4 & 0xFF); - } else { - CompInfo("%s [%d]\n", plugin.c_str(), version); - } - } - - CompDbg("\n"); - CompDbg("\nNum compiled bytes: %d\n", engineScript->info.dataLength); -} - -bool NVSECompiler::Compile() { - CompDbg("\n==== COMPILER ====\n\n"); - - insideNvseExpr.push(false); - loopIncrements.push(nullptr); - statementCounter.push(0); - scriptStart.push(0); - - ClearTempVars(); - ast.Accept(this); - PatchScopedGlobals(); - - if (!partial) { - engineScript->SetEditorID(scriptName.c_str()); - } - - engineScript->info.compiled = true; - engineScript->info.dataLength = data.size(); - engineScript->info.numRefs = engineScript->refList.Count(); - engineScript->info.varCount = engineScript->varList.Count(); - engineScript->info.unusedVariableCount = engineScript->info.varCount - usedVars.size(); - engineScript->data = static_cast(FormHeap_Allocate(data.size())); - memcpy(engineScript->data, data.data(), data.size()); - - PrintScriptInfo(); - - return true; -} - -void NVSECompiler::VisitNVSEScript(NVSEScript* nvScript) { - // Compile the script name - scriptName = nvScript->name.lexeme; - - // Dont allow naming script the same as another form, unless that form is the script itself - const auto comp = strcmp(scriptName.c_str(), originalScriptName); - if (ResolveObjReference(scriptName, false) && comp && !partial) { - //TODO throw std::runtime_error(std::format("Error: Form name '{}' is already in use.\n", scriptName)); - } - - // SCN - AddU32(static_cast(ScriptParsing::ScriptStatementCode::ScriptName)); - - // Add script requirement opcodes - for (const auto& [plugin, version] : ast.m_mpPluginRequirements) { - StartCall(OP_VERSION); - StartManualArg(); - AddString(plugin); - FinishManualArg(); - StartManualArg(); - AddU8('L'); - AddU32(version); - FinishManualArg(); - FinishCall(); - } - - for (auto& global_var : nvScript->globalVars) { - global_var->Accept(this); - } - - for (auto& block : nvScript->blocks) { - block->Accept(this); - } -} - -void NVSECompiler::VisitBeginStmt(BeginStmt* stmt) { - auto name = stmt->name.lexeme; - - // Shouldn't be null - const CommandInfo* beginInfo = stmt->beginInfo; - - // OP_BEGIN - AddU16(static_cast(ScriptParsing::ScriptStatementCode::Begin)); - - auto beginPatch = AddU16(0x0); - auto beginStart = data.size(); - - AddU16(beginInfo->opcode); - - const auto blockPatch = AddU32(0x0); - - // Add param shit here? - if (stmt->param.has_value()) { - auto param = stmt->param.value(); - - // Num args - AddU16(0x1); - - if (beginInfo->params[0].typeID == kParamType_Integer) { - AddU8('n'); - AddU32(static_cast(std::get(param.value))); - } - - // All other instances use ref? - else { - // Try to resolve the global ref - if (auto ref = ResolveObjReference(param.lexeme)) { - AddU8('r'); - AddU16(ref); - } - else { - throw std::runtime_error(std::format("Unable to resolve form '{}'.", param.lexeme)); - } - } - } else { - if (stmt->beginInfo->numParams > 0) { - AddU16(0x0); - } - } - - SetU16(beginPatch, data.size() - beginStart); - const auto blockStart = data.size(); - - CompileBlock(stmt->block, true); - - // OP_END - AddU32(static_cast(ScriptParsing::ScriptStatementCode::End)); - - SetU32(blockPatch, data.size() - blockStart); -} - -void NVSECompiler::VisitFnStmt(FnDeclStmt* stmt) { - // OP_BEGIN - AddU16(static_cast(ScriptParsing::ScriptStatementCode::Begin)); - - // OP_MODE_LEN - const auto opModeLenPatch = AddU16(0x0); - const auto opModeStart = data.size(); - - // OP_MODE - AddU16(0x0D); - - // SCRIPT_LEN - auto scriptLenPatch = AddU32(0x0); - - // BYTECODE VER - AddU8(0x1); - - // arg count - AddU8(stmt->args.size()); - - // add args - for (auto i = 0; i < stmt->args.size(); i++) { - auto& arg = stmt->args[i]; - auto token = std::get<0>(arg->values[0]); - auto var = arg->scopeVars[0]; - - // Since we are in fn decl we will need to declare these as temp globals - // __global_* - tempGlobals[var].push_back(AddU16(0x0)); - - AddU8(var->variableType); - - CompDbg("Creating global var %s.\n", var->rename.c_str()); - } - - // NUM_ARRAY_ARGS, always 0 - AddU8(0x0); - - auto scriptLenStart = data.size(); - - // Patch mode len - SetU16(opModeLenPatch, data.size() - opModeStart); - - // Compile script - CompileBlock(stmt->body, false); - - // OP_END - AddU32(static_cast(ScriptParsing::ScriptStatementCode::End)); - - SetU32(scriptLenPatch, data.size() - scriptLenStart); -} - -void NVSECompiler::VisitVarDeclStmt(VarDeclStmt* stmt) { - const uint8_t varType = GetScriptTypeFromToken(stmt->type); - - // Since we are doing multiple declarations at once, manually handle count here - statementCounter.top()--; - - for (int i = 0; i < stmt->values.size(); i++) { - auto token = std::get<0>(stmt->values[i]); - auto& value = std::get<1>(stmt->values[i]); - auto var = stmt->scopeVars[i]; - auto name = var->GetName(); - - // Compile lambdas differently - // Does not affect params as they cannot have value specified - if (value->IsType()) { - // To do a similar thing on access - lambdaVars.insert(name); - - StartCall(OP_SET_MOD_LOCAL_DATA); - StartManualArg(); - AddString(std::format("__temp_{}_lambda_{}", scriptName, var->index)); - FinishManualArg(); - AddCallArg(value); - FinishCall(); - continue; - } - - for (int i = 0; i < statementCounter.size(); i++) { - CompDbg(" "); - } - - CompDbg("Defined global variable %s\n", name.c_str()); - - if (var->isGlobal) { - AddVar(name, varType); - } - - if (!value) { - continue; - } - - statementCounter.top()++; - - StartCall(OP_LET); - StartManualArg(); - - // Add arg to stack - AddU8('V'); - AddU8(varType); - AddU16(0); // SCRV - - if (var->isGlobal) { - const auto idx = AddVar(name, varType); - AddU16(idx); // SCDA - } - else { - tempGlobals[var].push_back(AddU16(0x0)); - } - - // Build expression - value->Accept(this); - - // OP_ASSIGN - AddU8(0); - - FinishManualArg(); - FinishCall(); - } -} - -void NVSECompiler::VisitExprStmt(const ExprStmt* stmt) { - // These do not need to be wrapped in op_eval - if (stmt->expr->IsType() || stmt->expr->IsType()) { - stmt->expr->Accept(this); - } - else if (stmt->expr) { - StartCall(OP_EVAL); - AddCallArg(stmt->expr); - FinishCall(); - } else { - // Decrement counter as this is a NOP - statementCounter.top()--; - } -} - -void NVSECompiler::VisitForStmt(ForStmt* stmt) { - if (stmt->init) { - stmt->init->Accept(this); - statementCounter.top()++; - } - - // Store loop increment to be generated before OP_LOOP/OP_CONTINUE - if (stmt->post) { - loopIncrements.push(std::make_shared(stmt->post)); - } else { - loopIncrements.push(nullptr); - } - - // This is pretty much a copy of the while loop code, - // but it does something slightly different with the loopIncrements stack - - // OP_WHILE - AddU16(OP_WHILE); - - // Placeholder OP_LEN - auto exprPatch = AddU16(0x0); - auto exprStart = data.size(); - - auto jmpPatch = AddU32(0x0); - - // 1 param - AddU8(0x1); - - // Compile / patch condition - const auto condStart = data.size(); - const auto condPatch = AddU16(0x0); - insideNvseExpr.push(true); - stmt->cond->Accept(this); - insideNvseExpr.pop(); - SetU16(condPatch, data.size() - condStart); - - // Patch OP_LEN - SetU16(exprPatch, data.size() - exprStart); - - // Compile block - CompileBlock(stmt->block, true); - - // If we have a loop increment (for loop) - // Emit that before OP_LOOP - if (loopIncrements.top()) { - loopIncrements.top()->Accept(this); - statementCounter.top()++; - } - - // OP_LOOP - AddU16(OP_LOOP); - AddU16(0x0); - - // OP_LOOP is an extra statement - statementCounter.top()++; - - // Patch jmp - SetU32(jmpPatch, data.size() - scriptStart.top()); - - loopIncrements.pop(); -} - -void NVSECompiler::VisitForEachStmt(ForEachStmt* stmt) { - if (stmt->decompose) { - // Try to resolve second var if there is one - std::shared_ptr var1 = nullptr; - if (stmt->declarations[0]) { - var1 = stmt->scope->resolveVariable(std::get<0>(stmt->declarations[0]->values[0]).lexeme); - } - - // Try to resolve second var if there is one - std::shared_ptr var2 = nullptr; - if (stmt->declarations.size() == 2 && stmt->declarations[1]) { - var2 = stmt->scope->resolveVariable(std::get<0>(stmt->declarations[1]->values[0]).lexeme); - } - - // OP_FOREACH - AddU16(OP_FOREACH_ALT); - - // Placeholder OP_LEN - const auto exprPatch = AddU16(0x0); - const auto exprStart = data.size(); - const auto jmpPatch = AddU32(0x0); - - // Num args - // Either 2 or 3, depending on: - // "for ([int iKey, _] in { "1"::2 "3"::4 }" - 3 args, even though one can be an invalid variable - // VS: - // "for ([int iKey] in { "1"::2 "3"::4 }" - 2 args - AddU8(1 + stmt->declarations.size()); - insideNvseExpr.push(true); - - // Arg 1 - the source array - auto argStart = data.size(); - auto argPatch = AddU16(0x0); - stmt->rhs->Accept(this); - SetU16(argPatch, data.size() - argStart); - SetU16(exprPatch, data.size() - exprStart); - - // Arg 2 - // If there's two vars (including "_"), the 2nd arg for ForEachAlt is the valueIter. - // However, our syntax has key arg first: "for ([int iKey, int iValue] in { "1"::2 "3"::4 })" - // Thus pass iValue arg as 2nd arg instead of 3rd, even though it's the 2nd var in the statement. - if (stmt->declarations.size() == 2) { - argStart = data.size(); - argPatch = AddU16(0x0); - - // Arg 2 - valueIterVar - if (var2) { - AddU8('V'); - AddU8(var2->variableType); - AddU16(0); - tempGlobals[var2].push_back(AddU16(0x0)); - } - else { - // OptionalEmpty token - AddU8('K'); - } - SetU16(argPatch, data.size() - argStart); - SetU16(exprPatch, data.size() - exprStart); - - // Arg 3 - pass keyIterVar last - argStart = data.size(); - argPatch = AddU16(0x0); - - if (var1) { - AddU8('V'); - AddU8(var1->variableType); - AddU16(0); - tempGlobals[var1].push_back(AddU16(0x0)); - } else { - // OptionalEmpty token - AddU8('K'); - } - SetU16(argPatch, data.size() - argStart); - SetU16(exprPatch, data.size() - exprStart); - } - else { // assume 1 - argStart = data.size(); - argPatch = AddU16(0x0); - AddU8('V'); - AddU8(var1 != nullptr ? var1->variableType : 0); - AddU16(0); - if (var1) { - tempGlobals[var1].push_back(AddU16(0x0)); - } else { - AddU16(0); - } - SetU16(argPatch, data.size() - argStart); - SetU16(exprPatch, data.size() - exprStart); - } - - insideNvseExpr.pop(); - - loopIncrements.push(nullptr); - CompileBlock(stmt->block, true); - loopIncrements.pop(); - - // OP_LOOP - AddU16(OP_LOOP); - AddU16(0x0); - statementCounter.top()++; - - // Patch jmp - SetU32(jmpPatch, data.size() - scriptStart.top()); - } else { - // Get variable info - const auto varDecl = stmt->declarations[0]; - if (!varDecl) { - throw std::runtime_error("Unexpected compiler error."); - } - - auto var = stmt->scope->resolveVariable(std::get<0>(varDecl->values[0]).lexeme); - - // OP_FOREACH - AddU16(OP_FOREACH); - - // Placeholder OP_LEN - const auto exprPatch = AddU16(0x0); - const auto exprStart = data.size(); - - const auto jmpPatch = AddU32(0x0); - - // Num args - AddU8(0x1); - - // Arg 1 len - const auto argStart = data.size(); - const auto argPatch = AddU16(0x0); - - insideNvseExpr.push(true); - AddU8('V'); - AddU8(var->variableType); - AddU16(0); - AddU16(var->index); - - stmt->rhs->Accept(this); - AddU8(kOpType_In); - insideNvseExpr.pop(); - - SetU16(argPatch, data.size() - argStart); - SetU16(exprPatch, data.size() - exprStart); - - loopIncrements.push(nullptr); - CompileBlock(stmt->block, true); - loopIncrements.pop(); - - // OP_LOOP - AddU16(OP_LOOP); - AddU16(0x0); - statementCounter.top()++; - - // Patch jmp - SetU32(jmpPatch, data.size() - scriptStart.top()); - } -} - -void NVSECompiler::VisitIfStmt(IfStmt* stmt) { - // OP_IF - AddU16(static_cast(ScriptParsing::ScriptStatementCode::If)); - - // Placeholder OP_LEN - auto exprPatch = AddU16(0x0); - auto exprStart = data.size(); - - // Placeholder JMP_OPS - auto jmpPatch = AddU16(0x0); - - // Placeholder EXP_LEN - auto compPatch = AddU16(0x0); - auto compStart = data.size(); - - // OP_PUSH - AddU8(0x20); - - // Enter NVSE eval - AddU8(0x58); - StartCall(OP_EVAL); - AddCallArg(stmt->cond); - FinishCall(); - - // Patch lengths - SetU16(compPatch, data.size() - compStart); - SetU16(exprPatch, data.size() - exprStart); - - // Patch JMP_OPS - SetU16(jmpPatch, CompileBlock(stmt->block, true)); - - // Build OP_ELSE - if (stmt->elseBlock) { - statementCounter.top()++; - - // OP_ELSE - AddU16(static_cast(ScriptParsing::ScriptStatementCode::Else)); - - // OP_LEN - AddU16(0x02); - - // Placeholder JMP_OPS - auto elsePatch = AddU16(0x0); - - // Compile else block - SetU16(elsePatch, CompileBlock(stmt->elseBlock, true)); - } - - // OP_ENDIF - statementCounter.top()++; - AddU16(static_cast(ScriptParsing::ScriptStatementCode::EndIf)); - AddU16(0x0); -} - -void NVSECompiler::VisitReturnStmt(ReturnStmt* stmt) { - // Compile SetFunctionValue if we have a return value - if (stmt->expr) { - StartCall(OP_SET_FUNCTION_VALUE); - AddCallArg(stmt->expr); - FinishCall(); - } - - // Emit op_return - AddU32(static_cast(ScriptParsing::ScriptStatementCode::Return)); -} - -void NVSECompiler::VisitContinueStmt(ContinueStmt* stmt) { - if (loopIncrements.top()) { - loopIncrements.top()->Accept(this); - statementCounter.top()++; - } - - AddU32(OP_CONTINUE); -} - -void NVSECompiler::VisitBreakStmt(BreakStmt* stmt) { - AddU32(OP_BREAK); -} - -void NVSECompiler::VisitWhileStmt(WhileStmt* stmt) { - // OP_WHILE - AddU16(OP_WHILE); - - // Placeholder OP_LEN - const auto exprPatch = AddU16(0x0); - const auto exprStart = data.size(); - - const auto jmpPatch = AddU32(0x0); - - // 1 param - AddU8(0x1); - - // Compile / patch condition - auto condStart = data.size(); - auto condPatch = AddU16(0x0); - insideNvseExpr.push(true); - stmt->cond->Accept(this); - insideNvseExpr.pop(); - SetU16(condPatch, data.size() - condStart); - - // Patch OP_LEN - SetU16(exprPatch, data.size() - exprStart); - - // Compile block - loopIncrements.push(nullptr); - CompileBlock(stmt->block, true); - loopIncrements.pop(); - - // OP_LOOP - AddU16(OP_LOOP); - AddU16(0x0); - statementCounter.top()++; - - // Patch jmp - SetU32(jmpPatch, data.size() - scriptStart.top()); -} - -void NVSECompiler::VisitBlockStmt(BlockStmt* stmt) { - for (auto& statement : stmt->statements) { - statement->Accept(this); - statementCounter.top()++; - } -} - -void NVSECompiler::VisitAssignmentExpr(AssignmentExpr* expr) { - // Assignment as standalone statement - if (!insideNvseExpr.top()) { - StartCall(OP_LET); - StartManualArg(); - expr->left->Accept(this); - expr->expr->Accept(this); - AddU8(tokenOpToNVSEOpType[expr->token.type]); - FinishManualArg(); - FinishCall(); - } - - // Assignment as an expression - else { - // Build expression - expr->left->Accept(this); - expr->expr->Accept(this); - - // Assignment opcode - AddU8(tokenOpToNVSEOpType[expr->token.type]); - } -} - -void NVSECompiler::VisitTernaryExpr(TernaryExpr* expr) { - StartCall(OP_TERNARY); - AddCallArg(expr->cond); - AddCallArg(expr->left); - AddCallArg(expr->right); - FinishCall(); -} - -void NVSECompiler::VisitInExpr(InExpr* expr) { - // Value list provided - if (!expr->values.empty()) { - StartCall(OP_MATCHES_ANY); - AddCallArg(expr->lhs); - for (auto arg : expr->values) { - AddCallArg(arg); - } - FinishCall(); - } - // Array - else { - StartCall(OP_AR_EXISTS); - AddCallArg(expr->expression); - AddCallArg(expr->lhs); - FinishCall(); - } - - if (expr->isNot) { - AddU8(tokenOpToNVSEOpType[NVSETokenType::Bang]); - } -} - -void NVSECompiler::VisitBinaryExpr(BinaryExpr* expr) { - expr->left->Accept(this); - expr->right->Accept(this); - AddU8(tokenOpToNVSEOpType[expr->op.type]); -} - -void NVSECompiler::VisitUnaryExpr(UnaryExpr* expr) { - if (expr->postfix) { - // Slight hack to get postfix operators working - expr->expr->Accept(this); - expr->expr->Accept(this); - AddU8('B'); - AddU8(1); - if (expr->op.type == NVSETokenType::PlusPlus) { - AddU8(tokenOpToNVSEOpType[NVSETokenType::Plus]); - } - else { - AddU8(tokenOpToNVSEOpType[NVSETokenType::Minus]); - } - AddU8(tokenOpToNVSEOpType[NVSETokenType::Eq]); - - AddU8('B'); - AddU8(1); - if (expr->op.type == NVSETokenType::PlusPlus) { - AddU8(tokenOpToNVSEOpType[NVSETokenType::Minus]); - } - else { - AddU8(tokenOpToNVSEOpType[NVSETokenType::Plus]); - } - } - else { - expr->expr->Accept(this); - AddU8(tokenOpToNVSEOpType[expr->op.type]); - } -} - -void NVSECompiler::VisitSubscriptExpr(SubscriptExpr* expr) { - expr->left->Accept(this); - expr->index->Accept(this); - AddU8(tokenOpToNVSEOpType[NVSETokenType::LeftBracket]); -} - -void NVSECompiler::VisitCallExpr(CallExpr* expr) { - if (!expr->cmdInfo) { - throw std::runtime_error("expr->cmdInfo was null. Please report this as a bug."); - } - - // Handle call command separately, unique parse function - if (expr->cmdInfo->parse == kCommandInfo_Call.parse) { - if (insideNvseExpr.top()) { - AddU8('X'); - AddU16(0x0); // SCRV - } - - AddU16(OP_CALL); - const auto callLengthStart = data.size() + (insideNvseExpr.top() ? 0 : 2); - const auto callLengthPatch = AddU16(0x0); - insideNvseExpr.push(true); - - // Bytecode ver - AddU8(1); - - auto exprLengthStart = data.size(); - auto exprLengthPatch = AddU16(0x0); - - // Resolve identifier - expr->args[0]->Accept(this); - SetU16(exprLengthPatch, data.size() - exprLengthStart); - - // Add args - AddU8(expr->args.size() - 1); - for (int i = 1; i < expr->args.size(); i++) { - auto argStartInner = data.size(); - auto argPatchInner = AddU16(0x0); - expr->args[i]->Accept(this); - SetU16(argPatchInner, data.size() - argStartInner); - } - - SetU16(callLengthPatch, data.size() - callLengthStart); - - insideNvseExpr.pop(); - return; - } - - // See if we should wrap inside let - // Need to do this in the case of something like - // player.AddItem(Caps001, 10) to pass player on stack and use 'x' - // annoying but want to keep the way these were passed consistent, especially in case of - // Quest.target.AddItem() - if (expr->left && !insideNvseExpr.top()) { - // OP_EVAL - StartCall(OP_EVAL); - StartManualArg(); - expr->Accept(this); - FinishManualArg(); - FinishCall(); - return; - } - - StartCall(expr->cmdInfo, expr->left); - for (auto arg : expr->args) { - auto numExpr = dynamic_cast(arg.get()); - if (numExpr && numExpr->enumLen > 0) { - arg->Accept(this); - callBuffers.top().numArgs++; - } else { - AddCallArg(arg); - } - } - FinishCall(); -} - -void NVSECompiler::VisitGetExpr(GetExpr* expr) { - const auto refIdx = ResolveObjReference(expr->referenceName); - if (!refIdx) { - throw std::runtime_error(std::format("Unable to resolve reference '{}'.", expr->referenceName)); - } - - // Put ref on stack - AddU8('V'); - AddU8(expr->varInfo->type); - AddU16(refIdx); - AddU16(expr->varInfo->idx); -} - -void NVSECompiler::VisitBoolExpr(BoolExpr* expr) { - AddU8('B'); - AddU8(expr->value); -} - -void NVSECompiler::VisitNumberExpr(NumberExpr* expr) { - if (expr->isFp) { - AddF64(expr->value); - } - else if (expr->enumLen) { - if (expr->enumLen == 1) { - AddU8(static_cast(expr->value)); - } else { - AddU16(static_cast(expr->value)); - } - } - else { - if (expr->value <= UINT8_MAX) { - AddU8('B'); - AddU8(static_cast(expr->value)); - } - else if (expr->value <= UINT16_MAX) { - AddU8('I'); - AddU16(static_cast(expr->value)); - } - else { - AddU8('L'); - AddU32(static_cast(expr->value)); - } - } -} - -void NVSECompiler::VisitStringExpr(StringExpr* expr) { - AddString(std::get(expr->token.value)); -} - -void NVSECompiler::VisitIdentExpr(IdentExpr* expr) { - // Resolve in scope - const auto var = expr->varInfo; - std::string name; - if (var) { - name = var->GetName(); - } else { - name = expr->token.lexeme; - } - usedVars.emplace(name); - - // If this is a lambda var, inline it as a call to GetModLocalData - if (lambdaVars.contains(name)) { - if (!var) { - throw std::runtime_error("Unexpected compiler error. Lambda var info was null."); - } - - StartCall(OP_GET_MOD_LOCAL_DATA); - StartManualArg(); - AddString(std::format("__temp_{}_lambda_{}", scriptName, var->index)); - FinishManualArg(); - FinishCall(); - return; - } - - // Local variable - if (var) { - for (int i = 0; i < statementCounter.size(); i++) { - CompDbg(" "); - } - - CompDbg("Read from global variable %s\n", name.c_str()); - AddU8('V'); - AddU8(var->variableType); - AddU16(0); - - if (!var->rename.empty()) { - tempGlobals[var].push_back(AddU16(0x0)); - } else { - if (auto local = engineScript->varList.GetVariableByName(name.c_str())) { - AddU16(local->idx); - } else { - throw std::runtime_error(std::format("Unable to resolve variable {}. Please report this as a bug.", name)); - } - } - } - - else { - // Try to look up as object reference - if (auto refIdx = ResolveObjReference(name)) { - auto form = engineScript->refList.GetNthItem(refIdx - 1)->form; - if (form->typeID == kFormType_TESGlobal) { - AddU8('G'); - } else { - AddU8('R'); - } - AddU16(refIdx); - } - } -} - -void NVSECompiler::VisitMapLiteralExpr(MapLiteralExpr* expr) { - StartCall(OP_AR_MAP); - for (const auto& val : expr->values) { - AddCallArg(val); - } - FinishCall(); -} - -void NVSECompiler::VisitArrayLiteralExpr(ArrayLiteralExpr* expr) { - StartCall(OP_AR_LIST); - for (const auto &val : expr->values) { - AddCallArg(val); - } - FinishCall(); -} - -void NVSECompiler::VisitGroupingExpr(GroupingExpr* expr) { - expr->expr->Accept(this); -} - -void NVSECompiler::VisitLambdaExpr(LambdaExpr* expr) { - // PARAM_LAMBDA - AddU8('F'); - - // Arg size - const auto argSizePatch = AddU32(0x0); - const auto argStart = data.size(); - - // Compile lambda - { - scriptStart.push(data.size()); - - // SCN - AddU32(static_cast(ScriptParsing::ScriptStatementCode::ScriptName)); - - // OP_BEGIN - AddU16(0x10); - - // OP_MODE_LEN - const auto opModeLenPatch = AddU16(0x0); - const auto opModeStart = data.size(); - - // OP_MODE - AddU16(0x0D); - - // SCRIPT_LEN - const auto scriptLenPatch = AddU32(0x0); - - // BYTECODE VER - AddU8(0x1); - - // arg count - AddU8(expr->args.size()); - - // add args - for (auto i = 0; i < expr->args.size(); i++) { - auto& arg = expr->args[i]; - auto token = std::get<0>(arg->values[0]); - - // Resolve variable, since we are in lambda decl we will need to declare these as temp globals - // __global_* - auto var = arg->scopeVars[0]; - tempGlobals[var].push_back(AddU16(0x0)); - - AddU8(var->variableType); - - CompDbg("Creating global var %s.\n", var->rename.c_str()); - } - - // NUM_ARRAY_ARGS, always 0 - AddU8(0x0); - - auto scriptLenStart = data.size(); - - // Patch mode len - SetU16(opModeLenPatch, data.size() - opModeStart); - - // Compile script - insideNvseExpr.push(false); - CompileBlock(expr->body, false); - insideNvseExpr.pop(); - - // OP_END - AddU32(0x11); - - // Different inside of lambda for some reason? - SetU32(scriptLenPatch, data.size() - scriptLenStart); - - scriptStart.pop(); - } - - SetU32(argSizePatch, data.size() - argStart); -} - -uint32_t NVSECompiler::CompileBlock(StmtPtr stmt, bool incrementCurrent) { - for (int i = 0; i < statementCounter.size(); i++) { - CompDbg(" "); - } - CompDbg("Entering block\n"); - - // Get sub-block statement count - statementCounter.push(0); - stmt->Accept(this); - const auto newStatementCount = statementCounter.top(); - statementCounter.pop(); - - // Lambdas do not add to parent block stmt count - // Others do - if (incrementCurrent) { - statementCounter.top() += newStatementCount; - } - - for (int i = 0; i < statementCounter.size(); i++) { - CompDbg(" "); - } - CompDbg("Exiting Block. Size %d\n", newStatementCount); - - return newStatementCount; -} - -void NVSECompiler::StartCall(CommandInfo* cmd, ExprPtr stackRef) { - // Handle stack refs - if (stackRef) { - stackRef->Accept(this); - } - - if (insideNvseExpr.top()) { - if (stackRef) { - AddU8('x'); - } - else { - AddU8('X'); - } - AddU16(0x0); // SCRV - } - AddU16(cmd->opcode); - - // Call size - CallBuffer buf{}; - buf.parse = &cmd->parse; - buf.stackRef = stackRef; - buf.startPos = data.size() + (insideNvseExpr.top() ? 0 : 2); - buf.startPatch = AddU16(0x0); - - if (isDefaultParse(cmd->parse)) { - buf.argPos = AddU16(0x0); - } else { - buf.argPos = AddU8(0x0); - } - - callBuffers.push(buf); -} - -void NVSECompiler::FinishCall() { - auto buf = callBuffers.top(); - callBuffers.pop(); - - SetU16(buf.startPatch, data.size() - buf.startPos); - - if (isDefaultParse(*buf.parse)) { - SetU16(buf.argPos, buf.numArgs); - } - else { - SetU8(buf.argPos, buf.numArgs); - } - - // Handle stack refs - if (buf.stackRef) { - AddU8(tokenOpToNVSEOpType[NVSETokenType::Dot]); - } -} - -void NVSECompiler::StartCall(uint16_t opcode, ExprPtr stackRef) { - StartCall(g_scriptCommands.GetByOpcode(opcode), stackRef); -} - -void NVSECompiler::PerformCall(uint16_t opcode) { - StartCall(opcode); - FinishCall(); -} - -void NVSECompiler::AddCallArg(ExprPtr arg) { - // Make NVSE aware - if (isDefaultParse(*callBuffers.top().parse)) { - AddU16(0xFFFF); - } - - const auto argStartInner = data.size(); - const auto argPatchInner = AddU16(0x0); - - insideNvseExpr.push(true); - arg->Accept(this); - insideNvseExpr.pop(); - - SetU16(argPatchInner, data.size() - argStartInner); - callBuffers.top().numArgs++; -} - -void NVSECompiler::StartManualArg() { - // Make NVSE aware - if (isDefaultParse(*callBuffers.top().parse)) { - AddU16(0xFFFF); - } - - callBuffers.top().argStart = data.size(); - callBuffers.top().argPatch = AddU16(0x0); - insideNvseExpr.push(true); -} - -void NVSECompiler::FinishManualArg() { - insideNvseExpr.pop(); - SetU16(callBuffers.top().argPatch, data.size() - callBuffers.top().argStart); - callBuffers.top().numArgs++; -} \ No newline at end of file diff --git a/nvse/nvse/NVSECompiler.h b/nvse/nvse/NVSECompiler.h deleted file mode 100644 index 4518842f..00000000 --- a/nvse/nvse/NVSECompiler.h +++ /dev/null @@ -1,256 +0,0 @@ -#pragma once -#include -#include -#include -#include - -#include "NVSEParser.h" -#include "NVSEVisitor.h" - -#include "nvse/GameScript.h" -#include "nvse/GameAPI.h" -#include "nvse/GameObjects.h" - -class Script; - -struct CallBuffer { - size_t startPos{}; - size_t startPatch{}; - size_t argPos{}; - size_t numArgs{}; - size_t argStart{}; - size_t argPatch{}; - CommandInfo* cmdInfo{}; - Cmd_Parse* parse{}; - ExprPtr stackRef; -}; - -class NVSECompiler : NVSEVisitor { -public: - Script* engineScript; - bool partial; - NVSEScript * - - const char* originalScriptName; - std::string scriptName{}; - - std::stack callBuffers{}; - - std::stack insideNvseExpr{}; - - // When we enter a for loop, push the increment condition on the stack - // Insert this before any continue / break - std::stack loopIncrements{}; - - // Count number of statements compiled - // Certain blocks such as 'if' need to know how many ops to skip - // We also inject for loop increment statement before continue in loops - // This is to count that - std::stack statementCounter {}; - - // To set unused var count at end of script - std::set usedVars{}; - - // Keep track of lambda vars as these get inlined - std::set lambdaVars{}; - - // 'temp' / ad-hoc global vars to add to ref list at end of script - // Only used for lambda param declaration - std::unordered_map, std::vector> tempGlobals{}; - - std::stack scriptStart {}; - - // Look up a local variable, or create it if not already defined - uint16_t AddVar(const std::string& identifier, const uint8_t type) { - if (const auto info = engineScript->GetVariableByName(identifier.c_str())) { - if (info->type != type) { - // Remove from ref list if it was a ref prior - if (info->type == Script::eVarType_Ref) { - if (auto* v = engineScript->refList.FindFirst([&](const Script::RefVariable *cur) { return cur->varIdx == info->idx; })) { - engineScript->refList.Remove(v); - } - } - - // Add to ref list if new ref - if (type == Script::eVarType_Ref) { - auto ref = New(); - ref->name = String(); - ref->name.Set(identifier.c_str()); - ref->varIdx = info->idx; - engineScript->refList.Append(ref); - } - - info->type = type; - } - - return info->idx; - } - - auto varInfo = New(); - varInfo->name = String(); - varInfo->name.Set(identifier.c_str()); - varInfo->type = type; - varInfo->idx = engineScript->varList.Count() + 1; - engineScript->varList.Append(varInfo); - - if (type == Script::eVarType_Ref) { - auto ref = New(); - ref->name = String(); - ref->name.Set(identifier.c_str()); - ref->varIdx = varInfo->idx; - engineScript->refList.Append(ref); - } - - return static_cast(varInfo->idx); - } - - uint16_t ResolveObjReference(std::string identifier, const bool add = true) { - TESForm* form; - if (_stricmp(identifier.c_str(), "player") == 0) { - // PlayerRef (this is how the vanilla compiler handles it so I'm changing it for consistency and to fix issues) - form = LookupFormByID(0x14); - } - else { - form = GetFormByID(identifier.c_str()); - } - - if (form) { - // only persistent refs can be used in scripts - if (const auto refr = DYNAMIC_CAST(form, TESForm, TESObjectREFR); refr && !refr->IsPersistent()) { - throw std::runtime_error(std::format("Object reference '{}' must be persistent.", identifier)); - } - - // See if form is already registered - uint16_t i = 1; - for (auto cur = engineScript->refList.Begin(); !cur.End(); cur.Next()) { - if (cur->form == form) { - return i; - } - - i++; - } - - if (add) { - const auto ref = New(); - ref->name = String(); - ref->name.Set(identifier.c_str()); - ref->form = form; - engineScript->refList.Append(ref); - return static_cast(engineScript->refList.Count()); - } - - return 1; - } - - return 0; - } - - // Compiled bytes - std::vector data{}; - - size_t AddU8(const uint8_t byte) { - data.emplace_back(byte); - - return data.size() - 1; - } - - size_t AddU16(const uint16_t bytes) { - data.emplace_back(bytes & 0xFF); - data.emplace_back(bytes >> 8 & 0xFF); - - return data.size() - 2; - } - - size_t AddU32(const uint32_t bytes) { - data.emplace_back(bytes & 0xFF); - data.emplace_back(bytes >> 8 & 0xFF); - data.emplace_back(bytes >> 16 & 0xFF); - data.emplace_back(bytes >> 24 & 0xFF); - - return data.size() - 4; - } - - size_t AddF64(double value) { - const auto bytes = reinterpret_cast(&value); - - AddU8('Z'); - for (int i = 0; i < 8; i++) { - AddU8(bytes[i]); - } - - return data.size() - 8; - } - - size_t AddString(const std::string& str) { - AddU8('S'); - AddU16(str.size()); - for (char c : str) { - AddU8(c); - } - - return data.size() - str.size(); - } - - void SetU8(const size_t index, const uint8_t byte) { - data[index] = byte; - } - - void SetU16(const size_t index, const uint16_t byte) { - data[index] = byte & 0xFF; - data[index + 1] = byte >> 8 & 0xFF; - } - - void SetU32(const size_t index, const uint32_t byte) { - data[index] = byte & 0xFF; - data[index + 1] = byte >> 8 & 0xFF; - data[index + 2] = byte >> 16 & 0xFF; - data[index + 3] = byte >> 24 & 0xFF; - } - - NVSECompiler(Script *script, bool partial, NVSEScript& ast) - : engineScript(script), partial(partial), ast(ast), originalScriptName(script->GetEditorID()) {} - - void ClearTempVars(); - void PatchScopedGlobals(); - void PrintScriptInfo(); - bool Compile(); - - void VisitNVSEScript(NVSEScript* nvScript) override; - void VisitBeginStmt(BeginStmt* stmt) override; - void VisitFnStmt(FnDeclStmt* stmt) override; - void VisitVarDeclStmt(VarDeclStmt* stmt) override; - void VisitExprStmt(const ExprStmt* stmt) override; - void VisitForStmt(ForStmt* stmt) override; - void VisitForEachStmt(ForEachStmt* stmt) override; - void VisitIfStmt(IfStmt* stmt) override; - void VisitReturnStmt(ReturnStmt* stmt) override; - void VisitContinueStmt(ContinueStmt* stmt) override; - void VisitBreakStmt(BreakStmt* stmt) override; - void VisitWhileStmt(WhileStmt* stmt) override; - void VisitBlockStmt(BlockStmt* stmt) override; - void VisitAssignmentExpr(AssignmentExpr* expr) override; - void VisitTernaryExpr(TernaryExpr* expr) override; - void VisitInExpr(InExpr* expr) override; - void VisitBinaryExpr(BinaryExpr* expr) override; - void VisitUnaryExpr(UnaryExpr* expr) override; - void VisitSubscriptExpr(SubscriptExpr* expr) override; - void VisitCallExpr(CallExpr* expr) override; - void VisitGetExpr(GetExpr* expr) override; - void VisitBoolExpr(BoolExpr* expr) override; - void VisitNumberExpr(NumberExpr* expr) override; - void VisitStringExpr(StringExpr* expr) override; - void VisitIdentExpr(IdentExpr* expr) override; - void VisitMapLiteralExpr(MapLiteralExpr* expr) override; - void VisitArrayLiteralExpr(ArrayLiteralExpr* expr) override; - void VisitGroupingExpr(GroupingExpr* expr) override; - void VisitLambdaExpr(LambdaExpr* expr) override; - - uint32_t CompileBlock(StmtPtr stmt, bool incrementCurrent); - void FinishCall(); - void StartCall(CommandInfo* cmd, ExprPtr stackRef = nullptr); - void StartCall(uint16_t opcode, ExprPtr stackRef = nullptr); - void PerformCall(uint16_t opcode); - void AddCallArg(ExprPtr arg); - void StartManualArg(); - void FinishManualArg(); -}; diff --git a/nvse/nvse/NVSECompilerUtils.cpp b/nvse/nvse/NVSECompilerUtils.cpp deleted file mode 100644 index 4634424d..00000000 --- a/nvse/nvse/NVSECompilerUtils.cpp +++ /dev/null @@ -1,541 +0,0 @@ -#include "NVSECompilerUtils.h" - -inline bool consoleAllocated = false; - -void allocateConsole() { -#ifdef EDITOR - if (!consoleAllocated) { - AllocConsole(); - freopen("CONOUT$", "w", stdout); - consoleAllocated = true; - - HWND hwnd = GetConsoleWindow(); - if (hwnd != NULL) - { - HMENU hMenu = GetSystemMenu(hwnd, FALSE); - if (hMenu != NULL) DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND); - } - } -#endif -} - -void CompClear() { - if (consoleAllocated) { - system("CLS"); - } -} - -void CompDbg(const char* fmt, ...) { - allocateConsole(); - - va_list argList; - va_start(argList, fmt); -#if defined(EDITOR) && defined(_DEBUG) - vprintf(fmt, argList); -#else - v_DMESSAGE(fmt, argList); -#endif - va_end(argList); -} - -void CompInfo(const char* fmt, ...) { - allocateConsole(); - - va_list argList; - va_start(argList, fmt); -#if defined(EDITOR) - vprintf(fmt, argList); -#else - v_MESSAGE(fmt, argList); -#endif - va_end(argList); -} - -void CompErr(const char* fmt, ...) { - allocateConsole(); - - va_list argList; - va_start(argList, fmt); -#ifdef EDITOR - vprintf(fmt, argList); -#else - vShowRuntimeError(nullptr, fmt, argList); -#endif - va_end(argList); -} - -std::unordered_map tokenOpToNVSEOpType{ - {NVSETokenType::EqEq, kOpType_Equals}, - {NVSETokenType::LogicOr, kOpType_LogicalOr}, - {NVSETokenType::LogicAnd, kOpType_LogicalAnd}, - {NVSETokenType::Greater, kOpType_GreaterThan}, - {NVSETokenType::GreaterEq, kOpType_GreaterOrEqual}, - {NVSETokenType::Less, kOpType_LessThan}, - {NVSETokenType::LessEq, kOpType_LessOrEqual}, - {NVSETokenType::Bang, kOpType_LogicalNot}, - {NVSETokenType::BangEq, kOpType_NotEqual}, - - {NVSETokenType::Plus, kOpType_Add}, - {NVSETokenType::Minus, kOpType_Subtract}, - {NVSETokenType::Star, kOpType_Multiply}, - {NVSETokenType::Slash, kOpType_Divide}, - {NVSETokenType::Mod, kOpType_Modulo}, - {NVSETokenType::Pow, kOpType_Exponent}, - - {NVSETokenType::Eq, kOpType_Assignment}, - {NVSETokenType::PlusEq, kOpType_PlusEquals}, - {NVSETokenType::MinusEq, kOpType_MinusEquals}, - {NVSETokenType::StarEq, kOpType_TimesEquals}, - {NVSETokenType::SlashEq, kOpType_DividedEquals}, - {NVSETokenType::ModEq, kOpType_ModuloEquals}, - {NVSETokenType::PowEq, kOpType_ExponentEquals}, - - {NVSETokenType::MakePair, kOpType_MakePair}, - {NVSETokenType::Slice, kOpType_Slice}, - - // Logical - {NVSETokenType::BitwiseAnd, kOpType_BitwiseAnd}, - {NVSETokenType::BitwiseOr, kOpType_BitwiseOr}, - {NVSETokenType::BitwiseAndEquals, kOpType_BitwiseAndEquals}, - {NVSETokenType::BitwiseOrEquals, kOpType_BitwiseOrEquals}, - {NVSETokenType::LeftShift, kOpType_LeftShift}, - {NVSETokenType::RightShift, kOpType_RightShift}, - - // Unary - {NVSETokenType::Negate, kOpType_Negation}, - {NVSETokenType::Dollar, kOpType_ToString}, - {NVSETokenType::Pound, kOpType_ToNumber}, - {NVSETokenType::Box, kOpType_Box}, - {NVSETokenType::Unbox, kOpType_Dereference}, - {NVSETokenType::LeftBracket, kOpType_LeftBracket}, - {NVSETokenType::Dot, kOpType_Dot}, - {NVSETokenType::BitwiseNot, kOpType_BitwiseNot} -}; - -// Copied for testing from ScriptAnalyzer.cpp -const UInt32 g_gameParseCommands[] = { 0x5B1BA0, 0x5B3C70, 0x5B3CA0, 0x5B3C40, 0x5B3CD0, reinterpret_cast(Cmd_Default_Parse) }; -const UInt32 g_messageBoxParseCmds[] = { 0x5B3CD0, 0x5B3C40, 0x5B3C70, 0x5B3CA0 }; - -bool isDefaultParse(Cmd_Parse parse) { - return Contains(g_gameParseCommands, reinterpret_cast(parse)) || reinterpret_cast(parse) == 0x005C67E0; -} - -#ifdef EDITOR -constexpr uint32_t ACTOR_VALUE_ADDRESS = 0x491300; -constexpr uint32_t SEX_0 = 0xE9AB18; -constexpr uint32_t SEX_1 = 0xE9AB1C; -constexpr uint32_t MISC_STAT = 0x52E790; -#else -constexpr uint32_t ACTOR_VALUE_ADDRESS = 0x66EB40; -constexpr uint32_t SEX_0 = 0x104A0EC; -constexpr uint32_t SEX_1 = 0x104A0F4; -constexpr uint32_t MISC_STAT = 0x4D5EB0; -#endif - -void resolveVanillaEnum(const ParamInfo* info, const char* str, uint32_t* val, uint32_t* len) { - uint32_t i = -1; - *val = -1; - *len = 2; - switch (info->typeID) { - case kParamType_ActorValue: - i = CdeclCall(ACTOR_VALUE_ADDRESS, str); - *val = i < 77 ? i : -1; - return; - case kParamType_Axis: - if (strlen(str) == 1) { - const auto c = str[0] & 0xDF; - if (c < 'X' || c > 'Z') { - return; - } - *val = c; - *len = 1; - } - return; - case kParamType_AnimationGroup: - for (i = 0; i < 245 && StrCompare(g_animGroupInfos[i].name, str); i++) {} - *val = i < 245 ? i : -1; - return; - case kParamType_Sex: - if (!StrCompare(*reinterpret_cast(SEX_0), str)) { - *val = 0; - } - if (!StrCompare(*reinterpret_cast(SEX_1), str)) { - *val = 1; - } - return; - case kParamType_CrimeType: - if (IsStringInteger(str) && ((i = atoi(str)) <= 4)) { - *val = i; - } - return; - case kParamType_FormType: - for (auto& [formId, name] : g_formTypeNames) { - if (!StrCompare(name, str)) { - *val = formId; - } - } - return; - case kParamType_MiscellaneousStat: - i = CdeclCall(MISC_STAT, str); - *val = i < 43 ? i : -1; - return; - case kParamType_Alignment: - for (i = 0; (i < 5) && StrCompare(g_alignmentTypeNames[i], str); i++) {} - *val = i < 5 ? i : -1; - return; - case kParamType_EquipType: - for (i = 0; (i < 14) && StrCompare(g_equipTypeNames[i], str); i++) {} - *val = i < 14 ? i : -1; - return; - case kParamType_CriticalStage: - for (i = 0; (i < 5) && StrCompare(g_criticalStageNames[i], str); i++) {} - *val = i == 5 ? -1 : i; - return; - } -} - -#if EDITOR -constexpr UInt32 p_mapMarker = 0xEDDA34; -constexpr UInt32 g_isContainer = 0x63D740; -#else -constexpr UInt32 p_mapMarker = 0x11CA224; -constexpr UInt32 g_isContainer = 0x55D310; -#endif - -bool doesFormMatchParamType(TESForm* form, const ParamType type) -{ - if (!form) - return false; - - switch (type) - { - case kParamType_ObjectID: - if (!form || !kInventoryType[form->typeID]) - { - return false; - } - break; - case kParamType_ObjectRef: - if (!form || !DYNAMIC_CAST(form, TESForm, TESObjectREFR)) - { - return false; - } - break; - case kParamType_Actor: -#if EDITOR - if (!form || !form->IsActor_InEditor()) - { - return false; - } -#else - if (!form || !form->IsActor_Runtime()) - { - return false; - } -#endif - break; - case kParamType_MapMarker: - if (!form || NOT_ID(form, TESObjectREFR) || (((TESObjectREFR*)form)->baseForm != *(TESForm**)p_mapMarker)) - { - return false; - } - break; - case kParamType_Container: - if (!form || !DYNAMIC_CAST(form, TESForm, TESObjectREFR) || !ThisStdCall(g_isContainer, form)) - { - return false; - } - break; - case kParamType_SpellItem: - if (!form || (NOT_ID(form, SpellItem) && NOT_ID(form, TESObjectBOOK))) - { - return false; - } - break; - case kParamType_Cell: - if (!form || NOT_ID(form, TESObjectCELL) || !(((TESObjectCELL*)form)->cellFlags & 1)) - { - return false; - } - break; - case kParamType_MagicItem: - if (!form || !DYNAMIC_CAST(form, TESForm, MagicItem)) - { - return false; - } - break; - case kParamType_Sound: - if (!form || NOT_ID(form, TESSound)) - { - return false; - } - break; - case kParamType_Topic: - if (!form || NOT_ID(form, TESTopic)) - { - return false; - } - break; - case kParamType_Quest: - if (!form || NOT_ID(form, TESQuest)) - { - return false; - } - break; - case kParamType_Race: - if (!form || NOT_ID(form, TESRace)) - { - return false; - } - break; - case kParamType_Class: - if (!form || NOT_ID(form, TESClass)) - { - return false; - } - break; - case kParamType_Faction: - if (!form || NOT_ID(form, TESFaction)) - { - return false; - } - break; - case kParamType_Global: - if (!form || NOT_ID(form, TESGlobal)) - { - return false; - } - break; - case kParamType_Furniture: - if (!form || (NOT_ID(form, TESFurniture) && NOT_ID(form, BGSListForm))) - { - return false; - } - break; - case kParamType_TESObject: - if (!form || !DYNAMIC_CAST(form, TESForm, TESObject)) - { - return false; - } - break; - case kParamType_ActorBase: - if (!form || (NOT_ID(form, TESNPC) && NOT_ID(form, TESCreature))) - { - return false; - } - break; - case kParamType_WorldSpace: - if (!form || NOT_ID(form, TESWorldSpace)) - { - return false; - } - break; - case kParamType_AIPackage: - if (!form || NOT_ID(form, TESPackage)) - { - return false; - } - break; - case kParamType_CombatStyle: - if (!form || NOT_ID(form, TESCombatStyle)) - { - return false; - } - break; - case kParamType_MagicEffect: - if (!form || NOT_ID(form, EffectSetting)) - { - return false; - } - break; - case kParamType_WeatherID: - if (!form || NOT_ID(form, TESWeather)) - { - return false; - } - break; - case kParamType_NPC: - if (!form || NOT_ID(form, TESNPC)) - { - return false; - } - break; - case kParamType_Owner: - if (!form || (NOT_ID(form, TESFaction) && NOT_ID(form, TESNPC))) - { - return false; - } - break; - case kParamType_EffectShader: - if (!form || NOT_ID(form, TESEffectShader)) - { - return false; - } - break; - case kParamType_FormList: - if (!form || NOT_ID(form, BGSListForm)) - { - return false; - } - break; - case kParamType_MenuIcon: - if (!form || NOT_ID(form, BGSMenuIcon)) - { - return false; - } - break; - case kParamType_Perk: - if (!form || NOT_ID(form, BGSPerk)) - { - return false; - } - break; - case kParamType_Note: - if (!form || NOT_ID(form, BGSNote)) - { - return false; - } - break; - case kParamType_ImageSpaceModifier: - if (!form || NOT_ID(form, TESImageSpaceModifier)) - { - return false; - } - break; - case kParamType_ImageSpace: - if (!form || NOT_ID(form, TESImageSpace)) - { - return false; - } - break; - case kParamType_EncounterZone: - if (!form || NOT_ID(form, BGSEncounterZone)) - { - return false; - } - break; - case kParamType_IdleForm: - if (!form || NOT_ID(form, TESIdleForm)) - { - return false; - } - break; - case kParamType_Message: - if (!form || NOT_ID(form, BGSMessage)) - { - return false; - } - break; - case kParamType_InvObjOrFormList: - if (!form || (NOT_ID(form, BGSListForm) && !kInventoryType[form->typeID])) - { - return false; - } - break; - case kParamType_NonFormList: -#if RUNTIME - if (!form || NOT_ID(form, BGSListForm)) - { - return false; - } -#else - if (!form || (NOT_ID(form, BGSListForm) && !form->Unk_33())) - { - return false; - } -#endif - break; - case kParamType_SoundFile: - if (!form || NOT_ID(form, BGSMusicType)) - { - return false; - } - break; - case kParamType_LeveledOrBaseChar: - if (!form || (NOT_ID(form, TESNPC) && NOT_ID(form, TESLevCharacter))) - { - return false; - } - break; - case kParamType_LeveledOrBaseCreature: - if (!form || (NOT_ID(form, TESCreature) && NOT_ID(form, TESLevCreature))) - { - return false; - } - break; - case kParamType_LeveledChar: - if (!form || NOT_ID(form, TESLevCharacter)) - { - return false; - } - break; - case kParamType_LeveledCreature: - if (!form || NOT_ID(form, TESLevCreature)) - { - return false; - } - break; - case kParamType_LeveledItem: - if (!form || NOT_ID(form, TESLevItem)) - { - return false; - } - break; - case kParamType_AnyForm: - if (!form) - { - return false; - } - break; - case kParamType_Reputation: - if (!form || NOT_ID(form, TESReputation)) - { - return false; - } - break; - case kParamType_Casino: - if (!form || NOT_ID(form, TESCasino)) - { - return false; - } - break; - case kParamType_CasinoChip: - if (!form || NOT_ID(form, TESCasinoChips)) - { - return false; - } - break; - case kParamType_Challenge: - if (!form || NOT_ID(form, TESChallenge)) - { - return false; - } - break; - case kParamType_CaravanMoney: - if (!form || NOT_ID(form, TESCaravanMoney)) - { - return false; - } - break; - case kParamType_CaravanCard: - if (!form || NOT_ID(form, TESCaravanCard)) - { - return false; - } - break; - case kParamType_CaravanDeck: - if (!form || NOT_ID(form, TESCaravanDeck)) - { - return false; - } - break; - case kParamType_Region: - if (!form || NOT_ID(form, TESRegion)) - { - return false; - } - break; - } - - return true; -} diff --git a/nvse/nvse/NVSECompilerUtils.h b/nvse/nvse/NVSECompilerUtils.h deleted file mode 100644 index 9c817ff7..00000000 --- a/nvse/nvse/NVSECompilerUtils.h +++ /dev/null @@ -1,160 +0,0 @@ -#pragma once -#include - -#include "NVSELexer.h" -#include "ScriptTokens.h" -#include "ScriptUtils.h" - -// Define tokens -extern std::unordered_map tokenOpToNVSEOpType; - -inline Script::VariableType GetScriptTypeFromTokenType(NVSETokenType t) { - switch (t) { - case NVSETokenType::DoubleType: - return Script::eVarType_Float; - case NVSETokenType::IntType: - return Script::eVarType_Integer; - case NVSETokenType::RefType: - return Script::eVarType_Ref; - case NVSETokenType::ArrayType: - return Script::eVarType_Array; - case NVSETokenType::StringType: - return Script::eVarType_String; - default: - return Script::eVarType_Invalid; - } -} - -inline Script::VariableType GetScriptTypeFromToken(NVSEToken t) { - return GetScriptTypeFromTokenType(t.type); -} - -inline Token_Type GetBasicTokenType(Token_Type type) { - switch (type) { - case kTokenType_Array: - case kTokenType_ArrayVar: - return kTokenType_Array; - case kTokenType_Number: - case kTokenType_NumericVar: - return kTokenType_Number; - case kTokenType_String: - case kTokenType_StringVar: - return kTokenType_String; - case kTokenType_Ref: - case kTokenType_RefVar: - return kTokenType_Ref; - } - - return type; -} - -inline Token_Type GetDetailedTypeFromNVSEToken(NVSETokenType type) { - switch (type) { - case NVSETokenType::StringType: - return kTokenType_StringVar; - case NVSETokenType::ArrayType: - return kTokenType_ArrayVar; - case NVSETokenType::RefType: - return kTokenType_RefVar; - // Short - case NVSETokenType::DoubleType: - case NVSETokenType::IntType: - return kTokenType_NumericVar; - default: - return kTokenType_Ambiguous; - } -} - -inline Token_Type GetDetailedTypeFromVarType(Script::VariableType type) { - switch (type) { - case Script::eVarType_Float: - case Script::eVarType_Integer: - return kTokenType_Number; - case Script::eVarType_String: - return kTokenType_String; - case Script::eVarType_Array: - return kTokenType_Array; - case Script::eVarType_Ref: - return kTokenType_Ref; - case Script::eVarType_Invalid: - default: - return kTokenType_Invalid; - } -} - -inline Token_Type GetVariableTypeFromNonVarType(Token_Type type) { - switch (type) { - case kTokenType_Number: - return kTokenType_NumericVar; - case kTokenType_String: - return kTokenType_StringVar; - case kTokenType_Ref: - return kTokenType_RefVar; - case kTokenType_Array: - return kTokenType_ArrayVar; - default: - return kTokenType_Invalid; - } -} - -inline NVSEParamType GetParamTypeFromBasicTokenType(Token_Type tt) { - switch (tt) { - case kTokenType_Number: - return kNVSEParamType_Number; - case kTokenType_Form: - case kTokenType_Ref: - return kNVSEParamType_Form; - case kTokenType_Array: - return kNVSEParamType_Array; - case kTokenType_String: - return kNVSEParamType_String; - } - - throw std::runtime_error( - std::format("Type '{}' is not a basic type! Please report this as a bug.", TokenTypeToString(tt)) - ); -} - -inline bool CanUseDotOperator(Token_Type tt) { - if (tt == kTokenType_Form) { - return true; - } - - if (GetBasicTokenType(tt) == kTokenType_Ref) { - return true; - } - - if (tt == kTokenType_Ambiguous) { - return true; - } - - if (tt == kTokenType_ArrayElement) { - return true; - } - - return false; -} - -inline const char* GetBasicParamTypeString(NVSEParamType pt) { - switch (pt) { - case kNVSEParamType_Number: - return "Number"; - case kNVSEParamType_Form: - return "Form/Ref"; - case kNVSEParamType_Array: - return "Array"; - case kTokenType_String: - return "String"; - } - - return ""; -} - -void CompDbg(const char *fmt, ...); -void CompInfo(const char *fmt, ...); -void CompErr(const char *fmt, ...); - -bool isDefaultParse(Cmd_Parse parse); - -void resolveVanillaEnum(const ParamInfo* info, const char* str, uint32_t* val, uint32_t* len); -bool doesFormMatchParamType(TESForm* form, ParamType type); \ No newline at end of file diff --git a/nvse/nvse/NVSELexer.cpp b/nvse/nvse/NVSELexer.cpp deleted file mode 100644 index 7ead7d91..00000000 --- a/nvse/nvse/NVSELexer.cpp +++ /dev/null @@ -1,458 +0,0 @@ -#include "NVSELexer.h" - -#include -#include - -#include "NVSECompilerUtils.h" - - -NVSELexer::NVSELexer(const std::string& input) : pos(0) { - auto inputStr = std::string{ input }; - - // Replace tabs with 4 spaces - size_t it; - while ((it = inputStr.find('\t')) != std::string::npos) { - inputStr.replace(it, 1, " "); - } - - // Messy way of just getting string lines to start for later error reporting - std::string::size_type pos = 0; - std::string::size_type prev = 0; - while ((pos = inputStr.find('\n', prev)) != std::string::npos) { - lines.push_back(inputStr.substr(prev, pos - prev - 1)); - prev = pos + 1; - } - lines.push_back(inputStr.substr(prev)); - - this->input = inputStr; -} - -// Unused for now, working though -std::deque NVSELexer::lexString() { - std::stringstream resultString; - - auto start = pos - 1; - auto startLine = line; - auto startCol = column; - - std::deque results{}; - - while (pos < input.size()) { - char c = input[pos++]; - - if (c == '\\') { - if (pos >= input.size()) throw std::runtime_error("Unexpected end of input after escape character."); - if (char next = input[pos++]; next == '\\' || next == 'n' || next == '"') { - if (next == 'n') { - resultString << '\n'; - } - else { - resultString << next; - } - } - else { - throw std::runtime_error("Invalid escape sequence."); - } - } - else if (c == '\n') { - throw std::runtime_error("Unexpected end of line."); - } - else if (c == '"') { - if (line != startLine) { - throw std::runtime_error("Multiline strings are not allowed."); - } - - // Push final result as string - NVSEToken token{}; - token.type = NVSETokenType::String; - token.lexeme = '"' + resultString.str() + '"'; - token.value = resultString.str(); - token.line = line; - token.column = startCol; - results.push_back(token); - - // Update column - column = pos; - - return results; - } - else if (c == '$' && pos < input.size() && input[pos] == '{' && (pos == start + 1 || input[pos - 2] != '\\')) { - // Push previous result as string - results.push_back(MakeToken(NVSETokenType::String, '"' + resultString.str() + '"', resultString.str())); - - NVSEToken startInterpolate{}; - startInterpolate.type = NVSETokenType::Interp; - startInterpolate.lexeme = "${"; - startInterpolate.column = startCol + (pos - start); - startInterpolate.line = line; - results.push_back(startInterpolate); - - pos += 1; - NVSEToken tok; - while ((tok = GetNextToken(false)).type != NVSETokenType::RightBrace) { - if (tok.type == NVSETokenType::Eof) { - throw std::runtime_error("Unexpected end of file."); - } - results.push_back(tok); - } - - NVSEToken endInterpolate{}; - endInterpolate.type = NVSETokenType::EndInterp; - endInterpolate.lexeme = "}"; - endInterpolate.column = column - 1; - endInterpolate.line = line; - results.push_back(endInterpolate); - - resultString = {}; - startCol = pos; - } - else { - resultString << c; - } - } - - throw std::runtime_error("Unexpected end of input in string."); -} - -NVSEToken NVSELexer::GetNextToken(bool useStack) { - if (!tokenStack.empty() && useStack) { - auto tok = tokenStack.front(); - tokenStack.pop_front(); - return tok; - } - - // Skip over comments and whitespace in this block. - { - // Using an enum instead of two bools, since it's impossible to be in a singleline comment and a multiline comment at once. - enum class InWhatComment - { - None = 0, - SingleLine, - MultiLine - } inWhatComment = InWhatComment::None; - - while (pos < input.size()) { - if (!std::isspace(input[pos]) && inWhatComment == InWhatComment::None) { - if (pos < input.size() - 2 && input[pos] == '/') { - if (input[pos + 1] == '/') { - inWhatComment = InWhatComment::SingleLine; - pos += 2; - } - else if (input[pos + 1] == '*') - { - inWhatComment = InWhatComment::MultiLine; - pos += 2; - column += 2; // multiline comment could end on the same line it's declared on and have valid code after itself. - } - else { - break; // found start of next token - } - } - else { - break; // found start of next token - } - } - - if (inWhatComment == InWhatComment::MultiLine && - (pos < input.size() - 2) && input[pos] == '*' && input[pos + 1] == '/') - { - inWhatComment = InWhatComment::None; - pos += 2; - column += 2; // multiline comment could end on the same line it's declared on and have valid code after itself. - continue; // could be entering another comment right after this one; - // Don't want to reach the end of the loop and increment `pos` before that. - } - - if (input[pos] == '\n') { - line++; - column = 1; - if (inWhatComment == InWhatComment::SingleLine) { - inWhatComment = InWhatComment::None; - } - } - else if (input[pos] == '\r' && (pos < input.size() - 1) && input[pos + 1] == '\n') { - line++; - pos++; - column = 1; - if (inWhatComment == InWhatComment::SingleLine) { - inWhatComment = InWhatComment::None; - } - } - else { - column++; - } - - pos++; - } - } - - if (pos >= input.size()) { - return MakeToken(NVSETokenType::Eof, ""); - } - - char current = input[pos]; - if (std::isdigit(current)) { - int base = 10; - if (current == '0' && pos + 2 < input.size()) { - if (std::tolower(input[pos + 1]) == 'b') { - base = 2; - pos += 2; - current = input[pos]; - } else if (std::tolower(input[pos + 1]) == 'x') { - base = 16; - pos += 2; - current = input[pos]; - } - } - - size_t len; - double value; - try { - if (base == 16 || base == 2) { - value = std::stoll(&input[pos], &len, base); - } else { - value = std::stod(&input[pos], &len); - } - } catch (const std::invalid_argument &ex) { - throw std::runtime_error("Invalid numeric literal."); - } catch (const std::out_of_range &ex) { - throw std::runtime_error("Numeric literal value out of range."); - } - - if (input[pos + len - 1] == '.') { - len--; - } - pos += len; - column += len; - - // Handle 0b/0x - if (base == 2 || base == 16) { - len += 2; - } - - return MakeToken(NVSETokenType::Number, input.substr(pos - len, len), value); - } - - { - // Check if potential identifier has at least 1 alphabetical character. - // Must either start with underscores, or an alphabetical character. - bool isValidIdentifier = std::isalnum(current); - if (!isValidIdentifier && current == '_') { - size_t lookaheadPos = pos + 1; - while (lookaheadPos < input.size()) { - if (std::isalpha(input[lookaheadPos])) { - isValidIdentifier = true; - break; - } - else if (input[lookaheadPos] != '_') { - break; - } - ++lookaheadPos; - } - } - - if (isValidIdentifier) { - // Extract identifier - size_t start = pos; - while (pos < input.size() && (std::isalnum(input[pos]) || input[pos] == '_')) { - ++pos; - } - std::string identifier = input.substr(start, pos - start); - - // Keywords - if (_stricmp(identifier.c_str(), "if") == 0) return MakeToken(NVSETokenType::If, identifier); - if (_stricmp(identifier.c_str(), "else") == 0) return MakeToken(NVSETokenType::Else, identifier); - if (_stricmp(identifier.c_str(), "while") == 0) return MakeToken(NVSETokenType::While, identifier); - if (_stricmp(identifier.c_str(), "fn") == 0) return MakeToken(NVSETokenType::Fn, identifier); - if (_stricmp(identifier.c_str(), "return") == 0) return MakeToken(NVSETokenType::Return, identifier); - if (_stricmp(identifier.c_str(), "for") == 0) return MakeToken(NVSETokenType::For, identifier); - if (_stricmp(identifier.c_str(), "name") == 0) return MakeToken(NVSETokenType::Name, identifier); - if (_stricmp(identifier.c_str(), "continue") == 0) return MakeToken(NVSETokenType::Continue, identifier); - if (_stricmp(identifier.c_str(), "break") == 0) return MakeToken(NVSETokenType::Break, identifier); - if (_stricmp(identifier.c_str(), "in") == 0) return MakeToken(NVSETokenType::In, identifier); - if (_stricmp(identifier.c_str(), "not") == 0) return MakeToken(NVSETokenType::Not, identifier); - if (_stricmp(identifier.c_str(), "match") == 0) return MakeToken(NVSETokenType::Match, identifier); - - if (_stricmp(identifier.c_str(), "int") == 0) return MakeToken(NVSETokenType::IntType, identifier); - if (_stricmp(identifier.c_str(), "double") == 0) return MakeToken(NVSETokenType::DoubleType, identifier); - if (_stricmp(identifier.c_str(), "float") == 0) return MakeToken(NVSETokenType::DoubleType, identifier); - if (_stricmp(identifier.c_str(), "ref") == 0) return MakeToken(NVSETokenType::RefType, identifier); - if (_stricmp(identifier.c_str(), "string") == 0) return MakeToken(NVSETokenType::StringType, identifier); - if (_stricmp(identifier.c_str(), "array") == 0) return MakeToken(NVSETokenType::ArrayType, identifier); - - if (_stricmp(identifier.c_str(), "true") == 0) return MakeToken(NVSETokenType::Bool, identifier, 1); - if (_stricmp(identifier.c_str(), "false") == 0) return MakeToken(NVSETokenType::Bool, identifier, 0); - if (_stricmp(identifier.c_str(), "null") == 0) return MakeToken(NVSETokenType::Number, identifier, 0); - - - return MakeToken(NVSETokenType::Identifier, identifier); - } - } - - if (current == '"') { - pos++; - auto results = lexString(); - auto tok = results.front(); - results.pop_front(); - - for (auto r : results) { - tokenStack.push_back(r); - } - - return tok; - } - - pos++; - switch (current) { - // Operators - case '+': { - if (Match('=')) { - return MakeToken(NVSETokenType::PlusEq, "+="); - } - if (Match('+')) { - return MakeToken(NVSETokenType::PlusPlus, "++"); - } - return MakeToken(NVSETokenType::Plus, "+"); - } - case '-': { - if (Match('=')) { - return MakeToken(NVSETokenType::MinusEq, "-="); - } - if (Match('-')) { - return MakeToken(NVSETokenType::MinusMinus, "--"); - } - if (Match('>')) { - return MakeToken(NVSETokenType::Arrow, "->"); - } - return MakeToken(NVSETokenType::Minus, "-"); - } - case '*': - if (Match('=')) { - return MakeToken(NVSETokenType::StarEq, "*="); - } - return MakeToken(NVSETokenType::Star, "*"); - case '/': - if (Match('=')) { - return MakeToken(NVSETokenType::SlashEq, "/="); - } - return MakeToken(NVSETokenType::Slash, "/"); - case '%': - if (Match('=')) { - return MakeToken(NVSETokenType::ModEq, "%="); - } - return MakeToken(NVSETokenType::Mod, "%"); - case '^': - if (Match('=')) { - return MakeToken(NVSETokenType::PowEq, "^="); - } - return MakeToken(NVSETokenType::Pow, "^"); - case '=': - if (Match('=')) { - return MakeToken(NVSETokenType::EqEq, "=="); - } - return MakeToken(NVSETokenType::Eq, "="); - case '!': - if (Match('=')) { - return MakeToken(NVSETokenType::BangEq, "!="); - } - return MakeToken(NVSETokenType::Bang, "!"); - case '<': - if (Match('=')) { - return MakeToken(NVSETokenType::LessEq, "<="); - } - if (Match('<')) { - return MakeToken(NVSETokenType::LeftShift, "<<"); - } - return MakeToken(NVSETokenType::Less, "<"); - case '>': - if (Match('=')) { - return MakeToken(NVSETokenType::GreaterEq, ">="); - } - if (Match('>')) { - return MakeToken(NVSETokenType::RightShift, ">>"); - } - return MakeToken(NVSETokenType::Greater, ">"); - case '|': - if (Match('|')) { - return MakeToken(NVSETokenType::LogicOr, "||"); - } - if (Match('=')) { - return MakeToken(NVSETokenType::BitwiseOrEquals, "|="); - } - return MakeToken(NVSETokenType::BitwiseOr, "|"); - case '~': - return MakeToken(NVSETokenType::BitwiseNot, "~"); - case '&': - if (Match('&')) { - return MakeToken(NVSETokenType::LogicAnd, "&&"); - } - if (Match('=')) { - return MakeToken(NVSETokenType::BitwiseAndEquals, "&="); - } - return MakeToken(NVSETokenType::BitwiseAnd, "&"); - case '$': return MakeToken(NVSETokenType::Dollar, "$"); - case '#': return MakeToken(NVSETokenType::Pound, "#"); - - // Braces - case '{': return MakeToken(NVSETokenType::LeftBrace, "{"); - case '}': return MakeToken(NVSETokenType::RightBrace, "}"); - case '[': return MakeToken(NVSETokenType::LeftBracket, "["); - case ']': return MakeToken(NVSETokenType::RightBracket, "]"); - case '(': return MakeToken(NVSETokenType::LeftParen, "("); - case ')': return MakeToken(NVSETokenType::RightParen, ")"); - - // Misc - case ',': return MakeToken(NVSETokenType::Comma, ","); - case ';': return MakeToken(NVSETokenType::Semicolon, ";"); - case '?': return MakeToken(NVSETokenType::Ternary, "?"); - case ':': - if (Match(':')) { - return MakeToken(NVSETokenType::MakePair, "::"); - } - return MakeToken(NVSETokenType::Slice, ":"); - case '.': - return MakeToken(NVSETokenType::Dot, "."); - case '_': - return MakeToken(NVSETokenType::Underscore, "_"); - default: throw std::runtime_error("Unexpected character"); - } - - throw std::runtime_error("Unexpected character"); -} - -bool NVSELexer::Match(char c) { - if (pos >= input.size()) { - return false; - } - - if (input[pos] == c) { - pos++; - return true; - } - - return false; -} - -NVSEToken NVSELexer::MakeToken(NVSETokenType type, std::string lexeme) { - NVSEToken t(type, lexeme); - t.line = line; - t.column = column; - column += lexeme.length(); - return t; -} - -NVSEToken NVSELexer::MakeToken(NVSETokenType type, std::string lexeme, double value) { - NVSEToken t(type, lexeme, value); - t.line = line; - t.column = column; - column += lexeme.length(); - return t; -} - -NVSEToken NVSELexer::MakeToken(NVSETokenType type, std::string lexeme, std::string value) { - NVSEToken t(type, lexeme, value); - t.line = line; - t.column = column; - column += lexeme.length(); - return t; -} diff --git a/nvse/nvse/NVSELexer.h b/nvse/nvse/NVSELexer.h deleted file mode 100644 index f2b70dd5..00000000 --- a/nvse/nvse/NVSELexer.h +++ /dev/null @@ -1,211 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -enum class NVSETokenType { - // Keywords - If, - Else, - While, - Fn, - Return, - For, - Name, - Continue, - Break, - Export, - Match, - - // Types - IntType, - DoubleType, - RefType, - StringType, - ArrayType, - - // Operators - Plus, PlusEq, PlusPlus, - Minus, Negate, MinusEq, MinusMinus, - Star, StarEq, - Slash, SlashEq, - Mod, ModEq, - Pow, PowEq, - Eq, EqEq, - Less, Greater, - LessEq, - GreaterEq, - NotEqual, - Bang, BangEq, - LogicOr, - LogicOrEquals, - LogicAnd, - LogicAndEquals, - BitwiseOr, - BitwiseOrEquals, - BitwiseAnd, - BitwiseAndEquals, - LeftShift, - RightShift, - BitwiseNot, - Dollar, Pound, - Box, Unbox, - - // Braces - LeftBrace, RightBrace, - LeftBracket, RightBracket, - LeftParen, RightParen, - - // Literals - String, - Number, - Identifier, - Bool, - - // Misc - Comma, - Semicolon, - Ternary, - Slice, - Not, - In, - Eof, - Dot, - Interp, - EndInterp, - MakePair, - Arrow, - Underscore, -}; - -static const char* TokenTypeStr[]{ - // Keywords - "If", - "Else", - "While", - "Fn", - "Return", - "For", - "Name", - "Continue", - "Break", - "Export", - "Match", - - // Types - "IntType", - "DoubleType", - "RefType", - "StringType", - "ArrayType", - - // Operators - "Plus", - "PlusEq", - "PlusPlus", - "Minus", - "Negate", - "MinusEq", - "MinusMinus", - "Star", - "StarEq", - "Slash", - "SlashEq", - "Mod", - "ModEq", - "Pow", - "PowEq", - "Eq", - "EqEq", - "Less", - "Greater", - "LessEq", - "GreaterEq", - "NotEqual", - "Bang", - "BangEq", - "LogicOr", - "LogicOrEquals", - "LogicAnd", - "LogicAndEquals", - "BitwiseOr", - "BitwiseOrEquals", - "BitwiseAnd", - "BitwiseAndEquals", - "LeftShift", - "RightShift", - "BitwiseNot", - "Dollar", - "Pound", - - // These two get set by parser as they are context dependent - "Box", - "Unbox", - - // Braces - "LeftBrace", - "RightBrace", - "LeftBracket", - "RightBracket", - "LeftParen", - "RightParen", - - // Literals - "String", - "Number", - "Identifier", - "Bool", - - // Misc - "Comma", - "Semicolon", - "Ternary", - "Slice", - "Not", - "In", - "End", - "Dot", - "Interp", - "EndInterp", - "Arrow", - "Underscore", -}; - -struct NVSEToken { - NVSETokenType type; - std::variant value; - size_t line = 1; - size_t column = 0; - std::string lexeme; - - NVSEToken() : type(NVSETokenType::Eof), lexeme(""), value(std::monostate{}) {} - NVSEToken(NVSETokenType t) : type(t), lexeme(""), value(std::monostate{}) {} - NVSEToken(NVSETokenType t, std::string lexeme) : type(t), lexeme(lexeme), value(std::monostate{}) {} - NVSEToken(NVSETokenType t, std::string lexeme, double value) : type(t), lexeme(lexeme), value(value) {} - NVSEToken(NVSETokenType t, std::string lexeme, std::string value) : type(t), lexeme(lexeme), value(value) {} -}; - -class NVSELexer { - std::string input; - size_t pos; - - // For string interpolation - std::deque tokenStack{}; - -public: - size_t column = 1; - size_t line = 1; - std::vector lines{}; - - NVSELexer(const std::string& input); - std::deque lexString(); - - NVSEToken GetNextToken(bool useStack); - bool Match(char c); - NVSEToken MakeToken(NVSETokenType type, std::string lexeme); - NVSEToken MakeToken(NVSETokenType type, std::string lexeme, double value); - NVSEToken MakeToken(NVSETokenType type, std::string lexeme, std::string value); -}; \ No newline at end of file diff --git a/nvse/nvse/NVSEParser.cpp b/nvse/nvse/NVSEParser.cpp deleted file mode 100644 index 61b95bd6..00000000 --- a/nvse/nvse/NVSEParser.cpp +++ /dev/null @@ -1,1017 +0,0 @@ -#include "NVSEParser.h" -#include "NVSECompilerUtils.h" - -#include -#include - -#include "Hooks_Script.h" -#include "NVSETreePrinter.h" -#include "NVSEAst.h" -#include "PluginManager.h" - -NVSEParser::NVSEParser(NVSELexer& tokenizer) : lexer(tokenizer) { - Advance(); -} - -std::optional NVSEParser::Parse() { - NVSEToken name; - std::vector globals; - std::vector blocks{}; - auto& pluginVersions = g_currentCompilerPluginVersions.top(); - - CompDbg("==== PARSER ====\n\n"); - - try { - Expect(NVSETokenType::Name, "Expected 'name' as first statement of script."); - auto expr = Primary(); - if (!dynamic_cast(expr.get())) { - Error(previousToken, "Expected identifier."); - } - Expect(NVSETokenType::Semicolon, "Expected ';'."); - - name = dynamic_cast(expr.get())->token; - - while (currentToken.type != NVSETokenType::Eof) { - try { - // Version statements - // TODO: Extract this to a pre-processor - if (Match(NVSETokenType::Pound)) { - if (!Match(NVSETokenType::Identifier)) { - Error(currentToken, "Expected 'version'."); - } - - if (_stricmp(previousToken.lexeme.c_str(), "version")) { - Error(currentToken, "Expected 'version'."); - } - - Expect(NVSETokenType::LeftParen, "Expected '('."); - auto plugin = Expect(NVSETokenType::String, "Expected plugin name."); - Expect(NVSETokenType::Comma, "Expected ','."); - auto pluginName = std::get(plugin.value); - std::ranges::transform(pluginName.begin(), pluginName.end(), pluginName.begin(), [](unsigned char c) { return std::tolower(c); }); - if (StrEqual(pluginName.c_str(), "nvse")) { - auto major = static_cast(std::get(Expect(NVSETokenType::Number, "Expected major version").value)); - int minor = 255; - int beta = 255; - - if (Match(NVSETokenType::Comma)) { - minor = static_cast(std::get(Expect(NVSETokenType::Number, "Expected minor version").value)); - } - if (Match(NVSETokenType::Comma)) { - beta = static_cast(std::get(Expect(NVSETokenType::Number, "Expected beta version").value)); - } - pluginVersions[pluginName] = MAKE_NEW_VEGAS_VERSION(major, minor, beta); - } - else { // handle versions for plugins - auto pluginVersion = static_cast(std::get(Expect(NVSETokenType::Number, "Expected plugin version").value)); - auto* pluginInfo = g_pluginManager.GetInfoByName(pluginName.c_str()); - if (!pluginInfo) [[unlikely]] { - Error(std::format("No plugin with name {} could be found.\n", pluginName)); - } - pluginVersions[pluginName] = pluginVersion; - } - Expect(NVSETokenType::RightParen, "Expected ')'."); - } - // Get event or udf block - else if (PeekBlockType()) { - blocks.emplace_back(Begin()); - } - else if (Match(NVSETokenType::Fn)) { - auto fnToken = previousToken; - //auto ident = Expect(NVSETokenType::Identifier, "Expected identifier."); - auto args = ParseArgs(); - auto fnDecl = std::make_shared(fnToken, std::move(args), BlockStatement()); - blocks.emplace_back(fnDecl); - } - else if (Peek(NVSETokenType::IntType) || Peek(NVSETokenType::DoubleType) || Peek(NVSETokenType::RefType) || - Peek(NVSETokenType::ArrayType) || Peek(NVSETokenType::StringType)) - { - globals.emplace_back(VarDecl()); - Expect(NVSETokenType::Semicolon, "Expected ';' at end of statement."); - } - else { - Error(currentToken, "Expected variable declaration, block type (GameMode, MenuMode, ...), or 'fn'."); - } - } - catch (NVSEParseError& e) { - hadError = true; - CompErr("%s\n", e.what()); - Synchronize(); - } - } - } - catch (NVSEParseError& e) { - hadError = true; - CompErr("%s\n", e.what()); - } - - if (hadError) { - return std::optional{}; - } - - return NVSEScript(name, std::move(globals), std::move(blocks)); -} - -StmtPtr NVSEParser::Begin() { - ExpectBlockType("Expected block type"); - - auto blockName = previousToken; - auto blockNameStr = blockName.lexeme; - - std::optional mode{}; - CommandInfo* beginInfo = nullptr; - for (auto& info : g_eventBlockCommandInfos) { - if (!_stricmp(info.longName, blockNameStr.c_str())) { - beginInfo = &info; - break; - } - } - - if (Match(NVSETokenType::MakePair)) { - if (beginInfo->numParams == 0) { - Error(currentToken, "Cannot specify argument for this block type."); - } - - if (Match(NVSETokenType::Number)) { - if (beginInfo->params[0].typeID != kParamType_Integer) { - Error(currentToken, "Expected identifier."); - } - - auto modeToken = previousToken; - if (modeToken.lexeme.contains('.')) { - Error(currentToken, "Expected integer."); - } - - mode = modeToken; - } - else if (Match(NVSETokenType::Identifier)) { - if (beginInfo->params[0].typeID == kParamType_Integer) { - Error(currentToken, "Expected number."); - } - - mode = previousToken; - } - else { - Error(currentToken, "Expected identifier or number."); - } - } - else { - if (beginInfo->numParams > 0 && !beginInfo->params[0].isOptional) { - Error(currentToken, "Missing required parameter for block type '" + blockName.lexeme + "'"); - } - } - - if (!Peek(NVSETokenType::LeftBrace)) { - Error(currentToken, "Expected '{'."); - } - return std::make_shared(blockName, mode, BlockStatement(), beginInfo); -} - -StmtPtr NVSEParser::Statement() { - if (Peek(NVSETokenType::For)) { - return ForStatement(); - } - - if (Peek(NVSETokenType::If)) { - return IfStatement(); - } - - if (Peek(NVSETokenType::Match)) { - return MatchStatement(); - } - - if (Peek(NVSETokenType::Return)) { - return ReturnStatement(); - } - - if (Match(NVSETokenType::Break)) { - auto token = previousToken; - Expect(NVSETokenType::Semicolon, "Expected ';' at end of statement."); - return std::make_shared(previousToken); - } - - if (Match(NVSETokenType::Continue)) { - auto token = previousToken; - Expect(NVSETokenType::Semicolon, "Expected ';' at end of statement."); - return std::make_shared(previousToken); - } - - if (Peek(NVSETokenType::While)) { - return WhileStatement(); - } - - if (Peek(NVSETokenType::LeftBrace)) { - return BlockStatement(); - } - - if (PeekType()) { - auto expr = VarDecl(); - Expect(NVSETokenType::Semicolon, "Expected ';' at end of statement."); - return expr; - } - - return ExpressionStatement(); -} - -std::shared_ptr NVSEParser::VarDecl(bool allowValue, bool allowOnlyOneVarDecl) { - if (!MatchesType()) { - Error(currentToken, "Expected type."); - } - auto varType = previousToken; - std::vector> declarations{}; - do { - auto name = Expect(NVSETokenType::Identifier, "Expected identifier."); - ExprPtr value{nullptr}; - - if (Match(NVSETokenType::Eq)) { - if (!allowValue) { - Error(previousToken, "Variable definition is not allowed here."); - } - value = Expression(); - } - - declarations.emplace_back(name, value); - if (allowOnlyOneVarDecl) { - break; - } - } - while (Match(NVSETokenType::Comma)); - - return std::make_shared(varType, std::move(declarations)); -} - -StmtPtr NVSEParser::ForStatement() { - Match(NVSETokenType::For); - Expect(NVSETokenType::LeftParen, "Expected '(' after 'for'."); - - // Optional expression - bool forEach = false; - StmtPtr init = {nullptr}; - if (!Peek(NVSETokenType::Semicolon)) { - // for (array aIter in [1::1, 2::2]).. - if (MatchesType()) { - auto type = previousToken; - auto ident = Expect(NVSETokenType::Identifier, "Expected identifier."); - - if (Match(NVSETokenType::In)) { - auto decl = std::make_shared(type, ident, nullptr); - - ExprPtr rhs = Expression(); - Expect(NVSETokenType::RightParen, "Expected ')'."); - - std::shared_ptr block = BlockStatement(); - return std::make_shared(std::vector{ decl }, std::move(rhs), std::move(block), false); - } - - ExprPtr value{nullptr}; - if (Match(NVSETokenType::Eq)) { - value = Expression(); - } - Expect(NVSETokenType::Semicolon, "Expected ';' after loop initializer."); - init = std::make_shared(type, ident, value); - } - // for ([int key, int value] in [1::1, 2::2]). - // for ([_, int value] in [1::1, 2::2]). - // for ([int key, _] in [1::1, 2::2]). - // for ([int key] in [1::1, 2::2]). - // for ([int value] in [1, 2]). - else if (Match(NVSETokenType::LeftBracket)) { - std::vector> decls{}; - - // LHS - if (Match(NVSETokenType::Underscore)) { - decls.push_back(nullptr); - } else { - decls.push_back(VarDecl(false, true)); - } - - // RHS - if (Match(NVSETokenType::Comma)) { - if (Match(NVSETokenType::Underscore)) { - decls.push_back(nullptr); - } - else { - decls.push_back(VarDecl(false, true)); - } - } - - Expect(NVSETokenType::RightBracket, "Expected ']'."); - Expect(NVSETokenType::In, "Expected 'in'."); - - ExprPtr rhs = Expression(); - Expect(NVSETokenType::RightParen, "Expected ')'."); - - std::shared_ptr block = BlockStatement(); - return std::make_shared(decls, std::move(rhs), std::move(block), true); - } - else { - init = Statement(); - if (!init->IsType()) { - Error(currentToken, "Expected variable declaration or assignment."); - } - } - } - - // Default to true condition - ExprPtr cond = std::make_shared(NVSEToken{NVSETokenType::Bool, "true", 1}, true); - if (!Peek(NVSETokenType::Semicolon)) { - cond = Expression(); - Expect(NVSETokenType::Semicolon, "Expected ';' after loop condition."); - } - - ExprPtr incr = {nullptr}; - if (!Peek(NVSETokenType::RightParen)) { - incr = Expression(); - } - Expect(NVSETokenType::RightParen, "Expected ')'."); - - std::shared_ptr block = BlockStatement(); - return std::make_shared(std::move(init), std::move(cond), std::move(incr), std::move(block)); -} - -StmtPtr NVSEParser::IfStatement() { - Match(NVSETokenType::If); - auto token = previousToken; - - Expect(NVSETokenType::LeftParen, "Expected '(' after 'if'."); - auto cond = Expression(); - Expect(NVSETokenType::RightParen, "Expected ')' after 'if' condition."); - auto block = BlockStatement(); - StmtPtr elseBlock = nullptr; - if (Match(NVSETokenType::Else)) { - if (Peek(NVSETokenType::If)) { - std::vector statements{}; - statements.emplace_back(IfStatement()); - elseBlock = std::make_shared(std::move(statements)); - } - else { - elseBlock = BlockStatement(); - } - } - - return std::make_shared(token, std::move(cond), std::move(block), std::move(elseBlock)); -} - -StmtPtr NVSEParser::MatchStatement() { - Match(NVSETokenType::Match); - - Expect(NVSETokenType::LeftParen, "Expected '(' after 'match'."); - auto cond = Expression(); - Expect(NVSETokenType::RightParen, "Expected ')' after 'match' expression."); - - Expect(NVSETokenType::LeftBrace, "Expected '{' after 'match' declaration"); - - std::vector> matchCases{}; - std::shared_ptr defaultCase{}; - - do { - if (Match(NVSETokenType::Underscore)) { - if (defaultCase) { - Error(currentToken, "Only one default case can be specified."); - } - - Expect(NVSETokenType::Arrow, "Expected '->' after match case value"); - defaultCase = BlockStatement(); - continue; - } - - auto caseVal = Expression(); - auto eqExpr = std::make_shared(NVSEToken{ NVSETokenType::EqEq, "==" }, cond, caseVal); - Expect(NVSETokenType::Arrow, "Expected '->' after match case value"); - - const auto curIfStmt = std::make_shared( - currentToken, - std::move(eqExpr), - BlockStatement(), - nullptr - ); - - matchCases.push_back(curIfStmt); - - const auto caseCount = matchCases.size(); - if (caseCount > 1) { - matchCases[caseCount - 1]->elseBlock = std::make_shared(std::vector{ curIfStmt }); - } - } while (!Peek(NVSETokenType::RightBrace)); - - if (defaultCase) { - matchCases[matchCases.size() - 1]->elseBlock = defaultCase; - } - - Expect(NVSETokenType::RightBrace, "Expected '}'"); - - return matchCases[0]; -} - -StmtPtr NVSEParser::ReturnStatement() { - Match(NVSETokenType::Return); - auto token = previousToken; - if (Match(NVSETokenType::Semicolon)) { - return std::make_shared(token, nullptr); - } - - auto expr = std::make_shared(token, Expression()); - Expect(NVSETokenType::Semicolon, "Expected ';' at end of statement."); - return expr; -} - -StmtPtr NVSEParser::WhileStatement() { - Match(NVSETokenType::While); - auto token = previousToken; - - Expect(NVSETokenType::LeftParen, "Expected '(' after 'while'."); - auto cond = Expression(); - Expect(NVSETokenType::RightParen, "Expected ')' after 'while' condition."); - auto block = BlockStatement(); - - return std::make_shared(token, std::move(cond), std::move(block)); -} - -std::shared_ptr NVSEParser::BlockStatement() { - Expect(NVSETokenType::LeftBrace, "Expected '{'."); - - std::vector statements{}; - while (!Match(NVSETokenType::RightBrace)) { - try { - statements.emplace_back(Statement()); - } - catch (NVSEParseError e) { - hadError = true; - CompErr("%s\n", e.what()); - Synchronize(); - - if (currentToken.type == NVSETokenType::Eof) { - break; - } - } - } - - return std::make_shared(std::move(statements)); -} - -StmtPtr NVSEParser::ExpressionStatement() { - // Allow empty expression statements - if (Match(NVSETokenType::Semicolon)) { - return std::make_shared(nullptr); - } - - auto expr = Expression(); - if (!Match(NVSETokenType::Semicolon)) { - Error(previousToken, "Expected ';' at end of statement."); - } - return std::make_shared(std::move(expr)); -} - -ExprPtr NVSEParser::Expression() { - return Assignment(); -} - -ExprPtr NVSEParser::Assignment() { - ExprPtr left = Slice(); - - if (Match(NVSETokenType::Eq) || Match(NVSETokenType::PlusEq) || Match(NVSETokenType::MinusEq) || - Match(NVSETokenType::StarEq) || Match(NVSETokenType::SlashEq) || Match(NVSETokenType::ModEq) || Match( - NVSETokenType::PowEq) || Match(NVSETokenType::BitwiseOrEquals) || Match(NVSETokenType::BitwiseAndEquals)) { - auto token = previousToken; - - const auto prevTok = previousToken; - ExprPtr value = Assignment(); - - if (left->IsType() || left->IsType() || left->IsType()) { - return std::make_shared(token, std::move(left), std::move(value)); - } - - Error(prevTok, "Invalid assignment target."); - } - - return left; -} - -ExprPtr NVSEParser::Slice() { - ExprPtr left = Ternary(); - - while (Match(NVSETokenType::Slice)) { - const auto op = previousToken; - ExprPtr right = Ternary(); - left = std::make_shared(op, std::move(left), std::move(right)); - } - - return left; -} - -ExprPtr NVSEParser::Ternary() { - ExprPtr cond = LogicalOr(); - - while (Match(NVSETokenType::Ternary)) { - auto token = previousToken; - - ExprPtr left; - if (Match(NVSETokenType::Slice)) { - left = cond; - } else { - left = LogicalOr(); - Expect(NVSETokenType::Slice, "Expected ':'."); - } - - auto right = LogicalOr(); - cond = std::make_shared(token, std::move(cond), std::move(left), std::move(right)); - } - - return cond; -} - -ExprPtr NVSEParser::LogicalOr() { - ExprPtr left = LogicalAnd(); - - while (Match(NVSETokenType::LogicOr)) { - auto op = previousToken; - ExprPtr right = LogicalAnd(); - left = std::make_shared(op, std::move(left), std::move(right)); - } - - return left; -} - -ExprPtr NVSEParser::LogicalAnd() { - ExprPtr left = Equality(); - - while (Match(NVSETokenType::LogicAnd)) { - const auto op = previousToken; - ExprPtr right = Equality(); - left = std::make_shared(op, std::move(left), std::move(right)); - } - - return left; -} - -ExprPtr NVSEParser::Equality() { - ExprPtr left = Comparison(); - - while (Match(NVSETokenType::EqEq) || Match(NVSETokenType::BangEq)) { - const auto op = previousToken; - ExprPtr right = Comparison(); - left = std::make_shared(op, std::move(left), std::move(right)); - } - - return left; -} - -ExprPtr NVSEParser::Comparison() { - ExprPtr left = In(); - - while (Match(NVSETokenType::Less) || Match(NVSETokenType::LessEq) || Match(NVSETokenType::Greater) || Match( - NVSETokenType::GreaterEq)) { - const auto op = previousToken; - ExprPtr right = In(); - left = std::make_shared(op, std::move(left), std::move(right)); - } - - return left; -} - -ExprPtr NVSEParser::In() { - ExprPtr left = BitwiseOr(); - - bool isNot = Match(NVSETokenType::Not); - if (Match(NVSETokenType::In)) { - const auto op = previousToken; - - if (Match(NVSETokenType::LeftBracket)) { - std::vector values{}; - while (!Match(NVSETokenType::RightBracket)) { - if (!values.empty()) { - Expect(NVSETokenType::Comma, "Expected ','."); - } - values.emplace_back(Expression()); - } - - return std::make_shared(left, op, values, isNot); - } - - auto expr = Expression(); - return std::make_shared(left, op, expr, isNot); - } - - if (isNot) { - Error(currentToken, "Expected 'in'."); - } - - return left; -} - -ExprPtr NVSEParser::BitwiseOr() { - ExprPtr left = BitwiseAnd(); - - while (Match(NVSETokenType::BitwiseOr)) { - const auto op = previousToken; - ExprPtr right = BitwiseAnd(); - left = std::make_shared(op, std::move(left), std::move(right)); - } - - return left; -} - -ExprPtr NVSEParser::BitwiseAnd() { - ExprPtr left = Shift(); - - while (Match(NVSETokenType::BitwiseAnd)) { - const auto op = previousToken; - ExprPtr right = Shift(); - left = std::make_shared(op, std::move(left), std::move(right)); - } - - return left; -} - -ExprPtr NVSEParser::Shift() { - ExprPtr left = Term(); - - while (Match(NVSETokenType::LeftShift) || Match(NVSETokenType::RightShift)) { - const auto op = previousToken; - ExprPtr right = Term(); - left = std::make_shared(op, std::move(left), std::move(right)); - } - - return left; -} - -// term -> factor ((+ | -) factor)?; -ExprPtr NVSEParser::Term() { - ExprPtr left = Factor(); - - while (Match(NVSETokenType::Plus) || Match(NVSETokenType::Minus)) { - const auto op = previousToken; - ExprPtr right = Factor(); - left = std::make_shared(op, std::move(left), std::move(right)); - } - - return left; -} - -ExprPtr NVSEParser::Factor() { - ExprPtr left = Pair(); - - while (Match(NVSETokenType::Star) || Match(NVSETokenType::Slash) || Match(NVSETokenType::Mod) || Match( - NVSETokenType::Pow)) { - const auto op = previousToken; - ExprPtr right = Pair(); - left = std::make_shared(op, std::move(left), std::move(right)); - } - - return left; -} - -ExprPtr NVSEParser::Pair() { - ExprPtr left = Unary(); - - while (Match(NVSETokenType::MakePair)) { - auto op = previousToken; - ExprPtr right = Unary(); - left = std::make_shared(op, std::move(left), std::move(right)); - } - - return left; -} - -ExprPtr NVSEParser::Unary() { - if (Match(NVSETokenType::Bang) || Match(NVSETokenType::Minus) || Match(NVSETokenType::Dollar) || Match( - NVSETokenType::Pound) || Match(NVSETokenType::BitwiseAnd) || Match(NVSETokenType::Star) || Match(NVSETokenType::BitwiseNot)) { - auto op = previousToken; - ExprPtr right = Unary(); - - // Convert these two in case of box/unbox - if (op.type == NVSETokenType::BitwiseAnd) { - op.type = NVSETokenType::Box; - } - - if (op.type == NVSETokenType::Star) { - op.type = NVSETokenType::Unbox; - } - - if (op.type == NVSETokenType::Minus) { - op.type = NVSETokenType::Negate; - } - - return std::make_shared(op, std::move(right), false); - } - - return Postfix(); -} - -ExprPtr NVSEParser::Postfix() { - ExprPtr expr = Call(); - - while (Match(NVSETokenType::LeftBracket)) { - auto token = previousToken; - auto inner = Expression(); - Expect(NVSETokenType::RightBracket, "Expected ']'."); - - expr = std::make_shared(token, std::move(expr), std::move(inner)); - } - - if (Match(NVSETokenType::PlusPlus) || Match(NVSETokenType::MinusMinus)) { - if (!expr->IsType() && !expr->IsType()) { - Error(previousToken, "Invalid operand for '++', expected identifier."); - } - - auto op = previousToken; - return std::make_shared(op, std::move(expr), true); - } - - return expr; -} - -ExprPtr NVSEParser::Call() { - ExprPtr expr = Primary(); - - while (Match(NVSETokenType::Dot) || Match(NVSETokenType::LeftParen)) { - if (previousToken.type == NVSETokenType::Dot) { - if (!expr->IsType() && - !expr->IsType() && - !expr->IsType() && - !expr->IsType()) { - Error(currentToken, "Invalid member access."); - } - - auto token = previousToken; - const auto ident = Expect(NVSETokenType::Identifier, "Expected identifier."); - - if (Match(NVSETokenType::LeftParen)) { - std::vector args{}; - while (!Match(NVSETokenType::RightParen)) { - args.emplace_back(std::move(Expression())); - - if (!Peek(NVSETokenType::RightParen) && !Match(NVSETokenType::Comma)) { - Error(currentToken, "Expected ',' or ')'."); - } - } - expr = std::make_shared(std::move(expr), ident, std::move(args)); - } - else { - expr = std::make_shared(token, std::move(expr), ident); - } - } - else { - // Can only call on ident or get expr - if (!expr->IsType()) { - Error(currentToken, "Invalid callee."); - } - - auto ident = dynamic_cast(expr.get())->token; - - std::vector args{}; - while (!Match(NVSETokenType::RightParen)) { - args.emplace_back(std::move(Expression())); - - if (!Peek(NVSETokenType::RightParen) && !Match(NVSETokenType::Comma)) { - Error(currentToken, "Expected ',' or ')'."); - } - } - expr = std::make_shared(nullptr, ident, std::move(args)); - } - } - - return expr; -} - -ExprPtr NVSEParser::Primary() { - if (Match(NVSETokenType::Bool)) { - return std::make_shared(previousToken, std::get(previousToken.value)); - } - - if (Match(NVSETokenType::Number)) { - // Double literal - if (std::ranges::find(previousToken.lexeme, '.') != previousToken.lexeme.end()) { - return std::make_shared(previousToken, std::get(previousToken.value), true); - } - - // Int literal - return std::make_shared(previousToken, floor(std::get(previousToken.value)), false); - } - - if (Match(NVSETokenType::String)) { - ExprPtr expr = std::make_shared(previousToken); - while (Match(NVSETokenType::Interp)) { - auto inner = std::make_shared(NVSEToken{NVSETokenType::Dollar, "$"}, Expression(), false); - Expect(NVSETokenType::EndInterp, "Expected '}'"); - expr = std::make_shared(NVSEToken{NVSETokenType::Plus, "+"}, expr, inner); - if (Match(NVSETokenType::String) && previousToken.lexeme.length() > 2) { - auto endStr = std::make_shared(previousToken); - expr = std::make_shared(NVSEToken{NVSETokenType::Plus, "+"}, expr, endStr); - } - } - return expr; - } - - if (Match(NVSETokenType::Identifier)) { - return std::make_shared(previousToken); - } - - if (Match(NVSETokenType::LeftParen)) { - ExprPtr expr = Expression(); - Expect(NVSETokenType::RightParen, "Expected ')' after expression."); - return std::make_shared(std::move(expr)); - } - - if (Peek(NVSETokenType::LeftBracket)) { - return ArrayLiteral(); - } - - if (Peek(NVSETokenType::LeftBrace)) { - return MapLiteral(); - } - - if (Match(NVSETokenType::Fn)) { - return FnExpr(); - } - - Error(currentToken, "Expected expression."); - return ExprPtr{nullptr}; -} - -// Only called when 'fn' token is matched -ExprPtr NVSEParser::FnExpr() { - auto token = previousToken; - auto args = ParseArgs(); - - if (Match(NVSETokenType::Arrow)) { - // Build call stmt - auto callExpr = std::make_shared(nullptr, NVSEToken{NVSETokenType::Identifier, "SetFunctionValue"}, - std::vector{Expression()}); - StmtPtr exprStmt = std::make_shared(callExpr); - auto block = std::make_shared(std::vector{ exprStmt }); - return std::make_shared(token, std::move(args), block); - } - - return std::make_shared(token, std::move(args), BlockStatement()); -} - -ExprPtr NVSEParser::ArrayLiteral() { - Expect(NVSETokenType::LeftBracket, "Expected '['."); - const auto tok = previousToken; - - std::vector values{}; - while (!Match(NVSETokenType::RightBracket)) { - if (!values.empty()) { - Expect(NVSETokenType::Comma, "Expected ','."); - } - values.emplace_back(Expression()); - } - - return std::make_shared(tok, values); -} - -ExprPtr NVSEParser::MapLiteral() { - Expect(NVSETokenType::LeftBrace, "Expected '{'."); - const auto tok = previousToken; - - std::vector values{}; - while (!Match(NVSETokenType::RightBrace)) { - if (!values.empty()) { - Expect(NVSETokenType::Comma, "Expected ','."); - } - values.emplace_back(Expression()); - } - - return std::make_shared(tok, values); -} - -std::vector> NVSEParser::ParseArgs() { - Expect(NVSETokenType::LeftParen, "Expected '(' after 'fn'."); - - std::vector> args{}; - while (!Match(NVSETokenType::RightParen)) { - if (!Match(NVSETokenType::IntType) && !Match(NVSETokenType::DoubleType) && !Match(NVSETokenType::RefType) && - !Match(NVSETokenType::ArrayType) && !Match(NVSETokenType::StringType)) { - Error(currentToken, "Expected type."); - } - auto type = previousToken; - auto ident = Expect(NVSETokenType::Identifier, "Expected identifier."); - auto decl = std::make_shared(type, ident, nullptr); - - if (!Peek(NVSETokenType::RightParen) && !Match(NVSETokenType::Comma)) { - Error(currentToken, "Expected ',' or ')'."); - } - - args.emplace_back(std::move(decl)); - } - - return args; -} - -void NVSEParser::Advance() { - previousToken = currentToken; - // Bubble up lexer errors - try { - currentToken = lexer.GetNextToken(true); - } - catch (std::runtime_error& er) { - CompErr("[line %d] %s\n", lexer.line, er.what()); - } -} - -bool NVSEParser::Match(NVSETokenType type) { - if (currentToken.type == type) { - Advance(); - return true; - } - - return false; -} - -bool NVSEParser::MatchesType() { - return Match(NVSETokenType::IntType) || Match(NVSETokenType::DoubleType) || Match(NVSETokenType::RefType) || - Match(NVSETokenType::ArrayType) || Match(NVSETokenType::StringType); -} - -bool NVSEParser::Peek(NVSETokenType type) const { - if (currentToken.type == type) { - return true; - } - - return false; -} - -bool NVSEParser::PeekType() const { - return Peek(NVSETokenType::IntType) || Peek(NVSETokenType::DoubleType) || Peek(NVSETokenType::RefType) || - Peek(NVSETokenType::ArrayType) || Peek(NVSETokenType::StringType); -} - -bool NVSEParser::PeekBlockType() const { - if (!Peek(NVSETokenType::Identifier)) { - return false; - } - - const auto identifier = currentToken.lexeme; - for (const auto& g_eventBlockCommandInfo : g_eventBlockCommandInfos) { - if (!_stricmp(g_eventBlockCommandInfo.longName, identifier.c_str())) { - return true; - } - } - - return false; -} - -NVSEToken NVSEParser::ExpectBlockType(const std::string &&message) { - if (!PeekBlockType()) { - Error(currentToken, message); - return previousToken; - } - - const auto identifier = currentToken.lexeme; - for (const auto& g_eventBlockCommandInfo : g_eventBlockCommandInfos) { - if (!_stricmp(g_eventBlockCommandInfo.longName, identifier.c_str())) { - Advance(); - return previousToken; - } - } - - Error(currentToken, message); - return previousToken; -} - -void NVSEParser::Error(std::string message) { - panicMode = true; - hadError = true; - - throw NVSEParseError(std::format("Error: {}", message)); -} - -void NVSEParser::Error(NVSEToken token, std::string message) { - panicMode = true; - hadError = true; - - throw NVSEParseError(std::format("[line {}:{}] {}", token.line, token.column, message)); -} - -NVSEToken NVSEParser::Expect(NVSETokenType type, std::string message) { - if (!Match(type)) { - Error(currentToken, message); - } - - return previousToken; -} - -void NVSEParser::Synchronize() { - Advance(); - - while (currentToken.type != NVSETokenType::Eof) { - if (previousToken.type == NVSETokenType::Semicolon) { - panicMode = false; - return; - } - - switch (currentToken.type) { - case NVSETokenType::If: - case NVSETokenType::While: - case NVSETokenType::For: - case NVSETokenType::Return: - case NVSETokenType::RightBrace: - panicMode = false; - return; - default: ; - } - - Advance(); - } -} diff --git a/nvse/nvse/NVSEParser.h b/nvse/nvse/NVSEParser.h deleted file mode 100644 index 82731af6..00000000 --- a/nvse/nvse/NVSEParser.h +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -#include "NVSELexer.h" -#include "NVSEAst.h" - -#include -#include - -class NVSEParseError : public std::runtime_error { -public: - NVSEParseError(const std::string& message) : std::runtime_error(message) {}; -}; - -class NVSEParser { -public: - NVSEParser(NVSELexer& tokenizer); - std::optional Parse(); - -private: - NVSELexer& lexer; - NVSEToken currentToken; - NVSEToken previousToken; - bool panicMode = false; - bool hadError = false; - - std::shared_ptr FnDecl(); - std::shared_ptr VarDecl(bool allowValue = true, bool allowOnlyOneVarDecl = false); - - StmtPtr Begin(); - StmtPtr Statement(); - StmtPtr ExpressionStatement(); - StmtPtr ForStatement(); - StmtPtr IfStatement(); - StmtPtr MatchStatement(); - StmtPtr ReturnStatement(); - StmtPtr WhileStatement(); - std::shared_ptr BlockStatement(); - - ExprPtr Expression(); - ExprPtr Assignment(); - ExprPtr Ternary(); - ExprPtr Pair(); - ExprPtr LogicalOr(); - ExprPtr LogicalAnd(); - ExprPtr Slice(); - ExprPtr Equality(); - ExprPtr Comparison(); - ExprPtr BitwiseOr(); - ExprPtr BitwiseAnd(); - ExprPtr Shift(); - ExprPtr In(); - ExprPtr Term(); - ExprPtr Factor(); - ExprPtr Unary(); - ExprPtr Postfix(); - ExprPtr Call(); - ExprPtr FnExpr(); - ExprPtr ArrayLiteral(); - ExprPtr MapLiteral(); - std::vector> ParseArgs(); - ExprPtr Primary(); - - void Advance(); - bool Match(NVSETokenType type); - bool MatchesType(); - bool Peek(NVSETokenType type) const; - bool PeekType() const; - - bool PeekBlockType() const; - NVSEToken ExpectBlockType(const std::string&& message); - - void Error(std::string message); - void Error(NVSEToken token, std::string message); - NVSEToken Expect(NVSETokenType type, std::string message); - void Synchronize(); -}; diff --git a/nvse/nvse/NVSEScope.h b/nvse/nvse/NVSEScope.h deleted file mode 100644 index 180674d0..00000000 --- a/nvse/nvse/NVSEScope.h +++ /dev/null @@ -1,115 +0,0 @@ -#pragma once -#include - -#include "NVSECompilerUtils.h" -#include "NVSELexer.h" -#include "ScriptTokens.h" - -class NVSEScope { -public: - struct ScopeVar { - Token_Type detailedType; - Script::VariableType variableType; - - NVSEToken token; - uint32_t index; - uint32_t scopeIndex; - - bool isGlobal = false; - - // Set for lambdas - struct { - bool isLambda; - std::vector paramTypes{}; - Token_Type returnType = kTokenType_Invalid; - } lambdaTypeInfo; - - // Used for renaming global variables in certain scopes - // export keyword will NOT rename a global var - // fn (int x) ... WILL rename x, so that x can be reused for other lambdas - std::string rename {}; - - std::string GetName() { - return rename.empty() ? token.lexeme : rename; - } - }; - -private: - uint32_t scopeIndex{}; - std::shared_ptr parent{}; - std::unordered_map> m_scopeVars{}; - -protected: - uint32_t incrementVarIndex() { - if (parent) { - return parent->incrementVarIndex(); - } - - return varIndex++; - } - -public: - uint32_t varIndex{ 1 }; - - std::map allVars{}; - - NVSEScope (uint32_t scopeIndex, std::shared_ptr parent, bool isLambda = false) - : scopeIndex(scopeIndex), parent(parent) {} - - std::shared_ptr resolveVariable(std::string name, bool checkParent = true) { - // Lowercase name - std::ranges::transform(name, name.begin(), [](unsigned char c){ return std::tolower(c); }); - - if (m_scopeVars.contains(name)) { - return m_scopeVars[name]; - } - - if (checkParent && parent) { - const auto var = parent->resolveVariable(name); - if (!var) { - return nullptr; - } - - return var; - } - - return nullptr; - } - - std::shared_ptr addVariable(std::string name, ScopeVar variableInfo) { - // Lowercase name - std::ranges::transform(name, name.begin(), [](unsigned char c){ return std::tolower(c); }); - - variableInfo.index = incrementVarIndex(); - variableInfo.scopeIndex = scopeIndex; - - // Rename var to have a unique name - // These should also get saved to the var list LAST and be deleted on every recompile. - if (variableInfo.isGlobal) { - variableInfo.rename = ""; - } else { - variableInfo.rename = std::format("__temp__{}__{}", name, variableInfo.index); - } - - m_scopeVars[name] = std::make_shared(variableInfo); - addToAllVars(variableInfo); - return m_scopeVars[name]; - } - - void addToAllVars(ScopeVar var) { - if (parent) { - parent->addToAllVars(var); - return; - } - - allVars[var.index] = var.token.lexeme; - } - - bool isRootScope() const { - return parent == nullptr; - } - - uint32_t getScopeIndex() const { - return scopeIndex; - } -}; diff --git a/nvse/nvse/NVSETreePrinter.cpp b/nvse/nvse/NVSETreePrinter.cpp deleted file mode 100644 index e183308b..00000000 --- a/nvse/nvse/NVSETreePrinter.cpp +++ /dev/null @@ -1,524 +0,0 @@ -#include "NVSETreePrinter.h" -#include - -#include "NVSECompilerUtils.h" -#include "NVSEParser.h" - -void NVSETreePrinter::PrintTabs(const bool debugOnly) { - for (int i = 0; i < curTab; i++) { - if (debugOnly) { - CompDbg(" "); - } - else { - CompInfo(" "); - } - } -} - -void NVSETreePrinter::VisitNVSEScript(NVSEScript* script) { - CompDbg("\n==== AST ====\n\n"); - PrintTabs(); - CompDbg("name: %s\n", script->name.lexeme.c_str()); - PrintTabs(); - if (!script->globalVars.empty()) { - CompDbg("globals\n"); - curTab++; - for (auto& g : script->globalVars) { - g->Accept(this); - } - curTab--; - } - CompDbg("blocks\n"); - curTab++; - for (auto &b : script->blocks) { - b->Accept(this); - } - curTab--; -} - -void NVSETreePrinter::VisitBeginStmt(BeginStmt* stmt) { - PrintTabs(); - CompDbg("begin %s %s\n", stmt->name.lexeme.c_str(), stmt->param.has_value() ? stmt->param->lexeme.c_str() : ""); - curTab++; - PrintTabs(); - CompDbg("body\n"); - curTab++; - stmt->block->Accept(this); - curTab--; - curTab--; -} - -void NVSETreePrinter::VisitFnStmt(FnDeclStmt* stmt) { - PrintTabs(); - CompDbg("fn\n"); - curTab++; - PrintTabs(); - CompDbg("args\n"); - curTab++; - for (auto var_decl_stmt : stmt->args) { - var_decl_stmt->Accept(this); - } - curTab--; - PrintTabs(); - CompDbg("body\n"); - curTab++; - stmt->body->Accept(this); - curTab--; - curTab--; -} - -void NVSETreePrinter::VisitVarDeclStmt(VarDeclStmt* stmt) { - PrintTabs(); - CompDbg("vardecl\n"); - - curTab++; - for (auto [name, value] : stmt->values) { - PrintTabs(); - CompDbg("%s\n", name.lexeme.c_str()); - if (value) { - curTab++; - value->Accept(this); - curTab--; - } - } - - curTab--; -} - -void NVSETreePrinter::VisitExprStmt(const ExprStmt* stmt) { - PrintTabs(); - CompDbg("exprstmt\n"); - curTab++; - if (stmt->expr) { - stmt->expr->Accept(this); - } - curTab--; -} -void NVSETreePrinter::VisitForStmt(ForStmt* stmt) { - PrintTabs(); - CompDbg("for\n"); - - curTab++; - - if (stmt->init) { - PrintTabs(); - CompDbg("init\n"); - curTab++; - stmt->init->Accept(this); - curTab--; - } - - if (stmt->cond) { - PrintTabs(); - CompDbg("cond\n"); - curTab++; - stmt->cond->Accept(this); - curTab--; - } - - if (stmt->post) { - PrintTabs(); - CompDbg("post\n"); - curTab++; - stmt->post->Accept(this); - curTab--; - } - - PrintTabs(); - CompDbg("block\n"); - curTab++; - stmt->block->Accept(this); - curTab--; - - curTab--; -} - -void NVSETreePrinter::VisitForEachStmt(ForEachStmt* stmt) { - PrintTabs(); - CompDbg("foreach\n"); - - curTab++; - - PrintTabs(); - CompDbg("elem\n"); - curTab++; - for (auto decl : stmt->declarations) { - if (decl) { - decl->Accept(this); - } - } - curTab--; - - PrintTabs(); - CompDbg("in\n"); - curTab++; - stmt->rhs->Accept(this); - curTab--; - - PrintTabs(); - CompDbg("block\n"); - curTab++; - stmt->block->Accept(this); - curTab--; - - curTab--; -} - -void NVSETreePrinter::VisitIfStmt(IfStmt* stmt) { - PrintTabs(); - CompDbg("if\n"); - - curTab++; - - PrintTabs(); - CompDbg("cond\n"); - curTab++; - stmt->cond->Accept(this); - curTab--; - - PrintTabs(); - CompDbg("block\n"); - curTab++; - stmt->block->Accept(this); - curTab--; - - if (stmt->elseBlock) { - PrintTabs(); - CompDbg("else\n"); - curTab++; - stmt->elseBlock->Accept(this); - curTab--; - } - - curTab--; -} -void NVSETreePrinter::VisitReturnStmt(ReturnStmt* stmt) { - PrintTabs(); - CompDbg("return\n"); - - if (stmt->expr) { - curTab++; - PrintTabs(); - CompDbg("value\n"); - curTab++; - stmt->expr->Accept(this); - curTab--; - curTab--; - } -} - -void NVSETreePrinter::VisitContinueStmt(ContinueStmt* stmt) { - PrintTabs(); - CompDbg("continue"); -} - -void NVSETreePrinter::VisitBreakStmt(BreakStmt* stmt) { - PrintTabs(); - CompDbg("break"); -} - -void NVSETreePrinter::VisitWhileStmt(WhileStmt* stmt) { - PrintTabs(); - CompDbg("while\n"); - - curTab++; - - PrintTabs(); - CompDbg("cond\n"); - curTab++; - stmt->cond->Accept(this); - curTab--; - - PrintTabs(); - CompDbg("block\n"); - curTab++; - stmt->block->Accept(this); - curTab--; - - curTab--; -} - -void NVSETreePrinter::VisitBlockStmt(BlockStmt* stmt) { - for (auto stmt : stmt->statements) { - stmt->Accept(this); - } -} - -void NVSETreePrinter::VisitAssignmentExpr(AssignmentExpr* expr) { - PrintTabs(); - CompDbg("assignment\n"); - curTab++; - PrintTabs(); - CompDbg("op: %s\n", expr->token.lexeme.c_str()); - PrintTabs(); - CompDbg("lhs:\n"); - curTab++; - expr->left->Accept(this); - curTab--; - PrintTabs(); - CompDbg("rhs\n"); - curTab++; - expr->expr->Accept(this); - curTab--; - curTab--; -} - -void NVSETreePrinter::VisitTernaryExpr(TernaryExpr* expr) { - PrintTabs(); - CompDbg("ternary\n"); - curTab++; - - PrintTabs(); - CompDbg("cond\n"); - curTab++; - expr->cond->Accept(this); - curTab--; - - if (expr->left) { - PrintTabs(); - CompDbg("lhs\n"); - curTab++; - expr->left->Accept(this); - curTab--; - } - - PrintTabs(); - CompDbg("rhs\n"); - curTab++; - expr->right->Accept(this); - curTab--; - - curTab--; -} - -void NVSETreePrinter::VisitInExpr(InExpr* expr) { - PrintTabs(); - CompDbg("inexpr\n"); - curTab++; - PrintTabs(); - CompDbg("isNot: %s\n", expr->isNot ? "y" : "n"); - PrintTabs(); - CompDbg("expr\n"); - curTab++; - expr->lhs->Accept(this); - curTab--; - PrintTabs(); - CompDbg("val\n"); - curTab++; - if (expr->expression) { - expr->expression->Accept(this); - } else { - for (auto val : expr->values) { - val->Accept(this); - } - } - curTab--; - curTab--; -} - -void NVSETreePrinter::VisitBinaryExpr(BinaryExpr* expr) { - PrintTabs(); - CompDbg("binary: %s\n", expr->op.lexeme.c_str()); - curTab++; - PrintTabs(); - CompDbg("lhs\n"); - curTab++; - expr->left->Accept(this); - curTab--; - PrintTabs(); - CompDbg("rhs\n"); - curTab++; - expr->right->Accept(this); - curTab--; - - PrintTabs(); - CompDbg("detailed type: %s\n", TokenTypeToString(expr->tokenType)); - - curTab--; -} - -void NVSETreePrinter::VisitUnaryExpr(UnaryExpr* expr) { - PrintTabs(); - CompDbg("unary: %s\n", expr->op.lexeme.c_str()); - curTab++; - expr->expr->Accept(this); - - PrintTabs(); - CompDbg("detailed type: %s\n", TokenTypeToString(expr->tokenType)); - - curTab--; -} - -void NVSETreePrinter::VisitSubscriptExpr(SubscriptExpr* expr) { - PrintTabs(); - CompDbg("subscript\n"); - - curTab++; - PrintTabs(); - CompDbg("expr\n"); - curTab++; - expr->left->Accept(this); - curTab--; - - PrintTabs(); - CompDbg("[]\n"); - curTab++; - expr->index->Accept(this); - curTab--; - - PrintTabs(); - CompDbg("detailed type: %s\n", TokenTypeToString(expr->tokenType)); - - curTab--; -} - -void NVSETreePrinter::VisitCallExpr(CallExpr* expr) { - PrintTabs(); - CompDbg("call : %s\n", expr->token.lexeme.c_str()); - curTab++; - - if (expr->left) { - PrintTabs(); - CompDbg("expr\n"); - curTab++; - expr->left->Accept(this); - curTab--; - } - - if (!expr->args.empty()) { - PrintTabs(); - CompDbg("args\n"); - curTab++; - for (const auto& exp : expr->args) { - exp->Accept(this); - } - curTab--; - } - - PrintTabs(); - CompDbg("detailed type: %s\n", TokenTypeToString(expr->tokenType)); - - curTab--; -} - -void NVSETreePrinter::VisitGetExpr(GetExpr* expr) { - PrintTabs(); - CompDbg("get\n"); - curTab++; - PrintTabs(); - CompDbg("expr\n"); - curTab++; - expr->left->Accept(this); - curTab--; - PrintTabs(); - CompDbg("token: %s\n", expr->identifier.lexeme.c_str()); - - PrintTabs(); - CompDbg("detailed type: %s\n", TokenTypeToString(expr->tokenType)); - - curTab--; -} - -void NVSETreePrinter::VisitBoolExpr(BoolExpr* expr) { - PrintTabs(); - if (expr->value) { - CompDbg("boolean: true\n"); - } else { - CompDbg("boolean: false\n"); - } - - curTab++; - PrintTabs(); - CompDbg("detailed type: %s\n", TokenTypeToString(expr->tokenType)); - curTab--; -} - -void NVSETreePrinter::VisitNumberExpr(NumberExpr* expr) { - PrintTabs(); - CompDbg("number: %0.5f\n", expr->value); - - curTab++; - PrintTabs(); - CompDbg("detailed type: %s\n", TokenTypeToString(expr->tokenType)); - curTab--; -} - -void NVSETreePrinter::VisitStringExpr(StringExpr* expr) { - PrintTabs(); - CompDbg("string: %s\n", expr->token.lexeme.c_str()); - - curTab++; - PrintTabs(); - CompDbg("detailed type: %s\n", TokenTypeToString(expr->tokenType)); - curTab--; -} - -void NVSETreePrinter::VisitIdentExpr(IdentExpr* expr) { - PrintTabs(); - CompDbg("ident: %s\n", expr->token.lexeme.c_str()); - - curTab++; - PrintTabs(); - CompDbg("detailed type: %s\n", TokenTypeToString(expr->tokenType)); - curTab--; -} - -void NVSETreePrinter::VisitMapLiteralExpr(MapLiteralExpr* expr) {} - -void NVSETreePrinter::VisitArrayLiteralExpr(ArrayLiteralExpr* expr) { - PrintTabs(); - CompDbg("array literal\n"); - curTab++; - PrintTabs(); - CompDbg("values\n"); - curTab++; - for (auto val : expr->values) { - val->Accept(this); - } - curTab--; - PrintTabs(); - CompDbg("detailed type: %s\n", TokenTypeToString(expr->tokenType)); - curTab--; -} - -void NVSETreePrinter::VisitGroupingExpr(GroupingExpr* expr) { - PrintTabs(); - CompDbg("grouping\n"); - curTab++; - expr->expr->Accept(this); - curTab--; - - curTab++; - PrintTabs(); - CompDbg("detailed type: %s\n", TokenTypeToString(expr->tokenType)); - curTab--; -} - -void NVSETreePrinter::VisitLambdaExpr(LambdaExpr* expr) { - PrintTabs(); - CompDbg("lambda\n"); - curTab++; - - if (!expr->args.empty()) { - PrintTabs(); - CompDbg("args\n"); - curTab++; - for (auto &arg : expr->args) { - arg->Accept(this); - } - curTab--; - } - - PrintTabs(); - CompDbg("body\n"); - curTab++; - expr->body->Accept(this); - curTab--; - - curTab--; - - curTab++; - PrintTabs(); - CompDbg("detailed type: %s\n", TokenTypeToString(expr->tokenType)); - curTab--; -} \ No newline at end of file diff --git a/nvse/nvse/NVSETreePrinter.h b/nvse/nvse/NVSETreePrinter.h deleted file mode 100644 index ca0493ee..00000000 --- a/nvse/nvse/NVSETreePrinter.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once -#include "NVSEVisitor.h" - -class NVSETreePrinter : public NVSEVisitor { - int curTab = 0; - void PrintTabs(const bool debugOnly = true); - -public: - NVSETreePrinter() = default; - - void VisitNVSEScript(NVSEScript* script) override; - void VisitBeginStmt(BeginStmt* stmt) override; - void VisitFnStmt(FnDeclStmt* stmt) override; - void VisitVarDeclStmt(VarDeclStmt* stmt) override; - - void VisitExprStmt(const ExprStmt* stmt) override; - void VisitForStmt(ForStmt* stmt) override; - void VisitForEachStmt(ForEachStmt* stmt) override; - void VisitIfStmt(IfStmt* stmt) override; - void VisitReturnStmt(ReturnStmt* stmt) override; - void VisitContinueStmt(ContinueStmt* stmt) override; - void VisitBreakStmt(BreakStmt* stmt) override; - void VisitWhileStmt(WhileStmt* stmt) override; - void VisitBlockStmt(BlockStmt* stmt) override; - - void VisitAssignmentExpr(AssignmentExpr* expr) override; - void VisitTernaryExpr(TernaryExpr* expr) override; - void VisitInExpr(InExpr* expr) override; - void VisitBinaryExpr(BinaryExpr* expr) override; - void VisitUnaryExpr(UnaryExpr* expr) override; - void VisitSubscriptExpr(SubscriptExpr* expr) override; - void VisitCallExpr(CallExpr* expr) override; - void VisitGetExpr(GetExpr* expr) override; - void VisitBoolExpr(BoolExpr* expr) override; - void VisitNumberExpr(NumberExpr* expr) override; - void VisitStringExpr(StringExpr* expr) override; - void VisitIdentExpr(IdentExpr* expr) override; - void VisitMapLiteralExpr(MapLiteralExpr* expr) override; - void VisitArrayLiteralExpr(ArrayLiteralExpr* expr) override; - void VisitGroupingExpr(GroupingExpr* expr) override; - void VisitLambdaExpr(LambdaExpr* expr) override; -}; \ No newline at end of file diff --git a/nvse/nvse/NVSETypeChecker.cpp b/nvse/nvse/NVSETypeChecker.cpp deleted file mode 100644 index 97c8e594..00000000 --- a/nvse/nvse/NVSETypeChecker.cpp +++ /dev/null @@ -1,964 +0,0 @@ -#include "NVSETypeChecker.h" -#include "NVSEParser.h" -#include "ScriptTokens.h" - -#include -#include - -#include "GameForms.h" -#include "Commands_Scripting.h" -#include "GameData.h" -#include "Hooks_Script.h" -#include "NVSECompilerUtils.h" -#include "ScriptUtils.h" -#include "PluginAPI.h" - -#define WRAP_ERROR(expr) \ - try { \ - expr; \ -} \ - catch (std::runtime_error& er) { \ - CompErr("%s\n", er.what()); \ - } - -std::string getTypeErrorMsg(Token_Type lhs, Token_Type rhs) { - return std::format("Cannot convert from {} to {}", TokenTypeToString(lhs), TokenTypeToString(rhs)); -} - -std::shared_ptr NVSETypeChecker::EnterScope() { - if (!scopes.empty()) { - scopes.emplace(std::make_shared(scopeIndex++, scopes.top())); - } else { - scopes.emplace(std::make_shared(scopeIndex++, nullptr)); - } - - return scopes.top(); -} - -void NVSETypeChecker::LeaveScope() { - scopes.pop(); -} - -void NVSETypeChecker::error(size_t line, std::string msg) { - hadError = true; - throw std::runtime_error(std::format("[line {}] {}", line, msg)); -} - -void NVSETypeChecker::error(size_t line, size_t column, std::string msg) { - hadError = true; - throw std::runtime_error(std::format("[line {}:{}] {}", line, column, msg)); -} - -bool NVSETypeChecker::check() { - WRAP_ERROR(script->Accept(this)) - - return !hadError; -} - -void NVSETypeChecker::VisitNVSEScript(NVSEScript* script) { - // Add nvse as requirement - script->m_mpPluginRequirements["nvse"] = PACKED_NVSE_VERSION; - - // Add all #version statements to script requirements - for (const auto &[plugin, version] : g_currentCompilerPluginVersions.top()) { - auto nameLowered = plugin; - std::ranges::transform(nameLowered.begin(), nameLowered.end(), nameLowered.begin(), [](unsigned char c) { return std::tolower(c); }); - script->m_mpPluginRequirements[nameLowered] = version; - } - - EnterScope(); - - for (const auto& global : script->globalVars) { - global->Accept(this); - - // Dont allow initializers in global scope - for (auto& [name, value] : dynamic_cast(global.get())->values) { - if (value) { - WRAP_ERROR(error(name.line, "Variable initializers are not allowed in global scope.")) - } - } - } - - // Need to associate / start indexing after existing non-temp script vars - if (engineScript && engineScript->varList.Count() > 0) { - for (const auto var : engineScript->varList) { - if (strncmp(var->name.CStr(), "__temp", strlen("__temp")) != 0) { - scopes.top()->varIndex++; - } - } - } - - // Pre-process blocks - check for duplicate blocks - std::unordered_map> mpTypeToModes{}; - std::vector functions {}; - bool foundFn = false, foundEvent = false; - for (const auto& block : script->blocks) { - if (const auto b = dynamic_cast(block.get())) { - if (foundFn) { - WRAP_ERROR(error(b->name.line, "Cannot have a function block and an event block in the same script.")) - } - - foundEvent = true; - - std::string name = b->name.lexeme; - std::string param{}; - if (b->param.has_value()) { - param = b->param->lexeme; - } - - if (mpTypeToModes.contains(name) && mpTypeToModes[name].contains(param)) { - WRAP_ERROR(error(b->name.line, "Duplicate block declaration.")) - continue; - } - - if (!mpTypeToModes.contains(name)) { - mpTypeToModes[name] = std::unordered_set{}; - } - mpTypeToModes[name].insert(param); - } - - if (const auto b = dynamic_cast(block.get())) { - if (foundEvent) { - WRAP_ERROR(error(b->token.line, "Cannot have a function block and an event block in the same script.")) - } - - if (foundFn) { - WRAP_ERROR(error(b->token.line, "Duplicate block declaration.")) - } - - foundFn = true; - } - } - - for (const auto& block : script->blocks) { - block->Accept(this); - } - - LeaveScope(); -} - -void NVSETypeChecker::VisitBeginStmt(BeginStmt* stmt) { - stmt->scope = EnterScope(); - stmt->block->Accept(this); - LeaveScope(); -} - -void NVSETypeChecker::VisitFnStmt(FnDeclStmt* stmt) { - for (const auto& decl : stmt->args) { - WRAP_ERROR(decl->Accept(this)) - } - - stmt->scope = EnterScope(); - returnType.emplace(); - - stmt->body->Accept(this); - - returnType.pop(); - LeaveScope(); -} - -void NVSETypeChecker::VisitVarDeclStmt(VarDeclStmt* stmt) { - auto detailedType = GetDetailedTypeFromNVSEToken(stmt->type.type); - for (auto [name, expr] : stmt->values) { - // See if variable has already been declared - if (auto var = scopes.top()->resolveVariable(name.lexeme, false)) { - WRAP_ERROR(error(name.line, std::format("Variable with name '{}' has already been defined in the current scope (at line {})\n", name.lexeme, var->token.line))); - continue; - } - - if (g_scriptCommands.GetByName(name.lexeme.c_str())) { - WRAP_ERROR(error(name.line, std::format("Variable name '{}' conflicts with a command with the same name.", name.lexeme))); - continue; - } - - // Warn if name shadows a form - if (auto form = GetFormByID(name.lexeme.c_str())) { - auto modName = DataHandler::Get()->GetActiveModList()[form->GetModIndex()]->name; -#ifdef EDITOR - CompInfo("[line %d] Info: Variable with name '%s' shadows a form with the same name from mod '%s'\n", - name.line, name.lexeme.c_str(), modName); -#else - CompInfo("Info: Variable with name '%s' shadows a form with the same name from mod '%s'. This is NOT an error. Do not contact the mod author.", name.lexeme.c_str(), modName); -#endif - } - - if (auto shadowed = scopes.top()->resolveVariable(name.lexeme, true)) { -#ifdef EDITOR - CompInfo("[line %d] Info: Variable with name '%s' shadows a variable with the same name in outer scope. (Defined at line %d)\n", - name.line, name.lexeme.c_str(), shadowed->token.line, shadowed->token.column); -#endif - } - - if (expr) { - expr->Accept(this); - auto rhsType = expr->tokenType; - if (s_operators[kOpType_Assignment].GetResult(detailedType, rhsType) == kTokenType_Invalid) { - WRAP_ERROR(error(name.line, getTypeErrorMsg(rhsType, detailedType))) - return; - } - } - - NVSEScope::ScopeVar var {}; - var.token = name; - var.detailedType = detailedType; - var.isGlobal = scopes.top()->isRootScope(); - var.variableType = GetScriptTypeFromToken(stmt->type); - - // Set lambda info - if (expr->IsType()) { - auto* lambda = dynamic_cast(expr.get()); - var.lambdaTypeInfo.isLambda = true; - var.lambdaTypeInfo.returnType = lambda->typeinfo.returnType; - var.lambdaTypeInfo.paramTypes = lambda->typeinfo.paramTypes; - } - - // Assign this new scope var to this statment for lookup in compiler - stmt->scopeVars.push_back(scopes.top()->addVariable(name.lexeme, var)); - stmt->detailedType = detailedType; - } -} - -void NVSETypeChecker::VisitExprStmt(const ExprStmt* stmt) { - if (stmt->expr) { - stmt->expr->Accept(this); - } -} - -void NVSETypeChecker::VisitForStmt(ForStmt* stmt) { - stmt->scope = EnterScope(); - - if (stmt->init) { - WRAP_ERROR(stmt->init->Accept(this)) - } - - if (stmt->cond) { - WRAP_ERROR( - stmt->cond->Accept(this); - - // Check if condition can evaluate to bool - const auto lType = stmt->cond->tokenType; - const auto oType = s_operators[kOpType_Equals].GetResult(lType, kTokenType_Boolean); - if (oType != kTokenType_Boolean) { - error(stmt->line, std::format("Invalid expression type ('{}') for loop condition.", TokenTypeToString(oType))); - } - - stmt->cond->tokenType = oType; - ) - } - - if (stmt->post) { - WRAP_ERROR(stmt->post->Accept(this)) - } - - insideLoop.push(true); - stmt->block->Accept(this); - insideLoop.pop(); - - LeaveScope(); -} - -void NVSETypeChecker::VisitForEachStmt(ForEachStmt* stmt) { - stmt->scope = EnterScope(); - for (const auto &decl : stmt->declarations) { - if (decl) { - WRAP_ERROR(decl->Accept(this)) - } - } - stmt->rhs->Accept(this); - - WRAP_ERROR( - if (stmt->decompose) { - // TODO - // What should I check here? - } else { - // Get type of lhs identifier -- this is way too verbose - const auto lType = stmt->declarations[0]->detailedType; - const auto rType = stmt->rhs->tokenType; - if (s_operators[kOpType_In].GetResult(lType, rType) == kTokenType_Invalid) { - error(stmt->line, std::format("Invalid types '{}' and '{}' passed to for-in expression.", - TokenTypeToString(lType), TokenTypeToString(rType))); - } - } - ) - - insideLoop.push(true); - stmt->block->Accept(this); - insideLoop.pop(); - - LeaveScope(); -} - -void NVSETypeChecker::VisitIfStmt(IfStmt* stmt) { - stmt->scope = EnterScope(); - WRAP_ERROR( - stmt->cond->Accept(this); - - // Check if condition can evaluate to bool - const auto lType = stmt->cond->tokenType; - if (!CanConvertOperand(lType, kTokenType_Boolean)) { - error(stmt->line, std::format("Invalid expression type '{}' for if statement.", TokenTypeToString(lType))); - stmt->cond->tokenType = kTokenType_Invalid; - } - else { - stmt->cond->tokenType = kTokenType_Boolean; - } - ) - stmt->block->Accept(this); - LeaveScope(); - - if (stmt->elseBlock) { - stmt->elseBlock->scope = EnterScope(); - stmt->elseBlock->Accept(this); - LeaveScope(); - } -} - -void NVSETypeChecker::VisitReturnStmt(ReturnStmt* stmt) { - if (stmt->expr) { - stmt->expr->Accept(this); - - const auto basicType = GetBasicTokenType(stmt->expr->tokenType); - auto &[type, line] = returnType.top(); - - if (type != kTokenType_Invalid) { - if (!CanConvertOperand(basicType, type)) { - const auto msg = std::format( - "Return type '{}' not compatible with return type defined previously. ('{}' at line {})", - TokenTypeToString(basicType), - TokenTypeToString(type), - line); - - error(line, msg); - } - } else { - type = basicType; - line = stmt->line; - } - - stmt->detailedType = stmt->expr->tokenType; - } - else { - stmt->detailedType = kTokenType_Empty; - } -} - -void NVSETypeChecker::VisitContinueStmt(ContinueStmt* stmt) { - if (insideLoop.empty() || !insideLoop.top()) { - error(stmt->line, "Keyword 'continue' not valid outside of loops."); - } -} - -void NVSETypeChecker::VisitBreakStmt(BreakStmt* stmt) { - if (insideLoop.empty() || !insideLoop.top()) { - error(stmt->line, "Keyword 'break' not valid outside of loops."); - } -} - -void NVSETypeChecker::VisitWhileStmt(WhileStmt* stmt) { - stmt->scope = EnterScope(); - WRAP_ERROR( - stmt->cond->Accept(this); - - // Check if condition can evaluate to bool - const auto lType = stmt->cond->tokenType; - const auto rType = kTokenType_Boolean; - const auto oType = s_operators[kOpType_Equals].GetResult(lType, rType); - if (oType != kTokenType_Boolean) { - error(stmt->line, "Invalid expression type for while loop."); - } - stmt->cond->tokenType = oType; - ) - - insideLoop.push(true); - stmt->block->Accept(this); - insideLoop.pop(); - LeaveScope(); -} - -void NVSETypeChecker::VisitBlockStmt(BlockStmt* stmt) { - for (const auto &statement : stmt->statements) { - WRAP_ERROR(statement->Accept(this)) - } -} - -void NVSETypeChecker::VisitAssignmentExpr(AssignmentExpr* expr) { - expr->left->Accept(this); - expr->expr->Accept(this); - - const auto lType = expr->left->tokenType; - const auto rType = expr->expr->tokenType; - const auto oType = s_operators[tokenOpToNVSEOpType[expr->token.type]].GetResult(lType, rType); - if (oType == kTokenType_Invalid) { - const auto msg = std::format("Invalid types '{}' and '{}' for operator {} ({}).", - TokenTypeToString(lType), TokenTypeToString(rType), expr->token.lexeme, - TokenTypeStr[static_cast(expr->token.type)]); - error(expr->line, msg); - return; - } - - // Probably always true - if (expr->left->IsType()) { - const auto *ident = dynamic_cast(expr->left.get()); - if (ident->varInfo && ident->varInfo->lambdaTypeInfo.isLambda) { - error(expr->line, "Cannot assign to a variable that is holding a lambda."); - return; - } - - if (expr->expr->tokenType == kTokenType_Lambda) { - error(expr->line, "Cannot assign a lambda to a variable after it has been declared."); - return; - } - } - - expr->tokenType = oType; - expr->left->tokenType = oType; -} - -void NVSETypeChecker::VisitTernaryExpr(TernaryExpr* expr) { - WRAP_ERROR( - expr->cond->Accept(this); - - // Check if condition can evaluate to bool - const auto lType = expr->cond->tokenType; - const auto oType = s_operators[kOpType_Equals].GetResult(lType, kTokenType_Boolean); - if (oType == kTokenType_Invalid) { - error(expr->line, std::format("Invalid expression type '{}' for if statement.", TokenTypeToString(lType))); - expr->cond->tokenType = kTokenType_Invalid; - } - else { - expr->cond->tokenType = oType; - } - ) - - WRAP_ERROR(expr->left->Accept(this)) - WRAP_ERROR(expr->right->Accept(this)) - if (!CanConvertOperand(expr->right->tokenType, GetBasicTokenType(expr->left->tokenType))) { - error(expr->line, std::format("Incompatible value types ('{}' and '{}') specified for ternary expression.", - TokenTypeToString(expr->left->tokenType), TokenTypeToString(expr->right->tokenType))); - } - - expr->tokenType = expr->left->tokenType; -} - -void NVSETypeChecker::VisitInExpr(InExpr* expr) { - expr->lhs->Accept(this); - - if (!expr->values.empty()) { - int idx = 0; - for (const auto& val : expr->values) { - idx++; - val->Accept(this); - - const auto lhsType = expr->lhs->tokenType; - const auto rhsType = val->tokenType; - const auto outputType = s_operators[tokenOpToNVSEOpType[NVSETokenType::EqEq]].GetResult(lhsType, rhsType); - if (outputType == kTokenType_Invalid) { - WRAP_ERROR( - const auto msg = std::format("Value {} (type '{}') cannot compare against the {} specified on lhs of 'in' expression.", idx, - TokenTypeToString(rhsType), TokenTypeToString(lhsType)); - error(expr->token.line, msg); - ) - } - } - } - // Any other expression, compiles to ar_find - else { - expr->expression->Accept(this); - if (expr->expression->tokenType != kTokenType_Ambiguous) { - if (expr->expression->tokenType != kTokenType_Array && expr->expression->tokenType != kTokenType_ArrayVar) { - WRAP_ERROR( - const auto msg = std::format("Expected array for 'in' expression (Got '{}').", TokenTypeToString(expr->expression->tokenType)); - error(expr->token.line, msg); - ) - } - } - } - - expr->tokenType = kTokenType_Boolean; -} - -void NVSETypeChecker::VisitBinaryExpr(BinaryExpr* expr) { - expr->left->Accept(this); - expr->right->Accept(this); - - const auto lhsType = expr->left->tokenType; - const auto rhsType = expr->right->tokenType; - const auto outputType = s_operators[tokenOpToNVSEOpType[expr->op.type]].GetResult(lhsType, rhsType); - if (outputType == kTokenType_Invalid) { - const auto msg = std::format("Invalid types '{}' and '{}' for operator {} ({}).", - TokenTypeToString(lhsType), TokenTypeToString(rhsType), expr->op.lexeme, - TokenTypeStr[static_cast(expr->op.type)]); - error(expr->op.line, msg); - return; - } - - expr->tokenType = outputType; -} - -void NVSETypeChecker::VisitUnaryExpr(UnaryExpr* expr) { - expr->expr->Accept(this); - - if (expr->postfix) { - const auto lType = expr->expr->tokenType; - const auto rType = kTokenType_Number; - const auto oType = s_operators[tokenOpToNVSEOpType[expr->op.type]].GetResult(lType, rType); - if (oType == kTokenType_Invalid) { - error(expr->op.line, std::format("Invalid operand type '{}' for operator {} ({}).", - TokenTypeToString(lType), - expr->op.lexeme, TokenTypeStr[static_cast(expr->op.type)])); - } - - expr->tokenType = oType; - } - // -/!/$ - else { - const auto lType = expr->expr->tokenType; - const auto rType = kTokenType_Invalid; - const auto oType = s_operators[tokenOpToNVSEOpType[expr->op.type]].GetResult(lType, rType); - if (oType == kTokenType_Invalid) { - error(expr->op.line, std::format("Invalid operand type '{}' for operator {} ({}).", - TokenTypeToString(lType), - expr->op.lexeme, TokenTypeStr[static_cast(expr->op.type)])); - } - expr->tokenType = oType; - } -} - -void NVSETypeChecker::VisitSubscriptExpr(SubscriptExpr* expr) { - expr->left->Accept(this); - expr->index->Accept(this); - - const auto lhsType = expr->left->tokenType; - const auto indexType = expr->index->tokenType; - const auto outputType = s_operators[kOpType_LeftBracket].GetResult(lhsType, indexType); - if (outputType == kTokenType_Invalid) { - error(expr->op.line, - std::format("Expression type '{}' not valid for operator [].", TokenTypeToString(indexType))); - return; - } - - expr->tokenType = outputType; -} - -struct CallCommandInfo { - int funcIndex{}; - int argStart{}; -}; - -std::unordered_map callCmds = { - {"Call", {0, 1}}, - {"CallAfterFrames", {1, 3}}, - {"CallAfterSeconds", {1, 3}}, - {"CallForSeconds", {1, 3}}, -}; - -CallCommandInfo *getCallCommandInfo(const char *name) { - for (auto &[key, value] : callCmds) { - if (!_stricmp(key, name)) { - return &value; - } - } - - return nullptr; -} - -void NVSETypeChecker::VisitCallExpr(CallExpr* expr) { - auto checkLambdaArgs = [&] (IdentExpr *ident, int startArgs) { - const auto& paramTypes = ident->varInfo->lambdaTypeInfo.paramTypes; - - for (auto i = startArgs; i < expr->args.size(); i++) { - const auto& arg = expr->args[i]; - - const auto idx = i - startArgs + 1; - - // Too many args passed, handled below - if (i - 1 >= paramTypes.size()) { - break; - } - - const auto expected = paramTypes[i - 1]; - if (!ExpressionParser::ValidateArgType(static_cast(expected), arg->tokenType, true, nullptr)) { - WRAP_ERROR( - error(expr->token.line, std::format("Invalid expression for lambda parameter {}. (Expected {}, got {})", idx, GetBasicParamTypeString(expected), TokenTypeToString(arg->tokenType))); - ) - } - } - - const auto numArgsPassed = max(0, (int)expr->args.size() - startArgs); - if (numArgsPassed != paramTypes.size()) { - WRAP_ERROR( - error(expr->token.line, std::format("Invalid number of parameters specified for lambda '{}' (Expected {}, got {}).", ident->token.lexeme, paramTypes.size(), numArgsPassed)); - ) - } - }; - - std::string name = expr->token.lexeme; - const auto cmd = g_scriptCommands.GetByName(name.c_str()); - - // Try to get the script command by lexeme - if (!cmd) { - // See if its a lambda call - if (const auto &var = scopes.top()->resolveVariable(name)) { - if (var->lambdaTypeInfo.isLambda) { - // Turn this current expr from 'lambda(args)' into 'call(lambda, args)' - std::vector newArgs{}; - newArgs.push_back(std::make_shared(expr->token)); - expr->token.lexeme = "call"; - for (const auto& arg : expr->args) { - newArgs.push_back(arg); - } - expr->args = newArgs; - - expr->Accept(this); - return; - } - } - - error(expr->token.line, std::format("Invalid command '{}'.", name)); - return; - } - expr->cmdInfo = cmd; - - // Add command to script requirements - if (const auto plugin = g_scriptCommands.GetParentPlugin(cmd)) { - auto nameLowered = std::string(plugin->name); - std::ranges::transform(nameLowered.begin(), nameLowered.end(), nameLowered.begin(), [](unsigned char c) { return std::tolower(c); }); - - if (!script->m_mpPluginRequirements.contains(nameLowered)) { - script->m_mpPluginRequirements[std::string(nameLowered)] = plugin->version; - } else { - script->m_mpPluginRequirements[std::string(nameLowered)] = max(script->m_mpPluginRequirements[std::string(nameLowered)], plugin->version); - } - } - - if (expr->left) { - expr->left->Accept(this); - } - - if (expr->left && !CanUseDotOperator(expr->left->tokenType)) { - WRAP_ERROR(error(expr->token.line, "Left side of '.' must be a form or reference.")) - } - - // Check for calling reference if not an object script - if (engineScript) { - if (cmd->needsParent && !expr->left && engineScript->Type() != Script::eType_Object && engineScript->Type() != Script::eType_Magic) { - WRAP_ERROR(error(expr->token.line, std::format("Command '{}' requires a calling reference.", expr->token.lexeme))) - } - } - - // Normal (nvse + vanilla) calls - int argIdx = 0; - int paramIdx = 0; - for (; paramIdx < cmd->numParams && argIdx < expr->args.size(); paramIdx++) { - auto param = &cmd->params[paramIdx]; - auto arg = expr->args[argIdx]; - bool convertedEnum = false; - - if (isDefaultParse(cmd->parse) || !_stricmp(cmd->longName, "ShowMessage")) { - // Try to resolve identifiers as vanilla enums - auto ident = dynamic_cast(arg.get()); - - uint32_t idx = -1; - uint32_t len = 0; - if (ident) { - resolveVanillaEnum(param, ident->token.lexeme.c_str(), &idx, &len); - } - if (idx != -1) { - CompDbg("[line %d] INFO: Converting identifier '%s' to enum index %d\n", arg->line, ident->token.lexeme.c_str(), idx); - arg = std::make_shared(NVSEToken{}, static_cast(idx), false, len); - arg->tokenType = kTokenType_Number; - convertedEnum = true; - } else { - WRAP_ERROR(arg->Accept(this)) - } - - if (ident && arg->tokenType == kTokenType_Form) { - // Extract form from param - if (!doesFormMatchParamType(ident->form, static_cast(param->typeID))) { - if (!param->isOptional) { - WRAP_ERROR( - error(expr->token.line, std::format("Invalid expression for parameter {}. Expected {}.", argIdx + 1, param->typeStr)); - ) - } - } - else { - argIdx++; - continue; - } - } - } else { - WRAP_ERROR(arg->Accept(this)) - } - - // Try to resolve as nvse param - if (!convertedEnum && !ExpressionParser::ValidateArgType(static_cast(param->typeID), arg->tokenType, !isDefaultParse(cmd->parse), cmd)) { - if (!param->isOptional) { - WRAP_ERROR( - error(expr->token.line, std::format("Invalid expression for parameter {}. (Expected {}, got {}).", argIdx + 1, param->typeStr, TokenTypeToString(arg->tokenType))); - ) - } - } else { - expr->args[argIdx] = arg; - argIdx++; - } - } - - int numRequiredArgs = 0; - for (paramIdx = 0; paramIdx < cmd->numParams; paramIdx++) { - if (!cmd->params[paramIdx].isOptional) { - numRequiredArgs++; - } - } - - bool enoughArgs = expr->args.size() >= numRequiredArgs; - if (!enoughArgs) { - WRAP_ERROR( - error(expr->token.line, std::format("Invalid number of parameters specified to {} (Expected {}, got {}).", expr->token.lexeme, numRequiredArgs, expr->args.size())); - ) - } - - // Check lambda args differently - if (const auto callInfo = getCallCommandInfo(cmd->longName)) { - if (!enoughArgs) { - return; - } - - const auto& callee = expr->args[callInfo->funcIndex]; - const auto ident = dynamic_cast(callee.get()); - const auto lambdaCallee = ident && ident->varInfo && ident->varInfo->lambdaTypeInfo.isLambda; - - // Handle each call command separately as args to check are in different positions - // TODO: FIX THE CALL COMMAND - // Manually visit remaining args for call commands, since they operate differently from all other commands - while (argIdx < expr->args.size()) { - expr->args[argIdx]->Accept(this); - argIdx++; - } - - if (lambdaCallee) { - checkLambdaArgs(ident, callInfo->argStart); - expr->tokenType = ident->varInfo->lambdaTypeInfo.returnType; - } else { - expr->tokenType = kTokenType_Ambiguous; - } - - return; - } - - auto type = ToTokenType(g_scriptCommands.GetReturnType(cmd)); - if (type == kTokenType_Invalid) { - type = kTokenType_Ambiguous; - } - expr->tokenType = type; -} - -void NVSETypeChecker::VisitGetExpr(GetExpr* expr) { - expr->left->Accept(this); - - // Resolve variable type from form - // Try to resolve lhs reference - // Should be ident here - const auto ident = dynamic_cast(expr->left.get()); - if (!ident || expr->left->tokenType != kTokenType_Form) { - error(expr->token.line, "Member access not valid here. Left side of '.' must be a form or persistent reference."); - } - - const auto form = ident->form; - const auto& lhsName = ident->token.lexeme; - const auto& rhsName = expr->identifier.lexeme; - - TESScriptableForm* scriptable = nullptr; - switch (form->typeID) { - case kFormType_TESObjectREFR: { - const auto refr = DYNAMIC_CAST(form, TESForm, TESObjectREFR); - scriptable = DYNAMIC_CAST(refr->baseForm, TESForm, TESScriptableForm); - break; - } - case kFormType_TESQuest: { - scriptable = DYNAMIC_CAST(form, TESForm, TESScriptableForm); - break; - } - default: { - error(expr->line, "Unexpected form type found."); - } - } - - if (scriptable && scriptable->script) { - if (const auto varInfo = scriptable->script->GetVariableByName(rhsName.c_str())) { - const auto detailedType = GetDetailedTypeFromVarType(static_cast(varInfo->type)); - const auto detailedTypeConverted = GetVariableTypeFromNonVarType(detailedType); - if (detailedTypeConverted == kTokenType_Invalid) { - expr->tokenType = detailedType; - } - else { - expr->tokenType = detailedTypeConverted; - } - expr->varInfo = varInfo; - expr->referenceName = form->GetEditorID(); - return; - } - - error(expr->line, std::format("Variable {} not found on form {}.", rhsName, lhsName)); - } - - error(expr->line, std::format("Unable to resolve type for '{}.{}'.", lhsName, rhsName)); -} - -void NVSETypeChecker::VisitBoolExpr(BoolExpr* expr) { - expr->tokenType = kTokenType_Boolean; -} - -void NVSETypeChecker::VisitNumberExpr(NumberExpr* expr) { - if (!expr->isFp) { - if (expr->value > UINT32_MAX) { - WRAP_ERROR(error(expr->token.line, "Maximum value for integer literal exceeded. (Max: " + std::to_string(UINT32_MAX) + ")")) - } - } - - expr->tokenType = kTokenType_Number; -} - -void NVSETypeChecker::VisitMapLiteralExpr(MapLiteralExpr* expr) { - if (expr->values.empty()) { - WRAP_ERROR(error(expr->token.line, - "A map literal cannot be empty, as the key type cannot be deduced.")) - return; - } - - // Check that all expressions are pairs - for (int i = 0; i < expr->values.size(); i++) { - const auto &val = expr->values[i]; - val->Accept(this); - if (val->tokenType != kTokenType_Pair && val->tokenType != kTokenType_Ambiguous) { - WRAP_ERROR(error(expr->token.line, - std::format("Value {} is not a pair and is not valid for a map literal.", i + 1))) - return; - } - } - - // Now check key types - auto lhsType = kTokenType_Invalid; - for (int i = 0; i < expr->values.size(); i++) { - if (expr->values[i]->tokenType != kTokenType_Pair) { - continue; - } - - // Try to check the key - const auto pairPtr = dynamic_cast(expr->values[i].get()); - if (!pairPtr) { - continue; - } - - if (pairPtr->op.type != NVSETokenType::MakePair && pairPtr->left->tokenType != kTokenType_Pair) { - continue; - } - - if (lhsType == kTokenType_Invalid) { - lhsType = GetBasicTokenType(pairPtr->left->tokenType); - } else { - if (lhsType != pairPtr->left->tokenType) { - auto msg = std::format( - "Key for value {} (type '{}') specified for map literal conflicts with key type of previous value ('{}').", - i, - TokenTypeToString(pairPtr->left->tokenType), - TokenTypeToString(lhsType)); - - WRAP_ERROR(error(expr->token.line, msg)) - } - } - } - - expr->tokenType = kTokenType_Array; -} - -void NVSETypeChecker::VisitArrayLiteralExpr(ArrayLiteralExpr* expr) { - if (expr->values.empty()) { - expr->tokenType = kTokenType_Array; - return; - } - - for (const auto& val : expr->values) { - val->Accept(this); - - if (val->tokenType == kTokenType_Pair) { - WRAP_ERROR(error(val->line, "Invalid type inside of array literal. Expected array, string, ref, or number.")) - } - } - - auto lhsType = expr->values[0]->tokenType; - for (int i = 1; i < expr->values.size(); i++) { - if (!CanConvertOperand(expr->values[i]->tokenType, lhsType)) { - auto msg = std::format( - "Value {} (type '{}') specified for array literal conflicts with the type already specified in first element ('{}').", - i, - TokenTypeToString(expr->values[i]->tokenType), - TokenTypeToString(lhsType)); - - WRAP_ERROR(error(expr->token.line, msg)) - } - } - - expr->tokenType = kTokenType_Array; -} - -void NVSETypeChecker::VisitStringExpr(StringExpr* expr) { - expr->tokenType = kTokenType_String; -} - -void NVSETypeChecker::VisitIdentExpr(IdentExpr* expr) { - const auto name = expr->token.lexeme; - - if (const auto localVar = scopes.top()->resolveVariable(name)) { - expr->tokenType = localVar->detailedType; - expr->varInfo = localVar; - return; - } - - TESForm* form; - if (!_stricmp(name.c_str(), "player")) { - form = LookupFormByID(0x14); - } - else { - form = GetFormByID(name.c_str()); - } - - if (!form) { - expr->tokenType = kTokenType_Invalid; - error(expr->token.line, std::format("Unable to resolve identifier '{}'.", name)); - } - - if (form->typeID == kFormType_TESGlobal) { - expr->tokenType = kTokenType_Global; - } else { - expr->tokenType = kTokenType_Form; - expr->form = form; - } -} - -void NVSETypeChecker::VisitGroupingExpr(GroupingExpr* expr) { - WRAP_ERROR( - expr->expr->Accept(this); - expr->tokenType = expr->expr->tokenType; - ) -} - -void NVSETypeChecker::VisitLambdaExpr(LambdaExpr* expr) { - EnterScope(); - returnType.emplace(); - - for (const auto &decl : expr->args) { - WRAP_ERROR(decl->Accept(this)) - - expr->typeinfo.paramTypes.push_back(GetParamTypeFromBasicTokenType(GetBasicTokenType(decl->detailedType))); - } - - insideLoop.push(false); - expr->body->Accept(this); - insideLoop.pop(); - - expr->tokenType = kTokenType_Lambda; - expr->typeinfo.returnType = returnType.top().returnType; - - returnType.pop(); - LeaveScope(); -} diff --git a/nvse/nvse/NVSETypeChecker.h b/nvse/nvse/NVSETypeChecker.h deleted file mode 100644 index 9583912c..00000000 --- a/nvse/nvse/NVSETypeChecker.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once -#include - -#include "NVSELexer.h" -#include "NVSEScope.h" -#include "NVSEVisitor.h" -#include "ScriptTokens.h" - -class NVSETypeChecker : NVSEVisitor { - struct ReturnInfo { - Token_Type returnType {kTokenType_Invalid}; - size_t line {0}; - }; - - bool hadError = false; - NVSEScript *script; - Script* engineScript; - - std::stack insideLoop {}; - std::stack> scopes {}; - std::stack returnType {}; - - uint32_t scopeIndex {1}; - - bool bScopedGlobal = false; - - std::shared_ptr EnterScope(); - void LeaveScope(); - void error(size_t line, std::string msg); - void error(size_t line, size_t column, std::string msg); - -public: - NVSETypeChecker(NVSEScript* script, Script* engineScript) : script(script), engineScript(engineScript) {} - bool check(); - - void VisitNVSEScript(NVSEScript* script) override; - void VisitBeginStmt(BeginStmt* stmt) override; - void VisitFnStmt(FnDeclStmt* stmt) override; - void VisitVarDeclStmt(VarDeclStmt* stmt) override; - void VisitExprStmt(const ExprStmt* stmt) override; - void VisitForStmt(ForStmt* stmt) override; - void VisitForEachStmt(ForEachStmt* stmt) override; - void VisitIfStmt(IfStmt* stmt) override; - void VisitReturnStmt(ReturnStmt* stmt) override; - void VisitContinueStmt(ContinueStmt* stmt) override; - void VisitBreakStmt(BreakStmt* stmt) override; - void VisitWhileStmt(WhileStmt* stmt) override; - void VisitBlockStmt(BlockStmt* stmt) override; - void VisitAssignmentExpr(AssignmentExpr* expr) override; - void VisitTernaryExpr(TernaryExpr* expr) override; - void VisitInExpr(InExpr* expr) override; - void VisitBinaryExpr(BinaryExpr* expr) override; - void VisitUnaryExpr(UnaryExpr* expr) override; - void VisitSubscriptExpr(SubscriptExpr* expr) override; - void VisitCallExpr(CallExpr* expr) override; - void VisitGetExpr(GetExpr* expr) override; - void VisitBoolExpr(BoolExpr* expr) override; - void VisitNumberExpr(NumberExpr* expr) override; - void VisitMapLiteralExpr(MapLiteralExpr* expr) override; - void VisitArrayLiteralExpr(ArrayLiteralExpr* expr) override; - void VisitStringExpr(StringExpr* expr) override; - void VisitIdentExpr(IdentExpr* expr) override; - void VisitGroupingExpr(GroupingExpr* expr) override; - void VisitLambdaExpr(LambdaExpr* expr) override; -}; diff --git a/nvse/nvse/NVSEVisitor.h b/nvse/nvse/NVSEVisitor.h deleted file mode 100644 index ddd9e438..00000000 --- a/nvse/nvse/NVSEVisitor.h +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -struct NVSEScript; - -struct BeginStmt; -struct FnDeclStmt; -struct VarDeclStmt; - -struct ExprStmt; -struct ForStmt; -struct ForEachStmt; -struct IfStmt; -struct ReturnStmt; -struct ContinueStmt; -struct BreakStmt; -struct WhileStmt; -struct BlockStmt; - -struct AssignmentExpr; -struct TernaryExpr; -struct InExpr; -struct BinaryExpr; -struct UnaryExpr; -struct SubscriptExpr; -struct CallExpr; -struct GetExpr; -struct BoolExpr; -struct NumberExpr; -struct StringExpr; -struct IdentExpr; -struct MapLiteralExpr; -struct ArrayLiteralExpr; -struct GroupingExpr; -struct LambdaExpr; - -class NVSEVisitor { -public: - virtual ~NVSEVisitor() = default; - - virtual void VisitNVSEScript(NVSEScript* script) = 0; - virtual void VisitBeginStmt(BeginStmt* stmt) = 0; - virtual void VisitFnStmt(FnDeclStmt* stmt) = 0; - virtual void VisitVarDeclStmt(VarDeclStmt* stmt) = 0; - - virtual void VisitExprStmt(const ExprStmt* stmt) = 0; - virtual void VisitForStmt(ForStmt* stmt) = 0; - virtual void VisitForEachStmt(ForEachStmt* stmt) = 0; - virtual void VisitIfStmt(IfStmt* stmt) = 0; - virtual void VisitReturnStmt(ReturnStmt* stmt) = 0; - virtual void VisitContinueStmt(ContinueStmt* stmt) = 0; - virtual void VisitBreakStmt(BreakStmt* stmt) = 0; - virtual void VisitWhileStmt(WhileStmt* stmt) = 0; - virtual void VisitBlockStmt(BlockStmt* stmt) = 0; - - virtual void VisitAssignmentExpr(AssignmentExpr* expr) = 0; - virtual void VisitTernaryExpr(TernaryExpr* expr) = 0; - virtual void VisitInExpr(InExpr* in_expr) = 0; - virtual void VisitBinaryExpr(BinaryExpr* expr) = 0; - virtual void VisitUnaryExpr(UnaryExpr* expr) = 0; - virtual void VisitSubscriptExpr(SubscriptExpr* expr) = 0; - virtual void VisitCallExpr(CallExpr* expr) = 0; - virtual void VisitGetExpr(GetExpr* expr) = 0; - virtual void VisitBoolExpr(BoolExpr* expr) = 0; - virtual void VisitNumberExpr(NumberExpr* expr) = 0; - virtual void VisitStringExpr(StringExpr* expr) = 0; - virtual void VisitIdentExpr(IdentExpr* expr) = 0; - virtual void VisitArrayLiteralExpr(ArrayLiteralExpr* expr) = 0; - virtual void VisitMapLiteralExpr(MapLiteralExpr* expr) = 0; - virtual void VisitGroupingExpr(GroupingExpr* expr) = 0; - virtual void VisitLambdaExpr(LambdaExpr* expr) = 0; -}; diff --git a/nvse/nvse/ScriptUtils.cpp b/nvse/nvse/ScriptUtils.cpp index 61cc2314..f5f9e549 100644 --- a/nvse/nvse/ScriptUtils.cpp +++ b/nvse/nvse/ScriptUtils.cpp @@ -25,8 +25,9 @@ #include "ScriptAnalyzer.h" #include "StackVariables.h" #include "Hooks_Other.h" -#include "NVSEParser.h" -#include "NVSETypeChecker.h" +#include "Compiler/Parser.h" +#include "Compiler/NVSETypeChecker.h" +#include "Compiler/Passes/VariableResolution.h" std::map, Script::VariableType> g_variableDefinitionsMap; @@ -2285,32 +2286,31 @@ bool ExpressionParser::ParseUserFunctionParameters(std::vectorblocks; if (blocks.size() != 1) { return false; } - + const auto& firstBlock = blocks[0]; - if (const auto fnDecl = dynamic_cast(&*firstBlock)) { + if (const auto fnDecl = dynamic_cast(&*firstBlock)) { for (auto& varDeclStmt : fnDecl->args) { - if (varDeclStmt->scopeVars.empty()) { + if (varDeclStmt->declarations.empty()) { return false; } - - const auto& scopeVar = varDeclStmt->scopeVars[0]; - out.emplace_back(scopeVar->index, scopeVar->variableType); + + const auto& scopeVar = varDeclStmt->declarations[0].info; + out.emplace_back(scopeVar->index, scopeVar->type); } } - + return true; } - + return false; } diff --git a/nvse/nvse/nvse.vcxproj.filters b/nvse/nvse/nvse.vcxproj.filters index 7076d63e..965ab27c 100644 --- a/nvse/nvse/nvse.vcxproj.filters +++ b/nvse/nvse/nvse.vcxproj.filters @@ -257,22 +257,22 @@ internals - + compiler - + compiler - + compiler - + compiler - + compiler - + compiler @@ -282,6 +282,8 @@ internals + + @@ -542,31 +544,28 @@ internals - + compiler - + compiler - + compiler - + compiler - + compiler - + compiler - + compiler - - compiler - - + compiler @@ -579,6 +578,8 @@ lib + + From 12186b1c58ff2ccd6918633998d2def459ce44a4 Mon Sep 17 00:00:00 2001 From: cnf13 Date: Sat, 31 Jan 2026 21:40:28 -0500 Subject: [PATCH 2/4] Oops --- nvse/nvse/ScriptUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nvse/nvse/ScriptUtils.cpp b/nvse/nvse/ScriptUtils.cpp index f5f9e549..1881fe7d 100644 --- a/nvse/nvse/ScriptUtils.cpp +++ b/nvse/nvse/ScriptUtils.cpp @@ -25,7 +25,7 @@ #include "ScriptAnalyzer.h" #include "StackVariables.h" #include "Hooks_Other.h" -#include "Compiler/Parser.h" +#include "Compiler/Parser/Parser.h" #include "Compiler/NVSETypeChecker.h" #include "Compiler/Passes/VariableResolution.h" From c289d70e94d05fd8c335b038680ad548ae4d55fb Mon Sep 17 00:00:00 2001 From: cnf13 Date: Sat, 7 Feb 2026 14:16:08 -0500 Subject: [PATCH 3/4] [New Compiler] Fix type-checker failing to validate type of command arg if the input is a form --- nvse/nvse/Compiler/AST/AST.h | 10 ++++++++++ nvse/nvse/Compiler/NVSETypeChecker.cpp | 10 ++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/nvse/nvse/Compiler/AST/AST.h b/nvse/nvse/Compiler/AST/AST.h index 4cca8931..c892665c 100644 --- a/nvse/nvse/Compiler/AST/AST.h +++ b/nvse/nvse/Compiler/AST/AST.h @@ -45,6 +45,11 @@ namespace Compiler { bool IsType() { return dynamic_cast(this); } + + template + T* As() { + return dynamic_cast(this); + } }; namespace Statements { @@ -296,6 +301,11 @@ namespace Compiler { bool IsType() { return dynamic_cast(this); } + + template + T* As() { + return dynamic_cast(this); + } }; namespace Expressions { diff --git a/nvse/nvse/Compiler/NVSETypeChecker.cpp b/nvse/nvse/Compiler/NVSETypeChecker.cpp index 6001b39a..0c248bbb 100644 --- a/nvse/nvse/Compiler/NVSETypeChecker.cpp +++ b/nvse/nvse/Compiler/NVSETypeChecker.cpp @@ -619,13 +619,14 @@ namespace Compiler { if (isDefaultParse(cmd->parse) || !_stricmp(cmd->longName, "ShowMessage")) { // Try to resolve identifiers as vanilla enums - const auto ident = dynamic_cast(arg.get()); + const auto ident = arg->As(); uint32_t idx = -1; uint32_t len = 0; if (ident) { resolveVanillaEnum(param, ident->token.lexeme.c_str(), &idx, &len); } + if (idx != -1) { CompDbg("[line %d] INFO: Converting identifier '%s' to enum index %d\n", arg->line, ident->token.lexeme.c_str(), idx); arg = std::make_shared(NVSEToken{}, static_cast(idx), false, len); @@ -636,7 +637,12 @@ namespace Compiler { WRAP_ERROR(arg->Accept(this)) } - if (ident && arg->type == kTokenType_Form) { + if ( + ident && + arg->type == kTokenType_Form && + param->typeID >= kParamType_ObjectRef && + param->typeID <= kParamType_Region + ) { // Extract form from param if (!doesFormMatchParamType(ident->form, static_cast(param->typeID))) { if (!param->isOptional) { From 13de6e70ff1ec37ebc0b58ac9c4659f1bf1368ff Mon Sep 17 00:00:00 2001 From: cnf13 Date: Tue, 10 Feb 2026 19:09:08 -0500 Subject: [PATCH 4/4] Update vcxproj --- nvse/nvse/nvse.vcxproj | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/nvse/nvse/nvse.vcxproj b/nvse/nvse/nvse.vcxproj index 54545ec6..839b7576 100644 --- a/nvse/nvse/nvse.vcxproj +++ b/nvse/nvse/nvse.vcxproj @@ -530,6 +530,8 @@ xcopy "$(ProjectDir)unit_tests\new_compiler" "$(FalloutNVPath)\Data\NVSE\unit_te true + + true @@ -647,12 +649,12 @@ xcopy "$(ProjectDir)unit_tests\new_compiler" "$(FalloutNVPath)\Data\NVSE\unit_te - - - - - - + + + + + + @@ -711,6 +713,8 @@ xcopy "$(ProjectDir)unit_tests\new_compiler" "$(FalloutNVPath)\Data\NVSE\unit_te + + @@ -758,15 +762,14 @@ xcopy "$(ProjectDir)unit_tests\new_compiler" "$(FalloutNVPath)\Data\NVSE\unit_te - - - - - - - - - + + + + + + + +