Step 361: C Parser Functions Structs Enums (12/12 tests)
This commit is contained in:
@@ -2138,4 +2138,13 @@ add_executable(step360_test tests/step360_test.cpp)
|
||||
target_include_directories(step360_test PRIVATE src)
|
||||
target_link_libraries(step360_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
|
||||
add_executable(step361_test tests/step361_test.cpp)
|
||||
target_include_directories(step361_test PRIVATE src)
|
||||
target_link_libraries(step361_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)
|
||||
|
||||
@@ -118,6 +118,10 @@ public:
|
||||
auto pr = CSharpParser::parseCSharpWithDiagnostics(source);
|
||||
diags = std::move(pr.diagnostics);
|
||||
return std::move(pr.module);
|
||||
} else if (language == "c") {
|
||||
auto pr = CParser::parseCWithDiagnostics(source);
|
||||
diags = std::move(pr.diagnostics);
|
||||
return std::move(pr.module);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
288
editor/src/ast/CParser.h
Normal file
288
editor/src/ast/CParser.h
Normal file
@@ -0,0 +1,288 @@
|
||||
#pragma once
|
||||
#include "ASTNode.h"
|
||||
#include "Module.h"
|
||||
#include "Function.h"
|
||||
#include "Variable.h"
|
||||
#include "Parameter.h"
|
||||
#include "Type.h"
|
||||
#include "ClassDeclaration.h"
|
||||
#include "PreprocessorNodes.h"
|
||||
#include "EnumNamespaceNodes.h"
|
||||
#include "../ast/Parser.h"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class CParser {
|
||||
public:
|
||||
static std::unique_ptr<Module> parseC(const std::string& source) {
|
||||
auto module = std::make_unique<Module>();
|
||||
module->id = IdGenerator::next("mod");
|
||||
module->name = "parsed_c_module";
|
||||
module->targetLanguage = "c";
|
||||
|
||||
parsePreprocessor(source, module.get());
|
||||
std::string cleaned = stripComments(source);
|
||||
parseTypedefStructs(cleaned, module.get());
|
||||
parseStructs(cleaned, module.get());
|
||||
parseEnums(cleaned, module.get());
|
||||
parseFunctions(cleaned, module.get());
|
||||
parseTopLevelVariables(cleaned, module.get());
|
||||
return module;
|
||||
}
|
||||
|
||||
static ParseResult parseCWithDiagnostics(const std::string& source) {
|
||||
ParseResult result;
|
||||
result.module = parseC(source);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string trim(const std::string& s) {
|
||||
size_t start = s.find_first_not_of(" \t\r\n");
|
||||
if (start == std::string::npos) return "";
|
||||
size_t end = s.find_last_not_of(" \t\r\n");
|
||||
return s.substr(start, end - start + 1);
|
||||
}
|
||||
|
||||
static bool startsWith(const std::string& s, const std::string& prefix) {
|
||||
return s.rfind(prefix, 0) == 0;
|
||||
}
|
||||
|
||||
static std::string stripComments(const std::string& source) {
|
||||
std::string out;
|
||||
out.reserve(source.size());
|
||||
bool line = false, block = false, str = false, chr = false;
|
||||
for (size_t i = 0; i < source.size(); ++i) {
|
||||
char c = source[i];
|
||||
char n = (i + 1 < source.size()) ? source[i + 1] : '\0';
|
||||
if (line) {
|
||||
if (c == '\n') { line = false; out.push_back('\n'); } else out.push_back(' ');
|
||||
continue;
|
||||
}
|
||||
if (block) {
|
||||
if (c == '*' && n == '/') { block = false; out += " "; ++i; }
|
||||
else out.push_back(c == '\n' ? '\n' : ' ');
|
||||
continue;
|
||||
}
|
||||
if (!str && !chr && c == '/' && n == '/') { line = true; out += " "; ++i; continue; }
|
||||
if (!str && !chr && c == '/' && n == '*') { block = true; out += " "; ++i; continue; }
|
||||
if (c == '"' && !chr && !(i > 0 && source[i - 1] == '\\')) str = !str;
|
||||
if (c == '\'' && !str && !(i > 0 && source[i - 1] == '\\')) chr = !chr;
|
||||
out.push_back(c);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::vector<std::string> splitTopLevel(const std::string& input, char sep) {
|
||||
std::vector<std::string> parts;
|
||||
std::string cur;
|
||||
int depth = 0;
|
||||
for (char c : input) {
|
||||
if (c == '(') depth++;
|
||||
if (c == ')') depth--;
|
||||
if (c == sep && depth == 0) { parts.push_back(trim(cur)); cur.clear(); }
|
||||
else cur.push_back(c);
|
||||
}
|
||||
if (!trim(cur).empty()) parts.push_back(trim(cur));
|
||||
return parts;
|
||||
}
|
||||
|
||||
static void setType(ASTNode* owner, const std::string& role, const std::string& raw) {
|
||||
std::string t = trim(raw);
|
||||
if (t.empty()) return;
|
||||
static const std::vector<std::string> p = {
|
||||
"void","char","short","int","long","float","double","signed","unsigned",
|
||||
"size_t","ssize_t","bool","_Bool","const","static","extern"
|
||||
};
|
||||
bool primitive = false;
|
||||
for (const auto& k : p) {
|
||||
if (t == k || startsWith(t, k + " ") || startsWith(t, k + "*")) { primitive = true; break; }
|
||||
}
|
||||
if (primitive) owner->setChild(role, new PrimitiveType(IdGenerator::next("type"), t));
|
||||
else owner->setChild(role, new CustomType(IdGenerator::next("type"), t));
|
||||
}
|
||||
|
||||
static void parsePreprocessor(const std::string& source, Module* module) {
|
||||
std::istringstream in(source);
|
||||
std::string line;
|
||||
std::regex inc(R"(^\s*#\s*include\s*([<"])([^>"]+)[>"]\s*$)");
|
||||
std::regex defFn(R"(^\s*#\s*define\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*(.*)$)");
|
||||
std::regex defObj(R"(^\s*#\s*define\s+([A-Za-z_]\w*)\s*(.*)$)");
|
||||
std::regex prag(R"(^\s*#\s*pragma\s+(.*)$)");
|
||||
std::smatch m;
|
||||
while (std::getline(in, line)) {
|
||||
std::string t = trim(line);
|
||||
if (std::regex_match(line, m, inc)) {
|
||||
auto* node = new IncludeDirective(IdGenerator::next("inc"), trim(m[2].str()), m[1].str() == "<");
|
||||
module->addChild("statements", node);
|
||||
} else if (std::regex_match(line, m, defFn)) {
|
||||
auto* node = new MacroDefinition(IdGenerator::next("mac"), m[1].str());
|
||||
node->isFunctionLike = true;
|
||||
node->body = trim(m[3].str());
|
||||
for (const auto& part : splitTopLevel(m[2].str(), ',')) if (!part.empty()) node->parameters.push_back(part);
|
||||
module->addChild("statements", node);
|
||||
} else if (std::regex_match(line, m, defObj)) {
|
||||
auto* node = new MacroDefinition(IdGenerator::next("mac"), m[1].str());
|
||||
node->isFunctionLike = false;
|
||||
node->body = trim(m[2].str());
|
||||
module->addChild("statements", node);
|
||||
} else if (std::regex_match(line, m, prag)) {
|
||||
auto* node = new PragmaDirective(IdGenerator::next("prag"), trim(m[1].str()));
|
||||
module->addChild("statements", node);
|
||||
} else if (startsWith(t, "#ifndef") || startsWith(t, "#ifdef") || startsWith(t, "#endif")) {
|
||||
auto* node = new PragmaDirective(IdGenerator::next("prag"), t.substr(1));
|
||||
module->addChild("statements", node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void parseStructBody(const std::string& body, ClassDeclaration* cls) {
|
||||
std::istringstream in(body);
|
||||
std::string line;
|
||||
std::regex field(R"((?:const\s+)?([A-Za-z_]\w*(?:\s+\w+)?(?:\s*\*+)?)\s+([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?\s*;)");
|
||||
while (std::getline(in, line)) {
|
||||
for (auto it = std::sregex_iterator(line.begin(), line.end(), field); it != std::sregex_iterator(); ++it) {
|
||||
std::smatch m = *it;
|
||||
auto* v = new Variable(IdGenerator::next("var"), m[2].str());
|
||||
setType(v, "type", m[1].str());
|
||||
cls->addChild("fields", v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool insideTypedefStruct(const std::string& src, size_t pos) {
|
||||
std::regex rx(R"(typedef\s+struct\s*([A-Za-z_]\w*)?\s*\{[\s\S]*?\}\s*([A-Za-z_]\w*)\s*;)");
|
||||
for (auto it = std::sregex_iterator(src.begin(), src.end(), rx); it != std::sregex_iterator(); ++it) {
|
||||
size_t p = static_cast<size_t>((*it).position());
|
||||
size_t len = static_cast<size_t>((*it).length());
|
||||
if (pos >= p && pos < p + len) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void parseTypedefStructs(const std::string& src, Module* module) {
|
||||
std::regex rx(R"(typedef\s+struct\s*([A-Za-z_]\w*)?\s*\{([\s\S]*?)\}\s*([A-Za-z_]\w*)\s*;)");
|
||||
for (auto it = std::sregex_iterator(src.begin(), src.end(), rx); it != std::sregex_iterator(); ++it) {
|
||||
std::smatch m = *it;
|
||||
std::string sName = trim(m[1].str());
|
||||
std::string alias = trim(m[3].str());
|
||||
std::string className = sName.empty() ? alias : sName;
|
||||
auto* cls = new ClassDeclaration(IdGenerator::next("cls"), className);
|
||||
parseStructBody(m[2].str(), cls);
|
||||
module->addChild("classes", cls);
|
||||
auto* ta = new TypeAlias(IdGenerator::next("typedef"), alias,
|
||||
sName.empty() ? ("struct " + alias) : ("struct " + sName), false);
|
||||
module->addChild("statements", ta);
|
||||
}
|
||||
}
|
||||
|
||||
static void parseStructs(const std::string& src, Module* module) {
|
||||
std::regex rx(R"(struct\s+([A-Za-z_]\w*)\s*\{([\s\S]*?)\}\s*;)");
|
||||
for (auto it = std::sregex_iterator(src.begin(), src.end(), rx); it != std::sregex_iterator(); ++it) {
|
||||
size_t pos = static_cast<size_t>((*it).position());
|
||||
if (insideTypedefStruct(src, pos)) continue;
|
||||
std::smatch m = *it;
|
||||
auto* cls = new ClassDeclaration(IdGenerator::next("cls"), trim(m[1].str()));
|
||||
parseStructBody(m[2].str(), cls);
|
||||
module->addChild("classes", cls);
|
||||
}
|
||||
}
|
||||
|
||||
static void parseEnums(const std::string& src, Module* module) {
|
||||
std::regex rx(R"(enum\s*([A-Za-z_]\w*)?\s*\{([\s\S]*?)\}\s*;)");
|
||||
int anon = 0;
|
||||
for (auto it = std::sregex_iterator(src.begin(), src.end(), rx); it != std::sregex_iterator(); ++it) {
|
||||
std::smatch m = *it;
|
||||
auto* e = new EnumDeclaration();
|
||||
e->id = IdGenerator::next("enum");
|
||||
e->name = trim(m[1].str());
|
||||
if (e->name.empty()) e->name = "anonymous_enum_" + std::to_string(++anon);
|
||||
for (const auto& part : splitTopLevel(m[2].str(), ',')) {
|
||||
if (part.empty()) continue;
|
||||
size_t eq = part.find('=');
|
||||
std::string n = trim(eq == std::string::npos ? part : part.substr(0, eq));
|
||||
std::string v = trim(eq == std::string::npos ? "" : part.substr(eq + 1));
|
||||
if (!n.empty()) e->addChild("members", new EnumMember(IdGenerator::next("em"), n, v));
|
||||
}
|
||||
module->addChild("statements", e);
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<std::pair<std::string, std::string>> parseParams(const std::string& txt) {
|
||||
std::vector<std::pair<std::string, std::string>> out;
|
||||
std::string p = trim(txt);
|
||||
if (p.empty() || p == "void") return out;
|
||||
std::regex fnPtr(R"(^(.+?)\(\s*\*\s*([A-Za-z_]\w*)\s*\)\s*\((.*)\)\s*$)");
|
||||
for (const auto& raw : splitTopLevel(p, ',')) {
|
||||
std::string t = trim(raw);
|
||||
if (t.empty()) continue;
|
||||
std::smatch m;
|
||||
if (std::regex_match(t, m, fnPtr)) {
|
||||
out.push_back({trim(m[2].str()), trim(m[1].str()) + " (*)(" + trim(m[3].str()) + ")"});
|
||||
continue;
|
||||
}
|
||||
size_t sp = t.find_last_of(" \t");
|
||||
if (sp == std::string::npos) { out.push_back({t, "int"}); continue; }
|
||||
std::string name = trim(t.substr(sp + 1));
|
||||
std::string type = trim(t.substr(0, sp));
|
||||
while (!name.empty() && name[0] == '*') { type += "*"; name.erase(name.begin()); }
|
||||
out.push_back({trim(name.empty() ? "param" : name), trim(type.empty() ? "int" : type)});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static void parseFunctions(const std::string& src, Module* module) {
|
||||
std::regex rx(
|
||||
R"((^|[\n\r])\s*((?:static|extern|const|inline|volatile|register|signed|unsigned|\w|\s|\*)+?)\s+([A-Za-z_]\w*)\s*\(([^;{}]*)\)\s*\{)",
|
||||
std::regex::ECMAScript);
|
||||
for (auto it = std::sregex_iterator(src.begin(), src.end(), rx); it != std::sregex_iterator(); ++it) {
|
||||
std::smatch m = *it;
|
||||
std::string ret = trim(m[2].str());
|
||||
std::string name = trim(m[3].str());
|
||||
if (name == "if" || name == "for" || name == "while" || name == "switch") continue;
|
||||
if (ret.find("typedef") != std::string::npos) continue;
|
||||
auto* fn = new Function(IdGenerator::next("fn"), name);
|
||||
setType(fn, "returnType", ret);
|
||||
for (const auto& [pn, pt] : parseParams(m[4].str())) {
|
||||
auto* p = new Parameter(IdGenerator::next("param"), pn);
|
||||
setType(p, "type", pt);
|
||||
fn->addChild("parameters", p);
|
||||
}
|
||||
module->addChild("functions", fn);
|
||||
}
|
||||
}
|
||||
|
||||
static bool looksLikeType(const std::string& t) {
|
||||
static const std::vector<std::string> k = {
|
||||
"const","static","extern","unsigned","signed","volatile","register",
|
||||
"char","short","int","long","float","double","void","size_t","struct","enum"
|
||||
};
|
||||
for (const auto& x : k) if (t == x || startsWith(t, x + " ") || startsWith(t, x + "*")) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void parseTopLevelVariables(const std::string& src, Module* module) {
|
||||
std::istringstream in(src);
|
||||
std::string line;
|
||||
int depth = 0;
|
||||
std::regex rx(R"(^\s*([A-Za-z_]\w*(?:\s+\w+)?(?:\s*\*+)?)\s+([A-Za-z_]\w*)\s*(=\s*[^;]+)?\s*;\s*$)");
|
||||
std::smatch m;
|
||||
while (std::getline(in, line)) {
|
||||
for (char c : line) { if (c == '{') depth++; else if (c == '}') depth--; }
|
||||
if (depth != 0) continue;
|
||||
std::string t = trim(line);
|
||||
if (t.empty() || startsWith(t, "#") || t.find('(') != std::string::npos) continue;
|
||||
if (t.find("typedef") != std::string::npos || t.find("enum ") != std::string::npos) continue;
|
||||
if (!std::regex_match(t, m, rx)) continue;
|
||||
std::string type = trim(m[1].str());
|
||||
if (!looksLikeType(type)) continue;
|
||||
auto* v = new Variable(IdGenerator::next("var"), trim(m[2].str()));
|
||||
setType(v, "type", type);
|
||||
module->addChild("variables", v);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -164,3 +164,4 @@ private:
|
||||
// Standalone parsers (not tree-sitter fragment includes)
|
||||
#include "ast/KotlinParser.h"
|
||||
#include "ast/CSharpParser.h"
|
||||
#include "ast/CParser.h"
|
||||
|
||||
186
editor/tests/step361_test.cpp
Normal file
186
editor/tests/step361_test.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
// Step 361: C Parser — Functions, Structs, Enums (12 tests)
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "Pipeline.h"
|
||||
#include "ast/Parser.h"
|
||||
#include "ast/ClassDeclaration.h"
|
||||
#include "ast/EnumNamespaceNodes.h"
|
||||
|
||||
int main() {
|
||||
int passed = 0;
|
||||
|
||||
// Test 1: function parsing
|
||||
{
|
||||
auto mod = CParser::parseC("int add(int x, int y) { return x + y; }\n");
|
||||
auto fns = mod->getChildren("functions");
|
||||
assert(fns.size() == 1);
|
||||
auto* fn = static_cast<Function*>(fns[0]);
|
||||
assert(fn->name == "add");
|
||||
assert(fn->getChildren("parameters").size() == 2);
|
||||
std::cout << "Test 1 PASSED: function parsing\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 2: struct parsing
|
||||
{
|
||||
auto mod = CParser::parseC("struct Point { int x; int y; };\n");
|
||||
auto classes = mod->getChildren("classes");
|
||||
assert(classes.size() == 1);
|
||||
auto* cls = static_cast<ClassDeclaration*>(classes[0]);
|
||||
assert(cls->name == "Point");
|
||||
assert(cls->getChildren("fields").size() == 2);
|
||||
std::cout << "Test 2 PASSED: struct parsing\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 3: typedef struct parsing
|
||||
{
|
||||
auto mod = CParser::parseC("typedef struct { int id; char* name; } User;\n");
|
||||
auto classes = mod->getChildren("classes");
|
||||
assert(classes.size() == 1);
|
||||
auto* cls = static_cast<ClassDeclaration*>(classes[0]);
|
||||
assert(cls->name == "User");
|
||||
bool foundAlias = false;
|
||||
for (auto* s : mod->getChildren("statements")) {
|
||||
if (s->conceptType != "TypeAlias") continue;
|
||||
auto* ta = static_cast<TypeAlias*>(s);
|
||||
if (ta->aliasName == "User" && !ta->isUsing) foundAlias = true;
|
||||
}
|
||||
assert(foundAlias);
|
||||
std::cout << "Test 3 PASSED: typedef struct parsing\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 4: enum parsing
|
||||
{
|
||||
std::string src = "enum Color { RED=1, GREEN=2, BLUE=3 }; enum { A=1, B=2 };";
|
||||
auto mod = CParser::parseC(src);
|
||||
int enums = 0;
|
||||
bool named = false, anon = false;
|
||||
for (auto* s : mod->getChildren("statements")) {
|
||||
if (s->conceptType != "EnumDeclaration") continue;
|
||||
enums++;
|
||||
auto* e = static_cast<EnumDeclaration*>(s);
|
||||
if (e->name == "Color") named = true;
|
||||
if (e->name.find("anonymous_enum_") == 0) anon = true;
|
||||
}
|
||||
assert(enums == 2 && named && anon);
|
||||
std::cout << "Test 4 PASSED: enum parsing\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 5: preprocessor directives
|
||||
{
|
||||
std::string src =
|
||||
"#include <stdio.h>\n#include \"x.h\"\n#define MAX 10\n"
|
||||
"#define MIN(a,b) ((a)<(b)?(a):(b))\n#pragma once\n";
|
||||
auto mod = CParser::parseC(src);
|
||||
int includes = 0, macros = 0, pragmas = 0;
|
||||
for (auto* s : mod->getChildren("statements")) {
|
||||
if (s->conceptType == "IncludeDirective") includes++;
|
||||
if (s->conceptType == "MacroDefinition") macros++;
|
||||
if (s->conceptType == "PragmaDirective") pragmas++;
|
||||
}
|
||||
assert(includes == 2 && macros == 2 && pragmas == 1);
|
||||
std::cout << "Test 5 PASSED: preprocessor directives\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 6: static/extern qualifiers
|
||||
{
|
||||
auto mod = CParser::parseC("static int h(){return 1;}\nextern int api(){return 2;}\n");
|
||||
auto fns = mod->getChildren("functions");
|
||||
assert(fns.size() == 2);
|
||||
bool hasStatic = false, hasExtern = false;
|
||||
for (auto* f : fns) {
|
||||
auto* fn = static_cast<Function*>(f);
|
||||
auto* t = fn->getChild("returnType");
|
||||
if (!t || t->conceptType != "PrimitiveType") continue;
|
||||
auto kind = static_cast<PrimitiveType*>(t)->kind;
|
||||
if (fn->name == "h" && kind.find("static") != std::string::npos) hasStatic = true;
|
||||
if (fn->name == "api" && kind.find("extern") != std::string::npos) hasExtern = true;
|
||||
}
|
||||
assert(hasStatic && hasExtern);
|
||||
std::cout << "Test 6 PASSED: qualifiers parsed\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 7: function pointers
|
||||
{
|
||||
auto mod = CParser::parseC("void set_callback(void (*callback)(int)) { }");
|
||||
auto fns = mod->getChildren("functions");
|
||||
assert(fns.size() == 1);
|
||||
auto* fn = static_cast<Function*>(fns[0]);
|
||||
auto params = fn->getChildren("parameters");
|
||||
assert(params.size() == 1);
|
||||
auto* p = static_cast<Parameter*>(params[0]);
|
||||
assert(p->name == "callback");
|
||||
auto* t = p->getChild("type");
|
||||
assert(t != nullptr);
|
||||
std::cout << "Test 7 PASSED: function pointers\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 8: pointer variable
|
||||
{
|
||||
auto mod = CParser::parseC("char* name;");
|
||||
auto vars = mod->getChildren("variables");
|
||||
assert(vars.size() == 1);
|
||||
auto* v = static_cast<Variable*>(vars[0]);
|
||||
assert(v->name == "name");
|
||||
assert(v->getChild("type") != nullptr);
|
||||
std::cout << "Test 8 PASSED: pointer variable\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 9: multiple functions
|
||||
{
|
||||
auto mod = CParser::parseC("int a(){return 1;}\nint b(){return 2;}\nint c(){return 3;}\n");
|
||||
assert(mod->getChildren("functions").size() == 3);
|
||||
std::cout << "Test 9 PASSED: multiple functions\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 10: backward compat c++ parser
|
||||
{
|
||||
auto mod = TreeSitterParser::parseCpp("int cpp_fn() { return 7; }");
|
||||
assert(mod != nullptr);
|
||||
assert(mod->getChildren("functions").size() == 1);
|
||||
std::cout << "Test 10 PASSED: C++ parser unaffected\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 11: C-style comments
|
||||
{
|
||||
std::string src = "/* x */\n// y\nint live(int z) { return z; } // tail\n";
|
||||
auto mod = CParser::parseC(src);
|
||||
auto fns = mod->getChildren("functions");
|
||||
assert(fns.size() == 1);
|
||||
assert(static_cast<Function*>(fns[0])->name == "live");
|
||||
std::cout << "Test 11 PASSED: C comments handled\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 12: header guard + pipeline route
|
||||
{
|
||||
std::string src = "#ifndef X_H\n#define X_H\nint f(void){return 0;}\n#endif\n";
|
||||
auto mod = CParser::parseC(src);
|
||||
int pragmaLike = 0;
|
||||
for (auto* s : mod->getChildren("statements")) if (s->conceptType == "PragmaDirective") pragmaLike++;
|
||||
assert(pragmaLike >= 2);
|
||||
|
||||
Pipeline p;
|
||||
std::vector<ParseDiagnostic> diags;
|
||||
auto parsed = p.parse("int z(void){return 0;}", "c", diags);
|
||||
assert(parsed != nullptr && parsed->targetLanguage == "c");
|
||||
assert(diags.empty());
|
||||
std::cout << "Test 12 PASSED: header guard + pipeline route\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
std::cout << "\nResults: " << passed << "/12\n";
|
||||
assert(passed == 12);
|
||||
return 0;
|
||||
}
|
||||
42
progress.md
42
progress.md
@@ -2043,6 +2043,48 @@ state transitions, key symbol visibility, and panel layout persistence.
|
||||
helper functions to satisfy the max function length constraint (<=80 lines),
|
||||
with `step354_test`, `step355_test`, and `step360_test` revalidated
|
||||
|
||||
---
|
||||
|
||||
# Sprint 14 Progress — Language Batch 1
|
||||
|
||||
### Step 361: C Parser — Functions, Structs, Enums
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Added a standalone C parser (regex/text-based, no new tree-sitter dependency)
|
||||
that handles core C declarations and preprocessor constructs needed for the
|
||||
pipeline entry point in Sprint 14.
|
||||
|
||||
**Files created:**
|
||||
- `editor/src/ast/CParser.h` — C parser support:
|
||||
- `parseC(...)` and `parseCWithDiagnostics(...)`
|
||||
- preprocessor parsing: `#include`, `#define` (object/function-like), `#pragma`
|
||||
- header-guard shape recognition (`#ifndef/#ifdef/#endif`) into pragma-style nodes
|
||||
- C declarations: functions, structs, typedef-struct, enums (named + anonymous),
|
||||
top-level variables, function-pointer parameters
|
||||
- comment stripping for C line/block comments before declaration parsing
|
||||
- `editor/tests/step361_test.cpp` — 12 tests covering:
|
||||
1. function parsing
|
||||
2. struct parsing
|
||||
3. typedef struct parsing
|
||||
4. enum parsing (named + anonymous)
|
||||
5. preprocessor directives
|
||||
6. static/extern qualifiers
|
||||
7. function-pointer parameter parsing
|
||||
8. pointer variable parsing
|
||||
9. multiple functions per file
|
||||
10. C++ parser backward compatibility
|
||||
11. C comment handling
|
||||
12. pipeline routing for `"c"` language + header-guard pattern
|
||||
|
||||
**Files modified:**
|
||||
- `editor/src/ast/Parser.h` — include `ast/CParser.h`
|
||||
- `editor/src/Pipeline.h` — add parse routing for language `"c"`
|
||||
- `editor/CMakeLists.txt` — `step361_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `step361_test` — PASS (12/12) new step coverage
|
||||
- `step360_test` — PASS (8/8) regression coverage
|
||||
|
||||
# Roadmap Planning — Sprints 12-25+
|
||||
|
||||
## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)
|
||||
|
||||
Reference in New Issue
Block a user