about summary refs log tree commit diff stats
path: root/tree-sitter/dsk/dsk-cli/src/commands/new.ts
diff options
context:
space:
mode:
Diffstat (limited to 'tree-sitter/dsk/dsk-cli/src/commands/new.ts')
-rw-r--r--tree-sitter/dsk/dsk-cli/src/commands/new.ts485
1 files changed, 485 insertions, 0 deletions
diff --git a/tree-sitter/dsk/dsk-cli/src/commands/new.ts b/tree-sitter/dsk/dsk-cli/src/commands/new.ts
new file mode 100644
index 0000000..1829a21
--- /dev/null
+++ b/tree-sitter/dsk/dsk-cli/src/commands/new.ts
@@ -0,0 +1,485 @@
+/**
+ * New Command - Interactive Grammar Scaffolding
+ * 
+ * Creates a new DSL project with paradigm-aware grammar generation
+ */
+
+import { Command } from 'commander';
+import inquirer from 'inquirer';
+import chalk from 'chalk';
+import { existsSync, mkdirSync, writeFileSync } from 'fs';
+import { join, dirname } from 'path';
+import { fileURLToPath } from 'url';
+import { inferPattern, validatePattern, generateCustomPattern } from '../utils/inference.js';
+import { generateGrammar, generateGrammarFile } from '../utils/grammar-generator.js';
+import { processTemplate } from '../utils/template-processor.js';
+
+// Type definitions for our interactive flow
+export interface LanguageArchitecture {
+  name: string;
+  purpose: string;
+  paradigm: 'functional' | 'object-oriented' | 'procedural' | 'declarative' | 'mixed';
+  dataPhilosophy: 'immutable' | 'mutable' | 'mixed';
+}
+
+export interface LanguageFeatures {
+  controlFlow: string[];
+  dataStructures: string[];
+  functionTypes: string[];
+}
+
+export interface LanguageSyntax {
+  comments: { type: string; pattern: string };
+  identifiers: { pattern: string; examples: string[] };
+  numbers: { pattern: string; examples: string[] };
+  strings: { pattern: string; examples: string[] };
+  variables: { keyword: string; operator: string; terminator: string; example: string };
+  paradigmExamples: { [key: string]: string };
+}
+
+/**
+ * Create the new command
+ */
+export function createNewCommand(): Command {
+  const newCommand = new Command('new');
+  
+  newCommand
+    .description('Create a new DSL project')
+    .argument('<name>', 'Name of the DSL project')
+    .option('-i, --interactive', 'Use interactive grammar scaffolding')
+    .option('-t, --template <template>', 'Use a specific template (default: basic)')
+    .action(async (name: string, options) => {
+      console.log(chalk.blue('🚀 Creating DSL project:'), chalk.bold(name));
+      
+      if (options.interactive) {
+        await runInteractiveFlow(name);
+      } else {
+        await createBasicProject(name, options.template || 'basic');
+      }
+    });
+
+  return newCommand;
+}
+
+/**
+ * Run the interactive grammar scaffolding flow
+ */
+async function runInteractiveFlow(projectName: string): Promise<void> {
+  console.log();
+  console.log(chalk.green('🎯 Welcome to the DSK Grammar Scaffolder!'));
+  console.log(chalk.gray('I\'ll ask you a few questions about your new language.'));
+  console.log(chalk.gray('Just provide examples, and I\'ll build a starter grammar for you.'));
+  console.log();
+
+  try {
+    // Phase A: Language Architecture & Paradigm
+    console.log(chalk.blue('📋 Phase A: Language Architecture & Paradigm'));
+    const architecture = await gatherArchitecture(projectName);
+    console.log(chalk.green('✔'), 'Architecture defined');
+
+    // Phase B: Core Language Features  
+    console.log();
+    console.log(chalk.blue('🔧 Phase B: Core Language Features'));
+    const features = await gatherFeatures(architecture);
+    console.log(chalk.green('✔'), 'Features defined');
+
+    // Phase C: Syntax & Tokens
+    console.log();
+    console.log(chalk.blue('🔤 Phase C: Syntax & Tokens'));
+    const syntax = await gatherSyntax(architecture, features);
+    console.log(chalk.green('✔'), 'Syntax defined');
+
+    // Generate the project
+    console.log();
+    console.log(chalk.blue('🏗️  Generating project...'));
+    await generateProject(projectName, architecture, features, syntax);
+    
+    // Success message
+    console.log();
+    console.log(chalk.green('🎉 All done!'));
+    console.log(`Your ${chalk.bold('grammar.js')} has been created with rules for:`);
+    console.log(`  ${chalk.gray('•')} Comments, Identifiers, Numbers, Strings`);
+    console.log(`  ${chalk.gray('•')} Variable Declarations`);
+    console.log(`  ${chalk.gray('•')} ${architecture.paradigm} language constructs`);
+    console.log();
+    console.log(chalk.yellow('To start editing and testing, run:'));
+    console.log(`  ${chalk.cyan('cd')} ${projectName}`);
+    console.log(`  ${chalk.cyan('dsk dev')}`);
+    console.log();
+
+  } catch (error) {
+    console.error(chalk.red('❌ Error during interactive flow:'), error);
+    process.exit(1);
+  }
+}
+
+/**
+ * Phase A: Gather language architecture information
+ */
+async function gatherArchitecture(projectName: string): Promise<LanguageArchitecture> {
+  const purposeAnswer = await inquirer.prompt({
+    type: 'input',
+    name: 'purpose',
+    message: 'What is your language designed for? (e.g., configuration, scripting, domain modeling)',
+    default: 'General purpose scripting'
+  });
+
+  const paradigmAnswer = await inquirer.prompt({
+    type: 'list',
+    name: 'paradigm',
+    message: 'What programming style does your language follow?',
+    choices: [
+      { name: 'Functional (immutable data, functions as first-class)', value: 'functional' },
+      { name: 'Object-Oriented (classes, inheritance, methods)', value: 'object-oriented' },
+      { name: 'Procedural (step-by-step instructions, functions)', value: 'procedural' },
+      { name: 'Declarative (describe what, not how)', value: 'declarative' },
+      { name: 'Mixed (combination of above)', value: 'mixed' }
+    ],
+    default: 'procedural'
+  });
+
+  const dataAnswer = await inquirer.prompt({
+    type: 'list',
+    name: 'dataPhilosophy',
+    message: 'How does your language handle data?',
+    choices: [
+      { name: 'Immutable by default (functional style)', value: 'immutable' },
+      { name: 'Mutable variables (imperative style)', value: 'mutable' },
+      { name: 'Mixed approach', value: 'mixed' }
+    ],
+    default: 'mutable'
+  });
+
+  return {
+    name: projectName,
+    purpose: purposeAnswer.purpose,
+    paradigm: paradigmAnswer.paradigm,
+    dataPhilosophy: dataAnswer.dataPhilosophy
+  };
+}
+
+/**
+ * Phase B: Gather core language features
+ */
+async function gatherFeatures(architecture: LanguageArchitecture): Promise<LanguageFeatures> {
+  const controlFlowAnswer = await inquirer.prompt({
+    type: 'checkbox',
+    name: 'controlFlow',
+    message: 'What control structures does your language support?',
+    choices: [
+      { name: 'Conditionals (if/else)', value: 'conditionals', checked: true },
+      { name: 'Loops (for, while)', value: 'loops' },
+      { name: 'Pattern matching', value: 'pattern_matching' },
+      { name: 'Exception handling (try/catch)', value: 'exceptions' },
+      { name: 'Early returns/breaks', value: 'early_returns' }
+    ]
+  });
+
+  const dataStructuresAnswer = await inquirer.prompt({
+    type: 'checkbox', 
+    name: 'dataStructures',
+    message: 'What built-in data structures does your language have?',
+    choices: [
+      { name: 'Arrays/Lists: [1, 2, 3]', value: 'arrays', checked: true },
+      { name: 'Objects/Maps: {key: value}', value: 'objects' },
+      { name: 'Tuples: (a, b, c)', value: 'tuples' },
+      { name: 'Sets: {1, 2, 3}', value: 'sets' }
+    ]
+  });
+
+  const functionTypesAnswer = await inquirer.prompt({
+    type: 'checkbox',
+    name: 'functionTypes',
+    message: 'How are functions defined in your language?',
+    choices: [
+      { name: 'Named functions: function foo() { ... }', value: 'named', checked: true },
+      { name: 'Anonymous functions: (x) => x + 1', value: 'anonymous' },
+      { name: 'Methods on objects: obj.method()', value: 'methods' },
+      { name: 'First-class functions (can be passed around)', value: 'first_class' }
+    ]
+  });
+
+  return {
+    controlFlow: controlFlowAnswer.controlFlow,
+    dataStructures: dataStructuresAnswer.dataStructures,
+    functionTypes: functionTypesAnswer.functionTypes
+  };
+}
+
+/**
+ * Phase C: Gather syntax and token information
+ */
+async function gatherSyntax(architecture: LanguageArchitecture, features: LanguageFeatures): Promise<LanguageSyntax> {
+  const syntax: Partial<LanguageSyntax> = {};
+
+  // Comments
+  const commentAnswer = await inquirer.prompt({
+    type: 'input',
+    name: 'comment',
+    message: 'How do you write a single-line comment? (e.g., //, #, --, ;)',
+    default: '//'
+  });
+
+  const commentPrefix = String(commentAnswer.comment).trim().split(/\s+/)[0];
+  syntax.comments = {
+    type: 'line_comment',
+    pattern: commentPrefix
+  };
+
+  // Identifiers with inference
+  syntax.identifiers = await gatherTokenWithInference(
+    'identifier',
+    'Provide 3-5 examples of valid identifiers',
+    'Now provide 2-3 examples of invalid identifiers (optional)',
+    ['myVar', 'userName', '_private']
+  );
+
+  // Numbers with inference
+  syntax.numbers = await gatherTokenWithInference(
+    'number',
+    'Provide examples of numbers in your language',
+    'Provide examples of invalid numbers (optional)',
+    ['42', '3.14', '-17']
+  );
+
+  // Strings with inference (treat input as a single example, not space-split)
+  const stringValid = await inquirer.prompt({
+    type: 'input',
+    name: 'examples',
+    message: 'Provide an example of a string literal',
+    default: '"hello world"'
+  });
+  const stringInvalid = await inquirer.prompt({
+    type: 'input',
+    name: 'examples',
+    message: 'Provide examples of invalid strings (optional)',
+    default: ''
+  });
+  const stringValidExamples = stringValid.examples ? [String(stringValid.examples)] : ['"hello world"'];
+  const stringInvalidExamples = stringInvalid.examples ? [String(stringInvalid.examples)] : [];
+  const stringResult = inferPattern(stringValidExamples, stringInvalidExamples);
+  if (stringResult.pattern && stringResult.confidence > 0.7) {
+    syntax.strings = { pattern: stringResult.pattern.regex.source, examples: stringValidExamples };
+  } else {
+    syntax.strings = { pattern: '"[^"]*"', examples: stringValidExamples };
+  }
+
+  // Variable declarations
+  const varAnswer = await inquirer.prompt({
+    type: 'input',
+    name: 'example',
+    message: 'Show me how you declare a variable x with value 42 (helps identify keywords)',
+    default: 'let x = 42;'
+  });
+
+  const varParts = parseVariableDeclaration(varAnswer.example);
+  syntax.variables = varParts;
+
+  // Paradigm-specific examples
+  syntax.paradigmExamples = {};
+  
+  if (architecture.paradigm === 'object-oriented' || architecture.paradigm === 'mixed') {
+    const classAnswer = await inquirer.prompt({
+      type: 'input',
+      name: 'classExample',
+      message: 'Show me how you define a class with a method',
+      default: 'class Person { getName() { return this.name; } }'
+    });
+    syntax.paradigmExamples.class = classAnswer.classExample;
+  }
+
+  if (architecture.paradigm === 'functional' || architecture.paradigm === 'mixed') {
+    const funcAnswer = await inquirer.prompt({
+      type: 'input',
+      name: 'funcExample', 
+      message: 'Show me how you define and call a function',
+      default: 'function add(a, b) { return a + b; }'
+    });
+    syntax.paradigmExamples.function = funcAnswer.funcExample;
+  }
+
+  if (architecture.paradigm === 'declarative') {
+    const ruleAnswer = await inquirer.prompt({
+      type: 'input',
+      name: 'ruleExample',
+      message: 'Show me a typical declaration/rule in your language',
+      default: 'rule user_can_edit when user.role == "admin"'
+    });
+    syntax.paradigmExamples.rule = ruleAnswer.ruleExample;
+  }
+
+  return syntax as LanguageSyntax;
+}
+
+/**
+ * Gather token information with automatic pattern inference
+ */
+async function gatherTokenWithInference(
+  tokenType: string,
+  validPrompt: string,
+  invalidPrompt: string,
+  defaultExamples: string[]
+): Promise<{ pattern: string; examples: string[] }> {
+  
+  // Get valid examples
+  const validAnswer = await inquirer.prompt({
+    type: 'input',
+    name: 'examples',
+    message: `${validPrompt} (separate with spaces)`,
+    default: defaultExamples.join(' ')
+  });
+
+  const validExamples = validAnswer.examples.split(/\s+/).filter((ex: string) => ex.length > 0);
+
+  // Get invalid examples (optional)
+  const invalidAnswer = await inquirer.prompt({
+    type: 'input',
+    name: 'examples',
+    message: `${invalidPrompt} (separate with spaces, or press Enter to skip)`,
+    default: ''
+  });
+
+  const invalidExamples = invalidAnswer.examples 
+    ? invalidAnswer.examples.split(/\s+/).filter((ex: string) => ex.length > 0)
+    : [];
+
+  // Try inference
+  const result = inferPattern(validExamples, invalidExamples);
+  
+  if (result.pattern && result.confidence > 0.7) {
+    // Successful inference - confirm with user
+    const confirmAnswer = await inquirer.prompt({
+      type: 'confirm',
+      name: 'confirmed',
+      message: `I've inferred the pattern for ${tokenType} as: ${chalk.cyan(result.pattern.regex.source)}. Does this look correct?`,
+      default: true
+    });
+
+    if (confirmAnswer.confirmed) {
+      return {
+        pattern: result.pattern.regex.source,
+        examples: validExamples
+      };
+    }
+  }
+
+  // Inference failed or user rejected - offer alternatives
+  console.log(chalk.yellow(`I couldn't determine a reliable pattern from those examples.`));
+  
+  const fallbackAnswer = await inquirer.prompt({
+    type: 'list',
+    name: 'option',
+    message: 'How would you like to proceed?',
+    choices: [
+      { name: 'Provide a custom regular expression', value: 'custom_regex' },
+      { name: 'Generate a simple pattern from examples', value: 'simple_pattern' },
+      { name: 'Try different examples', value: 'retry' }
+    ]
+  });
+
+  if (fallbackAnswer.option === 'custom_regex') {
+    const regexAnswer = await inquirer.prompt({
+      type: 'input',
+      name: 'regex',
+      message: `Enter a regular expression for ${tokenType}:`,
+      validate: (input: string) => {
+        const validation = validatePattern(input, validExamples, invalidExamples);
+        return validation.isValid || validation.errors.join(', ');
+      }
+    });
+    
+    return {
+      pattern: regexAnswer.regex,
+      examples: validExamples
+    };
+  }
+
+  if (fallbackAnswer.option === 'simple_pattern') {
+    const simplePattern = generateCustomPattern(validExamples, invalidExamples);
+    return {
+      pattern: simplePattern,
+      examples: validExamples
+    };
+  }
+
+  // Retry with different examples
+  return await gatherTokenWithInference(tokenType, validPrompt, invalidPrompt, defaultExamples);
+}
+
+/**
+ * Parse variable declaration to extract components
+ */
+function parseVariableDeclaration(example: string): { keyword: string; operator: string; terminator: string; example: string } {
+  // Simple parsing - look for common patterns
+  const patterns = [
+    /^(\w+)\s+(\w+)\s*([=:])\s*[^;]*([;]?)/, // let x = 42;
+    /^(\w+)\s*([=:])\s*[^;]*([;]?)/, // x = 42;
+  ];
+
+  for (const pattern of patterns) {
+    const match = example.match(pattern);
+    if (match) {
+      return {
+        keyword: match[1] || '',
+        operator: match[3] || '=',
+        terminator: match[4] || ';',
+        example
+      };
+    }
+  }
+
+  // Fallback
+  return {
+    keyword: 'let',
+    operator: '=', 
+    terminator: ';',
+    example
+  };
+}
+
+/**
+ * Generate the project with collected information
+ */
+async function generateProject(
+  name: string,
+  architecture: LanguageArchitecture,
+  features: LanguageFeatures,
+  syntax: LanguageSyntax
+): Promise<void> {
+  // Create project directory
+  if (existsSync(name)) {
+    throw new Error(`Directory ${name} already exists`);
+  }
+
+  mkdirSync(name, { recursive: true });
+
+  // Generate grammar.js file
+  console.log(chalk.gray('🔧 Generating grammar.js...'));
+  const grammar = generateGrammar(architecture, features, syntax);
+  const grammarContent = generateGrammarFile(grammar);
+  writeFileSync(join(name, 'grammar.js'), grammarContent, 'utf-8');
+
+  // Process template files
+  console.log(chalk.gray('📋 Creating project structure...'));
+  const __filename = fileURLToPath(import.meta.url);
+  const __dirname = dirname(__filename);
+  const templateDir = join(__dirname, '..', '..', 'templates', 'default');
+  
+  const templateContext = {
+    architecture,
+    features,
+    syntax
+  };
+  
+  processTemplate(templateDir, name, templateContext);
+  
+  console.log(chalk.gray(`📁 Created project directory: ${name}/`));
+  console.log(chalk.green('✨ Project generated successfully!'));
+}
+
+/**
+ * Create a basic project without interactive flow
+ */
+async function createBasicProject(name: string, template: string): Promise<void> {
+  console.log(chalk.yellow(`🚧 Basic project creation (template: ${template}) coming soon!`));
+}