Step 407: add standalone VB.NET parser and parse routing
This commit is contained in:
@@ -2542,4 +2542,13 @@ target_link_libraries(step406_test PRIVATE
|
|||||||
tree_sitter_javascript tree_sitter_typescript
|
tree_sitter_javascript tree_sitter_typescript
|
||||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||||
|
|
||||||
|
add_executable(step407_test tests/step407_test.cpp)
|
||||||
|
target_include_directories(step407_test PRIVATE src)
|
||||||
|
target_link_libraries(step407_test PRIVATE
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
|
unofficial::tree-sitter::tree-sitter
|
||||||
|
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
|
||||||
|
tree_sitter_javascript tree_sitter_typescript
|
||||||
|
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||||
|
|
||||||
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)
|
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)
|
||||||
|
|||||||
@@ -122,6 +122,10 @@ public:
|
|||||||
auto pr = FSharpParser::parseFSharpWithDiagnostics(source);
|
auto pr = FSharpParser::parseFSharpWithDiagnostics(source);
|
||||||
diags = std::move(pr.diagnostics);
|
diags = std::move(pr.diagnostics);
|
||||||
return std::move(pr.module);
|
return std::move(pr.module);
|
||||||
|
} else if (language == "vbnet" || language == "vb" || language == "vb.net") {
|
||||||
|
auto pr = VBNetParser::parseVBNetWithDiagnostics(source);
|
||||||
|
diags = std::move(pr.diagnostics);
|
||||||
|
return std::move(pr.module);
|
||||||
} else if (language == "c") {
|
} else if (language == "c") {
|
||||||
auto pr = CParser::parseCWithDiagnostics(source);
|
auto pr = CParser::parseCWithDiagnostics(source);
|
||||||
diags = std::move(pr.diagnostics);
|
diags = std::move(pr.diagnostics);
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ private:
|
|||||||
#include "ast/KotlinParser.h"
|
#include "ast/KotlinParser.h"
|
||||||
#include "ast/CSharpParser.h"
|
#include "ast/CSharpParser.h"
|
||||||
#include "ast/FSharpParser.h"
|
#include "ast/FSharpParser.h"
|
||||||
|
#include "ast/VBNetParser.h"
|
||||||
#include "ast/CParser.h"
|
#include "ast/CParser.h"
|
||||||
#include "ast/WatParser.h"
|
#include "ast/WatParser.h"
|
||||||
#include "ast/CommonLispParser.h"
|
#include "ast/CommonLispParser.h"
|
||||||
|
|||||||
184
editor/src/ast/VBNetParser.h
Normal file
184
editor/src/ast/VBNetParser.h
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ASTNode.h"
|
||||||
|
#include "Module.h"
|
||||||
|
#include "Function.h"
|
||||||
|
#include "Variable.h"
|
||||||
|
#include "Statement.h"
|
||||||
|
#include "ClassDeclaration.h"
|
||||||
|
#include "EnumNamespaceNodes.h"
|
||||||
|
#include "../ast/Parser.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
|
class VBNetParser {
|
||||||
|
public:
|
||||||
|
static std::unique_ptr<Module> parseVBNet(const std::string& source) {
|
||||||
|
auto module = std::make_unique<Module>();
|
||||||
|
module->id = IdGenerator::next("mod");
|
||||||
|
module->name = "parsed_vbnet_module";
|
||||||
|
module->targetLanguage = "vbnet";
|
||||||
|
parseTopLevel(source, module.get());
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ParseResult parseVBNetWithDiagnostics(const std::string& source) {
|
||||||
|
ParseResult result;
|
||||||
|
result.module = parseVBNet(source);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void parseTopLevel(const std::string& source, Module* module) {
|
||||||
|
std::istringstream stream(source);
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
while (std::getline(stream, line)) {
|
||||||
|
std::string trimmed = trim(line);
|
||||||
|
if (trimmed.empty()) continue;
|
||||||
|
if (startsWithCaseInsensitive(trimmed, "'")) continue;
|
||||||
|
|
||||||
|
if (startsWithCaseInsensitive(trimmed, "Class ")) {
|
||||||
|
auto* cls = new ClassDeclaration();
|
||||||
|
cls->id = IdGenerator::next("cls");
|
||||||
|
cls->name = extractNameAfterKeyword(trimmed, "Class ");
|
||||||
|
module->addChild("classes", cls);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startsWithCaseInsensitive(trimmed, "Interface ")) {
|
||||||
|
auto* iface = new InterfaceDeclaration();
|
||||||
|
iface->id = IdGenerator::next("iface");
|
||||||
|
iface->name = extractNameAfterKeyword(trimmed, "Interface ");
|
||||||
|
module->addChild("interfaces", iface);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startsWithCaseInsensitive(trimmed, "Module ")) {
|
||||||
|
auto* ns = new NamespaceDeclaration();
|
||||||
|
ns->id = IdGenerator::next("ns");
|
||||||
|
ns->name = extractNameAfterKeyword(trimmed, "Module ");
|
||||||
|
module->addChild("statements", ns);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (containsCaseInsensitive(trimmed, "Sub ") &&
|
||||||
|
!startsWithCaseInsensitive(trimmed, "End Sub")) {
|
||||||
|
auto* fn = new Function();
|
||||||
|
fn->id = IdGenerator::next("fn");
|
||||||
|
fn->name = extractMethodName(trimmed, "Sub ");
|
||||||
|
module->addChild("functions", fn);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (containsCaseInsensitive(trimmed, "Function ") &&
|
||||||
|
!startsWithCaseInsensitive(trimmed, "End Function")) {
|
||||||
|
auto* fn = new Function();
|
||||||
|
fn->id = IdGenerator::next("fn");
|
||||||
|
fn->name = extractMethodName(trimmed, "Function ");
|
||||||
|
module->addChild("functions", fn);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startsWithCaseInsensitive(trimmed, "Dim ")) {
|
||||||
|
auto* var = new Variable();
|
||||||
|
var->id = IdGenerator::next("var");
|
||||||
|
var->name = extractDimName(trimmed);
|
||||||
|
module->addChild("variables", var);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startsWithCaseInsensitive(trimmed, "If ") &&
|
||||||
|
containsCaseInsensitive(trimmed, " Then")) {
|
||||||
|
auto* ifs = new IfStatement();
|
||||||
|
ifs->id = IdGenerator::next("if");
|
||||||
|
module->addChild("statements", ifs);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startsWithCaseInsensitive(trimmed, "For Each ") &&
|
||||||
|
containsCaseInsensitive(trimmed, " In ")) {
|
||||||
|
auto* loop = new ForLoop();
|
||||||
|
loop->id = IdGenerator::next("for");
|
||||||
|
loop->iteratorName = extractForEachIterator(trimmed);
|
||||||
|
module->addChild("statements", loop);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string extractForEachIterator(const std::string& line) {
|
||||||
|
std::string lower = toLower(line);
|
||||||
|
size_t start = lower.find("for each ");
|
||||||
|
if (start == std::string::npos) return "item";
|
||||||
|
start += 9;
|
||||||
|
size_t inPos = lower.find(" in ", start);
|
||||||
|
if (inPos == std::string::npos) return "item";
|
||||||
|
std::string token = trim(line.substr(start, inPos - start));
|
||||||
|
// "x As Type" => keep x only
|
||||||
|
auto asPos = toLower(token).find(" as ");
|
||||||
|
if (asPos != std::string::npos) token = trim(token.substr(0, asPos));
|
||||||
|
return token.empty() ? "item" : token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string extractMethodName(const std::string& line, const std::string& keyword) {
|
||||||
|
std::string lower = toLower(line);
|
||||||
|
std::string lowerKeyword = toLower(keyword);
|
||||||
|
auto pos = lower.find(lowerKeyword);
|
||||||
|
if (pos == std::string::npos) return "unknown";
|
||||||
|
pos += keyword.size();
|
||||||
|
auto end = line.find('(', pos);
|
||||||
|
if (end == std::string::npos) end = line.find(' ', pos);
|
||||||
|
if (end == std::string::npos) end = line.size();
|
||||||
|
return trim(line.substr(pos, end - pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string extractDimName(const std::string& line) {
|
||||||
|
auto after = trim(line.substr(4)); // after "Dim "
|
||||||
|
auto asPos = toLower(after).find(" as ");
|
||||||
|
auto eqPos = after.find('=');
|
||||||
|
size_t end = std::string::npos;
|
||||||
|
if (asPos != std::string::npos && eqPos != std::string::npos) end = std::min(asPos, eqPos);
|
||||||
|
else if (asPos != std::string::npos) end = asPos;
|
||||||
|
else if (eqPos != std::string::npos) end = eqPos;
|
||||||
|
if (end == std::string::npos) end = after.size();
|
||||||
|
return trim(after.substr(0, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string extractNameAfterKeyword(const std::string& line, const std::string& keyword) {
|
||||||
|
auto pos = line.find(keyword);
|
||||||
|
if (pos == std::string::npos) return "unknown";
|
||||||
|
pos += keyword.size();
|
||||||
|
auto end = line.find_first_of("(: ", pos);
|
||||||
|
if (end == std::string::npos) end = line.size();
|
||||||
|
return trim(line.substr(pos, end - pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool startsWithCaseInsensitive(const std::string& s, const std::string& prefix) {
|
||||||
|
if (prefix.size() > s.size()) return false;
|
||||||
|
for (size_t i = 0; i < prefix.size(); ++i) {
|
||||||
|
if (std::tolower(static_cast<unsigned char>(s[i])) !=
|
||||||
|
std::tolower(static_cast<unsigned char>(prefix[i]))) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool containsCaseInsensitive(const std::string& s, const std::string& needle) {
|
||||||
|
return toLower(s).find(toLower(needle)) != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string toLower(const std::string& s) {
|
||||||
|
std::string out = s;
|
||||||
|
for (char& c : out) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string trim(const std::string& s) {
|
||||||
|
auto start = s.find_first_not_of(" \t\r\n");
|
||||||
|
if (start == std::string::npos) return "";
|
||||||
|
auto end = s.find_last_not_of(" \t\r\n");
|
||||||
|
return s.substr(start, end - start + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
205
editor/tests/step407_test.cpp
Normal file
205
editor/tests/step407_test.cpp
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
// Step 407: VB.NET Parser Tests (12 tests)
|
||||||
|
|
||||||
|
#include "ast/VBNetParser.h"
|
||||||
|
#include "Pipeline.h"
|
||||||
|
#include "ast/Function.h"
|
||||||
|
#include "ast/Variable.h"
|
||||||
|
#include "ast/ClassDeclaration.h"
|
||||||
|
#include "ast/EnumNamespaceNodes.h"
|
||||||
|
#include "ast/Statement.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
static int passed = 0, failed = 0;
|
||||||
|
#define TEST(name) { std::cout << " " << #name << "... "; }
|
||||||
|
#define PASS() { std::cout << "PASS\n"; ++passed; }
|
||||||
|
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
|
||||||
|
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
|
||||||
|
|
||||||
|
void test_parse_sub_method() {
|
||||||
|
TEST(parse_sub_method);
|
||||||
|
auto mod = VBNetParser::parseVBNet(
|
||||||
|
"Public Sub DoWork(x As Integer)\n"
|
||||||
|
"End Sub\n");
|
||||||
|
CHECK(mod != nullptr, "module is null");
|
||||||
|
auto fns = mod->getChildren("functions");
|
||||||
|
CHECK(fns.size() == 1, "expected 1 function");
|
||||||
|
auto* fn = static_cast<Function*>(fns[0]);
|
||||||
|
CHECK(fn->name == "DoWork", "expected method DoWork");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_parse_function_method() {
|
||||||
|
TEST(parse_function_method);
|
||||||
|
auto mod = VBNetParser::parseVBNet(
|
||||||
|
"Public Function Sum(a As Integer, b As Integer) As Integer\n"
|
||||||
|
"End Function\n");
|
||||||
|
CHECK(mod != nullptr, "module is null");
|
||||||
|
auto fns = mod->getChildren("functions");
|
||||||
|
CHECK(fns.size() == 1, "expected 1 function");
|
||||||
|
auto* fn = static_cast<Function*>(fns[0]);
|
||||||
|
CHECK(fn->name == "Sum", "expected method Sum");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_parse_class() {
|
||||||
|
TEST(parse_class);
|
||||||
|
auto mod = VBNetParser::parseVBNet(
|
||||||
|
"Class Person\n"
|
||||||
|
"End Class\n");
|
||||||
|
CHECK(mod != nullptr, "module is null");
|
||||||
|
auto classes = mod->getChildren("classes");
|
||||||
|
CHECK(classes.size() == 1, "expected 1 class");
|
||||||
|
auto* cls = static_cast<ClassDeclaration*>(classes[0]);
|
||||||
|
CHECK(cls->name == "Person", "expected class Person");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_parse_interface() {
|
||||||
|
TEST(parse_interface);
|
||||||
|
auto mod = VBNetParser::parseVBNet(
|
||||||
|
"Interface IWorker\n"
|
||||||
|
"End Interface\n");
|
||||||
|
CHECK(mod != nullptr, "module is null");
|
||||||
|
auto ifaces = mod->getChildren("interfaces");
|
||||||
|
CHECK(ifaces.size() == 1, "expected 1 interface");
|
||||||
|
auto* iface = static_cast<InterfaceDeclaration*>(ifaces[0]);
|
||||||
|
CHECK(iface->name == "IWorker", "expected interface IWorker");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_parse_module_namespace() {
|
||||||
|
TEST(parse_module_namespace);
|
||||||
|
auto mod = VBNetParser::parseVBNet(
|
||||||
|
"Module Utils\n"
|
||||||
|
"End Module\n");
|
||||||
|
CHECK(mod != nullptr, "module is null");
|
||||||
|
auto stmts = mod->getChildren("statements");
|
||||||
|
NamespaceDeclaration* ns = nullptr;
|
||||||
|
for (auto* s : stmts) {
|
||||||
|
if (s->conceptType == "NamespaceDeclaration") ns = static_cast<NamespaceDeclaration*>(s);
|
||||||
|
}
|
||||||
|
CHECK(ns != nullptr, "expected namespace declaration");
|
||||||
|
CHECK(ns->name == "Utils", "expected module name Utils");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_parse_dim_variable() {
|
||||||
|
TEST(parse_dim_variable);
|
||||||
|
auto mod = VBNetParser::parseVBNet("Dim x As Integer = 5\n");
|
||||||
|
CHECK(mod != nullptr, "module is null");
|
||||||
|
auto vars = mod->getChildren("variables");
|
||||||
|
CHECK(vars.size() == 1, "expected 1 variable");
|
||||||
|
auto* var = static_cast<Variable*>(vars[0]);
|
||||||
|
CHECK(var->name == "x", "expected variable x");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_parse_if_then_block() {
|
||||||
|
TEST(parse_if_then_block);
|
||||||
|
auto mod = VBNetParser::parseVBNet(
|
||||||
|
"If x > 0 Then\n"
|
||||||
|
"ElseIf x < 0 Then\n"
|
||||||
|
"Else\n"
|
||||||
|
"End If\n");
|
||||||
|
CHECK(mod != nullptr, "module is null");
|
||||||
|
bool foundIf = false;
|
||||||
|
for (auto* s : mod->getChildren("statements")) {
|
||||||
|
if (s->conceptType == "IfStatement") foundIf = true;
|
||||||
|
}
|
||||||
|
CHECK(foundIf, "expected IfStatement marker");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_parse_for_each_block() {
|
||||||
|
TEST(parse_for_each_block);
|
||||||
|
auto mod = VBNetParser::parseVBNet(
|
||||||
|
"For Each item In collection\n"
|
||||||
|
"Next\n");
|
||||||
|
CHECK(mod != nullptr, "module is null");
|
||||||
|
bool foundFor = false;
|
||||||
|
for (auto* s : mod->getChildren("statements")) {
|
||||||
|
if (s->conceptType == "ForLoop") {
|
||||||
|
auto* loop = static_cast<ForLoop*>(s);
|
||||||
|
CHECK(loop->iteratorName == "item", "expected iterator item");
|
||||||
|
foundFor = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CHECK(foundFor, "expected ForLoop marker");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_case_insensitive_keywords() {
|
||||||
|
TEST(case_insensitive_keywords);
|
||||||
|
auto mod = VBNetParser::parseVBNet(
|
||||||
|
"public sub Run()\n"
|
||||||
|
"end sub\n");
|
||||||
|
CHECK(mod != nullptr, "module is null");
|
||||||
|
CHECK(mod->getChildren("functions").size() == 1, "expected function with lowercase keyword");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_parse_with_diagnostics() {
|
||||||
|
TEST(parse_with_diagnostics);
|
||||||
|
auto result = VBNetParser::parseVBNetWithDiagnostics(
|
||||||
|
"Function Echo(value As String) As String\n"
|
||||||
|
"End Function\n");
|
||||||
|
CHECK(result.module != nullptr, "module is null");
|
||||||
|
CHECK(result.module->getChildren("functions").size() == 1, "expected 1 function");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_parse_mixed_file() {
|
||||||
|
TEST(parse_mixed_file);
|
||||||
|
auto mod = VBNetParser::parseVBNet(
|
||||||
|
"' comment\n"
|
||||||
|
"Module App\n"
|
||||||
|
"End Module\n"
|
||||||
|
"Class Person\n"
|
||||||
|
"End Class\n"
|
||||||
|
"Public Sub Main()\n"
|
||||||
|
"End Sub\n"
|
||||||
|
"Dim count As Integer = 1\n");
|
||||||
|
CHECK(mod != nullptr, "module is null");
|
||||||
|
CHECK(mod->getChildren("statements").size() >= 1, "expected module statement");
|
||||||
|
CHECK(mod->getChildren("classes").size() == 1, "expected class");
|
||||||
|
CHECK(mod->getChildren("functions").size() == 1, "expected function");
|
||||||
|
CHECK(mod->getChildren("variables").size() == 1, "expected variable");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_pipeline_parse_routing() {
|
||||||
|
TEST(pipeline_parse_routing);
|
||||||
|
Pipeline p;
|
||||||
|
std::vector<ParseDiagnostic> d1, d2, d3;
|
||||||
|
auto m1 = p.parse("Sub Main()\nEnd Sub\n", "vbnet", d1);
|
||||||
|
auto m2 = p.parse("Sub Main()\nEnd Sub\n", "vb", d2);
|
||||||
|
auto m3 = p.parse("Sub Main()\nEnd Sub\n", "vb.net", d3);
|
||||||
|
CHECK(m1 != nullptr && m2 != nullptr && m3 != nullptr, "expected parse success for aliases");
|
||||||
|
CHECK(m1->targetLanguage == "vbnet", "expected vbnet target language");
|
||||||
|
CHECK(m2->targetLanguage == "vbnet", "expected vb alias target language");
|
||||||
|
CHECK(m3->targetLanguage == "vbnet", "expected vb.net alias target language");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << "Step 407: VB.NET Parser Tests\n";
|
||||||
|
|
||||||
|
test_parse_sub_method(); // 1
|
||||||
|
test_parse_function_method(); // 2
|
||||||
|
test_parse_class(); // 3
|
||||||
|
test_parse_interface(); // 4
|
||||||
|
test_parse_module_namespace(); // 5
|
||||||
|
test_parse_dim_variable(); // 6
|
||||||
|
test_parse_if_then_block(); // 7
|
||||||
|
test_parse_for_each_block(); // 8
|
||||||
|
test_case_insensitive_keywords(); // 9
|
||||||
|
test_parse_with_diagnostics(); // 10
|
||||||
|
test_parse_mixed_file(); // 11
|
||||||
|
test_pipeline_parse_routing(); // 12
|
||||||
|
|
||||||
|
std::cout << "\nResults: " << passed << "/" << (passed + failed)
|
||||||
|
<< " passed\n";
|
||||||
|
return failed == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
48
progress.md
48
progress.md
@@ -3725,6 +3725,54 @@ output, record-style class output, and F#-style type mappings.
|
|||||||
- `editor/src/MCPServer.h` (`1679` > `600`)
|
- `editor/src/MCPServer.h` (`1679` > `600`)
|
||||||
- `editor/src/HeadlessAgentRPCHandler.h` (`2623` > `600`)
|
- `editor/src/HeadlessAgentRPCHandler.h` (`2623` > `600`)
|
||||||
|
|
||||||
|
### Step 407: VB.NET Parser
|
||||||
|
**Status:** PASS (12/12 tests)
|
||||||
|
|
||||||
|
Added a standalone VB.NET parser with case-insensitive keyword handling for
|
||||||
|
core block forms and declarations. Covers `Sub`/`Function`, `Class`,
|
||||||
|
`Interface`, `Module`, `Dim`, `If...Then`, and `For Each...In` recognition.
|
||||||
|
|
||||||
|
**Files created:**
|
||||||
|
- `editor/src/ast/VBNetParser.h` — VB.NET parse support:
|
||||||
|
- `parseVBNet(...)` and `parseVBNetWithDiagnostics(...)`
|
||||||
|
- `Sub` and `Function` signature extraction
|
||||||
|
- `Class` and `Interface` declaration extraction
|
||||||
|
- `Module` mapping to `NamespaceDeclaration`
|
||||||
|
- `Dim ... As ...` variable extraction
|
||||||
|
- `If ... Then` marker to `IfStatement`
|
||||||
|
- `For Each ... In ...` marker to `ForLoop` with iterator extraction
|
||||||
|
- case-insensitive token matching helpers
|
||||||
|
- `editor/tests/step407_test.cpp` — 12 tests covering:
|
||||||
|
1. `Sub` parsing
|
||||||
|
2. `Function` parsing
|
||||||
|
3. class parsing
|
||||||
|
4. interface parsing
|
||||||
|
5. module/namespace parsing
|
||||||
|
6. `Dim` variable parsing
|
||||||
|
7. `If...Then` mapping
|
||||||
|
8. `For Each...In` mapping
|
||||||
|
9. case-insensitive keyword handling
|
||||||
|
10. diagnostics entry point
|
||||||
|
11. mixed-file parsing
|
||||||
|
12. pipeline parse routing for `vbnet` / `vb` / `vb.net`
|
||||||
|
|
||||||
|
**Files modified:**
|
||||||
|
- `editor/src/ast/Parser.h` — include `ast/VBNetParser.h`
|
||||||
|
- `editor/src/Pipeline.h` — parse routing for `vbnet`, `vb`, `vb.net`
|
||||||
|
- `editor/CMakeLists.txt` — `step407_test` target
|
||||||
|
|
||||||
|
**Verification run:**
|
||||||
|
- `step407_test` — PASS (12/12) new step coverage
|
||||||
|
- `step406_test` — PASS (12/12) regression coverage
|
||||||
|
- `step405_test` — PASS (12/12) regression coverage
|
||||||
|
|
||||||
|
**Architecture gate check:**
|
||||||
|
- `editor/src/ast/VBNetParser.h` within header-size limit (`184` <= `600`)
|
||||||
|
- `editor/tests/step407_test.cpp` within test-file size guidance (`205` lines)
|
||||||
|
- Legacy oversized headers persist:
|
||||||
|
- `editor/src/MCPServer.h` (`1679` > `600`)
|
||||||
|
- `editor/src/HeadlessAgentRPCHandler.h` (`2623` > `600`)
|
||||||
|
|
||||||
# Roadmap Planning — Sprints 12-25+
|
# Roadmap Planning — Sprints 12-25+
|
||||||
|
|
||||||
## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)
|
## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)
|
||||||
|
|||||||
Reference in New Issue
Block a user