mcard-js
Version:
MCard - Content-addressable storage with cryptographic hashing, handle resolution, and vector search for Node.js and browsers
217 lines • 8.35 kB
JavaScript
/**
* CLMLoader - Load and parse Cubical Logic Model files
*
* Supports YAML CLM specifications from chapters/
*/
import * as fs from 'fs';
import * as path from 'path';
import * as yaml from 'yaml';
/**
* Map balanced.test_cases to examples, preserving original input content.
*/
function mapTestCasesToExamples(testCases, treatEmptyWhenAsGiven) {
return testCases.map((tc) => {
const givenRaw = tc.given;
const givenLabel = (() => {
if (typeof givenRaw === 'string')
return givenRaw;
if (givenRaw && typeof givenRaw === 'object') {
if (typeof givenRaw.description === 'string')
return givenRaw.description;
if (typeof givenRaw.name === 'string')
return givenRaw.name;
try {
return JSON.stringify(givenRaw);
}
catch {
return String(givenRaw);
}
}
return String(givenRaw);
})();
const givenContext = (givenRaw && typeof givenRaw === 'object') ? (givenRaw.context || {}) : {};
const givenCondition = (givenRaw && typeof givenRaw === 'object') ? (givenRaw.condition || {}) : {};
const givenPreconditions = (givenContext && typeof givenContext === 'object' &&
givenCondition && typeof givenCondition === 'object')
? { ...givenContext, ...givenCondition }
: (givenCondition && typeof givenCondition === 'object')
? givenCondition
: (givenContext && typeof givenContext === 'object')
? givenContext
: {};
let input = tc.given;
if (tc.when) {
const params = tc.when.params || {};
const context = tc.when.context || {};
const args = tc.when.arguments || {};
const hasParams = Object.keys(params).length > 0;
const hasContext = Object.keys(context).length > 0;
const hasArgs = Object.keys(args).length > 0;
const hasWhenKeys = typeof tc.when === 'object' && Object.keys(tc.when).length > 0;
if (hasParams || hasContext || hasArgs) {
input = { ...tc.when, ...givenPreconditions, ...context, ...params, ...args };
// Also ensure params are nested if present
if (hasParams)
input.params = params;
if (input.__input_content__ === undefined) {
input.__input_content__ = givenLabel;
}
}
else if (tc.when &&
typeof tc.when === 'object' &&
(!treatEmptyWhenAsGiven || hasWhenKeys)) {
input = { ...tc.when, ...givenPreconditions };
if (input.__input_content__ === undefined) {
input.__input_content__ = givenLabel;
}
}
}
else if (givenPreconditions && typeof givenPreconditions === 'object' && Object.keys(givenPreconditions).length > 0) {
input = { ...givenPreconditions };
if (input.__input_content__ === undefined) {
input.__input_content__ = givenLabel;
}
}
return {
name: tc.name || `Test Case: ${givenLabel}`,
input,
expected_output: tc.then?.result,
result_contains: tc.then?.result_contains,
};
});
}
export class CLMLoader {
basePath;
constructor(basePath = process.cwd()) {
this.basePath = basePath;
}
/**
* Load a CLM file from path.
*/
load(clmPath) {
const fullPath = path.isAbsolute(clmPath)
? clmPath
: path.resolve(this.basePath, clmPath);
const content = fs.readFileSync(fullPath, 'utf-8');
const parsed = yaml.parse(content);
// Normalize format (aliases, defaults, legacy compatibility)
const spec = this.normalizeFormat(parsed, fullPath);
// Standard format: check for test_cases/examples
this.normalizeExamples(spec);
return spec;
}
/**
* Load all CLM files from a directory.
*/
loadDirectory(dirPath) {
const fullPath = path.resolve(this.basePath, dirPath);
const files = fs.readdirSync(fullPath);
const clmFiles = new Map();
for (const file of files) {
if (file.endsWith('.yaml') || file.endsWith('.clm')) {
const filePath = path.join(fullPath, file);
try {
clmFiles.set(file, this.load(filePath));
}
catch (e) {
console.warn(`Failed to load ${file}:`, e);
}
}
}
return clmFiles;
}
/**
* Load logic file content for a CLM.
*/
loadLogicFile(clm, chapterDir) {
const config = clm.clm?.concrete;
if (!config)
return null;
// Check for inline code
if (config.code) {
return config.code;
}
// Try runtimes_config for JavaScript
if (config.runtimes_config) {
const jsConfig = config.runtimes_config.find(r => r.name === 'javascript');
if (jsConfig?.file) {
const logicPath = path.resolve(this.basePath, chapterDir, jsConfig.file);
return fs.readFileSync(logicPath, 'utf-8');
}
}
// Try code_file
if (config.code_file) {
const logicPath = path.resolve(this.basePath, chapterDir, config.code_file);
if (fs.existsSync(logicPath)) {
return fs.readFileSync(logicPath, 'utf-8');
}
}
return null;
}
normalizeFormat(parsed, fullPath) {
// Ensure version exists
if (!parsed.version) {
parsed.version = '1.0';
}
// If clm block is missing but abstract is at root, it's legacy
if (!parsed.clm && parsed.abstract) {
const basename = path.basename(fullPath);
const title = parsed.chapter?.title || parsed.metadata?.name || basename;
parsed.clm = {
abstract: parsed.abstract,
concrete: parsed.concrete,
balanced: parsed.balanced
};
if (!parsed.chapter) {
parsed.chapter = { id: 0, title };
}
}
// Ensure clm block exists
if (!parsed.clm)
parsed.clm = {};
// Support aliases within clm block
const abstract = parsed.clm.abstract_spec || parsed.clm.abstract || {};
const concrete = parsed.clm.concrete_impl || parsed.clm.concrete || {};
const balanced = parsed.clm.balanced_exp || parsed.clm.balanced || {};
// Default to lambda runtime
if (!concrete.runtime) {
concrete.runtime = 'lambda';
}
// Default builtin to true for lambda and network
if (concrete.builtin === undefined) {
if (concrete.runtime === 'lambda' || concrete.runtime === 'network') {
concrete.builtin = true;
}
}
// Apply normalized dimensions back
parsed.clm.abstract = abstract;
parsed.clm.concrete = concrete;
parsed.clm.balanced = balanced;
return parsed;
}
normalizeExamples(parsed) {
const balanced = parsed.clm?.balanced;
if (!balanced)
return;
// Map test_cases to examples
if (balanced.test_cases) {
const mapped = mapTestCasesToExamples(balanced.test_cases, false);
if (!parsed.examples) {
parsed.examples = mapped;
}
else {
// Prepend test cases to examples to ensure verification runs
parsed.examples = [...mapped, ...parsed.examples];
}
}
// Map balanced.examples (used in some chapters)
if (balanced.examples && !parsed.examples) {
parsed.examples = balanced.examples.map((ex, i) => ({
name: `Example ${i + 1}`,
input: ex,
expected_output: undefined
}));
}
}
}
//# sourceMappingURL=loader.js.map