Step 121: add import/external module AST concepts
This commit is contained in:
@@ -685,6 +685,10 @@ add_executable(step120_test tests/step120_test.cpp)
|
||||
target_include_directories(step120_test PRIVATE src)
|
||||
target_link_libraries(step120_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
|
||||
add_executable(step121_test tests/step121_test.cpp)
|
||||
target_include_directories(step121_test PRIVATE src)
|
||||
target_link_libraries(step121_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
|
||||
find_package(SDL2 CONFIG REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(glad CONFIG REQUIRED)
|
||||
|
||||
19
editor/src/ast/ExternalModule.h
Normal file
19
editor/src/ast/ExternalModule.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include "ASTNode.h"
|
||||
|
||||
class ExternalModule : public ASTNode {
|
||||
public:
|
||||
std::string name;
|
||||
std::string version;
|
||||
std::string language;
|
||||
|
||||
ExternalModule() { conceptType = "ExternalModule"; }
|
||||
ExternalModule(const std::string& id, const std::string& name,
|
||||
const std::string& language,
|
||||
const std::string& version = "")
|
||||
: name(name), version(version), language(language) {
|
||||
this->id = id;
|
||||
this->conceptType = "ExternalModule";
|
||||
}
|
||||
// children: signatures (0..n) via addChild("signatures", TypeSignature*)
|
||||
};
|
||||
18
editor/src/ast/Import.h
Normal file
18
editor/src/ast/Import.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include "ASTNode.h"
|
||||
|
||||
class Import : public ASTNode {
|
||||
public:
|
||||
std::string moduleName;
|
||||
std::string alias;
|
||||
std::string importKind; // "module", "include", "require"
|
||||
|
||||
Import() { conceptType = "Import"; }
|
||||
Import(const std::string& id, const std::string& module,
|
||||
const std::string& kind = "module",
|
||||
const std::string& alias = "")
|
||||
: moduleName(module), alias(alias), importKind(kind) {
|
||||
this->id = id;
|
||||
this->conceptType = "Import";
|
||||
}
|
||||
};
|
||||
@@ -80,6 +80,8 @@ private:
|
||||
addRule("Module", "variables", {"Variable"}, false); // multi-valued
|
||||
addRule("Module", "targetLanguage", {}, true); // property, not used for AST nodes
|
||||
addRule("Module", "annotations", {"DerefStrategy", "OptimizationLock", "LangSpecific"}, false); // multi-valued
|
||||
addRule("Module", "imports", {"Import"}, false); // multi-valued
|
||||
addRule("Module", "externalModules", {"ExternalModule"}, false); // multi-valued
|
||||
|
||||
// Function concept rules
|
||||
addRule("Function", "parameters", {"Parameter"}, false); // multi-valued
|
||||
@@ -151,6 +153,17 @@ private:
|
||||
// Type concepts - no child rules needed as they're leaf nodes for types
|
||||
|
||||
// Annotation concepts - no child rules needed as they're leaf nodes for annotations
|
||||
|
||||
// External module concepts
|
||||
addRule("ExternalModule", "signatures", {"TypeSignature"}, false); // multi-valued
|
||||
addRule("TypeSignature", "returnType",
|
||||
{"PrimitiveType", "ListType", "SetType", "MapType", "TupleType",
|
||||
"ArrayType", "OptionalType", "CustomType"},
|
||||
true);
|
||||
addRule("TypeSignature", "paramTypes",
|
||||
{"PrimitiveType", "ListType", "SetType", "MapType", "TupleType",
|
||||
"ArrayType", "OptionalType", "CustomType"},
|
||||
false);
|
||||
}
|
||||
|
||||
void addRule(const std::string& parentConcept, const std::string& role, const std::vector<std::string>& allowedConcepts, bool isSingleValued) {
|
||||
@@ -159,4 +172,4 @@ private:
|
||||
rule.isSingleValued = isSingleValued;
|
||||
schema_[parentConcept][role] = rule;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
#include "Expression.h"
|
||||
#include "Type.h"
|
||||
#include "Annotation.h"
|
||||
#include "Import.h"
|
||||
#include "ExternalModule.h"
|
||||
#include "TypeSignature.h"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
@@ -88,6 +91,24 @@ inline json propertiesToJson(const ASTNode* node) {
|
||||
auto* n = static_cast<const CustomType*>(node);
|
||||
props["typeName"] = n->typeName;
|
||||
}
|
||||
// Imports / External modules
|
||||
else if (ct == "Import") {
|
||||
auto* n = static_cast<const Import*>(node);
|
||||
props["moduleName"] = n->moduleName;
|
||||
if (!n->alias.empty()) props["alias"] = n->alias;
|
||||
if (!n->importKind.empty()) props["importKind"] = n->importKind;
|
||||
}
|
||||
else if (ct == "ExternalModule") {
|
||||
auto* n = static_cast<const ExternalModule*>(node);
|
||||
props["name"] = n->name;
|
||||
if (!n->version.empty()) props["version"] = n->version;
|
||||
if (!n->language.empty()) props["language"] = n->language;
|
||||
}
|
||||
else if (ct == "TypeSignature") {
|
||||
auto* n = static_cast<const TypeSignature*>(node);
|
||||
props["name"] = n->name;
|
||||
props["variadic"] = n->variadic;
|
||||
}
|
||||
// Annotations
|
||||
else if (ct == "DerefStrategy") {
|
||||
auto* n = static_cast<const DerefStrategy*>(node);
|
||||
@@ -177,6 +198,9 @@ inline ASTNode* createNode(const std::string& conceptName) {
|
||||
if (conceptName == "ArrayType") return new ArrayType();
|
||||
if (conceptName == "OptionalType") return new OptionalType();
|
||||
if (conceptName == "CustomType") return new CustomType();
|
||||
if (conceptName == "Import") return new Import();
|
||||
if (conceptName == "ExternalModule") return new ExternalModule();
|
||||
if (conceptName == "TypeSignature") return new TypeSignature();
|
||||
if (conceptName == "DerefStrategy") return new DerefStrategy();
|
||||
if (conceptName == "OptimizationLock") return new OptimizationLock();
|
||||
if (conceptName == "LangSpecific") return new LangSpecific();
|
||||
@@ -251,6 +275,23 @@ inline void setPropertiesFromJson(ASTNode* node, const json& props) {
|
||||
auto* n = static_cast<CustomType*>(node);
|
||||
if (props.contains("typeName")) n->typeName = props["typeName"].get<std::string>();
|
||||
}
|
||||
else if (ct == "Import") {
|
||||
auto* n = static_cast<Import*>(node);
|
||||
if (props.contains("moduleName")) n->moduleName = props["moduleName"].get<std::string>();
|
||||
if (props.contains("alias")) n->alias = props["alias"].get<std::string>();
|
||||
if (props.contains("importKind")) n->importKind = props["importKind"].get<std::string>();
|
||||
}
|
||||
else if (ct == "ExternalModule") {
|
||||
auto* n = static_cast<ExternalModule*>(node);
|
||||
if (props.contains("name")) n->name = props["name"].get<std::string>();
|
||||
if (props.contains("version")) n->version = props["version"].get<std::string>();
|
||||
if (props.contains("language")) n->language = props["language"].get<std::string>();
|
||||
}
|
||||
else if (ct == "TypeSignature") {
|
||||
auto* n = static_cast<TypeSignature*>(node);
|
||||
if (props.contains("name")) n->name = props["name"].get<std::string>();
|
||||
if (props.contains("variadic")) n->variadic = props["variadic"].get<bool>();
|
||||
}
|
||||
else if (ct == "DerefStrategy") {
|
||||
auto* n = static_cast<DerefStrategy*>(node);
|
||||
if (props.contains("strategy")) n->strategy = props["strategy"].get<std::string>();
|
||||
|
||||
17
editor/src/ast/TypeSignature.h
Normal file
17
editor/src/ast/TypeSignature.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include "ASTNode.h"
|
||||
|
||||
class TypeSignature : public ASTNode {
|
||||
public:
|
||||
std::string name;
|
||||
bool variadic = false;
|
||||
|
||||
TypeSignature() { conceptType = "TypeSignature"; }
|
||||
TypeSignature(const std::string& id, const std::string& name, bool variadic = false)
|
||||
: name(name), variadic(variadic) {
|
||||
this->id = id;
|
||||
this->conceptType = "TypeSignature";
|
||||
}
|
||||
// children: returnType (1) via setChild("returnType", Type*)
|
||||
// children: paramTypes (0..n) via addChild("paramTypes", Type*)
|
||||
};
|
||||
84
editor/tests/step121_test.cpp
Normal file
84
editor/tests/step121_test.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
// Step 121 TDD Test: Import/ExternalModule/TypeSignature serialization
|
||||
#include "ast/Serialization.h"
|
||||
#include "ast/Import.h"
|
||||
#include "ast/ExternalModule.h"
|
||||
#include "ast/TypeSignature.h"
|
||||
#include "ast/Type.h"
|
||||
#include "ast/Module.h"
|
||||
#include <iostream>
|
||||
|
||||
static void expect(bool cond, const std::string& name, int& passed, int& failed) {
|
||||
if (cond) {
|
||||
std::cout << "Test " << (passed + failed + 1) << " PASS: " << name << "\n";
|
||||
++passed;
|
||||
} else {
|
||||
std::cout << "Test " << (passed + failed + 1) << " FAIL: " << name << "\n";
|
||||
++failed;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
int passed = 0;
|
||||
int failed = 0;
|
||||
|
||||
// Test 1: Import round-trip
|
||||
Module root("m1", "Root", "python");
|
||||
auto* imp = new Import("imp1", "numpy", "module", "np");
|
||||
root.addChild("imports", imp);
|
||||
|
||||
json j = toJson(&root);
|
||||
ASTNode* round = fromJson(j);
|
||||
expect(round && round->conceptType == "Module", "module round-trip", passed, failed);
|
||||
auto* roundMod = static_cast<Module*>(round);
|
||||
auto imports = roundMod->getChildren("imports");
|
||||
expect(imports.size() == 1 && imports[0]->conceptType == "Import", "import child preserved", passed, failed);
|
||||
if (!imports.empty()) {
|
||||
auto* ri = static_cast<Import*>(imports[0]);
|
||||
expect(ri->moduleName == "numpy" && ri->alias == "np" && ri->importKind == "module",
|
||||
"import properties", passed, failed);
|
||||
}
|
||||
|
||||
deleteTree(round);
|
||||
|
||||
// Test 2: ExternalModule + TypeSignature + Type children round-trip
|
||||
Module root2("m2", "Root2", "python");
|
||||
auto* ext = new ExternalModule("ext1", "numpy", "python", "1.26");
|
||||
auto* sig = new TypeSignature("sig1", "array", true);
|
||||
auto* retType = new CustomType("t1", "ndarray");
|
||||
auto* paramType = new PrimitiveType("t2", "float");
|
||||
sig->setChild("returnType", retType);
|
||||
sig->addChild("paramTypes", paramType);
|
||||
ext->addChild("signatures", sig);
|
||||
root2.addChild("externalModules", ext);
|
||||
|
||||
json j2 = toJson(&root2);
|
||||
ASTNode* round2 = fromJson(j2);
|
||||
expect(round2 && round2->conceptType == "Module", "module round-trip (external)", passed, failed);
|
||||
auto* roundMod2 = static_cast<Module*>(round2);
|
||||
auto externals = roundMod2->getChildren("externalModules");
|
||||
expect(externals.size() == 1 && externals[0]->conceptType == "ExternalModule",
|
||||
"external module child", passed, failed);
|
||||
if (!externals.empty()) {
|
||||
auto* re = static_cast<ExternalModule*>(externals[0]);
|
||||
expect(re->name == "numpy" && re->version == "1.26" && re->language == "python",
|
||||
"external module properties", passed, failed);
|
||||
auto sigs = re->getChildren("signatures");
|
||||
expect(sigs.size() == 1 && sigs[0]->conceptType == "TypeSignature",
|
||||
"type signature child", passed, failed);
|
||||
if (!sigs.empty()) {
|
||||
auto* rs = static_cast<TypeSignature*>(sigs[0]);
|
||||
expect(rs->name == "array" && rs->variadic == true,
|
||||
"type signature properties", passed, failed);
|
||||
auto* rret = rs->getChild("returnType");
|
||||
auto params = rs->getChildren("paramTypes");
|
||||
expect(rret && rret->conceptType == "CustomType", "return type child", passed, failed);
|
||||
expect(params.size() == 1 && params[0]->conceptType == "PrimitiveType",
|
||||
"param type child", passed, failed);
|
||||
}
|
||||
}
|
||||
|
||||
deleteTree(round2);
|
||||
|
||||
std::cout << "\n=== Step 121 Results: " << passed << " passed, " << failed << " failed ===\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -391,7 +391,7 @@ AST extensions for Sprint 5.
|
||||
AST atomically. Status bar shows undo depth.
|
||||
*Modifies:* `Orchestrator.h`, `main.cpp`
|
||||
|
||||
- [ ] **Step 121: AST Import/ExternalModule concepts**
|
||||
- [x] **Step 121: AST Import/ExternalModule concepts**
|
||||
Extend `ASTNode.h` with new concept types: `Import` (represents `import numpy`,
|
||||
`#include <vector>`, `(require 'cl-lib)`), `ExternalModule` (represents an
|
||||
external library with its public API surface), `TypeSignature` (function
|
||||
|
||||
Reference in New Issue
Block a user