Files
whetstone_DSL/editor/src/EnvironmentSpec.h
Bill d4a3609050 Steps 290-294: Phase 11a — Semanno format, annotation codegen, visitor dispatch (60/60 tests)
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>
2026-02-13 18:41:31 +00:00

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>();
}