diff options
Diffstat (limited to 'tree-sitter/dsk/dsk-cli/src/commands/new.ts')
-rw-r--r-- | tree-sitter/dsk/dsk-cli/src/commands/new.ts | 485 |
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!`)); +} |