concept-lang
Version:
A semantic network system for defining concepts, relationships, and building knowledge graphs with automatic inference
540 lines ⢠23.2 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const fs_1 = require("fs");
const path_1 = require("path");
const compiler_1 = require("../core/compiler");
const concept_runner_1 = require("../core/concept-runner");
const program = new commander_1.Command();
program
.name('concept')
.description('Concept language compiler and runtime')
.version('3.0.1');
program
.command('compile')
.description('Compile a Concept language file')
.argument('<input>', 'Input .concept file')
.option('-o, --output <file>', 'Output file (default: input with .out extension)')
.option('--no-infer', 'Disable automatic inference')
.action((input, options) => {
try {
const inputPath = (0, path_1.resolve)(input);
if (!(0, fs_1.existsSync)(inputPath)) {
console.error(`Error: Input file '${inputPath}' does not exist`);
process.exit(1);
}
if ((0, path_1.extname)(inputPath) !== '.concept') {
console.error(`Error: Input file must have .concept extension`);
process.exit(1);
}
const source = (0, fs_1.readFileSync)(inputPath, 'utf-8');
const compiler = new compiler_1.Compiler();
if (!options.infer) {
// Disable inference by overriding the method
compiler.block['inferMissingPairs'] = () => { };
}
const result = compiler.compile(source);
const outputPath = options.output || inputPath.replace('.concept', '.out.concept');
(0, fs_1.writeFileSync)(outputPath, result, 'utf-8');
console.log(`Compiled successfully: ${inputPath} -> ${outputPath}`);
}
catch (error) {
console.error(`Compilation error: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
}
});
program
.command('run')
.description('Run a Concept language file with optional plugins')
.argument('<input>', 'Input .concept file')
.option('--no-infer', 'Disable automatic inference')
.option('--disable-std', 'Disable standard library plugin')
.option('-p, --plugins <plugins...>', 'Plugin paths or names to load')
.option('-c, --config <file>', 'Configuration file path')
.option('-w, --watch', 'Watch for file changes')
.option('--output-dir <dir>', 'Output directory for generated files')
.option('--log-level <level>', 'Log level (debug, info, warn, error)', 'info')
.action(async (input, options) => {
try {
const inputPath = (0, path_1.resolve)(input);
if (!(0, fs_1.existsSync)(inputPath)) {
console.error(`Error: Input file '${inputPath}' does not exist`);
process.exit(1);
}
// Always use plugin runner to get std plugin functionality
// Use plugin runner for all features
const runner = new concept_runner_1.ConceptRunnerImpl();
// Load configuration
let config;
if (options.config) {
const configPath = (0, path_1.resolve)(options.config);
if (!(0, fs_1.existsSync)(configPath)) {
console.error(`Error: Config file '${configPath}' does not exist`);
process.exit(1);
}
const configData = JSON.parse((0, fs_1.readFileSync)(configPath, 'utf-8'));
// Determine which plugins to load
let plugins = configData.plugins || [];
// Load std plugin by default unless --disable-std is specified
if (!options.disableStd &&
!plugins.includes('./dist/plugins/std/index.js')) {
plugins.unshift('./dist/plugins/std/index.js'); // Add std plugin first
}
config = {
plugins: plugins,
watchMode: options.watch || false,
autoReload: true,
logLevel: options.logLevel || 'info',
outputDir: options.outputDir || './output',
};
}
else {
// Determine which plugins to load
let plugins = [];
// Load std plugin by default unless --disable-std is specified
if (!options.disableStd) {
plugins.push('./dist/plugins/std/index.js');
}
// Add any additional plugins specified
if (options.plugins) {
plugins.push(...options.plugins);
}
config = {
plugins: plugins,
watchMode: options.watch || false,
autoReload: true,
logLevel: options.logLevel || 'info',
outputDir: options.outputDir || './output',
};
}
// Initialize runner
await runner.initialize(config);
if (options.watch) {
// Watch directory mode
const dirPath = (0, path_1.resolve)(inputPath, '..');
await runner.watchDirectory(dirPath);
// Handle graceful shutdown
process.on('SIGINT', async () => {
console.log('\nš Shutting down...');
await runner.stop();
process.exit(0);
});
}
else {
// Single file mode
await runner.runFile(inputPath);
await runner.stop();
}
}
catch (error) {
console.error(`Execution error: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
}
});
program
.command('analyze')
.description('Analyze a Concept language file and show statistics')
.argument('<input>', 'Input .concept file')
.action((input) => {
try {
const inputPath = (0, path_1.resolve)(input);
if (!(0, fs_1.existsSync)(inputPath)) {
console.error(`Error: Input file '${inputPath}' does not exist`);
process.exit(1);
}
const source = (0, fs_1.readFileSync)(inputPath, 'utf-8');
const compiler = new compiler_1.Compiler();
const block = compiler.compileToState(source);
const stats = block.blockExplorer.getStats();
console.log('\n=== Concept File Analysis ===');
console.log(`Concepts: ${stats.conceptCount}`);
console.log(`Pairs: ${stats.pairCount}`);
console.log(`Relations: ${stats.dataCount}`);
console.log(`Avg relations per concept: ${stats.averageRelationsPerConcept.toFixed(2)}`);
console.log('\n=== Concepts ===');
block.concepts.forEach((concept) => {
console.log(`- ${concept.name}`);
});
console.log('\n=== Relationships ===');
block.chain.forEach((data) => {
const relation = data.value ? 'is' : 'isnt';
console.log(`${data.pair.conceptA.name} ${relation} ${data.pair.conceptB.name}`);
});
}
catch (error) {
console.error(`Analysis error: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
}
});
program
.command('create-plugin')
.description('Create a new plugin template')
.argument('<name>', 'Plugin name')
.option('-d, --dir <directory>', 'Output directory', './plugins')
.action((name, options) => {
try {
const { ConceptPluginManager } = require('../core/plugin-manager');
ConceptPluginManager.createPluginTemplate(name, options.dir);
console.log(`ā
Plugin template created: ${name}`);
console.log(`š Location: ${(0, path_1.resolve)(options.dir, name)}`);
console.log('\nNext steps:');
console.log('1. cd into the plugin directory');
console.log('2. npm install');
console.log('3. npm run build');
console.log('4. Use the plugin with: concept run --plugins ./path/to/plugin your-file.concept');
}
catch (error) {
console.error(`Plugin creation error: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
}
});
program
.command('list-plugins')
.description('List available plugins')
.option('-d, --dir <directory>', 'Plugin directory to scan', './plugins')
.action((options) => {
try {
const fs = require('fs');
const path = require('path');
const pluginDir = (0, path_1.resolve)(options.dir);
if (!(0, fs_1.existsSync)(pluginDir)) {
console.log('No plugins directory found');
return;
}
const plugins = fs
.readdirSync(pluginDir, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name);
if (plugins.length === 0) {
console.log('No plugins found');
return;
}
console.log('Available plugins:');
plugins.forEach((plugin) => {
const pluginPath = path.join(pluginDir, plugin);
const packageJsonPath = path.join(pluginPath, 'package.json');
if ((0, fs_1.existsSync)(packageJsonPath)) {
const packageJson = JSON.parse((0, fs_1.readFileSync)(packageJsonPath, 'utf-8'));
console.log(` š¦ ${plugin} v${packageJson.version || 'unknown'}`);
if (packageJson.description) {
console.log(` ${packageJson.description}`);
}
}
else {
console.log(` š¦ ${plugin} (no package.json)`);
}
});
}
catch (error) {
console.error(`Plugin listing error: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
}
});
// Blocks are native to the language - any command can have a block
// We use Shift+Enter to enable block mode for any command
program
.command('repl')
.description('Start an interactive REPL for testing concepts')
.option('-p, --plugins <plugins...>', 'Plugin paths or names to load')
.option('--no-infer', 'Disable automatic inference')
.action(async (options) => {
try {
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: 'concept> ',
});
// Enable raw mode for keypress detection
if (process.stdin.setRawMode) {
process.stdin.setRawMode(true);
}
process.stdin.resume();
// Load plugins if specified
const runner = new concept_runner_1.ConceptRunnerImpl();
const allPlugins = [...(options.plugins || [])];
// Load std plugin by default for REPL
if (!allPlugins.includes('./dist/plugins/std/index.js')) {
allPlugins.unshift('./dist/plugins/std/index.js');
}
const config = {
plugins: allPlugins,
watchMode: false,
autoReload: false,
logLevel: 'info',
outputDir: './repl-output',
};
await runner.initialize(config);
// Use the runner's compiler which has all the hooks
const compiler = runner.getCompiler();
if (!options.infer) {
compiler.block['inferMissingPairs'] = () => { };
}
console.log('š Concept Language REPL');
console.log('Type concept statements and press Enter to execute them.');
console.log('Commands:');
console.log(' .help - Show this help');
console.log(' .clear - Clear the current state');
console.log(' .state - Show current state');
console.log(' .plugins - Show loaded plugins');
console.log(' .quit - Exit the REPL');
console.log('');
console.log('š” Blocks are native to the language - any command can have a block');
console.log(' - Type any command and press Enter to execute normally');
console.log(' - Add indented content on the next line to create a block');
console.log(' - Press Enter on empty line to finish and execute the block');
console.log(' Examples: "user is", "db create users", "file write data.txt", "inspect apple"');
console.log('');
// Multi-line input handling for native blocks
let currentInput = '';
let inBlock = false;
let lastCommand = '';
rl.prompt();
rl.on('line', async (input) => {
const trimmed = input.trim();
// Handle special commands
if (trimmed === '.quit' || trimmed === '.exit') {
console.log('š Goodbye!');
if (runner) {
await runner.stop();
}
rl.close();
return;
}
if (trimmed === '.help') {
console.log('Commands:');
console.log(' .help - Show this help');
console.log(' .clear - Clear the current state');
console.log(' .state - Show current state');
console.log(' .plugins - Show loaded plugins');
console.log(' .quit - Exit the REPL');
console.log('');
console.log('š” Blocks are native to the language - any command can have a block');
console.log(' - Type any command and press Enter to execute normally');
console.log(' - Add indented content on the next line to create a block');
console.log(' - Press Enter on empty line to finish and execute the block');
console.log(' Examples: "user is", "db create users", "file write data.txt", "inspect apple"');
console.log('');
rl.prompt();
return;
}
if (trimmed === '.clear') {
// Reset the compiler state
const newCompiler = new compiler_1.Compiler();
if (!options.infer) {
newCompiler.block['inferMissingPairs'] = () => { };
}
// Replace the current compiler
Object.assign(compiler, newCompiler);
// Reset block state
currentInput = '';
inBlock = false;
lastCommand = '';
rl.setPrompt('concept> ');
console.log('ā
State cleared');
rl.prompt();
return;
}
if (trimmed === '.state') {
const concepts = compiler.block.concepts;
const chain = compiler.block.chain;
console.log('\n=== Current State ===');
console.log(`Concepts: ${concepts.length}`);
console.log(`Relationships: ${chain.length}`);
if (concepts.length > 0) {
console.log('\nConcepts:');
concepts.forEach((concept) => {
// Color code concepts - blue for regular concepts, green for boxed concepts
if (concept.block && concept.block.length > 0) {
console.log(` - \x1b[32m${concept.name}\x1b[0m \x1b[90m(boxed)\x1b[0m`);
}
else {
console.log(` - \x1b[34m${concept.name}\x1b[0m`);
}
});
}
if (chain.length > 0) {
console.log('\nRelationships:');
chain.forEach((data) => {
const relation = data.value ? 'is' : 'isnt';
const relationColor = data.value ? '\x1b[32m' : '\x1b[31m'; // Green for 'is', red for 'isnt'
console.log(` \x1b[34m${data.pair.conceptA.name}\x1b[0m ${relationColor}${relation}\x1b[0m \x1b[34m${data.pair.conceptB.name}\x1b[0m`);
});
}
console.log('');
rl.prompt();
return;
}
if (trimmed === '.plugins') {
if (runner) {
const plugins = runner.getLoadedPlugins();
console.log('\n=== Loaded Plugins ===');
if (plugins.length === 0) {
console.log('No plugins loaded');
}
else {
plugins.forEach((plugin) => {
console.log(` - ${plugin}`);
});
}
}
else {
console.log('No plugins loaded');
}
console.log('');
rl.prompt();
return;
}
// Handle multi-line input for native blocks
if (trimmed === '') {
if (inBlock && currentInput.trim() !== '') {
// End of block - process the accumulated input
try {
// Use the compiler directly instead of creating temporary files
compiler.compile(currentInput);
}
catch (error) {
console.error(`ā Error: ${error instanceof Error ? error.message : String(error)}`);
}
currentInput = '';
inBlock = false;
rl.setPrompt('concept> ');
}
else if (lastCommand && !inBlock) {
// Command that could have a block but no indented content - process it now
try {
compiler.compile(lastCommand);
}
catch (error) {
console.error(`ā Error: ${error instanceof Error ? error.message : String(error)}`);
}
lastCommand = '';
}
rl.prompt();
return;
}
// If we're in a block, add this line
if (inBlock) {
currentInput += '\n' + input;
rl.prompt();
return;
}
// Check if this is an indented line (should be part of a block)
const indent = input.length - input.trimStart().length;
if (!inBlock && indent > 0 && trimmed !== '') {
// This is an indented line - process it as a standalone boxed concept
if (lastCommand) {
// If there's a last command, treat it as a command with a block
currentInput = lastCommand + '\n' + input;
inBlock = true;
console.log('DEBUG: Created block, currentInput:', currentInput);
rl.prompt();
return;
}
else {
// Standalone indented content - process it directly
try {
compiler.compile(input);
}
catch (error) {
console.error(`ā Error: ${error instanceof Error ? error.message : String(error)}`);
}
rl.prompt();
return;
}
}
// Process any pending blocks first
if (inBlock && currentInput.trim() !== '') {
// We were in block mode but now have a non-indented command - process the block
try {
compiler.compile(currentInput);
}
catch (error) {
console.error(`ā Error: ${error instanceof Error ? error.message : String(error)}`);
}
currentInput = '';
inBlock = false;
lastCommand = '';
}
// Process single-line commands normally
if (!inBlock && trimmed !== '') {
// First, process any pending block if we have one
if (currentInput.trim() !== '') {
try {
compiler.compile(currentInput);
}
catch (error) {
console.error(`ā Error: ${error instanceof Error ? error.message : String(error)}`);
}
currentInput = '';
lastCommand = '';
}
// Store the last command for potential block usage
lastCommand = trimmed;
// Check if this command could have a block (ends with certain keywords)
const couldHaveBlock = trimmed.endsWith(' is') ||
trimmed.endsWith(' create') ||
trimmed.endsWith(' write') ||
trimmed.endsWith(' read') ||
trimmed.endsWith(' update') ||
trimmed.endsWith(' delete') ||
trimmed.endsWith(' say') ||
trimmed.endsWith(' print') ||
trimmed.endsWith(' echo');
if (couldHaveBlock) {
// Don't process immediately - wait to see if next line is indented
rl.prompt();
return;
}
else {
// Process immediately for commands that don't typically have blocks
try {
compiler.compile(trimmed);
}
catch (error) {
console.error(`ā Error: ${error instanceof Error ? error.message : String(error)}`);
}
rl.prompt();
return;
}
}
rl.prompt();
});
rl.on('close', async () => {
if (runner) {
await runner.stop();
}
process.exit(0);
});
}
catch (error) {
console.error(`REPL error: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
}
});
// Default command (backward compatibility)
program
.argument('[input]', 'Input file (default: input.concept)')
.argument('[output]', 'Output file (default: output.concept)')
.action((input = 'input.concept', output = 'output.concept') => {
try {
const inputPath = (0, path_1.resolve)(input);
const outputPath = (0, path_1.resolve)(output);
if (!(0, fs_1.existsSync)(inputPath)) {
console.error(`Error: Input file '${inputPath}' does not exist`);
process.exit(1);
}
const source = (0, fs_1.readFileSync)(inputPath, 'utf-8');
const compiler = new compiler_1.Compiler();
const result = compiler.compile(source);
(0, fs_1.writeFileSync)(outputPath, result, 'utf-8');
console.log(`Compiled: ${inputPath} -> ${outputPath}`);
}
catch (error) {
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
}
});
program.parse();
//# sourceMappingURL=index.js.map
;