tusktsk
Version:
TuskTsk - The Freedom Configuration Language. Query databases, use any syntax, never bow to any king!
367 lines (300 loc) • 11.7 kB
JavaScript
/**
* Binary Commands for TuskLang CLI
* =================================
* Implements binary performance operations
*/
const { Command } = require('commander');
const fs = require('fs').promises;
const path = require('path');
const TuskLang = require('../../index.js');
const PeanutConfig = require('../../peanut-config.js');
// Binary compile command
const compile = new Command('compile')
.description('Compile to binary format (.tskb)')
.argument('<file.tsk>', 'TSK file to compile')
.action(async (file) => {
try {
console.log(`🔄 Compiling TSK file to binary: ${file}`);
// Check if file exists
await fs.access(file);
// Read and parse TSK file
const content = await fs.readFile(file, 'utf8');
const tusk = new TuskLang();
const parsed = tusk.parse(content);
// Generate binary file path (use .pnt format for compatibility)
const binaryFile = file.replace(/\.tsk$/, '.pnt');
// Compile to binary using PeanutConfig
const peanutConfig = new PeanutConfig();
peanutConfig.compileToBinary(parsed, binaryFile);
// Get file sizes for comparison
const sourceStats = await fs.stat(file);
const binaryStats = await fs.stat(binaryFile);
const sourceSize = sourceStats.size;
const binarySize = binaryStats.size;
const compressionRatio = ((sourceSize - binarySize) / sourceSize * 100).toFixed(1);
console.log('✅ Binary compilation completed successfully');
console.log(`📍 Source file: ${file} (${sourceSize} bytes)`);
console.log(`📍 Binary file: ${binaryFile} (${binarySize} bytes)`);
console.log(`📍 Compression: ${compressionRatio}% smaller`);
return {
success: true,
source: { file, size: sourceSize },
binary: { file: binaryFile, size: binarySize },
compression: compressionRatio
};
} catch (error) {
console.error('❌ Binary compilation failed:', error.message);
return { success: false, error: error.message };
}
});
// Binary execute command
const execute = new Command('execute')
.description('Execute binary file directly')
.argument('<file.pnt>', 'Binary file to execute')
.action(async (file) => {
try {
console.log(`🔄 Executing binary file: ${file}`);
// Check if file exists
await fs.access(file);
// Load binary file
const peanutConfig = new PeanutConfig();
const config = peanutConfig.loadBinary(file);
console.log('✅ Binary file executed successfully');
console.log('📍 Configuration loaded:');
// Display configuration summary
const summary = summarizeConfiguration(config);
console.log(` - Total keys: ${summary.totalKeys}`);
console.log(` - Sections: ${summary.sections}`);
console.log(` - Data types: ${summary.dataTypes.join(', ')}`);
return {
success: true,
file,
config: summary
};
} catch (error) {
console.error('❌ Binary execution failed:', error.message);
return { success: false, error: error.message };
}
});
// Binary benchmark command
const benchmark = new Command('benchmark')
.description('Compare binary vs text performance')
.argument('<file>', 'File to benchmark (.tsk or .tskb)')
.action(async (file) => {
try {
console.log(`⚡ Benchmarking performance: ${file}`);
let textFile, binaryFile;
if (file.endsWith('.tsk')) {
textFile = file;
binaryFile = file.replace(/\.tsk$/, '.pnt');
} else if (file.endsWith('.pnt')) {
binaryFile = file;
textFile = file.replace(/\.pnt$/, '.tsk');
} else {
throw new Error('File must be .tsk or .pnt');
}
const results = {
text: null,
binary: null,
comparison: {}
};
// Benchmark text parsing
if (await fs.access(textFile).then(() => true).catch(() => false)) {
console.log(' 📊 Benchmarking text parsing...');
results.text = await benchmarkTextParsing(textFile);
}
// Benchmark binary loading
if (await fs.access(binaryFile).then(() => true).catch(() => false)) {
console.log(' 📊 Benchmarking binary loading...');
results.binary = await benchmarkBinaryLoading(binaryFile);
}
// Compare results
if (results.text && results.binary) {
const speedup = results.text.avgTime / results.binary.avgTime;
const memoryReduction = ((results.text.avgMemory - results.binary.avgMemory) / results.text.avgMemory * 100).toFixed(1);
results.comparison = {
speedup: speedup.toFixed(2),
memoryReduction: memoryReduction,
faster: speedup > 1
};
console.log('');
console.log('📊 Performance Comparison:');
console.log('==========================');
console.log(`Text parsing: ${results.text.avgTime.toFixed(2)}ms avg (${results.text.iterations} iterations)`);
console.log(`Binary loading: ${results.binary.avgTime.toFixed(2)}ms avg (${results.binary.iterations} iterations)`);
console.log(`Speedup: ${speedup.toFixed(2)}x faster`);
console.log(`Memory usage: ${memoryReduction}% less memory`);
console.log(`Recommendation: ${speedup > 1 ? 'Use binary format' : 'Use text format'}`);
}
return results;
} catch (error) {
console.error('❌ Benchmark failed:', error.message);
return { success: false, error: error.message };
}
});
// Binary optimize command
const optimize = new Command('optimize')
.description('Optimize binary for production')
.argument('<file>', 'Binary file to optimize')
.action(async (file) => {
try {
console.log(`🔄 Optimizing binary for production: ${file}`);
// Check if file exists
await fs.access(file);
// Load binary file
const peanutConfig = new PeanutConfig();
const config = peanutConfig.loadBinary(file);
// Apply optimizations
const optimized = optimizeConfiguration(config);
// Create optimized binary
const optimizedFile = file.replace(/\.(pnt)$/, '.optimized.$1');
peanutConfig.compileToBinary(optimized, optimizedFile);
// Get file sizes
const originalStats = await fs.stat(file);
const optimizedStats = await fs.stat(optimizedFile);
const originalSize = originalStats.size;
const optimizedSize = optimizedStats.size;
const sizeReduction = ((originalSize - optimizedSize) / originalSize * 100).toFixed(1);
console.log('✅ Binary optimization completed successfully');
console.log(`📍 Original: ${file} (${originalSize} bytes)`);
console.log(`📍 Optimized: ${optimizedFile} (${optimizedSize} bytes)`);
console.log(`📍 Size reduction: ${sizeReduction}%`);
// Show optimization details
console.log('');
console.log('🔧 Optimization Details:');
console.log(` - Removed null values: ${optimized.removedNulls}`);
console.log(` - Compressed strings: ${optimized.compressedStrings}`);
console.log(` - Optimized numbers: ${optimized.optimizedNumbers}`);
return {
success: true,
original: { file, size: originalSize },
optimized: { file: optimizedFile, size: optimizedSize },
reduction: sizeReduction,
details: optimized
};
} catch (error) {
console.error('❌ Binary optimization failed:', error.message);
return { success: false, error: error.message };
}
});
// Helper functions
async function benchmarkTextParsing(file) {
const tusk = new TuskLang();
const content = await fs.readFile(file, 'utf8');
const iterations = 100;
const times = [];
const memories = [];
for (let i = 0; i < iterations; i++) {
const startMemory = process.memoryUsage().heapUsed;
const startTime = process.hrtime.bigint();
const parsed = tusk.parse(content);
const endTime = process.hrtime.bigint();
const endMemory = process.memoryUsage().heapUsed;
const time = Number(endTime - startTime) / 1000000; // Convert to milliseconds
const memory = endMemory - startMemory;
times.push(time);
memories.push(memory);
}
const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
const avgMemory = memories.reduce((a, b) => a + b, 0) / memories.length;
return {
avgTime,
avgMemory,
iterations,
minTime: Math.min(...times),
maxTime: Math.max(...times)
};
}
async function benchmarkBinaryLoading(file) {
const peanutConfig = new PeanutConfig();
const iterations = 100;
const times = [];
const memories = [];
for (let i = 0; i < iterations; i++) {
const startMemory = process.memoryUsage().heapUsed;
const startTime = process.hrtime.bigint();
peanutConfig.loadBinary(file);
const endTime = process.hrtime.bigint();
const endMemory = process.memoryUsage().heapUsed;
const time = Number(endTime - startTime) / 1000000; // Convert to milliseconds
const memory = endMemory - startMemory;
times.push(time);
memories.push(memory);
}
const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
const avgMemory = memories.reduce((a, b) => a + b, 0) / memories.length;
return {
avgTime,
avgMemory,
iterations,
minTime: Math.min(...times),
maxTime: Math.max(...times)
};
}
function summarizeConfiguration(config) {
const summary = {
totalKeys: 0,
sections: 0,
dataTypes: new Set()
};
function analyzeObject(obj) {
for (const [key, value] of Object.entries(obj)) {
summary.totalKeys++;
summary.dataTypes.add(typeof value);
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
summary.sections++;
analyzeObject(value);
}
}
}
analyzeObject(config);
summary.dataTypes = Array.from(summary.dataTypes);
return summary;
}
function optimizeConfiguration(config) {
const optimized = {};
const stats = {
removedNulls: 0,
compressedStrings: 0,
optimizedNumbers: 0
};
function optimizeObject(obj) {
const result = {};
for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined) {
stats.removedNulls++;
continue; // Skip null/undefined values
}
if (typeof value === 'string') {
// Compress long strings
if (value.length > 100) {
stats.compressedStrings++;
// In a real implementation, this would use actual compression
}
result[key] = value;
} else if (typeof value === 'number') {
// Optimize numbers (e.g., use integers when possible)
if (Number.isInteger(value) && value >= 0 && value <= 255) {
stats.optimizedNumbers++;
}
result[key] = value;
} else if (typeof value === 'object' && !Array.isArray(value)) {
result[key] = optimizeObject(value);
} else {
result[key] = value;
}
}
return result;
}
optimized.data = optimizeObject(config);
optimized.removedNulls = stats.removedNulls;
optimized.compressedStrings = stats.compressedStrings;
optimized.optimizedNumbers = stats.optimizedNumbers;
return optimized;
}
module.exports = {
compile,
execute,
benchmark,
optimize
};