Semanno comment format standard (@semanno:type(key=value)) with emitter/parser covering all 67+ annotation types. SemannoAnnotationImpl CRTP mixin provides default visitor implementations for all 56 extended annotation methods across 7 language generators. Virtual inheritance resolves diamond ambiguity between ProjectionGenerator and SemannoAnnotationImpl. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
237 lines
9.1 KiB
C++
237 lines
9.1 KiB
C++
#pragma once
|
|
// Step 284-286: Environment Layer
|
|
//
|
|
// EnvironmentSpec — first-class environment model attached to modules.
|
|
// Capability vocabulary, validation, and environment-aware diagnostics.
|
|
|
|
#include "ast/ASTNode.h"
|
|
#include "ast/Annotation.h"
|
|
#include <nlohmann/json.hpp>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <set>
|
|
|
|
using json = nlohmann::json;
|
|
|
|
// --- EnvironmentSpec AST Node ---
|
|
class EnvironmentSpec : public ASTNode {
|
|
public:
|
|
std::string envId; // "posix_process", "browser", "jvm", etc.
|
|
std::string envVersion;
|
|
std::vector<std::string> capabilities;
|
|
std::vector<std::string> constraints;
|
|
std::string scheduler; // "event_loop"|"threads"|"fibers"|"coroutines"|"single_thread"
|
|
std::string memory; // "manual"|"raii"|"refcount"|"tracing_gc"|"region"
|
|
json bindingTimes; // per-feature: "compile"|"link"|"load"|"runtime"
|
|
std::string exceptions; // "unwind"|"checked"|"typed"|"untyped"
|
|
std::string ffi; // "c_abi"|"dynamic_linking"|"none"
|
|
|
|
EnvironmentSpec() {
|
|
conceptType = "EnvironmentSpec";
|
|
bindingTimes = json::object();
|
|
}
|
|
};
|
|
|
|
// --- CapabilityRequirement Annotation ---
|
|
class CapabilityRequirement : public Annotation {
|
|
public:
|
|
std::string capability;
|
|
bool required = true;
|
|
CapabilityRequirement() { conceptType = "CapabilityRequirement"; }
|
|
};
|
|
|
|
// --- Capability Vocabulary ---
|
|
inline const std::set<std::string>& knownCapabilities() {
|
|
static const std::set<std::string> caps = {
|
|
"io.fs", "io.net", "io.stdinout",
|
|
"clock.time", "timers",
|
|
"event_loop", "threads", "process", "signals",
|
|
"modules", "dynamic_loading", "reflection",
|
|
"gc", "jit",
|
|
"ui.dom", "editor.emacs", "host.imgui"
|
|
};
|
|
return caps;
|
|
}
|
|
|
|
inline bool isKnownCapability(const std::string& cap) {
|
|
return knownCapabilities().count(cap) > 0;
|
|
}
|
|
|
|
// --- Capability Validation ---
|
|
struct CapabilityDiagnostic {
|
|
std::string severity; // "error" | "warning"
|
|
std::string message;
|
|
std::string nodeId;
|
|
std::string capability;
|
|
};
|
|
|
|
inline std::vector<CapabilityDiagnostic> validateCapabilities(
|
|
const ASTNode* root, const EnvironmentSpec* env)
|
|
{
|
|
std::vector<CapabilityDiagnostic> diags;
|
|
if (!root || !env) return diags;
|
|
|
|
std::set<std::string> envCaps(env->capabilities.begin(), env->capabilities.end());
|
|
|
|
// Collect all CapabilityRequirements from the tree
|
|
std::vector<const ASTNode*> toVisit = {root};
|
|
while (!toVisit.empty()) {
|
|
const ASTNode* node = toVisit.back();
|
|
toVisit.pop_back();
|
|
for (const auto* child : node->allChildren()) {
|
|
if (child->conceptType == "CapabilityRequirement") {
|
|
auto* cr = static_cast<const CapabilityRequirement*>(child);
|
|
if (cr->required && envCaps.find(cr->capability) == envCaps.end()) {
|
|
diags.push_back({
|
|
"error",
|
|
"E0501: Unmet capability requirement: " + cr->capability,
|
|
node->id,
|
|
cr->capability
|
|
});
|
|
}
|
|
}
|
|
toVisit.push_back(child);
|
|
}
|
|
}
|
|
return diags;
|
|
}
|
|
|
|
// --- Environment-Incompatible Annotation Diagnostics (Step 286) ---
|
|
inline std::vector<CapabilityDiagnostic> validateEnvAnnotations(
|
|
const ASTNode* root, const EnvironmentSpec* env)
|
|
{
|
|
std::vector<CapabilityDiagnostic> diags;
|
|
if (!root || !env) return diags;
|
|
|
|
std::vector<const ASTNode*> toVisit = {root};
|
|
while (!toVisit.empty()) {
|
|
const ASTNode* node = toVisit.back();
|
|
toVisit.pop_back();
|
|
for (const auto* child : node->allChildren()) {
|
|
// ParallelAnnotation(data) in single_thread env
|
|
if (child->conceptType == "ParallelAnnotation" &&
|
|
env->scheduler == "single_thread") {
|
|
auto* pa = static_cast<const ParallelAnnotation*>(child);
|
|
diags.push_back({
|
|
"error",
|
|
"E0502: @Parallel(" + pa->kind + ") incompatible with single_thread scheduler",
|
|
node->id, ""
|
|
});
|
|
}
|
|
// ThreadModelAnnotation(os) in single_thread env
|
|
if (child->conceptType == "ThreadModelAnnotation" &&
|
|
env->scheduler == "single_thread") {
|
|
diags.push_back({
|
|
"error",
|
|
"E0503: @ThreadModel incompatible with single_thread scheduler",
|
|
node->id, ""
|
|
});
|
|
}
|
|
// AtomicAnnotation in single_thread env (warning, not error)
|
|
if (child->conceptType == "AtomicAnnotation" &&
|
|
env->scheduler == "single_thread") {
|
|
diags.push_back({
|
|
"warning",
|
|
"E0504: @Atomic unnecessary in single_thread environment",
|
|
node->id, ""
|
|
});
|
|
}
|
|
// ExecAnnotation(async) requires event_loop, threads, fibers, or coroutines
|
|
if (child->conceptType == "ExecAnnotation") {
|
|
auto* ea = static_cast<const ExecAnnotation*>(child);
|
|
if (ea->mode == "async" && env->scheduler == "single_thread") {
|
|
diags.push_back({
|
|
"error",
|
|
"E0505: @Exec(async) incompatible with single_thread scheduler",
|
|
node->id, ""
|
|
});
|
|
}
|
|
}
|
|
toVisit.push_back(child);
|
|
}
|
|
}
|
|
return diags;
|
|
}
|
|
|
|
// --- Environment-Aware Lowering Hints (Step 287) ---
|
|
struct LoweringHint {
|
|
std::string pattern; // e.g. "callback", "std_async", "try_catch"
|
|
std::string description;
|
|
};
|
|
|
|
inline std::vector<LoweringHint> getLoweringHints(
|
|
const ASTNode* node, const EnvironmentSpec* env)
|
|
{
|
|
std::vector<LoweringHint> hints;
|
|
if (!node || !env) return hints;
|
|
|
|
for (const auto* child : node->getChildren("annotations")) {
|
|
if (child->conceptType == "ExecAnnotation") {
|
|
auto* ea = static_cast<const ExecAnnotation*>(child);
|
|
if (ea->mode == "async") {
|
|
if (env->scheduler == "event_loop")
|
|
hints.push_back({"callback", "async → callback-based pattern"});
|
|
else if (env->scheduler == "threads")
|
|
hints.push_back({"std_async", "async → std::async/std::future"});
|
|
else if (env->scheduler == "coroutines")
|
|
hints.push_back({"coroutine", "async → co_await coroutine"});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Memory model hints
|
|
if (env->memory == "tracing_gc")
|
|
hints.push_back({"suppress_dealloc", "GC env → suppress explicit delete"});
|
|
else if (env->memory == "manual")
|
|
hints.push_back({"explicit_delete", "manual env → inject explicit delete"});
|
|
else if (env->memory == "raii")
|
|
hints.push_back({"raii_cleanup", "RAII env → destructor-based cleanup"});
|
|
|
|
// Exception model hints
|
|
if (env->exceptions == "unwind")
|
|
hints.push_back({"try_catch", "unwind → try/catch blocks"});
|
|
else if (env->exceptions == "typed")
|
|
hints.push_back({"expected", "typed → std::expected return types"});
|
|
else if (env->exceptions == "checked")
|
|
hints.push_back({"checked_throw", "checked → explicit throw declarations"});
|
|
|
|
return hints;
|
|
}
|
|
|
|
// --- EnvironmentSpec JSON serialization helpers ---
|
|
inline json envSpecToJson(const EnvironmentSpec* env) {
|
|
if (!env) return json();
|
|
json j;
|
|
j["envId"] = env->envId;
|
|
if (!env->envVersion.empty()) j["envVersion"] = env->envVersion;
|
|
if (!env->capabilities.empty()) j["capabilities"] = env->capabilities;
|
|
if (!env->constraints.empty()) j["constraints"] = env->constraints;
|
|
if (!env->scheduler.empty()) j["scheduler"] = env->scheduler;
|
|
if (!env->memory.empty()) j["memory"] = env->memory;
|
|
if (!env->bindingTimes.empty()) j["bindingTimes"] = env->bindingTimes;
|
|
if (!env->exceptions.empty()) j["exceptions"] = env->exceptions;
|
|
if (!env->ffi.empty()) j["ffi"] = env->ffi;
|
|
return j;
|
|
}
|
|
|
|
inline void envSpecFromJson(EnvironmentSpec* env, const json& j) {
|
|
if (!env) return;
|
|
if (j.contains("envId")) env->envId = j["envId"].get<std::string>();
|
|
if (j.contains("envVersion")) env->envVersion = j["envVersion"].get<std::string>();
|
|
if (j.contains("capabilities") && j["capabilities"].is_array()) {
|
|
env->capabilities.clear();
|
|
for (const auto& c : j["capabilities"])
|
|
if (c.is_string()) env->capabilities.push_back(c.get<std::string>());
|
|
}
|
|
if (j.contains("constraints") && j["constraints"].is_array()) {
|
|
env->constraints.clear();
|
|
for (const auto& c : j["constraints"])
|
|
if (c.is_string()) env->constraints.push_back(c.get<std::string>());
|
|
}
|
|
if (j.contains("scheduler")) env->scheduler = j["scheduler"].get<std::string>();
|
|
if (j.contains("memory")) env->memory = j["memory"].get<std::string>();
|
|
if (j.contains("bindingTimes")) env->bindingTimes = j["bindingTimes"];
|
|
if (j.contains("exceptions")) env->exceptions = j["exceptions"].get<std::string>();
|
|
if (j.contains("ffi")) env->ffi = j["ffi"].get<std::string>();
|
|
}
|